No Description

class.json-api-site-base.php 18KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721
  1. <?php
  2. require_once dirname( __FILE__ ) . '/class.json-api-date.php';
  3. require_once dirname( __FILE__ ) . '/class.json-api-post-base.php';
  4. /**
  5. * Base class for the Site Abstraction Layer (SAL)
  6. * Note that this is the site "as seen by user $user_id with token $token", which
  7. * is why we pass the token to the platform; these site instances are value objects
  8. * to be used in the context of a single request for a single user.
  9. * Also note that at present this class _assumes_ you've "switched to"
  10. * the site in question, and functions like `get_bloginfo( 'name' )` will
  11. * therefore return the correct value
  12. **/
  13. abstract class SAL_Site {
  14. public $blog_id;
  15. public $platform;
  16. public function __construct( $blog_id, $platform ) {
  17. $this->blog_id = $blog_id;
  18. $this->platform = $platform;
  19. }
  20. public function get_id() {
  21. return $this->blog_id;
  22. }
  23. public function get_name() {
  24. return (string) htmlspecialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES );
  25. }
  26. public function get_description() {
  27. return (string) htmlspecialchars_decode( get_bloginfo( 'description' ), ENT_QUOTES );
  28. }
  29. public function get_url() {
  30. return (string) home_url();
  31. }
  32. public function get_post_count() {
  33. return (int) wp_count_posts( 'post' )->publish;
  34. }
  35. public function get_quota() {
  36. return null;
  37. }
  38. abstract public function has_videopress();
  39. abstract public function upgraded_filetypes_enabled();
  40. abstract public function is_mapped_domain();
  41. abstract public function get_unmapped_url();
  42. abstract public function is_redirect();
  43. abstract public function is_headstart_fresh();
  44. abstract public function featured_images_enabled();
  45. abstract public function has_wordads();
  46. abstract public function get_frame_nonce();
  47. abstract public function get_jetpack_frame_nonce();
  48. abstract public function allowed_file_types();
  49. abstract public function get_post_formats();
  50. abstract public function is_private();
  51. abstract public function is_coming_soon();
  52. abstract public function is_following();
  53. abstract public function get_subscribers_count();
  54. abstract public function get_locale();
  55. /**
  56. * The flag indicates that the site has Jetpack installed
  57. *
  58. * @return bool
  59. */
  60. abstract public function is_jetpack();
  61. /**
  62. * The flag indicates that the site is connected to WP.com via Jetpack Connection
  63. *
  64. * @return bool
  65. */
  66. abstract public function is_jetpack_connection();
  67. abstract public function get_jetpack_modules();
  68. abstract public function is_module_active( $module );
  69. abstract public function is_vip();
  70. abstract public function is_multisite();
  71. abstract public function is_single_user_site();
  72. abstract public function get_plan();
  73. abstract public function get_ak_vp_bundle_enabled();
  74. abstract public function get_podcasting_archive();
  75. abstract public function get_import_engine();
  76. abstract public function get_jetpack_seo_front_page_description();
  77. abstract public function get_jetpack_seo_title_formats();
  78. abstract public function get_verification_services_codes();
  79. abstract public function before_render();
  80. abstract public function after_render( &$response );
  81. // TODO - factor this out? Seems an odd thing to have on a site
  82. abstract public function after_render_options( &$options );
  83. // wrap a WP_Post object with SAL methods
  84. abstract public function wrap_post( $post, $context );
  85. abstract protected function is_a8c_publication( $post_id );
  86. public function is_automated_transfer() {
  87. /**
  88. * Filter if a site is an automated-transfer site.
  89. *
  90. * @module json-api
  91. *
  92. * @since 6.4.0
  93. *
  94. * @param bool is_automated_transfer( $this->blog_id )
  95. * @param int $blog_id Blog identifier.
  96. */
  97. return apply_filters(
  98. 'jetpack_site_automated_transfer',
  99. false,
  100. $this->blog_id
  101. );
  102. }
  103. abstract protected function is_wpforteams_site();
  104. /**
  105. * Get hub blog id for P2 sites.
  106. *
  107. * @return null
  108. */
  109. public function get_p2_hub_blog_id() {
  110. return null;
  111. }
  112. /**
  113. * Getter for the p2 organization ID.
  114. *
  115. * @return int
  116. */
  117. public function get_p2_organization_id() {
  118. return 0; // WPForTeams\Constants\NO_ORG_ID not loaded.
  119. }
  120. /**
  121. * Detect whether a site is a WordPress.com on Atomic site.
  122. *
  123. * @return bool
  124. */
  125. public function is_wpcom_atomic() {
  126. return jetpack_is_atomic_site();
  127. }
  128. public function is_wpcom_store() {
  129. return false;
  130. }
  131. public function woocommerce_is_active() {
  132. return false;
  133. }
  134. public function is_cloud_eligible() {
  135. return false;
  136. }
  137. public function get_products() {
  138. return array();
  139. }
  140. public function get_post_by_id( $post_id, $context ) {
  141. $post = get_post( $post_id, OBJECT, $context );
  142. if ( ! $post ) {
  143. return new WP_Error( 'unknown_post', 'Unknown post', 404 );
  144. }
  145. $wrapped_post = $this->wrap_post( $post, $context );
  146. // validate access
  147. return $this->validate_access( $wrapped_post );
  148. }
  149. /**
  150. * Validate current user can access the post
  151. *
  152. * @return WP_Error or post
  153. */
  154. private function validate_access( $post ) {
  155. $context = $post->context;
  156. if (
  157. ! $this->is_post_type_allowed( $post->post_type )
  158. && ! $this->is_a8c_publication( $post->ID )
  159. ) {
  160. return new WP_Error( 'unknown_post', 'Unknown post', 404 );
  161. }
  162. switch ( $context ) {
  163. case 'edit' :
  164. if ( ! current_user_can( 'edit_post', $post->ID ) ) {
  165. return new WP_Error( 'unauthorized', 'User cannot edit post', 403 );
  166. }
  167. break;
  168. case 'display' :
  169. $can_view = $this->user_can_view_post( $post );
  170. if ( is_wp_error( $can_view ) ) {
  171. return $can_view;
  172. }
  173. break;
  174. default :
  175. return new WP_Error( 'invalid_context', 'Invalid API CONTEXT', 400 );
  176. }
  177. return $post;
  178. }
  179. public function current_user_can_access_post_type( $post_type, $context ) {
  180. $post_type_object = $this->get_post_type_object( $post_type );
  181. if ( ! $post_type_object ) {
  182. return false;
  183. }
  184. switch( $context ) {
  185. case 'edit':
  186. return current_user_can( $post_type_object->cap->edit_posts );
  187. case 'display':
  188. return $post_type_object->public || current_user_can( $post_type_object->cap->read_private_posts );
  189. default:
  190. return false;
  191. }
  192. }
  193. protected function get_post_type_object( $post_type ) {
  194. return get_post_type_object( $post_type );
  195. }
  196. // copied from class.json-api-endpoints.php
  197. public function is_post_type_allowed( $post_type ) {
  198. // if the post type is empty, that's fine, WordPress will default to post
  199. if ( empty( $post_type ) ) {
  200. return true;
  201. }
  202. // allow special 'any' type
  203. if ( 'any' == $post_type ) {
  204. return true;
  205. }
  206. // check for allowed types
  207. if ( in_array( $post_type, $this->get_whitelisted_post_types() ) ) {
  208. return true;
  209. }
  210. if ( $post_type_object = get_post_type_object( $post_type ) ) {
  211. if ( ! empty( $post_type_object->show_in_rest ) ) {
  212. return $post_type_object->show_in_rest;
  213. }
  214. if ( ! empty( $post_type_object->publicly_queryable ) ) {
  215. return $post_type_object->publicly_queryable;
  216. }
  217. }
  218. return ! empty( $post_type_object->public );
  219. }
  220. // copied from class.json-api-endpoints.php
  221. /**
  222. * Gets the whitelisted post types that JP should allow access to.
  223. *
  224. * @return array Whitelisted post types.
  225. */
  226. public function get_whitelisted_post_types() {
  227. $allowed_types = array( 'post', 'page', 'revision' );
  228. /**
  229. * Filter the post types Jetpack has access to, and can synchronize with WordPress.com.
  230. *
  231. * @module json-api
  232. *
  233. * @since 2.2.3
  234. *
  235. * @param array $allowed_types Array of whitelisted post types. Default to `array( 'post', 'page', 'revision' )`.
  236. */
  237. $allowed_types = apply_filters( 'rest_api_allowed_post_types', $allowed_types );
  238. return array_unique( $allowed_types );
  239. }
  240. // copied and modified a little from class.json-api-endpoints.php
  241. private function user_can_view_post( $post ) {
  242. if ( !$post || is_wp_error( $post ) ) {
  243. return false;
  244. }
  245. if ( 'inherit' === $post->post_status ) {
  246. $parent_post = get_post( $post->post_parent );
  247. $post_status_obj = get_post_status_object( $parent_post->post_status );
  248. } else {
  249. $post_status_obj = get_post_status_object( $post->post_status );
  250. }
  251. $authorized = (
  252. $post_status_obj->public ||
  253. ( is_user_logged_in() &&
  254. (
  255. ( $post_status_obj->protected && current_user_can( 'edit_post', $post->ID ) ) ||
  256. ( $post_status_obj->private && current_user_can( 'read_post', $post->ID ) ) ||
  257. ( 'trash' === $post->post_status && current_user_can( 'edit_post', $post->ID ) ) ||
  258. 'auto-draft' === $post->post_status
  259. )
  260. )
  261. );
  262. if ( ! $authorized ) {
  263. return new WP_Error( 'unauthorized', 'User cannot view post', 403 );
  264. }
  265. if (
  266. -1 == get_option( 'blog_public' ) &&
  267. /**
  268. * Filter access to a specific post.
  269. *
  270. * @module json-api
  271. *
  272. * @since 3.4.0
  273. *
  274. * @param bool current_user_can( 'read_post', $post->ID ) Can the current user access the post.
  275. * @param WP_Post $post Post data.
  276. */
  277. ! apply_filters(
  278. 'wpcom_json_api_user_can_view_post',
  279. current_user_can( 'read_post', $post->ID ),
  280. $post
  281. )
  282. ) {
  283. return new WP_Error( 'unauthorized', 'User cannot view post', array( 'status_code' => 403, 'error' => 'private_blog' ) );
  284. }
  285. if ( strlen( $post->post_password ) && !current_user_can( 'edit_post', $post->ID ) ) {
  286. return new WP_Error( 'unauthorized', 'User cannot view password protected post', array( 'status_code' => 403, 'error' => 'password_protected' ) );
  287. }
  288. return true;
  289. }
  290. /**
  291. * Get post ID by name
  292. *
  293. * Attempts to match name on post title and page path
  294. *
  295. * @param string $name
  296. *
  297. * @return int|object Post ID on success, WP_Error object on failure
  298. */
  299. public function get_post_id_by_name( $name ) {
  300. $name = sanitize_title( $name );
  301. if ( ! $name ) {
  302. return new WP_Error( 'invalid_post', 'Invalid post', 400 );
  303. }
  304. $posts = get_posts( array(
  305. 'name' => $name,
  306. 'numberposts' => 1,
  307. 'post_type' => $this->get_whitelisted_post_types(),
  308. ) );
  309. if ( ! $posts || ! isset( $posts[0]->ID ) || ! $posts[0]->ID ) {
  310. $page = get_page_by_path( $name );
  311. if ( ! $page ) {
  312. return new WP_Error( 'unknown_post', 'Unknown post', 404 );
  313. }
  314. return $page->ID;
  315. }
  316. return (int) $posts[0]->ID;
  317. }
  318. /**
  319. * Get post by name
  320. *
  321. * Attempts to match name on post title and page path
  322. *
  323. * @param string $name
  324. * @param string $context (display or edit)
  325. *
  326. * @return object Post object on success, WP_Error object on failure
  327. **/
  328. public function get_post_by_name( $name, $context ) {
  329. $post_id = $this->get_post_id_by_name( $name );
  330. if ( is_wp_error( $post_id ) ) {
  331. return $post_id;
  332. }
  333. return $this->get_post_by_id( $post_id, $context );
  334. }
  335. function user_can_manage() {
  336. current_user_can( 'manage_options' );
  337. }
  338. function get_xmlrpc_url() {
  339. $xmlrpc_scheme = apply_filters( 'wpcom_json_api_xmlrpc_scheme', wp_parse_url( get_option( 'home' ), PHP_URL_SCHEME ) );
  340. return site_url( 'xmlrpc.php', $xmlrpc_scheme );
  341. }
  342. function get_registered_date() {
  343. if ( function_exists( 'get_blog_details' ) ) {
  344. $blog_details = get_blog_details();
  345. if ( ! empty( $blog_details->registered ) ) {
  346. return WPCOM_JSON_API_Date::format_date( $blog_details->registered );
  347. }
  348. }
  349. return '0000-00-00T00:00:00+00:00';
  350. }
  351. function get_capabilities() {
  352. $is_wpcom_blog_owner = wpcom_get_blog_owner() === (int) get_current_user_id();
  353. return array(
  354. 'edit_pages' => current_user_can( 'edit_pages' ),
  355. 'edit_posts' => current_user_can( 'edit_posts' ),
  356. 'edit_others_posts' => current_user_can( 'edit_others_posts' ),
  357. 'edit_others_pages' => current_user_can( 'edit_others_pages' ),
  358. 'delete_posts' => current_user_can( 'delete_posts' ),
  359. 'delete_others_posts' => current_user_can( 'delete_others_posts' ),
  360. 'edit_theme_options' => current_user_can( 'edit_theme_options' ),
  361. 'edit_users' => current_user_can( 'edit_users' ),
  362. 'list_users' => current_user_can( 'list_users' ),
  363. 'manage_categories' => current_user_can( 'manage_categories' ),
  364. 'manage_options' => current_user_can( 'manage_options' ),
  365. 'moderate_comments' => current_user_can( 'moderate_comments' ),
  366. 'activate_wordads' => $is_wpcom_blog_owner,
  367. 'promote_users' => current_user_can( 'promote_users' ),
  368. 'publish_posts' => current_user_can( 'publish_posts' ),
  369. 'upload_files' => current_user_can( 'upload_files' ),
  370. 'delete_users' => current_user_can( 'delete_users' ),
  371. 'remove_users' => current_user_can( 'remove_users' ),
  372. 'own_site' => $is_wpcom_blog_owner,
  373. /**
  374. * Filter whether the Hosting section in Calypso should be available for site.
  375. *
  376. * @module json-api
  377. *
  378. * @since 8.2.0
  379. *
  380. * @param bool $view_hosting Can site access Hosting section. Default to false.
  381. */
  382. 'view_hosting' => apply_filters( 'jetpack_json_api_site_can_view_hosting', false ),
  383. 'view_stats' => stats_is_blog_user( $this->blog_id ),
  384. 'activate_plugins' => current_user_can( 'activate_plugins' ),
  385. );
  386. }
  387. function is_visible() {
  388. if ( is_user_logged_in() ) {
  389. $current_user = wp_get_current_user();
  390. $visible = (array) get_user_meta( $current_user->ID, 'blog_visibility', true );
  391. $is_visible = true;
  392. if ( isset( $visible[ $this->blog_id ] ) ) {
  393. $is_visible = (bool) $visible[ $this->blog_id ];
  394. }
  395. // null and true are visible
  396. return $is_visible;
  397. }
  398. return null;
  399. }
  400. function get_logo() {
  401. // Set an empty response array.
  402. $logo_setting = array(
  403. 'id' => (int) 0,
  404. 'sizes' => array(),
  405. 'url' => '',
  406. );
  407. // Get current site logo values.
  408. $logo_id = get_option( 'site_logo' );
  409. // Update the response array if there's a site logo currenty active.
  410. if ( $logo_id ) {
  411. $logo_setting['id'] = $logo_id;
  412. $logo_setting['url'] = wp_get_attachment_url( $logo_id );
  413. }
  414. return $logo_setting;
  415. }
  416. function get_timezone() {
  417. return (string) get_option( 'timezone_string' );
  418. }
  419. function get_gmt_offset() {
  420. return (float) get_option( 'gmt_offset' );
  421. }
  422. function get_login_url() {
  423. return wp_login_url();
  424. }
  425. function get_admin_url() {
  426. return get_admin_url();
  427. }
  428. function get_theme_slug() {
  429. return get_option( 'stylesheet' );
  430. }
  431. function get_header_image() {
  432. return get_theme_mod( 'header_image_data' );
  433. }
  434. function get_background_color() {
  435. return get_theme_mod( 'background_color' );
  436. }
  437. function get_image_default_link_type() {
  438. return get_option( 'image_default_link_type' );
  439. }
  440. function get_image_thumbnail_width() {
  441. return (int) get_option( 'thumbnail_size_w' );
  442. }
  443. function get_image_thumbnail_height() {
  444. return (int) get_option( 'thumbnail_size_h' );
  445. }
  446. function get_image_thumbnail_crop() {
  447. return get_option( 'thumbnail_crop' );
  448. }
  449. function get_image_medium_width() {
  450. return (int) get_option( 'medium_size_w' );
  451. }
  452. function get_image_medium_height() {
  453. return (int) get_option( 'medium_size_h' );
  454. }
  455. function get_image_large_width() {
  456. return (int) get_option( 'large_size_w' );
  457. }
  458. function get_image_large_height() {
  459. return (int) get_option( 'large_size_h' );
  460. }
  461. function get_permalink_structure() {
  462. return get_option( 'permalink_structure' );
  463. }
  464. function get_default_post_format() {
  465. return get_option( 'default_post_format' );
  466. }
  467. function get_default_category() {
  468. return (int) get_option( 'default_category' );
  469. }
  470. function get_show_on_front() {
  471. return get_option( 'show_on_front' );
  472. }
  473. function is_custom_front_page() {
  474. return ( 'page' === $this->get_show_on_front() );
  475. }
  476. function get_default_likes_enabled() {
  477. return (bool) apply_filters( 'wpl_is_enabled_sitewide', ! get_option( 'disabled_likes' ) );
  478. }
  479. function get_default_sharing_status() {
  480. $default_sharing_status = false;
  481. if ( class_exists( 'Sharing_Service' ) ) {
  482. $ss = new Sharing_Service();
  483. $blog_services = $ss->get_blog_services();
  484. $default_sharing_status = ! empty( $blog_services['visible'] );
  485. }
  486. return (bool) $default_sharing_status;
  487. }
  488. function get_default_comment_status() {
  489. return 'closed' !== get_option( 'default_comment_status' );
  490. }
  491. function default_ping_status() {
  492. return 'closed' !== get_option( 'default_ping_status' );
  493. }
  494. function is_publicize_permanently_disabled() {
  495. $publicize_permanently_disabled = false;
  496. if ( function_exists( 'is_publicize_permanently_disabled' ) ) {
  497. $publicize_permanently_disabled = is_publicize_permanently_disabled( $this->blog_id );
  498. }
  499. return $publicize_permanently_disabled;
  500. }
  501. function get_page_on_front() {
  502. return (int) get_option( 'page_on_front' );
  503. }
  504. function get_page_for_posts() {
  505. return (int) get_option( 'page_for_posts' );
  506. }
  507. function is_headstart() {
  508. return get_option( 'headstart' );
  509. }
  510. function get_wordpress_version() {
  511. global $wp_version;
  512. return $wp_version;
  513. }
  514. function is_domain_only() {
  515. $options = get_option( 'options' );
  516. return ! empty ( $options['is_domain_only'] ) ? (bool) $options['is_domain_only'] : false;
  517. }
  518. function get_blog_public() {
  519. return (int) get_option( 'blog_public' );
  520. }
  521. function has_pending_automated_transfer() {
  522. /**
  523. * Filter if a site is in pending automated transfer state.
  524. *
  525. * @module json-api
  526. *
  527. * @since 6.4.0
  528. *
  529. * @param bool has_site_pending_automated_transfer( $this->blog_id )
  530. * @param int $blog_id Blog identifier.
  531. */
  532. return apply_filters(
  533. 'jetpack_site_pending_automated_transfer',
  534. false,
  535. $this->blog_id
  536. );
  537. }
  538. function signup_is_store() {
  539. return $this->get_design_type() === 'store';
  540. }
  541. function get_roles() {
  542. return new WP_Roles();
  543. }
  544. function get_design_type() {
  545. $options = get_option( 'options' );
  546. return empty( $options[ 'designType'] ) ? null : $options[ 'designType' ];
  547. }
  548. function get_site_goals() {
  549. $options = get_option( 'options' );
  550. return empty( $options[ 'siteGoals'] ) ? null : $options[ 'siteGoals' ];
  551. }
  552. function get_launch_status() {
  553. return false;
  554. }
  555. function get_migration_meta() {
  556. return null;
  557. }
  558. function get_site_segment() {
  559. return false;
  560. }
  561. function get_site_creation_flow() {
  562. return get_option( 'site_creation_flow' );
  563. }
  564. public function get_selected_features() {
  565. return get_option( 'selected_features' );
  566. }
  567. /**
  568. * Get the option storing the Anchor podcast ID that identifies a site as a podcasting site.
  569. *
  570. * @return string
  571. */
  572. public function get_anchor_podcast() {
  573. return get_option( 'anchor_podcast' );
  574. }
  575. }