| @@ -34,9 +34,15 @@ | ||
| 34 | 34 | $this->db = $db; |
| 35 | 35 | add_action( 'plugins_loaded', [ $this, 'evaluate_early' ], 1 ); |
| 36 | 36 | add_filter( 'code_snippets/execute_snippets', [ $this, 'disable_snippet_execution' ], 5 ); |
| 37 | 37 | |
| 38 | - if ( $this->is_safe_mode_requested() ) { | |
| 38 | + // Only the query var is inspected here. This constructor runs while | |
| 39 | + // plugins are still being included, which is before WordPress loads | |
| 40 | + // pluggable.php, so a capability check at this point would call an | |
| 41 | + // undefined wp_get_current_user() and take the whole request down. | |
| 42 | + // The capability is checked in the callback instead, which never runs | |
| 43 | + // before URLs are being generated. | |
| 44 | + if ( $this->is_safe_mode_query_var_set() ) { | |
| 39 | 45 | add_filter( 'home_url', [ $this, 'add_safe_mode_query_var' ] ); |
| 40 | 46 | add_filter( 'admin_url', [ $this, 'add_safe_mode_query_var' ] ); |
| 41 | 47 | } |
| 42 | 48 | } |
| @@ -41,25 +47,41 @@ | ||
| 41 | 47 | } |
| 42 | 48 | } |
| 43 | 49 | |
| 44 | 50 | /** |
| 51 | + * Check whether the safe mode query var is present on this request. | |
| 52 | + * | |
| 53 | + * Safe to call at any point, as it reads nothing but the request. | |
| 54 | + * | |
| 55 | + * @return bool | |
| 56 | + */ | |
| 57 | + public function is_safe_mode_query_var_set(): bool { | |
| 58 | + // phpcs:ignore WordPress.Security.NonceVerification.Recommended | |
| 59 | + return ! empty( $_REQUEST['snippets-safe-mode'] ); | |
| 60 | + } | |
| 61 | + | |
| 62 | + /** | |
| 45 | 63 | * Check if safe mode has been requested via query var. |
| 46 | 64 | * |
| 65 | + * Performs a capability check, so must not be called before pluggable | |
| 66 | + * functions are available. | |
| 67 | + * | |
| 47 | 68 | * @return bool |
| 48 | 69 | */ |
| 49 | 70 | 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(); | |
| 71 | + return $this->is_safe_mode_query_var_set() && code_snippets()->current_user_can(); | |
| 52 | 72 | } |
| 53 | 73 | |
| 54 | 74 | /** |
| 55 | 75 | * Inject the safe mode query var into URLs |
| 56 | 76 | * |
| 57 | - * @param string $url Original URL. | |
| 77 | + * @param mixed $url Original URL, from an unknown earlier callback. | |
| 58 | 78 | * |
| 59 | 79 | * @return string Modified URL. |
| 60 | 80 | */ |
| 61 | - public function add_safe_mode_query_var( string $url ): string { | |
| 81 | + public function add_safe_mode_query_var( $url ): string { | |
| 82 | + $url = is_string( $url ) ? $url : ''; | |
| 83 | + | |
| 62 | 84 | return $this->is_safe_mode_requested() ? |
| 63 | 85 | add_query_arg( 'snippets-safe-mode', true, $url ) : |
| 64 | 86 | $url; |
| 65 | 87 | } |
| @@ -108,14 +130,15 @@ | ||
| 108 | 130 | |
| 109 | 131 | /** |
| 110 | 132 | * Disable snippet execution if the necessary query var is set. |
| 111 | 133 | * |
| 112 | - * @param bool $execute_snippets Current filter value. | |
| 134 | + * @param mixed $execute_snippets Current filter value, from an unknown | |
| 135 | + * earlier callback. | |
| 113 | 136 | * |
| 114 | 137 | * @return bool New filter value. |
| 115 | 138 | */ |
| 116 | - public function disable_snippet_execution( bool $execute_snippets ): bool { | |
| 117 | - return $execute_snippets && ! self::is_safe_mode_requested(); | |
| 139 | + public function disable_snippet_execution( $execute_snippets ): bool { | |
| 140 | + return $execute_snippets && ! $this->is_safe_mode_requested(); | |
| 118 | 141 | } |
| 119 | 142 | |
| 120 | 143 | /** |
| 121 | 144 | * Quickly deactivate a snippet with minimal overhead. |
| @@ -153,17 +176,17 @@ | ||
| 153 | 176 | } |
| 154 | 177 | } |
| 155 | 178 | |
| 156 | 179 | /** |
| 157 | - * Evaluate a snippet stored in a flat file. | |
| 180 | + * Evaluate a snippet. | |
| 158 | 181 | * |
| 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. | |
| 182 | + * @param array $snippet Snippet data. | |
| 183 | + * @param string|null $file_path Path to the snippet file, or null if executing from the database. | |
| 184 | + * @param array|null $edit_snippet Data of snippet currently being edited, if applicable. | |
| 162 | 185 | * |
| 163 | 186 | * @return void |
| 164 | 187 | */ |
| 165 | - private function evaluate_snippet_flat_file( array $snippet, string $file_path, ?array $edit_snippet = null ) { | |
| 188 | + private function evaluate_snippet( array $snippet, ?string $file_path, ?array $edit_snippet ) { | |
| 166 | 189 | $snippet_id = $snippet['id']; |
| 167 | 190 | $code = $snippet['code']; |
| 168 | 191 | $table_name = $snippet['table']; |
| 169 | 192 | |
| @@ -176,9 +199,13 @@ | ||
| 176 | 199 | return; |
| 177 | 200 | } |
| 178 | 201 | |
| 179 | 202 | if ( apply_filters( 'code_snippets/allow_execute_snippet', true, $snippet_id, $table_name ) ) { |
| 180 | - execute_snippet_from_flat_file( $code, $file_path, $snippet_id ); | |
| 203 | + if ( is_null( $file_path ) ) { | |
| 204 | + execute_snippet( $code, $snippet_id ); | |
| 205 | + } else { | |
| 206 | + execute_snippet_from_flat_file( $code, $file_path, $snippet_id ); | |
| 207 | + } | |
| 181 | 208 | } |
| 182 | 209 | } |
| 183 | 210 | |
| 184 | 211 | /** |
| @@ -208,21 +235,11 @@ | ||
| 208 | 235 | $active_snippets = $this->db->fetch_active_snippets( $scopes ); |
| 209 | 236 | $edit_snippet = $this->get_currently_editing_snippet(); |
| 210 | 237 | |
| 211 | 238 | 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 ); | |
| 239 | + if ( 'condition' !== $snippet['scope'] ) { | |
| 240 | + $this->evaluate_snippet( $snippet, null, $edit_snippet ); | |
| 219 | 241 | } |
| 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 | 242 | } |
| 226 | 243 | |
| 227 | 244 | return true; |
| 228 | 245 | } |
| @@ -238,13 +255,15 @@ | ||
| 238 | 255 | $snippets = Snippet_Files::get_active_snippets_from_flat_files( $scopes, $type ); |
| 239 | 256 | $edit_snippet = $this->get_currently_editing_snippet(); |
| 240 | 257 | |
| 241 | 258 | 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; | |
| 259 | + if ( 'condition' !== $snippet['scope'] ) { | |
| 260 | + $table_name = Snippet_Files::get_hashed_table_name( $snippet['table'] ); | |
| 261 | + $base_path = Snippet_Files::get_base_dir( $table_name, $type ); | |
| 262 | + $file = $base_path . '/' . $snippet['id'] . '.' . $type; | |
| 245 | 263 | |
| 246 | - $this->evaluate_snippet_flat_file( $snippet, $file, $edit_snippet ); | |
| 264 | + $this->evaluate_snippet( $snippet, $file, $edit_snippet ); | |
| 265 | + } | |
| 247 | 266 | } |
| 248 | 267 | |
| 249 | 268 | return true; |
| 250 | 269 | } |