Making WordPress.org

Ticket #7487: 7487-test-blueprint.diff

File 7487-test-blueprint.diff, 12.3 KB (added by tellyworth, 11 months ago)

Initial draft patch for discussion and review

  • 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/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        /**
  • themes/pub/wporg-plugins/template-parts/plugin-single.php

     
    4545                                <a class="plugin-preview button download-button button-large" target="_blank" href="<?php echo esc_attr( add_query_arg( array( 'preview' => 1 ), get_the_permalink() ) ); ?>"><?php esc_html_e( 'Live Preview', 'wporg-plugins' ); ?></a>
    4646                        <?php elseif ( Template::preview_link() && Template::is_preview_available( $post, 'edit' ) ) : ?>
    4747                                <a class="plugin-preview button download-button button-large" target="_blank" href="<?php echo esc_attr( add_query_arg( array( 'preview' => 1 ), get_the_permalink() ) ); ?>"><?php esc_html_e( 'Test Preview', 'wporg-plugins' ); ?></a>
     48                        <?php elseif ( current_user_can( 'plugin_admin_view', $post ) ) : ?>
     49                                <a class="plugin-preview button download-button button-large" target="_blank" href="<?php echo Template::preview_link_developer( $post->post_name, Template::download_link() ); ?>"><?php esc_html_e( 'Test in Playground', 'wporg-plugins' ); ?></a>
     50                                <br />
     51                                <a class="plugin-preview button button-link alignright" target="_blank" href="<?php echo Template::preview_link_developer( $post->post_name, Template::download_link(), true ); ?>"><?php esc_html_e( 'Download blueprint.json', 'wporg-plugins' ); ?></a>
    4852                        <?php endif; ?>
    4953                </div>
    5054