WooCommerce 11.2.1: what Rateo prints

The Rateo engine, run on WooCommerce, an ecommerce plugin. Every number below comes from the engine's output, which is linked in full at the end.

License
GPL-3.0-or-later
Source
https://downloads.wordpress.org/plugin/woocommerce.11.2.1.zip
Engine
2026.10.12
Rules
2026.10.7-provisional
Ran on
2026-10-16
Zip SHA-256
5317434cb381b5fdc7c1dd6ee258324f08c390976aaf8d1199f2a0e9a3ab8586
Command
build/rateo woocommerce --cache=none
from the folder the zip was extracted into

At a glance

Files analysed
3,144
Findings
8,281
Errors
816
Warnings
7,465

platform PHP: WordPress (plugin header in woocommerce.php)

not analysed: 1725 files (JavaScript 508, CSS 419, binary 275, SVG 207, JSON 186, Text 57, SCSS 38, HTML 24, CSV 9, Gettext Catalog 1, XML 1)

Folders and files not analysed, by reason: Dependency 1.

What it checks

Checked today

  • Size: long files, classes and functions
  • Complexity: functions and file level code with too many paths
  • Naming: mixed styles in a file, generic, one letter and very long names
  • Duplication: blocks repeated inside a file, even with renamed variables
  • Security: eval, shell commands, unserialize and includes built from data, SQL with values pasted in, request input never sanitized, output never escaped

Not checked yet

  • WordPress rules: nonces and capability checks

The security checks read one call or statement at a time and never follow a value through variables, so they show where to look, not proof of a hole. A long or branchy function is common in a large, mature plugin; each finding is a place worth a closer look, not a judgement of the code as a whole.

Findings by check

CheckLimits (warning / error)WarningsErrorsTotal
Size
length.function
A function or method body with too many lines
body lines: above 50 / above 1001,2243981,622
length.class
A class with too many lines
lines: above 500 / above 1000 (other)19490284
length.file
A file with too many lines
lines: above 600 / above 1200 (other)17174245
Complexity
complexity.function
A function or method with too many paths through it
cyclomatic complexity: above 10 / above 208122361,048
complexity.file_code
Code outside any function with too many paths through it
cyclomatic complexity: above 10 / above 20591877
Duplication
duplication.repeated_block
A block repeated later in the same file, even with its names changed
tokens: 50 or more / never an error1,62601,626
Naming
naming.inconsistent_style
A name in a different style from most names of its kind in the file
style: differs from the file's main style (groups of 3 or more names) / never an error59059
naming.generic
A local variable or parameter with a name that says nothing, like $data or $tmp
name: on the list of 16 generic names / never an error3720372
naming.too_short
A one letter name other than i, j, k, n, x, y, z or e
characters: under 2 / never an error1760176
naming.too_long
A very long name
characters: above 30 / never an error2,62002,620

A value equal to a limit is no finding.

Where they concentrate

The 10 files with the most findings

FileFindings
woocommerce/i18n/locale-info.php237
woocommerce/assets/client/blocks/blocks-json.php183
woocommerce/includes/wc-template-functions.php130
woocommerce/includes/wc-update-functions.php114
woocommerce/includes/class-wc-ajax.php110
woocommerce/src/Internal/Admin/Settings/PaymentsProviders/WooPayments/WooPaymentsService.php72
woocommerce/includes/rest-api/Controllers/Version1/class-wc-rest-products-v1-controller.php58
woocommerce/includes/wc-product-functions.php55
woocommerce/src/Internal/Admin/Suggestions/PaymentsExtensionSuggestions.php55
woocommerce/includes/admin/helper/class-wc-helper.php53

Findings per top level folder

FolderFindings
src/3,851
includes/3,385
packages/430
i18n/240
assets/183
lib/114
templates/74
(plugin root)3
patterns/1

Grades

AOverall: 91 out of 100
3 of 5 metrics graded

round((3 × 93Complexity+2 × 85Duplication+1 × 95Naming) ÷ 6) = 91

Weights: Complexity 3, Duplication 2, Naming 1. Size and Security are not graded, so they are left out and the other weights are rescaled.

A summary of the grades below, which you can recompute. Not a pass mark, and not a hiring decision.

The table shows how many files got each letter. A file with nothing to judge for a metric gets no grade for it. The whole plugin column pools every file's grade, so a file with more code to judge counts for more. The overall above weights those whole plugin grades. In the full engine output these are CodeLength, CyclomaticComplexity, Duplication, NamingConvention and Security.

MetricABCDFNot gradedWhole plugin
SizeFindings only, no grade
Complexity2,527162173355719093 A
Duplication2,843671120116685 B
Naming2,31027535234245995 A
SecurityFindings only, no grade

Security

Findings only, never a grade: a wrong security grade would hurt more than none. A finding the author already silenced with a phpcs comment counts as suppressed, apart from Errors and Warnings, so you can tell reviewed code from unreviewed code.

CheckErrorsWarningsSuppressed
security.eval
A call to eval, which runs a string as PHP code
000
security.shell
A shell command built from data
000
security.unserialize
unserialize without allowed_classes => false
002
security.include_variable
An include or require whose path is built from a variable
0261
security.extract_input
extract of request input, which turns its fields into variables
000
security.sql_interpolated
SQL with a value pasted in instead of passed through prepare
036290
security.unescaped_output
An echo, print or <?= of a value that is not escaped
049462
security.unsanitized_input
A request input read with no sanitizer around it
041208

Where to look first

Up to 10 places, Errors first, then Warnings, then by path and line. An Error means request input reaches the call or the output directly.

#1 woocommerce/includes/admin/class-wc-admin-api-keys-table-list.php:259

  • Warning security.sql_interpolated

    query `$wpdb->get_results` builds its SQL from `$search`; pass values through prepare with placeholders to prevent SQL injection

259 $keys = $wpdb->get_results(
260 "SELECT key_id, user_id, description, permissions, truncated_key, last_access FROM {$wpdb->prefix}woocommerce_api_keys WHERE 1 = 1 {$search}" .
261 $wpdb->prepare( 'ORDER BY key_id DESC LIMIT %d OFFSET %d;', $per_page, $offset ),
262 ARRAY_A
263 ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- The search fragment is escaped, pagination values are prepared, and the table name is trusted.
264
265 $count = $wpdb->get_var( "SELECT COUNT(key_id) FROM {$wpdb->prefix}woocommerce_api_keys WHERE 1 = 1 {$search};" ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- The search fragment is escaped and the table name is trusted.
266
267 $this->items = $keys;
268

#2 woocommerce/includes/admin/class-wc-admin-dashboard.php:471

  • Warning security.unescaped_output

    echo of `get_avatar( $comment->comment_author_email, '32' )` is not escaped at output; escape where you print (esc_html, esc_attr, htmlspecialchars)

471 echo get_avatar( $comment->comment_author_email, '32' );
472
473 /**
474 * Filters the product name for display in the latest reviews.
475 *
476 * @param string $product_title The product name.
477 * @param \stdClass $comment The comment.
478 * @since 2.1.0
479 */
480 $product_title = apply_filters( 'woocommerce_admin_dashboard_recent_reviews', $comment->post_title, $comment );

#3 woocommerce/includes/admin/class-wc-admin-importers.php:157

  • Warning security.include_variable

    require of `$class_wp_importer` builds the file path from data; a crafted path can load another file

157 require $class_wp_importer;
158 }
159 }
160
161 wc_admin_record_tracks_event( 'tax_rates_importer_view_from_wp_importer' );
162
163 require __DIR__ . '/importers/class-wc-tax-rate-importer.php';
164
165 $importer = new WC_Tax_Rate_Importer();
166 $importer->dispatch();

#4 woocommerce/includes/admin/class-wc-admin-log-table-list.php:73

  • Warning security.unsanitized_input

    read of `$_REQUEST['level']` with no sanitizer around it; sanitize request input where you read it

73 $selected_level = isset( $_REQUEST['level'] ) ? $_REQUEST['level'] : '';
74 ?>
75 <label for="filter-by-level" class="screen-reader-text"><?php esc_html_e( 'Filter by level', 'woocommerce' ); ?></label>
76 <select name="level" id="filter-by-level">
77 <option<?php selected( $selected_level, '' ); ?> value=""><?php esc_html_e( 'All levels', 'woocommerce' ); ?></option>
78 <?php
79 foreach ( $levels as $l ) {
80 printf(
81 '<option%1$s value="%2$s">%3$s</option>',
82 selected( $selected_level, $l['value'], false ),

#5 woocommerce/includes/admin/class-wc-admin-log-table-list.php:287

  • Warning security.unsanitized_input

    read of `$_REQUEST['source']` with no sanitizer around it; sanitize request input where you read it

287 $selected_source = isset( $_REQUEST['source'] ) ? $_REQUEST['source'] : '';
288 ?>
289 <label for="filter-by-source" class="screen-reader-text"><?php esc_html_e( 'Filter by source', 'woocommerce' ); ?></label>
290 <select name="source" id="filter-by-source">
291 <option<?php selected( $selected_source, '' ); ?> value=""><?php esc_html_e( 'All sources', 'woocommerce' ); ?></option>
292 <?php
293 foreach ( $sources as $s ) {
294 printf(
295 '<option%1$s value="%2$s">%3$s</option>',
296 selected( $selected_source, $s, false ),

#6 woocommerce/includes/admin/class-wc-admin-log-table-list.php:480

  • Warning security.unsanitized_input

    read of `$_REQUEST['order']` with no sanitizer around it; sanitize request input where you read it

480 if ( ! empty( $_REQUEST['order'] ) && 'asc' === strtolower( $_REQUEST['order'] ) ) {
481 $order = 'ASC';
482 } else {
483 $order = 'DESC';
484 }
485
486 $orderby = "ORDER BY {$by} {$order}";
487 if ( 'log_id' !== $by ) {
488 $orderby .= ", log_id {$order}";
489 }

#7 woocommerce/includes/admin/class-wc-admin-log-table-list.php:506

  • Warning security.unsanitized_input

    read of `$_REQUEST['level']` with no sanitizer around it; sanitize request input where you read it

506 if ( ! empty( $_REQUEST['level'] ) && WC_Log_Levels::is_valid_level( $_REQUEST['level'] ) ) {
507 $where_conditions[] = 'level >= %d';
508 $where_values[] = WC_Log_Levels::get_level_severity( $_REQUEST['level'] );
509 }
510 if ( ! empty( $_REQUEST['source'] ) ) {
511 $where_conditions[] = 'source = %s';
512 $where_values[] = wc_clean( $_REQUEST['source'] );
513 }
514 if ( ! empty( $_REQUEST['s'] ) ) {
515 $where_conditions[] = 'message like %s';

#8 woocommerce/includes/admin/class-wc-admin-log-table-list.php:508

  • Warning security.unsanitized_input

    read of `$_REQUEST['level']` with no sanitizer around it; sanitize request input where you read it

508 $where_values[] = WC_Log_Levels::get_level_severity( $_REQUEST['level'] );
509 }
510 if ( ! empty( $_REQUEST['source'] ) ) {
511 $where_conditions[] = 'source = %s';
512 $where_values[] = wc_clean( $_REQUEST['source'] );
513 }
514 if ( ! empty( $_REQUEST['s'] ) ) {
515 $where_conditions[] = 'message like %s';
516 $where_values[] = '%' . $wpdb->esc_like( wc_clean( wp_unslash( $_REQUEST['s'] ) ) ) . '%';
517 }

#9 woocommerce/includes/admin/class-wc-admin-log-table-list.php:523

  • Warning security.sql_interpolated

    query `$wpdb->prepare` builds its SQL from `implode( ' AND ', $where_conditions )`; pass values through prepare with placeholders to prevent SQL injection

523 return $wpdb->prepare( 'WHERE 1 = 1 AND ' . implode( ' AND ', $where_conditions ), $where_values );
524 }
525
526 /**
527 * Set _column_headers property for table list
528 */
529 protected function prepare_column_headers() {
530 $this->_column_headers = array(
531 $this->get_columns(),
532 array(),

#10 woocommerce/includes/admin/class-wc-admin-profile.php:170

  • Warning security.unescaped_output

    echo of `$fieldset['title']` is not escaped at output; escape where you print (esc_html, esc_attr, htmlspecialchars)

170 <h2><?php echo $fieldset['title']; ?></h2>
171 <table class="form-table" id="<?php echo esc_attr( 'fieldset-' . $fieldset_key ); ?>">
172 <?php foreach ( $fieldset['fields'] as $key => $field ) : ?>
173 <tr>
174 <th>
175 <label for="<?php echo esc_attr( $key ); ?>"><?php echo esc_html( $field['label'] ); ?></label>
176 </th>
177 <td>
178 <?php if ( ! empty( $field['type'] ) && 'select' === $field['type'] ) : ?>
179 <select name="<?php echo esc_attr( $key ); ?>" id="<?php echo esc_attr( $key ); ?>" class="<?php echo isset( $field['class'] ) ? esc_attr( $field['class'] ) : ''; ?>" style="width: 25em;">

The 10 worst places

Security findings are listed in their own section above. A place is a file and a line, so a function that is both too long and too complex shows once, with both findings. Places rank by their worst finding: errors first, then by how far the measured value goes beyond its limit. Each message is the engine's own, word for word.

#1 woocommerce/src/Internal/Admin/Suggestions/PaymentsExtensionSuggestions.php:3267

  • Error length.function

    PaymentsExtensionSuggestions::get_all_extensions_base_details has 1166 lines in its body, above 100

  • Warning naming.too_long

    method `get_all_extensions_base_details` is 31 characters (limit 30); very long names make code hard to read

3267 private function get_all_extensions_base_details(): array {
3268 if ( isset( $this->extensions_base_details_memo ) ) {
3269 return $this->extensions_base_details_memo;
3270 }
3271 $this->extensions_base_details_memo = array(
3272 self::AIRWALLEX => array(
3273 '_type' => self::TYPE_PSP,
3274 'title' => esc_html__( 'Airwallex Payments', 'woocommerce' ),
3275 'description' => esc_html__( 'Boost international sales and save on FX fees. Accept 60+ local payment methods including Apple Pay and Google Pay.', 'woocommerce' ),
3276 'image' => plugins_url( 'assets/images/onboarding/airwallex.png', WC_PLUGIN_FILE ),

#2 woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-orders-v2-controller.php:1211

  • Error length.function

    WC_REST_Orders_V2_Controller::get_item_schema has 931 lines in its body, above 100

1211 public function get_item_schema() {
1212 $schema = array(
1213 '$schema' => 'http://json-schema.org/draft-04/schema#',
1214 'title' => $this->post_type,
1215 'type' => 'object',
1216 'properties' => array(
1217 'id' => array(
1218 'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),
1219 'type' => 'integer',
1220 'context' => array( 'view', 'edit' ),

#3 woocommerce/src/Admin/Features/PaymentGatewaySuggestions/DefaultPaymentGateways.php:54

  • Error length.function

    DefaultPaymentGateways::get_all has 903 lines in its body, above 100

54 public static function get_all() {
55 $payment_gateways = array(
56 array(
57 'id' => 'affirm',
58 'title' => __( 'Affirm', 'woocommerce' ),
59 'content' => __( 'Affirm’s tailored Buy Now Pay Later programs remove price as a barrier, turning browsers into buyers, increasing average order value, and expanding your customer base.', 'woocommerce' ),
60 'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/payment_methods/72x72/affirm.png',
61 'image_72x72' => WC_ADMIN_IMAGES_FOLDER_URL . '/payment_methods/72x72/affirm.png',
62 'plugins' => array(),
63 'external_link' => 'https://woocommerce.com/products/woocommerce-gateway-affirm',

#4 woocommerce/includes/class-wc-countries.php:1055

  • Error length.function

    WC_Countries::get_country_locale has 898 lines in its body, above 100

  • Warning complexity.function

    WC_Countries::get_country_locale has a cyclomatic complexity of 11, moderate risk (above 10): each of its 11 paths needs its own test, which makes it hard to follow and to change safely

1055 public function get_country_locale() {
1056 if ( ! empty( $this->locale ) && null === $this->country_locale_built_for ) {
1057 return $this->locale;
1058 }
1059
1060 $cache_locale = $this->get_cache_locale();
1061
1062 if ( empty( $this->locale ) || ( null !== $this->country_locale_built_for && $cache_locale !== $this->country_locale_built_for ) ) {
1063 // The settings are narrowed to the country list, so a lookup made while either is being built gets the previous settings.
1064 if ( $this->country_locale_building || isset( $this->geo_cache_building['countries'][ $cache_locale ] ) ) {

#5 woocommerce/includes/rest-api/Controllers/Version1/class-wc-rest-products-v1-controller.php:1745

  • Error length.function

    WC_REST_Products_V1_Controller::get_item_schema has 843 lines in its body, above 100

1745 public function get_item_schema() {
1746 $weight_unit_label = I18nUtil::get_weight_unit_label( get_option( 'woocommerce_weight_unit', 'kg' ) );
1747 $dimension_unit_label = I18nUtil::get_dimensions_unit_label( get_option( 'woocommerce_dimension_unit', 'cm' ) );
1748 $schema = array(
1749 '$schema' => 'http://json-schema.org/draft-04/schema#',
1750 'title' => $this->post_type,
1751 'type' => 'object',
1752 'properties' => array(
1753 'id' => array(
1754 'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),

#6 woocommerce/src/Internal/RestApi/Routes/V4/Products/Controller.php:1444

  • Error length.function

    Controller::get_item_schema has 724 lines in its body, above 100

1444 public function get_item_schema() {
1445 $weight_unit_label = I18nUtil::get_weight_unit_label( get_option( 'woocommerce_weight_unit', WeightUnit::KILOGRAM ) );
1446 $dimension_unit_label = I18nUtil::get_dimensions_unit_label( get_option( 'woocommerce_dimension_unit', DimensionUnit::CENTIMETER ) );
1447 $schema = array(
1448 '$schema' => 'http://json-schema.org/draft-04/schema#',
1449 'title' => $this->post_type,
1450 'type' => 'object',
1451 'properties' => array(
1452 'id' => array(
1453 'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),

#7 woocommerce/assets/client/blocks/blocks-json.php:1

  • Error length.file

    the file has 8679 lines, above 1200 for a other

1<?php
2// This file is generated. Do not modify it manually.
3return array(
4 'accordion-group' => array(
5 '$schema' => 'https://schemas.wp.org/trunk/block.json',
6 'apiVersion' => 3,
7 'name' => 'woocommerce/accordion-group',
8 'title' => 'Accordion Group',
9 'category' => 'woocommerce',
10 'keywords' => array(

#8 woocommerce/includes/rest-api/Controllers/Version3/class-wc-rest-products-controller.php:1174

  • Error length.function

    WC_REST_Products_Controller::get_item_schema has 679 lines in its body, above 100

1174 public function get_item_schema() {
1175 $weight_unit_label = I18nUtil::get_weight_unit_label( get_option( 'woocommerce_weight_unit', 'kg' ) );
1176 $dimension_unit_label = I18nUtil::get_dimensions_unit_label( get_option( 'woocommerce_dimension_unit', 'cm' ) );
1177 $schema = array(
1178 '$schema' => 'http://json-schema.org/draft-04/schema#',
1179 'title' => $this->post_type,
1180 'type' => 'object',
1181 'properties' => array(
1182 'id' => array(
1183 'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),

#9 woocommerce/includes/rest-api/Controllers/Version1/class-wc-rest-orders-v1-controller.php:1024

  • Error length.function

    WC_REST_Orders_V1_Controller::get_item_schema has 678 lines in its body, above 100

1024 public function get_item_schema() {
1025 $schema = array(
1026 '$schema' => 'http://json-schema.org/draft-04/schema#',
1027 'title' => $this->post_type,
1028 'type' => 'object',
1029 'properties' => array(
1030 'id' => array(
1031 'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),
1032 'type' => 'integer',
1033 'context' => array( 'view', 'edit' ),

#10 woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-system-status-v2-controller.php:130

  • Error length.function

    WC_REST_System_Status_V2_Controller::get_item_schema has 673 lines in its body, above 100

130 public function get_item_schema() {
131 $schema = array(
132 '$schema' => 'http://json-schema.org/draft-04/schema#',
133 'title' => 'system_status',
134 'type' => 'object',
135 'properties' => array(
136 'environment' => array(
137 'description' => __( 'Environment.', 'woocommerce' ),
138 'type' => 'object',
139 'context' => array( 'view' ),

Full engine output

All 8,281 findings, plus 963 suppressed by a phpcs comment, exactly as the engine printed them: wordpress-woocommerce.txt (plain text).