├── languages
├── 404-to-301.mo
├── index.php
├── 404-to-301-pt_PT.mo
├── 404-to-301.pot
└── 404-to-301-pt_PT.po
├── index.php
├── includes
├── index.php
├── admin
│ ├── index.php
│ ├── views
│ │ ├── index.php
│ │ ├── admin.php
│ │ ├── custom-redirect.php
│ │ └── settings.php
│ ├── class-jj4t3-admin.php
│ └── class-jj4t3-log-listing.php
├── public
│ ├── index.php
│ ├── class-jj4t3-404-logging.php
│ ├── class-jj4t3-404-data.php
│ ├── class-jj4t3-404-email.php
│ └── class-jj4t3-404-actions.php
├── functions
│ ├── index.php
│ └── jj4t3-general-functions.php
├── class-jj4t3-i18n.php
├── class-jj-404-to-301.php
└── class-jj4t3-activator-deactivator-uninstaller.php
├── .svn-assets
├── screenshot-1.png
├── screenshot-2.png
└── screenshot-3.png
├── wpml-config.xml
├── .github
└── workflows
│ └── wpcs.yml
├── composer.json
├── assets
└── src
│ ├── css
│ └── admin.scss
│ └── js
│ └── admin.js
├── uninstall.php
├── .gitignore
├── package.json
├── webpack.config.js
├── README.md
├── 404-to-301.php
├── Gruntfile.js
├── readme.txt
└── LICENSE
/languages/404-to-301.mo:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/index.php:
--------------------------------------------------------------------------------
1 |
2 |
" . __( 'Bummer! You have one more 404', '404-to-301' ) . "
"; 190 | $message .= '| ' . __( '404 Path', '404-to-301' ) . ' | '; 194 | $message .= '' . $this->error_data->url . ' | '; 195 | $message .= '
|---|---|
| ' . __( 'IP Address', '404-to-301' ) . ' | '; 199 | $message .= '' . $this->error_data->ip . ' | '; 200 | $message .= '
| ' . __( 'Time', '404-to-301' ) . ' | '; 204 | $message .= '' . $this->error_data->time . ' | '; 205 | $message .= '
| ' . __( 'Referral Page', '404-to-301' ) . ' | '; 209 | $message .= '' . $this->error_data->ref . ' | '; 210 | $message .= '
' . sprintf( __( 'Alert sent by the %s404 to 301%s plugin for WordPress.', '404-to-301' ), '', '' ) . '
'; 214 | 215 | /** 216 | * Filter to alter email content. 217 | * 218 | * @since 3.0.0 219 | */ 220 | $this->body = apply_filters( 'jj4t3_email_body', $message ); 221 | } 222 | 223 | } 224 | -------------------------------------------------------------------------------- /includes/admin/views/settings.php: -------------------------------------------------------------------------------- 1 | 6 | 7 | 99 | -------------------------------------------------------------------------------- /includes/functions/jj4t3-general-functions.php: -------------------------------------------------------------------------------- 1 | __( '301 Redirect (SEO)', '404-to-301' ), 284 | 302 => __( '302 Redirect', '404-to-301' ), 285 | 307 => __( '307 Redirect', '404-to-301' ), 286 | ); 287 | 288 | /** 289 | * Filter for allowed status codes. 290 | * 291 | * If you want to add additional HTTP status codes 292 | * for redirect, please use this filter and add to 293 | * the statuses array. 294 | * DO NOT remove default values (301, 302 and 307) from 295 | * the array. 296 | * 297 | * @since 3.0.0 298 | */ 299 | return (array) apply_filters( 'jj4t3_redirect_statuses', $statuses ); 300 | } 301 | 302 | /** 303 | * Available columns in error logs table. 304 | * 305 | * This columns are being used few times. Use this to avoid 306 | * unwanted names. 307 | * Registering filter - "jj4t3_redirect_statuses". 308 | * 309 | * @since 3.0.0 310 | * @access private 311 | * 312 | * @return array Allowed HTTP status codes. 313 | */ 314 | function jj4t3_log_columns() { 315 | 316 | $columns = array( 317 | 'date' => __( 'Date', '404-to-301' ), 318 | 'url' => __( '404 Path', '404-to-301' ), 319 | 'ref' => __( 'From', '404-to-301' ), 320 | 'ip' => __( 'IP Address', '404-to-301' ), 321 | 'ua' => __( 'User Agent', '404-to-301' ), 322 | 'redirect' => __( 'Redirect', '404-to-301' ), 323 | ); 324 | 325 | /** 326 | * Filter for available columns. 327 | * 328 | * These are the availble column names in 404 329 | * error logs. 330 | * Registering filter - "jj4t3_log_columns". 331 | * 332 | * @param array columns name and slug. 333 | * 334 | * @since 3.0.0 335 | */ 336 | return (array) apply_filters( 'jj4t3_log_columns', $columns ); 337 | } 338 | 339 | /** 340 | * Retrive value from $_REQUEST. 341 | * 342 | * Helper function to retrive data from $_REQUEST 343 | * We can use this function to get values from request 344 | * and get a default value if the current key does not exist 345 | * or empty. 346 | * Output will be trimmed. 347 | * 348 | * @param string $key Key to get from request. 349 | * @param mixed $default Default value. 350 | * 351 | * @since 3.0.0 352 | * @access public 353 | * 354 | * @return array|string 355 | */ 356 | function jj4t3_from_request( $key = '', $default = '' ) { 357 | 358 | // Return default value if key is not given. 359 | if ( empty( $key ) || ! is_string( $key ) ) { 360 | return $default; 361 | } 362 | 363 | // Return default value if key not set. 364 | if ( ! isset( $_REQUEST[ $key ] ) ) { // phpcs:ignore 365 | return $default; 366 | } 367 | 368 | // Trim output. 369 | if ( is_string( $_REQUEST[ $key ] ) ) { // phpcs:ignore 370 | return sanitize_text_field( $_REQUEST[ $key ] ); // phpcs:ignore 371 | } elseif ( is_array( $_REQUEST[ $key ] ) ) { // phpcs:ignore 372 | return array_map( 'sanitize_text_field', $_REQUEST[ $key ] ); // phpcs:ignore 373 | } 374 | 375 | return $default; 376 | } 377 | -------------------------------------------------------------------------------- /includes/public/class-jj4t3-404-actions.php: -------------------------------------------------------------------------------- 1 | 16 | * @license http://www.gnu.org/licenses/ GNU General Public License 17 | * @link https://duckdev.com/products/404-to-301/ 18 | */ 19 | class JJ4T3_404_Actions extends JJ4T3_404_Data { 20 | 21 | /** 22 | * Redirect url for current 404. 23 | * 24 | * @var string 25 | * @access public 26 | * 27 | */ 28 | public $redirect_url = ''; 29 | 30 | /** 31 | * Custom redirect url for current 404. 32 | * 33 | * @var string 34 | * @access public 35 | * 36 | */ 37 | public $custom_redirect_url = ''; 38 | 39 | /** 40 | * Redirect status code for currenr 404. 41 | * 42 | * @var string 43 | * @access public 44 | * @since 3.0.0 45 | */ 46 | public $redirect_type = 301; 47 | 48 | /** 49 | * Is redirect enabled for current 404. 50 | * 51 | * @var boolean 52 | * @access public 53 | * @since 3.0.0 54 | */ 55 | public $redirect_enabled = false; 56 | 57 | /** 58 | * Is logging enabled for current 404. 59 | * 60 | * @var boolean 61 | * @access public 62 | * @since 3.0.0 63 | */ 64 | public $log_enabled = false; 65 | 66 | /** 67 | * Is email alert enabled for current 404. 68 | * 69 | * @var boolean 70 | * @access public 71 | * @since 3.0.0 72 | */ 73 | public $alert_enabled = false; 74 | 75 | /** 76 | * Is common check passed for current 404. 77 | * 78 | * @var boolean 79 | * @access public 80 | * @since 3.0.0 81 | */ 82 | public $common_check_passed = false; 83 | 84 | /** 85 | * Initialize the class and parent class. 86 | * 87 | * @since 3.0.0 88 | * @access public 89 | */ 90 | public function __construct() { 91 | 92 | // Main filter that handles 404. 93 | add_action( 'template_redirect', array( $this, 'handle_404' ) ); 94 | add_filter( 'redirect_canonical', array( $this, 'url_guessing' ) ); 95 | } 96 | 97 | /** 98 | * Perform 404 actions. 99 | * 100 | * Perform required actions on each 404 pages being visited. 101 | * Log error details, Alert via email, Redirect. 102 | * 103 | * @since 3.0.0 104 | * @access public 105 | * 106 | * @return void 107 | */ 108 | public function handle_404() { 109 | 110 | // Only if we can. 111 | if ( ! is_404() || is_admin() ) { 112 | return; 113 | } 114 | 115 | // Let's try folks. 116 | try { 117 | 118 | // Initialize. 119 | $this->init(); 120 | 121 | // Set options for current 404. 122 | $this->set_options(); 123 | 124 | // Log error details to database. 125 | $this->log_error(); 126 | 127 | // Send email alert about the error. 128 | $this->email_alert(); 129 | 130 | // Redirect the user. 131 | $this->redirect(); 132 | 133 | } catch ( Exception $ex ) { 134 | // Who cares? 135 | } 136 | } 137 | 138 | /** 139 | * Send email about the error. 140 | * 141 | * @since 3.0.0 142 | * @access public 143 | * 144 | * @return void 145 | */ 146 | public function email_alert() { 147 | 148 | /** 149 | * Filter to completely disable email alerts. 150 | * 151 | * @since 3.0.0 152 | */ 153 | if ( ! apply_filters( 'jj4t3_can_email_alert', $this->alert_enabled ) ) { 154 | return; 155 | } 156 | 157 | 158 | // Email alert class. 159 | $email = new JJ4T3_404_Email( $this ); 160 | $email->send_email(); 161 | } 162 | 163 | /** 164 | * Log details of error to the database. 165 | * 166 | * @since 3.0.0 167 | * @access public 168 | * 169 | * @return void 170 | */ 171 | public function log_error() { 172 | 173 | // Only if we can. 174 | if ( ! $this->log_enabled ) { 175 | return; 176 | } 177 | 178 | // Error logging class. 179 | $logging = new JJ4T3_404_Logging( $this ); 180 | $logging->log_error(); 181 | } 182 | 183 | /** 184 | * Redirect 404 requests. 185 | * 186 | * If a 404 page is requested, take visitors to a proper existing page. 187 | * Registering new action hook "jj4t3_before_redirect". 188 | * 189 | * @since 3.0.0 190 | * @access private 191 | * 192 | * @global object $wpdb WordPress DB object. 193 | * 194 | * @return void 195 | */ 196 | public function redirect() { 197 | 198 | // Only if we can. 199 | if ( ! $this->redirect_enabled ) { 200 | return; 201 | } 202 | 203 | if ( ! empty( $this->redirect_url ) ) { 204 | 205 | /** 206 | * Action hook to perform before redirect. 207 | * 208 | * @since 3.0.0 209 | * 210 | * @param string $url Link to redirect. 211 | */ 212 | do_action( 'jj4t3_before_redirect', $this->redirect_url ); 213 | 214 | // Perform redirect using WordPres. 215 | wp_redirect( $this->redirect_url, $this->redirect_type ); 216 | // Exit, because WordPress will not exit automatically. 217 | exit; 218 | } 219 | } 220 | 221 | /** 222 | * Set custom redirect url if set 223 | * 224 | * If custom redirect url is set for give 404 path, 225 | * set that link. 226 | * Registering filter "jj4t3_custom_redirect_url". 227 | * 228 | * @global object $wpdb WP DB object 229 | * @since 2.2.0 230 | * @access private 231 | * 232 | * @return void 233 | */ 234 | private function set_options() { 235 | 236 | if ( empty( $this->url ) ) { 237 | return; 238 | } 239 | 240 | global $wpdb; 241 | // Make sure that the errors are hidden. 242 | $wpdb->hide_errors(); 243 | 244 | // Get custom redirect if set. 245 | $result = $result = $wpdb->get_row( "SELECT redirect, options FROM " . JJ4T3_TABLE . " WHERE url = '" . $this->url . "' AND redirect IS NOT NULL LIMIT 0,1", "OBJECT" ); 246 | 247 | $options = empty( $result->options ) ? array() : maybe_unserialize( $result->options ); 248 | 249 | // Set all properties. 250 | $this->set_common_check(); 251 | $this->set_redirect_url( $result ); 252 | $this->set_redirect_type( $options ); 253 | $this->set_redirect_status( $options ); 254 | $this->set_logging_status( $options ); 255 | $this->set_alert_status( $options ); 256 | } 257 | 258 | /** 259 | * Get url to redirect to. 260 | * 261 | * This function is used to get the url 262 | * for redirecting to. 263 | * If a custom redirect is set through admin dashboard or even through 264 | * the filter "jj4t3_custom_redirect_url" for the current 404 page, it will be prioratised. 265 | * Otherwise global redirect link. 266 | * Registering filter - jj4t3_redirect_url. 267 | * 268 | * @param object $options Current 404 options. 269 | * 270 | * @since 3.0.0 271 | * @access private 272 | * 273 | * @return void 274 | */ 275 | private function set_redirect_url( $options ) { 276 | 277 | $url = false; 278 | 279 | $custom_redirect = empty( $options->redirect ) ? '' : $options->redirect; 280 | 281 | /** 282 | * Filter for modify/set current 404's custom redirect. 283 | * 284 | * Using this filter you can modify or set custom redirect 285 | * for any 404 path. 286 | * @note : If you want to remove custom redirect for a path, you can 287 | * use this filter and return an empty/false value. 288 | * If you have set a value here, this will get priority over global redirect. 289 | * 290 | * @since 3.0.0 291 | */ 292 | $custom_redirect = apply_filters( 'jj4t3_custom_redirect_url', $custom_redirect, $this->url ); 293 | 294 | if ( ! empty( $custom_redirect ) ) { 295 | $url = esc_url( $custom_redirect ); 296 | $this->custom_redirect_url = esc_url( $custom_redirect ); 297 | } else { 298 | // Get redirect to. 299 | $to = jj4t3_get_option( 'redirect_to' ); 300 | if ( 'page' === $to ) { 301 | // If an existing page is selected, get permalink. 302 | $url = get_permalink( jj4t3_get_option( 'redirect_page' ) ); 303 | } elseif ( 'link' === $to ) { 304 | // If a link. 305 | $url = jj4t3_get_option( 'redirect_link' ); 306 | } 307 | } 308 | 309 | /** 310 | * Filter hook to change redirect url. 311 | * 312 | * To alter redirect link. Return full absolute 313 | * path to redirect. 314 | * 315 | * @since 3.0.0 316 | */ 317 | $this->redirect_url = esc_url( apply_filters( 'jj4t3_redirect_url', $url ) ); 318 | } 319 | 320 | /** 321 | * Set redirect type for the current 404. 322 | * 323 | * This function is used to set the redirect type code 324 | * for the current 404. 325 | * Custom config for the 404 is considered first. 326 | * 327 | * @param object $options Current 404 options. 328 | * 329 | * @since 3.0.0 330 | * @access public 331 | * 332 | * @return void 333 | */ 334 | private function set_redirect_type( $options ) { 335 | 336 | if ( isset( $options['type'] ) && is_numeric( $options['type'] ) ) { 337 | $this->redirect_type = intval( $options[ 'type' ] ); 338 | } else { 339 | $this->redirect_type = jj4t3_redirect_type(); 340 | } 341 | } 342 | 343 | /** 344 | * Set if we can log 404 errors to database. 345 | * 346 | * This function is used to check and verify 347 | * if the error logging is set to enabled. 348 | * Checking custom config for the 404 first. 349 | * 350 | * @param object $options Current 404 options. 351 | * 352 | * @since 3.0.0 353 | * @access public 354 | * 355 | * @return void 356 | */ 357 | private function set_logging_status( $options ) { 358 | 359 | if ( isset( $options['log'] ) && in_array( $options['log'], array( 0, 1 ) ) ) { 360 | $enabled = boolval( $options[ 'log' ] ); 361 | } else { 362 | $enabled = jj4t3_log_enabled(); 363 | } 364 | 365 | if ( $enabled && jj4t3_is_human() && $this->common_check_passed ) { 366 | $this->log_enabled = true; 367 | } 368 | } 369 | 370 | /** 371 | * Set if we can email notify on errors. 372 | * 373 | * This function is used to check and verify 374 | * if the email notification is enabled. 375 | * Checking custom config for the 404 first. 376 | * 377 | * @param object $options Current 404 options. 378 | * 379 | * @since 3.0.0 380 | * @access public 381 | * 382 | * @return void 383 | */ 384 | private function set_alert_status( $options ) { 385 | 386 | if ( isset( $options['alert'] ) && in_array( $options['alert'], array( 0, 1 ) ) ) { 387 | $enabled = boolval( $options[ 'alert' ] ); 388 | } else { 389 | $enabled = jj4t3_email_notify_enabled(); 390 | } 391 | 392 | if ( $enabled && jj4t3_is_human() && $this->common_check_passed ) { 393 | $this->alert_enabled = true; 394 | } 395 | } 396 | 397 | /** 398 | * Set if we can perform redirect related actions. 399 | * 400 | * Verify that the common check passed. 401 | * Verify that redirect is enabled by user (custom if any). 402 | * 403 | * @param object $options Current 404 options. 404 | * 405 | * @since 2.2.0 406 | * @access public 407 | * 408 | * @return void 409 | */ 410 | private function set_redirect_status( $options ) { 411 | 412 | if ( isset( $options['redirect'] ) && in_array( $options['redirect'], array( 0, 1 ) ) ) { 413 | $enabled = boolval( $options['redirect'] ); 414 | } else { 415 | $enabled = jj4t3_redirect_enabled(); 416 | } 417 | 418 | if ( $enabled && $this->common_check_passed ) { 419 | $this->redirect_enabled = true; 420 | } 421 | } 422 | 423 | /** 424 | * Set if the common checks are passed. 425 | * 426 | * Verify that the current page is not excluded. 427 | * Verify that the current page is not an BuddyPress page 428 | * only if BuddyPress is active. 429 | * 430 | * @since 2.2.0 431 | * @access public 432 | * 433 | * @return void 434 | */ 435 | private function set_common_check() { 436 | 437 | // Do not redirect if excluded by user. 438 | if ( $this->is_excluded() ) { 439 | $this->common_check_passed = false; 440 | } elseif ( function_exists( 'bp_current_component' ) ) { 441 | $this->common_check_passed = ( ! bp_current_component() ); 442 | } else { 443 | $this->common_check_passed = true; 444 | } 445 | } 446 | 447 | /** 448 | * Disable URL guessing if enabled. 449 | * 450 | * @param bool $guess Current status. 451 | * 452 | * @since 3.0.4 453 | * 454 | * @return bool 455 | */ 456 | public function url_guessing( $guess ) { 457 | // Check if guessing is disabled. 458 | $disable_guessing = jj4t3_get_option( 'disable_guessing' ); 459 | // Disable only on 404. 460 | if ( $disable_guessing && is_404() && ! isset( $_GET['p'] ) ) { 461 | $guess = false; 462 | } 463 | 464 | return $guess; 465 | } 466 | } 467 | -------------------------------------------------------------------------------- /readme.txt: -------------------------------------------------------------------------------- 1 | === 404 to 301 - Redirect, Log and Notify 404 Errors === 2 | Contributors: joelcj91,duckdev 3 | Tags: 404, 301, 302, 307, not found, 404 redirect, 404 to 301, 301 redirect, seo redirect, error redirect, 404 seo, custom 404 page 4 | Donate link: https://www.paypal.me/JoelCJ 5 | Requires at least: 3.5 6 | Tested up to: 5.8 7 | Stable tag: 3.1.1 8 | Requires PHP: 5.6 9 | License: GPLv2 or later 10 | License URI: http://www.gnu.org/licenses/gpl-2.0.html 11 | 12 | Automatically redirect, log and notify all 404 page errors to any page using 301 redirect for SEO. No more 404 Errors in WebMaster tool. 13 | 14 | == Description == 15 | 16 | If you care about your website, you should take steps to avoid 404 errors as it affects your SEO badly. 404 ( Page not found ) errors are common and we all hate it, even Search engines do the same! Install this plugin then sit back and relax. It will take care of 404 errors! 17 | 18 | = What is 404 to 301? = 19 | 20 | *Handling 404 errors in your site should be easy. With this plugin, it finally is.* 21 | 22 | > #### 404 to 301 Log Manager - Add-on is now available! 23 | > 24 | > - Instead of instant email alerts, get **hourly, twice daily, daily, twice weekly, weekly** alerts.465 | display_name ) ? esc_html__( 'there', '404-to-301' ) : esc_html( ucwords( $current_user->display_name ) ), 469 | '', 470 | '' 471 | ); 472 | ?> 473 |
474 | 477 | 480 | 483 |' . $content . $count_text . '
'; 647 | } 648 | 649 | /** 650 | * Get default text if empty. 651 | * 652 | * Get an error text with custom class to show if the 653 | * current column value is empty or n/a. 654 | * 655 | * @param string $value Field value. 656 | * 657 | * @since 3.0.0 658 | * @access private 659 | * 660 | * @return string|boolean 661 | */ 662 | private function get_empty_content( $value ) { 663 | // Get default error text. 664 | if ( strtolower( $value ) === 'n/a' || empty( $value ) ) { 665 | return 'n/a'; 666 | } 667 | 668 | return false; 669 | } 670 | 671 | /** 672 | * Bulk actions drop down. 673 | * 674 | * Options to be added to the bulk actions drop down for users 675 | * to select. We have added 'Delete' actions. 676 | * Registering filter - "jj4t3_log_list_bulk_actions". 677 | * 678 | * @since 2.0.0 679 | * @access public 680 | * 681 | * @return array $actions Options to be added to the action select box. 682 | */ 683 | public function get_bulk_actions() { 684 | $actions = array( 685 | 'bulk_delete' => __( 'Delete Selected', '404-to-301' ), 686 | 'bulk_clean' => __( 'Delete All', '404-to-301' ), 687 | 'bulk_delete_all' => __( 'Delete All (Keep redirects)', '404-to-301' ), 688 | ); 689 | 690 | /** 691 | * Filter hook to change actions. 692 | * 693 | * @note If you are adding extra actions 694 | * Make sure it's actions are properly added. 695 | * 696 | * @since 3.0.0 697 | */ 698 | return apply_filters( 'jj4t3_log_list_bulk_actions', $actions ); 699 | } 700 | 701 | /** 702 | * Add extra action dropdown for grouping the error logs. 703 | * 704 | * @param string $which Top or Bottom. 705 | * 706 | * @access protected 707 | * @since 3.0.0 708 | * 709 | * @return void 710 | */ 711 | public function extra_tablenav( $which ) { 712 | if ( $this->has_items() && 'top' === $which ) { 713 | 714 | // This filter is already documented above. 715 | $allowed_values = apply_filters( 'jj4t3_log_list_groupby_allowed', array( 'url', 'ref', 'ip', 'ua' ) ); 716 | // Allowed/available columns. 717 | $available_columns = jj4t3_log_columns(); 718 | // Consider only available columns. 719 | $column_names = array_intersect( $allowed_values, array_keys( $available_columns ) ); 720 | // Add dropdown. 721 | echo '