PluginProbe
Plugin Check (PCP) / 1.5.0
Plugin Check (PCP) v1.5.0
2.1.0 trunk 0.1 0.2.0 0.2.1 0.2.2 0.2.3 1.0.0 1.0.1 1.0.2 1.1.0 1.2.0 1.3.0 1.3.1 1.4.0 1.5.0 1.6.0 1.7.0 1.8.0 1.9.0 2.0.0 ci-artifacts
plugin-check / includes / Checker / Checks / Abstract_File_Check.php

Abstract_File_Check.php in Plugin Check (PCP) 1.5.0, at includes/Checker/Checks/Abstract_File_Check.php

322 lines 9.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Class WordPress\Plugin_Check\Checker\Checks\Abstract_File_Check
4 *
5 * @package plugin-check
6 */
7
8 namespace WordPress\Plugin_Check\Checker\Checks;
9
10 use Exception;
11 use RecursiveDirectoryIterator;
12 use RecursiveIteratorIterator;
13 use WordPress\Plugin_Check\Checker\Check_Context;
14 use WordPress\Plugin_Check\Checker\Check_Result;
15 use WordPress\Plugin_Check\Checker\Static_Check;
16 use WordPress\Plugin_Check\Utilities\Plugin_Request_Utility;
17
18 /**
19 * Base class for a check that inspects the plugin's files and contents.
20 *
21 * @since 1.0.0
22 */
23 abstract class Abstract_File_Check implements Static_Check {
24
25 /**
26 * Internal cache for plugin-specific file lists.
27 *
28 * @since 1.0.0
29 * @var array
30 */
31 private static $file_list_cache = array();
32
33 /**
34 * Internal cache for file contents.
35 *
36 * @since 1.0.0
37 * @var array
38 */
39 private static $file_contents_cache = array();
40
41 /**
42 * Amends the given result by running the check on the associated plugin.
43 *
44 * @since 1.0.0
45 *
46 * @param Check_Result $result The check result to amend, including the plugin context to check.
47 *
48 * @throws Exception Thrown when the check fails with a critical error (unrelated to any errors detected as part of
49 * the check).
50 */
51 final public function run( Check_Result $result ) {
52 $files = self::get_files( $result->plugin() );
53 $this->check_files( $result, $files );
54 }
55
56 /**
57 * Amends the given result by running the check on the given list of files.
58 *
59 * @since 1.0.0
60 *
61 * @param Check_Result $result The check result to amend, including the plugin context to check.
62 * @param array $files List of absolute file paths.
63 *
64 * @throws Exception Thrown when the check fails with a critical error (unrelated to any errors detected as part of
65 * the check).
66 */
67 abstract protected function check_files( Check_Result $result, array $files );
68
69 /**
70 * Filters a given list of files to only contain those with specific extension.
71 *
72 * @since 1.0.0
73 *
74 * @param array $files List of absolute file paths.
75 * @param string $extension File extension to match.
76 * @return array Filtered $files list.
77 */
78 final protected static function filter_files_by_extension( array $files, $extension ) {
79 return self::filter_files_by_extensions( $files, array( $extension ) );
80 }
81
82 /**
83 * Filters a given list of files to only contain those with specific extensions.
84 *
85 * @since 1.0.0
86 *
87 * @param array $files List of absolute file paths.
88 * @param array $extensions List of file extensions to match.
89 * @return array Filtered $files list.
90 */
91 final protected static function filter_files_by_extensions( array $files, array $extensions ) {
92 // Inverse the array to speed up lookup.
93 $lookup = array_flip( $extensions );
94
95 return array_values(
96 array_filter(
97 $files,
98 static function ( $file ) use ( $lookup ) {
99 return isset( $lookup[ pathinfo( $file, PATHINFO_EXTENSION ) ] );
100 }
101 )
102 );
103 }
104
105 /**
106 * Filters a given list of files to only contain those where the file name matches the given regular expression.
107 *
108 * @since 1.0.0
109 *
110 * @param array $files List of absolute file paths.
111 * @param string $regex Regular expression for file paths to match.
112 * @return array Filtered $files list.
113 */
114 final protected static function filter_files_by_regex( array $files, $regex ) {
115 return preg_grep( $regex, $files );
116 }
117
118 /**
119 * Performs a regular expression match on the file contents of the given list of files.
120 *
121 * This is a wrapper around the native `preg_match()` function that will match the first occurrence within the
122 * list of files.
123 *
124 * @since 1.0.0
125 *
126 * @param string $pattern The pattern to search for.
127 * @param array $files List of absolute file paths.
128 * @param array $matches Optional. Array to store the matches, passed by reference. Similar to `preg_match()`,
129 * `$matches[0]` will contain the text that matched the full pattern, `$matches[1]` will
130 * have the text that matched the first captured parenthesized subpattern, and so on.
131 * @return string|bool File path if a match was found, false otherwise.
132 */
133 final protected static function file_preg_match( $pattern, array $files, ?array &$matches = null ) {
134 foreach ( $files as $file ) {
135 $contents = self::file_get_contents( $file );
136 if ( preg_match( $pattern, $contents, $m ) ) {
137 $matches = $m;
138 return $file;
139 }
140 }
141 return false;
142 }
143
144 /**
145 * Returns matched files performing a regular expression match on the file contents of the given list of files.
146 *
147 * @since 1.1.0
148 *
149 * @param string $pattern The pattern to search for.
150 * @param array $files List of absolute file paths.
151 * @return array|bool Array of file paths and matched string/pattern if matches were found, false otherwise.
152 */
153 final protected static function files_preg_match( $pattern, array $files ) {
154 $matched_files = array();
155
156 foreach ( $files as $file ) {
157 $matches = array();
158
159 $matched_file_name = self::file_preg_match( $pattern, array( $file ), $matches );
160
161 if ( false !== $matched_file_name ) {
162 $matched_files[] = array( $matched_file_name, $matches[0] );
163 }
164 }
165
166 return count( $matched_files ) > 0 ? $matched_files : false;
167 }
168
169 /**
170 * Returns matched files performing a regular expression match on the file contents of the given list of files with line and column information.
171 *
172 * @since 1.1.0
173 *
174 * @param string $pattern The pattern to search for.
175 * @param array $files List of absolute file paths.
176 * @return array|bool Array of file paths and matched string/pattern if matches were found, false otherwise.
177 */
178 final protected static function files_preg_match_all( $pattern, array $files ) {
179 $matched_files = array();
180
181 foreach ( $files as $file ) {
182 $matches = array();
183
184 $contents = self::file_get_contents( $file );
185
186 preg_match_all( $pattern, $contents, $matches, PREG_OFFSET_CAPTURE );
187
188 if ( is_array( $matches ) && ! empty( $matches ) ) {
189 foreach ( $matches[0] as $match ) {
190 $line = 0;
191 $column = 0;
192
193 if ( 0 === $match[1] ) {
194 $line = 1;
195 $column = 1;
196 } else {
197 list( $before ) = str_split( $contents, $match[1] );
198
199 $exploded = explode( PHP_EOL, $before );
200 $last_item = end( $exploded );
201
202 $line = count( $exploded );
203 $column = strlen( $last_item ) + 1;
204 }
205
206 $matched_files[] = array(
207 'file' => $file,
208 'line' => $line,
209 'column' => $column,
210 );
211 }
212 }
213 }
214
215 return count( $matched_files ) > 0 ? $matched_files : false;
216 }
217
218 /**
219 * Performs a check indicating if the needle is contained in the file contents of the given list of files.
220 *
221 * This is a wrapper around the native `str_contains()` function that will find the needle within the list of
222 * files.
223 *
224 * @since 1.0.0
225 *
226 * @param array $files List of absolute file paths.
227 * @param string $needle The substring to search for.
228 * @return string|bool File path if needle was found, false otherwise.
229 */
230 final protected static function file_str_contains( array $files, $needle ) {
231 foreach ( $files as $file ) {
232 $contents = self::file_get_contents( $file );
233 if ( str_contains( $contents, $needle ) ) {
234 return $file;
235 }
236 }
237 return false;
238 }
239
240 /**
241 * Gets the contents of the given file.
242 *
243 * This is effectively a caching wrapper around the native `file_get_contents()` function.
244 *
245 * @since 1.0.0
246 *
247 * @param string $file The file name.
248 * @return string The file contents.
249 */
250 private static function file_get_contents( $file ) {
251 if ( isset( self::$file_contents_cache[ $file ] ) ) {
252 return self::$file_contents_cache[ $file ];
253 }
254
255 // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
256 self::$file_contents_cache[ $file ] = file_get_contents( $file );
257
258 return self::$file_contents_cache[ $file ];
259 }
260
261 /**
262 * Gets the list of all files that are part of the given plugin.
263 *
264 * @since 1.0.0
265 *
266 * @param Check_Context $plugin Context for the plugin to check.
267 * @return array List of absolute file paths.
268 */
269 private static function get_files( Check_Context $plugin ) {
270 $location = wp_normalize_path( $plugin->location() );
271
272 if ( isset( self::$file_list_cache[ $location ] ) ) {
273 return self::$file_list_cache[ $location ];
274 }
275
276 self::$file_list_cache[ $location ] = array();
277
278 // If the location is a plugin folder, get all its files.
279 // Otherwise, it is a single-file plugin.
280 if ( $plugin->is_single_file_plugin() ) {
281 self::$file_list_cache[ $location ][] = $location;
282 } else {
283 $iterator = new RecursiveIteratorIterator( new RecursiveDirectoryIterator( $location ) );
284 foreach ( $iterator as $file ) {
285 if ( ! $file->isFile() ) {
286 continue;
287 }
288
289 $file_path = wp_normalize_path( $file->getPathname() );
290
291 $directories_to_ignore = Plugin_Request_Utility::get_directories_to_ignore();
292
293 // Flag to check if the file should be included or not.
294 $include_file = true;
295
296 foreach ( $directories_to_ignore as $directory ) {
297 // Check if the current file belongs to the directory you want to ignore.
298 if ( false !== strpos( $file_path, '/' . $directory . '/' ) ) {
299 $include_file = false;
300 break; // Skip the file if it matches any ignored directory.
301 }
302 }
303
304 $files_to_ignore = Plugin_Request_Utility::get_files_to_ignore();
305
306 foreach ( $files_to_ignore as $ignore_file ) {
307 if ( str_ends_with( $file_path, "/$ignore_file" ) ) {
308 $include_file = false;
309 break;
310 }
311 }
312
313 if ( $include_file ) {
314 self::$file_list_cache[ $location ][] = $file_path;
315 }
316 }
317 }
318
319 return self::$file_list_cache[ $location ];
320 }
321 }
322