initial
This commit is contained in:
@@ -0,0 +1,421 @@
|
||||
<?php
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* The dashboard-specific functionality of the plugin.
|
||||
*
|
||||
* Registers styles and scripts, adds the custom administration page,
|
||||
* and processes user input on the "search/replace" form.
|
||||
*
|
||||
* @link https://bettersearchreplace.com
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @package Better_Search_Replace
|
||||
* @subpackage Better_Search_Replace/includes
|
||||
*/
|
||||
|
||||
// Prevent direct access.
|
||||
if ( ! defined( 'BSR_PATH' ) ) exit;
|
||||
|
||||
class BSR_Admin {
|
||||
|
||||
/**
|
||||
* The ID of this plugin.
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @access private
|
||||
* @var string $better_search_replace The ID of this plugin.
|
||||
*/
|
||||
private $better_search_replace;
|
||||
|
||||
/**
|
||||
* The version of this plugin.
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @access private
|
||||
* @var string $version The current version of this plugin.
|
||||
*/
|
||||
private $version;
|
||||
|
||||
/**
|
||||
* Initialize the class and set its properties.
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @var string $better_search_replace The name of this plugin.
|
||||
* @var string $version The version of this plugin.
|
||||
*/
|
||||
public function __construct( $better_search_replace, $version ) {
|
||||
$this->better_search_replace = $better_search_replace;
|
||||
$this->version = $version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register any CSS and JS used by the plugin.
|
||||
* @since 1.0.0
|
||||
* @access public
|
||||
* @param string $hook Used for determining which page(s) to load our scripts.
|
||||
*/
|
||||
public function enqueue_scripts( $hook ) {
|
||||
if ( 'tools_page_better-search-replace' === $hook ) {
|
||||
$min = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
|
||||
|
||||
wp_enqueue_style( 'better-search-replace', BSR_URL . "assets/css/better-search-replace$min.css", array(), $this->version, 'all' );
|
||||
wp_enqueue_style( 'jquery-style', BSR_URL . 'assets/css/jquery-ui.min.css', array(), $this->version, 'all' );
|
||||
wp_enqueue_script( 'jquery-ui-slider' );
|
||||
wp_enqueue_script( 'better-search-replace', BSR_URL . "assets/js/better-search-replace$min.js", array( 'jquery' ), $this->version, true );
|
||||
wp_enqueue_style( 'thickbox' );
|
||||
wp_enqueue_script( 'thickbox' );
|
||||
|
||||
wp_localize_script( 'better-search-replace', 'bsr_object_vars', array(
|
||||
'page_size' => get_option( 'bsr_page_size' ) ? absint( get_option( 'bsr_page_size' ) ) : 20000,
|
||||
'endpoint' => BSR_AJAX::get_endpoint(),
|
||||
'ajax_nonce' => wp_create_nonce( 'bsr_ajax_nonce' ),
|
||||
'no_search' => __( 'No search string was defined, please enter a URL or string to search for.', 'better-search-replace' ),
|
||||
'no_tables' => __( 'Please select the tables that you want to update.', 'better-search-replace' ),
|
||||
'unknown' => __( 'An error occurred processing your request. Try decreasing the "Max Page Size", or contact support.', 'better-search-replace' ),
|
||||
'processing' => __( 'Processing...', 'better-search-replace' )
|
||||
) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register any menu pages used by the plugin.
|
||||
* @since 1.0.0
|
||||
* @access public
|
||||
*/
|
||||
public function bsr_menu_pages() {
|
||||
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Legacy filter name; public API for capability override.
|
||||
$cap = apply_filters( 'bsr_capability', 'manage_options' );
|
||||
add_submenu_page( 'tools.php', __( 'Better Search Replace', 'better-search-replace' ), __( 'Better Search Replace', 'better-search-replace' ), $cap, 'better-search-replace', array( $this, 'bsr_menu_pages_callback' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* The callback for creating a new submenu page under the "Tools" menu.
|
||||
* @access public
|
||||
*/
|
||||
public function bsr_menu_pages_callback() {
|
||||
require_once BSR_PATH . 'includes/class-bsr-templates-helper.php';
|
||||
require_once BSR_PATH . 'templates/bsr-dashboard.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the result or error onto the better-search-replace admin page.
|
||||
*/
|
||||
public static function render_result() {
|
||||
// phpcs:disable WordPress.Security.NonceVerification.Missing,WordPress.Security.NonceVerification.Recommended -- Each GET branch calls BSR_Utils::validate_tools_screen_get() before use.
|
||||
if ( ! filter_has_var( INPUT_GET, 'result' ) || ! BSR_Utils::validate_tools_screen_get() ) {
|
||||
// phpcs:enable WordPress.Security.NonceVerification.Missing,WordPress.Security.NonceVerification.Recommended
|
||||
return;
|
||||
}
|
||||
|
||||
// phpcs:enable WordPress.Security.NonceVerification.Missing,WordPress.Security.NonceVerification.Recommended
|
||||
$result = get_transient( 'bsr_results' );
|
||||
|
||||
// Have results with required fields set with correctly typed data?
|
||||
if (
|
||||
empty( $result ) ||
|
||||
! isset( $result['tables'] ) ||
|
||||
! is_int( $result['tables'] ) ||
|
||||
! isset( $result['change'] ) ||
|
||||
! is_int( $result['change'] ) ||
|
||||
! isset( $result['updates'] ) ||
|
||||
! is_int( $result['updates'] )
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TB_iframe is required for Thickbox iframe mode. Core thickbox truncates src at "TB_"; bsr_thickbox_fix() in assets/js/better-search-replace.js restores the full URL for action=bsr_view_details only.
|
||||
$details_url = self::get_view_details_url(
|
||||
array(
|
||||
'TB_iframe' => 'true',
|
||||
'width' => '800',
|
||||
'height' => '500',
|
||||
)
|
||||
);
|
||||
|
||||
$result_message_allowed_html = array(
|
||||
'p' => array(),
|
||||
'strong' => array(),
|
||||
'a' => array(
|
||||
'href' => true,
|
||||
'class' => true,
|
||||
'title' => true,
|
||||
),
|
||||
);
|
||||
|
||||
echo '<div class="updated bsr-updated" style="display: none;">';
|
||||
|
||||
if ( isset( $result['dry_run'] ) && $result['dry_run'] === 'on' ) {
|
||||
$msg = sprintf(
|
||||
/* translators: 1: number of tables, 2: cells found, 3: changes made, 4: details URL, 5: Thickbox title attribute. */
|
||||
__(
|
||||
'<p><strong>DRY RUN:</strong> <strong>%1$d</strong> tables were searched, <strong>%2$d</strong> cells were found that need to be updated, and <strong>%3$d</strong> changes were made.</p><p><a href="%4$s" class="thickbox" title="%5$s">Click here</a> for more details, or use the form below to run the search/replace.</p>',
|
||||
'better-search-replace'
|
||||
),
|
||||
(int) $result['tables'],
|
||||
(int) $result['change'],
|
||||
(int) $result['updates'],
|
||||
esc_url( $details_url ),
|
||||
esc_attr__( 'Dry Run Details', 'better-search-replace' )
|
||||
);
|
||||
echo wp_kses( $msg, $result_message_allowed_html );
|
||||
} else {
|
||||
$msg = sprintf(
|
||||
/* translators: 1: tables searched, 2: cells changed, 3: updates, 4: details URL, 5: Thickbox title attribute. */
|
||||
__(
|
||||
'<p>During the search/replace, <strong>%1$d</strong> tables were searched, with <strong>%2$d</strong> cells changed in <strong>%3$d</strong> updates.</p><p><a href="%4$s" class="thickbox" title="%5$s">Click here</a> for more details.</p>',
|
||||
'better-search-replace'
|
||||
),
|
||||
(int) $result['tables'],
|
||||
(int) $result['change'],
|
||||
(int) $result['updates'],
|
||||
esc_url( $details_url ),
|
||||
esc_attr__( 'Search/Replace Details', 'better-search-replace' )
|
||||
);
|
||||
echo wp_kses( $msg, $result_message_allowed_html );
|
||||
}
|
||||
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefills the given value on the search/replace page (dry run, live run, from profile).
|
||||
* @access public
|
||||
* @param string $value The value to check for.
|
||||
* @param string $type The type of the value we're filling.
|
||||
*/
|
||||
public static function prefill_value( $value, $type = 'text' ) {
|
||||
|
||||
if ( ! BSR_Utils::validate_tools_screen_get() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// phpcs:disable WordPress.Security.NonceVerification.Missing,WordPress.Security.NonceVerification.Recommended -- Tools GET gated by BSR_Utils::validate_tools_screen_get().
|
||||
if ( filter_has_var( INPUT_GET, 'result' ) && get_transient( 'bsr_results' ) ) {
|
||||
$values = get_transient( 'bsr_results' );
|
||||
} else {
|
||||
$values = array();
|
||||
}
|
||||
|
||||
// Prefill the value.
|
||||
if ( isset( $values[ $value ] ) ) {
|
||||
|
||||
if ( 'checkbox' === $type && 'on' === $values[ $value ] ) {
|
||||
echo 'checked';
|
||||
} else {
|
||||
echo esc_attr( str_replace( '#BSR_BACKSLASH#', '\\', $values[ $value ] ) );
|
||||
}
|
||||
}
|
||||
|
||||
// phpcs:enable WordPress.Security.NonceVerification.Missing,WordPress.Security.NonceVerification.Recommended
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the tables available to run a search replace, prefilling if already
|
||||
* selected the tables.
|
||||
* @access public
|
||||
*/
|
||||
public static function load_tables() {
|
||||
|
||||
if ( ! BSR_Utils::validate_tools_screen_get() ) {
|
||||
echo '<select id="bsr-table-select" name="select_tables[]" multiple="multiple" style=""></select>';
|
||||
return;
|
||||
}
|
||||
|
||||
$tables = BSR_DB::get_tables();
|
||||
$sizes = BSR_DB::get_sizes();
|
||||
|
||||
echo '<select id="bsr-table-select" name="select_tables[]" multiple="multiple" style="">';
|
||||
|
||||
$result = null;
|
||||
$transient_result = get_transient( 'bsr_results' );
|
||||
|
||||
// phpcs:disable WordPress.Security.NonceVerification.Missing,WordPress.Security.NonceVerification.Recommended -- Tools GET gated by BSR_Utils::validate_tools_screen_get().
|
||||
if ( filter_has_var( INPUT_GET, 'result' ) && $transient_result ) {
|
||||
$result = $transient_result;
|
||||
}
|
||||
|
||||
foreach ( $tables as $table ) {
|
||||
|
||||
$table_size = isset( $sizes[ $table ] ) ? $sizes[ $table ] : '';
|
||||
$selected = false;
|
||||
|
||||
if ( is_array( $result ) && isset( $result['table_reports'][ $table ] ) ) {
|
||||
$selected = true;
|
||||
}
|
||||
|
||||
printf(
|
||||
'<option value="%1$s"%2$s>%3$s</option>',
|
||||
esc_attr( $table ),
|
||||
$selected ? ' selected="selected"' : '',
|
||||
esc_html( $table . ' ' . $table_size )
|
||||
);
|
||||
}
|
||||
|
||||
echo '</select>';
|
||||
|
||||
// phpcs:enable WordPress.Security.NonceVerification.Missing,WordPress.Security.NonceVerification.Recommended
|
||||
}
|
||||
|
||||
/**
|
||||
* admin-post.php URL for bsr_view_details with nonce.
|
||||
*
|
||||
* For Thickbox iframe links you may pass TB_iframe=true (and width/height). Core thickbox.js
|
||||
* truncates iframe src at the substring "TB_"; bsr_thickbox_fix() in assets/js/better-search-replace.js
|
||||
* restores the full URL when action=bsr_view_details. Do not add other query keys whose names
|
||||
* contain "TB_".
|
||||
*
|
||||
* @param array $extra Query arguments (action is set automatically; _wpnonce is always appended last).
|
||||
* @return string Raw URL; use esc_url() when printing in HTML.
|
||||
*/
|
||||
public static function get_view_details_url( $extra = array() ) {
|
||||
$args = array_merge( array( 'action' => 'bsr_view_details' ), (array) $extra );
|
||||
$url = add_query_arg( $args, admin_url( 'admin-post.php' ) );
|
||||
|
||||
return add_query_arg( '_wpnonce', wp_create_nonce( 'bsr_view_details' ), $url );
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the result details (via Thickbox).
|
||||
* @access public
|
||||
*/
|
||||
public function load_details() {
|
||||
// phpcs:disable WordPress.Security.NonceVerification.Missing,WordPress.Security.NonceVerification.Recommended -- Nonce verified below; Thickbox GET must not rely on referer.
|
||||
if ( ! isset( $_REQUEST['_wpnonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( (string) $_REQUEST['_wpnonce'] ) ), 'bsr_view_details' ) ) {
|
||||
wp_die( esc_html__( 'Security check failed.', 'better-search-replace' ), '', array( 'response' => 403 ) );
|
||||
}
|
||||
// phpcs:enable WordPress.Security.NonceVerification.Missing,WordPress.Security.NonceVerification.Recommended
|
||||
|
||||
if ( ! bsr_enabled_for_user() ) {
|
||||
wp_die( esc_html__( 'Sorry, you are not allowed to view this content.', 'better-search-replace' ), '', array( 'response' => 403 ) );
|
||||
}
|
||||
|
||||
if ( ! get_transient( 'bsr_results' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$results = get_transient( 'bsr_results' );
|
||||
$min = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
|
||||
|
||||
wp_enqueue_style( 'common' );
|
||||
wp_enqueue_style(
|
||||
'better-search-replace',
|
||||
BSR_URL . 'assets/css/better-search-replace' . $min . '.css',
|
||||
array( 'common' ),
|
||||
BSR_VERSION,
|
||||
'all'
|
||||
);
|
||||
wp_print_styles( array( 'common', 'better-search-replace' ) );
|
||||
|
||||
$upgrade_url = 'https://deliciousbrains.com/better-search-replace/upgrade/?utm_source=insideplugin&utm_medium=web&utm_content=tooltip&utm_campaign=bsr-to-migrate';
|
||||
?>
|
||||
<div style="padding: 32px; background-color: var(--color-white); min-height: 100%;">
|
||||
<table id="bsr-results-table" class="widefat">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="bsr-first"><?php esc_html_e( 'Table', 'better-search-replace' ); ?></th>
|
||||
<th class="bsr-second"><?php esc_html_e( 'Changes Found', 'better-search-replace' ); ?></th>
|
||||
<th class="bsr-third"><?php esc_html_e( 'Rows Updated', 'better-search-replace' ); ?></th>
|
||||
<th class="bsr-fourth"><?php esc_html_e( 'Time', 'better-search-replace' ); ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php
|
||||
foreach ( $results['table_reports'] as $table_name => $report ) {
|
||||
$time = $report['end'] - $report['start'];
|
||||
|
||||
$change_cell = '';
|
||||
if ( 0 !== (int) $report['change'] ) {
|
||||
$change_cell = '<a class="tooltip">' . esc_html( (string) $report['change'] ) . '</a>';
|
||||
$change_cell .= '<span class="helper-message right">';
|
||||
$change_cell .= wp_kses_post(
|
||||
sprintf(
|
||||
/* translators: %s: URL to the upgrade page. */
|
||||
__( '<a href="%s" target="_blank">UPGRADE</a> to view details on the exact changes that will be made.', 'better-search-replace' ),
|
||||
esc_url( $upgrade_url )
|
||||
)
|
||||
);
|
||||
$change_cell .= '</span>';
|
||||
}
|
||||
|
||||
$updates_cell = '0';
|
||||
if ( 0 !== (int) $report['updates'] ) {
|
||||
$updates_cell = '<strong>' . esc_html( (string) $report['updates'] ) . '</strong>';
|
||||
}
|
||||
|
||||
printf(
|
||||
'<tr><td class="bsr-first">%1$s</td><td class="bsr-second">%2$s</td><td class="bsr-third">%3$s</td><td class="bsr-fourth">%4$s%5$s</td></tr>',
|
||||
esc_html( $table_name ),
|
||||
wp_kses_post( $change_cell ),
|
||||
wp_kses_post( $updates_cell ),
|
||||
esc_html( (string) round( $time, 3 ) ),
|
||||
esc_html__( ' seconds', 'better-search-replace' )
|
||||
);
|
||||
}
|
||||
?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers our settings in the options table.
|
||||
* @access public
|
||||
*/
|
||||
public function register_option() {
|
||||
register_setting( 'bsr_settings_fields', 'bsr_page_size', 'absint' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads the system info file for support.
|
||||
* @access public
|
||||
*/
|
||||
public function download_sysinfo() {
|
||||
if ( ! BSR_Utils::check_admin_referer( 'bsr_download_sysinfo', 'bsr_sysinfo_nonce', false ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// phpcs:disable WordPress.Security.NonceVerification.Missing,WordPress.Security.NonceVerification.Recommended -- Nonce verified via BSR_Utils::check_admin_referer() above; PHPCS does not recognize the wrapper.
|
||||
if ( ! isset( $_POST['bsr-sysinfo'] ) ) {
|
||||
// phpcs:enable WordPress.Security.NonceVerification.Missing,WordPress.Security.NonceVerification.Recommended
|
||||
return;
|
||||
}
|
||||
|
||||
nocache_headers();
|
||||
|
||||
header( 'Content-Type: text/plain' );
|
||||
header( 'Content-Disposition: attachment; filename="bsr-system-info.txt"' );
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.InputNotValidated,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Nonce verified via BSR_Utils::check_admin_referer(); isset() above; plain-text system info for download; tags stripped below.
|
||||
$sysinfo = wp_unslash( (string) $_POST['bsr-sysinfo'] );
|
||||
|
||||
echo esc_html( wp_strip_all_tags( $sysinfo ) );
|
||||
// phpcs:enable WordPress.Security.NonceVerification.Missing,WordPress.Security.NonceVerification.Recommended
|
||||
die();
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the link to upgrade to BSR Pro
|
||||
* @access public
|
||||
* @param array $links The links assigned to the plugin.
|
||||
*/
|
||||
public function meta_upgrade_link( $links, $file ) {
|
||||
$plugin = plugin_basename( BSR_FILE );
|
||||
|
||||
if ( $file == $plugin ) {
|
||||
return array_merge(
|
||||
$links,
|
||||
array(
|
||||
'<a href="' . esc_url( 'https://bettersearchreplace.com/?utm_source=insideplugin&utm_medium=web&utm_content=plugins-page&utm_campaign=pro-upsell' ) . '">' . esc_html__( 'Upgrade to Pro', 'better-search-replace' ) . '</a>',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return $links;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,290 @@
|
||||
<?php
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX-specific functionality for the plugin.
|
||||
*
|
||||
* @link https://bettersearchreplace.com
|
||||
* @since 1.2
|
||||
*
|
||||
* @package Better_Search_Replace
|
||||
* @subpackage Better_Search_Replace/includes
|
||||
*/
|
||||
|
||||
// Prevent direct access.
|
||||
if ( ! defined( 'BSR_PATH' ) ) exit;
|
||||
|
||||
class BSR_AJAX {
|
||||
|
||||
/**
|
||||
* Sanitized `bsr-ajax` action when `is_authenticated_bsr_ajax_request()` passes.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $authenticated_ajax_action = '';
|
||||
|
||||
/**
|
||||
* Initiate our custom ajax handlers.
|
||||
* @access public
|
||||
*/
|
||||
public function init() {
|
||||
add_action( 'init', array( $this, 'define_ajax' ), 1 );
|
||||
add_action( 'init', array( $this, 'do_bsr_ajax' ), 2 );
|
||||
$this->add_ajax_actions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets our custom endpoint.
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public static function get_endpoint() {
|
||||
return esc_url_raw(
|
||||
add_query_arg(
|
||||
'page',
|
||||
'better-search-replace',
|
||||
admin_url( 'tools.php' )
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom admin AJAX requests must include a valid nonce and capability.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function is_authenticated_bsr_ajax_request() {
|
||||
$this->authenticated_ajax_action = '';
|
||||
|
||||
if ( ! isset( $_REQUEST['bsr_ajax_nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( (string) $_REQUEST['bsr_ajax_nonce'] ) ), 'bsr_ajax_nonce' ) ) {
|
||||
return false;
|
||||
}
|
||||
if ( ! isset( $_REQUEST['bsr-ajax'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$ajax_action = sanitize_text_field( wp_unslash( (string) $_REQUEST['bsr-ajax'] ) );
|
||||
if ( empty( $ajax_action ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! bsr_enabled_for_user() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->authenticated_ajax_action = $ajax_action;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set BSR AJAX constant and headers.
|
||||
* @access public
|
||||
*/
|
||||
public function define_ajax() {
|
||||
|
||||
if ( ! $this->is_authenticated_bsr_ajax_request() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! defined( 'DOING_AJAX' ) ) {
|
||||
define( 'DOING_AJAX', true );
|
||||
}
|
||||
|
||||
if ( ! WP_DEBUG || ( WP_DEBUG && ! WP_DEBUG_DISPLAY ) ) {
|
||||
// phpcs:ignore Squiz.PHP.DiscouragedFunctions.Discouraged
|
||||
@ini_set( 'display_errors', 0 );
|
||||
}
|
||||
|
||||
send_origin_headers();
|
||||
@header( 'Content-Type: text/html; charset=' . get_option( 'blog_charset' ) );
|
||||
@header( 'X-Robots-Tag: noindex' );
|
||||
send_nosniff_header();
|
||||
nocache_headers();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if we're doing AJAX and fire the related action.
|
||||
* @access public
|
||||
*/
|
||||
public function do_bsr_ajax() {
|
||||
global $wp_query;
|
||||
|
||||
if ( ! $this->is_authenticated_bsr_ajax_request() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$wp_query->set( 'bsr-ajax', $this->authenticated_ajax_action );
|
||||
|
||||
if ( $action = $wp_query->get( 'bsr-ajax' ) ) {
|
||||
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Dynamic bsr_ajax_{$action} hooks; literal prefix bsr_ajax_.
|
||||
do_action( 'bsr_ajax_' . sanitize_text_field( $action ) );
|
||||
die();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds any AJAX-related actions.
|
||||
* @access public
|
||||
*/
|
||||
public function add_ajax_actions() {
|
||||
$actions = array(
|
||||
'process_search_replace',
|
||||
);
|
||||
|
||||
foreach ( $actions as $action ) {
|
||||
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Action name built from allowlisted values; prefix bsr_ajax_.
|
||||
add_action( 'bsr_ajax_' . $action, array( $this, $action ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the search/replace form submitted by the user.
|
||||
* @access public
|
||||
*/
|
||||
public function process_search_replace() {
|
||||
if ( ! BSR_Utils::check_admin_referer( 'bsr_ajax_nonce', 'bsr_ajax_nonce', false ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// phpcs:disable WordPress.Security.NonceVerification.Missing,WordPress.Security.NonceVerification.Recommended -- Nonce and capability verified via BSR_Utils::check_admin_referer(); PHPCS does not recognize the wrapper.
|
||||
|
||||
$db = new BSR_DB();
|
||||
$step = isset( $_REQUEST['bsr_step'] ) ? absint( $_REQUEST['bsr_step'] ) : 0;
|
||||
$page = isset( $_REQUEST['bsr_page'] ) ? absint( $_REQUEST['bsr_page'] ) : 0;
|
||||
|
||||
if ( 0 === $step && 0 === $page ) {
|
||||
$args = array();
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.ValidatedSanitizedInput.InputNotValidated -- bsr_data is URL-encoded form body; parsed after nonce; fields validated when applied.
|
||||
$bsr_data_raw = isset( $_REQUEST['bsr_data'] ) ? wp_unslash( (string) $_REQUEST['bsr_data'] ) : '';
|
||||
parse_str( $bsr_data_raw, $args );
|
||||
|
||||
if ( ! isset( $args['select_tables'] ) || ! is_array( $args['select_tables'] ) ) {
|
||||
$args['select_tables'] = array();
|
||||
}
|
||||
|
||||
$args = array(
|
||||
'select_tables' => array_map( 'trim', $args['select_tables'] ),
|
||||
'case_insensitive' => isset( $args['case_insensitive'] ) ? $args['case_insensitive'] : 'off',
|
||||
'replace_guids' => isset( $args['replace_guids'] ) ? $args['replace_guids'] : 'off',
|
||||
'dry_run' => isset( $args['dry_run'] ) ? $args['dry_run'] : 'off',
|
||||
'search_for' => isset( $args['search_for'] ) ? stripslashes( $args['search_for'] ) : '',
|
||||
'replace_with' => isset( $args['replace_with'] ) ? stripslashes( $args['replace_with'] ) : '',
|
||||
'completed_pages' => isset( $args['completed_pages'] ) ? absint( $args['completed_pages'] ) : 0,
|
||||
);
|
||||
|
||||
$args['total_pages'] = isset( $args['total_pages'] ) ? absint( $args['total_pages'] ) : $db->get_total_pages( $args['select_tables'] );
|
||||
|
||||
delete_transient( 'bsr_results' );
|
||||
delete_option( 'bsr_data' );
|
||||
} else {
|
||||
$args = get_option( 'bsr_data' );
|
||||
if ( ! is_array( $args ) ) {
|
||||
$args = array(
|
||||
'select_tables' => array(),
|
||||
'total_pages' => 1,
|
||||
'completed_pages' => 0,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ( isset( $args['select_tables'][ $step ] ) ) {
|
||||
|
||||
$result = $db->srdb( $args['select_tables'][ $step ], $page, $args );
|
||||
$this->append_report( $args['select_tables'][ $step ], $result['table_report'], $args );
|
||||
|
||||
if ( false === $result['table_complete'] ) {
|
||||
++$page;
|
||||
} else {
|
||||
++$step;
|
||||
$page = 0;
|
||||
}
|
||||
|
||||
if ( isset( $args['select_tables'][ $step ] ) ) {
|
||||
$msg_tbl = esc_html( $args['select_tables'][ $step ] );
|
||||
|
||||
$message = sprintf(
|
||||
/* translators: 1: current table number, 2: total number of tables, 3: table name. */
|
||||
__( 'Processing table %1$d of %2$d: %3$s', 'better-search-replace' ),
|
||||
$step + 1,
|
||||
count( $args['select_tables'] ),
|
||||
$msg_tbl
|
||||
);
|
||||
}
|
||||
|
||||
++$args['completed_pages'];
|
||||
$percentage = $args['completed_pages'] / $args['total_pages'] * 100 . '%';
|
||||
|
||||
} else {
|
||||
$db->maybe_update_site_url();
|
||||
$step = 'done';
|
||||
$percentage = '100%';
|
||||
}
|
||||
|
||||
update_option( 'bsr_data', $args );
|
||||
|
||||
$result = array(
|
||||
'step' => $step,
|
||||
'page' => $page,
|
||||
'percentage' => $percentage,
|
||||
'url' => esc_url_raw( BSR_Utils::tools_page_url( array( 'tab' => 'bsr_search_replace', 'result' => 'true' ) ) ),
|
||||
'bsr_data' => build_query( $args ),
|
||||
);
|
||||
|
||||
if ( isset( $message ) ) {
|
||||
$result['message'] = $message;
|
||||
}
|
||||
|
||||
// phpcs:enable WordPress.Security.NonceVerification.Missing,WordPress.Security.NonceVerification.Recommended
|
||||
wp_send_json( $result );
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function for assembling the BSR Results.
|
||||
* @access public
|
||||
* @param string $table The name of the table to append to.
|
||||
* @param array $report The report for that table.
|
||||
* @param array $args An array of arguements used for this run.
|
||||
* @return boolean
|
||||
*/
|
||||
public function append_report( $table, $report, $args ) {
|
||||
|
||||
if ( ! BSR_Utils::check_admin_referer( 'bsr_ajax_nonce', 'bsr_ajax_nonce', false ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$results = get_transient( 'bsr_results' ) ? get_transient( 'bsr_results' ) : array();
|
||||
|
||||
$results['search_for'] = isset( $args['search_for'] ) ? $args['search_for'] : '';
|
||||
$results['replace_with'] = isset( $args['replace_with'] ) ? $args['replace_with'] : '';
|
||||
$results['dry_run'] = isset( $args['dry_run'] ) ? $args['dry_run'] : 'off';
|
||||
$results['case_insensitive'] = isset( $args['case_insensitive'] ) ? $args['case_insensitive'] : 'off';
|
||||
$results['replace_guids'] = isset( $args['replace_guids'] ) ? $args['replace_guids'] : 'off';
|
||||
|
||||
$results['change'] = isset( $results['change'] ) ? $results['change'] + $report['change'] : $report['change'];
|
||||
$results['updates'] = isset( $results['updates'] ) ? $results['updates'] + $report['updates'] : $report['updates'];
|
||||
|
||||
if ( isset( $results['table_reports'] ) && isset( $results['table_reports'][ $table ] ) ) {
|
||||
$results['table_reports'][ $table ]['change'] = $results['table_reports'][ $table ]['change'] + $report['change'];
|
||||
$results['table_reports'][ $table ]['updates'] = $results['table_reports'][ $table ]['updates'] + $report['updates'];
|
||||
$results['table_reports'][ $table ]['end'] = $report['end'];
|
||||
} else {
|
||||
$results['table_reports'][ $table ] = $report;
|
||||
}
|
||||
|
||||
$results['tables'] = count( $results['table_reports'] );
|
||||
|
||||
if ( ! set_transient( 'bsr_results', $results, DAY_IN_SECONDS ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// phpcs:disable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound
|
||||
$bsr_ajax = new BSR_AJAX();
|
||||
$bsr_ajax->init();
|
||||
// phpcs:enable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound
|
||||
@@ -0,0 +1,128 @@
|
||||
<?php
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes compatibility functionality.
|
||||
* @since 1.0
|
||||
*
|
||||
* @package Better_Search_Replace
|
||||
* @subpackage Better_Search_Replace/includes
|
||||
*/
|
||||
|
||||
// Prevent direct access.
|
||||
if ( ! defined( 'BSR_PATH' ) ) exit;
|
||||
|
||||
class BSR_Compatibility {
|
||||
|
||||
/**
|
||||
* Returns the system info.
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public static function get_sysinfo() {
|
||||
|
||||
global $wpdb;
|
||||
|
||||
$return = '### Begin System Info ###' . "\n\n";
|
||||
|
||||
// Basic site info
|
||||
$return .= '-- WordPress Configuration' . "\n\n";
|
||||
$return .= 'Site URL: ' . site_url() . "\n";
|
||||
$return .= 'Home URL: ' . home_url() . "\n";
|
||||
$return .= 'Multisite: ' . ( is_multisite() ? 'Yes' : 'No' ) . "\n";
|
||||
$return .= 'Version: ' . get_bloginfo( 'version' ) . "\n";
|
||||
$return .= 'Language: ' . get_locale() . "\n";
|
||||
$return .= 'Table Prefix: ' . 'Length: ' . strlen( $wpdb->prefix ) . "\n";
|
||||
$return .= 'WP_DEBUG: ' . ( defined( 'WP_DEBUG' ) ? WP_DEBUG ? 'Enabled' : 'Disabled' : 'Not set' ) . "\n";
|
||||
$return .= 'Memory Limit: ' . WP_MEMORY_LIMIT . "\n";
|
||||
|
||||
// Plugin Configuration
|
||||
$return .= "\n" . '-- Better Search Replace Configuration' . "\n\n";
|
||||
$return .= 'Plugin Version: ' . BSR_VERSION . "\n";
|
||||
$db = new BSR_DB();
|
||||
$return .= 'Max Page Size: ' . $db->get_page_size() . "\n";
|
||||
|
||||
// Server Configuration
|
||||
$return .= "\n" . '-- Server Configuration' . "\n\n";
|
||||
$os = self::get_os();
|
||||
$return .= 'Operating System: ' . $os['name'] . "\n";
|
||||
$return .= 'PHP Version: ' . PHP_VERSION . "\n";
|
||||
$return .= 'MySQL Version: ' . $wpdb->db_version() . "\n";
|
||||
|
||||
$return .= 'Server Software: ' . self::get_server_software_for_display() . "\n";
|
||||
|
||||
// PHP configs... now we're getting to the important stuff
|
||||
$return .= "\n" . '-- PHP Configuration' . "\n\n";
|
||||
$return .= 'Memory Limit: ' . ini_get( 'memory_limit' ) . "\n";
|
||||
$return .= 'Post Max Size: ' . ini_get( 'post_max_size' ) . "\n";
|
||||
$return .= 'Upload Max Filesize: ' . ini_get( 'upload_max_filesize' ) . "\n";
|
||||
$return .= 'Time Limit: ' . ini_get( 'max_execution_time' ) . "\n";
|
||||
$return .= 'Max Input Vars: ' . ini_get( 'max_input_vars' ) . "\n";
|
||||
$return .= 'Display Errors: ' . ( ini_get( 'display_errors' ) ? 'On (' . ini_get( 'display_errors' ) . ')' : 'N/A' ) . "\n";
|
||||
|
||||
// WordPress active plugins
|
||||
$return .= "\n" . '-- WordPress Active Plugins' . "\n\n";
|
||||
$plugins = get_plugins();
|
||||
$active_plugins = get_option( 'active_plugins', array() );
|
||||
foreach( $plugins as $plugin_path => $plugin ) {
|
||||
if( !in_array( $plugin_path, $active_plugins ) )
|
||||
continue;
|
||||
$return .= $plugin['Name'] . ': ' . $plugin['Version'] . "\n";
|
||||
}
|
||||
|
||||
// WordPress inactive plugins
|
||||
$return .= "\n" . '-- WordPress Inactive Plugins' . "\n\n";
|
||||
foreach( $plugins as $plugin_path => $plugin ) {
|
||||
if( in_array( $plugin_path, $active_plugins ) )
|
||||
continue;
|
||||
$return .= $plugin['Name'] . ': ' . $plugin['Version'] . "\n";
|
||||
}
|
||||
|
||||
if( is_multisite() ) {
|
||||
// WordPress Multisite active plugins
|
||||
$return .= "\n" . '-- Network Active Plugins' . "\n\n";
|
||||
$plugins = wp_get_active_network_plugins();
|
||||
$active_plugins = get_site_option( 'active_sitewide_plugins', array() );
|
||||
foreach( $plugins as $plugin_path ) {
|
||||
$plugin_base = plugin_basename( $plugin_path );
|
||||
if( !array_key_exists( $plugin_base, $active_plugins ) )
|
||||
continue;
|
||||
$plugin = get_plugin_data( $plugin_path );
|
||||
$return .= $plugin['Name'] . ': ' . $plugin['Version'] . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
$return .= "\n" . '### End System Info ###';
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Server software string for system info (safe for display in plain-text export).
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private static function get_server_software_for_display() {
|
||||
if ( ! isset( $_SERVER['SERVER_SOFTWARE'] ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return sanitize_text_field( wp_unslash( (string) $_SERVER['SERVER_SOFTWARE'] ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the current operating system.
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public static function get_os() {
|
||||
$os = array();
|
||||
$uname = php_uname( 's' );
|
||||
$os['code'] = strtoupper( substr( $uname, 0, 3 ) );
|
||||
$os['name'] = $uname;
|
||||
return $os;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,536 @@
|
||||
<?php
|
||||
/**
|
||||
* Processes database-related functionality.
|
||||
* @since 1.0
|
||||
*
|
||||
* @package Better_Search_Replace
|
||||
* @subpackage Better_Search_Replace/includes
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
// Prevent direct access.
|
||||
if ( ! defined( 'BSR_PATH' ) ) exit;
|
||||
|
||||
class BSR_DB {
|
||||
|
||||
/**
|
||||
* The page size used throughout the plugin.
|
||||
* @var int
|
||||
*/
|
||||
public $page_size;
|
||||
|
||||
/**
|
||||
* The name of the backup file.
|
||||
* @var string
|
||||
*/
|
||||
public $file;
|
||||
|
||||
/**
|
||||
* The WordPress database class.
|
||||
* @var WPDB
|
||||
*/
|
||||
private $wpdb;
|
||||
|
||||
/**
|
||||
* Initializes the class and its properties.
|
||||
* @access public
|
||||
*/
|
||||
public function __construct() {
|
||||
|
||||
global $wpdb;
|
||||
$this->wpdb = $wpdb;
|
||||
|
||||
$this->page_size = $this->get_page_size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of tables in the database.
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public static function get_tables() {
|
||||
global $wpdb;
|
||||
|
||||
// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Schema discovery; not suitable for long-lived object cache.
|
||||
if ( function_exists( 'is_multisite' ) && is_multisite() ) {
|
||||
|
||||
if ( is_main_site() ) {
|
||||
$tables = $wpdb->get_col( 'SHOW TABLES' );
|
||||
} else {
|
||||
$blog_id = get_current_blog_id();
|
||||
$tables = $wpdb->get_col( $wpdb->prepare( 'SHOW TABLES LIKE %s', $wpdb->base_prefix . absint( $blog_id ) . '\_%' ) );
|
||||
}
|
||||
} else {
|
||||
$tables = $wpdb->get_col( 'SHOW TABLES' );
|
||||
}
|
||||
// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
|
||||
|
||||
return $tables;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array containing the size of each database table.
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public static function get_sizes() {
|
||||
global $wpdb;
|
||||
|
||||
$sizes = array();
|
||||
// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Server metadata for admin display only.
|
||||
$tables = $wpdb->get_results( 'SHOW TABLE STATUS', ARRAY_A );
|
||||
// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
|
||||
|
||||
if ( is_array( $tables ) && ! empty( $tables ) ) {
|
||||
|
||||
foreach ( $tables as $table ) {
|
||||
$size = round( $table['Data_length'] / 1024 / 1024, 2 );
|
||||
$sizes[ $table['Name'] ] = sprintf(
|
||||
/* translators: %s: table size in megabytes. */
|
||||
__( '(%s MB)', 'better-search-replace' ),
|
||||
$size
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $sizes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current page size.
|
||||
* @access public
|
||||
* @return int
|
||||
*/
|
||||
public function get_page_size() {
|
||||
$page_size = get_option( 'bsr_page_size' ) ? get_option( 'bsr_page_size' ) : 20000;
|
||||
return absint( $page_size );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of pages in a table.
|
||||
* @access public
|
||||
* @return int
|
||||
*/
|
||||
public function get_pages_in_table( $table ) {
|
||||
if ( false === $this->table_exists( $table ) ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$wpdb = $this->wpdb;
|
||||
// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Row count on allowlisted table name; not suitable for object cache.
|
||||
$rows = $wpdb->get_var( $wpdb->prepare( 'SELECT COUNT(*) FROM %i', $table ) );
|
||||
// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
|
||||
|
||||
$pages = ceil( $rows / $this->page_size );
|
||||
|
||||
return absint( $pages );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the total number of pages in the DB.
|
||||
* @access public
|
||||
* @return int
|
||||
*/
|
||||
public function get_total_pages( $tables ) {
|
||||
$total_pages = 0;
|
||||
|
||||
foreach ( $tables as $table ) {
|
||||
|
||||
// Get the number of rows & pages in the table.
|
||||
$pages = $this->get_pages_in_table( $table );
|
||||
|
||||
// Always include 1 page in case we have to create schemas, etc.
|
||||
if ( $pages == 0 ) {
|
||||
$pages = 1;
|
||||
}
|
||||
|
||||
$total_pages += $pages;
|
||||
}
|
||||
|
||||
return absint( $total_pages );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the columns in a table and its primary key column names (composite keys supported).
|
||||
*
|
||||
* @access public
|
||||
* @param string $table The table to check.
|
||||
* @return array First element: list of primary key column names (DESCRIBE order). Second: all column names.
|
||||
*/
|
||||
public function get_columns( $table ) {
|
||||
$primary_keys = null;
|
||||
$columns = array();
|
||||
|
||||
if ( false === $this->table_exists( $table ) ) {
|
||||
return array( $primary_keys, $columns );
|
||||
}
|
||||
|
||||
$wpdb = $this->wpdb;
|
||||
// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared,PluginCheck.Security.DirectDB.UnescapedDBParameter -- DESCRIBE on allowlisted table for SRDB column list.
|
||||
$fields = $wpdb->get_results( $wpdb->prepare( 'DESCRIBE %i', $table ) );
|
||||
// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared,PluginCheck.Security.DirectDB.UnescapedDBParameter
|
||||
|
||||
if ( is_array( $fields ) ) {
|
||||
foreach ( $fields as $column ) {
|
||||
$columns[] = $column->Field;
|
||||
if ( 'PRI' == $column->Key ) {
|
||||
$primary_keys[] = $column->Field;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return array( $primary_keys, $columns );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adapated from interconnect/it's search/replace script.
|
||||
*
|
||||
* Modified to use WordPress wpdb functions instead of PHP's native mysql/pdo functions,
|
||||
* and to be compatible with batch processing via AJAX.
|
||||
*
|
||||
* @link https://interconnectit.com/products/search-and-replace-for-wordpress-databases/
|
||||
*
|
||||
* @access public
|
||||
* @param string $table The table to run the replacement on.
|
||||
* @param int $page The page/block to begin the query on.
|
||||
* @param array $args An associative array containing arguements for this run.
|
||||
* @return array
|
||||
*/
|
||||
public function srdb( $table, $page, $args ) {
|
||||
|
||||
if ( ! $this->table_exists( $table ) ) {
|
||||
$table_report = array(
|
||||
'change' => 0,
|
||||
'updates' => 0,
|
||||
'start' => microtime( true ),
|
||||
'end' => microtime( true ),
|
||||
'errors' => array(),
|
||||
'skipped' => true,
|
||||
);
|
||||
return array( 'table_complete' => true, 'table_report' => $table_report );
|
||||
}
|
||||
|
||||
$wpdb = $this->wpdb;
|
||||
|
||||
$current_page = absint( $page );
|
||||
$pages = $this->get_pages_in_table( $table );
|
||||
$done = false;
|
||||
|
||||
$args['search_for'] = str_replace( '#BSR_BACKSLASH#', '\\', $args['search_for'] );
|
||||
$args['replace_with'] = str_replace( '#BSR_BACKSLASH#', '\\', $args['replace_with'] );
|
||||
|
||||
$table_report = array(
|
||||
'change' => 0,
|
||||
'updates' => 0,
|
||||
'start' => microtime( true ),
|
||||
'end' => microtime( true ),
|
||||
'errors' => array(),
|
||||
'skipped' => false,
|
||||
);
|
||||
|
||||
// Get a list of columns in this table.
|
||||
list( $primary_keys, $columns ) = $this->get_columns( $table );
|
||||
|
||||
// Bail out early if there isn't a primary key.
|
||||
if ( empty( $primary_keys ) ) {
|
||||
$table_report['skipped'] = true;
|
||||
return array( 'table_complete' => true, 'table_report' => $table_report );
|
||||
}
|
||||
|
||||
$current_row = 0;
|
||||
$start = $page * $this->page_size;
|
||||
$end = $this->page_size;
|
||||
|
||||
// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared,PluginCheck.Security.DirectDB.UnescapedDBParameter -- Paged read of allowlisted table for batch search/replace.
|
||||
$data = $wpdb->get_results(
|
||||
$wpdb->prepare(
|
||||
'SELECT * FROM %i LIMIT %d, %d',
|
||||
$table,
|
||||
$start,
|
||||
$end
|
||||
),
|
||||
ARRAY_A
|
||||
);
|
||||
// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared,PluginCheck.Security.DirectDB.UnescapedDBParameter
|
||||
|
||||
foreach ( $data as $row ) {
|
||||
$current_row++;
|
||||
$update_sql = array();
|
||||
$where_sql = array();
|
||||
$update_values = array();
|
||||
$where_values = array();
|
||||
$upd = false;
|
||||
|
||||
foreach ( $columns as $column ) {
|
||||
|
||||
$data_to_fix = $row[ $column ];
|
||||
|
||||
if ( in_array( $column, $primary_keys, true ) ) {
|
||||
$where_sql[] = '%i = %s';
|
||||
$where_values[] = $column;
|
||||
$where_values[] = $data_to_fix;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip GUIDs by default.
|
||||
if ( 'on' !== $args['replace_guids'] && 'guid' == $column ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( $wpdb->options === $table ) {
|
||||
|
||||
// Skip any BSR options as they may contain the search field.
|
||||
if ( isset( $should_skip ) && true === $should_skip ) {
|
||||
$should_skip = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the Site URL needs to be updated, let's do that last.
|
||||
if ( isset( $update_later ) && true === $update_later ) {
|
||||
$update_later = false;
|
||||
$edited_data = $this->recursive_unserialize_replace( $args['search_for'], $args['replace_with'], $data_to_fix, false, $args['case_insensitive'] );
|
||||
|
||||
if ( $edited_data != $data_to_fix ) {
|
||||
$table_report['change']++;
|
||||
$table_report['updates']++;
|
||||
update_option( 'bsr_update_site_url', $edited_data );
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if ( '_transient_bsr_results' === $data_to_fix || 'bsr_profiles' === $data_to_fix || 'bsr_update_site_url' === $data_to_fix || 'bsr_data' === $data_to_fix ) {
|
||||
$should_skip = true;
|
||||
}
|
||||
|
||||
if ( 'siteurl' === $data_to_fix && $args['dry_run'] !== 'on' ) {
|
||||
$update_later = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Run a search replace on the data that'll respect the serialisation.
|
||||
$edited_data = $this->recursive_unserialize_replace( $args['search_for'], $args['replace_with'], $data_to_fix, false, $args['case_insensitive'] );
|
||||
|
||||
// Something was changed
|
||||
if ( $edited_data != $data_to_fix ) {
|
||||
$update_sql[] = '%i = %s';
|
||||
$update_values[] = $column;
|
||||
$update_values[] = $edited_data;
|
||||
$upd = true;
|
||||
$table_report['change']++;
|
||||
}
|
||||
}
|
||||
|
||||
// Determine what to do with updates.
|
||||
if ( $args['dry_run'] === 'on' ) {
|
||||
// Don't do anything if a dry run
|
||||
} elseif ( $upd && ! empty( $where_sql ) ) {
|
||||
// If there are changes to make, run the query.
|
||||
$sql_template = 'UPDATE %i SET ' . implode( ', ', $update_sql ) . ' WHERE ' . implode( ' AND ', array_filter( $where_sql ) );
|
||||
$prepare_args = array_merge( array( $sql_template, $table ), $update_values, $where_values );
|
||||
|
||||
// phpcs:disable WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.DirectDatabaseQuery.SchemaChange -- Dynamic UPDATE: table and columns allowlisted; values passed through $wpdb->prepare().
|
||||
$result = $wpdb->query( call_user_func_array( array( $wpdb, 'prepare' ), $prepare_args ) );
|
||||
// phpcs:enable WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.DirectDatabaseQuery.SchemaChange
|
||||
|
||||
if ( ! $result ) {
|
||||
$table_report['errors'][] = sprintf(
|
||||
/* translators: %d: database row number that failed to update. */
|
||||
__( 'Error updating row: %d.', 'better-search-replace' ),
|
||||
$current_row
|
||||
);
|
||||
} else {
|
||||
$table_report['updates']++;
|
||||
}
|
||||
}
|
||||
} // end row loop
|
||||
|
||||
if ( $current_page >= $pages - 1 ) {
|
||||
$done = true;
|
||||
}
|
||||
|
||||
$table_report['end'] = microtime( true );
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Clear wpdb result buffers after batch SRDB.
|
||||
$wpdb->flush();
|
||||
|
||||
return array( 'table_complete' => $done, 'table_report' => $table_report );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adapated from interconnect/it's search/replace script.
|
||||
*
|
||||
* @link https://interconnectit.com/products/search-and-replace-for-wordpress-databases/
|
||||
*
|
||||
* Take a serialised array and unserialise it replacing elements as needed and
|
||||
* unserialising any subordinate arrays and performing the replace on those too.
|
||||
*
|
||||
* @access private
|
||||
* @param string $from String we're looking to replace.
|
||||
* @param string $to What we want it to be replaced with
|
||||
* @param array $data Used to pass any subordinate arrays back to in.
|
||||
* @param boolean $serialised Does the array passed via $data need serialising.
|
||||
* @param sting|boolean $case_insensitive Set to 'on' if we should ignore case, false otherwise.
|
||||
*
|
||||
* @return string|array The original array with all elements replaced as needed.
|
||||
*/
|
||||
public function recursive_unserialize_replace( $from = '', $to = '', $data = '', $serialised = false, $case_insensitive = false ) {
|
||||
try {
|
||||
// Exit early if $data is a string but has no search matches.
|
||||
if ( is_string( $data ) ) {
|
||||
$has_match = $case_insensitive ? false !== stripos( $data, $from ) : false !== strpos( $data, $from );
|
||||
if ( ! $has_match ) {
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
|
||||
if ( is_string( $data ) && ! is_serialized_string( $data ) && ( $unserialized = $this->unserialize( $data ) ) !== false ) {
|
||||
$data = $this->recursive_unserialize_replace( $from, $to, $unserialized, true, $case_insensitive );
|
||||
}
|
||||
|
||||
elseif ( is_array( $data ) ) {
|
||||
$_tmp = array( );
|
||||
foreach ( $data as $key => $value ) {
|
||||
$_tmp[ $key ] = $this->recursive_unserialize_replace( $from, $to, $value, false, $case_insensitive );
|
||||
}
|
||||
|
||||
$data = $_tmp;
|
||||
unset( $_tmp );
|
||||
}
|
||||
|
||||
// Submitted by Tina Matter
|
||||
elseif ( 'object' == gettype( $data ) ) {
|
||||
if($this->is_object_cloneable($data)) {
|
||||
$_tmp = clone $data;
|
||||
$props = get_object_vars( $data );
|
||||
foreach ( $props as $key => $value ) {
|
||||
// Integer properties are crazy and the best thing we can do is to just ignore them.
|
||||
// see http://stackoverflow.com/a/10333200
|
||||
if ( is_int( $key ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip any representation of a protected property
|
||||
// https://github.com/deliciousbrains/better-search-replace/issues/71#issuecomment-1369195244
|
||||
if ( is_string( $key ) && 1 === preg_match( "/^(\\\\0).+/im", preg_quote( $key ) ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$_tmp->$key = $this->recursive_unserialize_replace( $from, $to, $value, false, $case_insensitive );
|
||||
}
|
||||
|
||||
$data = $_tmp;
|
||||
unset( $_tmp );
|
||||
}
|
||||
}
|
||||
|
||||
elseif ( is_serialized_string( $data ) ) {
|
||||
$unserialized = $this->unserialize( $data );
|
||||
|
||||
if ( $unserialized !== false ) {
|
||||
$data = $this->recursive_unserialize_replace( $from, $to, $unserialized, true, $case_insensitive );
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
if ( is_string( $data ) ) {
|
||||
$data = $this->str_replace( $from, $to, $data, $case_insensitive );
|
||||
}
|
||||
}
|
||||
|
||||
if ( $serialised ) {
|
||||
return serialize( $data );
|
||||
}
|
||||
|
||||
} catch( Exception $error ) {
|
||||
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the Site URL if necessary.
|
||||
* @access public
|
||||
* @return boolean
|
||||
*/
|
||||
public function maybe_update_site_url() {
|
||||
$option = get_option( 'bsr_update_site_url' );
|
||||
|
||||
if ( $option ) {
|
||||
update_option( 'siteurl', $option );
|
||||
delete_option( 'bsr_update_site_url' );
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return unserialized object or array
|
||||
*
|
||||
* @param string $serialized_string Serialized string.
|
||||
* @param string $method The name of the caller method.
|
||||
*
|
||||
* @return mixed, false on failure
|
||||
*/
|
||||
public static function unserialize( $serialized_string ) {
|
||||
if ( ! is_serialized( $serialized_string ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$serialized_string = trim( $serialized_string );
|
||||
|
||||
if ( PHP_VERSION_ID >= 70000 ) {
|
||||
$unserialized_string = @unserialize( $serialized_string, array('allowed_classes' => false ) );
|
||||
} else {
|
||||
$unserialized_string = @BSR\Brumann\Polyfill\Unserialize::unserialize( $serialized_string, array( 'allowed_classes' => false ) );
|
||||
}
|
||||
|
||||
return $unserialized_string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for str_replace
|
||||
*
|
||||
* @param string $from
|
||||
* @param string $to
|
||||
* @param string $data
|
||||
* @param string|bool $case_insensitive
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function str_replace( $from, $to, $data, $case_insensitive = false ) {
|
||||
if ( 'on' === $case_insensitive ) {
|
||||
$data = str_ireplace( $from, $to, $data );
|
||||
} else {
|
||||
$data = str_replace( $from, $to, $data );
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a table exists in DB.
|
||||
*
|
||||
* @param $table
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function table_exists( $table ) {
|
||||
return in_array( $table, self::get_tables(), true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given object can be cloned.
|
||||
*
|
||||
* @param object $object
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function is_object_cloneable( $object ) {
|
||||
return ( new \ReflectionClass( get_class( $object ) ) )->isCloneable();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
<?php
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register all actions and filters for the plugin
|
||||
*
|
||||
* @link https://bettersearchreplace.com
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @package Better_Search_Replace
|
||||
* @subpackage Better_Search_Replace/includes
|
||||
*/
|
||||
|
||||
// Prevent direct access.
|
||||
if ( ! defined( 'BSR_PATH' ) ) exit;
|
||||
|
||||
/**
|
||||
* Register all actions and filters for the plugin.
|
||||
*
|
||||
* Maintain a list of all hooks that are registered throughout
|
||||
* the plugin, and register them with the WordPress API. Call the
|
||||
* run function to execute the list of actions and filters.
|
||||
*
|
||||
* @package Better_Search_Replace
|
||||
* @subpackage Better_Search_Replace/includes
|
||||
*/
|
||||
class BSR_Loader {
|
||||
|
||||
/**
|
||||
* The array of actions registered with WordPress.
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @access protected
|
||||
* @var array $actions The actions registered with WordPress to fire when the plugin loads.
|
||||
*/
|
||||
protected $actions;
|
||||
|
||||
/**
|
||||
* The array of filters registered with WordPress.
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @access protected
|
||||
* @var array $filters The filters registered with WordPress to fire when the plugin loads.
|
||||
*/
|
||||
protected $filters;
|
||||
|
||||
/**
|
||||
* Initialize the collections used to maintain the actions and filters.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function __construct() {
|
||||
|
||||
$this->actions = array();
|
||||
$this->filters = array();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new action to the collection to be registered with WordPress.
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @var string $hook The name of the WordPress action that is being registered.
|
||||
* @var object $component A reference to the instance of the object on which the action is defined.
|
||||
* @var string $callback The name of the function definition on the $component.
|
||||
* @var int Optional $priority The priority at which the function should be fired.
|
||||
* @var int Optional $accepted_args The number of arguments that should be passed to the $callback.
|
||||
*/
|
||||
public function add_action( $hook, $component, $callback, $priority = 10, $accepted_args = 1 ) {
|
||||
$this->actions = $this->add( $this->actions, $hook, $component, $callback, $priority, $accepted_args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new filter to the collection to be registered with WordPress.
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @var string $hook The name of the WordPress filter that is being registered.
|
||||
* @var object $component A reference to the instance of the object on which the filter is defined.
|
||||
* @var string $callback The name of the function definition on the $component.
|
||||
* @var int Optional $priority The priority at which the function should be fired.
|
||||
* @var int Optional $accepted_args The number of arguments that should be passed to the $callback.
|
||||
*/
|
||||
public function add_filter( $hook, $component, $callback, $priority = 10, $accepted_args = 1 ) {
|
||||
$this->filters = $this->add( $this->filters, $hook, $component, $callback, $priority, $accepted_args );
|
||||
}
|
||||
|
||||
/**
|
||||
* A utility function that is used to register the actions and hooks into a single
|
||||
* collection.
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @access private
|
||||
* @var array $hooks The collection of hooks that is being registered (that is, actions or filters).
|
||||
* @var string $hook The name of the WordPress filter that is being registered.
|
||||
* @var object $component A reference to the instance of the object on which the filter is defined.
|
||||
* @var string $callback The name of the function definition on the $component.
|
||||
* @var int Optional $priority The priority at which the function should be fired.
|
||||
* @var int Optional $accepted_args The number of arguments that should be passed to the $callback.
|
||||
* @return type The collection of actions and filters registered with WordPress.
|
||||
*/
|
||||
private function add( $hooks, $hook, $component, $callback, $priority, $accepted_args ) {
|
||||
|
||||
$hooks[] = array(
|
||||
'hook' => $hook,
|
||||
'component' => $component,
|
||||
'callback' => $callback,
|
||||
'priority' => $priority,
|
||||
'accepted_args' => $accepted_args
|
||||
);
|
||||
|
||||
return $hooks;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the filters and actions with WordPress.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function run() {
|
||||
|
||||
foreach ( $this->filters as $hook ) {
|
||||
add_filter( $hook['hook'], array( $hook['component'], $hook['callback'] ), $hook['priority'], $hook['accepted_args'] );
|
||||
}
|
||||
|
||||
foreach ( $this->actions as $hook ) {
|
||||
add_action( $hook['hook'], array( $hook['component'], $hook['callback'] ), $hook['priority'], $hook['accepted_args'] );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
<?php
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* The core plugin class.
|
||||
*
|
||||
* This is used to define internationalization, dashboard-specific hooks, and
|
||||
* public-facing site hooks.
|
||||
*
|
||||
* Also maintains the unique identifier of this plugin as well as the current
|
||||
* version of the plugin.
|
||||
*
|
||||
* @since 1.0
|
||||
* @package Better_Search_Replace
|
||||
* @subpackage Better_Search_Replace/includes
|
||||
*/
|
||||
|
||||
// Prevent direct access.
|
||||
if ( ! defined( 'BSR_PATH' ) ) exit;
|
||||
|
||||
class Better_Search_Replace {
|
||||
|
||||
/**
|
||||
* The loader that's responsible for maintaining and registering all hooks that power
|
||||
* the plugin.
|
||||
*
|
||||
* @since 1.0
|
||||
* @access protected
|
||||
* @var BSR_Loader $loader Maintains and registers all hooks for the plugin.
|
||||
*/
|
||||
protected $loader;
|
||||
|
||||
/**
|
||||
* The unique identifier of this plugin.
|
||||
*
|
||||
* @since 1.0
|
||||
* @access protected
|
||||
* @var string $plugin_name The string used to uniquely identify this plugin.
|
||||
*/
|
||||
protected $plugin_name;
|
||||
|
||||
/**
|
||||
* The current version of the plugin.
|
||||
*
|
||||
* @since 1.0
|
||||
* @access protected
|
||||
* @var string $version The current version of the plugin.
|
||||
*/
|
||||
protected $version;
|
||||
|
||||
/**
|
||||
* Define the core functionality of the plugin.
|
||||
*
|
||||
* Set the plugin name and the plugin version that can be used throughout the plugin.
|
||||
* Load the dependencies, define the locale, and set the hooks for the Dashboard and
|
||||
* the public-facing side of the site.
|
||||
*
|
||||
* @since 1.0
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->plugin_name = 'better-search-replace';
|
||||
$this->version = BSR_VERSION;
|
||||
$this->load_dependencies();
|
||||
$this->define_admin_hooks();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the required dependencies for this plugin.
|
||||
*
|
||||
* Create an instance of the loader which will be used to register the hooks
|
||||
* with WordPress.
|
||||
*
|
||||
* @since 1.0
|
||||
* @access private
|
||||
*/
|
||||
private function load_dependencies() {
|
||||
require_once BSR_PATH . 'includes/class-bsr-loader.php';
|
||||
require_once BSR_PATH . 'includes/class-bsr-admin.php';
|
||||
require_once BSR_PATH . 'includes/class-bsr-ajax.php';
|
||||
require_once BSR_PATH . 'includes/class-bsr-db.php';
|
||||
require_once BSR_PATH . 'includes/class-bsr-compatibility.php';
|
||||
require_once BSR_PATH . 'includes/class-bsr-plugin-footer.php';
|
||||
require_once BSR_PATH . 'includes/class-bsr-utils.php';
|
||||
|
||||
if ( PHP_VERSION_ID < 70000 ) {
|
||||
require_once BSR_PATH . 'vendor/brumann/polyfill-unserialize/src/Unserialize.php';
|
||||
require_once BSR_PATH . 'vendor/brumann/polyfill-unserialize/src/DisallowedClassesSubstitutor.php';
|
||||
}
|
||||
|
||||
$this->loader = new BSR_Loader();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register all of the hooks related to the dashboard functionality
|
||||
* of the plugin.
|
||||
*
|
||||
* @since 1.0
|
||||
* @access private
|
||||
*/
|
||||
private function define_admin_hooks() {
|
||||
|
||||
// Initialize the admin class.
|
||||
$plugin_admin = new BSR_Admin( $this->get_plugin_name(), $this->get_version() );
|
||||
$plugin_footer = new BSR_Plugin_Footer();
|
||||
|
||||
/// Register the admin pages and scripts.
|
||||
$this->loader->add_action( 'admin_enqueue_scripts', $plugin_admin, 'enqueue_scripts' );
|
||||
$this->loader->add_action( 'admin_menu', $plugin_admin, 'bsr_menu_pages' );
|
||||
|
||||
// Other admin actions.
|
||||
$this->loader->add_action( 'admin_init', $plugin_admin, 'register_option' );
|
||||
$this->loader->add_action( 'admin_post_bsr_view_details', $plugin_admin, 'load_details' );
|
||||
$this->loader->add_action( 'admin_post_bsr_download_sysinfo', $plugin_admin, 'download_sysinfo' );
|
||||
$this->loader->add_action( 'plugin_row_meta', $plugin_admin, 'meta_upgrade_link', 10, 2 );
|
||||
|
||||
// Footer Actions
|
||||
$this->loader->add_filter( 'update_footer', $plugin_footer, 'update_footer', 20);
|
||||
$this->loader->add_filter( 'admin_footer_text', $plugin_footer, 'admin_footer_text', 20);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the loader to execute all of the hooks with WordPress.
|
||||
*
|
||||
* @since 1.0
|
||||
*/
|
||||
public function run() {
|
||||
$this->loader->run();
|
||||
}
|
||||
|
||||
/**
|
||||
* The name of the plugin used to uniquely identify it within the context of
|
||||
* WordPress and to define internationalization functionality.
|
||||
*
|
||||
* @since 1.0
|
||||
* @return string The name of the plugin.
|
||||
*/
|
||||
public function get_plugin_name() {
|
||||
return $this->plugin_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* The reference to the class that orchestrates the hooks with the plugin.
|
||||
*
|
||||
* @since 1.0
|
||||
* @return Better_Search_Replace_Loader Orchestrates the hooks of the plugin.
|
||||
*/
|
||||
public function get_loader() {
|
||||
return $this->loader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the version number of the plugin.
|
||||
*
|
||||
* @since 1.0
|
||||
* @return string The version number of the plugin.
|
||||
*/
|
||||
public function get_version() {
|
||||
return $this->version;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Plugin footer functionality for the plugin
|
||||
*
|
||||
* @since 1.4.3
|
||||
*
|
||||
* @package Better_Search_Replace
|
||||
* @subpackage Better_Search_Replace/includes
|
||||
*/
|
||||
// Prevent direct access.
|
||||
if ( ! defined( 'BSR_PATH' ) ) exit;
|
||||
|
||||
class BSR_Plugin_Footer {
|
||||
|
||||
/**
|
||||
* Filter admin footer text for BSR pages
|
||||
*
|
||||
* @param string $text
|
||||
* @return string
|
||||
* @handles admin_footer_text
|
||||
**/
|
||||
public function admin_footer_text( $text ) {
|
||||
if ( ! BSR_Utils::is_bsr_screen() ) {
|
||||
return $text;
|
||||
}
|
||||
|
||||
$product_link = BSR_Utils::external_link(
|
||||
BSR_Utils::bsr_url(
|
||||
'',
|
||||
[
|
||||
'utm_source' => 'bsr_free',
|
||||
'utm_medium' => 'insideplugin',
|
||||
'utm_campaign' => 'plugin_footer',
|
||||
'utm_content' => 'footer_colophon'
|
||||
]
|
||||
),
|
||||
BSR_NAME
|
||||
);
|
||||
$wpe_link = BSR_Utils::external_link(
|
||||
BSR_Utils::wpe_url(
|
||||
'',
|
||||
[
|
||||
'utm_source' => 'bsr_plugin',
|
||||
'utm_content' => 'bsr_free_plugin_footer_text'
|
||||
]
|
||||
),
|
||||
'WP Engine'
|
||||
);
|
||||
return sprintf(
|
||||
/* translators: %1$s is a link to BSR's website, and %2$s is a link to WP Engine's website. */
|
||||
__( '%1$s is developed and maintained by %2$s.', 'better-search-replace' ),
|
||||
$product_link,
|
||||
$wpe_link
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter update footer text for BSR pages
|
||||
*
|
||||
* @param string $content
|
||||
* @return string
|
||||
* @handles update_footer
|
||||
**/
|
||||
public function update_footer( $content ) {
|
||||
if ( ! BSR_Utils::is_bsr_screen() ) {
|
||||
return $content;
|
||||
}
|
||||
$utm_params = [
|
||||
'utm_source' => 'bsr_free',
|
||||
'utm_campaign' => 'plugin_footer',
|
||||
'utm_content' => 'footer_navigation'
|
||||
];
|
||||
|
||||
$links[] = BSR_Utils::external_link(
|
||||
BSR_Utils::bsr_url(
|
||||
'/docs/',
|
||||
$utm_params
|
||||
),
|
||||
__( 'Documentation', 'better-search-replace' )
|
||||
);
|
||||
|
||||
$links[] = '<a href="' . BSR_Utils::plugin_page_url() . '&tab=bsr_help">' . __( 'Support', 'better-search-replace' ) . '</a>';
|
||||
|
||||
$links[] = BSR_Utils::external_link(
|
||||
BSR_Utils::bsr_url(
|
||||
'/feedback/',
|
||||
$utm_params
|
||||
),
|
||||
__( 'Feedback', 'better-search-replace' )
|
||||
);
|
||||
if ( defined( 'BSR_NAME' ) && defined( 'BSR_VERSION' ) ) {
|
||||
$links[] = BSR_NAME . ' ' . BSR_VERSION;
|
||||
}
|
||||
|
||||
return join( ' ∙ ', $links );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
// Prevent direct access.
|
||||
if ( ! defined( 'BSR_PATH' ) ) exit;
|
||||
|
||||
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedClassFound -- Historical class name; used by admin templates.
|
||||
class BSR_Templates_Helper {
|
||||
/**
|
||||
* Returns a fully qualified path for the given active tab name
|
||||
* if the file name is not supported, the default template path is returned.
|
||||
*
|
||||
* @param string $active_tab
|
||||
* @return string
|
||||
*/
|
||||
public static function get_tab_template($active_tab) {
|
||||
switch($active_tab) {
|
||||
case 'bsr_settings':
|
||||
return BSR_PATH . 'templates/bsr-settings.php';
|
||||
case 'bsr_help':
|
||||
return BSR_PATH . 'templates/bsr-help.php';
|
||||
default:
|
||||
return BSR_PATH . 'templates/bsr-search-replace.php';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
<?php
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility functionality for the plugin
|
||||
*
|
||||
* @since 1.4.3
|
||||
*
|
||||
* @package Better_Search_Replace
|
||||
* @subpackage Better_Search_Replace/includes
|
||||
*/
|
||||
|
||||
// Prevent direct access.
|
||||
if ( ! defined( 'BSR_PATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
class BSR_Utils {
|
||||
const BSR_URL = 'https://bettersearchreplace.com';
|
||||
const WPE_URL = 'https://wpengine.com';
|
||||
|
||||
/** Nonce action for tools.php?page=better-search-replace screen query args (redirects, tab links). */
|
||||
const TOOLS_SCREEN_NONCE_ACTION = 'bsr_tools_screen';
|
||||
|
||||
/**
|
||||
* Create an external link for given URL.
|
||||
*
|
||||
* @param string $url
|
||||
* @param string $text
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function external_link( $url, $text ) {
|
||||
return sprintf( '<a href="%s" target="_blank">%s</a>', esc_url( $url ), esc_html( $text ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate Better Search Replace site URL with correct UTM tags.
|
||||
*
|
||||
* @param string $path
|
||||
* @param array $args
|
||||
* @param string $hash
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function bsr_url( $path, $args = array(), $hash = '' ) {
|
||||
$args = wp_parse_args(
|
||||
$args,
|
||||
array( 'utm_medium' => 'insideplugin' )
|
||||
);
|
||||
$args = array_map( 'urlencode', $args );
|
||||
$url = trailingslashit( self::BSR_URL ) . ltrim( $path, '/' );
|
||||
$url = add_query_arg( $args, $url );
|
||||
if ( $hash ) {
|
||||
$url .= '#' . $hash;
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate WP Engine site URL with correct UTM tags.
|
||||
*
|
||||
* @param string $path
|
||||
* @param array $args
|
||||
* @param string $hash
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function wpe_url( $path = '', $args = array(), $hash = '' ) {
|
||||
$args = wp_parse_args(
|
||||
$args,
|
||||
[
|
||||
'utm_medium' => 'referral',
|
||||
'utm_campaign' => 'bx_prod_referral',
|
||||
]
|
||||
);
|
||||
$args = array_map( 'urlencode', $args );
|
||||
$url = trailingslashit( self::WPE_URL ) . ltrim( $path, '/' );
|
||||
$url = add_query_arg( $args, $url );
|
||||
|
||||
if ( $hash ) {
|
||||
$url .= '#' . $hash;
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the plugin page url
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function plugin_page_url() {
|
||||
return menu_page_url( 'better-search-replace', false );
|
||||
}
|
||||
|
||||
/**
|
||||
* Tools → Better Search Replace URL with a nonce for GET query args on that screen.
|
||||
*
|
||||
* @param array $args Query arguments (merged with page=better-search-replace).
|
||||
* @return string Unescaped URL; pass through esc_url() for HTML or esc_url_raw() for redirects.
|
||||
*/
|
||||
public static function tools_page_url( $args = array() ) {
|
||||
$url = add_query_arg(
|
||||
array_merge(
|
||||
array( 'page' => 'better-search-replace' ),
|
||||
$args
|
||||
),
|
||||
admin_url( 'tools.php' )
|
||||
);
|
||||
|
||||
return add_query_arg( '_wpnonce', wp_create_nonce( self::TOOLS_SCREEN_NONCE_ACTION ), $url );
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the current tools screen GET request may use BSR-specific query args (result, import, profile, etc.).
|
||||
* Missing nonce is allowed for backward compatibility when the user is already authorized.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function validate_tools_screen_get() {
|
||||
// phpcs:disable WordPress.Security.NonceVerification.Missing,WordPress.Security.NonceVerification.Recommended -- Nonce is verified below when present; capability is always required.
|
||||
if ( ! bsr_enabled_for_user() ) {
|
||||
return false;
|
||||
}
|
||||
if ( ! isset( $_GET['_wpnonce'] ) ) {
|
||||
// phpcs:enable WordPress.Security.NonceVerification.Missing,WordPress.Security.NonceVerification.Recommended
|
||||
return true;
|
||||
}
|
||||
|
||||
$valid = (bool) wp_verify_nonce( sanitize_text_field( wp_unslash( (string) $_GET['_wpnonce'] ) ), self::TOOLS_SCREEN_NONCE_ACTION );
|
||||
// phpcs:enable WordPress.Security.NonceVerification.Missing,WordPress.Security.NonceVerification.Recommended
|
||||
|
||||
return $valid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is current admin screen for bsr.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_bsr_screen() {
|
||||
$screen = get_current_screen();
|
||||
|
||||
return $screen->base === 'tools_page_better-search-replace';
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures intent by verifying that a user was referred from another admin
|
||||
* page with the correct security nonce, and that user has the capability
|
||||
* level to use the plugin.
|
||||
*
|
||||
* @param int|string $action The nonce action.
|
||||
* @param string $query_arg Key to check for nonce in `$_REQUEST`.
|
||||
* @param bool $die Whether to die on failure (default false so callers can return JSON/redirects).
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function check_admin_referer( $action, $query_arg, $die = false ) {
|
||||
return check_admin_referer( $action, $query_arg, $die ) && bsr_enabled_for_user();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<?php // Silence is golden
|
||||
Reference in New Issue
Block a user