GravityView  2.17
The best, easiest way to display Gravity Forms entries on your website.
ProductManager.php
Go to the documentation of this file.
1 <?php
2 /**
3  * @license GPL-2.0-or-later
4  *
5  * Modified by gravityview on 13-January-2023 using Strauss.
6  * @see https://github.com/BrianHenryIE/strauss
7  */
8 
10 
11 use Exception;
18 use Plugin_Upgrader;
19 
21  const EDD_PRODUCTS_API_ENDPOINT = 'https://www.gravitykit.com/edd-api/products/';
22 
23  const EDD_PRODUCTS_API_KEY = 'e4c7321c4dcf342c9cb078e27bf4ba97'; // Public key.
24 
25  const EDD_PRODUCTS_API_TOKEN = 'e031fd350b03bc223b10f04d8b5dde42'; // Public token.
26 
27  /**
28  * @since 1.0.0
29  *
30  * @var ProductManager Class instance.
31  */
32  private static $_instance;
33 
34  /**
35  * Returns class instance.
36  *
37  * @since 1.0.0
38  *
39  * @return ProductManager
40  */
41  public static function get_instance() {
42  if ( is_null( self::$_instance ) ) {
43  self::$_instance = new self();
44  }
45 
46  return self::$_instance;
47  }
48 
49  /**
50  * Initializes the class.
51  *
52  * @since 1.0.0
53  *
54  * @return void
55  */
56  public function init() {
57  static $initialized;
58 
59  if ( $initialized ) {
60  return;
61  }
62 
63  add_filter( 'gk/foundation/ajax/' . Framework::AJAX_ROUTER . '/routes', [ $this, 'configure_ajax_routes' ] );
64 
65  add_filter( 'plugin_action_links', [ $this, 'display_product_action_links' ], 10, 2 );
66 
68 
69  $initialized = true;
70  }
71 
72  /**
73  * Configures AJAX routes handled by this class.
74  *
75  * @since 1.0.0
76  *
77  * @see Core::process_ajax_request()
78  *
79  * @param array $routes AJAX action to class method map.
80  *
81  * @return array
82  */
83  public function configure_ajax_routes( array $routes ) {
84  return array_merge( $routes, [
85  'activate_product' => [ $this, 'ajax_activate_product' ],
86  'deactivate_product' => [ $this, 'ajax_deactivate_product' ],
87  'install_product' => [ $this, 'ajax_install_product' ],
88  'update_product' => [ $this, 'ajax_update_product' ],
89  'get_products' => [ $this, 'ajax_get_products_data' ],
90  ] );
91  }
92 
93  /**
94  * AJAX request wrapper for the install_product() method.
95  *
96  * @since 1.0.0
97  *
98  * @param array $payload
99  *
100  * @throws Exception
101  *
102  * @return array{path: string, active: bool, network_activated: bool, activation_error: null|string}
103  */
104  public function ajax_install_product( array $payload ) {
105  $payload = wp_parse_args( $payload, [
106  'id' => null,
107  'download' => null,
108  'activate' => false,
109  ] );
110 
111  if ( ! Framework::get_instance()->current_user_can( 'install_products' ) ) {
112  throw new Exception( esc_html__( 'You do not have a permission to perform this action.', 'gk-gravityview' ) );
113  }
114 
115  return $this->install_product( $payload );
116  }
117 
118  /**
119  * Installs a product.
120  *
121  * @since 1.0.0
122  *
123  * @param array $product
124  *
125  * @throws Exception
126  *
127  * @return array{path: string, active: bool, network_activated: bool, activation_error: null|string}
128  */
129  public function install_product( array $product ) {
130  if ( ! file_exists( ABSPATH . 'wp-admin/includes/class-wp-upgrader.php' ) ) {
131  throw new Exception( esc_html__( 'Unable to load core WordPress files required to install the product.', 'gk-gravityview' ) );
132  }
133 
134  include_once( ABSPATH . 'wp-admin/includes/class-wp-upgrader.php' );
135 
136  $product_id = $product['id'];
137 
138  $product_download_link = null;
139 
140  $license_manager = LicenseManager::get_instance();
141 
142  if ( empty( $product['download'] ) ) {
143  $licenses_data = $license_manager->get_licenses_data();
144 
145  foreach ( $licenses_data as $key => $license_data ) {
146  if ( $license_manager->is_expired_license( $license_data['expiry'] ) || empty( $license_data['products'] ) || ! isset( $license_data['products'][ $product_id ] ) ) {
147  continue;
148  }
149 
150  try {
151  $license = $license_manager->check_license( $key );
152  } catch ( Exception $e ) {
153  LoggerFramework::get_instance()->warning( "Unable to verify license key ${key} when installing product ID ${product_id}: " . $e->getMessage() );
154 
155  continue;
156  }
157 
158  if ( empty( $license['products'][ $product_id ]['download'] ) ) {
159  continue;
160  }
161 
162  $product_download_link = $license['products'][ $product_id ]['download'];
163 
164  break;
165  }
166  }
167 
168  if ( ! $product_download_link ) {
169  throw new Exception( esc_html__( 'Unable to locate product download link.', 'gk-gravityview' ) );
170  }
171 
172  $installer = new Plugin_Upgrader( new WPUpgraderSkin() );
173 
174  try {
175  $installer->install( $product_download_link );
176  } catch ( Exception $e ) {
177  $error = join( ' ', [
178  esc_html__( 'Installation failed.', 'gk-gravityview' ),
179  $e->getMessage()
180  ] );
181 
182  throw new Exception( $error );
183  }
184 
185  if ( ! $installer->result ) {
186  throw new Exception( esc_html__( 'Installation failed.', 'gk-gravityview' ) );
187  }
188 
189  $product_path = $installer->plugin_info();
190 
191  $activation_error = null;
192 
193  if ( ! is_plugin_active( $product_path ) && ! empty( $product['activate'] ) ) {
194  try {
195  $this->activate_product( $product_path );
196  } catch ( Exception $e ) {
197  $activation_error = $e->getMessage();
198  }
199  }
200 
201  return [
202  'path' => $product_path,
203  'active' => is_plugin_active( $product_path ),
204  'network_activated' => is_plugin_active_for_network( $product_path ),
205  'activation_error' => $activation_error,
206  ];
207  }
208 
209  /**
210  * AJAX request wrapper for the activate_product() method.
211  *
212  * @since 1.0.0
213  *
214  * @param array $payload
215  *
216  * @throws Exception
217  *
218  * @return array{active: bool, network_activated: bool}
219  */
220  public function ajax_activate_product( array $payload ) {
221  $payload = wp_parse_args( $payload, [
222  'path' => null,
223  ] );
224 
225  if ( ! Framework::get_instance()->current_user_can( 'activate_products' ) ) {
226  throw new Exception( esc_html__( 'You do not have a permission to perform this action.', 'gk-gravityview' ) );
227  }
228 
229  return $this->activate_product( $payload['path'] );
230  }
231 
232  /**
233  * Activates a product.
234  *
235  * @since 1.0.0
236  *
237  * @param string $product
238  *
239  * @throws Exception
240  *
241  * @return array{active: bool, network_activated: bool}
242  */
243  public function activate_product( $product ) {
244  if ( $this->is_product_active_in_current_context( $product ) ) {
245  throw new Exception( esc_html__( 'Product is already active.', 'gk-gravityview' ) );
246  }
247 
248  $result = activate_plugin( $product, false, CoreHelpers::is_network_admin() );
249 
250  if ( is_wp_error( $result ) || ! $this->is_product_active_in_current_context( $product ) ) {
251  throw new Exception( esc_html__( 'Could not activate the product.', 'gk-gravityview' ) );
252  }
253 
254  return [
255  'active' => is_plugin_active( $product ),
256  'network_activated' => is_plugin_active_for_network( $product ),
257  ];
258  }
259 
260  /**
261  * AJAX request wrapper for the deactivate_product() method.
262  *
263  * @since 1.0.0
264  *
265  * @param array $payload
266  *
267  * @throws Exception
268  *
269  * @return array
270  */
271  public function ajax_deactivate_product( array $payload ) {
272  $payload = wp_parse_args( $payload, [
273  'path' => null,
274  ] );
275 
276  if ( ! Framework::get_instance()->current_user_can( 'deactivate_products' ) ) {
277  throw new Exception( esc_html__( 'You do not have a permission to perform this action.', 'gk-gravityview' ) );
278  }
279 
280  return $this->deactivate_product( $payload['path'] );
281  }
282 
283  /**
284  * Deactivates a product.
285  *
286  * @since 1.0.0
287  *
288  * @param string $product
289  *
290  * @throws Exception
291  *
292  * @return array{active: bool, network_activated: bool}
293  */
294  public function deactivate_product( $product ) {
295  if ( ! $this->is_product_active_in_current_context( $product ) ) {
296  throw new Exception( esc_html__( 'Product in not active.', 'gk-gravityview' ) );
297  }
298 
299  deactivate_plugins( $product, false, CoreHelpers::is_network_admin() );
300 
301  if ( $this->is_product_active_in_current_context( $product ) ) {
302  throw new Exception( esc_html__( 'Could not deactivate the product.', 'gk-gravityview' ) );
303  }
304 
305  return [
306  'active' => is_plugin_active( $product ),
307  'network_activated' => is_plugin_active_for_network( $product ),
308  ];
309  }
310 
311  /**
312  * AJAX request wrapper for the update_product() method.
313  *
314  * @since 1.0.0
315  *
316  * @param array $payload
317  *
318  * @throws Exception
319  *
320  * @return array{active: bool, network_activated: bool, installed_version: string, update_available: bool}
321  */
322  public function ajax_update_product( array $payload ) {
323  $payload = wp_parse_args( $payload, [
324  'path' => null,
325  ] );
326 
327  if ( ! Framework::get_instance()->current_user_can( 'activate_products' ) ) {
328  throw new Exception( esc_html__( 'You do not have a permission to perform this action.', 'gk-gravityview' ) );
329  }
330 
331  return $this->update_product( $payload['path'] );
332  }
333 
334  /**
335  * Updates a product.
336  *
337  * @since 1.0.0
338  *
339  * @param string $product_path
340  *
341  * @throws Exception
342  *
343  * @return array{active: bool, network_activated: bool, installed_version: string, update_available: bool}
344  */
345  public function update_product( $product_path ) {
346  if ( ! file_exists( ABSPATH . 'wp-admin/includes/class-wp-upgrader.php' ) ) {
347  throw new Exception( esc_html__( 'Unable to load core WordPress files required to install the product.', 'gk-gravityview' ) );
348  }
349 
350  include_once( ABSPATH . 'wp-admin/includes/class-wp-upgrader.php' );
351 
352  // This is an edge case when for some reason the update_plugins transient is not set or the product is not marked as needing an update.
353  $update_plugins_transient_filter = function () {
354  return EDD::get_instance()->check_for_product_updates( new \stdClass() );
355  };
356 
357  // Tampering with the user-agent header (e.g., done by the WordPress Classifieds Plugin) breaks the update process.
358  $lock_user_agent_header = function ( $args, $url ) {
359  if ( strpos( $url, 'gravitykit.com' ) !== false ) {
360  $args['user-agent'] = 'WordPress/' . get_bloginfo( 'version' ) . '; ' . home_url();
361  }
362 
363  return $args;
364  };
365 
366  $updater = new Plugin_Upgrader( new WPUpgraderSkin() );
367 
368  try {
369  add_filter( 'pre_site_transient_update_plugins', $update_plugins_transient_filter );
370  add_filter( 'http_request_args', $lock_user_agent_header, 100, 2 );
371 
372  $updater->upgrade( $product_path );
373 
374  remove_filter( 'pre_site_transient_update_plugins', $update_plugins_transient_filter );
375  remove_filter( 'http_request_args', $lock_user_agent_header, 100 );
376  } catch ( Exception $e ) {
377  $error = join( ' ', [
378  esc_html__( 'Update failed.', 'gk-gravityview' ),
379  isset( $updater->strings[ $e->getMessage() ] ) ? $updater->strings[ $e->getMessage() ] : $e->getMessage(),
380  ] );
381 
382  throw new Exception( $error );
383  }
384 
385  if ( ! $updater->result ) {
386  throw new Exception( esc_html__( 'Installation failed.', 'gk-gravityview' ) );
387  }
388 
389  $activation_error = null;
390 
391  try {
392  $this->activate_product( $product_path );
393  } catch ( Exception $e ) {
394  $activation_error = $e->getMessage();
395  }
396 
397  $products_data = $this->get_products_data();
398 
399  return [
400  'active' => $products_data[ $product_path ]['active'],
401  'network_activated' => $products_data[ $product_path ]['network_activated'],
402  'installed_version' => $products_data[ $product_path ]['installed_version'],
403  'update_available' => $products_data[ $product_path ]['update_available'],
404  'activation_error' => $activation_error,
405  ];
406  }
407 
408  /**
409  * Returns a list of all GravityKit products from the API grouped by category (e.g., plugins, extensions, etc.).
410  *
411  * @since 1.0.0
412  *
413  * @throws Exception
414  *
415  * @return array
416  */
417  public function get_remote_products() {
418  try {
419  $response = Helpers::query_api(
420  self::EDD_PRODUCTS_API_ENDPOINT,
421  [
422  'key' => self::EDD_PRODUCTS_API_KEY,
423  'token' => self::EDD_PRODUCTS_API_TOKEN
424  ]
425  );
426  } catch ( Exception $e ) {
427  throw new Exception( $e->getMessage() );
428  }
429 
430  $remote_products = Arr::get( $response, 'products', [] );
431  $formatted_products = [];
432 
433  if ( empty( $response ) ) {
434  throw new Exception( esc_html__( 'Invalid product information received from the API.', 'gk-gravityview' ) );
435  }
436 
437  foreach ( $remote_products as $remote_product ) {
438  $categories = Arr::get( $remote_product, 'info.category', [] );
439 
440  if ( empty( $categories ) ) {
441  continue;
442  }
443 
444  foreach ( $categories as $category ) {
445  $category_slug = Arr::get( $category, 'slug' );
446  $category_name = Arr::get( $category, 'name' );
447 
448  if ( 'bundles' === $category_slug ) {
449  continue;
450  }
451 
452  if ( ! Arr::get( $formatted_products, $category_slug ) ) {
453  $formatted_products[ $category_slug ] = [
454  'category' => $category_name,
455  'products' => [],
456  ];
457  }
458 
459  $icons = unserialize( Arr::get( $remote_product, 'readme.icons', '' ) );
460 
461  $banners = unserialize( Arr::get( $remote_product, 'readme.banners', '' ) );
462 
463  $sections = unserialize( Arr::get( $remote_product, 'readme.sections', '' ) );
464 
465  $formatted_products[ $category_slug ]['products'][] = [
466  'id' => Arr::get( $remote_product, 'info.id' ),
467  'slug' => Arr::get( $remote_product, 'info.slug' ),
468  'text_domain' => Arr::get( $remote_product, 'info.textdomain' ),
469  'coming_soon' => Arr::get( $remote_product, 'info.coming_soon' ),
470  'title' => Arr::get( $remote_product, 'info.title' ),
471  'excerpt' => Arr::get( $remote_product, 'info.excerpt' ),
472  'buy_link' => Arr::get( $remote_product, 'info.buy_url' ),
473  'link' => esc_url( Arr::get( $remote_product, 'info.link', '' ) ),
474  'icon' => esc_url( Arr::get( $remote_product, 'info.icon', '' ) ),
475  'icons' => [
476  '1x' => esc_url( Arr::get( $icons, '1x', '' ) ),
477  '2x' => esc_url( Arr::get( $icons, '2x', '' ) ),
478  ],
479  'banners' => [
480  'low' => esc_url( Arr::get( $banners, 'low', '' ) ),
481  'high' => esc_url( Arr::get( $banners, 'high', '' ) ),
482  ],
483  'sections' => [
484  'description' => Arr::get( $sections, 'description' ),
485  'changelog' => Arr::get( $sections, 'changelog' ),
486  ],
487  'server_version' => Arr::get( $remote_product, 'licensing.version' ),
488  'modified_date' => Arr::get( $remote_product, 'info.modified_date' ),
489  'docs' => esc_url( Arr::get( $remote_product, 'info.docs_url', '' ) ),
490  'system_requirements' => Arr::get( $remote_product, 'system_requirements', [] ),
491  'plugin_dependencies' => Arr::get( $remote_product, 'plugin_dependencies', [] ),
492  ];
493  }
494  }
495 
496  return $formatted_products;
497  }
498 
499  /**
500  * AJAX request wrapper for the {@see get_products_data()} method.
501  *
502  * @since 1.0.0
503  *
504  * @param array $payload
505  *
506  * @throws Exception
507  *
508  * @return array
509  */
510  public function ajax_get_products_data( array $payload ) {
511  if ( ! Framework::get_instance()->current_user_can( 'view_products' ) ) {
512  throw new Exception( esc_html__( 'You do not have a permission to perform this action.', 'gk-gravityview' ) );
513  }
514 
515  $payload = wp_parse_args( $payload, [
516  'group_by_category' => true,
517  'skip_cache' => false,
518  ] );
519 
520  $products_data = $this->get_products_data( $payload );
521 
522  foreach ( $products_data as &$category ) {
523  foreach ( $category['products'] as &$product ) {
524  // Unset properties that are not needed in the UI.
525  foreach ( [ 'icons', 'banners', 'sections' ] as $property ) {
526  if ( isset( $product[ $property ] ) ) {
527  unset( $product[ $property ] );
528  }
529  }
530 
531  // Encrypt license keys.
532  $product['licenses'] = array_map( function ( $key ) {
533  return Encryption::get_instance()->encrypt( $key, false, Core::get_request_unique_string() );;
534  }, $product['licenses'] );
535  }
536  }
537 
538  return array_values( $products_data );
539  }
540 
541  /**
542  * Returns a list of all GravityKit products with associated installation/activation/licensing data.
543  *
544  * @since 1.0.0
545  *
546  * @param array $args (optional) Additional arguments. Default: ['skip_cache' => false, 'group_by_category' => false, 'key_by' => 'path'].
547  *
548  * @throws Exception
549  *
550  * @return array
551  */
552  public function get_products_data( array $args = [] ) {
553  $args = wp_parse_args( $args, [
554  'skip_cache' => false,
555  'group_by_category' => false,
556  'key_by' => 'path',
557  ] );
558 
559  $cache_id = Framework::ID . '/products';
560 
561  $products = ! $args['skip_cache'] ? get_site_transient( $cache_id ) : null;
562 
563  if ( ! $products ) {
564  try {
565  $products = $this->get_remote_products();
566  } catch ( Exception $e ) {
567  throw new Exception( $e->getMessage() );
568  }
569 
570  // json_encode() the object as serialize() may break due to unsupported characters.
571  set_site_transient( $cache_id, json_encode( $products ), DAY_IN_SECONDS );
572  } else if ( ! is_array( $products ) ) { // Backward compatibility for serialized data (used in earlier Foundation versions).
573  $products = json_decode( $products, true );
574  }
575 
576  if ( empty( $products ) ) {
577  LoggerFramework::get_instance()->warning( 'Products data is empty.' );
578 
579  return [];
580  }
581 
582  $product_license_map = LicenseManager::get_instance()->get_product_license_map();
583 
584  $flattened_products = [];
585 
586  // If a product is installed, add additional information to the products object.
587  foreach ( $products as &$data ) {
588  foreach ( $data['products'] as &$product_data ) {
589  $installed_product = CoreHelpers::get_installed_plugin_by_text_domain( $product_data['text_domain'] );
590 
591  /**
592  * @filter `gk/foundation/settings/{$product_slug}/settings-url` Sets link to the product settings page.
593  *
594  * @since 1.0.3
595  *
596  * @param string $settings_url URL to the product settings page.
597  */
598  $product_settings_url = apply_filters( "gk/foundation/settings/{$product_data['slug']}/settings-url", '' );
599 
600  $product_data = array_merge( $product_data, [
601  'id' => $product_data['id'],
602  'text_domain' => $installed_product ? $installed_product['text_domain'] : $product_data['text_domain'],
603  'installed' => ! is_null( $installed_product ),
604  'path' => $installed_product ? $installed_product['path'] : null,
605  'plugin_file' => $installed_product ? $installed_product['plugin_file'] : null,
606  'installed_version' => $installed_product ? $installed_product['version'] : null,
607  'update_available' => $installed_product && version_compare( $installed_product['version'], $product_data['server_version'], '<' ),
608  'active' => $installed_product ? $installed_product['active'] : false,
609  'network_activated' => $installed_product ? $installed_product['network_activated'] : false,
610  'licenses' => isset( $product_license_map[ $product_data['id'] ] ) ? $product_license_map[ $product_data['id'] ] : [],
611  'failed_system_requirements' => $this->validate_product_system_requirements( $product_data['system_requirements'] ),
612  'failed_plugin_dependencies' => $this->validate_product_plugin_dependencies( $product_data['plugin_dependencies'] ),
613  'settings' => esc_url( $product_settings_url ),
614  'has_git_folder' => $installed_product && file_exists( dirname( $installed_product['plugin_file'] ) . '/.git' ),
615  ] );
616 
617  if ( ! $args['group_by_category'] ) {
618  $key = isset( $product_data[ $args['key_by'] ] ) ? $product_data[ $args['key_by'] ] : $product_data['id'];
619 
620  $flattened_products[ $key ] = $product_data;
621  }
622  }
623  }
624 
625  $products = $args['group_by_category'] ? $products : $flattened_products;
626 
627  /**
628  * @filter `gk/foundation/products/data` Modifies products data object.
629  *
630  * @since 1.0.3
631  *
632  * @param array $products Products data.
633  * @param array $args Additional arguments passed to the get_products_data() method.
634  */
635  $products = apply_filters( 'gk/foundation/products/data', $products, $args );
636 
637  return $products;
638  }
639 
640  /**
641  * Checks if plugin is activated in the current context (network or site).
642  *
643  * @since 1.0.0
644  *
645  * @return bool
646  */
647  public function is_product_active_in_current_context( $plugin ) {
648  return CoreHelpers::is_network_admin() ? is_plugin_active_for_network( $plugin ) : is_plugin_active( $plugin );
649  }
650 
651  /**
652  * Optionally updates the Licenses submenu badge count if any of the products have newer versions available.
653  *
654  * @since 1.0.0
655  *
656  * @return void
657  */
658  public function update_submenu_badge_count() {
659  if ( ! Framework::get_instance()->current_user_can( 'install_products' ) ) {
660  return;
661  }
662  try {
663  $products_data = $this->get_products_data();
664  } catch ( Exception $e ) {
665  LoggerFramework::get_instance()->warning( 'Unable to get products when adding a badge count for products with updates.' );
666 
667  return;
668  }
669 
670  $update_count = 0;
671 
672  foreach ( $products_data as $product ) {
673  if ( $product['update_available'] && $product['active'] && ! empty( $product['licenses'] ) ) {
674  $update_count++;
675  }
676  }
677 
678  if ( ! $update_count ) {
679  return;
680  }
681 
682  add_filter( 'gk/foundation/admin-menu/submenu/' . Framework::ID . '/counter', function ( $count ) use ( $update_count ) {
683  return (int) $count + $update_count;
684  } );
685  }
686 
687  /**
688  * Validates product system requirements.
689  *
690  * @since 1.0.0
691  *
692  * @param array $requirements
693  *
694  * @return array|null An array of failed requirements or null if all requirements are met.
695  */
696  public function validate_product_system_requirements( $requirements ) {
697  $current_system_versions = [
698  'php' => PHP_VERSION,
699  'wp' => get_bloginfo( 'version' ),
700  ];
701 
702  $failed_requirements = [];
703 
704  if ( empty( $requirements ) ) {
705  return null;
706  }
707 
708  foreach ( $requirements as $key => $requirement ) {
709  if ( empty( $requirement['version'] ) ) {
710  continue;
711  }
712 
713  if ( ! isset( $current_system_versions[ $key ] ) ||
714  version_compare( $current_system_versions[ $key ], $requirement['version'], '<' )
715  ) {
716  $failed_requirements[ $key ] = array_merge( $requirement, [ 'version' => $current_system_versions[ $key ] ] );
717  }
718  }
719 
720  return $failed_requirements ?: null;
721  }
722 
723  /**
724  * Validates product plugin dependencies.
725  *
726  * @since 1.0.0
727  *
728  * @param array $dependencies
729  *
730  * @return array|null An array of failed dependencies or null if all dependencies are met.
731  */
732  public function validate_product_plugin_dependencies( $dependencies ) {
733  $failed_dependencies = [];
734 
735  if ( empty( $dependencies ) ) {
736  return null;
737  }
738 
739  foreach ( $dependencies as $text_domain => $dependency ) {
740  $installed_dependency = CoreHelpers::get_installed_plugin_by_text_domain( $text_domain );
741 
742  if ( ! $installed_dependency || ! $installed_dependency['active'] ) {
743  $failed_dependencies[ $text_domain ] = array_merge( $dependency, [ 'version' => null ] );
744  } else if ( ! empty( $dependency['version'] ) && version_compare( $installed_dependency['version'], $dependency['version'], '<' ) ) {
745  $failed_dependencies[ $text_domain ] = array_merge( $dependency, [ 'version' => $installed_dependency['version'] ] );
746  }
747  }
748 
749  return $failed_dependencies ?: null;
750  }
751 
752  /**
753  * Displays action links (e.g., Settings, Support, etc.) for each product in the Plugins page.
754  *
755  * @since 1.0.3
756  *
757  * @param array $links Links associated with the product.
758  * @param string $product_path Product path.
759  *
760  * @return array
761  */
762  public function display_product_action_links( $links, $product_path ) {
763  static $products_data;
764 
765  if ( ! $products_data ) {
766  try {
767  $products_data = $this->get_products_data();
768  } catch ( Exception $e ) {
769  LoggerFramework::get_instance()->error( 'Unable to get products when linking to the settings page in the Plugins area.' );
770 
771  return $links;
772  }
773  }
774 
775  $product = isset( $products_data[ $product_path ] ) ? $products_data[ $product_path ] : null;
776 
777  if ( ! $product ) {
778  return $links;
779  }
780 
781  $gk_links = [];
782 
783  if ( $product['settings'] ) {
784  $gk_links = [
785  'settings' => sprintf(
786  '<a href="%s">%s</a>',
787  $product['settings'],
788  esc_html__( 'Settings', 'gk-gravityview' )
789  )
790  ];
791  }
792 
793  $gk_links['support'] = sprintf(
794  '<a href="%s">%s</a>',
795  'https://docs.gravitykit.com',
796  esc_html__( 'Support', 'gk-gravityview' )
797  );
798 
799  /**
800  * @filter `gk/foundation/products/{$product_slug}/action-links` Sets product action links in the Plugins page.
801  *
802  * @since 1.0.3
803  *
804  * @param array $links Combined GravityKit and original action links.
805  * @param array $gk_links GravityKit-added action links.
806  * @param array $link Original action links.
807  */
808  return apply_filters( "gk/foundation/products/{$product['slug']}/action-links", array_merge( $gk_links, $links ), $gk_links, $links );
809  }
810 }
$url
Definition: post_image.php:25
ajax_activate_product(array $payload)
AJAX request wrapper for the activate_product() method.
ajax_get_products_data(array $payload)
AJAX request wrapper for the {.
ajax_deactivate_product(array $payload)
AJAX request wrapper for the deactivate_product() method.
static get( $array, $key, $default=null)
{}
Definition: Arr.php:99
get_products_data(array $args=[])
Returns a list of all GravityKit products with associated installation/activation/licensing data...
ajax_install_product(array $payload)
AJAX request wrapper for the install_product() method.
get_remote_products()
Returns a list of all GravityKit products from the API grouped by category (e.g., plugins...
This is class is used to catch errors and suppress output during product installation/update.
static get_instance( $secret_key='')
Returns class instance.
Definition: Encryption.php:67
is_product_active_in_current_context( $plugin)
Checks if plugin is activated in the current context (network or site).
validate_product_plugin_dependencies( $dependencies)
Validates product plugin dependencies.
static query_api( $url, array $args=[])
Performs remote call to GravityKit&#39;s EDD API.
ajax_update_product(array $payload)
AJAX request wrapper for the update_product() method.
$update_count
validate_product_system_requirements( $requirements)
Validates product system requirements.
static get_instance()
Returns class instance.
Definition: EDD.php:30
configure_ajax_routes(array $routes)
Configures AJAX routes handled by this class.
static get_request_unique_string()
Returns a unique value that was generated for this request.
Definition: Core.php:693
update_submenu_badge_count()
Optionally updates the Licenses submenu badge count if any of the products have newer versions availa...
display_product_action_links( $links, $product_path)
Displays action links (e.g., Settings, Support, etc.) for each product in the Plugins page...