PageRenderTime 47ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/class-jobs.php

https://github.com/humanmade/babble
PHP | 1022 lines | 606 code | 237 blank | 179 comment | 101 complexity | 0146729f327f050fe263497abcc4d32a MD5 | raw file
  1. <?php
  2. /**
  3. * Class for handling jobs for the various language
  4. * translation teams.
  5. *
  6. * @package Babble
  7. * @since 1.4
  8. */
  9. class Babble_Jobs extends Babble_Plugin {
  10. /**
  11. * A version number used for cachebusting, rewrite rule
  12. * flushing, etc.
  13. *
  14. * @var int
  15. **/
  16. protected $version;
  17. /**
  18. * A simple flag to stop infinite recursion in various places.
  19. *
  20. * @var boolean
  21. **/
  22. protected $no_recursion;
  23. public function __construct() {
  24. $this->setup( 'babble-job', 'plugin' );
  25. $this->add_action( 'init', 'init_early', 0 );
  26. $this->add_action( 'admin_init' );
  27. $this->add_action( 'edit_form_after_title' );
  28. $this->add_action( 'add_meta_boxes' );
  29. $this->add_action( 'bbl_translation_post_meta_boxes', null, 10, 3 );
  30. $this->add_action( 'bbl_translation_terms_meta_boxes', null, 10, 2 );
  31. $this->add_action( 'bbl_translation_submit_meta_boxes', null, 10, 2 );
  32. $this->add_action( 'save_post', null, null, 2 );
  33. $this->add_action( 'save_post', 'save_job', null, 2 );
  34. $this->add_action( 'manage_bbl_job_posts_custom_column', 'action_column', null, 2 );
  35. $this->add_action( 'add_meta_boxes_bbl_job', null, 999 );
  36. $this->add_action( 'load-post.php', 'load_post_edit' );
  37. $this->add_action( 'pre_get_posts' );
  38. $this->add_action( 'admin_menu' );
  39. $this->add_action( 'wp_before_admin_bar_render' );
  40. $this->add_filter( 'manage_bbl_job_posts_columns', 'filter_columns' );
  41. $this->add_filter( 'bbl_translated_post_type', null, null, 2 );
  42. $this->add_filter( 'bbl_translated_taxonomy', null, null, 2 );
  43. $this->add_filter( 'post_updated_messages' );
  44. $this->add_filter( 'wp_insert_post_empty_content', null, null, 2 );
  45. $this->add_filter( 'admin_title', null, null, 2 );
  46. $this->add_filter( 'query_vars' );
  47. $this->add_filter( 'user_has_cap', null, null, 3 );
  48. $this->version = 1.1;
  49. }
  50. public function add_meta_boxes_bbl_job( WP_Post $post ) {
  51. # Unapologetically remove all meta boxes from the translation screen:
  52. global $wp_meta_boxes;
  53. unset( $wp_meta_boxes['bbl_job'] );
  54. }
  55. public function wp_insert_post_empty_content( $maybe_empty, $postarr ) {
  56. // Allow translations to have empty content
  57. if ( bbl_get_base_post_type( $postarr['post_type'] ) != $postarr['post_type'] )
  58. return false;
  59. return $maybe_empty;
  60. }
  61. public function bbl_translated_post_type( $translated, $post_type ) {
  62. if ( 'bbl_job' == $post_type )
  63. return false;
  64. return $translated;
  65. }
  66. public function bbl_translated_taxonomy( $translated, $taxonomy ) {
  67. if ( 'bbl_job_language' == $taxonomy )
  68. return false;
  69. return $translated;
  70. }
  71. /**
  72. * Add our post type updated messages.
  73. *
  74. * The messages are as follows:
  75. *
  76. * 1 => "Post updated. {View Post}"
  77. * 2 => "Custom field updated."
  78. * 3 => "Custom field deleted."
  79. * 4 => "Post updated."
  80. * 5 => "Post restored to revision from [date]."
  81. * 6 => "Post published. {View post}"
  82. * 7 => "Post saved."
  83. * 8 => "Post submitted. {Preview post}"
  84. * 9 => "Post scheduled for: [date]. {Preview post}"
  85. * 10 => "Post draft updated. {Preview post}"
  86. *
  87. * @param array $messages An associative array of post updated messages with post type as keys.
  88. * @return array Updated array of post updated messages.
  89. */
  90. public function post_updated_messages( array $messages ) {
  91. $messages['bbl_job'] = array(
  92. 1 => __( 'Translation job updated.', 'babble' ),
  93. 4 => __( 'Translation job updated.', 'babble' ),
  94. 8 => __( 'Translation job submitted.', 'babble' ),
  95. 10 => __( 'Translation job draft updated.', 'babble' ),
  96. );
  97. return $messages;
  98. }
  99. /**
  100. * Hooks the WP admin_init action to enqueue some stuff.
  101. *
  102. * @return void
  103. **/
  104. public function admin_init() {
  105. # @TODO use filemtime everywhere
  106. wp_enqueue_style( 'bbl-jobs-admin', $this->url( 'css/jobs-admin.css' ), array(), $this->version );
  107. }
  108. /**
  109. * Hooks the WP action load-post.php to detect people
  110. * trying to edit translated posts, and instead kick
  111. * redirect them to an existing translation job or
  112. * create a translation job and direct them to that.
  113. *
  114. * @TODO this should be in the post-public class
  115. *
  116. * @action load-post.php
  117. *
  118. * @return void
  119. **/
  120. public function load_post_edit() {
  121. $post_id = isset( $_GET[ 'post' ] ) ? absint( $_GET[ 'post' ] ) : false;
  122. if ( ! $post_id )
  123. $post_id = isset( $_POST[ 'post_ID' ] ) ? absint( $_POST[ 'post_ID' ] ) : false;
  124. $translated_post = get_post( $post_id );
  125. if ( ! $translated_post )
  126. return;
  127. if ( ! bbl_is_translated_post_type( $translated_post->post_type ) )
  128. return;
  129. $canonical_post = bbl_get_default_lang_post( $translated_post );
  130. $lang_code = bbl_get_post_lang_code( $translated_post );
  131. if ( bbl_get_default_lang_code() == $lang_code )
  132. return;
  133. // @TODO Check capabilities include editing a translation post
  134. // - If not, the button shouldn't be on the Admin Bar
  135. // - But we also need to not process at this point
  136. $existing_jobs = $this->get_post_jobs( $canonical_post );
  137. if ( isset( $existing_jobs[ $lang_code ] ) ) {
  138. $url = get_edit_post_link( $existing_jobs[ $lang_code ], 'url' );
  139. wp_redirect( $url );
  140. exit;
  141. }
  142. // Create a new translation job for the current language
  143. $lang_codes = array( $lang_code );
  144. $jobs = $this->create_post_jobs( $canonical_post, $lang_codes );
  145. // Redirect to the translation job
  146. $url = get_edit_post_link( $jobs[0], 'url' );
  147. wp_redirect( $url );
  148. exit;
  149. }
  150. /**
  151. * Hooks the WP admin_title filter to give some context to the
  152. * page titles.
  153. *
  154. * @filter admin_title
  155. *
  156. * @param string $admin_title The admin title (for the TITLE element)
  157. * @param string $title The title used in the H2 element above the edit form
  158. * @return string The admin title
  159. **/
  160. public function admin_title( $admin_title, $title ) {
  161. $screen = get_current_screen();
  162. if ( 'post' == $screen->base && 'bbl_job' == $screen->post_type ) {
  163. $pto = get_post_type_object( 'bbl_job' );
  164. $job = get_post();
  165. if ( 'add' == $screen->action ) {
  166. if ( isset( $_GET['lang'] ) ) {
  167. $lang = bbl_get_lang( $_GET['lang'] );
  168. $admin_title = sprintf( $pto->labels->add_item_context, $lang->display_name );
  169. }
  170. } else {
  171. $lang = $this->get_job_language( $job );
  172. $admin_title = sprintf( $pto->labels->edit_item_context, $lang->display_name );
  173. }
  174. $GLOBALS[ 'title' ] = $admin_title;
  175. }
  176. return $admin_title;
  177. }
  178. /**
  179. * Filters the public query vars and adds some of our own
  180. *
  181. * @filter query_vars
  182. * @param array $vars Public query vars
  183. * @return array Updated public query vars
  184. */
  185. public function query_vars( array $vars ) {
  186. if ( is_admin() ) {
  187. $vars[] = 'bbl_job_post';
  188. $vars[] = 'bbl_job_term';
  189. }
  190. return $vars;
  191. }
  192. /**
  193. * Filter the user's capabilities so they can be added/removed on the fly.
  194. *
  195. * @TODO description of what this does
  196. *
  197. * @filter user_has_cap
  198. * @param array $user_caps User's capabilities
  199. * @param array $required_caps Actual required capabilities for the requested capability
  200. * @param array $args Arguments that accompany the requested capability check:
  201. * [0] => Requested capability from current_user_can()
  202. * [1] => Current user ID
  203. * [2] => Optional second parameter from current_user_can()
  204. * @return array User's capabilities
  205. */
  206. public function user_has_cap( array $user_caps, array $required_caps, array $args ) {
  207. $user = new WP_User( $args[1] );
  208. switch ( $args[0] ) {
  209. case 'edit_post':
  210. case 'edit_bbl_job':
  211. case 'delete_post':
  212. case 'delete_bbl_job':
  213. case 'publish_post':
  214. case 'publish_bbl_job':
  215. $job = get_post( $args[2] );
  216. if ( !$job or ( 'bbl_job' != $job->post_type ) )
  217. break;
  218. $objects = $this->get_job_objects( $job );
  219. $pto = get_post_type_object( $job->post_type );
  220. $cap = str_replace( 'bbl_job', 'post', $args[0] );
  221. if ( isset( $objects['post'] ) ) {
  222. # This directly maps the ability to edit/delete/publish the job with the ability to do the same to the job's post:
  223. $can = user_can( $user, $cap, $objects['post']->ID );
  224. foreach ( $required_caps as $required ) {
  225. if ( !isset( $user_caps[$required] ) )
  226. $user_caps[$required] = $can;
  227. }
  228. } else { # else if isset object terms
  229. }
  230. break;
  231. case 'edit_bbl_jobs':
  232. # Special case for displaying the admin menu:
  233. # By default, Translators will have this cap:
  234. if ( isset( $user_caps[$args[0]] ) )
  235. break;
  236. # Cycle through post types with show_ui true, give edit_bbl_jobs cap to the user if they can edit any of the post types
  237. foreach ( get_post_types( array( 'show_ui' => true ), 'objects' ) as $pto ) {
  238. // Don't check the capability we already checked.
  239. if ( $args[0] == $pto->cap->edit_posts ) {
  240. continue;
  241. }
  242. if ( user_can( $user, $pto->cap->edit_posts ) ) {
  243. $user_caps[$args[0]] = true;
  244. break;
  245. }
  246. }
  247. break;
  248. }
  249. return $user_caps;
  250. }
  251. /**
  252. * Hooks the WP pre_get_posts ref action in the WP_Query. Sets the meta query
  253. * that's necessary for filtering jobs by their objects.
  254. *
  255. * @param WP_Query $wp_query A WP_Query object, passed by reference
  256. * @return void (param passed by reference)
  257. **/
  258. public function pre_get_posts( WP_Query & $query ) {
  259. if ( $job_post = $query->get( 'bbl_job_post' ) ) {
  260. $query->set( 'meta_key', 'bbl_job_post' );
  261. $query->set( 'meta_value', $job_post );
  262. } else if ( $job_term = $query->get( 'bbl_job_term' ) ) {
  263. $query->set( 'meta_key', 'bbl_job_term' );
  264. $query->set( 'meta_value', $job_term );
  265. }
  266. }
  267. public function edit_form_after_title() {
  268. $screen = get_current_screen();
  269. if ( 'bbl_job' != $screen->post_type )
  270. return;
  271. $job = get_post();
  272. $items = $objects = $vars = array();
  273. if ( ( 'add' == $screen->action ) and isset( $_GET['lang'] ) ) {
  274. $vars['lang_code'] = stripslashes( $_GET['lang'] );
  275. if ( isset( $_GET['bbl_origin_post'] ) ) {
  276. $post = get_post( absint( $_GET['bbl_origin_post' ] ) );
  277. $terms = $this->get_post_terms_to_translate( $post, $_GET['lang'] );
  278. $objects['post'] = $post;
  279. if ( !empty( $terms ) )
  280. $objects['terms'] = $terms;
  281. $vars['origin_post'] = $post->ID;
  282. } else if ( isset( $_GET['bbl_origin_term'] ) and isset( $_GET['bbl_origin_taxonomy'] ) ) {
  283. $term = get_term( $_GET['bbl_origin_term'], $_GET['bbl_origin_taxonomy'] );
  284. $objects['terms'][$term->taxonomy][$term->term_id] = $term;
  285. $vars['origin_term'] = $term->term_id;
  286. $vars['origin_taxonomy'] = $term->taxonomy;
  287. }
  288. } else {
  289. $objects = $this->get_job_objects( $job );
  290. }
  291. if ( isset( $objects['post'] ) ) {
  292. $post = $objects['post'];
  293. $post_translation = get_post_meta( $job->ID, "bbl_post_{$post->ID}", true );
  294. if ( empty( $post_translation ) )
  295. $post_translation = array();
  296. $items['post'] = array(
  297. 'original' => $post,
  298. 'translation' => (object) $post_translation,
  299. );
  300. }
  301. if ( isset( $objects['terms'] ) ) {
  302. foreach ( $objects['terms'] as $taxo => $terms ) {
  303. foreach ( $terms as $term ) {
  304. $term_translation = get_post_meta( $job->ID, "bbl_term_{$term->term_id}", true );
  305. if ( empty( $term_translation ) )
  306. $term_translation = array();
  307. $items['terms'][$taxo][] = array(
  308. 'original' => $term,
  309. 'translation' => (object) $term_translation,
  310. );
  311. }
  312. }
  313. }
  314. $statuses = array(
  315. 'in-progress' => get_post_status_object( 'in-progress' )->label,
  316. );
  317. if ( ( 'pending' == $job->post_status ) or !current_user_can( 'publish_post', $job->ID ) )
  318. $statuses['pending'] = get_post_status_object( 'pending' )->label;
  319. if ( current_user_can( 'publish_post', $job->ID ) )
  320. $statuses['complete'] = get_post_status_object( 'complete' )->label;
  321. $statuses = apply_filters( 'bbl_job_statuses', $statuses, $job, $objects );
  322. $vars['job'] = $job;
  323. $vars['items'] = $items;
  324. $vars['statuses'] = $statuses;
  325. $this->render_admin( 'translation-editor.php', $vars );
  326. }
  327. public function admin_menu() {
  328. # Remove the 'Add New' submenu for Translations.
  329. //remove_submenu_page( 'edit.php?post_type=bbl_job', 'post-new.php?post_type=bbl_job' );
  330. }
  331. public function wp_before_admin_bar_render() {
  332. global $wp_admin_bar;
  333. # Remove the '+New -> Translation Job' admin bar menu.
  334. $wp_admin_bar->remove_node( 'new-bbl_job' );
  335. }
  336. /**
  337. * undocumented function
  338. *
  339. * @param
  340. * @return void
  341. **/
  342. public function add_meta_boxes( $post_type ) {
  343. if ( bbl_is_translated_post_type( $post_type ) ) {
  344. add_meta_box( 'bbl_translations', _x( 'Translations', 'Translations meta box title', 'babble' ), array( $this, 'metabox_post_translations' ), $post_type, 'side', 'high' );
  345. }
  346. }
  347. public function bbl_translation_post_meta_boxes( $type, $original, $translation ) {
  348. if ( !empty( $original->post_excerpt ) or !empty( $translation->post_excerpt ) ) {
  349. add_meta_box( 'postexcerpt', __( 'Excerpt', 'babble' ), array( $this, 'metabox_translation_post_excerpt' ), $type, 'post' );
  350. }
  351. }
  352. public function bbl_translation_terms_meta_boxes( $type, $items ) {
  353. foreach ( $items as $taxo => $terms ) {
  354. $tax = get_taxonomy( $taxo );
  355. add_meta_box( "{$taxo}_terms", $tax->labels->name, array( $this, 'metabox_translation_terms' ), $type, $taxo );
  356. }
  357. }
  358. public function bbl_translation_submit_meta_boxes( $type, $job ) {
  359. add_meta_box( 'bbl_job_submit', __( 'Save Translation' , 'babble'), array( $this, 'metabox_translation_submit' ), $type, 'submit' );
  360. }
  361. public function metabox_translation_terms( array $items ) {
  362. $vars = $items;
  363. $this->render_admin( 'translation-editor-terms.php', $vars );
  364. }
  365. public function metabox_translation_post_excerpt( array $items ) {
  366. $vars = $items;
  367. $this->render_admin( 'translation-editor-post-excerpt.php', $vars );
  368. }
  369. public function metabox_translation_submit( array $items ) {
  370. $vars = $items;
  371. $this->render_admin( 'translation-editor-submit.php', $vars );
  372. }
  373. public function save_job( $job_id, WP_Post $job ) {
  374. global $bbl_post_public, $bbl_taxonomies;
  375. if ( $this->no_recursion )
  376. return;
  377. if ( 'bbl_job' != $job->post_type )
  378. return;
  379. $edit_post_nonce = isset( $_POST[ '_bbl_translation_edit_post' ] ) ? $_POST[ '_bbl_translation_edit_post' ] : false;
  380. $edit_terms_nonce = isset( $_POST[ '_bbl_translation_edit_terms' ] ) ? $_POST[ '_bbl_translation_edit_terms' ] : false;
  381. $origin_post_nonce = isset( $_POST[ '_bbl_translation_origin_post' ] ) ? $_POST[ '_bbl_translation_origin_post' ] : false;
  382. $origin_term_nonce = isset( $_POST[ '_bbl_translation_origin_term' ] ) ? $_POST[ '_bbl_translation_origin_term' ] : false;
  383. $lang_code_nonce = isset( $_POST[ '_bbl_translation_lang_code' ] ) ? $_POST[ '_bbl_translation_lang_code' ] : false;
  384. if ( $lang_code_nonce and wp_verify_nonce( $lang_code_nonce, "bbl_translation_lang_code_{$job->ID}" ) ) {
  385. wp_set_object_terms( $job->ID, stripslashes( $_POST['bbl_lang_code'] ), 'bbl_job_language', false );
  386. }
  387. $language = get_the_terms( $job, 'bbl_job_language' );
  388. if ( empty( $language ) )
  389. return false;
  390. else
  391. $lang_code = reset( $language )->name;
  392. if ( $origin_post_nonce and wp_verify_nonce( $origin_post_nonce, "bbl_translation_origin_post_{$job->ID}") ) {
  393. if ( $origin_post = get_post( absint( $_POST['bbl_origin_post'] ) ) ) {
  394. add_post_meta( $job->ID, 'bbl_job_post', "{$origin_post->post_type}|{$origin_post->ID}", true );
  395. foreach ( $this->get_post_terms_to_translate( $origin_post->ID, $lang_code ) as $taxo => $terms ) {
  396. foreach ( $terms as $term_id => $term )
  397. add_post_meta( $job->ID, 'bbl_job_term', "{$taxo}|{$term_id}", false );
  398. }
  399. }
  400. # @TODO else wp_die()?
  401. }
  402. # @TODO not implemented:
  403. if ( $origin_term_nonce and wp_verify_nonce( $origin_term_nonce, "bbl_translation_origin_term_{$job->ID}") ) {
  404. if ( $origin_term = get_term( absint( $_POST['bbl_origin_term'] ), $_POST['bbl_origin_taxonomy'] ) )
  405. add_post_meta( $job->ID, 'bbl_job_term', "{$origin_term->taxonomy}|{$origin_term->term_id}", false );
  406. # @TODO else wp_die()?
  407. }
  408. if ( $edit_post_nonce and wp_verify_nonce( $edit_post_nonce, "bbl_translation_edit_post_{$job->ID}" ) ) {
  409. $post_data = stripslashes_deep( $_POST['bbl_translation']['post'] );
  410. if ( $post_data['post_name'] )
  411. $post_data['post_name'] = sanitize_title( $post_data['post_name'] );
  412. $post_info = get_post_meta( $job->ID, 'bbl_job_post', true );
  413. list( $post_type, $post_id ) = explode( '|', $post_info );
  414. $post = get_post( $post_id );
  415. update_post_meta( $job->ID, "bbl_post_{$post_id}", $post_data );
  416. if ( 'pending' == $job->post_status ) {
  417. # Nothing.
  418. }
  419. if ( 'complete' == $job->post_status ) {
  420. # The ability to complete a translation of a post directly
  421. # maps to the ability to publish the origin post.
  422. if ( current_user_can( 'publish_post', $job->ID ) ) {
  423. if ( !$trans = $bbl_post_public->get_post_in_lang( $post, $lang_code, false ) )
  424. $trans = $bbl_post_public->initialise_translation( $post, $lang_code );
  425. $post_data['ID'] = $trans->ID;
  426. $post_data['post_status'] = $post->post_status;
  427. $this->no_recursion = true;
  428. wp_update_post( $post_data, true );
  429. $this->no_recursion = false;
  430. } else {
  431. # Just in case. Switch the job back to in-progress status.
  432. # It would be nice to be able to use the 'publish' status because then we get the built-in
  433. # publish_post cap checks, but we can't control the post status label on a per-post-type basis yet.
  434. $this->no_recursion = true;
  435. wp_update_post( array(
  436. 'ID' => $job->ID,
  437. 'post_status' => 'in-progress',
  438. ), true );
  439. $this->no_recursion = false;
  440. }
  441. }
  442. }
  443. if ( $edit_terms_nonce and wp_verify_nonce( $edit_terms_nonce, "bbl_translation_edit_terms_{$job->ID}") ) {
  444. $terms_data = stripslashes_deep( $_POST['bbl_translation']['terms'] );
  445. $terms = get_post_meta( $job->ID, 'bbl_job_term', false );
  446. foreach ( $terms as $term_info ) {
  447. list( $taxo, $term_id ) = explode( '|', $term_info );
  448. $term = get_term( $term_id, $taxo );
  449. $terms_data[$term_id]['slug'] = sanitize_title( $terms_data[$term_id]['slug'] );
  450. update_post_meta( $job->ID, "bbl_term_{$term_id}", $terms_data[$term_id] );
  451. if ( 'complete' == $job->post_status ) {
  452. # @TODO if current user can edit term
  453. $trans = $bbl_taxonomies->get_term_in_lang( $term, $taxo, $lang_code, false );
  454. if ( !$trans )
  455. $trans = $bbl_taxonomies->initialise_translation( $term, $taxo, $lang_code );
  456. $terms_data[$term->term_id]['term_id'] = $trans->term_id;
  457. $args = array(
  458. 'name' => $terms_data[$term->term_id]['name'],
  459. 'slug' => '',
  460. );
  461. wp_update_term( absint( $trans->term_id ), $trans->taxonomy, $args );
  462. }
  463. }
  464. }
  465. }
  466. public function save_post( $post_id, WP_Post $post ) {
  467. if ( $this->no_recursion )
  468. return;
  469. if ( !bbl_is_translated_post_type( $post->post_type ) )
  470. return;
  471. $nonce = isset( $_POST[ '_bbl_ready_for_translation' ] ) ? $_POST[ '_bbl_ready_for_translation' ] : false;
  472. if ( !$nonce )
  473. return;
  474. if ( !wp_verify_nonce( $nonce, "bbl_ready_for_translation-{$post->ID}" ) )
  475. return;
  476. if ( !isset( $_POST['babble_ready_for_translation'] ) )
  477. return;
  478. # @TODO individual language selection when marking post as translation ready
  479. $langs = bbl_get_active_langs();
  480. $lang_codes = wp_list_pluck( $langs, 'code' );
  481. $this->create_post_jobs( $post->ID, $lang_codes );
  482. }
  483. /**
  484. * Hooks the WP init action early to register the
  485. * job post type.
  486. *
  487. * @return void
  488. **/
  489. public function init_early() {
  490. $labels = array(
  491. 'name' => _x( 'Translation Jobs', 'translation jobs general name', 'babble' ),
  492. 'singular_name' => _x( 'Translation Job', 'translation jobs singular name', 'babble' ),
  493. 'menu_name' => _x( 'Translations', 'translation jobs menu name', 'babble' ),
  494. 'add_new' => _x( 'Add New', 'translation job', 'babble' ),
  495. 'add_new_item' => _x( 'Create New Job', 'translation job', 'babble' ),
  496. 'add_item_context' => _x( 'Add Translation Job (%s)', 'translation job; e.g. "Add Translation Job (French)"', 'babble' ),
  497. 'edit_item' => _x( 'Edit Translation Job', 'translation job', 'babble' ),
  498. 'edit_item_context' => _x( 'Edit Translation Job (%s)', 'translation job; e.g. "Edit Translation Job (French)"', 'babble' ),
  499. 'new_item' => _x( 'New Job', 'translation job', 'babble' ),
  500. 'view_item' => _x( 'View Job', 'translation job', 'babble' ),
  501. 'search_items' => _x( 'Search Jobs', 'translation job', 'babble' ),
  502. 'not_found' => _x( 'No translation jobs found.', 'translation job', 'babble' ),
  503. 'not_found_in_trash' => _x( 'No translation jobs found in Trash.', 'translation job', 'babble' ),
  504. 'all_items' => _x( 'All Translation Jobs', 'translation job', 'babble' ),
  505. );
  506. $args = array(
  507. 'public' => false,
  508. 'publicly_queryable' => false,
  509. 'show_ui' => true,
  510. 'show_in_menu' => true,
  511. 'query_var' => false,
  512. 'labels' => $labels,
  513. 'can_export' => true,
  514. 'supports' => false,
  515. 'capability_type' => 'bbl_job',
  516. 'map_meta_cap' => true,
  517. );
  518. register_post_type( 'bbl_job', $args );
  519. register_post_status( 'new', array(
  520. 'label' => __( 'New', 'babble' ),
  521. 'public' => false,
  522. 'exclude_from_search' => false,
  523. 'show_in_admin_all_list' => true,
  524. 'label_count' => _n_noop( 'New <span class="count">(%s)</span>', 'New <span class="count">(%s)</span>', 'babble' ),
  525. 'protected' => true,
  526. ) );
  527. register_post_status( 'in-progress', array(
  528. 'label' => __( 'In Progress', 'babble' ),
  529. 'public' => false,
  530. 'exclude_from_search' => false,
  531. 'show_in_admin_all_list' => true,
  532. 'label_count' => _n_noop( 'In Progress <span class="count">(%s)</span>', 'In Progress <span class="count">(%s)</span>', 'babble' ),
  533. 'protected' => true,
  534. ) );
  535. register_post_status( 'complete', array(
  536. 'label' => __( 'Complete', 'babble' ),
  537. 'public' => false,
  538. 'exclude_from_search' => false,
  539. 'show_in_admin_all_list' => true,
  540. 'label_count' => _n_noop( 'Complete <span class="count">(%s)</span>', 'Complete <span class="count">(%s)</span>', 'babble' ),
  541. 'protected' => true,
  542. ) );
  543. $args = array(
  544. 'public' => false,
  545. 'show_ui' => false,
  546. );
  547. register_taxonomy( 'bbl_job_language', array( 'bbl_job' ), $args );
  548. }
  549. // CALLBACKS
  550. // =========
  551. public function filter_columns( $cols ) {
  552. $new_cols = array();
  553. foreach ( $cols as $col_id => $col ) {
  554. if ( 'date' != $col_id ) {
  555. $new_cols[$col_id] = $col;
  556. } else {
  557. $new_cols['bbl_language'] = __( 'Language', 'babble' );
  558. $new_cols['bbl_type'] = __( 'Items', 'babble' );
  559. $new_cols['bbl_status'] = __( 'Status', 'babble' );
  560. $new_cols['date'] = $col;
  561. }
  562. }
  563. return $new_cols;
  564. }
  565. public function action_column( $col, $post_id ) {
  566. $post = get_post( $post_id );
  567. $status = get_post_status_object( $post->post_status );
  568. switch ( $col ) {
  569. case 'bbl_language':
  570. echo $this->get_job_language( $post )->display_name;
  571. break;
  572. case 'bbl_type':
  573. echo implode( ', ', $this->get_job_type( $post ) );
  574. break;
  575. case 'bbl_status':
  576. echo $status->label;
  577. break;
  578. }
  579. }
  580. public function metabox_post_translations( WP_Post $post, array $metabox ) {
  581. $trans = bbl_get_post_translations( $post );
  582. $jobs = $this->get_post_jobs( $post );
  583. $default = bbl_get_default_lang_code();
  584. # The ability to create a translation of a post directly
  585. # maps to the ability to publish the canonical post.
  586. $capable = current_user_can( 'publish_post', $post->ID );
  587. unset( $trans[$default] );
  588. if ( !empty( $trans ) ) {
  589. if ( !empty( $jobs ) and $capable ) {
  590. ?><h4><?php _e( 'Complete:', 'babble' ); ?></h4><?php
  591. }
  592. foreach ( $trans as $lang_code => $translation ) {
  593. $lang = bbl_get_lang( $lang_code );
  594. ?>
  595. <p><?php printf( '%s: <a href="%s">%s</a>', $lang->display_name, get_edit_post_link( $translation->ID ), __( 'View', 'babble' ) ); ?>
  596. <?php
  597. }
  598. }
  599. if ( !empty( $jobs ) and $capable ) {
  600. ?><h4><?php _e( 'Pending:', 'babble' ); ?></h4><?php
  601. foreach ( $jobs as $job ) {
  602. $lang = $this->get_job_language( $job );
  603. $status = get_post_status_object( $job->post_status );
  604. ?>
  605. <p><?php printf( '%s (%s)', $lang->display_name, $status->label ); ?>
  606. <?php
  607. }
  608. $args = array(
  609. 'post_type' => 'bbl_job',
  610. 'bbl_job_post' => "{$post->post_type}|{$post->ID}",
  611. );
  612. ?>
  613. <p><a href="<?php echo add_query_arg( $args, admin_url( 'edit.php' ) ); ?>"><?php _e( 'View pending translation jobs &raquo;', 'babble' ); ?></a></p>
  614. <?php
  615. } else if ( $capable ) {
  616. wp_nonce_field( "bbl_ready_for_translation-{$post->ID}", '_bbl_ready_for_translation' );
  617. ?>
  618. <p><label><input type="checkbox" name="babble_ready_for_translation" value="<?php echo absint( $post->ID ); ?>" /> <?php _e( 'Ready for translation', 'babble' ); ?></label></p>
  619. <?php
  620. } else {
  621. ?>
  622. <p><?php _ex( 'None', 'No translations', 'babble' ); ?></p>
  623. <?php
  624. }
  625. }
  626. // PUBLIC METHODS
  627. // ==============
  628. /**
  629. * Return the array of jobs for a Post, keyed
  630. * by lang code.
  631. *
  632. * @param WP_Post|int $post A WP Post object or a post ID
  633. * @return array An array of WP Translation Job Post objects
  634. */
  635. public function get_post_jobs( $post ) {
  636. $post = get_post( $post );
  637. return $this->get_object_jobs( $post->ID, 'post', $post->post_type );
  638. }
  639. /**
  640. * Return the array of jobs for a Term, keyed
  641. * by lang code.
  642. *
  643. * @param object $term A WP Term object or a term ID
  644. * @return array An array of WP Translation Job Post objects
  645. */
  646. public function get_term_jobs( $term, $taxonomy ) {
  647. $term = get_term( $term, $taxonomy );
  648. return $this->get_object_jobs( $term->term_id, 'term', $term->taxonomy );
  649. }
  650. /**
  651. * Return the array of jobs for a term or post, keyed
  652. * by lang code.
  653. *
  654. * @param int The ID of the object (eg. post ID or term ID)
  655. * @param string $type Either 'term' or 'post'
  656. * @param string $name The post type name or the term's taxonomy name
  657. * @return array An array of translation job WP_Post objects
  658. */
  659. public function get_object_jobs( $id, $type, $name ) {
  660. $jobs = get_posts( array(
  661. 'bbl_translate' => false,
  662. 'post_type' => 'bbl_job',
  663. 'post_status' => array(
  664. 'new', 'in-progress'
  665. ),
  666. 'meta_key' => "bbl_job_{$type}",
  667. 'meta_value' => "{$name}|{$id}",
  668. 'posts_per_page' => -1,
  669. ) );
  670. if ( empty( $jobs ) )
  671. return array();
  672. $return = array();
  673. foreach ( $jobs as $job ) {
  674. if ( $lang = $this->get_job_language( $job ) )
  675. $return[$lang->code] = $job;
  676. }
  677. return $return;
  678. }
  679. public function get_job_language( $job ) {
  680. $job = get_post( $job );
  681. $languages = get_the_terms( $job, 'bbl_job_language' );
  682. if ( empty( $languages ) )
  683. return false;
  684. return bbl_get_lang( reset( $languages )->name );
  685. }
  686. public function get_job_type( $job ) {
  687. $job = get_post( $job );
  688. $post = get_post_meta( $job->ID, 'bbl_job_post', true );
  689. $terms = get_post_meta( $job->ID, 'bbl_job_term', false );
  690. $return = array();
  691. if ( !empty( $post ) ) {
  692. list( $post_type, $post_id ) = explode( '|', $post );
  693. $return[] = get_post_type_object( $post_type )->labels->singular_name;
  694. }
  695. if ( !empty( $terms ) ) {
  696. foreach ( $terms as $term ) {
  697. list( $taxonomy, $term_id ) = explode( '|', $term );
  698. $return[] = get_taxonomy( $taxonomy )->labels->name;
  699. }
  700. }
  701. return array_unique( $return );
  702. }
  703. public function get_job_objects( $job ) {
  704. $job = get_post( $job );
  705. $post = get_post_meta( $job->ID, 'bbl_job_post', true );
  706. $terms = get_post_meta( $job->ID, 'bbl_job_term', false );
  707. $return = array();
  708. if ( !empty( $post ) ) {
  709. list( $post_type, $post_id ) = explode( '|', $post );
  710. # @TODO in theory a translation job could actually include more than one post.
  711. # we should implement this earlier rather than later to save potential headaches down the road.
  712. $return['post'] = get_post( $post_id );
  713. }
  714. if ( !empty( $terms ) ) {
  715. foreach ( $terms as $term ) {
  716. list( $taxonomy, $term_id ) = explode( '|', $term );
  717. $return['terms'][$taxonomy][] = get_term( $term_id, $taxonomy );
  718. }
  719. }
  720. return $return;
  721. }
  722. /**
  723. * Create some translation jobs.
  724. *
  725. * @param int $post_id The ID of the post to create translation jobs for
  726. * @param array $lang_codes The language codes to create translation jobs of this post for
  727. * @return array An array of Translation Job posts
  728. **/
  729. public function create_post_jobs( $post_id, array $lang_codes ) {
  730. $post = get_post( $post_id );
  731. // @TODO Validate that the $post is in the default language, otherwise fail
  732. $jobs = array();
  733. foreach ( $lang_codes as $lang_code ) {
  734. if ( bbl_get_default_lang_code() == $lang_code )
  735. continue;
  736. $this->no_recursion = true;
  737. $job = wp_insert_post( array(
  738. 'post_type' => 'bbl_job',
  739. 'post_status' => 'new',
  740. 'post_author' => get_current_user_id(),
  741. 'post_title' => get_the_title( $post ),
  742. ) );
  743. $this->no_recursion = false;
  744. // @TODO If a translation already exists, populate the translation job with the translation
  745. $jobs[] = $job;
  746. add_post_meta( $job, 'bbl_job_post', "{$post->post_type}|{$post->ID}", true );
  747. wp_set_object_terms( $job, $lang_code, 'bbl_job_language' );
  748. foreach ( $this->get_post_terms_to_translate( $post->ID, $lang_code ) as $taxo => $terms ) {
  749. foreach ( $terms as $term_id => $term )
  750. add_post_meta( $job, 'bbl_job_term', "{$taxo}|{$term_id}", false );
  751. }
  752. }
  753. return $jobs;
  754. }
  755. public function get_post_terms_to_translate( $post_id, $lang_code ) {
  756. $post = get_post( $post_id );
  757. $taxos = get_object_taxonomies( $post->post_type );
  758. $trans_terms = array();
  759. foreach ( $taxos as $key => $taxo ) {
  760. if ( !bbl_is_translated_taxonomy( $taxo ) )
  761. continue;
  762. $terms = get_the_terms( $post, $taxo );
  763. if ( empty( $terms ) )
  764. continue;
  765. foreach ( $terms as $term ) {
  766. $trans = bbl_get_term_translations( $term->term_id, $term->taxonomy );
  767. if ( !isset( $trans[$lang_code] ) )
  768. $trans_terms[$taxo][$term->term_id] = $term;
  769. }
  770. }
  771. return $trans_terms;
  772. }
  773. // PRIVATE/PROTECTED METHODS
  774. // =========================
  775. /**
  776. * Called by admin_init, this method ensures we are all up to date and
  777. * so on.
  778. *
  779. * @return void
  780. **/
  781. protected function upgrade() {
  782. }
  783. }
  784. global $bbl_jobs;
  785. $bbl_jobs = new Babble_Jobs();