forked from GoogleChromeLabs/pwa-wp
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
added changes from PR GoogleChromeLabs#14
- Loading branch information
1 parent
5de9db9
commit 19f5827
Showing
5 changed files
with
337 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
<?php | ||
/** | ||
* Tests for class WP_Service_Workers. | ||
* | ||
* @package PWA | ||
*/ | ||
|
||
/** | ||
* Tests for class WP_Web_App_Manifest. | ||
*/ | ||
class Test_WP_Service_Workers extends WP_UnitTestCase { | ||
/** | ||
* Tested instance. | ||
* | ||
* @var WP_Service_Workers | ||
*/ | ||
public $instance; | ||
|
||
/** | ||
* Setup. | ||
* | ||
* @inheritdoc | ||
*/ | ||
public function setUp() { | ||
parent::setUp(); | ||
$this->instance = new WP_Service_Workers(); | ||
} | ||
|
||
/** | ||
* Test class constructor. | ||
* | ||
* @covers WP_Service_Workers::__construct() | ||
*/ | ||
public function test_construct() { | ||
$service_workers = new WP_Service_Workers(); | ||
$this->assertEquals( 'WP_Service_Workers', get_class( $service_workers ) ); | ||
} | ||
|
||
/** | ||
* Test adding new service worker. | ||
* | ||
* @covers WP_Service_Workers::add() | ||
*/ | ||
public function test_register() { | ||
$this->instance->register( 'foo', '/test-sw.js', array( 'bar' ) ); | ||
$default_scope = site_url( '/', 'relative' ); | ||
$this->assertTrue( in_array( $default_scope, $this->instance->get_scopes(), true ) ); | ||
$this->assertTrue( isset( $this->instance->registered['foo'] ) ); | ||
$registered_sw = $this->instance->registered['foo']; | ||
$this->assertEquals( '/test-sw.js', $registered_sw->src ); | ||
$this->assertTrue( in_array( $default_scope, $registered_sw->args['scopes'], true ) ); | ||
$this->assertEquals( array( 'bar' ), $registered_sw->deps ); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,172 @@ | ||
<?php | ||
/** | ||
* Dependencies API: WP_Service_Workers class | ||
* | ||
* @since ? | ||
* | ||
* @package PWA | ||
*/ | ||
/** | ||
* Class used to register service workers. | ||
* | ||
* @since ? | ||
* | ||
* @see WP_Dependencies | ||
*/ | ||
class WP_Service_Workers extends WP_Scripts { | ||
/** | ||
* Param for service workers. | ||
* | ||
* @var string | ||
*/ | ||
public $query_var = 'wp_service_worker'; | ||
/** | ||
* Output for service worker scope script. | ||
* | ||
* @var string | ||
*/ | ||
public $output = ''; | ||
/** | ||
* Initialize the class. | ||
*/ | ||
public function init() { | ||
/** | ||
* Fires when the WP_Service_Workers instance is initialized. | ||
* | ||
* @param WP_Service_Workers $this WP_Service_Workers instance (passed by reference). | ||
*/ | ||
do_action_ref_array( 'wp_default_service_workers', array( &$this ) ); | ||
} | ||
/** | ||
* Register service worker. | ||
* | ||
* Registers service worker if no item of that name already exists. | ||
* | ||
* @param string $handle Name of the item. Should be unique. | ||
* @param string|callable $src URL to the source in the WordPress install, or a callback that returns the JS to include in the service worker. | ||
* @param array $deps Optional. An array of registered item handles this item depends on. Default empty array. | ||
* @param array $scopes Optional. Scopes of the service worker. Default relative path. | ||
* @return bool Whether the item has been registered. True on success, false on failure. | ||
*/ | ||
public function register( $handle, $src, $deps = array(), $scopes = array() ) { | ||
// Set default scope if missing. | ||
if ( empty( $scopes ) ) { | ||
$scopes = array( site_url( '/', 'relative' ) ); | ||
} | ||
return parent::add( $handle, $src, $deps, false, compact( 'scopes' ) ); | ||
} | ||
/** | ||
* Get service worker logic for scope. | ||
* | ||
* @param string $scope Scope of the Service Worker. | ||
*/ | ||
public function serve_request( $scope ) { | ||
header( 'Content-Type: text/javascript; charset=utf-8' ); | ||
nocache_headers(); | ||
if ( ! in_array( $scope, $this->get_scopes(), true ) ) { | ||
status_header( 404 ); | ||
return; | ||
} | ||
$scope_items = array(); | ||
// Get handles from the relevant scope only. | ||
foreach ( $this->registered as $handle => $item ) { | ||
if ( in_array( $scope, $item->args['scopes'], true ) ) { | ||
$scope_items[] = $handle; | ||
} | ||
} | ||
$this->output = ''; | ||
$this->do_items( $scope_items ); | ||
$file_hash = md5( $this->output ); | ||
header( "Etag: $file_hash" ); | ||
$etag_header = isset( $_SERVER['HTTP_IF_NONE_MATCH'] ) ? trim( $_SERVER['HTTP_IF_NONE_MATCH'] ) : false; | ||
if ( $file_hash === $etag_header ) { | ||
status_header( 304 ); | ||
return; | ||
} | ||
echo $this->output; // phpcs:ignore WordPress.XSS.EscapeOutput, WordPress.Security.EscapeOutput | ||
} | ||
/** | ||
* Get all scopes. | ||
* | ||
* @return array Array of scopes. | ||
*/ | ||
public function get_scopes() { | ||
$scopes = array(); | ||
foreach ( $this->registered as $handle => $item ) { | ||
$scopes = array_merge( $scopes, $item->args['scopes'] ); | ||
} | ||
return array_unique( $scopes ); | ||
} | ||
/** | ||
* Process one registered script. | ||
* | ||
* @param string $handle Handle. | ||
* @param bool $group Group. | ||
* @return void | ||
*/ | ||
public function do_item( $handle, $group = false ) { | ||
$obj = $this->registered[ $handle ]; | ||
if ( is_callable( $obj->src ) ) { | ||
$this->output .= call_user_func( $obj->src ) . "\n"; | ||
} else { | ||
$validated_path = $this->get_validated_file_path( $obj->src ); | ||
if ( is_wp_error( $validated_path ) ) { | ||
_doing_it_wrong( | ||
__FUNCTION__, | ||
/* translators: %s is file URL */ | ||
sprintf( esc_html__( 'Service worker src is incorrect: %s', 'pwa' ), esc_html( $obj->src ) ), | ||
'0.1' | ||
); | ||
/* translators: %s is file URL */ | ||
$this->output .= "console.warn( '" . sprintf( esc_html__( 'Service worker src is incorrect: %s', 'pwa' ), esc_html( $obj->src ) ) . "' );\n"; | ||
} else { | ||
/* translators: %s is file URL */ | ||
$this->output .= sprintf( esc_html( "\n/* Source: %s */\n" ), esc_url( $obj->src ) ); | ||
$this->output .= @file_get_contents( $this->get_validated_file_path( $obj->src ) ) . "\n"; // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged, WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents, WordPress.WP.AlternativeFunctions.file_system_read_file_get_contents | ||
} | ||
} | ||
} | ||
/** | ||
* Remove URL scheme. | ||
* | ||
* @param string $schemed_url URL. | ||
* @return string URL. | ||
*/ | ||
public function remove_url_scheme( $schemed_url ) { | ||
return preg_replace( '#^\w+:(?=//)#', '', $schemed_url ); | ||
} | ||
/** | ||
* Get validated path to file. | ||
* | ||
* @param string $url Relative path. | ||
* @return null|string|WP_Error | ||
*/ | ||
public function get_validated_file_path( $url ) { | ||
if ( ! is_string( $url ) ) { | ||
return new WP_Error( 'incorrect_path_format', esc_html__( 'URL has to be a string', 'pwa' ) ); | ||
} | ||
$needs_base_url = ! preg_match( '|^(https?:)?//|', $url ); | ||
$base_url = site_url(); | ||
if ( $needs_base_url ) { | ||
$url = $base_url . $url; | ||
} | ||
// Strip URL scheme, query, and fragment. | ||
$url = $this->remove_url_scheme( preg_replace( ':[\?#].*$:', '', $url ) ); | ||
$content_url = $this->remove_url_scheme( content_url( '/' ) ); | ||
$allowed_host = wp_parse_url( $content_url, PHP_URL_HOST ); | ||
$url_host = wp_parse_url( $url, PHP_URL_HOST ); | ||
if ( $allowed_host !== $url_host ) { | ||
/* translators: %s is file URL */ | ||
return new WP_Error( 'external_file_url', sprintf( __( 'URL is located on an external domain: %s.', 'pwa' ), $url_host ) ); | ||
} | ||
$file_path = null; | ||
if ( 0 === strpos( $url, $content_url ) ) { | ||
$file_path = WP_CONTENT_DIR . substr( $url, strlen( $content_url ) - 1 ); | ||
} | ||
if ( ! $file_path || false !== strpos( '../', $file_path ) || 0 !== validate_file( $file_path ) || ! file_exists( $file_path ) ) { | ||
/* translators: %s is file URL */ | ||
return new WP_Error( 'file_path_not_found', sprintf( __( 'Unable to locate filesystem path for %s.', 'pwa' ), $url ) ); | ||
} | ||
return $file_path; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
<?php | ||
/** | ||
* Sets up the default filters and actions for PWA hooks. | ||
* | ||
* @package PWA | ||
*/ | ||
foreach ( array( 'wp_print_scripts', 'admin_print_scripts', 'customize_controls_print_scripts' ) as $filter ) { | ||
add_filter( $filter, 'wp_print_service_workers', 9 ); | ||
} | ||
add_action( 'parse_request', 'wp_service_worker_loaded' ); | ||
add_filter( 'query_vars', 'wp_add_service_worker_query_var' ); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
<?php | ||
/** | ||
* Service Worker functions. | ||
* | ||
* @since ? | ||
* | ||
* @package PWA | ||
*/ | ||
/** | ||
* Initialize $wp_service_workers if it has not been set. | ||
* | ||
* @global WP_Service_Workers $wp_service_workers | ||
* @return WP_Service_Workers WP_Service_Workers instance. | ||
*/ | ||
function wp_service_workers() { | ||
global $wp_service_workers; | ||
if ( ! ( $wp_service_workers instanceof WP_Service_Workers ) ) { | ||
$wp_service_workers = new WP_Service_Workers(); | ||
} | ||
return $wp_service_workers; | ||
} | ||
/** | ||
* Register a new service worker. | ||
* | ||
* @since ? | ||
* | ||
* @param string $handle Name of the service worker. Should be unique. | ||
* @param string|callable $src Callback method or relative path of the service worker. | ||
* @param array $deps Optional. An array of registered script handles this depends on. Default empty array. | ||
* @param array $scopes Optional Scopes of the service worker. | ||
* @return bool Whether the script has been registered. True on success, false on failure. | ||
*/ | ||
function wp_register_service_worker( $handle, $src, $deps = array(), $scopes = array() ) { | ||
$service_workers = wp_service_workers(); | ||
$registered = $service_workers->register( $handle, $src, $deps, $scopes ); | ||
return $registered; | ||
} | ||
/** | ||
* Get service worker URL by scope. | ||
* | ||
* @param string $scope Scope, for example 'wp-admin'. | ||
* @return string Service Worker URL. | ||
*/ | ||
function wp_get_service_worker_url( $scope ) { | ||
if ( ! $scope ) { | ||
$scope = site_url( '/', 'relative' ); | ||
} | ||
return add_query_arg( array( | ||
'wp_service_worker' => $scope, | ||
), site_url( '/', 'relative' ) ); | ||
} | ||
/** | ||
* Print service workers' scripts. | ||
*/ | ||
function wp_print_service_workers() { | ||
$scopes = wp_service_workers()->get_scopes(); | ||
if ( empty( $scopes ) ) { | ||
return; | ||
} | ||
?> | ||
<script> | ||
if ( navigator.serviceWorker ) { | ||
window.addEventListener('load', function() { | ||
<?php foreach ( $scopes as $scope ) { ?> | ||
navigator.serviceWorker.register( | ||
<?php echo wp_json_encode( wp_get_service_worker_url( $scope ) ); ?>, | ||
<?php echo wp_json_encode( compact( 'scope' ) ); ?> | ||
); | ||
<?php } ?> | ||
} ); | ||
} | ||
</script> | ||
<?php | ||
} | ||
/** | ||
* Register query var. | ||
* | ||
* @param array $query_vars Query vars. | ||
* @return array Query vars. | ||
*/ | ||
function wp_add_service_worker_query_var( $query_vars ) { | ||
$query_vars[] = 'wp_service_worker'; | ||
return $query_vars; | ||
} | ||
/** | ||
* If it's a service worker script page, display that. | ||
*/ | ||
function wp_service_worker_loaded() { | ||
if ( ! empty( $GLOBALS['wp']->query_vars['wp_service_worker'] ) ) { | ||
wp_service_workers()->serve_request( $GLOBALS['wp']->query_vars['wp_service_worker'] ); | ||
exit; | ||
} | ||
} |