Making WordPress.org

Changeset 2640


Ignore:
Timestamp:
02/26/2016 07:52:43 AM (9 years ago)
Author:
dd32
Message:

Plugin Directory: Upload shortcode: Abstract out the filesystem handling and cleanup remaining themes-specific logic which don't apply to plugins.
See #1569

Location:
sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory
Files:
2 added
3 edited

Legend:

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

    r2625 r2640  
    328328     * @return \WP_Post|\WP_Error
    329329     */
    330     public function get_plugin_post( $plugin_slug ) {
     330    static public function get_plugin_post( $plugin_slug ) {
    331331        if ( $plugin_slug instanceof \WP_Post ) {
    332332            return $plugin_slug;
     
    337337            'post_type'   => 'plugin',
    338338            'name'        => $plugin_slug,
    339             'post_status' => 'any',
     339            'post_status' => array( 'publish', 'pending', 'disabled', 'closed' ),
    340340        ) );
    341341        if ( ! $posts ) {
     
    360360     * @return \WP_Post|\WP_Error
    361361     */
    362     public function create_plugin_post( array $plugin_info ) {
     362    static public function create_plugin_post( array $plugin_info ) {
    363363        $title  = !empty( $plugin_info['title'] )       ? $plugin_info['title']       : '';
    364364        $slug   = !empty( $plugin_info['slug'] )        ? $plugin_info['slug']        : sanitize_title( $title );
  • sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/shortcodes/class-upload-handler.php

    r2625 r2640  
    11<?php
    22namespace WordPressdotorg\Plugin_Directory\Shortcodes;
     3use WordPressdotorg\Plugin_Directory\Plugin_Directory;
     4use WordPressdotorg\Plugin_Directory\Tools\Filesystem;
    35
    46/**
     
    1012
    1113    /**
    12      * Path to `rm` script.
     14     * Path to temporary plugin folder.
    1315     *
    1416     * @var string
    1517     */
    16     const RM = '/bin/rm';
    17 
    18     /**
    19      * Path to `unzip` script.
     18    protected $plugin_dir;
     19
     20    /**
     21     * Path to the detected plugins files.
    2022     *
    2123     * @var string
    2224     */
    23     const UNZIP = '/usr/bin/unzip';
    24 
    25     /**
    26      * Path to temporary directory.
     25    protected $plugin_root;
     26
     27    /**
     28     * The uploaded plugin headers.
     29     *
     30     * @var array
     31     */
     32    protected $plugin;
     33
     34    /**
     35     * The plugin slug.
    2736     *
    2837     * @var string
    2938     */
    30     protected $tmp_dir;
    31 
    32     /**
    33      * Path to temporary plugin folder.
    34      *
    35      * @var string
    36      */
    37     protected $plugin_dir;
    38 
    39     /**
    40      * The uploaded plugin.
    41      *
    42      * @var array
    43      */
    44     protected $plugin;
    45 
    46     /**
    47      * The plugin slug.
    48      *
    49      * @var string
    50      */
    5139    protected $plugin_slug;
    52 
    53     /**
    54      * The plugin post if it already exists in the repository.
    55      *
    56      * @var \WP_Post
    57      */
    58     protected $plugin_post;
    59 
    60     /**
    61      * The plugin author (current user).
    62      *
    63      * @var \WP_User
    64      */
    65     protected $author;
    6640
    6741    /**
     
    7347        require_once( ABSPATH . 'wp-admin/includes/file.php' );
    7448        require_once( ABSPATH . 'wp-admin/includes/media.php' );
    75 
    76         $this->create_tmp_dirs();
    77         $this->unwrap_package();
    78 
    79         add_filter( 'extra_plugin_headers', array( $this, 'extra_plugin_headers' ) );
    8049    }
    8150
     
    8857     */
    8958    public function process_upload() {
    90         $plugin_files = $this->get_all_files( $this->plugin_dir );
    91 
    92         // First things first. Do we have something to work with?
    93         if ( empty( $plugin_files ) ) {
    94             return __( 'The zip file was empty.', 'wporg-plugins' );
    95         }
    96 
     59        $zip_file = $_FILES['zip_file']['tmp_name'];
     60        $this->plugin_dir = Filesystem::unzip( $zip_file );
     61
     62        $plugin_files = Filesystem::list_files( $this->plugin_dir, true /* Recursive */, '!\.php$!i' );
    9763        foreach ( $plugin_files as $plugin_file ) {
    98             if ( ! is_readable( $plugin_file ) ) {
    99                 continue;
    100             }
    101 
    10264            $plugin_data = get_plugin_data( $plugin_file, false, false ); // No markup/translation needed.
    10365            if ( ! empty( $plugin_data['Name'] ) ) {
    10466                $this->plugin = $plugin_data;
     67                $this->plugin_root = dirname( $plugin_file );
    10568                break;
    10669            }
     
    10871
    10972        // Let's check some plugin headers, shall we?
    110 
    111         if ( ! $this->plugin['Name'] ) {
    112             $error = __( 'The plugin has no name.', 'wporg-plugins' ) . ' ';
     73        // Catches both empty Plugin Name & when no valid files could be found.
     74        if ( empty( $this->plugin['Name'] ) ) {
     75            $error = __( 'The plugin has no name.', 'wporg-plugins' );
    11376
    11477            /* translators: 1: comment header line, 2: Codex URL */
    115             $error .= sprintf( __( 'Add a %1$s line to your main plugin file and upload the plugin again. <a href="%2$s">Plugin Headers</a>', 'wporg-plugins' ),
     78            return $error . ' ' . sprintf( __( 'Add a %1$s line to your main plugin file and upload the plugin again. <a href="%2$s">Plugin Headers</a>', 'wporg-plugins' ),
    11679                '<code>Plugin Name:</code>',
    11780                __( 'https://codex.wordpress.org/File_Header', 'wporg-plugins' )
    11881            );
    119 
    120             return $error;
    12182        }
    12283
    12384        // Determine the plugin slug based on the name of the plugin in the main plugin file.
    12485        $this->plugin_slug = sanitize_title_with_dashes( $this->plugin['Name'] );
    125         $this->author      = wp_get_current_user();
    12686
    12787        // Make sure it doesn't use a slug deemed not to be used by the public.
    12888        if ( $this->has_reserved_slug() ) {
    12989            /* translators: 1: plugin slug, 2: style.css */
    130             return sprintf( __( 'Sorry, the plugin name %1$s is reserved for use by WordPress Core. Please change the name of your plugin and upload it again.', 'wporg-plugins' ),
     90            return sprintf( __( 'Sorry, the plugin name %1$s is reserved for use by WordPress. Please change the name of your plugin and upload it again.', 'wporg-plugins' ),
    13191                '<code>' . $this->plugin_slug . '</code>'
    13292            );
    13393        }
    13494
    135         // Populate the plugin post and author.
    136         $this->plugin_post = $this->get_plugin_post();
    137 
    13895        // Is there already a plugin with the name name?
    139         if ( ! empty( $this->plugin_post ) ) {
     96        if ( Plugin_Directory::get_plugin_post( $this->plugin_slug ) ) {
    14097            /* translators: 1: plugin slug, 2: style.css */
    14198            return sprintf( __( 'There is already a plugin called %1$s by a different author. Please change the name of your plugin and upload it again.', 'wporg-plugins' ),
     
    144101        }
    145102
    146         $plugin_description = $this->strip_non_utf8( (string) $this->plugin['Description'] );
    147         if ( empty( $plugin_description ) ) {
    148             $error = __( 'The plugin has no description.', 'wporg-plugins' ) . ' ';
     103        if ( ! $this->plugin['Description'] ) {
     104            $error = __( 'The plugin has no description.', 'wporg-plugins' );
    149105
    150106            /* translators: 1: comment header line, 2: style.css, 3: Codex URL */
    151             $error .= sprintf( __( 'Add a %1$s line to your main plugin file and upload the plugin again. <a href="%3$s">Plugin Headers</a>', 'wporg-plugins' ),
     107            return $error . ' ' . sprintf( __( 'Add a %1$s line to your main plugin file and upload the plugin again. <a href="%3$s">Plugin Headers</a>', 'wporg-plugins' ),
    152108                '<code>Description:</code>',
    153109                __( 'https://codex.wordpress.org/File_Header', 'wporg-plugins' )
    154110            );
    155 
    156             return $error;
    157111        }
    158112
    159113        if ( ! $this->plugin['Version'] ) {
    160             $error = __( 'The plugin has no version.', 'wporg-plugins' ) . ' ';
     114            $error = __( 'The plugin has no version.', 'wporg-plugins' );
    161115
    162116            /* translators: 1: comment header line, 2: style.css, 3: Codex URL */
    163             $error .= sprintf( __( 'Add a %1$s line to your main plugin file and upload the plugin again. <a href="%3$s">Plugin Headers</a>', 'wporg-plugins' ),
     117            return $error . ' ' . sprintf( __( 'Add a %1$s line to your main plugin file and upload the plugin again. <a href="%3$s">Plugin Headers</a>', 'wporg-plugins' ),
    164118                '<code>Version:</code>',
    165119                __( 'https://codex.wordpress.org/File_Header', 'wporg-plugins' )
    166120            );
    167 
    168             return $error;
    169121        }
    170122
     
    183135        }
    184136
    185         // Don't send special plugins through Plugin Check.
    186         if ( ! has_category( 'special-case-plugin', $this->plugin_post ) ) {
    187             // Pass it through Plugin Check and see how great this plugin really is.
    188             $result = $this->check_plugin( $plugin_files );
    189 
    190             if ( ! $result ) {
    191                 /* translators: 1: Plugin Check Plugin URL, 2: make.wordpress.org/plugins */
    192                 return sprintf( __( 'Your plugin has failed the plugin check. Please correct the problems with it 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' ),
    193                     '//wordpress.org/plugins/plugin-check/',
    194                     '<a href="https://make.wordpress.org/plugins">https://make.wordpress.org/plugins</a>'
    195                 );
    196             }
     137        // Pass it through Plugin Check and see how great this plugin really is.
     138        $result = $this->check_plugin();
     139
     140        if ( ! $result ) {
     141            /* translators: 1: Plugin Check Plugin URL, 2: make.wordpress.org/plugins */
     142            return sprintf( __( 'Your plugin has failed the plugin check. Please correct the problems with it 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' ),
     143                '//wordpress.org/plugins/plugin-check/',
     144                '<a href="https://make.wordpress.org/plugins">https://make.wordpress.org/plugins</a>'
     145            );
    197146        }
    198147
     
    201150
    202151        // Add a Plugin Directory entry for this plugin.
    203         $post_id = $this->create_plugin_post();
    204 
    205         $attachment = $this->save_zip_file( $post_id );
     152        $plugin_post = Plugin_Directory::create_plugin_post( array(
     153            'title'       => $this->plugin['Name'],
     154            'slug'        => $this->plugin_slug,
     155            'status'      => 'pending',
     156            'author'      => get_current_user_id(),
     157            'description' => $this->plugin['Description']
     158        ) );
     159        if ( is_wp_error( $plugin_post ) ) {
     160            return $plugin_post->get_error_message();
     161        }
     162
     163        $attachment = $this->save_zip_file( $plugin_post->ID );
    206164        if ( is_wp_error( $attachment ) ) {
    207165            return $attachment->get_error_message();
     
    211169        $this->send_email_notification();
    212170
    213         do_action( 'plugin_upload', $this->plugin, $this->plugin_post );
     171        do_action( 'plugin_upload', $this->plugin, $plugin_post );
    214172
    215173        // Success!
    216174        /* translators: 1: plugin name */
    217 
    218175        return sprintf( __( 'Thank you for uploading %1$s to the WordPress Plugin Directory. We&rsquo;ve sent you an email verifying that we&rsquo;ve received it.', 'wporg-plugins' ),
    219             $this->plugin['Name']
    220         );
    221     }
    222 
    223     /**
    224      * Creates a temporary directory, and the plugin dir within it.
    225      */
    226     public function create_tmp_dirs() {
    227         // Create a temporary directory if it doesn't exist yet.
    228         $tmp = '/tmp/wporg-plugin-upload';
    229         if ( ! is_dir( $tmp ) ) {
    230             mkdir( $tmp, 0777 );
    231         }
    232 
    233         // Create file with unique file name.
    234         $this->tmp_dir = tempnam( $tmp, 'WPORG_PLUGIN_' );
    235 
    236         // Remove that file.
    237         unlink( $this->tmp_dir );
    238 
    239         // Create a directory with that unique name.
    240         mkdir( $this->tmp_dir, 0777 );
    241 
    242         // Get a sanitized name for that plugin and create a directory for it.
    243         $base_name        = $this->get_sanitized_zip_name();
    244         $this->plugin_dir = "{$this->tmp_dir}/{$base_name}";
    245         mkdir( $this->plugin_dir, 0777 );
    246 
    247         // Make sure we clean up after ourselves.
    248         add_action( 'shutdown', array( $this, 'remove_files' ) );
    249     }
    250 
    251     /**
    252      * Unzips the uploaded plugin and saves it in the temporary plugin dir.
    253      */
    254     public function unwrap_package() {
    255         $unzip      = escapeshellarg( self::UNZIP );
    256         $zip_file   = escapeshellarg( $_FILES['zip_file']['tmp_name'] );
    257         $plugin_dir = escapeshellarg( $this->plugin_dir );
    258 
    259         // Unzip it into the plugin directory.
    260         exec( escapeshellcmd( "{$unzip} -DD {$zip_file} -d {$plugin_dir}" ) );
    261 
    262         // Fix any permissions issues with the files. Sets 755 on directories, 644 on files.
    263         exec( escapeshellcmd( "chmod -R 755 {$plugin_dir}" ) );
    264         exec( escapeshellcmd( "find {$plugin_dir} -type f -print0" ) . ' | xargs -I% -0 chmod 644 %' );
    265     }
    266 
    267     /**
    268      * Adds plugin headers that are expected in the directory.
    269      *
    270      * @param array $headers Additional plugin headers. Default empty array.
    271      * @return array
    272      */
    273     public function extra_plugin_headers( $headers ) {
    274         $headers['Tags'] = 'Tags';
    275 
    276         return $headers;
    277     }
    278 
    279     /**
    280      * Returns the the plugin post if it already exists in the repository.
    281      *
    282      * @return \WP_Post|null
    283      */
    284     public function get_plugin_post() {
    285         $plugins = get_posts( array(
    286             'name'             => $this->plugin_slug,
    287             'posts_per_page'   => 1,
    288             'post_type'        => 'plugin',
    289             'orderby'          => 'ID',
    290             /*
    291              * Specify post stati so this query returns a result for draft plugins, even
    292              * if the uploading user doesn't have have the permission to view drafts.
    293              */
    294             'post_status'      => array( 'publish', 'pending', 'draft', 'future', 'trash', 'suspend' ),
    295             'suppress_filters' => false,
    296         ) );
    297 
    298         return current( $plugins );
     176            esc_html( $this->plugin['Name'] )
     177        );
    299178    }
    300179
    301180    /**
    302181     * Whether the uploaded plugin uses a reserved slug.
    303      *
    304      * Passes if the author happens to be `wordpressdotorg`.
    305182     *
    306183     * @return bool
     
    320197        );
    321198
    322         return in_array( $this->plugin_slug, $reserved_slugs ) && 'wordpressdotorg' !== $this->author->user_login;
     199        return in_array( $this->plugin_slug, $reserved_slugs );
    323200    }
    324201
     
    326203     * Sends a plugin through Plugin Check.
    327204     *
    328      * @param array $files All plugin files to check.
    329205     * @return bool Whether the plugin passed the checks.
    330206     */
    331     public function check_plugin( $files ) {
    332 
     207    public function check_plugin() {
    333208        // Run the checks.
    334209        // @todo Include plugin checker.
     210        // pass $this->plugin_root as the plugin root
    335211        $result = true;
    336212
    337213        // Display the errors.
    338         $verdict = $result ? array( 'tc-pass', __( 'Pass', 'wporg-plugins' ) ) : array(
    339             'tc-fail',
    340             __( 'Fail', 'wporg-plugins' )
    341         );
     214        if ( $result ) {
     215            $verdict = array( 'pc-pass', __( 'Pass', 'wporg-plugins' ) );
     216        } else {
     217            $verdict = array( 'pc-fail', __( 'Fail', 'wporg-plugins' ) );
     218        }
     219
    342220        echo '<h4>' . sprintf( __( 'Results of Automated Plugin Scanning: %s', 'wporg-plugins' ), vsprintf( '<span class="%1$s">%2$s</span>', $verdict ) ) . '</h4>';
    343221        echo '<ul class="tc-result">' . 'Result' . '</ul>';
     
    348226
    349227    /**
    350      * Creates a plugin post.
    351      *
    352      * @return int|\WP_Error The post ID on success. The value 0 or WP_Error on failure.
    353      */
    354     public function create_plugin_post() {
    355         $upload_date = current_time( 'mysql' );
    356 
    357         return wp_insert_post( array(
    358             'post_author'    => $this->author->ID,
    359             'post_title'     => $this->plugin['Name'],
    360             'post_name'      => $this->plugin_slug,
    361             'post_excerpt'   => $this->plugin['Description'],
    362             'post_date'      => $upload_date,
    363             'post_date_gmt'  => $upload_date,
    364             'comment_status' => 'closed',
    365             'ping_status'    => 'closed',
    366             'post_status'    => 'pending',
    367             'post_type'      => 'plugin',
    368             'tags_input'     => $this->plugin['Tags'],
    369         ) );
    370     }
    371 
    372     /**
    373228     * Saves zip file and attaches it to the plugin post.
    374229     *
     
    377232     */
    378233    public function save_zip_file( $post_id ) {
    379         $_FILES['zip_file']['name'] = wp_generate_password( 12 ) . '-' . $_FILES['zip_file']['name'];
     234        $_FILES['zip_file']['name'] = wp_generate_password( 12, false ) . '-' . $_FILES['zip_file']['name'];
    380235
    381236        add_filter( 'site_option_upload_filetypes', array( $this, 'whitelist_zip_files' ) );
     
    396251
    397252        /* translators: %s: plugin name */
    398         $email_subject = sprintf( __( '[WordPress Plugins] New Plugin - %s', 'wporg-plugins' ),
     253        $email_subject = sprintf( __( '[WordPress.org Plugins] New Plugin - %s', 'wporg-plugins' ),
    399254            $this->plugin['Name']
    400255        );
     
    409264        );
    410265
    411         wp_mail( $this->author->user_email, $email_subject, $email_content, 'From: plugins@wordpress.org' );
     266        $user_email = wp_get_current_user()->user_email;
     267
     268        wp_mail( $user_email, $email_subject, $email_content, 'From: plugins@wordpress.org' );
    412269    }
    413270
     
    415272
    416273    /**
    417      * Returns a sanitized version of the uploaded zip file name.
    418      *
    419      * @return string
    420      */
    421     public function get_sanitized_zip_name() {
    422         return preg_replace( '|\W|', '', strtolower( basename( $_FILES['zip_file']['name'], '.zip' ) ) );
    423     }
    424 
    425     /**
    426      * Returns all (usable) files of a given directory.
    427      *
    428      * @param string $dir Path to directory to search.
    429      *
    430      * @return array All files within the passed directory.
    431      */
    432     public function get_all_files( $dir ) {
    433         $files        = array();
    434         $dir_iterator = new \RecursiveDirectoryIterator( $dir );
    435         $iterator     = new \RecursiveIteratorIterator( $dir_iterator, \RecursiveIteratorIterator::SELF_FIRST );
    436 
    437         foreach ( $iterator as $file ) {
    438             // Only return files that are no directory references or Mac resource forks.
    439             if ( $file->isFile() && ! in_array( $file->getBasename(), array(
    440                     '..',
    441                     '.'
    442                 ) ) && ! stristr( $file->getPathname(), '__MACOSX' )
    443             ) {
    444                 array_push( $files, $file->getPathname() );
    445             }
    446         }
    447 
    448         return $files;
    449     }
    450 
    451     /**
    452274     * Whitelist zip files to be allowed to be uploaded to the media library.
    453275     *
    454      * @param string $site_exts Whitelisted file extentions.
    455      *
    456      * @return string Whitelisted file extentions.
    457      */
    458     public function whitelist_zip_files( $site_exts ) {
    459         $file_extenstions   = explode( ' ', $site_exts );
    460         $file_extenstions[] = 'zip';
    461 
    462         return implode( ' ', array_unique( $file_extenstions ) );
    463     }
    464 
    465     /**
    466      * Deletes the temporary directory.
    467      */
    468     public function remove_files() {
    469         $rm    = escapeshellarg( self::RM );
    470         $files = escapeshellarg( $this->tmp_dir );
    471 
    472         exec( escapeshellcmd( "{$rm} -rf {$files}" ) );
    473     }
    474 
    475     /**
    476      * Strips invalid UTF-8 characters.
    477      *
    478      * Non-UTF-8 characters in plugin descriptions will causes blank descriptions in plugins.trac.
    479      *
    480      * @param string $string The string to be converted.
    481      *
    482      * @return string The converted string.
    483      */
    484     protected function strip_non_utf8( $string ) {
    485         ini_set( 'mbstring.substitute_character', 'none' );
    486 
    487         return mb_convert_encoding( $string, 'UTF-8', 'UTF-8' );
    488     }
     276     * As we only want to accept *.zip uploads, we specifically exclude all other types here.
     277     *
     278     * @return string Whitelisted ZIP filetypes.
     279     */
     280    public function whitelist_zip_files() {
     281        return 'zip';
     282    }
     283
    489284}
  • sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/shortcodes/class-upload.php

    r2625 r2640  
    1212            if ( ! empty( $_POST['_wpnonce'] ) && wp_verify_nonce( $_POST['_wpnonce'], 'wporg-plugins-upload' ) && 'upload' === $_POST['action'] ) {
    1313                if ( UPLOAD_ERR_OK === $_FILES['zip_file']['error'] ) {
    14                     switch_to_blog( WPORG_PLUGIN_DIRECTORY_BLOGID );
    1514                    $uploader = new Upload_Handler;
    1615                    $message  = $uploader->process_upload();
    17                     restore_current_blog();
    18 
    1916                }  else {
    2017                    $message = __( 'Error in file upload.', 'wporg-plugins' );
     
    3835        <?php else : ?>
    3936            <p><?php printf( __( 'Before you can upload a new plugin, <a href="%s">please log in</a>.', 'wporg-plugins' ), esc_url( 'https://login.wordpress.org/' ) ); ?>
    40             <p>
     37            </p>
    4138        <?php endif;
    4239    }
Note: See TracChangeset for help on using the changeset viewer.