plugin_file = $settings['file']; $this->plugin_slug = $settings['slug']; $this->plugin_name = $settings['name']; $this->plugin_version = $settings['version']; $this->get_settings(); $this->load(); } /** * Prevent Cloning * * @since 3 */ protected function __clone() {} /** * Get the Instance * * @since 8 * @param array $settings { * @type string $file Plugin Main File Path and Name * @type string $slug Plugin Slug * @type string $name Plugin Name * @type int $version Plugin Verion * } * @return singleton */ public static function getInstance( $settings ) { if ( null === self::$_instance ) { self::$_instance = new self( $settings ); } return self::$_instance; } /** * get plugin file * * @since 8 * @access public */ public function get_plugin_file() { return $this->plugin_file; } /** * get plugin slug * * @since 8 * @access public */ public function get_plugin_slug() { return $this->plugin_slug; } /** * get plugin name * * @since 8 * @access public */ public function get_plugin_name() { return $this->plugin_name; } /** * get plugin version * * @since 8 * @access public */ public function get_plugin_version() { return $this->plugin_version; } /** * get all settings * except 404page_method * the 404page_method setting is set in function set_mode() because it may be too early here and not everything is loaded properly * * since v 7 we have a settings class */ private function get_settings() { $this->settings = new PP_404Page_Settings(); } /** * Load * runs the init() function on firing of init action to ensure everything is loaded properly */ private function load() { add_action( 'init', array( $this, 'add_text_domain' ) ); add_action( 'init', array( $this, 'init' ) ); } /** * do plugin init * this runs after init action has fired to ensure everything is loaded properly */ function init() { // as of v 2.2 always call set_mode // as of v 2.4 we do not need to add an init action hook if ( !is_admin() && $this->get_id() > 0 ) { // as of v 3.0 we once check if there's a 404 page set and not in all functions separately $this->set_mode(); add_action( 'pre_get_posts', array ( $this, 'exclude_404page' ) ); add_filter( 'get_pages', array ( $this, 'remove_404page_from_array' ), 10, 2 ); // Stop URL guessing if activated if ( $this->settings->get_no_url_guessing() ) { add_filter( 'redirect_canonical' ,array ( $this, 'no_url_guessing' ) ); } // Remove 404 error page from YOAST sitemap // only if "Send an 404 error if the page is accessed directly by its URL" is active // @since 6 if ( $this->settings->get_fire_error() ) { add_filter( 'wpseo_exclude_from_sitemap_by_post_ids', function () { return array( $this->get_id() ); } ); } } if ( is_admin() ) { // load classes only if in admin // @since 10 $this->admin = new PP_404Page_Admin( $this, $this->settings ); $this->blockeditor = new PP_404Page_BlockEditor( $this ); $this->classiceditor = new PP_404Page_ClassicEditor( $this ); // Remove 404 page from post list if activated // not moved to PP_404Page_Admin because we also need exclude_404page() in frontend if ( $this->settings->get_hide() and $this->get_id() > 0 ) { add_action( 'pre_get_posts' ,array ( $this, 'exclude_404page' ) ); } } } /** * add text domain */ function add_text_domain() { load_plugin_textdomain( '404page' ); } /** * init filters */ function set_mode() { $this->settings->set_method(); if ( defined( 'CUSTOMIZR_VER' ) ) { // Customizr Compatibility Mode // @since 3.1 add_filter( 'body_class', array( $this, 'add_404_body_class_customizr_mode' ) ); add_filter( 'tc_404_header_content', array( $this, 'show404title_customizr_mode' ), 999 ); add_filter( 'tc_404_content', array( $this, 'show404_customizr_mode' ), 999 ); add_filter( 'tc_404_selectors', array( $this, 'show404articleselectors_customizr_mode' ), 999 ); // send http 410 instead of http 404 if requested resource is in trash // @since 3.2 if ( $this->settings->get_http410_if_trashed() ) { add_action( 'template_redirect', array( $this, 'maybe_send_410' ) ); } } elseif ( $this->settings->get_method() != 'STD' ) { // Compatibility Mode // as of v 2.4 we use the the_posts filter instead of posts_results, because the posts array is internally processed after posts_results fires add_filter( 'the_posts', array( $this, 'show404_compatiblity_mode' ), 999 ); // as of v 2.5 we remove the filter if the DW Question & Answer plugin by DesignWall (https://www.designwall.com/wordpress/plugins/dw-question-answer/) is active and we're in the answers list add_filter( 'dwqa_prepare_answers', array( $this, 'remove_show404_compatiblity_mode' ), 999 ); } else { // Standard Mode add_filter( '404_template', array( $this, 'show404_standard_mode' ), 999 ); if ( $this->settings->get_fire_error() ) { add_action( 'template_redirect', array( $this, 'do_404_header_standard_mode' ) ); } // send http 410 instead of http 404 if requested resource is in trash // @since 3.2 if ( $this->settings->get_http410_if_trashed() ) { add_action( 'template_redirect', array( $this, 'maybe_send_410' ) ); } } } /** * show 404 page * Standard Mode */ function show404_standard_mode( $template ) { global $wp_query; // @since 4 // fix for an ugly bbPress problem // see https://wordpress.org/support/topic/not-fully-bbpress-compatible/ // see https://bbpress.trac.wordpress.org/ticket/3161 // if a bbPress member page is shown and the member has no topics created yet the 404_template filter hook fires // this is a bbPress problem but it has not been fixed since 6 months // so let's bypass the problem if ( function_exists( 'bbp_is_single_user' ) ) { if ( bbp_is_single_user() ) { return $template; } } // that's it if ( ! $this->settings->get_native() ) { $wp_query = null; $wp_query = new WP_Query(); $wp_query->query( 'page_id=' . $this->get_page_id() ); $wp_query->the_post(); $template = get_page_template(); rewind_posts(); add_filter( 'body_class', array( $this, 'add_404_body_class' ) ); } $this->maybe_force_404(); $this->do_404page_action(); return $template; } /** * show 404 page * Compatibility Mode */ function show404_compatiblity_mode( $posts ) { global $wp_query; // remove the filter so we handle only the first query - no custom queries remove_filter( 'the_posts', array( $this, 'show404_compatiblity_mode' ), 999 ); // @since 4 // fix for an ugly bbPress problem // see show404_standard_mode() if ( function_exists( 'bbp_is_single_user' ) ) { if ( bbp_is_single_user() ) { return $posts; } } // that's it $pageid = $this->get_page_id(); if ( ! $this->settings->get_native() ) { // as of v 10 we also check if $wp_query->query[error] == 404 // this is necessary to bypass a WordPress bug // if permalink setting is something like e.g. /blog/%postname%/ the $posts is not empty // bug reported https://core.trac.wordpress.org/ticket/46000 if ( ( empty( $posts && is_main_query() && !is_robots() && !is_home() && !is_feed() && !is_search() && !is_archive() ) || ( isset( $wp_query->query['error'] ) && $wp_query->query['error'] == 404 ) ) && ( !defined('DOING_AJAX') || !DOING_AJAX ) ) { // as of v2.1 we do not alter the posts argument here because this does not work with SiteOrigin's Page Builder Plugin, template_include filter introduced $this->postid = $pageid; // as of v 2.4 we use the the_posts filter instead of posts_results // therefore we have to reset $wp_query // resetting $wp_query also forces us to remove the pre_get_posts action plus the get_pages filter remove_action( 'pre_get_posts', array ( $this, 'exclude_404page' ) ); remove_filter( 'get_pages', array ( $this, 'remove_404page_from_array' ), 10, 2 ); $wp_query = null; $wp_query = new WP_Query(); // @since 8 // added suppress_filters for compatibilty with current WPML version $wp_query->query( array( 'page_id' => $pageid, 'suppress_filters' => true ) ); $wp_query->the_post(); $this->template = get_page_template(); $posts = $wp_query->posts; $wp_query->rewind_posts(); add_action( 'wp', array( $this, 'do_404_header' ) ); add_filter( 'body_class', array( $this, 'add_404_body_class' ) ); add_filter( 'template_include', array( $this, 'change_404_template' ), 999 ); $this->maybe_force_404(); $this->do_404page_action(); } elseif ( 1 == count( $posts ) && 'page' == $posts[0]->post_type ) { // Do a 404 if the 404 page is opened directly if ( $this->settings->get_fire_error() ) { $curpageid = $posts[0]->ID; if ( defined( 'ICL_SITEPRESS_VERSION' ) ) { // WPML is active - get the post ID of the default language global $sitepress; $curpageid = apply_filters( 'wpml_object_id', $curpageid, 'page', $sitepress->get_default_language() ); $pageid = apply_filters( 'wpml_object_id', $pageid, 'page', $sitepress->get_default_language() ); } elseif ( defined( 'POLYLANG_VERSION' ) ) { // Polylang is active - get the post ID of the default language $curpageid = pll_get_post( $curpageid, pll_default_language() ); $pageid = pll_get_post( $pageid, pll_default_language() ); } if ( $pageid == $curpageid ) { add_action( 'wp', array( $this, 'do_404_header' ) ); add_filter( 'body_class', array( $this, 'add_404_body_class' ) ); $this->maybe_force_404(); $this->do_404page_action(); } } } } else { $this->maybe_force_404(); $this->do_404page_action(); } return $posts; } /** * for DW Question & Answer plugin * this function is called by the dwqa_prepare_answers filter */ function remove_show404_compatiblity_mode( $args ) { remove_filter( 'the_posts', array( $this, 'show404_compatiblity_mode' ), 999 ); return $args; } /** * this function overrides the page template in compatibilty mode */ function change_404_template( $template ) { // we have to check if the template file is there because if the theme was changed maybe a wrong template is stored in the database $new_template = locate_template( array( $this->template ) ); if ( '' != $new_template ) { return $new_template ; } return $template; } /** * send 404 HTTP header * Standard Mode */ function do_404_header_standard_mode() { if ( is_page() && get_the_ID() == $this->get_id() && !is_404() ) { status_header( 404 ); nocache_headers(); $this->maybe_force_404(); $this->do_404page_action(); } } /** * send 404 HTTP header * Compatibility Mode */ function do_404_header() { // remove the action so we handle only the first query - no custom queries remove_action( 'wp', array( $this, 'do_404_header' ) ); // send http 410 instead of http 404 if requested resource is in trash // @since 3.2 if ( $this->settings->get_http410_if_trashed() && $this->is_url_in_trash( rawurldecode ( ( is_ssl() ? 'https://' : 'http://' ) . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] ) ) ) { status_header( 410 ); } else { status_header( 404 ); } nocache_headers(); } /** * add body classes */ function add_404_body_class( $classes ) { // as of v 3.1 we first check if the class error404 already exists if ( ! in_array( 'error404', $classes ) ) { $classes[] = 'error404'; } // debug class // @since 3.1 $debug_class = 'pp404-'; if ( $this->settings->get_native() ) { $debug_class .= 'native'; } elseif ( defined( 'CUSTOMIZR_VER' ) ) { $debug_class .= 'customizr'; } elseif ( defined( 'ICL_SITEPRESS_VERSION' ) ) { $debug_class .= 'wpml'; } elseif ( $this->settings->get_method() != 'STD' ) { $debug_class .= 'cmp'; } else { $debug_class .= 'std'; } $classes[] = $debug_class; return $classes; } /** * add body classes customizr mode * @since 3.1 */ function add_404_body_class_customizr_mode( $classes ) { if ( is_404() ) { $classes = $this->add_404_body_class( $classes ); } return $classes; } /** * show title * Customizr Compatibility Mode */ function show404title_customizr_mode( $title ) { if ( ! $this->settings->get_native() ) { return '