Repository navigation
Expand file tree
/
Copy pathclass-plugin.php
More file actions
executable file
·594 lines (511 loc) · 17.4 KB
/
Copy pathclass-plugin.php
File metadata and controls
executable file
·594 lines (511 loc) · 17.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
<?php
/**
* Initializes plugin
*
* @package WP_Stream;
*/
namespace WP_Stream;
use RuntimeException;
/**
* Class Plugin
*/
class Plugin {
/**
* Plugin version number.
*
* TODO Maybe pass this as a constructor dependency?
*
* @const string
*/
const VERSION = '4.4.0';
/**
* WP-CLI command
*
* @const string
*/
const WP_CLI_COMMAND = 'stream';
/**
* Used to check if it's a single site, not multisite.
*
* @const string
*/
const SINGLE_SITE = 'single';
/**
* Used to check if it's a multisite with the plugin network enabled.
*
* @const string
*/
const MULTI_NETWORK = 'multisite-network';
/**
* Used to check if it's a multisite with the plugin not network enabled.
*
* @const string
*/
const MULTI_NOT_NETWORK = 'multisite-not-network';
/**
* Holds and manages WordPress Admin configurations.
*/
public ?Admin $admin = null;
/**
* Holds and manages alerts.
*/
public ?Alerts $alerts = null;
/**
* Holds and manages alerts lists.
*/
public ?Alerts_List $alerts_list = null;
/**
* Holds and manages WordPress Abilities API integration.
*/
public ?Abilities $abilities = null;
/**
* Holds and manages connectors
*/
public ?Connectors $connectors = null;
/**
* Holds and manages DB connections.
*/
public ?DB $db = null;
/**
* Holds and manages records.
*
* @var Log
*/
public $log;
/**
* Stores and manages WordPress settings.
*/
public ?Settings $settings = null;
/**
* Server-side backend for user pickers (native select / Ajax combobox).
*/
public ?User_Picker $user_picker = null;
/**
* Process DB migrations.
*/
public ?Install $install = null;
/**
* Backend used to schedule Stream's deferred work (purge / reset).
*
* Either an {@see AS_Scheduler} (Action Scheduler, default) or a
* {@see Cron_Scheduler} (WP-Cron fallback), selected at construction via
* the `wp_stream_use_action_scheduler` filter.
*/
public Scheduler $scheduler;
/**
* Whether the bundled Action Scheduler library was loaded.
*
* Set from a file_exists() check during boot() (called from the
* constructor), so it is reliable on `plugins_loaded` even though AS
* only declares its as_*() API later on `init`.
*/
protected bool $action_scheduler_available = false;
/**
* URLs and Paths used by the plugin
*/
public array $locations = array();
/**
* IP address for the current request to be associated with the log entry.
*
* @var null|false|string Valid IP address, null if not set, false if invalid.
*/
protected $client_ip_address;
/**
* Class constructor.
*
* Sets plugin locations that the autoloader needs, then boots subsystems.
*/
public function __construct() {
$locate = $this->locate_plugin();
$this->locations = array(
'plugin' => $locate['plugin_basename'],
'dir' => $locate['dir_path'],
'url' => $locate['dir_url'],
'inc_dir' => $locate['dir_path'] . 'includes/',
'class_dir' => $locate['dir_path'] . 'classes/',
);
$this->boot();
}
/**
* Load dependencies, instantiate subsystems, and register hooks.
*
* Called from the constructor after locations are set so the autoloader
* can resolve plugin classes. Hook names, priorities, and registration
* order match the previous constructor body.
*/
public function boot() {
spl_autoload_register( array( $this, 'autoload' ) );
// Determine the scheduler backend, then load Action Scheduler only if
// it is the selected backend. AS remains a hard, bundled dependency
// for the WordPress.org build (see issue #1907), but integrators who
// opt into the WP-Cron fallback via `wp_stream_use_action_scheduler`
// pay neither the require_once nor AS's own `init` bootstrap.
//
// Availability is tracked from a file_exists() check rather than
// function_exists(): AS only declares its as_*() API on `init`, but
// boot() runs at plugin-file inclusion time (before the
// `plugins_loaded` action fires), so the functions are not defined
// yet. Scheduler methods are only ever called on/after `init`
// (wp_loaded, AJAX, cron), by which point the API is loaded.
$action_scheduler = $this->locations['dir'] . '/vendor/woocommerce/action-scheduler/action-scheduler.php';
$this->action_scheduler_available = file_exists( $action_scheduler );
// Select the scheduler backend for Stream's deferred work, loading the
// bundled AS library only if it is the chosen backend.
$this->scheduler = $this->create_scheduler();
// Load helper functions.
require_once $this->locations['inc_dir'] . 'functions.php';
// Load DB helper interface/class.
/**
* Filter the database driver class name used by Stream.
*
* @param string $driver_class Database driver class name.
*/
$driver_class = apply_filters( 'wp_stream_db_driver', '\WP_Stream\DB_Driver_WPDB' );
$driver = null;
if ( class_exists( $driver_class ) ) {
$driver = new $driver_class();
$this->db = new DB( $driver );
}
$error = false;
if ( ! $this->db ) {
$error = esc_html__( 'Stream: Could not load chosen DB driver.', 'stream' );
} elseif ( ! $driver instanceof DB_Driver ) {
$error = esc_html__( 'Stream: DB driver must implement DB Driver interface.', 'stream' );
}
if ( $error ) {
wp_die(
esc_html( $error ),
esc_html__( 'Stream DB Error', 'stream' )
);
}
// Load logger class.
/**
* Filter the log handler instance used to persist Stream records.
*
* @param Log $log Log handler instance.
*/
$this->log = apply_filters( 'wp_stream_log_handler', new Log( $this ) );
// Set the IP address for the current request.
$this->client_ip_address = wp_stream_filter_input( INPUT_SERVER, 'REMOTE_ADDR', FILTER_VALIDATE_IP );
// Load settings and connectors after widgets_init and before the default init priority.
add_action( 'init', array( $this, 'init' ), 9 );
// Add frontend indicator.
add_action( 'wp_head', array( $this, 'frontend_indicator' ) );
// Change DB driver after plugin loaded if any add-ons want to replace.
add_action( 'plugins_loaded', array( $this, 'plugins_loaded' ), 20 );
// Load admin area classes.
if ( is_admin() || ( defined( 'WP_STREAM_DEV_DEBUG' ) && WP_STREAM_DEV_DEBUG ) || ( defined( 'WP_CLI' ) && WP_CLI ) ) {
$this->admin = new Admin( $this );
$install = $driver->setup_storage( $this );
if ( ! $install instanceof Install ) {
wp_die(
esc_html__( 'Stream: DB driver setup_storage() must return an Install instance.', 'stream' ),
esc_html__( 'Stream DB Error', 'stream' )
);
}
$this->install = $install;
} elseif ( defined( 'DOING_CRON' ) && DOING_CRON ) {
$this->admin = new Admin( $this, $driver );
}
// Load WP-CLI command.
if ( defined( 'WP_CLI' ) && WP_CLI ) {
\WP_CLI::add_command( self::WP_CLI_COMMAND, 'WP_Stream\CLI' );
}
}
/**
* Build the scheduler backend for Stream's deferred work (purge / reset).
*
* Defaults to Action Scheduler when its bundled library is present.
* Integrators that bundle Stream and run reliable cron (e.g. Cavalcade)
* can force the WP-Cron fallback by returning false from
* `wp_stream_use_action_scheduler`. When the fallback is chosen, the
* bundled AS library is not loaded at all (no require_once, no AS `init`
* bootstrap). See issue #1907.
*
* @return Scheduler
*/
public function create_scheduler() {
/**
* Filter whether Stream uses Action Scheduler for its deferred work.
*
* IMPORTANT — timing: this filter is applied in Plugin::boot(),
* which runs from the constructor when the Stream plugin file is
* included, i.e. BEFORE the `plugins_loaded` action. Callbacks must
* therefore be registered from code that loads before Stream: an
* mu-plugin, wp-config.php, or a plugin guaranteed to load earlier.
* Registering it from a regular plugin's `plugins_loaded` hook is too
* late and will be ignored.
*
* @param bool $use_action_scheduler Whether to use Action Scheduler.
* Defaults to true when the bundled
* AS library is present. Return a
* real boolean: the value is cast
* with (bool), so PHP string
* truthiness applies to strings
* (e.g. 'false' is truthy).
*/
$use_action_scheduler = (bool) apply_filters(
'wp_stream_use_action_scheduler',
$this->action_scheduler_available
);
if ( ! $use_action_scheduler ) {
return new Cron_Scheduler();
}
// Guard a forced `__return_true` override when the bundled AS library
// is absent: returning AS_Scheduler without loading AS would fatal on
// the first unguarded as_*() call. Fall back to the cron scheduler
// instead. The default path never hits this — the filter defaults to
// $this->action_scheduler_available.
if ( ! $this->action_scheduler_available ) {
return new Cron_Scheduler();
}
// Load the bundled AS library before instantiating its scheduler.
// AS's own ActionScheduler_Versions arbitration handles a host that
// already provides its own copy.
require_once $this->locations['dir'] . '/vendor/woocommerce/action-scheduler/action-scheduler.php';
return new AS_Scheduler();
}
/**
* Autoloader for classes
*
* @param string $class_name Fully qualified classname to be loaded.
*/
public function autoload( $class_name ) {
if ( ! preg_match( '/^(?P<namespace>.+)\\\\(?P<autoload>[^\\\\]+)$/', $class_name, $matches ) ) {
return;
}
static $reflection;
if ( empty( $reflection ) ) {
$reflection = new \ReflectionObject( $this );
}
if ( $reflection->getNamespaceName() !== $matches['namespace'] ) {
return;
}
$autoload_name = $matches['autoload'];
$autoload_dir = \trailingslashit( $this->locations['class_dir'] );
$autoload_path = sprintf( '%sclass-%s.php', $autoload_dir, strtolower( str_replace( '_', '-', $autoload_name ) ) );
if ( is_readable( $autoload_path ) ) {
require_once $autoload_path;
}
}
/**
* Load Settings, Notifications, and Connectors
*
* @action init
*/
public function init() {
$this->settings = new Settings( $this );
$this->connectors = new Connectors( $this );
$this->alerts = new Alerts( $this );
$this->alerts_list = new Alerts_List( $this );
$this->abilities = new Abilities( $this );
$this->user_picker = new User_Picker();
}
/**
* Displays an HTML comment in the frontend head to indicate that Stream is activated,
* and which version of Stream is currently in use.
*
* @action wp_head
*
* @return string|void An HTML comment, or nothing if the value is filtered out.
*/
public function frontend_indicator() {
/* translators: Localization not needed */
$comment = sprintf( 'Stream WordPress user activity plugin v%s', esc_html( $this->get_version() ) );
/**
* Filter allows the HTML output of the frontend indicator comment
* to be altered or removed, if desired.
*
* @param string $comment The content of the HTML comment
*/
$comment = apply_filters( 'wp_stream_frontend_indicator', $comment );
if ( ! empty( $comment ) ) {
printf( "<!-- %s -->\n", esc_html( $comment ) );
}
}
/**
* Version of plugin_dir_url() which works for plugins installed in the plugins directory,
* and for plugins bundled with themes.
*
* @return array
*/
private function locate_plugin() {
$dir_url = trailingslashit( plugins_url( '', __DIR__ ) );
$dir_path = plugin_dir_path( __DIR__ );
$dir_basename = basename( $dir_path );
$plugin_basename = trailingslashit( $dir_basename ) . 'stream.php';
return compact( 'dir_url', 'dir_path', 'dir_basename', 'plugin_basename' );
}
/**
* Getter for the version number.
*
* @return string
*/
public function get_version() {
return self::VERSION;
}
/**
* Change plugin database driver in case driver plugin loaded after stream
*/
public function plugins_loaded() {
// Load DB helper interface/class.
/** This filter is documented in classes/class-plugin.php in Plugin::boot() */
$driver_class = apply_filters( 'wp_stream_db_driver', '\WP_Stream\DB_Driver_WPDB' );
if ( class_exists( $driver_class ) ) {
$driver = new $driver_class();
$this->db = new DB( $driver );
}
}
/**
* Returns true if Stream is network activated, otherwise false
*
* @return bool
*/
public function is_network_activated() {
$is_network_activated = false;
if ( $this->is_mustuse() ) {
$is_network_activated = true;
} else {
if ( ! function_exists( 'is_plugin_active_for_network' ) ) {
require_once ABSPATH . '/wp-admin/includes/plugin.php';
}
$is_network_activated = is_plugin_active_for_network( $this->locations['plugin'] );
}
/**
* Filter allows the network activated detection to be overridden.
*
* @param string $is_network_activated Whether the plugin is network activated.
* @param WP_Stream\Plugin $plugin The stream plugin object.
*/
return apply_filters( 'wp_stream_is_network_activated', $is_network_activated, $this );
}
/**
* Returns true if Stream is a must-use plugin, otherwise false
*
* @return bool
*/
public function is_mustuse() {
$stream_php = trailingslashit( WPMU_PLUGIN_DIR ) . $this->locations['plugin'];
if ( file_exists( $stream_php ) && class_exists( 'WP_Stream\Plugin' ) ) {
return true;
}
return false;
}
/**
* Get the IP address for the current request.
*
* @return false|null|string Valid IP address, null if not set, false if invalid.
*/
public function get_client_ip_address() {
/**
* Filter the client IP address stored on Stream records.
*
* @param string|false|null $client_ip_address
*/
return apply_filters( 'wp_stream_client_ip_address', $this->client_ip_address );
}
/**
* Get the site type.
*
* This function determines the type of site based on whether it is a single site or a multisite.
* If it is a multisite, it also checks if it is network activated or not.
*
* @return string The site type
*/
public function get_site_type(): string {
// If it's a multisite, is it network activated or not?
if ( is_multisite() ) {
return $this->is_network_activated() ? self::MULTI_NETWORK : self::MULTI_NOT_NETWORK;
}
return self::SINGLE_SITE;
}
/**
* Should the number of records which need to be processed be considered "large"?
*
* @param int $record_number The number of rows in the {$wpdb->prefix}_stream table to be processed.
* @return bool Whether or not this should be considered large.
*/
public function is_large_records_table( int $record_number ): bool {
/**
* Filters whether or not the number of records should be considered a large table.
*
* @since 4.1.0
*
* @param bool $is_large_table Whether or not the number of records should be considered large.
* @param int $record_number The number of records being checked.
*/
return apply_filters( 'wp_stream_is_large_records_table', $record_number > 1000000, $record_number );
}
/**
* Checks if the plugin is running on a single site installation.
*
* @return bool True if the plugin is running on a single site installation, false otherwise.
*/
public function is_single_site() {
return self::SINGLE_SITE === $this->get_site_type();
}
/**
* Check if the plugin is activated on a multisite installation but not network activated.
*
* @return bool True if the plugin is activated on a multisite installation but not network activated, false otherwise.
*/
public function is_multisite_not_network_activated() {
return self::MULTI_NOT_NETWORK === $this->get_site_type();
}
/**
* Check if the plugin is activated on a multisite network.
*
* @return bool True if the plugin is network activated on a multisite, false otherwise.
*/
public function is_multisite_network_activated() {
return self::MULTI_NETWORK === $this->get_site_type();
}
/**
* Enqueue a script along with a stylesheet if it exists.
*
* @param string $handle Script handle.
* @param array $additional_dependencies Additional dependencies.
* @param array $data Data to pass to the script.
*
* @throws RuntimeException If built JavaScript assets are not found.
* @return void
*/
public function enqueue_asset( $handle, $additional_dependencies = array(), $data = array() ) {
// If is enqueued already, bail out.
if ( wp_script_is( $handle ) ) {
return;
}
$path = untrailingslashit( $this->locations['dir'] );
$url = untrailingslashit( $this->locations['url'] );
$script_asset_path = "$path/build/$handle.asset.php";
if ( ! file_exists( $script_asset_path ) ) {
throw new RuntimeException( 'Built JavaScript assets not found. Please run `npm run build`' );
}
$script_asset = require $script_asset_path; // phpcs:ignore WPThemeReview.CoreFunctionality.FileInclude.FileIncludeFound -- reason: webpack asset.php returns a generated dependency array, not a template.
wp_enqueue_script(
"wp-stream-$handle",
"$url/build/$handle.js",
array_merge(
$script_asset['dependencies'],
(array) $additional_dependencies
),
$script_asset['version'],
true
);
if ( file_exists( "$path/build/$handle.css" ) ) {
wp_enqueue_style(
"wp-stream-$handle",
"$url/build/$handle.css",
array(),
$script_asset['version']
);
}
if ( ! empty( $data ) ) {
wp_add_inline_script(
"wp-stream-$handle",
sprintf( 'window["%s"] = %s;', esc_attr( "wp-stream-$handle" ), wp_json_encode( $data ) ),
'before'
);
}
}
}