Making WordPress.org


Ignore:
Timestamp:
09/18/2024 09:48:20 AM (3 months ago)
Author:
dd32
Message:

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 #7778.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/shortcodes/class-upload-handler.php

    r13962 r14063  
    399399
    400400        // Pass it through Plugin Check and see how great this plugin really is.
    401         // We're not actually using this right now.
    402401        $plugin_check_result = $this->check_plugin();
    403402
    404         if ( ! $plugin_check_result && ! $has_upload_token ) {
    405             $error = __( 'Error: The plugin has failed the automated checks.', 'wporg-plugins' );
    406 
    407             return new WP_Error( 'failed_checks', $error . ' ' . sprintf(
    408                 /* translators: 1: Plugin Check Plugin URL, 2: https://make.wordpress.org/plugins */
    409                 __( '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' ),
    410                 'https://wordpress.org/plugins/plugin-check/',
    411                 '<a href="https://make.wordpress.org/plugins">https://make.wordpress.org/plugins</a>'
    412             ) );
     403        if ( ! $plugin_check_result['verdict'] && ! $has_upload_token ) {
     404            return new WP_Error(
     405                'failed_checks',
     406                __( 'Error: The plugin has failed the automated checks.', 'wporg-plugins' ) . ' ' .
     407                sprintf(
     408                    /* translators: 1: Plugin Check Plugin URL, 2: plugins email. */
     409                    __( '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' ),
     410                    'https://wordpress.org/plugins/plugin-check/',
     411                    '<a href="mailto:plugins@wordpress.org">plugins@wordpress.org</a>'
     412                ) .
     413                '</p><p>' .
     414                ( $plugin_check_result['html'] ?? '' )
     415            );
    413416        }
    414417
     
    494497        }
    495498
    496         $attachment = $this->save_zip_file( $plugin_post->ID, $upload_comment );
     499        $attachment = $this->save_zip_file( $plugin_post->ID, $upload_comment, $plugin_check_result );
    497500        if ( is_wp_error( $attachment ) ) {
    498501            return $attachment;
     
    551554
    552555        $message = sprintf(
    553             /* translators: 1: plugin name, 2: plugin slug, 3: plugins@wordpress.org */
     556            /* translators: 1: plugin name, 2: plugin slug */
    554557            __( '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.' ),
    555558            esc_html( $this->plugin['Name'] ),
     
    570573        $message .= __( 'Note: Reviews are currently in English only. We apologize for the inconvenience.', 'wporg-plugins' );
    571574
    572         $message .= '</p>';
     575        // Append the plugin check results.
     576        if ( ! empty( $plugin_check_result['html'] ) ) {
     577            $message .= $plugin_check_result['html'];
     578        }
    573579
    574580        // Success!
     
    641647     * Sends a plugin through Plugin Check.
    642648     *
    643      * @return bool Whether the plugin passed the checks.
     649     * @return array The results of the plugin check.
    644650     */
    645651    public function check_plugin() {
    646         return true;
    647652        // Run the checks.
    648         // @todo Include plugin checker.
    649         // Pass $this->plugin_root as the plugin root.
    650         $result = true;
    651 
    652         // Display the errors.
    653         if ( $result ) {
    654             $verdict = array( 'pc-pass', __( 'Pass', 'wporg-plugins' ) );
    655         } else {
    656             $verdict = array( 'pc-fail', __( 'Fail', 'wporg-plugins' ) );
    657         }
    658 
    659         echo '<h4>' . sprintf( __( 'Results of Automated Plugin Scanning: %s', 'wporg-plugins' ), vsprintf( '<span class="%1$s">%2$s</span>', $verdict ) ) . '</h4>';
    660         echo '<ul class="tc-result">' . __( 'Result', 'wporg-plugins' ) . '</ul>';
    661         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>';
    662 
    663         return $result;
     653        if (
     654            ! defined( 'WPCLI' ) ||
     655            ! defined( 'WP_CLI_CONFIG_PATH' ) ||
     656            // The plugin must be activated in order to have plugin-check run.
     657            ! defined( 'WP_PLUGIN_CHECK_VERSION' ) ||
     658            // WordPress.org only..
     659            ! function_exists( 'notify_slack' )
     660        ) {
     661            return true;
     662        }
     663
     664        // Run plugin check via CLI
     665        $start_time = microtime(1);
     666        exec(
     667            'export WP_CLI_CONFIG_PATH=' . escapeshellarg( WP_CLI_CONFIG_PATH ) . '; ' .
     668            'timeout 30 ' . // Timeout after 30s if plugin-check is not done.
     669            WPCLI . ' --url=https://wordpress.org/plugins ' .
     670            ' plugin check --error-severity=7 --format=json ' . escapeshellarg( $this->plugin_root ),
     671            $output,
     672            $return_code
     673        );
     674        $total_time = microtime(1) - $start_time;
     675
     676        /**
     677         * Anything that plugin-check outputs that we want to discard completely.
     678         */
     679        $is_ignored_code = static function( $code ) {
     680            $ignored_codes = [
     681            ];
     682
     683            return (
     684                in_array( $code, $ignored_codes, true ) ||
     685                // All the Readme parser warnings are duplicated, we'll exclude those.
     686                str_starts_with( $code, 'readme_parser_warnings_' )
     687            );
     688        };
     689
     690        /*
     691         * Convert the output into an array.
     692         * Format:
     693         * FILE: example.extension
     694         * [{.....}]
     695         *
     696         * FILE: example2.extension
     697         * [{.....}]
     698         */
     699        $verdict  = true;
     700        $results  = [];
     701        foreach ( array_chunk( $output, 3 ) as $file_result ) {
     702            if ( ! str_starts_with( $file_result[0], 'FILE:' ) ) {
     703                continue;
     704            }
     705
     706            $filename = trim( explode( ':' , $file_result[0], 2 )[1] );
     707            $json     = json_decode( $file_result[1], true );
     708
     709            foreach ( $json as $record ) {
     710                $record['file'] = $filename;
     711
     712                if ( $is_ignored_code( $record['code'] ) ) {
     713                    continue;
     714                }
     715
     716                $results[] = $record;
     717
     718                // Record submission stats.
     719                if ( function_exists( 'bump_stats_extra' ) && 'production' === wp_get_environment_type() ) {
     720                    bump_stats_extra( 'plugin-check-' . $record['type'], $record['code'] );
     721                }
     722
     723                // Determine if it failed the checks.
     724                if ( $verdict && 'ERROR' === $record['type'] ) {
     725                    $verdict = false;
     726                }
     727            }
     728        }
     729
     730        // Generage the HTML for the Plugin Check output.
     731        $html = sprintf(
     732            '<strong>' . __( 'Results of Automated Plugin Scanning: %s', 'wporg-plugins' ) . '</strong>',
     733            $verdict ? __( 'Pass', 'wporg-plugins' ) : __( 'Fail', 'wporg-plugins' )
     734        );
     735        if ( $results ) {
     736            $html .= '<ul class="pc-result" style="list-style: disc">';
     737            foreach ( $results as $result ) {
     738                $html .= sprintf(
     739                    '<li>%s <a href="%s">%s</a>: %s</li>',
     740                    esc_html( $result['file'] ),
     741                    esc_url( $result['docs'] ?? '' ),
     742                    esc_html( $result['type'] . ' ' . $result['code'] ),
     743                    esc_html( $result['message'] )
     744                );
     745            }
     746            $html .= '</ul>';
     747        }
     748        $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' );
     749
     750        // If the upload is blocked; log it.
     751        if ( ! $verdict || true ) { // TODO: Temporarily logging all to slack, as it's not output to the submitter.
     752            // Slack dm the logs.
     753            $zip_name = reset( $_FILES )['name'];
     754            $failpass = $verdict ? 'passed' : 'failed';
     755            if ( $return_code > 1 ) { // TODO: Temporary, as we're always hitting this branch.
     756                $failpass = ' errored: ' . $return_code;
     757            }
     758            $text     = "Plugin check {$failpass} for {$zip_name}: {$this->plugin['Name']} ({$this->plugin_slug}) took {$total_time}s\n";
     759
     760            // List the errors, then the warnings (which may be truncated).
     761            foreach ( [ wp_list_filter( $results, [ 'type' => 'ERROR' ] ), wp_list_filter( $results, [ 'type' => 'ERROR' ], 'NOT' ) ] as $result_set ) {
     762                foreach ( $result_set as $result ) {
     763                    $text .= " - {$result['file']}: {$result['type']} - {$result['code']}: {$result['message']}\n";
     764                }
     765            }
     766
     767            notify_slack( PLUGIN_CHECK_LOGS_SLACK_CHANNEL, $text, wp_get_current_user(), true );
     768        } elseif ( $return_code ) {
     769            // Log plugin-check timing out.
     770            $zip_name   = reset( $_FILES )['name'];
     771            $text       = "Plugin check error {$return_code} for {$zip_name}: {$this->plugin['Name']} ({$this->plugin_slug}) took {$total_time}s\n";
     772            notify_slack( PLUGIN_CHECK_LOGS_SLACK_CHANNEL, $text, wp_get_current_user(), true );
     773        }
     774
     775        // TODO: Payload to always pass, and not show anything to the submitter, temporary.
     776        return [
     777            'verdict' => true,
     778            'results' => $results,
     779            'html'    => '',
     780        ];
     781
     782        // Return the results.
     783        return [
     784            'verdict' => $verdict,
     785            'results' => $results,
     786            'html'    => $html,
     787        ];
    664788    }
    665789
     
    668792     *
    669793     * @param int $post_id Post ID.
     794     * @param string $upload_comment Comment for the upload.
     795     * @param array|bool $plugin_check_result Plugin check results.
    670796     * @return WP_Post|WP_Error Attachment post or upload error.
    671797     */
    672     public function save_zip_file( $post_id, $upload_comment ) {
     798    public function save_zip_file( $post_id, $upload_comment, $plugin_check_result = false ) {
    673799        $zip_hash = sha1_file( $_FILES['zip_file']['tmp_name'] );
    674800        if ( in_array( $zip_hash, get_post_meta( $post_id, 'uploaded_zip_hash' ) ?: [], true ) ) {
     
    700826            update_post_meta( $attachment->ID, 'version', $this->plugin['Version'] );
    701827            update_post_meta( $attachment->ID, 'submitted_name', $original_name );
     828
     829            if ( $plugin_check_result ) {
     830                update_post_meta( $attachment->ID, 'pc_verdict', $plugin_check_result['verdict'] );
     831                update_post_meta( $attachment->ID, 'pc_results', $plugin_check_result['results'] );
     832            }
    702833
    703834            // And record this ZIP as having been uploaded.
Note: See TracChangeset for help on using the changeset viewer.