mirror of
https://github.com/10h30/genesis-simple-sidebars.git
synced 2026-07-13 19:46:09 +09:00
2697 lines
88 KiB
PHP
2697 lines
88 KiB
PHP
<?php
|
|
/**
|
|
* Represents a PHP_CodeSniffer sniff for sniffing WordPress coding standards.
|
|
*
|
|
* @package WPCS\WordPressCodingStandards
|
|
* @link https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards
|
|
* @license https://opensource.org/licenses/MIT MIT
|
|
*/
|
|
|
|
namespace WordPress;
|
|
|
|
use PHP_CodeSniffer_Sniff as PHPCS_Sniff;
|
|
use PHP_CodeSniffer_File as File;
|
|
use PHP_CodeSniffer_Tokens as Tokens;
|
|
use WordPress\PHPCSHelper;
|
|
|
|
/**
|
|
* Represents a PHP_CodeSniffer sniff for sniffing WordPress coding standards.
|
|
*
|
|
* Provides a bootstrap for the sniffs, to reduce code duplication.
|
|
*
|
|
* @package WPCS\WordPressCodingStandards
|
|
* @since 0.4.0
|
|
*
|
|
* {@internal This class contains numerous properties where the array format looks
|
|
* like `'string' => true`, i.e. the array item is set as the array key.
|
|
* This allows for sniffs to verify whether something is in one of these
|
|
* lists using `isset()` rather than `in_array()` which is a much more
|
|
* efficient (faster) check to execute and therefore improves the
|
|
* performance of the sniffs.
|
|
* The `true` value in those cases is used as a placeholder and has no
|
|
* meaning in and of itself.
|
|
* In the rare few cases where the array values *do* have meaning, this
|
|
* is documented in the property documentation.}}
|
|
*/
|
|
abstract class Sniff implements PHPCS_Sniff {
|
|
|
|
/**
|
|
* Regex to get complex variables from T_DOUBLE_QUOTED_STRING or T_HEREDOC.
|
|
*
|
|
* @since 0.14.0
|
|
*
|
|
* @var string
|
|
*/
|
|
const REGEX_COMPLEX_VARS = '`(?:(\{)?(?<!\\\\)\$)?(\{)?(?<!\\\\)\$(\{)?(?P<varname>[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)(?:->\$?(?P>varname)|\[[^\]]+\]|::\$?(?P>varname)|\([^\)]*\))*(?(3)\}|)(?(2)\}|)(?(1)\}|)`';
|
|
|
|
/**
|
|
* Minimum supported WordPress version.
|
|
*
|
|
* Currently used by the `WordPress.WP.AlternativeFunctions`,
|
|
* `WordPress.WP.DeprecatedClasses`, `WordPress.WP.DeprecatedFunctions`
|
|
* and the `WordPress.WP.DeprecatedParameter` sniff.
|
|
*
|
|
* These sniffs will throw an error when usage of a deprecated class/function/parameter
|
|
* is detected if the class/function/parameter was deprecated before the minimum
|
|
* supported WP version; a warning otherwise.
|
|
* By default, it is set to presume that a project will support the current
|
|
* WP version and up to three releases before.
|
|
*
|
|
* This property allows changing the minimum supported WP version used by
|
|
* these sniffs by setting a property in a custom phpcs.xml ruleset.
|
|
* This property will need to be set for each sniff which uses it.
|
|
*
|
|
* Example usage:
|
|
* <rule ref="WordPress.WP.DeprecatedClasses">
|
|
* <properties>
|
|
* <property name="minimum_supported_version" value="4.3"/>
|
|
* </properties>
|
|
* </rule>
|
|
*
|
|
* Alternatively, the value can be passed in one go for all sniff using it via
|
|
* the command line or by setting a `<config>` value in a custom phpcs.xml ruleset.
|
|
* Note: the `_wp_` in the command line property name!
|
|
*
|
|
* CL: `phpcs --runtime-set minimum_supported_wp_version 4.5`
|
|
* Ruleset: `<config name="minimum_supported_wp_version" value="4.5"/>`
|
|
*
|
|
* @since 0.14.0 Previously the individual sniffs each contained this property.
|
|
*
|
|
* @internal When the value of this property is changed, it will also need
|
|
* to be changed in the `WP/AlternativeFunctionsUnitTest.inc` file.
|
|
*
|
|
* @var string WordPress version.
|
|
*/
|
|
public $minimum_supported_version = '4.7';
|
|
|
|
/**
|
|
* Custom list of classes which test classes can extend.
|
|
*
|
|
* This property allows end-users to add to the $test_class_whitelist via their ruleset.
|
|
* This property will need to be set for each sniff which uses the
|
|
* `is_test_class()` method.
|
|
* Currently the method is used by the `WordPress.WP.GlobalVariablesOverride`,
|
|
* `WordPress.NamingConventions.PrefixAllGlobals` and the `WordPress.Files.Filename` sniffs.
|
|
*
|
|
* Example usage:
|
|
* <rule ref="WordPress.[Subset].[Sniffname]">
|
|
* <properties>
|
|
* <property name="custom_test_class_whitelist" type="array" value="My_Plugin_First_Test_Class,My_Plugin_Second_Test_Class"/>
|
|
* </properties>
|
|
* </rule>
|
|
*
|
|
* @since 0.11.0
|
|
*
|
|
* @var string|string[]
|
|
*/
|
|
public $custom_test_class_whitelist = array();
|
|
|
|
/**
|
|
* List of the functions which verify nonces.
|
|
*
|
|
* @since 0.5.0
|
|
* @since 0.11.0 Changed from public static to protected non-static.
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $nonceVerificationFunctions = array(
|
|
'wp_verify_nonce' => true,
|
|
'check_admin_referer' => true,
|
|
'check_ajax_referer' => true,
|
|
);
|
|
|
|
/**
|
|
* Functions that escape values for display.
|
|
*
|
|
* @since 0.5.0
|
|
* @since 0.11.0 Changed from public static to protected non-static.
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $escapingFunctions = array(
|
|
'absint' => true,
|
|
'esc_attr__' => true,
|
|
'esc_attr_e' => true,
|
|
'esc_attr_x' => true,
|
|
'esc_attr' => true,
|
|
'esc_html__' => true,
|
|
'esc_html_e' => true,
|
|
'esc_html_x' => true,
|
|
'esc_html' => true,
|
|
'esc_js' => true,
|
|
'esc_sql' => true,
|
|
'esc_textarea' => true,
|
|
'esc_url_raw' => true,
|
|
'esc_url' => true,
|
|
'filter_input' => true,
|
|
'filter_var' => true,
|
|
'floatval' => true,
|
|
'intval' => true,
|
|
'json_encode' => true,
|
|
'like_escape' => true,
|
|
'number_format' => true,
|
|
'rawurlencode' => true,
|
|
'sanitize_html_class' => true,
|
|
'sanitize_user_field' => true,
|
|
'tag_escape' => true,
|
|
'urlencode_deep' => true,
|
|
'urlencode' => true,
|
|
'wp_json_encode' => true,
|
|
'wp_kses_allowed_html' => true,
|
|
'wp_kses_data' => true,
|
|
'wp_kses_post' => true,
|
|
'wp_kses' => true,
|
|
);
|
|
|
|
/**
|
|
* Functions whose output is automatically escaped for display.
|
|
*
|
|
* @since 0.5.0
|
|
* @since 0.11.0 Changed from public static to protected non-static.
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $autoEscapedFunctions = array(
|
|
'allowed_tags' => true,
|
|
'bloginfo' => true,
|
|
'body_class' => true,
|
|
'calendar_week_mod' => true,
|
|
'category_description' => true,
|
|
'checked' => true,
|
|
'comment_class' => true,
|
|
'count' => true,
|
|
'disabled' => true,
|
|
'do_shortcode' => true,
|
|
'do_shortcode_tag' => true,
|
|
'get_archives_link' => true,
|
|
'get_attachment_link' => true,
|
|
'get_avatar' => true,
|
|
'get_bookmark_field' => true,
|
|
'get_calendar' => true,
|
|
'get_comment_author_link' => true,
|
|
'get_current_blog_id' => true,
|
|
'get_delete_post_link' => true,
|
|
'get_search_form' => true,
|
|
'get_search_query' => true,
|
|
'get_the_author_link' => true,
|
|
'get_the_author' => true,
|
|
'get_the_date' => true,
|
|
'get_the_ID' => true,
|
|
'get_the_post_thumbnail' => true,
|
|
'get_the_term_list' => true,
|
|
'paginate_comments_links' => true,
|
|
'post_type_archive_title' => true,
|
|
'readonly' => true,
|
|
'selected' => true,
|
|
'single_cat_title' => true,
|
|
'single_month_title' => true,
|
|
'single_post_title' => true,
|
|
'single_tag_title' => true,
|
|
'single_term_title' => true,
|
|
'tag_description' => true,
|
|
'term_description' => true,
|
|
'the_author' => true,
|
|
'the_date' => true,
|
|
'the_title_attribute' => true,
|
|
'vip_powered_wpcom' => true,
|
|
'walk_nav_menu_tree' => true,
|
|
'wp_dropdown_categories' => true,
|
|
'wp_dropdown_users' => true,
|
|
'wp_generate_tag_cloud' => true,
|
|
'wp_get_archives' => true,
|
|
'wp_get_attachment_image' => true,
|
|
'wp_get_attachment_link' => true,
|
|
'wp_link_pages' => true,
|
|
'wp_list_authors' => true,
|
|
'wp_list_bookmarks' => true,
|
|
'wp_list_categories' => true,
|
|
'wp_list_comments' => true,
|
|
'wp_login_form' => true,
|
|
'wp_loginout' => true,
|
|
'wp_nav_menu' => true,
|
|
'wp_register' => true,
|
|
'wp_tag_cloud' => true,
|
|
'wp_title' => true,
|
|
);
|
|
|
|
/**
|
|
* Functions that sanitize values.
|
|
*
|
|
* This list is complementary to the `$unslashingSanitizingFunctions`
|
|
* list.
|
|
* Sanitizing functions should be added to this list if they do *not*
|
|
* implicitely unslash data and to the `$unslashingsanitizingFunctions`
|
|
* list if they do.
|
|
*
|
|
* @since 0.5.0
|
|
* @since 0.11.0 Changed from public static to protected non-static.
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $sanitizingFunctions = array(
|
|
'_wp_handle_upload' => true,
|
|
'array_key_exists' => true,
|
|
'esc_url_raw' => true,
|
|
'filter_input' => true,
|
|
'filter_var' => true,
|
|
'hash_equals' => true,
|
|
'in_array' => true,
|
|
'is_email' => true,
|
|
'number_format' => true,
|
|
'sanitize_bookmark_field' => true,
|
|
'sanitize_bookmark' => true,
|
|
'sanitize_email' => true,
|
|
'sanitize_file_name' => true,
|
|
'sanitize_hex_color_no_hash' => true,
|
|
'sanitize_hex_color' => true,
|
|
'sanitize_html_class' => true,
|
|
'sanitize_meta' => true,
|
|
'sanitize_mime_type' => true,
|
|
'sanitize_option' => true,
|
|
'sanitize_sql_orderby' => true,
|
|
'sanitize_term_field' => true,
|
|
'sanitize_term' => true,
|
|
'sanitize_text_field' => true,
|
|
'sanitize_textarea_field' => true,
|
|
'sanitize_title_for_query' => true,
|
|
'sanitize_title_with_dashes' => true,
|
|
'sanitize_title' => true,
|
|
'sanitize_user_field' => true,
|
|
'sanitize_user' => true,
|
|
'validate_file' => true,
|
|
'wp_handle_sideload' => true,
|
|
'wp_handle_upload' => true,
|
|
'wp_kses_allowed_html' => true,
|
|
'wp_kses_data' => true,
|
|
'wp_kses_post' => true,
|
|
'wp_kses' => true,
|
|
'wp_parse_id_list' => true,
|
|
'wp_redirect' => true,
|
|
'wp_safe_redirect' => true,
|
|
'wp_strip_all_tags' => true,
|
|
);
|
|
|
|
/**
|
|
* Sanitizing functions that implicitly unslash the data passed to them.
|
|
*
|
|
* This list is complementary to the `$sanitizingFunctions` list.
|
|
* Sanitizing functions should be added to this list if they also
|
|
* implicitely unslash data and to the `$sanitizingFunctions` list
|
|
* if they don't.
|
|
*
|
|
* @since 0.5.0
|
|
* @since 0.11.0 Changed from public static to protected non-static.
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $unslashingSanitizingFunctions = array(
|
|
'absint' => true,
|
|
'boolval' => true,
|
|
'floatval' => true,
|
|
'intval' => true,
|
|
'is_array' => true,
|
|
'sanitize_key' => true,
|
|
);
|
|
|
|
/**
|
|
* Token which when they preceed code indicate the value is safely casted.
|
|
*
|
|
* @since 1.1.0
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $safe_casts = array(
|
|
\T_INT_CAST => true,
|
|
\T_DOUBLE_CAST => true,
|
|
\T_BOOL_CAST => true,
|
|
);
|
|
|
|
/**
|
|
* Functions that format strings.
|
|
*
|
|
* These functions are often used for formatting values just before output, and
|
|
* it is common practice to escape the individual parameters passed to them as
|
|
* needed instead of escaping the entire result. This is especially true when the
|
|
* string being formatted contains HTML, which makes escaping the full result
|
|
* more difficult.
|
|
*
|
|
* @since 0.5.0
|
|
* @since 0.11.0 Changed from public static to protected non-static.
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $formattingFunctions = array(
|
|
'array_fill' => true,
|
|
'ent2ncr' => true,
|
|
'implode' => true,
|
|
'join' => true,
|
|
'nl2br' => true,
|
|
'sprintf' => true,
|
|
'vsprintf' => true,
|
|
'wp_sprintf' => true,
|
|
);
|
|
|
|
/**
|
|
* Functions which print output incorporating the values passed to them.
|
|
*
|
|
* @since 0.5.0
|
|
* @since 0.11.0 Changed from public static to protected non-static.
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $printingFunctions = array(
|
|
'_deprecated_argument' => true,
|
|
'_deprecated_constructor' => true,
|
|
'_deprecated_file' => true,
|
|
'_deprecated_function' => true,
|
|
'_deprecated_hook' => true,
|
|
'_doing_it_wrong' => true,
|
|
'_e' => true,
|
|
'_ex' => true,
|
|
'printf' => true,
|
|
'trigger_error' => true,
|
|
'user_error' => true,
|
|
'vprintf' => true,
|
|
'wp_die' => true,
|
|
'wp_dropdown_pages' => true,
|
|
);
|
|
|
|
/**
|
|
* Functions that escape values for use in SQL queries.
|
|
*
|
|
* @since 0.9.0
|
|
* @since 0.11.0 Changed from public static to protected non-static.
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $SQLEscapingFunctions = array(
|
|
'absint' => true,
|
|
'esc_sql' => true,
|
|
'floatval' => true,
|
|
'intval' => true,
|
|
'like_escape' => true,
|
|
);
|
|
|
|
/**
|
|
* Functions whose output is automatically escaped for use in SQL queries.
|
|
*
|
|
* @since 0.9.0
|
|
* @since 0.11.0 Changed from public static to protected non-static.
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $SQLAutoEscapedFunctions = array(
|
|
'count' => true,
|
|
);
|
|
|
|
/**
|
|
* A list of functions that get data from the cache.
|
|
*
|
|
* @since 0.6.0
|
|
* @since 0.11.0 Changed from public static to protected non-static.
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $cacheGetFunctions = array(
|
|
'wp_cache_get' => true,
|
|
);
|
|
|
|
/**
|
|
* A list of functions that set data in the cache.
|
|
*
|
|
* @since 0.6.0
|
|
* @since 0.11.0 Changed from public static to protected non-static.
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $cacheSetFunctions = array(
|
|
'wp_cache_set' => true,
|
|
'wp_cache_add' => true,
|
|
);
|
|
|
|
/**
|
|
* A list of functions that delete data from the cache.
|
|
*
|
|
* @since 0.6.0
|
|
* @since 0.11.0 Changed from public static to protected non-static.
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $cacheDeleteFunctions = array(
|
|
'wp_cache_delete' => true,
|
|
'clean_attachment_cache' => true,
|
|
'clean_blog_cache' => true,
|
|
'clean_bookmark_cache' => true,
|
|
'clean_category_cache' => true,
|
|
'clean_comment_cache' => true,
|
|
'clean_network_cache' => true,
|
|
'clean_object_term_cache' => true,
|
|
'clean_page_cache' => true,
|
|
'clean_post_cache' => true,
|
|
'clean_term_cache' => true,
|
|
'clean_user_cache' => true,
|
|
);
|
|
|
|
/**
|
|
* A list of functions that invoke WP hooks (filters/actions).
|
|
*
|
|
* @since 0.10.0
|
|
* @since 0.11.0 Changed from public static to protected non-static.
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $hookInvokeFunctions = array(
|
|
'do_action' => true,
|
|
'do_action_ref_array' => true,
|
|
'do_action_deprecated' => true,
|
|
'apply_filters' => true,
|
|
'apply_filters_ref_array' => true,
|
|
'apply_filters_deprecated' => true,
|
|
);
|
|
|
|
/**
|
|
* A list of functions that are used to interact with the WP plugins API.
|
|
*
|
|
* @since 0.10.0
|
|
* @since 0.11.0 Changed from public static to protected non-static.
|
|
*
|
|
* @var array <string function name> => <int position of the hook name argument in function signature>
|
|
*/
|
|
protected $hookFunctions = array(
|
|
'has_filter' => 1,
|
|
'add_filter' => 1,
|
|
'remove_filter' => 1,
|
|
'remove_all_filters' => 1,
|
|
'doing_filter' => 1, // Hook name optional.
|
|
'has_action' => 1,
|
|
'add_action' => 1,
|
|
'doing_action' => 1, // Hook name optional.
|
|
'did_action' => 1,
|
|
'remove_action' => 1,
|
|
'remove_all_actions' => 1,
|
|
'current_filter' => 0, // No hook name argument.
|
|
);
|
|
|
|
/**
|
|
* List of global WP variables.
|
|
*
|
|
* @since 0.3.0
|
|
* @since 0.11.0 Changed visibility from public to protected.
|
|
* @since 0.12.0 Renamed from `$globals` to `$wp_globals` to be more descriptive.
|
|
* @since 0.12.0 Moved from WordPress_Sniffs_Variables_GlobalVariablesSniff to WordPress_Sniff
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $wp_globals = array(
|
|
'_links_add_base' => true,
|
|
'_links_add_target' => true,
|
|
'_menu_item_sort_prop' => true,
|
|
'_nav_menu_placeholder' => true,
|
|
'_new_bundled_files' => true,
|
|
'_old_files' => true,
|
|
'_parent_pages' => true,
|
|
'_registered_pages' => true,
|
|
'_updated_user_settings' => true,
|
|
'_wp_additional_image_sizes' => true,
|
|
'_wp_admin_css_colors' => true,
|
|
'_wp_default_headers' => true,
|
|
'_wp_deprecated_widgets_callbacks' => true,
|
|
'_wp_last_object_menu' => true,
|
|
'_wp_last_utility_menu' => true,
|
|
'_wp_menu_nopriv' => true,
|
|
'_wp_nav_menu_max_depth' => true,
|
|
'_wp_post_type_features' => true,
|
|
'_wp_real_parent_file' => true,
|
|
'_wp_registered_nav_menus' => true,
|
|
'_wp_sidebars_widgets' => true,
|
|
'_wp_submenu_nopriv' => true,
|
|
'_wp_suspend_cache_invalidation' => true,
|
|
'_wp_theme_features' => true,
|
|
'_wp_using_ext_object_cache' => true,
|
|
'action' => true,
|
|
'active_signup' => true,
|
|
'admin_body_class' => true,
|
|
'admin_page_hooks' => true,
|
|
'all_links' => true,
|
|
'allowedentitynames' => true,
|
|
'allowedposttags' => true,
|
|
'allowedtags' => true,
|
|
'auth_secure_cookie' => true,
|
|
'authordata' => true,
|
|
'avail_post_mime_types' => true,
|
|
'avail_post_stati' => true,
|
|
'blog_id' => true,
|
|
'blog_title' => true,
|
|
'blogname' => true,
|
|
'cat' => true,
|
|
'cat_id' => true,
|
|
'charset_collate' => true,
|
|
'comment' => true,
|
|
'comment_alt' => true,
|
|
'comment_depth' => true,
|
|
'comment_status' => true,
|
|
'comment_thread_alt' => true,
|
|
'comment_type' => true,
|
|
'comments' => true,
|
|
'compress_css' => true,
|
|
'compress_scripts' => true,
|
|
'concatenate_scripts' => true,
|
|
'current_screen' => true,
|
|
'current_site' => true,
|
|
'current_user' => true,
|
|
'currentcat' => true,
|
|
'currentday' => true,
|
|
'currentmonth' => true,
|
|
'custom_background' => true,
|
|
'custom_image_header' => true,
|
|
'default_menu_order' => true,
|
|
'descriptions' => true,
|
|
'domain' => true,
|
|
'editor_styles' => true,
|
|
'error' => true,
|
|
'errors' => true,
|
|
'EZSQL_ERROR' => true,
|
|
'feeds' => true,
|
|
'GETID3_ERRORARRAY' => true,
|
|
'hook_suffix' => true,
|
|
'HTTP_RAW_POST_DATA' => true,
|
|
'id' => true,
|
|
'in_comment_loop' => true,
|
|
'interim_login' => true,
|
|
'is_apache' => true,
|
|
'is_chrome' => true,
|
|
'is_gecko' => true,
|
|
'is_IE' => true,
|
|
'is_IIS' => true,
|
|
'is_iis7' => true,
|
|
'is_macIE' => true,
|
|
'is_NS4' => true,
|
|
'is_opera' => true,
|
|
'is_safari' => true,
|
|
'is_winIE' => true,
|
|
'l10n' => true,
|
|
'link' => true,
|
|
'link_id' => true,
|
|
'locale' => true,
|
|
'locked_post_status' => true,
|
|
'lost' => true,
|
|
'm' => true,
|
|
'map' => true,
|
|
'menu' => true,
|
|
'menu_order' => true,
|
|
'merged_filters' => true,
|
|
'mode' => true,
|
|
'monthnum' => true,
|
|
'more' => true,
|
|
'multipage' => true,
|
|
'names' => true,
|
|
'nav_menu_selected_id' => true,
|
|
'new_whitelist_options' => true,
|
|
'numpages' => true,
|
|
'one_theme_location_no_menus' => true,
|
|
'opml' => true,
|
|
'order' => true,
|
|
'orderby' => true,
|
|
'overridden_cpage' => true,
|
|
'page' => true,
|
|
'paged' => true,
|
|
'pagenow' => true,
|
|
'pages' => true,
|
|
'parent_file' => true,
|
|
'pass_allowed_html' => true,
|
|
'pass_allowed_protocols' => true,
|
|
'path' => true,
|
|
'per_page' => true,
|
|
'PHP_SELF' => true,
|
|
'phpmailer' => true,
|
|
'plugin_page' => true,
|
|
'plugins' => true,
|
|
'post' => true,
|
|
'post_default_category' => true,
|
|
'post_default_title' => true,
|
|
'post_ID' => true,
|
|
'post_id' => true,
|
|
'post_mime_types' => true,
|
|
'post_type' => true,
|
|
'post_type_object' => true,
|
|
'posts' => true,
|
|
'preview' => true,
|
|
'previouscat' => true,
|
|
'previousday' => true,
|
|
'previousweekday' => true,
|
|
'redir_tab' => true,
|
|
'required_mysql_version' => true,
|
|
'required_php_version' => true,
|
|
'rnd_value' => true,
|
|
'role' => true,
|
|
's' => true,
|
|
'search' => true,
|
|
'self' => true,
|
|
'shortcode_tags' => true,
|
|
'show_admin_bar' => true,
|
|
'sidebars_widgets' => true,
|
|
'status' => true,
|
|
'submenu' => true,
|
|
'submenu_file' => true,
|
|
'super_admins' => true,
|
|
'tab' => true,
|
|
'table_prefix' => true,
|
|
'tabs' => true,
|
|
'tag' => true,
|
|
'targets' => true,
|
|
'tax' => true,
|
|
'taxnow' => true,
|
|
'taxonomy' => true,
|
|
'term' => true,
|
|
'text_direction' => true,
|
|
'theme_field_defaults' => true,
|
|
'themes_allowedtags' => true,
|
|
'timeend' => true,
|
|
'timestart' => true,
|
|
'tinymce_version' => true,
|
|
'title' => true,
|
|
'totals' => true,
|
|
'type' => true,
|
|
'typenow' => true,
|
|
'updated_timestamp' => true,
|
|
'upgrading' => true,
|
|
'urls' => true,
|
|
'user_email' => true,
|
|
'user_ID' => true,
|
|
'user_identity' => true,
|
|
'user_level' => true,
|
|
'user_login' => true,
|
|
'user_url' => true,
|
|
'userdata' => true,
|
|
'usersearch' => true,
|
|
'whitelist_options' => true,
|
|
'withcomments' => true,
|
|
'wp' => true,
|
|
'wp_actions' => true,
|
|
'wp_admin_bar' => true,
|
|
'wp_cockneyreplace' => true,
|
|
'wp_current_db_version' => true,
|
|
'wp_current_filter' => true,
|
|
'wp_customize' => true,
|
|
'wp_dashboard_control_callbacks' => true,
|
|
'wp_db_version' => true,
|
|
'wp_did_header' => true,
|
|
'wp_embed' => true,
|
|
'wp_file_descriptions' => true,
|
|
'wp_filesystem' => true,
|
|
'wp_filter' => true,
|
|
'wp_hasher' => true,
|
|
'wp_header_to_desc' => true,
|
|
'wp_importers' => true,
|
|
'wp_json' => true,
|
|
'wp_list_table' => true,
|
|
'wp_local_package' => true,
|
|
'wp_locale' => true,
|
|
'wp_meta_boxes' => true,
|
|
'wp_object_cache' => true,
|
|
'wp_plugin_paths' => true,
|
|
'wp_post_statuses' => true,
|
|
'wp_post_types' => true,
|
|
'wp_queries' => true,
|
|
'wp_query' => true,
|
|
'wp_registered_sidebars' => true,
|
|
'wp_registered_widget_controls' => true,
|
|
'wp_registered_widget_updates' => true,
|
|
'wp_registered_widgets' => true,
|
|
'wp_rewrite' => true,
|
|
'wp_rich_edit' => true,
|
|
'wp_rich_edit_exists' => true,
|
|
'wp_roles' => true,
|
|
'wp_scripts' => true,
|
|
'wp_settings_errors' => true,
|
|
'wp_settings_fields' => true,
|
|
'wp_settings_sections' => true,
|
|
'wp_smiliessearch' => true,
|
|
'wp_styles' => true,
|
|
'wp_taxonomies' => true,
|
|
'wp_the_query' => true,
|
|
'wp_theme_directories' => true,
|
|
'wp_themes' => true,
|
|
'wp_user_roles' => true,
|
|
'wp_version' => true,
|
|
'wp_widget_factory' => true,
|
|
'wp_xmlrpc_server' => true,
|
|
'wpcommentsjavascript' => true,
|
|
'wpcommentspopupfile' => true,
|
|
'wpdb' => true,
|
|
'wpsmiliestrans' => true,
|
|
'year' => true,
|
|
);
|
|
|
|
/**
|
|
* A list of superglobals that incorporate user input.
|
|
*
|
|
* @since 0.5.0
|
|
* @since 0.11.0 Changed from static to non-static.
|
|
*
|
|
* @var string[]
|
|
*/
|
|
protected $input_superglobals = array(
|
|
'$_COOKIE',
|
|
'$_GET',
|
|
'$_FILES',
|
|
'$_POST',
|
|
'$_REQUEST',
|
|
'$_SERVER',
|
|
);
|
|
|
|
/**
|
|
* Whitelist of classes which test classes can extend.
|
|
*
|
|
* @since 0.11.0
|
|
*
|
|
* @var string[]
|
|
*/
|
|
protected $test_class_whitelist = array(
|
|
'WP_UnitTestCase' => true,
|
|
'WP_Ajax_UnitTestCase' => true,
|
|
'WP_Canonical_UnitTestCase' => true,
|
|
'WP_Test_REST_TestCase' => true,
|
|
'WP_Test_REST_Controller_Testcase' => true,
|
|
'WP_Test_REST_Post_Type_Controller_Testcase' => true,
|
|
'WP_XMLRPC_UnitTestCase' => true,
|
|
'PHPUnit_Framework_TestCase' => true,
|
|
'PHPUnit\Framework\TestCase' => true,
|
|
);
|
|
|
|
/**
|
|
* The token "type" values for the PHPCS 3.2+ whitelist comments.
|
|
*
|
|
* PHPCS cross-version compatibility layer to allow sniffs to
|
|
* allow for the new PHPCS annotation comments without breaking in older
|
|
* PHPCS versions.
|
|
*
|
|
* @internal Can be replaced with using the PHPCS native Token::$phpcsCommentTokens
|
|
* array once the minimum WPCS requirement for PHPCS has gone up
|
|
* to PHPCS 3.2.3.
|
|
* Note: The PHPCS native property uses the constants/ token "code",
|
|
* so code referring to this property will need to be adjusted when
|
|
* the property is removed.
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $phpcsCommentTokens = array(
|
|
'T_PHPCS_ENABLE' => true,
|
|
'T_PHPCS_DISABLE' => true,
|
|
'T_PHPCS_SET' => true,
|
|
'T_PHPCS_IGNORE' => true,
|
|
'T_PHPCS_IGNORE_FILE' => true,
|
|
);
|
|
|
|
/**
|
|
* The current file being sniffed.
|
|
*
|
|
* @since 0.4.0
|
|
*
|
|
* @var \PHP_CodeSniffer\Files\File
|
|
*/
|
|
protected $phpcsFile;
|
|
|
|
/**
|
|
* The list of tokens in the current file being sniffed.
|
|
*
|
|
* @since 0.4.0
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $tokens;
|
|
|
|
/**
|
|
* Set sniff properties and hand off to child class for processing of the token.
|
|
*
|
|
* @since 0.11.0
|
|
*
|
|
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
|
|
* @param int $stackPtr The position of the current token
|
|
* in the stack passed in $tokens.
|
|
*
|
|
* @return int|void Integer stack pointer to skip forward or void to continue
|
|
* normal file processing.
|
|
*/
|
|
public function process( File $phpcsFile, $stackPtr ) {
|
|
$this->init( $phpcsFile );
|
|
return $this->process_token( $stackPtr );
|
|
}
|
|
|
|
/**
|
|
* Processes a sniff when one of its tokens is encountered.
|
|
*
|
|
* @since 0.11.0
|
|
*
|
|
* @param int $stackPtr The position of the current token in the stack.
|
|
*
|
|
* @return int|void Integer stack pointer to skip forward or void to continue
|
|
* normal file processing.
|
|
*/
|
|
abstract public function process_token( $stackPtr );
|
|
|
|
/**
|
|
* Initialize the class for the current process.
|
|
*
|
|
* This method must be called by child classes before using many of the methods
|
|
* below.
|
|
*
|
|
* @since 0.4.0
|
|
*
|
|
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file currently being processed.
|
|
*/
|
|
protected function init( File $phpcsFile ) {
|
|
$this->phpcsFile = $phpcsFile;
|
|
$this->tokens = $phpcsFile->getTokens();
|
|
}
|
|
|
|
/**
|
|
* Strip quotes surrounding an arbitrary string.
|
|
*
|
|
* Intended for use with the contents of a T_CONSTANT_ENCAPSED_STRING / T_DOUBLE_QUOTED_STRING.
|
|
*
|
|
* @since 0.11.0
|
|
*
|
|
* @param string $string The raw string.
|
|
* @return string String without quotes around it.
|
|
*/
|
|
public function strip_quotes( $string ) {
|
|
return preg_replace( '`^([\'"])(.*)\1$`Ds', '$2', $string );
|
|
}
|
|
|
|
/**
|
|
* Add a PHPCS message to the output stack as either a warning or an error.
|
|
*
|
|
* @since 0.11.0
|
|
*
|
|
* @param string $message The message.
|
|
* @param int $stackPtr The position of the token the message relates to.
|
|
* @param bool $is_error Optional. Whether to report the message as an 'error' or 'warning'.
|
|
* Defaults to true (error).
|
|
* @param string $code Optional error code for the message. Defaults to 'Found'.
|
|
* @param array $data Optional input for the data replacements.
|
|
* @param int $severity Optional. Severity level. Defaults to 0 which will translate to
|
|
* the PHPCS default severity level.
|
|
* @return bool
|
|
*/
|
|
protected function addMessage( $message, $stackPtr, $is_error = true, $code = 'Found', $data = array(), $severity = 0 ) {
|
|
return $this->throwMessage( $message, $stackPtr, $is_error, $code, $data, $severity, false );
|
|
}
|
|
|
|
/**
|
|
* Add a fixable PHPCS message to the output stack as either a warning or an error.
|
|
*
|
|
* @since 0.11.0
|
|
*
|
|
* @param string $message The message.
|
|
* @param int $stackPtr The position of the token the message relates to.
|
|
* @param bool $is_error Optional. Whether to report the message as an 'error' or 'warning'.
|
|
* Defaults to true (error).
|
|
* @param string $code Optional error code for the message. Defaults to 'Found'.
|
|
* @param array $data Optional input for the data replacements.
|
|
* @param int $severity Optional. Severity level. Defaults to 0 which will translate to
|
|
* the PHPCS default severity level.
|
|
* @return bool
|
|
*/
|
|
protected function addFixableMessage( $message, $stackPtr, $is_error = true, $code = 'Found', $data = array(), $severity = 0 ) {
|
|
return $this->throwMessage( $message, $stackPtr, $is_error, $code, $data, $severity, true );
|
|
}
|
|
|
|
/**
|
|
* Add a PHPCS message to the output stack as either a warning or an error.
|
|
*
|
|
* @since 0.11.0
|
|
*
|
|
* @param string $message The message.
|
|
* @param int $stackPtr The position of the token the message relates to.
|
|
* @param bool $is_error Optional. Whether to report the message as an 'error' or 'warning'.
|
|
* Defaults to true (error).
|
|
* @param string $code Optional error code for the message. Defaults to 'Found'.
|
|
* @param array $data Optional input for the data replacements.
|
|
* @param int $severity Optional. Severity level. Defaults to 0 which will translate to
|
|
* the PHPCS default severity level.
|
|
* @param bool $fixable Optional. Whether this is a fixable error. Defaults to false.
|
|
* @return bool
|
|
*/
|
|
private function throwMessage( $message, $stackPtr, $is_error = true, $code = 'Found', $data = array(), $severity = 0, $fixable = false ) {
|
|
|
|
$method = 'add';
|
|
if ( true === $fixable ) {
|
|
$method .= 'Fixable';
|
|
}
|
|
|
|
if ( true === $is_error ) {
|
|
$method .= 'Error';
|
|
} else {
|
|
$method .= 'Warning';
|
|
}
|
|
|
|
return \call_user_func( array( $this->phpcsFile, $method ), $message, $stackPtr, $code, $data, $severity );
|
|
}
|
|
|
|
/**
|
|
* Convert an arbitrary string to an alphanumeric string with underscores.
|
|
*
|
|
* Pre-empt issues with arbitrary strings being used as error codes in XML and PHP.
|
|
*
|
|
* @since 0.11.0
|
|
*
|
|
* @param string $base_string Arbitrary string.
|
|
*
|
|
* @return string
|
|
*/
|
|
protected function string_to_errorcode( $base_string ) {
|
|
return preg_replace( '`[^a-z0-9_]`i', '_', $base_string );
|
|
}
|
|
|
|
/**
|
|
* Merge a pre-set array with a ruleset provided array or inline provided string.
|
|
*
|
|
* - Will correctly handle custom array properties which were set without
|
|
* the `type="array"` indicator.
|
|
* This also allows for making these custom array properties testable using
|
|
* a `@codingStandardsChangeSetting` comment in the unit tests.
|
|
* - By default flips custom lists to allow for using `isset()` instead
|
|
* of `in_array()`.
|
|
* - When `$flip` is true:
|
|
* * Presumes the base array is in a `'value' => true` format.
|
|
* * Any custom items will be given the value `false` to be able to
|
|
* distinguish them from pre-set (base array) values.
|
|
* * Will filter previously added custom items out from the base array
|
|
* before merging/returning to allow for resetting to the base array.
|
|
*
|
|
* {@internal Function is static as it doesn't use any of the properties or others
|
|
* methods anyway and this way the `WordPress_Sniffs_NamingConventions_ValidVariableNameSniff`
|
|
* which extends an upstream sniff can also use it.}}
|
|
*
|
|
* @since 0.11.0
|
|
*
|
|
* @param array|string $custom Custom list as provided via a ruleset.
|
|
* Can be either a comma-delimited string or
|
|
* an array of values.
|
|
* @param array $base Optional. Base list. Defaults to an empty array.
|
|
* Expects `value => true` format when `$flip` is true.
|
|
* @param bool $flip Optional. Whether or not to flip the custom list.
|
|
* Defaults to true.
|
|
* @return array
|
|
*/
|
|
public static function merge_custom_array( $custom, $base = array(), $flip = true ) {
|
|
if ( true === $flip ) {
|
|
$base = array_filter( $base );
|
|
}
|
|
|
|
if ( empty( $custom ) || ( ! \is_array( $custom ) && ! \is_string( $custom ) ) ) {
|
|
return $base;
|
|
}
|
|
|
|
// Allow for a comma delimited list.
|
|
if ( \is_string( $custom ) ) {
|
|
$custom = explode( ',', $custom );
|
|
}
|
|
|
|
// Always trim whitespace from the values.
|
|
$custom = array_filter( array_map( 'trim', $custom ) );
|
|
|
|
if ( true === $flip ) {
|
|
$custom = array_fill_keys( $custom, false );
|
|
}
|
|
|
|
if ( empty( $base ) ) {
|
|
return $custom;
|
|
}
|
|
|
|
return array_merge( $base, $custom );
|
|
}
|
|
|
|
/**
|
|
* Get the last pointer in a line.
|
|
*
|
|
* @since 0.4.0
|
|
*
|
|
* @param integer $stackPtr The position of the current token in the stack passed
|
|
* in $tokens.
|
|
*
|
|
* @return integer Position of the last pointer on that line.
|
|
*/
|
|
protected function get_last_ptr_on_line( $stackPtr ) {
|
|
|
|
$tokens = $this->tokens;
|
|
$currentLine = $tokens[ $stackPtr ]['line'];
|
|
$nextPtr = ( $stackPtr + 1 );
|
|
|
|
while ( isset( $tokens[ $nextPtr ] ) && $tokens[ $nextPtr ]['line'] === $currentLine ) {
|
|
$nextPtr++;
|
|
// Do nothing, we just want the last token of the line.
|
|
}
|
|
|
|
// We've made it to the next line, back up one to the last in the previous line.
|
|
// We do this for micro-optimization of the above loop.
|
|
$lastPtr = ( $nextPtr - 1 );
|
|
|
|
return $lastPtr;
|
|
}
|
|
|
|
/**
|
|
* Overrule the minimum supported WordPress version with a command-line/config value.
|
|
*
|
|
* Handle setting the minimum supported WP version in one go for all sniffs which
|
|
* expect it via the command line or via a `<config>` variable in a ruleset.
|
|
* The config variable overrules the default `$minimum_supported_version` and/or a
|
|
* `$minimum_supported_version` set for individual sniffs through the ruleset.
|
|
*
|
|
* @since 0.14.0
|
|
*/
|
|
protected function get_wp_version_from_cl() {
|
|
$cl_supported_version = trim( PHPCSHelper::get_config_data( 'minimum_supported_wp_version' ) );
|
|
if ( ! empty( $cl_supported_version )
|
|
&& filter_var( $cl_supported_version, \FILTER_VALIDATE_FLOAT ) !== false
|
|
) {
|
|
$this->minimum_supported_version = $cl_supported_version;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Find whitelisting comment.
|
|
*
|
|
* Comment must be at the end of the line or at the end of the statement
|
|
* and must use // format.
|
|
* It can be prefixed or suffixed with anything e.g. "foobar" will match:
|
|
* ... // foobar okay
|
|
* ... // WPCS: foobar whitelist.
|
|
*
|
|
* There is an exception, and that is when PHP is being interspersed with HTML.
|
|
* In that case, the comment should always come at the end of the statement (right
|
|
* before the closing tag, ?>). For example:
|
|
*
|
|
* <input type="text" id="<?php echo $id; // XSS OK ?>" />
|
|
*
|
|
* @since 0.4.0
|
|
* @since 0.14.0 Whitelist comments at the end of the statement are now also accepted.
|
|
*
|
|
* @param string $comment Comment to find.
|
|
* @param integer $stackPtr The position of the current token in the stack passed
|
|
* in $tokens.
|
|
*
|
|
* @return boolean True if whitelisting comment was found, false otherwise.
|
|
*/
|
|
protected function has_whitelist_comment( $comment, $stackPtr ) {
|
|
|
|
// Respect the PHPCS 3.x --ignore-annotations setting.
|
|
if ( true === PHPCSHelper::ignore_annotations( $this->phpcsFile ) ) {
|
|
return false;
|
|
}
|
|
|
|
$regex = '#\b' . preg_quote( $comment, '#' ) . '\b#i';
|
|
|
|
// There is a findEndOfStatement() method, but it considers more tokens than
|
|
// we need to consider here.
|
|
$end_of_statement = $this->phpcsFile->findNext( array( \T_CLOSE_TAG, \T_SEMICOLON ), $stackPtr );
|
|
|
|
if ( false !== $end_of_statement ) {
|
|
// If the statement was ended by a semicolon, check if there is a whitelist comment directly after it.
|
|
if ( \T_SEMICOLON === $this->tokens[ $end_of_statement ]['code'] ) {
|
|
$lastPtr = $this->phpcsFile->findNext( \T_WHITESPACE, ( $end_of_statement + 1 ), null, true );
|
|
} elseif ( \T_CLOSE_TAG === $this->tokens[ $end_of_statement ]['code'] ) {
|
|
// If the semicolon was left out and it was terminated by an ending tag, we need to look backwards.
|
|
$lastPtr = $this->phpcsFile->findPrevious( \T_WHITESPACE, ( $end_of_statement - 1 ), null, true );
|
|
}
|
|
|
|
if ( ( ( \T_COMMENT === $this->tokens[ $lastPtr ]['code']
|
|
&& strpos( $this->tokens[ $lastPtr ]['content'], '@codingStandardsChangeSetting' ) === false )
|
|
|| ( isset( $this->phpcsCommentTokens[ $this->tokens[ $lastPtr ]['type'] ] )
|
|
&& 'T_PHPCS_SET' !== $this->tokens[ $lastPtr ]['type'] ) )
|
|
&& $this->tokens[ $lastPtr ]['line'] === $this->tokens[ $end_of_statement ]['line']
|
|
&& preg_match( $regex, $this->tokens[ $lastPtr ]['content'] ) === 1
|
|
) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// No whitelist comment found so far. Check at the end of the stackPtr line.
|
|
// Note: a T_COMMENT includes the new line character, so may be the last token on the line!
|
|
$end_of_line = $this->get_last_ptr_on_line( $stackPtr );
|
|
$lastPtr = $this->phpcsFile->findPrevious( \T_WHITESPACE, $end_of_line, null, true );
|
|
|
|
if ( ( ( \T_COMMENT === $this->tokens[ $lastPtr ]['code']
|
|
&& strpos( $this->tokens[ $lastPtr ]['content'], '@codingStandardsChangeSetting' ) === false )
|
|
|| ( isset( $this->phpcsCommentTokens[ $this->tokens[ $lastPtr ]['type'] ] )
|
|
&& 'T_PHPCS_SET' !== $this->tokens[ $lastPtr ]['type'] ) )
|
|
&& $this->tokens[ $lastPtr ]['line'] === $this->tokens[ $stackPtr ]['line']
|
|
&& preg_match( $regex, $this->tokens[ $lastPtr ]['content'] ) === 1
|
|
) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Check if a token is used within a unit test.
|
|
*
|
|
* Unit test methods are identified as such:
|
|
* - Method is within a known unit test class;
|
|
* - or Method is within a class/trait which extends a known unit test class.
|
|
*
|
|
* @since 0.11.0
|
|
* @since 1.1.0 Supports anonymous test classes and improved handling of nested scopes.
|
|
*
|
|
* @param int $stackPtr The position of the token to be examined.
|
|
*
|
|
* @return bool True if the token is within a unit test, false otherwise.
|
|
*/
|
|
protected function is_token_in_test_method( $stackPtr ) {
|
|
// Is the token inside of a function definition ?
|
|
$functionToken = $this->phpcsFile->getCondition( $stackPtr, \T_FUNCTION );
|
|
if ( false === $functionToken ) {
|
|
// No conditions or no function condition.
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Is this a method inside of a class or a trait ? If so, it is a test class/trait ?
|
|
*
|
|
* {@internal Once the minimum supported PHPCS version has gone up to 3.1.0, the
|
|
* local array here can be replace with Tokens::$ooScopeTokens.}}
|
|
*/
|
|
$oo_tokens = array(
|
|
\T_CLASS => true,
|
|
\T_TRAIT => true,
|
|
\T_ANON_CLASS => true,
|
|
);
|
|
$conditions = $this->tokens[ $stackPtr ]['conditions'];
|
|
|
|
foreach ( $conditions as $token => $condition ) {
|
|
if ( $token === $functionToken ) {
|
|
// Only examine the conditions the function is nested in, not those nested within the function.
|
|
break;
|
|
}
|
|
|
|
if ( isset( $oo_tokens[ $condition ] ) ) {
|
|
$is_test_class = $this->is_test_class( $token );
|
|
if ( true === $is_test_class ) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Check if a class token is part of a unit test suite.
|
|
*
|
|
* Unit test classes are identified as such:
|
|
* - Class which either extends WP_UnitTestCase or PHPUnit_Framework_TestCase
|
|
* or a custom whitelisted unit test class.
|
|
*
|
|
* @since 0.12.0 Split off from the `is_token_in_test_method()` method.
|
|
* @since 1.0.0 Improved recognition of namespaced class names.
|
|
*
|
|
* @param int $stackPtr The position of the token to be examined.
|
|
* This should be a class, anonymous class or trait token.
|
|
*
|
|
* @return bool True if the class is a unit test class, false otherwise.
|
|
*/
|
|
protected function is_test_class( $stackPtr ) {
|
|
|
|
if ( ! isset( $this->tokens[ $stackPtr ] )
|
|
|| \in_array( $this->tokens[ $stackPtr ]['type'], array( 'T_CLASS', 'T_ANON_CLASS', 'T_TRAIT' ), true ) === false
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
// Add any potentially whitelisted custom test classes to the whitelist.
|
|
$whitelist = $this->merge_custom_array(
|
|
$this->custom_test_class_whitelist,
|
|
$this->test_class_whitelist
|
|
);
|
|
|
|
/*
|
|
* Show some tolerance for user input.
|
|
* The custom test class names should be passed as FQN without a prefixing `\`.
|
|
*/
|
|
foreach ( $whitelist as $k => $v ) {
|
|
$whitelist[ $k ] = ltrim( $v, '\\' );
|
|
}
|
|
|
|
// Is the class/trait one of the whitelisted test classes ?
|
|
$namespace = $this->determine_namespace( $stackPtr );
|
|
$className = $this->phpcsFile->getDeclarationName( $stackPtr );
|
|
if ( '' !== $namespace ) {
|
|
if ( isset( $whitelist[ $namespace . '\\' . $className ] ) ) {
|
|
return true;
|
|
}
|
|
} elseif ( isset( $whitelist[ $className ] ) ) {
|
|
return true;
|
|
}
|
|
|
|
// Does the class/trait extend one of the whitelisted test classes ?
|
|
$extendedClassName = $this->phpcsFile->findExtendedClassName( $stackPtr );
|
|
if ( '\\' === $extendedClassName[0] ) {
|
|
if ( isset( $whitelist[ substr( $extendedClassName, 1 ) ] ) ) {
|
|
return true;
|
|
}
|
|
} elseif ( '' !== $namespace ) {
|
|
if ( isset( $whitelist[ $namespace . '\\' . $extendedClassName ] ) ) {
|
|
return true;
|
|
}
|
|
} elseif ( isset( $whitelist[ $extendedClassName ] ) ) {
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Not examining imported classes via `use` statements as with the variety of syntaxes,
|
|
* this would get very complicated.
|
|
* After all, users can add an `<exclude-pattern>` for a particular sniff to their
|
|
* custom ruleset to selectively exclude the test directory.
|
|
*/
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Check if this variable is being assigned a value.
|
|
*
|
|
* E.g., $var = 'foo';
|
|
*
|
|
* Also handles array assignments to arbitrary depth:
|
|
*
|
|
* $array['key'][ $foo ][ something() ] = $bar;
|
|
*
|
|
* @since 0.5.0
|
|
*
|
|
* @param int $stackPtr The index of the token in the stack. This must point to
|
|
* either a T_VARIABLE or T_CLOSE_SQUARE_BRACKET token.
|
|
*
|
|
* @return bool Whether the token is a variable being assigned a value.
|
|
*/
|
|
protected function is_assignment( $stackPtr ) {
|
|
|
|
static $valid = array(
|
|
\T_VARIABLE => true,
|
|
\T_CLOSE_SQUARE_BRACKET => true,
|
|
);
|
|
|
|
// Must be a variable, constant or closing square bracket (see below).
|
|
if ( ! isset( $valid[ $this->tokens[ $stackPtr ]['code'] ] ) ) {
|
|
return false;
|
|
}
|
|
|
|
$next_non_empty = $this->phpcsFile->findNext(
|
|
Tokens::$emptyTokens,
|
|
( $stackPtr + 1 ),
|
|
null,
|
|
true,
|
|
null,
|
|
true
|
|
);
|
|
|
|
// No token found.
|
|
if ( false === $next_non_empty ) {
|
|
return false;
|
|
}
|
|
|
|
// If the next token is an assignment, that's all we need to know.
|
|
if ( isset( Tokens::$assignmentTokens[ $this->tokens[ $next_non_empty ]['code'] ] ) ) {
|
|
return true;
|
|
}
|
|
|
|
// Check if this is an array assignment, e.g., `$var['key'] = 'val';` .
|
|
if ( \T_OPEN_SQUARE_BRACKET === $this->tokens[ $next_non_empty ]['code'] ) {
|
|
return $this->is_assignment( $this->tokens[ $next_non_empty ]['bracket_closer'] );
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Check if this token has an associated nonce check.
|
|
*
|
|
* @since 0.5.0
|
|
*
|
|
* @param int $stackPtr The position of the current token in the stack of tokens.
|
|
*
|
|
* @return bool
|
|
*/
|
|
protected function has_nonce_check( $stackPtr ) {
|
|
|
|
/**
|
|
* A cache of the scope that we last checked for nonce verification in.
|
|
*
|
|
* @var array {
|
|
* @var string $file The name of the file.
|
|
* @var int $start The index of the token where the scope started.
|
|
* @var int $end The index of the token where the scope ended.
|
|
* @var bool|int $nonce_check The index of the token where an nonce check
|
|
* was found, or false if none was found.
|
|
* }
|
|
*/
|
|
static $last;
|
|
|
|
$start = 0;
|
|
$end = $stackPtr;
|
|
|
|
$tokens = $this->phpcsFile->getTokens();
|
|
|
|
// If we're in a function, only look inside of it.
|
|
$f = $this->phpcsFile->getCondition( $stackPtr, \T_FUNCTION );
|
|
if ( false !== $f ) {
|
|
$start = $tokens[ $f ]['scope_opener'];
|
|
} else {
|
|
$f = $this->phpcsFile->getCondition( $stackPtr, \T_CLOSURE );
|
|
if ( false !== $f ) {
|
|
$start = $tokens[ $f ]['scope_opener'];
|
|
}
|
|
}
|
|
|
|
$in_isset = $this->is_in_isset_or_empty( $stackPtr );
|
|
|
|
// We allow for isset( $_POST['var'] ) checks to come before the nonce check.
|
|
// If this is inside an isset(), check after it as well, all the way to the
|
|
// end of the scope.
|
|
if ( $in_isset ) {
|
|
$end = ( 0 === $start ) ? $this->phpcsFile->numTokens : $tokens[ $start ]['scope_closer'];
|
|
}
|
|
|
|
// Check if we've looked here before.
|
|
$filename = $this->phpcsFile->getFilename();
|
|
|
|
if (
|
|
$filename === $last['file']
|
|
&& $start === $last['start']
|
|
) {
|
|
|
|
if ( false !== $last['nonce_check'] ) {
|
|
// If we have already found an nonce check in this scope, we just
|
|
// need to check whether it comes before this token. It is OK if the
|
|
// check is after the token though, if this was only a isset() check.
|
|
return ( $in_isset || $last['nonce_check'] < $stackPtr );
|
|
} elseif ( $end <= $last['end'] ) {
|
|
// If not, we can still go ahead and return false if we've already
|
|
// checked to the end of the search area.
|
|
return false;
|
|
}
|
|
|
|
// We haven't checked this far yet, but we can still save work by
|
|
// skipping over the part we've already checked.
|
|
$start = $last['end'];
|
|
} else {
|
|
$last = array(
|
|
'file' => $filename,
|
|
'start' => $start,
|
|
'end' => $end,
|
|
);
|
|
}
|
|
|
|
// Loop through the tokens looking for nonce verification functions.
|
|
for ( $i = $start; $i < $end; $i++ ) {
|
|
|
|
// If this isn't a function name, skip it.
|
|
if ( \T_STRING !== $tokens[ $i ]['code'] ) {
|
|
continue;
|
|
}
|
|
|
|
// If this is one of the nonce verification functions, we can bail out.
|
|
if ( isset( $this->nonceVerificationFunctions[ $tokens[ $i ]['content'] ] ) ) {
|
|
$last['nonce_check'] = $i;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// We're still here, so no luck.
|
|
$last['nonce_check'] = false;
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Check if a token is inside of an isset() or empty() statement.
|
|
*
|
|
* @since 0.5.0
|
|
*
|
|
* @param int $stackPtr The index of the token in the stack.
|
|
*
|
|
* @return bool Whether the token is inside an isset() or empty() statement.
|
|
*/
|
|
protected function is_in_isset_or_empty( $stackPtr ) {
|
|
|
|
if ( ! isset( $this->tokens[ $stackPtr ]['nested_parenthesis'] ) ) {
|
|
return false;
|
|
}
|
|
|
|
$nested_parenthesis = $this->tokens[ $stackPtr ]['nested_parenthesis'];
|
|
|
|
end( $nested_parenthesis );
|
|
$open_parenthesis = key( $nested_parenthesis );
|
|
|
|
$previous_non_empty = $this->phpcsFile->findPrevious( Tokens::$emptyTokens, ( $open_parenthesis - 1 ), null, true, null, true );
|
|
return in_array( $this->tokens[ $previous_non_empty ]['code'], array( \T_ISSET, \T_EMPTY ), true );
|
|
}
|
|
|
|
/**
|
|
* Check if something is only being sanitized.
|
|
*
|
|
* @since 0.5.0
|
|
*
|
|
* @param int $stackPtr The index of the token in the stack.
|
|
*
|
|
* @return bool Whether the token is only within a sanitization.
|
|
*/
|
|
protected function is_only_sanitized( $stackPtr ) {
|
|
|
|
// If it isn't being sanitized at all.
|
|
if ( ! $this->is_sanitized( $stackPtr ) ) {
|
|
return false;
|
|
}
|
|
|
|
// If this isn't set, we know the value must have only been casted, because
|
|
// is_sanitized() would have returned false otherwise.
|
|
if ( ! isset( $this->tokens[ $stackPtr ]['nested_parenthesis'] ) ) {
|
|
return true;
|
|
}
|
|
|
|
// At this point we're expecting the value to have not been casted. If it
|
|
// was, it wasn't *only* casted, because it's also in a function.
|
|
if ( $this->is_safe_casted( $stackPtr ) ) {
|
|
return false;
|
|
}
|
|
|
|
// The only parentheses should belong to the sanitizing function. If there's
|
|
// more than one set, this isn't *only* sanitization.
|
|
return ( \count( $this->tokens[ $stackPtr ]['nested_parenthesis'] ) === 1 );
|
|
}
|
|
|
|
/**
|
|
* Check if something is being casted to a safe value.
|
|
*
|
|
* @since 0.5.0
|
|
*
|
|
* @param int $stackPtr The index of the token in the stack.
|
|
*
|
|
* @return bool Whether the token being casted.
|
|
*/
|
|
protected function is_safe_casted( $stackPtr ) {
|
|
|
|
// Get the last non-empty token.
|
|
$prev = $this->phpcsFile->findPrevious(
|
|
Tokens::$emptyTokens,
|
|
( $stackPtr - 1 ),
|
|
null,
|
|
true
|
|
);
|
|
|
|
if ( false === $prev ) {
|
|
return false;
|
|
}
|
|
|
|
// Check if it is a safe cast.
|
|
return isset( $this->safe_casts[ $this->tokens[ $prev ]['code'] ] );
|
|
}
|
|
|
|
/**
|
|
* Check if something is being sanitized.
|
|
*
|
|
* @since 0.5.0
|
|
*
|
|
* @param int $stackPtr The index of the token in the stack.
|
|
* @param bool $require_unslash Whether to give an error if wp_unslash() isn't
|
|
* used on the variable before sanitization.
|
|
*
|
|
* @return bool Whether the token being sanitized.
|
|
*/
|
|
protected function is_sanitized( $stackPtr, $require_unslash = false ) {
|
|
|
|
// First we check if it is being casted to a safe value.
|
|
if ( $this->is_safe_casted( $stackPtr ) ) {
|
|
return true;
|
|
}
|
|
|
|
// If this isn't within a function call, we know already that it's not safe.
|
|
if ( ! isset( $this->tokens[ $stackPtr ]['nested_parenthesis'] ) ) {
|
|
if ( $require_unslash ) {
|
|
$this->add_unslash_error( $stackPtr );
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Get the function that it's in.
|
|
$nested_parenthesis = $this->tokens[ $stackPtr ]['nested_parenthesis'];
|
|
$function_closer = end( $nested_parenthesis );
|
|
$function_opener = key( $nested_parenthesis );
|
|
$function = $this->tokens[ ( $function_opener - 1 ) ];
|
|
|
|
// If it is just being unset, the value isn't used at all, so it's safe.
|
|
if ( \T_UNSET === $function['code'] ) {
|
|
return true;
|
|
}
|
|
|
|
// If this isn't a call to a function, it sure isn't sanitizing function.
|
|
if ( \T_STRING !== $function['code'] ) {
|
|
if ( $require_unslash ) {
|
|
$this->add_unslash_error( $stackPtr );
|
|
}
|
|
return false;
|
|
}
|
|
|
|
$functionName = $function['content'];
|
|
|
|
// Check if wp_unslash() is being used.
|
|
if ( 'wp_unslash' === $functionName ) {
|
|
|
|
$is_unslashed = true;
|
|
$function_closer = prev( $nested_parenthesis );
|
|
|
|
// If there is no other function being used, this value is unsanitized.
|
|
if ( ! $function_closer ) {
|
|
return false;
|
|
}
|
|
|
|
$function_opener = key( $nested_parenthesis );
|
|
$functionName = $this->tokens[ ( $function_opener - 1 ) ]['content'];
|
|
|
|
} else {
|
|
|
|
$is_unslashed = false;
|
|
}
|
|
|
|
// Arrays might be sanitized via array_map().
|
|
if ( 'array_map' === $functionName ) {
|
|
|
|
// Get the first parameter.
|
|
$callback = $this->get_function_call_parameter( ( $function_opener - 1 ), 1 );
|
|
|
|
if ( ! empty( $callback ) ) {
|
|
/*
|
|
* If this is a function callback (not a method callback array) and we're able
|
|
* to resolve the function name, do so.
|
|
*/
|
|
$first_non_empty = $this->phpcsFile->findNext(
|
|
Tokens::$emptyTokens,
|
|
$callback['start'],
|
|
( $callback['end'] + 1 ),
|
|
true
|
|
);
|
|
|
|
if ( false !== $first_non_empty && \T_CONSTANT_ENCAPSED_STRING === $this->tokens[ $first_non_empty ]['code'] ) {
|
|
$functionName = $this->strip_quotes( $this->tokens[ $first_non_empty ]['content'] );
|
|
}
|
|
}
|
|
}
|
|
|
|
// If slashing is required, give an error.
|
|
if ( ! $is_unslashed && $require_unslash && ! isset( $this->unslashingSanitizingFunctions[ $functionName ] ) ) {
|
|
$this->add_unslash_error( $stackPtr );
|
|
}
|
|
|
|
// Check if this is a sanitizing function.
|
|
if ( isset( $this->sanitizingFunctions[ $functionName ] ) || isset( $this->unslashingSanitizingFunctions[ $functionName ] ) ) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Add an error for missing use of wp_unslash().
|
|
*
|
|
* @since 0.5.0
|
|
*
|
|
* @param int $stackPtr The index of the token in the stack.
|
|
*/
|
|
public function add_unslash_error( $stackPtr ) {
|
|
|
|
$this->phpcsFile->addError(
|
|
'Missing wp_unslash() before sanitization.',
|
|
$stackPtr,
|
|
'MissingUnslash',
|
|
array( $this->tokens[ $stackPtr ]['content'] )
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get the index key of an array variable.
|
|
*
|
|
* E.g., "bar" in $foo['bar'].
|
|
*
|
|
* @since 0.5.0
|
|
*
|
|
* @param int $stackPtr The index of the token in the stack.
|
|
*
|
|
* @return string|false The array index key whose value is being accessed.
|
|
*/
|
|
protected function get_array_access_key( $stackPtr ) {
|
|
|
|
// Find the next non-empty token.
|
|
$open_bracket = $this->phpcsFile->findNext(
|
|
Tokens::$emptyTokens,
|
|
( $stackPtr + 1 ),
|
|
null,
|
|
true
|
|
);
|
|
|
|
// If it isn't a bracket, this isn't an array-access.
|
|
if ( false === $open_bracket || \T_OPEN_SQUARE_BRACKET !== $this->tokens[ $open_bracket ]['code'] ) {
|
|
return false;
|
|
}
|
|
|
|
$key = $this->phpcsFile->getTokensAsString(
|
|
( $open_bracket + 1 ),
|
|
( $this->tokens[ $open_bracket ]['bracket_closer'] - $open_bracket - 1 )
|
|
);
|
|
|
|
return trim( $key );
|
|
}
|
|
|
|
/**
|
|
* Check if the existence of a variable is validated with isset() or empty().
|
|
*
|
|
* When $in_condition_only is false, (which is the default), this is considered
|
|
* valid:
|
|
*
|
|
* ```php
|
|
* if ( isset( $var ) ) {
|
|
* // Do stuff, like maybe return or exit (but could be anything)
|
|
* }
|
|
*
|
|
* foo( $var );
|
|
* ```
|
|
*
|
|
* When it is true, that would be invalid, the use of the variable must be within
|
|
* the scope of the validating condition, like this:
|
|
*
|
|
* ```php
|
|
* if ( isset( $var ) ) {
|
|
* foo( $var );
|
|
* }
|
|
* ```
|
|
*
|
|
* @since 0.5.0
|
|
*
|
|
* @param int $stackPtr The index of this token in the stack.
|
|
* @param string $array_key An array key to check for ("bar" in $foo['bar']).
|
|
* @param bool $in_condition_only Whether to require that this use of the
|
|
* variable occur within the scope of the
|
|
* validating condition, or just in the same
|
|
* scope as it (default).
|
|
*
|
|
* @return bool Whether the var is validated.
|
|
*/
|
|
protected function is_validated( $stackPtr, $array_key = null, $in_condition_only = false ) {
|
|
|
|
if ( $in_condition_only ) {
|
|
/*
|
|
* This is a stricter check, requiring the variable to be used only
|
|
* within the validation condition.
|
|
*/
|
|
|
|
// If there are no conditions, there's no validation.
|
|
if ( empty( $this->tokens[ $stackPtr ]['conditions'] ) ) {
|
|
return false;
|
|
}
|
|
|
|
$conditions = $this->tokens[ $stackPtr ]['conditions'];
|
|
end( $conditions ); // Get closest condition.
|
|
$conditionPtr = key( $conditions );
|
|
$condition = $this->tokens[ $conditionPtr ];
|
|
|
|
if ( ! isset( $condition['parenthesis_opener'] ) ) {
|
|
// Live coding or parse error.
|
|
return false;
|
|
}
|
|
|
|
$scope_start = $condition['parenthesis_opener'];
|
|
$scope_end = $condition['parenthesis_closer'];
|
|
|
|
} else {
|
|
/*
|
|
* We are are more loose, requiring only that the variable be validated
|
|
* in the same function/file scope as it is used.
|
|
*/
|
|
|
|
$scope_start = 0;
|
|
|
|
// Check if we are in a function.
|
|
$function = $this->phpcsFile->getCondition( $stackPtr, \T_FUNCTION );
|
|
|
|
// If so, we check only within the function, otherwise the whole file.
|
|
if ( false !== $function ) {
|
|
$scope_start = $this->tokens[ $function ]['scope_opener'];
|
|
} else {
|
|
// Check if we are in a closure.
|
|
$closure = $this->phpcsFile->getCondition( $stackPtr, \T_CLOSURE );
|
|
|
|
// If so, we check only within the closure.
|
|
if ( false !== $closure ) {
|
|
$scope_start = $this->tokens[ $closure ]['scope_opener'];
|
|
}
|
|
}
|
|
|
|
$scope_end = $stackPtr;
|
|
|
|
}
|
|
|
|
$bare_array_key = $this->strip_quotes( $array_key );
|
|
|
|
for ( $i = ( $scope_start + 1 ); $i < $scope_end; $i++ ) {
|
|
|
|
if ( ! \in_array( $this->tokens[ $i ]['code'], array( \T_ISSET, \T_EMPTY, \T_UNSET ), true ) ) {
|
|
continue;
|
|
}
|
|
|
|
$issetOpener = $this->phpcsFile->findNext( \T_OPEN_PARENTHESIS, $i );
|
|
$issetCloser = $this->tokens[ $issetOpener ]['parenthesis_closer'];
|
|
|
|
// Look for this variable. We purposely stomp $i from the parent loop.
|
|
for ( $i = ( $issetOpener + 1 ); $i < $issetCloser; $i++ ) {
|
|
|
|
if ( \T_VARIABLE !== $this->tokens[ $i ]['code'] ) {
|
|
continue;
|
|
}
|
|
|
|
// If we're checking for a specific array key (ex: 'hello' in
|
|
// $_POST['hello']), that must match too. Quote-style, however, doesn't matter.
|
|
if ( isset( $array_key )
|
|
&& $this->strip_quotes( $this->get_array_access_key( $i ) ) !== $bare_array_key ) {
|
|
continue;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Check whether a variable is being compared to another value.
|
|
*
|
|
* E.g., $var === 'foo', 1 <= $var, etc.
|
|
*
|
|
* Also recognizes `switch ( $var )`.
|
|
*
|
|
* @since 0.5.0
|
|
*
|
|
* @param int $stackPtr The index of this token in the stack.
|
|
*
|
|
* @return bool Whether this is a comparison.
|
|
*/
|
|
protected function is_comparison( $stackPtr ) {
|
|
|
|
// We first check if this is a switch statement (switch ( $var )).
|
|
if ( isset( $this->tokens[ $stackPtr ]['nested_parenthesis'] ) ) {
|
|
$nested_parenthesis = $this->tokens[ $stackPtr ]['nested_parenthesis'];
|
|
$close_parenthesis = end( $nested_parenthesis );
|
|
|
|
if (
|
|
isset( $this->tokens[ $close_parenthesis ]['parenthesis_owner'] )
|
|
&& \T_SWITCH === $this->tokens[ $this->tokens[ $close_parenthesis ]['parenthesis_owner'] ]['code']
|
|
) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Find the previous non-empty token. We check before the var first because
|
|
// yoda conditions are usually expected.
|
|
$previous_token = $this->phpcsFile->findPrevious(
|
|
Tokens::$emptyTokens,
|
|
( $stackPtr - 1 ),
|
|
null,
|
|
true
|
|
);
|
|
|
|
if ( isset( Tokens::$comparisonTokens[ $this->tokens[ $previous_token ]['code'] ] ) ) {
|
|
return true;
|
|
}
|
|
|
|
// Maybe the comparison operator is after this.
|
|
$next_token = $this->phpcsFile->findNext(
|
|
Tokens::$emptyTokens,
|
|
( $stackPtr + 1 ),
|
|
null,
|
|
true
|
|
);
|
|
|
|
// This might be an opening square bracket in the case of arrays ($var['a']).
|
|
while ( \T_OPEN_SQUARE_BRACKET === $this->tokens[ $next_token ]['code'] ) {
|
|
|
|
$next_token = $this->phpcsFile->findNext(
|
|
Tokens::$emptyTokens,
|
|
( $this->tokens[ $next_token ]['bracket_closer'] + 1 ),
|
|
null,
|
|
true
|
|
);
|
|
}
|
|
|
|
if ( isset( Tokens::$comparisonTokens[ $this->tokens[ $next_token ]['code'] ] ) ) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Check what type of 'use' statement a token is part of.
|
|
*
|
|
* The T_USE token has multiple different uses:
|
|
*
|
|
* 1. In a closure: function () use ( $var ) {}
|
|
* 2. In a class, to import a trait: use Trait_Name
|
|
* 3. In a namespace, to import a class: use Some\Class;
|
|
*
|
|
* This function will check the token and return 'closure', 'trait', or 'class',
|
|
* based on which of these uses the use is being used for.
|
|
*
|
|
* @since 0.7.0
|
|
*
|
|
* @param int $stackPtr The position of the token to check.
|
|
*
|
|
* @return string The type of use.
|
|
*/
|
|
protected function get_use_type( $stackPtr ) {
|
|
|
|
// USE keywords inside closures.
|
|
$next = $this->phpcsFile->findNext( \T_WHITESPACE, ( $stackPtr + 1 ), null, true );
|
|
|
|
if ( \T_OPEN_PARENTHESIS === $this->tokens[ $next ]['code'] ) {
|
|
return 'closure';
|
|
}
|
|
|
|
// USE keywords for traits.
|
|
$valid_scopes = array(
|
|
'T_CLASS' => true,
|
|
'T_ANON_CLASS' => true,
|
|
'T_TRAIT' => true,
|
|
);
|
|
if ( false !== $this->valid_direct_scope( $stackPtr, $valid_scopes ) ) {
|
|
return 'trait';
|
|
}
|
|
|
|
// USE keywords for classes to import to a namespace.
|
|
return 'class';
|
|
}
|
|
|
|
/**
|
|
* Get the interpolated variable names from a string.
|
|
*
|
|
* Check if '$' is followed by a valid variable name, and that it is not preceded by an escape sequence.
|
|
*
|
|
* @since 0.9.0
|
|
*
|
|
* @param string $string The contents of a T_DOUBLE_QUOTED_STRING or T_HEREDOC token.
|
|
*
|
|
* @return array Variable names (without '$' sigil).
|
|
*/
|
|
protected function get_interpolated_variables( $string ) {
|
|
$variables = array();
|
|
if ( preg_match_all( '/(?P<backslashes>\\\\*)\$(?P<symbol>\w+)/', $string, $match_sets, \PREG_SET_ORDER ) ) {
|
|
foreach ( $match_sets as $matches ) {
|
|
if ( ! isset( $matches['backslashes'] ) || ( \strlen( $matches['backslashes'] ) % 2 ) === 0 ) {
|
|
$variables[] = $matches['symbol'];
|
|
}
|
|
}
|
|
}
|
|
return $variables;
|
|
}
|
|
|
|
/**
|
|
* Strip variables from an arbitrary double quoted/heredoc string.
|
|
*
|
|
* Intended for use with the contents of a T_DOUBLE_QUOTED_STRING or T_HEREDOC token.
|
|
*
|
|
* @since 0.14.0
|
|
*
|
|
* @param string $string The raw string.
|
|
*
|
|
* @return string String without variables in it.
|
|
*/
|
|
public function strip_interpolated_variables( $string ) {
|
|
if ( strpos( $string, '$' ) === false ) {
|
|
return $string;
|
|
}
|
|
|
|
return preg_replace( self::REGEX_COMPLEX_VARS, '', $string );
|
|
}
|
|
|
|
/**
|
|
* Checks if a function call has parameters.
|
|
*
|
|
* Expects to be passed the T_STRING stack pointer for the function call.
|
|
* If passed a T_STRING which is *not* a function call, the behaviour is unreliable.
|
|
*
|
|
* Extra feature: If passed an T_ARRAY or T_OPEN_SHORT_ARRAY stack pointer, it
|
|
* will detect whether the array has values or is empty.
|
|
*
|
|
* @link https://github.com/PHPCompatibility/PHPCompatibility/issues/120
|
|
* @link https://github.com/PHPCompatibility/PHPCompatibility/issues/152
|
|
*
|
|
* @since 0.11.0
|
|
*
|
|
* @param int $stackPtr The position of the function call token.
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function does_function_call_have_parameters( $stackPtr ) {
|
|
|
|
// Check for the existence of the token.
|
|
if ( false === isset( $this->tokens[ $stackPtr ] ) ) {
|
|
return false;
|
|
}
|
|
|
|
// Is this one of the tokens this function handles ?
|
|
if ( false === \in_array( $this->tokens[ $stackPtr ]['code'], array( \T_STRING, \T_ARRAY, \T_OPEN_SHORT_ARRAY ), true ) ) {
|
|
return false;
|
|
}
|
|
|
|
$next_non_empty = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $stackPtr + 1 ), null, true, null, true );
|
|
|
|
// Deal with short array syntax.
|
|
if ( 'T_OPEN_SHORT_ARRAY' === $this->tokens[ $stackPtr ]['type'] ) {
|
|
if ( false === isset( $this->tokens[ $stackPtr ]['bracket_closer'] ) ) {
|
|
return false;
|
|
}
|
|
|
|
if ( $next_non_empty === $this->tokens[ $stackPtr ]['bracket_closer'] ) {
|
|
// No parameters.
|
|
return false;
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Deal with function calls & long arrays.
|
|
// Next non-empty token should be the open parenthesis.
|
|
if ( false === $next_non_empty && \T_OPEN_PARENTHESIS !== $this->tokens[ $next_non_empty ]['code'] ) {
|
|
return false;
|
|
}
|
|
|
|
if ( false === isset( $this->tokens[ $next_non_empty ]['parenthesis_closer'] ) ) {
|
|
return false;
|
|
}
|
|
|
|
$close_parenthesis = $this->tokens[ $next_non_empty ]['parenthesis_closer'];
|
|
$next_next_non_empty = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $next_non_empty + 1 ), ( $close_parenthesis + 1 ), true );
|
|
|
|
if ( $next_next_non_empty === $close_parenthesis ) {
|
|
// No parameters.
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Count the number of parameters a function call has been passed.
|
|
*
|
|
* Expects to be passed the T_STRING stack pointer for the function call.
|
|
* If passed a T_STRING which is *not* a function call, the behaviour is unreliable.
|
|
*
|
|
* Extra feature: If passed an T_ARRAY or T_OPEN_SHORT_ARRAY stack pointer,
|
|
* it will return the number of values in the array.
|
|
*
|
|
* @link https://github.com/PHPCompatibility/PHPCompatibility/issues/111
|
|
* @link https://github.com/PHPCompatibility/PHPCompatibility/issues/114
|
|
* @link https://github.com/PHPCompatibility/PHPCompatibility/issues/151
|
|
*
|
|
* @since 0.11.0
|
|
*
|
|
* @param int $stackPtr The position of the function call token.
|
|
*
|
|
* @return int
|
|
*/
|
|
public function get_function_call_parameter_count( $stackPtr ) {
|
|
if ( false === $this->does_function_call_have_parameters( $stackPtr ) ) {
|
|
return 0;
|
|
}
|
|
|
|
return \count( $this->get_function_call_parameters( $stackPtr ) );
|
|
}
|
|
|
|
/**
|
|
* Get information on all parameters passed to a function call.
|
|
*
|
|
* Expects to be passed the T_STRING stack pointer for the function call.
|
|
* If passed a T_STRING which is *not* a function call, the behaviour is unreliable.
|
|
*
|
|
* Extra feature: If passed an T_ARRAY or T_OPEN_SHORT_ARRAY stack pointer,
|
|
* it will tokenize the values / key/value pairs contained in the array call.
|
|
*
|
|
* @since 0.11.0
|
|
*
|
|
* @param int $stackPtr The position of the function call token.
|
|
*
|
|
* @return array Multi-dimentional array with parameter details or
|
|
* empty array if no parameters are found.
|
|
*
|
|
* @type int $position 1-based index position of the parameter. {
|
|
* @type int $start Stack pointer for the start of the parameter.
|
|
* @type int $end Stack pointer for the end of parameter.
|
|
* @type int $raw Trimmed raw parameter content.
|
|
* }
|
|
*/
|
|
public function get_function_call_parameters( $stackPtr ) {
|
|
if ( false === $this->does_function_call_have_parameters( $stackPtr ) ) {
|
|
return array();
|
|
}
|
|
|
|
/*
|
|
* Ok, we know we have a T_STRING, T_ARRAY or T_OPEN_SHORT_ARRAY with parameters
|
|
* and valid open & close brackets/parenthesis.
|
|
*/
|
|
|
|
// Mark the beginning and end tokens.
|
|
if ( 'T_OPEN_SHORT_ARRAY' === $this->tokens[ $stackPtr ]['type'] ) {
|
|
$opener = $stackPtr;
|
|
$closer = $this->tokens[ $stackPtr ]['bracket_closer'];
|
|
|
|
$nestedParenthesisCount = 0;
|
|
} else {
|
|
$opener = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $stackPtr + 1 ), null, true, null, true );
|
|
$closer = $this->tokens[ $opener ]['parenthesis_closer'];
|
|
|
|
$nestedParenthesisCount = 1;
|
|
}
|
|
|
|
// Which nesting level is the one we are interested in ?
|
|
if ( isset( $this->tokens[ $opener ]['nested_parenthesis'] ) ) {
|
|
$nestedParenthesisCount += \count( $this->tokens[ $opener ]['nested_parenthesis'] );
|
|
}
|
|
|
|
$parameters = array();
|
|
$next_comma = $opener;
|
|
$param_start = ( $opener + 1 );
|
|
$cnt = 1;
|
|
while ( $next_comma = $this->phpcsFile->findNext( array( \T_COMMA, $this->tokens[ $closer ]['code'], \T_OPEN_SHORT_ARRAY, \T_CLOSURE ), ( $next_comma + 1 ), ( $closer + 1 ) ) ) {
|
|
// Ignore anything within short array definition brackets.
|
|
if ( 'T_OPEN_SHORT_ARRAY' === $this->tokens[ $next_comma ]['type']
|
|
&& ( isset( $this->tokens[ $next_comma ]['bracket_opener'] )
|
|
&& $this->tokens[ $next_comma ]['bracket_opener'] === $next_comma )
|
|
&& isset( $this->tokens[ $next_comma ]['bracket_closer'] )
|
|
) {
|
|
// Skip forward to the end of the short array definition.
|
|
$next_comma = $this->tokens[ $next_comma ]['bracket_closer'];
|
|
continue;
|
|
}
|
|
|
|
// Skip past closures passed as function parameters.
|
|
if ( 'T_CLOSURE' === $this->tokens[ $next_comma ]['type']
|
|
&& ( isset( $this->tokens[ $next_comma ]['scope_condition'] )
|
|
&& $this->tokens[ $next_comma ]['scope_condition'] === $next_comma )
|
|
&& isset( $this->tokens[ $next_comma ]['scope_closer'] )
|
|
) {
|
|
// Skip forward to the end of the closure declaration.
|
|
$next_comma = $this->tokens[ $next_comma ]['scope_closer'];
|
|
continue;
|
|
}
|
|
|
|
// Ignore comma's at a lower nesting level.
|
|
if ( \T_COMMA === $this->tokens[ $next_comma ]['code']
|
|
&& isset( $this->tokens[ $next_comma ]['nested_parenthesis'] )
|
|
&& \count( $this->tokens[ $next_comma ]['nested_parenthesis'] ) !== $nestedParenthesisCount
|
|
) {
|
|
continue;
|
|
}
|
|
|
|
// Ignore closing parenthesis/bracket if not 'ours'.
|
|
if ( $this->tokens[ $next_comma ]['type'] === $this->tokens[ $closer ]['type'] && $next_comma !== $closer ) {
|
|
continue;
|
|
}
|
|
|
|
// Ok, we've reached the end of the parameter.
|
|
$parameters[ $cnt ]['start'] = $param_start;
|
|
$parameters[ $cnt ]['end'] = ( $next_comma - 1 );
|
|
$parameters[ $cnt ]['raw'] = trim( $this->phpcsFile->getTokensAsString( $param_start, ( $next_comma - $param_start ) ) );
|
|
|
|
/*
|
|
* Check if there are more tokens before the closing parenthesis.
|
|
* Prevents code like the following from setting a third parameter:
|
|
* functionCall( $param1, $param2, );
|
|
*/
|
|
$has_next_param = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $next_comma + 1 ), $closer, true, null, true );
|
|
if ( false === $has_next_param ) {
|
|
break;
|
|
}
|
|
|
|
// Prepare for the next parameter.
|
|
$param_start = ( $next_comma + 1 );
|
|
$cnt++;
|
|
}
|
|
|
|
return $parameters;
|
|
}
|
|
|
|
/**
|
|
* Get information on a specific parameter passed to a function call.
|
|
*
|
|
* Expects to be passed the T_STRING stack pointer for the function call.
|
|
* If passed a T_STRING which is *not* a function call, the behaviour is unreliable.
|
|
*
|
|
* Will return a array with the start token pointer, end token pointer and the raw value
|
|
* of the parameter at a specific offset.
|
|
* If the specified parameter is not found, will return false.
|
|
*
|
|
* @since 0.11.0
|
|
*
|
|
* @param int $stackPtr The position of the function call token.
|
|
* @param int $param_offset The 1-based index position of the parameter to retrieve.
|
|
*
|
|
* @return array|false
|
|
*/
|
|
public function get_function_call_parameter( $stackPtr, $param_offset ) {
|
|
$parameters = $this->get_function_call_parameters( $stackPtr );
|
|
|
|
if ( false === isset( $parameters[ $param_offset ] ) ) {
|
|
return false;
|
|
}
|
|
|
|
return $parameters[ $param_offset ];
|
|
}
|
|
|
|
/**
|
|
* Find the array opener & closer based on a T_ARRAY or T_OPEN_SHORT_ARRAY token.
|
|
*
|
|
* @since 0.12.0
|
|
*
|
|
* @param int $stackPtr The stack pointer to the array token.
|
|
*
|
|
* @return array|bool Array with two keys `opener`, `closer` or false if
|
|
* either or these could not be determined.
|
|
*/
|
|
protected function find_array_open_close( $stackPtr ) {
|
|
/*
|
|
* Determine the array opener & closer.
|
|
*/
|
|
if ( \T_ARRAY === $this->tokens[ $stackPtr ]['code'] ) {
|
|
if ( isset( $this->tokens[ $stackPtr ]['parenthesis_opener'] ) ) {
|
|
$opener = $this->tokens[ $stackPtr ]['parenthesis_opener'];
|
|
|
|
if ( isset( $this->tokens[ $opener ]['parenthesis_closer'] ) ) {
|
|
$closer = $this->tokens[ $opener ]['parenthesis_closer'];
|
|
}
|
|
}
|
|
} else {
|
|
// Short array syntax.
|
|
$opener = $stackPtr;
|
|
|
|
if ( isset( $this->tokens[ $stackPtr ]['bracket_closer'] ) ) {
|
|
$closer = $this->tokens[ $stackPtr ]['bracket_closer'];
|
|
}
|
|
}
|
|
|
|
if ( isset( $opener, $closer ) ) {
|
|
return array(
|
|
'opener' => $opener,
|
|
'closer' => $closer,
|
|
);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Determine the namespace name an arbitrary token lives in.
|
|
*
|
|
* @since 0.10.0
|
|
* @since 0.12.0 Moved from the WordPress_AbstractClassRestrictionsSniff to this sniff.
|
|
*
|
|
* @param int $stackPtr The token position for which to determine the namespace.
|
|
*
|
|
* @return string Namespace name or empty string if it couldn't be determined or no namespace applies.
|
|
*/
|
|
public function determine_namespace( $stackPtr ) {
|
|
|
|
// Check for the existence of the token.
|
|
if ( ! isset( $this->tokens[ $stackPtr ] ) ) {
|
|
return '';
|
|
}
|
|
|
|
// Check for scoped namespace {}.
|
|
if ( ! empty( $this->tokens[ $stackPtr ]['conditions'] ) ) {
|
|
$namespacePtr = $this->phpcsFile->getCondition( $stackPtr, \T_NAMESPACE );
|
|
if ( false !== $namespacePtr ) {
|
|
$namespace = $this->get_declared_namespace_name( $namespacePtr );
|
|
if ( false !== $namespace ) {
|
|
return $namespace;
|
|
}
|
|
|
|
// We are in a scoped namespace, but couldn't determine the name.
|
|
// Searching for a global namespace is futile.
|
|
return '';
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Not in a scoped namespace, so let's see if we can find a non-scoped namespace instead.
|
|
* Keeping in mind that:
|
|
* - there can be multiple non-scoped namespaces in a file (bad practice, but it happens).
|
|
* - the namespace keyword can also be used as part of a function/method call and such.
|
|
* - that a non-named namespace resolves to the global namespace.
|
|
*/
|
|
$previousNSToken = $stackPtr;
|
|
$namespace = false;
|
|
do {
|
|
$previousNSToken = $this->phpcsFile->findPrevious( \T_NAMESPACE, ( $previousNSToken - 1 ) );
|
|
|
|
// Stop if we encounter a scoped namespace declaration as we already know we're not in one.
|
|
if ( ! empty( $this->tokens[ $previousNSToken ]['scope_condition'] )
|
|
&& $this->tokens[ $previousNSToken ]['scope_condition'] === $previousNSToken
|
|
) {
|
|
break;
|
|
}
|
|
|
|
$namespace = $this->get_declared_namespace_name( $previousNSToken );
|
|
|
|
} while ( false === $namespace && false !== $previousNSToken );
|
|
|
|
// If we still haven't got a namespace, return an empty string.
|
|
if ( false === $namespace ) {
|
|
return '';
|
|
}
|
|
|
|
return $namespace;
|
|
}
|
|
|
|
/**
|
|
* Get the complete namespace name for a namespace declaration.
|
|
*
|
|
* For hierarchical namespaces, the name will be composed of several tokens,
|
|
* i.e. MyProject\Sub\Level which will be returned together as one string.
|
|
*
|
|
* @since 0.12.0 A lesser variant of this method previously existed in the
|
|
* WordPress_AbstractClassRestrictionsSniff.
|
|
*
|
|
* @param int|bool $stackPtr The position of a T_NAMESPACE token.
|
|
*
|
|
* @return string|false Namespace name or false if not a namespace declaration.
|
|
* Namespace name can be an empty string for global namespace declaration.
|
|
*/
|
|
public function get_declared_namespace_name( $stackPtr ) {
|
|
|
|
// Check for the existence of the token.
|
|
if ( false === $stackPtr || ! isset( $this->tokens[ $stackPtr ] ) ) {
|
|
return false;
|
|
}
|
|
|
|
if ( \T_NAMESPACE !== $this->tokens[ $stackPtr ]['code'] ) {
|
|
return false;
|
|
}
|
|
|
|
$nextToken = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $stackPtr + 1 ), null, true, null, true );
|
|
if ( \T_NS_SEPARATOR === $this->tokens[ $nextToken ]['code'] ) {
|
|
// Not a namespace declaration, but use of, i.e. `namespace\someFunction();`.
|
|
return false;
|
|
}
|
|
|
|
if ( \T_OPEN_CURLY_BRACKET === $this->tokens[ $nextToken ]['code'] ) {
|
|
// Declaration for global namespace when using multiple namespaces in a file.
|
|
// I.e.: `namespace {}`.
|
|
return '';
|
|
}
|
|
|
|
// Ok, this should be a namespace declaration, so get all the parts together.
|
|
$acceptedTokens = array(
|
|
\T_STRING => true,
|
|
\T_NS_SEPARATOR => true,
|
|
);
|
|
$validTokens = $acceptedTokens + Tokens::$emptyTokens;
|
|
|
|
$namespaceName = '';
|
|
while ( isset( $validTokens[ $this->tokens[ $nextToken ]['code'] ] ) ) {
|
|
if ( isset( $acceptedTokens[ $this->tokens[ $nextToken ]['code'] ] ) ) {
|
|
$namespaceName .= trim( $this->tokens[ $nextToken ]['content'] );
|
|
}
|
|
++$nextToken;
|
|
}
|
|
|
|
return $namespaceName;
|
|
}
|
|
|
|
/**
|
|
* Check if a content string contains a specific html open tag.
|
|
*
|
|
* @since 0.11.0
|
|
* @since 0.13.0 No longer allows for the PHP 5.2 bug for which the function was
|
|
* originally created.
|
|
* @since 0.13.0 The $stackPtr parameter is now optional. Either that or the
|
|
* $content parameter has to be passed.
|
|
* @deprecated 1.0.0 This method is only used by deprecated sniffs.
|
|
*
|
|
* @param string $tag_name The name of the HTML tag without brackets. So if
|
|
* searching for '<span...', this would be 'span'.
|
|
* @param int $stackPtr Optional. The position of the current token in the
|
|
* token stack.
|
|
* This parameter needs to be passed if no $content is
|
|
* passed.
|
|
* @param string $content Optionally, the current content string, might be a
|
|
* substring of the original string.
|
|
* Defaults to `false` to distinguish between a passed
|
|
* empty string and not passing the $content string.
|
|
*
|
|
* @return bool True if the string contains an <tag_name> open tag, false otherwise.
|
|
*/
|
|
public function has_html_open_tag( $tag_name, $stackPtr = null, $content = false ) {
|
|
if ( false === $content && isset( $stackPtr ) ) {
|
|
$content = $this->tokens[ $stackPtr ]['content'];
|
|
}
|
|
|
|
if ( ! empty( $content ) && false !== strpos( $content, '<' . $tag_name ) ) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Check whether a T_CONST token is a class constant declaration.
|
|
*
|
|
* @since 0.14.0
|
|
*
|
|
* @param int $stackPtr The position in the stack of the T_CONST token to verify.
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function is_class_constant( $stackPtr ) {
|
|
if ( ! isset( $this->tokens[ $stackPtr ] ) || \T_CONST !== $this->tokens[ $stackPtr ]['code'] ) {
|
|
return false;
|
|
}
|
|
|
|
// Note: traits can not declare constants.
|
|
$valid_scopes = array(
|
|
'T_CLASS' => true,
|
|
'T_ANON_CLASS' => true,
|
|
'T_INTERFACE' => true,
|
|
);
|
|
|
|
return is_int( $this->valid_direct_scope( $stackPtr, $valid_scopes ) );
|
|
}
|
|
|
|
/**
|
|
* Check whether a T_VARIABLE token is a class property declaration.
|
|
*
|
|
* @since 0.14.0
|
|
*
|
|
* @param int $stackPtr The position in the stack of the T_VARIABLE token to verify.
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function is_class_property( $stackPtr ) {
|
|
if ( ! isset( $this->tokens[ $stackPtr ] ) || \T_VARIABLE !== $this->tokens[ $stackPtr ]['code'] ) {
|
|
return false;
|
|
}
|
|
|
|
// Note: interfaces can not declare properties.
|
|
$valid_scopes = array(
|
|
'T_CLASS' => true,
|
|
'T_ANON_CLASS' => true,
|
|
'T_TRAIT' => true,
|
|
);
|
|
|
|
$scopePtr = $this->valid_direct_scope( $stackPtr, $valid_scopes );
|
|
if ( false !== $scopePtr ) {
|
|
// Make sure it's not a method parameter.
|
|
if ( empty( $this->tokens[ $stackPtr ]['nested_parenthesis'] ) ) {
|
|
return true;
|
|
} else {
|
|
$parenthesis = array_keys( $this->tokens[ $stackPtr ]['nested_parenthesis'] );
|
|
$deepest_open = array_pop( $parenthesis );
|
|
if ( $deepest_open < $scopePtr
|
|
|| isset( $this->tokens[ $deepest_open ]['parenthesis_owner'] ) === false
|
|
|| \T_FUNCTION !== $this->tokens[ $this->tokens[ $deepest_open ]['parenthesis_owner'] ]['code']
|
|
) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Check whether the direct wrapping scope of a token is within a limited set of
|
|
* acceptable tokens.
|
|
*
|
|
* Used to check, for instance, if a T_CONST is a class constant.
|
|
*
|
|
* @since 0.14.0
|
|
*
|
|
* @param int $stackPtr The position in the stack of the token to verify.
|
|
* @param array $valid_scopes Array of token types.
|
|
* Keys should be the token types in string format
|
|
* to allow for newer token types.
|
|
* Value is irrelevant.
|
|
*
|
|
* @return int|bool StackPtr to the scope if valid, false otherwise.
|
|
*/
|
|
protected function valid_direct_scope( $stackPtr, array $valid_scopes ) {
|
|
if ( empty( $this->tokens[ $stackPtr ]['conditions'] ) ) {
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Check only the direct wrapping scope of the token.
|
|
*/
|
|
$conditions = array_keys( $this->tokens[ $stackPtr ]['conditions'] );
|
|
$ptr = array_pop( $conditions );
|
|
|
|
if ( ! isset( $this->tokens[ $ptr ] ) ) {
|
|
return false;
|
|
}
|
|
|
|
if ( isset( $valid_scopes[ $this->tokens[ $ptr ]['type'] ] ) ) {
|
|
return $ptr;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Checks whether this is a call to a $wpdb method that we want to sniff.
|
|
*
|
|
* If available in the child class, the $methodPtr, $i and $end properties are
|
|
* automatically set to correspond to the start and end of the method call.
|
|
* The $i property is also set if this is not a method call but rather the
|
|
* use of a $wpdb property.
|
|
*
|
|
* @since 0.8.0
|
|
* @since 0.9.0 The return value is now always boolean. The $end and $i member
|
|
* vars are automatically updated.
|
|
* @since 0.14.0 Moved this method from the `PreparedSQL` sniff to the base WP sniff.
|
|
*
|
|
* {@internal This method should probably be refactored.}}
|
|
*
|
|
* @param int $stackPtr The index of the $wpdb variable.
|
|
* @param array $target_methods Array of methods. Key(s) should be method name.
|
|
*
|
|
* @return bool Whether this is a $wpdb method call.
|
|
*/
|
|
protected function is_wpdb_method_call( $stackPtr, $target_methods ) {
|
|
|
|
// Check for wpdb.
|
|
if ( ( \T_VARIABLE === $this->tokens[ $stackPtr ]['code'] && '$wpdb' !== $this->tokens[ $stackPtr ]['content'] )
|
|
|| ( \T_STRING === $this->tokens[ $stackPtr ]['code'] && 'wpdb' !== $this->tokens[ $stackPtr ]['content'] )
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
// Check that this is a method call.
|
|
$is_object_call = $this->phpcsFile->findNext(
|
|
array( \T_OBJECT_OPERATOR, \T_DOUBLE_COLON ),
|
|
( $stackPtr + 1 ),
|
|
null,
|
|
false,
|
|
null,
|
|
true
|
|
);
|
|
if ( false === $is_object_call ) {
|
|
return false;
|
|
}
|
|
|
|
$methodPtr = $this->phpcsFile->findNext( \T_WHITESPACE, ( $is_object_call + 1 ), null, true, null, true );
|
|
if ( false === $methodPtr ) {
|
|
return false;
|
|
}
|
|
|
|
if ( \T_STRING === $this->tokens[ $methodPtr ]['code'] && property_exists( $this, 'methodPtr' ) ) {
|
|
$this->methodPtr = $methodPtr;
|
|
}
|
|
|
|
// Find the opening parenthesis.
|
|
$opening_paren = $this->phpcsFile->findNext( \T_WHITESPACE, ( $methodPtr + 1 ), null, true, null, true );
|
|
|
|
if ( false === $opening_paren ) {
|
|
return false;
|
|
}
|
|
|
|
if ( property_exists( $this, 'i' ) ) {
|
|
$this->i = $opening_paren;
|
|
}
|
|
|
|
if ( \T_OPEN_PARENTHESIS !== $this->tokens[ $opening_paren ]['code']
|
|
|| ! isset( $this->tokens[ $opening_paren ]['parenthesis_closer'] )
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
// Check that this is one of the methods that we are interested in.
|
|
if ( ! isset( $target_methods[ $this->tokens[ $methodPtr ]['content'] ] ) ) {
|
|
return false;
|
|
}
|
|
|
|
// Find the end of the first parameter.
|
|
$end = $this->phpcsFile->findEndOfStatement( $opening_paren + 1 );
|
|
|
|
if ( \T_COMMA !== $this->tokens[ $end ]['code'] ) {
|
|
++$end;
|
|
}
|
|
|
|
if ( property_exists( $this, 'end' ) ) {
|
|
$this->end = $end;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Determine whether an arbitrary T_STRING token is the use of a global constant.
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @param int $stackPtr The position of the function call token.
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function is_use_of_global_constant( $stackPtr ) {
|
|
// Check for the existence of the token.
|
|
if ( ! isset( $this->tokens[ $stackPtr ] ) ) {
|
|
return false;
|
|
}
|
|
|
|
// Is this one of the tokens this function handles ?
|
|
if ( \T_STRING !== $this->tokens[ $stackPtr ]['code'] ) {
|
|
return false;
|
|
}
|
|
|
|
$next = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $stackPtr + 1 ), null, true );
|
|
if ( false !== $next
|
|
&& ( \T_OPEN_PARENTHESIS === $this->tokens[ $next ]['code']
|
|
|| \T_DOUBLE_COLON === $this->tokens[ $next ]['code'] )
|
|
) {
|
|
// Function call or declaration.
|
|
return false;
|
|
}
|
|
|
|
// Array of tokens which if found preceding the $stackPtr indicate that a T_STRING is not a global constant.
|
|
$tokens_to_ignore = array(
|
|
'T_NAMESPACE' => true,
|
|
'T_USE' => true,
|
|
'T_CLASS' => true,
|
|
'T_TRAIT' => true,
|
|
'T_INTERFACE' => true,
|
|
'T_EXTENDS' => true,
|
|
'T_IMPLEMENTS' => true,
|
|
'T_NEW' => true,
|
|
'T_FUNCTION' => true,
|
|
'T_DOUBLE_COLON' => true,
|
|
'T_OBJECT_OPERATOR' => true,
|
|
'T_INSTANCEOF' => true,
|
|
'T_INSTEADOF' => true,
|
|
'T_GOTO' => true,
|
|
'T_AS' => true,
|
|
'T_PUBLIC' => true,
|
|
'T_PROTECTED' => true,
|
|
'T_PRIVATE' => true,
|
|
);
|
|
|
|
$prev = $this->phpcsFile->findPrevious( Tokens::$emptyTokens, ( $stackPtr - 1 ), null, true );
|
|
if ( false !== $prev
|
|
&& isset( $tokens_to_ignore[ $this->tokens[ $prev ]['type'] ] )
|
|
) {
|
|
// Not the use of a constant.
|
|
return false;
|
|
}
|
|
|
|
if ( false !== $prev
|
|
&& \T_NS_SEPARATOR === $this->tokens[ $prev ]['code']
|
|
&& \T_STRING === $this->tokens[ ( $prev - 1 ) ]['code']
|
|
) {
|
|
// Namespaced constant of the same name.
|
|
return false;
|
|
}
|
|
|
|
if ( false !== $prev
|
|
&& \T_CONST === $this->tokens[ $prev ]['code']
|
|
&& $this->is_class_constant( $prev )
|
|
) {
|
|
// Class constant declaration of the same name.
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Deal with a number of variations of use statements.
|
|
*/
|
|
for ( $i = $stackPtr; $i > 0; $i-- ) {
|
|
if ( $this->tokens[ $i ]['line'] !== $this->tokens[ $stackPtr ]['line'] ) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
$firstOnLine = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $i + 1 ), null, true );
|
|
if ( false !== $firstOnLine && \T_USE === $this->tokens[ $firstOnLine ]['code'] ) {
|
|
$nextOnLine = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $firstOnLine + 1 ), null, true );
|
|
if ( false !== $nextOnLine ) {
|
|
if ( \T_STRING === $this->tokens[ $nextOnLine ]['code']
|
|
&& 'const' === $this->tokens[ $nextOnLine ]['content']
|
|
) {
|
|
$hasNsSep = $this->phpcsFile->findNext( \T_NS_SEPARATOR, ( $nextOnLine + 1 ), $stackPtr );
|
|
if ( false !== $hasNsSep ) {
|
|
// Namespaced const (group) use statement.
|
|
return false;
|
|
}
|
|
} else {
|
|
// Not a const use statement.
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Determine if a variable is in the `as $key => $value` part of a foreach condition.
|
|
*
|
|
* @since 1.0.0
|
|
* @since 1.1.0 Moved from the PrefixAllGlobals sniff to the Sniff base class.
|
|
*
|
|
* @param int $stackPtr Pointer to the variable.
|
|
*
|
|
* @return bool True if it is. False otherwise.
|
|
*/
|
|
protected function is_foreach_as( $stackPtr ) {
|
|
if ( ! isset( $this->tokens[ $stackPtr ]['nested_parenthesis'] ) ) {
|
|
return false;
|
|
}
|
|
|
|
$nested_parenthesis = $this->tokens[ $stackPtr ]['nested_parenthesis'];
|
|
$close_parenthesis = end( $nested_parenthesis );
|
|
$open_parenthesis = key( $nested_parenthesis );
|
|
if ( ! isset( $this->tokens[ $close_parenthesis ]['parenthesis_owner'] ) ) {
|
|
return false;
|
|
}
|
|
|
|
if ( \T_FOREACH !== $this->tokens[ $this->tokens[ $close_parenthesis ]['parenthesis_owner'] ]['code'] ) {
|
|
return false;
|
|
}
|
|
|
|
$as_ptr = $this->phpcsFile->findNext( \T_AS, ( $open_parenthesis + 1 ), $close_parenthesis );
|
|
if ( false === $as_ptr ) {
|
|
// Should never happen.
|
|
return false;
|
|
}
|
|
|
|
return ( $stackPtr > $as_ptr );
|
|
}
|
|
|
|
}
|