diff --git a/wp/wp-admin/about.php b/wp/wp-admin/about.php index 041a459dd..8da0ec641 100644 --- a/wp/wp-admin/about.php +++ b/wp/wp-admin/about.php @@ -63,6 +63,46 @@
+ Version %s addressed some security issues.' ), + '7.0.4' + ); + ?> + the release notes.' ), + sprintf( + /* translators: %s: WordPress version. */ + esc_url( __( 'https://wordpress.org/documentation/wordpress-version/version-%s/' ) ), + sanitize_title( '7.0.4' ) + ) + ); + ?> +
++ Version %s addressed some security issues.' ), + '7.0.3' + ); + ?> + the release notes.' ), + sprintf( + /* translators: %s: WordPress version. */ + esc_url( __( 'https://wordpress.org/documentation/wordpress-version/version-%s/' ) ), + sanitize_title( '7.0.3' ) + ) + ); + ?> +
user_login = sanitize_user( wp_unslash( $_POST['user_login'] ), true );
}
+ $errors = new WP_Error();
+
$pass1 = '';
$pass2 = '';
if ( isset( $_POST['pass1'] ) ) {
@@ -78,7 +80,12 @@ function edit_user( $user_id = 0 ) {
}
if ( isset( $_POST['email'] ) ) {
- $user->user_email = sanitize_text_field( wp_unslash( $_POST['email'] ) );
+ $maybe_email = wp_unslash( $_POST['email'] );
+ if ( is_string( $maybe_email ) && is_email( $maybe_email ) ) {
+ $user->user_email = $maybe_email;
+ } else {
+ $errors->add( 'invalid_email', __( 'Error: The email address is not correct.' ), array( 'form-field' => 'email' ) );
+ }
}
if ( isset( $_POST['url'] ) ) {
if ( empty( $_POST['url'] ) || 'http://' === $_POST['url'] ) {
@@ -145,8 +152,6 @@ function edit_user( $user_id = 0 ) {
$user->use_ssl = 1;
}
- $errors = new WP_Error();
-
/* checking that username has been typed */
if ( '' === $user->user_login ) {
$errors->add( 'user_login', __( 'Error: Please enter a username.' ) );
diff --git a/wp/wp-admin/js/inline-edit-post.js b/wp/wp-admin/js/inline-edit-post.js
index 6e9f4e9f2..38557e233 100644
--- a/wp/wp-admin/js/inline-edit-post.js
+++ b/wp/wp-admin/js/inline-edit-post.js
@@ -359,7 +359,12 @@ window.wp = window.wp || {};
if ( !$(':input[name="post_author"] option[value="' + $('.post_author', rowData).text() + '"]', editRow).val() ) {
// The post author no longer has edit capabilities, so we need to add them to the list of authors.
- $(':input[name="post_author"]', editRow).prepend('');
+ $(':input[name="post_author"]', editRow).prepend(
+ new Option(
+ $('#post-' + id + ' .author').text(),
+ $('.post_author', rowData).text()
+ )
+ );
}
if ( $( ':input[name="post_author"] option', editRow ).length === 1 ) {
$('label.inline-edit-author', editRow).hide();
diff --git a/wp/wp-admin/js/inline-edit-post.min.js b/wp/wp-admin/js/inline-edit-post.min.js
index cd0d3d0da..ff6d0e373 100644
--- a/wp/wp-admin/js/inline-edit-post.min.js
+++ b/wp/wp-admin/js/inline-edit-post.min.js
@@ -1,2 +1,2 @@
/*! This file is auto-generated */
-window.wp=window.wp||{},function(u,h){window.inlineEditPost={init:function(){var i=this,t=u("#inline-edit"),e=u("#bulk-edit"),t=(i.type=u("table.widefat").hasClass("pages")?"page":"post",i.what="#post-",t.on("keyup",function(t){if(27===t.which)return inlineEditPost.revert()}),e.on("keyup",function(t){if(27===t.which)return inlineEditPost.revert()}),u(".cancel",t).on("click",function(){return inlineEditPost.revert()}),u(".save",t).on("click",function(){return inlineEditPost.save(this)}),u("td",t).on("keydown",function(t){if(13===t.which&&!u(t.target).hasClass("cancel"))return inlineEditPost.save(this)}),u(".cancel",e).on("click",function(){return inlineEditPost.revert()}),u('#inline-edit .inline-edit-private input[value="private"]').on("click",function(){var t=u("input.inline-edit-password-input");u(this).prop("checked")?t.val("").prop("disabled",!0):t.prop("disabled",!1)}),u("#the-list").on("click",".editinline",function(){u(this).attr("aria-expanded","true"),inlineEditPost.edit(this)}),u("#inline-edit fieldset.inline-edit-categories").clone());t.find("*[id]").each(function(){this.id="bulk-edit-"+this.id}),u("#bulk-edit").find("fieldset:first").after(t).siblings("fieldset:last").prepend(u("#inline-edit .inline-edit-tags-wrap").clone()),u('select[name="_status"] option[value="future"]',e).remove(),u("#doaction").on("click",function(t){var e;u('#posts-filter .check-column input[type="checkbox"]:checked').length<1||(i.whichBulkButtonId=u(this).attr("id"),e=i.whichBulkButtonId.substr(2),"edit"===u('select[name="'+e+'"]').val()?(t.preventDefault(),i.setBulk()):0'),u("#bulk-edit").addClass("inline-editor").show(),u('tbody th.check-column input[type="checkbox"]').each(function(){var t,e,i;u(this).prop("checked")&&(a=!1,t=u(this).val(),e=u("#inline_"+t+" .post_title").html()||h.i18n.__("(no title)"),i=h.i18n.sprintf(h.i18n.__("Remove “%s” from Bulk Edit"),e),n+=' %s
- ' . esc_html( basename( WP_CONTENT_DIR ) ) . '/db.php'
- );
- ?>
-
' . esc_html( basename( WP_CONTENT_DIR ) ) . '/db.php'
);
?>
-
-
-
- ' . esc_html( basename( WP_CONTENT_DIR ) ) . '/db.php'
- );
- ?>
- MySQL query:'+n+"
"),e.each(function(){var t=u(this).val();u("#category_"+t).text().split(",").map(function(t){i[t]||(i[t]=0),i[t]++})}),u('.inline-edit-categories input[name="post_category[]"]').each(function(){var t;i[u(this).val()]==e.length?u(this).prop("checked",!0):0').attr("aria-label",t.trim()+": "+h.i18n.__("Some selected posts have this category"))))}),u('.inline-edit-categories input[name="post_category[]"]:indeterminate').on("change",function(){u(this).removeAttr("aria-label").parent().find('input[name="indeterminate_post_category[]"]').remove()}),u(".inline-edit-save button").on("click",function(){u('.inline-edit-categories input[name="post_category[]"]').prop("indeterminate",!1)}),u("#bulk-titles .ntdelbutton").click(function(){var t=u(this),e=t.attr("id").substr(1),i=t.parent().prev().children(".ntdelbutton"),t=t.parent().next().children(".ntdelbutton");u("input#cb-select-all-1, input#cb-select-all-2").prop("checked",!1),u('table.widefat input[value="'+e+'"]').prop("checked",!1),u("#_"+e).parent().remove(),h.a11y.speak(h.i18n.__("Item removed."),"assertive"),t.length?t.focus():i.length?i.focus():(u("#bulk-titles-list").remove(),inlineEditPost.revert(),h.a11y.speak(h.i18n.__("All selected items have been removed. Select new items to use Bulk Actions.")))}),"post"===t&&u("tr.inline-editor textarea[data-wp-taxonomy]").each(function(t,e){u(e).autocomplete("instance")||u(e).wpTagsSuggest()}),u("#bulk-edit .inline-edit-wrapper").attr("tabindex","-1").focus(),u("html, body").animate({scrollTop:0},"fast")},edit:function(n){var t,a,e,i,s,r,o,l,d=this,c=!0;for(d.revert(),"object"==typeof n&&(n=d.getId(n)),t=["post_title","post_name","post_author","_status","jj","mm","aa","hh","mn","ss","post_password","post_format","menu_order","page_template"],"page"===d.type&&t.push("post_parent"),a=u("#inline-edit").clone(!0),u("td",a).attr("colspan",u("th:visible, td:visible",".widefat:first thead").length),u("td",a).find("#quick-edit-legend").removeAttr("id"),u("td",a).find('p[id^="quick-edit-"]').removeAttr("id"),u(d.what+n).removeClass("is-expanded").hide().after(a).after(''),e=u("#inline_"+n),u(':input[name="post_author"] option[value="'+u(".post_author",e).text()+'"]',a).val()||u(':input[name="post_author"]',a).prepend('"),1===u(':input[name="post_author"] option',a).length&&u("label.inline-edit-author",a).hide(),o=0;o ]*?>/g,""),e.removeClass("hidden"),i.html(t),h.a11y.speak(i.text())):(e.removeClass("hidden"),i.text(h.i18n.__("Error while saving the changes.")),h.a11y.speak(h.i18n.__("Error while saving the changes.")))},"html"),!1},revert:function(){var t=u(".widefat"),e=u(".inline-editor",t).attr("id");return e&&(u(".spinner",t).removeClass("is-active"),("bulk-edit"===e?(u("#bulk-edit",t).removeClass("inline-editor").hide().siblings(".hidden").remove(),u("#bulk-titles").empty(),u("#inlineedit").append(u("#bulk-edit")),u("#"+inlineEditPost.whichBulkButtonId)):(u("#"+e).siblings("tr.hidden").addBack().remove(),e=e.substr(e.lastIndexOf("-")+1),u(this.what+e).show().find(".editinline").attr("aria-expanded","false"))).trigger("focus")),!1},getId:function(t){t=u(t).closest("tr").attr("id").split("-");return t[t.length-1]}},u(function(){inlineEditPost.init()}),u(function(){void 0!==h&&h.heartbeat&&h.heartbeat.interval(10)}).on("heartbeat-tick.wp-check-locked-posts",function(t,e){var n=e["wp-check-locked-posts"]||{},a="wp-locked";u("#the-list tr").each(function(t,e){var i=e.id,e=u(e);n.hasOwnProperty(i)?e.hasClass(a)||(i=n[i],e.find(".column-title .locked-text").text(i.text),e.find(".check-column checkbox").prop("checked",!1),i.avatar_src&&(i=u(" '),u("#bulk-edit").addClass("inline-editor").show(),u('tbody th.check-column input[type="checkbox"]').each(function(){var t,e,i;u(this).prop("checked")&&(a=!1,t=u(this).val(),e=u("#inline_"+t+" .post_title").html()||h.i18n.__("(no title)"),i=h.i18n.sprintf(h.i18n.__("Remove “%s” from Bulk Edit"),e),n+='",{class:"avatar avatar-18 photo",width:18,height:18,alt:"",src:i.avatar_src,srcset:i.avatar_src_2x?i.avatar_src_2x+" 2x":void 0}),e.find(".column-title .locked-avatar").empty().append(i)),e.addClass(a)):e.hasClass(a)&&e.removeClass(a).find(".locked-info span").empty()})}).on("heartbeat-send.wp-check-locked-posts",function(t,e){var i=[];u("#the-list tr").each(function(t,e){e.id&&i.push(e.id)}),i.length&&(e["wp-check-locked-posts"]=i)})}(jQuery,window.wp);
\ No newline at end of file
+window.wp=window.wp||{},function(u,h){window.inlineEditPost={init:function(){var i=this,t=u("#inline-edit"),e=u("#bulk-edit"),t=(i.type=u("table.widefat").hasClass("pages")?"page":"post",i.what="#post-",t.on("keyup",function(t){if(27===t.which)return inlineEditPost.revert()}),e.on("keyup",function(t){if(27===t.which)return inlineEditPost.revert()}),u(".cancel",t).on("click",function(){return inlineEditPost.revert()}),u(".save",t).on("click",function(){return inlineEditPost.save(this)}),u("td",t).on("keydown",function(t){if(13===t.which&&!u(t.target).hasClass("cancel"))return inlineEditPost.save(this)}),u(".cancel",e).on("click",function(){return inlineEditPost.revert()}),u('#inline-edit .inline-edit-private input[value="private"]').on("click",function(){var t=u("input.inline-edit-password-input");u(this).prop("checked")?t.val("").prop("disabled",!0):t.prop("disabled",!1)}),u("#the-list").on("click",".editinline",function(){u(this).attr("aria-expanded","true"),inlineEditPost.edit(this)}),u("#inline-edit fieldset.inline-edit-categories").clone());t.find("*[id]").each(function(){this.id="bulk-edit-"+this.id}),u("#bulk-edit").find("fieldset:first").after(t).siblings("fieldset:last").prepend(u("#inline-edit .inline-edit-tags-wrap").clone()),u('select[name="_status"] option[value="future"]',e).remove(),u("#doaction").on("click",function(t){var e;u('#posts-filter .check-column input[type="checkbox"]:checked').length<1||(i.whichBulkButtonId=u(this).attr("id"),e=i.whichBulkButtonId.substr(2),"edit"===u('select[name="'+e+'"]').val()?(t.preventDefault(),i.setBulk()):0
'+n+"
"),e.each(function(){var t=u(this).val();u("#category_"+t).text().split(",").map(function(t){i[t]||(i[t]=0),i[t]++})}),u('.inline-edit-categories input[name="post_category[]"]').each(function(){var t;i[u(this).val()]==e.length?u(this).prop("checked",!0):0').attr("aria-label",t.trim()+": "+h.i18n.__("Some selected posts have this category"))))}),u('.inline-edit-categories input[name="post_category[]"]:indeterminate').on("change",function(){u(this).removeAttr("aria-label").parent().find('input[name="indeterminate_post_category[]"]').remove()}),u(".inline-edit-save button").on("click",function(){u('.inline-edit-categories input[name="post_category[]"]').prop("indeterminate",!1)}),u("#bulk-titles .ntdelbutton").click(function(){var t=u(this),e=t.attr("id").substr(1),i=t.parent().prev().children(".ntdelbutton"),t=t.parent().next().children(".ntdelbutton");u("input#cb-select-all-1, input#cb-select-all-2").prop("checked",!1),u('table.widefat input[value="'+e+'"]').prop("checked",!1),u("#_"+e).parent().remove(),h.a11y.speak(h.i18n.__("Item removed."),"assertive"),t.length?t.focus():i.length?i.focus():(u("#bulk-titles-list").remove(),inlineEditPost.revert(),h.a11y.speak(h.i18n.__("All selected items have been removed. Select new items to use Bulk Actions.")))}),"post"===t&&u("tr.inline-editor textarea[data-wp-taxonomy]").each(function(t,e){u(e).autocomplete("instance")||u(e).wpTagsSuggest()}),u("#bulk-edit .inline-edit-wrapper").attr("tabindex","-1").focus(),u("html, body").animate({scrollTop:0},"fast")},edit:function(n){var t,a,e,i,s,r,o,l,d=this,c=!0;for(d.revert(),"object"==typeof n&&(n=d.getId(n)),t=["post_title","post_name","post_author","_status","jj","mm","aa","hh","mn","ss","post_password","post_format","menu_order","page_template"],"page"===d.type&&t.push("post_parent"),a=u("#inline-edit").clone(!0),u("td",a).attr("colspan",u("th:visible, td:visible",".widefat:first thead").length),u("td",a).find("#quick-edit-legend").removeAttr("id"),u("td",a).find('p[id^="quick-edit-"]').removeAttr("id"),u(d.what+n).removeClass("is-expanded").hide().after(a).after(''),e=u("#inline_"+n),u(':input[name="post_author"] option[value="'+u(".post_author",e).text()+'"]',a).val()||u(':input[name="post_author"]',a).prepend(new Option(u("#post-"+n+" .author").text(),u(".post_author",e).text())),1===u(':input[name="post_author"] option',a).length&&u("label.inline-edit-author",a).hide(),o=0;o ]*?>/g,""),e.removeClass("hidden"),i.html(t),h.a11y.speak(i.text())):(e.removeClass("hidden"),i.text(h.i18n.__("Error while saving the changes.")),h.a11y.speak(h.i18n.__("Error while saving the changes.")))},"html"),!1},revert:function(){var t=u(".widefat"),e=u(".inline-editor",t).attr("id");return e&&(u(".spinner",t).removeClass("is-active"),("bulk-edit"===e?(u("#bulk-edit",t).removeClass("inline-editor").hide().siblings(".hidden").remove(),u("#bulk-titles").empty(),u("#inlineedit").append(u("#bulk-edit")),u("#"+inlineEditPost.whichBulkButtonId)):(u("#"+e).siblings("tr.hidden").addBack().remove(),e=e.substr(e.lastIndexOf("-")+1),u(this.what+e).show().find(".editinline").attr("aria-expanded","false"))).trigger("focus")),!1},getId:function(t){t=u(t).closest("tr").attr("id").split("-");return t[t.length-1]}},u(function(){inlineEditPost.init()}),u(function(){void 0!==h&&h.heartbeat&&h.heartbeat.interval(10)}).on("heartbeat-tick.wp-check-locked-posts",function(t,e){var n=e["wp-check-locked-posts"]||{},a="wp-locked";u("#the-list tr").each(function(t,e){var i=e.id,e=u(e);n.hasOwnProperty(i)?e.hasClass(a)||(i=n[i],e.find(".column-title .locked-text").text(i.text),e.find(".check-column checkbox").prop("checked",!1),i.avatar_src&&(i=u(" ",{class:"avatar avatar-18 photo",width:18,height:18,alt:"",src:i.avatar_src,srcset:i.avatar_src_2x?i.avatar_src_2x+" 2x":void 0}),e.find(".column-title .locked-avatar").empty().append(i)),e.addClass(a)):e.hasClass(a)&&e.removeClass(a).find(".locked-info span").empty()})}).on("heartbeat-send.wp-check-locked-posts",function(t,e){var i=[];u("#the-list tr").each(function(t,e){e.id&&i.push(e.id)}),i.length&&(e["wp-check-locked-posts"]=i)})}(jQuery,window.wp);
\ No newline at end of file
diff --git a/wp/wp-content/plugins/sqlite-database-integration/activate.php b/wp/wp-content/plugins/sqlite-database-integration/activate.php
index 1001914a2..2274dd1c6 100644
--- a/wp/wp-content/plugins/sqlite-database-integration/activate.php
+++ b/wp/wp-content/plugins/sqlite-database-integration/activate.php
@@ -1,23 +1,23 @@
base ) && 'settings_page_sqlite-integration' === $current_screen->base ) {
+ if (
+ ! isset( $_GET['page'], $_GET['confirm-install'] )
+ || 'sqlite-integration' !== $_GET['page']
+ ) {
return;
}
- if ( isset( $_GET['confirm-install'] ) && wp_verify_nonce( $_GET['_wpnonce'], 'sqlite-install' ) ) {
- // Handle upgrading from the performance-lab plugin.
- if ( isset( $_GET['upgrade-from-pl'] ) ) {
- global $wp_filesystem;
- require_once ABSPATH . '/wp-admin/includes/file.php';
- // Delete the previous db.php file.
- $wp_filesystem->delete( WP_CONTENT_DIR . '/db.php' );
- // Deactivate the performance-lab SQLite module.
- $pl_option_name = defined( 'PERFLAB_MODULES_SETTING' ) ? PERFLAB_MODULES_SETTING : 'perflab_modules_settings';
- $pl_option = get_option( $pl_option_name, array() );
- unset( $pl_option['database/sqlite'] );
- update_option( $pl_option_name, $pl_option );
- }
- sqlite_plugin_copy_db_file();
- // WordPress will automatically redirect to the install screen here.
- wp_redirect( admin_url() );
- exit;
+ if ( ! current_user_can( sqlite_plugin_get_manage_capability() ) ) {
+ wp_die(
+ esc_html__( 'Sorry, you are not allowed to install the SQLite database drop-in.', 'sqlite-database-integration' ),
+ 403
+ );
}
+
+ check_admin_referer( 'sqlite-install' );
+
+ sqlite_plugin_copy_db_file();
+
+ // WordPress will automatically redirect to the install screen here.
+ wp_redirect( admin_url() );
+ exit;
}
add_action( 'admin_init', 'sqlite_activation' );
@@ -75,7 +71,7 @@ function ( $result ) {
* When the plugin gets merged in wp-core, this is not to be ported.
*/
function sqlite_plugin_copy_db_file() {
- // Bail early if the PDO SQLite extension is not loaded.
+ // Bail early if the pdo_sqlite extension is not loaded.
if ( ! extension_loaded( 'pdo_sqlite' ) ) {
return;
}
diff --git a/wp/wp-content/plugins/sqlite-database-integration/admin-notices.php b/wp/wp-content/plugins/sqlite-database-integration/admin-notices.php
index a455cc86c..fb440d62d 100644
--- a/wp/wp-content/plugins/sqlite-database-integration/admin-notices.php
+++ b/wp/wp-content/plugins/sqlite-database-integration/admin-notices.php
@@ -1,9 +1,6 @@
' . esc_html( basename( WP_CONTENT_DIR ) ) . '/db.php',
- esc_url( admin_url( 'plugins.php' ) )
+ esc_url( self_admin_url( 'plugins.php' ) )
)
);
}
-add_action( 'admin_notices', 'sqlite_plugin_admin_notice' ); // Add the admin notices.
-
-// Remove the PL-plugin admin notices for SQLite.
-remove_action( 'admin_notices', 'perflab_sqlite_plugin_admin_notice' );
+add_action( is_multisite() ? 'network_admin_notices' : 'admin_notices', 'sqlite_plugin_admin_notice' ); // Add the admin notices.
diff --git a/wp/wp-content/plugins/sqlite-database-integration/admin-page.php b/wp/wp-content/plugins/sqlite-database-integration/admin-page.php
index 828156267..8f833f496 100644
--- a/wp/wp-content/plugins/sqlite-database-integration/admin-page.php
+++ b/wp/wp-content/plugins/sqlite-database-integration/admin-page.php
@@ -1,31 +1,36 @@
deactivate the plugin. Alternatively, you can manually delete the %2$s file from your server.', 'sqlite-database-integration' ),
- esc_url( admin_url( 'plugins.php' ) ),
+ esc_url( self_admin_url( 'plugins.php' ) ),
'' . esc_html( basename( WP_CONTENT_DIR ) ) . '/db.php'
);
?>
@@ -66,40 +71,17 @@ function sqlite_integration_admin_screen() {
-
- " or "
" format.
$parts = $node->get_descendant_nodes( 'identifier' );
$table = array_pop( $parts );
@@ -5022,7 +5499,7 @@ public function translate_table_ref( WP_Parser_Node $node ): string {
* @param array $column_map Optional. A map of column names (old name -> new name)
* to use when copying data from the original table.
* When not provided, all columns are copied without renaming.
- * @throws WP_SQLite_Driver_Exception
+ * @throws WP_MySQL_On_SQLite_Exception
*/
private function recreate_table_from_information_schema(
bool $table_is_temporary,
@@ -5190,7 +5667,7 @@ private function apply_auto_increment_table_option(
* @param WP_Parser_Node $like_or_where The "likeOrWhere" AST node.
* @param string $like_column The column name to use in the LIKE clause ("table_name", "column_name", etc.).
* @return string The translated value.
- * @throws WP_SQLite_Driver_Exception When the translation fails.
+ * @throws WP_MySQL_On_SQLite_Exception When the translation fails.
*/
private function translate_show_like_or_where_condition( WP_Parser_Node $like_or_where, string $like_column ): string {
$like_clause = $like_or_where->get_first_child_node( 'likeClause' );
@@ -5300,13 +5777,7 @@ private function translate_insert_or_replace_body(
// Check if the table exists.
if ( 0 === count( $columns ) ) {
- throw $this->new_driver_exception(
- sprintf(
- "SQLSTATE[42S02]: Base table or view not found: 1146 Table '%s' doesn't exist",
- $table_name
- ),
- '42S02'
- );
+ throw $this->new_table_not_found_exception( $table_name );
}
// Get a list of columns that are targeted by the INSERT or REPLACE query.
@@ -5614,13 +6085,7 @@ private function translate_update_list( string $table_name, WP_Parser_Node $pare
// Check if the table exists.
if ( 0 === count( $columns ) ) {
- throw $this->new_driver_exception(
- sprintf(
- "SQLSTATE[42S02]: Base table or view not found: 1146 Table '%s' doesn't exist",
- $table_name
- ),
- '42S02'
- );
+ throw $this->new_table_not_found_exception( $table_name );
}
$column_map = array_combine( array_column( $columns, 'COLUMN_NAME' ), $columns );
@@ -5682,6 +6147,171 @@ private function translate_update_list( string $table_name, WP_Parser_Node $pare
return $fragment;
}
+ /**
+ * Get the default SQL modes for the emulated MySQL version.
+ *
+ * @return string[] Default SQL mode names.
+ */
+ private function get_default_sql_modes(): array {
+ $sql_modes = self::DEFAULT_SQL_MODES;
+ if ( $this->mysql_version < 80000 ) {
+ $sql_modes[] = 'NO_AUTO_CREATE_USER';
+ }
+
+ return $sql_modes;
+ }
+
+ /**
+ * Set the active SQL modes from a name list or numeric bitmask.
+ *
+ * @param string[]|int $modes SQL mode names or a numeric bitmask.
+ */
+ private function set_sql_modes( $modes ): void {
+ if ( null === $modes ) {
+ throw $this->new_invalid_sql_mode_exception( $modes );
+ }
+
+ $known_sql_modes_mask = array_sum( self::SQL_MODES );
+
+ // TIME_TRUNCATE_FRACTIONAL was introduced in MySQL 8.0.1.
+ if ( $this->mysql_version < 80001 ) {
+ $known_sql_modes_mask &= ~self::SQL_MODES['TIME_TRUNCATE_FRACTIONAL'];
+ }
+
+ // Numeric SQL mode bitmap assignment (e.g., "SET sql_mode = 4" for ANSI_QUOTES).
+ if ( is_int( $modes ) ) {
+ // Ensure the bitmap contains only SQL mode bits known to the emulated MySQL version.
+ if ( $modes < 0 || ( $modes & ~$known_sql_modes_mask ) !== 0 ) {
+ throw $this->new_invalid_sql_mode_exception( $modes );
+ }
+
+ // Reject recognized but no longer supported SQL modes.
+ $unsupported_sql_modes = 0;
+ foreach ( self::SQL_MODES as $mode => $value ) {
+ if ( ( $modes & $value ) !== 0 && $this->is_sql_mode_removed( $mode ) ) {
+ $unsupported_sql_modes |= $value;
+ }
+ }
+
+ if ( 0 !== $unsupported_sql_modes ) {
+ throw $this->new_driver_exception(
+ sprintf(
+ 'SQLSTATE[HY000]: General error: 3899 sql_mode=0x%08x is not supported.',
+ $unsupported_sql_modes
+ ),
+ 'HY000'
+ );
+ }
+
+ $sql_modes = $modes;
+ } elseif ( is_array( $modes ) ) {
+ // String SQL mode assignment (e.g., "SET sql_mode = 'ANSI_QUOTES,STRICT_TRANS_TABLES'").
+ $sql_modes = 0;
+ foreach ( $modes as $mode ) {
+ if ( '' === $mode ) {
+ continue;
+ }
+
+ $normalized_mode = strtoupper( $mode );
+ $mode_value = self::SQL_MODES[ $normalized_mode ] ?? 0;
+ if (
+ 0 === ( $mode_value & $known_sql_modes_mask )
+ || ( 'NOT_USED' === $normalized_mode && $this->mysql_version < 80000 )
+ || $this->is_sql_mode_removed( $normalized_mode )
+ ) {
+ throw $this->new_invalid_sql_mode_exception( $mode );
+ }
+
+ $sql_modes |= $mode_value;
+ }
+ } else {
+ throw $this->new_driver_exception(
+ "SQLSTATE[42000]: Syntax error or access violation: 1232 Incorrect argument type to variable 'sql_mode'",
+ '42000'
+ );
+ }
+
+ if ( ( $sql_modes & self::SQL_MODES['NO_BACKSLASH_ESCAPES'] ) !== 0 ) {
+ throw $this->new_not_supported_exception( "SQL mode 'NO_BACKSLASH_ESCAPES'" );
+ }
+
+ /*
+ * MySQL retains composite SQL modes while enabling their component modes.
+ * Store both so "@@sql_mode" and individual mode checks match MySQL, even
+ * though not all resulting modes are respected by the emulation yet.
+ *
+ * See:
+ * https://dev.mysql.com/doc/refman/8.4/en/sql-mode.html#sql-mode-combo
+ * https://github.com/mysql/mysql-server/blob/8.4/sql/sys_vars.cc
+ */
+ if ( ( $sql_modes & self::SQL_MODES['ANSI'] ) !== 0 ) {
+ $sql_modes |= self::SQL_MODES['REAL_AS_FLOAT']
+ | self::SQL_MODES['PIPES_AS_CONCAT']
+ | self::SQL_MODES['ANSI_QUOTES']
+ | self::SQL_MODES['IGNORE_SPACE']
+ | self::SQL_MODES['ONLY_FULL_GROUP_BY'];
+ }
+
+ $this->active_sql_modes = $sql_modes;
+ }
+
+ /**
+ * Check whether an SQL mode was removed from the emulated MySQL version.
+ *
+ * @param string $mode Normalized SQL mode name.
+ * @return bool Whether the SQL mode was removed.
+ */
+ private function is_sql_mode_removed( string $mode ): bool {
+ /*
+ * MySQL still recognizes the legacy bits for modes removed in 8.0.11
+ * so it can report them as unsupported, rather than unknown.
+ */
+ return $this->mysql_version >= 80011
+ && in_array(
+ $mode,
+ array(
+ 'POSTGRESQL',
+ 'ORACLE',
+ 'MSSQL',
+ 'DB2',
+ 'MAXDB',
+ 'NO_KEY_OPTIONS',
+ 'NO_TABLE_OPTIONS',
+ 'NO_FIELD_OPTIONS',
+ 'MYSQL323',
+ 'MYSQL40',
+ 'NO_AUTO_CREATE_USER',
+ ),
+ true
+ );
+ }
+
+ /**
+ * Get the active SQL mode names in canonical bitmask order.
+ *
+ * @return string[] Active SQL mode names.
+ */
+ private function get_active_sql_mode_names(): array {
+ $active_modes = array();
+ foreach ( self::SQL_MODES as $mode => $value ) {
+ if ( ( $this->active_sql_modes & $value ) !== 0 ) {
+ if ( 'NOT_USED' === $mode && $this->mysql_version < 80000 ) {
+ /*
+ * MySQL 5.7 represents reserved bit 4 with a comma in its mode
+ * name table. Two empty components preserve that serialization
+ * when the final list is joined with commas.
+ */
+ $active_modes[] = '';
+ $active_modes[] = '';
+ } else {
+ $active_modes[] = $mode;
+ }
+ }
+ }
+
+ return $active_modes;
+ }
+
/**
* Store column metadata for the last SQLite statement.
*
@@ -6170,7 +6800,7 @@ private function get_database_name( WP_Parser_Node $node ): string {
* @param string $table_name The name of the table to create.
* @param string|null $new_table_name Override the original table name for ALTER TABLE emulation.
* @return string[] Queries to create the table, indexes, and constraints.
- * @throws WP_SQLite_Driver_Exception When the table information is missing.
+ * @throws WP_MySQL_On_SQLite_Exception When the table information is missing.
*/
private function get_sqlite_create_table_statement(
bool $table_is_temporary,
@@ -6194,10 +6824,7 @@ private function get_sqlite_create_table_statement(
)->fetch( PDO::FETCH_ASSOC );
if ( false === $table_info ) {
- throw $this->new_driver_exception(
- sprintf( "Table '%s' doesn't exist", $table_name ),
- '42S02'
- );
+ throw $this->new_table_not_found_exception( $table_name );
}
// 2. Get column info.
@@ -7019,6 +7646,7 @@ private function flush(): void {
$this->last_sqlite_queries = array();
$this->last_result_statement = null;
$this->last_affected_rows = null;
+ $this->last_insert_id = '0';
$this->last_column_meta = array();
$this->is_readonly = false;
$this->wrapper_transaction_type = null;
@@ -7124,19 +7752,21 @@ private function create_result_statement_from_data( array $columns, array $rows
}
/**
- * Create a new SQLite driver exception.
+ * Create a new MySQL-on-SQLite driver exception.
*
- * @param string $message The exception message.
- * @param int|string $code The exception code. For PDO errors, a string representing SQLSTATE.
- * @param Throwable|null $previous The previous exception.
- * @return WP_SQLite_Driver_Exception
+ * @param string $message The exception message.
+ * @param int|string $code The exception code. For PDO errors, a string representing SQLSTATE.
+ * @param Throwable|null $previous The previous exception.
+ * @param array|null $error_info PDO-style error information.
+ * @return WP_MySQL_On_SQLite_Exception
*/
private function new_driver_exception(
string $message,
$code = 0,
- ?Throwable $previous = null
- ): WP_SQLite_Driver_Exception {
- return new WP_SQLite_Driver_Exception( $this, $message, $code, $previous );
+ ?Throwable $previous = null,
+ ?array $error_info = null
+ ): WP_MySQL_On_SQLite_Exception {
+ return new WP_MySQL_On_SQLite_Exception( $this, $message, $code, $previous, $error_info );
}
/**
@@ -7145,10 +7775,10 @@ private function new_driver_exception(
* This exception can be used to mark cases that should never occur according
* to the MySQL grammar. It may serve as an assertion that should never fail.
*
- * @return WP_SQLite_Driver_Exception
+ * @return WP_MySQL_On_SQLite_Exception
*/
- private function new_invalid_input_exception(): WP_SQLite_Driver_Exception {
- return new WP_SQLite_Driver_Exception( $this, 'MySQL query syntax error.' );
+ private function new_invalid_input_exception(): WP_MySQL_On_SQLite_Exception {
+ return new WP_MySQL_On_SQLite_Exception( $this, 'MySQL query syntax error.' );
}
/**
@@ -7157,27 +7787,82 @@ private function new_invalid_input_exception(): WP_SQLite_Driver_Exception {
* This exception can be used to mark MySQL constructs that are not supported.
*
* @param string $cause The cause, indicating which construct is not supported.
- * @return WP_SQLite_Driver_Exception
+ * @return WP_MySQL_On_SQLite_Exception
*/
- private function new_not_supported_exception( string $cause ): WP_SQLite_Driver_Exception {
- return new WP_SQLite_Driver_Exception(
+ private function new_not_supported_exception( string $cause ): WP_MySQL_On_SQLite_Exception {
+ return new WP_MySQL_On_SQLite_Exception(
$this,
sprintf( 'MySQL query not supported. Cause: %s', $cause )
);
}
+ /**
+ * Create a MySQL-compatible table-not-found exception.
+ *
+ * @param string $table_name The missing table name.
+ * @param Throwable|null $previous The previous exception.
+ * @return WP_MySQL_On_SQLite_Exception
+ */
+ private function new_table_not_found_exception(
+ string $table_name,
+ ?Throwable $previous = null
+ ): WP_MySQL_On_SQLite_Exception {
+ $driver_message = sprintf( "Table '%s' doesn't exist", $table_name );
+ return $this->new_driver_exception(
+ 'SQLSTATE[42S02]: Base table or view not found: 1146 ' . $driver_message,
+ '42S02',
+ $previous,
+ array( '42S02', 1146, $driver_message )
+ );
+ }
+
+ /**
+ * Create a MySQL-compatible exception for an invalid SQL mode value.
+ *
+ * @param mixed $value The invalid SQL mode value.
+ * @return WP_MySQL_On_SQLite_Exception
+ */
+ private function new_invalid_sql_mode_exception( $value ): WP_MySQL_On_SQLite_Exception {
+ return $this->new_driver_exception(
+ sprintf(
+ "SQLSTATE[42000]: Syntax error or access violation: 1231 Variable 'sql_mode' can't be set to the value of '%s'",
+ null === $value ? 'NULL' : (string) $value
+ ),
+ '42000'
+ );
+ }
+
/**
* Create a new access denied exception for the information schema database.
*
- * @return WP_SQLite_Driver_Exception
+ * @return WP_MySQL_On_SQLite_Exception
*/
- private function new_access_denied_to_information_schema_exception(): WP_SQLite_Driver_Exception {
+ private function new_access_denied_to_information_schema_exception(): WP_MySQL_On_SQLite_Exception {
return $this->new_driver_exception(
"Access denied for user 'root'@'%' to database 'information_schema'",
'42000'
);
}
+ /**
+ * Convert a SQLite PDO exception to a MySQL-on-SQLite driver exception.
+ *
+ * @param PDOException $e The SQLite PDO exception.
+ * @return WP_MySQL_On_SQLite_Exception
+ */
+ private function convert_sqlite_exception( PDOException $e ): WP_MySQL_On_SQLite_Exception {
+ $error_info = is_array( $e->errorInfo ) ? $e->errorInfo : array();
+ $sqlstate = $error_info[0] ?? null;
+ $error_message = $error_info[2] ?? null;
+
+ if ( 'HY000' === $sqlstate && 0 === strpos( $error_message, 'no such table: ' ) ) {
+ $table_name = substr( $error_message, strlen( 'no such table: ' ) );
+ return $this->new_table_not_found_exception( $table_name, $e );
+ }
+
+ return $this->new_driver_exception( $e->getMessage(), $e->getCode(), $e );
+ }
+
/**
* Convert an information schema exception to a MySQL-like driver exception.
*
@@ -7192,52 +7877,63 @@ private function new_access_denied_to_information_schema_exception(): WP_SQLite_
private function convert_information_schema_exception( WP_SQLite_Information_Schema_Exception $e ): Throwable {
switch ( $e->get_type() ) {
case WP_SQLite_Information_Schema_Exception::TYPE_DUPLICATE_TABLE_NAME:
+ $driver_message = sprintf( "Table '%s' already exists", $e->get_data()['table_name'] );
return $this->new_driver_exception(
- sprintf(
- "SQLSTATE[42S01]: Base table or view already exists: 1050 Table '%s' already exists",
- $e->get_data()['table_name']
- ),
- '42S01'
+ 'SQLSTATE[42S01]: Base table or view already exists: 1050 ' . $driver_message,
+ '42S01',
+ null,
+ array( '42S01', 1050, $driver_message )
);
case WP_SQLite_Information_Schema_Exception::TYPE_DUPLICATE_COLUMN_NAME:
+ $driver_message = sprintf( "Duplicate column name '%s'", $e->get_data()['column_name'] );
return $this->new_driver_exception(
- sprintf(
- "SQLSTATE[42S21]: Column already exists: 1060 Duplicate column name '%s'",
- $e->get_data()['column_name']
- ),
- '42S21'
+ 'SQLSTATE[42S21]: Column already exists: 1060 ' . $driver_message,
+ '42S21',
+ null,
+ array( '42S21', 1060, $driver_message )
);
case WP_SQLite_Information_Schema_Exception::TYPE_DUPLICATE_KEY_NAME:
+ $driver_message = sprintf( "Duplicate key name '%s'", $e->get_data()['key_name'] );
return $this->new_driver_exception(
- sprintf(
- "SQLSTATE[42000]: Syntax error or access violation: 1061 Duplicate key name '%s'",
- $e->get_data()['key_name']
- ),
- '42S21'
+ 'SQLSTATE[42000]: Syntax error or access violation: 1061 ' . $driver_message,
+ '42000',
+ null,
+ array( '42000', 1061, $driver_message )
);
case WP_SQLite_Information_Schema_Exception::TYPE_KEY_COLUMN_NOT_FOUND:
+ $driver_message = sprintf( "Key column '%s' doesn't exist in table", $e->get_data()['column_name'] );
return $this->new_driver_exception(
- sprintf(
- "SQLSTATE[42000]: Syntax error or access violation: 1072 Key column '%s' doesn't exist in table",
- $e->get_data()['column_name']
- ),
- '42000'
+ 'SQLSTATE[42000]: Syntax error or access violation: 1072 ' . $driver_message,
+ '42000',
+ null,
+ array( '42000', 1072, $driver_message )
+ );
+ case WP_SQLite_Information_Schema_Exception::TYPE_INVALID_DEFAULT_VALUE:
+ $driver_message = sprintf( "Invalid default value for '%s'", $e->get_data()['column_name'] );
+ return $this->new_driver_exception(
+ 'SQLSTATE[42000]: Syntax error or access violation: 1067 ' . $driver_message,
+ '42000',
+ null,
+ array( '42000', 1067, $driver_message )
);
case WP_SQLite_Information_Schema_Exception::TYPE_CONSTRAINT_DOES_NOT_EXIST:
+ $driver_message = sprintf( "Constraint '%s' does not exist.", $e->get_data()['name'] );
return $this->new_driver_exception(
- sprintf(
- "SQLSTATE[HY000]: General error: 3940 Constraint '%s' does not exist.",
- $e->get_data()['name']
- ),
- 'HY000'
+ 'SQLSTATE[HY000]: General error: 3940 ' . $driver_message,
+ 'HY000',
+ null,
+ array( 'HY000', 3940, $driver_message )
);
case WP_SQLite_Information_Schema_Exception::TYPE_MULTIPLE_CONSTRAINTS_WITH_NAME:
+ $driver_message = sprintf(
+ "Table has multiple constraints with the name '%s'. Please use constraint specific 'DROP' clause.",
+ $e->get_data()['name']
+ );
return $this->new_driver_exception(
- sprintf(
- "SQLSTATE[HY000]: General error: 3939 Table has multiple constraints with the name '%s'. Please use constraint specific 'DROP' clause.",
- $e->get_data()['name']
- ),
- 'HY000'
+ 'SQLSTATE[HY000]: General error: 3939 ' . $driver_message,
+ 'HY000',
+ null,
+ array( 'HY000', 3939, $driver_message )
);
default:
return $e;
diff --git a/wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/sqlite/class-wp-sqlite-configurator.php b/wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/sqlite/class-wp-sqlite-configurator.php
index 280cfc283..45da0863a 100644
--- a/wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/sqlite/class-wp-sqlite-configurator.php
+++ b/wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/sqlite/class-wp-sqlite-configurator.php
@@ -9,6 +9,8 @@
* The configurator ensures that tables required for emulating MySQL behaviors
* are created and populated with necessary data. It is also able to partially
* repair and update these tables and metadata in case of database corruption.
+ *
+ * @access private
*/
class WP_SQLite_Configurator {
/**
diff --git a/wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/sqlite/class-wp-sqlite-connection.php b/wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/sqlite/class-wp-sqlite-connection.php
index 170ba01c8..78461f1b1 100644
--- a/wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/sqlite/class-wp-sqlite-connection.php
+++ b/wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/sqlite/class-wp-sqlite-connection.php
@@ -12,6 +12,8 @@
* It requires PDO with the SQLite driver, and currently, it is only a simple
* wrapper that leaks some of the PDO APIs (returns PDOStatement values, etc.).
* In the future, we may abstract it away from PDO and support SQLite3 as well.
+ *
+ * @access private
*/
class WP_SQLite_Connection {
/**
@@ -57,9 +59,9 @@ class WP_SQLite_Connection {
/**
* A query logger callback.
*
- * @var callable(string, array): void
+ * @var (callable(string, array): void)|null
*/
- private $query_logger;
+ private $query_logger = null;
/**
* Constructor.
@@ -79,6 +81,7 @@ class WP_SQLite_Connection {
* @type string|null $journal_mode Optional. SQLite journal mode. Defaults to WAL.
* @type string|int|null $synchronous Optional. SQLite synchronous setting. Defaults to
* NORMAL when the effective journal mode is WAL.
+ * @type array $pdo_options Optional. PDO constructor options.
* }
*
* @throws InvalidArgumentException When some connection options are invalid.
@@ -90,10 +93,15 @@ public function __construct( array $options ) {
$this->pdo = $options['pdo'];
} else {
if ( ! isset( $options['path'] ) || ! is_string( $options['path'] ) ) {
- throw new InvalidArgumentException( 'Option "path" is required when "connection" is not provided.' );
+ throw new InvalidArgumentException( 'Option "path" is required when "pdo" is not provided.' );
}
- $pdo_class = PHP_VERSION_ID >= 80400 ? PDO\SQLite::class : PDO::class;
- $this->pdo = new $pdo_class( 'sqlite:' . $options['path'] );
+ $pdo_class = PHP_VERSION_ID >= 80400 ? Pdo\Sqlite::class : PDO::class;
+ $pdo_options = $options['pdo_options'] ?? array();
+
+ // Internal driver operations require exceptions regardless of the
+ // caller-visible WP_MySQL_On_SQLite::ATTR_ERRMODE setting.
+ $pdo_options[ PDO::ATTR_ERRMODE ] = PDO::ERRMODE_EXCEPTION;
+ $this->pdo = new $pdo_class( 'sqlite:' . $options['path'], null, null, $pdo_options );
}
// Throw exceptions on error.
@@ -140,7 +148,7 @@ public function __construct( array $options ) {
*
* The synchronous=NORMAL setting provides the best balance between
* performance and safety for most applications running in WAL mode.
- * You lose durability across power lose with synchronous NORMAL in WAL
+ * You lose durability across power loss with synchronous NORMAL in WAL
* mode, but that is not important for most applications. Transactions
* are still atomic, consistent, and isolated, which are the most
* important characteristics in most use cases.
@@ -274,11 +282,11 @@ public function get_pdo(): PDO {
}
/**
- * Set a logger for the queries.
+ * Set or clear a logger for SQLite queries.
*
- * @param callable(string, array): void $logger A query logger callback.
+ * @param (callable(string, array): void)|null $logger A query logger callback, or null to clear it.
*/
- public function set_query_logger( callable $logger ): void {
+ public function set_query_logger( ?callable $logger ): void {
$this->query_logger = $logger;
}
}
diff --git a/wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/sqlite/class-wp-sqlite-driver-exception.php b/wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/sqlite/class-wp-sqlite-driver-exception.php
deleted file mode 100644
index 26a01ad2f..000000000
--- a/wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/sqlite/class-wp-sqlite-driver-exception.php
+++ /dev/null
@@ -1,33 +0,0 @@
-code = $code;
- $this->driver = $driver;
- }
-
- public function getDriver(): WP_MySQL_On_SQLite {
- return $this->driver;
- }
-}
diff --git a/wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/sqlite/class-wp-sqlite-driver.php b/wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/sqlite/class-wp-sqlite-driver.php
index d7010ecf3..73de8703e 100644
--- a/wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/sqlite/class-wp-sqlite-driver.php
+++ b/wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/sqlite/class-wp-sqlite-driver.php
@@ -3,24 +3,29 @@
/*
* The SQLite driver uses PDO. Enable PDO function calls:
* phpcs:disable WordPress.DB.RestrictedClasses.mysql__PDO
+ *
+ * PDO uses camel case naming, enable non-snake case:
+ * phpcs:disable WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid
*/
/**
- * For back compatibility with dependencies that use their own loader scripts
+ * For backward compatibility with dependencies that use their own loader scripts
* (e.g., WP CLI SQLite Command), ensure the PDO-based classes are loaded.
*/
require_once __DIR__ . '/class-wp-mysql-on-sqlite.php';
-require_once __DIR__ . '/class-wp-pdo-proxy-statement.php';
+require_once __DIR__ . '/class-wp-mysql-on-sqlite-statement.php';
/**
* Deprecated: A proxy of the WP_MySQL_On_SQLite class preserving the legacy API.
*
* This class temporarily preserves the legacy constructor and result API while
* consumers transition to the PDO-based WP_MySQL_On_SQLite API.
+ *
+ * @deprecated 3.0.0 Use WP_MySQL_On_SQLite instead.
*/
class WP_SQLite_Driver {
/**
- * The SQLite engine version.
+ * The emulated MySQL client library version.
*
* This is a mysqli-like property that is needed to avoid a PHP warning in
* the WordPress health info. The "WP_Debug_Data::get_wp_database()" method
@@ -42,6 +47,13 @@ class WP_SQLite_Driver {
*/
private $mysql_on_sqlite_driver;
+ /**
+ * Statement returned for the last emulated query.
+ *
+ * @var WP_MySQL_On_SQLite_Statement|null
+ */
+ private $last_statement;
+
/**
* Results of the last emulated query.
*
@@ -54,30 +66,30 @@ class WP_SQLite_Driver {
*
* Set up an SQLite connection and the MySQL-on-SQLite driver.
*
- * @param WP_SQLite_Connection $connection A SQLite database connection.
- * @param string $database The database name.
+ * @param WP_SQLite_Connection $connection A SQLite database connection.
+ * @param string $database The database name.
+ * @param int $mysql_version The emulated MySQL version as an integer.
*
- * @throws WP_SQLite_Driver_Exception When the driver initialization fails.
+ * @throws WP_MySQL_On_SQLite_Exception When the driver initialization fails.
*/
public function __construct(
WP_SQLite_Connection $connection,
string $database,
- int $mysql_version = 80038
+ int $mysql_version = WP_MySQL_On_SQLite::DEFAULT_MYSQL_VERSION
) {
$this->mysql_on_sqlite_driver = new WP_MySQL_On_SQLite(
- sprintf( 'mysql-on-sqlite:dbname=%s', $database ),
+ sprintf( 'mysql-on-sqlite:dbname=%s', str_replace( ';', ';;', $database ) ),
null,
null,
array(
- 'mysql_version' => $mysql_version,
- 'pdo' => $connection->get_pdo(),
- 'journal_mode' => $connection->query( 'PRAGMA journal_mode' )->fetchColumn(),
+ 'mysql_version' => $mysql_version,
+ 'sqlite_pdo' => $connection->get_pdo(),
+ 'sqlite_journal_mode' => $connection->query( 'PRAGMA journal_mode' )->fetchColumn(),
)
);
- $this->main_db_name = $database;
$this->client_info = $this->mysql_on_sqlite_driver->client_info;
- $connection->get_pdo()->setAttribute( PDO::ATTR_STRINGIFY_FETCHES, true );
+ $this->mysql_on_sqlite_driver->setAttribute( PDO::ATTR_STRINGIFY_FETCHES, true );
}
/**
@@ -145,7 +157,11 @@ public function get_last_sqlite_queries(): array {
* @return int|string
*/
public function get_insert_id() {
- return $this->mysql_on_sqlite_driver->get_insert_id();
+ $last_insert_id = $this->mysql_on_sqlite_driver->lastInsertId();
+ if ( is_numeric( $last_insert_id ) ) {
+ $last_insert_id = (int) $last_insert_id;
+ }
+ return $last_insert_id;
}
/**
@@ -155,10 +171,12 @@ public function get_insert_id() {
*
* @return mixed Return value, depending on the query type.
*
- * @throws WP_SQLite_Driver_Exception When the query execution fails.
+ * @throws WP_MySQL_On_SQLite_Exception When the query execution fails.
*/
public function query( string $query, $fetch_mode = PDO::FETCH_OBJ, ...$fetch_mode_args ) {
- $stmt = $this->mysql_on_sqlite_driver->query( $query, $fetch_mode, ...$fetch_mode_args );
+ $this->last_statement = null;
+ $stmt = $this->mysql_on_sqlite_driver->query( $query, $fetch_mode, ...$fetch_mode_args );
+ $this->last_statement = $stmt;
if ( $stmt->columnCount() > 0 ) {
$this->last_result = $stmt->fetchAll( $fetch_mode );
@@ -202,7 +220,7 @@ public function get_last_return_value() {
* @return int
*/
public function get_last_column_count(): int {
- return $this->mysql_on_sqlite_driver->get_last_column_count();
+ return null === $this->last_statement ? 0 : $this->last_statement->columnCount();
}
/**
@@ -211,7 +229,16 @@ public function get_last_column_count(): int {
* @return array
*/
public function get_last_column_meta(): array {
- return $this->mysql_on_sqlite_driver->get_last_column_meta();
+ if ( null === $this->last_statement ) {
+ return array();
+ }
+
+ $column_meta = array();
+ $column_count = $this->last_statement->columnCount();
+ for ( $i = 0; $i < $column_count; $i++ ) {
+ $column_meta[] = $this->last_statement->getColumnMeta( $i );
+ }
+ return $column_meta;
}
/**
@@ -229,12 +256,12 @@ public function execute_sqlite_query( string $sql, array $params = array() ): PD
/**
* Begin a new transaction or nested transaction.
*/
- public function beginTransaction(): void { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid
+ public function beginTransaction(): void {
$this->mysql_on_sqlite_driver->beginTransaction();
}
/**
- * A temporary alias for back compatibility.
+ * A temporary alias for backward compatibility.
*
* @see self::beginTransaction()
*/
@@ -255,26 +282,4 @@ public function commit(): void {
public function rollback(): void {
$this->mysql_on_sqlite_driver->rollback();
}
-
- /**
- * Proxy also the private property "$main_db_name", as it is used in tests.
- */
- public function __set( string $name, $value ): void {
- if ( 'main_db_name' === $name ) {
- $closure = function ( string $value ) {
- $this->main_db_name = $value;
- };
- $closure->call( $this->mysql_on_sqlite_driver, $value );
- }
- }
-
- /**
- * Proxy also this private method, as it is used in tests.
- */
- private function quote_mysql_utf8_string_literal( string $utf8_literal ): string {
- $closure = function ( string $utf8_literal ) {
- return $this->quote_mysql_utf8_string_literal( $utf8_literal );
- };
- return $closure->call( $this->mysql_on_sqlite_driver, $utf8_literal );
- }
}
diff --git a/wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/sqlite/class-wp-sqlite-information-schema-builder.php b/wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/sqlite/class-wp-sqlite-information-schema-builder.php
index 7c28316df..3aa4069c7 100644
--- a/wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/sqlite/class-wp-sqlite-information-schema-builder.php
+++ b/wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/sqlite/class-wp-sqlite-information-schema-builder.php
@@ -6,6 +6,8 @@
* This class builds and maintains MySQL INFORMATION_SCHEMA tables in SQLite.
* It consumes the AST of MySQL DDL queries and records the schema information
* in SQLite tables that emulate the MySQL INFORMATION_SCHEMA.
+ *
+ * @access private
*/
class WP_SQLite_Information_Schema_Builder {
/**
@@ -1505,7 +1507,7 @@ private function record_drop_check_constraint(
private function extract_column_data( string $table_name, string $column_name, WP_Parser_Node $node, int $position ): array {
list ( $data_type, $column_type ) = $this->get_column_data_types( $node );
- $default = $this->get_column_default( $node, $data_type );
+ $default = $this->get_column_default( $node, $data_type, $column_name );
$nullable = $this->get_column_nullable( $node );
$key = $this->get_column_key( $node );
$extra = $this->get_column_extra( $node );
@@ -2041,11 +2043,12 @@ private function get_table_comment( WP_Parser_Node $node ): string {
/**
* Extract column default value from the "columnDefinition" or "fieldDefinition" AST node.
*
- * @param WP_Parser_Node $node The "columnDefinition" or "fieldDefinition" AST node.
- * @param string $data_type The column data type as stored in information schema.
- * @return string|null The column default as stored in information schema.
+ * @param WP_Parser_Node $node The "columnDefinition" or "fieldDefinition" AST node.
+ * @param string $data_type The column data type as stored in information schema.
+ * @param string $column_name The column name.
+ * @return string|null The column default as stored in information schema.
*/
- private function get_column_default( WP_Parser_Node $node, string $data_type ): ?string {
+ private function get_column_default( WP_Parser_Node $node, string $data_type, string $column_name ): ?string {
$default_attr = null;
foreach ( $node->get_descendant_nodes( 'columnAttribute' ) as $attr ) {
if ( $attr->has_child_token( WP_MySQL_Lexer::DEFAULT_SYMBOL ) ) {
@@ -2079,7 +2082,7 @@ private function get_column_default( WP_Parser_Node $node, string $data_type ):
// A signed number, such as "-5", has no "literal" child node.
return $this->get_value( $signed_literal );
}
- return $this->get_literal_default( $literal, $data_type );
+ return $this->get_literal_default( $literal, $data_type, $column_name );
}
// DEFAULT (expression) - MySQL 8.0.13+ supports exprWithParentheses
@@ -2094,11 +2097,12 @@ private function get_column_default( WP_Parser_Node $node, string $data_type ):
/**
* Extract and normalize a literal default value.
*
- * @param WP_Parser_Node $literal The "literal" AST node.
- * @param string $data_type The column data type as stored in information schema.
- * @return string|null The default value as stored in information schema.
+ * @param WP_Parser_Node $literal The "literal" AST node.
+ * @param string $data_type The column data type as stored in information schema.
+ * @param string $column_name The column name.
+ * @return string|null The default value as stored in information schema.
*/
- private function get_literal_default( WP_Parser_Node $literal, string $data_type ): ?string {
+ private function get_literal_default( WP_Parser_Node $literal, string $data_type, string $column_name ): ?string {
// DEFAULT NULL
if ( $literal->has_child_node( 'nullLiteral' ) ) {
return null;
@@ -2114,7 +2118,15 @@ private function get_literal_default( WP_Parser_Node $literal, string $data_type
$default = $this->get_value( $literal );
if ( 'bit' === $data_type ) {
- return $this->get_bit_default( $default ) ?? $default;
+ /*
+ * @TODO: Validate and normalize defaults from their AST and the full
+ * column definition before storing them in the information schema.
+ */
+ $bit_default = $this->get_bit_default( $default );
+ if ( null === $bit_default ) {
+ throw WP_SQLite_Information_Schema_Exception::invalid_default_value( $column_name );
+ }
+ return $bit_default;
}
/*
@@ -2140,15 +2152,26 @@ private function get_literal_default( WP_Parser_Node $literal, string $data_type
private function get_bit_default( string $default_value ): ?string {
$value = strtolower( $default_value );
+ // An empty string coerces to zero.
+ if ( '' === $value ) {
+ return "b'0'";
+ }
+
// Bit literal, e.g. b'101' or 0b101.
- if ( str_starts_with( $value, "b'" ) || str_starts_with( $value, '0b' ) ) {
- $bits = ltrim( rtrim( substr( $value, 2 ), "'" ), '0' );
+ if (
+ preg_match( "/\Ab'([01]*)'\z/", $value, $matches )
+ || preg_match( '/\A0b([01]+)\z/', $value, $matches )
+ ) {
+ $bits = ltrim( $matches[1], '0' );
return "b'" . ( '' === $bits ? '0' : $bits ) . "'";
}
// Hex literal, e.g. x'05' or 0x05.
- if ( str_starts_with( $value, "x'" ) || str_starts_with( $value, '0x' ) ) {
- return "b'" . decbin( hexdec( rtrim( substr( $value, 2 ), "'" ) ) ) . "'";
+ if (
+ preg_match( "/\Ax'([0-9a-f]*)'\z/", $value, $matches )
+ || preg_match( '/\A0x([0-9a-f]+)\z/', $value, $matches )
+ ) {
+ return "b'" . decbin( hexdec( $matches[1] ) ) . "'";
}
// Decimal, e.g. 5, or a numeric string literal such as '0'.
diff --git a/wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/sqlite/class-wp-sqlite-information-schema-exception.php b/wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/sqlite/class-wp-sqlite-information-schema-exception.php
index 93a9ab88c..c88b50bda 100644
--- a/wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/sqlite/class-wp-sqlite-information-schema-exception.php
+++ b/wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/sqlite/class-wp-sqlite-information-schema-exception.php
@@ -5,6 +5,8 @@
*
* This class is used to represent errors that may occur when building
* the MySQL information schema for emulation in SQLite.
+ *
+ * @access private
*/
class WP_SQLite_Information_Schema_Exception extends Exception {
// Information schema exception types.
@@ -14,6 +16,7 @@ class WP_SQLite_Information_Schema_Exception extends Exception {
const TYPE_KEY_COLUMN_NOT_FOUND = 'key-column-not-found';
const TYPE_CONSTRAINT_DOES_NOT_EXIST = 'constraint-does-not-exist';
const TYPE_MULTIPLE_CONSTRAINTS_WITH_NAME = 'multiple-constraints-with-name';
+ const TYPE_INVALID_DEFAULT_VALUE = 'invalid-default-value';
/**
* The exception type.
@@ -149,4 +152,18 @@ public static function multiple_constraints_with_name( string $name ): WP_SQLite
array( 'name' => $name )
);
}
+
+ /**
+ * Create an invalid default value exception.
+ *
+ * @param string $column_name The name of the affected column.
+ * @return self The exception instance.
+ */
+ public static function invalid_default_value( string $column_name ): WP_SQLite_Information_Schema_Exception {
+ return new self(
+ self::TYPE_INVALID_DEFAULT_VALUE,
+ sprintf( "Invalid default value for '%s'.", $column_name ),
+ array( 'column_name' => $column_name )
+ );
+ }
}
diff --git a/wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/sqlite/class-wp-sqlite-information-schema-reconstructor.php b/wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/sqlite/class-wp-sqlite-information-schema-reconstructor.php
index 3b60b1fc1..759e636ad 100644
--- a/wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/sqlite/class-wp-sqlite-information-schema-reconstructor.php
+++ b/wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/sqlite/class-wp-sqlite-information-schema-reconstructor.php
@@ -6,14 +6,16 @@
*/
/**
- * SQLite information schema recconstructor for MySQL.
+ * SQLite information schema reconstructor for MySQL.
*
* This class checks and reconstructs the MySQL INFORMATION_SCHEMA data in SQLite
* when it becomes out of sync with the actual SQLite database schema.
*
- * Currently, it reconstructs schema infromation for missing tables, and removes
+ * Currently, it reconstructs schema information for missing tables, and removes
* stale data for tables that no longer exist. When used with WordPress, it uses
* the "wp_get_db_schema()" function to reconstruct WordPress table information.
+ *
+ * @access private
*/
class WP_SQLite_Information_Schema_Reconstructor {
/**
@@ -76,7 +78,7 @@ public function ensure_correct_information_schema(): void {
$sql = $this->generate_create_table_statement( $table );
$ast = $this->driver->create_parser( $sql )->parse();
if ( null === $ast ) {
- throw new WP_SQLite_Driver_Exception( $this->driver, 'Failed to parse the MySQL query.' );
+ throw new WP_MySQL_On_SQLite_Exception( $this->driver, 'Failed to parse the MySQL query.' );
}
}
@@ -111,7 +113,7 @@ private function record_drop_table( string $table_name ): void {
$sql = sprintf( 'DROP TABLE %s', $this->connection->quote_identifier( $table_name ) ); // TODO: mysql quote
$ast = $this->driver->create_parser( $sql )->parse();
if ( null === $ast ) {
- throw new WP_SQLite_Driver_Exception( $this->driver, 'Failed to parse the MySQL query.' );
+ throw new WP_MySQL_On_SQLite_Exception( $this->driver, 'Failed to parse the MySQL query.' );
}
$this->schema_builder->record_drop_table(
$ast->get_first_descendant_node( 'dropStatement' )
@@ -233,7 +235,7 @@ private function get_wp_create_table_statements(): array {
while ( $parser->next_query() ) {
$ast = $parser->get_query_ast();
if ( null === $ast ) {
- throw new WP_SQLite_Driver_Exception( $this->driver, 'Failed to parse the MySQL query.' );
+ throw new WP_MySQL_On_SQLite_Exception( $this->driver, 'Failed to parse the MySQL query.' );
}
$create_node = $ast->get_first_descendant_node( 'createStatement' );
@@ -572,9 +574,11 @@ private function generate_column_default( string $mysql_type, ?string $default_v
}
// HEX literals (numeric). E.g.: 0x1a2f, 0X1A2F
- $value = filter_var( $no_underscore_default_value, FILTER_VALIDATE_INT, FILTER_FLAG_ALLOW_HEX );
- if ( false !== $value ) {
- return $value;
+ if ( 1 === preg_match( '/^0[xX][0-9a-fA-F]+$/D', $no_underscore_default_value ) ) {
+ // Convert to signed 64-bit decimal text in SQLite to avoid PHP integer size limits.
+ return (string) $this->connection->query(
+ 'SELECT CAST(' . $no_underscore_default_value . ' AS TEXT)'
+ )->fetchColumn();
}
// BLOB literals (string). E.g.: x'1a2f', X'1A2F'
@@ -766,7 +770,7 @@ private function get_mysql_column_type( string $column_type ): string {
* See WP_MySQL_On_SQLite::quote_mysql_utf8_string_literal().
*
* TODO: This is a copy of WP_MySQL_On_SQLite::quote_mysql_utf8_string_literal().
- * We may consider extracing it to reusable MySQL helpers.
+ * We may consider extracting it to reusable MySQL helpers.
*
* @param string $utf8_literal The UTF-8 string literal to escape.
* @return string The escaped string literal.
diff --git a/wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/sqlite/class-wp-sqlite-pdo-user-defined-functions.php b/wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/sqlite/class-wp-sqlite-pdo-user-defined-functions.php
index 9e44bf17d..7b6f76772 100644
--- a/wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/sqlite/class-wp-sqlite-pdo-user-defined-functions.php
+++ b/wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/sqlite/class-wp-sqlite-pdo-user-defined-functions.php
@@ -1,37 +1,28 @@
- * new WP_SQLite_PDO_User_Defined_Functions(ref_to_pdo_obj);
- *
- *
- * This automatically enables ref_to_pdo_obj to replace the function in the SQL statement
- * to the ones defined here.
+ * @access private
*/
class WP_SQLite_PDO_User_Defined_Functions {
/**
- * Registers the user defined functions for SQLite to a PDO instance.
+ * Register the user-defined SQLite functions on a PDO connection.
+ *
* The functions are registered using PDO::sqliteCreateFunction().
*
- * @param PDO|PDO\SQLite $pdo The PDO object.
+ * @param PDO|Pdo\Sqlite $pdo The PDO object.
*/
public static function register_for( $pdo ): self {
$instance = new self();
foreach ( $instance->functions as $f => $t ) {
- if ( $pdo instanceof PDO\SQLite ) {
+ if ( $pdo instanceof Pdo\Sqlite ) {
$pdo->createFunction( $f, array( $instance, $t ) );
} else {
$pdo->sqliteCreateFunction( $f, array( $instance, $t ) );
@@ -842,7 +833,7 @@ public function inet_ntoa( $num ) {
* @return int long integer
*/
public function inet_aton( $addr ) {
- return absint( ip2long( $addr ) );
+ return abs( (int) ip2long( $addr ) );
}
/**
@@ -953,7 +944,7 @@ public function reverse( $str ) {
}
/**
- * A helper to covert LIKE pattern to a GLOB pattern for "LIKE BINARY" support.
+ * A helper to convert a LIKE pattern to a GLOB pattern for "LIKE BINARY" support.
* @TODO: Some of the MySQL string specifics described below are likely to
* affect also other patterns than just "LIKE BINARY". We should
diff --git a/wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/sqlite/trait-wp-mysql-on-sqlite-pdo-compat-php-84.php b/wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/sqlite/trait-wp-mysql-on-sqlite-pdo-compat-php-84.php
new file mode 100644
index 000000000..9a4bc1f15
--- /dev/null
+++ b/wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/sqlite/trait-wp-mysql-on-sqlite-pdo-compat-php-84.php
@@ -0,0 +1,31 @@
+resetDatabases();
- }
-
- private function resetDatabases() {
- if ( file_exists( FQDB ) ) {
- unlink( FQDB );
- }
- $GLOBALS['mysql']->query( 'DROP DATABASE IF EXISTS ' . DB_NAME );
- $GLOBALS['mysql']->query( 'CREATE DATABASE ' . DB_NAME );
- $GLOBALS['mysql']->query( 'USE ' . DB_NAME );
- }
-
- public function query( $query ) {
- /**
- * In MySQL, AUTO_INCREMENT columns don't reuse IDs assigned in rollback transactions
- * In SQLite, AUTOINCREMENT columns do reuse IDs assigned in rollback transactions
- *
- * Let's store the current AUTOINCREMENT value for each table, and restore it afterwards.
- */
- if ( preg_match( '/^\s*rollback/i', $query ) ) {
- $autoincrements = array();
- $tables = $GLOBALS['@pdo']->query( "SELECT name as `table` FROM sqlite_master WHERE type='table' ORDER BY name" )->fetchAll();
- foreach ( $tables as $table ) {
- $table = $table['table'];
- $autoincrement = $GLOBALS['@pdo']->query( "SELECT seq FROM sqlite_sequence WHERE name = '$table'" )->fetchColumn();
- $autoincrements[ $table ] = $autoincrement ?: 1;
- }
- }
- $sqlite_retval = parent::query( $query );
- if ( preg_match( '/^\s*rollback/i', $query ) ) {
- foreach ( $autoincrements as $table => $autoincrement ) {
- $GLOBALS['@pdo']->query( "UPDATE sqlite_sequence SET seq = $autoincrement WHERE name = '$table'" );
- }
- }
- $this->crosscheck( $query, $sqlite_retval );
- return $sqlite_retval;
- }
-
- private function crosscheck( $query, $sqlite_retval ) {
- // echo $query."\n\n";
- // Be lenient on cross-checking some query types
- if ( preg_match( '/^\s*SET storage_engine/i', $query ) ) {
- return;
- }
- $this->show_errors = false;
- $this->suppress_errors = true;
- $GLOBALS['mysql']->show_errors = false;
- $GLOBALS['mysql']->suppress_errors = true;
-
- ob_start();
- $mysql_retval = $GLOBALS['mysql']->query( $query );
- ob_end_clean();
-
- $tests = array(
- array( 'retval', $mysql_retval, $sqlite_retval ),
- array( 'num_rows', $GLOBALS['mysql']->num_rows, $GLOBALS['sqlite']->num_rows ),
- array( 'insert_id', $GLOBALS['mysql']->insert_id, $GLOBALS['sqlite']->insert_id ),
- array( 'rows_affected', $GLOBALS['mysql']->rows_affected, $GLOBALS['sqlite']->rows_affected ),
- );
-
- foreach ( $tests as $test ) {
- list($factor, $mysql, $sqlite) = $test;
- if ( $mysql !== $sqlite ) {
- if ( 'insert_id' === $factor ) {
- // On multi-inserts MySQL returns the first inserted ID
- // while SQLite returns the last one. The cached insert_id
- // value stays the same for a number of subsequent queries.
- // Let's forgive this for now.
- continue;
- }
- if ( 'rows_affected' === $factor && $mysql_retval === $mysql ) {
- // SQLite doesn't provide the rowcount() functionality
- continue;
- }
- if ( 'retval' === $factor && $GLOBALS['mysql']->rows_affected === $mysql ) {
- // SQLite doesn't provide the rowcount() functionality
- continue;
- }
- echo "======================================================\n";
- echo "======== *** $factor *** differed for query ========= \n";
- echo "======================================================\n";
- echo "MySQL query: \n";
- echo "$query\n\n";
-
- echo "SQLite queries: \n";
- foreach ( $this->dbh->last_translation->queries as $query ) {
- echo $query->sql . "\n";
- }
- echo "\n";
-
- $this->report_factor(
- 'error',
- $GLOBALS['mysql']->last_error,
- $GLOBALS['sqlite']->last_error
- );
- foreach ( $tests as $test ) {
- $this->report_factor(
- $test[0],
- $test[1],
- $test[2]
- );
- }
- // throw new Exception();
- break;
- }
- }
- }
-
- private function report_factor( $factor, $mysql, $sqlite ) {
- echo "$factor: \n";
- echo ' MySQL: ' . var_export( $mysql, true ) . "\n";
- echo ' SQLite: ' . var_export( $sqlite, true ) . "\n\n";
- }
-}
diff --git a/wp/wp-content/plugins/sqlite-database-integration/wp-includes/sqlite/class-wp-sqlite-db.php b/wp/wp-content/plugins/sqlite-database-integration/wp-includes/sqlite/class-wp-sqlite-db.php
index b94c66d20..a50fbc77b 100644
--- a/wp/wp-content/plugins/sqlite-database-integration/wp-includes/sqlite/class-wp-sqlite-db.php
+++ b/wp/wp-content/plugins/sqlite-database-integration/wp-includes/sqlite/class-wp-sqlite-db.php
@@ -1,9 +1,6 @@
charset = 'utf8mb4';
}
+ /**
+ * Returns the active MySQL-on-SQLite driver.
+ *
+ * @return WP_MySQL_On_SQLite The active driver.
+ * @throws RuntimeException When there is no active database connection.
+ */
+ public function get_driver(): WP_MySQL_On_SQLite {
+ if ( ! $this->dbh ) {
+ throw new RuntimeException( 'Cannot access the driver without an active database connection.' );
+ }
+
+ return $this->dbh;
+ }
+
/**
* Method to set character set for the database.
*
@@ -71,17 +82,59 @@ public function set_charset( $dbh, $charset = null, $collate = null ) {
}
/**
- * Method to get the character set for the database.
- * Hardcoded to utf8mb4 for now.
+ * Retrieves the character set for the given column.
+ *
+ * This overrides wpdb::get_col_charset() to enable the parent's implementation
+ * for SQLite by temporarily setting the is_mysql flag.
*
- * @param string $table The table name.
- * @param string $column The column name.
+ * @see wpdb::get_col_charset()
*
- * @return string The character set.
+ * @param string $table Table name.
+ * @param string $column Column name.
+ * @return string|false|WP_Error Column character set as a string. False if the column has
+ * no character set. WP_Error object on failure.
*/
public function get_col_charset( $table, $column ) {
- // Hardcoded for now.
- return 'utf8mb4';
+ $original_is_mysql = $this->is_mysql ?? null;
+
+ /*
+ * The parent method returns early when `$this->is_mysql` is falsy.
+ * Since SQLite doesn't set this flag, we enable it temporarily so
+ * the parent can run its full logic — querying column metadata via
+ * SHOW FULL COLUMNS (which the SQLite driver translates) and
+ * populating the `$this->col_meta` cache.
+ */
+ try {
+ $this->is_mysql = true;
+ return parent::get_col_charset( $table, $column );
+ } finally {
+ $this->is_mysql = $original_is_mysql;
+ }
+ }
+
+ /**
+ * Retrieves the maximum string length allowed in a given column.
+ *
+ * This overrides wpdb::get_col_length() to enable the parent's implementation
+ * for SQLite by temporarily setting the is_mysql flag.
+ *
+ * @see wpdb::get_col_length()
+ *
+ * @param string $table Table name.
+ * @param string $column Column name.
+ * @return array|false|WP_Error Column length information, false if the column has
+ * no length. WP_Error object on failure.
+ */
+ public function get_col_length( $table, $column ) {
+ $original_is_mysql = $this->is_mysql ?? null;
+
+ // See get_col_charset() for an explanation of the is_mysql flag.
+ try {
+ $this->is_mysql = true;
+ return parent::get_col_length( $table, $column );
+ } finally {
+ $this->is_mysql = $original_is_mysql;
+ }
}
/**
@@ -130,14 +183,98 @@ public function set_sql_mode( $modes = array() ) {
/**
* Closes the current database connection.
- * Noop in SQLite.
*
- * @return bool True to indicate the connection was successfully closed.
+ * This overrides wpdb::close() while closely mirroring its implementation.
+ *
+ * @see wpdb::close()
+ *
+ * @return bool True if the connection was successfully closed,
+ * false if it wasn't, or if the connection doesn't exist.
*/
public function close() {
+ if ( ! $this->dbh ) {
+ return false;
+ }
+
+ $connection = $this->dbh->get_connection();
+ $pdo = $connection->get_pdo();
+
+ try {
+ if ( $this->dbh->inTransaction() ) {
+ $this->dbh->rollBack();
+ } elseif ( $pdo->inTransaction() ) {
+ $pdo->rollBack();
+ } else {
+ /*
+ * On PHP < 8.4, PDO cannot detect transactions started via SQL.
+ * A savepoint ensures ROLLBACK succeeds with or without one.
+ */
+ $pdo->exec( 'SAVEPOINT wp_sqlite_db_close' );
+ $pdo->exec( 'ROLLBACK' );
+ }
+ } catch ( Throwable $e ) {
+ return false;
+ }
+
+ if (
+ isset( $GLOBALS['@pdo'] )
+ && $GLOBALS['@pdo'] === $pdo
+ ) {
+ unset( $GLOBALS['@pdo'] );
+ }
+
+ $connection->set_query_logger( null );
+ $this->result = null;
+ $this->dbh = null;
+ $this->ready = false;
+ $this->has_connected = false;
+
return true;
}
+ /**
+ * Determines the best charset and collation to use given a charset and collation.
+ *
+ * For example, when able, utf8mb4 should be used instead of utf8.
+ *
+ * This overrides wpdb::determine_charset() while closely mirroring its implementation.
+ * The override is needed because the parent checks for a mysqli connection object.
+ *
+ * @param string $charset The character set to check.
+ * @param string $collate The collation to check.
+ * @return array {
+ * The most appropriate character set and collation to use.
+ *
+ * @type string $charset Character set.
+ * @type string $collate Collation.
+ * }
+ */
+ public function determine_charset( $charset, $collate ) {
+ if ( ! $this->dbh ) {
+ return compact( 'charset', 'collate' );
+ }
+
+ if ( 'utf8' === $charset ) {
+ $charset = 'utf8mb4';
+ }
+
+ if ( 'utf8mb4' === $charset ) {
+ // _general_ is outdated, so we can upgrade it to _unicode_, instead.
+ if ( ! $collate || 'utf8_general_ci' === $collate ) {
+ $collate = 'utf8mb4_unicode_ci';
+ } else {
+ $collate = str_replace( 'utf8_', 'utf8mb4_', $collate );
+ }
+ }
+
+ // _unicode_520_ is a better collation, we should use that when it's available.
+ if ( $this->has_cap( 'utf8mb4_520' ) && 'utf8mb4_unicode_ci' === $collate ) {
+ $collate = 'utf8mb4_unicode_520_ci';
+ }
+
+ return compact( 'charset', 'collate' );
+ }
+
/**
* Method to select the database connection.
*
@@ -162,12 +299,20 @@ public function select( $db, $dbh = null ) {
* @param string $data The string to escape.
*
* @return string escaped
+ * @throws RuntimeException When the database connection is not initialized.
*/
public function _real_escape( $data ) {
if ( ! is_scalar( $data ) ) {
return '';
}
- $escaped = addslashes( $data );
+
+ if ( ! $this->dbh ) {
+ throw new RuntimeException( 'Cannot escape data without an active database connection.' );
+ }
+
+ // Escape the string without bounding quotes to mirror mysqli_real_escape_string().
+ $quoted = $this->dbh->quote( (string) $data );
+ $escaped = substr( $quoted, 1, -1 );
return $this->add_placeholder_escape( $escaped );
}
@@ -268,17 +413,23 @@ public function flush() {
* @see wpdb::db_connect()
*
* @param bool $allow_bail Not used.
- * @return void
+ * @return bool True on a successful connection, false on failure.
*/
public function db_connect( $allow_bail = true ) {
if ( $this->dbh ) {
- return;
+ return $this->ready;
}
- $this->init_charset();
- $pdo = null;
+ $this->last_error = '';
if ( isset( $GLOBALS['@pdo'] ) ) {
- $pdo = $GLOBALS['@pdo'];
+ trigger_error(
+ 'PDO injection via $GLOBALS[\'@pdo\'] is no longer supported. The existing PDO will be ignored and a new connection will be created.',
+ E_USER_WARNING
+ );
+ }
+
+ if ( ! isset( $this->charset ) ) {
+ $this->init_charset();
}
// Migrate the database file from a legacy path, if it exists.
@@ -312,12 +463,9 @@ public function db_connect( $allow_bail = true ) {
try {
$options = array(
- 'journal_mode' => defined( 'SQLITE_JOURNAL_MODE' ) ? SQLITE_JOURNAL_MODE : null,
+ 'sqlite_journal_mode' => defined( 'SQLITE_JOURNAL_MODE' ) ? SQLITE_JOURNAL_MODE : null,
);
- if ( null !== $pdo ) {
- $options['pdo'] = $pdo;
- }
- $this->dbh = new WP_MySQL_On_SQLite(
+ $dbh = new WP_MySQL_On_SQLite(
sprintf(
'mysql-on-sqlite:path=%s;dbname=%s',
str_replace( ';', ';;', FQDB ),
@@ -327,27 +475,44 @@ public function db_connect( $allow_bail = true ) {
null,
$options
);
- $this->dbh->setAttribute( PDO::ATTR_STRINGIFY_FETCHES, true ); // phpcs:ignore WordPress.DB.RestrictedClasses.mysql__PDO
- $GLOBALS['@pdo'] = $this->dbh->get_connection()->get_pdo();
+ $dbh->setAttribute( PDO::ATTR_STRINGIFY_FETCHES, true ); // phpcs:ignore WordPress.DB.RestrictedClasses.mysql__PDO
+ $this->dbh = $dbh;
+
+ /**
+ * Exposes the underlying PDO SQLite connection for backward compatibility.
+ *
+ * @deprecated 3.0.0 Use WP_SQLite_DB::get_driver() with
+ * WP_MySQL_On_SQLite::get_sqlite_pdo() instead.
+ */
+ $GLOBALS['@pdo'] = $dbh->get_sqlite_pdo();
} catch ( Throwable $e ) {
$this->last_error = $this->format_error_message( $e );
}
if ( $this->last_error ) {
return false;
}
+
+ $this->has_connected = true;
+ $this->set_charset( $this->dbh );
+
$this->ready = true;
$this->set_sql_mode();
+ return true;
}
/**
- * Method to dummy out wpdb::check_connection()
+ * Checks that the database connection is available.
*
* @param bool $allow_bail Not used.
*
- * @return bool
+ * @return bool True when the connection is available, false otherwise.
*/
public function check_connection( $allow_bail = true ) {
- return true;
+ if ( $this->dbh ) {
+ return true;
+ }
+
+ return $this->db_connect( $allow_bail );
}
/**
@@ -420,14 +585,13 @@ public function query( $query ) {
$last_query_count = count( $this->queries ?? array() );
/*
- * @TODO: WPDB uses "$this->check_current_query" to check table/column
- * charset and strip all invalid characters from the query.
- * This is an involved process that we can bypass for SQLite,
- * if we simply strip all invalid UTF-8 characters from the query.
+ * @TODO: wpdb uses "$this->check_current_query" and table metadata to
+ * reject queries containing invalid text. Implement equivalent handling
+ * for SQLite without relying on the MySQL-specific conversion pipeline.
*
- * To do so, mb_convert_encoding can be used with an optional
- * fallback to a htmlspecialchars method. E.g.:
- * https://github.com/nette/utils/blob/be534713c227aeef57ce1883fc17bc9f9e29eca2/src/Utils/Strings.php#L42
+ * PCRE's "u" modifier can validate UTF-8 without constructing a converted
+ * query copy: 1 === preg_match( '//u', $query ). The implementation must
+ * preserve wpdb's exemptions for prevalidated and binary data.
*/
$this->_do_query( $query );
@@ -448,7 +612,7 @@ public function query( $query ) {
// Take note of the insert_id.
if ( preg_match( '/^\s*(insert|replace)\s/i', $query ) ) {
- $this->insert_id = $this->dbh->get_insert_id();
+ $this->insert_id = (int) $this->dbh->lastInsertId();
}
// Return number of rows affected.
@@ -539,7 +703,11 @@ protected function load_col_info() {
return;
}
$this->col_info = array();
- foreach ( $this->dbh->get_last_column_meta() as $column ) {
+ if ( null === $this->result ) {
+ return;
+ }
+ for ( $i = 0; $i < $this->result->columnCount(); $i++ ) {
+ $column = $this->result->getColumnMeta( $i );
$this->col_info[] = (object) array(
'name' => $column['name'],
'orgname' => $column['mysqli:orgname'],
@@ -559,43 +727,52 @@ protected function load_col_info() {
}
/**
- * Method to return what the database can do.
+ * Determines whether the database supports a given feature.
*
- * This overrides wpdb::has_cap() to avoid using MySQL functions.
- * SQLite supports subqueries, but not support collation, group_concat and set_charset.
+ * The utf8mb4 check is handled here because older WordPress versions inspect
+ * the MySQL client library. All other capabilities use the parent logic.
*
* @see wpdb::has_cap()
*
- * @param string $db_cap The feature to check for. Accepts 'collation',
- * 'group_concat', 'subqueries', 'set_charset',
- * 'utf8mb4', or 'utf8mb4_520'.
- *
- * @return bool Whether the database feature is supported, false otherwise.
+ * @param string $db_cap The feature to check for.
+ * @return bool True when the database feature is supported, false otherwise.
*/
public function has_cap( $db_cap ) {
- return 'subqueries' === strtolower( $db_cap );
+ if ( 'utf8mb4' === strtolower( $db_cap ) ) {
+ return true;
+ }
+
+ return parent::has_cap( $db_cap );
}
/**
- * Method to return database version number.
+ * Retrieves the emulated database server version number.
*
- * This overrides wpdb::db_version() to avoid using MySQL function.
- * It returns mysql version number, but it means nothing for SQLite.
- * So it return the newest mysql version.
+ * This mirrors wpdb::db_version(), but must also be defined here because
+ * WordPress 5.4 and older fetch server information directly from the MySQL
+ * extension instead of delegating to wpdb::db_server_info().
*
* @see wpdb::db_version()
+ *
+ * @return string Version number on success, or an empty string while disconnected.
*/
public function db_version() {
- return '8.0';
+ return preg_replace( '/[^0-9.].*/', '', $this->db_server_info() );
}
/**
- * Returns the version of the SQLite engine.
+ * Returns the raw version string of the emulated MySQL server.
+ *
+ * @see wpdb::db_server_info()
*
- * @return string SQLite engine version as a string.
+ * @return string Emulated MySQL server version, or an empty string while disconnected.
*/
public function db_server_info() {
- return $this->dbh->get_sqlite_version();
+ if ( ! $this->dbh ) {
+ return '';
+ }
+
+ return $this->dbh->getAttribute( PDO::ATTR_SERVER_VERSION ); // phpcs:ignore WordPress.DB.RestrictedClasses.mysql__PDO
}
/**
@@ -647,7 +824,7 @@ private function ensure_database_directory( string $database_path ) {
/**
- * Format SQLite driver error message.
+ * Format MySQL-on-SQLite driver error message.
*
* @return string
*/
@@ -655,8 +832,8 @@ private function format_error_message( Throwable $e ) {
$output = '
Query was: %s
',
- var_export( $query, true )
- );
- $message .= sprintf( 'Error message is: %s', $err_data[2] );
- wp_die( $message, 'Database Error!' );
- }
- }
- }
- }
-
return true;
}
@@ -125,8 +73,6 @@ function sqlite_make_db_sqlite() {
* Runs the required functions to set up and populate the database,
* including primary admin user and initial options.
*
- * @since 1.0.0
- *
* @param string $blog_title Site title.
* @param string $user_name User's username.
* @param string $user_email User's email.
diff --git a/wp/wp-includes/blocks/latest-comments.php b/wp/wp-includes/blocks/latest-comments.php
index eec7e1045..37ee11c4e 100644
--- a/wp/wp-includes/blocks/latest-comments.php
+++ b/wp/wp-includes/blocks/latest-comments.php
@@ -120,7 +120,10 @@ function render_block_core_latest_comments( $attributes ) {
}
$list_items_markup .= '';
if ( 'full' === $display_content ) {
- $list_items_markup .= '