Diff: STRATO-apps/wordpress_03/app/wp-content/plugins/code-snippets/php/class-snippet.php
Keine Baseline-Datei – Diff nur gegen leer.
1
-
1
+
<?php
2
+
3
+
namespace Code_Snippets;
4
+
5
+
use DateTime;
6
+
use DateTimeZone;
7
+
use Exception;
8
+
9
+
/**
10
+
* A snippet object.
11
+
*
12
+
* @since 2.4.0
13
+
* @package Code_Snippets
14
+
*
15
+
* @property int $id The database ID.
16
+
* @property string $name The snippet title.
17
+
* @property string $desc The formatted description.
18
+
* @property string $code The executable code.
19
+
* @property array<string> $tags An array of the tags.
20
+
* @property string $scope The scope name.
21
+
* @property int $condition_id ID of the condition this snippet is linked to.
22
+
* @property int $priority Execution priority.
23
+
* @property bool $active The active status.
24
+
* @property bool $network true if is multisite-wide snippet, false if site-wide.
25
+
* @property bool $shared_network Whether the snippet is a shared network snippet.
26
+
* @property string $modified The date and time when the snippet data was most recently saved to the database.
27
+
* @property array{string,int}|null $code_error Code error encountered when last testing snippet code.
28
+
* @property int $revision Revision or version number of snippet.
29
+
* @property string $cloud_id Cloud ID and ownership status of snippet.
30
+
*
31
+
* @property-read string $display_name The snippet name if it exists or a placeholder if it does not.
32
+
* @property-read string $tags_list The tags in string list format.
33
+
* @property-read string $scope_icon The dashicon used to represent the current scope.
34
+
* @property-read string $scope_name Human-readable description of the snippet type.
35
+
* @property-read string $type The type of snippet.
36
+
* @property-read string $lang The language that the snippet code is written in.
37
+
* @property-read int $modified_timestamp The last modification date in Unix timestamp format.
38
+
* @property-read DateTime $modified_local The last modification date in the local timezone.
39
+
* @property-read boolean $is_pro Whether the snippet type is pro-only.
40
+
*/
41
+
class Snippet extends Data_Item {
42
+
43
+
/**
44
+
* MySQL datetime format (YYYY-MM-DD hh:mm:ss).
45
+
*/
46
+
public const DATE_FORMAT = 'Y-m-d H:i:s';
47
+
48
+
/**
49
+
* Default value used for a datetime variable.
50
+
*/
51
+
public const DEFAULT_DATE = '0000-00-00 00:00:00';
52
+
53
+
/**
54
+
* Raw active value from database before processing.
55
+
*
56
+
* @var mixed
57
+
*/
58
+
private $raw_active_value;
59
+
60
+
/**
61
+
* Constructor function.
62
+
*
63
+
* @param array<string, mixed>|object $initial_data Initial snippet data.
64
+
*/
65
+
public function __construct( $initial_data = null ) {
66
+
if ( is_array( $initial_data ) && isset( $initial_data['active'] ) ) {
67
+
$this->raw_active_value = $initial_data['active'];
68
+
} elseif ( is_object( $initial_data ) && isset( $initial_data->active ) ) {
69
+
$this->raw_active_value = $initial_data->active;
70
+
}
71
+
72
+
$default_values = array(
73
+
'id' => 0,
74
+
'name' => '',
75
+
'desc' => '',
76
+
'code' => '',
77
+
'tags' => array(),
78
+
'scope' => 'global',
79
+
'condition_id' => 0,
80
+
'active' => false,
81
+
'priority' => 10,
82
+
'network' => null,
83
+
'shared_network' => null,
84
+
'modified' => null,
85
+
'code_error' => null,
86
+
'revision' => 1,
87
+
'cloud_id' => '',
88
+
);
89
+
90
+
$field_aliases = array(
91
+
'description' => 'desc',
92
+
'language' => 'lang',
93
+
'conditionId' => 'condition_id',
94
+
);
95
+
96
+
parent::__construct( $default_values, $initial_data, $field_aliases );
97
+
}
98
+
99
+
/**
100
+
* Add a new tag
101
+
*
102
+
* @param string $tag Tag content to add to list.
103
+
*/
104
+
public function add_tag( string $tag ) {
105
+
$this->fields['tags'][] = $tag;
106
+
}
107
+
108
+
/**
109
+
* Determine if the snippet is a condition.
110
+
*
111
+
* @return bool
112
+
*/
113
+
public function is_condition(): bool {
114
+
return 'condition' === $this->scope;
115
+
}
116
+
117
+
/**
118
+
* Determine if the snippet is trashed (soft deleted).
119
+
*
120
+
* @return bool
121
+
*/
122
+
public function is_trashed(): bool {
123
+
return -1 === (int) $this->raw_active_value;
124
+
}
125
+
126
+
/**
127
+
* Prepare a value before it is stored.
128
+
*
129
+
* @param mixed $value Value to prepare.
130
+
* @param string $field Field name.
131
+
*
132
+
* @return mixed Value in the correct format.
133
+
*/
134
+
protected function prepare_field( $value, string $field ) {
135
+
switch ( $field ) {
136
+
case 'id':
137
+
case 'priority':
138
+
case 'condition_id':
139
+
return absint( $value );
140
+
141
+
case 'tags':
142
+
return code_snippets_build_tags_array( $value );
143
+
144
+
case 'active':
145
+
return ( is_bool( $value ) ? $value : (bool) $value ) && ! $this->is_condition() && (int) $value != -1;
146
+
147
+
default:
148
+
return $value;
149
+
}
150
+
}
151
+
152
+
/**
153
+
* Prepare the scope by ensuring that it is a valid choice
154
+
*
155
+
* @param int|string $scope The field as provided.
156
+
*
157
+
* @return string The field in the correct format.
158
+
*/
159
+
protected function prepare_scope( $scope ) {
160
+
$scopes = self::get_all_scopes();
161
+
162
+
if ( in_array( $scope, $scopes, true ) ) {
163
+
return $scope;
164
+
}
165
+
166
+
if ( is_numeric( $scope ) && isset( $scopes[ $scope ] ) ) {
167
+
return $scopes[ $scope ];
168
+
}
169
+
170
+
return $this->fields['scope'];
171
+
}
172
+
173
+
/**
174
+
* If $network is anything other than true, set it to false
175
+
*
176
+
* @param bool $network The field as provided.
177
+
*
178
+
* @return bool The field in the correct format.
179
+
*/
180
+
protected function prepare_network( bool $network ): bool {
181
+
if ( null === $network && function_exists( 'is_network_admin' ) ) {
182
+
return is_network_admin();
183
+
}
184
+
185
+
return true === $network;
186
+
}
187
+
188
+
/**
189
+
* Determine the type of code a given scope will produce.
190
+
*
191
+
* @param string $scope Scope name.
192
+
*
193
+
* @return string The snippet type – will be a filename extension.
194
+
*/
195
+
public static function get_type_from_scope( string $scope ): string {
196
+
if ( '-css' === substr( $scope, -4 ) ) {
197
+
return 'css';
198
+
} elseif ( '-js' === substr( $scope, -3 ) ) {
199
+
return 'js';
200
+
} elseif ( 'content' === substr( $scope, -7 ) ) {
201
+
return 'html';
202
+
} elseif ( 'condition' === $scope ) {
203
+
return 'cond';
204
+
} else {
205
+
return 'php';
206
+
}
207
+
}
208
+
209
+
/**
210
+
* Determine the type of code this snippet is, based on its scope
211
+
*
212
+
* @return string The snippet type – will be a filename extension.
213
+
*/
214
+
public function get_type(): string {
215
+
return self::get_type_from_scope( $this->scope );
216
+
}
217
+
218
+
/**
219
+
* Retrieve a list of all valid types.
220
+
*
221
+
* @return string[]
222
+
*/
223
+
public static function get_types(): array {
224
+
return [ 'php', 'html', 'css', 'js', 'cond' ];
225
+
}
226
+
227
+
/**
228
+
* Determine the language that the snippet code is written in, based on the scope
229
+
*
230
+
* @return string The name of a language filename extension.
231
+
*/
232
+
protected function get_lang(): string {
233
+
return 'cond' === $this->type ? 'json' : $this->type;
234
+
}
235
+
236
+
/**
237
+
* Prepare the modification field by ensuring it is in the correct format.
238
+
*
239
+
* @param DateTime|string $modified Snippet modification date.
240
+
*
241
+
* @return string
242
+
*/
243
+
protected function prepare_modified( $modified ): ?string {
244
+
245
+
// If the supplied value is a DateTime object, convert it to string representation.
246
+
if ( $modified instanceof DateTime ) {
247
+
return $modified->format( self::DATE_FORMAT );
248
+
}
249
+
250
+
// If the supplied value is probably a timestamp, attempt to convert it to a string.
251
+
if ( is_numeric( $modified ) ) {
252
+
return gmdate( self::DATE_FORMAT, $modified );
253
+
}
254
+
255
+
// If the supplied value is a string, check it is not just the default value.
256
+
if ( is_string( $modified ) && self::DEFAULT_DATE !== $modified ) {
257
+
return $modified;
258
+
}
259
+
260
+
// Otherwise, discard the supplied value.
261
+
return null;
262
+
}
263
+
264
+
/**
265
+
* Update the last modification date to the current date and time.
266
+
*
267
+
* @return void
268
+
*/
269
+
public function update_modified() {
270
+
$this->modified = gmdate( self::DATE_FORMAT );
271
+
}
272
+
273
+
/**
274
+
* Retrieve the snippet title if set or a placeholder title if not.
275
+
*
276
+
* @return string
277
+
*/
278
+
protected function get_display_name(): string {
279
+
// translators: %s: snippet identifier.
280
+
return empty( $this->name ) ? sprintf( esc_html__( 'Snippet #%d', 'code-snippets' ), $this->id ) : $this->name;
281
+
}
282
+
283
+
/**
284
+
* Retrieve the tags in list format
285
+
*
286
+
* @return string The tags separated by a comma and a space.
287
+
*/
288
+
protected function get_tags_list(): string {
289
+
return implode( ', ', $this->tags );
290
+
}
291
+
292
+
/**
293
+
* Retrieve a list of all available scopes
294
+
*
295
+
* @return array<string> List of scope names.
296
+
*
297
+
* @phpcs:disable WordPress.Arrays.ArrayDeclarationSpacing.ArrayItemNoNewLine
298
+
*/
299
+
public static function get_all_scopes(): array {
300
+
return array(
301
+
'global', 'admin', 'front-end', 'single-use',
302
+
'content', 'head-content', 'footer-content',
303
+
'admin-css', 'site-css',
304
+
'site-head-js', 'site-footer-js',
305
+
'condition',
306
+
);
307
+
}
308
+
309
+
/**
310
+
* Retrieve a list of all scope icons
311
+
*
312
+
* @return array<string, string> Scope name keyed to the class name of a dashicon.
313
+
*/
314
+
public static function get_scope_icons(): array {
315
+
return array(
316
+
'global' => 'admin-site',
317
+
'admin' => 'admin-tools',
318
+
'front-end' => 'admin-appearance',
319
+
'single-use' => 'clock',
320
+
'content' => 'shortcode',
321
+
'head-content' => 'editor-code',
322
+
'footer-content' => 'editor-code',
323
+
'admin-css' => 'dashboard',
324
+
'site-css' => 'admin-customizer',
325
+
'site-head-js' => 'media-code',
326
+
'site-footer-js' => 'media-code',
327
+
'condition' => 'randomize',
328
+
);
329
+
}
330
+
331
+
/**
332
+
* Retrieve the string representation of the scope
333
+
*
334
+
* @return string The name of the scope.
335
+
*/
336
+
protected function get_scope_name(): string {
337
+
switch ( $this->scope ) {
338
+
case 'global':
339
+
return __( 'Global function', 'code-snippets' );
340
+
case 'admin':
341
+
return __( 'Admin function', 'code-snippets' );
342
+
case 'front-end':
343
+
return __( 'Front-end function', 'code-snippets' );
344
+
case 'single-use':
345
+
return __( 'Single-use function', 'code-snippets' );
346
+
case 'content':
347
+
return __( 'Content', 'code-snippets' );
348
+
case 'head-content':
349
+
return __( 'Head content', 'code-snippets' );
350
+
case 'footer-content':
351
+
return __( 'Footer content', 'code-snippets' );
352
+
case 'admin-css':
353
+
return __( 'Admin styles', 'code-snippets' );
354
+
case 'site-css':
355
+
return __( 'Front-end styles', 'code-snippets' );
356
+
case 'site-head-js':
357
+
return __( 'Head scripts', 'code-snippets' );
358
+
case 'site-footer-js':
359
+
return __( 'Footer scripts', 'code-snippets' );
360
+
}
361
+
362
+
return '';
363
+
}
364
+
365
+
/**
366
+
* Retrieve the icon used for the current scope
367
+
*
368
+
* @return string A dashicon name.
369
+
*/
370
+
protected function get_scope_icon(): string {
371
+
$icons = self::get_scope_icons();
372
+
373
+
return $icons[ $this->scope ];
374
+
}
375
+
376
+
/**
377
+
* Determine if the snippet is a shared network snippet
378
+
*
379
+
* @return bool Whether the snippet is a shared network snippet.
380
+
*/
381
+
protected function get_shared_network(): bool {
382
+
if ( isset( $this->fields['shared_network'] ) ) {
383
+
return $this->fields['shared_network'];
384
+
}
385
+
386
+
if ( ! is_multisite() || ! $this->fields['network'] ) {
387
+
$this->fields['shared_network'] = false;
388
+
} else {
389
+
$shared_network_snippets = get_site_option( 'shared_network_snippets', array() );
390
+
$this->fields['shared_network'] = in_array( $this->fields['id'], $shared_network_snippets, true );
391
+
}
392
+
393
+
return $this->fields['shared_network'];
394
+
}
395
+
396
+
/**
397
+
* Retrieve the snippet modification date as a timestamp.
398
+
*
399
+
* @return integer Timestamp value.
400
+
*/
401
+
protected function get_modified_timestamp(): int {
402
+
$datetime = DateTime::createFromFormat( self::DATE_FORMAT, $this->modified, new DateTimeZone( 'UTC' ) );
403
+
404
+
return $datetime ? $datetime->getTimestamp() : 0;
405
+
}
406
+
407
+
/**
408
+
* Retrieve the modification time in the local timezone.
409
+
*
410
+
* @return DateTime
411
+
*/
412
+
protected function get_modified_local(): DateTime {
413
+
$datetime = DateTime::createFromFormat( self::DATE_FORMAT, $this->modified, new DateTimeZone( 'UTC' ) );
414
+
415
+
if ( function_exists( 'wp_timezone' ) ) {
416
+
$timezone = wp_timezone();
417
+
} else {
418
+
$timezone = get_option( 'timezone_string' );
419
+
420
+
// Calculate the timezone manually if it is not available.
421
+
if ( ! $timezone ) {
422
+
$offset = (float) get_option( 'gmt_offset' );
423
+
$hours = (int) $offset;
424
+
$minutes = ( $offset - $hours ) * 60;
425
+
426
+
$sign = ( $offset < 0 ) ? '-' : '+';
427
+
$timezone = sprintf( '%s%02d:%02d', $sign, abs( $hours ), abs( $minutes ) );
428
+
}
429
+
430
+
try {
431
+
$timezone = new DateTimeZone( $timezone );
432
+
} catch ( Exception $exception ) {
433
+
return $datetime;
434
+
}
435
+
}
436
+
437
+
$datetime->setTimezone( $timezone );
438
+
return $datetime;
439
+
}
440
+
441
+
/**
442
+
* Retrieve the last modified time, nicely formatted for readability.
443
+
*
444
+
* @param boolean $include_html Whether to include HTML in the output.
445
+
*
446
+
* @return string
447
+
*/
448
+
public function format_modified( bool $include_html = true ): string {
449
+
if ( ! $this->modified ) {
450
+
return '';
451
+
}
452
+
453
+
$timestamp = $this->modified_timestamp;
454
+
$time_diff = time() - $timestamp;
455
+
$local_time = $this->modified_local;
456
+
457
+
if ( $time_diff >= 0 && $time_diff < YEAR_IN_SECONDS ) {
458
+
// translators: %s: Human-readable time difference.
459
+
$human_time = sprintf( __( '%s ago', 'code-snippets' ), human_time_diff( $timestamp ) );
460
+
} else {
461
+
$human_time = $local_time->format( __( 'Y/m/d', 'code-snippets' ) );
462
+
}
463
+
464
+
if ( ! $include_html ) {
465
+
return $human_time;
466
+
}
467
+
468
+
// translators: 1: date format, 2: time format.
469
+
$date_format = _x( '%1$s at %2$s', 'date and time format', 'code-snippets' );
470
+
$date_format = sprintf( $date_format, get_option( 'date_format' ), get_option( 'time_format' ) );
471
+
472
+
return sprintf( '<span title="%s">%s</span>', $local_time->format( $date_format ), $human_time );
473
+
}
474
+
475
+
/**
476
+
* Determine whether the current snippet type is pro-only.
477
+
*/
478
+
private function get_is_pro(): bool {
479
+
return 'css' === $this->type || 'js' === $this->type || 'cond' === $this->type;
480
+
}
481
+
482
+
/**
483
+
* Increment the revision number by one.
484
+
*/
485
+
public function increment_revision() {
486
+
++$this->revision;
487
+
}
488
+
}
489
+