PluginProbe
Code Snippets / 3.10.0
Code Snippets v3.10.0
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 3.0.1 All 64 releases
code-snippets / php / Integration / Evaluate_Functions.php

Evaluate_Functions.php in Code Snippets 3.10.0, at php/Integration/Evaluate_Functions.php

252 lines 7.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\Integration;
4
5 use Code_Snippets\Core\DB;
6 use Code_Snippets\Flat_Files\Snippet_Files;
7 use Code_Snippets\REST_API\Snippets\Snippets_REST_Controller;
8 use function Code_Snippets\clean_active_snippets_cache;
9 use function Code_Snippets\clean_snippets_cache;
10 use function Code_Snippets\code_snippets;
11 use function Code_Snippets\execute_snippet;
12 use function Code_Snippets\execute_snippet_from_flat_file;
13
14 /**
15 * Class for evaluating functions snippets.
16 *
17 * @package Code_Snippets
18 */
19 class Evaluate_Functions {
20
21 /**
22 * Database class.
23 *
24 * @var DB
25 */
26 private DB $db;
27
28 /**
29 * Class constructor.
30 *
31 * @param DB $db Database class instance.
32 */
33 public function __construct( DB $db ) {
34 $this->db = $db;
35 add_action( 'plugins_loaded', [ $this, 'evaluate_early' ], 1 );
36 add_filter( 'code_snippets/execute_snippets', [ $this, 'disable_snippet_execution' ], 5 );
37
38 if ( $this->is_safe_mode_requested() ) {
39 add_filter( 'home_url', [ $this, 'add_safe_mode_query_var' ] );
40 add_filter( 'admin_url', [ $this, 'add_safe_mode_query_var' ] );
41 }
42 }
43
44 /**
45 * Check if safe mode has been requested via query var.
46 *
47 * @return bool
48 */
49 public function is_safe_mode_requested(): bool {
50 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
51 return ! empty( $_REQUEST['snippets-safe-mode'] ) && code_snippets()->current_user_can();
52 }
53
54 /**
55 * Inject the safe mode query var into URLs
56 *
57 * @param string $url Original URL.
58 *
59 * @return string Modified URL.
60 */
61 public function add_safe_mode_query_var( string $url ): string {
62 return $this->is_safe_mode_requested() ?
63 add_query_arg( 'snippets-safe-mode', true, $url ) :
64 $url;
65 }
66
67 /**
68 * Retrieve details about the currently edited snippet, if any.
69 *
70 * @return ?array{id: int, table: string}
71 */
72 private function get_currently_editing_snippet(): ?array {
73 if ( wp_is_json_request() && ! empty( $_SERVER['REQUEST_URI'] ) ) {
74 $url = wp_parse_url( esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) ) );
75
76 if ( isset( $url['path'] ) && false !== strpos( $url['path'], Snippets_REST_Controller::get_prefixed_base_route() ) ) {
77 $path_parts = explode( '/', $url['path'] );
78 $edit_id = intval( end( $path_parts ) );
79
80 if ( ! empty( $url['query'] ) ) {
81 wp_parse_str( $url['query'], $path_params );
82 $edit_table = isset( $path_params['network'] ) && rest_sanitize_boolean( $path_params['network'] )
83 ? $this->db->ms_table
84 : $this->db->table;
85 }
86
87 return [
88 'id' => $edit_id,
89 'table' => $edit_table ?? $this->db->table,
90 ];
91 }
92 }
93
94 return null;
95 }
96
97 /**
98 * Check if the plugin is running in safe mode.
99 *
100 * @return bool
101 *
102 * @noinspection PhpUndefinedConstantInspection
103 */
104 public static function is_safe_mode_active(): bool {
105 return ( defined( 'CODE_SNIPPETS_SAFE_MODE' ) && CODE_SNIPPETS_SAFE_MODE ) ||
106 ! apply_filters( 'code_snippets/execute_snippets', true );
107 }
108
109 /**
110 * Disable snippet execution if the necessary query var is set.
111 *
112 * @param bool $execute_snippets Current filter value.
113 *
114 * @return bool New filter value.
115 */
116 public function disable_snippet_execution( bool $execute_snippets ): bool {
117 return $execute_snippets && ! self::is_safe_mode_requested();
118 }
119
120 /**
121 * Quickly deactivate a snippet with minimal overhead.
122 *
123 * @param int $snippet_id ID of the snippet to deactivate.
124 * @param string $table_name Name of the table where the snippet is stored.
125 *
126 * @return void
127 */
128 private function quick_deactivate_snippet( int $snippet_id, string $table_name ) {
129 global $wpdb;
130
131 $active_shared_ids = get_option( 'active_shared_network_snippets', [] );
132 $active_shared_ids = is_array( $active_shared_ids )
133 ? array_map( 'intval', $active_shared_ids )
134 : [];
135
136 if ( $table_name === $this->db->ms_table && in_array( $snippet_id, $active_shared_ids, true ) ) {
137 unset( $active_shared_ids[ array_search( $snippet_id, $active_shared_ids, true ) ] );
138 $active_shared_ids = array_values( $active_shared_ids );
139 update_option( 'active_shared_network_snippets', $active_shared_ids );
140 clean_active_snippets_cache( $table_name );
141 } else {
142 $wpdb->update(
143 $table_name,
144 [ 'active' => '0' ],
145 [ 'id' => $snippet_id ],
146 [ '%d' ],
147 [ '%d' ]
148 );
149 clean_snippets_cache( $table_name );
150
151 $network = $table_name === $this->db->ms_table;
152 do_action( 'code_snippets/deactivate_snippet', $snippet_id, $network );
153 }
154 }
155
156 /**
157 * Evaluate a snippet stored in a flat file.
158 *
159 * @param array $snippet Snippet data.
160 * @param string $file_path Path to the snippet file.
161 * @param array|null $edit_snippet Data of snippet currently being edited, if applicable.
162 *
163 * @return void
164 */
165 private function evaluate_snippet_flat_file( array $snippet, string $file_path, ?array $edit_snippet = null ) {
166 $snippet_id = $snippet['id'];
167 $code = $snippet['code'];
168 $table_name = $snippet['table'];
169
170 // If the snippet is a single-use snippet, deactivate it before execution to ensure that the process always happens.
171 if ( 'single-use' === $snippet['scope'] ) {
172 $this->quick_deactivate_snippet( $snippet_id, $table_name );
173 }
174
175 if ( ! is_null( $edit_snippet ) && $edit_snippet['id'] === $snippet_id && $edit_snippet['table'] === $table_name ) {
176 return;
177 }
178
179 if ( apply_filters( 'code_snippets/allow_execute_snippet', true, $snippet_id, $table_name ) ) {
180 execute_snippet_from_flat_file( $code, $file_path, $snippet_id );
181 }
182 }
183
184 /**
185 * Evaluate applicable active snippets as early as possible.
186 *
187 * @return bool True if snippets were evaluated, false if safe mode is active.
188 */
189 public function evaluate_early(): bool {
190 if ( self::is_safe_mode_active() ) {
191 return false;
192 }
193
194 if ( Snippet_Files::is_active() ) {
195 return $this->evaluate_file_snippets();
196 }
197
198 return $this->evaluate_db_snippets();
199 }
200
201 /**
202 * Evaluate active snippets stored in the database.
203 *
204 * @return bool True on completion.
205 */
206 public function evaluate_db_snippets(): bool {
207 $scopes = [ 'global', 'single-use', is_admin() ? 'admin' : 'front-end' ];
208 $active_snippets = $this->db->fetch_active_snippets( $scopes );
209 $edit_snippet = $this->get_currently_editing_snippet();
210
211 foreach ( $active_snippets as $snippet ) {
212 $snippet_id = $snippet['id'];
213 $code = $snippet['code'];
214 $table_name = $snippet['table'];
215
216 // If the snippet is a single-use snippet, deactivate it before execution to ensure that the process always happens.
217 if ( 'single-use' === $snippet['scope'] ) {
218 $this->quick_deactivate_snippet( $snippet_id, $table_name );
219 }
220
221 if ( apply_filters( 'code_snippets/allow_execute_snippet', true, $snippet_id, $table_name ) &&
222 ( is_null( $edit_snippet ) || $edit_snippet['id'] !== $snippet_id || $edit_snippet['table'] !== $table_name ) ) {
223 execute_snippet( $code, $snippet_id );
224 }
225 }
226
227 return true;
228 }
229
230 /**
231 * Evaluate active snippets stored in flat files.
232 *
233 * @return bool True on completion.
234 */
235 private function evaluate_file_snippets(): bool {
236 $type = 'php';
237 $scopes = [ 'global', 'single-use', is_admin() ? 'admin' : 'front-end' ];
238 $snippets = Snippet_Files::get_active_snippets_from_flat_files( $scopes, $type );
239 $edit_snippet = $this->get_currently_editing_snippet();
240
241 foreach ( $snippets as $snippet ) {
242 $table_name = Snippet_Files::get_hashed_table_name( $snippet['table'] );
243 $base_path = Snippet_Files::get_base_dir( $table_name, $type );
244 $file = $base_path . '/' . $snippet['id'] . '.' . $type;
245
246 $this->evaluate_snippet_flat_file( $snippet, $file, $edit_snippet );
247 }
248
249 return true;
250 }
251 }
252