diff --git a/admin/aioseop_module_class.php b/admin/aioseop_module_class.php index 4436d2744..282760c30 100644 --- a/admin/aioseop_module_class.php +++ b/admin/aioseop_module_class.php @@ -1980,6 +1980,12 @@ function add_menu( $parent_slug ) { } else { $name = $this->name; } + + if( is_multisite() && is_network_admin() && $name == 'Robots.txt' ){ + // Add the robots.txt editor into the network admin menu. + add_menu_page( 'Robots.txt Editor','Robots.txt Editor','edit_themes',plugin_basename( $this->file ),array( $this, 'display_settings_page', )); + } + if ( $this->locations === null ) { $hookname = add_submenu_page( $parent_slug, $name, $name, apply_filters( 'manage_aiosp', 'aiosp_manage_seo' ), plugin_basename( $this->file ), array( @@ -2306,7 +2312,7 @@ function get_option_html( $args ) { } else { $count_desc = __( ' characters. Most search engines use a maximum of %1$s chars for the %2$s.', 'all-in-one-seo-pack' ); } - $buf .= "
" + $buf .= "
" . sprintf( $count_desc, $size, trim( $this->strtolower( $options['name'] ), ':' ) ); if ( ! empty( $onload ) ) { $buf .= ""; diff --git a/admin/aioseop_module_manager.php b/admin/aioseop_module_manager.php index 372d52d5f..f794821d5 100644 --- a/admin/aioseop_module_manager.php +++ b/admin/aioseop_module_manager.php @@ -160,7 +160,7 @@ function load_module( $mod ) { if ( 'performance' === $mod && ! is_super_admin() ) { return false; } - if ( ( 'file_editor' === $mod || 'robots' === $mod ) + if ( ( 'file_editor' === $mod ) && ( ( defined( 'DISALLOW_FILE_EDIT' ) && DISALLOW_FILE_EDIT ) || ( defined( 'DISALLOW_FILE_MODS' ) && DISALLOW_FILE_MODS ) || ! is_super_admin() ) diff --git a/admin/display/menu.php b/admin/display/menu.php index c56ede21a..28bc00b64 100644 --- a/admin/display/menu.php +++ b/admin/display/menu.php @@ -11,6 +11,13 @@ class AIOSEOPAdminMenus { * Constructor to add the actions. */ function __construct() { + + add_action( 'network_admin_menu', array( $this, 'remove_menus' ), 15 ); + + if ( is_multisite()){ + return; + } + if ( current_user_can( 'manage_options' ) || current_user_can( 'aiosp_manage_seo' ) ) { add_action( 'admin_menu', array( $this, 'add_pro_submenu' ), 11 ); } else { @@ -18,6 +25,10 @@ function __construct() { } } + function remove_menus(){ + remove_menu_page( AIOSEOP_PLUGIN_DIRNAME . '/aioseop_class.php' ); // Remove AIOSEOP menu from the network admin. + } + /** * Adds Upgrade link to our menu. * @@ -35,4 +46,5 @@ function add_pro_submenu() { } } -new AIOSEOPAdminMenus(); + new AIOSEOPAdminMenus(); + diff --git a/aioseop_class.php b/aioseop_class.php index 2b94f37ae..19745185d 100644 --- a/aioseop_class.php +++ b/aioseop_class.php @@ -3651,7 +3651,11 @@ function add_hooks() { } if ( is_admin() ) { + if ( is_multisite() ) { + add_action( 'network_admin_menu', array( $this, 'admin_menu' ) ); + } add_action( 'admin_menu', array( $this, 'admin_menu' ) ); + add_action( 'admin_head', array( $this, 'add_page_icon' ) ); add_action( 'admin_init', 'aioseop_addmycolumns', 1 ); add_action( 'admin_init', 'aioseop_handle_ignore_notice' ); diff --git a/all_in_one_seo_pack.php b/all_in_one_seo_pack.php index 44a8b48e8..a69c518bc 100644 --- a/all_in_one_seo_pack.php +++ b/all_in_one_seo_pack.php @@ -4,7 +4,7 @@ Plugin Name: All In One SEO Pack Plugin URI: https://semperplugins.com/all-in-one-seo-pack-pro-version/ Description: Out-of-the-box SEO for your WordPress blog. Features like XML Sitemaps, SEO for custom post types, SEO for blogs or business sites, SEO for ecommerce sites, and much more. More than 30 million downloads since 2007. -Version: 2.6.1 +Version: 2.7 Author: Michael Torbert Author URI: https://semperplugins.com/all-in-one-seo-pack-pro-version/ Text Domain: all-in-one-seo-pack @@ -32,14 +32,14 @@ * The original WordPress SEO plugin. * * @package All-in-One-SEO-Pack - * @version 2.6.1 + * @version 2.7 */ if ( ! defined( 'AIOSEOPPRO' ) ) { define( 'AIOSEOPPRO', false ); } if ( ! defined( 'AIOSEOP_VERSION' ) ) { - define( 'AIOSEOP_VERSION', '2.6.1' ); + define( 'AIOSEOP_VERSION', '2.7' ); } global $aioseop_plugin_name; $aioseop_plugin_name = 'All in One SEO Pack'; @@ -279,7 +279,11 @@ function aioseop_activate() { } } -add_action( 'plugins_loaded', 'aioseop_init_class' ); +if ( is_multisite() ) { + add_action( 'muplugins_loaded', 'aioseop_init_class' ); +} else { + add_action( 'plugins_loaded', 'aioseop_init_class' ); +} if ( ! function_exists( 'aiosp_plugin_row_meta' ) ) { diff --git a/css/modules/aioseop_module.css b/css/modules/aioseop_module.css index 0f408ffa2..883d26a64 100644 --- a/css/modules/aioseop_module.css +++ b/css/modules/aioseop_module.css @@ -810,38 +810,79 @@ div.aioseop_feature#aioseop_coming_soon2 .aioseop_featured_image { /* max-width: 900px; */ } -#aiosp_sitemap_addl_pages, -#aiosp_video_sitemap_addl_pages { - clear: left; - margin-left: 20px; - max-width: 1072px; +/*** Sitemap Additional Pages section ***/ +#aiosp_sitemap_addl_pages_metabox .aioseop_options, +#aiosp_video_sitemap_addl_pages_metabox .aioseop_options { + width: 97%; + margin: 5px; +} + +#aiosp_sitemap_addl_pages_metabox .aioseop_wrapper#aiosp_sitemap_addl_instructions_wrapper, +#aiosp_video_sitemap_addl_pages_metabox .aioseop_wrapper#aiosp_video_sitemap_addl_instructions_wrapper { + display: block; + width: 100%; + float: none; + margin: 0; +} + +#aiosp_sitemap_addl_pages_metabox .aioseop_wrapper#aiosp_sitemap_addl_instructions_wrapper .aioseop_input, +#aiosp_video_sitemap_addl_pages_metabox .aioseop_wrapper#aiosp_video_sitemap_addl_instructions_wrapper .aioseop_input { + display: block; + width: 100%; } #aiosp_sitemap_addl_pages_metabox .aioseop_wrapper, #aiosp_video_sitemap_addl_pages_metabox .aioseop_wrapper { - width: 23%; - min-width: 165px; + padding: 0; +} + +#aiosp_sitemap_addl_pages_metabox .aioseop_wrapper .aioseop_input, +#aiosp_video_sitemap_addl_pages_metabox .aioseop_wrapper .aioseop_input { display: inline-block; - max-width: 265px; + vertical-align: middle; + width: 25%; + min-width: 120px; + height: 70px; +} + +#aiosp_sitemap_addl_pages_metabox .aioseop_wrapper .aioseop_top_label, +#aiosp_video_sitemap_addl_pages_metabox .aioseop_wrapper .aioseop_top_label { + width: 70%; + margin: 0; +} + +#aiosp_sitemap_addl_pages_metabox .aioseop_wrapper .aioseop_option_label, +#aiosp_video_sitemap_addl_pages_metabox .aioseop_wrapper .aioseop_option_label { + height: 30px !important; +} + +#aiosp_sitemap_addl_pages_metabox .aioseop_wrapper#aiosp_sitemap_addl_mod_wrapper input.aiseop-date, +#aiosp_video_sitemap_addl_pages_metabox .aioseop_wrapper#aiosp_video_sitemap_addl_mod_wrapper input.aiseop-date { + height: 36px; +} + +#aiosp_sitemap_addl_pages_metabox .aioseop_options .aioseop_submit_type, +#aiosp_video_sitemap_addl_pages_metabox .aioseop_options .aioseop_submit_type { + margin: 0; +} + +#aiosp_sitemap_addl_pages_metabox .aioseop_options .aioseop_submit_type input.button-primary, +#aiosp_video_sitemap_addl_pages_metabox .aioseop_options .aioseop_submit_type input.button-primary { + margin-left: 0 !important; } #aiosp_sitemap_addl_pages_metabox .aioseop_help_text_div, #aiosp_video_sitemap_addl_pages_metabox .aioseop_help_text_div { position: absolute; + width: auto; margin: 5px 0 10px 0; } -#aiosp_sitemap_addl_pages_metabox .aioseop_option_input, -#aiosp_video_sitemap_addl_pages_metabox .aioseop_option_input { - width: 94%; - min-width: 94%; -} - #aiosp_sitemap_addl_pages_metabox table.aioseop_table, #aiosp_video_sitemap_addl_pages_metabox table.aioseop_table { width: 96%; border: 1px solid #CCC; - margin: 5px 0 10px 0; + margin: 5px 5px 10px; } table.aioseop_table tr:nth-child(odd) { @@ -913,10 +954,15 @@ table.aioseop_table td { #aiosp_sitemap_addl_pages_metabox table.aioseop_table td, #aiosp_video_sitemap_addl_pages_metabox table.aioseop_table td { - width: 25%; + width: 27%; padding-left: 5%; } +#aiosp_sitemap_addl_pages_metabox table.aioseop_table td:first-child, +#aiosp_video_sitemap_addl_pages_metabox table.aioseop_table td:first-child { + padding-left: 2%; +} + table.aioseop_table td, table.aioseop_table th { padding: 3px; } @@ -952,6 +998,40 @@ table.aioseop_table td, table.aioseop_table th { clear: right; } +#aiosp_robots_rules { + clear: left; + margin-left: 20px; + max-width: 1072px; +} + +#aiosp_robots_default_metabox .aioseop_wrapper { + width: 31%; + min-width: 165px; + display: inline-block; + max-width: 265px; +} + +#aiosp_robots_default_metabox .aioseop_help_text_div { + position: absolute; + margin: 5px 0 10px 0; +} + +#aiosp_robots_default_metabox .aioseop_option_input { + width: 94%; + min-width: 94%; +} + +#aiosp_robots_default_metabox table.aioseop_table { + width: 96%; + border: 1px solid #CCC; + margin: 5px 0 10px 0; +} + +#aiosp_robots_default_metabox table.aioseop_table td { + width: 25%; + padding-left: 5%; +} + #aiosp_settings_form .aioseop_no_label, .aioseop_no_label { float: left; width: 92%; @@ -1189,7 +1269,7 @@ div.aioseop_notice a.aioseop_dismiss_link { vertical-align: bottom; } -.aiosp_delete_url { +.aiosp_delete { background-image: url('../../images/delete.png'); display: inline-block; width: 16px; @@ -1416,4 +1496,10 @@ div#aioseop_snippet > div > span { .aioseop_count_ugly { color: #fff !important; background-color: #f00 !important; +} + +textarea.robots-text { + background-color: #eee; + width: 100%; + height: 100%; } \ No newline at end of file diff --git a/inc/aioseop_functions.php b/inc/aioseop_functions.php index cd2542803..dbbb64d84 100644 --- a/inc/aioseop_functions.php +++ b/inc/aioseop_functions.php @@ -925,11 +925,11 @@ function fnmatch( $pattern, $string ) { } if ( ! function_exists( 'aiosp_log' ) ) { - function aiosp_log( $log ) { + function aiosp_log( $log, $force = false ) { global $aioseop_options; - if ( ! empty( $aioseop_options ) && isset( $aioseop_options['aiosp_do_log'] ) && $aioseop_options['aiosp_do_log'] ) { + if ( ( ! empty( $aioseop_options ) && isset( $aioseop_options['aiosp_do_log'] ) && $aioseop_options['aiosp_do_log'] ) || $force || defined( 'AIOSEOP_DO_LOG' ) ) { if ( is_array( $log ) || is_object( $log ) ) { error_log( print_r( $log, true ) ); diff --git a/js/modules/aioseop_module.js b/js/modules/aioseop_module.js index 86cdc60e9..4d7ed91d1 100644 --- a/js/modules/aioseop_module.js +++ b/js/modules/aioseop_module.js @@ -282,280 +282,91 @@ jQuery( document ).ready( } } ); - } -); - -/** - * @summary Custom jQuery plugin that enables image uploader in wordpress. - * - * @since 2.3.13 - * @since 2.4.14 Added success callback and options. - * @see http://www.webmaster-source.com/2013/02/06/using-the-wordpress-3-5-media-uploader-in-your-plugin-or-theme/ - * - * @param object options Plugin options. - */ -jQuery.fn.aioseopImageUploader = function( options ) { - // Keep reference to this. - var self = this; - - // Options - self.options = jQuery.extend( - { - success: undefined, - }, options - ); - - // Set input target when to update image url value - self.target = jQuery( self ).next(); - - // Uploader per image button - // * Having only one uploader was causing problems when multiple image buttons where in place - self.uploader = wp.media( - { - title: 'Choose Image', - button: { - text: 'Choose Image' - }, - multiple: false - } - ); - - /** - * Event handler that will be called when an image is selected from media uploader. - */ - self.onSelect = function() { - var url = self.uploader.state().get( 'selection' ).first().toJSON().url; - if ( self.target.length >= 0 ) { - jQuery( self.target ).val( url ); - } - if ( self.options.success !== undefined ) { - self.options.success( url, self ); - } - }; - - /** - * Click event handler. - * @param object e Click event. - */ - self.onClick = function( e ) { - e.preventDefault(); - self.uploader.open(); - }; - - // Set uploader select handler - self.uploader.on( 'select', self.onSelect ); - - // Set click handler - jQuery( self ).click( self.onClick ); -}; - -/** - * @summary Javascript for using WP media uploader. Indentifies which DOM should use custom uploader plugin. - * - * @see http://www.webmaster-source.com/2013/02/06/using-the-wordpress-3-5-media-uploader-in-your-plugin-or-theme/ - * @since ? - * @since 2.3.11.2 Use WP 3.5 new media uploader - * @since 2.3.13 Fixed issue #[740](https://github.com/semperfiwebdesign/all-in-one-seo-pack/issues/740) - * - */ -jQuery( document ).ready( - function($){ - - jQuery( '.aioseop_upload_image_button' ).each( - function() { - jQuery( this ).aioseopImageUploader( - { - success: function( url, el ) { - // Update checker - if ( jQuery( el ).prev().length > 0 ) { - jQuery( el ).prev().val( 1 ); - } - }, - } - ); - } - ); - - } -); - -/** - * @summary workaround for bug that causes radio inputs to lose settings when meta box is dragged. - * - * props to commentluv for this fix - * @author commentluv. - * @link https://core.trac.wordpress.org/ticket/16972 - * @since 1.0.0 - */ -jQuery( document ).ready( - function() { - - // listen for drag drop of metaboxes , bind mousedown to .hndle so it only fires when starting to drag - jQuery( '.hndle' ).mousedown( - function() { - - // set live event listener for mouse up on the content .wrap and wait a tick to give the dragged div time to settle before firing the reclick function - jQuery( '.wrap' ).mouseup( - function() { - aiosp_store_radio(); - setTimeout( function() { - aiosp_reclick_radio(); - }, 50 ); - } - ); - } - ); - } -); - -/** - * @summary Stores object of all radio buttons that are checked for entire form. - * - * @since 1.0.0 - */ -function aiosp_store_radio() { - var radioshack = {}; - jQuery( 'input[type="radio"]' ).each( - function() { - if ( jQuery( this ).is( ':checked' ) ) { - radioshack[ jQuery( this ).attr( 'name' ) ] = jQuery( this ).val(); - } - jQuery( document ).data( 'radioshack', radioshack ); - } - ); -} - -/** - * @summary Detects mouseup and restore all radio buttons that were checked. - * - * @since 1.0.0 - */ -function aiosp_reclick_radio() { - - // gets the object of checked radio button names and values - var radios = jQuery( document ).data( 'radioshack' ); - - // steps thru each object element and trigger a click on it's corresponding radio button - for ( var key in radios ) { - jQuery( 'input[name="' + key + '"]' ) - .filter( '[value="' + radios[ key ] + '"]' ) - .trigger( 'click' ); - } - // unbinds the event listener on .wrap (prevents clicks on inputs from triggering function) - jQuery( '.wrap' ).unbind( 'mouseup' ); -} - -/** - * @summary Handdles ajax call. - * - * @since 1.0.0 - * @param $action. - * @param $setting. - * @param $options. - * @param $success. - */ -function aioseop_handle_ajax_call( action, settings, options, success ) { - var aioseop_sack = new sack( ajaxurl ); - aioseop_sack.execute = 1; - aioseop_sack.method = 'POST'; - aioseop_sack.setVar( "action", action ); - aioseop_sack.setVar( "settings", settings ); - aioseop_sack.setVar( "options", options ); - if ( typeof success != 'undefined' ) { - aioseop_sack.onCompletion = success; - } - aioseop_sack.setVar( - "nonce-aioseop", - jQuery( 'input[name="nonce-aioseop"]' ).val() - ); - aioseop_sack.setVar( - "nonce-aioseop-edit", - jQuery( 'input[name="nonce-aioseop-edit"]' ).val() - ); - aioseop_sack.onError = function() { - alert( 'Ajax error on saving.' ); - }; - aioseop_sack.runAJAX(); -} - -/** - * @summary Handdles posts URL. - * - * @since 1.0.0 - * @param $action. - * @param $setting. - * @param $options. - * @param $success. - */ -function aioseop_handle_post_url( action, settings, options, success) { - jQuery( "div#aiosp_" + settings ).fadeOut( - 'fast', function() { - var loading = ' Please wait...'; - jQuery( "div#aiosp_" + settings ).fadeIn( - 'fast', function() { - aioseop_handle_ajax_call( action, settings, options, success ); - } - ); - jQuery( "div#aiosp_" + settings ).html( loading ); - } - ); -} - -/** - * @summary Handles when AIOSEOP is overflowed. - * - * @since 1.0.0 - * @param $element. - * @return mixed. - */ -function aioseop_is_overflowed( element ) { - return element.scrollHeight > element.clientHeight || element.scrollWidth > element.clientWidth; -} - -/** - * @summary Handles when overflowed border. - * - * @since 1.0.0 - * @param $el. - */ -function aioseop_overflow_border( el ) { - if ( aioseop_is_overflowed( el ) ) { - el.className = 'aioseop_option_div aioseop_overflowed'; - } else { - el.className = 'aioseop_option_div'; - } -} -/** - * @since 1.0.0 - * @return mixed. - */ -jQuery( document ).ready( - function() { - jQuery( "#poststuff .aioseop_radio_type input[type='radio']" ).on( - 'click', function() { - var previousValue = jQuery( this ).attr( 'previousValue' ); - var name = jQuery( this ).attr( 'name' ); - if ( typeof previousValue == 'undefined' ) { - if ( jQuery( this ).prop( "checked" ) ) { - jQuery( this ).prop( 'checked', true ); - jQuery( this ).attr( 'previousValue', 'checked' ); - } else { - jQuery( this ).prop( 'checked', false ); - jQuery( this ).attr( 'previousValue', false ); - } - return; - } - if ( previousValue == 'checked' ) { - jQuery( this ).prop( 'checked', false ); - jQuery( this ).attr( 'previousValue', false ); - } else { - jQuery( "input[name=" + name + "]:radio" ) - .attr( 'previousValue', false ); - jQuery( this ).attr( 'previousValue', 'checked' ); - } - } - ); + /** + * @summary workaround for bug that causes radio inputs to lose settings when meta box is dragged. + * + * props to commentluv for this fix + * @author commentluv. + * @link https://core.trac.wordpress.org/ticket/16972 + * @since 1.0.0 + */ + jQuery(document).ready( + function () { + // listen for drag drop of metaboxes , bind mousedown to .hndle so it only fires when starting to drag + jQuery('.hndle').mousedown( + function () { + + // set live event listener for mouse up on the content .wrap and wait a tick to give the dragged div time to settle before firing the reclick function + jQuery('.wrap').mouseup( + function () { + aiosp_store_radio(); + setTimeout(function () { + aiosp_reclick_radio(); + }, 50); + } + ); + } + ); + } + ); + + /** + * @summary Javascript for using WP media uploader. Indentifies which DOM should use custom uploader plugin. + * + * @see http://www.webmaster-source.com/2013/02/06/using-the-wordpress-3-5-media-uploader-in-your-plugin-or-theme/ + * @since ? + * @since 2.3.11.2 Use WP 3.5 new media uploader + * @since 2.3.13 Fixed issue #[740](https://github.com/semperfiwebdesign/all-in-one-seo-pack/issues/740) + * + */ + jQuery(document).ready( + function ($) { + jQuery('.aioseop_upload_image_button').each( + function () { + jQuery(this).aioseopImageUploader( + { + success: function (url, el) { + // Update checker + if (jQuery(el).prev().length > 0) { + jQuery(el).prev().val(1); + } + }, + } + ); + } + ); + } + ); + + jQuery(document).ready( + function () { + jQuery("#poststuff .aioseop_radio_type input[type='radio']").on( + 'click', function () { + var previousValue = jQuery(this).attr('previousValue'); + var name = jQuery(this).attr('name'); + if (typeof previousValue == 'undefined') { + if (jQuery(this).prop("checked")) { + jQuery(this).prop('checked', true); + jQuery(this).attr('previousValue', 'checked'); + } else { + jQuery(this).prop('checked', false); + jQuery(this).attr('previousValue', false); + } + return; + } + if (previousValue == 'checked') { + jQuery(this).prop('checked', false); + jQuery(this).attr('previousValue', false); + } else { + jQuery("input[name=" + name + "]:radio") + .attr('previousValue', false); + jQuery(this).attr('previousValue', 'checked'); + } + } + ); + } + ); if ( typeof aiosp_data.pointers != 'undefined' ) { /** @@ -800,21 +611,255 @@ jQuery( document ).ready( return false; } ); + + + jQuery( "div#aiosp_robots_default_metabox" ) + .delegate( + "a.aiosp_robots_delete_rule", "click", function( e ) { + e.preventDefault(); + aioseop_handle_post_url( + 'aioseop_ajax_delete_rule', + 'robots_rules', + jQuery( this ).attr( "data-id" ), + function() { + window.location.reload(); + } + ); + return false; + } + ); + + jQuery( "div#aiosp_robots_metabox" ) + .delegate( + "a.aiosp_robots_physical", "click", function( e ) { + e.preventDefault(); + aioseop_handle_post_url( + 'aioseop_ajax_robots_physical', + 'robots_metabox', + jQuery( this ).attr( "data-action" ), + function(data) { + if(data.data && data.data.message){ + alert(data.data.message); + } + window.location.reload(); + }, + true + ); + return false; + } + ); + + aiospinitAll(); + aiospinitCounting(); + } ); +/** + * @summary Custom jQuery plugin that enables image uploader in wordpress. + * + * @since 2.3.13 + * @since 2.4.14 Added success callback and options. + * @see http://www.webmaster-source.com/2013/02/06/using-the-wordpress-3-5-media-uploader-in-your-plugin-or-theme/ + * + * @param object options Plugin options. + */ +jQuery.fn.aioseopImageUploader = function( options ) { + // Keep reference to this. + var self = this; -jQuery( document ).ready( - function() { - // TODO: consider moving EVERYTHING that needs ready() to this function - aiospinitAll( jQuery ); - aiospinitCounting( jQuery ); + // Options + self.options = jQuery.extend( + { + success: undefined, + }, options + ); + + // Set input target when to update image url value + self.target = jQuery( self ).next(); + + // Uploader per image button + // * Having only one uploader was causing problems when multiple image buttons where in place + self.uploader = wp.media( + { + title: 'Choose Image', + button: { + text: 'Choose Image' + }, + multiple: false + } + ); + + /** + * Event handler that will be called when an image is selected from media uploader. + */ + self.onSelect = function() { + var url = self.uploader.state().get( 'selection' ).first().toJSON().url; + if ( self.target.length >= 0 ) { + jQuery( self.target ).val( url ); + } + if ( self.options.success !== undefined ) { + self.options.success( url, self ); + } + }; + + /** + * Click event handler. + * @param object e Click event. + */ + self.onClick = function( e ) { + e.preventDefault(); + self.uploader.open(); + }; + + // Set uploader select handler + self.uploader.on( 'select', self.onSelect ); + + // Set click handler + jQuery( self ).click( self.onClick ); +}; + +/** + * @summary Stores object of all radio buttons that are checked for entire form. + * + * @since 1.0.0 + */ +function aiosp_store_radio() { + var radioshack = {}; + jQuery( 'input[type="radio"]' ).each( + function() { + if ( jQuery( this ).is( ':checked' ) ) { + radioshack[ jQuery( this ).attr( 'name' ) ] = jQuery( this ).val(); + } + jQuery( document ).data( 'radioshack', radioshack ); + } + ); +} + +/** + * @summary Detects mouseup and restore all radio buttons that were checked. + * + * @since 1.0.0 + */ +function aiosp_reclick_radio() { + + // gets the object of checked radio button names and values + var radios = jQuery( document ).data( 'radioshack' ); + + // steps thru each object element and trigger a click on it's corresponding radio button + for ( var key in radios ) { + jQuery( 'input[name="' + key + '"]' ) + .filter( '[value="' + radios[ key ] + '"]' ) + .trigger( 'click' ); } -); + // unbinds the event listener on .wrap (prevents clicks on inputs from triggering function) + jQuery( '.wrap' ).unbind( 'mouseup' ); +} -function aiospinitAll($){ - if ( $( '.aiseop-date' ).length > 0 && $( '.aiseop-date' ).eq( 0 ).prop( 'type' ).toLowerCase() === 'text' ) { - $( '.aiseop-date' ).datepicker( +/** + * @summary Handdles ajax call. + * + * @since 1.0.0 + * @param $action. + * @param $setting. + * @param $options. + * @param $success. + */ +function aioseop_handle_ajax_call( action, settings, options, success ) { + var aioseop_sack = new sack( ajaxurl ); + aioseop_sack.execute = 1; + aioseop_sack.method = 'POST'; + aioseop_sack.setVar( "action", action ); + aioseop_sack.setVar( "settings", settings ); + aioseop_sack.setVar( "options", options ); + if ( typeof success != 'undefined' ) { + aioseop_sack.onCompletion = success; + } + aioseop_sack.setVar( + "nonce-aioseop", + jQuery( 'input[name="nonce-aioseop"]' ).val() + ); + aioseop_sack.setVar( + "nonce-aioseop-edit", + jQuery( 'input[name="nonce-aioseop-edit"]' ).val() + ); + aioseop_sack.onError = function() { + alert( 'Ajax error on saving.' ); + }; + aioseop_sack.runAJAX(); +} + +/** + * @summary Handdles posts URL. + * + * @since 1.0.0 + * @param $action. + * @param $setting. + * @param $options. + * @param $success. + */ +function aioseop_handle_post_url( action, settings, options, success_function, use_native) { + jQuery( "div#aiosp_" + settings ).fadeOut( + 'fast', function() { + var loading = ' Please wait...'; + jQuery( "div#aiosp_" + settings ).fadeIn( + 'fast', function() { + if(use_native) { + jQuery.ajax({ + url : ajaxurl, + method : 'POST', + dataType: 'json', + data : { + 'action' : action, + 'options' : options, + 'settings' : settings, + 'nonce-aioseop': jQuery( 'input[name="nonce-aioseop"]' ).val(), + 'nonce-aioseop-edit': jQuery( 'input[name="nonce-aioseop-edit"]' ).val() + }, + success : function(data){ + if(success_function){ + success_function(data); + } + } + }); + }else{ + aioseop_handle_ajax_call( action, settings, options, success_function ); + } + } + ); + jQuery( "div#aiosp_" + settings ).html( loading ); + } + ); +} + +/** + * @summary Handles when AIOSEOP is overflowed. + * + * @since 1.0.0 + * @param $element. + * @return mixed. + */ +function aioseop_is_overflowed( element ) { + return element.scrollHeight > element.clientHeight || element.scrollWidth > element.clientWidth; +} + +/** + * @summary Handles when overflowed border. + * + * @since 1.0.0 + * @param $el. + */ +function aioseop_overflow_border( el ) { + if ( aioseop_is_overflowed( el ) ) { + el.className = 'aioseop_option_div aioseop_overflowed'; + } else { + el.className = 'aioseop_option_div'; + } +} + +function aiospinitAll(){ + if ( jQuery( '.aiseop-date' ).length > 0 && jQuery( '.aiseop-date' ).eq( 0 ).prop( 'type' ).toLowerCase() === 'text' ) { + jQuery( '.aiseop-date' ).datepicker( { dateFormat: "yy-mm-dd" } @@ -822,13 +867,12 @@ function aiospinitAll($){ } } -function aiospinitCounting($){ +function aiospinitCounting(){ /* count them characters */ - $( '.aioseop_count_chars' ).on('keyup keydown', function(){ - countChars( $(this).eq(0), $(this).parent().find('[name="' + $(this).attr('data-length-field') + '"]').eq(0)); + jQuery( '.aioseop_count_chars' ).on('keyup keydown', function(){ + countChars( jQuery(this).eq(0), jQuery(this).parent().find('[name="' + jQuery(this).attr('data-length-field') + '"]').eq(0)); }); - $( '.aioseop_count_chars' ).each(function(){ - countChars( $(this).eq(0), $(this).parent().find('[name="' + $(this).attr('data-length-field') + '"]').eq(0)); + jQuery( '.aioseop_count_chars' ).each(function(){ + countChars( jQuery(this).eq(0), jQuery(this).parent().find('[name="' + jQuery(this).attr('data-length-field') + '"]').eq(0)); }); } - diff --git a/modules/aioseop_feature_manager.php b/modules/aioseop_feature_manager.php index 419613a58..9d085119f 100644 --- a/modules/aioseop_feature_manager.php +++ b/modules/aioseop_feature_manager.php @@ -48,7 +48,7 @@ function __construct( $mod ) { 'file_editor' => array( /* translators: the File Editor module allows users to edit the robots.txt file or .htaccess file on their site. */ 'name' => __( 'File Editor', 'all-in-one-seo-pack' ), - 'description' => __( 'Edit your robots.txt file and your .htaccess file to fine-tune your site.', 'all-in-one-seo-pack' ), + 'description' => __( 'Edit your your .htaccess file to fine-tune your site.', 'all-in-one-seo-pack' ), ), 'importer_exporter' => array( /* translators: the Importer & Exporter module allows users to import/export their All in One SEO Pack diff --git a/modules/aioseop_file_editor.php b/modules/aioseop_file_editor.php index 5d0f41db8..7454a4b78 100644 --- a/modules/aioseop_file_editor.php +++ b/modules/aioseop_file_editor.php @@ -20,25 +20,15 @@ function __construct() { $this->prefix = 'aiosp_file_editor_'; // option prefix $this->file = __FILE__; // the current file parent::__construct(); - $this->current_tab = 'robots'; + $this->current_tab = 'htaccess'; if ( isset( $_REQUEST['tab'] ) ) { $this->current_tab = $_REQUEST['tab']; } $help_text = array( - 'robotfile' => __( 'Robots.txt editor', 'all-in-one-seo-pack' ), 'htaccfile' => __( '.htaccess editor', 'all-in-one-seo-pack' ), ); $this->default_options = array( - 'robotfile' => array( - 'name' => __( 'Edit Robots.txt', 'all-in-one-seo-pack' ), - 'save' => false, - 'default' => '', - 'type' => 'textarea', - 'cols' => 70, - 'rows' => 25, - 'label' => 'top', - ), 'htaccfile' => array( 'name' => __( 'Edit .htaccess', 'all-in-one-seo-pack' ), 'save' => false, @@ -56,16 +46,10 @@ function __construct() { } } $this->tabs = array( - 'robots' => array( 'name' => __( 'robots.txt' ) ), 'htaccess' => array( 'name' => __( '.htaccess' ) ), ); $this->layout = array( - 'robots' => array( - 'name' => __( 'Edit robots.txt', 'all-in-one-seo-pack' ), - 'options' => array( 'robotfile' ), - 'tab' => 'robots', - ), 'htaccess' => array( 'name' => __( 'Edit .htaccess', 'all-in-one-seo-pack' ), 'options' => array( 'htaccfile' ), @@ -95,13 +79,7 @@ function add_page_hooks() { function filter_submit( $submit, $location ) { unset( $submit['Submit_Default'] ); $submit['Submit']['type'] = 'hidden'; - if ( 'robots' === $this->current_tab ) { - $submit['Submit_File_Editor'] = array( - 'type' => 'submit', - 'class' => 'button-primary', - 'value' => __( 'Update robots.txt', 'all-in-one-seo-pack' ) . ' »', - ); - } elseif ( 'htaccess' === $this->current_tab ) { + if ( 'htaccess' === $this->current_tab ) { $submit['Submit_htaccess'] = array( 'type' => 'submit', 'class' => 'button-primary', @@ -120,9 +98,7 @@ function filter_submit( $submit, $location ) { */ function filter_options( $options, $location ) { $prefix = $this->get_prefix( $location ); - if ( 'robots' === $this->current_tab ) { - $options = $this->load_files( $options, array( 'robotfile' => 'robots.txt' ), $prefix ); - } elseif ( 'htaccess' === $this->current_tab ) { + if ( 'htaccess' === $this->current_tab ) { $options = $this->load_files( $options, array( 'htaccfile' => '.htaccess' ), $prefix ); } @@ -135,9 +111,7 @@ function filter_options( $options, $location ) { */ function do_file_editor( $options, $location ) { $prefix = $this->get_prefix( $location ); - if ( 'robots' === $this->current_tab && isset( $_POST['Submit_File_Editor'] ) && $_POST['Submit_File_Editor'] ) { - $this->save_files( array( 'robotfile' => 'robots.txt' ), $prefix ); - } elseif ( 'htaccess' === $this->current_tab && isset( $_POST['Submit_htaccess'] ) && $_POST['Submit_htaccess'] ) { + if ( 'htaccess' === $this->current_tab && isset( $_POST['Submit_htaccess'] ) && $_POST['Submit_htaccess'] ) { $this->save_files( array( 'htaccfile' => '.htaccess' ), $prefix ); } } diff --git a/modules/aioseop_robots.php b/modules/aioseop_robots.php index 18b05d15f..a50700fd8 100644 --- a/modules/aioseop_robots.php +++ b/modules/aioseop_robots.php @@ -9,634 +9,552 @@ class All_in_One_SEO_Pack_Robots extends All_in_One_SEO_Pack_Module { function __construct() { + // only for testing + /* + if ( ! defined( 'AIOSEOP_DO_LOG' ) ) { + define( 'AIOSEOP_DO_LOG', true ); + } + */ + $this->name = __( 'Robots.txt', 'all-in-one-seo-pack' ); // Human-readable name of the plugin $this->prefix = 'aiosp_robots_'; // option prefix $this->file = __FILE__; // the current file parent::__construct(); $help_text = array( - 'additional' => __( 'Rule Type', 'all-in-one-seo-pack' ), - 'useragent' => __( 'User Agent', 'all-in-one-seo-pack' ), + 'type' => __( 'Rule Type', 'all-in-one-seo-pack' ), + 'agent' => __( 'User Agent', 'all-in-one-seo-pack' ), 'path' => __( 'Directory Path', 'all-in-one-seo-pack' ), - 'robotgen' => __( 'Robots.txt editor', 'all-in-one-seo-pack' ), ); $this->default_options = array( 'usage' => array( 'type' => 'html', 'label' => 'none', - 'default' => __( 'Use the rule builder below to add rules to create a new Robots.txt file.  If you already have a Robots.txt file you should use the File Editor feature in All in One SEO Pack to edit it or you can delete your current Robots.txt file and start a new one with the rule builder below.', 'all-in-one-seo-pack' ), + 'default' => __( 'Use the rule builder below to add/delete rules.', 'all-in-one-seo-pack' ), 'save' => false, ), - 'additional' => array( - 'name' => __( 'Rule Type', 'all-in-one-seo-pack' ), + ); + + $this->rule_fields = array( + 'agent' => array( + 'name' => __( 'User Agent', 'all-in-one-seo-pack' ), + 'type' => 'text', + 'label' => 'top', 'save' => false, - 'type' => 'select', - 'initial_options' => array( 'allow' => 'Allow', 'block' => 'Block' ), - ), - 'useragent' => array( - 'name' => __( 'User Agent', 'all-in-one-seo-pack' ), - 'save' => false, - 'type' => 'text', - ), - 'path' => array( - 'name' => __( 'Directory Path', 'all-in-one-seo-pack' ), - 'save' => false, - 'type' => 'text', ), - 'robotgen' => array( - 'name' => __( 'Generate Robots.txt', 'all-in-one-seo-pack' ), - 'save' => false, - 'default' => '', - 'type' => 'textarea', - 'cols' => 57, - 'rows' => 20, - 'label' => 'none', - 'readonly' => 'readonly', + 'type' => array( + 'name' => __( 'Rule', 'all-in-one-seo-pack' ), + 'type' => 'select', + 'initial_options' => array( 'allow' => __( 'Allow', 'all-in-one-seo-pack' ), 'disallow' => __( 'Block', 'all-in-one-seo-pack' ) ), + 'label' => 'top', + 'save' => false, ), - 'Submit_Preview' => array( - 'type' => 'submit', - 'class' => 'button-primary MRL', - 'name' => __( 'Add Rule', 'all-in-one-seo-pack' ) . ' »', - 'nowrap' => 1, - ), - 'Submit_Update' => array( - 'type' => 'submit', - 'class' => 'button-primary', - 'name' => __( 'Save Robots.txt File', 'all-in-one-seo-pack' ) . ' »', - 'nowrap' => 1, - ), - 'Submit_Delete' => array( - 'type' => 'submit', - 'class' => 'button-primary', - 'name' => __( 'Delete Robots.txt File', 'all-in-one-seo-pack' ) . ' »', - 'nowrap' => 1, - ), - 'optusage' => array( - 'type' => 'html', - 'label' => 'none', - 'default' => __( 'Click the Optimize button below and All in One SEO Pack will analyze your Robots.txt file to make sure it complies with the standards for Robots.txt files.  The results will be displayed in a table below.', 'all-in-one-seo-pack' ), - 'save' => false, - ), - 'Submit_Opt_Update' => array( - 'type' => 'submit', - 'class' => 'button-primary', - 'name' => __( 'Update Robots.txt File', 'all-in-one-seo-pack' ) . ' »', - 'nowrap' => 1, - 'style' => 'margin-left: 20px;', - ), - 'Submit_Opt_Preview' => array( - 'type' => 'submit', - 'class' => 'button-primary', - 'name' => __( 'Disregard Changes', 'all-in-one-seo-pack' ) . ' »', - 'nowrap' => 1, + 'path' => array( + 'name' => __( 'Directory Path', 'all-in-one-seo-pack' ), + 'type' => 'text', + 'label' => 'top', + 'save' => false, ), - 'Submit_Optimize' => array( + 'Submit' => array( 'type' => 'submit', 'class' => 'button-primary', - 'name' => __( 'Optimize', 'all-in-one-seo-pack' ) . ' »', + 'name' => __( 'Add Rule', 'all-in-one-seo-pack' ) . ' »', + 'style' => 'margin-left: 20px;', + 'label' => 'none', + 'save' => false, + 'value' => 1, + ), + 'rules' => array( + 'name' => __( 'Configured Rules', 'all-in-one-seo-pack' ), + 'type' => 'custom', + 'save' => true, + ), + 'robots.txt' => array( + 'name' => __( 'Robots.txt', 'all-in-one-seo-pack' ), + 'type' => 'custom', + 'save' => true, ), ); + add_filter( $this->prefix . 'submit_options', array( $this, 'submit_options'), 10, 2 ); + + if ( $this->has_physical_file() ) { + if ( ( is_multisite() && is_network_admin() ) || ( ! is_multisite() && current_user_can( 'manage_options') ) ) { + $this->default_options['usage']['default'] .= '

' . sprintf( __( 'A physical file exists. Do you want to %simport and delete%s it, %sdelete%s it or continue using it?', 'all-in-one-seo-pack' ), '', '', '', '' ) . '

'; + } else { + $this->default_options['usage']['default'] .= '

' . __( 'A physical file exists. This feature cannot be used.', 'all-in-one-seo-pack' ) . '

'; + } + + add_action( 'wp_ajax_aioseop_ajax_robots_physical', array( $this, 'ajax_action_physical_file' ) ); + + return; + } else { + add_action( 'admin_init', array( $this, 'import_default_robots' ) ); + } + + $this->default_options = array_merge( $this->default_options, $this->rule_fields ); + if ( ! empty( $help_text ) ) { foreach ( $help_text as $k => $v ) { $this->default_options[ $k ]['help_text'] = $v; } } - $this->locations = array( - 'generator' => array( - 'name' => 'Robots.txt', - 'type' => 'settings', - 'options' => array( - 'usage', - 'additional', - 'useragent', - 'path', - 'Submit_Preview', - 'Submit_Update', - 'Submit_Delete', - 'robotgen', - 'optusage', - 'Submit_Opt_Update', - 'Submit_Opt_Preview', - 'Submit_Optimize', - ), - ), - ); - $this->layout = array( 'default' => array( 'name' => __( 'Create a Robots.txt File', 'all-in-one-seo-pack' ), - 'options' => array( - 'usage', - 'additional', - 'useragent', - 'path', - 'Submit_Preview', - 'Submit_Update', - 'Submit_Delete', - 'robotgen', - ), // this is set below, to the remaining options -- pdb + 'help_link' => 'https://semperplugins.com/documentation/robots-txt-module/', + 'options' => array_merge( array( 'usage' ), array_keys( $this->rule_fields ) ), ), ); - $this->layout['optimize'] = array( - 'name' => __( 'Optimize your Robots.txt File', 'all-in-one-seo-pack' ), - 'options' => array( 'optusage', 'Submit_Optimize' ), - ); - if ( isset( $_POST['Submit_Optimize'] ) ) { - $this->layout['optimize']['options'] = array( - 'optusage', - 'Submit_Opt_Update', - 'Submit_Opt_Preview', - 'robothtml', - ); - $this->default_options['optusage']['default'] = __( 'Your Robots.txt file has been optimized.  Here are the results and recommendations.  Click the Update Robots.txt File button below to write these changes to your Robots.txt file.  Click the Disregard Changes button to ignore these recommendations and keep your current Robots.txt file.', 'all-in-one-seo-pack' ); - } // load initial options / set defaults $this->update_options(); - add_action( $this->prefix . 'settings_update', array( $this, 'do_robots' ), 10, 2 ); - add_filter( $this->prefix . 'display_options', array( $this, 'filter_options' ), 10, 2 ); - add_filter( $this->prefix . 'submit_options', array( $this, 'filter_submit' ), 10, 2 ); - add_filter( $this->prefix . 'display_settings', array( $this, 'filter_settings' ), 10, 2 ); + add_filter( $this->prefix . 'output_option', array( $this, 'display_custom_options' ), 10, 2 ); + add_filter( $this->prefix . 'update_options', array( $this, 'filter_options' ) ); + add_filter( $this->prefix . 'display_options', array( $this, 'filter_display_options' ) ); + add_action( 'wp_ajax_aioseop_ajax_delete_rule', array( $this, 'ajax_delete_rule' ) ); + add_filter( 'robots_txt', array( $this, 'robots_txt' ), 10, 2 ); } - function filter_settings( $settings, $location ) { - if ( $location == 'generator' ) { - $prefix = $this->get_prefix( $location ) . $location . '_'; - if ( isset( $_POST['Submit_Optimize'] ) ) { - if ( isset( $settings[ $prefix . 'robotgen' ] ) ) { - $settings[ $prefix . 'robotgen' ]['type'] = 'hidden'; - $settings[ $prefix . 'robotgen' ]['label'] = 'none'; - $settings[ $prefix . 'robotgen' ]['help_text'] = ''; - $settings[ $prefix . 'robothtml' ] = array( - 'name' => __( 'Robots.txt', 'all-in-one-seo-pack' ), - 'save' => false, - 'default' => '', - 'type' => 'html', - 'label' => 'none', - 'style' => 'margin-top:10px;', - ); - } + function filter_display_options( $options ) { + $errors = get_transient( "{$this->prefix}errors" . get_current_user_id() ); + if ( false !== $errors ) { + if ( is_array( $errors ) ) { + $errors = implode( '
', $errors ); } + echo sprintf( '

%s

', $errors ); + } + return $options; + } + + /** + * First time import of the default robots.txt rules. + */ + function import_default_robots() { + $options = $this->get_option_for_blog( $this->get_network_id() ); + if ( array_key_exists( 'default', $options ) ) { + return; } - return $settings; + $default = $this->do_robots(); + $lines = explode( "\n", $default ); + $rules = $this->extract_rules( $lines ); + aiosp_log("adding default rules: " . print_r($rules,true)); + + global $aioseop_options; + $aioseop_options['modules']["{$this->prefix}options"]['default'] = $rules; + update_option( 'aioseop_options', $aioseop_options ); + } + + function submit_options( $submit_options, $location ) { + unset( $submit_options['Submit'] ); + unset( $submit_options['Submit_Default'] ); + return $submit_options; } - function filter_submit( $submit, $location ) { - if ( $location == 'generator' ) { - unset( $submit['Submit_Default'] ); - $submit['Submit']['type'] = 'hidden'; + function ajax_action_physical_file() { + aioseop_ajax_init(); + $action = $_POST['options']; + + switch ( $action ) { + case 'import': + $this->import_default_robots(); + if ( ! $this->import_physical_file() ) { + wp_send_json_success( array( 'message' => __( 'Unable to read file', 'all-in-one-seo-pack' ) ) ); + } + // fall-through. + case 'delete': + if ( ! $this->delete_physical_file() ) { + wp_send_json_success( array( 'message' => __( 'Unable to delete file', 'all-in-one-seo-pack' ) ) ); + } + break; } - return $submit; + wp_send_json_success(); } - /** - * Returns the sitemap filename; - * - * @return bool - */ - function get_sitemap_filename() { + private function import_physical_file() { + $wp_filesystem = $this->get_filesystem_object(); + $file = trailingslashit( $wp_filesystem->abspath() ) . 'robots.txt'; + if ( ! $wp_filesystem->is_readable( $file ) ) { + return false; + } - global $aioseop_options; - if ( isset( $aioseop_options['modules']['aiosp_sitemap_options']['aiosp_sitemap_filename'] ) ) { - return $aioseop_options['modules']['aiosp_sitemap_options']['aiosp_sitemap_filename']; + $lines = $wp_filesystem->get_contents_array( $file ); + if ( ! $lines ) { + return true; } - return false; + $rules = $this->extract_rules( $lines ); + aiosp_log("importing rules: " . print_r($rules,true)); + + global $aioseop_options; + $aioseop_options['modules']["{$this->prefix}options"]["{$this->prefix}rules"] = $rules; + update_option( 'aioseop_options', $aioseop_options ); + return true; } - /** - * Filters the options. - * - * @todo Much of this couldn't be considered filtering options, and should be extracted to other functions. - * @since ?? - * @since 2.3.6 - */ - function filter_options( $options, $location ) { - if ( $location ) { - $prefix = $this->get_prefix( $location ) . $location . '_'; - } - if ( $location === 'generator' ) { - $optimize = false; - $robotgen = ''; - if ( ! empty( $_POST[ $prefix . 'robotgen' ] ) ) { - $robotgen = str_replace( "\r\n", "\n", $_POST[ $prefix . 'robotgen' ] ); - } - if ( isset( $_POST['Submit_Preview'] ) ) { - $options[ $prefix . 'robotgen' ] = $robotgen; + private function extract_rules( array $lines ) { + $rules = array(); + $user_agent = null; + $rule = array(); + $blog_rules = $this->get_all_rules(); + foreach ( $lines as $line ) { + if ( empty( $line ) ) { + continue; } - if ( ! isset( $_POST['Submit_Preview'] ) ) { - if ( isset( $_POST['Submit_Optimize'] ) && ! isset( $_POST['Submit_Delete'] ) && ! isset( $_POST['Submit_Update'] ) && ! isset( $_POST['Submit_Opt_Update'] ) ) { - $optimize = true; - } - if ( ! isset( $options[ $prefix . 'robotgen' ] ) || empty( $options[ $prefix . 'robotgen' ] ) ) { - if ( $optimize ) { - $options[ $prefix . 'robotgen' ] = $robotgen; - } - if ( empty( $options[ $prefix . 'robotgen' ] ) ) { - $options = $this->load_files( $options, array( 'robotgen' => 'robots.txt' ), $prefix ); - } - } + $array = array_map( 'trim', explode( ':', $line ) ); + if ( $array && count( $array ) !== 2 ) { + aiosp_log( "Ignoring $line from robots.txt" ); + continue; } - $access = ( get_option( 'blog_public' ) ) ? 'allow' : 'block'; - if ( $access ) { - global $aioseop_options; - $sitemapurl = ''; - $sitemap_filename = $this->get_sitemap_filename(); - if ( $sitemap_filename ) { - $sitemapurl = trailingslashit( get_home_url() ) . $sitemap_filename . '.xml'; - } - $allow_rule = "Sitemap: $sitemapurl \n\n# global\nUser-agent: *\nDisallow: /xmlrpc.php\n\n"; - $block_rule = "# global\nUser-agent: *\nDisallow: /\n\n"; - if ( empty( $options[ $prefix . 'robotgen' ] ) ) { - $options[ $prefix . 'robotgen' ] = ''; - } - if ( isset( $_POST['Submit_Preview'] ) && ( ( $options[ $prefix . 'robotgen' ] == $allow_rule ) || - ( $options[ $prefix . 'robotgen' ] == $block_rule ) ) - ) { - $options[ $prefix . 'robotgen' ] = ''; - } - if ( $access === 'block' && empty( $options[ $prefix . 'robotgen' ] ) ) { - $options[ $prefix . 'robotgen' ] .= $block_rule; - } elseif ( $access === 'allow' && empty( $options[ $prefix . 'robotgen' ] ) ) { - $options[ $prefix . 'robotgen' ] .= $allow_rule; - } - } - foreach ( array( 'ad' => 'additional', 'ua' => 'useragent', 'dp' => 'path' ) as $k => $v ) { - if ( isset( $_POST[ $prefix . $v ] ) ) { - $$k = $_POST[ $prefix . $v ]; - } + $operand = $array[0]; + switch ( strtolower( $operand ) ) { + case 'user-agent': + $user_agent = $array[1]; + break; + case 'disallow': + // fall-through. + case 'allow': + $rule[ 'agent' ] = $user_agent; + $rule[ 'type' ] = $operand; + $rule[ 'path' ] = $array[1]; + break; } - if ( ! empty( $ad ) && ! empty( $ua ) && ! empty( $dp ) ) { - if ( $ad === 'allow' ) { - $ad = 'Allow: '; + if ( $rule ) { + $rule = $this->validate_rule( $blog_rules, $rule ); + if ( is_wp_error( $rule ) ) { + $this->add_error( $rule ); } else { - $ad = 'Disallow: '; + $rules[] = $rule; } - $options[ $prefix . 'robotgen' ] .= "User-agent: $ua\n$ad $dp\n\n"; - } - $file = explode( "\n", $options[ $prefix . 'robotgen' ] ); - if ( $optimize ) { - $rules = $this->parse_robots( $file ); - $user_agents = $this->get_robot_user_agents( $rules ); - foreach ( $user_agents as $ua => $rules ) { - $user_agents[ $ua ]['disallow'] = $this->opt_robot_rule( $rules['disallow'] ); - $user_agents[ $ua ]['allow'] = $this->opt_robot_rule( $rules['allow'] ); - } - $rules = $this->flatten_user_agents( $user_agents ); - unset( $user_agents ); - foreach ( $rules as $r ) { - $r['disallow'] = $this->opt_robot_rule( $r['disallow'] ); - $r['allow'] = $this->opt_robot_rule( $r['allow'] ); - } - $options[ $prefix . 'robotgen' ] = $this->output_robots( $rules ); - $file2 = explode( "\n", $options[ $prefix . 'robotgen' ] ); - $options[ $prefix . 'robothtml' ] = '
' . $this->annotate_robots_html( $file, true, __( 'Current File', 'all-in-one-seo-pack' ) ) . '' . $this->annotate_robots_html( $file2, true, __( 'Proposed Changes', 'all-in-one-seo-pack' ) ) . '
'; - } else { - $options[ $prefix . 'robothtml' ] = $this->annotate_robots_html( $file, true, __( 'Current File', 'all-in-one-seo-pack' ) ); + $rule = array(); } } + return $rules; + } - return $options; + private function delete_physical_file() { + $wp_filesystem = $this->get_filesystem_object(); + $file = trailingslashit( $wp_filesystem->abspath() ) . 'robots.txt'; + return $wp_filesystem->delete( $file ); + } + + private function has_physical_file() { + $wp_filesystem = $this->get_filesystem_object(); + $file = trailingslashit( $wp_filesystem->abspath() ) . 'robots.txt'; + return $wp_filesystem->exists( $file ); + } + + function robots_txt( $output, $public ) { + return $output . "\r\n" . $this->get_rules(); } - function do_robots( $options, $location ) { - if ( $location ) { - $prefix = $this->get_prefix( $location ) . $location . '_'; + private function get_rules() { + $robots = array(); + $blog_rules = $this->get_all_rules( is_multisite() ? $this->get_network_id() : null ); + if ( is_multisite() && $this->get_network_id() != get_current_blog_id() ) { + $blog_rules = array_merge( $blog_rules, $this->get_all_rules( get_current_blog_id() ) ); } - if ( $location === 'generator' ) { - if ( isset( $_POST['Submit_Update'] ) || isset( $_POST['Submit_Opt_Update'] ) ) { - $this->save_files( array( 'robotgen' => 'robots.txt' ), $prefix ); - } elseif ( isset( $_POST['Submit_Delete'] ) ) { - $this->delete_files( array( 'robotgen' => 'robots.txt' ) ); + $rules = array(); + foreach ( $blog_rules as $rule ) { + $condition = sprintf( '%s: %s', $rule['type'], $rule['path'] ); + $agent = $rule['agent']; + if ( ! array_key_exists( $agent, $rules ) ) { + $rules[$agent] = array(); } + $rules[ $agent ][] = $condition; } + + foreach( $rules as $agent => $conditions ) { + $robots[] = sprintf( 'User-agent: %s', $agent ); + $robots[] = implode( "\r\n", $conditions ); + $robots[] = ""; + } + return implode( "\r\n", $robots ); } - function annotate_robots_html( $file, $show_help = false, $title = '' ) { - $robots = $this->annotate_robots( $file ); - if ( ! empty( $robots ) ) { - $buf = ''; - if ( ! empty( $title ) ) { - $buf .= ''; - } - $buf .= ''; - $buf .= ''; - $buf .= ''; - $buf .= ''; - - foreach ( $robots as $r ) { - $class = 'robots'; - $status = '#9cf975'; - $help = ''; - if ( ! $r['valid'] || ! $r['strict'] ) { - if ( ! $r['strict'] ) { - $class .= ' quirks'; - $status = 'yellow'; - } - if ( ! $r['valid'] ) { - $class .= ' invalid'; - $status = '#f9534a'; - } - if ( $show_help ) { - $help = '' - . '
'; - } - } - $buf .= ""; - } - $buf .= ''; - - $buf .= '
' . $title . '
ParameterStatus
{$help}
'; - if ( $show_help ) { - $buf .= '
-

' . __( 'Legend', 'all-in-one-seo-pack' ) . '

- - ' . __( 'More Information', 'all-in-one-seo-pack' ) . ' -
'; - } - } else { - $buf = '

Your Robots.txt file is either empty, cannot be found, or has invalid data.

'; + private function get_network_id() { + if ( is_multisite() ) { + return get_network()->site_id; } + return get_current_blog_id(); + } - return $buf; + private function get_option_for_blog( $id = null ) { + if ( is_null( $id ) ) { + $id = get_current_blog_id(); + } + if ( is_multisite() ) { + switch_to_blog( $id ); + } + $options = get_option('aioseop_options'); + if ( is_multisite() ) { + restore_current_blog(); + } + return array_key_exists( 'modules', $options ) && array_key_exists( "{$this->prefix}options", $options['modules'] ) ? $options['modules']["{$this->prefix}options"] : array(); + } + + /** + * Get all rules defined for the blog. + */ + private function get_all_rules( $id = null ) { + $options = $this->get_option_for_blog( $id ); + return array_key_exists( "{$this->prefix}rules", $options ) ? $options[ "{$this->prefix}rules" ] : array(); + } + + /** + * Get the default robot rules that were saved in the first initialization. + */ + private function get_default_rules() { + $options = $this->get_option_for_blog( $this->get_network_id() ); + return array_key_exists( 'default', $options ) ? $options[ 'default' ] : array(); } - function annotate_robots( $robots ) { - $state = 0; + function ajax_delete_rule() { + aioseop_ajax_init(); + $id = $_POST['options']; + + global $aioseop_options; + + // first check the defined rules. + $blog_rules = $this->get_all_rules(); $rules = array(); - foreach ( $robots as $l ) { - $l = trim( $l ); - if ( empty( $l[0] ) ) { - if ( $state > 1 ) { - $rules[] = array( - 'state' => 0, - 'type' => 'blank', - 'content' => $l, - 'valid' => true, - 'strict' => true, - ); - $state = 0; - } - } elseif ( $l[0] === '#' ) { - if ( $state < 1 ) { - $state = 1; - } - $rules[] = array( - 'state' => $state, - 'type' => 'comment', - 'content' => $l, - 'valid' => true, - 'strict' => true, - ); - } elseif ( stripos( $l, 'sitemap' ) === 0 ) { - $state = 2; - $rules[] = array( - 'state' => $state, - 'type' => 'sitemap', - 'content' => $l, - 'valid' => true, - 'strict' => false, - ); - } elseif ( stripos( $l, 'crawl-delay' ) === 0 ) { - $state = 3; - $rules[] = array( - 'state' => $state, - 'type' => 'crawl-delay', - 'content' => $l, - 'valid' => true, - 'strict' => false, - ); - } elseif ( stripos( $l, 'user-agent' ) === 0 ) { - $state = 3; - $rules[] = array( - 'state' => $state, - 'type' => 'user-agent', - 'content' => $l, - 'valid' => true, - 'strict' => true, - ); - } elseif ( stripos( $l, 'useragent' ) === 0 ) { - $state = 3; - $rules[] = array( - 'state' => $state, - 'type' => 'user-agent', - 'content' => $l, - 'valid' => true, - 'strict' => false, - ); - } elseif ( stripos( $l, 'disallow' ) === 0 ) { - if ( $state < 3 ) { - $rules[] = array( - 'state' => $state, - 'type' => 'disallow', - 'content' => $l, - 'valid' => false, - 'strict' => false, - ); - continue; - } - $state = 3; - $rules[] = array( - 'state' => $state, - 'type' => 'disallow', - 'content' => $l, - 'valid' => true, - 'strict' => true, - ); - } elseif ( stripos( $l, 'allow' ) === 0 ) { - if ( $state < 3 ) { - $rules[] = array( - 'state' => $state, - 'type' => 'allow', - 'content' => $l, - 'valid' => false, - 'strict' => false, - ); - continue; - } - $state = 3; - $rules[] = array( - 'state' => $state, - 'type' => 'allow', - 'content' => $l, - 'valid' => true, - 'strict' => false, - ); - } else { - $rules[] = array( - 'state' => $state, - 'type' => 'unknown', - 'content' => $l, - 'valid' => false, - 'strict' => false, - ); + foreach ( $blog_rules as $rule ) { + if ( $id === $rule['id'] ) { + continue; } + $rules[] = $rule; } + $aioseop_options['modules']["{$this->prefix}options"]["{$this->prefix}rules"] = $rules; + update_option( 'aioseop_options', $aioseop_options ); + } - return $rules; + + private function add_error( $error ) { + $errors = get_transient( "{$this->prefix}errors" . get_current_user_id() ); + if ( false === $errors ) { + $errors = array(); + } + $errors[] = $error->get_error_message(); + // set the error in a transient. + set_transient( "{$this->prefix}errors" . get_current_user_id(), $errors, 5 ); } - function parse_annotated_robots( $robots ) { - $state = 0; - $rules = array(); - $opts = array( 'sitemap', 'crawl-delay', 'user-agent', 'allow', 'disallow', 'comment' ); - $rule = array(); - foreach ( $opts as $o ) { - $rule[ $o ] = array(); - } - $blank_rule = $rule; - foreach ( $robots as $l ) { - switch ( $l['type'] ) { - case 'blank': - if ( $state >= 1 ) { - if ( ( $state === 1 ) && ( empty( $rule['user-agent'] ) ) ) { - $rule['user-agent'] = array( null ); - } - $rules[] = $rule; - $rule = $blank_rule; - } - continue; - case 'comment': - $rule['comment'][] = $l['content']; - continue; - case 'sitemap': - $rule['sitemap'][] = trim( substr( $l['content'], 8 ) ); - break; - case 'crawl-delay': - $rule['crawl-delay'][] = trim( substr( $l['content'], 12 ) ); - break; - case 'user-agent': - if ( $l['strict'] ) { - $ua = trim( substr( $l['content'], 11 ) ); - } else { - $ua = trim( substr( $l['content'], 10 ) ); - } - $rule['user-agent'][] = $ua; - break; - case 'disallow': - if ( $l['valid'] ) { - $rule['disallow'][] = trim( substr( $l['content'], 9 ) ); - break; - } - continue; - case 'allow': - if ( $l['valid'] ) { - $rule['allow'][] = trim( substr( $l['content'], 6 ) ); - break; - } - continue; - case 'unknown': - default: + /** + * Filter options. + * + * @param $options + * + * @return mixed + */ + function filter_options( $options ) { + $blog_rules = $this->get_all_rules(); + if ( ! empty( $_POST[ "{$this->prefix}path" ] ) ) { + foreach ( array_keys( $this->rule_fields ) as $field ) { + $post_field = $this->prefix . "" . $field; + if ( ! empty( $_POST[ $post_field ] ) ) { + $_POST[ $post_field ] = esc_attr( wp_kses_post( $_POST[ $post_field ] ) ); + } else { + $_POST[ $post_field ] = ''; + } + } + $new_rule = array( + 'path' => $_POST[ "{$this->prefix}path" ], + 'type' => $_POST[ "{$this->prefix}type" ], + 'agent' => $_POST[ "{$this->prefix}agent" ], + ); + $rule = $this->validate_rule( $blog_rules, $new_rule ); + if ( is_wp_error( $rule ) ) { + $this->add_error( $rule ); + } else { + $blog_rules[] = $rule; } - $state = $l['state']; } - if ( ( $state === 1 ) && ( empty( $rule['user-agent'] ) ) ) { - $rule['user-agent'] = array( null ); + // testing only - to clear the rules. + //$blog_rules = array(); + $options[ "{$this->prefix}rules" ] = $blog_rules; + return $options; + } + + private function sanitize_path( $path ) { + // if path does not have a trailing wild card (*) or does not refer to a file (with extension), add trailing slash. + if ( '*' !== substr( $path, -1 ) && false === strpos( $path, '.' ) ) { + $path = trailingslashit( $path ); } - if ( $state >= 1 ) { - $rules[] = $rule; + + // if path does not have a leading slash, add it. + if ( '/' !== substr( $path, 0, 1 ) ) { + $path = '/' . $path; } - return $rules; + // convert everything to lower case. + $path = strtolower( $path ); + + return $path; } - function parse_robots( $robots ) { - return $this->parse_annotated_robots( $this->annotate_robots( $robots ) ); + private function create_rule_id( $type, $agent, $path ) { + return md5( $type . $agent . $path ); } - function get_robot_user_agents( $rules ) { - $opts = array( 'sitemap', 'crawl-delay', 'user-agent', 'allow', 'disallow', 'comment' ); - $user_agents = array(); - foreach ( $rules as $r ) { - if ( ! empty( $r['sitemap'] ) && empty( $r['user-agent'] ) ) { - $r['user-agent'] = array( null ); + private function validate_rule( $rules, $new_rule ) { + if ( empty( $new_rule[ 'agent' ] ) ) { + return new WP_Error('invalid', __( 'User Agent cannot be empty', 'all-in-one-seo-pack' ) ); + } + if ( empty( $new_rule[ 'path' ] ) ) { + return new WP_Error('invalid', __( 'Directory Path cannot be empty', 'all-in-one-seo-pack' ) ); + } + + $default = $this->get_default_rules(); + $network = $this->get_all_rules( $this->get_network_id() ); + if ( ! is_array( $network ) ) { + $network = array(); + } + $network = array_merge( $default, $network, $rules ); + + // sanitize path. + $path = $this->sanitize_path( $new_rule[ 'path' ] ); + + // generate id to check uniqueness and also for purposes of deletion. + $id = $this->create_rule_id( $new_rule[ 'type' ], $new_rule[ 'agent' ], $path ); + if ( is_array( $rules ) ) { + $ids = wp_list_pluck( $rules, 'id' ); + if ( in_array( $id, $ids ) ) { + aiosp_log("rejected: same rule id exists - " . print_r($new_rule,true) . " vs. " . print_r($rules,true)); + return new WP_Error('duplicate', sprintf( __( 'Identical rule exists: %s', 'all-in-one-seo-pack' ), $new_rule[ 'path' ] ) ); } - foreach ( $r['user-agent'] as $ua ) { - if ( ! isset( $user_agents[ $ua ] ) ) { - $user_agents[ $ua ] = array(); - } - foreach ( $opts as $o ) { - if ( ! isset( $user_agents[ $ua ][ $o ] ) ) { - $user_agents[ $ua ][ $o ] = $r[ $o ]; - } else { - $user_agents[ $ua ][ $o ] = array_merge( $user_agents[ $ua ][ $o ], $r[ $o ] ); - } + } + + if ( $network ) { + $nw_agent_paths = array(); + foreach ( $network as $n ) { + $nw_agent_paths[] = $n['agent'] . $n['path']; + } + + // the same rule cannot be duplicated by the Admin. + $agent_path = $new_rule[ 'agent' ] . $path; + if ( in_array( $agent_path, $nw_agent_paths ) ) { + aiosp_log("rejected: same agent/path being overridden - " . print_r($new_rule,true) . " vs. " . print_r($rules,true)); + return new WP_Error('duplicate', sprintf( __( 'Rule cannot be overridden: %s', 'all-in-one-seo-pack' ), $new_rule[ 'path' ] ) ); + } + + // an identical path as specified by Network Admin cannot be overriden by Admin. + $nw_paths = wp_list_pluck( $network, 'path' ); + if ( in_array( $path, $nw_paths ) ) { + aiosp_log("rejected: same path being overridden - " . print_r($new_rule,true) . " vs. " . print_r($rules,true)); + return new WP_Error('duplicate', sprintf( __( 'Path cannot be overridden: %s', 'all-in-one-seo-pack' ), $new_rule[ 'path' ] ) ); + } + + // a wild-carded path specified by the Admin cannot override a path specified by Network Admin. + $pattern = str_replace( + array( + '.', + '/', + '*', + ), + array( + '\.', + '\/', + '(.*)', + ), + $path + ); + foreach ( $nw_paths as $nw_path ) { + $matches = array(); + preg_match( "/{$pattern}/", $nw_path, $matches ); + if ( ! empty( $matches ) && count( $matches ) >= 2 && ! empty( $matches[1] ) ) { + aiosp_log("rejected: wild card path being overridden - " . print_r($new_rule,true) . " vs. " . print_r($rules,true)); + return new WP_Error('conflict', sprintf( __( 'Wild-card path cannot be overridden: %s', 'all-in-one-seo-pack' ), $new_rule[ 'path' ] ) ); } } } - return $user_agents; + return array( + 'type' => ucwords( $new_rule[ 'type' ] ), + 'agent' => $new_rule[ 'agent' ], + 'path' => $path, + 'id' => $id, + ); } - function flatten_user_agents( $user_agents ) { - $rules = array(); - foreach ( $user_agents as $ua => $r ) { - $r['user-agent'] = array( $ua ); - $rules[] = $r; + private function reorder_rules( $rules ) { + if ( is_array( $rules ) ) { + uasort( $rules, array( $this, 'sort_rules' ) ); } - return $rules; } - function opt_robot_rule( $dis ) { - if ( is_array( $dis ) ) { // unique rules only - $dis = array_unique( $dis, SORT_STRING ); - $pd = null; - foreach ( $dis as $k => $d ) { - $d = trim( $d ); - if ( ! empty( $pd ) && ! empty( $d ) ) { - if ( strpos( $d, $pd ) === 0 ) { - unset( $dis[ $k ] ); - continue; // get rid of subpaths of $pd - } - } - $l = strlen( $d ); - if ( ( $l > 0 ) && ( $d[ $l - 1 ] !== '/' ) ) { - continue; - } - $pd = $d; // only allow directory paths for $pd + function sort_rules( $a, $b ) { + return $a['agent'] > $b['agent']; + } + + private function get_display_rules( $rules ) { + $buf = ''; + if ( ! empty( $rules ) ) { + $rules = $this->reorder_rules( $rules ); + $buf = "\n"; + $row = "\t\n"; + foreach ( $rules as $v ) { + $buf .= sprintf( $row, $v['id'], $v['agent'], $v['type'], $v['path'] ); } + $buf .= "
%s%s%s
\n"; } + return $buf; + } - return $dis; + private function do_robots() { + // disable header warnings. + error_reporting(0); + ob_start(); + do_action( 'do_robots' ); + if ( is_admin() ) { + // conflict with WooCommerce etc. cause the page to render as text/plain. + header( 'Content-Type:text/html' ); + } + return ob_get_clean(); } - function output_robots( $rules ) { - $robots = ''; - foreach ( $rules as $r ) { - foreach ( $r['comment'] as $c ) { - $robots .= "$c\n"; - } - foreach ( $r['user-agent'] as $u ) { - if ( $u != '' ) { - $robots .= "User-agent: $u\n"; - } - } - foreach ( $r['crawl-delay'] as $c ) { - $robots .= "Crawl-Delay: $c\n"; - } - foreach ( $r['allow'] as $a ) { - $robots .= "Allow: $a\n"; - } - foreach ( $r['disallow'] as $d ) { - $robots .= "Disallow: $d\n"; - } - foreach ( $r['sitemap'] as $s ) { - $robots .= "Sitemap: $s\n"; - } - $robots .= "\n"; + /** + * Custom settings. + * + * Displays boxes in a table layout. + * + * @param $buf + * @param $args + * + * @return string + */ + function display_custom_options( $buf, $args ) { + switch ( $args['name'] ) { + case "{$this->prefix}rules": + $buf .= "
"; + $rules = $args['value']; + $buf .= $this->get_display_rules( $rules ); + $buf .= '
'; + break; + case "{$this->prefix}robots.txt": + $buf .= ""; + break; } - return $robots; + $args['options']['type'] = 'hidden'; + if ( ! empty( $args['value'] ) ) { + $args['value'] = wp_json_encode( $args['value'] ); + } else { + $args['options']['type'] = 'html'; + } + if ( empty( $args['value'] ) ) { + $args['value'] = ''; + } + $buf .= $this->get_option_html( $args ); + + return $buf; } } } diff --git a/modules/aioseop_sitemap.php b/modules/aioseop_sitemap.php index bf4017913..b5ccfcb46 100644 --- a/modules/aioseop_sitemap.php +++ b/modules/aioseop_sitemap.php @@ -502,7 +502,7 @@ function display_custom_options( $buf, $args ) { if ( is_object( $v ) ) { $v = (Array) $v; } - $buf .= "\t {$k}{$v['prio']}{$v['freq']}{$v['mod']}\n"; + $buf .= "\t {$k}{$v['prio']}{$v['freq']}{$v['mod']}\n"; } $buf .= "\n"; } diff --git a/readme.txt b/readme.txt index b250be1e8..15acc8a3b 100644 --- a/readme.txt +++ b/readme.txt @@ -4,7 +4,7 @@ Donate link: https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=mrtor Tags: SEO, all in one seo, WordPress SEO, Google Search Console, XML Sitemap, image seo Requires at least: 4.4 Tested up to: 4.9 -Stable tag: 2.6.1 +Stable tag: 2.7 License: GPLv2 or later Requires PHP: 5.2.4