/modules/publicize/publicize.php

  1. <?php 
  2.  
  3. abstract class Publicize_Base { 
  4.  
  5. /** 
  6. * Services that are currently connected to the given user 
  7. * through publicize. 
  8. */ 
  9. public $connected_services = array(); 
  10.  
  11. /** 
  12. * Sservices that are supported by publicize. They don't 
  13. * neccessarly need to be connected to the current user. 
  14. */ 
  15. public $services; 
  16.  
  17. /** 
  18. * key names for post meta 
  19. */ 
  20. public $ADMIN_PAGE = 'wpas'; 
  21. public $POST_MESS = '_wpas_mess'; 
  22. public $POST_SKIP = '_wpas_skip_'; // connection id appended to indicate that a connection should NOT be publicized to 
  23. public $POST_DONE = '_wpas_done_'; // connection id appended to indicate a connection has already been publicized to 
  24. public $USER_AUTH = 'wpas_authorize'; 
  25. public $USER_OPT = 'wpas_'; 
  26. public $PENDING = '_publicize_pending'; // ready for Publicize to do its thing 
  27. public $POST_SERVICE_DONE = '_publicize_done_external'; // array of external ids where we've Publicized 
  28.  
  29. /** 
  30. * default pieces of the message used in constructing the 
  31. * content pushed out to other social networks 
  32. */ 
  33.  
  34. public $default_prefix = ''; 
  35. public $default_message = '%title%'; 
  36. public $default_suffix = ' '; 
  37.  
  38. /** 
  39. * What WP capability is require to create/delete global connections? 
  40. * All users with this cap can unglobalize all other global connections, and globalize any of their own 
  41. * Globalized connections cannot be unselected by users without this capability when publishing 
  42. */ 
  43. public $GLOBAL_CAP = 'edit_others_posts'; 
  44.  
  45. /** 
  46. * Sets up the basics of Publicize 
  47. */ 
  48. function __construct() { 
  49. $this->default_message = Publicize_Util::build_sprintf( array( 
  50. /** 
  51. * Filter the default Publicize message. 
  52. * 
  53. * @module publicize 
  54. * 
  55. * @since 2.0.0 
  56. * 
  57. * @param string $this->default_message Publicize's default message. Default is the post title. 
  58. */ 
  59. apply_filters( 'wpas_default_message', $this->default_message ),  
  60. 'title',  
  61. 'url',  
  62. ) ); 
  63.  
  64. $this->default_prefix = Publicize_Util::build_sprintf( array( 
  65. /** 
  66. * Filter the message prepended to the Publicize custom message. 
  67. * 
  68. * @module publicize 
  69. * 
  70. * @since 2.0.0 
  71. * 
  72. * @param string $this->default_prefix String prepended to the Publicize custom message. 
  73. */ 
  74. apply_filters( 'wpas_default_prefix', $this->default_prefix ),  
  75. 'url',  
  76. ) ); 
  77.  
  78. $this->default_suffix = Publicize_Util::build_sprintf( array( 
  79. /** 
  80. * Filter the message appended to the Publicize custom message. 
  81. * 
  82. * @module publicize 
  83. * 
  84. * @since 2.0.0 
  85. * 
  86. * @param string $this->default_suffix String appended to the Publicize custom message. 
  87. */ 
  88. apply_filters( 'wpas_default_suffix', $this->default_suffix ),  
  89. 'url',  
  90. ) ); 
  91.  
  92. /** 
  93. * Filter the capability to change global Publicize connection options. 
  94. * 
  95. * All users with this cap can unglobalize all other global connections, and globalize any of their own 
  96. * Globalized connections cannot be unselected by users without this capability when publishing. 
  97. * 
  98. * @module publicize 
  99. * 
  100. * @since 2.2.1 
  101. * 
  102. * @param string $this->GLOBAL_CAP default capability in control of global Publicize connection options. Default to edit_others_posts. 
  103. */ 
  104. $this->GLOBAL_CAP = apply_filters( 'jetpack_publicize_global_connections_cap', $this->GLOBAL_CAP ); 
  105.  
  106. // stage 1 and 2 of 3-stage Publicize. Flag for Publicize on creation, save meta,  
  107. // then check meta and publicze based on that. stage 3 implemented on wpcom 
  108. add_action( 'transition_post_status', array( $this, 'flag_post_for_publicize' ), 10, 3 ); 
  109. add_action( 'save_post', array( &$this, 'save_meta' ), 20, 2 ); 
  110.  
  111. // Connection test callback 
  112. add_action( 'wp_ajax_test_publicize_conns', array( $this, 'test_publicize_conns' ) ); 
  113.  
  114. /** 
  115. * Functions to be implemented by the extended class (publicize-wpcom or publicize-jetpack) 
  116. */ 
  117. abstract function get_connection_id( $connection ); 
  118. abstract function connect_url( $service_name ); 
  119. abstract function disconnect_url( $service_name, $id ); 
  120. abstract function get_connection_meta( $connection ); 
  121. abstract function get_services( $filter ); 
  122. abstract function get_connections( $service, $_blog_id = false, $_user_id = false ); 
  123. abstract function get_connection( $service, $id, $_blog_id = false, $_user_id = false ); 
  124. abstract function flag_post_for_publicize( $new_status, $old_status, $post ); 
  125. abstract function test_connection( $service_name, $connection ); 
  126. abstract function disconnect( $service, $connection_id, $_blog_id = false, $_user_id = false, $force_delete = false ); 
  127.  
  128. /** 
  129. * Shared Functions 
  130. */ 
  131.  
  132. /** 
  133. * Returns an external URL to the connection's profile 
  134. */ 
  135. function get_profile_link( $service_name, $c ) { 
  136. $cmeta = $this->get_connection_meta( $c ); 
  137.  
  138. if ( isset( $cmeta['connection_data']['meta']['link'] ) ) { 
  139. if ( 'facebook' == $service_name && 0 === strpos( parse_url( $cmeta['connection_data']['meta']['link'], PHP_URL_PATH ), '/app_scoped_user_id/' ) ) { 
  140. // App-scoped Facebook user IDs are not usable profile links 
  141. return false; 
  142.  
  143. return $cmeta['connection_data']['meta']['link']; 
  144. } elseif ( 'facebook' == $service_name && isset( $cmeta['connection_data']['meta']['facebook_page'] ) ) { 
  145. return 'https://facebook.com/' . $cmeta['connection_data']['meta']['facebook_page']; 
  146. } elseif ( 'tumblr' == $service_name && isset( $cmeta['connection_data']['meta']['tumblr_base_hostname'] ) ) { 
  147. return 'http://' . $cmeta['connection_data']['meta']['tumblr_base_hostname']; 
  148. } elseif ( 'twitter' == $service_name ) { 
  149. return 'https://twitter.com/' . substr( $cmeta['external_display'], 1 ); // Has a leading '@' 
  150. } elseif ( 'google_plus' == $service_name && isset( $cmeta['connection_data']['meta']['google_plus_page'] ) ) { 
  151. return 'https://plus.google.com/' . $cmeta['connection_data']['meta']['google_plus_page']; 
  152. } elseif ( 'google_plus' == $service_name ) { 
  153. return 'https://plus.google.com/' . $cmeta['external_id']; 
  154. } else if ( 'linkedin' == $service_name ) { 
  155. if ( !isset( $cmeta['connection_data']['meta']['profile_url'] ) ) { 
  156. return false; 
  157.  
  158. $profile_url_query = parse_url( $cmeta['connection_data']['meta']['profile_url'], PHP_URL_QUERY ); 
  159. wp_parse_str( $profile_url_query, $profile_url_query_args ); 
  160. if ( isset( $profile_url_query_args['key'] ) ) { 
  161. $id = $profile_url_query_args['key']; 
  162. } elseif ( isset( $profile_url_query_args['id'] ) ) { 
  163. $id = $profile_url_query_args['id']; 
  164. } else { 
  165. return false; 
  166.  
  167. return esc_url_raw( add_query_arg( 'id', urlencode( $id ), 'http://www.linkedin.com/profile/view' ) ); 
  168. } else { 
  169. return false; // no fallback. we just won't link it 
  170.  
  171. /** 
  172. * Returns a display name for the connection 
  173. */ 
  174. function get_display_name( $service_name, $c ) { 
  175. $cmeta = $this->get_connection_meta( $c ); 
  176.  
  177. if ( isset( $cmeta['connection_data']['meta']['display_name'] ) ) { 
  178. return $cmeta['connection_data']['meta']['display_name']; 
  179. } elseif ( $service_name == 'tumblr' && isset( $cmeta['connection_data']['meta']['tumblr_base_hostname'] ) ) { 
  180. return $cmeta['connection_data']['meta']['tumblr_base_hostname']; 
  181. } elseif ( $service_name == 'twitter' ) { 
  182. return $cmeta['external_display']; 
  183. } else { 
  184. $connection_display = $cmeta['external_display']; 
  185. if ( empty( $connection_display ) ) 
  186. $connection_display = $cmeta['external_name']; 
  187. return $connection_display; 
  188.  
  189. public static function get_service_label( $service_name ) { 
  190. switch ( $service_name ) { 
  191. case 'linkedin': 
  192. return 'LinkedIn'; 
  193. break; 
  194. case 'google_plus': 
  195. return 'Google+'; 
  196. break; 
  197. case 'twitter': 
  198. case 'facebook': 
  199. case 'tumblr': 
  200. default: 
  201. return ucfirst( $service_name ); 
  202. break; 
  203.  
  204. function show_options_popup( $service_name, $c ) { 
  205. $cmeta = $this->get_connection_meta( $c ); 
  206.  
  207. // always show if no selection has been made for facebook 
  208. if ( 'facebook' == $service_name && empty( $cmeta['connection_data']['meta']['facebook_profile'] ) && empty( $cmeta['connection_data']['meta']['facebook_page'] ) ) 
  209. return true; 
  210.  
  211. // always show if no selection has been made for tumblr 
  212. if ( 'tumblr' == $service_name && empty ( $cmeta['connection_data']['meta']['tumblr_base_hostname'] ) ) 
  213. return true; 
  214.  
  215. // if we have the specific conncetion info.. 
  216. if ( isset( $_GET['id'] ) ) { 
  217. if ( $cmeta['connection_data']['id'] == $_GET['id'] ) 
  218. return true; 
  219. } else { 
  220. // otherwise, just show if this is the completed step / first load 
  221. if ( !empty( $_GET['action'] ) && 'completed' == $_GET['action'] && !empty( $_GET['service'] ) && $service_name == $_GET['service'] && ! in_array( $_GET['service'], array( 'facebook', 'tumblr' ) ) ) 
  222. return true; 
  223.  
  224. return false; 
  225.  
  226. function user_id() { 
  227. global $current_user; 
  228. return $current_user->ID; 
  229.  
  230. function blog_id() { 
  231. return get_current_blog_id(); 
  232.  
  233. /** 
  234. * Returns true if a user has a connection to a particular service, false otherwise 
  235. */ 
  236. function is_enabled( $service, $_blog_id = false, $_user_id = false ) { 
  237. if ( !$_blog_id ) 
  238. $_blog_id = $this->blog_id(); 
  239.  
  240. if ( !$_user_id ) 
  241. $_user_id = $this->user_id(); 
  242.  
  243. $connections = $this->get_connections( $service, $_blog_id, $_user_id ); 
  244. return ( is_array( $connections ) && count( $connections ) > 0 ? true : false ); 
  245.  
  246. /** 
  247. * Fires when a post is saved, checks conditions and saves state in postmeta so that it 
  248. * can be picked up later by @see ::publicize_post() 
  249. */ 
  250. function save_meta( $post_id, $post ) { 
  251. $cron_user = null; 
  252. $submit_post = true; 
  253.  
  254. if ( ! $this->post_type_is_publicizeable( $post->post_type ) ) 
  255. return; 
  256.  
  257. // Don't Publicize during certain contexts: 
  258.  
  259. // - import 
  260. if ( defined( 'WP_IMPORTING' ) && WP_IMPORTING ) { 
  261. $submit_post = false; 
  262.  
  263. // - on quick edit, autosave, etc but do fire on p2, quickpress, and instapost ajax 
  264. if ( 
  265. defined( 'DOING_AJAX' ) 
  266. && 
  267. DOING_AJAX 
  268. && 
  269. !did_action( 'p2_ajax' ) 
  270. && 
  271. !did_action( 'wp_ajax_json_quickpress_post' ) 
  272. && 
  273. !did_action( 'wp_ajax_instapost_publish' ) 
  274. && 
  275. !did_action( 'wp_ajax_post_reblog' ) 
  276. ) { 
  277. $submit_post = false; 
  278.  
  279. // - bulk edit 
  280. if ( isset( $_GET['bulk_edit'] ) ) { 
  281. $submit_post = false; 
  282.  
  283. // - API/XML-RPC Test Posts 
  284. if ( 
  285. defined( 'XMLRPC_REQUEST' ) 
  286. && 
  287. XMLRPC_REQUEST 
  288. || 
  289. defined( 'APP_REQUEST' ) 
  290. && 
  291. APP_REQUEST 
  292. && 
  293. 0 === strpos( $post->post_title, 'Temporary Post Used For Theme Detection' ) 
  294. ) { 
  295. $submit_post = false; 
  296.  
  297. // only work with certain statuses (avoids inherits, auto drafts etc) 
  298. if ( !in_array( $post->post_status, array( 'publish', 'draft', 'future' ) ) ) { 
  299. $submit_post = false; 
  300.  
  301. // don't publish password protected posts 
  302. if ( '' !== $post->post_password ) { 
  303. $submit_post = false; 
  304.  
  305. // Did this request happen via wp-admin? 
  306. $from_web = 'post' == strtolower( $_SERVER['REQUEST_METHOD'] ) && isset( $_POST[$this->ADMIN_PAGE] ); 
  307.  
  308. if ( ( $from_web || defined( 'POST_BY_EMAIL' ) ) && isset( $_POST['wpas_title'] ) ) { 
  309. if ( empty( $_POST['wpas_title'] ) ) { 
  310. delete_post_meta( $post_id, $this->POST_MESS ); 
  311. } else { 
  312. update_post_meta( $post_id, $this->POST_MESS, trim( stripslashes( $_POST['wpas_title'] ) ) ); 
  313.  
  314. // change current user to provide context for get_services() if we're running during cron 
  315. if ( defined( 'DOING_CRON' ) && DOING_CRON ) { 
  316. $cron_user = (int) $GLOBALS['user_ID']; 
  317. wp_set_current_user( $post->post_author ); 
  318.  
  319. /** 
  320. * In this phase, we mark connections that we want to SKIP. When Publicize is actually triggered,  
  321. * it will Publicize to everything *except* those marked for skipping. 
  322. */ 
  323. foreach ( (array) $this->get_services( 'connected' ) as $service_name => $connections ) { 
  324. foreach ( $connections as $connection ) { 
  325. $connection_data = ''; 
  326. if ( method_exists( $connection, 'get_meta' ) ) 
  327. $connection_data = $connection->get_meta( 'connection_data' ); 
  328. elseif ( ! empty( $connection['connection_data'] ) ) 
  329. $connection_data = $connection['connection_data']; 
  330.  
  331. /** This action is documented in modules/publicize/ui.php */ 
  332. if ( false == apply_filters( 'wpas_submit_post?', $submit_post, $post_id, $service_name, $connection_data ) ) { 
  333. delete_post_meta( $post_id, $this->PENDING ); 
  334. continue; 
  335.  
  336. if ( !empty( $connection->unique_id ) ) 
  337. $unique_id = $connection->unique_id; 
  338. else if ( !empty( $connection['connection_data']['token_id'] ) ) 
  339. $unique_id = $connection['connection_data']['token_id']; 
  340.  
  341. // This was a wp-admin request, so we need to check the state of checkboxes 
  342. if ( $from_web ) { 
  343. // delete stray service-based post meta 
  344. delete_post_meta( $post_id, $this->POST_SKIP . $service_name ); 
  345.  
  346. // We *unchecked* this stream from the admin page, or it's set to readonly, or it's a new addition 
  347. if ( empty( $_POST[$this->ADMIN_PAGE]['submit'][$unique_id] ) ) { 
  348. // Also make sure that the service-specific input isn't there. 
  349. // If the user connected to a new service 'in-page' then a hidden field with the service 
  350. // name is added, so we just assume they wanted to Publicize to that service. 
  351. if ( empty( $_POST[$this->ADMIN_PAGE]['submit'][$service_name] ) ) { 
  352. // Nothing seems to be checked, so we're going to mark this one to be skipped 
  353. update_post_meta( $post_id, $this->POST_SKIP . $unique_id, 1 ); 
  354. continue; 
  355. } else { 
  356. // clean up any stray post meta 
  357. delete_post_meta( $post_id, $this->POST_SKIP . $unique_id ); 
  358. } else { 
  359. // The checkbox for this connection is explicitly checked -- make sure we DON'T skip it 
  360. delete_post_meta( $post_id, $this->POST_SKIP . $unique_id ); 
  361.  
  362. /** 
  363. * Fires right before the post is processed for Publicize. 
  364. * Users may hook in here and do anything else they need to after meta is written,  
  365. * and before the post is processed for Publicize. 
  366. * 
  367. * @since 2.1.2 
  368. * 
  369. * @param bool $submit_post Should the post be publicized. 
  370. * @param int $post->ID Post ID. 
  371. * @param string $service_name Service name. 
  372. * @param array $connection Array of connection details. 
  373. */ 
  374. do_action( 'publicize_save_meta', $submit_post, $post_id, $service_name, $connection ); 
  375.  
  376. if ( defined( 'DOING_CRON' ) && DOING_CRON ) { 
  377. wp_set_current_user( $cron_user ); 
  378.  
  379. // Next up will be ::publicize_post() 
  380.  
  381. /** 
  382. * Is a given post type Publicize-able? 
  383. * 
  384. * Not every CPT lends itself to Publicize-ation. Allow CPTs to register by adding their CPT via 
  385. * the publicize_post_types array filter. 
  386. * 
  387. * @param string $post_type The post type to check. 
  388. * $return bool True if the post type can be Publicized. 
  389. */ 
  390. function post_type_is_publicizeable( $post_type ) { 
  391. if ( 'post' == $post_type ) 
  392. return true; 
  393.  
  394. return post_type_supports( $post_type, 'publicize' ); 
  395.  
  396. /** 
  397. * Runs tests on all the connections and returns the results to the caller 
  398. */ 
  399. function test_publicize_conns() { 
  400. $test_results = array(); 
  401.  
  402. foreach ( (array) $this->get_services( 'connected' ) as $service_name => $connections ) { 
  403. foreach ( $connections as $connection ) { 
  404.  
  405. $id = $this->get_connection_id( $connection ); 
  406.  
  407. $connection_test_passed = true; 
  408. $connection_test_message = __( 'This connection is working correctly.' , 'jetpack' ); 
  409. $user_can_refresh = false; 
  410. $refresh_text = ''; 
  411. $refresh_url = ''; 
  412.  
  413. $connection_test_result = true; 
  414. if ( method_exists( $this, 'test_connection' ) ) { 
  415. $connection_test_result = $this->test_connection( $service_name, $connection ); 
  416.  
  417. if ( is_wp_error( $connection_test_result ) ) { 
  418. $connection_test_passed = false; 
  419. $connection_test_message = $connection_test_result->get_error_message(); 
  420. $error_data = $connection_test_result->get_error_data(); 
  421.  
  422. $user_can_refresh = $error_data['user_can_refresh']; 
  423. $refresh_text = $error_data['refresh_text']; 
  424. $refresh_url = $error_data['refresh_url']; 
  425.  
  426. $test_results[] = array( 
  427. 'connectionID' => $id,  
  428. 'serviceName' => $service_name,  
  429. 'connectionTestPassed' => $connection_test_passed,  
  430. 'connectionTestMessage' => esc_attr( $connection_test_message ),  
  431. 'userCanRefresh' => $user_can_refresh,  
  432. 'refreshText' => esc_attr( $refresh_text ),  
  433. 'refreshURL' => $refresh_url 
  434. ); 
  435.  
  436. wp_send_json_success( $test_results ); 
.