| @@ -95,10 +95,16 @@ | ||
| 95 | 95 | * for — the JS token lists name WooCommerce and Sensei directly — so |
| 96 | 96 | * on a site that has them, Commerce is a WooCommerce desk in |
| 97 | 97 | * everything but its label. |
| 98 | 98 | * |
| 99 | + * And on a site that does not have them, the template is left out: | |
| 100 | + * `requires` names the plugin, and this is the side of the wire that | |
| 101 | + * knows whether it is active. The client's switcher shows what this | |
| 102 | + * list names — see `installWorkspacePresetSync()` — so dropping an | |
| 103 | + * entry here is what hides the card. | |
| 104 | + * | |
| 99 | 105 | * Filterable so a site can add a template, or drop one it has no use |
| 100 | - * for. A blog with no store has no reason to be offered a Woo desk. | |
| 106 | + * for. | |
| 101 | 107 | * |
| 102 | 108 | * @return array[] List of `array{ id, label, description, icon, color, layout }`. |
| 103 | 109 | */ |
| 104 | 110 | function openstation_workspace_presets() { |
| @@ -110,8 +116,9 @@ | ||
| 110 | 116 | 'icon' => 'dashicons-cart', |
| 111 | 117 | 'color' => '#7f54b3', |
| 112 | 118 | 'layout' => 'columns', |
| 113 | 119 | 'order' => 10, |
| 120 | + 'requires' => array( 'woocommerce/woocommerce.php' ), | |
| 114 | 121 | ), |
| 115 | 122 | array( |
| 116 | 123 | 'id' => 'learning', |
| 117 | 124 | 'label' => __( 'Learning', 'desktop-mode' ), |
| @@ -119,8 +126,9 @@ | ||
| 119 | 126 | 'icon' => 'dashicons-welcome-learn-more', |
| 120 | 127 | 'color' => '#43a047', |
| 121 | 128 | 'layout' => 'tile', |
| 122 | 129 | 'order' => 20, |
| 130 | + 'requires' => array( 'sensei-lms/sensei-lms.php' ), | |
| 123 | 131 | ), |
| 124 | 132 | array( |
| 125 | 133 | 'id' => 'publishing', |
| 126 | 134 | 'label' => __( 'Publishing', 'desktop-mode' ), |
| @@ -142,8 +150,13 @@ | ||
| 142 | 150 | * resolves a built-in's. The three shipped entries deliberately |
| 143 | 151 | * carry neither, because the client already has their token lists |
| 144 | 152 | * and duplicating them here would be two places to keep in step. |
| 145 | 153 | * |
| 154 | + * `requires` is the one field that stays on this side: a list of | |
| 155 | + * plugin basenames that must be active for the template to be | |
| 156 | + * offered at all. Unset it on a shipped entry to be offered that | |
| 157 | + * desk whatever is installed. | |
| 158 | + * | |
| 146 | 159 | * @param array[] $presets List of preset definitions. |
| 147 | 160 | */ |
| 148 | 161 | $presets = apply_filters( 'openstation_workspace_presets', $presets ); |
| 149 | 162 | |
| @@ -152,8 +165,11 @@ | ||
| 152 | 165 | } |
| 153 | 166 | |
| 154 | 167 | $clean = array(); |
| 155 | 168 | foreach ( $presets as $preset ) { |
| 169 | + if ( ! openstation_workspace_preset_requirements_met( $preset ) ) { | |
| 170 | + continue; | |
| 171 | + } | |
| 156 | 172 | $entry = openstation_sanitize_workspace_preset( $preset ); |
| 157 | 173 | if ( null !== $entry ) { |
| 158 | 174 | $clean[] = $entry; |
| 159 | 175 | } |
| @@ -158,8 +174,43 @@ | ||
| 158 | 174 | $clean[] = $entry; |
| 159 | 175 | } |
| 160 | 176 | } |
| 161 | 177 | return $clean; |
| 178 | +} | |
| 179 | + | |
| 180 | +/** | |
| 181 | + * Whether the plugins a template is built around are active here. | |
| 182 | + * | |
| 183 | + * `requires` is a list of plugin basenames — `woocommerce/woocommerce.php`, | |
| 184 | + * the same string `is_plugin_active()` takes — and every one of them has | |
| 185 | + * to be active or the template is not offered at all. A template that | |
| 186 | + * names none is always offered. | |
| 187 | + * | |
| 188 | + * The gate runs after the `openstation_workspace_presets` filter, so a | |
| 189 | + * site that wants a template anyway can unset its `requires` there. | |
| 190 | + * | |
| 191 | + * @param mixed $preset Raw preset definition. | |
| 192 | + * @return bool Whether the template may be offered. | |
| 193 | + */ | |
| 194 | +function openstation_workspace_preset_requirements_met( $preset ) { | |
| 195 | + if ( ! is_array( $preset ) || empty( $preset['requires'] ) || ! is_array( $preset['requires'] ) ) { | |
| 196 | + return true; | |
| 197 | + } | |
| 198 | + if ( ! function_exists( 'is_plugin_active' ) ) { | |
| 199 | + require_once ABSPATH . 'wp-admin/includes/plugin.php'; | |
| 200 | + } | |
| 201 | + foreach ( $preset['requires'] as $plugin ) { | |
| 202 | + if ( ! is_string( $plugin ) ) { | |
| 203 | + continue; | |
| 204 | + } | |
| 205 | + // A basename is a path, so the traversal characters go — the | |
| 206 | + // value is compared against `active_plugins`, never opened. | |
| 207 | + $plugin = str_replace( '..', '', substr( preg_replace( '#[^A-Za-z0-9_./-]#', '', $plugin ), 0, 256 ) ); | |
| 208 | + if ( '' === $plugin || ! is_plugin_active( $plugin ) ) { | |
| 209 | + return false; | |
| 210 | + } | |
| 211 | + } | |
| 212 | + return true; | |
| 162 | 213 | } |
| 163 | 214 | |
| 164 | 215 | /** |
| 165 | 216 | * Sanitizes a launch entry's `place` — where a window goes, as |