PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 16.1-a.5
Jetpack – WP Security, Backup, Speed, & Growth v16.1-a.5
16.2-a.3 16.1.2 16.2-a.1 16.1.1 16.1 16.1-beta 16.1-beta.2 16.1-beta.3 16.1-a.5 16.1-a.3 16.0.1 16.1-a.1 16.0 16.0-beta 16.0-a.7 16.0-a.5 15.9.1 16.0-a.3 16.0-a.1 15.9 15.9-beta 15.9-a.7 15.9-a.5 15.9-a.3 15.9-a.1 15.8 15.8-beta 15.8-a.7 15.8-a.5 5.2.5 5.3.4 5.4.4 5.5.5 5.6.5 5.7.5 5.8.4 5.9.4 6.0.4 6.1 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 6.2 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.3 6.3.1 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6 6.3.7 6.4 6.4.1 6.4.2 6.4.3 6.4.4 6.4.5 6.4.6 6.5 6.5.1 6.5.2 6.5.3 6.5.4 6.6 6.6.1 6.6.2 6.6.3 6.6.4 6.6.5 6.7 6.7.1 6.7.2 6.7.3 6.7.4 6.8 6.8.1 6.8.2 6.8.3 6.8.4 6.8.5 6.9 6.9.1 6.9.2 6.9.3 6.9.4 7.0 7.0.1 7.0.2 7.0.3 7.0.4 7.0.5 7.1 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5 7.2 7.2.1 7.2.1.1 7.2.2 7.2.3 7.2.4 7.2.5 7.3 7.3.0.1 7.3.1 7.3.1.1 7.3.2 7.3.3 7.3.4 7.3.5 7.4 7.4.1 7.4.2 7.4.3 7.4.4 7.4.5 7.5 7.5.0.1 7.5.1 7.5.2 7.5.3 7.5.4 7.5.5 7.5.6 7.5.7 7.6 7.6.1 7.6.2 7.6.3 7.6.4 7.7 7.7.1 7.7.2 7.7.3 7.7.4 7.7.5 7.7.6 7.8 7.8.1 7.8.2 7.8.3 7.8.4 7.9 7.9.1 7.9.2 7.9.3 7.9.4 8.0 8.0.1 8.0.2 8.0.3 8.1 8.1.1 8.1.2 8.1.3 8.1.4 8.2 8.2.0.1 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.2.6 8.3 8.3.1 8.3.2 8.3.3 8.4 8.4.1 8.4.2 8.4.3 8.4.4 8.4.5 8.5 8.5.1 8.5.2 8.5.3 8.6 8.6.1 8.6.2 8.6.3 8.6.4 8.7 8.7.0.1 8.7.1 8.7.2 8.7.3 8.7.4 8.8 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.9 8.9.1 8.9.2 8.9.3 8.9.4 9.0 9.0.1 9.0.2 9.0.3 9.0.4 9.0.5 9.1 9.1.1 9.1.2 9.1.3 9.2 9.2.1 9.2.2 9.2.3 9.2.4 9.3 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.4 9.4.1 9.4.2 9.4.3 9.4.4 9.5 9.5.1 9.5.2 9.5.3 9.5.4 9.5.5 9.6 9.6.1 9.6.2 9.6.3 9.6.4 9.7 9.7.1 9.7.2 15.7-beta.2 9.7.3 15.7.1 9.8 15.8-a.1 9.8.1 15.8-a.3 9.8.2 2.0.9 9.8.3 2.1.7 9.9 2.2.10 9.9.1 2.3.10 9.9.2 2.4.7 9.9.3 2.5.5 2.6.6 2.7.5 2.8.5 2.9.6 3.0.6 3.1.5 3.2.5 3.3.6 3.4.6 3.5.6 3.6.4 3.7.5 3.8.5 3.9.10 4.0.7 4.1.4 4.2.5 4.3.5 4.4.5 4.5.3 4.6.3 4.7.4 4.8.5 4.9.3 5.0.3 5.1.4 trunk 10.0 10.0.1 10.0.2 10.1 10.1.1 10.1.2 10.2 10.2.1 10.2.2 10.2.3 10.3 10.3.1 10.3.2 10.4 10.4.1 10.4.2 10.5 10.5.1 10.5.2 10.5.3 10.6 10.6.1 10.6.2 10.7 10.7.1 10.7.2 10.8 10.8.1 10.8.2 10.9 10.9.1 10.9.2 10.9.3 11.0 11.0.1 11.0.2 11.1 11.1.1 11.1.2 11.1.3 11.1.4 11.2 11.2.1 11.2.2 11.3 11.3.1 11.3.2 11.3.3 11.3.4 11.4 11.4.1 11.4.2 11.5 11.5.1 11.5.2 11.5.3 11.6 11.6.1 11.6.2 11.7 11.7.1 11.7.2 11.7.3 11.8 11.8.3 11.8.4 11.8.5 11.8.6 11.9 11.9.1 11.9.2 11.9.3 12.0 12.0.1 12.0.2 12.1 12.1.1 12.1.2 12.2 12.2.1 12.2.2 12.3 12.3.1 12.4 12.4.1 12.5 12.5.1 12.6 12.6.1 12.6.2 12.6.3 12.7 12.7.1 12.7.2 12.8 12.8.1 12.8.2 12.9 12.9.1 12.9.2 12.9.3 12.9.4 13.0 13.0.1 13.1 13.1.1 13.1.2 13.1.3 13.1.4 13.2 13.2.1 13.2.2 13.2.3 13.3 13.3.1 13.3.2 13.4 13.4.1 13.4.2 13.4.3 13.4.4 13.5 13.5.1 13.6 13.6.1 13.7 13.7.1 13.8 13.8.1 13.8.2 13.9 13.9.1 14.0 14.1 14.2 14.2.1 14.3 14.4 14.4.1 14.5 14.6 14.7 14.8 14.9 14.9.1 15.0 15.0.1 15.0.2 15.1 15.1.1 15.2 15.3 15.3.1 15.4 15.5 15.6 15.7 15.7-a.1 15.7-a.3 15.7-a.5 15.7-a.7 15.7-beta
jetpack / jetpack_vendor / automattic / jetpack-search / src / dashboard / class-initial-state.php
jetpack / jetpack_vendor / automattic / jetpack-search / src / dashboard Last commit date
class-dashboard.php 9 months ago class-initial-state.php 2 months ago
class-initial-state.php
479 lines
1 <?php
2 /**
3 * The React initial state.
4 *
5 * @package automattic/jetpack-search
6 */
7
8 namespace Automattic\Jetpack\Search;
9
10 use Automattic\Jetpack\Connection\Manager as Connection_Manager;
11 use Automattic\Jetpack\Status;
12 use Jetpack_Options;
13
14 /**
15 * The React initial state.
16 */
17 class Initial_State {
18
19 /**
20 * Connection Manager
21 *
22 * @var Connection_Manager
23 */
24 protected $connection_manager;
25
26 /**
27 * Search Module Control
28 *
29 * @var Module_Control
30 */
31 protected $module_control;
32
33 /**
34 * Constructor
35 *
36 * @param Connection_Manager $connection_manager - Connection mananger instance.
37 * @param Module_Control $module_control - Module control instance.
38 */
39 public function __construct( $connection_manager = null, $module_control = null ) {
40 $this->connection_manager = $connection_manager ? $connection_manager : new Connection_Manager( Package::SLUG );
41 $this->module_control = $module_control ? $module_control : new Module_Control();
42 }
43
44 /**
45 * Render JS for the initial state
46 *
47 * @return string - JS string.
48 */
49 public function render() {
50 return 'var JETPACK_SEARCH_DASHBOARD_INITIAL_STATE=' . wp_json_encode( $this->get_initial_state(), JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP ) . ';';
51 }
52
53 /**
54 * Get the initial state data.
55 *
56 * @return array
57 */
58 public function get_initial_state() {
59 return array(
60 'siteData' => array(
61 'WP_API_root' => esc_url_raw( rest_url() ),
62 'wpcomOriginApiUrl' => $this->get_wp_api_root(),
63 'WP_API_nonce' => wp_create_nonce( 'wp_rest' ),
64 'registrationNonce' => wp_create_nonce( 'jetpack-registration-nonce' ),
65 'purchaseToken' => $this->get_purchase_token(),
66 /**
67 * Whether promotions are visible or not.
68 *
69 * @param bool $are_promotions_active Status of promotions visibility. True by default.
70 */
71 'showPromotions' => apply_filters( 'jetpack_show_promotions', true ),
72 'adminUrl' => esc_url( admin_url() ),
73 'readerChatGuidelinesUrl' => $this->get_reader_chat_guidelines_url(),
74 'aiAgentAccessAvailable' => $this->is_ai_agent_access_available(),
75 'aiAgentAccessGuidelinesUrl' => $this->get_ai_agent_access_guidelines_url(),
76 'blogId' => Jetpack_Options::get_option( 'id', 0 ),
77 'version' => Package::VERSION,
78 'calypsoSlug' => ( new Status() )->get_site_suffix(),
79 'title' => get_bloginfo( 'name' ),
80 'postTypes' => $this->get_post_types_with_labels(),
81 'isWpcom' => Helper::is_wpcom(),
82 // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
83 'isPlanJustUpgraded' => isset( $_GET['just_upgraded'] ) && wp_unslash( $_GET['just_upgraded'] ),
84 /**
85 * Whether the Jetpack Search 3.0 Interactivity API blocks are enabled.
86 * Mirrors the `jetpack_search_blocks_enabled` server-side filter so the
87 * dashboard React app can gate the new feature-selection UI on the
88 * same flag the back end uses to register the blocks themselves.
89 */
90 'searchBlocksEnabled' => (bool) apply_filters( 'jetpack_search_blocks_enabled', true ),
91 /**
92 * Whether the experimental blocks-powered Overlay search experience
93 * is available in the Experience Selector. Mirrors the
94 * `jetpack_search_overlay_block_template_enabled` server-side filter
95 * so the dashboard React app can gate the new card on the same flag
96 * the back end uses to enable the runtime swap. Defaults to true so
97 * the Beta card ships to every site; operators that pin the filter
98 * to false fall back to the original four-card selector.
99 */
100 'blockOverlayEnabled' => (bool) apply_filters( 'jetpack_search_overlay_block_template_enabled', true ),
101 /**
102 * Editor affordances for the experimental blocks-powered overlay.
103 * Surfaces in the new Overlay search card so admins can edit the
104 * rendered template via the standard block editor on `post.php` —
105 * works on both block and classic themes. URLs are null for any
106 * visitor without `manage_options`; the action handlers also
107 * enforce that capability server-side.
108 */
109 'blockTemplateOverlay' => $this->get_block_template_overlay_config(),
110 /**
111 * Same as `blockTemplateOverlay` but for the WooCommerce product
112 * variant of the overlay — the Overlay card surfaces a second
113 * "Edit the product Search overlay" entry on Woo stores so the
114 * product-search overlay template is customizable too.
115 */
116 'productOverlayTemplate' => $this->get_product_overlay_template_config(),
117 /**
118 * Editor affordances for the classic-theme search-template
119 * singleton CPT. The Embedded card surfaces these on
120 * classic themes (which can't reach the Site Editor) so
121 * admins still get an "Edit search template" entry — same
122 * shape and same React link as `blockTemplateOverlay`.
123 */
124 'searchTemplate' => $this->get_search_template_config(),
125 /**
126 * Same as `searchTemplate` but for the WooCommerce product
127 * search shim — the `WooCommerceProductSearchControl`
128 * surfaces this on classic themes so the "Edit the product
129 * search template" link routes to `post.php` on the hidden
130 * CPT instead of a useless Site Editor URL.
131 */
132 'productSearchTemplate' => $this->get_product_search_template_config(),
133 // Gates the WooCommerce Product Search control to stores.
134 'isWooCommerceActive' => Search_Blocks::woocommerce_blocks_enabled(),
135 /**
136 * Active theme stylesheet — used by the experience-selector to deep-link
137 * the "Edit search template" action to the right Site Editor entry
138 * (`?p=/wp_template/<stylesheet>//jetpack-search`).
139 */
140 'activeThemeStylesheet' => get_stylesheet(),
141 /**
142 * Whether the active theme is a block theme. Embedded itself
143 * works on every theme — block themes through the FSE
144 * `search_template_hierarchy` route, classic themes through
145 * the singleton-CPT shim — but the Embedded card's
146 * customization affordances diverge: block themes get the
147 * Site-Editor entry points ("Edit search template" / "Insert
148 * pattern"), classic themes get the block-editor-on-a-hidden-
149 * CPT path via `searchTemplate`. This flag is the dashboard's
150 * branch selector for which to render.
151 */
152 'themeSupportsBlocks' => wp_is_block_theme(),
153 ),
154 'userData' => array(
155 'currentUser' => $this->current_user_data(),
156 ),
157 'jetpackSettings' => array(
158 'search' => $this->module_control->is_active(),
159 'instant_search_enabled' => $this->module_control->is_instant_search_enabled(),
160 'experience' => $this->module_control->get_experience(),
161 ),
162 'features' => array_map(
163 'sanitize_text_field',
164 // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
165 isset( $_GET['features'] ) ? explode( ',', wp_unslash( $_GET['features'] ) ) : array()
166 ),
167 );
168 }
169
170 /**
171 * Get API root.
172 *
173 * It return first party API root for WPCOM simple sites.
174 */
175 protected function get_wp_api_root() {
176 if ( ! Helper::is_wpcom() ) {
177 return esc_url_raw( rest_url() );
178 }
179 // First party API prefix for WPCOM.
180 return esc_url_raw( site_url( '/wp-json/wpcom-origin/' ) );
181 }
182
183 /**
184 * Get the Reader Chat guidelines admin page URL when it is registered.
185 *
186 * The guidelines page is controlled outside Jetpack. Returning an empty
187 * URL lets the dashboard hide the link when that experiment is unavailable.
188 *
189 * @return string Guidelines admin URL, or an empty string when unavailable.
190 */
191 protected function get_reader_chat_guidelines_url() {
192 return $this->get_guidelines_url();
193 }
194
195 /**
196 * Get the AI Agent Access guidelines admin page URL when it is registered.
197 *
198 * The guidelines page is controlled outside Jetpack. Returning an empty
199 * URL lets the dashboard hide the link when that page is unavailable.
200 *
201 * @return string Guidelines admin URL, or an empty string when unavailable.
202 */
203 protected function get_ai_agent_access_guidelines_url() {
204 return $this->get_guidelines_url();
205 }
206
207 /**
208 * Get the Guidelines admin page URL when it is registered.
209 *
210 * @return string Guidelines admin URL, or an empty string when unavailable.
211 */
212 protected function get_guidelines_url() {
213 if ( ! function_exists( 'menu_page_url' ) ) {
214 return '';
215 }
216
217 return esc_url_raw( menu_page_url( 'guidelines-wp-admin', false ) );
218 }
219
220 /**
221 * Check whether the AI Agent Access toggle should be available.
222 *
223 * Private sites are not eligible because external AI agents cannot read
224 * their public content.
225 *
226 * @return bool
227 */
228 protected function is_ai_agent_access_available() {
229 return ! $this->is_private_site();
230 }
231
232 /**
233 * Build the block-template overlay editor config exposed to the dashboard.
234 *
235 * `enabled` mirrors `Search_Blocks::is_block_template_overlay_enabled()`
236 * — true only when the user is currently *on* the blocks Overlay arm.
237 * The editor URLs, by contrast, are gated on the operator filter alone
238 * so admins can edit the template from the Beta card even before
239 * activating it (otherwise the React store, computed at page load
240 * before any switch, would carry a null `editorUrl` and the link would
241 * become a no-op once the card flipped to Active without a refresh).
242 *
243 * @return array{enabled: bool, editorUrl: string|null, postType: string|null, isCustomized: bool}
244 */
245 protected function get_block_template_overlay_config(): array {
246 return $this->build_singleton_template_config(
247 Search_Blocks::is_block_template_overlay_enabled(),
248 Search_Blocks::is_block_template_overlay_filter_on() && current_user_can( 'manage_options' ),
249 Overlay_Template::class
250 );
251 }
252
253 /**
254 * Build the product-overlay editor config exposed to the dashboard.
255 * Counterpart of `get_block_template_overlay_config()` for the WooCommerce
256 * product variant. `enabled` adds the override-on + WC check on top of the
257 * overlay-arm gate — it signals the product overlay's front-end render path
258 * is actually wired. `$can_edit` mirrors the init gate: the editable CPT
259 * only registers on Woo stores with the overlay filter on (see
260 * `Search_Blocks::init()`), so exposing the editor URL off Woo would produce
261 * a link that silently does nothing.
262 *
263 * @return array{enabled: bool, editorUrl: string|null, postType: string|null, isCustomized: bool}
264 */
265 protected function get_product_overlay_template_config(): array {
266 $wc_enabled = Search_Blocks::woocommerce_blocks_enabled();
267 return $this->build_singleton_template_config(
268 Search_Blocks::is_block_template_overlay_enabled()
269 && $wc_enabled
270 && Search_Blocks::woocommerce_search_template_override_enabled(),
271 Search_Blocks::is_block_template_overlay_filter_on()
272 && $wc_enabled
273 && current_user_can( 'manage_options' ),
274 Product_Overlay_Template::class
275 );
276 }
277
278 /**
279 * Build the classic-theme search-template editor config exposed to the
280 * dashboard. Counterpart of `get_block_template_overlay_config()` —
281 * same `{enabled, editorUrl, postType, isCustomized}` shape and the
282 * same "expose URLs before activation" rule: admins can edit the
283 * template from the Embedded card on any classic theme, even before
284 * actually switching to Embedded.
285 *
286 * @return array{enabled: bool, editorUrl: string|null, postType: string|null, isCustomized: bool}
287 */
288 protected function get_search_template_config(): array {
289 $is_classic = ! wp_is_block_theme();
290 return $this->build_singleton_template_config(
291 $is_classic && Module_Control::EXPERIENCE_EMBEDDED === $this->module_control->get_experience(),
292 $is_classic && current_user_can( 'manage_options' ),
293 Search_Template::class
294 );
295 }
296
297 /**
298 * Build the classic-theme product-search-template editor config exposed
299 * to the dashboard. Counterpart of `get_search_template_config()` for the
300 * WooCommerce product shim — same `{enabled, editorUrl, postType, isCustomized}`
301 * shape. `enabled` adds the override-on check on top of the classic +
302 * server-rendered-experience gate — it signals "the front-end render path
303 * is actually wired", distinct from "the override toggle is on" in
304 * `jetpackSettings.override_woocommerce_search_template`. `$can_edit`
305 * mirrors the init gate: `Product_Search_Template::init()` (in
306 * `Search_Blocks::init()`) registers `maybe_handle_editor_request` on
307 * classic Embedded **and** classic Inline (both route the product shim),
308 * so the editor URL surfaces for both — but not on a block theme, where
309 * the Site Editor link is used instead.
310 *
311 * @return array{enabled: bool, editorUrl: string|null, postType: string|null, isCustomized: bool}
312 */
313 protected function get_product_search_template_config(): array {
314 $experience = $this->module_control->get_experience();
315 $is_classic = ! wp_is_block_theme();
316 // Embedded and Inline both route the classic product shim, so both
317 // expose the CPT editor.
318 $is_classic_product_override = $is_classic
319 && in_array(
320 $experience,
321 array( Module_Control::EXPERIENCE_EMBEDDED, Module_Control::EXPERIENCE_INLINE ),
322 true
323 );
324 return $this->build_singleton_template_config(
325 $is_classic_product_override && Search_Blocks::woocommerce_search_template_override_enabled(),
326 $is_classic_product_override && current_user_can( 'manage_options' ),
327 Product_Search_Template::class
328 );
329 }
330
331 /**
332 * Shared assembly for a {@see Singleton_Template_Cpt}-backed editor
333 * config block. Both `blockTemplateOverlay` and `searchTemplate` ship
334 * the same `{enabled, editorUrl, postType, isCustomized}` shape to
335 * the React dashboard. The `$enabled` and `$can_edit` flags are
336 * intentionally separate: the card lights up as "active" on
337 * `$enabled`, but the URLs / `postType` surface as soon as `$can_edit`
338 * is true so admins can pre-customize the template before activating
339 * the experience — without forcing a page reload after the switch.
340 *
341 * `postType` (rather than a pre-built REST path) is what the dashboard
342 * needs: the React "Restore default" handler builds the
343 * `${wpcomOriginApiUrl}jetpack/v4/search/templates/<post_type>` URL
344 * itself, which is how the request reaches the right route on wpcom
345 * Simple sites (the local /wp-json/ surface there doesn't expose
346 * Jetpack routes). The handler re-checks `manage_options` server-side,
347 * so omitting the URLs / postType here just keeps the wire payload
348 * tight for non-admins.
349 *
350 * @param bool $enabled Whether the CPT-backed route is currently live on the site.
351 * @param bool $can_edit Whether to expose the editor URL + postType (operator-level gate + capability).
352 * @param string $cpt_class Concrete `Singleton_Template_Cpt` subclass to query.
353 * @return array{enabled: bool, editorUrl: string|null, postType: string|null, isCustomized: bool}
354 */
355 protected function build_singleton_template_config( bool $enabled, bool $can_edit, string $cpt_class ): array {
356 return array(
357 'enabled' => $enabled,
358 'editorUrl' => $can_edit ? $cpt_class::get_editor_url() : null,
359 'postType' => $can_edit ? $cpt_class::POST_TYPE : null,
360 'isCustomized' => $can_edit && $cpt_class::is_customized(),
361 );
362 }
363
364 /**
365 * Check whether the current site is private.
366 *
367 * @return bool
368 */
369 protected function is_private_site() {
370 if ( function_exists( 'is_private_blog' ) ) {
371 return (bool) \is_private_blog();
372 }
373
374 return -1 === (int) get_option( 'blog_public', 1 );
375 }
376
377 /**
378 * Gather data about the current user.
379 *
380 * @return array
381 */
382 protected function current_user_data() {
383 $current_user = wp_get_current_user();
384 $is_user_connected = $this->connection_manager->is_user_connected( $current_user->ID );
385 $is_master_user = $is_user_connected && (int) $current_user->ID && (int) Jetpack_Options::get_option( 'master_user' ) === (int) $current_user->ID;
386 $dotcom_data = $this->connection_manager->get_connected_user_data();
387
388 $current_user_data = array(
389 'isConnected' => $is_user_connected,
390 'isMaster' => $is_master_user,
391 'username' => $current_user->user_login,
392 'id' => $current_user->ID,
393 'wpcomUser' => $dotcom_data,
394 'permissions' => array(
395 'manage_options' => current_user_can( 'manage_options' ),
396 ),
397 );
398
399 return $current_user_data;
400 }
401
402 /**
403 * Gets the post type labels for all of the site's post types (including custom post types)
404 *
405 * @return array
406 */
407 protected function get_post_types_with_labels() {
408
409 $args = array(
410 'public' => true,
411 );
412
413 $post_types_with_labels = array();
414
415 $post_types = get_post_types( $args, 'objects' );
416
417 // We don't need all the additional post_type data, just the slug & label
418 foreach ( $post_types as $post_type ) {
419 $post_type_with_label = array(
420 'slug' => $post_type->name,
421 'label' => $post_type->label,
422 );
423
424 $post_types_with_labels[ $post_type->name ] = $post_type_with_label;
425 }
426 return $post_types_with_labels;
427 }
428
429 /**
430 * Gets a purchase token that is used for Jetpack logged out visitor checkout.
431 * The purchase token should be appended to all CTA url's that lead to checkout.
432 *
433 * @return string|boolean
434 */
435 protected function get_purchase_token() {
436 if ( ! $this->current_user_can_purchase() ) {
437 return false;
438 }
439
440 $purchase_token = Jetpack_Options::get_option( 'purchase_token', false );
441
442 if ( $purchase_token ) {
443 return $purchase_token;
444 }
445 // If the purchase token is not saved in the options table yet, then add it.
446 Jetpack_Options::update_option( 'purchase_token', $this->generate_purchase_token(), true );
447 return Jetpack_Options::get_option( 'purchase_token', false );
448 }
449
450 /**
451 * Generates a purchase token that is used for Jetpack logged out visitor checkout.
452 *
453 * @return string
454 */
455 protected function generate_purchase_token() {
456 return wp_generate_password( 12, false );
457 }
458
459 /**
460 * Determine if the current user is allowed to make Jetpack purchases without
461 * a WordPress.com account
462 *
463 * @return boolean True if the user can make purchases, false if not
464 */
465 public function current_user_can_purchase() {
466 // The site must be site-connected to Jetpack (no users connected).
467 if ( ! $this->connection_manager->is_site_connection() ) {
468 return false;
469 }
470
471 // Make sure only administrators can make purchases.
472 if ( ! current_user_can( 'manage_options' ) ) {
473 return false;
474 }
475
476 return true;
477 }
478 }
479