├── version.php
├── load.php
├── wp-includes
├── sqlite-ast
│ ├── class-wp-sqlite-driver-exception.php
│ ├── class-wp-sqlite-information-schema-exception.php
│ ├── class-wp-sqlite-connection.php
│ ├── class-wp-sqlite-configurator.php
│ └── class-wp-sqlite-information-schema-reconstructor.php
├── mysql
│ ├── class-wp-mysql-parser.php
│ └── class-wp-mysql-token.php
├── parser
│ ├── class-wp-parser-token.php
│ ├── class-wp-parser.php
│ ├── class-wp-parser-grammar.php
│ └── class-wp-parser-node.php
└── sqlite
│ ├── db.php
│ ├── install-functions.php
│ ├── class-wp-sqlite-query-rewriter.php
│ ├── class-wp-sqlite-token.php
│ ├── class-wp-sqlite-db.php
│ └── class-wp-sqlite-pdo-user-defined-functions.php
├── php-polyfills.php
├── constants.php
├── db.copy
├── readme.txt
├── deactivate.php
├── admin-notices.php
├── health-check.php
├── integrations
└── query-monitor
│ ├── plugin.php
│ └── boot.php
├── activate.php
├── admin-page.php
└── LICENSE
/version.php:
--------------------------------------------------------------------------------
1 | code = $code;
27 | $this->driver = $driver;
28 | }
29 |
30 | public function getDriver(): WP_SQLite_Driver {
31 | return $this->driver;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/wp-includes/mysql/class-wp-mysql-parser.php:
--------------------------------------------------------------------------------
1 | next_query() ) {
22 | * $ast = $parser->get_query_ast();
23 | * if ( ! $ast ) {
24 | * // The parsing failed.
25 | * }
26 | * // The query was successfully parsed.
27 | * }
28 | *
29 | * @return bool Whether a query was successfully parsed.
30 | */
31 | public function next_query(): bool {
32 | if ( $this->position >= count( $this->tokens ) ) {
33 | return false;
34 | }
35 | $this->current_ast = $this->parse();
36 | return true;
37 | }
38 |
39 | /**
40 | * Get the current query AST.
41 | *
42 | * When no query has been parsed yet, the parsing failed, or the end of the
43 | * input was reached, this method returns null.
44 | *
45 | * @see WP_MySQL_Parser::next_query() for usage example.
46 | *
47 | * @return WP_Parser_Node|null The current query AST, or null if no query was parsed.
48 | */
49 | public function get_query_ast(): ?WP_Parser_Node {
50 | return $this->current_ast;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/php-polyfills.php:
--------------------------------------------------------------------------------
1 | id = $id;
55 | $this->start = $start;
56 | $this->length = $length;
57 | $this->input = $input;
58 | }
59 |
60 | /**
61 | * Get the raw bytes of the token from the input.
62 | *
63 | * @return string The token bytes.
64 | */
65 | public function get_bytes(): string {
66 | return substr( $this->input, $this->start, $this->length );
67 | }
68 |
69 | /**
70 | * Get the real unquoted value of the token.
71 | *
72 | * @return string The token value.
73 | */
74 | public function get_value(): string {
75 | return $this->get_bytes();
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/db.copy:
--------------------------------------------------------------------------------
1 | delete( WP_CONTENT_DIR . '/db.php' );
29 | // Flush the cache again to mitigate a possible race condition.
30 | wp_cache_flush();
31 | }
32 |
33 | // Run an action on `shutdown`, to deactivate the option in the MySQL database.
34 | add_action(
35 | 'shutdown',
36 | function () {
37 | global $table_prefix;
38 |
39 | // Get credentials for the MySQL database.
40 | $dbuser = defined( 'DB_USER' ) ? DB_USER : '';
41 | $dbpassword = defined( 'DB_PASSWORD' ) ? DB_PASSWORD : '';
42 | $dbname = defined( 'DB_NAME' ) ? DB_NAME : '';
43 | $dbhost = defined( 'DB_HOST' ) ? DB_HOST : '';
44 |
45 | // Init a connection to the MySQL database.
46 | $wpdb_mysql = new wpdb( $dbuser, $dbpassword, $dbname, $dbhost );
47 | $wpdb_mysql->set_prefix( $table_prefix );
48 |
49 | // Get the perflab options, remove the database/sqlite module and update the option.
50 | $row = $wpdb_mysql->get_row( $wpdb_mysql->prepare( "SELECT option_value FROM $wpdb_mysql->options WHERE option_name = %s LIMIT 1", 'active_plugins' ) );
51 | if ( is_object( $row ) ) {
52 | $value = maybe_unserialize( $row->option_value );
53 | if ( is_array( $value ) ) {
54 | $value_flipped = array_flip( $value );
55 | $items = array_reverse( explode( DIRECTORY_SEPARATOR, SQLITE_MAIN_FILE ) );
56 | $item = $items[1] . DIRECTORY_SEPARATOR . $items[0];
57 | unset( $value_flipped[ $item ] );
58 | $value = array_flip( $value_flipped );
59 | $wpdb_mysql->update( $wpdb_mysql->options, array( 'option_value' => maybe_serialize( $value ) ), array( 'option_name' => 'active_plugins' ) );
60 | }
61 | }
62 | },
63 | PHP_INT_MAX
64 | );
65 | // Flush any persistent cache.
66 | wp_cache_flush();
67 | }
68 | register_deactivation_hook( SQLITE_MAIN_FILE, 'sqlite_plugin_remove_db_file' ); // Remove db.php file on plugin deactivation.
69 |
--------------------------------------------------------------------------------
/admin-notices.php:
--------------------------------------------------------------------------------
1 | base ) && 'settings_page_sqlite-integration' === $current_screen->base ) {
19 | return;
20 | }
21 |
22 | // If PDO SQLite is not loaded, bail early.
23 | if ( ! extension_loaded( 'pdo_sqlite' ) ) {
24 | printf(
25 | '
%s
',
26 | esc_html__( 'The SQLite Integration plugin is active, but the PDO SQLite extension is missing from your server. Please make sure that PDO SQLite is enabled in your PHP installation.', 'sqlite-database-integration' )
27 | );
28 | return;
29 | }
30 |
31 | /*
32 | * If the SQLITE_DB_DROPIN_VERSION constant is not defined
33 | * but there's a db.php file in the wp-content directory, then the module can't be activated.
34 | * The module should not have been activated in the first place
35 | * (there's a check in the can-load.php file), but this is a fallback check.
36 | */
37 | if ( file_exists( WP_CONTENT_DIR . '/db.php' ) && ! defined( 'SQLITE_DB_DROPIN_VERSION' ) ) {
38 | printf(
39 | '
%s
',
40 | sprintf(
41 | /* translators: 1: SQLITE_DB_DROPIN_VERSION constant, 2: db.php drop-in path */
42 | __( 'The SQLite Integration module is active, but the %1$s constant is missing. It appears you already have another %2$s file present on your site. ', 'sqlite-database-integration' ),
43 | 'SQLITE_DB_DROPIN_VERSION',
44 | '' . esc_html( basename( WP_CONTENT_DIR ) ) . '/db.php'
45 | )
46 | );
47 |
48 | return;
49 | }
50 |
51 | if ( file_exists( WP_CONTENT_DIR . '/db.php' ) ) {
52 | return;
53 | }
54 |
55 | if ( ! wp_is_writable( WP_CONTENT_DIR ) ) {
56 | printf(
57 | '
%s
',
58 | esc_html__( 'The SQLite Integration plugin is active, but the wp-content/db.php file is missing and the wp-content directory is not writable. Please ensure the wp-content folder is writable, then deactivate the plugin and try again.', 'sqlite-database-integration' )
59 | );
60 | return;
61 | }
62 | // The dropin db.php is missing.
63 | printf(
64 | '
%s
',
65 | sprintf(
66 | /* translators: 1: db.php drop-in path, 2: Admin URL to deactivate the module */
67 | __( 'The SQLite Integration plugin is active, but the %1$s file is missing. Please deactivate the plugin and re-activate it to try again.', 'sqlite-database-integration' ),
68 | '' . esc_html( basename( WP_CONTENT_DIR ) ) . '/db.php',
69 | esc_url( admin_url( 'plugins.php' ) )
70 | )
71 | );
72 | }
73 | add_action( 'admin_notices', 'sqlite_plugin_admin_notice' ); // Add the admin notices.
74 |
75 | // Remove the PL-plugin admin notices for SQLite.
76 | remove_action( 'admin_notices', 'perflab_sqlite_plugin_admin_notice' );
77 |
--------------------------------------------------------------------------------
/wp-includes/sqlite/db.php:
--------------------------------------------------------------------------------
1 | %1$s
%2$s
',
28 | 'PHP PDO Extension is not loaded',
29 | 'Your PHP installation appears to be missing the PDO extension which is required for this version of WordPress and the type of database you have specified.'
30 | )
31 | ),
32 | 'PHP PDO Extension is not loaded.'
33 | );
34 | }
35 |
36 | if ( ! extension_loaded( 'pdo_sqlite' ) ) {
37 | wp_die(
38 | new WP_Error(
39 | 'pdo_driver_not_loaded',
40 | sprintf(
41 | '
%1$s
%2$s
',
42 | 'PDO Driver for SQLite is missing',
43 | 'Your PHP installation appears not to have the right PDO drivers loaded. These are required for this version of WordPress and the type of database you have specified.'
44 | )
45 | ),
46 | 'PDO Driver for SQLite is missing.'
47 | );
48 | }
49 |
50 | require_once __DIR__ . '/class-wp-sqlite-lexer.php';
51 | require_once __DIR__ . '/class-wp-sqlite-query-rewriter.php';
52 | require_once __DIR__ . '/class-wp-sqlite-translator.php';
53 | require_once __DIR__ . '/class-wp-sqlite-token.php';
54 | require_once __DIR__ . '/class-wp-sqlite-pdo-user-defined-functions.php';
55 | require_once __DIR__ . '/class-wp-sqlite-db.php';
56 | require_once __DIR__ . '/install-functions.php';
57 |
58 | /**
59 | * The DB_NAME constant is required by the new SQLite driver.
60 | *
61 | * There are some existing projects in which the DB_NAME constant is missing in
62 | * wp-config.php. To enable easier early adoption and testing of the new SQLite
63 | * driver, let's allow using a default database name when DB_NAME is not set.
64 | *
65 | * TODO: For version 3.0, enforce the DB_NAME constant and remove the fallback.
66 | */
67 | if ( defined( 'DB_NAME' ) && '' !== DB_NAME ) {
68 | $db_name = DB_NAME;
69 | } else {
70 | $db_name = apply_filters( 'wp_sqlite_default_db_name', 'database_name_here' );
71 | }
72 |
73 | /*
74 | * Debug: Cross-check with MySQL.
75 | * This is for debugging purpose only and requires files
76 | * that are present in the GitHub repository
77 | * but not the plugin published on WordPress.org.
78 | */
79 | $crosscheck_tests_file_path = dirname( __DIR__, 2 ) . '/tests/class-wp-sqlite-crosscheck-db.php';
80 | if ( defined( 'SQLITE_DEBUG_CROSSCHECK' ) && SQLITE_DEBUG_CROSSCHECK && file_exists( $crosscheck_tests_file_path ) ) {
81 | require_once $crosscheck_tests_file_path;
82 | $GLOBALS['wpdb'] = new WP_SQLite_Crosscheck_DB( $db_name );
83 | } else {
84 | $GLOBALS['wpdb'] = new WP_SQLite_DB( $db_name );
85 |
86 | // Boot the Query Monitor plugin if it is active.
87 | require_once dirname( __DIR__, 2 ) . '/integrations/query-monitor/boot.php';
88 | }
89 |
--------------------------------------------------------------------------------
/health-check.php:
--------------------------------------------------------------------------------
1 | 'DB_ENGINE',
22 | 'value' => ( defined( 'DB_ENGINE' ) ? DB_ENGINE : __( 'Undefined', 'sqlite-database-integration' ) ),
23 | 'debug' => ( defined( 'DB_ENGINE' ) ? DB_ENGINE : 'undefined' ),
24 | );
25 |
26 | $info['wp-database']['fields']['db_engine'] = array(
27 | 'label' => __( 'Database type', 'sqlite-database-integration' ),
28 | 'value' => 'sqlite' === $db_engine ? 'SQLite' : 'MySQL/MariaDB',
29 | );
30 |
31 | if ( 'sqlite' === $db_engine ) {
32 | $info['wp-database']['fields']['database_version'] = array(
33 | 'label' => __( 'SQLite version', 'sqlite-database-integration' ),
34 | 'value' => $info['wp-database']['fields']['server_version'] ?? null,
35 | );
36 |
37 | $info['wp-database']['fields']['database_file'] = array(
38 | 'label' => __( 'Database file', 'sqlite-database-integration' ),
39 | 'value' => FQDB,
40 | 'private' => true,
41 | );
42 |
43 | $info['wp-database']['fields']['database_size'] = array(
44 | 'label' => __( 'Database size', 'sqlite-database-integration' ),
45 | 'value' => size_format( filesize( FQDB ) ),
46 | );
47 |
48 | unset( $info['wp-database']['fields']['extension'] );
49 | unset( $info['wp-database']['fields']['server_version'] );
50 | unset( $info['wp-database']['fields']['client_version'] );
51 | unset( $info['wp-database']['fields']['database_host'] );
52 | unset( $info['wp-database']['fields']['database_user'] );
53 | unset( $info['wp-database']['fields']['database_name'] );
54 | unset( $info['wp-database']['fields']['database_charset'] );
55 | unset( $info['wp-database']['fields']['database_collate'] );
56 | unset( $info['wp-database']['fields']['max_allowed_packet'] );
57 | unset( $info['wp-database']['fields']['max_connections'] );
58 | }
59 |
60 | return $info;
61 | }
62 | add_filter( 'debug_information', 'sqlite_plugin_filter_debug_data' ); // Filter debug data in site-health screen.
63 |
64 | /**
65 | * Filter site_status tests in site-health screen.
66 | *
67 | * When the plugin gets merged in wp-core, these should be merged in src/wp-admin/includes/class-wp-site-health.php
68 | *
69 | * @param array $tests The tests.
70 | * @return array
71 | */
72 | function sqlite_plugin_filter_site_status_tests( $tests ) {
73 | $db_engine = defined( 'DB_ENGINE' ) && 'sqlite' === DB_ENGINE ? 'sqlite' : 'mysql';
74 |
75 | if ( 'sqlite' === $db_engine ) {
76 | unset( $tests['direct']['utf8mb4_support'] );
77 | unset( $tests['direct']['sql_server'] );
78 | unset( $tests['direct']['persistent_object_cache'] ); // Throws an error because DB_NAME is not defined.
79 | }
80 |
81 | return $tests;
82 | }
83 | add_filter( 'site_status_tests', 'sqlite_plugin_filter_site_status_tests' );
84 |
--------------------------------------------------------------------------------
/integrations/query-monitor/plugin.php:
--------------------------------------------------------------------------------
1 | $row The row data.
19 | * @param array $cols The column names.
20 | * @return void
21 | */
22 | protected function output_query_row( array $row, array $cols ) {
23 | // Capture the query row HTML.
24 | ob_start();
25 | parent::output_query_row( $row, $cols );
26 | $data = ob_get_length() > 0 ? ob_get_clean() : '';
27 |
28 | // Get the corresponding SQLite queries.
29 | global $wpdb;
30 | static $query_index = 0;
31 | $sqlite_queries = $wpdb->queries[ $query_index ]['sqlite_queries'] ?? array();
32 | $sqlite_query_count = count( $sqlite_queries );
33 | $query_index += 1;
34 |
35 | // Build the SQLite info HTML.
36 | $sqlite_info = sprintf(
37 | '
142 | ' . __( 'Database: SQLite', 'sqlite-database-integration' ) . $suffix . '';
160 | } elseif ( stripos( $wpdb->db_server_info(), 'maria' ) !== false ) {
161 | $title = '' . __( 'Database: MariaDB', 'sqlite-database-integration' ) . '';
162 | } else {
163 | $title = '' . __( 'Database: MySQL', 'sqlite-database-integration' ) . '';
164 | }
165 |
166 | $args = array(
167 | 'id' => 'sqlite-db-integration',
168 | 'parent' => 'top-secondary',
169 | 'title' => $title,
170 | 'href' => esc_url( admin_url( 'options-general.php?page=sqlite-integration' ) ),
171 | 'meta' => false,
172 | );
173 | $admin_bar->add_node( $args );
174 | }
175 | add_action( 'admin_bar_menu', 'sqlite_plugin_adminbar_item', 999 );
176 |
--------------------------------------------------------------------------------
/wp-includes/sqlite/install-functions.php:
--------------------------------------------------------------------------------
1 | = 80400 ? PDO\SQLite::class : PDO::class; // phpcs:ignore WordPress.DB.RestrictedClasses.mysql__PDO
29 | $pdo = new $pdo_class( 'sqlite:' . FQDB, null, null, array( PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION ) ); // phpcs:ignore WordPress.DB.RestrictedClasses
30 | } catch ( PDOException $err ) {
31 | $err_data = $err->errorInfo; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
32 | $message = 'Database connection error! ';
33 | $message .= sprintf( 'Error message is: %s', $err_data[2] );
34 | wp_die( $message, 'Database Error!' );
35 | }
36 |
37 | if ( defined( 'WP_SQLITE_AST_DRIVER' ) && WP_SQLITE_AST_DRIVER ) {
38 | $translator = new WP_SQLite_Driver(
39 | new WP_SQLite_Connection( array( 'pdo' => $pdo ) ),
40 | $wpdb->dbname
41 | );
42 | } else {
43 | $translator = new WP_SQLite_Translator( $pdo );
44 | }
45 | $query = null;
46 |
47 | try {
48 | $translator->begin_transaction();
49 | foreach ( $queries as $query ) {
50 | $query = trim( $query );
51 | if ( empty( $query ) ) {
52 | continue;
53 | }
54 |
55 | $result = $translator->query( $query );
56 | if ( false === $result ) {
57 | throw new PDOException( $translator->get_error_message() );
58 | }
59 | }
60 | $translator->commit();
61 | } catch ( PDOException $err ) {
62 | $err_data = $err->errorInfo; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
63 | $err_code = $err_data[1];
64 | $translator->rollback();
65 | $message = sprintf(
66 | 'Error occurred while creating tables or indexes... Query was: %s ',
67 | var_export( $query, true )
68 | );
69 | $message .= sprintf( 'Error message is: %s', $err_data[2] );
70 | wp_die( $message, 'Database Error!' );
71 | }
72 |
73 | /*
74 | * Debug: Cross-check with MySQL.
75 | * This is for debugging purpose only and requires files
76 | * that are present in the GitHub repository
77 | * but not the plugin published on WordPress.org.
78 | */
79 | if ( defined( 'SQLITE_DEBUG_CROSSCHECK' ) && SQLITE_DEBUG_CROSSCHECK ) {
80 | $host = DB_HOST;
81 | $port = 3306;
82 | if ( str_contains( $host, ':' ) ) {
83 | $host_parts = explode( ':', $host );
84 | $host = $host_parts[0];
85 | $port = $host_parts[1];
86 | }
87 | $dsn = 'mysql:host=' . $host . '; port=' . $port . '; dbname=' . DB_NAME;
88 | $pdo_class = PHP_VERSION_ID >= 80400 ? PDO\MySQL::class : PDO::class; // phpcs:ignore WordPress.DB.RestrictedClasses.mysql__PDO
89 | $pdo_mysql = new $pdo_class( $dsn, DB_USER, DB_PASSWORD, array( PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION ) ); // phpcs:ignore WordPress.DB.RestrictedClasses.mysql__PDO
90 | $pdo_mysql->query( 'SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";' );
91 | $pdo_mysql->query( 'SET time_zone = "+00:00";' );
92 | foreach ( $queries as $query ) {
93 | $query = trim( $query );
94 | if ( empty( $query ) ) {
95 | continue;
96 | }
97 | try {
98 | $pdo_mysql->beginTransaction();
99 | $pdo_mysql->query( $query );
100 | } catch ( PDOException $err ) {
101 | $err_data = $err->errorInfo; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
102 | $err_code = $err_data[1];
103 | // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual
104 | if ( 5 == $err_code || 6 == $err_code ) {
105 | // If the database is locked, commit again.
106 | $pdo_mysql->commit();
107 | } else {
108 | $pdo_mysql->rollBack();
109 | $message = sprintf(
110 | 'Error occurred while creating tables or indexes... Query was: %s ',
111 | var_export( $query, true )
112 | );
113 | $message .= sprintf( 'Error message is: %s', $err_data[2] );
114 | wp_die( $message, 'Database Error!' );
115 | }
116 | }
117 | }
118 | }
119 |
120 | $pdo = null;
121 |
122 | return true;
123 | }
124 |
125 | if ( ! function_exists( 'wp_install' ) ) {
126 | /**
127 | * Installs the site.
128 | *
129 | * Runs the required functions to set up and populate the database,
130 | * including primary admin user and initial options.
131 | *
132 | * @since 1.0.0
133 | *
134 | * @param string $blog_title Site title.
135 | * @param string $user_name User's username.
136 | * @param string $user_email User's email.
137 | * @param bool $is_public Whether the site is public.
138 | * @param string $deprecated Optional. Not used.
139 | * @param string $user_password Optional. User's chosen password. Default empty (random password).
140 | * @param string $language Optional. Language chosen. Default empty.
141 | * @return array {
142 | * Data for the newly installed site.
143 | *
144 | * @type string $url The URL of the site.
145 | * @type int $user_id The ID of the site owner.
146 | * @type string $password The password of the site owner, if their user account didn't already exist.
147 | * @type string $password_message The explanatory message regarding the password.
148 | * }
149 | */
150 | function wp_install( $blog_title, $user_name, $user_email, $is_public, $deprecated = '', $user_password = '', $language = '' ) {
151 | if ( ! empty( $deprecated ) ) {
152 | _deprecated_argument( __FUNCTION__, '2.6.0' );
153 | }
154 |
155 | wp_check_mysql_version();
156 | wp_cache_flush();
157 | /* SQLite changes: Replace the call to make_db_current_silent() with sqlite_make_db_sqlite(). */
158 | sqlite_make_db_sqlite(); // phpcs:ignore PHPCompatibility.Extensions.RemovedExtensions.sqliteRemoved
159 | populate_options();
160 | populate_roles();
161 |
162 | update_option( 'blogname', $blog_title );
163 | update_option( 'admin_email', $user_email );
164 | update_option( 'blog_public', $is_public );
165 |
166 | // Freshness of site - in the future, this could get more specific about actions taken, perhaps.
167 | update_option( 'fresh_site', 1 );
168 |
169 | if ( $language ) {
170 | update_option( 'WPLANG', $language );
171 | }
172 |
173 | $guessurl = wp_guess_url();
174 |
175 | update_option( 'siteurl', $guessurl );
176 |
177 | // If not a public site, don't ping.
178 | if ( ! $is_public ) {
179 | update_option( 'default_pingback_flag', 0 );
180 | }
181 |
182 | /*
183 | * Create default user. If the user already exists, the user tables are
184 | * being shared among sites. Just set the role in that case.
185 | */
186 | $user_id = username_exists( $user_name );
187 | $user_password = trim( $user_password );
188 | $email_password = false;
189 | $user_created = false;
190 |
191 | if ( ! $user_id && empty( $user_password ) ) {
192 | $user_password = wp_generate_password( 12, false );
193 | $message = __( 'Note that password carefully! It is a random password that was generated just for you.', 'sqlite-database-integration' );
194 | $user_id = wp_create_user( $user_name, $user_password, $user_email );
195 | update_user_meta( $user_id, 'default_password_nag', true );
196 | $email_password = true;
197 | $user_created = true;
198 | } elseif ( ! $user_id ) {
199 | // Password has been provided.
200 | $message = '' . __( 'Your chosen password.', 'sqlite-database-integration' ) . '';
201 | $user_id = wp_create_user( $user_name, $user_password, $user_email );
202 | $user_created = true;
203 | } else {
204 | $message = __( 'User already exists. Password inherited.', 'sqlite-database-integration' );
205 | }
206 |
207 | $user = new WP_User( $user_id );
208 | $user->set_role( 'administrator' );
209 |
210 | if ( $user_created ) {
211 | $user->user_url = $guessurl;
212 | wp_update_user( $user );
213 | }
214 |
215 | wp_install_defaults( $user_id );
216 |
217 | wp_install_maybe_enable_pretty_permalinks();
218 |
219 | flush_rewrite_rules();
220 |
221 | wp_new_blog_notification( $blog_title, $guessurl, $user_id, ( $email_password ? $user_password : __( 'The password you chose during installation.', 'sqlite-database-integration' ) ) );
222 |
223 | wp_cache_flush();
224 |
225 | /**
226 | * Fires after a site is fully installed.
227 | *
228 | * @since 3.9.0
229 | *
230 | * @param WP_User $user The site owner.
231 | */
232 | do_action( 'wp_install', $user );
233 |
234 | return array(
235 | 'url' => $guessurl,
236 | 'user_id' => $user_id,
237 | 'password' => $user_password,
238 | 'password_message' => $message,
239 | );
240 | }
241 | }
242 |
--------------------------------------------------------------------------------
/wp-includes/sqlite/class-wp-sqlite-query-rewriter.php:
--------------------------------------------------------------------------------
1 | input_tokens = $input_tokens;
76 | $this->max = count( $input_tokens );
77 | }
78 |
79 | /**
80 | * Returns the updated query.
81 | *
82 | * @return string
83 | */
84 | public function get_updated_query() {
85 | $query = '';
86 | foreach ( $this->output_tokens as $token ) {
87 | $query .= $token->token;
88 | }
89 | return $query;
90 | }
91 |
92 | /**
93 | * Add a token to the output.
94 | *
95 | * @param WP_SQLite_Token $token Token object.
96 | */
97 | public function add( $token ) {
98 | if ( $token ) {
99 | $this->output_tokens[] = $token;
100 | }
101 | }
102 |
103 | /**
104 | * Add multiple tokens to the output.
105 | *
106 | * @param WP_SQLite_Token[] $tokens Array of token objects.
107 | */
108 | public function add_many( $tokens ) {
109 | $this->output_tokens = array_merge( $this->output_tokens, $tokens );
110 | }
111 |
112 | /**
113 | * Replaces all tokens.
114 | *
115 | * @param WP_SQLite_Token[] $tokens Array of token objects.
116 | */
117 | public function replace_all( $tokens ) {
118 | $this->output_tokens = $tokens;
119 | }
120 |
121 | /**
122 | * Peek at the next tokens and return one that matches the given criteria.
123 | *
124 | * @param array $query Optional. Search query.
125 | * [
126 | * 'type' => string|null, // Token type.
127 | * 'flags' => int|null, // Token flags.
128 | * 'values' => string|null, // Token values.
129 | * ].
130 | *
131 | * @return WP_SQLite_Token
132 | */
133 | public function peek( $query = array() ) {
134 | $type = isset( $query['type'] ) ? $query['type'] : null;
135 | $flags = isset( $query['flags'] ) ? $query['flags'] : null;
136 | $values = isset( $query['value'] )
137 | ? ( is_array( $query['value'] ) ? $query['value'] : array( $query['value'] ) )
138 | : null;
139 |
140 | $i = $this->index;
141 | while ( ++$i < $this->max ) {
142 | if ( $this->input_tokens[ $i ]->matches( $type, $flags, $values ) ) {
143 | return $this->input_tokens[ $i ];
144 | }
145 | }
146 | }
147 |
148 | /**
149 | * Move forward and return the next tokens that match the given criteria.
150 | *
151 | * @param int $nth The nth token to return.
152 | *
153 | * @return WP_SQLite_Token
154 | */
155 | public function peek_nth( $nth ) {
156 | $found = 0;
157 | for ( $i = $this->index + 1;$i < $this->max;$i++ ) {
158 | $token = $this->input_tokens[ $i ];
159 | if ( ! $token->is_semantically_void() ) {
160 | ++$found;
161 | }
162 | if ( $found === $nth ) {
163 | return $this->input_tokens[ $i ];
164 | }
165 | }
166 | }
167 |
168 | /**
169 | * Consume all the tokens.
170 | *
171 | * @param array $query Search query.
172 | *
173 | * @return void
174 | */
175 | public function consume_all( $query = array() ) {
176 | while ( $this->consume( $query ) ) {
177 | // Do nothing.
178 | }
179 | }
180 |
181 | /**
182 | * Consume the next tokens and return one that matches the given criteria.
183 | *
184 | * @param array $query Search query.
185 | * [
186 | * 'type' => null, // Optional. Token type.
187 | * 'flags' => null, // Optional. Token flags.
188 | * 'values' => null, // Optional. Token values.
189 | * ].
190 | *
191 | * @return WP_SQLite_Token|null
192 | */
193 | public function consume( $query = array() ) {
194 | $tokens = $this->move_forward( $query );
195 | $this->output_tokens = array_merge( $this->output_tokens, $tokens );
196 | return $this->token;
197 | }
198 |
199 | /**
200 | * Drop the last consumed token and return it.
201 | *
202 | * @return WP_SQLite_Token|null
203 | */
204 | public function drop_last() {
205 | return array_pop( $this->output_tokens );
206 | }
207 |
208 | /**
209 | * Skip over the next tokens and return one that matches the given criteria.
210 | *
211 | * @param array $query Search query.
212 | * [
213 | * 'type' => null, // Optional. Token type.
214 | * 'flags' => null, // Optional. Token flags.
215 | * 'values' => null, // Optional. Token values.
216 | * ].
217 | *
218 | * @return WP_SQLite_Token|null
219 | */
220 | public function skip( $query = array() ) {
221 | $this->skip_and_return_all( $query );
222 | return $this->token;
223 | }
224 |
225 | /**
226 | * Skip over the next tokens until one matches the given criteria,
227 | * and return all the skipped tokens.
228 | *
229 | * @param array $query Search query.
230 | * [
231 | * 'type' => null, // Optional. Token type.
232 | * 'flags' => null, // Optional. Token flags.
233 | * 'values' => null, // Optional. Token values.
234 | * ].
235 | *
236 | * @return WP_SQLite_Token[]
237 | */
238 | public function skip_and_return_all( $query = array() ) {
239 | $tokens = $this->move_forward( $query );
240 |
241 | /*
242 | * When skipping over whitespaces, make sure to consume
243 | * at least one to avoid SQL syntax errors.
244 | */
245 | foreach ( $tokens as $token ) {
246 | if ( $token->matches( WP_SQLite_Token::TYPE_WHITESPACE ) ) {
247 | $this->add( $token );
248 | break;
249 | }
250 | }
251 |
252 | return $tokens;
253 | }
254 |
255 | /**
256 | * Returns the next tokens that match the given criteria.
257 | *
258 | * @param array $query Search query.
259 | * [
260 | * 'type' => string|null, // Optional. Token type.
261 | * 'flags' => int|null, // Optional. Token flags.
262 | * 'values' => string|null, // Optional. Token values.
263 | * ].
264 | *
265 | * @return array
266 | */
267 | private function move_forward( $query = array() ) {
268 | $type = isset( $query['type'] ) ? $query['type'] : null;
269 | $flags = isset( $query['flags'] ) ? $query['flags'] : null;
270 | $values = isset( $query['value'] )
271 | ? ( is_array( $query['value'] ) ? $query['value'] : array( $query['value'] ) )
272 | : null;
273 | $depth = isset( $query['depth'] ) ? $query['depth'] : null;
274 |
275 | $buffered = array();
276 | while ( true ) {
277 | if ( ++$this->index >= $this->max ) {
278 | $this->token = null;
279 | $this->call_stack = array();
280 | break;
281 | }
282 | $this->token = $this->input_tokens[ $this->index ];
283 | $this->update_call_stack();
284 | $buffered[] = $this->token;
285 | if (
286 | ( null === $depth || $this->depth === $depth )
287 | && $this->token->matches( $type, $flags, $values )
288 | ) {
289 | break;
290 | }
291 | }
292 |
293 | return $buffered;
294 | }
295 |
296 | /**
297 | * Returns the last call stack element.
298 | *
299 | * @return array|null
300 | */
301 | public function last_call_stack_element() {
302 | return count( $this->call_stack ) ? $this->call_stack[ count( $this->call_stack ) - 1 ] : null;
303 | }
304 |
305 | /**
306 | * Updates the call stack.
307 | *
308 | * @return void
309 | */
310 | private function update_call_stack() {
311 | if ( $this->token->flags & WP_SQLite_Token::FLAG_KEYWORD_FUNCTION ) {
312 | $this->last_function_call = $this->token->value;
313 | }
314 | if ( WP_SQLite_Token::TYPE_OPERATOR === $this->token->type ) {
315 | switch ( $this->token->value ) {
316 | case '(':
317 | if ( $this->last_function_call ) {
318 | array_push(
319 | $this->call_stack,
320 | array(
321 | 'function' => $this->last_function_call,
322 | 'depth' => $this->depth,
323 | )
324 | );
325 | $this->last_function_call = null;
326 | }
327 | ++$this->depth;
328 | break;
329 |
330 | case ')':
331 | --$this->depth;
332 | $call_parent = $this->last_call_stack_element();
333 | if (
334 | $call_parent &&
335 | $call_parent['depth'] === $this->depth
336 | ) {
337 | array_pop( $this->call_stack );
338 | }
339 | break;
340 | }
341 | }
342 | }
343 | }
344 |
--------------------------------------------------------------------------------
/wp-includes/sqlite/class-wp-sqlite-token.php:
--------------------------------------------------------------------------------
1 | , !==, etc.
35 | * Bitwise operators: &, |, ^, etc.
36 | * Assignment operators: =, +=, -=, etc.
37 | * SQL specific operators: . (e.g. .. WHERE database.table ..),
38 | * * (e.g. SELECT * FROM ..)
39 | */
40 | const TYPE_OPERATOR = 2;
41 |
42 | /**
43 | * Spaces, tabs, new lines, etc.
44 | */
45 | const TYPE_WHITESPACE = 3;
46 |
47 | /**
48 | * Any type of legal comment.
49 | *
50 | * Bash (#), C (/* *\/) or SQL (--) comments:
51 | *
52 | * -- SQL-comment
53 | *
54 | * #Bash-like comment
55 | *
56 | * /*C-like comment*\/
57 | *
58 | * or:
59 | *
60 | * /*C-like
61 | * comment*\/
62 | *
63 | * Backslashes were added to respect PHP's comments syntax.
64 | */
65 | const TYPE_COMMENT = 4;
66 |
67 | /**
68 | * Boolean values: true or false.
69 | */
70 | const TYPE_BOOL = 5;
71 |
72 | /**
73 | * Numbers: 4, 0x8, 15.16, 23e42, etc.
74 | */
75 | const TYPE_NUMBER = 6;
76 |
77 | /**
78 | * Literal strings: 'string', "test".
79 | * Some of these strings are actually symbols.
80 | */
81 | const TYPE_STRING = 7;
82 |
83 | /**
84 | * Database, table names, variables, etc.
85 | * For example: ```SELECT `foo`, `bar` FROM `database`.`table`;```.
86 | */
87 | const TYPE_SYMBOL = 8;
88 |
89 | /**
90 | * Delimits an unknown string.
91 | * For example: ```SELECT * FROM test;```, `test` is a delimiter.
92 | */
93 | const TYPE_DELIMITER = 9;
94 |
95 | /**
96 | * Labels in LOOP statement, ITERATE statement etc.
97 | * For example (only for begin label):
98 | * begin_label: BEGIN [statement_list] END [end_label]
99 | * begin_label: LOOP [statement_list] END LOOP [end_label]
100 | * begin_label: REPEAT [statement_list] ... END REPEAT [end_label]
101 | * begin_label: WHILE ... DO [statement_list] END WHILE [end_label].
102 | */
103 | const TYPE_LABEL = 10;
104 |
105 | // Flags that describe the tokens in more detail.
106 | // All keywords must have flag 1 so `Context::isKeyword` method doesn't
107 | // require strict comparison.
108 | const FLAG_KEYWORD_RESERVED = 2;
109 | const FLAG_KEYWORD_COMPOSED = 4;
110 | const FLAG_KEYWORD_DATA_TYPE = 8;
111 | const FLAG_KEYWORD_KEY = 16;
112 | const FLAG_KEYWORD_FUNCTION = 32;
113 |
114 | // Numbers related flags.
115 | const FLAG_NUMBER_HEX = 1;
116 | const FLAG_NUMBER_FLOAT = 2;
117 | const FLAG_NUMBER_APPROXIMATE = 4;
118 | const FLAG_NUMBER_NEGATIVE = 8;
119 | const FLAG_NUMBER_BINARY = 16;
120 |
121 | // Strings related flags.
122 | const FLAG_STRING_SINGLE_QUOTES = 1;
123 | const FLAG_STRING_DOUBLE_QUOTES = 2;
124 |
125 | // Comments related flags.
126 | const FLAG_COMMENT_BASH = 1;
127 | const FLAG_COMMENT_C = 2;
128 | const FLAG_COMMENT_SQL = 4;
129 | const FLAG_COMMENT_MYSQL_CMD = 8;
130 |
131 | // Operators related flags.
132 | const FLAG_OPERATOR_ARITHMETIC = 1;
133 | const FLAG_OPERATOR_LOGICAL = 2;
134 | const FLAG_OPERATOR_BITWISE = 4;
135 | const FLAG_OPERATOR_ASSIGNMENT = 8;
136 | const FLAG_OPERATOR_SQL = 16;
137 |
138 | // Symbols related flags.
139 | const FLAG_SYMBOL_VARIABLE = 1;
140 | const FLAG_SYMBOL_BACKTICK = 2;
141 | const FLAG_SYMBOL_USER = 4;
142 | const FLAG_SYMBOL_SYSTEM = 8;
143 | const FLAG_SYMBOL_PARAMETER = 16;
144 |
145 | /**
146 | * The token it its raw string representation.
147 | *
148 | * @var string
149 | */
150 | public $token;
151 |
152 | /**
153 | * The value this token contains (i.e. token after some evaluation).
154 | *
155 | * @var mixed
156 | */
157 | public $value;
158 |
159 | /**
160 | * The keyword value this token contains, always uppercase.
161 | *
162 | * @var mixed|string|null
163 | */
164 | public $keyword = null;
165 |
166 | /**
167 | * The type of this token.
168 | *
169 | * @var int
170 | */
171 | public $type;
172 |
173 | /**
174 | * The flags of this token.
175 | *
176 | * @var int
177 | */
178 | public $flags;
179 |
180 | /**
181 | * The position in the initial string where this token started.
182 | *
183 | * The position is counted in chars, not bytes, so you should
184 | * use mb_* functions to properly handle utf-8 multibyte chars.
185 | *
186 | * @var int|null
187 | */
188 | public $position;
189 |
190 | /**
191 | * Constructor.
192 | *
193 | * @param string $token The value of the token.
194 | * @param int $type The type of the token.
195 | * @param int $flags The flags of the token.
196 | */
197 | public function __construct( $token, $type = 0, $flags = 0 ) {
198 | $this->token = $token;
199 | $this->type = $type;
200 | $this->flags = $flags;
201 | $this->value = $this->extract();
202 | }
203 |
204 | /**
205 | * Check if the token matches the given parameters.
206 | *
207 | * @param int|null $type The type of the token.
208 | * @param int|null $flags The flags of the token.
209 | * @param array|null $values The values of the token.
210 | *
211 | * @return bool
212 | */
213 | public function matches( $type = null, $flags = null, $values = null ) {
214 | if ( null === $type && null === $flags && ( null === $values || array() === $values ) ) {
215 | return ! $this->is_semantically_void();
216 | }
217 |
218 | return (
219 | ( null === $type || $this->type === $type )
220 | && ( null === $flags || ( $this->flags & $flags ) )
221 | && ( null === $values || in_array( strtoupper( $this->value ?? '' ), $values, true ) )
222 | );
223 | }
224 |
225 | /**
226 | * Check if the token is semantically void (i.e. whitespace or comment).
227 | *
228 | * @return bool
229 | */
230 | public function is_semantically_void() {
231 | return $this->matches( self::TYPE_WHITESPACE ) || $this->matches( self::TYPE_COMMENT );
232 | }
233 |
234 | /**
235 | * Does little processing to the token to extract a value.
236 | *
237 | * If no processing can be done it will return the initial string.
238 | *
239 | * @return mixed
240 | */
241 | private function extract() {
242 | switch ( $this->type ) {
243 | case self::TYPE_KEYWORD:
244 | $this->keyword = strtoupper( $this->token ?? '' );
245 | if ( ! ( $this->flags & self::FLAG_KEYWORD_RESERVED ) ) {
246 | /*
247 | * Unreserved keywords should stay the way they are
248 | * because they might represent field names.
249 | */
250 | return $this->token;
251 | }
252 |
253 | return $this->keyword;
254 |
255 | case self::TYPE_WHITESPACE:
256 | return ' ';
257 |
258 | case self::TYPE_BOOL:
259 | return strtoupper( $this->token ?? '' ) === 'TRUE';
260 |
261 | case self::TYPE_NUMBER:
262 | $ret = str_replace( '--', '', $this->token ); // e.g. ---42 === -42.
263 | if ( $this->flags & self::FLAG_NUMBER_HEX ) {
264 | $ret = str_replace( array( '-', '+' ), '', $this->token );
265 | if ( $this->flags & self::FLAG_NUMBER_NEGATIVE ) {
266 | $ret = -hexdec( $ret );
267 | } else {
268 | $ret = hexdec( $ret );
269 | }
270 | } elseif ( ( $this->flags & self::FLAG_NUMBER_APPROXIMATE ) || ( $this->flags & self::FLAG_NUMBER_FLOAT ) ) {
271 | $ret = (float) $ret;
272 | } elseif ( ! ( $this->flags & self::FLAG_NUMBER_BINARY ) ) {
273 | $ret = (int) $ret;
274 | }
275 |
276 | return $ret;
277 |
278 | case self::TYPE_STRING:
279 | // Trims quotes.
280 | $str = $this->token;
281 | $str = mb_substr( $str, 1, -1, 'UTF-8' );
282 |
283 | // Removes surrounding quotes.
284 | $quote = $this->token[0];
285 | $str = str_replace( $quote . $quote, $quote, $str );
286 |
287 | /*
288 | * Finally unescapes the string.
289 | *
290 | * `stripcslashes` replaces escape sequences with their
291 | * representation.
292 | */
293 | $str = stripcslashes( $str );
294 |
295 | return $str;
296 |
297 | case self::TYPE_SYMBOL:
298 | $str = $this->token;
299 | if ( isset( $str[0] ) && ( '@' === $str[0] ) ) {
300 | /*
301 | * `mb_strlen($str)` must be used instead of `null` because
302 | * in PHP 5.3- the `null` parameter isn't handled correctly.
303 | */
304 | $str = mb_substr(
305 | $str,
306 | ! empty( $str[1] ) && ( '@' === $str[1] ) ? 2 : 1,
307 | mb_strlen( $str ),
308 | 'UTF-8'
309 | );
310 | }
311 |
312 | if ( isset( $str[0] ) && ( ':' === $str[0] ) ) {
313 | $str = mb_substr( $str, 1, mb_strlen( $str ), 'UTF-8' );
314 | }
315 |
316 | if ( isset( $str[0] ) && ( ( '`' === $str[0] ) || ( '"' === $str[0] ) || ( '\'' === $str[0] ) ) ) {
317 | $quote = $str[0];
318 | $str = str_replace( $quote . $quote, $quote, $str );
319 | $str = mb_substr( $str, 1, -1, 'UTF-8' );
320 | }
321 |
322 | return $str;
323 | }
324 |
325 | return $this->token;
326 | }
327 | }
328 |
--------------------------------------------------------------------------------
/wp-includes/sqlite-ast/class-wp-sqlite-configurator.php:
--------------------------------------------------------------------------------
1 | driver = $driver;
46 | $this->schema_builder = $schema_builder;
47 | $this->schema_reconstructor = new WP_SQLite_Information_Schema_Reconstructor(
48 | $driver,
49 | $schema_builder
50 | );
51 | }
52 |
53 | /**
54 | * Ensure that the SQLite database is configured.
55 | *
56 | * This method checks if the database is configured for the latest SQLite
57 | * driver version, and if it is not, it will configure the database.
58 | */
59 | public function ensure_database_configured(): void {
60 | $version = SQLITE_DRIVER_VERSION;
61 | $db_version = $this->driver->get_saved_driver_version();
62 | if ( version_compare( $version, $db_version ) > 0 ) {
63 | $this->configure_database();
64 | }
65 | }
66 |
67 | /**
68 | * Configure the SQLite database.
69 | *
70 | * This method creates tables used for emulating MySQL behaviors in SQLite,
71 | * and populates them with necessary data. When it is used with an already
72 | * configured database, it will update the configuration as per the current
73 | * SQLite driver version and attempt to repair any configuration corruption.
74 | */
75 | public function configure_database(): void {
76 | // Use an EXCLUSIVE transaction to prevent multiple connections
77 | // from attempting to configure the database at the same time.
78 | $this->driver->execute_sqlite_query( 'BEGIN EXCLUSIVE TRANSACTION' );
79 | try {
80 | $this->ensure_global_variables_table();
81 | $this->schema_builder->ensure_information_schema_tables();
82 | $this->schema_reconstructor->ensure_correct_information_schema();
83 | $this->save_current_driver_version();
84 | $this->ensure_database_data();
85 | } catch ( Throwable $e ) {
86 | $this->driver->execute_sqlite_query( 'ROLLBACK' );
87 | throw $e;
88 | }
89 | $this->driver->execute_sqlite_query( 'COMMIT' );
90 | }
91 |
92 | /**
93 | * Ensure that the global variables table exists.
94 | *
95 | * This method configures a database table to store MySQL global variables
96 | * and other internal configuration values.
97 | */
98 | private function ensure_global_variables_table(): void {
99 | $this->driver->execute_sqlite_query(
100 | sprintf(
101 | 'CREATE TABLE IF NOT EXISTS %s (name TEXT PRIMARY KEY, value TEXT)',
102 | $this->driver->get_connection()->quote_identifier(
103 | WP_SQLite_Driver::GLOBAL_VARIABLES_TABLE_NAME
104 | )
105 | )
106 | );
107 | }
108 |
109 | /**
110 | * Ensure that the database data is correctly populated.
111 | *
112 | * This method ensures that the "INFORMATION_SCHEMA.SCHEMATA" table contains
113 | * records for both the "INFORMATION_SCHEMA" database and the user database.
114 | * At the moment, only a single user database is supported.
115 | *
116 | * Additionally, this method ensures that the user database name is stored
117 | * correctly in all the information schema tables.
118 | */
119 | public function ensure_database_data(): void {
120 | // Get all databases from the "SCHEMATA" table.
121 | $schemata_table = $this->schema_builder->get_table_name( false, 'schemata' );
122 | $databases = $this->driver->execute_sqlite_query(
123 | sprintf(
124 | 'SELECT SCHEMA_NAME FROM %s',
125 | $this->driver->get_connection()->quote_identifier( $schemata_table )
126 | )
127 | )->fetchAll( PDO::FETCH_COLUMN ); // phpcs:disable WordPress.DB.RestrictedClasses.mysql__PDO
128 |
129 | // Ensure that the "INFORMATION_SCHEMA" database record exists.
130 | if ( ! in_array( 'information_schema', $databases, true ) ) {
131 | $this->driver->execute_sqlite_query(
132 | sprintf(
133 | 'INSERT INTO %s (SCHEMA_NAME, DEFAULT_CHARACTER_SET_NAME, DEFAULT_COLLATION_NAME) VALUES (?, ?, ?)',
134 | $this->driver->get_connection()->quote_identifier( $schemata_table )
135 | ),
136 | // The "INFORMATION_SCHEMA" database stays on "utf8mb3" even in MySQL 8 and 9.
137 | array( 'information_schema', 'utf8mb3', 'utf8mb3_general_ci' )
138 | );
139 | }
140 |
141 | // Get the existing user database name.
142 | $existing_user_db_name = null;
143 | foreach ( $databases as $database ) {
144 | if ( 'information_schema' !== strtolower( $database ) ) {
145 | $existing_user_db_name = $database;
146 | break;
147 | }
148 | }
149 |
150 | // Ensure that the user database record exists.
151 | if ( null === $existing_user_db_name ) {
152 | $existing_user_db_name = WP_SQLite_Information_Schema_Builder::SAVED_DATABASE_NAME;
153 | $this->driver->execute_sqlite_query(
154 | sprintf(
155 | 'INSERT INTO %s (SCHEMA_NAME, DEFAULT_CHARACTER_SET_NAME, DEFAULT_COLLATION_NAME) VALUES (?, ?, ?)',
156 | $this->driver->get_connection()->quote_identifier( $schemata_table )
157 | ),
158 | // @TODO: This should probably be version-dependent.
159 | // Before MySQL 8, the default was different.
160 | array( $existing_user_db_name, 'utf8mb4', 'utf8mb4_0900_ai_ci' )
161 | );
162 | }
163 |
164 | // Migrate from older versions without dynamic database names.
165 | $saved_database_name = WP_SQLite_Information_Schema_Builder::SAVED_DATABASE_NAME;
166 | if ( $saved_database_name !== $existing_user_db_name ) {
167 | // INFORMATION_SCHEMA.SCHEMATA
168 | $this->driver->execute_sqlite_query(
169 | sprintf(
170 | "UPDATE %s SET SCHEMA_NAME = ? WHERE SCHEMA_NAME != 'information_schema'",
171 | $this->driver->get_connection()->quote_identifier( $schemata_table )
172 | ),
173 | array( $saved_database_name )
174 | );
175 |
176 | // INFORMATION_SCHEMA.TABLES
177 | $tables_table = $this->schema_builder->get_table_name( false, 'tables' );
178 | $this->driver->execute_sqlite_query(
179 | sprintf(
180 | "UPDATE %s SET TABLE_SCHEMA = ? WHERE TABLE_SCHEMA != 'information_schema'",
181 | $this->driver->get_connection()->quote_identifier( $tables_table )
182 | ),
183 | array( $saved_database_name )
184 | );
185 |
186 | // INFORMATION_SCHEMA.COLUMNS
187 | $columns_table = $this->schema_builder->get_table_name( false, 'columns' );
188 | $this->driver->execute_sqlite_query(
189 | sprintf(
190 | "UPDATE %s SET TABLE_SCHEMA = ? WHERE TABLE_SCHEMA != 'information_schema'",
191 | $this->driver->get_connection()->quote_identifier( $columns_table )
192 | ),
193 | array( $saved_database_name )
194 | );
195 |
196 | // INFORMATION_SCHEMA.STATISTICS
197 | $statistics_table = $this->schema_builder->get_table_name( false, 'statistics' );
198 | $this->driver->execute_sqlite_query(
199 | sprintf(
200 | "UPDATE %s SET TABLE_SCHEMA = ?, INDEX_SCHEMA = ? WHERE TABLE_SCHEMA != 'information_schema'",
201 | $this->driver->get_connection()->quote_identifier( $statistics_table )
202 | ),
203 | array( $saved_database_name, $saved_database_name )
204 | );
205 |
206 | // INFORMATION_SCHEMA.TABLE_CONSTRAINTS
207 | $table_constraints_table = $this->schema_builder->get_table_name( false, 'table_constraints' );
208 | $this->driver->execute_sqlite_query(
209 | sprintf(
210 | "UPDATE %s SET TABLE_SCHEMA = ?, CONSTRAINT_SCHEMA = ? WHERE TABLE_SCHEMA != 'information_schema'",
211 | $this->driver->get_connection()->quote_identifier( $table_constraints_table )
212 | ),
213 | array( $saved_database_name, $saved_database_name )
214 | );
215 |
216 | // INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS
217 | $referential_constraints_table = $this->schema_builder->get_table_name( false, 'referential_constraints' );
218 | $this->driver->execute_sqlite_query(
219 | sprintf(
220 | "UPDATE %s SET CONSTRAINT_SCHEMA = ?, UNIQUE_CONSTRAINT_SCHEMA = ? WHERE CONSTRAINT_SCHEMA != 'information_schema'",
221 | $this->driver->get_connection()->quote_identifier( $referential_constraints_table )
222 | ),
223 | array( $saved_database_name, $saved_database_name )
224 | );
225 |
226 | // INFORMATION_SCHEMA.KEY_COLUMN_USAGE
227 | $key_column_usage_table = $this->schema_builder->get_table_name( false, 'key_column_usage' );
228 | $this->driver->execute_sqlite_query(
229 | sprintf(
230 | "UPDATE %s
231 | SET
232 | TABLE_SCHEMA = ?,
233 | CONSTRAINT_SCHEMA = ?,
234 | REFERENCED_TABLE_SCHEMA = IIF(REFERENCED_TABLE_SCHEMA IS NULL, NULL, ?)
235 | WHERE TABLE_SCHEMA != 'information_schema'",
236 | $this->driver->get_connection()->quote_identifier( $key_column_usage_table )
237 | ),
238 | array( $saved_database_name, $saved_database_name, $saved_database_name )
239 | );
240 |
241 | // INFORMATION_SCHEMA.CHECK_CONSTRAINTS
242 | $check_constraints_table = $this->schema_builder->get_table_name( false, 'check_constraints' );
243 | $this->driver->execute_sqlite_query(
244 | sprintf(
245 | "UPDATE %s SET CONSTRAINT_SCHEMA = ? WHERE CONSTRAINT_SCHEMA != 'information_schema'",
246 | $this->driver->get_connection()->quote_identifier( $check_constraints_table )
247 | ),
248 | array( $saved_database_name )
249 | );
250 | }
251 | }
252 |
253 | /**
254 | * Save the current SQLite driver version.
255 | *
256 | * This method saves the current SQLite driver version to the database.
257 | */
258 | private function save_current_driver_version(): void {
259 | $this->driver->execute_sqlite_query(
260 | sprintf(
261 | 'INSERT INTO %s (name, value) VALUES (?, ?) ON CONFLICT(name) DO UPDATE SET value = ?',
262 | $this->driver->get_connection()->quote_identifier(
263 | WP_SQLite_Driver::GLOBAL_VARIABLES_TABLE_NAME
264 | )
265 | ),
266 | array(
267 | WP_SQLite_Driver::DRIVER_VERSION_VARIABLE_NAME,
268 | SQLITE_DRIVER_VERSION,
269 | SQLITE_DRIVER_VERSION,
270 | )
271 | );
272 | }
273 | }
274 |
--------------------------------------------------------------------------------
/wp-includes/parser/class-wp-parser-node.php:
--------------------------------------------------------------------------------
1 | rule_id = $rule_id;
22 | $this->rule_name = $rule_name;
23 | }
24 |
25 | public function append_child( $node ) {
26 | $this->children[] = $node;
27 | }
28 |
29 | /**
30 | * Flatten the matched rule fragments as if their children were direct
31 | * descendants of the current rule.
32 | *
33 | * What are rule fragments?
34 | *
35 | * When we initially parse the grammar file, it has compound rules such
36 | * as this one:
37 | *
38 | * query ::= EOF | ((simpleStatement | beginWork) ((SEMICOLON_SYMBOL EOF?) | EOF))
39 | *
40 | * Building a parser that can understand such rules is way more complex than building
41 | * a parser that only follows simple rules, so we flatten those compound rules into
42 | * simpler ones. The above rule would be flattened to:
43 | *
44 | * query ::= EOF | %query0
45 | * %query0 ::= %%query01 %%query02
46 | * %%query01 ::= simpleStatement | beginWork
47 | * %%query02 ::= SEMICOLON_SYMBOL EOF_zero_or_one | EOF
48 | * EOF_zero_or_one ::= EOF | ε
49 | *
50 | * This factorization happens in "convert-grammar.php".
51 | *
52 | * "Fragments" are intermediate artifacts whose names are not in the original grammar.
53 | * They are extremely useful for the parser, but the API consumer should never have to
54 | * worry about them. Fragment names start with a percent sign ("%").
55 | *
56 | * The code below inlines every fragment back in its parent rule.
57 | *
58 | * We could optimize this. The current $match may be discarded later on so any inlining
59 | * effort here would be wasted. However, inlining seems cheap and doing it bottom-up here
60 | * is **much** easier than reprocessing the parse tree top-down later on.
61 | *
62 | * The following parse tree:
63 | *
64 | * [
65 | * 'query' => [
66 | * [
67 | * '%query01' => [
68 | * [
69 | * 'simpleStatement' => [
70 | * MySQLToken(MySQLLexer::WITH_SYMBOL, 'WITH')
71 | * ],
72 | * '%query02' => [
73 | * [
74 | * 'simpleStatement' => [
75 | * MySQLToken(MySQLLexer::WITH_SYMBOL, 'WITH')
76 | * ]
77 | * ],
78 | * ]
79 | * ]
80 | * ]
81 | * ]
82 | * ]
83 | *
84 | * Would be inlined as:
85 | *
86 | * [
87 | * 'query' => [
88 | * [
89 | * 'simpleStatement' => [
90 | * MySQLToken(MySQLLexer::WITH_SYMBOL, 'WITH')
91 | * ]
92 | * ],
93 | * [
94 | * 'simpleStatement' => [
95 | * MySQLToken(MySQLLexer::WITH_SYMBOL, 'WITH')
96 | * ]
97 | * ]
98 | * ]
99 | * ]
100 | */
101 | public function merge_fragment( $node ) {
102 | $this->children = array_merge( $this->children, $node->children );
103 | }
104 |
105 | /**
106 | * Check if this node has any child nodes or tokens.
107 | *
108 | * @return bool True if this node has any child nodes or tokens, false otherwise.
109 | */
110 | public function has_child(): bool {
111 | return count( $this->children ) > 0;
112 | }
113 |
114 | /**
115 | * Check if this node has any child nodes.
116 | *
117 | * @param string|null $rule_name Optional. A node rule name to check for.
118 | * @return bool True if any child nodes are found, false otherwise.
119 | */
120 | public function has_child_node( ?string $rule_name = null ): bool {
121 | foreach ( $this->children as $child ) {
122 | if (
123 | $child instanceof WP_Parser_Node
124 | && ( null === $rule_name || $child->rule_name === $rule_name )
125 | ) {
126 | return true;
127 | }
128 | }
129 | return false;
130 | }
131 |
132 | /**
133 | * Check if this node has any child tokens.
134 | *
135 | * @param int|null $token_id Optional. A token ID to check for.
136 | * @return bool True if any child tokens are found, false otherwise.
137 | */
138 | public function has_child_token( ?int $token_id = null ): bool {
139 | foreach ( $this->children as $child ) {
140 | if (
141 | $child instanceof WP_Parser_Token
142 | && ( null === $token_id || $child->id === $token_id )
143 | ) {
144 | return true;
145 | }
146 | }
147 | return false;
148 | }
149 |
150 | /**
151 | * Get the first child node or token of this node.
152 | *
153 | * @return WP_Parser_Node|WP_Parser_Token|null The first child node or token;
154 | * null when no children are found.
155 | */
156 | public function get_first_child() {
157 | return $this->children[0] ?? null;
158 | }
159 |
160 | /**
161 | * Get the first child node of this node.
162 | *
163 | * @param string|null $rule_name Optional. A node rule name to check for.
164 | * @return WP_Parser_Node|null The first matching child node; null when no children are found.
165 | */
166 | public function get_first_child_node( ?string $rule_name = null ): ?WP_Parser_Node {
167 | foreach ( $this->children as $child ) {
168 | if (
169 | $child instanceof WP_Parser_Node
170 | && ( null === $rule_name || $child->rule_name === $rule_name )
171 | ) {
172 | return $child;
173 | }
174 | }
175 | return null;
176 | }
177 |
178 | /**
179 | * Get the first child token of this node.
180 | *
181 | * @param int|null $token_id Optional. A token ID to check for.
182 | * @return WP_Parser_Token|null The first matching child token; null when no children are found.
183 | */
184 | public function get_first_child_token( ?int $token_id = null ): ?WP_Parser_Token {
185 | foreach ( $this->children as $child ) {
186 | if (
187 | $child instanceof WP_Parser_Token
188 | && ( null === $token_id || $child->id === $token_id )
189 | ) {
190 | return $child;
191 | }
192 | }
193 | return null;
194 | }
195 |
196 | /**
197 | * Get the first descendant node of this node.
198 | *
199 | * The node children are traversed recursively in a depth-first order until
200 | * a matching descendant node is found, or the entire subtree is searched.
201 | *
202 | * @param string|null $rule_name Optional. A node rule name to check for.
203 | * @return WP_Parser_Node|null The first matching descendant node; null when no descendants are found.
204 | */
205 | public function get_first_descendant_node( ?string $rule_name = null ): ?WP_Parser_Node {
206 | for ( $i = 0; $i < count( $this->children ); $i++ ) {
207 | $child = $this->children[ $i ];
208 | if ( ! $child instanceof WP_Parser_Node ) {
209 | continue;
210 | }
211 | if ( null === $rule_name || $child->rule_name === $rule_name ) {
212 | return $child;
213 | }
214 | $node = $child->get_first_descendant_node( $rule_name );
215 | if ( $node ) {
216 | return $node;
217 | }
218 | }
219 | return null;
220 | }
221 |
222 | /**
223 | * Get the first descendant token of this node.
224 | *
225 | * The node children are traversed recursively in a depth-first order until
226 | * a matching descendant token is found, or the entire subtree is searched.
227 | *
228 | * @param int|null $token_id Optional. A token ID to check for.
229 | * @return WP_Parser_Token|null The first matching descendant token; null when no descendants are found.
230 | */
231 | public function get_first_descendant_token( ?int $token_id = null ): ?WP_Parser_Token {
232 | for ( $i = 0; $i < count( $this->children ); $i++ ) {
233 | $child = $this->children[ $i ];
234 | if ( $child instanceof WP_Parser_Token ) {
235 | if ( null === $token_id || $child->id === $token_id ) {
236 | return $child;
237 | }
238 | } else {
239 | $token = $child->get_first_descendant_token( $token_id );
240 | if ( $token ) {
241 | return $token;
242 | }
243 | }
244 | }
245 | return null;
246 | }
247 |
248 | /**
249 | * Get all children of this node.
250 | *
251 | * @return array An array of all child nodes and tokens of this node.
252 | */
253 | public function get_children(): array {
254 | return $this->children;
255 | }
256 |
257 | /**
258 | * Get all child nodes of this node.
259 | *
260 | * @param string|null $rule_name Optional. A node rule name to check for.
261 | * @return WP_Parser_Node[] An array of all matching child nodes.
262 | */
263 | public function get_child_nodes( ?string $rule_name = null ): array {
264 | $nodes = array();
265 | foreach ( $this->children as $child ) {
266 | if (
267 | $child instanceof WP_Parser_Node
268 | && ( null === $rule_name || $child->rule_name === $rule_name )
269 | ) {
270 | $nodes[] = $child;
271 | }
272 | }
273 | return $nodes;
274 | }
275 |
276 | /**
277 | * Get all child tokens of this node.
278 | *
279 | * @param int|null $token_id Optional. A token ID to check for.
280 | * @return WP_Parser_Token[] An array of all matching child tokens.
281 | */
282 | public function get_child_tokens( ?int $token_id = null ): array {
283 | $tokens = array();
284 | foreach ( $this->children as $child ) {
285 | if (
286 | $child instanceof WP_Parser_Token
287 | && ( null === $token_id || $child->id === $token_id )
288 | ) {
289 | $tokens[] = $child;
290 | }
291 | }
292 | return $tokens;
293 | }
294 |
295 | /**
296 | * Get all descendants of this node.
297 | *
298 | * The descendants are collected using a depth-first pre-order NLR traversal.
299 | * This produces a natural ordering that corresponds to the original input.
300 | *
301 | * @return array An array of all descendant nodes and tokens of this node.
302 | */
303 | public function get_descendants(): array {
304 | $descendants = array();
305 | foreach ( $this->children as $child ) {
306 | if ( $child instanceof WP_Parser_Node ) {
307 | $descendants[] = $child;
308 | $descendants = array_merge( $descendants, $child->get_descendants() );
309 | } else {
310 | $descendants[] = $child;
311 | }
312 | }
313 | return $descendants;
314 | }
315 |
316 | /**
317 | * Get all descendant nodes of this node.
318 | *
319 | * The descendants are collected using a depth-first pre-order NLR traversal.
320 | * This produces a natural ordering that corresponds to the original input.
321 | * All matching nodes are collected during the traversal.
322 | *
323 | * @param string|null $rule_name Optional. A node rule name to check for.
324 | * @return WP_Parser_Node[] An array of all matching descendant nodes.
325 | */
326 | public function get_descendant_nodes( ?string $rule_name = null ): array {
327 | $nodes = array();
328 | foreach ( $this->children as $child ) {
329 | if ( ! $child instanceof WP_Parser_Node ) {
330 | continue;
331 | }
332 | if ( null === $rule_name || $child->rule_name === $rule_name ) {
333 | $nodes[] = $child;
334 | }
335 | $nodes = array_merge( $nodes, $child->get_descendant_nodes( $rule_name ) );
336 | }
337 | return $nodes;
338 | }
339 |
340 | /**
341 | * Get all descendant tokens of this node.
342 | *
343 | * The descendants are collected using a depth-first pre-order NLR traversal.
344 | * This produces a natural ordering that corresponds to the original input.
345 | * All matching tokens are collected during the traversal.
346 | *
347 | * @param int|null $token_id Optional. A token ID to check for.
348 | * @return WP_Parser_Token[] An array of all matching descendant tokens.
349 | */
350 | public function get_descendant_tokens( ?int $token_id = null ): array {
351 | $tokens = array();
352 | foreach ( $this->children as $child ) {
353 | if ( $child instanceof WP_Parser_Token ) {
354 | if ( null === $token_id || $child->id === $token_id ) {
355 | $tokens[] = $child;
356 | }
357 | } else {
358 | $tokens = array_merge( $tokens, $child->get_descendant_tokens( $token_id ) );
359 | }
360 | }
361 | return $tokens;
362 | }
363 |
364 | /**
365 | * Get the byte offset in the input string where this node begins.
366 | *
367 | * @return int The byte offset in the input string where this node begins.
368 | */
369 | public function get_start(): int {
370 | return $this->get_first_descendant_token()->start;
371 | }
372 |
373 | /**
374 | * Get the byte length of this node in the input string.
375 | *
376 | * @return int The byte length of this node in the input string.
377 | */
378 | public function get_length(): int {
379 | $tokens = $this->get_descendant_tokens();
380 | $first_token = $tokens[0];
381 | $last_token = $tokens[ count( $tokens ) - 1 ];
382 | return $last_token->start + $last_token->length - $first_token->start;
383 | }
384 | }
385 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 2, June 1991
3 |
4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
6 | Everyone is permitted to copy and distribute verbatim copies
7 | of this license document, but changing it is not allowed.
8 |
9 | Preamble
10 |
11 | The licenses for most software are designed to take away your
12 | freedom to share and change it. By contrast, the GNU General Public
13 | License is intended to guarantee your freedom to share and change free
14 | software--to make sure the software is free for all its users. This
15 | General Public License applies to most of the Free Software
16 | Foundation's software and to any other program whose authors commit to
17 | using it. (Some other Free Software Foundation software is covered by
18 | the GNU Lesser General Public License instead.) You can apply it to
19 | your programs, too.
20 |
21 | When we speak of free software, we are referring to freedom, not
22 | price. Our General Public Licenses are designed to make sure that you
23 | have the freedom to distribute copies of free software (and charge for
24 | this service if you wish), that you receive source code or can get it
25 | if you want it, that you can change the software or use pieces of it
26 | in new free programs; and that you know you can do these things.
27 |
28 | To protect your rights, we need to make restrictions that forbid
29 | anyone to deny you these rights or to ask you to surrender the rights.
30 | These restrictions translate to certain responsibilities for you if you
31 | distribute copies of the software, or if you modify it.
32 |
33 | For example, if you distribute copies of such a program, whether
34 | gratis or for a fee, you must give the recipients all the rights that
35 | you have. You must make sure that they, too, receive or can get the
36 | source code. And you must show them these terms so they know their
37 | rights.
38 |
39 | We protect your rights with two steps: (1) copyright the software, and
40 | (2) offer you this license which gives you legal permission to copy,
41 | distribute and/or modify the software.
42 |
43 | Also, for each author's protection and ours, we want to make certain
44 | that everyone understands that there is no warranty for this free
45 | software. If the software is modified by someone else and passed on, we
46 | want its recipients to know that what they have is not the original, so
47 | that any problems introduced by others will not reflect on the original
48 | authors' reputations.
49 |
50 | Finally, any free program is threatened constantly by software
51 | patents. We wish to avoid the danger that redistributors of a free
52 | program will individually obtain patent licenses, in effect making the
53 | program proprietary. To prevent this, we have made it clear that any
54 | patent must be licensed for everyone's free use or not licensed at all.
55 |
56 | The precise terms and conditions for copying, distribution and
57 | modification follow.
58 |
59 | GNU GENERAL PUBLIC LICENSE
60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
61 |
62 | 0. This License applies to any program or other work which contains
63 | a notice placed by the copyright holder saying it may be distributed
64 | under the terms of this General Public License. The "Program", below,
65 | refers to any such program or work, and a "work based on the Program"
66 | means either the Program or any derivative work under copyright law:
67 | that is to say, a work containing the Program or a portion of it,
68 | either verbatim or with modifications and/or translated into another
69 | language. (Hereinafter, translation is included without limitation in
70 | the term "modification".) Each licensee is addressed as "you".
71 |
72 | Activities other than copying, distribution and modification are not
73 | covered by this License; they are outside its scope. The act of
74 | running the Program is not restricted, and the output from the Program
75 | is covered only if its contents constitute a work based on the
76 | Program (independent of having been made by running the Program).
77 | Whether that is true depends on what the Program does.
78 |
79 | 1. You may copy and distribute verbatim copies of the Program's
80 | source code as you receive it, in any medium, provided that you
81 | conspicuously and appropriately publish on each copy an appropriate
82 | copyright notice and disclaimer of warranty; keep intact all the
83 | notices that refer to this License and to the absence of any warranty;
84 | and give any other recipients of the Program a copy of this License
85 | along with the Program.
86 |
87 | You may charge a fee for the physical act of transferring a copy, and
88 | you may at your option offer warranty protection in exchange for a fee.
89 |
90 | 2. You may modify your copy or copies of the Program or any portion
91 | of it, thus forming a work based on the Program, and copy and
92 | distribute such modifications or work under the terms of Section 1
93 | above, provided that you also meet all of these conditions:
94 |
95 | a) You must cause the modified files to carry prominent notices
96 | stating that you changed the files and the date of any change.
97 |
98 | b) You must cause any work that you distribute or publish, that in
99 | whole or in part contains or is derived from the Program or any
100 | part thereof, to be licensed as a whole at no charge to all third
101 | parties under the terms of this License.
102 |
103 | c) If the modified program normally reads commands interactively
104 | when run, you must cause it, when started running for such
105 | interactive use in the most ordinary way, to print or display an
106 | announcement including an appropriate copyright notice and a
107 | notice that there is no warranty (or else, saying that you provide
108 | a warranty) and that users may redistribute the program under
109 | these conditions, and telling the user how to view a copy of this
110 | License. (Exception: if the Program itself is interactive but
111 | does not normally print such an announcement, your work based on
112 | the Program is not required to print an announcement.)
113 |
114 | These requirements apply to the modified work as a whole. If
115 | identifiable sections of that work are not derived from the Program,
116 | and can be reasonably considered independent and separate works in
117 | themselves, then this License, and its terms, do not apply to those
118 | sections when you distribute them as separate works. But when you
119 | distribute the same sections as part of a whole which is a work based
120 | on the Program, the distribution of the whole must be on the terms of
121 | this License, whose permissions for other licensees extend to the
122 | entire whole, and thus to each and every part regardless of who wrote it.
123 |
124 | Thus, it is not the intent of this section to claim rights or contest
125 | your rights to work written entirely by you; rather, the intent is to
126 | exercise the right to control the distribution of derivative or
127 | collective works based on the Program.
128 |
129 | In addition, mere aggregation of another work not based on the Program
130 | with the Program (or with a work based on the Program) on a volume of
131 | a storage or distribution medium does not bring the other work under
132 | the scope of this License.
133 |
134 | 3. You may copy and distribute the Program (or a work based on it,
135 | under Section 2) in object code or executable form under the terms of
136 | Sections 1 and 2 above provided that you also do one of the following:
137 |
138 | a) Accompany it with the complete corresponding machine-readable
139 | source code, which must be distributed under the terms of Sections
140 | 1 and 2 above on a medium customarily used for software interchange; or,
141 |
142 | b) Accompany it with a written offer, valid for at least three
143 | years, to give any third party, for a charge no more than your
144 | cost of physically performing source distribution, a complete
145 | machine-readable copy of the corresponding source code, to be
146 | distributed under the terms of Sections 1 and 2 above on a medium
147 | customarily used for software interchange; or,
148 |
149 | c) Accompany it with the information you received as to the offer
150 | to distribute corresponding source code. (This alternative is
151 | allowed only for noncommercial distribution and only if you
152 | received the program in object code or executable form with such
153 | an offer, in accord with Subsection b above.)
154 |
155 | The source code for a work means the preferred form of the work for
156 | making modifications to it. For an executable work, complete source
157 | code means all the source code for all modules it contains, plus any
158 | associated interface definition files, plus the scripts used to
159 | control compilation and installation of the executable. However, as a
160 | special exception, the source code distributed need not include
161 | anything that is normally distributed (in either source or binary
162 | form) with the major components (compiler, kernel, and so on) of the
163 | operating system on which the executable runs, unless that component
164 | itself accompanies the executable.
165 |
166 | If distribution of executable or object code is made by offering
167 | access to copy from a designated place, then offering equivalent
168 | access to copy the source code from the same place counts as
169 | distribution of the source code, even though third parties are not
170 | compelled to copy the source along with the object code.
171 |
172 | 4. You may not copy, modify, sublicense, or distribute the Program
173 | except as expressly provided under this License. Any attempt
174 | otherwise to copy, modify, sublicense or distribute the Program is
175 | void, and will automatically terminate your rights under this License.
176 | However, parties who have received copies, or rights, from you under
177 | this License will not have their licenses terminated so long as such
178 | parties remain in full compliance.
179 |
180 | 5. You are not required to accept this License, since you have not
181 | signed it. However, nothing else grants you permission to modify or
182 | distribute the Program or its derivative works. These actions are
183 | prohibited by law if you do not accept this License. Therefore, by
184 | modifying or distributing the Program (or any work based on the
185 | Program), you indicate your acceptance of this License to do so, and
186 | all its terms and conditions for copying, distributing or modifying
187 | the Program or works based on it.
188 |
189 | 6. Each time you redistribute the Program (or any work based on the
190 | Program), the recipient automatically receives a license from the
191 | original licensor to copy, distribute or modify the Program subject to
192 | these terms and conditions. You may not impose any further
193 | restrictions on the recipients' exercise of the rights granted herein.
194 | You are not responsible for enforcing compliance by third parties to
195 | this License.
196 |
197 | 7. If, as a consequence of a court judgment or allegation of patent
198 | infringement or for any other reason (not limited to patent issues),
199 | conditions are imposed on you (whether by court order, agreement or
200 | otherwise) that contradict the conditions of this License, they do not
201 | excuse you from the conditions of this License. If you cannot
202 | distribute so as to satisfy simultaneously your obligations under this
203 | License and any other pertinent obligations, then as a consequence you
204 | may not distribute the Program at all. For example, if a patent
205 | license would not permit royalty-free redistribution of the Program by
206 | all those who receive copies directly or indirectly through you, then
207 | the only way you could satisfy both it and this License would be to
208 | refrain entirely from distribution of the Program.
209 |
210 | If any portion of this section is held invalid or unenforceable under
211 | any particular circumstance, the balance of the section is intended to
212 | apply and the section as a whole is intended to apply in other
213 | circumstances.
214 |
215 | It is not the purpose of this section to induce you to infringe any
216 | patents or other property right claims or to contest validity of any
217 | such claims; this section has the sole purpose of protecting the
218 | integrity of the free software distribution system, which is
219 | implemented by public license practices. Many people have made
220 | generous contributions to the wide range of software distributed
221 | through that system in reliance on consistent application of that
222 | system; it is up to the author/donor to decide if he or she is willing
223 | to distribute software through any other system and a licensee cannot
224 | impose that choice.
225 |
226 | This section is intended to make thoroughly clear what is believed to
227 | be a consequence of the rest of this License.
228 |
229 | 8. If the distribution and/or use of the Program is restricted in
230 | certain countries either by patents or by copyrighted interfaces, the
231 | original copyright holder who places the Program under this License
232 | may add an explicit geographical distribution limitation excluding
233 | those countries, so that distribution is permitted only in or among
234 | countries not thus excluded. In such case, this License incorporates
235 | the limitation as if written in the body of this License.
236 |
237 | 9. The Free Software Foundation may publish revised and/or new versions
238 | of the General Public License from time to time. Such new versions will
239 | be similar in spirit to the present version, but may differ in detail to
240 | address new problems or concerns.
241 |
242 | Each version is given a distinguishing version number. If the Program
243 | specifies a version number of this License which applies to it and "any
244 | later version", you have the option of following the terms and conditions
245 | either of that version or of any later version published by the Free
246 | Software Foundation. If the Program does not specify a version number of
247 | this License, you may choose any version ever published by the Free Software
248 | Foundation.
249 |
250 | 10. If you wish to incorporate parts of the Program into other free
251 | programs whose distribution conditions are different, write to the author
252 | to ask for permission. For software which is copyrighted by the Free
253 | Software Foundation, write to the Free Software Foundation; we sometimes
254 | make exceptions for this. Our decision will be guided by the two goals
255 | of preserving the free status of all derivatives of our free software and
256 | of promoting the sharing and reuse of software generally.
257 |
258 | NO WARRANTY
259 |
260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
268 | REPAIR OR CORRECTION.
269 |
270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
278 | POSSIBILITY OF SUCH DAMAGES.
279 |
280 | END OF TERMS AND CONDITIONS
281 |
282 | How to Apply These Terms to Your New Programs
283 |
284 | If you develop a new program, and you want it to be of the greatest
285 | possible use to the public, the best way to achieve this is to make it
286 | free software which everyone can redistribute and change under these terms.
287 |
288 | To do so, attach the following notices to the program. It is safest
289 | to attach them to the start of each source file to most effectively
290 | convey the exclusion of warranty; and each file should have at least
291 | the "copyright" line and a pointer to where the full notice is found.
292 |
293 |
294 | Copyright (C)
295 |
296 | This program is free software; you can redistribute it and/or modify
297 | it under the terms of the GNU General Public License as published by
298 | the Free Software Foundation; either version 2 of the License, or
299 | (at your option) any later version.
300 |
301 | This program is distributed in the hope that it will be useful,
302 | but WITHOUT ANY WARRANTY; without even the implied warranty of
303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
304 | GNU General Public License for more details.
305 |
306 | You should have received a copy of the GNU General Public License along
307 | with this program; if not, write to the Free Software Foundation, Inc.,
308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
309 |
310 | Also add information on how to contact you by electronic and paper mail.
311 |
312 | If the program is interactive, make it output a short notice like this
313 | when it starts in an interactive mode:
314 |
315 | Gnomovision version 69, Copyright (C) year name of author
316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
317 | This is free software, and you are welcome to redistribute it
318 | under certain conditions; type `show c' for details.
319 |
320 | The hypothetical commands `show w' and `show c' should show the appropriate
321 | parts of the General Public License. Of course, the commands you use may
322 | be called something other than `show w' and `show c'; they could even be
323 | mouse-clicks or menu items--whatever suits your program.
324 |
325 | You should also get your employer (if you work as a programmer) or your
326 | school, if any, to sign a "copyright disclaimer" for the program, if
327 | necessary. Here is a sample; alter the names:
328 |
329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program
330 | `Gnomovision' (which makes passes at compilers) written by James Hacker.
331 |
332 | , 1 April 1989
333 | Ty Coon, President of Vice
334 |
335 | This General Public License does not permit incorporating your program into
336 | proprietary programs. If your program is a subroutine library, you may
337 | consider it more useful to permit linking proprietary applications with the
338 | library. If this is what you want to do, use the GNU Lesser General
339 | Public License instead of this License.
340 |
--------------------------------------------------------------------------------
/wp-includes/sqlite/class-wp-sqlite-db.php:
--------------------------------------------------------------------------------
1 | db_connect()".
46 | * 2. The database connection call initializes the SQLite driver.
47 | * 3. The SQLite driver initializes and runs "WP_SQLite_Configurator".
48 | * 4. The configurator uses "WP_SQLite_Information_Schema_Reconstructor",
49 | * which requires "wp-admin/includes/schema.php" when in WordPress.
50 | * 5. The "wp-admin/includes/schema.php" requires the "$wpdb" global,
51 | * which creates a circular dependency.
52 | */
53 | $GLOBALS['wpdb'] = $this;
54 |
55 | parent::__construct( '', '', $dbname, '' );
56 | $this->charset = 'utf8mb4';
57 | }
58 |
59 | /**
60 | * Method to set character set for the database.
61 | *
62 | * This overrides wpdb::set_charset(), only to dummy out the MySQL function.
63 | *
64 | * @see wpdb::set_charset()
65 | *
66 | * @param resource $dbh The resource given by mysql_connect.
67 | * @param string $charset Optional. The character set. Default null.
68 | * @param string $collate Optional. The collation. Default null.
69 | */
70 | public function set_charset( $dbh, $charset = null, $collate = null ) {
71 | }
72 |
73 | /**
74 | * Method to get the character set for the database.
75 | * Hardcoded to utf8mb4 for now.
76 | *
77 | * @param string $table The table name.
78 | * @param string $column The column name.
79 | *
80 | * @return string The character set.
81 | */
82 | public function get_col_charset( $table, $column ) {
83 | // Hardcoded for now.
84 | return 'utf8mb4';
85 | }
86 |
87 | /**
88 | * Changes the current SQL mode, and ensures its WordPress compatibility.
89 | *
90 | * If no modes are passed, it will ensure the current MySQL server modes are compatible.
91 | *
92 | * This overrides wpdb::set_sql_mode() while closely mirroring its implementation.
93 | *
94 | * @param array $modes Optional. A list of SQL modes to set. Default empty array.
95 | */
96 | public function set_sql_mode( $modes = array() ) {
97 | if ( ! $this->dbh instanceof WP_SQLite_Driver ) {
98 | return;
99 | }
100 |
101 | if ( empty( $modes ) ) {
102 | $result = $this->dbh->query( 'SELECT @@SESSION.sql_mode' );
103 | if ( ! isset( $result[0] ) ) {
104 | return;
105 | }
106 |
107 | $modes_str = $result[0]->{'@@SESSION.sql_mode'};
108 | if ( empty( $modes_str ) ) {
109 | return;
110 | }
111 | $modes = explode( ',', $modes_str );
112 | }
113 |
114 | $modes = array_change_key_case( $modes, CASE_UPPER );
115 |
116 | /**
117 | * Filters the list of incompatible SQL modes to exclude.
118 | *
119 | * @since 3.9.0
120 | *
121 | * @param array $incompatible_modes An array of incompatible modes.
122 | */
123 | $incompatible_modes = (array) apply_filters( 'incompatible_sql_modes', $this->incompatible_modes );
124 |
125 | foreach ( $modes as $i => $mode ) {
126 | if ( in_array( $mode, $incompatible_modes, true ) ) {
127 | unset( $modes[ $i ] );
128 | }
129 | }
130 | $modes_str = implode( ',', $modes );
131 |
132 | $this->dbh->query( "SET SESSION sql_mode='$modes_str'" );
133 | }
134 |
135 | /**
136 | * Closes the current database connection.
137 | * Noop in SQLite.
138 | *
139 | * @return bool True to indicate the connection was successfully closed.
140 | */
141 | public function close() {
142 | return true;
143 | }
144 |
145 | /**
146 | * Method to select the database connection.
147 | *
148 | * This overrides wpdb::select(), only to dummy out the MySQL function.
149 | *
150 | * @see wpdb::select()
151 | *
152 | * @param string $db MySQL database name. Not used.
153 | * @param resource|null $dbh Optional link identifier.
154 | */
155 | public function select( $db, $dbh = null ) {
156 | $this->ready = true;
157 | }
158 |
159 | /**
160 | * Method to escape characters.
161 | *
162 | * This overrides wpdb::_real_escape() to avoid using mysql_real_escape_string().
163 | *
164 | * @see wpdb::_real_escape()
165 | *
166 | * @param string $data The string to escape.
167 | *
168 | * @return string escaped
169 | */
170 | public function _real_escape( $data ) {
171 | if ( ! is_scalar( $data ) ) {
172 | return '';
173 | }
174 | $escaped = addslashes( $data );
175 | return $this->add_placeholder_escape( $escaped );
176 | }
177 |
178 | /**
179 | * Method to dummy out wpdb::esc_like() function.
180 | *
181 | * WordPress 4.0.0 introduced esc_like() function that adds backslashes to %,
182 | * underscore and backslash, which is not interpreted as escape character
183 | * by SQLite. So we override it and dummy out this function.
184 | *
185 | * @param string $text The raw text to be escaped. The input typed by the user should have no
186 | * extra or deleted slashes.
187 | *
188 | * @return string Text in the form of a LIKE phrase. The output is not SQL safe. Call $wpdb::prepare()
189 | * or real_escape next.
190 | */
191 | public function esc_like( $text ) {
192 | // The new driver adds "ESCAPE '\\'" to every LIKE expression by default.
193 | // We only need to overload this function to a no-op for the old driver.
194 | if ( $this->dbh instanceof WP_SQLite_Driver ) {
195 | return parent::esc_like( $text );
196 | }
197 | return $text;
198 | }
199 |
200 | /**
201 | * Prints SQL/DB error.
202 | *
203 | * This overrides wpdb::print_error() while closely mirroring its implementation.
204 | *
205 | * @global array $EZSQL_ERROR Stores error information of query and error string.
206 | *
207 | * @param string $str The error to display.
208 | * @return void|false Void if the showing of errors is enabled, false if disabled.
209 | */
210 | public function print_error( $str = '' ) {
211 | global $EZSQL_ERROR;
212 |
213 | if ( ! $str ) {
214 | $str = $this->last_error;
215 | }
216 |
217 | $EZSQL_ERROR[] = array(
218 | 'query' => $this->last_query,
219 | 'error_str' => $str,
220 | );
221 |
222 | if ( $this->suppress_errors ) {
223 | return false;
224 | }
225 |
226 | $caller = $this->get_caller();
227 | if ( $caller ) {
228 | // Not translated, as this will only appear in the error log.
229 | $error_str = sprintf( 'WordPress database error %1$s for query %2$s made by %3$s', $str, $this->last_query, $caller );
230 | } else {
231 | $error_str = sprintf( 'WordPress database error %1$s for query %2$s', $str, $this->last_query );
232 | }
233 |
234 | error_log( $error_str );
235 |
236 | // Are we showing errors?
237 | if ( ! $this->show_errors ) {
238 | return false;
239 | }
240 |
241 | wp_load_translations_early();
242 |
243 | // If there is an error then take note of it.
244 | if ( is_multisite() ) {
245 | $msg = sprintf(
246 | "%s [%s]\n%s\n",
247 | __( 'WordPress database error:' ),
248 | $str,
249 | $this->last_query
250 | );
251 |
252 | if ( defined( 'ERRORLOGFILE' ) ) {
253 | error_log( $msg, 3, ERRORLOGFILE );
254 | }
255 | if ( defined( 'DIEONDBERROR' ) ) {
256 | wp_die( $msg );
257 | }
258 | } else {
259 | $str = htmlspecialchars( $str, ENT_QUOTES );
260 | $query = htmlspecialchars( $this->last_query, ENT_QUOTES );
261 |
262 | printf(
263 | '
%s [%s] %s
',
264 | __( 'WordPress database error:' ),
265 | $str,
266 | $query
267 | );
268 | }
269 | }
270 |
271 | /**
272 | * Method to flush cached data.
273 | *
274 | * This overrides wpdb::flush(). This is not necessarily overridden, because
275 | * $result will never be resource.
276 | *
277 | * @see wpdb::flush
278 | */
279 | public function flush() {
280 | $this->last_result = array();
281 | $this->col_info = null;
282 | $this->last_query = null;
283 | $this->rows_affected = 0;
284 | $this->num_rows = 0;
285 | $this->last_error = '';
286 | $this->result = null;
287 | }
288 |
289 | /**
290 | * Method to do the database connection.
291 | *
292 | * This overrides wpdb::db_connect() to avoid using MySQL function.
293 | *
294 | * @see wpdb::db_connect()
295 | *
296 | * @param bool $allow_bail Not used.
297 | * @return void
298 | */
299 | public function db_connect( $allow_bail = true ) {
300 | if ( $this->dbh ) {
301 | return;
302 | }
303 | $this->init_charset();
304 |
305 | $pdo = null;
306 | if ( isset( $GLOBALS['@pdo'] ) ) {
307 | $pdo = $GLOBALS['@pdo'];
308 | }
309 | if ( defined( 'WP_SQLITE_AST_DRIVER' ) && WP_SQLITE_AST_DRIVER ) {
310 | if ( null === $this->dbname || '' === $this->dbname ) {
311 | $this->bail(
312 | 'The database name was not set. The SQLite driver requires a database name to be set to emulate MySQL information schema tables.',
313 | 'db_connect_fail'
314 | );
315 | return false;
316 | }
317 |
318 | require_once __DIR__ . '/../../wp-includes/parser/class-wp-parser-grammar.php';
319 | require_once __DIR__ . '/../../wp-includes/parser/class-wp-parser.php';
320 | require_once __DIR__ . '/../../wp-includes/parser/class-wp-parser-node.php';
321 | require_once __DIR__ . '/../../wp-includes/parser/class-wp-parser-token.php';
322 | require_once __DIR__ . '/../../wp-includes/mysql/class-wp-mysql-token.php';
323 | require_once __DIR__ . '/../../wp-includes/mysql/class-wp-mysql-lexer.php';
324 | require_once __DIR__ . '/../../wp-includes/mysql/class-wp-mysql-parser.php';
325 | require_once __DIR__ . '/../../wp-includes/sqlite-ast/class-wp-sqlite-connection.php';
326 | require_once __DIR__ . '/../../wp-includes/sqlite-ast/class-wp-sqlite-configurator.php';
327 | require_once __DIR__ . '/../../wp-includes/sqlite-ast/class-wp-sqlite-driver.php';
328 | require_once __DIR__ . '/../../wp-includes/sqlite-ast/class-wp-sqlite-driver-exception.php';
329 | require_once __DIR__ . '/../../wp-includes/sqlite-ast/class-wp-sqlite-information-schema-builder.php';
330 | require_once __DIR__ . '/../../wp-includes/sqlite-ast/class-wp-sqlite-information-schema-exception.php';
331 | require_once __DIR__ . '/../../wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php';
332 | $this->ensure_database_directory( FQDB );
333 |
334 | try {
335 | $connection = new WP_SQLite_Connection(
336 | array(
337 | 'pdo' => $pdo,
338 | 'path' => FQDB,
339 | 'journal_mode' => defined( 'SQLITE_JOURNAL_MODE' ) ? SQLITE_JOURNAL_MODE : null,
340 | )
341 | );
342 | $this->dbh = new WP_SQLite_Driver( $connection, $this->dbname );
343 | $GLOBALS['@pdo'] = $this->dbh->get_connection()->get_pdo();
344 | } catch ( Throwable $e ) {
345 | $this->last_error = $this->format_error_message( $e );
346 | }
347 | } else {
348 | $this->dbh = new WP_SQLite_Translator( $pdo );
349 | $this->last_error = $this->dbh->get_error_message();
350 | $GLOBALS['@pdo'] = $this->dbh->get_pdo();
351 | }
352 | if ( $this->last_error ) {
353 | return false;
354 | }
355 | $this->ready = true;
356 | $this->set_sql_mode();
357 | }
358 |
359 | /**
360 | * Method to dummy out wpdb::check_connection()
361 | *
362 | * @param bool $allow_bail Not used.
363 | *
364 | * @return bool
365 | */
366 | public function check_connection( $allow_bail = true ) {
367 | return true;
368 | }
369 |
370 | /**
371 | * Prepares a SQL query for safe execution.
372 | *
373 | * See "wpdb::prepare()". This override only fixes a WPDB test issue.
374 | *
375 | * @param string $query Query statement with `sprintf()`-like placeholders.
376 | * @param array|mixed $args The array of variables or the first variable to substitute.
377 | * @param mixed ...$args Further variables to substitute when using individual arguments.
378 | * @return string|void Sanitized query string, if there is a query to prepare.
379 | */
380 | public function prepare( $query, ...$args ) {
381 | /*
382 | * Sync "$allow_unsafe_unquoted_parameters" with the WPDB parent property.
383 | * This is only needed because some WPDB tests are accessing the private
384 | * property externally via PHP reflection. This should be fixed WP tests.
385 | */
386 | $wpdb_allow_unsafe_unquoted_parameters = $this->__get( 'allow_unsafe_unquoted_parameters' );
387 | if ( $wpdb_allow_unsafe_unquoted_parameters !== $this->allow_unsafe_unquoted_parameters ) {
388 | $property = new ReflectionProperty( 'wpdb', 'allow_unsafe_unquoted_parameters' );
389 | $property->setAccessible( true );
390 | $property->setValue( $this, $this->allow_unsafe_unquoted_parameters );
391 | $property->setAccessible( false );
392 | }
393 |
394 | return parent::prepare( $query, ...$args );
395 | }
396 |
397 | /**
398 | * Performs a database query.
399 | *
400 | * This overrides wpdb::query() while closely mirroring its implementation.
401 | *
402 | * @see wpdb::query()
403 | *
404 | * @param string $query Database query.
405 | *
406 | * @param string $query Database query.
407 | * @return int|bool Boolean true for CREATE, ALTER, TRUNCATE and DROP queries. Number of rows
408 | * affected/selected for all other queries. Boolean false on error.
409 | */
410 | public function query( $query ) {
411 | // Query Monitor integration:
412 | $query_monitor_active = defined( 'SQLITE_QUERY_MONITOR_LOADED' ) && SQLITE_QUERY_MONITOR_LOADED;
413 | if ( $query_monitor_active && $this->show_errors ) {
414 | $this->hide_errors();
415 | }
416 |
417 | if ( ! $this->ready ) {
418 | return false;
419 | }
420 |
421 | $query = apply_filters( 'query', $query );
422 |
423 | if ( ! $query ) {
424 | $this->insert_id = 0;
425 | return false;
426 | }
427 |
428 | $this->flush();
429 |
430 | // Log how the function was called.
431 | $this->func_call = "\$db->query(\"$query\")";
432 |
433 | // Keep track of the last query for debug.
434 | $this->last_query = $query;
435 |
436 | // Save the query count before running another query.
437 | $last_query_count = count( $this->queries ?? array() );
438 |
439 | /*
440 | * @TODO: WPDB uses "$this->check_current_query" to check table/column
441 | * charset and strip all invalid characters from the query.
442 | * This is an involved process that we can bypass for SQLite,
443 | * if we simply strip all invalid UTF-8 characters from the query.
444 | *
445 | * To do so, mb_convert_encoding can be used with an optional
446 | * fallback to a htmlspecialchars method. E.g.:
447 | * https://github.com/nette/utils/blob/be534713c227aeef57ce1883fc17bc9f9e29eca2/src/Utils/Strings.php#L42
448 | */
449 | $this->_do_query( $query );
450 |
451 | if ( $this->last_error ) {
452 | // Clear insert_id on a subsequent failed insert.
453 | if ( $this->insert_id && preg_match( '/^\s*(insert|replace)\s/i', $query ) ) {
454 | $this->insert_id = 0;
455 | }
456 |
457 | $this->print_error();
458 | return false;
459 | }
460 |
461 | if ( preg_match( '/^\s*(create|alter|truncate|drop)\s/i', $query ) ) {
462 | $return_val = true;
463 | } elseif ( preg_match( '/^\s*(insert|delete|update|replace)\s/i', $query ) ) {
464 | if ( $this->dbh instanceof WP_SQLite_Driver ) {
465 | $this->rows_affected = $this->dbh->get_last_return_value();
466 | } else {
467 | $this->rows_affected = $this->dbh->get_affected_rows();
468 | }
469 |
470 | // Take note of the insert_id.
471 | if ( preg_match( '/^\s*(insert|replace)\s/i', $query ) ) {
472 | $this->insert_id = $this->dbh->get_insert_id();
473 | }
474 |
475 | // Return number of rows affected.
476 | $return_val = $this->rows_affected;
477 | } else {
478 | $num_rows = 0;
479 |
480 | if ( is_array( $this->result ) ) {
481 | $this->last_result = $this->result;
482 | $num_rows = count( $this->result );
483 | }
484 |
485 | // Log and return the number of rows selected.
486 | $this->num_rows = $num_rows;
487 | $return_val = $num_rows;
488 | }
489 |
490 | // Query monitor integration:
491 | if ( $query_monitor_active && class_exists( 'QM_Backtrace' ) ) {
492 | if ( did_action( 'qm/cease' ) ) {
493 | $this->queries = array();
494 | }
495 |
496 | $i = $last_query_count;
497 | if ( ! isset( $this->queries[ $i ] ) ) {
498 | return $return_val;
499 | }
500 |
501 | $this->queries[ $i ]['trace'] = new QM_Backtrace();
502 | if ( ! isset( $this->queries[ $i ][3] ) ) {
503 | $this->queries[ $i ][3] = $this->time_start;
504 | }
505 |
506 | if ( $this->last_error && ! $this->suppress_errors ) {
507 | $this->queries[ $i ]['result'] = new WP_Error( 'qmdb', $this->last_error );
508 | } else {
509 | $this->queries[ $i ]['result'] = (int) $return_val;
510 | }
511 |
512 | // Add SQLite query data.
513 | if ( $this->dbh instanceof WP_SQLite_Driver ) {
514 | $this->queries[ $i ]['sqlite_queries'] = $this->dbh->get_last_sqlite_queries();
515 | } else {
516 | $this->queries[ $i ]['sqlite_queries'] = $this->dbh->executed_sqlite_queries;
517 | }
518 | }
519 | return $return_val;
520 | }
521 |
522 | /**
523 | * Internal function to perform the SQLite query call.
524 | *
525 | * This closely mirrors wpdb::_do_query().
526 | *
527 | * @see wpdb::_do_query()
528 | *
529 | * @param string $query The query to run.
530 | */
531 | private function _do_query( $query ) {
532 | if ( defined( 'SAVEQUERIES' ) && SAVEQUERIES ) {
533 | $this->timer_start();
534 | }
535 |
536 | try {
537 | $this->result = $this->dbh->query( $query );
538 | } catch ( Throwable $e ) {
539 | $this->last_error = $this->format_error_message( $e );
540 | }
541 |
542 | if ( $this->dbh instanceof WP_SQLite_Translator ) {
543 | $this->last_error = $this->dbh->get_error_message();
544 | }
545 |
546 | ++$this->num_queries;
547 |
548 | if ( defined( 'SAVEQUERIES' ) && SAVEQUERIES ) {
549 | $this->log_query(
550 | $query,
551 | $this->timer_stop(),
552 | $this->get_caller(),
553 | $this->time_start,
554 | array()
555 | );
556 | }
557 | }
558 |
559 | /**
560 | * Method to set the class variable $col_info.
561 | *
562 | * This overrides wpdb::load_col_info(), which uses a mysql function.
563 | *
564 | * @see wpdb::load_col_info()
565 | */
566 | protected function load_col_info() {
567 | if ( $this->col_info ) {
568 | return;
569 | }
570 | if ( $this->dbh instanceof WP_SQLite_Driver ) {
571 | $this->col_info = array();
572 | foreach ( $this->dbh->get_last_column_meta() as $column ) {
573 | $this->col_info[] = (object) array(
574 | 'name' => $column['name'],
575 | 'orgname' => $column['mysqli:orgname'],
576 | 'table' => $column['table'],
577 | 'orgtable' => $column['mysqli:orgtable'],
578 | 'def' => '', // Unused, always ''.
579 | 'db' => $column['mysqli:db'],
580 | 'catalog' => 'def', // Unused, always 'def'.
581 | 'max_length' => 0, // As of PHP 8.1, this is always 0.
582 | 'length' => $column['len'],
583 | 'charsetnr' => $column['mysqli:charsetnr'],
584 | 'flags' => $column['mysqli:flags'],
585 | 'type' => $column['mysqli:type'],
586 | 'decimals' => $column['precision'],
587 | );
588 | }
589 | } else {
590 | $this->col_info = $this->dbh->get_columns();
591 | }
592 | }
593 |
594 | /**
595 | * Method to return what the database can do.
596 | *
597 | * This overrides wpdb::has_cap() to avoid using MySQL functions.
598 | * SQLite supports subqueries, but not support collation, group_concat and set_charset.
599 | *
600 | * @see wpdb::has_cap()
601 | *
602 | * @param string $db_cap The feature to check for. Accepts 'collation',
603 | * 'group_concat', 'subqueries', 'set_charset',
604 | * 'utf8mb4', or 'utf8mb4_520'.
605 | *
606 | * @return bool Whether the database feature is supported, false otherwise.
607 | */
608 | public function has_cap( $db_cap ) {
609 | return 'subqueries' === strtolower( $db_cap );
610 | }
611 |
612 | /**
613 | * Method to return database version number.
614 | *
615 | * This overrides wpdb::db_version() to avoid using MySQL function.
616 | * It returns mysql version number, but it means nothing for SQLite.
617 | * So it return the newest mysql version.
618 | *
619 | * @see wpdb::db_version()
620 | */
621 | public function db_version() {
622 | return '8.0';
623 | }
624 |
625 | /**
626 | * Returns the version of the SQLite engine.
627 | *
628 | * @return string SQLite engine version as a string.
629 | */
630 | public function db_server_info() {
631 | return $this->dbh->get_sqlite_version();
632 | }
633 |
634 | /**
635 | * Make sure the SQLite database directory exists and is writable.
636 | * Create .htaccess and index.php files to prevent direct access.
637 | *
638 | * @param string $database_path The path to the SQLite database file.
639 | */
640 | private function ensure_database_directory( string $database_path ) {
641 | $dir = dirname( $database_path );
642 |
643 | // Set the umask to 0000 to apply permissions exactly as specified.
644 | // A non-zero umask affects new file and directory permissions.
645 | $umask = umask( 0 );
646 |
647 | // Ensure database directory.
648 | if ( ! is_dir( $dir ) ) {
649 | if ( ! @mkdir( $dir, 0700, true ) ) {
650 | wp_die( sprintf( 'Failed to create database directory: %s', $dir ), 'Error!' );
651 | }
652 | }
653 | if ( ! is_writable( $dir ) ) {
654 | wp_die( sprintf( 'Database directory is not writable: %s', $dir ), 'Error!' );
655 | }
656 |
657 | // Ensure .htaccess file to prevent direct access.
658 | $path = $dir . DIRECTORY_SEPARATOR . '.htaccess';
659 | if ( ! is_file( $path ) ) {
660 | $result = file_put_contents( $path, 'DENY FROM ALL', LOCK_EX );
661 | if ( false === $result ) {
662 | wp_die( sprintf( 'Failed to create file: %s', $path ), 'Error!' );
663 | }
664 | chmod( $path, 0600 );
665 | }
666 |
667 | // Ensure index.php file to prevent direct access.
668 | $path = $dir . DIRECTORY_SEPARATOR . 'index.php';
669 | if ( ! is_file( $path ) ) {
670 | $result = file_put_contents( $path, '', LOCK_EX );
671 | if ( false === $result ) {
672 | wp_die( sprintf( 'Failed to create file: %s', $path ), 'Error!' );
673 | }
674 | chmod( $path, 0600 );
675 | }
676 |
677 | // Restore the original umask value.
678 | umask( $umask );
679 | }
680 |
681 |
682 | /**
683 | * Format SQLite driver error message.
684 | *
685 | * @return string
686 | */
687 | private function format_error_message( Throwable $e ) {
688 | $output = '