<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[13076] sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory: Plugin Dir: Add Preview links for moderators in plugin queue</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { white-space: pre-line; overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta" style="font-size: 105%">
<dt style="float: left; width: 6em; font-weight: bold">Revision</dt> <dd><a style="font-weight: bold" href="http://meta.trac.wordpress.org/changeset/13076">13076</a><script type="application/ld+json">{"@context":"http://schema.org","@type":"EmailMessage","description":"Review this Commit","action":{"@type":"ViewAction","url":"http://meta.trac.wordpress.org/changeset/13076","name":"Review Commit"}}</script></dd>
<dt style="float: left; width: 6em; font-weight: bold">Author</dt> <dd>tellyworth</dd>
<dt style="float: left; width: 6em; font-weight: bold">Date</dt> <dd>2023-12-21 05:40:09 +0000 (Thu, 21 Dec 2023)</dd>
</dl>

<pre style='padding-left: 1em; margin: 2em 0; border-left: 2px solid #ccc; line-height: 1.25; font-size: 105%; font-family: sans-serif'>Plugin Dir: Add Preview links for moderators in plugin queue

This adds private Playground Preview links for the plugin review team to more easily test newly submitted plugins that have not yet been published.

Plugins are loaded with plugin-check active.

See <a href="http://meta.trac.wordpress.org/ticket/7380">#7380</a>.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#sitestrunkwordpressorgpublic_htmlwpcontentpluginsplugindirectoryadminlisttableclasspluginpostsphp">sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/admin/list-table/class-plugin-posts.php</a></li>
<li><a href="#sitestrunkwordpressorgpublic_htmlwpcontentpluginsplugindirectoryadminmetaboxclassreviewtoolsphp">sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/admin/metabox/class-review-tools.php</a></li>
<li><a href="#sitestrunkwordpressorgpublic_htmlwpcontentpluginsplugindirectoryapiroutesclasspluginblueprintphp">sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/api/routes/class-plugin-blueprint.php</a></li>
<li><a href="#sitestrunkwordpressorgpublic_htmlwpcontentpluginsplugindirectoryclasstemplatephp">sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/class-template.php</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="sitestrunkwordpressorgpublic_htmlwpcontentpluginsplugindirectoryadminlisttableclasspluginpostsphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/admin/list-table/class-plugin-posts.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/admin/list-table/class-plugin-posts.php 2023-12-21 00:25:28 UTC (rev 13075)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/admin/list-table/class-plugin-posts.php   2023-12-21 05:40:09 UTC (rev 13076)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -711,10 +711,11 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        $name = explode( '_', $name, 3 )[2];
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                        printf(
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                '<a href="%1$s">%2$s</a><br>%3$s</li>',
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         '<a href="%1$s">%2$s</a><br>%3$s<br>(<a href="%4$s" target="_blank">preview</a>)</li>',
</ins><span class="cx" style="display: block; padding: 0 10px">                                 esc_url( $url ),
</span><span class="cx" style="display: block; padding: 0 10px">                                esc_html( $name ),
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                esc_html( $zip_size )
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         esc_html( $zip_size ),
+                               esc_url( Template::preview_link_zip( $post->post_name, $zip_file->ID ) )
</ins><span class="cx" style="display: block; padding: 0 10px">                         );
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span></span></pre></div>
<a id="sitestrunkwordpressorgpublic_htmlwpcontentpluginsplugindirectoryadminmetaboxclassreviewtoolsphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/admin/metabox/class-review-tools.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/admin/metabox/class-review-tools.php    2023-12-21 00:25:28 UTC (rev 13075)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/admin/metabox/class-review-tools.php      2023-12-21 05:40:09 UTC (rev 13076)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -127,13 +127,15 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        foreach ( $zip_files as $zip_date => $zip ) {
</span><span class="cx" style="display: block; padding: 0 10px">                                list( $zip_url, $zip_file ) = $zip;
</span><span class="cx" style="display: block; padding: 0 10px">                                $zip_size                   = size_format( filesize( get_attached_file( $zip_file->ID ) ), 1 );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                $zip_preview                = Template::preview_link_zip( $slug, $zip_file->ID );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                                printf(
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                        '<li>%1$s <a href="%2$s">%3$s</a> (%4$s)</li>',
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                 '<li>%1$s <a href="%2$s">%3$s</a> (%4$s) (<a href="%5$s" target="_blank">preview</a>)</li>',
</ins><span class="cx" style="display: block; padding: 0 10px">                                         esc_html( $zip_date ),
</span><span class="cx" style="display: block; padding: 0 10px">                                        esc_url( $zip_url ),
</span><span class="cx" style="display: block; padding: 0 10px">                                        esc_html( basename( $zip_url ) ),
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                        esc_html( $zip_size )
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                 esc_html( $zip_size ),
+                                       esc_url( $zip_preview )
</ins><span class="cx" style="display: block; padding: 0 10px">                                 );
</span><span class="cx" style="display: block; padding: 0 10px">                        }
</span><span class="cx" style="display: block; padding: 0 10px">                        echo '</ul>';
</span></span></pre></div>
<a id="sitestrunkwordpressorgpublic_htmlwpcontentpluginsplugindirectoryapiroutesclasspluginblueprintphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/api/routes/class-plugin-blueprint.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/api/routes/class-plugin-blueprint.php   2023-12-21 00:25:28 UTC (rev 13075)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/api/routes/class-plugin-blueprint.php     2023-12-21 05:40:09 UTC (rev 13076)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -17,6 +17,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                register_rest_route( 'plugins/v1', '/plugin/(?P<plugin_slug>[^/]+)/blueprint.json', array(
</span><span class="cx" style="display: block; padding: 0 10px">                        'methods'             => array( \WP_REST_Server::READABLE, \WP_REST_Server::CREATABLE ),
</span><span class="cx" style="display: block; padding: 0 10px">                        'callback'            => array( $this, 'blueprint' ),
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                        // Note: the zip part of the endpoint is also public, since playground requests blueprints without cookie credentials
</ins><span class="cx" style="display: block; padding: 0 10px">                         'permission_callback' => '__return_true',
</span><span class="cx" style="display: block; padding: 0 10px">                        'args'                => array(
</span><span class="cx" style="display: block; padding: 0 10px">                                'plugin_slug' => array(
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -35,6 +36,54 @@
</span><span class="cx" style="display: block; padding: 0 10px">        public function blueprint( $request ) {
</span><span class="cx" style="display: block; padding: 0 10px">                $plugin = Plugin_Directory::get_plugin_post( $request['plugin_slug'] );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                // Direct zip preview for plugin reviewers
+               if ( $request->get_param('zip_hash') ) {
+                       foreach ( get_attached_media( 'application/zip', $plugin ) as $zip_file ) {
+                               if ( hash_equals( Template::preview_link_hash( $zip_file->ID, 0 ), $request->get_param('zip_hash') ) ||
+                                    hash_equals( Template::preview_link_hash( $zip_file->ID, -1 ), $request->get_param('zip_hash') ) ) {
+                                       $zip_url = wp_get_attachment_url( $zip_file->ID );
+                                       $zip_blueprint =<<<EOF
+{
+    "landingPage": "/wp-admin/plugins.php",
+    "preferredVersions": {
+        "php": "8.0",
+        "wp": "latest"
+       },
+    "phpExtensionBundles": [
+        "kitchen-sink"
+    ],
+    "features": {
+        "networking": true
+    },
+    "steps": [
+               {
+            "step": "installPlugin",
+            "pluginZipFile": {
+                "resource": "wordpress.org/plugins",
+                "slug": "plugin-check"
+            }
+        },
+        {
+            "step": "installPlugin",
+            "pluginZipFile": {
+                "resource": "url",
+                "url": "$zip_url"
+            }
+               },
+               {
+            "step": "login",
+            "username": "admin",
+            "password": "password"
+        }
+       ]
+}
+EOF;
+                                       header( 'Access-Control-Allow-Origin: https://playground.wordpress.net' );
+                                       die( $zip_blueprint );
+                               }
+                       }
+               }
+
</ins><span class="cx" style="display: block; padding: 0 10px">         $blueprints = get_post_meta( $plugin->ID, 'assets_blueprints', true );
</span><span class="cx" style="display: block; padding: 0 10px">         // Note: for now, only use a file called `blueprint.json`.
</span><span class="cx" style="display: block; padding: 0 10px">                if ( !isset( $blueprints['blueprint.json'] ) ) {
</span></span></pre></div>
<a id="sitestrunkwordpressorgpublic_htmlwpcontentpluginsplugindirectoryclasstemplatephp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/class-template.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/class-template.php      2023-12-21 00:25:28 UTC (rev 13075)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/class-template.php        2023-12-21 05:40:09 UTC (rev 13076)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -765,6 +765,51 @@
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         * Generate a live preview (playground) link for a zip attachment. Needed for newly uploaded plugins that have not yet been published.
+        *
+        * @param string $slug            The slug of the plugin post.
+        * @param int $attachment_id      The ID of the attachment post corresponding to a plugin zip file. Must be attached to the post identified by $slug.
+        * @return false|string           The preview URL.
+        */
+       public static function preview_link_zip( $slug, $attachment_id ) {
+
+               $zip_hash = self::preview_link_hash( $attachment_id );
+               if ( !$zip_hash ) {
+                       return false;
+               }
+               $zip_blueprint = sprintf( 'https://wordpress.org/plugins/wp-json/plugins/v1/plugin/%s/blueprint.json?zip_hash=%s', esc_attr( $slug ), esc_attr( $zip_hash ) );
+               $zip_preview = add_query_arg( 'blueprint-url', urlencode($zip_blueprint), 'https://playground.wordpress.net/' );
+
+               return $zip_preview;
+       }
+
+       /**
+        * Return a time-dependent variable for zip preview links.
+        *
+        * @param int $lifespan           The life span of the nonce, in seconds. Default is one week.
+        * @return float                  The tick value.
+        */
+       public static function preview_link_tick( $lifespan = WEEK_IN_SECONDS ) {
+               return ceil( time() / ( $lifespan / 2 ) );
+       }
+
+       /**
+        * Return a nonce-style hash for zip preview links.
+        *
+        * @param int $attachment_id      The ID of the attachment post corresponding to a plugin zip file.
+        * @param int $tick_offest        Number to subtract from the nonce tick. Use both 0 and -1 to verify older nonces.
+        * @return false|string           The hash as a hex string; or false if the attachment ID is invalid.
+        */
+       public static function preview_link_hash( $attachment_id, $tick_offset = 0 ) {
+               $file = get_attached_file( $attachment_id );
+               if ( !$file ) {
+                       return false;
+               }
+               $tick = self::preview_link_tick() - $tick_offset;
+               return wp_hash( $tick . '|' . $file, 'nonce' );
+       }
+
+       /**
</ins><span class="cx" style="display: block; padding: 0 10px">          * Return a list of blueprints for the given plugin.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * @param int|\WP_Post|null $post    Optional. Post ID or post object. Defaults to global $post.
</span></span></pre>
</div>
</div>

</body>
</html>