Advanced Access Manager - Version 5.7.3

Version Description

  • Fixed the bug with JWT authentication
  • Fixed the PHP bug in policy metabox when no errors with JSON is detected
  • Fixed the bug with license expiration for Extended versions not properly displayed
  • Fixed the bug with Admin Menu listed incorrectly when Default Access Settings defined
  • Fixed the PHP bug in Post object when access settings are defined in a Policy
  • Improved role creation feature
  • Improved capability handling with Access & Security Policy
  • Refactor the way extension is installed to eliminate cURL issues
  • Deprecated and removed aam_display_license capability
  • Extended default policy document with dependencies
  • Added support for Features in the Access & Security Policy
  • Added policy Validation functionality
  • Reduced number of methods that use cURL to contact aamplugin.com API
Download this release

Release Info

Developer vasyltech
Plugin Icon 128x128 Advanced Access Manager
Version 5.7.3
Comparing to
See all releases

Code changes from version 5.7.2 to 5.7.3

Application/Backend/Feature/Extension/Manager.php CHANGED
@@ -62,16 +62,11 @@ class AAM_Backend_Feature_Extension_Manager extends AAM_Backend_Feature_Abstract
62
  public function install($storedLicense = null) {
63
  $repo = AAM_Extension_Repository::getInstance();
64
  $license = AAM_Core_Request::post('license', $storedLicense);
 
65
 
66
- //download the extension from the server first
67
- $package = AAM_Core_Server::download($license);
68
 
69
- if (is_wp_error($package)) {
70
- $response = array(
71
- 'status' => 'failure',
72
- 'error' => wp_strip_all_tags($package->get_error_message())
73
- );
74
- }elseif ($error = $repo->checkDirectory()) {
75
  $response = $this->installFailureResponse($error, $package);
76
  $repo->storeLicense($package, $license);
77
  } elseif (empty($package->content)) { //any unpredictable scenario
@@ -181,20 +176,6 @@ class AAM_Backend_Feature_Extension_Manager extends AAM_Backend_Feature_Abstract
181
  return $response;
182
  }
183
 
184
- /**
185
- *
186
- * @return type
187
- */
188
- public function canShowLicense() {
189
- $result = true;
190
-
191
- if (AAM_Core_API::capabilityExists('aam_display_license')) {
192
- $result = !empty(AAM::getUser()->allcaps['aam_display_license']);
193
- }
194
-
195
- return $result;
196
- }
197
-
198
  /**
199
  * Install extension failure response
200
  *
62
  public function install($storedLicense = null) {
63
  $repo = AAM_Extension_Repository::getInstance();
64
  $license = AAM_Core_Request::post('license', $storedLicense);
65
+ $package = (object) AAM_Core_Request::post('package');
66
 
67
+ $error = $repo->checkDirectory();
 
68
 
69
+ if ($error) {
 
 
 
 
 
70
  $response = $this->installFailureResponse($error, $package);
71
  $repo->storeLicense($package, $license);
72
  } elseif (empty($package->content)) { //any unpredictable scenario
176
  return $response;
177
  }
178
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
179
  /**
180
  * Install extension failure response
181
  *
Application/Backend/Feature/Main/Capability.php CHANGED
@@ -266,7 +266,7 @@ class AAM_Backend_Feature_Main_Capability extends AAM_Backend_Feature_Abstract {
266
  }
267
  }
268
 
269
- $caps = array_merge($policyCaps, AAM_Core_API::getAllCapabilities());
270
 
271
  foreach (array_keys($caps) as $cap) {
272
  if (AAM::api()->isAllowed("Capability:{$cap}:AAM:list") !== false) {
266
  }
267
  }
268
 
269
+ $caps = array_merge(AAM_Core_API::getAllCapabilities(), $policyCaps);
270
 
271
  foreach (array_keys($caps) as $cap) {
272
  if (AAM::api()->isAllowed("Capability:{$cap}:AAM:list") !== false) {
Application/Backend/Feature/Main/Policy.php CHANGED
@@ -22,6 +22,46 @@ class AAM_Backend_Feature_Main_Policy extends AAM_Backend_Feature_Abstract {
22
  public function getTable() {
23
  return wp_json_encode($this->retrievePolicies());
24
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
 
26
  /**
27
  * Save post properties
22
  public function getTable() {
23
  return wp_json_encode($this->retrievePolicies());
24
  }
25
+
26
+ /**
27
+ * Install policy
28
+ *
29
+ * @return string
30
+ *
31
+ * @access public
32
+ * @since v5.7.3
33
+ */
34
+ public function install() {
35
+ $package = (object) AAM_Core_Request::post('package');
36
+
37
+ if (!empty($package->content)) {
38
+ $json = base64_decode($package->content);
39
+
40
+ $result = wp_insert_post(array(
41
+ 'post_author' => get_current_user_id(),
42
+ 'post_content' => $json,
43
+ 'post_title' => $package->title,
44
+ 'post_excerpt' => $package->description,
45
+ 'post_status' => 'publish',
46
+ 'post_type' => 'aam_policy'
47
+ ));
48
+
49
+ if (!is_wp_error($result)) {
50
+ $response = array('status' => 'success');
51
+ } else {
52
+ $response = array(
53
+ 'status' => 'failure', 'reason' => $result->get_error_message()
54
+ );
55
+ }
56
+ } else {
57
+ $response = array(
58
+ 'status' => 'failure',
59
+ 'reason' => __('Failed to fetch policy. Please try again.', AAM_KEY)
60
+ );
61
+ }
62
+
63
+ return wp_json_encode($response);
64
+ }
65
 
66
  /**
67
  * Save post properties
Application/Backend/Feature/Subject/Role.php CHANGED
@@ -183,6 +183,8 @@ class AAM_Backend_Feature_Subject_Role {
183
  }
184
 
185
  do_action('aam-post-add-role-action', $role, $parent);
 
 
186
  }
187
  }
188
 
183
  }
184
 
185
  do_action('aam-post-add-role-action', $role, $parent);
186
+ } else {
187
+ $response['reason'] = __("Role with slug [{$role_id}] already exists", AAM_KEY);
188
  }
189
  }
190
 
Application/Backend/Filter.php CHANGED
@@ -33,7 +33,9 @@ class AAM_Backend_Filter {
33
  */
34
  protected function __construct() {
35
  //menu filter
36
- add_filter('parent_file', array($this, 'filterMenu'), 999, 1);
 
 
37
 
38
  //manager WordPress metaboxes
39
  add_action("in_admin_header", array($this, 'metaboxes'), 999);
33
  */
34
  protected function __construct() {
35
  //menu filter
36
+ if (!AAM::isAAM() || !current_user_can('aam_manage_admin_menu')) {
37
+ add_filter('parent_file', array($this, 'filterMenu'), 999, 1);
38
+ }
39
 
40
  //manager WordPress metaboxes
41
  add_action("in_admin_header", array($this, 'metaboxes'), 999);
Application/Backend/Manager.php CHANGED
@@ -64,7 +64,7 @@ class AAM_Backend_Manager {
64
  //screen options & contextual help hooks
65
  add_filter('screen_options_show_screen', array($this, 'screenOptions'));
66
  add_filter('contextual_help', array($this, 'helpOptions'), 10, 3);
67
-
68
  //manager Admin Menu
69
  if (is_multisite() && is_network_admin()) {
70
  //register AAM in the network admin panel
@@ -144,23 +144,16 @@ class AAM_Backend_Manager {
144
  * @param type $data
145
  * @return type
146
  */
147
- public function filterPostData($data) {
148
  if (isset($data['post_type']) && ($data['post_type'] === 'aam_policy')) {
149
- $data['post_content'] = trim(filter_input(INPUT_POST, 'aam-policy'));
 
 
 
 
150
 
151
  if (empty($data['post_content'])) {
152
- $data['post_content'] = <<<EOT
153
- {
154
- "Version": "1.0.0",
155
- "Statement": [
156
- {
157
- "Effect": "deny",
158
- "Resource": [],
159
- "Action": []
160
- }
161
- ]
162
- }
163
- EOT;
164
  }
165
 
166
  AAM_Core_API::clearCache();
@@ -744,7 +737,7 @@ EOT;
744
  public function printJavascript() {
745
  if (AAM::isAAM()) {
746
  wp_enqueue_script('aam-vendor', AAM_MEDIA . '/js/vendor.js');
747
- wp_enqueue_script('aam-main', AAM_MEDIA . '/js/aam-5.7.2.js');
748
 
749
  //add plugin localization
750
  $this->printLocalization('aam-main');
@@ -782,16 +775,18 @@ EOT;
782
  * @access protected
783
  */
784
  protected function printLocalization($localKey) {
785
- $subject = AAM_Backend_Subject::getInstance();
 
786
 
787
  $locals = array(
788
  'nonce' => wp_create_nonce('aam_ajax'),
789
- 'ajaxurl' => admin_url('admin-ajax.php'),
790
  'ui' => AAM_Core_Request::get('aamframe', 'main'),
791
  'url' => array(
792
- 'site' => admin_url('index.php'),
793
- 'editUser' => admin_url('user-edit.php'),
794
- 'addUser' => admin_url('user-new.php')
 
795
  ),
796
  'level' => AAM_Core_API::maxLevel(wp_get_current_user()->allcaps),
797
  'subject' => array(
@@ -801,6 +796,11 @@ EOT;
801
  'level' => $subject->getMaxLevel(),
802
  'blog' => get_current_blog_id()
803
  ),
 
 
 
 
 
804
  'translation' => AAM_Backend_View_Localization::get(),
805
  'caps' => array(
806
  'create_roles' => current_user_can('aam_create_roles'),
64
  //screen options & contextual help hooks
65
  add_filter('screen_options_show_screen', array($this, 'screenOptions'));
66
  add_filter('contextual_help', array($this, 'helpOptions'), 10, 3);
67
+
68
  //manager Admin Menu
69
  if (is_multisite() && is_network_admin()) {
70
  //register AAM in the network admin panel
144
  * @param type $data
145
  * @return type
146
  */
147
+ public function filterPostData($data, $postarr) {
148
  if (isset($data['post_type']) && ($data['post_type'] === 'aam_policy')) {
149
+ $content = trim(filter_input(INPUT_POST, 'aam-policy'));
150
+
151
+ if (!empty($content)) { // Edit form was submitted
152
+ $data['post_content'] = $content;
153
+ }
154
 
155
  if (empty($data['post_content'])) {
156
+ $data['post_content'] = AAM_Backend_View_Helper::getDefaultPolicy();
 
 
 
 
 
 
 
 
 
 
 
157
  }
158
 
159
  AAM_Core_API::clearCache();
737
  public function printJavascript() {
738
  if (AAM::isAAM()) {
739
  wp_enqueue_script('aam-vendor', AAM_MEDIA . '/js/vendor.js');
740
+ wp_enqueue_script('aam-main', AAM_MEDIA . '/js/aam-5.7.3.js');
741
 
742
  //add plugin localization
743
  $this->printLocalization('aam-main');
775
  * @access protected
776
  */
777
  protected function printLocalization($localKey) {
778
+ $subject = AAM_Backend_Subject::getInstance();
779
+ $endpoint = getenv('AAM_ENDPOINT');
780
 
781
  $locals = array(
782
  'nonce' => wp_create_nonce('aam_ajax'),
783
+ 'ajaxurl' => esc_url(admin_url('admin-ajax.php')),
784
  'ui' => AAM_Core_Request::get('aamframe', 'main'),
785
  'url' => array(
786
+ 'site' => esc_url(admin_url('index.php')),
787
+ 'editUser' => esc_url(admin_url('user-edit.php')),
788
+ 'addUser' => esc_url(admin_url('user-new.php')),
789
+ 'addPolicy' => esc_url(admin_url('post-new.php?post_type=aam_policy'))
790
  ),
791
  'level' => AAM_Core_API::maxLevel(wp_get_current_user()->allcaps),
792
  'subject' => array(
796
  'level' => $subject->getMaxLevel(),
797
  'blog' => get_current_blog_id()
798
  ),
799
+ 'system' => array(
800
+ 'domain' => wp_parse_url(site_url(), PHP_URL_HOST),
801
+ 'uid' => AAM_Core_API::getOption('aam-uid', null, 'site'),
802
+ 'apiEndpoint' => ($endpoint ? $endpoint : AAM_Core_Server::SERVER_URL)
803
+ ),
804
  'translation' => AAM_Backend_View_Localization::get(),
805
  'caps' => array(
806
  'create_roles' => current_user_can('aam_create_roles'),
Application/Backend/Subject.php CHANGED
@@ -71,6 +71,7 @@ class AAM_Backend_Subject {
71
 
72
  if (class_exists($classname)) {
73
  $subject = new $classname(stripslashes($id));
 
74
  // Load access policy
75
  $subject->getObject('policy');
76
 
71
 
72
  if (class_exists($classname)) {
73
  $subject = new $classname(stripslashes($id));
74
+
75
  // Load access policy
76
  $subject->getObject('policy');
77
 
Application/Backend/View/Helper.php CHANGED
@@ -50,4 +50,38 @@ class AAM_Backend_View_Helper {
50
  $value = '/\\' . ($index % 2 ? ']' : '[') . '/';
51
  }
52
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
  }
50
  $value = '/\\' . ($index % 2 ? ']' : '[') . '/';
51
  }
52
 
53
+ /**
54
+ * Get default Access Policy
55
+ *
56
+ * @global string $wp_version
57
+ *
58
+ * @return string
59
+ *
60
+ * @access public
61
+ * @static
62
+ * @since v5.7.3
63
+ */
64
+ public static function getDefaultPolicy() {
65
+ global $wp_version;
66
+
67
+ $aamVersion = AAM_Core_API::version();
68
+
69
+ return <<<EOT
70
+ {
71
+ "Version": "1.0.0",
72
+ "Dependency": {
73
+ "wordpress": ">=$wp_version",
74
+ "advanced-access-manager": ">=$aamVersion"
75
+ },
76
+ "Statement": [
77
+ {
78
+ "Effect": "deny",
79
+ "Resource": [],
80
+ "Action": []
81
+ }
82
+ ]
83
+ }
84
+ EOT;
85
+ }
86
+
87
  }
Application/Backend/phtml/extensions.phtml CHANGED
@@ -39,7 +39,7 @@
39
  <tr>
40
  <td width="80%">
41
  <span class='aam-setting-title'><?php echo $product['title'], (!empty($product['tag']) ? '<sup><span class="badge sup">' . $product['tag'] . '</span></sup>' : ''), (!empty($product['version']) ? ' <small class="text-muted">v' . $product['version'] . '</small>' : ''); ?></span>
42
- <?php if (!empty($product['license']) && $this->canShowLicense()) { ?><small class="aam-license-key"><b>License:</b> <?php echo $product['license'] . (!empty($product['expire']) ? " (updates expire on {$product['expire']})" : ''); ?></small><?php } ?>
43
  <p class="aam-extension-description">
44
  <?php echo $product['description']; ?>
45
  </p>
@@ -52,7 +52,7 @@
52
  <a href="#" class="btn btn-sm btn-danger btn-block aam-deactivate-extension" data-product="<?php echo $product['id']; ?>"><i class="icon-attention-circled"></i> <?php echo __('Deactivate', AAM_KEY); ?></a><small><?php echo __('extension is active', AAM_KEY); ?></small>
53
  <?php } ?>
54
  <?php } elseif ($product['status'] == AAM_Extension_Repository::STATUS_UPDATE) { ?>
55
- <a href="#" class="btn btn-sm btn-warning btn-block aam-update-extension<?php echo (empty($product['license']) ? ' disabled' : ''); ?>" data-product="<?php echo $product['id']; ?>"><i class="icon-arrows-cw"></i> <?php echo __('Update', AAM_KEY); ?></a><?php echo (empty($product['license']) ? '<small>' . __('license is missing', AAM_KEY) . '</small>' : ''); ?>
56
  <?php } elseif ($product['status'] == AAM_Extension_Repository::STATUS_INACTIVE) { ?>
57
  <a href="#" class="btn btn-sm btn-success btn-block aam-activate-extension" data-product="<?php echo $product['id']; ?>"><i class="icon-check"></i> <?php echo __('Activate', AAM_KEY); ?></a><small><?php echo __('extension is inactive', AAM_KEY); ?></small>
58
  <?php } else { ?>
39
  <tr>
40
  <td width="80%">
41
  <span class='aam-setting-title'><?php echo $product['title'], (!empty($product['tag']) ? '<sup><span class="badge sup">' . $product['tag'] . '</span></sup>' : ''), (!empty($product['version']) ? ' <small class="text-muted">v' . $product['version'] . '</small>' : ''); ?></span>
42
+ <?php if (!empty($product['license'])) { ?><small class="aam-license-key"><b>License:</b> <?php echo $product['license'] . (!empty($product['expire']) ? " (updates expire on {$product['expire']})" : ''); ?></small><?php } ?>
43
  <p class="aam-extension-description">
44
  <?php echo $product['description']; ?>
45
  </p>
52
  <a href="#" class="btn btn-sm btn-danger btn-block aam-deactivate-extension" data-product="<?php echo $product['id']; ?>"><i class="icon-attention-circled"></i> <?php echo __('Deactivate', AAM_KEY); ?></a><small><?php echo __('extension is active', AAM_KEY); ?></small>
53
  <?php } ?>
54
  <?php } elseif ($product['status'] == AAM_Extension_Repository::STATUS_UPDATE) { ?>
55
+ <a href="#" class="btn btn-sm btn-warning btn-block aam-update-extension<?php echo (empty($product['license']) ? ' disabled' : ''); ?>" data-license="<?php echo (!empty($product['license']) ? $product['license'] : ''); ?>"><i class="icon-arrows-cw"></i> <?php echo __('Update', AAM_KEY); ?></a><?php echo (empty($product['license']) ? '<small>' . __('license is missing', AAM_KEY) . '</small>' : ''); ?>
56
  <?php } elseif ($product['status'] == AAM_Extension_Repository::STATUS_INACTIVE) { ?>
57
  <a href="#" class="btn btn-sm btn-success btn-block aam-activate-extension" data-product="<?php echo $product['id']; ?>"><i class="icon-check"></i> <?php echo __('Activate', AAM_KEY); ?></a><small><?php echo __('extension is inactive', AAM_KEY); ?></small>
58
  <?php } else { ?>
Application/Backend/phtml/metabox/policy-metabox.phtml CHANGED
@@ -370,32 +370,17 @@
370
 
371
  <?php
372
  if (!empty($post->post_content)) {
373
- $decoded = json_decode(htmlspecialchars_decode($post->post_content));
374
-
375
- if (json_last_error() !== JSON_ERROR_NONE) {
376
- $error = AAM_Backend_View_Helper::preparePhrase(
377
- esc_js('[' . json_last_error_msg() . ']: ' . __('Access &amp; Security Policy is invalid and is ignored by AAM.', AAM_KEY)),
378
- 'b'
379
- );
380
- }
381
  } else {
382
- $post->post_content = <<<EOT
383
- {
384
- "Version": "1.0.0",
385
- "Statement": [
386
- {
387
- "Effect": "deny",
388
- "Resource": [],
389
- "Action": []
390
- }
391
- ]
392
- }
393
- EOT;
394
  }
395
  ?>
396
 
397
- <div class="aam-alert-danger<?php echo (empty($error) ? ' hidden' : ''); ?>" id="policy-parsing-error">
398
- <?php echo (!empty($error) ? $error : ''); ?>
399
  </div>
400
 
401
  <textarea id="aam-policy-editor" name="aam-policy" class="policy-editor" rows="10"><?php echo $post->post_content; ?></textarea>
370
 
371
  <?php
372
  if (!empty($post->post_content)) {
373
+ // Validate the policy
374
+ $validator = new AAM_Core_Policy_Validator(htmlspecialchars_decode($post->post_content));
375
+ $errors = $validator->validate();
 
 
 
 
 
376
  } else {
377
+ $post->post_content = AAM_Backend_View_Helper::getDefaultPolicy();
378
+ $errors = array();
 
 
 
 
 
 
 
 
 
 
379
  }
380
  ?>
381
 
382
+ <div class="aam-alert-danger<?php echo (empty($errors) ? ' hidden' : ''); ?>" id="policy-parsing-error">
383
+ <?php echo implode('<br/>', $errors); ?>
384
  </div>
385
 
386
  <textarea id="aam-policy-editor" name="aam-policy" class="policy-editor" rows="10"><?php echo $post->post_content; ?></textarea>
Application/Core/Compatibility.php CHANGED
@@ -35,6 +35,27 @@ class AAM_Core_Compatibility {
35
  return $key;
36
  }
37
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
  /**
39
  * Converting metabox options from 2 dimensional to 1
40
  *
35
  return $key;
36
  }
37
 
38
+ /**
39
+ *
40
+ * @param type $list
41
+ * @return type
42
+ * @since v5.7.3
43
+ *
44
+ * @todo Remove Jan 2020
45
+ */
46
+ public static function preparePolicyList($list) {
47
+ if (!is_null($list)) {
48
+ if (empty($list['Statements'])) {
49
+ $list = array(
50
+ 'Statements' => $list,
51
+ 'Features' => array()
52
+ );
53
+ }
54
+ }
55
+
56
+ return $list;
57
+ }
58
+
59
  /**
60
  * Converting metabox options from 2 dimensional to 1
61
  *
Application/Core/Config.php CHANGED
@@ -147,6 +147,7 @@ class AAM_Core_Config {
147
  */
148
  protected static function readConfigPress($param, $default = null) {
149
  $config = AAM_Core_ConfigPress::get('aam.' . $param, $default);
 
150
  if (is_array($config) && isset($config['userFunc'])) {
151
  if (is_callable($config['userFunc'])) {
152
  $response = call_user_func($config['userFunc']);
147
  */
148
  protected static function readConfigPress($param, $default = null) {
149
  $config = AAM_Core_ConfigPress::get('aam.' . $param, $default);
150
+
151
  if (is_array($config) && isset($config['userFunc'])) {
152
  if (is_callable($config['userFunc'])) {
153
  $response = call_user_func($config['userFunc']);
Application/Core/Gateway.php CHANGED
@@ -109,6 +109,23 @@ final class AAM_Core_Gateway {
109
  return $policy->isAllowed($resource, $action);
110
  }
111
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
  /**
113
  * Get policy manager
114
  *
109
  return $policy->isAllowed($resource, $action);
110
  }
111
 
112
+ /**
113
+ * Check if feature is enabled
114
+ *
115
+ * @param string $feature
116
+ * @param string $plugin
117
+ *
118
+ * @return boolean|null
119
+ *
120
+ * @access public
121
+ * @since v5.7.3
122
+ */
123
+ public function isEnabled($feature, $plugin = 'advanced-access-manager') {
124
+ $policy = AAM::api()->getUser()->getObject('policy');
125
+
126
+ return $policy->isEnabled($feature, $plugin);
127
+ }
128
+
129
  /**
130
  * Get policy manager
131
  *
Application/Core/Object/Policy.php CHANGED
@@ -27,6 +27,18 @@ class AAM_Core_Object_Policy extends AAM_Core_Object {
27
  */
28
  protected static $resources = array();
29
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  /**
31
  * Constructor
32
  *
@@ -39,7 +51,7 @@ class AAM_Core_Object_Policy extends AAM_Core_Object {
39
  public function __construct(AAM_Core_Subject $subject) {
40
  parent::__construct($subject);
41
 
42
- $this->initialize(); // Read options from the database table first
43
  }
44
 
45
  /**
@@ -77,27 +89,43 @@ class AAM_Core_Object_Policy extends AAM_Core_Object {
77
  */
78
  public function load($subjectId, $policies) {
79
  $resources = array();
 
 
 
80
 
81
- foreach($this->loadStatements($subjectId, $policies) as $statement) {
 
82
  if (isset($statement['Resource']) && $this->applicable($statement)) {
83
  $this->evaluateStatement($statement, $resources);
84
  }
85
  }
86
-
87
  self::$resources[$subjectId] = $resources;
 
 
 
 
 
 
 
 
 
88
  }
89
 
90
  /**
91
  *
92
  * @return type
93
  */
94
- protected function loadStatements($subjectId, $policies) {
95
- $cache = AAM::api()->getUser()->getObject('cache');
96
- $statements = $cache->get('policy', $subjectId, null);
 
 
97
 
98
- // Step #1. Extract all statements
99
- if (is_null($statements)) {
100
- $statements = array();
 
 
101
 
102
  foreach($policies as $id => $effect) {
103
  if ($effect) {
@@ -106,19 +134,74 @@ class AAM_Core_Object_Policy extends AAM_Core_Object {
106
  if (is_a($policy, 'WP_Post')) {
107
  $obj = json_decode($policy->post_content, true);
108
  if (json_last_error() === JSON_ERROR_NONE) {
109
- $statements = array_merge(
110
- $statements, $this->extractStatements($obj)
 
 
 
111
  );
112
  }
113
  }
114
  }
115
  }
116
- $cache->add('policy', $subjectId, $statements);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
117
  }
118
 
119
  return $statements;
120
  }
121
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
122
  /**
123
  *
124
  * @param type $statement
@@ -146,6 +229,23 @@ class AAM_Core_Object_Policy extends AAM_Core_Object {
146
  }
147
  }
148
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
149
  /**
150
  *
151
  * @param type $resources
@@ -535,34 +635,6 @@ class AAM_Core_Object_Policy extends AAM_Core_Object {
535
  return date($prop);
536
  }
537
 
538
- /**
539
- *
540
- * @param type $policy
541
- * @return type
542
- */
543
- protected function extractStatements($policy) {
544
- $statements = array();
545
-
546
- if (isset($policy['Statement'])) {
547
- if (is_array($policy['Statement'])) {
548
- $statements = $policy['Statement'];
549
- } else {
550
- $statements = array($policy['Statement']);
551
- }
552
- }
553
-
554
- // normalize each statement
555
- foreach(array('Action', 'Condition') as $prop) {
556
- foreach($statements as $i => $statement) {
557
- if (isset($statement[$prop])) {
558
- $statements[$i][$prop] = (array) $statement[$prop];
559
- }
560
- }
561
- }
562
-
563
- return $statements;
564
- }
565
-
566
  /**
567
  *
568
  * @param type $left
@@ -627,6 +699,25 @@ class AAM_Core_Object_Policy extends AAM_Core_Object {
627
  return $allowed;
628
  }
629
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
630
  /**
631
  *
632
  * @param type $id
@@ -679,4 +770,33 @@ class AAM_Core_Object_Policy extends AAM_Core_Object {
679
 
680
  return $response;
681
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
682
  }
27
  */
28
  protected static $resources = array();
29
 
30
+ /**
31
+ * Feature tree
32
+ *
33
+ * Shared features tree across all the policy instances
34
+ *
35
+ * @var array
36
+ *
37
+ * @access protected
38
+ * @static
39
+ */
40
+ protected static $features = array();
41
+
42
  /**
43
  * Constructor
44
  *
51
  public function __construct(AAM_Core_Subject $subject) {
52
  parent::__construct($subject);
53
 
54
+ $this->initialize();
55
  }
56
 
57
  /**
89
  */
90
  public function load($subjectId, $policies) {
91
  $resources = array();
92
+ $features = array();
93
+
94
+ $list = $this->parsePolicy($subjectId, $policies);
95
 
96
+ // Evaluate all Statements first
97
+ foreach($list['Statements'] as $statement) {
98
  if (isset($statement['Resource']) && $this->applicable($statement)) {
99
  $this->evaluateStatement($statement, $resources);
100
  }
101
  }
 
102
  self::$resources[$subjectId] = $resources;
103
+
104
+ // Evaluate all Features then
105
+ foreach($list['Features'] as $feature) {
106
+ if ($this->applicable($feature)) {
107
+ $this->evaluateFeature($feature, $features);
108
+ }
109
+ }
110
+
111
+ self::$features[$subjectId] = $features;
112
  }
113
 
114
  /**
115
  *
116
  * @return type
117
  */
118
+ protected function parsePolicy($subjectId, $policies) {
119
+ $cache = AAM::api()->getUser()->getObject('cache');
120
+ $list = AAM_Core_Compatibility::preparePolicyList(
121
+ $cache->get('policy', $subjectId, null)
122
+ );
123
 
124
+ if (is_null($list)) {
125
+ $list = array(
126
+ 'Statements' => array(),
127
+ 'Features' => array()
128
+ );
129
 
130
  foreach($policies as $id => $effect) {
131
  if ($effect) {
134
  if (is_a($policy, 'WP_Post')) {
135
  $obj = json_decode($policy->post_content, true);
136
  if (json_last_error() === JSON_ERROR_NONE) {
137
+ $list['Statements'] = array_merge(
138
+ $list['Statements'], $this->extractStatements($obj)
139
+ );
140
+ $list['Features'] = array_merge(
141
+ $list['Features'], $this->extractFeatures($obj)
142
  );
143
  }
144
  }
145
  }
146
  }
147
+ $cache->add('policy', $subjectId, $list);
148
+ }
149
+
150
+ return $list;
151
+ }
152
+
153
+ /**
154
+ *
155
+ * @param type $policy
156
+ * @return type
157
+ */
158
+ protected function extractStatements($policy) {
159
+ $statements = array();
160
+
161
+ if (isset($policy['Statement'])) {
162
+ if (is_array($policy['Statement'])) {
163
+ $statements = $policy['Statement'];
164
+ } else {
165
+ $statements = array($policy['Statement']);
166
+ }
167
+ }
168
+
169
+ // normalize each statement
170
+ foreach(array('Action', 'Condition') as $prop) {
171
+ foreach($statements as $i => $statement) {
172
+ if (isset($statement[$prop])) {
173
+ $statements[$i][$prop] = (array) $statement[$prop];
174
+ }
175
+ }
176
  }
177
 
178
  return $statements;
179
  }
180
 
181
+ /**
182
+ * Extract list of policy features
183
+ *
184
+ * @param array $policy
185
+ *
186
+ * @return array
187
+ *
188
+ * @access protected
189
+ * @since v5.7.3
190
+ */
191
+ protected function extractFeatures($policy) {
192
+ $features = array();
193
+
194
+ if (isset($policy['Feature'])) {
195
+ if (is_array($policy['Feature'])) {
196
+ $features = $policy['Feature'];
197
+ } else {
198
+ $features = array($policy['Feature']);
199
+ }
200
+ }
201
+
202
+ return $features;
203
+ }
204
+
205
  /**
206
  *
207
  * @param type $statement
229
  }
230
  }
231
 
232
+ /**
233
+ *
234
+ * @param type $feature
235
+ * @param type $features
236
+ */
237
+ protected function evaluateFeature($feature, &$features) {
238
+ $id = strtolower("{$feature['Plugin']}:{$feature['Feature']}");
239
+
240
+ // Add new statement
241
+ if (!isset($features[$id])) {
242
+ $features[$id] = $feature;
243
+ // Override feature unless the first one is marked as Enforced
244
+ } elseif (empty($features[$id]['Enforce'])) {
245
+ $features[$id] = $feature;
246
+ }
247
+ }
248
+
249
  /**
250
  *
251
  * @param type $resources
635
  return date($prop);
636
  }
637
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
638
  /**
639
  *
640
  * @param type $left
699
  return $allowed;
700
  }
701
 
702
+ /**
703
+ *
704
+ * @param type $feature
705
+ * @param type $plugin
706
+ * @return type
707
+ */
708
+ public function isEnabled($feature, $plugin) {
709
+ $enabled = null;
710
+
711
+ $id = strtolower("{$plugin}:{$feature}");
712
+ $res = $this->getFeatures();
713
+
714
+ if (isset($res[$id])) {
715
+ $enabled = in_array($res[$id]['Effect'], array('allow', 'enable'), true);
716
+ }
717
+
718
+ return $enabled;
719
+ }
720
+
721
  /**
722
  *
723
  * @param type $id
770
 
771
  return $response;
772
  }
773
+
774
+ /**
775
+ *
776
+ * @return type
777
+ */
778
+ public function getFeatures(AAM_Core_Subject $subject = null) {
779
+ $response = array();
780
+
781
+ if (is_null($subject)) {
782
+ if (!isset(self::$features['__combined'])) {
783
+ foreach(self::$features as $features) {
784
+ $response = array_merge($features, $response);
785
+ }
786
+ self::$features['__combined'] = $response;
787
+ } else {
788
+ $response = self::$features['__combined'];
789
+ }
790
+ } else {
791
+ $subjectId = $subject->getUID();
792
+ $subjectId .= ($subject->getId() ? ".{$subject->getId()}" : '');
793
+
794
+ if (isset(self::$features[$subjectId])) {
795
+ $response = self::$features[$subjectId];
796
+ }
797
+ }
798
+
799
+ return $response;
800
+ }
801
+
802
  }
Application/Core/Object/Post.php CHANGED
@@ -99,6 +99,7 @@ class AAM_Core_Object_Post extends AAM_Core_Object {
99
  "/^post:{$post->post_type}:({$post->post_name}|{$post->ID}):/",
100
  $subject
101
  );
 
102
 
103
  foreach($stms as $key => $stm) {
104
  // TODO: Prepare better conversion from policy Action to AAM
99
  "/^post:{$post->post_type}:({$post->post_name}|{$post->ID}):/",
100
  $subject
101
  );
102
+ $option = array();
103
 
104
  foreach($stms as $key => $stm) {
105
  // TODO: Prepare better conversion from policy Action to AAM
Application/Core/Policy/Validator.php ADDED
@@ -0,0 +1,181 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * ======================================================================
5
+ * LICENSE: This file is subject to the terms and conditions defined in *
6
+ * file 'license.txt', which is part of this source code package. *
7
+ * ======================================================================
8
+ */
9
+
10
+ use Composer\Semver\Semver;
11
+
12
+ /**
13
+ * AAM core policy validator
14
+ *
15
+ * @package AAM
16
+ * @author Vasyl Martyniuk <vasyl@vasyltech.com>
17
+ * @since AAM v5.7.3
18
+ */
19
+ class AAM_Core_Policy_Validator {
20
+
21
+ /**
22
+ * Raw policy text
23
+ *
24
+ * @var string
25
+ *
26
+ * @access protected
27
+ */
28
+ protected $policy;
29
+
30
+ /**
31
+ * Parsed JSON document
32
+ *
33
+ * @var array
34
+ *
35
+ * @access protected
36
+ */
37
+ protected $json;
38
+
39
+ /**
40
+ * Collection of errors
41
+ *
42
+ * @var array
43
+ *
44
+ * @access protected
45
+ */
46
+ protected $errors = array();
47
+
48
+ /**
49
+ * Constructor
50
+ *
51
+ * @param string $policy
52
+ *
53
+ * @access public
54
+ */
55
+ public function __construct($policy) {
56
+ $this->policy = trim($policy);
57
+ $this->json = json_decode($policy, true);
58
+ }
59
+
60
+ /**
61
+ * Validate the policy
62
+ *
63
+ * @return array
64
+ *
65
+ * @access public
66
+ */
67
+ public function validate() {
68
+ $steps = array(
69
+ 'isJSON', // #1. Check if policy is valid JSON
70
+ 'isNotEmpty', // #2. Check if policy is not empty
71
+ 'isValidDepenency', // #3. Check if all dependencies are defined properly
72
+ );
73
+
74
+ foreach($steps as $step) {
75
+ if (call_user_func(array($this, $step)) === false) {
76
+ break;
77
+ }
78
+ }
79
+
80
+ return $this->errors;
81
+ }
82
+
83
+ /**
84
+ * Check if policy is valid JSON
85
+ *
86
+ * @return boolean
87
+ *
88
+ * @access public
89
+ */
90
+ public function isJSON() {
91
+ $result = is_array($this->json);
92
+
93
+ if ($result === false) {
94
+ $this->errors[] = __('The policy is not valid JSON object', AAM_KEY);
95
+ }
96
+
97
+ return $result;
98
+ }
99
+
100
+ /**
101
+ * Check if policy is empty
102
+ *
103
+ * @return boolean
104
+ *
105
+ * @access public
106
+ */
107
+ public function isNotEmpty() {
108
+ $result = !empty($this->policy) && !empty($this->json);
109
+
110
+ if ($result === false) {
111
+ $this->errors[] = __('The policy document is empty', AAM_KEY);
112
+ }
113
+
114
+ return $result;
115
+ }
116
+
117
+ public function isValidDepenency() {
118
+ if (!empty($this->json['Dependency'])) {
119
+ foreach($this->json['Dependency'] as $app => $constraints) {
120
+ try {
121
+ $satifies = Semver::satisfies(
122
+ $this->getAppVersion(strtolower($app)), $constraints
123
+ );
124
+ if ($satifies === false) {
125
+ throw new Exception(
126
+ AAM_Backend_View_Helper::preparePhrase(
127
+ "The dependency [{$app}] does not satisfy version requirement by the policy",
128
+ 'b'
129
+ )
130
+ );
131
+ }
132
+ } catch (Exception $e) {
133
+ $this->errors[] = $e->getMessage();
134
+ }
135
+ }
136
+ }
137
+ }
138
+
139
+ protected function getAppVersion($app) {
140
+ global $wp_version;
141
+
142
+ if ($app === 'wordpress') {
143
+ $version = $wp_version;
144
+ } else {
145
+ $version = $this->getPluginVersion($app);
146
+ }
147
+
148
+ return $version;
149
+ }
150
+
151
+ protected function getPluginVersion($slug) {
152
+ static $plugins = null;
153
+
154
+ if (is_null($plugins)) {
155
+ if (file_exists(ABSPATH . 'wp-admin/includes/plugin.php')) {
156
+ require_once ABSPATH . 'wp-admin/includes/plugin.php';
157
+ }
158
+
159
+ $plugins = get_plugins();
160
+ }
161
+
162
+ $version = null;
163
+
164
+ foreach($plugins as $plugin => $data) {
165
+ if (stripos($plugin, $slug . '/') === 0) {
166
+ $version = $data['Version'];
167
+ }
168
+ }
169
+
170
+ if (is_null($version)) {
171
+ throw new Exception(
172
+ AAM_Backend_View_Helper::preparePhrase(
173
+ "The plugin [{$slug}] is required by the policy",
174
+ 'b'
175
+ )
176
+ );
177
+ }
178
+
179
+ return $version;
180
+ }
181
+ }
Application/Core/Server.php CHANGED
@@ -22,26 +22,6 @@ final class AAM_Core_Server {
22
  */
23
  const SERVER_URL = 'https://aamplugin.com/api/v1';
24
 
25
- /**
26
- * Fetch the extension list
27
- *
28
- * Fetch the extension list with versions from the server
29
- *
30
- * @return array
31
- *
32
- * @access public
33
- */
34
- public static function register() {
35
- //prepare check params
36
- $params = array(
37
- 'domain' => wp_parse_url(site_url(), PHP_URL_HOST),
38
- 'version' => AAM_Core_API::version(),
39
- 'uid' => AAM_Core_API::getOption('aam-uid', null, 'site')
40
- );
41
-
42
- self::send('/register', $params);
43
- }
44
-
45
  /**
46
  * Fetch the extension list
47
  *
@@ -75,40 +55,6 @@ final class AAM_Core_Server {
75
  return $result;
76
  }
77
 
78
- /**
79
- * Download the extension
80
- *
81
- * @param string $license
82
- *
83
- * @return base64|WP_Error
84
- *
85
- * @access public
86
- */
87
- public static function download($license) {
88
- $domain = wp_parse_url(site_url(), PHP_URL_HOST);
89
-
90
- $response = self::send(
91
- '/download',
92
- array(
93
- 'license' => $license,
94
- 'domain' => $domain,
95
- 'uid' => AAM_Core_API::getOption('aam-uid', null, 'site')
96
- )
97
- );
98
-
99
- if (!is_wp_error($response)) {
100
- if ($response->error === true) {
101
- $result = new WP_Error($response->code, $response->message);
102
- } else {
103
- $result = $response;
104
- }
105
- } else {
106
- $result = $response;
107
- }
108
-
109
- return $result;
110
- }
111
-
112
  /**
113
  * Send request
114
  *
22
  */
23
  const SERVER_URL = 'https://aamplugin.com/api/v1';
24
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  /**
26
  * Fetch the extension list
27
  *
55
  return $result;
56
  }
57
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
58
  /**
59
  * Send request
60
  *
Application/Core/Subject/User.php CHANGED
@@ -29,12 +29,33 @@ class AAM_Core_Subject_User extends AAM_Core_Subject {
29
  */
30
  const AAM_CAPKEY = 'aam_capability';
31
 
 
 
 
 
 
 
32
  /**
33
  *
34
  * @var type
35
  */
36
  protected $parent = null;
37
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
  /**
39
  *
40
  */
@@ -199,17 +220,8 @@ class AAM_Core_Subject_User extends AAM_Core_Subject {
199
  }
200
 
201
  //reset the user capabilities
202
- $subject->allcaps = array_merge($subject->allcaps, $policyCaps);
203
- $subject->caps = array_merge($subject->caps, $policyCaps);
204
-
205
- // Retrieve user capabilities set with AAM
206
- $userCaps = get_user_option(self::AAM_CAPKEY, $this->getId());
207
-
208
- if (is_array($userCaps)) {
209
- //reset the user capabilities
210
- $subject->allcaps = array_merge($subject->allcaps, $userCaps);
211
- $subject->caps = array_merge($subject->caps, $userCaps);
212
- }
213
  }
214
 
215
  /**
@@ -233,7 +245,24 @@ class AAM_Core_Subject_User extends AAM_Core_Subject {
233
  * @access public
234
  */
235
  public function hasCapability($capability) {
236
- return user_can($this->getSubject(), $capability);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
237
  }
238
 
239
  /**
29
  */
30
  const AAM_CAPKEY = 'aam_capability';
31
 
32
+ /**
33
+ *
34
+ * @var type
35
+ */
36
+ protected $aamCaps = array();
37
+
38
  /**
39
  *
40
  * @var type
41
  */
42
  protected $parent = null;
43
 
44
+ /**
45
+ *
46
+ * @param type $id
47
+ */
48
+ public function __construct($id = '') {
49
+ parent::__construct($id);
50
+
51
+ // Retrieve user capabilities set with AAM
52
+ $aamCaps = get_user_option(self::AAM_CAPKEY, $id);
53
+
54
+ if (is_array($aamCaps)) {
55
+ $this->aamCaps = $aamCaps;
56
+ }
57
+ }
58
+
59
  /**
60
  *
61
  */
220
  }
221
 
222
  //reset the user capabilities
223
+ $subject->allcaps = array_merge($subject->allcaps, $policyCaps, $this->aamCaps);
224
+ $subject->caps = array_merge($subject->caps, $policyCaps, $this->aamCaps);
 
 
 
 
 
 
 
 
 
225
  }
226
 
227
  /**
245
  * @access public
246
  */
247
  public function hasCapability($capability) {
248
+ // Priority #1: capability that has been explicitely set
249
+ if (isset($this->aamCaps[$capability])) {
250
+ $result = !empty($this->aamCaps[$capability]);
251
+ } else {
252
+ // Priority #2: capability that has been defined in policy
253
+ // Override by policy if is set
254
+ $stm = AAM::api()->getPolicyManager()->find(
255
+ "/^Capability:{$capability}$/i", $this
256
+ );
257
+ if (!empty($stm)) {
258
+ $val = end($stm);
259
+ $result = ($val['Effect'] === 'allow' ? 1 : 0);
260
+ } else {
261
+ $result = user_can($this->getSubject(), $capability);
262
+ }
263
+ }
264
+
265
+ return $result;
266
  }
267
 
268
  /**
Application/Extension/List.php CHANGED
@@ -22,7 +22,7 @@ class AAM_Extension_List {
22
  'description' => 'Get the complete list of all premium AAM extensions in one package and all future premium extensions already included for now additional cost.',
23
  'url' => 'https://aamplugin.com/complete-package',
24
  'version' => (defined('AAM_COMPLETE_PACKAGE') ? constant('AAM_COMPLETE_PACKAGE') : null),
25
- 'latest' => '3.8.11'
26
  ),
27
  'AAM_PLUS_PACKAGE' => array(
28
  'title' => 'Plus Package',
@@ -31,7 +31,7 @@ class AAM_Extension_List {
31
  'description' => 'Manage access to your WordPress website posts, pages, media, custom post types, categories and hierarchical taxonomies for any role, individual user, visitors or even define default access for everybody; and do this separately for frontend, backend or API levels. As the bonus, define more granular access to how comments can be managed on the backend by other users.',
32
  'url' => 'https://aamplugin.com/extension/plus-package',
33
  'version' => (defined('AAM_PLUS_PACKAGE') ? constant('AAM_PLUS_PACKAGE') : null),
34
- 'latest' => '3.8.5'
35
  ),
36
  'AAM_IP_CHECK' => array(
37
  'title' => 'IP Check',
22
  'description' => 'Get the complete list of all premium AAM extensions in one package and all future premium extensions already included for now additional cost.',
23
  'url' => 'https://aamplugin.com/complete-package',
24
  'version' => (defined('AAM_COMPLETE_PACKAGE') ? constant('AAM_COMPLETE_PACKAGE') : null),
25
+ 'latest' => '3.8.12'
26
  ),
27
  'AAM_PLUS_PACKAGE' => array(
28
  'title' => 'Plus Package',
31
  'description' => 'Manage access to your WordPress website posts, pages, media, custom post types, categories and hierarchical taxonomies for any role, individual user, visitors or even define default access for everybody; and do this separately for frontend, backend or API levels. As the bonus, define more granular access to how comments can be managed on the backend by other users.',
32
  'url' => 'https://aamplugin.com/extension/plus-package',
33
  'version' => (defined('AAM_PLUS_PACKAGE') ? constant('AAM_PLUS_PACKAGE') : null),
34
+ 'latest' => '3.9'
35
  ),
36
  'AAM_IP_CHECK' => array(
37
  'title' => 'IP Check',
Application/Extension/Repository.php CHANGED
@@ -212,7 +212,7 @@ class AAM_Extension_Repository {
212
  $response[] = array(
213
  'license' => $data['license'],
214
  'extension' => $extensions[$key]['title'],
215
- 'expires' => (isset($data['expires']) ? $data['expires'] : null)
216
  );
217
  } else {
218
  $response[] = $data['license'];
@@ -294,7 +294,7 @@ class AAM_Extension_Repository {
294
  if (empty($item['license'])) {
295
  if (!empty($index[$id]['license'])) {
296
  $item['license'] = $index[$id]['license'];
297
- $item['expire'] = (isset($index[$id]['expire']) ? date('Y-m-d', strtotime($index[$id]['expire'])) : null);
298
  } else {
299
  $item['license'] = '';
300
  }
212
  $response[] = array(
213
  'license' => $data['license'],
214
  'extension' => $extensions[$key]['title'],
215
+ 'expires' => (!empty($data['expires']) ? $data['expires'] : null)
216
  );
217
  } else {
218
  $response[] = $data['license'];
294
  if (empty($item['license'])) {
295
  if (!empty($index[$id]['license'])) {
296
  $item['license'] = $index[$id]['license'];
297
+ $item['expire'] = (!empty($index[$id]['expire']) ? date('Y-m-d', strtotime($index[$id]['expire'])) : null);
298
  } else {
299
  $item['license'] = '';
300
  }
aam.php CHANGED
@@ -3,7 +3,7 @@
3
  /**
4
  Plugin Name: Advanced Access Manager
5
  Description: All you need to manage access to your WordPress website
6
- Version: 5.7.2
7
  Author: Vasyl Martyniuk <vasyl@vasyltech.com>
8
  Author URI: https://vasyltech.com
9
 
@@ -115,6 +115,16 @@ class AAM {
115
  //load AAM core config
116
  AAM_Core_Config::bootstrap();
117
 
 
 
 
 
 
 
 
 
 
 
118
  // Load AAM
119
  AAM::getInstance();
120
 
@@ -128,16 +138,6 @@ class AAM {
128
 
129
  //load WP Core hooks
130
  AAM_Shared_Manager::bootstrap();
131
-
132
- //login control
133
- if (AAM_Core_Config::get('core.settings.secureLogin', true)) {
134
- AAM_Core_Login::bootstrap();
135
- }
136
-
137
- //JWT Authentication
138
- if (AAM_Core_Config::get('core.settings.jwtAuthentication', false)) {
139
- AAM_Core_JwtAuth::bootstrap();
140
- }
141
  }
142
 
143
  /**
3
  /**
4
  Plugin Name: Advanced Access Manager
5
  Description: All you need to manage access to your WordPress website
6
+ Version: 5.7.3
7
  Author: Vasyl Martyniuk <vasyl@vasyltech.com>
8
  Author URI: https://vasyltech.com
9
 
115
  //load AAM core config
116
  AAM_Core_Config::bootstrap();
117
 
118
+ //login control
119
+ if (AAM_Core_Config::get('core.settings.secureLogin', true)) {
120
+ AAM_Core_Login::bootstrap();
121
+ }
122
+
123
+ //JWT Authentication
124
+ if (AAM_Core_Config::get('core.settings.jwtAuthentication', false)) {
125
+ AAM_Core_JwtAuth::bootstrap();
126
+ }
127
+
128
  // Load AAM
129
  AAM::getInstance();
130
 
138
 
139
  //load WP Core hooks
140
  AAM_Shared_Manager::bootstrap();
 
 
 
 
 
 
 
 
 
 
141
  }
142
 
143
  /**
media/js/{aam-5.7.2.js → aam-5.7.3.js} RENAMED
@@ -513,10 +513,9 @@
513
  response.role.level
514
  );
515
  getAAM().fetchContent('main');
516
- $('#add-role-modal').modal('hide');
517
  } else {
518
  getAAM().notification(
519
- 'danger', getAAM().__('Failed to add new role')
520
  );
521
  }
522
  },
@@ -524,6 +523,7 @@
524
  getAAM().notification('danger', getAAM().__('Application error'));
525
  },
526
  complete: function () {
 
527
  $(_this).text(getAAM().__('Add Role')).attr('disabled', false);
528
  }
529
  });
@@ -1330,6 +1330,56 @@
1330
  */
1331
  (function ($) {
1332
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1333
  /**
1334
  *
1335
  * @param {type} subject
@@ -1357,6 +1407,23 @@
1357
  getAAM().reset('policy', $(this));
1358
  });
1359
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1360
  $('#policy-list').DataTable({
1361
  autoWidth: false,
1362
  ordering: false,
@@ -1386,6 +1453,26 @@
1386
  columnDefs: [
1387
  {visible: false, targets: [0,3]}
1388
  ],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1389
  createdRow: function (row, data) {
1390
  var actions = data[2].split(',');
1391
 
@@ -3436,29 +3523,58 @@
3436
  * @returns {undefined}
3437
  */
3438
  function downloadExtension(data, cb) {
3439
- $.ajax(getLocal().ajaxurl, {
3440
- type: 'POST',
3441
  dataType: 'json',
3442
- data: data,
3443
- success: function (response) {
3444
- if (response.status === 'success') {
3445
- setTimeout(function () {
3446
- getAAM().fetchContent('extensions');
3447
- }, 500);
 
 
3448
  } else {
3449
- getAAM().notification('danger', getAAM().__(response.error));
3450
- if (typeof response.content !== 'undefined') {
3451
- dump = response;
3452
- $('#installation-error').text(response.error);
3453
- $('#extension-notification-modal').modal('show');
3454
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3455
  }
3456
  },
3457
- error: function () {
3458
- getAAM().notification('danger', getAAM().__('Application error'));
3459
- },
3460
- complete: function() {
3461
- cb();
3462
  }
3463
  });
3464
  }
@@ -3505,6 +3621,7 @@
3505
  function initialize() {
3506
  if ($('#extension-content').length) {
3507
  $('[data-toggle="toggle"]', '.extensions-metabox').bootstrapToggle();
 
3508
  //check for updates
3509
  $('#aam-update-check').bind('click', function() {
3510
  $.ajax(getLocal().ajaxurl, {
@@ -3558,7 +3675,7 @@
3558
  action: 'aam',
3559
  sub_action: 'Extension_Manager.update',
3560
  _ajax_nonce: getLocal().nonce,
3561
- extension: _this.data('product')
3562
  }, function() {
3563
  $('i', _this).attr('class', 'icon-arrows-cw');
3564
  });
@@ -3648,9 +3765,9 @@
3648
  //bind the download handler
3649
  $('#download-extension').bind('click', function () {
3650
  download(
3651
- 'data:application/zip;base64,' + dump.content,
3652
- dump.title + '.zip',
3653
- 'application/zip'
3654
  );
3655
  $('#extension-notification-modal').modal('hide');
3656
  });
513
  response.role.level
514
  );
515
  getAAM().fetchContent('main');
 
516
  } else {
517
  getAAM().notification(
518
+ 'danger', response.reason
519
  );
520
  }
521
  },
523
  getAAM().notification('danger', getAAM().__('Application error'));
524
  },
525
  complete: function () {
526
+ $('#add-role-modal').modal('hide');
527
  $(_this).text(getAAM().__('Add Role')).attr('disabled', false);
528
  }
529
  });
1330
  */
1331
  (function ($) {
1332
 
1333
+ /**
1334
+ *
1335
+ * @param {type} data
1336
+ * @param {type} cb
1337
+ * @returns {undefined}
1338
+ */
1339
+ function downloadLicense(data, cb) {
1340
+ $.ajax(getLocal().system.apiEndpoint + '/download', {
1341
+ type: 'GET',
1342
+ dataType: 'json',
1343
+ data: {
1344
+ license: data.license,
1345
+ domain: getLocal().system.domain,
1346
+ uid: getLocal().system.uid
1347
+ },
1348
+ success: function (package) {
1349
+ $.ajax(getLocal().ajaxurl, {
1350
+ type: 'POST',
1351
+ dataType: 'json',
1352
+ data: {
1353
+ action: 'aam',
1354
+ sub_action: 'Main_Policy.install',
1355
+ _ajax_nonce: getLocal().nonce,
1356
+ license: data.license,
1357
+ package: package
1358
+ },
1359
+ success: function (response) {
1360
+ if (response.status !== 'success') {
1361
+ getAAM().notification('danger', getAAM().__(response.error));
1362
+ }
1363
+ },
1364
+ error: function () {
1365
+ getAAM().notification(
1366
+ 'danger',
1367
+ getAAM().__('Application error')
1368
+ );
1369
+ },
1370
+ complete: function() {
1371
+ cb();
1372
+ }
1373
+ });
1374
+ },
1375
+ error: function (response) {
1376
+ getAAM().notification(
1377
+ 'danger', response.responseJSON.message
1378
+ );
1379
+ }
1380
+ });
1381
+ }
1382
+
1383
  /**
1384
  *
1385
  * @param {type} subject
1407
  getAAM().reset('policy', $(this));
1408
  });
1409
 
1410
+ $('#download-policy').bind('click', function() {
1411
+ var license = $.trim($('#policy-license-key').val());
1412
+
1413
+ if (license) {
1414
+ $(this).text(getAAM().__('Downloading'));
1415
+ downloadLicense({
1416
+ license: license
1417
+ }, function() {
1418
+ $('#download-policy').text(getAAM().__('Download'));
1419
+ $('#policy-list').DataTable().ajax.reload();
1420
+ $('#download-policy-modal').modal('hide');
1421
+ });
1422
+ } else {
1423
+ $('#policy-license-key').focus();
1424
+ }
1425
+ });
1426
+
1427
  $('#policy-list').DataTable({
1428
  autoWidth: false,
1429
  ordering: false,
1453
  columnDefs: [
1454
  {visible: false, targets: [0,3]}
1455
  ],
1456
+ initComplete: function () {
1457
+ var create = $('<a/>', {
1458
+ 'href': '#',
1459
+ 'class': 'btn btn-primary'
1460
+ }).html('<i class="icon-plus"></i> ' + getAAM().__('Create'))
1461
+ .bind('click', function () {
1462
+ window.open(getLocal().url.addPolicy, '_blank');
1463
+ });
1464
+
1465
+ /*var download = $('<a/>', {
1466
+ 'href': '#',
1467
+ 'class': 'btn btn-success'
1468
+ }).html('<i class="icon-download-cloud"></i> ' + getAAM().__('Download'))
1469
+ .bind('click', function () {
1470
+ $('#download-policy-modal').modal('show');
1471
+ });
1472
+
1473
+ $('.dataTables_filter', '#policy-list_wrapper').append(download);*/
1474
+ $('.dataTables_filter', '#policy-list_wrapper').append(create);
1475
+ },
1476
  createdRow: function (row, data) {
1477
  var actions = data[2].split(',');
1478
 
3523
  * @returns {undefined}
3524
  */
3525
  function downloadExtension(data, cb) {
3526
+ $.ajax(getLocal().system.apiEndpoint + '/download', {
3527
+ type: 'GET',
3528
  dataType: 'json',
3529
+ data: {
3530
+ license: data.license,
3531
+ domain: getLocal().system.domain,
3532
+ uid: getLocal().system.uid
3533
+ },
3534
+ success: function (package) {
3535
+ if (package.error === true) {
3536
+ getAAM().notification('danger', package.message);
3537
  } else {
3538
+ $.ajax(getLocal().ajaxurl, {
3539
+ type: 'POST',
3540
+ dataType: 'json',
3541
+ data: {
3542
+ action: 'aam',
3543
+ sub_action: 'Extension_Manager.install',
3544
+ _ajax_nonce: getLocal().nonce,
3545
+ license: data.license,
3546
+ package: package
3547
+ },
3548
+ success: function (response) {
3549
+ if (response.status === 'success') {
3550
+ setTimeout(function () {
3551
+ getAAM().fetchContent('extensions');
3552
+ }, 500);
3553
+ } else {
3554
+ getAAM().notification('danger', response.error);
3555
+ if (typeof package.content !== 'undefined') {
3556
+ dump = package;
3557
+ $('#installation-error').text(response.error);
3558
+ $('#extension-notification-modal').modal('show');
3559
+ }
3560
+ }
3561
+ },
3562
+ error: function () {
3563
+ getAAM().notification(
3564
+ 'danger',
3565
+ getAAM().__('Application error')
3566
+ );
3567
+ },
3568
+ complete: function() {
3569
+ cb();
3570
+ }
3571
+ });
3572
  }
3573
  },
3574
+ error: function (response) {
3575
+ getAAM().notification(
3576
+ 'danger', response.responseJSON.message
3577
+ );
 
3578
  }
3579
  });
3580
  }
3621
  function initialize() {
3622
  if ($('#extension-content').length) {
3623
  $('[data-toggle="toggle"]', '.extensions-metabox').bootstrapToggle();
3624
+
3625
  //check for updates
3626
  $('#aam-update-check').bind('click', function() {
3627
  $.ajax(getLocal().ajaxurl, {
3675
  action: 'aam',
3676
  sub_action: 'Extension_Manager.update',
3677
  _ajax_nonce: getLocal().nonce,
3678
+ license: _this.data('license')
3679
  }, function() {
3680
  $('i', _this).attr('class', 'icon-arrows-cw');
3681
  });
3765
  //bind the download handler
3766
  $('#download-extension').bind('click', function () {
3767
  download(
3768
+ 'data:application/zip;base64,' + dump.content,
3769
+ dump.title + '.zip',
3770
+ 'application/zip'
3771
  );
3772
  $('#extension-notification-modal').modal('hide');
3773
  });
readme.txt CHANGED
@@ -3,7 +3,7 @@ Contributors: vasyltech
3
  Tags: access control, membership, backend menu, user role, restricted content, security, jwt
4
  Requires at least: 4.0
5
  Tested up to: 5.0.2
6
- Stable tag: 5.7.2
7
 
8
  All you need to manage access to you WordPress websites on frontend, backend and API levels for any role, user or visitors.
9
 
@@ -78,6 +78,21 @@ https://www.youtube.com/watch?v=mj5Xa_Wc16Y
78
 
79
  == Changelog ==
80
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81
  = 5.7.2 =
82
  * Fixed bug with Posts & Terms feature for WP version under 4.8
83
  * Fixed bug were Access Policy can't be attached to any principal on the Policy edit screen
3
  Tags: access control, membership, backend menu, user role, restricted content, security, jwt
4
  Requires at least: 4.0
5
  Tested up to: 5.0.2
6
+ Stable tag: 5.7.3
7
 
8
  All you need to manage access to you WordPress websites on frontend, backend and API levels for any role, user or visitors.
9
 
78
 
79
  == Changelog ==
80
 
81
+ = 5.7.3 =
82
+ * Fixed the bug with JWT authentication
83
+ * Fixed the PHP bug in policy metabox when no errors with JSON is detected
84
+ * Fixed the bug with license expiration for Extended versions not properly displayed
85
+ * Fixed the bug with Admin Menu listed incorrectly when Default Access Settings defined
86
+ * Fixed the PHP bug in Post object when access settings are defined in a Policy
87
+ * Improved role creation feature
88
+ * Improved capability handling with Access & Security Policy
89
+ * Refactor the way extension is installed to eliminate cURL issues
90
+ * Deprecated and removed `aam_display_license` capability
91
+ * Extended default policy document with dependencies
92
+ * Added support for `Features` in the Access & Security Policy
93
+ * Added policy Validation functionality
94
+ * Reduced number of methods that use cURL to contact aamplugin.com API
95
+
96
  = 5.7.2 =
97
  * Fixed bug with Posts & Terms feature for WP version under 4.8
98
  * Fixed bug were Access Policy can't be attached to any principal on the Policy edit screen
vendor/autoload.php CHANGED
@@ -7,9 +7,36 @@
7
  * ======================================================================
8
  */
9
 
 
10
  if (!class_exists('Firebase\JWT')) {
11
  require __DIR__ . '/firebase/BeforeValidException.php';
12
  require __DIR__ . '/firebase/ExpiredException.php';
13
  require __DIR__ . '/firebase/SignatureInvalidException.php';
14
  require __DIR__ . '/firebase/JWT.php';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
  }
7
  * ======================================================================
8
  */
9
 
10
+ // Firebase for JWT token handling
11
  if (!class_exists('Firebase\JWT')) {
12
  require __DIR__ . '/firebase/BeforeValidException.php';
13
  require __DIR__ . '/firebase/ExpiredException.php';
14
  require __DIR__ . '/firebase/SignatureInvalidException.php';
15
  require __DIR__ . '/firebase/JWT.php';
16
+ }
17
+
18
+ //Composer Semver for Policy dependency versioning
19
+ if (!class_exists('Composer\Semver')) {
20
+ /**
21
+ * Load composer semantic version check
22
+ *
23
+ * @param string $classname
24
+ *
25
+ * @return void
26
+ */
27
+ function loadComposerSemver($classname) {
28
+ if (strpos($classname, 'Composer\Semver') === 0) {
29
+ $normalized = str_replace(
30
+ array('Composer\Semver', '\\'),
31
+ array('Composer', '/'),
32
+ $classname
33
+ );
34
+ $filename = __DIR__ . '/' . $normalized . '.php';
35
+ }
36
+
37
+ if (!empty($filename) && file_exists($filename)) {
38
+ require $filename;
39
+ }
40
+ }
41
+ spl_autoload_register('loadComposerSemver');
42
  }
vendor/composer/Comparator.php ADDED
@@ -0,0 +1,111 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of composer/semver.
5
+ *
6
+ * (c) Composer <https://github.com/composer>
7
+ *
8
+ * For the full copyright and license information, please view
9
+ * the LICENSE file that was distributed with this source code.
10
+ */
11
+
12
+ namespace Composer\Semver;
13
+
14
+ use Composer\Semver\Constraint\Constraint;
15
+
16
+ class Comparator
17
+ {
18
+ /**
19
+ * Evaluates the expression: $version1 > $version2.
20
+ *
21
+ * @param string $version1
22
+ * @param string $version2
23
+ *
24
+ * @return bool
25
+ */
26
+ public static function greaterThan($version1, $version2)
27
+ {
28
+ return self::compare($version1, '>', $version2);
29
+ }
30
+
31
+ /**
32
+ * Evaluates the expression: $version1 >= $version2.
33
+ *
34
+ * @param string $version1
35
+ * @param string $version2
36
+ *
37
+ * @return bool
38
+ */
39
+ public static function greaterThanOrEqualTo($version1, $version2)
40
+ {
41
+ return self::compare($version1, '>=', $version2);
42
+ }
43
+
44
+ /**
45
+ * Evaluates the expression: $version1 < $version2.
46
+ *
47
+ * @param string $version1
48
+ * @param string $version2
49
+ *
50
+ * @return bool
51
+ */
52
+ public static function lessThan($version1, $version2)
53
+ {
54
+ return self::compare($version1, '<', $version2);
55
+ }
56
+
57
+ /**
58
+ * Evaluates the expression: $version1 <= $version2.
59
+ *
60
+ * @param string $version1
61
+ * @param string $version2
62
+ *
63
+ * @return bool
64
+ */
65
+ public static function lessThanOrEqualTo($version1, $version2)
66
+ {
67
+ return self::compare($version1, '<=', $version2);
68
+ }
69
+
70
+ /**
71
+ * Evaluates the expression: $version1 == $version2.
72
+ *
73
+ * @param string $version1
74
+ * @param string $version2
75
+ *
76
+ * @return bool
77
+ */
78
+ public static function equalTo($version1, $version2)
79
+ {
80
+ return self::compare($version1, '==', $version2);
81
+ }
82
+
83
+ /**
84
+ * Evaluates the expression: $version1 != $version2.
85
+ *
86
+ * @param string $version1
87
+ * @param string $version2
88
+ *
89
+ * @return bool
90
+ */
91
+ public static function notEqualTo($version1, $version2)
92
+ {
93
+ return self::compare($version1, '!=', $version2);
94
+ }
95
+
96
+ /**
97
+ * Evaluates the expression: $version1 $operator $version2.
98
+ *
99
+ * @param string $version1
100
+ * @param string $operator
101
+ * @param string $version2
102
+ *
103
+ * @return bool
104
+ */
105
+ public static function compare($version1, $operator, $version2)
106
+ {
107
+ $constraint = new Constraint($operator, $version2);
108
+
109
+ return $constraint->matches(new Constraint('==', $version1));
110
+ }
111
+ }
vendor/composer/Constraint/AbstractConstraint.php ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of composer/semver.
5
+ *
6
+ * (c) Composer <https://github.com/composer>
7
+ *
8
+ * For the full copyright and license information, please view
9
+ * the LICENSE file that was distributed with this source code.
10
+ */
11
+
12
+ namespace Composer\Semver\Constraint;
13
+
14
+ trigger_error('The ' . __CLASS__ . ' abstract class is deprecated, there is no replacement for it, it will be removed in the next major version.', E_USER_DEPRECATED);
15
+
16
+ /**
17
+ * Base constraint class.
18
+ */
19
+ abstract class AbstractConstraint implements ConstraintInterface
20
+ {
21
+ /** @var string */
22
+ protected $prettyString;
23
+
24
+ /**
25
+ * @param ConstraintInterface $provider
26
+ *
27
+ * @return bool
28
+ */
29
+ public function matches(ConstraintInterface $provider)
30
+ {
31
+ if ($provider instanceof $this) {
32
+ // see note at bottom of this class declaration
33
+ return $this->matchSpecific($provider);
34
+ }
35
+
36
+ // turn matching around to find a match
37
+ return $provider->matches($this);
38
+ }
39
+
40
+ /**
41
+ * @param string $prettyString
42
+ */
43
+ public function setPrettyString($prettyString)
44
+ {
45
+ $this->prettyString = $prettyString;
46
+ }
47
+
48
+ /**
49
+ * @return string
50
+ */
51
+ public function getPrettyString()
52
+ {
53
+ if ($this->prettyString) {
54
+ return $this->prettyString;
55
+ }
56
+
57
+ return $this->__toString();
58
+ }
59
+
60
+ // implementations must implement a method of this format:
61
+ // not declared abstract here because type hinting violates parameter coherence (TODO right word?)
62
+ // public function matchSpecific(<SpecificConstraintType> $provider);
63
+ }
vendor/composer/Constraint/Constraint.php ADDED
@@ -0,0 +1,219 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of composer/semver.
5
+ *
6
+ * (c) Composer <https://github.com/composer>
7
+ *
8
+ * For the full copyright and license information, please view
9
+ * the LICENSE file that was distributed with this source code.
10
+ */
11
+
12
+ namespace Composer\Semver\Constraint;
13
+
14
+ /**
15
+ * Defines a constraint.
16
+ */
17
+ class Constraint implements ConstraintInterface
18
+ {
19
+ /* operator integer values */
20
+ const OP_EQ = 0;
21
+ const OP_LT = 1;
22
+ const OP_LE = 2;
23
+ const OP_GT = 3;
24
+ const OP_GE = 4;
25
+ const OP_NE = 5;
26
+
27
+ /**
28
+ * Operator to integer translation table.
29
+ *
30
+ * @var array
31
+ */
32
+ private static $transOpStr = array(
33
+ '=' => self::OP_EQ,
34
+ '==' => self::OP_EQ,
35
+ '<' => self::OP_LT,
36
+ '<=' => self::OP_LE,
37
+ '>' => self::OP_GT,
38
+ '>=' => self::OP_GE,
39
+ '<>' => self::OP_NE,
40
+ '!=' => self::OP_NE,
41
+ );
42
+
43
+ /**
44
+ * Integer to operator translation table.
45
+ *
46
+ * @var array
47
+ */
48
+ private static $transOpInt = array(
49
+ self::OP_EQ => '==',
50
+ self::OP_LT => '<',
51
+ self::OP_LE => '<=',
52
+ self::OP_GT => '>',
53
+ self::OP_GE => '>=',
54
+ self::OP_NE => '!=',
55
+ );
56
+
57
+ /** @var string */
58
+ protected $operator;
59
+
60
+ /** @var string */
61
+ protected $version;
62
+
63
+ /** @var string */
64
+ protected $prettyString;
65
+
66
+ /**
67
+ * @param ConstraintInterface $provider
68
+ *
69
+ * @return bool
70
+ */
71
+ public function matches(ConstraintInterface $provider)
72
+ {
73
+ if ($provider instanceof $this) {
74
+ return $this->matchSpecific($provider);
75
+ }
76
+
77
+ // turn matching around to find a match
78
+ return $provider->matches($this);
79
+ }
80
+
81
+ /**
82
+ * @param string $prettyString
83
+ */
84
+ public function setPrettyString($prettyString)
85
+ {
86
+ $this->prettyString = $prettyString;
87
+ }
88
+
89
+ /**
90
+ * @return string
91
+ */
92
+ public function getPrettyString()
93
+ {
94
+ if ($this->prettyString) {
95
+ return $this->prettyString;
96
+ }
97
+
98
+ return $this->__toString();
99
+ }
100
+
101
+ /**
102
+ * Get all supported comparison operators.
103
+ *
104
+ * @return array
105
+ */
106
+ public static function getSupportedOperators()
107
+ {
108
+ return array_keys(self::$transOpStr);
109
+ }
110
+
111
+ /**
112
+ * Sets operator and version to compare with.
113
+ *
114
+ * @param string $operator
115
+ * @param string $version
116
+ *
117
+ * @throws \InvalidArgumentException if invalid operator is given.
118
+ */
119
+ public function __construct($operator, $version)
120
+ {
121
+ if (!isset(self::$transOpStr[$operator])) {
122
+ throw new \InvalidArgumentException(sprintf(
123
+ 'Invalid operator "%s" given, expected one of: %s',
124
+ $operator,
125
+ implode(', ', self::getSupportedOperators())
126
+ ));
127
+ }
128
+
129
+ $this->operator = self::$transOpStr[$operator];
130
+ $this->version = $version;
131
+ }
132
+
133
+ /**
134
+ * @param string $a
135
+ * @param string $b
136
+ * @param string $operator
137
+ * @param bool $compareBranches
138
+ *
139
+ * @throws \InvalidArgumentException if invalid operator is given.
140
+ *
141
+ * @return bool
142
+ */
143
+ public function versionCompare($a, $b, $operator, $compareBranches = false)
144
+ {
145
+ if (!isset(self::$transOpStr[$operator])) {
146
+ throw new \InvalidArgumentException(sprintf(
147
+ 'Invalid operator "%s" given, expected one of: %s',
148
+ $operator,
149
+ implode(', ', self::getSupportedOperators())
150
+ ));
151
+ }
152
+
153
+ $aIsBranch = 'dev-' === substr($a, 0, 4);
154
+ $bIsBranch = 'dev-' === substr($b, 0, 4);
155
+
156
+ if ($aIsBranch && $bIsBranch) {
157
+ return $operator === '==' && $a === $b;
158
+ }
159
+
160
+ // when branches are not comparable, we make sure dev branches never match anything
161
+ if (!$compareBranches && ($aIsBranch || $bIsBranch)) {
162
+ return false;
163
+ }
164
+
165
+ return version_compare($a, $b, $operator);
166
+ }
167
+
168
+ /**
169
+ * @param Constraint $provider
170
+ * @param bool $compareBranches
171
+ *
172
+ * @return bool
173
+ */
174
+ public function matchSpecific(Constraint $provider, $compareBranches = false)
175
+ {
176
+ $noEqualOp = str_replace('=', '', self::$transOpInt[$this->operator]);
177
+ $providerNoEqualOp = str_replace('=', '', self::$transOpInt[$provider->operator]);
178
+
179
+ $isEqualOp = self::OP_EQ === $this->operator;
180
+ $isNonEqualOp = self::OP_NE === $this->operator;
181
+ $isProviderEqualOp = self::OP_EQ === $provider->operator;
182
+ $isProviderNonEqualOp = self::OP_NE === $provider->operator;
183
+
184
+ // '!=' operator is match when other operator is not '==' operator or version is not match
185
+ // these kinds of comparisons always have a solution
186
+ if ($isNonEqualOp || $isProviderNonEqualOp) {
187
+ return !$isEqualOp && !$isProviderEqualOp
188
+ || $this->versionCompare($provider->version, $this->version, '!=', $compareBranches);
189
+ }
190
+
191
+ // an example for the condition is <= 2.0 & < 1.0
192
+ // these kinds of comparisons always have a solution
193
+ if ($this->operator !== self::OP_EQ && $noEqualOp === $providerNoEqualOp) {
194
+ return true;
195
+ }
196
+
197
+ if ($this->versionCompare($provider->version, $this->version, self::$transOpInt[$this->operator], $compareBranches)) {
198
+ // special case, e.g. require >= 1.0 and provide < 1.0
199
+ // 1.0 >= 1.0 but 1.0 is outside of the provided interval
200
+ if ($provider->version === $this->version
201
+ && self::$transOpInt[$provider->operator] === $providerNoEqualOp
202
+ && self::$transOpInt[$this->operator] !== $noEqualOp) {
203
+ return false;
204
+ }
205
+
206
+ return true;
207
+ }
208
+
209
+ return false;
210
+ }
211
+
212
+ /**
213
+ * @return string
214
+ */
215
+ public function __toString()
216
+ {
217
+ return self::$transOpInt[$this->operator] . ' ' . $this->version;
218
+ }
219
+ }
vendor/composer/Constraint/ConstraintInterface.php ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of composer/semver.
5
+ *
6
+ * (c) Composer <https://github.com/composer>
7
+ *
8
+ * For the full copyright and license information, please view
9
+ * the LICENSE file that was distributed with this source code.
10
+ */
11
+
12
+ namespace Composer\Semver\Constraint;
13
+
14
+ interface ConstraintInterface
15
+ {
16
+ /**
17
+ * @param ConstraintInterface $provider
18
+ *
19
+ * @return bool
20
+ */
21
+ public function matches(ConstraintInterface $provider);
22
+
23
+ /**
24
+ * @return string
25
+ */
26
+ public function getPrettyString();
27
+
28
+ /**
29
+ * @return string
30
+ */
31
+ public function __toString();
32
+ }
vendor/composer/Constraint/EmptyConstraint.php ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of composer/semver.
5
+ *
6
+ * (c) Composer <https://github.com/composer>
7
+ *
8
+ * For the full copyright and license information, please view
9
+ * the LICENSE file that was distributed with this source code.
10
+ */
11
+
12
+ namespace Composer\Semver\Constraint;
13
+
14
+ /**
15
+ * Defines the absence of a constraint.
16
+ */
17
+ class EmptyConstraint implements ConstraintInterface
18
+ {
19
+ /** @var string */
20
+ protected $prettyString;
21
+
22
+ /**
23
+ * @param ConstraintInterface $provider
24
+ *
25
+ * @return bool
26
+ */
27
+ public function matches(ConstraintInterface $provider)
28
+ {
29
+ return true;
30
+ }
31
+
32
+ /**
33
+ * @param $prettyString
34
+ */
35
+ public function setPrettyString($prettyString)
36
+ {
37
+ $this->prettyString = $prettyString;
38
+ }
39
+
40
+ /**
41
+ * @return string
42
+ */
43
+ public function getPrettyString()
44
+ {
45
+ if ($this->prettyString) {
46
+ return $this->prettyString;
47
+ }
48
+
49
+ return $this->__toString();
50
+ }
51
+
52
+ /**
53
+ * @return string
54
+ */
55
+ public function __toString()
56
+ {
57
+ return '[]';
58
+ }
59
+ }
vendor/composer/Constraint/MultiConstraint.php ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of composer/semver.
5
+ *
6
+ * (c) Composer <https://github.com/composer>
7
+ *
8
+ * For the full copyright and license information, please view
9
+ * the LICENSE file that was distributed with this source code.
10
+ */
11
+
12
+ namespace Composer\Semver\Constraint;
13
+
14
+ /**
15
+ * Defines a conjunctive or disjunctive set of constraints.
16
+ */
17
+ class MultiConstraint implements ConstraintInterface
18
+ {
19
+ /** @var ConstraintInterface[] */
20
+ protected $constraints;
21
+
22
+ /** @var string */
23
+ protected $prettyString;
24
+
25
+ /** @var bool */
26
+ protected $conjunctive;
27
+
28
+ /**
29
+ * @param ConstraintInterface[] $constraints A set of constraints
30
+ * @param bool $conjunctive Whether the constraints should be treated as conjunctive or disjunctive
31
+ */
32
+ public function __construct(array $constraints, $conjunctive = true)
33
+ {
34
+ $this->constraints = $constraints;
35
+ $this->conjunctive = $conjunctive;
36
+ }
37
+
38
+ /**
39
+ * @return ConstraintInterface[]
40
+ */
41
+ public function getConstraints()
42
+ {
43
+ return $this->constraints;
44
+ }
45
+
46
+ /**
47
+ * @return bool
48
+ */
49
+ public function isConjunctive()
50
+ {
51
+ return $this->conjunctive;
52
+ }
53
+
54
+ /**
55
+ * @return bool
56
+ */
57
+ public function isDisjunctive()
58
+ {
59
+ return !$this->conjunctive;
60
+ }
61
+
62
+ /**
63
+ * @param ConstraintInterface $provider
64
+ *
65
+ * @return bool
66
+ */
67
+ public function matches(ConstraintInterface $provider)
68
+ {
69
+ if (false === $this->conjunctive) {
70
+ foreach ($this->constraints as $constraint) {
71
+ if ($constraint->matches($provider)) {
72
+ return true;
73
+ }
74
+ }
75
+
76
+ return false;
77
+ }
78
+
79
+ foreach ($this->constraints as $constraint) {
80
+ if (!$constraint->matches($provider)) {
81
+ return false;
82
+ }
83
+ }
84
+
85
+ return true;
86
+ }
87
+
88
+ /**
89
+ * @param string $prettyString
90
+ */
91
+ public function setPrettyString($prettyString)
92
+ {
93
+ $this->prettyString = $prettyString;
94
+ }
95
+
96
+ /**
97
+ * @return string
98
+ */
99
+ public function getPrettyString()
100
+ {
101
+ if ($this->prettyString) {
102
+ return $this->prettyString;
103
+ }
104
+
105
+ return $this->__toString();
106
+ }
107
+
108
+ /**
109
+ * @return string
110
+ */
111
+ public function __toString()
112
+ {
113
+ $constraints = array();
114
+ foreach ($this->constraints as $constraint) {
115
+ $constraints[] = (string) $constraint;
116
+ }
117
+
118
+ return '[' . implode($this->conjunctive ? ' ' : ' || ', $constraints) . ']';
119
+ }
120
+ }
vendor/composer/Semver.php ADDED
@@ -0,0 +1,127 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of composer/semver.
5
+ *
6
+ * (c) Composer <https://github.com/composer>
7
+ *
8
+ * For the full copyright and license information, please view
9
+ * the LICENSE file that was distributed with this source code.
10
+ */
11
+
12
+ namespace Composer\Semver;
13
+
14
+ use Composer\Semver\Constraint\Constraint;
15
+
16
+ class Semver
17
+ {
18
+ const SORT_ASC = 1;
19
+ const SORT_DESC = -1;
20
+
21
+ /** @var VersionParser */
22
+ private static $versionParser;
23
+
24
+ /**
25
+ * Determine if given version satisfies given constraints.
26
+ *
27
+ * @param string $version
28
+ * @param string $constraints
29
+ *
30
+ * @return bool
31
+ */
32
+ public static function satisfies($version, $constraints)
33
+ {
34
+ if (null === self::$versionParser) {
35
+ self::$versionParser = new VersionParser();
36
+ }
37
+
38
+ $versionParser = self::$versionParser;
39
+ $provider = new Constraint('==', $versionParser->normalize($version));
40
+ $constraints = $versionParser->parseConstraints($constraints);
41
+
42
+ return $constraints->matches($provider);
43
+ }
44
+
45
+ /**
46
+ * Return all versions that satisfy given constraints.
47
+ *
48
+ * @param array $versions
49
+ * @param string $constraints
50
+ *
51
+ * @return array
52
+ */
53
+ public static function satisfiedBy(array $versions, $constraints)
54
+ {
55
+ $versions = array_filter($versions, function ($version) use ($constraints) {
56
+ return Semver::satisfies($version, $constraints);
57
+ });
58
+
59
+ return array_values($versions);
60
+ }
61
+
62
+ /**
63
+ * Sort given array of versions.
64
+ *
65
+ * @param array $versions
66
+ *
67
+ * @return array
68
+ */
69
+ public static function sort(array $versions)
70
+ {
71
+ return self::usort($versions, self::SORT_ASC);
72
+ }
73
+
74
+ /**
75
+ * Sort given array of versions in reverse.
76
+ *
77
+ * @param array $versions
78
+ *
79
+ * @return array
80
+ */
81
+ public static function rsort(array $versions)
82
+ {
83
+ return self::usort($versions, self::SORT_DESC);
84
+ }
85
+
86
+ /**
87
+ * @param array $versions
88
+ * @param int $direction
89
+ *
90
+ * @return array
91
+ */
92
+ private static function usort(array $versions, $direction)
93
+ {
94
+ if (null === self::$versionParser) {
95
+ self::$versionParser = new VersionParser();
96
+ }
97
+
98
+ $versionParser = self::$versionParser;
99
+ $normalized = array();
100
+
101
+ // Normalize outside of usort() scope for minor performance increase.
102
+ // Creates an array of arrays: [[normalized, key], ...]
103
+ foreach ($versions as $key => $version) {
104
+ $normalized[] = array($versionParser->normalize($version), $key);
105
+ }
106
+
107
+ usort($normalized, function (array $left, array $right) use ($direction) {
108
+ if ($left[0] === $right[0]) {
109
+ return 0;
110
+ }
111
+
112
+ if (Comparator::lessThan($left[0], $right[0])) {
113
+ return -$direction;
114
+ }
115
+
116
+ return $direction;
117
+ });
118
+
119
+ // Recreate input array, using the original indexes which are now in sorted order.
120
+ $sorted = array();
121
+ foreach ($normalized as $item) {
122
+ $sorted[] = $versions[$item[1]];
123
+ }
124
+
125
+ return $sorted;
126
+ }
127
+ }
vendor/composer/VersionParser.php ADDED
@@ -0,0 +1,548 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of composer/semver.
5
+ *
6
+ * (c) Composer <https://github.com/composer>
7
+ *
8
+ * For the full copyright and license information, please view
9
+ * the LICENSE file that was distributed with this source code.
10
+ */
11
+
12
+ namespace Composer\Semver;
13
+
14
+ use Composer\Semver\Constraint\ConstraintInterface;
15
+ use Composer\Semver\Constraint\EmptyConstraint;
16
+ use Composer\Semver\Constraint\MultiConstraint;
17
+ use Composer\Semver\Constraint\Constraint;
18
+
19
+ /**
20
+ * Version parser.
21
+ *
22
+ * @author Jordi Boggiano <j.boggiano@seld.be>
23
+ */
24
+ class VersionParser
25
+ {
26
+ /**
27
+ * Regex to match pre-release data (sort of).
28
+ *
29
+ * Due to backwards compatibility:
30
+ * - Instead of enforcing hyphen, an underscore, dot or nothing at all are also accepted.
31
+ * - Only stabilities as recognized by Composer are allowed to precede a numerical identifier.
32
+ * - Numerical-only pre-release identifiers are not supported, see tests.
33
+ *
34
+ * |--------------|
35
+ * [major].[minor].[patch] -[pre-release] +[build-metadata]
36
+ *
37
+ * @var string
38
+ */
39
+ private static $modifierRegex = '[._-]?(?:(stable|beta|b|RC|alpha|a|patch|pl|p)((?:[.-]?\d+)*+)?)?([.-]?dev)?';
40
+
41
+ /** @var array */
42
+ private static $stabilities = array('stable', 'RC', 'beta', 'alpha', 'dev');
43
+
44
+ /**
45
+ * Returns the stability of a version.
46
+ *
47
+ * @param string $version
48
+ *
49
+ * @return string
50
+ */
51
+ public static function parseStability($version)
52
+ {
53
+ $version = preg_replace('{#.+$}i', '', $version);
54
+
55
+ if ('dev-' === substr($version, 0, 4) || '-dev' === substr($version, -4)) {
56
+ return 'dev';
57
+ }
58
+
59
+ preg_match('{' . self::$modifierRegex . '(?:\+.*)?$}i', strtolower($version), $match);
60
+ if (!empty($match[3])) {
61
+ return 'dev';
62
+ }
63
+
64
+ if (!empty($match[1])) {
65
+ if ('beta' === $match[1] || 'b' === $match[1]) {
66
+ return 'beta';
67
+ }
68
+ if ('alpha' === $match[1] || 'a' === $match[1]) {
69
+ return 'alpha';
70
+ }
71
+ if ('rc' === $match[1]) {
72
+ return 'RC';
73
+ }
74
+ }
75
+
76
+ return 'stable';
77
+ }
78
+
79
+ /**
80
+ * @param string $stability
81
+ *
82
+ * @return string
83
+ */
84
+ public static function normalizeStability($stability)
85
+ {
86
+ $stability = strtolower($stability);
87
+
88
+ return $stability === 'rc' ? 'RC' : $stability;
89
+ }
90
+
91
+ /**
92
+ * Normalizes a version string to be able to perform comparisons on it.
93
+ *
94
+ * @param string $version
95
+ * @param string $fullVersion optional complete version string to give more context
96
+ *
97
+ * @throws \UnexpectedValueException
98
+ *
99
+ * @return string
100
+ */
101
+ public function normalize($version, $fullVersion = null)
102
+ {
103
+ $version = trim($version);
104
+ if (null === $fullVersion) {
105
+ $fullVersion = $version;
106
+ }
107
+
108
+ // strip off aliasing
109
+ if (preg_match('{^([^,\s]++) ++as ++([^,\s]++)$}', $version, $match)) {
110
+ $version = $match[1];
111
+ }
112
+
113
+ // match master-like branches
114
+ if (preg_match('{^(?:dev-)?(?:master|trunk|default)$}i', $version)) {
115
+ return '9999999-dev';
116
+ }
117
+
118
+ // if requirement is branch-like, use full name
119
+ if ('dev-' === strtolower(substr($version, 0, 4))) {
120
+ return 'dev-' . substr($version, 4);
121
+ }
122
+
123
+ // strip off build metadata
124
+ if (preg_match('{^([^,\s+]++)\+[^\s]++$}', $version, $match)) {
125
+ $version = $match[1];
126
+ }
127
+
128
+ // match classical versioning
129
+ if (preg_match('{^v?(\d{1,5})(\.\d++)?(\.\d++)?(\.\d++)?' . self::$modifierRegex . '$}i', $version, $matches)) {
130
+ $version = $matches[1]
131
+ . (!empty($matches[2]) ? $matches[2] : '.0')
132
+ . (!empty($matches[3]) ? $matches[3] : '.0')
133
+ . (!empty($matches[4]) ? $matches[4] : '.0');
134
+ $index = 5;
135
+ // match date(time) based versioning
136
+ } elseif (preg_match('{^v?(\d{4}(?:[.:-]?\d{2}){1,6}(?:[.:-]?\d{1,3})?)' . self::$modifierRegex . '$}i', $version, $matches)) {
137
+ $version = preg_replace('{\D}', '.', $matches[1]);
138
+ $index = 2;
139
+ }
140
+
141
+ // add version modifiers if a version was matched
142
+ if (isset($index)) {
143
+ if (!empty($matches[$index])) {
144
+ if ('stable' === $matches[$index]) {
145
+ return $version;
146
+ }
147
+ $version .= '-' . $this->expandStability($matches[$index]) . (!empty($matches[$index + 1]) ? ltrim($matches[$index + 1], '.-') : '');
148
+ }
149
+
150
+ if (!empty($matches[$index + 2])) {
151
+ $version .= '-dev';
152
+ }
153
+
154
+ return $version;
155
+ }
156
+
157
+ // match dev branches
158
+ if (preg_match('{(.*?)[.-]?dev$}i', $version, $match)) {
159
+ try {
160
+ return $this->normalizeBranch($match[1]);
161
+ } catch (\Exception $e) {
162
+ }
163
+ }
164
+
165
+ $extraMessage = '';
166
+ if (preg_match('{ +as +' . preg_quote($version) . '$}', $fullVersion)) {
167
+ $extraMessage = ' in "' . $fullVersion . '", the alias must be an exact version';
168
+ } elseif (preg_match('{^' . preg_quote($version) . ' +as +}', $fullVersion)) {
169
+ $extraMessage = ' in "' . $fullVersion . '", the alias source must be an exact version, if it is a branch name you should prefix it with dev-';
170
+ }
171
+
172
+ throw new \UnexpectedValueException('Invalid version string "' . $version . '"' . $extraMessage);
173
+ }
174
+
175
+ /**
176
+ * Extract numeric prefix from alias, if it is in numeric format, suitable for version comparison.
177
+ *
178
+ * @param string $branch Branch name (e.g. 2.1.x-dev)
179
+ *
180
+ * @return string|false Numeric prefix if present (e.g. 2.1.) or false
181
+ */
182
+ public function parseNumericAliasPrefix($branch)
183
+ {
184
+ if (preg_match('{^(?P<version>(\d++\\.)*\d++)(?:\.x)?-dev$}i', $branch, $matches)) {
185
+ return $matches['version'] . '.';
186
+ }
187
+
188
+ return false;
189
+ }
190
+
191
+ /**
192
+ * Normalizes a branch name to be able to perform comparisons on it.
193
+ *
194
+ * @param string $name
195
+ *
196
+ * @return string
197
+ */
198
+ public function normalizeBranch($name)
199
+ {
200
+ $name = trim($name);
201
+
202
+ if (in_array($name, array('master', 'trunk', 'default'))) {
203
+ return $this->normalize($name);
204
+ }
205
+
206
+ if (preg_match('{^v?(\d++)(\.(?:\d++|[xX*]))?(\.(?:\d++|[xX*]))?(\.(?:\d++|[xX*]))?$}i', $name, $matches)) {
207
+ $version = '';
208
+ for ($i = 1; $i < 5; ++$i) {
209
+ $version .= isset($matches[$i]) ? str_replace(array('*', 'X'), 'x', $matches[$i]) : '.x';
210
+ }
211
+
212
+ return str_replace('x', '9999999', $version) . '-dev';
213
+ }
214
+
215
+ return 'dev-' . $name;
216
+ }
217
+
218
+ /**
219
+ * Parses a constraint string into MultiConstraint and/or Constraint objects.
220
+ *
221
+ * @param string $constraints
222
+ *
223
+ * @return ConstraintInterface
224
+ */
225
+ public function parseConstraints($constraints)
226
+ {
227
+ $prettyConstraint = $constraints;
228
+
229
+ if (preg_match('{^([^,\s]*?)@(' . implode('|', self::$stabilities) . ')$}i', $constraints, $match)) {
230
+ $constraints = empty($match[1]) ? '*' : $match[1];
231
+ }
232
+
233
+ if (preg_match('{^(dev-[^,\s@]+?|[^,\s@]+?\.x-dev)#.+$}i', $constraints, $match)) {
234
+ $constraints = $match[1];
235
+ }
236
+
237
+ $orConstraints = preg_split('{\s*\|\|?\s*}', trim($constraints));
238
+ $orGroups = array();
239
+ foreach ($orConstraints as $constraints) {
240
+ $andConstraints = preg_split('{(?<!^|as|[=>< ,]) *(?<!-)[, ](?!-) *(?!,|as|$)}', $constraints);
241
+ if (count($andConstraints) > 1) {
242
+ $constraintObjects = array();
243
+ foreach ($andConstraints as $constraint) {
244
+ foreach ($this->parseConstraint($constraint) as $parsedConstraint) {
245
+ $constraintObjects[] = $parsedConstraint;
246
+ }
247
+ }
248
+ } else {
249
+ $constraintObjects = $this->parseConstraint($andConstraints[0]);
250
+ }
251
+
252
+ if (1 === count($constraintObjects)) {
253
+ $constraint = $constraintObjects[0];
254
+ } else {
255
+ $constraint = new MultiConstraint($constraintObjects);
256
+ }
257
+
258
+ $orGroups[] = $constraint;
259
+ }
260
+
261
+ if (1 === count($orGroups)) {
262
+ $constraint = $orGroups[0];
263
+ } elseif (2 === count($orGroups)
264
+ // parse the two OR groups and if they are contiguous we collapse
265
+ // them into one constraint
266
+ && $orGroups[0] instanceof MultiConstraint
267
+ && $orGroups[1] instanceof MultiConstraint
268
+ && 2 === count($orGroups[0]->getConstraints())
269
+ && 2 === count($orGroups[1]->getConstraints())
270
+ && ($a = (string) $orGroups[0])
271
+ && substr($a, 0, 3) === '[>=' && (false !== ($posA = strpos($a, '<', 4)))
272
+ && ($b = (string) $orGroups[1])
273
+ && substr($b, 0, 3) === '[>=' && (false !== ($posB = strpos($b, '<', 4)))
274
+ && substr($a, $posA + 2, -1) === substr($b, 4, $posB - 5)
275
+ ) {
276
+ $constraint = new MultiConstraint(array(
277
+ new Constraint('>=', substr($a, 4, $posA - 5)),
278
+ new Constraint('<', substr($b, $posB + 2, -1)),
279
+ ));
280
+ } else {
281
+ $constraint = new MultiConstraint($orGroups, false);
282
+ }
283
+
284
+ $constraint->setPrettyString($prettyConstraint);
285
+
286
+ return $constraint;
287
+ }
288
+
289
+ /**
290
+ * @param string $constraint
291
+ *
292
+ * @throws \UnexpectedValueException
293
+ *
294
+ * @return array
295
+ */
296
+ private function parseConstraint($constraint)
297
+ {
298
+ if (preg_match('{^([^,\s]+?)@(' . implode('|', self::$stabilities) . ')$}i', $constraint, $match)) {
299
+ $constraint = $match[1];
300
+ if ($match[2] !== 'stable') {
301
+ $stabilityModifier = $match[2];
302
+ }
303
+ }
304
+
305
+ if (preg_match('{^v?[xX*](\.[xX*])*$}i', $constraint)) {
306
+ return array(new EmptyConstraint());
307
+ }
308
+
309
+ $versionRegex = 'v?(\d++)(?:\.(\d++))?(?:\.(\d++))?(?:\.(\d++))?' . self::$modifierRegex . '(?:\+[^\s]+)?';
310
+
311
+ // Tilde Range
312
+ //
313
+ // Like wildcard constraints, unsuffixed tilde constraints say that they must be greater than the previous
314
+ // version, to ensure that unstable instances of the current version are allowed. However, if a stability
315
+ // suffix is added to the constraint, then a >= match on the current version is used instead.
316
+ if (preg_match('{^~>?' . $versionRegex . '$}i', $constraint, $matches)) {
317
+ if (substr($constraint, 0, 2) === '~>') {
318
+ throw new \UnexpectedValueException(
319
+ 'Could not parse version constraint ' . $constraint . ': ' .
320
+ 'Invalid operator "~>", you probably meant to use the "~" operator'
321
+ );
322
+ }
323
+
324
+ // Work out which position in the version we are operating at
325
+ if (isset($matches[4]) && '' !== $matches[4] && null !== $matches[4]) {
326
+ $position = 4;
327
+ } elseif (isset($matches[3]) && '' !== $matches[3] && null !== $matches[3]) {
328
+ $position = 3;
329
+ } elseif (isset($matches[2]) && '' !== $matches[2] && null !== $matches[2]) {
330
+ $position = 2;
331
+ } else {
332
+ $position = 1;
333
+ }
334
+
335
+ // Calculate the stability suffix
336
+ $stabilitySuffix = '';
337
+ if (!empty($matches[5])) {
338
+ $stabilitySuffix .= '-' . $this->expandStability($matches[5]) . (!empty($matches[6]) ? $matches[6] : '');
339
+ }
340
+
341
+ if (!empty($matches[7])) {
342
+ $stabilitySuffix .= '-dev';
343
+ }
344
+
345
+ if (!$stabilitySuffix) {
346
+ $stabilitySuffix = '-dev';
347
+ }
348
+
349
+ $lowVersion = $this->manipulateVersionString($matches, $position, 0) . $stabilitySuffix;
350
+ $lowerBound = new Constraint('>=', $lowVersion);
351
+
352
+ // For upper bound, we increment the position of one more significance,
353
+ // but highPosition = 0 would be illegal
354
+ $highPosition = max(1, $position - 1);
355
+ $highVersion = $this->manipulateVersionString($matches, $highPosition, 1) . '-dev';
356
+ $upperBound = new Constraint('<', $highVersion);
357
+
358
+ return array(
359
+ $lowerBound,
360
+ $upperBound,
361
+ );
362
+ }
363
+
364
+ // Caret Range
365
+ //
366
+ // Allows changes that do not modify the left-most non-zero digit in the [major, minor, patch] tuple.
367
+ // In other words, this allows patch and minor updates for versions 1.0.0 and above, patch updates for
368
+ // versions 0.X >=0.1.0, and no updates for versions 0.0.X
369
+ if (preg_match('{^\^' . $versionRegex . '($)}i', $constraint, $matches)) {
370
+ // Work out which position in the version we are operating at
371
+ if ('0' !== $matches[1] || '' === $matches[2] || null === $matches[2]) {
372
+ $position = 1;
373
+ } elseif ('0' !== $matches[2] || '' === $matches[3] || null === $matches[3]) {
374
+ $position = 2;
375
+ } else {
376
+ $position = 3;
377
+ }
378
+
379
+ // Calculate the stability suffix
380
+ $stabilitySuffix = '';
381
+ if (empty($matches[5]) && empty($matches[7])) {
382
+ $stabilitySuffix .= '-dev';
383
+ }
384
+
385
+ $lowVersion = $this->normalize(substr($constraint . $stabilitySuffix, 1));
386
+ $lowerBound = new Constraint('>=', $lowVersion);
387
+
388
+ // For upper bound, we increment the position of one more significance,
389
+ // but highPosition = 0 would be illegal
390
+ $highVersion = $this->manipulateVersionString($matches, $position, 1) . '-dev';
391
+ $upperBound = new Constraint('<', $highVersion);
392
+
393
+ return array(
394
+ $lowerBound,
395
+ $upperBound,
396
+ );
397
+ }
398
+
399
+ // X Range
400
+ //
401
+ // Any of X, x, or * may be used to "stand in" for one of the numeric values in the [major, minor, patch] tuple.
402
+ // A partial version range is treated as an X-Range, so the special character is in fact optional.
403
+ if (preg_match('{^v?(\d++)(?:\.(\d++))?(?:\.(\d++))?(?:\.[xX*])++$}', $constraint, $matches)) {
404
+ if (isset($matches[3]) && '' !== $matches[3] && null !== $matches[3]) {
405
+ $position = 3;
406
+ } elseif (isset($matches[2]) && '' !== $matches[2] && null !== $matches[2]) {
407
+ $position = 2;
408
+ } else {
409
+ $position = 1;
410
+ }
411
+
412
+ $lowVersion = $this->manipulateVersionString($matches, $position) . '-dev';
413
+ $highVersion = $this->manipulateVersionString($matches, $position, 1) . '-dev';
414
+
415
+ if ($lowVersion === '0.0.0.0-dev') {
416
+ return array(new Constraint('<', $highVersion));
417
+ }
418
+
419
+ return array(
420
+ new Constraint('>=', $lowVersion),
421
+ new Constraint('<', $highVersion),
422
+ );
423
+ }
424
+
425
+ // Hyphen Range
426
+ //
427
+ // Specifies an inclusive set. If a partial version is provided as the first version in the inclusive range,
428
+ // then the missing pieces are replaced with zeroes. If a partial version is provided as the second version in
429
+ // the inclusive range, then all versions that start with the supplied parts of the tuple are accepted, but
430
+ // nothing that would be greater than the provided tuple parts.
431
+ if (preg_match('{^(?P<from>' . $versionRegex . ') +- +(?P<to>' . $versionRegex . ')($)}i', $constraint, $matches)) {
432
+ // Calculate the stability suffix
433
+ $lowStabilitySuffix = '';
434
+ if (empty($matches[6]) && empty($matches[8])) {
435
+ $lowStabilitySuffix = '-dev';
436
+ }
437
+
438
+ $lowVersion = $this->normalize($matches['from']);
439
+ $lowerBound = new Constraint('>=', $lowVersion . $lowStabilitySuffix);
440
+
441
+ $empty = function ($x) {
442
+ return ($x === 0 || $x === '0') ? false : empty($x);
443
+ };
444
+
445
+ if ((!$empty($matches[11]) && !$empty($matches[12])) || !empty($matches[14]) || !empty($matches[16])) {
446
+ $highVersion = $this->normalize($matches['to']);
447
+ $upperBound = new Constraint('<=', $highVersion);
448
+ } else {
449
+ $highMatch = array('', $matches[10], $matches[11], $matches[12], $matches[13]);
450
+ $highVersion = $this->manipulateVersionString($highMatch, $empty($matches[11]) ? 1 : 2, 1) . '-dev';
451
+ $upperBound = new Constraint('<', $highVersion);
452
+ }
453
+
454
+ return array(
455
+ $lowerBound,
456
+ $upperBound,
457
+ );
458
+ }
459
+
460
+ // Basic Comparators
461
+ if (preg_match('{^(<>|!=|>=?|<=?|==?)?\s*(.*)}', $constraint, $matches)) {
462
+ try {
463
+ $version = $this->normalize($matches[2]);
464
+
465
+ if (!empty($stabilityModifier) && $this->parseStability($version) === 'stable') {
466
+ $version .= '-' . $stabilityModifier;
467
+ } elseif ('<' === $matches[1] || '>=' === $matches[1]) {
468
+ if (!preg_match('/-' . self::$modifierRegex . '$/', strtolower($matches[2]))) {
469
+ if (substr($matches[2], 0, 4) !== 'dev-') {
470
+ $version .= '-dev';
471
+ }
472
+ }
473
+ }
474
+
475
+ return array(new Constraint($matches[1] ?: '=', $version));
476
+ } catch (\Exception $e) {
477
+ }
478
+ }
479
+
480
+ $message = 'Could not parse version constraint ' . $constraint;
481
+ if (isset($e)) {
482
+ $message .= ': ' . $e->getMessage();
483
+ }
484
+
485
+ throw new \UnexpectedValueException($message);
486
+ }
487
+
488
+ /**
489
+ * Increment, decrement, or simply pad a version number.
490
+ *
491
+ * Support function for {@link parseConstraint()}
492
+ *
493
+ * @param array $matches Array with version parts in array indexes 1,2,3,4
494
+ * @param int $position 1,2,3,4 - which segment of the version to increment/decrement
495
+ * @param int $increment
496
+ * @param string $pad The string to pad version parts after $position
497
+ *
498
+ * @return string The new version
499
+ */
500
+ private function manipulateVersionString($matches, $position, $increment = 0, $pad = '0')
501
+ {
502
+ for ($i = 4; $i > 0; --$i) {
503
+ if ($i > $position) {
504
+ $matches[$i] = $pad;
505
+ } elseif ($i === $position && $increment) {
506
+ $matches[$i] += $increment;
507
+ // If $matches[$i] was 0, carry the decrement
508
+ if ($matches[$i] < 0) {
509
+ $matches[$i] = $pad;
510
+ --$position;
511
+
512
+ // Return null on a carry overflow
513
+ if ($i === 1) {
514
+ return;
515
+ }
516
+ }
517
+ }
518
+ }
519
+
520
+ return $matches[1] . '.' . $matches[2] . '.' . $matches[3] . '.' . $matches[4];
521
+ }
522
+
523
+ /**
524
+ * Expand shorthand stability string to long version.
525
+ *
526
+ * @param string $stability
527
+ *
528
+ * @return string
529
+ */
530
+ private function expandStability($stability)
531
+ {
532
+ $stability = strtolower($stability);
533
+
534
+ switch ($stability) {
535
+ case 'a':
536
+ return 'alpha';
537
+ case 'b':
538
+ return 'beta';
539
+ case 'p':
540
+ case 'pl':
541
+ return 'patch';
542
+ case 'rc':
543
+ return 'RC';
544
+ default:
545
+ return $stability;
546
+ }
547
+ }
548
+ }