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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631
  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 is_redirect();
  42. abstract public function is_headstart_fresh();
  43. abstract public function featured_images_enabled();
  44. abstract public function has_wordads();
  45. abstract public function get_frame_nonce();
  46. abstract public function allowed_file_types();
  47. abstract public function get_post_formats();
  48. abstract public function is_private();
  49. abstract public function is_following();
  50. abstract public function get_subscribers_count();
  51. abstract public function get_locale();
  52. abstract public function is_jetpack();
  53. abstract public function get_jetpack_modules();
  54. abstract public function is_vip();
  55. abstract public function is_multisite();
  56. abstract public function is_single_user_site();
  57. abstract public function get_plan();
  58. abstract public function get_ak_vp_bundle_enabled();
  59. abstract public function get_podcasting_archive();
  60. abstract public function get_jetpack_seo_front_page_description();
  61. abstract public function get_jetpack_seo_title_formats();
  62. abstract public function get_verification_services_codes();
  63. abstract public function before_render();
  64. abstract public function after_render( &$response );
  65. // TODO - factor this out? Seems an odd thing to have on a site
  66. abstract public function after_render_options( &$options );
  67. // wrap a WP_Post object with SAL methods
  68. abstract public function wrap_post( $post, $context );
  69. abstract protected function is_a8c_publication( $post_id );
  70. public function is_automated_transfer() {
  71. /**
  72. * Filter if a site is an automated-transfer site.
  73. *
  74. * @module json-api
  75. *
  76. * @since 6.4.0
  77. *
  78. * @param bool is_automated_transfer( $this->blog_id )
  79. * @param int $blog_id Blog identifier.
  80. */
  81. return apply_filters(
  82. 'jetpack_site_automated_transfer',
  83. false,
  84. $this->blog_id
  85. );
  86. }
  87. public function is_wpcom_store() {
  88. return false;
  89. }
  90. public function woocommerce_is_active() {
  91. return false;
  92. }
  93. public function get_post_by_id( $post_id, $context ) {
  94. // Remove the skyword tracking shortcode for posts returned via the API.
  95. remove_shortcode( 'skyword-tracking' );
  96. add_shortcode( 'skyword-tracking', '__return_empty_string' );
  97. $post = get_post( $post_id, OBJECT, $context );
  98. if ( ! $post ) {
  99. return new WP_Error( 'unknown_post', 'Unknown post', 404 );
  100. }
  101. $wrapped_post = $this->wrap_post( $post, $context );
  102. // validate access
  103. return $this->validate_access( $wrapped_post );
  104. }
  105. /**
  106. * Validate current user can access the post
  107. *
  108. * @return WP_Error or post
  109. */
  110. private function validate_access( $post ) {
  111. $context = $post->context;
  112. if (
  113. ! $this->is_post_type_allowed( $post->post_type )
  114. && ! $this->is_a8c_publication( $post->ID )
  115. ) {
  116. return new WP_Error( 'unknown_post', 'Unknown post', 404 );
  117. }
  118. switch ( $context ) {
  119. case 'edit' :
  120. if ( ! current_user_can( 'edit_post', $post ) ) {
  121. return new WP_Error( 'unauthorized', 'User cannot edit post', 403 );
  122. }
  123. break;
  124. case 'display' :
  125. $can_view = $this->user_can_view_post( $post );
  126. if ( is_wp_error( $can_view ) ) {
  127. return $can_view;
  128. }
  129. break;
  130. default :
  131. return new WP_Error( 'invalid_context', 'Invalid API CONTEXT', 400 );
  132. }
  133. return $post;
  134. }
  135. public function current_user_can_access_post_type( $post_type, $context ) {
  136. $post_type_object = $this->get_post_type_object( $post_type );
  137. if ( ! $post_type_object ) {
  138. return false;
  139. }
  140. switch( $context ) {
  141. case 'edit':
  142. return current_user_can( $post_type_object->cap->edit_posts );
  143. case 'display':
  144. return $post_type_object->public || current_user_can( $post_type_object->cap->read_private_posts );
  145. default:
  146. return false;
  147. }
  148. }
  149. protected function get_post_type_object( $post_type ) {
  150. return get_post_type_object( $post_type );
  151. }
  152. // copied from class.json-api-endpoints.php
  153. public function is_post_type_allowed( $post_type ) {
  154. // if the post type is empty, that's fine, WordPress will default to post
  155. if ( empty( $post_type ) ) {
  156. return true;
  157. }
  158. // allow special 'any' type
  159. if ( 'any' == $post_type ) {
  160. return true;
  161. }
  162. // check for allowed types
  163. if ( in_array( $post_type, $this->get_whitelisted_post_types() ) ) {
  164. return true;
  165. }
  166. if ( $post_type_object = get_post_type_object( $post_type ) ) {
  167. if ( ! empty( $post_type_object->show_in_rest ) ) {
  168. return $post_type_object->show_in_rest;
  169. }
  170. if ( ! empty( $post_type_object->publicly_queryable ) ) {
  171. return $post_type_object->publicly_queryable;
  172. }
  173. }
  174. return ! empty( $post_type_object->public );
  175. }
  176. // copied from class.json-api-endpoints.php
  177. /**
  178. * Gets the whitelisted post types that JP should allow access to.
  179. *
  180. * @return array Whitelisted post types.
  181. */
  182. public function get_whitelisted_post_types() {
  183. $allowed_types = array( 'post', 'page', 'revision' );
  184. /**
  185. * Filter the post types Jetpack has access to, and can synchronize with WordPress.com.
  186. *
  187. * @module json-api
  188. *
  189. * @since 2.2.3
  190. *
  191. * @param array $allowed_types Array of whitelisted post types. Default to `array( 'post', 'page', 'revision' )`.
  192. */
  193. $allowed_types = apply_filters( 'rest_api_allowed_post_types', $allowed_types );
  194. return array_unique( $allowed_types );
  195. }
  196. // copied and modified a little from class.json-api-endpoints.php
  197. private function user_can_view_post( $post ) {
  198. if ( !$post || is_wp_error( $post ) ) {
  199. return false;
  200. }
  201. if ( 'inherit' === $post->post_status ) {
  202. $parent_post = get_post( $post->post_parent );
  203. $post_status_obj = get_post_status_object( $parent_post->post_status );
  204. } else {
  205. $post_status_obj = get_post_status_object( $post->post_status );
  206. }
  207. $authorized = (
  208. $post_status_obj->public ||
  209. ( is_user_logged_in() &&
  210. (
  211. ( $post_status_obj->protected && current_user_can( 'edit_post', $post->ID ) ) ||
  212. ( $post_status_obj->private && current_user_can( 'read_post', $post->ID ) ) ||
  213. ( 'trash' === $post->post_status && current_user_can( 'edit_post', $post->ID ) ) ||
  214. 'auto-draft' === $post->post_status
  215. )
  216. )
  217. );
  218. if ( ! $authorized ) {
  219. return new WP_Error( 'unauthorized', 'User cannot view post', 403 );
  220. }
  221. if (
  222. -1 == get_option( 'blog_public' ) &&
  223. /**
  224. * Filter access to a specific post.
  225. *
  226. * @module json-api
  227. *
  228. * @since 3.4.0
  229. *
  230. * @param bool current_user_can( 'read_post', $post->ID ) Can the current user access the post.
  231. * @param WP_Post $post Post data.
  232. */
  233. ! apply_filters(
  234. 'wpcom_json_api_user_can_view_post',
  235. current_user_can( 'read_post', $post->ID ),
  236. $post
  237. )
  238. ) {
  239. return new WP_Error( 'unauthorized', 'User cannot view post', array( 'status_code' => 403, 'error' => 'private_blog' ) );
  240. }
  241. if ( strlen( $post->post_password ) && !current_user_can( 'edit_post', $post->ID ) ) {
  242. return new WP_Error( 'unauthorized', 'User cannot view password protected post', array( 'status_code' => 403, 'error' => 'password_protected' ) );
  243. }
  244. return true;
  245. }
  246. /**
  247. * Get post ID by name
  248. *
  249. * Attempts to match name on post title and page path
  250. *
  251. * @param string $name
  252. *
  253. * @return int|object Post ID on success, WP_Error object on failure
  254. */
  255. public function get_post_id_by_name( $name ) {
  256. $name = sanitize_title( $name );
  257. if ( ! $name ) {
  258. return new WP_Error( 'invalid_post', 'Invalid post', 400 );
  259. }
  260. $posts = get_posts( array(
  261. 'name' => $name,
  262. 'numberposts' => 1,
  263. 'post_type' => $this->get_whitelisted_post_types(),
  264. ) );
  265. if ( ! $posts || ! isset( $posts[0]->ID ) || ! $posts[0]->ID ) {
  266. $page = get_page_by_path( $name );
  267. if ( ! $page ) {
  268. return new WP_Error( 'unknown_post', 'Unknown post', 404 );
  269. }
  270. return $page->ID;
  271. }
  272. return (int) $posts[0]->ID;
  273. }
  274. /**
  275. * Get post by name
  276. *
  277. * Attempts to match name on post title and page path
  278. *
  279. * @param string $name
  280. * @param string $context (display or edit)
  281. *
  282. * @return object Post object on success, WP_Error object on failure
  283. **/
  284. public function get_post_by_name( $name, $context ) {
  285. $post_id = $this->get_post_id_by_name( $name );
  286. if ( is_wp_error( $post_id ) ) {
  287. return $post_id;
  288. }
  289. return $this->get_post_by_id( $post_id, $context );
  290. }
  291. function user_can_manage() {
  292. current_user_can( 'manage_options' );
  293. }
  294. function get_xmlrpc_url() {
  295. $xmlrpc_scheme = apply_filters( 'wpcom_json_api_xmlrpc_scheme', parse_url( get_option( 'home' ), PHP_URL_SCHEME ) );
  296. return site_url( 'xmlrpc.php', $xmlrpc_scheme );
  297. }
  298. function get_registered_date() {
  299. if ( function_exists( 'get_blog_details' ) ) {
  300. $blog_details = get_blog_details();
  301. if ( ! empty( $blog_details->registered ) ) {
  302. return WPCOM_JSON_API_Date::format_date( $blog_details->registered );
  303. }
  304. }
  305. return '0000-00-00T00:00:00+00:00';
  306. }
  307. function get_capabilities() {
  308. return array(
  309. 'edit_pages' => current_user_can( 'edit_pages' ),
  310. 'edit_posts' => current_user_can( 'edit_posts' ),
  311. 'edit_others_posts' => current_user_can( 'edit_others_posts' ),
  312. 'edit_others_pages' => current_user_can( 'edit_others_pages' ),
  313. 'delete_posts' => current_user_can( 'delete_posts' ),
  314. 'delete_others_posts' => current_user_can( 'delete_others_posts' ),
  315. 'edit_theme_options' => current_user_can( 'edit_theme_options' ),
  316. 'edit_users' => current_user_can( 'edit_users' ),
  317. 'list_users' => current_user_can( 'list_users' ),
  318. 'manage_categories' => current_user_can( 'manage_categories' ),
  319. 'manage_options' => current_user_can( 'manage_options' ),
  320. 'moderate_comments' => current_user_can( 'moderate_comments' ),
  321. 'activate_wordads' => wpcom_get_blog_owner() === (int) get_current_user_id(),
  322. 'promote_users' => current_user_can( 'promote_users' ),
  323. 'publish_posts' => current_user_can( 'publish_posts' ),
  324. 'upload_files' => current_user_can( 'upload_files' ),
  325. 'delete_users' => current_user_can( 'delete_users' ),
  326. 'remove_users' => current_user_can( 'remove_users' ),
  327. 'view_stats' => stats_is_blog_user( $this->blog_id )
  328. );
  329. }
  330. function is_visible() {
  331. if ( is_user_logged_in() ) {
  332. $current_user = wp_get_current_user();
  333. $visible = (array) get_user_meta( $current_user->ID, 'blog_visibility', true );
  334. $is_visible = true;
  335. if ( isset( $visible[ $this->blog_id ] ) ) {
  336. $is_visible = (bool) $visible[ $this->blog_id ];
  337. }
  338. // null and true are visible
  339. return $is_visible;
  340. }
  341. return null;
  342. }
  343. function get_logo() {
  344. // Set an empty response array.
  345. $logo_setting = array(
  346. 'id' => (int) 0,
  347. 'sizes' => array(),
  348. 'url' => '',
  349. );
  350. // Get current site logo values.
  351. $logo = get_option( 'site_logo' );
  352. // Update the response array if there's a site logo currenty active.
  353. if ( $logo && 0 != $logo['id'] ) {
  354. $logo_setting['id'] = $logo['id'];
  355. $logo_setting['url'] = $logo['url'];
  356. foreach ( $logo['sizes'] as $size => $properties ) {
  357. $logo_setting['sizes'][ $size ] = $properties;
  358. }
  359. }
  360. return $logo_setting;
  361. }
  362. function get_timezone() {
  363. return (string) get_option( 'timezone_string' );
  364. }
  365. function get_gmt_offset() {
  366. return (float) get_option( 'gmt_offset' );
  367. }
  368. function get_login_url() {
  369. return wp_login_url();
  370. }
  371. function get_admin_url() {
  372. return get_admin_url();
  373. }
  374. function get_unmapped_url() {
  375. return get_site_url( get_current_blog_id() );
  376. }
  377. function get_theme_slug() {
  378. return get_option( 'stylesheet' );
  379. }
  380. function get_header_image() {
  381. return get_theme_mod( 'header_image_data' );
  382. }
  383. function get_background_color() {
  384. return get_theme_mod( 'background_color' );
  385. }
  386. function get_image_default_link_type() {
  387. return get_option( 'image_default_link_type' );
  388. }
  389. function get_image_thumbnail_width() {
  390. return (int) get_option( 'thumbnail_size_w' );
  391. }
  392. function get_image_thumbnail_height() {
  393. return (int) get_option( 'thumbnail_size_h' );
  394. }
  395. function get_image_thumbnail_crop() {
  396. return get_option( 'thumbnail_crop' );
  397. }
  398. function get_image_medium_width() {
  399. return (int) get_option( 'medium_size_w' );
  400. }
  401. function get_image_medium_height() {
  402. return (int) get_option( 'medium_size_h' );
  403. }
  404. function get_image_large_width() {
  405. return (int) get_option( 'large_size_w' );
  406. }
  407. function get_image_large_height() {
  408. return (int) get_option( 'large_size_h' );
  409. }
  410. function get_permalink_structure() {
  411. return get_option( 'permalink_structure' );
  412. }
  413. function get_default_post_format() {
  414. return get_option( 'default_post_format' );
  415. }
  416. function get_default_category() {
  417. return (int) get_option( 'default_category' );
  418. }
  419. function get_show_on_front() {
  420. return get_option( 'show_on_front' );
  421. }
  422. function is_custom_front_page() {
  423. return ( 'page' === $this->get_show_on_front() );
  424. }
  425. function get_default_likes_enabled() {
  426. return (bool) apply_filters( 'wpl_is_enabled_sitewide', ! get_option( 'disabled_likes' ) );
  427. }
  428. function get_default_sharing_status() {
  429. $default_sharing_status = false;
  430. if ( class_exists( 'Sharing_Service' ) ) {
  431. $ss = new Sharing_Service();
  432. $blog_services = $ss->get_blog_services();
  433. $default_sharing_status = ! empty( $blog_services['visible'] );
  434. }
  435. return (bool) $default_sharing_status;
  436. }
  437. function get_default_comment_status() {
  438. return 'closed' !== get_option( 'default_comment_status' );
  439. }
  440. function default_ping_status() {
  441. return 'closed' !== get_option( 'default_ping_status' );
  442. }
  443. function is_publicize_permanently_disabled() {
  444. $publicize_permanently_disabled = false;
  445. if ( function_exists( 'is_publicize_permanently_disabled' ) ) {
  446. $publicize_permanently_disabled = is_publicize_permanently_disabled( $this->blog_id );
  447. }
  448. return $publicize_permanently_disabled;
  449. }
  450. function get_page_on_front() {
  451. return (int) get_option( 'page_on_front' );
  452. }
  453. function get_page_for_posts() {
  454. return (int) get_option( 'page_for_posts' );
  455. }
  456. function is_headstart() {
  457. return get_option( 'headstart' );
  458. }
  459. function get_wordpress_version() {
  460. global $wp_version;
  461. return $wp_version;
  462. }
  463. function is_domain_only() {
  464. $options = get_option( 'options' );
  465. return ! empty ( $options['is_domain_only'] ) ? (bool) $options['is_domain_only'] : false;
  466. }
  467. function get_blog_public() {
  468. return (int) get_option( 'blog_public' );
  469. }
  470. function has_pending_automated_transfer() {
  471. /**
  472. * Filter if a site is in pending automated transfer state.
  473. *
  474. * @module json-api
  475. *
  476. * @since 6.4.0
  477. *
  478. * @param bool has_site_pending_automated_transfer( $this->blog_id )
  479. * @param int $blog_id Blog identifier.
  480. */
  481. return apply_filters(
  482. 'jetpack_site_pending_automated_transfer',
  483. false,
  484. $this->blog_id
  485. );
  486. }
  487. function signup_is_store() {
  488. return $this->get_design_type() === 'store';
  489. }
  490. function get_roles() {
  491. return new WP_Roles();
  492. }
  493. function get_design_type() {
  494. $options = get_option( 'options' );
  495. return empty( $options[ 'designType'] ) ? null : $options[ 'designType' ];
  496. }
  497. function get_site_goals() {
  498. $options = get_option( 'options' );
  499. return empty( $options[ 'siteGoals'] ) ? null : $options[ 'siteGoals' ];
  500. }
  501. }