Automattic / vip-governance-plugin

WordPress plugin that adds additional governance capabilities to the block editor.
https://wpvip.com
GNU General Public License v3.0
17 stars 1 forks source link
wordpress wordpress-plugin wpvip-plugin

WordPress VIP Block Governance plugin

This WordPress plugin adds additional governance capabilities to the block editor. This is accomplished via two dimensions:

We have approached this plugin from an opt-in standpoint. In other words, enabling this plugin without any rules will severely limit the editing experience. The goal is to create a stable editor with new blocks and features being enabled explicitly via rules, rather than implicitly via updates.

This plugin is currently developed for use on WordPress sites hosted on the VIP Platform.

Try it out

Try out the VIP Governance plugin in your browser with WordPress Playground.

Installation

To use the WordPress VIP Block Governance plugin after activation, skip to Usage.

Install on WordPress VIP

The WordPress VIP Block Governance plugin is authored and maintained by WordPress VIP, and made available to all WordPress sites by VIP MU plugins. Customers who host on WordPress VIP or use vip dev-env to develop locally have access to this plugin automatically. We recommend this activation method for WordPress VIP customers.

Enable the plugin by adding the method shown below to your application's [client-mu-plugins/plugin-loader.php][vip-go-skeleton-plugin-loader-example]:

// client-mu-plugins/plugin-loader.php

\Automattic\VIP\Integrations\activate( 'vip-governance' );

Create this path in your WordPress VIP site if it does not yet exist.

This will automatically install and activate the latest mu-plugins release of the WordPress VIP Block Governance plugin. Remove this line to deactivate the plugin.

Install via ZIP file

The latest version of the plugin can be downloaded from the repository's Releases page. Unzip the downloaded plugin and add it to the plugins/ directory of your site's GitHub repository.

Usage

Your governance rules are saved in governance-rules.json in your private folder. Before diving into how it's used, a quick run down of the schema will shed light on how it works.

Note: The private folder is only supported on VIP sites, or while using vip dev-env locally.

Schema Basics

You can find the schema definition used for the rules here. You can use https://api.wpvip.com/schemas/plugins/governance.json as the schema entry in your rules, to take advantage of code completion in most editors.

We have allowed significant space for customization. This means it is also possible to create unintended rule interactions. We recommend making rule changes one or two at a time to troubleshoot these interactions.

Each rule is an object in an array. The one required property is type, which can be default, role, or postType. Your rules should only have one entry of the default type, as described below, and it is the only type that is required in your rule set.

Rules not of type default require an additional field. These are broken down below, along with examples of their possible values:

Rule Type Required Field Possible Values
role roles name/slug of any default or custom roles
postType postTypes name/slug of any default or custom post types

Each rule can have any one of the following properties.

Non-default rule types will be merged with the default rule. This is done intentionally to avoid needless repetition of your default properties. If multiple non-default rule types are provided, they will be applied in the following ascending priority:

  1. Post Type
  2. Role

So if a matching postType and role rule is found, the role rule will be applied, and the postType rule will be ignored. The best analogy is the CSS cascade where more specific rules overwrite less specific rules. We are making a choice that Role-based rules should overwrite Post Type rules. We will introduce a filter in the near future to allow this priority to be customized.

Wildcards

The wildcard * can be used within allowedBlocks and within blockSettings to target more than 1 block. The intention is that it will limit repeated rules, and allow greater flexibility in controlling the editor experience.

For an example of this feature, refer to the example file here.

Note: allowedBlocks are not respected when a parent blockSettings also has a wildcard. For example, this will not work:

❌ Using allowedBlocks under a parent wildcard:
{
  "$schema": "https://api.wpvip.com/schemas/plugins/governance.json",
  "version": "1.0.0",
  "rules": [
    {
      "type": "default",
      "allowedFeatures": [ "codeEditor", "lockBlocks" ],
      "allowedBlocks": [ "core/*" ],
      "blockSettings": {
        "core/*": {
          "allowedBlocks": [ "core/paragraph", "core/heading" ],  // ← Not allowed under "core/*"
          "color": {
            "text": true,
          }
        }
      }
    }
  ]
}

Instead, only apply block settings to wildcards, and specify allowedBlocks to individual parent blocks:

✅ Using allowedBlocks under defined blocks:
{
  "$schema": "https://api.wpvip.com/schemas/plugins/governance.json",
  "version": "1.0.0",
  "rules": [
    {
      "type": "default",
      "allowedFeatures": [ "codeEditor", "lockBlocks" ],
      "allowedBlocks": [ "core/*" ],
      "blockSettings": {
        "core/*": {
          "color": {
            "text": true
          }
        },
        "core/quote": {
          "allowedBlocks": [ "core/paragraph", "core/heading" ]
        },
        "core/media-text": {
          "allowedBlocks": [ "core/paragraph", "core/heading" ]
        }
      }
    }
  ]
}

Quick Start

By default, the plugin uses this governance-rules.json. To start using the plugin with your own rules, you'll need to create your own governance-rules.json in your private folder. We recommend duplicating one of the starter rule sets provided below, and adapting it for your needs. In order to take advantage of the rules schema for in-editor support, use https://api.wpvip.com/schemas/plugins/governance.json.

With this default rule set, all blocks and all features are enabled. It is sensible to set your default rule to the settings you want for your least privileged user then add capabilities with role and/or post type-specific rules.

Starter Rule Sets

Below is some rule sets that you can use to build your governance-rules.json. They cover a wide range of use cases.

Default Rule Set

This is the default rule set used by the plugin.

{
  "$schema": "https://api.wpvip.com/schemas/plugins/governance.json",
  "version": "1.0.0",
  "rules": [
    {
      "type": "default",
      "allowedFeatures": [ "codeEditor", "lockBlocks" ],
      "allowedBlocks": [ "*" ]
    }
  ]
}

With this rule set, the following rules will apply:

Default Rule Set With Restrictions

This expands the default rule set by adding restrictions for all users and post types.

{
  "$schema": "https://api.wpvip.com/schemas/plugins/governance.json",
  "version": "1.0.0",
  "rules": [
    {
      "type": "default",
      "allowedFeatures": [ "codeEditor", "lockBlocks" ],
      "allowedBlocks": [ "core/group", "core/heading", "core/paragraph", "core/image" ],
      "blockSettings": {
        "core/group": {
          "spacing": {
            "spacingSizes": [
              {
                "size": "clamp(2.5rem, 6vw, 3rem)",
                "slug": "300",
                "name": "12"
              }
            ]
          }
        },
        "core/heading": {
          "color": {
            "palette": [
              {
                "color": "#ff0000",
                "name": "Custom red",
                "slug": "custom-red"
              },
              {
                "color": "#00FF00",
                "name": "Custom green",
                "slug": "custom-green"
              },
              {
                "color": "#FFFF00",
                "name": "Custom yellow",
                "slug": "custom-yellow"
              }
            ],
            "gradients": [
              {
                "slug": "vertical-red-to-green",
                "gradient": "linear-gradient(to bottom,#ff0000 0%,#00FF00 100%)",
                "name": "Vertical red to green"
              }
            ]
          },
          "typography": {
            "fontFamilies": [
              {
                "fontFamily": "Consolas, Fira Code, monospace",
                "slug": "code-font",
                "name": "Code Font"
              }
            ],
            "fontSizes": [
              {
                "name": "Large",
                "size": "2.75rem",
                "slug": "large"
              },
              {
                "name": "X-Large",
                "size": "3.75rem",
                "slug": "x-large"
              }
            ]
          }
        }
      }
    }
  ]
}

With this rule set, the following rules will apply:

Default and User Role Rule Set

This example focuses on providing a restrictive default rule set, and expanded permissions for a specific user role.

{
  "$schema": "https://api.wpvip.com/schemas/plugins/governance.json",
  "version": "1.0.0",
  "rules": [
    {
      "type": "role",
      "roles": [ "administrator" ],
      "allowedFeatures": [ "codeEditor", "lockBlocks" ],
      "allowedBlocks": [ "core/quote", "core/media-text", "core/image" ],
      "blockSettings": {
        "core/media-text": {
          "core/heading": {
            "color": {
              "text": true,
              "palette": [
                {
                  "color": "#ff0000",
                  "name": "Custom red",
                  "slug": "custom-red"
                }
              ]
            }
          }
        },
        "core/quote": {
          "core/paragraph": {
            "color": {
              "text": true,
              "palette": [
                {
                  "color": "#00FF00",
                  "name": "Custom green",
                  "slug": "custom-green"
                }
              ]
            }
          }
        }
      }
    },
    {
      "type": "default",
      "allowedBlocks": [ "core/heading", "core/paragraph" ],
      "blockSettings": {
        "core/heading": {
          "color": {
            "text": true,
            "palette": [
              {
                "color": "#FFFF00",
                "name": "Custom yellow",
                "slug": "custom-yellow"
              }
            ]
          }
        }
      }
    }
  ]
}

With this rule set, the following rules will apply:

Default and Post Type Rule Set

This example focuses on providing a restrictive default rule set, and expanded permissions for a specific post type.

{
  "$schema": "https://api.wpvip.com/schemas/plugins/governance.json",
  "version": "1.0.0",
  "rules": [
    {
      "type": "postType",
      "postTypes": [ "post" ],
      "allowedFeatures": [ "lockBlocks" ],
      "allowedBlocks": [ "core/quote", "core/image" ],
      "blockSettings": {
        "core/quote": {
          "allowedBlocks": [ "core/paragraph", "core/heading" ],
          "core/paragraph": {
            "color": {
              "text": true,
              "palette": [
                {
                  "color": "#00FF00",
                  "name": "Custom green",
                  "slug": "custom-green"
                }
              ]
            }
          }
        }
      }
    },
    {
      "type": "default",
      "allowedFeatures": [ "codeEditor" ],
      "allowedBlocks": [ "core/heading", "core/paragraph" ],
      "blockSettings": {
        "core/heading": {
          "color": {
            "text": true,
            "palette": [
              {
                "color": "#FFFF00",
                "name": "Custom yellow",
                "slug": "custom-yellow"
              }
            ]
          }
        }
      }
    }
  ]
}

With this rule set, the following rules will apply:

Default Wildcard Rule Set

This example focuses on providing a default rule set, using wildcards within the blockSettings and allowedBlocks. The use of a wildcard is helpful in targetting a wide variety of blocks, with minimal configuration.

{
  "$schema": "https://api.wpvip.com/schemas/plugins/governance.json",
  "version": "1.0.0",
  "rules": [
    {
      "type": "default",
      "allowedFeatures": [ "codeEditor", "lockBlocks" ],
      "allowedBlocks": [ "core/*" ],
      "blockSettings": {
        "core/heading": {
          "color": {
            "text": true,
            "palette": [
              {
                "color": "#FFFF00",
                "name": "Custom yellow",
                "slug": "custom-yellow"
              }
            ]
          }
        },
        "core/quote": {
          "allowedBlocks": [ "core/paragraph", "core/heading" ],
          "core/*": {
            "color": {
              "text": true,
              "palette": [
                {
                  "color": "#00FF00",
                  "name": "Custom green",
                  "slug": "custom-green"
                }
              ]
            }
          }
        }
      }
    }
  ]
}

With this rule set, the following rules will apply:

Limitations

Code Filters

There are filters in place that can be applied to change the behavior for what's allowed and what's not allowed.

vip_governance__governance_file_path

Change the governance rules file that's used by the plugin, based on a variety of filter options that are available. By default, it is set to the path to governance-rules.json in the private directory in a VIP site. For non-vip sites, it is set to the path to governance-rules.json in the plugin directory.

/**
 * Filter the governance file path, based on the filter options provided.
 *
 * Currently supported keys:
 *
 * site_id: The site ID for the current site.
 *
 * @param string $governance_file_path Path to the governance file.
 * @param array $filter_options Options that can be used as a filter for determining the right file.
 */
apply_filters( 'vip_governance__governance_file_path', $governance_file_path, $filter_options );

For example, this filter can be used to customize the rules file used for a network site:

add_filter( 'vip_governance__governance_file_path', function ( $governance_file_path, $filter_options ) {
    if ( isset( $filter_options['site_id'] ) && $filter_options['site_id'] === 2 ) {
        return WPCOM_VIP_PRIVATE_DIR . '/site/2/' . WPCOMVIP_GOVERNANCE_RULES_FILENAME;
    }

    return $governance_file_path;
}, 10, 2 );

vip_governance__governance_rules_json

Change, or programmatically set the governance rules used by the plugin, based on a variety of filter options that are available. By default, the rules read from the governance-rules.json are set regardless of a site being VIP or non-vip.

/**
 * Filter the governance rules, based on the filter options provided.
 *
 * Currently supported keys:
 *
 * site_id: The site ID for the current site.
 *
 * This filter can be used to either modify the governance rules content before it's parsed, or to generate the content dynamically.
 *
 * @param string $governance_rules_json Governance rules content.
 * @param array $filter_options Options that can be used as a filter for determining the right rules.
 */
apply_filters( 'vip_governance__governance_rules_json', $governance_rules_json, $filter_options );

For example, this filter can be used to programmatically set the rules used by the plugin instead of using the default rule set provided by the plugin:

add_filter( 'vip_governance__governance_rules_json', function ( $governance_rules_json, $filter_options ) {
            return '{
                "$schema": "https://api.wpvip.com/schemas/plugins/governance.json",
                "version": "1.0.0",
                "rules": [
                    {
                    "type": "default",
                    "allowedFeatures": [ "codeEditor", "lockBlocks" ],
                    "allowedBlocks": [ "core/heading", "core/paragraph" ],
                    "blockSettings": {
                        "core/heading": {
                        "typography": {
                            "fontFamilies": [
                            {
                                "name": "Arial",
                                "slug": "arial",
                                "css": "Arial, sans-serif"
                            }
                            ]
                        }
                        }
                    }
                    }
                ]
            }';
        }, 10, 2 );

This way the governance-rules.json no longer needs to be created outside the plugin.

vip_governance__is_block_allowed_for_insertion

Change what blocks are allowed to be inserted in the block editor. By default, root level and children blocks are compared against the governance rules, and then a decision is made to allow or reject them. This filter will allow you to override the default logic for insertion.

/**
 * Change what blocks are allowed to be inserted in the block editor.
 *
 * @param {bool}     isAllowed        Whether or not the block will be allowed.
 * @param {string}   blockName        The name of the block to be inserted.
 * @param {string[]} parentBlockNames An array of zero or more parent block names,
 *                                    starting with the most recent parent ancestor.
 * @param {Object}   governanceRules  An object containing the full set of governance
 *                                    rules for the current user.
 */
return applyFilters(
    'vip_governance__is_block_allowed_for_insertion',
    isAllowed,
    blockType.name,
    parentBlockNames,
    governanceRules
);

For example, this filter can be used to allow the insertion of a custom block even if it's not allowed by the governance rules:

addFilter(
    'vip_governance__is_block_allowed_for_insertion',
    'example/allow-custom-block-insertion',
    ( isAllowed, blockName, parentBlockNames, governanceRules ) => {
        if ( blockName === 'custom/my-amazing-block' ) {
            return true;
        }

        return isAllowed;
    }
);

vip_governance__is_block_allowed_for_editing

Change what blocks are allowed to be edited in the block editor. Disabled blocks will display with a grey border and will not be editable. By default, root level and children blocks are compared against the governance rules, and then a decision is made to allow or reject them. This filter will allow you to override the default logic for editing.

/**
 * Change what blocks are allowed to be edited in the block editor.
 *
 * @param {bool}     isAllowed        Whether or not the block will be allowed.
 * @param {string}   blockName        The name of the block to be edited.
 * @param {string[]} parentBlockNames An array of zero or more parent block names,
 *                                    starting with the most recent parent ancestor.
 * @param {Object}   governanceRules  An object containing the full set of governance
 *                                    rules for the current user.
 */
applyFilters(
    'vip_governance__is_block_allowed_for_editing',
    isAllowed,
    blockName,
    parentBlockNames,
    governanceRules
);

For example, this filter can be used to allow the editing of a custom block type even if it is disabled by governance rules:

addFilter(
    'vip_governance__is_block_allowed_for_editing',
    'example/allow-custom-block-editing',
    ( isAllowed, blockName, parentBlockNames, governanceRules ) => {
        if ( blockName === 'custom/my-amazing-block' ) {
            return true;
        }

        return isAllowed;
    }
);

vip_governance__is_block_allowed_in_hierarchy

Select the mode that's used for determining if a block should be allowed or not, between cascading and restrictive. Cascading works similarly to CSS in that, the rules of the parent are looked up first, followed by the root-level rules for determining if the block is to be allowed or not. On the other hand, restrictive only looks up the rules under the parent. If there are no rules under a parent or a block is not allowed under a parent, then that block cannot be inserted. Cascading allows for a simpler rule file avoiding excessive repetition of blocks under a parent. Restrictive does result in more repetition in the rules file, but it results in a more locked-down editor experience. By default, the filter is set to cascading mode. Note that, you have access to the parent block names, block name, and the governance rules in order to decide what mode should be used. So you can fine tune the mode based on any of these values.

/**
 * Select the mode used to determine if a block should be allowed or not, between cascading and restrictive.
 *
 * @param {bool}                      True, if cascading mode is to be used or false if restrictive is to be used.
 * @param {string}   blockName        The name of the block to be edited.
 * @param {string[]} parentBlockNames An array of zero or more parent block names,
 *                                    starting with the most recent parent ancestor.
 * @param {Object}   governanceRules  An object containing the full set of governance
 *                                    rules for the current user.
 */
  applyFilters(
    'vip_governance__is_block_allowed_in_hierarchy',
    true,
    blockName,
    parentBlockNames,
    governanceRules
  );

Admin Settings

There is an admin settings menu titled VIP Block Governance that's created with the use of this plugin. This page offers:

Admin setting in action

Endpoints

vip-governance/v1/<role>/rules

This endpoint is used to return the combined rules for a given role. This API is utilized by the settings page to visualize merged default and role rules for a selected role. It's only available to users with the manage_options permission.

It has only three root level keys: allowedBlocks, blockSettings, and allowedFeatures.

Example

This example involves making a call to http://my.site/wp-json/vip-governance/v1/editor/rules for an editor role, while using this rule file found in the starter rule sets:

{
  "allowedBlocks": [ "core/heading", "core/paragraph" ],
  "blockSettings": {
    "core/heading": {
      "color": {
        "text": true,
        "palette": [
          {
            "color": "#FFFF00",
            "name": "Custom yellow",
            "slug": "custom-yellow"
          }
        ]
      }
    }
  },
  "allowedFeatures": []
}

Analytics

Please note this is for VIP sites only. Analytics are disabled if this plugin is not being run on VIP sites.

The plugin records two data points for analytics, on VIP sites:

  1. A usage metric when the block editor is loaded with the WordPress VIP Block Governance plugin activated. This analytic data simply is a counter, and includes no information about the post's content or metadata. It will only include the customer site ID to associate the usage.

  2. When an error occurs from within the plugin on the WordPress VIP platform. This is used to identify issues with customers for private follow-up.

Both of these data points are a counter that is incremented and do not contain any other telemetry or sensitive data. You can see what's being collected in code here.

Development

In order to ensure no dev dependencies are installed, the following can be done while installing the packages:

composer install --no-dev

Tests

We currently have unit, and e2e tests to ensure thorough code coverage of the plugin. These tests can be run locally with wp-env and Docker.

For the PHP unit tests:

wp-env start
composer install
composer run test

For the JS unit tests:

npm install
npm run test:js

For the e2e tests:

wp-env start
composer install
npm install
npx playwright install chromium --with-deps
npx playwright test