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
A value equal to a limit is no finding.
Where they concentrate
The 10 files with the most findings
Findings per top level folder
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.
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.
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
| 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
| 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
| 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
| 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
| 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
| 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
| 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
| 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
| 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
| 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
| 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
| 1 | <?php |
| 2 | // This file is generated. Do not modify it manually. |
| 3 | return 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
| 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
| 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
| 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).