Brak opisu

class.jetpack-videopress.php 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401
  1. <?php
  2. use Automattic\Jetpack\Assets;
  3. use Automattic\Jetpack\Connection\Client;
  4. /**
  5. * VideoPress in Jetpack
  6. */
  7. class Jetpack_VideoPress {
  8. /** @var string */
  9. public $module = 'videopress';
  10. /** @var int */
  11. public $version = 5;
  12. /**
  13. * Singleton
  14. */
  15. public static function init() {
  16. static $instance = false;
  17. if ( ! $instance ) {
  18. $instance = new Jetpack_VideoPress();
  19. }
  20. return $instance;
  21. }
  22. /**
  23. * Jetpack_VideoPress constructor.
  24. *
  25. * Sets up the initializer and makes sure that videopress activates and deactivates properly.
  26. */
  27. private function __construct() {
  28. // $this->version = time(); // <s>ghost</s> cache busters!
  29. add_action( 'init', array( $this, 'on_init' ) );
  30. add_action( 'jetpack_deactivate_module_videopress', array( $this, 'jetpack_module_deactivated' ) );
  31. }
  32. /**
  33. * Fires on init
  34. */
  35. public function on_init() {
  36. add_action( 'wp_enqueue_media', array( $this, 'enqueue_admin_scripts' ) );
  37. add_filter( 'plupload_default_settings', array( $this, 'videopress_pluploder_config' ) );
  38. add_filter( 'wp_get_attachment_url', array( $this, 'update_attachment_url_for_videopress' ), 10, 2 );
  39. if ( Jetpack_Plan::supports( 'videopress' ) ) {
  40. add_filter( 'upload_mimes', array( $this, 'add_video_upload_mimes' ), 999 );
  41. }
  42. add_action( 'admin_print_footer_scripts', array( $this, 'print_in_footer_open_media_add_new' ) );
  43. add_action( 'admin_head', array( $this, 'enqueue_admin_styles' ) );
  44. add_filter( 'pre_delete_attachment', array( $this, 'delete_video_wpcom' ), 10, 2 );
  45. add_filter( 'wp_mime_type_icon', array( $this, 'wp_mime_type_icon' ), 10, 3 );
  46. add_filter( 'wp_video_extensions', array( $this, 'add_videopress_extenstion' ) );
  47. VideoPress_Scheduler::init();
  48. VideoPress_XMLRPC::init();
  49. }
  50. /**
  51. * Runs when the VideoPress module is deactivated.
  52. */
  53. public function jetpack_module_deactivated() {
  54. VideoPress_Options::delete_options();
  55. }
  56. /**
  57. * A can of coke
  58. *
  59. * Similar to current_user_can, but internal to VideoPress. Returns
  60. * true if the given VideoPress capability is allowed by the given user.
  61. */
  62. public function can( $cap, $user_id = false ) {
  63. if ( ! $user_id ) {
  64. $user_id = get_current_user_id();
  65. }
  66. // Connection owners are allowed to do all the things.
  67. if ( Jetpack::connection()->is_connection_owner( $user_id ) ) {
  68. return true;
  69. }
  70. // Additional and internal caps checks
  71. if ( ! user_can( $user_id, 'upload_files' ) ) {
  72. return false;
  73. }
  74. if ( 'edit_videos' == $cap && ! user_can( $user_id, 'edit_others_posts' ) ) {
  75. return false;
  76. }
  77. if ( 'delete_videos' == $cap && ! user_can( $user_id, 'delete_others_posts' ) ) {
  78. return false;
  79. }
  80. return true;
  81. }
  82. /**
  83. * Returns true if the provided user is the Jetpack connection owner.
  84. *
  85. * @deprecated since 7.7
  86. *
  87. * @param Integer|Boolean $user_id the user identifier. False for current user.
  88. * @return bool Whether the current user is the connection owner.
  89. */
  90. public function is_connection_owner( $user_id = false ) {
  91. _deprecated_function( __METHOD__, 'jetpack-7.7', 'Automattic\\Jetpack\\Connection\\Manager::is_connection_owner' );
  92. return Jetpack::connection()->is_connection_owner( $user_id );
  93. }
  94. /**
  95. * Register and enqueue VideoPress admin styles.
  96. */
  97. public function enqueue_admin_styles() {
  98. wp_register_style( 'videopress-admin', plugins_url( 'videopress-admin.css', __FILE__ ), array(), $this->version );
  99. wp_enqueue_style( 'videopress-admin' );
  100. }
  101. /**
  102. * Attempts to delete a VideoPress video from wp.com.
  103. * Will block the deletion from continuing if certain errors return from the wp.com API.
  104. *
  105. * @param Boolean $delete if the deletion should occur or not (unused).
  106. * @param WP_Post $post the post object.
  107. *
  108. * @return null|WP_Error|Boolean null if deletion should continue.
  109. */
  110. public function delete_video_wpcom( $delete, $post ) {
  111. if ( ! is_videopress_attachment( $post->ID ) ) {
  112. return null;
  113. }
  114. $guid = get_post_meta( $post->ID, 'videopress_guid', true );
  115. if ( empty( $guid ) ) {
  116. $this->delete_video_poster_attachment( $post->ID );
  117. return null;
  118. }
  119. // Phone home and have wp.com delete the VideoPress entry and files.
  120. $wpcom_response = Client::wpcom_json_api_request_as_blog(
  121. sprintf( '/videos/%s/delete', $guid ),
  122. '1.1',
  123. array( 'method' => 'POST' )
  124. );
  125. if ( is_wp_error( $wpcom_response ) ) {
  126. return $wpcom_response;
  127. }
  128. // Upon success or a 404 (video already deleted on wp.com), return null to allow the deletion to continue.
  129. if ( 200 === $wpcom_response['response']['code'] || 404 === $wpcom_response['response']['code'] ) {
  130. $this->delete_video_poster_attachment( $post->ID );
  131. return null;
  132. }
  133. // Otherwise we stop the deletion from proceeding.
  134. return false;
  135. }
  136. /**
  137. * Deletes a video poster attachment if it exists.
  138. *
  139. * @param int $attachment_id the WP attachment id.
  140. */
  141. private function delete_video_poster_attachment( $attachment_id ) {
  142. $thumbnail_id = get_post_meta( $attachment_id, '_thumbnail_id', true );
  143. if ( ! empty( $thumbnail_id ) ) {
  144. // Let's ensure this is a VP poster image before we delete it.
  145. if ( '1' === get_post_meta( $thumbnail_id, 'videopress_poster_image', true ) ) {
  146. // This call triggers the `delete_video_wpcom` filter again but it bails early at the is_videopress_attachment() check.
  147. wp_delete_attachment( $thumbnail_id );
  148. }
  149. }
  150. }
  151. /**
  152. * Register VideoPress admin scripts.
  153. */
  154. public function enqueue_admin_scripts() {
  155. if ( did_action( 'videopress_enqueue_admin_scripts' ) ) {
  156. return;
  157. }
  158. if ( $this->should_override_media_uploader() ) {
  159. wp_enqueue_script(
  160. 'videopress-plupload',
  161. Assets::get_file_url_for_environment(
  162. '_inc/build/videopress/js/videopress-plupload.min.js',
  163. 'modules/videopress/js/videopress-plupload.js'
  164. ),
  165. array(
  166. 'jquery',
  167. 'wp-plupload',
  168. ),
  169. $this->version
  170. );
  171. wp_enqueue_script(
  172. 'videopress-uploader',
  173. Assets::get_file_url_for_environment(
  174. '_inc/build/videopress/js/videopress-uploader.min.js',
  175. 'modules/videopress/js/videopress-uploader.js'
  176. ),
  177. array(
  178. 'videopress-plupload',
  179. ),
  180. $this->version
  181. );
  182. wp_enqueue_script(
  183. 'media-video-widget-extensions',
  184. Assets::get_file_url_for_environment(
  185. '_inc/build/videopress/js/media-video-widget-extensions.min.js',
  186. 'modules/videopress/js/media-video-widget-extensions.js'
  187. ),
  188. array(),
  189. $this->version,
  190. true
  191. );
  192. }
  193. /**
  194. * Fires after VideoPress scripts are enqueued in the dashboard.
  195. *
  196. * @since 2.5.0
  197. */
  198. do_action( 'videopress_enqueue_admin_scripts' );
  199. }
  200. /**
  201. * An override for the attachment url, which returns back the WPCOM VideoPress processed url.
  202. *
  203. * This is an action proxy to the videopress_get_attachment_url() utility function.
  204. *
  205. * @param string $url
  206. * @param int $post_id
  207. *
  208. * @return string
  209. */
  210. public function update_attachment_url_for_videopress( $url, $post_id ) {
  211. if ( $videopress_url = videopress_get_attachment_url( $post_id ) ) {
  212. return $videopress_url;
  213. }
  214. return $url;
  215. }
  216. /**
  217. * Modify the default plupload config to turn on videopress specific filters.
  218. */
  219. public function videopress_pluploder_config( $config ) {
  220. if ( ! isset( $config['filters']['max_file_size'] ) ) {
  221. $config['filters']['max_file_size'] = wp_max_upload_size() . 'b';
  222. }
  223. $config['filters']['videopress_check_uploads'] = $config['filters']['max_file_size'];
  224. // We're doing our own check in the videopress_check_uploads filter.
  225. unset( $config['filters']['max_file_size'] );
  226. return $config;
  227. }
  228. /**
  229. * Helper function to determine if the media uploader should be overridden.
  230. *
  231. * The rules are simple, only try to load the script when on the edit post or new post pages.
  232. *
  233. * @return bool
  234. */
  235. protected function should_override_media_uploader() {
  236. global $pagenow;
  237. // Only load in the admin
  238. if ( ! is_admin() ) {
  239. return false;
  240. }
  241. $acceptable_pages = array(
  242. 'post-new.php',
  243. 'post.php',
  244. 'upload.php',
  245. 'customize.php',
  246. );
  247. // Only load on the post, new post, or upload pages.
  248. if ( ! in_array( $pagenow, $acceptable_pages ) ) {
  249. return false;
  250. }
  251. $options = VideoPress_Options::get_options();
  252. return $options['shadow_blog_id'] > 0;
  253. }
  254. /**
  255. * A work-around / hack to make it possible to go to the media library with the add new box open.
  256. *
  257. * @return bool
  258. */
  259. public function print_in_footer_open_media_add_new() {
  260. global $pagenow;
  261. // Only load in the admin
  262. if ( ! is_admin() ) {
  263. return false;
  264. }
  265. if ( $pagenow !== 'upload.php' ) {
  266. return false;
  267. }
  268. if ( ! isset( $_GET['action'] ) || $_GET['action'] !== 'add-new' ) {
  269. return false;
  270. }
  271. ?>
  272. <script type="text/javascript">
  273. ( function( $ ) {
  274. window.setTimeout( function() {
  275. $('#wp-media-grid .page-title-action').click();
  276. }, 500 );
  277. }( jQuery ) );
  278. </script>
  279. <?php
  280. }
  281. /**
  282. * Makes sure that all video mimes are added in, as multi site installs can remove them.
  283. *
  284. * @param array $existing_mimes
  285. * @return array
  286. */
  287. public function add_video_upload_mimes( $existing_mimes = array() ) {
  288. $mime_types = wp_get_mime_types();
  289. $video_types = array_filter( $mime_types, array( $this, 'filter_video_mimes' ) );
  290. foreach ( $video_types as $key => $value ) {
  291. $existing_mimes[ $key ] = $value;
  292. }
  293. // Make sure that videopress mimes are considered videos.
  294. $existing_mimes['videopress'] = 'video/videopress';
  295. return $existing_mimes;
  296. }
  297. /**
  298. * Filter designed to get rid of non video mime types.
  299. *
  300. * @param string $value
  301. * @return int
  302. */
  303. public function filter_video_mimes( $value ) {
  304. return preg_match( '@^video/@', $value );
  305. }
  306. /**
  307. * @param string $icon
  308. * @param string $mime
  309. * @param int $post_id
  310. *
  311. * @return string
  312. */
  313. public function wp_mime_type_icon( $icon, $mime, $post_id ) {
  314. if ( $mime !== 'video/videopress' ) {
  315. return $icon;
  316. }
  317. $status = get_post_meta( $post_id, 'videopress_status', true );
  318. if ( $status === 'complete' ) {
  319. return $icon;
  320. }
  321. return 'https://wordpress.com/wp-content/mu-plugins/videopress/images/media-video-processing-icon.png';
  322. }
  323. /**
  324. * @param array $extensions
  325. *
  326. * @return array
  327. */
  328. public function add_videopress_extenstion( $extensions ) {
  329. $extensions[] = 'videopress';
  330. return $extensions;
  331. }
  332. }
  333. // Initialize the module.
  334. Jetpack_VideoPress::init();