├── .gitignore ├── 20180820plugincompatibility.csv ├── README.md ├── index.php ├── knack.js ├── launch-environment.php └── plugin-stats.php /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | local-config.php 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Gutenberg Plugin Compatibility 2 | ============================== 3 | 4 | **Hello! This research project has concluded, and the database is no longer being actively maintained.** 5 | 6 | ## Overview 7 | 8 | This project's goal is to produce a [database](https://plugincompat.danielbachhuber.com/) documenting whether or not WordPress plugins are compatible with [Gutenberg](https://wordpress.org/gutenberg/). For any questions beyond what's covered in this document, [please open an issue](https://github.com/danielbachhuber/gutenberg-plugin-compatibility/issues) and we'll do our best to help out. The complete backstory is covered in [wordpress/gutenberg#4072](https://github.com/WordPress/gutenberg/issues/4072). 9 | 10 | **_How do I know whether a plugin is compatible with Gutenberg?_** 11 | 12 | For our purposes, a plugin is compatible with Gutenberg when: 13 | 14 | * A WordPress user can perform the same functional task with Gutenberg active. For instance, if the plugin includes an "Add Media" button, it's considered Gutenberg-compatible when it has a block registered for the Gutenberg inserter. Feature-parity, essentially. 15 | * There are no (obvious) errors when the WordPress plugin is active alongside Gutenberg. 16 | 17 | Once a plugin is manually reviewed in a test environment, it's either marked `is_compatible=yes` or `is_compatible=no` in the database. Some plugins are pre-classified as `is_compatible=likely_yes` or `is_compatible=likely_no` based on reasonable assumptions (e.g. a caching plugin probably doesn't expose editor-specific functionality). 18 | 19 | **_Why are you doing this?_** 20 | 21 | We want to make sure everyone can use Gutenberg when WordPress 5.0 is released. Plugin incompatibility is statistically one of the most likely reasons they won't be able to. Having this compatibility data will help us strategize the release process. 22 | 23 | The WordPress.org Plugins Directory has an incredibly long tail distribution of `active_installs`. The 5000 plugins listed in the Gutenberg Compatibility Database [represent >90% of the total](https://danielbachhuber.com/2018/01/04/brief-wordpress-org-plugin-directory-data-analysis/) `active_installs` count. 24 | 25 | **_This sounds important. What can I do to help?_** 26 | 27 | See [Testing](#testing) for more details. 28 | 29 | ## Testing 30 | 31 | Before you begin, you'll need to [register for an account](https://plugincompat.danielbachhuber.com/#account-details/) if you haven't done so already. Our ideal testers are people who have time to test dozens of plugins. Each plugin can take a minute to test, so you can probably work through 30 plugins in 30 minutes. 32 | 33 | Visual learner? [Watch this video walkthrough](https://www.youtube.com/watch?v=qX4iZ9hCPyk), and then read the rest of the documentation (which has important specifics). 34 | 35 | ### Creating the test environment 36 | 37 | ![image](https://user-images.githubusercontent.com/36432/36801021-ca3a27ea-1c65-11e8-956c-02d460ba07e8.png) 38 | 39 | Once you've logged in, click the "Create Test Environment" button to create a fresh WordPress sandbox (using [WP Sandbox](https://wpsandbox.io/)) with both Gutenberg and a randomly-selected plugin with `is_compatible=unknown`. In this process, `is_compatible` is set to `testing` for the plugin. It can take several seconds for the sandbox to spin up. 40 | 41 | ### Opening the WordPress admin 42 | 43 | ![image](https://user-images.githubusercontent.com/36432/36801215-4ed987d4-1c66-11e8-9815-7a5b4316eb9f.png) 44 | 45 | After the sandbox is created, you'll be taken to the randomly-selected plugin's reporting form. Click the "Open Editor" button to access the Manage Posts screen in the WordPress admin. 46 | 47 | ### Manually evaluating compatibility 48 | 49 | ![image](https://user-images.githubusercontent.com/36432/36803008-47befaa6-1c6b-11e8-8d83-b4491044b4f1.png) 50 | 51 | In the WordPress backend, first look at the Classic Editor to see if the plugin exposes any editor-specific functionality (e.g. a meta box, TinyMCE button, etc.). Use your best judgement in the process; plugin may require some configuration before it exposes editor-specific functionality. 52 | 53 | **If the plugin does expose functionality in the Classic Editor**, open the Gutenberg Editor to see if the user can perform the same functional task. Everything should work 100% as expected for the plugin to be considered completely compatible. Even little bugs should be considered incompatibilities; this is valuable data to document. 54 | 55 | Common incompatibilities include: 56 | 57 | * Plugin adds an "Add Media" button in the Classic Editor, which doesn't exist in Gutenberg. 58 | * Plugin renders a metabox in Gutenberg that isn't fully-functional for some reason. 59 | 60 | **If the plugin doesn't expose editor UI**, then it's likely compatible with Gutenberg. But again, use your best judgement and assess the plugin's description, etc. 61 | 62 | ### Recording compatibility findings 63 | 64 | ![image](https://user-images.githubusercontent.com/36432/36802794-b88240c8-1c6a-11e8-99ae-1370d99cfafd.png) 65 | 66 | When you feel confident with your assessment, log your findings in the database: 67 | 68 | * Mark `is_compatible=yes` or `is_compatible=no` based on your judgement. 69 | * Categorize the compatibility details based on one or more of the available options. 70 | * Use the open-ended text field to clarify your response with sufficient detail for the next person who reads it. 71 | * "Tested version" is the plugin version, not the Gutenberg version. This should be automatically populated when the sandbox is created. 72 | * If you can find some official conversation about Gutenberg compatibility for the plugin, please include that link as well. 73 | 74 | Double-check all of the data you've entered. Once you're satisfied hit "Submit" to save your results, and then click "Create New Test Environment" to launch an environment with a different plugin. 75 | 76 | ## Meta 77 | 78 | This repository is a bunch of files that power the Gutenberg Plugin Compatibility database: 79 | 80 | 1. [plugin-stats.php](plugin-stats.php) downloads key plugin data from the WordPress.org REST API. 81 | 2. [Knack](https://www.knack.com/) is a SaaS application used to store our database and make it editable. 82 | 3. [index.php](index.php) renders the webpage with the Knack database application. 83 | 4. [knack.js](knack.js) powers our logic to launch a new environment and bring you to the edit plugin view. 84 | 5. [launch-environment.php](launch-environment.php) calls out to create the sandbox environment. 85 | 86 | Everything in the repository is set up to auto-deploy to the server that hosts [plugincompat.danielbachhuber.com](https://plugincompat.danielbachhuber.com). 87 | -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | Gutenberg Plugin Compatibility 10 | 11 | 12 | 13 | 14 | 41 | 42 | 43 | 44 | 51 | 52 | 53 | 54 | 55 |
56 | 59 | 60 |
61 |

This project is now complete.

62 | 63 |

The Gutenberg Plugin Compatibility database started as a research project and is no longer actively being pursued. The database has been shut down because it's not actively maintained.

64 | 65 |

If you'd like to review the final data, you can download the database as CSV.

66 | 67 |
68 | 69 | 72 | 73 |
74 | 75 | Fork me on GitHub 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /knack.js: -------------------------------------------------------------------------------- 1 | $(document).on('knack-scene-render.any', function(event, page) { 2 | var launchTestButton = $('#launch-test-button'), 3 | editorButton = $('#editor-button'), 4 | errorMessage = $('#error-message'); 5 | 6 | launchTestButton.off( 'click.launch-test' ); 7 | errorMessage.hide(); 8 | 9 | if (typeof Knack.getUserToken() !== 'undefined' ) { 10 | launchTestButton.removeAttr('disabled').show(); 11 | $('#requires-login').hide(); 12 | $('#register-login').hide(); 13 | // Force logged-in user back to compatibility results 14 | if ( 0 === window.location.hash.indexOf('#account-details' ) ) { 15 | window.location.hash = '#compatibility-results'; 16 | } 17 | } else { 18 | launchTestButton.hide(); 19 | $('#requires-login').show(); 20 | $('#register-login').removeAttr('disabled').show(); 21 | } 22 | 23 | launchTestButton.on( 'click.launch-test', function(ev) { 24 | ev.stopPropagation(); 25 | ev.preventDefault(); 26 | editorButton.attr('disabled','disabled' ).hide(); 27 | fetchNextTest(); 28 | }); 29 | 30 | function handleError(xhr) { 31 | Knack.hideSpinner(); 32 | errorMessage.find('p').text(xhr.responseText); 33 | errorMessage.show(); 34 | } 35 | 36 | function fetchNextTest() { 37 | launchTestButton.attr('disabled', 'disabled'); 38 | launchTestButton.text('Creating (takes 5-10 seconds)...'); 39 | var baseURL = 'https://api.knack.com/v1/pages/scene_1/views/view_1/records'; 40 | var requestFilters = [ 41 | { 42 | 'field':'field_2', 43 | 'operator':'is', 44 | 'value':'unknown' 45 | } 46 | ]; 47 | var requestURL = baseURL + '?filters=' + encodeURIComponent(JSON.stringify(requestFilters)); 48 | Knack.showSpinner(); 49 | $.ajax({ 50 | url: requestURL, 51 | type: "GET", 52 | beforeSend: function(xhr){ 53 | xhr.setRequestHeader('Authorization', Knack.getUserToken() ); 54 | xhr.setRequestHeader('X-Knack-Application-Id', Knack.application_id ); 55 | xhr.setRequestHeader('content-type', 'application/json'); 56 | }, 57 | }).success(function(result) { 58 | var plugin = result.records[Math.floor(Math.random() * result.records.length)]; 59 | var pluginID = plugin.id; 60 | var pluginSlug = plugin.field_1; 61 | $.ajax({ 62 | url: '/launch-environment.php?plugins=' + pluginSlug, 63 | type: 'GET', 64 | cache: false 65 | }).success(function( sandboxUrl ){ 66 | setTestingState(pluginID, pluginSlug, sandboxUrl); 67 | }).error(handleError); 68 | }).error(handleError); 69 | } 70 | 71 | function setTestingState(pluginID, slug, sandboxUrl) { 72 | var baseURL = 'https://api.knack.com/v1/pages/scene_1/views/view_1/records/'; 73 | var requestURL = baseURL + pluginID; 74 | var requestData = { 75 | 'field_2':'testing', 76 | 'field_2_raw':'testing', 77 | }; 78 | Knack.showSpinner(); 79 | $.ajax({ 80 | url: 'https://api.wordpress.org/plugins/info/1.0/' + slug + '.json', 81 | type: 'GET', 82 | }).success(function( plugin ){ 83 | requestData['field_4'] = plugin['version']; 84 | $.ajax({ 85 | url: requestURL, 86 | type: 'PUT', 87 | data: JSON.stringify(requestData), 88 | beforeSend: function(xhr){ 89 | xhr.setRequestHeader('Authorization', Knack.getUserToken() ); 90 | xhr.setRequestHeader('X-Knack-Application-Id', Knack.application_id ); 91 | xhr.setRequestHeader('content-type', 'application/json'); 92 | }, 93 | }).success(function(result) { 94 | Knack.hideSpinner(); 95 | launchTestButton.removeAttr('disabled'); 96 | launchTestButton.text('Create New Test Environment'); 97 | editorButton.removeAttr('disabled'); 98 | editorButton.attr( 'href', sandboxUrl ); 99 | editorButton.show(); 100 | // Load the correct view 101 | window.location.hash = '#compatibility-results/edit-plugin/' + pluginID + '/'; 102 | }).error(handleError); 103 | }).error(handleError); 104 | } 105 | 106 | }); 107 | -------------------------------------------------------------------------------- /launch-environment.php: -------------------------------------------------------------------------------- 1 | select( 'gutenberg_plugins', $wpdb->dbh ); 9 | 10 | $original_request_url = 'https://wordpress.org/plugins/wp-json/plugins/v1/query-plugins?s=&posts_per_page=100'; 11 | $paged = 1; 12 | 13 | do { 14 | $request_url = $original_request_url . '&paged=' . $paged; 15 | WP_CLI::log( 'Requesting: ' . $request_url ); 16 | $response = Utils\http_request( 'GET', $request_url ); 17 | $list_body = json_decode( $response->body, true ); 18 | if ( ! empty( $list_body['plugins'] ) ) { 19 | foreach ( $list_body['plugins'] as $plugin_name ) { 20 | WP_CLI::log( ' -> ' . $plugin_name ); 21 | $plugin_url = 'https://wordpress.org/plugins/wp-json/plugins/v1/plugin/' . $plugin_name; 22 | $response = Utils\http_request( 'GET', $plugin_url ); 23 | $plugin = json_decode( $response->body, true ); 24 | $tags = ! empty( $plugin['tags'] ) ? implode( ', ', $plugin['tags'] ) : ''; 25 | $last_updated = human_time_diff( strtotime( $plugin['last_updated'] ), time() ); 26 | $wpdb->query( $wpdb->prepare( 'INSERT INTO plugins (plugin_slug, active_installs, last_updated, short_description, tags) VALUES(%s, %s, %s, %s, %s) ON DUPLICATE KEY UPDATE active_installs=%s, last_updated=%s, short_description=%s, tags=%s', $plugin_name, $plugin['active_installs'], $last_updated, $plugin['short_description'], $tags, $plugin['active_installs'], $last_updated, $plugin['short_description'], $tags ) ); 27 | } 28 | } else { 29 | $request_url = false; 30 | } 31 | $paged++; 32 | } while( $request_url ); 33 | 34 | WP_CLI::success( 'All done' ); 35 | --------------------------------------------------------------------------------