remove_placeholder_escape( (string) esc_sql( $value ) ) . "'"; } /** * What the session reads as sql_mode now, remembered per connection until * SessionSqlMode changes it. * * @var \WeakMap<\wpdb, string>|null */ private static ?\WeakMap $session_modes = null; /** Tells the helper the session mode changed; $mode is the new one, or null to read it again. */ public static function session_mode_changed( \wpdb $wpdb, ?string $mode = null ): void { self::$session_modes ??= new \WeakMap(); if ( null === $mode ) { unset( self::$session_modes[ $wpdb ] ); return; } self::$session_modes[ $wpdb ] = $mode; } /** * esc_sql() escapes for the session it runs in. Under NO_BACKSLASH_ESCAPES it * only doubles quotes, but the importer and the restorer on the other side * read a backslash as an escape, so a value ending in one would close its * quote there and let the rest run as SQL. * * @throws \RuntimeException When the session does not treat a backslash as an escape. */ private static function assert_backslash_escapes( \wpdb $wpdb ): void { self::$session_modes ??= new \WeakMap(); if ( ! isset( self::$session_modes[ $wpdb ] ) ) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- server metadata, nothing to cache self::$session_modes[ $wpdb ] = (string) ( $wpdb->get_var( 'SELECT @@SESSION.sql_mode' ) ?? '' ); } if ( str_contains( strtoupper( self::$session_modes[ $wpdb ] ), 'NO_BACKSLASH_ESCAPES' ) ) { throw new \RuntimeException( 'Refused to write an SQL literal: the session runs with NO_BACKSLASH_ESCAPES, so quotes would be escaped the way the receiving side does not read them.' ); } } /** A table or column name in backticks, with any backtick in it doubled. */ public static function identifier( string $name ): string { return '`' . str_replace( '`', '``', $name ) . '`'; } /** * A comma separated column list. * * @param string[] $names Column names. */ public static function identifiers( array $names ): string { return implode( ', ', array_map( [ self::class, 'identifier' ], $names ) ); } }