PluginProbe
Code Snippets / 3.10.2
Code Snippets v3.10.2
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
code-snippets / php / Flat_Files / Handlers / Functions_Snippet_Handler.php

Functions_Snippet_Handler.php in Code Snippets 3.10.2, at php/Flat_Files/Handlers/Functions_Snippet_Handler.php

146 lines 4.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Code_Snippets\Flat_Files\Handlers;
4
5 use Code_Snippets\Flat_Files\Interfaces\Snippet_Type_Handler;
6
7 /**
8 * Snippet type handler for functions snippets.
9 */
10 class Functions_Snippet_Handler implements Snippet_Type_Handler {
11
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 /**
18 * Set 'php' as the file extension for functions snippets, so they can be directly loaded.
19 *
20 * @return string
21 */
22 public function get_file_extension(): string {
23 return 'php';
24 }
25
26 /**
27 * Store content snippets in a 'php' directory.
28 *
29 * @return string
30 */
31 public function get_dir_name(): string {
32 return 'php';
33 }
34
35 /**
36 * Wrap functions snippets by adding a header that disallows direct access.
37 *
38 * The guard cannot simply be prepended. PHP requires `declare` and
39 * `namespace` to come before any other statement, so a snippet opening with
40 * either used to fatal on load with "Namespace declaration statement has to
41 * be the very first statement" — taking down every page of the site, with
42 * nothing written to the error log. The guard is inserted after that
43 * prologue instead, and inside the braces when a namespace uses block
44 * syntax, since no code may sit outside `namespace {}` blocks.
45 *
46 * @param string $code Snippet PHP code.
47 *
48 * @return string Content snippet code with header prepended.
49 */
50 public function wrap_code( string $code ): string {
51 $offset = $this->find_prologue_end( $code );
52
53 if ( 0 === $offset ) {
54 return "<?php\n\n" . self::DIRECT_ACCESS_GUARD . "\n\n" . $code;
55 }
56
57 return "<?php\n\n" . rtrim( substr( $code, 0, $offset ) ) .
58 "\n\n" . self::DIRECT_ACCESS_GUARD . "\n\n" .
59 ltrim( substr( $code, $offset ), "\n" );
60 }
61
62 /**
63 * Find the offset in the snippet code at which the guard may be inserted.
64 *
65 * Everything up to that offset is the statement prologue that PHP insists
66 * on seeing first: any number of `declare` statements, optionally followed
67 * by a namespace declaration. A braced namespace reports the offset just
68 * inside the opening brace rather than after the statement.
69 *
70 * @param string $code Snippet PHP code, stored without an opening tag.
71 *
72 * @return int Offset into `$code`, or zero to insert at the top.
73 */
74 private function find_prologue_end( string $code ): int {
75 $open_tag = "<?php\n";
76 $tokens = token_get_all( $open_tag . $code );
77 $skipped = [ T_OPEN_TAG, T_WHITESPACE, T_COMMENT, T_DOC_COMMENT ];
78 $offset = 0;
79 $end = 0;
80 $in_statement = false;
81
82 foreach ( $tokens as $index => $token ) {
83 $offset += strlen( is_array( $token ) ? $token[1] : $token );
84
85 if ( $in_statement ) {
86 if ( '{' === $token ) {
87 // Block syntax: the guard belongs inside the braces.
88 $end = $offset;
89 break;
90 }
91
92 if ( ';' === $token ) {
93 $end = $offset;
94 $in_statement = false;
95 }
96
97 continue;
98 }
99
100 $id = is_array( $token ) ? $token[0] : null;
101
102 if ( in_array( $id, $skipped, true ) ) {
103 continue;
104 }
105
106 if ( T_DECLARE === $id || $this->is_namespace_declaration( $tokens, $index ) ) {
107 $in_statement = true;
108 continue;
109 }
110
111 break;
112 }
113
114 return max( 0, $end - strlen( $open_tag ) );
115 }
116
117 /**
118 * Check whether a token opens a namespace declaration.
119 *
120 * `namespace\my_function()` uses the same keyword as an operator, and must
121 * not be mistaken for a declaration.
122 *
123 * @param array<int, array{0: int, 1: string}|string> $tokens Token list.
124 * @param int $index Token to test.
125 *
126 * @return bool
127 */
128 private function is_namespace_declaration( array $tokens, int $index ): bool {
129 if ( ! is_array( $tokens[ $index ] ) || T_NAMESPACE !== $tokens[ $index ][0] ) {
130 return false;
131 }
132
133 $count = count( $tokens );
134
135 for ( $next = $index + 1; $next < $count; $next++ ) {
136 if ( is_array( $tokens[ $next ] ) && T_WHITESPACE === $tokens[ $next ][0] ) {
137 continue;
138 }
139
140 return ! is_array( $tokens[ $next ] ) || T_NS_SEPARATOR !== $tokens[ $next ][0];
141 }
142
143 return false;
144 }
145 }
146