PluginProbe
Code Snippets / 4.0.0-beta.2
Code Snippets v4.0.0-beta.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 4.0.0-beta.2, at php/Flat_Files/Handlers/Functions_Snippet_Handler.php

138 lines 3.5 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, compatible with PHP namespaces.
37 *
38 * @param string $code Snippet PHP code.
39 *
40 * @return string Content snippet code with header prepended.
41 */
42 public function wrap_code( string $code ): string {
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;
136 }
137 }
138