+
+ 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+=''+e+"")}),a)return this.revert();u("#bulk-titles").html('"),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;od?u('select[name="_status"] option[value="future"]',a):u('select[name="_status"] option[value="publish"]',a)).remove(),d=u(".inline-edit-password-input").prop("disabled",!1),"private"===p&&(u('input[name="keep_private"]',a).prop("checked",!0),d.val("").prop("disabled",!0)),0<(i=u('select[name="post_parent"] option[value="'+n+'"]',a)).length){for(s=i[0].className.split("-")[1],r=i;c&&0!==(r=r.next("option")).length;)r[0].className.split("-")[1]<=s?c=!1:(r.remove(),r=i);i.remove()}return u(a).attr("id","edit-"+n).addClass("inline-editor").show(),u(".ptitle",a).trigger("focus"),!1},save:function(n){var t=u(".post_status_page").val()||"";return"object"==typeof n&&(n=this.getId(n)),u("table.widefat .spinner").addClass("is-active"),t={action:"inline-save",post_type:typenow,post_ID:n,edit_date:"true",post_status:t},t=u("#edit-"+n).find(":input").serialize()+"&"+u.param(t),u.post(ajaxurl,t,function(t){var e=u("#edit-"+n+" .inline-edit-save .notice-error"),i=e.find(".error");u("table.widefat .spinner").removeClass("is-active"),t?-1!==t.indexOf("]*?>/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
+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+=''+e+"")}),a)return this.revert();u("#bulk-titles").html('"),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;od?u('select[name="_status"] option[value="future"]',a):u('select[name="_status"] option[value="publish"]',a)).remove(),d=u(".inline-edit-password-input").prop("disabled",!1),"private"===p&&(u('input[name="keep_private"]',a).prop("checked",!0),d.val("").prop("disabled",!0)),0<(i=u('select[name="post_parent"] option[value="'+n+'"]',a)).length){for(s=i[0].className.split("-")[1],r=i;c&&0!==(r=r.next("option")).length;)r[0].className.split("-")[1]<=s?c=!1:(r.remove(),r=i);i.remove()}return u(a).attr("id","edit-"+n).addClass("inline-editor").show(),u(".ptitle",a).trigger("focus"),!1},save:function(n){var t=u(".post_status_page").val()||"";return"object"==typeof n&&(n=this.getId(n)),u("table.widefat .spinner").addClass("is-active"),t={action:"inline-save",post_type:typenow,post_ID:n,edit_date:"true",post_status:t},t=u("#edit-"+n).find(":input").serialize()+"&"+u.param(t),u.post(ajaxurl,t,function(t){var e=u("#edit-"+n+" .inline-edit-save .notice-error"),i=e.find(".error");u("table.widefat .spinner").removeClass("is-active"),t?-1!==t.indexOf("]*?>/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..fc6cd91d1 100644
--- a/wp/wp-content/plugins/sqlite-database-integration/activate.php
+++ b/wp/wp-content/plugins/sqlite-database-integration/activate.php
@@ -14,10 +14,15 @@
* @param string $plugin The plugin basename.
*/
function sqlite_plugin_activation_redirect( $plugin ) {
- if ( plugin_basename( SQLITE_MAIN_FILE ) === $plugin ) {
- if ( wp_safe_redirect( admin_url( 'options-general.php?page=sqlite-integration' ) ) ) {
- exit;
- }
+ if (
+ plugin_basename( SQLITE_MAIN_FILE ) !== $plugin
+ || ! current_user_can( sqlite_plugin_get_manage_capability() )
+ ) {
+ return;
+ }
+
+ if ( wp_safe_redirect( sqlite_plugin_get_admin_page_url() ) ) {
+ exit;
}
}
add_action( 'activated_plugin', 'sqlite_plugin_activation_redirect' );
@@ -25,36 +30,34 @@ function sqlite_plugin_activation_redirect( $plugin ) {
/**
* Check the URL to ensure we're on the plugin page,
* the user has clicked the button to install SQLite,
- * and the nonce is valid.
+ * the user has permission to manage the database drop-in, and the nonce is valid.
* If the above conditions are met, run the sqlite_plugin_copy_db_file() function,
* and redirect to the install screen.
*
* @since 1.0.0
*/
function sqlite_activation() {
- global $current_screen;
- if ( isset( $current_screen->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' );
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..df8028af9 100644
--- a/wp/wp-content/plugins/sqlite-database-integration/admin-notices.php
+++ b/wp/wp-content/plugins/sqlite-database-integration/admin-notices.php
@@ -12,6 +12,9 @@
* When the plugin gets merged in wp-core, this is not to be ported.
*/
function sqlite_plugin_admin_notice() {
+ if ( ! current_user_can( sqlite_plugin_get_manage_capability() ) ) {
+ return;
+ }
// Don't print notices in the plugin's admin screen.
global $current_screen;
@@ -66,11 +69,8 @@ function sqlite_plugin_admin_notice() {
/* translators: 1: db.php drop-in path, 2: Admin URL to deactivate the module */
__( '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' ),
'' . 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..a6e3c4569 100644
--- a/wp/wp-content/plugins/sqlite-database-integration/admin-page.php
+++ b/wp/wp-content/plugins/sqlite-database-integration/admin-page.php
@@ -12,20 +12,30 @@
* @since 1.0.0
*/
function sqlite_add_admin_menu() {
- add_options_page(
+ $parent_slug = is_multisite() ? 'settings.php' : 'options-general.php';
+
+ add_submenu_page(
+ $parent_slug,
__( 'SQLite integration', 'sqlite-database-integration' ),
__( 'SQLite integration', 'sqlite-database-integration' ),
- 'manage_options',
+ sqlite_plugin_get_manage_capability(),
'sqlite-integration',
'sqlite_integration_admin_screen'
);
}
-add_action( 'admin_menu', 'sqlite_add_admin_menu' );
+add_action( is_multisite() ? 'network_admin_menu' : 'admin_menu', 'sqlite_add_admin_menu' );
/**
* The admin page contents.
*/
function sqlite_integration_admin_screen() {
+ if ( ! current_user_can( sqlite_plugin_get_manage_capability() ) ) {
+ wp_die(
+ esc_html__( 'Sorry, you are not allowed to access the SQLite integration settings.', 'sqlite-database-integration' ),
+ 403
+ );
+ }
+
$db_dropin_path = WP_CONTENT_DIR . '/db.php';
/*
@@ -56,7 +66,7 @@ function sqlite_integration_admin_screen() {
printf(
/* translators: 1: Admin URL to deactivate the module, 2: db.php drop-in path, */
__( 'The SQLite drop-in is enabled. To disable it and get back to your previous, MySQL database, you can 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 +76,17 @@ function sqlite_integration_admin_screen() {
-
-
-
- ' . esc_html( basename( WP_CONTENT_DIR ) ) . '/db.php'
- );
- ?>
-
-
-
+
+
' . esc_html( basename( WP_CONTENT_DIR ) ) . '/db.php'
);
?>
-
-
-
-
- ' . esc_html( basename( WP_CONTENT_DIR ) ) . '/db.php'
- );
- ?>
-
-
-
+
+
@@ -136,7 +123,7 @@ function sqlite_integration_admin_screen() {
-
+
'sqlite-db-integration',
'parent' => 'top-secondary',
'title' => $title,
- 'href' => esc_url( admin_url( 'options-general.php?page=sqlite-integration' ) ),
+ 'href' => esc_url( sqlite_plugin_get_admin_page_url() ),
'meta' => false,
);
$admin_bar->add_node( $args );
}
add_action( 'admin_bar_menu', 'sqlite_plugin_adminbar_item', 999 );
+
+/**
+ * Get the SQLite integration admin page URL.
+ *
+ * @access private
+ *
+ * @return string Admin page URL.
+ */
+function sqlite_plugin_get_admin_page_url() {
+ if ( is_multisite() ) {
+ return network_admin_url( 'settings.php?page=sqlite-integration' );
+ }
+ return admin_url( 'options-general.php?page=sqlite-integration' );
+}
diff --git a/wp/wp-content/plugins/sqlite-database-integration/capabilities.php b/wp/wp-content/plugins/sqlite-database-integration/capabilities.php
new file mode 100644
index 000000000..deab5108c
--- /dev/null
+++ b/wp/wp-content/plugins/sqlite-database-integration/capabilities.php
@@ -0,0 +1,80 @@
+set_prefix( $table_prefix );
- // Get the perflab options, remove the database/sqlite module and update the option.
- $row = $wpdb_mysql->get_row( $wpdb_mysql->prepare( "SELECT option_value FROM $wpdb_mysql->options WHERE option_name = %s LIMIT 1", 'active_plugins' ) );
- if ( is_object( $row ) ) {
- $value = maybe_unserialize( $row->option_value );
- if ( is_array( $value ) ) {
- $value_flipped = array_flip( $value );
- $items = array_reverse( explode( DIRECTORY_SEPARATOR, SQLITE_MAIN_FILE ) );
- $item = $items[1] . DIRECTORY_SEPARATOR . $items[0];
- unset( $value_flipped[ $item ] );
- $value = array_flip( $value_flipped );
- $wpdb_mysql->update( $wpdb_mysql->options, array( 'option_value' => maybe_serialize( $value ) ), array( 'option_name' => 'active_plugins' ) );
- }
- }
+ sqlite_plugin_deactivate_in_mysql( $wpdb_mysql, $network_deactivating );
},
PHP_INT_MAX
);
@@ -66,3 +62,52 @@ function () {
wp_cache_flush();
}
register_deactivation_hook( SQLITE_MAIN_FILE, 'sqlite_plugin_remove_db_file' ); // Remove db.php file on plugin deactivation.
+
+/**
+ * Deactivate the plugin in the original MySQL database.
+ *
+ * @access private
+ *
+ * @param wpdb $wpdb_mysql MySQL database connection.
+ * @param bool $network_deactivating Whether the plugin is being deactivated network-wide.
+ */
+function sqlite_plugin_deactivate_in_mysql( $wpdb_mysql, $network_deactivating ) {
+ if ( $network_deactivating ) {
+ $network_id = get_current_network_id();
+ $row = $wpdb_mysql->get_row( $wpdb_mysql->prepare( "SELECT meta_value AS active_plugins FROM $wpdb_mysql->sitemeta WHERE site_id = %d AND meta_key = %s LIMIT 1", $network_id, 'active_sitewide_plugins' ) );
+ $table = $wpdb_mysql->sitemeta;
+ $value_column = 'meta_value';
+ $where = array(
+ 'site_id' => $network_id,
+ 'meta_key' => 'active_sitewide_plugins',
+ );
+ } else {
+ $row = $wpdb_mysql->get_row( $wpdb_mysql->prepare( "SELECT option_value AS active_plugins FROM $wpdb_mysql->options WHERE option_name = %s LIMIT 1", 'active_plugins' ) );
+ $table = $wpdb_mysql->options;
+ $value_column = 'option_value';
+ $where = array( 'option_name' => 'active_plugins' );
+ }
+
+ if ( ! is_object( $row ) ) {
+ return;
+ }
+
+ $active_plugins = maybe_unserialize( $row->active_plugins );
+ if ( ! is_array( $active_plugins ) ) {
+ return;
+ }
+
+ $item = plugin_basename( SQLITE_MAIN_FILE );
+ if ( $network_deactivating ) {
+ $key = $item;
+ } else {
+ $key = array_search( $item, $active_plugins, true );
+ }
+
+ if ( false === $key || ! array_key_exists( $key, $active_plugins ) ) {
+ return;
+ }
+
+ unset( $active_plugins[ $key ] );
+ $wpdb_mysql->update( $table, array( $value_column => maybe_serialize( $active_plugins ) ), $where );
+}
diff --git a/wp/wp-content/plugins/sqlite-database-integration/load.php b/wp/wp-content/plugins/sqlite-database-integration/load.php
index fe8fbd5d5..ba8e6f5d8 100644
--- a/wp/wp-content/plugins/sqlite-database-integration/load.php
+++ b/wp/wp-content/plugins/sqlite-database-integration/load.php
@@ -3,8 +3,9 @@
* Plugin Name: SQLite Database Integration
* Description: SQLite database driver drop-in.
* Author: The WordPress Team
- * Version: 3.0.0-rc.7
+ * Version: 3.0.0-rc.8
* Requires PHP: 7.2
+ * Network: true
* Textdomain: sqlite-database-integration
*
* This feature plugin allows WordPress to use SQLite instead of MySQL as its database.
@@ -20,6 +21,7 @@
define( 'SQLITE_MAIN_FILE', __FILE__ );
+require_once __DIR__ . '/capabilities.php';
require_once __DIR__ . '/admin-page.php';
require_once __DIR__ . '/activate.php';
require_once __DIR__ . '/deactivate.php';
diff --git a/wp/wp-content/plugins/sqlite-database-integration/readme.txt b/wp/wp-content/plugins/sqlite-database-integration/readme.txt
index d19a6521c..ae7a6fe95 100644
--- a/wp/wp-content/plugins/sqlite-database-integration/readme.txt
+++ b/wp/wp-content/plugins/sqlite-database-integration/readme.txt
@@ -4,7 +4,7 @@ Contributors: wordpressdotorg, aristath, janjakes, zieladam, berislav.grgic
Requires at least: 6.4
Tested up to: 7.0
Requires PHP: 7.2
-Stable tag: 3.0.0-rc.7
+Stable tag: 3.0.0-rc.8
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html
Tags: performance, database
@@ -44,6 +44,17 @@ with SQLite syntax and behavior.
== Changelog ==
+= 3.0.0-rc.8 =
+
+* Preserve aliases in `UPDATE JOIN` translation ([#462](https://github.com/WordPress/sqlite-database-integration/pull/462))
+* Restrict SQLite installation permissions ([#464](https://github.com/WordPress/sqlite-database-integration/pull/464))
+* Improve string escaping ([#466](https://github.com/WordPress/sqlite-database-integration/pull/466))
+* Stabilize WordPress E2E tests ([#459](https://github.com/WordPress/sqlite-database-integration/pull/459))
+* Address WordPress PHPUnit test fails: charset detection, length validation etc ([#331](https://github.com/WordPress/sqlite-database-integration/pull/331))
+* Add Unicode support to the user-defined `REVERSE()` function ([#453](https://github.com/WordPress/sqlite-database-integration/pull/453))
+* Simplify the MySQL-on-SQLite driver API ([#449](https://github.com/WordPress/sqlite-database-integration/pull/449))
+* Add WordPress 7.0 compatibility ([#450](https://github.com/WordPress/sqlite-database-integration/pull/450))
+
= 3.0.0-rc.7 =
* Preserve configured SQLite journal mode in driver wrapper ([#447](https://github.com/WordPress/sqlite-database-integration/pull/447))
diff --git a/wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/load.php b/wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/load.php
index 165e27e97..8ef704366 100644
--- a/wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/load.php
+++ b/wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/load.php
@@ -36,10 +36,10 @@
require_once __DIR__ . '/sqlite/class-wp-sqlite-connection.php';
require_once __DIR__ . '/sqlite/class-wp-sqlite-configurator.php';
require_once __DIR__ . '/sqlite/class-wp-sqlite-driver.php';
-require_once __DIR__ . '/sqlite/class-wp-sqlite-driver-exception.php';
+require_once __DIR__ . '/sqlite/class-wp-mysql-on-sqlite-exception.php';
require_once __DIR__ . '/sqlite/class-wp-sqlite-information-schema-builder.php';
require_once __DIR__ . '/sqlite/class-wp-sqlite-information-schema-exception.php';
require_once __DIR__ . '/sqlite/class-wp-sqlite-information-schema-reconstructor.php';
require_once __DIR__ . '/sqlite/class-wp-sqlite-pdo-user-defined-functions.php';
require_once __DIR__ . '/sqlite/class-wp-mysql-on-sqlite.php';
-require_once __DIR__ . '/sqlite/class-wp-pdo-proxy-statement.php';
+require_once __DIR__ . '/sqlite/class-wp-mysql-on-sqlite-statement.php';
diff --git a/wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/mysql/class-wp-mysql-lexer.php b/wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/mysql/class-wp-mysql-lexer.php
index d6ee9970e..bd9a617cd 100644
--- a/wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/mysql/class-wp-mysql-lexer.php
+++ b/wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/mysql/class-wp-mysql-lexer.php
@@ -16,6 +16,8 @@
* https://github.com/mysql/mysql-workbench/blob/8.0.38/library/parsers/grammars/MySQLLexer.g4
* https://github.com/mysql/mysql-workbench/blob/8.0.38/library/parsers/grammars/predefined.tokens
* https://github.com/mysql/mysql-workbench/blob/8.0.38/library/parsers/mysql/MySQLBaseLexer.cpp
+ *
+ * @access private
*/
class WP_MySQL_Lexer {
/**
@@ -32,6 +34,7 @@ class WP_MySQL_Lexer {
const SQL_MODE_PIPES_AS_CONCAT = 2;
const SQL_MODE_IGNORE_SPACE = 4;
const SQL_MODE_NO_BACKSLASH_ESCAPES = 8;
+ const SQL_MODE_ANSI_QUOTES = 16;
/**
* Character masks for frequently used character classes.
@@ -2209,6 +2212,19 @@ public function __construct(
$this->sql_modes |= self::SQL_MODE_IGNORE_SPACE;
} elseif ( 'NO_BACKSLASH_ESCAPES' === $sql_mode ) {
$this->sql_modes |= self::SQL_MODE_NO_BACKSLASH_ESCAPES;
+ } elseif ( 'ANSI_QUOTES' === $sql_mode ) {
+ $this->sql_modes |= self::SQL_MODE_ANSI_QUOTES;
+ } elseif ( 'ANSI' === $sql_mode ) {
+ /*
+ * Expand the composite ANSI mode into its lexer-relevant components.
+ * The ANSI mode also implies REAL_AS_FLOAT and ONLY_FULL_GROUP_BY,
+ * which do not affect the lexer.
+ *
+ * See: https://dev.mysql.com/doc/refman/8.4/en/sql-mode.html#sqlmode_ansi
+ */
+ $this->sql_modes |= self::SQL_MODE_PIPES_AS_CONCAT
+ | self::SQL_MODE_IGNORE_SPACE
+ | self::SQL_MODE_ANSI_QUOTES;
}
}
}
@@ -2891,15 +2907,30 @@ private function read_number(): ?int {
*
* Rules:
* 1. Quotes can be escaped by doubling them ('', "", ``).
- * 2. Backslashes escape the next character, unless NO_BACKSLASH_ESCAPES is set.
+ * 2. In string literals, backslashes escape the next character,
+ * unless the NO_BACKSLASH_ESCAPES SQL mode is set.
+ * 3. In identifiers, backslashes are always literal and never escape.
*/
private function read_quoted_text(): ?int {
$quote = $this->sql[ $this->bytes_already_read ];
$this->bytes_already_read += 1; // Consume the quote.
- $no_backslash_escapes = $this->is_sql_mode_active(
- self::SQL_MODE_NO_BACKSLASH_ESCAPES
- );
+ /*
+ * Determine whether the quote opens an identifier or a string literal.
+ * An identifier is quoted with a backtick or a double quote when the
+ * ANSI_QUOTES SQL mode is active. Otherwise, it is a string literal.
+ *
+ * See: https://dev.mysql.com/doc/refman/8.4/en/sql-mode.html#sqlmode_ansi_quotes
+ */
+ $is_identifier_quote = '`' === $quote
+ || ( '"' === $quote && $this->is_sql_mode_active( self::SQL_MODE_ANSI_QUOTES ) );
+
+ /*
+ * Backslash escapes apply only to string literals, and only when the
+ * NO_BACKSLASH_ESCAPES SQL mode is not set.
+ */
+ $backslash_is_escape = ! $is_identifier_quote
+ && ! $this->is_sql_mode_active( self::SQL_MODE_NO_BACKSLASH_ESCAPES );
// We need to look for the closing quote in a loop, as it can be escaped,
// in which case the escape sequence is consumed and the loop continues.
@@ -2912,9 +2943,9 @@ private function read_quoted_text(): ?int {
$at = $quote_at;
/*
- * By default, quotes can be escaped with a "\".
- * When NO_BACKSLASH_ESCAPES SQL mode is active, the "\" treated as
- * a regular character.
+ * In string literals, quotes can be escaped with a backslash. When
+ * NO_BACKSLASH_ESCAPES SQL mode is active, the backslash is treated
+ * as a regular character. Identifiers never use backslash escaping.
*
* The quote is escaped only when the number of preceding backslashes
* is odd - "\" is an escape sequence, "\\" is an escaped backslash,
@@ -2925,7 +2956,7 @@ private function read_quoted_text(): ?int {
* sits at the very start of the input. The `?? null` covers
* positive out-of-range indexes belt-and-suspenders.
*/
- if ( ! $no_backslash_escapes ) {
+ if ( $backslash_is_escape ) {
$i = 0;
while ( ( $at - $i - 1 ) >= 0 && '\\' === ( $this->sql[ $at - $i - 1 ] ?? null ) ) {
$i += 1;
@@ -2948,13 +2979,10 @@ private function read_quoted_text(): ?int {
$this->bytes_already_read = $at;
- if ( '`' === $quote ) {
+ if ( $is_identifier_quote ) {
return self::BACK_TICK_QUOTED_ID;
- } elseif ( '"' === $quote ) {
- return self::DOUBLE_QUOTED_TEXT;
- } else {
- return self::SINGLE_QUOTED_TEXT;
}
+ return '"' === $quote ? self::DOUBLE_QUOTED_TEXT : self::SINGLE_QUOTED_TEXT;
}
private function read_line_comment(): int {
diff --git a/wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/mysql/class-wp-mysql-parser.php b/wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/mysql/class-wp-mysql-parser.php
index 69282b9c4..0417085fe 100644
--- a/wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/mysql/class-wp-mysql-parser.php
+++ b/wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/mysql/class-wp-mysql-parser.php
@@ -1,5 +1,10 @@
sql_mode_no_backslash_escapes_enabled ) {
+ if (
+ WP_MySQL_Lexer::BACK_TICK_QUOTED_ID === $this->id
+ || $this->sql_mode_no_backslash_escapes_enabled
+ ) {
return str_replace( $quote . $quote, $quote, $value );
}
diff --git a/wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/mysql/native/class-wp-mysql-lexer.php b/wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/mysql/native/class-wp-mysql-lexer.php
index def8ca3fa..1a645ab52 100644
--- a/wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/mysql/native/class-wp-mysql-lexer.php
+++ b/wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/mysql/native/class-wp-mysql-lexer.php
@@ -1,3 +1,8 @@
*/
diff --git a/wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/mysql/native/trait-wp-mysql-native-parser-impl.php b/wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/mysql/native/trait-wp-mysql-native-parser-impl.php
index c53e96e53..52628831a 100644
--- a/wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/mysql/native/trait-wp-mysql-native-parser-impl.php
+++ b/wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/mysql/native/trait-wp-mysql-native-parser-impl.php
@@ -13,6 +13,8 @@
*
* Adding a public method here is enough to plumb a new public method
* through to the native parser; the using class does not need touching.
+ *
+ * @access private
*/
trait WP_MySQL_Native_Parser_Impl {
/**
diff --git a/wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/parser/class-wp-parser-grammar.php b/wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/parser/class-wp-parser-grammar.php
index 9bf30b973..5e9222112 100644
--- a/wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/parser/class-wp-parser-grammar.php
+++ b/wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/parser/class-wp-parser-grammar.php
@@ -8,6 +8,8 @@
* representation, and precomputes a lookup table for quick branch selection.
*
* @TODO: Add more details about the grammar implementation.
+ *
+ * @access private
*/
class WP_Parser_Grammar {
/**
diff --git a/wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/parser/class-wp-parser-node.php b/wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/parser/class-wp-parser-node.php
index b61f38d5e..c64c83db8 100644
--- a/wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/parser/class-wp-parser-node.php
+++ b/wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/parser/class-wp-parser-node.php
@@ -8,6 +8,8 @@
* Each node can contain children, consisting of other nodes and grammar tokens.
* In this way, a parser node constitutes a recursive structure that represents
* a parse (sub)tree at each level of the full grammar tree.
+ *
+ * @access private
*/
class WP_Parser_Node {
/**
diff --git a/wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/parser/class-wp-parser-token.php b/wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/parser/class-wp-parser-token.php
index 4132ba382..0f4ea54b2 100644
--- a/wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/parser/class-wp-parser-token.php
+++ b/wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/parser/class-wp-parser-token.php
@@ -7,6 +7,8 @@
* In a parse tree, a token represent a leaf, that is, a node without children.
* It is a simple generic container for a token ID and value, that can be used
* as a base class and extended for specific use cases.
+ *
+ * @access private
*/
class WP_Parser_Token {
/**
diff --git a/wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/parser/class-wp-parser.php b/wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/parser/class-wp-parser.php
index 4436892fa..9953f1a58 100644
--- a/wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/parser/class-wp-parser.php
+++ b/wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/parser/class-wp-parser.php
@@ -7,6 +7,8 @@
*
* @TODO: Add a detailed description and list the properties that a grammar must
* satisfy in order to be supported by this parser (e.g., no left recursion).
+ *
+ * @access private
*/
class WP_Parser {
protected $grammar;
diff --git a/wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/sqlite/class-wp-mysql-on-sqlite-exception.php b/wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/sqlite/class-wp-mysql-on-sqlite-exception.php
new file mode 100644
index 000000000..72112c16e
--- /dev/null
+++ b/wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/sqlite/class-wp-mysql-on-sqlite-exception.php
@@ -0,0 +1,59 @@
+code = $code;
+ $this->driver = $driver;
+ $this->errorInfo = $error_info ?? $this->create_error_info( $message, $code, $previous );
+ }
+
+ public function get_driver(): WP_MySQL_On_SQLite {
+ return $this->driver;
+ }
+
+ /**
+ * Create PDO-style error information from an originating exception or from
+ * the emulated driver error.
+ *
+ * @param string $message The exception message.
+ * @param int|string $code The exception code.
+ * @param Throwable|null $previous The previous throwable.
+ * @return array PDO-style error information.
+ */
+ private function create_error_info( string $message, $code, ?Throwable $previous ): array {
+ if ( $previous instanceof PDOException && is_array( $previous->errorInfo ) ) {
+ return $previous->errorInfo;
+ }
+
+ $sqlstate = is_string( $code ) && 5 === strlen( $code ) ? $code : 'HY000';
+ return array( $sqlstate, 1105, $message );
+ }
+}
diff --git a/wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/sqlite/class-wp-pdo-proxy-statement.php b/wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/sqlite/class-wp-mysql-on-sqlite-statement.php
similarity index 77%
rename from wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/sqlite/class-wp-pdo-proxy-statement.php
rename to wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/sqlite/class-wp-mysql-on-sqlite-statement.php
index f56340d70..7e01766be 100644
--- a/wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/sqlite/class-wp-pdo-proxy-statement.php
+++ b/wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/sqlite/class-wp-mysql-on-sqlite-statement.php
@@ -8,6 +8,7 @@
* PDO uses camel case naming, enable non-snake case:
* phpcs:disable WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid
* phpcs:disable WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
+ * phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
*
* PDO uses $class as a variable name, enable it:
* phpcs:disable Universal.NamingConventions.NoReservedKeywordParameterNames.classFound
@@ -26,7 +27,12 @@
* we conditionally define traits with different APIs based on the PHP version.
*/
if ( PHP_VERSION_ID < 80000 ) {
- trait WP_PDO_Proxy_Statement_PHP_Compat {
+ /**
+ * PHP compatibility methods for WP_MySQL_On_SQLite_Statement.
+ *
+ * @access private
+ */
+ trait WP_MySQL_On_SQLite_Statement_PHP_Compat {
/**
* Set the default fetch mode for this statement.
*
@@ -59,9 +65,26 @@ public function fetchAll( $mode = null, $class_name = null, $constructor_args =
}
return $this->fetchAllRows( $mode, $class_name, $constructor_args );
}
+
+ /**
+ * Get metadata for a column in a result set.
+ *
+ * @param int $column The index of the column (0-indexed).
+ * @return array|false The column metadata as an associative array,
+ * or false if the column does not exist.
+ */
+ #[ReturnTypeWillChange]
+ public function getColumnMeta( $column ) {
+ return $this->getColumnMetadata( $column );
+ }
}
} else {
- trait WP_PDO_Proxy_Statement_PHP_Compat {
+ /**
+ * PHP compatibility methods for WP_MySQL_On_SQLite_Statement.
+ *
+ * @access private
+ */
+ trait WP_MySQL_On_SQLite_Statement_PHP_Compat {
/**
* Set the default fetch mode for this statement.
*
@@ -84,14 +107,29 @@ public function setFetchMode( $mode, ...$args ): bool {
public function fetchAll( $mode = PDO::FETCH_DEFAULT, ...$args ): array {
return $this->fetchAllRows( $mode, ...$args );
}
+
+ /**
+ * Get metadata for a column in a result set.
+ *
+ * @param int $column The index of the column (0-indexed).
+ * @return array|false The column metadata as an associative array,
+ * or false if the column does not exist.
+ */
+ #[ReturnTypeWillChange]
+ public function getColumnMeta( int $column ) {
+ if ( $column < 0 ) {
+ throw new ValueError( 'PDOStatement::getColumnMeta(): Argument #1 ($column) must be greater than or equal to 0' );
+ }
+ return $this->getColumnMetadata( $column );
+ }
}
}
/**
- * PDOStatement implementation that operates on in-memory data.
+ * PDOStatement implementation for MySQL-on-SQLite query results.
*
- * This class implements a complete PDOStatement interface on top of PHP arrays.
- * It is used for result sets that are composed or transformed in the PHP layer.
+ * Delegates operations to the underlying SQLite statement while adapting
+ * MySQL-specific behavior such as affected row counts.
*
* PDO supports the following fetch modes:
* - PDO::FETCH_DEFAULT: current default fetch mode (available from PHP 8.0)
@@ -108,8 +146,8 @@ public function fetchAll( $mode = PDO::FETCH_DEFAULT, ...$args ): array {
* - PDO::FETCH_BOUND: bind values to PHP variables, can't be used with fetchAll()
* - PDO::FETCH_FUNC: custom function, only works with fetchAll(), can't be default [1 extra arg]
*/
-class WP_PDO_Proxy_Statement extends PDOStatement {
- use WP_PDO_Proxy_Statement_PHP_Compat;
+class WP_MySQL_On_SQLite_Statement extends PDOStatement implements IteratorAggregate {
+ use WP_MySQL_On_SQLite_Statement_PHP_Compat;
/**
* The original PDO statement.
@@ -118,6 +156,20 @@ class WP_PDO_Proxy_Statement extends PDOStatement {
*/
private $statement;
+ /**
+ * Resolve MySQL-compatible metadata by column index.
+ *
+ * @var callable
+ */
+ private $column_meta_resolver;
+
+ /**
+ * Resolved MySQL-compatible metadata, keyed by column index.
+ *
+ * @var array
+ */
+ private $resolved_column_meta = array();
+
/**
* The number of affected rows.
*
@@ -128,15 +180,25 @@ class WP_PDO_Proxy_Statement extends PDOStatement {
/**
* Constructor.
*
- * @param PDOStatement $statement The original PDO statement.
- * @param int $affected_rows The number of affected rows.
+ * @param PDOStatement $statement The original PDO statement.
+ * @param string $query The original MySQL query.
+ * @param callable $column_meta_resolver Resolves metadata by column index.
+ * @param int|null $affected_rows The number of affected rows.
*/
public function __construct(
PDOStatement $statement,
+ string $query,
+ callable $column_meta_resolver,
?int $affected_rows = null
) {
- $this->statement = $statement;
- $this->affected_rows = $affected_rows;
+ $this->statement = $statement;
+
+ // Userland can only initialize PDOStatement::$queryString on PHP 8.1+.
+ if ( PHP_VERSION_ID >= 80100 ) {
+ $this->queryString = $query;
+ }
+ $this->column_meta_resolver = $column_meta_resolver;
+ $this->affected_rows = $affected_rows;
}
/**
@@ -211,17 +273,6 @@ public function fetchObject( $class = 'stdClass', $constructorArgs = array() ) {
return $this->statement->fetchObject( $class, $constructorArgs );
}
- /**
- * Get metadata for a column in a result set.
- *
- * @param int $column The index of the column (0-indexed).
- * @return array|false The column metadata as an associative array,
- * or false if the column does not exist.
- */
- public function getColumnMeta( $column ): array {
- throw new RuntimeException( 'Not implemented' );
- }
-
/**
* Fetch the SQLSTATE associated with the last statement operation.
*
@@ -229,7 +280,7 @@ public function getColumnMeta( $column ): array {
* or null if there is no error.
*/
public function errorCode(): ?string {
- throw new RuntimeException( 'Not implemented' );
+ return $this->statement->errorCode();
}
/**
@@ -241,7 +292,11 @@ public function errorCode(): ?string {
* 2: Driver-specific error message.
*/
public function errorInfo(): array {
- throw new RuntimeException( 'Not implemented' );
+ // Normalize successful results. PDO_SQLite may retain stale driver-specific fields on PHP < 8.0.
+ if ( '00000' === $this->statement->errorCode() ) {
+ return array( '00000', null, null );
+ }
+ return $this->statement->errorInfo();
}
/**
@@ -272,7 +327,7 @@ public function setAttribute( $attribute, $value ): bool {
* @return Iterator The iterator for the result set.
*/
public function getIterator(): Iterator {
- throw new RuntimeException( 'Not implemented' );
+ yield from $this->statement;
}
/**
@@ -290,7 +345,7 @@ public function nextRowset(): bool {
* @return bool True on success, false on failure.
*/
public function closeCursor(): bool {
- throw new RuntimeException( 'Not implemented' );
+ return $this->statement->closeCursor();
}
/**
@@ -303,8 +358,8 @@ public function closeCursor(): bool {
* @param mixed $driverOptions Optional parameters for the driver.
* @return bool True on success, false on failure.
*/
- public function bindColumn( $column, &$var, $type = null, $maxLength = null, $driverOptions = null ): bool {
- throw new RuntimeException( 'Not implemented' );
+ public function bindColumn( $column, &$var, $type = PDO::PARAM_STR, $maxLength = 0, $driverOptions = null ): bool {
+ return $this->statement->bindColumn( $column, $var, $type, $maxLength, $driverOptions );
}
/**
@@ -344,10 +399,27 @@ public function debugDumpParams(): ?bool {
throw new RuntimeException( 'Not implemented' );
}
+ /**
+ * Get metadata for a column in a result set.
+ *
+ * This is used internally by the "WP_MySQL_On_SQLite_Statement_PHP_Compat" trait,
+ * that is defined conditionally based on the current PHP version.
+ *
+ * @param int $column The index of the column (0-indexed).
+ * @return array|false The column metadata as an associative array,
+ * or false if the column does not exist.
+ */
+ private function getColumnMetadata( $column ) {
+ if ( ! array_key_exists( $column, $this->resolved_column_meta ) ) {
+ $this->resolved_column_meta[ $column ] = ( $this->column_meta_resolver )( $column );
+ }
+ return $this->resolved_column_meta[ $column ];
+ }
+
/**
* Fetch all remaining rows from the result set.
*
- * This is used internally by the "WP_PDO_Proxy_Statement_PHP_Compat" trait,
+ * This is used internally by the "WP_MySQL_On_SQLite_Statement_PHP_Compat" trait,
* that is defined conditionally based on the current PHP version.
*
* @param int $mode The fetch mode to use.
@@ -361,7 +433,7 @@ private function fetchAllRows( $mode = null, ...$args ): array {
/**
* Set the default fetch mode for this statement.
*
- * This is used internally by the "WP_PDO_Proxy_Statement_PHP_Compat" trait,
+ * This is used internally by the "WP_MySQL_On_SQLite_Statement_PHP_Compat" trait,
* that is defined conditionally based on the current PHP version.
*
* @param int $mode The fetch mode to set as the default.
diff --git a/wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/sqlite/class-wp-mysql-on-sqlite.php b/wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/sqlite/class-wp-mysql-on-sqlite.php
index c8f20e27a..3f769fe97 100644
--- a/wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/sqlite/class-wp-mysql-on-sqlite.php
+++ b/wp/wp-content/plugins/sqlite-database-integration/wp-includes/database/sqlite/class-wp-mysql-on-sqlite.php
@@ -3,7 +3,30 @@
/*
* 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
+ * phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
+ *
+ * PDO uses $string as a parameter name, enable it:
+ * phpcs:disable Universal.NamingConventions.NoReservedKeywordParameterNames.stringFound
+ *
+ * We conditionally define a trait for PHP-version-specific PDO methods:
+ * phpcs:disable Generic.Files.OneObjectStructurePerFile.MultipleFound
+ */
+
+/*
+ * The "PDO::connect()" method in PHP 8.4 uses a "static" return type declaration,
+ * which PHP 7 cannot parse. Therefore, a conditional file import is needed.
*/
+if ( PHP_VERSION_ID >= 80400 ) {
+ require_once __DIR__ . '/trait-wp-mysql-on-sqlite-pdo-compat-php-84.php';
+} else {
+ /**
+ * @access private
+ */
+ trait WP_MySQL_On_SQLite_PDO_Compat {}
+}
/**
* SQLite driver for MySQL.
@@ -15,10 +38,12 @@
* The driver requires PDO with the SQLite driver, and the PCRE engine.
*/
class WP_MySQL_On_SQLite extends PDO {
+ use WP_MySQL_On_SQLite_PDO_Compat;
+
/**
* The path to the MySQL SQL grammar file.
*/
- const MYSQL_GRAMMAR_PATH = __DIR__ . '/../mysql/mysql-grammar.php';
+ private const MYSQL_GRAMMAR_PATH = __DIR__ . '/../mysql/mysql-grammar.php';
/**
* The minimum required version of SQLite.
@@ -31,6 +56,8 @@ class WP_MySQL_On_SQLite extends PDO {
/**
* An identifier prefix for internal database objects.
*
+ * @access private
+ *
* @TODO: Do not allow accessing objects with this prefix.
*/
const RESERVED_PREFIX = '_wp_sqlite_';
@@ -40,6 +67,8 @@ class WP_MySQL_On_SQLite extends PDO {
*
* This special table is used to emulate MySQL global variables and to store
* some internal configuration values.
+ *
+ * @access private
*/
const GLOBAL_VARIABLES_TABLE_NAME = self::RESERVED_PREFIX . 'global_variables';
@@ -47,22 +76,87 @@ class WP_MySQL_On_SQLite extends PDO {
* Name of the connection-private TEMP table used to build empty result sets
* without acquiring a write lock on the database. See create_result_statement_from_data().
*/
- const EMPTY_RESULT_TABLE_NAME = self::RESERVED_PREFIX . 'empty_result';
+ private const EMPTY_RESULT_TABLE_NAME = self::RESERVED_PREFIX . 'empty_result';
/**
* The name of the SQLite driver version variable.
*
* This internal variable is used to store the latest version of the SQLite
* driver that was used to initialize and configure the SQLite database.
+ *
+ * @access private
*/
const DRIVER_VERSION_VARIABLE_NAME = self::RESERVED_PREFIX . 'driver_version';
+ /**
+ * MySQL SQL modes mapped to their bitmask values.
+ *
+ * The modes are ordered by their bit position, matching MySQL's canonical
+ * serialization order.
+ *
+ * See:
+ * https://github.com/mysql/mysql-server/blob/8.4/sql/system_variables.h
+ * https://github.com/mysql/mysql-server/blob/5.7/sql/sys_vars.cc
+ */
+ private const SQL_MODES = array(
+ 'REAL_AS_FLOAT' => 1 << 0,
+ 'PIPES_AS_CONCAT' => 1 << 1,
+ 'ANSI_QUOTES' => 1 << 2,
+ 'IGNORE_SPACE' => 1 << 3,
+ 'NOT_USED' => 1 << 4,
+ 'ONLY_FULL_GROUP_BY' => 1 << 5,
+ 'NO_UNSIGNED_SUBTRACTION' => 1 << 6,
+ 'NO_DIR_IN_CREATE' => 1 << 7,
+ 'POSTGRESQL' => 1 << 8,
+ 'ORACLE' => 1 << 9,
+ 'MSSQL' => 1 << 10,
+ 'DB2' => 1 << 11,
+ 'MAXDB' => 1 << 12,
+ 'NO_KEY_OPTIONS' => 1 << 13,
+ 'NO_TABLE_OPTIONS' => 1 << 14,
+ 'NO_FIELD_OPTIONS' => 1 << 15,
+ 'MYSQL323' => 1 << 16,
+ 'MYSQL40' => 1 << 17,
+ 'ANSI' => 1 << 18,
+ 'NO_AUTO_VALUE_ON_ZERO' => 1 << 19,
+ 'NO_BACKSLASH_ESCAPES' => 1 << 20,
+ 'STRICT_TRANS_TABLES' => 1 << 21,
+ 'STRICT_ALL_TABLES' => 1 << 22,
+ 'NO_ZERO_IN_DATE' => 1 << 23,
+ 'NO_ZERO_DATE' => 1 << 24,
+ 'ALLOW_INVALID_DATES' => 1 << 25,
+ 'ERROR_FOR_DIVISION_BY_ZERO' => 1 << 26,
+ 'TRADITIONAL' => 1 << 27,
+ 'NO_AUTO_CREATE_USER' => 1 << 28,
+ 'HIGH_NOT_PRECEDENCE' => 1 << 29,
+ 'NO_ENGINE_SUBSTITUTION' => 1 << 30,
+ 'PAD_CHAR_TO_FULL_LENGTH' => 1 << 31,
+
+ // Modes below require 64-bit PHP.
+ // TODO: Consider supporting these values on 32-bit PHP as well.
+ 'TIME_TRUNCATE_FRACTIONAL' => 1 << 32,
+ );
+
+ /**
+ * The default SQL modes shared by MySQL 5.7 and 8.0.
+ *
+ * MySQL 5.7 additionally enables NO_AUTO_CREATE_USER.
+ */
+ private const DEFAULT_SQL_MODES = array(
+ 'ERROR_FOR_DIVISION_BY_ZERO',
+ 'NO_ENGINE_SUBSTITUTION',
+ 'NO_ZERO_DATE',
+ 'NO_ZERO_IN_DATE',
+ 'ONLY_FULL_GROUP_BY',
+ 'STRICT_TRANS_TABLES',
+ );
+
/**
* A map of MySQL tokens to SQLite data types.
*
* This is used to translate a MySQL data type to an SQLite data type.
*/
- const DATA_TYPE_MAP = array(
+ private const DATA_TYPE_MAP = array(
// Numeric data types:
WP_MySQL_Lexer::BIT_SYMBOL => 'INTEGER',
WP_MySQL_Lexer::BOOL_SYMBOL => 'INTEGER',
@@ -128,7 +222,7 @@ class WP_MySQL_On_SQLite extends PDO {
* INFORMATION_SCHEMA tables. They keys are MySQL data types normalized
* as they appear in the INFORMATION_SCHEMA. Values are SQLite data types.
*/
- const DATA_TYPE_STRING_MAP = array(
+ private const DATA_TYPE_STRING_MAP = array(
// Numeric data types:
'bit' => 'INTEGER',
'bool' => 'INTEGER',
@@ -199,7 +293,7 @@ class WP_MySQL_On_SQLite extends PDO {
* https://www.sqlite.org/lang_datefunc.html
* https://strftime.org/
*/
- const MYSQL_DATE_FORMAT_TO_SQLITE_STRFTIME_MAP = array(
+ private const MYSQL_DATE_FORMAT_TO_SQLITE_STRFTIME_MAP = array(
'%a' => '%D',
'%b' => '%M',
'%c' => '%n',
@@ -241,7 +335,7 @@ class WP_MySQL_On_SQLite extends PDO {
* See:
* https://dev.mysql.com/doc/refman/8.4/en/data-type-defaults.html#data-type-defaults-implicit
*/
- const DATA_TYPE_IMPLICIT_DEFAULT_MAP = array(
+ private const DATA_TYPE_IMPLICIT_DEFAULT_MAP = array(
// Numeric data types:
'bit' => '0',
'bool' => '0',
@@ -311,7 +405,7 @@ class WP_MySQL_On_SQLite extends PDO {
*
* This is used to compute the column metadata from the information schema.
*/
- const COLUMN_INFO_MYSQL_TO_NATIVE_TYPES_MAP = array(
+ private const COLUMN_INFO_MYSQL_TO_NATIVE_TYPES_MAP = array(
// Numeric data types:
'bit' => array( 'BIT', 16, 1, 0 ),
'tinyint' => array( 'TINY', 1, 4, 0 ),
@@ -375,7 +469,7 @@ class WP_MySQL_On_SQLite extends PDO {
* This is used to compute the MySQL column metadata for non-column fields
* that have no records in the information schema (i.e., expressions).
*/
- const COLUMN_INFO_SQLITE_TO_NATIVE_TYPES_MAP = array(
+ private const COLUMN_INFO_SQLITE_TO_NATIVE_TYPES_MAP = array(
'NULL' => array( 'NULL', 6, 0, 0 ),
'INT' => array( 'LONGLONG', 8, 21, 0 ),
'INTEGER' => array( 'LONGLONG', 8, 21, 0 ),
@@ -449,6 +543,47 @@ class WP_MySQL_On_SQLite extends PDO {
*/
private $connection;
+ /**
+ * Caller-visible PDO error mode.
+ *
+ * The underlying SQLite connection always uses exceptions so internal
+ * operations can handle errors reliably.
+ *
+ * @var int
+ */
+ private $error_mode = PDO::ERRMODE_EXCEPTION;
+
+ /**
+ * Whether fetched scalar values should be converted to strings.
+ *
+ * PDO SQLite cannot report PDO::ATTR_STRINGIFY_FETCHES on PHP 7.2–8.1,
+ * so the wrapper tracks its value on those versions.
+ *
+ * @var bool
+ */
+ private $stringify_fetches = false;
+
+ /**
+ * SQLSTATE associated with the last PDO operation.
+ *
+ * @var string|null
+ */
+ private $error_code;
+
+ /**
+ * Error information associated with the last PDO operation.
+ *
+ * @var array
+ */
+ private $error_info = array( '', null, null );
+
+ /**
+ * ID generated by the last user-issued INSERT or REPLACE statement.
+ *
+ * @var string
+ */
+ private $last_insert_id = '0';
+
/**
* User-defined functions registered on the SQLite connection.
*
@@ -591,16 +726,9 @@ class WP_MySQL_On_SQLite extends PDO {
* TODO: This may be represented using a temporary table in the future,
* together with GLOBAL SQL mode (a non-temporary table).
*
- * @var string[]
+ * @var int
*/
- private $active_sql_modes = array(
- 'ERROR_FOR_DIVISION_BY_ZERO',
- 'NO_ENGINE_SUBSTITUTION',
- 'NO_ZERO_DATE',
- 'NO_ZERO_IN_DATE',
- 'ONLY_FULL_GROUP_BY',
- 'STRICT_TRANS_TABLES',
- );
+ private $active_sql_modes;
/**
* A name-to-value map of MySQL system variables for the current session.
@@ -635,23 +763,28 @@ class WP_MySQL_On_SQLite extends PDO {
* @param string $dsn MySQL-on-SQLite DSN containing the SQLite path and database name.
* @param string|null $username Optional. Ignored by this driver.
* @param string|null $password Optional. Ignored by this driver.
- * @param array $options {
+ * @param array|null $options {
* Optional driver options.
*
+ * Numeric keys are handled as standard PDO constructor options.
+ * Driver-specific PDO options are not supported.
+ *
* @type int $mysql_version Optional. MySQL version to emulate. Default 80038.
* @type PDO|null $pdo Optional. Existing SQLite PDO connection.
* @type string|null $journal_mode Optional. SQLite journal mode. Default 'WAL'.
* @type string|int|null $synchronous Optional. SQLite synchronous setting.
* }
*
- * @throws WP_SQLite_Driver_Exception When the driver initialization fails.
+ * @throws WP_MySQL_On_SQLite_Exception When the driver initialization fails.
*/
public function __construct(
string $dsn,
?string $username = null,
?string $password = null,
- array $options = array()
+ ?array $options = null
) {
+ $options = $options ?? array();
+
// PDO DSN can't include "\0" bytes; parsing stops at the first one.
$first_null_byte_index = strpos( $dsn, "\0" );
if ( false !== $first_null_byte_index ) {
@@ -692,9 +825,25 @@ public function __construct(
$db_name = $args['dbname'] ?? 'sqlite_database';
// Create a new SQLite connection.
+ $pdo_options = array_filter(
+ $options,
+ function ( $key ) {
+ return is_int( $key );
+ },
+ ARRAY_FILTER_USE_KEY
+ );
+
+ $connection_pdo_options = array();
+ if ( array_key_exists( PDO::ATTR_PERSISTENT, $pdo_options ) ) {
+ // Persistence is connection-time-only. Do not apply it again after initialization.
+ $connection_pdo_options[ PDO::ATTR_PERSISTENT ] = $pdo_options[ PDO::ATTR_PERSISTENT ];
+ unset( $pdo_options[ PDO::ATTR_PERSISTENT ] );
+ }
+
$connection_options = array(
'journal_mode' => $options['journal_mode'] ?? null,
'synchronous' => $options['synchronous'] ?? null,
+ 'pdo_options' => $connection_pdo_options,
);
if ( isset( $options['pdo'] ) ) {
$connection_options['pdo'] = $options['pdo'];
@@ -706,6 +855,7 @@ public function __construct(
$this->mysql_version = $options['mysql_version'] ?? 80038;
$this->main_db_name = $db_name;
$this->db_name = $db_name;
+ $this->set_sql_modes( $this->get_default_sql_modes() );
// Check the database name.
if ( '' === $this->db_name ) {
@@ -791,6 +941,35 @@ function ( string $sql, array $params ) {
);
}
);
+
+ foreach ( $pdo_options as $attribute => $value ) {
+ $this->setAttribute( $attribute, $value );
+ }
+ }
+
+ /**
+ * PDO API: Prepare a MySQL statement for execution.
+ *
+ * Prepared statements are not implemented yet. Report the standard PDO
+ * unsupported-function diagnostic instead of using uninitialized parent
+ * PDO state.
+ *
+ * @param string $query The MySQL statement to prepare.
+ * @param array|null $options Optional statement options.
+ * @return PDOStatement|false False when exceptions are disabled.
+ *
+ * @throws WP_MySQL_On_SQLite_Exception When exception mode is enabled.
+ */
+ #[ReturnTypeWillChange]
+ public function prepare( $query, $options = null ) {
+ $driver_message = 'driver does not support prepared statements';
+ $exception = $this->new_driver_exception(
+ 'SQLSTATE[IM001]: Driver does not support this function: ' . $driver_message,
+ 'IM001',
+ null,
+ array( 'IM001', 0, $driver_message )
+ );
+ return $this->handle_pdo_error( $exception );
}
/**
@@ -805,7 +984,7 @@ function ( string $sql, array $params ) {
*
* @return PDOStatement|false PDO statement, or false when the fetch mode is invalid on PHP < 8.1.
*
- * @throws WP_SQLite_Driver_Exception When the query execution fails.
+ * @throws WP_MySQL_On_SQLite_Exception When the query execution fails.
*/
#[ReturnTypeWillChange]
public function query( string $query, ?int $fetch_mode = null, ...$fetch_mode_args ) {
@@ -836,11 +1015,6 @@ public function query( string $query, ?int $fetch_mode = null, ...$fetch_mode_ar
);
return false;
}
-
- // When the default FETCH_BOTH is not set explicitly, additional
- // arguments are ignored, and the argument count is not validated.
- $fetch_mode = $this->connection->get_pdo()->getAttribute( PDO::ATTR_DEFAULT_FETCH_MODE );
- $fetch_mode_args = array();
} elseif ( PDO::FETCH_COLUMN === $fetch_mode ) {
if ( 3 !== $arg_count ) {
throw new ArgumentCountError(
@@ -974,8 +1148,17 @@ public function query( string $query, ?int $fetch_mode = null, ...$fetch_mode_ar
$this->last_result_statement = $this->create_result_statement_from_data( array(), array() );
}
- $stmt = new WP_PDO_Proxy_Statement( $this->last_result_statement, $this->last_affected_rows );
- $stmt->setFetchMode( $fetch_mode, ...$fetch_mode_args );
+ $stmt = new WP_MySQL_On_SQLite_Statement(
+ $this->last_result_statement,
+ $query,
+ $this->create_column_meta_resolver( $this->last_column_meta ),
+ $this->last_affected_rows
+ );
+ if ( null !== $fetch_mode ) {
+ $stmt->setFetchMode( $fetch_mode, ...$fetch_mode_args );
+ }
+ $this->error_code = '00000';
+ $this->error_info = array( '00000', null, null );
return $stmt;
} catch ( Throwable $e ) {
try {
@@ -984,12 +1167,14 @@ public function query( string $query, ?int $fetch_mode = null, ...$fetch_mode_ar
} catch ( Throwable $rollback_exception ) {
// Ignore rollback errors.
}
- if ( $e instanceof WP_SQLite_Driver_Exception ) {
- throw $e;
- } elseif ( $e instanceof WP_SQLite_Information_Schema_Exception ) {
- throw $this->convert_information_schema_exception( $e );
+ if ( $e instanceof WP_SQLite_Information_Schema_Exception ) {
+ $e = $this->convert_information_schema_exception( $e );
+ }
+ if ( ! ( $e instanceof WP_MySQL_On_SQLite_Exception ) ) {
+ $e = $this->new_driver_exception( $e->getMessage(), $e->getCode(), $e );
}
- throw $this->new_driver_exception( $e->getMessage(), $e->getCode(), $e );
+
+ return $this->handle_pdo_error( $e );
} finally {
// A query that doesn't return any rows or fails sets found rows to 0.
if ( ! $this->is_readonly || isset( $e ) ) {
@@ -1006,19 +1191,139 @@ public function query( string $query, ?int $fetch_mode = null, ...$fetch_mode_ar
#[ReturnTypeWillChange]
public function exec( $query ) {
$stmt = $this->query( $query );
+ if ( false === $stmt ) {
+ return false;
+ }
return $stmt->rowCount();
}
+ /**
+ * PDO API: Return the ID of the last inserted row.
+ *
+ * @param string|null $name Optional sequence name. Ignored by SQLite.
+ * @return string|false The last insert ID, or false on failure.
+ */
+ #[ReturnTypeWillChange]
+ public function lastInsertId( $name = null ) {
+ if (
+ is_array( $name )
+ || is_resource( $name )
+ || ( is_object( $name ) && ! method_exists( $name, '__toString' ) )
+ ) {
+ if ( PHP_VERSION_ID >= 80000 ) {
+ throw new TypeError(
+ sprintf(
+ 'PDO::lastInsertId(): Argument #1 ($name) must be of type ?string, %s given',
+ get_debug_type( $name )
+ )
+ );
+ }
+ trigger_error(
+ sprintf( 'PDO::lastInsertId() expects parameter 1 to be string, %s given', strtolower( gettype( $name ) ) ),
+ E_USER_WARNING
+ );
+ return false;
+ }
+
+ return $this->last_insert_id;
+ }
+
+ /**
+ * PDO API: Fetch the SQLSTATE associated with the last operation.
+ *
+ * @return string|null The SQLSTATE error code, or null when unavailable.
+ */
+ public function errorCode(): ?string {
+ return $this->error_code;
+ }
+
+ /**
+ * PDO API: Fetch error information associated with the last operation.
+ *
+ * @return array Error information from the last PDO operation.
+ */
+ public function errorInfo(): array {
+ return $this->error_info;
+ }
+
+ /**
+ * PDO API: Quote a string for use in a MySQL query.
+ *
+ * @param string $string The string to quote.
+ * @param int $type The PDO parameter type.
+ * @return string The quoted string.
+ */
+ #[ReturnTypeWillChange]
+ public function quote( $string, $type = PDO::PARAM_STR ) {
+ // Mirror PDO\MySQL::quote() value validation.
+ if (
+ is_array( $string )
+ || is_resource( $string )
+ || ( is_object( $string ) && ! method_exists( $string, '__toString' ) )
+ ) {
+ $given_type = is_object( $string ) ? get_class( $string ) : gettype( $string );
+ throw new TypeError(
+ sprintf(
+ 'WP_MySQL_On_SQLite::quote(): Argument #1 ($string) must be of type string, %s given',
+ $given_type
+ )
+ );
+ }
+ $string = (string) $string;
+
+ // Handle binary and national character prefixes.
+ $prefix = '';
+ if ( PDO::PARAM_LOB === ( $type & PDO::PARAM_LOB ) ) {
+ $prefix = '_binary';
+ } elseif (
+ PDO::PARAM_STR_NATL === ( $type & PDO::PARAM_STR_NATL )
+ && PDO::PARAM_STR_CHAR !== ( $type & PDO::PARAM_STR_CHAR )
+ ) {
+ $prefix = 'N';
+ }
+
+ /*
+ * PDO uses mysqlnd by default and can alternatively use libmysqlclient.
+ * This escaped character mapping matches the escaping of both drivers.
+ * Their malformed multibyte sequence handling is not needed for UTF-8.
+ *
+ * @see https://github.com/php/php-src/blob/dd6e76cce27aaa0ed9f7520648ed1081dfb6af36/ext/mysqlnd/mysqlnd_charset.c#L905
+ * @see https://github.com/mysql/mysql-server/blob/dc86e412f18b36ce271f791026714e8caa0ec919/mysys/charset.cc#L413
+ *
+ * We can't use "addcslashes()" here, because it has an unusual handling
+ * of the ASCII NULL and Control+Z characters, escaping them to "\000"
+ * and "\032" instead of "\0" and "\Z", respectively.
+ *
+ * It is important to use "strtr()" and not "str_replace()", because
+ * "str_replace()" applies replacements one after another, modifying
+ * intermediate changes rather than just the original string:
+ *
+ * - str_replace( [ 'a', 'b' ], [ 'b', 'c' ], 'ab' ); // 'cc' (bad)
+ * - strtr( 'ab', [ 'a' => 'b', 'b' => 'c' ] ); // 'bc' (good)
+ */
+ $backslash = chr( 92 );
+ $replacements = array(
+ chr( 0 ) => $backslash . '0', // An ASCII NULL character (\0).
+ chr( 10 ) => $backslash . 'n', // A newline (linefeed) character (\n).
+ chr( 13 ) => $backslash . 'r', // A carriage return character (\r).
+ $backslash => $backslash . $backslash, // A backslash character (\).
+ "'" => $backslash . "'", // A single quote character (').
+ '"' => $backslash . '"', // A double quote character (").
+ chr( 26 ) => $backslash . 'Z', // An ASCII 26 (Control+Z) character.
+ );
+ return $prefix . "'" . strtr( $string, $replacements ) . "'";
+ }
+
/**
* PDO API: Begin a transaction.
*
* @return bool True on success, false on failure.
*/
- // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid
public function beginTransaction(): bool {
if ( $this->inTransaction() ) {
throw $this->new_driver_exception( 'There is already an active transaction' );
}
+ $this->flush();
$this->begin_user_transaction();
return true;
}
@@ -1032,6 +1337,7 @@ public function commit(): bool {
if ( ! $this->inTransaction() ) {
throw $this->new_driver_exception( 'There is no active transaction' );
}
+ $this->flush();
$this->commit_user_transaction();
return true;
}
@@ -1041,11 +1347,11 @@ public function commit(): bool {
*
* @return bool True on success, false on failure.
*/
- // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid
public function rollBack(): bool {
if ( ! $this->inTransaction() ) {
throw $this->new_driver_exception( 'There is no active transaction' );
}
+ $this->flush();
$this->rollback_user_transaction();
return true;
}
@@ -1055,7 +1361,6 @@ public function rollBack(): bool {
*
* @return bool True if a transaction is active, false otherwise.
*/
- // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid
public function inTransaction(): bool {
if ( PHP_VERSION_ID < 80400 ) {
/*
@@ -1081,7 +1386,29 @@ public function inTransaction(): bool {
* @return bool True on success, false on failure.
*/
public function setAttribute( $attribute, $value ): bool {
- return $this->connection->get_pdo()->setAttribute( $attribute, $value );
+ // PDO reserves IDs starting at 1000 for driver-specific attributes.
+ // These differ for MySQL and SQLite and are currently not supported.
+ if ( is_int( $attribute ) && 1000 <= $attribute ) {
+ return false;
+ }
+
+ // Track the caller's error mode while keeping internal SQLite operations in exception mode.
+ if ( PDO::ATTR_ERRMODE === $attribute ) {
+ $pdo = $this->connection->get_pdo();
+ $result = $pdo->setAttribute( $attribute, $value );
+ if ( ! $result ) {
+ return false;
+ }
+ $this->error_mode = $pdo->getAttribute( PDO::ATTR_ERRMODE );
+ $pdo->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
+ return true;
+ }
+
+ $result = $this->connection->get_pdo()->setAttribute( $attribute, $value );
+ if ( $result && PDO::ATTR_STRINGIFY_FETCHES === $attribute ) {
+ $this->stringify_fetches = (bool) $value;
+ }
+ return $result;
}
/**
@@ -1096,12 +1423,36 @@ public function setAttribute( $attribute, $value ): bool {
*/
#[ReturnTypeWillChange]
public function getAttribute( $attribute ) {
+ // Return the caller's error mode instead of the exception mode used internally.
+ if ( PDO::ATTR_ERRMODE === $attribute ) {
+ return $this->error_mode;
+ }
+ if ( PDO::ATTR_STRINGIFY_FETCHES === $attribute && PHP_VERSION_ID < 80200 ) {
+ // PDO SQLite cannot report this attribute before PHP 8.2.
+ return $this->stringify_fetches;
+ }
return $this->connection->get_pdo()->getAttribute( $attribute );
}
+ /**
+ * Get the underlying SQLite PDO instance.
+ *
+ * Exposes the SQLite PDO for advanced use, bypassing MySQL emulation.
+ * Do not retain it across reconnections or modify driver-owned state.
+ *
+ * @since 3.0.0
+ *
+ * @return PDO The underlying SQLite PDO instance.
+ */
+ public function get_sqlite_pdo(): PDO {
+ return $this->connection->get_pdo();
+ }
+
/**
* Get the SQLite connection instance.
*
+ * @access private
+ *
* @return WP_SQLite_Connection
*/
public function get_connection(): WP_SQLite_Connection {
@@ -1123,6 +1474,8 @@ public function get_sqlite_version(): string {
* The saved driver version corresponds to the latest version of the SQLite
* driver that was used to initialize and configure the SQLite database.
*
+ * @access private
+ *
* @return string SQLite driver version as a string.
* @throws PDOException When the query execution fails.
*/
@@ -1148,16 +1501,25 @@ public function get_saved_driver_version(): string {
/**
* Check if a specific SQL mode is active.
*
+ * @access private
+ *
* @param string $mode The SQL mode to check.
* @return bool True if the SQL mode is active, false otherwise.
*/
public function is_sql_mode_active( string $mode ): bool {
- return in_array( strtoupper( $mode ), $this->active_sql_modes, true );
+ $mode = strtoupper( $mode );
+ if ( 'NOT_USED' === $mode && $this->mysql_version < 80000 ) {
+ return false;
+ }
+ return isset( self::SQL_MODES[ $mode ] )
+ && ( $this->active_sql_modes & self::SQL_MODES[ $mode ] ) !== 0;
}
/**
* Get the last executed MySQL query.
*
+ * @access private
+ *
* @return string|null
*/
public function get_last_mysql_query(): ?string {
@@ -1167,28 +1529,19 @@ public function get_last_mysql_query(): ?string {
/**
* Get SQLite queries executed for the last MySQL query.
*
+ * @access private
+ *
* @return array{ sql: string, params: array }[]
*/
public function get_last_sqlite_queries(): array {
return $this->last_sqlite_queries;
}
- /**
- * Get the auto-increment value generated for the last query.
- *
- * @return int|string
- */
- public function get_insert_id() {
- $last_insert_id = $this->connection->get_last_insert_id();
- if ( is_numeric( $last_insert_id ) ) {
- $last_insert_id = (int) $last_insert_id;
- }
- return $last_insert_id;
- }
-
/**
* Tokenize a MySQL query and initialize a parser.
*
+ * @access private
+ *
* @param string $query The MySQL query to parse.
* @return WP_MySQL_Parser A parser initialized for the MySQL query.
*/
@@ -1196,7 +1549,7 @@ public function create_parser( string $query ): WP_MySQL_Parser {
$lexer = new WP_MySQL_Lexer(
$query,
80038,
- $this->active_sql_modes
+ $this->get_active_sql_mode_names()
);
$tokens = $lexer instanceof WP_MySQL_Native_Lexer
? $lexer->native_token_stream()
@@ -1221,192 +1574,229 @@ private function reset_or_create_parser( $tokens ): WP_MySQL_Parser {
}
/**
- * Get the number of columns returned by the last emulated query.
+ * Execute a query in SQLite.
+ *
+ * @access private
*
- * @return int
+ * @param string $sql The query to execute.
+ * @param array $params The query parameters.
+ * @throws PDOException When the query execution fails.
+ * @return PDOStatement The PDO statement object.
*/
- public function get_last_column_count(): int {
- return count( $this->last_column_meta );
+ public function execute_sqlite_query( string $sql, array $params = array() ): PDOStatement {
+ return $this->connection->query( $sql, $params );
}
/**
- * Get column metadata for results of the last emulated query.
+ * Create a lazy MySQL-compatible column metadata resolver.
*
- * @return array
+ * Only raw SQLite metadata and database context are snapshotted here.
+ * The INFORMATION_SCHEMA metadata is resolved lazily and may reflect
+ * schema changes made after statement execution. This is a trade-off
+ * that avoids schema queries when column metadata is not requested.
+ *
+ * @param array $raw_column_meta Raw SQLite result column metadata.
+ * @return callable The column metadata resolver.
*/
- public function get_last_column_meta(): array {
- // Build the column metadata as per "PDOStatement::getColumnMeta()".
- $column_meta = array();
- foreach ( $this->last_column_meta as $meta ) {
- $table = $meta['table'] ?? null;
- $name = $meta['name'];
- $type = strtoupper( $meta['sqlite:decl_type'] ?? $meta['native_type'] ?? '' );
-
- // When table is known, we can get data from the information schema.
- $column_info = null;
- if ( null !== $table ) {
- $table_is_temporary = $this->information_schema_builder->temporary_table_exists( $table );
- $columns_table = $this->information_schema_builder->get_table_name( $table_is_temporary, 'columns' );
- $column_info = $this->execute_sqlite_query(
- sprintf(
- '
- SELECT
- IS_NULLABLE,
- DATA_TYPE,
- COLUMN_TYPE,
- COLUMN_KEY,
- CHARACTER_MAXIMUM_LENGTH,
- NUMERIC_PRECISION,
- NUMERIC_SCALE
- FROM %s
- WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ? AND COLUMN_NAME = ?
- ',
- $this->quote_sqlite_identifier( $columns_table )
- ),
- array( $this->get_saved_db_name(), $table, $name )
- )->fetch( PDO::FETCH_ASSOC );
+ private function create_column_meta_resolver( array $raw_column_meta ): callable {
+ $db_name = $this->db_name;
+ return function ( $column ) use ( $raw_column_meta, $db_name ) {
+ if ( ! array_key_exists( $column, $raw_column_meta ) ) {
+ return false;
+ }
- if ( false === $column_info ) {
- $column_info = null;
- }
+ $last_sqlite_queries = $this->last_sqlite_queries;
+ try {
+ return $this->resolve_column_meta( $raw_column_meta[ $column ], $db_name );
+ } finally {
+ $this->last_sqlite_queries = $last_sqlite_queries;
}
+ };
+ }
- // If we have information schema data, we can use it.
- if ( null !== $column_info ) {
- $type_info = self::COLUMN_INFO_MYSQL_TO_NATIVE_TYPES_MAP[ $column_info['DATA_TYPE'] ] ?? null;
- if ( null === $type_info ) {
- $type_info = self::COLUMN_INFO_SQLITE_TO_NATIVE_TYPES_MAP[ $type ] ?? null;
- }
- $native_type = $type_info[0];
- $mysqli_type = $type_info[1];
- $len = $type_info[2];
- $precision = $type_info[3];
+ /**
+ * Resolve raw SQLite column metadata into MySQL-compatible metadata.
+ *
+ * @param array $meta Raw SQLite column metadata.
+ * @param string $db_name Database selected when the query was executed.
+ * @return array MySQL-compatible column metadata.
+ */
+ private function resolve_column_meta( array $meta, string $db_name ): array {
+ $table = $meta['table'] ?? null;
+ $name = $meta['name'];
+ $type = strtoupper( $meta['sqlite:decl_type'] ?? $meta['native_type'] ?? '' );
- if ( 'tinyint(1)' === $column_info['COLUMN_TYPE'] ) {
- $len = 1;
- }
+ // When table is known, we can get data from the information schema.
+ $column_info = null;
+ if ( null !== $table ) {
+ $table_is_temporary = $this->information_schema_builder->temporary_table_exists( $table );
+ $columns_table = $this->information_schema_builder->get_table_name( $table_is_temporary, 'columns' );
+ $column_info = $this->execute_sqlite_query(
+ sprintf(
+ '
+ SELECT
+ IS_NULLABLE,
+ DATA_TYPE,
+ COLUMN_TYPE,
+ COLUMN_KEY,
+ CHARACTER_MAXIMUM_LENGTH,
+ NUMERIC_PRECISION,
+ NUMERIC_SCALE
+ FROM %s
+ WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ? AND COLUMN_NAME = ?
+ ',
+ $this->quote_sqlite_identifier( $columns_table )
+ ),
+ array( $this->get_saved_db_name( $db_name ), $table, $name )
+ )->fetch( PDO::FETCH_ASSOC );
- if ( 'decimal' === $column_info['DATA_TYPE'] ) {
- $len = (int) $column_info['NUMERIC_PRECISION'] + (int) $column_info['NUMERIC_SCALE'];
- $precision = (int) $column_info['NUMERIC_SCALE'];
- }
+ if ( false === $column_info ) {
+ $column_info = null;
+ }
+ }
- if (
- str_contains( $column_info['COLUMN_TYPE'], 'unsigned' )
- && ! str_contains( $column_info['COLUMN_TYPE'], 'bigint' )
- ) {
- $len -= 1;
- }
+ // If we have information schema data, we can use it.
+ if ( null !== $column_info ) {
+ $type_info = self::COLUMN_INFO_MYSQL_TO_NATIVE_TYPES_MAP[ $column_info['DATA_TYPE'] ] ?? null;
+ if ( null === $type_info ) {
+ $type_info = self::COLUMN_INFO_SQLITE_TO_NATIVE_TYPES_MAP[ $type ] ?? null;
+ }
+ $native_type = $type_info[0];
+ $mysqli_type = $type_info[1];
+ $len = $type_info[2];
+ $precision = $type_info[3];
- // If set, lenght can be taken from the information schema.
- if ( isset( $column_info['CHARACTER_MAXIMUM_LENGTH'] ) ) {
- $len = (int) $column_info['CHARACTER_MAXIMUM_LENGTH'];
- }
+ if ( 'tinyint(1)' === $column_info['COLUMN_TYPE'] ) {
+ $len = 1;
+ }
- // For string types, the length is multiplied by the maximum number
- // of bytes per character for the used connection encoding. In our
- // case, it's always "utf8mb4" and therefore 4 bytes per character.
- if (
- str_contains( $column_info['DATA_TYPE'], 'text' )
- || str_contains( $column_info['DATA_TYPE'], 'char' )
- || 'enum' === $column_info['DATA_TYPE']
- || 'set' === $column_info['DATA_TYPE']
- ) {
- // Except for "longtext" - this might be a MySQL bug.
- if ( 'longtext' !== $column_info['DATA_TYPE'] ) {
- $len = 4 * $len;
- }
- }
+ if ( 'decimal' === $column_info['DATA_TYPE'] ) {
+ $len = (int) $column_info['NUMERIC_PRECISION'] + (int) $column_info['NUMERIC_SCALE'];
+ $precision = (int) $column_info['NUMERIC_SCALE'];
+ }
- // Flags.
- $flags = array();
- if ( 'NO' === $column_info['IS_NULLABLE'] ) {
- $flags[] = 'not_null';
- }
- if ( 'PRI' === $column_info['COLUMN_KEY'] ) {
- $flags[] = 'primary_key';
- } elseif ( 'UNI' === $column_info['COLUMN_KEY'] ) {
- $flags[] = 'unique_key';
- } elseif ( 'MUL' === $column_info['COLUMN_KEY'] ) {
- $flags[] = 'multiple_key';
- }
- } else {
- $type_info = self::COLUMN_INFO_SQLITE_TO_NATIVE_TYPES_MAP[ $type ];
- $native_type = $type_info[0];
- $mysqli_type = $type_info[1];
- $len = $type_info[2] ?? 0;
- $precision = $type_info[3];
-
- // Flags.
- $flags = array();
- if ( 'NULL' !== $type ) {
- $flags[] = 'not_null';
- }
+ if (
+ str_contains( $column_info['COLUMN_TYPE'], 'unsigned' )
+ && ! str_contains( $column_info['COLUMN_TYPE'], 'bigint' )
+ ) {
+ $len -= 1;
}
- if ( 'BLOB' === $native_type || 'GEOMETRY' === $native_type ) {
- $flags[] = 'blob';
+ // If set, length can be taken from the information schema.
+ if ( isset( $column_info['CHARACTER_MAXIMUM_LENGTH'] ) ) {
+ $len = (int) $column_info['CHARACTER_MAXIMUM_LENGTH'];
}
- // PDO type.
- if ( 'INT' === $type || 'INTEGER' === $type ) {
- $pdo_type = PDO::PARAM_INT;
- } else {
- $pdo_type = PDO::PARAM_STR;
+ // For string types, the length is multiplied by the maximum number
+ // of bytes per character for the used connection encoding. In our
+ // case, it's always "utf8mb4" and therefore 4 bytes per character.
+ if (
+ str_contains( $column_info['DATA_TYPE'], 'text' )
+ || str_contains( $column_info['DATA_TYPE'], 'char' )
+ || 'enum' === $column_info['DATA_TYPE']
+ || 'set' === $column_info['DATA_TYPE']
+ ) {
+ // Except for "longtext" - this might be a MySQL bug.
+ if ( 'longtext' !== $column_info['DATA_TYPE'] ) {
+ $len = 4 * $len;
+ }
}
- // MySQLi charset number.
- $is_string = 'STRING' === $type || 'TEXT' === $type;
- $is_binary = 'BLOB' === $type || 'GEOMETRY' === $native_type;
- $is_datetime = str_contains( $native_type, 'DATE' ) || str_contains( $native_type, 'TIME' ) || 'YEAR' === $native_type;
- if ( $is_string && ! $is_binary && ! $is_datetime ) {
- $mysqli_charsetnr = 255; // utf8mb4_0900_ai_ci
- } else {
- $mysqli_charsetnr = 63; // binary
+ // Flags.
+ $flags = array();
+ if ( 'NO' === $column_info['IS_NULLABLE'] ) {
+ $flags[] = 'not_null';
+ }
+ if ( 'PRI' === $column_info['COLUMN_KEY'] ) {
+ $flags[] = 'primary_key';
+ } elseif ( 'UNI' === $column_info['COLUMN_KEY'] ) {
+ $flags[] = 'unique_key';
+ } elseif ( 'MUL' === $column_info['COLUMN_KEY'] ) {
+ $flags[] = 'multiple_key';
+ }
+ } else {
+ $type_info = self::COLUMN_INFO_SQLITE_TO_NATIVE_TYPES_MAP[ $type ];
+ $native_type = $type_info[0];
+ $mysqli_type = $type_info[1];
+ $len = $type_info[2] ?? 0;
+ $precision = $type_info[3];
+
+ // Flags.
+ $flags = array();
+ if ( 'NULL' !== $type ) {
+ $flags[] = 'not_null';
}
+ }
- $column_meta[] = array(
- 'native_type' => $native_type,
- 'pdo_type' => $pdo_type,
- 'flags' => $flags,
- 'table' => $meta['table'] ?? '',
- 'name' => $meta['name'],
- 'len' => $len,
- 'precision' => $precision,
- 'sqlite:decl_type' => $meta['sqlite:decl_type'] ?? '',
+ if ( 'BLOB' === $native_type || 'GEOMETRY' === $native_type ) {
+ $flags[] = 'blob';
+ }
- /*
- * The MySQLi PHP extension exposes more MySQL column metadata than PDO.
- * We'll add the data here for use cases such as "wpdb::get_col_info()".
- */
- 'mysqli:orgname' => $meta['name'], // TODO: Use correct original name when alias is used.
- 'mysqli:orgtable' => $meta['table'] ?? '', // TODO: Use correct original name when table alias is used.
- 'mysqli:db' => $this->db_name, // TODO: Use correct DB for queries to information schema.
- 'mysqli:charsetnr' => $mysqli_charsetnr,
- 'mysqli:flags' => 0, // TODO: We can compute correct MySQL flags.
- 'mysqli:type' => $mysqli_type,
- );
+ // PDO type.
+ if ( 'INT' === $type || 'INTEGER' === $type ) {
+ $pdo_type = PDO::PARAM_INT;
+ } else {
+ $pdo_type = PDO::PARAM_STR;
}
- return $column_meta;
+
+ // MySQLi charset number.
+ $is_string = 'STRING' === $type || 'TEXT' === $type;
+ $is_binary = 'BLOB' === $type || 'GEOMETRY' === $native_type;
+ $is_datetime = str_contains( $native_type, 'DATE' ) || str_contains( $native_type, 'TIME' ) || 'YEAR' === $native_type;
+ if ( $is_string && ! $is_binary && ! $is_datetime ) {
+ $mysqli_charsetnr = 255; // utf8mb4_0900_ai_ci
+ } else {
+ $mysqli_charsetnr = 63; // binary
+ }
+
+ return array(
+ 'native_type' => $native_type,
+ 'pdo_type' => $pdo_type,
+ 'flags' => $flags,
+ 'table' => $meta['table'] ?? '',
+ 'name' => $meta['name'],
+ 'len' => $len,
+ 'precision' => $precision,
+ 'sqlite:decl_type' => $meta['sqlite:decl_type'] ?? '',
+
+ /*
+ * The MySQLi PHP extension exposes more MySQL column metadata than PDO.
+ * We'll add the data here for use cases such as "wpdb::get_col_info()".
+ */
+ 'mysqli:orgname' => $meta['name'], // TODO: Use correct original name when alias is used.
+ 'mysqli:orgtable' => $meta['table'] ?? '', // TODO: Use correct original name when table alias is used.
+ 'mysqli:db' => $db_name, // TODO: Use correct DB for queries to information schema.
+ 'mysqli:charsetnr' => $mysqli_charsetnr,
+ 'mysqli:flags' => 0, // TODO: We can compute correct MySQL flags.
+ 'mysqli:type' => $mysqli_type,
+ );
}
/**
- * Execute a query in SQLite.
+ * Record and report a PDO operation error according to the configured mode.
*
- * @param string $sql The query to execute.
- * @param array $params The query parameters.
- * @throws PDOException When the query execution fails.
- * @return PDOStatement The PDO statement object.
+ * @param WP_MySQL_On_SQLite_Exception $exception The operation error.
+ * @return false Always false when the error is not thrown.
+ * @throws WP_MySQL_On_SQLite_Exception When exception mode is enabled.
*/
- public function execute_sqlite_query( string $sql, array $params = array() ): PDOStatement {
- return $this->connection->query( $sql, $params );
+ private function handle_pdo_error( WP_MySQL_On_SQLite_Exception $exception ): bool {
+ $this->error_info = $exception->errorInfo;
+ $this->error_code = $this->error_info[0];
+
+ if ( PDO::ERRMODE_EXCEPTION === $this->error_mode ) {
+ throw $exception;
+ }
+ if ( PDO::ERRMODE_WARNING === $this->error_mode ) {
+ trigger_error( $exception->getMessage(), E_USER_WARNING );
+ }
+ return false;
}
/**
* Translate and execute a MySQL query in SQLite.
*
* @param WP_Parser_Node $node The "query" AST node with "simpleStatement" child.
- * @throws WP_SQLite_Driver_Exception When the query is not supported.
+ * @throws WP_MySQL_On_SQLite_Exception When the query is not supported.
*/
private function execute_mysql_query( WP_Parser_Node $node ): void {
if ( 'query' !== $node->rule_name ) {
@@ -1705,7 +2095,7 @@ private function rollback_user_transaction(): void {
* Execute a MySQL transaction or locking statement in SQLite.
*
* @param WP_Parser_Node $node The "transactionOrLockingStatement" AST node.
- * @throws WP_SQLite_Driver_Exception When the query execution fails.
+ * @throws WP_MySQL_On_SQLite_Exception When the query execution fails.
*/
private function execute_transaction_or_locking_statement( WP_Parser_Node $node ): void {
$subnode = $node->get_first_child_node();
@@ -1822,7 +2212,7 @@ private function execute_transaction_or_locking_statement( WP_Parser_Node $node
* Translate and execute a MySQL SELECT statement in SQLite.
*
* @param WP_Parser_Node $node The "selectStatement" AST node.
- * @throws WP_SQLite_Driver_Exception When the query execution fails.
+ * @throws WP_MySQL_On_SQLite_Exception When the query execution fails.
*/
private function execute_select_statement( WP_Parser_Node $node ): void {
/*
@@ -1902,7 +2292,7 @@ private function execute_select_statement( WP_Parser_Node $node ): void {
* Translate and execute a MySQL INSERT or REPLACE statement in SQLite.
*
* @param WP_Parser_Node $node The "insertStatement" or "replaceStatement" AST node.
- * @throws WP_SQLite_Driver_Exception When the query execution fails.
+ * @throws WP_MySQL_On_SQLite_Exception When the query execution fails.
*/
private function execute_insert_or_replace_statement( WP_Parser_Node $node ): void {
$parts = array();
@@ -2019,17 +2409,19 @@ function ( $column ) {
throw $e;
}
}
+ $this->last_insert_id = $this->connection->get_last_insert_id();
return;
}
$this->last_result_statement = $this->execute_sqlite_query( $query );
+ $this->last_insert_id = $this->connection->get_last_insert_id();
}
/**
* Translate and execute a MySQL UPDATE statement in SQLite.
*
* @param WP_Parser_Node $node The "updateStatement" AST node.
- * @throws WP_SQLite_Driver_Exception When the query execution fails.
+ * @throws WP_MySQL_On_SQLite_Exception When the query execution fails.
*/
private function execute_update_statement( WP_Parser_Node $node ): void {
// @TODO: Add support for UPDATE with multiple tables and JOINs.
@@ -2147,10 +2539,24 @@ private function execute_update_statement( WP_Parser_Node $node ): void {
)->fetchAll( PDO::FETCH_COLUMN );
}
- $matched_tables = array_merge( $matched_temporary_tables, $matched_persistent_tables );
- $updates_multiple_tables = count( $matched_tables ) > 1;
- if ( 1 === count( $matched_tables ) ) {
- $table_or_alias = $matched_tables[0];
+ $matched_tables = array_merge( $matched_temporary_tables, $matched_persistent_tables );
+ $matched_aliases = array();
+ foreach ( $table_alias_map as $alias => $data ) {
+ // Derived tables do not have a table name.
+ if ( null === $data['table_name'] ) {
+ continue;
+ }
+
+ foreach ( $matched_tables as $matched_table ) {
+ if ( 0 === strcasecmp( $data['table_name'], $matched_table ) ) {
+ $matched_aliases[] = $alias;
+ break;
+ }
+ }
+ }
+ $updates_multiple_tables = count( $matched_aliases ) > 1;
+ if ( 1 === count( $matched_aliases ) ) {
+ $table_or_alias = $matched_aliases[0];
} else {
break;
}
@@ -2265,7 +2671,7 @@ private function execute_update_statement( WP_Parser_Node $node ): void {
* Translate and execute a MySQL DELETE statement in SQLite.
*
* @param WP_Parser_Node $node The "deleteStatement" AST node.
- * @throws WP_SQLite_Driver_Exception When the query execution fails.
+ * @throws WP_MySQL_On_SQLite_Exception When the query execution fails.
*/
private function execute_delete_statement( WP_Parser_Node $node ): void {
/*
@@ -2406,7 +2812,7 @@ private function execute_delete_statement( WP_Parser_Node $node ): void {
* Translate and execute a MySQL CREATE TABLE statement in SQLite.
*
* @param WP_Parser_Node $node The "createStatement" AST node with "createTable" child.
- * @throws WP_SQLite_Driver_Exception When the query execution fails.
+ * @throws WP_MySQL_On_SQLite_Exception When the query execution fails.
*/
private function execute_create_table_statement( WP_Parser_Node $node ): void {
$subnode = $node->get_first_child_node();
@@ -2477,7 +2883,7 @@ private function execute_create_table_statement( WP_Parser_Node $node ): void {
* Translate and execute a MySQL ALTER TABLE statement in SQLite.
*
* @param WP_Parser_Node $node The "alterStatement" AST node with "alterTable" child.
- * @throws WP_SQLite_Driver_Exception When the query execution fails.
+ * @throws WP_MySQL_On_SQLite_Exception When the query execution fails.
*/
private function execute_alter_table_statement( WP_Parser_Node $node ): void {
$table_ref = $node->get_first_descendant_node( 'tableRef' );
@@ -2565,7 +2971,7 @@ private function execute_alter_table_statement( WP_Parser_Node $node ): void {
* Translate and execute a MySQL DROP TABLE statement in SQLite.
*
* @param WP_Parser_Node $node The "dropStatement" AST node with "dropTable" child.
- * @throws WP_SQLite_Driver_Exception When the query execution fails.
+ * @throws WP_MySQL_On_SQLite_Exception When the query execution fails.
*/
private function execute_drop_table_statement( WP_Parser_Node $node ): void {
// Record the changes in the information schema.
@@ -2617,7 +3023,7 @@ private function execute_drop_table_statement( WP_Parser_Node $node ): void {
* Translate and execute a MySQL TRUNCATE TABLE statement in SQLite.
*
* @param WP_Parser_Node $node The "truncateTableStatement" AST node.
- * @throws WP_SQLite_Driver_Exception When the query execution fails.
+ * @throws WP_MySQL_On_SQLite_Exception When the query execution fails.
*/
private function execute_truncate_table_statement( WP_Parser_Node $node ): void {
$table_ref = $node->get_first_child_node( 'tableRef' );
@@ -2648,7 +3054,7 @@ private function execute_truncate_table_statement( WP_Parser_Node $node ): void
* Translate and execute a MySQL CREATE INDEX statement in SQLite.
*
* @param WP_Parser_Node $node The "createStatement" AST node with "createIndex" child.
- * @throws WP_SQLite_Driver_Exception When the query execution fails.
+ * @throws WP_MySQL_On_SQLite_Exception When the query execution fails.
*/
private function execute_create_index_statement( WP_Parser_Node $node ): void {
$create_index = $node->get_first_child_node( 'createIndex' );
@@ -2706,7 +3112,7 @@ private function execute_create_index_statement( WP_Parser_Node $node ): void {
* Translate and execute a MySQL DROP INDEX statement in SQLite.
*
* @param WP_Parser_Node $node The "dropStatement" AST node with "dropIndex" child.
- * @throws WP_SQLite_Driver_Exception When the query execution fails.
+ * @throws WP_MySQL_On_SQLite_Exception When the query execution fails.
*/
private function execute_drop_index_statement( WP_Parser_Node $node ): void {
$drop_index = $node->get_first_child_node( 'dropIndex' );
@@ -2747,7 +3153,7 @@ private function execute_drop_index_statement( WP_Parser_Node $node ): void {
* Translate and execute a MySQL SHOW statement in SQLite.
*
* @param WP_Parser_Node $node The "showStatement" AST node.
- * @throws WP_SQLite_Driver_Exception When the query execution fails.
+ * @throws WP_MySQL_On_SQLite_Exception When the query execution fails.
*/
private function execute_show_statement( WP_Parser_Node $node ): void {
$tokens = $node->get_child_tokens();
@@ -3045,7 +3451,7 @@ private function execute_show_index_statement( WP_Parser_Node $node ): void {
* Translate and execute a MySQL SHOW TABLE STATUS statement in SQLite.
*
* @param WP_Parser_Node $node The "showStatement" AST node.
- * @throws WP_SQLite_Driver_Exception When the query execution fails.
+ * @throws WP_MySQL_On_SQLite_Exception When the query execution fails.
*/
private function execute_show_table_status_statement( WP_Parser_Node $node ): void {
// FROM/IN database.
@@ -3132,7 +3538,7 @@ private function execute_show_table_status_statement( WP_Parser_Node $node ): vo
* Translate and execute a MySQL SHOW TABLES statement in SQLite.
*
* @param WP_Parser_Node $node The "showStatement" AST node.
- * @throws WP_SQLite_Driver_Exception When the query execution fails.
+ * @throws WP_MySQL_On_SQLite_Exception When the query execution fails.
*/
private function execute_show_tables_statement( WP_Parser_Node $node ): void {
// FROM/IN database.
@@ -3182,7 +3588,7 @@ private function execute_show_tables_statement( WP_Parser_Node $node ): void {
* Translate and execute a MySQL SHOW COLUMNS statement in SQLite.
*
* @param WP_Parser_Node $node The "showStatement" AST node.
- * @throws WP_SQLite_Driver_Exception When the query execution fails.
+ * @throws WP_MySQL_On_SQLite_Exception When the query execution fails.
* @throws PDOException When given table doesn't exist.
*/
private function execute_show_columns_statement( WP_Parser_Node $node ): void {
@@ -3277,7 +3683,7 @@ private function execute_show_columns_statement( WP_Parser_Node $node ): void {
* Translate and execute a MySQL DESCRIBE statement in SQLite.
*
* @param WP_Parser_Node $node The "describeStatement" AST node.
- * @throws WP_SQLite_Driver_Exception When the query execution fails.
+ * @throws WP_MySQL_On_SQLite_Exception When the query execution fails.
*/
private function execute_describe_statement( WP_Parser_Node $node ): void {
$table_ref = $node->get_first_child_node( 'tableRef' );
@@ -3316,7 +3722,7 @@ private function execute_describe_statement( WP_Parser_Node $node ): void {
* Translate and execute a MySQL USE statement in SQLite.
*
* @param WP_Parser_Node $node The "useStatement" AST node.
- * @throws WP_SQLite_Driver_Exception When the query execution fails.
+ * @throws WP_MySQL_On_SQLite_Exception When the query execution fails.
*/
private function execute_use_statement( WP_Parser_Node $node ): void {
$database_name = $this->unquote_sqlite_identifier(
@@ -3341,7 +3747,7 @@ private function execute_use_statement( WP_Parser_Node $node ): void {
* Translate and execute a MySQL SET statement in SQLite.
*
* @param WP_Parser_Node $node The "setStatement" AST node.
- * @throws WP_SQLite_Driver_Exception When the query execution fails.
+ * @throws WP_MySQL_On_SQLite_Exception When the query execution fails.
*/
private function execute_set_statement( WP_Parser_Node $node ): void {
/*
@@ -3454,7 +3860,7 @@ private function execute_set_statement( WP_Parser_Node $node ): void {
* @param WP_Parser_Node $value_node The "setExprOrDefault" AST node.
* @param int $default_type The currently active default variable type.
* One of the SESSION, GLOBAL, PERSIST, PERSIST_ONLY tokens.
- * @throws WP_SQLite_Driver_Exception When the query execution fails.
+ * @throws WP_MySQL_On_SQLite_Exception When the query execution fails.
*/
private function execute_set_system_variable_statement(
WP_Parser_Node $set_var_node,
@@ -3518,14 +3924,26 @@ private function execute_set_system_variable_statement(
* SET updatable_views_with_limit = false; SELECT @@updatable_views_with_limit; -> NO
*/
$lowercase_value = null === $value ? null : strtolower( $value );
- if ( 'on' === $lowercase_value || 'off' === $lowercase_value ) {
+ if ( 'sql_mode' !== $name && ( 'on' === $lowercase_value || 'off' === $lowercase_value ) ) {
$value = 'on' === $lowercase_value ? 1 : 0;
}
if ( WP_MySQL_Lexer::SESSION_SYMBOL === $type ) {
if ( 'sql_mode' === $name ) {
- $modes = explode( ',', strtoupper( $value ) );
- $this->active_sql_modes = $modes;
+ if ( null !== $value_node->get_first_child_token( WP_MySQL_Lexer::DEFAULT_SYMBOL ) ) {
+ $sql_modes = $this->get_default_sql_modes();
+ } elseif ( is_string( $value ) ) {
+ /*
+ * MySQL removes spaces only from the end of the complete value.
+ * Spaces within individual mode names remain invalid.
+ *
+ * See: https://github.com/mysql/mysql-server/blob/8.4/sql/strfunc.cc#L39-L64
+ */
+ $sql_modes = explode( ',', rtrim( $value, ' ' ) );
+ } else {
+ $sql_modes = $value;
+ }
+ $this->set_sql_modes( $sql_modes );
} else {
$this->session_system_variables[ $name ] = $value;
}
@@ -3545,7 +3963,7 @@ private function execute_set_system_variable_statement(
*
* @param WP_Parser_Node $user_variable The "userVariable" AST node.
* @param WP_Parser_Node $expr The "expr" AST node.
- * @throws WP_SQLite_Driver_Exception When the query execution fails.
+ * @throws WP_MySQL_On_SQLite_Exception When the query execution fails.
*/
private function execute_set_user_variable_statement(
WP_Parser_Node $user_variable,
@@ -3570,7 +3988,7 @@ private function execute_set_user_variable_statement(
* - REPAIR TABLE
*
* @param WP_Parser_Node $node A "tableAdministrationStatement" AST node.
- * @throws WP_SQLite_Driver_Exception When the query execution fails.
+ * @throws WP_MySQL_On_SQLite_Exception When the query execution fails.
*/
private function execute_administration_statement( WP_Parser_Node $node ): void {
$first_token = $node->get_first_child_token();
@@ -3697,7 +4115,7 @@ private function execute_administration_statement( WP_Parser_Node $node ): void
* @param WP_Parser_Node $node The "expr" AST node.
* @return mixed The value of the expression.
*/
- public function evaluate_expression( WP_Parser_Node $node ) {
+ private function evaluate_expression( WP_Parser_Node $node ) {
// To support expressions, we'll use a SQLite query.
$stmt = $this->execute_sqlite_query(
sprintf( 'SELECT %s', $this->translate( $node ) )
@@ -3721,7 +4139,7 @@ public function evaluate_expression( WP_Parser_Node $node ) {
*
* @param WP_Parser_Node|WP_MySQL_Token $node The AST node to translate.
* @return string|null The translated query fragment.
- * @throws WP_SQLite_Driver_Exception When the translation fails.
+ * @throws WP_MySQL_On_SQLite_Exception When the translation fails.
*/
private function translate( $node ): ?string {
if ( null === $node ) {
@@ -3876,7 +4294,7 @@ private function translate( $node ): ?string {
$name = strtolower( $original_name );
$type = $type_token ? $type_token->id : WP_MySQL_Lexer::SESSION_SYMBOL;
if ( 'sql_mode' === $name ) {
- $value = implode( ',', $this->active_sql_modes );
+ $value = implode( ',', $this->get_active_sql_mode_names() );
} elseif ( 'version' === $name ) {
$version = (string) $this->mysql_version;
$value = sprintf(
@@ -4040,7 +4458,7 @@ private function translate_token( WP_MySQL_Token $token ): ?string {
* @param array $nodes The MySQL token to translate.
* @param string $separator The separator to use between fragments.
* @return string|null 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_sequence( array $nodes, string $separator = ' ' ): ?string {
$parts = array();
@@ -4146,7 +4564,7 @@ private function translate_pure_identifier( WP_Parser_Node $node ): string {
* (table, view, procedure, trigger, etc.).
* @param WP_Parser_Node|null $child_node An identifier node representing an object child name (column, index, 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_qualified_identifier(
?WP_Parser_Node $schema_node,
@@ -4194,7 +4612,7 @@ private function translate_qualified_identifier(
*
* @param WP_Parser_Node $node The "queryExpression" AST node.
* @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_query_expression( WP_Parser_Node $node ): string {
// Get the query expression subnode under which we need to look for the
@@ -4269,7 +4687,7 @@ private function translate_query_expression( WP_Parser_Node $node ): string {
*
* @param WP_Parser_Node $node The "querySpecification" AST node.
* @return string The translated value.
- * @throws WP_SQLite_Driver_Exception When the translation fails.
+ * @throws WP_MySQL_On_SQLite_Exception When the translation fails.
* @return string|null
*/
private function translate_query_specification( WP_Parser_Node $node ): string {
@@ -4355,7 +4773,7 @@ private function translate_query_specification( WP_Parser_Node $node ): string {
*
* @param WP_Parser_Node $node The "simpleExprBody" AST node.
* @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_simple_expr_body( WP_Parser_Node $node ): string {
$token = $node->get_first_child_token();
@@ -4437,7 +4855,7 @@ private function translate_cast_expr( WP_Parser_Node $expr, WP_Parser_Node $cast
*
* @param WP_Parser_Node $node The "predicateOperations" AST node.
* @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_like( WP_Parser_Node $node ): string {
$tokens = $node->get_descendant_tokens();
@@ -4481,7 +4899,7 @@ private function translate_like( WP_Parser_Node $node ): string {
*
* @param WP_Parser_Node $node The "predicateOperations" AST node.
* @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_regexp_functions( WP_Parser_Node $node ): string {
$tokens = $node->get_descendant_tokens();
@@ -4511,7 +4929,7 @@ private function translate_regexp_functions( WP_Parser_Node $node ): string {
*
* @param WP_Parser_Node $node The "runtimeFunctionCall" AST node.
* @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_runtime_function_call( WP_Parser_Node $node ): string {
$child = $node->get_first_child();
@@ -4563,7 +4981,7 @@ private function translate_runtime_function_call( WP_Parser_Node $node ): string
*
* @param WP_Parser_Node $node The "functionCall" AST node.
* @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_function_call( WP_Parser_Node $node ): string {
$nodes = $node->get_child_nodes();
@@ -4785,7 +5203,7 @@ private function translate_datetime_literal( string $value ): string {
* @param WP_Parser_Node $node The "selectItem" AST node.
* @return string The translated expression.
*/
- public function translate_select_item( WP_Parser_Node $node ): string {
+ private function translate_select_item( WP_Parser_Node $node ): string {
/*
* First, let's translate the select item subtree.
*
@@ -4899,9 +5317,9 @@ public function translate_select_item( WP_Parser_Node $node ): string {
*
* @param WP_Parser_Node $node The "tableRef" AST node.
* @return string The translated value.
- * @throws WP_SQLite_Driver_Exception When the translation fails.
+ * @throws WP_MySQL_On_SQLite_Exception When the translation fails.
*/
- public function translate_table_ref( WP_Parser_Node $node ): string {
+ private function translate_table_ref( WP_Parser_Node $node ): string {
// The table reference is in "." or "" format.
$parts = $node->get_descendant_nodes( 'identifier' );
$table = array_pop( $parts );
@@ -5022,7 +5440,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 +5608,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' );
@@ -5682,6 +6100,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 +6753,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,
@@ -7019,6 +7602,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 +7708,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 +7731,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,21 +7743,37 @@ 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 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'
@@ -7192,52 +7794,55 @@ 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_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..60792103b 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.
@@ -92,8 +95,13 @@ public function __construct( array $options ) {
if ( ! isset( $options['path'] ) || ! is_string( $options['path'] ) ) {
throw new InvalidArgumentException( 'Option "path" is required when "connection" 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.
@@ -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..dacb0c759 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,6 +3,9 @@
/*
* 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
*/
/**
@@ -10,13 +13,15 @@
* (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 {
/**
@@ -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,10 +66,11 @@ 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,
@@ -65,7 +78,7 @@ public function __construct(
int $mysql_version = 80038
) {
$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(
@@ -74,10 +87,9 @@ public function __construct(
'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,7 +256,7 @@ 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();
}
@@ -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..b113f293b 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 {
/**
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..7b7e62920 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.
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..bc860c571 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
@@ -14,6 +14,8 @@
* Currently, it reconstructs schema infromation 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' );
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..54dfae7fa 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
@@ -19,6 +19,8 @@
*
* 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 {
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..f59d71322
--- /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..ada0cf207 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
@@ -56,6 +56,22 @@ public function __construct( $dbname ) {
$this->charset = 'utf8mb4';
}
+ /**
+ * Returns the active MySQL-on-SQLite driver.
+ *
+ * @since 3.0.0
+ *
+ * @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 +87,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 +188,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 +304,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 +418,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.
@@ -314,10 +470,7 @@ public function db_connect( $allow_bail = true ) {
$options = array(
'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 +480,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 SQLite PDO instance 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 +590,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 +617,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 +708,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,21 +732,22 @@ 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 );
}
/**
@@ -592,9 +766,13 @@ public function db_version() {
/**
* Returns the version of the SQLite engine.
*
- * @return string SQLite engine version as a string.
+ * @return string SQLite engine version, or an empty string while disconnected.
*/
public function db_server_info() {
+ if ( ! $this->dbh ) {
+ return '';
+ }
+
return $this->dbh->get_sqlite_version();
}
@@ -647,7 +825,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 +833,8 @@ private function format_error_message( Throwable $e ) {
$output = '
' . PHP_EOL;
// Queries.
- if ( $e instanceof WP_SQLite_Driver_Exception ) {
- $driver = $e->getDriver();
+ if ( $e instanceof WP_MySQL_On_SQLite_Exception ) {
+ $driver = $e->get_driver();
$output .= '' . PHP_EOL;
$output .= '
MySQL query:
' . PHP_EOL;
diff --git a/wp/wp-content/plugins/sqlite-database-integration/wp-includes/sqlite/db.php b/wp/wp-content/plugins/sqlite-database-integration/wp-includes/sqlite/db.php
index 033135152..7ede214af 100644
--- a/wp/wp-content/plugins/sqlite-database-integration/wp-includes/sqlite/db.php
+++ b/wp/wp-content/plugins/sqlite-database-integration/wp-includes/sqlite/db.php
@@ -51,21 +51,8 @@
require_once __DIR__ . '/class-wp-sqlite-db.php';
require_once __DIR__ . '/install-functions.php';
-$db_name = defined( 'DB_NAME' ) ? DB_NAME : '';
+$db_name = defined( 'DB_NAME' ) ? DB_NAME : '';
+$GLOBALS['wpdb'] = new WP_SQLite_DB( $db_name );
-/*
- * Debug: Cross-check with MySQL.
- * This is for debugging purpose only and requires files
- * that are present in the GitHub repository
- * but not the plugin published on WordPress.org.
- */
-$crosscheck_tests_file_path = __DIR__ . '/class-wp-sqlite-crosscheck-db.php';
-if ( defined( 'SQLITE_DEBUG_CROSSCHECK' ) && SQLITE_DEBUG_CROSSCHECK && file_exists( $crosscheck_tests_file_path ) ) {
- require_once $crosscheck_tests_file_path;
- $GLOBALS['wpdb'] = new WP_SQLite_Crosscheck_DB( $db_name );
-} else {
- $GLOBALS['wpdb'] = new WP_SQLite_DB( $db_name );
-
- // Boot the Query Monitor plugin if it is active.
- require_once __DIR__ . '/../../integrations/query-monitor/boot.php';
-}
+// Boot the Query Monitor plugin if it is active.
+require_once __DIR__ . '/../../integrations/query-monitor/boot.php';
diff --git a/wp/wp-content/plugins/sqlite-database-integration/wp-includes/sqlite/install-functions.php b/wp/wp-content/plugins/sqlite-database-integration/wp-includes/sqlite/install-functions.php
index 89d1fc1ad..e849fe181 100644
--- a/wp/wp-content/plugins/sqlite-database-integration/wp-includes/sqlite/install-functions.php
+++ b/wp/wp-content/plugins/sqlite-database-integration/wp-includes/sqlite/install-functions.php
@@ -68,53 +68,6 @@ function sqlite_make_db_sqlite() {
wp_die( $message, 'Database Error!' );
}
- /*
- * Debug: Cross-check with MySQL.
- * This is for debugging purpose only and requires files
- * that are present in the GitHub repository
- * but not the plugin published on WordPress.org.
- */
- if ( defined( 'SQLITE_DEBUG_CROSSCHECK' ) && SQLITE_DEBUG_CROSSCHECK ) {
- $host = DB_HOST;
- $port = 3306;
- if ( str_contains( $host, ':' ) ) {
- $host_parts = explode( ':', $host );
- $host = $host_parts[0];
- $port = $host_parts[1];
- }
- $dsn = 'mysql:host=' . $host . '; port=' . $port . '; dbname=' . DB_NAME;
- $pdo_class = PHP_VERSION_ID >= 80400 ? PDO\MySQL::class : PDO::class; // phpcs:ignore WordPress.DB.RestrictedClasses.mysql__PDO
- $pdo_mysql = new $pdo_class( $dsn, DB_USER, DB_PASSWORD, array( PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION ) ); // phpcs:ignore WordPress.DB.RestrictedClasses.mysql__PDO
- $pdo_mysql->query( 'SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";' );
- $pdo_mysql->query( 'SET time_zone = "+00:00";' );
- foreach ( $queries as $query ) {
- $query = trim( $query );
- if ( empty( $query ) ) {
- continue;
- }
- try {
- $pdo_mysql->beginTransaction();
- $pdo_mysql->query( $query );
- } catch ( PDOException $err ) {
- $err_data = $err->errorInfo; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
- $err_code = $err_data[1];
- // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual
- if ( 5 == $err_code || 6 == $err_code ) {
- // If the database is locked, commit again.
- $pdo_mysql->commit();
- } else {
- $pdo_mysql->rollBack();
- $message = sprintf(
- 'Error occurred while creating tables or indexes...
Query was: %s
',
- var_export( $query, true )
- );
- $message .= sprintf( 'Error message is: %s', $err_data[2] );
- wp_die( $message, 'Database Error!' );
- }
- }
- }
- }
-
return true;
}
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 .= '';
+ $comment_text = post_password_required( $comment->comment_post_ID )
+ ? __( 'Password protected' )
+ : get_comment_text( $comment );
+ $list_items_markup .= '';
} elseif ( 'excerpt' === $display_content ) {
$list_items_markup .= '';
}
diff --git a/wp/wp-includes/blocks/post-content.php b/wp/wp-includes/blocks/post-content.php
index 0be1a8730..b75d7144d 100644
--- a/wp/wp-includes/blocks/post-content.php
+++ b/wp/wp-includes/blocks/post-content.php
@@ -56,8 +56,18 @@ function render_block_core_post_content( $attributes, $content, $block ) {
$tag_name = 'div';
- if ( ! empty( $attributes['tagName'] ) && tag_escape( $attributes['tagName'] ) === $attributes['tagName'] ) {
- $tag_name = $attributes['tagName'];
+ if ( isset( $attributes['tagName'] ) && is_string( $attributes['tagName'] ) ) {
+ /**
+ * The allowed tag names match the options offered in the editor.
+ *
+ * @see packages/block-library/src/post-content/edit.js
+ */
+ $allowed_tag_names = array( 'div', 'main', 'section', 'article' );
+ $normalized_tag_name = strtolower( $attributes['tagName'] );
+
+ if ( in_array( $normalized_tag_name, $allowed_tag_names, true ) ) {
+ $tag_name = $normalized_tag_name;
+ }
}
$wrapper_attributes = get_block_wrapper_attributes( array( 'class' => 'entry-content' ) );
diff --git a/wp/wp-includes/blocks/post-date.php b/wp/wp-includes/blocks/post-date.php
index a8073dd84..4e6e9e52f 100644
--- a/wp/wp-includes/blocks/post-date.php
+++ b/wp/wp-includes/blocks/post-date.php
@@ -81,10 +81,10 @@ function render_block_core_post_date( $attributes, $content, $block ) {
$wrapper_attributes = get_block_wrapper_attributes( array( 'class' => implode( ' ', $classes ) ) );
- $time_tag = sprintf( '
', $unformatted_date, $formatted_date );
+ $time_tag = sprintf( '
', esc_attr( $unformatted_date ), esc_html( $formatted_date ) );
if ( isset( $attributes['isLink'] ) && $attributes['isLink'] && isset( $block->context['postId'] ) ) {
- $time_tag = sprintf( '
%2s', get_the_permalink( $block->context['postId'] ), $time_tag );
+ $time_tag = sprintf( '
%2$s', esc_url( get_the_permalink( $block->context['postId'] ) ), $time_tag );
}
return sprintf( '
%2$s
', $wrapper_attributes, $time_tag );
diff --git a/wp/wp-includes/canonical.php b/wp/wp-includes/canonical.php
index 9315ba7fb..ff0e773b0 100644
--- a/wp/wp-includes/canonical.php
+++ b/wp/wp-includes/canonical.php
@@ -985,7 +985,7 @@ function redirect_guess_404_permalink() {
if ( empty( $post_types ) ) {
return false;
}
- $where .= " AND post_type IN ('" . join( "', '", esc_sql( get_query_var( 'post_type' ) ) ) . "')";
+ $where .= " AND post_type IN ('" . join( "', '", esc_sql( $post_types ) ) . "')";
} else {
if ( ! in_array( get_query_var( 'post_type' ), $publicly_viewable_post_types, true ) ) {
return false;
diff --git a/wp/wp-includes/class-wp-query.php b/wp/wp-includes/class-wp-query.php
index 854b5506f..b60ea9cf3 100644
--- a/wp/wp-includes/class-wp-query.php
+++ b/wp/wp-includes/class-wp-query.php
@@ -2815,11 +2815,11 @@ public function get_posts() {
if ( $this->is_comment_feed && ! $this->is_singular ) {
if ( $this->is_archive || $this->is_search ) {
$cjoin = "JOIN {$wpdb->posts} ON ( {$wpdb->comments}.comment_post_ID = {$wpdb->posts}.ID ) $join ";
- $cwhere = "WHERE comment_approved = '1' $where";
+ $cwhere = "WHERE comment_approved = '1' AND {$wpdb->comments}.comment_type != 'note' $where";
$cgroupby = "{$wpdb->comments}.comment_id";
} else { // Other non-singular, e.g. front.
$cjoin = "JOIN {$wpdb->posts} ON ( {$wpdb->comments}.comment_post_ID = {$wpdb->posts}.ID )";
- $cwhere = "WHERE ( post_status = 'publish' OR ( post_status = 'inherit' AND post_type = 'attachment' ) ) AND comment_approved = '1'";
+ $cwhere = "WHERE ( post_status = 'publish' OR ( post_status = 'inherit' AND post_type = 'attachment' ) ) AND comment_approved = '1' AND {$wpdb->comments}.comment_type != 'note'";
$cgroupby = '';
}
@@ -3477,7 +3477,7 @@ public function get_posts() {
$cjoin = apply_filters_ref_array( 'comment_feed_join', array( '', &$this ) );
/** This filter is documented in wp-includes/class-wp-query.php */
- $cwhere = apply_filters_ref_array( 'comment_feed_where', array( "WHERE comment_post_ID = '{$this->posts[0]->ID}' AND comment_approved = '1'", &$this ) );
+ $cwhere = apply_filters_ref_array( 'comment_feed_where', array( "WHERE comment_post_ID = '{$this->posts[0]->ID}' AND comment_approved = '1' AND {$wpdb->comments}.comment_type != 'note'", &$this ) );
/** This filter is documented in wp-includes/class-wp-query.php */
$cgroupby = apply_filters_ref_array( 'comment_feed_groupby', array( '', &$this ) );
diff --git a/wp/wp-includes/class-wp-script-modules.php b/wp/wp-includes/class-wp-script-modules.php
index 154c95151..9c9f8a762 100644
--- a/wp/wp-includes/class-wp-script-modules.php
+++ b/wp/wp-includes/class-wp-script-modules.php
@@ -1058,15 +1058,16 @@ public function print_script_module_data(): void {
* The data for a given Script Module, if provided, will be JSON serialized in a script
* tag with an ID of the form `wp-script-module-data-{$module_id}`.
*
- * The data can be read on the client with a pattern like this:
+ * The data can be read on the client with a pattern like the following; you are encouraged
+ * to use this pattern _verbatim_ to avoid common pitfalls or vulnerabilities:
*
* Example:
*
- * const dataContainer = document.getElementById( 'wp-script-module-data-MyScriptModuleID' );
+ * const dataContainer = document.querySelector( 'script[id="wp-script-module-data-MyScriptModuleID"]' );
* let data = {};
- * if ( dataContainer ) {
+ * if ( dataContainer instanceof HTMLScriptElement ) {
* try {
- * data = JSON.parse( dataContainer.textContent );
+ * data = JSON.parse( dataContainer.text );
* } catch {}
* }
* // data.dataForClient === 'ok';
diff --git a/wp/wp-includes/http.php b/wp/wp-includes/http.php
index 8280f4249..67da47cd4 100644
--- a/wp/wp-includes/http.php
+++ b/wp/wp-includes/http.php
@@ -595,9 +595,30 @@ function wp_http_validate_url( $url ) {
}
if ( $ip ) {
$parts = array_map( 'intval', explode( '.', $ip ) );
- if ( 127 === $parts[0] || 10 === $parts[0] || 0 === $parts[0]
- || ( 172 === $parts[0] && 16 <= $parts[1] && 31 >= $parts[1] )
- || ( 192 === $parts[0] && 168 === $parts[1] )
+
+ /*
+ * These IP address ranges are not considered valid external hosts for HTTP requests.
+ *
+ * If the host resolves to an IP address in these ranges, the request will be rejected unless the 'http_request_host_is_external' filter allows it.
+ *
+ * References:
+ *
+ * - IPv4 Special-Purpose Address Space: https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml
+ * - IPv4 Multicast Address Assignments: https://www.rfc-editor.org/rfc/rfc5771.html
+ */
+ if ( 127 === $parts[0] || 10 === $parts[0] || 0 === $parts[0] // 127.0.0.0/8 (loopback), 10.0.0.0/8 (private), 0.0.0.0/8 (this network).
+ || ( 172 === $parts[0] && 16 <= $parts[1] && 31 >= $parts[1] ) // 172.16.0.0/12 (private).
+ || ( 192 === $parts[0] && 168 === $parts[1] ) // 192.168.0.0/16 (private).
+ || ( 192 === $parts[0] && 0 === $parts[1] && 0 === $parts[2] ) // 192.0.0.0/24 (IETF protocol assignments).
+ || ( 192 === $parts[0] && 0 === $parts[1] && 2 === $parts[2] ) // 192.0.2.0/24 (TEST-NET-1).
+ || ( 192 === $parts[0] && 88 === $parts[1] && 99 === $parts[2] ) // 192.88.99.0/24 (6to4 relay anycast).
+ || ( 198 === $parts[0] && 51 === $parts[1] && 100 === $parts[2] ) // 198.51.100.0/24 (TEST-NET-2).
+ || ( 203 === $parts[0] && 0 === $parts[1] && 113 === $parts[2] ) // 203.0.113.0/24 (TEST-NET-3).
+ || ( 169 === $parts[0] && 254 === $parts[1] ) // 169.254.0.0/16 (link-local and cloud metadata).
+ || ( 100 === $parts[0] && 64 <= $parts[1] && 127 >= $parts[1] ) // 100.64.0.0/10 (CGNAT).
+ || ( 198 === $parts[0] && 18 <= $parts[1] && 19 >= $parts[1] ) // 198.18.0.0/15 (benchmarking).
+ || ( 224 <= $parts[0] && 239 >= $parts[0] ) // 224.0.0.0/4 (multicast).
+ || 240 <= $parts[0] // 240.0.0.0/4 (reserved, includes 255.255.255.255 broadcast).
) {
// If host appears local, reject unless specifically allowed.
/**
diff --git a/wp/wp-includes/js/wp-emoji-loader.js b/wp/wp-includes/js/wp-emoji-loader.js
index 561d3656a..488210767 100644
--- a/wp/wp-includes/js/wp-emoji-loader.js
+++ b/wp/wp-includes/js/wp-emoji-loader.js
@@ -16,9 +16,12 @@
* @property {?string} source.wpemoji
*/
-const settings = /** @type {WPEmojiSettings} */ (
- JSON.parse( document.getElementById( 'wp-emoji-settings' ).textContent )
-);
+const selector = 'script#wp-emoji-settings';
+const script = document.querySelector( selector );
+if ( ! ( script instanceof HTMLScriptElement ) ) {
+ throw new Error( `Element missing: ${ selector }`);
+}
+const settings = /** @type {WPEmojiSettings} */ ( JSON.parse( script.text ) );
// For compatibility with other scripts that read from this global, in particular wp-includes/js/wp-emoji.js (source file: js/_enqueues/wp/emoji.js).
window._wpemojiSettings = settings;
diff --git a/wp/wp-includes/js/wp-emoji-loader.min.js b/wp/wp-includes/js/wp-emoji-loader.min.js
index b0cf072e3..e11c598b2 100644
--- a/wp/wp-includes/js/wp-emoji-loader.min.js
+++ b/wp/wp-includes/js/wp-emoji-loader.min.js
@@ -1,2 +1,2 @@
/*! This file is auto-generated */
-const a=JSON.parse(document.getElementById("wp-emoji-settings").textContent),o=(window._wpemojiSettings=a,"wpEmojiSettingsSupports"),s=["flag","emoji"];function i(e){try{var t={supportTests:e,timestamp:(new Date).valueOf()};sessionStorage.setItem(o,JSON.stringify(t))}catch(e){}}function c(e,t,n){e.clearRect(0,0,e.canvas.width,e.canvas.height),e.fillText(t,0,0);t=new Uint32Array(e.getImageData(0,0,e.canvas.width,e.canvas.height).data);e.clearRect(0,0,e.canvas.width,e.canvas.height),e.fillText(n,0,0);const a=new Uint32Array(e.getImageData(0,0,e.canvas.width,e.canvas.height).data);return t.every((e,t)=>e===a[t])}function p(e,t){e.clearRect(0,0,e.canvas.width,e.canvas.height),e.fillText(t,0,0);var n=e.getImageData(16,16,1,1);for(let e=0;e
{s[e]=t(o,e,n,a)}),s}function r(e){var t=document.createElement("script");t.src=e,t.defer=!0,document.head.appendChild(t)}a.supports={everything:!0,everythingExceptFlag:!0},new Promise(t=>{let n=function(){try{var e=JSON.parse(sessionStorage.getItem(o));if("object"==typeof e&&"number"==typeof e.timestamp&&(new Date).valueOf(){i(n=e.data),r.terminate(),t(n)})}catch(e){}i(n=f(s,u,c,p))}t(n)}).then(e=>{for(const n in e)a.supports[n]=e[n],a.supports.everything=a.supports.everything&&a.supports[n],"flag"!==n&&(a.supports.everythingExceptFlag=a.supports.everythingExceptFlag&&a.supports[n]);var t;a.supports.everythingExceptFlag=a.supports.everythingExceptFlag&&!a.supports.flag,a.supports.everything||((t=a.source||{}).concatemoji?r(t.concatemoji):t.wpemoji&&t.twemoji&&(r(t.twemoji),r(t.wpemoji)))});
\ No newline at end of file
+var e="script#wp-emoji-settings",t=document.querySelector(e);if(!(t instanceof HTMLScriptElement))throw new Error("Element missing: "+e);const r=JSON.parse(t.text),s=(window._wpemojiSettings=r,"wpEmojiSettingsSupports"),o=["flag","emoji"];function i(e){try{var t={supportTests:e,timestamp:(new Date).valueOf()};sessionStorage.setItem(s,JSON.stringify(t))}catch(e){}}function c(e,t,n){e.clearRect(0,0,e.canvas.width,e.canvas.height),e.fillText(t,0,0);t=new Uint32Array(e.getImageData(0,0,e.canvas.width,e.canvas.height).data);e.clearRect(0,0,e.canvas.width,e.canvas.height),e.fillText(n,0,0);const r=new Uint32Array(e.getImageData(0,0,e.canvas.width,e.canvas.height).data);return t.every((e,t)=>e===r[t])}function p(e,t){e.clearRect(0,0,e.canvas.width,e.canvas.height),e.fillText(t,0,0);var n=e.getImageData(16,16,1,1);for(let e=0;e{o[e]=t(s,e,n,r)}),o}function a(e){var t=document.createElement("script");t.src=e,t.defer=!0,document.head.appendChild(t)}r.supports={everything:!0,everythingExceptFlag:!0},new Promise(t=>{let n=function(){try{var e=JSON.parse(sessionStorage.getItem(s));if("object"==typeof e&&"number"==typeof e.timestamp&&(new Date).valueOf(){i(n=e.data),a.terminate(),t(n)})}catch(e){}i(n=f(o,u,c,p))}t(n)}).then(e=>{for(const n in e)r.supports[n]=e[n],r.supports.everything=r.supports.everything&&r.supports[n],"flag"!==n&&(r.supports.everythingExceptFlag=r.supports.everythingExceptFlag&&r.supports[n]);var t;r.supports.everythingExceptFlag=r.supports.everythingExceptFlag&&!r.supports.flag,r.supports.everything||((t=r.source||{}).concatemoji?a(t.concatemoji):t.wpemoji&&t.twemoji&&(a(t.twemoji),a(t.wpemoji)))});
\ No newline at end of file
diff --git a/wp/wp-includes/kses.php b/wp/wp-includes/kses.php
index a45d1697e..bf02737ec 100644
--- a/wp/wp-includes/kses.php
+++ b/wp/wp-includes/kses.php
@@ -2856,11 +2856,16 @@ function safecss_filter_attr( $css, $deprecated = '' ) {
$css_test_string
);
+ // Bail if the recursive function stripping hit a PCRE error (e.g. stack/backtrack limit).
+ if ( null === $css_test_string ) {
+ continue;
+ }
+
/*
* Disallow CSS containing \ ( & } = or comments, except for within url(), var(), calc(), etc.
* which were removed from the test string above.
*/
- $allow_css = ! preg_match( '%[\\\(&=}]|/\*%', $css_test_string );
+ $allow_css = 0 === preg_match( '%[\\\(&=}]|/\*%', $css_test_string );
/**
* Filters the check for unsafe CSS in `safecss_filter_attr`.
diff --git a/wp/wp-includes/user.php b/wp/wp-includes/user.php
index 9c635f63d..360049a90 100644
--- a/wp/wp-includes/user.php
+++ b/wp/wp-includes/user.php
@@ -186,7 +186,7 @@ function wp_authenticate_username_password(
sprintf(
/* translators: %s: User name. */
__( 'Error: The username %s is not registered on this site. If you are unsure of your username, try your email address instead.' ),
- $username
+ esc_html( $username )
)
);
}
@@ -213,7 +213,7 @@ function wp_authenticate_username_password(
sprintf(
/* translators: %s: User name. */
__( 'Error: The password you entered for the username %s is incorrect.' ),
- '' . $username . ''
+ '' . esc_html( $username ) . ''
) .
' ' .
__( 'Lost your password?' ) .
@@ -296,7 +296,7 @@ function wp_authenticate_email_password(
sprintf(
/* translators: %s: Email address. */
__( 'Error: The password you entered for the email address %s is incorrect.' ),
- '' . $email . ''
+ '' . esc_html( $email ) . ''
) .
' ' .
__( 'Lost your password?' ) .
@@ -3568,7 +3568,7 @@ function register_new_user( $user_login, $user_email ) {
sprintf(
/* translators: %s: Link to the login page. */
__( 'Error: This email address is already registered. Log in with this address or choose another one.' ),
- wp_login_url()
+ esc_url( wp_login_url() )
)
);
}
@@ -3616,7 +3616,7 @@ function register_new_user( $user_login, $user_email ) {
sprintf(
/* translators: %s: Admin email address. */
__( 'Error: Could not register you… please contact the site admin!' ),
- get_option( 'admin_email' )
+ esc_attr( get_option( 'admin_email' ) )
)
);
return $errors;
@@ -3839,18 +3839,26 @@ function _wp_get_current_user() {
*
* @since 3.0.0
* @since 4.9.0 This function was moved from wp-admin/includes/ms.php so it's no longer Multisite specific.
+ * @since 7.0.3 Added the `$user_id` parameter, which is sent with the `personal_options_update` action.
+ *
+ * @param int $user_id Optional. The ID of the user whose email is being changed. Defaults to `$_POST['user_id']` if set, otherwise 0.
*
* @global WP_Error $errors WP_Error object.
*/
-function send_confirmation_on_profile_email() {
+function send_confirmation_on_profile_email( $user_id = 0 ) {
global $errors;
+ // Maintain backward compatibility for those relying on a check based on $_POST['user_id'].
+ if ( ! $user_id && isset( $_POST['user_id'] ) ) {
+ $user_id = absint( $_POST['user_id'] );
+ }
+
$current_user = wp_get_current_user();
if ( ! is_object( $errors ) ) {
$errors = new WP_Error();
}
- if ( $current_user->ID !== (int) $_POST['user_id'] ) {
+ if ( 0 === $current_user->ID || $current_user->ID !== (int) $user_id ) {
return false;
}
@@ -3864,6 +3872,7 @@ function send_confirmation_on_profile_email() {
)
);
+ $_POST['email'] = addslashes( $current_user->user_email );
return;
}
@@ -3877,6 +3886,7 @@ function send_confirmation_on_profile_email() {
);
delete_user_meta( $current_user->ID, '_new_email' );
+ $_POST['email'] = addslashes( $current_user->user_email );
return;
}
diff --git a/wp/wp-includes/version.php b/wp/wp-includes/version.php
index 7c6ce2a28..2c57bf92b 100644
--- a/wp/wp-includes/version.php
+++ b/wp/wp-includes/version.php
@@ -16,7 +16,7 @@
*
* @global string $wp_version
*/
-$wp_version = '7.0.2';
+$wp_version = '7.0.3';
/**
* Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.
diff --git a/wp/wp-login.php b/wp/wp-login.php
index abedea82c..f72d1c738 100644
--- a/wp/wp-login.php
+++ b/wp/wp-login.php
@@ -1220,7 +1220,7 @@ function wp_login_viewport_meta() {
sprintf(
/* translators: %s: Link to the login page. */
__( 'Check your email for the confirmation link, then visit the login page.' ),
- wp_login_url()
+ esc_url( wp_login_url() )
),
'message'
);
@@ -1230,7 +1230,7 @@ function wp_login_viewport_meta() {
sprintf(
/* translators: %s: Link to the login page. */
__( 'Registration complete. Please check your email, then visit the login page.' ),
- wp_login_url()
+ esc_url( wp_login_url() )
),
'message'
);
diff --git a/wp/wp-signup.php b/wp/wp-signup.php
index 50ab624bb..05b7fa221 100644
--- a/wp/wp-signup.php
+++ b/wp/wp-signup.php
@@ -999,7 +999,11 @@ function signup_get_available_languages() {
}
break;
case 'gimmeanotherblog':
- validate_another_blog_signup();
+ if ( 'all' === $active_signup || 'blog' === $active_signup ) {
+ validate_another_blog_signup();
+ } else {
+ _e( 'Site registration has been disabled.' );
+ }
break;
case 'default':
default: