PluginProbe
UpStream: a Project Management Plugin for WordPress / trunk
UpStream: a Project Management Plugin for WordPress vtrunk
trunk 1.39.0 1.39.1 1.39.2 1.39.3 2.0.7 2.1.0
upstream / vendor / alledia / wordpress-plugin-framework / src / library / Module / Addons.php

Addons.php in UpStream: a Project Management Plugin for WordPress trunk, at vendor/alledia/wordpress-plugin-framework/src/library/Module/Addons.php

600 lines 18.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Allex\Module;
4
5 use Allex\Container;
6 use Exception;
7 use Twig_Environment;
8 use Twig_Error_Loader;
9 use Twig_Error_Runtime;
10 use Twig_Error_Syntax;
11
12 class Addons extends Abstract_Module
13 {
14 /**
15 * Constant for valid status
16 */
17 const LICENSE_STATUS_VALID = 'valid';
18
19 /**
20 * Constant for invalid status
21 */
22 const LICENSE_STATUS_INVALID = 'invalid';
23
24 /**
25 * @var string
26 */
27 protected $plugin_basename;
28
29 /**
30 * @var string
31 */
32 protected $plugin_name;
33
34 /**
35 * @var string
36 */
37 protected $plugin_dir_path;
38
39 /**
40 * @var Twig_Environment
41 */
42 protected $twig;
43
44 /**
45 * @var array
46 */
47 protected $addons;
48
49 /**
50 * @var string
51 */
52 protected $edd_api_url;
53
54 /**
55 * @var string
56 */
57 protected $plugin_author;
58
59 /**
60 * @var string
61 */
62 protected $setLicenseKeyLink;
63
64 /**
65 * @var string
66 */
67 protected $updates_doc_url;
68
69 /**
70 * Upgrade constructor.
71 *
72 * @param Container $container
73 */
74 public function __construct(Container $container)
75 {
76 parent::__construct($container);
77
78 $this->plugin_basename = $this->container['PLUGIN_BASENAME'];
79 $this->plugin_name = $this->container['PLUGIN_NAME'];
80 $this->plugin_dir_path = $this->get_plugins_dir_path();
81 $this->twig = $this->container['twig'];
82 $this->edd_api_url = $this->container['EDD_API_URL'];
83 $this->updates_doc_url = $this->container['UPDATES_DOC_URL'];
84 $this->plugin_author = $this->container['PLUGIN_NAME'];
85 }
86
87 /**
88 * @return bool|string
89 */
90 protected function get_plugins_dir_path()
91 {
92 return dirname(dirname(dirname(dirname(dirname(dirname(dirname(__DIR__)))))));
93 }
94
95 /**
96 * Add an Upgrade link to the action links in the plugin list.
97 */
98 public function init()
99 {
100 $this->add_hooks();
101 }
102
103 /**
104 * Add the hooks.
105 */
106 protected function add_hooks()
107 {
108 add_action('allex_echo_addons_page', [$this, 'echo_addons_page'], 10, 2);
109 add_action('wp_ajax_allex_addon_license_validate', [$this, 'ajax_validate_license_key']);
110 add_action('allex_set_license_key_links', [$this, 'setLicenseKeyLinks'], 10, 2);
111
112 add_filter('allex_addons', [$this, 'filter_allex_addons_append_data'], 999998, 2);
113 add_filter('allex_installed_addons', [$this, 'filter_allex_installed_addons'], 999999, 2);
114 add_filter('plugin_action_links_' . $this->plugin_name, [$this, 'add_action_links']);
115 }
116
117 /**
118 * @param string $addons_page_url
119 * @param string $plugin_name
120 *
121 * @throws Twig_Error_Loader
122 * @throws Twig_Error_Runtime
123 * @throws Twig_Error_Syntax
124 */
125 public function echo_addons_page($addons_page_url, $plugin_name = null)
126 {
127 /*
128 * ---------------------------------------------------------------------
129 * Backward compatibility for users using PublishPress and UpStream with
130 * an older version of this library where the $plugin_name param is not
131 * sent. We force its value, so the add-ons page keeps working until
132 * they update the plugin.
133 *
134 * @todo: Remove this after a few releases
135 *
136 * @since 1.16.3
137 */
138 if (is_null($plugin_name)) {
139 $plugin_name = $this->plugin_name;
140 }
141 /*
142 * ---------------------------------------------------------------------
143 */
144
145 // If not related to this plugin, we skip it.
146 if ($plugin_name !== $this->plugin_name) {
147 return;
148 }
149
150 /**
151 * $addons = [
152 * [
153 * 'slug' => '',
154 * 'title' => '',
155 * 'description' => '',
156 * 'icon_class' => '',
157 * ],
158 * ],
159 */
160 $addons = apply_filters('allex_addons', [], $this->plugin_name, false);
161
162 // Cache the add-ons list.
163 $this->addons = $addons;
164
165 // Count the installed add-ons.
166 $count = 0;
167 foreach ($addons as $addon) {
168 if ($addon['is_installed']) {
169 $count++;
170 }
171 }
172
173 /**
174 * Filter to return a boolean value to say if it should display or not a sidebar.
175 *
176 * @var bool
177 */
178 $show_sidebar = apply_filters('allex_upgrade_show_sidebar_ad', false, $this->plugin_name, 'addons');
179
180 $sidebar_output = '';
181 if ($show_sidebar) {
182 ob_start();
183 do_action('allex_upgrade_sidebar_ad', $this->plugin_name);
184 $sidebar_output = ob_get_clean();
185 }
186
187 $context = [
188 'browse_more_url' => $addons_page_url,
189 'addons' => $addons,
190 'count_addons' => $count,
191 'count_addons_total' => count($addons),
192 'plugin_name' => $this->plugin_name,
193 'show_sidebar' => $show_sidebar,
194 'sidebar_output' => $sidebar_output,
195 'nonce' => wp_create_nonce('allex_activate_license'),
196 'labels' => [
197 'installed' => __('Installed Extensions', 'allex'),
198 'browse_more' => __('Browse More Extensions', 'allex'),
199 'enter_license_key' => __('Enter your license key', 'allex'),
200 'activate' => __('Activate', 'allex'),
201 'license_key' => __('License Key', 'allex'),
202 'change' => __('Change', 'allex'),
203 'get_plugins' => __('Get Pro Add-ons!', 'allex'),
204 ],
205 ];
206
207 echo $this->twig->render('addons_list.twig', $context);
208 }
209
210 /**
211 * Append add-ons' state and license info to the add-ons array.
212 *
213 * @param $addons
214 * @param $plugin_name
215 *
216 * @return mixed
217 */
218 public function filter_allex_addons_append_data($addons, $plugin_name)
219 {
220 // If not related to this plugin, we skip it.
221 if ($plugin_name !== $this->plugin_name) {
222 return $addons;
223 }
224
225 $this->set_addons_state($addons);
226 $this->check_addons_license($addons);
227
228 return $addons;
229 }
230
231 /**
232 * Set the a property "is_installed" and "is_active" according to the current state of the add-on.
233 *
234 * @param array $addons
235 */
236 protected function set_addons_state(&$addons)
237 {
238 if (!empty($addons)) {
239 foreach ($addons as &$addon) {
240 $is_installed = $this->is_plugin_installed($addon['slug']);
241 $is_active = false;
242
243 if ($is_installed) {
244 $is_active = $this->is_plugin_active($addon['slug']);
245 }
246
247 $addon['is_installed'] = $is_installed;
248 $addon['is_active'] = $is_active;
249 }
250 }
251 }
252
253 /**
254 * @param $plugin_name
255 *
256 * @return bool
257 */
258 public function is_plugin_installed($plugin_name)
259 {
260 return file_exists($this->plugin_dir_path . "/{$plugin_name}/{$plugin_name}.php");
261 }
262
263 /**
264 * @param $plugin_name
265 *
266 * @return bool
267 */
268 public function is_plugin_active($plugin_name)
269 {
270 return is_plugin_active("{$plugin_name}/{$plugin_name}.php");
271 }
272
273 /**
274 * @param array $addons
275 */
276 protected function check_addons_license(&$addons)
277 {
278 if (empty($addons)) {
279 return;
280 }
281
282 foreach ($addons as &$addon) {
283 $addon_name = str_replace('-', '_', $addon['slug']);
284 $addon['license_status'] = get_option("{$addon_name}_license_status", self::LICENSE_STATUS_INVALID);
285 $addon['license_key'] = get_option("{$addon_name}_license_key");
286
287 // Applies filters to the license key and status.
288 $addon['license_status'] = apply_filters(
289 'allex_addons_get_license_status',
290 $addon['license_status'],
291 $addon['slug']
292 );
293 $addon['license_key'] = apply_filters(
294 'allex_addons_get_license_key',
295 $addon['license_key'],
296 $addon['slug']
297 );
298 }
299 }
300
301 /**
302 * Filter the list of add-ons by state.
303 *
304 * @param $addons
305 * @param $plugin_name
306 *
307 * @return mixed
308 */
309 public function filter_allex_installed_addons($addons, $plugin_name)
310 {
311 // If not related to this plugin, we skip it returning an empty array.
312 if ($plugin_name !== $this->plugin_name) {
313 return $addons;
314 }
315
316 $addons = apply_filters('allex_addons', [], $plugin_name);
317
318 foreach ($addons as $addon_slug => $addon) {
319 if (!$addon['is_installed']) {
320 unset($addons[$addon_slug]);
321 }
322 }
323
324 return $addons;
325 }
326
327 /**
328 * AJAX endpoint that validates a given license key for an extension.
329 * Echoes JSON data.
330 */
331 public function ajax_validate_license_key()
332 {
333 // If we are not in the valid plugin, we skip it. Maybe the method was called by another instance.
334 if (!isset($_POST['plugin_name']) || $_POST['plugin_name'] !== $this->plugin_name) {
335 return;
336 }
337
338 $response = [
339 'success' => false,
340 'message' => '',
341 'license_status' => self::LICENSE_STATUS_INVALID,
342 'license_key' => '',
343 ];
344
345 try {
346 if (!current_user_can('activate_plugins')) {
347 throw new Exception(__("You're not allowed to do this.", 'allex'));
348 }
349
350 if (empty($_POST) || !isset($_POST['key']) || !isset($_POST['plugin_name']) || !isset($_POST['nonce'])) {
351 throw new Exception(__('Invalid request.', 'allex'));
352 }
353
354 if (!check_ajax_referer('allex_activate_license', 'nonce', false)) {
355 throw new Exception(__("You're not allowed to do this.", 'allex'));
356 }
357
358 $plugin_name = sanitize_text_field(trim($_POST['plugin_name']));
359 if (empty($plugin_name)) {
360 throw new Exception(__('Invalid plugin name.', 'allex'));
361 }
362
363 $addon_slug = sanitize_text_field(trim($_POST['addon_name']));
364 if (empty($addon_slug)) {
365 throw new Exception(__('Invalid addon name.', 'allex'));
366 }
367
368 $addon_edd_id = sanitize_text_field(trim($_POST['addon_edd_id']));
369 if (empty($addon_edd_id)) {
370 throw new Exception(__('Invalid addon EDD ID.', 'allex'));
371 }
372
373 $license_key = sanitize_text_field(trim($_POST['key']));
374 if (empty($license_key)) {
375 throw new Exception(__('Invalid license key.', 'allex'));
376 }
377
378 /**
379 * $addons = [
380 * [
381 * 'slug' => '',
382 * 'title' => '',
383 * 'description' => '',
384 * 'icon_class' => '',
385 * ],
386 * ],
387 */
388 $addons = apply_filters('allex_addons', [], $this->plugin_name);
389
390 // Check if it is a valid add-on.
391 if (empty($addons)) {
392 throw new Exception(__("Invalid add-on.", 'allex'));
393 }
394
395 if (!array_key_exists($addon_slug, $addons)) {
396 throw new Exception(__('Invalid addon name.', 'allex'));
397 }
398
399 // The default status.
400 $license_new_status = self::LICENSE_STATUS_INVALID;
401
402 // Make the request.
403 $edd_response = wp_remote_post(
404 $this->edd_api_url,
405 [
406 'timeout' => 30,
407 'sslverify' => true,
408 'body' => [
409 'edd_action' => "activate_license",
410 'license' => $license_key,
411 'item_id' => $addon_edd_id,
412 'url' => site_url(),
413 ],
414 ]
415 );
416
417 $response['license_key'] = $license_key;
418
419 // Is the response an error?
420 if (is_wp_error($edd_response) || 200 !== wp_remote_retrieve_response_code($edd_response)) {
421 $message = $edd_response->get_error_message();
422
423 if (empty($message)) {
424 throw new Exception(
425 __(
426 'An error occurred. Please, try again or contact the support team.',
427 'allex'
428 )
429 );
430 }
431
432 throw new Exception($message, 'allex');
433 }
434
435 // Convert data response to an object.
436 $data = json_decode(wp_remote_retrieve_body($edd_response));
437
438 // Do we have empty data? Throw an error.
439 if (empty($data) || !is_object($data)) {
440 throw new Exception(
441 __(
442 'An error occurred. Please, try again or contact the support team.',
443 'allex'
444 )
445 );
446 }
447
448 $response['success'] = true;
449
450 $addon_name = str_replace('-', '_', $addon_slug);
451
452 // Deal with invalid licenses.
453 if (!$data->success && $data->license === self::LICENSE_STATUS_INVALID) {
454 if (isset($data->error) && !empty($data->error)) {
455 $response['license_status'] = $data->error;
456
457 update_option("{$addon_name}_license_key", $license_key, true);
458 update_option("{$addon_name}_license_status", $data->error, true);
459
460 do_action('allex_addon_update_license', $plugin_name, $addon_slug, $license_key, $data->error);
461 }
462 }
463
464 // Deal with valid licenses.
465 if ($data->success && $data->license === self::LICENSE_STATUS_VALID) {
466 $response['license_status'] = static::LICENSE_STATUS_VALID;
467
468 // Store the license key and status.
469 update_option("{$addon_name}_license_key", $license_key, true);
470 update_option("{$addon_name}_license_status", static::LICENSE_STATUS_VALID, true);
471
472 do_action(
473 'allex_addon_update_license',
474 $plugin_name,
475 $addon_slug,
476 $license_key,
477 static::LICENSE_STATUS_VALID
478 );
479 }
480 } catch (Exception $e) {
481 $response['message'] = $e->getMessage();
482 }
483
484 header('Content-Type: application/json');
485 echo wp_json_encode($response);
486
487 wp_die();
488 }
489
490 /**
491 * @param $plugin
492 * @param $link
493 *
494 * @return array
495 */
496 public function setLicenseKeyLinks($plugin, $link)
497 {
498 $this->setLicenseKeyLink = $link;
499
500 // Set license key link
501 $pool = apply_filters('allex_addons', [], $plugin);
502
503 foreach ($pool as $addon) {
504 if (file_exists(WP_PLUGIN_DIR . '/' . $addon['slug'] . '/' . $addon['slug'] . '.php')) {
505 add_action(
506 'plugin_action_links_' . $addon['slug'] . '/' . $addon['slug'] . '.php',
507 [$this, 'actionSetLicenseKeyLink'],
508 998,
509 2
510 );
511
512 add_action(
513 'in_plugin_update_message-' . $addon['slug'] . '/' . $addon['slug'] . '.php',
514 [$this, 'inPluginUpdateMessage'],
515 998,
516 2
517 );
518 }
519 }
520 }
521
522 /**
523 * @param $pluginData
524 * @param $response
525 */
526 public function inPluginUpdateMessage($pluginData, $response)
527 {
528 // Get the plugin's add-ons
529 $addons = apply_filters('allex_addons', [], $this->plugin_name);
530 $addons = array_keys($addons);
531
532 // If is not a plugin add-on, we stop.
533 if (!in_array($pluginData['slug'], $addons)) {
534 return;
535 }
536
537 $addon = str_replace('-', '_', str_replace('.php', '', basename($pluginData['slug'])));
538 $licenseKey = get_option($addon . '_license_key');
539 $licenseStatus = get_option($addon . '_license_status');
540
541 if ((empty($licenseKey) || $licenseStatus !== 'valid') && empty($response->package)) {
542 // Update available, but there is no package probably because there is no valid license key.
543 $context = [
544 'text' => [
545 'please_enter_license_key' => __(
546 'Please enter a valid license key to enable automatic updates.',
547 'allex'
548 ),
549 'enter_license_key' => __('Enter License Key', 'allex'),
550 ],
551 'link' => $this->setLicenseKeyLink,
552 ];
553
554 echo $this->twig->render('after_plugin_row.twig', $context);
555 } elseif (empty($response->package)) {
556 // Update available, valid license key, but no package found.
557 $context = [
558 'link_docs' => $this->updates_doc_url,
559 'text' => [
560 'message' => __('Sorry, we couldn’t access the files for this update.', 'allex'),
561 'link_docs' => __('Please click this link for more details.', 'allex'),
562 ],
563 ];
564
565 echo $this->twig->render('after_plugin_row_error.twig', $context);
566 }
567 }
568
569 /**
570 * Show a link to set the license key if there is no license key or is invalid.
571 *
572 * @param $links
573 * @param $pluginFile
574 *
575 * @return array
576 */
577 public function actionSetLicenseKeyLink($links, $pluginFile)
578 {
579 $addon = str_replace('-', '_', str_replace('.php', '', basename($pluginFile)));
580 $licenseKey = get_option($addon . '_license_key');
581 $licenseStatus = get_option($addon . '_license_status');
582
583 if (empty($licenseKey) || $licenseStatus !== 'valid') {
584 $context = [
585 'url' => $this->setLicenseKeyLink,
586 'label' => __('Enter License Key', 'allex'),
587 ];
588
589 $link = $this->twig->render('action_link_set_key.twig', $context);
590
591 // Avoid to add duplicated link, in case it already exist
592 if (array_search($link, $links) === false) {
593 $links[] = $link;
594 }
595 }
596
597 return $links;
598 }
599 }
600