| 1 |
<?php |
| 2 |
/** |
| 3 |
* Resolves whether a Jetpack admin menu item's feature is active. |
| 4 |
* |
| 5 |
* @package automattic/my-jetpack |
| 6 |
*/ |
| 7 |
|
| 8 |
namespace Automattic\Jetpack\My_Jetpack; |
| 9 |
|
| 10 |
use Automattic\Jetpack\Admin_UI\Admin_Menu; |
| 11 |
use Automattic\Jetpack\Modules; |
| 12 |
|
| 13 |
/** |
| 14 |
* The activation half of the sidebar visibility contract. |
| 15 |
* |
| 16 |
* Admin_Menu owns when the question is asked and what a host may do with the answer; this |
| 17 |
* class owns the answer itself, because the product classes it reads live here. A menu item |
| 18 |
* declares its gate at registration and this resolves it from the same product class its |
| 19 |
* My Jetpack card reads. |
| 20 |
*/ |
| 21 |
class Menu_Visibility { |
| 22 |
|
| 23 |
/** |
| 24 |
* Gates already resolved this registration pass, keyed by type and slug. |
| 25 |
* |
| 26 |
* @var array<string, bool|null> |
| 27 |
*/ |
| 28 |
private static $resolved = array(); |
| 29 |
|
| 30 |
/** |
| 31 |
* Registers this class as Admin_Menu's visibility resolver. |
| 32 |
* |
| 33 |
* @return void |
| 34 |
*/ |
| 35 |
public static function init() { |
| 36 |
// An older admin-ui, loaded first by another plugin, may predate the resolver; items then stay visible. |
| 37 |
if ( ! method_exists( Admin_Menu::class, 'set_visibility_resolver' ) ) { |
| 38 |
return; |
| 39 |
} |
| 40 |
|
| 41 |
Admin_Menu::set_visibility_resolver( array( __CLASS__, 'resolve' ) ); |
| 42 |
|
| 43 |
// Memoized answers last one registration pass, which is as long as they can stay true. |
| 44 |
add_action( 'admin_menu', array( __CLASS__, 'forget_resolved_gates' ), 0 ); |
| 45 |
add_action( 'network_admin_menu', array( __CLASS__, 'forget_resolved_gates' ), 0 ); |
| 46 |
} |
| 47 |
|
| 48 |
/** |
| 49 |
* Drops the gates resolved during the previous registration pass. |
| 50 |
* |
| 51 |
* @return void |
| 52 |
*/ |
| 53 |
public static function forget_resolved_gates() { |
| 54 |
self::$resolved = array(); |
| 55 |
} |
| 56 |
|
| 57 |
/** |
| 58 |
* Answers whether a menu item's declared gate is satisfied. |
| 59 |
* |
| 60 |
* Answers are memoized for one registration pass: resolving a product rebuilds the product |
| 61 |
* class map and rescans the installed plugins, and the sidebar asks once per gated item. |
| 62 |
* |
| 63 |
* @param array $args The item's visibility declaration, as passed to Admin_Menu::add_menu(). |
| 64 |
* @return bool|null True or false, or null when the gate cannot be resolved here. |
| 65 |
*/ |
| 66 |
public static function resolve( $args ) { |
| 67 |
if ( ! empty( $args['product'] ) ) { |
| 68 |
$key = 'product:' . $args['product']; |
| 69 |
|
| 70 |
if ( ! array_key_exists( $key, self::$resolved ) ) { |
| 71 |
self::$resolved[ $key ] = self::is_product_activated( $args['product'] ); |
| 72 |
} |
| 73 |
|
| 74 |
return self::$resolved[ $key ]; |
| 75 |
} |
| 76 |
|
| 77 |
if ( ! empty( $args['module'] ) ) { |
| 78 |
$key = 'module:' . $args['module']; |
| 79 |
|
| 80 |
if ( ! array_key_exists( $key, self::$resolved ) ) { |
| 81 |
self::$resolved[ $key ] = self::is_module_activated( $args['module'] ); |
| 82 |
} |
| 83 |
|
| 84 |
return self::$resolved[ $key ]; |
| 85 |
} |
| 86 |
|
| 87 |
return null; |
| 88 |
} |
| 89 |
|
| 90 |
/** |
| 91 |
* Whether a Jetpack module gating an item is switched on. |
| 92 |
* |
| 93 |
* A name this site has no module for is unanswerable rather than off, so a typo in a gate |
| 94 |
* fails open like an unknown product slug does instead of silently removing the item. Off |
| 95 |
* the Jetpack plugin that covers any module a standalone plugin did not declare through |
| 96 |
* `jetpack_get_available_standalone_modules`. |
| 97 |
* |
| 98 |
* @param string $module_name A Jetpack module name. |
| 99 |
* @return bool|null Null when this site has no such module. |
| 100 |
*/ |
| 101 |
private static function is_module_activated( $module_name ) { |
| 102 |
$modules = new Modules(); |
| 103 |
|
| 104 |
if ( ! in_array( $module_name, $modules->get_available(), true ) ) { |
| 105 |
return null; |
| 106 |
} |
| 107 |
|
| 108 |
return $modules->is_active( $module_name ); |
| 109 |
} |
| 110 |
|
| 111 |
/** |
| 112 |
* Whether the site has switched a My Jetpack product on. |
| 113 |
* |
| 114 |
* Reads is_activated(), not is_active(): a lapsed plan is not "off", and resolving plans |
| 115 |
* costs WordPress.com requests on every admin page load. No connection check is added. |
| 116 |
* |
| 117 |
* @param string $product_slug A My Jetpack product slug. |
| 118 |
* @return bool|null Null when no product class is registered under that slug. |
| 119 |
*/ |
| 120 |
private static function is_product_activated( $product_slug ) { |
| 121 |
$product_class = Products::get_product_class( $product_slug ); |
| 122 |
|
| 123 |
if ( ! $product_class ) { |
| 124 |
return null; |
| 125 |
} |
| 126 |
|
| 127 |
return (bool) $product_class::is_activated(); |
| 128 |
} |
| 129 |
} |
| 130 |
|