├── assets-wp-repo ├── icon-128x128.png ├── icon-256x256.png ├── screenshot-1.png └── screenshot-2.png ├── tests └── phpunit │ ├── framework │ ├── Integration_Test_Case.php │ └── Unit_Test_Case.php │ ├── bootstrap.php │ ├── unit │ └── Sample_Tests.php │ ├── integration │ └── Sample_Tests.php │ ├── phpunit-compat.php │ └── bootstrap-integration.php ├── phpcs.xml.dist ├── .editorconfig ├── .gitignore ├── .codeclimate.yml ├── phpunit.xml.dist ├── phpunit-integration.xml.dist ├── .github ├── PULL_REQUEST_TEMPLATE.md └── ISSUE_TEMPLATE.md ├── phpmd.xml.dist ├── src ├── Policies_Setting.php ├── Policy_Headers.php ├── Feature.php ├── Admin │ ├── Pointers.php │ └── Settings_Screen.php ├── Plugin.php └── Features.php ├── composer.json ├── README.md ├── CONTRIBUTING.md ├── readme.txt ├── .travis.yml ├── feature-policy.php ├── deploy.sh └── LICENSE /assets-wp-repo/icon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/wp-feature-policy/HEAD/assets-wp-repo/icon-128x128.png -------------------------------------------------------------------------------- /assets-wp-repo/icon-256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/wp-feature-policy/HEAD/assets-wp-repo/icon-256x256.png -------------------------------------------------------------------------------- /assets-wp-repo/screenshot-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/wp-feature-policy/HEAD/assets-wp-repo/screenshot-1.png -------------------------------------------------------------------------------- /assets-wp-repo/screenshot-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/wp-feature-policy/HEAD/assets-wp-repo/screenshot-2.png -------------------------------------------------------------------------------- /tests/phpunit/framework/Integration_Test_Case.php: -------------------------------------------------------------------------------- 1 | assertTrue( true ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /phpcs.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | Ruleset for a WordPress plugin. 4 | 5 | ./feature-policy.php 6 | ./src 7 | 8 | ^/vendor/* 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # WordPress Coding Standards 2 | # https://make.wordpress.org/core/handbook/best-practices/coding-standards/ 3 | 4 | root = true 5 | 6 | [*] 7 | charset = utf-8 8 | end_of_line = lf 9 | indent_size = 4 10 | tab_width = 4 11 | indent_style = tab 12 | insert_final_newline = true 13 | trim_trailing_whitespace = true 14 | 15 | [*.md] 16 | trim_trailing_whitespace = false 17 | indent_style = space 18 | indent_size = 2 19 | 20 | [*.txt] 21 | trim_trailing_whitespace = false 22 | 23 | [*.json] 24 | insert_final_newline = false 25 | indent_style = space 26 | indent_size = 2 27 | 28 | [.*rc] 29 | insert_final_newline = false 30 | indent_style = space 31 | indent_size = 2 32 | 33 | [*.yml] 34 | insert_final_newline = false 35 | indent_style = space 36 | indent_size = 2 37 | -------------------------------------------------------------------------------- /tests/phpunit/integration/Sample_Tests.php: -------------------------------------------------------------------------------- 1 | assertTrue( defined( 'ABSPATH' ) ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ############ 2 | ## IDEs 3 | ############ 4 | 5 | *.pydevproject 6 | .project 7 | .metadata 8 | *.swp 9 | *~.nib 10 | local.properties 11 | .classpath 12 | .settings/ 13 | .loadpath 14 | .externalToolBuilders/ 15 | *.launch 16 | .cproject 17 | .buildpath 18 | nbproject/ 19 | 20 | ############ 21 | ## Vendor 22 | ############ 23 | 24 | node_modules/ 25 | vendor/ 26 | package-lock.json 27 | composer.lock 28 | 29 | ############ 30 | ## OSes 31 | ############ 32 | 33 | [Tt]humbs.db 34 | [Dd]esktop.ini 35 | *.DS_store 36 | .DS_store? 37 | 38 | ############ 39 | ## Misc 40 | ############ 41 | 42 | tests/logs 43 | bin/ 44 | tmp/ 45 | *.tmp 46 | *.bak 47 | *.log 48 | *.[Cc]ache 49 | *.cpr 50 | *.orig 51 | *.php.in 52 | .idea/ 53 | .sass-cache/* 54 | temp/ 55 | ._* 56 | .Trashes 57 | .svn 58 | -------------------------------------------------------------------------------- /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | --- 2 | engines: 3 | csslint: 4 | enabled: true 5 | duplication: 6 | enabled: true 7 | config: 8 | languages: 9 | - ruby 10 | - javascript 11 | - python 12 | - php 13 | eslint: 14 | enabled: true 15 | fixme: 16 | enabled: true 17 | phpcodesniffer: 18 | enabled: true 19 | config: 20 | file_extensions: "php" 21 | standard: "phpcs.xml.dist" 22 | ignore_warnings: true 23 | encoding: utf-8 24 | phpmd: 25 | enabled: true 26 | config: 27 | file_extensions: "php" 28 | rulesets: "phpmd.xml.dist" 29 | ratings: 30 | paths: 31 | - "**.css" 32 | - "**.js" 33 | - "**.php" 34 | exclude_paths: 35 | - "gulpfile.js" 36 | - "node_modules/*" 37 | - "tests/*" 38 | - "vendor/*" -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 20 | ./tests/phpunit/unit/ 21 | 22 | 23 | 24 | 25 | 26 | ./src 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /phpunit-integration.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 20 | ./tests/phpunit/integration/ 21 | 22 | 23 | 24 | 25 | 26 | ./src 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 7 | 8 | ## Summary 9 | 10 | 11 | This PR can be summarized in the following changelog entry: 12 | 13 | * 14 | 15 | 16 | Adresses issue # 17 | 18 | ## Relevant technical choices 19 | 20 | 21 | ## Checklist: 22 | - [ ] My code is tested. 23 | - [ ] My code is backward-compatible with WordPress 4.7 and PHP 5.6. 24 | - [ ] My code follows the [WordPress](https://make.wordpress.org/core/handbook/best-practices/coding-standards/) coding standards. 25 | - [ ] My code has proper inline documentation. 26 | -------------------------------------------------------------------------------- /phpmd.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 9 | A custom set of PHPMD rules. 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /tests/phpunit/phpunit-compat.php: -------------------------------------------------------------------------------- 1 | 10 | 11 | ## Issue Overview 12 | 13 | 14 | ## Steps to Reproduce (for bugs) 15 | 16 | 17 | 1. 18 | 2. 19 | 3. 20 | 4. 21 | 22 | 23 | ## Expected Behavior 24 | 25 | 26 | 27 | ## Current Behavior 28 | 29 | 30 | 31 | ## Possible Solution 32 | 33 | 34 | 35 | ## Screenshots / Video 36 | 37 | 38 | ## Related Issues and/or PRs 39 | 40 | -------------------------------------------------------------------------------- /tests/phpunit/framework/Unit_Test_Case.php: -------------------------------------------------------------------------------- 1 | 'object', 36 | 'description' => __( 'Feature Policy features and their origins.', 'feature-policy' ), 37 | 'sanitize_callback' => array( $this, 'sanitize' ), 38 | 'default' => array(), 39 | ) 40 | ); 41 | } 42 | ); 43 | } 44 | 45 | /** 46 | * Gets the features list from the option. 47 | * 48 | * @since 0.1.0 49 | * 50 | * @return array Associative array of $policy_name => $policy_origins pairs. 51 | */ 52 | public function get() { 53 | return array_filter( (array) get_option( self::OPTION_NAME, array() ) ); 54 | } 55 | 56 | /** 57 | * Sanitizes the value for the setting. 58 | * 59 | * @since 0.1.0 60 | * 61 | * @param mixed $value Unsanitized setting value. 62 | * @return array Associative array of $policy_name => $policy_origins pairs. 63 | */ 64 | public function sanitize( $value ) { 65 | // TODO: This is probably too basic. 66 | if ( ! is_array( $value ) ) { 67 | return array(); 68 | } 69 | return $value; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "google/wp-feature-policy", 3 | "description": "WordPress plugin for managing feature policy headers.", 4 | "version": "0.1.0", 5 | "license": "GPL-2.0-or-later", 6 | "type": "wordpress-plugin", 7 | "keywords": [ 8 | "feature", 9 | "policy" 10 | ], 11 | "homepage": "https://wordpress.org/plugins/feature-policy/", 12 | "authors": [ 13 | { 14 | "name": "Google", 15 | "email": "westonruter@google.com", 16 | "homepage": "https://opensource.google.com/" 17 | } 18 | ], 19 | "support": { 20 | "issues": "https://github.com/GoogleChromeLabs/wp-feature-policy/issues" 21 | }, 22 | "autoload": { 23 | "psr-4": { 24 | "Google\\WP_Feature_Policy\\": "src" 25 | } 26 | }, 27 | "autoload-dev": { 28 | "psr-4": { 29 | "Google\\WP_Feature_Policy\\Tests\\PHPUnit\\Framework\\": "tests/phpunit/framework" 30 | } 31 | }, 32 | "config": { 33 | "platform": { 34 | "php": "5.6" 35 | } 36 | }, 37 | "require": { 38 | "php": ">=5.6", 39 | "composer/installers": "^1" 40 | }, 41 | "require-dev": { 42 | "squizlabs/php_codesniffer": "^3.3", 43 | "dealerdirect/phpcodesniffer-composer-installer": "^0.4", 44 | "wp-coding-standards/wpcs": "^2", 45 | "phpmd/phpmd": "^2.6", 46 | "phpunit/phpunit": ">4.8.20 <6.0", 47 | "brain/monkey": "^2" 48 | }, 49 | "scripts": { 50 | "phplint": "find -L . -path ./vendor -prune -o -name '*.php' -print0 | xargs -0 -n 1 -P 4 php -l", 51 | "phpcs": "@php ./vendor/bin/phpcs", 52 | "phpmd": "@php ./vendor/bin/phpmd src text phpmd.xml.dist", 53 | "phpunit": "@php ./vendor/bin/phpunit", 54 | "phpunit-cov": "@php ./vendor/bin/phpunit --coverage-clover tests/logs/clover.xml", 55 | "phpunit-integration": "@php ./vendor/bin/phpunit -c phpunit-integration.xml.dist", 56 | "phpunit-integration-cov": "@php ./vendor/bin/phpunit -c phpunit-integration.xml.dist --coverage-clover tests/logs/clover.xml" 57 | } 58 | } -------------------------------------------------------------------------------- /src/Policy_Headers.php: -------------------------------------------------------------------------------- 1 | features = $features; 46 | $this->policies_setting = $policies_setting; 47 | } 48 | 49 | /** 50 | * Sends feature policy headers. 51 | * 52 | * @since 0.1.0 53 | */ 54 | public function send_headers() { 55 | $features = $this->features->get_all(); 56 | $option = $this->policies_setting->get(); 57 | 58 | $headers = array(); 59 | foreach ( $option as $policy_slug => $policy_origins ) { 60 | if ( ! isset( $features[ $policy_slug ] ) ) { 61 | continue; 62 | } 63 | 64 | if ( empty( $policy_origins ) || array( $features[ $policy_slug ]->default_origin ) === $policy_origins ) { 65 | continue; 66 | } 67 | 68 | $policy_header = $features[ $policy_slug ]->name; 69 | foreach ( $policy_origins as $origin ) { 70 | if ( Feature::ORIGIN_SELF === $origin || Feature::ORIGIN_NONE === $origin ) { 71 | $policy_header .= " '{$origin}'"; 72 | continue; 73 | } 74 | 75 | $policy_header .= " {$origin}"; 76 | } 77 | 78 | $headers[] = $policy_header; 79 | } 80 | 81 | if ( empty( $headers ) ) { 82 | return; 83 | } 84 | 85 | $value = implode( '; ', $headers ); 86 | 87 | header( "Feature-Policy: {$value}" ); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![WordPress plugin](https://img.shields.io/wordpress/plugin/v/feature-policy.svg?maxAge=2592000)](https://wordpress.org/plugins/feature-policy/) 2 | [![WordPress](https://img.shields.io/wordpress/v/feature-policy.svg?maxAge=2592000)](https://wordpress.org/plugins/feature-policy/) 3 | [![Build Status](https://api.travis-ci.org/GoogleChromeLabs/wp-feature-policy.png?branch=main)](https://travis-ci.org/GoogleChromeLabs/wp-feature-policy) 4 | 5 | # Feature Policy 6 | 7 | WordPress plugin for managing feature policy headers. 8 | 9 | ## Details 10 | 11 | As [noted on the Google Developers blog](https://developers.google.com/web/updates/2018/06/feature-policy): 12 | 13 | > Feature Policy allows web developers to selectively enable, disable, and modify the behavior of certain APIs and web features in the browser. **It's like CSP but instead of controlling security, it controls features!** 14 | > 15 | > The feature policies themselves are little opt-in agreements between developer and browser that can help foster our goals of building (and maintaining) high quality web apps. 16 | 17 | This plugin provides an API for sending the `Feature-Policy` response headers, as well as an admin interface for deciding which policy to apply for each feature. 18 | 19 | ![Settings screen to control policies for all available features](assets-wp-repo/screenshot-1.png) 20 | 21 | As the Feature Policy specification is still evolving and at an early stage, the plugin reflects that and is currently an experimental prototype, to demonstrate how Feature Policy can be used in WordPress. 22 | 23 | ### Did you know? 24 | 25 | The Feature Policy specification will integrate with the new Reporting API specification. There is a [WordPress plugin for that specification](https://github.com/GoogleChromeLabs/wp-reporting-api) as well. 26 | 27 | ## Requirements 28 | 29 | * WordPress >= 4.7 30 | * PHP >= 5.6 31 | 32 | ## Contributing 33 | 34 | Any kind of contributions to Feature Policy are welcome. Please [read the contributing guidelines](https://github.com/GoogleChromeLabs/wp-feature-policy/blob/main/CONTRIBUTING.md) to get started. 35 | 36 | ## Further Resources 37 | 38 | * [https://featurepolicy.rocks/](https://featurepolicy.rocks/) 39 | * [https://developers.google.com/web/updates/2018/06/feature-policy](https://developers.google.com/web/updates/2018/06/feature-policy) 40 | * [https://developers.google.com/web/updates/2018/09/reportingapi](https://developers.google.com/web/updates/2018/09/reportingapi) 41 | * [https://developer.mozilla.org/en-US/docs/Web/HTTP/Feature_Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Feature_Policy) 42 | * [https://wicg.github.io/feature-policy/](https://wicg.github.io/feature-policy/) 43 | * [https://github.com/WICG/feature-policy](https://github.com/WICG/feature-policy) 44 | -------------------------------------------------------------------------------- /src/Feature.php: -------------------------------------------------------------------------------- 1 | name = $name; 58 | $this->set_args( $args ); 59 | } 60 | 61 | /** 62 | * Magic isset-er. 63 | * 64 | * @since 0.1.0 65 | * 66 | * @param string $prop Property to check for. 67 | * @return bool True if the property is set, false otherwise. 68 | */ 69 | public function __isset( $prop ) { 70 | if ( 'name' === $prop ) { 71 | return true; 72 | } 73 | 74 | return isset( $this->args[ $prop ] ); 75 | } 76 | 77 | /** 78 | * Magic getter. 79 | * 80 | * @since 0.1.0 81 | * 82 | * @param string $prop Property to get. 83 | * @return mixed The property value, or null if property not set. 84 | */ 85 | public function __get( $prop ) { 86 | if ( 'name' === $prop ) { 87 | return $this->name; 88 | } 89 | 90 | if ( isset( $this->args[ $prop ] ) ) { 91 | return $this->args[ $prop ]; 92 | } 93 | 94 | return null; 95 | } 96 | 97 | /** 98 | * Sets the feature arguments. 99 | * 100 | * @since 0.1.0 101 | * 102 | * @param array $args List of feature arguments. See {@see Feature::__construct()} for a list of supported 103 | * arguments. 104 | */ 105 | protected function set_args( array $args ) { 106 | $this->args = wp_parse_args( 107 | $args, 108 | array( 109 | 'title' => '', 110 | 'default_origin' => self::ORIGIN_ANY, 111 | ) 112 | ); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /tests/phpunit/bootstrap-integration.php: -------------------------------------------------------------------------------- 1 | = 6.0 shim, which WordPress core only includes itself since version 4.7. 42 | if ( ! file_exists( $test_root . '/includes/phpunit6-compat.php' ) && class_exists( 'PHPUnit\Runner\Version' ) && version_compare( PHPUnit\Runner\Version::id(), '6.0', '>=' ) ) { 43 | class_alias( 'PHPUnit\Framework\TestCase', 'PHPUnit_Framework_TestCase' ); 44 | class_alias( 'PHPUnit\Framework\Exception', 'PHPUnit_Framework_Exception' ); 45 | class_alias( 'PHPUnit\Framework\ExpectationFailedException', 'PHPUnit_Framework_ExpectationFailedException' ); 46 | class_alias( 'PHPUnit\Framework\Error\Notice', 'PHPUnit_Framework_Error_Notice' ); 47 | class_alias( 'PHPUnit\Framework\Error\Warning', 'PHPUnit_Framework_Error_Warning' ); 48 | class_alias( 'PHPUnit\Framework\Test', 'PHPUnit_Framework_Test' ); 49 | class_alias( 'PHPUnit\Framework\Warning', 'PHPUnit_Framework_Warning' ); 50 | class_alias( 'PHPUnit\Framework\AssertionFailedError', 'PHPUnit_Framework_AssertionFailedError' ); 51 | class_alias( 'PHPUnit\Framework\TestSuite', 'PHPUnit_Framework_TestSuite' ); 52 | class_alias( 'PHPUnit\Framework\TestListener', 'PHPUnit_Framework_TestListener' ); 53 | class_alias( 'PHPUnit\Util\GlobalState', 'PHPUnit_Util_GlobalState' ); 54 | class_alias( 'PHPUnit\Util\Getopt', 'PHPUnit_Util_Getopt' ); 55 | class_alias( 'PHPUnit\Util\Test', 'PHPUnit_Util_Test' ); 56 | 57 | // This only needs to be included to that the WP test suite does not call the `getTickets()` method which conflicts. 58 | define( 'WP_TESTS_FORCE_KNOWN_BUGS', true ); 59 | } 60 | 61 | // Ensure the plugin is loaded. 62 | $GLOBALS['wp_tests_options'] = array( 63 | 'active_plugins' => array( basename( TESTS_PLUGIN_DIR ) . '/feature-policy.php' ), 64 | ); 65 | 66 | // Load the WordPress tests environment. 67 | require_once $test_root . '/includes/bootstrap.php'; 68 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribute to Feature Policy 2 | 3 | Bug reports, patches, translations and any kind of contributions are very welcome. When contributing, please ensure you stick to the following guidelines. 4 | 5 | ## Writing a Bug Report 6 | 7 | When writing a bug report... 8 | 9 | * [open an issue](https://github.com/GoogleChromeLabs/wp-feature-policy/issues/new) 10 | * follow the guidelines specified in the issue template 11 | 12 | We will take a look at your issue and either assign it keywords and a milestone or get back to you if there are open questions. 13 | 14 | ## Contributing Code 15 | 16 | When contributing code... 17 | 18 | * fork the `main` branch of the repository on GitHub 19 | * make changes to the forked repository 20 | * write code that is backward-compatible with WordPress 4.7 and PHP 5.6 21 | * make sure you stick to the [WordPress](https://make.wordpress.org/core/handbook/best-practices/coding-standards/) coding standards 22 | * make sure you document the code properly 23 | * test your code with the constant `WP_DEBUG` enabled 24 | * when committing, in addition to a note about the fix, please reference your issue (if present) 25 | * push the changes to your fork and [submit a pull request](https://github.com/GoogleChromeLabs/wp-feature-policy/compare) to the `main` branch 26 | * follow the guidelines specified in the pull request template 27 | 28 | After that we will review the pull-request as soon as possible and either merge it, make suggestions on improvements or ask you for further details about your implementation. 29 | 30 | ### Contributor License Agreement 31 | 32 | Contributions to this project must be accompanied by a Contributor License 33 | Agreement. You (or your employer) retain the copyright to your contribution; 34 | this simply gives us permission to use and redistribute your contributions as 35 | part of the project. Head over to to see 36 | your current agreements on file or to sign a new one. 37 | 38 | You generally only need to submit a CLA once, so if you've already submitted one 39 | (even if it was for a different project), you probably don't need to do it 40 | again. 41 | 42 | ### Code reviews 43 | 44 | All submissions, including submissions by project members, require review. We 45 | use GitHub pull requests for this purpose. Consult 46 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more 47 | information on using pull requests. 48 | 49 | ## Providing Translations 50 | 51 | When providing translations... 52 | 53 | * visit our project on [translate.wordpress.org](https://translate.wordpress.org/projects/wp-plugins/feature-policy) and sign in with your wordpress.org account (create one if you haven't yet) 54 | * select the language you would like to contribute to 55 | * provide the missing translations 56 | * make sure you follow the guidelines outlined in the [Translator Handbook](https://make.wordpress.org/polyglots/handbook/translating/expectations/) 57 | * stick to the translation conventions for your locale (if there are any) - you can find these [here](https://make.wordpress.org/polyglots/handbook/tools/list-of-glossaries-per-locale/) 58 | 59 | After having provided translations, a translation editor will review your submissions soon and approve them as appropriate. 60 | 61 | ## Community Guidelines 62 | 63 | This project follows 64 | [Google's Open Source Community Guidelines](https://opensource.google.com/conduct/). 65 | -------------------------------------------------------------------------------- /readme.txt: -------------------------------------------------------------------------------- 1 | === Feature Policy === 2 | 3 | Contributors: google, westonruter, flixos90 4 | Requires at least: 4.7 5 | Tested up to: 5.1 6 | Requires PHP: 5.6 7 | Stable tag: 0.1.0 8 | License: GNU General Public License v2 (or later) 9 | License URI: https://www.gnu.org/licenses/gpl-2.0.html 10 | Tags: feature, policy 11 | 12 | WordPress plugin for managing feature policy headers. 13 | 14 | == Description == 15 | 16 | As [noted on the Google Developers blog](https://developers.google.com/web/updates/2018/06/feature-policy): 17 | 18 | > Feature Policy allows web developers to selectively enable, disable, and modify the behavior of certain APIs and web features in the browser. **It's like CSP but instead of controlling security, it controls features!** 19 | > 20 | > The feature policies themselves are little opt-in agreements between developer and browser that can help foster our goals of building (and maintaining) high quality web apps. 21 | 22 | This plugin provides an API for sending the `Feature-Policy` response headers, as well as an admin interface for deciding which policy to apply for each feature. 23 | 24 | As the Feature Policy specification is still evolving and at an early stage, the plugin reflects that and is currently an experimental prototype, to demonstrate how Feature Policy can be used in WordPress. 25 | 26 | = Did you know? = 27 | 28 | The Feature Policy specification will integrate with the new Reporting API specification. There is a [WordPress plugin for that specification](https://wordpress.org/plugins/reporting-api/) as well. 29 | 30 | == Installation == 31 | 32 | 1. Upload the entire `feature-policy` folder to the `/wp-content/plugins/` directory or download it through the WordPress backend. 33 | 2. Activate the plugin through the 'Plugins' menu in WordPress. 34 | 35 | == Frequently Asked Questions == 36 | 37 | = Which browsers support the Feature Policy specification? = 38 | 39 | The Feature Policy standard is quite bleeding-edge, so support is currently still limited. The latest versions of Chrome, Safari, Opera and several mobile browsers support it. For detailed support stats, please check [caniuse.com/#feat=feature-policy](https://caniuse.com/#feat=feature-policy). 40 | 41 | = Where should I submit my support request? = 42 | 43 | Note that this is an experimental plugin, so support is limited and volunteer-driven. For regular support requests, please use the [wordpress.org support forums](https://wordpress.org/support/plugin/feature-policy). If you have a technical issue with the plugin where you already have more insight on how to fix it, you can also [open an issue on Github instead](https://github.com/GoogleChromeLabs/wp-feature-policy/issues). 44 | 45 | = How can I contribute to the plugin? = 46 | 47 | If you have some ideas to improve the plugin or to solve a bug, feel free to raise an issue or submit a pull request in the [Github repository for the plugin](https://github.com/GoogleChromeLabs/wp-feature-policy). Please stick to the [contributing guidelines](https://github.com/GoogleChromeLabs/wp-feature-policy/blob/main/CONTRIBUTING.md). 48 | 49 | You can also contribute to the plugin by translating it. Simply visit [translate.wordpress.org](https://translate.wordpress.org/projects/wp-plugins/feature-policy) to get started. 50 | 51 | == Screenshots == 52 | 53 | 1. Settings screen to control policies for all available features 54 | 2. Settings screen with a link to Feature Policy reports (with the Reporting API plugin active) 55 | 56 | == Changelog == 57 | 58 | = 0.1.0 = 59 | 60 | * Initial release 61 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | dist: trusty 3 | language: php 4 | cache: 5 | directories: 6 | - vendor 7 | - $HOME/.composer/cache 8 | matrix: 9 | include: 10 | - php: 7.3 11 | env: UNIT=1 PHPLINT=1 PHPCS=1 COVERAGE=1 12 | - php: 5.6 13 | env: UNIT=1 PHPLINT=1 14 | dist: trusty 15 | - php: 7.3 16 | env: INTEGRATION=1 WP_VERSION=latest 17 | - php: 5.6 18 | env: INTEGRATION=1 WP_VERSION=latest 19 | dist: trusty 20 | - php: 7.3 21 | env: INTEGRATION=1 WP_VERSION=4.7 22 | - php: 5.6 23 | env: INTEGRATION=1 WP_VERSION=4.7 24 | dist: trusty 25 | - php: nightly 26 | env: UNIT=1 27 | - php: 7.3 28 | env: INTEGRATION=1 WP_VERSION=master 29 | allow_failures: 30 | - php: nightly 31 | env: UNIT=1 32 | - php: 7.3 33 | env: INTEGRATION=1 WP_VERSION=master 34 | before_install: 35 | - | 36 | if [[ -z "$CC_TEST_REPORTER_ID" ]]; then 37 | COVERAGE="0" 38 | fi 39 | - | 40 | if [[ "$COVERAGE" == "1" ]]; then 41 | curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter 42 | chmod +x ./cc-test-reporter 43 | ./cc-test-reporter before-build 44 | fi 45 | - | 46 | if [[ "$COVERAGE" != "1" ]]; then 47 | stable='^[0-9\.]+$' 48 | if [[ "$TRAVIS_PHP_VERSION" =~ $stable ]]; then 49 | phpenv config-rm xdebug.ini 50 | fi 51 | fi 52 | - composer install 53 | before_script: 54 | - | 55 | if [[ "$INTEGRATION" == "1" ]]; then 56 | if [[ "$WP_VERSION" == "latest" ]]; then 57 | curl -s http://api.wordpress.org/core/version-check/1.7/ > /tmp/wp-latest.json 58 | WP_VERSION=$(grep -o '"version":"[^"]*' /tmp/wp-latest.json | sed 's/"version":"//') 59 | fi 60 | PLUGIN_SLUG=$(basename $(pwd)) 61 | export WP_DEVELOP_DIR=/tmp/wordpress/ 62 | git clone --depth=50 --branch="$WP_VERSION" git://develop.git.wordpress.org/ /tmp/wordpress 63 | cd .. 64 | cp -r "$PLUGIN_SLUG" "/tmp/wordpress/src/wp-content/plugins/$PLUGIN_SLUG" 65 | cd /tmp/wordpress/ 66 | cp wp-tests-config-sample.php wp-tests-config.php 67 | sed -i "s/youremptytestdbnamehere/wordpress_tests/" wp-tests-config.php 68 | sed -i "s/yourusernamehere/travis/" wp-tests-config.php 69 | sed -i "s/yourpasswordhere//" wp-tests-config.php 70 | mysql -e "CREATE DATABASE wordpress_tests;" -uroot 71 | cd "/tmp/wordpress/src/wp-content/plugins/$PLUGIN_SLUG" 72 | fi 73 | - phpenv rehash 74 | script: 75 | - | 76 | if [[ "$PHPLINT" == "1" ]]; then 77 | composer run-script phplint 78 | fi 79 | - | 80 | if [[ "$PHPCS" == "1" ]]; then 81 | composer run-script phpcs 82 | fi 83 | - | 84 | if [[ "$PHPMD" == "1" ]]; then 85 | composer run-script phpmd 86 | fi 87 | - | 88 | if [[ "$UNIT" == "1" ]]; then 89 | if [[ "$COVERAGE" == "1" ]]; then 90 | mkdir -p tests/logs 91 | composer run-script phpunit-cov 92 | else 93 | composer run-script phpunit 94 | fi 95 | fi 96 | - | 97 | if [[ "$INTEGRATION" == "1" ]]; then 98 | if [[ "$COVERAGE" == "1" ]]; then 99 | mkdir -p tests/logs 100 | composer run-script phpunit-integration-cov 101 | else 102 | composer run-script phpunit-integration 103 | fi 104 | fi 105 | after_script: 106 | - | 107 | if [[ "$COVERAGE" == "1" ]]; then 108 | ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT 109 | fi 110 | notifications: 111 | email: false -------------------------------------------------------------------------------- /feature-policy.php: -------------------------------------------------------------------------------- 1 | 61 |
62 |

63 | 71 |

72 |
73 | 84 |
85 |

86 | 94 |

95 |
96 | 107 |
108 |

109 | composer install' 114 | ); 115 | ?> 116 |

117 |
118 | get_pointers( $hook_suffix ); 37 | if ( empty( $pointers ) ) { 38 | return; 39 | } 40 | 41 | $dismissed = explode( ',', (string) get_user_meta( get_current_user_id(), 'dismissed_wp_pointers', true ) ); 42 | 43 | $pointers = array_diff_key( $pointers, array_flip( $dismissed ) ); 44 | 45 | $got_pointers = false; 46 | foreach ( $pointers as $pointer_id => $pointer_args ) { 47 | if ( empty( $pointer_args['render_callback'] ) ) { 48 | continue; 49 | } 50 | 51 | if ( ! empty( $pointer_args['active_callback'] ) && ! call_user_func( $pointer_args['active_callback'] ) ) { 52 | continue; 53 | } 54 | 55 | add_action( 'admin_print_footer_scripts', $pointer_args['render_callback'] ); 56 | $got_pointers = true; 57 | } 58 | 59 | // Bail if no pointers need to be loaded. 60 | if ( ! $got_pointers ) { 61 | return; 62 | } 63 | 64 | wp_enqueue_style( 'wp-pointer' ); 65 | wp_enqueue_script( 'wp-pointer' ); 66 | } 67 | 68 | /** 69 | * Gets available admin pointers. 70 | * 71 | * @since 0.1.0 72 | * 73 | * @param string $hook_suffix The current admin page. 74 | * @return array Associative array of $pointer_id => $pointer_args pairs. 75 | */ 76 | protected function get_pointers( $hook_suffix ) { 77 | $pointers = array( 78 | self::ACTIVATION => array( 79 | 'render_callback' => function() { 80 | $content = '

' . __( 'Feature Policy', 'feature-policy' ) . '

'; 81 | $content .= '

' . __( 'You can now control how certain web features act on your site under Settings > Feature Policy.', 'feature-policy' ) . '

'; 82 | 83 | $position = array( 84 | 'edge' => is_rtl() ? 'right' : 'left', 85 | 'align' => 'bottom', 86 | ); 87 | 88 | $js_args = array( 89 | 'content' => $content, 90 | 'position' => $position, 91 | 'pointerClass' => 'wp-pointer arrow-bottom', 92 | ); 93 | $this->print_js( self::ACTIVATION, '#menu-settings', $js_args ); 94 | }, 95 | 'active_callback' => function() { 96 | if ( ! current_user_can( Settings_Screen::CAPABILITY ) ) { 97 | return false; 98 | } 99 | 100 | return true; 101 | }, 102 | ), 103 | ); 104 | 105 | $pointers = array( 106 | 'index.php' => array( self::ACTIVATION => $pointers[ self::ACTIVATION ] ), 107 | 'plugins.php' => array( self::ACTIVATION => $pointers[ self::ACTIVATION ] ), 108 | ); 109 | 110 | if ( ! isset( $pointers[ $hook_suffix ] ) ) { 111 | return array(); 112 | } 113 | 114 | return $pointers[ $hook_suffix ]; 115 | } 116 | 117 | /** 118 | * Prints JavaScript data for a given pointer. 119 | * 120 | * @since 0.1.0 121 | * 122 | * @param string $pointer_id The pointer ID. 123 | * @param string $selector The HTML elements, on which the pointer should be attached. 124 | * @param array $args Arguments to be passed to the pointer JS (see wp-pointer.js). 125 | */ 126 | protected function print_js( $pointer_id, $selector, $args ) { 127 | if ( empty( $pointer_id ) || empty( $selector ) || empty( $args ) || empty( $args['content'] ) ) { 128 | return; 129 | } 130 | 131 | ?> 132 | 159 | /tmp/wppdcommitmsg.tmp 74 | echo "Done." 75 | 76 | printf "Preparing assets-wp-repo..." 77 | if [ -d $SVNPATH/trunk/assets-wp-repo ] 78 | then 79 | svn checkout --quiet $SVNURL/assets $SVNPATH/assets > /dev/null 2>&1 80 | mkdir $SVNPATH/assets/ > /dev/null 2>&1 # Create assets directory if it doesn't exists 81 | mv $SVNPATH/trunk/assets-wp-repo/* $SVNPATH/assets/ # Move new assets 82 | rm -rf $SVNPATH/trunk/assets-wp-repo # Clean up 83 | cd $SVNPATH/assets/ # Switch to assets directory 84 | svn stat | grep "^?\|^M" > /dev/null 2>&1 # Check if new or updated assets exists 85 | if [ $? -eq 0 ] 86 | then 87 | svn stat | grep "^?" | awk '{print $2}' | xargs svn add --quiet # Add new assets 88 | echo -en "Committing new assets..." 89 | svn commit --quiet -m "updated assets" 90 | echo "Done." 91 | else 92 | echo "Unchanged." 93 | fi 94 | else 95 | echo "No assets exists." 96 | fi 97 | 98 | cd $SVNPATH/trunk/ 99 | 100 | composer install --no-dev 101 | 102 | printf "Ignoring GitHub specific files and deployment script..." 103 | svn propset --quiet svn:ignore ".codeclimate.yml 104 | .git 105 | .gitattributes 106 | .github 107 | .gitignore 108 | .travis.yml 109 | composer.json 110 | composer.lock 111 | CONTRIBUTING.md 112 | deploy.sh 113 | phpcs.xml.dist 114 | phpmd.xml.dist 115 | phpunit-integration.xml.dist 116 | phpunit.xml.dist 117 | README.md 118 | tests" . 119 | echo "Done." 120 | 121 | printf "Adding new files..." 122 | svn stat | grep "^?" | awk '{print $2}' | xargs svn add --quiet 123 | echo "Done." 124 | 125 | printf "Removing old files..." 126 | svn stat | grep "^\!" | awk '{print $2}' | xargs svn remove --quiet 127 | echo "Done." 128 | 129 | printf "Enter a commit message for this new SVN version..." 130 | $DEFAULT_EDITOR /tmp/wppdcommitmsg.tmp 131 | COMMITMSG=`cat /tmp/wppdcommitmsg.tmp` 132 | rm /tmp/wppdcommitmsg.tmp 133 | echo "Done." 134 | 135 | printf "Committing new SVN version..." 136 | svn commit --quiet -m "$COMMITMSG" 137 | echo "Done." 138 | 139 | printf "Tagging and committing new SVN tag..." 140 | svn copy $SVNURL/trunk $SVNURL/tags/$NEWVERSION1 --quiet -m "tagged version $NEWVERSION1" 141 | echo "Done." 142 | 143 | printf "Removing temporary directory %s..." "$SVNPATH" 144 | rm -rf $SVNPATH/ 145 | echo "Done." 146 | 147 | echo 148 | echo "Plugin $PLUGINSLUG version $NEWVERSION1 has been successfully deployed." 149 | echo 150 | -------------------------------------------------------------------------------- /src/Plugin.php: -------------------------------------------------------------------------------- 1 | main_file = $main_file; 61 | 62 | $this->features = new Features(); 63 | $this->policies_setting = new Policies_Setting(); 64 | } 65 | 66 | /** 67 | * Registers the plugin with WordPress. 68 | * 69 | * @since 0.1.0 70 | */ 71 | public function register() { 72 | $this->policies_setting->register(); 73 | 74 | add_filter( 75 | 'user_has_cap', 76 | array( $this, 'grant_feature_policy_cap' ) 77 | ); 78 | 79 | add_action( 80 | 'send_headers', 81 | function() { 82 | $policy_headers = new Policy_Headers( $this->features, $this->policies_setting ); 83 | $policy_headers->send_headers(); 84 | } 85 | ); 86 | 87 | add_action( 88 | 'admin_menu', 89 | function() { 90 | $admin_screen = new Admin\Settings_Screen( $this->features, $this->policies_setting ); 91 | $admin_screen->register_menu(); 92 | } 93 | ); 94 | 95 | add_action( 96 | 'admin_enqueue_scripts', 97 | function( $hook_suffix ) { 98 | $admin_screen = new Admin\Pointers(); 99 | $admin_screen->enqueue_scripts( $hook_suffix ); 100 | } 101 | ); 102 | } 103 | 104 | /** 105 | * Gets the plugin basename, which consists of the plugin directory name and main file name. 106 | * 107 | * @since 0.1.0 108 | * 109 | * @return string Plugin basename. 110 | */ 111 | public function basename() { 112 | return plugin_basename( $this->main_file ); 113 | } 114 | 115 | /** 116 | * Gets the absolute path for a path relative to the plugin directory. 117 | * 118 | * @since 0.1.0 119 | * 120 | * @param string $relative_path Optional. Relative path. Default '/'. 121 | * @return string Absolute path. 122 | */ 123 | public function path( $relative_path = '/' ) { 124 | return plugin_dir_path( $this->main_file ) . ltrim( $relative_path, '/' ); 125 | } 126 | 127 | /** 128 | * Gets the full URL for a path relative to the plugin directory. 129 | * 130 | * @since 0.1.0 131 | * 132 | * @param string $relative_path Optional. Relative path. Default '/'. 133 | * @return string Full URL. 134 | */ 135 | public function url( $relative_path = '/' ) { 136 | return plugin_dir_url( $this->main_file ) . ltrim( $relative_path, '/' ); 137 | } 138 | 139 | /** 140 | * Gets the URL to the plugin's settings screen. 141 | * 142 | * @since 0.1.0 143 | * 144 | * @return string Settings screen URL. 145 | */ 146 | public function settings_screen_url() { 147 | return add_query_arg( 'page', Admin\Settings_Screen::SLUG, admin_url( Admin\Settings_Screen::PARENT_SLUG ) ); 148 | } 149 | 150 | /** 151 | * Dynamically grants the 'manage_feature_policy' capability based on 'manage_options'. 152 | * 153 | * This method is hooked into the `user_has_cap` filter and can be unhooked and replaced with custom functionality 154 | * if needed. 155 | * 156 | * @since 0.1.0 157 | * 158 | * @param array $allcaps Associative array of $cap => $grant pairs. 159 | * @return array Filtered $allcaps array. 160 | */ 161 | public function grant_feature_policy_cap( array $allcaps ) { 162 | if ( isset( $allcaps['manage_options'] ) ) { 163 | $allcaps[ Admin\Settings_Screen::CAPABILITY ] = $allcaps['manage_options']; 164 | } 165 | 166 | return $allcaps; 167 | } 168 | 169 | /** 170 | * Retrieves the main instance of the plugin. 171 | * 172 | * @since 0.1.0 173 | * 174 | * @return Plugin Plugin main instance. 175 | */ 176 | public static function instance() { 177 | return static::$instance; 178 | } 179 | 180 | /** 181 | * Loads the plugin main instance and initializes it. 182 | * 183 | * @since 0.1.0 184 | * 185 | * @param string $main_file Absolute path to the plugin main file. 186 | * @return bool True if the plugin main instance could be loaded, false otherwise. 187 | */ 188 | public static function load( $main_file ) { 189 | if ( null !== static::$instance ) { 190 | return false; 191 | } 192 | 193 | static::$instance = new static( $main_file ); 194 | static::$instance->register(); 195 | 196 | return true; 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /src/Features.php: -------------------------------------------------------------------------------- 1 | $feature_instance pairs. 34 | */ 35 | public function get_all() { 36 | if ( ! empty( $this->features ) ) { 37 | return $this->features; 38 | } 39 | 40 | $features = array( 41 | 'accelerometer' => array( 42 | 'title' => __( 'Accelerometer', 'feature-policy' ), 43 | 'default_origin' => Feature::ORIGIN_SELF, 44 | ), 45 | 'ambient-light-sensor' => array( 46 | 'title' => __( 'Ambient Light Sensor', 'feature-policy' ), 47 | 'default_origin' => Feature::ORIGIN_SELF, 48 | ), 49 | 'autoplay' => array( 50 | 'title' => __( 'Autoplay', 'feature-policy' ), 51 | 'default_origin' => Feature::ORIGIN_SELF, 52 | ), 53 | 'camera' => array( 54 | 'title' => __( 'Camera', 'feature-policy' ), 55 | 'default_origin' => Feature::ORIGIN_SELF, 56 | ), 57 | 'document-domain' => array( 58 | 'title' => __( 'document.domain', 'feature-policy' ), 59 | 'default_origin' => Feature::ORIGIN_ANY, 60 | ), 61 | 'document-write' => array( 62 | 'title' => __( 'document.write()', 'feature-policy' ), 63 | 'default_origin' => Feature::ORIGIN_ANY, 64 | ), 65 | 'encrypted-media' => array( 66 | 'title' => __( 'Encrypted Media', 'feature-policy' ), 67 | 'default_origin' => Feature::ORIGIN_SELF, 68 | ), 69 | 'fullscreen' => array( 70 | 'title' => __( 'Fullscreen', 'feature-policy' ), 71 | 'default_origin' => Feature::ORIGIN_SELF, 72 | ), 73 | 'geolocation' => array( 74 | 'title' => __( 'Geolocation', 'feature-policy' ), 75 | 'default_origin' => Feature::ORIGIN_SELF, 76 | ), 77 | 'gyroscope' => array( 78 | 'title' => __( 'Gyroscope', 'feature-policy' ), 79 | 'default_origin' => Feature::ORIGIN_SELF, 80 | ), 81 | 'layout-animations' => array( 82 | 'title' => __( 'Layout Animations', 'feature-policy' ), 83 | 'default_origin' => Feature::ORIGIN_ANY, 84 | ), 85 | 'lazyload' => array( 86 | 'title' => __( 'Lazy Loading', 'feature-policy' ), 87 | 'default_origin' => Feature::ORIGIN_ANY, 88 | ), 89 | 'legacy-image-formats' => array( 90 | 'title' => __( 'Legacy Image Formats', 'feature-policy' ), 91 | 'default_origin' => Feature::ORIGIN_ANY, 92 | ), 93 | 'magnetometer' => array( 94 | 'title' => __( 'Magnetometer', 'feature-policy' ), 95 | 'default_origin' => Feature::ORIGIN_SELF, 96 | ), 97 | 'microphone' => array( 98 | 'title' => __( 'Microphone', 'feature-policy' ), 99 | 'default_origin' => Feature::ORIGIN_SELF, 100 | ), 101 | 'midi' => array( 102 | 'title' => __( 'Midi', 'feature-policy' ), 103 | 'default_origin' => Feature::ORIGIN_SELF, 104 | ), 105 | 'oversized-images' => array( 106 | 'title' => __( 'Oversized Images', 'feature-policy' ), 107 | 'default_origin' => Feature::ORIGIN_ANY, 108 | ), 109 | 'payment' => array( 110 | 'title' => __( 'Payment', 'feature-policy' ), 111 | 'default_origin' => Feature::ORIGIN_SELF, 112 | ), 113 | 'picture-in-picture' => array( 114 | 'title' => __( 'Picture-in-Picture', 'feature-policy' ), 115 | 'default_origin' => Feature::ORIGIN_ANY, 116 | ), 117 | 'speaker' => array( 118 | 'title' => __( 'Speaker', 'feature-policy' ), 119 | 'default_origin' => Feature::ORIGIN_SELF, 120 | ), 121 | 'sync-script' => array( 122 | 'title' => __( 'Synchronous Scripts', 'feature-policy' ), 123 | 'default_origin' => Feature::ORIGIN_ANY, 124 | ), 125 | 'sync-xhr' => array( 126 | 'title' => __( 'Synchronous XHR', 'feature-policy' ), 127 | 'default_origin' => Feature::ORIGIN_ANY, 128 | ), 129 | 'unoptimized-images' => array( 130 | 'title' => __( 'Unoptimized Images', 'feature-policy' ), 131 | 'default_origin' => Feature::ORIGIN_ANY, 132 | ), 133 | 'unsized-media' => array( 134 | 'title' => __( 'Unsized Media', 'feature-policy' ), 135 | 'default_origin' => Feature::ORIGIN_ANY, 136 | ), 137 | 'usb' => array( 138 | 'title' => __( 'USB', 'feature-policy' ), 139 | 'default_origin' => Feature::ORIGIN_ANY, 140 | ), 141 | 'vertical-scroll' => array( 142 | 'title' => __( 'Vertical Scroll', 'feature-policy' ), 143 | 'default_origin' => Feature::ORIGIN_ANY, 144 | ), 145 | 'vr' => array( 146 | 'title' => __( 'VR', 'feature-policy' ), 147 | 'default_origin' => Feature::ORIGIN_SELF, 148 | ), 149 | ); 150 | 151 | $this->features = array(); 152 | foreach ( $features as $feature => $feature_info ) { 153 | $this->features[ $feature ] = new Feature( 154 | $feature, 155 | $feature_info 156 | ); 157 | } 158 | 159 | return $this->features; 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /src/Admin/Settings_Screen.php: -------------------------------------------------------------------------------- 1 | features = $features; 74 | $this->policies_setting = $policies_setting; 75 | } 76 | 77 | /** 78 | * Registers the menu item for the admin screen. 79 | * 80 | * @since 0.1.0 81 | */ 82 | public function register_menu() { 83 | $hook_suffix = add_submenu_page( 84 | self::PARENT_SLUG, 85 | __( 'Feature Policy Settings', 'feature-policy' ), 86 | __( 'Feature Policy', 'feature-policy' ), 87 | self::CAPABILITY, 88 | self::SLUG, 89 | array( $this, 'render' ) 90 | ); 91 | add_action( 92 | "load-{$hook_suffix}", 93 | function() { 94 | $this->add_settings_ui(); 95 | } 96 | ); 97 | } 98 | 99 | /** 100 | * Renders the admin screen. 101 | * 102 | * @since 0.1.0 103 | */ 104 | public function render() { 105 | ?> 106 | 117 |
118 |

119 | 120 |

121 | reports_screen_url() ) ) . '" class="page-title-action">' . esc_html__( 'View Violation Reports', 'feature-policy' ) . ''; 124 | } 125 | ?> 126 |
127 | 128 |

129 | 130 | %2$s %3$s', 133 | esc_url( _x( 'https://developers.google.com/web/updates/2018/06/feature-policy', 'learn more link', 'feature-policy' ) ), 134 | esc_html__( 'Learn more about Feature Policy', 'feature-policy' ), 135 | /* translators: accessibility text */ 136 | esc_html__( '(opens in a new tab)', 'feature-policy' ) 137 | ); 138 | ?> 139 |

140 | 141 |
142 | 143 | 144 | 145 |
146 |
147 | features->get_all(); 159 | $option = $this->policies_setting->get(); 160 | 161 | foreach ( $features as $feature ) { 162 | add_settings_field( 163 | $feature->name, 164 | $feature->title, 165 | function( $args ) use ( $feature, $option ) { 166 | $origin = isset( $option[ $feature->name ] ) ? $option[ $feature->name ][0] : $feature->default_origin; 167 | $this->render_field( $feature, $origin ); 168 | }, 169 | self::SLUG, 170 | 'default', 171 | array( 'label_for' => $feature->name ) 172 | ); 173 | } 174 | } 175 | 176 | /** 177 | * Renders the UI field for determining the status of a feature policy. 178 | * 179 | * @since 0.1.0 180 | * 181 | * @param Feature $feature Feature definition. 182 | * @param string $origin Origin set for the feature policy. 183 | */ 184 | protected function render_field( Feature $feature, $origin ) { 185 | $choices = array( 186 | Feature::ORIGIN_ANY => __( 'Any', 'feature-policy' ), 187 | Feature::ORIGIN_SELF => __( 'Self', 'feature-policy' ), 188 | Feature::ORIGIN_NONE => __( 'None', 'feature-policy' ), 189 | ); 190 | 191 | ?> 192 | 206 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | --------------------------------------------------------------------------------