', esc_attr( $identifier ), esc_attr( $class ) );
+
+ $tab_filter_name = sprintf( '%s_%s', $option_tabs->get_base(), $tab->get_name() );
+
+ /**
+ * Allows to override the content that is display on the specific option tab.
+ *
+ * @internal For internal Yoast SEO use only.
+ *
+ * @param string|null $tab_contents The content that should be displayed for this tab. Leave empty for default behaviour.
+ * @param WPSEO_Option_Tabs $option_tabs The registered option tabs.
+ * @param WPSEO_Option_Tab $tab The tab that is being displayed.
+ */
+ $option_tab_content = apply_filters( 'wpseo_option_tab-' . $tab_filter_name, null, $option_tabs, $tab );
+ if ( ! empty( $option_tab_content ) ) {
+ echo wp_kses_post( $option_tab_content );
+ }
+
+ if ( empty( $option_tab_content ) ) {
+ // Output the settings view for all tabs.
+ $tab_view = $this->get_tab_view( $option_tabs, $tab );
+
+ if ( is_file( $tab_view ) ) {
+ $yform = Yoast_Form::get_instance();
+ require $tab_view;
+ }
+ }
+
+ echo '
';
+ }
+ }
+}
diff --git a/wp-content/plugins/wordpress-seo/admin/class-option-tabs.php b/wp-content/plugins/wordpress-seo/admin/class-option-tabs.php
new file mode 100755
index 00000000..fb0c4512
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/class-option-tabs.php
@@ -0,0 +1,124 @@
+base = sanitize_title( $base );
+
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
+ $tab = isset( $_GET['tab'] ) && is_string( $_GET['tab'] ) ? sanitize_text_field( wp_unslash( $_GET['tab'] ) ) : '';
+ $this->active_tab = empty( $tab ) ? $active_tab : $tab;
+ }
+
+ /**
+ * Get the base.
+ *
+ * @return string
+ */
+ public function get_base() {
+ return $this->base;
+ }
+
+ /**
+ * Add a tab.
+ *
+ * @param WPSEO_Option_Tab $tab Tab to add.
+ *
+ * @return $this
+ */
+ public function add_tab( WPSEO_Option_Tab $tab ) {
+ $this->tabs[] = $tab;
+
+ return $this;
+ }
+
+ /**
+ * Get active tab.
+ *
+ * @return WPSEO_Option_Tab|null Get the active tab.
+ */
+ public function get_active_tab() {
+ if ( empty( $this->active_tab ) ) {
+ return null;
+ }
+
+ $active_tabs = array_filter( $this->tabs, [ $this, 'is_active_tab' ] );
+ if ( ! empty( $active_tabs ) ) {
+ $active_tabs = array_values( $active_tabs );
+ if ( count( $active_tabs ) === 1 ) {
+ return $active_tabs[0];
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Is the tab the active tab.
+ *
+ * @param WPSEO_Option_Tab $tab Tab to check for active tab.
+ *
+ * @return bool
+ */
+ public function is_active_tab( WPSEO_Option_Tab $tab ) {
+ return ( $tab->get_name() === $this->active_tab );
+ }
+
+ /**
+ * Get all tabs.
+ *
+ * @return WPSEO_Option_Tab[]
+ */
+ public function get_tabs() {
+ return $this->tabs;
+ }
+
+ /**
+ * Display the tabs.
+ *
+ * @param Yoast_Form $yform Yoast Form needed in the views.
+ *
+ * @return void
+ */
+ public function display( Yoast_Form $yform ) {
+ $formatter = new WPSEO_Option_Tabs_Formatter();
+ $formatter->run( $this, $yform );
+ }
+}
diff --git a/wp-content/plugins/wordpress-seo/admin/class-paper-presenter.php b/wp-content/plugins/wordpress-seo/admin/class-paper-presenter.php
new file mode 100755
index 00000000..99550e4a
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/class-paper-presenter.php
@@ -0,0 +1,141 @@
+ null,
+ 'paper_id_prefix' => 'wpseo-',
+ 'collapsible' => false,
+ 'collapsible_header_class' => '',
+ 'expanded' => false,
+ 'help_text' => '',
+ 'title_after' => '',
+ 'class' => '',
+ 'content' => '',
+ 'view_data' => [],
+ ];
+
+ $this->settings = wp_parse_args( $settings, $defaults );
+ $this->title = $title;
+ $this->view_file = $view_file;
+ }
+
+ /**
+ * Renders the collapsible paper and returns it as a string.
+ *
+ * @return string The rendered paper.
+ */
+ public function get_output() {
+ $view_variables = $this->get_view_variables();
+
+ extract( $view_variables, EXTR_SKIP );
+
+ $content = $this->settings['content'];
+
+ if ( $this->view_file !== null ) {
+ ob_start();
+ require $this->view_file;
+ $content = ob_get_clean();
+ }
+
+ ob_start();
+ require WPSEO_PATH . 'admin/views/paper-collapsible.php';
+ $rendered_output = ob_get_clean();
+
+ return $rendered_output;
+ }
+
+ /**
+ * Retrieves the view variables.
+ *
+ * @return array The view variables.
+ */
+ private function get_view_variables() {
+ if ( $this->settings['help_text'] instanceof WPSEO_Admin_Help_Panel === false ) {
+ $this->settings['help_text'] = new WPSEO_Admin_Help_Panel( '', '', '' );
+ }
+
+ $view_variables = [
+ 'class' => $this->settings['class'],
+ 'collapsible' => $this->settings['collapsible'],
+ 'collapsible_config' => $this->collapsible_config(),
+ 'collapsible_header_class' => $this->settings['collapsible_header_class'],
+ 'title_after' => $this->settings['title_after'],
+ 'help_text' => $this->settings['help_text'],
+ 'view_file' => $this->view_file,
+ 'title' => $this->title,
+ 'paper_id' => $this->settings['paper_id'],
+ 'paper_id_prefix' => $this->settings['paper_id_prefix'],
+ 'yform' => Yoast_Form::get_instance(),
+ ];
+
+ return array_merge( $this->settings['view_data'], $view_variables );
+ }
+
+ /**
+ * Retrieves the collapsible config based on the settings.
+ *
+ * @return array The config.
+ */
+ protected function collapsible_config() {
+ if ( empty( $this->settings['collapsible'] ) ) {
+ return [
+ 'toggle_icon' => '',
+ 'class' => '',
+ 'expanded' => '',
+ ];
+ }
+
+ if ( ! empty( $this->settings['expanded'] ) ) {
+ return [
+ 'toggle_icon' => 'dashicons-arrow-up-alt2',
+ 'class' => 'toggleable-container',
+ 'expanded' => 'true',
+ ];
+ }
+
+ return [
+ 'toggle_icon' => 'dashicons-arrow-down-alt2',
+ 'class' => 'toggleable-container toggleable-container-hidden',
+ 'expanded' => 'false',
+ ];
+ }
+}
diff --git a/wp-content/plugins/wordpress-seo/admin/class-plugin-availability.php b/wp-content/plugins/wordpress-seo/admin/class-plugin-availability.php
new file mode 100755
index 00000000..8ed94914
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/class-plugin-availability.php
@@ -0,0 +1,357 @@
+register_yoast_plugins();
+ $this->register_yoast_plugins_status();
+ }
+
+ /**
+ * Registers all the available Yoast SEO plugins.
+ *
+ * @return void
+ */
+ protected function register_yoast_plugins() {
+ $this->plugins = [
+ 'yoast-seo-premium' => [
+ 'url' => WPSEO_Shortlinker::get( 'https://yoa.st/1y7' ),
+ 'title' => 'Yoast SEO Premium',
+ 'description' => sprintf(
+ /* translators: %1$s expands to Yoast SEO */
+ __( 'The premium version of %1$s with more features & support.', 'wordpress-seo' ),
+ 'Yoast SEO'
+ ),
+ 'installed' => false,
+ 'slug' => 'wordpress-seo-premium/wp-seo-premium.php',
+ 'version_sync' => true,
+ 'premium' => true,
+ ],
+
+ 'video-seo-for-wordpress-seo-by-yoast' => [
+ 'url' => WPSEO_Shortlinker::get( 'https://yoa.st/1y8' ),
+ 'title' => 'Video SEO',
+ 'description' => __( 'Optimize your videos to show them off in search results and get more clicks!', 'wordpress-seo' ),
+ 'installed' => false,
+ 'slug' => 'wpseo-video/video-seo.php',
+ 'version_sync' => true,
+ 'premium' => true,
+ ],
+
+ 'yoast-news-seo' => [
+ 'url' => WPSEO_Shortlinker::get( 'https://yoa.st/1y9' ),
+ 'title' => 'News SEO',
+ 'description' => __( 'Are you in Google News? Increase your traffic from Google News by optimizing for it!', 'wordpress-seo' ),
+ 'installed' => false,
+ 'slug' => 'wpseo-news/wpseo-news.php',
+ 'version_sync' => true,
+ 'premium' => true,
+ ],
+
+ 'local-seo-for-yoast-seo' => [
+ 'url' => WPSEO_Shortlinker::get( 'https://yoa.st/1ya' ),
+ 'title' => 'Local SEO',
+ 'description' => __( 'Rank better locally and in Google Maps, without breaking a sweat!', 'wordpress-seo' ),
+ 'installed' => false,
+ 'slug' => 'wordpress-seo-local/local-seo.php',
+ 'version_sync' => true,
+ 'premium' => true,
+ ],
+
+ 'yoast-woocommerce-seo' => [
+ 'url' => WPSEO_Shortlinker::get( 'https://yoa.st/1o0' ),
+ 'title' => 'Yoast WooCommerce SEO',
+ 'description' => sprintf(
+ /* translators: %1$s expands to Yoast SEO */
+ __( 'Seamlessly integrate WooCommerce with %1$s and get extra features!', 'wordpress-seo' ),
+ 'Yoast SEO'
+ ),
+ '_dependencies' => [
+ 'WooCommerce' => [
+ 'slug' => 'woocommerce/woocommerce.php', // Kept for backwards compatibility, in case external code uses get_dependencies(). Deprecated in 22.4.
+ 'conditional' => new WooCommerce_Conditional(),
+ ],
+ ],
+ 'installed' => false,
+ 'slug' => 'wpseo-woocommerce/wpseo-woocommerce.php',
+ 'version_sync' => true,
+ 'premium' => true,
+ ],
+ ];
+ }
+
+ /**
+ * Sets certain plugin properties based on WordPress' status.
+ *
+ * @return void
+ */
+ protected function register_yoast_plugins_status() {
+
+ foreach ( $this->plugins as $name => $plugin ) {
+
+ $plugin_slug = $plugin['slug'];
+ $plugin_path = WP_PLUGIN_DIR . '/' . $plugin_slug;
+
+ if ( file_exists( $plugin_path ) ) {
+ $plugin_data = get_plugin_data( $plugin_path, false, false );
+ $this->plugins[ $name ]['installed'] = true;
+ $this->plugins[ $name ]['version'] = $plugin_data['Version'];
+ $this->plugins[ $name ]['active'] = is_plugin_active( $plugin_slug );
+ }
+ }
+ }
+
+ /**
+ * Checks if there are dependencies available for the plugin.
+ *
+ * @param array $plugin The information available about the plugin.
+ *
+ * @return bool Whether there is a dependency present.
+ */
+ public function has_dependencies( $plugin ) {
+ return ( isset( $plugin['_dependencies'] ) && ! empty( $plugin['_dependencies'] ) );
+ }
+
+ /**
+ * Gets the dependencies for the plugin.
+ *
+ * @param array $plugin The information available about the plugin.
+ *
+ * @return array Array containing all the dependencies associated with the plugin.
+ */
+ public function get_dependencies( $plugin ) {
+ if ( ! $this->has_dependencies( $plugin ) ) {
+ return [];
+ }
+
+ return $plugin['_dependencies'];
+ }
+
+ /**
+ * Checks if all dependencies are satisfied.
+ *
+ * @param array $plugin The information available about the plugin.
+ *
+ * @return bool Whether or not the dependencies are satisfied.
+ */
+ public function dependencies_are_satisfied( $plugin ) {
+ if ( ! $this->has_dependencies( $plugin ) ) {
+ return true;
+ }
+
+ $dependencies = $this->get_dependencies( $plugin );
+ $active_dependencies = array_filter( $dependencies, [ $this, 'is_dependency_active' ] );
+
+ return count( $active_dependencies ) === count( $dependencies );
+ }
+
+ /**
+ * Checks whether or not one of the plugins is properly installed and usable.
+ *
+ * @param array $plugin The information available about the plugin.
+ *
+ * @return bool Whether or not the plugin is properly installed.
+ */
+ public function is_installed( $plugin ) {
+ if ( empty( $plugin ) ) {
+ return false;
+ }
+
+ return $this->is_available( $plugin );
+ }
+
+ /**
+ * Checks for the availability of the plugin.
+ *
+ * @param array $plugin The information available about the plugin.
+ *
+ * @return bool Whether or not the plugin is available.
+ */
+ public function is_available( $plugin ) {
+ return isset( $plugin['installed'] ) && $plugin['installed'] === true;
+ }
+
+ /**
+ * Checks whether a dependency is active.
+ *
+ * @param array $dependency The information about the dependency to look for.
+ *
+ * @return bool Whether or not the dependency is active.
+ */
+ public function is_dependency_active( $dependency ) {
+ return $dependency['conditional']->is_met();
+ }
+
+ /**
+ * Gets an array of plugins that have defined dependencies.
+ *
+ * @return array Array of the plugins that have dependencies.
+ */
+ public function get_plugins_with_dependencies() {
+ return array_filter( $this->plugins, [ $this, 'has_dependencies' ] );
+ }
+
+ /**
+ * Determines whether or not a plugin is active.
+ *
+ * @deprecated 23.4
+ * @codeCoverageIgnore
+ *
+ * @param string $plugin The plugin slug to check.
+ *
+ * @return bool Whether or not the plugin is active.
+ */
+ public function is_active( $plugin ) {
+ _deprecated_function( __METHOD__, 'Yoast SEO 23.4', 'is_plugin_active' );
+
+ return is_plugin_active( $plugin );
+ }
+
+ /**
+ * Gets all the possibly available plugins.
+ *
+ * @deprecated 23.4
+ * @codeCoverageIgnore
+ *
+ * @return array Array containing the information about the plugins.
+ */
+ public function get_plugins() {
+ _deprecated_function( __METHOD__, 'Yoast SEO 23.4', 'WPSEO_Addon_Manager::get_addon_filenames' );
+
+ return $this->plugins;
+ }
+
+ /**
+ * Gets a specific plugin. Returns an empty array if it cannot be found.
+ *
+ * @deprecated 23.4
+ * @codeCoverageIgnore
+ *
+ * @param string $plugin The plugin to search for.
+ *
+ * @return array The plugin properties.
+ */
+ public function get_plugin( $plugin ) { // @phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found, VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- needed for BC reasons
+ _deprecated_function( __METHOD__, 'Yoast SEO 23.4', 'WPSEO_Addon_Manager::get_plugin_file' );
+ if ( ! isset( $this->plugins[ $plugin ] ) ) {
+ return [];
+ }
+
+ return $this->plugins[ $plugin ];
+ }
+
+ /**
+ * Gets the version of the plugin.
+ *
+ * @deprecated 23.4
+ * @codeCoverageIgnore
+ *
+ * @param array $plugin The information available about the plugin.
+ *
+ * @return string The version associated with the plugin.
+ */
+ public function get_version( $plugin ) { // @phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found, VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- needed for BC reasons
+ _deprecated_function( __METHOD__, 'Yoast SEO 23.4', 'WPSEO_Addon_Manager::get_installed_addons_versions' );
+ if ( ! isset( $plugin['version'] ) ) {
+ return '';
+ }
+
+ return $plugin['version'];
+ }
+
+ /**
+ * Checks whether a dependency is available.
+ *
+ * @deprecated 22.4
+ * @codeCoverageIgnore
+ *
+ * @param array $dependency The information about the dependency to look for.
+ *
+ * @return bool Whether or not the dependency is available.
+ */
+ public function is_dependency_available( $dependency ) {
+ _deprecated_function( __METHOD__, 'Yoast SEO 22.4' );
+
+ return isset( get_plugins()[ $dependency['slug'] ] );
+ }
+
+ /**
+ * Gets the names of the dependencies.
+ *
+ * @deprecated 23.4
+ * @codeCoverageIgnore
+ *
+ * @param array $plugin The plugin to get the dependency names from.
+ *
+ * @return array Array containing the names of the associated dependencies.
+ */
+ public function get_dependency_names( $plugin ) { // @phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found, VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- needed for BC reasons
+ _deprecated_function( __METHOD__, 'Yoast SEO 23.4' );
+ if ( ! $this->has_dependencies( $plugin ) ) {
+ return [];
+ }
+
+ return array_keys( $plugin['_dependencies'] );
+ }
+
+ /**
+ * Determines whether or not a plugin is a Premium product.
+ *
+ * @deprecated 23.4
+ * @codeCoverageIgnore
+ *
+ * @param array $plugin The plugin to check.
+ *
+ * @return bool Whether or not the plugin is a Premium product.
+ */
+ public function is_premium( $plugin ) {
+ _deprecated_function( __METHOD__, 'Yoast SEO 23.4' );
+
+ return isset( $plugin['premium'] ) && $plugin['premium'] === true;
+ }
+
+ /**
+ * Gets all installed plugins.
+ *
+ * @deprecated 23.4
+ * @codeCoverageIgnore
+ *
+ * @return array The installed plugins.
+ */
+ public function get_installed_plugins() {
+
+ _deprecated_function( __METHOD__, 'Yoast SEO 23.4', 'WPSEO_Addon_Manager::get_installed_addons_versions' );
+ $installed = [];
+
+ foreach ( $this->plugins as $plugin_key => $plugin ) {
+ if ( $this->is_installed( $plugin ) ) {
+ $installed[ $plugin_key ] = $plugin;
+ }
+ }
+
+ return $installed;
+ }
+}
diff --git a/wp-content/plugins/wordpress-seo/admin/class-plugin-conflict.php b/wp-content/plugins/wordpress-seo/admin/class-plugin-conflict.php
new file mode 100755
index 00000000..a90e8acd
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/class-plugin-conflict.php
@@ -0,0 +1,94 @@
+>
+ */
+ protected $plugins = [
+ // The plugin which are writing OG metadata.
+ 'open_graph' => Conflicting_Plugins::OPEN_GRAPH_PLUGINS,
+ 'xml_sitemaps' => Conflicting_Plugins::XML_SITEMAPS_PLUGINS,
+ 'cloaking' => Conflicting_Plugins::CLOAKING_PLUGINS,
+ 'seo' => Conflicting_Plugins::SEO_PLUGINS,
+ ];
+
+ /**
+ * Overrides instance to set with this class as class.
+ *
+ * @param string $class_name Optional class name.
+ *
+ * @return Yoast_Plugin_Conflict
+ */
+ public static function get_instance( $class_name = self::class ) {
+ return parent::get_instance( $class_name );
+ }
+
+ /**
+ * After activating any plugin, this method will be executed by a hook.
+ *
+ * If the activated plugin is conflicting with ours a notice will be shown.
+ *
+ * @param string|bool $plugin Optional plugin basename to check.
+ *
+ * @return void
+ */
+ public static function hook_check_for_plugin_conflicts( $plugin = false ) {
+ // The instance of the plugin.
+ $instance = self::get_instance();
+
+ // Only add the plugin as an active plugin if $plugin isn't false.
+ if ( $plugin && is_string( $plugin ) ) {
+ $instance->add_active_plugin( $instance->find_plugin_category( $plugin ), $plugin );
+ }
+
+ $plugin_sections = [];
+
+ // Only check for open graph problems when they are enabled.
+ if ( WPSEO_Options::get( 'opengraph' ) ) {
+ /* translators: %1$s expands to Yoast SEO, %2$s: 'Facebook' plugin name of possibly conflicting plugin with regard to creating OpenGraph output. */
+ $plugin_sections['open_graph'] = __( 'Both %1$s and %2$s create Open Graph output, which might make Facebook, X, LinkedIn and other social networks use the wrong texts and images when your pages are being shared.', 'wordpress-seo' )
+ . '
'
+ . ''
+ /* translators: %1$s expands to Yoast SEO. */
+ . sprintf( __( 'Configure %1$s\'s Open Graph settings', 'wordpress-seo' ), 'Yoast SEO' )
+ . '';
+ }
+
+ // Only check for XML conflicts if sitemaps are enabled.
+ if ( WPSEO_Options::get( 'enable_xml_sitemap' ) ) {
+ /* translators: %1$s expands to Yoast SEO, %2$s: 'Google XML Sitemaps' plugin name of possibly conflicting plugin with regard to the creation of sitemaps. */
+ $plugin_sections['xml_sitemaps'] = __( 'Both %1$s and %2$s can create XML sitemaps. Having two XML sitemaps is not beneficial for search engines and might slow down your site.', 'wordpress-seo' )
+ . '
'
+ . ''
+ /* translators: %1$s expands to Yoast SEO. */
+ . sprintf( __( 'Toggle %1$s\'s XML Sitemap', 'wordpress-seo' ), 'Yoast SEO' )
+ . '';
+ }
+
+ /* translators: %2$s expands to 'RS Head Cleaner' plugin name of possibly conflicting plugin with regard to differentiating output between search engines and normal users. */
+ $plugin_sections['cloaking'] = __( 'The plugin %2$s changes your site\'s output and in doing that differentiates between search engines and normal users, a process that\'s called cloaking. We highly recommend that you disable it.', 'wordpress-seo' );
+
+ /* translators: %1$s expands to Yoast SEO, %2$s: 'SEO' plugin name of possibly conflicting plugin with regard to the creation of duplicate SEO meta. */
+ $plugin_sections['seo'] = __( 'Both %1$s and %2$s manage the SEO of your site. Running two SEO plugins at the same time is detrimental.', 'wordpress-seo' );
+
+ $instance->check_plugin_conflicts( $plugin_sections );
+ }
+}
diff --git a/wp-content/plugins/wordpress-seo/admin/class-premium-popup.php b/wp-content/plugins/wordpress-seo/admin/class-premium-popup.php
new file mode 100755
index 00000000..35820291
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/class-premium-popup.php
@@ -0,0 +1,105 @@
+identifier = $identifier;
+ $this->heading_level = $heading_level;
+ $this->title = $title;
+ $this->content = $content;
+ $this->url = $url;
+ }
+
+ /**
+ * Returns the premium popup as an HTML string.
+ *
+ * @param bool $popup Show this message as a popup show it straight away.
+ *
+ * @return string
+ */
+ public function get_premium_message( $popup = true ) {
+ // Don't show in Premium.
+ if ( defined( 'WPSEO_PREMIUM_FILE' ) ) {
+ return '';
+ }
+
+ $assets_uri = trailingslashit( plugin_dir_url( WPSEO_FILE ) );
+
+ /* translators: %s expands to Yoast SEO Premium */
+ $cta_text = esc_html( sprintf( __( 'Get %s', 'wordpress-seo' ), 'Yoast SEO Premium' ) );
+ /* translators: Hidden accessibility text. */
+ $new_tab_message = '' . esc_html__( '(Opens in a new browser tab)', 'wordpress-seo' ) . '';
+ $caret_icon = '';
+ $classes = '';
+ if ( $popup ) {
+ $classes = ' hidden';
+ }
+ $micro_copy = __( '1 year free support and updates included!', 'wordpress-seo' );
+
+ $popup = <<
+
+ <{$this->heading_level} id="wpseo-contact-support-popup-title" class="wpseo-premium-popup-title">{$this->title}{$this->heading_level}>
+ {$this->content}
+
+ {$cta_text} {$new_tab_message} {$caret_icon}
+
+ {$micro_copy}
+
+EO_POPUP;
+
+ return $popup;
+ }
+}
diff --git a/wp-content/plugins/wordpress-seo/admin/class-premium-upsell-admin-block.php b/wp-content/plugins/wordpress-seo/admin/class-premium-upsell-admin-block.php
new file mode 100755
index 00000000..0eb90bca
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/class-premium-upsell-admin-block.php
@@ -0,0 +1,224 @@
+hook = $hook;
+ }
+
+ /**
+ * Registers WordPress hooks.
+ *
+ * @return void
+ */
+ public function register_hooks() {
+ add_action( $this->hook, [ $this, 'render' ] );
+ }
+
+ /**
+ * Renders the upsell block.
+ *
+ * @return void
+ */
+ public function render() {
+
+ $is_woocommerce_active = ( new WooCommerce_Conditional() )->is_met();
+ $url = ( $is_woocommerce_active ) ? WPSEO_Shortlinker::get( 'https://yoa.st/admin-footer-upsell-woocommerce' ) : WPSEO_Shortlinker::get( 'https://yoa.st/17h' );
+
+ [ $header_text, $header_icon ] = $this->get_header( $is_woocommerce_active );
+
+ $arguments = $this->get_arguments( $is_woocommerce_active );
+
+ $now_including = [ 'Local SEO', 'News SEO', 'Video SEO', __( 'Google Docs add-on (1 seat)', 'wordpress-seo' ) ];
+ if ( $is_woocommerce_active ) {
+ array_unshift( $now_including, 'Yoast SEO Premium' );
+ }
+
+ $header_class = ( $is_woocommerce_active ) ? 'woo-header' : '';
+ $arguments_html = implode( '', array_map( [ $this, 'get_argument_html' ], $arguments ) );
+ $badge_class = ( $is_woocommerce_active ) ? 'woo-badge' : '';
+
+ $class = $this->get_html_class();
+
+ /* translators: %s expands to Yoast SEO Premium */
+ $button_text = $this->get_button_text( $is_woocommerce_active );
+ /* translators: Hidden accessibility text. */
+ $button_text .= '' . esc_html__( '(Opens in a new browser tab)', 'wordpress-seo' ) . ''
+ . '';
+
+ $upgrade_button = sprintf(
+ '%3$s',
+ esc_attr( 'wpseo-' . $this->identifier . '-popup-button' ),
+ esc_url( $url ),
+ $button_text
+ );
+
+ echo '
' . PHP_EOL . PHP_EOL;
+ }
+
+ /**
+ * Creates a toggle switch to define whether an indexable should be indexed or not.
+ *
+ * @param string $variable The variable within the option to create the radio buttons for.
+ * @param string $label The visual label for the radio buttons group, used as the fieldset legend.
+ * @param string $help Inline Help that will be printed out before the visible toggles text.
+ * @param array $attr Extra attributes to add to the index switch.
+ *
+ * @return void
+ */
+ public function index_switch( $variable, $label, $help = '', $attr = [] ) {
+ $defaults = [
+ 'disabled' => false,
+ ];
+ $attr = wp_parse_args( $attr, $defaults );
+
+ $index_switch_values = [
+ 'off' => __( 'On', 'wordpress-seo' ),
+ 'on' => __( 'Off', 'wordpress-seo' ),
+ ];
+
+ $is_disabled = ( isset( $attr['disabled'] ) && $attr['disabled'] );
+
+ $this->toggle_switch(
+ $variable,
+ $index_switch_values,
+ sprintf(
+ /* translators: %s expands to an indexable object's name, like a post type or taxonomy */
+ esc_html__( 'Show %s in search results?', 'wordpress-seo' ),
+ $label
+ ),
+ $help,
+ [ 'disabled' => $is_disabled ]
+ );
+ }
+
+ /**
+ * Creates a toggle switch to show hide certain options.
+ *
+ * @param string $variable The variable within the option to create the radio buttons for.
+ * @param string $label The visual label for the radio buttons group, used as the fieldset legend.
+ * @param bool $inverse_keys Whether or not the option keys need to be inverted to support older functions.
+ * @param string $help Inline Help that will be printed out before the visible toggles text.
+ * @param array $attr Extra attributes to add to the show-hide switch.
+ *
+ * @return void
+ */
+ public function show_hide_switch( $variable, $label, $inverse_keys = false, $help = '', $attr = [] ) {
+ $defaults = [
+ 'disabled' => false,
+ ];
+ $attr = wp_parse_args( $attr, $defaults );
+
+ $on_key = ( $inverse_keys ) ? 'off' : 'on';
+ $off_key = ( $inverse_keys ) ? 'on' : 'off';
+
+ $show_hide_switch = [
+ $on_key => __( 'On', 'wordpress-seo' ),
+ $off_key => __( 'Off', 'wordpress-seo' ),
+ ];
+
+ $is_disabled = ( isset( $attr['disabled'] ) && $attr['disabled'] );
+
+ $this->toggle_switch(
+ $variable,
+ $show_hide_switch,
+ $label,
+ $help,
+ [ 'disabled' => $is_disabled ]
+ );
+ }
+
+ /**
+ * Retrieves the value for the form field.
+ *
+ * @param string $field_name The field name to retrieve the value for.
+ * @param string|null $default_value The default value, when field has no value.
+ *
+ * @return mixed|null The retrieved value.
+ */
+ protected function get_field_value( $field_name, $default_value = null ) {
+ // On multisite subsites, the Usage tracking feature should always be set to Off.
+ if ( $this->is_tracking_on_subsite( $field_name ) ) {
+ return false;
+ }
+
+ return WPSEO_Options::get( $field_name, $default_value );
+ }
+
+ /**
+ * Checks whether a given control should be disabled.
+ *
+ * @param string $variable The variable within the option to check whether its control should be disabled.
+ *
+ * @return bool True if control should be disabled, false otherwise.
+ */
+ protected function is_control_disabled( $variable ) {
+ if ( $this->option_instance === null ) {
+ return false;
+ }
+
+ // Disable the Usage tracking feature for multisite subsites.
+ if ( $this->is_tracking_on_subsite( $variable ) ) {
+ return true;
+ }
+
+ return $this->option_instance->is_disabled( $variable );
+ }
+
+ /**
+ * Gets the explanation note to print if a given control is disabled.
+ *
+ * @param string $variable The variable within the option to print a disabled note for.
+ * @param string $custom_note An optional custom note to print instead.
+ *
+ * @return string Explanation note HTML string, or empty string if no note necessary.
+ */
+ protected function get_disabled_note( $variable, $custom_note = '' ) {
+ if ( $custom_note === '' && ! $this->is_control_disabled( $variable ) ) {
+ return '';
+ }
+ $disabled_message = esc_html__( 'This feature has been disabled by the network admin.', 'wordpress-seo' );
+
+ // The explanation to show when disabling the Usage tracking feature for multisite subsites.
+ if ( $this->is_tracking_on_subsite( $variable ) ) {
+ $disabled_message = esc_html__( 'This feature has been disabled since subsites never send tracking data.', 'wordpress-seo' );
+ }
+
+ if ( $custom_note ) {
+ $disabled_message = esc_html( $custom_note );
+ }
+
+ return '
' . $disabled_message . '
';
+ }
+
+ /**
+ * Determines whether we are dealing with the Usage tracking feature on a multisite subsite.
+ * This feature requires specific behavior for the toggle switch.
+ *
+ * @param string $feature_setting The feature setting.
+ *
+ * @return bool True if we are dealing with the Usage tracking feature on a multisite subsite.
+ */
+ protected function is_tracking_on_subsite( $feature_setting ) {
+ return ( $feature_setting === 'tracking' && ! is_network_admin() && ! is_main_site() );
+ }
+
+ /**
+ * Returns the disabled attribute HTML.
+ *
+ * @param string $variable The variable within the option of the related form element.
+ * @param array $attr Extra attributes added to the form element.
+ *
+ * @return string The disabled attribute HTML.
+ */
+ protected function get_disabled_attribute( $variable, $attr ) {
+ if ( $this->is_control_disabled( $variable ) || ( isset( $attr['disabled'] ) && $attr['disabled'] ) ) {
+ return ' disabled';
+ }
+
+ return '';
+ }
+}
diff --git a/wp-content/plugins/wordpress-seo/admin/class-yoast-input-validation.php b/wp-content/plugins/wordpress-seo/admin/class-yoast-input-validation.php
new file mode 100755
index 00000000..667ee78f
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/class-yoast-input-validation.php
@@ -0,0 +1,252 @@
+
+ */
+ private static $error_descriptions = [];
+
+ /**
+ * Check whether an option group is a Yoast SEO setting.
+ *
+ * The normal pattern is 'yoast' . $option_name . 'options'.
+ *
+ * @since 12.0
+ *
+ * @param string $group_name The option group name.
+ *
+ * @return bool Whether or not it's an Yoast SEO option group.
+ */
+ public static function is_yoast_option_group_name( $group_name ) {
+ return ( strpos( $group_name, 'yoast' ) !== false );
+ }
+
+ /**
+ * Adds an error message to the document title when submitting a settings
+ * form and errors are returned.
+ *
+ * Uses the WordPress `admin_title` filter in the WPSEO_Option subclasses.
+ *
+ * @since 12.0
+ *
+ * @param string $admin_title The page title, with extra context added.
+ *
+ * @return string The modified or original admin title.
+ */
+ public static function add_yoast_admin_document_title_errors( $admin_title ) {
+ $errors = get_settings_errors();
+ $error_count = 0;
+
+ foreach ( $errors as $error ) {
+ // For now, filter the admin title only in the Yoast SEO settings pages.
+ if ( self::is_yoast_option_group_name( $error['setting'] ) && $error['code'] !== 'settings_updated' ) {
+ ++$error_count;
+ }
+ }
+
+ if ( $error_count > 0 ) {
+ return sprintf(
+ /* translators: %1$s: amount of errors, %2$s: the admin page title */
+ _n( 'The form contains %1$s error. %2$s', 'The form contains %1$s errors. %2$s', $error_count, 'wordpress-seo' ),
+ number_format_i18n( $error_count ),
+ $admin_title
+ );
+ }
+
+ return $admin_title;
+ }
+
+ /**
+ * Checks whether a specific form input field was submitted with an invalid value.
+ *
+ * @since 12.1
+ *
+ * @param string $error_code Must be the same slug-name used for the field variable and for `add_settings_error()`.
+ *
+ * @return bool Whether or not the submitted input field contained an invalid value.
+ */
+ public static function yoast_form_control_has_error( $error_code ) {
+ $errors = get_settings_errors();
+
+ foreach ( $errors as $error ) {
+ if ( $error['code'] === $error_code ) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Sets the error descriptions.
+ *
+ * @since 12.1
+ * @deprecated 23.3
+ * @codeCoverageIgnore
+ *
+ * @param array $descriptions An associative array of error descriptions.
+ * For each entry, the key must be the setting variable.
+ *
+ * @return void
+ */
+ public static function set_error_descriptions( $descriptions = [] ) { // @phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable, Generic.CodeAnalysis.UnusedFunctionParameter.Found -- Needed for BC.
+ _deprecated_function( __METHOD__, 'Yoast SEO 23.3' );
+ }
+
+ /**
+ * Gets all the error descriptions.
+ *
+ * @since 12.1
+ * @deprecated 23.3
+ * @codeCoverageIgnore
+ *
+ * @return array An associative array of error descriptions.
+ */
+ public static function get_error_descriptions() {
+ _deprecated_function( __METHOD__, 'Yoast SEO 23.3' );
+ return [];
+ }
+
+ /**
+ * Gets a specific error description.
+ *
+ * @since 12.1
+ *
+ * @param string $error_code Code of the error set via `add_settings_error()`, normally the variable name.
+ *
+ * @return string|null The error description.
+ */
+ public static function get_error_description( $error_code ) {
+ if ( ! isset( self::$error_descriptions[ $error_code ] ) ) {
+ return null;
+ }
+
+ return self::$error_descriptions[ $error_code ];
+ }
+
+ /**
+ * Gets the aria-invalid HTML attribute based on the submitted invalid value.
+ *
+ * @since 12.1
+ *
+ * @param string $error_code Code of the error set via `add_settings_error()`, normally the variable name.
+ *
+ * @return string The aria-invalid HTML attribute or empty string.
+ */
+ public static function get_the_aria_invalid_attribute( $error_code ) {
+ if ( self::yoast_form_control_has_error( $error_code ) ) {
+ return ' aria-invalid="true"';
+ }
+
+ return '';
+ }
+
+ /**
+ * Gets the aria-describedby HTML attribute based on the submitted invalid value.
+ *
+ * @since 12.1
+ *
+ * @param string $error_code Code of the error set via `add_settings_error()`, normally the variable name.
+ *
+ * @return string The aria-describedby HTML attribute or empty string.
+ */
+ public static function get_the_aria_describedby_attribute( $error_code ) {
+ if ( self::yoast_form_control_has_error( $error_code ) && self::get_error_description( $error_code ) ) {
+ return ' aria-describedby="' . esc_attr( $error_code ) . '-error-description"';
+ }
+
+ return '';
+ }
+
+ /**
+ * Gets the error description wrapped in a HTML paragraph.
+ *
+ * @since 12.1
+ *
+ * @param string $error_code Code of the error set via `add_settings_error()`, normally the variable name.
+ *
+ * @return string The error description HTML or empty string.
+ */
+ public static function get_the_error_description( $error_code ) {
+ $error_description = self::get_error_description( $error_code );
+
+ if ( self::yoast_form_control_has_error( $error_code ) && $error_description ) {
+ return '
' . $error_description . '
';
+ }
+
+ return '';
+ }
+
+ /**
+ * Adds the submitted invalid value to the WordPress `$wp_settings_errors` global.
+ *
+ * @since 12.1
+ *
+ * @param string $error_code Code of the error set via `add_settings_error()`, normally the variable name.
+ * @param string $dirty_value The submitted invalid value.
+ *
+ * @return void
+ */
+ public static function add_dirty_value_to_settings_errors( $error_code, $dirty_value ) {
+ global $wp_settings_errors;
+
+ if ( ! is_array( $wp_settings_errors ) ) {
+ return;
+ }
+
+ foreach ( $wp_settings_errors as $index => $error ) {
+ if ( $error['code'] === $error_code ) {
+ // phpcs:ignore WordPress.WP.GlobalVariablesOverride -- This is a deliberate action.
+ $wp_settings_errors[ $index ]['yoast_dirty_value'] = $dirty_value;
+ }
+ }
+ }
+
+ /**
+ * Gets an invalid submitted value.
+ *
+ * @since 12.1
+ * @deprecated 23.3
+ * @codeCoverageIgnore
+ *
+ * @param string $error_code Code of the error set via `add_settings_error()`, normally the variable name.
+ *
+ * @return string The submitted invalid input field value.
+ */
+ public static function get_dirty_value( $error_code ) { // @phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable, Generic.CodeAnalysis.UnusedFunctionParameter.Found -- Needed for BC.
+ _deprecated_function( __METHOD__, 'Yoast SEO 23.3' );
+ return '';
+ }
+
+ /**
+ * Gets a specific invalid value message.
+ *
+ * @since 12.1
+ * @deprecated 23.3
+ * @codeCoverageIgnore
+ *
+ * @param string $error_code Code of the error set via `add_settings_error()`, normally the variable name.
+ *
+ * @return string The error invalid value message or empty string.
+ */
+ public static function get_dirty_value_message( $error_code ) { // @phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable, Generic.CodeAnalysis.UnusedFunctionParameter.Found -- Needed for BC.
+ _deprecated_function( __METHOD__, 'Yoast SEO 23.3' );
+
+ return '';
+ }
+}
diff --git a/wp-content/plugins/wordpress-seo/admin/class-yoast-network-admin.php b/wp-content/plugins/wordpress-seo/admin/class-yoast-network-admin.php
new file mode 100755
index 00000000..01f8f2f3
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/class-yoast-network-admin.php
@@ -0,0 +1,334 @@
+ $site_label pairs.
+ */
+ public function get_site_choices( $include_empty = false, $show_title = false ) {
+ $choices = [];
+
+ if ( $include_empty ) {
+ $choices['-'] = __( 'None', 'wordpress-seo' );
+ }
+
+ $criteria = [
+ 'deleted' => 0,
+ 'network_id' => get_current_network_id(),
+ ];
+ $sites = get_sites( $criteria );
+
+ foreach ( $sites as $site ) {
+ $site_name = $site->domain . $site->path;
+ if ( $show_title ) {
+ $site_name = $site->blogname . ' (' . $site->domain . $site->path . ')';
+ }
+ $choices[ $site->blog_id ] = $site->blog_id . ': ' . $site_name;
+
+ $site_states = $this->get_site_states( $site );
+ if ( ! empty( $site_states ) ) {
+ $choices[ $site->blog_id ] .= ' [' . implode( ', ', $site_states ) . ']';
+ }
+ }
+
+ return $choices;
+ }
+
+ /**
+ * Gets the states of a site.
+ *
+ * @param WP_Site $site Site object.
+ *
+ * @return array Array of $state_slug => $state_label pairs.
+ */
+ public function get_site_states( $site ) {
+ $available_states = [
+ 'public' => __( 'public', 'wordpress-seo' ),
+ 'archived' => __( 'archived', 'wordpress-seo' ),
+ 'mature' => __( 'mature', 'wordpress-seo' ),
+ 'spam' => __( 'spam', 'wordpress-seo' ),
+ 'deleted' => __( 'deleted', 'wordpress-seo' ),
+ ];
+
+ $site_states = [];
+ foreach ( $available_states as $state_slug => $state_label ) {
+ if ( $site->$state_slug === '1' ) {
+ $site_states[ $state_slug ] = $state_label;
+ }
+ }
+
+ return $site_states;
+ }
+
+ /**
+ * Handles a request to update plugin network options.
+ *
+ * This method works similar to how option updates are handled in `wp-admin/options.php` and
+ * `wp-admin/network/settings.php`.
+ *
+ * @return void
+ */
+ public function handle_update_options_request() {
+ // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification will happen in verify_request below.
+ if ( ! isset( $_POST['network_option_group'] ) || ! is_string( $_POST['network_option_group'] ) ) {
+ return;
+ }
+
+ // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification will happen in verify_request below.
+ $option_group = sanitize_text_field( wp_unslash( $_POST['network_option_group'] ) );
+
+ if ( empty( $option_group ) ) {
+ return;
+ }
+
+ $this->verify_request( "{$option_group}-network-options" );
+
+ $whitelist_options = Yoast_Network_Settings_API::get()->get_whitelist_options( $option_group );
+
+ if ( empty( $whitelist_options ) ) {
+ add_settings_error( $option_group, 'settings_updated', __( 'You are not allowed to modify unregistered network settings.', 'wordpress-seo' ), 'error' );
+
+ $this->terminate_request();
+ return;
+ }
+
+ // phpcs:disable WordPress.Security.NonceVerification -- Nonce verified via `verify_request()` above.
+ foreach ( $whitelist_options as $option_name ) {
+ $value = null;
+ if ( isset( $_POST[ $option_name ] ) ) {
+ // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: Adding sanitize_text_field around this will break the saving of settings because it expects a string: https://github.com/Yoast/wordpress-seo/issues/12440.
+ $value = wp_unslash( $_POST[ $option_name ] );
+ }
+
+ WPSEO_Options::update_site_option( $option_name, $value );
+ }
+ // phpcs:enable WordPress.Security.NonceVerification
+
+ $settings_errors = get_settings_errors();
+ if ( empty( $settings_errors ) ) {
+ add_settings_error( $option_group, 'settings_updated', __( 'Settings Updated.', 'wordpress-seo' ), 'updated' );
+ }
+
+ $this->terminate_request();
+ }
+
+ /**
+ * Handles a request to restore a site's default settings.
+ *
+ * @return void
+ */
+ public function handle_restore_site_request() {
+ $this->verify_request( 'wpseo-network-restore', 'restore_site_nonce' );
+
+ $option_group = 'wpseo_ms';
+
+ // phpcs:ignore WordPress.Security.NonceVerification -- Nonce verified via `verify_request()` above.
+ $site_id = ! empty( $_POST[ $option_group ]['site_id'] ) ? (int) $_POST[ $option_group ]['site_id'] : 0;
+ if ( ! $site_id ) {
+ add_settings_error( $option_group, 'settings_updated', __( 'No site has been selected to restore.', 'wordpress-seo' ), 'error' );
+
+ $this->terminate_request();
+ return;
+ }
+
+ $site = get_site( $site_id );
+ if ( ! $site ) {
+ /* translators: %s expands to the ID of a site within a multisite network. */
+ add_settings_error( $option_group, 'settings_updated', sprintf( __( 'Site with ID %d not found.', 'wordpress-seo' ), $site_id ), 'error' );
+ }
+ else {
+ WPSEO_Options::reset_ms_blog( $site_id );
+
+ /* translators: %s expands to the name of a site within a multisite network. */
+ add_settings_error( $option_group, 'settings_updated', sprintf( __( '%s restored to default SEO settings.', 'wordpress-seo' ), esc_html( $site->blogname ) ), 'updated' );
+ }
+
+ $this->terminate_request();
+ }
+
+ /**
+ * Outputs nonce, action and option group fields for a network settings page in the plugin.
+ *
+ * @param string $option_group Option group name for the current page.
+ *
+ * @return void
+ */
+ public function settings_fields( $option_group ) {
+ ?>
+
+
+ enqueue_script( 'network-admin' );
+
+ $translations = [
+ /* translators: %s: success message */
+ 'success_prefix' => __( 'Success: %s', 'wordpress-seo' ),
+ /* translators: %s: error message */
+ 'error_prefix' => __( 'Error: %s', 'wordpress-seo' ),
+ ];
+ $asset_manager->localize_script(
+ 'network-admin',
+ 'wpseoNetworkAdminGlobalL10n',
+ $translations
+ );
+ }
+
+ /**
+ * Hooks in the necessary actions and filters.
+ *
+ * @return void
+ */
+ public function register_hooks() {
+
+ if ( ! $this->meets_requirements() ) {
+ return;
+ }
+
+ add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] );
+
+ add_action( 'admin_action_' . self::UPDATE_OPTIONS_ACTION, [ $this, 'handle_update_options_request' ] );
+ add_action( 'admin_action_' . self::RESTORE_SITE_ACTION, [ $this, 'handle_restore_site_request' ] );
+ }
+
+ /**
+ * Hooks in the necessary AJAX actions.
+ *
+ * @return void
+ */
+ public function register_ajax_hooks() {
+ add_action( 'wp_ajax_' . self::UPDATE_OPTIONS_ACTION, [ $this, 'handle_update_options_request' ] );
+ add_action( 'wp_ajax_' . self::RESTORE_SITE_ACTION, [ $this, 'handle_restore_site_request' ] );
+ }
+
+ /**
+ * Checks whether the requirements to use this class are met.
+ *
+ * @return bool True if requirements are met, false otherwise.
+ */
+ public function meets_requirements() {
+ return is_multisite() && is_network_admin();
+ }
+
+ /**
+ * Verifies that the current request is valid.
+ *
+ * @param string $action Nonce action.
+ * @param string $query_arg Optional. Nonce query argument. Default '_wpnonce'.
+ *
+ * @return void
+ */
+ public function verify_request( $action, $query_arg = '_wpnonce' ) {
+ $has_access = current_user_can( 'wpseo_manage_network_options' );
+
+ if ( wp_doing_ajax() ) {
+ check_ajax_referer( $action, $query_arg );
+
+ if ( ! $has_access ) {
+ wp_die( -1, 403 );
+ }
+ return;
+ }
+
+ check_admin_referer( $action, $query_arg );
+
+ if ( ! $has_access ) {
+ wp_die( esc_html__( 'You are not allowed to perform this action.', 'wordpress-seo' ) );
+ }
+ }
+
+ /**
+ * Terminates the current request by either redirecting back or sending an AJAX response.
+ *
+ * @return void
+ */
+ public function terminate_request() {
+ if ( wp_doing_ajax() ) {
+ $settings_errors = get_settings_errors();
+
+ if ( ! empty( $settings_errors ) && $settings_errors[0]['type'] === 'updated' ) {
+ wp_send_json_success( $settings_errors, 200 );
+ }
+
+ wp_send_json_error( $settings_errors, 400 );
+ }
+
+ $this->persist_settings_errors();
+ $this->redirect_back( [ 'settings-updated' => 'true' ] );
+ }
+
+ /**
+ * Persists settings errors.
+ *
+ * Settings errors are stored in a transient for 30 seconds so that this transient
+ * can be retrieved on the next page load.
+ *
+ * @return void
+ */
+ protected function persist_settings_errors() {
+ /*
+ * A regular transient is used here, since it is automatically cleared right after the redirect.
+ * A network transient would be cleaner, but would require a lot of copied code from core for
+ * just a minor adjustment when displaying settings errors.
+ */
+ set_transient( 'settings_errors', get_settings_errors(), 30 );
+ }
+
+ /**
+ * Redirects back to the referer URL, with optional query arguments.
+ *
+ * @param array $query_args Optional. Query arguments to add to the redirect URL. Default none.
+ *
+ * @return void
+ */
+ protected function redirect_back( $query_args = [] ) {
+ $sendback = wp_get_referer();
+
+ if ( ! empty( $query_args ) ) {
+ $sendback = add_query_arg( $query_args, $sendback );
+ }
+
+ wp_safe_redirect( $sendback );
+ exit;
+ }
+}
diff --git a/wp-content/plugins/wordpress-seo/admin/class-yoast-network-settings-api.php b/wp-content/plugins/wordpress-seo/admin/class-yoast-network-settings-api.php
new file mode 100755
index 00000000..a573d141
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/class-yoast-network-settings-api.php
@@ -0,0 +1,164 @@
+ $option_group,
+ 'sanitize_callback' => null,
+ ];
+ $args = wp_parse_args( $args, $defaults );
+
+ if ( ! isset( $this->whitelist_options[ $option_group ] ) ) {
+ $this->whitelist_options[ $option_group ] = [];
+ }
+
+ $this->whitelist_options[ $option_group ][] = $option_name;
+
+ if ( ! empty( $args['sanitize_callback'] ) ) {
+ add_filter( "sanitize_option_{$option_name}", [ $this, 'filter_sanitize_option' ], 10, 2 );
+ }
+
+ if ( array_key_exists( 'default', $args ) ) {
+ add_filter( "default_site_option_{$option_name}", [ $this, 'filter_default_option' ], 10, 2 );
+ }
+
+ $this->registered_settings[ $option_name ] = $args;
+ }
+
+ /**
+ * Gets the registered settings and their data.
+ *
+ * @return array Array of $option_name => $data pairs.
+ */
+ public function get_registered_settings() {
+ return $this->registered_settings;
+ }
+
+ /**
+ * Gets the whitelisted options for a given option group.
+ *
+ * @param string $option_group Option group.
+ *
+ * @return array List of option names, or empty array if unknown option group.
+ */
+ public function get_whitelist_options( $option_group ) {
+ if ( ! isset( $this->whitelist_options[ $option_group ] ) ) {
+ return [];
+ }
+
+ return $this->whitelist_options[ $option_group ];
+ }
+
+ /**
+ * Filters sanitization for a network option value.
+ *
+ * This method is added as a filter to `sanitize_option_{$option}` for network options that are
+ * registered with a sanitize callback.
+ *
+ * @param string $value The sanitized option value.
+ * @param string $option The option name.
+ *
+ * @return string The filtered sanitized option value.
+ */
+ public function filter_sanitize_option( $value, $option ) {
+
+ if ( empty( $this->registered_settings[ $option ] ) ) {
+ return $value;
+ }
+
+ return call_user_func( $this->registered_settings[ $option ]['sanitize_callback'], $value );
+ }
+
+ /**
+ * Filters the default value for a network option.
+ *
+ * This function is added as a filter to `default_site_option_{$option}` for network options that
+ * are registered with a default.
+ *
+ * @param mixed $default_value Existing default value to return.
+ * @param string $option The option name.
+ *
+ * @return mixed The filtered default value.
+ */
+ public function filter_default_option( $default_value, $option ) {
+
+ // If a default value was manually passed to the function, allow it to override.
+ if ( $default_value !== false ) {
+ return $default_value;
+ }
+
+ if ( empty( $this->registered_settings[ $option ] ) ) {
+ return $default_value;
+ }
+
+ return $this->registered_settings[ $option ]['default'];
+ }
+
+ /**
+ * Checks whether the requirements to use this class are met.
+ *
+ * @return bool True if requirements are met, false otherwise.
+ */
+ public function meets_requirements() {
+ return is_multisite();
+ }
+
+ /**
+ * Gets the singleton instance of this class.
+ *
+ * @return Yoast_Network_Settings_API The singleton instance.
+ */
+ public static function get() {
+
+ if ( self::$instance === null ) {
+ self::$instance = new self();
+ }
+
+ return self::$instance;
+ }
+}
diff --git a/wp-content/plugins/wordpress-seo/admin/class-yoast-notification-center.php b/wp-content/plugins/wordpress-seo/admin/class-yoast-notification-center.php
new file mode 100755
index 00000000..3d6f422a
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/class-yoast-notification-center.php
@@ -0,0 +1,960 @@
+get_notification_by_id( $notification_id );
+ if ( ( $notification instanceof Yoast_Notification ) === false ) {
+
+ // Permit legacy.
+ $options = [
+ 'id' => $notification_id,
+ 'dismissal_key' => $notification_id,
+ ];
+ $notification = new Yoast_Notification( '', $options );
+ }
+
+ if ( self::maybe_dismiss_notification( $notification ) ) {
+ exit( '1' );
+ }
+
+ exit( '-1' );
+ }
+
+ /**
+ * Check if the user has dismissed a notification.
+ *
+ * @param Yoast_Notification $notification The notification to check for dismissal.
+ * @param int|null $user_id User ID to check on.
+ *
+ * @return bool
+ */
+ public static function is_notification_dismissed( Yoast_Notification $notification, $user_id = null ) {
+
+ $user_id = self::get_user_id( $user_id );
+ $dismissal_key = $notification->get_dismissal_key();
+
+ // This checks both the site-specific user option and the meta value.
+ $current_value = get_user_option( $dismissal_key, $user_id );
+
+ // Migrate old user meta to user option on-the-fly.
+ if ( ! empty( $current_value )
+ && metadata_exists( 'user', $user_id, $dismissal_key )
+ && update_user_option( $user_id, $dismissal_key, $current_value ) ) {
+ delete_user_meta( $user_id, $dismissal_key );
+ }
+
+ return ! empty( $current_value );
+ }
+
+ /**
+ * Checks if the notification is being dismissed.
+ *
+ * @param Yoast_Notification $notification Notification to check dismissal of.
+ * @param string $meta_value Value to set the meta value to if dismissed.
+ *
+ * @return bool True if dismissed.
+ */
+ public static function maybe_dismiss_notification( Yoast_Notification $notification, $meta_value = 'seen' ) {
+
+ // Only persistent notifications are dismissible.
+ if ( ! $notification->is_persistent() ) {
+ return false;
+ }
+
+ // If notification is already dismissed, we're done.
+ if ( self::is_notification_dismissed( $notification ) ) {
+ return true;
+ }
+
+ $dismissal_key = $notification->get_dismissal_key();
+ $notification_id = $notification->get_id();
+
+ $is_dismissing = ( $dismissal_key === self::get_user_input( 'notification' ) );
+ if ( ! $is_dismissing ) {
+ $is_dismissing = ( $notification_id === self::get_user_input( 'notification' ) );
+ }
+
+ // Fallback to ?dismissal_key=1&nonce=bla when JavaScript fails.
+ if ( ! $is_dismissing ) {
+ $is_dismissing = ( self::get_user_input( $dismissal_key ) === '1' );
+ }
+
+ if ( ! $is_dismissing ) {
+ return false;
+ }
+
+ $user_nonce = self::get_user_input( 'nonce' );
+ if ( wp_verify_nonce( $user_nonce, $notification_id ) === false ) {
+ return false;
+ }
+
+ return self::dismiss_notification( $notification, $meta_value );
+ }
+
+ /**
+ * Dismisses a notification.
+ *
+ * @param Yoast_Notification $notification Notification to dismiss.
+ * @param string $meta_value Value to save in the dismissal.
+ *
+ * @return bool True if dismissed, false otherwise.
+ */
+ public static function dismiss_notification( Yoast_Notification $notification, $meta_value = 'seen' ) {
+ // Dismiss notification.
+ return update_user_option( get_current_user_id(), $notification->get_dismissal_key(), $meta_value ) !== false;
+ }
+
+ /**
+ * Restores a notification.
+ *
+ * @param Yoast_Notification $notification Notification to restore.
+ *
+ * @return bool True if restored, false otherwise.
+ */
+ public static function restore_notification( Yoast_Notification $notification ) {
+
+ $user_id = get_current_user_id();
+ $dismissal_key = $notification->get_dismissal_key();
+
+ // Restore notification.
+ $restored = delete_user_option( $user_id, $dismissal_key );
+
+ // Delete unprefixed user meta too for backward-compatibility.
+ if ( metadata_exists( 'user', $user_id, $dismissal_key ) ) {
+ $restored = delete_user_meta( $user_id, $dismissal_key ) && $restored;
+ }
+
+ return $restored;
+ }
+
+ /**
+ * Clear dismissal information for the specified Notification.
+ *
+ * When a cause is resolved, the next time it is present we want to show
+ * the message again.
+ *
+ * @param string|Yoast_Notification $notification Notification to clear the dismissal of.
+ *
+ * @return bool
+ */
+ public function clear_dismissal( $notification ) {
+
+ global $wpdb;
+
+ if ( $notification instanceof Yoast_Notification ) {
+ $dismissal_key = $notification->get_dismissal_key();
+ }
+
+ if ( is_string( $notification ) ) {
+ $dismissal_key = $notification;
+ }
+
+ if ( empty( $dismissal_key ) ) {
+ return false;
+ }
+
+ // Remove notification dismissal for all users.
+ $deleted = delete_metadata( 'user', 0, $wpdb->get_blog_prefix() . $dismissal_key, '', true );
+
+ // Delete unprefixed user meta too for backward-compatibility.
+ $deleted = delete_metadata( 'user', 0, $dismissal_key, '', true ) || $deleted;
+
+ return $deleted;
+ }
+
+ /**
+ * Retrieves notifications from the storage and merges in previous notification changes.
+ *
+ * The current user in WordPress is not loaded shortly before the 'init' hook, but the plugin
+ * sometimes needs to add or remove notifications before that. In such cases, the transactions
+ * are not actually executed, but added to a queue. That queue is then handled in this method,
+ * after notifications for the current user have been set up.
+ *
+ * @return void
+ */
+ public function setup_current_notifications() {
+ $this->retrieve_notifications_from_storage( get_current_user_id() );
+
+ foreach ( $this->queued_transactions as $transaction ) {
+ list( $callback, $args ) = $transaction;
+
+ call_user_func_array( $callback, $args );
+ }
+
+ $this->queued_transactions = [];
+ }
+
+ /**
+ * Add notification to the cookie.
+ *
+ * @param Yoast_Notification $notification Notification object instance.
+ *
+ * @return void
+ */
+ public function add_notification( Yoast_Notification $notification ) {
+
+ $callback = [ $this, __FUNCTION__ ];
+ $args = func_get_args();
+ if ( $this->queue_transaction( $callback, $args ) ) {
+ return;
+ }
+
+ // Don't add if the user can't see it.
+ if ( ! $notification->display_for_current_user() ) {
+ return;
+ }
+
+ $notification_id = $notification->get_id();
+ $user_id = $notification->get_user_id();
+
+ // Empty notifications are always added.
+ if ( $notification_id !== '' ) {
+
+ // If notification ID exists in notifications, don't add again.
+ $present_notification = $this->get_notification_by_id( $notification_id, $user_id );
+ if ( $present_notification !== null ) {
+ $this->remove_notification( $present_notification, false );
+ }
+
+ if ( $present_notification === null ) {
+ $this->new[] = $notification_id;
+ }
+ }
+
+ // Add to list.
+ $this->notifications[ $user_id ][] = $notification;
+
+ $this->notifications_need_storage = true;
+ }
+
+ /**
+ * Get the notification by ID and user ID.
+ *
+ * @param string $notification_id The ID of the notification to search for.
+ * @param int|null $user_id The ID of the user.
+ *
+ * @return Yoast_Notification|null
+ */
+ public function get_notification_by_id( $notification_id, $user_id = null ) {
+ $user_id = self::get_user_id( $user_id );
+
+ $notifications = $this->get_notifications_for_user( $user_id );
+
+ foreach ( $notifications as $notification ) {
+ if ( $notification_id === $notification->get_id() ) {
+ return $notification;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Display the notifications.
+ *
+ * @param bool $echo_as_json True when notifications should be printed directly.
+ *
+ * @return void
+ */
+ public function display_notifications( $echo_as_json = false ) {
+
+ // Never display notifications for network admin.
+ if ( is_network_admin() ) {
+ return;
+ }
+
+ $sorted_notifications = $this->get_sorted_notifications();
+ $notifications = array_filter( $sorted_notifications, [ $this, 'is_notification_persistent' ] );
+
+ if ( empty( $notifications ) ) {
+ return;
+ }
+
+ array_walk( $notifications, [ $this, 'remove_notification' ] );
+
+ $notifications = array_unique( $notifications );
+ if ( $echo_as_json ) {
+ $notification_json = [];
+
+ foreach ( $notifications as $notification ) {
+ $notification_json[] = $notification->render();
+ }
+
+ // phpcs:ignore WordPress.Security.EscapeOutput -- Reason: WPSEO_Utils::format_json_encode is safe.
+ echo WPSEO_Utils::format_json_encode( $notification_json );
+
+ return;
+ }
+
+ foreach ( $notifications as $notification ) {
+ // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Reason: Temporarily disabled, see: https://github.com/Yoast/wordpress-seo-premium/issues/2510 and https://github.com/Yoast/wordpress-seo-premium/issues/2511.
+ echo $notification;
+ }
+ }
+
+ /**
+ * Remove notification after it has been displayed.
+ *
+ * @param Yoast_Notification $notification Notification to remove.
+ * @param bool $resolve Resolve as fixed.
+ *
+ * @return void
+ */
+ public function remove_notification( Yoast_Notification $notification, $resolve = true ) {
+
+ $callback = [ $this, __FUNCTION__ ];
+ $args = func_get_args();
+ if ( $this->queue_transaction( $callback, $args ) ) {
+ return;
+ }
+
+ $index = false;
+
+ // ID of the user to show the notification for, defaults to current user id.
+ $user_id = $notification->get_user_id();
+ $notifications = $this->get_notifications_for_user( $user_id );
+
+ // Match persistent Notifications by ID, non persistent by item in the array.
+ if ( $notification->is_persistent() ) {
+ foreach ( $notifications as $current_index => $present_notification ) {
+ if ( $present_notification->get_id() === $notification->get_id() ) {
+ $index = $current_index;
+ break;
+ }
+ }
+ }
+ else {
+ $index = array_search( $notification, $notifications, true );
+ }
+
+ if ( $index === false ) {
+ return;
+ }
+
+ if ( $notification->is_persistent() && $resolve ) {
+ ++$this->resolved;
+ $this->clear_dismissal( $notification );
+ }
+
+ unset( $notifications[ $index ] );
+ $this->notifications[ $user_id ] = array_values( $notifications );
+
+ $this->notifications_need_storage = true;
+ }
+
+ /**
+ * Removes a notification by its ID.
+ *
+ * @param string $notification_id The notification id.
+ * @param bool $resolve Resolve as fixed.
+ *
+ * @return void
+ */
+ public function remove_notification_by_id( $notification_id, $resolve = true ) {
+ $notification = $this->get_notification_by_id( $notification_id );
+
+ if ( $notification === null ) {
+ return;
+ }
+
+ $this->remove_notification( $notification, $resolve );
+ $this->notifications_need_storage = true;
+ }
+
+ /**
+ * Get the notification count.
+ *
+ * @param bool $dismissed Count dismissed notifications.
+ *
+ * @return int Number of notifications
+ */
+ public function get_notification_count( $dismissed = false ) {
+
+ $notifications = $this->get_notifications_for_user( get_current_user_id() );
+ $notifications = array_filter( $notifications, [ $this, 'filter_persistent_notifications' ] );
+
+ if ( ! $dismissed ) {
+ $notifications = array_filter( $notifications, [ $this, 'filter_dismissed_notifications' ] );
+ }
+
+ return count( $notifications );
+ }
+
+ /**
+ * Get the number of notifications resolved this execution.
+ *
+ * These notifications have been resolved and should be counted when active again.
+ *
+ * @return int
+ */
+ public function get_resolved_notification_count() {
+
+ return $this->resolved;
+ }
+
+ /**
+ * Return the notifications sorted on type and priority.
+ *
+ * @return Yoast_Notification[] Sorted Notifications
+ */
+ public function get_sorted_notifications() {
+ $notifications = $this->get_notifications_for_user( get_current_user_id() );
+ if ( empty( $notifications ) ) {
+ return [];
+ }
+
+ // Sort by severity, error first.
+ usort( $notifications, [ $this, 'sort_notifications' ] );
+
+ return $notifications;
+ }
+
+ /**
+ * AJAX display notifications.
+ *
+ * @return void
+ */
+ public function ajax_get_notifications() {
+ $echo = false;
+ // phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: We are not processing form data.
+ if ( isset( $_POST['version'] ) && is_string( $_POST['version'] ) ) {
+ // phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: We are only comparing the variable in a condition.
+ $echo = wp_unslash( $_POST['version'] ) === '2';
+ }
+
+ // Display the notices.
+ $this->display_notifications( $echo );
+
+ // AJAX die.
+ exit;
+ }
+
+ /**
+ * Remove storage when the plugin is deactivated.
+ *
+ * @return void
+ */
+ public function deactivate_hook() {
+
+ $this->clear_notifications();
+ }
+
+ /**
+ * Returns the given user ID if it exists.
+ * Otherwise, this function returns the ID of the current user.
+ *
+ * @param int $user_id The user ID to check.
+ *
+ * @return int The user ID to use.
+ */
+ private static function get_user_id( $user_id ) {
+ if ( $user_id ) {
+ return $user_id;
+ }
+ return get_current_user_id();
+ }
+
+ /**
+ * Splits the notifications on user ID.
+ *
+ * In other terms, it returns an associative array,
+ * mapping user ID to a list of notifications for this user.
+ *
+ * @param Yoast_Notification[] $notifications The notifications to split.
+ *
+ * @return array The notifications, split on user ID.
+ */
+ private function split_on_user_id( $notifications ) {
+ $split_notifications = [];
+ foreach ( $notifications as $notification ) {
+ $split_notifications[ $notification->get_user_id() ][] = $notification;
+ }
+ return $split_notifications;
+ }
+
+ /**
+ * Save persistent notifications to storage.
+ *
+ * We need to be able to retrieve these so they can be dismissed at any time during the execution.
+ *
+ * @since 3.2
+ *
+ * @return void
+ */
+ public function update_storage() {
+ /**
+ * Plugins might exit on the plugins_loaded hook.
+ * This prevents the pluggable.php file from loading, as it's loaded after the plugins_loaded hook.
+ * As we need functions defined in pluggable.php, make sure it's loaded.
+ */
+ require_once ABSPATH . WPINC . '/pluggable.php';
+
+ $notifications = $this->notifications;
+
+ /**
+ * One array of Yoast_Notifications, merged from multiple arrays.
+ *
+ * @var Yoast_Notification[] $merged_notifications
+ */
+ $merged_notifications = [];
+ if ( ! empty( $notifications ) ) {
+ $merged_notifications = array_merge( ...$notifications );
+ }
+
+ /**
+ * Filter: 'yoast_notifications_before_storage' - Allows developer to filter notifications before saving them.
+ *
+ * @param Yoast_Notification[] $notifications
+ */
+ $filtered_merged_notifications = apply_filters( 'yoast_notifications_before_storage', $merged_notifications );
+
+ // The notifications were filtered and therefore need to be stored.
+ if ( $merged_notifications !== $filtered_merged_notifications ) {
+ $merged_notifications = $filtered_merged_notifications;
+ $this->notifications_need_storage = true;
+ }
+
+ $notifications = $this->split_on_user_id( $merged_notifications );
+
+ // No notifications to store, clear storage if it was previously present.
+ if ( empty( $notifications ) ) {
+ $this->remove_storage();
+
+ return;
+ }
+
+ // Only store notifications if changes are made.
+ if ( $this->notifications_need_storage ) {
+ array_walk( $notifications, [ $this, 'store_notifications_for_user' ] );
+ }
+ }
+
+ /**
+ * Stores the notifications to its respective user's storage.
+ *
+ * @param Yoast_Notification[] $notifications The notifications to store.
+ * @param int $user_id The ID of the user for which to store the notifications.
+ *
+ * @return void
+ */
+ private function store_notifications_for_user( $notifications, $user_id ) {
+ $notifications_as_arrays = array_map( [ $this, 'notification_to_array' ], $notifications );
+ update_user_option( $user_id, self::STORAGE_KEY, $notifications_as_arrays );
+ }
+
+ /**
+ * Provide a way to verify present notifications.
+ *
+ * @return Yoast_Notification[] Registered notifications.
+ */
+ public function get_notifications() {
+ if ( ! $this->notifications ) {
+ return [];
+ }
+ return array_merge( ...$this->notifications );
+ }
+
+ /**
+ * Returns the notifications for the given user.
+ *
+ * @param int $user_id The id of the user to check.
+ *
+ * @return Yoast_Notification[] The notifications for the user with the given ID.
+ */
+ public function get_notifications_for_user( $user_id ) {
+ if ( array_key_exists( $user_id, $this->notifications ) ) {
+ return $this->notifications[ $user_id ];
+ }
+ return [];
+ }
+
+ /**
+ * Get newly added notifications.
+ *
+ * @return array
+ */
+ public function get_new_notifications() {
+
+ return array_map( [ $this, 'get_notification_by_id' ], $this->new );
+ }
+
+ /**
+ * Get information from the User input.
+ *
+ * Note that this function does not handle nonce verification.
+ *
+ * @param string $key Key to retrieve.
+ *
+ * @return string non-sanitized value of key if set, an empty string otherwise.
+ */
+ private static function get_user_input( $key ) {
+ // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,WordPress.Security.NonceVerification.Missing -- Reason: We are not processing form information and only using this variable in a comparison.
+ $request_method = isset( $_SERVER['REQUEST_METHOD'] ) && is_string( $_SERVER['REQUEST_METHOD'] ) ? strtoupper( wp_unslash( $_SERVER['REQUEST_METHOD'] ) ) : '';
+ // phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: This function does not sanitize variables.
+ // phpcs:disable WordPress.Security.NonceVerification.Recommended,WordPress.Security.NonceVerification.Missing -- Reason: This function does not verify a nonce.
+ if ( $request_method === 'POST' ) {
+ if ( isset( $_POST[ $key ] ) && is_string( $_POST[ $key ] ) ) {
+ return wp_unslash( $_POST[ $key ] );
+ }
+ }
+ elseif ( isset( $_GET[ $key ] ) && is_string( $_GET[ $key ] ) ) {
+ return wp_unslash( $_GET[ $key ] );
+ }
+ // phpcs:enable WordPress.Security.NonceVerification.Missing,WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
+ return '';
+ }
+
+ /**
+ * Retrieve the notifications from storage and fill the relevant property.
+ *
+ * @param int $user_id The ID of the user to retrieve notifications for.
+ *
+ * @return void
+ */
+ private function retrieve_notifications_from_storage( $user_id ) {
+ if ( $this->notifications_retrieved ) {
+ return;
+ }
+
+ $this->notifications_retrieved = true;
+
+ $stored_notifications = get_user_option( self::STORAGE_KEY, $user_id );
+
+ // Check if notifications are stored.
+ if ( empty( $stored_notifications ) ) {
+ return;
+ }
+
+ if ( is_array( $stored_notifications ) ) {
+ $notifications = array_map( [ $this, 'array_to_notification' ], $stored_notifications );
+
+ // Apply array_values to ensure we get a 0-indexed array.
+ $notifications = array_values( array_filter( $notifications, [ $this, 'filter_notification_current_user' ] ) );
+
+ $this->notifications[ $user_id ] = $notifications;
+ }
+ }
+
+ /**
+ * Sort on type then priority.
+ *
+ * @param Yoast_Notification $a Compare with B.
+ * @param Yoast_Notification $b Compare with A.
+ *
+ * @return int 1, 0 or -1 for sorting offset.
+ */
+ private function sort_notifications( Yoast_Notification $a, Yoast_Notification $b ) {
+
+ $a_type = $a->get_type();
+ $b_type = $b->get_type();
+
+ if ( $a_type === $b_type ) {
+ return WPSEO_Utils::calc( $b->get_priority(), 'compare', $a->get_priority() );
+ }
+
+ if ( $a_type === 'error' ) {
+ return -1;
+ }
+
+ if ( $b_type === 'error' ) {
+ return 1;
+ }
+
+ return 0;
+ }
+
+ /**
+ * Clear local stored notifications.
+ *
+ * @return void
+ */
+ private function clear_notifications() {
+
+ $this->notifications = [];
+ $this->notifications_retrieved = false;
+ }
+
+ /**
+ * Filter out non-persistent notifications.
+ *
+ * @since 3.2
+ *
+ * @param Yoast_Notification $notification Notification to test for persistent.
+ *
+ * @return bool
+ */
+ private function filter_persistent_notifications( Yoast_Notification $notification ) {
+
+ return $notification->is_persistent();
+ }
+
+ /**
+ * Filter out dismissed notifications.
+ *
+ * @param Yoast_Notification $notification Notification to check.
+ *
+ * @return bool
+ */
+ private function filter_dismissed_notifications( Yoast_Notification $notification ) {
+
+ return ! self::maybe_dismiss_notification( $notification );
+ }
+
+ /**
+ * Convert Notification to array representation.
+ *
+ * @since 3.2
+ *
+ * @param Yoast_Notification $notification Notification to convert.
+ *
+ * @return array
+ */
+ private function notification_to_array( Yoast_Notification $notification ) {
+
+ $notification_data = $notification->to_array();
+
+ if ( isset( $notification_data['nonce'] ) ) {
+ unset( $notification_data['nonce'] );
+ }
+
+ return $notification_data;
+ }
+
+ /**
+ * Convert stored array to Notification.
+ *
+ * @param array $notification_data Array to convert to Notification.
+ *
+ * @return Yoast_Notification
+ */
+ private function array_to_notification( $notification_data ) {
+
+ if ( isset( $notification_data['options']['nonce'] ) ) {
+ unset( $notification_data['options']['nonce'] );
+ }
+
+ if ( isset( $notification_data['message'] )
+ && is_subclass_of( $notification_data['message'], Abstract_Presenter::class, false )
+ ) {
+ $notification_data['message'] = $notification_data['message']->present();
+ }
+
+ if ( isset( $notification_data['options']['user'] ) ) {
+ $notification_data['options']['user_id'] = $notification_data['options']['user']->ID;
+ unset( $notification_data['options']['user'] );
+
+ $this->notifications_need_storage = true;
+ }
+
+ return new Yoast_Notification(
+ $notification_data['message'],
+ $notification_data['options']
+ );
+ }
+
+ /**
+ * Filter notifications that should not be displayed for the current user.
+ *
+ * @param Yoast_Notification $notification Notification to test.
+ *
+ * @return bool
+ */
+ private function filter_notification_current_user( Yoast_Notification $notification ) {
+ return $notification->display_for_current_user();
+ }
+
+ /**
+ * Checks if given notification is persistent.
+ *
+ * @param Yoast_Notification $notification The notification to check.
+ *
+ * @return bool True when notification is not persistent.
+ */
+ private function is_notification_persistent( Yoast_Notification $notification ) {
+ return ! $notification->is_persistent();
+ }
+
+ /**
+ * Queues a notification transaction for later execution if notifications are not yet set up.
+ *
+ * @param callable $callback Callback that performs the transaction.
+ * @param array $args Arguments to pass to the callback.
+ *
+ * @return bool True if transaction was queued, false if it can be performed immediately.
+ */
+ private function queue_transaction( $callback, $args ) {
+ if ( $this->notifications_retrieved ) {
+ return false;
+ }
+
+ $this->add_transaction_to_queue( $callback, $args );
+
+ return true;
+ }
+
+ /**
+ * Adds a notification transaction to the queue for later execution.
+ *
+ * @param callable $callback Callback that performs the transaction.
+ * @param array $args Arguments to pass to the callback.
+ *
+ * @return void
+ */
+ private function add_transaction_to_queue( $callback, $args ) {
+ $this->queued_transactions[] = [ $callback, $args ];
+ }
+
+ /**
+ * Removes all notifications from storage.
+ *
+ * @return bool True when notifications got removed.
+ */
+ protected function remove_storage() {
+ if ( ! $this->has_stored_notifications() ) {
+ return false;
+ }
+
+ delete_user_option( get_current_user_id(), self::STORAGE_KEY );
+ return true;
+ }
+
+ /**
+ * Checks if there are stored notifications.
+ *
+ * @return bool True when there are stored notifications.
+ */
+ protected function has_stored_notifications() {
+ $stored_notifications = $this->get_stored_notifications();
+
+ return ! empty( $stored_notifications );
+ }
+
+ /**
+ * Retrieves the stored notifications.
+ *
+ * @codeCoverageIgnore
+ *
+ * @return array|false Array with notifications or false when not set.
+ */
+ protected function get_stored_notifications() {
+ return get_user_option( self::STORAGE_KEY, get_current_user_id() );
+ }
+}
diff --git a/wp-content/plugins/wordpress-seo/admin/class-yoast-notification.php b/wp-content/plugins/wordpress-seo/admin/class-yoast-notification.php
new file mode 100755
index 00000000..f8e380e5
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/class-yoast-notification.php
@@ -0,0 +1,438 @@
+ self::UPDATED,
+ 'id' => '',
+ 'user_id' => null,
+ 'nonce' => null,
+ 'priority' => 0.5,
+ 'data_json' => [],
+ 'dismissal_key' => null,
+ 'capabilities' => [],
+ 'capability_check' => self::MATCH_ALL,
+ 'yoast_branding' => false,
+ ];
+
+ /**
+ * The message for the notification.
+ *
+ * @var string
+ */
+ private $message;
+
+ /**
+ * Notification class constructor.
+ *
+ * @param string $message Message string.
+ * @param array $options Set of options.
+ */
+ public function __construct( $message, $options = [] ) {
+ $this->message = $message;
+ $this->options = $this->normalize_options( $options );
+ }
+
+ /**
+ * Retrieve notification ID string.
+ *
+ * @return string
+ */
+ public function get_id() {
+ return $this->options['id'];
+ }
+
+ /**
+ * Retrieve the user to show the notification for.
+ *
+ * @deprecated 21.6
+ * @codeCoverageIgnore
+ *
+ * @return WP_User|null The user to show this notification for.
+ */
+ public function get_user() {
+ _deprecated_function( __METHOD__, 'Yoast SEO 21.6' );
+ return null;
+ }
+
+ /**
+ * Retrieve the id of the user to show the notification for.
+ *
+ * Returns the id of the current user if not user has been sent.
+ *
+ * @return int The user id
+ */
+ public function get_user_id() {
+ return ( $this->options['user_id'] ?? get_current_user_id() );
+ }
+
+ /**
+ * Retrieve nonce identifier.
+ *
+ * @return string|null Nonce for this Notification.
+ */
+ public function get_nonce() {
+ if ( $this->options['id'] && empty( $this->options['nonce'] ) ) {
+ $this->options['nonce'] = wp_create_nonce( $this->options['id'] );
+ }
+
+ return $this->options['nonce'];
+ }
+
+ /**
+ * Make sure the nonce is up to date.
+ *
+ * @return void
+ */
+ public function refresh_nonce() {
+ if ( $this->options['id'] ) {
+ $this->options['nonce'] = wp_create_nonce( $this->options['id'] );
+ }
+ }
+
+ /**
+ * Get the type of the notification.
+ *
+ * @return string
+ */
+ public function get_type() {
+ return $this->options['type'];
+ }
+
+ /**
+ * Priority of the notification.
+ *
+ * Relative to the type.
+ *
+ * @return float Returns the priority between 0 and 1.
+ */
+ public function get_priority() {
+ return $this->options['priority'];
+ }
+
+ /**
+ * Get the User Meta key to check for dismissal of notification.
+ *
+ * @return string User Meta Option key that registers dismissal.
+ */
+ public function get_dismissal_key() {
+ if ( empty( $this->options['dismissal_key'] ) ) {
+ return $this->options['id'];
+ }
+
+ return $this->options['dismissal_key'];
+ }
+
+ /**
+ * Is this Notification persistent.
+ *
+ * @return bool True if persistent, False if fire and forget.
+ */
+ public function is_persistent() {
+ $id = $this->get_id();
+
+ return ! empty( $id );
+ }
+
+ /**
+ * Check if the notification is relevant for the current user.
+ *
+ * @return bool True if a user needs to see this notification, false if not.
+ */
+ public function display_for_current_user() {
+ // If the notification is for the current page only, always show.
+ if ( ! $this->is_persistent() ) {
+ return true;
+ }
+
+ // If the current user doesn't match capabilities.
+ return $this->match_capabilities();
+ }
+
+ /**
+ * Does the current user match required capabilities.
+ *
+ * @return bool
+ */
+ public function match_capabilities() {
+ // Super Admin can do anything.
+ if ( is_multisite() && is_super_admin( $this->options['user_id'] ) ) {
+ return true;
+ }
+
+ /**
+ * Filter capabilities that enable the displaying of this notification.
+ *
+ * @param array $capabilities The capabilities that must be present for this notification.
+ * @param Yoast_Notification $notification The notification object.
+ *
+ * @return array Array of capabilities or empty for no restrictions.
+ *
+ * @since 3.2
+ */
+ $capabilities = apply_filters( 'wpseo_notification_capabilities', $this->options['capabilities'], $this );
+
+ // Should be an array.
+ if ( ! is_array( $capabilities ) ) {
+ $capabilities = (array) $capabilities;
+ }
+
+ /**
+ * Filter capability check to enable all or any capabilities.
+ *
+ * @param string $capability_check The type of check that will be used to determine if an capability is present.
+ * @param Yoast_Notification $notification The notification object.
+ *
+ * @return string self::MATCH_ALL or self::MATCH_ANY.
+ *
+ * @since 3.2
+ */
+ $capability_check = apply_filters( 'wpseo_notification_capability_check', $this->options['capability_check'], $this );
+
+ if ( ! in_array( $capability_check, [ self::MATCH_ALL, self::MATCH_ANY ], true ) ) {
+ $capability_check = self::MATCH_ALL;
+ }
+
+ if ( ! empty( $capabilities ) ) {
+
+ $has_capabilities = array_filter( $capabilities, [ $this, 'has_capability' ] );
+
+ switch ( $capability_check ) {
+ case self::MATCH_ALL:
+ return $has_capabilities === $capabilities;
+ case self::MATCH_ANY:
+ return ! empty( $has_capabilities );
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Array filter function to find matched capabilities.
+ *
+ * @param string $capability Capability to test.
+ *
+ * @return bool
+ */
+ private function has_capability( $capability ) {
+ $user_id = $this->options['user_id'];
+ if ( ! is_numeric( $user_id ) ) {
+ return false;
+ }
+ $user = get_user_by( 'id', $user_id );
+ if ( ! $user ) {
+ return false;
+ }
+
+ return $user->has_cap( $capability );
+ }
+
+ /**
+ * Return the object properties as an array.
+ *
+ * @return array
+ */
+ public function to_array() {
+ return [
+ 'message' => $this->message,
+ 'options' => $this->options,
+ ];
+ }
+
+ /**
+ * Adds string (view) behaviour to the notification.
+ *
+ * @return string
+ */
+ public function __toString() {
+ return $this->render();
+ }
+
+ /**
+ * Renders the notification as a string.
+ *
+ * @return string The rendered notification.
+ */
+ public function render() {
+ $attributes = [];
+
+ // Default notification classes.
+ $classes = [
+ 'yoast-notification',
+ ];
+
+ // Maintain WordPress visualisation of notifications when they are not persistent.
+ if ( ! $this->is_persistent() ) {
+ $classes[] = 'notice';
+ $classes[] = $this->get_type();
+ }
+
+ if ( ! empty( $classes ) ) {
+ $attributes['class'] = implode( ' ', $classes );
+ }
+
+ // Combined attribute key and value into a string.
+ array_walk( $attributes, [ $this, 'parse_attributes' ] );
+
+ $message = null;
+ if ( $this->options['yoast_branding'] ) {
+ $message = $this->wrap_yoast_seo_icon( $this->message );
+ }
+
+ if ( $message === null ) {
+ $message = wpautop( $this->message );
+ }
+
+ // Build the output DIV.
+ return '
' . $message . '
' . PHP_EOL;
+ }
+
+ /**
+ * Get the message for the notification.
+ *
+ * @return string The message.
+ */
+ public function get_message() {
+ return wpautop( $this->message );
+ }
+
+ /**
+ * Wraps the message with a Yoast SEO icon.
+ *
+ * @param string $message The message to wrap.
+ *
+ * @return string The wrapped message.
+ */
+ private function wrap_yoast_seo_icon( $message ) {
+ $out = sprintf(
+ '',
+ esc_url( plugin_dir_url( WPSEO_FILE ) . 'packages/js/images/Yoast_SEO_Icon.svg' ),
+ 60,
+ 60
+ );
+ $out .= '
';
+ $out .= $message;
+ $out .= '
';
+
+ return $out;
+ }
+
+ /**
+ * Get the JSON if provided.
+ *
+ * @return string|false
+ */
+ public function get_json() {
+ if ( empty( $this->options['data_json'] ) ) {
+ return '';
+ }
+
+ return WPSEO_Utils::format_json_encode( $this->options['data_json'] );
+ }
+
+ /**
+ * Make sure we only have values that we can work with.
+ *
+ * @param array $options Options to normalize.
+ *
+ * @return array
+ */
+ private function normalize_options( $options ) {
+ $options = wp_parse_args( $options, $this->defaults );
+
+ // Should not exceed 0 or 1.
+ $options['priority'] = min( 1, max( 0, $options['priority'] ) );
+
+ // Set default capabilities when not supplied.
+ if ( empty( $options['capabilities'] ) || $options['capabilities'] === [] ) {
+ $options['capabilities'] = [ 'wpseo_manage_options' ];
+ }
+
+ // Set to the id of the current user if not supplied.
+ if ( $options['user_id'] === null ) {
+ $options['user_id'] = get_current_user_id();
+ }
+
+ return $options;
+ }
+
+ /**
+ * Format HTML element attributes.
+ *
+ * @param string $value Attribute value.
+ * @param string $key Attribute name.
+ *
+ * @return void
+ */
+ private function parse_attributes( &$value, $key ) {
+ $value = sprintf( '%s="%s"', sanitize_key( $key ), esc_attr( $value ) );
+ }
+}
diff --git a/wp-content/plugins/wordpress-seo/admin/class-yoast-notifications.php b/wp-content/plugins/wordpress-seo/admin/class-yoast-notifications.php
new file mode 100755
index 00000000..c3847e01
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/class-yoast-notifications.php
@@ -0,0 +1,319 @@
+add_hooks();
+ }
+
+ /**
+ * Add hooks
+ *
+ * @return void
+ */
+ private function add_hooks() {
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
+ if ( isset( $_GET['page'] ) && is_string( $_GET['page'] ) ) {
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
+ $page = sanitize_text_field( wp_unslash( $_GET['page'] ) );
+ if ( $page === self::ADMIN_PAGE ) {
+ add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] );
+ }
+ }
+
+ // Needed for adminbar and Notifications page.
+ add_action( 'admin_init', [ self::class, 'collect_notifications' ], 99 );
+
+ // Add AJAX hooks.
+ add_action( 'wp_ajax_yoast_dismiss_notification', [ $this, 'ajax_dismiss_notification' ] );
+ add_action( 'wp_ajax_yoast_restore_notification', [ $this, 'ajax_restore_notification' ] );
+ }
+
+ /**
+ * Enqueue assets.
+ *
+ * @return void
+ */
+ public function enqueue_assets() {
+ $asset_manager = new WPSEO_Admin_Asset_Manager();
+
+ $asset_manager->enqueue_style( 'notifications' );
+ }
+
+ /**
+ * Handle ajax request to dismiss a notification.
+ *
+ * @return void
+ */
+ public function ajax_dismiss_notification() {
+
+ $notification = $this->get_notification_from_ajax_request();
+ if ( $notification ) {
+ $notification_center = Yoast_Notification_Center::get();
+ $notification_center->maybe_dismiss_notification( $notification );
+
+ $this->output_ajax_response( $notification->get_type() );
+ }
+
+ wp_die();
+ }
+
+ /**
+ * Handle ajax request to restore a notification.
+ *
+ * @return void
+ */
+ public function ajax_restore_notification() {
+
+ $notification = $this->get_notification_from_ajax_request();
+ if ( $notification ) {
+ $notification_center = Yoast_Notification_Center::get();
+ $notification_center->restore_notification( $notification );
+
+ $this->output_ajax_response( $notification->get_type() );
+ }
+
+ wp_die();
+ }
+
+ /**
+ * Create AJAX response data.
+ *
+ * @param string $type Notification type.
+ *
+ * @return void
+ */
+ private function output_ajax_response( $type ) {
+
+ $html = $this->get_view_html( $type );
+ // phpcs:disable WordPress.Security.EscapeOutput -- Reason: WPSEO_Utils::format_json_encode is safe.
+ echo WPSEO_Utils::format_json_encode(
+ [
+ 'html' => $html,
+ 'total' => self::get_active_notification_count(),
+ ]
+ );
+ // phpcs:enable -- Reason: WPSEO_Utils::format_json_encode is safe.
+ }
+
+ /**
+ * Get the HTML to return in the AJAX request.
+ *
+ * @param string $type Notification type.
+ *
+ * @return bool|string
+ */
+ private function get_view_html( $type ) {
+
+ switch ( $type ) {
+ case 'error':
+ $view = 'errors';
+ break;
+
+ case 'warning':
+ default:
+ $view = 'warnings';
+ break;
+ }
+
+ // Re-collect notifications.
+ self::collect_notifications();
+
+ /**
+ * Stops PHPStorm from nagging about this variable being unused. The variable is used in the view.
+ *
+ * @noinspection PhpUnusedLocalVariableInspection
+ */
+ $notifications_data = self::get_template_variables();
+
+ ob_start();
+ include WPSEO_PATH . 'admin/views/partial-notifications-' . $view . '.php';
+ $html = ob_get_clean();
+
+ return $html;
+ }
+
+ /**
+ * Extract the Yoast Notification from the AJAX request.
+ *
+ * This function does not handle nonce verification.
+ *
+ * @return Yoast_Notification|null A Yoast_Notification on success, null on failure.
+ */
+ private function get_notification_from_ajax_request() {
+ // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: This function does not handle nonce verification.
+ if ( ! isset( $_POST['notification'] ) || ! is_string( $_POST['notification'] ) ) {
+ return null;
+ }
+ // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: This function does not handle nonce verification.
+ $notification_id = sanitize_text_field( wp_unslash( $_POST['notification'] ) );
+
+ if ( empty( $notification_id ) ) {
+ return null;
+ }
+ $notification_center = Yoast_Notification_Center::get();
+ return $notification_center->get_notification_by_id( $notification_id );
+ }
+
+ /**
+ * Collect the notifications and group them together.
+ *
+ * @return void
+ */
+ public static function collect_notifications() {
+
+ $notification_center = Yoast_Notification_Center::get();
+
+ $notifications = $notification_center->get_sorted_notifications();
+ self::$notification_count = count( $notifications );
+
+ self::$errors = array_filter( $notifications, [ self::class, 'filter_error_notifications' ] );
+ self::$dismissed_errors = array_filter( self::$errors, [ self::class, 'filter_dismissed_notifications' ] );
+ self::$active_errors = array_diff( self::$errors, self::$dismissed_errors );
+
+ self::$warnings = array_filter( $notifications, [ self::class, 'filter_warning_notifications' ] );
+ self::$dismissed_warnings = array_filter( self::$warnings, [ self::class, 'filter_dismissed_notifications' ] );
+ self::$active_warnings = array_diff( self::$warnings, self::$dismissed_warnings );
+ }
+
+ /**
+ * Get the variables needed in the views.
+ *
+ * @return array
+ */
+ public static function get_template_variables() {
+
+ return [
+ 'metrics' => [
+ 'total' => self::$notification_count,
+ 'active' => self::get_active_notification_count(),
+ 'errors' => count( self::$errors ),
+ 'warnings' => count( self::$warnings ),
+ ],
+ 'errors' => [
+ 'dismissed' => self::$dismissed_errors,
+ 'active' => self::$active_errors,
+ ],
+ 'warnings' => [
+ 'dismissed' => self::$dismissed_warnings,
+ 'active' => self::$active_warnings,
+ ],
+ ];
+ }
+
+ /**
+ * Get the number of active notifications.
+ *
+ * @return int
+ */
+ public static function get_active_notification_count() {
+
+ return ( count( self::$active_errors ) + count( self::$active_warnings ) );
+ }
+
+ /**
+ * Filter out any non-errors.
+ *
+ * @param Yoast_Notification $notification Notification to test.
+ *
+ * @return bool
+ */
+ private static function filter_error_notifications( Yoast_Notification $notification ) {
+
+ return $notification->get_type() === 'error';
+ }
+
+ /**
+ * Filter out any non-warnings.
+ *
+ * @param Yoast_Notification $notification Notification to test.
+ *
+ * @return bool
+ */
+ private static function filter_warning_notifications( Yoast_Notification $notification ) {
+
+ return $notification->get_type() !== 'error';
+ }
+
+ /**
+ * Filter out any dismissed notifications.
+ *
+ * @param Yoast_Notification $notification Notification to test.
+ *
+ * @return bool
+ */
+ private static function filter_dismissed_notifications( Yoast_Notification $notification ) {
+
+ return Yoast_Notification_Center::is_notification_dismissed( $notification );
+ }
+}
+
+class_alias( Yoast_Notifications::class, 'Yoast_Alerts' );
diff --git a/wp-content/plugins/wordpress-seo/admin/class-yoast-plugin-conflict.php b/wp-content/plugins/wordpress-seo/admin/class-yoast-plugin-conflict.php
new file mode 100755
index 00000000..838d8a70
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/class-yoast-plugin-conflict.php
@@ -0,0 +1,342 @@
+plugins the active plugins will be stored in this
+ * property.
+ *
+ * @var array
+ */
+ protected $active_conflicting_plugins = [];
+
+ /**
+ * Property for holding instance of itself.
+ *
+ * @var Yoast_Plugin_Conflict
+ */
+ protected static $instance;
+
+ /**
+ * For the use of singleton pattern. Create instance of itself and return this instance.
+ *
+ * @param string $class_name Give the classname to initialize. If classname is
+ * false (empty) it will use it's own __CLASS__.
+ *
+ * @return Yoast_Plugin_Conflict
+ */
+ public static function get_instance( $class_name = '' ) {
+
+ if ( self::$instance === null ) {
+ if ( ! is_string( $class_name ) || $class_name === '' ) {
+ $class_name = self::class;
+ }
+
+ self::$instance = new $class_name();
+ }
+
+ return self::$instance;
+ }
+
+ /**
+ * Setting instance, all active plugins and search for active plugins.
+ *
+ * Protected constructor to prevent creating a new instance of the
+ * *Singleton* via the `new` operator from outside this class.
+ */
+ protected function __construct() {
+ // Set active plugins.
+ $this->all_active_plugins = get_option( 'active_plugins' );
+
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
+ if ( isset( $_GET['action'] ) && is_string( $_GET['action'] ) ) {
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: We are not processing form information and only comparing the variable in a condition.
+ $action = wp_unslash( $_GET['action'] );
+ if ( $action === 'deactivate' ) {
+ $this->remove_deactivated_plugin();
+ }
+ }
+
+ // Search for active plugins.
+ $this->search_active_plugins();
+ }
+
+ /**
+ * Check if there are conflicting plugins for given $plugin_section.
+ *
+ * @param string $plugin_section Type of plugin conflict (such as Open Graph or sitemap).
+ *
+ * @return bool
+ */
+ public function check_for_conflicts( $plugin_section ) {
+
+ static $sections_checked;
+
+ // Return early if there are no active conflicting plugins at all.
+ if ( empty( $this->active_conflicting_plugins ) ) {
+ return false;
+ }
+
+ if ( $sections_checked === null ) {
+ $sections_checked = [];
+ }
+
+ if ( ! in_array( $plugin_section, $sections_checked, true ) ) {
+ $sections_checked[] = $plugin_section;
+ return ( ! empty( $this->active_conflicting_plugins[ $plugin_section ] ) );
+ }
+
+ return false;
+ }
+
+ /**
+ * Checks for given $plugin_sections for conflicts.
+ *
+ * @param array $plugin_sections Set of sections.
+ *
+ * @return void
+ */
+ public function check_plugin_conflicts( $plugin_sections ) {
+ foreach ( $plugin_sections as $plugin_section => $readable_plugin_section ) {
+ // Check for conflicting plugins and show error if there are conflicts.
+ if ( $this->check_for_conflicts( $plugin_section ) ) {
+ $this->set_error( $plugin_section, $readable_plugin_section );
+ }
+ }
+
+ // List of all active sections.
+ $sections = array_keys( $plugin_sections );
+ // List of all sections.
+ $all_plugin_sections = array_keys( $this->plugins );
+
+ /*
+ * Get all sections that are inactive.
+ * These plugins need to be cleared.
+ *
+ * This happens when Sitemaps or OpenGraph implementations toggle active/disabled.
+ */
+ $inactive_sections = array_diff( $all_plugin_sections, $sections );
+ if ( ! empty( $inactive_sections ) ) {
+ foreach ( $inactive_sections as $section ) {
+ array_walk( $this->plugins[ $section ], [ $this, 'clear_error' ] );
+ }
+ }
+
+ // For active sections clear errors for inactive plugins.
+ foreach ( $sections as $section ) {
+ // By default, clear errors for all plugins of the section.
+ $inactive_plugins = $this->plugins[ $section ];
+
+ // If there are active plugins, filter them from being cleared.
+ if ( isset( $this->active_conflicting_plugins[ $section ] ) ) {
+ $inactive_plugins = array_diff( $this->plugins[ $section ], $this->active_conflicting_plugins[ $section ] );
+ }
+
+ array_walk( $inactive_plugins, [ $this, 'clear_error' ] );
+ }
+ }
+
+ /**
+ * Setting an error on the screen.
+ *
+ * @param string $plugin_section Type of conflict group (such as Open Graph or sitemap).
+ * @param string $readable_plugin_section This is the value for the translation.
+ *
+ * @return void
+ */
+ protected function set_error( $plugin_section, $readable_plugin_section ) {
+
+ $notification_center = Yoast_Notification_Center::get();
+
+ foreach ( $this->active_conflicting_plugins[ $plugin_section ] as $plugin_file ) {
+
+ $plugin_name = $this->get_plugin_name( $plugin_file );
+
+ $error_message = '';
+ /* translators: %1$s: 'Facebook & Open Graph' plugin name(s) of possibly conflicting plugin(s), %2$s to Yoast SEO */
+ $error_message .= '
' . sprintf( __( 'The %1$s plugin might cause issues when used in conjunction with %2$s.', 'wordpress-seo' ), '' . $plugin_name . '', 'Yoast SEO' ) . '
';
+
+ /* translators: %s: 'Facebook' plugin name of possibly conflicting plugin */
+ $error_message .= '' . sprintf( __( 'Deactivate %s', 'wordpress-seo' ), $this->get_plugin_name( $plugin_file ) ) . ' ';
+
+ $identifier = $this->get_notification_identifier( $plugin_file );
+
+ // Add the message to the notifications center.
+ $notification_center->add_notification(
+ new Yoast_Notification(
+ $error_message,
+ [
+ 'type' => Yoast_Notification::ERROR,
+ 'id' => 'wpseo-conflict-' . $identifier,
+ ]
+ )
+ );
+ }
+ }
+
+ /**
+ * Clear the notification for a plugin.
+ *
+ * @param string $plugin_file Clear the optional notification for this plugin.
+ *
+ * @return void
+ */
+ public function clear_error( $plugin_file ) {
+ $identifier = $this->get_notification_identifier( $plugin_file );
+
+ $notification_center = Yoast_Notification_Center::get();
+ $notification_center->remove_notification_by_id( 'wpseo-conflict-' . $identifier );
+ }
+
+ /**
+ * Loop through the $this->plugins to check if one of the plugins is active.
+ *
+ * This method will store the active plugins in $this->active_plugins.
+ *
+ * @return void
+ */
+ protected function search_active_plugins() {
+ foreach ( $this->plugins as $plugin_section => $plugins ) {
+ $this->check_plugins_active( $plugins, $plugin_section );
+ }
+ }
+
+ /**
+ * Loop through plugins and check if each plugin is active.
+ *
+ * @param array $plugins Set of plugins.
+ * @param string $plugin_section Type of conflict group (such as Open Graph or sitemap).
+ *
+ * @return void
+ */
+ protected function check_plugins_active( $plugins, $plugin_section ) {
+ foreach ( $plugins as $plugin ) {
+ if ( $this->check_plugin_is_active( $plugin ) ) {
+ $this->add_active_plugin( $plugin_section, $plugin );
+ }
+ }
+ }
+
+ /**
+ * Check if given plugin exists in array with all_active_plugins.
+ *
+ * @param string $plugin Plugin basename string.
+ *
+ * @return bool
+ */
+ protected function check_plugin_is_active( $plugin ) {
+ return in_array( $plugin, $this->all_active_plugins, true );
+ }
+
+ /**
+ * Add plugin to the list of active plugins.
+ *
+ * This method will check first if key $plugin_section exists, if not it will create an empty array
+ * If $plugin itself doesn't exist it will be added.
+ *
+ * @param string $plugin_section Type of conflict group (such as Open Graph or sitemap).
+ * @param string $plugin Plugin basename string.
+ *
+ * @return void
+ */
+ protected function add_active_plugin( $plugin_section, $plugin ) {
+ if ( ! array_key_exists( $plugin_section, $this->active_conflicting_plugins ) ) {
+ $this->active_conflicting_plugins[ $plugin_section ] = [];
+ }
+
+ if ( ! in_array( $plugin, $this->active_conflicting_plugins[ $plugin_section ], true ) ) {
+ $this->active_conflicting_plugins[ $plugin_section ][] = $plugin;
+ }
+ }
+
+ /**
+ * Search in $this->plugins for the given $plugin.
+ *
+ * If there is a result it will return the plugin category.
+ *
+ * @param string $plugin Plugin basename string.
+ *
+ * @return int|string
+ */
+ protected function find_plugin_category( $plugin ) {
+ foreach ( $this->plugins as $plugin_section => $plugins ) {
+ if ( in_array( $plugin, $plugins, true ) ) {
+ return $plugin_section;
+ }
+ }
+ }
+
+ /**
+ * Get plugin name from file.
+ *
+ * @param string $plugin Plugin path relative to plugins directory.
+ *
+ * @return string|bool Plugin name or false when no name is set.
+ */
+ protected function get_plugin_name( $plugin ) {
+ $plugin_details = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin );
+
+ if ( $plugin_details['Name'] !== '' ) {
+ return $plugin_details['Name'];
+ }
+
+ return false;
+ }
+
+ /**
+ * When being in the deactivation process the currently deactivated plugin has to be removed.
+ *
+ * @return void
+ */
+ private function remove_deactivated_plugin() {
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: On the deactivation screen the nonce is already checked by WordPress itself.
+ if ( ! isset( $_GET['plugin'] ) || ! is_string( $_GET['plugin'] ) ) {
+ return;
+ }
+
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: On the deactivation screen the nonce is already checked by WordPress itself.
+ $deactivated_plugin = sanitize_text_field( wp_unslash( $_GET['plugin'] ) );
+ $key_to_remove = array_search( $deactivated_plugin, $this->all_active_plugins, true );
+
+ if ( $key_to_remove !== false ) {
+ unset( $this->all_active_plugins[ $key_to_remove ] );
+ }
+ }
+
+ /**
+ * Get the identifier from the plugin file.
+ *
+ * @param string $plugin_file Plugin file to get Identifier from.
+ *
+ * @return string
+ */
+ private function get_notification_identifier( $plugin_file ) {
+ return md5( $plugin_file );
+ }
+}
diff --git a/wp-content/plugins/wordpress-seo/admin/endpoints/class-endpoint-file-size.php b/wp-content/plugins/wordpress-seo/admin/endpoints/class-endpoint-file-size.php
new file mode 100755
index 00000000..9f2bec07
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/endpoints/class-endpoint-file-size.php
@@ -0,0 +1,85 @@
+service = $service;
+ }
+
+ /**
+ * Registers the routes for the endpoints.
+ *
+ * @return void
+ */
+ public function register() {
+ $route_args = [
+ 'methods' => 'GET',
+ 'args' => [
+ 'url' => [
+ 'required' => true,
+ 'type' => 'string',
+ 'description' => 'The url to retrieve',
+ ],
+ ],
+ 'callback' => [
+ $this->service,
+ 'get',
+ ],
+ 'permission_callback' => [
+ $this,
+ 'can_retrieve_data',
+ ],
+ ];
+ register_rest_route( self::REST_NAMESPACE, self::ENDPOINT_SINGULAR, $route_args );
+ }
+
+ /**
+ * Determines whether or not data can be retrieved for the registered endpoints.
+ *
+ * @return bool Whether or not data can be retrieved.
+ */
+ public function can_retrieve_data() {
+ return current_user_can( self::CAPABILITY_RETRIEVE );
+ }
+}
diff --git a/wp-content/plugins/wordpress-seo/admin/endpoints/class-endpoint-statistics.php b/wp-content/plugins/wordpress-seo/admin/endpoints/class-endpoint-statistics.php
new file mode 100755
index 00000000..392d1c13
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/endpoints/class-endpoint-statistics.php
@@ -0,0 +1,73 @@
+service = $service;
+ }
+
+ /**
+ * Registers the REST routes that are available on the endpoint.
+ *
+ * @return void
+ */
+ public function register() {
+ // Register fetch config.
+ $route_args = [
+ 'methods' => 'GET',
+ 'callback' => [ $this->service, 'get_statistics' ],
+ 'permission_callback' => [ $this, 'can_retrieve_data' ],
+ ];
+ register_rest_route( self::REST_NAMESPACE, self::ENDPOINT_RETRIEVE, $route_args );
+ }
+
+ /**
+ * Determines whether or not data can be retrieved for the registered endpoints.
+ *
+ * @return bool Whether or not data can be retrieved.
+ */
+ public function can_retrieve_data() {
+ return current_user_can( self::CAPABILITY_RETRIEVE );
+ }
+}
diff --git a/wp-content/plugins/wordpress-seo/admin/endpoints/class-endpoint.php b/wp-content/plugins/wordpress-seo/admin/endpoints/class-endpoint.php
new file mode 100755
index 00000000..abbc9d0e
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/endpoints/class-endpoint.php
@@ -0,0 +1,26 @@
+is_filter_active() ) {
+ add_action( 'restrict_manage_posts', [ $this, 'render_hidden_input' ] );
+ }
+
+ if ( $this->is_filter_active() ) {
+ add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_explanation_assets' ] );
+ }
+ }
+
+ /**
+ * Adds the filter links to the view_edit screens to give the user a filter link.
+ *
+ * @return void
+ */
+ public function add_filter_links() {
+ foreach ( $this->get_post_types() as $post_type ) {
+ add_filter( 'views_edit-' . $post_type, [ $this, 'add_filter_link' ] );
+ }
+ }
+
+ /**
+ * Enqueues the necessary assets to display a filter explanation.
+ *
+ * @return void
+ */
+ public function enqueue_explanation_assets() {
+ $explanation = $this->get_explanation();
+
+ if ( $explanation === null ) {
+ return;
+ }
+
+ $asset_manager = new WPSEO_Admin_Asset_Manager();
+ $asset_manager->enqueue_script( 'filter-explanation' );
+ $asset_manager->enqueue_style( 'filter-explanation' );
+ $asset_manager->localize_script(
+ 'filter-explanation',
+ 'yoastFilterExplanation',
+ [ 'text' => $explanation ]
+ );
+ }
+
+ /**
+ * Adds a filter link to the views.
+ *
+ * @param array $views Array with the views.
+ *
+ * @return array Array of views including the added view.
+ */
+ public function add_filter_link( $views ) {
+ $views[ 'yoast_' . $this->get_query_val() ] = sprintf(
+ '%3$s (%4$s)',
+ esc_url( $this->get_filter_url() ),
+ ( $this->is_filter_active() ) ? ' class="current" aria-current="page"' : '',
+ $this->get_label(),
+ $this->get_post_total()
+ );
+
+ return $views;
+ }
+
+ /**
+ * Returns a text explaining this filter. Null if no explanation is necessary.
+ *
+ * @return string|null The explanation or null.
+ */
+ protected function get_explanation() {
+ return null;
+ }
+
+ /**
+ * Renders a hidden input to preserve this filter's state when using sub-filters.
+ *
+ * @return void
+ */
+ public function render_hidden_input() {
+ echo '';
+ }
+
+ /**
+ * Returns an url to edit.php with post_type and this filter as the query arguments.
+ *
+ * @return string The url to activate this filter.
+ */
+ protected function get_filter_url() {
+ $query_args = [
+ self::FILTER_QUERY_ARG => $this->get_query_val(),
+ 'post_type' => $this->get_current_post_type(),
+ ];
+
+ return add_query_arg( $query_args, 'edit.php' );
+ }
+
+ /**
+ * Returns true when the filter is active.
+ *
+ * @return bool Whether the filter is active.
+ */
+ protected function is_filter_active() {
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
+ if ( isset( $_GET[ self::FILTER_QUERY_ARG ] ) && is_string( $_GET[ self::FILTER_QUERY_ARG ] ) ) {
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
+ return sanitize_text_field( wp_unslash( $_GET[ self::FILTER_QUERY_ARG ] ) ) === $this->get_query_val();
+ }
+ return false;
+ }
+
+ /**
+ * Returns the current post type.
+ *
+ * @return string The current post type.
+ */
+ protected function get_current_post_type() {
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
+ if ( isset( $_GET['post_type'] ) && is_string( $_GET['post_type'] ) ) {
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
+ $post_type = sanitize_text_field( wp_unslash( $_GET['post_type'] ) );
+ if ( ! empty( $post_type ) ) {
+ return $post_type;
+ }
+ }
+ return 'post';
+ }
+
+ /**
+ * Returns the post types to which this filter should be added.
+ *
+ * @return array The post types to which this filter should be added.
+ */
+ protected function get_post_types() {
+ return WPSEO_Post_Type::get_accessible_post_types();
+ }
+
+ /**
+ * Checks if the post type is supported.
+ *
+ * @param string $post_type Post type to check against.
+ *
+ * @return bool True when it is supported.
+ */
+ protected function is_supported_post_type( $post_type ) {
+ return in_array( $post_type, $this->get_post_types(), true );
+ }
+}
diff --git a/wp-content/plugins/wordpress-seo/admin/filters/class-cornerstone-filter.php b/wp-content/plugins/wordpress-seo/admin/filters/class-cornerstone-filter.php
new file mode 100755
index 00000000..19831289
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/filters/class-cornerstone-filter.php
@@ -0,0 +1,150 @@
+is_filter_active() ) {
+ global $wpdb;
+
+ $where .= $wpdb->prepare(
+ " AND {$wpdb->posts}.ID IN ( SELECT post_id FROM {$wpdb->postmeta} WHERE meta_key = %s AND meta_value = '1' ) ",
+ WPSEO_Meta::$meta_prefix . self::META_NAME
+ );
+ }
+
+ return $where;
+ }
+
+ /**
+ * Filters the post types that have the metabox disabled.
+ *
+ * @param array $post_types The post types to filter.
+ *
+ * @return array The filtered post types.
+ */
+ public function filter_metabox_disabled( $post_types ) {
+ $filtered_post_types = [];
+ foreach ( $post_types as $post_type_key => $post_type ) {
+ if ( ! WPSEO_Post_Type::has_metabox_enabled( $post_type_key ) ) {
+ continue;
+ }
+
+ $filtered_post_types[ $post_type_key ] = $post_type;
+ }
+
+ return $filtered_post_types;
+ }
+
+ /**
+ * Returns the label for this filter.
+ *
+ * @return string The label for this filter.
+ */
+ protected function get_label() {
+ return __( 'Cornerstone content', 'wordpress-seo' );
+ }
+
+ /**
+ * Returns a text explaining this filter.
+ *
+ * @return string|null The explanation.
+ */
+ protected function get_explanation() {
+ $post_type_object = get_post_type_object( $this->get_current_post_type() );
+
+ if ( $post_type_object === null ) {
+ return null;
+ }
+
+ return sprintf(
+ /* translators: %1$s expands to the posttype label, %2$s expands anchor to blog post about cornerstone content, %3$s expands to */
+ __( 'Mark the most important %1$s as \'cornerstone content\' to improve your site structure. %2$sLearn more about cornerstone content%3$s.', 'wordpress-seo' ),
+ strtolower( $post_type_object->labels->name ),
+ '',
+ ''
+ );
+ }
+
+ /**
+ * Returns the total amount of articles marked as cornerstone content.
+ *
+ * @return int
+ */
+ protected function get_post_total() {
+ global $wpdb;
+
+ return (int) $wpdb->get_var(
+ $wpdb->prepare(
+ "SELECT COUNT( 1 )
+ FROM {$wpdb->postmeta}
+ WHERE post_id IN( SELECT ID FROM {$wpdb->posts} WHERE post_type = %s ) AND
+ meta_key = %s AND meta_value = '1'
+ ",
+ $this->get_current_post_type(),
+ WPSEO_Meta::$meta_prefix . self::META_NAME
+ )
+ );
+ }
+
+ /**
+ * Returns the post types to which this filter should be added.
+ *
+ * @return array The post types to which this filter should be added.
+ */
+ protected function get_post_types() {
+ /**
+ * Filter: 'wpseo_cornerstone_post_types' - Filters post types to exclude the cornerstone feature for.
+ *
+ * @param array $post_types The accessible post types to filter.
+ */
+ $post_types = apply_filters( 'wpseo_cornerstone_post_types', parent::get_post_types() );
+ if ( ! is_array( $post_types ) ) {
+ return [];
+ }
+
+ return $post_types;
+ }
+}
diff --git a/wp-content/plugins/wordpress-seo/admin/formatter/class-metabox-formatter.php b/wp-content/plugins/wordpress-seo/admin/formatter/class-metabox-formatter.php
new file mode 100755
index 00000000..0a765c2d
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/formatter/class-metabox-formatter.php
@@ -0,0 +1,81 @@
+formatter = $formatter;
+ }
+
+ /**
+ * Returns the values.
+ *
+ * @return array|bool|int>
+ */
+ public function get_values() {
+ $defaults = $this->get_defaults();
+ $values = $this->formatter->get_values();
+
+ return ( $values + $defaults );
+ }
+
+ /**
+ * Returns array with all the values always needed by a scraper object.
+ *
+ * @return array|bool|int> Default settings for the metabox.
+ */
+ private function get_defaults() {
+ $schema_types = new Schema_Types();
+
+ $defaults = [
+ 'author_name' => get_the_author_meta( 'display_name' ),
+ 'keyword_usage' => [],
+ 'title_template' => '',
+ 'metadesc_template' => '',
+ 'schema' => [
+ 'displayFooter' => WPSEO_Capability_Utils::current_user_can( 'wpseo_manage_options' ),
+ 'pageTypeOptions' => $schema_types->get_page_type_options(),
+ 'articleTypeOptions' => $schema_types->get_article_type_options(),
+ ],
+ 'twitterCardType' => 'summary_large_image',
+ /**
+ * Filter to determine if the markers should be enabled or not.
+ *
+ * @param bool $showMarkers Should the markers being enabled. Default = true.
+ */
+ 'show_markers' => apply_filters( 'wpseo_enable_assessment_markers', true ),
+ ];
+
+ $integration_information_repo = YoastSEO()->classes->get( Integration_Information_Repository::class );
+
+ $enabled_integrations = $integration_information_repo->get_integration_information();
+ $defaults = array_merge( $defaults, $enabled_integrations );
+ $enabled_features_repo = YoastSEO()->classes->get( Enabled_Analysis_Features_Repository::class );
+
+ $enabled_features = $enabled_features_repo->get_enabled_features()->parse_to_legacy_array();
+ return array_merge( $defaults, $enabled_features );
+ }
+}
diff --git a/wp-content/plugins/wordpress-seo/admin/formatter/class-post-metabox-formatter.php b/wp-content/plugins/wordpress-seo/admin/formatter/class-post-metabox-formatter.php
new file mode 100755
index 00000000..909876d2
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/formatter/class-post-metabox-formatter.php
@@ -0,0 +1,95 @@
+post = $post;
+ $this->permalink = $structure;
+ }
+
+ /**
+ * Determines whether the social templates should be used.
+ *
+ * @deprecated 23.1
+ * @codeCoverageIgnore
+ *
+ * @return void
+ */
+ public function use_social_templates() {
+ _deprecated_function( __METHOD__, 'Yoast SEO 23.1' );
+ }
+
+ /**
+ * Returns the translated values.
+ *
+ * @return array
+ */
+ public function get_values() {
+
+ $values = [
+ 'metaDescriptionDate' => '',
+ ];
+
+ if ( $this->post instanceof WP_Post ) {
+
+ /** @var Post_Seo_Information_Repository $repo */
+ $repo = YoastSEO()->classes->get( Post_Seo_Information_Repository::class );
+ $repo->set_post( $this->post );
+
+ $values_to_set = [
+ 'isInsightsEnabled' => $this->is_insights_enabled(),
+ ];
+
+ $values = ( $values_to_set + $values );
+ $values = ( $repo->get_seo_data() + $values );
+ }
+
+ /**
+ * Filter: 'wpseo_post_edit_values' - Allows changing the values Yoast SEO uses inside the post editor.
+ *
+ * @param array $values The key-value map Yoast SEO uses inside the post editor.
+ * @param WP_Post $post The post opened in the editor.
+ */
+ return apply_filters( 'wpseo_post_edit_values', $values, $this->post );
+ }
+
+ /**
+ * Determines whether the insights feature is enabled for this post.
+ *
+ * @return bool
+ */
+ protected function is_insights_enabled() {
+ return WPSEO_Options::get( 'enable_metabox_insights', false );
+ }
+}
diff --git a/wp-content/plugins/wordpress-seo/admin/formatter/class-term-metabox-formatter.php b/wp-content/plugins/wordpress-seo/admin/formatter/class-term-metabox-formatter.php
new file mode 100755
index 00000000..29218d38
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/formatter/class-term-metabox-formatter.php
@@ -0,0 +1,98 @@
+taxonomy = $taxonomy;
+ $this->term = $term;
+
+ $this->use_social_templates = $this->use_social_templates();
+ }
+
+ /**
+ * Determines whether the social templates should be used.
+ *
+ * @return bool Whether the social templates should be used.
+ */
+ public function use_social_templates() {
+ return WPSEO_Options::get( 'opengraph', false ) === true;
+ }
+
+ /**
+ * Returns the translated values.
+ *
+ * @return array
+ */
+ public function get_values() {
+ $values = [];
+
+ // Todo: a column needs to be added on the termpages to add a filter for the keyword, so this can be used in the focus keyphrase doubles.
+ if ( is_object( $this->term ) && property_exists( $this->term, 'taxonomy' ) ) {
+ $values = [
+ 'taxonomy' => $this->term->taxonomy,
+ 'semrushIntegrationActive' => 0,
+ 'wincherIntegrationActive' => 0,
+ 'isInsightsEnabled' => $this->is_insights_enabled(),
+ ];
+
+ $repo = YoastSEO()->classes->get( Term_Seo_Information_Repository::class );
+ $repo->set_term( $this->term );
+ $values = ( $repo->get_seo_data() + $values );
+ }
+
+ return $values;
+ }
+
+ /**
+ * Determines whether the insights feature is enabled for this taxonomy.
+ *
+ * @return bool
+ */
+ protected function is_insights_enabled() {
+ return WPSEO_Options::get( 'enable_metabox_insights', false );
+ }
+}
diff --git a/wp-content/plugins/wordpress-seo/admin/formatter/interface-metabox-formatter.php b/wp-content/plugins/wordpress-seo/admin/formatter/interface-metabox-formatter.php
new file mode 100755
index 00000000..8c220480
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/formatter/interface-metabox-formatter.php
@@ -0,0 +1,19 @@
+admin_header( false, 'wpseo-gsc', false, 'yoast_wpseo_gsc_options' );
+
+// GSC Error notification.
+$gsc_url = 'https://search.google.com/search-console/index';
+$gsc_post_url = 'https://yoa.st/google-search-console-deprecated';
+$gsc_style_alert = '
+ display: flex;
+ align-items: baseline;
+ position: relative;
+ padding: 16px;
+ border: 1px solid rgba(0, 0, 0, 0.2);
+ font-size: 14px;
+ font-weight: 400;
+ line-height: 1.5;
+ margin: 16px 0;
+ color: #450c11;
+ background: #f8d7da;
+';
+$gsc_style_alert_icon = 'display: block; margin-right: 8px;';
+$gsc_style_alert_content = 'max-width: 600px;';
+$gsc_style_alert_link = 'color: #004973;';
+$gsc_notification = sprintf(
+ /* Translators: %1$s: expands to opening anchor tag, %2$s expands to closing anchor tag. */
+ __( 'Google has discontinued its Crawl Errors API. Therefore, any possible crawl errors you might have cannot be displayed here anymore. %1$sRead our statement on this for further information%2$s.', 'wordpress-seo' ),
+ '',
+ WPSEO_Admin_Utils::get_new_tab_message() . ''
+);
+$gsc_notification .= '
';
+$gsc_notification .= sprintf(
+ /* Translators: %1$s: expands to opening anchor tag, %2$s expands to closing anchor tag. */
+ __( 'To view your current crawl errors, %1$splease visit Google Search Console%2$s.', 'wordpress-seo' ),
+ '',
+ WPSEO_Admin_Utils::get_new_tab_message() . ''
+);
+?>
+
+
+
+
+
+
+';
+printf(
+ /* Translators: %s: expands to Yoast SEO Premium */
+ esc_html__( 'Creating redirects is a %s feature', 'wordpress-seo' ),
+ 'Yoast SEO Premium'
+);
+echo '';
+echo '
';
+printf(
+ /* Translators: %1$s: expands to 'Yoast SEO Premium', %2$s: links to Yoast SEO Premium plugin page. */
+ esc_html__( 'To be able to create a redirect and fix this issue, you need %1$s. You can buy the plugin, including one year of support and updates, on %2$s.', 'wordpress-seo' ),
+ 'Yoast SEO Premium',
+ 'yoast.com'
+);
+echo '
';
+echo '';
diff --git a/wp-content/plugins/wordpress-seo/admin/import/class-import-detector.php b/wp-content/plugins/wordpress-seo/admin/import/class-import-detector.php
new file mode 100755
index 00000000..48d31cc1
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/import/class-import-detector.php
@@ -0,0 +1,36 @@
+status->status ) {
+ $this->needs_import[ $importer_class ] = $importer->get_plugin_name();
+ }
+ }
+ }
+}
diff --git a/wp-content/plugins/wordpress-seo/admin/import/class-import-plugin.php b/wp-content/plugins/wordpress-seo/admin/import/class-import-plugin.php
new file mode 100755
index 00000000..d71fff83
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/import/class-import-plugin.php
@@ -0,0 +1,63 @@
+importer = $importer;
+
+ switch ( $action ) {
+ case 'cleanup':
+ $this->status = $this->importer->run_cleanup();
+ break;
+ case 'import':
+ $this->status = $this->importer->run_import();
+ break;
+ case 'detect':
+ default:
+ $this->status = $this->importer->run_detect();
+ }
+
+ $this->status->set_msg( $this->complete_msg( $this->status->get_msg() ) );
+ }
+
+ /**
+ * Convenience function to replace %s with plugin name in import message.
+ *
+ * @param string $msg Message string.
+ *
+ * @return string Returns message with plugin name instead of replacement variables.
+ */
+ protected function complete_msg( $msg ) {
+ return sprintf( $msg, $this->importer->get_plugin_name() );
+ }
+}
diff --git a/wp-content/plugins/wordpress-seo/admin/import/class-import-settings.php b/wp-content/plugins/wordpress-seo/admin/import/class-import-settings.php
new file mode 100755
index 00000000..3bec4c8f
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/import/class-import-settings.php
@@ -0,0 +1,127 @@
+status = new WPSEO_Import_Status( 'import', false );
+ }
+
+ /**
+ * Imports the data submitted by the user.
+ *
+ * @return void
+ */
+ public function import() {
+ check_admin_referer( self::NONCE_ACTION );
+
+ if ( ! WPSEO_Capability_Utils::current_user_can( 'wpseo_manage_options' ) ) {
+ return;
+ }
+
+ if ( ! isset( $_POST['settings_import'] ) || ! is_string( $_POST['settings_import'] ) ) {
+ return;
+ }
+
+ // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: The raw content will be parsed afterwards.
+ $content = wp_unslash( $_POST['settings_import'] );
+
+ if ( empty( $content ) ) {
+ return;
+ }
+
+ $this->parse_options( $content );
+ }
+
+ /**
+ * Parse the options.
+ *
+ * @param string $raw_options The content to parse.
+ *
+ * @return void
+ */
+ protected function parse_options( $raw_options ) {
+ $options = parse_ini_string( $raw_options, true, INI_SCANNER_RAW );
+
+ if ( is_array( $options ) && $options !== [] ) {
+ $this->import_options( $options );
+
+ return;
+ }
+
+ $this->status->set_msg( __( 'Settings could not be imported:', 'wordpress-seo' ) . ' ' . __( 'No settings found.', 'wordpress-seo' ) );
+ }
+
+ /**
+ * Parse the option group and import it.
+ *
+ * @param string $name Name string.
+ * @param array $option_group Option group data.
+ * @param array $options Options data.
+ *
+ * @return void
+ */
+ protected function parse_option_group( $name, $option_group, $options ) {
+ // Make sure that the imported options are cleaned/converted on import.
+ $option_instance = WPSEO_Options::get_option_instance( $name );
+ if ( is_object( $option_instance ) && method_exists( $option_instance, 'import' ) ) {
+ $option_instance->import( $option_group, $this->old_wpseo_version, $options );
+ }
+ }
+
+ /**
+ * Imports the options if found.
+ *
+ * @param array $options The options parsed from the provided settings.
+ *
+ * @return void
+ */
+ protected function import_options( $options ) {
+ if ( isset( $options['wpseo']['version'] ) && $options['wpseo']['version'] !== '' ) {
+ $this->old_wpseo_version = $options['wpseo']['version'];
+ }
+
+ foreach ( $options as $name => $option_group ) {
+ $this->parse_option_group( $name, $option_group, $options );
+ }
+
+ $this->status->set_msg( __( 'Settings successfully imported.', 'wordpress-seo' ) );
+ $this->status->set_status( true );
+
+ // Reset the cached option values.
+ WPSEO_Options::clear_cache();
+ }
+}
diff --git a/wp-content/plugins/wordpress-seo/admin/import/class-import-status.php b/wp-content/plugins/wordpress-seo/admin/import/class-import-status.php
new file mode 100755
index 00000000..c105d4a7
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/import/class-import-status.php
@@ -0,0 +1,131 @@
+action = $action;
+ $this->status = $status;
+ $this->msg = $msg;
+ }
+
+ /**
+ * Get the import message.
+ *
+ * @return string Message about current status.
+ */
+ public function get_msg() {
+ if ( $this->msg !== '' ) {
+ return $this->msg;
+ }
+
+ if ( $this->status === false ) {
+ /* translators: %s is replaced with the name of the plugin we're trying to find data from. */
+ return __( '%s data not found.', 'wordpress-seo' );
+ }
+
+ return $this->get_default_success_message();
+ }
+
+ /**
+ * Get the import action.
+ *
+ * @return string Import action type.
+ */
+ public function get_action() {
+ return $this->action;
+ }
+
+ /**
+ * Set the import action, set status to false.
+ *
+ * @param string $action The type of action to set as import action.
+ *
+ * @return void
+ */
+ public function set_action( $action ) {
+ $this->action = $action;
+ $this->status = false;
+ }
+
+ /**
+ * Sets the importer status message.
+ *
+ * @param string $msg The message to set.
+ *
+ * @return void
+ */
+ public function set_msg( $msg ) {
+ $this->msg = $msg;
+ }
+
+ /**
+ * Sets the importer status.
+ *
+ * @param bool $status The status to set.
+ *
+ * @return WPSEO_Import_Status The current object.
+ */
+ public function set_status( $status ) {
+ $this->status = (bool) $status;
+
+ return $this;
+ }
+
+ /**
+ * Returns a success message depending on the action.
+ *
+ * @return string Returns a success message for the current action.
+ */
+ private function get_default_success_message() {
+ switch ( $this->action ) {
+ case 'import':
+ /* translators: %s is replaced with the name of the plugin we're importing data from. */
+ return __( '%s data successfully imported.', 'wordpress-seo' );
+ case 'cleanup':
+ /* translators: %s is replaced with the name of the plugin we're removing data from. */
+ return __( '%s data successfully removed.', 'wordpress-seo' );
+ case 'detect':
+ default:
+ /* translators: %s is replaced with the name of the plugin we've found data from. */
+ return __( '%s data found.', 'wordpress-seo' );
+ }
+ }
+}
diff --git a/wp-content/plugins/wordpress-seo/admin/import/plugins/class-abstract-plugin-importer.php b/wp-content/plugins/wordpress-seo/admin/import/plugins/class-abstract-plugin-importer.php
new file mode 100755
index 00000000..6f5674f2
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/import/plugins/class-abstract-plugin-importer.php
@@ -0,0 +1,329 @@
+plugin_name;
+ }
+
+ /**
+ * Imports the settings and post meta data from another SEO plugin.
+ *
+ * @return WPSEO_Import_Status Import status object.
+ */
+ public function run_import() {
+ $this->status = new WPSEO_Import_Status( 'import', false );
+
+ if ( ! $this->detect() ) {
+ return $this->status;
+ }
+
+ $this->status->set_status( $this->import() );
+
+ // Flush the entire cache, as we no longer know what's valid and what's not.
+ wp_cache_flush();
+
+ return $this->status;
+ }
+
+ /**
+ * Handles post meta data to import.
+ *
+ * @return bool Import success status.
+ */
+ protected function import() {
+ return $this->meta_keys_clone( $this->clone_keys );
+ }
+
+ /**
+ * Removes the plugin data from the database.
+ *
+ * @return WPSEO_Import_Status Import status object.
+ */
+ public function run_cleanup() {
+ $this->status = new WPSEO_Import_Status( 'cleanup', false );
+
+ if ( ! $this->detect() ) {
+ return $this->status;
+ }
+
+ return $this->status->set_status( $this->cleanup() );
+ }
+
+ /**
+ * Removes the plugin data from the database.
+ *
+ * @return bool Cleanup status.
+ */
+ protected function cleanup() {
+ global $wpdb;
+ if ( empty( $this->meta_key ) ) {
+ return true;
+ }
+ $wpdb->query(
+ $wpdb->prepare(
+ "DELETE FROM {$wpdb->postmeta} WHERE meta_key LIKE %s",
+ $this->meta_key
+ )
+ );
+ $result = $wpdb->__get( 'result' );
+ if ( ! $result ) {
+ $this->cleanup_error_msg();
+ }
+
+ return $result;
+ }
+
+ /**
+ * Sets the status message for when a cleanup has gone bad.
+ *
+ * @return void
+ */
+ protected function cleanup_error_msg() {
+ /* translators: %s is replaced with the plugin's name. */
+ $this->status->set_msg( sprintf( __( 'Cleanup of %s data failed.', 'wordpress-seo' ), $this->plugin_name ) );
+ }
+
+ /**
+ * Detects whether an import for this plugin is needed.
+ *
+ * @return WPSEO_Import_Status Import status object.
+ */
+ public function run_detect() {
+ $this->status = new WPSEO_Import_Status( 'detect', false );
+
+ if ( ! $this->detect() ) {
+ return $this->status;
+ }
+
+ return $this->status->set_status( true );
+ }
+
+ /**
+ * Detects whether there is post meta data to import.
+ *
+ * @return bool Boolean indicating whether there is something to import.
+ */
+ protected function detect() {
+ global $wpdb;
+
+ $meta_keys = wp_list_pluck( $this->clone_keys, 'old_key' );
+ $result = $wpdb->get_var(
+ $wpdb->prepare(
+ "SELECT COUNT(*) AS `count`
+ FROM {$wpdb->postmeta}
+ WHERE meta_key IN ( " . implode( ', ', array_fill( 0, count( $meta_keys ), '%s' ) ) . ' )',
+ $meta_keys
+ )
+ );
+
+ if ( $result === '0' ) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Helper function to clone meta keys and (optionally) change their values in bulk.
+ *
+ * @param string $old_key The existing meta key.
+ * @param string $new_key The new meta key.
+ * @param array $replace_values An array, keys old value, values new values.
+ *
+ * @return bool Clone status.
+ */
+ protected function meta_key_clone( $old_key, $new_key, $replace_values = [] ) {
+ global $wpdb;
+
+ // First we create a temp table with all the values for meta_key.
+ $result = $wpdb->query(
+ $wpdb->prepare(
+ // phpcs:ignore WordPress.DB.DirectDatabaseQuery.SchemaChange -- This is intentional + temporary.
+ "CREATE TEMPORARY TABLE tmp_meta_table SELECT * FROM {$wpdb->postmeta} WHERE meta_key = %s",
+ $old_key
+ )
+ );
+ if ( $result === false ) {
+ $this->set_missing_db_rights_status();
+ return false;
+ }
+
+ // Delete all the values in our temp table for posts that already have data for $new_key.
+ $wpdb->query(
+ $wpdb->prepare(
+ "DELETE FROM tmp_meta_table WHERE post_id IN ( SELECT post_id FROM {$wpdb->postmeta} WHERE meta_key = %s )",
+ WPSEO_Meta::$meta_prefix . $new_key
+ )
+ );
+
+ /*
+ * We set meta_id to NULL so on re-insert into the postmeta table, MYSQL can set
+ * new meta_id's and we don't get duplicates.
+ */
+ $wpdb->query( 'UPDATE tmp_meta_table SET meta_id = NULL' );
+
+ // Now we rename the meta_key.
+ $wpdb->query(
+ $wpdb->prepare(
+ 'UPDATE tmp_meta_table SET meta_key = %s',
+ WPSEO_Meta::$meta_prefix . $new_key
+ )
+ );
+
+ $this->meta_key_clone_replace( $replace_values );
+
+ // With everything done, we insert all our newly cloned lines into the postmeta table.
+ $wpdb->query( "INSERT INTO {$wpdb->postmeta} SELECT * FROM tmp_meta_table" );
+
+ // Now we drop our temporary table.
+ // phpcs:ignore WordPress.DB.DirectDatabaseQuery.SchemaChange -- This is intentional + a temporary table.
+ $wpdb->query( 'DROP TEMPORARY TABLE IF EXISTS tmp_meta_table' );
+
+ return true;
+ }
+
+ /**
+ * Clones multiple meta keys.
+ *
+ * @param array $clone_keys The keys to clone.
+ *
+ * @return bool Success status.
+ */
+ protected function meta_keys_clone( $clone_keys ) {
+ foreach ( $clone_keys as $clone_key ) {
+ $result = $this->meta_key_clone( $clone_key['old_key'], $clone_key['new_key'], ( $clone_key['convert'] ?? [] ) );
+ if ( ! $result ) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Sets the import status to false and returns a message about why it failed.
+ *
+ * @return void
+ */
+ protected function set_missing_db_rights_status() {
+ $this->status->set_status( false );
+ /* translators: %s is replaced with Yoast SEO. */
+ $this->status->set_msg( sprintf( __( 'The %s importer functionality uses temporary database tables. It seems your WordPress install does not have the capability to do this, please consult your hosting provider.', 'wordpress-seo' ), 'Yoast SEO' ) );
+ }
+
+ /**
+ * Helper function to search for a key in an array and maybe save it as a meta field.
+ *
+ * @param string $plugin_key The key in the $data array to check.
+ * @param string $yoast_key The identifier we use in our meta settings.
+ * @param array $data The array of data for this post to sift through.
+ * @param int $post_id The post ID.
+ *
+ * @return void
+ */
+ protected function import_meta_helper( $plugin_key, $yoast_key, $data, $post_id ) {
+ if ( ! empty( $data[ $plugin_key ] ) ) {
+ $this->maybe_save_post_meta( $yoast_key, $data[ $plugin_key ], $post_id );
+ }
+ }
+
+ /**
+ * Saves a post meta value if it doesn't already exist.
+ *
+ * @param string $new_key The key to save.
+ * @param mixed $value The value to set the key to.
+ * @param int $post_id The Post to save the meta for.
+ *
+ * @return void
+ */
+ protected function maybe_save_post_meta( $new_key, $value, $post_id ) {
+ // Big. Fat. Sigh. Mostly used for _yst_is_cornerstone, but might be useful for other hidden meta's.
+ $key = WPSEO_Meta::$meta_prefix . $new_key;
+ $wpseo_meta = true;
+ if ( substr( $new_key, 0, 1 ) === '_' ) {
+ $key = $new_key;
+ $wpseo_meta = false;
+ }
+
+ $existing_value = get_post_meta( $post_id, $key, true );
+ if ( empty( $existing_value ) ) {
+ if ( $wpseo_meta ) {
+ WPSEO_Meta::set_value( $new_key, $value, $post_id );
+ return;
+ }
+ update_post_meta( $post_id, $new_key, $value );
+ }
+ }
+
+ /**
+ * Replaces values in our temporary table according to our settings.
+ *
+ * @param array $replace_values Key value pair of values to replace with other values.
+ *
+ * @return void
+ */
+ protected function meta_key_clone_replace( $replace_values ) {
+ global $wpdb;
+
+ // Now we replace values if needed.
+ if ( is_array( $replace_values ) && $replace_values !== [] ) {
+ foreach ( $replace_values as $old_value => $new_value ) {
+ $wpdb->query(
+ $wpdb->prepare(
+ 'UPDATE tmp_meta_table SET meta_value = %s WHERE meta_value = %s',
+ $new_value,
+ $old_value
+ )
+ );
+ }
+ }
+ }
+}
diff --git a/wp-content/plugins/wordpress-seo/admin/import/plugins/class-import-aioseo-v4.php b/wp-content/plugins/wordpress-seo/admin/import/plugins/class-import-aioseo-v4.php
new file mode 100755
index 00000000..9cd5dc61
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/import/plugins/class-import-aioseo-v4.php
@@ -0,0 +1,241 @@
+ '_aioseo_title',
+ 'new_key' => 'title',
+ ],
+ [
+ 'old_key' => '_aioseo_description',
+ 'new_key' => 'metadesc',
+ ],
+ [
+ 'old_key' => '_aioseo_og_title',
+ 'new_key' => 'opengraph-title',
+ ],
+ [
+ 'old_key' => '_aioseo_og_description',
+ 'new_key' => 'opengraph-description',
+ ],
+ [
+ 'old_key' => '_aioseo_twitter_title',
+ 'new_key' => 'twitter-title',
+ ],
+ [
+ 'old_key' => '_aioseo_twitter_description',
+ 'new_key' => 'twitter-description',
+ ],
+ ];
+
+ /**
+ * Mapping between the AiOSEO replace vars and the Yoast replace vars.
+ *
+ * @see https://yoast.com/help/list-available-snippet-variables-yoast-seo/
+ *
+ * @var array
+ */
+ protected $replace_vars = [
+ // They key is the AiOSEO replace var, the value is the Yoast replace var (see class-wpseo-replace-vars).
+ '#author_first_name' => '%%author_first_name%%',
+ '#author_last_name' => '%%author_last_name%%',
+ '#author_name' => '%%name%%',
+ '#categories' => '%%category%%',
+ '#current_date' => '%%currentdate%%',
+ '#current_day' => '%%currentday%%',
+ '#current_month' => '%%currentmonth%%',
+ '#current_year' => '%%currentyear%%',
+ '#permalink' => '%%permalink%%',
+ '#post_content' => '%%post_content%%',
+ '#post_date' => '%%date%%',
+ '#post_day' => '%%post_day%%',
+ '#post_month' => '%%post_month%%',
+ '#post_title' => '%%title%%',
+ '#post_year' => '%%post_year%%',
+ '#post_excerpt_only' => '%%excerpt_only%%',
+ '#post_excerpt' => '%%excerpt%%',
+ '#separator_sa' => '%%sep%%',
+ '#site_title' => '%%sitename%%',
+ '#tagline' => '%%sitedesc%%',
+ '#taxonomy_title' => '%%category_title%%',
+ ];
+
+ /**
+ * Replaces the AiOSEO variables in our temporary table with Yoast variables (replace vars).
+ *
+ * @param array $replace_values Key value pair of values to replace with other values. This is only used in the base class but not here.
+ * That is because this class doesn't have any `convert` keys in `$clone_keys`.
+ * For that reason, we're overwriting the base class' `meta_key_clone_replace()` function without executing that base functionality.
+ *
+ * @return void
+ */
+ protected function meta_key_clone_replace( $replace_values ) {
+ global $wpdb;
+
+ // At this point we're already looping through all the $clone_keys (this happens in meta_keys_clone() in the abstract class).
+ // Now, we'll also loop through the replace_vars array, which holds the mappings between the AiOSEO variables and the Yoast variables.
+ // We'll replace all the AiOSEO variables in the temporary table with their Yoast equivalents.
+ foreach ( $this->replace_vars as $aioseo_variable => $yoast_variable ) {
+ // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: We need this query and this is done at many other places as well, for example class-import-rankmath.
+ $wpdb->query(
+ $wpdb->prepare(
+ 'UPDATE tmp_meta_table SET meta_value = REPLACE( meta_value, %s, %s )',
+ $aioseo_variable,
+ $yoast_variable
+ )
+ );
+ }
+
+ // The AiOSEO custom fields take the form of `#custom_field-myfield`.
+ // These should be mapped to %%cf_myfield%%.
+ $meta_values_with_custom_fields = $this->get_meta_values_with_custom_field_or_taxonomy( $wpdb, 'custom_field' );
+ $unique_custom_fields = $this->get_unique_custom_fields_or_taxonomies( $meta_values_with_custom_fields, 'custom_field' );
+ $this->replace_custom_field_or_taxonomy_replace_vars( $unique_custom_fields, $wpdb, 'custom_field', 'cf' );
+
+ // Map `#tax_name-{tax-slug}` to `%%ct_{tax-slug}%%``.
+ $meta_values_with_custom_taxonomies = $this->get_meta_values_with_custom_field_or_taxonomy( $wpdb, 'tax_name' );
+ $unique_custom_taxonomies = $this->get_unique_custom_fields_or_taxonomies( $meta_values_with_custom_taxonomies, 'tax_name' );
+ $this->replace_custom_field_or_taxonomy_replace_vars( $unique_custom_taxonomies, $wpdb, 'tax_name', 'ct' );
+ }
+
+ /**
+ * Filters out all unique custom fields/taxonomies/etc. used in an AiOSEO replace var.
+ *
+ * @param string[] $meta_values An array of all the meta values that
+ * contain one or more AIOSEO custom field replace vars
+ * (in the form `#custom_field-xyz`).
+ * @param string $aioseo_prefix The AiOSEO prefix to use
+ * (e.g. `custom-field` for custom fields or `tax_name` for custom taxonomies).
+ *
+ * @return string[] An array of all the unique custom fields/taxonomies/etc. used in the replace vars.
+ * E.g. `xyz` in the above example.
+ */
+ protected function get_unique_custom_fields_or_taxonomies( $meta_values, $aioseo_prefix ) {
+ $unique_custom_fields_or_taxonomies = [];
+
+ foreach ( $meta_values as $meta_value ) {
+ // Find all custom field replace vars, store them in `$matches`.
+ preg_match_all(
+ "/#$aioseo_prefix-([\w-]+)/",
+ $meta_value,
+ $matches
+ );
+
+ /*
+ * `$matches[1]` contain the captured matches of the
+ * first capturing group (the `([\w-]+)` in the regex above).
+ */
+ $custom_fields_or_taxonomies = $matches[1];
+
+ foreach ( $custom_fields_or_taxonomies as $custom_field_or_taxonomy ) {
+ $unique_custom_fields_or_taxonomies[ trim( $custom_field_or_taxonomy ) ] = 1;
+ }
+ }
+
+ return array_keys( $unique_custom_fields_or_taxonomies );
+ }
+
+ /**
+ * Replaces every AIOSEO custom field/taxonomy/etc. replace var with the Yoast version.
+ *
+ * E.g. `#custom_field-xyz` becomes `%%cf_xyz%%`.
+ *
+ * @param string[] $unique_custom_fields_or_taxonomies An array of unique custom fields to replace the replace vars of.
+ * @param wpdb $wpdb The WordPress database object.
+ * @param string $aioseo_prefix The AiOSEO prefix to use
+ * (e.g. `custom-field` for custom fields or `tax_name` for custom taxonomies).
+ * @param string $yoast_prefix The Yoast prefix to use (e.g. `cf` for custom fields).
+ *
+ * @return void
+ */
+ protected function replace_custom_field_or_taxonomy_replace_vars( $unique_custom_fields_or_taxonomies, $wpdb, $aioseo_prefix, $yoast_prefix ) {
+ foreach ( $unique_custom_fields_or_taxonomies as $unique_custom_field_or_taxonomy ) {
+ $aioseo_variable = "#{$aioseo_prefix}-{$unique_custom_field_or_taxonomy}";
+ $yoast_variable = "%%{$yoast_prefix}_{$unique_custom_field_or_taxonomy}%%";
+
+ // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
+ $wpdb->query(
+ $wpdb->prepare(
+ 'UPDATE tmp_meta_table SET meta_value = REPLACE( meta_value, %s, %s )',
+ $aioseo_variable,
+ $yoast_variable
+ )
+ );
+ }
+ }
+
+ // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
+
+ /**
+ * Retrieve all the meta values from the temporary meta table that contain
+ * at least one AiOSEO custom field replace var.
+ *
+ * @param wpdb $wpdb The WordPress database object.
+ * @param string $aioseo_prefix The AiOSEO prefix to use
+ * (e.g. `custom-field` for custom fields or `tax_name` for custom taxonomies).
+ *
+ * @return string[] All meta values that contain at least one AioSEO custom field replace var.
+ */
+ protected function get_meta_values_with_custom_field_or_taxonomy( $wpdb, $aioseo_prefix ) {
+ return $wpdb->get_col(
+ $wpdb->prepare(
+ 'SELECT meta_value FROM tmp_meta_table WHERE meta_value LIKE %s',
+ "%#$aioseo_prefix-%"
+ )
+ );
+ }
+
+ // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
+
+ /**
+ * Detects whether there is AIOSEO data to import by looking whether the AIOSEO data have been cleaned up.
+ *
+ * @return bool Boolean indicating whether there is something to import.
+ */
+ protected function detect() {
+ $aioseo_cleanup_action = YoastSEO()->classes->get( Aioseo_Cleanup_Action::class );
+ return ( $aioseo_cleanup_action->get_total_unindexed() > 0 );
+ }
+
+ /**
+ * Import AIOSEO post data from their custom indexable table. Not currently used.
+ *
+ * @return void
+ */
+ protected function import() {
+ // This is overriden from the import.js and never run.
+ $aioseo_posts_import_action = YoastSEO()->classes->get( Aioseo_Posts_Importing_Action::class );
+ $aioseo_posts_import_action->index();
+ }
+}
diff --git a/wp-content/plugins/wordpress-seo/admin/import/plugins/class-import-aioseo.php b/wp-content/plugins/wordpress-seo/admin/import/plugins/class-import-aioseo.php
new file mode 100755
index 00000000..cf7ab491
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/import/plugins/class-import-aioseo.php
@@ -0,0 +1,110 @@
+ 'opengraph-title',
+ 'aioseop_opengraph_settings_desc' => 'opengraph-description',
+ 'aioseop_opengraph_settings_customimg' => 'opengraph-image',
+ 'aioseop_opengraph_settings_customimg_twitter' => 'twitter-image',
+ ];
+
+ /**
+ * Array of meta keys to detect and import.
+ *
+ * @var array
+ */
+ protected $clone_keys = [
+ [
+ 'old_key' => '_aioseop_title',
+ 'new_key' => 'title',
+ ],
+ [
+ 'old_key' => '_aioseop_description',
+ 'new_key' => 'metadesc',
+ ],
+ [
+ 'old_key' => '_aioseop_noindex',
+ 'new_key' => 'meta-robots-noindex',
+ 'convert' => [ 'on' => 1 ],
+ ],
+ [
+ 'old_key' => '_aioseop_nofollow',
+ 'new_key' => 'meta-robots-nofollow',
+ 'convert' => [ 'on' => 1 ],
+ ],
+ ];
+
+ /**
+ * Import All In One SEO meta values.
+ *
+ * @return bool Import success status.
+ */
+ protected function import() {
+ $status = parent::import();
+ if ( $status ) {
+ $this->import_opengraph();
+ }
+ return $status;
+ }
+
+ /**
+ * Imports the OpenGraph and Twitter settings for all posts.
+ *
+ * @return bool
+ */
+ protected function import_opengraph() {
+ $query_posts = new WP_Query( 'post_type=any&meta_key=_aioseop_opengraph_settings&order=ASC&fields=ids&nopaging=true' );
+
+ if ( ! empty( $query_posts->posts ) ) {
+ foreach ( array_values( $query_posts->posts ) as $post_id ) {
+ $this->import_post_opengraph( $post_id );
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Imports the OpenGraph and Twitter settings for a single post.
+ *
+ * @param int $post_id Post ID.
+ *
+ * @return void
+ */
+ private function import_post_opengraph( $post_id ) {
+ $meta = get_post_meta( $post_id, '_aioseop_opengraph_settings', true );
+ $meta = maybe_unserialize( $meta );
+
+ foreach ( $this->import_keys as $old_key => $new_key ) {
+ $this->maybe_save_post_meta( $new_key, $meta[ $old_key ], $post_id );
+ }
+ }
+}
diff --git a/wp-content/plugins/wordpress-seo/admin/import/plugins/class-import-greg-high-performance-seo.php b/wp-content/plugins/wordpress-seo/admin/import/plugins/class-import-greg-high-performance-seo.php
new file mode 100755
index 00000000..8925421f
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/import/plugins/class-import-greg-high-performance-seo.php
@@ -0,0 +1,42 @@
+ '_ghpseo_alternative_description',
+ 'new_key' => 'metadesc',
+ ],
+ [
+ 'old_key' => '_ghpseo_secondary_title',
+ 'new_key' => 'title',
+ ],
+ ];
+}
diff --git a/wp-content/plugins/wordpress-seo/admin/import/plugins/class-import-headspace.php b/wp-content/plugins/wordpress-seo/admin/import/plugins/class-import-headspace.php
new file mode 100755
index 00000000..3a43d169
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/import/plugins/class-import-headspace.php
@@ -0,0 +1,54 @@
+ '_headspace_description',
+ 'new_key' => 'metadesc',
+ ],
+ [
+ 'old_key' => '_headspace_page_title',
+ 'new_key' => 'title',
+ ],
+ [
+ 'old_key' => '_headspace_noindex',
+ 'new_key' => 'meta-robots-noindex',
+ 'convert' => [ 'on' => 1 ],
+ ],
+ [
+ 'old_key' => '_headspace_nofollow',
+ 'new_key' => 'meta-robots-nofollow',
+ 'convert' => [ 'on' => 1 ],
+ ],
+ ];
+}
diff --git a/wp-content/plugins/wordpress-seo/admin/import/plugins/class-import-jetpack.php b/wp-content/plugins/wordpress-seo/admin/import/plugins/class-import-jetpack.php
new file mode 100755
index 00000000..5f57d816
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/import/plugins/class-import-jetpack.php
@@ -0,0 +1,40 @@
+ 'advanced_seo_description',
+ 'new_key' => 'metadesc',
+ ],
+ ];
+}
diff --git a/wp-content/plugins/wordpress-seo/admin/import/plugins/class-import-platinum-seo-pack.php b/wp-content/plugins/wordpress-seo/admin/import/plugins/class-import-platinum-seo-pack.php
new file mode 100755
index 00000000..16a5ce9e
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/import/plugins/class-import-platinum-seo-pack.php
@@ -0,0 +1,138 @@
+ 'description',
+ 'new_key' => 'metadesc',
+ ],
+ [
+ 'old_key' => 'title',
+ 'new_key' => 'title',
+ ],
+ ];
+
+ /**
+ * Runs the import of post meta keys stored by Platinum SEO Pack.
+ *
+ * @return bool
+ */
+ protected function import() {
+ $return = parent::import();
+ if ( $return ) {
+ $this->import_robots_meta();
+ }
+
+ return $return;
+ }
+
+ /**
+ * Cleans up all the meta values Platinum SEO pack creates.
+ *
+ * @return bool
+ */
+ protected function cleanup() {
+ $this->meta_key = 'title';
+ parent::cleanup();
+
+ $this->meta_key = 'description';
+ parent::cleanup();
+
+ $this->meta_key = 'metarobots';
+ parent::cleanup();
+
+ return true;
+ }
+
+ /**
+ * Finds all the robotsmeta fields to import and deals with them.
+ *
+ * There are four potential values that Platinum SEO stores:
+ * - index,folllow
+ * - index,nofollow
+ * - noindex,follow
+ * - noindex,nofollow
+ *
+ * We only have to deal with the latter 3, the first is our default.
+ *
+ * @return void
+ */
+ protected function import_robots_meta() {
+ $this->import_by_meta_robots( 'index,nofollow', [ 'nofollow' ] );
+ $this->import_by_meta_robots( 'noindex,follow', [ 'noindex' ] );
+ $this->import_by_meta_robots( 'noindex,nofollow', [ 'noindex', 'nofollow' ] );
+ }
+
+ /**
+ * Imports the values for all index, nofollow posts.
+ *
+ * @param string $value The meta robots value to find posts for.
+ * @param array $metas The meta field(s) to save.
+ *
+ * @return void
+ */
+ protected function import_by_meta_robots( $value, $metas ) {
+ $posts = $this->find_posts_by_robots_meta( $value );
+ if ( ! $posts ) {
+ return;
+ }
+
+ foreach ( $posts as $post_id ) {
+ foreach ( $metas as $meta ) {
+ $this->maybe_save_post_meta( 'meta-robots-' . $meta, 1, $post_id );
+ }
+ }
+ }
+
+ /**
+ * Finds posts by a given meta robots value.
+ *
+ * @param string $meta_value Robots meta value.
+ *
+ * @return array|bool Array of Post IDs on success, false on failure.
+ */
+ protected function find_posts_by_robots_meta( $meta_value ) {
+ $posts = get_posts(
+ [
+ 'post_type' => 'any',
+ 'meta_key' => 'robotsmeta',
+ 'meta_value' => $meta_value,
+ 'order' => 'ASC',
+ 'fields' => 'ids',
+ 'nopaging' => true,
+ ]
+ );
+ if ( empty( $posts ) ) {
+ return false;
+ }
+ return $posts;
+ }
+}
diff --git a/wp-content/plugins/wordpress-seo/admin/import/plugins/class-import-premium-seo-pack.php b/wp-content/plugins/wordpress-seo/admin/import/plugins/class-import-premium-seo-pack.php
new file mode 100755
index 00000000..bd93b91e
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/import/plugins/class-import-premium-seo-pack.php
@@ -0,0 +1,39 @@
+table_name = $wpdb->prefix . 'psp';
+ $this->meta_key = '';
+ }
+
+ /**
+ * Returns the query to return an identifier for the posts to import.
+ *
+ * @return string
+ */
+ protected function retrieve_posts_query() {
+ return "SELECT URL AS identifier FROM {$this->table_name} WHERE blog_id = %d";
+ }
+}
diff --git a/wp-content/plugins/wordpress-seo/admin/import/plugins/class-import-rankmath.php b/wp-content/plugins/wordpress-seo/admin/import/plugins/class-import-rankmath.php
new file mode 100755
index 00000000..68e7c0c1
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/import/plugins/class-import-rankmath.php
@@ -0,0 +1,179 @@
+ 'rank_math_description',
+ 'new_key' => 'metadesc',
+ ],
+ [
+ 'old_key' => 'rank_math_title',
+ 'new_key' => 'title',
+ ],
+ [
+ 'old_key' => 'rank_math_canonical_url',
+ 'new_key' => 'canonical',
+ ],
+ [
+ 'old_key' => 'rank_math_primary_category',
+ 'new_key' => 'primary_category',
+ ],
+ [
+ 'old_key' => 'rank_math_facebook_title',
+ 'new_key' => 'opengraph-title',
+ ],
+ [
+ 'old_key' => 'rank_math_facebook_description',
+ 'new_key' => 'opengraph-description',
+ ],
+ [
+ 'old_key' => 'rank_math_facebook_image',
+ 'new_key' => 'opengraph-image',
+ ],
+ [
+ 'old_key' => 'rank_math_facebook_image_id',
+ 'new_key' => 'opengraph-image-id',
+ ],
+ [
+ 'old_key' => 'rank_math_twitter_title',
+ 'new_key' => 'twitter-title',
+ ],
+ [
+ 'old_key' => 'rank_math_twitter_description',
+ 'new_key' => 'twitter-description',
+ ],
+ [
+ 'old_key' => 'rank_math_twitter_image',
+ 'new_key' => 'twitter-image',
+ ],
+ [
+ 'old_key' => 'rank_math_twitter_image_id',
+ 'new_key' => 'twitter-image-id',
+ ],
+ [
+ 'old_key' => 'rank_math_focus_keyword',
+ 'new_key' => 'focuskw',
+ ],
+ ];
+
+ /**
+ * Handles post meta data to import.
+ *
+ * @return bool Import success status.
+ */
+ protected function import() {
+ global $wpdb;
+ // Replace % with %% as their variables are the same except for that.
+ $wpdb->query( "UPDATE $wpdb->postmeta SET meta_value = REPLACE( meta_value, '%', '%%' ) WHERE meta_key IN ( 'rank_math_description', 'rank_math_title' )" );
+
+ $this->import_meta_robots();
+ $return = $this->meta_keys_clone( $this->clone_keys );
+
+ // Return %% to % so our import is non-destructive.
+ $wpdb->query( "UPDATE $wpdb->postmeta SET meta_value = REPLACE( meta_value, '%%', '%' ) WHERE meta_key IN ( 'rank_math_description', 'rank_math_title' )" );
+
+ if ( $return ) {
+ $this->import_settings();
+ }
+
+ return $return;
+ }
+
+ /**
+ * RankMath stores robots meta quite differently, so we have to parse it out.
+ *
+ * @return void
+ */
+ private function import_meta_robots() {
+ global $wpdb;
+ $post_metas = $wpdb->get_results( "SELECT post_id, meta_value FROM $wpdb->postmeta WHERE meta_key = 'rank_math_robots'" );
+ foreach ( $post_metas as $post_meta ) {
+ // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- Reason: We can't control the form in which Rankmath sends the data.
+ $robots_values = unserialize( $post_meta->meta_value );
+ foreach ( [ 'noindex', 'nofollow' ] as $directive ) {
+ $directive_key = array_search( $directive, $robots_values, true );
+ if ( $directive_key !== false ) {
+ update_post_meta( $post_meta->post_id, '_yoast_wpseo_meta-robots-' . $directive, 1 );
+ unset( $robots_values[ $directive_key ] );
+ }
+ }
+ if ( count( $robots_values ) > 0 ) {
+ $value = implode( ',', $robots_values );
+ update_post_meta( $post_meta->post_id, '_yoast_wpseo_meta-robots-adv', $value );
+ }
+ }
+ }
+
+ /**
+ * Imports some of the RankMath settings.
+ *
+ * @return void
+ */
+ private function import_settings() {
+ $settings = [
+ 'title_separator' => 'separator',
+ 'homepage_title' => 'title-home-wpseo',
+ 'homepage_description' => 'metadesc-home-wpseo',
+ 'author_archive_title' => 'title-author-wpseo',
+ 'date_archive_title' => 'title-archive-wpseo',
+ 'search_title' => 'title-search-wpseo',
+ '404_title' => 'title-404-wpseo',
+ 'pt_post_title' => 'title-post',
+ 'pt_page_title' => 'title-page',
+ ];
+ $options = get_option( 'rank-math-options-titles' );
+
+ foreach ( $settings as $import_setting_key => $setting_key ) {
+ if ( ! empty( $options[ $import_setting_key ] ) ) {
+ $value = $options[ $import_setting_key ];
+ // Make sure replace vars work.
+ $value = str_replace( '%', '%%', $value );
+ WPSEO_Options::set( $setting_key, $value );
+ }
+ }
+ }
+
+ /**
+ * Removes the plugin data from the database.
+ *
+ * @return bool Cleanup status.
+ */
+ protected function cleanup() {
+ $return = parent::cleanup();
+ if ( $return ) {
+ global $wpdb;
+ $wpdb->query( "DELETE FROM $wpdb->options WHERE option_name LIKE 'rank-math-%'" );
+ $wpdb->query( "DELETE FROM $wpdb->options WHERE option_name LIKE '%rank_math%'" );
+ }
+
+ return $return;
+ }
+}
diff --git a/wp-content/plugins/wordpress-seo/admin/import/plugins/class-import-seo-framework.php b/wp-content/plugins/wordpress-seo/admin/import/plugins/class-import-seo-framework.php
new file mode 100755
index 00000000..8a8ac9e1
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/import/plugins/class-import-seo-framework.php
@@ -0,0 +1,94 @@
+ '_genesis_description',
+ 'new_key' => 'metadesc',
+ ],
+ [
+ 'old_key' => '_genesis_title',
+ 'new_key' => 'title',
+ ],
+ [
+ 'old_key' => '_genesis_noindex',
+ 'new_key' => 'meta-robots-noindex',
+ ],
+ [
+ 'old_key' => '_genesis_nofollow',
+ 'new_key' => 'meta-robots-nofollow',
+ ],
+ [
+ 'old_key' => '_genesis_canonical_uri',
+ 'new_key' => 'canonical',
+ ],
+ [
+ 'old_key' => '_open_graph_title',
+ 'new_key' => 'opengraph-title',
+ ],
+ [
+ 'old_key' => '_open_graph_description',
+ 'new_key' => 'opengraph-description',
+ ],
+ [
+ 'old_key' => '_social_image_url',
+ 'new_key' => 'opengraph-image',
+ ],
+ [
+ 'old_key' => '_twitter_title',
+ 'new_key' => 'twitter-title',
+ ],
+ [
+ 'old_key' => '_twitter_description',
+ 'new_key' => 'twitter-description',
+ ],
+ ];
+
+ /**
+ * Removes all the metadata set by the SEO Framework plugin.
+ *
+ * @return bool
+ */
+ protected function cleanup() {
+ $set1 = parent::cleanup();
+
+ $this->meta_key = '_social_image_%';
+ $set2 = parent::cleanup();
+
+ $this->meta_key = '_twitter_%';
+ $set3 = parent::cleanup();
+
+ $this->meta_key = '_open_graph_%';
+ $set4 = parent::cleanup();
+
+ return ( $set1 || $set2 || $set3 || $set4 );
+ }
+}
diff --git a/wp-content/plugins/wordpress-seo/admin/import/plugins/class-import-seopressor.php b/wp-content/plugins/wordpress-seo/admin/import/plugins/class-import-seopressor.php
new file mode 100755
index 00000000..4009c798
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/import/plugins/class-import-seopressor.php
@@ -0,0 +1,175 @@
+ '_seop_settings',
+ ],
+ ];
+
+ /**
+ * Imports the post meta values to Yoast SEO.
+ *
+ * @return bool Import success status.
+ */
+ protected function import() {
+ // Query for all the posts that have an _seop_settings meta set.
+ $query_posts = new WP_Query( 'post_type=any&meta_key=_seop_settings&order=ASC&fields=ids&nopaging=true' );
+ foreach ( $query_posts->posts as $post_id ) {
+ $this->import_post_focus_keywords( $post_id );
+ $this->import_seopressor_post_settings( $post_id );
+ }
+
+ return true;
+ }
+
+ /**
+ * Removes all the post meta fields SEOpressor creates.
+ *
+ * @return bool Cleanup status.
+ */
+ protected function cleanup() {
+ global $wpdb;
+
+ // If we get to replace the data, let's do some proper cleanup.
+ return $wpdb->query( "DELETE FROM {$wpdb->postmeta} WHERE meta_key LIKE '_seop_%'" );
+ }
+
+ /**
+ * Imports the data. SEOpressor stores most of the data in one post array, this loops over it.
+ *
+ * @param int $post_id Post ID.
+ *
+ * @return void
+ */
+ private function import_seopressor_post_settings( $post_id ) {
+ $settings = get_post_meta( $post_id, '_seop_settings', true );
+
+ foreach (
+ [
+ 'fb_description' => 'opengraph-description',
+ 'fb_title' => 'opengraph-title',
+ 'fb_type' => 'og_type',
+ 'fb_img' => 'opengraph-image',
+ 'meta_title' => 'title',
+ 'meta_description' => 'metadesc',
+ 'meta_canonical' => 'canonical',
+ 'tw_description' => 'twitter-description',
+ 'tw_title' => 'twitter-title',
+ 'tw_image' => 'twitter-image',
+ ] as $seopressor_key => $yoast_key ) {
+ $this->import_meta_helper( $seopressor_key, $yoast_key, $settings, $post_id );
+ }
+
+ if ( isset( $settings['meta_rules'] ) ) {
+ $this->import_post_robots( $settings['meta_rules'], $post_id );
+ }
+ }
+
+ /**
+ * Imports the focus keywords, and stores them for later use.
+ *
+ * @param int $post_id Post ID.
+ *
+ * @return void
+ */
+ private function import_post_focus_keywords( $post_id ) {
+ // Import the focus keyword.
+ $focuskw = trim( get_post_meta( $post_id, '_seop_kw_1', true ) );
+ $this->maybe_save_post_meta( 'focuskw', $focuskw, $post_id );
+
+ // Import additional focus keywords for use in premium.
+ $focuskw2 = trim( get_post_meta( $post_id, '_seop_kw_2', true ) );
+ $focuskw3 = trim( get_post_meta( $post_id, '_seop_kw_3', true ) );
+
+ $focus_keywords = [];
+ if ( ! empty( $focuskw2 ) ) {
+ $focus_keywords[] = $focuskw2;
+ }
+ if ( ! empty( $focuskw3 ) ) {
+ $focus_keywords[] = $focuskw3;
+ }
+
+ if ( $focus_keywords !== [] ) {
+ $this->maybe_save_post_meta( 'focuskeywords', WPSEO_Utils::format_json_encode( $focus_keywords ), $post_id );
+ }
+ }
+
+ /**
+ * Retrieves the SEOpressor robot value and map this to Yoast SEO values.
+ *
+ * @param string $meta_rules The meta rules taken from the SEOpressor settings array.
+ * @param int $post_id The post id of the current post.
+ *
+ * @return void
+ */
+ private function import_post_robots( $meta_rules, $post_id ) {
+ $seopressor_robots = explode( '#|#|#', $meta_rules );
+ $robot_value = $this->get_robot_value( $seopressor_robots );
+
+ // Saving the new meta values for Yoast SEO.
+ $this->maybe_save_post_meta( 'meta-robots-noindex', $robot_value['index'], $post_id );
+ $this->maybe_save_post_meta( 'meta-robots-nofollow', $robot_value['follow'], $post_id );
+ $this->maybe_save_post_meta( 'meta-robots-adv', $robot_value['advanced'], $post_id );
+ }
+
+ /**
+ * Gets the robot config by given SEOpressor robots value.
+ *
+ * @param array $seopressor_robots The value in SEOpressor that needs to be converted to the Yoast format.
+ *
+ * @return array The robots values in Yoast format.
+ */
+ private function get_robot_value( $seopressor_robots ) {
+ $return = [
+ 'index' => 2,
+ 'follow' => 0,
+ 'advanced' => '',
+ ];
+
+ if ( in_array( 'noindex', $seopressor_robots, true ) ) {
+ $return['index'] = 1;
+ }
+ if ( in_array( 'nofollow', $seopressor_robots, true ) ) {
+ $return['follow'] = 1;
+ }
+ foreach ( [ 'noarchive', 'nosnippet', 'noimageindex' ] as $needle ) {
+ if ( in_array( $needle, $seopressor_robots, true ) ) {
+ $return['advanced'] .= $needle . ',';
+ }
+ }
+ $return['advanced'] = rtrim( $return['advanced'], ',' );
+
+ return $return;
+ }
+}
diff --git a/wp-content/plugins/wordpress-seo/admin/import/plugins/class-import-smartcrawl.php b/wp-content/plugins/wordpress-seo/admin/import/plugins/class-import-smartcrawl.php
new file mode 100755
index 00000000..507120c6
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/import/plugins/class-import-smartcrawl.php
@@ -0,0 +1,151 @@
+ '_wds_metadesc',
+ 'new_key' => 'metadesc',
+ ],
+ [
+ 'old_key' => '_wds_title',
+ 'new_key' => 'title',
+ ],
+ [
+ 'old_key' => '_wds_canonical',
+ 'new_key' => 'canonical',
+ ],
+ [
+ 'old_key' => '_wds_focus-keywords',
+ 'new_key' => 'focuskw',
+ ],
+ [
+ 'old_key' => '_wds_meta-robots-noindex',
+ 'new_key' => 'meta-robots-noindex',
+ ],
+ [
+ 'old_key' => '_wds_meta-robots-nofollow',
+ 'new_key' => 'meta-robots-nofollow',
+ ],
+ ];
+
+ /**
+ * Used for importing Twitter and Facebook meta's.
+ *
+ * @var array
+ */
+ protected $social_keys = [];
+
+ /**
+ * Handles post meta data to import.
+ *
+ * @return bool Import success status.
+ */
+ protected function import() {
+ $return = parent::import();
+ if ( $return ) {
+ $this->import_opengraph();
+ $this->import_twitter();
+ }
+
+ return $return;
+ }
+
+ /**
+ * Imports the OpenGraph meta keys saved by Smartcrawl.
+ *
+ * @return bool Import status.
+ */
+ protected function import_opengraph() {
+ $this->social_keys = [
+ 'title' => 'opengraph-title',
+ 'description' => 'opengraph-description',
+ 'images' => 'opengraph-image',
+ ];
+ return $this->post_find_import( '_wds_opengraph' );
+ }
+
+ /**
+ * Imports the Twitter meta keys saved by Smartcrawl.
+ *
+ * @return bool Import status.
+ */
+ protected function import_twitter() {
+ $this->social_keys = [
+ 'title' => 'twitter-title',
+ 'description' => 'twitter-description',
+ ];
+ return $this->post_find_import( '_wds_twitter' );
+ }
+
+ /**
+ * Imports a post's serialized post meta values.
+ *
+ * @param int $post_id Post ID.
+ * @param string $key The meta key to import.
+ *
+ * @return void
+ */
+ protected function import_serialized_post_meta( $post_id, $key ) {
+ $data = get_post_meta( $post_id, $key, true );
+ $data = maybe_unserialize( $data );
+ foreach ( $this->social_keys as $key => $meta_key ) {
+ if ( ! isset( $data[ $key ] ) ) {
+ return;
+ }
+ $value = $data[ $key ];
+ if ( is_array( $value ) ) {
+ $value = $value[0];
+ }
+ $this->maybe_save_post_meta( $meta_key, $value, $post_id );
+ }
+ }
+
+ /**
+ * Finds all the posts with a certain meta key and imports its values.
+ *
+ * @param string $key The meta key to search for.
+ *
+ * @return bool Import status.
+ */
+ protected function post_find_import( $key ) {
+ $query_posts = new WP_Query( 'post_type=any&meta_key=' . $key . '&order=ASC&fields=ids&nopaging=true' );
+
+ if ( empty( $query_posts->posts ) ) {
+ return false;
+ }
+
+ foreach ( array_values( $query_posts->posts ) as $post_id ) {
+ $this->import_serialized_post_meta( $post_id, $key );
+ }
+
+ return true;
+ }
+}
diff --git a/wp-content/plugins/wordpress-seo/admin/import/plugins/class-import-squirrly.php b/wp-content/plugins/wordpress-seo/admin/import/plugins/class-import-squirrly.php
new file mode 100755
index 00000000..e5ecc617
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/import/plugins/class-import-squirrly.php
@@ -0,0 +1,224 @@
+ 'meta-robots-noindex',
+ 'nofollow' => 'meta-robots-nofollow',
+ 'title' => 'title',
+ 'description' => 'metadesc',
+ 'canonical' => 'canonical',
+ 'cornerstone' => '_yst_is_cornerstone',
+ 'tw_media' => 'twitter-image',
+ 'tw_title' => 'twitter-title',
+ 'tw_description' => 'twitter-description',
+ 'og_title' => 'opengraph-title',
+ 'og_description' => 'opengraph-description',
+ 'og_media' => 'opengraph-image',
+ 'focuskw' => 'focuskw',
+ ];
+
+ /**
+ * WPSEO_Import_Squirrly constructor.
+ */
+ public function __construct() {
+ parent::__construct();
+
+ global $wpdb;
+ $this->table_name = $wpdb->prefix . 'qss';
+ }
+
+ /**
+ * Imports the post meta values to Yoast SEO.
+ *
+ * @return bool Import success status.
+ */
+ protected function import() {
+ $results = $this->retrieve_posts();
+ foreach ( $results as $post ) {
+ $return = $this->import_post_values( $post->identifier );
+ if ( ! $return ) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Retrieve the posts from the Squirrly Database.
+ *
+ * @return array Array of post IDs from the DB.
+ */
+ protected function retrieve_posts() {
+ global $wpdb;
+ return $wpdb->get_results(
+ $wpdb->prepare(
+ $this->retrieve_posts_query(),
+ get_current_blog_id()
+ )
+ );
+ }
+
+ /**
+ * Returns the query to return an identifier for the posts to import.
+ *
+ * @return string Query to get post ID's from the DB.
+ */
+ protected function retrieve_posts_query() {
+ return "SELECT post_id AS identifier FROM {$this->table_name} WHERE blog_id = %d";
+ }
+
+ /**
+ * Removes the DB table and the post meta field Squirrly creates.
+ *
+ * @return bool Cleanup status.
+ */
+ protected function cleanup() {
+ global $wpdb;
+
+ // If we can clean, let's clean.
+ $wpdb->query( "DROP TABLE {$this->table_name}" );
+
+ // This removes the post meta field for the focus keyword from the DB.
+ parent::cleanup();
+
+ // If we can still see the table, something went wrong.
+ if ( $this->detect() ) {
+ $this->cleanup_error_msg();
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Detects whether there is post meta data to import.
+ *
+ * @return bool Boolean indicating whether there is something to import.
+ */
+ protected function detect() {
+ global $wpdb;
+
+ $result = $wpdb->get_var( "SHOW TABLES LIKE '{$this->table_name}'" );
+ if ( is_wp_error( $result ) || $result === null ) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Imports the data of a post out of Squirrly's DB table.
+ *
+ * @param mixed $post_identifier Post identifier, can be ID or string.
+ *
+ * @return bool Import status.
+ */
+ private function import_post_values( $post_identifier ) {
+ $data = $this->retrieve_post_data( $post_identifier );
+ if ( ! $data ) {
+ return false;
+ }
+
+ if ( ! is_numeric( $post_identifier ) ) {
+ $post_id = url_to_postid( $post_identifier );
+ }
+
+ if ( is_numeric( $post_identifier ) ) {
+ $post_id = (int) $post_identifier;
+ $data['focuskw'] = $this->maybe_add_focus_kw( $post_identifier );
+ }
+
+ foreach ( $this->seo_field_keys as $squirrly_key => $yoast_key ) {
+ $this->import_meta_helper( $squirrly_key, $yoast_key, $data, $post_id );
+ }
+ return true;
+ }
+
+ /**
+ * Retrieves the Squirrly SEO data for a post from the DB.
+ *
+ * @param int $post_identifier Post ID.
+ *
+ * @return array|bool Array of data or false.
+ */
+ private function retrieve_post_data( $post_identifier ) {
+ global $wpdb;
+
+ if ( is_numeric( $post_identifier ) ) {
+ $post_identifier = (int) $post_identifier;
+ $query_where = 'post_id = %d';
+ }
+ if ( ! is_numeric( $post_identifier ) ) {
+ $query_where = 'URL = %s';
+ }
+
+ $replacements = [
+ get_current_blog_id(),
+ $post_identifier,
+ ];
+
+ $data = $wpdb->get_var(
+ $wpdb->prepare(
+ "SELECT seo FROM {$this->table_name} WHERE blog_id = %d AND " . $query_where,
+ $replacements
+ )
+ );
+ if ( ! $data || is_wp_error( $data ) ) {
+ return false;
+ }
+ $data = maybe_unserialize( $data );
+ return $data;
+ }
+
+ /**
+ * Squirrly stores the focus keyword in post meta.
+ *
+ * @param int $post_id Post ID.
+ *
+ * @return string The focus keyword.
+ */
+ private function maybe_add_focus_kw( $post_id ) {
+ $focuskw = get_post_meta( $post_id, '_sq_post_keyword', true );
+ if ( $focuskw ) {
+ $focuskw = json_decode( $focuskw );
+ return $focuskw->keyword;
+ }
+ return '';
+ }
+}
diff --git a/wp-content/plugins/wordpress-seo/admin/import/plugins/class-import-ultimate-seo.php b/wp-content/plugins/wordpress-seo/admin/import/plugins/class-import-ultimate-seo.php
new file mode 100755
index 00000000..a5113650
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/import/plugins/class-import-ultimate-seo.php
@@ -0,0 +1,64 @@
+ '_su_description',
+ 'new_key' => 'metadesc',
+ ],
+ [
+ 'old_key' => '_su_title',
+ 'new_key' => 'title',
+ ],
+ [
+ 'old_key' => '_su_og_title',
+ 'new_key' => 'opengraph-title',
+ ],
+ [
+ 'old_key' => '_su_og_description',
+ 'new_key' => 'opengraph-description',
+ ],
+ [
+ 'old_key' => '_su_og_image',
+ 'new_key' => 'opengraph-image',
+ ],
+ [
+ 'old_key' => '_su_meta_robots_noindex',
+ 'new_key' => 'meta-robots-noindex',
+ 'convert' => [ 'on' => 1 ],
+ ],
+ [
+ 'old_key' => '_su_meta_robots_nofollow',
+ 'new_key' => 'meta-robots-nofollow',
+ 'convert' => [ 'on' => 1 ],
+ ],
+ ];
+}
diff --git a/wp-content/plugins/wordpress-seo/admin/import/plugins/class-import-woothemes-seo.php b/wp-content/plugins/wordpress-seo/admin/import/plugins/class-import-woothemes-seo.php
new file mode 100755
index 00000000..5ee943c3
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/import/plugins/class-import-woothemes-seo.php
@@ -0,0 +1,138 @@
+ 'seo_description',
+ 'new_key' => 'metadesc',
+ ],
+ [
+ 'old_key' => 'seo_title',
+ 'new_key' => 'title',
+ ],
+ [
+ 'old_key' => 'seo_noindex',
+ 'new_key' => 'meta-robots-noindex',
+ ],
+ [
+ 'old_key' => 'seo_follow',
+ 'new_key' => 'meta-robots-nofollow',
+ ],
+ ];
+
+ /**
+ * Holds the meta fields we can delete after import.
+ *
+ * @var array
+ */
+ protected $cleanup_metas = [
+ 'seo_follow',
+ 'seo_noindex',
+ 'seo_title',
+ 'seo_description',
+ 'seo_keywords',
+ ];
+
+ /**
+ * Holds the options we can delete after import.
+ *
+ * @var array
+ */
+ protected $cleanup_options = [
+ 'seo_woo_archive_layout',
+ 'seo_woo_single_layout',
+ 'seo_woo_page_layout',
+ 'seo_woo_wp_title',
+ 'seo_woo_meta_single_desc',
+ 'seo_woo_meta_single_key',
+ 'seo_woo_home_layout',
+ ];
+
+ /**
+ * Cleans up the WooThemes SEO settings.
+ *
+ * @return bool Cleanup status.
+ */
+ protected function cleanup() {
+ $result = $this->cleanup_meta();
+ if ( $result ) {
+ $this->cleanup_options();
+ }
+ return $result;
+ }
+
+ /**
+ * Removes the Woo Options from the database.
+ *
+ * @return void
+ */
+ private function cleanup_options() {
+ foreach ( $this->cleanup_options as $option ) {
+ delete_option( $option );
+ }
+ }
+
+ /**
+ * Removes the post meta fields from the database.
+ *
+ * @return bool Cleanup status.
+ */
+ private function cleanup_meta() {
+ foreach ( $this->cleanup_metas as $key ) {
+ $result = $this->cleanup_meta_key( $key );
+ if ( ! $result ) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Removes a single meta field from the postmeta table in the database.
+ *
+ * @param string $key The meta_key to delete.
+ *
+ * @return bool Cleanup status.
+ */
+ private function cleanup_meta_key( $key ) {
+ global $wpdb;
+
+ $wpdb->query(
+ $wpdb->prepare(
+ "DELETE FROM {$wpdb->postmeta} WHERE meta_key = %s",
+ $key
+ )
+ );
+ return $wpdb->__get( 'result' );
+ }
+}
diff --git a/wp-content/plugins/wordpress-seo/admin/import/plugins/class-import-wp-meta-seo.php b/wp-content/plugins/wordpress-seo/admin/import/plugins/class-import-wp-meta-seo.php
new file mode 100755
index 00000000..e6a55efb
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/import/plugins/class-import-wp-meta-seo.php
@@ -0,0 +1,82 @@
+ '_metaseo_metadesc',
+ 'new_key' => 'metadesc',
+ ],
+ [
+ 'old_key' => '_metaseo_metatitle',
+ 'new_key' => 'title',
+ ],
+ [
+ 'old_key' => '_metaseo_metaopengraph-title',
+ 'new_key' => 'opengraph-title',
+ ],
+ [
+ 'old_key' => '_metaseo_metaopengraph-desc',
+ 'new_key' => 'opengraph-description',
+ ],
+ [
+ 'old_key' => '_metaseo_metaopengraph-image',
+ 'new_key' => 'opengraph-image',
+ ],
+ [
+ 'old_key' => '_metaseo_metatwitter-title',
+ 'new_key' => 'twitter-title',
+ ],
+ [
+ 'old_key' => '_metaseo_metatwitter-desc',
+ 'new_key' => 'twitter-description',
+ ],
+ [
+ 'old_key' => '_metaseo_metatwitter-image',
+ 'new_key' => 'twitter-image',
+ ],
+ [
+ 'old_key' => '_metaseo_metaindex',
+ 'new_key' => 'meta-robots-noindex',
+ 'convert' => [
+ 'index' => 0,
+ 'noindex' => 1,
+ ],
+ ],
+ [
+ 'old_key' => '_metaseo_metafollow',
+ 'new_key' => 'meta-robots-nofollow',
+ 'convert' => [
+ 'follow' => 0,
+ 'nofollow' => 1,
+ ],
+ ],
+ ];
+}
diff --git a/wp-content/plugins/wordpress-seo/admin/import/plugins/class-import-wpseo.php b/wp-content/plugins/wordpress-seo/admin/import/plugins/class-import-wpseo.php
new file mode 100755
index 00000000..0d138f2b
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/import/plugins/class-import-wpseo.php
@@ -0,0 +1,310 @@
+ '_wpseo_edit_description',
+ 'new_key' => 'metadesc',
+ ],
+ [
+ 'old_key' => '_wpseo_edit_title',
+ 'new_key' => 'title',
+ ],
+ [
+ 'old_key' => '_wpseo_edit_canonical',
+ 'new_key' => 'canonical',
+ ],
+ [
+ 'old_key' => '_wpseo_edit_og_title',
+ 'new_key' => 'opengraph-title',
+ ],
+ [
+ 'old_key' => '_wpseo_edit_og_description',
+ 'new_key' => 'opengraph-description',
+ ],
+ [
+ 'old_key' => '_wpseo_edit_og_image',
+ 'new_key' => 'opengraph-image',
+ ],
+ [
+ 'old_key' => '_wpseo_edit_twittercard_title',
+ 'new_key' => 'twitter-title',
+ ],
+ [
+ 'old_key' => '_wpseo_edit_twittercard_description',
+ 'new_key' => 'twitter-description',
+ ],
+ [
+ 'old_key' => '_wpseo_edit_twittercard_image',
+ 'new_key' => 'twitter-image',
+ ],
+ ];
+
+ /**
+ * The values 1 - 6 are the configured values from wpSEO. This array will map the values of wpSEO to our values.
+ *
+ * There are some double array like 1-6 and 3-4. The reason is they only set the index value. The follow value is
+ * the default we use in the cases there isn't a follow value present.
+ *
+ * @var array
+ */
+ private $robot_values = [
+ // In wpSEO: index, follow.
+ 1 => [
+ 'index' => 2,
+ 'follow' => 0,
+ ],
+ // In wpSEO: index, nofollow.
+ 2 => [
+ 'index' => 2,
+ 'follow' => 1,
+ ],
+ // In wpSEO: noindex.
+ 3 => [
+ 'index' => 1,
+ 'follow' => 0,
+ ],
+ // In wpSEO: noindex, follow.
+ 4 => [
+ 'index' => 1,
+ 'follow' => 0,
+ ],
+ // In wpSEO: noindex, nofollow.
+ 5 => [
+ 'index' => 1,
+ 'follow' => 1,
+ ],
+ // In wpSEO: index.
+ 6 => [
+ 'index' => 2,
+ 'follow' => 0,
+ ],
+ ];
+
+ /**
+ * Imports wpSEO settings.
+ *
+ * @return bool Import success status.
+ */
+ protected function import() {
+ $status = parent::import();
+ if ( $status ) {
+ $this->import_post_robots();
+ $this->import_taxonomy_metas();
+ }
+
+ return $status;
+ }
+
+ /**
+ * Removes wpseo.de post meta's.
+ *
+ * @return bool Cleanup status.
+ */
+ protected function cleanup() {
+ $this->cleanup_term_meta();
+ $result = $this->cleanup_post_meta();
+ return $result;
+ }
+
+ /**
+ * Detects whether there is post meta data to import.
+ *
+ * @return bool Boolean indicating whether there is something to import.
+ */
+ protected function detect() {
+ if ( parent::detect() ) {
+ return true;
+ }
+
+ global $wpdb;
+ $count = $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->options} WHERE option_name LIKE 'wpseo_category_%'" );
+ if ( $count !== '0' ) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Imports the robot values from WPSEO plugin. These have to be converted to the Yoast format.
+ *
+ * @return void
+ */
+ private function import_post_robots() {
+ $query_posts = new WP_Query( 'post_type=any&meta_key=_wpseo_edit_robots&order=ASC&fields=ids&nopaging=true' );
+
+ if ( ! empty( $query_posts->posts ) ) {
+ foreach ( array_values( $query_posts->posts ) as $post_id ) {
+ $this->import_post_robot( $post_id );
+ }
+ }
+ }
+
+ /**
+ * Gets the wpSEO robot value and map this to Yoast SEO values.
+ *
+ * @param int $post_id The post id of the current post.
+ *
+ * @return void
+ */
+ private function import_post_robot( $post_id ) {
+ $wpseo_robots = get_post_meta( $post_id, '_wpseo_edit_robots', true );
+ $robot_value = $this->get_robot_value( $wpseo_robots );
+
+ // Saving the new meta values for Yoast SEO.
+ $this->maybe_save_post_meta( 'meta-robots-noindex', $robot_value['index'], $post_id );
+ $this->maybe_save_post_meta( 'meta-robots-nofollow', $robot_value['follow'], $post_id );
+ }
+
+ /**
+ * Imports the taxonomy metas from wpSEO.
+ *
+ * @return void
+ */
+ private function import_taxonomy_metas() {
+ $terms = get_terms(
+ [
+ 'taxonomy' => get_taxonomies(),
+ 'hide_empty' => false,
+ ]
+ );
+ $tax_meta = get_option( 'wpseo_taxonomy_meta' );
+
+ foreach ( $terms as $term ) {
+ $this->import_taxonomy_description( $tax_meta, $term->taxonomy, $term->term_id );
+ $this->import_taxonomy_robots( $tax_meta, $term->taxonomy, $term->term_id );
+ }
+
+ update_option( 'wpseo_taxonomy_meta', $tax_meta );
+ }
+
+ /**
+ * Imports the meta description to Yoast SEO.
+ *
+ * @param array $tax_meta The array with the current metadata.
+ * @param string $taxonomy String with the name of the taxonomy.
+ * @param string $term_id The ID of the current term.
+ *
+ * @return void
+ */
+ private function import_taxonomy_description( &$tax_meta, $taxonomy, $term_id ) {
+ $description = get_option( 'wpseo_' . $taxonomy . '_' . $term_id, false );
+ if ( $description !== false ) {
+ // Import description.
+ $tax_meta[ $taxonomy ][ $term_id ]['wpseo_desc'] = $description;
+ }
+ }
+
+ /**
+ * Imports the robot value to Yoast SEO.
+ *
+ * @param array $tax_meta The array with the current metadata.
+ * @param string $taxonomy String with the name of the taxonomy.
+ * @param string $term_id The ID of the current term.
+ *
+ * @return void
+ */
+ private function import_taxonomy_robots( &$tax_meta, $taxonomy, $term_id ) {
+ $wpseo_robots = get_option( 'wpseo_' . $taxonomy . '_' . $term_id . '_robots', false );
+ if ( $wpseo_robots === false ) {
+ return;
+ }
+ // The value 1, 2 and 6 are the index values in wpSEO.
+ $new_robot_value = 'noindex';
+
+ if ( in_array( (int) $wpseo_robots, [ 1, 2, 6 ], true ) ) {
+ $new_robot_value = 'index';
+ }
+
+ $tax_meta[ $taxonomy ][ $term_id ]['wpseo_noindex'] = $new_robot_value;
+ }
+
+ /**
+ * Deletes the wpSEO taxonomy meta data.
+ *
+ * @param string $taxonomy String with the name of the taxonomy.
+ * @param string $term_id The ID of the current term.
+ *
+ * @return void
+ */
+ private function delete_taxonomy_metas( $taxonomy, $term_id ) {
+ delete_option( 'wpseo_' . $taxonomy . '_' . $term_id );
+ delete_option( 'wpseo_' . $taxonomy . '_' . $term_id . '_robots' );
+ }
+
+ /**
+ * Gets the robot config by given wpSEO robots value.
+ *
+ * @param string $wpseo_robots The value in wpSEO that needs to be converted to the Yoast format.
+ *
+ * @return string The correct robot value.
+ */
+ private function get_robot_value( $wpseo_robots ) {
+ if ( array_key_exists( $wpseo_robots, $this->robot_values ) ) {
+ return $this->robot_values[ $wpseo_robots ];
+ }
+
+ return $this->robot_values[1];
+ }
+
+ /**
+ * Deletes wpSEO postmeta from the database.
+ *
+ * @return bool Cleanup status.
+ */
+ private function cleanup_post_meta() {
+ global $wpdb;
+
+ // If we get to replace the data, let's do some proper cleanup.
+ return $wpdb->query( "DELETE FROM {$wpdb->postmeta} WHERE meta_key LIKE '_wpseo_edit_%'" );
+ }
+
+ /**
+ * Cleans up the wpSEO term meta.
+ *
+ * @return void
+ */
+ private function cleanup_term_meta() {
+ $terms = get_terms(
+ [
+ 'taxonomy' => get_taxonomies(),
+ 'hide_empty' => false,
+ ]
+ );
+
+ foreach ( $terms as $term ) {
+ $this->delete_taxonomy_metas( $term->taxonomy, $term->term_id );
+ }
+ }
+}
diff --git a/wp-content/plugins/wordpress-seo/admin/import/plugins/class-importers.php b/wp-content/plugins/wordpress-seo/admin/import/plugins/class-importers.php
new file mode 100755
index 00000000..d2336ecd
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/import/plugins/class-importers.php
@@ -0,0 +1,47 @@
+get_manage_capability();
+ $page_identifier = $this->get_page_identifier();
+ $admin_page_callback = $this->get_admin_page_callback();
+
+ // Get all submenu pages.
+ $submenu_pages = $this->get_submenu_pages();
+
+ foreach ( $submenu_pages as $submenu_page ) {
+ if ( WPSEO_Capability_Utils::current_user_can( $submenu_page[3] ) ) {
+ $manage_capability = $submenu_page[3];
+ $page_identifier = $submenu_page[4];
+ $admin_page_callback = $submenu_page[5];
+ break;
+ }
+ }
+
+ foreach ( $submenu_pages as $index => $submenu_page ) {
+ $submenu_pages[ $index ][0] = $page_identifier;
+ }
+
+ /*
+ * The current user has the capability to control anything.
+ * This means that all submenus and dashboard can be shown.
+ */
+ global $admin_page_hooks;
+
+ add_menu_page(
+ 'Yoast SEO: ' . __( 'Dashboard', 'wordpress-seo' ),
+ 'Yoast SEO ' . $this->get_notification_counter(),
+ $manage_capability,
+ $page_identifier,
+ $admin_page_callback,
+ $this->get_icon_svg(),
+ 99
+ );
+
+ // Wipe notification bits from hooks.
+ // phpcs:ignore WordPress.WP.GlobalVariablesOverride -- This is a deliberate action.
+ $admin_page_hooks[ $page_identifier ] = 'seo';
+
+ // Add submenu items to the main menu if possible.
+ $this->register_submenu_pages( $submenu_pages );
+ }
+
+ /**
+ * Returns the list of registered submenu pages.
+ *
+ * @return array List of registered submenu pages.
+ */
+ public function get_submenu_pages() {
+ global $wpseo_admin;
+
+ $search_console_callback = null;
+
+ // Account for when the available submenu pages are requested from outside the admin.
+ if ( isset( $wpseo_admin ) ) {
+ $google_search_console = new WPSEO_GSC();
+ $search_console_callback = [ $google_search_console, 'display' ];
+ }
+
+ // Submenu pages.
+ $submenu_pages = [
+ $this->get_submenu_page(
+ __( 'Search Console', 'wordpress-seo' ),
+ 'wpseo_search_console',
+ $search_console_callback
+ ),
+ $this->get_submenu_page( __( 'Tools', 'wordpress-seo' ), 'wpseo_tools' ),
+ ];
+
+ /**
+ * Filter: 'wpseo_submenu_pages' - Collects all submenus that need to be shown.
+ *
+ * @param array $submenu_pages List with all submenu pages.
+ */
+ return (array) apply_filters( 'wpseo_submenu_pages', $submenu_pages );
+ }
+
+ /**
+ * Returns the notification count in HTML format.
+ *
+ * @return string The notification count in HTML format.
+ */
+ protected function get_notification_counter() {
+ $notification_center = Yoast_Notification_Center::get();
+ $notification_count = $notification_center->get_notification_count();
+
+ // Add main page.
+ /* translators: Hidden accessibility text; %s: number of notifications. */
+ $notifications = sprintf( _n( '%s notification', '%s notifications', $notification_count, 'wordpress-seo' ), number_format_i18n( $notification_count ) );
+
+ return sprintf( '%1$d%2$s', $notification_count, $notifications );
+ }
+
+ /**
+ * Returns the capability that is required to manage all options.
+ *
+ * @return string Capability to check against.
+ */
+ protected function get_manage_capability() {
+ return 'wpseo_manage_options';
+ }
+}
diff --git a/wp-content/plugins/wordpress-seo/admin/menu/class-base-menu.php b/wp-content/plugins/wordpress-seo/admin/menu/class-base-menu.php
new file mode 100755
index 00000000..216fc843
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/menu/class-base-menu.php
@@ -0,0 +1,287 @@
+menu = $menu;
+ }
+
+ /**
+ * Returns the list of registered submenu pages.
+ *
+ * @return array List of registered submenu pages.
+ */
+ abstract public function get_submenu_pages();
+
+ /**
+ * Creates a submenu formatted array.
+ *
+ * @param string $page_title Page title to use.
+ * @param string $page_slug Page slug to use.
+ * @param callable|null $callback Optional. Callback which handles the page request.
+ * @param callable[]|null $hook Optional. Hook to trigger when the page is registered.
+ *
+ * @return array Formatted submenu.
+ */
+ protected function get_submenu_page( $page_title, $page_slug, $callback = null, $hook = null ) {
+ if ( $callback === null ) {
+ $callback = $this->get_admin_page_callback();
+ }
+
+ return [
+ $this->get_page_identifier(),
+ '',
+ $page_title,
+ $this->get_manage_capability(),
+ $page_slug,
+ $callback,
+ $hook,
+ ];
+ }
+
+ /**
+ * Registers submenu pages as menu pages.
+ *
+ * This method should only be used if the user does not have the required capabilities
+ * to access the parent menu page.
+ *
+ * @param array $submenu_pages List of submenu pages to register.
+ *
+ * @return void
+ */
+ protected function register_menu_pages( $submenu_pages ) {
+ if ( ! is_array( $submenu_pages ) || empty( $submenu_pages ) ) {
+ return;
+ }
+
+ // Loop through submenu pages and add them.
+ array_walk( $submenu_pages, [ $this, 'register_menu_page' ] );
+ }
+
+ /**
+ * Registers submenu pages.
+ *
+ * @param array $submenu_pages List of submenu pages to register.
+ *
+ * @return void
+ */
+ protected function register_submenu_pages( $submenu_pages ) {
+ if ( ! is_array( $submenu_pages ) || empty( $submenu_pages ) ) {
+ return;
+ }
+
+ // Loop through submenu pages and add them.
+ array_walk( $submenu_pages, [ $this, 'register_submenu_page' ] );
+ }
+
+ /**
+ * Registers a submenu page as a menu page.
+ *
+ * This method should only be used if the user does not have the required capabilities
+ * to access the parent menu page.
+ *
+ * @param array $submenu_page {
+ * Submenu page definition.
+ *
+ * @type string $0 Parent menu page slug.
+ * @type string $1 Page title, currently unused.
+ * @type string $2 Title to display in the menu.
+ * @type string $3 Required capability to access the page.
+ * @type string $4 Page slug.
+ * @type callable $5 Callback to run when the page is rendered.
+ * @type array $6 Optional. List of callbacks to run when the page is loaded.
+ * }
+ *
+ * @return void
+ */
+ protected function register_menu_page( $submenu_page ) {
+
+ // If the submenu page requires the general manage capability, it must be added as an actual submenu page.
+ if ( $submenu_page[3] === $this->get_manage_capability() ) {
+ return;
+ }
+
+ $page_title = 'Yoast SEO: ' . $submenu_page[2];
+
+ // Register submenu page as menu page.
+ $hook_suffix = add_menu_page(
+ $page_title,
+ $submenu_page[2],
+ $submenu_page[3],
+ $submenu_page[4],
+ $submenu_page[5],
+ $this->get_icon_svg(),
+ 99
+ );
+
+ // If necessary, add hooks for the submenu page.
+ if ( isset( $submenu_page[6] ) && ( is_array( $submenu_page[6] ) ) ) {
+ $this->add_page_hooks( $hook_suffix, $submenu_page[6] );
+ }
+ }
+
+ /**
+ * Registers a submenu page.
+ *
+ * This method will override the capability of the page to automatically use the
+ * general manage capability. Use the `register_menu_page()` method if the submenu
+ * page should actually use a different capability.
+ *
+ * @param array $submenu_page {
+ * Submenu page definition.
+ *
+ * @type string $0 Parent menu page slug.
+ * @type string $1 Page title, currently unused.
+ * @type string $2 Title to display in the menu.
+ * @type string $3 Required capability to access the page.
+ * @type string $4 Page slug.
+ * @type callable $5 Callback to run when the page is rendered.
+ * @type array $6 Optional. List of callbacks to run when the page is loaded.
+ * }
+ *
+ * @return void
+ */
+ protected function register_submenu_page( $submenu_page ) {
+ $page_title = $submenu_page[2];
+
+ /*
+ * Handle the Google Search Console special case by passing a fake parent
+ * page slug. This way, the sub-page is stil registered and can be accessed
+ * directly. Its menu item won't be displayed.
+ */
+ if ( $submenu_page[4] === 'wpseo_search_console' ) {
+ // Set the parent page slug to a non-existing one.
+ $submenu_page[0] = 'wpseo_fake_menu_parent_page_slug';
+ }
+
+ $page_title .= ' - Yoast SEO';
+
+ // Register submenu page.
+ $hook_suffix = add_submenu_page(
+ $submenu_page[0],
+ $page_title,
+ $submenu_page[2],
+ $submenu_page[3],
+ $submenu_page[4],
+ $submenu_page[5]
+ );
+
+ // If necessary, add hooks for the submenu page.
+ if ( isset( $submenu_page[6] ) && ( is_array( $submenu_page[6] ) ) ) {
+ $this->add_page_hooks( $hook_suffix, $submenu_page[6] );
+ }
+ }
+
+ /**
+ * Adds hook callbacks for a given admin page hook suffix.
+ *
+ * @param string $hook_suffix Admin page hook suffix, as returned by `add_menu_page()`
+ * or `add_submenu_page()`.
+ * @param array $callbacks Callbacks to add.
+ *
+ * @return void
+ */
+ protected function add_page_hooks( $hook_suffix, array $callbacks ) {
+ foreach ( $callbacks as $callback ) {
+ add_action( 'load-' . $hook_suffix, $callback );
+ }
+ }
+
+ /**
+ * Gets the main admin page identifier.
+ *
+ * @return string Admin page identifier.
+ */
+ protected function get_page_identifier() {
+ return $this->menu->get_page_identifier();
+ }
+
+ /**
+ * Checks whether the current user has capabilities to manage all options.
+ *
+ * @return bool True if capabilities are sufficient, false otherwise.
+ */
+ protected function check_manage_capability() {
+ return WPSEO_Capability_Utils::current_user_can( $this->get_manage_capability() );
+ }
+
+ /**
+ * Returns the capability that is required to manage all options.
+ *
+ * @return string Capability to check against.
+ */
+ abstract protected function get_manage_capability();
+
+ /**
+ * Returns the page handler callback.
+ *
+ * @return array Callback page handler.
+ */
+ protected function get_admin_page_callback() {
+ return [ $this->menu, 'load_page' ];
+ }
+
+ /**
+ * Returns the page title to use for the licenses page.
+ *
+ * @deprecated 25.5
+ * @codeCoverageIgnore
+ *
+ * @return string The title for the license page.
+ */
+ protected function get_license_page_title() {
+ static $title = null;
+
+ _deprecated_function( __METHOD__, 'Yoast SEO 25.5' );
+
+ if ( $title === null ) {
+ $title = __( 'Upgrades', 'wordpress-seo' );
+ }
+
+ if ( YoastSEO()->classes->get( Promotion_Manager::class )->is( 'black-friday-promotion' ) && ! YoastSEO()->helpers->product->is_premium() ) {
+ $title = __( 'Upgrades', 'wordpress-seo' ) . '' . __( '30% OFF', 'wordpress-seo' ) . '';
+ }
+
+ return $title;
+ }
+
+ /**
+ * Returns a base64 URL for the svg for use in the menu.
+ *
+ * @param bool $base64 Whether or not to return base64'd output.
+ *
+ * @return string SVG icon.
+ */
+ public function get_icon_svg( $base64 = true ) {
+ $svg = '';
+
+ if ( $base64 ) {
+ //phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode -- This encoding is intended.
+ return 'data:image/svg+xml;base64,' . base64_encode( $svg );
+ }
+
+ return $svg;
+ }
+}
diff --git a/wp-content/plugins/wordpress-seo/admin/menu/class-menu.php b/wp-content/plugins/wordpress-seo/admin/menu/class-menu.php
new file mode 100755
index 00000000..56b98f7b
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/menu/class-menu.php
@@ -0,0 +1,91 @@
+register_hooks();
+
+ if ( WPSEO_Utils::is_plugin_network_active() ) {
+ $network_admin_menu = new WPSEO_Network_Admin_Menu( $this );
+ $network_admin_menu->register_hooks();
+ }
+
+ $capability_normalizer = new WPSEO_Submenu_Capability_Normalize();
+ $capability_normalizer->register_hooks();
+ }
+
+ /**
+ * Returns the main menu page identifier.
+ *
+ * @return string Page identifier to use.
+ */
+ public function get_page_identifier() {
+ return self::PAGE_IDENTIFIER;
+ }
+
+ /**
+ * Loads the requested admin settings page.
+ *
+ * @return void
+ */
+ public function load_page() {
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
+ if ( isset( $_GET['page'] ) && is_string( $_GET['page'] ) ) {
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
+ $page = sanitize_text_field( wp_unslash( $_GET['page'] ) );
+ $this->show_page( $page );
+ }
+ }
+
+ /**
+ * Shows an admin settings page.
+ *
+ * @param string $page Page to display.
+ *
+ * @return void
+ */
+ protected function show_page( $page ) {
+ switch ( $page ) {
+ case 'wpseo_tools':
+ require_once WPSEO_PATH . 'admin/pages/tools.php';
+ break;
+
+ case 'wpseo_files':
+ require_once WPSEO_PATH . 'admin/views/tool-file-editor.php';
+ break;
+
+ default:
+ break;
+ }
+ }
+}
diff --git a/wp-content/plugins/wordpress-seo/admin/menu/class-network-admin-menu.php b/wp-content/plugins/wordpress-seo/admin/menu/class-network-admin-menu.php
new file mode 100755
index 00000000..ef5879ef
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/menu/class-network-admin-menu.php
@@ -0,0 +1,102 @@
+check_manage_capability() ) {
+ return;
+ }
+
+ add_menu_page(
+ __( 'Network Settings', 'wordpress-seo' ) . ' - Yoast SEO',
+ 'Yoast SEO',
+ $this->get_manage_capability(),
+ $this->get_page_identifier(),
+ [ $this, 'network_config_page' ],
+ $this->get_icon_svg()
+ );
+
+ $submenu_pages = $this->get_submenu_pages();
+ $this->register_submenu_pages( $submenu_pages );
+ }
+
+ /**
+ * Returns the list of registered submenu pages.
+ *
+ * @return array List of registered submenu pages.
+ */
+ public function get_submenu_pages() {
+
+ // Submenu pages.
+ $submenu_pages = [
+ $this->get_submenu_page(
+ __( 'General', 'wordpress-seo' ),
+ $this->get_page_identifier(),
+ [ $this, 'network_config_page' ]
+ ),
+ ];
+
+ if ( WPSEO_Utils::allow_system_file_edit() === true ) {
+ $submenu_pages[] = $this->get_submenu_page( __( 'Edit Files', 'wordpress-seo' ), 'wpseo_files' );
+ }
+
+ /**
+ * Filter: 'wpseo_network_submenu_pages' - Collects all network submenus that need to be shown.
+ *
+ * @internal For internal Yoast SEO use only.
+ *
+ * @param array $submenu_pages List with all submenu pages.
+ */
+ return (array) apply_filters( 'wpseo_network_submenu_pages', $submenu_pages );
+ }
+
+ /**
+ * Loads the form for the network configuration page.
+ *
+ * @return void
+ */
+ public function network_config_page() {
+ require_once WPSEO_PATH . 'admin/pages/network.php';
+ }
+
+ /**
+ * Checks whether the current user has capabilities to manage all options.
+ *
+ * @return bool True if capabilities are sufficient, false otherwise.
+ */
+ protected function check_manage_capability() {
+ return current_user_can( $this->get_manage_capability() );
+ }
+
+ /**
+ * Returns the capability that is required to manage all options.
+ *
+ * @return string Capability to check against.
+ */
+ protected function get_manage_capability() {
+ return 'wpseo_manage_network_options';
+ }
+}
diff --git a/wp-content/plugins/wordpress-seo/admin/menu/class-replacevar-editor.php b/wp-content/plugins/wordpress-seo/admin/menu/class-replacevar-editor.php
new file mode 100755
index 00000000..be3a4125
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/menu/class-replacevar-editor.php
@@ -0,0 +1,159 @@
+ true,
+ 'label_title' => '',
+ 'label_description' => '',
+ 'description_placeholder' => '',
+ 'has_new_badge' => false,
+ 'is_disabled' => false,
+ 'has_premium_badge' => false,
+ ]
+ );
+
+ $this->validate_arguments( $arguments );
+
+ $this->yform = $yform;
+ $this->arguments = [
+ 'title' => (string) $arguments['title'],
+ 'description' => (string) $arguments['description'],
+ 'page_type_recommended' => (string) $arguments['page_type_recommended'],
+ 'page_type_specific' => (string) $arguments['page_type_specific'],
+ 'paper_style' => (bool) $arguments['paper_style'],
+ 'label_title' => (string) $arguments['label_title'],
+ 'label_description' => (string) $arguments['label_description'],
+ 'description_placeholder' => (string) $arguments['description_placeholder'],
+ 'has_new_badge' => (bool) $arguments['has_new_badge'],
+ 'is_disabled' => (bool) $arguments['is_disabled'],
+ 'has_premium_badge' => (bool) $arguments['has_premium_badge'],
+ ];
+ }
+
+ /**
+ * Renders a div for the react application to mount to, and hidden inputs where
+ * the app should store it's value so they will be properly saved when the form
+ * is submitted.
+ *
+ * @return void
+ */
+ public function render() {
+ $this->yform->hidden( $this->arguments['title'], $this->arguments['title'] );
+ $this->yform->hidden( $this->arguments['description'], $this->arguments['description'] );
+
+ printf(
+ '',
+ esc_attr( $this->arguments['title'] ),
+ esc_attr( $this->arguments['description'] ),
+ esc_attr( $this->arguments['page_type_recommended'] ),
+ esc_attr( $this->arguments['page_type_specific'] ),
+ esc_attr( $this->arguments['paper_style'] ),
+ esc_attr( $this->arguments['label_title'] ),
+ esc_attr( $this->arguments['label_description'] ),
+ esc_attr( $this->arguments['description_placeholder'] ),
+ esc_attr( $this->arguments['has_new_badge'] ),
+ esc_attr( $this->arguments['is_disabled'] ),
+ esc_attr( $this->arguments['has_premium_badge'] )
+ );
+ }
+
+ /**
+ * Validates the replacement variable editor arguments.
+ *
+ * @param array $arguments The arguments to validate.
+ *
+ * @return void
+ *
+ * @throws InvalidArgumentException Thrown when not all required arguments are present.
+ */
+ protected function validate_arguments( array $arguments ) {
+ $required_arguments = [
+ 'title',
+ 'description',
+ 'page_type_recommended',
+ 'page_type_specific',
+ 'paper_style',
+ ];
+
+ foreach ( $required_arguments as $field_name ) {
+ if ( ! array_key_exists( $field_name, $arguments ) ) {
+ throw new InvalidArgumentException(
+ sprintf(
+ /* translators: %1$s expands to the missing field name. */
+ __( 'Not all required fields are given. Missing field %1$s', 'wordpress-seo' ),
+ $field_name
+ )
+ );
+ }
+ }
+ }
+}
diff --git a/wp-content/plugins/wordpress-seo/admin/menu/class-replacevar-field.php b/wp-content/plugins/wordpress-seo/admin/menu/class-replacevar-field.php
new file mode 100755
index 00000000..e94d2c73
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/menu/class-replacevar-field.php
@@ -0,0 +1,88 @@
+yform = $yform;
+ $this->field_id = $field_id;
+ $this->label = $label;
+ $this->page_type_recommended = $page_type_recommended;
+ $this->page_type_specific = $page_type_specific;
+ }
+
+ /**
+ * Renders a div for the react application to mount to, and hidden inputs where
+ * the app should store it's value so they will be properly saved when the form
+ * is submitted.
+ *
+ * @return void
+ */
+ public function render() {
+ $this->yform->hidden( $this->field_id, $this->field_id );
+
+ printf(
+ '',
+ esc_attr( $this->field_id ),
+ esc_attr( $this->label ),
+ esc_attr( $this->page_type_recommended ),
+ esc_attr( $this->page_type_specific )
+ );
+ }
+}
diff --git a/wp-content/plugins/wordpress-seo/admin/menu/class-submenu-capability-normalize.php b/wp-content/plugins/wordpress-seo/admin/menu/class-submenu-capability-normalize.php
new file mode 100755
index 00000000..6e35718f
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/menu/class-submenu-capability-normalize.php
@@ -0,0 +1,41 @@
+ $submenu_page ) {
+ if ( $submenu_page[3] === 'manage_options' ) {
+ $submenu_pages[ $index ][3] = 'wpseo_manage_options';
+ }
+ }
+
+ return $submenu_pages;
+ }
+}
diff --git a/wp-content/plugins/wordpress-seo/admin/metabox/class-abstract-sectioned-metabox-tab.php b/wp-content/plugins/wordpress-seo/admin/metabox/class-abstract-sectioned-metabox-tab.php
new file mode 100755
index 00000000..29ec6e90
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/metabox/class-abstract-sectioned-metabox-tab.php
@@ -0,0 +1,97 @@
+ '',
+ 'link_class' => '',
+ 'link_aria_label' => '',
+ ];
+
+ $options = array_merge( $default_options, $options );
+
+ $this->name = $name;
+
+ $this->link_content = $link_content;
+ $this->link_title = $options['link_title'];
+ $this->link_class = $options['link_class'];
+ $this->link_aria_label = $options['link_aria_label'];
+ }
+
+ /**
+ * Outputs the section link if any section has been added.
+ *
+ * @return void
+ */
+ public function display_link() {
+ if ( $this->has_sections() ) {
+ printf(
+ '
',
+ esc_attr( $this->name ),
+ esc_attr( $this->link_class ),
+ ( $this->link_title !== '' ) ? ' title="' . esc_attr( $this->link_title ) . '"' : '',
+ ( $this->link_aria_label !== '' ) ? ' aria-label="' . esc_attr( $this->link_aria_label ) . '"' : '',
+ $this->link_content
+ );
+ }
+ }
+
+ /**
+ * Checks whether the tab has any sections.
+ *
+ * @return bool Whether the tab has any sections
+ */
+ abstract protected function has_sections();
+}
diff --git a/wp-content/plugins/wordpress-seo/admin/metabox/class-metabox-analysis-inclusive-language.php b/wp-content/plugins/wordpress-seo/admin/metabox/class-metabox-analysis-inclusive-language.php
new file mode 100755
index 00000000..1fe2a1fd
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/metabox/class-metabox-analysis-inclusive-language.php
@@ -0,0 +1,58 @@
+is_globally_enabled() && $this->is_user_enabled() && $this->is_current_version_supported()
+ && YoastSEO()->helpers->language->has_inclusive_language_support( WPSEO_Language_Utils::get_language( get_locale() ) );
+ }
+
+ /**
+ * Whether or not this analysis is enabled by the user.
+ *
+ * @return bool Whether or not this analysis is enabled by the user.
+ */
+ public function is_user_enabled() {
+ return ! get_the_author_meta( 'wpseo_inclusive_language_analysis_disable', get_current_user_id() );
+ }
+
+ /**
+ * Whether or not this analysis is enabled globally.
+ *
+ * @return bool Whether or not this analysis is enabled globally.
+ */
+ public function is_globally_enabled() {
+ return WPSEO_Options::get( 'inclusive_language_analysis_active', false );
+ }
+
+ /**
+ * Whether the inclusive language analysis should be loaded in Free.
+ *
+ * It should always be loaded when Premium is not active. If Premium is active, it depends on the version. Some Premium
+ * versions also have inclusive language code (when it was still a Premium only feature) which would result in rendering
+ * the analysis twice. In those cases, the analysis should be only loaded from the Premium side.
+ *
+ * @return bool Whether or not the inclusive language analysis should be loaded.
+ */
+ private function is_current_version_supported() {
+ $is_premium = YoastSEO()->helpers->product->is_premium();
+ $premium_version = YoastSEO()->helpers->product->get_premium_version();
+
+ return ! $is_premium
+ || version_compare( $premium_version, '19.6-RC0', '>=' )
+ || version_compare( $premium_version, '19.2', '==' );
+ }
+}
diff --git a/wp-content/plugins/wordpress-seo/admin/metabox/class-metabox-analysis-readability.php b/wp-content/plugins/wordpress-seo/admin/metabox/class-metabox-analysis-readability.php
new file mode 100755
index 00000000..65345c48
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/metabox/class-metabox-analysis-readability.php
@@ -0,0 +1,39 @@
+is_globally_enabled() && $this->is_user_enabled();
+ }
+
+ /**
+ * Whether or not this analysis is enabled by the user.
+ *
+ * @return bool Whether or not this analysis is enabled by the user.
+ */
+ public function is_user_enabled() {
+ return ! get_the_author_meta( 'wpseo_content_analysis_disable', get_current_user_id() );
+ }
+
+ /**
+ * Whether or not this analysis is enabled globally.
+ *
+ * @return bool Whether or not this analysis is enabled globally.
+ */
+ public function is_globally_enabled() {
+ return WPSEO_Options::get( 'content_analysis_active', true );
+ }
+}
diff --git a/wp-content/plugins/wordpress-seo/admin/metabox/class-metabox-analysis-seo.php b/wp-content/plugins/wordpress-seo/admin/metabox/class-metabox-analysis-seo.php
new file mode 100755
index 00000000..8225defb
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/metabox/class-metabox-analysis-seo.php
@@ -0,0 +1,39 @@
+is_globally_enabled() && $this->is_user_enabled();
+ }
+
+ /**
+ * Whether or not this analysis is enabled by the user.
+ *
+ * @return bool Whether or not this analysis is enabled by the user.
+ */
+ public function is_user_enabled() {
+ return ! get_the_author_meta( 'wpseo_keyword_analysis_disable', get_current_user_id() );
+ }
+
+ /**
+ * Whether or not this analysis is enabled globally.
+ *
+ * @return bool Whether or not this analysis is enabled globally.
+ */
+ public function is_globally_enabled() {
+ return WPSEO_Options::get( 'keyword_analysis_active', true );
+ }
+}
diff --git a/wp-content/plugins/wordpress-seo/admin/metabox/class-metabox-collapsible.php b/wp-content/plugins/wordpress-seo/admin/metabox/class-metabox-collapsible.php
new file mode 100755
index 00000000..c5d378cd
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/metabox/class-metabox-collapsible.php
@@ -0,0 +1,84 @@
+name = $name;
+ $this->content = $content;
+ $this->link_content = $link_content;
+ }
+
+ /**
+ * Returns the html for the tab link.
+ *
+ * @return string
+ */
+ public function link() {
+ return $this->link_content;
+ }
+
+ /**
+ * Returns the html for the tab content.
+ *
+ * @return string
+ */
+ public function content() {
+ $collapsible_paper = new WPSEO_Paper_Presenter(
+ $this->link(),
+ null,
+ [
+ 'content' => $this->content,
+ 'collapsible' => true,
+ 'class' => 'metabox wpseo-form wpseo-collapsible-container',
+ 'paper_id' => 'collapsible-' . $this->name,
+ ]
+ );
+
+ return $collapsible_paper->get_output();
+ }
+
+ /**
+ * Returns the collapsible's unique identifier.
+ *
+ * @return string
+ */
+ public function get_name() {
+ return $this->name;
+ }
+}
diff --git a/wp-content/plugins/wordpress-seo/admin/metabox/class-metabox-collapsibles-section.php b/wp-content/plugins/wordpress-seo/admin/metabox/class-metabox-collapsibles-section.php
new file mode 100755
index 00000000..14e8638e
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/metabox/class-metabox-collapsibles-section.php
@@ -0,0 +1,65 @@
+collapsibles = $collapsibles;
+ }
+
+ /**
+ * Outputs the section content if any tab has been added.
+ *
+ * @return void
+ */
+ public function display_content() {
+ if ( $this->has_sections() ) {
+ printf( '
';
+ }
+
+ /**
+ * WARNING: This hook is intended for internal use only.
+ * Don't use it in your code as it will be removed shortly.
+ */
+ do_action( 'wpseo_tools_overview_list_items_internal' );
+
+ echo '
';
+}
+else {
+ echo '', esc_html__( '« Back to Tools page', 'wordpress-seo' ), '';
+
+ $tool_pages = [ 'bulk-editor', 'import-export' ];
+
+ if ( WPSEO_Utils::allow_system_file_edit() === true && ! is_multisite() ) {
+ $tool_pages[] = 'file-editor';
+ }
+
+ if ( in_array( $tool_page, $tool_pages, true ) ) {
+ require_once WPSEO_PATH . 'admin/views/tool-' . $tool_page . '.php';
+ }
+}
+
+$yform->admin_footer( false );
diff --git a/wp-content/plugins/wordpress-seo/admin/roles/class-abstract-role-manager.php b/wp-content/plugins/wordpress-seo/admin/roles/class-abstract-role-manager.php
new file mode 100755
index 00000000..39edad49
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/roles/class-abstract-role-manager.php
@@ -0,0 +1,149 @@
+roles[ $role ] = (object) [
+ 'display_name' => $display_name,
+ 'template' => $template,
+ ];
+ }
+
+ /**
+ * Returns the list of registered roles.
+ *
+ * @return string[] List or registered roles.
+ */
+ public function get_roles() {
+ return array_keys( $this->roles );
+ }
+
+ /**
+ * Adds the registered roles.
+ *
+ * @return void
+ */
+ public function add() {
+ foreach ( $this->roles as $role => $data ) {
+ $capabilities = $this->get_capabilities( $data->template );
+ $capabilities = $this->filter_existing_capabilties( $role, $capabilities );
+
+ $this->add_role( $role, $data->display_name, $capabilities );
+ }
+ }
+
+ /**
+ * Removes the registered roles.
+ *
+ * @return void
+ */
+ public function remove() {
+ $roles = array_keys( $this->roles );
+ array_map( [ $this, 'remove_role' ], $roles );
+ }
+
+ /**
+ * Returns the capabilities for the specified role.
+ *
+ * @param string $role Role to fetch capabilities from.
+ *
+ * @return array List of capabilities.
+ */
+ protected function get_capabilities( $role ) {
+ if ( ! is_string( $role ) || empty( $role ) ) {
+ return [];
+ }
+
+ $wp_role = get_role( $role );
+ if ( ! $wp_role ) {
+ return [];
+ }
+
+ return $wp_role->capabilities;
+ }
+
+ /**
+ * Returns true if the capability exists on the role.
+ *
+ * @param WP_Role $role Role to check capability against.
+ * @param string $capability Capability to check.
+ *
+ * @return bool True if the capability is defined for the role.
+ */
+ protected function capability_exists( WP_Role $role, $capability ) {
+ return ! array_key_exists( $capability, $role->capabilities );
+ }
+
+ /**
+ * Filters out capabilities that are already set for the role.
+ *
+ * This makes sure we don't override configurations that have been previously set.
+ *
+ * @param string $role The role to check against.
+ * @param array $capabilities The capabilities that should be set.
+ *
+ * @return array Capabilties that can be safely set.
+ */
+ protected function filter_existing_capabilties( $role, array $capabilities ) {
+ if ( $capabilities === [] ) {
+ return $capabilities;
+ }
+
+ $wp_role = get_role( $role );
+ if ( ! $wp_role ) {
+ return $capabilities;
+ }
+
+ foreach ( $capabilities as $capability => $grant ) {
+ if ( $this->capability_exists( $wp_role, $capability ) ) {
+ unset( $capabilities[ $capability ] );
+ }
+ }
+
+ return $capabilities;
+ }
+
+ /**
+ * Adds a role to the system.
+ *
+ * @param string $role Role to add.
+ * @param string $display_name Name to display for the role.
+ * @param array $capabilities Capabilities to add to the role.
+ *
+ * @return void
+ */
+ abstract protected function add_role( $role, $display_name, array $capabilities = [] );
+
+ /**
+ * Removes a role from the system.
+ *
+ * @param string $role Role to remove.
+ *
+ * @return void
+ */
+ abstract protected function remove_role( $role );
+}
diff --git a/wp-content/plugins/wordpress-seo/admin/roles/class-register-roles.php b/wp-content/plugins/wordpress-seo/admin/roles/class-register-roles.php
new file mode 100755
index 00000000..9636237e
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/roles/class-register-roles.php
@@ -0,0 +1,33 @@
+register( 'wpseo_manager', 'SEO Manager', 'editor' );
+ $role_manager->register( 'wpseo_editor', 'SEO Editor', 'editor' );
+ }
+}
diff --git a/wp-content/plugins/wordpress-seo/admin/roles/class-role-manager-factory.php b/wp-content/plugins/wordpress-seo/admin/roles/class-role-manager-factory.php
new file mode 100755
index 00000000..d22753a2
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/roles/class-role-manager-factory.php
@@ -0,0 +1,27 @@
+ $grant ) {
+ $wp_role->add_cap( $capability, $grant );
+ }
+
+ return;
+ }
+
+ add_role( $role, $display_name, $capabilities );
+ }
+
+ /**
+ * Removes a role from the system.
+ *
+ * @param string $role Role to remove.
+ *
+ * @return void
+ */
+ protected function remove_role( $role ) {
+ remove_role( $role );
+ }
+
+ /**
+ * Formats the capabilities to the required format.
+ *
+ * @param array $capabilities Capabilities to format.
+ * @param bool $enabled Whether these capabilities should be enabled or not.
+ *
+ * @return array Formatted capabilities.
+ */
+ protected function format_capabilities( array $capabilities, $enabled = true ) {
+ // Flip keys and values.
+ $capabilities = array_flip( $capabilities );
+
+ // Set all values to $enabled.
+ return array_fill_keys( array_keys( $capabilities ), $enabled );
+ }
+}
diff --git a/wp-content/plugins/wordpress-seo/admin/roles/class-role-manager.php b/wp-content/plugins/wordpress-seo/admin/roles/class-role-manager.php
new file mode 100755
index 00000000..7f9d82bb
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/roles/class-role-manager.php
@@ -0,0 +1,44 @@
+get_file_url( $request );
+
+ return new WP_REST_Response(
+ [
+ 'type' => 'success',
+ 'size_in_bytes' => $this->get_file_size( $file_url ),
+ ],
+ 200
+ );
+ } catch ( WPSEO_File_Size_Exception $exception ) {
+ return new WP_REST_Response(
+ [
+ 'type' => 'failure',
+ 'response' => $exception->getMessage(),
+ ],
+ 404
+ );
+ }
+ }
+
+ /**
+ * Retrieves the file url.
+ *
+ * @param WP_REST_Request $request The request to retrieve file url from.
+ *
+ * @return string The file url.
+ * @throws WPSEO_File_Size_Exception The file is hosted externally.
+ */
+ protected function get_file_url( WP_REST_Request $request ) {
+ $file_url = rawurldecode( $request->get_param( 'url' ) );
+
+ if ( ! $this->is_externally_hosted( $file_url ) ) {
+ return $file_url;
+ }
+
+ throw WPSEO_File_Size_Exception::externally_hosted( $file_url );
+ }
+
+ /**
+ * Checks if the file is hosted externally.
+ *
+ * @param string $file_url The file url.
+ *
+ * @return bool True if it is hosted externally.
+ */
+ protected function is_externally_hosted( $file_url ) {
+ return wp_parse_url( home_url(), PHP_URL_HOST ) !== wp_parse_url( $file_url, PHP_URL_HOST );
+ }
+
+ /**
+ * Returns the file size.
+ *
+ * @param string $file_url The file url to get the size for.
+ *
+ * @return int The file size.
+ * @throws WPSEO_File_Size_Exception Retrieval of file size went wrong for unknown reasons.
+ */
+ protected function get_file_size( $file_url ) {
+ $file_config = wp_upload_dir();
+ $file_url = str_replace( $file_config['baseurl'], '', $file_url );
+ $file_size = $this->calculate_file_size( $file_url );
+
+ if ( ! $file_size ) {
+ throw WPSEO_File_Size_Exception::unknown_error( $file_url );
+ }
+
+ return $file_size;
+ }
+
+ /**
+ * Calculates the file size using the Utils class.
+ *
+ * @param string $file_url The file to retrieve the size for.
+ *
+ * @return int|bool The file size or False if it could not be retrieved.
+ */
+ protected function calculate_file_size( $file_url ) {
+ return WPSEO_Image_Utils::get_file_size(
+ [
+ 'path' => $file_url,
+ ]
+ );
+ }
+}
diff --git a/wp-content/plugins/wordpress-seo/admin/statistics/class-statistics-integration.php b/wp-content/plugins/wordpress-seo/admin/statistics/class-statistics-integration.php
new file mode 100755
index 00000000..756f314c
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/statistics/class-statistics-integration.php
@@ -0,0 +1,36 @@
+statistics = $statistics;
+ }
+
+ /**
+ * Fetches statistics by REST request.
+ *
+ * @return WP_REST_Response The response object.
+ */
+ public function get_statistics() {
+ // Switch to the user locale with fallback to the site locale.
+ switch_to_locale( get_user_locale() );
+
+ $this->labels = $this->labels();
+ $statistics = $this->statistic_items();
+
+ $data = [
+ 'header' => $this->get_header_from_statistics( $statistics ),
+ 'seo_scores' => $statistics['scores'],
+ ];
+
+ return new WP_REST_Response( $data );
+ }
+
+ /**
+ * Gets a header summarizing the given statistics results.
+ *
+ * @param array $statistics The statistics results.
+ *
+ * @return string The header summing up the statistics results.
+ */
+ private function get_header_from_statistics( array $statistics ) {
+ // Personal interpretation to allow release, should be looked at later.
+ if ( $statistics['division'] === false ) {
+ return __( 'You don\'t have any published posts, your SEO scores will appear here once you make your first post!', 'wordpress-seo' );
+ }
+
+ if ( $statistics['division']['good'] > 0.66 ) {
+ return __( 'Hey, your SEO is doing pretty well! Check out the stats:', 'wordpress-seo' );
+ }
+
+ return __( 'Below are your published posts\' SEO scores. Now is as good a time as any to start improving some of your posts!', 'wordpress-seo' );
+ }
+
+ /**
+ * An array representing items to be added to the At a Glance dashboard widget.
+ *
+ * @return array The statistics for the current user.
+ */
+ private function statistic_items() {
+ $transient = $this->get_transient();
+ $user_id = get_current_user_id();
+
+ if ( isset( $transient[ $user_id ] ) ) {
+ return $transient[ $user_id ];
+ }
+
+ return $this->set_statistic_items_for_user( $transient, $user_id );
+ }
+
+ /**
+ * Gets the statistics transient value. Returns array if transient wasn't set.
+ *
+ * @return array|mixed Returns the transient or an empty array if the transient doesn't exist.
+ */
+ private function get_transient() {
+ $transient = get_transient( self::CACHE_TRANSIENT_KEY );
+
+ if ( $transient === false ) {
+ return [];
+ }
+
+ return $transient;
+ }
+
+ /**
+ * Set the statistics transient cache for a specific user.
+ *
+ * @param array $transient The current stored transient with the cached data.
+ * @param int $user The user's ID to assign the retrieved values to.
+ *
+ * @return array The statistics transient for the user.
+ */
+ private function set_statistic_items_for_user( $transient, $user ) {
+ $scores = $this->get_seo_scores_with_post_count();
+ $division = $this->get_seo_score_division( $scores );
+
+ $transient[ $user ] = [
+ // Use array_values because array_filter may return non-zero indexed arrays.
+ 'scores' => array_values( array_filter( $scores, [ $this, 'filter_items' ] ) ),
+ 'division' => $division,
+ ];
+
+ set_transient( self::CACHE_TRANSIENT_KEY, $transient, DAY_IN_SECONDS );
+
+ return $transient[ $user ];
+ }
+
+ /**
+ * Gets the division of SEO scores.
+ *
+ * @param array $scores The SEO scores.
+ *
+ * @return array|bool The division of SEO scores, false if there are no posts.
+ */
+ private function get_seo_score_division( array $scores ) {
+ $total = 0;
+ $division = [];
+
+ foreach ( $scores as $score ) {
+ $total += $score['count'];
+ }
+
+ if ( $total === 0 ) {
+ return false;
+ }
+
+ foreach ( $scores as $score ) {
+ $division[ $score['seo_rank'] ] = ( $score['count'] / $total );
+ }
+
+ return $division;
+ }
+
+ /**
+ * Get all SEO ranks and data associated with them.
+ *
+ * @return array An array of SEO scores and associated data.
+ */
+ private function get_seo_scores_with_post_count() {
+ $ranks = WPSEO_Rank::get_all_ranks();
+
+ return array_map( [ $this, 'map_rank_to_widget' ], $ranks );
+ }
+
+ /**
+ * Converts a rank to data usable in the dashboard widget.
+ *
+ * @param WPSEO_Rank $rank The rank to map.
+ *
+ * @return array The mapped rank.
+ */
+ private function map_rank_to_widget( WPSEO_Rank $rank ) {
+ return [
+ 'seo_rank' => $rank->get_rank(),
+ 'label' => $this->get_label_for_rank( $rank ),
+ 'count' => $this->statistics->get_post_count( $rank ),
+ 'link' => $this->get_link_for_rank( $rank ),
+ ];
+ }
+
+ /**
+ * Returns a dashboard widget label to use for a certain rank.
+ *
+ * @param WPSEO_Rank $rank The rank to return a label for.
+ *
+ * @return string The label for the rank.
+ */
+ private function get_label_for_rank( WPSEO_Rank $rank ) {
+ return $this->labels[ $rank->get_rank() ];
+ }
+
+ /**
+ * Determines the labels for the various scoring ranks that are known within Yoast SEO.
+ *
+ * @return array Array containing the translatable labels.
+ */
+ private function labels() {
+ return [
+ WPSEO_Rank::NO_FOCUS => sprintf(
+ /* translators: %1$s expands to an opening strong tag, %2$s expands to a closing strong tag */
+ __( 'Posts %1$swithout%2$s a focus keyphrase', 'wordpress-seo' ),
+ '',
+ ''
+ ),
+ WPSEO_Rank::BAD => sprintf(
+ /* translators: %s expands to the score */
+ __( 'Posts with the SEO score: %s', 'wordpress-seo' ),
+ '' . __( 'Needs improvement', 'wordpress-seo' ) . ''
+ ),
+ WPSEO_Rank::OK => sprintf(
+ /* translators: %s expands to the score */
+ __( 'Posts with the SEO score: %s', 'wordpress-seo' ),
+ '' . __( 'OK', 'wordpress-seo' ) . ''
+ ),
+ WPSEO_Rank::GOOD => sprintf(
+ /* translators: %s expands to the score */
+ __( 'Posts with the SEO score: %s', 'wordpress-seo' ),
+ '' . __( 'Good', 'wordpress-seo' ) . ''
+ ),
+ WPSEO_Rank::NO_INDEX => __( 'Posts that should not show up in search results', 'wordpress-seo' ),
+ ];
+ }
+
+ /**
+ * Filter items if they have a count of zero.
+ *
+ * @param array $item The item to potentially filter out.
+ *
+ * @return bool Whether or not the count is zero.
+ */
+ private function filter_items( $item ) {
+ return $item['count'] !== 0;
+ }
+
+ /**
+ * Returns a link for the overview of posts of a certain rank.
+ *
+ * @param WPSEO_Rank $rank The rank to return a link for.
+ *
+ * @return string The link that shows an overview of posts with that rank.
+ */
+ private function get_link_for_rank( WPSEO_Rank $rank ) {
+ if ( current_user_can( 'edit_others_posts' ) === false ) {
+ return esc_url( admin_url( 'edit.php?post_status=publish&post_type=post&seo_filter=' . $rank->get_rank() . '&author=' . get_current_user_id() ) );
+ }
+
+ return esc_url( admin_url( 'edit.php?post_status=publish&post_type=post&seo_filter=' . $rank->get_rank() ) );
+ }
+}
diff --git a/wp-content/plugins/wordpress-seo/admin/taxonomy/class-taxonomy-columns.php b/wp-content/plugins/wordpress-seo/admin/taxonomy/class-taxonomy-columns.php
new file mode 100755
index 00000000..fda2f19c
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/taxonomy/class-taxonomy-columns.php
@@ -0,0 +1,231 @@
+taxonomy = $this->get_taxonomy();
+
+ if ( ! empty( $this->taxonomy ) ) {
+ add_filter( 'manage_edit-' . $this->taxonomy . '_columns', [ $this, 'add_columns' ] );
+ add_filter( 'manage_' . $this->taxonomy . '_custom_column', [ $this, 'parse_column' ], 10, 3 );
+ }
+
+ $this->analysis_seo = new WPSEO_Metabox_Analysis_SEO();
+ $this->analysis_readability = new WPSEO_Metabox_Analysis_Readability();
+ $this->indexable_repository = YoastSEO()->classes->get( Indexable_Repository::class );
+ $this->score_icon_helper = YoastSEO()->helpers->score_icon;
+ }
+
+ /**
+ * Adds an SEO score column to the terms table, right after the description column.
+ *
+ * @param array $columns Current set columns.
+ *
+ * @return array
+ */
+ public function add_columns( array $columns ) {
+ if ( $this->display_metabox( $this->taxonomy ) === false ) {
+ return $columns;
+ }
+
+ $new_columns = [];
+
+ foreach ( $columns as $column_name => $column_value ) {
+ $new_columns[ $column_name ] = $column_value;
+
+ if ( $column_name === 'description' && $this->analysis_seo->is_enabled() ) {
+ $new_columns['wpseo-score'] = ''
+ . __( 'SEO score', 'wordpress-seo' ) . '';
+ }
+
+ if ( $column_name === 'description' && $this->analysis_readability->is_enabled() ) {
+ $new_columns['wpseo-score-readability'] = ''
+ . __( 'Readability score', 'wordpress-seo' ) . '';
+ }
+ }
+
+ return $new_columns;
+ }
+
+ /**
+ * Parses the column.
+ *
+ * @param string $content The current content of the column.
+ * @param string $column_name The name of the column.
+ * @param int $term_id ID of requested taxonomy.
+ *
+ * @return string
+ */
+ public function parse_column( $content, $column_name, $term_id ) {
+
+ switch ( $column_name ) {
+ case 'wpseo-score':
+ return $this->get_score_value( $term_id );
+
+ case 'wpseo-score-readability':
+ return $this->get_score_readability_value( $term_id );
+ }
+
+ return $content;
+ }
+
+ /**
+ * Retrieves the taxonomy from the $_GET or $_POST variable.
+ *
+ * @return string|null The current taxonomy or null when it is not set.
+ */
+ public function get_current_taxonomy() {
+ // phpcs:disable WordPress.Security.NonceVerification.Missing,WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
+ if ( ! empty( $_SERVER['REQUEST_METHOD'] ) && $_SERVER['REQUEST_METHOD'] === 'POST' ) {
+ if ( isset( $_POST['taxonomy'] ) && is_string( $_POST['taxonomy'] ) ) {
+ return sanitize_text_field( wp_unslash( $_POST['taxonomy'] ) );
+ }
+ }
+ elseif ( isset( $_GET['taxonomy'] ) && is_string( $_GET['taxonomy'] ) ) {
+ return sanitize_text_field( wp_unslash( $_GET['taxonomy'] ) );
+ }
+ // phpcs:enable WordPress.Security.NonceVerification.Missing,WordPress.Security.NonceVerification.Recommended
+ return null;
+ }
+
+ /**
+ * Returns the posted/get taxonomy value if it is set.
+ *
+ * @return string|null
+ */
+ private function get_taxonomy() {
+ // phpcs:disable WordPress.Security.NonceVerification.Missing,WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
+ if ( wp_doing_ajax() ) {
+ if ( isset( $_POST['taxonomy'] ) && is_string( $_POST['taxonomy'] ) ) {
+ return sanitize_text_field( wp_unslash( $_POST['taxonomy'] ) );
+ }
+ }
+ elseif ( isset( $_GET['taxonomy'] ) && is_string( $_GET['taxonomy'] ) ) {
+ return sanitize_text_field( wp_unslash( $_GET['taxonomy'] ) );
+ }
+ // phpcs:enable WordPress.Security.NonceVerification.Missing,WordPress.Security.NonceVerification.Recommended
+ return null;
+ }
+
+ /**
+ * Parses the value for the score column.
+ *
+ * @param int $term_id ID of requested term.
+ *
+ * @return string
+ */
+ private function get_score_value( $term_id ) {
+ $indexable = $this->indexable_repository->find_by_id_and_type( (int) $term_id, 'term' );
+
+ return $this->score_icon_helper->for_seo( $indexable, '', __( 'Term is set to noindex.', 'wordpress-seo' ) );
+ }
+
+ /**
+ * Parses the value for the readability score column.
+ *
+ * @param int $term_id ID of the requested term.
+ *
+ * @return string The HTML for the readability score indicator.
+ */
+ private function get_score_readability_value( $term_id ) {
+ $score = (int) WPSEO_Taxonomy_Meta::get_term_meta( $term_id, $this->taxonomy, 'content_score' );
+
+ return $this->score_icon_helper->for_readability( $score );
+ }
+
+ /**
+ * Check if the taxonomy is indexable.
+ *
+ * @param mixed $term The current term.
+ *
+ * @return bool Whether the term is indexable.
+ */
+ private function is_indexable( $term ) {
+ // When the no_index value is not empty and not default, check if its value is index.
+ $no_index = WPSEO_Taxonomy_Meta::get_term_meta( $term->term_id, $this->taxonomy, 'noindex' );
+
+ // Check if the default for taxonomy is empty (this will be index).
+ if ( ! empty( $no_index ) && $no_index !== 'default' ) {
+ return ( $no_index === 'index' );
+ }
+
+ if ( is_object( $term ) ) {
+ $no_index_key = 'noindex-tax-' . $term->taxonomy;
+
+ // If the option is false, this means we want to index it.
+ return WPSEO_Options::get( $no_index_key, false ) === false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Wraps the WPSEO_Metabox check to determine whether the metabox should be displayed either by
+ * choice of the admin or because the taxonomy is not public.
+ *
+ * @since 7.0
+ *
+ * @param string|null $taxonomy Optional. The taxonomy to test, defaults to the current taxonomy.
+ *
+ * @return bool Whether the meta box (and associated columns etc) should be hidden.
+ */
+ private function display_metabox( $taxonomy = null ) {
+ $current_taxonomy = $this->get_current_taxonomy();
+
+ if ( ! isset( $taxonomy ) && ! empty( $current_taxonomy ) ) {
+ $taxonomy = $current_taxonomy;
+ }
+
+ return WPSEO_Utils::is_metabox_active( $taxonomy, 'taxonomy' );
+ }
+}
diff --git a/wp-content/plugins/wordpress-seo/admin/taxonomy/class-taxonomy-fields-presenter.php b/wp-content/plugins/wordpress-seo/admin/taxonomy/class-taxonomy-fields-presenter.php
new file mode 100755
index 00000000..a32b8530
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/taxonomy/class-taxonomy-fields-presenter.php
@@ -0,0 +1,197 @@
+tax_meta = WPSEO_Taxonomy_Meta::get_term_meta( (int) $term->term_id, $term->taxonomy );
+ }
+
+ /**
+ * Displaying the form fields.
+ *
+ * @param array $fields Array with the fields that will be displayed.
+ *
+ * @return string
+ */
+ public function html( array $fields ) {
+ $content = '';
+ foreach ( $fields as $field_name => $field_configuration ) {
+ $content .= $this->form_row( 'wpseo_' . $field_name, $field_configuration );
+ }
+ return $content;
+ }
+
+ /**
+ * Create a row in the form table.
+ *
+ * @param string $field_name Variable the row controls.
+ * @param array $field_configuration Array with the field configuration.
+ *
+ * @return string
+ */
+ private function form_row( $field_name, array $field_configuration ) {
+ $esc_field_name = esc_attr( $field_name );
+
+ $options = (array) $field_configuration['options'];
+
+ if ( ! empty( $field_configuration['description'] ) ) {
+ $options['description'] = $field_configuration['description'];
+ }
+
+ $label = $this->get_label( $field_configuration['label'], $esc_field_name );
+ $field = $this->get_field( $field_configuration['type'], $esc_field_name, $this->get_field_value( $field_name ), $options );
+ $help_content = ( $field_configuration['options']['help'] ?? '' );
+ $help_button_text = ( $field_configuration['options']['help-button'] ?? '' );
+ $help = new WPSEO_Admin_Help_Panel( $field_name, $help_button_text, $help_content );
+
+ return $this->parse_row( $label, $help, $field );
+ }
+
+ /**
+ * Generates the html for the given field config.
+ *
+ * @param string $field_type The fieldtype, e.g: text, checkbox, etc.
+ * @param string $field_name The name of the field.
+ * @param string $field_value The value of the field.
+ * @param array $options Array with additional options.
+ *
+ * @return string
+ */
+ private function get_field( $field_type, $field_name, $field_value, array $options ) {
+
+ $class = $this->get_class( $options );
+ $field = '';
+ $description = '';
+ $aria_describedby = '';
+
+ if ( ! empty( $options['description'] ) ) {
+ $aria_describedby = ' aria-describedby="' . $field_name . '-desc"';
+ $description = '
';
+ }
+
+ /**
+ * Returns the relevant metabox sections for the current view.
+ *
+ * @return WPSEO_Metabox_Section[]
+ */
+ private function get_tabs() {
+ $tabs = [];
+
+ $label = __( 'SEO', 'wordpress-seo' );
+ if ( $this->seo_analysis->is_enabled() ) {
+ $label = '' . $label;
+ }
+
+ $tabs[] = new WPSEO_Metabox_Section_React( 'content', $label );
+
+ if ( $this->readability_analysis->is_enabled() ) {
+ $tabs[] = new WPSEO_Metabox_Section_Readability();
+ }
+
+ if ( $this->inclusive_language_analysis->is_enabled() ) {
+ $tabs[] = new WPSEO_Metabox_Section_Inclusive_Language();
+ }
+
+ if ( $this->is_social_enabled ) {
+ $tabs[] = new WPSEO_Metabox_Section_React(
+ 'social',
+ '' . __( 'Social', 'wordpress-seo' ),
+ '',
+ [
+ 'html_after' => '',
+ ]
+ );
+ }
+
+ $tabs = array_merge( $tabs, $this->get_additional_tabs() );
+
+ return $tabs;
+ }
+
+ /**
+ * Returns the metabox tabs that have been added by other plugins.
+ *
+ * @return WPSEO_Metabox_Section_Additional[]
+ */
+ protected function get_additional_tabs() {
+ $tabs = [];
+
+ /**
+ * Private filter: 'yoast_free_additional_taxonomy_metabox_sections'.
+ *
+ * Meant for internal use only. Allows adding additional tabs to the Yoast SEO metabox for taxonomies.
+ *
+ * @param array[] $tabs {
+ * An array of arrays with tab specifications.
+ *
+ * @type array $tab {
+ * A tab specification.
+ *
+ * @type string $name The name of the tab. Used in the HTML IDs, href and aria properties.
+ * @type string $link_content The content of the tab link.
+ * @type string $content The content of the tab.
+ * @type array $options {
+ * Optional. Extra options.
+ *
+ * @type string $link_class Optional. The class for the tab link.
+ * @type string $link_aria_label Optional. The aria label of the tab link.
+ * }
+ * }
+ * }
+ */
+ $requested_tabs = apply_filters( 'yoast_free_additional_taxonomy_metabox_sections', [] );
+
+ foreach ( $requested_tabs as $tab ) {
+ if ( is_array( $tab ) && array_key_exists( 'name', $tab ) && array_key_exists( 'link_content', $tab ) && array_key_exists( 'content', $tab ) ) {
+ $options = array_key_exists( 'options', $tab ) ? $tab['options'] : [];
+ $tabs[] = new WPSEO_Metabox_Section_Additional(
+ $tab['name'],
+ $tab['link_content'],
+ $tab['content'],
+ $options
+ );
+ }
+ }
+
+ return $tabs;
+ }
+
+ /**
+ * Retrieves the product title.
+ *
+ * @return string The product title.
+ */
+ protected function get_product_title() {
+ return YoastSEO()->helpers->product->get_product_name();
+ }
+}
diff --git a/wp-content/plugins/wordpress-seo/admin/taxonomy/class-taxonomy.php b/wp-content/plugins/wordpress-seo/admin/taxonomy/class-taxonomy.php
new file mode 100755
index 00000000..a63d3778
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/taxonomy/class-taxonomy.php
@@ -0,0 +1,487 @@
+taxonomy = $this::get_taxonomy();
+
+ add_action( 'edit_term', [ $this, 'update_term' ], 99, 3 );
+ add_action( 'init', [ $this, 'custom_category_descriptions_allow_html' ] );
+ add_action( 'admin_init', [ $this, 'admin_init' ] );
+
+ if ( self::is_term_overview( $GLOBALS['pagenow'] ) ) {
+ new WPSEO_Taxonomy_Columns();
+ }
+ $this->analysis_seo = new WPSEO_Metabox_Analysis_SEO();
+ $this->analysis_readability = new WPSEO_Metabox_Analysis_Readability();
+ $this->analysis_inclusive_language = new WPSEO_Metabox_Analysis_Inclusive_Language();
+ }
+
+ /**
+ * Add hooks late enough for taxonomy object to be available for checks.
+ *
+ * @return void
+ */
+ public function admin_init() {
+
+ $taxonomy = get_taxonomy( $this->taxonomy );
+
+ if ( empty( $taxonomy ) || empty( $taxonomy->public ) || ! $this->show_metabox() ) {
+ return;
+ }
+
+ // Adds custom category description editor. Needs a hook that runs before the description field.
+ add_action( "{$this->taxonomy}_term_edit_form_top", [ $this, 'custom_category_description_editor' ] );
+
+ add_action( sanitize_text_field( $this->taxonomy ) . '_edit_form', [ $this, 'term_metabox' ], 90, 1 );
+ add_action( 'admin_enqueue_scripts', [ $this, 'admin_enqueue_scripts' ] );
+ }
+
+ /**
+ * Show the SEO inputs for term.
+ *
+ * @param stdClass|WP_Term $term Term to show the edit boxes for.
+ *
+ * @return void
+ */
+ public function term_metabox( $term ) {
+ if ( WPSEO_Metabox::is_internet_explorer() ) {
+ $this->show_internet_explorer_notice();
+ return;
+ }
+
+ $metabox = new WPSEO_Taxonomy_Metabox( $this->taxonomy, $term );
+ $metabox->display();
+ }
+
+ /**
+ * Renders the content for the internet explorer metabox.
+ *
+ * @return void
+ */
+ private function show_internet_explorer_notice() {
+ $product_title = YoastSEO()->helpers->product->get_product_name();
+
+ // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Reason: $product_title is hardcoded.
+ printf( '
%1$s
', $product_title );
+ echo '
';
+
+ $content = sprintf(
+ /* translators: 1: Link start tag to the Firefox website, 2: Link start tag to the Chrome website, 3: Link start tag to the Edge website, 4: Link closing tag. */
+ esc_html__( 'The browser you are currently using is unfortunately rather dated. Since we strive to give you the best experience possible, we no longer support this browser. Instead, please use %1$sFirefox%4$s, %2$sChrome%4$s or %3$sMicrosoft Edge%4$s.', 'wordpress-seo' ),
+ '',
+ '',
+ '',
+ ''
+ );
+ // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Output escaped above.
+ echo new Alert_Presenter( $content );
+
+ echo '
';
+ }
+
+ /**
+ * Queue assets for taxonomy screens.
+ *
+ * @since 1.5.0
+ *
+ * @return void
+ */
+ public function admin_enqueue_scripts() {
+
+ $pagenow = $GLOBALS['pagenow'];
+
+ if ( ! ( self::is_term_edit( $pagenow ) || self::is_term_overview( $pagenow ) ) ) {
+ return;
+ }
+
+ $asset_manager = new WPSEO_Admin_Asset_Manager();
+ $asset_manager->enqueue_style( 'monorepo' );
+
+ $tag_id = $this::get_tag_id();
+
+ if (
+ self::is_term_edit( $pagenow )
+ && $tag_id !== null
+ ) {
+ wp_enqueue_media(); // Enqueue files needed for upload functionality.
+
+ $asset_manager->enqueue_style( 'metabox-css' );
+ if ( $this->analysis_readability->is_enabled() ) {
+ $asset_manager->enqueue_style( 'scoring' );
+ }
+ $asset_manager->enqueue_style( 'ai-generator' );
+ $asset_manager->enqueue_script( 'term-edit' );
+
+ /**
+ * Remove the emoji script as it is incompatible with both React and any
+ * contenteditable fields.
+ */
+ remove_action( 'admin_print_scripts', 'print_emoji_detection_script' );
+
+ $asset_manager->localize_script( 'term-edit', 'wpseoAdminL10n', WPSEO_Utils::get_admin_l10n() );
+
+ $script_data = [
+ 'analysis' => [
+ 'plugins' => [
+ 'replaceVars' => [
+ 'replace_vars' => $this->get_replace_vars(),
+ 'recommended_replace_vars' => $this->get_recommended_replace_vars(),
+ 'scope' => $this->determine_scope(),
+ ],
+ 'shortcodes' => [
+ 'wpseo_shortcode_tags' => $this->get_valid_shortcode_tags(),
+ 'wpseo_filter_shortcodes_nonce' => wp_create_nonce( 'wpseo-filter-shortcodes' ),
+ ],
+ ],
+ 'worker' => [
+ 'url' => YoastSEO()->helpers->asset->get_asset_url( 'yoast-seo-analysis-worker' ),
+ 'dependencies' => YoastSEO()->helpers->asset->get_dependency_urls_by_handle( 'yoast-seo-analysis-worker' ),
+ 'keywords_assessment_url' => YoastSEO()->helpers->asset->get_asset_url( 'yoast-seo-used-keywords-assessment' ),
+ 'log_level' => WPSEO_Utils::get_analysis_worker_log_level(),
+ ],
+ ],
+ 'metabox' => $this->localize_term_scraper_script( $tag_id ),
+ 'isTerm' => true,
+ 'postId' => $tag_id,
+ 'postType' => $this->get_taxonomy(),
+ 'usedKeywordsNonce' => wp_create_nonce( 'wpseo-keyword-usage' ),
+ ];
+
+ /**
+ * The website information repository.
+ *
+ * @var Website_Information_Repository $repo
+ */
+ $repo = YoastSEO()->classes->get( Website_Information_Repository::class );
+ $term_information = $repo->get_term_site_information();
+ $term_information->set_term( get_term_by( 'id', $tag_id, $this::get_taxonomy() ) );
+ $script_data = array_merge_recursive( $term_information->get_legacy_site_information(), $script_data );
+
+ $asset_manager->localize_script( 'term-edit', 'wpseoScriptData', $script_data );
+ }
+
+ if ( self::is_term_overview( $pagenow ) ) {
+ $asset_manager->enqueue_script( 'edit-page' );
+ $asset_manager->enqueue_style( 'edit-page' );
+ }
+ }
+
+ /**
+ * Update the taxonomy meta data on save.
+ *
+ * @param int $term_id ID of the term to save data for.
+ * @param int $tt_id The taxonomy_term_id for the term.
+ * @param string $taxonomy The taxonomy the term belongs to.
+ *
+ * @return void
+ */
+ public function update_term( $term_id, $tt_id, $taxonomy ) {
+ // Bail if this is a multisite installation and the site has been switched.
+ if ( is_multisite() && ms_is_switched() ) {
+ return;
+ }
+
+ /* Create post array with only our values. */
+ $new_meta_data = [];
+ foreach ( WPSEO_Taxonomy_Meta::$defaults_per_term as $key => $default ) {
+ // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce is already checked by WordPress before executing this action.
+ if ( isset( $_POST[ $key ] ) && is_string( $_POST[ $key ] ) ) {
+ // phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: $data is getting sanitized later.
+ $data = wp_unslash( $_POST[ $key ] );
+ $new_meta_data[ $key ] = ( $key !== 'wpseo_canonical' ) ? WPSEO_Utils::sanitize_text_field( $data ) : WPSEO_Utils::sanitize_url( $data );
+ }
+
+ // If analysis is disabled remove that analysis score value from the DB.
+ if ( $this->is_meta_value_disabled( $key ) ) {
+ $new_meta_data[ $key ] = '';
+ }
+ }
+
+ // Saving the values.
+ WPSEO_Taxonomy_Meta::set_values( $term_id, $taxonomy, $new_meta_data );
+ }
+
+ /**
+ * Determines if the given meta value key is disabled.
+ *
+ * @param string $key The key of the meta value.
+ * @return bool Whether the given meta value key is disabled.
+ */
+ public function is_meta_value_disabled( $key ) {
+ if ( $key === 'wpseo_linkdex' && ! $this->analysis_seo->is_enabled() ) {
+ return true;
+ }
+
+ if ( $key === 'wpseo_content_score' && ! $this->analysis_readability->is_enabled() ) {
+ return true;
+ }
+
+ if ( $key === 'wpseo_inclusive_language_score' && ! $this->analysis_inclusive_language->is_enabled() ) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Allows post-kses-filtered HTML in term descriptions.
+ *
+ * @return void
+ */
+ public function custom_category_descriptions_allow_html() {
+ remove_filter( 'term_description', 'wp_kses_data' );
+ remove_filter( 'pre_term_description', 'wp_filter_kses' );
+ add_filter( 'term_description', 'wp_kses_post' );
+ add_filter( 'pre_term_description', 'wp_filter_post_kses' );
+ }
+
+ /**
+ * Output the WordPress editor.
+ *
+ * @return void
+ */
+ public function custom_category_description_editor() {
+ wp_editor( '', 'description' );
+ }
+
+ /**
+ * Pass variables to js for use with the term-scraper.
+ *
+ * @param int $term_id The ID of the term to localize the script for.
+ *
+ * @return array
+ */
+ public function localize_term_scraper_script( $term_id ) {
+ $term = get_term_by( 'id', $term_id, $this::get_taxonomy() );
+ $taxonomy = get_taxonomy( $term->taxonomy );
+
+ $term_formatter = new WPSEO_Metabox_Formatter(
+ new WPSEO_Term_Metabox_Formatter( $taxonomy, $term )
+ );
+
+ return $term_formatter->get_values();
+ }
+
+ /**
+ * Pass some variables to js for replacing variables.
+ *
+ * @return array
+ */
+ public function localize_replace_vars_script() {
+ return [
+ 'replace_vars' => $this->get_replace_vars(),
+ 'recommended_replace_vars' => $this->get_recommended_replace_vars(),
+ 'scope' => $this->determine_scope(),
+ ];
+ }
+
+ /**
+ * Determines the scope based on the current taxonomy.
+ * This can be used by the replacevar plugin to determine if a replacement needs to be executed.
+ *
+ * @return string String decribing the current scope.
+ */
+ private function determine_scope() {
+ $taxonomy = $this::get_taxonomy();
+
+ if ( $taxonomy === 'category' ) {
+ return 'category';
+ }
+
+ if ( $taxonomy === 'post_tag' ) {
+ return 'tag';
+ }
+
+ return 'term';
+ }
+
+ /**
+ * Determines if a given page is the term overview page.
+ *
+ * @param string $page The string to check for the term overview page.
+ *
+ * @return bool
+ */
+ public static function is_term_overview( $page ) {
+ return $page === 'edit-tags.php';
+ }
+
+ /**
+ * Determines if a given page is the term edit page.
+ *
+ * @param string $page The string to check for the term edit page.
+ *
+ * @return bool
+ */
+ public static function is_term_edit( $page ) {
+ return $page === 'term.php';
+ }
+
+ /**
+ * Function to get the labels for the current taxonomy.
+ *
+ * @return object|null Labels for the current taxonomy or null if the taxonomy is not set.
+ */
+ public static function get_labels() {
+ $term = self::get_taxonomy();
+ if ( $term !== '' ) {
+ $taxonomy = get_taxonomy( $term );
+ return $taxonomy->labels;
+ }
+ return null;
+ }
+
+ /**
+ * Retrieves a template.
+ * Check if metabox for current taxonomy should be displayed.
+ *
+ * @return bool
+ */
+ private function show_metabox() {
+ $option_key = 'display-metabox-tax-' . $this->taxonomy;
+
+ return WPSEO_Options::get( $option_key );
+ }
+
+ /**
+ * Getting the taxonomy from the URL.
+ *
+ * @return string
+ */
+ private static function get_taxonomy() {
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
+ if ( isset( $_GET['taxonomy'] ) && is_string( $_GET['taxonomy'] ) ) {
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
+ return sanitize_text_field( wp_unslash( $_GET['taxonomy'] ) );
+ }
+ return '';
+ }
+
+ /**
+ * Get the current tag ID from the GET parameters.
+ *
+ * @return int|null the tag ID if it exists, null otherwise.
+ */
+ private static function get_tag_id() {
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
+ if ( isset( $_GET['tag_ID'] ) && is_string( $_GET['tag_ID'] ) ) {
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: We are not processing form information, We are casting to an integer.
+ $tag_id = (int) wp_unslash( $_GET['tag_ID'] );
+ if ( $tag_id > 0 ) {
+ return $tag_id;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Prepares the replace vars for localization.
+ *
+ * @return array The replacement variables.
+ */
+ private function get_replace_vars() {
+ $term_id = $this::get_tag_id();
+ $term = get_term_by( 'id', $term_id, $this::get_taxonomy() );
+
+ $cached_replacement_vars = [];
+
+ $vars_to_cache = [
+ 'date',
+ 'id',
+ 'sitename',
+ 'sitedesc',
+ 'sep',
+ 'page',
+ 'term_title',
+ 'term_description',
+ 'term_hierarchy',
+ 'category_description',
+ 'tag_description',
+ 'searchphrase',
+ 'currentyear',
+ ];
+
+ foreach ( $vars_to_cache as $var ) {
+ $cached_replacement_vars[ $var ] = wpseo_replace_vars( '%%' . $var . '%%', $term );
+ }
+
+ return $cached_replacement_vars;
+ }
+
+ /**
+ * Prepares the recommended replace vars for localization.
+ *
+ * @return array The recommended replacement variables.
+ */
+ private function get_recommended_replace_vars() {
+ $recommended_replace_vars = new WPSEO_Admin_Recommended_Replace_Vars();
+ $taxonomy = $this::get_taxonomy();
+
+ if ( $taxonomy === '' ) {
+ return [];
+ }
+
+ // What is recommended depends on the current context.
+ $page_type = $recommended_replace_vars->determine_for_term( $taxonomy );
+
+ return $recommended_replace_vars->get_recommended_replacevars_for( $page_type );
+ }
+
+ /**
+ * Returns an array with shortcode tags for all registered shortcodes.
+ *
+ * @return array Array with shortcode tags.
+ */
+ private function get_valid_shortcode_tags() {
+ $shortcode_tags = [];
+
+ foreach ( $GLOBALS['shortcode_tags'] as $tag => $description ) {
+ $shortcode_tags[] = $tag;
+ }
+
+ return $shortcode_tags;
+ }
+}
diff --git a/wp-content/plugins/wordpress-seo/admin/tracking/class-tracking-addon-data.php b/wp-content/plugins/wordpress-seo/admin/tracking/class-tracking-addon-data.php
new file mode 100755
index 00000000..0cbc27c7
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/tracking/class-tracking-addon-data.php
@@ -0,0 +1,126 @@
+is_installed( WPSEO_Addon_Manager::LOCAL_SLUG ) ) {
+ $addon_settings = $this->get_local_addon_settings( $addon_settings, 'wpseo_local', WPSEO_Addon_Manager::LOCAL_SLUG, $this->local_include_list );
+ }
+
+ if ( $addon_manager->is_installed( WPSEO_Addon_Manager::WOOCOMMERCE_SLUG ) ) {
+ $addon_settings = $this->get_addon_settings( $addon_settings, 'wpseo_woo', WPSEO_Addon_Manager::WOOCOMMERCE_SLUG, $this->woo_include_list );
+ }
+
+ if ( $addon_manager->is_installed( WPSEO_Addon_Manager::NEWS_SLUG ) ) {
+ $addon_settings = $this->get_addon_settings( $addon_settings, 'wpseo_news', WPSEO_Addon_Manager::NEWS_SLUG, $this->news_include_list );
+ }
+
+ if ( $addon_manager->is_installed( WPSEO_Addon_Manager::VIDEO_SLUG ) ) {
+ $addon_settings = $this->get_addon_settings( $addon_settings, 'wpseo_video', WPSEO_Addon_Manager::VIDEO_SLUG, $this->video_include_list );
+ }
+
+ return $addon_settings;
+ }
+
+ /**
+ * Gets the tracked options from the addon
+ *
+ * @param array $addon_settings The current list of addon settings.
+ * @param string $source_name The option key of the addon.
+ * @param string $slug The addon slug.
+ * @param array $option_include_list All the options to be included in tracking.
+ *
+ * @return array
+ */
+ public function get_addon_settings( array $addon_settings, $source_name, $slug, $option_include_list ) {
+ $source_options = get_option( $source_name, [] );
+ if ( ! is_array( $source_options ) || empty( $source_options ) ) {
+ return $addon_settings;
+ }
+ $addon_settings[ $slug ] = array_intersect_key( $source_options, array_flip( $option_include_list ) );
+
+ return $addon_settings;
+ }
+
+ /**
+ * Filter business_type in local addon settings.
+ *
+ * Remove the business_type setting when 'multiple_locations_shared_business_info' setting is turned off.
+ *
+ * @param array $addon_settings The current list of addon settings.
+ * @param string $source_name The option key of the addon.
+ * @param string $slug The addon slug.
+ * @param array $option_include_list All the options to be included in tracking.
+ *
+ * @return array
+ */
+ public function get_local_addon_settings( array $addon_settings, $source_name, $slug, $option_include_list ) {
+ $source_options = get_option( $source_name, [] );
+ if ( ! is_array( $source_options ) || empty( $source_options ) ) {
+ return $addon_settings;
+ }
+ $addon_settings[ $slug ] = array_intersect_key( $source_options, array_flip( $option_include_list ) );
+
+ if ( array_key_exists( 'use_multiple_locations', $source_options ) && array_key_exists( 'business_type', $addon_settings[ $slug ] ) && $source_options['use_multiple_locations'] === 'on' && $source_options['multiple_locations_shared_business_info'] === 'off' ) {
+ $addon_settings[ $slug ]['business_type'] = 'multiple_locations';
+ }
+
+ if ( ! ( new WooCommerce_Conditional() )->is_met() ) {
+ unset( $addon_settings[ $slug ]['woocommerce_local_pickup_setting'] );
+ }
+
+ return $addon_settings;
+ }
+}
diff --git a/wp-content/plugins/wordpress-seo/admin/tracking/class-tracking-default-data.php b/wp-content/plugins/wordpress-seo/admin/tracking/class-tracking-default-data.php
new file mode 100755
index 00000000..498e7d08
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/tracking/class-tracking-default-data.php
@@ -0,0 +1,60 @@
+ get_option( 'blogname' ),
+ '@timestamp' => (int) gmdate( 'Uv' ),
+ 'wpVersion' => $this->get_wordpress_version(),
+ 'homeURL' => home_url(),
+ 'adminURL' => admin_url(),
+ 'isMultisite' => is_multisite(),
+ 'siteLanguage' => get_bloginfo( 'language' ),
+ 'gmt_offset' => get_option( 'gmt_offset' ),
+ 'timezoneString' => get_option( 'timezone_string' ),
+ 'migrationStatus' => get_option( 'yoast_migrations_free' ),
+ 'countPosts' => $this->get_post_count( 'post' ),
+ 'countPages' => $this->get_post_count( 'page' ),
+ ];
+ }
+
+ /**
+ * Returns the number of posts of a certain type.
+ *
+ * @param string $post_type The post type return the count for.
+ *
+ * @return int The count for this post type.
+ */
+ protected function get_post_count( $post_type ) {
+ $count = wp_count_posts( $post_type );
+ if ( isset( $count->publish ) ) {
+ return $count->publish;
+ }
+ return 0;
+ }
+
+ /**
+ * Returns the WordPress version.
+ *
+ * @return string The version.
+ */
+ protected function get_wordpress_version() {
+ global $wp_version;
+
+ return $wp_version;
+ }
+}
diff --git a/wp-content/plugins/wordpress-seo/admin/tracking/class-tracking-plugin-data.php b/wp-content/plugins/wordpress-seo/admin/tracking/class-tracking-plugin-data.php
new file mode 100755
index 00000000..2c585e1d
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/tracking/class-tracking-plugin-data.php
@@ -0,0 +1,90 @@
+ $this->get_plugin_data(),
+ ];
+ }
+
+ /**
+ * Returns all plugins.
+ *
+ * @return array The formatted plugins.
+ */
+ protected function get_plugin_data() {
+
+ if ( ! function_exists( 'get_plugin_data' ) ) {
+ require_once ABSPATH . 'wp-admin/includes/plugin.php';
+ }
+ $plugins = wp_get_active_and_valid_plugins();
+
+ $plugins = array_map( 'get_plugin_data', $plugins );
+ $this->set_auto_update_plugin_list();
+ $plugins = array_map( [ $this, 'format_plugin' ], $plugins );
+
+ $plugin_data = [];
+ foreach ( $plugins as $plugin ) {
+ $plugin_key = sanitize_title( $plugin['name'] );
+ $plugin_data[ $plugin_key ] = $plugin;
+ }
+
+ return $plugin_data;
+ }
+
+ /**
+ * Sets all auto updating plugin data so it can be used in the tracking list.
+ *
+ * @return void
+ */
+ public function set_auto_update_plugin_list() {
+
+ $auto_update_plugins = [];
+ $auto_update_plugin_files = get_option( 'auto_update_plugins' );
+ if ( $auto_update_plugin_files ) {
+ foreach ( $auto_update_plugin_files as $auto_update_plugin ) {
+ $data = get_plugin_data( WP_PLUGIN_DIR . DIRECTORY_SEPARATOR . $auto_update_plugin );
+ $auto_update_plugins[ $data['Name'] ] = $data;
+ }
+ }
+
+ $this->auto_update_plugin_list = $auto_update_plugins;
+ }
+
+ /**
+ * Formats the plugin array.
+ *
+ * @param array $plugin The plugin details.
+ *
+ * @return array The formatted array.
+ */
+ protected function format_plugin( array $plugin ) {
+
+ return [
+ 'name' => $plugin['Name'],
+ 'version' => $plugin['Version'],
+ 'auto_updating' => array_key_exists( $plugin['Name'], $this->auto_update_plugin_list ),
+ ];
+ }
+}
diff --git a/wp-content/plugins/wordpress-seo/admin/tracking/class-tracking-server-data.php b/wp-content/plugins/wordpress-seo/admin/tracking/class-tracking-server-data.php
new file mode 100755
index 00000000..220753f1
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/tracking/class-tracking-server-data.php
@@ -0,0 +1,85 @@
+ $this->get_server_data(),
+ ];
+ }
+
+ /**
+ * Returns the values with server details.
+ *
+ * @return array Array with the value.
+ */
+ protected function get_server_data() {
+ $server_data = [];
+
+ // Validate if the server address is a valid IP-address.
+ $ipaddress = isset( $_SERVER['SERVER_ADDR'] ) ? filter_var( wp_unslash( $_SERVER['SERVER_ADDR'] ), FILTER_VALIDATE_IP ) : '';
+ if ( $ipaddress ) {
+ $server_data['ip'] = $ipaddress;
+ $server_data['Hostname'] = gethostbyaddr( $ipaddress );
+ }
+
+ $server_data['os'] = function_exists( 'php_uname' ) ? php_uname() : PHP_OS;
+ $server_data['PhpVersion'] = PHP_VERSION;
+ $server_data['CurlVersion'] = $this->get_curl_info();
+ $server_data['PhpExtensions'] = $this->get_php_extensions();
+
+ return $server_data;
+ }
+
+ /**
+ * Returns details about the curl version.
+ *
+ * @return array|null The curl info. Or null when curl isn't available..
+ */
+ protected function get_curl_info() {
+ if ( ! function_exists( 'curl_version' ) ) {
+ return null;
+ }
+
+ $curl = curl_version();
+
+ $ssl_support = true;
+ if ( ! $curl['features'] && CURL_VERSION_SSL ) {
+ $ssl_support = false;
+ }
+
+ return [
+ 'version' => $curl['version'],
+ 'sslSupport' => $ssl_support,
+ ];
+ }
+
+ /**
+ * Returns a list with php extensions.
+ *
+ * @return array Returns the state of the php extensions.
+ */
+ protected function get_php_extensions() {
+ return [
+ 'imagick' => extension_loaded( 'imagick' ),
+ 'filter' => extension_loaded( 'filter' ),
+ 'bcmath' => extension_loaded( 'bcmath' ),
+ 'pcre' => extension_loaded( 'pcre' ),
+ 'xml' => extension_loaded( 'xml' ),
+ 'pdo_mysql' => extension_loaded( 'pdo_mysql' ),
+ ];
+ }
+}
diff --git a/wp-content/plugins/wordpress-seo/admin/tracking/class-tracking-settings-data.php b/wp-content/plugins/wordpress-seo/admin/tracking/class-tracking-settings-data.php
new file mode 100755
index 00000000..24ff8645
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/tracking/class-tracking-settings-data.php
@@ -0,0 +1,287 @@
+include_list = apply_filters( 'wpseo_tracking_settings_include_list', $this->include_list );
+
+ $options = WPSEO_Options::get_all();
+ // Returns the settings of which the keys intersect with the values of the include list.
+ $options = array_intersect_key( $options, array_flip( $this->include_list ) );
+
+ return [
+ 'settings' => $this->anonymize_settings( $options ),
+ ];
+ }
+
+ /**
+ * Anonimizes the WPSEO_Options array by replacing all $anonymous_settings values to 'used'.
+ *
+ * @param array $settings The settings.
+ *
+ * @return array The anonymized settings.
+ */
+ private function anonymize_settings( $settings ) {
+ foreach ( $this->anonymous_settings as $setting ) {
+ if ( ! empty( $settings[ $setting ] ) ) {
+ $settings[ $setting ] = 'used';
+ }
+ }
+
+ return $settings;
+ }
+}
diff --git a/wp-content/plugins/wordpress-seo/admin/tracking/class-tracking-theme-data.php b/wp-content/plugins/wordpress-seo/admin/tracking/class-tracking-theme-data.php
new file mode 100755
index 00000000..e2225950
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/tracking/class-tracking-theme-data.php
@@ -0,0 +1,51 @@
+ [
+ 'name' => $theme->get( 'Name' ),
+ 'url' => $theme->get( 'ThemeURI' ),
+ 'version' => $theme->get( 'Version' ),
+ 'author' => [
+ 'name' => $theme->get( 'Author' ),
+ 'url' => $theme->get( 'AuthorURI' ),
+ ],
+ 'parentTheme' => $this->get_parent_theme( $theme ),
+ 'blockTemplateSupport' => current_theme_supports( 'block-templates' ),
+ 'isBlockTheme' => function_exists( 'wp_is_block_theme' ) && wp_is_block_theme(),
+ ],
+ ];
+ }
+
+ /**
+ * Returns the name of the parent theme.
+ *
+ * @param WP_Theme $theme The theme object.
+ *
+ * @return string|null The name of the parent theme or null.
+ */
+ private function get_parent_theme( WP_Theme $theme ) {
+ if ( is_child_theme() ) {
+ return $theme->get( 'Template' );
+ }
+
+ return null;
+ }
+}
diff --git a/wp-content/plugins/wordpress-seo/admin/tracking/class-tracking.php b/wp-content/plugins/wordpress-seo/admin/tracking/class-tracking.php
new file mode 100755
index 00000000..a7154c20
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/tracking/class-tracking.php
@@ -0,0 +1,240 @@
+tracking_enabled() ) {
+ return;
+ }
+
+ $this->endpoint = $endpoint;
+ $this->threshold = $threshold;
+ $this->current_time = time();
+ }
+
+ /**
+ * Registers all hooks to WordPress.
+ *
+ * @return void
+ */
+ public function register_hooks() {
+ if ( ! $this->tracking_enabled() ) {
+ return;
+ }
+
+ // Send tracking data on `admin_init`.
+ add_action( 'admin_init', [ $this, 'send' ], 1 );
+
+ // Add an action hook that will be triggered at the specified time by `wp_schedule_single_event()`.
+ add_action( 'wpseo_send_tracking_data_after_core_update', [ $this, 'send' ] );
+ // Call `wp_schedule_single_event()` after a WordPress core update.
+ add_action( 'upgrader_process_complete', [ $this, 'schedule_tracking_data_sending' ], 10, 2 );
+ }
+
+ /**
+ * Schedules a new sending of the tracking data after a WordPress core update.
+ *
+ * @param bool|WP_Upgrader $upgrader Optional. WP_Upgrader instance or false.
+ * Depending on context, it might be a Theme_Upgrader,
+ * Plugin_Upgrader, Core_Upgrade, or Language_Pack_Upgrader.
+ * instance. Default false.
+ * @param array $data Array of update data.
+ *
+ * @return void
+ */
+ public function schedule_tracking_data_sending( $upgrader = false, $data = [] ) {
+ // Return if it's not a WordPress core update.
+ if ( ! $upgrader || ! isset( $data['type'] ) || $data['type'] !== 'core' ) {
+ return;
+ }
+
+ /*
+ * To uniquely identify the scheduled cron event, `wp_next_scheduled()`
+ * needs to receive the same arguments as those used when originally
+ * scheduling the event otherwise it will always return false.
+ */
+ if ( ! wp_next_scheduled( 'wpseo_send_tracking_data_after_core_update', [ true ] ) ) {
+ /*
+ * Schedule sending of data tracking 6 hours after a WordPress core
+ * update. Pass a `true` parameter for the callback `$force` argument.
+ */
+ wp_schedule_single_event( ( time() + ( HOUR_IN_SECONDS * 6 ) ), 'wpseo_send_tracking_data_after_core_update', [ true ] );
+ }
+ }
+
+ /**
+ * Sends the tracking data.
+ *
+ * @param bool $force Whether to send the tracking data ignoring the two
+ * weeks time threshold. Default false.
+ *
+ * @return void
+ */
+ public function send( $force = false ) {
+ if ( ! $this->should_send_tracking( $force ) ) {
+ return;
+ }
+
+ // Set a 'content-type' header of 'application/json'.
+ $tracking_request_args = [
+ 'headers' => [
+ 'content-type:' => 'application/json',
+ ],
+ ];
+
+ $collector = $this->get_collector();
+
+ $request = new WPSEO_Remote_Request( $this->endpoint, $tracking_request_args );
+ $request->set_body( $collector->get_as_json() );
+ $request->send();
+
+ update_option( $this->option_name, $this->current_time, 'yes' );
+ }
+
+ /**
+ * Determines whether to send the tracking data.
+ *
+ * Returns false if tracking is disabled or the current page is one of the
+ * admin plugins pages. Returns true when there's no tracking data stored or
+ * the data was sent more than two weeks ago. The two weeks interval is set
+ * when instantiating the class.
+ *
+ * @param bool $ignore_time_treshhold Whether to send the tracking data ignoring
+ * the two weeks time treshhold. Default false.
+ *
+ * @return bool True when tracking data should be sent.
+ */
+ protected function should_send_tracking( $ignore_time_treshhold = false ) {
+ global $pagenow;
+
+ // Only send tracking on the main site of a multi-site instance. This returns true on non-multisite installs.
+ if ( is_network_admin() || ! is_main_site() ) {
+ return false;
+ }
+
+ // Because we don't want to possibly block plugin actions with our routines.
+ if ( in_array( $pagenow, [ 'plugins.php', 'plugin-install.php', 'plugin-editor.php' ], true ) ) {
+ return false;
+ }
+
+ $last_time = get_option( $this->option_name );
+
+ // When tracking data haven't been sent yet or when sending data is forced.
+ if ( ! $last_time || $ignore_time_treshhold ) {
+ return true;
+ }
+
+ return $this->exceeds_treshhold( $this->current_time - $last_time );
+ }
+
+ /**
+ * Checks if the given amount of seconds exceeds the set threshold.
+ *
+ * @param int $seconds The amount of seconds to check.
+ *
+ * @return bool True when seconds is bigger than threshold.
+ */
+ protected function exceeds_treshhold( $seconds ) {
+ return ( $seconds > $this->threshold );
+ }
+
+ /**
+ * Returns the collector for collecting the data.
+ *
+ * @return WPSEO_Collector The instance of the collector.
+ */
+ public function get_collector() {
+ $collector = new WPSEO_Collector();
+ $collector->add_collection( new WPSEO_Tracking_Default_Data() );
+ $collector->add_collection( new WPSEO_Tracking_Server_Data() );
+ $collector->add_collection( new WPSEO_Tracking_Theme_Data() );
+ $collector->add_collection( new WPSEO_Tracking_Plugin_Data() );
+ $collector->add_collection( new WPSEO_Tracking_Settings_Data() );
+ $collector->add_collection( new WPSEO_Tracking_Addon_Data() );
+ $collector->add_collection( YoastSEO()->classes->get( Missing_Indexables_Collector::class ) );
+ $collector->add_collection( YoastSEO()->classes->get( To_Be_Cleaned_Indexables_Collector::class ) );
+
+ return $collector;
+ }
+
+ /**
+ * See if we should run tracking at all.
+ *
+ * @return bool True when we can track, false when we can't.
+ */
+ private function tracking_enabled() {
+ // Check if we're allowing tracking.
+ $tracking = WPSEO_Options::get( 'tracking' );
+
+ if ( $tracking === false ) {
+ return false;
+ }
+
+ // Save this state.
+ if ( $tracking === null ) {
+ /**
+ * Filter: 'wpseo_enable_tracking' - Enables the data tracking of Yoast SEO Premium and add-ons.
+ *
+ * @param string|false $is_enabled The enabled state. Default is false.
+ */
+ $tracking = apply_filters( 'wpseo_enable_tracking', false );
+
+ WPSEO_Options::set( 'tracking', $tracking );
+ }
+
+ if ( $tracking === false ) {
+ return false;
+ }
+
+ if ( ! YoastSEO()->helpers->environment->is_production_mode() ) {
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/wp-content/plugins/wordpress-seo/admin/views/class-yoast-feature-toggle.php b/wp-content/plugins/wordpress-seo/admin/views/class-yoast-feature-toggle.php
new file mode 100755
index 00000000..9dd62ab4
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/views/class-yoast-feature-toggle.php
@@ -0,0 +1,206 @@
+ $value ) {
+ if ( property_exists( $this, $key ) ) {
+ $this->$key = $value;
+ }
+ }
+ }
+
+ /**
+ * Magic isset-er.
+ *
+ * @param string $key Key to check whether a value for it is set.
+ *
+ * @return bool True if set, false otherwise.
+ */
+ public function __isset( $key ) {
+ return isset( $this->$key );
+ }
+
+ /**
+ * Magic getter.
+ *
+ * @param string $key Key to get the value for.
+ *
+ * @return mixed Value for the key, or null if not set.
+ */
+ public function __get( $key ) {
+ if ( isset( $this->$key ) ) {
+ return $this->$key;
+ }
+
+ return null;
+ }
+
+ /**
+ * Checks whether the feature for this toggle is enabled.
+ *
+ * @return bool True if the feature is enabled, false otherwise.
+ */
+ public function is_enabled() {
+ return (bool) WPSEO_Options::get( $this->setting );
+ }
+}
diff --git a/wp-content/plugins/wordpress-seo/admin/views/class-yoast-feature-toggles.php b/wp-content/plugins/wordpress-seo/admin/views/class-yoast-feature-toggles.php
new file mode 100755
index 00000000..14aca6b9
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/views/class-yoast-feature-toggles.php
@@ -0,0 +1,285 @@
+toggles === null ) {
+ $this->toggles = $this->load_toggles();
+ }
+
+ return $this->toggles;
+ }
+
+ /**
+ * Loads the available feature toggles.
+ *
+ * Also ensures that the toggles are all Yoast_Feature_Toggle instances and sorted by their order value.
+ *
+ * @return array List of sorted Yoast_Feature_Toggle instances.
+ */
+ protected function load_toggles() {
+ $xml_sitemap_extra = false;
+ if ( WPSEO_Options::get( 'enable_xml_sitemap' ) ) {
+ $xml_sitemap_extra = '' . esc_html__( 'See the XML sitemap.', 'wordpress-seo' ) . '';
+ }
+
+ $feature_toggles = [
+ (object) [
+ 'name' => __( 'SEO analysis', 'wordpress-seo' ),
+ 'setting' => 'keyword_analysis_active',
+ 'label' => __( 'The SEO analysis offers suggestions to improve the SEO of your text.', 'wordpress-seo' ),
+ 'read_more_label' => __( 'Learn how the SEO analysis can help you rank.', 'wordpress-seo' ),
+ 'read_more_url' => 'https://yoa.st/2ak',
+ 'order' => 10,
+ ],
+ (object) [
+ 'name' => __( 'Readability analysis', 'wordpress-seo' ),
+ 'setting' => 'content_analysis_active',
+ 'label' => __( 'The readability analysis offers suggestions to improve the structure and style of your text.', 'wordpress-seo' ),
+ 'read_more_label' => __( 'Discover why readability is important for SEO.', 'wordpress-seo' ),
+ 'read_more_url' => 'https://yoa.st/2ao',
+ 'order' => 20,
+ ],
+ (object) [
+ 'name' => __( 'Inclusive language analysis', 'wordpress-seo' ),
+ 'supported_languages' => Language_Helper::$languages_with_inclusive_language_support,
+ 'setting' => 'inclusive_language_analysis_active',
+ 'label' => __( 'The inclusive language analysis offers suggestions to write more inclusive copy.', 'wordpress-seo' ),
+ 'read_more_label' => __( 'Discover why inclusive language is important for SEO.', 'wordpress-seo' ),
+ 'read_more_url' => 'https://yoa.st/inclusive-language-features-free',
+ 'order' => 25,
+ ],
+ (object) [
+ 'name' => __( 'Cornerstone content', 'wordpress-seo' ),
+ 'setting' => 'enable_cornerstone_content',
+ 'label' => __( 'The cornerstone content feature lets you to mark and filter cornerstone content on your website.', 'wordpress-seo' ),
+ 'read_more_label' => __( 'Find out how cornerstone content can help you improve your site structure.', 'wordpress-seo' ),
+ 'read_more_url' => 'https://yoa.st/dashboard-help-cornerstone',
+ 'order' => 30,
+ ],
+ (object) [
+ 'name' => __( 'Text link counter', 'wordpress-seo' ),
+ 'setting' => 'enable_text_link_counter',
+ 'label' => __( 'The text link counter helps you improve your site structure.', 'wordpress-seo' ),
+ 'read_more_label' => __( 'Find out how the text link counter can enhance your SEO.', 'wordpress-seo' ),
+ 'read_more_url' => 'https://yoa.st/2aj',
+ 'order' => 40,
+ ],
+ (object) [
+ 'name' => __( 'Insights', 'wordpress-seo' ),
+ 'setting' => 'enable_metabox_insights',
+ 'label' => __( 'Find relevant data about your content right in the Insights section in the Yoast SEO metabox. You’ll see what words you use most often and if they’re a match with your keywords! ', 'wordpress-seo' ),
+ 'read_more_label' => __( 'Find out how Insights can help you improve your content.', 'wordpress-seo' ),
+ 'read_more_url' => 'https://yoa.st/4ew',
+ 'premium_url' => 'https://yoa.st/2ai',
+ 'order' => 41,
+ ],
+ (object) [
+ 'name' => __( 'Link suggestions', 'wordpress-seo' ),
+ 'premium' => true,
+ 'setting' => 'enable_link_suggestions',
+ 'label' => __( 'Get relevant internal linking suggestions — while you’re writing! The link suggestions metabox shows a list of posts on your blog with similar content that might be interesting to link to. ', 'wordpress-seo' ),
+ 'read_more_label' => __( 'Read more about how internal linking can improve your site structure.', 'wordpress-seo' ),
+ 'read_more_url' => 'https://yoa.st/4ev',
+ 'premium_url' => 'https://yoa.st/17g',
+ 'premium_upsell_url' => 'https://yoa.st/get-link-suggestions',
+ 'order' => 42,
+ ],
+ (object) [
+ 'name' => __( 'XML sitemaps', 'wordpress-seo' ),
+ 'setting' => 'enable_xml_sitemap',
+ /* translators: %s: Yoast SEO */
+ 'label' => sprintf( __( 'Enable the XML sitemaps that %s generates.', 'wordpress-seo' ), 'Yoast SEO' ),
+ 'read_more_label' => __( 'Read why XML Sitemaps are important for your site.', 'wordpress-seo' ),
+ 'read_more_url' => 'https://yoa.st/2a-',
+ 'extra' => $xml_sitemap_extra,
+ 'after' => $this->sitemaps_toggle_after(),
+ 'order' => 60,
+ ],
+ (object) [
+ 'name' => __( 'Admin bar menu', 'wordpress-seo' ),
+ 'setting' => 'enable_admin_bar_menu',
+ /* translators: 1: Yoast SEO */
+ 'label' => sprintf( __( 'The %1$s admin bar menu contains useful links to third-party tools for analyzing pages and makes it easy to see if you have new notifications.', 'wordpress-seo' ), 'Yoast SEO' ),
+ 'order' => 80,
+ ],
+ (object) [
+ 'name' => __( 'Security: no advanced or schema settings for authors', 'wordpress-seo' ),
+ 'setting' => 'disableadvanced_meta',
+ 'label' => sprintf(
+ /* translators: 1: Yoast SEO, 2: translated version of "Off" */
+ __( 'The advanced section of the %1$s meta box allows a user to remove posts from the search results or change the canonical. The settings in the schema tab allows a user to change schema meta data for a post. These are things you might not want any author to do. That\'s why, by default, only editors and administrators can do this. Setting to "%2$s" allows all users to change these settings.', 'wordpress-seo' ),
+ 'Yoast SEO',
+ __( 'Off', 'wordpress-seo' )
+ ),
+ 'order' => 90,
+ ],
+ (object) [
+ 'name' => __( 'Usage tracking', 'wordpress-seo' ),
+ 'label' => __( 'Usage tracking', 'wordpress-seo' ),
+ 'setting' => 'tracking',
+ 'read_more_label' => sprintf(
+ /* translators: 1: Yoast SEO */
+ __( 'Allow us to track some data about your site to improve our plugin.', 'wordpress-seo' ),
+ 'Yoast SEO'
+ ),
+ 'read_more_url' => 'https://yoa.st/usage-tracking-2',
+ 'order' => 95,
+ ],
+ (object) [
+ 'name' => __( 'REST API: Head endpoint', 'wordpress-seo' ),
+ 'setting' => 'enable_headless_rest_endpoints',
+ 'label' => sprintf(
+ /* translators: 1: Yoast SEO */
+ __( 'This %1$s REST API endpoint gives you all the metadata you need for a specific URL. This will make it very easy for headless WordPress sites to use %1$s for all their SEO meta output.', 'wordpress-seo' ),
+ 'Yoast SEO'
+ ),
+ 'order' => 100,
+ ],
+ (object) [
+ 'name' => __( 'Enhanced Slack sharing', 'wordpress-seo' ),
+ 'setting' => 'enable_enhanced_slack_sharing',
+ 'label' => __( 'This adds an author byline and reading time estimate to the article’s snippet when shared on Slack.', 'wordpress-seo' ),
+ 'read_more_label' => __( 'Find out how a rich snippet can improve visibility and click-through-rate.', 'wordpress-seo' ),
+ 'read_more_url' => 'https://yoa.st/help-slack-share',
+ 'order' => 105,
+ ],
+ (object) [
+ 'name' => __( 'IndexNow', 'wordpress-seo' ),
+ 'premium' => true,
+ 'setting' => 'enable_index_now',
+ 'label' => __( 'Automatically ping search engines like Bing and Yandex whenever you publish, update or delete a post.', 'wordpress-seo' ),
+ 'read_more_label' => __( 'Find out how IndexNow can help your site.', 'wordpress-seo' ),
+ 'read_more_url' => 'https://yoa.st/index-now-read-more',
+ 'premium_url' => 'https://yoa.st/index-now-feature',
+ 'premium_upsell_url' => 'https://yoa.st/get-indexnow',
+ 'order' => 110,
+ ],
+ (object) [
+ 'name' => __( 'AI title & description generator', 'wordpress-seo' ),
+ 'premium' => true,
+ 'setting' => 'enable_ai_generator',
+ 'label' => __( 'Use the power of Yoast AI to automatically generate compelling titles and descriptions for your posts and pages.', 'wordpress-seo' ),
+ 'read_more_label' => __( 'Learn more', 'wordpress-seo' ),
+ 'read_more_url' => 'https://yoa.st/ai-generator-read-more',
+ 'premium_url' => 'https://yoa.st/ai-generator-feature',
+ 'premium_upsell_url' => 'https://yoa.st/get-ai-generator',
+ 'order' => 115,
+ ],
+ ];
+
+ /**
+ * Filter to add feature toggles from add-ons.
+ *
+ * @param array $feature_toggles Array with feature toggle objects where each object
+ * should have a `name`, `setting` and `label` property.
+ */
+ $feature_toggles = apply_filters( 'wpseo_feature_toggles', $feature_toggles );
+
+ $feature_toggles = array_map( [ $this, 'ensure_toggle' ], $feature_toggles );
+ usort( $feature_toggles, [ $this, 'sort_toggles_callback' ] );
+
+ return $feature_toggles;
+ }
+
+ /**
+ * Returns html for a warning that core sitemaps are enabled when yoast seo sitemaps are disabled.
+ *
+ * @return string HTML string for the warning.
+ */
+ protected function sitemaps_toggle_after() {
+ $out = '
';
+ $alert = new Alert_Presenter(
+ /* translators: %1$s: expands to an opening anchor tag, %2$s: expands to a closing anchor tag */
+ sprintf( esc_html__( 'Disabling Yoast SEO\'s XML sitemaps will not disable WordPress\' core sitemaps. In some cases, this %1$s may result in SEO errors on your site%2$s. These may be reported in Google Search Console and other tools.', 'wordpress-seo' ), '', '' ),
+ 'warning'
+ );
+ $out .= $alert->present();
+ $out .= '
';
+
+ return $out;
+ }
+
+ /**
+ * Ensures that the passed value is a Yoast_Feature_Toggle.
+ *
+ * @param Yoast_Feature_Toggle|object|array $toggle_data Feature toggle instance, or raw object or array
+ * containing feature toggle data.
+ *
+ * @return Yoast_Feature_Toggle Feature toggle instance based on $toggle_data.
+ */
+ protected function ensure_toggle( $toggle_data ) {
+ if ( $toggle_data instanceof Yoast_Feature_Toggle ) {
+ return $toggle_data;
+ }
+
+ if ( is_object( $toggle_data ) ) {
+ $toggle_data = get_object_vars( $toggle_data );
+ }
+
+ return new Yoast_Feature_Toggle( $toggle_data );
+ }
+
+ /**
+ * Callback for sorting feature toggles by their order.
+ *
+ * {@internal Once the minimum PHP version goes up to PHP 7.0, the logic in the function
+ * can be replaced with the spaceship operator `<=>`.}
+ *
+ * @param Yoast_Feature_Toggle $feature_a Feature A.
+ * @param Yoast_Feature_Toggle $feature_b Feature B.
+ *
+ * @return int An integer less than, equal to, or greater than zero indicating respectively
+ * that feature A is considered to be less than, equal to, or greater than feature B.
+ */
+ protected function sort_toggles_callback( Yoast_Feature_Toggle $feature_a, Yoast_Feature_Toggle $feature_b ) {
+ return ( $feature_a->order - $feature_b->order );
+ }
+}
diff --git a/wp-content/plugins/wordpress-seo/admin/views/class-yoast-input-select.php b/wp-content/plugins/wordpress-seo/admin/views/class-yoast-input-select.php
new file mode 100755
index 00000000..1f2a1735
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/views/class-yoast-input-select.php
@@ -0,0 +1,146 @@
+select_id = $select_id;
+ $this->select_name = $select_name;
+ $this->select_options = $select_options;
+ $this->selected_option = $selected_option;
+ }
+
+ /**
+ * Print the rendered view.
+ *
+ * @return void
+ */
+ public function output_html() {
+ // Extract it, because we want each value accessible via a variable instead of accessing it as an array.
+ extract( $this->get_select_values() );
+
+ require WPSEO_PATH . 'admin/views/form/select.php';
+ }
+
+ /**
+ * Return the rendered view.
+ *
+ * @return string
+ */
+ public function get_html() {
+ ob_start();
+
+ $this->output_html();
+
+ $rendered_output = ob_get_contents();
+ ob_end_clean();
+
+ return $rendered_output;
+ }
+
+ /**
+ * Add an attribute to the attributes property.
+ *
+ * @param string $attribute The name of the attribute to add.
+ * @param string $value The value of the attribute.
+ *
+ * @return void
+ */
+ public function add_attribute( $attribute, $value ) {
+ $this->select_attributes[ $attribute ] = $value;
+ }
+
+ /**
+ * Return the set fields for the select.
+ *
+ * @return array
+ */
+ private function get_select_values() {
+ return [
+ 'id' => $this->select_id,
+ 'name' => $this->select_name,
+ 'attributes' => $this->get_attributes(),
+ 'options' => $this->select_options,
+ 'selected' => $this->selected_option,
+ ];
+ }
+
+ /**
+ * Return the attribute string, when there are attributes set.
+ *
+ * @return string
+ */
+ private function get_attributes() {
+ $attributes = $this->select_attributes;
+
+ if ( ! empty( $attributes ) ) {
+ array_walk( $attributes, [ $this, 'parse_attribute' ] );
+
+ return implode( ' ', $attributes ) . ' ';
+ }
+
+ return '';
+ }
+
+ /**
+ * Get an attribute from the attributes.
+ *
+ * @param string $value The value of the attribute.
+ * @param string $attribute The attribute to look for.
+ *
+ * @return void
+ */
+ private function parse_attribute( &$value, $attribute ) {
+ $value = sprintf( '%s="%s"', sanitize_key( $attribute ), esc_attr( $value ) );
+ }
+}
diff --git a/wp-content/plugins/wordpress-seo/admin/views/class-yoast-integration-toggles.php b/wp-content/plugins/wordpress-seo/admin/views/class-yoast-integration-toggles.php
new file mode 100755
index 00000000..ac66ee0f
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/views/class-yoast-integration-toggles.php
@@ -0,0 +1,139 @@
+toggles === null ) {
+ $this->toggles = $this->load_toggles();
+ }
+
+ return $this->toggles;
+ }
+
+ /**
+ * Loads the available integration toggles.
+ *
+ * Also ensures that the toggles are all Yoast_Feature_Toggle instances and sorted by their order value.
+ *
+ * @return array List of sorted Yoast_Feature_Toggle instances.
+ */
+ protected function load_toggles() {
+ $integration_toggles = [
+ (object) [
+ /* translators: %s: 'Semrush' */
+ 'name' => sprintf( __( '%s integration', 'wordpress-seo' ), 'Semrush' ),
+ 'setting' => 'semrush_integration_active',
+ 'label' => sprintf(
+ /* translators: %s: 'Semrush' */
+ __( 'The %s integration offers suggestions and insights for keywords related to the entered focus keyphrase.', 'wordpress-seo' ),
+ 'Semrush'
+ ),
+ 'order' => 10,
+ ],
+ (object) [
+ /* translators: %s: Algolia. */
+ 'name' => sprintf( esc_html__( '%s integration', 'wordpress-seo' ), 'Algolia' ),
+ 'premium' => true,
+ 'setting' => 'algolia_integration_active',
+ 'label' => __( 'Improve the quality of your site search! Automatically helps your users find your cornerstone and most important content in your internal search results. It also removes noindexed posts & pages from your site’s search results.', 'wordpress-seo' ),
+ /* translators: %s: Algolia. */
+ 'read_more_label' => sprintf( __( 'Find out more about our %s integration.', 'wordpress-seo' ), 'Algolia' ),
+ 'read_more_url' => 'https://yoa.st/4eu',
+ 'premium_url' => 'https://yoa.st/4ex',
+ 'premium_upsell_url' => 'https://yoa.st/get-algolia-integration',
+ 'order' => 25,
+ ],
+ ];
+
+ /**
+ * Filter to add integration toggles from add-ons.
+ *
+ * @param array $integration_toggles Array with integration toggle objects where each object
+ * should have a `name`, `setting` and `label` property.
+ */
+ $integration_toggles = apply_filters( 'wpseo_integration_toggles', $integration_toggles );
+
+ $integration_toggles = array_map( [ $this, 'ensure_toggle' ], $integration_toggles );
+ usort( $integration_toggles, [ $this, 'sort_toggles_callback' ] );
+
+ return $integration_toggles;
+ }
+
+ /**
+ * Ensures that the passed value is a Yoast_Feature_Toggle.
+ *
+ * @param Yoast_Feature_Toggle|object|array $toggle_data Feature toggle instance, or raw object or array
+ * containing integration toggle data.
+ * @return Yoast_Feature_Toggle Feature toggle instance based on $toggle_data.
+ */
+ protected function ensure_toggle( $toggle_data ) {
+ if ( $toggle_data instanceof Yoast_Feature_Toggle ) {
+ return $toggle_data;
+ }
+
+ if ( is_object( $toggle_data ) ) {
+ $toggle_data = get_object_vars( $toggle_data );
+ }
+
+ return new Yoast_Feature_Toggle( $toggle_data );
+ }
+
+ /**
+ * Callback for sorting integration toggles by their order.
+ *
+ * {@internal Once the minimum PHP version goes up to PHP 7.0, the logic in the function
+ * can be replaced with the spaceship operator `<=>`.}
+ *
+ * @param Yoast_Feature_Toggle $feature_a Feature A.
+ * @param Yoast_Feature_Toggle $feature_b Feature B.
+ *
+ * @return int An integer less than, equal to, or greater than zero indicating respectively
+ * that feature A is considered to be less than, equal to, or greater than feature B.
+ */
+ protected function sort_toggles_callback( Yoast_Feature_Toggle $feature_a, Yoast_Feature_Toggle $feature_b ) {
+ return ( $feature_a->order - $feature_b->order );
+ }
+}
diff --git a/wp-content/plugins/wordpress-seo/admin/views/form/select.php b/wp-content/plugins/wordpress-seo/admin/views/form/select.php
new file mode 100755
index 00000000..8f3a846c
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/views/form/select.php
@@ -0,0 +1,26 @@
+
+
+
diff --git a/wp-content/plugins/wordpress-seo/admin/views/interface-yoast-form-element.php b/wp-content/plugins/wordpress-seo/admin/views/interface-yoast-form-element.php
new file mode 100755
index 00000000..24a8ccb3
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/views/interface-yoast-form-element.php
@@ -0,0 +1,19 @@
+
+
+
+
+
diff --git a/wp-content/plugins/wordpress-seo/admin/views/paper-collapsible.php b/wp-content/plugins/wordpress-seo/admin/views/paper-collapsible.php
new file mode 100755
index 00000000..e8e3fea4
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/views/paper-collapsible.php
@@ -0,0 +1,79 @@
+
+
+hidden( 'show_onboarding_notice', 'wpseo_show_onboarding_notice' );
diff --git a/wp-content/plugins/wordpress-seo/admin/views/tabs/network/general.php b/wp-content/plugins/wordpress-seo/admin/views/tabs/network/general.php
new file mode 100755
index 00000000..a73c722b
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/views/tabs/network/general.php
@@ -0,0 +1,56 @@
+';
+
+/*
+ * {@internal Important: Make sure the options added to the array here are in line with the
+ * options set in the WPSEO_Option_MS::$allowed_access_options property.}}
+ */
+$yform->select(
+ 'access',
+ /* translators: %1$s expands to Yoast SEO */
+ sprintf( __( 'Who should have access to the %1$s settings', 'wordpress-seo' ), 'Yoast SEO' ),
+ [
+ 'admin' => __( 'Site Admins (default)', 'wordpress-seo' ),
+ 'superadmin' => __( 'Super Admins only', 'wordpress-seo' ),
+ ]
+);
+
+if ( get_blog_count() <= 100 ) {
+ $network_admin = new Yoast_Network_Admin();
+
+ $yform->select(
+ 'defaultblog',
+ __( 'New sites in the network inherit their SEO settings from this site', 'wordpress-seo' ),
+ $network_admin->get_site_choices( true, true )
+ );
+ echo '
' . esc_html__( 'Choose the site whose settings you want to use as default for all sites that are added to your network. If you choose \'None\', the normal plugin defaults will be used.', 'wordpress-seo' ) . '
';
+}
+else {
+ $yform->textinput( 'defaultblog', __( 'New sites in the network inherit their SEO settings from this site', 'wordpress-seo' ) );
+ echo '
';
+ printf(
+ /* translators: 1: link open tag; 2: link close tag. */
+ esc_html__( 'Enter the %1$sSite ID%2$s for the site whose settings you want to use as default for all sites that are added to your network. Leave empty for none (i.e. the normal plugin defaults will be used).', 'wordpress-seo' ),
+ '',
+ ''
+ );
+ echo '
';
+}
+
+echo '
' . esc_html__( 'Take note:', 'wordpress-seo' ) . ' ' . esc_html__( 'Privacy sensitive (FB admins and such), theme specific (title rewrite) and a few very site specific settings will not be imported to new sites.', 'wordpress-seo' ) . '
+hidden( 'show_onboarding_notice', 'wpseo_show_onboarding_notice' );
diff --git a/wp-content/plugins/wordpress-seo/admin/views/tabs/network/restore-site.php b/wp-content/plugins/wordpress-seo/admin/views/tabs/network/restore-site.php
new file mode 100755
index 00000000..ce6701a9
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/views/tabs/network/restore-site.php
@@ -0,0 +1,32 @@
+' . esc_html__( 'Using this form you can reset a site to the default SEO settings.', 'wordpress-seo' ) . '';
+
+if ( get_blog_count() <= 100 ) {
+ $network_admin = new Yoast_Network_Admin();
+
+ $yform->select(
+ 'site_id',
+ __( 'Site ID', 'wordpress-seo' ),
+ $network_admin->get_site_choices( false, true )
+ );
+}
+else {
+ $yform->textinput( 'site_id', __( 'Site ID', 'wordpress-seo' ) );
+}
+
+wp_nonce_field( 'wpseo-network-restore', 'restore_site_nonce', false );
+echo '';
diff --git a/wp-content/plugins/wordpress-seo/admin/views/tabs/tool/import-seo.php b/wp-content/plugins/wordpress-seo/admin/views/tabs/tool/import-seo.php
new file mode 100755
index 00000000..a5f666e4
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/views/tabs/tool/import-seo.php
@@ -0,0 +1,128 @@
+detect();
+if ( count( $import_check->needs_import ) === 0 ) {
+ echo '
', esc_html__( 'Import from other SEO plugins', 'wordpress-seo' ), '
';
+ echo '
';
+ printf(
+ /* translators: %s expands to Yoast SEO */
+ esc_html__( '%s did not detect any plugin data from plugins it can import from.', 'wordpress-seo' ),
+ 'Yoast SEO'
+ );
+ echo '
';
+
+ return;
+}
+
+/**
+ * Creates a select box given a name and plugins array.
+ *
+ * @param string $name Name field for the select field.
+ * @param array $plugins An array of plugins and classes.
+ *
+ * @return void
+ */
+function wpseo_import_external_select( $name, $plugins ) {
+ esc_html_e( 'Plugin: ', 'wordpress-seo' );
+ echo '';
+}
+
+?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ',
+ ''
+ );
+ ?>
+
+
+
+
+
+
+
+
+
+
diff --git a/wp-content/plugins/wordpress-seo/admin/views/tabs/tool/wpseo-export.php b/wp-content/plugins/wordpress-seo/admin/views/tabs/tool/wpseo-export.php
new file mode 100755
index 00000000..d0a41961
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/views/tabs/tool/wpseo-export.php
@@ -0,0 +1,39 @@
+export();
+ return;
+}
+
+$wpseo_export_phrase = sprintf(
+ /* translators: %1$s expands to Yoast SEO */
+ __( 'Export your %1$s settings here, to copy them on another site.', 'wordpress-seo' ),
+ 'Yoast SEO'
+);
+?>
+
+
+
diff --git a/wp-content/plugins/wordpress-seo/admin/views/tabs/tool/wpseo-import.php b/wp-content/plugins/wordpress-seo/admin/views/tabs/tool/wpseo-import.php
new file mode 100755
index 00000000..18a5bfe9
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/views/tabs/tool/wpseo-import.php
@@ -0,0 +1,46 @@
+
+
';
+ printf(
+ /* translators: %s expands to robots.txt. */
+ esc_html__( 'If you had a %s file and it was editable, you could edit it from here.', 'wordpress-seo' ),
+ 'robots.txt'
+ );
+ echo '
';
+ printf(
+ /* translators: %s expands to robots.txt. */
+ esc_html__( 'If your %s were writable, you could edit it from here.', 'wordpress-seo' ),
+ 'robots.txt'
+ );
+ echo '
';
+ printf(
+ /* translators: %s expands to ".htaccess". */
+ esc_html__( 'If your %s were writable, you could edit it from here.', 'wordpress-seo' ),
+ '.htaccess'
+ );
+ echo '
';
+ printf(
+ /* translators: %s expands to ".htaccess". */
+ esc_html__( 'If you had a %s file and it was editable, you could edit it from here.', 'wordpress-seo' ),
+ '.htaccess'
+ );
+ echo '
';
+}
+
+/**
+ * Allow adding a custom import tab.
+ */
+do_action( 'wpseo_import_tab_content' );
diff --git a/wp-content/plugins/wordpress-seo/admin/watchers/class-slug-change-watcher.php b/wp-content/plugins/wordpress-seo/admin/watchers/class-slug-change-watcher.php
new file mode 100755
index 00000000..68d18616
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/admin/watchers/class-slug-change-watcher.php
@@ -0,0 +1,256 @@
+helpers->product->is_premium() ) {
+ return;
+ }
+
+ add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] );
+
+ // Detect a post trash.
+ add_action( 'wp_trash_post', [ $this, 'detect_post_trash' ] );
+
+ // Detect a post delete.
+ add_action( 'before_delete_post', [ $this, 'detect_post_delete' ] );
+
+ // Detects deletion of a term.
+ add_action( 'delete_term_taxonomy', [ $this, 'detect_term_delete' ] );
+ }
+
+ /**
+ * Enqueues the quick edit handler.
+ *
+ * @return void
+ */
+ public function enqueue_assets() {
+ global $pagenow;
+
+ if ( ! in_array( $pagenow, [ 'edit.php', 'edit-tags.php' ], true ) ) {
+ return;
+ }
+
+ $asset_manager = new WPSEO_Admin_Asset_Manager();
+ $asset_manager->enqueue_script( 'quick-edit-handler' );
+ }
+
+ /**
+ * Shows a message when a post is about to get trashed.
+ *
+ * @param int $post_id The current post ID.
+ *
+ * @return void
+ */
+ public function detect_post_trash( $post_id ) {
+ if ( ! $this->is_post_viewable( $post_id ) ) {
+ return;
+ }
+
+ $post_label = $this->get_post_type_label( get_post_type( $post_id ) );
+
+ /* translators: %1$s expands to the translated name of the post type. */
+ $first_sentence = sprintf( __( 'You just trashed a %1$s.', 'wordpress-seo' ), $post_label );
+ $second_sentence = __( 'Search engines and other websites can still send traffic to your trashed content.', 'wordpress-seo' );
+ $message = $this->get_message( $first_sentence, $second_sentence );
+
+ $this->add_notification( $message );
+ }
+
+ /**
+ * Shows a message when a post is about to get trashed.
+ *
+ * @param int $post_id The current post ID.
+ *
+ * @return void
+ */
+ public function detect_post_delete( $post_id ) {
+ if ( ! $this->is_post_viewable( $post_id ) ) {
+ return;
+ }
+
+ $post_label = $this->get_post_type_label( get_post_type( $post_id ) );
+
+ /* translators: %1$s expands to the translated name of the post type. */
+ $first_sentence = sprintf( __( 'You just deleted a %1$s.', 'wordpress-seo' ), $post_label );
+ $second_sentence = __( 'Search engines and other websites can still send traffic to your deleted content.', 'wordpress-seo' );
+ $message = $this->get_message( $first_sentence, $second_sentence );
+
+ $this->add_notification( $message );
+ }
+
+ /**
+ * Shows a message when a term is about to get deleted.
+ *
+ * @param int $term_taxonomy_id The term taxonomy ID that will be deleted.
+ *
+ * @return void
+ */
+ public function detect_term_delete( $term_taxonomy_id ) {
+ if ( ! $this->is_term_viewable( $term_taxonomy_id ) ) {
+ return;
+ }
+
+ $term = get_term_by( 'term_taxonomy_id', (int) $term_taxonomy_id );
+ $term_label = $this->get_taxonomy_label_for_term( $term->term_id );
+
+ /* translators: %1$s expands to the translated name of the term. */
+ $first_sentence = sprintf( __( 'You just deleted a %1$s.', 'wordpress-seo' ), $term_label );
+ $second_sentence = __( 'Search engines and other websites can still send traffic to your deleted content.', 'wordpress-seo' );
+ $message = $this->get_message( $first_sentence, $second_sentence );
+
+ $this->add_notification( $message );
+ }
+
+ /**
+ * Checks if the post is viewable.
+ *
+ * @param string $post_id The post id to check.
+ *
+ * @return bool Whether the post is viewable or not.
+ */
+ protected function is_post_viewable( $post_id ) {
+ $post_type = get_post_type( $post_id );
+ if ( ! WPSEO_Post_Type::is_post_type_accessible( $post_type ) ) {
+ return false;
+ }
+
+ $post_status = get_post_status( $post_id );
+ if ( ! $this->check_visible_post_status( $post_status ) ) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Checks if the term is viewable.
+ *
+ * @param int $term_taxonomy_id The term taxonomy ID to check.
+ *
+ * @return bool Whether the term is viewable or not.
+ */
+ protected function is_term_viewable( $term_taxonomy_id ) {
+ $term = get_term_by( 'term_taxonomy_id', (int) $term_taxonomy_id );
+
+ if ( ! $term || is_wp_error( $term ) ) {
+ return false;
+ }
+
+ $taxonomy = get_taxonomy( $term->taxonomy );
+ if ( ! $taxonomy ) {
+ return false;
+ }
+
+ return $taxonomy->publicly_queryable || $taxonomy->public;
+ }
+
+ /**
+ * Gets the taxonomy label to use for a term.
+ *
+ * @param int $term_id The term ID.
+ *
+ * @return string The taxonomy's singular label.
+ */
+ protected function get_taxonomy_label_for_term( $term_id ) {
+ $term = get_term( $term_id );
+ $taxonomy = get_taxonomy( $term->taxonomy );
+
+ return $taxonomy->labels->singular_name;
+ }
+
+ /**
+ * Retrieves the singular post type label.
+ *
+ * @param string $post_type Post type to retrieve label from.
+ *
+ * @return string The singular post type name.
+ */
+ protected function get_post_type_label( $post_type ) {
+ $post_type_object = get_post_type_object( $post_type );
+
+ // If the post type of this post wasn't registered default back to post.
+ if ( $post_type_object === null ) {
+ $post_type_object = get_post_type_object( 'post' );
+ }
+
+ return $post_type_object->labels->singular_name;
+ }
+
+ /**
+ * Checks whether the given post status is visible or not.
+ *
+ * @param string $post_status The post status to check.
+ *
+ * @return bool Whether or not the post is visible.
+ */
+ protected function check_visible_post_status( $post_status ) {
+ $visible_post_statuses = [
+ 'publish',
+ 'static',
+ 'private',
+ ];
+
+ return in_array( $post_status, $visible_post_statuses, true );
+ }
+
+ /**
+ * Returns the message around changed URLs.
+ *
+ * @param string $first_sentence The first sentence of the notification.
+ * @param string $second_sentence The second sentence of the notification.
+ *
+ * @return string The full notification.
+ */
+ protected function get_message( $first_sentence, $second_sentence ) {
+ return '
' . __( 'Make sure you don\'t miss out on traffic!', 'wordpress-seo' ) . '
'
+ . '
'
+ . $first_sentence
+ . ' ' . $second_sentence
+ . ' ' . __( 'You should create a redirect to ensure your visitors do not get a 404 error when they click on the no longer working URL.', 'wordpress-seo' )
+ /* translators: %s expands to Yoast SEO Premium */
+ . ' ' . sprintf( __( 'With %s, you can easily create such redirects.', 'wordpress-seo' ), 'Yoast SEO Premium' )
+ . '
+ Generated by Yoast SEO, this is an XML Sitemap, meant for consumption by search engines.
+ You can find more information about XML sitemaps on sitemaps.org.
+
+
+
+ This XML Sitemap Index file contains sitemaps.
+
+
+
+
+
diff --git a/wp-content/plugins/wordpress-seo/images/Yoast_SEO_negative_icon.svg b/wp-content/plugins/wordpress-seo/images/Yoast_SEO_negative_icon.svg
new file mode 100755
index 00000000..ad6a6b3e
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/images/Yoast_SEO_negative_icon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/wp-content/plugins/wordpress-seo/images/academy/ai_for_seo_icon_my_yoast.png b/wp-content/plugins/wordpress-seo/images/academy/ai_for_seo_icon_my_yoast.png
new file mode 100755
index 00000000..4e27c6e2
Binary files /dev/null and b/wp-content/plugins/wordpress-seo/images/academy/ai_for_seo_icon_my_yoast.png differ
diff --git a/wp-content/plugins/wordpress-seo/images/academy/all_around_seo.png b/wp-content/plugins/wordpress-seo/images/academy/all_around_seo.png
new file mode 100755
index 00000000..c63aa729
Binary files /dev/null and b/wp-content/plugins/wordpress-seo/images/academy/all_around_seo.png differ
diff --git a/wp-content/plugins/wordpress-seo/images/academy/block_editor.png b/wp-content/plugins/wordpress-seo/images/academy/block_editor.png
new file mode 100755
index 00000000..bb47a94a
Binary files /dev/null and b/wp-content/plugins/wordpress-seo/images/academy/block_editor.png differ
diff --git a/wp-content/plugins/wordpress-seo/images/academy/copywriting.png b/wp-content/plugins/wordpress-seo/images/academy/copywriting.png
new file mode 100755
index 00000000..50812e74
Binary files /dev/null and b/wp-content/plugins/wordpress-seo/images/academy/copywriting.png differ
diff --git a/wp-content/plugins/wordpress-seo/images/academy/crawlability.png b/wp-content/plugins/wordpress-seo/images/academy/crawlability.png
new file mode 100755
index 00000000..ab7c573f
Binary files /dev/null and b/wp-content/plugins/wordpress-seo/images/academy/crawlability.png differ
diff --git a/wp-content/plugins/wordpress-seo/images/academy/ecommerce.png b/wp-content/plugins/wordpress-seo/images/academy/ecommerce.png
new file mode 100755
index 00000000..26af3317
Binary files /dev/null and b/wp-content/plugins/wordpress-seo/images/academy/ecommerce.png differ
diff --git a/wp-content/plugins/wordpress-seo/images/academy/hosting_and_server.png b/wp-content/plugins/wordpress-seo/images/academy/hosting_and_server.png
new file mode 100755
index 00000000..4aabc12f
Binary files /dev/null and b/wp-content/plugins/wordpress-seo/images/academy/hosting_and_server.png differ
diff --git a/wp-content/plugins/wordpress-seo/images/academy/keyword_research.png b/wp-content/plugins/wordpress-seo/images/academy/keyword_research.png
new file mode 100755
index 00000000..75d5d451
Binary files /dev/null and b/wp-content/plugins/wordpress-seo/images/academy/keyword_research.png differ
diff --git a/wp-content/plugins/wordpress-seo/images/academy/local.png b/wp-content/plugins/wordpress-seo/images/academy/local.png
new file mode 100755
index 00000000..6916755f
Binary files /dev/null and b/wp-content/plugins/wordpress-seo/images/academy/local.png differ
diff --git a/wp-content/plugins/wordpress-seo/images/academy/multilingual.png b/wp-content/plugins/wordpress-seo/images/academy/multilingual.png
new file mode 100755
index 00000000..cc30e09e
Binary files /dev/null and b/wp-content/plugins/wordpress-seo/images/academy/multilingual.png differ
diff --git a/wp-content/plugins/wordpress-seo/images/academy/seo_for_beginners.png b/wp-content/plugins/wordpress-seo/images/academy/seo_for_beginners.png
new file mode 100755
index 00000000..4c932b2f
Binary files /dev/null and b/wp-content/plugins/wordpress-seo/images/academy/seo_for_beginners.png differ
diff --git a/wp-content/plugins/wordpress-seo/images/academy/seo_for_wp.png b/wp-content/plugins/wordpress-seo/images/academy/seo_for_wp.png
new file mode 100755
index 00000000..363417ee
Binary files /dev/null and b/wp-content/plugins/wordpress-seo/images/academy/seo_for_wp.png differ
diff --git a/wp-content/plugins/wordpress-seo/images/academy/site_structure.png b/wp-content/plugins/wordpress-seo/images/academy/site_structure.png
new file mode 100755
index 00000000..ff0b7477
Binary files /dev/null and b/wp-content/plugins/wordpress-seo/images/academy/site_structure.png differ
diff --git a/wp-content/plugins/wordpress-seo/images/academy/structured_data_for_beginners.png b/wp-content/plugins/wordpress-seo/images/academy/structured_data_for_beginners.png
new file mode 100755
index 00000000..100db7fb
Binary files /dev/null and b/wp-content/plugins/wordpress-seo/images/academy/structured_data_for_beginners.png differ
diff --git a/wp-content/plugins/wordpress-seo/images/academy/understanding_structured_data.png b/wp-content/plugins/wordpress-seo/images/academy/understanding_structured_data.png
new file mode 100755
index 00000000..efad745d
Binary files /dev/null and b/wp-content/plugins/wordpress-seo/images/academy/understanding_structured_data.png differ
diff --git a/wp-content/plugins/wordpress-seo/images/academy/wp_for_beginners.png b/wp-content/plugins/wordpress-seo/images/academy/wp_for_beginners.png
new file mode 100755
index 00000000..d677b4dd
Binary files /dev/null and b/wp-content/plugins/wordpress-seo/images/academy/wp_for_beginners.png differ
diff --git a/wp-content/plugins/wordpress-seo/images/acf-logo.png b/wp-content/plugins/wordpress-seo/images/acf-logo.png
new file mode 100755
index 00000000..829a7023
Binary files /dev/null and b/wp-content/plugins/wordpress-seo/images/acf-logo.png differ
diff --git a/wp-content/plugins/wordpress-seo/images/ai-brand-insights-pre-launch.png b/wp-content/plugins/wordpress-seo/images/ai-brand-insights-pre-launch.png
new file mode 100755
index 00000000..efef7282
Binary files /dev/null and b/wp-content/plugins/wordpress-seo/images/ai-brand-insights-pre-launch.png differ
diff --git a/wp-content/plugins/wordpress-seo/images/ai-consent.png b/wp-content/plugins/wordpress-seo/images/ai-consent.png
new file mode 100755
index 00000000..6843e928
Binary files /dev/null and b/wp-content/plugins/wordpress-seo/images/ai-consent.png differ
diff --git a/wp-content/plugins/wordpress-seo/images/ai-fix-assessments-thumbnail.png b/wp-content/plugins/wordpress-seo/images/ai-fix-assessments-thumbnail.png
new file mode 100755
index 00000000..bb7a4ace
Binary files /dev/null and b/wp-content/plugins/wordpress-seo/images/ai-fix-assessments-thumbnail.png differ
diff --git a/wp-content/plugins/wordpress-seo/images/ai-generator-preview.png b/wp-content/plugins/wordpress-seo/images/ai-generator-preview.png
new file mode 100755
index 00000000..73ba3411
Binary files /dev/null and b/wp-content/plugins/wordpress-seo/images/ai-generator-preview.png differ
diff --git a/wp-content/plugins/wordpress-seo/images/alert-error-icon.svg b/wp-content/plugins/wordpress-seo/images/alert-error-icon.svg
new file mode 100755
index 00000000..80fa0626
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/images/alert-error-icon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/wp-content/plugins/wordpress-seo/images/alert-info-icon.svg b/wp-content/plugins/wordpress-seo/images/alert-info-icon.svg
new file mode 100755
index 00000000..332d7af5
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/images/alert-info-icon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/wp-content/plugins/wordpress-seo/images/alert-success-icon.svg b/wp-content/plugins/wordpress-seo/images/alert-success-icon.svg
new file mode 100755
index 00000000..30519e54
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/images/alert-success-icon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/wp-content/plugins/wordpress-seo/images/alert-warning-icon.svg b/wp-content/plugins/wordpress-seo/images/alert-warning-icon.svg
new file mode 100755
index 00000000..f52df867
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/images/alert-warning-icon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/wp-content/plugins/wordpress-seo/images/black-friday-2025.gif b/wp-content/plugins/wordpress-seo/images/black-friday-2025.gif
new file mode 100755
index 00000000..96b1aa19
Binary files /dev/null and b/wp-content/plugins/wordpress-seo/images/black-friday-2025.gif differ
diff --git a/wp-content/plugins/wordpress-seo/images/error-icon.svg b/wp-content/plugins/wordpress-seo/images/error-icon.svg
new file mode 100755
index 00000000..43e859de
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/images/error-icon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/wp-content/plugins/wordpress-seo/images/icon-admin-bar.svg b/wp-content/plugins/wordpress-seo/images/icon-admin-bar.svg
new file mode 100755
index 00000000..b9469f12
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/images/icon-admin-bar.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/wp-content/plugins/wordpress-seo/images/icon-cornerstone-content.svg b/wp-content/plugins/wordpress-seo/images/icon-cornerstone-content.svg
new file mode 100755
index 00000000..ca3a0b79
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/images/icon-cornerstone-content.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/wp-content/plugins/wordpress-seo/images/icon-inclusive-language-analysis.svg b/wp-content/plugins/wordpress-seo/images/icon-inclusive-language-analysis.svg
new file mode 100755
index 00000000..7125464d
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/images/icon-inclusive-language-analysis.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/wp-content/plugins/wordpress-seo/images/icon-index-now.svg b/wp-content/plugins/wordpress-seo/images/icon-index-now.svg
new file mode 100755
index 00000000..5385a9c6
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/images/icon-index-now.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/wp-content/plugins/wordpress-seo/images/icon-insights.svg b/wp-content/plugins/wordpress-seo/images/icon-insights.svg
new file mode 100755
index 00000000..d58c9934
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/images/icon-insights.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/wp-content/plugins/wordpress-seo/images/icon-internal-linking-suggestions.svg b/wp-content/plugins/wordpress-seo/images/icon-internal-linking-suggestions.svg
new file mode 100755
index 00000000..2bce75a0
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/images/icon-internal-linking-suggestions.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/wp-content/plugins/wordpress-seo/images/icon-llms-txt.svg b/wp-content/plugins/wordpress-seo/images/icon-llms-txt.svg
new file mode 100755
index 00000000..14593c4d
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/images/icon-llms-txt.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/wp-content/plugins/wordpress-seo/images/icon-open-graph.svg b/wp-content/plugins/wordpress-seo/images/icon-open-graph.svg
new file mode 100755
index 00000000..1d1bb006
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/images/icon-open-graph.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/wp-content/plugins/wordpress-seo/images/icon-readability-analysis.svg b/wp-content/plugins/wordpress-seo/images/icon-readability-analysis.svg
new file mode 100755
index 00000000..47a3b2b2
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/images/icon-readability-analysis.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/wp-content/plugins/wordpress-seo/images/icon-rest-api-endpoint.svg b/wp-content/plugins/wordpress-seo/images/icon-rest-api-endpoint.svg
new file mode 100755
index 00000000..5b917bf5
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/images/icon-rest-api-endpoint.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/wp-content/plugins/wordpress-seo/images/icon-seo-analysis.svg b/wp-content/plugins/wordpress-seo/images/icon-seo-analysis.svg
new file mode 100755
index 00000000..88878c06
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/images/icon-seo-analysis.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/wp-content/plugins/wordpress-seo/images/icon-slack-sharing.svg b/wp-content/plugins/wordpress-seo/images/icon-slack-sharing.svg
new file mode 100755
index 00000000..082073d9
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/images/icon-slack-sharing.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/wp-content/plugins/wordpress-seo/images/icon-sparkles.svg b/wp-content/plugins/wordpress-seo/images/icon-sparkles.svg
new file mode 100755
index 00000000..9e44c354
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/images/icon-sparkles.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/wp-content/plugins/wordpress-seo/images/icon-text-link-counter.svg b/wp-content/plugins/wordpress-seo/images/icon-text-link-counter.svg
new file mode 100755
index 00000000..47fa40cf
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/images/icon-text-link-counter.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/wp-content/plugins/wordpress-seo/images/icon-x-card-data.svg b/wp-content/plugins/wordpress-seo/images/icon-x-card-data.svg
new file mode 100755
index 00000000..dc8ff7b3
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/images/icon-x-card-data.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/wp-content/plugins/wordpress-seo/images/icon-xml-sitemaps.svg b/wp-content/plugins/wordpress-seo/images/icon-xml-sitemaps.svg
new file mode 100755
index 00000000..5cd00ba4
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/images/icon-xml-sitemaps.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/wp-content/plugins/wordpress-seo/images/index.php b/wp-content/plugins/wordpress-seo/images/index.php
new file mode 100755
index 00000000..e94d9a42
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/images/index.php
@@ -0,0 +1,4 @@
+
\ No newline at end of file
diff --git a/wp-content/plugins/wordpress-seo/images/link-in-icon.svg b/wp-content/plugins/wordpress-seo/images/link-in-icon.svg
new file mode 100755
index 00000000..c3748559
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/images/link-in-icon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/wp-content/plugins/wordpress-seo/images/link-out-icon.svg b/wp-content/plugins/wordpress-seo/images/link-out-icon.svg
new file mode 100755
index 00000000..202082bc
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/images/link-out-icon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/wp-content/plugins/wordpress-seo/images/local_plugin_assistant.svg b/wp-content/plugins/wordpress-seo/images/local_plugin_assistant.svg
new file mode 100755
index 00000000..44e32643
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/images/local_plugin_assistant.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/wp-content/plugins/wordpress-seo/images/mirrored_fit_bubble_man_1_optim.svg b/wp-content/plugins/wordpress-seo/images/mirrored_fit_bubble_man_1_optim.svg
new file mode 100755
index 00000000..187e6c45
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/images/mirrored_fit_bubble_man_1_optim.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/wp-content/plugins/wordpress-seo/images/mirrored_fit_bubble_woman_1_optim.svg b/wp-content/plugins/wordpress-seo/images/mirrored_fit_bubble_woman_1_optim.svg
new file mode 100755
index 00000000..4d5a2480
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/images/mirrored_fit_bubble_woman_1_optim.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/wp-content/plugins/wordpress-seo/images/mirrored_fit_bubble_woman_2_optim.svg b/wp-content/plugins/wordpress-seo/images/mirrored_fit_bubble_woman_2_optim.svg
new file mode 100755
index 00000000..801c69a0
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/images/mirrored_fit_bubble_woman_2_optim.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/wp-content/plugins/wordpress-seo/images/new-to-configuration-notice.svg b/wp-content/plugins/wordpress-seo/images/new-to-configuration-notice.svg
new file mode 100755
index 00000000..2343c570
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/images/new-to-configuration-notice.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/wp-content/plugins/wordpress-seo/images/news_plugin_assistant.svg b/wp-content/plugins/wordpress-seo/images/news_plugin_assistant.svg
new file mode 100755
index 00000000..f178f9c2
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/images/news_plugin_assistant.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/wp-content/plugins/wordpress-seo/images/plugin_subscription.svg b/wp-content/plugins/wordpress-seo/images/plugin_subscription.svg
new file mode 100755
index 00000000..a3c077b6
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/images/plugin_subscription.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/wp-content/plugins/wordpress-seo/images/question-mark.png b/wp-content/plugins/wordpress-seo/images/question-mark.png
new file mode 100755
index 00000000..f8472201
Binary files /dev/null and b/wp-content/plugins/wordpress-seo/images/question-mark.png differ
diff --git a/wp-content/plugins/wordpress-seo/images/readability-icon.svg b/wp-content/plugins/wordpress-seo/images/readability-icon.svg
new file mode 100755
index 00000000..439f52f8
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/images/readability-icon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/wp-content/plugins/wordpress-seo/images/redirect-manager-thumbnail.png b/wp-content/plugins/wordpress-seo/images/redirect-manager-thumbnail.png
new file mode 100755
index 00000000..a6ba3906
Binary files /dev/null and b/wp-content/plugins/wordpress-seo/images/redirect-manager-thumbnail.png differ
diff --git a/wp-content/plugins/wordpress-seo/images/stale-cornerstone-content-in-yoast-seo.png b/wp-content/plugins/wordpress-seo/images/stale-cornerstone-content-in-yoast-seo.png
new file mode 100755
index 00000000..581509a3
Binary files /dev/null and b/wp-content/plugins/wordpress-seo/images/stale-cornerstone-content-in-yoast-seo.png differ
diff --git a/wp-content/plugins/wordpress-seo/images/succes_marieke_bubble_optm.svg b/wp-content/plugins/wordpress-seo/images/succes_marieke_bubble_optm.svg
new file mode 100755
index 00000000..cfb36829
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/images/succes_marieke_bubble_optm.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/wp-content/plugins/wordpress-seo/images/support-team.svg b/wp-content/plugins/wordpress-seo/images/support-team.svg
new file mode 100755
index 00000000..27e2678d
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/images/support-team.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/wp-content/plugins/wordpress-seo/images/support/github.png b/wp-content/plugins/wordpress-seo/images/support/github.png
new file mode 100755
index 00000000..fc38adf8
Binary files /dev/null and b/wp-content/plugins/wordpress-seo/images/support/github.png differ
diff --git a/wp-content/plugins/wordpress-seo/images/support/help_center.png b/wp-content/plugins/wordpress-seo/images/support/help_center.png
new file mode 100755
index 00000000..43429ade
Binary files /dev/null and b/wp-content/plugins/wordpress-seo/images/support/help_center.png differ
diff --git a/wp-content/plugins/wordpress-seo/images/support/support_forums.png b/wp-content/plugins/wordpress-seo/images/support/support_forums.png
new file mode 100755
index 00000000..b4fd6afa
Binary files /dev/null and b/wp-content/plugins/wordpress-seo/images/support/support_forums.png differ
diff --git a/wp-content/plugins/wordpress-seo/images/video_plugin_assistant.svg b/wp-content/plugins/wordpress-seo/images/video_plugin_assistant.svg
new file mode 100755
index 00000000..403d0fd9
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/images/video_plugin_assistant.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/wp-content/plugins/wordpress-seo/images/woo_plugin_assistant.svg b/wp-content/plugins/wordpress-seo/images/woo_plugin_assistant.svg
new file mode 100755
index 00000000..7d00c056
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/images/woo_plugin_assistant.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/wp-content/plugins/wordpress-seo/inc/class-addon-manager.php b/wp-content/plugins/wordpress-seo/inc/class-addon-manager.php
new file mode 100755
index 00000000..54b463b1
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/inc/class-addon-manager.php
@@ -0,0 +1,892 @@
+
+ */
+ protected static $addons = [
+ 'wp-seo-premium.php' => self::PREMIUM_SLUG,
+ 'wpseo-news.php' => self::NEWS_SLUG,
+ 'video-seo.php' => self::VIDEO_SLUG,
+ 'wpseo-woocommerce.php' => self::WOOCOMMERCE_SLUG,
+ 'local-seo.php' => self::LOCAL_SLUG,
+ ];
+
+ /**
+ * The addon data for the shortlinks.
+ *
+ * @var array>
+ */
+ private $addon_details = [
+ self::PREMIUM_SLUG => [
+ 'name' => 'Yoast SEO Premium',
+ 'short_link_activation' => 'https://yoa.st/13j',
+ 'short_link_renewal' => 'https://yoa.st/4ey',
+ ],
+ self::NEWS_SLUG => [
+ 'name' => 'Yoast News SEO',
+ 'short_link_activation' => 'https://yoa.st/4xq',
+ 'short_link_renewal' => 'https://yoa.st/4xv',
+ ],
+ self::WOOCOMMERCE_SLUG => [
+ 'name' => 'Yoast WooCommerce SEO',
+ 'short_link_activation' => 'https://yoa.st/4xs',
+ 'short_link_renewal' => 'https://yoa.st/4xx',
+ ],
+ self::VIDEO_SLUG => [
+ 'name' => 'Yoast Video SEO',
+ 'short_link_activation' => 'https://yoa.st/4xr',
+ 'short_link_renewal' => 'https://yoa.st/4xw',
+ ],
+ self::LOCAL_SLUG => [
+ 'name' => 'Yoast Local SEO',
+ 'short_link_activation' => 'https://yoa.st/4xp',
+ 'short_link_renewal' => 'https://yoa.st/4xu',
+ ],
+ ];
+
+ /**
+ * Holds the site information data.
+ *
+ * @var stdClass
+ */
+ private $site_information;
+
+ /**
+ * Hooks into WordPress.
+ *
+ * @codeCoverageIgnore
+ *
+ * @return void
+ */
+ public function register_hooks() {
+ add_action( 'admin_init', [ $this, 'validate_addons' ], 15 );
+ add_filter( 'pre_set_site_transient_update_plugins', [ $this, 'check_for_updates' ] );
+ add_filter( 'plugins_api', [ $this, 'get_plugin_information' ], 10, 3 );
+ add_action( 'plugins_loaded', [ $this, 'register_expired_messages' ], 10 );
+ }
+
+ /**
+ * Registers "expired subscription" warnings to the update messages of our addons.
+ *
+ * @return void
+ */
+ public function register_expired_messages() {
+ foreach ( array_keys( $this->get_installed_addons() ) as $plugin_file ) {
+ add_action( 'in_plugin_update_message-' . $plugin_file, [ $this, 'expired_subscription_warning' ], 10, 2 );
+ }
+ }
+
+ /**
+ * Gets the subscriptions for current site.
+ *
+ * @return stdClass The subscriptions.
+ */
+ public function get_subscriptions() {
+ return $this->get_site_information()->subscriptions;
+ }
+
+ /**
+ * Provides a list of addon filenames.
+ *
+ * @return string[] List of addon filenames with their slugs.
+ */
+ public function get_addon_filenames() {
+ return self::$addons;
+ }
+
+ /**
+ * Finds the plugin file.
+ *
+ * @param string $plugin_slug The plugin slug to search.
+ *
+ * @return bool|string Plugin file when installed, False when plugin isn't installed.
+ */
+ public function get_plugin_file( $plugin_slug ) {
+ $plugins = $this->get_plugins();
+ $plugin_files = array_keys( $plugins );
+ $target_plugin_file = array_search( $plugin_slug, $this->get_addon_filenames(), true );
+
+ if ( ! $target_plugin_file ) {
+ return false;
+ }
+
+ foreach ( $plugin_files as $plugin_file ) {
+ if ( strpos( $plugin_file, $target_plugin_file ) !== false ) {
+ return $plugin_file;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Retrieves the subscription for the given slug.
+ *
+ * @param string $slug The plugin slug to retrieve.
+ *
+ * @return stdClass|false Subscription data when found, false when not found.
+ */
+ public function get_subscription( $slug ) {
+ foreach ( $this->get_subscriptions() as $subscription ) {
+ if ( $subscription->product->slug === $slug ) {
+ return $subscription;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Retrieves a list of (subscription) slugs by the active addons.
+ *
+ * @return array The slugs.
+ */
+ public function get_subscriptions_for_active_addons() {
+ $active_addons = array_keys( $this->get_active_addons() );
+ $subscription_slugs = array_map( [ $this, 'get_slug_by_plugin_file' ], $active_addons );
+ $subscriptions = [];
+ foreach ( $subscription_slugs as $subscription_slug ) {
+ $subscriptions[ $subscription_slug ] = $this->get_subscription( $subscription_slug );
+ }
+
+ return $subscriptions;
+ }
+
+ /**
+ * Retrieves a list of versions for each addon.
+ *
+ * @return array The addon versions.
+ */
+ public function get_installed_addons_versions() {
+ $addon_versions = [];
+ foreach ( $this->get_installed_addons() as $plugin_file => $installed_addon ) {
+ $addon_versions[ $this->get_slug_by_plugin_file( $plugin_file ) ] = $installed_addon['Version'];
+ }
+
+ return $addon_versions;
+ }
+
+ /**
+ * Retrieves the plugin information from the subscriptions.
+ *
+ * @param stdClass|false $data The result object. Default false.
+ * @param string $action The type of information being requested from the Plugin Installation API.
+ * @param stdClass $args Plugin API arguments.
+ *
+ * @return object Extended plugin data.
+ */
+ public function get_plugin_information( $data, $action, $args ) {
+ if ( $action !== 'plugin_information' ) {
+ return $data;
+ }
+
+ if ( ! isset( $args->slug ) ) {
+ return $data;
+ }
+
+ $subscription = $this->get_subscription( $args->slug );
+ if ( ! $subscription ) {
+ return $data;
+ }
+
+ $data = $this->convert_subscription_to_plugin( $subscription, null, true );
+
+ if ( $this->has_subscription_expired( $subscription ) ) {
+ unset( $data->package, $data->download_link );
+ }
+
+ return $data;
+ }
+
+ /**
+ * Retrieves information from MyYoast about which addons are connected to the current site.
+ *
+ * @return stdClass The list of addons activated for this site.
+ */
+ public function get_myyoast_site_information() {
+ if ( $this->site_information === null ) {
+ $this->site_information = $this->get_site_information_transient();
+ }
+
+ if ( $this->site_information ) {
+ return $this->site_information;
+ }
+
+ $this->site_information = $this->request_current_sites();
+ if ( $this->site_information ) {
+ $this->site_information = $this->map_site_information( $this->site_information );
+
+ $this->set_site_information_transient( $this->site_information );
+
+ return $this->site_information;
+ }
+
+ return $this->get_site_information_default();
+ }
+
+ /**
+ * Checks if the subscription for the given slug is valid.
+ *
+ * @param string $slug The plugin slug to retrieve.
+ *
+ * @return bool True when the subscription is valid.
+ */
+ public function has_valid_subscription( $slug ) {
+ $subscription = $this->get_subscription( $slug );
+
+ // An non-existing subscription is never valid.
+ if ( ! $subscription ) {
+ return false;
+ }
+
+ return ! $this->has_subscription_expired( $subscription );
+ }
+
+ /**
+ * Checks if there are addon updates.
+ *
+ * @param stdClass|mixed $data The current data for update_plugins.
+ *
+ * @return stdClass Extended data for update_plugins.
+ */
+ public function check_for_updates( $data ) {
+ global $wp_version;
+
+ if ( empty( $data ) ) {
+ return $data;
+ }
+
+ // We have to figure out if we're safe to upgrade the add-ons, based on what the latest Yoast Free requirements for the WP version is.
+ $yoast_free_data = $this->extract_yoast_data( $data );
+
+ foreach ( $this->get_installed_addons() as $plugin_file => $installed_plugin ) {
+ $subscription_slug = $this->get_slug_by_plugin_file( $plugin_file );
+ $subscription = $this->get_subscription( $subscription_slug );
+
+ if ( ! $subscription ) {
+ continue;
+ }
+
+ $plugin_data = $this->convert_subscription_to_plugin( $subscription, $yoast_free_data, false, $plugin_file );
+
+ // Let's assume for now that it will get added in the 'no_update' key that we'll return to the WP API.
+ $is_no_update = true;
+
+ // If the add-on's version is the latest, we have to do no further checks.
+ if ( version_compare( $installed_plugin['Version'], $plugin_data->new_version, '<' ) ) {
+ // If we haven't retrieved the Yoast Free requirements for the WP version yet, do nothing. The next run will probably get us that information.
+ if ( $plugin_data->requires === null ) {
+ continue;
+ }
+
+ if ( version_compare( $plugin_data->requires, $wp_version, '<=' ) ) {
+ // The add-on has an available update *and* the Yoast Free requirements for the WP version are also met, so go ahead and show the upgrade info to the user.
+ $is_no_update = false;
+ $data->response[ $plugin_file ] = $plugin_data;
+
+ if ( $this->has_subscription_expired( $subscription ) ) {
+ unset( $data->response[ $plugin_file ]->package, $data->response[ $plugin_file ]->download_link );
+ }
+ }
+ }
+
+ if ( $is_no_update ) {
+ // Still convert subscription when no updates is available.
+ $data->no_update[ $plugin_file ] = $plugin_data;
+
+ if ( $this->has_subscription_expired( $subscription ) ) {
+ unset( $data->no_update[ $plugin_file ]->package, $data->no_update[ $plugin_file ]->download_link );
+ }
+ }
+ }
+
+ return $data;
+ }
+
+ /**
+ * Extracts Yoast SEO Free's data from the wp.org API response.
+ *
+ * @param object $data The wp.org API response.
+ *
+ * @return object Yoast Free's data from wp.org.
+ */
+ protected function extract_yoast_data( $data ) {
+ if ( isset( $data->response[ WPSEO_BASENAME ] ) ) {
+ return $data->response[ WPSEO_BASENAME ];
+ }
+
+ if ( isset( $data->no_update[ WPSEO_BASENAME ] ) ) {
+ return $data->no_update[ WPSEO_BASENAME ];
+ }
+
+ return (object) [];
+ }
+
+ /**
+ * If the plugin is lacking an active subscription, throw a warning.
+ *
+ * @param array $plugin_data The data for the plugin in this row.
+ *
+ * @return void
+ */
+ public function expired_subscription_warning( $plugin_data ) {
+ $subscription = $this->get_subscription( $plugin_data['slug'] );
+ if ( $subscription && $this->has_subscription_expired( $subscription ) ) {
+ $addon_link = ( isset( $this->addon_details[ $plugin_data['slug'] ] ) ) ? $this->addon_details[ $plugin_data['slug'] ]['short_link_renewal'] : $this->addon_details[ self::PREMIUM_SLUG ]['short_link_renewal'];
+
+ $sale_copy = '';
+ if ( YoastSEO()->classes->get( Promotion_Manager::class )->is( 'black-friday-promotion' ) ) {
+ $sale_copy = sprintf(
+ /* translators: %1$s and %2$s are a opening and closing tag. */
+ esc_html__( '%1$s30%% OFF - Black Friday %2$s', 'wordpress-seo' ),
+ '',
+ ''
+ );
+ }
+ echo '
';
+ echo ' '
+ . sprintf(
+ /* translators: %1$s is the plugin name, %2$s and %3$s are a link. */
+ esc_html__( 'Your %1$s plugin cannot be updated as your subscription has expired. %2$sRenew your product subscription%3$s to restore updates and full feature access.', 'wordpress-seo' ),
+ esc_html( $plugin_data['name'] ),
+ '',
+ ''
+ )
+ // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Output is escaped above.
+ . $sale_copy
+ . '';
+ }
+ }
+
+ /**
+ * Checks if there are any installed addons.
+ *
+ * @return bool True when there are installed Yoast addons.
+ */
+ public function has_installed_addons() {
+ $installed_addons = $this->get_installed_addons();
+
+ return ! empty( $installed_addons );
+ }
+
+ /**
+ * Checks if the plugin is installed and activated in WordPress.
+ *
+ * @param string $slug The class' slug.
+ *
+ * @return bool True when installed and activated.
+ */
+ public function is_installed( $slug ) {
+ $slug_to_class_map = [
+ static::PREMIUM_SLUG => 'WPSEO_Premium',
+ static::NEWS_SLUG => 'WPSEO_News',
+ static::WOOCOMMERCE_SLUG => 'Yoast_WooCommerce_SEO',
+ static::VIDEO_SLUG => 'WPSEO_Video_Sitemap',
+ static::LOCAL_SLUG => 'WPSEO_Local_Core',
+ ];
+
+ if ( ! isset( $slug_to_class_map[ $slug ] ) ) {
+ return false;
+ }
+
+ return class_exists( $slug_to_class_map[ $slug ] );
+ }
+
+ /**
+ * Validates the addons and show a notice for the ones that are invalid.
+ *
+ * @return void
+ */
+ public function validate_addons() {
+ $notification_center = Yoast_Notification_Center::get();
+
+ if ( $notification_center === null ) {
+ return;
+ }
+
+ foreach ( $this->addon_details as $slug => $addon_info ) {
+ $notification = $this->create_notification( $addon_info['name'], $addon_info['short_link_activation'] );
+
+ // Add a notification when the installed plugin isn't activated in My Yoast.
+ if ( $this->is_installed( $slug ) && ! $this->has_valid_subscription( $slug ) ) {
+ $notification_center->add_notification( $notification );
+
+ continue;
+ }
+
+ $notification_center->remove_notification( $notification );
+ }
+ }
+
+ /**
+ * Checks if the user has any active addons.
+ *
+ * @return bool Whether there are active addons.
+ */
+ public function has_active_addons() {
+ $active_addons = $this->get_active_addons();
+
+ return ! empty( $active_addons );
+ }
+
+ /**
+ * Removes the site information transients.
+ *
+ * @codeCoverageIgnore
+ *
+ * @return void
+ */
+ public function remove_site_information_transients() {
+ delete_transient( self::SITE_INFORMATION_TRANSIENT );
+ delete_transient( self::SITE_INFORMATION_TRANSIENT_QUICK );
+ }
+
+ /**
+ * Creates an instance of Yoast_Notification.
+ *
+ * @param string $product_name The product to create the notification for.
+ * @param string $short_link The short link for the addon notification.
+ *
+ * @return Yoast_Notification The created notification.
+ */
+ protected function create_notification( $product_name, $short_link ) {
+ $notification_options = [
+ 'type' => Yoast_Notification::ERROR,
+ 'id' => 'wpseo-dismiss-' . sanitize_title_with_dashes( $product_name, null, 'save' ),
+ 'capabilities' => 'wpseo_manage_options',
+ ];
+
+ return new Yoast_Notification(
+ sprintf(
+ /* translators: %1$s expands to a strong tag, %2$s expands to the product name, %3$s expands to a closing strong tag, %4$s expands to an a tag. %5$s expands to MyYoast, %6$s expands to a closing a tag, %7$s expands to the product name */
+ __( '%1$s %2$s isn\'t working as expected %3$s and you are not receiving updates or support! Make sure to %4$s activate your product subscription in %5$s%6$s to unlock all the features of %7$s.', 'wordpress-seo' ),
+ '',
+ $product_name,
+ '',
+ '',
+ 'MyYoast',
+ '',
+ $product_name
+ ),
+ $notification_options
+ );
+ }
+
+ /**
+ * Checks whether a plugin expiry date has been passed.
+ *
+ * @param stdClass $subscription Plugin subscription.
+ *
+ * @return bool Has the plugin expired.
+ */
+ protected function has_subscription_expired( $subscription ) {
+ return ( strtotime( $subscription->expiry_date ) - time() ) < 0;
+ }
+
+ /**
+ * Converts a subscription to plugin based format.
+ *
+ * @param stdClass $subscription The subscription to convert.
+ * @param stdClass|null $yoast_free_data The Yoast Free's data.
+ * @param bool $plugin_info Whether we're in the plugin information modal.
+ * @param string $plugin_file The plugin filename.
+ *
+ * @return stdClass The converted subscription.
+ */
+ protected function convert_subscription_to_plugin( $subscription, $yoast_free_data = null, $plugin_info = false, $plugin_file = '' ) {
+ $changelog = '';
+ if ( isset( $subscription->product->changelog ) ) {
+ // We need to replace h2's and h3's with h4's because the styling expects that.
+ $changelog = str_replace( 'product->changelog ) );
+ $changelog = str_replace( ' ( $plugin_info ) ? YOAST_SEO_WP_REQUIRED : null,
+ ];
+
+ return (object) [
+ 'new_version' => ( $subscription->product->version ?? '' ),
+ 'name' => $subscription->product->name,
+ 'slug' => $subscription->product->slug,
+ 'plugin' => $plugin_file,
+ 'url' => $subscription->product->store_url,
+ 'last_update' => $subscription->product->last_updated,
+ 'homepage' => $subscription->product->store_url,
+ 'download_link' => $subscription->product->download,
+ 'package' => $subscription->product->download,
+ 'sections' => [
+ 'changelog' => $changelog,
+ 'support' => $this->get_support_section(),
+ ],
+ 'icons' => [
+ '2x' => $this->get_icon( $subscription->product->slug ),
+ ],
+ 'update_supported' => true,
+ 'banners' => $this->get_banners( $subscription->product->slug ),
+ // If we have extracted Yoast Free's data before, use that. If not, resort to the defaults.
+ 'tested' => YOAST_SEO_WP_TESTED,
+ 'requires' => ( $yoast_free_data->requires ?? $defaults['requires'] ),
+ 'requires_php' => YOAST_SEO_PHP_REQUIRED,
+ ];
+ }
+
+ /**
+ * Returns the plugin's icon URL.
+ *
+ * @param string $slug The plugin slug.
+ *
+ * @return string The icon URL for this plugin.
+ */
+ protected function get_icon( $slug ) {
+ switch ( $slug ) {
+ case self::LOCAL_SLUG:
+ return 'https://yoa.st/local-seo-icon';
+ case self::NEWS_SLUG:
+ return 'https://yoa.st/news-seo-icon';
+ case self::PREMIUM_SLUG:
+ return 'https://yoa.st/yoast-seo-icon';
+ case self::VIDEO_SLUG:
+ return 'https://yoa.st/video-seo-icon';
+ case self::WOOCOMMERCE_SLUG:
+ return 'https://yoa.st/woo-seo-icon';
+ }
+ }
+
+ /**
+ * Return an array of plugin banner URLs.
+ *
+ * @param string $slug The plugin slug.
+ *
+ * @return string[]
+ */
+ protected function get_banners( $slug ) {
+ switch ( $slug ) {
+ case self::LOCAL_SLUG:
+ return [
+ 'high' => 'https://yoa.st/yoast-seo-banner-local',
+ 'low' => 'https://yoa.st/yoast-seo-banner-low-local',
+ ];
+ case self::NEWS_SLUG:
+ return [
+ 'high' => 'https://yoa.st/yoast-seo-banner-news',
+ 'low' => 'https://yoa.st/yoast-seo-banner-low-news',
+ ];
+ case self::PREMIUM_SLUG:
+ return [
+ 'high' => 'https://yoa.st/yoast-seo-banner-premium',
+ 'low' => 'https://yoa.st/yoast-seo-banner-low-premium',
+ ];
+ case self::VIDEO_SLUG:
+ return [
+ 'high' => 'https://yoa.st/yoast-seo-banner-video',
+ 'low' => 'https://yoa.st/yoast-seo-banner-low-video',
+ ];
+ case self::WOOCOMMERCE_SLUG:
+ return [
+ 'high' => 'https://yoa.st/yoast-seo-banner-woo',
+ 'low' => 'https://yoa.st/yoast-seo-banner-low-woo',
+ ];
+ }
+ }
+
+ /**
+ * Checks if the given plugin_file belongs to a Yoast addon.
+ *
+ * @param string $plugin_file Path to the plugin.
+ *
+ * @return bool True when plugin file is for a Yoast addon.
+ */
+ protected function is_yoast_addon( $plugin_file ) {
+ return $this->get_slug_by_plugin_file( $plugin_file ) !== '';
+ }
+
+ /**
+ * Retrieves the addon slug by given plugin file path.
+ *
+ * @param string $plugin_file The file path to the plugin.
+ *
+ * @return string The slug when found or empty string when not.
+ */
+ protected function get_slug_by_plugin_file( $plugin_file ) {
+ $addons = self::$addons;
+
+ // Yoast SEO Free isn't an addon, but we needed it in Premium to fetch translations.
+ if ( YoastSEO()->helpers->product->is_premium() ) {
+ $addons['wp-seo.php'] = self::FREE_SLUG;
+ }
+
+ foreach ( $addons as $addon => $addon_slug ) {
+ if ( strpos( $plugin_file, $addon ) !== false ) {
+ return $addon_slug;
+ }
+ }
+
+ return '';
+ }
+
+ /**
+ * Retrieves the installed Yoast addons.
+ *
+ * @return array The installed plugins.
+ */
+ protected function get_installed_addons() {
+ return array_filter( $this->get_plugins(), [ $this, 'is_yoast_addon' ], ARRAY_FILTER_USE_KEY );
+ }
+
+ /**
+ * Retrieves a list of active addons.
+ *
+ * @return array The active addons.
+ */
+ protected function get_active_addons() {
+ return array_filter( $this->get_installed_addons(), [ $this, 'is_plugin_active' ], ARRAY_FILTER_USE_KEY );
+ }
+
+ /**
+ * Retrieves the current sites from the API.
+ *
+ * @codeCoverageIgnore
+ *
+ * @return bool|stdClass Object when request is successful. False if not.
+ */
+ protected function request_current_sites() {
+ $api_request = new WPSEO_MyYoast_Api_Request( 'sites/current' );
+ if ( $api_request->fire() ) {
+ return $api_request->get_response();
+ }
+
+ return $this->get_site_information_default();
+ }
+
+ /**
+ * Retrieves the transient value with the site information.
+ *
+ * @codeCoverageIgnore
+ *
+ * @return stdClass|false The transient value.
+ */
+ protected function get_site_information_transient() {
+ global $pagenow;
+
+ // Force re-check on license & dashboard pages.
+ $current_page = null;
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
+ if ( isset( $_GET['page'] ) && is_string( $_GET['page'] ) ) {
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: We are not processing form information, We are only strictly comparing and thus no need to sanitize.
+ $current_page = wp_unslash( $_GET['page'] );
+ }
+
+ // Check whether the licenses are valid or whether we need to show notifications.
+ $quick = ( $current_page === Plans_Page_Integration::PAGE || $current_page === General_Page_Integration::PAGE );
+
+ // Also do a fresh request on Plugins & Core Update pages.
+ $quick = $quick || $pagenow === 'plugins.php';
+ $quick = $quick || $pagenow === 'update-core.php';
+
+ if ( $quick ) {
+ return get_transient( self::SITE_INFORMATION_TRANSIENT_QUICK );
+ }
+
+ return get_transient( self::SITE_INFORMATION_TRANSIENT );
+ }
+
+ /**
+ * Sets the site information transient.
+ *
+ * @codeCoverageIgnore
+ *
+ * @param stdClass $site_information The site information to save.
+ *
+ * @return void
+ */
+ protected function set_site_information_transient( $site_information ) {
+ set_transient( self::SITE_INFORMATION_TRANSIENT, $site_information, DAY_IN_SECONDS );
+ set_transient( self::SITE_INFORMATION_TRANSIENT_QUICK, $site_information, 60 );
+ }
+
+ /**
+ * Retrieves all installed WordPress plugins.
+ *
+ * @codeCoverageIgnore
+ *
+ * @return array The plugins.
+ */
+ protected function get_plugins() {
+ if ( ! function_exists( 'get_plugins' ) ) {
+ require_once ABSPATH . 'wp-admin/includes/plugin.php';
+ }
+
+ return get_plugins();
+ }
+
+ /**
+ * Checks if the given plugin file belongs to an active plugin.
+ *
+ * @codeCoverageIgnore
+ *
+ * @param string $plugin_file The file path to the plugin.
+ *
+ * @return bool True when plugin is active.
+ */
+ protected function is_plugin_active( $plugin_file ) {
+ return is_plugin_active( $plugin_file );
+ }
+
+ /**
+ * Returns an object with no subscriptions.
+ *
+ * @codeCoverageIgnore
+ *
+ * @return stdClass Site information.
+ */
+ protected function get_site_information_default() {
+ return (object) [
+ 'url' => WPSEO_Utils::get_home_url(),
+ 'subscriptions' => [],
+ ];
+ }
+
+ /**
+ * Maps the plugin API response.
+ *
+ * @param object $site_information Site information as received from the API.
+ *
+ * @return stdClass Mapped site information.
+ */
+ protected function map_site_information( $site_information ) {
+ return (object) [
+ 'url' => $site_information->url,
+ 'subscriptions' => array_map( [ $this, 'map_subscription' ], $site_information->subscriptions ),
+ ];
+ }
+
+ /**
+ * Maps a plugin subscription.
+ *
+ * @param object $subscription Subscription information as received from the API.
+ *
+ * @return stdClass Mapped subscription.
+ */
+ protected function map_subscription( $subscription ) {
+ // phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- Not our properties.
+ return (object) [
+ 'renewal_url' => $subscription->renewalUrl,
+ 'expiry_date' => $subscription->expiryDate,
+ 'product' => (object) [
+ 'version' => $subscription->product->version,
+ 'name' => $subscription->product->name,
+ 'slug' => $subscription->product->slug,
+ 'last_updated' => $subscription->product->lastUpdated,
+ 'store_url' => $subscription->product->storeUrl,
+ // Ternary operator is necessary because download can be undefined.
+ 'download' => ( $subscription->product->download ?? null ),
+ 'changelog' => $subscription->product->changelog,
+ ],
+ ];
+ // phpcs:enable
+ }
+
+ /**
+ * Retrieves the site information.
+ *
+ * @return stdClass The site information.
+ */
+ private function get_site_information() {
+ if ( ! $this->has_installed_addons() ) {
+ return $this->get_site_information_default();
+ }
+
+ return $this->get_myyoast_site_information();
+ }
+
+ /**
+ * Retrieves the contents for the support section.
+ *
+ * @return string The support section content.
+ */
+ protected function get_support_section() {
+ return '
' . __( 'Need support?', 'wordpress-seo' ) . '
'
+ . '
'
+ /* translators: 1: expands to that refers to the help page, 2: closing tag. */
+ . sprintf( __( 'You can probably find an answer to your question in our %1$shelp center%2$s.', 'wordpress-seo' ), '', '' )
+ . ' '
+ /* translators: %s expands to a mailto support link. */
+ . sprintf( __( 'If you still need support and have an active subscription for this product, please email %s.', 'wordpress-seo' ), 'support@yoast.com' )
+ . '
';
+ }
+}
diff --git a/wp-content/plugins/wordpress-seo/inc/class-my-yoast-api-request.php b/wp-content/plugins/wordpress-seo/inc/class-my-yoast-api-request.php
new file mode 100755
index 00000000..752c22be
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/inc/class-my-yoast-api-request.php
@@ -0,0 +1,224 @@
+ 'GET',
+ 'timeout' => 5,
+ 'headers' => [
+ 'Accept-Encoding' => '*',
+ 'Expect' => '',
+ ],
+ ];
+
+ /**
+ * Contains the fetched response.
+ *
+ * @var stdClass
+ */
+ protected $response;
+
+ /**
+ * Contains the error message when request went wrong.
+ *
+ * @var string
+ */
+ protected $error_message = '';
+
+ /**
+ * Constructor.
+ *
+ * @codeCoverageIgnore
+ *
+ * @param string $url The request url.
+ * @param array $args The request arguments.
+ */
+ public function __construct( $url, array $args = [] ) {
+ $this->url = 'https://my.yoast.com/api/' . $url;
+ $this->args = wp_parse_args( $args, $this->args );
+ }
+
+ /**
+ * Fires the request.
+ *
+ * @return bool True when request is successful.
+ */
+ public function fire() {
+ try {
+ $response = $this->do_request( $this->url, $this->args );
+ $response = $this->decode_response( $response );
+ $this->response = $this->validate_response( $response );
+ return true;
+ } catch ( WPSEO_MyYoast_Bad_Request_Exception $bad_request_exception ) {
+ $this->error_message = $bad_request_exception->getMessage();
+
+ return false;
+ }
+ }
+
+ /**
+ * Retrieves the error message.
+ *
+ * @return string The set error message.
+ */
+ public function get_error_message() {
+ return $this->error_message;
+ }
+
+ /**
+ * Retrieves the response.
+ *
+ * @return stdClass The response object.
+ */
+ public function get_response() {
+ return $this->response;
+ }
+
+ /**
+ * Performs the request using WordPress internals.
+ *
+ * @codeCoverageIgnore
+ *
+ * @param string $url The request URL.
+ * @param array $request_arguments The request arguments.
+ *
+ * @return string The retrieved body.
+ * @throws WPSEO_MyYoast_Bad_Request_Exception When request is invalid.
+ */
+ protected function do_request( $url, $request_arguments ) {
+ $request_arguments = $this->enrich_request_arguments( $request_arguments );
+ $response = wp_remote_request( $url, $request_arguments );
+
+ if ( is_wp_error( $response ) ) {
+ throw new WPSEO_MyYoast_Bad_Request_Exception( $response->get_error_message() );
+ }
+
+ $response_code = wp_remote_retrieve_response_code( $response );
+ $response_message = wp_remote_retrieve_response_message( $response );
+
+ // Do nothing, response code is okay.
+ if ( $response_code === 200 ) {
+ return wp_remote_retrieve_body( $response );
+ }
+
+ throw new WPSEO_MyYoast_Bad_Request_Exception( esc_html( $response_message ), (int) $response_code );
+ }
+
+ /**
+ * Decodes the JSON encoded response.
+ *
+ * @param string $response The response to decode.
+ *
+ * @return stdClass The json decoded response.
+ * @throws WPSEO_MyYoast_Invalid_JSON_Exception When decoded string is not a JSON object.
+ */
+ protected function decode_response( $response ) {
+ $response = json_decode( $response );
+
+ if ( ! is_object( $response ) ) {
+ throw new WPSEO_MyYoast_Invalid_JSON_Exception(
+ esc_html__( 'No JSON object was returned.', 'wordpress-seo' )
+ );
+ }
+
+ return $response;
+ }
+
+ /**
+ * Validates that all the needed fields are in de decoded response.
+ *
+ * @param stdClass $response The response to validate.
+ *
+ * @return stdClass The json decoded response.
+ * @throws WPSEO_MyYoast_Invalid_JSON_Exception When not all needed fields are found.
+ */
+ private function validate_response( $response ) {
+ if ( isset( $response->url, $response->subscriptions ) && is_array( $response->subscriptions ) ) {
+ return $response;
+ }
+
+ throw new WPSEO_MyYoast_Invalid_JSON_Exception(
+ esc_html__( 'Not all needed fields are present.', 'wordpress-seo' )
+ );
+ }
+
+ /**
+ * Checks if MyYoast tokens are allowed and adds the token to the request body.
+ *
+ * When tokens are disallowed it will add the url to the request body.
+ *
+ * @param array $request_arguments The arguments to enrich.
+ *
+ * @return array The enriched arguments.
+ */
+ protected function enrich_request_arguments( array $request_arguments ) {
+ $request_arguments = wp_parse_args( $request_arguments, [ 'headers' => [] ] );
+ $addon_version_headers = $this->get_installed_addon_versions();
+
+ foreach ( $addon_version_headers as $addon => $version ) {
+ $request_arguments['headers'][ $addon . '-version' ] = $version;
+ }
+
+ $request_body = $this->get_request_body();
+ if ( $request_body !== [] ) {
+ $request_arguments['body'] = $request_body;
+ }
+
+ return $request_arguments;
+ }
+
+ /**
+ * Retrieves the request body based on URL or access token support.
+ *
+ * @codeCoverageIgnore
+ *
+ * @return array The request body.
+ */
+ public function get_request_body() {
+ return [ 'url' => WPSEO_Utils::get_home_url() ];
+ }
+
+ /**
+ * Wraps the get current user id function.
+ *
+ * @codeCoverageIgnore
+ *
+ * @return int The user id.
+ */
+ protected function get_current_user_id() {
+ return get_current_user_id();
+ }
+
+ /**
+ * Retrieves the installed addons as http headers.
+ *
+ * @codeCoverageIgnore
+ *
+ * @return array The installed addon versions.
+ */
+ protected function get_installed_addon_versions() {
+ $addon_manager = new WPSEO_Addon_Manager();
+
+ return $addon_manager->get_installed_addons_versions();
+ }
+}
diff --git a/wp-content/plugins/wordpress-seo/inc/class-post-type.php b/wp-content/plugins/wordpress-seo/inc/class-post-type.php
new file mode 100755
index 00000000..944e1f66
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/inc/class-post-type.php
@@ -0,0 +1,133 @@
+helpers->post_type->get_accessible_post_types();
+ }
+
+ /**
+ * Returns whether the passed post type is considered accessible.
+ *
+ * @param string $post_type The post type to check.
+ *
+ * @return bool Whether or not the post type is considered accessible.
+ */
+ public static function is_post_type_accessible( $post_type ) {
+ return in_array( $post_type, self::get_accessible_post_types(), true );
+ }
+
+ /**
+ * Checks if the request post type is public and indexable.
+ *
+ * @param string $post_type_name The name of the post type to lookup.
+ *
+ * @return bool True when post type is set to index.
+ */
+ public static function is_post_type_indexable( $post_type_name ) {
+ return YoastSEO()->helpers->post_type->is_indexable( $post_type_name );
+ }
+
+ /**
+ * Filters the attachment post type from an array with post_types.
+ *
+ * @param array $post_types The array to filter the attachment post type from.
+ *
+ * @return array The filtered array.
+ */
+ public static function filter_attachment_post_type( array $post_types ) {
+ if ( WPSEO_Options::get( 'disable-attachment' ) === true ) {
+ unset( $post_types['attachment'] );
+ }
+
+ return $post_types;
+ }
+
+ /**
+ * Checks if the post type is enabled in the REST API.
+ *
+ * @param string $post_type The post type to check.
+ *
+ * @return bool Whether or not the post type is available in the REST API.
+ */
+ public static function is_rest_enabled( $post_type ) {
+ $post_type_object = get_post_type_object( $post_type );
+
+ if ( $post_type_object === null ) {
+ return false;
+ }
+
+ return $post_type_object->show_in_rest === true;
+ }
+
+ /**
+ * Checks if the current post type has an archive.
+ *
+ * Context: The has_archive value can be a string or a boolean. In most case it will be a boolean,
+ * but it can be defined as a string. When it is a string the archive_slug will be overwritten to
+ * define another endpoint.
+ *
+ * @param WP_Post_Type $post_type The post type object.
+ *
+ * @return bool True whether the post type has an archive.
+ */
+ public static function has_archive( $post_type ) {
+ return YoastSEO()->helpers->post_type->has_archive( $post_type );
+ }
+
+ /**
+ * Checks if the Yoast Metabox has been enabled for the post type.
+ *
+ * @param string $post_type The post type name.
+ *
+ * @return bool True whether the metabox is enabled.
+ */
+ public static function has_metabox_enabled( $post_type ) {
+ return WPSEO_Options::get( 'display-metabox-pt-' . $post_type, false );
+ }
+
+ /* ********************* DEPRECATED METHODS ********************* */
+
+ /**
+ * Removes the notification related to the post types which have been made public.
+ *
+ * @deprecated 20.10
+ * @codeCoverageIgnore
+ *
+ * @return void
+ */
+ public static function remove_post_types_made_public_notification() {
+ _deprecated_function( __METHOD__, 'Yoast SEO 20.10', 'Content_Type_Visibility_Dismiss_Notifications::dismiss_notifications' );
+ $notification_center = Yoast_Notification_Center::get();
+ $notification_center->remove_notification_by_id( 'post-types-made-public' );
+ }
+
+ /**
+ * Removes the notification related to the taxonomies which have been made public.
+ *
+ * @deprecated 20.10
+ * @codeCoverageIgnore
+ *
+ * @return void
+ */
+ public static function remove_taxonomies_made_public_notification() {
+ _deprecated_function( __METHOD__, 'Yoast SEO 20.10', 'Content_Type_Visibility_Dismiss_Notifications::dismiss_notifications' );
+ $notification_center = Yoast_Notification_Center::get();
+ $notification_center->remove_notification_by_id( 'taxonomies-made-public' );
+ }
+}
diff --git a/wp-content/plugins/wordpress-seo/inc/class-rewrite.php b/wp-content/plugins/wordpress-seo/inc/class-rewrite.php
new file mode 100755
index 00000000..9622ada0
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/inc/class-rewrite.php
@@ -0,0 +1,256 @@
+ $query_vars Main query vars to filter.
+ *
+ * @return array The query vars.
+ */
+ public function query_vars( $query_vars ) {
+ if ( WPSEO_Options::get( 'stripcategorybase' ) === true ) {
+ $query_vars[] = 'wpseo_category_redirect';
+ }
+
+ return $query_vars;
+ }
+
+ /**
+ * Checks whether the redirect needs to be created.
+ *
+ * @param array $query_vars Query vars to check for existence of redirect var.
+ *
+ * @return array The query vars.
+ */
+ public function request( $query_vars ) {
+ if ( WPSEO_Options::get( 'stripcategorybase' ) !== true ) {
+ return $query_vars;
+ }
+
+ if ( ! isset( $query_vars['wpseo_category_redirect'] ) ) {
+ return $query_vars;
+ }
+
+ $this->redirect( $query_vars['wpseo_category_redirect'] );
+ return [];
+ }
+
+ /**
+ * Wrapper for the category_rewrite_rules() below, so we can add the $rules param in a BC way.
+ *
+ * @param array $rules Rewrite rules generated for the current permastruct, keyed by their regex pattern.
+ *
+ * @return array The category rewrite rules.
+ */
+ public function category_rewrite_rules_wrapper( $rules ) {
+ if ( WPSEO_Options::get( 'stripcategorybase' ) !== true ) {
+ return $rules;
+ }
+
+ return $this->category_rewrite_rules();
+ }
+
+ /**
+ * This function taken and only slightly adapted from WP No Category Base plugin by Saurabh Gupta.
+ *
+ * @return array The category rewrite rules.
+ */
+ public function category_rewrite_rules() {
+ global $wp_rewrite;
+
+ $category_rewrite = [];
+
+ $taxonomy = get_taxonomy( 'category' );
+ $permalink_structure = get_option( 'permalink_structure' );
+
+ $blog_prefix = '';
+ if ( strpos( $permalink_structure, '/blog/' ) === 0 ) {
+ if ( ( is_multisite() && ! is_subdomain_install() ) || is_main_site() || is_main_network() ) {
+ $blog_prefix = 'blog/';
+ }
+ }
+
+ $categories = get_categories( [ 'hide_empty' => false ] );
+ if ( is_array( $categories ) && $categories !== [] ) {
+ foreach ( $categories as $category ) {
+ $category_nicename = $category->slug;
+ if ( $category->parent === $category->cat_ID ) {
+ // Recursive recursion.
+ $category->parent = 0;
+ }
+ elseif ( $taxonomy->rewrite['hierarchical'] !== false && $category->parent !== 0 ) {
+ $parents = get_category_parents( $category->parent, false, '/', true );
+ if ( ! is_wp_error( $parents ) ) {
+ $category_nicename = $parents . $category_nicename;
+ }
+ unset( $parents );
+ }
+
+ $category_rewrite = $this->add_category_rewrites( $category_rewrite, $category_nicename, $blog_prefix, $wp_rewrite->pagination_base );
+
+ // Adds rules for the uppercase encoded URIs.
+ $category_nicename_filtered = $this->convert_encoded_to_upper( $category_nicename );
+
+ if ( $category_nicename_filtered !== $category_nicename ) {
+ $category_rewrite = $this->add_category_rewrites( $category_rewrite, $category_nicename_filtered, $blog_prefix, $wp_rewrite->pagination_base );
+ }
+ }
+ unset( $categories, $category, $category_nicename, $category_nicename_filtered );
+ }
+
+ // Redirect support from Old Category Base.
+ $old_base = $wp_rewrite->get_category_permastruct();
+ $old_base = str_replace( '%category%', '(.+)', $old_base );
+ $old_base = trim( $old_base, '/' );
+ $category_rewrite[ $old_base . '$' ] = 'index.php?wpseo_category_redirect=$matches[1]';
+
+ return $category_rewrite;
+ }
+
+ /**
+ * Adds required category rewrites rules.
+ *
+ * @param array $rewrites The current set of rules.
+ * @param string $category_name Category nicename.
+ * @param string $blog_prefix Multisite blog prefix.
+ * @param string $pagination_base WP_Query pagination base.
+ *
+ * @return array The added set of rules.
+ */
+ protected function add_category_rewrites( $rewrites, $category_name, $blog_prefix, $pagination_base ) {
+ $rewrite_name = $blog_prefix . '(' . $category_name . ')';
+
+ global $wp_rewrite;
+ $feed_regex = '(' . implode( '|', $wp_rewrite->feeds ) . ')';
+
+ $rewrites[ $rewrite_name . '/(?:feed/)?' . $feed_regex . '/?$' ] = 'index.php?category_name=$matches[1]&feed=$matches[2]';
+ $rewrites[ $rewrite_name . '/' . $pagination_base . '/?([0-9]{1,})/?$' ] = 'index.php?category_name=$matches[1]&paged=$matches[2]';
+ $rewrites[ $rewrite_name . '/?$' ] = 'index.php?category_name=$matches[1]';
+
+ return $rewrites;
+ }
+
+ /**
+ * Walks through category nicename and convert encoded parts
+ * into uppercase using $this->encode_to_upper().
+ *
+ * @param string $name The encoded category URI string.
+ *
+ * @return string The convered URI string.
+ */
+ protected function convert_encoded_to_upper( $name ) {
+ // Checks if name has any encoding in it.
+ if ( strpos( $name, '%' ) === false ) {
+ return $name;
+ }
+
+ $names = explode( '/', $name );
+ $names = array_map( [ $this, 'encode_to_upper' ], $names );
+
+ return implode( '/', $names );
+ }
+
+ /**
+ * Converts the encoded URI string to uppercase.
+ *
+ * @param string $encoded The encoded string.
+ *
+ * @return string The uppercased string.
+ */
+ public function encode_to_upper( $encoded ) {
+ if ( strpos( $encoded, '%' ) === false ) {
+ return $encoded;
+ }
+
+ return strtoupper( $encoded );
+ }
+
+ /**
+ * Redirect the "old" category URL to the new one.
+ *
+ * @codeCoverageIgnore
+ *
+ * @param string $category_redirect The category page to redirect to.
+ * @return void
+ */
+ protected function redirect( $category_redirect ) {
+ $catlink = trailingslashit( get_option( 'home' ) ) . user_trailingslashit( $category_redirect, 'category' );
+
+ wp_safe_redirect( $catlink, 301, 'Yoast SEO' );
+ exit;
+ }
+}
diff --git a/wp-content/plugins/wordpress-seo/inc/class-upgrade-history.php b/wp-content/plugins/wordpress-seo/inc/class-upgrade-history.php
new file mode 100755
index 00000000..227cb672
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/inc/class-upgrade-history.php
@@ -0,0 +1,136 @@
+option_name = $option_name;
+ }
+ }
+
+ /**
+ * Retrieves the content of the history items currently stored.
+ *
+ * @return array> The contents of the history option.
+ */
+ public function get() {
+ $data = get_option( $this->get_option_name(), [] );
+ if ( ! is_array( $data ) ) {
+ return [];
+ }
+
+ return $data;
+ }
+
+ /**
+ * Adds a new history entry in the storage.
+ *
+ * @param string $old_version The version we are upgrading from.
+ * @param string $new_version The version we are upgrading to.
+ * @param array $option_names The options that need to be stored.
+ *
+ * @return void
+ */
+ public function add( $old_version, $new_version, array $option_names ) {
+ $option_data = [];
+ if ( $option_names !== [] ) {
+ $option_data = $this->get_options_data( $option_names );
+ }
+
+ // Retrieve current history.
+ $data = $this->get();
+
+ // Add new entry.
+ $data[ time() ] = [
+ 'options' => $option_data,
+ 'old_version' => $old_version,
+ 'new_version' => $new_version,
+ ];
+
+ // Store the data.
+ $this->set( $data );
+ }
+
+ /**
+ * Retrieves the data for the specified option names from the database.
+ *
+ * @param array $option_names The option names to retrieve.
+ *
+ * @return array> The retrieved data.
+ */
+ protected function get_options_data( array $option_names ) {
+ $wpdb = $this->get_wpdb();
+
+ $results = $wpdb->get_results(
+ $wpdb->prepare(
+ '
+ SELECT %i, %i FROM ' . $wpdb->options . ' WHERE
+ %i IN ( ' . implode( ',', array_fill( 0, count( $option_names ), '%s' ) ) . ' )
+ ',
+ array_merge( [ 'option_value', 'option_name', 'option_name' ], $option_names )
+ ),
+ ARRAY_A
+ );
+
+ $data = [];
+ foreach ( $results as $result ) {
+ $data[ $result['option_name'] ] = maybe_unserialize( $result['option_value'] );
+ }
+
+ return $data;
+ }
+
+ /**
+ * Stores the new history state.
+ *
+ * @param array> $data The data to store.
+ *
+ * @return void
+ */
+ protected function set( array $data ) {
+ // This should not be autoloaded!
+ update_option( $this->get_option_name(), $data, false );
+ }
+
+ /**
+ * Retrieves the WPDB object.
+ *
+ * @return wpdb The WPDB object to use.
+ */
+ protected function get_wpdb() {
+ global $wpdb;
+
+ return $wpdb;
+ }
+
+ /**
+ * Retrieves the option name to store the history in.
+ *
+ * @return string The option name to store the history in.
+ */
+ protected function get_option_name() {
+ return $this->option_name;
+ }
+}
diff --git a/wp-content/plugins/wordpress-seo/inc/class-upgrade.php b/wp-content/plugins/wordpress-seo/inc/class-upgrade.php
new file mode 100755
index 00000000..3ea7da39
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/inc/class-upgrade.php
@@ -0,0 +1,1873 @@
+taxonomy_helper = YoastSEO()->helpers->taxonomy;
+
+ $version = WPSEO_Options::get( 'version' );
+
+ WPSEO_Options::maybe_set_multisite_defaults( false );
+
+ $routines = [
+ '1.5.0' => 'upgrade_15',
+ '2.0' => 'upgrade_20',
+ '2.1' => 'upgrade_21',
+ '2.2' => 'upgrade_22',
+ '2.3' => 'upgrade_23',
+ '3.0' => 'upgrade_30',
+ '3.3' => 'upgrade_33',
+ '3.6' => 'upgrade_36',
+ '4.0' => 'upgrade_40',
+ '4.4' => 'upgrade_44',
+ '4.7' => 'upgrade_47',
+ '4.9' => 'upgrade_49',
+ '5.0' => 'upgrade_50',
+ '5.5' => 'upgrade_55',
+ '6.3' => 'upgrade_63',
+ '7.0-RC0' => 'upgrade_70',
+ '7.1-RC0' => 'upgrade_71',
+ '7.3-RC0' => 'upgrade_73',
+ '7.4-RC0' => 'upgrade_74',
+ '7.5.3' => 'upgrade_753',
+ '7.7-RC0' => 'upgrade_77',
+ '7.7.2-RC0' => 'upgrade_772',
+ '9.0-RC0' => 'upgrade_90',
+ '10.0-RC0' => 'upgrade_100',
+ '11.1-RC0' => 'upgrade_111',
+ // Reset notifications because we removed the AMP Glue plugin notification.
+ '12.1-RC0' => 'clean_all_notifications',
+ '12.3-RC0' => 'upgrade_123',
+ '12.4-RC0' => 'upgrade_124',
+ '12.8-RC0' => 'upgrade_128',
+ '13.2-RC0' => 'upgrade_132',
+ '14.0.3-RC0' => 'upgrade_1403',
+ '14.1-RC0' => 'upgrade_141',
+ '14.2-RC0' => 'upgrade_142',
+ '14.5-RC0' => 'upgrade_145',
+ '14.9-RC0' => 'upgrade_149',
+ '15.1-RC0' => 'upgrade_151',
+ '15.3-RC0' => 'upgrade_153',
+ '15.5-RC0' => 'upgrade_155',
+ '15.7-RC0' => 'upgrade_157',
+ '15.9.1-RC0' => 'upgrade_1591',
+ '16.2-RC0' => 'upgrade_162',
+ '16.5-RC0' => 'upgrade_165',
+ '17.2-RC0' => 'upgrade_172',
+ '17.7.1-RC0' => 'upgrade_1771',
+ '17.9-RC0' => 'upgrade_179',
+ '18.3-RC3' => 'upgrade_183',
+ '18.6-RC0' => 'upgrade_186',
+ '18.9-RC0' => 'upgrade_189',
+ '19.1-RC0' => 'upgrade_191',
+ '19.3-RC0' => 'upgrade_193',
+ '19.6-RC0' => 'upgrade_196',
+ '19.11-RC0' => 'upgrade_1911',
+ '20.2-RC0' => 'upgrade_202',
+ '20.5-RC0' => 'upgrade_205',
+ '20.7-RC0' => 'upgrade_207',
+ '20.8-RC0' => 'upgrade_208',
+ '22.6-RC0' => 'upgrade_226',
+ ];
+
+ array_walk( $routines, [ $this, 'run_upgrade_routine' ], $version );
+ if ( version_compare( $version, '12.5-RC0', '<' ) ) {
+ /*
+ * We have to run this by hook, because otherwise:
+ * - the theme support check isn't available.
+ * - the notification center notifications are not filled yet.
+ */
+ add_action( 'init', [ $this, 'upgrade_125' ] );
+ }
+
+ /**
+ * Filter: 'wpseo_run_upgrade' - Runs the upgrade hook which are dependent on Yoast SEO.
+ *
+ * @param string $version The current version of Yoast SEO
+ */
+ do_action( 'wpseo_run_upgrade', $version );
+
+ $this->finish_up( $version );
+ }
+
+ /**
+ * Runs the upgrade routine.
+ *
+ * @param string $routine The method to call.
+ * @param string $version The new version.
+ * @param string $current_version The current set version.
+ *
+ * @return void
+ */
+ protected function run_upgrade_routine( $routine, $version, $current_version ) {
+ if ( version_compare( $current_version, $version, '<' ) ) {
+ $this->$routine( $current_version );
+ }
+ }
+
+ /**
+ * Adds a new upgrade history entry.
+ *
+ * @param string $current_version The old version from which we are upgrading.
+ * @param string $new_version The version we are upgrading to.
+ *
+ * @return void
+ */
+ protected function add_upgrade_history( $current_version, $new_version ) {
+ $upgrade_history = new WPSEO_Upgrade_History();
+ $upgrade_history->add( $current_version, $new_version, array_keys( WPSEO_Options::$options ) );
+ }
+
+ /**
+ * Runs the needed cleanup after an update, setting the DB version to latest version, flushing caches etc.
+ *
+ * @param string|null $previous_version The previous version.
+ *
+ * @return void
+ */
+ protected function finish_up( $previous_version = null ) {
+ if ( $previous_version ) {
+ WPSEO_Options::set( 'previous_version', $previous_version, 'wpseo' );
+ // Store timestamp when plugin is updated from a previous version.
+ WPSEO_Options::set( 'last_updated_on', time(), 'wpseo' );
+ }
+ WPSEO_Options::set( 'version', WPSEO_VERSION, 'wpseo' );
+
+ // Just flush rewrites, always, to at least make them work after an upgrade.
+ add_action( 'shutdown', 'flush_rewrite_rules' );
+
+ // Flush the sitemap cache.
+ WPSEO_Sitemaps_Cache::clear();
+
+ // Make sure all our options always exist - issue #1245.
+ WPSEO_Options::ensure_options_exist();
+ }
+
+ /**
+ * Run the Yoast SEO 1.5 upgrade routine.
+ *
+ * @param string $version Current plugin version.
+ *
+ * @return void
+ */
+ private function upgrade_15( $version ) {
+ // Clean up options and meta.
+ WPSEO_Options::clean_up( null, $version );
+ WPSEO_Meta::clean_up();
+ }
+
+ /**
+ * Moves options that moved position in WPSEO 2.0.
+ *
+ * @return void
+ */
+ private function upgrade_20() {
+ /**
+ * Clean up stray wpseo_ms options from the options table, option should only exist in the sitemeta table.
+ * This could have been caused in many version of Yoast SEO, so deleting it for everything below 2.0.
+ */
+ delete_option( 'wpseo_ms' );
+
+ $wpseo = $this->get_option_from_database( 'wpseo' );
+ $this->save_option_setting( $wpseo, 'pinterestverify' );
+
+ // Re-save option to trigger sanitization.
+ $this->cleanup_option_data( 'wpseo' );
+ }
+
+ /**
+ * Detects if taxonomy terms were split and updates the corresponding taxonomy meta's accordingly.
+ *
+ * @return void
+ */
+ private function upgrade_21() {
+ $taxonomies = get_option( 'wpseo_taxonomy_meta', [] );
+
+ if ( ! empty( $taxonomies ) ) {
+ foreach ( $taxonomies as $taxonomy => $tax_metas ) {
+ foreach ( $tax_metas as $term_id => $tax_meta ) {
+ if ( function_exists( 'wp_get_split_term' ) ) {
+ $new_term_id = wp_get_split_term( $term_id, $taxonomy );
+ if ( $new_term_id !== false ) {
+ $taxonomies[ $taxonomy ][ $new_term_id ] = $taxonomies[ $taxonomy ][ $term_id ];
+ unset( $taxonomies[ $taxonomy ][ $term_id ] );
+ }
+ }
+ }
+ }
+
+ update_option( 'wpseo_taxonomy_meta', $taxonomies );
+ }
+ }
+
+ /**
+ * Performs upgrade functions to Yoast SEO 2.2.
+ *
+ * @return void
+ */
+ private function upgrade_22() {
+ // Unschedule our tracking.
+ wp_clear_scheduled_hook( 'yoast_tracking' );
+
+ $this->cleanup_option_data( 'wpseo' );
+ }
+
+ /**
+ * Schedules upgrade function to Yoast SEO 2.3.
+ *
+ * @return void
+ */
+ private function upgrade_23() {
+ add_action( 'wp', [ $this, 'upgrade_23_query' ], 90 );
+ add_action( 'admin_head', [ $this, 'upgrade_23_query' ], 90 );
+ }
+
+ /**
+ * Performs upgrade query to Yoast SEO 2.3.
+ *
+ * @return void
+ */
+ public function upgrade_23_query() {
+ // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- Reason: executed only during the upgrade routine.
+ // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value -- Reason: executed only during the upgrade routine.
+ $wp_query = new WP_Query( 'post_type=any&meta_key=_yoast_wpseo_sitemap-include&meta_value=never&order=ASC' );
+
+ if ( ! empty( $wp_query->posts ) ) {
+ $options = get_option( 'wpseo_xml' );
+
+ $excluded_posts = [];
+ if ( $options['excluded-posts'] !== '' ) {
+ $excluded_posts = explode( ',', $options['excluded-posts'] );
+ }
+
+ foreach ( $wp_query->posts as $post ) {
+ if ( ! in_array( (string) $post->ID, $excluded_posts, true ) ) {
+ $excluded_posts[] = $post->ID;
+ }
+ }
+
+ // Updates the meta value.
+ $options['excluded-posts'] = implode( ',', $excluded_posts );
+
+ // Update the option.
+ update_option( 'wpseo_xml', $options );
+ }
+
+ // Remove the meta fields.
+ delete_post_meta_by_key( '_yoast_wpseo_sitemap-include' );
+ }
+
+ /**
+ * Performs upgrade functions to Yoast SEO 3.0.
+ *
+ * @return void
+ */
+ private function upgrade_30() {
+ // Remove the meta fields for sitemap prio.
+ delete_post_meta_by_key( '_yoast_wpseo_sitemap-prio' );
+ }
+
+ /**
+ * Performs upgrade functions to Yoast SEO 3.3.
+ *
+ * @return void
+ */
+ private function upgrade_33() {
+ // Notification dismissals have been moved to User Meta instead of global option.
+ delete_option( Yoast_Notification_Center::STORAGE_KEY );
+ }
+
+ /**
+ * Performs upgrade functions to Yoast SEO 3.6.
+ *
+ * @return void
+ */
+ protected function upgrade_36() {
+ global $wpdb;
+
+ // Between 3.2 and 3.4 the sitemap options were saved with autoloading enabled.
+ // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: No relevant caches.
+ // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reason: Most performant way.
+ $wpdb->query(
+ $wpdb->prepare(
+ 'DELETE FROM %i WHERE %i LIKE %s AND autoload IN ("on", "yes")',
+ [ $wpdb->options, 'option_name', 'wpseo_sitemap_%' ]
+ )
+ );
+ }
+
+ /**
+ * Removes the about notice when its still in the database.
+ *
+ * @return void
+ */
+ private function upgrade_40() {
+ $center = Yoast_Notification_Center::get();
+ $center->remove_notification_by_id( 'wpseo-dismiss-about' );
+ }
+
+ /**
+ * Moves the content-analysis-active and keyword-analysis-acive options from wpseo-titles to wpseo.
+ *
+ * @return void
+ */
+ private function upgrade_44() {
+ $wpseo_titles = $this->get_option_from_database( 'wpseo_titles' );
+
+ $this->save_option_setting( $wpseo_titles, 'content-analysis-active', 'content_analysis_active' );
+ $this->save_option_setting( $wpseo_titles, 'keyword-analysis-active', 'keyword_analysis_active' );
+
+ // Remove irrelevant content from the option.
+ $this->cleanup_option_data( 'wpseo_titles' );
+ }
+
+ /**
+ * Renames the meta name for the cornerstone content. It was a public meta field and it has to be private.
+ *
+ * @return void
+ */
+ private function upgrade_47() {
+ global $wpdb;
+
+ // The meta key has to be private, so prefix it.
+ // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: No relevant caches.
+ // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reason: Most performant way.
+ $wpdb->query(
+ $wpdb->prepare(
+ 'UPDATE ' . $wpdb->postmeta . ' SET meta_key = %s WHERE meta_key = "yst_is_cornerstone"',
+ WPSEO_Cornerstone_Filter::META_NAME
+ )
+ );
+ }
+
+ /**
+ * Removes the 'wpseo-dismiss-about' notice for every user that still has it.
+ *
+ * @return void
+ */
+ protected function upgrade_49() {
+ global $wpdb;
+
+ /*
+ * Using a filter to remove the notification for the current logged in user. The notification center is
+ * initializing the notifications before the upgrade routine has been executedd and is saving the stored
+ * notifications on shutdown. This causes the returning notification. By adding this filter the shutdown
+ * routine on the notification center will remove the notification.
+ */
+ add_filter( 'yoast_notifications_before_storage', [ $this, 'remove_about_notice' ] );
+
+ $meta_key = $wpdb->get_blog_prefix() . Yoast_Notification_Center::STORAGE_KEY;
+
+ // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: No relevant caches.
+ // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reason: Most performant way.
+ $usermetas = $wpdb->get_results(
+ $wpdb->prepare(
+ '
+ SELECT %i, %i
+ FROM %i
+ WHERE %i = %s AND %i LIKE %s
+ ',
+ [
+ 'user_id',
+ 'meta_value',
+ $wpdb->usermeta,
+ 'meta_key',
+ $meta_key,
+ 'meta_value',
+ '%wpseo-dismiss-about%',
+ ]
+ ),
+ ARRAY_A
+ );
+
+ if ( empty( $usermetas ) ) {
+ return;
+ }
+
+ foreach ( $usermetas as $usermeta ) {
+ $notifications = maybe_unserialize( $usermeta['meta_value'] );
+
+ foreach ( $notifications as $notification_key => $notification ) {
+ if ( ! empty( $notification['options']['id'] ) && $notification['options']['id'] === 'wpseo-dismiss-about' ) {
+ unset( $notifications[ $notification_key ] );
+ }
+ }
+
+ update_user_option( $usermeta['user_id'], Yoast_Notification_Center::STORAGE_KEY, array_values( $notifications ) );
+ }
+ }
+
+ /**
+ * Removes the wpseo-dismiss-about notice from a list of notifications.
+ *
+ * @param Yoast_Notification[] $notifications The notifications to filter.
+ *
+ * @return Yoast_Notification[] The filtered list of notifications. Excluding the wpseo-dismiss-about notification.
+ */
+ public function remove_about_notice( $notifications ) {
+ foreach ( $notifications as $notification_key => $notification ) {
+ if ( $notification->get_id() === 'wpseo-dismiss-about' ) {
+ unset( $notifications[ $notification_key ] );
+ }
+ }
+
+ return $notifications;
+ }
+
+ /**
+ * Adds the yoast_seo_links table to the database.
+ *
+ * @return void
+ */
+ protected function upgrade_50() {
+ global $wpdb;
+
+ // Deletes the post meta value, which might created in the RC.
+ // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: No relevant caches.
+ // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reason: Most performant way.
+ $wpdb->query(
+ $wpdb->prepare(
+ "DELETE FROM %i
+ WHERE %i = '_yst_content_links_processed'",
+ [ $wpdb->postmeta, 'meta_key' ]
+ )
+ );
+ }
+
+ /**
+ * Register new capabilities and roles.
+ *
+ * @return void
+ */
+ private function upgrade_55() {
+ // Register roles.
+ do_action( 'wpseo_register_roles' );
+ WPSEO_Role_Manager_Factory::get()->add();
+
+ // Register capabilities.
+ do_action( 'wpseo_register_capabilities' );
+ WPSEO_Capability_Manager_Factory::get()->add();
+ }
+
+ /**
+ * Removes some no longer used options for noindexing subpages and for meta keywords and its associated templates.
+ *
+ * @return void
+ */
+ private function upgrade_63() {
+ $this->cleanup_option_data( 'wpseo_titles' );
+ }
+
+ /**
+ * Perform the 7.0 upgrade, moves settings around, deletes several options.
+ *
+ * @return void
+ */
+ private function upgrade_70() {
+
+ $wpseo_permalinks = $this->get_option_from_database( 'wpseo_permalinks' );
+ $wpseo_xml = $this->get_option_from_database( 'wpseo_xml' );
+ $wpseo_rss = $this->get_option_from_database( 'wpseo_rss' );
+ $wpseo = $this->get_option_from_database( 'wpseo' );
+ $wpseo_internallinks = $this->get_option_from_database( 'wpseo_internallinks' );
+
+ // Move some permalink settings, then delete the option.
+ $this->save_option_setting( $wpseo_permalinks, 'redirectattachment', 'disable-attachment' );
+ $this->save_option_setting( $wpseo_permalinks, 'stripcategorybase' );
+
+ // Move one XML sitemap setting, then delete the option.
+ $this->save_option_setting( $wpseo_xml, 'enablexmlsitemap', 'enable_xml_sitemap' );
+
+ // Move the RSS settings to the search appearance settings, then delete the RSS option.
+ $this->save_option_setting( $wpseo_rss, 'rssbefore' );
+ $this->save_option_setting( $wpseo_rss, 'rssafter' );
+
+ $this->save_option_setting( $wpseo, 'company_logo' );
+ $this->save_option_setting( $wpseo, 'company_name' );
+ $this->save_option_setting( $wpseo, 'company_or_person' );
+ $this->save_option_setting( $wpseo, 'person_name' );
+
+ // Remove the website name and altername name as we no longer need them.
+ $this->cleanup_option_data( 'wpseo' );
+
+ // All the breadcrumbs settings have moved to the search appearance settings.
+ foreach ( array_keys( $wpseo_internallinks ) as $key ) {
+ $this->save_option_setting( $wpseo_internallinks, $key );
+ }
+
+ // Convert hidden metabox options to display metabox options.
+ $title_options = get_option( 'wpseo_titles' );
+
+ foreach ( $title_options as $key => $value ) {
+ if ( strpos( $key, 'hideeditbox-tax-' ) === 0 ) {
+ $taxonomy = substr( $key, strlen( 'hideeditbox-tax-' ) );
+ WPSEO_Options::set( 'display-metabox-tax-' . $taxonomy, ! $value );
+ continue;
+ }
+
+ if ( strpos( $key, 'hideeditbox-' ) === 0 ) {
+ $post_type = substr( $key, strlen( 'hideeditbox-' ) );
+ WPSEO_Options::set( 'display-metabox-pt-' . $post_type, ! $value );
+ continue;
+ }
+ }
+
+ // Cleanup removed options.
+ delete_option( 'wpseo_xml' );
+ delete_option( 'wpseo_permalinks' );
+ delete_option( 'wpseo_rss' );
+ delete_option( 'wpseo_internallinks' );
+
+ // Remove possibly present plugin conflict notice for plugin that was removed from the list of conflicting plugins.
+ $yoast_plugin_conflict = WPSEO_Plugin_Conflict::get_instance();
+ $yoast_plugin_conflict->clear_error( 'header-footer/plugin.php' );
+
+ // Moves the user meta for excluding from the XML sitemap to a noindex.
+ global $wpdb;
+ // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: No relevant caches.
+ // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reason: Most performant way.
+ $wpdb->query( "UPDATE $wpdb->usermeta SET meta_key = 'wpseo_noindex_author' WHERE meta_key = 'wpseo_excludeauthorsitemap'" );
+ }
+
+ /**
+ * Perform the 7.1 upgrade.
+ *
+ * @return void
+ */
+ private function upgrade_71() {
+ $this->cleanup_option_data( 'wpseo_social' );
+
+ // Move the breadcrumbs setting and invert it.
+ $title_options = $this->get_option_from_database( 'wpseo_titles' );
+
+ if ( array_key_exists( 'breadcrumbs-blog-remove', $title_options ) ) {
+ WPSEO_Options::set( 'breadcrumbs-display-blog-page', ! $title_options['breadcrumbs-blog-remove'] );
+
+ $this->cleanup_option_data( 'wpseo_titles' );
+ }
+ }
+
+ /**
+ * Perform the 7.3 upgrade.
+ *
+ * @return void
+ */
+ private function upgrade_73() {
+ global $wpdb;
+ // We've moved the cornerstone checkbox to our proper namespace.
+ // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: No relevant caches.
+ // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reason: Most performant way.
+ $wpdb->query( "UPDATE $wpdb->postmeta SET meta_key = '_yoast_wpseo_is_cornerstone' WHERE meta_key = '_yst_is_cornerstone'" );
+
+ // Remove the previous Whip dismissed message, as this is a new one regarding PHP 5.2.
+ delete_option( 'whip_dismiss_timestamp' );
+ }
+
+ /**
+ * Performs the 7.4 upgrade.
+ *
+ * @return void
+ */
+ protected function upgrade_74() {
+ $this->remove_sitemap_validators();
+ }
+
+ /**
+ * Performs the 7.5.3 upgrade.
+ *
+ * When upgrading purging media is potentially relevant.
+ *
+ * @return void
+ */
+ private function upgrade_753() {
+ // Only when attachments are not disabled.
+ if ( WPSEO_Options::get( 'disable-attachment' ) === true ) {
+ return;
+ }
+
+ // Only when attachments are not no-indexed.
+ if ( WPSEO_Options::get( 'noindex-attachment' ) === true ) {
+ return;
+ }
+
+ // Set purging relevancy.
+ WPSEO_Options::set( 'is-media-purge-relevant', true );
+ }
+
+ /**
+ * Performs the 7.7 upgrade.
+ *
+ * @return void
+ */
+ private function upgrade_77() {
+ // Remove all OpenGraph content image cache.
+ $this->delete_post_meta( '_yoast_wpseo_post_image_cache' );
+ }
+
+ /**
+ * Performs the 7.7.2 upgrade.
+ *
+ * @return void
+ */
+ private function upgrade_772() {
+ if ( YoastSEO()->helpers->woocommerce->is_active() ) {
+ $this->migrate_woocommerce_archive_setting_to_shop_page();
+ }
+ }
+
+ /**
+ * Performs the 9.0 upgrade.
+ *
+ * @return void
+ */
+ protected function upgrade_90() {
+ global $wpdb;
+
+ // Invalidate all sitemap cache transients.
+ WPSEO_Sitemaps_Cache_Validator::cleanup_database();
+
+ // Removes all scheduled tasks for hitting the sitemap index.
+ wp_clear_scheduled_hook( 'wpseo_hit_sitemap_index' );
+
+ // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: No relevant caches.
+ // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reason: Most performant way.
+ $wpdb->query(
+ $wpdb->prepare(
+ 'DELETE FROM %i
+ WHERE %i LIKE %s',
+ [ $wpdb->options, 'option_name', 'wpseo_sitemap_%' ]
+ )
+ );
+ }
+
+ /**
+ * Performs the 10.0 upgrade.
+ *
+ * @return void
+ */
+ private function upgrade_100() {
+ // Removes recalibration notifications.
+ $this->clean_all_notifications();
+
+ // Removes recalibration options.
+ WPSEO_Options::clean_up( 'wpseo' );
+ delete_option( 'wpseo_recalibration_beta_mailinglist_subscription' );
+ }
+
+ /**
+ * Performs the 11.1 upgrade.
+ *
+ * @return void
+ */
+ private function upgrade_111() {
+ // Set company_or_person to company when it's an invalid value.
+ $company_or_person = WPSEO_Options::get( 'company_or_person', '' );
+
+ if ( ! in_array( $company_or_person, [ 'company', 'person' ], true ) ) {
+ WPSEO_Options::set( 'company_or_person', 'company' );
+ }
+ }
+
+ /**
+ * Performs the 12.3 upgrade.
+ *
+ * Removes the about notice when its still in the database.
+ *
+ * @return void
+ */
+ private function upgrade_123() {
+ $plugins = [
+ 'yoast-seo-premium',
+ 'video-seo-for-wordpress-seo-by-yoast',
+ 'yoast-news-seo',
+ 'local-seo-for-yoast-seo',
+ 'yoast-woocommerce-seo',
+ 'yoast-acf-analysis',
+ ];
+
+ $center = Yoast_Notification_Center::get();
+ foreach ( $plugins as $plugin ) {
+ $center->remove_notification_by_id( 'wpseo-outdated-yoast-seo-plugin-' . $plugin );
+ }
+ }
+
+ /**
+ * Performs the 12.4 upgrade.
+ *
+ * Removes the Google plus defaults from the database.
+ *
+ * @return void
+ */
+ private function upgrade_124() {
+ $this->cleanup_option_data( 'wpseo_social' );
+ }
+
+ /**
+ * Performs the 12.5 upgrade.
+ *
+ * @return void
+ */
+ public function upgrade_125() {
+ // Disables the force rewrite title when the theme supports it through WordPress.
+ if ( WPSEO_Options::get( 'forcerewritetitle', false ) && current_theme_supports( 'title-tag' ) ) {
+ WPSEO_Options::set( 'forcerewritetitle', false );
+ }
+
+ global $wpdb;
+ // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: No relevant caches.
+ // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reason: Most performant way.
+ $wpdb->query(
+ $wpdb->prepare(
+ 'DELETE FROM %i
+ WHERE %i = %s',
+ [ $wpdb->usermeta, 'meta_key', 'wp_yoast_promo_hide_premium_upsell_admin_block' ]
+ )
+ );
+
+ // Removes the WordPress update notification, because it is no longer necessary when WordPress 5.3 is released.
+ $center = Yoast_Notification_Center::get();
+ $center->remove_notification_by_id( 'wpseo-dismiss-wordpress-upgrade' );
+ }
+
+ /**
+ * Performs the 12.8 upgrade.
+ *
+ * @return void
+ */
+ private function upgrade_128() {
+ // Re-save wpseo to make sure bf_banner_2019_dismissed key is gone.
+ $this->cleanup_option_data( 'wpseo' );
+
+ Yoast_Notification_Center::get()->remove_notification_by_id( 'wpseo-dismiss-page_comments-notice' );
+ Yoast_Notification_Center::get()->remove_notification_by_id( 'wpseo-dismiss-wordpress-upgrade' );
+ }
+
+ /**
+ * Performs the 13.2 upgrade.
+ *
+ * @return void
+ */
+ private function upgrade_132() {
+ Yoast_Notification_Center::get()->remove_notification_by_id( 'wpseo-dismiss-tagline-notice' );
+ Yoast_Notification_Center::get()->remove_notification_by_id( 'wpseo-dismiss-permalink-notice' );
+ Yoast_Notification_Center::get()->remove_notification_by_id( 'wpseo-dismiss-onpageorg' );
+
+ // Transfers the onpage option value to the ryte option.
+ $ryte_option = get_option( 'wpseo_ryte' );
+ $onpage_option = get_option( 'wpseo_onpage' );
+ if ( ! $ryte_option && $onpage_option ) {
+ update_option( 'wpseo_ryte', $onpage_option );
+ delete_option( 'wpseo_onpage' );
+ }
+
+ // Changes onpage_indexability to ryte_indexability.
+ $wpseo_option = get_option( 'wpseo' );
+ if ( isset( $wpseo_option['onpage_indexability'] ) && ! isset( $wpseo_option['ryte_indexability'] ) ) {
+ $wpseo_option['ryte_indexability'] = $wpseo_option['onpage_indexability'];
+ unset( $wpseo_option['onpage_indexability'] );
+ update_option( 'wpseo', $wpseo_option );
+ }
+
+ if ( wp_next_scheduled( 'wpseo_ryte_fetch' ) ) {
+ wp_clear_scheduled_hook( 'wpseo_ryte_fetch' );
+ }
+
+ /*
+ * Re-register capabilities to add the new `view_site_health_checks`
+ * capability to the SEO Manager role.
+ */
+ do_action( 'wpseo_register_capabilities' );
+ WPSEO_Capability_Manager_Factory::get()->add();
+ }
+
+ /**
+ * Perform the 14.0.3 upgrade.
+ *
+ * @return void
+ */
+ private function upgrade_1403() {
+ WPSEO_Options::set( 'ignore_indexation_warning', false );
+ }
+
+ /**
+ * Performs the 14.1 upgrade.
+ *
+ * @return void
+ */
+ private function upgrade_141() {
+ /*
+ * These notifications are retrieved from storage on the `init` hook with
+ * priority 1. We need to remove them after they're retrieved.
+ */
+ add_action( 'init', [ $this, 'remove_notifications_for_141' ] );
+ add_action( 'init', [ $this, 'clean_up_private_taxonomies_for_141' ] );
+
+ $this->reset_permalinks_of_attachments_for_141();
+ }
+
+ /**
+ * Performs the 14.2 upgrade.
+ *
+ * Removes the yoast-acf-analysis notice when it's still in the database.
+ *
+ * @return void
+ */
+ private function upgrade_142() {
+ add_action( 'init', [ $this, 'remove_acf_notification_for_142' ] );
+ }
+
+ /**
+ * Performs the 14.5 upgrade.
+ *
+ * @return void
+ */
+ private function upgrade_145() {
+ add_action( 'init', [ $this, 'set_indexation_completed_option_for_145' ] );
+ }
+
+ /**
+ * Performs the 14.9 upgrade.
+ *
+ * @return void
+ */
+ private function upgrade_149() {
+ $version = get_option( 'wpseo_license_server_version', 2 );
+ WPSEO_Options::set( 'license_server_version', $version );
+ delete_option( 'wpseo_license_server_version' );
+ }
+
+ /**
+ * Performs the 15.1 upgrade.
+ *
+ * @return void
+ */
+ private function upgrade_151() {
+ $this->set_home_url_for_151();
+ $this->move_indexables_indexation_reason_for_151();
+
+ add_action( 'init', [ $this, 'set_permalink_structure_option_for_151' ] );
+ add_action( 'init', [ $this, 'store_custom_taxonomy_slugs_for_151' ] );
+ }
+
+ /**
+ * Performs the 15.3 upgrade.
+ *
+ * @return void
+ */
+ private function upgrade_153() {
+ WPSEO_Options::set( 'category_base_url', get_option( 'category_base' ) );
+ WPSEO_Options::set( 'tag_base_url', get_option( 'tag_base' ) );
+
+ // Rename a couple of options.
+ $indexation_started_value = WPSEO_Options::get( 'indexation_started' );
+ WPSEO_Options::set( 'indexing_started', $indexation_started_value );
+
+ $indexables_indexing_completed_value = WPSEO_Options::get( 'indexables_indexation_completed' );
+ WPSEO_Options::set( 'indexables_indexing_completed', $indexables_indexing_completed_value );
+ }
+
+ /**
+ * Performs the 15.5 upgrade.
+ *
+ * @return void
+ */
+ private function upgrade_155() {
+ // Unset the fbadminapp value in the wpseo_social option.
+ $wpseo_social_option = get_option( 'wpseo_social' );
+
+ if ( isset( $wpseo_social_option['fbadminapp'] ) ) {
+ unset( $wpseo_social_option['fbadminapp'] );
+ update_option( 'wpseo_social', $wpseo_social_option );
+ }
+ }
+
+ /**
+ * Performs the 15.7 upgrade.
+ *
+ * @return void
+ */
+ private function upgrade_157() {
+ add_action( 'init', [ $this, 'remove_plugin_updated_notification_for_157' ] );
+ }
+
+ /**
+ * Performs the 15.9.1 upgrade routine.
+ *
+ * @return void
+ */
+ private function upgrade_1591() {
+ $enabled_auto_updates = get_option( 'auto_update_plugins' );
+ $addon_update_watcher = YoastSEO()->classes->get( Addon_Update_Watcher::class );
+ $addon_update_watcher->toggle_auto_updates_for_add_ons( 'auto_update_plugins', [], $enabled_auto_updates );
+ }
+
+ /**
+ * Performs the 16.2 upgrade routine.
+ *
+ * @return void
+ */
+ private function upgrade_162() {
+ $enabled_auto_updates = get_site_option( 'auto_update_plugins' );
+ $addon_update_watcher = YoastSEO()->classes->get( Addon_Update_Watcher::class );
+ $addon_update_watcher->toggle_auto_updates_for_add_ons( 'auto_update_plugins', $enabled_auto_updates, [] );
+ }
+
+ /**
+ * Performs the 16.5 upgrade.
+ *
+ * @return void
+ */
+ private function upgrade_165() {
+ add_action( 'init', [ $this, 'copy_og_settings_from_social_to_titles' ], 99 );
+
+ // Run after the WPSEO_Options::enrich_defaults method which has priority 99.
+ add_action( 'init', [ $this, 'reset_og_settings_to_default_values' ], 100 );
+ }
+
+ /**
+ * Performs the 17.2 upgrade. Cleans out any unnecessary indexables. See $cleanup_integration->get_cleanup_tasks()
+ * to see what will be cleaned out.
+ *
+ * @return void
+ */
+ private function upgrade_172() {
+ wp_unschedule_hook( 'wpseo_cleanup_orphaned_indexables' );
+ wp_unschedule_hook( 'wpseo_cleanup_indexables' );
+
+ if ( ! wp_next_scheduled( Cleanup_Integration::START_HOOK ) ) {
+ wp_schedule_single_event( ( time() + ( MINUTE_IN_SECONDS * 5 ) ), Cleanup_Integration::START_HOOK );
+ }
+ }
+
+ /**
+ * Performs the 17.7.1 upgrade routine.
+ *
+ * @return void
+ */
+ private function upgrade_1771() {
+ $enabled_auto_updates = get_site_option( 'auto_update_plugins' );
+ $addon_update_watcher = YoastSEO()->classes->get( Addon_Update_Watcher::class );
+ $addon_update_watcher->toggle_auto_updates_for_add_ons( 'auto_update_plugins', $enabled_auto_updates, [] );
+ }
+
+ /**
+ * Performs the 17.9 upgrade routine.
+ *
+ * @return void
+ */
+ private function upgrade_179() {
+ WPSEO_Options::set( 'wincher_integration_active', true );
+ }
+
+ /**
+ * Performs the 18.3 upgrade routine.
+ *
+ * @return void
+ */
+ private function upgrade_183() {
+ $this->delete_post_meta( 'yoast-structured-data-blocks-images-cache' );
+ }
+
+ /**
+ * Performs the 18.6 upgrade routine.
+ *
+ * @return void
+ */
+ private function upgrade_186() {
+ if ( is_multisite() ) {
+ WPSEO_Options::set( 'allow_wincher_integration_active', false );
+ }
+ }
+
+ /**
+ * Performs the 18.9 upgrade routine.
+ *
+ * @return void
+ */
+ private function upgrade_189() {
+ // Make old users not get the Installation Success page after upgrading.
+ WPSEO_Options::set( 'should_redirect_after_install_free', false );
+ // We're adding a hardcoded time here, so that in the future we can be able to identify whether the user did see the Installation Success page or not.
+ // If they did, they wouldn't have this hardcoded value in that option, but rather (roughly) the timestamp of the moment they saw it.
+ WPSEO_Options::set( 'activation_redirect_timestamp_free', 1652258756 );
+
+ // Transfer the Social URLs.
+ $other = [];
+ $other[] = WPSEO_Options::get( 'instagram_url' );
+ $other[] = WPSEO_Options::get( 'linkedin_url' );
+ $other[] = WPSEO_Options::get( 'myspace_url' );
+ $other[] = WPSEO_Options::get( 'pinterest_url' );
+ $other[] = WPSEO_Options::get( 'youtube_url' );
+ $other[] = WPSEO_Options::get( 'wikipedia_url' );
+
+ WPSEO_Options::set( 'other_social_urls', array_values( array_unique( array_filter( $other ) ) ) );
+
+ // Transfer the progress of the old Configuration Workout.
+ $workout_data = WPSEO_Options::get( 'workouts_data' );
+ $old_conf_progress = ( $workout_data['configuration']['finishedSteps'] ?? [] );
+
+ if ( in_array( 'optimizeSeoData', $old_conf_progress, true ) && in_array( 'siteRepresentation', $old_conf_progress, true ) ) {
+ // If completed ‘SEO optimization’ and ‘Site representation’ step, we assume the workout was completed.
+ $configuration_finished_steps = [
+ 'siteRepresentation',
+ 'socialProfiles',
+ 'personalPreferences',
+ ];
+ WPSEO_Options::set( 'configuration_finished_steps', $configuration_finished_steps );
+ }
+ }
+
+ /**
+ * Performs the 19.1 upgrade routine.
+ *
+ * @return void
+ */
+ private function upgrade_191() {
+ if ( is_multisite() ) {
+ WPSEO_Options::set( 'allow_remove_feed_post_comments', true );
+ }
+ }
+
+ /**
+ * Performs the 19.3 upgrade routine.
+ *
+ * @return void
+ */
+ private function upgrade_193() {
+ if ( empty( get_option( 'wpseo_premium', [] ) ) ) {
+ WPSEO_Options::set( 'enable_index_now', true );
+ WPSEO_Options::set( 'enable_link_suggestions', true );
+ }
+ }
+
+ /**
+ * Performs the 19.6 upgrade routine.
+ *
+ * @return void
+ */
+ private function upgrade_196() {
+ WPSEO_Options::set( 'ryte_indexability', false );
+ WPSEO_Options::set( 'allow_ryte_indexability', false );
+ wp_clear_scheduled_hook( 'wpseo_ryte_fetch' );
+ }
+
+ /**
+ * Performs the 19.11 upgrade routine.
+ *
+ * @return void
+ */
+ private function upgrade_1911() {
+ add_action( 'shutdown', [ $this, 'remove_indexable_rows_for_non_public_post_types' ] );
+ add_action( 'shutdown', [ $this, 'remove_indexable_rows_for_non_public_taxonomies' ] );
+ $this->deduplicate_unindexed_indexable_rows();
+ $this->remove_indexable_rows_for_disabled_authors_archive();
+ if ( ! wp_next_scheduled( Cleanup_Integration::START_HOOK ) ) {
+ wp_schedule_single_event( ( time() + ( MINUTE_IN_SECONDS * 5 ) ), Cleanup_Integration::START_HOOK );
+ }
+ }
+
+ /**
+ * Performs the 20.2 upgrade routine.
+ *
+ * @return void
+ */
+ private function upgrade_202() {
+ if ( WPSEO_Options::get( 'disable-attachment', true ) ) {
+ $attachment_cleanup_helper = YoastSEO()->helpers->attachment_cleanup;
+
+ $attachment_cleanup_helper->remove_attachment_indexables( true );
+ $attachment_cleanup_helper->clean_attachment_links_from_target_indexable_ids( true );
+ }
+
+ $this->clean_unindexed_indexable_rows_with_no_object_id();
+
+ if ( ! wp_next_scheduled( Cleanup_Integration::START_HOOK ) ) {
+ // This schedules the cleanup routine cron again, since in combination of premium cleans up the prominent words table. We also want to cleanup possible orphaned hierarchies from the above cleanups.
+ wp_schedule_single_event( ( time() + ( MINUTE_IN_SECONDS * 5 ) ), Cleanup_Integration::START_HOOK );
+ }
+ }
+
+ /**
+ * Performs the 20.5 upgrade routine.
+ *
+ * @return void
+ */
+ private function upgrade_205() {
+ if ( ! wp_next_scheduled( Cleanup_Integration::START_HOOK ) ) {
+ wp_schedule_single_event( ( time() + ( MINUTE_IN_SECONDS * 5 ) ), Cleanup_Integration::START_HOOK );
+ }
+ }
+
+ /**
+ * Performs the 20.7 upgrade routine.
+ * Removes the metadata related to the settings page introduction modal for all the users.
+ * Also, schedules another cleanup scheduled action.
+ *
+ * @return void
+ */
+ private function upgrade_207() {
+ add_action( 'shutdown', [ $this, 'delete_user_introduction_meta' ] );
+ }
+
+ /**
+ * Performs the 20.8 upgrade routine.
+ * Schedules another cleanup scheduled action.
+ *
+ * @return void
+ */
+ private function upgrade_208() {
+ if ( ! wp_next_scheduled( Cleanup_Integration::START_HOOK ) ) {
+ wp_schedule_single_event( ( time() + ( MINUTE_IN_SECONDS * 5 ) ), Cleanup_Integration::START_HOOK );
+ }
+ }
+
+ /**
+ * Performs the 22.6 upgrade routine.
+ * Schedules another cleanup scheduled action, but starting from the last cleanup action we just added (if there
+ * aren't any running cleanups already).
+ *
+ * @return void
+ */
+ private function upgrade_226() {
+ if ( get_option( Cleanup_Integration::CURRENT_TASK_OPTION ) === false ) {
+ $cleanup_integration = YoastSEO()->classes->get( Cleanup_Integration::class );
+ $cleanup_integration->start_cron_job( 'clean_selected_empty_usermeta', DAY_IN_SECONDS );
+ }
+ }
+
+ /**
+ * Sets the home_url option for the 15.1 upgrade routine.
+ *
+ * @return void
+ */
+ protected function set_home_url_for_151() {
+ $home_url = WPSEO_Options::get( 'home_url' );
+
+ if ( empty( $home_url ) ) {
+ WPSEO_Options::set( 'home_url', get_home_url() );
+ }
+ }
+
+ /**
+ * Moves the `indexables_indexation_reason` option to the
+ * renamed `indexing_reason` option.
+ *
+ * @return void
+ */
+ protected function move_indexables_indexation_reason_for_151() {
+ $reason = WPSEO_Options::get( 'indexables_indexation_reason', '' );
+ WPSEO_Options::set( 'indexing_reason', $reason );
+ }
+
+ /**
+ * Checks if the indexable indexation is completed.
+ * If so, sets the `indexables_indexation_completed` option to `true`,
+ * else to `false`.
+ *
+ * @return void
+ */
+ public function set_indexation_completed_option_for_145() {
+ WPSEO_Options::set( 'indexables_indexation_completed', YoastSEO()->helpers->indexing->get_limited_filtered_unindexed_count( 1 ) === 0 );
+ }
+
+ /**
+ * Cleans up the private taxonomies from the indexables table for the upgrade routine to 14.1.
+ *
+ * @return void
+ */
+ public function clean_up_private_taxonomies_for_141() {
+ global $wpdb;
+
+ // If migrations haven't been completed successfully the following may give false errors. So suppress them.
+ $show_errors = $wpdb->show_errors;
+ $wpdb->show_errors = false;
+
+ // Clean up indexables of private taxonomies.
+ $private_taxonomies = get_taxonomies( [ 'public' => false ], 'names' );
+
+ if ( empty( $private_taxonomies ) ) {
+ return;
+ }
+
+ $replacements = array_merge(
+ [
+ Model::get_table_name( 'Indexable' ),
+ 'object_type',
+ 'object_sub_type',
+ ],
+ $private_taxonomies
+ );
+
+ // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: No relevant caches.
+ // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reason: Most performant way.
+ $wpdb->query(
+ $wpdb->prepare(
+ "DELETE FROM %i
+ WHERE %i = 'term'
+ AND %i IN ("
+ . implode( ', ', array_fill( 0, count( $private_taxonomies ), '%s' ) )
+ . ')',
+ $replacements
+ )
+ );
+
+ $wpdb->show_errors = $show_errors;
+ }
+
+ /**
+ * Resets the permalinks of attachments to `null` in the indexable table for the upgrade routine to 14.1.
+ *
+ * @return void
+ */
+ private function reset_permalinks_of_attachments_for_141() {
+ global $wpdb;
+
+ // If migrations haven't been completed succesfully the following may give false errors. So suppress them.
+ $show_errors = $wpdb->show_errors;
+ $wpdb->show_errors = false;
+
+ // Reset the permalinks of the attachments in the indexable table.
+ // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: No relevant caches.
+ // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reason: Most performant way.
+ $wpdb->query(
+ $wpdb->prepare(
+ "UPDATE %i SET %i = NULL WHERE %i = 'post' AND %i = 'attachment'",
+ [ Model::get_table_name( 'Indexable' ), 'permalink', 'object_type', 'object_sub_type' ]
+ )
+ );
+
+ $wpdb->show_errors = $show_errors;
+ }
+
+ /**
+ * Removes notifications from the Notification center for the 14.1 upgrade.
+ *
+ * @return void
+ */
+ public function remove_notifications_for_141() {
+ Yoast_Notification_Center::get()->remove_notification_by_id( 'wpseo-dismiss-recalculate' );
+ Yoast_Notification_Center::get()->remove_notification_by_id( 'wpseo-dismiss-blog-public-notice' );
+ Yoast_Notification_Center::get()->remove_notification_by_id( 'wpseo-links-table-not-accessible' );
+ Yoast_Notification_Center::get()->remove_notification_by_id( 'wpseo-post-type-archive-notification' );
+ }
+
+ /**
+ * Removes the wpseo-suggested-plugin-yoast-acf-analysis notification from the Notification center for the 14.2
+ * upgrade.
+ *
+ * @return void
+ */
+ public function remove_acf_notification_for_142() {
+ Yoast_Notification_Center::get()->remove_notification_by_id( 'wpseo-suggested-plugin-yoast-acf-analysis' );
+ }
+
+ /**
+ * Removes the wpseo-plugin-updated notification from the Notification center for the 15.7 upgrade.
+ *
+ * @return void
+ */
+ public function remove_plugin_updated_notification_for_157() {
+ Yoast_Notification_Center::get()->remove_notification_by_id( 'wpseo-plugin-updated' );
+ }
+
+ /**
+ * Removes all notifications saved in the database under 'wp_yoast_notifications'.
+ *
+ * @return void
+ */
+ private function clean_all_notifications() {
+ global $wpdb;
+ delete_metadata( 'user', 0, $wpdb->get_blog_prefix() . Yoast_Notification_Center::STORAGE_KEY, '', true );
+ }
+
+ /**
+ * Removes the post meta fields for a given meta key.
+ *
+ * @param string $meta_key The meta key.
+ *
+ * @return void
+ */
+ private function delete_post_meta( $meta_key ) {
+ global $wpdb;
+ $deleted = $wpdb->delete( $wpdb->postmeta, [ 'meta_key' => $meta_key ], [ '%s' ] );
+
+ if ( $deleted ) {
+ wp_cache_set( 'last_changed', microtime(), 'posts' );
+ }
+ }
+
+ /**
+ * Removes all sitemap validators.
+ *
+ * This should be executed on every upgrade routine until we have removed the sitemap caching in the database.
+ *
+ * @return void
+ */
+ private function remove_sitemap_validators() {
+ global $wpdb;
+
+ // Remove all sitemap validators.
+ // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: No relevant caches.
+ // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reason: Most performant way.
+ $wpdb->query(
+ $wpdb->prepare(
+ 'DELETE FROM %i WHERE %i LIKE %s',
+ [ $wpdb->options, 'option_name', 'wpseo_sitemap%validator%' ]
+ )
+ );
+ }
+
+ /**
+ * Retrieves the option value directly from the database.
+ *
+ * @param string $option_name Option to retrieve.
+ *
+ * @return int|string|bool|float|array The content of the option if exists, otherwise an
+ * empty array.
+ */
+ protected function get_option_from_database( $option_name ) {
+ global $wpdb;
+
+ // Load option directly from the database, to avoid filtering and sanitization.
+ // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: No relevant caches.
+ // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reason: Most performant way.
+ $results = $wpdb->get_results(
+ $wpdb->prepare(
+ 'SELECT %i FROM %i WHERE %i = %s',
+ [ 'option_value', $wpdb->options, 'option_name', $option_name ]
+ ),
+ ARRAY_A
+ );
+
+ if ( ! empty( $results ) ) {
+ return maybe_unserialize( $results[0]['option_value'] );
+ }
+
+ return [];
+ }
+
+ /**
+ * Cleans the option to make sure only relevant settings are there.
+ *
+ * @param string $option_name Option name save.
+ *
+ * @return void
+ */
+ protected function cleanup_option_data( $option_name ) {
+ $data = get_option( $option_name, [] );
+ if ( ! is_array( $data ) || $data === [] ) {
+ return;
+ }
+
+ /*
+ * Clean up the option by re-saving it.
+ *
+ * The option framework will remove any settings that are not configured
+ * for this option, removing any migrated settings.
+ */
+ update_option( $option_name, $data );
+ }
+
+ /**
+ * Saves an option setting to where it should be stored.
+ *
+ * @param int|string|bool|float|array $source_data The option containing the value to be
+ * migrated.
+ * @param string $source_setting Name of the key in the "from" option.
+ * @param string|null $target_setting Name of the key in the "to" option.
+ *
+ * @return void
+ */
+ protected function save_option_setting( $source_data, $source_setting, $target_setting = null ) {
+ if ( $target_setting === null ) {
+ $target_setting = $source_setting;
+ }
+
+ if ( isset( $source_data[ $source_setting ] ) ) {
+ WPSEO_Options::set( $target_setting, $source_data[ $source_setting ] );
+ }
+ }
+
+ /**
+ * Migrates WooCommerce archive settings to the WooCommerce Shop page meta-data settings.
+ *
+ * If no Shop page is defined, nothing will be migrated.
+ *
+ * @return void
+ */
+ private function migrate_woocommerce_archive_setting_to_shop_page() {
+ $shop_page_id = wc_get_page_id( 'shop' );
+
+ if ( $shop_page_id === -1 ) {
+ return;
+ }
+
+ $title = WPSEO_Meta::get_value( 'title', $shop_page_id );
+
+ if ( empty( $title ) ) {
+ $option_title = WPSEO_Options::get( 'title-ptarchive-product' );
+
+ WPSEO_Meta::set_value(
+ 'title',
+ $option_title,
+ $shop_page_id
+ );
+
+ WPSEO_Options::set( 'title-ptarchive-product', '' );
+ }
+
+ $meta_description = WPSEO_Meta::get_value( 'metadesc', $shop_page_id );
+
+ if ( empty( $meta_description ) ) {
+ $option_metadesc = WPSEO_Options::get( 'metadesc-ptarchive-product' );
+
+ WPSEO_Meta::set_value(
+ 'metadesc',
+ $option_metadesc,
+ $shop_page_id
+ );
+
+ WPSEO_Options::set( 'metadesc-ptarchive-product', '' );
+ }
+
+ $bc_title = WPSEO_Meta::get_value( 'bctitle', $shop_page_id );
+
+ if ( empty( $bc_title ) ) {
+ $option_bctitle = WPSEO_Options::get( 'bctitle-ptarchive-product' );
+
+ WPSEO_Meta::set_value(
+ 'bctitle',
+ $option_bctitle,
+ $shop_page_id
+ );
+
+ WPSEO_Options::set( 'bctitle-ptarchive-product', '' );
+ }
+
+ $noindex = WPSEO_Meta::get_value( 'meta-robots-noindex', $shop_page_id );
+
+ if ( $noindex === '0' ) {
+ $option_noindex = WPSEO_Options::get( 'noindex-ptarchive-product' );
+
+ WPSEO_Meta::set_value(
+ 'meta-robots-noindex',
+ $option_noindex,
+ $shop_page_id
+ );
+
+ WPSEO_Options::set( 'noindex-ptarchive-product', false );
+ }
+ }
+
+ /**
+ * Stores the initial `permalink_structure` option.
+ *
+ * @return void
+ */
+ public function set_permalink_structure_option_for_151() {
+ WPSEO_Options::set( 'permalink_structure', get_option( 'permalink_structure' ) );
+ }
+
+ /**
+ * Stores the initial slugs of custom taxonomies.
+ *
+ * @return void
+ */
+ public function store_custom_taxonomy_slugs_for_151() {
+ $taxonomies = $this->taxonomy_helper->get_custom_taxonomies();
+
+ $custom_taxonomies = [];
+
+ foreach ( $taxonomies as $taxonomy ) {
+ $slug = $this->taxonomy_helper->get_taxonomy_slug( $taxonomy );
+
+ $custom_taxonomies[ $taxonomy ] = $slug;
+ }
+
+ WPSEO_Options::set( 'custom_taxonomy_slugs', $custom_taxonomies );
+ }
+
+ /**
+ * Copies the frontpage social settings to the titles options.
+ *
+ * @return void
+ */
+ public function copy_og_settings_from_social_to_titles() {
+ $wpseo_social = get_option( 'wpseo_social' );
+ $wpseo_titles = get_option( 'wpseo_titles' );
+
+ $copied_options = [];
+ // Reset to the correct default value.
+ $copied_options['open_graph_frontpage_title'] = '%%sitename%%';
+
+ $options = [
+ 'og_frontpage_title' => 'open_graph_frontpage_title',
+ 'og_frontpage_desc' => 'open_graph_frontpage_desc',
+ 'og_frontpage_image' => 'open_graph_frontpage_image',
+ 'og_frontpage_image_id' => 'open_graph_frontpage_image_id',
+ ];
+
+ foreach ( $options as $social_option => $titles_option ) {
+ if ( ! empty( $wpseo_social[ $social_option ] ) ) {
+ $copied_options[ $titles_option ] = $wpseo_social[ $social_option ];
+ }
+ }
+
+ $wpseo_titles = array_merge( $wpseo_titles, $copied_options );
+
+ update_option( 'wpseo_titles', $wpseo_titles );
+ }
+
+ /**
+ * Reset the social options with the correct default values.
+ *
+ * @return void
+ */
+ public function reset_og_settings_to_default_values() {
+ $wpseo_titles = get_option( 'wpseo_titles' );
+ $updated_options = [];
+
+ $updated_options['social-title-author-wpseo'] = '%%name%%';
+ $updated_options['social-title-archive-wpseo'] = '%%date%%';
+
+ /* translators: %s expands to the name of a post type (plural). */
+ $post_type_archive_default = sprintf( __( '%s Archive', 'wordpress-seo' ), '%%pt_plural%%' );
+
+ /* translators: %s expands to the variable used for term title. */
+ $term_archive_default = sprintf( __( '%s Archives', 'wordpress-seo' ), '%%term_title%%' );
+
+ $post_type_objects = get_post_types( [ 'public' => true ], 'objects' );
+
+ if ( $post_type_objects ) {
+ foreach ( $post_type_objects as $pt ) {
+ // Post types.
+ if ( isset( $wpseo_titles[ 'social-title-' . $pt->name ] ) ) {
+ $updated_options[ 'social-title-' . $pt->name ] = '%%title%%';
+ }
+ // Post type archives.
+ if ( isset( $wpseo_titles[ 'social-title-ptarchive-' . $pt->name ] ) ) {
+ $updated_options[ 'social-title-ptarchive-' . $pt->name ] = $post_type_archive_default;
+ }
+ }
+ }
+
+ $taxonomy_objects = get_taxonomies( [ 'public' => true ], 'object' );
+
+ if ( $taxonomy_objects ) {
+ foreach ( $taxonomy_objects as $tax ) {
+ if ( isset( $wpseo_titles[ 'social-title-tax-' . $tax->name ] ) ) {
+ $updated_options[ 'social-title-tax-' . $tax->name ] = $term_archive_default;
+ }
+ }
+ }
+
+ $wpseo_titles = array_merge( $wpseo_titles, $updated_options );
+
+ update_option( 'wpseo_titles', $wpseo_titles );
+ }
+
+ /**
+ * Removes all indexables for posts that are not publicly viewable.
+ * This method should be called after init, because post_types can still be registered.
+ *
+ * @return void
+ */
+ public function remove_indexable_rows_for_non_public_post_types() {
+ global $wpdb;
+
+ // If migrations haven't been completed successfully the following may give false errors. So suppress them.
+ $show_errors = $wpdb->show_errors;
+ $wpdb->show_errors = false;
+
+ $indexable_table = Model::get_table_name( 'Indexable' );
+
+ $included_post_types = YoastSEO()->helpers->post_type->get_indexable_post_types();
+
+ if ( empty( $included_post_types ) ) {
+ // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: No relevant caches.
+ // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reason: Most performant way.
+ $wpdb->query(
+ $wpdb->prepare(
+ "DELETE FROM %i
+ WHERE %i = 'post'
+ AND %i IS NOT NULL",
+ [ $indexable_table, 'object_type', 'object_sub_type' ]
+ )
+ );
+ }
+ else {
+ // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: No relevant caches.
+ // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reason: Most performant way.
+ $wpdb->query(
+ $wpdb->prepare(
+ "DELETE FROM %i
+ WHERE %i = 'post'
+ AND %i IS NOT NULL
+ AND %i NOT IN ( " . implode( ', ', array_fill( 0, count( $included_post_types ), '%s' ) ) . ' )',
+ array_merge(
+ [
+ $indexable_table,
+ 'object_type',
+ 'object_sub_type',
+ 'object_sub_type',
+ ],
+ $included_post_types
+ )
+ )
+ );
+ }
+
+ $wpdb->show_errors = $show_errors;
+ }
+
+ /**
+ * Removes all indexables for terms that are not publicly viewable.
+ * This method should be called after init, because taxonomies can still be registered.
+ *
+ * @return void
+ */
+ public function remove_indexable_rows_for_non_public_taxonomies() {
+ global $wpdb;
+
+ // If migrations haven't been completed successfully the following may give false errors. So suppress them.
+ $show_errors = $wpdb->show_errors;
+ $wpdb->show_errors = false;
+
+ $indexable_table = Model::get_table_name( 'Indexable' );
+
+ $included_taxonomies = YoastSEO()->helpers->taxonomy->get_indexable_taxonomies();
+
+ if ( empty( $included_taxonomies ) ) {
+ // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: No relevant caches.
+ // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reason: Most performant way.
+ $wpdb->query(
+ $wpdb->prepare(
+ "DELETE FROM %i
+ WHERE %i = 'term'
+ AND %i IS NOT NULL",
+ [ $indexable_table, 'object_type', 'object_sub_type' ]
+ )
+ );
+ }
+ else {
+ // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: No relevant caches.
+ // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reason: Most performant way.
+ $wpdb->query(
+ $wpdb->prepare(
+ "DELETE FROM %i
+ WHERE %i = 'term'
+ AND %i IS NOT NULL
+ AND %i NOT IN ( " . implode( ', ', array_fill( 0, count( $included_taxonomies ), '%s' ) ) . ' )',
+ array_merge(
+ [
+ $indexable_table,
+ 'object_type',
+ 'object_sub_type',
+ 'object_sub_type',
+ ],
+ $included_taxonomies
+ )
+ )
+ );
+ }
+
+ $wpdb->show_errors = $show_errors;
+ }
+
+ /**
+ * De-duplicates indexables that have more than one "unindexed" rows for the same object. Keeps the newest
+ * indexable.
+ *
+ * @return void
+ */
+ protected function deduplicate_unindexed_indexable_rows() {
+ global $wpdb;
+
+ // If migrations haven't been completed successfully the following may give false errors. So suppress them.
+ $show_errors = $wpdb->show_errors;
+ $wpdb->show_errors = false;
+
+ // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: No relevant caches.
+ // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reason: Most performant way.
+ $duplicates = $wpdb->get_results(
+ $wpdb->prepare(
+ "
+ SELECT
+ MAX(id) as newest_id,
+ object_id,
+ object_type
+ FROM
+ %i
+ WHERE
+ post_status = 'unindexed'
+ AND object_type IN ( 'term', 'post', 'user' )
+ GROUP BY
+ object_id,
+ object_type
+ HAVING
+ count(*) > 1",
+ [ Model::get_table_name( 'Indexable' ) ]
+ ),
+ ARRAY_A
+ );
+
+ if ( empty( $duplicates ) ) {
+ $wpdb->show_errors = $show_errors;
+
+ return;
+ }
+
+ // Users, terms and posts may share the same object_id. So delete them in separate, more performant, queries.
+ $delete_queries = [
+ $this->get_indexable_deduplication_query_for_type( 'post', $duplicates, $wpdb ),
+ $this->get_indexable_deduplication_query_for_type( 'term', $duplicates, $wpdb ),
+ $this->get_indexable_deduplication_query_for_type( 'user', $duplicates, $wpdb ),
+ ];
+
+ foreach ( $delete_queries as $delete_query ) {
+ if ( ! empty( $delete_query ) ) {
+ // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reason: Most performant way.
+ // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: No relevant caches.
+ // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared -- Reason: Is it prepared already.
+ $wpdb->query( $delete_query );
+ // phpcs:enable
+ }
+ }
+
+ $wpdb->show_errors = $show_errors;
+ }
+
+ /**
+ * Cleans up "unindexed" indexable rows when appropriate, aka when there's no object ID even though it should.
+ *
+ * @return void
+ */
+ protected function clean_unindexed_indexable_rows_with_no_object_id() {
+ global $wpdb;
+
+ // If migrations haven't been completed successfully the following may give false errors. So suppress them.
+ $show_errors = $wpdb->show_errors;
+ $wpdb->show_errors = false;
+
+ // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: No relevant caches.
+ // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reason: Most performant way.
+ $wpdb->query(
+ $wpdb->prepare(
+ "DELETE FROM %i
+ WHERE %i = 'unindexed'
+ AND %i NOT IN ( 'home-page', 'date-archive', 'post-type-archive', 'system-page' )
+ AND %i IS NULL",
+ [ Model::get_table_name( 'Indexable' ), 'post_status', 'object_type', 'object_id' ]
+ )
+ );
+
+ $wpdb->show_errors = $show_errors;
+ }
+
+ /**
+ * Removes all user indexable rows when the author archive is disabled.
+ *
+ * @return void
+ */
+ protected function remove_indexable_rows_for_disabled_authors_archive() {
+ global $wpdb;
+
+ if ( ! YoastSEO()->helpers->author_archive->are_disabled() ) {
+ return;
+ }
+
+ // If migrations haven't been completed successfully the following may give false errors. So suppress them.
+ $show_errors = $wpdb->show_errors;
+ $wpdb->show_errors = false;
+
+ // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: No relevant caches.
+ // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reason: Most performant way.
+ $wpdb->query(
+ $wpdb->prepare(
+ "DELETE FROM %i WHERE %i = 'user'",
+ [ Model::get_table_name( 'Indexable' ), 'object_type' ]
+ )
+ );
+
+ $wpdb->show_errors = $show_errors;
+ }
+
+ /**
+ * Creates a query for de-duplicating indexables for a particular type.
+ *
+ * @param string $object_type The object type to deduplicate.
+ * @param string|array> $duplicates The result of the duplicate query.
+ * @param wpdb $wpdb The wpdb object.
+ *
+ * @return string The query that removes all but one duplicate for each object of the object type.
+ */
+ protected function get_indexable_deduplication_query_for_type( $object_type, $duplicates, $wpdb ) {
+ $filtered_duplicates = array_filter(
+ $duplicates,
+ static function ( $duplicate ) use ( $object_type ) {
+ return $duplicate['object_type'] === $object_type;
+ }
+ );
+
+ if ( empty( $filtered_duplicates ) ) {
+ return '';
+ }
+
+ $object_ids = wp_list_pluck( $filtered_duplicates, 'object_id' );
+ $newest_indexable_ids = wp_list_pluck( $filtered_duplicates, 'newest_id' );
+
+ $replacements = array_merge(
+ [
+ Model::get_table_name( 'Indexable' ),
+ 'object_id',
+ ],
+ array_values( $object_ids ),
+ array_values( $newest_indexable_ids )
+ );
+ $replacements[] = $object_type;
+
+ // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: No relevant caches.
+ // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reason: Most performant way.
+ return $wpdb->prepare(
+ 'DELETE FROM
+ %i
+ WHERE
+ %i IN ( ' . implode( ', ', array_fill( 0, count( $filtered_duplicates ), '%d' ) ) . ' )
+ AND id NOT IN ( ' . implode( ', ', array_fill( 0, count( $filtered_duplicates ), '%d' ) ) . ' )
+ AND object_type = %s',
+ $replacements
+ );
+ }
+
+ /**
+ * Removes the settings' introduction modal data for users.
+ *
+ * @return void
+ */
+ public function delete_user_introduction_meta() {
+ delete_metadata( 'user', 0, '_yoast_settings_introduction', '', true );
+ }
+}
diff --git a/wp-content/plugins/wordpress-seo/inc/class-wpseo-admin-bar-menu.php b/wp-content/plugins/wordpress-seo/inc/class-wpseo-admin-bar-menu.php
new file mode 100755
index 00000000..a9fae5ed
--- /dev/null
+++ b/wp-content/plugins/wordpress-seo/inc/class-wpseo-admin-bar-menu.php
@@ -0,0 +1,941 @@
+classes->get( Indexable_Repository::class );
+ }
+ if ( ! $score_icon_helper ) {
+ $score_icon_helper = YoastSEO()->helpers->score_icon;
+ }
+ if ( ! $product_helper ) {
+ $product_helper = YoastSEO()->helpers->product;
+ }
+ if ( ! $shortlinker ) {
+ $shortlinker = new WPSEO_Shortlinker();
+ }
+
+ $this->product_helper = $product_helper;
+ $this->asset_manager = $asset_manager;
+ $this->indexable_repository = $indexable_repository;
+ $this->score_icon_helper = $score_icon_helper;
+ $this->shortlinker = $shortlinker;
+ }
+
+ /**
+ * Gets whether SEO score is enabled, with cache applied.
+ *
+ * @return bool True if SEO score is enabled, false otherwise.
+ */
+ protected function get_is_seo_enabled() {
+ if ( $this->is_seo_enabled === null ) {
+ $this->is_seo_enabled = ( new WPSEO_Metabox_Analysis_SEO() )->is_enabled();
+ }
+
+ return $this->is_seo_enabled;
+ }
+
+ /**
+ * Gets whether readability is enabled, with cache applied.
+ *
+ * @return bool True if readability is enabled, false otherwise.
+ */
+ protected function get_is_readability_enabled() {
+ if ( $this->is_readability_enabled === null ) {
+ $this->is_readability_enabled = ( new WPSEO_Metabox_Analysis_Readability() )->is_enabled();
+ }
+
+ return $this->is_readability_enabled;
+ }
+
+ /**
+ * Returns the indexable for the current WordPress page, with cache applied.
+ *
+ * @return bool|Indexable The indexable, false if none could be found.
+ */
+ protected function get_current_indexable() {
+ if ( $this->current_indexable === null ) {
+ $this->current_indexable = $this->indexable_repository->for_current_page();
+ }
+
+ return $this->current_indexable;
+ }
+
+ /**
+ * Adds the admin bar menu.
+ *
+ * @param WP_Admin_Bar $wp_admin_bar Admin bar instance to add the menu to.
+ *
+ * @return void
+ */
+ public function add_menu( WP_Admin_Bar $wp_admin_bar ) {
+ // On block editor pages, the admin bar only shows on mobile, where having this menu icon is not very helpful.
+ if ( is_admin() ) {
+ $screen = get_current_screen();
+ if ( isset( $screen ) && $screen->is_block_editor() ) {
+ return;
+ }
+ }
+
+ // If the current user can't write posts, this is all of no use, so let's not output an admin menu.
+ if ( ! current_user_can( 'edit_posts' ) ) {
+ return;
+ }
+
+ $this->add_root_menu( $wp_admin_bar );
+
+ /**
+ * Adds a submenu item in the top of the adminbar.
+ *
+ * @param WP_Admin_Bar $wp_admin_bar Admin bar instance to add the menu to.
+ * @param string $menu_identifier The menu identifier.
+ */
+ do_action( 'wpseo_add_adminbar_submenu', $wp_admin_bar, self::MENU_IDENTIFIER );
+
+ if ( ! is_admin() ) {
+
+ if ( is_singular() || is_tag() || is_tax() || is_category() ) {
+ $is_seo_enabled = $this->get_is_seo_enabled();
+ $is_readability_enabled = $this->get_is_readability_enabled();
+
+ $indexable = $this->get_current_indexable();
+
+ if ( $is_seo_enabled ) {
+ $focus_keyword = ( ! is_a( $indexable, 'Yoast\WP\SEO\Models\Indexable' ) || $indexable->primary_focus_keyword === null ) ? __( 'not set', 'wordpress-seo' ) : $indexable->primary_focus_keyword;
+
+ $wp_admin_bar->add_menu(
+ [
+ 'parent' => self::MENU_IDENTIFIER,
+ 'id' => 'wpseo-seo-focus-keyword',
+ 'title' => __( 'Focus keyphrase: ', 'wordpress-seo' ) . '' . $focus_keyword . '',
+ 'meta' => [ 'tabindex' => '0' ],
+ ]
+ );
+ $wp_admin_bar->add_menu(
+ [
+ 'parent' => self::MENU_IDENTIFIER,
+ 'id' => 'wpseo-seo-score',
+ 'title' => __( 'SEO score', 'wordpress-seo' ) . ': ' . $this->score_icon_helper->for_seo( $indexable, 'adminbar-sub-menu-score' )
+ ->present(),
+ 'meta' => [ 'tabindex' => '0' ],
+ ]
+ );
+ }
+
+ if ( $is_readability_enabled ) {
+ $wp_admin_bar->add_menu(
+ [
+ 'parent' => self::MENU_IDENTIFIER,
+ 'id' => 'wpseo-readability-score',
+ 'title' => __( 'Readability', 'wordpress-seo' ) . ': ' . $this->score_icon_helper->for_readability( $indexable->readability_score, 'adminbar-sub-menu-score' )
+ ->present(),
+ 'meta' => [ 'tabindex' => '0' ],
+ ]
+ );
+ }
+
+ if ( ! $this->product_helper->is_premium() ) {
+ $wp_admin_bar->add_menu(
+ [
+ 'parent' => self::MENU_IDENTIFIER,
+ 'id' => 'wpseo-frontend-inspector',
+ 'href' => $this->shortlinker->build_shortlink( 'https://yoa.st/admin-bar-frontend-inspector' ),
+ 'title' => __( 'Front-end SEO inspector', 'wordpress-seo' ) . new Premium_Badge_Presenter( 'wpseo-frontend-inspector-badge' ),
+ 'meta' => [
+ 'tabindex' => '0',
+ 'target' => '_blank',
+ ],
+ ]
+ );
+ }
+ }
+ $this->add_analysis_submenu( $wp_admin_bar );
+ $this->add_seo_tools_submenu( $wp_admin_bar );
+ $this->add_how_to_submenu( $wp_admin_bar );
+ $this->add_get_help_submenu( $wp_admin_bar );
+ }
+
+ if ( ! is_admin() || is_blog_admin() ) {
+ $this->add_settings_submenu( $wp_admin_bar );
+ }
+ elseif ( is_network_admin() ) {
+ $this->add_network_settings_submenu( $wp_admin_bar );
+ }
+
+ $this->add_premium_link( $wp_admin_bar );
+ }
+
+ /**
+ * Enqueues admin bar assets.
+ *
+ * @return void
+ */
+ public function enqueue_assets() {
+ if ( ! is_admin_bar_showing() ) {
+ return;
+ }
+
+ // If the current user can't write posts, this is all of no use, so let's not output an admin menu.
+ if ( ! current_user_can( 'edit_posts' ) ) {
+ return;
+ }
+
+ $this->asset_manager->register_assets();
+ $this->asset_manager->enqueue_style( 'adminbar' );
+ }
+
+ /**
+ * Registers the hooks.
+ *
+ * @return void
+ */
+ public function register_hooks() {
+ if ( ! $this->meets_requirements() ) {
+ return;
+ }
+
+ add_action( 'admin_bar_menu', [ $this, 'add_menu' ], 95 );
+
+ add_action( 'wp_enqueue_scripts', [ $this, 'enqueue_assets' ] );
+ add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] );
+ }
+
+ /**
+ * Checks whether the requirements to use this class are met.
+ *
+ * @return bool True if requirements are met, false otherwise.
+ */
+ public function meets_requirements() {
+ if ( is_network_admin() ) {
+ return WPSEO_Utils::is_plugin_network_active();
+ }
+
+ if ( WPSEO_Options::get( 'enable_admin_bar_menu' ) !== true ) {
+ return false;
+ }
+
+ return ! is_admin() || is_blog_admin();
+ }
+
+ /**
+ * Adds the admin bar root menu.
+ *
+ * @param WP_Admin_Bar $wp_admin_bar Admin bar instance to add the menu to.
+ *
+ * @return void
+ */
+ protected function add_root_menu( WP_Admin_Bar $wp_admin_bar ) {
+ $title = $this->get_title();
+
+ $score = '';
+ $settings_url = '';
+ $counter = '';
+ $notification_popup = '';
+ $notification_count = 0;
+
+ $post = $this->get_singular_post();
+ if ( $post ) {
+ $score = $this->get_post_score( $post );
+ }
+
+ $term = $this->get_singular_term();
+ if ( $term ) {
+ $score = $this->get_term_score( $term );
+ }
+
+ $can_manage_options = $this->can_manage_options();
+
+ if ( $can_manage_options ) {
+ $settings_url = $this->get_settings_page_url();
+ }
+
+ if ( empty( $score ) && ! is_network_admin() && $can_manage_options ) {
+ $notification_center = Yoast_Notification_Center::get();
+ $notification_count = $notification_center->get_notification_count();
+
+ $counter = $this->get_notification_counter( $notification_count );
+ $notification_popup = $this->get_notification_popup();
+ }
+
+ $admin_bar_menu_args = [
+ 'id' => self::MENU_IDENTIFIER,
+ 'title' => $title . $score . $counter . $notification_popup,
+ 'href' => $settings_url,
+ 'meta' => [ 'tabindex' => ! empty( $settings_url ) ? false : '0' ],
+ ];
+ $wp_admin_bar->add_menu( $admin_bar_menu_args );
+
+ if ( $notification_count > 0 ) {
+ $admin_bar_menu_args = [
+ 'parent' => self::MENU_IDENTIFIER,
+ 'id' => 'wpseo-notifications',
+ 'title' => __( 'Notifications', 'wordpress-seo' ) . $counter,
+ 'href' => empty( $settings_url ) ? '' : $settings_url . '#/alert-center',
+ 'meta' => [ 'tabindex' => ! empty( $settings_url ) ? false : '0' ],
+ ];
+ $wp_admin_bar->add_menu( $admin_bar_menu_args );
+ }
+ }
+
+ /**
+ * Adds the admin bar analysis submenu.
+ *
+ * @param WP_Admin_Bar $wp_admin_bar Admin bar instance to add the menu to.
+ *
+ * @return void
+ */
+ protected function add_analysis_submenu( WP_Admin_Bar $wp_admin_bar ) {
+ try {
+ $url = YoastSEO()->meta->for_current_page()->canonical;
+ } catch ( Exception $e ) {
+ // This is not the type of error we can handle here.
+ return;
+ }
+
+ if ( ! $url ) {
+ return;
+ }
+
+ $menu_args = [
+ 'parent' => self::MENU_IDENTIFIER,
+ 'id' => self::ANALYSIS_SUBMENU_IDENTIFIER,
+ 'title' => __( 'Analyze this page', 'wordpress-seo' ),
+ 'meta' => [ 'tabindex' => '0' ],
+ ];
+ $wp_admin_bar->add_menu( $menu_args );
+
+ $encoded_url = rawurlencode( $url );
+ $submenu_items = [
+ [
+ 'id' => 'wpseo-inlinks',
+ 'title' => __( 'Check links to this URL', 'wordpress-seo' ),
+ 'href' => 'https://search.google.com/search-console/links/drilldown?resource_id=' . rawurlencode( get_option( 'siteurl' ) ) . '&type=EXTERNAL&target=' . $encoded_url . '&domain=',
+ ],
+ [
+ 'id' => 'wpseo-structureddata',
+ 'title' => __( 'Google Rich Results Test', 'wordpress-seo' ),
+ 'href' => 'https://search.google.com/test/rich-results?url=' . $encoded_url,
+ ],
+ [
+ 'id' => 'wpseo-facebookdebug',
+ 'title' => __( 'Facebook Debugger', 'wordpress-seo' ),
+ 'href' => '//developers.facebook.com/tools/debug/?q=' . $encoded_url,
+ ],
+ [
+ 'id' => 'wpseo-pagespeed',
+ 'title' => __( 'Google Page Speed Test', 'wordpress-seo' ),
+ 'href' => '//developers.google.com/speed/pagespeed/insights/?url=' . $encoded_url,
+ ],
+ ];
+
+ $this->add_submenu_items( $submenu_items, $wp_admin_bar, self::ANALYSIS_SUBMENU_IDENTIFIER );
+ }
+
+ /**
+ * Adds the admin bar tools submenu.
+ *
+ * @param WP_Admin_Bar $wp_admin_bar Admin bar instance to add the menu to.
+ *
+ * @return void
+ */
+ protected function add_seo_tools_submenu( WP_Admin_Bar $wp_admin_bar ) {
+ $menu_args = [
+ 'parent' => self::MENU_IDENTIFIER,
+ 'id' => 'wpseo-sub-tools',
+ 'title' => __( 'SEO Tools', 'wordpress-seo' ),
+ 'meta' => [ 'tabindex' => '0' ],
+ ];
+ $wp_admin_bar->add_menu( $menu_args );
+
+ $submenu_items = [
+ [
+ 'id' => 'wpseo-semrush',
+ 'title' => 'Semrush',
+ 'href' => $this->shortlinker->build_shortlink( 'https://yoa.st/admin-bar-semrush' ),
+ ],
+ [
+ 'id' => 'wpseo-wincher',
+ 'title' => 'Wincher',
+ 'href' => $this->shortlinker->build_shortlink( 'https://yoa.st/admin-bar-wincher' ),
+ ],
+ [
+ 'id' => 'wpseo-google-trends',
+ 'title' => 'Google trends',
+ 'href' => $this->shortlinker->build_shortlink( 'https://yoa.st/admin-bar-gtrends' ),
+ ],
+ ];
+
+ $this->add_submenu_items( $submenu_items, $wp_admin_bar, 'wpseo-sub-tools' );
+ }
+
+ /**
+ * Adds the admin bar How To submenu.
+ *
+ * @param WP_Admin_Bar $wp_admin_bar Admin bar instance to add the menu to.
+ *
+ * @return void
+ */
+ protected function add_how_to_submenu( WP_Admin_Bar $wp_admin_bar ) {
+ $menu_args = [
+ 'parent' => self::MENU_IDENTIFIER,
+ 'id' => 'wpseo-sub-howto',
+ 'title' => __( 'How to', 'wordpress-seo' ),
+ 'meta' => [ 'tabindex' => '0' ],
+ ];
+ $wp_admin_bar->add_menu( $menu_args );
+
+ $submenu_items = [
+ [
+ 'id' => 'wpseo-learn-seo',
+ 'title' => __( 'Learn more SEO', 'wordpress-seo' ),
+ 'href' => $this->shortlinker->build_shortlink( 'https://yoa.st/admin-bar-learn-more-seo' ),
+ ],
+ [
+ 'id' => 'wpseo-improve-blogpost',
+ 'title' => __( 'Improve your blog post', 'wordpress-seo' ),
+ 'href' => $this->shortlinker->build_shortlink( 'https://yoa.st/admin-bar-improve-blog-post' ),
+ ],
+ [
+ 'id' => 'wpseo-write-better-content',
+ 'title' => __( 'Write better content', 'wordpress-seo' ),
+ 'href' => $this->shortlinker->build_shortlink( 'https://yoa.st/admin-bar-write-better' ),
+ ],
+ ];
+
+ $this->add_submenu_items( $submenu_items, $wp_admin_bar, 'wpseo-sub-howto' );
+ }
+
+ /**
+ * Adds the admin bar How To submenu.
+ *
+ * @param WP_Admin_Bar $wp_admin_bar Admin bar instance to add the menu to.
+ *
+ * @return void
+ */
+ protected function add_get_help_submenu( WP_Admin_Bar $wp_admin_bar ) {
+ $menu_args = [
+ 'parent' => self::MENU_IDENTIFIER,
+ 'id' => 'wpseo-sub-get-help',
+ 'title' => __( 'Help', 'wordpress-seo' ),
+ 'meta' => [ 'tabindex' => '0' ],
+ ];
+
+ if ( current_user_can( Support_Integration::CAPABILITY ) ) {
+ $menu_args['href'] = admin_url( 'admin.php?page=' . Support_Integration::PAGE );
+ $wp_admin_bar->add_menu( $menu_args );
+
+ return;
+ }
+ $wp_admin_bar->add_menu( $menu_args );
+
+ $submenu_items = [
+ [
+ 'id' => 'wpseo-yoast-help',
+ 'title' => __( 'Yoast.com help section', 'wordpress-seo' ),
+ 'href' => $this->shortlinker->build_shortlink( 'https://yoa.st/admin-bar-yoast-help' ),
+ ],
+ [
+ 'id' => 'wpseo-premium-support',
+ 'title' => __( 'Yoast Premium support', 'wordpress-seo' ),
+ 'href' => $this->shortlinker->build_shortlink( 'https://yoa.st/admin-bar-premium-support' ),
+ ],
+ [
+ 'id' => 'wpseo-wp-support-forums',
+ 'title' => __( 'WordPress.org support forums', 'wordpress-seo' ),
+ 'href' => $this->shortlinker->build_shortlink( 'https://yoa.st/admin-bar-wp-support-forums' ),
+ ],
+ [
+ 'id' => 'wpseo-learn-seo-2',
+ 'title' => __( 'Learn more SEO', 'wordpress-seo' ),
+ 'href' => $this->shortlinker->build_shortlink( 'https://yoa.st/admin-bar-learn-more-seo-help' ),
+ ],
+ ];
+
+ $this->add_submenu_items( $submenu_items, $wp_admin_bar, 'wpseo-sub-get-help' );
+ }
+
+ /**
+ * Adds the admin bar How To submenu.
+ *
+ * @param WP_Admin_Bar $wp_admin_bar Admin bar instance to add the menu to.
+ *
+ * @return void
+ */
+ protected function add_premium_link( WP_Admin_Bar $wp_admin_bar ) {
+ $link = $this->shortlinker->build_shortlink( 'https://yoa.st/admin-bar-get-premium' );
+ $has_woocommerce = ( new Woocommerce_Conditional() )->is_met();
+
+ if ( $this->product_helper->is_premium() ) {
+ $link = $this->shortlinker->build_shortlink( 'https://yoa.st/admin-bar-get-ai-insights' );
+ }
+ elseif ( $has_woocommerce ) {
+ $link = $this->shortlinker->build_shortlink( 'https://yoa.st/admin-bar-get-premium-woocommerce' );
+ }
+
+ $button_label = esc_html__( 'Upgrade', 'wordpress-seo' );
+ $badge = '';
+ if ( $this->product_helper->is_premium() ) {
+ $badge = '
)$")},54241:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e){return e.findAll((e=>!!e.sentences)).flatMap((t=>t.sentences.map((r=>(r.setParentAttributes(t,e),r)))))}},47647:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var s,n=(s=r(29003))&&s.__esModule?s:{default:s},i=r(92819);t.default=(0,i.memoize)((function(e,t=!0){const r=new n.default,{tokenizer:s,tokens:i}=r.createTokenizer();return r.tokenize(s,e),0===i.length?[]:r.getSentencesFromTokens(i,t)}),((...e)=>JSON.stringify(e)))},91916:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e,t){const r=t.getHelper("customCountLength");return e.map((e=>{const t=r?r(e.text):(0,s.getWordsFromTokens)(e.tokens,!1).length;if(t>0)return{sentence:e,sentenceLength:t,firstToken:e.getFirstToken()||null,lastToken:e.getLastToken()||null}})).filter(Boolean)};var s=r(60914)},58766:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var s=r(92819);t.default=class{constructor(e){this._location=e.location,this._fragment=e.word,this._syllables=e.syllables,this._regex=null,this._options=(0,s.pick)(e,["notFollowedBy","alsoFollowedBy"])}createRegex(){const e=this._options;let t,r=this._fragment;switch((0,s.isUndefined)(e.notFollowedBy)||(r+="(?!["+e.notFollowedBy.join("")+"])"),(0,s.isUndefined)(e.alsoFollowedBy)||(r+="["+e.alsoFollowedBy.join("")+"]?"),this._location){case"atBeginning":t="^"+r;break;case"atEnd":t=r+"$";break;case"atBeginningOrEnd":t="(^"+r+")|("+r+"$)";break;default:t=r}this._regex=new RegExp(t)}getRegex(){return null===this._regex&&this.createRegex(),this._regex}occursIn(e){return this.getRegex().test(e)}removeFrom(e){return e.replace(this._fragment," ")}getSyllables(){return this._syllables}}},39617:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=t.countSyllablesInWord=void 0;var s=o(r(1105)),n=r(92819),i=o(r(15495)),a=o(r(58766));function o(e){return e&&e.__esModule?e:{default:e}}const l=(0,n.memoize)((function(e){let t=[];const r=e.deviations;return t=(0,n.flatMap)(r.words.fragments,(function(e,t){return(0,n.map)(e,(function(e){return e.location=t,new a.default(e)}))})),t})),u=function(e,t){let r=0;if(!(0,n.isUndefined)(t.deviations)&&!(0,n.isUndefined)(t.deviations.words)){if(!(0,n.isUndefined)(t.deviations.words.full)){const r=function(e,t){const r=t.deviations.words.full,s=(0,n.find)(r,(function(t){return t.word===e}));return(0,n.isUndefined)(s)?0:s.syllables}(e,t);if(0!==r)return r}if(!(0,n.isUndefined)(t.deviations.words.fragments)){const s=function(e,t){const r=l(t);let s=e,i=0;return(0,n.forEach)(r,(function(e){e.occursIn(s)&&(s=e.removeFrom(s),i+=e.getSyllables())})),{word:s,syllableCount:i}}(e,t);e=s.word,r+=s.syllableCount}}return r+=function(e,t){let r=0;return r+=function(e,t){let r=0;const s=new RegExp("[^"+t.vowels+"]","ig"),i=e.split(s);return r+=(0,n.filter)(i,(function(e){return""!==e})).length,r}(e,t),(0,n.isUndefined)(t.deviations)||(0,n.isUndefined)(t.deviations.vowels)||(r+=function(e,t){return new i.default(t).countSyllables(e)}(e,t)),r}(e,t),r};t.countSyllablesInWord=u,t.default=function(e,t){e=e.toLocaleLowerCase();const r=(0,s.default)(e),i=(0,n.map)(r,(function(e){return u(e,t)}));return(0,n.sum)(i)}},15495:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var s,n=(s=r(87743))&&s.__esModule?s:{default:s},i=r(92819);t.default=class{constructor(e){this.countSteps=[],(0,i.isUndefined)(e)||this.createSyllableCountSteps(e.deviations.vowels)}createSyllableCountSteps(e){(0,i.forEach)(e,function(e){this.countSteps.push(new n.default(e))}.bind(this))}getAvailableSyllableCountSteps(){return this.countSteps}countSyllables(e){let t=0;return(0,i.forEach)(this.countSteps,(function(r){t+=r.countSyllables(e)})),t}}},87743:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var s,n=r(92819),i=(s=r(2008))&&s.__esModule?s:{default:s};t.default=class{constructor(e){this._hasRegex=!1,this._regex="",this._multiplier="",this.createRegex(e)}hasRegex(){return this._hasRegex}createRegex(e){(0,n.isUndefined)(e)||(0,n.isUndefined)(e.fragments)||(this._hasRegex=!0,this._regex=(0,i.default)(e.fragments,!0),this._multiplier=e.countModifier)}getRegex(){return this._regex}countSyllables(e){return this._hasRegex?(e.match(this._regex)||[]).length*this._multiplier:0}}},85336:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e){let t=e;return e.forEach((r=>{(r=r.split("-")).length>0&&r.filter((t=>!e.includes(t))).length>0&&(t=t.concat(r))})),t}},74169:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e){const t=(0,n.default)();for(let r=0;r{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.arraysDifference=l,t.arraysOverlap=u,t.combinations=c,t.getIndicesOfCharacter=o,t.getIndicesOfWords=a,t.replaceTurkishIs=h,t.replaceTurkishIsMemoized=void 0;var s,n=r(92819),i=(s=r(1105))&&s.__esModule?s:{default:s};function a(e){const t=[],r=(0,i.default)(e);let s=0;return r.forEach((function(r){const n=e.indexOf(r,s);t.push(n),s=n+r.length})),t}function o(e,t){const r=[];if(e.indexOf(t)>-1)for(let s=0;s{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e,t){const r=(0,n.default)(t);for(let t=0;t{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e,t){const r=(0,n.default)(t);for(let t=r.length-1;t>=0;t--)e=e.replace(r[t].letter,r[t].alternative);return e};var s,n=(s=r(23324))&&s.__esModule?s:{default:s}},84159:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e){return e.replace(/[-_]/gi," ")}},20917:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var s,n=(s=r(8575))&&s.__esModule?s:{default:s};const i=/href=(["'])([^"']+)\1/i;function a(e){return e.split("#")[0]}function o(e){return e.split("?")[0]}function l(e){return e.replace(/\/$/,"")}function u(e){return l(e)+"/"}t.default={removeHash:a,removeQueryArgs:o,removeTrailingSlash:l,addTrailingSlash:u,getFromAnchorTag:function(e){const t=i.exec(e);return null===t?"":t[2]},areEqual:function(e,t){return e=o(a(e)),t=o(a(t)),u(e)===u(t)},getHostname:function(e){return(e=n.default.parse(e)).hostname},getProtocol:function(e){return n.default.parse(e).protocol},isInternalLink:function(e,t){const r=n.default.parse(e,!1,!0).hostname;return-1===e.indexOf("//")&&0===e.indexOf("/")||0!==e.indexOf("#")&&(!r||r===t||r===n.default.parse(t).hostname)},protocolIsHttpScheme:function(e){return!!e&&("http:"===e||"https:"===e)},isRelativeFragmentURL:function(e){return 0===e.indexOf("#")}}},28875:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e,t=!1,r="",s=""){let n,i;return n="id"===s?'[ \\u00a0\\n\\r\\t.,()”“〝〞〟‟„"+;!¡?¿:/»«‹›'+r+"<>":'[ \\u00a0\\u2014\\u06d4\\u061f\\u060C\\u061B\\n\\r\\t.,()”“〝〞〟‟„"+\\-;!¡?¿:/»«‹›'+r+"<>",i=t?"($|((?="+n+"]))|((['‘’‛`])("+n+"])))":"($|("+n+"])|((['‘’‛`])("+n+"])))","(^|"+n+"'‘’‛`])"+e+i}},11475:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e,t){return 0!==(0,n.filter)((0,i.default)(t),(function(t){return e.includes(t.toLocaleLowerCase())})).length};var s,n=r(92819),i=(s=r(1105))&&s.__esModule?s:{default:s}},40231:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e,t){let r=t.length;return""!==e&&r>0&&(r+=e.length+3),r}},33870:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e){return(0,n.default)(e).length};var s,n=(s=r(1105))&&s.__esModule?s:{default:s}},16431:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var s=r(8737),n=r(33888);t.default=e=>{const t=[];return e.forEach((e=>{const r=[],i=[];for(;s.punctuationRegexStart.test(e)&&!n.hashedHtmlEntitiesRegexStart.test(e);)r.push(e[0]),e=e.slice(1);for(;s.punctuationRegexEnd.test(e)&&!n.hashedHtmlEntitiesRegexEnd.test(e);)i.unshift(e[e.length-1]),e=e.slice(0,-1);let a=[...r,e,...i];a=a.filter((e=>""!==e)),t.push(...a)})),t}},49325:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e,t=[]){return(0,s.filter)(e,(function(e){return!(0,s.includes)(t,e.trim().toLocaleLowerCase())}))};var s=r(92819)},89032:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e,t){if((0,s.isEmpty)(e))return!1;const r=t.index+t.match.length,n=[];return(0,s.forEach)(e,(function(e){n.push(e.index)})),(0,s.includes)(n,r)};var s=r(92819)},60914:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e){const t=(0,s.default)(e.getTree());return o((0,n.flatMap)(t.map((e=>e.tokens))))},t.getWordsFromTokens=o;var s=a(r(54241)),n=r(92819),i=a(r(8737));function a(e){return e&&e.__esModule?e:{default:e}}function o(e,t=!0){let r=e.map((e=>e.text));return t||function(e,t){for(;-1!==e.indexOf(t);){if(0===e.indexOf(t)||e.indexOf(t)===e.length-1){e.splice(e.indexOf(t),1);continue}const r=e.indexOf(t),s=e[r-1],n=e[r+1];e.splice(r-1,3,s+t+n)}}(r,"-"),r=r.map((e=>(0,i.default)(e))),r.filter((e=>""!==e.trim()))}},1105:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e,t="\\s",r=!0){if(""===(e=(0,n.default)(e)))return[];const s=new RegExp(t,"g");let o=e.split(s);return o=r?o.map(a.default):(0,i.flatMap)(o,(e=>e.replace(l," $1 ").split(" "))),(0,i.filter)(o,(function(e){return""!==e.trim()}))};var s,n=(s=r(86697))&&s.__esModule?s:{default:s},i=r(92819),a=function(e,t){if(e&&e.__esModule)return e;if(null===e||"object"!=typeof e&&"function"!=typeof e)return{default:e};var r=o(t);if(r&&r.has(e))return r.get(e);var s={__proto__:null},n=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var i in e)if("default"!==i&&{}.hasOwnProperty.call(e,i)){var a=n?Object.getOwnPropertyDescriptor(e,i):null;a&&(a.get||a.set)?Object.defineProperty(s,i,a):s[i]=e[i]}return s.default=e,r&&r.set(e,s),s}(r(8737));function o(e){if("function"!=typeof WeakMap)return null;var t=new WeakMap,r=new WeakMap;return(o=function(e){return e?r:t})(e)}const l=new RegExp(`([${a.punctuationRegexString}])`,"g")},9803:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e,t,r=!0){const n=r?1:0;if((0,s.isEmpty)(e))return!1;const i=[];return(0,s.forEach)(e,(function(e){const t=e.index+e.match.length+n;i.push(t)})),(0,s.includes)(i,t)};var s=r(92819)},61403:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.filterIndices=t.default=void 0,t.getIndicesByWord=o,t.sortIndices=t.getIndicesByWordListSorted=t.getIndicesByWordList=void 0;var s,n=r(92819),i=(s=r(47793))&&s.__esModule?s:{default:s},a=r(30043);function o(e,t){let r=0;const s=e.length;let n;const i=[];for(;(n=t.indexOf(e,r))>-1;){const o=(0,a.characterInBoundary)(t[n-1])||0===n,l=(0,a.characterInBoundary)(t[n+s])||t.length===n+s;o&&l&&i.push({index:n,match:e}),r=n+s}return i}const l=function(e,t){let r=[];return(0,n.forEach)(e,(function(e){e=(0,i.default)(e),(0,a.isWordInSentence)(e,t)&&(r=r.concat(o(e,t)))})),r};t.getIndicesByWordList=l;const u=function(e){return e.sort((function(e,t){return e.index-t.index}))};t.sortIndices=u;const c=function(e){e=u(e);const t=[];for(let r=0;rt.index?1:0})),r};t.getIndicesByWordListSorted=d,t.default={getIndicesByWord:o,getIndicesByWordList:l,filterIndices:c,sortIndices:u,getIndicesByWordListSorted:d}},48024:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.deConstructAnchor=t.collectMarkingsInSentence=void 0,t.markWordsInASentence=g,t.markWordsInSentences=function(e,t,r,n){let i=[],a=[];return t.forEach((function(t){i=(0,s.default)(t,e,r,n).matches,i.length>0&&(a=a.concat(g(t,i,n)))})),a},t.reConstructAnchor=void 0;var s=c(r(7407)),n=c(r(2008)),i=c(r(5262)),a=c(r(41054)),o=r(92819),l=c(r(30341)),u=r(37361);function c(e){return e&&e.__esModule?e:{default:e}}const d=/(]+>)([^]*?)(<\/a>)/,h=function(e){const[,t,r]=e.match(d);return{openTag:t,content:r}};t.deConstructAnchor=h;const f=function(e,t){return`${e}${t}`};t.reConstructAnchor=f;const p=function(e,t,r){const s=[];t.forEach((e=>{const t=e.match(u.SINGLE_QUOTES_REGEX);t?u.SINGLE_QUOTES_ARRAY.forEach((r=>{t.forEach((t=>{s.push((0,o.escapeRegExp)(e.replace(new RegExp(t,"g"),r)))}))})):s.push((0,o.escapeRegExp)(e))}));const a=r?(0,n.default)(s,!0):(0,n.default)(s),{anchors:c,markedAnchors:d}=function(e,t){const r=(0,l.default)(e),s=r.map((e=>{const{openTag:r,content:s}=h(e),n=s.replace(t,(e=>(0,i.default)(e)));return f(r,n)}));return{anchors:r,markedAnchors:s}}(e,a);let p=e.replace(a,(function(e){return(0,i.default)(e)}));if(c.length>0){const e=(0,l.default)(p);for(let t=0;t","ig")," ")};function g(e,t,r){return[new a.default({original:e,marked:p(e,t,r)})]}t.collectMarkingsInSentence=p},30043:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.isWordInSentence=t.default=t.characterInBoundary=void 0;var s=a(r(35468)),n=r(92819),i=a(r(28875));function a(e){return e&&e.__esModule?e:{default:e}}const o=(0,s.default)(),l=function(e){return(0,n.includes)(o,e)};t.characterInBoundary=l;const u=function(e,t){e=e.toLocaleLowerCase(),t=t.toLocaleLowerCase();const r=(0,i.default)((0,n.escapeRegExp)(e));let s=t.search(new RegExp(r,"ig"));if(-1===s)return!1;s>0&&(s+=1);const a=s+e.length,o=l(t[s-1])||0===s,u=l(t[a])||a===t.length;return o&&u};t.isWordInSentence=u,t.default={characterInBoundary:l,isWordInSentence:u}},7337:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var s,n=(s=r(16431))&&s.__esModule?s:{default:s};const i=/([\s\t\u00A0\u2013\u2014\u002d[\]]|#nbsp;)/;t.default=e=>{if(!e)return[];const t=e.split(i).filter((e=>""!==e));return(0,n.default)(t)}},58677:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"AbstractResearcher",{enumerable:!0,get:function(){return c.default}}),Object.defineProperty(t,"areWordsInSentence",{enumerable:!0,get:function(){return O.default}}),Object.defineProperty(t,"baseStemmer",{enumerable:!0,get:function(){return l.default}}),Object.defineProperty(t,"buildFormRule",{enumerable:!0,get:function(){return f.default}}),Object.defineProperty(t,"collectMarkingsInSentence",{enumerable:!0,get:function(){return G.collectMarkingsInSentence}}),Object.defineProperty(t,"countMetaDescriptionLength",{enumerable:!0,get:function(){return R.default}}),Object.defineProperty(t,"createRegexFromArray",{enumerable:!0,get:function(){return i.default}}),Object.defineProperty(t,"createRulesFromArrays",{enumerable:!0,get:function(){return p.default}}),Object.defineProperty(t,"createSingleRuleFromArray",{enumerable:!0,get:function(){return p.createSingleRuleFromArray}}),Object.defineProperty(t,"directPrecedenceException",{enumerable:!0,get:function(){return m.default}}),t.exceptionListHelpers=void 0,Object.defineProperty(t,"findMatchingEndingInArray",{enumerable:!0,get:function(){return E.default}}),Object.defineProperty(t,"findWordFormsInString",{enumerable:!0,get:function(){return B.findWordFormsInString}}),Object.defineProperty(t,"flattenSortLength",{enumerable:!0,get:function(){return d.default}}),Object.defineProperty(t,"getClauses",{enumerable:!0,get:function(){return S.default}}),Object.defineProperty(t,"getClausesSplitOnStopWords",{enumerable:!0,get:function(){return I.default}}),Object.defineProperty(t,"getFieldsToMark",{enumerable:!0,get:function(){return M.getFieldsToMark}}),Object.defineProperty(t,"getLanguage",{enumerable:!0,get:function(){return k.default}}),Object.defineProperty(t,"getSentences",{enumerable:!0,get:function(){return L.default}}),Object.defineProperty(t,"getWords",{enumerable:!0,get:function(){return u.default}}),t.helpers=void 0,Object.defineProperty(t,"imageInText",{enumerable:!0,get:function(){return a.default}}),Object.defineProperty(t,"indices",{enumerable:!0,get:function(){return h.default}}),Object.defineProperty(t,"markWordsInSentences",{enumerable:!0,get:function(){return G.markWordsInSentences}}),Object.defineProperty(t,"matchRegularParticiples",{enumerable:!0,get:function(){return g.default}}),Object.defineProperty(t,"mergeListItems",{enumerable:!0,get:function(){return U.mergeListItems}}),Object.defineProperty(t,"nonDirectPrecedenceException",{enumerable:!0,get:function(){return T.default}}),Object.defineProperty(t,"normalizeHTML",{enumerable:!0,get:function(){return P.default}}),Object.defineProperty(t,"normalizeSingle",{enumerable:!0,get:function(){return x.normalizeSingle}}),Object.defineProperty(t,"parseSynonyms",{enumerable:!0,get:function(){return F.default}}),Object.defineProperty(t,"precedenceException",{enumerable:!0,get:function(){return _.default}}),t.regexHelpers=void 0,Object.defineProperty(t,"removePunctuation",{enumerable:!0,get:function(){return w.default}}),Object.defineProperty(t,"replaceDiacritics",{enumerable:!0,get:function(){return s.default}}),t.researches=void 0,Object.defineProperty(t,"sanitizeString",{enumerable:!0,get:function(){return N.default}}),t.stemHelpers=void 0,Object.defineProperty(t,"stripBlockTagsAtStartEnd",{enumerable:!0,get:function(){return C.stripBlockTagsAtStartEnd}}),Object.defineProperty(t,"stripHTMLTags",{enumerable:!0,get:function(){return C.stripFullTags}}),Object.defineProperty(t,"stripSpaces",{enumerable:!0,get:function(){return o.default}}),Object.defineProperty(t,"transliterate",{enumerable:!0,get:function(){return n.default}}),Object.defineProperty(t,"unifyAllSpaces",{enumerable:!0,get:function(){return D.unifyAllSpaces}}),t.values=void 0;var s=q(r(74169)),n=q(r(36064)),i=q(r(2008)),a=q(r(61440)),o=q(r(47793)),l=q(r(42992)),u=q(r(1105)),c=q(r(67986)),d=q(r(44949)),h=q(r(61403)),f=q(r(19605)),p=W(r(53371)),g=q(r(2329)),m=q(r(23107)),_=q(r(14456)),T=q(r(95580)),E=q(r(30259)),y=W(r(64396));t.regexHelpers=y;var A=W(r(74252));t.exceptionListHelpers=A;var v=W(r(74694));t.stemHelpers=v;var b=W(r(62390));t.values=b;var O=q(r(11475)),S=q(r(43527)),I=q(r(80053)),C=r(62240),N=q(r(86697)),D=r(81831),w=q(r(8737)),P=q(r(88567)),R=q(r(40231)),k=q(r(67404)),L=q(r(9862)),M=r(29368),x=r(37361),F=q(r(73481)),U=r(10840),B=r(36478),G=r(48024),H=W(r(29866));t.helpers=H;var j=W(r(42299));function $(e){if("function"!=typeof WeakMap)return null;var t=new WeakMap,r=new WeakMap;return($=function(e){return e?r:t})(e)}function W(e,t){if(!t&&e&&e.__esModule)return e;if(null===e||"object"!=typeof e&&"function"!=typeof e)return{default:e};var r=$(t);if(r&&r.has(e))return r.get(e);var s={__proto__:null},n=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var i in e)if("default"!==i&&{}.hasOwnProperty.call(e,i)){var a=n?Object.getOwnPropertyDescriptor(e,i):null;a&&(a.get||a.set)?Object.defineProperty(s,i,a):s[i]=e[i]}return s.default=e,r&&r.set(e,s),s}function q(e){return e&&e.__esModule?e:{default:e}}t.researches=j},24081:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0,t.default={wordLength:10}},60645:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e,t,s){const n=e.wordLength,i=s.frequencyList.list;return!((t=t.toLowerCase()).length<=n)&&(!i.includes(t)&&(!r.test(t)||(t=t.replace(r,""),!i.includes(t))))};const r=new RegExp("(en|e|s)$")},31946:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0,t.default=["A.D.","Adm.","Adv.","B.C.","Br.","Brig.","Cmrd.","Col.","Cpl.","Cpt.","Dr.","Esq.","Fr.","Gen.","Gov.","Hon.","Jr.","Lieut.","Lt.","Maj.","Mr.","Mrs.","Ms.","Msgr.","Mx.","No.","Pfc.","Pr.","Prof.","Pvt.","Rep.","Reps.","Rev.","Rt. Hon.","Sen.","Sens.","Sgt.","Sps.","Sr.","St.","vs.","i.e.","e.g.","viz.","Mt."]},89456:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.nonNouns=t.filteredAtEnding=t.filteredAtBeginningAndEnding=t.filteredAnywhere=t.default=t.cannotDirectlyPrecedePassiveParticiple=t.cannotBeBetweenPassiveAuxiliaryAndParticiple=t.all=void 0;var s,n=r(64998),i=r(27350),a=(s=r(85336))&&s.__esModule?s:{default:s};const o=["the","an","a"],l=["one","two","three","four","five","six","seven","eight","nine","ten","eleven","twelve","thirteen","fourteen","fifteen","sixteen","seventeen","eighteen","nineteen","twenty","hundred","hundreds","thousand","thousands","million","millions","billion","billions"],u=["first","second","third","fourth","fifth","sixth","seventh","eighth","ninth","tenth","eleventh","twelfth","thirteenth","fourteenth","fifteenth","sixteenth","seventeenth","eighteenth","nineteenth","twentieth"],c=["i","you","he","she","it","we","they"],d=["me","him","us","them"],h=["this","that","these","those"],f=["my","your","his","her","its","their","our","mine","yours","hers","theirs","ours"],p=["all","some","many","lot","lots","ton","tons","bit","no","every","enough","little","much","more","most","plenty","several","few","fewer","kind","kinds"],g=["myself","yourself","himself","herself","itself","oneself","ourselves","yourselves","themselves"],m=["none","nobody","everyone","everybody","someone","somebody","anyone","anybody","nothing","everything","something","anything","each","other","whatever","whichever","whoever","whomever","whomsoever","whosoever","others","neither","both","either","any","such"],_=["one's","nobody's","everyone's","everybody's","someone's","somebody's","anyone's","anybody's","nothing's","everything's","something's","anything's","whoever's","others'","other's","another's","neither's","either's"],T=["which","what","whose"],E=["who","whom"],y=["where","how","why","whether","wherever","whyever","wheresoever","whensoever","howsoever","whysoever","whatsoever","whereso","whomso","whenso","howso","whyso","whoso","whatso"],A=["therefor","therein","hereby","hereto","wherein","therewith","herewith","wherewith","thereby"],v=["there","here","whither","thither","hither","whence","thence"],b=["always","once","twice","thrice"],O=["can","cannot","can't","could","couldn't","could've","dare","dares","dared","do","don't","does","doesn't","did","didn't","done","have","haven't","had","hadn't","has","hasn't","i've","you've","we've","they've","i'd","you'd","he'd","she'd","it'd","we'd","they'd","would","wouldn't","would've","may","might","must","need","needn't","needs","ought","shall","shalln't","shan't","should","shouldn't","will","won't","i'll","you'll","he'll","she'll","it'll","we'll","they'll","there's","there're","there'll","here's","here're","there'll"],S=["appear","appears","appeared","become","becomes","became","come","comes","came","keep","keeps","kept","remain","remains","remained","stay","stays","stayed","turn","turns","turned"],I=["doing","daring","having","appearing","becoming","coming","keeping","remaining","staying","saying","asking","stating","seeming","letting","making","setting","showing","putting","adding","going","using","trying","containing"],C=["in","from","with","under","throughout","atop","for","on","of","to","aboard","about","above","abreast","absent","across","adjacent","after","against","along","alongside","amid","mid","among","apropos","apud","around","as","astride","at","ontop","afore","tofore","behind","ahind","below","ablow","beneath","neath","beside","between","atween","beyond","ayond","by","chez","circa","spite","down","except","into","less","like","minus","near","nearer","nearest","anear","notwithstanding","off","onto","opposite","out","outen","over","past","per","pre","qua","sans","sithence","through","thru","truout","toward","underneath","up","upon","upside","versus","via","vis-à-vis","without","ago","apart","aside","aslant","away","withal","towards","amidst","amongst","midst","whilst"],N=["back","within","forward","backward","ahead"],D=["and","or","and/or","yet"],w=["sooner","just","only"],P=["if","even"],R=["say","says","said","claimed","ask","asks","asked","stated","explain","explains","explained","think","thinks","talks","talked","announces","announced","tells","told","discusses","discussed","suggests","suggested","understands","understood"],k=["again","definitely","eternally","expressively","instead","expressly","immediately","including","instantly","namely","naturally","next","notably","now","nowadays","ordinarily","positively","truly","ultimately","uniquely","usually","almost","maybe","probably","granted","initially","too","actually","already","e.g","i.e","often","regularly","simply","optionally","perhaps","sometimes","likely","never","ever","else","inasmuch","provided","currently","incidentally","elsewhere","particular","recently","relatively","f.i","clearly","apparently"],L=["highly","very","really","extremely","absolutely","completely","totally","utterly","quite","somewhat","seriously","fairly","fully","amazingly"],M=["seem","seems","seemed","let","let's","lets","make","makes","made","want","showed","shown","go","goes","went","gone","take","takes","took","taken","put","puts","use","used","try","tries","tried","mean","means","meant","called","based","add","adds","added","contain","contains","contained","consist","consists","consisted","ensure","ensures","ensured"],x=["new","newer","newest","old","older","oldest","previous","good","well","better","best","big","bigger","biggest","easy","easier","easiest","fast","faster","fastest","far","hard","harder","hardest","least","own","large","larger","largest","long","longer","longest","low","lower","lowest","high","higher","highest","regular","simple","simpler","simplest","small","smaller","smallest","tiny","tinier","tiniest","short","shorter","shortest","main","actual","nice","nicer","nicest","real","same","able","certain","usual","so-called","mainly","mostly","recent","anymore","complete","lately","possible","commonly","constantly","continually","directly","easily","nearly","slightly","somewhere","estimated","latest","different","similar","widely","bad","worse","worst","great","specific","available","average","awful","awesome","basic","beautiful","busy","current","entire","everywhere","important","major","multiple","normal","necessary","obvious","partly","special","last","early","earlier","earliest","young","younger","youngest"],F=["oh","wow","tut-tut","tsk-tsk","ugh","whew","phew","yeah","yea","shh","oops","ouch","aha","yikes"],U=["tbs","tbsp","spk","lb","qt","pk","bu","oz","pt","mod","doz","hr","f.g","ml","dl","cl","l","mg","g","kg","quart"],B=["seconds","minute","minutes","hour","hours","day","days","week","weeks","month","months","year","years","today","tomorrow","yesterday"],G=["thing","things","way","ways","matter","case","likelihood","ones","piece","pieces","stuff","times","part","parts","percent","instance","instances","aspect","aspects","item","items","idea","theme","person","instance","instances","detail","details","factor","factors","difference","differences"],H=["not","yes","sure","top","bottom","ok","okay","amen","aka","etc","etcetera","sorry","please"],j=["jr","sr"],$=t.filteredAtEnding=(0,a.default)([].concat(u,I,x)),W=t.filteredAtBeginningAndEnding=(0,a.default)([].concat(o,C,D,h,L,p,f)),q=t.filteredAnywhere=(0,a.default)([].concat(i.singleWords,b,c,d,g,F,l,n.all,O,S,R,M,m,w,P,T,E,y,v,H,N,A,U,B,G)),V=t.cannotDirectlyPrecedePassiveParticiple=(0,a.default)([].concat(o,C,h,f,u,I,p)),K=t.cannotBeBetweenPassiveAuxiliaryAndParticiple=(0,a.default)([].concat(O,S,R,M)),Y=(t.nonNouns=(0,a.default)([].concat(o,l,u,h,f,g,c,d,p,m,I,_,T,E,y,A,v,b,N,n.all,O,S,C,D,w,P,R,i.singleWords,k,L,M,F,x,U,H,j)),t.all=(0,a.default)([].concat(o,l,u,h,f,g,c,d,p,m,I,_,T,E,y,A,v,b,N,n.all,O,S,C,D,w,P,R,i.singleWords,k,L,M,F,x,U,G,H,B,["ms","mss","mrs","mr","dr","prof"],j)));t.default={filteredAtEnding:$,filteredAtBeginningAndEnding:W,filteredAnywhere:q,cannotDirectlyPrecedePassiveParticiple:V,cannotBeBetweenPassiveAuxiliaryAndParticiple:K,all:Y}},64998:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.other=t.negatedFormsOfToBe=t.formsOfToGet=t.formsOfToBe=t.default=t.all=void 0;const r=t.formsOfToBe=["am","is","are","was","were","been","be","she's","he's","it's","i'm","we're","they're","you're","that's","being"],s=t.negatedFormsOfToBe=["isn't","weren't","wasn't","aren't"],n=t.formsOfToGet=["get","gets","got","gotten","getting"],i=t.other=["having","what's"],a=t.all=r.concat(s,n,i);t.default=a},89597:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0,t.default=["arisen","awoken","reawoken","babysat","backslid","backslidden","beat","beaten","become","begun","bent","unbent","bet","bid","outbid","rebid","underbid","overbid","bidden","bitten","blown","bought","overbought","bound","unbound","rebound","broadcast","rebroadcast","broken","brought","browbeat","browbeaten","built","prebuilt","rebuilt","overbuilt","burnt","burst","bust","cast","miscast","recast","caught","chosen","clung","come","overcome","cost","crept","cut","undercut","recut","daydreamt","dealt","misdealt","redealt","disproven","done","predone","outdone","misdone","redone","overdone","undone","drawn","outdrawn","redrawn","overdrawn","dreamt","driven","outdriven","drunk","outdrunk","overdrunk","dug","dwelt","eaten","overeaten","fallen","felt","fit","refit","retrofit","flown","outflown","flung","forbidden","forecast","foregone","foreseen","foretold","forgiven","forgotten","forsaken","fought","outfought","found","frostbitten","frozen","unfrozen","given","gone","undergone","gotten","ground","reground","grown","outgrown","regrown","had","handwritten","heard","reheard","misheard","overheard","held","hewn","hidden","unhidden","hit","hung","rehung","overhung","unhung","hurt","inlaid","input","interwound","interwoven","jerry-built","kept","knelt","knit","reknit","unknit","known","laid","mislaid","relaid","overlaid","lain","underlain","leant","leapt","outleapt","learnt","unlearnt","relearnt","mislearnt","left","lent","let","lip-read","lit","relit","lost","made","premade","remade","meant","met","mown","offset","paid","prepaid","repaid","overpaid","partaken","proofread","proven","put","quick-frozen","quit","read","misread","reread","retread","rewaken","rid","ridden","outridden","overridden","risen","roughcast","run","outrun","rerun","overrun","rung","said","sand-cast","sat","outsat","sawn","seen","overseen","sent","resent","set","preset","reset","misset","sewn","resewn","oversewn","unsewn","shaken","shat","shaven","shit","shone","outshone","shorn","shot","outshot","overshot","shown","shrunk","preshrunk","shut","sight-read","slain","slept","outslept","overslept","slid","slit","slung","unslung","slunk","smelt","outsmelt","snuck","sold","undersold","presold","outsold","resold","oversold","sought","sown","spat","spelt","misspelt","spent","underspent","outspent","misspent","overspent","spilt","overspilt","spit","split","spoilt","spoken","outspoken","misspoken","overspoken","spread","sprung","spun","unspun","stolen","stood","understood","misunderstood","strewn","stricken","stridden","striven","struck","strung","unstrung","stuck","unstuck","stung","stunk","sublet","sunburnt","sung","outsung","sunk","sweat","swept","swollen","sworn","outsworn","swum","outswum","swung","taken","undertaken","mistaken","retaken","overtaken","taught","mistaught","retaught","telecast","test-driven","test-flown","thought","outthought","rethought","overthought","thrown","outthrown","overthrown","thrust","told","retold","torn","retorn","trod","trodden","typecast","typeset","upheld","upset","waylaid","wept","wet","rewet","withdrawn","withheld","withstood","woken","won","rewon","worn","reworn","wound","rewound","overwound","unwound","woven","rewoven","unwoven","written","typewritten","underwritten","outwritten","miswritten","rewritten","overwritten","wrung"]},74016:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.regularParticiplesRegex=void 0,t.regularParticiplesRegex=/\w+ed($|[ \n\r\t.,'()"+\-;!?:/»«‹›<>])/gi},27350:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.singleWords=t.multipleWords=t.default=t.allWords=void 0;const r=t.singleWords=["accordingly","additionally","afterward","afterwards","albeit","also","although","altogether","another","basically","because","before","besides","but","certainly","chiefly","comparatively","concurrently","consequently","contrarily","conversely","correspondingly","despite","doubtedly","during","e.g.","earlier","emphatically","equally","especially","eventually","evidently","explicitly","finally","firstly","following","formerly","forthwith","fourthly","further","furthermore","generally","hence","henceforth","however","i.e.","identically","indeed","initially","instead","last","lastly","later","lest","likewise","markedly","meanwhile","moreover","nevertheless","nonetheless","nor","notwithstanding","obviously","occasionally","otherwise","overall","particularly","presently","previously","rather","regardless","secondly","shortly","significantly","similarly","simultaneously","since","so","soon","specifically","still","straightaway","subsequently","surely","surprisingly","than","then","thereafter","therefore","thereupon","thirdly","though","thus","till","undeniably","undoubtedly","unless","unlike","unquestionably","until","when","whenever","whereas","while","whether","if","actually","anyway","anyways","anyhow","mostly","namely","including","suddenly"],s=t.multipleWords=["above all","after all","after that","all in all","all of a sudden","all things considered","analogous to","although this may be true","analogous to","another key point","as a matter of fact","as a result","as an illustration","as can be seen","as has been noted","as I have noted","as I have said","as I have shown","as long as","as much as","as opposed to","as shown above","as soon as","as well as","at any rate","at first","at last","at least","at length","at the present time","at the same time","at this instant","at this point","at this time","balanced against","being that","by all means","by and large","by comparison","by the same token","by the time","compared to","be that as it may","coupled with","different from","due to","equally important","even if","even more","even so","even though","first thing to remember","for example","for fear that","for instance","for one thing","for that reason","for the most part","for the purpose of","for the same reason","for this purpose","for this reason","from time to time","given that","given these points","important to realize","in a word","in addition","in another case","in any case","in any event","in brief","in case","in conclusion","in contrast","in detail","in due time","in effect","in either case","either way","in essence","in fact","in general","in light of","in like fashion","in like manner","in order that","in order to","in other words","in particular","in reality","in short","in similar fashion","in spite of","in sum","in summary","in that case","in the event that","in the final analysis","in the first place","in the fourth place","in the hope that","in the light of","in the long run","in the meantime","in the same fashion","in the same way","in the second place","in the third place","in this case","in this situation","in time","in truth","in view of","inasmuch as","most compelling evidence","most important","must be remembered","not only","not to mention","note that","now that","of course","on account of","on balance","on condition that","on one hand","on the condition that","on the contrary","on the negative side","on the other hand","on the positive side","on the whole","on this occasion","all at once","once in a while","only if","owing to","point often overlooked","prior to","provided that","seeing that","so as to","so far","so long as","so that","sooner or later","such as","summing up","take the case of","that is","that is to say","then again","this time","to be sure","to begin with","to clarify","to conclude","to demonstrate","to emphasize","to enumerate","to explain","to illustrate","to list","to point out","to put it another way","to put it differently","to repeat","to rephrase it","to say nothing of","to sum up","to summarize","to that end","to the end that","to this end","together with","under those circumstances","until now","up against","up to the present time","vis a vis","what's more","what is more","while it may be true","while this may be true","with attention to","with the result that","with this in mind","with this intention","with this purpose in mind","without a doubt","without delay","without doubt","without reservation","according to","no sooner","at most","at the most","from now on"],n=t.allWords=r.concat(s);t.default=n},92518:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0,t.default={wordLength:7,doesUpperCaseDecreaseComplexity:!0}},46882:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e,t,r){const i=e.wordLength,a=r.frequencyList.list,o=e.doesUpperCaseDecreaseComplexity;if(t.length<=i)return!1;if(o&&t[0].toLowerCase()!==t[0])return!1;if(a.includes(t))return!1;if(r){const e=(0,s.default)(t,(0,n.default)(r.nouns.regexNoun.singularize));return!a.includes(e)}return!0};var s=i(r(19605)),n=i(r(53371));function i(e){return e&&e.__esModule?e:{default:e}}},45072:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0,t.default={wordLength:7}},66676:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e,t,i){const a=e.wordLength,o=i.frequencyList.list;return!(t.length<=a)&&(t[0].toLowerCase()===t[0]&&(!o.includes(t)&&(s.test(t)?(t=t.replace(n,""),!o.includes(t)):!r.test(t)||(t=t.replace(n,""),!o.includes(t)))))};const r=new RegExp("[aeiuoyáéíóúñ](s)$"),s=new RegExp("[bcdfghjklmnpqrstvwxzñ](es)$"),n=new RegExp("(s|es)$")},20712:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0,t.default={wordLength:9}},8920:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e,t,r){const i=e.wordLength,a=r.frequencyList.list;if(t=(0,s.normalizeSingle)(t),n.test(t)&&(t=t.replace(n,"")),t.length<=i)return!1;if(a.includes(t))return!1;if(t[0].toLowerCase()===t[0]){const e=new RegExp(r.suffixGroupsComplexity.standardSuffixesWithSplural),s=new RegExp(r.suffixGroupsComplexity.standardSuffixesWithXplural),n=r.suffixGroupsComplexity.irregularPluralSingularSuffixes,i=new RegExp(n[0]);return e.test(t)||s.test(t)?t=t.substring(0,t.length-1):i.test(t)&&(t=t.replace(i,n[1])),!a.includes(t)}return!1};var s=r(37361);const n=new RegExp("^(c'|d'|l'|s')")},49581:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0,t.default={recommendedMaximumLength:60,maximumLength:80}},40027:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e,t){const r=(0,a.default)(e),s=t.getResearch("morphology"),n=t.getHelper("matchWordCustomHelper");return l(r,s,e.getLocale(),n)};var s=o(r(15770)),n=r(36478),i=r(92819),a=o(r(92017));function o(e){return e&&e.__esModule?e:{default:e}}const l=function(e,t,r,a){const o={noAlt:0,withAlt:0,withAltKeyword:0,withAltNonKeyword:0};return e.forEach((e=>{const l=(0,s.default)(e);""!==l?(0,i.isEmpty)(t.keyphraseForms)?o.withAlt++:(0,n.findTopicFormsInString)(t,l,!0,r,a).percentWordMatches>=50?o.withAltKeyword++:o.withAltNonKeyword++:o.noAlt++})),o}},99447:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e,t){const r=(0,n.default)(e.getTree());return(0,s.default)(r,t)};var s=i(r(91916)),n=i(r(54241));function i(e){return e&&e.__esModule?e:{default:e}}},14117:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var s=r(92819),n=c(r(75118)),i=r(36478),a=r(66382),o=c(r(83927)),l=c(r(1105)),u=r(10152);function c(e){return e&&e.__esModule?e:{default:e}}let d=[];const h=function(e,t){return 0===t||0===d.length?t:function(e){e=e.toLocaleLowerCase();let t=(0,l.default)(e.toLocaleLowerCase(),u.WORD_BOUNDARY_WITH_HYPHEN);return t=(0,s.filter)(t,(function(e){return!(0,s.includes)(d,e.trim().toLocaleLowerCase())})),(0,s.isEmpty)(t)}(e.substring(0,t))?0:t};t.default=function(e,t){d=t.getConfig("functionWords");let r=(0,s.escapeRegExp)(e.getKeyword());const l=e.getTitle(),u=e.getLocale();let c={exactMatchFound:!1,allWordsFound:!1,position:-1,exactMatchKeyphrase:!1};const f=(0,o.default)(r);f.exactMatchRequested&&(r=f.keyphrase,c.exactMatchKeyphrase=!0);const p=t.getConfig("prefixedFunctionWordsRegex"),g=(0,n.default)(l,r,u,!1);return g.count>0&&!p?(c.exactMatchFound=!0,c.allWordsFound=!0,c.position=h(l,g.position),c):(c=function(e,t,r,o,l){const u=e.getTitle(),c=e.getLocale(),d=t.getResearch("morphology"),f=(0,i.findTopicFormsInString)(d,u,!1,c,!1);if(100===f.percentWordMatches){if(l){const{exactMatchFound:e,position:t}=function(e,t,r,i,o,l){const u=(0,s.uniq)(e.matches.map((e=>(0,a.stemPrefixedFunctionWords)(e,i).prefix)).concat([""])),c=t.split(" ").map((e=>u.map((t=>t+e)).filter((e=>(0,n.default)(o,e,l,!1).count>0))));if(!c.find((e=>0===e.length)))for(const e of function*(...e){const t=e.map((e=>e.length)),r=Array(e.length).fill(0);for(;;){yield r.map(((t,r)=>e[r][t]));let s=e.length-1;for(;s>=0&&++r[s]===t[s];)r[s]=0,s--;if(s<0)break}}(...c)){const t=Array.isArray(e)?e.join(" "):e,s=(0,n.default)(o,t,l,!1);if(s.count>0){r.exactMatchFound=!0,r.position=h(o,s.position);break}}return 0===e.position&&(r.position=0),r}(f,r,o,l,u,c);o={...o,exactMatchFound:e,position:t}}o.allWordsFound=!0}return o}(e,t,r,c,p),c)}},86232:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e,t){let r=t.getResearch("getParagraphs");const a=e.getTree();r=r.filter((e=>{const t=e.getParentNode(a);return!(e.isImplicit&&t&&"figcaption"===t.name)})),r=r.filter((e=>!(e.childNodes&&e.childNodes[0]&&(0,i.createShortcodeTagsRegex)(["caption","gallery","embed","playlist"]).test(e.childNodes[0].value))));const o=r[0],l=t.getResearch("morphology"),u=t.getHelper("matchWordCustomHelper"),c=e.getLocale(),d=o&&o.sourceCodeLocation.startOffset,h=e._attributes.wpBlocks,f=h&&h.filter((e=>(0,s.inRange)(d,e.startOffset,e.endOffset)))[0],p=null==o?void 0:o.getParentNode(a),g={foundInOneSentence:!1,foundInParagraph:!1,keyphraseOrSynonym:"",introduction:o,parentBlock:f||p};if((0,s.isEmpty)(o))return g;const m=o.sentences.map((e=>e.text));if(!(0,s.isEmpty)(m)){const e=m.map((e=>(0,n.findTopicFormsInString)(l,e,!0,c,u))).find((e=>100===e.percentWordMatches));if(e)return g.foundInOneSentence=!0,g.foundInParagraph=!0,g.keyphraseOrSynonym=e.keyphraseOrSynonym,g;const t=(0,n.findTopicFormsInString)(l,o.innerText(),!0,c,u);if(100===t.percentWordMatches)return g.foundInParagraph=!0,g.keyphraseOrSynonym=t.keyphraseOrSynonym,g}return g};var s=r(92819),n=r(36478),i=r(29866)},69564:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e,t){const r=t.getHelper("matchTransitionWordsHelper"),s=t.getConfig("transitionWords"),i=t.getConfig("twoPartTransitionWords"),a=t.getHelper("memoizedTokenizer");let l=e.getText();l=(0,o.default)(l),l=(0,u.filterShortcodesFromHTML)(l,e._attributes&&e._attributes.shortcodes);const c=(0,n.default)(l,a),d=f(c,s,i,r);return{totalSentences:c.length,sentenceResults:d,transitionWordSentences:d.length}};var s=c(r(55508)),n=c(r(9862)),i=r(37361),a=r(30043),o=c(r(96908)),l=r(92819),u=r(29866);function c(e){return e&&e.__esModule?e:{default:e}}let d=null,h="";const f=function(e,t,r,n){const o=[];return e.forEach((e=>{if(r){const t=function(e,t){e=(0,i.normalizeSingle)(e);const r=function(e){const t=(0,l.flattenDeep)(e).join("");return h===t&&null!==d||(h=t,d=(0,s.default)(e)),d}(t);return e.match(r)}(e,r);if(null!==t)return void o.push({sentence:e,transitionWords:t})}const u=n?n(e,t):function(e,t){return e=(0,i.normalizeSingle)(e),t.filter((t=>(0,a.isWordInSentence)(t,e)))}(e,t);0!==u.length&&o.push({sentence:e,transitionWords:u})})),o}},3479:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e,t){const r=t.getConfig("functionWords"),o=t.getHelper("getWordsCustomHelper"),l=e.getKeyword();if((0,i.default)(l).exactMatchRequested)return!1;let u=o?o(l):(0,n.default)(l,a.WORD_BOUNDARY_WITH_HYPHEN);return u=(0,s.filter)(u,(function(e){return!(0,s.includes)(r,e.trim().toLocaleLowerCase())})),(0,s.isEmpty)(u)};var s=r(92819),n=o(r(1105)),i=o(r(83927)),a=r(10152);function o(e){return e&&e.__esModule?e:{default:e}}},63216:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e,t){f=t.getConfig("functionWords");const r=t.getConfig("areHyphensWordBoundaries"),h={anchorsWithKeyphrase:[],anchorsWithKeyphraseCount:0};if(""===e.getText())return h;const p=e.getKeyword();if(""===p)return h;const g=(0,l.default)(e.getSynonyms());g.push(p);let m=e.getTree().findAll((e=>"a"===e.name));if(m=function(e,t){const r=e.map((function(e){const r=e.attributes.href;return!!r&&function(e,t){return Boolean(c.default.areEqual(e,t)||c.default.isRelativeFragmentURL(e))}(r,t)}));return e.filter(((e,t)=>!r[t]))}(m,e.getPermalink()),0===m.length)return h;const _=e.getLocale(),T=t.getResearch("morphology"),E={matchWordCustomHelper:t.getHelper("matchWordCustomHelper"),getWordsCustomHelper:t.getHelper("getWordsCustomHelper")};if(m=function(e,t,r,s){const n=e.map((function(e){const n=e.innerText();return 100===(0,i.findTopicFormsInString)(t,n,!0,r,s).percentWordMatches}));return e.filter(((e,t)=>n[t]))}(m,T,_,E.matchWordCustomHelper),0===m.length)return h;return m=function(e,t,r,i,l,u){const c=i.matchWordCustomHelper,h=i.getWordsCustomHelper,p=[(0,s.flatten)(t.keyphraseForms)];t.synonymsForms.forEach((e=>p.push((0,s.flatten)(e))));const g=[];return e.forEach((function(e){const t=e.innerText();let i;i=h?(0,s.uniq)(h(t)):u?(0,s.uniq)((0,a.default)(t,d.WORD_BOUNDARY_WITH_HYPHEN)):(0,s.uniq)((0,a.default)(t,d.WORD_BOUNDARY_WITHOUT_HYPHEN));const m=(0,n.default)(i,f);m.length>0&&(i=m),l.forEach((e=>{e.exactMatchRequested&&i.every((t=>e.keyphrase.includes(t)))&&g.push(!0)}));for(let e=0;e(0,o.default)(e,t,r,c).count>0))){g.push(!0);break}}})),e.filter(((e,t)=>g[t]))}(m,T,_,E,g.map((e=>(0,u.default)(e))),r),{anchorsWithKeyphrase:m,anchorsWithKeyphraseCount:m.length}};var s=r(92819),n=h(r(49325)),i=r(36478),a=h(r(1105)),o=h(r(7407)),l=h(r(73481)),u=h(r(83927)),c=h(r(20917)),d=r(10152);function h(e){return e&&e.__esModule?e:{default:e}}let f=[]},82163:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.DIFFICULTY=void 0,t.default=function(e,t){const r=t.getConfig("syllables"),l=t.getHelper("memoizedTokenizer"),d=function(e){return e.getConfig("fleschReadingEaseScores")||{borders:{veryEasy:90,easy:80,fairlyEasy:70,okay:60,fairlyDifficult:50,difficult:30,veryDifficult:0},scores:{veryEasy:9,easy:9,fairlyEasy:9,okay:9,fairlyDifficult:6,difficult:3,veryDifficult:3}}}(t);let h=e.getText();if(""===h)return{score:-1,difficulty:c.NO_DATA};h=(0,s.default)(h);const f=(0,n.default)(h,l),p=(0,i.default)(h);if(f<1||p<=10)return{score:-1,difficulty:c.NO_DATA};const g=(0,a.default)(h,r),m={numberOfSentences:f,numberOfWords:p,numberOfSyllables:g,averageWordsPerSentence:u(p,f),syllablesPer100Words:g*(100/p)},_=t.getHelper("fleschReadingScore"),T=(0,o.clamp)(_(m),0,100),E=function(e,t){return e>=t.borders.veryEasy?c.VERY_EASY:(0,o.inRange)(e,t.borders.easy,t.borders.veryEasy)?c.EASY:(0,o.inRange)(e,t.borders.fairlyEasy,t.borders.easy)?c.FAIRLY_EASY:(0,o.inRange)(e,t.borders.okay,t.borders.fairlyEasy)?c.OKAY:(0,o.inRange)(e,t.borders.fairlyDifficult,t.borders.okay)?c.FAIRLY_DIFFICULT:(0,o.inRange)(e,t.borders.difficult,t.borders.fairlyDifficult)?c.DIFFICULT:c.VERY_DIFFICULT}(T,d);return{score:T,difficulty:E}};var s=l(r(86812)),n=l(r(55473)),i=l(r(33870)),a=l(r(39617)),o=r(92819);function l(e){return e&&e.__esModule?e:{default:e}}const u=function(e,t){return e/t},c=t.DIFFICULTY={NO_DATA:-1,VERY_EASY:0,EASY:1,FAIRLY_EASY:2,OKAY:3,FAIRLY_DIFFICULT:4,DIFFICULT:5,VERY_DIFFICULT:6}},78160:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=i,t.getKeywordDensity=function(e,t){return console.warn("This function is deprecated, use getKeyphraseDensity instead."),i(e,t)};var s,n=(s=r(60914))&&s.__esModule?s:{default:s};function i(e,t){const r=t.getHelper("getWordsCustomHelper");let s=0;return s=r?r(e.getText()).length:(0,n.default)(e).length,0===s?0:t.getResearch("getKeyphraseCount").count/s*100}},45095:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e){const t=(0,a.default)(e.getText()),r=(0,n.default)(t),o=e.getPermalink(),l={total:r.length,internalTotal:0,internalDofollow:0,internalNofollow:0,externalTotal:0,externalDofollow:0,externalNofollow:0,otherTotal:0,otherDofollow:0,otherNofollow:0};for(let e=0;e{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e){const t=(0,s.default)(e.getText());return(0,n.map)(t,i.default.getFromAnchorTag)};var s=a(r(30341)),n=r(92819),i=a(r(20917));function a(e){return e&&e.__esModule?e:{default:e}}},37228:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e,t){const r=e.getTree(),a=r.findAll((e=>"p"===e.name)),o=r.findAll((e=>i.test(e.name))),l=t.getConfig("centerClasses");return function(e,t){return e.filter((e=>!!e.attributes.class&&(0,s.intersection)([...e.attributes.class],t).length>0&&e.innerText().length>n))}(a.concat(o),l)};var s=r(92819);const n=50,i=/^h[1-6]$/},72619:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e,t){const r=e.getTree().findAll((e=>"p"===e.name)),n=[];return r.forEach((e=>{const r=t.getHelper("customCountLength"),i=e.sentences.map((e=>e.tokens)).flat(),a=r?r(e.innerText()):(0,s.getWordsFromTokens)(i,!1).length;a>0&&n.push({paragraph:e,paragraphLength:a})})),n};var s=r(60914)},41564:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e){let t=e.getTree().findAll((e=>"p"===e.name));return t=(0,s.reject)(t,(e=>0===e.sentences.length)),t=(0,s.reject)(t,(e=>e.childNodes.every((e=>"a"===e.name)))),t};var s=r(92819)},52364:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e,t){const r=t.getConfig("passiveConstructionType");return"periphrastic"===r?d(e,t):"morphological"===r?c(e,t):h(e,t)},t.getPeriphrasticPassives=t.getMorphologicalPassives=void 0;var s=u(r(9862)),n=r(62240),i=u(r(18812)),a=r(92819),o=u(r(96908)),l=r(29866);function u(e){return e&&e.__esModule?e:{default:e}}const c=function(e,t){const r=t.getHelper("isPassiveSentence");let u=e.getText();u=(0,o.default)(u),u=(0,l.filterShortcodesFromHTML)(u,e._attributes&&e._attributes.shortcodes);const c=t.getHelper("memoizedTokenizer"),d=(0,s.default)(u,c).map((function(e){return new i.default(e)})),h=d.length,f=[];return(0,a.forEach)(d,(function(e){const t=(0,n.stripFullTags)(e.getSentenceText()).toLocaleLowerCase();e.setPassive(r(t)),!0===e.isPassive()&&f.push(e.getSentenceText())})),{total:h,passives:f}};t.getMorphologicalPassives=c;const d=function(e,t){const r=t.getHelper("getClauses");let u=e.getText();u=(0,o.default)(u),u=(0,l.filterShortcodesFromHTML)(u,e._attributes&&e._attributes.shortcodes);const c=t.getHelper("memoizedTokenizer"),d=(0,s.default)(u,c).map((function(e){return new i.default(e)})),h=d.length,f=[];return(0,a.forEach)(d,(function(e){const t=(0,n.stripFullTags)(e.getSentenceText()).toLocaleLowerCase(),s=r(t);e.setClauses(s),e.isPassive()&&f.push(e.getSentenceText())})),{total:h,passives:f}};t.getPeriphrasticPassives=d;const h=function(e,t){const r=c(e,t),s=d(e,t).passives;return{total:r.total,passives:s.concat(r.passives)}}},18807:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var s=r(92819),n=r(85683),i=o(r(85476)),a=o(r(32879));function o(e){return e&&e.__esModule?e:{default:e}}t.default=function(e,t){const r=t.getConfig("functionWords"),o=t.getHelper("customGetStemmer"),l=o?o(t):t.getHelper("getStemmer")(t),u=t.getHelper("getWordsCustomHelper");let c=e.getText();c=(0,i.default)(c),c=(0,a.default)(c);const d=u?[]:(0,n.retrieveAbbreviations)(c),h=(0,n.getProminentWords)(c,d,l,r,u),f=(0,n.collapseProminentWordsOnStem)(h);return(0,n.sortProminentWords)(f),(0,s.take)((0,n.filterProminentWords)(f,5),20)}},58743:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var s=r(92819),n=c(r(33870)),i=r(85683),a=r(84285),o=c(r(42992)),l=c(r(85476)),u=c(r(32879));function c(e){return e&&e.__esModule?e:{default:e}}const d=function(e){return e=(0,l.default)(e),(0,u.default)(e)};t.default=function(e,t){const r=t.getConfig("functionWords"),l=t.getHelper("customGetStemmer"),u=l?l(t):t.getHelper("getStemmer")(t),c=t.getHelper("getWordsCustomHelper"),h=t.getHelper("customCountLength"),f=d(e.getText()),p=d(e.getDescription()),g=d(e.getTitle()),m={};if(m.hasMetaDescription=""!==p,m.hasTitle=""!==g,m.prominentWords=[],h){if(h(f)<200)return m}else if((0,n.default)(f)<100)return m;const _=(0,a.getSubheadingsTopLevel)(f).map((e=>e[2])),T=[e.getKeyword(),e.getSynonyms(),g,p,_.join(" ")],E=c?[]:(0,i.retrieveAbbreviations)(f.concat(T.join(" "))),y=(0,a.removeSubheadingsTopLevel)(f),A=(0,i.getProminentWords)(y,E,u,r,c),v=(0,i.getProminentWordsFromPaperAttributes)(T,E,u,r,c);v.forEach((e=>e.setOccurrences(3*e.getOccurrences())));const b=(0,i.collapseProminentWordsOnStem)(v.concat(A));(0,i.sortProminentWords)(b);let O=4;return u===o.default&&(O=2),m.prominentWords=(0,s.take)((0,i.filterProminentWords)(b,O),100),m}},93540:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var s=u(r(47793)),n=u(r(54241)),i=r(60914),a=r(59603),o=u(r(46705)),l=r(92819);function u(e){return e&&e.__esModule?e:{default:e}}t.default=(e,t)=>{const r=t.getConfig("firstWordExceptions"),u=t.getConfig("secondWordExceptions"),c=[(0,a.elementHasName)("ol"),(0,a.elementHasName)("ul"),(0,a.elementHasName)("table"),(0,a.elementHasClass)("wp-block-table")];let d=(0,l.cloneDeep)(e.getTree());d=(0,o.default)(d,c);const h=(0,n.default)(d),f=h.map((e=>((e,t,r)=>{const n=(0,i.getWordsFromTokens)(e.tokens,!1).filter((e=>" "!==(0,s.default)(e)));if(0===n.length)return"";let a=n[0].toLowerCase();return t.includes(a)&&n.length>1&&(a+=" "+n[1].toLowerCase(),r&&r.includes(n[1])&&(a+=" "+n[2].toLowerCase())),a})(e,r,u)));return((e,t)=>{const r=[];let s=[];return e.forEach(((n,i)=>{const a=e[i+1];s.push(t[i]),n&&n!==a&&(r.push({word:n,count:s.length,sentences:s}),s=[])})),r})(f,h)}},21706:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e,t){let r=e.getText();r=(0,a.default)(r),r=(0,o.filterShortcodesFromHTML)(r,e._attributes&&e._attributes.shortcodes);const l=(0,s.default)(r),u=t.getHelper("customCountLength"),c=[];(0,i.forEach)(l,(function(e){c.push({subheading:e.subheading,text:e.text,countLength:u?u(e.text):(0,n.default)(e.text),index:e.index})}));let d=0,h="";if(c.length>0){const e=c[0];h=r.slice(0,e.index),d=u?u(h):(0,n.default)(h)}return d>0&&""!==h&&c.unshift({subheading:"",text:h,countLength:d}),c};var s=l(r(89272)),n=l(r(33870)),i=r(92819),a=l(r(96908)),o=r(29866);function l(e){return e&&e.__esModule?e:{default:e}}},31015:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e,t){const r=t.getConfig("functionWords"),s=t.getHelper("getStemmer")(t),i=t.getHelper("createBasicWordForms"),l=t.getConfig("language"),f=t.getConfig("areHyphensWordBoundaries"),p=(0,a.default)(e,f).map((e=>e.toLocaleLowerCase(l)));return function(e,t,r,s,i,a,o){const l=(0,n.collectStems)(e,t,i,s,o),f=l.keyphraseStems,p=l.synonymsStems;if(0===f.stemOriginalPairs.length&&0===p.length)return new c;if([f,...p].every((e=>!0===e.exactMatch)))return new c([[f.stemOriginalPairs[0].stem]],p.map((e=>[[e.stemOriginalPairs[0].stem]])));const g=[...new Set(d(f,p))],m=[...new Set(r.filter((e=>!s.includes(e))))].map((e=>new n.StemOriginalPair(i(e),e))).filter((e=>g.includes(e.stem))).sort(((e,t)=>e.stem.localeCompare(t.stem))).reduce((function(e,t){const r=e[e.length-1];return 0===e.length||r.stem!==t.stem?e.push(new u(t.stem,[t.original])):r.forms.push(t.original),e}),[]);return new c(h(f,m,a),p.map((e=>h(e,m,a))))}(e.getKeyword().toLocaleLowerCase(l).trim(),(0,o.default)(e.getSynonyms().toLocaleLowerCase(l).trim()),p,r,s,i,f)};var s=r(37361),n=r(17811),i=r(92819),a=l(r(90831)),o=l(r(73481));function l(e){return e&&e.__esModule?e:{default:e}}function u(e,t){this.stem=e,this.forms=t}function c(e=[],t=[]){this.keyphraseForms=e,this.synonymsForms=t}function d(e,t){const r=0===e.stemOriginalPairs.length?[]:e.getStems(),s=0===t.length?[]:t.map((e=>e.getStems()));return[...r,...(0,i.flattenDeep)(s)]}function h(e,t,r){return 0===e.stemOriginalPairs.length?[]:e.exactMatch?[[e.stemOriginalPairs[0].stem]]:e.stemOriginalPairs.map((function(e){return function(e,t,r){const n=t.find((t=>t.stem===e.stem)),a=(0,s.normalizeSingle)((0,i.escapeRegExp)(e.original)),o=n?[a,...n.forms]:[a];return r&&o.push(...r(e.original)),[...new Set(o)]}(e,t,r)}))}},31051:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e){return e.getTree().findAll((e=>"h1"===e.name)).map((e=>({tag:"h1",content:e.findAll((e=>"#text"===e.name)).map((e=>e.value)).join(""),position:{startOffset:e.sourceCodeLocation.startTag.endOffset,endOffset:e.sourceCodeLocation.endTag.startOffset,clientId:e.clientId||""}}))).filter((e=>!!e.content))}},52948:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e){return(0,n.default)(e).length};var s,n=(s=r(92017))&&s.__esModule?s:{default:s}},42299:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"getLongCenterAlignedTexts",{enumerable:!0,get:function(){return s.default}}),Object.defineProperty(t,"keyphraseDistribution",{enumerable:!0,get:function(){return i.default}}),Object.defineProperty(t,"wordComplexity",{enumerable:!0,get:function(){return n.default}});var s=a(r(37228)),n=a(r(53201)),i=a(r(53127));function a(e){return e&&e.__esModule?e:{default:e}}},53127:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.maximizeSentenceScores=t.keyphraseDistributionResearcher=t.getDistraction=t.default=t.computeScoresPerSentenceShortTopic=t.computeScoresPerSentenceLongTopic=void 0;var s=r(92819),n=r(36478),i=r(48024),a=d(r(9862)),o=d(r(73481)),l=r(10840),u=d(r(96908)),c=r(24533);function d(e){return e&&e.__esModule?e:{default:e}}const h=function(e,t,r,s){const i=Array(t.length);for(let a=0;a=50?i[a]=9:i[a]=3;return i};t.computeScoresPerSentenceLongTopic=h;const f=function(e,t,r,s){const i=Array(t.length);for(let a=0;a3&&r.push(s);const n=r.length;if(0===n)return t;r.unshift(-1),r.push(t);const i=[];for(let e=1;ev.push(d(e)))));const b=e.getLocale(),O=[A.keyphraseForms];A.synonymsForms.forEach((function(e){O.push(e)}));const S=(0,s.uniq)((0,s.flattenDeep)(O)).sort(((e,t)=>t.length-e.length)),I=function(e,t,r,n,i,a=4,o,l){const u=t.length,c=Array(u);if(n.length>0)for(let s=0;s({sentence:e,score:t}))).filter((e=>e.score>3)).map((e=>e.sentence))}}(y,O,b,r,n,T,v,m),C=I.maximizedSentenceScores,N=g(C);return{sentencesToHighlight:(0,i.markWordsInSentences)(S,I.sentencesWithTopic,b,n),keyphraseDistributionScore:N/y.length*100}};t.keyphraseDistributionResearcher=m,t.default=m},57419:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e,t){const r=t.getResearch("morphology"),s=t.getConfig("functionWords");return{keyphraseLength:r.keyphraseForms.length,functionWords:s}}},82242:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.countKeyphraseInText=f,t.default=p,t.keywordCount=function(e,t){return console.warn("This function is deprecated, use getKeyphraseCount instead."),p(e,t)};var s=r(92819),n=h(r(54241)),i=r(37361),a=h(r(82676)),o=h(r(57329)),l=h(r(23116)),u=r(48024),c=h(r(9862)),d=r(29866);function h(e){return e&&e.__esModule?e:{default:e}}function f(e,t,r,n,i,l){const c={count:0,markings:[]};return e.forEach((e=>{const d=t.map((t=>(0,o.default)(e,t,r,n,i,l)));if(d.every((e=>e.count>0))){const t=d.map((e=>e.count)),r=Math.min(...t),i=(0,s.flattenDeep)(d.map((e=>e.matches)));let o=[];o=n?(0,u.markWordsInASentence)(e,i,n):(0,a.default)(e,i),c.count+=r,c.markings.push(o)}})),c}function p(e,t){const r={count:0,markings:[],keyphraseLength:0};let a=t.getResearch("morphology").keyphraseForms;const o=a.length;if(a=a.map((e=>e.map((e=>(0,i.normalizeSingle)(e))))),0===o)return r;const u=t.getHelper("matchWordCustomHelper"),h=t.getHelper("memoizedTokenizer"),p=t.getHelper("splitIntoTokensCustom"),g=e.getLocale(),m=u?(0,d.filterShortcodesFromHTML)(e.getText(),e._attributes&&e._attributes.shortcodes):e.getText(),_=f(u?(0,c.default)(m,h):(0,n.default)(e.getTree()),a,g,u,(0,l.default)(e.getKeyword()),p);return r.count=_.count,r.markings=(0,s.flatten)(_.markings),r.keyphraseLength=o,r}},74066:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0,t.keywordCountInSlug=o,t.keywordCountInUrl=function(e,t){return console.warn("This function is deprecated, use keywordCountInSlug instead."),o(e,t)};var s,n=(s=r(84159))&&s.__esModule?s:{default:s},i=r(36478),a=r(92819);function o(e,t){const r=(0,a.cloneDeep)(t);r.addConfig("areHyphensWordBoundaries",!0);const s=r.getResearch("morphology"),o=(0,n.default)(e.getSlug()),l=e.getLocale(),u=(0,i.findTopicFormsInString)(s,o,!1,l,!1);return{keyphraseLength:s.keyphraseForms.length,percentWordMatches:u.percentWordMatches}}t.default=o},72286:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e,t){const r=t.getConfig("functionWords"),i=t.getHelper("matchWordCustomHelper");let l=e.getText();l=(0,a.default)(l),l=(0,o.filterShortcodesFromHTML)(l,e._attributes&&e._attributes.shortcodes),l=(0,n.default)(l);const c=t.getResearch("morphology"),d=e.getLocale(),h={count:0,matches:0,percentReflectingTopic:0},f=(0,s.getSubheadingContentsTopLevel)(l);return 0!==f.length&&(h.count=f.length,h.matches=u(c,f,!0,d,r,i),h.percentReflectingTopic=h.matches/h.count*100),h};var s=r(84285),n=l(r(57719)),i=r(36478),a=l(r(96908)),o=r(29866);function l(e){return e&&e.__esModule?e:{default:e}}const u=function(e,t,r,s,n,a){return t.filter((t=>{const o=(0,i.findTopicFormsInString)(e,t,r,s,a);return 0===n.length?100===o.percentWordMatches:o.percentWordMatches>50})).length}},90497:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e,t){const r=e.getDescription(),s=e.getLocale(),i=t.getResearch("morphology"),a=t.getHelper("matchWordCustomHelper"),l=t.getHelper("memoizedTokenizer");return(0,n.default)(r,l).map((e=>o(e,i,s,a))).reduce(((e,t)=>e+t),0)};var s=i(r(7407)),n=i(r(9862));function i(e){return e&&e.__esModule?e:{default:e}}const a=function(e,t,r){return t.forEach((t=>t.matches.slice(0,r).forEach((t=>{e=e.replace(t,"")})))),e},o=function(e,t,r,n){const i=t.keyphraseForms.map((t=>(0,s.default)(e,t,r,n))),o=Math.min(...i.map((e=>e.count)));return e=a(e,i,o),[o,...t.synonymsForms.map((t=>{const o=t.map((t=>(0,s.default)(e,t,r,n))),l=Math.min(...o.map((e=>e.count)));return e=a(e,i,l),l}))].reduce(((e,t)=>e+t),0)}},96458:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e){return(0,n.default)(e.getDate(),e.getDescription())};var s,n=(s=r(40231))&&s.__esModule?s:{default:s}},33035:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e){return e.hasTitle()?e.getTitleWidth():0}},48799:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e,t){const r=(0,s.default)(e.getLocale()),a=t.getHelper("getWordsCustomHelper"),o=t.getHelper("wordsCharacterCount"),l={ar:138,cn:158,de:179,en:228,es:218,fi:161,fr:195,he:187,it:188,nl:202,pl:166,pt:181,ru:184,sl:180,sv:199,tr:166},u=l[r],c={ja:357}[r];let d,h=(0,n.default)(e).count;c?(h=o(a(e.getText())),d=h/c):d=u?h/u:h/(Object.values(l).reduce(((e,t)=>e+t))/Object.keys(l).length);const f=(0,i.default)(e);return Math.ceil(d+.2*f)};var s=a(r(67404)),n=a(r(44518)),i=a(r(52948));function a(e){return e&&e.__esModule?e:{default:e}}},25969:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e,t){const r=t.getHelper("memoizedTokenizer");let a=e.getText();return a=(0,n.default)(a),a=(0,i.filterShortcodesFromHTML)(a,e._attributes&&e._attributes.shortcodes),(0,s.default)(a,r)};var s=a(r(9862)),n=a(r(96908)),i=r(29866);function a(e){return e&&e.__esModule?e:{default:e}}},99074:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e){const t=new RegExp("(")}}},96682:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var s=r(65736),n=r(92819),i=d(r(73054)),a=d(r(41054)),o=d(r(15010)),l=r(49061),u=r(58677),c=r(88626);function d(e){return e&&e.__esModule?e:{default:e}}t.default=class{constructor({identifier:e,nonInclusivePhrases:t,inclusiveAlternatives:r,score:s,feedbackFormat:i,learnMoreUrl:a,rule:o,ruleDescription:u,caseSensitive:d,category:h}){this.identifier=e,this.nonInclusivePhrases=t,this.inclusiveAlternatives=r,(0,n.isString)(this.inclusiveAlternatives)&&(this.inclusiveAlternatives=[this.inclusiveAlternatives]),this.score=s,this.feedbackFormat=i,this.learnMoreUrl=(0,l.createAnchorOpeningTag)(a),this.rule=o||c.includesConsecutiveWords,this.ruleDescription=u,this.caseSensitive=d||!1,this.category=h}isApplicable(e,t){const r=t.getResearch("sentences"),s=e.getTextTitle();return r.push(s),this.foundPhrases=[],r.forEach((e=>{let t=(0,u.getWords)(e,"\\s",!1);this.caseSensitive||(t=t.map((e=>e.toLocaleLowerCase())));const r=this.nonInclusivePhrases.find((e=>this.rule(t,(0,u.getWords)(e,"\\s",!1)).length>=1));r&&this.foundPhrases.push({sentence:e,phrase:r})})),this.foundPhrases.length>=1}getResult(){const e=(0,s.sprintf)("%1$sLearn more.%2$s",this.learnMoreUrl,""),t=(0,s.sprintf)(this.feedbackFormat,this.foundPhrases[0].phrase,...this.inclusiveAlternatives),r=new i.default({score:this.score,text:`${t} ${e}`});return r.setIdentifier(this.identifier),r.setHasMarks(!0),r}getMarks(){return this.foundPhrases?this.foundPhrases.map((e=>new a.default({original:e.sentence,marked:(0,o.default)(e.sentence)}))):[]}}},46176:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var s,n=r(78261),i=r(35946),a=r(17864),o=r(64948),l=r(88626),u=r(28045),c=(s=r(77965))&&s.__esModule?s:{default:s},d=r(29700);const h=[{identifier:"seniorCitizen",nonInclusivePhrases:["senior citizen"],inclusiveAlternatives:["older person, older citizen","person older than 70"],score:u.SCORES.POTENTIALLY_NON_INCLUSIVE,feedbackFormat:[n.orangeUnlessSomeoneWants,i.specificAgeGroup].join(" ")},{identifier:"seniorCitizens",nonInclusivePhrases:["senior citizens"],inclusiveAlternatives:["older people, older citizens","people older than 70"],score:u.SCORES.POTENTIALLY_NON_INCLUSIVE,feedbackFormat:[n.orangeUnlessSomeoneWants,i.specificAgeGroup].join(" ")},{identifier:"agingDependants",nonInclusivePhrases:["aging dependants"],inclusiveAlternatives:["older people","people older than 70"],score:u.SCORES.POTENTIALLY_NON_INCLUSIVE,feedbackFormat:[n.orangeUnlessSomeoneWants,i.specificAgeGroup].join(" ")},{identifier:"elderly",nonInclusivePhrases:["elderly"],inclusiveAlternatives:["older people","people older than 70"],score:u.SCORES.POTENTIALLY_NON_INCLUSIVE,feedbackFormat:[n.orangeUnlessSomeoneWants,i.specificAgeGroup].join(" ")},{identifier:"senile",nonInclusivePhrases:["senile"],inclusiveAlternatives:"a specific characteristic or experience if it is known (e.g. has Alzheimer's)",score:u.SCORES.NON_INCLUSIVE,feedbackFormat:n.redHarmful},{identifier:"senility",nonInclusivePhrases:["senility"],inclusiveAlternatives:"dementia",score:u.SCORES.NON_INCLUSIVE,feedbackFormat:n.redHarmful},{identifier:"seniors",nonInclusivePhrases:["seniors"],inclusiveAlternatives:["older people","people older than 70"],score:u.SCORES.POTENTIALLY_NON_INCLUSIVE,feedbackFormat:[n.orangeUnlessSomeoneWants,i.specificAgeGroup].join(" "),rule:(e,t)=>(0,l.includesConsecutiveWords)(e,t).filter((0,a.isNotPrecededByException)(e,["high school","college","graduating","juniors and"])).filter((0,o.isNotFollowedByException)(e,t,["in high school","in college","who are graduating"])),ruleDescription:(0,d.notPrecededAndNotFollowed)(["high school","college","graduating","juniors and"],["in high school","in college","who are graduating"])},{identifier:"theAged",nonInclusivePhrases:["the aged"],inclusiveAlternatives:["older people","people older than 70"],score:u.SCORES.NON_INCLUSIVE,feedbackFormat:[n.redHarmful,i.specificAgeGroup].join(" "),rule:(e,t)=>(0,l.includesConsecutiveWords)(e,t).filter((0,c.default)(e,t)),ruleDescription:d.nonInclusiveWhenStandalone}];h.forEach((e=>{e.category="age",e.learnMoreUrl="https://yoa.st/inclusive-language-age"})),t.default=h},9214:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var s,n=r(78261),i=r(28045),a=r(88626),o=(s=r(77965))&&s.__esModule?s:{default:s},l=r(29700);const u=[{identifier:"albinos",nonInclusivePhrases:["albinos"],inclusiveAlternatives:"people with albinism, albino people",score:i.SCORES.POTENTIALLY_NON_INCLUSIVE,feedbackFormat:n.orangeUnlessSomeoneWants},{identifier:"anAlbino",nonInclusivePhrases:["an albino"],inclusiveAlternatives:"person with albinism, albino person",score:i.SCORES.POTENTIALLY_NON_INCLUSIVE,feedbackFormat:n.orangeUnlessSomeoneWants,rule:(e,t)=>(0,a.includesConsecutiveWords)(e,t).filter((0,o.default)(e,t)),ruleDescription:l.nonInclusiveWhenStandalone},{identifier:"obese",nonInclusivePhrases:["obese","overweight"],inclusiveAlternatives:"has a higher weight, higher-weight person, person in higher weight body, heavier person",score:i.SCORES.POTENTIALLY_NON_INCLUSIVE,feedbackFormat:[n.orangeUnlessSomeoneWants,n.preferredDescriptorIfKnown].join(" ")},{identifier:"obesitySingular",nonInclusivePhrases:["person with obesity","fat person"],inclusiveAlternatives:"person who has a higher weight, higher-weight person, person in higher weight body, heavier person",score:i.SCORES.POTENTIALLY_NON_INCLUSIVE,feedbackFormat:[n.orangeUnlessSomeoneWants,n.preferredDescriptorIfKnown].join(" ")},{identifier:"obesityPlural",nonInclusivePhrases:["people with obesity","fat people"],inclusiveAlternatives:"people who have a higher weight, higher-weight people, people in higher weight bodies, heavier people",score:i.SCORES.POTENTIALLY_NON_INCLUSIVE,feedbackFormat:[n.orangeUnlessSomeoneWants].join(" ")},{identifier:"verticallyChallenged",nonInclusivePhrases:["vertically challenged"],inclusiveAlternatives:"little person, has short stature, someone with dwarfism",score:i.SCORES.NON_INCLUSIVE,feedbackFormat:n.redHarmful},{identifier:"midget",nonInclusivePhrases:["midget"],inclusiveAlternatives:"little person, has short stature, someone with dwarfism",score:i.SCORES.NON_INCLUSIVE,feedbackFormat:n.redHarmful},{identifier:"midgets",nonInclusivePhrases:["midgets"],inclusiveAlternatives:"little people, have short stature, people with dwarfism",score:i.SCORES.NON_INCLUSIVE,feedbackFormat:n.redHarmful},{identifier:"harelip",nonInclusivePhrases:["harelip"],inclusiveAlternatives:"cleft lip, cleft palate",score:i.SCORES.NON_INCLUSIVE,feedbackFormat:n.redHarmful}];u.forEach((e=>{e.category="appearance",e.learnMoreUrl="https://yoa.st/inclusive-language-appearance"})),t.default=u},20117:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var s=r(28045),n=r(88626),i=r(64948),a=r(78261),o=r(38362),l=r(29700);const u=[{identifier:"firstWorld",nonInclusivePhrases:["First World"],inclusiveAlternatives:"the specific name for the region or country",score:s.SCORES.NON_INCLUSIVE,feedbackFormat:a.redHarmful,caseSensitive:!0,rule:(e,t)=>(0,n.includesConsecutiveWords)(e,t).filter((0,i.isNotFollowedByException)(e,t,["War","war","Assembly","assembly"])),ruleDescription:(0,l.notFollowed)(["War","war","Assembly","assembly"])},{identifier:"thirdWorld",nonInclusivePhrases:["Third World"],inclusiveAlternatives:"the specific name for the region or country",score:s.SCORES.NON_INCLUSIVE,feedbackFormat:a.redHarmful,caseSensitive:!0,rule:(e,t)=>(0,n.includesConsecutiveWords)(e,t).filter((0,i.isNotFollowedByException)(e,t,["War","war","Quarterly","quarterly","country"])),ruleDescription:(0,l.notFollowed)(["War","war","Quarterly","quarterly","country"])},{identifier:"tribe",nonInclusivePhrases:["tribe"],inclusiveAlternatives:"group, cohort, crew, league, guild, team, union",score:s.SCORES.POTENTIALLY_NON_INCLUSIVE,feedbackFormat:o.orangeUnlessCultureUsesTerm},{identifier:"tribes",nonInclusivePhrases:["tribes"],inclusiveAlternatives:"groups, cohorts, crews, leagues, guilds, teams, unions",score:s.SCORES.POTENTIALLY_NON_INCLUSIVE,feedbackFormat:o.orangeUnlessCultureUsesTerm},{identifier:"exotic",nonInclusivePhrases:["exotic"],inclusiveAlternatives:"unfamiliar, foreign, peculiar, fascinating, alluring, bizarre, non-native, introduced",score:s.SCORES.POTENTIALLY_NON_INCLUSIVE,feedbackFormat:a.beCarefulHarmful+" Unless you are referring to animals or scientific terms, consider using an alternative, such as %2$s.",rule:(e,t)=>(0,n.includesConsecutiveWords)(e,t).filter((0,i.isNotFollowedByException)(e,t,["longhair","longhairs","shorthair","shorthairs","bloom","blooms","species","florals","botanical","botanicals","leather","leathers","material","materials","timber","timbers","composite","composites","atom","atoms","molecule","molecules","hadron","hadrons","sphere","spheres","star","stars","car","cars","sports car","sports cars"])),ruleDescription:(0,l.notFollowed)(["longhair","longhairs","shorthair","shorthairs","bloom","blooms","species","florals","botanical","botanicals","leather","leathers","material","materials","timber","timbers","composite","composites","atom","atoms","molecule","molecules","hadron","hadrons","sphere","spheres","star","stars","car","cars","sports car","sports cars"])},{identifier:"sherpa",nonInclusivePhrases:["sherpa"],inclusiveAlternatives:"commander, coach, mastermind, coach, mentor",score:s.SCORES.POTENTIALLY_NON_INCLUSIVE,feedbackFormat:o.orangeUnlessCultureOfOrigin},{identifier:"guru",nonInclusivePhrases:["guru"],inclusiveAlternatives:"mentor, doyen, coach, mastermind, virtuoso, expert",score:s.SCORES.POTENTIALLY_NON_INCLUSIVE,feedbackFormat:o.orangeUnlessCultureOfOrigin},{identifier:"gurus",nonInclusivePhrases:["gurus"],inclusiveAlternatives:"mentors, doyens, coaches, masterminds, virtuosos, experts",score:s.SCORES.POTENTIALLY_NON_INCLUSIVE,feedbackFormat:o.orangeUnlessCultureOfOrigin},{identifier:"nonWhite",nonInclusivePhrases:["non-white"],inclusiveAlternatives:"people of color, POC, BIPOC or specifying the racial groups mentioned",score:s.SCORES.NON_INCLUSIVE,feedbackFormat:a.redHarmful},{identifier:"oriental",nonInclusivePhrases:["oriental"],inclusiveAlternatives:"Asian. When possible, be more specific (e.g. East Asian)",score:s.SCORES.POTENTIALLY_NON_INCLUSIVE,feedbackFormat:a.orangeUnlessAnimalsObjects},{identifier:"asianAmerican",nonInclusivePhrases:["Asian-American"],inclusiveAlternatives:"Asian American",score:s.SCORES.NON_INCLUSIVE,feedbackFormat:a.redHarmful,caseSensitive:!0},{identifier:"asianAmericans",nonInclusivePhrases:["Asian-Americans"],inclusiveAlternatives:"Asian Americans",score:s.SCORES.NON_INCLUSIVE,feedbackFormat:a.redHarmful,caseSensitive:!0},{identifier:"africanAmerican",nonInclusivePhrases:["African-American"],inclusiveAlternatives:"African American, Black, American of African descent",score:s.SCORES.NON_INCLUSIVE,feedbackFormat:a.redHarmful,caseSensitive:!0},{identifier:"africanAmericans",nonInclusivePhrases:["African-Americans"],inclusiveAlternatives:"African Americans, Black, Americans of African descent",score:s.SCORES.NON_INCLUSIVE,feedbackFormat:a.redHarmful,caseSensitive:!0},{identifier:"whiteRace",nonInclusivePhrases:["the White race"],inclusiveAlternatives:"",score:s.SCORES.NON_INCLUSIVE,feedbackFormat:a.avoidHarmful,caseSensitive:!0},{identifier:"whitelist",nonInclusivePhrases:["whitelist"],inclusiveAlternatives:"allowlist",score:s.SCORES.NON_INCLUSIVE,feedbackFormat:a.redHarmful},{identifier:"whitelists",nonInclusivePhrases:["whitelists"],inclusiveAlternatives:"allowlists",score:s.SCORES.NON_INCLUSIVE,feedbackFormat:a.redHarmful},{identifier:"whitelisting",nonInclusivePhrases:["whitelisting"],inclusiveAlternatives:"allowlisting",score:s.SCORES.NON_INCLUSIVE,feedbackFormat:a.redHarmful},{identifier:"whitelisted",nonInclusivePhrases:["whitelisted"],inclusiveAlternatives:"allowlisted",score:s.SCORES.NON_INCLUSIVE,feedbackFormat:a.redHarmful},{identifier:"blacklist",nonInclusivePhrases:["blacklist"],inclusiveAlternatives:"blocklist, denylist, faillist, redlist",score:s.SCORES.NON_INCLUSIVE,feedbackFormat:a.redHarmful},{identifier:"blacklists",nonInclusivePhrases:["blacklists"],inclusiveAlternatives:"blocklists, denylists, faillists, redlists",score:s.SCORES.NON_INCLUSIVE,feedbackFormat:a.redHarmful},{identifier:"blacklisting",nonInclusivePhrases:["blacklisting"],inclusiveAlternatives:"blocklisting, denylisting, faillisting, redlisting",score:s.SCORES.NON_INCLUSIVE,feedbackFormat:a.redHarmful},{identifier:"blacklisted",nonInclusivePhrases:["blacklisted"],inclusiveAlternatives:"blocklisted, denylisted, faillisted, redlisted",score:s.SCORES.NON_INCLUSIVE,feedbackFormat:a.redHarmful},{identifier:"gyp",nonInclusivePhrases:["gyp"],inclusiveAlternatives:"fraud, cheat, swindle, rip-off",score:s.SCORES.NON_INCLUSIVE,feedbackFormat:a.redHarmful},{identifier:"gyps",nonInclusivePhrases:["gyps"],inclusiveAlternatives:"frauds, cheats, swindles, rips off, rip-offs",score:s.SCORES.NON_INCLUSIVE,feedbackFormat:a.redHarmful},{identifier:"gypped",nonInclusivePhrases:["gypped"],inclusiveAlternatives:"cheated, swindled, ripped off",score:s.SCORES.NON_INCLUSIVE,feedbackFormat:a.redHarmful},{identifier:"gypping",nonInclusivePhrases:["gypping"],inclusiveAlternatives:"cheating, swindling, ripping off",score:s.SCORES.NON_INCLUSIVE,feedbackFormat:a.redHarmful},{identifier:"gypsy",nonInclusivePhrases:["gypsy","gipsy"],inclusiveAlternatives:["Rom, Roma person, Romani, Romani person","traveler, wanderer, free-spirited"],score:s.SCORES.POTENTIALLY_NON_INCLUSIVE,feedbackFormat:[a.orangeUnlessSomeoneWants,"If you are referring to a lifestyle rather than the ethnic group or their music, consider using an alternative such as %3$s."].join(" ")},{identifier:"gypsies",nonInclusivePhrases:["gypsies","gipsies"],inclusiveAlternatives:["Roma, Romani, Romani people","travelers, wanderers, free-spirited"],score:s.SCORES.POTENTIALLY_NON_INCLUSIVE,feedbackFormat:[a.orangeUnlessSomeoneWants,"If you are referring to a lifestyle rather than the ethnic group or their music, consider using an alternative such as %3$s."].join(" ")},{identifier:"eskimo",nonInclusivePhrases:["eskimo","eskimos"],inclusiveAlternatives:"the specific name of the Indigenous community (for example, Inuit)",score:s.SCORES.POTENTIALLY_NON_INCLUSIVE,feedbackFormat:a.orangeUnlessSomeoneWants},{identifier:"coloredPerson",nonInclusivePhrases:["colored person"],inclusiveAlternatives:"person of color, POC, BIPOC",score:s.SCORES.NON_INCLUSIVE,feedbackFormat:a.redHarmful},{identifier:"coloredPeople",nonInclusivePhrases:["colored people"],inclusiveAlternatives:"people of color, POC, BIPOC",score:s.SCORES.NON_INCLUSIVE,feedbackFormat:a.redHarmful},{identifier:"americanIndians",nonInclusivePhrases:["American Indian","American Indians"],inclusiveAlternatives:"Native American(s), Indigenous peoples of America",score:s.SCORES.POTENTIALLY_NON_INCLUSIVE,feedbackFormat:a.orangeUnlessSomeoneWants,caseSensitive:!0},{identifier:"mulatto",nonInclusivePhrases:["mulatto","mulattos","mulattoes"],inclusiveAlternatives:"mixed, biracial, multiracial",score:s.SCORES.NON_INCLUSIVE,feedbackFormat:a.redHarmful},{identifier:"savage",nonInclusivePhrases:["savage"],inclusiveAlternatives:"severe, dreadful, untamed, brutal, fearless, fierce, brilliant, amazing",score:s.SCORES.NON_INCLUSIVE,feedbackFormat:a.redHarmful},{identifier:"civilized",nonInclusivePhrases:["civilized"],inclusiveAlternatives:"proper, well-mannered, enlightened, respectful",score:s.SCORES.NON_INCLUSIVE,feedbackFormat:a.redHarmful},{identifier:"primitive",nonInclusivePhrases:["primitive"],inclusiveAlternatives:"early, rudimentary",score:s.SCORES.NON_INCLUSIVE,feedbackFormat:a.redHarmful},{identifier:"powWow",nonInclusivePhrases:["pow-wow"],inclusiveAlternatives:"chat, brief conversation, brainstorm, huddle",score:s.SCORES.POTENTIALLY_NON_INCLUSIVE,feedbackFormat:o.orangeUnlessCultureOfOrigin},{identifier:"lowManOnTheTotemPole",nonInclusivePhrases:["low man on the totem pole"],inclusiveAlternatives:"person of lower rank, junior-level",score:s.SCORES.NON_INCLUSIVE,feedbackFormat:a.redHarmful},{identifier:"spiritAnimal",nonInclusivePhrases:["spirit animal"],inclusiveAlternatives:"inspiration, hero, icon, idol",score:s.SCORES.POTENTIALLY_NON_INCLUSIVE,feedbackFormat:o.orangeUnlessCultureOfOrigin},{identifier:"firstWorldCountries",nonInclusivePhrases:["first world countries"],inclusiveAlternatives:"the specific name for the regions or countries",score:s.SCORES.NON_INCLUSIVE,feedbackFormat:a.redHarmful},{identifier:"firstWorldHyphen",nonInclusivePhrases:["first-world"],inclusiveAlternatives:"the specific name for the region or country",score:s.SCORES.NON_INCLUSIVE,feedbackFormat:a.redHarmful},{identifier:"third-worldCountry",nonInclusivePhrases:["third-world country"],inclusiveAlternatives:"low-income country, developing country",score:s.SCORES.NON_INCLUSIVE,feedbackFormat:a.redHarmful},{identifier:"third-worldCountry",nonInclusivePhrases:["third world country"],inclusiveAlternatives:"low-income country, developing country",score:s.SCORES.NON_INCLUSIVE,feedbackFormat:a.redHarmful},{identifier:"underdevelopedCountry",nonInclusivePhrases:["underdeveloped country"],inclusiveAlternatives:"developing country",score:s.SCORES.NON_INCLUSIVE,feedbackFormat:"Avoid using %1$s as it is potentially harmful. Consider using an alternative, such as %2$s instead or be more specific about what aspect this word refers to."},{identifier:"underdevelopedCountries",nonInclusivePhrases:["underdeveloped countries"],inclusiveAlternatives:"developing countries",score:s.SCORES.NON_INCLUSIVE,feedbackFormat:"Avoid using %1$s as it is potentially harmful. Consider using an alternative, such as %2$s instead or be more specific about what aspect this word refers to."}];u.forEach((e=>{e.category="culture",e.learnMoreUrl="https://yoa.st/inclusive-language-culture"})),t.default=u},77018:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var s,n=r(78261),i=r(69960),a=r(17864),o=r(64948),l=r(5719),u=r(88626),c=r(28045),d=(s=r(77965))&&s.__esModule?s:{default:s},h=r(538),f=r(65736),p=r(29700);const g=[{identifier:"binge",nonInclusivePhrases:["binge"],inclusiveAlternatives:"indulge, satiate, wallow, spree, marathon, consume excessively",score:c.SCORES.POTENTIALLY_NON_INCLUSIVE,feedbackFormat:"Be careful when using %1$s, unless talking about a symptom of a medical condition. If you are not referencing a symptom, consider other alternatives to describe the trait or behavior, such as %2$s.",rule:(e,t)=>(0,u.includesConsecutiveWords)(e,t).filter((0,o.isNotFollowedByException)(e,t,["drink","drinks","drinking","eating disorder","and purge","behavior","behaviors","behaviour","behaviours"])),ruleDescription:(0,p.notFollowed)(["drink","drinks","drinking","eating disorder","and purge","behavior","behaviors","behaviour","behaviours"])},{identifier:"bingeing",nonInclusivePhrases:["bingeing","binging"],inclusiveAlternatives:"indulging, satiating, wallowing, spreeing, marathoning, consuming excessively",score:c.SCORES.POTENTIALLY_NON_INCLUSIVE,feedbackFormat:"Be careful when using %1$s, unless talking about a symptom of a medical condition. If you are not referencing a symptom, consider other alternatives to describe the trait or behavior, such as %2$s.",rule:(e,t)=>(0,u.includesConsecutiveWords)(e,t).filter((0,o.isNotFollowedByException)(e,t,["and purging","behavior","behaviors","behaviour","behaviours"])),ruleDescription:(0,p.notFollowed)(["and purging","behavior","behaviors","behaviour","behaviours"])},{identifier:"binged",nonInclusivePhrases:["binged"],inclusiveAlternatives:"indulged, satiated, wallowed, spreed, marathoned, consumed excessively",score:c.SCORES.POTENTIALLY_NON_INCLUSIVE,feedbackFormat:"Be careful when using %1$s, unless talking about a symptom of a medical condition. If you are not referencing a symptom, consider other alternatives to describe the trait or behavior, such as %2$s.",rule:(e,t)=>(0,u.includesConsecutiveWords)(e,t).filter((0,o.isNotFollowedByException)(e,t,["and purged"])),ruleDescription:(0,p.notFollowed)(["and purged"])},{identifier:"binges",nonInclusivePhrases:["binges"],inclusiveAlternatives:"indulges, satiates, wallows, sprees, marathons, consumes excessively",score:c.SCORES.POTENTIALLY_NON_INCLUSIVE,feedbackFormat:"Be careful when using %1$s, unless talking about a symptom of a medical condition. If you are not referencing a symptom, consider other alternatives to describe the trait or behavior, such as %2$s.",rule:(e,t)=>(0,u.includesConsecutiveWords)(e,t).filter((0,o.isNotFollowedByException)(e,t,["and purges"])),ruleDescription:(0,p.notFollowed)(["and purges"])},{identifier:"wheelchairBound",nonInclusivePhrases:["wheelchair-bound","wheelchair bound","confined to a wheelchair"],inclusiveAlternatives:"uses a wheelchair, is a wheelchair user",score:c.SCORES.NON_INCLUSIVE,feedbackFormat:n.redHarmful},{identifier:"mentallyRetarded",nonInclusivePhrases:["mentally retarded"],inclusiveAlternatives:"person with an intellectual disability",score:c.SCORES.NON_INCLUSIVE,feedbackFormat:n.redHarmful},{identifier:"retarded",nonInclusivePhrases:["retarded"],inclusiveAlternatives:"uninformed, ignorant, foolish, irrational, insensible",score:c.SCORES.NON_INCLUSIVE,feedbackFormat:[n.avoidDerogatory,n.alternative].join(" "),rule:(e,t)=>(0,u.includesConsecutiveWords)(e,t).filter((0,a.isNotPrecededByException)(e,["mentally"])),ruleDescription:(0,p.notPreceded)(["mentally"])},{identifier:"alcoholic",nonInclusivePhrases:["an alcoholic"],inclusiveAlternatives:"person with alcohol use disorder",score:c.SCORES.POTENTIALLY_NON_INCLUSIVE,feedbackFormat:n.orangeUnlessSomeoneWants,rule:(e,t)=>(0,u.includesConsecutiveWords)(e,t).filter((0,o.isNotFollowedByException)(e,t,["drink","beverage"])),ruleDescription:(0,p.notFollowed)(["drink","beverage"])},{identifier:"alcoholics",nonInclusivePhrases:["alcoholics"],inclusiveAlternatives:"people with alcohol use disorder",score:c.SCORES.POTENTIALLY_NON_INCLUSIVE,feedbackFormat:n.orangeUnlessSomeoneWants,rule:(e,t)=>(0,u.includesConsecutiveWords)(e,t).filter((0,o.isNotFollowedByException)(e,t,["anonymous"])),ruleDescription:(0,p.notFollowed)(["anonymous"])},{identifier:"cripple",nonInclusivePhrases:["a cripple"],inclusiveAlternatives:"person with a physical disability, a physically disabled person",score:c.SCORES.NON_INCLUSIVE,feedbackFormat:[n.avoidDerogatory,n.alternative].join(" ")},{identifier:"crippled",nonInclusivePhrases:["crippled"],inclusiveAlternatives:"has a physical disability, is physically disabled",score:c.SCORES.NON_INCLUSIVE,feedbackFormat:n.redHarmful},{identifier:"daft",nonInclusivePhrases:["daft"],inclusiveAlternatives:"uninformed, ignorant, foolish, inconsiderate, irrational, reckless",score:c.SCORES.NON_INCLUSIVE,feedbackFormat:n.redHarmful},{identifier:"handicapped",nonInclusivePhrases:["handicapped"],inclusiveAlternatives:"disabled, person with a disability",score:c.SCORES.NON_INCLUSIVE,feedbackFormat:n.redHarmful},{identifier:"handicap",nonInclusivePhrases:["handicap"],inclusiveAlternatives:"disability",score:c.SCORES.NON_INCLUSIVE,feedbackFormat:n.redHarmful,rule:(e,t)=>(0,u.includesConsecutiveWords)(e,t).filter((0,o.isNotFollowedByException)(e,t,["toilet","toilets","parking","bathroom","bathrooms","stall","stalls"])),ruleDescription:(0,p.notFollowed)(["toilet","toilets","parking","bathroom","bathrooms","stall","stalls"])},{identifier:"insane",nonInclusivePhrases:["insane"],inclusiveAlternatives:"wild, confusing, unpredictable, impulsive, reckless, out of control, unbelievable, amazing, incomprehensible, nonsensical, outrageous, ridiculous",score:c.SCORES.NON_INCLUSIVE,feedbackFormat:n.redHarmful,rule:(e,t)=>(0,u.includesConsecutiveWords)(e,t).filter((0,a.isNotPrecededByException)(e,h.shouldNotPrecedeStandaloneCrazy)).filter((0,l.isNotFollowedAndPrecededByException)(e,t,h.shouldNotPrecedeStandaloneCrazyWhenFollowedByAbout,h.shouldNotFollowStandaloneCrazyWhenPrecededByToBe)),ruleDescription:"Not targeted with this feedback when part of a more specific phrase that we target ('to drive insane', 'to go insane')."},{identifier:"insanely",nonInclusivePhrases:["insanely"],inclusiveAlternatives:"extremely, amazingly, wildly, ferociously, ridiculously, unbelievably",score:c.SCORES.NON_INCLUSIVE,feedbackFormat:n.redHarmful},{identifier:"imbecile",nonInclusivePhrases:["imbecile"],inclusiveAlternatives:"uninformed, ignorant, foolish, inconsiderate, irrational, reckless",score:c.SCORES.NON_INCLUSIVE,feedbackFormat:[n.avoidDerogatory,n.alternative].join(" ")},{identifier:"specialNeeds",nonInclusivePhrases:["special needs"],inclusiveAlternatives:["functional needs, support needs","disabled, person with a disability"],score:c.SCORES.NON_INCLUSIVE,feedbackFormat:[n.avoidHarmful,"Consider using an alternative, such as %2$s when referring to someone's needs, or %3$s when referring to a person."].join(" ")},{identifier:"hardOfHearing",nonInclusivePhrases:["hard-of-hearing"],inclusiveAlternatives:"hard of hearing, partially deaf, has partial hearing loss",score:c.SCORES.NON_INCLUSIVE,feedbackFormat:n.redHarmful},{identifier:"hearingImpaired",nonInclusivePhrases:["hearing impaired"],inclusiveAlternatives:"deaf or hard of hearing, partially deaf, has partial hearing loss",score:c.SCORES.NON_INCLUSIVE,feedbackFormat:n.redHarmful},{identifier:"functioning",nonInclusivePhrases:["high functioning","low functioning"],inclusiveAlternatives:"describing the specific characteristic or experience",score:c.SCORES.POTENTIALLY_NON_INCLUSIVE,feedbackFormat:"Be careful when using %1$s as it is potentially harmful. Consider using an alternative, such as %2$s, unless referring to how you characterize your own condition.",rule:(e,t)=>(0,u.includesConsecutiveWords)(e,t).filter((0,o.isNotFollowedByException)(e,t,["autism"])),ruleDescription:(0,p.notFollowed)(["autism"])},{identifier:"autismHigh",nonInclusivePhrases:["high functioning autism","high-functioning autism"],inclusiveAlternatives:"autism with high support needs or describing the specific characteristic or experience",score:c.SCORES.NON_INCLUSIVE,feedbackFormat:"Avoid using %1$s as it is potentially harmful. Consider using an alternative, such as %2$s, unless referring to how you characterize your own condition."},{identifier:"autismLow",nonInclusivePhrases:["low functioning autism","low-functioning autism"],inclusiveAlternatives:"autism with low support needs or describing the specific characteristic or experience",score:c.SCORES.NON_INCLUSIVE,feedbackFormat:"Avoid using %1$s as it is potentially harmful. Consider using an alternative, such as %2$s, unless referring to how you characterize your own condition."},{identifier:"birthDefect",nonInclusivePhrases:["birth defect"],inclusiveAlternatives:"congenital disability, born with a disability, disability since birth",score:c.SCORES.POTENTIALLY_NON_INCLUSIVE,feedbackFormat:"Be careful when using %1$s to describe someone's specific condition. Consider using an alternative, such as %2$s, unless referring to how you characterize your own condition."},{identifier:"lame",nonInclusivePhrases:["lame"],inclusiveAlternatives:["boring, lousy, unimpressive, sad, corny","person with a disability, person who has difficulty with walking"],score:c.SCORES.POTENTIALLY_NON_INCLUSIVE,feedbackFormat:"Be careful when using %1$s as it is potentially harmful. Unless you are using it as a noun to refer to an object (such as the kitchen tool), consider using an alternative. For example, %2$s. If referring to someone's disability, use an alternative such as %3$s."},{identifier:"lamer",nonInclusivePhrases:["lamer"],inclusiveAlternatives:"more boring, lousier, more unimpressive, sadder, cornier",score:c.SCORES.NON_INCLUSIVE,feedbackFormat:n.redHarmful},{identifier:"lamest",nonInclusivePhrases:["lamest"],inclusiveAlternatives:"most boring, lousiest, most unimpressive, saddest, corniest",score:c.SCORES.NON_INCLUSIVE,feedbackFormat:n.redHarmful},{identifier:"commitSuicide",nonInclusivePhrases:["commit suicide"],inclusiveAlternatives:"take one's life, die by suicide, kill oneself",score:c.SCORES.NON_INCLUSIVE,feedbackFormat:n.redHarmful},{identifier:"committingSuicide",nonInclusivePhrases:["committing suicide"],inclusiveAlternatives:"taking one's life, dying by suicide, killing oneself",score:c.SCORES.NON_INCLUSIVE,feedbackFormat:n.redHarmful},{identifier:"commitsSuicide",nonInclusivePhrases:["commits suicide"],inclusiveAlternatives:"takes one's life, dies by suicide, kills oneself",score:c.SCORES.NON_INCLUSIVE,feedbackFormat:n.redHarmful},{identifier:"committedSuicide",nonInclusivePhrases:["committed suicide"],inclusiveAlternatives:"took one's life, died by suicide, killed themself",score:c.SCORES.NON_INCLUSIVE,feedbackFormat:n.redHarmful},{identifier:"handicapParking",nonInclusivePhrases:["handicap parking"],inclusiveAlternatives:"accessible parking",score:c.SCORES.NON_INCLUSIVE,feedbackFormat:n.redHarmful},{identifier:"fellOnDeafEars",nonInclusivePhrases:["fell on deaf ears"],inclusiveAlternatives:"was not addressed, was ignored, was disregarded",score:c.SCORES.NON_INCLUSIVE,feedbackFormat:n.redHarmful},{identifier:"turnOnBlindEye",nonInclusivePhrases:["turn a blind eye"],inclusiveAlternatives:"ignore, pretend not to notice",score:c.SCORES.NON_INCLUSIVE,feedbackFormat:n.redHarmful},{identifier:"blindLeadingBlind",nonInclusivePhrases:["the blind leading the blind"],inclusiveAlternatives:"ignorant, misguided, incompetent, unqualified, insensitive, unaware",score:c.SCORES.NON_INCLUSIVE,feedbackFormat:n.redHarmful},{identifier:"handicapBathroom",nonInclusivePhrases:["handicap bathroom","handicap bathrooms"],inclusiveAlternatives:"accessible bathroom(s)",score:c.SCORES.NON_INCLUSIVE,feedbackFormat:n.redHarmful},{identifier:"handicapToilet",nonInclusivePhrases:["handicap toilet","handicap toilets"],inclusiveAlternatives:"accessible toilet(s)",score:c.SCORES.NON_INCLUSIVE,feedbackFormat:n.redHarmful},{identifier:"handicapStall",nonInclusivePhrases:["handicap stall","handicap stalls"],inclusiveAlternatives:"accessible stall(s)",score:c.SCORES.NON_INCLUSIVE,feedbackFormat:n.redHarmful},{identifier:"stupid",nonInclusivePhrases:["stupid"],inclusiveAlternatives:["uninformed, ignorant, foolish, inconsiderate, irrational, reckless"],score:c.SCORES.NON_INCLUSIVE,feedbackFormat:n.redHarmful},{identifier:"dumbDown",nonInclusivePhrases:["dumb down"],inclusiveAlternatives:"oversimplify",score:c.SCORES.NON_INCLUSIVE,feedbackFormat:n.redHarmful},{identifier:"dumbingDown",nonInclusivePhrases:["dumbing down"],inclusiveAlternatives:"oversimplifying",score:c.SCORES.NON_INCLUSIVE,feedbackFormat:n.redHarmful},{identifier:"dumbedDown",nonInclusivePhrases:["dumbed down"],inclusiveAlternatives:"oversimplified",score:c.SCORES.NON_INCLUSIVE,feedbackFormat:n.redHarmful},{identifier:"dumbItDown",nonInclusivePhrases:["dumb it down"],inclusiveAlternatives:"oversimplify it",score:c.SCORES.NON_INCLUSIVE,feedbackFormat:n.redHarmful},{identifier:"dumbingItDown",nonInclusivePhrases:["dumbing it down"],inclusiveAlternatives:"oversimplifying it",score:c.SCORES.NON_INCLUSIVE,feedbackFormat:n.redHarmful},{identifier:"dumbedItDown",nonInclusivePhrases:["dumbed it down"],inclusiveAlternatives:"oversimplified it",score:c.SCORES.NON_INCLUSIVE,feedbackFormat:n.redHarmful},{identifier:"dumb",nonInclusivePhrases:["dumb","dumber","dumbest"],inclusiveAlternatives:["uninformed, ignorant, foolish, inconsiderate, irrational, reckless"],score:c.SCORES.NON_INCLUSIVE,feedbackFormat:n.redHarmful,rule:(e,t)=>(0,u.includesConsecutiveWords)(e,t).filter((0,a.isNotPrecededByException)(e,["deaf and"])).filter((0,o.isNotFollowedByException)(e,t,["down"])).filter((0,o.isNotFollowedByException)(e,t,["it down"])),ruleDescription:(0,p.notPreceded)(["deaf and"])},{identifier:"deaf",nonInclusivePhrases:["deaf-mute","deaf and dumb"],inclusiveAlternatives:"deaf",score:c.SCORES.NON_INCLUSIVE,feedbackFormat:n.redHarmful},{identifier:"addict",nonInclusivePhrases:["addict"],inclusiveAlternatives:"person with a (drug, alcohol, ...) addiction, person with substance abuse disorder",score:c.SCORES.POTENTIALLY_NON_INCLUSIVE,feedbackFormat:n.orangeUnlessSomeoneWants},{identifier:"addicts",nonInclusivePhrases:["addicts"],inclusiveAlternatives:"people with a (drug, alcohol, ...) addiction, people with substance abuse disorder",score:c.SCORES.POTENTIALLY_NON_INCLUSIVE,feedbackFormat:n.orangeUnlessSomeoneWants},{identifier:"brainDamaged",nonInclusivePhrases:["brain-damaged"],inclusiveAlternatives:"person with a (traumatic) brain injury",score:c.SCORES.POTENTIALLY_NON_INCLUSIVE,feedbackFormat:n.orangeUnlessSomeoneWants},{identifier:"differentlyAbled",nonInclusivePhrases:["differently abled","differently-abled"],inclusiveAlternatives:"disabled, person with a disability",score:c.SCORES.POTENTIALLY_NON_INCLUSIVE,feedbackFormat:n.orangeUnlessSomeoneWants},{identifier:"epilepticFit",nonInclusivePhrases:["epileptic fit"],inclusiveAlternatives:"epileptic seizure",score:c.SCORES.NON_INCLUSIVE,feedbackFormat:n.redHarmful},{identifier:"epilepticFits",nonInclusivePhrases:["epileptic fits"],inclusiveAlternatives:"epileptic seizures",score:c.SCORES.NON_INCLUSIVE,feedbackFormat:n.redHarmful},{identifier:"sanityCheck",nonInclusivePhrases:["sanity check"],inclusiveAlternatives:"final check, confidence check, rationality check, soundness check",score:c.SCORES.NON_INCLUSIVE,feedbackFormat:n.redHarmful},{identifier:"to not be crazy about",nonInclusivePhrases:["crazy about"],inclusiveAlternatives:"to not be impressed by, to not be enthusiastic about, to not be into, to not like",score:c.SCORES.NON_INCLUSIVE,feedbackFormat:["Avoid using to not be crazy about as it is potentially harmful.",n.alternative].join(" "),rule:(e,t)=>(0,u.includesConsecutiveWords)(e,t).filter((0,a.isPrecededByException)(e,h.formsOfToBeNotWithOptionalIntensifier)),ruleDescription:"Targeted when preceded by a negated form of 'to be' or 'to get' and an optional intensifier."},{identifier:"to be crazy about",nonInclusivePhrases:["crazy about"],inclusiveAlternatives:"to love, to be obsessed with, to be infatuated with",score:c.SCORES.NON_INCLUSIVE,feedbackFormat:["Avoid using to be crazy about as it is potentially harmful.",n.alternative].join(" "),rule:(e,t)=>(0,u.includesConsecutiveWords)(e,t).filter((0,a.isPrecededByException)(e,h.formsOfToBeWithOptionalIntensifier)),ruleDescription:"Targeted when preceded by a form of 'to be' or 'to get' and an optional intensifier."},{identifier:"to be nuts about",nonInclusivePhrases:["nuts about"],inclusiveAlternatives:"to love, to be obsessed with, to be infatuated with",score:c.SCORES.NON_INCLUSIVE,feedbackFormat:["Avoid using to be nuts about as it is potentially harmful.",n.alternative].join(" "),rule:(e,t)=>(0,u.includesConsecutiveWords)(e,t).filter((0,a.isPrecededByException)(e,h.formsOfToBeWithOptionalIntensifier)),ruleDescription:"Targeted when preceded by a form of 'to be' or 'to get' and an optional intensifier."},{identifier:"to be bananas about",nonInclusivePhrases:["bananas about"],inclusiveAlternatives:"to love, to be obsessed with, to be infatuated with",score:c.SCORES.NON_INCLUSIVE,feedbackFormat:["Avoid using to be bananas about as it is potentially harmful.",n.alternative].join(" "),rule:(e,t)=>(0,u.includesConsecutiveWords)(e,t).filter((0,a.isPrecededByException)(e,h.formsOfToBeWithOptionalIntensifier)),ruleDescription:"Targeted when preceded by a form of 'to be' or 'to get' and an optional intensifier."},{identifier:"crazy in love",nonInclusivePhrases:["crazy in love"],inclusiveAlternatives:"wildly in love, head over heels, infatuated",score:c.SCORES.NON_INCLUSIVE,feedbackFormat:n.redHarmful},{identifier:"to go crazy",nonInclusivePhrases:["crazy"],inclusiveAlternatives:"to go wild, to go out of control, to go up the wall, to be aggravated, to get confused",score:c.SCORES.NON_INCLUSIVE,feedbackFormat:["Avoid using to go crazy as it is potentially harmful.",n.alternative].join(" "),rule:(e,t)=>(0,u.includesConsecutiveWords)(e,t).filter((0,a.isPrecededByException)(e,h.formsOfToGo)),ruleDescription:(0,p.isPreceded)(h.formsOfToGo)},{identifier:"to go insane",nonInclusivePhrases:["insane"],inclusiveAlternatives:"to go wild, to go out of control, to go up the wall, to be aggravated, to get confused",score:c.SCORES.NON_INCLUSIVE,feedbackFormat:["Avoid using to go insane as it is potentially harmful.",n.alternative].join(" "),rule:(e,t)=>(0,u.includesConsecutiveWords)(e,t).filter((0,a.isPrecededByException)(e,h.formsOfToGo)),ruleDescription:(0,p.isPreceded)(h.formsOfToGo)},{identifier:"to go mad",nonInclusivePhrases:["mad"],inclusiveAlternatives:"to go wild, to go out of control, to go up the wall, to be aggravated, to get confused",score:c.SCORES.NON_INCLUSIVE,feedbackFormat:["Avoid using to go mad as it is potentially harmful.",n.alternative].join(" "),rule:(e,t)=>(0,u.includesConsecutiveWords)(e,t).filter((0,a.isPrecededByException)(e,h.formsOfToGo)),ruleDescription:(0,p.isPreceded)(h.formsOfToGo)},{identifier:"to go nuts",nonInclusivePhrases:["nuts"],inclusiveAlternatives:"to go wild, to go out of control, to go up the wall, to be aggravated, to get confused",score:c.SCORES.NON_INCLUSIVE,feedbackFormat:["Avoid using to go nuts as it is potentially harmful.",n.alternative].join(" "),rule:(e,t)=>(0,u.includesConsecutiveWords)(e,t).filter((0,a.isPrecededByException)(e,h.formsOfToGo)),ruleDescription:(0,p.isPreceded)(h.formsOfToGo)},{identifier:"to go bananas",nonInclusivePhrases:["bananas"],inclusiveAlternatives:"to go wild, to go out of control, to go up the wall, to be aggravated, to get confused",score:c.SCORES.NON_INCLUSIVE,feedbackFormat:["Avoid using to go bananas as it is potentially harmful.",n.alternative].join(" "),rule:(e,t)=>(0,u.includesConsecutiveWords)(e,t).filter((0,a.isPrecededByException)(e,h.formsOfToGo)),ruleDescription:(0,p.isPreceded)(h.formsOfToGo)},{identifier:"to drive crazy",nonInclusivePhrases:["crazy"],inclusiveAlternatives:"to drive one to their limit, to get on one's last nerve, to make one livid, to aggravate, to make one's blood boil, to exasperate, to get into one's head",score:c.SCORES.NON_INCLUSIVE,feedbackFormat:["Avoid using to drive crazy as it is potentially harmful.",n.alternative].join(" "),rule:(e,t)=>(0,u.includesConsecutiveWords)(e,t).filter((0,a.isPrecededByException)(e,h.combinationsOfDriveAndObjectPronoun)),ruleDescription:"Targeted when preceded by a form of 'to drive' and an object pronoun (e.g. 'driving me')"},{identifier:"to drive insane",nonInclusivePhrases:["insane"],inclusiveAlternatives:"to drive one to their limit, to get on one's last nerve, to make one livid, to aggravate, to make one's blood boil, to exasperate, to get into one's head",score:c.SCORES.NON_INCLUSIVE,feedbackFormat:["Avoid using to drive insane as it is potentially harmful.",n.alternative].join(" "),rule:(e,t)=>(0,u.includesConsecutiveWords)(e,t).filter((0,a.isPrecededByException)(e,h.combinationsOfDriveAndObjectPronoun)),ruleDescription:"Targeted when preceded by a form of 'to drive' and an object pronoun (e.g. 'driving me')"},{identifier:"to drive mad",nonInclusivePhrases:["mad"],inclusiveAlternatives:"to drive one to their limit, to get on one's last nerve, to make one livid, to aggravate, to make one's blood boil, to exasperate, to get into one's head",score:c.SCORES.NON_INCLUSIVE,feedbackFormat:["Avoid using to drive mad as it is potentially harmful.",n.alternative].join(" "),rule:(e,t)=>(0,u.includesConsecutiveWords)(e,t).filter((0,a.isPrecededByException)(e,h.combinationsOfDriveAndObjectPronoun)),ruleDescription:"Targeted when preceded by a form of 'to drive' and an object pronoun (e.g. 'driving me')"},{identifier:"to drive nuts",nonInclusivePhrases:["nuts"],inclusiveAlternatives:"to drive one to their limit, to get on one's last nerve, to make one livid, to aggravate, to make one's blood boil, to exasperate, to get into one's head",score:c.SCORES.NON_INCLUSIVE,feedbackFormat:["Avoid using to drive nuts as it is potentially harmful.",n.alternative].join(" "),rule:(e,t)=>(0,u.includesConsecutiveWords)(e,t).filter((0,a.isPrecededByException)(e,h.combinationsOfDriveAndObjectPronoun)),ruleDescription:"Targeted when preceded by a form of 'to drive' and an object pronoun (e.g. 'driving me')"},{identifier:"to drive bananas",nonInclusivePhrases:["bananas"],inclusiveAlternatives:"to drive one to their limit, to get on one's last nerve, to make one livid, to aggravate, to make one's blood boil, to exasperate, to get into one's head",score:c.SCORES.NON_INCLUSIVE,feedbackFormat:["Avoid using to drive bananas as it is potentially harmful.",n.alternative].join(" "),rule:(e,t)=>(0,u.includesConsecutiveWords)(e,t).filter((0,a.isPrecededByException)(e,h.combinationsOfDriveAndObjectPronoun)),ruleDescription:"Targeted when preceded by a form of 'to drive' and an object pronoun (e.g. 'driving me')"},{identifier:"nuts",nonInclusivePhrases:["nuts"],inclusiveAlternatives:"wild, baffling, out of control, inexplicable, unbelievable, aggravating, shocking, intense, impulsive, chaotic, confused, mistaken, obsessed",score:c.SCORES.NON_INCLUSIVE,feedbackFormat:n.redHarmful,rule:(e,t)=>(0,u.includesConsecutiveWords)(e,t).filter((0,a.isPrecededByException)(e,h.shouldPrecedeNutsBananasWithIntensifier)).filter((0,l.isNotFollowedAndPrecededByException)(e,t,h.shouldNotPrecedeStandaloneCrazyWhenFollowedByAbout,h.shouldNotFollowStandaloneCrazyWhenPrecededByToBe)),ruleDescription:"Targeted when preceded by is/he's/she's and an optional intensifier and when it's not part of a more specific phrase that we target ('to go nuts', 'to drive nuts', 'to be nuts about')."},{identifier:"bananas",nonInclusivePhrases:["bananas"],inclusiveAlternatives:"wild, baffling, out of control, inexplicable, unbelievable, aggravating, shocking, intense, impulsive, chaotic, confused, mistaken, obsessed",score:c.SCORES.NON_INCLUSIVE,feedbackFormat:n.redHarmful,rule:(e,t)=>(0,u.includesConsecutiveWords)(e,t).filter((0,a.isPrecededByException)(e,h.shouldPrecedeNutsBananasWithIntensifier)).filter((0,l.isNotFollowedAndPrecededByException)(e,t,h.shouldNotPrecedeStandaloneCrazyWhenFollowedByAbout,h.shouldNotFollowStandaloneCrazyWhenPrecededByToBe)),ruleDescription:"Targeted when preceded by is/he's/she's and an optional intensifier and when it's not part of a more specific phrase that we target ('to go bananas', 'to drive bananas', 'to be bananas about')."},{identifier:"crazier",nonInclusivePhrases:["crazier"],inclusiveAlternatives:"more wild, baffling, out of control, inexplicable, unbelievable, aggravating, shocking, intense, impulsive, chaotic, confused, mistaken, obsessed",score:c.SCORES.NON_INCLUSIVE,feedbackFormat:n.redHarmful},{identifier:"craziest",nonInclusivePhrases:["craziest"],inclusiveAlternatives:"most wild, baffling, out of control, inexplicable, unbelievable, aggravating, shocking, intense, impulsive, chaotic, confused, mistaken, obsessed",score:c.SCORES.NON_INCLUSIVE,feedbackFormat:n.redHarmful},{identifier:"psychopathic",nonInclusivePhrases:["psychopath","psychopaths","psychopathic"],inclusiveAlternatives:"toxic, manipulative, unpredictable, impulsive, reckless, out of control",score:c.SCORES.NON_INCLUSIVE,feedbackFormat:n.redHarmful},{identifier:"schizophrenic",nonInclusivePhrases:["schizophrenic","bipolar"],inclusiveAlternatives:"of two minds, chaotic, confusing",score:c.SCORES.POTENTIALLY_NON_INCLUSIVE,feedbackFormat:i.orangeUnlessMedicalCondition,rule:(e,t)=>(0,u.includesConsecutiveWords)(e,t).filter((0,o.isNotFollowedByException)(e,t,["disorder"])),ruleDescription:(0,p.notFollowed)(["disorder"])},{identifier:"paranoid",nonInclusivePhrases:["paranoid"],inclusiveAlternatives:"overly suspicious, unreasonable, defensive",score:c.SCORES.POTENTIALLY_NON_INCLUSIVE,feedbackFormat:i.orangeUnlessMedicalCondition,rule:(e,t)=>(0,u.includesConsecutiveWords)(e,t).filter((0,o.isNotFollowedByException)(e,t,["personality disorder","delusion","delusions","ideation"])),ruleDescription:(0,p.notFollowed)(["personality disorder","delusion","delusions","ideation"])},{identifier:"manic",nonInclusivePhrases:["manic"],inclusiveAlternatives:"excited, raving, unbalanced, wild",score:c.SCORES.POTENTIALLY_NON_INCLUSIVE,feedbackFormat:i.orangeUnlessMedicalCondition,rule:(e,t)=>(0,u.includesConsecutiveWords)(e,t).filter((0,o.isNotFollowedByException)(e,t,["episode","episodes","state","states","symptoms","and depressive episodes","and hypomanic","or hypomanic"])),ruleDescription:(0,p.notFollowed)(["episode","episodes","state","states","symptoms","and depressive episodes","and hypomanic","or hypomanic"])},{identifier:"hysterical",nonInclusivePhrases:["hysterical"],inclusiveAlternatives:"intense, vehement, piercing, chaotic",score:c.SCORES.NON_INCLUSIVE,feedbackFormat:n.redHarmful},{identifier:"psycho",nonInclusivePhrases:["psycho","psychos"],inclusiveAlternatives:"toxic, distraught, unpredictable, reckless, out of control",score:c.SCORES.NON_INCLUSIVE,feedbackFormat:n.redHarmful},{identifier:"neurotic",nonInclusivePhrases:["neurotic","lunatic"],inclusiveAlternatives:"distraught, unstable, startling, confusing, baffling",score:c.SCORES.NON_INCLUSIVE,feedbackFormat:n.redHarmful},{identifier:"sociopath",nonInclusivePhrases:["sociopath"],inclusiveAlternatives:["person with antisocial personality disorder","toxic, manipulative, cruel"],score:c.SCORES.POTENTIALLY_NON_INCLUSIVE,feedbackFormat:"Be careful when using %1$s as it is potentially harmful. If you are referencing the medical condition, use %2$s instead, unless referring to someone who explicitly wants to be referred to with this term. If you are not referencing the medical condition, consider other alternatives to describe the trait or behavior, such as %3$s."},{identifier:"sociopaths",nonInclusivePhrases:["sociopaths"],inclusiveAlternatives:["people with antisocial personality disorder","toxic, manipulative, cruel"],score:c.SCORES.POTENTIALLY_NON_INCLUSIVE,feedbackFormat:"Be careful when using %1$s as it is potentially harmful. If you are referencing the medical condition, use %2$s instead, unless referring to someone who explicitly wants to be referred to with this term. If you are not referencing the medical condition, consider other alternatives to describe the trait or behavior, such as %3$s."},{identifier:"spaz",nonInclusivePhrases:["spaz","spazz"],inclusiveAlternatives:["incompetent person, erratic person, inept person, hyperactive person, agitated person, amateur, unqualified person, ignorant person","lose control, flip out, throw a tantrum, behave erratically, go on the fritz, twitch, move clumsily, move awkwardly"],score:c.SCORES.NON_INCLUSIVE,feedbackFormat:"Avoid using %1$s as it is potentially harmful. Consider using an alternative, such as %2$s when referring to a person, or %3$s when referring to an action.",rule:(e,t)=>(0,u.includesConsecutiveWords)(e,t).filter((0,o.isNotFollowedByException)(e,t,["out"])),ruleDescription:(0,p.notFollowed)(["out"])},{identifier:"spazzes",nonInclusivePhrases:["spazzes"],inclusiveAlternatives:["incompetent people, erratic people, inept people, hyperactive people, agitated people, amateurs, unqualified people, ignorant people","loses control, flips out, throws a tantrum, behaves erratically, goes on the fritz, twitches, moves clumsily, moves awkwardly"],score:c.SCORES.NON_INCLUSIVE,feedbackFormat:"Avoid using %1$s as it is potentially harmful. Consider using an alternative, such as %2$s when referring to a person, or %3$s when referring to an action.",rule:(e,t)=>(0,u.includesConsecutiveWords)(e,t).filter((0,o.isNotFollowedByException)(e,t,["out"])),ruleDescription:(0,p.notFollowed)(["out"])},{identifier:"spazzing",nonInclusivePhrases:["spazzing"],inclusiveAlternatives:"losing control, flipping out, throwing a tantrum, behaving erratically, going on the fritz, twitching, moving clumsily, moving awkwardly",score:c.SCORES.NON_INCLUSIVE,feedbackFormat:n.redHarmful,rule:(e,t)=>(0,u.includesConsecutiveWords)(e,t).filter((0,o.isNotFollowedByException)(e,t,["out"])),ruleDescription:(0,p.notFollowed)(["out"])},{identifier:"spazzed",nonInclusivePhrases:["spazzed"],inclusiveAlternatives:"lost control, flipped out, threw a tantrum, behaved erratically, went on the fritz, twitched, moved clumsily, moved awkwardly",score:c.SCORES.NON_INCLUSIVE,feedbackFormat:n.redHarmful},{identifier:"spazOut",nonInclusivePhrases:["spaz out","spazz out"],inclusiveAlternatives:"flip out, throw a tantrum, lose control, move clumsily, move awkwardly",score:c.SCORES.NON_INCLUSIVE,feedbackFormat:n.redHarmful},{identifier:"spazzesOut",nonInclusivePhrases:["spazzes out"],inclusiveAlternatives:"flips out, throws a tantrum, loses control, moves clumsily, moves awkwardly",score:c.SCORES.NON_INCLUSIVE,feedbackFormat:n.redHarmful},{identifier:"spazzingOut",nonInclusivePhrases:["spazzing out"],inclusiveAlternatives:"flipping out, throwing a tantrum, losing control, moving clumsily, moving awkwardly",score:c.SCORES.NON_INCLUSIVE,feedbackFormat:n.redHarmful},{identifier:"crazy",nonInclusivePhrases:["crazy"],inclusiveAlternatives:"wild, baffling, out of control, inexplicable, unbelievable, aggravating, shocking, intense, impulsive, chaotic, confused, mistaken, obsessed",score:c.SCORES.NON_INCLUSIVE,feedbackFormat:n.redHarmful,rule:(e,t)=>(0,u.includesConsecutiveWords)(e,t).filter((0,a.isNotPrecededByException)(e,h.shouldNotPrecedeStandaloneCrazy)).filter((0,o.isNotFollowedByException)(e,t,h.shouldNotFollowStandaloneCrazy)).filter((0,l.isNotFollowedAndPrecededByException)(e,t,h.shouldNotPrecedeStandaloneCrazyWhenFollowedByAbout,h.shouldNotFollowStandaloneCrazyWhenPrecededByToBe)),ruleDescription:"Not targeted with this feedback when part of a more specific phrase that we target ('to drive crazy', to go crazy', 'to (not) be crazy about', 'crazy in love')."},{identifier:"narcissistic",nonInclusivePhrases:["narcissistic"],inclusiveAlternatives:["person with narcissistic personality disorder","selfish, egotistical, self-centered, self-absorbed, vain, toxic, manipulative"],score:c.SCORES.POTENTIALLY_NON_INCLUSIVE,feedbackFormat:"Be careful when using %1$s as it is potentially harmful. If you are referencing the medical condition, use %2$s instead, unless referring to someone who explicitly wants to be referred to with this term. If you are not referencing the medical condition, consider other alternatives to describe the trait or behavior, such as %3$s.",rule:(e,t)=>(0,u.includesConsecutiveWords)(e,t).filter((0,o.isNotFollowedByException)(e,t,["personality disorder"])),ruleDescription:(0,p.notFollowed)(["personality disorder"])},{identifier:"OCD",nonInclusivePhrases:["ocd"],inclusiveAlternatives:"pedantic, obsessed, perfectionist",score:c.SCORES.POTENTIALLY_NON_INCLUSIVE,feedbackFormat:[(0,f.sprintf)(i.orangeUnlessMedicalCondition,"OCD","%2$s"),"If you are referring to someone who has the medical condition, then state that they have OCD rather than that they are OCD."].join(" "),rule:(e,t)=>(0,u.includesConsecutiveWords)(e,t).filter((0,a.isPrecededByException)(e,h.formsOfToBeAndToBeNotWithOptionalIntensifier)),ruleDescription:"Targeted when preceded by a form of 'to be' or 'to get' (including their negated forms)and an optional intensifier"},{identifier:"theMentallyIll",nonInclusivePhrases:["the mentally ill"],inclusiveAlternatives:"people who are mentally ill, mentally ill people",score:c.SCORES.NON_INCLUSIVE,feedbackFormat:n.redHarmful,rule:(e,t)=>(0,u.includesConsecutiveWords)(e,t).filter((0,d.default)(e,t)),ruleDescription:p.nonInclusiveWhenStandalone},{identifier:"theDisabled",nonInclusivePhrases:["the disabled"],inclusiveAlternatives:"people who have a disability, disabled people",score:c.SCORES.NON_INCLUSIVE,feedbackFormat:n.redHarmful,rule:(e,t)=>(0,u.includesConsecutiveWords)(e,t).filter((0,d.default)(e,t)),ruleDescription:p.nonInclusiveWhenStandalone}];g.forEach((e=>{e.category="disability",e.learnMoreUrl="https://yoa.st/inclusive-language-disability"})),t.default=g},538:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.shouldPrecedeNutsBananasWithIntensifier=t.shouldNotPrecedeStandaloneCrazyWhenFollowedByAbout=t.shouldNotPrecedeStandaloneCrazy=t.shouldNotFollowStandaloneCrazyWhenPrecededByToBe=t.shouldNotFollowStandaloneCrazy=t.formsOfToGo=t.formsOfToBeWithOptionalIntensifier=t.formsOfToBeNotWithOptionalIntensifier=t.formsOfToBeAndToBeNotWithOptionalIntensifier=t.default=t.combinationsOfDriveAndObjectPronoun=void 0;var s=r(64998),n=r(92819);const i=["so","very","a bit","really","pretty","kind of","that","too","totally","completely","absolutely","even","also","as"],a=s.formsOfToBe.concat(s.formsOfToGet),o=function(e,t){return(0,n.flatMap)(e,(e=>(0,n.flatMap)(t,(t=>`${e} ${t}`))))},l=o(a,i),u=t.formsOfToBeWithOptionalIntensifier=l.concat(a);let c=(0,n.flatMap)(a,(e=>`${e} not`));c=c.concat(s.negatedFormsOfToBe);const d=o(c,i),h=t.formsOfToBeNotWithOptionalIntensifier=d.concat(c),f=t.formsOfToBeAndToBeNotWithOptionalIntensifier=u.concat(h),p=t.combinationsOfDriveAndObjectPronoun=o(["driving","drive","drove","drives","driven"],["me","you","them","him","her","someone","somebody","anyone","anybody","everyone","everybody"]),g=t.formsOfToGo=["go","goes","going","gone","went"],m=t.shouldNotPrecedeStandaloneCrazy=p.concat(g),_=t.shouldNotFollowStandaloneCrazy=["in love"],T=t.shouldNotPrecedeStandaloneCrazyWhenFollowedByAbout=u.concat(h),E=t.shouldNotFollowStandaloneCrazyWhenPrecededByToBe=["about"],y=["is","she's","he's"],A=t.shouldPrecedeNutsBananasWithIntensifier=o(y,i).concat(y);t.default={formsOfToBeWithOptionalIntensifier:u,formsOfToBeNotWithOptionalIntensifier:h,formsOfToBeAndToBeNotWithOptionalIntensifier:f,combinationsOfDriveAndObjectPronoun:p,formsOfToGo:g,shouldNotPrecedeStandaloneCrazy:m,shouldNotFollowStandaloneCrazy:_,shouldNotPrecedeStandaloneCrazyWhenFollowedByAbout:T,shouldNotFollowStandaloneCrazyWhenPrecededByToBe:E,shouldPrecedeNutsBananasWithIntensifier:A}},35946:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.specificAgeGroup=void 0,t.specificAgeGroup="Or, if possible, be specific about the group you are referring to (e.g. %3$s)."},38362:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.orangeUnlessCultureUsesTerm=t.orangeUnlessCultureOfOrigin=void 0;var s=r(78261);t.orangeUnlessCultureOfOrigin=[s.beCarefulHarmful,"Consider using an alternative, such as %2$s instead, unless you are referring to the culture in which this term originated."].join(" "),t.orangeUnlessCultureUsesTerm=[s.beCarefulHarmful,"Consider using an alternative, such as %2$s instead, unless you are referring to a culture that uses this term."].join(" ")},69960:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.orangeUnlessMedicalCondition=void 0;var s=r(78261);t.orangeUnlessMedicalCondition=s.beCarefulHarmful+" Unless you are referencing the specific medical condition, consider using another alternative to describe the trait or behavior, such as %2$s."},85805:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.orangeExclusionaryUnlessUseTheTerm=t.orangeExclusionaryUnlessTwoGenders=t.orangeExclusionaryUnlessMenAndWomen=t.orangeExclusionaryUnlessMen=t.orangeExclusionaryUnless=void 0,t.orangeExclusionaryUnless="Be careful when using %1$s as it can be exclusionary. Unless you are sure that the group you refer to only consists of %1$s, use an alternative, such as %2$s.",t.orangeExclusionaryUnlessMen="Be careful when using %1$s as it can be exclusionary. Unless you are sure that the group you refer to only consists of men, use an alternative, such as %2$s.",t.orangeExclusionaryUnlessMenAndWomen="Be careful when using %1$s as it can be exclusionary. Unless you are sure that the group you refer to only consists of men and women, use an alternative, such as %2$s.",t.orangeExclusionaryUnlessTwoGenders="Be careful when using %1$s as it can be exclusionary. Unless you are sure that the group you refer to only consists of two genders, use an alternative, such as %2$s.",t.orangeExclusionaryUnlessUseTheTerm="Be careful when using %1$s as it can be exclusionary. Unless you are sure that the group you refer to only consists of people who use this term, use an alternative, such as %2$s."},78261:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.redPotentiallyExclusionary=t.redHarmful=t.redExclusionary=t.preferredDescriptorIfKnown=t.orangeUnlessSomeoneWants=t.orangeUnlessAnimalsObjects=t.orangeNoUnless=t.orangeExclusionaryNoUnless=t.beCarefulHarmful=t.avoidHarmful=t.avoidDerogatory=t.alternative=void 0,t.avoidDerogatory="Avoid using %1$s as it is derogatory.";const r=t.avoidHarmful="Avoid using %1$s as it is potentially harmful.",s=t.beCarefulHarmful="Be careful when using %1$s as it is potentially harmful.",n=t.alternative="Consider using an alternative, such as %2$s.";t.preferredDescriptorIfKnown="Alternatively, if talking about a specific person, use their preferred descriptor if known.",t.orangeNoUnless=[s,n].join(" "),t.orangeExclusionaryNoUnless=["Be careful when using %1$s as it is potentially exclusionary.",n].join(" "),t.orangeUnlessAnimalsObjects=[s,"Unless you are referring to objects or animals, consider using an alternative, such as %2$s."].join(" "),t.orangeUnlessSomeoneWants=[s,"Consider using an alternative, such as %2$s, unless referring to someone who explicitly wants to be referred to with this term."].join(" "),t.redHarmful=[r,n].join(" "),t.redExclusionary=["Avoid using %1$s as it is exclusionary.",n].join(" "),t.redPotentiallyExclusionary=["Avoid using %1$s as it is potentially exclusionary.",n].join(" ")},556:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var s,n=r(78261),i=r(85805),a=r(28045),o=r(88626),l=(s=r(77965))&&s.__esModule?s:{default:s},u=r(29700);const c=[{identifier:"firemen",nonInclusivePhrases:["firemen"],inclusiveAlternatives:"firefighters",score:a.SCORES.POTENTIALLY_NON_INCLUSIVE,feedbackFormat:i.orangeExclusionaryUnlessMen},{identifier:"policemen",nonInclusivePhrases:["policemen"],inclusiveAlternatives:"police officers",score:a.SCORES.POTENTIALLY_NON_INCLUSIVE,feedbackFormat:i.orangeExclusionaryUnlessMen},{identifier:"menAndWomen",nonInclusivePhrases:["men and women","women and men"],inclusiveAlternatives:"people, people of all genders, individuals, human beings",score:a.SCORES.POTENTIALLY_NON_INCLUSIVE,feedbackFormat:i.orangeExclusionaryUnless},{identifier:"boysAndGirls",nonInclusivePhrases:["boys and girls","girls and boys"],inclusiveAlternatives:"kids, children",score:a.SCORES.POTENTIALLY_NON_INCLUSIVE,feedbackFormat:i.orangeExclusionaryUnless},{identifier:"heOrShe",nonInclusivePhrases:["he/she","he or she","she or he","(s)he"],inclusiveAlternatives:"they",score:a.SCORES.POTENTIALLY_NON_INCLUSIVE,feedbackFormat:n.orangeExclusionaryNoUnless},{identifier:"birthSex",nonInclusivePhrases:["birth sex","natal sex"],inclusiveAlternatives:"assigned sex, assigned sex at birth",score:a.SCORES.NON_INCLUSIVE,feedbackFormat:n.redHarmful},{identifier:"mankind",nonInclusivePhrases:["mankind"],inclusiveAlternatives:"individuals, people, persons, human beings, humanity",score:a.SCORES.NON_INCLUSIVE,feedbackFormat:n.redExclusionary},{identifier:"preferredPronouns",nonInclusivePhrases:["preferred pronouns"],inclusiveAlternatives:"pronouns",score:a.SCORES.POTENTIALLY_NON_INCLUSIVE,feedbackFormat:[n.orangeNoUnless.slice(0,-1),", unless referring to someone who explicitly wants to use this term to describe their own pronouns."].join("")},{identifier:"oppositeGender",nonInclusivePhrases:["opposite gender"],inclusiveAlternatives:"another gender",score:a.SCORES.NON_INCLUSIVE,feedbackFormat:n.redExclusionary},{identifier:"oppositeSex",nonInclusivePhrases:["opposite sex"],inclusiveAlternatives:"another sex",score:a.SCORES.NON_INCLUSIVE,feedbackFormat:n.redExclusionary},{identifier:"femaleBodied",nonInclusivePhrases:["female-bodied"],inclusiveAlternatives:"assigned female at birth",score:a.SCORES.NON_INCLUSIVE,feedbackFormat:n.redPotentiallyExclusionary.slice(0,-1)+" if you are discussing a person based on their sex or assigned gender at birth. If talking about human anatomy, use the specific anatomical phrase as opposed to %1$s."},{identifier:"maleBodied",nonInclusivePhrases:["male-bodied"],inclusiveAlternatives:"assigned male at birth",score:a.SCORES.NON_INCLUSIVE,feedbackFormat:n.redPotentiallyExclusionary.slice(0,-1)+" if you are discussing a person based on their sex or assigned gender at birth. If talking about human anatomy, use the specific anatomical phrase as opposed to %1$s."},{identifier:"hermaphrodite",nonInclusivePhrases:["hermaphrodite"],inclusiveAlternatives:"intersex",score:a.SCORES.NON_INCLUSIVE,feedbackFormat:n.redHarmful},{identifier:"hermaphrodites",nonInclusivePhrases:["hermaphrodites"],inclusiveAlternatives:"intersex people",score:a.SCORES.NON_INCLUSIVE,feedbackFormat:n.redHarmful},{identifier:"bothGenders",nonInclusivePhrases:["both genders"],inclusiveAlternatives:"people, folks, human beings, all genders",score:a.SCORES.POTENTIALLY_NON_INCLUSIVE,feedbackFormat:i.orangeExclusionaryUnlessTwoGenders},{identifier:"ladiesAndGentleman",nonInclusivePhrases:["ladies and gentlemen"],inclusiveAlternatives:"everyone, folks, honored guests",score:a.SCORES.POTENTIALLY_NON_INCLUSIVE,feedbackFormat:i.orangeExclusionaryUnlessMenAndWomen},{identifier:"husbandAndWife",nonInclusivePhrases:["husband and wife","husbands and wives"],inclusiveAlternatives:"spouses, partners",score:a.SCORES.POTENTIALLY_NON_INCLUSIVE,feedbackFormat:n.orangeExclusionaryNoUnless.slice(0,-1)+", unless referring to someone who explicitly wants to be referred to with this term."},{identifier:"mothersAndFathers",nonInclusivePhrases:["mothers and fathers","fathers and mothers"],inclusiveAlternatives:"parents",score:a.SCORES.POTENTIALLY_NON_INCLUSIVE,feedbackFormat:i.orangeExclusionaryUnlessUseTheTerm},{identifier:"manHours",nonInclusivePhrases:["man-hours"],inclusiveAlternatives:"person-hours, business hours",score:a.SCORES.NON_INCLUSIVE,feedbackFormat:n.redExclusionary},{identifier:"preferredName",nonInclusivePhrases:["preferred name"],inclusiveAlternatives:"name, affirming name",score:a.SCORES.POTENTIALLY_NON_INCLUSIVE,feedbackFormat:[n.orangeNoUnless.slice(0,-1),", unless referring to someone who explicitly wants to use this term to describe their own name."].join("")},{identifier:"transgenders",nonInclusivePhrases:["transgenders"],inclusiveAlternatives:"trans people, transgender people",score:a.SCORES.NON_INCLUSIVE,feedbackFormat:[n.avoidDerogatory,n.alternative].join(" ")},{identifier:"transsexual",nonInclusivePhrases:["transsexual"],inclusiveAlternatives:"transgender",score:a.SCORES.POTENTIALLY_NON_INCLUSIVE,feedbackFormat:n.orangeUnlessSomeoneWants},{identifier:"transsexuals",nonInclusivePhrases:["transsexuals"],inclusiveAlternatives:"trans people, transgender people",score:a.SCORES.POTENTIALLY_NON_INCLUSIVE,feedbackFormat:n.orangeUnlessSomeoneWants},{identifier:"transWoman",nonInclusivePhrases:["transwoman"],inclusiveAlternatives:"trans woman, transgender woman",score:a.SCORES.POTENTIALLY_NON_INCLUSIVE,feedbackFormat:n.orangeUnlessSomeoneWants},{identifier:"transWomen",nonInclusivePhrases:["transwomen"],inclusiveAlternatives:"trans women, transgender women",score:a.SCORES.POTENTIALLY_NON_INCLUSIVE,feedbackFormat:n.orangeUnlessSomeoneWants},{identifier:"transMan",nonInclusivePhrases:["transman"],inclusiveAlternatives:"trans man, transgender man",score:a.SCORES.POTENTIALLY_NON_INCLUSIVE,feedbackFormat:n.orangeUnlessSomeoneWants},{identifier:"transMen",nonInclusivePhrases:["transmen"],inclusiveAlternatives:"trans men, transgender men",score:a.SCORES.POTENTIALLY_NON_INCLUSIVE,feedbackFormat:n.orangeUnlessSomeoneWants},{identifier:"transgendered",nonInclusivePhrases:["transgendered"],inclusiveAlternatives:["transgender, trans","transitioned, went through a gender transition"],score:a.SCORES.NON_INCLUSIVE,feedbackFormat:[n.redHarmful.slice(0,-1),"if referring to a person. If referring to a transition process, consider using an alternative such as %3$s."].join(" ")},{identifier:"maleToFemale",nonInclusivePhrases:["male-to-female","mtf"],inclusiveAlternatives:"trans woman, transgender woman",score:a.SCORES.POTENTIALLY_NON_INCLUSIVE,feedbackFormat:n.orangeUnlessSomeoneWants},{identifier:"femaleToMale",nonInclusivePhrases:["female-to-male","ftm"],inclusiveAlternatives:"trans man, transgender man",score:a.SCORES.POTENTIALLY_NON_INCLUSIVE,feedbackFormat:n.orangeUnlessSomeoneWants},{identifier:"heShe",nonInclusivePhrases:["he-she"],inclusiveAlternatives:"",score:a.SCORES.NON_INCLUSIVE,feedbackFormat:n.avoidDerogatory},{identifier:"shemale",nonInclusivePhrases:["shemale","she-male"],inclusiveAlternatives:"",score:a.SCORES.NON_INCLUSIVE,feedbackFormat:n.avoidDerogatory},{identifier:"manMade",nonInclusivePhrases:["man-made","manmade"],inclusiveAlternatives:"artificial, synthetic, machine-made",score:a.SCORES.NON_INCLUSIVE,feedbackFormat:n.redExclusionary},{identifier:"toEachTheirOwn",nonInclusivePhrases:["to each his own"],inclusiveAlternatives:"to each their own",score:a.SCORES.NON_INCLUSIVE,feedbackFormat:n.redExclusionary},{identifier:"manned",nonInclusivePhrases:["manned"],inclusiveAlternatives:"crewed",score:a.SCORES.NON_INCLUSIVE,feedbackFormat:n.redExclusionary},{identifier:"aTransgender",nonInclusivePhrases:["a transgender","the transgender"],inclusiveAlternatives:"transgender person",score:a.SCORES.NON_INCLUSIVE,feedbackFormat:n.redHarmful,rule:(e,t)=>(0,o.includesConsecutiveWords)(e,t).filter((0,l.default)(e,t)),ruleDescription:u.nonInclusiveWhenStandalone},{identifier:"pregnant women",nonInclusivePhrases:["pregnant women"],inclusiveAlternatives:"pregnant people",score:a.SCORES.POTENTIALLY_NON_INCLUSIVE,feedbackFormat:"Be careful when using %1$s as it can be exclusionary. Unless you are sure that the group you refer to only consists of women, use an alternative, such as %2$s."}];c.forEach((e=>{e.category="gender",e.learnMoreUrl="https://yoa.st/inclusive-language-gender"})),t.default=c},66064:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var s=d(r(46176)),n=d(r(9214)),i=d(r(77018)),a=d(r(556)),o=d(r(20117)),l=d(r(27540)),u=d(r(12297)),c=d(r(32296));function d(e){return e&&e.__esModule?e:{default:e}}t.default=[...s.default,...n.default,...i.default,...a.default,...o.default,...l.default,...u.default,...c.default]},12297:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var s=r(28045),n=r(78261),i=r(88626),a=r(17864),o=r(29700);const l=[{identifier:"minorities",nonInclusivePhrases:["minorities"],inclusiveAlternatives:["members of the LGBTQ+ community","Indigenous peoples","marginalized groups","religious minorities"],score:s.SCORES.POTENTIALLY_NON_INCLUSIVE,feedbackFormat:[n.beCarefulHarmful,"Consider using an alternative by being specific about which group(s) of people you are referring to. For example: %2$s, %3$s, %4$s. In case an alternative is not available, make sure to specify the type of minorities you are referring to, e.g., %5$s."].join(" ")},{identifier:"normalPerson",nonInclusivePhrases:["normal person"],inclusiveAlternatives:["typical person, average person or describing the person's specific trait, experience, or behavior"],score:s.SCORES.NON_INCLUSIVE,feedbackFormat:n.redHarmful,rule:(e,t)=>(0,i.includesConsecutiveWords)(e,t).filter((0,a.isNotPrecededByException)(e,["mentally","behaviorally","behaviourally"])),ruleDescription:(0,o.notPreceded)(["mentally","behaviorally","behaviourally"])},{identifier:"normalPeople",nonInclusivePhrases:["normal people","Normal people"],inclusiveAlternatives:["typical people, average people or describing people's specific trait, experience, or behavior"],score:s.SCORES.NON_INCLUSIVE,feedbackFormat:n.redHarmful,caseSensitive:!0,rule:(e,t)=>(0,i.includesConsecutiveWords)(e,t).filter((0,a.isNotPrecededByException)(e,["mentally","behaviorally","behaviourally"])),ruleDescription:(0,o.notPreceded)(["mentally","behaviorally","behaviourally"])},{identifier:"mentallyNormal",nonInclusivePhrases:["mentally normal"],inclusiveAlternatives:["people without mental health conditions, mentally healthy people"],score:s.SCORES.NON_INCLUSIVE,feedbackFormat:[n.avoidHarmful,"Consider using an alternative, such as %2$s. If possible, be more specific. For example: people who don’t have anxiety disorders, people who haven't experienced trauma, etc. Be careful when using mental health descriptors and try to avoid making assumptions about someone's mental health."].join(" ")},{identifier:"behaviorallyNormal",nonInclusivePhrases:["behaviorally normal","behaviourally normal"],inclusiveAlternatives:["showing typical behavior or describing the specific behavior"],score:s.SCORES.POTENTIALLY_NON_INCLUSIVE,feedbackFormat:n.orangeUnlessAnimalsObjects},{identifier:"abnormalPerson",nonInclusivePhrases:["abnormal person"],inclusiveAlternatives:["describing the person's specific trait, experience, or behavior"],score:s.SCORES.NON_INCLUSIVE,feedbackFormat:n.redHarmful},{identifier:"abnormalPeople",nonInclusivePhrases:["abnormal people"],inclusiveAlternatives:["describing people's specific trait, experience, or behavior"],score:s.SCORES.NON_INCLUSIVE,feedbackFormat:n.redHarmful},{identifier:"mentallyAbnormal",nonInclusivePhrases:["mentally abnormal"],inclusiveAlternatives:["people with a mental health condition, people with mental health problems"],score:s.SCORES.NON_INCLUSIVE,feedbackFormat:[n.avoidHarmful,"Consider using an alternative, such as %2$s. If possible, be more specific. For example: people who have anxiety disorders, people who have experienced trauma, etc. Be careful when using mental health descriptors and try to avoid making assumptions about someone's mental health."].join(" ")},{identifier:"behaviorallyAbnormal",nonInclusivePhrases:["behaviorally abnormal","behaviourally abnormal"],inclusiveAlternatives:["showing atypical behavior, showing dysfunctional behavior or describing the specific behavior"],score:s.SCORES.POTENTIALLY_NON_INCLUSIVE,feedbackFormat:n.orangeUnlessAnimalsObjects},{identifier:"abnormalBehavior",nonInclusivePhrases:["abnormal behavior","abnormal behaviour"],inclusiveAlternatives:["atypical behavior, unusual behavior or describing the specific behavior"],score:s.SCORES.POTENTIALLY_NON_INCLUSIVE,feedbackFormat:n.orangeUnlessAnimalsObjects}];l.forEach((e=>{e.category="other",e.learnMoreUrl="https://yoa.st/inclusive-language-other"})),t.default=l},28045:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.SCORES=void 0,t.SCORES={NON_INCLUSIVE:3,POTENTIALLY_NON_INCLUSIVE:6}},27540:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var s,n=r(78261),i=r(28045),a=r(88626),o=(s=r(77965))&&s.__esModule?s:{default:s},l=r(29700);const u=[{identifier:"illegalImmigrant",nonInclusivePhrases:["illegal immigrant","illegal alien"],inclusiveAlternatives:"undocumented person, person without papers, immigrant without papers",score:i.SCORES.NON_INCLUSIVE,feedbackFormat:n.redHarmful},{identifier:"illegalImmigrants",nonInclusivePhrases:["illegal immigrants","illegal aliens"],inclusiveAlternatives:"undocumented people, people without papers, immigrants without papers",score:i.SCORES.NON_INCLUSIVE,feedbackFormat:n.redHarmful},{identifier:"povertyStricken",nonInclusivePhrases:["poverty stricken"],inclusiveAlternatives:"people whose income is below the poverty threshold, people with low-income",score:i.SCORES.NON_INCLUSIVE,feedbackFormat:n.redHarmful},{identifier:"welfareReliant",nonInclusivePhrases:["welfare reliant"],inclusiveAlternatives:"receiving welfare",score:i.SCORES.NON_INCLUSIVE,feedbackFormat:n.redHarmful},{identifier:"prostitute",nonInclusivePhrases:["prostitute"],inclusiveAlternatives:"sex worker",score:i.SCORES.POTENTIALLY_NON_INCLUSIVE,feedbackFormat:n.orangeUnlessSomeoneWants},{identifier:"prostitutes",nonInclusivePhrases:["prostitutes"],inclusiveAlternatives:"sex workers",score:i.SCORES.POTENTIALLY_NON_INCLUSIVE,feedbackFormat:n.orangeUnlessSomeoneWants},{identifier:"ex-con",nonInclusivePhrases:["ex-con"],inclusiveAlternatives:"person who has had felony convictions, person who has been incarcerated",score:i.SCORES.NON_INCLUSIVE,feedbackFormat:n.redHarmful},{identifier:"ex-cons",nonInclusivePhrases:["ex-cons"],inclusiveAlternatives:"people who have had felony convictions, people who have been incarcerated",score:i.SCORES.NON_INCLUSIVE,feedbackFormat:n.redHarmful},{identifier:"felon",nonInclusivePhrases:["felon"],inclusiveAlternatives:"person with felony convictions, person who has been incarcerated",score:i.SCORES.NON_INCLUSIVE,feedbackFormat:n.redHarmful},{identifier:"felons",nonInclusivePhrases:["felons"],inclusiveAlternatives:"people with felony convictions, people who have been incarcerated",score:i.SCORES.NON_INCLUSIVE,feedbackFormat:n.redHarmful},{identifier:"ex-offender",nonInclusivePhrases:["ex-offender"],inclusiveAlternatives:"formerly incarcerated person",score:i.SCORES.NON_INCLUSIVE,feedbackFormat:n.redHarmful},{identifier:"ex-offenders",nonInclusivePhrases:["ex-offenders"],inclusiveAlternatives:"formerly incarcerated people",score:i.SCORES.NON_INCLUSIVE,feedbackFormat:n.redHarmful},{identifier:"theHomeless",nonInclusivePhrases:["the homeless"],inclusiveAlternatives:"people experiencing homelessness",score:i.SCORES.NON_INCLUSIVE,feedbackFormat:n.redHarmful,rule:(e,t)=>(0,a.includesConsecutiveWords)(e,t).filter((0,o.default)(e,t)),ruleDescription:l.nonInclusiveWhenStandalone},{identifier:"theUndocumented",nonInclusivePhrases:["the undocumented"],inclusiveAlternatives:"people who are undocumented, undocumented people, people without papers",score:i.SCORES.NON_INCLUSIVE,feedbackFormat:n.redHarmful,rule:(e,t)=>(0,a.includesConsecutiveWords)(e,t).filter((0,o.default)(e,t)),ruleDescription:l.nonInclusiveWhenStandalone},{identifier:"thePoor",nonInclusivePhrases:["the poor"],inclusiveAlternatives:"people whose income is below the poverty threshold, people with low-income",score:i.SCORES.NON_INCLUSIVE,feedbackFormat:n.redHarmful,rule:(e,t)=>(0,a.includesConsecutiveWords)(e,t).filter((0,o.default)(e,t)),ruleDescription:l.nonInclusiveWhenStandalone}];u.forEach((e=>{e.category="ses",e.learnMoreUrl="https://yoa.st/inclusive-language-ses"})),t.default=u},32296:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var s=r(28045),n=r(78261);const i=[{identifier:"homosexuals",nonInclusivePhrases:["homosexuals"],inclusiveAlternatives:"gay people, queer people, lesbians, gay men, people in same-gender relationships",score:s.SCORES.POTENTIALLY_NON_INCLUSIVE,feedbackFormat:[n.orangeUnlessSomeoneWants,"Be as specific possible and use people's preferred labels if they are known."].join(" ")}];i.forEach((e=>{e.category="sexualOrientation",e.learnMoreUrl="https://yoa.st/inclusive-language-orientation"})),t.default=i},29700:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.isPreceded=function(e){return"Targeted when preceded by '"+e.join("', '")+"'."},t.nonInclusiveWhenStandalone=void 0,t.notFollowed=function(e){return"Targeted unless followed by '"+e.join("', '")+"'."},t.notPreceded=function(e){return"Targeted unless preceded by '"+e.join("', '")+"'."},t.notPrecededAndNotFollowed=function(e,t){return"Targeted unless preceded by '"+e.join("', '")+"' and/or followed by '"+t.join("', '")+"'."},t.nonInclusiveWhenStandalone="Targeted when followed by a participle, a function word (other than a noun), or punctuation."},88626:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.includesConsecutiveWords=function(e,t){const r=[];return e.forEach(((n,i)=>{(0,s.includesWordsAtPosition)(t,i,e)&&r.push(i)})),r};var s=r(40880)},40880:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.includesWordsAtPosition=function(e,t,r){return e.every(((e,s)=>r[t+s]===e))}},5719:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.isFollowedAndPrecededByException=i,t.isNotFollowedAndPrecededByException=function(e,t,r,s){return n=>!i(e,t,r,s)(n)};var s=r(64948),n=r(17864);function i(e,t,r,i){return a=>(0,s.isFollowedByException)(e,t,i)(a)&&(0,n.isPrecededByException)(e,r)(a)}},64948:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.isFollowedByException=i,t.isNotFollowedByException=function(e,t,r){return s=>!i(e,t,r)(s)};var s=r(58677),n=r(40880);function i(e,t,r){const i=r.map((e=>(0,s.getWords)(e,"\\s",!1)));return r=>i.some((s=>{const i=r+t.length;return i>=0&&(0,n.includesWordsAtPosition)(s,i,e)}))}},88883:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.isFollowedByParticiple=function(e,t){return r=>{const s=r+t.length;return s{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.isNotPrecededByException=function(e,t){return r=>!i(e,t)(r)},t.isPrecededByException=i;var s=r(58677),n=r(40880);function i(e,t){const r=t.map((e=>(0,s.getWords)(e,"\\s",!1)));return t=>r.some((r=>{const s=t-r.length;return s>=0&&(0,n.includesWordsAtPosition)(r,s,e)}))}},77965:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e,t){return r=>(0,s.isFollowedByException)(e,t,i.nonNouns)(r)||(0,n.isFollowedByParticiple)(e,t)(r)||(0,s.isFollowedByException)(e,t,l)(r)};var s=r(64948),n=r(88883),i=r(89456),a=r(8737),o=r(58677);const l=a.punctuationList.filter((e=>(0,o.getWords)(e,"\\s",!1).length>0))},43947:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.seo=t.readability=t.inclusiveLanguage=void 0;var s=F(r(40774)),n=F(r(25636)),i=F(r(38196)),a=F(r(86089)),o=F(r(7261)),l=F(r(35780)),u=F(r(62318)),c=F(r(57749)),d=F(r(50791)),h=F(r(97758)),f=F(r(3139)),p=F(r(92922)),g=F(r(90575)),m=F(r(99815)),_=function(e,t){if(e&&e.__esModule)return e;if(null===e||"object"!=typeof e&&"function"!=typeof e)return{default:e};var r=x(t);if(r&&r.has(e))return r.get(e);var s={__proto__:null},n=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var i in e)if("default"!==i&&{}.hasOwnProperty.call(e,i)){var a=n?Object.getOwnPropertyDescriptor(e,i):null;a&&(a.get||a.set)?Object.defineProperty(s,i,a):s[i]=e[i]}return s.default=e,r&&r.set(e,s),s}(r(70966)),T=F(r(34847)),E=F(r(70476)),y=F(r(27661)),A=F(r(46430)),v=F(r(47502)),b=F(r(17915)),O=F(r(69360)),S=F(r(57480)),I=F(r(46787)),C=r(80009),N=F(r(8980)),D=F(r(38754)),w=F(r(58850)),P=F(r(77428)),R=F(r(40826)),k=F(r(76369)),L=F(r(11842)),M=F(r(96682));function x(e){if("function"!=typeof WeakMap)return null;var t=new WeakMap,r=new WeakMap;return(x=function(e){return e?r:t})(e)}function F(e){return e&&e.__esModule?e:{default:e}}t.readability={ListAssessment:h.default,ParagraphTooLongAssessment:s.default,PassiveVoiceAssessment:n.default,SentenceBeginningsAssessment:i.default,SentenceLengthInTextAssessment:a.default,SubheadingDistributionTooLongAssessment:o.default,TextAlignmentAssessment:c.default,TextPresenceAssessment:l.default,TransitionWordsAssessment:u.default,WordComplexityAssessment:d.default},t.seo={FunctionWordsInKeyphraseAssessment:f.default,ImageAltTagsAssessment:R.default,ImageCountAssessment:D.default,ImageKeyphraseAssessment:N.default,InternalLinksAssessment:p.default,IntroductionKeywordAssessment:g.default,KeyphraseDistributionAssessment:w.default,KeyphraseInSEOTitleAssessment:I.default,KeyphraseLengthAssessment:m.default,KeyphraseDensityAssessment:_.default,KeywordDensityAssessment:_.KeywordDensityAssessment,MetaDescriptionKeywordAssessment:T.default,MetaDescriptionLengthAssessment:E.default,OutboundLinksAssessment:y.default,PageTitleWidthAssessment:A.default,ProductIdentifiersAssessment:k.default,ProductSKUAssessment:L.default,SingleH1Assessment:v.default,SubheadingsKeywordAssessment:b.default,TextCompetingLinksAssessment:O.default,TextLengthAssessment:S.default,TextTitleAssessment:P.default,SlugKeywordAssessment:C.SlugKeywordAssessment,UrlKeywordAssessment:C.UrlKeywordAssessment},t.inclusiveLanguage={InclusiveLanguageAssessment:M.default}},97758:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var s=r(92819),n=o(r(9017)),i=o(r(73054)),a=r(49061);function o(e){return e&&e.__esModule?e:{default:e}}class l extends n.default{constructor(e={}){super(),this._config=(0,s.merge)({urlTitle:"https://yoa.st/shopify38",urlCallToAction:"https://yoa.st/shopify39",scores:{bad:3,good:9},callbacks:{}},e),this.identifier="listsPresence"}findList(e){return e.getTree().findAll((e=>"ul"===e.name||"ol"===e.name)).some((e=>e.childNodes.some((e=>"li"===e.name&&e.childNodes.some((e=>"p"===e.name))))))}getResult(e){this.textContainsList=this.findList(e);const t=this.calculateResult(),r=new i.default;return r.setScore(t.score),r.setText(t.resultText),r}calculateResult(){const{good:e,bad:t}=this.getFeedbackStrings();return this.textContainsList?{score:this._config.scores.good,resultText:e}:{score:this._config.scores.bad,resultText:t}}getFeedbackStrings(){const e=(0,a.createAnchorOpeningTag)(this._config.urlTitle),t=(0,a.createAnchorOpeningTag)(this._config.urlCallToAction);if(!this._config.callbacks.getResultTexts){const r={good:"%1$sLists%3$s: There is at least one list on this page. Great!",bad:"%1$sLists%3$s: No lists appear on this page. %2$sAdd at least one ordered or unordered list%3$s!"};return(0,s.mapValues)(r,(r=>this.formatResultText(r,e,t)))}return this._config.callbacks.getResultTexts({urlTitleAnchorOpeningTag:e,urlActionAnchorOpeningTag:t})}}t.default=l},40774:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var s=r(65736),n=r(92819),i=r(49061),a=r(76663),o=c(r(73054)),l=c(r(41054)),u=c(r(9017));function c(e){return e&&e.__esModule?e:{default:e}}class d extends u.default{constructor(e={},t=!1){super();const r={urlTitle:(0,i.createAnchorOpeningTag)("https://yoa.st/35d"),urlCallToAction:(0,i.createAnchorOpeningTag)("https://yoa.st/35e"),countCharacters:!1,parameters:{recommendedLength:150,maximumRecommendedLength:200}};this.identifier="textParagraphTooLong",this._config=(0,n.merge)(r,e),this._isProduct=t}getTooLongParagraphs(e,t){return e.filter((e=>e.paragraphLength>t.parameters.recommendedLength))}getConfig(e){const t=this._config,r=e.getConfig("paragraphLength");return r&&(t.parameters=this._isProduct?r.productPageParams:r.defaultPageParams),t}getScore(e,t){if(0===e.length)return 9;const r=e.sort(((e,t)=>t.paragraphLength-e.paragraphLength))[0].paragraphLength;let s;return r<=t.parameters.recommendedLength&&(s=9),(0,a.inRangeEndInclusive)(r,t.parameters.recommendedLength,t.parameters.maximumRecommendedLength)&&(s=6),r>t.parameters.maximumRecommendedLength&&(s=3),s}calculateResult(e,t){const r=this.getTooLongParagraphs(e,t),n=new o.default,i=this.getScore(e,t);if(n.setScore(i),i>=7)return n.setHasMarks(!1),n.setText((0,s.sprintf)(/* translators: %1$s expands to a link on yoast.com, %2$s expands to the anchor end tag */
+(0,s.__)("%1$sParagraph length%2$s: There are no paragraphs that are too long. Great job!","wordpress-seo"),t.urlTitle,"")),n;const a=(0,s.sprintf)(
+/* translators: %1$s and %5$s expand to links on yoast.com, %2$s expands to the anchor end tag,
+ %3$d expands to the number of paragraphs over the recommended limit, %4$d expands to the limit. */
+(0,s._n)("%1$sParagraph length%2$s: %3$d of the paragraphs contains more than the recommended maximum number of words (%4$d). %5$sShorten your paragraphs%2$s!","%1$sParagraph length%2$s: %3$d of the paragraphs contain more than the recommended maximum number of words (%4$d). %5$sShorten your paragraphs%2$s!",r.length,"wordpress-seo"),t.urlTitle,"",r.length,t.parameters.recommendedLength,t.urlCallToAction),l=(0,s.sprintf)(
+/* translators: %1$s and %5$s expand to links on yoast.com, %2$s expands to the anchor end tag,
+ %3$d expands to the number of paragraphs over the recommended limit, %4$d expands to the limit. */
+(0,s._n)("%1$sParagraph length%2$s: %3$d of the paragraphs contains more than the recommended maximum number of characters (%4$d). %5$sShorten your paragraphs%2$s!","%1$sParagraph length%2$s: %3$d of the paragraphs contain more than the recommended maximum number of characters (%4$d). %5$sShorten your paragraphs%2$s!",r.length,"wordpress-seo"),t.urlTitle,"",r.length,t.parameters.recommendedLength,t.urlCallToAction);return n.setHasMarks(!0),n.setText(t.countCharacters?l:a),n.setHasAIFixes(!0),n}getMarks(e,t){const r=t.getResearch("getParagraphLength");return this.getTooLongParagraphs(r,this.getConfig(t)).flatMap((({paragraph:e})=>{const t=e.sourceCodeLocation;return new l.default({position:{startOffset:t.startTag?t.startTag.endOffset:t.startOffset,endOffset:t.endTag?t.endTag.startOffset:t.endOffset,startOffsetBlock:0,endOffsetBlock:t.endOffset-t.startOffset,clientId:e.clientId||"",attributeId:e.attributeId||"",isFirstSection:e.isFirstSection||!1}})}))}getResult(e,t){const r=t.getResearch("getParagraphLength");return this._config.countCharacters=!!t.getConfig("countCharacters"),this.calculateResult(r,this.getConfig(t))}}t.default=d},25636:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var s=r(65736),n=r(92819),i=f(r(17179)),a=r(76663),o=f(r(15010)),l=r(49061),u=r(62240),c=f(r(73054)),d=f(r(41054)),h=f(r(9017));function f(e){return e&&e.__esModule?e:{default:e}}class p extends h.default{constructor(e={}){super();const t={urlTitle:(0,l.createAnchorOpeningTag)("https://yoa.st/34t"),urlCallToAction:(0,l.createAnchorOpeningTag)("https://yoa.st/34u")};this.identifier="passiveVoice",this._config=(0,n.merge)(t,e)}calculatePassiveVoiceResult(e){let t=0,r=0;0!==e.total&&(r=(0,i.default)(e.passives.length/e.total*100));const n=r>0;return r<=10&&(t=9),(0,a.inRangeEndInclusive)(r,10,15)&&(t=6),r>15&&(t=3),t>=7?{score:t,hasMarks:n,text:(0,s.sprintf)(/* translators: %1$s expands to a link on yoast.com, %2$s expands to the anchor end tag. */
+(0,s.__)("%1$sPassive voice%2$s: You are not using too much passive voice. That's great!","wordpress-seo"),this._config.urlTitle,"")}:{score:t,hasMarks:n,text:(0,s.sprintf)(
+/* translators: %1$s and %5$s expand to a link on yoast.com, %2$s expands to the anchor end tag,
+ %3$s expands to the percentage of sentences in passive voice, %4$s expands to the recommended value. */
+(0,s.__)("%1$sPassive voice%2$s: %3$s of the sentences contain passive voice, which is more than the recommended maximum of %4$s. %5$sTry to use their active counterparts%2$s.","wordpress-seo"),this._config.urlTitle,"",r+"%","10%",this._config.urlCallToAction)}}getMarks(e,t){const r=t.getResearch("getPassiveVoiceResult");return(0,n.map)(r.passives,(function(e){e=(0,u.stripIncompleteTags)(e);const t=(0,o.default)(e);return new d.default({original:e,marked:t})}))}getResult(e,t){const r=t.getResearch("getPassiveVoiceResult"),s=this.calculatePassiveVoiceResult(r),n=new c.default;return n.setScore(s.score),n.setText(s.text),n.setHasMarks(s.hasMarks),n}isApplicable(e,t){return t.hasResearch("getPassiveVoiceResult")}}t.default=p},38196:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var s=r(92819),n=r(65736),i=r(49061),a=u(r(73054)),o=u(r(41054)),l=u(r(9017));function u(e){return e&&e.__esModule?e:{default:e}}class c extends l.default{constructor(e={}){super();const t={urlTitle:(0,i.createAnchorOpeningTag)("https://yoa.st/35f"),urlCallToAction:(0,i.createAnchorOpeningTag)("https://yoa.st/35g")};this.identifier="sentenceBeginnings",this._config=(0,s.merge)(t,e)}groupSentenceBeginnings(e){const t=(0,s.partition)(e,(e=>e.count>2));if(0===t[0].length)return{total:0,lowestCount:0};const r=(0,s.sortBy)(t[0],(e=>e.count));return{total:t[0].length,lowestCount:r[0].count}}calculateSentenceBeginningsResult(e){const t=new a.default;return e.total>0?(t.setScore(3),t.setHasMarks(!0),t.setText((0,n.sprintf)(
+/* translators: %1$s and %5$s expand to a link on yoast.com, %2$s expands to the anchor end tag,
+ %3$d expands to the number of consecutive sentences starting with the same word,
+ %4$d expands to the number of instances where 3 or more consecutive sentences start with the same word. */
+(0,n._n)("%1$sConsecutive sentences%2$s: The text contains %3$d consecutive sentences starting with the same word. %5$sTry to mix things up%2$s!","%1$sConsecutive sentences%2$s: The text contains %4$d instances where %3$d or more consecutive sentences start with the same word. %5$sTry to mix things up%2$s!",e.total,"wordpress-seo"),this._config.urlTitle,"",e.lowestCount,e.total,this._config.urlCallToAction))):(t.setScore(9),t.setHasMarks(!1),t.setText((0,n.sprintf)(/* translators: %1$s expands to a link on yoast.com, %2$s expands to the anchor end tag */
+(0,n.__)("%1$sConsecutive sentences%2$s: There are no repetitive sentence beginnings. That's great!","wordpress-seo"),this._config.urlTitle,""))),t}getMarks(e,t){return t.getResearch("getSentenceBeginnings").filter((e=>e.count>2)).flatMap((e=>e.sentences)).map((e=>{var t,r;const s=(null===(t=e.getFirstToken())||void 0===t?void 0:t.sourceCodeRange.startOffset)||0,n=(null===(r=e.getLastToken())||void 0===r?void 0:r.sourceCodeRange.endOffset)||0;return new o.default({position:{startOffset:s,endOffset:n,startOffsetBlock:s-(e.parentStartOffset||0),endOffsetBlock:n-(e.parentStartOffset||0),clientId:e.parentClientId||"",attributeId:e.parentAttributeId||"",isFirstSection:e.isParentFirstSectionOfBlock||!1}})}))}getResult(e,t){const r=t.getResearch("getSentenceBeginnings"),s=this.groupSentenceBeginnings(r);return this.calculateSentenceBeginningsResult(s)}isApplicable(e,t){return t.hasResearch("getSentenceBeginnings")}}t.default=c},86089:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var s=r(65736),n=r(92819),i=d(r(9017)),a=d(r(17179)),o=r(76663),l=r(49061),u=d(r(73054)),c=d(r(41054));function d(e){return e&&e.__esModule?e:{default:e}}class h extends i.default{constructor(e={},t=!1,r=!1){super();const s={recommendedLength:20,slightlyTooMany:25,farTooMany:30,urlTitle:(0,l.createAnchorOpeningTag)("https://yoa.st/34v"),urlCallToAction:(0,l.createAnchorOpeningTag)("https://yoa.st/34w"),countCharacters:!1};this._config=(0,n.merge)(s,e),this._isCornerstone=t,this._isProduct=r,this.identifier="textSentenceLength"}getResult(e,t){const r=t.getResearch("countSentencesFromText");t.getConfig("sentenceLength")&&(this._config=this.getLanguageSpecificConfig(t)),this._config.countCharacters=!!t.getConfig("countCharacters");const s=this.calculatePercentage(r),n=this.calculateScore(s),i=new u.default;return n<9&&i.setHasAIFixes(!0),i.setScore(n),i.setText(this.translateScore(n,s)),i.setHasMarks(s>0),i}getMarks(e,t){const r=t.getResearch("countSentencesFromText");return t.getConfig("sentenceLength")&&(this._config=this.getLanguageSpecificConfig(t)),this.getTooLongSentences(r).map((e=>{const{sentence:t,firstToken:r,lastToken:s}=e,n=r.sourceCodeRange.startOffset,i=s.sourceCodeRange.endOffset;return new c.default({position:{startOffset:n,endOffset:i,startOffsetBlock:n-(t.parentStartOffset||0),endOffsetBlock:i-(t.parentStartOffset||0),clientId:t.parentClientId||"",attributeId:t.parentAttributeId||"",isFirstSection:t.isParentFirstSectionOfBlock||!1}})}))}getLanguageSpecificConfig(e){const t=this._config,r=e.getConfig("sentenceLength");return r.hasOwnProperty("recommendedLength")&&(t.recommendedLength=r.recommendedLength),!0===this._isCornerstone&&!1===this._isProduct&&r.hasOwnProperty("cornerstonePercentages")?(0,n.merge)(t,r.cornerstonePercentages):!1===this._isCornerstone&&!1===this._isProduct&&r.hasOwnProperty("percentages")?(0,n.merge)(t,r.percentages):t}translateScore(e,t){if(e>=7)return(0,s.sprintf)(/* translators: %1$s expands to a link on yoast.com, %2$s expands to the anchor end tag */
+(0,s.__)("%1$sSentence length%2$s: Great!","wordpress-seo"),this._config.urlTitle,"");const r=(0,s.sprintf)(
+/* translators: %1$s and %6$s expand to links on yoast.com, %2$s expands to the anchor end tag,
+ %3$s expands to percentage of sentences, %4$d expands to the recommended maximum sentence length,
+ %5$s expands to the recommended maximum percentage. */
+(0,s._n)("%1$sSentence length%2$s: %3$s of the sentences contain more than %4$d word, which is more than the recommended maximum of %5$s. %6$sTry to shorten the sentences%2$s.","%1$sSentence length%2$s: %3$s of the sentences contain more than %4$d words, which is more than the recommended maximum of %5$s. %6$sTry to shorten the sentences%2$s.",this._config.recommendedLength,"wordpress-seo"),this._config.urlTitle,"",t+"%",this._config.recommendedLength,this._config.slightlyTooMany+"%",this._config.urlCallToAction),n=(0,s.sprintf)(
+/* translators: %1$s and %6$s expand to links on yoast.com, %2$s expands to the anchor end tag,
+ %3$s expands to percentage of sentences, %4$d expands to the recommended maximum sentence length,
+ %5$s expands to the recommended maximum percentage. */
+(0,s._n)("%1$sSentence length%2$s: %3$s of the sentences contain more than %4$d character, which is more than the recommended maximum of %5$s. %6$sTry to shorten the sentences%2$s.","%1$sSentence length%2$s: %3$s of the sentences contain more than %4$d characters, which is more than the recommended maximum of %5$s. %6$sTry to shorten the sentences%2$s.",this._config.recommendedLength,"wordpress-seo"),this._config.urlTitle,"",t+"%",this._config.recommendedLength,this._config.slightlyTooMany+"%",this._config.urlCallToAction);return this._config.countCharacters?n:r}calculatePercentage(e){let t=0;if(0!==e.length){const r=this.getTooLongSentences(e).length;t=(0,a.default)(r/e.length*100)}return t}calculateScore(e){let t;return e<=this._config.slightlyTooMany&&(t=9),(0,o.inRangeEndInclusive)(e,this._config.slightlyTooMany,this._config.farTooMany)&&(t=6),e>this._config.farTooMany&&(t=3),t}getTooLongSentences(e){return e.filter((e=>e.sentenceLength>this._config.recommendedLength))}}t.default=h},7261:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var s=r(65736),n=r(92819),i=m(r(15010)),a=m(r(41054)),o=m(r(9017)),l=r(76663),u=r(49061),c=r(84285),d=m(r(1105)),h=m(r(73054)),f=r(62240),p=m(r(96908)),g=r(29866);function m(e){return e&&e.__esModule?e:{default:e}}class _ extends o.default{constructor(e={}){super();const t={parameters:{recommendedMaximumLength:300,slightlyTooMany:300,farTooMany:350},urlTitle:(0,u.createAnchorOpeningTag)("https://yoa.st/34x"),urlCallToAction:(0,u.createAnchorOpeningTag)("https://yoa.st/34y"),scores:{goodShortTextNoSubheadings:9,goodSubheadings:9,okSubheadings:6,badSubheadings:3,badLongTextNoSubheadings:2},cornerstoneContent:!1,countCharacters:!1};this.identifier="subheadingsTooLong",this._config=(0,n.merge)(t,e)}checkTextBeforeFirstSubheadingLength(e){let t={isLong:!1,isVeryLong:!1};if(e.length>0&&""===e[0].subheading&&""!==e[0].text){const r=e[0].countLength;t={isLong:(0,l.inRangeEndInclusive)(r,this._config.parameters.slightlyTooMany,this._config.parameters.farTooMany),isVeryLong:r>this._config.parameters.farTooMany}}return t}getTextLength(e,t){const r=t.getHelper("customCountLength");let s=e.getText();return s=(0,p.default)(s),s=(0,g.filterShortcodesFromHTML)(s,e._attributes&&e._attributes.shortcodes),r?r(s):(0,d.default)(s).length}getResult(e,t){this._subheadingTextsLength=t.getResearch("getSubheadingTextLengths"),t.getConfig("subheadingsTooLong")&&(this._config=this.getLanguageSpecificConfig(t)),this._config.countCharacters=!!t.getConfig("countCharacters"),this._hasSubheadings=this.hasSubheadings(e),this._tooLongTextsNumber=this.getTooLongSubheadingTexts().length,this._textLength=this.getTextLength(e,t);const r=this.checkTextBeforeFirstSubheadingLength(this._subheadingTextsLength);this._subheadingTextsLength=this._subheadingTextsLength.sort(((e,t)=>t.countLength-e.countLength));const s=this.calculateResult(r),n=new h.default;return n.setIdentifier(this.identifier),n.setScore(s.score),n.setText(s.resultText),n.setHasMarks(s.hasMarks),n}getLanguageSpecificConfig(e){const t=this._config,r=e.getConfig("subheadingsTooLong");return!0===t.cornerstoneContent&&Object.hasOwn(r,"cornerstoneParameters")?(0,n.merge)(t,r.cornerstoneParameters):(0,n.merge)(t,r.defaultParameters)}hasSubheadings(e){return(0,c.getSubheadings)(e.getText()).length>0}getMarks(){return this.getTooLongSubheadingTexts().map((({subheading:e})=>{e=(0,f.stripFullTags)(e);const t=(0,i.default)(e);return new a.default({original:e,marked:t,fieldsToMark:["heading"]})})).filter((e=>""!==e.getOriginal()))}getTooLongSubheadingTexts(){return this._subheadingTextsLength.filter((e=>e.countLength>this._config.parameters.recommendedMaximumLength))}getFeedbackTexts(){return{beginning:e=>{const t=(0,s.sprintf)(/* translators: %1$s and %3$s expand to links on yoast.com, %2$s expands to the anchor end tag, %4$s expands to the recommended maximum length of a text without subheading. */
+(0,s._n)("%1$sSubheading distribution%2$s: The beginning of your text is longer than %4$d word and is not separated by any subheadings. %3$sAdd subheadings to improve readability.%2$s","%1$sSubheading distribution%2$s: The beginning of your text is longer than %4$d words and is not separated by any subheadings. %3$sAdd subheadings to improve readability.%2$s",this._config.parameters.recommendedMaximumLength,"wordpress-seo"),this._config.urlTitle,"",this._config.urlCallToAction,this._config.parameters.recommendedMaximumLength),r=(0,s.sprintf)(/* translators: %1$s and %3$s expand to links on yoast.com, %2$s expands to the anchor end tag, %4$s expands to the recommended maximum length of a text without subheading. */
+(0,s._n)("%1$sSubheading distribution%2$s: The beginning of your text is longer than %4$d character and is not separated by any subheadings. %3$sAdd subheadings to improve readability.%2$s","%1$sSubheading distribution%2$s: The beginning of your text is longer than %4$d characters and is not separated by any subheadings. %3$sAdd subheadings to improve readability.%2$s",this._config.parameters.recommendedMaximumLength,"wordpress-seo"),this._config.urlTitle,"",this._config.urlCallToAction,this._config.parameters.recommendedMaximumLength);return e?r:t},nonBeginning:e=>{const t=(0,s.sprintf)(/* translators: %1$s and %5$s expand to links on yoast.com, %2$s expands to the anchor end tag, %3$d expands to the number of sections that are too long, %4$s expands to the recommended maximum length of a text without subheading. */
+(0,s._n)("%1$sSubheading distribution%2$s: %3$d section of your text is longer than the recommended number of words (%4$d) and is not separated by any subheadings. %5$sAdd subheadings to improve readability%2$s.","%1$sSubheading distribution%2$s: %3$d sections of your text are longer than the recommended number of words (%4$d) and are not separated by any subheadings. %5$sAdd subheadings to improve readability%2$s.",this._tooLongTextsNumber,"wordpress-seo"),this._config.urlTitle,"",this._tooLongTextsNumber,this._config.parameters.recommendedMaximumLength,this._config.urlCallToAction),r=(0,s.sprintf)(/* translators: %1$s and %5$s expand to links on yoast.com, %2$s expands to the anchor end tag, %3$d expands to the number of sections that are too long, %4$s expands to the recommended maximum length of a text without subheading. */
+(0,s._n)("%1$sSubheading distribution%2$s: %3$d section of your text is longer than the recommended number of characters (%4$d) and is not separated by any subheadings. %5$sAdd subheadings to improve readability%2$s.","%1$sSubheading distribution%2$s: %3$d sections of your text are longer than the recommended number of characters (%4$d) and are not separated by any subheadings. %5$sAdd subheadings to improve readability%2$s.",this._tooLongTextsNumber,"wordpress-seo"),this._config.urlTitle,"",this._tooLongTextsNumber,this._config.parameters.recommendedMaximumLength,this._config.urlCallToAction);return e?r:t}}}calculateResultForLongTextWithoutSubheadings(e){const t=this.getFeedbackTexts();if(this._hasSubheadings){if(e.isLong&&this._tooLongTextsNumber<2)return{score:this._config.scores.okSubheadings,hasMarks:!1,resultText:t.beginning(this._config.countCharacters)};if(e.isVeryLong&&this._tooLongTextsNumber<2)return{score:this._config.scores.badSubheadings,hasMarks:!1,resultText:t.beginning(this._config.countCharacters)};const r=this._subheadingTextsLength[0].countLength;return r<=this._config.parameters.slightlyTooMany?{score:this._config.scores.goodSubheadings,hasMarks:!1,resultText:(0,s.sprintf)(/* translators: %1$s expands to a link on yoast.com, %2$s expands to the anchor end tag */
+(0,s.__)("%1$sSubheading distribution%2$s: Great job!","wordpress-seo"),this._config.urlTitle,"")}:(0,l.inRangeEndInclusive)(r,this._config.parameters.slightlyTooMany,this._config.parameters.farTooMany)?{score:this._config.scores.okSubheadings,hasMarks:!0,resultText:t.nonBeginning(this._config.countCharacters)}:{score:this._config.scores.badSubheadings,hasMarks:!0,resultText:t.nonBeginning(this._config.countCharacters)}}return{score:this._config.scores.badLongTextNoSubheadings,hasMarks:!1,resultText:(0,s.sprintf)(/* translators: %1$s expands to a link on yoast.com, %2$s expands to the anchor end tag */
+(0,s.__)("%1$sSubheading distribution%2$s: You are not using any subheadings, although your text is rather long. %3$sTry and add some subheadings%2$s.","wordpress-seo"),this._config.urlTitle,"",this._config.urlCallToAction)}}calculateResult(e){return this._textLength>this._config.parameters.recommendedMaximumLength?this.calculateResultForLongTextWithoutSubheadings(e):this._hasSubheadings?{score:this._config.scores.goodSubheadings,hasMarks:!1,resultText:(0,s.sprintf)(/* translators: %1$s expands to a link on yoast.com, %2$s expands to the anchor end tag */
+(0,s.__)("%1$sSubheading distribution%2$s: Great job!","wordpress-seo"),this._config.urlTitle,"")}:{score:this._config.scores.goodShortTextNoSubheadings,hasMarks:!1,resultText:(0,s.sprintf)(/* translators: %1$s expands to a link on yoast.com, %2$s expands to the anchor end tag */
+(0,s.__)("%1$sSubheading distribution%2$s: You are not using any subheadings, but your text is short enough and probably doesn't need them.","wordpress-seo"),this._config.urlTitle,"")}}}t.default=_},57749:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var s=r(92819),n=l(r(9017)),i=l(r(41054)),a=l(r(73054)),o=r(49061);function l(e){return e&&e.__esModule?e:{default:e}}class u extends n.default{constructor(e={}){super(),this._config=(0,s.merge)({urlTitle:"https://yoa.st/assessment-alignment",urlCallToAction:"https://yoa.st/assessment-alignment-cta",scores:{bad:2},callbacks:{}},e),this.identifier="textAlignment"}getResult(e,t){const r=t.getResearch("getLongCenterAlignedTexts");this.numberOfLongCenterAlignedTexts=r.length;const s=new a.default;if(0===this.numberOfLongCenterAlignedTexts)return s;const n=this.calculateResult(e,this.numberOfLongCenterAlignedTexts);return s.setScore(n.score),s.setText(n.resultText),s.setHasMarks(!0),s}getMarks(e,t){return t.getResearch("getLongCenterAlignedTexts").map((e=>new i.default({position:{clientId:e.clientId||"",startOffset:e.sourceCodeLocation.startOffset,endOffset:e.sourceCodeLocation.endOffset,startOffsetBlock:0,endOffsetBlock:e.sourceCodeLocation.endOffset-e.sourceCodeLocation.startOffset}})))}isApplicable(e,t){return t.hasResearch("getLongCenterAlignedTexts")}calculateResult(e,t){const{rightToLeft:r,leftToRight:s}=this.getFeedbackStrings();if(t>0)return"RTL"===e.getWritingDirection()?{score:this._config.scores.bad,resultText:r}:{score:this._config.scores.bad,resultText:s}}getFeedbackStrings(){const e=(0,o.createAnchorOpeningTag)(this._config.urlTitle),t=(0,o.createAnchorOpeningTag)(this._config.urlCallToAction);if(!this._config.callbacks.getResultTexts){const r={rightToLeft:"%1$sAlignment%3$s: There are long sections of center-aligned text. %2$sWe recommend making them right-aligned%3$s.",leftToRight:"%1$sAlignment%3$s: There are long sections of center-aligned text. %2$sWe recommend making them left-aligned%3$s."};return 1===this.numberOfLongCenterAlignedTexts&&(r.rightToLeft="%1$sAlignment%3$s: There is a long section of center-aligned text. %2$sWe recommend making it right-aligned%3$s.",r.leftToRight="%1$sAlignment%3$s: There is a long section of center-aligned text. %2$sWe recommend making it left-aligned%3$s."),(0,s.mapValues)(r,(r=>this.formatResultText(r,e,t)))}return this._config.callbacks.getResultTexts({urlTitleAnchorOpeningTag:e,urlActionAnchorOpeningTag:t,numberOfLongCenterAlignedTexts:this.numberOfLongCenterAlignedTexts})}}t.default=u},35780:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var s=r(65736),n=r(33140),i=l(r(73054)),a=l(r(9017)),o=r(92819);function l(e){return e&&e.__esModule?e:{default:e}}class u extends a.default{constructor(e={}){super();const t={urlTitle:(0,n.createAnchorOpeningTag)("https://yoa.st/35h"),urlCallToAction:(0,n.createAnchorOpeningTag)("https://yoa.st/35i")};this.identifier="textPresence",this._config=(0,o.merge)(t,e)}getResult(e){if(!this.hasEnoughContentForAssessment(e)){const e=new i.default;return e.setText((0,s.sprintf)(
+/* translators: %1$s and %3$s expand to links to articles on Yoast.com,
+ %2$s expands to the anchor end tag*/
+(0,s.__)("%1$sNot enough content%2$s: %3$sPlease add some content to enable a good analysis%2$s.","wordpress-seo"),this._config.urlTitle,"",this._config.urlCallToAction)),e.setScore(3),e}return new i.default}}t.default=u},62318:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var s=r(65736),n=r(92819),i=m(r(17179)),a=r(76663),o=r(33140),l=r(62240),u=m(r(73054)),c=m(r(41054)),d=m(r(15010)),h=m(r(9017)),f=m(r(96908)),p=m(r(1105)),g=r(29866);function m(e){return e&&e.__esModule?e:{default:e}}class _ extends h.default{constructor(e={}){super();const t={urlTitle:(0,o.createAnchorOpeningTag)("https://yoa.st/34z"),urlCallToAction:(0,o.createAnchorOpeningTag)("https://yoa.st/35a"),transitionWordsNeededIfTextLongerThan:200};this.identifier="textTransitionWords",this._config=(0,n.merge)(t,e)}calculateTransitionWordPercentage(e){return 0===e.transitionWordSentences||0===e.totalSentences?0:(0,i.default)(e.transitionWordSentences/e.totalSentences*100)}calculateScoreFromPercentage(e){return e<20?3:(0,a.inRangeStartInclusive)(e,20,30)?6:9}calculateTransitionWordResult(e,t){const r=this.calculateTransitionWordPercentage(e),n=this.calculateScoreFromPercentage(r),a=r>0;return t0?{score:(0,i.default)(9),hasMarks:a,text:(0,s.sprintf)(/* translators: %1$s expands to a link on yoast.com, %2$s expands to the anchor end tag. */
+(0,s.__)("%1$sTransition words%2$s: Well done!","wordpress-seo"),this._config.urlTitle,"")}:{score:(0,i.default)(9),hasMarks:a,text:(0,s.sprintf)(/* translators: %1$s expands to a link on yoast.com, %2$s expands to the anchor end tag. */
+(0,s.__)("%1$sTransition words%2$s: You are not using any transition words, but your text is short enough and probably doesn't need them.","wordpress-seo"),this._config.urlTitle,"")}:n<7&&0===r?{score:(0,i.default)(n),hasMarks:a,text:(0,s.sprintf)(/* translators: %1$s and %3$s expand to a link to yoast.com, %2$s expands to the anchor end tag */
+(0,s.__)("%1$sTransition words%2$s: None of the sentences contain transition words. %3$sUse some%2$s.","wordpress-seo"),this._config.urlTitle,"",this._config.urlCallToAction)}:n<7?{score:(0,i.default)(n),hasMarks:a,text:(0,s.sprintf)(
+/* translators: %1$s and %4$s expand to a link to yoast.com, %2$s expands to the anchor end tag,
+ %3$s expands to the percentage of sentences containing transition words */
+(0,s.__)("%1$sTransition words%2$s: Only %3$s of the sentences contain transition words, which is not enough. %4$sUse more of them%2$s.","wordpress-seo"),this._config.urlTitle,"",r+"%",this._config.urlCallToAction)}:{score:(0,i.default)(n),hasMarks:a,text:(0,s.sprintf)(/* translators: %1$s expands to a link on yoast.com, %2$s expands to the anchor end tag. */
+(0,s.__)("%1$sTransition words%2$s: Well done!","wordpress-seo"),this._config.urlTitle,"")}}getResult(e,t){const r=t.getHelper("customCountLength"),s=t.getConfig("assessmentApplicability").transitionWords;s&&(this._config.transitionWordsNeededIfTextLongerThan=s);let n=e.getText();n=(0,f.default)(n),n=(0,g.filterShortcodesFromHTML)(n,e._attributes&&e._attributes.shortcodes);const i=r?r(n):(0,p.default)(n).length,a=t.getResearch("findTransitionWords"),o=this.calculateTransitionWordResult(a,i),l=new u.default;return l.setScore(o.score),l.setText(o.text),l.setHasMarks(o.hasMarks),l}getMarks(e,t){const r=t.getResearch("findTransitionWords");return(0,n.map)(r.sentenceResults,(function(e){let t=e.sentence;return t=(0,l.stripIncompleteTags)(t),new c.default({original:t,marked:(0,d.default)(t)})}))}isApplicable(e,t){return t.hasResearch("findTransitionWords")}}t.default=_},50791:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var s=r(92819),n=u(r(9017)),i=u(r(41054)),a=u(r(73054)),o=r(49061),l=r(48024);function u(e){return e&&e.__esModule?e:{default:e}}class c extends n.default{constructor(e={}){super(),this.identifier="wordComplexity",this._config=(0,s.merge)({scores:{acceptableAmount:6,goodAmount:9},urlTitle:"https://yoa.st/4ls",urlCallToAction:"https://yoa.st/4lt",callbacks:{}},e)}getResult(e,t){this._wordComplexity=t.getResearch("wordComplexity");const r=this.calculateResult(),s=new a.default;return s.setScore(r.score),s.setText(r.resultText),s.setHasMarks(r.hasMarks),s}calculateResult(){const e=this._wordComplexity.percentage,t=e>0,{goodAmount:r,acceptableAmount:s}=this.getFeedbackStrings();return e<10?{score:this._config.scores.goodAmount,hasMarks:t,resultText:r}:{score:this._config.scores.acceptableAmount,hasMarks:t,resultText:s}}getFeedbackStrings(){const e=(0,o.createAnchorOpeningTag)(this._config.urlTitle),t=(0,o.createAnchorOpeningTag)(this._config.urlCallToAction);if(!this._config.callbacks.getResultTexts){const r={acceptableAmount:"%1$sWord complexity%3$s: Some words in your text are considered complex. %2$sTry to use shorter and more familiar words to improve readability%3$s.",goodAmount:"%1$sWord complexity%3$s: You are not using too many complex words, which makes your text easy to read. Good job!"};return(0,s.mapValues)(r,(r=>this.formatResultText(r,e,t)))}const r=this._wordComplexity.percentage;return this._config.callbacks.getResultTexts({urlTitleAnchorOpeningTag:e,urlActionAnchorOpeningTag:t,complexWordsPercentage:r})}getMarks(e,t){const r=t.getResearch("wordComplexity").complexWords,s=t.getHelper("matchWordCustomHelper"),n=[];return r.forEach((e=>{const t=e.complexWords,r=e.sentence;t.length>0&&n.push(new i.default({original:r,marked:(0,l.collectMarkingsInSentence)(r,t,s)}))})),n}isApplicable(e,t){return t.hasResearch("wordComplexity")}}t.default=c},3139:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var s=r(65736),n=r(92819),i=l(r(9017)),a=r(33140),o=l(r(73054));function l(e){return e&&e.__esModule?e:{default:e}}class u extends i.default{constructor(e={}){super();const t={scores:{onlyFunctionWords:0},urlTitle:(0,a.createAnchorOpeningTag)("https://yoa.st/functionwordskeyphrase-1"),urlCallToAction:(0,a.createAnchorOpeningTag)("https://yoa.st/functionwordskeyphrase-2")};this.identifier="functionWordsInKeyphrase",this._config=(0,n.merge)(t,e)}getResult(e,t){this._functionWordsInKeyphrase=t.getResearch("functionWordsInKeyphrase"),this._keyword=(0,n.escape)(e.getKeyword());const r=new o.default;return this._functionWordsInKeyphrase&&(r.setScore(this._config.scores.onlyFunctionWords),r.setText((0,s.sprintf)(
+/**
+ * translators:
+ * %1$s and %2$s expand to links on yoast.com,
+ * %3$s expands to the anchor end tag,
+ * %4$s expands to the focus keyphrase of the article.
+ */
+(0,s.__)('%1$sFunction words in keyphrase%3$s: Your keyphrase "%4$s" contains function words only. %2$sLearn more about what makes a good keyphrase.%3$s',"wordpress-seo"),this._config.urlTitle,this._config.urlCallToAction,"",this._keyword)),r.setHasJumps(!0),r.setEditFieldName("keyphrase"),r.setEditFieldAriaLabel((0,s.__)("Edit your keyphrase","wordpress-seo"))),r}isApplicable(e,t){return e.hasKeyword()&&t.hasResearch("functionWordsInKeyphrase")}}t.default=u},40826:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var s=r(92819),n=o(r(9017)),i=o(r(73054)),a=r(49061);function o(e){return e&&e.__esModule?e:{default:e}}class l extends n.default{constructor(e={}){super(),this.identifier="imageAltTags",this._config=(0,s.merge)({scores:{bad:3,good:9},urlTitle:"",urlCallToAction:"",callbacks:{}},e)}getResult(e,t){this.altTagsProperties=t.getResearch("altTagCount"),this.imageCount=t.getResearch("imageCount");const r=this.calculateResult(),s=new i.default;return s.setScore(r.score),s.setText(r.resultText),s}calculateResult(){const e=this.altTagsProperties.noAlt,{good:t,noImagesBad:r,noneHasAltBad:s,someHaveAltBad:n}=this.getFeedbackStrings();return 0===this.imageCount?{score:this._config.scores.bad,resultText:r}:e===this.imageCount?{score:this._config.scores.bad,resultText:s}:e>0?{score:this._config.scores.bad,resultText:n}:{score:this._config.scores.good,resultText:t}}getFeedbackStrings(){const e=(0,a.createAnchorOpeningTag)(this._config.urlTitle),t=(0,a.createAnchorOpeningTag)(this._config.urlCallToAction),r=this.altTagsProperties.noAlt;if(!this._config.callbacks.getResultTexts){const n={good:"%1$sImage alt attributes%3$s: All images have alt attributes. Good job!",noneHasAltBad:"%1$sImage alt attributes%3$s: None of the images have alt attributes. %2$sAdd alt attributes to your images%3$s!",noImagesBad:"%1$sImage alt attributes%3$s: This page does not have images with alt attributes. %2$sAdd some%3$s!",someHaveAltBad:"%1$sImage alt attributes%3$s: Some images don't have alt attributes. %2$sAdd alt attributes to your images%3$s!"};return 1===r&&(n.someHaveAltBad="%1$sImage alt attributes%3$s: One image doesn't have alt attributes. %2$sAdd alt attributes to your images%3$s!"),(0,s.mapValues)(n,(r=>this.formatResultText(r,e,t)))}return this._config.callbacks.getResultTexts({urlTitleAnchorOpeningTag:e,urlActionAnchorOpeningTag:t,numberOfImagesWithoutAlt:r,totalNumberOfImages:this.imageCount})}}t.default=l},38754:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var s=r(65736),n=r(92819),i=r(76663),a=u(r(9017)),o=r(49061),l=u(r(73054));function u(e){return e&&e.__esModule?e:{default:e}}class c extends a.default{constructor(e={},t=!1){super();const r={scores:{bad:3,good:9},recommendedCount:1,urlTitle:(0,o.createAnchorOpeningTag)("https://yoa.st/4f4"),urlCallToAction:(0,o.createAnchorOpeningTag)("https://yoa.st/4f5")};this.identifier="images",this._config=(0,n.merge)(r,e),this._countVideos=t}getResult(e,t){this.imageCount=t.getResearch("imageCount"),this.videoCount=t.getResearch("videoCount");const r=this.calculateResult(),s=new l.default;return s.setScore(r.score),s.setText(r.resultText),s}calculateResult(){const e=this._countVideos?this.imageCount+this.videoCount:this.imageCount;if(0===e)return this._countVideos?{score:this._config.scores.bad,resultText:(0,s.sprintf)(/* translators: %1$s and %2$s expand to links on yoast.com, %3$s expands to the anchor end tag */
+(0,s.__)("%1$sImages and videos%3$s: No images or videos appear on this page. %2$sAdd some%3$s!","wordpress-seo"),this._config.urlTitle,this._config.urlCallToAction,"")}:{score:this._config.scores.bad,resultText:(0,s.sprintf)(/* translators: %1$s and %2$s expand to links on yoast.com, %3$s expands to the anchor end tag */
+(0,s.__)("%1$sImages%3$s: No images appear on this page. %2$sAdd some%3$s!","wordpress-seo"),this._config.urlTitle,this._config.urlCallToAction,"")};if(this._config.scores.okay){if((0,i.inRangeStartEndInclusive)(e,1,3)&&!this._countVideos)return{score:this._config.scores.okay,resultText:(0,s.sprintf)(
+/* translators: %3$s and %4$s expand to links on yoast.com, %5$s expands to the anchor end tag,
+ * %1$d expands to the number of images found in the text,
+ * %2$d expands to the recommended number of images in the text, */
+(0,s._n)("%3$sImages%5$s: Only %1$d image appears on this page. We recommend at least %2$d. %4$sAdd more relevant images%5$s!","%3$sImages%5$s: Only %1$d images appear on this page. We recommend at least %2$d. %4$sAdd more relevant images%5$s!",e,"wordpress-seo"),e,this._config.recommendedCount,this._config.urlTitle,this._config.urlCallToAction,"")};if((0,i.inRangeStartEndInclusive)(e,1,3)&&this._countVideos)return{score:this._config.scores.okay,resultText:(0,s.sprintf)(
+/* translators: %3$s and %4$s expand to links on yoast.com, %5$s expands to the anchor end tag,
+ * %1$d expands to the number of images found in the text,
+ * %2$d expands to the recommended number of images in the text, */
+(0,s._n)("%3$sImages and videos%5$s: Only %1$d image or video appears on this page. We recommend at least %2$d. %4$sAdd more relevant images or videos%5$s!","%3$sImages and videos%5$s: Only %1$d images or videos appear on this page. We recommend at least %2$d. %4$sAdd more relevant images or videos%5$s!",e,"wordpress-seo"),e,this._config.recommendedCount,this._config.urlTitle,this._config.urlCallToAction,"")}}return this._countVideos?{score:this._config.scores.good,resultText:(0,s.sprintf)(
+/* translators: %1$s expands to a link on yoast.com,
+ * %2$s expands to the anchor end tag. */
+(0,s.__)("%1$sImages and videos%2$s: Good job!","wordpress-seo"),this._config.urlTitle,"")}:{score:this._config.scores.good,resultText:(0,s.sprintf)(
+/* translators: %1$s expands to a link on yoast.com,
+ * %2$s expands to the anchor end tag. */
+(0,s.__)("%1$sImages%2$s: Good job!","wordpress-seo"),this._config.urlTitle,"")}}}t.default=c},92922:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var s=r(65736),n=r(92819),i=l(r(9017)),a=r(49061),o=l(r(73054));function l(e){return e&&e.__esModule?e:{default:e}}class u extends i.default{constructor(e={}){super();const t={parameters:{recommendedMinimum:1},scores:{allInternalFollow:9,someInternalFollow:8,noneInternalFollow:7,noInternal:3},urlTitle:(0,a.createAnchorOpeningTag)("https://yoa.st/33z"),urlCallToAction:(0,a.createAnchorOpeningTag)("https://yoa.st/34a")};this.identifier="internalLinks",this._config=(0,n.merge)(t,e)}getResult(e,t){this.linkStatistics=t.getResearch("getLinkStatistics");const r=new o.default,s=this.calculateResult();return r.setScore(s.score),r.setText(s.resultText),r}calculateResult(){return 0===this.linkStatistics.internalTotal?{score:this._config.scores.noInternal,resultText:(0,s.sprintf)(/* translators: %1$s and %2$s expand to links on yoast.com, %3$s expands to the anchor end tag */
+(0,s.__)("%1$sInternal links%3$s: No internal links appear in this page, %2$smake sure to add some%3$s!","wordpress-seo"),this._config.urlTitle,this._config.urlCallToAction,"")}:this.linkStatistics.internalNofollow===this.linkStatistics.internalTotal?{score:this._config.scores.noneInternalFollow,resultText:(0,s.sprintf)(/* translators: %1$s and %2$s expand to links on yoast.com, %3$s expands to the anchor end tag */
+(0,s.__)("%1$sInternal links%3$s: The internal links in this page are all nofollowed. %2$sAdd some good internal links%3$s.","wordpress-seo"),this._config.urlTitle,this._config.urlCallToAction,"")}:this.linkStatistics.internalDofollow===this.linkStatistics.internalTotal?{score:this._config.scores.allInternalFollow,resultText:(0,s.sprintf)(/* translators: %1$s expands to a link on yoast.com, %2$s expands to the anchor end tag */
+(0,s.__)("%1$sInternal links%2$s: You have enough internal links. Good job!","wordpress-seo"),this._config.urlTitle,"")}:{score:this._config.scores.someInternalFollow,resultText:(0,s.sprintf)(/* translators: %1$s expands to a link on yoast.com, %2$s expands to the anchor end tag */
+(0,s.__)("%1$sInternal links%2$s: There are both nofollowed and normal internal links on this page. Good job!","wordpress-seo"),this._config.urlTitle,"")}}}t.default=u},90575:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var s=r(65736),n=r(92819),i=l(r(9017)),a=r(49061),o=l(r(73054));function l(e){return e&&e.__esModule?e:{default:e}}class u extends i.default{constructor(e={}){super();const t={scores:{good:9,okay:6,bad:3},urlTitle:(0,a.createAnchorOpeningTag)("https://yoa.st/33e"),urlCallToAction:(0,a.createAnchorOpeningTag)("https://yoa.st/33f")};this.identifier="introductionKeyword",this._config=(0,n.merge)(t,e)}getResult(e,t){const r=new o.default;this._canAssess=!1,e.hasKeyword()&&e.hasText()&&(this._firstParagraphMatches=t.getResearch("findKeywordInFirstParagraph"),this._canAssess=!0);const s=this.calculateResult();return r.setScore(s.score),r.setText(s.resultText),s.score<9&&this._canAssess&&r.setHasAIFixes(!0),r}calculateResult(){return this._canAssess?this._firstParagraphMatches.foundInOneSentence?{score:this._config.scores.good,resultText:(0,s.sprintf)(/* translators: %1$s expands to a link on yoast.com, %2$s expands to the anchor end tag. */
+(0,s.__)("%1$sKeyphrase in introduction%2$s: Well done!","wordpress-seo"),this._config.urlTitle,"")}:this._firstParagraphMatches.foundInParagraph?{score:this._config.scores.okay,resultText:(0,s.sprintf)(/* translators: %1$s and %2$s expand to links on yoast.com, %3$s expands to the anchor end tag. */
+(0,s.__)("%1$sKeyphrase in introduction%3$s: Your keyphrase or its synonyms appear in the first paragraph of the copy, but not within one sentence. %2$sFix that%3$s!","wordpress-seo"),this._config.urlTitle,this._config.urlCallToAction,"")}:{score:this._config.scores.bad,resultText:(0,s.sprintf)(/* translators: %1$s and %2$s expand to links on yoast.com, %3$s expands to the anchor end tag. */
+(0,s.__)("%1$sKeyphrase in introduction%3$s: Your keyphrase or its synonyms do not appear in the first paragraph. %2$sMake sure the topic is clear immediately%3$s.","wordpress-seo"),this._config.urlTitle,this._config.urlCallToAction,"")}:{score:this._config.scores.bad,resultText:(0,s.sprintf)(/* translators: %1$s and %2$s expand to links on yoast.com, %3$s expands to the anchor end tag. */
+(0,s.__)("%1$sKeyphrase in introduction%3$s: %2$sPlease add both a keyphrase and an introduction containing the keyphrase%3$s.","wordpress-seo"),this._config.urlTitle,this._config.urlCallToAction,"")}}}t.default=u},58850:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var s=r(92819),n=c(r(9017)),i=c(r(73054)),a=r(49061),o=c(r(9862)),l=c(r(96908)),u=r(29866);function c(e){return e&&e.__esModule?e:{default:e}}class d extends n.default{constructor(e={}){super(),this.identifier="keyphraseDistribution",this._config=(0,s.merge)({parameters:{goodDistributionScore:30,acceptableDistributionScore:50},scores:{good:9,okay:6,bad:1,consideration:0},urlTitle:"https://yoa.st/33q",urlCallToAction:"https://yoa.st/33u",callbacks:{}},e)}getResult(e,t){this._keyphraseDistribution=t.getResearch("keyphraseDistribution");const r=new i.default,s=this.calculateResult();return r.setScore(s.score),r.setText(s.resultText),r.setHasMarks(s.hasMarks),s.score<9&&r.setHasAIFixes(!0),r}calculateResult(){const e=this._keyphraseDistribution.keyphraseDistributionScore,t=this._keyphraseDistribution.sentencesToHighlight.length>0,{good:r,okay:s,bad:n,consideration:i}=this.getFeedbackStrings();return 100===e?{score:this._config.scores.consideration,hasMarks:t,resultText:i}:e>this._config.parameters.acceptableDistributionScore?{score:this._config.scores.bad,hasMarks:t,resultText:n}:e>this._config.parameters.goodDistributionScore&&e<=this._config.parameters.acceptableDistributionScore?{score:this._config.scores.okay,hasMarks:t,resultText:s}:{score:this._config.scores.good,hasMarks:t,resultText:r}}getFeedbackStrings(){const e=(0,a.createAnchorOpeningTag)(this._config.urlTitle),t=(0,a.createAnchorOpeningTag)(this._config.urlCallToAction);if(!this._config.callbacks.getResultTexts){const r={good:"%1$sKeyphrase distribution%3$s: Good job!",okay:"%1$sKeyphrase distribution%3$s: Uneven. Some parts of your text do not contain the keyphrase or its synonyms. %2$sDistribute them more evenly%3$s.",bad:"%1$sKeyphrase distribution%3$s: Very uneven. Large parts of your text do not contain the keyphrase or its synonyms. %2$sDistribute them more evenly%3$s.",consideration:"%1$sKeyphrase distribution%3$s: %2$sInclude your keyphrase or its synonyms in the text so that we can check keyphrase distribution%3$s."};return(0,s.mapValues)(r,(r=>this.formatResultText(r,e,t)))}return this._config.callbacks.getResultTexts({urlTitleAnchorOpeningTag:e,urlActionAnchorOpeningTag:t})}getMarks(){return this._keyphraseDistribution.sentencesToHighlight}isApplicable(e,t){const r=t.getHelper("memoizedTokenizer");let s=e.getText();s=(0,l.default)(s),s=(0,u.filterShortcodesFromHTML)(s,e._attributes&&e._attributes.shortcodes);const n=(0,o.default)(s,r);return e.hasText()&&e.hasKeyword()&&n.length>=15&&t.hasResearch("keyphraseDistribution")}}t.default=d},8980:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var s=r(65736),n=r(92819),i=u(r(9017)),a=r(76663),o=r(49061),l=u(r(73054));function u(e){return e&&e.__esModule?e:{default:e}}class c extends i.default{constructor(e={}){super();const t={parameters:{lowerBoundary:.3,upperBoundary:.75},scores:{withAltGoodNumberOfKeywordMatches:9,withAltTooFewKeywordMatches:6,withAltTooManyKeywordMatches:6,withAltNonKeyword:6,noAlt:6,noImagesOrKeyphrase:3},urlTitle:(0,o.createAnchorOpeningTag)("https://yoa.st/4f7"),urlCallToAction:(0,o.createAnchorOpeningTag)("https://yoa.st/4f6")};this.identifier="imageKeyphrase",this._config=(0,n.merge)(t,e)}getResult(e,t){this.imageCount=t.getResearch("imageCount"),this.imageCount>0&&(this.altProperties=t.getResearch("altTagCount"),this._minNumberOfKeyphraseMatches=Math.ceil(this.imageCount*this._config.parameters.lowerBoundary),this._maxNumberOfKeyphraseMatches=Math.floor(this.imageCount*this._config.parameters.upperBoundary));const r=this.calculateResult(e),s=new l.default;return s.setScore(r.score),s.setText(r.resultText),s}hasTooFewMatches(){return this.imageCount>4&&this.altProperties.withAltKeyword>0&&this.altProperties.withAltKeyword0||5===this.imageCount&&(0,a.inRangeStartEndInclusive)(this.altProperties.withAltKeyword,2,4)||this.imageCount>4&&(0,a.inRangeStartEndInclusive)(this.altProperties.withAltKeyword,this._minNumberOfKeyphraseMatches,this._maxNumberOfKeyphraseMatches)}hasTooManyMatches(){return this.imageCount>4&&this.altProperties.withAltKeyword>this._maxNumberOfKeyphraseMatches}hasNoKeyphraseMatches(){return this.altProperties.withAltNonKeyword>0&&0===this.altProperties.withAltKeyword}calculateResult(e){return e.hasKeyword()&&0!==this.imageCount?this.hasNoKeyphraseMatches()?{score:this._config.scores.withAltNonKeyword,resultText:(0,s.sprintf)(/* translators: %1$s and %2$s expand to links on yoast.com, %3$s expands to the anchor end tag */
+(0,s.__)("%1$sKeyphrase in image alt attributes%3$s: Images on this page do not have alt attributes with at least half of the words from your keyphrase. %2$sFix that%3$s!","wordpress-seo"),this._config.urlTitle,this._config.urlCallToAction,"")}:this.hasTooFewMatches()?{score:this._config.scores.withAltTooFewKeywordMatches,resultText:(0,s.sprintf)(
+/* translators: %1$d expands to the number of images containing an alt attribute with the keyphrase,
+ * %2$d expands to the total number of images, %3$s and %4$s expand to links on yoast.com,
+ * %5$s expands to the anchor end tag. */
+(0,s._n)("%3$sKeyphrase in image alt attributes%5$s: Out of %2$d images on this page, only %1$d has an alt attribute that reflects the topic of your text. %4$sAdd your keyphrase or synonyms to the alt tags of more relevant images%5$s!","%3$sKeyphrase in image alt attributes%5$s: Out of %2$d images on this page, only %1$d have alt attributes that reflect the topic of your text. %4$sAdd your keyphrase or synonyms to the alt tags of more relevant images%5$s!",this.altProperties.withAltKeyword,"wordpress-seo"),this.altProperties.withAltKeyword,this.imageCount,this._config.urlTitle,this._config.urlCallToAction,"")}:this.hasGoodNumberOfMatches()?{score:this._config.scores.withAltGoodNumberOfKeywordMatches,resultText:(0,s.sprintf)(
+/* translators: %1$s expands to a link on yoast.com,
+ * %2$s expands to the anchor end tag. */
+(0,s.__)("%1$sKeyphrase in image alt attributes%2$s: Good job!","wordpress-seo"),this._config.urlTitle,"")}:this.hasTooManyMatches()?{score:this._config.scores.withAltTooManyKeywordMatches,resultText:(0,s.sprintf)(
+/* translators: %1$d expands to the number of images containing an alt attribute with the keyphrase,
+ * %2$d expands to the total number of images, %3$s and %4$s expand to a link on yoast.com,
+ * %5$s expands to the anchor end tag. */
+(0,s.__)("%3$sKeyphrase in image alt attributes%5$s: Out of %2$d images on this page, %1$d have alt attributes with words from your keyphrase or synonyms. That's a bit much. %4$sOnly include the keyphrase or its synonyms when it really fits the image%5$s.","wordpress-seo"),this.altProperties.withAltKeyword,this.imageCount,this._config.urlTitle,this._config.urlCallToAction,"")}:{score:this._config.scores.noAlt,resultText:(0,s.sprintf)(/* translators: %1$s and %2$s expand to links on yoast.com, %3$s expands to the anchor end tag */
+(0,s.__)("%1$sKeyphrase in image alt attributes%3$s: Images on this page do not have alt attributes that reflect the topic of your text. %2$sAdd your keyphrase or synonyms to the alt tags of relevant images%3$s!","wordpress-seo"),this._config.urlTitle,this._config.urlCallToAction,"")}:{score:this._config.scores.noImagesOrKeyphrase,resultText:(0,s.sprintf)(/* translators: %1$s and %2$s expand to links on yoast.com, %3$s expands to the anchor end tag */
+(0,s.__)("%1$sKeyphrase in image alt attributes%3$s: This page does not have images, a keyphrase, or both. %2$sAdd some images with alt attributes that include the keyphrase or synonyms%3$s!","wordpress-seo"),this._config.urlTitle,this._config.urlCallToAction,"")}}}t.default=c},46787:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var s=r(65736),n=r(92819),i=u(r(67404)),a=u(r(9017)),o=r(49061),l=u(r(73054));function u(e){return e&&e.__esModule?e:{default:e}}class c extends a.default{constructor(e={}){super();const t={parameters:{recommendedPosition:0},scores:{good:9,okay:6,bad:2},urlTitle:(0,o.createAnchorOpeningTag)("https://yoa.st/33g"),urlCallToAction:(0,o.createAnchorOpeningTag)("https://yoa.st/33h"),feedbackStrings:{bad:(0,s.__)("For the best SEO results write the exact match of your keyphrase in the SEO title, and put the keyphrase at the beginning of the title","wordpress-seo")}};this.identifier="keyphraseInSEOTitle",
+/* translators: This is the name of the 'Keyphrase in SEO title' SEO assessment.
+ It appears before the feedback in the analysis, for example in the feedback string:
+ "Keyphrase in SEO title: The focus keyphrase appears at the beginning of the SEO title. Good job!" */
+this.name=(0,s.__)("Keyphrase in SEO title","wordpress-seo"),this._config=(0,n.merge)(t,e)}getResult(e,t){const r=(0,i.default)(e.getLocale());this._canAssess=!1,e.hasKeyword()&&e.hasTitle()&&(this._keyphraseMatches=t.getResearch("findKeyphraseInSEOTitle"),this._keyphrase=(0,n.escape)(e.getKeyword()),this._canAssess=!0);const a=new l.default,o=this.calculateResult(this._keyphrase,r);return a.setScore(o.score),a.setText(o.resultText),a.getScore()<9&&(a.setHasJumps(!0),e.hasKeyword()?(a.setEditFieldName("title"),a.setEditFieldAriaLabel((0,s.__)("Edit your SEO title","wordpress-seo"))):(a.setEditFieldName("keyphrase"),a.setEditFieldAriaLabel((0,s.__)("Edit your keyphrase","wordpress-seo")))),a}calculateResult(e,t){const r=this._config.urlTitle+this.name+"";if(!this._canAssess)return{score:this._config.scores.bad,resultText:(0,s.sprintf)(
+/* translators: %1$s expands to the title of the "Keyphrase in SEO title" assessment (translated to the current language)
+ and links to an article on yoast.com. %2$s expands to a link on yoast.com, %3$s expands to the anchor end tag. */
+(0,s.__)("%1$s: %2$sPlease add both a keyphrase and an SEO title beginning with the keyphrase%3$s.","wordpress-seo"),r,this._config.urlCallToAction,"")};const n=this._config.feedbackStrings;"ja"===t&&(n.bad=(0,s.__)("For the best SEO results include all words of your keyphrase in the SEO title, and put the keyphrase at the beginning of the title","wordpress-seo"));const i=this._keyphraseMatches.exactMatchFound,a=this._keyphraseMatches.position,o=this._keyphraseMatches.allWordsFound,l=this._keyphraseMatches.exactMatchKeyphrase;return!0===i?0===a?{score:this._config.scores.good,resultText:(0,s.sprintf)(
+/* translators: %1$s expands to the title of the "Keyphrase in SEO title" assessment (translated to the current language)
+ and links to an article on yoast.com. */
+(0,s.__)("%1$s: The exact match of the focus keyphrase appears at the beginning of the SEO title. Good job!","wordpress-seo"),r)}:{score:this._config.scores.okay,resultText:(0,s.sprintf)(
+/* translators: %1$s expands to the title of the "Keyphrase in SEO title" assessment (translated to the current language)
+ and links to an article on yoast.com. %2$s expand to a link on yoast.com, %3$s expands to the anchor end tag. */
+(0,s.__)("%1$s: The exact match of the focus keyphrase appears in the SEO title, but not at the beginning. %2$sMove it to the beginning for the best results%3$s.","wordpress-seo"),r,this._config.urlCallToAction,"")}:o?"ja"===t?0===a?{score:this._config.scores.good,resultText:(0,s.sprintf)(
+/* translators: %1$s expands to the title of the "Keyphrase in SEO title" assessment (translated to the current language)
+ and links to an article on yoast.com. */
+(0,s.__)("%1$s: The focus keyphrase appears at the beginning of the SEO title. Good job!","wordpress-seo"),r,"")}:{score:this._config.scores.okay,resultText:(0,s.sprintf)(
+/* translators: %1$s expands to the title of the "Keyphrase in SEO title" assessment (translated to the current language)
+ and links to an article on yoast.com. %2$s expands to a link on yoast.com, %3$s expands to the anchor end tag. */
+(0,s.__)("%1$s: Title does not begin with the focus keyphrase. %2$sMove your focus keyphrase to the beginning of the title%3$s.","wordpress-seo"),r,this._config.urlCallToAction,"")}:{score:this._config.scores.okay,resultText:(0,s.sprintf)(
+/* translators: %1$s expands to the title of the "Keyphrase in SEO title" assessment (translated to the current language)
+ and links to an article on yoast.com. %2$s expands to a link on yoast.com, %3$s expands to the anchor end tag. */
+(0,s.__)("%1$s: Does not contain the exact match. %2$sTry to write the exact match of your keyphrase in the SEO title and put it at the beginning of the title%3$s.","wordpress-seo"),r,this._config.urlCallToAction,"")}:l?{score:this._config.scores.bad,resultText:(0,s.sprintf)(
+/* translators: %1$s expands to the title of the "Keyphrase in SEO title" assessment (translated to the current language)
+ and links to an article on yoast.com. %2$s expands to a link on yoast.com, %3$s expands to the anchor end tag. */
+(0,s.__)("%1$s: Does not contain the exact match. %2$sTry to write the exact match of your keyphrase in the SEO title and put it at the beginning of the title%3$s.","wordpress-seo"),r,this._config.urlCallToAction,"",e)}:{score:this._config.scores.bad,resultText:(0,s.sprintf)(
+/* translators: %1$s expands to the title of the "Keyphrase in SEO title" assessment (translated to the current language)
+ and links to an article on yoast.com. %2$s expands to a link on yoast.com, %3$s expands to the anchor end tag,
+ %4$s expands to the keyphrase of the article, %5$s expands to the call to action text. */
+(0,s.__)('%1$s: Not all the words from your keyphrase "%4$s" appear in the SEO title. %2$s%5$s%3$s.',"wordpress-seo"),r,this._config.urlCallToAction,"",e,n.bad)}}}t.default=c},99815:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var s=r(65736),n=r(92819),i=c(r(9017)),a=r(49061),o=c(r(73054)),l=r(76663),u=c(r(83927));function c(e){return e&&e.__esModule?e:{default:e}}const d=Object.freeze({WORDS:"words",CONTENT_WORDS:"content words",CHARACTERS:"characters"});class h extends i.default{constructor(e,t=!1){super(),this.defaultConfig={parameters:{recommendedMinimum:1,recommendedMaximum:4,acceptableMaximum:8},parametersNoFunctionWordSupport:{recommendedMaximum:6,acceptableMaximum:9},scores:{veryBad:-999,bad:3,okay:6,good:9},countTextIn:d.WORDS,urlTitle:(0,a.createAnchorOpeningTag)("https://yoa.st/33i"),urlCallToAction:(0,a.createAnchorOpeningTag)("https://yoa.st/33j"),isRelatedKeyphrase:!1},this.identifier="keyphraseLength",this._config=(0,n.merge)(this.defaultConfig,e),this._isProductPage=t}getResult(e,t){this._keyphraseLengthData=t.getResearch("keyphraseLength");const r=new o.default;t.getConfig("countCharacters")&&(this._config.countTextIn=d.CHARACTERS);const i=e.getKeyword();this._keyphraseLengthData.functionWords.length>0&&!(0,u.default)(i).exactMatchRequested&&(this._config.countTextIn=d.CONTENT_WORDS),t.getConfig("keyphraseLength")?this._config=this.getCustomConfig(t):0===this._keyphraseLengthData.functionWords.length&&(this._config.parameters=(0,n.merge)({},this._config.parameters,this._config.parametersNoFunctionWordSupport)),this._boundaries=this._config.parameters;const a=this.calculateResult();return r.setScore(a.score),r.setText(a.resultText),r.getScore()<9&&(r.setHasJumps(!0),r.setEditFieldName("keyphrase"),r.setEditFieldAriaLabel((0,s.__)("Edit your keyphrase","wordpress-seo"))),r}getCustomConfig(e){const t=e.getConfig("keyphraseLength");return this._isProductPage&&Object.hasOwn(t,"productPages")?(0,n.merge)(this._config,t.productPages):(0,n.merge)(this._config,t.defaultAnalysis)}getFeedbackTexts(){return{firstSentence:e=>{const t=(0,s.sprintf)(/* translators: %1$d expands to the number of words, %2$s expands to a link on yoast.com, %3$s expands to the anchor end tag. */
+(0,s._n)("%2$sKeyphrase length%3$s: The keyphrase contains %1$d word.","%2$sKeyphrase length%3$s: The keyphrase contains %1$d words.",this._keyphraseLengthData.keyphraseLength,"wordpress-seo"),this._keyphraseLengthData.keyphraseLength,this._config.urlTitle,""),r=(0,s.sprintf)(/* translators: %1$d expands to the number of content words, %2$s expands to a link on yoast.com, %3$s expands to the anchor end tag. */
+(0,s._n)("%2$sKeyphrase length%3$s: The keyphrase contains %1$d content word.","%2$sKeyphrase length%3$s: The keyphrase contains %1$d content words.",this._keyphraseLengthData.keyphraseLength,"wordpress-seo"),this._keyphraseLengthData.keyphraseLength,this._config.urlTitle,""),n=(0,s.sprintf)(/* translators: %1$d expands to the number of characters, %2$s expands to a link on yoast.com, %3$s expands to the anchor end tag. */
+(0,s._n)("%2$sKeyphrase length%3$s: The keyphrase contains %1$d character.","%2$sKeyphrase length%3$s: The keyphrase contains %1$d characters.",this._keyphraseLengthData.keyphraseLength,"wordpress-seo"),this._keyphraseLengthData.keyphraseLength,this._config.urlTitle,"");return e===d.WORDS?t:e===d.CONTENT_WORDS?r:n},moreThanMinimum:(e,t)=>{const r=(0,s.sprintf)(/* translators: %1$d expands to the number of words, %2$s expands to the sentence "The keyphrase contains X word(s).", %3$s expands to a link on yoast.com, %4$s expands to the anchor end tag. */
+(0,s._n)("%2$s That's more than the recommended maximum of %1$d word. %3$sMake it shorter%4$s!","%2$s That's more than the recommended maximum of %1$d words. %3$sMake it shorter%4$s!",this._boundaries.recommendedMaximum,"wordpress-seo"),this._boundaries.recommendedMaximum,t,this._config.urlCallToAction,""),n=(0,s.sprintf)(/* translators: %1$d expands to the number of content words, %2$s expands to the sentence "The keyphrase contains X content word(s).", %3$s expands to a link on yoast.com, %4$s expands to the anchor end tag. */
+(0,s._n)("%2$s That's more than the recommended maximum of %1$d content word. %3$sMake it shorter%4$s!","%2$s That's more than the recommended maximum of %1$d content words. %3$sMake it shorter%4$s!",this._boundaries.recommendedMaximum,"wordpress-seo"),this._boundaries.recommendedMaximum,t,this._config.urlCallToAction,""),i=(0,s.sprintf)(/* translators: %1$d expands to the number of characters, %2$s expands to the sentence "The keyphrase contains X character(s).", %3$s expands to a link on yoast.com, %4$s expands to the anchor end tag. */
+(0,s._n)("%2$s That's more than the recommended maximum of %1$d character. %3$sMake it shorter%4$s!","%2$s That's more than the recommended maximum of %1$d characters. %3$sMake it shorter%4$s!",this._boundaries.recommendedMaximum,"wordpress-seo"),this._boundaries.recommendedMaximum,t,this._config.urlCallToAction,"");return e===d.WORDS?r:e===d.CONTENT_WORDS?n:i},wayMoreThanMinimum:(e,t)=>{const r=(0,s.sprintf)(/* translators: %1$d expands to the number of words, %2$s expands to the sentence "The keyphrase contains X word(s).", %3$s expands to a link on yoast.com, %4$s expands to the anchor end tag. */
+(0,s._n)("%2$s That's way more than the recommended maximum of %1$d word. %3$sMake it shorter%4$s!","%2$s That's way more than the recommended maximum of %1$d words. %3$sMake it shorter%4$s!",this._boundaries.recommendedMaximum,"wordpress-seo"),this._boundaries.recommendedMaximum,t,this._config.urlCallToAction,""),n=(0,s.sprintf)(/* translators: %1$d expands to the number of content words, %2$s expands to the sentence "The keyphrase contains X content word(s).", %3$s expands to a link on yoast.com, %4$s expands to the anchor end tag. */
+(0,s._n)("%2$s That's way more than the recommended maximum of %1$d content word. %3$sMake it shorter%4$s!","%2$s That's way more than the recommended maximum of %1$d content words. %3$sMake it shorter%4$s!",this._boundaries.recommendedMaximum,"wordpress-seo"),this._boundaries.recommendedMaximum,t,this._config.urlCallToAction,""),i=(0,s.sprintf)(/* translators: %1$d expands to the number of characters, %2$s expands to the sentence "The keyphrase contains X character(s).", %3$s expands to a link on yoast.com, %4$s expands to the anchor end tag. */
+(0,s._n)("%2$s That's way more than the recommended maximum of %1$d character. %3$sMake it shorter%4$s!","%2$s That's way more than the recommended maximum of %1$d characters. %3$sMake it shorter%4$s!",this._boundaries.recommendedMaximum,"wordpress-seo"),this._boundaries.recommendedMaximum,t,this._config.urlCallToAction,"");return e===d.WORDS?r:e===d.CONTENT_WORDS?n:i},lessThanMinimum:(e,t)=>{const r=(0,s.sprintf)(/* translators: %1$d expands to the number of words, %2$s expands to the sentence "The keyphrase contains X word(s).", %3$s expands to a link on yoast.com, %4$s expands to the anchor end tag. */
+(0,s._n)("%2$s That's less than the recommended minimum of %1$d word. %3$sMake it longer%4$s!","%2$s That's less than the recommended minimum of %1$d words. %3$sMake it longer%4$s!",this._boundaries.recommendedMinimum,"wordpress-seo"),this._boundaries.recommendedMinimum,t,this._config.urlCallToAction,""),n=(0,s.sprintf)(/* translators: %1$d expands to the number of content words, %2$s expands to the sentence "The keyphrase contains X content word(s).", %3$s expands to a link on yoast.com, %4$s expands to the anchor end tag. */
+(0,s._n)("%2$s That's less than the recommended minimum of %1$d content word. %3$sMake it longer%4$s!","%2$s That's less than the recommended minimum of %1$d content words. %3$sMake it longer%4$s!",this._boundaries.recommendedMinimum,"wordpress-seo"),this._boundaries.recommendedMinimum,t,this._config.urlCallToAction,""),i=(0,s.sprintf)(/* translators: %1$d expands to the number of characters, %2$s expands to the sentence "The keyphrase contains X character(s).", %3$s expands to a link on yoast.com, %4$s expands to the anchor end tag. */
+(0,s._n)("%2$s That's less than the recommended minimum of %1$d character. %3$sMake it longer%4$s!","%2$s That's less than the recommended minimum of %1$d characters. %3$sMake it longer%4$s!",this._boundaries.recommendedMinimum,"wordpress-seo"),this._boundaries.recommendedMinimum,t,this._config.urlCallToAction,"");return e===d.WORDS?r:e===d.CONTENT_WORDS?n:i},wayLessThanMinimum:(e,t)=>{const r=(0,s.sprintf)(/* translators: %1$d expands to the number of words, %2$s expands to the sentence "The keyphrase contains X word(s).", %3$s expands to a link on yoast.com, %4$s expands to the anchor end tag. */
+(0,s._n)("%2$s That's way less than the recommended minimum of %1$d word. %3$sMake it longer%4$s!","%2$s That's way less than the recommended minimum of %1$d words. %3$sMake it longer%4$s!",this._boundaries.recommendedMinimum,"wordpress-seo"),this._boundaries.recommendedMinimum,t,this._config.urlCallToAction,""),n=(0,s.sprintf)(/* translators: %1$d expands to the number of content words, %2$s expands to the sentence "The keyphrase contains X content word(s).", %3$s expands to a link on yoast.com, %4$s expands to the anchor end tag. */
+(0,s._n)("%2$s That's way less than the recommended minimum of %1$d content word. %3$sMake it longer%4$s!","%2$s That's way less than the recommended minimum of %1$d content words. %3$sMake it longer%4$s!",this._boundaries.recommendedMinimum,"wordpress-seo"),this._boundaries.recommendedMinimum,t,this._config.urlCallToAction,""),i=(0,s.sprintf)(/* translators: %1$d expands to the number of characters, %2$s expands to the sentence "The keyphrase contains X character(s).", %3$s expands to a link on yoast.com, %4$s expands to the anchor end tag. */
+(0,s._n)("%2$s That's way less than the recommended minimum of %1$d character. %3$sMake it longer%4$s!","%2$s That's way less than the recommended minimum of %1$d characters. %3$sMake it longer%4$s!",this._boundaries.recommendedMinimum,"wordpress-seo"),this._boundaries.recommendedMinimum,t,this._config.urlCallToAction,"");return e===d.WORDS?r:e===d.CONTENT_WORDS?n:i}}}calculateResultForProduct(){if(0===this._keyphraseLengthData.keyphraseLength)return this.getNoKeyphraseFeedback();if((0,l.inRangeStartEndInclusive)(this._keyphraseLengthData.keyphraseLength,this._boundaries.recommendedMinimum,this._boundaries.recommendedMaximum))return{score:this._config.scores.good,resultText:(0,s.sprintf)(/* translators: %1$s expands to a link on yoast.com, %2$s expands to the anchor end tag. */
+(0,s.__)("%1$sKeyphrase length%2$s: Good job!","wordpress-seo"),this._config.urlTitle,"")};const e=this.getFeedbackTexts(),t=e.firstSentence(this._config.countTextIn);return this._keyphraseLengthData.keyphraseLength<=this._boundaries.acceptableMinimum?{score:this._config.scores.bad,resultText:e.wayLessThanMinimum(this._config.countTextIn,t)}:this._keyphraseLengthData.keyphraseLength>this._boundaries.acceptableMaximum?{score:this._config.scores.bad,resultText:e.wayMoreThanMinimum(this._config.countTextIn,t)}:(0,n.inRange)(this._keyphraseLengthData.keyphraseLength,this._boundaries.acceptableMinimum,this._boundaries.recommendedMinimum)?{score:this._config.scores.okay,resultText:e.lessThanMinimum(this._config.countTextIn,t)}:{score:this._config.scores.okay,resultText:e.moreThanMinimum(this._config.countTextIn,t)}}getNoKeyphraseFeedback(){return this._config.isRelatedKeyphrase?{score:this._config.scores.veryBad,resultText:(0,s.sprintf)(/* translators: %1$s and %2$s expand to links on yoast.com, %3$s expands to the anchor end tag */
+(0,s.__)("%1$sKeyphrase length%3$s: %2$sSet a keyphrase in order to calculate your SEO score%3$s.","wordpress-seo"),this._config.urlTitle,this._config.urlCallToAction,"")}:{score:this._config.scores.veryBad,resultText:(0,s.sprintf)(/* translators: %1$s and %2$s expand to links on yoast.com, %3$s expands to the anchor end tag */
+(0,s.__)("%1$sKeyphrase length%3$s: No focus keyphrase was set for this page. %2$sSet a keyphrase in order to calculate your SEO score%3$s.","wordpress-seo"),this._config.urlTitle,this._config.urlCallToAction,"")}}calculateResult(){if(this._isProductPage)return this.calculateResultForProduct();if(this._keyphraseLengthData.keyphraseLength")};const e=this.getFeedbackTexts(),t=e.firstSentence(this._config.countTextIn);return(0,n.inRange)(this._keyphraseLengthData.keyphraseLength,this._boundaries.recommendedMaximum+1,this._boundaries.acceptableMaximum+1)?{score:this._config.scores.okay,resultText:e.moreThanMinimum(this._config.countTextIn,t)}:{score:this._config.scores.bad,resultText:e.wayMoreThanMinimum(this._config.countTextIn,t)}}}t.default=h},70966:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=t.KeywordDensityAssessment=t.KeyphraseDensityAssessment=void 0;var s=r(65736),n=r(92819),i=h(r(68055)),a=h(r(9017)),o=h(r(73054)),l=r(76663),u=r(49061),c=h(r(4913)),d=h(r(60914));function h(e){return e&&e.__esModule?e:{default:e}}class f extends a.default{constructor(e={}){super();const t={parameters:{noWordForms:{overMaximum:4,maximum:3,minimum:.5},multipleWordForms:{overMaximum:4,maximum:3.5,minimum:.5}},scores:{wayOverMaximum:-50,overMaximum:-10,correctDensity:9,underMinimum:4},urlTitle:(0,u.createAnchorOpeningTag)("https://yoa.st/33v"),urlCallToAction:(0,u.createAnchorOpeningTag)("https://yoa.st/33w"),applicableIfTextLongerThan:100};this.identifier="keyphraseDensity",this._config=(0,n.merge)(t,e)}setBoundaries(e,t,r){this._hasMorphologicalForms?this._boundaries=this._config.parameters.multipleWordForms:this._boundaries=this._config.parameters.noWordForms,this._minRecommendedKeyphraseCount=(0,i.default)(e,t,this._boundaries.minimum,"min",r),this._maxRecommendedKeyphraseCount=(0,i.default)(e,t,this._boundaries.maximum,"max",r)}getResult(e,t){const r=t.getHelper("getWordsCustomHelper");this._keyphraseCount=t.getResearch("getKeyphraseCount");const s=this._keyphraseCount.keyphraseLength,n=new o.default;this._keyphraseDensity=t.getResearch("getKeyphraseDensity"),this._hasMorphologicalForms=!1!==t.getData("morphology"),this.setBoundaries(e,s,r),this._keyphraseDensity=this._keyphraseDensity*(0,c.default)(s);const i=this.calculateResult();return n.setScore(i.score),n.setText(i.resultText),n.setHasMarks(this._keyphraseCount.count>0),i.score===this._config.scores.underMinimum&&n.setHasAIFixes(!0),n}hasNoMatches(){return 0===this._keyphraseCount.count}hasTooFewMatches(){return(0,l.inRangeStartInclusive)(this._keyphraseDensity,0,this._boundaries.minimum)||1===this._keyphraseCount.count}hasGoodNumberOfMatches(){return(0,l.inRangeStartEndInclusive)(this._keyphraseDensity,this._boundaries.minimum,this._boundaries.maximum)||2===this._keyphraseCount.count&&this._minRecommendedKeyphraseCount<=2}hasTooManyMatches(){return(0,l.inRangeEndInclusive)(this._keyphraseDensity,this._boundaries.maximum,this._boundaries.overMaximum)}calculateResult(){return this.hasNoMatches()?{score:this._config.scores.underMinimum,resultText:(0,s.sprintf)(
+/* translators:
+ %1$s and %4$s expand to links to Yoast.com,
+ %2$s expands to the anchor end tag,
+ %3$d expands to the recommended minimal number of times the keyphrase should occur in the text. */
+(0,s.__)("%1$sKeyphrase density%2$s: The keyphrase was found 0 times. That's less than the recommended minimum of %3$d times for a text of this length. %4$sFocus on your keyphrase%2$s!","wordpress-seo"),this._config.urlTitle,"",this._minRecommendedKeyphraseCount,this._config.urlCallToAction)}:this.hasTooFewMatches()?{score:this._config.scores.underMinimum,resultText:(0,s.sprintf)(
+/* translators:
+ %1$s and %4$s expand to links to Yoast.com,
+ %2$s expands to the anchor end tag,
+ %3$d expands to the recommended minimal number of times the keyphrase should occur in the text,
+ %5$d expands to the number of times the keyphrase occurred in the text. */
+(0,s._n)("%1$sKeyphrase density%2$s: The keyphrase was found %5$d time. That's less than the recommended minimum of %3$d times for a text of this length. %4$sFocus on your keyphrase%2$s!","%1$sKeyphrase density%2$s: The keyphrase was found %5$d times. That's less than the recommended minimum of %3$d times for a text of this length. %4$sFocus on your keyphrase%2$s!",this._keyphraseCount.count,"wordpress-seo"),this._config.urlTitle,"",this._minRecommendedKeyphraseCount,this._config.urlCallToAction,this._keyphraseCount.count)}:this.hasGoodNumberOfMatches()?{score:this._config.scores.correctDensity,resultText:(0,s.sprintf)(
+/* translators:
+ %1$s expands to a link to Yoast.com,
+ %2$s expands to the anchor end tag,
+ %3$d expands to the number of times the keyphrase occurred in the text. */
+(0,s._n)("%1$sKeyphrase density%2$s: The keyphrase was found %3$d time. This is great!","%1$sKeyphrase density%2$s: The keyphrase was found %3$d times. This is great!",this._keyphraseCount.count,"wordpress-seo"),this._config.urlTitle,"",this._keyphraseCount.count)}:this.hasTooManyMatches()?{score:this._config.scores.overMaximum,resultText:(0,s.sprintf)(
+/* translators:
+ %1$s and %4$s expand to links to Yoast.com,
+ %2$s expands to the anchor end tag,
+ %3$d expands to the recommended maximal number of times the keyphrase should occur in the text,
+ %5$d expands to the number of times the keyphrase occurred in the text. */
+(0,s._n)("%1$sKeyphrase density%2$s: The keyphrase was found %5$d time. That's more than the recommended maximum of %3$d times for a text of this length. %4$sDon't overoptimize%2$s!","%1$sKeyphrase density%2$s: The keyphrase was found %5$d times. That's more than the recommended maximum of %3$d times for a text of this length. %4$sDon't overoptimize%2$s!",this._keyphraseCount.count,"wordpress-seo"),this._config.urlTitle,"",this._maxRecommendedKeyphraseCount,this._config.urlCallToAction,this._keyphraseCount.count)}:{score:this._config.scores.wayOverMaximum,resultText:(0,s.sprintf)(
+/* translators:
+ %1$s and %4$s expand to links to Yoast.com,
+ %2$s expands to the anchor end tag,
+ %3$d expands to the recommended maximal number of times the keyphrase should occur in the text,
+ %5$d expands to the number of times the keyphrase occurred in the text. */
+(0,s._n)("%1$sKeyphrase density%2$s: The keyphrase was found %5$d time. That's way more than the recommended maximum of %3$d times for a text of this length. %4$sDon't overoptimize%2$s!","%1$sKeyphrase density%2$s: The keyphrase was found %5$d times. That's way more than the recommended maximum of %3$d times for a text of this length. %4$sDon't overoptimize%2$s!",this._keyphraseCount.count,"wordpress-seo"),this._config.urlTitle,"",this._maxRecommendedKeyphraseCount,this._config.urlCallToAction,this._keyphraseCount.count)}}getMarks(){return this._keyphraseCount.markings}isApplicable(e,t){const r=t.getHelper("customCountLength"),s=t.getConfig("assessmentApplicability").keyphraseDensity;s&&(this._config.applicableIfTextLongerThan=s);const n=r?r(e.getText()):(0,d.default)(e).length;return e.hasText()&&e.hasKeyword()&&n>=this._config.applicableIfTextLongerThan}}t.KeyphraseDensityAssessment=f,t.KeywordDensityAssessment=class extends f{constructor(e={}){super(e),this.identifier="keywordDensity",console.warn("This object is deprecated, use KeyphraseDensityAssessment instead.")}},t.default=f},34847:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var s=r(65736),n=r(92819),i=l(r(9017)),a=r(49061),o=l(r(73054));function l(e){return e&&e.__esModule?e:{default:e}}class u extends i.default{constructor(e={}){super();const t={scores:{good:9,bad:3},urlTitle:(0,a.createAnchorOpeningTag)("https://yoa.st/33k"),urlCallToAction:(0,a.createAnchorOpeningTag)("https://yoa.st/33l")};this.identifier="metaDescriptionKeyword",this._config=(0,n.merge)(t,e)}getResult(e,t){this._canAssess=!1,e.hasKeyword()&&e.hasDescription()&&(this._keyphraseCounts=t.getResearch("metaDescriptionKeyword"),this._canAssess=!0);const r=new o.default,n=this.calculateResult();return r.setScore(n.score),r.setText(n.resultText),r.getScore()<9&&(r.setHasJumps(!0),e.hasKeyword()?(r.setEditFieldName("description"),r.setEditFieldAriaLabel((0,s.__)("Edit your meta description","wordpress-seo"))):(r.setEditFieldName("keyphrase"),r.setEditFieldAriaLabel((0,s.__)("Edit your keyphrase","wordpress-seo")))),r}calculateResult(){return this._canAssess?1===this._keyphraseCounts||2===this._keyphraseCounts?{score:this._config.scores.good,resultText:(0,s.sprintf)(/* translators: %1$s expands to a link on yoast.com, %2$s expands to the anchor end tag. */
+(0,s.__)("%1$sKeyphrase in meta description%2$s: Keyphrase or synonym appear in the meta description. Well done!","wordpress-seo"),this._config.urlTitle,"")}:this._keyphraseCounts>=3?{score:this._config.scores.bad,resultText:(0,s.sprintf)(
+/**
+ * translators:
+ * %1$s expands to a link on yoast.com, %2$s expands to the anchor end tag,
+ * %3$s expands to the number of sentences containing the keyphrase,
+ * %4$s expands to a link on yoast.com, %5$s expands to the anchor end tag.
+ */
+(0,s.__)("%1$sKeyphrase in meta description%2$s: The meta description contains the keyphrase %3$s times, which is over the advised maximum of 2 times. %4$sLimit that%5$s!","wordpress-seo"),this._config.urlTitle,"",this._keyphraseCounts,this._config.urlCallToAction,"")}:{score:this._config.scores.bad,resultText:(0,s.sprintf)(
+/**
+ * translators:
+ * %1$s expands to a link on yoast.com, %2$s expands to the anchor end tag.
+ * %3$s expands to a link on yoast.com, %4$s expands to the anchor end tag.
+ */
+(0,s.__)("%1$sKeyphrase in meta description%2$s: The meta description has been specified, but it does not contain the keyphrase. %3$sFix that%4$s!","wordpress-seo"),this._config.urlTitle,"",this._config.urlCallToAction,"")}:{score:this._config.scores.bad,resultText:(0,s.sprintf)(/* translators: %1$s and %2$s expand to a link on yoast.com, %3$s expands to the anchor end tag. */
+(0,s.__)("%1$sKeyphrase in meta description%3$s: %2$sPlease add both a keyphrase and a meta description containing the keyphrase%3$s.","wordpress-seo"),this._config.urlTitle,this._config.urlCallToAction,"")}}}t.default=u},70476:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var s=r(65736),n=r(92819),i=u(r(9017)),a=r(49061),o=u(r(73054)),l=u(r(49581));function u(e){return e&&e.__esModule?e:{default:e}}class c extends i.default{constructor(e={}){super();const t={recommendedMaximumLength:120,maximumLength:156,scores:{noMetaDescription:1,tooLong:6,tooShort:6,correctLength:9},urlTitle:(0,a.createAnchorOpeningTag)("https://yoa.st/34d"),urlCallToAction:(0,a.createAnchorOpeningTag)("https://yoa.st/34e")};this.identifier="metaDescriptionLength",this._config=(0,n.merge)(t,e)}getMaximumLength(e){return this.getConfig(e).maximumLength}getConfig(e){let t=this._config;return"ja"===e&&(t=(0,n.merge)(t,l.default)),t}getResult(e,t){const r=t.getResearch("metaDescriptionLength"),n=new o.default,i=t.getConfig("language"),a=this.getConfig(i);return n.setScore(this.calculateScore(r,i)),n.setText(this.translateScore(r,a)),n.getScore()<9&&(n.setHasJumps(!0),n.setEditFieldName("description"),n.setEditFieldAriaLabel((0,s.__)("Edit your meta description","wordpress-seo"))),n.max=a.maximumLength,n.actual=r,n}calculateScore(e,t){const r=this.getConfig(t);return 0===e?r.scores.noMetaDescription:e<=this._config.recommendedMaximumLength?r.scores.tooShort:e>this._config.maximumLength?r.scores.tooLong:r.scores.correctLength}translateScore(e,t){return 0===e?(0,s.sprintf)(/* translators: %1$s and %2$s expand to a links on yoast.com, %3$s expands to the anchor end tag */
+(0,s.__)("%1$sMeta description length%3$s: No meta description has been specified. Search engines will display copy from the page instead. %2$sMake sure to write one%3$s!","wordpress-seo"),t.urlTitle,t.urlCallToAction,""):e<=t.recommendedMaximumLength?(0,s.sprintf)(
+/* translators: %1$s and %2$s expand to links on yoast.com, %3$s expands to the anchor end tag,
+ %4$d expands to the number of characters in the meta description, %5$d expands to
+ the total available number of characters in the meta description */
+(0,s.__)("%1$sMeta description length%3$s: The meta description is too short (under %4$d characters). Up to %5$d characters are available. %2$sUse the space%3$s!","wordpress-seo"),t.urlTitle,t.urlCallToAction,"",t.recommendedMaximumLength,t.maximumLength):e>t.maximumLength?(0,s.sprintf)(
+/* translators: %1$s and %2$s expand to links on yoast.com, %3$s expands to the anchor end tag,
+ %4$d expands to the total available number of characters in the meta description */
+(0,s.__)("%1$sMeta description length%3$s: The meta description is over %4$d characters. To ensure the entire description will be visible, %2$syou should reduce the length%3$s!","wordpress-seo"),t.urlTitle,t.urlCallToAction,"",t.maximumLength):(0,s.sprintf)(/* translators: %1$s expands to a link on yoast.com, %2$s expands to the anchor end tag */
+(0,s.__)("%1$sMeta description length%2$s: Well done!","wordpress-seo"),t.urlTitle,"")}}t.default=c},27661:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var s=r(65736),n=r(92819),i=l(r(9017)),a=r(49061),o=l(r(73054));function l(e){return e&&e.__esModule?e:{default:e}}class u extends i.default{constructor(e={}){super();const t={scores:{noLinks:3,allNofollowed:7,someNoFollowed:8,allFollowed:9},urlTitle:(0,a.createAnchorOpeningTag)("https://yoa.st/34f"),urlCallToAction:(0,a.createAnchorOpeningTag)("https://yoa.st/34g")};this.identifier="externalLinks",this._config=(0,n.merge)(t,e)}getResult(e,t){const r=t.getResearch("getLinkStatistics"),s=new o.default;return(0,n.isEmpty)(r)||(s.setScore(this.calculateScore(r)),s.setText(this.translateScore(r))),s}calculateScore(e){return 0===e.externalTotal?this._config.scores.noLinks:e.externalNofollow===e.externalTotal?this._config.scores.allNofollowed:e.externalDofollow"):e.externalNofollow===e.externalTotal?(0,s.sprintf)(/* translators: %1$s and %2$s expand to links on yoast.com, %3$s expands to the anchor end tag */
+(0,s.__)("%1$sOutbound links%3$s: All outbound links on this page are nofollowed. %2$sAdd some normal links%3$s.","wordpress-seo"),this._config.urlTitle,this._config.urlCallToAction,""):e.externalDofollow===e.externalTotal?(0,s.sprintf)(/* translators: %1$s expands to a link on yoast.com, %2$s expands to the anchor end tag */
+(0,s.__)("%1$sOutbound links%2$s: Good job!","wordpress-seo"),this._config.urlTitle,""):e.externalDofollow"):""}}t.default=u},46430:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var s=r(65736),n=r(92819),i=u(r(9017)),a=r(76663),o=r(33140),l=u(r(73054));function u(e){return e&&e.__esModule?e:{default:e}}class c extends i.default{constructor(e={},t=!1){super();const r={minLength:400,maxLength:600,scores:{noTitle:1,widthTooShort:6,widthTooLong:3,widthCorrect:9},urlTitle:(0,o.createAnchorOpeningTag)("https://yoa.st/34h"),urlCallToAction:(0,o.createAnchorOpeningTag)("https://yoa.st/34i")};this._allowShortTitle=t,this.identifier="titleWidth",this._config=(0,n.merge)(r,e)}getMaximumLength(){return 600}getResult(e,t){const r=t.getResearch("pageTitleWidth"),n=new l.default;return n.setScore(this.calculateScore(r)),n.setText(this.translateScore(r)),n.getScore()<9&&(n.setHasJumps(!0),n.setEditFieldName("title"),n.setEditFieldAriaLabel((0,s.__)("Edit your SEO title","wordpress-seo"))),n.max=this._config.maxLength,n.actual=r,n}calculateScore(e){return(0,a.inRangeEndInclusive)(e,1,400)?this._config.scores.widthTooShort:(0,a.inRangeEndInclusive)(e,this._config.minLength,this._config.maxLength)?this._config.scores.widthCorrect:e>this._config.maxLength?this._config.scores.widthTooLong:this._config.scores.noTitle}translateScore(e){return(0,a.inRangeEndInclusive)(e,1,400)?this._allowShortTitle?(0,s.sprintf)(/* translators: %1$s expands to a link on yoast.com, %2$s expands to the anchor end tag */
+(0,s.__)("%1$sSEO title width%2$s: Good job!","wordpress-seo"),this._config.urlTitle,""):(0,s.sprintf)(/* translators: %1$s and %2$s expand to links on yoast.com, %3$s expands to the anchor end tag */
+(0,s.__)("%1$sSEO title width%3$s: The SEO title is too short. %2$sUse the space to add keyphrase variations or create compelling call-to-action copy%3$s.","wordpress-seo"),this._config.urlTitle,this._config.urlCallToAction,""):(0,a.inRangeEndInclusive)(e,this._config.minLength,this._config.maxLength)?(0,s.sprintf)(/* translators: %1$s expands to a link on yoast.com, %2$s expands to the anchor end tag */
+(0,s.__)("%1$sSEO title width%2$s: Good job!","wordpress-seo"),this._config.urlTitle,""):e>this._config.maxLength?(0,s.sprintf)(/* translators: %1$s and %2$s expand to links on yoast.com, %3$s expands to the anchor end tag */
+(0,s.__)("%1$sSEO title width%3$s: The SEO title is wider than the viewable limit. %2$sTry to make it shorter%3$s.","wordpress-seo"),this._config.urlTitle,this._config.urlCallToAction,""):(0,s.sprintf)(/* translators: %1$s and %2$s expand to links on yoast.com, %3$s expands to the anchor end tag */
+(0,s.__)("%1$sSEO title width%3$s: %2$sPlease create an SEO title%3$s.","wordpress-seo"),this._config.urlTitle,this._config.urlCallToAction,"")}}t.default=c},76369:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var s=r(92819),n=o(r(9017)),i=o(r(73054)),a=r(49061);function o(e){return e&&e.__esModule?e:{default:e}}class l extends n.default{constructor(e={}){super(),this.identifier="productIdentifier",this._config=(0,s.merge)({scores:{good:9,ok:6},urlTitle:"https://yoa.st/4ly",urlCallToAction:"https://yoa.st/4lz",assessVariants:!1,shouldShowEditButton:!1,editFieldAriaLabel:"Edit your product identifiers",callbacks:{}},e)}getResult(e){const t=e.getCustomData(),r=this.scoreProductIdentifier(t,this._config),s=new i.default;return r&&(s.setScore(r.score),s.setText(r.text)),s.getScore()<9&&this._config.shouldShowEditButton&&(s.setHasJumps(!0),s.setEditFieldName("productIdentifier"),s.setEditFieldAriaLabel(this._config.editFieldAriaLabel)),s}isApplicable(e){const t=e.getCustomData();return!(!1===t.canRetrieveGlobalIdentifier&&(["simple","external","grouped"].includes(t.productType)||!1===t.hasVariants)||!1===t.canRetrieveVariantIdentifiers&&!0===t.hasVariants&&"variable"===t.productType||!1===this._config.assessVariants&&t.hasVariants)}scoreProductIdentifier(e,t){const{good:r,okay:s}=this.getFeedbackStrings();return["simple","grouped","external"].includes(e.productType)||"variable"===e.productType&&!e.hasVariants?e.hasGlobalIdentifier?{score:t.scores.good,text:r.withoutVariants}:{score:t.scores.ok,text:s.withoutVariants}:"variable"===e.productType&&e.hasVariants?e.doAllVariantsHaveIdentifier?{score:t.scores.good,text:r.withVariants}:{score:t.scores.ok,text:s.withVariants}:{}}getFeedbackStrings(){const e=(0,a.createAnchorOpeningTag)(this._config.urlTitle),t=(0,a.createAnchorOpeningTag)(this._config.urlCallToAction);if(!this._config.callbacks.getResultTexts){const r={good:{withoutVariants:"%1$sProduct identifier%3$s: Your product has an identifier. Good job!",withVariants:"%1$sProduct identifier%3$s: All your product variants have an identifier. Good job!"},okay:{withoutVariants:"%1$sProduct identifier%3$s: Your product is missing an identifier (like a GTIN code). %2$sInclude it if you can, as it will help search engines to better understand your content.%3$s",withVariants:"%1$sProduct identifier%3$s: Not all your product variants have an identifier. %2$sInclude it if you can, as it will help search engines to better understand your content.%3$s"}};return r.good=(0,s.mapValues)(r.good,(r=>this.formatResultText(r,e,t))),r.okay=(0,s.mapValues)(r.okay,(r=>this.formatResultText(r,e,t))),r}return this._config.callbacks.getResultTexts({urlTitleAnchorOpeningTag:e,urlActionAnchorOpeningTag:t})}}t.default=l},11842:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var s=r(92819),n=o(r(9017)),i=o(r(73054)),a=r(49061);function o(e){return e&&e.__esModule?e:{default:e}}class l extends n.default{constructor(e={}){super(),this.identifier="productSKU",this._config=(0,s.merge)({scores:{good:9,ok:6},urlTitle:"https://yoa.st/4lw",urlCallToAction:"https://yoa.st/4lx",assessVariants:!1,shouldShowEditButton:!1,editFieldAriaLabel:"Edit your SKU",callbacks:{}},e)}getResult(e){const t=e.getCustomData(),r=this.scoreProductSKU(t,this._config),s=new i.default;return r&&(s.setScore(r.score),s.setText(r.text)),s.getScore()<9&&this._config.shouldShowEditButton&&(s.setHasJumps(!0),s.setEditFieldName("productSKU"),s.setEditFieldAriaLabel(this._config.editFieldAriaLabel)),s}isApplicable(e){const t=e.getCustomData();return!(!1===t.canRetrieveGlobalSku&&(["simple","external"].includes(t.productType)||!1===t.hasVariants)||!1===t.canRetrieveVariantSkus&&!0===t.hasVariants&&"variable"===t.productType||!1===this._config.assessVariants&&t.hasVariants)}scoreProductSKU(e,t){const{good:r,okay:s}=this.getFeedbackStrings();return["simple","external","grouped"].includes(e.productType)||"variable"===e.productType&&!e.hasVariants?e.hasGlobalSKU?{score:t.scores.good,text:r.withoutVariants}:{score:t.scores.ok,text:s.withoutVariants}:"variable"===e.productType&&e.hasVariants?e.doAllVariantsHaveSKU?{score:t.scores.good,text:r.withVariants}:{score:t.scores.ok,text:s.withVariants}:{}}getFeedbackStrings(){const e=(0,a.createAnchorOpeningTag)(this._config.urlTitle),t=(0,a.createAnchorOpeningTag)(this._config.urlCallToAction);if(!this._config.callbacks.getResultTexts){const r={good:{withoutVariants:"%1$sSKU%3$s: Your product has a SKU. Good job!",withVariants:"%1$sSKU%3$s: All your product variants have a SKU. Good job!"},okay:{withoutVariants:"%1$sSKU%3$s: Your product is missing a SKU. %2$sInclude it if you can, as it will help search engines to better understand your content.%3$s",withVariants:"%1$sSKU%3$s: Not all your product variants have a SKU. %2$sInclude it if you can, as it will help search engines to better understand your content.%3$s"}};return r.good=(0,s.mapValues)(r.good,(r=>this.formatResultText(r,e,t))),r.okay=(0,s.mapValues)(r.okay,(r=>this.formatResultText(r,e,t))),r}return this._config.callbacks.getResultTexts({urlTitleAnchorOpeningTag:e,urlActionAnchorOpeningTag:t})}}t.default=l},47502:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var s=r(65736),n=r(92819),i=c(r(9017)),a=r(49061),o=c(r(15010)),l=c(r(73054)),u=c(r(41054));function c(e){return e&&e.__esModule?e:{default:e}}class d extends i.default{constructor(e={}){super();const t={scores:{good:8,bad:1},urlTitle:(0,a.createAnchorOpeningTag)("https://yoa.st/3a6"),urlCallToAction:(0,a.createAnchorOpeningTag)("https://yoa.st/3a7")};this.identifier="singleH1",this._config=(0,n.merge)(t,e)}getResult(e,t){this._h1s=t.getResearch("h1s");const r=new l.default,s=this.calculateResult();return r.setScore(s.score),r.setText(s.resultText),1===s.score&&r.setHasMarks(!0),r}calculateResult(){return this._h1s.length<=1?{score:this._config.scores.good,resultText:(0,s.sprintf)(/* translators: %1$s expands to a link on yoast.com, %2$s expands to the anchor end tag */
+(0,s.__)("%1$sSingle title%2$s: You don't have multiple H1 headings, well done!","wordpress-seo"),this._config.urlTitle,"")}:this._h1s.length>1?{score:this._config.scores.bad,resultText:(0,s.sprintf)(/* translators: %1$s and %2$s expand to links on yoast.com, %3$s expands to the anchor end tag */
+(0,s.__)("%1$sSingle title%3$s: H1s should only be used as your main title. Find all H1s in your text that aren't your main title and %2$schange them to a lower heading level%3$s!","wordpress-seo"),this._config.urlTitle,this._config.urlCallToAction,"")}:void 0}getMarks(){return this._h1s.map((function(e){return new u.default({original:"