Easy Updates Manager - Version 9.0.14

Version Description

  • 2022-10-24 =

  • TWEAK: Add admin notice for DISABLE_WP_CRON constant and remove notices regarding WP_AUTO_UPDATE_CORE and AUTOMATIC_UPDATER_DISABLED constants as they now won't prevent automatic updates from being run

  • FIX: Core minor updates became major when two updates-related scheduled events got triggered in the same process

  • FIX: In some cases the serialisation of call stack could cause a PHP fatal error due to the output of a backtrace (debug_backtrace) containing 'Closure' (anonymous) functions

  • TWEAK: Update notice class

Download this release

Release Info

Developer DavidAnderson
Plugin Icon 128x128 Easy Updates Manager
Version 9.0.14
Comparing to
See all releases

Code changes from version 9.0.13 to 9.0.14

Files changed (37) hide show
  1. includes/MPSUM_Admin.php +1 -1
  2. includes/MPSUM_Constant_Checks.php +7 -9
  3. includes/MPSUM_Disable_Updates.php +18 -8
  4. includes/MPSUM_Logs.php +43 -4
  5. {js → includes/blockui}/jquery.blockUI.js +16 -13
  6. includes/blockui/jquery.blockUI.min.js +1 -0
  7. includes/easy-updates-manager-notices.php +2 -2
  8. js/jquery.blockUI.min.js +0 -1
  9. languages/stops-core-theme-and-plugin-updates.pot +12 -12
  10. main.php +9 -9
  11. readme.txt +9 -2
  12. templates/notices/dashboard-constant-warning.php +12 -15
  13. vendor/autoload.php +7 -0
  14. vendor/composer/ClassLoader.php +481 -0
  15. vendor/composer/InstalledVersions.php +337 -0
  16. vendor/composer/LICENSE +21 -0
  17. vendor/composer/autoload_classmap.php +10 -0
  18. vendor/composer/autoload_namespaces.php +9 -0
  19. vendor/composer/autoload_psr4.php +9 -0
  20. vendor/composer/autoload_real.php +55 -0
  21. vendor/composer/autoload_static.php +20 -0
  22. vendor/composer/installed.json +42 -0
  23. vendor/composer/installed.php +32 -0
  24. vendor/team-updraft/common-libs/CI/php-compatibility.xml +17 -0
  25. vendor/team-updraft/common-libs/CI/php-syntax-check.xml +12 -0
  26. vendor/team-updraft/common-libs/README.md +10 -0
  27. vendor/team-updraft/common-libs/composer.json +21 -0
  28. {includes → vendor/team-updraft/common-libs/src/updraft-notices}/updraft-notices.php +57 -36
  29. vendor/team-updraft/common-libs/src/updraft-rpc/class-udrpc.php +1115 -0
  30. vendor/team-updraft/common-libs/src/updraft-semaphore/class-updraft-semaphore.php +213 -0
  31. vendor/team-updraft/common-libs/src/updraft-semaphore/test.php +56 -0
  32. vendor/team-updraft/common-libs/src/updraft-tasks/class-updraft-task-manager-commands.php +188 -0
  33. vendor/team-updraft/common-libs/src/updraft-tasks/class-updraft-task-manager.php +387 -0
  34. vendor/team-updraft/common-libs/src/updraft-tasks/class-updraft-task-meta.php +96 -0
  35. vendor/team-updraft/common-libs/src/updraft-tasks/class-updraft-task-options.php +127 -0
  36. vendor/team-updraft/common-libs/src/updraft-tasks/class-updraft-task.php +693 -0
  37. vendor/team-updraft/common-libs/src/updraft-tasks/class-updraft-tasks-activation.php +207 -0
includes/MPSUM_Admin.php CHANGED
@@ -271,7 +271,7 @@ class MPSUM_Admin {
271
  wp_enqueue_style('thickbox');
272
  }
273
 
274
- wp_enqueue_script('jquery-blockui', MPSUM_Updates_Manager::get_plugin_url('/js/jquery.blockUI' . $min_or_not . '.js'), array('jquery'), EASY_UPDATES_MANAGER_VERSION, true);
275
  wp_enqueue_script('jquery-serializejson', MPSUM_Updates_Manager::get_plugin_url('/js/jquery.serializejson' . $min_or_not . '.js'), array('jquery'), EASY_UPDATES_MANAGER_VERSION, true);
276
  wp_enqueue_script('mpsum_dashboard_js', MPSUM_Updates_Manager::get_plugin_url('/js/eum-admin' . $min_or_not .'.js'), array( 'jquery', 'wp-ajax-response' ), EASY_UPDATES_MANAGER_VERSION, true);
277
  wp_enqueue_script('mpsum_dashboard_react', MPSUM_Updates_Manager::get_plugin_url('/js/admin' . $min_or_not . '.js'), array( 'jquery', 'mpsum_dashboard_js' ), EASY_UPDATES_MANAGER_VERSION, true);
271
  wp_enqueue_style('thickbox');
272
  }
273
 
274
+ wp_enqueue_script('jquery-blockui', MPSUM_Updates_Manager::get_plugin_url('/includes/blockui/jquery.blockUI' . $min_or_not . '.js'), array('jquery'), EASY_UPDATES_MANAGER_VERSION, true);
275
  wp_enqueue_script('jquery-serializejson', MPSUM_Updates_Manager::get_plugin_url('/js/jquery.serializejson' . $min_or_not . '.js'), array('jquery'), EASY_UPDATES_MANAGER_VERSION, true);
276
  wp_enqueue_script('mpsum_dashboard_js', MPSUM_Updates_Manager::get_plugin_url('/js/eum-admin' . $min_or_not .'.js'), array( 'jquery', 'wp-ajax-response' ), EASY_UPDATES_MANAGER_VERSION, true);
277
  wp_enqueue_script('mpsum_dashboard_react', MPSUM_Updates_Manager::get_plugin_url('/js/admin' . $min_or_not . '.js'), array( 'jquery', 'mpsum_dashboard_js' ), EASY_UPDATES_MANAGER_VERSION, true);
includes/MPSUM_Constant_Checks.php CHANGED
@@ -30,17 +30,15 @@ class MPSUM_CONSTANT_CHECKS {
30
  }
31
 
32
  /**
33
- * Outputs feature heading
34
  *
35
- * @return bool true if there are constants that disable EUM, false if not.
36
  */
37
- public function is_config_options_disabled() {
38
- if (defined('AUTOMATIC_UPDATER_DISABLED') && true === AUTOMATIC_UPDATER_DISABLED) {
39
- return true;
 
40
  }
41
- if (defined('WP_AUTO_UPDATE_CORE') && (false === WP_AUTO_UPDATE_CORE || 'minor' === WP_AUTO_UPDATE_CORE)) {
42
- return true;
43
- }
44
- return false;
45
  }
46
  }
30
  }
31
 
32
  /**
33
+ * Get a list of constants which are active but prohibited
34
  *
35
+ * @return array a list of constants that may prevent automatic updates from being work properly
36
  */
37
+ public function get_prohibited_active_constants() {
38
+ $constants = array();
39
+ if (defined('DISABLE_WP_CRON') && DISABLE_WP_CRON) {
40
+ $constants[] = 'DISABLE_WP_CRON';
41
  }
42
+ return $constants;
 
 
 
43
  }
44
  }
includes/MPSUM_Disable_Updates.php CHANGED
@@ -16,6 +16,8 @@ class MPSUM_Disable_Updates {
16
  */
17
  private static $instance = null;
18
 
 
 
19
  /**
20
  * Set a class instance.
21
  *
@@ -85,6 +87,10 @@ class MPSUM_Disable_Updates {
85
  // Core Minor Updates
86
  add_filter('allow_minor_auto_core_updates', array($this, 'core_should_update_to_new_version'), PHP_INT_MAX - 10);
87
 
 
 
 
 
88
  // Manually update / Disables Core Automatic Updates
89
  // When the __return_false function is hooked to the three filters above, that means core automatic updates is disabled or it's a manually update
90
  }
@@ -369,24 +375,21 @@ class MPSUM_Disable_Updates {
369
  /**
370
  * Determine whether WordPress Core should update to a new version or not
371
  *
372
- * @param boolean $upgrade Whether to update or not
373
  * @return boolean True if we should update to the new version, false otherwise
374
  */
375
- public function core_should_update_to_new_version($upgrade) {
376
 
377
  $core_options = MPSUM_Updates_Manager::get_options('core');
378
 
379
  if (doing_filter('allow_dev_auto_core_updates')) {
380
- $upgrade = isset($core_options['core_updates']) && in_array($core_options['core_updates'], array('automatic', 'automatic_minor')) && isset($core_options['automatic_development_updates']) && 'on' == $core_options['automatic_development_updates'] ? true : false;
381
  } elseif (doing_filter('allow_major_auto_core_updates')) {
382
- $upgrade = isset($core_options['core_updates']) && 'automatic' === $core_options['core_updates'] ? true : false;
383
  } elseif (doing_filter('allow_minor_auto_core_updates')) {
384
- $upgrade = isset($core_options['core_updates']) && in_array($core_options['core_updates'], array('automatic', 'automatic_minor')) ? true : false;
385
  }
386
-
387
- add_filter('auto_update_core', $upgrade ? '__return_true' : '__return_false', PHP_INT_MAX - 10);
388
 
389
- return $upgrade;
390
  }
391
 
392
  /**
@@ -400,4 +403,11 @@ class MPSUM_Disable_Updates {
400
  if ('automatic_updater' === $context) return true;
401
  return $file_mod_allowed;
402
  }
 
 
 
 
 
 
 
403
  }
16
  */
17
  private static $instance = null;
18
 
19
+ private $is_core_updating_allowed = true;
20
+
21
  /**
22
  * Set a class instance.
23
  *
87
  // Core Minor Updates
88
  add_filter('allow_minor_auto_core_updates', array($this, 'core_should_update_to_new_version'), PHP_INT_MAX - 10);
89
 
90
+ // Core Global Updates - a hook for making final decision as to whether core updating is allowed after examing other core-related hooks, also ensuring that no other filters will override our value
91
+ add_filter('auto_update_core', array($this, 'is_core_updating_allowed'), PHP_INT_MAX - 10);
92
+ if (!isset($core_options['core_updates']) || 'on' == $core_options['core_updates']) $this->is_core_updating_allowed = false; // on means manually update
93
+
94
  // Manually update / Disables Core Automatic Updates
95
  // When the __return_false function is hooked to the three filters above, that means core automatic updates is disabled or it's a manually update
96
  }
375
  /**
376
  * Determine whether WordPress Core should update to a new version or not
377
  *
 
378
  * @return boolean True if we should update to the new version, false otherwise
379
  */
380
+ public function core_should_update_to_new_version() {
381
 
382
  $core_options = MPSUM_Updates_Manager::get_options('core');
383
 
384
  if (doing_filter('allow_dev_auto_core_updates')) {
385
+ $this->is_core_updating_allowed = isset($core_options['core_updates']) && in_array($core_options['core_updates'], array('automatic', 'automatic_minor')) && isset($core_options['automatic_development_updates']) && 'on' == $core_options['automatic_development_updates'] ? true : false;
386
  } elseif (doing_filter('allow_major_auto_core_updates')) {
387
+ $this->is_core_updating_allowed = isset($core_options['core_updates']) && 'automatic' === $core_options['core_updates'] ? true : false;
388
  } elseif (doing_filter('allow_minor_auto_core_updates')) {
389
+ $this->is_core_updating_allowed = isset($core_options['core_updates']) && in_array($core_options['core_updates'], array('automatic', 'automatic_minor')) ? true : false;
390
  }
 
 
391
 
392
+ return $this->is_core_updating_allowed;
393
  }
394
 
395
  /**
403
  if ('automatic_updater' === $context) return true;
404
  return $file_mod_allowed;
405
  }
406
+
407
+ /**
408
+ * Return the value that previously has been looked over the core-related filters as to whether core updating is allowed or not
409
+ */
410
+ public function is_core_updating_allowed() {
411
+ return $this->is_core_updating_allowed;
412
+ }
413
  }
includes/MPSUM_Logs.php CHANGED
@@ -99,11 +99,18 @@ class MPSUM_Logs {
99
  */
100
  public function perform_shutdown_task() {
101
  if (!is_array($this->log_messages) || empty($this->log_messages)) return;
102
- $stacktrace = maybe_serialize(debug_backtrace(false)); // phpcs:ignore PHPCompatibility.FunctionUse.ArgumentFunctionsReportCurrentValue.NeedsInspection
 
 
 
 
 
 
 
103
  foreach ($this->log_messages as $type => $entities) {
104
  if (!is_array($entities) || empty($entities)) continue;
105
  foreach ($entities as $data) {
106
- $this->insert_log($data['name'], $type, $data['from'], $data['to'], $this->auto_update ? 'automatic' : 'manual', 0, get_current_user_id(), '', $stacktrace);
107
  }
108
  }
109
  }
@@ -253,7 +260,14 @@ class MPSUM_Logs {
253
  */
254
  public function log_updates($wp_upgrader, $hook_extra) {
255
  if (isset($hook_extra['action']) && 'update' !== $hook_extra['action']) return;
256
- $stacktrace = maybe_serialize(debug_backtrace(false)); // phpcs:ignore PHPCompatibility.FunctionUse.ArgumentFunctionsReportCurrentValue.NeedsInspection
 
 
 
 
 
 
 
257
  $notes = '';
258
  if (is_a($wp_upgrader->skin, 'Automatic_Upgrader_Skin')) {
259
  foreach ($wp_upgrader->skin->get_upgrade_messages() as $message) {
@@ -362,7 +376,14 @@ class MPSUM_Logs {
362
  */
363
  public function log_automatic_updates($update_results) {
364
  if (empty($update_results)) return;
365
- $stacktrace = maybe_serialize(debug_backtrace(false)); // phpcs:ignore PHPCompatibility.FunctionUse.ArgumentFunctionsReportCurrentValue.NeedsInspection
 
 
 
 
 
 
 
366
  foreach ($update_results as $type => $results) {
367
  foreach ($results as $result) {
368
  $notes = '';
@@ -629,4 +650,22 @@ class MPSUM_Logs {
629
  $wpdb->query($sql);
630
  delete_site_option('mpsum_log_table_version');
631
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
632
  }
99
  */
100
  public function perform_shutdown_task() {
101
  if (!is_array($this->log_messages) || empty($this->log_messages)) return;
102
+ try {
103
+ $stacktrace = maybe_serialize(apply_filters('eum_normalized_call_stack_args', $this->normalise_call_stack_args(debug_backtrace(false)))); // phpcs:ignore PHPCompatibility.FunctionUse.ArgumentFunctionsReportCurrentValue.NeedsInspection
104
+ } catch (Exception $e) {
105
+ $stacktrace = serialize(array()); // if an exception still happens even after the call stack is already normalised then we won't provide stacktrace for a log entry
106
+ // @codingStandardsIgnoreLine
107
+ } catch (Error $e) {
108
+ $stacktrace = serialize(array());
109
+ }
110
  foreach ($this->log_messages as $type => $entities) {
111
  if (!is_array($entities) || empty($entities)) continue;
112
  foreach ($entities as $data) {
113
+ $this->insert_log($data['name'], $type, $data['from'], $data['to'], $this->auto_update ? 'automatic' : 'manual', doing_action('upgrader_process_complete') || doing_action('automatic_updates_complete') ? 1 : 0, get_current_user_id(), '', $stacktrace);
114
  }
115
  }
116
  }
260
  */
261
  public function log_updates($wp_upgrader, $hook_extra) {
262
  if (isset($hook_extra['action']) && 'update' !== $hook_extra['action']) return;
263
+ try {
264
+ $stacktrace = maybe_serialize(apply_filters('eum_normalized_call_stack_args', $this->normalise_call_stack_args(debug_backtrace(false)))); // phpcs:ignore PHPCompatibility.FunctionUse.ArgumentFunctionsReportCurrentValue.NeedsInspection
265
+ } catch (Exception $e) {
266
+ $stacktrace = serialize(array()); // if an exception still happens even after the call stack is already normalised then we won't provide stacktrace for a log entry
267
+ // @codingStandardsIgnoreLine
268
+ } catch (Error $e) {
269
+ $stacktrace = serialize(array());
270
+ }
271
  $notes = '';
272
  if (is_a($wp_upgrader->skin, 'Automatic_Upgrader_Skin')) {
273
  foreach ($wp_upgrader->skin->get_upgrade_messages() as $message) {
376
  */
377
  public function log_automatic_updates($update_results) {
378
  if (empty($update_results)) return;
379
+ try {
380
+ $stacktrace = maybe_serialize(apply_filters('eum_normalized_call_stack_args', $this->normalise_call_stack_args(debug_backtrace(false)))); // phpcs:ignore PHPCompatibility.FunctionUse.ArgumentFunctionsReportCurrentValue.NeedsInspection
381
+ } catch (Exception $e) {
382
+ $stacktrace = serialize(array()); // if an exception still happens even after the call stack is already normalised then we won't provide stacktrace for a log entry
383
+ // @codingStandardsIgnoreLine
384
+ } catch (Error $e) {
385
+ $stacktrace = serialize(array());
386
+ }
387
  foreach ($update_results as $type => $results) {
388
  foreach ($results as $result) {
389
  $notes = '';
650
  $wpdb->query($sql);
651
  delete_site_option('mpsum_log_table_version');
652
  }
653
+
654
+ /**
655
+ * Normalise call stacks by clearing out unnecessary objects from their arguments list, leaving only the first arguments as a string. The call stacks should be one that is generated by debug_backtrace() function.
656
+ *
657
+ * @param array $backtrace The output of the debug_backtrace() function
658
+ * @return array An array of associative arrays after being normalised
659
+ */
660
+ public function normalise_call_stack_args($backtrace) {
661
+ foreach ((array) $backtrace as $index => $element) {
662
+ if (!isset($element['args']) || !is_array($element['args']) || !isset($element['args'][0])) $backtrace[$index]['args'] = array('');
663
+ if (is_object($backtrace[$index]['args'][0])) {
664
+ $backtrace[$index]['args'] = array(get_class($backtrace[$index]['args'][0]));
665
+ } elseif (!is_string($backtrace[$index]['args'][0])) {
666
+ $backtrace[$index]['args'] = array('');
667
+ }
668
+ }
669
+ return $backtrace;
670
+ }
671
  }
{js → includes/blockui}/jquery.blockUI.js RENAMED
@@ -1,7 +1,7 @@
1
  /*!
2
  * jQuery blockUI plugin
3
- * Version 2.70.0-2014.11.23
4
- * Requires jQuery v1.7 or later
5
  *
6
  * Examples at: http://malsup.com/jquery/block/
7
  * Copyright (c) 2007-2013 M. Alsup
@@ -17,6 +17,9 @@
17
  "use strict";
18
 
19
  function setup($) {
 
 
 
20
  $.fn._fadeIn = $.fn.fadeIn;
21
 
22
  var noOp = $.noop || function() {};
@@ -26,7 +29,7 @@
26
  var msie = /MSIE/.test(navigator.userAgent);
27
  var ie6 = /MSIE 6.0/.test(navigator.userAgent) && ! /MSIE 8.0/.test(navigator.userAgent);
28
  var mode = document.documentMode || 0;
29
- var setExpr = $.isFunction( document.createElement('div').style.setExpression );
30
 
31
  // global $ methods for blocking/unblocking the entire page
32
  $.blockUI = function(opts) { install(window, opts); };
@@ -57,7 +60,7 @@
57
 
58
  callBlock();
59
  var nonmousedOpacity = $m.css('opacity');
60
- $m.mouseover(function() {
61
  callBlock({
62
  fadeIn: 0,
63
  timeout: 30000
@@ -66,7 +69,7 @@
66
  var displayBlock = $('.blockMsg');
67
  displayBlock.stop(); // cancel fadeout if it has started
68
  displayBlock.fadeTo(300, 1); // make it easier to read the message by removing transparency
69
- }).mouseout(function() {
70
  $('.blockMsg').fadeOut(1000);
71
  });
72
  // End konapun additions
@@ -358,14 +361,14 @@
358
  }
359
 
360
  // ie7 must use absolute positioning in quirks mode and to account for activex issues (when scrolling)
361
- var expr = setExpr && (!$.support.boxModel || $('object,embed', full ? null : el).length > 0);
362
  if (ie6 || expr) {
363
  // give body 100% height
364
- if (full && opts.allowBodyStretch && $.support.boxModel)
365
  $('html,body').css('height','100%');
366
 
367
  // fix ie6 issue when blocked element has a border width
368
- if ((ie6 || !$.support.boxModel) && !full) {
369
  var t = sz(el,'borderTopWidth'), l = sz(el,'borderLeftWidth');
370
  var fixT = t ? '(0 - '+t+')' : 0;
371
  var fixL = l ? '(0 - '+l+')' : 0;
@@ -377,11 +380,11 @@
377
  s.position = 'absolute';
378
  if (i < 2) {
379
  if (full)
380
- s.setExpression('height','Math.max(document.body.scrollHeight, document.body.offsetHeight) - (jQuery.support.boxModel?0:'+opts.quirksmodeOffsetHack+') + "px"');
381
  else
382
  s.setExpression('height','this.parentNode.offsetHeight + "px"');
383
  if (full)
384
- s.setExpression('width','jQuery.support.boxModel && document.documentElement.clientWidth || document.body.clientWidth + "px"');
385
  else
386
  s.setExpression('width','this.parentNode.offsetWidth + "px"');
387
  if (fixL) s.setExpression('left', fixL);
@@ -551,9 +554,9 @@
551
  // bind anchors and inputs for mouse and key events
552
  var events = 'mousedown mouseup keydown keypress keyup touchstart touchend touchmove';
553
  if (b)
554
- $(document).bind(events, opts, handler);
555
  else
556
- $(document).unbind(events, handler);
557
 
558
  // former impl...
559
  // var $e = $('a,:input');
@@ -606,7 +609,7 @@
606
  function sz(el, p) {
607
  return parseInt($.css(el,p),10)||0;
608
  }
609
-
610
  }
611
 
612
 
1
  /*!
2
  * jQuery blockUI plugin
3
+ * Version 2.71.0-2020.12.08
4
+ * Requires jQuery v1.12 or later
5
  *
6
  * Examples at: http://malsup.com/jquery/block/
7
  * Copyright (c) 2007-2013 M. Alsup
17
  "use strict";
18
 
19
  function setup($) {
20
+ var migrateDeduplicateWarnings = jQuery.migrateDeduplicateWarnings || false;
21
+ jQuery.migrateDeduplicateWarnings = false;
22
+
23
  $.fn._fadeIn = $.fn.fadeIn;
24
 
25
  var noOp = $.noop || function() {};
29
  var msie = /MSIE/.test(navigator.userAgent);
30
  var ie6 = /MSIE 6.0/.test(navigator.userAgent) && ! /MSIE 8.0/.test(navigator.userAgent);
31
  var mode = document.documentMode || 0;
32
+ var setExpr = "function" === typeof document.createElement('div').style.setExpression;
33
 
34
  // global $ methods for blocking/unblocking the entire page
35
  $.blockUI = function(opts) { install(window, opts); };
60
 
61
  callBlock();
62
  var nonmousedOpacity = $m.css('opacity');
63
+ $m.on('mouseover', function() {
64
  callBlock({
65
  fadeIn: 0,
66
  timeout: 30000
69
  var displayBlock = $('.blockMsg');
70
  displayBlock.stop(); // cancel fadeout if it has started
71
  displayBlock.fadeTo(300, 1); // make it easier to read the message by removing transparency
72
+ }).on('mouseout', function() {
73
  $('.blockMsg').fadeOut(1000);
74
  });
75
  // End konapun additions
361
  }
362
 
363
  // ie7 must use absolute positioning in quirks mode and to account for activex issues (when scrolling)
364
+ var expr = setExpr && ( "CSS1Compat" !== document.compatMode || $('object,embed', full ? null : el).length > 0);
365
  if (ie6 || expr) {
366
  // give body 100% height
367
+ if (full && opts.allowBodyStretch && "CSS1Compat" === document.compatMode)
368
  $('html,body').css('height','100%');
369
 
370
  // fix ie6 issue when blocked element has a border width
371
+ if ((ie6 || "CSS1Compat" !== document.compatMode) && !full) {
372
  var t = sz(el,'borderTopWidth'), l = sz(el,'borderLeftWidth');
373
  var fixT = t ? '(0 - '+t+')' : 0;
374
  var fixL = l ? '(0 - '+l+')' : 0;
380
  s.position = 'absolute';
381
  if (i < 2) {
382
  if (full)
383
+ s.setExpression('height','Math.max(document.body.scrollHeight, document.body.offsetHeight) - ("CSS1Compat" === document.compatMode?0:'+opts.quirksmodeOffsetHack+') + "px"');
384
  else
385
  s.setExpression('height','this.parentNode.offsetHeight + "px"');
386
  if (full)
387
+ s.setExpression('width','"CSS1Compat" === document.compatMode && document.documentElement.clientWidth || document.body.clientWidth + "px"');
388
  else
389
  s.setExpression('width','this.parentNode.offsetWidth + "px"');
390
  if (fixL) s.setExpression('left', fixL);
554
  // bind anchors and inputs for mouse and key events
555
  var events = 'mousedown mouseup keydown keypress keyup touchstart touchend touchmove';
556
  if (b)
557
+ $(document).on(events, opts, handler);
558
  else
559
+ $(document).off(events, handler);
560
 
561
  // former impl...
562
  // var $e = $('a,:input');
609
  function sz(el, p) {
610
  return parseInt($.css(el,p),10)||0;
611
  }
612
+ jQuery.migrateDeduplicateWarnings = migrateDeduplicateWarnings;
613
  }
614
 
615
 
includes/blockui/jquery.blockUI.min.js ADDED
@@ -0,0 +1 @@
 
1
+ !function(){"use strict";function e(p){var e=jQuery.migrateDeduplicateWarnings||!1,b=(jQuery.migrateDeduplicateWarnings=!1,p.fn._fadeIn=p.fn.fadeIn,p.noop||function(){}),h=/MSIE/.test(navigator.userAgent),m=/MSIE 6.0/.test(navigator.userAgent)&&!/MSIE 8.0/.test(navigator.userAgent),I=(document.documentMode,"function"==typeof document.createElement("div").style.setExpression),k=(p.blockUI=function(e){o(window,e)},p.unblockUI=function(e){g(window,e)},p.growlUI=function(e,t,o,n){function i(e){p.blockUI({message:s,fadeIn:void 0!==(e=e||{}).fadeIn?e.fadeIn:700,fadeOut:void 0!==e.fadeOut?e.fadeOut:1e3,timeout:void 0!==e.timeout?e.timeout:o,centerY:!1,showOverlay:!1,onUnblock:n,css:p.blockUI.defaults.growlCSS})}var s=p('<div class="growlUI"></div>');e&&s.append("<h1>"+e+"</h1>"),t&&s.append("<h2>"+t+"</h2>"),void 0===o&&(o=3e3),i(),s.css("opacity");s.on("mouseover",function(){i({fadeIn:0,timeout:3e4});var e=p(".blockMsg");e.stop(),e.fadeTo(300,1)}).on("mouseout",function(){p(".blockMsg").fadeOut(1e3)})},p.fn.block=function(e){if(this[0]===window)return p.blockUI(e),this;var t=p.extend({},p.blockUI.defaults,e||{});return this.each(function(){var e=p(this);t.ignoreIfBlocked&&e.data("blockUI.isBlocked")||e.unblock({fadeOut:0})}),this.each(function(){"static"==p.css(this,"position")&&(this.style.position="relative",p(this).data("blockUI.static",!0)),this.style.zoom=1,o(this,e)})},p.fn.unblock=function(e){return this[0]===window?(p.unblockUI(e),this):this.each(function(){g(this,e)})},p.blockUI.version=2.7,p.blockUI.defaults={message:"<h1>Please wait...</h1>",title:null,draggable:!0,theme:!1,css:{padding:0,margin:0,width:"30%",top:"40%",left:"35%",textAlign:"center",color:"#000",border:"3px solid #aaa",backgroundColor:"#fff",cursor:"wait"},themedCSS:{width:"30%",top:"40%",left:"35%"},overlayCSS:{backgroundColor:"#000",opacity:.6,cursor:"wait"},cursorReset:"default",growlCSS:{width:"350px",top:"10px",left:"",right:"10px",border:"none",padding:"5px",opacity:.6,cursor:"default",color:"#fff",backgroundColor:"#000","-webkit-border-radius":"10px","-moz-border-radius":"10px","border-radius":"10px"},iframeSrc:/^https/i.test(window.location.href||"")?"javascript:false":"about:blank",forceIframe:!1,baseZ:1e3,centerX:!0,centerY:!0,allowBodyStretch:!0,bindEvents:!0,constrainTabKey:!0,fadeIn:200,fadeOut:400,timeout:0,showOverlay:!0,focusInput:!0,focusableElements:":input:enabled:visible",onBlock:null,onUnblock:null,onOverlayClick:null,quirksmodeOffsetHack:4,blockMsgClass:"blockMsg",ignoreIfBlocked:!1},null),y=[];function o(e,o){var n=e==window,t=o&&void 0!==o.message?o.message:void 0;if(!(o=p.extend({},p.blockUI.defaults,o||{})).ignoreIfBlocked||!p(e).data("blockUI.isBlocked")){o.overlayCSS=p.extend({},p.blockUI.defaults.overlayCSS,o.overlayCSS||{}),c=p.extend({},p.blockUI.defaults.css,o.css||{}),o.onOverlayClick&&(o.overlayCSS.cursor="pointer"),d=p.extend({},p.blockUI.defaults.themedCSS,o.themedCSS||{}),t=void 0===t?o.message:t,n&&k&&g(window,{fadeOut:0}),t&&"string"!=typeof t&&(t.parentNode||t.jquery)&&(s=t.jquery?t[0]:t,i={},p(e).data("blockUI.history",i),i.el=s,i.parent=s.parentNode,i.display=s.style.display,i.position=s.style.position,i.parent&&i.parent.removeChild(s)),p(e).data("blockUI.onUnblock",o.onUnblock);var r,u,i=o.baseZ,s=h||o.forceIframe?p('<iframe class="blockUI" style="z-index:'+i+++';display:none;border:none;margin:0;padding:0;position:absolute;width:100%;height:100%;top:0;left:0" src="'+o.iframeSrc+'"></iframe>'):p('<div class="blockUI" style="display:none"></div>'),l=o.theme?p('<div class="blockUI blockOverlay ui-widget-overlay" style="z-index:'+i+++';display:none"></div>'):p('<div class="blockUI blockOverlay" style="z-index:'+i+++';display:none;border:none;margin:0;padding:0;width:100%;height:100%;top:0;left:0"></div>'),a=(o.theme&&n?(a='<div class="blockUI '+o.blockMsgClass+' blockPage ui-dialog ui-widget ui-corner-all" style="z-index:'+(i+10)+';display:none;position:fixed">',o.title&&(a+='<div class="ui-widget-header ui-dialog-titlebar ui-corner-all blockTitle">'+(o.title||"&nbsp;")+"</div>"),a+='<div class="ui-widget-content ui-dialog-content"></div></div>'):o.theme?(a='<div class="blockUI '+o.blockMsgClass+' blockElement ui-dialog ui-widget ui-corner-all" style="z-index:'+(i+10)+';display:none;position:absolute">',o.title&&(a+='<div class="ui-widget-header ui-dialog-titlebar ui-corner-all blockTitle">'+(o.title||"&nbsp;")+"</div>"),a+='<div class="ui-widget-content ui-dialog-content"></div></div>'):a=n?'<div class="blockUI '+o.blockMsgClass+' blockPage" style="z-index:'+(i+10)+';display:none;position:fixed"></div>':'<div class="blockUI '+o.blockMsgClass+' blockElement" style="z-index:'+(i+10)+';display:none;position:absolute"></div>',i=p(a),t&&(o.theme?(i.css(d),i.addClass("ui-widget-content")):i.css(c)),o.theme||l.css(o.overlayCSS),l.css("position",n?"fixed":"absolute"),(h||o.forceIframe)&&s.css("opacity",0),[s,l,i]),f=p(n?"body":e),d=(p.each(a,function(){this.appendTo(f)}),o.theme&&o.draggable&&p.fn.draggable&&i.draggable({handle:".ui-dialog-titlebar",cancel:"li"}),I&&("CSS1Compat"!==document.compatMode||0<p("object,embed",n?null:e).length));if((m||d)&&(n&&o.allowBodyStretch&&"CSS1Compat"===document.compatMode&&p("html,body").css("height","100%"),!m&&"CSS1Compat"===document.compatMode||n||(c=v(e,"borderTopWidth"),d=v(e,"borderLeftWidth"),r=c?"(0 - "+c+")":0,u=d?"(0 - "+d+")":0),p.each(a,function(e,t){t=t[0].style;t.position="absolute",e<2?(n?t.setExpression("height",'Math.max(document.body.scrollHeight, document.body.offsetHeight) - ("CSS1Compat" === document.compatMode?0:'+o.quirksmodeOffsetHack+') + "px"'):t.setExpression("height",'this.parentNode.offsetHeight + "px"'),n?t.setExpression("width",'"CSS1Compat" === document.compatMode && document.documentElement.clientWidth || document.body.clientWidth + "px"'):t.setExpression("width",'this.parentNode.offsetWidth + "px"'),u&&t.setExpression("left",u),r&&t.setExpression("top",r)):o.centerY?(n&&t.setExpression("top",'(document.documentElement.clientHeight || document.body.clientHeight) / 2 - (this.offsetHeight / 2) + (blah = document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + "px"'),t.marginTop=0):!o.centerY&&n&&(e=o.css&&o.css.top?parseInt(o.css.top,10):0,t.setExpression("top","((document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + "+e+') + "px"'))})),t&&((o.theme?i.find(".ui-widget-content"):i).append(t),(t.jquery||t.nodeType)&&p(t).show()),(h||o.forceIframe)&&o.showOverlay&&s.show(),o.fadeIn?(c=o.onBlock||b,d=o.showOverlay&&!t?c:b,a=t?c:b,o.showOverlay&&l._fadeIn(o.fadeIn,d),t&&i._fadeIn(o.fadeIn,a)):(o.showOverlay&&l.show(),t&&i.show(),o.onBlock&&o.onBlock.bind(i)()),w(1,e,o),n)k=i[0],y=p(o.focusableElements,k),o.focusInput&&setTimeout(U,20);else{var s=i[0],c=o.centerX,d=o.centerY,a=s.parentNode,l=s.style,t=(a.offsetWidth-s.offsetWidth)/2-v(a,"borderLeftWidth"),s=(a.offsetHeight-s.offsetHeight)/2-v(a,"borderTopWidth");c&&(l.left=0<t?t+"px":"0"),d&&(l.top=0<s?s+"px":"0")}o.timeout&&(i=setTimeout(function(){n?p.unblockUI(o):p(e).unblock(o)},o.timeout),p(e).data("blockUI.timeout",i))}}function g(e,t){var o,n,i=e==window,s=p(e),l=s.data("blockUI.history"),a=s.data("blockUI.timeout");a&&(clearTimeout(a),s.removeData("blockUI.timeout")),t=p.extend({},p.blockUI.defaults,t||{}),w(0,e,t),null===t.onUnblock&&(t.onUnblock=s.data("blockUI.onUnblock"),s.removeData("blockUI.onUnblock")),n=i?p("body").children().filter(".blockUI").add("body > .blockUI"):s.find(">.blockUI"),t.cursorReset&&(1<n.length&&(n[1].style.cursor=t.cursorReset),2<n.length&&(n[2].style.cursor=t.cursorReset)),i&&(k=y=null),t.fadeOut?(o=n.length,n.stop().fadeOut(t.fadeOut,function(){0==--o&&d(n,l,t,e)})):d(n,l,t,e)}function d(e,t,o,n){var i=p(n);i.data("blockUI.isBlocked")||(e.each(function(e,t){this.parentNode&&this.parentNode.removeChild(this)}),t&&t.el&&(t.el.style.display=t.display,t.el.style.position=t.position,t.el.style.cursor="default",t.parent&&t.parent.appendChild(t.el),i.removeData("blockUI.history")),i.data("blockUI.static")&&i.css("position","static"),"function"==typeof o.onUnblock&&o.onUnblock(n,o),t=(e=p(document.body)).width(),i=e[0].style.width,e.width(t-1).width(t),e[0].style.width=i)}function w(e,t,o){var n=t==window,t=p(t);!e&&(n&&!k||!n&&!t.data("blockUI.isBlocked"))||(t.data("blockUI.isBlocked",e),n&&o.bindEvents&&(!e||o.showOverlay)&&(t="mousedown mouseup keydown keypress keyup touchstart touchend touchmove",e?p(document).on(t,o,i):p(document).off(t,i)))}function i(e){if("keydown"===e.type&&e.keyCode&&9==e.keyCode&&k&&e.data.constrainTabKey){var t=y,o=!e.shiftKey&&e.target===t[t.length-1],n=e.shiftKey&&e.target===t[0];if(o||n)return setTimeout(function(){U(n)},10),!1}t=e.data,o=p(e.target);return o.hasClass("blockOverlay")&&t.onOverlayClick&&t.onOverlayClick(e),0<o.parents("div."+t.blockMsgClass).length||0===o.parents().children().filter("div.blockUI").length}function U(e){!y||(e=y[!0===e?y.length-1:0])&&e.focus()}function v(e,t){return parseInt(p.css(e,t),10)||0}jQuery.migrateDeduplicateWarnings=e}"function"==typeof define&&define.amd&&define.amd.jQuery?define(["jquery"],e):e(jQuery)}();
includes/easy-updates-manager-notices.php CHANGED
@@ -2,12 +2,12 @@
2
 
3
  if (!defined('EASY_UPDATES_MANAGER_MAIN_PATH')) die('No direct access allowed');
4
 
5
- if (!class_exists('Updraft_Notices_1_0')) require_once(EASY_UPDATES_MANAGER_MAIN_PATH.'includes/updraft-notices.php');
6
 
7
  /**
8
  * Class Easy_Updates_Manager_Notices
9
  */
10
- class Easy_Updates_Manager_Notices extends Updraft_Notices_1_0 {
11
 
12
  protected static $_instance = null;
13
 
2
 
3
  if (!defined('EASY_UPDATES_MANAGER_MAIN_PATH')) die('No direct access allowed');
4
 
5
+ if (!class_exists('Updraft_Notices_1_2')) require_once(EASY_UPDATES_MANAGER_MAIN_PATH.'/vendor/team-updraft/common-libs/src/updraft-notices/updraft-notices.php');
6
 
7
  /**
8
  * Class Easy_Updates_Manager_Notices
9
  */
10
+ class Easy_Updates_Manager_Notices extends Updraft_Notices_1_2 {
11
 
12
  protected static $_instance = null;
13
 
js/jquery.blockUI.min.js DELETED
@@ -1 +0,0 @@
1
- !function(){"use strict";function e(b){b.fn._fadeIn=b.fn.fadeIn;var p=b.noop||function(){},h=/MSIE/.test(navigator.userAgent),k=/MSIE 6.0/.test(navigator.userAgent)&&!/MSIE 8.0/.test(navigator.userAgent),y=(document.documentMode,b.isFunction(document.createElement("div").style.setExpression)),m=(b.blockUI=function(e){o(window,e)},b.unblockUI=function(e){v(window,e)},b.growlUI=function(e,t,o,n){function i(e){b.blockUI({message:s,fadeIn:void 0!==(e=e||{}).fadeIn?e.fadeIn:700,fadeOut:void 0!==e.fadeOut?e.fadeOut:1e3,timeout:void 0!==e.timeout?e.timeout:o,centerY:!1,showOverlay:!1,onUnblock:n,css:b.blockUI.defaults.growlCSS})}var s=b('<div class="growlUI"></div>');e&&s.append("<h1>"+e+"</h1>"),t&&s.append("<h2>"+t+"</h2>"),void 0===o&&(o=3e3),i(),s.css("opacity");s.mouseover(function(){i({fadeIn:0,timeout:3e4});var e=b(".blockMsg");e.stop(),e.fadeTo(300,1)}).mouseout(function(){b(".blockMsg").fadeOut(1e3)})},b.fn.block=function(e){if(this[0]===window)return b.blockUI(e),this;var t=b.extend({},b.blockUI.defaults,e||{});return this.each(function(){var e=b(this);t.ignoreIfBlocked&&e.data("blockUI.isBlocked")||e.unblock({fadeOut:0})}),this.each(function(){"static"==b.css(this,"position")&&(this.style.position="relative",b(this).data("blockUI.static",!0)),this.style.zoom=1,o(this,e)})},b.fn.unblock=function(e){return this[0]===window?(b.unblockUI(e),this):this.each(function(){v(this,e)})},b.blockUI.version=2.7,b.blockUI.defaults={message:"<h1>Please wait...</h1>",title:null,draggable:!0,theme:!1,css:{padding:0,margin:0,width:"30%",top:"40%",left:"35%",textAlign:"center",color:"#000",border:"3px solid #aaa",backgroundColor:"#fff",cursor:"wait"},themedCSS:{width:"30%",top:"40%",left:"35%"},overlayCSS:{backgroundColor:"#000",opacity:.6,cursor:"wait"},cursorReset:"default",growlCSS:{width:"350px",top:"10px",left:"",right:"10px",border:"none",padding:"5px",opacity:.6,cursor:"default",color:"#fff",backgroundColor:"#000","-webkit-border-radius":"10px","-moz-border-radius":"10px","border-radius":"10px"},iframeSrc:/^https/i.test(window.location.href||"")?"javascript:false":"about:blank",forceIframe:!1,baseZ:1e3,centerX:!0,centerY:!0,allowBodyStretch:!0,bindEvents:!0,constrainTabKey:!0,fadeIn:200,fadeOut:400,timeout:0,showOverlay:!0,focusInput:!0,focusableElements:":input:enabled:visible",onBlock:null,onUnblock:null,onOverlayClick:null,quirksmodeOffsetHack:4,blockMsgClass:"blockMsg",ignoreIfBlocked:!1},null),g=[];function o(e,o){var n=e==window,t=o&&void 0!==o.message?o.message:void 0;if(!(o=b.extend({},b.blockUI.defaults,o||{})).ignoreIfBlocked||!b(e).data("blockUI.isBlocked")){o.overlayCSS=b.extend({},b.blockUI.defaults.overlayCSS,o.overlayCSS||{}),f=b.extend({},b.blockUI.defaults.css,o.css||{}),o.onOverlayClick&&(o.overlayCSS.cursor="pointer"),u=b.extend({},b.blockUI.defaults.themedCSS,o.themedCSS||{}),t=void 0===t?o.message:t,n&&m&&v(window,{fadeOut:0}),t&&"string"!=typeof t&&(t.parentNode||t.jquery)&&(d=t.jquery?t[0]:t,l={},b(e).data("blockUI.history",l),l.el=d,l.parent=d.parentNode,l.display=d.style.display,l.position=d.style.position,l.parent&&l.parent.removeChild(d)),b(e).data("blockUI.onUnblock",o.onUnblock);var i,s,l=o.baseZ,d=h||o.forceIframe?b('<iframe class="blockUI" style="z-index:'+l+++';display:none;border:none;margin:0;padding:0;position:absolute;width:100%;height:100%;top:0;left:0" src="'+o.iframeSrc+'"></iframe>'):b('<div class="blockUI" style="display:none"></div>'),a=o.theme?b('<div class="blockUI blockOverlay ui-widget-overlay" style="z-index:'+l+++';display:none"></div>'):b('<div class="blockUI blockOverlay" style="z-index:'+l+++';display:none;border:none;margin:0;padding:0;width:100%;height:100%;top:0;left:0"></div>'),c=(o.theme&&n?(c='<div class="blockUI '+o.blockMsgClass+' blockPage ui-dialog ui-widget ui-corner-all" style="z-index:'+(l+10)+';display:none;position:fixed">',o.title&&(c+='<div class="ui-widget-header ui-dialog-titlebar ui-corner-all blockTitle">'+(o.title||"&nbsp;")+"</div>"),c+='<div class="ui-widget-content ui-dialog-content"></div></div>'):o.theme?(c='<div class="blockUI '+o.blockMsgClass+' blockElement ui-dialog ui-widget ui-corner-all" style="z-index:'+(l+10)+';display:none;position:absolute">',o.title&&(c+='<div class="ui-widget-header ui-dialog-titlebar ui-corner-all blockTitle">'+(o.title||"&nbsp;")+"</div>"),c+='<div class="ui-widget-content ui-dialog-content"></div></div>'):c=n?'<div class="blockUI '+o.blockMsgClass+' blockPage" style="z-index:'+(l+10)+';display:none;position:fixed"></div>':'<div class="blockUI '+o.blockMsgClass+' blockElement" style="z-index:'+(l+10)+';display:none;position:absolute"></div>',l=b(c),t&&(o.theme?(l.css(u),l.addClass("ui-widget-content")):l.css(f)),o.theme||a.css(o.overlayCSS),a.css("position",n?"fixed":"absolute"),(h||o.forceIframe)&&d.css("opacity",0),[d,a,l]),r=b(n?"body":e),u=(b.each(c,function(){this.appendTo(r)}),o.theme&&o.draggable&&b.fn.draggable&&l.draggable({handle:".ui-dialog-titlebar",cancel:"li"}),y&&(!b.support.boxModel||0<b("object,embed",n?null:e).length));if((k||u)&&(n&&o.allowBodyStretch&&b.support.boxModel&&b("html,body").css("height","100%"),!k&&b.support.boxModel||n||(f=U(e,"borderTopWidth"),u=U(e,"borderLeftWidth"),i=f?"(0 - "+f+")":0,s=u?"(0 - "+u+")":0),b.each(c,function(e,t){t=t[0].style;t.position="absolute",e<2?(n?t.setExpression("height","Math.max(document.body.scrollHeight, document.body.offsetHeight) - (jQuery.support.boxModel?0:"+o.quirksmodeOffsetHack+') + "px"'):t.setExpression("height",'this.parentNode.offsetHeight + "px"'),n?t.setExpression("width",'jQuery.support.boxModel && document.documentElement.clientWidth || document.body.clientWidth + "px"'):t.setExpression("width",'this.parentNode.offsetWidth + "px"'),s&&t.setExpression("left",s),i&&t.setExpression("top",i)):o.centerY?(n&&t.setExpression("top",'(document.documentElement.clientHeight || document.body.clientHeight) / 2 - (this.offsetHeight / 2) + (blah = document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + "px"'),t.marginTop=0):!o.centerY&&n&&(e=o.css&&o.css.top?parseInt(o.css.top,10):0,t.setExpression("top","((document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + "+e+') + "px"'))})),t&&((o.theme?l.find(".ui-widget-content"):l).append(t),(t.jquery||t.nodeType)&&b(t).show()),(h||o.forceIframe)&&o.showOverlay&&d.show(),o.fadeIn?(f=o.onBlock||p,u=o.showOverlay&&!t?f:p,c=t?f:p,o.showOverlay&&a._fadeIn(o.fadeIn,u),t&&l._fadeIn(o.fadeIn,c)):(o.showOverlay&&a.show(),t&&l.show(),o.onBlock&&o.onBlock.bind(l)()),I(1,e,o),n)m=l[0],g=b(o.focusableElements,m),o.focusInput&&setTimeout(w,20);else{var d=l[0],f=o.centerX,u=o.centerY,c=d.parentNode,a=d.style,t=(c.offsetWidth-d.offsetWidth)/2-U(c,"borderLeftWidth"),d=(c.offsetHeight-d.offsetHeight)/2-U(c,"borderTopWidth");f&&(a.left=0<t?t+"px":"0"),u&&(a.top=0<d?d+"px":"0")}o.timeout&&(l=setTimeout(function(){n?b.unblockUI(o):b(e).unblock(o)},o.timeout),b(e).data("blockUI.timeout",l))}}function v(e,t){var o,n,i=e==window,s=b(e),l=s.data("blockUI.history"),d=s.data("blockUI.timeout");d&&(clearTimeout(d),s.removeData("blockUI.timeout")),t=b.extend({},b.blockUI.defaults,t||{}),I(0,e,t),null===t.onUnblock&&(t.onUnblock=s.data("blockUI.onUnblock"),s.removeData("blockUI.onUnblock")),n=i?b("body").children().filter(".blockUI").add("body > .blockUI"):s.find(">.blockUI"),t.cursorReset&&(1<n.length&&(n[1].style.cursor=t.cursorReset),2<n.length&&(n[2].style.cursor=t.cursorReset)),i&&(m=g=null),t.fadeOut?(o=n.length,n.stop().fadeOut(t.fadeOut,function(){0==--o&&a(n,l,t,e)})):a(n,l,t,e)}function a(e,t,o,n){var i=b(n);i.data("blockUI.isBlocked")||(e.each(function(e,t){this.parentNode&&this.parentNode.removeChild(this)}),t&&t.el&&(t.el.style.display=t.display,t.el.style.position=t.position,t.el.style.cursor="default",t.parent&&t.parent.appendChild(t.el),i.removeData("blockUI.history")),i.data("blockUI.static")&&i.css("position","static"),"function"==typeof o.onUnblock&&o.onUnblock(n,o),t=(e=b(document.body)).width(),i=e[0].style.width,e.width(t-1).width(t),e[0].style.width=i)}function I(e,t,o){var n=t==window,t=b(t);!e&&(n&&!m||!n&&!t.data("blockUI.isBlocked"))||(t.data("blockUI.isBlocked",e),n&&o.bindEvents&&(!e||o.showOverlay)&&(t="mousedown mouseup keydown keypress keyup touchstart touchend touchmove",e?b(document).bind(t,o,i):b(document).unbind(t,i)))}function i(e){if("keydown"===e.type&&e.keyCode&&9==e.keyCode&&m&&e.data.constrainTabKey){var t=g,o=!e.shiftKey&&e.target===t[t.length-1],n=e.shiftKey&&e.target===t[0];if(o||n)return setTimeout(function(){w(n)},10),!1}t=e.data,o=b(e.target);return o.hasClass("blockOverlay")&&t.onOverlayClick&&t.onOverlayClick(e),0<o.parents("div."+t.blockMsgClass).length||0===o.parents().children().filter("div.blockUI").length}function w(e){!g||(e=g[!0===e?g.length-1:0])&&e.focus()}function U(e,t){return parseInt(b.css(e,t),10)||0}}"function"==typeof define&&define.amd&&define.amd.jQuery?define(["jquery"],e):e(jQuery)}();
 
languages/stops-core-theme-and-plugin-updates.pot CHANGED
@@ -1110,7 +1110,7 @@ msgstr ""
1110
  msgid "Updates options"
1111
  msgstr ""
1112
 
1113
- #: src/includes/MPSUM_Admin.php:609, src/includes/MPSUM_Admin.php:747, src/includes/MPSUM_Send_Email_Notifications.php:135, src/main.php:792, src/templates/notices/dashboard-constant-warning.php:8, src/templates/notices/thanks-for-using-main-dash.php:18
1114
  msgid "Easy Updates Manager"
1115
  msgstr ""
1116
 
@@ -1412,7 +1412,7 @@ msgstr ""
1412
  msgid "Show Trace"
1413
  msgstr ""
1414
 
1415
- #: src/includes/MPSUM_Logs.php:422
1416
  msgid "No items found."
1417
  msgstr ""
1418
 
@@ -1884,28 +1884,28 @@ msgstr ""
1884
  msgid "Force updates"
1885
  msgstr ""
1886
 
1887
- #: src/templates/notices/dashboard-constant-warning.php:4, src/templates/notices/horizontal-notice.php:16, src/templates/notices/horizontal-notice.php:18
1888
- msgid "Dismiss"
1889
  msgstr ""
1890
 
1891
- #: src/templates/notices/dashboard-constant-warning.php:9
1892
- msgid "The following constants are set and will prevent automatic updates in %s."
1893
  msgstr ""
1894
 
1895
- #: src/templates/notices/dashboard-constant-warning.php:12
1896
- msgid "Please check your wp-config.php file or other files for these constants and remove them to allow Easy Updates Manager to have control."
1897
  msgstr ""
1898
 
1899
- #: src/templates/notices/dashboard-constant-warning.php:16
1900
- msgid "This constant disables any automatic updates."
1901
  msgstr ""
1902
 
1903
  #: src/templates/notices/dashboard-constant-warning.php:19
1904
- msgid "This constant prevents automatic updates to new major releases of WordPress."
1905
  msgstr ""
1906
 
1907
  #: src/templates/notices/dashboard-constant-warning.php:22
1908
- msgid "This constant disables WordPress core from being automatically updated."
1909
  msgstr ""
1910
 
1911
  #: src/templates/notices/horizontal-notice.php:6
1110
  msgid "Updates options"
1111
  msgstr ""
1112
 
1113
+ #: src/includes/MPSUM_Admin.php:609, src/includes/MPSUM_Admin.php:747, src/includes/MPSUM_Send_Email_Notifications.php:135, src/main.php:792, src/templates/notices/dashboard-constant-warning.php:18, src/templates/notices/thanks-for-using-main-dash.php:18
1114
  msgid "Easy Updates Manager"
1115
  msgstr ""
1116
 
1412
  msgid "Show Trace"
1413
  msgstr ""
1414
 
1415
+ #: src/includes/MPSUM_Logs.php:443
1416
  msgid "No items found."
1417
  msgstr ""
1418
 
1884
  msgid "Force updates"
1885
  msgstr ""
1886
 
1887
+ #: src/templates/notices/dashboard-constant-warning.php:7
1888
+ msgid "This constant prevents automatic updates scheduled tasks from being run within WordPress internal cron."
1889
  msgstr ""
1890
 
1891
+ #: src/templates/notices/dashboard-constant-warning.php:7
1892
+ msgid "Typically, when enabled, automatic updates events are checked on every page load and any events due to run will be called during that page load."
1893
  msgstr ""
1894
 
1895
+ #: src/templates/notices/dashboard-constant-warning.php:7
1896
+ msgid "However, if it's intentionally being set because you use external cron (server cron) then you can ignore this warning."
1897
  msgstr ""
1898
 
1899
+ #: src/templates/notices/dashboard-constant-warning.php:14, src/templates/notices/horizontal-notice.php:16, src/templates/notices/horizontal-notice.php:18
1900
+ msgid "Dismiss"
1901
  msgstr ""
1902
 
1903
  #: src/templates/notices/dashboard-constant-warning.php:19
1904
+ msgid "The following constants are set and will prevent automatic updates in %s."
1905
  msgstr ""
1906
 
1907
  #: src/templates/notices/dashboard-constant-warning.php:22
1908
+ msgid "Please check your wp-config.php file or other files for these constants and remove them to allow Easy Updates Manager to have control."
1909
  msgstr ""
1910
 
1911
  #: src/templates/notices/horizontal-notice.php:6
main.php CHANGED
@@ -5,7 +5,7 @@ Plugin Name: Easy Updates Manager
5
  Plugin URI: https://easyupdatesmanager.com
6
  Description: Manage and disable WordPress updates, including core, plugin, theme, and automatic updates - Works with Multisite and has built-in logging features.
7
  Author: Easy Updates Manager Team
8
- Version: 9.0.13
9
  Update URI: https://wordpress.org/plugins/stops-core-theme-and-plugin-updates/
10
  Author URI: https://easyupdatesmanager.com
11
  Contributors: kidsguide, ronalfy
@@ -18,7 +18,7 @@ Network: true
18
 
19
  if (!defined('ABSPATH')) die('No direct access allowed');
20
 
21
- if (!defined('EASY_UPDATES_MANAGER_VERSION')) define('EASY_UPDATES_MANAGER_VERSION', '9.0.13');
22
 
23
  if (!defined('EASY_UPDATES_MANAGER_MAIN_PATH')) define('EASY_UPDATES_MANAGER_MAIN_PATH', plugin_dir_path(__FILE__));
24
  if (!defined('EASY_UPDATES_MANAGER_URL')) define('EASY_UPDATES_MANAGER_URL', plugin_dir_url(__FILE__));
@@ -694,12 +694,12 @@ if (!class_exists('MPSUM_Updates_Manager')) {
694
  $this->register_template_directories();
695
 
696
  // Check for WP Constants that disable updates and display a notice.
697
- $upgrade_constants = MPSUM_Constant_Checks::get_instance();
698
- if ($upgrade_constants->is_config_options_disabled()) {
699
- $upgrade_constant_notice = get_site_option('easy_updates_manager_dismiss_constant_notices', 0);
700
- if (!$upgrade_constant_notice) {
701
- add_action('all_admin_notices', array($this, 'show_autoupdate_constant_warning'));
702
- }
703
  }
704
 
705
  // Add filters to overwrite auto update UI in WP 5.5
@@ -934,7 +934,7 @@ if (!class_exists('MPSUM_Updates_Manager')) {
934
  } elseif ('dismiss_survey_notice_until' == $subaction) {
935
  update_site_option('easy_updates_manager_dismiss_survey_notice_until', (time() + 366 * 86400));
936
  } elseif ('dismiss_constant_notices' == $subaction) {
937
- update_site_option('easy_updates_manager_dismiss_constant_notices', true);
938
  }
939
 
940
  wp_send_json($results);
5
  Plugin URI: https://easyupdatesmanager.com
6
  Description: Manage and disable WordPress updates, including core, plugin, theme, and automatic updates - Works with Multisite and has built-in logging features.
7
  Author: Easy Updates Manager Team
8
+ Version: 9.0.14
9
  Update URI: https://wordpress.org/plugins/stops-core-theme-and-plugin-updates/
10
  Author URI: https://easyupdatesmanager.com
11
  Contributors: kidsguide, ronalfy
18
 
19
  if (!defined('ABSPATH')) die('No direct access allowed');
20
 
21
+ if (!defined('EASY_UPDATES_MANAGER_VERSION')) define('EASY_UPDATES_MANAGER_VERSION', '9.0.14');
22
 
23
  if (!defined('EASY_UPDATES_MANAGER_MAIN_PATH')) define('EASY_UPDATES_MANAGER_MAIN_PATH', plugin_dir_path(__FILE__));
24
  if (!defined('EASY_UPDATES_MANAGER_URL')) define('EASY_UPDATES_MANAGER_URL', plugin_dir_url(__FILE__));
694
  $this->register_template_directories();
695
 
696
  // Check for WP Constants that disable updates and display a notice.
697
+ $prohibited_active_constants = MPSUM_Constant_Checks::get_instance()->get_prohibited_active_constants();
698
+ $upgrade_constant_notice = get_site_option('easy_updates_manager_dismiss_constant_notices', array());
699
+ if (!is_array($upgrade_constant_notice)) $upgrade_constant_notice = array();
700
+ $prohibited_active_constants = array_diff($prohibited_active_constants, $upgrade_constant_notice);
701
+ if (!empty($prohibited_active_constants)) {
702
+ add_action('all_admin_notices', array($this, 'show_autoupdate_constant_warning'));
703
  }
704
 
705
  // Add filters to overwrite auto update UI in WP 5.5
934
  } elseif ('dismiss_survey_notice_until' == $subaction) {
935
  update_site_option('easy_updates_manager_dismiss_survey_notice_until', (time() + 366 * 86400));
936
  } elseif ('dismiss_constant_notices' == $subaction) {
937
+ update_site_option('easy_updates_manager_dismiss_constant_notices', MPSUM_Constant_Checks::get_instance()->get_prohibited_active_constants());
938
  }
939
 
940
  wp_send_json($results);
readme.txt CHANGED
@@ -5,7 +5,7 @@ Requires at least: 5.1
5
  Requires PHP: 5.6
6
  Donate link: https://easyupdatesmanager.com
7
  Tested up to: 6.1
8
- Stable tag: 9.0.13
9
  License: GPLv2 or later
10
 
11
  Manage all your WordPress updates, including individual updates, automatic updates, logs, and loads more. This also works very well with WordPress Multisite.
@@ -126,6 +126,13 @@ For additional information and FAQs for Easy Updates Manager <a href="https://ea
126
 
127
  == Changelog ==
128
 
 
 
 
 
 
 
 
129
  = 9.0.13 - 2022-06-07 =
130
 
131
  * TWEAK: Update logs are sent separately causing a huge number of emails being sent to the user (Premium)
@@ -385,4 +392,4 @@ For past changelogs, <a href="https://easyupdatesmanager.com/blog/">please visit
385
 
386
  == Upgrade Notice ==
387
 
388
- * 9.0.13: Various tweaks and fixes. Better log mechanism, effective and efficient use of auto-updates notification emails. See changelog for full details. A recommended update for all.
5
  Requires PHP: 5.6
6
  Donate link: https://easyupdatesmanager.com
7
  Tested up to: 6.1
8
+ Stable tag: 9.0.14
9
  License: GPLv2 or later
10
 
11
  Manage all your WordPress updates, including individual updates, automatic updates, logs, and loads more. This also works very well with WordPress Multisite.
126
 
127
  == Changelog ==
128
 
129
+ = 9.0.14 - 2022-10-24 =
130
+
131
+ * TWEAK: Add admin notice for DISABLE_WP_CRON constant and remove notices regarding WP_AUTO_UPDATE_CORE and AUTOMATIC_UPDATER_DISABLED constants as they now won't prevent automatic updates from being run
132
+ * FIX: Core minor updates became major when two updates-related scheduled events got triggered in the same process
133
+ * FIX: In some cases the serialisation of call stack could cause a PHP fatal error due to the output of a backtrace (debug_backtrace) containing 'Closure' (anonymous) functions
134
+ * TWEAK: Update notice class
135
+
136
  = 9.0.13 - 2022-06-07 =
137
 
138
  * TWEAK: Update logs are sent separately causing a huge number of emails being sent to the user (Premium)
392
 
393
  == Upgrade Notice ==
394
 
395
+ * 9.0.14: Various tweaks and fixes. Core minor updates bug, and constant warnings that are no longer relevant. See changelog for full details. A recommended update for all.
templates/notices/dashboard-constant-warning.php CHANGED
@@ -1,4 +1,14 @@
1
- <?php if (!defined('EASY_UPDATES_MANAGER_MAIN_PATH')) die('No direct access allowed'); ?>
 
 
 
 
 
 
 
 
 
 
2
 
3
  <div id="easy-updates-manager-constants-enabled" class="error">
4
  <div style="float:right;"><a href="#" onclick="jQuery('#easy-updates-manager-constants-enabled').slideUp(); jQuery.post(ajaxurl, {action: 'easy_updates_manager_ajax', subaction: 'dismiss_constant_notices', nonce: '<?php echo wp_create_nonce('easy-updates-manager-ajax-nonce'); ?>' });"><?php printf(__('Dismiss', 'stops-core-theme-and-plugin-updates')); ?></a></div>
@@ -10,19 +20,6 @@
10
  ?></h3>
11
  <div id="easy-updates-manager-constants-enabled-wrapper">
12
  <p><?php sprintf(esc_html_e('Please check your wp-config.php file or other files for these constants and remove them to allow Easy Updates Manager to have control.', 'stops-core-theme-and-plugin-updates'), $eum_white_label); ?></p>
13
- <?php
14
- $html = '<ul>';
15
- if (defined('AUTOMATIC_UPDATER_DISABLED') && true === AUTOMATIC_UPDATER_DISABLED) {
16
- $html .= sprintf('<li><strong>%s</strong>: %s</li>', 'AUTOMATIC_UPDATER_DISABLED', esc_html__('This constant disables any automatic updates.', 'stops-core-theme-and-plugin-updates'));
17
- }
18
- if (defined('WP_AUTO_UPDATE_CORE') && 'minor' === WP_AUTO_UPDATE_CORE) {
19
- $html .= sprintf('<li><strong>%s</strong>: %s</li>', 'WP_AUTO_UPDATE_CORE', esc_html__('This constant prevents automatic updates to new major releases of WordPress.', 'stops-core-theme-and-plugin-updates'));
20
- }
21
- if (defined('WP_AUTO_UPDATE_CORE') && false === WP_AUTO_UPDATE_CORE) {
22
- $html .= sprintf('<li><strong>%s</strong>: %s</li>', 'WP_AUTO_UPDATE_CORE', esc_html__('This constant disables WordPress core from being automatically updated.', 'stops-core-theme-and-plugin-updates'));
23
- }
24
- $html .= '</ul>';
25
- echo $html;
26
- ?>
27
  </div>
28
  </div>
1
+ <?php
2
+ if (!defined('EASY_UPDATES_MANAGER_MAIN_PATH')) die('No direct access allowed');
3
+
4
+ $prohibited_active_constants = MPSUM_Constant_Checks::get_instance()->get_prohibited_active_constants();
5
+ $html = '';
6
+ if (in_array('DISABLE_WP_CRON', $prohibited_active_constants)) {
7
+ $html .= sprintf('<li><strong>%s</strong>: %s</li>', 'DISABLE_WP_CRON', esc_html__('This constant prevents automatic updates scheduled tasks from being run within WordPress internal cron.', 'stops-core-theme-and-plugin-updates')." ".esc_html__('Typically, when enabled, automatic updates events are checked on every page load and any events due to run will be called during that page load.', 'stops-core-theme-and-plugin-updates')." ".esc_html__("However, if it's intentionally being set because you use external cron (server cron) then you can ignore this warning.", 'stops-core-theme-and-plugin-updates'));
8
+ }
9
+ $html = !empty($html) ? '<ul>'.$html.'</ul>' : $html;
10
+ if (empty($html)) return;
11
+ ?>
12
 
13
  <div id="easy-updates-manager-constants-enabled" class="error">
14
  <div style="float:right;"><a href="#" onclick="jQuery('#easy-updates-manager-constants-enabled').slideUp(); jQuery.post(ajaxurl, {action: 'easy_updates_manager_ajax', subaction: 'dismiss_constant_notices', nonce: '<?php echo wp_create_nonce('easy-updates-manager-ajax-nonce'); ?>' });"><?php printf(__('Dismiss', 'stops-core-theme-and-plugin-updates')); ?></a></div>
20
  ?></h3>
21
  <div id="easy-updates-manager-constants-enabled-wrapper">
22
  <p><?php sprintf(esc_html_e('Please check your wp-config.php file or other files for these constants and remove them to allow Easy Updates Manager to have control.', 'stops-core-theme-and-plugin-updates'), $eum_white_label); ?></p>
23
+ <?php echo $html; ?>
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  </div>
25
  </div>
vendor/autoload.php ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // autoload.php @generated by Composer
4
+
5
+ require_once __DIR__ . '/composer/autoload_real.php';
6
+
7
+ return ComposerAutoloaderInit5c27f3864a6e3814ff0decde3a95b7bb::getLoader();
vendor/composer/ClassLoader.php ADDED
@@ -0,0 +1,481 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Composer.
5
+ *
6
+ * (c) Nils Adermann <naderman@naderman.de>
7
+ * Jordi Boggiano <j.boggiano@seld.be>
8
+ *
9
+ * For the full copyright and license information, please view the LICENSE
10
+ * file that was distributed with this source code.
11
+ */
12
+
13
+ namespace Composer\Autoload;
14
+
15
+ /**
16
+ * ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
17
+ *
18
+ * $loader = new \Composer\Autoload\ClassLoader();
19
+ *
20
+ * // register classes with namespaces
21
+ * $loader->add('Symfony\Component', __DIR__.'/component');
22
+ * $loader->add('Symfony', __DIR__.'/framework');
23
+ *
24
+ * // activate the autoloader
25
+ * $loader->register();
26
+ *
27
+ * // to enable searching the include path (eg. for PEAR packages)
28
+ * $loader->setUseIncludePath(true);
29
+ *
30
+ * In this example, if you try to use a class in the Symfony\Component
31
+ * namespace or one of its children (Symfony\Component\Console for instance),
32
+ * the autoloader will first look for the class under the component/
33
+ * directory, and it will then fallback to the framework/ directory if not
34
+ * found before giving up.
35
+ *
36
+ * This class is loosely based on the Symfony UniversalClassLoader.
37
+ *
38
+ * @author Fabien Potencier <fabien@symfony.com>
39
+ * @author Jordi Boggiano <j.boggiano@seld.be>
40
+ * @see https://www.php-fig.org/psr/psr-0/
41
+ * @see https://www.php-fig.org/psr/psr-4/
42
+ */
43
+ class ClassLoader
44
+ {
45
+ private $vendorDir;
46
+
47
+ // PSR-4
48
+ private $prefixLengthsPsr4 = array();
49
+ private $prefixDirsPsr4 = array();
50
+ private $fallbackDirsPsr4 = array();
51
+
52
+ // PSR-0
53
+ private $prefixesPsr0 = array();
54
+ private $fallbackDirsPsr0 = array();
55
+
56
+ private $useIncludePath = false;
57
+ private $classMap = array();
58
+ private $classMapAuthoritative = false;
59
+ private $missingClasses = array();
60
+ private $apcuPrefix;
61
+
62
+ private static $registeredLoaders = array();
63
+
64
+ public function __construct($vendorDir = null)
65
+ {
66
+ $this->vendorDir = $vendorDir;
67
+ }
68
+
69
+ public function getPrefixes()
70
+ {
71
+ if (!empty($this->prefixesPsr0)) {
72
+ return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
73
+ }
74
+
75
+ return array();
76
+ }
77
+
78
+ public function getPrefixesPsr4()
79
+ {
80
+ return $this->prefixDirsPsr4;
81
+ }
82
+
83
+ public function getFallbackDirs()
84
+ {
85
+ return $this->fallbackDirsPsr0;
86
+ }
87
+
88
+ public function getFallbackDirsPsr4()
89
+ {
90
+ return $this->fallbackDirsPsr4;
91
+ }
92
+
93
+ public function getClassMap()
94
+ {
95
+ return $this->classMap;
96
+ }
97
+
98
+ /**
99
+ * @param array $classMap Class to filename map
100
+ */
101
+ public function addClassMap(array $classMap)
102
+ {
103
+ if ($this->classMap) {
104
+ $this->classMap = array_merge($this->classMap, $classMap);
105
+ } else {
106
+ $this->classMap = $classMap;
107
+ }
108
+ }
109
+
110
+ /**
111
+ * Registers a set of PSR-0 directories for a given prefix, either
112
+ * appending or prepending to the ones previously set for this prefix.
113
+ *
114
+ * @param string $prefix The prefix
115
+ * @param array|string $paths The PSR-0 root directories
116
+ * @param bool $prepend Whether to prepend the directories
117
+ */
118
+ public function add($prefix, $paths, $prepend = false)
119
+ {
120
+ if (!$prefix) {
121
+ if ($prepend) {
122
+ $this->fallbackDirsPsr0 = array_merge(
123
+ (array) $paths,
124
+ $this->fallbackDirsPsr0
125
+ );
126
+ } else {
127
+ $this->fallbackDirsPsr0 = array_merge(
128
+ $this->fallbackDirsPsr0,
129
+ (array) $paths
130
+ );
131
+ }
132
+
133
+ return;
134
+ }
135
+
136
+ $first = $prefix[0];
137
+ if (!isset($this->prefixesPsr0[$first][$prefix])) {
138
+ $this->prefixesPsr0[$first][$prefix] = (array) $paths;
139
+
140
+ return;
141
+ }
142
+ if ($prepend) {
143
+ $this->prefixesPsr0[$first][$prefix] = array_merge(
144
+ (array) $paths,
145
+ $this->prefixesPsr0[$first][$prefix]
146
+ );
147
+ } else {
148
+ $this->prefixesPsr0[$first][$prefix] = array_merge(
149
+ $this->prefixesPsr0[$first][$prefix],
150
+ (array) $paths
151
+ );
152
+ }
153
+ }
154
+
155
+ /**
156
+ * Registers a set of PSR-4 directories for a given namespace, either
157
+ * appending or prepending to the ones previously set for this namespace.
158
+ *
159
+ * @param string $prefix The prefix/namespace, with trailing '\\'
160
+ * @param array|string $paths The PSR-4 base directories
161
+ * @param bool $prepend Whether to prepend the directories
162
+ *
163
+ * @throws \InvalidArgumentException
164
+ */
165
+ public function addPsr4($prefix, $paths, $prepend = false)
166
+ {
167
+ if (!$prefix) {
168
+ // Register directories for the root namespace.
169
+ if ($prepend) {
170
+ $this->fallbackDirsPsr4 = array_merge(
171
+ (array) $paths,
172
+ $this->fallbackDirsPsr4
173
+ );
174
+ } else {
175
+ $this->fallbackDirsPsr4 = array_merge(
176
+ $this->fallbackDirsPsr4,
177
+ (array) $paths
178
+ );
179
+ }
180
+ } elseif (!isset($this->prefixDirsPsr4[$prefix])) {
181
+ // Register directories for a new namespace.
182
+ $length = strlen($prefix);
183
+ if ('\\' !== $prefix[$length - 1]) {
184
+ throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
185
+ }
186
+ $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
187
+ $this->prefixDirsPsr4[$prefix] = (array) $paths;
188
+ } elseif ($prepend) {
189
+ // Prepend directories for an already registered namespace.
190
+ $this->prefixDirsPsr4[$prefix] = array_merge(
191
+ (array) $paths,
192
+ $this->prefixDirsPsr4[$prefix]
193
+ );
194
+ } else {
195
+ // Append directories for an already registered namespace.
196
+ $this->prefixDirsPsr4[$prefix] = array_merge(
197
+ $this->prefixDirsPsr4[$prefix],
198
+ (array) $paths
199
+ );
200
+ }
201
+ }
202
+
203
+ /**
204
+ * Registers a set of PSR-0 directories for a given prefix,
205
+ * replacing any others previously set for this prefix.
206
+ *
207
+ * @param string $prefix The prefix
208
+ * @param array|string $paths The PSR-0 base directories
209
+ */
210
+ public function set($prefix, $paths)
211
+ {
212
+ if (!$prefix) {
213
+ $this->fallbackDirsPsr0 = (array) $paths;
214
+ } else {
215
+ $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
216
+ }
217
+ }
218
+
219
+ /**
220
+ * Registers a set of PSR-4 directories for a given namespace,
221
+ * replacing any others previously set for this namespace.
222
+ *
223
+ * @param string $prefix The prefix/namespace, with trailing '\\'
224
+ * @param array|string $paths The PSR-4 base directories
225
+ *
226
+ * @throws \InvalidArgumentException
227
+ */
228
+ public function setPsr4($prefix, $paths)
229
+ {
230
+ if (!$prefix) {
231
+ $this->fallbackDirsPsr4 = (array) $paths;
232
+ } else {
233
+ $length = strlen($prefix);
234
+ if ('\\' !== $prefix[$length - 1]) {
235
+ throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
236
+ }
237
+ $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
238
+ $this->prefixDirsPsr4[$prefix] = (array) $paths;
239
+ }
240
+ }
241
+
242
+ /**
243
+ * Turns on searching the include path for class files.
244
+ *
245
+ * @param bool $useIncludePath
246
+ */
247
+ public function setUseIncludePath($useIncludePath)
248
+ {
249
+ $this->useIncludePath = $useIncludePath;
250
+ }
251
+
252
+ /**
253
+ * Can be used to check if the autoloader uses the include path to check
254
+ * for classes.
255
+ *
256
+ * @return bool
257
+ */
258
+ public function getUseIncludePath()
259
+ {
260
+ return $this->useIncludePath;
261
+ }
262
+
263
+ /**
264
+ * Turns off searching the prefix and fallback directories for classes
265
+ * that have not been registered with the class map.
266
+ *
267
+ * @param bool $classMapAuthoritative
268
+ */
269
+ public function setClassMapAuthoritative($classMapAuthoritative)
270
+ {
271
+ $this->classMapAuthoritative = $classMapAuthoritative;
272
+ }
273
+
274
+ /**
275
+ * Should class lookup fail if not found in the current class map?
276
+ *
277
+ * @return bool
278
+ */
279
+ public function isClassMapAuthoritative()
280
+ {
281
+ return $this->classMapAuthoritative;
282
+ }
283
+
284
+ /**
285
+ * APCu prefix to use to cache found/not-found classes, if the extension is enabled.
286
+ *
287
+ * @param string|null $apcuPrefix
288
+ */
289
+ public function setApcuPrefix($apcuPrefix)
290
+ {
291
+ $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
292
+ }
293
+
294
+ /**
295
+ * The APCu prefix in use, or null if APCu caching is not enabled.
296
+ *
297
+ * @return string|null
298
+ */
299
+ public function getApcuPrefix()
300
+ {
301
+ return $this->apcuPrefix;
302
+ }
303
+
304
+ /**
305
+ * Registers this instance as an autoloader.
306
+ *
307
+ * @param bool $prepend Whether to prepend the autoloader or not
308
+ */
309
+ public function register($prepend = false)
310
+ {
311
+ spl_autoload_register(array($this, 'loadClass'), true, $prepend);
312
+
313
+ if (null === $this->vendorDir) {
314
+ return;
315
+ }
316
+
317
+ if ($prepend) {
318
+ self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
319
+ } else {
320
+ unset(self::$registeredLoaders[$this->vendorDir]);
321
+ self::$registeredLoaders[$this->vendorDir] = $this;
322
+ }
323
+ }
324
+
325
+ /**
326
+ * Unregisters this instance as an autoloader.
327
+ */
328
+ public function unregister()
329
+ {
330
+ spl_autoload_unregister(array($this, 'loadClass'));
331
+
332
+ if (null !== $this->vendorDir) {
333
+ unset(self::$registeredLoaders[$this->vendorDir]);
334
+ }
335
+ }
336
+
337
+ /**
338
+ * Loads the given class or interface.
339
+ *
340
+ * @param string $class The name of the class
341
+ * @return true|null True if loaded, null otherwise
342
+ */
343
+ public function loadClass($class)
344
+ {
345
+ if ($file = $this->findFile($class)) {
346
+ includeFile($file);
347
+
348
+ return true;
349
+ }
350
+
351
+ return null;
352
+ }
353
+
354
+ /**
355
+ * Finds the path to the file where the class is defined.
356
+ *
357
+ * @param string $class The name of the class
358
+ *
359
+ * @return string|false The path if found, false otherwise
360
+ */
361
+ public function findFile($class)
362
+ {
363
+ // class map lookup
364
+ if (isset($this->classMap[$class])) {
365
+ return $this->classMap[$class];
366
+ }
367
+ if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
368
+ return false;
369
+ }
370
+ if (null !== $this->apcuPrefix) {
371
+ $file = apcu_fetch($this->apcuPrefix.$class, $hit);
372
+ if ($hit) {
373
+ return $file;
374
+ }
375
+ }
376
+
377
+ $file = $this->findFileWithExtension($class, '.php');
378
+
379
+ // Search for Hack files if we are running on HHVM
380
+ if (false === $file && defined('HHVM_VERSION')) {
381
+ $file = $this->findFileWithExtension($class, '.hh');
382
+ }
383
+
384
+ if (null !== $this->apcuPrefix) {
385
+ apcu_add($this->apcuPrefix.$class, $file);
386
+ }
387
+
388
+ if (false === $file) {
389
+ // Remember that this class does not exist.
390
+ $this->missingClasses[$class] = true;
391
+ }
392
+
393
+ return $file;
394
+ }
395
+
396
+ /**
397
+ * Returns the currently registered loaders indexed by their corresponding vendor directories.
398
+ *
399
+ * @return self[]
400
+ */
401
+ public static function getRegisteredLoaders()
402
+ {
403
+ return self::$registeredLoaders;
404
+ }
405
+
406
+ private function findFileWithExtension($class, $ext)
407
+ {
408
+ // PSR-4 lookup
409
+ $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
410
+
411
+ $first = $class[0];
412
+ if (isset($this->prefixLengthsPsr4[$first])) {
413
+ $subPath = $class;
414
+ while (false !== $lastPos = strrpos($subPath, '\\')) {
415
+ $subPath = substr($subPath, 0, $lastPos);
416
+ $search = $subPath . '\\';
417
+ if (isset($this->prefixDirsPsr4[$search])) {
418
+ $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
419
+ foreach ($this->prefixDirsPsr4[$search] as $dir) {
420
+ if (file_exists($file = $dir . $pathEnd)) {
421
+ return $file;
422
+ }
423
+ }
424
+ }
425
+ }
426
+ }
427
+
428
+ // PSR-4 fallback dirs
429
+ foreach ($this->fallbackDirsPsr4 as $dir) {
430
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
431
+ return $file;
432
+ }
433
+ }
434
+
435
+ // PSR-0 lookup
436
+ if (false !== $pos = strrpos($class, '\\')) {
437
+ // namespaced class name
438
+ $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
439
+ . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
440
+ } else {
441
+ // PEAR-like class name
442
+ $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
443
+ }
444
+
445
+ if (isset($this->prefixesPsr0[$first])) {
446
+ foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
447
+ if (0 === strpos($class, $prefix)) {
448
+ foreach ($dirs as $dir) {
449
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
450
+ return $file;
451
+ }
452
+ }
453
+ }
454
+ }
455
+ }
456
+
457
+ // PSR-0 fallback dirs
458
+ foreach ($this->fallbackDirsPsr0 as $dir) {
459
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
460
+ return $file;
461
+ }
462
+ }
463
+
464
+ // PSR-0 include paths.
465
+ if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
466
+ return $file;
467
+ }
468
+
469
+ return false;
470
+ }
471
+ }
472
+
473
+ /**
474
+ * Scope isolated include.
475
+ *
476
+ * Prevents access to $this/self from included files.
477
+ */
478
+ function includeFile($file)
479
+ {
480
+ include $file;
481
+ }
vendor/composer/InstalledVersions.php ADDED
@@ -0,0 +1,337 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Composer.
5
+ *
6
+ * (c) Nils Adermann <naderman@naderman.de>
7
+ * Jordi Boggiano <j.boggiano@seld.be>
8
+ *
9
+ * For the full copyright and license information, please view the LICENSE
10
+ * file that was distributed with this source code.
11
+ */
12
+
13
+ namespace Composer;
14
+
15
+ use Composer\Autoload\ClassLoader;
16
+ use Composer\Semver\VersionParser;
17
+
18
+ /**
19
+ * This class is copied in every Composer installed project and available to all
20
+ *
21
+ * See also https://getcomposer.org/doc/07-runtime.md#installed-versions
22
+ *
23
+ * To require it's presence, you can require `composer-runtime-api ^2.0`
24
+ */
25
+ class InstalledVersions
26
+ {
27
+ private static $installed;
28
+ private static $canGetVendors;
29
+ private static $installedByVendor = array();
30
+
31
+ /**
32
+ * Returns a list of all package names which are present, either by being installed, replaced or provided
33
+ *
34
+ * @return string[]
35
+ * @psalm-return list<string>
36
+ */
37
+ public static function getInstalledPackages()
38
+ {
39
+ $packages = array();
40
+ foreach (self::getInstalled() as $installed) {
41
+ $packages[] = array_keys($installed['versions']);
42
+ }
43
+
44
+ if (1 === \count($packages)) {
45
+ return $packages[0];
46
+ }
47
+
48
+ return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
49
+ }
50
+
51
+ /**
52
+ * Returns a list of all package names with a specific type e.g. 'library'
53
+ *
54
+ * @param string $type
55
+ * @return string[]
56
+ * @psalm-return list<string>
57
+ */
58
+ public static function getInstalledPackagesByType($type)
59
+ {
60
+ $packagesByType = array();
61
+
62
+ foreach (self::getInstalled() as $installed) {
63
+ foreach ($installed['versions'] as $name => $package) {
64
+ if (isset($package['type']) && $package['type'] === $type) {
65
+ $packagesByType[] = $name;
66
+ }
67
+ }
68
+ }
69
+
70
+ return $packagesByType;
71
+ }
72
+
73
+ /**
74
+ * Checks whether the given package is installed
75
+ *
76
+ * This also returns true if the package name is provided or replaced by another package
77
+ *
78
+ * @param string $packageName
79
+ * @param bool $includeDevRequirements
80
+ * @return bool
81
+ */
82
+ public static function isInstalled($packageName, $includeDevRequirements = true)
83
+ {
84
+ foreach (self::getInstalled() as $installed) {
85
+ if (isset($installed['versions'][$packageName])) {
86
+ return $includeDevRequirements || empty($installed['versions'][$packageName]['dev_requirement']);
87
+ }
88
+ }
89
+
90
+ return false;
91
+ }
92
+
93
+ /**
94
+ * Checks whether the given package satisfies a version constraint
95
+ *
96
+ * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call:
97
+ *
98
+ * Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3')
99
+ *
100
+ * @param VersionParser $parser Install composer/semver to have access to this class and functionality
101
+ * @param string $packageName
102
+ * @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package
103
+ * @return bool
104
+ */
105
+ public static function satisfies(VersionParser $parser, $packageName, $constraint)
106
+ {
107
+ $constraint = $parser->parseConstraints($constraint);
108
+ $provided = $parser->parseConstraints(self::getVersionRanges($packageName));
109
+
110
+ return $provided->matches($constraint);
111
+ }
112
+
113
+ /**
114
+ * Returns a version constraint representing all the range(s) which are installed for a given package
115
+ *
116
+ * It is easier to use this via isInstalled() with the $constraint argument if you need to check
117
+ * whether a given version of a package is installed, and not just whether it exists
118
+ *
119
+ * @param string $packageName
120
+ * @return string Version constraint usable with composer/semver
121
+ */
122
+ public static function getVersionRanges($packageName)
123
+ {
124
+ foreach (self::getInstalled() as $installed) {
125
+ if (!isset($installed['versions'][$packageName])) {
126
+ continue;
127
+ }
128
+
129
+ $ranges = array();
130
+ if (isset($installed['versions'][$packageName]['pretty_version'])) {
131
+ $ranges[] = $installed['versions'][$packageName]['pretty_version'];
132
+ }
133
+ if (array_key_exists('aliases', $installed['versions'][$packageName])) {
134
+ $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
135
+ }
136
+ if (array_key_exists('replaced', $installed['versions'][$packageName])) {
137
+ $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
138
+ }
139
+ if (array_key_exists('provided', $installed['versions'][$packageName])) {
140
+ $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
141
+ }
142
+
143
+ return implode(' || ', $ranges);
144
+ }
145
+
146
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
147
+ }
148
+
149
+ /**
150
+ * @param string $packageName
151
+ * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
152
+ */
153
+ public static function getVersion($packageName)
154
+ {
155
+ foreach (self::getInstalled() as $installed) {
156
+ if (!isset($installed['versions'][$packageName])) {
157
+ continue;
158
+ }
159
+
160
+ if (!isset($installed['versions'][$packageName]['version'])) {
161
+ return null;
162
+ }
163
+
164
+ return $installed['versions'][$packageName]['version'];
165
+ }
166
+
167
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
168
+ }
169
+
170
+ /**
171
+ * @param string $packageName
172
+ * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
173
+ */
174
+ public static function getPrettyVersion($packageName)
175
+ {
176
+ foreach (self::getInstalled() as $installed) {
177
+ if (!isset($installed['versions'][$packageName])) {
178
+ continue;
179
+ }
180
+
181
+ if (!isset($installed['versions'][$packageName]['pretty_version'])) {
182
+ return null;
183
+ }
184
+
185
+ return $installed['versions'][$packageName]['pretty_version'];
186
+ }
187
+
188
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
189
+ }
190
+
191
+ /**
192
+ * @param string $packageName
193
+ * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference
194
+ */
195
+ public static function getReference($packageName)
196
+ {
197
+ foreach (self::getInstalled() as $installed) {
198
+ if (!isset($installed['versions'][$packageName])) {
199
+ continue;
200
+ }
201
+
202
+ if (!isset($installed['versions'][$packageName]['reference'])) {
203
+ return null;
204
+ }
205
+
206
+ return $installed['versions'][$packageName]['reference'];
207
+ }
208
+
209
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
210
+ }
211
+
212
+ /**
213
+ * @param string $packageName
214
+ * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path.
215
+ */
216
+ public static function getInstallPath($packageName)
217
+ {
218
+ foreach (self::getInstalled() as $installed) {
219
+ if (!isset($installed['versions'][$packageName])) {
220
+ continue;
221
+ }
222
+
223
+ return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null;
224
+ }
225
+
226
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
227
+ }
228
+
229
+ /**
230
+ * @return array
231
+ * @psalm-return array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string}
232
+ */
233
+ public static function getRootPackage()
234
+ {
235
+ $installed = self::getInstalled();
236
+
237
+ return $installed[0]['root'];
238
+ }
239
+
240
+ /**
241
+ * Returns the raw installed.php data for custom implementations
242
+ *
243
+ * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect.
244
+ * @return array[]
245
+ * @psalm-return array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string}>}
246
+ */
247
+ public static function getRawData()
248
+ {
249
+ @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED);
250
+
251
+ if (null === self::$installed) {
252
+ // only require the installed.php file if this file is loaded from its dumped location,
253
+ // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
254
+ if (substr(__DIR__, -8, 1) !== 'C') {
255
+ self::$installed = include __DIR__ . '/installed.php';
256
+ } else {
257
+ self::$installed = array();
258
+ }
259
+ }
260
+
261
+ return self::$installed;
262
+ }
263
+
264
+ /**
265
+ * Returns the raw data of all installed.php which are currently loaded for custom implementations
266
+ *
267
+ * @return array[]
268
+ * @psalm-return list<array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string}>}>
269
+ */
270
+ public static function getAllRawData()
271
+ {
272
+ return self::getInstalled();
273
+ }
274
+
275
+ /**
276
+ * Lets you reload the static array from another file
277
+ *
278
+ * This is only useful for complex integrations in which a project needs to use
279
+ * this class but then also needs to execute another project's autoloader in process,
280
+ * and wants to ensure both projects have access to their version of installed.php.
281
+ *
282
+ * A typical case would be PHPUnit, where it would need to make sure it reads all
283
+ * the data it needs from this class, then call reload() with
284
+ * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure
285
+ * the project in which it runs can then also use this class safely, without
286
+ * interference between PHPUnit's dependencies and the project's dependencies.
287
+ *
288
+ * @param array[] $data A vendor/composer/installed.php data set
289
+ * @return void
290
+ *
291
+ * @psalm-param array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string}>} $data
292
+ */
293
+ public static function reload($data)
294
+ {
295
+ self::$installed = $data;
296
+ self::$installedByVendor = array();
297
+ }
298
+
299
+ /**
300
+ * @return array[]
301
+ * @psalm-return list<array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string}>}>
302
+ */
303
+ private static function getInstalled()
304
+ {
305
+ if (null === self::$canGetVendors) {
306
+ self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
307
+ }
308
+
309
+ $installed = array();
310
+
311
+ if (self::$canGetVendors) {
312
+ foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
313
+ if (isset(self::$installedByVendor[$vendorDir])) {
314
+ $installed[] = self::$installedByVendor[$vendorDir];
315
+ } elseif (is_file($vendorDir.'/composer/installed.php')) {
316
+ $installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php';
317
+ if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) {
318
+ self::$installed = $installed[count($installed) - 1];
319
+ }
320
+ }
321
+ }
322
+ }
323
+
324
+ if (null === self::$installed) {
325
+ // only require the installed.php file if this file is loaded from its dumped location,
326
+ // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
327
+ if (substr(__DIR__, -8, 1) !== 'C') {
328
+ self::$installed = require __DIR__ . '/installed.php';
329
+ } else {
330
+ self::$installed = array();
331
+ }
332
+ }
333
+ $installed[] = self::$installed;
334
+
335
+ return $installed;
336
+ }
337
+ }
vendor/composer/LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ Copyright (c) Nils Adermann, Jordi Boggiano
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ of this software and associated documentation files (the "Software"), to deal
6
+ in the Software without restriction, including without limitation the rights
7
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the Software is furnished
9
+ to do so, subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in all
12
+ copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ THE SOFTWARE.
21
+
vendor/composer/autoload_classmap.php ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // autoload_classmap.php @generated by Composer
4
+
5
+ $vendorDir = dirname(dirname(__FILE__));
6
+ $baseDir = dirname($vendorDir);
7
+
8
+ return array(
9
+ 'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
10
+ );
vendor/composer/autoload_namespaces.php ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // autoload_namespaces.php @generated by Composer
4
+
5
+ $vendorDir = dirname(dirname(__FILE__));
6
+ $baseDir = dirname($vendorDir);
7
+
8
+ return array(
9
+ );
vendor/composer/autoload_psr4.php ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // autoload_psr4.php @generated by Composer
4
+
5
+ $vendorDir = dirname(dirname(__FILE__));
6
+ $baseDir = dirname($vendorDir);
7
+
8
+ return array(
9
+ );
vendor/composer/autoload_real.php ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // autoload_real.php @generated by Composer
4
+
5
+ class ComposerAutoloaderInit5c27f3864a6e3814ff0decde3a95b7bb
6
+ {
7
+ private static $loader;
8
+
9
+ public static function loadClassLoader($class)
10
+ {
11
+ if ('Composer\Autoload\ClassLoader' === $class) {
12
+ require __DIR__ . '/ClassLoader.php';
13
+ }
14
+ }
15
+
16
+ /**
17
+ * @return \Composer\Autoload\ClassLoader
18
+ */
19
+ public static function getLoader()
20
+ {
21
+ if (null !== self::$loader) {
22
+ return self::$loader;
23
+ }
24
+
25
+ spl_autoload_register(array('ComposerAutoloaderInit5c27f3864a6e3814ff0decde3a95b7bb', 'loadClassLoader'), true, true);
26
+ self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__)));
27
+ spl_autoload_unregister(array('ComposerAutoloaderInit5c27f3864a6e3814ff0decde3a95b7bb', 'loadClassLoader'));
28
+
29
+ $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
30
+ if ($useStaticLoader) {
31
+ require __DIR__ . '/autoload_static.php';
32
+
33
+ call_user_func(\Composer\Autoload\ComposerStaticInit5c27f3864a6e3814ff0decde3a95b7bb::getInitializer($loader));
34
+ } else {
35
+ $map = require __DIR__ . '/autoload_namespaces.php';
36
+ foreach ($map as $namespace => $path) {
37
+ $loader->set($namespace, $path);
38
+ }
39
+
40
+ $map = require __DIR__ . '/autoload_psr4.php';
41
+ foreach ($map as $namespace => $path) {
42
+ $loader->setPsr4($namespace, $path);
43
+ }
44
+
45
+ $classMap = require __DIR__ . '/autoload_classmap.php';
46
+ if ($classMap) {
47
+ $loader->addClassMap($classMap);
48
+ }
49
+ }
50
+
51
+ $loader->register(true);
52
+
53
+ return $loader;
54
+ }
55
+ }
vendor/composer/autoload_static.php ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // autoload_static.php @generated by Composer
4
+
5
+ namespace Composer\Autoload;
6
+
7
+ class ComposerStaticInit5c27f3864a6e3814ff0decde3a95b7bb
8
+ {
9
+ public static $classMap = array (
10
+ 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
11
+ );
12
+
13
+ public static function getInitializer(ClassLoader $loader)
14
+ {
15
+ return \Closure::bind(function () use ($loader) {
16
+ $loader->classMap = ComposerStaticInit5c27f3864a6e3814ff0decde3a95b7bb::$classMap;
17
+
18
+ }, null, ClassLoader::class);
19
+ }
20
+ }
vendor/composer/installed.json ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "packages": [
3
+ {
4
+ "name": "team-updraft/common-libs",
5
+ "version": "3.0.3",
6
+ "version_normalized": "3.0.3.0",
7
+ "source": {
8
+ "type": "git",
9
+ "url": "https://source.updraftplus.com/team-updraft/common-libs.git",
10
+ "reference": "cd30bf3b65e2cadcea1cca01deb32f5cd21a0b63"
11
+ },
12
+ "dist": {
13
+ "type": "zip",
14
+ "url": "https://source.updraftplus.com/api/v4/projects/28/packages/composer/archives/team-updraft/common-libs.zip?sha=cd30bf3b65e2cadcea1cca01deb32f5cd21a0b63",
15
+ "reference": "cd30bf3b65e2cadcea1cca01deb32f5cd21a0b63",
16
+ "shasum": ""
17
+ },
18
+ "require-dev": {
19
+ "dealerdirect/phpcodesniffer-composer-installer": "0.7.*",
20
+ "phpcompatibility/php-compatibility": "9.3.*",
21
+ "sirbrillig/phpcs-variable-analysis": "2.11.*",
22
+ "squizlabs/php_codesniffer": "3.6.*",
23
+ "wp-coding-standards/wpcs": "2.3.*"
24
+ },
25
+ "type": "library",
26
+ "installation-source": "dist",
27
+ "license": [
28
+ "GPL-3.0-only"
29
+ ],
30
+ "authors": [
31
+ {
32
+ "name": "Team Updraft",
33
+ "email": "team.updraft@gmail.com"
34
+ }
35
+ ],
36
+ "description": "These are the common libs used across all of our projects",
37
+ "install-path": "../team-updraft/common-libs"
38
+ }
39
+ ],
40
+ "dev": false,
41
+ "dev-package-names": []
42
+ }
vendor/composer/installed.php ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php return array(
2
+ 'root' => array(
3
+ 'pretty_version' => 'dev-master',
4
+ 'version' => 'dev-master',
5
+ 'type' => 'project',
6
+ 'install_path' => __DIR__ . '/../../',
7
+ 'aliases' => array(),
8
+ 'reference' => '94db1fe10b91d20c60f0de21f1f79510497d6bf1',
9
+ 'name' => 'updraftplus/stops-core-theme-and-plugin-updates',
10
+ 'dev' => false,
11
+ ),
12
+ 'versions' => array(
13
+ 'team-updraft/common-libs' => array(
14
+ 'pretty_version' => '3.0.3',
15
+ 'version' => '3.0.3.0',
16
+ 'type' => 'library',
17
+ 'install_path' => __DIR__ . '/../team-updraft/common-libs',
18
+ 'aliases' => array(),
19
+ 'reference' => 'cd30bf3b65e2cadcea1cca01deb32f5cd21a0b63',
20
+ 'dev_requirement' => false,
21
+ ),
22
+ 'updraftplus/stops-core-theme-and-plugin-updates' => array(
23
+ 'pretty_version' => 'dev-master',
24
+ 'version' => 'dev-master',
25
+ 'type' => 'project',
26
+ 'install_path' => __DIR__ . '/../../',
27
+ 'aliases' => array(),
28
+ 'reference' => '94db1fe10b91d20c60f0de21f1f79510497d6bf1',
29
+ 'dev_requirement' => false,
30
+ ),
31
+ ),
32
+ );
vendor/team-updraft/common-libs/CI/php-compatibility.xml ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0"?>
2
+ <ruleset name="UpdraftPlus">
3
+ <!-- How to run on Commandline: vendor/bin/phpcs -p -s -d memory_limit=150M \-\-standard=CI/php-compatibility.xml src/ \-\-report-full \-\-extensions=php -->
4
+ <description>UpdraftPlus PHP Compatibility Check</description>
5
+ <!-- Set Memory Limit -->
6
+ <ini name="memory_limit" value="150M"/>
7
+ <!-- CI Cache -->
8
+ <arg name="cache" value="../CI/phpcs-cache-compatibility"/>
9
+ <!-- Check up to 4 files simultanously. -->
10
+ <arg name="parallel" value="4"/>
11
+ <!-- Only Test for PHP 5.2+ -->
12
+ <config name="testVersion" value="5.2-"/>
13
+ <!-- Ignoring Folders As they are part of Vendor packages -->
14
+ <!-- <exclude-pattern>src/tools/customer-tools/vendor</exclude-pattern> -->
15
+ <!-- PHPCompatibility -->
16
+ <rule ref="PHPCompatibility"/>
17
+ </ruleset>
vendor/team-updraft/common-libs/CI/php-syntax-check.xml ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0"?>
2
+ <ruleset name="UpdraftPlus">
3
+ <description>UpdraftPlus PHP Syntax Check</description>
4
+ <!-- Set Memory Limit -->
5
+ <ini name="memory_limit" value="150M"/>
6
+ <!-- CI Cache -->
7
+ <arg name="cache" value="../CI/phpcs-cache-syntax-check"/>
8
+ <!-- Check up to 4 files simultanously. -->
9
+ <arg name="parallel" value="4"/>
10
+ <!-- Check for PHP syntax errors -->
11
+ <rule ref="Generic.PHP.Syntax"/>
12
+ </ruleset>
vendor/team-updraft/common-libs/README.md ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ # Common Libraries
2
+
3
+ This project contains many useful libraries that are currently used and can be reused across our projects. They are kept here for easy maintenance and also so that consumers get a uniform interface and things dont break across versions or on updates.
4
+
5
+ CHANGELOG
6
+ * TWEAK: Port from previous semaphore classes to Updraft_Semaphore_3_0 in updraft-tasks
7
+ * FIX: Wrong query value in `delete_task_meta` method
8
+ * TWEAK: Make the logging format uniform
9
+ * FIX: Wrong DB Schema reference
10
+ * TWEAK: Logging on the semaphore
vendor/team-updraft/common-libs/composer.json ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "team-updraft/common-libs",
3
+ "description": "These are the common libs used across all of our projects",
4
+ "type": "library",
5
+ "license": "GPL-3.0-only",
6
+ "authors": [{
7
+ "name": "Team Updraft",
8
+ "email": "team.updraft@gmail.com"
9
+ }],
10
+ "config": {
11
+ "platform-check": false
12
+ },
13
+ "require-dev": {
14
+ "squizlabs/php_codesniffer": "3.6.*",
15
+ "phpcompatibility/php-compatibility": "9.3.*",
16
+ "wp-coding-standards/wpcs": "2.3.*",
17
+ "sirbrillig/phpcs-variable-analysis": "2.11.*",
18
+ "dealerdirect/phpcodesniffer-composer-installer": "0.7.*"
19
+ },
20
+ "prefer-stable" : true
21
+ }
{includes → vendor/team-updraft/common-libs/src/updraft-notices}/updraft-notices.php RENAMED
@@ -2,18 +2,11 @@
2
 
3
  if (!defined('ABSPATH')) die('No direct access allowed');
4
 
5
- /**
6
- * If we ever change the API of the Updraft_Notices class, then we'll need to rename and version it, e.g. Updraft_Notices_1_0, because otherwise a plugin may find that it's loaded an older instance than it wanted from another plugin.
7
- */
8
- abstract class Updraft_Notices_1_0 {
9
 
10
  protected $notices_content;
11
 
12
- /**
13
- * These variables are just short-hands to be used in advert content.
14
- *
15
- * @var array
16
- */
17
  protected $dashboard_top = array('top');
18
 
19
  protected $dashboard_top_or_report = array('top', 'report', 'report-plain');
@@ -27,12 +20,11 @@ abstract class Updraft_Notices_1_0 {
27
  protected $autobackup_bottom_or_report = array('autobackup', 'bottom', 'report', 'report-plain');
28
 
29
  /**
30
- * Populates notices content
31
  *
32
  * @return array
33
  */
34
  protected function populate_notices_content() {
35
- // Global adverts that appear in all products will be returned to the child to display.
36
  return array();
37
  }
38
 
@@ -40,14 +32,14 @@ abstract class Updraft_Notices_1_0 {
40
  * Call this method to setup the notices.
41
  */
42
  abstract protected function notices_init();
43
-
44
  /**
45
- * Checks whether plugin is installed and checks status if needed.
46
  *
47
- * @param null $product Plugin to check
48
- * @param bool $also_require_active Whether to active status is required or not
49
  *
50
- * @return bool Returns true, if plugin is installed otherwise false
51
  */
52
  protected function is_plugin_installed($product = null, $also_require_active = false) {
53
  if ($also_require_active) return class_exists($product);
@@ -63,12 +55,12 @@ abstract class Updraft_Notices_1_0 {
63
  }
64
 
65
  /**
66
- * Checks whether translation is needed or not
67
  *
68
- * @param string $plugin_base_dir Base directory of plugin
69
- * @param string $product_name Plugin name
70
  *
71
- * @return bool Returns true if translation is needed, otherwise false
72
  */
73
  protected function translation_needed($plugin_base_dir, $product_name) {
74
  $wplang = get_locale();
@@ -78,26 +70,45 @@ abstract class Updraft_Notices_1_0 {
78
  return true;
79
  }
80
 
 
 
 
 
 
 
 
 
 
 
81
  protected function url_start($html_allowed, $url, $https = false, $website_home = null) {
82
  $proto = ($https) ? 'https' : 'http';
83
  if (strpos($url, $website_home) !== false) {
84
- return (($html_allowed) ? "<a href=".apply_filters(str_replace('.', '_', $website_home).'_link', $proto.'://'.$url).">" : "");
85
  } else {
86
- return (($html_allowed) ? '<a href="'.$proto.'://'.$url.'">' : "");
87
  }
88
  }
89
 
 
 
 
 
 
 
 
 
 
90
  protected function url_end($html_allowed, $url, $https = false) {
91
- $proto = (($https) ? 'https' : 'http');
92
- return (($html_allowed) ? '</a>' : " (".$proto."://".$url.")");
93
  }
94
 
95
  /**
96
  * Renders notice
97
  *
98
- * @param bool $notice Whether to render notice or not
99
- * @param string $position Notice rendering position
100
- * @param bool $return_instead_of_echo Whether echo notice or return as string
101
  *
102
  * @return mixed Returns string or echos notice
103
  */
@@ -117,9 +128,10 @@ abstract class Updraft_Notices_1_0 {
117
  /**
118
  * This method will return a notice ready for display.
119
  *
120
- * @param boolean $notice Sends True or False if there are notices to show.
121
- * @param string $position Which screen position the notice is.
122
- * @return array Returns Notice data.
 
123
  */
124
  protected function get_notice_data($notice = false, $position = 'top') {
125
 
@@ -137,6 +149,9 @@ abstract class Updraft_Notices_1_0 {
137
 
138
  if ($dismiss) return false;
139
 
 
 
 
140
  return $this->notices_content[$notice];
141
  }
142
 
@@ -165,6 +180,10 @@ abstract class Updraft_Notices_1_0 {
165
  if (empty($available_notices)) return false;
166
 
167
  // If a seasonal advert can't be returned then we will return a random advert.
 
 
 
 
168
  // Using shuffle here as something like rand which produces a random number and uses that as the array index fails, this is because in future an advert may not be numbered and could have a string as its key which will then cause errors.
169
  shuffle($available_notices);
170
  return $available_notices[0];
@@ -173,25 +192,27 @@ abstract class Updraft_Notices_1_0 {
173
  /**
174
  * Skip seasonal notices
175
  *
176
- * @return bool
 
 
177
  */
178
- protected function skip_seasonal_notices($notice_data) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- Easy_Updates_Manager_Notices::skip_seasonal_notices should be compatible with Updraft_Notices_1_0::skip_seasonal_notices so $notice_data is needed
179
  return false;
180
  }
181
 
182
  /**
183
- * Returns Affiliate ID
184
  *
185
- * @return mixed Returns Affiliate ID
186
  */
187
  public function get_affiliate_id() {
188
  return $this->self_affiliate_id;
189
  }
190
 
191
  /**
192
- * Checks whether notice is dismissed
193
  *
194
- * @param string $dismiss_time Dismiss time
195
  *
196
  * @return mixed
197
  */
2
 
3
  if (!defined('ABSPATH')) die('No direct access allowed');
4
 
5
+ abstract class Updraft_Notices_1_2 {
 
 
 
6
 
7
  protected $notices_content;
8
 
9
+ // These variables are just short-hands to be used in advert content.
 
 
 
 
10
  protected $dashboard_top = array('top');
11
 
12
  protected $dashboard_top_or_report = array('top', 'report', 'report-plain');
20
  protected $autobackup_bottom_or_report = array('autobackup', 'bottom', 'report', 'report-plain');
21
 
22
  /**
23
+ * Global adverts that appear in all products will be returned to the child to display
24
  *
25
  * @return array
26
  */
27
  protected function populate_notices_content() {
 
28
  return array();
29
  }
30
 
32
  * Call this method to setup the notices.
33
  */
34
  abstract protected function notices_init();
35
+
36
  /**
37
+ * Checks if the plugin is installed and checks status if needed.
38
  *
39
+ * @param null $product - Plugin to check
40
+ * @param boolean $also_require_active - bool to indicate if active status is required or not
41
  *
42
+ * @return boolean Returns true, if plugin is installed otherwise false
43
  */
44
  protected function is_plugin_installed($product = null, $also_require_active = false) {
45
  if ($also_require_active) return class_exists($product);
55
  }
56
 
57
  /**
58
+ * Checks if translation is needed or not
59
  *
60
+ * @param string $plugin_base_dir - Base directory of plugin
61
+ * @param string $product_name - Plugin name
62
  *
63
+ * @return boolean Returns true if translation is needed, otherwise false
64
  */
65
  protected function translation_needed($plugin_base_dir, $product_name) {
66
  $wplang = get_locale();
70
  return true;
71
  }
72
 
73
+ /**
74
+ * Generates the start of a HTML URL
75
+ *
76
+ * @param boolean $html_allowed - indicates if HTML is allowed or not
77
+ * @param string $url - the URL
78
+ * @param boolean $https - the protocol to use
79
+ * @param string $website_home - the product website name
80
+ *
81
+ * @return string returns a partial HTML URL
82
+ */
83
  protected function url_start($html_allowed, $url, $https = false, $website_home = null) {
84
  $proto = ($https) ? 'https' : 'http';
85
  if (strpos($url, $website_home) !== false) {
86
+ return $html_allowed ? "<a href=".apply_filters(str_replace('.', '_', $website_home).'_link', $proto.'://'.$url).'>' : '';
87
  } else {
88
+ return $html_allowed ? '<a href="'.$proto.'://'.$url.'">' : '';
89
  }
90
  }
91
 
92
+ /**
93
+ * Generate the end of a HTML URL
94
+ *
95
+ * @param boolean $html_allowed - indicates if HTML is allowed or not
96
+ * @param string $url - the URL
97
+ * @param boolean $https - the protocol to use
98
+ *
99
+ * @return string returns a partial HTML URL
100
+ */
101
  protected function url_end($html_allowed, $url, $https = false) {
102
+ $proto = $https ? 'https' : 'http';
103
+ return $html_allowed ? '</a>' : ' ('.$proto.'://'.$url.')';
104
  }
105
 
106
  /**
107
  * Renders notice
108
  *
109
+ * @param mixed $notice - a specific notice to render or false
110
+ * @param string $position - position of the notice
111
+ * @param boolean $return_instead_of_echo - indicates if we should echo notice or return as string
112
  *
113
  * @return mixed Returns string or echos notice
114
  */
128
  /**
129
  * This method will return a notice ready for display.
130
  *
131
+ * @param boolean $notice - a specific notice to render or false
132
+ * @param string $position - position of the notice
133
+ *
134
+ * @return array returns notice data
135
  */
136
  protected function get_notice_data($notice = false, $position = 'top') {
137
 
149
 
150
  if ($dismiss) return false;
151
 
152
+ // If the advert has a validity function, then require the advert to be valid
153
+ if (!empty($this->notices_content[$notice]['validity_function']) && !call_user_func(array($this, $this->notices_content[$notice]['validity_function']))) return false;
154
+
155
  return $this->notices_content[$notice];
156
  }
157
 
180
  if (empty($available_notices)) return false;
181
 
182
  // If a seasonal advert can't be returned then we will return a random advert.
183
+
184
+ // Here we give a 25% chance for the rate advert to be returned before selecting a random advert from the entire collection which also includes the rate advert
185
+ if (0 == rand(0, 3) && isset($available_notices['rate'])) return $available_notices['rate'];
186
+
187
  // Using shuffle here as something like rand which produces a random number and uses that as the array index fails, this is because in future an advert may not be numbered and could have a string as its key which will then cause errors.
188
  shuffle($available_notices);
189
  return $available_notices[0];
192
  /**
193
  * Skip seasonal notices
194
  *
195
+ * @param array $notice_data - an array of data for the chosen notice
196
+ *
197
+ * @return boolean
198
  */
199
+ protected function skip_seasonal_notices($notice_data) {// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
200
  return false;
201
  }
202
 
203
  /**
204
+ * Returns affiliate ID
205
  *
206
+ * @return mixed Returns affiliate ID
207
  */
208
  public function get_affiliate_id() {
209
  return $this->self_affiliate_id;
210
  }
211
 
212
  /**
213
+ * Checks if the notice has been dismissed
214
  *
215
+ * @param string $dismiss_time - dismiss time
216
  *
217
  * @return mixed
218
  */
vendor/team-updraft/common-libs/src/updraft-rpc/class-udrpc.php ADDED
@@ -0,0 +1,1115 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ // @codingStandardsIgnoreStart
3
+ /*
4
+ This class provides methods for encrypting, sending, receiving and decrypting messages of arbitrary length, using standard encryption methods and including protection against replay attacks.
5
+
6
+ Example:
7
+
8
+ // Set a key and encrypt with it
9
+ $ud_rpc = new UpdraftPlus_Remote_Communications($name_indicator); // $name_indicator is a key indicator - indicating which key is being used.
10
+ $ud_rpc->set_key_local($our_private_key);
11
+ $ud_rpc->set_key_remote($their_public_key);
12
+ $encrypted = $ud_rpc->encrypt_message('blah blah');
13
+
14
+ // Use the saved WP site option
15
+ $ud_rpc = new UpdraftPlus_Remote_Communications($name_indicator); // $name_indicator is a key indicator - indicating which key is being used.
16
+ $ud_rpc->set_option_name('udrpc_remotekey');
17
+ if (!$ud_rpc->get_key_remote()) throw new Exception('...');
18
+ $encrypted = $ud_rpc->encrypt_message('blah blah');
19
+
20
+ // Generate a new key
21
+ $ud_rpc = new UpdraftPlus_Remote_Communications('myindicator.example.com');
22
+ $ud_rpc->set_option_name('udrpc_localkey'); // Save as a WP site option
23
+ $new_pair = $ud_rpc->generate_new_keypair();
24
+ if ($new_pair) {
25
+ $local_private_key = $ud_rpc->get_key_local();
26
+ $remote_public_key = $ud_rpc->get_key_remote();
27
+ // ...
28
+ } else {
29
+ throw new Exception('...');
30
+ }
31
+
32
+ // Send a message
33
+ $ud_rpc->activate_replay_protection();
34
+ $ud_rpc->set_destination_url('https://example.com/path/to/wp');
35
+ $ud_rpc->send_message('ping');
36
+ $ud_rpc->send_message('somecommand', array('param1' => 'data', 'param2' => 'moredata'));
37
+
38
+ // N.B. The data sent needs to be something that will pass json_encode(). So, it may be desirable to base64-encode it first.
39
+
40
+ // Create a listener for incoming messages
41
+
42
+ add_filter('udrpc_command_somecommand', 'my_function', 10, 3);
43
+ // function my_function($response, $data, $name_indicator) { ... ; return array('response' => 'my_reply', 'data' => 'any mixed data'); }
44
+ // Or:
45
+ // add_filter('udrpc_action', 'some_function', 10, 4); // Function must return something other than false to indicate that it handled the specific command. Any returned value will be sent as the reply.
46
+ // function some_function($response, $command, $data, $name_indicator) { ...; return array('response' => 'my_reply', 'data' => 'any mixed data'); }
47
+ $ud_rpc->set_option_name('udrpc_local_private_key');
48
+ $ud_rpc->activate_replay_protection();
49
+ if ($ud_rpc->get_key_local()) {
50
+ // Make sure you call this before the wp_loaded action is fired (e.g. at init)
51
+ $ud_rpc->create_listener();
52
+ }
53
+
54
+ // Instead of using activate_replay_protection(), you can use activate_sequence_protection() (receiving side) and set_next_send_sequence_id(). They are very similar; but, the sequence number code isn't tested, and is problematic if you may have multiple clients that don't share storage (you can use the current time as a sequence number, but if two clients send at the same millisecond (or whatever granularity you use), you may have problems); whereas the replay protection code relies on database storage on the sending side (not just the receiving).
55
+
56
+ */
57
+ // @codingStandardsIgnoreEnd
58
+ if (!class_exists('UpdraftPlus_Remote_Communications')) :
59
+ class UpdraftPlus_Remote_Communications {
60
+
61
+ // Version numbers relate to versions of this PHP library only (i.e. it's not a protocol support number, and version numbers of other compatible libraries (e.g. JavaScript) are not comparable)
62
+ public $version = '1.4.23';
63
+
64
+ private $key_name_indicator;
65
+
66
+ private $key_option_name = false;
67
+
68
+ private $key_remote = false;
69
+
70
+ private $key_local = false;
71
+
72
+ private $can_generate = false;
73
+
74
+ private $destination_url = false;
75
+
76
+ private $maximum_replay_time_difference = 300;
77
+
78
+ private $extra_replay_protection = false;
79
+
80
+ private $sequence_protection_tolerance;
81
+
82
+ private $sequence_protection_table;
83
+
84
+ private $sequence_protection_column;
85
+
86
+ private $sequence_protection_where_sql;
87
+
88
+ // Debug may log confidential data using $this->log() - so only use when you are in a secure environment
89
+ private $debug = false;
90
+
91
+ private $next_send_sequence_id;
92
+
93
+ private $allow_cors_from = array();
94
+
95
+ private $http_transport = null;
96
+
97
+ // Default protocol version - this can be over-ridden with set_message_format
98
+ // Protocol version 1 (which uses only one RSA key-pair, instead of two) is legacy/deprecated
99
+ private $format = 2;
100
+
101
+ private $http_credentials = array();
102
+
103
+ private $incoming_message = null;
104
+
105
+ private $message_random_number = null;
106
+
107
+ private $require_message_to_be_understood = false;
108
+
109
+ public function __construct($key_name_indicator = 'default') {
110
+ $this->set_key_name_indicator($key_name_indicator);
111
+ }
112
+
113
+ public function set_key_name_indicator($key_name_indicator) {
114
+ $this->key_name_indicator = $key_name_indicator;
115
+ }
116
+
117
+ public function set_can_generate($can_generate = true) {
118
+ $this->can_generate = $can_generate;
119
+ }
120
+
121
+ /**
122
+ * Which sites to allow CORS requests from
123
+ *
124
+ * @param string $allow_cors_from
125
+ */
126
+ public function set_allow_cors_from($allow_cors_from) {
127
+ $this->allow_cors_from = $allow_cors_from;
128
+ }
129
+
130
+ public function set_maximum_replay_time_difference($replay_time_difference) {
131
+ $this->maximum_replay_time_difference = (int) $replay_time_difference;
132
+ }
133
+
134
+ /**
135
+ * This will cause more things to be sent to $this->log()
136
+ *
137
+ * @param boolean $debug
138
+ */
139
+ public function set_debug($debug = true) {
140
+ $this->debug = (bool) $debug;
141
+ }
142
+
143
+ /**
144
+ * Supported values: a Guzzle object, or, if not, then WP's HTTP API function siwll be used
145
+ *
146
+ * @param string $transport
147
+ */
148
+ public function set_http_transport($transport) {
149
+ $this->http_transport = $transport;
150
+ }
151
+
152
+ /**
153
+ * Sequence protection and replay protection perform similar functions, and using both is often over-kill; the distinction is that sequence protection can be used without needing to do database writes on the sending side (e.g. use the value of time() as the sequence number).
154
+ * The only rule of sequences is that the receiving side will reject any sequence number that is less than the last previously seen one, within the bounds of the tolerance (but it may also reject those if they are repeats).
155
+ * The given table/column will record a comma-separated list of recently seen sequences numbers within the tolerance threshold.
156
+ *
157
+ * @param string $table
158
+ * @param string $column
159
+ * @param string $where_sql
160
+ * @param integer $tolerance
161
+ */
162
+ public function activate_sequence_protection($table, $column, $where_sql, $tolerance = 5) {
163
+ $this->sequence_protection_tolerance = (int) $tolerance;
164
+ $this->sequence_protection_table = (string) $table;
165
+ $this->sequence_protection_column = (string) $column;
166
+ $this->sequence_protection_where_sql = (string) $where_sql;
167
+ }
168
+
169
+ private function ensure_crypto_loaded() {
170
+ if (!class_exists('Crypt_Rijndael') || !class_exists('Crypt_RSA') || !class_exists('Crypt_Hash')) {
171
+ global $updraftplus;
172
+ // phpseclib 1.x uses deprecated PHP4-style constructors
173
+ $this->no_deprecation_warnings_on_php7();
174
+ if (is_a($updraftplus, 'UpdraftPlus')) {
175
+ // Since May 2019, the second parameter is unused; but, since we don't know the version, we send it.
176
+ $ensure_phpseclib = $updraftplus->ensure_phpseclib(array('Crypt_Rijndael', 'Crypt_RSA', 'Crypt_Hash'), array('Crypt/Rijndael', 'Crypt/RSA', 'Crypt/Hash'));
177
+ if (is_wp_error($ensure_phpseclib)) return $ensure_phpseclib;
178
+ } elseif (defined('UPDRAFTPLUS_DIR') && file_exists(UPDRAFTPLUS_DIR.'/vendor/phpseclib/phpseclib/phpseclib')) {
179
+ $pdir = UPDRAFTPLUS_DIR.'/vendor/phpseclib/phpseclib/phpseclib';
180
+ if (false === strpos(get_include_path(), $pdir)) set_include_path($pdir.PATH_SEPARATOR.get_include_path());
181
+ if (!class_exists('Crypt_Rijndael')) include_once 'Crypt/Rijndael.php';
182
+ if (!class_exists('Crypt_RSA')) include_once 'Crypt/RSA.php';
183
+ if (!class_exists('Crypt_Hash')) include_once 'Crypt/Hash.php';
184
+ } elseif (file_exists(dirname(dirname(__FILE__)).'/vendor/phpseclib/phpseclib/phpseclib')) {
185
+ $pdir = dirname(dirname(__FILE__)).'/vendor/phpseclib/phpseclib/phpseclib';
186
+ if (false === strpos(get_include_path(), $pdir)) set_include_path($pdir.PATH_SEPARATOR.get_include_path());
187
+ if (!class_exists('Crypt_Rijndael')) include_once 'Crypt/Rijndael.php';
188
+ if (!class_exists('Crypt_RSA')) include_once 'Crypt/RSA.php';
189
+ if (!class_exists('Crypt_Hash')) include_once 'Crypt/Hash.php';
190
+ }
191
+ }
192
+ }
193
+
194
+ /**
195
+ * Ugly, but necessary to prevent debug output breaking the conversation when the user has debug turned on
196
+ */
197
+ private function no_deprecation_warnings_on_php7() {
198
+ // PHP_MAJOR_VERSION is defined in PHP 5.2.7+
199
+ // We don't test for PHP > 7 because the specific deprecated element will be removed in PHP 8 - and so no warning should come anyway (and we shouldn't suppress other stuff until we know we need to).
200
+ // @codingStandardsIgnoreLine
201
+ if (defined('PHP_MAJOR_VERSION') && PHP_MAJOR_VERSION == 7) {
202
+ $old_level = error_reporting();
203
+ // @codingStandardsIgnoreLine
204
+ $new_level = $old_level & ~E_DEPRECATED;
205
+ if ($old_level != $new_level) error_reporting($new_level);
206
+ }
207
+ }
208
+
209
+ public function set_destination_url($destination_url) {
210
+ $this->destination_url = $destination_url;
211
+ }
212
+
213
+ public function get_destination_url() {
214
+ return $this->destination_url;
215
+ }
216
+
217
+ public function set_option_name($key_option_name) {
218
+ $this->key_option_name = $key_option_name;
219
+ }
220
+
221
+ /**
222
+ * Method to get the remote key
223
+ *
224
+ * @return string
225
+ */
226
+ public function get_key_remote() {
227
+ if (empty($this->key_remote) && $this->can_generate) {
228
+ $this->generate_new_keypair();
229
+ }
230
+
231
+ return empty($this->key_remote) ? false : $this->key_remote;
232
+ }
233
+
234
+ /**
235
+ * Set the remote key
236
+ *
237
+ * @param string $key_remote
238
+ */
239
+ public function set_key_remote($key_remote) {
240
+ $this->key_remote = $key_remote;
241
+ }
242
+
243
+ /**
244
+ * Used for sending - when receiving, the format is part of the message
245
+ *
246
+ * @param integer $format
247
+ */
248
+ public function set_message_format($format = 2) {
249
+ $this->format = $format;
250
+ }
251
+
252
+ /**
253
+ * Used for sending - when receiving, the format is part of the message
254
+ *
255
+ * @return integer
256
+ */
257
+ public function get_message_format() {
258
+ return $this->format;
259
+ }
260
+
261
+ /**
262
+ * Method to get the local key
263
+ *
264
+ * @return string
265
+ */
266
+ public function get_key_local() {
267
+ if (empty($this->key_local)) {
268
+ if ($this->key_option_name) {
269
+ $key_local = get_site_option($this->key_option_name);
270
+ if ($key_local) {
271
+ $this->key_local = $key_local;
272
+ }
273
+ }
274
+ }
275
+ if (empty($this->key_local) && $this->can_generate) {
276
+ $this->generate_new_keypair();
277
+ }
278
+
279
+ return empty($this->key_local) ? false : $this->key_local;
280
+ }
281
+
282
+ /**
283
+ * Tests whether a supplied string (after trimming) is a valid portable bundle
284
+ *
285
+ * @param string $bundle [description]
286
+ * @param string $format same as get_portable_bundle()
287
+ * @return array (which the consumer is free to use - e.g. convert into internationalised string), with keys 'code' and (perhaps) 'data'
288
+ */
289
+ public function decode_portable_bundle($bundle, $format = 'raw') {
290
+ $bundle = trim($bundle);
291
+ if ('base64_with_count' == $format) {
292
+ if (strlen($bundle) < 5) return array('code' => 'invalid_wrong_length', 'data' => 'too_short');
293
+ $len = substr($bundle, 0, 4);
294
+ $bundle = substr($bundle, 4);
295
+ $len = hexdec($len);
296
+ if (strlen($bundle) != $len) return array('code' => 'invalid_wrong_length', 'data' => "1,$len,".strlen($bundle));
297
+ if (false === ($bundle = base64_decode($bundle))) return array('code' => 'invalid_corrupt', 'data' => 'not_base64');
298
+ if (null === ($bundle = json_decode($bundle, true))) return array('code' => 'invalid_corrupt', 'data' => 'not_json');
299
+ }
300
+ if (empty($bundle['key'])) return array('code' => 'invalid_corrupt', 'data' => 'no_key');
301
+ if (empty($bundle['url'])) return array('code' => 'invalid_corrupt', 'data' => 'no_url');
302
+ if (empty($bundle['name_indicator'])) return array('code' => 'invalid_corrupt', 'data' => 'no_name_indicator');
303
+
304
+ return $bundle;
305
+ }
306
+
307
+ /**
308
+ * Method to get a portable bundle sufficient to contact this site (i.e. remote site - so you need to have generated a key-pair, or stored the remote key somewhere and restored it)
309
+ *
310
+ * @param string $format Supported formats: base64_with_count and default)raw
311
+ * @param array $extra_info needs to be JSON-serialisable, so be careful about what you put into it.
312
+ * @param array $options [description]
313
+ * @return array
314
+ */
315
+ public function get_portable_bundle($format = 'raw', $extra_info = array(), $options = array()) {
316
+
317
+ $bundle = array_merge($extra_info, array(
318
+ 'key' => empty($options['key']) ? $this->get_key_remote() : $options['key'],
319
+ 'name_indicator' => $this->key_name_indicator,
320
+ 'url' => trailingslashit(network_site_url()),
321
+ 'admin_url' => trailingslashit(admin_url()),
322
+ 'network_admin_url' => trailingslashit(network_admin_url()),
323
+ 'format_support' => 2,
324
+ ));
325
+
326
+ if ('base64_with_count' == $format) {
327
+ $bundle = base64_encode(json_encode($bundle));
328
+
329
+ $len = strlen($bundle); // Get the length
330
+ $len = dechex($len); // The first bytes of the message are the bundle length
331
+ $len = str_pad($len, 4, '0', STR_PAD_LEFT); // Zero pad
332
+
333
+ return $len.$bundle;
334
+
335
+ } else {
336
+ return $bundle;
337
+ }
338
+
339
+ }
340
+
341
+ public function set_key_local($key_local) {
342
+ $this->key_local = $key_local;
343
+ if ($this->key_option_name) update_site_option($this->key_option_name, $this->key_local);
344
+ }
345
+
346
+ public function generate_new_keypair($key_size = 2048) {
347
+
348
+ $this->ensure_crypto_loaded();
349
+
350
+ $rsa = new Crypt_RSA();
351
+ $keys = $rsa->createKey($key_size);
352
+
353
+ if (empty($keys['privatekey'])) {
354
+ $this->set_key_local(false);
355
+ } else {
356
+ $this->set_key_local($keys['privatekey']);
357
+ }
358
+
359
+ if (empty($keys['publickey'])) {
360
+ $this->set_key_remote(false);
361
+ } else {
362
+ $this->set_key_remote($keys['publickey']);
363
+ }
364
+
365
+ return empty($keys['publickey']) ? false : true;
366
+ }
367
+
368
+ /**
369
+ * A base-64 encoded RSA hash (PKCS_1) of the message digest
370
+ *
371
+ * @param string $message
372
+ * @param boolean $use_key
373
+ * @return array
374
+ */
375
+ public function signature_for_message($message, $use_key = false) {
376
+
377
+ $hash_algorithm = 'sha256';
378
+
379
+ // Sign with the private (local) key
380
+ if (!$use_key) {
381
+ if (!$this->key_local) throw new Exception('No signing key has been set');
382
+ $use_key = $this->key_local;
383
+ }
384
+
385
+ $this->ensure_crypto_loaded();
386
+
387
+ $rsa = new Crypt_RSA();
388
+ $rsa->loadKey($use_key);
389
+ // This is the older signature mode; phpseclib's default is the preferred CRYPT_RSA_SIGNATURE_PSS; however, Forge JS doesn't yet support this. More info: https://en.wikipedia.org/wiki/PKCS_1
390
+ $rsa->setSignatureMode(CRYPT_RSA_SIGNATURE_PKCS1);
391
+
392
+ // Don't do this: Crypt_RSA::sign() already calculates the digest of the hash
393
+ // $hash = new Crypt_Hash($hash_algorithm);
394
+ // $hashed = $hash->hash($message);
395
+
396
+ // if ($this->debug) $this->log("Message hash (hash=$hash_algorithm) (hex): ".bin2hex($hashed));
397
+
398
+ // phpseclib defaults to SHA1
399
+ $rsa->setHash($hash_algorithm);
400
+ $encrypted = $rsa->sign($message);
401
+
402
+ if ($this->debug) $this->log('Signed hash (mode='.CRYPT_RSA_SIGNATURE_PKCS1.') (hex): '.bin2hex($encrypted));
403
+
404
+ $signature = base64_encode($encrypted);
405
+
406
+ if ($this->debug) $this->log("Message signature (base64): $signature");
407
+
408
+ return $signature;
409
+ }
410
+
411
+ /**
412
+ * Log description
413
+ *
414
+ * @param string $message
415
+ * @param string $level $level is not yet used much
416
+ */
417
+ private function log($message, $level = 'notice') {
418
+ // Allow other plugins to do something with the message
419
+ do_action('udrpc_log', $message, $level, $this->key_name_indicator, $this->debug, $this);
420
+ if ('info' != $level) error_log('UDRPC ('.$this->key_name_indicator.", $level): $message");
421
+ }
422
+
423
+ /**
424
+ * Encrypt the message, using the local key (which needs to exist)
425
+ *
426
+ * @param string $plaintext
427
+ * @param boolean $use_key
428
+ * @param integer $key_length
429
+ * @return array
430
+ */
431
+ public function encrypt_message($plaintext, $use_key = false, $key_length = 32) {
432
+
433
+ if (!$use_key) {
434
+ if (1 == $this->format) {
435
+ if (!$this->key_local) throw new Exception('No encryption key has been set');
436
+ $use_key = $this->key_local;
437
+ } else {
438
+ if (!$this->key_remote) throw new Exception('No encryption key has been set');
439
+ $use_key = $this->key_remote;
440
+ }
441
+ }
442
+
443
+ $this->ensure_crypto_loaded();
444
+
445
+ $rsa = new Crypt_RSA();
446
+
447
+ if (defined('UDRPC_PHPSECLIB_ENCRYPTION_MODE')) $rsa->setEncryptionMode(UDRPC_PHPSECLIB_ENCRYPTION_MODE);
448
+
449
+ $rij = new Crypt_Rijndael();
450
+
451
+ // Generate Random Symmetric Key
452
+ $sym_key = crypt_random_string($key_length);
453
+
454
+ if ($this->debug) $this->log('Unencrypted symmetric key (hex): '.bin2hex($sym_key));
455
+
456
+ // Encrypt Message with new Symmetric Key
457
+ $rij->setKey($sym_key);
458
+ $ciphertext = $rij->encrypt($plaintext);
459
+
460
+ if ($this->debug) $this->log('Encrypted ciphertext (hex): '.bin2hex($ciphertext));
461
+
462
+ $ciphertext = base64_encode($ciphertext);
463
+
464
+ // Encrypt the Symmetric Key with the Asymmetric Key
465
+ $rsa->loadKey($use_key);
466
+ $sym_key = $rsa->encrypt($sym_key);
467
+
468
+ if ($this->debug) $this->log('Encrypted symmetric key (hex): '.bin2hex($sym_key));
469
+
470
+ // Base 64 encode the symmetric key for transport
471
+ $sym_key = base64_encode($sym_key);
472
+
473
+ if ($this->debug) $this->log('Encrypted symmetric key (b64): '.$sym_key);
474
+
475
+ $len = str_pad(dechex(strlen($sym_key)), 3, '0', STR_PAD_LEFT); // Zero pad to be sure.
476
+
477
+ // 16 characters of hex is enough for the payload to be to 16 exabytes (giga < tera < peta < exa) of data
478
+ $cipherlen = str_pad(dechex(strlen($ciphertext)), 16, '0', STR_PAD_LEFT);
479
+
480
+ // Concatenate the length, the encrypted symmetric key, and the message
481
+ return $len.$sym_key.$cipherlen.$ciphertext;
482
+
483
+ }
484
+
485
+ /**
486
+ * Decrypt the message, using the local key (which needs to exist)
487
+ *
488
+ * @param string $message
489
+ * @return array
490
+ */
491
+ public function decrypt_message($message) {
492
+
493
+ if (!$this->key_local) throw new Exception('No decryption key has been set');
494
+
495
+ $this->ensure_crypto_loaded();
496
+
497
+ $rsa = new Crypt_RSA();
498
+ if (defined('UDRPC_PHPSECLIB_ENCRYPTION_MODE')) $rsa->setEncryptionMode(UDRPC_PHPSECLIB_ENCRYPTION_MODE);
499
+ // Defaults to CRYPT_AES_MODE_CBC
500
+ $rij = new Crypt_Rijndael();
501
+
502
+ // Extract the Symmetric Key
503
+ $len = substr($message, 0, 3);
504
+ $len = hexdec($len);
505
+ $sym_key = substr($message, 3, $len);
506
+
507
+ // Extract the encrypted message
508
+ $cipherlen = substr($message, ($len + 3), 16);
509
+ $cipherlen = hexdec($cipherlen);
510
+
511
+ $ciphertext = substr($message, ($len + 19), $cipherlen);
512
+ $ciphertext = base64_decode($ciphertext);
513
+
514
+ // Decrypt the encrypted symmetric key
515
+ $rsa->loadKey($this->key_local);
516
+ $sym_key = base64_decode($sym_key);
517
+ $sym_key = $rsa->decrypt($sym_key);
518
+
519
+ // Decrypt the message
520
+ $rij->setKey($sym_key);
521
+
522
+ return $rij->decrypt($ciphertext);
523
+
524
+ }
525
+
526
+ /**
527
+ * Creates a message
528
+ *
529
+ * @param string $command
530
+ * @param string $data
531
+ * @param boolean $is_response
532
+ * @param boolean $use_key_remote
533
+ * @param boolean $use_key_local
534
+ * @return array which the caller will then format as required (e.g. use as body in post, or JSON-encode, etc.) [description]
535
+ */
536
+ public function create_message($command, $data = null, $is_response = false, $use_key_remote = false, $use_key_local = false) {
537
+
538
+ if ($is_response) {
539
+ $send_array = array('response' => $command);
540
+ } else {
541
+ $send_array = array('command' => $command);
542
+ }
543
+
544
+ $send_array['time'] = time();
545
+ // This goes in the encrypted portion as well to prevent replays with a different unencrypted name indicator
546
+ $send_array['key_name'] = $this->key_name_indicator;
547
+
548
+ // This random element means that if the site needs to send two identical commands or responses in the same second, then it can, and still use replay protection
549
+ // The value of PHP_INT_MAX on a 32-bit platform
550
+ $this->message_random_number = rand(1, 2147483647);
551
+ $send_array['rand'] = $this->message_random_number;
552
+
553
+ if ($this->next_send_sequence_id) {
554
+ $send_array['sequence_id'] = $this->next_send_sequence_id;
555
+ ++$this->next_send_sequence_id;
556
+ }
557
+
558
+ if ($is_response && !empty($this->incoming_message) && isset($this->incoming_message['rand'])) {
559
+ $send_array['incoming_rand'] = $this->incoming_message['rand'];
560
+ }
561
+
562
+ if (null !== $data) $send_array['data'] = $data;
563
+ $send_data = $this->encrypt_message(json_encode($send_array), $use_key_remote);
564
+
565
+ $message = array(
566
+ 'format' => $this->format,
567
+ 'key_name' => $this->key_name_indicator,
568
+ 'udrpc_message' => $send_data,
569
+ );
570
+
571
+ if ($this->format >= 2) {
572
+ $signature = $this->signature_for_message($send_data, $use_key_local);
573
+ $message['signature'] = $signature;
574
+ }
575
+
576
+ return $message;
577
+
578
+ }
579
+
580
+ /**
581
+ * N.B. There's already some time-based replay protection. This can be turned on to beef it up.
582
+ * This is only for listeners. Replays can only be detection if transients are working on the WP site (which by default only means that the option table is working).
583
+ *
584
+ * @param boolean $activate
585
+ */
586
+ public function activate_replay_protection($activate = true) {
587
+ $this->extra_replay_protection = (bool) $activate;
588
+ }
589
+
590
+ public function set_next_send_sequence_id($id) {
591
+ $this->next_send_sequence_id = $id;
592
+ }
593
+
594
+ /**
595
+ * Set_http_credentials
596
+ *
597
+ * @param string $credentials should be an array with entries for 'username' and 'password'
598
+ */
599
+ public function set_http_credentials($credentials) {
600
+ $this->http_credentials = $credentials;
601
+ }
602
+
603
+ /**
604
+ * This needs only to return an array with keys body and response - where response is also an array, with key 'code' (the HTTP status code)
605
+ * The $post_options array support these keys: timeout, body,
606
+ * Public, to allow short-circuiting of the library's own encoding/decoding (e.g. for acting as a proxy for a message already encrypted elsewhere)
607
+ *
608
+ * @param array $post_options
609
+ * @return array
610
+ */
611
+ public function http_post($post_options) {
612
+ global $wp_version;
613
+ include ABSPATH.WPINC.'/version.php';
614
+ $http_credentials = $this->http_credentials;
615
+
616
+ if (is_a($this->http_transport, 'GuzzleHttp\Client')) {
617
+
618
+ // https://guzzle.readthedocs.org/en/5.3/clients.html
619
+
620
+ $client = $this->http_transport;
621
+
622
+ $guzzle_options = array(
623
+ 'form_params' => $post_options['body'],
624
+ 'headers' => array(
625
+ 'User-Agent' => 'WordPress/'.$wp_version.'; class-udrpc.php-Guzzle/'.$this->version.'; '.get_bloginfo('url'),
626
+ ),
627
+ 'exceptions' => false,
628
+ 'timeout' => $post_options['timeout'],
629
+ );
630
+
631
+ if (!class_exists('WP_HTTP_Proxy')) include_once ABSPATH.WPINC.'/class-http.php';
632
+ $proxy = new WP_HTTP_Proxy();
633
+ if ($proxy->is_enabled()) {
634
+ $user = $proxy->username();
635
+ $pass = $proxy->password();
636
+ $host = $proxy->host();
637
+ $port = (int) $proxy->port();
638
+ if (empty($port)) $port = 8080;
639
+ if (!empty($host) && $proxy->send_through_proxy($this->destination_url)) {
640
+ $proxy_auth = '';
641
+ if (!empty($user)) {
642
+ $proxy_auth = $user;
643
+ if (!empty($pass)) $proxy_auth .= ':'.$pass;
644
+ $proxy_auth .= '@';
645
+ }
646
+ $guzzle_options['proxy'] = array(
647
+ 'http' => "http://${proxy_auth}$host:$port",
648
+ 'https' => "http://${proxy_auth}$host:$port",
649
+ );
650
+ }
651
+ }
652
+
653
+ if (defined('UDRPC_GUZZLE_SSL_VERIFY')) {
654
+ $verify = UDRPC_GUZZLE_SSL_VERIFY;
655
+ } elseif (file_exists(ABSPATH.WPINC.'/certificates/ca-bundle.crt')) {
656
+ $verify = ABSPATH.WPINC.'/certificates/ca-bundle.crt';
657
+ } else {
658
+ $verify = true;
659
+ }
660
+
661
+ $guzzle_options['verify'] = apply_filters('udrpc_guzzle_verify', $verify);
662
+
663
+ if (!empty($http_credentials['username'])) {
664
+
665
+ $authentication_method = empty($http_credentials['authentication_method']) ? 'basic' : $http_credentials['authentication_method'];
666
+
667
+ $password = empty($http_credentials['password']) ? '' : $http_credentials['password'];
668
+
669
+ $guzzle_options['auth'] = array(
670
+ $http_credentials['username'],
671
+ $password,
672
+ $authentication_method,
673
+ );
674
+
675
+ }
676
+
677
+ $response = $client->post($this->destination_url, apply_filters('udrpc_guzzle_options', $guzzle_options, $this));
678
+
679
+ $formatted_response = array(
680
+ 'response' => array(
681
+ 'code' => $response->getStatusCode(),
682
+ ),
683
+ 'body' => $response->getBody(),
684
+ );
685
+
686
+ return $formatted_response;
687
+
688
+ } else {
689
+
690
+ $post_options['user-agent'] = 'WordPress/'.$wp_version.'; class-udrpc.php/'.$this->version.'; '.get_bloginfo('url');
691
+
692
+ if (!empty($http_credentials['username'])) {
693
+
694
+ $authentication_type = empty($http_credentials['authentication_type']) ? 'basic' : $http_credentials['authentication_type'];
695
+
696
+ if ('basic' != $authentication_type) {
697
+ return new WP_Error('unsupported_http_authentication_type', 'Only HTTP basic authentication is supported (for other types, use Guzzle)');
698
+ }
699
+
700
+ $password = empty($http_credentials['password']) ? '' : $http_credentials['password'];
701
+ $post_options['headers'] = array(
702
+ 'Authorization' => 'Basic '.base64_encode($http_credentials['username'].':'.$password),
703
+ );
704
+ }
705
+
706
+ return wp_remote_post(
707
+ $this->destination_url,
708
+ $post_options
709
+ );
710
+ }
711
+ }
712
+
713
+ public function send_message($command, $data = null, $timeout = 20) {
714
+
715
+ if (empty($this->destination_url)) return new WP_Error('not_initialised', 'RPC error: URL not initialised');
716
+
717
+ $message = $this->create_message($command, $data);
718
+
719
+ $post_options = array(
720
+ 'timeout' => $timeout,
721
+ 'body' => $message,
722
+ );
723
+
724
+ $post_options = apply_filters('udrpc_post_options', $post_options, $command, $data, $timeout, $this);
725
+
726
+ // Make the memory available - may be useful if the message was large
727
+ unset($data);
728
+
729
+ try {
730
+ $post = $this->http_post($post_options);
731
+ } catch (Exception $e) {
732
+ // Curl can return an error code 0, which causes WP_Error to return early, without recording the message. So, we prefix the code.
733
+ return new WP_Error('http_post_'.$e->getCode(), $e->getMessage());
734
+ }
735
+
736
+ if (is_wp_error($post)) return $post;
737
+
738
+ $response_code = wp_remote_retrieve_response_code($post);
739
+
740
+ if (empty($response_code)) return new WP_Error('empty_http_code', 'Unexpected HTTP response code');
741
+
742
+ if ($response_code < 200 || $response_code >= 300) return new WP_Error('unexpected_http_code', 'Unexpected HTTP response code ('.$response_code.')', $post);
743
+
744
+ $response_body = wp_remote_retrieve_body($post);
745
+
746
+ if (empty($response_body)) return new WP_Error('empty_response', 'Empty response from remote site');
747
+
748
+ $decoded = json_decode($response_body, true);
749
+
750
+ if (empty($decoded)) {
751
+
752
+ if (false != ($found_at = strpos($response_body, '{"format":'))) {
753
+ $new_body = substr($response_body, $found_at);
754
+ $decoded = json_decode($new_body, true);
755
+ }
756
+
757
+ if (empty($decoded)) {
758
+ $this->log('response from remote site ('.$this->destination_url.') could not be understood: '.substr($response_body, 0, 100).' ... ', 'info');
759
+ return new WP_Error('response_not_understood', 'Response from remote site could not be understood', $response_body);
760
+ }
761
+ }
762
+
763
+ if (!is_array($decoded) || empty($decoded['udrpc_message'])) return new WP_Error('response_not_understood', 'Response from remote site was not in the expected format ('.$post['body'].')', $decoded);
764
+
765
+ if ($this->format >= 2) {
766
+ if (empty($decoded['signature'])) {
767
+ $this->log('No message signature found');
768
+ die;
769
+ }
770
+ if (!$this->key_remote) {
771
+ $this->log('No signature verification key has been set');
772
+ die;
773
+ }
774
+ if (!$this->verify_signature($decoded['udrpc_message'], $decoded['signature'], $this->key_remote)) {
775
+ $this->log('Signature verification failed; discarding');
776
+ die;
777
+ }
778
+ }
779
+
780
+ $decoded = $this->decrypt_message($decoded['udrpc_message']);
781
+
782
+ if (!is_string($decoded)) return new WP_Error('not_decrypted', 'Response from remote site was not successfully decrypted', $decoded['udrpc_message']);
783
+
784
+ $json_decoded = json_decode($decoded, true);
785
+
786
+ if (!is_array($json_decoded) || empty($json_decoded['response']) || empty($json_decoded['time']) || !is_numeric($json_decoded['time'])) return new WP_Error('response_corrupt', 'Response from remote site was not in the expected format', $decoded);
787
+
788
+ // Don't do the reply detection until now, because $post['body'] may not be a message that originated from the remote component at all (e.g. an HTTP error)
789
+ if ($this->extra_replay_protection) {
790
+ $message_hash = $this->calculate_message_hash((string) $post['body']);
791
+ if ($this->message_hash_seen($message_hash)) {
792
+ return new WP_Error('replay_detected', 'Message refused: replay detected', $message_hash);
793
+ }
794
+ }
795
+
796
+ $time_difference = absint((time() - $json_decoded['time']));
797
+ if ($time_difference > $this->maximum_replay_time_difference) return new WP_Error('window_error', 'Message refused: maxium replay time difference exceeded', $time_difference);
798
+
799
+ if (isset($json_decoded['incoming_rand']) && !empty($this->message_random_number) && $json_decoded['incoming_rand'] != $this->message_random_number) {
800
+ // @codingStandardsIgnoreLine
801
+ $this->log('UDRPC: Message mismatch (possibly MITM) (sent_rand=' + $this->message_random_number + ', returned_rand='.$json_decoded['incoming_rand'].'): dropping', 'error');
802
+
803
+ return new WP_Error('message_mismatch_error', 'Message refused: message mismatch (possible MITM)');
804
+
805
+ }
806
+
807
+ // Should be an array with keys including 'response' and (if relevant) 'data'
808
+ return $json_decoded;
809
+
810
+ }
811
+
812
+ /**
813
+ * Returns a boolean indicating whether a listener was created - which depends on whether one was needed (so, false does not necessarily indicate an error condition)
814
+ *
815
+ * @return boolean
816
+ */
817
+ public function create_listener() {
818
+
819
+ $http_origin = function_exists('get_http_origin') ? get_http_origin() : (empty($_SERVER['HTTP_ORIGIN']) ? '' : $_SERVER['HTTP_ORIGIN']);
820
+
821
+ // Create the WP actions to handle incoming commands, handle built-in commands (e.g. ping, create_keys (authenticate with admin creds)), dispatch them to the right place, and die
822
+ if ((!empty($_POST) && !empty($_POST['udrpc_message']) && !empty($_POST['format'])) || (!empty($_SERVER['REQUEST_METHOD']) && 'OPTIONS' == $_SERVER['REQUEST_METHOD'] && $http_origin)) {
823
+ add_action('wp_loaded', array($this, 'wp_loaded'));
824
+ add_action('wp_loaded', array($this, 'wp_loaded_final'), 10000);
825
+ return true;
826
+ }
827
+
828
+ return false;
829
+ }
830
+
831
+ public function wp_loaded_final() {
832
+ if (empty($this->require_message_to_be_understood)) return;
833
+ $message_for = empty($_POST['key_name']) ? '' : (string) $_POST['key_name'];
834
+ $this->log("Message was received, but not understood by local site (for: $message_for)");
835
+ die;
836
+ }
837
+
838
+ public function wp_loaded() {
839
+
840
+ /*
841
+ // What if something else already set some response headers?
842
+ if (function_exists('apache_response_headers')) {
843
+ $apache_response_headers = apache_response_headers();
844
+ // Do something...
845
+ }
846
+ */
847
+
848
+ // CORS: https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS
849
+ // get_http_origin() : since WP 3.4
850
+ $http_origin = function_exists('get_http_origin') ? get_http_origin() : (empty($_SERVER['HTTP_ORIGIN']) ? '' : $_SERVER['HTTP_ORIGIN']);
851
+ if (!empty($_SERVER['REQUEST_METHOD']) && 'OPTIONS' == $_SERVER['REQUEST_METHOD'] && $http_origin) {
852
+ if (in_array($http_origin, $this->allow_cors_from)) {
853
+ // @codingStandardsIgnoreLine
854
+ if (!defined('UDRPC_DO_NOT_SEND_CORS_HEADERS') || !UDRPC_DO_NOT_SEND_CORS_HEADERS) {
855
+ header("Access-Control-Allow-Origin: $http_origin");
856
+ header('Access-Control-Allow-Credentials: true');
857
+ if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD'])) header('Access-Control-Allow-Methods: POST, OPTIONS');
858
+ if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS'])) header('Access-Control-Allow-Headers: '.$_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']);
859
+ }
860
+ die;
861
+ } elseif ($this->debug) {
862
+ $this->log('Non-allowed CORS from: '.$http_origin);
863
+ }
864
+ // Having detected that this is a CORS request, there's nothing more to do. We return, because a different listener might pick it up, even though we didn't.
865
+ return;
866
+ }
867
+
868
+ // Silently return, rather than dying, in case another instance is able to handle this
869
+ if (empty($_POST['format']) || (1 != $_POST['format'] && 2 != $_POST['format'])) return;
870
+
871
+ $this->require_message_to_be_understood = true;
872
+
873
+ $format = $_POST['format'];
874
+
875
+ /*
876
+ In format 1 (legacy/obsolete), the one encrypts (the shared AES key) using one half of the key-pair, and decrypts with the other; whereas the other side of the conversation does the reverse when replying (and uses a different shared AES key). Though this is possible in RSA, this is the wrong thing to do - see https://crypto.stackexchange.com/questions/2123/rsa-encryption-with-private-key-and-decryption-with-a-public-key
877
+ In format 2, both sides have their own private and public key. The sender encrypts using the other side's public key, and decrypts using its own private key. Messages are signed (the message digest is SHA-256).
878
+ */
879
+
880
+ // Is this for us?
881
+ if (empty($_POST['key_name']) || $_POST['key_name'] != $this->key_name_indicator) {
882
+ return;
883
+ }
884
+
885
+ // wp_unslash() does not exist until after WP 3.5
886
+ // $udrpc_message = function_exists('wp_unslash') ? wp_unslash($_POST['udrpc_message']) : stripslashes_deep($_POST['udrpc_message']);
887
+
888
+ // Data should not have any slashes - it is base64-encoded
889
+ $udrpc_message = (string) $_POST['udrpc_message'];
890
+
891
+ // Check this now, rather than allow the decrypt method to thrown an Exception
892
+
893
+ if (empty($this->key_local)) {
894
+ $this->log('no local key (format 1): cannot decrypt', 'error');
895
+ die;
896
+ }
897
+
898
+ if ($format >= 2) {
899
+ if (empty($_POST['signature'])) {
900
+ $this->log('No message signature found', 'error');
901
+ die;
902
+ }
903
+ if (!$this->key_remote) {
904
+ $this->log('No signature verification key has been set', 'error');
905
+ die;
906
+ }
907
+ if (!$this->verify_signature($udrpc_message, $_POST['signature'], $this->key_remote)) {
908
+ $this->log('Signature verification failed; discarding', 'error');
909
+ die;
910
+ }
911
+ }
912
+
913
+ try {
914
+ $udrpc_message = $this->decrypt_message($udrpc_message);
915
+ } catch (Exception $e) {
916
+ $this->log('Exception ('.get_class($e).'): '.$e->getMessage(), 'error');
917
+ die;
918
+ }
919
+
920
+ $udrpc_message = json_decode($udrpc_message, true);
921
+
922
+ if (empty($udrpc_message) || !is_array($udrpc_message) || empty($udrpc_message['command']) || !is_string($udrpc_message['command'])) {
923
+ $this->log('Could not decode JSON on incoming message', 'error');
924
+ die;
925
+ }
926
+
927
+ if (empty($udrpc_message['time'])) {
928
+ $this->log('No time set in incoming message', 'error');
929
+ die;
930
+ }
931
+
932
+ // Mismatch indicating a replay of the message with a different key name in the unencrypted portion?
933
+ if (empty($udrpc_message['key_name']) || $_POST['key_name'] != $udrpc_message['key_name']) {
934
+ $this->log('key_name mismatch between encrypted and unencrypted portions', 'error');
935
+ die;
936
+ }
937
+
938
+ if ($this->extra_replay_protection) {
939
+ $message_hash = $this->calculate_message_hash((string) $_POST['udrpc_message']);
940
+ if ($this->message_hash_seen($message_hash)) {
941
+ $this->log("Message dropped: apparently a replay (hash: $message_hash)", 'error');
942
+ die;
943
+ }
944
+ }
945
+
946
+ // Do this after the extra replay protection, as that checks hashes within the maximum time window - so don't check the maximum time window until afterwards, to avoid a tiny window (race) in between.
947
+ $time_difference = absint($udrpc_message['time'] - time());
948
+ if ($time_difference > $this->maximum_replay_time_difference) {
949
+ $this->log("Time in incoming message is outside of allowed window ($time_difference > ".$this->maximum_replay_time_difference.')', 'error');
950
+ die;
951
+ }
952
+
953
+ // The sequence number should always be larger than any previously-sent sequence number
954
+ if ($this->sequence_protection_tolerance) {
955
+
956
+ if ($this->debug) $this->log('Sequence protection is active; tolerance: '.$this->sequence_protection_tolerance);
957
+
958
+ global $wpdb;
959
+
960
+ if (!isset($udrpc_message['sequence_id']) || !is_numeric($udrpc_message['sequence_id'])) {
961
+ $this->log('a numerical sequence number is required, but none was included in the message - dropping', 'error');
962
+ die;
963
+ }
964
+
965
+ $message_sequence_id = (int) $udrpc_message['sequence_id'];
966
+ $recently_seen_sequences_ids = $wpdb->get_var($wpdb->prepare('SELECT %s FROM %s LIMIT 1 WHERE '.$this->sequence_protection_where_sql, $this->sequence_protection_column, $this->sequence_protection_table));
967
+
968
+ if ('' === $recently_seen_sequences_ids) $recently_seen_sequences_ids = '0';
969
+
970
+ $recently_seen_sequences_ids_as_array = explode($recently_seen_sequences_ids, ',');
971
+ sort($recently_seen_sequences_ids_as_array);
972
+
973
+ // Seen before?
974
+ if (in_array($message_sequence_id, $recently_seen_sequences_ids_as_array)) {
975
+ $this->log("message with duplicate sequence number received - dropping (received=$message_sequence_id, seen=$recently_seen_sequences_ids)");
976
+ die;
977
+ }
978
+
979
+ // Within the tolerance threshold? That means: a) either bigger than the max, or b) no more than <tolerance> lower than the least
980
+ if ($message_sequence_id > max($recently_seen_sequences_ids)) {
981
+ if ($this->debug) $this->log("Sequence id ($message_sequence_id) is greater than any previous (".max($recently_seen_sequences_ids).') - message is thus OK');
982
+ // All is well
983
+ $recently_seen_sequences_ids_as_array[] = $message_sequence_id;
984
+ } elseif ((max($recently_seen_sequences_ids) - $message_sequence_id) <= $this->sequence_protection_tolerance) {
985
+ // All is well - was one of those 'missing' in the sequence
986
+ if ($this->debug) $this->log("Sequence id ($message_sequence_id) is within tolerance range of previous maximum (".max($recently_seen_sequences_ids).') - message is thus OK');
987
+ $recently_seen_sequences_ids_as_array[] = $message_sequence_id;
988
+ } else {
989
+ $this->log("message received outside of allowed sequence window - dropping (received=$message_sequence_id, seen=$recently_seen_sequences_ids, tolerance=".$this->sequence_protection_tolerance.')', 'error');
990
+ die;
991
+ }
992
+
993
+ // Remove out-of-bounds seen IDs
994
+ $max_sequence_id_seen = max($recently_seen_sequences_ids_as_array);
995
+ foreach ($recently_seen_sequences_ids_as_array as $k => $id) {
996
+ if ($max_sequence_id_seen - $id > $this->sequence_protection_tolerance) {
997
+ if ($this->debug) $this->log("Removing no-longer-relevant sequence from list of those recently seen: $id");
998
+ unset($recently_seen_sequences_ids_as_array[$k]);
999
+ }
1000
+ }
1001
+
1002
+ // Allow reset
1003
+ if ($message_sequence_id > PHP_INT_MAX - 10) {
1004
+ $recently_seen_sequences_ids_as_array = array(0);
1005
+ }
1006
+
1007
+ // Write them back to the database
1008
+ $sql = $wpdb->prepare('UPDATE %s SET %s=%s WHERE '.$this->sequence_protection_where_sql, $this->sequence_protection_table, $this->sequence_protection_column, implode(',', $recently_seen_sequences_ids_as_array));
1009
+ if ($this->debug) $this->log("SQL to send recent sequence IDs back to the database: $sql");
1010
+ $wpdb->query($sql);
1011
+
1012
+ }
1013
+
1014
+ $this->incoming_message = $udrpc_message;
1015
+
1016
+ $command = (string) $udrpc_message['command'];
1017
+ $data = empty($udrpc_message['data']) ? null : $udrpc_message['data'];
1018
+
1019
+ // @codingStandardsIgnoreLine
1020
+ if ($http_origin && !empty($udrpc_message['cors_headers_wanted']) && (!defined('UDRPC_DO_NOT_SEND_CORS_HEADERS') || !UDRPC_DO_NOT_SEND_CORS_HEADERS)) {
1021
+ header("Access-Control-Allow-Origin: $http_origin");
1022
+ header('Access-Control-Allow-Credentials: true');
1023
+ }
1024
+
1025
+ $this->log('Command received: '.$command, 'info');
1026
+
1027
+ if ('ping' == $command) {
1028
+ $response = array('response' => 'pong', 'data' => null);
1029
+ } else {
1030
+ if (has_filter('udrpc_command_'.$command)) {
1031
+ $response = apply_filters('udrpc_command_'.$command, null, $data, $this->key_name_indicator);
1032
+ } else {
1033
+ $response = array('response' => 'rpcerror', 'data' => array('code' => 'unknown_rpc_command', 'data' => $command));
1034
+ }
1035
+ }
1036
+
1037
+ $response = apply_filters('udrpc_action', $response, $command, $data, $this->key_name_indicator, $this);
1038
+
1039
+ if (is_array($response)) {
1040
+
1041
+ if ($this->debug) {
1042
+ $this->log('UDRPC response (pre-encoding/encryption): '.serialize($response));
1043
+ }
1044
+
1045
+ $data = isset($response['data']) ? $response['data'] : null;
1046
+
1047
+ $final_response = json_encode($this->create_message($response['response'], $data, true));
1048
+
1049
+ do_action('udrpc_action_send_response', $final_response, $command);
1050
+
1051
+ echo $final_response;
1052
+ }
1053
+
1054
+ die;
1055
+
1056
+ }
1057
+
1058
+ /**
1059
+ * The hash needs to be in a format that phpseclib likes. phpseclib uses lower case.
1060
+ * Pass in a base64-encoded signature (i.e. just as signature_for_message creates)
1061
+ *
1062
+ * @param string $message
1063
+ * @param string $signature
1064
+ * @param string $key
1065
+ * @param string $hash_algorithm
1066
+ * @return boolean
1067
+ */
1068
+ public function verify_signature($message, $signature, $key, $hash_algorithm = 'sha256') {
1069
+ $this->ensure_crypto_loaded();
1070
+ $rsa = new Crypt_RSA();
1071
+ $rsa->setHash(strtolower($hash_algorithm));
1072
+ // This is not the default, but is what we use
1073
+ $rsa->setSignatureMode(CRYPT_RSA_SIGNATURE_PKCS1);
1074
+ $rsa->loadKey($key);
1075
+
1076
+ // Don't hash it - Crypt_RSA::verify() already does that
1077
+ // $hash = new Crypt_Hash($hash_algorithm);
1078
+ // $hashed = $hash->hash($message);
1079
+
1080
+ $verified = $rsa->verify($message, base64_decode($signature));
1081
+
1082
+ if ($this->debug) $this->log('Signature verification result: '.serialize($verified));
1083
+
1084
+ return $verified;
1085
+ }
1086
+
1087
+ private function calculate_message_hash($message) {
1088
+ return hash('sha256', $message);
1089
+ }
1090
+
1091
+ private function message_hash_seen($message_hash) {
1092
+ // 39 characters - less than the WP site transient name limit (40). Though, we use a normal transient, as these don't auto-load at all times.
1093
+ $transient_name = 'udrpch_'.md5($this->key_name_indicator);
1094
+ $seen_hashes = get_transient($transient_name);
1095
+ if (!is_array($seen_hashes)) $seen_hashes = array();
1096
+ $time_now = time();
1097
+ // $any_changes = false;
1098
+ // Prune the old hashes
1099
+ foreach ($seen_hashes as $hash => $last_seen) {
1100
+ if ($last_seen < ($time_now - $this->maximum_replay_time_difference)) {
1101
+ // $any_changes = true;
1102
+ unset($seen_hashes[$hash]);
1103
+ }
1104
+ }
1105
+ if (isset($seen_hashes[$message_hash])) {
1106
+ return true;
1107
+ }
1108
+ $seen_hashes[$message_hash] = $time_now;
1109
+ set_transient($transient_name, $seen_hashes, $this->maximum_replay_time_difference);
1110
+
1111
+ return false;
1112
+ }
1113
+ }
1114
+
1115
+ endif;
vendor/team-updraft/common-libs/src/updraft-semaphore/class-updraft-semaphore.php ADDED
@@ -0,0 +1,213 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ if (!defined('ABSPATH')) die('No direct access.');
4
+
5
+ /**
6
+ * Class Updraft_Semaphore_3_0
7
+ *
8
+ * This class is much simpler to use than the the previous series, as it has dropped support for complicated cases that were not being used. It also now only uses a single row in the options database, and takes care of creating it itself internally.
9
+ *
10
+ * Logging, though, may be noisier, unless your loggers are taking note of the log level and only registering what is required.
11
+ *
12
+ * Example of use (a lock that will expire if not released within 300 seconds)
13
+ *
14
+ * See test.php for a longer example (including logging).
15
+ *
16
+ * $my_lock = new Updraft_Semaphore_3_0('my_lock_name', 300);
17
+ * // If getting the lock does not succeed first time, try again up to twice
18
+ * if ($my_lock->lock(2)) {
19
+ * try {
20
+ * // do stuff ...
21
+ * } catch (Exception $e) {
22
+ * // We are making sure we release the lock in case of an error
23
+ * } catch (Error $e) {
24
+ * // We are making sure we release the lock in case of an error
25
+ * }
26
+ * $my_lock->release();
27
+ * } else {
28
+ * error_log("Sorry, could not get the lock");
29
+ * }
30
+ */
31
+ class Updraft_Semaphore_3_0 {
32
+
33
+ // Time after which the lock will expire (in seconds)
34
+ protected $locked_for;
35
+
36
+ // Name for the lock in the WP options table
37
+ protected $option_name;
38
+
39
+ // Lock status - a boolean
40
+ protected $acquired = false;
41
+
42
+ // An array of loggers
43
+ protected $loggers = array();
44
+
45
+ /**
46
+ * Constructor. Instantiating does not lock anything, but sets up the details for future operations.
47
+ *
48
+ * @param String $name - a unique (across the WP site) name for the lock. Should be no more than 51 characters in length (because of the use of the WP options table, with some further characters used internally)
49
+ * @param Integer $locked_for - time (in seconds) after which the lock will expire if not released. This needs to be positive if you don't want bad things to happen.
50
+ * @param Array $loggers - an array of loggers
51
+ */
52
+ public function __construct($name, $locked_for = 300, $loggers = array()) {
53
+ $this->option_name = 'updraft_lock_'.$name;
54
+ $this->locked_for = $locked_for;
55
+ $this->loggers = $loggers;
56
+ }
57
+
58
+ /**
59
+ * Internal function to make sure that the lock is set up in the database
60
+ *
61
+ * @return Integer - 0 means 'failed' (which could include that someone else concurrently created it); 1 means 'already existed'; 2 means 'exists, because we created it). The intention is that non-zero results mean that the lock exists.
62
+ */
63
+ private function ensure_database_initialised() {
64
+
65
+ global $wpdb;
66
+
67
+ $sql = $wpdb->prepare("SELECT COUNT(*) FROM {$wpdb->options} WHERE option_name = %s", $this->option_name);
68
+
69
+ if (1 === (int) $wpdb->get_var($sql)) {
70
+ $this->log('Lock option ('.$this->option_name.', '.$wpdb->options.') already existed in the database', 'debug');
71
+ return 1;
72
+ }
73
+
74
+ $sql = $wpdb->prepare("INSERT INTO {$wpdb->options} (option_name, option_value, autoload) VALUES(%s, '0', 'no');", $this->option_name);
75
+
76
+ $rows_affected = $wpdb->query($sql);
77
+
78
+ if ($rows_affected > 0) {
79
+ $this->log('Lock option ('.$this->option_name.', '.$wpdb->options.') was created in the database', 'debug');
80
+ } else {
81
+ $this->log('Lock option ('.$this->option_name.', '.$wpdb->options.') failed to be created in the database (could already exist)', 'notice');
82
+ }
83
+
84
+ return ($rows_affected > 0) ? 2 : 0;
85
+ }
86
+
87
+ /**
88
+ * Attempt to acquire the lock. If it was already acquired, then nothing extra will be done (the method will be a no-op).
89
+ *
90
+ * @param Integer $retries - how many times to retry (after a 1 second sleep each time)
91
+ *
92
+ * @return Boolean - whether the lock was successfully acquired or not
93
+ */
94
+ public function lock($retries = 0) {
95
+
96
+ if ($this->acquired) return true;
97
+
98
+ global $wpdb;
99
+
100
+ $time_now = time();
101
+ $acquire_until = $time_now + $this->locked_for;
102
+
103
+ $sql = $wpdb->prepare("UPDATE {$wpdb->options} SET option_value = %s WHERE option_name = %s AND option_value < %d", $acquire_until, $this->option_name, $time_now);
104
+
105
+ if (1 === $wpdb->query($sql)) {
106
+ $this->log('Lock ('.$this->option_name.', '.$wpdb->options.') acquired', 'info');
107
+ $this->acquired = true;
108
+ return true;
109
+ }
110
+
111
+ // See if the failure was caused by the row not existing (we check this only after failure, because it should only occur once on the site)
112
+ if (!$this->ensure_database_initialised()) return false;
113
+
114
+ do {
115
+ // Now that the row has been created, try again
116
+ if (1 === $wpdb->query($sql)) {
117
+ $this->log('Lock ('.$this->option_name.', '.$wpdb->options.') acquired after initialising the database', 'info');
118
+ $this->acquired = true;
119
+ return true;
120
+ }
121
+ $retries--;
122
+ if ($retries >=0) {
123
+ $this->log('Lock ('.$this->option_name.', '.$wpdb->options.') not yet acquired; sleeping', 'debug');
124
+ sleep(1);
125
+ // As a second has passed, update the time we are aiming for
126
+ $time_now = time();
127
+ $acquire_until = $time_now + $this->locked_for;
128
+ $sql = $wpdb->prepare("UPDATE {$wpdb->options} SET option_value = %s WHERE option_name = %s AND option_value < %d", $acquire_until, $this->option_name, $time_now);
129
+ }
130
+ } while ($retries >= 0);
131
+
132
+ $this->log('Lock ('.$this->option_name.', '.$wpdb->options.') could not be acquired (it is locked)', 'info');
133
+
134
+ return false;
135
+ }
136
+
137
+ /**
138
+ * Release the lock
139
+ *
140
+ * N.B. We don't attempt to unlock it unless we locked it. i.e. Lost locks are left to expire rather than being forced. (If we want to force them, we'll need to introduce a new parameter).
141
+ *
142
+ * @return Boolean - if it returns false, then the lock was apparently not locked by us (and the caller will most likely therefore ignore the result, whatever it is).
143
+ */
144
+ public function release() {
145
+ if (!$this->acquired) return false;
146
+ global $wpdb;
147
+ $sql = $wpdb->prepare("UPDATE {$wpdb->options} SET option_value = '0' WHERE option_name = %s", $this->option_name);
148
+
149
+ $this->log('Lock option ('.$this->option_name.', '.$wpdb->options.') released', 'info');
150
+
151
+ $result = (int) $wpdb->query($sql) === 1;
152
+
153
+ $this->acquired = false;
154
+
155
+ return $result;
156
+ }
157
+
158
+ /**
159
+ * Cleans up the DB of any residual data. This should not be used as part of ordinary unlocking; only as part of deinstalling, or if you otherwise know that the lock will not be used again. If calling this, it's redundant to first unlock (and a no-op to attempt to do so afterwards).
160
+ */
161
+ public function delete() {
162
+ $this->acquired = false;
163
+
164
+ global $wpdb;
165
+ $wpdb->query($wpdb->prepare("DELETE FROM {$wpdb->options} WHERE option_name = %s", $this->option_name));
166
+
167
+ $this->log('Lock option ('.$this->option_name.', '.$wpdb->options.') was deleted from the database');
168
+ }
169
+
170
+ /**
171
+ * Captures and logs any given messages
172
+ *
173
+ * @param String $message - the error message
174
+ * @param String $level - the message level (debug, notice, info, warning, error)
175
+ */
176
+ public function log($message, $level = 'info') {
177
+ if (isset($this->loggers)) {
178
+ foreach ($this->loggers as $logger) {
179
+ $logger->log($message, $level);
180
+ }
181
+ }
182
+ }
183
+
184
+ /**
185
+ * Sets the list of loggers for this instance (removing any others).
186
+ *
187
+ * @param Array $loggers - the loggers for this task
188
+ */
189
+ public function set_loggers($loggers) {
190
+ $this->loggers = array();
191
+ foreach ($loggers as $logger) {
192
+ $this->add_logger($logger);
193
+ }
194
+ }
195
+
196
+ /**
197
+ * Add a logger to loggers list
198
+ *
199
+ * @param Callable $logger - a logger (a method with a callable function 'log', taking string parameters $level $message)
200
+ */
201
+ public function add_logger($logger) {
202
+ $this->loggers[] = $logger;
203
+ }
204
+
205
+ /**
206
+ * Return the current list of loggers
207
+ *
208
+ * @return Array
209
+ */
210
+ public function get_loggers() {
211
+ return $this->loggers;
212
+ }
213
+ }
vendor/team-updraft/common-libs/src/updraft-semaphore/test.php ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ Example usage:
5
+
6
+ php -a
7
+ require 'wp-load.php';
8
+ define('I_AM_TESTING', true);
9
+ require 'test.php';
10
+ */
11
+
12
+ if (!defined('ABSPATH')) die('No direct access.');
13
+
14
+ if (!defined('I_AM_TESTING') || !I_AM_TESTING) die('Please define I_AM_TESTING.');
15
+
16
+ require_once(dirname(__FILE__).'/class-updraft-semaphore.php');
17
+
18
+ class Test_Logger_1 {
19
+ function log($message, $level) {
20
+ echo "Test_Logger_1::log(level=$level, message=$message)\n";
21
+ }
22
+ }
23
+
24
+ class Test_Logger_2 {
25
+ function log($message, $level) {
26
+ echo "Test_Logger_2::log(level=$level, message=$message)\n";
27
+ }
28
+ }
29
+
30
+ $my_lock = new Updraft_Semaphore_3_0('my_test_lock_name', 4, array(new Test_Logger_1()));
31
+
32
+ if ($my_lock->lock()) {
33
+ try {
34
+ // do stuff ...
35
+ $my_lock_again = new Updraft_Semaphore_3_0('my_test_lock_name', 4, array(new Test_Logger_2()));
36
+ $time_now = microtime(true);
37
+ if ($my_lock_again->lock(6)) {
38
+ echo "Eventually got it after ".round(microtime(true) - $time_now, 3)." seconds\n";
39
+ $my_lock_again->release();
40
+ } else {
41
+ echo("Sorry, could not get the second lock\n");
42
+ }
43
+
44
+ } catch (Exception $e) {
45
+ var_dump($e);
46
+ // We are making sure we release the lock in case of an error
47
+ } catch (Error $e) { // phpcs:ignore PHPCompatibility.Classes.NewClasses.errorFound
48
+ var_dump($e);
49
+ // We are making sure we release the lock in case of an error
50
+ }
51
+
52
+ $my_lock->release();
53
+
54
+ } else {
55
+ echo("Sorry, could not get the first lock\n");
56
+ }
vendor/team-updraft/common-libs/src/updraft-tasks/class-updraft-task-manager-commands.php ADDED
@@ -0,0 +1,188 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * The AJAX Commands manager class
4
+ */
5
+
6
+ if (!defined('ABSPATH')) die('Access denied.');
7
+
8
+ if (!defined('Updraft_Task_Manager_Commands_1_0')) :
9
+
10
+ class Updraft_Task_Manager_Commands_1_0 {
11
+
12
+ protected $task_manager;
13
+
14
+ /**
15
+ * Constructor
16
+ *
17
+ * @param Updraft_Task_Manager_1_3 $task_manager The task manager instance
18
+ */
19
+ public function __construct($task_manager) {
20
+ $this->task_manager = $task_manager;
21
+ }
22
+
23
+ /**
24
+ * A list of allowed commands via AJAX
25
+ *
26
+ * @return array - List of allowed commands
27
+ */
28
+ public static function get_allowed_ajax_commands() {
29
+
30
+ $commands = array(
31
+ 'process_task',
32
+ 'get_task_status',
33
+ 'end_task',
34
+ 'process_queue',
35
+ 'get_active_tasks',
36
+ 'clean_up_old_tasks',
37
+ );
38
+
39
+ return apply_filters('updraft_task_manager_allowed_ajax_commands', $commands);
40
+ }
41
+
42
+ /**
43
+ * Process a single task in the queue
44
+ *
45
+ * @param array $data data passed via AJAX
46
+ * @return void|WP_Error status of the operation
47
+ */
48
+ public function process_task($data) {
49
+
50
+ if (!isset($data['task_id']))
51
+ return new WP_Error('id_missing', 'Task ID is missing or invalid');
52
+
53
+ $task_id = (int) $data['task_id'];
54
+
55
+ $response = apply_filters('updraft_task_manager_process_task_response', "Processing task: {$task_id}", $task_id);
56
+ $this->close_browser_connection($response);
57
+ $this->task_manager->process_task($task_id);
58
+ }
59
+
60
+ /**
61
+ * Process a single task in the queue
62
+ *
63
+ * @param array $data data passed via AJAX
64
+ * @return String - status of task or false if none found
65
+ */
66
+ public function get_task_status($data) {
67
+
68
+ if (!isset($data['task_id']))
69
+ return new WP_Error('id_missing', 'Task ID is missing or invalid');
70
+
71
+ $task_id = (int) $data['task_id'];
72
+
73
+ return $this->task_manager->get_task_status($task_id);
74
+ }
75
+
76
+ /**
77
+ * Ends a given task
78
+ *
79
+ * @param array $data data passed via AJAX
80
+ * @return boolean - Status of the operation.
81
+ */
82
+ public function end_task($data) {
83
+
84
+ if (!isset($data['task_id']))
85
+ return new WP_Error('id_missing', 'Task ID is missing or invalid');
86
+
87
+ $task_id = (int) $data['task_id'];
88
+
89
+ $status = $this->task_manager->end_task($task_id);
90
+
91
+ if (!$status) return new WP_Error('end_task_failed', 'Task is already ended');
92
+
93
+ $response = apply_filters('updraft_task_manager_end_task_response', "Successfully ended task with id : {$task_id}", $task_id);
94
+ $this->close_browser_connection($response);
95
+ }
96
+
97
+ /**
98
+ * Fetches a list of all active tasks
99
+ *
100
+ * @param array $data data passed via AJAX
101
+ * @return Mixed - array of UpdraftPlus_Task ojects or NULL if none found
102
+ */
103
+ public function get_active_tasks($data) {
104
+
105
+ if (!isset($data['type']))
106
+ return new WP_Error('type_missing', 'Task type is missing or invalid');
107
+
108
+ $type = $data['type'];
109
+ $tasks = $this->task_manager->get_active_tasks($type);
110
+
111
+ $ids = array();
112
+
113
+ if ($tasks) {
114
+ foreach ($tasks as $task) {
115
+ array_push($ids, $task->get_id());
116
+ }
117
+ }
118
+
119
+ $response = apply_filters('updraft_task_manager_get_active_tasks_response', $ids, $type);
120
+ return $response;
121
+ }
122
+
123
+ /**
124
+ * Cleans out all complete tasks from the DB.
125
+ *
126
+ * @param array $data data passed via AJAX
127
+ * @return void|WP_Error status of the operation
128
+ */
129
+ public function clean_up_old_tasks($data) {
130
+
131
+ if (!isset($data['type']))
132
+ return new WP_Error('type_missing', 'Task type is missing or invalid');
133
+
134
+ $type = $data['type'];
135
+ $status = $this->task_manager->clean_up_old_tasks($type);
136
+
137
+ if (!$status) return new WP_Error('clean_up_failed', 'Queue is already empty or the task type invalid');
138
+
139
+ $response = apply_filters('updraft_task_manager_clean_up_old_tasks_response', "Cleaned up old tasks of type : $type", $type);
140
+ $this->close_browser_connection($response);
141
+ }
142
+
143
+ /**
144
+ * Processes a queue of a specific type of task
145
+ *
146
+ * @param array $data data passed via AJAX
147
+ * @return void|WP_Error status of the operation
148
+ */
149
+ public function process_queue($data) {
150
+ if (!isset($data['type']))
151
+ return new WP_Error('type_missing', 'Task type is missing or invalid');
152
+
153
+ $type = $data['type'];
154
+
155
+ $response = apply_filters('updraft_task_manager_process_queue_response', "Processing queue of type {$type}", $type);
156
+
157
+ $this->close_browser_connection(json_encode($response));
158
+ $status = $this->task_manager->process_queue($type);
159
+
160
+ if (!$status)
161
+ return new WP_Error('process_queue_operation_failed', 'Failed to process the queue');
162
+ }
163
+
164
+ /**
165
+ * Close browser connection so that it can resume AJAX polling
166
+ *
167
+ * @param array $txt Response to browser
168
+ * @return void
169
+ */
170
+ public function close_browser_connection($txt = '') {
171
+ header('Content-Length: '.((!empty($txt)) ? 5+strlen($txt) : '0'));
172
+ header('Connection: close');
173
+ header('Content-Encoding: none');
174
+ if (session_id()) session_write_close();
175
+ echo "\r\n\r\n";
176
+ echo $txt;
177
+
178
+ $levels = ob_get_level();
179
+
180
+ for ($i = 0; $i < $levels; $i++) {
181
+ ob_end_flush();
182
+ }
183
+
184
+ flush();
185
+ }
186
+ }
187
+
188
+ endif;
vendor/team-updraft/common-libs/src/updraft-tasks/class-updraft-task-manager.php ADDED
@@ -0,0 +1,387 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * A task manager that locks and processes the task queue
4
+ */
5
+
6
+ if (!defined('ABSPATH')) die('Access denied.');
7
+
8
+ if (!class_exists('Updraft_Task_Manager_1_3')) :
9
+
10
+ abstract class Updraft_Task_Manager_1_3 {
11
+
12
+ protected $loggers;
13
+
14
+ public $commands;
15
+
16
+ private $queue_semaphore;
17
+
18
+ /**
19
+ * Set this to the number of seconds for the lock timeout, or 0 to not use a lock
20
+ */
21
+ protected $use_per_task_lock = 0;
22
+
23
+ /**
24
+ * The Task Manager constructor
25
+ */
26
+ public function __construct() {
27
+
28
+ if (!class_exists('Updraft_Task_1_2')) require_once('class-updraft-task.php');
29
+ if (!class_exists('Updraft_Task_Manager_Commands_1_0')) require_once('class-updraft-task-manager-commands.php');
30
+ if (!class_exists('Updraft_Semaphore_3_0')) require_once(dirname(__FILE__).'/../updraft-semaphore/class-updraft-semaphore.php');
31
+ if (!class_exists('Updraft_Tasks_Activation')) require_once(dirname(__FILE__).'/class-updraft-tasks-activation.php');
32
+
33
+ $this->commands = new Updraft_Task_Manager_Commands_1_0($this);
34
+
35
+ add_action('wp_ajax_updraft_taskmanager_ajax', array($this, 'updraft_taskmanager_ajax'));
36
+
37
+ do_action('updraft_task_manager_loaded', $this);
38
+ }
39
+
40
+ /**
41
+ * The Task Manager AJAX handler
42
+ */
43
+ public function updraft_taskmanager_ajax() {
44
+
45
+ $nonce = empty($_REQUEST['nonce']) ? '' : $_REQUEST['nonce'];
46
+
47
+ if (!wp_verify_nonce($nonce, 'updraft-task-manager-ajax-nonce') || empty($_REQUEST['subaction']))
48
+ die('Security check failed');
49
+
50
+ $subaction = $_REQUEST['subaction'];
51
+
52
+ $allowed_commands = Updraft_Task_Manager_Commands_1_0::get_allowed_ajax_commands();
53
+
54
+ if (in_array($subaction, $allowed_commands)) {
55
+
56
+ if (isset($_REQUEST['action_data']))
57
+ $data = $_REQUEST['action_data'];
58
+
59
+ $results = call_user_func(array($this->commands, $subaction), $data);
60
+
61
+ if (is_wp_error($results)) {
62
+ $results = array(
63
+ 'result' => false,
64
+ 'error_code' => $results->get_error_code(),
65
+ 'error_message' => $results->get_error_message(),
66
+ 'error_data' => $results->get_error_data(),
67
+ );
68
+ }
69
+
70
+ echo json_encode($results);
71
+ } else {
72
+ echo json_encode("{'error' : 'No such command found'}");
73
+ }
74
+ die;
75
+ }
76
+
77
+ /**
78
+ * Process a single task in the queue
79
+ *
80
+ * @param int|Updraft_Task - $task Task ID or Updraft_Task object.
81
+ * @return boolean|WP_Error - status of task or error if task not found
82
+ */
83
+ public function process_task($task) {
84
+
85
+ if (!is_a($task, 'Updraft_Task_1_2')) {
86
+ $task_id = (int) $task;
87
+ $task = $this->get_task_instance($task_id);
88
+ }
89
+
90
+ if (!$task) return new WP_Error('id_invalid', 'Task not found or ID is invalid');
91
+
92
+ return $task->attempt(apply_filters('updraft_task_lock_for', $this->use_per_task_lock, $this));
93
+
94
+ }
95
+
96
+ /**
97
+ * Gets a list of all tasks that matches the $status flag
98
+ *
99
+ * @param int|Updraft_Task - $task Task ID or Updraft_Task object.
100
+ * @return String|WP_Error - status of task or error if task not found.
101
+ */
102
+ public function get_task_status($task) {
103
+
104
+ if (!($task instanceof Updraft_Task_1_2)) {
105
+ $task_id = (int) $task;
106
+ $task = $this->get_task_instance($task_id);
107
+ }
108
+
109
+ if (!$task) return new WP_Error('id_invalid', 'Task not found or ID is invalid');
110
+
111
+ return $task->get_status();
112
+ }
113
+
114
+ /**
115
+ * Ends a given task
116
+ *
117
+ * @param int|Updraft_Task - $task Task ID or Updraft_Task object.
118
+ * @return boolean|WP_Error - Status of the operation or error if task not found.
119
+ */
120
+ public function end_task($task) {
121
+
122
+ if (!($task instanceof Updraft_Task_1_2)) {
123
+ $task_id = (int) $task;
124
+ $task = $this->get_task_instance($task_id);
125
+ }
126
+
127
+ if (!$task) return new WP_Error('id_invalid', 'Task not found or ID is invalid');
128
+
129
+ return $task->complete();
130
+ }
131
+
132
+ /**
133
+ * Process a the queue of a specifed task type
134
+ *
135
+ * @param string $type queue type to process
136
+ * @return bool true on success, false otherwise
137
+ */
138
+ public function process_queue($type) {
139
+
140
+ $task_list = $this->get_active_tasks($type);
141
+ $total = is_array($task_list) ? count($task_list) : 0;
142
+
143
+ if (1 > $total) {
144
+ $this->log(sprintf('The queue for tasks of type "%s" is empty. Aborting!', $type));
145
+ return true;
146
+ } else {
147
+ $this->log(sprintf('A total of %d tasks of type %s found and will be processed in this iteration', $total, $type));
148
+ }
149
+
150
+ $this->queue_semaphore = new Updraft_Semaphore_3_0($type);
151
+
152
+ // Prevent PHP warning which trigger from semaphore class set_loggers method if $this->loggers is empty
153
+ if (!empty($this->loggers)) {
154
+ $this->queue_semaphore->set_loggers($this->loggers);
155
+ }
156
+
157
+ if (!$this->queue_semaphore->lock()) {
158
+
159
+ $this->log(sprintf('Failed to gain semaphore lock (%s) - another process is already processing the queue - aborting (if this is wrong - i.e. if the other process crashed without removing the lock, then another can be started after 1 minute', $type));
160
+
161
+ return false;
162
+ }
163
+
164
+ $done = 0;
165
+ foreach ($task_list as $task) {
166
+ $this->process_task($task);
167
+ $done++;
168
+ /**
169
+ * Filters if the queue should be interrupted. Used after processing each task.
170
+ *
171
+ * @param boolean $interrupt_queue - If the queue should be interrupted. Default to FALSE
172
+ * @param object $task - The current task object
173
+ * @param object $task_manager - The task manager instance
174
+ */
175
+ if (apply_filters('updraft_interrupt_tasks_queue_'.$type, false, $task, $this)) {
176
+ break;
177
+ }
178
+ }
179
+
180
+ $this->queue_semaphore->release();
181
+ $this->log(sprintf('Successfully processed the queue (%s). %d tasks were processed out of %d.', $type, $done, $total));
182
+ $this->queue_semaphore->delete();
183
+
184
+ return $done == $total;
185
+ }
186
+
187
+ /**
188
+ * Cleans out all complete tasks from the DB.
189
+ *
190
+ * @param String $type type of the task
191
+ */
192
+ public function clean_up_old_tasks($type) {
193
+ $completed_tasks = $this->get_completed_tasks($type);
194
+
195
+ if (!$completed_tasks) return false;
196
+
197
+ $this->log(sprintf('Cleaning up tasks of type (%s). A total of %d tasks will be deleted.', $type, count($completed_tasks)));
198
+
199
+ foreach ($completed_tasks as $task) {
200
+ $task->delete_meta();
201
+ $task->delete();
202
+ }
203
+
204
+ return true;
205
+ }
206
+
207
+ /**
208
+ * Delete all tasks from queue.
209
+ *
210
+ * @param string $type
211
+ *
212
+ * @return boolean|integer Number of rows deleted, or (boolean)false upon error
213
+ */
214
+ public function delete_tasks($task_type) {
215
+ global $wpdb;
216
+
217
+ $sql = "DELETE t, tm FROM `{$wpdb->base_prefix}tm_tasks` t LEFT JOIN `{$wpdb->base_prefix}tm_taskmeta` tm ON t.id = tm.task_id WHERE t.type = '{$task_type}'";
218
+
219
+ return $wpdb->query($sql);
220
+ }
221
+
222
+ /**
223
+ * Get count of completed and all tasks.
224
+ *
225
+ * @return array - [ ['complete_tasks' => , 'all_tasks' => ] ]
226
+ */
227
+ public function get_status($task_type) {
228
+ global $wpdb;
229
+
230
+ $query = $wpdb->prepare(
231
+ "SELECT complete_tasks, all_tasks FROM (SELECT COUNT(*) AS complete_tasks FROM {$wpdb->base_prefix}tm_tasks WHERE `type` = %s AND `status` = %s) a, (SELECT COUNT(*) AS all_tasks FROM {$wpdb->base_prefix}tm_tasks WHERE `type` = %s) b",
232
+ array(
233
+ $task_type,
234
+ 'complete',
235
+ $task_type,
236
+ )
237
+ );
238
+
239
+ $status = $wpdb->get_row($query, ARRAY_A);
240
+
241
+ if (empty($status)) {
242
+ $status = array(
243
+ 'complete_tasks' => 0,
244
+ 'all_tasks' => 0,
245
+ );
246
+ }
247
+
248
+ return $status;
249
+ }
250
+
251
+ /**
252
+ * Fetches a list of all active tasks
253
+ *
254
+ * @param String $type type of the task
255
+ * @return Mixed - array of Task ojects or NULL if none found
256
+ */
257
+ public function get_active_tasks($type) {
258
+ return $this->get_tasks('active', $type);
259
+ }
260
+
261
+ /**
262
+ * Gets a list of all completed tasks
263
+ *
264
+ * @param String $type type of the task
265
+ * @return Mixed - array of Task ojects or NULL if none found
266
+ */
267
+ public function get_completed_tasks($type) {
268
+ return $this->get_tasks('complete', $type);
269
+ }
270
+
271
+ /**
272
+ * Gets a list of all tasks that matches the $status flag
273
+ *
274
+ * @param String $status - status of tasks to return, defaults to all tasks
275
+ * @param String $type - type of task
276
+ *
277
+ * @return Mixed - array of Task objects or NULL if none found
278
+ */
279
+ public function get_tasks($status, $type) {
280
+ global $wpdb;
281
+
282
+ $tasks = array();
283
+
284
+ if (array_key_exists($status, Updraft_Task_1_2::get_allowed_statuses())) {
285
+ $sql = $wpdb->prepare("SELECT * FROM {$wpdb->base_prefix}tm_tasks WHERE status = %s AND type = %s", $status, $type);
286
+ } else {
287
+ $sql = $wpdb->prepare("SELECT * FROM {$wpdb->base_prefix}tm_tasks WHERE type = %s", $type);
288
+ }
289
+
290
+ $_tasks = $wpdb->get_results($sql);
291
+
292
+ if (!$_tasks) {
293
+ // if we got an error then check if task manager tables are in the database
294
+ // and recreate them if needed.
295
+ if ($wpdb->last_error) {
296
+ Updraft_Tasks_Activation::reinstall_if_needed();
297
+ }
298
+ return;
299
+ }
300
+
301
+
302
+ foreach ($_tasks as $_task) {
303
+ $task = $this->get_task_instance($_task->id);
304
+ if ($task) array_push($tasks, $task);
305
+ }
306
+
307
+ return $tasks;
308
+ }
309
+
310
+ /**
311
+ * Retrieve the task instance using its ID
312
+ *
313
+ * @access public
314
+ *
315
+ * @global wpdb $wpdb WordPress database abstraction object.
316
+ *
317
+ * @param int $task_id Task ID.
318
+ * @return Task|Boolean Task object, false otherwise.
319
+ */
320
+ public function get_task_instance($task_id) {
321
+ global $wpdb;
322
+
323
+ $task_id = (int) $task_id;
324
+ if (!$task_id) return false;
325
+
326
+ $sql = $wpdb->prepare("SELECT * FROM {$wpdb->base_prefix}tm_tasks WHERE id = %d LIMIT 1", $task_id);
327
+ $_task = $wpdb->get_row($sql);
328
+
329
+ if (!$_task)
330
+ return false;
331
+
332
+ $class_identifier = $_task->class_identifier;
333
+
334
+ if (class_exists($class_identifier))
335
+ $task_instance = new $class_identifier($_task);
336
+ $task_instance->set_loggers($this->loggers);
337
+ return $task_instance;
338
+
339
+ return false;
340
+ }
341
+
342
+ /**
343
+ * Sets the logger for this instance.
344
+ *
345
+ * @param array $loggers - the loggers for this task
346
+ */
347
+ public function set_loggers($loggers) {
348
+ foreach ($loggers as $logger) {
349
+ $this->add_logger($logger);
350
+ }
351
+ }
352
+
353
+ /**
354
+ * Add a logger to loggers list
355
+ *
356
+ * @param Object $logger - a logger for the instance
357
+ */
358
+ public function add_logger($logger) {
359
+ $this->loggers[] = $logger;
360
+ }
361
+
362
+ /**
363
+ * Return list of loggers
364
+ *
365
+ * @return array
366
+ */
367
+ public function get_loggers() {
368
+ return $this->loggers;
369
+ }
370
+
371
+ /**
372
+ * Captures and logs any interesting messages
373
+ *
374
+ * @param String $message - the error message
375
+ * @param String $error_type - the error type
376
+ */
377
+ public function log($message, $error_type = 'info') {
378
+ if (isset($this->loggers)) {
379
+ foreach ($this->loggers as $logger) {
380
+ $logger->log($message, $error_type);
381
+ }
382
+ }
383
+ }
384
+
385
+ }
386
+
387
+ endif;
vendor/team-updraft/common-libs/src/updraft-tasks/class-updraft-task-meta.php ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * The DB handle class for the options framework
4
+ */
5
+
6
+ if (!defined('ABSPATH')) die('Access denied.');
7
+
8
+ if (!class_exists('Updraft_Task_Meta')) :
9
+
10
+ class Updraft_Task_Meta {
11
+
12
+ /**
13
+ * This method gets data from the task meta table in the WordPress database
14
+ *
15
+ * @param int $id the instance id of the task
16
+ * @param String $key the key to get
17
+ *
18
+ * @return Mixed The option from the database
19
+ */
20
+ public static function get_task_meta($id, $key) {
21
+ global $wpdb;
22
+
23
+ $id = (int) $id;
24
+ if (!$id) return false;
25
+
26
+ $sql = $wpdb->prepare("SELECT meta_value FROM {$wpdb->base_prefix}tm_taskmeta WHERE task_id = %d AND meta_key = %s LIMIT 1", $id, $key);
27
+
28
+ $meta = $wpdb->get_var($sql);
29
+
30
+ if ($meta)
31
+ return maybe_unserialize($meta);
32
+ else return false;
33
+ }
34
+
35
+
36
+ /**
37
+ * This method is used to update data stored in the WordPress database
38
+ *
39
+ * @param int $id the instance id of the task
40
+ * @param String $key the key of the data to update
41
+ * @param Mixed $value the value to save to the option
42
+ *
43
+ * @return Mixed the status of the update operation
44
+ */
45
+ public static function update_task_meta($id, $key, $value) {
46
+ global $wpdb;
47
+
48
+ $id = (int) $id;
49
+ if (!$id) return false;
50
+
51
+ $value = maybe_serialize($value);
52
+
53
+ if (false !== self::get_task_meta($id, $key)) {
54
+ $sql = $wpdb->prepare("UPDATE {$wpdb->base_prefix}tm_taskmeta SET meta_value = %s WHERE meta_key = %s AND task_id = %d", $value, $key, $id);
55
+ } else {
56
+ $sql = $wpdb->prepare("INSERT INTO {$wpdb->base_prefix}tm_taskmeta (task_id, meta_key, meta_value) VALUES (%d, %s, %s)", $id, $key, $value);
57
+ }
58
+
59
+ return $wpdb->query($sql);
60
+ }
61
+
62
+ /**
63
+ * This method is used to delete task data stored in the WordPress database
64
+ *
65
+ * @param int $id the instance id of the task
66
+ * @param String $key the key to delete
67
+ *
68
+ * @return Mixed the status of the delete operation
69
+ */
70
+ public static function delete_task_meta($id, $key) {
71
+ global $wpdb;
72
+
73
+ $id = (int) $id;
74
+ if (!$id) return false;
75
+
76
+ $sql = $wpdb->prepare("DELETE FROM {$wpdb->base_prefix}tm_taskmeta WHERE task_id = %d AND meta_key = %s LIMIT 1", $id, $key);
77
+ return $wpdb->query($sql);
78
+ }
79
+
80
+ /**
81
+ * Bulk delete task
82
+ *
83
+ * @param int $id the instance id of the task
84
+ */
85
+ public static function bulk_delete_task_meta($id) {
86
+ global $wpdb;
87
+
88
+ $id = (int) $id;
89
+ if (!$id) return false;
90
+
91
+ $sql = $wpdb->prepare("DELETE FROM {$wpdb->base_prefix}tm_taskmeta WHERE task_id = %d", $id);
92
+ return $wpdb->query($sql);
93
+ }
94
+ }
95
+
96
+ endif;
vendor/team-updraft/common-libs/src/updraft-tasks/class-updraft-task-options.php ADDED
@@ -0,0 +1,127 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * The options framework for tasks
4
+ */
5
+
6
+ if (!defined('ABSPATH')) die('Access denied.');
7
+
8
+ if (!class_exists('Updraft_Task_Options')) :
9
+
10
+ class Updraft_Task_Options {
11
+
12
+ /**
13
+ * This method gets an option from the task meta table in the WordPress database
14
+ *
15
+ * @param int $instance_id the instance id of the task
16
+ * @param String $option the name of the option to get
17
+ * @param Mixed $default a value to return if the option is not currently set
18
+ *
19
+ * @return Mixed The option from the database
20
+ */
21
+ public static function get_task_option($instance_id, $option, $default = null) {
22
+
23
+ $tmp = Updraft_Task_Meta::get_task_meta($instance_id, 'task_options');
24
+
25
+ if (isset($tmp[$option])) {
26
+ $value = $tmp[$option];
27
+ } else {
28
+ $value = $default;
29
+ }
30
+
31
+ /**
32
+ * Filters the value of an existing option.
33
+ *
34
+ * The dynamic portion of the hook name, `$option`, refers to the option name.
35
+ */
36
+ return apply_filters("ud_task_option_{$option}", maybe_unserialize($value), $option, $default, $instance_id);
37
+ }
38
+
39
+ /**
40
+ * This method is used to update a task option stored in the WordPress database
41
+ *
42
+ * @param int $instance_id the instance id of the task
43
+ * @param String $option the name of the option to update
44
+ * @param Mixed $value the value to save to the option
45
+ *
46
+ * @return Mixed the status of the update operation
47
+ */
48
+ public static function update_task_option($instance_id, $option, $value) {
49
+
50
+ $option = trim($option);
51
+
52
+ if (empty($option)) return false;
53
+
54
+ $old_value = self::get_task_option($instance_id, $option);
55
+
56
+ /**
57
+ * Filters a specific option before its value is (maybe) serialized and updated.
58
+ */
59
+ $value = apply_filters("ud_pre_update_task_option_{$option}", $value, $old_value, $option, $instance_id);
60
+
61
+ $tmp = Updraft_Task_Meta::get_task_meta($instance_id, 'task_options');
62
+
63
+ if (!is_array($tmp)) $tmp = array();
64
+ $tmp[$option] = maybe_serialize($value);
65
+
66
+ $result = Updraft_Task_Meta::update_task_meta($instance_id, 'task_options', $tmp);
67
+
68
+ if ($result) {
69
+
70
+ /**
71
+ * Fires after the value of a specific option has been successfully updated.
72
+ */
73
+ do_action("ud_update_task_option_{$option}", $value, $old_value, $option, $instance_id);
74
+ }
75
+
76
+ return $result;
77
+ }
78
+
79
+ /**
80
+ * This method is used to delete a task option stored in the WordPress database
81
+ *
82
+ * @param int $instance_id the instance id of the task
83
+ * @param String $option the option to delete
84
+ */
85
+ public static function delete_task_option($instance_id, $option) {
86
+
87
+ /**
88
+ * Fires immediately before an option is deleted.
89
+ */
90
+ do_action("ud_before_delete_task_option", $option, $instance_id);
91
+
92
+ $tmp = Updraft_Task_Meta::get_task_meta($instance_id, 'task_options');
93
+
94
+ if (is_array($tmp)) {
95
+ if (isset($tmp[$option])) unset($tmp[$option]);
96
+ } else {
97
+ $tmp = array();
98
+ }
99
+
100
+ $result = Updraft_Task_Meta::update_task_meta($instance_id, 'task_options', $tmp);
101
+
102
+ if ($result) {
103
+
104
+ /**
105
+ * Fires after a specific option has been successfully deleted.
106
+ */
107
+ do_action("ud_delete_task_option_{$option}", $option);
108
+ }
109
+
110
+ return $result;
111
+ }
112
+
113
+ /**
114
+ * This method gets all options assoicated with a task
115
+ *
116
+ * @param int $instance_id the instance id of the task
117
+ *
118
+ * @return Mixed The options from the database
119
+ */
120
+ public static function get_all_task_options($instance_id) {
121
+
122
+ $value = Updraft_Task_Meta::get_task_meta($instance_id, 'task_options');
123
+ return apply_filters("ud_all_task_options", maybe_unserialize($value), $instance_id);
124
+ }
125
+ }
126
+
127
+ endif;
vendor/team-updraft/common-libs/src/updraft-tasks/class-updraft-task.php ADDED
@@ -0,0 +1,693 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * The base class which must be extended to use the tasks library
4
+ */
5
+
6
+ if (!defined('ABSPATH')) die('Access denied.');
7
+
8
+ if (!class_exists('Updraft_Task_1_2')) :
9
+
10
+ if (!class_exists('Updraft_Task_Options')) require_once('class-updraft-task-options.php');
11
+ if (!class_exists('Updraft_Task_Meta')) require_once('class-updraft-task-meta.php');
12
+
13
+ abstract class Updraft_Task_1_2 {
14
+
15
+ /**
16
+ * A unique ID for the specific task
17
+ *
18
+ * @var int
19
+ */
20
+ private $id;
21
+
22
+ /**
23
+ * The user id of the creator of this task
24
+ *
25
+ * @var string
26
+ */
27
+ private $user_id;
28
+
29
+ /**
30
+ * A text description for the task
31
+ *
32
+ * @var string
33
+ */
34
+ private $description;
35
+
36
+ /**
37
+ * A type for the task
38
+ *
39
+ * @var string
40
+ */
41
+ private $type;
42
+
43
+ /**
44
+ * A timestamp indicating the time the task was created
45
+ *
46
+ * @var string
47
+ */
48
+ private $time_created;
49
+
50
+ /**
51
+ * The number of times this task was attempted
52
+ *
53
+ * @var int
54
+ */
55
+ private $attempts;
56
+
57
+ /**
58
+ * A text description describing the status of the task
59
+ *
60
+ * @var string
61
+ */
62
+ private $status;
63
+
64
+ /**
65
+ * An identifier indicating which child class created this instance
66
+ *
67
+ * @var string
68
+ */
69
+ private $class_identifier;
70
+
71
+ /**
72
+ * A logger object that can be used to capture interesting events / messages
73
+ *
74
+ * @var Object
75
+ */
76
+ protected $_loggers;
77
+
78
+ /**
79
+ * The Task constructor
80
+ *
81
+ * @param UpdraftPlus_Task|object $task UpdraftPlus_Task object.
82
+ */
83
+ public function __construct($task) {
84
+ foreach (get_object_vars($task) as $key => $value)
85
+ $this->$key = $value;
86
+ }
87
+
88
+ /**
89
+ * Sets the instance ID.
90
+ *
91
+ * @param String $instance_id - the instance ID
92
+ */
93
+ public function set_id($instance_id) {
94
+ $this->id = $instance_id;
95
+ }
96
+
97
+ /**
98
+ * Gets the instance ID.
99
+ *
100
+ * @return String the instance ID
101
+ */
102
+ public function get_id() {
103
+ return $this->id;
104
+ }
105
+
106
+ /**
107
+ * Sets the description.
108
+ *
109
+ * @param String $description - the description of the task
110
+ */
111
+ public function set_description($description) {
112
+ $this->description = $description;
113
+ }
114
+
115
+ /**
116
+ * Gets the task description
117
+ *
118
+ * @return String $description - the description of the task
119
+ */
120
+ public function get_description() {
121
+ return $this->description;
122
+ }
123
+
124
+ /**
125
+ * Sets the type.
126
+ *
127
+ * @param String $type - the type of the task
128
+ */
129
+ public function set_type($type) {
130
+ $this->type = $type;
131
+ }
132
+
133
+ /**
134
+ * Gets the number of times this task was attempted
135
+ *
136
+ * @return int $attempts - the count
137
+ */
138
+ public function get_attempts() {
139
+ return $this->attempts;
140
+ }
141
+
142
+ /**
143
+ * Sets the number of times this task was attempted
144
+ *
145
+ * @param String $attempts - the count
146
+ */
147
+ public function set_attempts($attempts) {
148
+ if (is_numeric($attempts))
149
+ $this->attempts = $attempts;
150
+ else return false;
151
+
152
+ return $this->update_attempts($this->id, $this->attempts);
153
+ }
154
+
155
+ /**
156
+ * Gets the task type
157
+ *
158
+ * @return String $type - the type of the task
159
+ */
160
+ public function get_type() {
161
+ return $this->type;
162
+ }
163
+
164
+ /**
165
+ * Sets the task status.
166
+ *
167
+ * @param String $status - the status of the task
168
+ *
169
+ * @return Boolean - the result of the status update
170
+ */
171
+ public function set_status($status) {
172
+
173
+ if (array_key_exists($status, self::get_allowed_statuses()))
174
+ $this->status = $status;
175
+ else return false;
176
+
177
+ return $this->update_status($this->id, $this->status);
178
+ }
179
+
180
+ /**
181
+ * Gets the task status
182
+ *
183
+ * @return String $status - the status of the task
184
+ */
185
+ public function get_status() {
186
+ return $this->status;
187
+ }
188
+
189
+ /**
190
+ * Sets the logger for this task.
191
+ *
192
+ * @param array $loggers - the loggers for this task
193
+ */
194
+ public function set_loggers($loggers) {
195
+ if (is_array($loggers)) {
196
+ foreach ($loggers as $logger) {
197
+ $this->add_logger($logger);
198
+ }
199
+ }
200
+ }
201
+
202
+ /**
203
+ * Add a logger to loggers list
204
+ *
205
+ * @param Object $logger - a logger for the task
206
+ */
207
+ public function add_logger($logger) {
208
+ $this->_loggers[] = $logger;
209
+ }
210
+
211
+ /**
212
+ * Return list of loggers
213
+ *
214
+ * @return array
215
+ */
216
+ public function get_loggers() {
217
+ return $this->_loggers;
218
+ }
219
+
220
+ /**
221
+ * The initialisation function that accepts and processes any parameters needed before the task starts
222
+ *
223
+ * @param Array $options - array of options
224
+ *
225
+ * @uses update_option
226
+ */
227
+ public function initialise($options = array()) {
228
+
229
+ do_action('ud_task_before_initialise', $this, $options);
230
+
231
+ /**
232
+ * Parse incoming $options into an array and merge it with defaults
233
+ */
234
+ $defaults = $this->get_default_options();
235
+ $options = wp_parse_args($options, $defaults);
236
+
237
+ foreach ($options as $option => $value) {
238
+ $this->update_option($option, $value);
239
+ }
240
+
241
+ do_action('ud_task_initialise_complete', $this, $options);
242
+
243
+ }
244
+
245
+ /**
246
+ * Attempts to perform the task
247
+ *
248
+ * @param integer $lock_for - if greater than zero, then lock the task, and don't break until this number of seconds has passed
249
+ *
250
+ * @return boolean Status of the attempt
251
+ */
252
+ public function attempt($lock_for = 0) {
253
+
254
+ $_task = $this->get_task_from_db($this->get_id());
255
+
256
+ if (!$_task) {
257
+ $this->log("The task with id : {$this->get_id()}, and type '{$this->get_type()}' seems to have been deleted from the database.");
258
+ return false;
259
+ }
260
+
261
+ if ('complete' == $this->get_status()) {
262
+ $this->log("Attempting already complete task with ID : {$this->get_id()}, and type '{$this->get_type()}'. Aborting !");
263
+ return true;
264
+ }
265
+
266
+ if ($lock_for) {
267
+ $try = 1;
268
+ $locked = false;
269
+ while ($try < 4) {
270
+ if ($locked = $this->lock($this->get_id(), true, $lock_for)) break;
271
+ $try ++;
272
+ sleep(1);
273
+ }
274
+ if (!$locked) {
275
+ $this->fail('could_not_lock', 'The task could not be locked');
276
+ return false;
277
+ }
278
+ }
279
+
280
+ $attempts = $this->get_attempts();
281
+
282
+ if ($attempts >= $this->get_max_attempts()) {
283
+ $this->fail("max_attempts_exceeded", "Maximum attempts ($attempts) exceeded for task");
284
+ return false;
285
+ }
286
+
287
+ $this->log("Processing task with ID : {$this->get_id()}, and type '{$this->get_type()}'");
288
+ $this->set_attempts(++$attempts);
289
+ $status = $this->run();
290
+
291
+ if ($status) {
292
+ $this->complete();
293
+ $this->log("Completed processing task with ID : {$this->get_id()}, and type '{$this->get_type()}'");
294
+ }
295
+
296
+ if ($lock_for) $this->lock($this->get_id(), false);
297
+
298
+ return $status;
299
+ }
300
+
301
+ /**
302
+ * Lock or unlock a task
303
+ *
304
+ * @param Integer - $task_id - task identifier
305
+ * @param Boolean - $lock - whether to lock or unlock
306
+ * @param Integer - $lock_for - if already locked, how long after which to break the lock
307
+ *
308
+ * @return Boolean - whether the operation was successful
309
+ */
310
+ public function lock($task_id, $lock = true, $lock_for = 60) {
311
+
312
+ global $wpdb;
313
+
314
+ if (!$lock) {
315
+ return $wpdb->update($wpdb->base_prefix.'tm_tasks', array('last_locked_at' => 0), array('id' => $task_id)) ? true : false;
316
+ }
317
+
318
+ // Mode: lock. Attempt to set the lock
319
+ $affected = $wpdb->update($wpdb->base_prefix.'tm_tasks', array('last_locked_at' => time()), array('id' => $task_id, 'last_locked_at' => 0));
320
+
321
+ // Success.
322
+ if (1 == $affected) return true;
323
+
324
+ // Failed - something else already had it locked. Grab the lock if it had expired.
325
+ $affected = $wpdb->update($wpdb->base_prefix.'tm_tasks', array('last_locked_at' => time()), array('id' => $task_id, 'last_locked_at' => 0));
326
+
327
+ $expires_at = time() - $lock_for;
328
+
329
+ $affected = $wpdb->query($wpdb->prepare("
330
+ UPDATE {$wpdb->base_prefix}tm_tasks
331
+ SET last_locked_at = %d
332
+ WHERE id = %d
333
+ AND last_locked_at <= %s
334
+ ", time(), $task_id, $expires_at));
335
+
336
+ return $affected ? true : false;
337
+ }
338
+
339
+ /**
340
+ * This function is called to allow for the task to perform a small chunk of work.
341
+ * It should be written in a way that anticipates it being killed off at any time.
342
+ */
343
+ abstract public function run();
344
+
345
+ /**
346
+ * Any clean up code goes here.
347
+ */
348
+ public function complete() {
349
+
350
+ do_action('ud_task_before_complete', $this);
351
+
352
+ $this->set_status('complete');
353
+
354
+ do_action('ud_task_completed', $this);
355
+
356
+ return true;
357
+ }
358
+
359
+ /**
360
+ * Fires if the task fails, any clean up code and logging should go here
361
+ *
362
+ * @param String $error_code - A code for the failure
363
+ * @param String $error_message - A description for the failure
364
+ */
365
+ public function fail($error_code = "Unknown", $error_message = "Unknown") {
366
+
367
+ do_action('ud_task_before_failed', $this);
368
+
369
+ $this->set_status('failed');
370
+ $this->log(sprintf("Task with ID %d and type (%s) failed with error code %s - %s", $this->id, $this->type, $error_code, $error_message));
371
+
372
+ $this->update_option("error_code", $error_code);
373
+ $this->update_option("error_message", $error_message);
374
+
375
+ do_action('ud_task_failed', $this);
376
+
377
+ return true;
378
+ }
379
+
380
+ /**
381
+ * Prints any information about the task that the UI can use on the front end for debugging
382
+ * @param String $title the header to use in the report
383
+ *
384
+ * @return String The task report HTML
385
+ */
386
+ public function print_task_report_widget($title = 'Task Summary') {
387
+
388
+ $ret = "";
389
+
390
+ $status = $this->get_status();
391
+ $stage = $this->get_option('stage') ? $this->get_option('stage') : 'Unknown';
392
+ $description = $this->get_status_description($status);
393
+
394
+
395
+ $ret .= "<div class='task task-report task-{$this->type}' id='task-id-{$this->id}'>";
396
+ $ret .= "<h4>Task Summary</h4>";
397
+ $ret .= "<ul class='properties-list task-{$this->type}'>";
398
+
399
+ foreach ($this as $key => $value) {
400
+ $ret .= sprintf("<li> %s : %s </li>", $key, $value);
401
+ }
402
+ $ret .='</ul>';
403
+
404
+ $ret .= "<h4> $title </h4>";
405
+ $ret .= "<ul class='data-list task-{$this->type}'>";
406
+
407
+ foreach ($this->get_all_options() as $key => $value) {
408
+ if (is_array(maybe_unserialize($value))) {
409
+ $ret .= sprintf("<li> %s</li>", $key);
410
+ $ret .= "<ul class='sub-list'>";
411
+ foreach (maybe_unserialize($value) as $k => $v) {
412
+ $ret .= sprintf("<li> %s => %s </li>", $k, $v);
413
+ }
414
+ $ret .= "</ul>";
415
+ } else {
416
+ $ret .= sprintf("<li> %s : %s </li>", $key, $value);
417
+ }
418
+ }
419
+ $ret .='</ul>';
420
+ $ret .='</div>';
421
+
422
+ return apply_filters('ud_print_task_report_widget', $ret, $this);
423
+
424
+ }
425
+
426
+ /**
427
+ * This method gets an option from the task options in the WordPress database if available,
428
+ * otherwise returns the default for this task type
429
+ *
430
+ * @param String $option the name of the option to get
431
+ * @param Mixed $default a value to return if the option is not currently set
432
+ *
433
+ * @return Mixed The option from the database
434
+ */
435
+ public function get_option($option = null, $default = null) {
436
+ return Updraft_Task_Options::get_task_option($this->id, $option, $default);
437
+ }
438
+
439
+ /**
440
+ * This method is used to add a task option stored in the WordPress database
441
+ *
442
+ * @param String $option the name of the option to update
443
+ * @param Mixed $value the value to save to the option
444
+ *
445
+ * @return Mixed the status of the add operation
446
+ */
447
+ public function add_option($option, $value) {
448
+ return Updraft_Task_Options::update_task_option($this->id, $option, $value);
449
+ }
450
+
451
+ /**
452
+ * This method is used to update a task option stored in the WordPress database
453
+ *
454
+ * @param String $option the name of the option to update
455
+ * @param Mixed $value the value to save to the option
456
+ *
457
+ * @return Mixed the status of the update operation
458
+ */
459
+ public function update_option($option, $value) {
460
+ return Updraft_Task_Options::update_task_option($this->id, $option, $value);
461
+ }
462
+
463
+ /**
464
+ * This method is used to delete a task option stored in the WordPress database
465
+ *
466
+ * @param String $option the option to delete
467
+ *
468
+ * @return Boolean the result of the delete operation
469
+ */
470
+ public function delete_option($option) {
471
+ return Updraft_Task_Options::delete_task_option($this->id, $option);
472
+ }
473
+
474
+ /**
475
+ * This method gets all options assoicated with a task
476
+ */
477
+ public function get_all_options() {
478
+ return Updraft_Task_Options::get_all_task_options($this->id);
479
+ }
480
+
481
+ /**
482
+ * Retrieve default options for this task.
483
+ * This method should normally be over-ridden by the child.
484
+ *
485
+ * @return Array - an array of options
486
+ */
487
+ public function get_default_options() {
488
+
489
+ $this->log(sprintf('The get_default_options() method was not over-ridden for the class : %s', $this->get_description()));
490
+
491
+ return array();
492
+ }
493
+
494
+ /**
495
+ * Returns a unique label for this instance that can be used as an identifier
496
+ *
497
+ * @return String - a unique label for this instance
498
+ */
499
+ protected function get_unique_label() {
500
+ return apply_filters('ud_task_unique_label', $this->id."-".$this->type, $this);
501
+ }
502
+
503
+ /**
504
+ * Updates the status of the given task in the DB
505
+ *
506
+ * @param String $id - the id of the task
507
+ * @param String $status - the status of the task
508
+ *
509
+ * @return Boolean - the stauts of the update operation
510
+ */
511
+ public function update_status($id, $status) {
512
+
513
+ if (!array_key_exists($status, self::get_allowed_statuses()))
514
+ return false;
515
+
516
+ global $wpdb;
517
+ $sql = $wpdb->prepare("UPDATE {$wpdb->base_prefix}tm_tasks SET status = %s WHERE id = %d", $status, $id);
518
+
519
+ return $wpdb->query($sql);
520
+ }
521
+
522
+ /**
523
+ * Updates the number of attempts made for the given task in the DB
524
+ *
525
+ * @param String $id - the id of the task
526
+ * @param int $attempts - the status of the task
527
+ *
528
+ * @return Boolean - the stauts of the update operation
529
+ */
530
+ public function update_attempts($id, $attempts) {
531
+
532
+ if (!is_numeric($attempts))
533
+ return false;
534
+
535
+ global $wpdb;
536
+ $sql = $wpdb->prepare("UPDATE {$wpdb->base_prefix}tm_tasks SET attempts = %s WHERE id = %d", $attempts, $id);
537
+
538
+ return $wpdb->query($sql);
539
+ }
540
+
541
+ /**
542
+ * Cleans out the given task from the DB
543
+ *
544
+ * @return Boolean - the status of the delete operation
545
+ */
546
+ public function delete() {
547
+ global $wpdb;
548
+
549
+ $sql = $wpdb->prepare("DELETE FROM {$wpdb->base_prefix}tm_tasks WHERE id = %d", $this->id);
550
+ return $wpdb->query($sql);
551
+ }
552
+
553
+ /**
554
+ * Cleans out the given task meta from the DB
555
+ *
556
+ * @return Boolean - the status of the delete operation
557
+ */
558
+ public function delete_meta() {
559
+ return Updraft_Task_Meta::bulk_delete_task_meta($this->id);
560
+ }
561
+
562
+ /**
563
+ * Helper function to convert object to array.
564
+ *
565
+ * @return array Object as array.
566
+ */
567
+ public function to_array() {
568
+ $task = get_object_vars($this);
569
+
570
+ foreach (array( 'task_options', 'task_data', 'task_logs', 'task_extras' ) as $key) {
571
+ if ($this->__isset($key))
572
+ $task[$key] = $this->__get($key);
573
+ }
574
+
575
+ return $task;
576
+ }
577
+
578
+ /**
579
+ * Captures and logs any interesting messages
580
+ *
581
+ * @param String $message - the error message
582
+ * @param String $error_type - the error type
583
+ */
584
+ public function log($message, $error_type = 'info') {
585
+
586
+ if (isset($this->_loggers)) {
587
+ foreach ($this->_loggers as $logger) {
588
+ $logger->log($message, $error_type);
589
+ }
590
+ }
591
+ }
592
+
593
+ /**
594
+ * Retrieve all the supported task statuses.
595
+ *
596
+ * Tasks should have a limited set of valid status values, this method provides a
597
+ * list of values and descriptions.
598
+ *
599
+ * @return array List of task statuses.
600
+ */
601
+ public static function get_allowed_statuses() {
602
+ $status = array(
603
+ 'initialised' => __('Initialised'),
604
+ 'active' => __('Active'),
605
+ 'paused' => __('Paused'),
606
+ 'complete' => __('Completed'),
607
+ 'failed' => __('Failed')
608
+ );
609
+
610
+ return apply_filters('ud_allowed_task_statuses', $status);
611
+ }
612
+
613
+
614
+ /**
615
+ * Retrieve the max attempts permitted for task type
616
+ *
617
+ * @return int Max attempts permitted for task type
618
+ */
619
+ private function get_max_attempts() {
620
+ return apply_filters('ud_max_attempts', 5, $this);
621
+ }
622
+
623
+ /**
624
+ * Retrieve the text description of the task status.
625
+ *
626
+ * @param String $status - The task status
627
+ *
628
+ * @return String Description of the task status.
629
+ */
630
+ public static function get_status_description($status) {
631
+ $list = self::get_allowed_statuses();
632
+
633
+ if (!array_key_exists($status, self::get_allowed_statuses()))
634
+ return __('Unknown');
635
+
636
+ return apply_filters("ud_task_status_description_{$status}", $list[$status], $status, $list);
637
+ }
638
+
639
+
640
+ /**
641
+ * Creates a new task instance and returns it
642
+ *
643
+ * @access public
644
+ *
645
+ * @global wpdb $wpdb WordPress database abstraction object.
646
+ *
647
+ * @param String $type A identifier for the task
648
+ * @param String $description A description of the task
649
+ * @param Mixed $options A list of options to initialise the task
650
+ * @param String $task_class Class name of task; only needed/used on PHP 5.2 (due to lack of late static binding)
651
+ *
652
+ * @return Updraft_Task|false Task object, false otherwise.
653
+ */
654
+ public static function create_task($type, $description, $options = array(), $task_class = '') {
655
+ global $wpdb;
656
+
657
+ $user_id = get_current_user_id();
658
+ $class_identifier = function_exists('get_called_class') ? get_called_class() : $task_class;// phpcs:ignore PHPCompatibility.FunctionUse.NewFunctions.get_called_classFound
659
+
660
+ $is_anonymous_user_allowed = isset($options['anonymous_user_allowed']) && $options['anonymous_user_allowed'];
661
+ if (!$user_id && !$is_anonymous_user_allowed) return false;
662
+
663
+ $sql = $wpdb->prepare("INSERT INTO {$wpdb->base_prefix}tm_tasks (type, user_id, description, class_identifier, status) VALUES (%s, %d, %s, %s, %s)", $type, $user_id, $description, $class_identifier, 'active');
664
+
665
+ $wpdb->query($sql);
666
+
667
+ $task_id = $wpdb->insert_id;
668
+
669
+ if (!$task_id) return false;
670
+
671
+ $_task = $wpdb->get_row("SELECT * FROM {$wpdb->base_prefix}tm_tasks WHERE id = {$task_id} LIMIT 1");
672
+
673
+ $task = new $class_identifier($_task);
674
+
675
+ if (!$task) return false;
676
+
677
+ $task->initialise($options);
678
+
679
+ return $task;
680
+ }
681
+
682
+ /**
683
+ * Select the current task from the database
684
+ *
685
+ * @param int $task_id - The task ID
686
+ * @return object|false
687
+ */
688
+ private function get_task_from_db($task_id) {
689
+ global $wpdb;
690
+ return $wpdb->get_row("SELECT * FROM {$wpdb->base_prefix}tm_tasks WHERE id = {$task_id} LIMIT 1");
691
+ }
692
+ }
693
+ endif;
vendor/team-updraft/common-libs/src/updraft-tasks/class-updraft-tasks-activation.php ADDED
@@ -0,0 +1,207 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Initialise the tasks module and create the needed DB tables
4
+ */
5
+
6
+ if (!defined('ABSPATH')) die('Access denied.');
7
+
8
+ if (!class_exists('Updraft_Tasks_Activation')) :
9
+
10
+ class Updraft_Tasks_Activation {
11
+
12
+ private static $table_prefix;
13
+
14
+ /**
15
+ * Format: key=<version>, value=array of method names to call
16
+ * Example Usage:
17
+ * private static $db_updates = array(
18
+ * '1.0.1' => array(
19
+ * 'update_101_add_new_column',
20
+ * ),
21
+ * );
22
+ *
23
+ * @var Mixed
24
+ */
25
+ private static $db_updates = array(
26
+ '0.0.1' => array('create_tables'),
27
+ '1.0.1' => array('add_attempts_and_class_identifier'),
28
+ '1.1' => array('add_lock_column'),
29
+ );
30
+
31
+
32
+ const UPDRAFT_TASKS_DB_VERSION = '1.1';
33
+
34
+ /**
35
+ * Initialise this class
36
+ */
37
+ public static function init_db() {
38
+ self::$table_prefix = defined('UPDRAFT_TASKS_TABLE_PREFIX') ? UPDRAFT_TASKS_TABLE_PREFIX : 'tm_';
39
+ }
40
+
41
+ /**
42
+ * This is the class entry point
43
+ */
44
+ public static function install() {
45
+ self::init_db();
46
+ self::create_tables();
47
+ // we need walk through all updates when install at first.
48
+ self::check_updates();
49
+ }
50
+
51
+ /**
52
+ * Check needed tables in data base and if one of them doesn't exist force reinstall.
53
+ */
54
+ public static function reinstall_if_needed() {
55
+ static $done = false;
56
+
57
+ if ($done) return;
58
+
59
+ if (!self::check_if_tables_exist()) self::reinstall();
60
+
61
+ $done = true;
62
+ }
63
+
64
+ /**
65
+ * Drop database version variable from option from database and run install again.
66
+ */
67
+ public static function reinstall() {
68
+ if (is_multisite()) {
69
+ delete_site_option('updraft_task_manager_dbversion');
70
+ } else {
71
+ delete_option('updraft_task_manager_dbversion');
72
+ }
73
+ self::install();
74
+ }
75
+
76
+ /**
77
+ * Check if needed task manager tables exist.
78
+ *
79
+ * @return bool
80
+ */
81
+ public static function check_if_tables_exist() {
82
+ global $wpdb;
83
+ self::init_db();
84
+ $our_prefix = $wpdb->base_prefix.self::$table_prefix;
85
+ $tables = array($our_prefix.'tasks', $our_prefix.'taskmeta');
86
+
87
+ foreach ($tables as $table) {
88
+ $query = "SHOW TABLES LIKE '{$table}'";
89
+ $tables = $wpdb->get_results($query, ARRAY_A);
90
+ if (!is_array($tables) || 0 == count($tables)) return false;
91
+ }
92
+
93
+ return true;
94
+ }
95
+
96
+ /**
97
+ * See if any database schema updates are needed, and perform them if so.
98
+ * Example Usage:
99
+ * public static function update_101_add_new_column() {
100
+ * $wpdb = $GLOBALS['wpdb'];
101
+ * $wpdb->query('ALTER TABLE tm_tasks ADD task_expiry varchar(300) AFTER id');
102
+ * }
103
+ */
104
+ public static function check_updates() {
105
+ self::init_db();
106
+ $our_version = self::get_version();
107
+ if (is_multisite()) {
108
+ $db_version = get_site_option('updraft_task_manager_dbversion');
109
+ } else {
110
+ $db_version = get_option('updraft_task_manager_dbversion');
111
+ }
112
+ if (!$db_version || version_compare($our_version, $db_version, '>')) {
113
+ foreach (self::$db_updates as $version => $updates) {
114
+ if (version_compare($version, $db_version, '>')) {
115
+ foreach ($updates as $update) {
116
+ call_user_func(array(__CLASS__, $update));
117
+ }
118
+ }
119
+ }
120
+ if (is_multisite()) {
121
+ update_site_option('updraft_task_manager_dbversion', self::get_version());
122
+ } else {
123
+ update_option('updraft_task_manager_dbversion', self::get_version());
124
+ }
125
+ }
126
+ }
127
+
128
+ /**
129
+ * Returns the current version of the plugin
130
+ */
131
+ public static function get_version() {
132
+ return self::UPDRAFT_TASKS_DB_VERSION;
133
+ }
134
+
135
+ /**
136
+ * Create the database tables
137
+ */
138
+ public static function create_tables() {
139
+
140
+ $wpdb = $GLOBALS['wpdb'];
141
+
142
+ $our_prefix = $wpdb->base_prefix.self::$table_prefix;
143
+ $collate = '';
144
+
145
+ if ($wpdb->has_cap('collation')) {
146
+ if (!empty($wpdb->charset)) {
147
+ $collate .= "DEFAULT CHARACTER SET $wpdb->charset";
148
+ }
149
+ if (!empty($wpdb->collate)) {
150
+ $collate .= " COLLATE $wpdb->collate";
151
+ }
152
+ }
153
+
154
+ include_once ABSPATH.'wp-admin/includes/upgrade.php';
155
+
156
+ // Important: obey the magical/arbitrary rules for formatting this stuff: https://codex.wordpress.org/Creating_Tables_with_Plugins
157
+ // Otherwise, you get SQL errors and unwanted header output warnings when activating
158
+
159
+ $create_tables = 'CREATE TABLE '.$our_prefix."tasks (
160
+ task_id bigint(20) NOT NULL auto_increment,
161
+ user_id bigint(20) NOT NULL,
162
+ type varchar(300) NOT NULL,
163
+ description varchar(300),
164
+ PRIMARY KEY (task_id),
165
+ KEY user_id (user_id),
166
+ time_created TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
167
+ status varchar(300)
168
+ ) $collate;
169
+ ";
170
+ // KEY attribute_name (attribute_name)
171
+ dbDelta($create_tables);
172
+
173
+ $max_index_length = 191;
174
+
175
+ $create_tables = 'CREATE TABLE '.$our_prefix."taskmeta (
176
+ meta_id bigint(20) NOT NULL auto_increment,
177
+ task_id bigint(20) NOT NULL default '0',
178
+ meta_key varchar(255) DEFAULT NULL,
179
+ meta_value longtext,
180
+ PRIMARY KEY (meta_id),
181
+ KEY meta_key (meta_key($max_index_length)),
182
+ KEY task_id (task_id)
183
+ ) $collate;
184
+ ";
185
+
186
+ dbDelta($create_tables);
187
+ }
188
+
189
+ public static function add_attempts_and_class_identifier() {
190
+ $wpdb = $GLOBALS['wpdb'];
191
+ $our_prefix = $wpdb->base_prefix.self::$table_prefix;
192
+
193
+ $wpdb->query("ALTER TABLE ".$our_prefix."tasks CHANGE COLUMN `task_id` `id` INT NOT NULL");
194
+ $wpdb->query("ALTER TABLE ".$our_prefix."tasks MODIFY COLUMN `id` INT auto_increment");
195
+ $wpdb->query("ALTER TABLE ".$our_prefix."tasks ADD attempts INT DEFAULT 0 AFTER type");
196
+ $wpdb->query("ALTER TABLE ".$our_prefix."tasks ADD class_identifier varchar(300) DEFAULT 0 AFTER type");
197
+ }
198
+
199
+ public static function add_lock_column() {
200
+ $wpdb = $GLOBALS['wpdb'];
201
+ $our_prefix = $wpdb->base_prefix.self::$table_prefix;
202
+ $wpdb->query('ALTER TABLE '.$our_prefix.'tasks ADD last_locked_at BIGINT DEFAULT 0 AFTER time_created');
203
+ }
204
+
205
+ }
206
+
207
+ endif;