Plugin Directory

Changeset 2685592


Ignore:
Timestamp:
02/27/2022 01:44:33 PM (3 years ago)
Author:
Clorith
Message:

Version 2.5.0

Location:
string-locator
Files:
38 added
4 edited

Legend:

Unmodified
Added
Removed
  • string-locator/trunk/changelog.txt

    r2432238 r2685592  
     1= 2.4.2 =
     2* Fixed the option to restore previous search.
     3* Fixed respecting text capitalization in previews when doing a non-regex search.
     4* Changed capability checks, now works on hosts that maintain updates for their users.
     5
    16= 2.4.1 =
    27* Fixed case-sensitive class call, apparently not all PHP versions are equal in how this is treated.
  • string-locator/trunk/includes/class-string-locator.php

    r2432238 r2685592  
    11<?php
     2
     3namespace JITS\StringLocator;
    24
    35/**
     
    68class String_Locator {
    79    /**
    8      * @var string $string_locator_language The code language used for the editing page.
    9      * @var string $version String Locator version number.
    10      * @var array $notice An array containing all notices to display.
    11      * @var bool $failed_edit Has there been a failed edit.
    12      * @var string $path_to_use The path to the currently editable file.
    13      * @var array $bad_http_codes An array of HTTP status codes that will trigger the rollback feature.
    14      * @var array $bad_file_types An array of file extensions that will be ignored by the scanner.
    15      * @var int $excerpt_length The length of the excerpt from the line containing a match.
    16      * @var int|null $max_execution_time The server-configured max time a script can run.
    17      * @var int $start_execution_time The current time when our script started executing.
    18      * @var int $max_memory_consumption The server-configured max amount of memory a script can use.
     10     * The code language used for the editing page.
     11     *
     12     * @var string
    1913     */
    2014    public $string_locator_language = '';
    21     public $version                 = '2.4.2';
    22     public $notice                  = array();
    23     public $failed_edit             = false;
    24     private $path_to_use            = '';
    25     private $bad_http_codes         = array( '500' );
    26     private $bad_file_types         = array( 'rar', '7z', 'zip', 'tar', 'gz', 'jpg', 'jpeg', 'png', 'gif', 'mp3', 'mp4', 'avi', 'wmv' );
    27     private $excerpt_length         = 25;
    28     private $max_execution_time     = null;
    29     private $start_execution_timer  = 0;
    30     private $max_memory_consumption = 0;
    31 
    32     private $rest_namespace = 'string-locator';
     15
     16    /**
     17     * An array containing all notices to display.
     18     *
     19     * @var array
     20     */
     21    public $notice = array();
    3322
    3423    /**
     
    4837     */
    4938    public function init() {
    50         /**
    51          * Define class variables requiring expressions
    52          */
    53         $this->path_to_use    = ( is_multisite() ? 'network/admin.php' : 'tools.php' );
    54         $this->excerpt_length = apply_filters( 'string_locator_excerpt_length', 25 );
    55 
    56         $this->max_execution_time    = absint( ini_get( 'max_execution_time' ) );
    57         $this->start_execution_timer = microtime( true );
    58 
    59         if ( $this->max_execution_time > 30 ) {
    60             $this->max_execution_time = 30;
    61         }
    62 
    63         $this->set_memory_limit();
    64 
    6539        add_filter( 'admin_body_class', array( $this, 'admin_body_class' ) );
    6640
     
    7246        add_action( 'plugins_loaded', array( $this, 'load_i18n' ) );
    7347
    74         add_action( 'wp_ajax_string-locator-get-directory-structure', array( $this, 'ajax_get_directory_structure' ) );
    75         add_action( 'wp_ajax_string-locator-search', array( $this, 'ajax_file_search' ) );
    76         add_action( 'wp_ajax_string-locator-clean', array( $this, 'ajax_clean_search' ) );
    77 
    7848        add_filter( 'plugin_row_meta', array( $this, 'plugin_row_meta' ), 10, 2 );
    7949
    80         add_action( 'rest_api_init', array( $this, 'add_rest_route' ) );
    81     }
    82 
    83     public function add_rest_route() {
    84         register_rest_route(
    85             sprintf(
    86                 '%s/v1',
    87                 $this->rest_namespace
    88             ),
    89             '/save',
    90             array(
    91                 'methods'             => 'POST',
    92                 'callback'            => array( $this, 'editor_save' ),
    93                 'permission_callback' => function() {
    94                     return current_user_can( 'edit_themes' );
    95                 },
    96             )
    97         );
    98     }
    99 
    100     /**
    101      * Sets up the memory limit variables.
    102      *
    103      * @since 2.0.0
    104      *
    105      * @return void
    106      */
    107     function set_memory_limit() {
    108         $memory_limit = ini_get( 'memory_limit' );
    109 
    110         $this->max_memory_consumption = absint( $memory_limit );
    111 
    112         if ( strstr( $memory_limit, 'k' ) ) {
    113             $this->max_memory_consumption = ( str_replace( 'k', '', $memory_limit ) * 1000 );
    114         }
    115         if ( strstr( $memory_limit, 'M' ) ) {
    116             $this->max_memory_consumption = ( str_replace( 'M', '', $memory_limit ) * 1000000 );
    117         }
    118         if ( strstr( $memory_limit, 'G' ) ) {
    119             $this->max_memory_consumption = ( str_replace( 'G', '', $memory_limit ) * 1000000000 );
    120         }
     50        add_filter( 'string_locator_search_sources_markup', array( $this, 'add_search_options' ), 10, 2 );
     51        add_action( 'wp_ajax_string_locator_notice_dismiss', array( $this, 'dismiss_notice' ) );
     52
     53        add_action( 'admin_notices', array( $this, 'show_sponsorship_notice' ) );
     54    }
     55
     56    public function dismiss_notice() {
     57        // Only users who can use the plugin should be able to dismiss the notice.
     58        if ( ! current_user_can( 'update_core' ) || ! wp_verify_nonce( $_POST['_nonce'], 'string-locator-notice-dismiss' ) ) {
     59            return;
     60        }
     61
     62        update_option( 'string-locator-sponsorship-notice-dismissed', array( 'is_dismissed' => true ) );
     63    }
     64
     65    public function show_sponsorship_notice() {
     66        $screen = get_current_screen();
     67
     68        // Only show the notice on the plugins search page.
     69        if ( 'tools_page_string-locator' !== $screen->id || isset( $_GET['edit-file'] ) ) {
     70            return;
     71        }
     72
     73        // Do not show the notice to users who have dismissed it.
     74        $option = get_option( 'string-locator-sponsorship-notice-dismissed', array( 'is_dismissed' => false ) );
     75        if ( true === $option['is_dismissed'] ) {
     76            return;
     77        }
     78
     79        ?>
     80
     81        <div class="notice notice-info is-dismissible" id="string-locator-sponsorship-notice">
     82            <p>
     83                <?php esc_html_e( 'Thank you for trying out the String Locator plugin!', 'string-locator' ); ?>
     84            </p>
     85
     86            <p>
     87                <?php esc_html_e( 'If you like the plugin, please consider making a donation to support the plugin development.', 'string-locator' ); ?>
     88            </p>
     89
     90            <p>
     91                <a href="https://github.com/sponsors/Clorith/" target="_blank" class="button button-primary">
     92                    <?php esc_html_e( 'Donate via GitHub', 'string-locator' ); ?>
     93                </a>
     94
     95                <a href="https://paypal.me/clorith" target="_blank" class="button">
     96                    <?php esc_html_e( 'Donate via PayPal', 'string-locator' ); ?>
     97                </a>
     98            </p>
     99
     100            <script>
     101                jQuery( document ).ready( function( $ ) {
     102                    $( '#string-locator-sponsorship-notice' ).on( 'click', '.notice-dismiss', function() {
     103                        $.post( ajaxurl, {
     104                            action: 'string_locator_notice_dismiss',
     105                            _nonce: '<?php echo wp_create_nonce( 'string-locator-notice-dismiss' ); ?>'
     106                        } );
     107                    } );
     108                } );
     109            </script>
     110        </div>
     111
     112        <?php
     113    }
     114
     115    public function add_search_options( $searchers, $search_location ) {
     116        ob_start();
     117        ?>
     118        <optgroup label="<?php esc_attr_e( 'Core', 'string-locator' ); ?>">
     119                <option value="core"><?php esc_html_e( 'The whole WordPress directory', 'string-locator' ); ?></option>
     120        <option value="wp-content"><?php esc_html_e( 'Everything under wp-content', 'string-locator' ); ?></option>
     121        </optgroup>
     122        <optgroup label="<?php esc_attr_e( 'Themes', 'string-locator' ); ?>">
     123            <?php echo String_Locator::get_themes_options( $search_location ); ?>
     124        </optgroup>
     125        <?php if ( String_Locator::has_mu_plugins() ) : ?>
     126            <optgroup label="<?php esc_attr_e( 'Must Use Plugins', 'string-locator' ); ?>">
     127                <?php echo String_Locator::get_mu_plugins_options( $search_location ); ?>
     128            </optgroup>
     129        <?php endif; ?>
     130        <optgroup label="<?php esc_attr_e( 'Plugins', 'string-locator' ); ?>">
     131            <?php echo String_Locator::get_plugins_options( $search_location ); ?>
     132        </optgroup>
     133        <?php
     134
     135        $searchers .= ob_get_clean();
     136
     137        return $searchers;
    121138    }
    122139
     
    132149        if ( 'string-locator/string-locator.php' === $plugin_file ) {
    133150            $meta[] = sprintf(
    134                 '<a href="https://www.paypal.me/clorith">%s</a>',
     151                '<a href="https://github.com/sponsors/Clorith/">%s</a>',
    135152                esc_html__( 'Donate to this plugin', 'string-locator' )
    136153            );
     
    193210        );
    194211
     212        $fields = apply_filters( 'string_locator_editor_fields', $fields );
     213
    195214        $field_output = array();
    196215
     
    287306
    288307        return false;
    289     }
    290 
    291     /**
    292      * Handles the AJAX request to prepare the search hierarchy.
    293      *
    294      * @return void
    295      */
    296     function ajax_get_directory_structure() {
    297         if ( ! check_ajax_referer( 'string-locator-search', 'nonce', false ) ) {
    298             wp_send_json_error( __( 'Authentication failed', 'string-locator' ) );
    299         }
    300 
    301         $scan_path = $this->prepare_scan_path( $_POST['directory'] );
    302         if ( is_file( $scan_path->path ) ) {
    303             $files = array( $scan_path->path );
    304         } else {
    305             $files = $this->ajax_scan_path( $scan_path->path );
    306         }
    307 
    308         /*
    309          * Make sure each chunk of file arrays never exceeds 500 files
    310          * This is to prevent the SQL string from being too large and crashing everything
    311          */
    312         $back_compat_filter = apply_filters( 'string-locator-files-per-array', 500 ); //phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
    313 
    314         $file_chunks = array_chunk( $files, apply_filters( 'string_locator_files_per_array', $back_compat_filter ), true );
    315 
    316         $store = (object) array(
    317             'scan_path' => $scan_path,
    318             'search'    => wp_unslash( $_POST['search'] ),
    319             'directory' => $_POST['directory'],
    320             'chunks'    => count( $file_chunks ),
    321             'regex'     => $_POST['regex'],
    322         );
    323 
    324         $response = array(
    325             'total'     => count( $files ),
    326             'current'   => 0,
    327             'directory' => $scan_path,
    328             'chunks'    => count( $file_chunks ),
    329             'regex'     => $_POST['regex'],
    330         );
    331 
    332         set_transient( 'string-locator-search-overview', $store );
    333         update_option( 'string-locator-search-history', array(), false );
    334 
    335         foreach ( $file_chunks as $count => $file_chunk ) {
    336             set_transient( 'string-locator-search-files-' . $count, $file_chunk );
    337         }
    338 
    339         wp_send_json_success( $response );
    340     }
    341 
    342     /**
    343      * Check if the script is about to exceed the max execution time.
    344      *
    345      * @since 1.9.0
    346      *
    347      * @return bool
    348      */
    349     function nearing_execution_limit() {
    350         // Max execution time is 0 or -1 (infinite) in server config
    351         if ( 0 === $this->max_execution_time || - 1 === $this->max_execution_time ) {
    352             return false;
    353         }
    354 
    355         $back_compat_filter = apply_filters( 'string-locator-extra-search-delay', 2 ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
    356 
    357         $built_in_delay = apply_filters( 'string_locator_extra_search_delay', $back_compat_filter );
    358         $execution_time = ( microtime( true ) - $this->start_execution_timer + $built_in_delay );
    359 
    360         if ( $execution_time >= $this->max_execution_time ) {
    361             return $execution_time;
    362         }
    363 
    364         return false;
    365     }
    366 
    367     /**
    368      * Check if the script is about to exceed the server memory limit.
    369      *
    370      * @since 2.0.0
    371      *
    372      * @return bool
    373      */
    374     function nearing_memory_limit() {
    375         // Check if the memory limit is set t o0 or -1 (infinite) in server config
    376         if ( 0 === $this->max_memory_consumption || - 1 === $this->max_memory_consumption ) {
    377             return false;
    378         }
    379 
    380         // We give our selves a 256k memory buffer, as we need to close off the script properly as well
    381         $back_compat_filter = apply_filters( 'string-locator-extra-memory-buffer', 256000 ); //phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
    382         $built_in_buffer    = apply_filters( 'string_locator_extra_memory_buffer', $back_compat_filter );
    383         $memory_use         = ( memory_get_usage( true ) + $built_in_buffer );
    384 
    385         if ( $memory_use >= $this->max_memory_consumption ) {
    386             return $memory_use;
    387         }
    388 
    389         return false;
    390     }
    391 
    392     public static function absbool( $value ) {
    393         if ( is_bool( $value ) ) {
    394             $bool = $value;
    395         } else {
    396             if ( 'false' === $value ) {
    397                 $bool = false;
    398             } else {
    399                 $bool = true;
    400             }
    401         }
    402 
    403         return $bool;
    404     }
    405 
    406     /**
    407      * Search an individual file supplied via AJAX.
    408      *
    409      * @since 1.9.0
    410      *
    411      * @return void
    412      */
    413     function ajax_file_search() {
    414         if ( ! check_ajax_referer( 'string-locator-search', 'nonce', false ) ) {
    415             wp_send_json_error( __( 'Authentication failed', 'string-locator' ) );
    416         }
    417 
    418         $back_compat_filter = apply_filters( 'string-locator-files-per-array', 500 ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
    419 
    420         $files_per_chunk = apply_filters( 'string_locator_files_per_array', $back_compat_filter );
    421         $response        = array(
    422             'search'  => array(),
    423             'filenum' => absint( $_POST['filenum'] ),
    424         );
    425 
    426         $filenum   = absint( $_POST['filenum'] );
    427         $next_file = $filenum + 1;
    428 
    429         $next_chunk = ( ceil( ( $next_file ) / $files_per_chunk ) - 1 );
    430         $chunk      = ( ceil( $filenum / $files_per_chunk ) - 1 );
    431         if ( $chunk < 0 ) {
    432             $chunk = 0;
    433         }
    434         if ( $next_chunk < 0 ) {
    435             $next_chunk = 0;
    436         }
    437 
    438         $scan_data = get_transient( 'string-locator-search-overview' );
    439         $file_data = get_transient( 'string-locator-search-files-' . $chunk );
    440 
    441         if ( ! isset( $file_data[ $filenum ] ) ) {
    442             wp_send_json_error(
    443                 array(
    444                     'continue' => false,
    445                     'message'  => sprintf(
    446                         /* translators: %d: The numbered reference to a file being searched. */
    447                         esc_html__( 'The file-number, %d, that was sent could not be found.', 'string-locator' ),
    448                         $filenum
    449                     ),
    450                 )
    451             );
    452         }
    453 
    454         if ( $this->nearing_execution_limit() ) {
    455             wp_send_json_error(
    456                 array(
    457                     'continue' => false,
    458                     'message'  => sprintf(
    459                         /* translators: %1$d: The time a PHP file can run, as defined by the server configuration. %2$d: The amount of time used by the PHP file so far. */
    460                         esc_html__( 'The maximum time your server allows a script to run (%1$d) is too low for the plugin to run as intended, at startup %2$d seconds have passed', 'string-locator' ),
    461                         $this->max_execution_time,
    462                         $this->nearing_execution_limit()
    463                     ),
    464                 )
    465             );
    466         }
    467         if ( $this->nearing_memory_limit() ) {
    468             wp_send_json_error(
    469                 array(
    470                     'continue' => false,
    471                     'message'  => sprintf(
    472                         /* translators: %1$d: Current amount of used system memory resources. %2$d: The maximum available system memory. */
    473                         esc_html__( 'The memory limit is about to be exceeded before the search has started, this could be an early indicator that your site may soon struggle as well, unfortunately this means the plugin is unable to perform any searches. Current memory consumption: %1$d of %2$d bytes', 'string-locator' ),
    474                         $this->nearing_memory_limit(),
    475                         $this->max_memory_consumption
    476                     ),
    477                 )
    478             );
    479         }
    480 
    481         $is_regex = false;
    482         if ( isset( $scan_data->regex ) ) {
    483             $is_regex = $this->absbool( $scan_data->regex );
    484         }
    485 
    486         if ( $is_regex ) {
    487             if ( false === @preg_match( $scan_data->search, '' ) ) { // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
    488                 wp_send_json_error(
    489                     array(
    490                         'continue' => false,
    491                         'message'  => sprintf(
    492                             /* translators: %s: The search string used. */
    493                             __( 'Your search string, <strong>%s</strong>, is not a valid pattern, and the search has been aborted.', 'string-locator' ),
    494                             esc_html( $scan_data->search )
    495                         ),
    496                     )
    497                 );
    498             }
    499         }
    500 
    501         while ( ! $this->nearing_execution_limit() && ! $this->nearing_memory_limit() && isset( $file_data[ $filenum ] ) ) {
    502             $filenum        = absint( $_POST['filenum'] );
    503             $search_results = null;
    504             $next_file      = $filenum + 1;
    505 
    506             $next_chunk = ( ceil( ( $next_file ) / $files_per_chunk ) - 1 );
    507             $chunk      = ( ceil( $filenum / $files_per_chunk ) - 1 );
    508             if ( $chunk < 0 ) {
    509                 $chunk = 0;
    510             }
    511             if ( $next_chunk < 0 ) {
    512                 $next_chunk = 0;
    513             }
    514 
    515             if ( ! isset( $file_data[ $filenum ] ) ) {
    516                 $chunk ++;
    517                 $file_data = get_transient( 'string-locator-search-files-' . $chunk );
    518                 continue;
    519             }
    520 
    521             $file_name = explode( '/', $file_data[ $filenum ] );
    522             $file_name = end( $file_name );
    523 
    524             /*
    525              * Check the file type, if it's an unsupported type, we skip it
    526              */
    527             $file_type = explode( '.', $file_name );
    528             $file_type = strtolower( end( $file_type ) );
    529 
    530             /*
    531              * Scan the file and look for our string, but only if it's an approved file extension
    532              */
    533             $bad_file_types = apply_filters( 'string_locator_bad_file_types', $this->bad_file_types );
    534             if ( ! in_array( $file_type, $bad_file_types, true ) ) {
    535                 $search_results = $this->scan_file( $file_data[ $filenum ], $scan_data->search, $file_data[ $filenum ], $scan_data->scan_path->type, '', $is_regex );
    536             }
    537 
    538             $response['last_file'] = $file_data[ $filenum ];
    539             $response['filenum']   = $filenum;
    540             $response['filename']  = $file_name;
    541             if ( $search_results ) {
    542                 $response['search'][] = $search_results;
    543             }
    544 
    545             if ( $next_chunk !== $chunk ) {
    546                 $file_data = get_transient( 'string-locator-search-files-' . $next_chunk );
    547             }
    548 
    549             $response['next_file'] = ( isset( $file_data[ $next_file ] ) ? $file_data[ $next_file ] : '' );
    550 
    551             if ( ! empty( $search_results ) ) {
    552                 $history = get_option( 'string-locator-search-history', array() );
    553                 $history = array_merge( $history, $search_results );
    554                 update_option( 'string-locator-search-history', $history, false );
    555             }
    556 
    557             $_POST['filenum'] ++;
    558         }
    559 
    560         wp_send_json_success( $response );
    561     }
    562 
    563     /**
    564      * Clean up our options used to help during the search.
    565      *
    566      * @return void
    567      */
    568     function ajax_clean_search() {
    569         if ( ! check_ajax_referer( 'string-locator-search', 'nonce', false ) ) {
    570             wp_send_json_error( __( 'Authentication failed', 'string-locator' ) );
    571         }
    572 
    573         $scan_data = get_transient( 'string-locator-search-overview' );
    574         for ( $i = 0; $i < $scan_data->chunks; $i ++ ) {
    575             delete_transient( 'string-locator-search-files-' . $i );
    576         }
    577 
    578         wp_send_json_success( true );
    579308    }
    580309
     
    648377        $table_columns = sprintf(
    649378            '<tr>
    650                 <th scope="col" class="manage-column column-stringresult column-primary">%s</th>
    651                 <th scope="col" class="manage-column column-filename">%s</th>
    652                 <th scope="col" class="manage-column column-linenum">%s</th>
    653                 <th scope="col" class="manage-column column-linepos">%s</th>
     379                <th scope="col" class="manage-column column-stringresult column-primary string">%s</th>
     380                <th scope="col" class="manage-column column-filename filename">%s</th>
     381                <th scope="col" class="manage-column column-linenum line">%s</th>
     382                <th scope="col" class="manage-column column-linepos position">%s</th>
    654383            </tr>',
    655384            esc_html( __( 'String', 'string-locator' ) ),
     
    676405
    677406    /**
    678      * Create an admin edit link for the supplied path.
    679      *
    680      * @param string $path Path to the file we'er adding a link for.
    681      * @param int $line The line in the file where our search result was found.
    682      * @param int $linepos The positin in the line where the search result was found.
    683      *
    684      * @return string
    685      */
    686     function create_edit_link( $path, $line = 0, $linepos = 0 ) {
    687         $file_type    = 'core';
    688         $file_slug    = '';
    689         $content_path = str_replace( '\\', '/', WP_CONTENT_DIR );
    690 
    691         $path  = str_replace( '\\', '/', $path );
    692         $paths = explode( '/', $path );
    693 
    694         $url_args = array(
    695             'page=string-locator',
    696             'edit-file=' . end( $paths ),
    697         );
    698 
    699         switch ( true ) {
    700             case ( in_array( 'wp-content', $paths, true ) && in_array( 'plugins', $paths, true ) ):
    701                 $file_type     = 'plugin';
    702                 $content_path .= '/plugins/';
    703                 break;
    704             case ( in_array( 'wp-content', $paths, true ) && in_array( 'themes', $paths, true ) ):
    705                 $file_type     = 'theme';
    706                 $content_path .= '/themes/';
    707                 break;
    708         }
    709 
    710         $rel_path  = str_replace( $content_path, '', $path );
    711         $rel_paths = explode( '/', $rel_path );
    712 
    713         if ( 'core' !== $file_type ) {
    714             $file_slug = $rel_paths[0];
    715         }
    716 
    717         $url_args[] = 'file-reference=' . $file_slug;
    718         $url_args[] = 'file-type=' . $file_type;
    719         $url_args[] = 'string-locator-line=' . absint( $line );
    720         $url_args[] = 'string-locator-linepos=' . absint( $linepos );
    721         $url_args[] = 'string-locator-path=' . urlencode( str_replace( '/', DIRECTORY_SEPARATOR, $path ) );
    722 
    723         $url = admin_url( $this->path_to_use . '?' . implode( '&', $url_args ) );
    724 
    725         return $url;
    726     }
    727 
    728     /**
    729      * Parse the search option to determine what kind of search we are performing and what directory to start in.
    730      *
    731      * @param string $option The search-type identifier.
    732      *
    733      * @return bool|object
    734      */
    735     function prepare_scan_path( $option ) {
    736         $data = array(
    737             'path' => '',
    738             'type' => '',
    739             'slug' => '',
    740         );
    741 
    742         switch ( true ) {
    743             case ( 't--' === $option ):
    744                 $data['path'] = WP_CONTENT_DIR . '/themes/';
    745                 $data['type'] = 'theme';
    746                 break;
    747             case ( strlen( $option ) > 3 && 't-' === substr( $option, 0, 2 ) ):
    748                 $data['path'] = WP_CONTENT_DIR . '/themes/' . substr( $option, 2 );
    749                 $data['type'] = 'theme';
    750                 $data['slug'] = substr( $option, 2 );
    751                 break;
    752             case ( 'p--' === $option ):
    753                 $data['path'] = WP_CONTENT_DIR . '/plugins/';
    754                 $data['type'] = 'plugin';
    755                 break;
    756             case ( 'mup--' === $option ):
    757                 $data['path'] = WP_CONTENT_DIR . '/mu-plugins/';
    758                 $data['type'] = 'mu-plugin';
    759                 break;
    760             case ( strlen( $option ) > 3 && 'p-' === substr( $option, 0, 2 ) ):
    761                 $slug = explode( '/', substr( $option, 2 ) );
    762 
    763                 $data['path'] = WP_CONTENT_DIR . '/plugins/' . $slug[0];
    764                 $data['type'] = 'plugin';
    765                 $data['slug'] = $slug[0];
    766                 break;
    767             case ( 'core' === $option ):
    768                 $data['path'] = ABSPATH;
    769                 $data['type'] = 'core';
    770                 break;
    771             case ( 'wp-content' === $option ):
    772                 $data['path'] = WP_CONTENT_DIR;
    773                 $data['type'] = 'core';
    774                 break;
    775         }
    776 
    777         if ( empty( $data['path'] ) ) {
    778             return false;
    779         }
    780 
    781         return (object) $data;
    782     }
    783 
    784     /**
    785      * Check if a file path is valid for editing.
    786      *
    787      * @param string $path Path to file.
    788      *
    789      * @return bool
    790      */
    791     function is_valid_location( $path ) {
    792         $valid   = true;
    793         $path    = str_replace( array( '/' ), array( DIRECTORY_SEPARATOR ), stripslashes( $path ) );
    794         $abspath = str_replace( array( '/' ), array( DIRECTORY_SEPARATOR ), ABSPATH );
    795 
    796         // Check that it is a valid file we are trying to access as well.
    797         if ( ! file_exists( $path ) ) {
    798             $valid = false;
    799         }
    800 
    801         if ( empty( $path ) ) {
    802             $valid = false;
    803         }
    804         if ( stristr( $path, '..' ) ) {
    805             $valid = false;
    806         }
    807         if ( ! stristr( $path, $abspath ) ) {
    808             $valid = false;
    809         }
    810 
    811         return $valid;
    812     }
    813 
    814     /**
    815407     * Set the text domain for translated plugin content.
    816408     *
     
    820412        $i18n_dir = 'string-locator/languages/';
    821413        load_plugin_textdomain( 'string-locator', false, $i18n_dir );
     414    }
     415
     416    /**
     417     * Convert a value to its absolute boolean interpretation.
     418     *
     419     * @param $value
     420     *
     421     * @return bool
     422     */
     423    public static function absbool( $value ) {
     424        if ( is_bool( $value ) ) {
     425            $bool = $value;
     426        } else {
     427            if ( 'false' === $value ) {
     428                $bool = false;
     429            } else {
     430                $bool = true;
     431            }
     432        }
     433
     434        return $bool;
    822435    }
    823436
     
    833446        }
    834447
    835         if ( ! wp_script_is( 'react', 'registered' ) ) {
    836             wp_register_script( 'react', trailingslashit( STRING_LOCATOR_PLUGIN_URL ) . 'resources/js/react.js', array() );
    837         }
    838 
    839         if ( ! wp_script_is( 'react-dom', 'registered' ) ) {
    840             wp_register_script( 'react-dom', trailingslashit( STRING_LOCATOR_PLUGIN_URL ) . 'resources/js/react-dom.js', array() );
    841         }
     448        $search = STRING_LOCATOR_PLUGIN_DIR . 'build/string-locator-search.asset.php';
     449        $editor = STRING_LOCATOR_PLUGIN_DIR . 'build/string-locator.asset.php';
     450
     451        $search = file_exists( $search ) ? require $search : array();
     452        $editor = file_exists( $editor ) ? require $editor : array();
    842453
    843454        /**
    844455         * String Locator Styles
    845456         */
    846         wp_enqueue_style( 'string-locator', trailingslashit( STRING_LOCATOR_PLUGIN_URL ) . 'resources/css/string-locator.css', array(), $this->version );
     457        wp_enqueue_style( 'string-locator', trailingslashit( STRING_LOCATOR_PLUGIN_URL ) . 'build/string-locator.css', array(), $search['version'] );
    847458
    848459        if ( ! isset( $_GET['edit-file'] ) || ! current_user_can( 'edit_themes' ) ) {
     
    850461             * String Locator Scripts
    851462             */
    852             wp_enqueue_script( 'string-locator-search', trailingslashit( STRING_LOCATOR_PLUGIN_URL ) . 'resources/js/string-locator-search.js', array( 'jquery', 'wp-util' ), $this->version, true );
     463            wp_enqueue_script(
     464                'string-locator-search',
     465                trailingslashit( STRING_LOCATOR_PLUGIN_URL ) . 'build/string-locator-search.js',
     466                array( 'jquery', 'wp-util' ),
     467                $search['version'],
     468                true
     469            );
    853470
    854471            wp_localize_script(
     
    856473                'string_locator',
    857474                array(
    858                     'ajax_url'              => admin_url( 'admin-ajax.php' ),
     475                    'rest_nonce'            => wp_create_nonce( 'wp_rest' ),
    859476                    'search_nonce'          => wp_create_nonce( 'string-locator-search' ),
    860477                    'search_current_prefix' => __( 'Next file: ', 'string-locator' ),
     
    865482                    'search_no_results'     => __( 'Your search was completed, but no results were found.', 'string-locator' ),
    866483                    'warning_title'         => __( 'Warning', 'string-locator' ),
     484                    'url'                   => array(
     485                        'search'              => get_rest_url( null, 'string-locator/v1/search' ),
     486                        'clean'               => get_rest_url( null, 'string-locator/v1/clean' ),
     487                        'directory_structure' => get_rest_url( null, 'string-locator/v1/get-directory-structure' ),
     488                    ),
    867489                )
    868490            );
     
    878500             * String Locator Scripts
    879501             */
    880             wp_enqueue_script( 'string-locator-editor', trailingslashit( STRING_LOCATOR_PLUGIN_URL ) . 'resources/js/string-locator.js', array( 'jquery', 'code-editor', 'wp-util' ), $this->version, true );
     502            wp_enqueue_script(
     503                'string-locator-editor',
     504                trailingslashit( STRING_LOCATOR_PLUGIN_URL ) . 'build/string-locator.js',
     505                array( 'jquery', 'code-editor', 'wp-util' ),
     506                $editor['version'],
     507                true
     508            );
    881509
    882510            wp_localize_script(
     
    887515                    'goto_line'    => absint( $_GET['string-locator-line'] ),
    888516                    'goto_linepos' => absint( $_GET['string-locator-linepos'] ),
    889                     'save_url'     => get_rest_url( null, 'string-locator/v1/save' ),
     517                    'url'          => array(
     518                        'save' => get_rest_url( null, 'string-locator/v1/save' ),
     519                    ),
    890520                )
    891521            );
     
    939569            return false;
    940570        }
     571
     572        $include_path = '';
    941573
    942574        /**
     
    947579         * - The user is capable of editing files.
    948580         */
    949         if ( isset( $_GET['string-locator-path'] ) && $this->is_valid_location( $_GET['string-locator-path'] ) && current_user_can( 'edit_themes' ) ) {
    950             include_once( dirname( __FILE__ ) . '/../editor.php' );
     581        if ( isset( $_GET['string-locator-path'] ) && self::is_valid_location( $_GET['string-locator-path'] ) && current_user_can( 'edit_themes' ) ) {
     582            $include_path = trailingslashit( STRING_LOCATOR_PLUGIN_DIR ) . 'views/editor.php';
    951583        } else {
    952             include_once( dirname( __FILE__ ) . '/../search.php' );
     584            $include_path = trailingslashit( STRING_LOCATOR_PLUGIN_DIR ) . 'views/search.php';
     585        }
     586
     587        $include_path = apply_filters( 'string_locator_view', $include_path );
     588
     589        if ( ! empty( $include_path ) ) {
     590            include_once $include_path;
    953591        }
    954592    }
    955593
    956594    function admin_body_class( $class ) {
    957         if ( isset( $_GET['string-locator-path'] ) && $this->is_valid_location( $_GET['string-locator-path'] ) && current_user_can( 'edit_themes' ) ) {
     595        if ( isset( $_GET['string-locator-path'] ) && self::is_valid_location( $_GET['string-locator-path'] ) && current_user_can( 'edit_themes' ) ) {
    958596            $class .= ' file-edit-screen';
    959597        }
    960598
    961599        return $class;
    962     }
    963 
    964     /**
    965      * Check for inconsistencies in brackets and similar.
    966      *
    967      * @param string $start Start delimited.
    968      * @param string $end End delimiter.
    969      * @param string $string The string to scan.
    970      *
    971      * @return array
    972      */
    973     function smart_scan( $start, $end, $string ) {
    974         $opened = array();
    975 
    976         $lines = explode( "\n", $string );
    977         for ( $i = 0; $i < count( $lines ); $i ++ ) {
    978             if ( stristr( $lines[ $i ], $start ) ) {
    979                 $opened[] = $i;
    980             }
    981             if ( stristr( $lines[ $i ], $end ) ) {
    982                 array_pop( $opened );
    983             }
    984         }
    985 
    986         return $opened;
    987     }
    988 
    989     /**
    990      * Handler for storing the content of the code editor.
    991      *
    992      * Also runs over the Smart-Scan if enabled.
    993      *
    994      * @return void|array
    995      */
    996     function editor_save( $request ) {
    997         $_POST = $request->get_params();
    998 
    999         $check_loopback = isset( $_POST['string-locator-loopback-check'] );
    1000         $do_smart_scan  = isset( $_POST['string-locator-smart-edit'] );
    1001 
    1002         if ( $this->is_valid_location( $_POST['string-locator-path'] ) ) {
    1003             $path    = urldecode( $_POST['string-locator-path'] );
    1004             $content = stripslashes( $_POST['string-locator-editor-content'] );
    1005 
    1006             /**
    1007              * Send an error notice if the file isn't writable
    1008              */
    1009             if ( ! is_writeable( $path ) ) {
    1010                 $this->notice[] = array(
    1011                     'type'    => 'error',
    1012                     'message' => __( 'The file could not be written to, please check file permissions or edit it manually.', 'string-locator' ),
    1013                 );
    1014 
    1015                 return array(
    1016                     'notices' => $this->notice,
    1017                 );
    1018             }
    1019 
    1020             /**
    1021              * If enabled, run the Smart-Scan on the content before saving it
    1022              */
    1023             if ( $do_smart_scan ) {
    1024                 $open_brace  = substr_count( $content, '{' );
    1025                 $close_brace = substr_count( $content, '}' );
    1026                 if ( $open_brace !== $close_brace ) {
    1027                     $this->failed_edit = true;
    1028 
    1029                     $opened = $this->smart_scan( '{', '}', $content );
    1030 
    1031                     foreach ( $opened as $line ) {
    1032                         $this->notice[] = array(
    1033                             'type'    => 'error',
    1034                             'message' => sprintf(
    1035                                 // translators: 1: Line number with an error.
    1036                                 __( 'There is an inconsistency in the opening and closing braces, { and }, of your file on line %s', 'string-locator' ),
    1037                                 '<a href="#" class="string-locator-edit-goto" data-goto-line="' . ( $line + 1 ) . '">' . ( $line + 1 ) . '</a>'
    1038                             ),
    1039                         );
    1040                     }
    1041                 }
    1042 
    1043                 $open_bracket  = substr_count( $content, '[' );
    1044                 $close_bracket = substr_count( $content, ']' );
    1045                 if ( $open_bracket !== $close_bracket ) {
    1046                     $this->failed_edit = true;
    1047 
    1048                     $opened = $this->smart_scan( '[', ']', $content );
    1049 
    1050                     foreach ( $opened as $line ) {
    1051                         $this->notice[] = array(
    1052                             'type'    => 'error',
    1053                             'message' => sprintf(
    1054                                 // translators: 1: Line number with an error.
    1055                                 __( 'There is an inconsistency in the opening and closing braces, [ and ], of your file on line %s', 'string-locator' ),
    1056                                 '<a href="#" class="string-locator-edit-goto" data-goto-line="' . ( $line + 1 ) . '">' . ( $line + 1 ) . '</a>'
    1057                             ),
    1058                         );
    1059                     }
    1060                 }
    1061 
    1062                 $open_parenthesis  = substr_count( $content, '(' );
    1063                 $close_parenthesis = substr_count( $content, ')' );
    1064                 if ( $open_parenthesis !== $close_parenthesis ) {
    1065                     $this->failed_edit = true;
    1066 
    1067                     $opened = $this->smart_scan( '(', ')', $content );
    1068 
    1069                     foreach ( $opened as $line ) {
    1070                         $this->notice[] = array(
    1071                             'type'    => 'error',
    1072                             'message' => sprintf(
    1073                                 // translators: 1: Line number with an error.
    1074                                 __( 'There is an inconsistency in the opening and closing braces, ( and ), of your file on line %s', 'string-locator' ),
    1075                                 '<a href="#" class="string-locator-edit-goto" data-goto-line="' . ( $line + 1 ) . '">' . ( $line + 1 ) . '</a>'
    1076                             ),
    1077                         );
    1078                     }
    1079                 }
    1080 
    1081                 if ( $this->failed_edit ) {
    1082                     return array(
    1083                         'notices' => $this->notice,
    1084                     );
    1085                 }
    1086             }
    1087 
    1088             $original = file_get_contents( $path );
    1089 
    1090             $this->write_file( $path, $content );
    1091 
    1092             /**
    1093              * Check the status of the site after making our edits.
    1094              * If the site fails, revert the changes to return the sites to its original state
    1095              */
    1096             if ( $check_loopback ) {
    1097                 $header = wp_remote_head( site_url() );
    1098 
    1099                 if ( ! is_wp_error( $header ) && 301 === (int) $header['response']['code'] ) {
    1100                     $header = wp_remote_head( $header['headers']['location'] );
    1101                 }
    1102 
    1103                 $bad_http_check = apply_filters( 'string_locator_bad_http_codes', $this->bad_http_codes );
    1104             }
    1105 
    1106             if ( $check_loopback && is_wp_error( $header ) ) {
    1107                 $this->failed_edit = true;
    1108                 $this->write_file( $path, $original );
    1109 
    1110                 // Likely loopback error, so be useful in our errors.
    1111                 if ( 'http_request_failed' === $header->get_error_code() ) {
    1112                     return array(
    1113                         'notices' => array(
    1114                             array(
    1115                                 'type'    => 'error',
    1116                                 'message' => __( 'Your changes were not saved, as a check of your site could not be completed afterwards. This may be due to a <a href="https://wordpress.org/support/article/loopbacks/">loopback</a> error.', 'string-locator' ),
    1117                             ),
    1118                         ),
    1119                     );
    1120                 }
    1121 
    1122                 // Fallback error message here.
    1123                 return array(
    1124                     'notices' => array(
    1125                         array(
    1126                             'type'    => 'error',
    1127                             'message' => $header->get_error_message(),
    1128                         ),
    1129                     ),
    1130                 );
    1131             } elseif ( $check_loopback && in_array( $header['response']['code'], $bad_http_check, true ) ) {
    1132                 $this->failed_edit = true;
    1133                 $this->write_file( $path, $original );
    1134 
    1135                 return array(
    1136                     'notices' => array(
    1137                         array(
    1138                             'type'    => 'error',
    1139                             'message' => __( 'A 500 server error was detected on your site after updating your file. We have restored the previous version of the file for you.', 'string-locator' ),
    1140                         ),
    1141                     ),
    1142                 );
    1143             } else {
    1144                 return array(
    1145                     'notices' => array(
    1146                         array(
    1147                             'type'    => 'success',
    1148                             'message' => __( 'The file has been saved', 'string-locator' ),
    1149                         ),
    1150                     ),
    1151                 );
    1152             }
    1153         } else {
    1154             return array(
    1155                 'notices' => array(
    1156                     array(
    1157                         'type'    => 'error',
    1158                         'message' => sprintf(
    1159                             // translators: %s: The file location that was sent.
    1160                             __( 'The file location provided, <strong>%s</strong>, is not valid.', 'string-locator' ),
    1161                             $_POST['string-locator-path']
    1162                         ),
    1163                     ),
    1164                 ),
    1165             );
    1166         }
    1167     }
    1168 
    1169     /**
    1170      * When editing a file, this is where we write all the new content.
    1171      * We will break early if the user isn't allowed to edit files.
    1172      *
    1173      * @param string $path The path to the file.
    1174      * @param string $content The content to write to the file.
    1175      *
    1176      * @return void
    1177      */
    1178     private function write_file( $path, $content ) {
    1179         if ( ! current_user_can( 'edit_themes' ) ) {
    1180             return;
    1181         }
    1182 
    1183         // Verify the location is valid before we try using it.
    1184         if ( ! $this->is_valid_location( $path ) ) {
    1185             return;
    1186         }
    1187 
    1188         $back_compat_filter = apply_filters( 'string-locator-filter-closing-php-tags', true ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
    1189 
    1190         if ( apply_filters( 'string_locator_filter_closing_php_tags', $back_compat_filter ) ) {
    1191             $content = preg_replace( '/\?>$/si', '', trim( $content ), - 1, $replaced_strings );
    1192 
    1193             if ( $replaced_strings >= 1 ) {
    1194                 $this->notice[] = array(
    1195                     'type'    => 'error',
    1196                     'message' => __( 'We detected a PHP code tag ending, this has been automatically stripped out to help prevent errors in your code.', 'string-locator' ),
    1197                 );
    1198             }
    1199         }
    1200 
    1201         $file        = fopen( $path, 'w' );
    1202         $lines       = explode( "\n", str_replace( array( "\r\n", "\r" ), "\n", $content ) );
    1203         $total_lines = count( $lines );
    1204 
    1205         for ( $i = 0; $i < $total_lines; $i ++ ) {
    1206             $write_line = $lines[ $i ];
    1207 
    1208             if ( ( $i + 1 ) < $total_lines ) {
    1209                 $write_line .= PHP_EOL;
    1210             }
    1211 
    1212             fwrite( $file, $write_line );
    1213         }
    1214 
    1215         fclose( $file );
    1216600    }
    1217601
     
    1234618
    1235619    /**
    1236      * Scan through an individual file to look for occurrences of £string.
    1237      *
    1238      * @param string $filename The path to the file.
    1239      * @param string $string The search string.
    1240      * @param mixed $location The file location object/string.
    1241      * @param string $type File type.
    1242      * @param string $slug The plugin/theme slug of the file.
    1243      * @param boolean $regex Should a regex search be performed.
    1244      *
    1245      * @return array
    1246      */
    1247     function scan_file( $filename, $string, $location, $type, $slug, $regex = false ) {
    1248         if ( empty( $string ) || ! is_file( $filename ) ) {
    1249             return array();
    1250         }
    1251         $output      = array();
    1252         $linenum     = 0;
    1253         $match_count = 0;
    1254 
    1255         if ( ! is_object( $location ) ) {
    1256             $path     = $location;
    1257             $location = explode( DIRECTORY_SEPARATOR, $location );
    1258             $file     = end( $location );
    1259         } else {
    1260             $path = $location->getPathname();
    1261             $file = $location->getFilename();
    1262         }
    1263 
    1264         /*
    1265          * Check if the filename matches our search pattern
    1266          */
    1267         if ( stristr( $file, $string ) || ( $regex && preg_match( $string, $file ) ) ) {
    1268             $relativepath = str_replace(
    1269                 array(
    1270                     ABSPATH,
    1271                     '\\',
    1272                     '/',
    1273                 ),
    1274                 array(
    1275                     '',
    1276                     DIRECTORY_SEPARATOR,
    1277                     DIRECTORY_SEPARATOR,
    1278                 ),
    1279                 $path
    1280             );
    1281             $match_count ++;
    1282 
    1283             $editurl = $this->create_edit_link( $path, $linenum );
    1284 
    1285             $path_string = sprintf(
    1286                 '<a href="%s">%s</a>',
    1287                 esc_url( $editurl ),
    1288                 esc_html( $relativepath )
    1289             );
    1290 
    1291             $output[] = array(
    1292                 'ID'           => $match_count,
    1293                 'linenum'      => sprintf(
    1294                     '[%s]',
    1295                     esc_html__( 'Filename matches search', 'string-locator' )
    1296                 ),
    1297                 'linepos'      => '',
    1298                 'path'         => $path,
    1299                 'filename'     => $path_string,
    1300                 'filename_raw' => $relativepath,
    1301                 'editurl'      => ( current_user_can( 'edit_themes' ) ? $editurl : false ),
    1302                 'stringresult' => $file,
    1303             );
    1304         }
    1305 
    1306         $readfile = @fopen( $filename, 'r' );
    1307         if ( $readfile ) {
    1308             while ( ( $readline = fgets( $readfile ) ) !== false ) { // phpcs:ignore WordPress.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition
    1309                 $string_preview_is_cut = false;
    1310                 $linenum ++;
    1311                 /**
    1312                  * If our string is found in this line, output the line number and other data
    1313                  */
    1314                 if ( ( ! $regex && stristr( $readline, $string ) ) || ( $regex && preg_match( $string, $readline, $match, PREG_OFFSET_CAPTURE ) ) ) {
    1315                     /**
    1316                      * Prepare the visual path for the end user
    1317                      * Removes path leading up to WordPress root and ensures consistent directory separators
    1318                      */
    1319                     $relativepath = str_replace(
    1320                         array(
    1321                             ABSPATH,
    1322                             '\\',
    1323                             '/',
    1324                         ),
    1325                         array(
    1326                             '',
    1327                             DIRECTORY_SEPARATOR,
    1328                             DIRECTORY_SEPARATOR,
    1329                         ),
    1330                         $path
    1331                     );
    1332                     $match_count ++;
    1333 
    1334                     if ( $regex ) {
    1335                         $str_pos = $match[0][1];
    1336                     } else {
    1337                         $str_pos = stripos( $readline, $string );
    1338                     }
    1339 
    1340                     /**
    1341                      * Create the URL to take the user to the editor
    1342                      */
    1343                     $editurl = $this->create_edit_link( $path, $linenum, $str_pos );
    1344 
    1345                     $string_preview = $readline;
    1346                     if ( strlen( $string_preview ) > ( strlen( $string ) + $this->excerpt_length ) ) {
    1347                         $string_location = strpos( $string_preview, $string );
    1348 
    1349                         $string_location_start = $string_location - $this->excerpt_length;
    1350                         if ( $string_location_start < 0 ) {
    1351                             $string_location_start = 0;
    1352                         }
    1353 
    1354                         $string_location_end = ( strlen( $string ) + ( $this->excerpt_length * 2 ) );
    1355                         if ( $string_location_end > strlen( $string_preview ) ) {
    1356                             $string_location_end = strlen( $string_preview );
    1357                         }
    1358 
    1359                         $string_preview        = substr( $string_preview, $string_location_start, $string_location_end );
    1360                         $string_preview_is_cut = true;
    1361                     }
    1362 
    1363                     if ( $regex ) {
    1364                         $string_preview = preg_replace( preg_replace( '/\/(.+)\//', '/($1)/', $string ), '<strong>$1</strong>', esc_html( $string_preview ) );
    1365                     } else {
    1366                         $string_preview = preg_replace( '/(' . $string . ')/i', '<strong>$1</strong>', esc_html( $string_preview ) );
    1367                     }
    1368                     if ( $string_preview_is_cut ) {
    1369                         $string_preview = sprintf(
    1370                             '&hellip;%s&hellip;',
    1371                             $string_preview
    1372                         );
    1373                     }
    1374 
    1375                     $path_string = sprintf(
    1376                         '<a href="%s">%s</a>',
    1377                         esc_url( $editurl ),
    1378                         esc_html( $relativepath )
    1379                     );
    1380 
    1381                     $output[] = array(
    1382                         'ID'           => $match_count,
    1383                         'linenum'      => $linenum,
    1384                         'linepos'      => $str_pos,
    1385                         'path'         => $path,
    1386                         'filename'     => $path_string,
    1387                         'filename_raw' => $relativepath,
    1388                         'editurl'      => ( current_user_can( 'edit_themes' ) ? $editurl : false ),
    1389                         'stringresult' => $string_preview,
    1390                     );
    1391                 }
    1392             }
    1393 
    1394             fclose( $readfile );
    1395         } else {
    1396             /**
    1397              * The file was unreadable, give the user a friendly notification
    1398              */
    1399             $output[] = array(
    1400                 'linenum'      => '#',
    1401                 // translators: 1: Filename.
    1402                 'filename'     => esc_html( sprintf( __( 'Could not read file: %s', 'string-locator' ), $filename ) ),
    1403                 'stringresult' => '',
    1404             );
    1405         }
    1406 
    1407         return $output;
    1408     }
    1409 
    1410     function ajax_scan_path( $path ) {
    1411         $files = array();
    1412 
    1413         $paths = new RecursiveIteratorIterator(
    1414             new RecursiveDirectoryIterator( $path ),
    1415             RecursiveIteratorIterator::SELF_FIRST
    1416         );
    1417 
    1418         foreach ( $paths as $name => $location ) {
    1419             if ( is_dir( $location->getPathname() ) ) {
    1420                 continue;
    1421             }
    1422 
    1423             $files[] = $location->getPathname();
    1424         }
    1425 
    1426         return $files;
     620     * Check if a file path is valid for editing.
     621     *
     622     * @param string $path Path to file.
     623     *
     624     * @return bool
     625     */
     626    public static function is_valid_location( $path ) {
     627        $valid   = true;
     628        $path    = str_replace( array( '/' ), array( DIRECTORY_SEPARATOR ), stripslashes( $path ) );
     629        $abspath = str_replace( array( '/' ), array( DIRECTORY_SEPARATOR ), ABSPATH );
     630
     631        // Check that it is a valid file we are trying to access as well.
     632        if ( ! file_exists( $path ) ) {
     633            $valid = false;
     634        }
     635
     636        if ( empty( $path ) ) {
     637            $valid = false;
     638        }
     639        if ( stristr( $path, '..' ) ) {
     640            $valid = false;
     641        }
     642        if ( ! stristr( $path, $abspath ) ) {
     643            $valid = false;
     644        }
     645
     646        return $valid;
    1427647    }
    1428648}
  • string-locator/trunk/readme.txt

    r2566661 r2685592  
    66Tags: text, search, find, syntax, highlight
    77Requires at least: 4.9
    8 Tested up to: 5.8
    9 Stable tag: 2.4.2
     8Tested up to: 5.9
     9Stable tag: 2.5.0
    1010License: GPLv2 or later
    1111License URI: http://www.gnu.org/licenses/gpl-2.0.html
     
    4646== Changelog ==
    4747
    48 = 2.4.2 =
    49 * Fixed the option to restore previous search.
    50 * Fixed respecting text capitalization in previews when doing a non-regex search.
    51 * Changed capability checks, now works on hosts that maintain updates for their users.
     48= 2.5.0 (2022-02-27) =
     49* Fixed a bug where content would have slashes stripped unexpectedly.
     50* Improved table spacing on search results.
     51* Improved loopback checks to also check admin access.
     52* Hardened the search iterator so users can't accidentally perform unexpected directory traversal.
     53* Introduced actions and filters in various places to enable extenders, and future enhancements.
     54* Moved all ajax requests to dedicated REST endpoints.
     55* Refactored file structure.
    5256
    5357= Older entries =
  • string-locator/trunk/string-locator.php

    r2432238 r2685592  
    44 * Plugin URI: http://www.clorith.net/wordpress-string-locator/
    55 * Description: Scan through theme and plugin files looking for text strings
    6  * Version: 2.4.2
     6 * Version: 2.5.0
    77 * Author: Clorith
    88 * Author URI: http://www.clorith.net
     
    2626 */
    2727
     28namespace JITS\StringLocator;
     29
    2830if ( ! defined( 'ABSPATH' ) ) {
    2931    die();
     
    3335define( 'STRING_LOCATOR_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
    3436
    35 require_once( __DIR__ . '/includes/class-string-locator.php' );
     37/**
     38 * Plugin test runners
     39 */
     40require_once __DIR__ . '/includes/Tests/class-loopback.php';
     41require_once __DIR__ . '/includes/Tests/class-smart-scan.php';
     42
     43/**
     44 * Plugin action classes.
     45 */
     46require_once __DIR__ . '/includes/class-save.php';
     47require_once __DIR__ . '/includes/class-search.php';
     48require_once __DIR__ . '/includes/class-directory-iterator.php';
     49
     50/**
     51 * Prepare REST endpoints.
     52 */
     53require_once __DIR__ . '/includes/REST/class-base.php';
     54require_once __DIR__ . '/includes/REST/class-save.php';
     55require_once __DIR__ . '/includes/REST/class-clean.php';
     56require_once __DIR__ . '/includes/REST/class-search.php';
     57require_once __DIR__ . '/includes/REST/class-directory-structure.php';
    3658
    3759/**
    3860 * Instantiate the plugin
    3961 */
    40 $string_locator = new String_Locator();
     62require_once __DIR__ . '/includes/class-string-locator.php';
     63new String_Locator();
Note: See TracChangeset for help on using the changeset viewer.