PluginProbe ʕ •ᴥ•ʔ
Smart Custom 404 Error Page / trunk
Smart Custom 404 Error Page vtrunk
trunk 11.4.6 11.4.7 11.4.8
404page / inc / class-404page.php
404page / inc Last commit date
admin 1 year ago ppf 1 year ago class-404page-admin.php 1 year ago class-404page-block-editor.php 1 year ago class-404page-classic-editor.php 1 year ago class-404page-deprecated.php 1 year ago class-404page-settings.php 1 year ago class-404page.php 1 year ago index.php 9 years ago
class-404page.php
1259 lines
1 <?php
2
3 /**
4 * The 404page core plugin class
5 */
6
7 if ( ! defined( 'ABSPATH' ) ) {
8 exit; // Exit if accessed directly
9 }
10
11
12 // indicate that 404page plugin is active
13 if ( ! defined( 'PP_404' ) ) {
14 define( 'PP_404', true );
15 }
16
17
18 /**
19 * The core plugin class
20 */
21 if ( !class_exists( 'PP_404Page' ) ) {
22
23
24 class PP_404Page extends PPF09_Plugin {
25
26
27 /**
28 * Native Mode
29 *
30 * @since 11.0.0 - was part of previous settings class
31 * @var bool
32 * @access private
33 */
34 private $native;
35
36 private $template;
37 private $postid;
38
39
40 /**
41 * Admin Class
42 *
43 * @see class-404page-admin.php
44 * @since 10
45 * @var object
46 * @access private
47 */
48 private $admin;
49
50
51 /**
52 * Block Editor Class
53 *
54 * @see class-404page-block-editor.php
55 * @since 9
56 * @var object
57 * @access private
58 */
59 private $blockeditor;
60
61
62 /**
63 * Classic Editor Class
64 *
65 * @see class-404page-classic-editor.php
66 * @since 9
67 * @var object
68 * @access private
69 */
70 private $classiceditor;
71
72
73 /**
74 * Deprecated Class
75 *
76 * @see class-404page-deprecated.php
77 * @since 11.0.0
78 * @var object
79 * @access private
80 */
81 private $deprecated;
82
83
84 /**
85 * URL that caused the 404 error
86 *
87 * @since 11.4.0
88 * @var array
89 * @access private
90 */
91 private $error_url;
92
93
94 /**
95 * Init the Class
96 *
97 * @since 11.0.0
98 * was part of __construct before
99 */
100 public function plugin_init() {
101
102 // settings defaults
103 // @since 11.0.0
104 $defaults = array(
105 'page_id' => 0,
106 'hide' => false,
107 'fire_error' => true,
108 'force_error' => false,
109 'no_url_guessing' => false,
110 'http410_if_trashed' => false,
111 'http410_always' => false,
112 'method' => 'STD'
113 );
114
115 // since 11.0.0 we use add_settings_class() to load the settings
116 $this->add_settings_class( 'PP_404Page_Settings', 'class-404page-settings', $this, $defaults );
117
118 // @since 11.0.0
119 $this->add_action( 'init' );
120 }
121
122
123 /**
124 * do plugin init
125 * this runs after init action has fired to ensure everything is loaded properly
126 * was init() before 11.0.0
127 */
128 function action_init() {
129
130 // moved from add_text_domain() in v 11.0.0
131 load_plugin_textdomain( '404page' );
132
133
134 // change old stuff to new stuff for backward compatibility
135 // since v 11.0.0
136 $this->deprecated = $this->add_sub_class_always( 'PP_404Page_Deprecated', 'class-404page-deprecated', $this );
137
138 // load the following subclasses only on backend
139 // using add_sub_class_backend() sinde v 11
140 $this->admin = $this->add_sub_class_backend( 'PP_404Page_Admin', 'class-404page-admin', $this, $this->settings() );
141 $this->blockeditor = $this->add_sub_class_backend( 'PP_404Page_BlockEditor', 'class-404page-block-editor', $this, $this->settings() );
142 $this->classiceditor = $this->add_sub_class_backend( 'PP_404Page_ClassicEditor', 'class-404page-classic-editor', $this, $this->settings() );
143
144 // as of v 2.2 always call set_mode
145 // as of v 2.4 we do not need to add an init action hook
146
147 if ( !is_admin() && $this->settings()->get( 'page_id' ) > 0 ) {
148
149 // as of v 3.0 we once check if there's a 404 page set and not in all functions separately
150 $this->set_mode();
151 add_action( 'pre_get_posts', array ( $this, 'exclude_404page' ) );
152 add_filter( 'get_pages', array ( $this, 'remove_404page_from_array' ), 10, 2 );
153
154 // Stop URL guessing if activated
155 if ( $this->settings()->get( 'no_url_guessing' ) ) {
156 add_filter( 'redirect_canonical' ,array ( $this, 'no_url_guessing' ) );
157 }
158
159
160 // Remove 404 error page from XML sitemaps
161 // only if "Send an 404 error if the page is accessed directly by its URL" is active
162 if ( $this->settings()->get( 'fire_error' ) ) {
163
164 // YOAST sitemap
165 // @since 6
166
167 // we do not need to check if Yoast Sitemap feature is activated because the filter only fires if it is active
168 add_filter( 'wpseo_exclude_from_sitemap_by_post_ids', function ( $alreadyExcluded ) {
169 // as of 11.0.5 we add the page ID to the array of already excluded pages
170 // plus we exclude the 404 page in all languages
171 return array_merge( $alreadyExcluded, $this->get_all_page_ids() );
172 }, 999 );
173
174
175 // Jetpack sitemap
176 // @since 11.1.2
177
178 // we do not need to check if Jetpack Sitemap feature is activated because the filter only fires if it is active
179 add_filter( 'jetpack_sitemap_skip_post', function ( $skip, $post ) {
180 if ( in_array( intval( $post->ID ), $this->get_all_page_ids() ) ) {
181 $skip = true;
182 }
183 return $skip;
184 }, 999, 2 );
185
186 }
187
188 }
189
190
191 if ( class_exists( 'PP_404Page_Admin' ) ) {
192
193 // load classes only if in admin
194 // @since 10
195 // using class_exists( 'PP_404Page_Admin' ) instead of is_admin() as of v 10.3 for compatibilty with iThemes Sync
196
197 //$this->admin = new PP_404Page_Admin( $this, $this->settings );
198 //$this->blockeditor = new PP_404Page_BlockEditor( $this );
199 //$this->classiceditor = new PP_404Page_ClassicEditor( $this );
200
201 // Remove 404 page from post list if activated
202 // not moved to PP_404Page_Admin because we also need exclude_404page() in frontend
203 if ( $this->settings()->get( 'hide' ) and $this->settings()->get( 'page_id' ) > 0 ) {
204 add_action( 'pre_get_posts' ,array ( $this, 'exclude_404page' ) );
205 }
206
207 }
208
209 }
210
211
212 /**
213 * init filters
214 */
215 function set_mode() {
216
217 $this->settings()->set_method();
218
219 if ( defined( 'CUSTOMIZR_VER' ) ) {
220
221 // Customizr Compatibility Mode
222
223 // @since 3.1
224 add_filter( 'body_class', array( $this, 'add_404_body_class_customizr_mode' ) );
225
226 add_filter( 'tc_404_header_content', array( $this, 'show404title_customizr_mode' ), 999 );
227 add_filter( 'tc_404_content', array( $this, 'show404_customizr_mode' ), 999 );
228 add_filter( 'tc_404_selectors', array( $this, 'show404articleselectors_customizr_mode' ), 999 );
229
230 // send http 410 instead of http 404 if requested resource is in trash
231 // @since 3.2
232 // or always send http 410
233 // @since 11.3.0
234 if ( $this->settings()->get( 'http410_if_trashed' ) || $this->settings()->get( 'http410_always' ) ) {
235
236 add_action( 'template_redirect', array( $this, 'maybe_send_410' ) );
237
238 }
239
240 } elseif ( $this->settings()->get( 'method' ) != 'STD' ) {
241
242 // Compatibility Mode
243 // 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
244 // as of v 11.4.3 we also pass the query
245 add_filter( 'the_posts', array( $this, 'show404_compatiblity_mode' ), 999, 2 );
246
247 // 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
248 add_filter( 'dwqa_prepare_answers', array( $this, 'remove_show404_compatiblity_mode' ), 999 );
249
250 } else {
251
252 // Standard Mode
253 add_filter( '404_template', array( $this, 'show404_standard_mode' ), 999 );
254
255 if ( $this->settings()->get( 'fire_error' ) ) {
256
257 add_action( 'template_redirect', array( $this, 'do_404_header_standard_mode' ) );
258
259 }
260
261 // send http 410 instead of http 404 if requested resource is in trash
262 // @since 3.2
263 // or always send http 410
264 // @since 11.3.0
265 if ( $this->settings()->get( 'http410_if_trashed' ) || $this->settings()->get( 'http410_always' ) ) {
266
267 add_action( 'template_redirect', array( $this, 'maybe_send_410' ) );
268
269 }
270
271 }
272
273 }
274
275
276 /**
277 * show 404 page
278 * Standard Mode
279 */
280 function show404_standard_mode( $template ) {
281
282 global $wp_query;
283
284 // @since 4
285 // fix for an ugly bbPress problem
286 // see https://wordpress.org/support/topic/not-fully-bbpress-compatible/
287 // see https://bbpress.trac.wordpress.org/ticket/3161
288 // if a bbPress member page is shown and the member has no topics created yet the 404_template filter hook fires
289 // this is a bbPress problem but it has not been fixed since 6 months
290 // so let's bypass the problem
291 if ( function_exists( 'bbp_is_single_user' ) ) {
292
293 if ( bbp_is_single_user() ) {
294
295 return $template;
296
297 }
298
299 }
300 // that's it
301
302 // save URL that caused the 404 - since 11.4.0
303 $this->set_404_url();
304
305 if ( ! $this->is_native() ) {
306
307 $this->disable_caching();
308
309 $wp_query = null;
310 $wp_query = new WP_Query();
311 $wp_query->query( 'page_id=' . $this->get_page_id() );
312
313
314 $wp_query->the_post();
315 $template = get_page_template();
316 rewind_posts();
317 add_filter( 'body_class', array( $this, 'add_404_body_class' ) );
318 }
319 $this->maybe_force_404();
320 $this->do_404page_action();
321 return $template;
322
323 }
324
325
326 /**
327 * show 404 page
328 * Compatibility Mode
329 */
330 function show404_compatiblity_mode( $posts, $query ) {
331
332 global $wp_query;
333
334 // remove the filter so we handle only the first query - no custom queries
335 // moved in 11.4.3 due to problems with WP 6.1
336 // remove_filter( 'the_posts', array( $this, 'show404_compatiblity_mode' ), 999 );
337
338 // @since 4
339 // fix for an ugly bbPress problem
340 // see show404_standard_mode()
341 if ( function_exists( 'bbp_is_single_user' ) ) {
342
343 if ( bbp_is_single_user() ) {
344
345 return $posts;
346
347 }
348
349 }
350 // that's it
351
352 $pageid = $this->get_page_id();
353 if ( ! $this->is_native() ) {
354
355 // as of v 10 we also check if $wp_query->query[error] == 404
356 // this is necessary to bypass a WordPress bug
357 // if permalink setting is something like e.g. /blog/%postname%/ the $posts is not empty
358 // bug reported https://core.trac.wordpress.org/ticket/46000
359
360 // as of v 11.0.3 we also check for REST_REQUEST to not create a 404 page in case of REST API call
361 // as of v 11.2.4 we also check for DOING_CRON
362
363 // is_main_query() is true in multiple cases
364 // as of v 11.4.3 we check the query against the main query
365
366 if ( ( empty( $posts ) || ( isset( $wp_query->query['error'] ) && $wp_query->query['error'] == 404 ) ) && $wp_query == $query && is_main_query() && !is_robots() && !is_home() && !is_feed() && !is_search() && !is_archive() && ( !defined('DOING_AJAX') || !DOING_AJAX ) && ( !defined('DOING_CRON') || !DOING_CRON ) && ( !defined('REST_REQUEST') || !REST_REQUEST ) ) {
367
368 // save URL that caused the 404 - since 11.4.0
369 $this->set_404_url();
370
371 // 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
372 $this->postid = $pageid;
373
374 // as of v 2.4 we use the the_posts filter instead of posts_results
375 // therefore we have to reset $wp_query
376 // resetting $wp_query also forces us to remove the pre_get_posts action plus the get_pages filter
377
378 remove_action( 'pre_get_posts', array ( $this, 'exclude_404page' ) );
379 remove_filter( 'get_pages', array ( $this, 'remove_404page_from_array' ), 10, 2 );
380
381 // moved here in 11.4.3
382 remove_filter( 'the_posts', array( $this, 'show404_compatiblity_mode' ), 999 );
383
384 $this->disable_caching();
385
386 $wp_query = null;
387 $wp_query = new WP_Query();
388
389 // @since 8
390 // added suppress_filters for compatibilty with current WPML version
391 $wp_query->query( array( 'page_id' => $pageid, 'suppress_filters' => true ) );
392
393 $wp_query->the_post();
394 $this->template = get_page_template();
395 $posts = $wp_query->posts;
396 $wp_query->rewind_posts();
397
398 add_action( 'wp', array( $this, 'do_404_header' ) );
399 add_filter( 'body_class', array( $this, 'add_404_body_class' ) );
400 add_filter( 'template_include', array( $this, 'change_404_template' ), 999 );
401
402
403 $this->maybe_force_404();
404 $this->do_404page_action();
405
406 } elseif ( 1 == count( $posts ) && 'page' == $posts[0]->post_type ) {
407
408 // Do a 404 if the 404 page is opened directly
409 if ( $this->settings()->get( 'fire_error' ) ) {
410
411 // save URL that caused the 404 - since 11.4.0
412 $this->set_404_url();
413
414 $curpageid = $posts[0]->ID;
415
416 if ( defined( 'ICL_SITEPRESS_VERSION' ) ) {
417
418 // WPML is active - get the post ID of the default language
419 global $sitepress;
420 $curpageid = apply_filters( 'wpml_object_id', $curpageid, 'page', $sitepress->get_default_language() );
421 $pageid = apply_filters( 'wpml_object_id', $pageid, 'page', $sitepress->get_default_language() );
422
423 } elseif ( function_exists( 'pll_get_post' ) ) {
424
425 // was defined( 'POLYLANG_VERSION' ) before version 11.2.3
426
427 // Polylang is active - get the post ID of the default language
428 $curpageid = pll_get_post( $curpageid, pll_default_language() );
429 $pageid = pll_get_post( $pageid, pll_default_language() );
430
431 }
432
433 if ( $pageid == $curpageid ) {
434 add_action( 'wp', array( $this, 'do_404_header' ) );
435 add_filter( 'body_class', array( $this, 'add_404_body_class' ) );
436 $this->maybe_force_404();
437 $this->do_404page_action();
438 }
439 }
440
441 }
442 } else {
443 $this->maybe_force_404();
444 $this->do_404page_action();
445 }
446 return $posts;
447 }
448
449 /**
450 * disable caching for known caching plugins
451 *
452 * @since 11.2.0
453 */
454 function disable_caching() {
455
456 // WP Super Cache
457 if ( defined( 'WPCACHEHOME' ) ) {
458
459 global $cache_enabled;
460
461 // is caching active?
462 if ( $cache_enabled ) {
463
464 define( 'DONOTCACHEPAGE', true );
465
466 }
467
468 }
469
470
471 // W3 Total Cache
472 if ( defined( 'W3TC' ) ) {
473
474 if ( class_exists( 'W3TC\Dispatcher' ) ) {
475
476 // is caching active?
477 if ( W3TC\Dispatcher::config()->get_boolean( 'pgcache.enabled' ) ) {
478
479 define( 'DONOTCACHEPAGE', true );
480
481 }
482
483 }
484
485 }
486
487 }
488
489
490 /**
491 * for DW Question & Answer plugin
492 * this function is called by the dwqa_prepare_answers filter
493 */
494 function remove_show404_compatiblity_mode( $args ) {
495 remove_filter( 'the_posts', array( $this, 'show404_compatiblity_mode' ), 999 );
496 return $args;
497 }
498
499
500 /**
501 * this function overrides the page template in compatibilty mode
502 */
503 function change_404_template( $template ) {
504
505 // 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
506 $new_template = locate_template( array( $this->template ) );
507 if ( '' != $new_template ) {
508 return $new_template ;
509 }
510 return $template;
511 }
512
513
514 /**
515 * send 404 HTTP header
516 * Standard Mode
517 */
518 function do_404_header_standard_mode() {
519 if ( is_page() && get_the_ID() == $this->settings()->get( 'page_id' ) && !is_404() ) {
520 // save URL that caused the 404 - since 11.4.0
521 $this->set_404_url();
522
523 status_header( 404 );
524 nocache_headers();
525 $this->maybe_force_404();
526 // Add 404 body class - since 11.4.2
527 add_filter( 'body_class', array( $this, 'add_404_body_class' ) );
528 $this->do_404page_action();
529 }
530 }
531
532
533 /**
534 * send 404 HTTP header
535 * Compatibility Mode
536 */
537 function do_404_header() {
538 // remove the action so we handle only the first query - no custom queries
539 remove_action( 'wp', array( $this, 'do_404_header' ) );
540
541 // send http 410 instead of http 404 if requested resource is in trash
542 // @since 3.2
543
544 if ( $this->settings()->get( 'http410_if_trashed' ) && $this->is_url_in_trash( $this->get_404_url() ) ) {
545
546 status_header( 410 );
547
548 } else {
549
550 status_header( 404 );
551
552 }
553 nocache_headers();
554 }
555
556
557 /**
558 * add body classes
559 */
560 function add_404_body_class( $classes ) {
561
562 // as of v 3.1 we first check if the class error404 already exists
563 if ( ! in_array( 'error404', $classes ) ) {
564
565 $classes[] = 'error404';
566
567 }
568
569 // debug class
570 // @since 3.1
571 $debug_class = 'pp404-';
572 if ( $this->is_native() ) {
573 $debug_class .= 'native';
574 } elseif ( defined( 'CUSTOMIZR_VER' ) ) {
575 $debug_class .= 'customizr';
576 } elseif ( defined( 'ICL_SITEPRESS_VERSION' ) ) {
577 $debug_class .= 'wpml';
578 } elseif ( $this->settings()->get( 'method' ) != 'STD' ) {
579 $debug_class .= 'cmp';
580 } else {
581 $debug_class .= 'std';
582 }
583 $classes[] = $debug_class;
584
585 return $classes;
586 }
587
588
589 /**
590 * add body classes customizr mode
591 * @since 3.1
592 */
593 function add_404_body_class_customizr_mode( $classes ) {
594
595 if ( is_404() ) {
596
597 // save URL that caused the 404 - since 11.4.0
598 $this->set_404_url();
599
600 $classes = $this->add_404_body_class( $classes );
601
602 }
603
604 return $classes;
605 }
606
607
608 /**
609 * show title
610 * Customizr Compatibility Mode
611 */
612 function show404title_customizr_mode( $title ) {
613 if ( ! $this->is_native() ) {
614 return '<h1 class="entry-title">' . get_the_title( $this->get_page_id() ) . '</h1>';
615 } else {
616 return $title;
617 }
618 }
619
620
621 /**
622 * show content
623 * Customizr Compatibility Mode
624 */
625 function show404_customizr_mode( $content ) {
626 if ( ! $this->is_native() ) {
627 return '<div class="entry-content">' . apply_filters( 'the_content', get_post_field( 'post_content', $this->get_page_id() ) ) . '</div>';
628 } else {
629 return $content;
630 }
631 $this->do_404page_action();
632 }
633
634
635 /**
636 * change article selectors
637 * Customizr Compatibility Mode
638 */
639 function show404articleselectors_customizr_mode( $selectors ) {
640 if ( ! $this->is_native() ) {
641 return 'id="post-' . $this->get_page_id() . '" class="' . join( ' ', get_post_class( 'row-fluid', $this->get_page_id() ) ) . '"';
642 } else {
643 return $selectors;
644 }
645 }
646
647
648 /**
649 * do we have to force a 404 in wp_head?
650 */
651 function maybe_force_404() {
652 if ( $this->settings()->get( 'force_error' ) ) {
653 add_action( 'wp_head', array( $this, 'force_404_start' ), 9.9 );
654 add_action( 'wp_head', array( $this, 'force_404_end' ), 99 );
655 }
656 }
657
658
659 /**
660 * Force 404 in wp_head start
661 * potentially dangerous!
662 */
663 function force_404_start() {
664 global $wp_query;
665 $wp_query->is_404 = true;
666 }
667
668
669 /**
670 * Force 404 in wp_head end
671 * potentially dangerous!
672 */
673 function force_404_end() {
674 global $wp_query;
675 $wp_query->is_404 = false;
676 }
677
678
679 /**
680 * disable URL autocorrect guessing
681 */
682 function no_url_guessing( $redirect_url ) {
683 if ( is_404() && !isset($_GET['p']) ) {
684 $redirect_url = false;
685 }
686 return $redirect_url;
687 }
688
689
690 /**
691 * send http 410 instead of http 404 in case the requested URL can be found in trash
692 * @since 3.2
693 * or always send http 410
694 * @since 11.3.0
695
696 */
697 function maybe_send_410() {
698
699 // we don't do anything if there is no 404
700 if ( is_404() ) {
701
702 if ( $this->settings()->get( 'http410_always' ) || ( $this->settings()->get( 'http410_if_trashed' ) && $this->is_url_in_trash( $this->get_404_url() ) ) ) {
703
704 status_header( 410 );
705
706 }
707 }
708
709 }
710
711
712 /**
713 * hide the 404 page from the list of pages
714 */
715
716 function exclude_404page( $query ) {
717
718 $pageid = $this->get_page_id();
719
720 if ( $pageid > 0 ) {
721
722 global $pagenow;
723
724 $post_type = $query->get( 'post_type' );
725
726 // as of v 2.3 we check the post_type on front end
727 // as of v 2.5 we also hide the page from search results on front end
728 if( ( is_admin() && ( 'edit.php' == $pagenow && !current_user_can( 'create_users' ) ) ) || ( ! is_admin() && ( is_search() || ( !empty( $post_type) && ( ('page' === $post_type || 'any' === $post_type) || ( is_array( $post_type ) && in_array( 'page', $post_type ) ) ) ) ) ) ) {
729
730 // as of v 2.4 we hide all translations in admin for WPML
731 // as of v 2.5 we hide all translations from search results on front end for WPML
732 if ( is_admin() || ( ! is_admin() && is_search() ) ) {
733
734 $pageids = $this->get_all_page_ids();
735
736 } else {
737
738 $pageids = array( $pageid );
739
740 }
741
742 // as of v 2.3 we add the ID of the 404 page to post__not_in
743 // using just $query->set() overrides existing settings but not adds a new setting
744 $query->set( 'post__not_in', array_merge( (array)$query->get( 'post__not_in', array() ), $pageids ) );
745
746 }
747
748 }
749
750 }
751
752
753 /**
754 * remove the 404 page from get_pages result array
755 */
756 function remove_404page_from_array( $pages, $r ) {
757
758 $pageid = $this->get_page_id();
759
760 if ( $pageid > 0 ) {
761
762 for ( $i = 0; $i < sizeof( $pages ); $i++ ) {
763
764 if ( $pages[$i]->ID == $pageid ) {
765
766 unset( $pages[$i] );
767 break;
768
769 }
770
771 }
772
773 }
774
775 return array_values( $pages );
776
777 }
778
779
780 /**
781 * check if the requested url is found in trash
782 * @since 3.2
783 * based on WP core function url_to_postid()
784 */
785 function is_url_in_trash( $url ) {
786
787 global $wp_rewrite;
788 global $wp;
789
790 // First, check to see if there is a 'p=N' or 'page_id=N' to match against
791 if ( preg_match( '#[?&](p|page_id|attachment_id)=(\d+)#', $url, $values ) ) {
792
793 $id = absint( $values[2] );
794
795 if ( $id ) {
796
797 if ( 'trash' == get_post_status( $id ) ) {
798
799 return true;
800
801 } else {
802
803 return false;
804
805 }
806
807 }
808
809 }
810
811 // Check to see if we are using rewrite rules
812 $rewrite = $wp_rewrite->wp_rewrite_rules();
813
814 // Not using rewrite rules, and 'p=N' and 'page_id=N' methods failed, so we're out of options
815 if ( empty( $rewrite ) ) {
816
817 return false;
818
819 }
820
821 // Get rid of the #anchor
822 $url_split = explode('#', $url);
823 $url = $url_split[0];
824
825 // Get rid of URL ?query=string
826 $url_split = explode('?', $url);
827 $url = $url_split[0];
828
829 // Add 'www.' if it is absent and should be there
830 if ( false !== strpos( home_url(), '://www.' ) && false === strpos( $url, '://www.' ) ) {
831
832 $url = str_replace('://', '://www.', $url);
833
834 }
835
836 // Strip 'www.' if it is present and shouldn't be
837 if ( false === strpos( home_url(), '://www.' ) ) {
838
839 $url = str_replace('://www.', '://', $url);
840
841 }
842
843 // Strip 'index.php/' if we're not using path info permalinks
844 if ( !$wp_rewrite->using_index_permalinks() ) {
845
846 $url = str_replace( $wp_rewrite->index . '/', '', $url );
847
848 }
849
850
851 if ( false !== strpos( trailingslashit( $url ), home_url( '/' ) ) ) {
852
853 // Chop off http://domain.com/[path]
854 $url = str_replace(home_url(), '', $url);
855
856 } else {
857
858 // Chop off /path/to/blog
859 $home_path = parse_url( home_url( '/' ) );
860 $home_path = isset( $home_path['path'] ) ? $home_path['path'] : '' ;
861 $url = preg_replace( sprintf( '#^%s#', preg_quote( $home_path ) ), '', trailingslashit( $url ) );
862
863 }
864
865 // Trim leading and lagging slashes
866 $url = trim($url, '/');
867
868 $request = $url;
869 $post_type_query_vars = array();
870
871 foreach ( get_post_types( array() , 'objects' ) as $post_type => $t ) {
872
873 if ( ! empty( $t->query_var ) ) {
874
875 $post_type_query_vars[ $t->query_var ] = $post_type;
876
877 }
878 }
879
880 // Look for matches.
881 $request_match = $request;
882 foreach ( (array)$rewrite as $match => $query) {
883
884 // If the requesting file is the anchor of the match, prepend it
885 // to the path info.
886 if ( !empty( $url ) && ( $url != $request ) && ( strpos( $match, $url ) === 0 ) ) {
887
888 $request_match = $url . '/' . $request;
889
890 }
891
892 if ( preg_match( "#^$match#", $request_match, $matches ) ) {
893
894 if ( $wp_rewrite->use_verbose_page_rules && preg_match( '/pagename=\$matches\[([0-9]+)\]/', $query, $varmatch ) ) {
895
896 // This is a verbose page match, let's check to be sure about it.
897 if ( ! get_page_by_path( $matches[ $varmatch[1] ] ) ) {
898
899 continue;
900
901 }
902 }
903
904 // Got a match.
905
906 // Trim the query of everything up to the '?'.
907 $query = preg_replace( "!^.+\?!", '', $query );
908
909 // Substitute the substring matches into the query.
910 $query = addslashes( WP_MatchesMapRegex::apply( $query, $matches ) );
911
912 // Filter out non-public query vars
913 parse_str( $query, $query_vars );
914 $query = array();
915
916 foreach ( (array) $query_vars as $key => $value ) {
917
918 if ( in_array( $key, $wp->public_query_vars ) ) {
919
920 $query[$key] = $value;
921
922 if ( isset( $post_type_query_vars[$key] ) ) {
923
924 $query['post_type'] = $post_type_query_vars[$key];
925 $query['name'] = $value;
926
927 }
928
929 }
930
931 }
932
933 // Magic
934 if ( isset( $query['pagename'] ) ) {
935
936 $query['pagename'] .= '__trashed' ;
937
938 }
939
940 if ( isset( $query['name'] ) ) {
941
942 $query['name'] .= '__trashed' ;
943
944 }
945
946 $query['post_status'] = array( 'trash' );
947
948 // Resolve conflicts between posts with numeric slugs and date archive queries.
949 $query = wp_resolve_numeric_slug_conflicts( $query );
950
951 // Do the query
952 $query = new WP_Query( $query );
953
954 if ( $query->found_posts == 1 ) {
955
956 return true;
957
958 } else {
959
960 return false;
961
962 }
963
964 }
965
966 }
967
968 return false;
969
970 }
971
972
973 /**
974 * get the id of the 404 page in the current language if WPML or Polylang is active
975 * public since v 11.1.0 to use it in pp_404_get_page_id()
976 */
977 public function get_page_id() {
978
979 $pageid = $this->settings()->get( 'page_id' );
980
981 if ( $pageid > 0 ) {
982
983 if ( defined( 'ICL_SITEPRESS_VERSION' ) ) {
984
985 // WPML is active
986 $pageid = apply_filters( 'wpml_object_id', $pageid, 'page', true );
987
988 } elseif ( function_exists( 'pll_get_post' ) ) {
989
990 // was defined( 'POLYLANG_VERSION' ) before version 11.2.3
991
992 // Polylang is active
993 $translatedpageid = pll_get_post( $pageid );
994 if ( !empty( $translatedpageid ) && 'publish' == get_post_status( $translatedpageid ) ) {
995 $pageid = $translatedpageid;
996 }
997
998 }
999
1000 }
1001
1002 return $pageid;
1003
1004 }
1005
1006
1007 /**
1008 * get 404 pages in all available languages
1009 * if WPML is active this function returns an array of all page ids in all available languages
1010 * otherwise it returns the page id as array
1011 * introduced in v 2.4
1012 * public since v9 to access it from other classes
1013 */
1014 public function get_all_page_ids() {
1015
1016 if ( defined( 'ICL_SITEPRESS_VERSION' ) ) {
1017
1018 // WPML is active
1019 // get an array for all translations
1020 $pageid = $this->settings()->get( 'page_id' );
1021 $pages = array( $pageid );
1022
1023 if ( $pageid > 0 ) {
1024
1025 $languages = apply_filters( 'wpml_active_languages', NULL );
1026
1027 if ( !empty( $languages ) ) {
1028
1029 foreach( $languages as $l ) {
1030
1031 $p = apply_filters( 'wpml_object_id', $pageid, 'page', false, $l['language_code'] );
1032
1033 if ( $p ) {
1034
1035 $pages[] = $p;
1036
1037 }
1038
1039 }
1040
1041 }
1042
1043 }
1044
1045 $pageids = array_unique( $pages, SORT_NUMERIC );
1046
1047 } else {
1048
1049 $pageids = array( $this->settings()->get( 'page_id' ) );
1050
1051 }
1052
1053 return $pageids;
1054
1055 }
1056
1057
1058 /**
1059 * fire 404page_after_404 hook to make plugin expandable
1060 */
1061 function do_404page_action() {
1062
1063 do_action( '404page_after_404' );
1064
1065 }
1066
1067
1068 /**
1069 * uninstall plugin
1070 */
1071 function uninstall() {
1072
1073 if( is_multisite() ) {
1074
1075 $this->uninstall_network();
1076
1077 } else {
1078
1079 $this->uninstall_single();
1080
1081 }
1082
1083 }
1084
1085
1086 /**
1087 * uninstall network wide
1088 */
1089 function uninstall_network() {
1090
1091 global $wpdb;
1092 $activeblog = $wpdb->blogid;
1093 $blogids = $wpdb->get_col( esc_sql( 'SELECT blog_id FROM ' . $wpdb->blogs ) );
1094
1095 foreach ( $blogids as $blogid ) {
1096
1097 switch_to_blog( $blogid );
1098 $this->uninstall_single();
1099
1100 }
1101
1102 switch_to_blog( $activeblog );
1103
1104 }
1105
1106
1107 /**
1108 * uninstall for a single blog
1109 */
1110 function uninstall_single() {
1111
1112 $this->data_remove();
1113 $this->settings()->remove();
1114
1115 }
1116
1117
1118 /**
1119 * functions for theme usage
1120 */
1121
1122 // check if there's a custom 404 page set
1123 function pp_404_is_active() {
1124
1125 return ( $this->settings()->get( 'page_id' ) > 0 );
1126
1127 }
1128
1129 // activate the native theme support
1130 function pp_404_set_native_support() {
1131
1132 $this->set_native();
1133
1134 }
1135
1136 // get the title - native theme support
1137 function pp_404_get_the_title() {
1138
1139 $title = '';
1140
1141 if ( $this->settings()->get( 'page_id' ) > 0 && $this->is_native() ) {
1142
1143 $title = get_the_title( $this->get_page_id() );
1144
1145 }
1146
1147 return $title;
1148
1149 }
1150
1151 // print title - native theme support
1152 function pp_404_the_title() {
1153
1154 echo esc_html( $this->pp_404_get_the_title() );
1155
1156 }
1157
1158 // get the content - native theme support
1159 function pp_404_get_the_content() {
1160
1161 $content = '';
1162
1163 if ( $this->settings()->get( 'page_id' ) > 0 && $this->is_native() ) {
1164
1165 $content = apply_filters( 'the_content', get_post_field( 'post_content', $this->get_page_id() ) );
1166
1167 }
1168
1169 return $content;
1170
1171 }
1172
1173 // print content - native theme support
1174 function pp_404_the_content() {
1175
1176 echo esc_html( $this->pp_404_get_the_content() );
1177
1178 }
1179
1180
1181 /**
1182 * set native mode
1183 *
1184 * @since 11.0.0 - was part of previous settings class
1185 * @access public
1186 */
1187 public function set_native() {
1188
1189 $this->native = true;
1190
1191 }
1192
1193
1194 /**
1195 * is native mode ative
1196 *
1197 * @since 11.0.0 - was part of previous settings class
1198 * @access public
1199 */
1200 public function is_native() {
1201
1202 return ( true === $this->native );
1203
1204 }
1205
1206
1207 /**
1208 * get settings class
1209 *
1210 * @since 11.3.0
1211 * @access public
1212 * @return object
1213 */
1214 public function admin() {
1215
1216 return $this->admin;
1217 }
1218
1219
1220
1221 /**
1222 * save URL that caused the 404 error
1223 *
1224 * @since 11.4.0
1225 * @access private
1226 */
1227 private function set_404_url() {
1228
1229 $url = rawurldecode( ( is_ssl() ? 'https://' : 'http://' ) . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] );
1230
1231 $this->error_url = parse_url( $url );
1232 $this->error_url['full'] = $url;
1233
1234 return true;
1235
1236 }
1237
1238
1239 /**
1240 * get URL that caused the 404 error
1241 *
1242 * @since 11.4.0
1243 * @access public
1244 * @param string $what - array index to get
1245 * @return string
1246 */
1247 public function get_404_url( $what = 'full' ) {
1248
1249 if ( $this->error_url && is_array( $this->error_url ) && isset( $this->error_url[$what] ) && ! empty( $this->error_url[$what] ) ) {
1250
1251 return $this->error_url[$what];
1252
1253 }
1254
1255 }
1256
1257 }
1258
1259 }