Making WordPress.org

Ticket #7487: 7487-test-blueprint.3.diff

File 7487-test-blueprint.3.diff, 15.6 KB (added by tellyworth, 10 months ago)

Adds a dismiss button, and minor markup changes to the notice.

  • plugins/plugin-directory/api/routes/class-plugin-blueprint.php

     
    3939                if ( $request->get_param('zip_hash') ) {
    4040                        $this->reviewer_blueprint( $request, $plugin );
    4141                }
     42                if ( $request->get_param('url_hash') ) {
     43                        $this->developer_blueprint( $request, $plugin );
     44                }
    4245
    4346        $blueprints = get_post_meta( $plugin->ID, 'assets_blueprints', true );
    4447        // Note: for now, only use a file called `blueprint.json`.
     
    7578                // Direct zip preview for plugin reviewers
    7679                if ( $request->get_param('zip_hash') ) {
    7780                        foreach ( get_attached_media( 'application/zip', $plugin ) as $zip_file ) {
    78                                 if ( hash_equals( Template::preview_link_hash( $zip_file->ID, 0 ), $request->get_param('zip_hash') ) ||
    79                                      hash_equals( Template::preview_link_hash( $zip_file->ID, -1 ), $request->get_param('zip_hash') ) ) {
     81                                $zip_file_path = get_attached_file( $zip_file->ID );
     82                                if ( hash_equals( Template::preview_link_hash( $zip_file_path, 0 ), $request->get_param('zip_hash') ) ||
     83                                     hash_equals( Template::preview_link_hash( $zip_file_path, -1 ), $request->get_param('zip_hash') ) ) {
    8084                                        $zip_url = wp_get_attachment_url( $zip_file->ID );
    8185                                        if ( $zip_url ) {
     86                                                $is_pcp = 'pcp' === $request->get_param('type');
     87                                                $output = $this->generate_blueprint( $request, $plugin, $zip_url, $is_pcp, true );
    8288
    83                                                 $landing_page = '/wp-admin/plugins.php';
    84                                                 $activate_plugin = true;
    85                                                 $dependencies = $plugin->requires_plugins ?: [];
    86 
    87                                                 if ( stripos( $plugin->post_title, 'woocommerce' ) ) {
    88                                                         $dependencies[] = 'woocommerce';
     89                                                if ( $output ) {
     90                                                        header( 'Access-Control-Allow-Origin: https://playground.wordpress.net' );
     91                                                        die( $output );
    8992                                                }
    90                                                 if ( stripos( $plugin->post_title, 'buddypress' ) ) {
    91                                                         $dependencies[] = 'buddypress';
    92                                                 }
     93                                        }
     94                                }
     95                        }
     96                }
    9397
    94                                                 $dependencies = array_diff( $dependencies, [ $plugin->post_name ] );
     98                return new \WP_Error( 'invalid_blueprint', 'Invalid file', array( 'status' => 500 ) );
     99        }
    95100
    96                                                 // Plugin deactivated, and land on the Plugin Check page
    97                                                 if ( 'pcp' === $request->get_param('type') ) {
    98                                                         $landing_page = '/wp-admin/admin.php?page=plugin-check&plugin=' . sanitize_title( $request['plugin_slug'] );
    99                                                         $activate_plugin = false;
    100                                                         $dependencies = [];
    101                                                 }
     101        function developer_blueprint( $request, $plugin ) {
     102                // Generated blueprint for developers who haven't yet created a custom blueprint
     103                if ( $request->get_param('url_hash') ) {
     104                        $download_link = Template::download_link( $plugin );
     105                        if ( $download_link ) {
     106                                if ( hash_equals( Template::preview_link_hash( $download_link, 0 ), $request->get_param('url_hash') ) ||
     107                                        hash_equals( Template::preview_link_hash( $download_link, -1 ), $request->get_param('url_hash') ) ) {
     108                                        $output = $this->generate_blueprint( $request, $plugin, $download_link, false, false );
    102109
    103                                                 $zip_blueprint = (object)[
    104                                                         'landingPage' => $landing_page,
    105                                                         'preferredVersions' => (object)[
    106                                                                 'php' => '8.0',
    107                                                                 'wp'  => 'latest',
    108                                                         ],
    109                                                         'phpExtensionBundles' => [
    110                                                                 'kitchen-sink'
    111                                                         ],
    112                                                         'features' => (object)[
    113                                                                 'networking' => true
    114                                                         ],
    115                                                         'steps' => [
    116                                                                 (object)[
    117                                                                         'step' => 'installPlugin',
    118                                                                         'pluginZipFile' => (object)[
    119                                                                                 'resource' => 'wordpress.org/plugins',
    120                                                                                 'slug'     => 'plugin-check',
    121                                                                         ]
    122                                                                 ],
    123                                                                 (object)[
    124                                                                         'step' => 'installPlugin',
    125                                                                         'pluginZipFile' => (object)[
    126                                                                                 'resource' => 'url',
    127                                                                                 'url'      => $zip_url,
    128                                                                         ],
    129                                                                         'options' => (object)[
    130                                                                                 'activate' => (bool)$activate_plugin
    131                                                                         ]
    132                                                                 ],
    133                                                                 (object)[
    134                                                                         'step' => 'login',
    135                                                                         'username' => 'admin',
    136                                                                         'password' => 'password',
    137                                                                 ]
    138                                                         ]
    139                                                 ];
     110                                        if ( $output ) {
     111                                                header( 'Access-Control-Allow-Origin: https://playground.wordpress.net' );
     112                                                die( $output );
     113                                        }
     114                                }
     115                        }
     116                }
     117        }
    140118
    141                                                 if ( $dependencies ) {
    142                                                         $dep_step = [];
    143                                                         foreach ( $dependencies as $slug ) {
    144                                                                 $dep_step[] = (object)[
    145                                                                         'step' => 'installPlugin',
    146                                                                         'pluginZipFile' => [
    147                                                                                 'resource' => 'wordpress.org/plugins',
    148                                                                                 'slug'     => sanitize_title( $slug ),
    149                                                                         ],
    150                                                                         'options' => (object)[
    151                                                                                 'activate' => true
    152                                                                         ]
    153                                                                 ];
    154                                                         }
    155                                                         // Insert dependencies aftter PCP
    156                                                         array_splice( $zip_blueprint->steps, 1, 0, $dep_step );
    157                                                 }
     119        public function generate_blueprint( $request, $plugin, $zip_url, $install_pcp = true, $install_prh = true ) {
     120                $landing_page = '/wp-admin/plugins.php';
     121                $activate_plugin = true;
     122                $dependencies = $plugin->requires_plugins ?: [];
    158123
    159                                                 // Include the helper plugin too
    160                                                 $helper_zip = self::get_zip_url_by_slug( 'playground-review-helper' );
    161                                                 if ( $helper_zip && 'pcp' !== $request->get_param('type') ) {
    162                                                         $helper_step = [
    163                                                                 (object)[
    164                                                                         'step' => 'installPlugin',
    165                                                                         'pluginZipFile' => [
    166                                                                                 'resource' => 'url',
    167                                                                                 'url'      => $helper_zip,
    168                                                                         ],
    169                                                                         'options' => (object)[
    170                                                                                 'activate' => (bool)$activate_plugin
    171                                                                         ]
    172                                                                 ]
    173                                                         ];
    174                                                         array_splice( $zip_blueprint->steps, 1, 0, $helper_step );
    175                                                 }
     124                if ( stripos( $plugin->post_title, 'woocommerce' ) ) {
     125                        $dependencies[] = 'woocommerce';
     126                }
     127                if ( stripos( $plugin->post_title, 'buddypress' ) ) {
     128                        $dependencies[] = 'buddypress';
     129                }
    176130
    177                                                 $output = json_encode( $zip_blueprint );
     131                $dependencies = array_diff( $dependencies, [ $plugin->post_name ] );
    178132
    179                                                 if ( $output ) {
    180                                                         header( 'Access-Control-Allow-Origin: https://playground.wordpress.net' );
    181                                                         die( $output );
    182                                                 }
    183                                         }
    184                                 }
     133                // Plugin deactivated, and land on the Plugin Check page
     134                if ( $install_pcp ) {
     135                        $landing_page = '/wp-admin/admin.php?page=plugin-check&plugin=' . sanitize_title( $request['plugin_slug'] );
     136                        $activate_plugin = false;
     137                        $dependencies = [];
     138                }
     139
     140                $zip_blueprint = (object)[
     141                        'landingPage' => $landing_page,
     142                        'preferredVersions' => (object)[
     143                                'php' => '8.0',
     144                                'wp'  => 'latest',
     145                        ],
     146                        'phpExtensionBundles' => [
     147                                'kitchen-sink'
     148                        ],
     149                        'features' => (object)[
     150                                'networking' => true
     151                        ],
     152                ];
     153
     154                $steps = [];
     155
     156                // PCP first, if needed.
     157                if ( $install_pcp ) {
     158                        $steps[] = (object)[
     159                                'step' => 'installPlugin',
     160                                'pluginZipFile' => (object)[
     161                                        'resource' => 'wordpress.org/plugins',
     162                                        'slug'     => 'plugin-check',
     163                                ]
     164                        ];
     165                }
     166
     167                // Include the helper plugin too
     168                $helper_zip = self::get_zip_url_by_slug( 'playground-review-helper' );
     169                if ( $helper_zip && $install_prh ) {
     170                        $steps[] = (object)[
     171                                        'step' => 'installPlugin',
     172                                        'pluginZipFile' => [
     173                                                'resource' => 'url',
     174                                                'url'      => $helper_zip,
     175                                        ],
     176                                        'options' => (object)[
     177                                                'activate' => (bool)$activate_plugin
     178                                        ]
     179                                ];
     180                }
     181
     182                // Dependencies next
     183                if ( $dependencies ) {
     184                        foreach ( $dependencies as $slug ) {
     185                                $steps[] = (object)[
     186                                        'step' => 'installPlugin',
     187                                        'pluginZipFile' => [
     188                                                'resource' => 'wordpress.org/plugins',
     189                                                'slug'     => sanitize_title( $slug ),
     190                                        ],
     191                                        'options' => (object)[
     192                                                'activate' => true
     193                                        ]
     194                                ];
    185195                        }
    186196                }
    187197
    188                 return new \WP_Error( 'invalid_blueprint', 'Invalid file', array( 'status' => 500 ) );
     198                // Now the plugin itself
     199                $steps[] = (object)[
     200                                        'step' => 'installPlugin',
     201                                        'pluginZipFile' => (object)[
     202                                                'resource' => 'url',
     203                                                'url'      => $zip_url,
     204                                        ],
     205                                        'options' => (object)[
     206                                                'activate' => (bool)$activate_plugin
     207                                        ]
     208                                ];
    189209
     210                // Finally log in
     211                $steps[] = (object)[
     212                                        'step' => 'login',
     213                                        'username' => 'admin',
     214                                        'password' => 'password',
     215                                ];
     216
     217                $zip_blueprint->steps = $steps;
     218
     219                $output = json_encode( $zip_blueprint );
     220
     221                return $output;
    190222        }
    191223
    192224}
  • plugins/plugin-directory/api/routes/class-plugin-self-toggle-preview.php

     
    6363                ];
    6464                header( 'Location: ' . $result['location'] );
    6565
     66                if ( $request->get_param('dismiss') ) {
     67                        return $this->self_dismiss( $request, $plugin );
     68                }
     69
    6670                // Toggle the postmeta value. Note that there is a race condition here.
    6771                $did = '';
    6872                if ( get_post_meta( $plugin->ID, '_public_preview', true ) ) {
     
    8286                return $result;
    8387        }
    8488
     89        /**
     90         * Endpoint special case to dismiss the missing blueprint notice.
     91         *
     92         * @param \WP_REST_Request $request The Rest API Request.
     93         * @param object $plugin The plugin post.
     94         * @return bool True if the toggle was successful.
     95         */
     96        public function self_dismiss( $request, $plugin ) {
     97                $result = [
     98                        'location' => wp_get_referer() ?: get_permalink( $plugin ),
     99                ];
     100
     101                delete_post_meta( $plugin->ID, '_missing_blueprint_notice' );
     102
     103                return $result;
     104        }
    85105}
  • plugins/plugin-directory/class-template.php

     
    773773         */
    774774        public static function preview_link_zip( $slug, $attachment_id, $type = null ) {
    775775
    776                 $zip_hash = self::preview_link_hash( $attachment_id );
     776                $file = get_attached_file( $attachment_id );
     777                $zip_hash = self::preview_link_hash( $file );
    777778                if ( !$zip_hash ) {
    778779                        return false;
    779780                }
     
    787788        }
    788789
    789790        /**
     791         * Generate a live preview (playground) link for a published plugin that does not yet have a custom blueprint. Needed for developer testing.
     792         *
     793         * @param string $slug            The slug of the plugin post.
     794         * @param int $download_link      The URL of the zip download for the plugin.
     795         * @param bool $blueprint_only    False will return a full preview URL. True will return only a blueprint URL.
     796         * @return false|string           The preview or blueprint URL.
     797         */
     798        public static function preview_link_developer( $slug, $download_link, $blueprint_only = false ) {
     799
     800                $url_hash = self::preview_link_hash( $download_link );
     801                if ( !$url_hash ) {
     802                        return false;
     803                }
     804                $dev_blueprint = sprintf( 'https://wordpress.org/plugins/wp-json/plugins/v1/plugin/%s/blueprint.json?url_hash=%s', esc_attr( $slug ), esc_attr( $url_hash ) );
     805                if ( $blueprint_only ) {
     806                        return $dev_blueprint;
     807                }
     808                $url_preview = add_query_arg( 'blueprint-url', urlencode($dev_blueprint), 'https://playground.wordpress.net/' );
     809
     810                return $url_preview;
     811        }
     812
     813        /**
    790814         * Return a time-dependent variable for zip preview links.
    791815         *
    792816         * @param int $lifespan           The life span of the nonce, in seconds. Default is one week.
     
    799823        /**
    800824         * Return a nonce-style hash for zip preview links.
    801825         *
    802          * @param int $attachment_id      The ID of the attachment post corresponding to a plugin zip file.
     826         * @param string $zip_file        The filesystem path or URL of the zip file.
    803827         * @param int $tick_offest        Number to subtract from the nonce tick. Use both 0 and -1 to verify older nonces.
    804828         * @return false|string           The hash as a hex string; or false if the attachment ID is invalid.
    805829         */
    806         public static function preview_link_hash( $attachment_id, $tick_offset = 0 ) {
    807                 $file = get_attached_file( $attachment_id );
    808                 if ( !$file ) {
     830        public static function preview_link_hash( $zip_file, $tick_offset = 0 ) {
     831                if ( !$zip_file ) {
    809832                        return false;
    810833                }
    811834                $tick = self::preview_link_tick() - $tick_offset;
    812                 return wp_hash( $tick . '|' . $file, 'nonce' );
     835                return wp_hash( $tick . '|' . $zip_file, 'nonce' );
    813836        }
    814837
    815838        /**
     
    916939        }
    917940
    918941        /**
     942         * Generates a link to dismiss a missing blueprint notice.
     943         *
     944         * @param int|\WP_Post|null $post Optional. Post ID or post object. Defaults to global $post.
     945         * @return string URL to toggle status.
     946         */
     947        public static function get_self_dismiss_blueprint_notice_link( $post = null ) {
     948                $post = get_post( $post );
     949
     950                return add_query_arg(
     951                        array( '_wpnonce' => wp_create_nonce( 'wp_rest' ), 'dismiss' => 1 ),
     952                        home_url( 'wp-json/plugins/v1/plugin/' . $post->post_name . '/self-toggle-preview' )
     953                );
     954        }
     955
     956        /**
    919957         * Generates a link to enable Release Confirmations.
    920958         *
    921959         * @param int|\WP_Post|null $post Optional. Post ID or post object. Defaults to global $post.
  • plugins/plugin-directory/cli/class-import.php

     
    390390                        update_post_meta( $plugin->ID, 'assets_blueprints', wp_slash( $assets['blueprint'] ) );
    391391                } else {
    392392                        delete_post_meta( $plugin->ID, 'assets_blueprints' );
     393                        // TODO: maybe if ( $touches_stable_tag )?
     394                        add_post_meta( $plugin->ID, '_missing_blueprint_notice', 1, true );
    393395                }
    394396
    395397                // Store the block data, if known
  • themes/pub/wporg-plugins/inc/template-tags.php

     
    727727                        wp_kses_post( $import_warnings )
    728728                );
    729729        }
     730
     731        // This is less important, so only show if there are no other notices.
     732        if ( !$notice && !$import_warnings && get_post_meta( $post->ID, '_missing_blueprint_notice', true ) ) {
     733                $blueprint_test_button = sprintf(
     734                        '<a class="plugin-preview" target="_blank" href="%s">%s</a>',
     735                        Template::preview_link_developer( $post->post_name, Template::download_link() ),
     736                        esc_html__( 'Test your plugin in Playground', 'wporg-plugins' )
     737                );
     738                $blueprint_download_button = sprintf(
     739                        '<a class="plugin-preview" target="_blank" href="%s">%s</a>',
     740                        Template::preview_link_developer( $post->post_name, Template::download_link(), true ),
     741                        esc_html__( 'Download blueprint.json', 'wporg-plugins' )
     742                );
     743                $blueprint_dismiss_button = sprintf(
     744                        '<form method="POST" action="%s"><p><input type="submit" name="dismiss" value="%s" class="plugin-preview button button-secondary alignright" /></p></form>',
     745                        #'<a class="plugin-preview button button-secondary alignright" href="%s">%s</a>',
     746                        Template::get_self_dismiss_blueprint_notice_link( $post ),
     747                        esc_html__( 'dismiss', 'wporg-plugins' )
     748                );
     749
     750                // There is surely a neater way to format this.
     751                $blueprint_notice = sprintf(
     752                        '<ol><li>%s</li><li>%s</li><li>%s</li><li>%s</li></ol>',
     753                        $blueprint_test_button,
     754                        esc_html__( 'Fix any bugs in your plugin that prevent it from working in Playground.', 'wporg-plugins' ),
     755                        $blueprint_download_button,
     756                        esc_html__( 'Commit your blueprint to svn.', 'wporg-plugins' )
     757                );
     758                $blueprint_more = sprintf(
     759                        __( 'More info can be found in the <a href="%s">plugin handbook</a>.', 'wporg-plugins' ),
     760                        'https://developer.wordpress.org/plugins/wordpress-org/previews-and-blueprints/'
     761                );
     762
     763                printf(
     764                        '<div class="notice notice-info notice-alt">%s</div>',
     765                        '<p><strong>' . __( 'Your plugin does not yet have a blueprint file for user previews. If you\'d like to enable previews, please follow these steps to create a blueprint.', 'wporg-plugins' ) . '</strong></p>' .
     766                        $blueprint_notice .
     767                        $blueprint_dismiss_button .
     768                        '<p>' . $blueprint_more . '</p>'
     769                );
     770        }
    730771}