mirror of
https://git.sindominio.net/estibadores/wordpress.git
synced 2024-11-14 23:21:07 +01:00
370 lines
10 KiB
PHP
370 lines
10 KiB
PHP
|
<?php
|
||
|
|
||
|
namespace SearchRegex;
|
||
|
|
||
|
use SearchRegex\Search_Regex;
|
||
|
|
||
|
/**
|
||
|
* Represents a source of data that can be searched. Typically maps directly to a database table
|
||
|
*/
|
||
|
abstract class Search_Source {
|
||
|
/** @var Search_Flags */
|
||
|
protected $search_flags;
|
||
|
/** @var Source_Flags */
|
||
|
protected $source_flags;
|
||
|
/** @var String */
|
||
|
protected $source_type;
|
||
|
/** @var String */
|
||
|
protected $source_name;
|
||
|
|
||
|
/**
|
||
|
* Create a Search_Source object
|
||
|
*
|
||
|
* @param Array $handler Source handler information - an array of `name`, `class`, `label`, and `type`.
|
||
|
* @param Search_Flags $search_flags A Search_Flags object.
|
||
|
* @param Source_Flags $source_flags A Source_Flags object.
|
||
|
*/
|
||
|
public function __construct( array $handler, Search_Flags $search_flags, Source_Flags $source_flags ) {
|
||
|
$this->search_flags = $search_flags;
|
||
|
$this->source_flags = $source_flags;
|
||
|
|
||
|
$this->source_type = isset( $handler['name'] ) ? $handler['name'] : 'unknown';
|
||
|
$this->source_name = isset( $handler['label'] ) ? $handler['label'] : $this->source_type;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Return the source type
|
||
|
*
|
||
|
* @param Array $row Database row, used in some sources to determine the type.
|
||
|
* @return String Source type
|
||
|
*/
|
||
|
public function get_type( array $row ) {
|
||
|
return $this->source_type;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Return the source name
|
||
|
*
|
||
|
* @param Array $row Database row, used in some sources to determine the type.
|
||
|
* @return String A user viewable source name
|
||
|
*/
|
||
|
public function get_name( array $row ) {
|
||
|
return $this->source_name;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Return the associated Source_Flags
|
||
|
*
|
||
|
* @return Source_Flags Source_Flags object
|
||
|
*/
|
||
|
public function get_source_flags() {
|
||
|
return $this->source_flags;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Return an array of columns for this source
|
||
|
*
|
||
|
* @return Array The array of column names
|
||
|
*/
|
||
|
abstract public function get_columns();
|
||
|
|
||
|
/**
|
||
|
* Return an array of additional columns to return in a search. These aren't searched, and can be used by the source.
|
||
|
*
|
||
|
* @return Array The array of column names
|
||
|
*/
|
||
|
public function get_info_columns() {
|
||
|
return [];
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Return an the table's ID column name
|
||
|
*
|
||
|
* @return String The table's ID column name
|
||
|
*/
|
||
|
abstract public function get_table_id();
|
||
|
|
||
|
/**
|
||
|
* Return the column name used as a visible title for the source. For example, a post would have `post_title`
|
||
|
*
|
||
|
* @return String Column used for the title
|
||
|
*/
|
||
|
abstract public function get_title_column();
|
||
|
|
||
|
/**
|
||
|
* Return the table name
|
||
|
*
|
||
|
* @return String The table name for the source
|
||
|
*/
|
||
|
abstract public function get_table_name();
|
||
|
|
||
|
/**
|
||
|
* Return an array of additional search conditions applied to each query. These will be ANDed together.
|
||
|
* These conditions should be sanitized here, and won't be sanitized elsewhere.
|
||
|
*
|
||
|
* @param String $search Search phrase.
|
||
|
* @return Array Array of SQL condition
|
||
|
*/
|
||
|
public function get_search_conditions( $search ) {
|
||
|
return [];
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Return a visible label for the column. This is shown to the user and should be more descriptive than the column name itself
|
||
|
*
|
||
|
* @param String $column Column name.
|
||
|
* @return String Column label
|
||
|
*/
|
||
|
public function get_column_label( $column ) {
|
||
|
return $column;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Return an array of flags used by this source via Source_Flags. Used primarily to validate the flag.
|
||
|
*
|
||
|
* @return Array The array of flags
|
||
|
*/
|
||
|
public function get_supported_flags() {
|
||
|
return [];
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get an array of actions for a given row
|
||
|
*
|
||
|
* @param Result $result The Result object containing the row from the source.
|
||
|
* @return Array An array of action type => action URL
|
||
|
*/
|
||
|
public function get_actions( Result $result ) {
|
||
|
return [];
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get the total number of matches for this search
|
||
|
*
|
||
|
* @param String $search Search string.
|
||
|
* @return Array<String,Int>|\WP_Error The number of matches as an array of 'matches' and 'rows', or WP_Error on error
|
||
|
*/
|
||
|
public function get_total_matches( $search ) {
|
||
|
global $wpdb;
|
||
|
|
||
|
$search_query = $this->get_search_query( $search );
|
||
|
|
||
|
// Sum all the matches
|
||
|
$sum = [];
|
||
|
foreach ( $this->get_columns() as $column ) {
|
||
|
$cropped = mb_substr( $search, 0, mb_strlen( $search, 'UTF-8' ) - 1, 'UTF-8' );
|
||
|
|
||
|
// phpcs:ignore
|
||
|
$sum[] = $wpdb->prepare( "SUM( CHAR_LENGTH( $column ) - CHAR_LENGTH( REPLACE( UPPER($column), UPPER(%s), UPPER(%s) ) ) )", $search, $cropped );
|
||
|
}
|
||
|
|
||
|
// This is a known and validated query
|
||
|
// phpcs:ignore
|
||
|
$result = $wpdb->get_row( "SELECT COUNT(*) AS match_rows, " . implode( ' + ', $sum ) . " AS match_total FROM {$this->get_table_name()} WHERE " . $search_query );
|
||
|
if ( $result === null ) {
|
||
|
return new \WP_Error( 'searchregex_database', $wpdb->last_error, 401 );
|
||
|
}
|
||
|
|
||
|
return [
|
||
|
'matches' => intval( $result->match_total, 10 ),
|
||
|
'rows' => intval( $result->match_rows, 10 ),
|
||
|
];
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get total number of rows for this source
|
||
|
*
|
||
|
* @return Int|\WP_Error The number of rows, or WP_Error on error
|
||
|
*/
|
||
|
public function get_total_rows() {
|
||
|
global $wpdb;
|
||
|
|
||
|
// This is a known and validated query
|
||
|
// phpcs:ignore
|
||
|
$result = $wpdb->get_var( "SELECT COUNT(*) FROM {$this->get_table_name()}" );
|
||
|
if ( $result === null ) {
|
||
|
return new \WP_Error( 'searchregex_database', $wpdb->last_error, 401 );
|
||
|
}
|
||
|
|
||
|
return intval( $result, 10 );
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get a single row from the source
|
||
|
*
|
||
|
* @param int $row_id The row ID.
|
||
|
* @return Object|\WP_Error The database row, or WP_Error on error
|
||
|
*/
|
||
|
public function get_row( $row_id ) {
|
||
|
global $wpdb;
|
||
|
|
||
|
$columns = $this->get_query_columns();
|
||
|
|
||
|
// $columns, get_table_id, and get_table_name are sanitized for any user input
|
||
|
// phpcs:ignore
|
||
|
$row = $wpdb->get_row( $wpdb->prepare( "SELECT {$columns} FROM {$this->get_table_name()} WHERE {$this->get_table_id()}=%d", $row_id ), ARRAY_A );
|
||
|
if ( $row === null ) {
|
||
|
return new \WP_Error( 'searchregex_database', $wpdb->last_error, 401 );
|
||
|
}
|
||
|
|
||
|
return $row;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get all database rows for the source from the start offset to the limit
|
||
|
*
|
||
|
* @param String $search The search phrase.
|
||
|
* @param int $offset The row offset.
|
||
|
* @param int $limit The number of rows to return.
|
||
|
* @return Array|\WP_Error The database rows, or WP_Error on error
|
||
|
*/
|
||
|
public function get_all_rows( $search, $offset, $limit ) {
|
||
|
global $wpdb;
|
||
|
|
||
|
$columns = $this->get_query_columns();
|
||
|
|
||
|
// Add any source specific conditions
|
||
|
$source_conditions = $this->get_search_conditions( $search );
|
||
|
$search_phrase = '';
|
||
|
if ( count( $source_conditions ) > 0 ) {
|
||
|
$search_phrase = ' WHERE ' . implode( ' AND ', $source_conditions );
|
||
|
}
|
||
|
|
||
|
// This is a known and validated query
|
||
|
// phpcs:ignore
|
||
|
$results = $wpdb->get_results( $wpdb->prepare( "SELECT {$columns} FROM {$this->get_table_name()}{$search_phrase} ORDER BY {$this->get_table_id()} ASC LIMIT %d,%d", $offset, $limit ), ARRAY_A );
|
||
|
if ( $results === false || $wpdb->last_error ) {
|
||
|
return new \WP_Error( 'searchregex_database', $wpdb->last_error, 401 );
|
||
|
}
|
||
|
|
||
|
return $results;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get a set of matching rows
|
||
|
*
|
||
|
* @param String $search The search string.
|
||
|
* @param int $offset The row offset.
|
||
|
* @param int $limit The number of rows to return.
|
||
|
* @return Array|\WP_Error The database rows, or WP_Error on error
|
||
|
*/
|
||
|
public function get_matched_rows( $search, $offset, $limit ) {
|
||
|
global $wpdb;
|
||
|
|
||
|
$search_query = $this->get_search_query( $search );
|
||
|
$columns = $this->get_query_columns();
|
||
|
|
||
|
// This is a known and validated query
|
||
|
// phpcs:ignore
|
||
|
$results = $wpdb->get_results( $wpdb->prepare( "SELECT {$columns} FROM {$this->get_table_name()} WHERE {$search_query} ORDER BY {$this->get_table_id()} ASC LIMIT %d,%d", $offset, $limit ), ARRAY_A );
|
||
|
if ( $results === false || $wpdb->last_error ) {
|
||
|
return new \WP_Error( 'searchregex_database', $wpdb->last_error, 401 );
|
||
|
}
|
||
|
|
||
|
return $results;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Save a replacement to the database
|
||
|
*
|
||
|
* @param int $row_id The row ID to save.
|
||
|
* @param String $column_id The column to save.
|
||
|
* @param String $content The value to save to the column in the row.
|
||
|
* @return Bool|\WP_Error True on success, or WP_Error on error
|
||
|
*/
|
||
|
public function save( $row_id, $column_id, $content ) {
|
||
|
global $wpdb;
|
||
|
|
||
|
// Final check that is specific to this handler. The API check is general over all handlers
|
||
|
$columns = $this->get_columns();
|
||
|
if ( ! in_array( $column_id, $columns, true ) ) {
|
||
|
return new \WP_Error( 'searchregex_database', 'Unknown column for database' );
|
||
|
}
|
||
|
|
||
|
$result = $wpdb->update( $this->get_table_name(), [ $column_id => $content ], [ $this->get_table_id() => $row_id ] );
|
||
|
|
||
|
if ( $result === null ) {
|
||
|
return new \WP_Error( 'searchregex_database', $wpdb->last_error, 401 );
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Delete a row from the source
|
||
|
*
|
||
|
* @param int $row_id The row ID.
|
||
|
* @return Bool|\WP_Error true on success, or WP_Error on error
|
||
|
*/
|
||
|
public function delete_row( $row_id ) {
|
||
|
global $wpdb;
|
||
|
|
||
|
if ( $wpdb->delete( $this->get_table_name(), [ $this->get_table_id() => $row_id ] ) === false ) {
|
||
|
return new \WP_Error( 'searchregex_database', $wpdb->last_error, 401 );
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns database columns in SQL format
|
||
|
*
|
||
|
* @internal
|
||
|
* @return String SQL string
|
||
|
*/
|
||
|
protected function get_query_columns() {
|
||
|
$columns = array_merge(
|
||
|
[ $this->get_table_id() ],
|
||
|
$this->get_columns(),
|
||
|
$this->get_info_columns()
|
||
|
);
|
||
|
|
||
|
return implode( ', ', $columns );
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns a LIKE query for a given column and search phrase
|
||
|
*
|
||
|
* @internal
|
||
|
* @param String $column Column name.
|
||
|
* @param String $search Search phrase.
|
||
|
* @return String SQL string
|
||
|
*/
|
||
|
protected function get_search_query_as_like( $column, $search ) {
|
||
|
global $wpdb;
|
||
|
|
||
|
if ( $this->search_flags->is_case_insensitive() ) {
|
||
|
return 'UPPER(' . $column . ') ' . $wpdb->prepare( 'LIKE %s', '%' . $wpdb->esc_like( strtoupper( $search ) ) . '%' );
|
||
|
}
|
||
|
|
||
|
return $column . ' ' . $wpdb->prepare( 'LIKE %s', '%' . $wpdb->esc_like( $search ) . '%' );
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the search for each of the columns in SQL format
|
||
|
*
|
||
|
* @internal
|
||
|
* @param String $search Search phrase.
|
||
|
* @return String SQL string
|
||
|
*/
|
||
|
protected function get_search_query( $search ) {
|
||
|
$source_matches = [];
|
||
|
|
||
|
// Look for the text in all the columns
|
||
|
foreach ( $this->get_columns() as $column ) {
|
||
|
$source_matches[] = $this->get_search_query_as_like( $column, $search );
|
||
|
}
|
||
|
|
||
|
// Add any source specific conditions
|
||
|
$source_conditions = $this->get_search_conditions( $search );
|
||
|
|
||
|
$search_phrase = '(' . implode( ' OR ', $source_matches ) . ')';
|
||
|
$conditions = '';
|
||
|
if ( count( $source_conditions ) > 0 ) {
|
||
|
$conditions = ' AND ' . implode( ' AND ', $source_conditions );
|
||
|
}
|
||
|
|
||
|
return $search_phrase . $conditions;
|
||
|
}
|
||
|
}
|