Making WordPress.org


Ignore:
Timestamp:
04/03/2024 07:01:21 AM (6 months ago)
Author:
dd32
Message:

Plugin Directory: Store error codes (rather than english error messages) that occur during a plugin import, to allow the errors to be generated with the appropriate translations.

This expands some errors to better highlight what is invalid as well.

See #6108.

File:
1 edited

Legend:

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

    r13267 r13443  
    6363     */
    6464    public function validate_content( $readme ) {
     65        $output = $this->validate( $readme );
     66
     67        // Translate error codes to human-readable messages.
     68        foreach ( $output as $type => $items ) {
     69            foreach ( $items as $error_code => $error_data ) {
     70                $output[ $type ][ $error_code ] = $this->translate_code_to_message( $error_code, $error_data );
     71            }
     72        }
     73
     74        return $output;
     75    }
     76
     77    /**
     78     * Validates a readme by string, and returns a structured array of errors, warnings, and notes.
     79     *
     80     * These elements can be swapped for a textual translated string at display time via translate_code_to_message().
     81     *
     82     * @param string $readme The text of the readme.
     83     * @return array Array of the readme validation result codes.
     84     */
     85    public function validate( $readme ) {
     86        $errors = $warnings = $notes = array();
    6587
    6688        // Security note: Keep the data: protocol here, Parser accepts a string HOWEVER
     
    6991        $readme = new Parser( 'data:text/plain,' . urlencode( $readme ) );
    7092
    71         $errors = $warnings = $notes = array();
    72 
    7393        // Fatal errors.
    74         if ( empty( $readme->name ) || isset( $readme->warnings['invalid_plugin_name_header'] ) ) {
    75             $errors[] = sprintf(
    76                 /* translators: 1: 'Plugin Name' section title, 2: 'Plugin Name' */
    77                 __( 'We cannot find a plugin name in your readme. Plugin names look like: %1$s. Please change %2$s to reflect the actual name of your plugin.', 'wporg-plugins' ),
    78                 '<code>=== Plugin Name ===</code>',
    79                 '<code>Plugin Name</code>'
    80             );
     94        if ( isset( $readme->warnings['invalid_plugin_name_header'] ) ) {
     95            $errors['invalid_plugin_name_header'] = $readme->warnings['invalid_plugin_name_header'];
     96        } elseif (  empty( $readme->name ) ) {
     97            $errors['invalid_plugin_name_header'] = true;
    8198        }
    8299
    83100        // Warnings & Notes.
    84101        if ( isset( $readme->warnings['requires_header_ignored'] ) ) {
    85             $latest_wordpress_version = defined( 'WP_CORE_STABLE_BRANCH' ) ? WP_CORE_STABLE_BRANCH : '5.0';
    86 
    87             $warnings[] = sprintf(
    88                 /* translators: 1: plugin header tag; 2: Example version 5.0. 3: Example version 4.9. */
    89                 __( 'The %1$s field was ignored. This field should only contain a valid WordPress version such as %2$s or %3$s.', 'wporg-plugins' ),
    90                 '<code>Requires at least</code>',
    91                 '<code>' . number_format( $latest_wordpress_version, 1 ) . '</code>',
    92                 '<code>' . number_format( $latest_wordpress_version - 0.1, 1 ) . '</code>'
     102            $warnings['requires_header_ignored'] = $readme->warnings['requires_header_ignored'];
     103        }
     104
     105        if ( isset( $readme->warnings['tested_header_ignored'] ) ) {
     106            $warnings['tested_header_ignored'] = $readme->warnings['tested_header_ignored'];
     107        } elseif ( empty( $readme->tested ) ) {
     108            $warnings['tested_header_missing'] = true;
     109        }
     110
     111        if ( isset( $readme->warnings['requires_php_header_ignored'] ) ) {
     112            $warnings['requires_php_header_ignored'] = $readme->warnings['requires_php_header_ignored'];
     113        }
     114
     115        if ( empty( $readme->stable_tag ) || str_contains( $readme->stable_tag, 'trunk' ) ) {
     116            $warnings['stable_tag_invalid'] = true;
     117        }
     118
     119        if ( isset( $readme->warnings['contributor_ignored'] ) ) {
     120            $warnings['contributor_ignored'] = $readme->warnings['contributor_ignored'];
     121        } elseif ( ! count( $readme->contributors ) ) {
     122            $notes['contributors_missing'] = true;
     123        }
     124
     125        if ( empty( $readme->license ) ) {
     126            $warnings['license_missing'] = true;
     127        }
     128
     129        if ( isset( $readme->warnings['too_many_tags'] ) ) {
     130            $warnings['too_many_tags'] = $readme->warnings['too_many_tags'];
     131        }
     132
     133        if ( isset( $readme->warnings['ignored_tags'] ) ) {
     134            $warnings['ignored_tags'] = $readme->warnings['ignored_tags'];
     135        }
     136
     137        // Check if the tags are low-quality (ie. little used)
     138        if ( $readme->tags && taxonomy_exists( 'plugin_tags' ) ) {
     139            $tags = get_terms( array(
     140                'taxonomy' => 'plugin_tags',
     141                'name'     => $readme->tags,
     142            ) );
     143
     144            $low_usage_tags = array_filter(
     145                $tags,
     146                function( $term ) {
     147                    return $term->count < 5;
     148                }
    93149            );
    94         }
    95 
    96         if ( isset( $readme->warnings['tested_header_ignored'] ) ) {
    97             $latest_wordpress_version = defined( 'WP_CORE_STABLE_BRANCH' ) ? WP_CORE_STABLE_BRANCH : '5.0';
    98 
    99             $warnings[] = sprintf(
    100                 /* translators: 1: plugin header tag; 2: Example version 5.0. 3: Example version 5.1. */
    101                 __( 'The %1$s field was ignored. This field should only contain a valid WordPress version such as %2$s or %3$s.', 'wporg-plugins' ),
    102                 '<code>Tested up to</code>',
    103                 '<code>' . number_format( $latest_wordpress_version, 1 ) . '</code>',
    104                 '<code>' . number_format( $latest_wordpress_version + 0.1, 1 ) . '</code>'
    105             );
    106         } elseif ( empty( $readme->tested ) ) {
    107             $warnings[] = sprintf(
    108                 /* translators: %s: plugin header tag */
    109                 __( 'The %s field is missing.', 'wporg-plugins' ),
    110                 '<code>Tested up to</code>'
    111             );
    112         }
    113 
    114         if ( isset( $readme->warnings['requires_php_header_ignored'] ) ) {
    115             $warnings[] = sprintf(
    116                 /* translators: 1: plugin header tag; 2: Example version 5.2.4. 3: Example version 7.0. */
    117                 __( 'The %1$s field was ignored. This field should only contain a PHP version such as %2$s or %3$s.', 'wporg-plugins' ),
    118                 '<code>Requires PHP</code>',
    119                 '<code>5.2.4</code>',
    120                 '<code>7.0</code>'
    121             );
    122         }
    123 
    124         if ( empty( $readme->stable_tag ) || str_contains( $readme->stable_tag, 'trunk' ) ) {
    125             $warnings[] = sprintf(
    126                 /* translators: 1: 'Stable tag', 2: /trunk/ SVN directory */
    127                 __( 'The %1$s field is missing or invalid.  Note: We ask you no longer attempt to use %2$s as stable, so that all plugins can be rolled back.', 'wporg-plugins' ),
    128                 '<code>Stable tag</code>',
    129                 '<code>/trunk/</code>'
    130             );
    131         }
    132 
    133         if ( isset( $readme->warnings['contributor_ignored'] ) ) {
    134             $warnings[] = sprintf(
    135                 /* translators: %s: plugin header tag */
    136                 __( 'One or more contributors listed were ignored. The %s field should only contain WordPress.org usernames. Remember that usernames are case-sensitive.', 'wporg-plugins' ),
    137                 '<code>Contributors</code>'
    138             );
    139         } elseif ( ! count( $readme->contributors ) ) {
    140             $notes[] = sprintf(
    141                 /* translators: %s: plugin header tag */
    142                 __( 'The %s field is missing.', 'wporg-plugins' ),
    143                 '<code>Contributors</code>'
    144             );
    145         }
    146 
    147         if ( isset( $readme->warnings['too_many_tags'] ) ) {
    148             $warnings[] = __( 'One or more tags were ignored. Please limit your plugin to 5 tags.', 'wporg-plugins' );
    149         }
    150 
    151         if ( isset( $readme->warnings['ignored_tags'] ) ) {
    152             $warnings[] = sprintf(
    153                 /* translators: %s: list of tags not supported */
    154                 __( 'One or more tags were ignored. The following tags are not permitted: %s', 'wporg-plugins' ),
    155                 '<code>' . implode( '</code>, <code>', $readme->ignore_tags ) . '</code>'
    156             );
    157         }
    158 
    159         if ( isset( $readme->warnings['low_usage_tags'] ) ) {
    160             $notes[] = sprintf(
    161                 /* translators: %s: list of tags with low usage. */
    162                 __( 'The following tags are not widely used: %s', 'wporg-plugins' ),
    163                 '<code>' . implode( '</code>, <code>', array_map( 'esc_html', $readme->warnings['low_usage_tags'] ) ) . '</code>'
    164             );
     150
     151            if ( $low_usage_tags ) {
     152                $notes['low_usage_tags'] = wp_list_pluck( $low_usage_tags, 'name' );
     153            }
    165154        }
    166155
    167156        if ( empty( $readme->requires ) ) {
    168             $notes[] = sprintf(
    169                 /* translators: %s: plugin header tag */
    170                 __( 'The %s field is missing. It should be defined here, or in your main plugin file.', 'wporg-plugins' ),
    171                 '<code>Requires at least</code>'
    172             );
     157            $notes['requires_header_missing'] = true;
    173158        }
    174159
    175160        if ( empty( $readme->requires_php ) ) {
    176             $notes[] = sprintf(
    177                 /* translators: %s: plugin header tag */
    178                 __( 'The %s field is missing. It should be defined here, or in your main plugin file.', 'wporg-plugins' ),
    179                 '<code>Requires PHP</code>'
    180             );
     161            $notes['requires_php_header_missing'] = true;
    181162        }
    182163
    183164        if ( isset( $readme->warnings['no_short_description_present'] ) ) {
    184             $notes[] = sprintf(
    185                 /* translators: %s: section title */
    186                 __( 'The %s section is missing. An excerpt was generated from your main plugin description.', 'wporg-plugins' ),
    187                 '<code>Short Description</code>'
    188             );
    189         } elseif( isset( $readme->warnings['trimmed_short_description'] ) ) {
    190             $warnings[] = sprintf(
    191                 /* translators: %s: section title */
    192                 __( 'The %s section is too long and was truncated. A maximum of 150 characters is supported.', 'wporg-plugins' ),
    193                 '<code>Short Description</code>',
    194                 number_format_i18n( $readme->maximum_field_lengths['short_description'] )
    195             );
     165            $notes['no_short_description_present'] = $readme->warnings['no_short_description_present'];
     166
     167        } elseif ( isset( $readme->warnings['trimmed_short_description'] ) ) {
     168            $warnings['trimmed_short_description'] = $readme->warnings['trimmed_short_description'];
    196169        }
    197170
     
    200173        }, ARRAY_FILTER_USE_KEY );
    201174        foreach ( $trimmed_sections as $section_name => $dummy ) {
    202             $section_name = str_replace( 'trimmed_section_', '', $section_name );
    203 
    204             $max_length_field = "section-{$section_name}";
    205             if ( ! isset( $readme->maximum_field_lengths[ $max_length_field ] ) ) {
    206                 $max_length_field = 'section';
    207             }
    208 
    209             $warnings[]   = sprintf(
    210                 /* translators: %s: section title */
    211                 __( 'The %s section is too long and was truncated. A maximum of %s words is supported.', 'wporg-plugins' ),
    212                 '<code>' . esc_html( ucwords( str_replace( '_', ' ', $section_name ) ) ) . '</code>',
    213                 number_format_i18n( $readme->maximum_field_lengths[ $max_length_field ] )
    214             );
     175            $warnings[ $section_name ] = true;
    215176        }
    216177
    217178        if ( empty( $readme->sections['faq'] ) ) {
    218             $notes[] = sprintf(
    219                 /* translators: %s: section title */
    220                 __( 'No %s section was found', 'wporg-plugins' ),
    221                 '<code>== Frequently Asked Questions ==</code>'
    222             );
     179            $notes['faq_missing'] = true;
    223180        }
    224181
    225182        if ( empty( $readme->sections['changelog'] ) ) {
    226             $notes[] = sprintf(
    227                 /* translators: %s: section title */
    228                 __( 'No %s section was found', 'wporg-plugins' ),
    229                 '<code>== Changelog ==</code>'
    230             );
     183            $notes['changelog_missing'] = true;
    231184        }
    232185
    233186        if ( empty( $readme->upgrade_notice ) ) {
    234             $notes[] = sprintf(
    235                 /* translators: %s: section title */
    236                 __( 'No %s section was found', 'wporg-plugins' ),
    237                 '<code>== Upgrade Notice ==</code>'
    238             );
     187            $notes['upgrade_notice_missing'] = true;
    239188        }
    240189
    241190        if ( empty( $readme->screenshots ) ) {
    242             $notes[] = sprintf(
    243                 /* translators: %s: section title */
    244                 __( 'No %s section was found', 'wporg-plugins' ),
    245                 '<code>== Screenshots ==</code>'
    246             );
     191            $notes['screenshots_missing'] = true;
    247192        }
    248193
    249194        if ( empty( $readme->donate_link ) ) {
    250             $notes[] = __( 'No donate link was found', 'wporg-plugins' );
     195            $notes['donate_link_missing'] = true;
    251196        }
    252197
    253198        return compact( 'errors', 'warnings', 'notes' );
    254 
    255199    }
    256200
     201    /**
     202     * Translate an error code to a human-readable message.
     203     *
     204     * @param string $error_code The error code to translate.
     205     * @param mixed  $data       Optional data to provide context in the message.
     206     * @return string|false The translated message, or false if the error code is not recognized.
     207     */
     208    public function translate_code_to_message( $error_code, $data = false ) {
     209        if ( $data && is_bool( $data ) ) {
     210            $data = false;
     211        }
     212
     213        switch( $error_code ) {
     214            case 'invalid_plugin_name_header':
     215                return sprintf(
     216                    /* translators: 1: 'Plugin Name' section title, 2: 'Plugin Name' */
     217                    __( 'We cannot find a plugin name in your readme. Plugin names look like: %1$s. Please change %2$s to reflect the actual name of your plugin.', 'wporg-plugins' ),
     218                    '<code>=== Plugin Name ===</code>',
     219                    '<code>Plugin Name</code>'
     220                );
     221            case 'requires_header_ignored':
     222                $latest_wordpress_version = defined( 'WP_CORE_STABLE_BRANCH' ) ? WP_CORE_STABLE_BRANCH : '6.5';
     223
     224                return sprintf(
     225                    /* translators: 1: plugin header tag; 2: Example version 5.0. 3: Example version 4.9. */
     226                    __( 'The %1$s field was ignored. This field should only contain a valid WordPress version such as %2$s or %3$s.', 'wporg-plugins' ),
     227                    '<code>Requires at least</code>',
     228                    '<code>' . number_format( $latest_wordpress_version, 1 ) . '</code>',
     229                    '<code>' . number_format( $latest_wordpress_version - 0.1, 1 ) . '</code>'
     230                );
     231            case 'tested_header_ignored':
     232                $latest_wordpress_version = defined( 'WP_CORE_STABLE_BRANCH' ) ? WP_CORE_STABLE_BRANCH : '6.5';
     233
     234                return sprintf(
     235                    /* translators: 1: plugin header tag; 2: Example version 5.0. 3: Example version 5.1. */
     236                    __( 'The %1$s field was ignored. This field should only contain a valid WordPress version such as %2$s or %3$s.', 'wporg-plugins' ),
     237                    '<code>Tested up to</code>',
     238                    '<code>' . number_format( $latest_wordpress_version, 1 ) . '</code>',
     239                    '<code>' . number_format( $latest_wordpress_version + 0.1, 1 ) . '</code>'
     240                );
     241            case 'tested_header_missing':
     242                return sprintf(
     243                    /* translators: %s: plugin header tag */
     244                    __( 'The %s field is missing.', 'wporg-plugins' ),
     245                    '<code>Tested up to</code>'
     246                );
     247            case 'requires_header_missing':
     248                return sprintf(
     249                    /* translators: %s: plugin header tag */
     250                    __( 'The %s field is missing. It should be defined here, or in your main plugin file.', 'wporg-plugins' ),
     251                    '<code>Requires at least</code>'
     252                );
     253            case 'requires_php_header_ignored':
     254                global $required_php_version; // WP wp-includes/version.php.
     255                return sprintf(
     256                    /* translators: 1: plugin header tag; 2: Example version 7.0. 3: Example version 8.2. */
     257                    __( 'The %1$s field was ignored. This field should only contain a PHP version such as %2$s or %3$s.', 'wporg-plugins' ),
     258                    '<code>Requires PHP</code>',
     259                    '<code>' . esc_html( $required_php_version ?? '7.0' ) . '</code>',
     260                    '<code>8.2</code>'
     261                );
     262            case 'requires_php_header_missing':
     263                return sprintf(
     264                    /* translators: %s: plugin header tag */
     265                    __( 'The %s field is missing. It should be defined here, or in your main plugin file.', 'wporg-plugins' ),
     266                    '<code>Requires PHP</code>'
     267                );
     268            case 'stable_tag_invalid':
     269                return sprintf(
     270                    /* translators: 1: 'Stable tag', 2: /trunk/ SVN directory */
     271                    __( 'The %1$s field is missing or invalid.  Note: We ask you no longer attempt to use %2$s as stable, so that all plugins can be rolled back.', 'wporg-plugins' ),
     272                    '<code>Stable tag</code>',
     273                    '<code>/trunk/</code>'
     274                );
     275            case 'contributor_ignored':
     276                if ( ! $data ) {
     277                    return sprintf(
     278                        /* translators: %s: plugin header tag */
     279                        __( 'One or more contributors listed were ignored. The %s field should only contain WordPress.org usernames.', 'wporg-plugins' ),
     280                        '<code>Contributors</code>'
     281                    );
     282                } else {
     283                    return sprintf(
     284                        /* translators: 1: List of authors from the readme 2: plugin header tag */
     285                        __( 'The following contributors listed were ignored, as the WordPress.org user could not be found. %1$s. The %2$s field should only contain WordPress.org usernames.', 'wporg-plugins' ),
     286                        '<code>' . implode( '</code>, <code>', array_map( 'esc_html', $data ) ) . '</code>',
     287                        '<code>Contributors</code>'
     288                    );
     289                }
     290            case 'contributors_missing':
     291                return sprintf(
     292                    /* translators: %s: plugin header tag */
     293                    __( 'The %s field is missing.', 'wporg-plugins' ),
     294                    '<code>Contributors</code>'
     295                );
     296            case 'too_many_tags':
     297                if ( $data ) {
     298                    return sprintf(
     299                        /* translators: %s: list of tags not supported */
     300                        __( 'One or more tags were ignored: %s. Please limit your plugin to 5 tags.', 'wporg-plugins' ),
     301                        '<code>' . implode( '</code>, <code>', array_map( 'esc_html', $data ) ) . '</code>'
     302                    );
     303                } else {
     304                    return __( 'One or more tags were ignored. Please limit your plugin to 5 tags.', 'wporg-plugins' );
     305                }
     306            case 'ignored_tags':
     307                return sprintf(
     308                    /* translators: %s: list of tags not supported */
     309                    __( 'One or more tags were ignored. The following tags are not permitted: %s', 'wporg-plugins' ),
     310                    '<code>' . implode( '</code>, <code>', $readme->ignore_tags ) . '</code>'
     311                );
     312            case 'low_usage_tags':
     313                return sprintf(
     314                    /* translators: %s: list of tags with low usage. */
     315                    __( 'The following tags are not widely used: %s', 'wporg-plugins' ),
     316                    '<code>' . implode( '</code>, <code>', array_map( 'esc_html', $data ) ) . '</code>'
     317                );
     318            case 'no_short_description_present':
     319                return sprintf(
     320                    /* translators: %s: section title */
     321                    __( 'The %s section is missing. An excerpt was generated from your main plugin description.', 'wporg-plugins' ),
     322                    '<code>Short Description</code>'
     323                );
     324            case 'trimmed_short_description':
     325                return sprintf(
     326                    /* translators: %s: section title */
     327                    __( 'The %s section is too long and was truncated. A maximum of %s characters is supported.', 'wporg-plugins' ),
     328                    '<code>Short Description</code>',
     329                    number_format_i18n( (new Parser)->maximum_field_lengths['short_description'] )
     330                );
     331            case 'trimmed_section_description':
     332            case 'trimmed_section_installation':
     333            case 'trimmed_section_faq':
     334            case 'trimmed_section_screenshots':
     335            case 'trimmed_section_changelog':
     336            case 'trimmed_section_upgrade_notice':
     337            case 'trimmed_section_other_notes':
     338                $readme       = new Parser;
     339                $section_name = str_replace( 'trimmed_section_', '', $error_code );
     340
     341                $max_length_field = "section-{$section_name}";
     342                if ( ! isset( $readme->maximum_field_lengths[ $max_length_field ] ) ) {
     343                    $max_length_field = 'section';
     344                }
     345   
     346                return sprintf(
     347                    /* translators: %s: section title */
     348                    __( 'The %s section is too long and was truncated. A maximum of %s words is supported.', 'wporg-plugins' ),
     349                    '<code>' . esc_html( ucwords( str_replace( '_', ' ', $section_name ) ) ) . '</code>',
     350                    number_format_i18n( $readme->maximum_field_lengths[ $max_length_field ] )
     351                );
     352            case 'faq_missing':
     353                return sprintf(
     354                    /* translators: %s: section title */
     355                    __( 'No %s section was found', 'wporg-plugins' ),
     356                    '<code>== Frequently Asked Questions ==</code>'
     357                );
     358            case 'changelog_missing':
     359                return sprintf(
     360                    /* translators: %s: section title */
     361                    __( 'No %s section was found', 'wporg-plugins' ),
     362                    '<code>== Changelog ==</code>'
     363                );
     364            case 'upgrade_notice_missing':
     365                return sprintf(
     366                    /* translators: %s: section title */
     367                    __( 'No %s section was found', 'wporg-plugins' ),
     368                    '<code>== Upgrade Notice ==</code>'
     369                );
     370            case 'screenshots_missing':
     371                return sprintf(
     372                    /* translators: %s: section title */
     373                    __( 'No %s section was found', 'wporg-plugins' ),
     374                    '<code>== Screenshots ==</code>'
     375                );
     376            case 'donate_link_missing':
     377                return __( 'No donate link was found', 'wporg-plugins' );
     378            case 'license_missing':
     379                return sprintf(
     380                    /* translators: 1: 'License' */
     381                    __( 'The %1$s field is missing or invalid. A GPLv2 or later compatible license should be specified.', 'wporg-plugins' ),
     382                    '<code>License</code>'
     383                );
     384
     385            /* The are not generated by the Readme Parser, but rather the import parser. */
     386            case 'invalid_update_uri':
     387                return sprintf(
     388                    /* translators: %s 'Update URI' */
     389                    __( 'The %s specified is invalid. This field should not be used with WordPress.org hosted plugins.', 'wporg-plugins' ),
     390                    '<code>Update URI</code>'
     391                );
     392            case 'unmet_dependencies':
     393                return sprintf(
     394                    /* translators: %s: list of plugin dependencies */
     395                    __( 'Invalid plugin dependencies specified. The following dependencies could not be resolved: %s', 'wporg-plugins' ),
     396                    '<code>' . implode( '</code>, <code>', array_map( 'esc_html', $data ) ) . '</code>'
     397                );
     398        }
     399
     400        return false;
     401    }
     402
    257403}
Note: See TracChangeset for help on using the changeset viewer.