<!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>[14063] sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/shortcodes/class-upload-handler.php: Plugin Directory: Add code to run newly submitted plugins through plugin-check.</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/14063">14063</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/14063","name":"Review Commit"}}</script></dd>
<dt style="float: left; width: 6em; font-weight: bold">Author</dt> <dd>dd32</dd>
<dt style="float: left; width: 6em; font-weight: bold">Date</dt> <dd>2024-09-18 09:48:20 +0000 (Wed, 18 Sep 2024)</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 Directory: Add code to run newly submitted plugins through plugin-check.

At present this doesn't show anything to the submitter, pending confirmation of implementations.

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

<h3>Modified Paths</h3>
<ul>
<li><a href="#sitestrunkwordpressorgpublic_htmlwpcontentpluginsplugindirectoryshortcodesclassuploadhandlerphp">sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/shortcodes/class-upload-handler.php</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="sitestrunkwordpressorgpublic_htmlwpcontentpluginsplugindirectoryshortcodesclassuploadhandlerphp"></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/shortcodes/class-upload-handler.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/shortcodes/class-upload-handler.php     2024-09-18 04:52:47 UTC (rev 14062)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/shortcodes/class-upload-handler.php       2024-09-18 09:48:20 UTC (rev 14063)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -398,18 +398,21 @@
</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">                // Pass it through Plugin Check and see how great this plugin really is.
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                // We're not actually using this right now.
</del><span class="cx" style="display: block; padding: 0 10px">                 $plugin_check_result = $this->check_plugin();
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                if ( ! $plugin_check_result && ! $has_upload_token ) {
-                       $error = __( 'Error: The plugin has failed the automated checks.', 'wporg-plugins' );
-
-                       return new WP_Error( 'failed_checks', $error . ' ' . sprintf(
-                               /* translators: 1: Plugin Check Plugin URL, 2: https://make.wordpress.org/plugins */
-                               __( 'Please correct the listed problems with your plugin and upload it again. You can also use the <a href="%1$s">Plugin Check Plugin</a> to test your plugin before uploading. If you have any questions about this please post them to %2$s.', 'wporg-plugins' ),
-                               'https://wordpress.org/plugins/plugin-check/',
-                               '<a href="https://make.wordpress.org/plugins">https://make.wordpress.org/plugins</a>'
-                       ) );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         if ( ! $plugin_check_result['verdict'] && ! $has_upload_token ) {
+                       return new WP_Error(
+                               'failed_checks',
+                               __( 'Error: The plugin has failed the automated checks.', 'wporg-plugins' ) . ' ' .
+                               sprintf(
+                                       /* translators: 1: Plugin Check Plugin URL, 2: plugins email. */
+                                       __( 'Please correct the listed problems with your plugin and upload it again. You can also use the <a href="%1$s">Plugin Check Plugin</a> to test your plugin before uploading. If you have any questions about this please contact %2$s.', 'wporg-plugins' ),
+                                       'https://wordpress.org/plugins/plugin-check/',
+                                       '<a href="mailto:plugins@wordpress.org">plugins@wordpress.org</a>'
+                               ) .
+                               '</p><p>' .
+                               ( $plugin_check_result['html'] ?? '' )
+                       );
</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">                // Passed all tests!
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -493,7 +496,7 @@
</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><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $attachment = $this->save_zip_file( $plugin_post->ID, $upload_comment );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $attachment = $this->save_zip_file( $plugin_post->ID, $upload_comment, $plugin_check_result );
</ins><span class="cx" style="display: block; padding: 0 10px">                 if ( is_wp_error( $attachment ) ) {
</span><span class="cx" style="display: block; padding: 0 10px">                        return $attachment;
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -550,7 +553,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                $email->send();
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                $message = sprintf(
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        /* translators: 1: plugin name, 2: plugin slug, 3: plugins@wordpress.org */
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 /* translators: 1: plugin name, 2: plugin slug */
</ins><span class="cx" style="display: block; padding: 0 10px">                         __( 'Thank you for uploading %1$s to the WordPress Plugin Directory. Your plugin has been given the initial slug of %2$s, however that is subject to change based on the results of your code review. If this slug is incorrect, please change it below. Remember, a plugin slug cannot be changed once your plugin is approved.' ),
</span><span class="cx" style="display: block; padding: 0 10px">                        esc_html( $this->plugin['Name'] ),
</span><span class="cx" style="display: block; padding: 0 10px">                        '<code>' . $this->plugin_slug . '</code>'
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -569,7 +572,10 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                $message .= __( 'Note: Reviews are currently in English only. We apologize for the inconvenience.', 'wporg-plugins' );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $message .= '</p>';
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         // Append the plugin check results.
+               if ( ! empty( $plugin_check_result['html'] ) ) {
+                       $message .= $plugin_check_result['html'];
+               }
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                // Success!
</span><span class="cx" style="display: block; padding: 0 10px">                return $message;
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -640,27 +646,145 @@
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><span class="cx" style="display: block; padding: 0 10px">         * Sends a plugin through Plugin Check.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-         * @return bool Whether the plugin passed the checks.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+  * @return array The results of the plugin check.
</ins><span class="cx" style="display: block; padding: 0 10px">          */
</span><span class="cx" style="display: block; padding: 0 10px">        public function check_plugin() {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                return true;
</del><span class="cx" style="display: block; padding: 0 10px">                 // Run the checks.
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                // @todo Include plugin checker.
-               // Pass $this->plugin_root as the plugin root.
-               $result = true;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         if (
+                       ! defined( 'WPCLI' ) ||
+                       ! defined( 'WP_CLI_CONFIG_PATH' ) ||
+                       // The plugin must be activated in order to have plugin-check run.
+                       ! defined( 'WP_PLUGIN_CHECK_VERSION' ) ||
+                       // WordPress.org only..
+                       ! function_exists( 'notify_slack' )
+               ) {
+                       return true;
+               }
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                // Display the errors.
-               if ( $result ) {
-                       $verdict = array( 'pc-pass', __( 'Pass', 'wporg-plugins' ) );
-               } else {
-                       $verdict = array( 'pc-fail', __( 'Fail', 'wporg-plugins' ) );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         // Run plugin check via CLI
+               $start_time = microtime(1);
+               exec(
+                       'export WP_CLI_CONFIG_PATH=' . escapeshellarg( WP_CLI_CONFIG_PATH ) . '; ' .
+                       'timeout 30 ' . // Timeout after 30s if plugin-check is not done.
+                       WPCLI . ' --url=https://wordpress.org/plugins ' .
+                       ' plugin check --error-severity=7 --format=json ' . escapeshellarg( $this->plugin_root ),
+                       $output,
+                       $return_code
+               );
+               $total_time = microtime(1) - $start_time;
+
+               /**
+                * Anything that plugin-check outputs that we want to discard completely.
+                */
+               $is_ignored_code = static function( $code ) {
+                       $ignored_codes = [
+                       ];
+
+                       return (
+                               in_array( $code, $ignored_codes, true ) ||
+                               // All the Readme parser warnings are duplicated, we'll exclude those.
+                               str_starts_with( $code, 'readme_parser_warnings_' )
+                       );
+               };
+
+               /*
+                * Convert the output into an array.
+                * Format:
+                * FILE: example.extension
+                * [{.....}]
+                *
+                * FILE: example2.extension
+                * [{.....}]
+                */
+               $verdict  = true;
+               $results  = [];
+               foreach ( array_chunk( $output, 3 ) as $file_result ) {
+                       if ( ! str_starts_with( $file_result[0], 'FILE:' ) ) {
+                               continue;
+                       }
+
+                       $filename = trim( explode( ':' , $file_result[0], 2 )[1] );
+                       $json     = json_decode( $file_result[1], true );
+
+                       foreach ( $json as $record ) {
+                               $record['file'] = $filename;
+
+                               if ( $is_ignored_code( $record['code'] ) ) {
+                                       continue;
+                               }
+
+                               $results[] = $record;
+
+                               // Record submission stats.
+                               if ( function_exists( 'bump_stats_extra' ) && 'production' === wp_get_environment_type() ) {
+                                       bump_stats_extra( 'plugin-check-' . $record['type'], $record['code'] );
+                               }
+
+                               // Determine if it failed the checks.
+                               if ( $verdict && 'ERROR' === $record['type'] ) {
+                                       $verdict = false;
+                               }
+                       }
</ins><span class="cx" style="display: block; padding: 0 10px">                 }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                echo '<h4>' . sprintf( __( 'Results of Automated Plugin Scanning: %s', 'wporg-plugins' ), vsprintf( '<span class="%1$s">%2$s</span>', $verdict ) ) . '</h4>';
-               echo '<ul class="tc-result">' . __( 'Result', 'wporg-plugins' ) . '</ul>';
-               echo '<div class="notice notice-info"><p>' . __( 'Note: While the automated plugin scan is based on the Plugin Review Guidelines, it is not a complete review. A successful result from the scan does not guarantee that the plugin will be approved, only that it is sufficient to be reviewed. All submitted plugins are checked manually to ensure they meet security and guideline standards before approval.', 'wporg-plugins' ) . '</p></div>';
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         // Generage the HTML for the Plugin Check output.
+               $html = sprintf(
+                       '<strong>' . __( 'Results of Automated Plugin Scanning: %s', 'wporg-plugins' ) . '</strong>',
+                       $verdict ? __( 'Pass', 'wporg-plugins' ) : __( 'Fail', 'wporg-plugins' )
+               );
+               if ( $results ) {
+                       $html .= '<ul class="pc-result" style="list-style: disc">';
+                       foreach ( $results as $result ) {
+                               $html .= sprintf(
+                                       '<li>%s <a href="%s">%s</a>: %s</li>',
+                                       esc_html( $result['file'] ),
+                                       esc_url( $result['docs'] ?? '' ),
+                                       esc_html( $result['type'] . ' ' . $result['code'] ),
+                                       esc_html( $result['message'] )
+                               );
+                       }
+                       $html .= '</ul>';
+               }
+               $html .= __( 'Note: While the automated plugin scan is based on the Plugin Review Guidelines, it is not a complete review. A successful result from the scan does not guarantee that the plugin will be approved, only that it is sufficient to be reviewed. All submitted plugins are checked manually to ensure they meet security and guideline standards before approval.', 'wporg-plugins' );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                return $result;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         // If the upload is blocked; log it.
+               if ( ! $verdict || true ) { // TODO: Temporarily logging all to slack, as it's not output to the submitter.
+                       // Slack dm the logs.
+                       $zip_name = reset( $_FILES )['name'];
+                       $failpass = $verdict ? 'passed' : 'failed';
+                       if ( $return_code > 1 ) { // TODO: Temporary, as we're always hitting this branch.
+                               $failpass = ' errored: ' . $return_code;
+                       }
+                       $text     = "Plugin check {$failpass} for {$zip_name}: {$this->plugin['Name']} ({$this->plugin_slug}) took {$total_time}s\n";
+
+                       // List the errors, then the warnings (which may be truncated).
+                       foreach ( [ wp_list_filter( $results, [ 'type' => 'ERROR' ] ), wp_list_filter( $results, [ 'type' => 'ERROR' ], 'NOT' ) ] as $result_set ) {
+                               foreach ( $result_set as $result ) {
+                                       $text .= " - {$result['file']}: {$result['type']} - {$result['code']}: {$result['message']}\n";
+                               }
+                       }
+
+                       notify_slack( PLUGIN_CHECK_LOGS_SLACK_CHANNEL, $text, wp_get_current_user(), true );
+               } elseif ( $return_code ) {
+                       // Log plugin-check timing out.
+                       $zip_name   = reset( $_FILES )['name'];
+                       $text       = "Plugin check error {$return_code} for {$zip_name}: {$this->plugin['Name']} ({$this->plugin_slug}) took {$total_time}s\n";
+                       notify_slack( PLUGIN_CHECK_LOGS_SLACK_CHANNEL, $text, wp_get_current_user(), true );
+               }
+
+               // TODO: Payload to always pass, and not show anything to the submitter, temporary.
+               return [
+                       'verdict' => true,
+                       'results' => $results,
+                       'html'    => '',
+               ];
+
+               // Return the results.
+               return [
+                       'verdict' => $verdict,
+                       'results' => $results,
+                       'html'    => $html,
+               ];
</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 class="lines" style="display: block; padding: 0 10px; color: #888">@@ -667,9 +791,11 @@
</span><span class="cx" style="display: block; padding: 0 10px">         * Saves zip file and attaches it to the plugin post.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * @param int $post_id Post ID.
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         * @param string $upload_comment Comment for the upload.
+        * @param array|bool $plugin_check_result Plugin check results.
</ins><span class="cx" style="display: block; padding: 0 10px">          * @return WP_Post|WP_Error Attachment post or upload error.
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        public function save_zip_file( $post_id, $upload_comment ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ public function save_zip_file( $post_id, $upload_comment, $plugin_check_result = false ) {
</ins><span class="cx" style="display: block; padding: 0 10px">                 $zip_hash = sha1_file( $_FILES['zip_file']['tmp_name'] );
</span><span class="cx" style="display: block; padding: 0 10px">                if ( in_array( $zip_hash, get_post_meta( $post_id, 'uploaded_zip_hash' ) ?: [], true ) ) {
</span><span class="cx" style="display: block; padding: 0 10px">                        return new WP_Error( 'already_uploaded', __( "You've already uploaded that ZIP file.", 'wporg-plugins' ) );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -700,6 +826,11 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        update_post_meta( $attachment->ID, 'version', $this->plugin['Version'] );
</span><span class="cx" style="display: block; padding: 0 10px">                        update_post_meta( $attachment->ID, 'submitted_name', $original_name );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                        if ( $plugin_check_result ) {
+                               update_post_meta( $attachment->ID, 'pc_verdict', $plugin_check_result['verdict'] );
+                               update_post_meta( $attachment->ID, 'pc_results', $plugin_check_result['results'] );
+                       }
+
</ins><span class="cx" style="display: block; padding: 0 10px">                         // And record this ZIP as having been uploaded.
</span><span class="cx" style="display: block; padding: 0 10px">                        add_post_meta( $post_id, 'uploaded_zip_hash', $zip_hash );
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span></span></pre>
</div>
</div>

</body>
</html>