PluginProbe
Code Snippets / trunk
Code Snippets vtrunk
4.0.0-beta.2 3.10.2 3.10.1 3.10.0 3.10.0-beta.2 3.10.0-beta.1 4.0.0-beta.1 3.9.6 trunk 2.10.0 2.10.1 2.12.0 2.12.1 2.13.0 2.13.1 2.13.2 2.13.3 2.14.0 2.14.1 2.14.2 2.14.3 2.14.4 2.14.5 2.14.6 3.0.0 All 65 releases
← All changes | php/Flat_Files/Handlers/Functions_Snippet_Handler.php +99 -2 3.10.0 → trunk View file →
@@ -9,8 +9,13 @@
9 9 */
10 10 class Functions_Snippet_Handler implements Snippet_Type_Handler {
11 11
12 12 /**
13 + * The guard that prevents a flat file from being requested directly.
14 + */
15 + private const DIRECT_ACCESS_GUARD = "if ( ! defined( 'ABSPATH' ) ) { return; }";
16 +
17 + /**
13 18 * Set 'php' as the file extension for functions snippets, so they can be directly loaded.
14 19 *
15 20 * @return string
16 21 */
@@ -27,9 +32,9 @@
27 32 return 'php';
28 33 }
29 34
30 35 /**
31 - * Wrap functions snippets by adding a header that disallows direct access.
36 + * Wrap functions snippets by adding a header that disallows direct access, compatible with PHP namespaces.
32 37 *
33 38 * @param string $code Snippet PHP code.
34 39 *
35 40 * @return string Content snippet code with header prepended.
@@ -34,7 +39,99 @@
34 39 *
35 40 * @return string Content snippet code with header prepended.
36 41 */
37 42 public function wrap_code( string $code ): string {
38 - return "<?php\n\nif ( ! defined( 'ABSPATH' ) ) { return; }\n\n" . $code;
43 + $offset = $this->find_prologue_end( $code );
44 +
45 + if ( 0 === $offset ) {
46 + return "<?php\n\n" . self::DIRECT_ACCESS_GUARD . "\n\n" . $code;
47 + }
48 +
49 + return "<?php\n\n" . rtrim( substr( $code, 0, $offset ) ) .
50 + "\n\n" . self::DIRECT_ACCESS_GUARD . "\n\n" .
51 + ltrim( substr( $code, $offset ), "\n" );
52 + }
53 +
54 + /**
55 + * Find the offset in the snippet code at which the guard may be inserted.
56 + *
57 + * Everything up to that offset is the statement prologue that PHP insists
58 + * on seeing first: any number of `declare` statements, optionally followed
59 + * by a namespace declaration. A braced namespace reports the offset just
60 + * inside the opening brace rather than after the statement.
61 + *
62 + * @param string $code Snippet PHP code, stored without an opening tag.
63 + *
64 + * @return int Offset into `$code`, or zero to insert at the top.
65 + */
66 + private function find_prologue_end( string $code ): int {
67 + $open_tag = "<?php\n";
68 + $tokens = token_get_all( $open_tag . $code );
69 + $skipped = [ T_OPEN_TAG, T_WHITESPACE, T_COMMENT, T_DOC_COMMENT ];
70 + $offset = 0;
71 + $end = 0;
72 + $in_statement = false;
73 +
74 + foreach ( $tokens as $index => $token ) {
75 + $offset += strlen( is_array( $token ) ? $token[1] : $token );
76 +
77 + if ( $in_statement ) {
78 + if ( '{' === $token ) {
79 + // Block syntax: the guard belongs inside the braces.
80 + $end = $offset;
81 + break;
82 + }
83 +
84 + if ( ';' === $token ) {
85 + $end = $offset;
86 + $in_statement = false;
87 + }
88 +
89 + continue;
90 + }
91 +
92 + $id = is_array( $token ) ? $token[0] : null;
93 +
94 + if ( in_array( $id, $skipped, true ) ) {
95 + continue;
96 + }
97 +
98 + if ( T_DECLARE === $id || $this->is_namespace_declaration( $tokens, $index ) ) {
99 + $in_statement = true;
100 + continue;
101 + }
102 +
103 + break;
104 + }
105 +
106 + return max( 0, $end - strlen( $open_tag ) );
107 + }
108 +
109 + /**
110 + * Check whether a token opens a namespace declaration.
111 + *
112 + * `namespace\my_function()` uses the same keyword as an operator, and must
113 + * not be mistaken for a declaration.
114 + *
115 + * @param array<int, array{0: int, 1: string}|string> $tokens Token list.
116 + * @param int $index Token to test.
117 + *
118 + * @return bool
119 + */
120 + private function is_namespace_declaration( array $tokens, int $index ): bool {
121 + if ( ! is_array( $tokens[ $index ] ) || T_NAMESPACE !== $tokens[ $index ][0] ) {
122 + return false;
123 + }
124 +
125 + $count = count( $tokens );
126 +
127 + for ( $next = $index + 1; $next < $count; $next++ ) {
128 + if ( is_array( $tokens[ $next ] ) && T_WHITESPACE === $tokens[ $next ][0] ) {
129 + continue;
130 + }
131 +
132 + return ! is_array( $tokens[ $next ] ) || T_NS_SEPARATOR !== $tokens[ $next ][0];
133 + }
134 +
135 + return false;
39 136 }
40 137 }