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