Making WordPress.org


Ignore:
Timestamp:
01/15/2024 05:23:52 AM (2 years ago)
Author:
dd32
Message:

Plugin Directory: Allow plugins to upload additional versions for review.

This is currently limited to new plugins, which is prior to the first human review.

Iterations upon this will open it to plugins in the pending state as well, which is during the human review.

See #7384.

File:
1 edited

Legend:

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

    r13021 r13109  
    22namespace WordPressdotorg\Plugin_Directory\Shortcodes;
    33
     4use WP_Error;
    45use WordPressdotorg\Plugin_Directory\CLI\Import;
    56use WordPressdotorg\Plugin_Directory\Readme\Parser;
     
    5859     * Runs various tests and creates plugin post.
    5960     *
     61     * @param int $for_plugin Optional. The plugin being uploaded to. This is used when adding additional .zip files.
     62     *
    6063     * @return string|WP_Error Confirmation message on success, WP_Error object on failure.
    6164     */
    62     public function process_upload() {
     65    public function process_upload( $for_plugin = 0 ) {
    6366        if ( UPLOAD_ERR_OK !== $_FILES['zip_file']['error'] ) {
    64             return new \WP_Error( 'error_upload', __( 'Error in file upload.', 'wporg-plugins' ) );
     67            return new WP_Error( 'error_upload', __( 'Error in file upload.', 'wporg-plugins' ) );
    6568        }
    6669
    6770        // Validate the maximum upload size.
    6871        if ( $_FILES['zip_file']['size'] > wp_max_upload_size() ) {
    69             return new \WP_Error( 'error_upload', __( 'Error in file upload.', 'wporg-plugins' ) );
     72            return new WP_Error( 'error_upload', __( 'Error in file upload.', 'wporg-plugins' ) );
    7073        }
    7174
     
    7376        $has_upload_token = $this->has_valid_upload_token();
    7477        $this->plugin_dir = Filesystem::unzip( $zip_file );
     78
     79        $plugin_post       = $for_plugin ? get_post( $for_plugin ) : false;
     80        $updating_existing = (bool) $plugin_post;
     81        $this->plugin_slug = $plugin_post->post_name ?? '';
     82
     83        if ( $for_plugin && ! $updating_existing ) {
     84            return new WP_Error( 'error_upload', __( 'Error in file upload.', 'wporg-plugins' ) );
     85        }
     86
     87        // Allow plugin reviewers to bypass some restrictions.
     88        if ( $updating_existing && current_user_can( 'approve_plugins' ) && ! $has_upload_token ) {
     89            $has_upload_token = true;
     90        }
     91
     92        // If the plugin was uploaded using a token, we'll assume future uploads for the plugin should use one.
     93        if ( $updating_existing && ! $has_upload_token && $plugin_post->{'_used_upload_token'} ) {
     94            $has_upload_token = true;
     95        }
    7596
    7697        $plugin_data = (array) Import::find_plugin_headers( $this->plugin_dir, 1 /* Max Depth to search */ );
     
    85106            $error = __( 'Error: The plugin has no name.', 'wporg-plugins' );
    86107
    87             return new \WP_Error( 'no_name', $error . ' ' . sprintf(
     108            return new WP_Error( 'no_name', $error . ' ' . sprintf(
    88109                /* translators: 1: plugin header line, 2: Documentation URL */
    89110                __( 'Add a %1$s line to your main plugin file and upload the plugin again. For more information, please review our documentation on <a href="%2$s">Plugin Headers</a>.', 'wporg-plugins' ),
     
    94115
    95116        // Determine the plugin slug based on the name of the plugin in the main plugin file.
    96         $this->plugin_slug = remove_accents( $this->plugin['Name'] );
    97         $this->plugin_slug = preg_replace( '/[^a-z0-9 _.-]/i', '', $this->plugin_slug );
    98         $this->plugin_slug = str_replace( '_', '-', $this->plugin_slug );
    99         $this->plugin_slug = sanitize_title_with_dashes( $this->plugin_slug );
     117        if ( ! $this->plugin_slug ) {
     118            $this->plugin_slug = $this->generate_plugin_slug( $this->plugin['Name'] );
     119        }
    100120
    101121        if ( ! $this->plugin_slug ) {
    102122            $error = __( 'Error: The plugin has an unsupported name.', 'wporg-plugins' );
    103123
    104             return new \WP_Error( 'unsupported_name', $error . ' ' . sprintf(
     124            return new WP_Error( 'unsupported_name', $error . ' ' . sprintf(
    105125                /* translators: %s: 'Plugin Name:' */
    106126                __( 'Plugin names may only contain latin letters (A-z), numbers, spaces, and hyphens. Please change the %s line in your main plugin file and readme, then you may upload it again.', 'wporg-plugins' ),
     
    114134            $error = __( 'Error: The plugin has a reserved name.', 'wporg-plugins' );
    115135
    116             return new \WP_Error( 'reserved_name', $error . ' ' . sprintf(
     136            return new WP_Error( 'reserved_name', $error . ' ' . sprintf(
    117137                /* translators: 1: plugin slug, 2: 'Plugin Name:' */
    118138                __( 'Your chosen plugin name - %1$s - has been reserved or otherwise restricted from use entirely. Please change the %2$s line in your main plugin file and readme, then you may upload it again.', 'wporg-plugins' ),
     
    123143
    124144        // Make sure it doesn't use a TRADEMARK protected slug.
    125         if ( false !== $this->has_trademarked_slug() && ! $has_upload_token ) {
     145        if ( ! $updating_existing ) {
     146            $has_trademarked_slug = $this->has_trademarked_slug( $this->plugin_slug );
     147        } else {
     148            // If we're updating an existing plugin, we need to check the new name, but the slug may be different.
     149            $has_trademarked_slug = $this->has_trademarked_slug(
     150                $this->generate_plugin_slug( $this->plugin['Name'] )
     151            );
     152        }
     153        if ( false !== $has_trademarked_slug && ! $has_upload_token ) {
    126154            $error = __( 'Error: The plugin name includes a restricted term.', 'wporg-plugins' );
    127155
    128             if ( $this->has_trademarked_slug() === trim( $this->has_trademarked_slug(), '-' ) ) {
     156            if ( $has_trademarked_slug === trim( $has_trademarked_slug, '-' ) ) {
    129157                // Trademarks that do NOT end in "-" indicate slug cannot contain term at all.
    130158                $message = sprintf(
    131159                    /* translators: 1: plugin slug, 2: trademarked term, 3: 'Plugin Name:', 4: plugin email address */
    132160                    __( 'Your chosen plugin name - %1$s - contains the restricted term "%2$s" and cannot be used at all in your plugin permalink nor the display name. To proceed with this submission you must remove "%2$s" from the %3$s line in both your main plugin file and readme entirely. Once you\'ve finished, you may upload the plugin again. Do not attempt to work around this by removing letters (i.e. WordPess) or using numbers (4 instead of A). Those are seen as intentional actions to avoid our restrictions, and are not permitted. If you feel this is in error, such as you legally own the trademark for a term, please email us at %4$s and explain your situation.', 'wporg-plugins' ),
    133                     '<code>' . $this->plugin_slug . '</code>',
    134                     trim( $this->has_trademarked_slug(), '-' ),
     161                    '<code>' . esc_html( $this->plugin['Name'] ) . '</code>',
     162                    trim( $has_trademarked_slug, '-' ),
    135163                    '<code>Plugin Name:</code>',
    136164                    '<code>plugins@wordpress.org</code>'
     
    141169                    /* translators: 1: plugin slug, 2: trademarked term, 3: 'Plugin Name:', 4: plugin email address */
    142170                    __( 'Your chosen plugin name - %1$s - contains the restricted term "%2$s" and cannot be used to begin your permalink or display name. We disallow the use of certain terms in ways that are abused, or potentially infringe on and/or are misleading with regards to trademarks. In order to proceed with this submission, you must change the %3$s line in your main plugin file and readme to end with  "-%2$s" instead. Once you\'ve finished, you may upload the plugin again. If you feel this is in error, such as you legally own the trademark for the term, please email us at %4$s and explain your situation.', 'wporg-plugins' ),
    143                     '<code>' . $this->plugin_slug . '</code>',
    144                     trim( $this->has_trademarked_slug(), '-' ),
     171                    '<code>' . esc_html( $this->plugin['Name'] ) . '</code>',
     172                    trim( $has_trademarked_slug, '-' ),
    145173                    '<code>Plugin Name:</code>',
    146174                    '<code>plugins@wordpress.org</code>'
     
    148176            }
    149177
    150             return new \WP_Error( 'trademarked_name', $error . ' ' . $message );
    151         }
    152 
    153         $plugin_post = Plugin_Directory::get_plugin_post( $this->plugin_slug );
     178            return new WP_Error( 'trademarked_name', $error . ' ' . $message );
     179        }
     180
     181        if ( ! $plugin_post ) {
     182            $plugin_post = Plugin_Directory::get_plugin_post( $this->plugin_slug );
     183        }
    154184
    155185        // If no matching plugin by that slug, check to see if a plugin exists with that Title in the database.
     
    170200            $error = __( 'Error: The plugin already exists.', 'wporg-plugins' );
    171201
    172             return new \WP_Error( 'already_exists', $error . ' ' . sprintf(
     202            return new WP_Error( 'already_exists', $error . ' ' . sprintf(
    173203                /* translators: 1: plugin slug, 2: 'Plugin Name:' */
    174204                __( 'There is already a plugin with the name %1$s in the directory. You must rename your plugin by changing the %2$s line in your main plugin file and in your readme. Once you have done so, you may upload it again.', 'wporg-plugins' ),
     
    179209
    180210        // Is there already a plugin with the same slug by the same author?
    181         if ( $plugin_post ) {
     211        if ( $plugin_post && ! $updating_existing ) {
    182212            $error = __( 'Error: The plugin has already been submitted.', 'wporg-plugins' );
    183213
    184             return new \WP_Error( 'already_submitted', $error . ' ' . sprintf(
     214            return new WP_Error( 'already_submitted', $error . ' ' . sprintf(
    185215                /* translators: 1: plugin slug, 2: Documentation URL, 3: plugins@wordpress.org */
    186216                __( 'You have already submitted a plugin named %1$s. There is no need to resubmit existing plugins, even for new versions. Instead, please update your plugin within the directory via <a href="%2$s">SVN</a>. If you need assistance, email <a href="mailto:%3$s">%3$s</a> and let us know.', 'wporg-plugins' ),
     
    195225            $error = __( 'Error: The plugin slug is too short.', 'wporg-plugins' );
    196226
    197             return new \WP_Error( 'trademarked_name', $error . ' ' . sprintf(
     227            return new WP_Error( 'trademarked_name', $error . ' ' . sprintf(
    198228                /* translators: 1: plugin slug, 2: 'Plugin Name:' */
    199229                __( 'Your chosen plugin name - %1$s - is not permitted because it is too short. Please change the %2$s line in your main plugin file and readme to a different name. When you have finished, you may upload your plugin again.', 'wporg-plugins' ),
     
    207237            $error = __( 'Error: The plugin has no description.', 'wporg-plugins' );
    208238
    209             return new \WP_Error( 'no_description', $error . ' ' . sprintf(
     239            return new WP_Error( 'no_description', $error . ' ' . sprintf(
    210240                /* translators: 1: plugin header line, 2: Documentation URL */
    211241                __( 'We cannot find a description in your plugin headers. Please add a %1$s line to your main plugin file and upload the complete plugin again. If you need more information, please review our documentation on <a href="%2$s">Plugin Headers</a>.', 'wporg-plugins' ),
     
    219249            $error = __( 'Error: The plugin has no version.', 'wporg-plugins' );
    220250
    221             return new \WP_Error( 'no_version', $error . ' ' . sprintf(
     251            return new WP_Error( 'no_version', $error . ' ' . sprintf(
    222252                /* translators: 1: plugin header line, 2: Documentation URL */
    223253                __( 'We cannot find a version listed in your plugin headers. Please add a %1$s line to your main plugin file and upload the complete plugin again. If you need more information, please review our documentation on <a href="%2$s">Plugin Headers</a>.', 'wporg-plugins' ),
     
    231261            $error = __( 'Error: Plugin versions are expected to be numbers.', 'wporg-plugins' );
    232262
    233             return new \WP_Error( 'invalid_version', $error . ' ' . sprintf(
     263            return new WP_Error( 'invalid_version', $error . ' ' . sprintf(
    234264                /* translators: %s: 'Version:' */
    235265                __( 'Version strings may only contain numeric and period characters (i.e. 1.2). Please correct the %s line in your main plugin file and upload the plugin again.', 'wporg-plugins' ),
     
    240270        // Prevent duplicate URLs.
    241271        // This is part of how the API looks for updates, so having them different helps prevent conflicts.
    242         if ( ! empty( $this->plugin['PluginURI'] ) && ! empty( $this->plugin['AuthorURI'] ) && $this->plugin['PluginURI'] == $this->plugin['AuthorURI'] ) {
     272        if (
     273            ! empty( $this->plugin['PluginURI'] ) &&
     274            ! empty( $this->plugin['AuthorURI'] ) &&
     275            $this->plugin['PluginURI'] == $this->plugin['AuthorURI']
     276        ) {
    243277            $error = __( 'Error: Your plugin and author URIs are the same.', 'wporg-plugins' );
    244278
    245             return new \WP_Error(
     279            return new WP_Error(
    246280                'plugin_author_uri', $error . ' ' .
    247281                __( 'Your plugin headers in the main plugin file headers have the same value for both the plugin and author URI (Uniform Resource Identifier). A plugin URI is a webpage that provides details about this specific plugin. An author URI is a webpage that provides information about the author of the plugin. Those two must be different. You are not required to provide both, so pick the one that best applies to your situation.', 'wporg-plugins' )
     
    250284
    251285        // Prevent uploads using popular Plugin names in the wild.
    252         if ( function_exists( 'wporg_stats_get_plugin_name_install_count' ) && ! $has_upload_token ) {
     286        if ( function_exists( 'wporg_stats_get_plugin_name_install_count' ) && ! $has_upload_token && ! $updating_existing ) {
    253287            $installs = wporg_stats_get_plugin_name_install_count( $this->plugin['Name'] );
    254288
     
    256290                $error = __( 'Error: That plugin name is already in use.', 'wporg-plugins' );
    257291
    258                 return new \WP_Error( 'already_exists_in_the_wild', $error . ' ' . sprintf(
     292                return new WP_Error( 'already_exists_in_the_wild', $error . ' ' . sprintf(
    259293                    /* translators: 1: plugin slug, 2: 'Plugin Name:' */
    260294                    __( 'There is already a plugin with the name %1$s known to exist, though it is not hosted on WordPress.org. This means the permalink %2$s is already in use, and has a significant user base. Were we to accept it as-is, our system would overwrite those other installs and potentially damage any existing users. This is especially true since WordPress 5.5 and up will automatically update plugins and themes. You must rename your plugin by changing the %3$s line in your main plugin file and in your readme. Once you have done so, you may upload it again. If you feel this is an incorrect assessment of the situation, please email <a href="mailto:%4$s">%4$s</a> and explain why so that we may help you.', 'wporg-plugins' ),
     
    272306            $error = __( 'Error: The plugin has no readme.', 'wporg-plugins' );
    273307
    274             return new \WP_Error( 'no_readme', $error . ' ' . sprintf(
     308            return new WP_Error( 'no_readme', $error . ' ' . sprintf(
    275309                /* translators: 1: readme.txt, 2: readme.md */
    276310                __( 'The zip file must include a file named %1$s or %2$s. We recommend using %1$s as it will allow you to fully utilize our directory.', 'wporg-plugins' ),
     
    283317        // Double check no existing plugins clash with the readme title.
    284318        $readme_plugin_post = get_posts( array(
    285             'post_type'   => 'plugin',
    286             'title'       => $readme->name,
    287             'post_status' => array( 'publish', 'pending', 'disabled', 'closed', 'new', 'draft', 'approved' ),
     319            'post_type'    => 'plugin',
     320            'title'        => $readme->name,
     321            'post_status'  => array( 'publish', 'pending', 'disabled', 'closed', 'new', 'draft', 'approved' ),
     322            'post__not_in' => $plugin_post ? array( $plugin_post->ID ) : [],
    288323        ) );
    289324        if ( $readme_plugin_post && trim( $readme->name ) ) {
     
    291326
    292327            if ( $readme_plugin_post->post_author != get_current_user_id() ) {
    293                 return new \WP_Error( 'already_submitted', $error . ' ' . sprintf(
     328                return new WP_Error( 'already_submitted', $error . ' ' . sprintf(
    294329                    /* translators: 1: plugin slug, 2: 'Plugin Name:' */
    295330                    __( 'There is already a plugin with the name %1$s in the directory. You must rename your plugin by changing the %2$s line in your main plugin file and in your readme. Once you have done so, you may upload it again.', 'wporg-plugins' ),
     
    299334            }
    300335
    301             return new \WP_Error( 'already_submitted', $error . ' ' . sprintf(
     336            return new WP_Error( 'already_submitted', $error . ' ' . sprintf(
    302337                /* translators: 1: plugin slug, 2: Documentation URL, 3: plugins@wordpress.org */
    303338                __( 'You have already submitted a plugin named %1$s. There is no need to resubmit existing plugins, even for new versions. Instead, please update your plugin within the directory via <a href="%2$s">SVN</a>. If you need assistance, email <a href="mailto:%3$s">%3$s</a> and let us know.', 'wporg-plugins' ),
     
    308343        }
    309344
    310         if ( function_exists( 'wporg_stats_get_plugin_name_install_count' ) && ! $has_upload_token ) {
     345        if ( function_exists( 'wporg_stats_get_plugin_name_install_count' ) && ! $has_upload_token && ! $updating_existing ) {
    311346            $installs = wporg_stats_get_plugin_name_install_count( $readme->name );
    312347
     
    314349                $error = __( 'Error: That plugin name is already in use.', 'wporg-plugins' );
    315350
    316                 return new \WP_Error( 'already_exists_in_the_wild', $error . ' ' . sprintf(
     351                return new WP_Error( 'already_exists_in_the_wild', $error . ' ' . sprintf(
    317352                    /* translators: 1: plugin slug, 2: 'Plugin Name:' */
    318353                    __( 'There is already a plugin with the name %1$s known to exist, though it is not hosted on WordPress.org. This means the permalink %2$s is already in use, and has a significant user base. Were we to accept it as-is, our system would overwrite those other installs and potentially damage any existing users. This is especially true since WordPress 5.5 and up will automatically update plugins and themes. You must rename your plugin by changing the %3$s line in your main plugin file and in your readme. Once you have done so, you may upload it again. If you feel this is an incorrect assessment of the situation, please email <a href="mailto:%4$s">%4$s</a> and explain why so that we may help you.', 'wporg-plugins' ),
     
    329364            $error = __( 'Error: No license defined.', 'wporg-plugins' );
    330365
    331             return new \WP_Error( 'no_license', $error . ' ' . sprintf(
     366            return new WP_Error( 'no_license', $error . ' ' . sprintf(
    332367                /* translators: 1: readme.txt */
    333368                __( 'Your plugin has no license declared. Please update your %1$s with a GPLv2 (or later) compatible license.', 'wporg-plugins' ),
     
    338373        // Pass it through Plugin Check and see how great this plugin really is.
    339374        // We're not actually using this right now.
    340         $result = $this->check_plugin();
    341 
    342         if ( ! $result && ! $has_upload_token ) {
     375        $plugin_check_result = $this->check_plugin();
     376
     377        if ( ! $plugin_check_result && ! $has_upload_token ) {
    343378            $error = __( 'Error: The plugin has failed the automated checks.', 'wporg-plugins' );
    344379
    345             return new \WP_Error( 'failed_checks', $error . ' ' . sprintf(
     380            return new WP_Error( 'failed_checks', $error . ' ' . sprintf(
    346381                /* translators: 1: Plugin Check Plugin URL, 2: https://make.wordpress.org/plugins */
    347382                __( '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' ),
    348                 '//wordpress.org/plugins/plugin-check/',
     383                'https://wordpress.org/plugins/plugin-check/',
    349384                '<a href="https://make.wordpress.org/plugins">https://make.wordpress.org/plugins</a>'
    350385            ) );
     
    354389        // Let's save everything and get things wrapped up.
    355390        // Create a new post on first-time submissions.
     391        $content = '';
     392        foreach ( $readme->sections as $section => $section_content ) {
     393            $content .= "\n\n<!--section={$section}-->\n{$section_content}";
     394        }
     395
     396        $post_args = array(
     397            'ID'           => $plugin_post->ID ?? 0,
     398            'post_title'   => $this->plugin['Name'],
     399            'post_name'    => $this->plugin_slug,
     400            'post_status'  => $plugin_post->post_status ?? 'new',
     401            'post_content' => $content,
     402            'post_excerpt' => $this->plugin['Description'],
     403            // 'tax_input'    => wp_unslash( $_POST['tax_input'] ), // for category selection
     404            'meta_input'   => array(
     405                'tested'                   => $readme->tested,
     406                'requires'                 => $readme->requires,
     407                'requires_php'             => $readme->requires_php,
     408                'stable_tag'               => $readme->stable_tag,
     409                'upgrade_notice'           => $readme->upgrade_notice,
     410                'contributors'             => $readme->contributors,
     411                'screenshots'              => $readme->screenshots,
     412                'donate_link'              => $readme->donate_link,
     413                'license'                  => $readme->license,
     414                'license_uri'              => $readme->license_uri,
     415                'sections'                 => array_keys( $readme->sections ),
     416                'version'                  => $this->plugin['Version'],
     417                'header_name'              => $this->plugin['Name'],
     418                'header_plugin_uri'        => $this->plugin['PluginURI'],
     419                'header_author'            => $this->plugin['Author'],
     420                'header_author_uri'        => $this->plugin['AuthorURI'],
     421                'header_textdomain'        => $this->plugin['TextDomain'],
     422                'header_description'       => $this->plugin['Description'],
     423                'requires_plugins'         => array_filter( array_map( 'trim', explode( ',', $this->plugin['RequiresPlugins'] ) ) ),
     424                'assets_screenshots'       => array(),
     425                'assets_icons'             => array(),
     426                'assets_banners'           => array(),
     427                'assets_banners_color'     => false,
     428                'support_threads'          => 0,
     429                'support_threads_resolved' => 0,
     430                'downloads'                => 0,
     431                'last_updated'             => gmdate( 'Y-m-d H:i:s' ),
     432                'rating'                   => 0,
     433                'ratings'                  => array(),
     434                'active_installs'          => 0,
     435                '_active_installs'         => 0,
     436                'usage'                    => array(),
     437            ),
     438        );
     439
     440        // First time submission, track some additional metadata.
    356441        if ( ! $plugin_post ) {
    357             $content = '';
    358             foreach ( $readme->sections as $section => $section_content ) {
    359                 $content .= "\n\n<!--section={$section}-->\n{$section_content}";
    360             }
    361 
    362             // Add a Plugin Directory entry for this plugin.
    363             $plugin_post = Plugin_Directory::create_plugin_post( array(
    364                 'post_title'   => $this->plugin['Name'],
    365                 'post_name'    => $this->plugin_slug,
    366                 'post_status'  => 'new',
    367                 'post_content' => $content,
    368                 'post_excerpt' => $this->plugin['Description'],
    369                 // 'tax_input'    => wp_unslash( $_POST['tax_input'] ), // for category selection
    370                 'meta_input'   => array(
    371                     'tested'                   => $readme->tested,
    372                     'requires'                 => $readme->requires,
    373                     'requires_php'             => $readme->requires_php,
    374                     'stable_tag'               => $readme->stable_tag,
    375                     'upgrade_notice'           => $readme->upgrade_notice,
    376                     'contributors'             => $readme->contributors,
    377                     'screenshots'              => $readme->screenshots,
    378                     'donate_link'              => $readme->donate_link,
    379                     'license'                  => $readme->license,
    380                     'license_uri'              => $readme->license_uri,
    381                     'sections'                 => array_keys( $readme->sections ),
    382                     'version'                  => $this->plugin['Version'],
    383                     'header_name'              => $this->plugin['Name'],
    384                     'header_plugin_uri'        => $this->plugin['PluginURI'],
    385                     'header_author'            => $this->plugin['Author'],
    386                     'header_author_uri'        => $this->plugin['AuthorURI'],
    387                     'header_textdomain'        => $this->plugin['TextDomain'],
    388                     'header_description'       => $this->plugin['Description'],
    389                     'requires_plugins'         => array_filter( array_map( 'trim', explode( ',', $this->plugin['RequiresPlugins'] ) ) ),
    390                     'assets_screenshots'       => array(),
    391                     'assets_icons'             => array(),
    392                     'assets_banners'           => array(),
    393                     'assets_banners_color'     => false,
    394                     'support_threads'          => 0,
    395                     'support_threads_resolved' => 0,
    396                     'downloads'                => 0,
    397                     'last_updated'             => gmdate( 'Y-m-d H:i:s' ),
    398                     'rating'                   => 0,
    399                     'ratings'                  => array(),
    400                     'active_installs'          => 0,
    401                     '_active_installs'         => 0,
    402                     'usage'                    => array(),
    403                     '_author_ip'               => preg_replace( '/[^0-9a-fA-F:., ]/', '', $_SERVER['REMOTE_ADDR'] ),
    404                     '_submitted_date'          => time(),
    405                 ),
    406             ) );
    407             if ( is_wp_error( $plugin_post ) ) {
    408                 return $plugin_post->get_error_message();
    409             }
     442            $post_args['meta_input']['_author_ip']        = preg_replace( '/[^0-9a-fA-F:., ]/', '', $_SERVER['REMOTE_ADDR'] );
     443            $post_args['meta_input']['_submitted_date']   = time();
     444            $post_args['meta_input']['_used_upload_token'] = $has_upload_token;
     445        }
     446
     447        // Add/Update the Plugin Directory entry for this plugin.
     448        $plugin_post = Plugin_Directory::create_plugin_post( $post_args );
     449
     450        if ( is_wp_error( $plugin_post ) ) {
     451            return $plugin_post;
    410452        }
    411453
    412454        $attachment = $this->save_zip_file( $plugin_post->ID );
    413455        if ( is_wp_error( $attachment ) ) {
    414             return $attachment->get_error_message();
     456            return $attachment;
    415457        }
    416458
     
    422464        update_post_meta( $plugin_post->ID, '_submitted_zip_loc', $lines_of_code );
    423465
     466        do_action( 'plugin_upload', $this->plugin, $plugin_post );
     467
     468        if ( $updating_existing ) {
     469            return sprintf(
     470                __( 'New version of %s uploaded for review.', 'wporg-plugins' ),
     471                esc_html( $this->plugin['Name'] )
     472            );
     473        }
     474
    424475        // Send plugin author an email for peace of mind.
    425476        $this->send_email_notification();
    426 
    427         do_action( 'plugin_upload', $this->plugin, $plugin_post );
    428477
    429478        $message = sprintf(
     
    451500        // Success!
    452501        return $message;
     502    }
     503
     504    /**
     505     * Generate a plugin slug from a Plugin name.
     506     *
     507     * @param string $plugin_name The plugin name.
     508     * @return string The generated plugin slug.
     509     */
     510    public function generate_plugin_slug( $plugin_name ) {
     511        $plugin_slug = remove_accents( $plugin_name );
     512        $plugin_slug = preg_replace( '/[^a-z0-9 _.-]/i', '', $plugin_slug );
     513        $plugin_slug = str_replace( '_', '-', $plugin_slug );
     514        $plugin_slug = sanitize_title_with_dashes( $plugin_slug );
     515
     516        return $plugin_slug;
    453517    }
    454518
     
    505569     * @return string|false The trademarked slug if found, false otherwise.
    506570     */
    507     public function has_trademarked_slug() {
     571    public function has_trademarked_slug( $plugin_slug = false ) {
     572        $plugin_slug = $plugin_slug ?: $this->plugin_slug;
     573
    508574        $trademarked_slugs = array(
    509575            'adobe-',
     
    647713            if ( '-' === $trademark[-1] ) {
    648714                // Trademarks ending in "-" indicate slug cannot begin with that term.
    649                 if ( 0 === strpos( $this->plugin_slug, $trademark ) ) {
     715                if ( 0 === strpos( $plugin_slug, $trademark ) ) {
    650716                    $has_trademarked_slug = $trademark;
    651717                    break;
    652718                }
    653             } elseif ( false !== strpos( $this->plugin_slug, $trademark ) ) {
     719            } elseif ( false !== strpos( $plugin_slug, $trademark ) ) {
    654720                // Otherwise, the term cannot appear anywhere in slug.
    655721                $has_trademarked_slug = $trademark;
     
    662728            $for_trademark = '-for-' . $has_trademarked_slug;
    663729            // At this point we might be okay, but there's one more check.
    664             if ( $for_trademark === substr( $this->plugin_slug, -1 * strlen( $for_trademark ) ) ) {
     730            if ( $for_trademark === substr( $plugin_slug, -1 * strlen( $for_trademark ) ) ) {
    665731                // Yes the slug ENDS with 'for-TRADEMARK'.
    666732                $has_trademarked_slug = false;
     
    670736        // Check portmanteaus.
    671737        foreach ( $portmanteaus as $portmanteau ) {
    672             if ( 0 === strpos( $this->plugin_slug, $portmanteau ) ) {
     738            if ( 0 === strpos( $plugin_slug, $portmanteau ) ) {
    673739                $has_trademarked_slug = $portmanteau;
    674740                break;
     
    720786     *
    721787     * @param int $post_id Post ID.
    722      * @return int|\WP_Error Attachment ID or upload error.
     788     * @return int|WP_Error Attachment ID or upload error.
    723789     */
    724790    public function save_zip_file( $post_id ) {
     791        $zip_hash = sha1_file( $_FILES['zip_file']['tmp_name'] );
     792        if ( in_array( $zip_hash, get_post_meta( $post_id, 'uploaded_zip_hash' ) ?: [], true ) ) {
     793            return new WP_Error( 'already_uploaded', __( "You've already uploaded that ZIP file.", 'wporg-plugins' ) );
     794        }
    725795
    726796        // Upload folders are already year/month based. A second-based prefix should be specific enough.
     797        $original_name              = $_FILES['zip_file']['name'];
    727798        $_FILES['zip_file']['name'] = date( 'd_H-i-s' ) . '_' . $_FILES['zip_file']['name'];
    728799
     
    730801        add_filter( 'default_site_option_upload_filetypes', array( $this, 'whitelist_zip_files' ) );
    731802
    732         $attachment_id = media_handle_upload( 'zip_file', $post_id );
     803        // Store the plugin details against the media as well.
     804        $post_details  = array(
     805            'post_title'   => sprintf( '%s Version %s', $this->plugin['Name'], $this->plugin['Version'] ),
     806            'post_excerpt' => $this->plugin['Description'],
     807        );
     808        $attachment_id = media_handle_upload( 'zip_file', $post_id, $post_details );
    733809
    734810        remove_filter( 'site_option_upload_filetypes', array( $this, 'whitelist_zip_files' ) );
    735811        remove_filter( 'default_site_option_upload_filetypes', array( $this, 'whitelist_zip_files' ) );
     812
     813        if ( ! is_wp_error( $attachment_id ) ) {
     814            // Save some basic details with the ZIP.
     815            update_post_meta( $attachment_id, 'version', $this->plugin['Version'] );
     816            update_post_meta( $attachment_id, 'submitted_name', $original_name );
     817
     818            // And record this ZIP as having been uploaded.
     819            add_post_meta( $post_id, 'uploaded_zip_hash', $zip_hash );
     820        }
    736821
    737822        return $attachment_id;
Note: See TracChangeset for help on using the changeset viewer.