PluginProbe
Theme Check / 20230417
Theme Check v20230417
trunk 20190208.1 20190801.1 20200612.1 20200731.1 20200922.1 20210617 20210921 20211203 20220929 20230412 20230417 20231220 20260821
theme-check / checkbase.php

checkbase.php in Theme Check 20230417, at checkbase.php

391 lines 11.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Runs checks against themes and displays the results
4 *
5 * Runs checks against themes and displays the results. Includes helper functions
6 * for performing checks.
7 *
8 * @package Theme Check
9 */
10
11 // main global to hold our checks.
12 global $themechecks;
13 $themechecks = array();
14
15 // counter for the checks.
16 global $checkcount;
17 $checkcount = 0;
18
19 // current WP_Theme being tested. Internal use only.
20 global $theme_check_current_theme;
21 $theme_check_current_theme = false;
22
23 // interface that all checks should implement.
24 interface themecheck {
25
26 // should return true for good/okay/acceptable, false for bad/not-okay/unacceptable.
27 public function check( $php_files, $css_files, $other_files );
28
29 // should return an array of strings explaining any problems found.
30 public function getError();
31 }
32
33 // load all the checks in the checks directory.
34 foreach ( glob( __DIR__ . '/checks/*.php' ) as $file ) {
35 include $file;
36 }
37
38 do_action( 'themecheck_checks_loaded' );
39
40 /**
41 * Run Theme Check against a given theme.
42 *
43 * @param WP_Theme $theme A WP_Theme instance.
44 * @param string $theme_slug The slug of the given theme.
45 * @return bool
46 */
47 function run_themechecks_against_theme( $theme, $theme_slug ) {
48 $files = $theme->get_files(
49 null /* all file types */,
50 -1 /* infinite recursion */,
51 true /* include parent theme files */
52 );
53 unset( $files[0] ); // Work around https://core.trac.wordpress.org/ticket/53599
54
55 $php = array();
56 $css = array();
57 $other = array();
58 foreach ( $files as $filename ) {
59 if ( substr( $filename, -4 ) === '.php' ) {
60 $php[ $filename ] = file_get_contents( $filename );
61 $php[ $filename ] = tc_strip_comments( $php[ $filename ] );
62 } elseif ( substr( $filename, -4 ) === '.css' ) {
63 $css[ $filename ] = file_get_contents( $filename );
64 } else {
65 // In local development it might be useful to skip other files
66 // (non .php or .css files) in dev directories.
67 if ( apply_filters( 'tc_skip_development_directories', false ) ) {
68 if ( tc_is_other_file_in_dev_directory( $filename ) ) {
69 continue;
70 }
71 }
72 $other[ $filename ] = file_get_contents( $filename );
73 }
74 }
75
76 // Run the checks.
77 return run_themechecks(
78 $php,
79 $css,
80 $other,
81 array(
82 'theme' => $theme,
83 'slug' => $theme_slug,
84 )
85 );
86 }
87
88 /**
89 * Run the Theme Checks against a set of files.
90 *
91 * @param array $php The PHP files.
92 * @param array $css The CSS files.
93 * @param array $other Any non-php/css files.
94 * @param array $context Any context for the Theme Checks.
95 *
96 * @return bool
97 */
98 function run_themechecks( $php, $css, $other, $context = array() ) {
99 global $themechecks, $theme_check_current_theme;
100
101 // Provide context to some functions that need to know the current theme, but aren't passed the object.
102 $theme_check_current_theme = isset( $context['theme'] ) ? $context['theme'] : false;
103
104 $pass = true;
105
106 tc_adapt_checks_for_fse_themes( $php, $css, $other );
107
108 foreach ( $themechecks as $check ) {
109 if ( $check instanceof themecheck ) {
110 if ( $context && is_callable( array( $check, 'set_context' ) ) ) {
111 $check->set_context( $context );
112 }
113
114 $pass = $pass & $check->check( $php, $css, $other );
115 }
116 }
117
118 $theme_check_current_theme = false;
119
120 return $pass;
121 }
122
123 function display_themechecks() {
124 $results = '';
125 global $themechecks;
126 $errors = array();
127 foreach ( $themechecks as $check ) {
128 if ( $check instanceof themecheck ) {
129 $error = $check->getError();
130 $error = (array) $error;
131 if ( ! empty( $error ) ) {
132 $errors = array_unique( array_merge( $error, $errors ) );
133 }
134 }
135 }
136 if ( ! empty( $errors ) ) {
137 rsort( $errors );
138 foreach ( $errors as $e ) {
139
140 if ( defined( 'TC_TRAC' ) ) {
141 $results .= ( isset( $_POST['s_info'] ) && preg_match( '/INFO/', $e ) ) ? '' : '* ' . tc_trac( $e ) . "\r\n";
142 } else {
143 $results .= ( isset( $_POST['s_info'] ) && preg_match( '/INFO/', $e ) ) ? '' : '<li>' . tc_trac( $e ) . '</li>';
144 }
145 }
146 }
147
148 if ( defined( 'TC_TRAC' ) ) {
149
150 if ( defined( 'TC_PRE' ) ) {
151 $results = TC_PRE . $results;
152 }
153 $results = '<textarea cols=140 rows=20>' . wp_strip_all_tags( $results );
154 if ( defined( 'TC_POST' ) ) {
155 $results = $results . TC_POST;
156 }
157 $results .= '</textarea>';
158 }
159 return $results;
160 }
161
162 function checkcount() {
163 global $checkcount;
164 $checkcount++;
165 }
166
167 // some functions theme checks use.
168 function tc_grep( $error, $file ) {
169 if ( ! file_exists( $file ) ) {
170 return '';
171 }
172 $lines = file( $file, FILE_IGNORE_NEW_LINES ); // Read the theme file into an array.
173 $line_index = 0;
174 $bad_lines = '';
175 foreach ( $lines as $this_line ) {
176 if ( stristr( $this_line, $error ) ) {
177 $error = str_replace( '"', "'", $error );
178 $this_line = str_replace( '"', "'", $this_line );
179 $error = ltrim( $error );
180 $pos = strpos( $this_line, $error );
181 $pre = ( false !== $pos ? substr( $this_line, 0, $pos ) : false );
182 $pre = ltrim( htmlspecialchars( $pre ) );
183 $bad_lines .= "<pre class='tc-grep'>" . __( 'Line ', 'theme-check' ) . ( $line_index + 1 ) . ': ' . $pre . htmlspecialchars( substr( stristr( $this_line, $error ), 0, 75 ) ) . '</pre>';
184 }
185 $line_index++;
186 }
187 return str_replace( $error, '<span class="tc-grep">' . $error . '</span>', $bad_lines );
188 }
189
190 function tc_preg( $preg, $file ) {
191 if ( ! file_exists( $file ) ) {
192 return '';
193 }
194 $lines = file( $file, FILE_IGNORE_NEW_LINES ); // Read the theme file into an array.
195 $line_index = 0;
196 $bad_lines = '';
197 $error = '';
198 foreach ( $lines as $this_line ) {
199 if ( preg_match( $preg, $this_line, $matches ) ) {
200 $error = $matches[0];
201 $this_line = str_replace( '"', "'", $this_line );
202 $error = ltrim( $error );
203 $pre = '';
204 if ( ! empty( $error ) ) {
205 $pos = strpos( $this_line, $error );
206 $pre = ( false !== $pos ? substr( $this_line, 0, $pos ) : false );
207 }
208 $pre = ltrim( htmlspecialchars( $pre ) );
209 $bad_lines .= "<pre class='tc-grep'>" . __( 'Line ', 'theme-check' ) . ( $line_index + 1 ) . ': ' . $pre . htmlspecialchars( substr( stristr( $this_line, $error ), 0, 75 ) ) . '</pre>';
210 }
211 $line_index++;
212
213 }
214 return str_replace( $error, '<span class="tc-grep">' . $error . '</span>', $bad_lines );
215 }
216
217 function tc_filename( $file ) {
218 // If we know the WP_Theme object, we can get the exact path.
219 $filename = _get_filename_from_current_theme( $file );
220 if ( $filename ) {
221 return $filename;
222 }
223
224 // If the $file exists within a theme-like folder, use that.
225 // Does not support themes nested in directories such as wp-content/themes/pub/wporg-themes/index.php
226 if ( preg_match( '!/themes/[^/]+/(.*)$!i', $file, $m ) ) {
227 return $m[1];
228 }
229
230 // If still nothing, use the basename.
231 return basename( $file );
232 }
233
234 /**
235 * Get a filename relative to the current theme.
236 *
237 * @param string $file the file to get a relative filename for.
238 * @return false|string The filename, or false on failure.
239 * @access private
240 */
241 function _get_filename_from_current_theme( $file ) {
242 global $theme_check_current_theme;
243 static $theme_files = array();
244 static $theme_path = '';
245
246 if ( empty( $theme_check_current_theme ) ) {
247 return false;
248 }
249
250 // Fetch the files for the theme, once per theme.
251 if ( $theme_path != $theme_check_current_theme->get_stylesheet_directory() ) {
252 $theme_path = $theme_check_current_theme->get_stylesheet_directory();
253
254 $theme_files = $theme_check_current_theme->get_files(
255 null /* all file types */,
256 -1 /* infinite recursion */,
257 true /* include parent theme files */
258 );
259 }
260
261 return array_search( $file, $theme_files, true );
262 }
263
264 function tc_trac( $e ) {
265 $trac_left = array( '<strong>', '</strong>' );
266 $trac_right = array( "'''", "'''" );
267 $html_link = '/<a\s?href\s?=\s?[\'|"]([^"|\']*)[\'|"]>([^<]*)<\/a>/i';
268 $html_new = '[$1 $2]';
269 if ( defined( 'TC_TRAC' ) ) {
270 $e = preg_replace( $html_link, $html_new, $e );
271 $e = str_replace( $trac_left, $trac_right, $e );
272 $e = preg_replace( '/<pre.*?>/', "\r\n{{{\r\n", $e );
273 $e = str_replace( '</pre>', "\r\n}}}\r\n", $e );
274 }
275 return $e;
276 }
277
278 // Strip comments from a PHP file in a way that will not change the underlying structure of the file.
279 function tc_strip_comments( $code ) {
280 $strip = array(
281 T_COMMENT => true,
282 T_DOC_COMMENT => true,
283 );
284 $newlines = array(
285 "\n" => true,
286 "\r" => true,
287 );
288 $tokens = token_get_all( $code );
289 reset( $tokens );
290 $return = '';
291 $token = current( $tokens );
292 while ( $token ) {
293 if ( ! is_array( $token ) ) {
294 $return .= $token;
295 } elseif ( ! isset( $strip[ $token[0] ] ) ) {
296 $return .= $token[1];
297 } else {
298 for ( $i = 0, $token_length = strlen( $token[1] ); $i < $token_length; ++$i ) {
299 if ( isset( $newlines[ $token[1][ $i ] ] ) ) {
300 $return .= $token[1][ $i ];
301 }
302 }
303 }
304 $token = next( $tokens );
305 }
306 return $return;
307 }
308
309 /**
310 * Used to allow some directories to be skipped during development.
311 *
312 * @param string $filename a filename/path.
313 * @return boolean
314 */
315 function tc_is_other_file_in_dev_directory( $filename ) {
316 $skip = false;
317 // Filterable List of dirs that you may want to skip other files in during
318 // development.
319 $dev_dirs = apply_filters(
320 'tc_common_dev_directories',
321 array(
322 'node_modules',
323 'vendor',
324 )
325 );
326 foreach ( $dev_dirs as $dev_dir ) {
327 if ( strpos( $filename, $dev_dir ) ) {
328 $skip = true;
329 break;
330 }
331 }
332 return $skip;
333 }
334
335 /**
336 * Adapt the Theme Checks if the theme is an experiment Full-Site Editing theme.
337 *
338 * @param array $php_files The theme's PHP files.
339 * @param array $css_files The theme's CSS files.
340 * @param array $other_files Any other theme files.
341 *
342 * @return bool Whether the theme checks were adapted for FSE or not.
343 */
344 function tc_adapt_checks_for_fse_themes( $php_files, $css_files, $other_files ) {
345 global $themechecks;
346
347 // Get a list of all non PHP and CSS file paths, relative to the theme root.
348 $other_filenames = array();
349 foreach ( $other_files as $path => $contents ) {
350 $other_filenames[] = tc_filename( $path );
351 }
352
353 // Check whether this is a FSE theme by searching for an index.html block template.
354 if ( ! in_array( 'block-templates/index.html', $other_filenames, true ) && ! in_array( 'templates/index.html', $other_filenames, true ) ) {
355 return false;
356 }
357
358 // Remove theme checks that do not apply to FSE themes.
359 foreach ( $themechecks as $key => $check ) {
360 if ( $check instanceof Tag_Check
361 || $check instanceof Suggested_Styles_Check
362 || $check instanceof Widgets_Check
363 || $check instanceof Gravatar_Check
364 || $check instanceof Post_Pagination_Check
365 || $check instanceof Basic_Check
366 || $check instanceof Comments_Check
367 || $check instanceof Comment_Pagination_Check
368 || $check instanceof Comment_Reply_Check
369 || $check instanceof Nav_Menu_Check
370 || $check instanceof Post_Thumbnail_Check
371 || $check instanceof Theme_Support_Check
372 || $check instanceof Editor_Style_Check
373 || $check instanceof Underscores_Check
374 || $check instanceof Constants_Check
375 || $check instanceof Customizer_Check
376 || $check instanceof Post_Format_Check
377 || $check instanceof Search_Form_Check
378 || $check instanceof Theme_Support_Title_Tag_Check
379 || $check instanceof Screen_Reader_Text_Check
380 || $check instanceof Include_Check
381 ) {
382 unset( $themechecks[ $key ] );
383 }
384 }
385
386 // Add FSE specific checks.
387 $themechecks[] = new FSE_Required_Files_Check();
388
389 return true;
390 }
391