diff --git a/packages/php-wasm/node/src/test/php.spec.ts b/packages/php-wasm/node/src/test/php.spec.ts index 8d16254490..f805413257 100644 --- a/packages/php-wasm/node/src/test/php.spec.ts +++ b/packages/php-wasm/node/src/test/php.spec.ts @@ -2166,10 +2166,10 @@ bar1 it('Should be able to use intl functions', async () => { const response = await php.run({ code: `format(100.00); - $formatter = new NumberFormatter('fr-FR', NumberFormatter::CURRENCY); - echo $formatter->format(100.00); + $formatter = numfmt_create('en-US', NumberFormatter::CURRENCY); + echo numfmt_format($formatter, 100.00); + $formatter = numfmt_create('fr-FR', NumberFormatter::CURRENCY); + echo numfmt_format($formatter, 100.00); ?>`, }); expect(response.text).toEqual('$100.00100,00\xA0€'); diff --git a/packages/php-wasm/web/vite.config.ts b/packages/php-wasm/web/vite.config.ts index 4d245cab91..bbe3151e3e 100644 --- a/packages/php-wasm/web/vite.config.ts +++ b/packages/php-wasm/web/vite.config.ts @@ -121,7 +121,7 @@ export default defineConfig(({ command }) => { // the preserve-php-loaders-imports plugin above. external: [ /php_\d_\d.js$/, - /icudt74l\.js$/, + /icudt74l.js$/, ...getExternalModules(), ], }, diff --git a/packages/playground/blueprints/public/blueprint-schema-validator.js b/packages/playground/blueprints/public/blueprint-schema-validator.js index 50409fc09a..308f4930fd 100644 --- a/packages/playground/blueprints/public/blueprint-schema-validator.js +++ b/packages/playground/blueprints/public/blueprint-schema-validator.js @@ -74,6 +74,7 @@ const schema11 = { features: { type: 'object', properties: { + intl: { type: 'boolean' }, networking: { type: 'boolean', description: @@ -155,18 +156,7 @@ const schema11 = { }, SupportedPHPVersion: { type: 'string', - enum: [ - '8.4', - '8.3', - '8.2', - '8.1', - '8.0', - '7.4', - '7.3', - '7.2', - '7.1', - '7.0', - ], + enum: ['8.4', '8.3', '8.2', '8.1', '8.0', '7.4', '7.3', '7.2'], }, ExtraLibrary: { type: 'string', const: 'wp-cli' }, PHPConstants: { @@ -473,6 +463,13 @@ const schema11 = { $ref: '#/definitions/FileReference', description: 'The file to import', }, + importer: { + type: 'string', + enum: ['data-liberation', 'default'], + description: + 'The importer to use. Possible values:\n\n- `default`: The importer from https://github.com/humanmade/WordPress-Importer\n- `data-liberation`: The experimental Data Liberation WXR importer developed at https://github.com/WordPress/wordpress-playground/issues/1894\n\nThis option is deprecated. The syntax will not be removed, but once the Data Liberation importer matures, it will become the only supported importer and the `importer` option will be ignored.', + deprecated: true, + }, }, required: ['file', 'step'], }, @@ -560,7 +557,7 @@ const schema11 = { }, pluginZipFile: { $ref: '#/definitions/FileReference', - deprecated: '. Use `pluginData` instead.', + deprecated: ". Use 'pluginData' instead.", }, options: { $ref: '#/definitions/InstallPluginOptions', @@ -602,7 +599,7 @@ const schema11 = { }, themeZipFile: { $ref: '#/definitions/FileReference', - deprecated: '. Use `themeData` instead.', + deprecated: ". Use 'themeData' instead.", }, options: { $ref: '#/definitions/InstallThemeOptions', @@ -997,7 +994,8 @@ const schema11 = { }, filesTree: { $ref: '#/definitions/DirectoryReference', - description: 'The data to write', + description: + "The 'filesTree' defines the directory structure, supporting 'literal:directory' or 'git:directory' types. The 'name' represents the root directory, while 'files' is an object where keys are file paths, and values contain either file content as a string or nested objects for subdirectories.", }, }, required: ['filesTree', 'step', 'writeToPath'], @@ -1413,6 +1411,7 @@ const schema12 = { features: { type: 'object', properties: { + intl: { type: 'boolean' }, networking: { type: 'boolean', description: @@ -1491,18 +1490,7 @@ const schema12 = { }; const schema13 = { type: 'string', - enum: [ - '8.4', - '8.3', - '8.2', - '8.1', - '8.0', - '7.4', - '7.3', - '7.2', - '7.1', - '7.0', - ], + enum: ['8.4', '8.3', '8.2', '8.1', '8.0', '7.4', '7.3', '7.2'], }; const schema14 = { type: 'string', const: 'wp-cli' }; const schema15 = { @@ -3358,6 +3346,13 @@ const schema23 = { $ref: '#/definitions/FileReference', description: 'The file to import', }, + importer: { + type: 'string', + enum: ['data-liberation', 'default'], + description: + 'The importer to use. Possible values:\n\n- `default`: The importer from https://github.com/humanmade/WordPress-Importer\n- `data-liberation`: The experimental Data Liberation WXR importer developed at https://github.com/WordPress/wordpress-playground/issues/1894\n\nThis option is deprecated. The syntax will not be removed, but once the Data Liberation importer matures, it will become the only supported importer and the `importer` option will be ignored.', + deprecated: true, + }, }, required: ['file', 'step'], }, @@ -3444,7 +3439,7 @@ const schema23 = { }, pluginZipFile: { $ref: '#/definitions/FileReference', - deprecated: '. Use `pluginData` instead.', + deprecated: ". Use 'pluginData' instead.", }, options: { $ref: '#/definitions/InstallPluginOptions', @@ -3485,7 +3480,7 @@ const schema23 = { }, themeZipFile: { $ref: '#/definitions/FileReference', - deprecated: '. Use `themeData` instead.', + deprecated: ". Use 'themeData' instead.", }, options: { $ref: '#/definitions/InstallThemeOptions', @@ -3861,7 +3856,8 @@ const schema23 = { }, filesTree: { $ref: '#/definitions/DirectoryReference', - description: 'The data to write', + description: + "The 'filesTree' defines the directory structure, supporting 'literal:directory' or 'git:directory' types. The 'name' represents the root directory, while 'files' is an object where keys are file paths, and values contain either file content as a string or nested objects for subdirectories.", }, }, required: ['filesTree', 'step', 'writeToPath'], @@ -10245,6 +10241,38 @@ function validate14( ]; return false; } + if ( + !( + data40 === + 'data-liberation' || + data40 === + 'default' + ) + ) { + validate14.errors = + [ + { + instancePath: + instancePath + + '/importer', + schemaPath: + '#/oneOf/6/properties/importer/enum', + keyword: + 'enum', + params: { + allowedValues: + schema23 + .oneOf[6] + .properties + .importer + .enum, + }, + message: + 'must be equal to one of the allowed values', + }, + ]; + return false; + } var valid21 = _errs109 === errors; } else { @@ -19495,9 +19523,7 @@ function validate11( data9 === '8.0' || data9 === '7.4' || data9 === '7.3' || - data9 === '7.2' || - data9 === '7.1' || - data9 === '7.0' + data9 === '7.2' ) ) { const err1 = { @@ -19697,7 +19723,12 @@ function validate11( ) { const _errs33 = errors; for (const key3 in data11) { - if (!(key3 === 'networking')) { + if ( + !( + key3 === 'intl' || + key3 === 'networking' + ) + ) { validate11.errors = [ { instancePath: @@ -19720,21 +19751,19 @@ function validate11( } } if (_errs33 === errors) { - if ( - data11.networking !== - undefined - ) { + if (data11.intl !== undefined) { + const _errs34 = errors; if ( - typeof data11.networking !== + typeof data11.intl !== 'boolean' ) { validate11.errors = [ { instancePath: instancePath + - '/features/networking', + '/features/intl', schemaPath: - '#/properties/features/properties/networking/type', + '#/properties/features/properties/intl/type', keyword: 'type', params: { type: 'boolean', @@ -19745,6 +19774,45 @@ function validate11( ]; return false; } + var valid6 = + _errs34 === errors; + } else { + var valid6 = true; + } + if (valid6) { + if ( + data11.networking !== + undefined + ) { + const _errs36 = errors; + if ( + typeof data11.networking !== + 'boolean' + ) { + validate11.errors = + [ + { + instancePath: + instancePath + + '/features/networking', + schemaPath: + '#/properties/features/properties/networking/type', + keyword: + 'type', + params: { + type: 'boolean', + }, + message: + 'must be boolean', + }, + ]; + return false; + } + var valid6 = + _errs36 === errors; + } else { + var valid6 = true; + } } } } else { @@ -19769,21 +19837,21 @@ function validate11( } if (valid0) { if (data.extraLibraries !== undefined) { - let data13 = data.extraLibraries; - const _errs36 = errors; - if (errors === _errs36) { - if (Array.isArray(data13)) { + let data14 = data.extraLibraries; + const _errs38 = errors; + if (errors === _errs38) { + if (Array.isArray(data14)) { var valid7 = true; - const len1 = data13.length; + const len1 = data14.length; for ( let i1 = 0; i1 < len1; i1++ ) { - let data14 = data13[i1]; - const _errs38 = errors; + let data15 = data14[i1]; + const _errs40 = errors; if ( - typeof data14 !== + typeof data15 !== 'string' ) { validate11.errors = [ @@ -19804,7 +19872,7 @@ function validate11( ]; return false; } - if ('wp-cli' !== data14) { + if ('wp-cli' !== data15) { validate11.errors = [ { instancePath: @@ -19826,7 +19894,7 @@ function validate11( return false; } var valid7 = - _errs38 === errors; + _errs40 === errors; if (!valid7) { break; } @@ -19850,34 +19918,34 @@ function validate11( return false; } } - var valid0 = _errs36 === errors; + var valid0 = _errs38 === errors; } else { var valid0 = true; } if (valid0) { if (data.constants !== undefined) { - let data15 = data.constants; - const _errs41 = errors; - const _errs42 = errors; - if (errors === _errs42) { + let data16 = data.constants; + const _errs43 = errors; + const _errs44 = errors; + if (errors === _errs44) { if ( - data15 && - typeof data15 == 'object' && - !Array.isArray(data15) + data16 && + typeof data16 == 'object' && + !Array.isArray(data16) ) { - for (const key4 in data15) { - let data16 = - data15[key4]; - const _errs45 = errors; + for (const key4 in data16) { + let data17 = + data16[key4]; + const _errs47 = errors; if ( - typeof data16 !== + typeof data17 !== 'string' && - typeof data16 !== + typeof data17 !== 'boolean' && !( - typeof data16 == + typeof data17 == 'number' && - isFinite(data16) + isFinite(data17) ) ) { validate11.errors = @@ -19911,7 +19979,7 @@ function validate11( return false; } var valid10 = - _errs45 === errors; + _errs47 === errors; if (!valid10) { break; } @@ -19935,35 +20003,35 @@ function validate11( return false; } } - var valid0 = _errs41 === errors; + var valid0 = _errs43 === errors; } else { var valid0 = true; } if (valid0) { if (data.plugins !== undefined) { - let data17 = data.plugins; - const _errs47 = errors; - if (errors === _errs47) { - if (Array.isArray(data17)) { + let data18 = data.plugins; + const _errs49 = errors; + if (errors === _errs49) { + if (Array.isArray(data18)) { var valid11 = true; const len2 = - data17.length; + data18.length; for ( let i2 = 0; i2 < len2; i2++ ) { - let data18 = - data17[i2]; - const _errs49 = + let data19 = + data18[i2]; + const _errs51 = errors; - const _errs50 = + const _errs52 = errors; let valid12 = false; - const _errs51 = + const _errs53 = errors; if ( - typeof data18 !== + typeof data19 !== 'string' ) { const err5 = { @@ -19996,24 +20064,24 @@ function validate11( errors++; } var _valid1 = - _errs51 === + _errs53 === errors; valid12 = valid12 || _valid1; if (!valid12) { - const _errs53 = + const _errs55 = errors; if ( !validate12( - data18, + data19, { instancePath: instancePath + '/plugins/' + i2, parentData: - data17, + data18, parentDataProperty: i2, rootData, @@ -20031,7 +20099,7 @@ function validate11( vErrors.length; } var _valid1 = - _errs53 === + _errs55 === errors; valid12 = valid12 || @@ -20069,16 +20137,16 @@ function validate11( return false; } else { errors = - _errs50; + _errs52; if ( vErrors !== null ) { if ( - _errs50 + _errs52 ) { vErrors.length = - _errs50; + _errs52; } else { vErrors = null; @@ -20086,7 +20154,7 @@ function validate11( } } var valid11 = - _errs49 === + _errs51 === errors; if (!valid11) { break; @@ -20111,7 +20179,7 @@ function validate11( return false; } } - var valid0 = _errs47 === errors; + var valid0 = _errs49 === errors; } else { var valid0 = true; } @@ -20120,31 +20188,31 @@ function validate11( data.siteOptions !== undefined ) { - let data19 = + let data20 = data.siteOptions; - const _errs54 = errors; - if (errors === _errs54) { + const _errs56 = errors; + if (errors === _errs56) { if ( - data19 && - typeof data19 == + data20 && + typeof data20 == 'object' && !Array.isArray( - data19 + data20 ) ) { - const _errs56 = + const _errs58 = errors; - for (const key5 in data19) { + for (const key5 in data20) { if ( !( key5 === 'blogname' ) ) { - const _errs57 = + const _errs59 = errors; if ( - typeof data19[ + typeof data20[ key5 ] !== 'string' @@ -20178,7 +20246,7 @@ function validate11( return false; } var valid13 = - _errs57 === + _errs59 === errors; if ( !valid13 @@ -20188,15 +20256,15 @@ function validate11( } } if ( - _errs56 === + _errs58 === errors ) { if ( - data19.blogname !== + data20.blogname !== undefined ) { if ( - typeof data19.blogname !== + typeof data20.blogname !== 'string' ) { validate11.errors = @@ -20242,7 +20310,7 @@ function validate11( } } var valid0 = - _errs54 === errors; + _errs56 === errors; } else { var valid0 = true; } @@ -20250,13 +20318,13 @@ function validate11( if ( data.login !== undefined ) { - let data22 = data.login; - const _errs61 = errors; - const _errs62 = errors; - let valid15 = false; + let data23 = data.login; const _errs63 = errors; + const _errs64 = errors; + let valid15 = false; + const _errs65 = errors; if ( - typeof data22 !== + typeof data23 !== 'boolean' ) { const err7 = { @@ -20286,31 +20354,31 @@ function validate11( errors++; } var _valid2 = - _errs63 === errors; + _errs65 === errors; valid15 = valid15 || _valid2; if (!valid15) { - const _errs65 = + const _errs67 = errors; if ( errors === - _errs65 + _errs67 ) { if ( - data22 && - typeof data22 == + data23 && + typeof data23 == 'object' && !Array.isArray( - data22 + data23 ) ) { let missing2; if ( - (data22.username === + (data23.username === undefined && (missing2 = 'username')) || - (data22.password === + (data23.password === undefined && (missing2 = 'password')) @@ -20348,9 +20416,9 @@ function validate11( } errors++; } else { - const _errs67 = + const _errs69 = errors; - for (const key6 in data22) { + for (const key6 in data23) { if ( !( key6 === @@ -20393,17 +20461,17 @@ function validate11( } } if ( - _errs67 === + _errs69 === errors ) { if ( - data22.username !== + data23.username !== undefined ) { - const _errs68 = + const _errs70 = errors; if ( - typeof data22.username !== + typeof data23.username !== 'string' ) { const err10 = @@ -20437,7 +20505,7 @@ function validate11( errors++; } var valid16 = - _errs68 === + _errs70 === errors; } else { var valid16 = true; @@ -20446,13 +20514,13 @@ function validate11( valid16 ) { if ( - data22.password !== + data23.password !== undefined ) { - const _errs70 = + const _errs72 = errors; if ( - typeof data22.password !== + typeof data23.password !== 'string' ) { const err11 = @@ -20486,7 +20554,7 @@ function validate11( errors++; } var valid16 = - _errs70 === + _errs72 === errors; } else { var valid16 = true; @@ -20527,7 +20595,7 @@ function validate11( } } var _valid2 = - _errs65 === + _errs67 === errors; valid15 = valid15 || @@ -20562,13 +20630,13 @@ function validate11( vErrors; return false; } else { - errors = _errs62; + errors = _errs64; if ( vErrors !== null ) { - if (_errs62) { + if (_errs64) { vErrors.length = - _errs62; + _errs64; } else { vErrors = null; @@ -20576,7 +20644,7 @@ function validate11( } } var valid0 = - _errs61 === errors; + _errs63 === errors; } else { var valid0 = true; } @@ -20585,49 +20653,49 @@ function validate11( data.steps !== undefined ) { - let data25 = + let data26 = data.steps; - const _errs72 = + const _errs74 = errors; if ( errors === - _errs72 + _errs74 ) { if ( Array.isArray( - data25 + data26 ) ) { var valid17 = true; const len3 = - data25.length; + data26.length; for ( let i3 = 0; i3 < len3; i3++ ) { - let data26 = - data25[ + let data27 = + data26[ i3 ]; - const _errs74 = + const _errs76 = errors; - const _errs75 = + const _errs77 = errors; let valid18 = false; - const _errs76 = + const _errs78 = errors; if ( !validate14( - data26, + data27, { instancePath: instancePath + '/steps/' + i3, parentData: - data25, + data26, parentDataProperty: i3, rootData, @@ -20645,7 +20713,7 @@ function validate11( vErrors.length; } var _valid3 = - _errs76 === + _errs78 === errors; valid18 = valid18 || @@ -20653,10 +20721,10 @@ function validate11( if ( !valid18 ) { - const _errs77 = + const _errs79 = errors; if ( - typeof data26 !== + typeof data27 !== 'string' ) { const err14 = @@ -20691,7 +20759,7 @@ function validate11( errors++; } var _valid3 = - _errs77 === + _errs79 === errors; valid18 = valid18 || @@ -20699,7 +20767,7 @@ function validate11( if ( !valid18 ) { - const _errs79 = + const _errs81 = errors; const err15 = { @@ -20730,7 +20798,7 @@ function validate11( } errors++; var _valid3 = - _errs79 === + _errs81 === errors; valid18 = valid18 || @@ -20738,10 +20806,10 @@ function validate11( if ( !valid18 ) { - const _errs81 = + const _errs83 = errors; if ( - typeof data26 !== + typeof data27 !== 'boolean' ) { const err16 = @@ -20777,7 +20845,7 @@ function validate11( } if ( false !== - data26 + data27 ) { const err17 = { @@ -20811,7 +20879,7 @@ function validate11( errors++; } var _valid3 = - _errs81 === + _errs83 === errors; valid18 = valid18 || @@ -20819,10 +20887,10 @@ function validate11( if ( !valid18 ) { - const _errs83 = + const _errs85 = errors; if ( - data26 !== + data27 !== null ) { const err18 = @@ -20857,7 +20925,7 @@ function validate11( errors++; } var _valid3 = - _errs83 === + _errs85 === errors; valid18 = valid18 || @@ -20902,16 +20970,16 @@ function validate11( return false; } else { errors = - _errs75; + _errs77; if ( vErrors !== null ) { if ( - _errs75 + _errs77 ) { vErrors.length = - _errs75; + _errs77; } else { vErrors = null; @@ -20919,7 +20987,7 @@ function validate11( } } var valid17 = - _errs74 === + _errs76 === errors; if ( !valid17 @@ -20949,7 +21017,7 @@ function validate11( } } var valid0 = - _errs72 === + _errs74 === errors; } else { var valid0 = true; @@ -20959,7 +21027,7 @@ function validate11( data.$schema !== undefined ) { - const _errs85 = + const _errs87 = errors; if ( typeof data.$schema !== @@ -20985,7 +21053,7 @@ function validate11( return false; } var valid0 = - _errs85 === + _errs87 === errors; } else { var valid0 = true; diff --git a/packages/playground/blueprints/public/blueprint-schema.json b/packages/playground/blueprints/public/blueprint-schema.json index c4e3f81a6d..59656dbd31 100644 --- a/packages/playground/blueprints/public/blueprint-schema.json +++ b/packages/playground/blueprints/public/blueprint-schema.json @@ -68,6 +68,9 @@ "features": { "type": "object", "properties": { + "intl": { + "type": "boolean" + }, "networking": { "type": "boolean", "description": "Should boot with support for network request via wp_safe_remote_get?" @@ -562,7 +565,7 @@ }, "importer": { "type": "string", - "enum": ["default"], + "enum": ["data-liberation", "default"], "description": "The importer to use. Possible values:\n\n- `default`: The importer from https://github.com/humanmade/WordPress-Importer\n- `data-liberation`: The experimental Data Liberation WXR importer developed at https://github.com/WordPress/wordpress-playground/issues/1894\n\nThis option is deprecated. The syntax will not be removed, but once the Data Liberation importer matures, it will become the only supported importer and the `importer` option will be ignored.", "deprecated": true } @@ -667,7 +670,7 @@ }, "pluginZipFile": { "$ref": "#/definitions/FileReference", - "deprecated": ". Use `pluginData` instead." + "deprecated": ". Use 'pluginData' instead." }, "options": { "$ref": "#/definitions/InstallPluginOptions", @@ -715,7 +718,7 @@ }, "themeZipFile": { "$ref": "#/definitions/FileReference", - "deprecated": ". Use `themeData` instead." + "deprecated": ". Use 'themeData' instead." }, "options": { "$ref": "#/definitions/InstallThemeOptions", @@ -1224,7 +1227,7 @@ }, "filesTree": { "$ref": "#/definitions/DirectoryReference", - "description": "The data to write" + "description": "The 'filesTree' defines the directory structure, supporting 'literal:directory' or 'git:directory' types. The 'name' represents the root directory, while 'files' is an object where keys are file paths, and values contain either file content as a string or nested objects for subdirectories." } }, "required": ["filesTree", "step", "writeToPath"] diff --git a/packages/playground/blueprints/src/lib/blueprint.ts b/packages/playground/blueprints/src/lib/blueprint.ts index a6a5f51939..31e3311b67 100644 --- a/packages/playground/blueprints/src/lib/blueprint.ts +++ b/packages/playground/blueprints/src/lib/blueprint.ts @@ -74,6 +74,7 @@ export type BlueprintDeclaration = { wp: string | 'latest'; }; features?: { + intl?: boolean; /** Should boot with support for network request via wp_safe_remote_get? */ networking?: boolean; }; diff --git a/packages/playground/blueprints/src/lib/compile.ts b/packages/playground/blueprints/src/lib/compile.ts index cab4af9673..d7ada10638 100644 --- a/packages/playground/blueprints/src/lib/compile.ts +++ b/packages/playground/blueprints/src/lib/compile.ts @@ -51,6 +51,7 @@ export interface CompiledBlueprint { wp: string; }; features: { + intl: boolean; /** Should boot with support for network request via wp_safe_remote_get? */ networking: boolean; }; @@ -321,6 +322,8 @@ function compileBlueprintJson( wp: blueprint.preferredVersions?.wp || 'latest', }, features: { + // Disable intl by default + intl: blueprint.features?.intl ?? false, // Disable networking by default networking: blueprint.features?.networking ?? false, }, diff --git a/packages/playground/client/src/index.ts b/packages/playground/client/src/index.ts index 911403765b..d0c23b8da8 100644 --- a/packages/playground/client/src/index.ts +++ b/packages/playground/client/src/index.ts @@ -154,6 +154,7 @@ export async function startPlaygroundWeb({ shouldInstallWordPress, phpVersion: compiled.versions.php, wpVersion: compiled.versions.wp, + withICU: compiled.features.intl, withNetworking: compiled.features.networking, corsProxyUrl: corsProxy, sqliteDriverVersion, diff --git a/packages/playground/remote/src/lib/disabled-functions.ts b/packages/playground/remote/src/lib/disabled-functions.ts new file mode 100644 index 0000000000..ff3954f3e0 --- /dev/null +++ b/packages/playground/remote/src/lib/disabled-functions.ts @@ -0,0 +1,187 @@ +export const intlDisabledFunctions = [ + 'intlcal_create_instance', + 'intlcal_get_keyword_values_for_locale', + 'intlcal_get_now', + 'intlcal_get_available_locales', + 'intlcal_get', + 'intlcal_get_time', + 'intlcal_set_time', + 'intlcal_add', + 'intlcal_set_time_zone', + 'intlcal_after', + 'intlcal_before', + 'intlcal_set', + 'intlcal_roll', + 'intlcal_clear', + 'intlcal_field_difference', + 'intlcal_get_actual_maximum', + 'intlcal_get_actual_minimum', + 'intlcal_get_day_of_week_type', + 'intlcal_get_first_day_of_week', + 'intlcal_get_least_maximum', + 'intlcal_get_greatest_minimum', + 'intlcal_get_locale', + 'intlcal_get_maximum', + 'intlcal_get_minimal_days_in_first_week', + 'intlcal_set_minimal_days_in_first_week', + 'intlcal_get_minimum', + 'intlcal_get_time_zone', + 'intlcal_get_type', + 'intlcal_get_weekend_transition', + 'intlcal_in_daylight_time', + 'intlcal_is_lenient', + 'intlcal_is_set', + 'intlcal_is_equivalent_to', + 'intlcal_is_weekend', + 'intlcal_set_first_day_of_week', + 'intlcal_set_lenient', + 'intlcal_get_repeated_wall_time_option', + 'intlcal_equals', + 'intlcal_get_skipped_wall_time_option', + 'intlcal_set_repeated_wall_time_option', + 'intlcal_set_skipped_wall_time_option', + 'intlcal_from_date_time', + 'intlcal_to_date_time', + 'intlcal_get_error_code', + 'intlcal_get_error_message', + 'intlgregcal_create_instance', + 'intlgregcal_set_gregorian_change', + 'intlgregcal_get_gregorian_change', + 'intlgregcal_is_leap_year', + 'collator_create', + 'collator_compare', + 'collator_get_attribute', + 'collator_set_attribute', + 'collator_get_strength', + 'collator_set_strength', + 'collator_sort', + 'collator_sort_with_sort_keys', + 'collator_asort', + 'collator_get_locale', + 'collator_get_error_code', + 'collator_get_error_message', + 'collator_get_sort_key', + 'intl_get_error_code', + 'intl_get_error_message', + 'intl_is_failure', + 'intl_error_name', + 'datefmt_create', + 'datefmt_get_datetype', + 'datefmt_get_timetype', + 'datefmt_get_calendar', + 'datefmt_set_calendar', + 'datefmt_get_timezone_id', + 'datefmt_get_calendar_object', + 'datefmt_get_timezone', + 'datefmt_set_timezone', + 'datefmt_set_pattern', + 'datefmt_get_pattern', + 'datefmt_get_locale', + 'datefmt_set_lenient', + 'datefmt_is_lenient', + 'datefmt_format', + 'datefmt_format_object', + 'datefmt_parse', + 'datefmt_localtime', + 'datefmt_get_error_code', + 'datefmt_get_error_message', + 'numfmt_create', + 'numfmt_format', + 'numfmt_parse', + 'numfmt_format_currency', + 'numfmt_parse_currency', + 'numfmt_set_attribute', + 'numfmt_get_attribute', + 'numfmt_set_text_attribute', + 'numfmt_get_text_attribute', + 'numfmt_set_symbol', + 'numfmt_get_symbol', + 'numfmt_set_pattern', + 'numfmt_get_pattern', + 'numfmt_get_locale', + 'numfmt_get_error_code', + 'numfmt_get_error_message', + 'grapheme_strlen', + 'grapheme_strpos', + 'grapheme_stripos', + 'grapheme_strrpos', + 'grapheme_strripos', + 'grapheme_substr', + 'grapheme_strstr', + 'grapheme_stristr', + 'grapheme_str_split', + 'grapheme_extract', + 'idn_to_ascii', + 'idn_to_utf8', + 'locale_get_default', + 'locale_set_default', + 'locale_get_primary_language', + 'locale_get_script', + 'locale_get_region', + 'locale_get_keywords', + 'locale_get_display_script', + 'locale_get_display_region', + 'locale_get_display_name', + 'locale_get_display_language', + 'locale_get_display_variant', + 'locale_compose', + 'locale_parse', + 'locale_get_all_variants', + 'locale_filter_matches', + 'locale_canonicalize', + 'locale_lookup', + 'locale_accept_from_http', + 'msgfmt_create', + 'msgfmt_format', + 'msgfmt_format_message', + 'msgfmt_parse', + 'msgfmt_parse_message', + 'msgfmt_set_pattern', + 'msgfmt_get_pattern', + 'msgfmt_get_locale', + 'msgfmt_get_error_code', + 'msgfmt_get_error_message', + 'normalizer_normalize', + 'normalizer_is_normalized', + 'normalizer_get_raw_decomposition', + 'resourcebundle_create', + 'resourcebundle_get', + 'resourcebundle_count', + 'resourcebundle_locales', + 'resourcebundle_get_error_code', + 'resourcebundle_get_error_message', + 'intltz_count_equivalent_ids', + 'intltz_create_default', + 'intltz_create_enumeration', + 'intltz_create_time_zone', + 'intltz_create_time_zone_id_enumeration', + 'intltz_from_date_time_zone', + 'intltz_get_canonical_id', + 'intltz_get_display_name', + 'intltz_get_dst_savings', + 'intltz_get_equivalent_id', + 'intltz_get_error_code', + 'intltz_get_error_message', + 'intltz_get_gmt', + 'intltz_get_id', + 'intltz_get_offset', + 'intltz_get_raw_offset', + 'intltz_get_region', + 'intltz_get_tz_data_version', + 'intltz_get_unknown', + 'intltz_get_windows_id', + 'intltz_get_id_for_windows_id', + 'intltz_has_same_rules', + 'intltz_to_date_time_zone', + 'intltz_use_daylight_time', + 'intltz_get_iana_id', + 'transliterator_create', + 'transliterator_create_from_rules', + 'transliterator_list_ids', + 'transliterator_create_inverse', + 'transliterator_transliterate', + 'transliterator_get_error_code', + 'transliterator_get_error_message', +]; + +export const networkingDisabledFunctions = ['curl_exec', 'curl_multi_exec']; diff --git a/packages/playground/remote/src/lib/worker-thread.ts b/packages/playground/remote/src/lib/worker-thread.ts index 30bca5fb0d..845dab63d2 100644 --- a/packages/playground/remote/src/lib/worker-thread.ts +++ b/packages/playground/remote/src/lib/worker-thread.ts @@ -51,6 +51,10 @@ import { import { wpVersionToStaticAssetsDirectory } from '@wp-playground/wordpress-builds'; import { logger } from '@php-wasm/logger'; import { generateCertificate, certificateToPEM } from '@php-wasm/web'; +import { + intlDisabledFunctions, + networkingDisabledFunctions, +} from './disabled-functions'; // post message to parent self.postMessage('worker-script-started'); @@ -73,6 +77,7 @@ export type WorkerBootOptions = { phpVersion?: SupportedPHPVersion; sapiName?: string; scope: string; + withICU: boolean; withNetworking: boolean; mounts?: Array; shouldInstallWordPress?: boolean; @@ -169,6 +174,7 @@ export class PlaygroundWorkerEndpoint extends PHPWorker { sqliteDriverVersion = LatestSqliteDriverVersion, phpVersion = '8.0', sapiName = 'cli', + withICU = false, withNetworking = false, shouldInstallWordPress = true, corsProxyUrl, @@ -247,6 +253,15 @@ export class PlaygroundWorkerEndpoint extends PHPWorker { }; let CAroot: false | GeneratedCertificate = false; let tcpOverFetch: TCPOverFetchOptions | undefined = undefined; + if (!withICU) { + phpIniEntries['disable_functions'] = ( + phpIniEntries['disable_functions'] ?? '' + ) + .split(',') + .concat(intlDisabledFunctions) + .filter((n) => n) + .join(','); + } if (withNetworking) { /** * Generate a self-signed CA certificate and tell PHP to trust it. @@ -274,8 +289,13 @@ export class PlaygroundWorkerEndpoint extends PHPWorker { // Calling curl_exec() with networking disabled causes PHP to // enter an infinite loop. Let's disable it completely to // throw a fatal error instead. - phpIniEntries['disable_functions'] = - 'curl_exec,curl_multi_exec'; + phpIniEntries['disable_functions'] = ( + phpIniEntries['disable_functions'] ?? '' + ) + .split(',') + .concat(networkingDisabledFunctions) + .filter((n) => n) + .join(','); } const requestHandler = await bootWordPress({ siteUrl: setURLScope(wordPressSiteUrl, scope).toString(), @@ -283,6 +303,7 @@ export class PlaygroundWorkerEndpoint extends PHPWorker { let wasmUrl = ''; return await loadWebRuntime(phpVersion, { tcpOverFetch, + withICU, emscriptenOptions: { instantiateWasm(imports, receiveInstance) { // Using .then because Emscripten typically returns an empty diff --git a/packages/playground/website/playwright/e2e/blueprints.spec.ts b/packages/playground/website/playwright/e2e/blueprints.spec.ts index 700ddda7ea..59c8661ddf 100644 --- a/packages/playground/website/playwright/e2e/blueprints.spec.ts +++ b/packages/playground/website/playwright/e2e/blueprints.spec.ts @@ -196,6 +196,58 @@ test('wp-cli step should create a post', async ({ website, wordpress }) => { ).toBeVisible(); }); +test('Intl functions should be disabled by default', async ({ + website, + wordpress, +}) => { + const blueprint: Blueprint = { + landingPage: '/intl-test.php', + steps: [ + { + step: 'writeFile', + path: '/wordpress/intl-test.php', + data: ` { + if (testInfo.project.name === 'chromium') { + test.skip(true, 'Skipping this test on Chromium due to unknown issues'); + } + const blueprint: Blueprint = { + landingPage: '/intl-test.php', + features: { intl: true }, + steps: [ + { + step: 'writeFile', + path: '/wordpress/intl-test.php', + data: `