PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3
Jetpack – WP Security, Backup, Speed, & Growth v16.3
16.3 16.3-beta 16.3-a.5 16.3-a.7 16.3-a.3 16.3-a.1 16.2 16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 All 508 releases
← All changes | sal/class.json-api-site-base.php +1909 -0 16.2-beta → 16.3 View file →
@@ -1,0 +1,1909 @@
1 +<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
2 +/**
3 + * This file defines the base class for the Site Abstraction Layer (SAL).
4 + * Note that this is the site "as seen by user $user_id with token $token", which
5 + * is why we pass the token to the platform; these site instances are value objects
6 + * to be used in the context of a single request for a single user.
7 + * Also note that at present this class _assumes_ you've "switched to"
8 + * the site in question, and functions like `get_bloginfo( 'name' )` will
9 + * therefore return the correct value.
10 + *
11 + * @package automattic/jetpack
12 + **/
13 +
14 +use Automattic\Jetpack\Blaze;
15 +use Automattic\Jetpack\Status;
16 +use Automattic\Jetpack\Status\Host;
17 +
18 +if ( ! defined( 'ABSPATH' ) ) {
19 + exit( 0 );
20 +}
21 +
22 +require_once __DIR__ . '/class.json-api-date.php';
23 +require_once __DIR__ . '/class.json-api-post-base.php';
24 +
25 +/**
26 + * Base class for SAL_Site.
27 + * The abstract functions here are extended by Abstract_Jetpack_Site in class.json-api-site-jetpack-base.php.
28 + */
29 +abstract class SAL_Site {
30 +
31 + /**
32 + * The Jetpack blog ID for the site.
33 + *
34 + * @var int
35 + */
36 + public $blog_id;
37 +
38 + /**
39 + * A new WPORG_Platform instance.
40 + *
41 + * @see class.json-api-platform-jetpack.php.
42 + *
43 + * @var WPORG_Platform
44 + */
45 + public $platform;
46 +
47 + /**
48 + * Contructs the SAL_Site instance.
49 + *
50 + * @param int $blog_id The Jetpack blog ID for the site.
51 + * @param WPORG_Platform $platform A new WPORG_Platform instance.
52 + */
53 + public function __construct( $blog_id, $platform ) {
54 + $this->blog_id = $blog_id;
55 + $this->platform = $platform;
56 + }
57 +
58 + /**
59 + * Get the blog_id property.
60 + *
61 + * @return int
62 + */
63 + public function get_id() {
64 + return $this->blog_id;
65 + }
66 +
67 + /**
68 + * Returns the site slug.
69 + *
70 + * @return string
71 + */
72 + public function get_slug() {
73 + return ( new Status() )->get_site_suffix();
74 + }
75 +
76 + /**
77 + * Returns the site name.
78 + *
79 + * @return string
80 + */
81 + public function get_name() {
82 + $name = get_bloginfo( 'name' );
83 + return is_string( $name ) ? htmlspecialchars_decode( $name, ENT_QUOTES ) : '';
84 + }
85 +
86 + /**
87 + * Returns the site description.
88 + *
89 + * @return string
90 + */
91 + public function get_description() {
92 + $description = get_bloginfo( 'description' );
93 + return is_string( $description ) ? htmlspecialchars_decode( $description, ENT_QUOTES ) : '';
94 + }
95 +
96 + /**
97 + * Returns the URL for the current site.
98 + *
99 + * @return string
100 + */
101 + public function get_url() {
102 + return (string) home_url();
103 + }
104 +
105 + /**
106 + * Returns the number of published posts with the 'post' post-type.
107 + *
108 + * @return int
109 + */
110 + public function get_post_count() {
111 + return (int) wp_count_posts( 'post' )->publish;
112 + }
113 +
114 + /**
115 + * A prototype function for get_quota - currently returns null.
116 + *
117 + * @return null
118 + */
119 + public function get_quota() {
120 + return null;
121 + }
122 +
123 + /**
124 + * Returns an array of blogging prompt settings. Only applicable on WordPress.com.
125 + *
126 + * Data comes from .com since the fearture requires a .com connection to work.
127 + *
128 + * @param int $user_id the current user_id.
129 + * @param int $blog_id the blog id in this context.
130 + */
131 + public function get_blogging_prompts_settings( $user_id, $blog_id ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
132 + return false;
133 + }
134 +
135 + /**
136 + * Returns true if a site has the 'videopress' option enabled, false otherwise.
137 + *
138 + * @see class.json-api-site-jetpack.php for implementation.
139 + */
140 + abstract public function has_videopress();
141 +
142 + /**
143 + * Returns VideoPress storage used, in MB.
144 + *
145 + * @see class.json-api-site-jetpack-shadow.php on WordPress.com for implementation. Only applicable on WordPress.com.
146 + */
147 + abstract public function get_videopress_storage_used();
148 +
149 + /**
150 + * Sets the upgraded_filetypes_enabled Jetpack option to true as a default. Only relevant for WordPress.com sites.
151 + *
152 + * @see class.json-api-site-jetpack.php for implementation.
153 + */
154 + abstract public function upgraded_filetypes_enabled();
155 +
156 + /**
157 + * Sets the is_mapped_domain Jetpack option to true as a default.
158 + *
159 + * Primarily used in WordPress.com to confirm the current blog's domain does or doesn't match the primary redirect.
160 + *
161 + * @see class.json-api-site-jetpack.php for implementation.
162 + */
163 + abstract public function is_mapped_domain();
164 +
165 + /**
166 + * Fallback to the home URL since all Jetpack sites don't have an unmapped *.wordpress.com domain.
167 + *
168 + * @see class.json-api-site-jetpack.php for implementation.
169 + */
170 + abstract public function get_unmapped_url();
171 +
172 + /**
173 + * Whether the domain is a site redirect or not. Defaults to false on a Jetpack site.
174 + *
175 + * Primarily used in WordPress.com where it is determined if a HTTP status check is a redirect or not and whether an exception should be thrown.
176 + *
177 + * @see class.json-api-site-jetpack.php for implementation.
178 + */
179 + abstract public function is_redirect();
180 +
181 + /**
182 + * Defaults to false on Jetpack sites, however is used on WordPress.com sites, where it returns true if the headstart-fresh blog sticker is present.
183 + *
184 + * @see class.json-api-site-jetpack.php for implementation.
185 + */
186 + abstract public function is_headstart_fresh();
187 +
188 + /**
189 + * If the site's current theme supports post thumbnails, return true (otherwise return false).
190 + *
191 + * @see class.json-api-site-jetpack-base.php for implementation.
192 + */
193 + abstract public function featured_images_enabled();
194 +
195 + /**
196 + * Whether or not the Jetpack 'wordads' module is active on the site.
197 + *
198 + * @see class.json-api-site-jetpack.php for implementation.
199 + */
200 + abstract public function has_wordads();
201 +
202 + /**
203 + * Defaults to false on Jetpack sites, however is used on WordPress.com sites. This nonce is used for previews on Jetpack sites.
204 + *
205 + * @see class.json-api-site-jetpack.php for implementation.
206 + */
207 + abstract public function get_frame_nonce();
208 +
209 + /**
210 + * Defaults to false on Jetpack sites, however is used on WordPress.com sites where
211 + * it creates a nonce to be used with iframed block editor requests to a Jetpack site.
212 + *
213 + * @see class.json-api-site-jetpack.php for implementation.
214 + */
215 + abstract public function get_jetpack_frame_nonce();
216 +
217 + /**
218 + * Returns the allowed mime types and file extensions for a site.
219 + *
220 + * @see class.json-api-site-jetpack.php for implementation.
221 + */
222 + abstract public function allowed_file_types();
223 +
224 + /**
225 + * Returns an array of supported post formats.
226 + *
227 + * @see class.json-api-site-jetpack-base.php for implementation.
228 + */
229 + abstract public function get_post_formats();
230 +
231 + /**
232 + * Return site's privacy status.
233 + *
234 + * @see class.json-api-site-jetpack.php for implementation.
235 + */
236 + abstract public function is_private();
237 +
238 + /**
239 + * Return site's coming soon status.
240 + *
241 + * @see class.json-api-site-jetpack.php for implementation.
242 + */
243 + abstract public function is_coming_soon();
244 +
245 + /**
246 + * Whether or not the current user is following this blog. Defaults to false.
247 + *
248 + * @see class.json-api-site-jetpack.php for implementation.
249 + */
250 + abstract public function is_following();
251 +
252 + /**
253 + * Defaults to 0 for the number of WordPress.com subscribers - this is filled in on the WordPress.com side.
254 + *
255 + * @see class.json-api-site-jetpack.php for implementation.
256 + */
257 + abstract public function get_subscribers_count();
258 +
259 + /**
260 + * Returns the language code for the current site.
261 + *
262 + * @see class.json-api-site-jetpack.php for implementation.
263 + */
264 + abstract public function get_locale();
265 +
266 + /**
267 + * The flag indicates that the site has Jetpack installed.
268 + *
269 + * @see class.json-api-site-jetpack.php for implementation.
270 + */
271 + abstract public function is_jetpack();
272 +
273 + /**
274 + * The flag indicates that the site is connected to WP.com via Jetpack Connection.
275 + *
276 + * @see class.json-api-site-jetpack.php for implementation.
277 + */
278 + abstract public function is_jetpack_connection();
279 +
280 + /**
281 + * This function returns the values of any active Jetpack modules.
282 + *
283 + * @see class.json-api-site-jetpack-base.php for implementation.
284 + */
285 + abstract public function get_jetpack_modules();
286 +
287 + /**
288 + * This function returns true if a specified Jetpack module is active, false otherwise.
289 + *
290 + * @see class.json-api-site-jetpack-base.php for implementation.
291 + *
292 + * @param string $module The Jetpack module name to check.
293 + */
294 + abstract public function is_module_active( $module );
295 +
296 + /**
297 + * This function returns false for a check as to whether a site is a VIP site or not.
298 + *
299 + * @see class.json-api-site-jetpack-base.php for implementation.
300 + */
301 + abstract public function is_vip();
302 +
303 + /**
304 + * Returns true if Multisite is enabled, false otherwise.
305 + *
306 + * @see class.json-api-site-jetpack.php for implementation.
307 + */
308 + abstract public function is_multisite();
309 +
310 + /**
311 + * Points to the user ID of the site owner
312 + *
313 + * @see class.json-api-site-jetpack.php for implementation.
314 + */
315 + abstract public function get_site_owner();
316 +
317 + /**
318 + * Returns true if the current site is a single user site, false otherwise.
319 + *
320 + * @see class.json-api-site-jetpack.php for implementation.
321 + */
322 + abstract public function is_single_user_site();
323 +
324 + /**
325 + * Defaults to false instead of returning the current site plan.
326 + *
327 + * @see class.json-api-site-jetpack.php for implementation.
328 + */
329 + abstract public function get_plan();
330 +
331 + /**
332 + * Empty function declaration - this function is filled out on the WordPress.com side, returning true if the site has an AK / VP bundle.
333 + *
334 + * @see class.json-api-site-jetpack.php and /wpcom/public.api/rest/sal/class.json-api-site-jetpack-shadow.php.
335 + */
336 + abstract public function get_ak_vp_bundle_enabled();
337 +
338 + /**
339 + * Returns null for Jetpack sites. For WordPress.com sites this returns the value of the 'podcasting_archive' option.
340 + *
341 + * @see class.json-api-site-jetpack.php for implementation.
342 + */
343 + abstract public function get_podcasting_archive();
344 +
345 + /**
346 + * Return the last engine used for an import on the site. Not used in Jetpack.
347 + *
348 + * @see class.json-api-site-jetpack.php for implementation.
349 + */
350 + abstract public function get_import_engine();
351 +
352 + /**
353 + * Returns the front page meta description for current site.
354 + *
355 + * @see class.json-api-site-jetpack.php for implementation.
356 + */
357 + abstract public function get_jetpack_seo_front_page_description();
358 +
359 + /**
360 + * Returns custom title formats from site option.
361 + *
362 + * @see class.json-api-site-jetpack.php for implementation.
363 + */
364 + abstract public function get_jetpack_seo_title_formats();
365 +
366 + /**
367 + * Returns website verification codes. Allowed keys include: google, pinterest, bing, yandex, facebook.
368 + *
369 + * @see class.json-api-site-jetpack.php for implementation.
370 + */
371 + abstract public function get_verification_services_codes();
372 +
373 + /**
374 + * This function is implemented on WPCom sites, where a filter is removed which forces the URL to http.
375 + *
376 + * @see class.json-api-site-jetpack-base.php and /wpcom/public.api/rest/sal/class.json-api-site-jetpack-shadow.php.
377 + */
378 + abstract public function before_render();
379 +
380 + /**
381 + * If a user has manage options permissions and the site is the main site of the network, make updates visible.
382 + *
383 + * Called after response_keys have been rendered, which itself is used to return all the necessary information for a site’s response.
384 + *
385 + * @see class.json-api-site-jetpack-base.php for implementation.
386 + *
387 + * @param array $response an array of the response keys.
388 + */
389 + abstract public function after_render( &$response );
390 +
391 + /**
392 + * Extends the Jetpack options array with details including site constraints, WordPress and Jetpack versions, and plugins using the Jetpack connection.
393 + *
394 + * @see class.json-api-site-jetpack-base.php for implementation.
395 + * @todo factor this out? Seems an odd thing to have on a site
396 + *
397 + * @param array $options an array of the Jetpack options.
398 + */
399 + abstract public function after_render_options( &$options );
400 +
401 + /**
402 + * Wrap a WP_Post object with SAL methods, returning a Jetpack_Post object.
403 + *
404 + * @see class.json-api-site-jetpack.php for implementation.
405 + *
406 + * @param WP_Post $post A WP_Post object.
407 + * @param string $context The post request context (for example 'edit' or 'display').
408 + */
409 + abstract public function wrap_post( $post, $context );
410 +
411 + /**
412 + * For Jetpack sites this will always return false.
413 + *
414 + * @see class.json-api-site-jetpack-base.php for implementation.
415 + *
416 + * @param int $post_id The post id.
417 + */
418 + abstract protected function is_a8c_publication( $post_id );
419 +
420 + /**
421 + * Return the user interactions with a site. Not used in Jetpack.
422 + *
423 + * @see class.json-api-site-jetpack.php for implementation.
424 + */
425 + abstract public function get_user_interactions();
426 +
427 + /**
428 + * Flag a site as deleted. Not used in Jetpack.
429 + *
430 + * @see class.json-api-site-jetpack.php for implementation.
431 + */
432 + abstract public function is_deleted();
433 +
434 + /**
435 + * Indicates that a site is an A4A client. Not used in Jetpack.
436 + *
437 + * @see class.json-api-site-jetpack.php for implementation.
438 + */
439 + abstract public function is_a4a_client();
440 +
441 + /**
442 + * Indicates that a site is an A4A dev site.
443 + *
444 + * @return bool
445 + */
446 + public function is_a4a_dev_site() {
447 + if ( function_exists( 'has_blog_sticker' ) ) {
448 + return has_blog_sticker( 'a4a-is-dev-site' );
449 + }
450 + return false;
451 + }
452 +
453 + /**
454 + * Return the user interactions with a site. Not used in Jetpack.
455 + *
456 + * @param string $role The capability to check.
457 + * @return bool
458 + * @see class.json-api-site-jetpack.php for implementation.
459 + * @see class.json-api-site-wpcom.php (on WPCOM) for Simple-site implementation.
460 + * @see class.json-api-site-jetpack-shadow.php (on WPCOM) for Atomic-site implementation.
461 + */
462 + abstract public function current_user_can( $role );
463 +
464 + /**
465 + * Defines a filter to set whether a site is an automated_transfer site or not.
466 + *
467 + * Default is false.
468 + *
469 + * @return bool
470 + */
471 + public function is_automated_transfer() {
472 + /**
473 + * Filter if a site is an automated-transfer site.
474 + *
475 + * @module json-api
476 + *
477 + * @since 6.4.0
478 + *
479 + * @param bool is_automated_transfer( $this->blog_id )
480 + * @param int $blog_id Blog identifier.
481 + */
482 + return apply_filters(
483 + 'jetpack_site_automated_transfer',
484 + false,
485 + $this->blog_id
486 + );
487 + }
488 +
489 + /**
490 + * Defaulting to false and not relevant for Jetpack sites, this is expanded on the WordPress.com side for a specific wp.com/start 'WP for teams' flow.
491 + *
492 + * @see class.json-api-site-jetpack.php for implementation.
493 + */
494 + abstract public function is_wpforteams_site();
495 +
496 + /**
497 + * Get hub blog id for P2 sites.
498 + *
499 + * @return null
500 + */
501 + public function get_p2_hub_blog_id() {
502 + return null;
503 + }
504 +
505 + /**
506 + * Getter for the p2 organization ID.
507 + *
508 + * @return int
509 + */
510 + public function get_p2_organization_id() {
511 + return 0; // WPForTeams\Constants\NO_ORG_ID not loaded.
512 + }
513 +
514 + /**
515 + * Get details used to render a thumbnail of the site. P2020 themed sites only.
516 + *
517 + * @return ?array
518 + */
519 + public function get_p2_thumbnail_elements() {
520 + return null;
521 + }
522 +
523 + /**
524 + * Detect whether a site is a WordPress.com on Atomic site.
525 + *
526 + * @return bool
527 + */
528 + public function is_wpcom_atomic() {
529 + return ( new Host() )->is_woa_site();
530 + }
531 +
532 + /**
533 + * Detect whether a site is an automated transfer site and WooCommerce is active.
534 + *
535 + * @see /wpcom/public.api/rest/sal/class.json-api-site-jetpack-shadow.php.
536 + *
537 + * @return bool - False for Jetpack-connected sites.
538 + */
539 + public function is_wpcom_store() {
540 + return false;
541 + }
542 +
543 + /**
544 + * Indicate whether this site was ever a specific trial.
545 + *
546 + * @param string $trial The trial type to check for.
547 + *
548 + * @return bool
549 + */
550 + public function was_trial( $trial ) {
551 + if ( function_exists( 'has_blog_sticker' ) ) {
552 + return has_blog_sticker( "had-{$trial}-trial" );
553 + }
554 + return false;
555 + }
556 +
557 + /**
558 + * Indicate whether this site was upgraded from a trial plan at some point.
559 + *
560 + * @return bool
561 + */
562 + public function was_upgraded_from_trial() {
563 + if ( function_exists( 'has_blog_sticker' ) ) {
564 + return has_blog_sticker( 'has-upgraded-from-ecommerce-trial' );
565 + }
566 + return false;
567 + }
568 +
569 + /**
570 + * Detect whether a site has the WooCommerce plugin active.
571 + *
572 + * @see /wpcom/public.api/rest/sal/class.json-api-site-jetpack-shadow.php.
573 + *
574 + * @return bool - Default false for Jetpack-connected sites.
575 + */
576 + public function woocommerce_is_active() {
577 + return false;
578 + }
579 +
580 + /**
581 + * Whether the Editing Toolkit plugin is active (relevant only on WordPress.com).
582 + *
583 + * @return true
584 + */
585 + public function editing_toolkit_is_active() {
586 + return true;
587 + }
588 +
589 + /**
590 + * Detect whether a site has access to the Jetpack cloud.
591 + *
592 + * @see /wpcom/public.api/rest/sal/class.json-api-site-jetpack-shadow.php.
593 + *
594 + * @return bool - Default false for Jetpack-connected sites.
595 + */
596 + public function is_cloud_eligible() {
597 + return false;
598 + }
599 +
600 + /**
601 + * Returns an array of WPCOM_Store products.
602 + *
603 + * @see /wpcom/public.api/rest/sal/class.json-api-site-jetpack-shadow.php.
604 + *
605 + * @return bool - Default empty array for Jetpack-connected sites.
606 + */
607 + public function get_products() {
608 + return array();
609 + }
610 +
611 + /**
612 + * Get post by ID
613 + *
614 + * @param int $post_id The ID of the post.
615 + * @param string $context The context by which the post data is required (display or edit).
616 + *
617 + * @return Jetpack_Post Post object on success, WP_Error object on failure
618 + **/
619 + public function get_post_by_id( $post_id, $context ) {
620 + $post = get_post( $post_id, OBJECT, $context );
621 +
622 + if ( ! $post ) {
623 + return new WP_Error( 'unknown_post', 'Unknown post', 404 );
624 + }
625 +
626 + $wrapped_post = $this->wrap_post( $post, $context );
627 + // validate access
628 + return $this->validate_access( $wrapped_post );
629 + }
630 +
631 + /**
632 + * Validate current user can access the post
633 + *
634 + * @param Jetpack_Post $post Post object.
635 + *
636 + * @return WP_Error|Jetpack_Post
637 + */
638 + private function validate_access( $post ) {
639 + $context = $post->context;
640 +
641 + if (
642 + ! $this->is_post_type_allowed( $post->post_type )
643 + && ! $this->is_a8c_publication( $post->ID )
644 + ) {
645 + return new WP_Error( 'unknown_post', 'Unknown post', 404 );
646 + }
647 +
648 + switch ( $context ) {
649 + case 'edit':
650 + if ( ! current_user_can( 'edit_post', $post->ID ) ) {
651 + return new WP_Error( 'unauthorized', 'User cannot edit post', 403 );
652 + }
653 + break;
654 + case 'display':
655 + $can_view = $this->user_can_view_post( $post );
656 + if ( is_wp_error( $can_view ) ) {
657 + return $can_view;
658 + }
659 + break;
660 + default:
661 + return new WP_Error( 'invalid_context', 'Invalid API CONTEXT', 400 );
662 + }
663 +
664 + return $post;
665 + }
666 +
667 + /**
668 + * Validate whether the current user can access the specified post type.
669 + *
670 + * @param string $post_type The post type to check.
671 + * @param string $context The context by which the post data is required (display or edit).
672 + *
673 + * @return bool
674 + */
675 + public function current_user_can_access_post_type( $post_type, $context ) {
676 + $post_type_object = $this->get_post_type_object( $post_type );
677 + if ( ! $post_type_object ) {
678 + return false;
679 + }
680 +
681 + switch ( $context ) {
682 + case 'edit':
683 + return current_user_can( $post_type_object->cap->edit_posts );
684 + case 'display':
685 + return $post_type_object->public || current_user_can( $post_type_object->cap->read_private_posts );
686 + default:
687 + return false;
688 + }
689 + }
690 +
691 + /**
692 + * Retrieves a post type object by name.
693 + *
694 + * @param string $post_type The post type to check.
695 + *
696 + * @return WP_Post_Type|null
697 + */
698 + protected function get_post_type_object( $post_type ) {
699 + return get_post_type_object( $post_type );
700 + }
701 +
702 + /**
703 + * Is the post type allowed?
704 + *
705 + * Function copied from class.json-api-endpoints.php.
706 + *
707 + * @param string $post_type Post type.
708 + *
709 + * @return bool
710 + */
711 + public function is_post_type_allowed( $post_type ) {
712 + // if the post type is empty, that's fine, WordPress will default to post
713 + if ( empty( $post_type ) ) {
714 + return true;
715 + }
716 +
717 + // allow special 'any' type
718 + if ( 'any' === $post_type ) {
719 + return true;
720 + }
721 +
722 + // check for allowed types
723 + if ( in_array( $post_type, $this->get_whitelisted_post_types(), true ) ) {
724 + return true;
725 + }
726 +
727 + $post_type_object = get_post_type_object( $post_type );
728 + if ( $post_type_object ) {
729 + if ( ! empty( $post_type_object->show_in_rest ) ) {
730 + return $post_type_object->show_in_rest;
731 + }
732 + if ( ! empty( $post_type_object->publicly_queryable ) ) {
733 + return $post_type_object->publicly_queryable;
734 + }
735 + }
736 +
737 + return ! empty( $post_type_object->public );
738 + }
739 +
740 + /**
741 + * Gets the whitelisted post types that JP should allow access to.
742 + *
743 + * Function copied from class.json-api-endpoints.php.
744 + *
745 + * @return array Whitelisted post types.
746 + */
747 + public function get_whitelisted_post_types() {
748 + $allowed_types = array( 'post', 'page', 'revision' );
749 +
750 + /**
751 + * Filter the post types Jetpack has access to, and can synchronize with WordPress.com.
752 + *
753 + * @module json-api
754 + *
755 + * @since 2.2.3
756 + *
757 + * @param array $allowed_types Array of whitelisted post types. Default to `array( 'post', 'page', 'revision' )`.
758 + */
759 + $allowed_types = apply_filters( 'rest_api_allowed_post_types', $allowed_types );
760 +
761 + return array_unique( $allowed_types );
762 + }
763 +
764 + /**
765 + * Can the user view the post?
766 + *
767 + * Function copied from class.json-api-endpoints.php and modified.
768 + *
769 + * @param Jetpack_Post $post Post object.
770 + * @return bool|WP_Error
771 + */
772 + private function user_can_view_post( $post ) {
773 + if ( ! $post || is_wp_error( $post ) ) {
774 + return false;
775 + }
776 + // If the post is of status inherit, check if the parent exists ( different to 0 ) to check for the parent status object.
777 + if ( 'inherit' === $post->post_status && 0 !== (int) $post->post_parent ) {
778 + $parent_post = get_post( $post->post_parent );
779 + $post_status_obj = $parent_post ? get_post_status_object( $parent_post->post_status ) : null;
780 + } else {
781 + $post_status_obj = get_post_status_object( $post->post_status );
782 + }
783 +
784 + $authorized = false;
785 +
786 + if ( $post_status_obj ) {
787 + $authorized = $post_status_obj->public
788 + || ( is_user_logged_in() && (
789 + ( $post_status_obj->protected && current_user_can( 'edit_post', $post->ID ) )
790 + || ( $post_status_obj->private && current_user_can( 'read_post', $post->ID ) )
791 + ) );
792 + }
793 +
794 + $authorized = $authorized || (
795 + ( 'trash' === $post->post_status && current_user_can( 'edit_post', $post->ID ) )
796 + || 'auto-draft' === $post->post_status
797 + );
798 +
799 + if ( ! $authorized ) {
800 + return new WP_Error( 'unauthorized', 'User cannot view post', 403 );
801 + }
802 +
803 + if (
804 + ( new Status() )->is_private_site() &&
805 + /**
806 + * Filter access to a specific post.
807 + *
808 + * @module json-api
809 + *
810 + * @since 3.4.0
811 + *
812 + * @param bool current_user_can( 'read_post', $post->ID ) Can the current user access the post.
813 + * @param WP_Post $post Post data.
814 + */
815 + ! apply_filters(
816 + 'wpcom_json_api_user_can_view_post',
817 + current_user_can( 'read_post', $post->ID ),
818 + $post
819 + )
820 + ) {
821 + return new WP_Error(
822 + 'unauthorized',
823 + 'User cannot view post',
824 + array(
825 + 'status_code' => 403,
826 + 'error' => 'private_blog',
827 + )
828 + );
829 + }
830 +
831 + if ( strlen( $post->post_password ) && ! current_user_can( 'edit_post', $post->ID ) ) {
832 + return new WP_Error(
833 + 'unauthorized',
834 + 'User cannot view password protected post',
835 + array(
836 + 'status_code' => 403,
837 + 'error' => 'password_protected',
838 + )
839 + );
840 + }
841 +
842 + return true;
843 + }
844 +
845 + /**
846 + * Get post ID by name
847 + *
848 + * Attempts to match name on post title and page path
849 + *
850 + * @param string $name The post name.
851 + *
852 + * @return int|WP_Error Post ID on success, WP_Error object on failure
853 + */
854 + public function get_post_id_by_name( $name ) {
855 + $name = sanitize_title( $name );
856 +
857 + if ( ! $name ) {
858 + return new WP_Error( 'invalid_post', 'Invalid post', 400 );
859 + }
860 +
861 + $posts = get_posts(
862 + array(
863 + 'name' => $name,
864 + 'numberposts' => 1,
865 + 'post_type' => $this->get_whitelisted_post_types(),
866 + )
867 + );
868 +
869 + if ( ! $posts || ! isset( $posts[0]->ID ) || ! $posts[0]->ID ) {
870 + $page = get_page_by_path( $name );
871 +
872 + if ( ! $page ) {
873 + return new WP_Error( 'unknown_post', 'Unknown post', 404 );
874 + }
875 +
876 + return $page->ID;
877 + }
878 +
879 + return (int) $posts[0]->ID;
880 + }
881 +
882 + /**
883 + * Get post by name
884 + *
885 + * Attempts to match name on post title and page path
886 + *
887 + * @param string $name The post name.
888 + * @param string $context (display or edit).
889 + *
890 + * @return Jetpack_Post|WP_Error Post object on success, WP_Error object on failure
891 + **/
892 + public function get_post_by_name( $name, $context ) {
893 + $post_id = $this->get_post_id_by_name( $name );
894 + if ( is_wp_error( $post_id ) ) {
895 + return $post_id;
896 + }
897 +
898 + return $this->get_post_by_id( $post_id, $context );
899 + }
900 +
901 + /**
902 + * Whether or not the current user is an admin (has option management capabilities).
903 + *
904 + * @return bool
905 + **/
906 + public function user_can_manage() {
907 + return current_user_can( 'manage_options' );
908 + }
909 +
910 + /**
911 + * Returns the XMLRPC URL - the site URL including the URL scheme that is used when querying your site's REST API endpoint.
912 + *
913 + * @return string
914 + **/
915 + public function get_xmlrpc_url() {
916 + $xmlrpc_scheme = apply_filters( 'wpcom_json_api_xmlrpc_scheme', wp_parse_url( get_option( 'home' ), PHP_URL_SCHEME ) );
917 + return site_url( 'xmlrpc.php', $xmlrpc_scheme );
918 + }
919 +
920 + /**
921 + * Returns a date/time string with the date the site was registered, or a default date/time string otherwise.
922 + *
923 + * @return string
924 + **/
925 + public function get_registered_date() {
926 + if ( function_exists( 'get_blog_details' ) ) {
927 + $blog_details = get_blog_details();
928 + if ( ! empty( $blog_details->registered ) ) {
929 + return WPCOM_JSON_API_Date::format_date( $blog_details->registered );
930 + }
931 + }
932 +
933 + return '0000-00-00T00:00:00+00:00';
934 + }
935 +
936 + /**
937 + * Returns a date/time string with the date the site was last updated, or a default date/time string otherwise.
938 + *
939 + * @return string
940 + **/
941 + public function get_last_update_date() {
942 + if ( function_exists( 'get_blog_details' ) ) {
943 + $blog_details = get_blog_details();
944 + if ( ! empty( $blog_details->last_updated ) ) {
945 + return WPCOM_JSON_API_Date::format_date( $blog_details->last_updated );
946 + }
947 + }
948 +
949 + return '0000-00-00T00:00:00+00:00';
950 + }
951 +
952 + /**
953 + * Returns an array including the current users relevant capabilities.
954 + *
955 + * @return array
956 + **/
957 + public function get_capabilities() {
958 + $is_wpcom_blog_owner = wpcom_get_blog_owner() === (int) get_current_user_id();
959 +
960 + return array(
961 + 'edit_pages' => $this->current_user_can( 'edit_pages' ),
962 + 'edit_posts' => $this->current_user_can( 'edit_posts' ),
963 + 'edit_others_posts' => $this->current_user_can( 'edit_others_posts' ),
964 + 'edit_others_pages' => $this->current_user_can( 'edit_others_pages' ),
965 + 'delete_posts' => $this->current_user_can( 'delete_posts' ),
966 + 'delete_others_posts' => $this->current_user_can( 'delete_others_posts' ),
967 + 'edit_theme_options' => $this->current_user_can( 'edit_theme_options' ),
968 + 'edit_users' => $this->current_user_can( 'edit_users' ),
969 + 'list_users' => $this->current_user_can( 'list_users' ),
970 + 'manage_categories' => $this->current_user_can( 'manage_categories' ),
971 + 'manage_options' => $this->current_user_can( 'manage_options' ),
972 + 'moderate_comments' => $this->current_user_can( 'moderate_comments' ),
973 + 'activate_wordads' => $is_wpcom_blog_owner,
974 + 'promote_users' => $this->current_user_can( 'promote_users' ),
975 + 'publish_posts' => $this->current_user_can( 'publish_posts' ),
976 + 'upload_files' => $this->current_user_can( 'upload_files' ),
977 + 'delete_users' => $this->current_user_can( 'delete_users' ),
978 + 'remove_users' => $this->current_user_can( 'remove_users' ),
979 + 'own_site' => $is_wpcom_blog_owner,
980 + 'view_stats' => stats_is_blog_user( $this->blog_id ),
981 + 'activate_plugins' => $this->current_user_can( 'activate_plugins' ),
982 + 'update_plugins' => $this->current_user_can( 'update_plugins' ),
983 + 'export' => $this->current_user_can( 'export' ),
984 + 'import' => $this->current_user_can( 'import' ),
985 + );
986 + }
987 +
988 + /**
989 + * Whether or not a site is public.
990 + *
991 + * @return bool
992 + **/
993 + public function is_visible() {
994 + if ( is_user_logged_in() ) {
995 + $current_user = wp_get_current_user();
996 + $visible = (array) get_user_meta( $current_user->ID, 'blog_visibility', true );
997 +
998 + $is_visible = true;
999 + if ( isset( $visible[ $this->blog_id ] ) ) {
1000 + $is_visible = (bool) $visible[ $this->blog_id ];
1001 + }
1002 +
1003 + // null and true are visible
1004 + return $is_visible;
1005 + }
1006 +
1007 + return null;
1008 + }
1009 +
1010 + /**
1011 + * Creates and returns an array with logo settings.
1012 + *
1013 + * @return array
1014 + **/
1015 + public function get_logo() {
1016 + // Set an empty response array.
1017 + $logo_setting = array(
1018 + 'id' => 0,
1019 + 'sizes' => array(),
1020 + 'url' => '',
1021 + );
1022 +
1023 + // Get current site logo values.
1024 + $logo_id = get_option( 'site_logo' );
1025 +
1026 + // Update the response array if there's a site logo currenty active.
1027 + if ( $logo_id ) {
1028 + $logo_setting['id'] = $logo_id;
1029 + $logo_setting['url'] = wp_get_attachment_url( $logo_id );
1030 + }
1031 +
1032 + return $logo_setting;
1033 + }
1034 +
1035 + /**
1036 + * Returns the timezone string from the site's settings (eg. 'Europe/London').
1037 + *
1038 + * @return string
1039 + **/
1040 + public function get_timezone() {
1041 + return (string) get_option( 'timezone_string' );
1042 + }
1043 +
1044 + /**
1045 + * Returns the GMT offset from the site's settings (eg. 5.5).
1046 + *
1047 + * @return float
1048 + **/
1049 + public function get_gmt_offset() {
1050 + return (float) get_option( 'gmt_offset' );
1051 + }
1052 +
1053 + /**
1054 + * Returns the site's login URL.
1055 + *
1056 + * @return string
1057 + **/
1058 + public function get_login_url() {
1059 + return wp_login_url();
1060 + }
1061 +
1062 + /**
1063 + * Returns the URL for a site's admin area.
1064 + *
1065 + * @return string
1066 + **/
1067 + public function get_admin_url() {
1068 + return get_admin_url();
1069 + }
1070 +
1071 + /**
1072 + * Returns the theme's slug (eg. 'twentytwentytwo')
1073 + *
1074 + * @return string
1075 + **/
1076 + public function get_theme_slug() {
1077 + return get_option( 'stylesheet' );
1078 + }
1079 +
1080 + /**
1081 + * Returns a list of errors for broken themes on the site.
1082 + *
1083 + * @return array
1084 + */
1085 + public function get_theme_errors() {
1086 + $themes_with_errors = wp_get_themes( array( 'errors' => true ) );
1087 + $theme_errors = array();
1088 +
1089 + foreach ( $themes_with_errors as $theme ) {
1090 + $errors = $theme->errors();
1091 +
1092 + if ( is_wp_error( $errors ) && ! empty( $errors->get_error_messages() ) ) {
1093 + $theme_errors[] = array(
1094 + 'name' => sanitize_title( $theme->get( 'Name' ) ),
1095 + 'errors' => (array) $errors->get_error_messages(),
1096 + );
1097 + }
1098 + }
1099 +
1100 + return $theme_errors;
1101 + }
1102 +
1103 + /**
1104 + * Gets the header image data.
1105 + *
1106 + * @return bool|object
1107 + **/
1108 + public function get_header_image() {
1109 + return get_theme_mod( 'header_image_data' );
1110 + }
1111 +
1112 + /**
1113 + * Gets the theme background color.
1114 + *
1115 + * @return bool|string
1116 + **/
1117 + public function get_background_color() {
1118 + return get_theme_mod( 'background_color' );
1119 + }
1120 +
1121 + /**
1122 + * Get the image default link type.
1123 + *
1124 + * @return string
1125 + **/
1126 + public function get_image_default_link_type() {
1127 + return get_option( 'image_default_link_type' );
1128 + }
1129 +
1130 + /**
1131 + * Gets the image thumbnails width.
1132 + *
1133 + * @return int
1134 + **/
1135 + public function get_image_thumbnail_width() {
1136 + return (int) get_option( 'thumbnail_size_w' );
1137 + }
1138 +
1139 + /**
1140 + * Gets the image thumbnails height.
1141 + *
1142 + * @return int
1143 + **/
1144 + public function get_image_thumbnail_height() {
1145 + return (int) get_option( 'thumbnail_size_h' );
1146 + }
1147 +
1148 + /**
1149 + * Whether cropping is enabled for thumbnails.
1150 + *
1151 + * @return string
1152 + **/
1153 + public function get_image_thumbnail_crop() {
1154 + return get_option( 'thumbnail_crop' );
1155 + }
1156 +
1157 + /**
1158 + * Gets the medium sized image setting's width.
1159 + *
1160 + * @return int
1161 + **/
1162 + public function get_image_medium_width() {
1163 + return (int) get_option( 'medium_size_w' );
1164 + }
1165 +
1166 + /**
1167 + * Gets the medium sized image setting's height.
1168 + *
1169 + * @return int
1170 + **/
1171 + public function get_image_medium_height() {
1172 + return (int) get_option( 'medium_size_h' );
1173 + }
1174 +
1175 + /**
1176 + * Gets the large sized image setting's width.
1177 + *
1178 + * @return int
1179 + **/
1180 + public function get_image_large_width() {
1181 + return (int) get_option( 'large_size_w' );
1182 + }
1183 +
1184 + /**
1185 + * Gets the large sized image setting's height.
1186 + *
1187 + * @return int
1188 + **/
1189 + public function get_image_large_height() {
1190 + return (int) get_option( 'large_size_h' );
1191 + }
1192 +
1193 + /**
1194 + * Gets the permalink structure as defined in the site's settings.
1195 + *
1196 + * @return string
1197 + **/
1198 + public function get_permalink_structure() {
1199 + return get_option( 'permalink_structure' );
1200 + }
1201 +
1202 + /**
1203 + * Gets the default post format
1204 + *
1205 + * @return string
1206 + **/
1207 + public function get_default_post_format() {
1208 + return get_option( 'default_post_format' );
1209 + }
1210 +
1211 + /**
1212 + * Gets the default post category
1213 + *
1214 + * @return int
1215 + **/
1216 + public function get_default_category() {
1217 + return (int) get_option( 'default_category' );
1218 + }
1219 +
1220 + /**
1221 + * Returns what should be shown on the front page (eg. page or posts)
1222 + *
1223 + * @return string
1224 + **/
1225 + public function get_show_on_front() {
1226 + return get_option( 'show_on_front' );
1227 + }
1228 +
1229 + /**
1230 + * Whether or not the front page is set as 'page' to allow a custom front page
1231 + *
1232 + * @return bool
1233 + **/
1234 + public function is_custom_front_page() {
1235 + return ( 'page' === $this->get_show_on_front() );
1236 + }
1237 +
1238 + /**
1239 + * Whether or not likes have been enabled on all site posts
1240 + *
1241 + * @return bool
1242 + **/
1243 + public function get_default_likes_enabled() {
1244 + return (bool) apply_filters( 'wpl_is_enabled_sitewide', ! get_option( 'disabled_likes' ) );
1245 + }
1246 +
1247 + /**
1248 + * If sharing has been enabled and there are visible blog services (eg. 'facebook', 'twitter'), returns true.
1249 + *
1250 + * @return bool
1251 + **/
1252 + public function get_default_sharing_status() {
1253 + $default_sharing_status = false;
1254 + if ( class_exists( 'Sharing_Service' ) ) {
1255 + $ss = new Sharing_Service();
1256 + $blog_services = $ss->get_blog_services();
1257 + $default_sharing_status = ! empty( $blog_services['visible'] );
1258 + }
1259 + return $default_sharing_status;
1260 + }
1261 +
1262 + /**
1263 + * Displays the current comment status
1264 + *
1265 + * @return bool False if closed, true for all other comment statuses.
1266 + **/
1267 + public function get_default_comment_status() {
1268 + return 'closed' !== get_option( 'default_comment_status' );
1269 + }
1270 +
1271 + /**
1272 + * Displays the current site-wide post ping status (for pingbacks and trackbacks)
1273 + *
1274 + * @return bool False if closed, true for all other ping statuses.
1275 + **/
1276 + public function default_ping_status() {
1277 + return 'closed' !== get_option( 'default_ping_status' );
1278 + }
1279 +
1280 + /**
1281 + * Whether or not Publicize has been permanently disabled on the site
1282 + *
1283 + * @see wpcom/wp-content/admin-plugins/publicize/publicize-wpcom.php
1284 + *
1285 + * @return bool Default false.
1286 + **/
1287 + public function is_publicize_permanently_disabled() {
1288 + $publicize_permanently_disabled = false;
1289 + if ( function_exists( 'is_publicize_permanently_disabled' ) ) {
1290 + $publicize_permanently_disabled = is_publicize_permanently_disabled( $this->blog_id );
1291 + }
1292 + return $publicize_permanently_disabled;
1293 + }
1294 +
1295 + /**
1296 + * Returns the post ID of the static front page.
1297 + *
1298 + * @return int
1299 + **/
1300 + public function get_page_on_front() {
1301 + return (int) get_option( 'page_on_front' );
1302 + }
1303 +
1304 + /**
1305 + * Returns the post ID of the page designated as the posts page.
1306 + *
1307 + * @return int
1308 + **/
1309 + public function get_page_for_posts() {
1310 + return (int) get_option( 'page_for_posts' );
1311 + }
1312 +
1313 + /**
1314 + * Whether or not headstart is enabled for the site
1315 + *
1316 + * @return bool
1317 + **/
1318 + public function is_headstart() {
1319 + return get_option( 'headstart' );
1320 + }
1321 +
1322 + /**
1323 + * The WordPress version on the site.
1324 + *
1325 + * @return string
1326 + **/
1327 + public function get_wordpress_version() {
1328 + global $wp_version;
1329 + return $wp_version;
1330 + }
1331 +
1332 + /**
1333 + * Whether or not this is a domain-only site (only relevant on WordPress.com simple sites - false otherwise)
1334 + *
1335 + * @return bool
1336 + **/
1337 + public function is_domain_only() {
1338 + $options = get_option( 'options' );
1339 + return ! empty( $options['is_domain_only'] ) ? (bool) $options['is_domain_only'] : false;
1340 + }
1341 +
1342 + /**
1343 + * Whether or not the blog is set to public (not hidden from search engines)
1344 + *
1345 + * @return int 1 for true, 0 for false.
1346 + **/
1347 + public function get_blog_public() {
1348 + return (int) get_option( 'blog_public' );
1349 + }
1350 +
1351 + /**
1352 + * Whether or not the site is in a 'pending automated transfer' state.
1353 + *
1354 + * @return bool
1355 + **/
1356 + public function has_pending_automated_transfer() {
1357 + /**
1358 + * Filter if a site is in pending automated transfer state.
1359 + *
1360 + * @module json-api
1361 + *
1362 + * @since 6.4.0
1363 + *
1364 + * @param bool has_site_pending_automated_transfer( $this->blog_id )
1365 + * @param int $blog_id Blog identifier.
1366 + */
1367 + return apply_filters(
1368 + 'jetpack_site_pending_automated_transfer',
1369 + false,
1370 + $this->blog_id
1371 + );
1372 + }
1373 +
1374 + /**
1375 + * Whether or not the site has a 'designType' option set as 'store'
1376 + *
1377 + * @return bool
1378 + **/
1379 + public function signup_is_store() {
1380 + return $this->get_design_type() === 'store';
1381 + }
1382 +
1383 + /**
1384 + * Return a new WP_Roles instance, which implements a user roles API
1385 + *
1386 + * @return WP_Roles
1387 + **/
1388 + public function get_roles() {
1389 + return new WP_Roles();
1390 + }
1391 +
1392 + /**
1393 + * Returns the 'designType' option if set (the site design type), null otherwise.
1394 + *
1395 + * @return string|null
1396 + **/
1397 + public function get_design_type() {
1398 + $options = get_option( 'options' );
1399 + return empty( $options['designType'] ) ? null : $options['designType'];
1400 + }
1401 +
1402 + /**
1403 + * Returns the 'site_goals' option if set (eg. share, promote, educate, sell, showcase).
1404 + *
1405 + * @return array
1406 + **/
1407 + public function get_site_goals() {
1408 + $site_goals_option = get_option( 'site_goals' );
1409 +
1410 + if ( is_array( $site_goals_option ) ) {
1411 + return $site_goals_option;
1412 + }
1413 +
1414 + return array();
1415 + }
1416 + /**
1417 + * Return site's launch status. Expanded in class.json-api-site-jetpack.php.
1418 + *
1419 + * @return bool False in this case.
1420 + */
1421 + public function get_launch_status() {
1422 + return false;
1423 + }
1424 +
1425 + /**
1426 + * Whether a site has any migration meta details - only applicable on WordPress.com
1427 + *
1428 + * @see /wpcom/public.api/rest/sal/class.json-api-site-jetpack-shadow.php
1429 + *
1430 + * @return null
1431 + */
1432 + public function get_migration_meta() {
1433 + return null;
1434 + }
1435 +
1436 + /**
1437 + * Whether a site has a site segment - only applicable on WordPress.com
1438 + *
1439 + * @see /wpcom/public.api/rest/sal/class.json-api-site-wpcom.php
1440 + *
1441 + * @return false
1442 + */
1443 + public function get_site_segment() {
1444 + return false;
1445 + }
1446 +
1447 + /**
1448 + * Whether a site has Vertical ID (used for Starter Templates) - default to only applicable on WordPress.com
1449 + *
1450 + * @see /wpcom/public.api/rest/sal/class.json-api-site-wpcom.php
1451 + *
1452 + * @return false
1453 + */
1454 + public function get_site_vertical_id() {
1455 + return false;
1456 + }
1457 +
1458 + /**
1459 + * Whether a site has a 'site_creation_flow' option set (eg gutenboarding, mobile) - only applicable on WordPress.com
1460 + *
1461 + * @see /wpcom-json-endpoints/class.wpcom-json-api-new-site-endpoint.php for more on the option.
1462 + *
1463 + * @return bool
1464 + */
1465 + public function get_site_creation_flow() {
1466 + return get_option( 'site_creation_flow' );
1467 + }
1468 +
1469 + /**
1470 + * Whether a site has a 'site_source_slug' option set - only applicable on WordPress.com
1471 + *
1472 + * @see /wpcom-json-endpoints/class.wpcom-json-api-new-site-endpoint.php for more on the option.
1473 + *
1474 + * @return bool
1475 + */
1476 + public function get_site_source_slug() {
1477 + return get_option( 'site_source_slug' );
1478 + }
1479 +
1480 + /**
1481 + * Return any selected features (used to help recommend plans)
1482 + *
1483 + * @return string
1484 + */
1485 + public function get_selected_features() {
1486 + return get_option( 'selected_features' );
1487 + }
1488 +
1489 + /**
1490 + * Return true if the site design was created with a Blank Canvas (empty homepage template), false otherwise.
1491 + *
1492 + * @return bool
1493 + */
1494 + public function was_created_with_blank_canvas_design() {
1495 + return (bool) get_option( 'was_created_with_blank_canvas_design' );
1496 + }
1497 +
1498 + /**
1499 + * Get the option storing the Anchor podcast ID that identifies a site as a podcasting site.
1500 + *
1501 + * @return string
1502 + */
1503 + public function get_anchor_podcast() {
1504 + return get_option( 'anchor_podcast' );
1505 + }
1506 +
1507 + /**
1508 + * Check if the site is currently being built by the DIFM Lite team.
1509 + *
1510 + * @return bool
1511 + */
1512 + public function is_difm_lite_in_progress() {
1513 + if ( function_exists( 'has_blog_sticker' ) ) {
1514 + return has_blog_sticker( 'difm-lite-in-progress' );
1515 + } elseif ( function_exists( 'wpcomsh_is_site_sticker_active' ) ) {
1516 + // For atomic sites
1517 + return wpcomsh_is_site_sticker_active( 'difm-lite-in-progress' );
1518 + }
1519 + return false;
1520 + }
1521 +
1522 + /**
1523 + * Get the DIFM Lite site options exposed to the frontend while a build is in progress.
1524 + *
1525 + * Returns null unless the site has an active DIFM Lite build and the code is
1526 + * running on WordPress.com, where the DIFM Lite library exists.
1527 + *
1528 + * @return array|null
1529 + */
1530 + public function get_difm_lite_site_options() {
1531 + if ( ! $this->is_difm_lite_in_progress() ) {
1532 + return null;
1533 + }
1534 + if ( ! defined( 'IS_WPCOM' ) || ! IS_WPCOM || ! function_exists( 'require_lib' ) ) {
1535 + return null;
1536 + }
1537 + require_lib( 'difm-lite' );
1538 + if ( ! class_exists( '\DIFM_Lite_Options' ) ) {
1539 + // Fail closed if the library did not define the class; this method
1540 + // documents null for every path where the options are unavailable.
1541 + return null;
1542 + }
1543 + // The submission state is canonical on the purchase blog. This site may be
1544 + // the stickered staging build target, whose own options blob is empty, so
1545 + // resolve to the purchase blog before reading — otherwise a post-submit
1546 + // staging target reports itself as pre-submit.
1547 + //
1548 + // resolve_purchase_site_id() ships in the wpcom DIFM Lite site-role
1549 + // pointers change. This Jetpack code can deploy before that lands, so
1550 + // fall back to the current blog when it is unavailable — no retargeted
1551 + // builds can exist until that change is live, so the current blog is the
1552 + // purchase blog in that window.
1553 + $purchase_blog_id = $this->blog_id;
1554 + // @phan-suppress-next-line PhanUndeclaredClassReference -- wpcom-only class, guarded above.
1555 + if ( method_exists( '\DIFM_Lite_Options', 'resolve_purchase_site_id' ) ) {
1556 + // @phan-suppress-next-line PhanUndeclaredClassMethod -- wpcom-only class, guarded above.
1557 + $purchase_blog_id = \DIFM_Lite_Options::resolve_purchase_site_id( $this->blog_id );
1558 + }
1559 + // @phan-suppress-next-line PhanUndeclaredClassMethod -- wpcom-only class, guarded above.
1560 + $difm_lite_options = new \DIFM_Lite_Options( $purchase_blog_id );
1561 + return array(
1562 + // @phan-suppress-next-line PhanUndeclaredClassProperty -- wpcom-only class, guarded above.
1563 + 'is_website_content_submitted' => (bool) $difm_lite_options->is_website_content_submitted,
1564 + );
1565 + }
1566 +
1567 + /**
1568 + * Check if the site has the gating-business-q1 blog sticker.
1569 + *
1570 + * @return bool
1571 + */
1572 + public function is_gating_business_q1() {
1573 + if ( function_exists( 'has_blog_sticker' ) ) {
1574 + return has_blog_sticker( 'gating-business-q1' );
1575 + } elseif ( function_exists( 'wpcomsh_is_site_sticker_active' ) ) {
1576 + // For atomic sites
1577 + return wpcomsh_is_site_sticker_active( 'gating-business-q1' );
1578 + }
1579 + return false;
1580 + }
1581 +
1582 + /**
1583 + * Whether the site is still on the pre-2026 feature gating.
1584 + *
1585 + * @return bool
1586 + */
1587 + public function is_legacy_gating_site() {
1588 + if ( ! method_exists( 'WPCOM_Features', 'is_legacy_gating_site' ) ) {
1589 + return false;
1590 + }
1591 +
1592 + return (bool) WPCOM_Features::is_legacy_gating_site( $this->blog_id );
1593 + }
1594 +
1595 + /**
1596 + * Get the option of site intent which value is coming from the Hero Flow
1597 + *
1598 + * @return string
1599 + */
1600 + public function get_site_intent() {
1601 + return get_option( 'site_intent', '' );
1602 + }
1603 +
1604 + /**
1605 + * Get the option of site partner bundle which value is coming from the Partner Flow
1606 + *
1607 + * @return string
1608 + */
1609 + public function get_site_partner_bundle() {
1610 + return get_option( 'site_partner_bundle', '' );
1611 + }
1612 +
1613 + /**
1614 + * Get site option to determine if and how to display launchpad onboarding
1615 + *
1616 + * @return string
1617 + */
1618 + public function get_launchpad_screen() {
1619 + return get_option( 'launchpad_screen' );
1620 + }
1621 +
1622 + /**
1623 + * Get the option onboarding_segment coming from the Guided Flow
1624 + *
1625 + * @return string
1626 + */
1627 + public function get_onboarding_segment() {
1628 + return get_option( 'onboarding_segment', '' );
1629 + }
1630 +
1631 + /**
1632 + * Get site option for completed launchpad checklist tasks
1633 + *
1634 + * @return string
1635 + */
1636 + public function get_launchpad_checklist_tasks_statuses() {
1637 + $launchpad_checklist_tasks_statuses_option = get_option( 'launchpad_checklist_tasks_statuses' );
1638 +
1639 + if ( is_array( $launchpad_checklist_tasks_statuses_option ) ) {
1640 + return $launchpad_checklist_tasks_statuses_option;
1641 + }
1642 +
1643 + return array();
1644 + }
1645 +
1646 + /**
1647 + * Whether the AI Launchpad is enabled for this site. Mirrors AI_Launchpad::is_enabled_for_site() in jetpack-mu-wpcom.
1648 + *
1649 + * @return bool
1650 + */
1651 + public function is_ai_launchpad_enabled() {
1652 + return (bool) get_option( 'wpcom_ai_launchpad_enabled' ) && ! $this->is_ai_launchpad_no_guidance();
1653 + }
1654 +
1655 + /**
1656 + * Whether the AI Launchpad was dismissed (skipped), which leaves the site with no setup guidance.
1657 + *
1658 + * @return bool
1659 + */
1660 + public function is_ai_launchpad_dismissed() {
1661 + return (bool) get_option( 'wpcom_ai_launchpad_dismissed' );
1662 + }
1663 +
1664 + /**
1665 + * Whether every AI Launchpad task has been completed.
1666 + *
1667 + * @return bool
1668 + */
1669 + public function is_ai_launchpad_completed() {
1670 + return (bool) get_option( 'wpcom_ai_launchpad_completed' );
1671 + }
1672 +
1673 + /**
1674 + * Whether the site gets no setup guidance: no My Home, Site Setup, or launchpad. Mirrors
1675 + * wpcom_launchpad_is_no_guidance() in jetpack-mu-wpcom, which also counts a skipped AI Launchpad.
1676 + *
1677 + * @return bool
1678 + */
1679 + public function is_ai_launchpad_no_guidance() {
1680 + if ( get_option( 'wpcom_ai_launchpad_no_guidance' ) ) {
1681 + return true;
1682 + }
1683 +
1684 + return (bool) get_option( 'wpcom_ai_launchpad_enabled' ) && (bool) get_option( 'wpcom_ai_launchpad_dismissed' );
1685 + }
1686 +
1687 + /**
1688 + * Get site option for migration source site domain
1689 + *
1690 + * @return string
1691 + */
1692 + public function get_migration_source_site_domain() {
1693 + return get_option( 'migration_source_site_domain', '' );
1694 + }
1695 +
1696 + /**
1697 + * Detect whether a site is WordPress.com Staging Site.
1698 + *
1699 + * @see class.json-api-site-jetpack.php for implementation.
1700 + */
1701 + abstract public function is_wpcom_staging_site();
1702 +
1703 + /**
1704 + * Get site option for the production blog id (if is a WP.com Staging Site).
1705 + *
1706 + * @see class.json-api-site-jetpack.php for implementation.
1707 + */
1708 + abstract public function get_wpcom_production_blog_id();
1709 +
1710 + /**
1711 + * Get site option for the staging blog ids (if it has them)
1712 + *
1713 + * @see class.json-api-site-jetpack.php for implementation.
1714 + */
1715 + abstract public function get_wpcom_staging_blog_ids();
1716 +
1717 + /**
1718 + * Get the site's Blaze eligibility status.
1719 + *
1720 + * @return bool
1721 + */
1722 + public function can_blaze() {
1723 + return (bool) Blaze::site_supports_blaze( $this->blog_id );
1724 + }
1725 +
1726 + /**
1727 + * Return site's setup identifier.
1728 + *
1729 + * @return string
1730 + */
1731 + public function get_wpcom_site_setup() {
1732 + return get_option( 'wpcom_site_setup' );
1733 + }
1734 +
1735 + /**
1736 + * Returns whether the site is commercial.
1737 + *
1738 + * @return mixed
1739 + *
1740 + * - `true`: the site is commercial
1741 + * - `false`: the site is not commercial
1742 + * - `null`: the commercial status is not yet determined
1743 + */
1744 + public function is_commercial() {
1745 + // Override if blog has the commercial stickers.
1746 + if ( function_exists( 'has_blog_sticker' ) ) {
1747 + $has_not_commercial_sticker = has_blog_sticker( 'jetpack-site-is-not-commercial-override', $this->blog_id );
1748 + if ( $has_not_commercial_sticker ) {
1749 + return false;
1750 + }
1751 + $has_commercial_sticker = has_blog_sticker( 'jetpack-site-is-commercial-override', $this->blog_id );
1752 + if ( $has_commercial_sticker ) {
1753 + return true;
1754 + }
1755 + }
1756 +
1757 + $is_commercial = get_option( '_jetpack_site_is_commercial', null );
1758 + return $is_commercial === null ? null : (bool) $is_commercial;
1759 + }
1760 +
1761 + /**
1762 + * Returns an array of reasons why the site is considered commercial.
1763 + *
1764 + * @return array|null
1765 + */
1766 + public function get_is_commercial_reasons() {
1767 + $reasons = get_option( '_jetpack_site_is_commercial_reason', array() );
1768 +
1769 + // Add override as reason if blog has the commercial stickers.
1770 + if ( empty( $reasons ) && $this->is_commercial() ) {
1771 + return array( 'manual-override' );
1772 + } elseif ( ! is_array( $reasons ) ) {
1773 + return array();
1774 + }
1775 +
1776 + return $reasons;
1777 + }
1778 +
1779 + /**
1780 + * Returns the site's interface selection e.g. calypso vs. wp-admin
1781 + *
1782 + * @return string
1783 + **/
1784 + public function get_wpcom_admin_interface() {
1785 + return (string) get_option( 'wpcom_admin_interface' );
1786 + }
1787 +
1788 + /**
1789 + * Returns whether the site is part of the classic view early release.
1790 + *
1791 + * @return bool
1792 + **/
1793 + public function get_wpcom_classic_early_release() {
1794 + return ! empty( get_option( 'wpcom_classic_early_release' ) );
1795 + }
1796 +
1797 + /**
1798 + * Returns whether APM (Application Performance Monitoring) is enabled for the site.
1799 + *
1800 + * APM is an Atomic-only hosting feature. Non-Atomic site types (Simple wpcom, real
1801 + * Jetpack) return false; Jetpack_Shadow_Site overrides with the actual read.
1802 + *
1803 + * @return bool
1804 + **/
1805 + public function get_apm_enabled() {
1806 + return false;
1807 + }
1808 +
1809 + /**
1810 + * Get Zendesk site meta.
1811 + *
1812 + * @return array|null
1813 + */
1814 + abstract public function get_zendesk_site_meta();
1815 +
1816 + /**
1817 + * Detect whether there's a pending plan for this site.
1818 + *
1819 + * @return bool
1820 + */
1821 + abstract public function is_pending_plan();
1822 +
1823 + /**
1824 + * Detect whether the site is a Garden site.
1825 + *
1826 + * @return bool
1827 + */
1828 + public function is_garden() {
1829 + return false;
1830 + }
1831 +
1832 + /**
1833 + * Get the Garden name.
1834 + *
1835 + * @return string
1836 + */
1837 + public function garden_name() {
1838 + return null;
1839 + }
1840 +
1841 + /**
1842 + * Get the Garden partner.
1843 + *
1844 + * @return string
1845 + */
1846 + public function garden_partner() {
1847 + return null;
1848 + }
1849 +
1850 + /**
1851 + * Detect whether the Garden site is provisioned.
1852 + *
1853 + * @return bool|null
1854 + */
1855 + public function garden_is_provisioned() {
1856 + return null;
1857 + }
1858 +
1859 + /**
1860 + * Detect whether the site is a Flex site.
1861 + *
1862 + * @return bool
1863 + */
1864 + public function is_wpcom_flex() {
1865 + if ( function_exists( 'has_blog_sticker' ) ) {
1866 + return has_blog_sticker( 'flex-cache-site' );
1867 + }
1868 + return false;
1869 + }
1870 +
1871 + /**
1872 + * Detect whether Big Sky AI assistant is enabled for this site.
1873 + *
1874 + * @return bool
1875 + */
1876 + public function is_big_sky_enabled() {
1877 + return false;
1878 + }
1879 +
1880 + /**
1881 + * Get the state of any block on the site's outgoing email.
1882 + *
1883 + * @return array|null `status` (`blocked` or `at_risk`), `reason` and `expires_on`,
1884 + * or null if the site has never been blocked.
1885 + */
1886 + public function get_atomic_email_block() {
1887 + return null;
1888 + }
1889 +
1890 + /**
1891 + * Get Jetpack recovery mode status.
1892 + *
1893 + * @return array|null
1894 + */
1895 + public function get_jetpack_recovery_mode_status() {
1896 + $status = get_option( 'jetpack_recovery_mode_status' );
1897 + return is_array( $status ) ? $status : null;
1898 + }
1899 +
1900 + /**
1901 + * Whether WordPress.com accounts must have two-step authentication to log in through SSO.
1902 + *
1903 + * @return bool
1904 + */
1905 + public function get_jetpack_sso_require_two_step() {
1906 + /** This filter is documented in projects/packages/connection/src/sso/class-helpers.php */
1907 + return (bool) apply_filters( 'jetpack_sso_require_two_step', get_option( 'jetpack_sso_require_two_step', false ) );
1908 + }
1909 +}