Making WordPress.org

Ticket #1569: 1569.2.diff

File 1569.2.diff, 19.0 KB (added by obenland, 9 years ago)
  • wp-content/plugins/plugin-directory/class-plugin-directory.php

     
    107107         * Register the Shortcodes used within the content.
    108108         */
    109109        public function register_shortcodes() {
     110                add_shortcode( 'wporg-plugin-upload',       array( __NAMESPACE__ . '\\Shortcodes\\Upload',      'display' ) );
    110111                add_shortcode( 'wporg-plugins-screenshots', array( __NAMESPACE__ . '\\Shortcodes\\Screenshots', 'display' ) );
    111112        //      add_shortcode( 'wporg-plugins-stats',       array( __NAMESPACE__ . '\\Shortcodes\\Stats',       'display' ) );
    112113        //      add_shortcode( 'wporg-plugins-developer',   array( __NAMESPACE__ . '\\Shortcodes\\Developer',   'display' ) );
  • wp-content/plugins/plugin-directory/shortcodes/class-upload-handler.php

     
     1<?php
     2namespace WordPressdotorg\Plugin_Directory\Shortcodes;
     3
     4/**
     5 * The [wporg-plugin-upload] shortcode handler to display a plugin uploader.
     6 *
     7 * @package WordPressdotorg\Plugin_Directory\Shortcodes
     8 */
     9class Upload_Handler {
     10
     11        /**
     12         * Path to `rm` script.
     13         *
     14         * @var string
     15         */
     16        const RM = '/bin/rm';
     17
     18        /**
     19         * Path to `unzip` script.
     20         *
     21         * @var string
     22         */
     23        const UNZIP = '/usr/bin/unzip';
     24
     25        /**
     26         * Path to temporary directory.
     27         *
     28         * @var string
     29         */
     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         */
     51        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;
     66
     67        /**
     68         * Get set up to run tests on the uploaded plugin.
     69         */
     70        public function __construct() {
     71                require_once( ABSPATH . 'wp-admin/includes/plugin.php' );
     72                require_once( ABSPATH . 'wp-admin/includes/image.php' );
     73                require_once( ABSPATH . 'wp-admin/includes/file.php' );
     74                require_once( ABSPATH . 'wp-admin/includes/media.php' );
     75
     76                $this->create_tmp_dirs();
     77                $this->unwrap_package();
     78        }
     79
     80        /**
     81         * Processes the plugin upload.
     82         *
     83         * Runs various tests and creates plugin post.
     84         *
     85         * @return string Failure or success message.
     86         */
     87        public function process_upload() {
     88                $plugin_files = $this->get_all_files( $this->plugin_dir );
     89
     90                // First things first. Do we have something to work with?
     91                if ( empty( $plugin_files ) ) {
     92                        return __( 'The zip file was empty.', 'wporg-plugins' );
     93                }
     94
     95                foreach ( $plugin_files as $plugin_file ) {
     96                        if ( ! is_readable( $plugin_file ) ) {
     97                                continue;
     98                        }
     99
     100                        $plugin_data = get_plugin_data( $plugin_file, false, false ); // No markup/translation needed.
     101                        if ( ! empty( $plugin_data['Name'] ) ) {
     102                                $this->plugin = $plugin_data;
     103                                break;
     104                        }
     105                }
     106
     107                // Let's check some plugin headers, shall we?
     108
     109                if ( ! $this->plugin['Name'] ) {
     110                        $error = __( 'The plugin has no name.', 'wporg-plugins' ) . ' ';
     111
     112                        /* translators: 1: comment header line, 2: Codex URL */
     113                        $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' ),
     114                                '<code>Plugin Name:</code>',
     115                                __( 'https://codex.wordpress.org/File_Header', 'wporg-plugins' )
     116                        );
     117
     118                        return $error;
     119                }
     120
     121                // Determine the plugin slug based on the name of the plugin in the main plugin file.
     122                $this->plugin_slug = sanitize_title_with_dashes( $this->plugin['Name'] );
     123                $this->author      = wp_get_current_user();
     124
     125                // Make sure it doesn't use a slug deemed not to be used by the public.
     126                if ( $this->has_reserved_slug() ) {
     127                        /* translators: 1: plugin slug, 2: style.css */
     128                        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' ),
     129                                '<code>' . $this->plugin_slug . '</code>'
     130                        );
     131                }
     132
     133                // Populate the plugin post and author.
     134                $this->plugin_post = $this->get_plugin_post();
     135
     136                // Is there already a plugin with the name name?
     137                if ( ! empty( $this->plugin_post ) ) {
     138                        /* translators: 1: plugin slug, 2: style.css */
     139                        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' ),
     140                                '<code>' . $this->plugin_slug . '</code>'
     141                        );
     142                }
     143
     144                $plugin_description = $this->strip_non_utf8( (string) $this->plugin['Description'] );
     145                if ( empty( $plugin_description ) ) {
     146                        $error = __( 'The plugin has no description.', 'wporg-plugins' ) . ' ';
     147
     148                        /* translators: 1: comment header line, 2: style.css, 3: Codex URL */
     149                        $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' ),
     150                                '<code>Description:</code>',
     151                                __( 'https://codex.wordpress.org/File_Header', 'wporg-plugins' )
     152                        );
     153
     154                        return $error;
     155                }
     156
     157                if ( ! $this->plugin['Version'] ) {
     158                        $error = __( 'The plugin has no version.', 'wporg-plugins' ) . ' ';
     159
     160                        /* translators: 1: comment header line, 2: style.css, 3: Codex URL */
     161                        $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' ),
     162                                '<code>Version:</code>',
     163                                __( 'https://codex.wordpress.org/File_Header', 'wporg-plugins' )
     164                        );
     165
     166                        return $error;
     167                }
     168
     169                if ( preg_match( '|[^\d\.]|', $this->plugin['Version'] ) ) {
     170                        /* translators: %s: style.css */
     171                        return sprintf( __( 'Version strings can only contain numeric and period characters (like 1.2). Please fix your %s line in your main plugin file and upload the plugin again.', 'wporg-plugins' ),
     172                                '<code>Version:</code>'
     173                        );
     174                }
     175
     176                // Prevent duplicate URLs.
     177                $plugin_uri = $this->plugin['PluginURI'];
     178                $author_uri = $this->plugin['AuthorURI'];
     179                if ( ! empty( $plugin_uri ) && ! empty( $author_uri ) && $plugin_uri == $author_uri ) {
     180                        return __( 'Duplicate plugin and author URLs. A plugin URL is a page/site that provides details about this specific plugin. An author URL is a page/site that provides information about the author of the plugin. You aren&rsquo;t required to provide both, so pick the one that best applies to your URL.', 'wporg-plugins' );
     181                }
     182
     183                // Don't send special plugins through Plugin Check.
     184                if ( ! has_category( 'special-case-plugin', $this->plugin_post ) ) {
     185                        // Pass it through Plugin Check and see how great this plugin really is.
     186                        $result = $this->check_plugin( $plugin_files );
     187
     188                        if ( ! $result ) {
     189                                /* translators: 1: Plugin Check Plugin URL, 2: make.wordpress.org/plugins */
     190                                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' ),
     191                                        '//wordpress.org/plugins/plugin-check/',
     192                                        '<a href="https://make.wordpress.org/plugins">https://make.wordpress.org/plugins</a>'
     193                                );
     194                        }
     195                }
     196
     197                // Passed all tests!
     198                // Let's save everything and get things wrapped up.
     199
     200                // Add a Plugin Directory entry for this plugin.
     201                $post_id = $this->create_plugin_post();
     202
     203                $attachment = $this->save_zip_file( $post_id );
     204                if ( is_wp_error( $attachment ) ) {
     205                        return $attachment->get_error_message();
     206                }
     207
     208                // Send plugin author an email for peace of mind.
     209                $this->send_email_notification();
     210
     211                do_action( 'plugin_upload', $this->plugin, $this->plugin_post );
     212
     213                // Success!
     214                /* translators: 1: plugin name */
     215
     216                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' ),
     217                        $this->plugin['Name']
     218                );
     219        }
     220
     221        /**
     222         * Creates a temporary directory, and the plugin dir within it.
     223         */
     224        public function create_tmp_dirs() {
     225                // Create a temporary directory if it doesn't exist yet.
     226                $tmp = '/tmp/wporg-plugin-upload';
     227                if ( ! is_dir( $tmp ) ) {
     228                        mkdir( $tmp, 0777 );
     229                }
     230
     231                // Create file with unique file name.
     232                $this->tmp_dir = tempnam( $tmp, 'WPORG_PLUGIN_' );
     233
     234                // Remove that file.
     235                unlink( $this->tmp_dir );
     236
     237                // Create a directory with that unique name.
     238                mkdir( $this->tmp_dir, 0777 );
     239
     240                // Get a sanitized name for that plugin and create a directory for it.
     241                $base_name        = $this->get_sanitized_zip_name();
     242                $this->plugin_dir = "{$this->tmp_dir}/{$base_name}";
     243                mkdir( $this->plugin_dir, 0777 );
     244
     245                // Make sure we clean up after ourselves.
     246                add_action( 'shutdown', array( $this, 'remove_files' ) );
     247        }
     248
     249        /**
     250         * Unzips the uploaded plugin and saves it in the temporary plugin dir.
     251         */
     252        public function unwrap_package() {
     253                $unzip      = escapeshellarg( self::UNZIP );
     254                $zip_file   = escapeshellarg( $_FILES['zip_file']['tmp_name'] );
     255                $plugin_dir = escapeshellarg( $this->plugin_dir );
     256
     257                // Unzip it into the plugin directory.
     258                exec( escapeshellcmd( "{$unzip} -DD {$zip_file} -d {$plugin_dir}" ) );
     259
     260                // Fix any permissions issues with the files. Sets 755 on directories, 644 on files.
     261                exec( escapeshellcmd( "chmod -R 755 {$plugin_dir}" ) );
     262                exec( escapeshellcmd( "find {$plugin_dir} -type f -print0" ) . ' | xargs -I% -0 chmod 644 %' );
     263        }
     264
     265        /**
     266         * Returns the the plugin post if it already exists in the repository.
     267         *
     268         * @return \WP_Post|null
     269         */
     270        public function get_plugin_post() {
     271                $plugins = get_posts( array(
     272                        'name'             => $this->plugin_slug,
     273                        'posts_per_page'   => 1,
     274                        'post_type'        => 'plugin',
     275                        'orderby'          => 'ID',
     276                        /*
     277                         * Specify post stati so this query returns a result for draft plugins, even
     278                         * if the uploading user doesn't have have the permission to view drafts.
     279                         */
     280                        'post_status'      => array( 'publish', 'pending', 'draft', 'future', 'trash', 'suspend' ),
     281                        'suppress_filters' => false,
     282                ) );
     283
     284                return current( $plugins );
     285        }
     286
     287        /**
     288         * Whether the uploaded plugin uses a reserved slug.
     289         *
     290         * Passes if the author happens to be `wordpressdotorg`.
     291         *
     292         * @return bool
     293         */
     294        public function has_reserved_slug() {
     295                $reserved_slugs = array(
     296                        // Plugin Directory URL parameters.
     297                        'browse',
     298                        'tag',
     299                        'search',
     300                        'filter',
     301                        'upload',
     302                        'featured',
     303                        'popular',
     304                        'new',
     305                        'updated',
     306                );
     307
     308                return in_array( $this->plugin_slug, $reserved_slugs ) && 'wordpressdotorg' !== $this->author->user_login;
     309        }
     310
     311        /**
     312         * Sends a plugin through Plugin Check.
     313         *
     314         * @param array $files All plugin files to check.
     315         * @return bool Whether the plugin passed the checks.
     316         */
     317        public function check_plugin( $files ) {
     318
     319                // Run the checks.
     320                // @todo Include plugin checker.
     321                $result = true;
     322
     323                // Display the errors.
     324                $verdict = $result ? array( 'tc-pass', __( 'Pass', 'wporg-plugins' ) ) : array(
     325                        'tc-fail',
     326                        __( 'Fail', 'wporg-plugins' )
     327                );
     328                echo '<h4>' . sprintf( __( 'Results of Automated Plugin Scanning: %s', 'wporg-plugins' ), vsprintf( '<span class="%1$s">%2$s</span>', $verdict ) ) . '</h4>';
     329                echo '<ul class="tc-result">' . 'Result' . '</ul>';
     330                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 pass review. All submitted plugins are reviewed manually before approval.', 'wporg-plugins' ) . '</p></div>';
     331
     332                return $result;
     333        }
     334
     335        /**
     336         * Creates a plugin post.
     337         *
     338         * @return int|\WP_Error The post ID on success. The value 0 or WP_Error on failure.
     339         */
     340        public function create_plugin_post() {
     341                $upload_date = current_time( 'mysql' );
     342
     343                return wp_insert_post( array(
     344                        'post_author'    => $this->author->ID,
     345                        'post_title'     => $this->plugin['Name'],
     346                        'post_name'      => $this->plugin_slug,
     347                        'post_excerpt'   => $this->plugin['Description'],
     348                        'post_date'      => $upload_date,
     349                        'post_date_gmt'  => $upload_date,
     350                        'comment_status' => 'closed',
     351                        'ping_status'    => 'closed',
     352                        'post_status'    => 'pending',
     353                        'post_type'      => 'plugin',
     354                        'tags_input'     => $this->plugin['Tags'],
     355                ) );
     356        }
     357
     358        /**
     359         * Saves zip file and attaches it to the plugin post.
     360         *
     361         * @param int $post_id Post ID.
     362         * @return int|\WP_Error Attachment ID or upload error.
     363         */
     364        public function save_zip_file( $post_id ) {
     365                add_filter( 'site_option_upload_filetypes', array( $this, 'whitelist_zip_files' ) );
     366                add_filter( 'default_site_option_upload_filetypes', array( $this, 'whitelist_zip_files' ) );
     367
     368                $attachment_id = media_handle_upload( 'zip_file', $post_id );
     369
     370                remove_filter( 'site_option_upload_filetypes', array( $this, 'whitelist_zip_files' ) );
     371                remove_filter( 'default_site_option_upload_filetypes', array( $this, 'whitelist_zip_files' ) );
     372
     373                return $attachment_id;
     374        }
     375
     376        /**
     377         * Sends out an email confirmation to the plugin's author.
     378         */
     379        public function send_email_notification() {
     380
     381                /* translators: %s: plugin name */
     382                $email_subject = sprintf( __( '[WordPress Plugins] New Plugin - %s', 'wporg-plugins' ),
     383                        $this->plugin['Name']
     384                );
     385
     386                /* translators: 1: plugin name, 2: Trac ticket URL */
     387                $email_content = sprintf( __( 'Thank you for uploading %1$s to the WordPress Plugin Directory. If your plugin is selected to be part of the directory we\'ll send a follow up email.
     388
     389--
     390The WordPress.org Plugins Team
     391https://make.wordpress.org/plugins', 'wporg-plugins' ),
     392                        $this->plugin['Name']
     393                );
     394
     395                wp_mail( $this->author->user_email, $email_subject, $email_content, 'From: plugins@wordpress.org' );
     396        }
     397
     398        // Helper
     399
     400        /**
     401         * Returns a sanitized version of the uploaded zip file name.
     402         *
     403         * @return string
     404         */
     405        public function get_sanitized_zip_name() {
     406                return preg_replace( '|\W|', '', strtolower( basename( $_FILES['zip_file']['name'], '.zip' ) ) );
     407        }
     408
     409        /**
     410         * Returns all (usable) files of a given directory.
     411         *
     412         * @param string $dir Path to directory to search.
     413         *
     414         * @return array All files within the passed directory.
     415         */
     416        public function get_all_files( $dir ) {
     417                $files        = array();
     418                $dir_iterator = new \RecursiveDirectoryIterator( $dir );
     419                $iterator     = new \RecursiveIteratorIterator( $dir_iterator, \RecursiveIteratorIterator::SELF_FIRST );
     420
     421                foreach ( $iterator as $file ) {
     422                        // Only return files that are no directory references or Mac resource forks.
     423                        if ( $file->isFile() && ! in_array( $file->getBasename(), array(
     424                                        '..',
     425                                        '.'
     426                                ) ) && ! stristr( $file->getPathname(), '__MACOSX' )
     427                        ) {
     428                                array_push( $files, $file->getPathname() );
     429                        }
     430                }
     431
     432                return $files;
     433        }
     434
     435        /**
     436         * Whitelist zip files to be allowed to be uploaded to the media library.
     437         *
     438         * @param string $site_exts Whitelisted file extentions.
     439         *
     440         * @return string Whitelisted file extentions.
     441         */
     442        public function whitelist_zip_files( $site_exts ) {
     443                $file_extenstions   = explode( ' ', $site_exts );
     444                $file_extenstions[] = 'zip';
     445
     446                return implode( ' ', array_unique( $file_extenstions ) );
     447        }
     448
     449        /**
     450         * Deletes the temporary directory.
     451         */
     452        public function remove_files() {
     453                $rm    = escapeshellarg( self::RM );
     454                $files = escapeshellarg( $this->tmp_dir );
     455
     456                exec( escapeshellcmd( "{$rm} -rf {$files}" ) );
     457        }
     458
     459        /**
     460         * Strips invalid UTF-8 characters.
     461         *
     462         * Non-UTF-8 characters in plugin descriptions will causes blank descriptions in plugins.trac.
     463         *
     464         * @param string $string The string to be converted.
     465         *
     466         * @return string The converted string.
     467         */
     468        protected function strip_non_utf8( $string ) {
     469                ini_set( 'mbstring.substitute_character', 'none' );
     470
     471                return mb_convert_encoding( $string, 'UTF-8', 'UTF-8' );
     472        }
     473}
  • wp-content/plugins/plugin-directory/shortcodes/class-upload.php

    Property changes on: wp-content/plugins/plugin-directory/shortcodes/class-upload-handler.php
    ___________________________________________________________________
    Added: svn:eol-style
    ## -0,0 +1 ##
    +native
    \ No newline at end of property
     
     1<?php
     2namespace WordPressdotorg\Plugin_Directory\Shortcodes;
     3
     4class Upload {
     5
     6        /**
     7         * Renders the upload shortcode.
     8         */
     9        public static function display() {
     10                if ( is_user_logged_in() ) :
     11
     12                        if ( ! empty( $_POST['_wpnonce'] ) && wp_verify_nonce( $_POST['_wpnonce'], 'wporg-plugins-upload' ) && 'upload' === $_POST['action'] ) {
     13                                if ( UPLOAD_ERR_OK === $_FILES['zip_file']['error'] ) {
     14                                        switch_to_blog( WPORG_PLUGIN_DIRECTORY_BLOGID );
     15                                        $uploader = new Upload_Handler;
     16                                        $message  = $uploader->process_upload();
     17                                        restore_current_blog();
     18
     19                                }  else {
     20                                        $message = __( 'Error in file upload.', 'wporg-plugins' );
     21                                }
     22
     23                                if ( ! empty( $message ) ) {
     24                                        echo "<div class='notice notice-warning'><p>{$message}</p></div>\n";
     25                                }
     26                        }
     27                        ?>
     28                        <form enctype="multipart/form-data" id="upload_form" method="POST" action="">
     29                                <?php wp_nonce_field( 'wporg-plugins-upload' ); ?>
     30                                <input type="hidden" name="action" value="upload"/>
     31                                <input type="file" id="zip_file" name="zip_file" size="25"/>
     32                                <input id="upload_button" class="button" type="submit" value="<?php esc_attr_e( 'Upload', 'wporg-plugins' ); ?>"/>
     33
     34                                <p>
     35                                        <small><?php printf( __( 'Maximum allowed file size: %s', 'wporg-plugins' ), esc_html( self::get_max_allowed_file_size() ) ); ?></small>
     36                                </p>
     37                        </form>
     38                <?php else : ?>
     39                        <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>
     41                <?php endif;
     42        }
     43
     44        /**
     45         * Returns a human readable version of the max allowed upload size.
     46         *
     47         * @return string The allowed file size.
     48         */
     49        public static function get_max_allowed_file_size() {
     50                $upload_size_unit = wp_max_upload_size();
     51                $byte_sizes       = array( 'KB', 'MB', 'GB' );
     52
     53                for ( $unit = - 1; $upload_size_unit > 1024 && $unit < count( $byte_sizes ) - 1; $unit ++ ) {
     54                        $upload_size_unit /= 1024;
     55                }
     56
     57                if ( $unit < 0 ) {
     58                        $upload_size_unit = $unit = 0;
     59                } else {
     60                        $upload_size_unit = (int) $upload_size_unit;
     61                }
     62
     63                return $upload_size_unit . $byte_sizes[ $unit ];
     64        }
     65}