diff --git src/wp-includes/class.wp-scripts.php src/wp-includes/class.wp-scripts.php index 144bfce..a3d0ad5 100644 --- src/wp-includes/class.wp-scripts.php +++ src/wp-includes/class.wp-scripts.php @@ -163,6 +163,38 @@ class WP_Scripts extends WP_Dependencies { } /** + * Get the expanded source URL for a script. + * + * @since 4.6.0 + * + * @param string $handle The script's handle. + * @return string The URL. + */ + public function get_script_src( $handle ) { + $obj = $this->registered[ $handle ]; + $src = $obj->src; + if ( null === $obj->ver ) { + $ver = ''; + } else { + $ver = $obj->ver ? $obj->ver : $this->default_version; + } + + // Add any extra arguments to the version string. + if ( isset($this->args[ $handle ]) ) + $ver = $ver ? $ver . '&' . $this->args[ $handle ] : $this->args[ $handle ]; + + if ( ! preg_match( '|^(https?:)?//|', $src ) && ! ( $this->content_url && 0 === strpos( $src, $this->content_url ) ) ) { + $src = $this->base_url . $src; + } + + if ( ! empty( $ver ) ) + $src = add_query_arg( 'ver', $ver, $src ); + + /** This filter is documented in wp-includes/class.wp-scripts.php */ + return apply_filters( 'script_loader_src', $src, $handle ); + } + + /** * Prints scripts. * * Prints the scripts passed to it or the print queue. Also prints all necessary dependencies. @@ -329,20 +361,13 @@ class WP_Scripts extends WP_Dependencies { return true; } - if ( ! preg_match( '|^(https?:)?//|', $src ) && ! ( $this->content_url && 0 === strpos( $src, $this->content_url ) ) ) { - $src = $this->base_url . $src; - } - - if ( ! empty( $ver ) ) - $src = add_query_arg( 'ver', $ver, $src ); - - /** This filter is documented in wp-includes/class.wp-scripts.php */ - $src = esc_url( apply_filters( 'script_loader_src', $src, $handle ) ); + $src = $this->get_script_src( $handle ); - if ( ! $src ) + if ( ! $src ) { return true; - - $tag = "{$cond_before}{$before_handle}\n{$after_handle}{$cond_after}"; + } + $attributes = $this->get_script_attributes_html( $handle ); + $tag = "{$cond_before}{$before_handle}\n{$after_handle}{$cond_after}"; /** * Filter the HTML script tag of an enqueued script. @@ -421,6 +446,64 @@ class WP_Scripts extends WP_Dependencies { } /** + * Get the HTML element attributes for a script. + * + * Does not include the `src` attribute. + * + * @since 4.6.0 + * + * @param string $handle Script registered handle. + * @return array Attribues. + */ + public function get_script_attributes( $handle ) { + $default_attributes = array( + 'type' => 'text/javascript', + 'src' => $this->get_script_src( $handle ), + ); + + if ( isset( $this->registered[ $handle ]->args['attributes'] ) ) { + $extra_attributes = $this->registered[ $handle ]->args['attributes']; + $attributes = wp_parse_args( $extra_attributes, $default_attributes ); + } else { + $attributes = $default_attributes; + } + + /** + * Filter the HTML element attributes for a script. + * + * @since 4.6.0 + * + * @param array $attributes Array of script element attributes. + * @param string $handle Script handle. + */ + $attributes = apply_filters( 'script_additional_attributes', $attributes, $handle ); + return $attributes; + } + + + /** + * Get the concatenated HTML element attributes for a script. + * + * @since 4.6.0 + * + * @param string $handle The script handle. + * @return string Concatenated attributes. + */ + public function get_script_attributes_html( $handle ) { + $attributes = $this->get_script_attributes( $handle ); + $html = ''; + foreach ( $attributes as $attribute => $attribute_value ) { + if ( 'src' === $attribute ) { + $escaped_attribute_value = esc_url_raw( $attribute_value ); + } else { + $escaped_attribute_value = esc_attr( $attribute_value ); + } + $html .= sprintf( " %s='%s'", esc_attr_name( $attribute ), $escaped_attribute_value ); + } + return $html; + } + + /** * Localizes a script, only if the script has already been added. * * @since 2.1.0 diff --git src/wp-includes/class.wp-styles.php src/wp-includes/class.wp-styles.php index a175058..b0d9a68 100644 --- src/wp-includes/class.wp-styles.php +++ src/wp-includes/class.wp-styles.php @@ -128,6 +128,34 @@ class WP_Styles extends WP_Dependencies { } /** + * Get the HTML element attributes for a stylesheet. + * + * Does not include the `href` attribute. + * + * @since 4.6.0 + * + * @param string $handle The stylesheet handle. + * @return array Attribues. + */ + public function get_style_additional_attributes( $handle ) { + $obj = $this->registered[ $handle ]; + if ( null === $obj->ver ) { + $ver = ''; + } else { + $ver = $obj->ver ? $obj->ver : $this->default_version; + } + + $attributes = array( + 'rel' => isset($obj->extra['alt']) && $obj->extra['alt'] ? 'alternate stylesheet' : 'stylesheet', + 'id' => $handle . '-css', + 'title' => isset($obj->extra['title']) ? $obj->extra['title'] : '', + 'type' => 'text/css', + 'media' => isset( $obj->args ) ? $obj->args : 'all' + ); + return $attributes; + } + + /** * Processes a style dependency. * * @since 2.6.0 @@ -200,7 +228,7 @@ class WP_Styles extends WP_Dependencies { * @param string $href The stylesheet's source URL. * @param string $media The stylesheet's media attribute. */ - $tag = apply_filters( 'style_loader_tag', "\n", $handle, $href, $media); + $tag = apply_filters( 'style_loader_tag', "get_style_attribute_html( $handle ) . " />\n", $handle, $href, $media); if ( 'rtl' === $this->text_direction && isset($obj->extra['rtl']) && $obj->extra['rtl'] ) { if ( is_bool( $obj->extra['rtl'] ) || 'replace' === $obj->extra['rtl'] ) { $suffix = isset( $obj->extra['suffix'] ) ? $obj->extra['suffix'] : ''; @@ -243,6 +271,23 @@ class WP_Styles extends WP_Dependencies { } /** + * Get the concatenated HTML element attributes for a stylesheet + * + * @since 4.6.0 + * + * @param string $handle The stylesheet handle. + * @return string Concatenated attributes. + */ + public function get_style_attribute_html( $handle ) { + $attributes = $this->get_style_additional_attributes( $handle ); + $html = ''; + foreach ( $attributes as $attribute => $attribute_value ) { + $html .= sprintf( " %s='%s'", esc_attr_name( $attribute ), esc_attr( $attribute_value ) ); + } + return $html; + } + + /** * Adds extra CSS styles to a registered stylesheet. * * @since 3.3.0 diff --git src/wp-includes/functions.wp-scripts.php src/wp-includes/functions.wp-scripts.php index 0e06218..e9a27ca 100644 --- src/wp-includes/functions.wp-scripts.php +++ src/wp-includes/functions.wp-scripts.php @@ -129,6 +129,7 @@ function wp_add_inline_script( $handle, $data, $position = 'after' ) { * * @since 2.1.0 * @since 4.3.0 A return value was added. + * @since 4.6.0 Introduced the `$attributes` parameter. * * @param string $handle Name of the script. Should be unique. * @param string $src Full URL of the script, or path of the script relative to the WordPress root directory. @@ -139,13 +140,21 @@ function wp_add_inline_script( $handle, $data, $position = 'after' ) { * If set to null, no version is added. * @param bool $in_footer Optional. Whether to enqueue the script before
instead of in the
. * Default 'false'. + * @param array $args { + * Optional script arguments. + * + * @type array $attributes Array of script element attributes. + * Default: array( 'type' => 'text/javascript' ) + * } * @return bool Whether the script has been registered. True on success, false on failure. */ -function wp_register_script( $handle, $src, $deps = array(), $ver = false, $in_footer = false ) { +function wp_register_script( $handle, $src, $deps = array(), $ver = false, $in_footer = false, $attributes = array() ) { $wp_scripts = wp_scripts(); _wp_scripts_maybe_doing_it_wrong( __FUNCTION__ ); - $registered = $wp_scripts->add( $handle, $src, $deps, $ver ); + $attributes = wp_parse_args( $attributes, array( 'type' => 'text/javascript' ) ); + + $registered = $wp_scripts->add( $handle, $src, $deps, $ver, array( 'attributes' => $attributes ) ); if ( $in_footer ) { $wp_scripts->add_data( $handle, 'group', 1 ); } @@ -244,6 +253,7 @@ function wp_deregister_script( $handle ) { * @see WP_Dependencies::enqueue() * * @since 2.1.0 + * @since 4.6.0 Introduced the `$attributes` parameter. * * @param string $handle Name of the script. Should be unique. * @param string $src Full URL of the script, or path of the script relative to the WordPress root directory. @@ -254,18 +264,25 @@ function wp_deregister_script( $handle ) { * If set to null, no version is added. * @param bool $in_footer Optional. Whether to enqueue the script before instead of in the
. * Default 'false'. + * @param array $args { + * Optional script arguments. + * + * @type array $attributes Array of script element attributes. + * Default: array( 'type' => 'text/javascript' ) + * } */ -function wp_enqueue_script( $handle, $src = false, $deps = array(), $ver = false, $in_footer = false ) { +function wp_enqueue_script( $handle, $src = false, $deps = array(), $ver = false, $in_footer = false, $attributes = array() ) { $wp_scripts = wp_scripts(); _wp_scripts_maybe_doing_it_wrong( __FUNCTION__ ); + $attributes = wp_parse_args( $attributes, array( 'type' => 'text/javascript' ) ); if ( $src || $in_footer ) { $_handle = explode( '?', $handle ); if ( $src ) { - $wp_scripts->add( $_handle[0], $src, $deps, $ver ); + $wp_scripts->add( $_handle[0], $src, $deps, $ver, array( 'attributes' => $attributes ) ); } if ( $in_footer ) { diff --git tests/phpunit/tests/dependencies/getScriptAttributes.php tests/phpunit/tests/dependencies/getScriptAttributes.php new file mode 100644 index 0000000..24f84b7 --- /dev/null +++ tests/phpunit/tests/dependencies/getScriptAttributes.php @@ -0,0 +1,54 @@ +old_wp_scripts = isset( $GLOBALS['wp_scripts'] ) ? $GLOBALS['wp_scripts'] : null; + remove_action( 'wp_default_scripts', 'wp_default_scripts' ); + $GLOBALS['wp_scripts'] = new WP_Scripts(); + $GLOBALS['wp_scripts']->default_version = get_bloginfo( 'version' ); + } + + function tearDown() { + $GLOBALS['wp_scripts'] = $this->old_wp_scripts; + add_action( 'wp_default_scripts', 'wp_default_scripts' ); + parent::tearDown(); + } + + /** + * @ticket 22249 + */ + function test_get_script_attributes_with_defaults() { + global $wp_scripts; + wp_register_script( 'cool-script', '/cool-script.js', array(), '1' ); + + $expected_attributes = array( + 'type' => 'text/javascript', + 'src' => '/cool-script.js?ver=1', + ); + $actual_attributes = $wp_scripts->get_script_attributes( 'cool-script' ); + $this->assertEquals( $expected_attributes, $actual_attributes ); + } + + /** + * @ticket 22249 + */ + function test_get_script_attributes_with_arbitrary_attributes_added() { + global $wp_scripts; + wp_register_script( 'cool-script', '/cool-script.js', array(), '1', false, array( 'async' => 'async' ) ); + + $expected_attributes = array( + 'type' => 'text/javascript', + 'src' => '/cool-script.js?ver=1', + 'async' => 'async', + ); + $actual_attributes = $wp_scripts->get_script_attributes( 'cool-script' ); + $this->assertEquals( $expected_attributes, $actual_attributes ); + } + +} diff --git tests/phpunit/tests/dependencies/getScriptAttributesHtml.php tests/phpunit/tests/dependencies/getScriptAttributesHtml.php new file mode 100644 index 0000000..5a2a766 --- /dev/null +++ tests/phpunit/tests/dependencies/getScriptAttributesHtml.php @@ -0,0 +1,47 @@ +old_wp_scripts = isset( $GLOBALS['wp_scripts'] ) ? $GLOBALS['wp_scripts'] : null; + remove_action( 'wp_default_scripts', 'wp_default_scripts' ); + $GLOBALS['wp_scripts'] = new WP_Scripts(); + $GLOBALS['wp_scripts']->default_version = get_bloginfo( 'version' ); + } + + function tearDown() { + $GLOBALS['wp_scripts'] = $this->old_wp_scripts; + add_action( 'wp_default_scripts', 'wp_default_scripts' ); + parent::tearDown(); + } + + /** + * @ticket 22249 + */ + function test_get_script_attributes_html_with_defaults() { + global $wp_scripts; + wp_register_script( 'cool-script', '/cool-script.js', array(), '1' ); + + $actual = $wp_scripts->get_script_attributes_html( 'cool-script' ); + $expected = " type='text/javascript' src='/cool-script.js?ver=1'"; + $this->assertEquals( $expected, $actual ); + } + + /** + * @ticket 22249 + */ + function test_get_script_attributes_html_with_arbitrary_attributes_added() { + global $wp_scripts; + wp_register_script( 'cool-script', '/cool-script.js', array(), '1', false, array( 'async' => 'async' ) ); + + $actual = $wp_scripts->get_script_attributes_html( 'cool-script' ); + $expected = " type='text/javascript' src='/cool-script.js?ver=1' async='async'"; + $this->assertEquals( $expected, $actual ); + } + +} diff --git tests/phpunit/tests/dependencies/getScriptSrc.php tests/phpunit/tests/dependencies/getScriptSrc.php new file mode 100644 index 0000000..64d7007 --- /dev/null +++ tests/phpunit/tests/dependencies/getScriptSrc.php @@ -0,0 +1,45 @@ +old_wp_scripts = isset( $GLOBALS['wp_scripts'] ) ? $GLOBALS['wp_scripts'] : null; + remove_action( 'wp_default_scripts', 'wp_default_scripts' ); + $GLOBALS['wp_scripts'] = new WP_Scripts(); + $GLOBALS['wp_scripts']->default_version = get_bloginfo( 'version' ); + } + + function tearDown() { + $GLOBALS['wp_scripts'] = $this->old_wp_scripts; + add_action( 'wp_default_scripts', 'wp_default_scripts' ); + parent::tearDown(); + } + + /** + * @ticket 22249 + */ + function test_get_script_src_for_script_with_relative_url() { + global $wp_scripts; + wp_register_script( 'cool-script', '/cool-script.js', array(), '1' ); + + $actual = $wp_scripts->get_script_src( 'cool-script' ); + $this->assertEquals( '/cool-script.js?ver=1', $actual ); + } + + /** + * @ticket 22249 + */ + function test_get_script_src_for_script_with_full_url() { + global $wp_scripts; + wp_register_script( 'd3', 'https://d3js.org/d3.v3.min.js', array(), '1' ); + + $actual = $wp_scripts->get_script_src( 'd3' ); + $this->assertEquals( 'https://d3js.org/d3.v3.min.js?ver=1', $actual ); + } + +} diff --git tests/phpunit/tests/dependencies/scripts.php tests/phpunit/tests/dependencies/scripts.php index efe5a7b..01c56ab 100644 --- tests/phpunit/tests/dependencies/scripts.php +++ tests/phpunit/tests/dependencies/scripts.php @@ -724,4 +724,31 @@ class Tests_Dependencies_Scripts extends WP_UnitTestCase { $this->assertEquals( $expected, get_echo( 'wp_print_scripts' ) ); } + + /** + * @ticket 22249 + */ + function test_wp_register_script_should_allow_arbitrary_element_attributes() { + wp_register_script( 'cool-script', '/cool-script.js', array(), '1', false, array( 'async' => 'async' ) ); + + wp_enqueue_script( 'cool-script' ); + + $header = get_echo( 'wp_print_head_scripts' ); + $expected_header = "\n"; + + $this->assertEquals( $expected_header, $header ); + } + + /** + * @ticket 22249 + */ + function test_wp_enqueue_script_should_allow_arbitrary_element_attributes() { + wp_enqueue_script( 'cool-script', '/cool-script.js', array(), '1', false, array( 'async' => 'async' ) ); + + $header = get_echo( 'wp_print_head_scripts' ); + $expected_header = "\n"; + + $this->assertEquals( $expected_header, $header ); + } + }