Skip to content

Instantly share code, notes, and snippets.

@pento
Last active July 6, 2017 03:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pento/714bfd58c423ff06a37d8268a5af5cd3 to your computer and use it in GitHub Desktop.
Save pento/714bfd58c423ff06a37d8268a5af5cd3 to your computer and use it in GitHub Desktop.
🙃db, the next evolution of wpdb.
<?php
class 🙃db extends wpdb {
public $tables🙃 = array(
// Tables
'posts' => '⭕',
'postmeta' => '⭕➡️',
'comments' => '♻',
'commentmeta' => '♻➡️',
'terms' => '↔️',
'termmeta' => '↔️➡️',
'term_taxonomy' => '↔️➰',
'term_relationships' => '↔️➿',
'options' => 'ℹ',
'links' => '❌',
// Global tables
'users' => '⚜',
'usermeta' => '⚜➡️',
// Multisite global tables
'blogs' => '✴',
'signups' => '‼',
'site' => '❇',
'sitemeta' => '❇➡️',
'sitecategories' => '❇↔️',
'registration_log' => '⁉_〽',
'blog_versions' => '✴_〰',
);
public function tables( $scope = 'all', $prefix = true, $blog_id = 0 ) {
$tables = parent::tables( $scope, $prefix, $blog_id );
foreach ( $this->tables🙃 as $name => $replacement ) {
if ( $prefix && ! empty( $tables[ $name ] ) ) {
$tables[ $name ] = str_replace( $name, $replacement, $tables[ $name ] );
}
if ( ! $prefix && in_array( $name, $tables, true ) ) {
$tables = str_replace( $name, $replacement, $tables );
}
}
return $tables;
}
protected function get_table_from_query( $query ) {
// Remove characters that can legally trail the table name.
$query = rtrim( $query, ';/-#' );
// Allow (select...) union [...] style queries. Use the first query's table name.
$query = ltrim( $query, "\r\n\t (" );
// Strip everything between parentheses except nested selects.
$query = preg_replace( '/\((?!\s*select)[^(]*?\)/is', '()', $query );
// Quickly match most common queries.
if ( preg_match( '/^\s*(?:'
. 'SELECT.*?\s+FROM'
. '|INSERT(?:\s+LOW_PRIORITY|\s+DELAYED|\s+HIGH_PRIORITY)?(?:\s+IGNORE)?(?:\s+INTO)?'
. '|REPLACE(?:\s+LOW_PRIORITY|\s+DELAYED)?(?:\s+INTO)?'
. '|UPDATE(?:\s+LOW_PRIORITY)?(?:\s+IGNORE)?'
. '|DELETE(?:\s+LOW_PRIORITY|\s+QUICK|\s+IGNORE)*(?:.+?FROM)?'
. ')\s+((?:[^\s,])+)/is', $query, $maybe ) ) {
return str_replace( '`', '', $maybe[1] );
}
// SHOW TABLE STATUS and SHOW TABLES WHERE Name = 'wp_posts'
if ( preg_match( '/^\s*SHOW\s+(?:TABLE\s+STATUS|(?:FULL\s+)?TABLES).+WHERE\s+Name\s*=\s*("|\')((?:[^\s,])+)\\1/is', $query, $maybe ) ) {
return $maybe[2];
}
// SHOW TABLE STATUS LIKE and SHOW TABLES LIKE 'wp\_123\_%'
// This quoted LIKE operand seldom holds a full table name.
// It is usually a pattern for matching a prefix so we just
// strip the trailing % and unescape the _ to get 'wp_123_'
// which drop-ins can use for routing these SQL statements.
if ( preg_match( '/^\s*SHOW\s+(?:TABLE\s+STATUS|(?:FULL\s+)?TABLES)\s+(?:WHERE\s+Name\s+)?LIKE\s*("|\')((?:[^\s,])+)%?\\1/is', $query, $maybe ) ) {
return str_replace( '\\_', '_', $maybe[2] );
}
// Big pattern for the rest of the table-related queries.
if ( preg_match( '/^\s*(?:'
. '(?:EXPLAIN\s+(?:EXTENDED\s+)?)?SELECT.*?\s+FROM'
. '|DESCRIBE|DESC|EXPLAIN|HANDLER'
. '|(?:LOCK|UNLOCK)\s+TABLE(?:S)?'
. '|(?:RENAME|OPTIMIZE|BACKUP|RESTORE|CHECK|CHECKSUM|ANALYZE|REPAIR).*\s+TABLE'
. '|TRUNCATE(?:\s+TABLE)?'
. '|CREATE(?:\s+TEMPORARY)?\s+TABLE(?:\s+IF\s+NOT\s+EXISTS)?'
. '|ALTER(?:\s+IGNORE)?\s+TABLE'
. '|DROP\s+TABLE(?:\s+IF\s+EXISTS)?'
. '|CREATE(?:\s+\w+)?\s+INDEX.*\s+ON'
. '|DROP\s+INDEX.*\s+ON'
. '|LOAD\s+DATA.*INFILE.*INTO\s+TABLE'
. '|(?:GRANT|REVOKE).*ON\s+TABLE'
. '|SHOW\s+(?:.*FROM|.*TABLE)'
. ')\s+\(*\s*((?:[^\s,])+)\s*\)*/is', $query, $maybe ) ) {
return str_replace( '`', '', $maybe[1] );
}
return false;
}
public function query( $query ) {
// WP_Date_Query::validate_column() strips non-ASCII table names, which makes 🙃db sad.
$known_columns = array(
$this->posts => array(
'post_date',
'post_date_gmt',
'post_modified',
'post_modified_gmt',
),
$this->comments => array(
'comment_date',
'comment_date_gmt',
),
$this->users => array(
'user_registered',
),
$this->blogs => array(
'registered',
'last_updated',
),
);
foreach ( $known_columns as $table => $columns ) {
foreach ( $columns as $column ) {
$query = str_replace( "$this->prefix.$column", "$table.$column", $query );
}
}
// Tests_List_Pages::setUp() assumes the posts table name.
$query = str_replace( "TRUNCATE {$this->prefix}posts", "TRUNCATE $this->posts", $query );
// WP_User_Query::prepare_query() assumes the posts table name.
$wrong_name = $this->prefix . 'posts';
$right_name = $this->tables()['posts'];
$query = str_replace( "FROM $wrong_name WHERE", "FROM $right_name WHERE", $query );
$query = str_replace( " $wrong_name.", " $right_name.", $query );
$matches = array();
if ( is_multisite() && preg_match( "/{$this->prefix}(\d+)_posts/", $query, $matches ) ) {
$wrong_name = $this->prefix . $matches[1] . '_posts';
$right_name = $this->tables( 'all', true, $matches[1] )['posts'];
$query = str_replace( "FROM $wrong_name WHERE", "FROM $right_name WHERE", $query );
$query = str_replace( " $wrong_name.", " $right_name.", $query );
}
// No need to check DROP TABLE queries
if ( strpos( $query, 'DROP TABLE' ) === 0 ) {
$this->check_current_query = false;
}
return parent::query( $query );
}
}
$wpdb = new 🙃db( DB_USER, DB_PASSWORD, DB_NAME, DB_HOST );
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment