Current File : /home/honehdyv/readbtooom.com/wp-content/plugins/autodescription/inc/classes/init.class.php |
<?php
/**
* @package The_SEO_Framework\Classes\Facade\Init
*/
namespace The_SEO_Framework;
\defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
/**
* The SEO Framework plugin
* Copyright (C) 2015 - 2023 Sybre Waaijer, CyberWire B.V. (https://cyberwire.nl/)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as published
* by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Class The_SEO_Framework\Init
*
* Outputs all data in front-end header
*
* @since 2.8.0
*/
class Init extends Query {
/**
* A true legacy. Ran the plugin on the front-end.
*
* @since 1.0.0
* @since 2.8.0 Silently deprecated. Displaying legacy roots.
* @deprecated
* @ignore
*/
public function autodescription_run() {
$this->init_the_seo_framework();
}
/**
* Initializes the plugin actions and filters.
*
* @since 2.8.0
*/
public function init_the_seo_framework() {
/**
* @since 2.8.0
* Runs before the plugin is initialized.
*/
\do_action( 'the_seo_framework_init' );
$this->init_global_actions();
$this->init_global_filters();
if ( \is_admin() ) {
$this->init_admin_actions();
} else {
$this->init_front_end_actions();
$this->init_front_end_filters();
}
/**
* @since 3.1.0
* Runs after the plugin is initialized.
* Use this to remove filters and actions.
*/
\do_action( 'the_seo_framework_after_init' );
}
/**
* Initializes the plugin front- and back-end actions.
*
* @since 2.8.0
*/
public function init_global_actions() {
if ( \wp_doing_cron() )
$this->init_cron_actions();
if ( \wp_doing_ajax() )
$this->init_ajax_actions();
}
/**
* Initializes the plugin front- and back-end filters.
*
* @since 2.8.0
*/
public function init_global_filters() {
// Adjust category link to accommodate primary term.
\add_filter( 'post_link_category', [ $this, '_adjust_post_link_category' ], 10, 3 );
}
/**
* Initializes cron actions.
*
* @since 2.8.0
* @since 4.1.2 1. Added hook for sitemap prerender.
* 2. Added hook for ping retry.
* @since 4.2.0 Is now protexted
* @access protected
*/
protected function init_cron_actions() {
// Init post update/delete caching actions which may occur during cronjobs.
$this->init_post_cache_actions();
// Ping searchengines.
if ( $this->get_option( 'ping_use_cron' ) ) {
if ( $this->get_option( 'sitemaps_output' ) && $this->get_option( 'ping_use_cron_prerender' ) )
\add_action( 'tsf_sitemap_cron_hook_before', [ new Builders\Sitemap\Base, 'prerender_sitemap' ] );
\add_action( 'tsf_sitemap_cron_hook', [ Bridges\Ping::class, 'ping_search_engines' ] );
\add_action( 'tsf_sitemap_cron_hook_retry', [ Bridges\Ping::class, 'retry_ping_search_engines' ] );
}
}
/**
* Initializes AJAX actions.
*
* @since 4.1.4
*/
protected function init_ajax_actions() {
// Admin AJAX for notice dismissal.
\add_action( 'wp_ajax_tsf_dismiss_notice', [ Bridges\AJAX::class, '_wp_ajax_dismiss_notice' ] );
// Admin AJAX for TSF Cropper
\add_action( 'wp_ajax_tsf_crop_image', [ Bridges\AJAX::class, '_wp_ajax_crop_image' ] );
// Admin AJAX for counter options.
\add_action( 'wp_ajax_tsf_update_counter', [ Bridges\AJAX::class, '_wp_ajax_update_counter_type' ] );
// Admin AJAX for Gutenberg SEO Bar update.
\add_action( 'wp_ajax_tsf_update_post_data', [ Bridges\AJAX::class, '_wp_ajax_get_post_data' ] );
}
/**
* Initializes Admin Menu actions.
*
* @since 2.7.0
*/
public function init_admin_actions() {
/**
* @since 2.8.0
* Runs before the plugin is initialized in the admin screens.
*/
\do_action( 'the_seo_framework_admin_init' );
// Initialize caching actions.
$this->init_admin_caching_actions();
if ( ! $this->is_headless['meta'] ) {
// Initialize term meta filters and actions.
$this->init_term_meta();
// Initialize term meta filters and actions.
$this->init_post_meta();
// Enqueue Post meta boxes.
\add_action( 'add_meta_boxes', [ $this, '_init_post_edit_view' ], 5, 2 );
// Enqueue Term meta output.
\add_action( 'current_screen', [ $this, '_init_term_edit_view' ] );
// Adds post states to list view tables.
\add_filter( 'display_post_states', [ $this, '_add_post_state' ], 10, 2 );
// Initialize the SEO Bar for tables.
\add_action( 'admin_init', [ $this, '_init_seo_bar_tables' ] );
// Initialize List Edit for tables.
\add_action( 'admin_init', [ $this, '_init_list_edit' ] );
}
if ( ! $this->is_headless['settings'] ) {
// Set up site settings and allow saving resetting them.
\add_action( 'admin_init', [ $this, 'register_settings' ], 5 );
// Loads setting notices.
\add_action( 'the_seo_framework_setting_notices', [ $this, '_do_settings_page_notices' ] );
// Add menu links and register $this->seo_settings_page_hook
\add_action( 'admin_menu', [ $this, 'add_menu_link' ] );
}
if ( ! $this->is_headless['user'] ) {
// Initialize user meta filters and actions.
$this->init_user_meta();
// Enqueue user meta output.
\add_action( 'current_screen', [ $this, '_init_user_edit_view' ] );
}
if ( \in_array( false, $this->is_headless, true ) ) {
// Set up notices.
\add_action( 'admin_notices', [ $this, '_output_notices' ] );
// Fallback HTML-only notice dismissal.
\add_action( 'admin_init', [ $this, '_dismiss_notice' ] );
// Enqueues admin scripts.
\add_action( 'admin_enqueue_scripts', [ $this, '_init_admin_scripts' ], 0, 1 );
}
// Add plugin links to the plugin activation page.
\add_filter(
'plugin_action_links_' . THE_SEO_FRAMEWORK_PLUGIN_BASENAME,
[ '\The_SEO_Framework\Bridges\PluginTable', '_add_plugin_action_links' ],
10,
2
);
\add_filter(
'plugin_row_meta',
[ '\The_SEO_Framework\Bridges\PluginTable', '_add_plugin_row_meta' ],
10,
2
);
/**
* @since 2.9.4
* Runs after the plugin is initialized in the admin screens.
* Use this to remove actions.
*/
\do_action( 'the_seo_framework_after_admin_init' );
}
/**
* Initializes front-end actions.
* Disregards other SEO plugins, the meta output does look at detection.
*
* WARNING: Do not use query functions here.
*
* @since 2.5.2
*/
protected function init_front_end_actions() {
/**
* @since 2.8.0
* Runs before the plugin is initialized on the front-end.
*/
\do_action( 'the_seo_framework_front_init' );
// Remove canonical header tag from WP
\remove_action( 'wp_head', 'rel_canonical' );
// Remove shortlink.
\remove_action( 'wp_head', 'wp_shortlink_wp_head' );
// Remove adjecent rel tags.
\remove_action( 'wp_head', 'adjacent_posts_rel_link_wp_head' );
// Earlier removal of the generator tag. Doesn't require filter.
\remove_action( 'wp_head', 'wp_generator' );
// Prepares sitemap or stylesheet output.
if ( $this->can_run_sitemap() ) {
\add_action( 'template_redirect', [ $this, '_init_sitemap' ], 1 );
\add_filter( 'wp_sitemaps_enabled', '__return_false' );
} else {
// Augment Core sitemaps. Can't hook into `wp_sitemaps_init` as we're augmenting the providers before that.
$this->_init_core_sitemap();
}
// Initialize 301 redirects.
\add_action( 'template_redirect', [ $this, '_init_custom_field_redirect' ] );
// Prepares requisite robots headers to avoid low-quality content penalties.
$this->prepare_robots_headers();
\add_action( 'the_seo_framework_before_meta_output', [ $this, '_do_deprecated_output_hooks_before' ], 5 );
\add_action( 'the_seo_framework_after_meta_output', [ $this, '_do_deprecated_output_hooks_after' ], 15 );
// Output meta tags.
\add_action( 'wp_head', [ $this, 'html_output' ], 1 );
if ( $this->get_option( 'alter_archive_query' ) )
$this->init_alter_archive_query();
if ( $this->get_option( 'alter_search_query' ) )
$this->init_alter_search_query();
// Modify the feed.
if ( $this->get_option( 'excerpt_the_feed' ) || $this->get_option( 'source_the_feed' ) ) {
// We could use actions 'do_feed_{$feed}', but I don't trust its variability.
// We could use action 'rss_tag_pre', but I don't trust its availability.
\add_action( 'template_redirect', [ $this, '_init_feed' ], 1 );
}
/**
* @since 2.9.4
* Runs before the plugin is initialized on the front-end.
* Use this to remove actions.
*/
\do_action( 'the_seo_framework_after_front_init' );
}
/**
* Runs front-end filters.
*
* @since 2.5.2
*/
protected function init_front_end_filters() {
// Overwrite the robots.txt file
\add_filter( 'robots_txt', [ $this, 'robots_txt' ], 10, 2 );
/**
* @since 2.9.3
* @param bool $overwrite_titles Whether to enable title overwriting.
*/
if ( \apply_filters( 'the_seo_framework_overwrite_titles', true ) ) {
// Removes all pre_get_document_title filters.
\remove_all_filters( 'pre_get_document_title', false );
// New WordPress 4.4.0 filter. Hurray! It's also much faster :)
\add_filter( 'pre_get_document_title', [ $this, 'get_document_title' ], 10 );
/**
* @since 2.4.1
* @param bool $overwrite_titles Whether to enable legacy title overwriting.
*
* TODO remove this block? -- it's been 7 years...
* <https://make.wordpress.org/core/2015/10/20/document-title-in-4-4/>
*/
if ( \apply_filters( 'the_seo_framework_manipulate_title', true ) ) {
\remove_all_filters( 'wp_title', false );
// Override WordPress Title
\add_filter( 'wp_title', [ $this, 'get_wp_title' ], 9 );
// Override WooThemes Title TODO move this to wc compat file.
\add_filter( 'woo_title', [ $this, 'get_document_title' ], 99 );
}
}
/**
* @since 4.1.4
* @param bool $kill_core_robots Whether you lack sympathy for rocks tricked to think.
*/
if ( \apply_filters( 'the_seo_framework_kill_core_robots', true ) ) {
\remove_filter( 'wp_robots', 'wp_robots_max_image_preview_large' );
// Reconsider readding this to "supported" queries only?
\remove_filter( 'wp_robots', 'wp_robots_noindex_search' );
}
if ( $this->get_option( 'og_tags' ) ) { // independent from filter at use_og_tags--let that be deciding later.
// Disable Jetpack's Open Graph tags. But Sybre, compat files? Yes.
\add_filter( 'jetpack_enable_open_graph', '__return_false' );
}
if ( $this->get_option( 'twitter_tags' ) ) { // independent from filter at use_twitter_tags--let that be deciding later.
// Disable Jetpack's Twitter Card tags. But Sybre, compat files? Maybe.
\add_filter( 'jetpack_disable_twitter_cards', '__return_true' );
// Future, maybe. See <https://github.com/Automattic/jetpack/issues/13146#issuecomment-516841698>
// \add_filter( 'jetpack_enable_twitter_cards', '__return_false' );
}
if ( ! $this->get_option( 'oembed_scripts' ) ) {
/**
* Only hide the scripts, don't permeably purge them. This should be enough.
*
* This will still allow embedding within WordPress Multisite via WP-REST's proxy, since WP won't look for a script.
* We'd need to empty 'oembed_response_data' in that case... However, thanks to a bug in WP, this 'works' anyway.
* The bug: WP_oEmbed_Controller::get_proxy_item_permissions_check() always returns \WP_Error.
*/
\remove_action( 'wp_head', 'wp_oembed_add_discovery_links' );
}
/**
* WordPress also filters this at priority '10', but it's registered before this runs.
* Careful, WordPress can switch blogs when this filter runs. So, run this always,
* and assess options (uncached!) therein.
*/
\add_filter( 'oembed_response_data', [ $this, '_alter_oembed_response_data' ], 10, 4 );
}
/**
* Outputs deprecated output hooks.
*
* @since 4.2.0
* @access private
* @TODO delete me. v5.0.0+
*/
public function _do_deprecated_output_hooks_before() {
// phpcs:disable, WordPress.Security.EscapeOutput -- Everything we produce is escaped.
/**
* @since 2.6.0
* @since 4.2.0 Deprecated.
* @param string $before The content before the SEO output.
*/
echo \apply_filters_deprecated(
'the_seo_framework_pre',
[ '' ],
'4.2.0 of The SEO Framework',
'Action the_seo_framework_before_meta_output'
);
/**
* @since 2.2.6
* @since 4.2.0 Deprecated
* @param array $functions {
* 'callback' => string|array The function to call.
* 'args' => scalar|array Arguments. When array, each key is a new argument.
* }
*/
$functions = (array) \apply_filters_deprecated(
'the_seo_framework_before_output',
[ [] ],
'4.2.0 of The SEO Framework',
'Action the_seo_framework_before_meta_output'
);
foreach ( $functions as $function ) {
if ( ! empty( $function['callback'] ) )
echo \call_user_func_array( $function['callback'], [ ( $function['args'] ?? null ) ] );
}
// phpcs:enable, WordPress.Security.EscapeOutput
}
/**
* Outputs deprecated output hooks.
*
* @since 4.2.0
* @access private
* @TODO delete me. v5.0.0+
*/
public function _do_deprecated_output_hooks_after() {
// phpcs:disable, WordPress.Security.EscapeOutput -- Everything we produce is escaped.
/**
* @since 2.2.6
* @since 4.2.0 Deprecated
* @param array $functions {
* 'callback' => string|array The function to call.
* 'args' => scalar|array Arguments. When array, each key is a new argument.
* }
*/
$functions = (array) \apply_filters_deprecated(
'the_seo_framework_after_output',
[ [] ],
'4.2.0 of The SEO Framework',
'Action the_seo_framework_after_meta_output'
);
foreach ( $functions as $function ) {
if ( ! empty( $function['callback'] ) )
echo \call_user_func_array( $function['callback'], [ ( $function['args'] ?? null ) ] );
}
/**
* @since 2.6.0
* @since 4.2.0 Deprecated.
* @param string $after The content after the SEO output.
*/
echo \apply_filters_deprecated(
'the_seo_framework_pro',
[ '' ],
'4.2.0 of The SEO Framework',
'Action the_seo_framework_after_meta_output'
);
// phpcs:enable, WordPress.Security.EscapeOutput
}
/**
* Echos the header meta and scripts.
*
* @since 1.0.0
* @since 2.8.0 Cache is busted on each new release.
* @since 3.0.0 Now converts timezone if needed.
* @since 3.1.0 1. Now no longer outputs anything on preview.
* 2. Now no longer outputs anything on blocked post types.
* @since 4.0.0 Now no longer outputs anything on Customizer.
* @since 4.0.4 1. Now sets timezone to UTC to fix WP 5.3 bug <https://core.trac.wordpress.org/ticket/48623>
* 2. Now always sets timezone regardless of settings, because, again, bug.
* @since 4.2.0 No longer sets timezone.
* @since 4.2.7 No longer marked as private.
*/
public function html_output() {
if ( $this->is_preview() || \is_customize_preview() || ! $this->query_supports_seo() ) return;
/**
* @since 2.6.0
*/
\do_action( 'the_seo_framework_do_before_output' );
/**
* The bootstrap timer keeps adding when metadata is strapping.
* This causes both timers to increase simultaneously.
* We catch the bootstrap here, and let the meta-timer take over.
*/
$bootstrap_timer = _bootstrap_timer();
/**
* Start the meta timer here. This also catches file inclusions,
* which is also caught by the _bootstrap_timer().
*/
$init_start = microtime( true );
// phpcs:disable, WordPress.Security.EscapeOutput -- Output is escaped.
echo "\n", $this->get_plugin_indicator( 'before' );
$this->do_meta_output();
echo $this->get_plugin_indicator(
'after',
microtime( true ) - $init_start,
$bootstrap_timer
), "\n";
// phpcs:enable, WordPress.Security.EscapeOutput
/**
* @since 2.6.0
*/
\do_action( 'the_seo_framework_do_after_output' );
}
/**
* Outputs all meta tags for the current query.
*
* @since 4.1.4
* @since 4.2.0 1. Now invokes two actions before and after output.
* 2. No longer rectifies timezones.
*/
public function do_meta_output() {
/**
* @since 4.2.0
*/
\do_action( 'the_seo_framework_before_meta_output' );
$get = [ 'robots' ];
// Limit processing and redundant tags on 404 and search.
if ( $this->is_search() ) :
array_push(
$get,
...[
'og_locale',
'og_type',
'og_title',
'og_url',
'og_sitename',
'theme_color',
'shortlink',
'canonical',
'paged_urls',
'google_site_output',
'bing_site_output',
'yandex_site_output',
'baidu_site_output',
'pint_site_output',
]
);
elseif ( \is_404() ) :
array_push(
$get,
...[
'theme_color',
'google_site_output',
'bing_site_output',
'yandex_site_output',
'baidu_site_output',
'pint_site_output',
]
);
elseif ( $this->is_query_exploited() ) :
$get[] = 'advanced_query_protection';
else :
array_push(
$get,
...[
'the_description',
'og_image',
'og_locale',
'og_type',
'og_title',
'og_description',
'og_url',
'og_sitename',
'og_updated_time',
'facebook_publisher',
'facebook_author',
'facebook_app_id',
'article_published_time',
'article_modified_time',
'twitter_card',
'twitter_site',
'twitter_creator',
'twitter_title',
'twitter_description',
'twitter_image',
'theme_color',
'shortlink',
'canonical',
'paged_urls',
'ld_json',
'google_site_output',
'bing_site_output',
'yandex_site_output',
'baidu_site_output',
'pint_site_output',
]
);
endif;
// TODO add filter to $get? It won't last a few major updates though...
// But that's why I created this method like so... anyway... tough luck.
// phpcs:ignore, WordPress.Security.EscapeOutput -- Everything we produce is escaped.
foreach ( $get as $method ) echo $this->{$method}();
/**
* @since 4.2.0
*/
\do_action( 'the_seo_framework_after_meta_output' );
}
/**
* Redirects singular page to an alternate URL.
*
* @since 2.9.0
* @since 3.1.0 1. Now no longer redirects on preview.
* 2. Now listens to post type settings.
* @since 4.0.0 1. No longer tries to redirect on "search".
* 2. Added term redirect support.
* 3. No longer redirects on Customizer.
* @access private
*
* @return void early on non-singular pages.
*/
public function _init_custom_field_redirect() {
if ( $this->is_preview() || \is_customize_preview() || ! $this->query_supports_seo() ) return;
$url = $this->get_redirect_url();
if ( $url ) {
/**
* @since 4.1.2
* @param string $url The URL we're redirecting to.
*/
\do_action( 'the_seo_framework_before_redirect', $url );
$this->do_redirect( $url );
}
}
/**
* Redirects vistor to input $url.
*
* @since 2.9.0
*
* @param string $url The redirection URL
* @return void Early if no URL is supplied.
*/
public function do_redirect( $url = '' ) {
if ( 'template_redirect' !== \current_action() ) {
$this->_doing_it_wrong( __METHOD__, 'Only use this method on action "template_redirect".', '2.9.0' );
return;
}
// All WP defined protocols are allowed.
$url = \esc_url_raw( $url );
if ( empty( $url ) ) {
$this->_doing_it_wrong( __METHOD__, 'You need to supply an input URL.', '2.9.0' );
return;
}
/**
* @since 2.8.0
* @param int <unsigned> $redirect_type
*/
$redirect_type = \absint( \apply_filters( 'the_seo_framework_redirect_status_code', 301 ) );
if ( $redirect_type > 399 || $redirect_type < 300 )
$this->_doing_it_wrong( __METHOD__, 'You should use 3xx HTTP Status Codes. Recommended 301 and 302.', '2.8.0' );
if ( ! $this->allow_external_redirect() ) {
// Only HTTP/HTTPS and home URLs are allowed.
$path = $this->set_url_scheme( $url, 'relative' );
$url = \trailingslashit( $this->get_home_host() ) . ltrim( $path, ' /' );
// Maintain current request's scheme.
$scheme = $this->is_ssl() ? 'https' : 'http';
\wp_safe_redirect( $this->set_url_scheme( $url, $scheme ), $redirect_type );
exit;
}
// phpcs:ignore, WordPress.Security.SafeRedirect.wp_redirect_wp_redirect -- intended feature. Disable via $this->allow_external_redirect().
\wp_redirect( $url, $redirect_type );
exit;
}
/**
* Prepares sitemap output.
*
* @since 4.0.0
* @access private
*/
public function _init_sitemap() {
Bridges\Sitemap::get_instance()->_init();
}
/**
* Prepares core sitemap output.
*
* @since 4.1.2
* @access private
*/
public function _init_core_sitemap() {
// It's not a bridge, don't treat it like one: So, submit hooks here... But, clean me up?
\add_filter( 'wp_sitemaps_add_provider', [ Builders\CoreSitemaps\Main::class, '_filter_add_provider' ], 9, 2 );
\add_filter( 'wp_sitemaps_max_urls', [ Builders\CoreSitemaps\Main::class, '_filter_max_urls' ], 9 );
// We miss the proper hooks. https://github.com/sybrew/the-seo-framework/issues/610#issuecomment-1300191500
\add_filter( 'wp_sitemaps_posts_query_args', [ Builders\CoreSitemaps\Main::class, '_trick_filter_doing_sitemap' ], 11 );
}
/**
* Prepares feed modifications.
*
* @since 4.1.0
* @access private
*/
public function _init_feed() {
\is_feed() and new Bridges\Feed;
}
/**
* Edits the robots.txt output.
* Requires the site not to have a robots.txt file in the root directory.
*
* This methods completely hijacks default output, intentionally.
*
* The robots.txt file should be left as default, so to improve SEO.
* The Robots Exclusion Protocol encourages you not to use this file for
* non-administrative endpoints. Use the robots meta tags (and headers) instead.
*
* @since 2.2.9
* @since 2.9.3 Casts $public to string for check.
* @since 4.0.5 1. The output is now filterable.
* 2. Improved invalid location test.
* 3. No longer shortcircuits on non-public sites.
* 4. Now marked as private. Will be renamed to `_robots_txt()` in the future.
* @since 4.1.0 Now adds the WordPress Core sitemap URL.
* @since 4.1.2 Now only adds the WP Core sitemap URL when the provider tells us it's enabled.
* @since 4.1.4 Removed object caching support.
* @uses robots_txt filter located at WP core
* @access private
* @TODO extrapolate the contents without a warning to get_robots_txt(). Forward filter to it.
* See Monitor extension.
* @TODO rework into a workable standard...
*
* @param string $robots_txt The current robots_txt output. Not used.
* @param string $public The blog_public option value.
* @return string Robots.txt output.
*/
public function robots_txt( $robots_txt = '', $public = '' ) {
$site_path = \esc_attr( parse_url( \site_url(), PHP_URL_PATH ) ) ?: '';
/**
* @since 2.5.0
* @param string $pre The output before this plugin's output.
* Don't forget to add line breaks ( "\n" )!
*/
$output = (string) \apply_filters( 'the_seo_framework_robots_txt_pre', '' );
// Output defaults
$output .= "User-agent: *\n";
$output .= "Disallow: $site_path/wp-admin/\n";
$output .= "Allow: $site_path/wp-admin/admin-ajax.php\n";
/**
* @since 2.5.0
* @param bool $disallow Whether to disallow robots queries.
*/
if ( \apply_filters( 'the_seo_framework_robots_disallow_queries', false ) )
$output .= "Disallow: /*?*\n";
/**
* @since 2.5.0
* @param string $pro The output after this plugin's output.
* Don't forget to add line breaks ( "\n" )!
*/
$output .= (string) \apply_filters( 'the_seo_framework_robots_txt_pro', '' );
// Add extra whitespace and sitemap full URL
if ( $this->get_option( 'sitemaps_robots' ) ) {
if ( $this->get_option( 'sitemaps_output' ) ) {
$sitemaps = Bridges\Sitemap::get_instance();
foreach ( $sitemaps->get_sitemap_endpoint_list() as $id => $data )
if ( ! empty( $data['robots'] ) )
$output .= sprintf( "\nSitemap: %s", \esc_url( $sitemaps->get_expected_sitemap_endpoint_url( $id ) ) );
$output .= "\n";
} elseif ( ! $this->detect_sitemap_plugin() ) { // detect_sitemap_plugin() temp backward compat.
if ( $this->use_core_sitemaps() ) {
$wp_sitemaps_server = \wp_sitemaps_get_server();
if ( method_exists( $wp_sitemaps_server, 'add_robots' ) ) {
// This method augments the output--it doesn't overwrite it.
$output = \wp_sitemaps_get_server()->add_robots( $output, $public );
}
}
}
}
$raw_uri = rawurldecode(
\wp_check_invalid_utf8(
stripslashes( $_SERVER['REQUEST_URI'] )
)
) ?: '/robots.txt';
// Simple test for invalid directory depth. Even //robots.txt is an invalid location.
if ( strrpos( $raw_uri, '/' ) > 0 ) {
$error = sprintf(
"%s\n%s\n\n",
'# This is an invalid robots.txt location.',
'# Please visit: ' . \esc_url( \trailingslashit( $this->set_preferred_url_scheme( $this->get_home_host() ) ) . 'robots.txt' )
);
$output = "$error$output";
}
/**
* The robots.txt output.
*
* @since 4.0.5
* @param string $output The (cached) robots.txt output.
*/
return (string) \apply_filters( 'the_seo_framework_robots_txt', $output );
}
/**
* Prepares the X-Robots-Tag headers for various endpoints.
*
* @since 4.0.2
*/
protected function prepare_robots_headers() {
\add_action( 'template_redirect', [ $this, '_init_robots_headers' ] );
\add_action( 'the_seo_framework_sitemap_header', [ $this, '_output_robots_noindex_headers' ] );
// This is not necessarily a WordPress query. Test it inline.
if ( \defined( 'XMLRPC_REQUEST' ) && XMLRPC_REQUEST )
$this->_output_robots_noindex_headers();
}
/**
* Sets the X-Robots-Tag headers on various endpoints.
*
* @since 4.0.0
* @since 4.0.5 Added filter.
* @access private
*/
public function _init_robots_headers() {
/**
* @since 4.0.5
* @param bool $noindex Whether a noindex header must be set.
*/
if ( \apply_filters(
'the_seo_framework_set_noindex_header',
\is_robots() || ( ! $this->get_option( 'index_the_feed' ) && \is_feed() )
) ) $this->_output_robots_noindex_headers();
}
/**
* Sets the X-Robots tag headers to 'noindex'.
*
* @since 4.0.0
* @access private
*/
public function _output_robots_noindex_headers() {
headers_sent() or header( 'X-Robots-Tag: noindex', true );
}
/**
* Initializes search query adjustments.
*
* @since 2.9.4
*/
public function init_alter_search_query() {
switch ( $this->get_option( 'alter_search_query_type' ) ) :
case 'post_query':
\add_filter( 'the_posts', [ $this, '_alter_search_query_post' ], 10, 2 );
break;
default:
case 'in_query':
\add_action( 'pre_get_posts', [ $this, '_alter_search_query_in' ], 9999, 1 );
break;
endswitch;
}
/**
* Initializes archive query adjustments.
*
* @since 2.9.4
*/
public function init_alter_archive_query() {
switch ( $this->get_option( 'alter_archive_query_type' ) ) :
case 'post_query':
\add_filter( 'the_posts', [ $this, '_alter_archive_query_post' ], 10, 2 );
break;
default:
case 'in_query':
\add_action( 'pre_get_posts', [ $this, '_alter_archive_query_in' ], 9999, 1 );
break;
endswitch;
}
/**
* Alters search query.
*
* @since 2.9.4
* @since 3.0.0 Exchanged meta query for post__not_in query.
* @see Twenty Fourteen theme @source \Featured_Content::pre_get_posts()
* @access private
*
* @param \WP_Query $wp_query The WP_Query instance.
* @return void Early if no search query is found.
*/
public function _alter_search_query_in( $wp_query ) {
// Don't exclude pages in wp-admin.
if ( $wp_query->is_search ) {
// Only interact with an actual Search Query.
if ( ! isset( $wp_query->query['s'] ) )
return;
if ( $this->is_query_adjustment_blocked( $wp_query ) )
return;
$excluded = $this->get_ids_excluded_from_search();
if ( ! $excluded )
return;
$post__not_in = $wp_query->get( 'post__not_in' );
if ( ! empty( $post__not_in ) ) {
$excluded = array_unique(
array_merge( (array) $post__not_in, $excluded )
);
}
$wp_query->set( 'post__not_in', $excluded );
}
}
/**
* Alters archive query.
*
* @since 2.9.4
* @since 3.0.0 Exchanged meta query for post__not_in query.
* @see Twenty Fourteen theme @source \Featured_Content::pre_get_posts()
* @access private
*
* @param \WP_Query $wp_query The WP_Query instance.
* @return void Early if query alteration is useless or blocked.
*/
public function _alter_archive_query_in( $wp_query ) {
if ( $wp_query->is_archive || $wp_query->is_home ) {
if ( $this->is_query_adjustment_blocked( $wp_query ) )
return;
$excluded = $this->get_ids_excluded_from_archive();
if ( ! $excluded )
return;
$post__not_in = $wp_query->get( 'post__not_in' );
if ( ! empty( $post__not_in ) ) {
$excluded = array_unique(
array_merge( (array) $post__not_in, $excluded )
);
}
$wp_query->set( 'post__not_in', $excluded );
}
}
/**
* Alters search results after database query.
*
* @since 2.9.4
* @access private
*
* @param array $posts The array of retrieved posts.
* @param \WP_Query $wp_query The WP_Query instance.
* @return array $posts
*/
public function _alter_search_query_post( $posts, $wp_query ) {
if ( $wp_query->is_search ) {
if ( $this->is_query_adjustment_blocked( $wp_query ) )
return $posts;
foreach ( $posts as $n => $post ) {
if ( $this->get_post_meta_item( 'exclude_local_search', $post->ID ) )
unset( $posts[ $n ] );
}
// Reset numeric index.
$posts = array_values( $posts );
}
return $posts;
}
/**
* Alters archive results after database query.
*
* @since 2.9.4
* @access private
*
* @param array $posts The array of retrieved posts.
* @param \WP_Query $wp_query The WP_Query instance.
* @return array $posts
*/
public function _alter_archive_query_post( $posts, $wp_query ) {
if ( $wp_query->is_archive || $wp_query->is_home ) {
if ( $this->is_query_adjustment_blocked( $wp_query ) )
return $posts;
foreach ( $posts as $n => $post ) {
if ( $this->get_post_meta_item( 'exclude_from_archive', $post->ID ) )
unset( $posts[ $n ] );
}
// Reset numeric index.
$posts = array_values( $posts );
}
return $posts;
}
/**
* Determines whether the archive query adjustment is blocked.
*
* We do NOT treat this feature with security: If a post still slips through
* a query, then so be it. The post may be accessed anyway, otherwise,
* if not redirected. This last part is of concern, however, because one
* might think the contents of a post is hidden thanks to the redirect, for it
* to be exposable via other means. Nevertheless, we never (and won't ever)
* redirect REST queries, which may access post content regardless of user settings.
*
* Perhaps, we should add a disclaimer: Even IF you redirect the post, noindex it,
* exclude it from search and archive queries, the post content may still be readable
* to the public.
*
* @since 2.9.4
* @since 3.1.0 Now checks for the post type.
* @since 4.1.4 1. Renamed from `is_archive_query_adjustment_blocked()`
* 2. Added taxonomy-supported lookups.
* 3. Added WP Rest checks for the Block Editor.
* @since 4.2.0 Improved supported taxonomy loop.
* @since 4.2.6 Added check for `did_action( 'wp_loaded' )` early, before queries are tested and cached.
* @since 4.2.7 No longer affects the sitemap query.
*
* @param \WP_Query $wp_query WP_Query object.
* @return bool
*/
protected function is_query_adjustment_blocked( $wp_query ) {
static $has_filter = null;
if ( null === $has_filter )
$has_filter = \has_filter( 'the_seo_framework_do_adjust_archive_query' );
if ( $has_filter ) {
/**
* This filter affects both 'search-"archives"' and terms/taxonomies.
*
* @since 2.9.4
* @param bool $do True is unblocked (do adjustment), false is blocked (don't do adjustment).
* @param \WP_Query $wp_query The current query.
*/
if ( ! \apply_filters_ref_array( 'the_seo_framework_do_adjust_archive_query', [ true, $wp_query ] ) )
return true;
}
if ( ! \did_action( 'wp_loaded' ) )
return true;
if ( \defined( 'REST_REQUEST' ) && REST_REQUEST ) {
$referer = \wp_get_referer();
if ( false !== strpos( $referer, 'post.php' ) || false !== strpos( $referer, 'post-new.php' ) ) {
/**
* WordPress should've authenthicated the user at
* WP_REST_Server::check_authentication() -> rest_cookie_check_errors() -> wp_nonce et al.
* before executing the query. For REST_REQUEST can not be true otherwise. Ergo,
* \current_user_can() should work. If it returns true, we can trust it's a safe request.
* If it returns false, the user may still be logged in, but the request isn't sent via
* WordPress's API with the proper nonces supplied. This is as perfect as it can be.
*/
if ( \current_user_can( 'edit_posts' ) )
return true;
}
}
// If doing sitemap, don't adjust query via query settings.
if ( $this->is_sitemap() )
return true;
// This should primarily affect 'terms'. Test if TSF is blocked from supporting said terms.
if ( ! empty( $wp_query->tax_query->queries ) ) :
$supported = true;
foreach ( $wp_query->tax_query->queries as $_query ) {
if ( isset( $_query['taxonomy'] ) ) {
$supported = $this->is_taxonomy_supported( $_query['taxonomy'] );
// If just one tax is supported for this query, greenlight it: all must be blocking.
if ( $supported ) break;
}
}
if ( ! $supported )
return true;
endif;
return false;
}
/**
* Alters the oEmbed response data.
*
* @WARNING: WordPress can switch blogs as this filter runs. So, check all options again, without cache!
* This should only happen at `/oembed/1.0/proxy`.
* @since 4.0.5
* @since 4.1.1 Now also alters titles and images.
* @access private
*
* @param array $data The response data.
* @param \WP_Post $post The post object.
* @param int $width The requested width. Unused.
* @param int $height The calculated height. Unused.
* @return array Possibly altered $data.
*/
public function _alter_oembed_response_data( $data, $post, $width, $height ) {
// Don't use cache. See @WARNING in doc comment.
if ( $this->get_option( 'oembed_use_og_title', false ) )
$data['title'] = $this->get_open_graph_title( [ 'id' => $post->ID ] ) ?: $data['title'];
// Don't use cache. See @WARNING in doc comment.
if ( $this->get_option( 'oembed_use_social_image', false ) ) {
$image_details = current( $this->get_image_details(
[ 'id' => $post->ID ],
true,
'oembed',
true
) );
if ( $image_details && $image_details['url'] && $image_details['width'] && $image_details['height'] ) {
// Override WordPress provided data.
$data['thumbnail_url'] = $image_details['url'];
$data['thumbnail_width'] = $image_details['width'];
$data['thumbnail_height'] = $image_details['height'];
}
}
// Don't use cache. See @WARNING in doc comment.
if ( $this->get_option( 'oembed_remove_author', false ) )
unset( $data['author_url'], $data['author_name'] );
return $data;
}
}