@@ -24,10 +24,18 @@ public function get_version( string $path ): ?string {
2424
2525 if ( empty ( $ version ) ) {
2626 foreach ( glob ( $ path . '/*.php ' ) as $ php_file ) {
27- $ contents = file_get_contents ( $ php_file , false , null , 0 , 5000 );
28- $ version = $ this ->get_version_in_code ( $ contents );
29- if ( ! empty ( $ version ) ) {
30- $ version = trim ( $ version );
27+ $ headers = $ this ->get_file_data (
28+ $ php_file ,
29+ array (
30+ 'name ' => 'Plugin Name ' ,
31+ 'version ' => 'Version ' ,
32+ )
33+ );
34+ if ( empty ( $ headers ['name ' ] ) ) {
35+ continue ;
36+ }
37+ if ( ! empty ( $ headers ['version ' ] ) ) {
38+ $ version = $ headers ['version ' ];
3139 break ;
3240 }
3341 }
@@ -52,78 +60,60 @@ public function get_version( string $path ): ?string {
5260 }
5361
5462 /**
55- * Gets the content of a version tag in any doc block in the given source code string .
63+ * Retrieves metadata from a file .
5664 *
57- * The version tag might be specified as "@version x.y.z" or "Version: x.y.z" and it can
58- * be preceded by an asterisk (*).
65+ * Modified slightly from WordPress 6.5.2 wp-includes/functions.php:6830
66+ * @see get_file_data()
67+ * @see https://github.com/WordPress/WordPress/blob/ddc3f387b5df4687f5b829119d0c0f797be674bf/wp-includes/functions.php#L6830-L6888
5968 *
60- * @param string $code_str The source code string to look into.
61- * @return null|string The detected version string.
62- */
63- public function get_version_in_code ( $ code_str ) {
64- $ tokens = array_values (
65- array_filter (
66- token_get_all ( $ code_str ),
67- function ( $ token ) {
68- return ! is_array ( $ token ) || T_WHITESPACE !== $ token [0 ];
69- }
70- )
71- );
72- foreach ( $ tokens as $ token ) {
73- if ( T_DOC_COMMENT === $ token [0 ] ) {
74- $ version = $ this ->get_version_in_docblock ( $ token [1 ] );
75- if ( null !== $ version ) {
76- return $ version ;
77- }
78- }
79- }
80- return null ;
81- }
82-
83- /**
84- * Gets the content of a version tag in a docblock.
69+ * Searches for metadata in the first 8 KB of a file, such as a plugin or theme.
70+ * Each piece of metadata must be on its own line. Fields can not span multiple
71+ * lines, the value will get cut at the end of the first line.
8572 *
86- * @param string $docblock Docblock to parse.
87- * @return null|string The content of the version tag.
73+ * @link https://codex.wordpress.org/File_Header
74+ *
75+ * @param string $file Absolute path to the file.
76+ * @param array $all_headers List of headers, in the format `array( 'HeaderKey' => 'Header Name' )`.
77+ * @return string[] Array of file header values keyed by header name.
8878 */
89- private function get_version_in_docblock ( $ docblock ) {
90- $ docblocktags = $ this ->parse_doc_block ( $ docblock );
91- if ( isset ( $ docblocktags ['version ' ] ) ) {
92- return $ docblocktags ['version ' ];
79+ private function get_file_data ( string $ file , array $ all_headers ): array {
80+
81+ /**
82+ * @see wp_initial_constants()
83+ * `define( 'KB_IN_BYTES', 1024 );`
84+ */
85+ $ kb_in_bytes = 1024 ;
86+
87+ // Pull only the first 8 KB of the file in.
88+ $ file_data = file_get_contents ( $ file , false , null , 0 , 8 * $ kb_in_bytes );
89+
90+ if ( false === $ file_data ) {
91+ $ file_data = '' ;
9392 }
94- return null ;
95- }
9693
97- /**
98- * Parses a docblock and gets an array of tags with their values.
99- *
100- * The tags might be specified as "@version x.y.z" or "Version: x.y.z" and they can
101- * be preceded by an asterisk (*).
102- *
103- * This code is based on the 'phpactor' package.
104- * @see https://github.com/phpactor/docblock/blob/master/lib/Parser.php
105- *
106- * @param string $docblock Docblock to parse.
107- * @return array Associative array of parsed data.
108- */
109- private function parse_doc_block ( $ docblock ) {
110- $ tag_documentor = '{@([a-zA-Z0-9-_ \\\]+)\s*?(.*)?} ' ;
111- $ tag_property = '{\s*\*?\s*(.*?):(.*)} ' ;
112- $ lines = explode ( PHP_EOL , $ docblock );
113- $ tags = [];
114-
115- foreach ( $ lines as $ line ) {
116- if ( 0 === preg_match ( $ tag_documentor , $ line , $ matches ) ) {
117- if ( 0 === preg_match ( $ tag_property , $ line , $ matches ) ) {
118- continue ;
119- }
120- }
94+ // Make sure we catch CR-only line endings.
95+ $ file_data = str_replace ( "\r" , "\n" , $ file_data );
12196
122- $ tag_name = strtolower ( $ matches [1 ] );
123- $ metadata = trim ( isset ( $ matches [2 ] ) ? $ matches [2 ] : '' );
97+ /**
98+ * Strips close comment and close php tags from file headers used by WP.
99+ *
100+ * functions.php:6763
101+ *
102+ * @param string $str Header comment to clean up.
103+ * @return string
104+ */
105+ $ _cleanup_header_comment = function ( $ str ) {
106+ return trim ( preg_replace ( '/\s*(?:\*\/|\?>).*/ ' , '' , $ str ) );
107+ };
124108
125- $ tags [ $ tag_name ] = $ metadata ;
109+ foreach ( $ all_headers as $ field => $ regex ) {
110+ if ( preg_match ( '/^(?:[ \t]*<\?php)?[ \t\/*#@]* ' . preg_quote ( $ regex , '/ ' ) . ':(.*)$/mi ' , $ file_data , $ match ) && $ match [1 ] ) {
111+ $ all_headers [ $ field ] = $ _cleanup_header_comment ( $ match [1 ] );
112+ } else {
113+ $ all_headers [ $ field ] = '' ;
114+ }
126115 }
127- return $ tags ;
116+
117+ return $ all_headers ;
128118 }
129119}
0 commit comments