[
  {
    "path": "CONTRIBUTING.md",
    "content": "Contribute\n==========\n\nFirst off: thanks for looking into contributing to Babble, we appreciate your help.\n\nSetting up\n----------\n\n1. Clone this git repository on your WordPress development site.\n2. Switch to the develop branch\n\nSubmitting patches\n------------------\n\nWhether you want to fix a bug or implement a new feature, the process is much the same:\n\n0. [Search existing issues](https://github.com/cftp/babble/issues); if you can't find anything related to what you want to work on, open a new issue so that you can get some initial feedback.\n1. [Fork](https://github.com/cftp/babble/fork) the repository.\n2. Push the code changes from your local clone to your fork.\n3. Open a pull request to the *develop* branch.\n\nIt doesn't matter if the code isn't perfect. The idea is to get it reviewed early and iterate on it.\n\nPlease follow the [WordPress Coding Standards](http://make.wordpress.org/core/handbook/coding-standards/).\n\nLicence\n-------\n\nThe Babble plugin is released under the [GPLv2 or later](http://www.gnu.org/licenses/gpl-2.0.html), \nby contributing code to Babble you grant its use under the GNU General Public License v2 (or later).\n"
  },
  {
    "path": "api.php",
    "content": "<?php\n\n/**\n * Translations and languages API.\n *\n * @package Babble\n * @since Alpha 1\n */\n\n/*  Copyright 2013 Code for the People\n\nThis program is free software; you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation; either version 2 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA\n\n*/\n\n\n/**\n * Returns the current content language code.\n *\n * @FIXME: Currently does not check for language validity, though perhaps we should check that elsewhere and redirect?\n *\n * @return string A language code\n * @access public\n **/\nfunction bbl_get_current_content_lang_code() {\n\tglobal $bbl_locale;\n\treturn $bbl_locale->get_content_lang();\n}\n\n/**\n * Returns the current interface language code.\n *\n * @FIXME: Currently does not check for language validity, though perhaps we should check that elsewhere and redirect?\n *\n * @return string A language code\n * @access public\n **/\nfunction bbl_get_current_interface_lang_code() {\n\tglobal $bbl_locale;\n\treturn $bbl_locale->get_interface_lang();\n}\n\n/**\n * Returns the current (content) language code.\n *\n * @return string A language code\n * @access public\n **/\nfunction bbl_get_current_lang_code() {\n\treturn bbl_get_current_content_lang_code();\n}\n\n/**\n * Given a lang object or lang code, this checks whether the\n * language is public or not.\n *\n * @param string $lang_code A language code\n * @return boolean True if public\n * @access public\n **/\nfunction bbl_is_public_lang( $lang_code ) {\n\tglobal $bbl_languages;\n\treturn $bbl_languages->is_public_lang( $lang_code );\n}\n\n/**\n * Set the current (content) lang.\n * \n * @uses Babble_Locale::switch_to_lang to do the actual work\n * @see switch_to_blog for similarities\n *\n * @param string $lang The language code to switch to \n * @return void\n **/\nfunction bbl_switch_to_lang( $lang ) {\n\tglobal $bbl_locale;\n\t$bbl_locale->switch_to_lang( $lang );\n}\n\n/**\n * Restore the previous lang.\n * \n * @uses Babble_Locale::restore_lang to do the actual work\n * @see restore_current_blog for similarities\n *\n * @return void\n **/\nfunction bbl_restore_lang() {\n\tglobal $bbl_locale;\n\t$bbl_locale->restore_lang();\n}\n\n/**\n * Get the terms which are the translations for the provided \n * term ID. N.B. The returned array of term objects (and false \n * values) will include the term for the term ID passed.\n * \n * @param int|object $term Either a WP Term object, or a term_id \n * @return array Either an array keyed by the site languages, each key containing false (if no translation) or a WP Post object\n * @access public\n **/\nfunction bbl_get_term_translations( $term, $taxonomy ) {\n\tglobal $bbl_taxonomies;\n\treturn $bbl_taxonomies->get_term_translations( $term, $taxonomy );\n}\n\n/**\n * Get the posts which are the translation jobs for the provided \n * term ID.\n * \n * @param int|object $term Either a WP Term object, or a term_id \n * @return array An array keyed by the site languages, each key containing a WP Post object\n * @access public\n **/\nfunction bbl_get_term_jobs( $term, $taxonomy ) {\n\tglobal $bbl_jobs;\n\treturn $bbl_jobs->get_term_jobs( $term, $taxonomy );\n}\n\n/**\n * Return the admin URL to create a new translation for a term in a\n * particular language.\n *\n * @param int|object $default_term The term in the default language to create a new translation for, either WP Post object or post ID\n * @param string $lang The language code \n * @param string $taxonomy The taxonomy\n * @return string The admin URL to create the new translation\n * @access public\n **/\nfunction bbl_get_new_term_translation_url( $default_term, $lang, $taxonomy = null ) {\n\tglobal $bbl_taxonomies;\n\treturn $bbl_taxonomies->get_new_term_translation_url( $default_term, $lang, $taxonomy );\n}\n\n/**\n * Returns the language code associated with a particular taxonomy.\n *\n * @param string $taxonomy The taxonomy to get the language for \n * @return string The lang code\n **/\nfunction bbl_get_taxonomy_lang_code( $taxonomy ) {\n\tglobal $bbl_taxonomies;\n\treturn $bbl_taxonomies->get_taxonomy_lang_code( $taxonomy );\n}\n\n/**\n * Return the base taxonomy (in the default language) for a \n * provided taxonomy.\n *\n * @param string $taxonomy The name of a taxonomy \n * @return string The name of the base taxonomy\n **/\nfunction bbl_get_base_taxonomy( $taxonomy ) {\n\tglobal $bbl_taxonomies;\n\treturn $bbl_taxonomies->get_base_taxonomy( $taxonomy );\n}\n\n/**\n * Returns the equivalent taxonomy in the specified language.\n *\n * @param string $taxonomy A taxonomy to return in a given language\n * @param string $lang_code The language code for the required language (optional, defaults to current)\n * @return string The taxonomy name\n **/\nfunction bbl_get_taxonomy_in_lang( $taxonomy, $lang_code = null ) {\n\tglobal $bbl_taxonomies;\n\treturn $bbl_taxonomies->get_taxonomy_in_lang( $taxonomy, $lang_code );\n}\n\n/**\n * Test whether a particular taxonomy is translated or not.\n * \n * @param string $taxonomy The name of the taxonomy to check\n * @return bool True if this is a translated taxonomy\n */\nfunction bbl_is_translated_taxonomy( $taxonomy ) {\n\treturn (bool) apply_filters( 'bbl_translated_taxonomy', true, $taxonomy );\n}\n\n/**\n * Test whether a particular post type is translated or not.\n * \n * @param string $post_type The name of the post type to check\n * @return bool True if this is a translated post type\n */\nfunction bbl_is_translated_post_type( $post_type ) {\n\treturn (bool) apply_filters( 'bbl_translated_post_type', true, $post_type );\n}\n\n/**\n * Returns a taxonomy slug translated into a particular language.\n *\n * @param string $slug The slug to translate\n * @param string $lang_code The language code for the required language (optional, defaults to current)\n * @return string A translated slug\n **/\nfunction bbl_get_taxonomy_slug_in_lang( $slug, $lang_code = null ) {\n\tglobal $bbl_taxonomies;\n\treturn $bbl_taxonomies->get_slug_in_lang( $slug, $lang_code );\n}\n\n/**\n * Get the posts which are the translations for the provided \n * post ID. N.B. The returned array of post objects (and false \n * values) will include the post for the post ID passed.\n * \n * @param int|object $post Either a WP Post object, or a post ID \n * @return array Either an array keyed by the site languages, each key containing false (if no translation) or a WP Post object\n * @access public\n **/\nfunction bbl_get_post_translations( $post ) {\n\tglobal $bbl_post_public;\n\treturn $bbl_post_public->get_post_translations( $post );\n}\n\n/**\n * Get the posts which are the translation jobs for the provided \n * post ID.\n * \n * @param int|object $post Either a WP Post object, or a post ID \n * @return array Either an array keyed by the site languages, each key containing a WP Post object\n * @access public\n **/\nfunction bbl_get_incomplete_post_jobs( $post ) {\n\tglobal $bbl_jobs;\n\treturn $bbl_jobs->get_incomplete_post_jobs( $post );\n}\n\n/**\n * Returns the post ID for the post in the default language from which \n * this post was translated.\n *\n * @param int|object $post Either a WP Post object, or a post ID \n * @return int The ID of the default language equivalent post\n * @access public\n **/\nfunction bbl_get_default_lang_post( $post ) {\n\tglobal $bbl_post_public;\n\treturn $bbl_post_public->get_default_lang_post( $post );\n}\n\n/**\n * Return the language code for the language a given post is written for/in.\n *\n * @param int|object $post Either a WP Post object, or a post ID \n * @return string|object Either a language code, or a WP_Error object\n * @access public\n **/\nfunction bbl_get_post_lang_code( $post ) {\n\tglobal $bbl_post_public;\n\treturn $bbl_post_public->get_post_lang_code( $post );\n}\n\n/**\n * Return the admin URL to create a new translation for a post in a\n * particular language.\n *\n * @param int|object $default_post The post in the default language to create a new translation for, either WP Post object or post ID\n * @param string $lang The language code \n * @return string The admin URL to create the new translation\n * @access public\n **/\nfunction bbl_get_new_post_translation_url( $default_post, $lang ) {\n\tglobal $bbl_post_public;\n\treturn $bbl_post_public->get_new_post_translation_url( $default_post, $lang );\n}\n\n/**\n * Return the post type name for the equivalent post type for the \n * supplied original post type in the requested language.\n *\n * @param string $post_type The originating post type\n * @param string $lang_code The language code for the required language (optional, defaults to current)\n * @return string A post type name, e.g. \"page\" or \"post\"\n **/\nfunction bbl_get_post_type_in_lang( $original_post_type, $lang_code = null ) {\n\tglobal $bbl_post_public;\n\tif ( is_null( $lang_code ) )\n\t\t$lang_code = bbl_get_current_lang_code();\n\treturn $bbl_post_public->get_post_type_in_lang( $original_post_type, $lang_code );\n}\n\nadd_filter( 'bbl_get_content_post_type', 'bbl_get_post_type_in_lang' );\n\n/**\n * Is the query for a single page or translation or a single page?\n *\n * If the $page parameter is specified, this function will additionally\n * check if the query is for one of the pages specified.\n *\n * @see is_page()\n *\n * @param mixed $page Page ID, title, slug, or array of such.\n * @return bool\n */\nfunction bbl_is_page( $page = '' ) {\n\t$base_page = bbl_get_post_in_lang( get_the_ID(), bbl_get_default_lang_code() );\n\tif ( ! $page )\n\t\treturn 'page' == $base_page->post_type;\n\tif ( is_int( $page ) )\n\t\treturn $page == $base_page->ID;\n\tif ( $page == $base_page->post_name )\n\t\treturn true;\n\tif ( $page == $base_page->post_title )\n\t\treturn true;\n\tif ( $page == (string) $base_page->ID )\n\t\treturn true;\n\treturn false;\n}\n\n/**\n * Returns the post in a particular language\n *\n * @param int|object $post Either a WP Post object, or a post ID \n * @param string $lang_code The language code for the required language \n * @param boolean $fallback If true: if a post is not available, fallback to the default language content (defaults to true)\n * @return object|boolean The WP Post object, or if $fallback was false and no post then returns false\n **/\nfunction bbl_get_post_in_lang( $post, $lang_code, $fallback = true ) {\n\tglobal $bbl_post_public;\n\treturn $bbl_post_public->get_post_in_lang( $post, $lang_code, $fallback );\n}\n\n/**\n * Returns the term in a particular language\n *\n * @param int|object $term Either a term object, or a term ID \n * @param string $taxonomy The term taxonomy\n * @param string $lang_code The language code for the required language \n * @param boolean $fallback If true: if a term is not available, fallback to the default language content (defaults to true)\n * @return object|boolean The term object, or if $fallback was false and no term then returns false\n **/\nfunction bbl_get_term_in_lang( $term, $taxonomy, $lang_code, $fallback = true ) {\n\tglobal $bbl_taxonomies;\n\treturn $bbl_taxonomies->get_term_in_lang( $term, $taxonomy, $lang_code, $fallback );\n}\n\n/**\n * Returns a post_type slug translated into a particular language.\n *\n * @param string $slug The slug to translate\n * @param string $lang_code The language code for the required language (optional, defaults to current)\n * @return string A translated slug\n **/\nfunction bbl_get_post_type_slug_in_lang( $slug, $lang_code = null ) {\n\tglobal $bbl_post_public;\n\t$lang = bbl_get_lang( $lang_code );\n\treturn $bbl_post_public->get_slug_in_lang( $slug, $lang );\n}\n\n/**\n * Echoes the title of a post, in the requested language (if available).\n *\n * @param int|object $post Either a WP Post object, or a post ID \n * @param string $lang_code The code for the language the title is requested in\n * @param bool $fallback Whether to provide a fallback title in the default language if the requested language is unavailable (defaults to false)\n * @return void\n **/\nfunction bbl_the_title_in_lang( $post = null, $lang_code = null, $fallback = false ) {\n\techo bbl_get_the_title_in_lang( $post, $lang_code, $fallback );\n}\n\n/**\n * Returns the title of a post, in the requested language (if available).\n *\n * @param int|object $post Either a WP Post object, or a post ID \n * @param string $lang_code The code for the language the title is requested in\n * @param bool $fallback Whether to provide a fallback title in the default language if the requested language is unavailable (defaults to false)\n * @return void\n **/\nfunction bbl_get_the_title_in_lang( $post = null, $lang_code = null, $fallback = false ) {\n\t$post = get_post( $post );\n\tif ( is_null( $lang_code ) )\n\t\t$lang_code = bbl_get_current_lang_code();\n\n\t// Hopefully we find the post in the right language\n\tif ( $lang_post = bbl_get_post_in_lang( $post, $lang_code, $fallback ) )\n\t\treturn apply_filters( 'bbl_the_title_in_lang', get_the_title( $lang_post->ID ), $lang_code );\n\n\t// We have failed…\n\treturn '';\n}\n\n/**\n * Echoes the permalink of a post, in the requested language (if available).\n *\n * @param int|object $post Either a WP Post object, or a post ID \n * @param string $lang_code The code for the language the title is requested in\n * @param bool $fallback Whether to provide a fallback title in the default language if the requested language is unavailable (defaults to false)\n * @return void\n **/\nfunction bbl_the_permalink_in_lang( $post = null, $lang_code = null, $fallback = false ) {\n\techo bbl_get_the_permalink_in_lang( $post, $lang_code, $fallback );\n}\n\n/**\n * Returns the permalink of a post, in the requested language (if available).\n *\n * @param int|object $post Either a WP Post object, or a post ID \n * @param string $lang_code The code for the language the title is requested in\n * @param bool $fallback Whether to provide a fallback title in the default language if the requested language is unavailable (defaults to false)\n * @return void\n **/\nfunction bbl_get_the_permalink_in_lang( $post = null, $lang_code = null, $fallback = false ) {\n\t$post = get_post( $post );\n\tif ( is_null( $lang_code ) )\n\t\t$lang_code = bbl_get_current_lang_code();\n\n\t// Hopefully we find the post in the right language\n\tif ( $lang_post = bbl_get_post_in_lang( $post, $lang_code, $fallback ) )\n\t\treturn apply_filters( 'bbl_permalink_in_lang', get_permalink( $lang_post->ID ), $lang_code );\n\n\t// We have failed…\n\treturn '';\n}\n\n/**\n * Returns the link to a post type in a particular language.\n *\n * @param string $post_type A post type for which you want a translated archive link\n * @param string $lang_code The code for the language the link is requested in\n * @return void\n **/\nfunction bbl_get_post_type_archive_link_in_lang( $post_type, $lang_code = null ) {\n\tif ( is_null( $lang_code ) )\n\t\t$lang_code = bbl_get_current_lang_code();\n\tbbl_switch_to_lang( $lang_code );\n\t$lang_post_type = bbl_get_post_type_in_lang( $post_type, $lang_code );\n\t$link = get_post_type_archive_link( $lang_post_type );\n\tbbl_restore_lang();\n\treturn apply_filters( 'bbl_post_type_archive_link_in_lang', $link );\n}\n\n/**\n * Return the base post type (in the default language) for a \n * provided post type.\n *\n * @param string $post_type The name of a post type \n * @return string The name of the base post type\n **/\nfunction bbl_get_base_post_type( $post_type ) {\n\tglobal $bbl_post_public;\n\treturn $bbl_post_public->get_base_post_type( $post_type );\n}\n\n/**\n * Return all the base post types (in the default language).\n *\n * @return array An array of post_type objects\n **/\nfunction bbl_get_base_post_types() {\n\tglobal $bbl_post_public;\n\treturn $bbl_post_public->get_base_post_types();\n}\n\n/**\n * Returns an array of all the shadow post types associated with\n * this post type.\n *\n * @param string $base_post_type The post type to look up shadow post types for \n * @return array The names of all the related shadow post types\n **/\nfunction bbl_get_shadow_post_types( $base_post_type ) {\n\tglobal $bbl_post_public;\n\treturn $bbl_post_public->get_shadow_post_types( $base_post_type );\n}\n\n/**\n * Return the active language objects for the current site. A\n * language object looks like:\n * 'ar' => \n * \t\tobject(stdClass)\n * \t\t\tpublic 'name' => string 'Arabic'\n * \t\t\tpublic 'code' => string 'ar'\n * \t\t\tpublic 'url_prefix' => string 'ar'\n * \t\t\tpublic 'text_direction' => string 'rtl'\n * \t\t\tpublic 'display_name' => string 'Arabic'\n * \n * @uses Babble_Languages::get_active_langs to do the actual work\n *\n * @return array An array of Babble language objects\n **/\nfunction bbl_get_active_langs() {\n\tglobal $bbl_languages;\n\treturn $bbl_languages->get_active_langs();\n}\n\n/**\n * Returns the requested language object.\n *\n * @param string $code A language code, e.g. \"fr_BE\" \n * @return object|boolean A Babble language object\n **/\nfunction bbl_get_lang( $lang_code ) {\n\tglobal $bbl_languages;\n\treturn $bbl_languages->get_lang( $lang_code );\n}\n\n/**\n * Returns the current language object, respecting any\n * language switches; i.e. if your request was for\n * Arabic, but the language is currently switched to\n * French, this will return French.\n *\n * @return object A Babble language object\n **/\nfunction bbl_get_current_lang() {\n\tglobal $bbl_languages;\n\treturn $bbl_languages->get_current_lang();\n}\n\n/**\n * Returns the default language code for this site.\n *\n * @return string A language code, e.g. \"he_IL\"\n **/\nfunction bbl_get_default_lang_code() {\n\tglobal $bbl_languages;\n\treturn $bbl_languages->get_default_lang_code();\n}\n\n/**\n * Returns the default language for this site.\n *\n * @return object A language object\n **/\nfunction bbl_get_default_lang() {\n\tglobal $bbl_languages;\n\treturn $bbl_languages->get_default_lang();\n}\n\n/**\n * Checks whether either the provided language code, \n * if provided, or the current language code are\n * the default language.\n * \n * i.e. is this language the default language\n *\n * n.b. the current language could have been switched\n * using bbl_switch_to_lang\n *\n * @param string $lang_code The language code to check (optional) \n * @return bool True if the default language\n **/\nfunction bbl_is_default_lang( $lang_code = null ) {\n\tif ( is_null( $lang_code ) )\n\t\t$lang = bbl_get_current_lang();\n\telse if ( is_string( $lang_code ) ) // In case someone passes a lang object\n\t\t$lang = bbl_get_lang( $lang_code );\n\treturn ( bbl_get_default_lang_code() == $lang->code );\n}\n\n/**\n * Returns the default language code for this site.\n *\n * @return string The language URL prefix set by the admin, e.g. \"de\"\n **/\nfunction bbl_get_default_lang_url_prefix() {\n\tglobal $bbl_languages;\n\t$code = $bbl_languages->get_default_lang_code();\n\treturn $bbl_languages->get_url_prefix_from_code( $code );\n}\n\n/**\n * Returns the language code for the provided URL prefix.\n *\n * @param string $url_prefix The URL prefix to find the language code for \n * @return string The language code, or false\n **/\nfunction bbl_get_lang_from_prefix( $url_prefix ) {\n\tglobal $bbl_languages;\n\treturn $bbl_languages->get_code_from_url_prefix( $url_prefix );\n}\n\n/**\n * Returns the language code for the provided URL prefix.\n *\n * @param string $lang_code The language code to look up\n * @return string The language URL prefix set by the admin, e.g. \"de\"\n **/\nfunction bbl_get_prefix_from_lang_code( $lang_code ) {\n\tglobal $bbl_languages;\n\treturn $bbl_languages->get_url_prefix_from_code( $lang_code );\n}\n\n/**\n * Returns the switch links for the current content.\n *\n * @param string $id_prefix A prefix to the ID for each item\n * @return array An array of admin menu nodes\n **/\nfunction bbl_get_switcher_links( $id_prefix = '' ) {\n\tglobal $bbl_switcher_menu;\n\treturn $bbl_switcher_menu->get_switcher_links( $id_prefix );\n}\n\n/**\n * Start logging for Babble\n *\n * @return void\n **/\nfunction bbl_start_logging() {\n\tglobal $bbl_log;\n\t$bbl_log->logging = true;\n}\n\n/**\n * Stop logging for Babble\n *\n * @return void\n **/\nfunction bbl_stop_logging() {\n\tglobal $bbl_log;\n\t$bbl_log->logging = false;\n}\n\n/**\n * Log a message.\n *\n * @param string $msg Log this message \n * @return void\n **/\nfunction bbl_log( $msg ) {\n\tglobal $bbl_log;\n\tif ( $bbl_log )\n\t\t$bbl_log->log( $msg );\n\telse\n\t\terror_log( \"Full Babble logging unavailable: $msg\" );\n}\n\n/**\n * Whether Babble is logging right now.\n *\n * @return boolean True for yes, natch\n **/\nfunction bbl_is_logging() {\n\tglobal $bbl_log;\n\treturn $bbl_log->logging;\n}\n\n?>"
  },
  {
    "path": "babble.php",
    "content": "<?php\n/*\nPlugin Name: Babble\nPlugin URI:  http://babbleplugin.com/\nDescription: Multilingual WordPress done right\nVersion:     1.5.1\nAuthor:      Automattic\nAuthor URI:  https://automattic.com/\nText Domain: babble\nDomain Path: /languages/\nLicense:     GPL v2 or later\n\nCopyright 2011-2015 Simon Wheatley, Code For The People Ltd, & Automattic Ltd\n\n                _____________\n               /      ____   \\\n         _____/       \\   \\   \\\n        /\\    \\        \\___\\   \\\n       /  \\    \\                \\\n      /   /    /          _______\\\n     /   /    /          \\       /\n    /   /    /            \\     /\n    \\   \\    \\ _____    ___\\   /\n     \\   \\    /\\    \\  /       \\\n      \\   \\  /  \\____\\/    _____\\\n       \\   \\/        /    /    / \\\n        \\           /____/    /___\\\n         \\                        /\n          \\______________________/\n\n\nThis program is free software; you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation; either version 2 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA\n\n*/\n\n/**\n * Main plugin information and requires.\n *\n * @package Babble\n * @since Alpha 1\n * @copyright Copyright (c) Simon Wheatley & Code For The People Ltd (except where noted)\n */\n\nrequire_once 'class-babble-log.php';\n\nrequire_once 'api.php';\nrequire_once 'deprecated.php';\nrequire_once 'widget.php';\n\nrequire_once 'class-plugin.php';\n\nrequire_once 'class-jobs.php';\nrequire_once 'class-meta.php';\nrequire_once 'class-languages.php';\nrequire_once 'class-locale.php';\nrequire_once 'class-post-public.php';\nrequire_once 'class-comment.php';\nrequire_once 'class-taxonomy.php';\nrequire_once 'class-switcher-content.php';\nrequire_once 'class-switcher-interface.php';\nrequire_once 'class-admin-bar.php';\nrequire_once 'class-translator.php';\nrequire_once 'class-updates.php';\n\nrequire_once 'miscellaneous.php';\n"
  },
  {
    "path": "class-admin-bar.php",
    "content": "<?php\n\n/**\n * Class to handle adding our links to the admin bar.\n *\n * @package Babble\n * @since 0.2\n */\nclass Babble_Admin_bar extends Babble_Plugin {\n\t\n\tfunction __construct() {\n\t\t$this->setup( 'babble-switcher-menu', 'plugin' );\n\t\t$this->add_action( 'admin_bar_menu', null, 100 );\n\t}\n\n\t/**\n\t * Hooks the WP admin_bar_menu action \n\t *\n\t * @param object $wp_admin_bar The WP Admin Bar, passed by reference\n\t * @return void\n\t **/\n\tpublic function admin_bar_menu( $wp_admin_bar ) {\n\t\t$links = bbl_get_switcher_links( 'bbl-admin-bar' );\n\t\t\n\t\t$current_lang = bbl_get_current_lang();\n\n\t\t// Remove the current language\n\t\tunset( $links[ $current_lang->code ] );\n\t\t\n\t\t$parent_id = \"bbl-admin-bar-{$current_lang->url_prefix}\";\n\t\t$wp_admin_bar->add_menu( array(\n\t\t\t'children' => array(),\n\t\t\t'href' => '#',\n\t\t\t'id' => $parent_id,\n\t\t\t'meta' => array( 'class' => \"bbl_lang_{$current_lang->code} bbl_lang\" ),\n\t\t\t'title' => $current_lang->display_name,\n\t\t\t'parent' => false,\n\t\t) );\n\t\tforeach ( $links as & $link ) {\n\t\t\t$link[  'parent' ] = $parent_id;\n\t\t\t$wp_admin_bar->add_menu( $link );\n\t\t}\n\t}\n\t\n}\n\nglobal $bbl_admin_bar;\n$bbl_admin_bar = new Babble_Admin_bar();\n"
  },
  {
    "path": "class-babble-log.php",
    "content": "<?php\n\n/**\n * Manages the locale currently set for the site.\n *\n * @package Babble\n * @since Alpha 1\n */\nclass Babble_Log {\n\t\n\t/**\n\t * Whether to log or not.\n\t *\n\t * @var boolean\n\t **/\n\tpublic $logging = false;\n\n\t/**\n\t * A unique ID so we can identify different sessions in \n\t * the error log.\n\t *\n\t * @var string\n\t **/\n\tprotected $session;\n\t\n\t/**\n\t * Construction time!\n\t *\n\t * @return void\n\t **/\n\tpublic function __construct() {\n\t\t$this->session = uniqid();\n\t}\n\n\t/**\n\t * Hooks the WP admin_init action \n\t *\n\t * @return void\n\t **/\n\tpublic function log( $msg ) {\n\t\tif ( $this->logging )\n\t\t\terror_log( \"[$this->session] BABBLE LOG: $msg\" );\n\t}\n\n}\n\nglobal $bbl_log;\n$bbl_log = new Babble_Log();\n\n?>"
  },
  {
    "path": "class-comment.php",
    "content": "<?php\n\n/**\n * Class for handling comments.\n *\n * @package Babble\n * @since 0.1\n */\nclass Babble_Comment extends Babble_Plugin {\n\t\n\tpublic function __construct() {\n\t\t$this->setup( 'babble-comment', 'plugin' );\n\n\t\t$this->add_filter( 'comments_template_args' );\n\t\t$this->add_filter( 'preprocess_comment' );\n\t\t$this->add_filter( 'get_comments_number', null, null, 2 );\n\t}\n\n\t/**\n\t * Hooks the comments_template_args on Bbl_Comment_Query,\n\t * and hopefully soon on WP_Comment_Query (Trac #19623),\n\t * in order to ensure we get the comments from all the\n\t * translated posts in this translation group.\n\t *\n\t * @param array $args The args for WP_Comment_Query in comments_template \n\t * @return array The args for WP_Comment_Query in comments_template \n\t **/\n\tpublic function comments_template_args( $args ) {\n\t\tif ( isset( $args[ 'post_id' ] ) && ! empty( $args[ 'post_id' ] ) ) {\n\t\t\t$posts = bbl_get_post_translations( $args[ 'post_id' ] );\n\t\t\tif ( isset( $args[ 'post__in' ] ) && ! is_array( $args[ 'post__in' ] ) )\n\t\t\t\t$args[ 'post__in' ] = array();\n\t\t\tforeach ( $posts as & $post )\n\t\t\t\t$args[ 'post__in' ][] = $post->ID;\n\t\t\tunset( $args[ 'post_id' ] );\n\t\t}\n\t\treturn $args;\n\t}\n\n\t/**\n\t * Hooks the WP preprocess_comment filter to ensure that when someone\n\t * replies to a comment which has been included in a merged comment\n\t * stream on a post in a different language, the reply is assigned \n\t * language post of the parent comment.\n\t *\n\t * @param array $comment_data The comment data  \n\t * @return void\n\t **/\n\tpublic function preprocess_comment( $comment_data ) {\n\t\t// If comment_post_ID exists in the data, the only acceptable\n\t\t// value is the same as the parent comment's comment_post_ID\n\t\t$parent_comment = get_comment( $comment_data[ 'comment_parent' ] );\n\t\tif ( $parent_comment && $comment_data[ 'comment_post_ID' ] )\n\t\t\t$comment_data[ 'comment_post_ID' ] = $parent_comment->comment_post_ID;\n\t\treturn $comment_data;\n\t}\n\n\t/**\n\t * Hooks the WP get_comments_number filter to get the number of comments \n\t * across all posts in the translation group.\n\t *\n\t * @param int $count The number of comments on the single translation\n\t * @param int $post_id The post ID of the single translation \n\t * @return int The count of all comments on published posts in this translation group\n\t **/\n\tpublic function get_comments_number( $count, $post_id ) {\n\t\t$translations = bbl_get_post_translations( $post_id );\n\t\t$count = 0;\n\t\tforeach ( $translations as & $translation ) {\n\t\t\t$post_status = get_post_status_object( $translation->post_status );\n\t\t\t// FIXME: I'm not entirely sure about using publicly_queryable here… what I want to avoid is draft, private, etc statii.\n\t\t\tif ( $post_status->publicly_queryable )\n\t\t\t\t$count += $translation->comment_count;\n\t\t}\n\t\treturn $count;\n\t}\n\t\n\t// PUBLIC METHODS\n\t// ==============\n\n\t\n\t// PRIVATE/PROTECTED METHODS\n\t// =========================\n\n}\n\nglobal $bbl_comment;\n$bbl_comment = new Babble_Comment();\n\n?>"
  },
  {
    "path": "class-jobs.php",
    "content": "<?php\n/**\n * Class for handling jobs for the various language\n * translation teams.\n *\n * @package Babble\n * @since 1.4\n */\nclass Babble_Jobs extends Babble_Plugin {\n\t\n\t/**\n\t * A version number used for cachebusting, rewrite rule\n\t * flushing, etc.\n\t *\n\t * @var int\n\t **/\n\tprotected $version;\n\n\t/**\n\t * A simple flag to stop infinite recursion in various places.\n\t *\n\t * @var boolean\n\t **/\n\tprotected $no_recursion;\n\t\n\tpublic function __construct() {\n\t\t$this->setup( 'babble-job', 'plugin' );\n\t\t\n\t\t$this->add_action( 'add_meta_boxes' );\n\t\t$this->add_action( 'add_meta_boxes_bbl_job', null, 999 );\n\t\t$this->add_action( 'admin_init' );\n\t\t$this->add_action( 'admin_menu' );\n\t\t$this->add_action( 'babble_create_empty_translation', 'create_empty_translation' );\n\t\t$this->add_action( 'bbl_translation_post_meta_boxes', null, 10, 3 );\n\t\t$this->add_action( 'bbl_translation_submit_meta_boxes', null, 10, 2 );\n\t\t$this->add_action( 'bbl_translation_terms_meta_boxes', null, 10, 2 );\n\t\t$this->add_action( 'bbl_translation_meta_meta_boxes', null, 10, 2 );\n\t\t$this->add_action( 'edit_form_after_title' );\n\t\t$this->add_action( 'init', 'init_early', 0 );\n\t\t$this->add_action( 'load-post.php', 'load_post_edit' );\n\t\t$this->add_action( 'manage_bbl_job_posts_custom_column', 'action_column', null, 2 );\n\t\t$this->add_action( 'pre_get_posts' );\n\t\t$this->add_action( 'save_post', 'save_job', null, 2 );\n\t\t$this->add_action( 'save_post', null, null, 2 );\n\t\t$this->add_action( 'wp_before_admin_bar_render' );\n\n\t\t$this->add_filter( 'admin_title', null, null, 2 );\n\t\t$this->add_filter( 'bbl_translated_post_type', null, null, 2 );\n\t\t$this->add_filter( 'bbl_translated_taxonomy', null, null, 2 );\n\t\t$this->add_filter( 'get_edit_post_link', null, null, 3 );\n\t\t$this->add_filter( 'manage_bbl_job_posts_columns', 'filter_columns' );\n\t\t$this->add_filter( 'post_updated_messages' );\n\t\t$this->add_filter( 'query_vars' );\n\t\t$this->add_filter( 'user_has_cap', null, null, 3 );\n\t\t$this->add_filter( 'wp_insert_post_empty_content', null, null, 2 );\n\n\t\t$this->version = 1.1;\n\t}\n\n\tpublic function add_meta_boxes_bbl_job( WP_Post $post ) {\n\n\t\t# Unapologetically remove all meta boxes from the translation screen:\n\n\t\tglobal $wp_meta_boxes;\n\t\tunset( $wp_meta_boxes['bbl_job'] );\n\n\t}\n\n\tpublic function wp_insert_post_empty_content( $maybe_empty, $postarr ) {\n\t\t// Allow translations to have empty content\n\t\tif ( bbl_get_base_post_type( $postarr['post_type'] ) != $postarr['post_type'] )\n\t\t\treturn false;\n\t\treturn $maybe_empty;\n\t}\n\n\tpublic function bbl_translated_post_type( $translated, $post_type ) {\n\t\tif ( 'bbl_job' == $post_type )\n\t\t\treturn false;\n\t\treturn $translated;\n\t}\n\n\tpublic function bbl_translated_taxonomy( $translated, $taxonomy ) {\n\t\tif ( 'bbl_job_language' == $taxonomy )\n\t\t\treturn false;\n\t\treturn $translated;\n\t}\n\n\t/**\n\t * Add our post type updated messages.\n\t *\n\t * The messages are as follows:\n\t *\n\t *   1 => \"Post updated. {View Post}\"\n\t *   2 => \"Custom field updated.\"\n\t *   3 => \"Custom field deleted.\"\n\t *   4 => \"Post updated.\"\n\t *   5 => \"Post restored to revision from [date].\"\n\t *   6 => \"Post published. {View post}\"\n\t *   7 => \"Post saved.\"\n\t *   8 => \"Post submitted. {Preview post}\"\n\t *   9 => \"Post scheduled for: [date]. {Preview post}\"\n\t *  10 => \"Post draft updated. {Preview post}\"\n\t *\n\t * @param array $messages An associative array of post updated messages with post type as keys.\n\t * @return array Updated array of post updated messages.\n\t */\n\tpublic function post_updated_messages( array $messages ) {\n\n\t\t$messages['bbl_job'] = array(\n\t\t\t1  => __( 'Translation job updated.', 'babble' ),\n\t\t\t4  => __( 'Translation job updated.', 'babble' ),\n\t\t\t8  => __( 'Translation job submitted.', 'babble' ),\n\t\t\t10 => __( 'Translation job draft updated.', 'babble' ),\n\t\t);\n\n\t\treturn $messages;\n\n\t}\n\n\t/**\n\t * Hooks the WP admin_init action to enqueue some stuff.\n\t *\n\t * @return void\n\t **/\n\tpublic function admin_init() {\n\t\twp_enqueue_style( 'bbl-jobs-admin', $this->url( 'css/jobs-admin.css' ), array(), filemtime( $this->dir( 'css/jobs-admin.css' ) ) );\n\t}\n\n\t/**\n\t * Hooks the WP action load-post.php to detect people\n\t * trying to edit translated posts, and instead kick \n\t * redirect them to an existing translation job or\n\t * create a translation job and direct them to that.\n\t *\n\t * @TODO this should be in the post-public class\n\t * \n\t * @action load-post.php\n\t * \n\t * @return void\n\t **/\n\tpublic function load_post_edit() {\n\t\t$post_id = isset( $_GET[ 'post' ] ) ? absint( $_GET[ 'post' ] ) : false;\n\t\tif ( ! $post_id )\n\t\t\t$post_id = isset( $_POST[ 'post_ID' ] ) ? absint( $_POST[ 'post_ID' ] ) : false;\n\t\t$translated_post = get_post( $post_id );\n\t\tif ( ! $translated_post )\n\t\t\treturn;\n\t\tif ( ! bbl_is_translated_post_type( $translated_post->post_type ) )\n\t\t\treturn;\n\t\t$canonical_post = bbl_get_default_lang_post( $translated_post );\n\t\t$lang_code = bbl_get_post_lang_code( $translated_post );\n\t\tif ( bbl_get_default_lang_code() == $lang_code )\n\t\t\treturn;\n\t\t// @TODO Check capabilities include editing a translation post\n\t\t// - If not, the button shouldn't be on the Admin Bar\n\t\t// - But we also need to not process at this point\n\t\t$existing_jobs = $this->get_incomplete_post_jobs( $canonical_post );\n\t\tif ( isset( $existing_jobs[ $lang_code ] ) ) {\n\t\t\t$url = get_edit_post_link( $existing_jobs[ $lang_code ], 'url' );\n\t\t\twp_redirect( $url );\n\t\t\texit;\n\t\t}\n\t\t// Create a new translation job for the current language\n\t\t$lang_codes = array( $lang_code );\n\t\t$jobs = $this->create_post_jobs( $canonical_post, $lang_codes );\n\t\t// Redirect to the translation job\n\t\t$url = get_edit_post_link( $jobs[0], 'url' );\n\t\twp_redirect( $url );\n\t\texit;\n\t}\n\n\t/**\n\t * Hooks the WP admin_title filter to give some context to the\n\t * page titles.\n\t *\n\t * @filter admin_title\n\t *\n\t * @param string $admin_title The admin title (for the TITLE element)\n\t * @param string $title The title used in the H2 element above the edit form\n\t * @return string The admin title\n\t **/\n\tpublic function admin_title( $admin_title, $title ) {\n\t\t$screen = get_current_screen();\n\t\tif ( 'post' == $screen->base && 'bbl_job' == $screen->post_type ) {\n\t\t\t$pto = get_post_type_object( 'bbl_job' );\n\t\t\t$job = get_post();\n\t\t\tif ( 'add' == $screen->action ) {\n\t\t\t\tif ( isset( $_GET['lang'] ) ) {\n\t\t\t\t\t$lang = bbl_get_lang( $_GET['lang'] );\n\t\t\t\t\t$admin_title = sprintf( $pto->labels->add_item_context, $lang->display_name );\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t$lang = $this->get_job_language( $job );\n\t\t\t\t$admin_title = sprintf( $pto->labels->edit_item_context, $lang->display_name );\n\t\t\t}\n\t\t\t$GLOBALS[ 'title' ] = $admin_title;\n\t\t}\n\t\treturn $admin_title;\n\t}\n\n\t/**\n\t * Filters the public query vars and adds some of our own\n\t *\n\t * @filter query_vars\n\t * @param  array $vars Public query vars\n\t * @return array Updated public query vars\n\t */\n\tpublic function query_vars( array $vars ) {\n\t\tif ( is_admin() ) {\n\t\t\t$vars[] = 'bbl_job_post';\n\t\t\t$vars[] = 'bbl_job_term';\n\t\t\t$vars[] = 'bbl_job_meta';\n\t\t}\n\t\treturn $vars;\n\t}\n\n\t/**\n\t * Filter the user's capabilities so they can be added/removed on the fly.\n\t *\n\t * @TODO description of what this does\n\t *\n\t * @filter user_has_cap\n\t * @param array $user_caps     User's capabilities\n\t * @param array $required_caps Actual required capabilities for the requested capability\n\t * @param array $args          Arguments that accompany the requested capability check:\n\t *                             [0] => Requested capability from current_user_can()\n\t *                             [1] => Current user ID\n\t *                             [2] => Optional second parameter from current_user_can()\n\t * @return array User's capabilities\n\t */\n\tpublic function user_has_cap( array $user_caps, array $required_caps, array $args ) {\n\n\t\t$user = new WP_User( $args[1] );\n\n\t\tswitch ( $args[0] ) {\n\n\t\t\tcase 'edit_post':\n\t\t\tcase 'edit_bbl_job':\n\t\t\tcase 'delete_post':\n\t\t\tcase 'delete_bbl_job':\n\t\t\tcase 'publish_post':\n\t\t\tcase 'publish_bbl_job':\n\n\t\t\t\t$job = get_post( $args[2] );\n\n\t\t\t\tif ( ! $job or ( 'bbl_job' != $job->post_type ) ) {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\t$objects = $this->get_job_objects( $job );\n\t\t\t\t$pto     = get_post_type_object( $job->post_type );\n\t\t\t\t$cap     = str_replace( 'bbl_job', 'post', $args[0] );\n\n\t\t\t\tif ( isset( $objects['post'] ) && $objects['post']->post_type != 'bbl_job' ) {\n\n\t\t\t\t\t# This directly maps the ability to edit/delete/publish the job with the ability to do the same to the job's post:\n\n\t\t\t\t\t$can = user_can( $user, $cap, $objects['post']->ID );\n\t\t\t\t\tforeach ( $required_caps as $required ) {\n\t\t\t\t\t\tif ( ! isset( $user_caps[$required] ) ) {\n\t\t\t\t\t\t\t$user_caps[$required] = $can;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t} else { # else if isset object terms\n\n\t\t\t\t}\n\n\t\t\t\tbreak;\n\n\t\t\tcase 'edit_bbl_jobs':\n\n\t\t\t\t# Special case for displaying the admin menu:\n\n\t\t\t\t# By default, Translators will have this cap:\n\t\t\t\tif ( isset( $user_caps[$args[0]] ) )\n\t\t\t\t\tbreak;\n\n\t\t\t\t# 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\n\n\t\t\t\tforeach ( get_post_types( array( 'show_ui' => true ), 'objects' ) as $pto ) {\n\t\t\t\t\t// Don't check the capability we already checked.\n\t\t\t\t\tif ( $args[0] == $pto->cap->edit_posts ) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tif ( user_can( $user, $pto->cap->edit_posts ) ) {\n\t\t\t\t\t\t$user_caps[$args[0]] = true;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tbreak;\n\n\t\t}\n\n\t\treturn $user_caps;\n\n\t}\n\n\t/**\n\t * Hooks the WP pre_get_posts ref action in the WP_Query. Sets the meta query\n\t * that's necessary for filtering jobs by their objects.\n\t *\n\t * @param WP_Query $wp_query A WP_Query object, passed by reference\n\t * @return void (param passed by reference)\n\t **/\n\tpublic function pre_get_posts( WP_Query & $query ) {\n\n\t\tif ( $job_post = $query->get( 'bbl_job_post' ) ) {\n\t\t\t$query->set( 'meta_key', 'bbl_job_post' );\n\t\t\t$query->set( 'meta_value', $job_post );\n\t\t} else if ( $job_term = $query->get( 'bbl_job_term' ) ) {\n\t\t\t$query->set( 'meta_key', 'bbl_job_term' );\n\t\t\t$query->set( 'meta_value', $job_term );\n\t\t} else if ( $job_meta = $query->get( 'bbl_job_meta' ) ) {\n\t\t\t$query->set( 'meta_key', 'bbl_job_meta' );\n\t\t\t$query->set( 'meta_value', $job_meta );\n\t\t}\n\n\t}\n\n\t/**\n\t * Hooks the WP filter get_edit_post_link\n\t *\n\t * @filter get_edit_post_link\n\t * @param string $url The edit post link URL\n\t * @param int $post_ID The ID of the post to edit\n\t * @param string $context The link context.\n\t *\n\t * @return string The edit post link URL\n\t * @author Simon Wheatley\n\t **/\n\tpublic function get_edit_post_link( $url, $post_ID, $context ) {\n\t\tif ( $this->no_recursion ) {\n\t\t\treturn $url;\n\t\t}\n\t\tif ( bbl_get_default_lang_code() == bbl_get_post_lang_code( $post_ID ) ) {\n\t\t\treturn $url;\n\t\t}\n\n\t\t$completed_jobs = $this->get_completed_post_jobs( bbl_get_default_lang_post( $post_ID ) );\n\t\tif ( ! isset( $completed_jobs[ bbl_get_current_lang_code() ] ) ) {\n\t\t\treturn $url;\n\t\t}\n\t\t$job = $completed_jobs[ bbl_get_current_lang_code() ];\n\n\t\tif ( ! current_user_can( 'publish_post', $job->ID ) ) {\n\t\t\treturn $url;\n\t\t}\n\n\t\t$this->no_recursion = true;\n\t\t$url = get_edit_post_link( $completed_jobs[ bbl_get_current_lang_code() ]->ID );\n\t\t$this->no_recursion = false;\n\t\treturn $url;\n\t}\n\n\tpublic function edit_form_after_title() {\n\n\t\t$screen = get_current_screen();\n\n\t\tif ( 'bbl_job' != $screen->post_type ) {\n\t\t\treturn;\n\t\t}\n\n\t\t$job   = get_post();\n\t\t$items = $objects = $vars = array();\n\n\t\tif ( ( 'add' == $screen->action ) and isset( $_GET['lang'] ) ) {\n\n\t\t\t$vars['lang_code'] = stripslashes( $_GET['lang'] );\n\n\t\t\tif ( isset( $_GET['bbl_origin_post'] ) ) {\n\n\t\t\t\t$post  = get_post( absint( $_GET['bbl_origin_post' ] ) );\n\t\t\t\t$terms = $this->get_post_terms_to_translate( $post, $_GET['lang'] );\n\t\t\t\t$meta  = $this->get_post_meta_to_translate( $post, $_GET['lang'] );\n\t\t\t\t$objects['post'] = $post;\n\n\t\t\t\tif ( !empty( $terms ) ) {\n\t\t\t\t\t$objects['terms'] = $terms;\n\t\t\t\t}\n\n\t\t\t\tif ( !empty( $meta ) ) {\n\t\t\t\t\t$objects['meta'] = $meta;\n\t\t\t\t}\n\n\t\t\t\t$vars['origin_post'] = $post->ID;\n\n\t\t\t} else if ( isset( $_GET['bbl_origin_term'] ) and isset( $_GET['bbl_origin_taxonomy'] ) ) {\n\n\t\t\t\t$term = get_term( $_GET['bbl_origin_term'], $_GET['bbl_origin_taxonomy'] );\n\t\t\t\t$objects['terms'][$term->taxonomy][$term->term_id] = $term;\n\t\t\t\t$vars['origin_term']     = $term->term_id;\n\t\t\t\t$vars['origin_taxonomy'] = $term->taxonomy;\n\n\t\t\t}\n\n\t\t} else {\n\n\t\t\t$objects = $this->get_job_objects( $job );\n\n\t\t}\n\n\t\tif ( isset( $objects['post'] ) ) {\n\n\t\t\t$post = $objects['post'];\n\t\t\t$post_translation = get_post_meta( $job->ID, \"bbl_post_{$post->ID}\", true );\n\n\t\t\tif ( empty( $post_translation ) ) {\n\t\t\t\t$post_translation = get_default_post_to_edit( $post->post_type );\n\t\t\t}\n\n\t\t\t$items['post'] = array(\n\t\t\t\t'original'    => $post,\n\t\t\t\t'translation' => (object) $post_translation,\n\t\t\t);\n\n\t\t}\n\n\t\tif ( isset( $objects['meta'] ) ) {\n\n\t\t\tforeach ( $objects['meta'] as $meta_key => $meta_field ) {\n\n\t\t\t\t$meta_translation = get_post_meta( $job->ID, \"bbl_meta_{$meta_key}\", true );\n\n\t\t\t\tif ( empty( $meta_translation ) ) {\n\t\t\t\t\t$meta_translation = '';\n\t\t\t\t}\n\n\t\t\t\t$items['meta'][$meta_key] = array(\n\t\t\t\t\t'original'    => $meta_field,\n\t\t\t\t\t'translation' => $meta_translation,\n\t\t\t\t);\n\n\t\t\t}\n\n\t\t}\n\n\t\tif ( isset( $objects['terms'] ) ) {\n\n\t\t\tforeach ( $objects['terms'] as $taxo => $terms ) {\n\n\t\t\t\tforeach ( $terms as $term ) {\n\n\t\t\t\t\t$term_translation = get_post_meta( $job->ID, \"bbl_term_{$term->term_id}\", true );\n\n\t\t\t\t\tif ( empty( $term_translation ) ) {\n\t\t\t\t\t\t$term_translation = array( 'name' => '', 'slug' => '' );\n\t\t\t\t\t}\n\n\t\t\t\t\t$items['terms'][$taxo][] = array(\n\t\t\t\t\t\t'original'    => $term,\n\t\t\t\t\t\t'translation' => (object) $term_translation,\n\t\t\t\t\t);\n\n\t\t\t\t}\n\n\t\t\t}\n\n\t\t}\n\n\t\t$statuses = array(\n\t\t\t'in-progress' => get_post_status_object( 'in-progress' )->label,\n\t\t);\n\n\t\tif ( ( 'pending' == $job->post_status ) or !current_user_can( 'publish_post', $job->ID ) ) {\n\t\t\t$statuses['pending'] = get_post_status_object( 'pending' )->label;\n\t\t}\n\n\t\tif ( current_user_can( 'publish_post', $job->ID ) ) {\n\t\t\t$statuses['complete'] = get_post_status_object( 'complete' )->label;\n\t\t}\n\n\t\t$statuses = apply_filters( 'bbl_job_statuses', $statuses, $job, $objects );\n\n\t\t$vars['job']      = $job;\n\t\t$vars['items']    = $items;\n\t\t$vars['statuses'] = $statuses;\n\n\t\t$this->render_admin( 'translation-editor.php', $vars );\n\n\t}\n\n\tpublic function admin_menu() {\n\t\t# Remove the 'Add New' submenu for Translations.\n\t\tremove_submenu_page( 'edit.php?post_type=bbl_job', 'post-new.php?post_type=bbl_job' );\n\t}\n\n\tpublic function wp_before_admin_bar_render() {\n\t\tglobal $wp_admin_bar;\n\t\t# Remove the '+New -> Translation Job' admin bar menu.\n\t\t$wp_admin_bar->remove_node( 'new-bbl_job' );\n\t}\n\n\t/**\n\t * undocumented function\n\t *\n\t * @param  \n\t * @return void\n\t **/\n\tpublic function add_meta_boxes( $post_type ) {\n\n\t\t if ( bbl_is_translated_post_type( $post_type ) ) {\n\t\t\tadd_meta_box( 'bbl_translations', _x( 'Translations', 'Translations meta box title', 'babble' ), array( $this, 'metabox_post_translations' ), $post_type, 'side', 'high' );\n\t\t}\n\n\t}\n\n\tpublic function bbl_translation_post_meta_boxes( $type, $original, $translation ) {\n\n\t\tif ( !empty( $original->post_excerpt ) or !empty( $translation->post_excerpt ) ) {\n\t\t\tadd_meta_box( 'postexcerpt', __( 'Excerpt', 'babble' ), array( $this, 'metabox_translation_post_excerpt' ), $type, 'post' );\n\t\t}\n\n\t}\n\n\tpublic function bbl_translation_terms_meta_boxes( $type, $items ) {\n\n\t\tforeach ( $items as $taxo => $terms ) {\n\t\t\t$tax = get_taxonomy( $taxo );\n\t\t\tadd_meta_box( \"{$taxo}_terms\", $tax->labels->name, array( $this, 'metabox_translation_terms' ), $type, $taxo );\n\t\t}\n\n\t}\n\n\tpublic function bbl_translation_meta_meta_boxes( $type, $items ) {\n\n\t\t$i = 0;\n\n\t\tforeach ( $items as $meta_key => $meta_field ) {\n\t\t\tadd_meta_box( \"meta_{$i}\", esc_html( $meta_field['original']->get_title() ), array( $this, 'metabox_translation_meta' ), $type, $meta_key );\n\t\t\t$i++;\n\t\t}\n\n\t}\n\n\tpublic function bbl_translation_submit_meta_boxes( $type, $job ) {\n\n\t\tadd_meta_box( 'bbl_job_submit', __( 'Save Translation' , 'babble'), array( $this, 'metabox_translation_submit' ), $type, 'submit' );\n\n\t}\n\n\tpublic function metabox_translation_terms( array $items ) {\n\n\t\t$vars = $items;\n\n\t\t$this->render_admin( 'translation-editor-terms.php', $vars );\n\n\t}\n\n\tpublic function metabox_translation_meta( array $items ) {\n\n\t\t$vars = $items;\n\n\t\t$this->render_admin( 'translation-editor-meta.php', $vars );\n\n\t}\n\n\tpublic function metabox_translation_post_excerpt( array $items ) {\n\n\t\t$vars = $items;\n\n\t\t$this->render_admin( 'translation-editor-post-excerpt.php', $vars );\n\n\t}\n\n\tpublic function metabox_translation_submit( array $items ) {\n\n\t\t$vars = $items;\n\n\t\t$this->render_admin( 'translation-editor-submit.php', $vars );\n\n\t}\n\n\tpublic function save_job( $job_id, WP_Post $job ) {\n\n\t\tglobal $bbl_post_public, $bbl_taxonomies;\n\n\t\tif ( $this->no_recursion )\n\t\t\treturn;\n\t\tif ( 'bbl_job' != $job->post_type )\n\t\t\treturn;\n\n\t\t$edit_post_nonce   = isset( $_POST[ '_bbl_translation_edit_post' ] ) ? $_POST[ '_bbl_translation_edit_post' ] : false;\n\t\t$edit_terms_nonce  = isset( $_POST[ '_bbl_translation_edit_terms' ] ) ? $_POST[ '_bbl_translation_edit_terms' ] : false;\n\t\t$edit_meta_nonce   = isset( $_POST[ '_bbl_translation_edit_meta' ] ) ? $_POST[ '_bbl_translation_edit_meta' ] : false;\n\t\t$origin_post_nonce = isset( $_POST[ '_bbl_translation_origin_post' ] ) ? $_POST[ '_bbl_translation_origin_post' ] : false;\n\t\t$origin_term_nonce = isset( $_POST[ '_bbl_translation_origin_term' ] ) ? $_POST[ '_bbl_translation_origin_term' ] : false;\n\t\t$lang_code_nonce   = isset( $_POST[ '_bbl_translation_lang_code' ] ) ? $_POST[ '_bbl_translation_lang_code' ] : false;\n\n\t\tif ( $lang_code_nonce and wp_verify_nonce( $lang_code_nonce, \"bbl_translation_lang_code_{$job->ID}\" ) ) {\n\t\t\twp_set_object_terms( $job->ID, stripslashes( $_POST['bbl_lang_code'] ), 'bbl_job_language', false );\n\t\t}\n\n\t\t$language = get_the_terms( $job, 'bbl_job_language' );\n\n\t\tif ( empty( $language ) )\n\t\t\treturn false;\n\t\telse\n\t\t\t$lang_code = reset( $language )->name;\n\n\t\tif ( $origin_post_nonce and wp_verify_nonce( $origin_post_nonce, \"bbl_translation_origin_post_{$job->ID}\") ) {\n\t\t\tif ( $origin_post = get_post( absint( $_POST['bbl_origin_post'] ) ) ) {\n\t\t\t\tadd_post_meta( $job->ID, 'bbl_job_post', \"{$origin_post->post_type}|{$origin_post->ID}\", true );\n\n\t\t\t\tforeach ( $this->get_post_terms_to_translate( $origin_post, $lang_code ) as $taxo => $terms ) {\n\t\t\t\t\tforeach ( $terms as $term_id => $term )\n\t\t\t\t\t\tadd_post_meta( $job->ID, 'bbl_job_term', \"{$taxo}|{$term_id}\", false );\n\t\t\t\t}\n\n\t\t\t\tforeach ( $this->get_post_meta_to_translate( $origin_post, $lang_code ) as $key => $field ) {\n\t\t\t\t\tadd_post_meta( $job->ID, 'bbl_job_meta', $key, false );\n\t\t\t\t}\n\n\t\t\t}\n\t\t\t# @TODO else wp_die()?\n\t\t}\n\n\t\t# @TODO not implemented:\n\t\tif ( $origin_term_nonce and wp_verify_nonce( $origin_term_nonce, \"bbl_translation_origin_term_{$job->ID}\") ) {\n\t\t\tif ( $origin_term = get_term( absint( $_POST['bbl_origin_term'] ), $_POST['bbl_origin_taxonomy'] ) )\n\t\t\t\tadd_post_meta( $job->ID, 'bbl_job_term', \"{$origin_term->taxonomy}|{$origin_term->term_id}\", false );\n\t\t\t# @TODO else wp_die()?\n\t\t}\n\n\t\tif ( $edit_post_nonce and wp_verify_nonce( $edit_post_nonce, \"bbl_translation_edit_post_{$job->ID}\" ) ) {\n\n\t\t\t$post_data = stripslashes_deep( $_POST['bbl_translation']['post'] );\n\t\t\tif ( $post_data['post_name'] )\n\t\t\t\t$post_data['post_name'] = sanitize_title( $post_data['post_name'] );\n\t\t\t$post_info = get_post_meta( $job->ID, 'bbl_job_post', true );\n\t\t\tlist( $post_type, $post_id ) = explode( '|', $post_info );\n\t\t\t$post = get_post( $post_id );\n\n\t\t\tupdate_post_meta( $job->ID, \"bbl_post_{$post_id}\", $post_data );\n\n\t\t\tif ( 'pending' == $job->post_status ) {\n\n\t\t\t\t# Nothing.\n\n\t\t\t}\n\n\t\t\tif ( 'complete' == $job->post_status ) {\n\n\t\t\t\t# The ability to complete a translation of a post directly\n\t\t\t\t# maps to the ability to publish the origin post.\n\n\t\t\t\tif ( current_user_can( 'publish_post', $job->ID ) ) {\n\n\t\t\t\t\tif ( !$trans = $bbl_post_public->get_post_in_lang( $post, $lang_code, false ) )\n\t\t\t\t\t\t$trans = $bbl_post_public->initialise_translation( $post, $lang_code );\n\n\t\t\t\t\t$post_data['ID']          = $trans->ID;\n\t\t\t\t\t$post_data['post_status'] = $post->post_status;\n\n\t\t\t\t\t$this->no_recursion = true;\n\t\t\t\t\twp_update_post( $post_data, true );\n\t\t\t\t\t$this->no_recursion = false;\n\n\t\t\t\t} else {\n\n\t\t\t\t\t# Just in case. Switch the job back to in-progress status.\n\t\t\t\t\t# It would be nice to be able to use the 'publish' status because then we get the built-in\n\t\t\t\t\t# publish_post cap checks, but we can't control the post status label on a per-post-type basis yet.\n\n\t\t\t\t\t$this->no_recursion = true;\n\t\t\t\t\twp_update_post( array(\n\t\t\t\t\t\t'ID'          => $job->ID,\n\t\t\t\t\t\t'post_status' => 'in-progress',\n\t\t\t\t\t), true );\n\t\t\t\t\t$this->no_recursion = false;\n\n\t\t\t\t}\n\n\t\t\t}\n\n\t\t\tif ( $edit_meta_nonce and wp_verify_nonce( $edit_meta_nonce, \"bbl_translation_edit_meta_{$job->ID}\" ) ) {\n\n\t\t\t\t$meta_data = stripslashes_deep( $_POST['bbl_translation']['meta'] );\n\n\t\t\t\tforeach ( $meta_data as $meta_key => $meta_value ) {\n\n\t\t\t\t\tupdate_post_meta( $job->ID, \"bbl_meta_{$meta_key}\", $meta_value );\n\n\t\t\t\t\tif ( 'complete' == $job->post_status ) {\n\t\t\t\t\t\tif ( current_user_can( 'publish_post', $job->ID ) ) {\n\t\t\t\t\t\t\tupdate_post_meta( $trans->ID, $meta_key, $meta_value );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t}\n\n\t\t\t}\n\n\t\t}\n\n\t\tif ( $edit_terms_nonce and wp_verify_nonce( $edit_terms_nonce, \"bbl_translation_edit_terms_{$job->ID}\") ) {\n\n\t\t\t$terms_data = stripslashes_deep( $_POST['bbl_translation']['terms'] );\n\t\t\t$terms      = get_post_meta( $job->ID, 'bbl_job_term', false );\n\n\t\t\tforeach ( $terms as $term_info ) {\n\n\t\t\t\tlist( $taxo, $term_id ) = explode( '|', $term_info );\n\t\t\t\t$term = get_term( $term_id, $taxo );\n\t\t\t\t$terms_data[$term_id]['slug'] = sanitize_title( $terms_data[$term_id]['slug'] );\n\n\t\t\t\tupdate_post_meta( $job->ID, \"bbl_term_{$term_id}\", $terms_data[$term_id] );\n\n\t\t\t\tif ( 'complete' == $job->post_status ) {\n\n\t\t\t\t\t# @TODO if current user can edit term\n\n\t\t\t\t\t$trans = $bbl_taxonomies->get_term_in_lang( $term, $taxo, $lang_code, false );\n\t\t\t\t\tif ( !$trans )\n\t\t\t\t\t\t$trans = $bbl_taxonomies->initialise_translation( $term, $taxo, $lang_code );\n\n\t\t\t\t\t$terms_data[$term->term_id]['term_id'] = $trans->term_id;\n\n\t\t\t\t\t$args = array(\n\t\t\t\t\t\t'name' => $terms_data[$term->term_id]['name'],\n\t\t\t\t\t\t'slug' => '',\n\t\t\t\t\t);\n\t\t\t\t\twp_update_term( absint( $trans->term_id ), $trans->taxonomy, $args );\n\n\t\t\t\t}\n\n\t\t\t}\n\n\t\t}\n\n\t}\n\n\tpublic function save_post( $post_id, WP_Post $post ) {\n\n\t\tif ( $this->no_recursion )\n\t\t\treturn;\n\t\tif ( !bbl_is_translated_post_type( $post->post_type ) )\n\t\t\treturn;\n\n\t\t$nonce = isset( $_POST[ '_bbl_ready_for_translation' ] ) ? $_POST[ '_bbl_ready_for_translation' ] : false;\n\n\t\tif ( !$nonce )\n\t\t\treturn;\n\t\tif ( !wp_verify_nonce( $nonce, \"bbl_ready_for_translation-{$post->ID}\" ) )\n\t\t\treturn;\n\t\tif ( !isset( $_POST['babble_ready_for_translation'] ) )\n\t\t\treturn;\n\n\t\t# @TODO individual language selection when marking post as translation ready\n\t\t$langs       = bbl_get_active_langs();\n\t\t$lang_codes  = wp_list_pluck( $langs, 'code' );\n\t\t$this->create_post_jobs( $post->ID, $lang_codes );\n\t}\n\n\t/**\n\t* Hooks the WP init action early to register the\n\t* job post type.\n\t*\n\t* @return void\n\t**/\n\tpublic function init_early() {\n\t\t$labels = array(\n\t\t\t'name'               => _x( 'Translation Jobs', 'translation jobs general name', 'babble' ),\n\t\t\t'singular_name'      => _x( 'Translation Job', 'translation jobs singular name', 'babble' ),\n\t\t\t'menu_name'          => _x( 'Translations', 'translation jobs menu name', 'babble' ),\n\t\t\t'add_new'            => _x( 'Add New', 'translation job', 'babble' ),\n\t\t\t'add_new_item'       => _x( 'Create New Job', 'translation job', 'babble' ),\n\t\t\t'add_item_context'   => _x( 'Add Translation Job (%s)', 'translation job; e.g. \"Add Translation Job (French)\"', 'babble' ),\n\t\t\t'edit_item'          => _x( 'Edit Translation', 'translation job', 'babble' ),\n\t\t\t'edit_item_context'  => _x( 'Edit Translation (%s)', 'translation job; e.g. \"Edit Translation (French)\"', 'babble' ),\n\t\t\t'new_item'           => _x( 'New Job', 'translation job', 'babble' ),\n\t\t\t'view_item'          => _x( 'View Job', 'translation job', 'babble' ),\n\t\t\t'search_items'       => _x( 'Search Jobs', 'translation job', 'babble' ),\n\t\t\t'not_found'          => _x( 'No translation jobs found.', 'translation job', 'babble' ),\n\t\t\t'not_found_in_trash' => _x( 'No translation jobs found in Trash.', 'translation job', 'babble' ),\n\t\t\t'all_items'          => _x( 'All Translation Jobs', 'translation job', 'babble' ),\n\t\t);\n\t\t$args = array(\n\t\t\t'public'             => false,\n\t\t\t'publicly_queryable' => false,\n\t\t\t'show_ui'            => true,\n\t\t\t'show_in_menu'       => true,\n\t\t\t'menu_icon'          => 'dashicons-clipboard',\n\t\t\t'query_var'          => false,\n\t\t\t'labels'             => $labels,\n\t\t\t'can_export'         => true,\n\t\t\t'supports'           => false,\n\t\t\t'capability_type'    => 'bbl_job',\n\t\t\t'map_meta_cap'       => true,\n\t\t);\n\t\tregister_post_type( 'bbl_job', $args );\n\t\tregister_post_status( 'new', array(\n\t\t\t'label'                  => __( 'New', 'babble' ),\n\t\t\t'public'                 => false,\n\t\t\t'exclude_from_search'    => false,\n\t\t\t'show_in_admin_all_list' => true,\n\t\t\t'label_count'            => _n_noop( 'New <span class=\"count\">(%s)</span>', 'New <span class=\"count\">(%s)</span>', 'babble' ),\n\t\t\t'protected'              => true,\n\t\t\t) );\n\t\tregister_post_status( 'in-progress', array(\n\t\t\t'label'                  => __( 'In Progress', 'babble' ),\n\t\t\t'public'                 => false,\n\t\t\t'exclude_from_search'    => false,\n\t\t\t'show_in_admin_all_list' => true,\n\t\t\t'label_count'            => _n_noop( 'In Progress <span class=\"count\">(%s)</span>', 'In Progress <span class=\"count\">(%s)</span>', 'babble' ),\n\t\t\t'protected'              => true,\n\t\t) );\n\t\tregister_post_status( 'complete', array(\n\t\t\t'label'                  => __( 'Complete', 'babble' ),\n\t\t\t'public'                 => false,\n\t\t\t'exclude_from_search'    => false,\n\t\t\t'show_in_admin_all_list' => true,\n\t\t\t'label_count'            => _n_noop( 'Complete <span class=\"count\">(%s)</span>', 'Complete <span class=\"count\">(%s)</span>', 'babble' ),\n\t\t\t'protected'              => true,\n\t\t) );\n\t\t$args = array(\n\t\t\t'public'  => false,\n\t\t\t'show_ui' => false,\n\t\t);\n\t\tregister_taxonomy( 'bbl_job_language', array( 'bbl_job' ), $args );\n\t}\n\n\t// CALLBACKS\n\t// =========\n\n\tpublic function filter_columns( $cols ) {\n\t\t$new_cols = array();\n\t\tforeach ( $cols as $col_id => $col ) {\n\t\t\tif ( 'date' != $col_id ) {\n\t\t\t\t$new_cols[$col_id] = $col;\n\t\t\t} else {\n\t\t\t\t$new_cols['bbl_language'] = __( 'Language', 'babble' );\n\t\t\t\t$new_cols['bbl_type']     = __( 'Items', 'babble' );\n\t\t\t\t$new_cols['bbl_status']   = __( 'Status', 'babble' );\n\t\t\t\t$new_cols['date'] = $col;\n\t\t\t}\n\t\t}\n\t\treturn $new_cols;\n\t}\n\n\tpublic function action_column( $col, $post_id ) {\n\n\t\t$post = get_post( $post_id );\n\t\t$status = get_post_status_object( $post->post_status );\n\n\t\tswitch ( $col ) {\n\n\t\t\tcase 'bbl_language':\n\t\t\t\techo $this->get_job_language( $post )->display_name;\n\t\t\t\tbreak;\n\n\t\t\tcase 'bbl_type':\n\t\t\t\techo implode( ', ', $this->get_job_type( $post ) );\n\t\t\t\tbreak;\n\n\t\t\tcase 'bbl_status':\n\t\t\t\techo $status->label;\n\t\t\t\tbreak;\n\n\t\t}\n\n\t}\n\n\tpublic function metabox_post_translations( WP_Post $post, array $metabox ) {\n\n\t\t$trans   = bbl_get_post_translations( $post );\n\t\t$incomplete_jobs    = $this->get_incomplete_post_jobs( $post );\n\t\t$completed_jobs    = $this->get_completed_post_jobs( $post );\n\t\t$default = bbl_get_default_lang_code();\n\n\t\t# The ability to create a translation of a post directly\n\t\t# maps to the ability to publish the canonical post.\n\t\t$capable = current_user_can( 'publish_post', $post->ID );\n\n\t\tunset( $trans[$default] );\n\n\t\tif ( !empty( $trans ) ) {\n\n\t\t\tif ( !empty( $completed_jobs ) and $capable ) {\n\t\t\t\t?><h4><?php _e( 'Complete:', 'babble' ); ?></h4><?php\n\t\t\t}\n\n\t\t\tforeach ( $completed_jobs as $lang_code => $job ) {\n\t\t\t\t$lang = bbl_get_lang( $lang_code );\n\t\t\t\t?>\n\t\t\t\t<p><?php printf( '%s: <a href=\"%s\">%s</a>', $lang->display_name, get_edit_post_link( $job->ID ), __( 'View', 'babble' ) ); ?>\n\t\t\t\t<?php\n\t\t\t}\n\n\t\t}\n\n\t\tif ( !empty( $incomplete_jobs ) and $capable ) {\n\n\t\t\t?><h4><?php _e( 'Pending:', 'babble' ); ?></h4><?php\n\t\t\tforeach ( $incomplete_jobs as $job ) {\n\t\t\t\t$lang = $this->get_job_language( $job );\n\t\t\t\t$status = get_post_status_object( $job->post_status );\n\t\t\t\t?>\n\t\t\t\t<p><?php printf( '%s (%s)', $lang->display_name, $status->label ); ?>\n\t\t\t\t<?php\n\t\t\t}\n\n\t\t\t$args = array(\n\t\t\t\t'post_type'    => 'bbl_job',\n\t\t\t\t'bbl_job_post' => \"{$post->post_type}|{$post->ID}\",\n\t\t\t);\n\t\t\t?>\n\t\t\t<p><a href=\"<?php echo add_query_arg( $args, admin_url( 'edit.php' ) ); ?>\"><?php _e( 'View pending translation jobs &raquo;', 'babble' ); ?></a></p>\n\t\t\t<?php\n\n\t\t} else if ( $capable ) {\n\n\t\t\twp_nonce_field( \"bbl_ready_for_translation-{$post->ID}\", '_bbl_ready_for_translation' );\n\n\t\t\t?>\n\t\t\t<p><label><input type=\"checkbox\" name=\"babble_ready_for_translation\" value=\"<?php echo absint( $post->ID ); ?>\" /> <?php _e( 'Ready for translation', 'babble' ); ?></label></p>\n\t\t\t<?php\n\n\t\t} else {\n\n\t\t\t?>\n\t\t\t<p><?php _ex( 'None', 'No translations', 'babble' ); ?></p>\n\t\t\t<?php\n\n\t\t}\n\n\t}\n\n\t// PUBLIC METHODS\n\t// ==============\n\n\t/**\n\t * Return the array of incomplete jobs for a Post, keyed\n\t * by lang code.\n\t *\n\t * @param WP_Post|int $post A WP Post object or a post ID\n\t * @return array An array of WP Translation Job Post objects \n\t */\n\tpublic function get_incomplete_post_jobs( $post ) {\n\t\t$post = get_post( $post );\n\t\treturn $this->get_object_jobs( $post->ID, 'post', $post->post_type, array( 'new', 'in-progress' ) );\n\t}\n\n\t/**\n\t * Return the array of completed jobs for a Post, keyed\n\t * by lang code.\n\t *\n\t * @param WP_Post|int $post A WP Post object or a post ID\n\t * @return array An array of WP Translation Job Post objects \n\t */\n\tpublic function get_completed_post_jobs( $post ) {\n\t\t$post = get_post( $post );\n\t\treturn $this->get_object_jobs( $post->ID, 'post', $post->post_type, array( 'complete' ) );\n\t}\n\n\t/**\n\t * Return the array of jobs for a Term, keyed\n\t * by lang code.\n\t *\n\t * @param object $term A WP Term object or a term ID\n\t * @return array An array of WP Translation Job Post objects \n\t */\n\tpublic function get_term_jobs( $term, $taxonomy ) {\n\t\t$term = get_term( $term, $taxonomy );\n\t\treturn $this->get_object_jobs( $term->term_id, 'term', $term->taxonomy );\n\t}\n\n\t/**\n\t * Return the array of jobs for a term or post, keyed\n\t * by lang code.\n\t *\n\t * @param int The ID of the object (eg. post ID or term ID)\n\t * @param string $type Either 'term' or 'post'\n\t * @param string $name The post type name or the term's taxonomy name\n\t * @return array An array of translation job WP_Post objects\n\t */\n\tpublic function get_object_jobs( $id, $type, $name, $statuses = array( 'new', 'in-progress', 'complete' ) ) {\n\n\t\t$jobs = get_posts( array(\n\t\t\t'bbl_translate'  => false,\n\t\t\t'post_type'      => 'bbl_job',\n\t\t\t'post_status'    => $statuses,\n\t\t\t'meta_key'       => \"bbl_job_{$type}\",\n\t\t\t'meta_value'     => \"{$name}|{$id}\",\n\t\t\t'posts_per_page' => -1,\n\t\t) );\n\n\t\tif ( empty( $jobs ) )\n\t\t\treturn array();\n\n\t\t$return = array();\n\n\t\tforeach ( $jobs as $job ) {\n\t\t\tif ( $lang = $this->get_job_language( $job ) )\n\t\t\t\t$return[$lang->code] = $job;\n\t\t}\n\n\t\treturn $return;\n\n\t}\n\n\tpublic function get_job_language( $job ) {\n\t\t$job       = get_post( $job );\n\t\t$languages = get_the_terms( $job, 'bbl_job_language' );\n\t\tif ( empty( $languages ) )\n\t\t\treturn false;\n\t\treturn bbl_get_lang( reset( $languages )->name );\n\t}\n\n\tpublic function get_job_type( $job ) {\n\n\t\t$job   = get_post( $job );\n\t\t$post  = get_post_meta( $job->ID, 'bbl_job_post', true );\n\t\t$terms = get_post_meta( $job->ID, 'bbl_job_term', false );\n\n\t\t$return = array();\n\n\t\tif ( !empty( $post ) ) {\n\t\t\tlist( $post_type, $post_id ) = explode( '|', $post );\n\t\t\t$return[] = get_post_type_object( $post_type )->labels->singular_name;\n\t\t}\n\n\t\tif ( !empty( $terms ) ) {\n\n\t\t\tforeach ( $terms as $term ) {\n\t\t\t\tlist( $taxonomy, $term_id ) = explode( '|', $term );\n\t\t\t\t$return[] = get_taxonomy( $taxonomy )->labels->name;\n\t\t\t}\n\n\t\t}\n\n\t\treturn array_unique( $return );\n\n\t}\n\n\tpublic function get_job_objects( $job ) {\n\n\t\t$job   = get_post( $job );\n\t\t$post  = get_post_meta( $job->ID, 'bbl_job_post', true );\n\t\t$terms = get_post_meta( $job->ID, 'bbl_job_term', false );\n\t\t$meta  = get_post_meta( $job->ID, 'bbl_job_meta', false );\n\t\t$lang  = $this->get_job_language( $job );\n\n\t\t$return = array();\n\n\t\tif ( !empty( $post ) ) {\n\t\t\tlist( $post_type, $post_id ) = explode( '|', $post );\n\t\t\t# @TODO in theory a translation job could actually include more than one post.\n\t\t\t# we should implement this earlier rather than later to save potential headaches down the road.\n\t\t\t$return['post'] = get_post( $post_id );\n\t\t}\n\n\t\tif ( !empty( $terms ) ) {\n\n\t\t\tforeach ( $terms as $term ) {\n\t\t\t\tlist( $taxonomy, $term_id ) = explode( '|', $term );\n\t\t\t\t$return['terms'][$taxonomy][] = get_term( $term_id, $taxonomy );\n\t\t\t}\n\n\t\t}\n\n\t\tif ( !empty( $meta ) ) {\n\n\t\t\t$post = get_post_meta( $job->ID, 'bbl_job_post', true );\n\t\t\tlist( $post_type, $post_id ) = explode( '|', $post );\n\t\t\t$post = get_post( $post_id );\n\n\t\t\tforeach ( $this->get_post_meta_to_translate( $post, $lang->code ) as $meta_key => $meta_field ) {\n\t\t\t\t$return['meta'][$meta_key] = $meta_field;\n\t\t\t}\n\n\t\t}\n\n\t\treturn $return;\n\n\t}\n\n\t/**\n\t * Create empty translations of a post for all languages. Called via WP-Cron on the `babble_create_empty_translation` hook.\n\t *\n\t * @param  array  $args Args array containing a `post_id` element.\n\t */\n\tpublic function create_empty_translation( array $args ) {\n\t\tglobal $bbl_post_public;\n\n\t\tif ( !$post = get_post( $args['post_id'] ) ) {\n\t\t\treturn;\n\t\t}\n\n\t\tforeach ( bbl_get_active_langs() as $lang ) {\n\n\t\t\tif ( !$trans = $bbl_post_public->get_post_in_lang( $post, $lang->code, false ) ) {\n\t\t\t\t$trans = $bbl_post_public->initialise_translation( $post, $lang->code );\n\t\t\t}\n\n\t\t\t$post_data = array(\n\t\t\t\t'ID'          => $trans->ID,\n\t\t\t\t'post_status' => $post->post_status,\n\t\t\t\t'post_name'   => $post->post_name,\n\t\t\t);\n\n\t\t\t$this->no_recursion = true;\n\t\t\twp_update_post( $post_data, true );\n\t\t\t$this->no_recursion = false;\n\n\t\t}\n\n\t}\n\n\t/**\n\t * Create some translation jobs.\n\t *\n\t * @param int $post_id The ID of the post to create translation jobs for\n\t * @param array $lang_codes The language codes to create translation jobs of this post for\n\t * @return array An array of Translation Job post IDs\n\t **/\n\tpublic function create_post_jobs( $post_id, array $lang_codes ) {\n\t\t$post = get_post( $post_id );\n\n\t\t// @TODO Validate that the $post is in the default language, otherwise fail\n\n\t\t$jobs = array();\n\t\tforeach ( $lang_codes as $lang_code ) {\n\n\t\t\tif ( bbl_get_default_lang_code() == $lang_code )\n\t\t\t\tcontinue;\n\n\t\t\tif ( apply_filters( 'bbl_create_empty_translation', false, $post ) ) {\n\n\t\t\t\twp_schedule_single_event( time(), 'babble_create_empty_translation', array(\n\t\t\t\t\tarray(\n\t\t\t\t\t\t'post_id' => $post->ID,\n\t\t\t\t\t)\n\t\t\t\t) );\n\n\t\t\t}\n\n\t\t\t$this->no_recursion = true;\n\t\t\t$job = wp_insert_post( array(\n\t\t\t\t'post_type'   => 'bbl_job',\n\t\t\t\t'post_status' => 'new',\n\t\t\t\t'post_author' => get_current_user_id(),\n\t\t\t\t'post_title'  => get_the_title( $post ),\n\t\t\t) );\n\t\t\t$this->no_recursion = false;\n\t\t\t// @TODO If a translation already exists, populate the translation job with the translation\n\t\t\t$jobs[] = $job;\n\n\t\t\tadd_post_meta( $job, 'bbl_job_post', \"{$post->post_type}|{$post->ID}\", true );\n\t\t\twp_set_object_terms( $job, $lang_code, 'bbl_job_language' );\n\n\t\t\tforeach ( $this->get_post_terms_to_translate( $post, $lang_code ) as $taxo => $terms ) {\n\t\t\t\tforeach ( $terms as $term_id => $term )\n\t\t\t\t\tadd_post_meta( $job, 'bbl_job_term', \"{$taxo}|{$term_id}\", false );\n\t\t\t}\n\n\t\t\tforeach ( $this->get_post_meta_to_translate( $post, $lang_code ) as $key => $field ) {\n\t\t\t\tadd_post_meta( $job, 'bbl_job_meta', $key, false );\n\t\t\t}\n\n\t\t}\n\n\t\treturn $jobs;\n\t}\n\n\tpublic function get_post_terms_to_translate( $post_id, $lang_code ) {\n\n\t\t$post        = get_post( $post_id );\n\t\t$taxos       = get_object_taxonomies( $post->post_type );\n\t\t$trans_terms = array();\n\n\t\tforeach ( $taxos as $key => $taxo ) {\n\n\t\t\tif ( !bbl_is_translated_taxonomy( $taxo ) )\n\t\t\t\tcontinue;\n\n\t\t\t$terms = get_the_terms( $post, $taxo );\n\n\t\t\tif ( empty( $terms ) )\n\t\t\t\tcontinue;\n\n\t\t\tforeach ( $terms as $term ) {\n\n\t\t\t\t$trans = bbl_get_term_translations( $term->term_id, $term->taxonomy );\n\n\t\t\t\tif ( !isset( $trans[$lang_code] ) )\n\t\t\t\t\t$trans_terms[$taxo][$term->term_id] = $term;\n\n\t\t\t}\n\n\t\t}\n\n\t\treturn $trans_terms;\n\n\t}\n\n\t/**\n\t * Return an array of a post's meta fields which are to be translated. The array keys are the post meta keys and the\n\t * array values are the meta value for that key.\n\t *\n\t * @param  WP_Post $post_id  A post object.\n\t * @param  string $lang_code The language code for the translation job for this post.\n\t * @return array             An array of post meta keys which should be translated for this post.\n\t */\n\tpublic function get_post_meta_to_translate( WP_Post $post, $lang_code ) {\n\t\t$meta = get_post_meta( $post->ID );\n\n\t\tif ( empty( $meta ) ) {\n\t\t\treturn array();\n\t\t}\n\n\t\t$fields = $this->get_translated_meta_fields( $post );\n\n\t\treturn array_intersect_key( $fields, $meta );\n\t}\n\n\t/**\n\t * Return an array of meta field keys which should be translated. Array contains `Babble_Meta_Field` objects with\n\t * the meta keys as the array keys.\n\t *\n\t * @param  WP_Post             A post object.\n\t * @return Babble_Meta_Field[] An array of Babble meta field handlers.\n\t */\n\tpublic function get_translated_meta_fields( WP_Post $post ) {\n\t\t$fields = (array) apply_filters( 'bbl_translated_meta_fields', array(), $post );\n\n\t\tforeach ( $fields as $key => $field ) {\n\t\t\tif ( ! ( $field instanceof Babble_Meta_Field ) ) {\n\t\t\t\tunset( $fields[ $key ] );\n\t\t\t}\n\t\t}\n\n\t\treturn $fields;\n\t}\n\n}\n\nglobal $bbl_jobs;\n$bbl_jobs = new Babble_Jobs();\n"
  },
  {
    "path": "class-languages.php",
    "content": "<?php\n\n/**\n * Manages the languages available for the site.\n *\n * @package Babble\n * @since Alpha 1.1\n */\nclass Babble_Languages extends Babble_Plugin {\n\t\n\t/**\n\t * The languages available within the system.\n\t *\n\t * @var array\n\t **/\n\tprotected $available_langs;\n\t\n\t/**\n\t * The language preferences set for this site, \n\t * i.e. url prefixes and display names.\n\t *\n\t * @var array\n\t **/\n\tprotected $lang_prefs;\n\t\n\t/**\n\t * An array of language codes, keyed by language prefix\n\t * for all the languages selected as ACTIVE for this site.\n\t *\n\t * Active languages are available to admins to add content.\n\t *\n\t * @var array\n\t **/\n\tprotected $active_langs;\n\t\n\t/**\n\t * An array of language codes, keyed by language prefix\n\t * for all the languages selected as PUBLIC for this site.\n\t *\n\t * Public languages are available to readers of the site\n\t * to read.\n\t *\n\t * @var array\n\t **/\n\tprotected $public_langs;\n\n\t/**\n\t * The language code for the default language.\n\t *\n\t * @var string\n\t **/\n\tprotected $default_lang;\n\t\n\t/**\n\t * The current version for purposes of rewrite rules, any \n\t * DB updates, cache busting, etc\n\t *\n\t * @var int\n\t **/\n\tprotected $version = 1;\n\t\n\t/**\n\t * Any fields to show errors on, currently only used by URL Prefix fields.\n\t *\n\t * @var array\n\t **/\n\tprotected $errors;\n\t\n\t/**\n\t * Setup any add_action or add_filter calls. Initiate properties.\n\t *\n\t * @return void\n\t **/\n\tpublic function __construct() {\n\t\t$this->setup( 'babble-languages', 'plugin' );\n\t\t$this->add_action( 'admin_menu', 'admin_menu' );\n\t\t$this->add_action( 'admin_notices', 'admin_notices' );\n\t\t$this->add_action( 'load-settings_page_babble_languages', 'load_options' );\n\n\n\t\t$this->initiate();\n\t}\n\n\t/**\n\t * (Re)initiates the properties of this object.\n\t *\n\t * @return void\n\t **/\n\tpublic function initiate() {\n\t\tif ( ! ( $this->available_langs = $this->get_option( 'available_langs', false ) ) ) {\n\t\t\t$this->parse_available_languages();\n\t\t}\n\t\t$this->active_langs = $this->get_option( 'active_langs', array() );\n\t\t$this->langs = $this->get_option( 'langs', array() );\n\t\t$this->lang_prefs = $this->get_option( 'lang_prefs', array() );\n\t\t$this->langs = $this->merge_lang_sets( $this->langs, $this->lang_prefs );\n\t\t$this->default_lang = $this->get_option( 'default_lang', 'en_US' );\n\t\t$this->public_langs = $this->get_option( 'public_langs', array( $this->default_lang ) );\n\t\t// @FIXME: Add something in so the user gets setup with the single language they are currently using\n\t\tif ( ! $this->get_option( 'active_langs', false ) || ! $this->get_option( 'default_lang', false ) )\n\t\t\t$this->set_defaults();\n\t}\n\t\n\t// WP HOOKS\n\t// ========\n\n\t/**\n\t * Hooks the WP admin_notices action to warn the admin\n\t * if the available languages need to be set up.\n\t *\n\t * @return void\n\t **/\n\tpublic function admin_notices() {\n\t\tif ( get_current_screen()->id == 'settings_page_babble_languages' )\n\t\t\treturn;\n\t\tif ( ! $this->get_option( 'active_langs', false ) || ! $this->get_option( 'default_lang', false ) ) {\n\t\t\tprintf( '<div class=\"error\"><p>%s</p></div>', sprintf( __( '<strong>Babble setup:</strong> Please visit the <a href=\"%s\">Available Languages settings</a> and setup your available languages and the default language.', 'babble' ), admin_url( 'options-general.php?page=babble_languages' ) ) );\n\t\t}\n\t}\n\n\t/**\n\t * Hooks the WP admin_menu action \n\t *\n\t * @return void\n\t **/\n\tpublic function admin_menu() {\n\t\tadd_options_page( __( 'Available Languages', 'babble' ), __( 'Available Languages' , 'babble'), 'manage_options', 'babble_languages', array( $this, 'options' ) );\n\t}\n\t\n\t/**\n\t * Hooks the load action for the options page.\n\t *\n\t * @return void\n\t **/\n\tpublic function load_options() {\n\t\twp_enqueue_style( 'babble_languages_options', $this->url( '/css/languages-options.css' ), null, filemtime( $this->dir( 'css/languages-options.css' ) ) );\n\t\t$this->maybe_process_languages();\n\t}\n\t\n\t// CALLBACKS\n\t// =========\n\n\t/**\n\t * Callback function to provide the HTML for the \"Available Languages\"\n\t * options page.\n\t *\n\t * @return void\n\t **/\n\tpublic function options() {\n\t\t// Refresh the current languages\n\t\t$this->parse_available_languages();\n\t\t// Merge in our previously set language settings\n\t\t$langs = $this->merge_lang_sets( $this->available_langs, $this->lang_prefs );\n\t\t// Merge in any POSTed field values\n\t\tforeach ( $langs as $code => & $lang ) {\n\t\t\t$lang->url_prefix = ( @ isset( $_POST[ 'url_prefix_' . $code ] ) ) ? $_POST[ \"url_prefix_$code\" ] : @ $lang->url_prefix;\n\t\t\tif ( ! $lang->url_prefix )\n\t\t\t\t$lang->url_prefix = $lang->url_prefix;\n\t\t\t$lang->text_direction = $lang->text_direction;\n\t\t\t// This line must come after the text direction value is set\n\t\t\t$lang->input_lang_class = ( 'rtl' == $lang->text_direction ) ? 'lang-rtl' : 'lang-ltr' ;\n\t\t\t$lang->display_name = ( @ isset( $_POST[ \"display_name_$code\" ] ) ) ? $_POST[ \"display_name_$code\" ] : @ $lang->display_name;\n\t\t\tif ( ! $lang->display_name )\n\t\t\t\t$lang->display_name = $lang->name;\n\t\t\t// Note any url_prefix errors\n\t\t\t$lang->url_prefix_error = ( @ $this->errors[ \"url_prefix_$code\" ] ) ? 'babble-error' : '0' ;\n\t\t\t// Flag the active languages\n\t\t\t$lang->active = false;\n\t\t\tif ( in_array( $code, $this->active_langs ) )\n\t\t\t\t$lang->active = true;\n\t\t\t\n\t\t}\n\t\t$vars = array();\n\t\t$vars[ 'langs' ] = $langs;\n\t\t$vars[ 'default_lang' ] = $this->default_lang;\n\t\t$vars[ 'active_langs' ] = $this->get_active_langs();\n\t\t$this->render_admin( 'options-available-languages.php', $vars );\n\t}\n\t\n\t// PUBLIC METHODS\n\t// ==============\n\n\t/**\n\t * Set the active language objects for the current site, keyed\n\t * by URL prefix.\n\t * \n\t * @return array An array of Babble language objects\n\t **/\n\tpublic function set_active_langs( $lang_codes ) {\n\t\t$this->parse_available_languages();\n\t\terror_log( \"SW: WP_LANG_DIR: \" . WP_LANG_DIR );\n\t\t$this->active_langs = $lang_codes;\n\t}\n\n \t/**\n\t * Return the active language objects for the current site, keyed\n\t * by URL prefix. A language object looks like:\n\t * 'ar' => \n\t * \t\tobject(stdClass)\n\t * \t\t\tpublic 'name' => string 'Arabic'\n\t * \t\t\tpublic 'code' => string 'ar'\n\t * \t\t\tpublic 'url_prefix' => string 'ar'\n\t * \t\t\tpublic 'text_direction' => string 'rtl'\n\t * \t\t\tpublic 'display_name' => string 'Arabic'\n\t * \n\t * @return array An array of Babble language objects\n\t **/\n\tpublic function get_active_langs() {\n\t\t$langs = array();\n\t\tforeach ( $this->active_langs as $url_prefix => $code )\n\t\t\t$langs[ $url_prefix ] = $this->langs[ $code ];\n\t\treturn $langs;\n\t}\n\n\t/**\n\t * Given a lang object or lang code, this checks whether the\n\t * language is public or not.\n\t * \n\t * @param string $lang_code A language code\n\t * @return boolean True if public\n\t **/\n\tpublic function is_public_lang( $lang_code ) {\n\t\tif ( ! is_string( $lang_code ) )\n\t\t\tthrow new exception( 'Please provide a lang_code for the is_public_lang method.' );\n\t\treturn in_array( $lang_code, $this->public_langs );\n\t}\n\n\t/**\n \t * Returns the requested language object.\n\t *\n\t * @param string $code A language code, e.g. \"fr_BE\" \n\t * @return object|boolean A Babble language object\n\t **/\n\tpublic function get_lang( $lang_code ) {\n\t\tif ( ! isset( $this->langs[ $lang_code ] ) )\n\t\t\treturn false;\n\t\treturn $this->langs[ $lang_code ];\n\t}\n\n\t/**\n\t * Returns the current language object, respecting any\n\t * language switches; i.e. if your request was for\n\t * Arabic, but the language is currently switched to\n\t * French, this will return French.\n\t *\n\t * @return object|boolean A Babble language object\n\t **/\n\tpublic function get_current_lang() {\n\t\tglobal $bbl_locale;\n\t\treturn $this->get_lang( $bbl_locale->get_lang() );\n\t}\n\n\t/**\n\t * Returns the default language code for this site.\n\t *\n\t * @return string A language code, e.g. \"he_IL\"\n\t **/\n\tpublic function get_default_lang_code() {\n\t\treturn $this->default_lang;\n\t}\n\n\t/**\n\t * Returns the default language for this site.\n\t *\n\t * @return object The language object for the default language\n\t **/\n\tpublic function get_default_lang() {\n\t\treturn bbl_get_lang( $this->default_lang );\n\t}\n\t\n\t/**\n\t * Given a language code, return the URL prefix.\n\t *\n\t * @param string $code A language code, e.g. \"fr_BE\" \n\t * @return bool|string A URL prefix, as set by the admin when editing the lang prefs, or false if no language\n\t **/\n\tpublic function get_url_prefix_from_code( $code ) {\n\t\tif ( ! isset( $this->langs[ $code ]->url_prefix ) )\n\t\t\treturn false;\n\t\treturn $this->langs[ $code ]->url_prefix;\n\t}\n\t\n\t/**\n\t * Given a URL prefix, return the language code.\n\t *\n\t * @param string $code A URL prefix, e.g. \"de\", as set by the admin\n\t * @return bool|string A language code, e.g. \"de_DE\", or false if no language\n\t **/\n\tpublic function get_code_from_url_prefix( $url_prefix ) {\n\t\tif ( ! isset( $this->active_langs[ $url_prefix ] ) )\n\t\t\treturn false;\n\t\treturn $this->active_langs[ $url_prefix ];\n\t}\n\t\n\t// PRIVATE/PROTECTED METHODS\n\t// =========================\n\n\t/**\n\t * Merge two arrays of language objects. If a language exists in\n\t * $langs_b that doesn't in $langs_a, it will be added to the \n\t * final array. If a language has a property in both arrays, the\n\t * property value from $langs_b will overwrite the property value\n\t * in $langs_a. If a language in $langs_b has a property that \n\t * doesn't exist in $langs_a then it will be added to that\n\t * language in the final array.\n\t *\n\t * @param array $langs_a An array of language objects\n\t * @param array $langs_b An array of language objects\n\t * @return array An array of language objects\n\t **/\n\tprotected function merge_lang_sets( $langs_a, $langs_b ) {\n\t\t$langs = array();\n\t\tforeach ( $langs_a as $code => $lang_a ) {\n\t\t\t// Langs only in A get copied from A, simple.\n\t\t\tif ( ! isset( $langs_b[ $code ] ) ) {\n\t\t\t\t$langs[ $code ] = $lang_a;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t// The properties of langs in both A & B are merged\n\t\t\t$langs[ $code ] = $lang_a;\n\t\t\t$lang_b = $langs_b[ $code ];\n\t\t\tforeach ( $lang_b as $p => $v )\n\t\t\t\t$langs[ $code ]->$p = $v;\n\t\t}\n\t\treturn $langs;\n\t}\n\t\n\t/**\n\t * Checks if there is a POSTed request to process. Checks it's properly\n\t * nonced up. Processes it. Redirects if there's no errors.\n\t *\n\t * @return void\n\t **/\n\tprotected function maybe_process_languages() {\n\t\tif ( ! isset( $_POST[ '_babble_nonce' ] ) )\n\t\t\treturn;\n\t\tcheck_admin_referer( 'babble_lang_prefs', '_babble_nonce' );\n\n\t\t// Now save the language preferences for all languages\n\n\t\t$lang_prefs = array();\n\t\t$url_prefixes = array();\n\t\tforeach ( $this->available_langs as $code => $lang ) {\n\t\t\t$lang_pref = new stdClass;\n\t\t\t$lang_pref->display_name = @ $_POST[ 'display_name_' . $code ];\n\t\t\t$lang_pref->url_prefix = @ $_POST[ 'url_prefix_' . $code ];\n\t\t\t// Check we don't have more than one language using the same url prefix\n\t\t\tif ( array_key_exists( $lang_pref->url_prefix, $url_prefixes ) ) {\n\t\t\t\t$lang_1 = $this->format_code_lang( $code );\n\t\t\t\t$lang_2 = $this->format_code_lang( $url_prefixes[ $lang_pref->url_prefix ] );\n\t\t\t\t$msg = sprintf( __( 'The languages \"%1$s\" and \"%2$s\" are using the same URL Prefix. Each URL prefix should be unique.', 'babble' ), $lang_1, $lang_2 );\n\t\t\t\t$this->set_admin_error( $msg );\n\t\t\t\t$this->errors[ 'url_prefix_' . $lang_pref->url_prefix ] = true;\n\t\t\t\t$this->errors[ \"url_prefix_$code\" ] = true;\n\t\t\t} else {\n\t\t\t\t$url_prefixes[ $lang_pref->url_prefix ] = $code;\n\t\t\t}\n\t\t\t$lang_prefs[ $code ] = $lang_pref;\n\t\t}\n\t\t\n\t\terror_log( \"SW: Available langs: \" . print_r( $this->available_langs, true ) );\n\t\terror_log( \"SW: Lang prefs: \" . print_r( $lang_prefs, true ) );\n\t\t\n\t\t// Now save the active languages, i.e. the selected languages\n\t\t\n\t\tif ( ! $this->errors ) {\n\t\t\t$langs = $this->merge_lang_sets( $this->available_langs, $this->lang_prefs );\n\t\t\t$active_langs = array();\n\t\t\tforeach ( (array) @ $_POST[ 'active_langs' ] as $code )\n\t\t\t\t$active_langs[ $langs[ $code ]->url_prefix ] = $code;\n\t\t\tif ( count( $active_langs ) < 2 ) {\n\t\t\t\t$this->set_admin_error( __( 'You must set at least two languages as active.', 'babble' ) );\n\t\t\t} else {\n\t\t\t\t$this->active_langs = $active_langs;\n\t\t\t\t$this->update_option( 'active_langs', $this->active_langs );\n\t\t\t\t$this->langs = $langs;\n\t\t\t\t$this->update_option( 'langs', $this->langs );\n\t\t\t}\n\t\t\tif ( ! isset( $_POST[ 'public_langs' ] ) ) {\n\t\t\t\t$this->set_admin_error( __( 'You must set at least your default language as public.', 'babble' ) );\n\t\t\t} else {\n\t\t\t\t$public_langs = (array) $_POST[ 'public_langs' ];\n\t\t\t\tif ( ! in_array( @ $_POST[ 'default_lang' ], $public_langs ) )\n\t\t\t\t\t$this->set_admin_error( __( 'You must set your default language as public.', 'babble' ) );\n\t\t\t}\n\t\t}\n\t\t// Finish up, redirecting if we're all OK\n\t\tif ( ! $this->errors ) {\n\t\t\t// Save the public languages\n\t\t\t$this->update_option( 'public_langs', $public_langs );\n\t\t\t\n\t\t\t// First the default language\n\t\t\t$default_lang = @ $_POST[ 'default_lang' ];\n\t\t\t$this->update_option( 'default_lang', $default_lang );\n\t\t\t// Now the prefs\n\t\t\t$this->update_option( 'lang_prefs', $lang_prefs );\n\t\t\t// Now set a reassuring message and redirect back to the clean settings page\n\t\t\t$this->set_admin_notice( __( 'Your language settings have been saved.', 'babble' ) );\n\t\t\t$url = admin_url( 'options-general.php?page=babble_languages' );\n\t\t\twp_redirect( $url );\n\t\t\texit;\n\t\t}\n\t}\n\t\n\t/**\n\t * Parse the files in wp-content/languages and work out what \n\t * languages we've got available. Populates self::available_langs\n\t * with an array of language objects which look like:\n  \t * 'ar' => \n  \t * \t\tobject(stdClass)\n  \t * \t\t\tpublic 'name' => string 'Arabic'\n  \t * \t\t\tpublic 'code' => string 'ar'\n  \t * \t\t\tpublic 'url_prefix' => string 'ar'\n  \t * \t\t\tpublic 'text_direction' => string 'rtl'\n \t * \n\t * @return void\n\t **/\n\tprotected function parse_available_languages() {\n\t\tunset( $this->available_langs );\n\t\t$this->available_langs = array();\n\t\tforeach ( get_available_languages() as $lang_code ) {\n\t\t\tlist( $prefix ) = explode( '_', $lang_code );\n\t\t\t$lang = array(\n\t\t\t\t'name' => $this->format_code_lang( $prefix ),\n\t\t\t\t'code' => $lang_code,\n\t\t\t\t'url_prefix' => $prefix,\n\t\t\t\t'text_direction' => $this->is_rtl( $lang_code ),\n\t\t\t);\n\t\t\t// Cast to an object, in case we want to start using actual classes\n\t\t\t// at some point in the future.\n\t\t\t$this->available_langs[ $lang_code ] = (object) $lang;\n\t\t}\n\t\t// Add in US English, which is the default on WordPress and has no language files\n\t\t$en = new stdClass;\n\t\t$en->name = 'English (US)';\n\t\t$en->code = 'en_US';\n\t\t$en->url_prefix = 'en';\n\t\t$en->text_direction = 'ltr';\n\t\t$this->available_langs[ 'en_US' ] = $en;\n\t\t$this->available_langs = apply_filters( 'bbl_available_langs', $this->available_langs );\n\t\tksort( $this->available_langs );\n\t\t$this->update_option( 'available_langs', $this->available_langs );\n\t}\n\t\n\t/**\n\t * Parse (DON'T require or include) the [lang_code].php locale file in the languages \n\t * directory to work if the specified language is right to left. (We can't include or \n\t * require because it may contain function names which clash with other locale files.)\n\t *\n\t * @param string $lang The language code to retrieve RTL info for\n\t * @return bool True if the language is RTL\n\t **/\n\tprotected function is_rtl( $lang ) {\n\t\t$locale_file = WP_LANG_DIR . \"/$lang.php\";\n\t\tif ( ( 0 === validate_file( $lang ) ) && is_readable( $locale_file ) ) {\n\t\t\t$locale_file_code = file_get_contents( $locale_file );\n\t\t\t// Regex to find something looking like: $text_direction = 'rtl';\n\t\t\treturn ( (bool) preg_match( '/\\$text_direction\\s?=\\s?[\\'|\"]rtl[\\'|\"]\\s?;/i', $locale_file_code ) ) ? 'rtl' : 'ltr';\n\t\t}\n\t\treturn 'ltr';\n\t}\n\t\n\t/**\n\t * Return the language name for the provided language code.\n\t *\n\t * This method is an identical copy of format_code_lang \n\t * in wp-admin/includes/ms.php which is only available on Multisite.\n\t *\n\t * @FIXME: We end up with a load of anglicised names, which doesn't seem super-friendly, internationally speaking.\n\t * \n\t * @see format_code_lang()\n\t *\n\t * @param string $lang_short The language short code, e.g. 'en' (not 'en_GB')\n\t * @return string The language name, e.g. 'English'\n\t **/\n\tprotected function format_code_lang( $code ) {\n\t\t$code = strtolower( substr( $code, 0, 2 ) );\n\t\t$lang_codes = array(\n\t\t\t'aa' => 'Afar', 'ab' => 'Abkhazian', 'af' => 'Afrikaans', 'ak' => 'Akan', 'sq' => 'Albanian', 'am' => 'Amharic', 'ar' => 'Arabic', 'an' => 'Aragonese', 'hy' => 'Armenian', 'as' => 'Assamese', 'av' => 'Avaric', 'ae' => 'Avestan', 'ay' => 'Aymara', 'az' => 'Azerbaijani', 'ba' => 'Bashkir', 'bm' => 'Bambara', 'eu' => 'Basque', 'be' => 'Belarusian', 'bn' => 'Bengali',\n\t\t\t'bh' => 'Bihari', 'bi' => 'Bislama', 'bs' => 'Bosnian', 'br' => 'Breton', 'bg' => 'Bulgarian', 'my' => 'Burmese', 'ca' => 'Catalan; Valencian', 'ch' => 'Chamorro', 'ce' => 'Chechen', 'zh' => 'Chinese', 'cu' => 'Church Slavic; Old Slavonic; Church Slavonic; Old Bulgarian; Old Church Slavonic', 'cv' => 'Chuvash', 'kw' => 'Cornish', 'co' => 'Corsican', 'cr' => 'Cree',\n\t\t\t'cs' => 'Czech', 'da' => 'Danish', 'dv' => 'Divehi; Dhivehi; Maldivian', 'nl' => 'Dutch; Flemish', 'dz' => 'Dzongkha', 'en' => 'English', 'eo' => 'Esperanto', 'et' => 'Estonian', 'ee' => 'Ewe', 'fo' => 'Faroese', 'fj' => 'Fijjian', 'fi' => 'Finnish', 'fr' => 'French', 'fy' => 'Western Frisian', 'ff' => 'Fulah', 'ka' => 'Georgian', 'de' => 'German', 'gd' => 'Gaelic; Scottish Gaelic',\n\t\t\t'ga' => 'Irish', 'gl' => 'Galician', 'gv' => 'Manx', 'el' => 'Greek, Modern', 'gn' => 'Guarani', 'gu' => 'Gujarati', 'ht' => 'Haitian; Haitian Creole', 'ha' => 'Hausa', 'he' => 'Hebrew', 'hz' => 'Herero', 'hi' => 'Hindi', 'ho' => 'Hiri Motu', 'hu' => 'Hungarian', 'ig' => 'Igbo', 'is' => 'Icelandic', 'io' => 'Ido', 'ii' => 'Sichuan Yi', 'iu' => 'Inuktitut', 'ie' => 'Interlingue',\n\t\t\t'ia' => 'Interlingua (International Auxiliary Language Association)', 'id' => 'Indonesian', 'ik' => 'Inupiaq', 'it' => 'Italian', 'jv' => 'Javanese', 'ja' => 'Japanese', 'kl' => 'Kalaallisut; Greenlandic', 'kn' => 'Kannada', 'ks' => 'Kashmiri', 'kr' => 'Kanuri', 'kk' => 'Kazakh', 'km' => 'Central Khmer', 'ki' => 'Kikuyu; Gikuyu', 'rw' => 'Kinyarwanda', 'ky' => 'Kirghiz; Kyrgyz',\n\t\t\t'kv' => 'Komi', 'kg' => 'Kongo', 'ko' => 'Korean', 'kj' => 'Kuanyama; Kwanyama', 'ku' => 'Kurdish', 'lo' => 'Lao', 'la' => 'Latin', 'lv' => 'Latvian', 'li' => 'Limburgan; Limburger; Limburgish', 'ln' => 'Lingala', 'lt' => 'Lithuanian', 'lb' => 'Luxembourgish; Letzeburgesch', 'lu' => 'Luba-Katanga', 'lg' => 'Ganda', 'mk' => 'Macedonian', 'mh' => 'Marshallese', 'ml' => 'Malayalam',\n\t\t\t'mi' => 'Maori', 'mr' => 'Marathi', 'ms' => 'Malay', 'mg' => 'Malagasy', 'mt' => 'Maltese', 'mo' => 'Moldavian', 'mn' => 'Mongolian', 'na' => 'Nauru', 'nv' => 'Navajo; Navaho', 'nr' => 'Ndebele, South; South Ndebele', 'nd' => 'Ndebele, North; North Ndebele', 'ng' => 'Ndonga', 'ne' => 'Nepali', 'nn' => 'Norwegian Nynorsk; Nynorsk, Norwegian', 'nb' => 'Bokmål, Norwegian, Norwegian Bokmål',\n\t\t\t'no' => 'Norwegian', 'ny' => 'Chichewa; Chewa; Nyanja', 'oc' => 'Occitan, Provençal', 'oj' => 'Ojibwa', 'or' => 'Oriya', 'om' => 'Oromo', 'os' => 'Ossetian; Ossetic', 'pa' => 'Panjabi; Punjabi', 'fa' => 'Persian', 'pi' => 'Pali', 'pl' => 'Polish', 'pt' => 'Portuguese', 'ps' => 'Pushto', 'qu' => 'Quechua', 'rm' => 'Romansh', 'ro' => 'Romanian', 'rn' => 'Rundi', 'ru' => 'Russian',\n\t\t\t'sg' => 'Sango', 'sa' => 'Sanskrit', 'sr' => 'Serbian', 'hr' => 'Croatian', 'si' => 'Sinhala; Sinhalese', 'sk' => 'Slovak', 'sl' => 'Slovenian', 'se' => 'Northern Sami', 'sm' => 'Samoan', 'sn' => 'Shona', 'sd' => 'Sindhi', 'so' => 'Somali', 'st' => 'Sotho, Southern', 'es' => 'Spanish; Castilian', 'sc' => 'Sardinian', 'ss' => 'Swati', 'su' => 'Sundanese', 'sw' => 'Swahili',\n\t\t\t'sv' => 'Swedish', 'ty' => 'Tahitian', 'ta' => 'Tamil', 'tt' => 'Tatar', 'te' => 'Telugu', 'tg' => 'Tajik', 'tl' => 'Tagalog', 'th' => 'Thai', 'bo' => 'Tibetan', 'ti' => 'Tigrinya', 'to' => 'Tonga (Tonga Islands)', 'tn' => 'Tswana', 'ts' => 'Tsonga', 'tk' => 'Turkmen', 'tr' => 'Turkish', 'tw' => 'Twi', 'ug' => 'Uighur; Uyghur', 'uk' => 'Ukrainian', 'ur' => 'Urdu', 'uz' => 'Uzbek',\n\t\t\t've' => 'Venda', 'vi' => 'Vietnamese', 'vo' => 'Volapük', 'cy' => 'Welsh','wa' => 'Walloon','wo' => 'Wolof', 'xh' => 'Xhosa', 'yi' => 'Yiddish', 'yo' => 'Yoruba', 'za' => 'Zhuang; Chuang', 'zu' => 'Zulu' );\n\t\t$lang_codes = apply_filters( 'lang_codes', $lang_codes, $code );\n\t\t$lang_codes = apply_filters( 'bbl_lang_codes', $lang_codes, $code );\n\t\treturn strtr( $code, $lang_codes );\n\t}\n\n\t/**\n\t * Setup some initial language data, so the user's site doesn't immediately\n\t * fail when the plugin is activated.\n\t *\n\t * @return void\n\t **/\n\tprotected function set_defaults() {\n\n\t\t$locale = get_option( 'WPLANG' );\n\n\t\tif ( empty( $locale ) and is_multisite() ) {\n\t\t\t$locale = get_site_option( 'WPLANG' );\n\t\t}\n\n\t\tif ( empty( $locale ) and defined( 'WPLANG' ) ) {\n\t\t\t// The WPLANG constant is deprecated since WordPress 4.0.\n\t\t\t$locale = WPLANG;\n\t\t}\n\n\t\tif ( empty( $locale ) ) {\n\t\t\t$locale = 'en_US';\n\t\t}\n\n\t\t$url_prefix = strtolower( substr( $locale, 0, 2 ) );\n\n\t\t$this->active_langs = array( $url_prefix => $locale );\n\n\t\t$this->langs = array( $locale => $this->available_langs[ $locale ] );\n\t\t$this->langs[ $locale ]->url_prefix = $url_prefix;\n\t\t$this->langs[ $locale ]->display_name = $this->langs[ $locale ]->name;\n\t\t$this->default_lang = $locale;\n\t\t$this->public_langs = array( $locale );\n\t}\n}\n\nglobal $bbl_languages;\n$bbl_languages = new Babble_Languages();\n"
  },
  {
    "path": "class-locale.php",
    "content": "<?php\n\n/**\n * Manages the locale currently set for the site.\n *\n * @package Babble\n * @since Alpha 1\n */\nclass Babble_Locale {\n\t\n\t/**\n\t * A regex to get the language code prefix from\n\t * a URL.\n\t *\n\t * @var string\n\t **/\n\tprotected $lang_regex = '|^[^/]+|i';\n\n\t/**\n\t * The language for the content of the current request.\n\t *\n\t * @var string\n\t **/\n\tprotected $content_lang;\n\n\t/**\n\t * The interface language for the current request.\n\t *\n\t * @var string\n\t **/\n\tprotected $interface_lang;\n\n\t/**\n\t * The locale for the current request.\n\t *\n\t * @var string\n\t **/\n\tprotected $locale;\n\n\t/**\n\t * The URL prefix for the current request\n\t *\n\t * @var string\n\t **/\n\tprotected $url_prefix;\n\t\n\t/**\n\t * A simple flag to stop infinite recursion in various places.\n\t *\n\t * @var boolean\n\t **/\n\tprotected $no_recursion;\n\t\n\t/**\n\t * The languages that we've switched to, in order.\n\t *\n\t * @var array\n\t **/\n\tprotected $lang_stack;\n\t\n\t/**\n\t * The current version for purposes of rewrite rules, any \n\t * DB updates, cache busting, etc\n\t *\n\t * @var int\n\t **/\n\tprotected $version = 2;\n\t\n\t/**\n\t * Setup any add_action or add_filter calls. Initiate properties.\n\t *\n\t * @return void\n\t **/\n\tfunction __construct() {\n\t\tadd_action( 'plugins_loaded',                  array( $this, 'plugins_loaded' ), 0 );\n\t\tadd_action( 'admin_init',                      array( $this, 'admin_init' ) );\n\t\tadd_action( 'admin_notices',                   array( $this, 'admin_notices' ) );\n\t\tadd_action( 'parse_request',                   array( $this, 'parse_request_early' ), 0 );\n\t\tadd_action( 'pre_comment_on_post',             array( $this, 'pre_comment_on_post' ) );\n\n\t\tadd_filter( 'body_class',                      array( $this, 'body_class' ) );\n\t\tadd_filter( 'locale',                          array( $this, 'set_locale' ) );\n\t\tadd_filter( 'mod_rewrite_rules',               array( $this, 'mod_rewrite_rules' ) );\n\t\tadd_filter( 'post_class',                      array( $this, 'post_class' ), null, 3 );\n\t\tadd_filter( 'pre_update_option_rewrite_rules', array( $this, 'internal_rewrite_rules_filter' ) );\n\t\tadd_filter( 'query_vars',                      array( $this, 'query_vars' ) );\n\t}\n\n\tpublic function plugins_loaded() {\n\t\tglobal $wpdb;\n\n\t\t# @TODO this exposes the $wpdb prefix. We should set the cookie path to the site path instead\n\t\t# (example.com/site or site.example.com) so the cookie is only set for the current site on a multisite install\n\t\t# @TODO actually, both of these should be user preferences, not cookies.\n\t\t$this->content_lang_cookie   = $wpdb->prefix . '_bbl_content_lang_' . COOKIEHASH;\n\t\t$this->interface_lang_cookie = $wpdb->prefix . '_bbl_interface_lang_' . COOKIEHASH;\n\t}\n\n\t/**\n\t * Hooks the WP admin_init action \n\t *\n\t * @return void\n\t **/\n\tpublic function admin_init() {\n\t\tadd_filter( 'home_url', array( $this, 'home_url' ), null, 2 );\n\t\t$this->maybe_update();\n\t\t$this->maybe_set_cookie_content_lang();\n\t\t$this->maybe_set_cookie_interface_lang();\n\t}\n\n\t/**\n\t * Hooks the WP admin_notices action to warn the admin\n\t * if the permalinks aren't pretty enough.\n\t *\n\t * @return void\n\t **/\n\tpublic function admin_notices() {\n\t\tif ( ! get_option( 'permalink_structure' ) ) {\n\t\t\tprintf( '<div class=\"error\"><p>%s</p></div>', sprintf( __( '<strong>Babble problem:</strong> Fancy permalinks are disabled. <a href=\"%s\">Please enable them</a> in order to have language prefixed URLs work correctly.', 'babble' ), admin_url( '/options-permalink.php' ) ) );\n\t\t}\n\t}\n\n\t/**\n\t * Ensure we keep the standard WP rewrite rules.\n\t *\n\t * @param string $rules The mod_rewrite rules block generated by WP \n\t * @return string A mod_rewrite rules block\n\t **/\n\tpublic function mod_rewrite_rules( $rules ) {\n\t\tglobal $wp_rewrite;\n\t\tif ( $this->no_recursion )\n\t\t\treturn $rules;\n\t\t$this->no_recursion = true;\n\t\t// We need the WP_Rewrite mod_rewrite_rules method to run\n\t\t// home_url without a lang query var set, or it generates \n\t\t// an inaccurate RewriteBase and last RewriteRule.\n\t\tremove_filter( 'home_url', array( $this, 'home_url' ), null, 2 );\n\t\t$rules = $wp_rewrite->mod_rewrite_rules();\n\t\tadd_filter( 'home_url', array( $this, 'home_url' ), null, 2 );\n\t\t$this->no_recursion = false;\n\t\treturn $rules;\n\t}\n\t\n\t/**\n\t * Hooks the WP pre_update_option_rewrite_rules filter to add \n\t * a prefix to the URL to pick up the virtual sub-dir specifying\n\t * the language. The redirect portion can and should remain perfectly\n\t * ignorant of it though, as we change it in parse_request.\n\t * \n\t * @param array $langs The language codes\n\t * @return array An array of language codes utilised for this site. \n\t **/\n\tpublic function internal_rewrite_rules_filter( $rules ){\n\t\tglobal $wp_rewrite;\n\n\t\t// Some rules need to be at the root of the site, without a\n\t\t// language prefix, e.g. http://www.example.com/humans.txt. \n\t\t// The following filter allows plugin and theme devs to add \n\t\t// to this list of site root level URLs which are untranslated.\n\t\t$non_translated_rewrite_rules = apply_filters( 'bbl_non_translated_queries', array(\n\t\t\t'humans\\.txt$',\n\t\t\t'robots\\.txt$',\n\t\t) );\n\n\t    foreach( (array) $rules as $regex => $query ) {\n\t\t\tif ( in_array( $regex, $non_translated_rewrite_rules ) ) {\n\t\t\t\t$new_rules[ $regex ] = $query;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif ( substr( $regex, 0, 1 ) == '^' ) {\n\t\t\t\t$new_rules[ '^[a-zA-Z_]+/' . substr( $regex, 1 ) ] = $query;\n\t\t\t}\n\t\t\telse {\n\t\t\t\t$new_rules[ '[a-zA-Z_]+/' . $regex ] = $query;\n\t\t\t}\n\t\t}\n\n\t\t// The WP robots.txt rewrite rule will not have worked, as the\n\t\t// code objects to the language prefix. Here we add it in again.\n\t\t$hooked = false;\n\n\t\tif ( has_filter( 'home_url' ) ) {\n\t\t\tremove_filter( 'home_url', array( $this, 'home_url' ), null, 2 );\n\t\t\t$hooked = true;\n\t\t}\n\n\t\t$home_path = parse_url( home_url() );\n\n\t\tif ( $hooked ) {\n\t\t\tadd_filter( 'home_url', array( $this, 'home_url' ), null, 2 );\n\t\t}\n\n\t\tif ( empty( $home_path['path'] ) || '/' == $home_path['path'] ) {\n\t\t\t$new_rules[ 'robots\\.txt$' ] = $wp_rewrite->index . '?robots=1';\n\t\t}\n\n\t    return $new_rules;\n\t}\n\n\t/**\n\t * Hooks the WP locale filter to switch locales whenever we gosh darned want.\n\t *\n\t * @param string $locale The locale \n\t * @return string The locale\n\t **/\n\tpublic function set_locale( $locale ) {\n\t\t// Deal with the special case of wp-comments-post.php\n\t\tif ( false !== stristr( $_SERVER[ 'REQUEST_URI' ], 'wp-comments-post.php' ) ) {\n\t\t\t// @TODO we should be able to hook into an action here (pre_comment_post) rather than looking at the URL.\n\t\t\tif ( $comment_post_ID = ( isset( $_POST[ 'comment_post_ID' ] ) ) ? (int) $_POST[ 'comment_post_ID' ] : false ) {\n\t\t\t\tif ( ! isset( $this->content_lang ) ) {\n\t\t\t\t\t$this->set_content_lang( bbl_get_post_lang_code( $comment_post_ID ) );\n\t\t\t\t}\n\t\t\t\treturn $this->content_lang;\n\t\t\t}\n\t\t}\n\n\t\tif ( is_admin() ) {\n\t\t\tif ( isset( $this->interface_lang ) ) {\n\t\t\t\treturn $this->interface_lang;\n\t\t\t}\n\t\t} else {\n\t\t\tif ( isset( $this->content_lang ) ) {\n\t\t\t\treturn $this->content_lang;\n\t\t\t}\n\t\t}\n\n\t\t// $current_user = wp_get_current_user();\n\t\tif ( $lang = $this->get_cookie_interface_lang() ) {\n\t\t\t$this->set_interface_lang( $lang );\n\t\t}\n\n\t\t// $current_user = wp_get_current_user();\n\t\tif ( $lang = $this->get_cookie_content_lang() ) {\n\t\t\t$this->set_content_lang( $lang );\n\t\t}\n\n\t\tif ( is_admin() ) {\n\t\t\t// @FIXME: At this point a mischievous XSS \"attack\" could set a user's admin area language for them\n\t\t\tif ( isset( $_POST[ 'interface_lang' ] ) ) {\n\t\t\t\t$this->set_interface_lang( $_POST[ 'interface_lang' ] );\n\t\t\t}\n\t\t\t// @FIXME: At this point a mischievous XSS \"attack\" could set a user's content language for them\n\t\t\tif ( isset( $_GET[ 'lang' ] ) ) {\n\t\t\t\t$this->set_content_lang( $_GET[ 'lang' ] );\n\t\t\t}\n\t\t} else { // Front end\n\t\t\t// @FIXME: Should probably check the available languages here\n\t\t\tif ( preg_match( $this->lang_regex, $this->get_request_string(), $matches ) )\n\t\t\t\t$this->set_content_lang_from_prefix( $matches[ 0 ] );\n\t\t}\n\n\t\tif ( ! isset( $this->content_lang ) || ! $this->content_lang )\n\t\t\t$this->set_content_lang( bbl_get_default_lang_code() );\n\t\tif ( ! isset( $this->interface_lang ) || ! $this->interface_lang )\n\t\t\t$this->set_interface_lang( bbl_get_default_lang_code() );\n\n\t\tif ( is_admin() )\n\t\t\treturn $this->interface_lang;\n\t\telse\n\t\t\treturn $this->content_lang;\n\t}\n\n\t/**\n\t * Hooks the WP parse_request action \n\t *\n\t * FIXME: Should I be extending and replacing the WP class?\n\t *\n\t * @param WP $wp The WP object, passed by reference (so no need to return)\n\t * @return void\n\t **/\n\tpublic function parse_request_early( WP $wp ) {\n\t\t// If this is the site root, redirect to default language homepage \n\t\tif ( ! $wp->request ) {\n\t\t\tremove_filter( 'home_url', array( $this, 'home_url' ), null, 2 );\n\t\t\twp_redirect( home_url( bbl_get_default_lang_url_prefix() ) );\n\t\t\texit;\n\t\t}\n\t\t// Otherwise, simply set the lang for this request\n\t\t$wp->query_vars[ 'lang' ] = $this->content_lang;\n\t\t$wp->query_vars[ 'lang_url_prefix' ] = $this->url_prefix;\n\t}\n\n\t/**\n\t * Hooks the WP query_vars filter to add the home_url filter.\n\t *\n\t * @param array $query_vars An array of the public query vars \n\t * @return array An array of the public query vars\n\t **/\n\tpublic function query_vars( array $query_vars ) {\n\t\t# @TODO why is this here?\n\t\tadd_filter( 'home_url', array( $this, 'home_url' ), null, 2 );\n\t\treturn array_merge( $query_vars, array( 'lang', 'lang_url_prefix' ) );\n\t}\n\n\t/**\n\t * Hooks the WP pre_comment_on_post action to add the \n\t * home_url filter.\n\t *\n\t * @return void\n\t **/\n\tpublic function pre_comment_on_post() {\n\t\t# @TODO why is this here?\n\t\tadd_filter( 'home_url', array( $this, 'home_url' ), null, 2 );\n\t}\n\n\t/**\n\t * Hooks the WP home_url action \n\t * \n\t * Hackity hack: this function is attached with add_filter within\n\t * the query_vars filter and the pre_comment_on_post action.\n\t * @TODO: Can't remember why this is attached like this… investigate.\n\t *\n\t * @param string $url The URL \n\t * @param string $path The path \n\t * @param string $orig_scheme The original scheme \n\t * @param int $blog_id The ID of the blog \n\t * @return string The URL\n\t **/\n\tpublic function home_url( $url, $path ) {\n\t\t$base_url = get_option( 'home' );\n\t\t$url      = trailingslashit( $base_url ) . $this->url_prefix;\n\n\t\tif ( $path && is_string( $path ) )\n\t\t\t$url .= '/' . ltrim( $path, '/' );\n\n\t\treturn $url;\n\t}\n\n\t/**\n\t * Hooks the WP body_class filter to add some language specific classes.\n\t *\n\t * @param array $classes The body classes \n\t * @return array The body classes \n\t **/\n\tpublic function body_class( array $classes ) {\n\t\t$lang = bbl_get_current_lang();\n\t\t$classes[] = 'bbl-' . $lang->text_direction;\n\t\t# @TODO I don't think this class should be included:\n\t\t$classes[] = 'bbl-' . sanitize_title( $lang->name );\n\t\t$classes[] = 'bbl-' . sanitize_title( $lang->url_prefix );\n\t\t$classes[] = 'bbl-' . sanitize_title( $lang->code );\n\t\t# @TODO I don't think this class should be included:\n\t\t$classes[] = 'bbl-' . sanitize_title( $lang->display_name );\n\t\treturn $classes;\n\t}\n\n\t/**\n\t * Hooks the WP post_class filter to add some language specific classes.\n\t *\n\t * @param array $classes The post classes \n\t * @param array $class One or more classes which have been added to the class list.\n\t * @param int $post_id The ID of the post we're providing classes for \n\t * @return array The body classes \n\t **/\n\tpublic function post_class( array $classes, $class, $post_id ) {\n\t\t$post = get_post( $post_id );\n\t\t$post_lang_code = bbl_get_post_lang_code( $post );\n\t\t$lang = bbl_get_lang( $post_lang_code );\n\t\tif ( self::use_default_text_direction( $post ) ) {\n\t\t\t$default_lang = bbl_get_default_lang();\n\t\t\t$classes[] = 'bbl-post-' . $default_lang->text_direction;\n\t\t} else {\n\t\t\t$classes[] = 'bbl-post-' . $lang->text_direction;\n\t\t}\n\t\t# @TODO I don't think this class should be included:\n\t\t$classes[] = 'bbl-post-' . sanitize_title( $lang->name );\n\t\t$classes[] = 'bbl-post-' . sanitize_title( $lang->url_prefix );\n\t\t$classes[] = 'bbl-post-' . sanitize_title( $lang->code );\n\t\t# @TODO I don't think this class should be included:\n\t\t$classes[] = 'bbl-post-' . sanitize_title( $lang->display_name );\n\t\treturn $classes;\n\t}\n\n\t// Public Methods\n\t// --------------\n\n\t/**\n\t * Return whether the post should use the default language's text direction or not.\n\t *\n\t * @param  WP_Post $post The post object.\n\t * @return bool          True if the post should use the default language text direction. False if not.\n\t */\n\tpublic static function use_default_text_direction( WP_Post $post ) {\n\t\tif ( get_post_meta( $post->ID, '_bbl_default_text_direction', true ) ) {\n\t\t\treturn true;\n\t\t} else if ( empty( $post->post_content ) ) {\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t}\n\n\t/**\n\t * Get the current (content) lang for this class, which is also the\n\t * current lang in the Query Vars.\n\t *\n\t * @TODO deprecate\n\t *\n\t * @return string\n\t **/\n\tpublic function get_lang() {\n\t\treturn $this->get_content_lang();\n\t}\n\n\t/**\n\t * Get the current content lang for this class, which is also the\n\t * current lang in the Query Vars.\n\t *\n\t * @return string\n\t **/\n\tpublic function get_content_lang() {\n\t\treturn $this->content_lang;\n\t}\n\n\t/**\n\t * Get the current interface lang for this class.\n\t *\n\t * @return string\n\t **/\n\tpublic function get_interface_lang() {\n\t\treturn $this->interface_lang;\n\t}\n\n\t/**\n\t * Set the current (content) lang for this class, and in Query Vars.\n\t *\n\t * @param string $lang The language code to switch to \n\t * @return void\n\t **/\n\tpublic function switch_to_lang( $lang ) {\n\t\t// @FIXME: Need to validate language here\n\t\tif ( ! is_array( $this->lang_stack ) )\n\t\t\t$this->lang_stack = array();\n\t\t$this->lang_stack[] = $this->content_lang;\n\t\t$this->set_content_lang( $lang );\n\t\tset_query_var( 'lang', $this->content_lang );\n\t}\n\t\n\t/**\n\t * Restore the previous lang from the switched stack.\n\t *\n\t * @return void\n\t **/\n\tpublic function restore_lang() {\n\t\t$this->set_content_lang( array_pop( $this->lang_stack ) );\n\t\tset_query_var( 'lang', $this->content_lang );\n\t}\n\n\t// Non-public Methods\n\t// ------------------\n\n\t/**\n\t * Set the content language code and URL prefix for any \n\t * subsequent requests.\n\t *\n\t * @FIXME: Currently we don't check that the language is valid\n\t *\n\t * @param string $code A language code\n\t * @return void\n\t **/\n\tprotected function set_content_lang( $code ) {\n\t\tglobal $bbl_languages;\n\t\t// Set the content language in the application\n\t\t$this->content_lang = $code;\n\t\t$this->url_prefix = $bbl_languages->get_url_prefix_from_code( $this->content_lang );\n\t}\n\n\t/**\n\t * Set the interace language code.\n\t *\n\t * @FIXME: Currently we don't check that the language is valid\n\t *\n\t * @param string $code A language code\n\t * @return void\n\t **/\n\tprotected function set_interface_lang( $code ) {\n\t\t// Set the interface language in the application\n\t\t$this->interface_lang = $code;\n\t}\n\n\t/**\n\t * Set the content language for the URL prefix provided.\n\t *\n\t * @param string $url_prefix A URL prefix, e.g. \"de\" \n\t * @return void\n\t **/\n\tprotected function set_content_lang_from_prefix( $url_prefix ) {\n\t\tglobal $bbl_languages;\n\t\t$this->set_content_lang( bbl_get_lang_from_prefix( $url_prefix ) );\n\t}\n\n\t/**\n\t * Get the request string for the request, using code copied \n\t * straight from WP->parse_request.\n\t *\n\t * @return string The request\n\t **/\n\tprotected function get_request_string() {\n\t\tglobal $wp_rewrite;\n\t\t// @FIXME: Copying a huge hunk of code from WP->parse_request here, feels ugly.\n\t\t// START: Huge hunk of WP->parse_request\n\t\tif ( isset($_SERVER['PATH_INFO']) )\n\t\t\t$pathinfo = $_SERVER['PATH_INFO'];\n\t\telse\n\t\t\t$pathinfo = '';\n\t\t$pathinfo_array = explode('?', $pathinfo);\n\t\t$pathinfo = str_replace(\"%\", \"%25\", $pathinfo_array[0]);\n\t\t$req_uri = $_SERVER['REQUEST_URI'];\n\t\t$req_uri_array = explode('?', $req_uri);\n\t\t$req_uri = $req_uri_array[0];\n\t\t$self = $_SERVER['PHP_SELF'];\n\t\t$home_path = parse_url(home_url());\n\t\tif ( isset($home_path['path']) )\n\t\t\t$home_path = $home_path['path'];\n\t\telse\n\t\t\t$home_path = '';\n\t\t$home_path = trim($home_path, '/');\n\n\t\t// Trim path info from the end and the leading home path from the\n\t\t// front.  For path info requests, this leaves us with the requesting\n\t\t// filename, if any.  For 404 requests, this leaves us with the\n\t\t// requested permalink.\n\t\t$req_uri = str_replace($pathinfo, '', $req_uri);\n\t\t$req_uri = trim($req_uri, '/');\n\t\t$req_uri = preg_replace(\"|^$home_path|\", '', $req_uri);\n\t\t$req_uri = trim($req_uri, '/');\n\t\t$pathinfo = trim($pathinfo, '/');\n\t\t$pathinfo = preg_replace(\"|^$home_path|\", '', $pathinfo);\n\t\t$pathinfo = trim($pathinfo, '/');\n\t\t$self = trim($self, '/');\n\t\t$self = preg_replace(\"|^$home_path|\", '', $self);\n\t\t$self = trim($self, '/');\n\n\t\t// The requested permalink is in $pathinfo for path info requests and\n\t\t//  $req_uri for other requests.\n\t\tif ( ! empty($pathinfo) && !preg_match('|^.*' . $wp_rewrite->index . '$|', $pathinfo) ) {\n\t\t\t$request = $pathinfo;\n\t\t} else {\n\t\t\t// If the request uri is the index, blank it out so that we don't try to match it against a rule.\n\t\t\tif ( is_object( $wp_rewrite ) && $req_uri == $wp_rewrite->index )\n\t\t\t\t$req_uri = '';\n\t\t\t$request = $req_uri;\n\t\t}\n\t\t// END: Huge hunk of WP->parse_request\n\t\treturn $request;\n\t}\n\n\t/**\n\t * Sets the content language cookie where necessary. We are using cookies\n\t * as we cannot get userdata at the set_locale action, which is where \n\t * we need to read the user's language.\n\t *\n\t * @return void\n\t **/\n\tprotected function maybe_set_cookie_content_lang() {\n\t\t// @FIXME: At this point a mischievous XSS \"attack\" could set a user's content language for them\n\t\tif ( $requested_lang = ( isset( $_GET[ 'lang' ] ) ) ? $_GET[ 'lang' ] : false )\n\t\t\tsetcookie( $this->content_lang_cookie, $requested_lang, time() + 31536000, COOKIEPATH, COOKIE_DOMAIN);\n\t}\n\n\t/**\n\t * Sets the admin language cookie where necessary. We are using cookies\n\t * as we cannot get userdata at the set_locale action, which is where \n\t * we need to read the user's language.\n\t *\n\t * @return void\n\t **/\n\tprotected function maybe_set_cookie_interface_lang() {\n\t\t// @FIXME: At this point a mischievous XSS \"attack\" could set a user's admin area language for them\n\t\tif ( $requested_lang = ( isset( $_POST[ 'interface_lang' ] ) ) ? $_POST[ 'interface_lang' ] : false )\n\t\t\tsetcookie( $this->interface_lang_cookie, $requested_lang, time() + 31536000, COOKIEPATH, COOKIE_DOMAIN);\n\t}\n\n\t/**\n\t * Gets the language code from the content language cookie.\n\t *\n\t * @TODO: This should use a cookie that's keyed to the current user when present\n\t *\n\t * @return string A language code\n\t **/\n\tprotected function get_cookie_content_lang() {\n\t\treturn ( isset( $_COOKIE[ $this->content_lang_cookie ] ) ) ? $_COOKIE[ $this->content_lang_cookie ] : '';\n\t}\n\n\t/**\n\t * Gets the language code from the interface language cookie.\n\t *\n\t * @TODO: This should use a cookie that's keyed to the current user when present\n\t *\n\t * @return string A language code\n\t **/\n\tprotected function get_cookie_interface_lang() {\n\t\treturn ( isset( $_COOKIE[ $this->interface_lang_cookie] ) ) ? $_COOKIE[ $this->interface_lang_cookie ] : '';\n\t}\n\n\t/**\n\t * Checks the DB structure is up to date.\n\t *\n\t * @return void\n\t * @author Simon Wheatley\n\t **/\n\tprotected function maybe_update() {\n\t\tglobal $wpdb;\n\t\t$option_name = 'bbl-locale-version';\n\t\t$version = get_option( $option_name, 0 );\n\n\t\tif ( $this->version == $version )\n\t\t\treturn;\n\n\t\tif ( $version < 1 ) {\n\t\t\terror_log( \"Babble Locale: Flushing rewrite rules\" );\n\t\t\tflush_rewrite_rules();\n\t\t}\n\n\t\terror_log( \"Babble Locale: Done updates\" );\n\t\tupdate_option( $option_name, $this->version );\n\t}\n\n}\n\nglobal $bbl_locale;\n$bbl_locale = new Babble_Locale();\n"
  },
  {
    "path": "class-meta.php",
    "content": "<?php\n/**\n * Class for handling post meta translations.\n *\n * @package Babble\n * @since 1.5\n */\nabstract class Babble_Meta_Field {\n\t\n\tpublic function __construct( WP_Post $post, $meta_key, $meta_title, array $args = array() ) {\n\t\t$this->post       = $post;\n\t\t$this->meta_key   = $meta_key;\n\t\t$this->meta_title = $meta_title;\n\t\t$this->meta_value = get_post_meta( $this->post->ID, $this->meta_key, true );\n\t\t$this->args       = $args;\n\t}\n\n\tabstract public function get_input( $name, $value );\n\n\tpublic function get_output() {\n\t\treturn esc_html( $this->get_value() );\n\t}\n\n\tpublic function get_title() {\n\t\treturn $this->meta_title;\n\t}\n\n\tpublic function get_value() {\n\t\treturn $this->meta_value;\n\t}\n\n\tpublic function get_key() {\n\t\treturn $this->meta_key;\n\t}\n\n\tpublic function update( $value, WP_Post $job ) {\n\t\treturn $value;\n\t}\n\n}\n\nclass Babble_Meta_Field_Text extends Babble_Meta_Field {\n\n\tpublic function get_input( $name, $value ) {\n\t\treturn sprintf( '<input type=\"text\" name=\"%s\" value=\"%s\">',\n\t\t\tesc_attr( $name ),\n\t\t\tesc_attr( $value )\n\t\t);\n\t}\n\n}\n\nclass Babble_Meta_Field_Textarea extends Babble_Meta_Field {\n\n\tpublic function get_input( $name, $value ) {\n\t\treturn sprintf( '<textarea name=\"%s\" rows=\"10\">%s</textarea>',\n\t\t\tesc_attr( $name ),\n\t\t\tesc_textarea( $value )\n\t\t);\n\t}\n\n\tpublic function get_output() {\n\t\treturn nl2br( esc_html( $this->get_value() ) );\n\t}\n\n}\n\nclass Babble_Meta_Field_Editor extends Babble_Meta_Field {\n\n\tpublic function get_input( $name, $value ) {\n\t\t$args = array(\n\t\t\t'textarea_name' => $name,\n\t\t);\n\n\t\t# see _WP_Editors()::parse_settings() for available editor settings\n\t\tif ( !empty( $this->args['editor_settings'] ) ) {\n\t\t\t$args = array_merge( $args, $this->args['editor_settings'] );\n\t\t}\n\n\t\tob_start();\n\t\twp_editor( $value, sprintf( 'meta-input-%s', $this->get_key() ), $args );\n\t\treturn ob_get_clean();\n\t}\n\n\tpublic function get_output() {\n\t\t$args = array(\n\t\t\t'textarea_name' => 'doesnotmatter',\n\t\t\t'media_buttons' => false,\n\t\t\t'tinymce'       => array(\n\t\t\t\t'readonly' => 1,\n\t\t\t),\n\t\t);\n\n\t\tob_start();\n\t\twp_editor( $this->get_value(), sprintf( 'meta-output-%s', $this->get_key() ), $args );\n\t\treturn ob_get_clean();\n\t}\n\n}\n"
  },
  {
    "path": "class-plugin.php",
    "content": "<?php \n\n// ======================================================================================\n// This library is free software; you can redistribute it and/or\n// modify it under the terms of the GNU Lesser General Public\n// License as published by the Free Software Foundation; either\n// version 2.1 of the License, or (at your option) any later version.\n// \n// This library is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n// Lesser General Public License for more details.\n// ======================================================================================\n// @author     Simon Wheatley (http://simonwheatley.co.uk)\n// @version    1.0\n// @copyright  Copyright &copy; 2010 Simon Wheatley, All Rights Reserved\n// @copyright  Some parts Copyright &copy; 2007 John Godley, All Rights Reserved\n// ======================================================================================\n// 1.0     - Initial release\n// 1.01    - Added add_shortcode\n// 1.10    - Added code to allow the base class to be used in a theme\n// 1.2     - Truncate helper method, admin notices/errors, throw error if not provided \n//           with name in setup method call, default $pluginfile to __FILE__, bugfix around \n//           option key in delete_option method.\n// 1.3     - Locale stuff\n//         - Fix for get_option\n// 1.31    - Attempt to cope with Win32 directory separators\n// 1.32    - Add a remove_filter method\n// 1.33    - Add `sil_plugins_dir` and `sil_plugins_url` filters, to allow placement \n//           outside the `wp-content/plugins/` folder, for example using `require_once` \n//           to include from the theme `functions.php`.\n// ======================================================================================\n\n\n/**\n * Wraps up several useful functions for WordPress plugins and provides a method to separate\n * display HTML from PHP code.\n *\n * <h4>Display Rendering</h4>\n * \n * The class uses a similar technique to Ruby On Rails views, whereby the display HTML is kept\n * in a separate directory and file from the main code.  A display is 'rendered' (sent to the browser)\n * or 'captured' (returned to the calling function).\n * \n * Template files are separated into two areas: admin and user.  Admin templates are only for display in\n * the WordPress admin interface, while user templates are typically for display on the site (although neither\n * of these are enforced).  All templates are PHP code, but are referred to without .php extension.\n * \n * The reason for this separation is that one golden rule of plugin creation is that someone will \n * always want to change the formatting and style of your output.  Rather than forcing them to \n * modify the plugin (bad), or modify files within the plugin (equally bad), the class allows \n * user templates to be overridden with files contained within the theme.\n *\n * An additional benefit is that it leads to code re-use, especially with regards to Ajax (i.e. \n * your display code can be called from many locations)\n * \n * @package Babble\n * @author Simon Wheatley\n * @copyright Copyright (C) Simon Wheatley (except where noted)\n **/\nclass Babble_Plugin {\n\n\t/**\n\t * The name of this plugin\n\t *\n\t * @var string\n\t **/\n\tprotected $name;\n\n\t/**\n\t * The filepath to the directory containing this plugin\n\t *\n\t * @var string\n\t **/\n\tprotected $dir;\n\n\t/**\n\t * The URL for the directory containing this plugin\n\t *\n\t * @var string\n\t **/\n\tprotected $url;\n\t\n\t/**\n\t * Useful for switching between debug and compressed scripts.\n\t *\n\t * @var string\n\t **/\n\tprotected $suffix;\n\n\t/**\n\t * Records the type of this class, either 'plugin' or 'theme'.\n\t *\n\t * @var string\n\t **/\n\tprotected $type;\n\t\n\t/**\n\t * Note the name of the function to call when the theme is activated.\n\t *\n\t * @var string\n\t **/\n\tprotected $theme_activation_function;\n\n\t/**\n\t * Initiate!\n\t *\n\t * @return void\n\t * @author Simon Wheatley\n\t **/\n\tpublic function setup( $name = '', $type = null ) {\n\t\tif ( ! $name )\n\t\t\tthrow new exception( \"Please pass the name parameter into the setup method.\" );\n\t\t$this->name = $name;\n\n\t\t// Attempt to handle a Windows\n\t\t$ds = ( defined( 'DIRECTORY_SEPARATOR' ) ) ? DIRECTORY_SEPARATOR : '\\\\';\n\t\t$file = str_replace( $ds, '/', __FILE__ );\n\t\t$plugins_dir = str_replace( $ds, '/', dirname( __FILE__ ) );\n\t\t// Setup the dir and url for this plugin/theme\n\t\tif ( 'theme' == $type ) {\n\t\t\t// This is a theme\n\t\t\t$this->type = 'theme';\n\t\t\t$this->dir = get_stylesheet_directory();\n\t\t\t$this->url = get_stylesheet_directory_uri();\n\t\t} elseif ( stripos( $file, $plugins_dir ) !== false || 'plugin' == $type ) {\n\t\t\t// This is a plugin\n\t\t\t$this->type = 'plugin';\n\n\t\t\t// Allow someone to override the assumptions we're making here about where \n\t\t\t// the plugin is held. For example, if this plugin is included as part of \n\t\t\t// the files for a theme, in wp-content/themes/[your theme]/plugins/ then\n\t\t\t// you could hook `sil_plugins_dir` and `sil_plugins_url` to correct\n\t\t\t// our assumptions.\n\t\t\t// N.B. Because this code is running when the file is required, other plugins\n\t\t\t// may not be loaded and able to hook these filters!\n\t\t\t$plugins_dir = apply_filters( 'sil_plugins_dir', $plugins_dir, $this->name );\n\t\t\t$plugins_url = apply_filters( 'sil_plugins_url', plugins_url( '', __FILE__ ), $this->name );\n\t\t\t$this->dir = trailingslashit( $plugins_dir );\n\t\t\t$this->url = trailingslashit( $plugins_url );\n\t\t} else {\n\t\t\t// WTF?\n\t\t\terror_log( 'PLUGIN/THEME ERROR: Cannot find ' . $plugins_dir . ' or \"themes\" in ' . $file );\n\t\t}\n\n\t\t// Suffix for enqueuing\n\t\t$this->suffix = defined('SCRIPT_DEBUG') && SCRIPT_DEBUG ? '.dev' : '';\n\t\t\n\t\tif ( is_admin() ) {\n\t\t\t// Admin notices\n\t\t\t$this->add_action( 'admin_notices', '_admin_notices' );\n\t\t}\n\n\t\t$this->add_action( 'init', 'load_locale' );\n\t}\n\n\t/**\n\t * Hook called to change the locale directory.\n\t * \n\t * @return void\n\t * @author © John Godley\n\t **/\n\tfunction load_locale() {\n\t\t// Here we manually fudge the plugin locale as WP doesnt allow many options\n\t\t$locale = get_locale();\n\t\tif( empty( $locale ) )\n\t\t\t$locale = 'en_US';\n\n\t\t$mofile = $this->dir( \"/locale/$locale.mo\" );\n\t\tload_textdomain( $this->name, $mofile );\n\t}\n\t\n\t/**\n\t * Register a WordPress action and map it back to the calling object\n\t *\n\t * @param string $action Name of the action\n\t * @param string $function Function name (optional)\n\t * @param int $priority WordPress priority (optional)\n\t * @param int $accepted_args Number of arguments the function accepts (optional)\n\t * @return void\n\t * @author © John Godley\n\t **/\n\tfunction add_action ($action, $function = '', $priority = 10, $accepted_args = 1) {\n\t\tif ( $priority === null )\n\t\t\t$priority = 10;\n\t\tadd_action ($action, array ($this, $function == '' ? $action : $function), $priority, $accepted_args);\n\t}\n\n\n\t/**\n\t * Register a WordPress filter and map it back to the calling object\n\t *\n\t * @param string $action Name of the action\n\t * @param string $function Function name (optional)\n\t * @param int $priority WordPress priority (optional)\n\t * @param int $accepted_args Number of arguments the function accepts (optional)\n\t * @return void\n\t * @author © John Godley\n\t **/\n\tfunction add_filter ($filter, $function = '', $priority = 10, $accepted_args = 1) {\n\t\tadd_filter ($filter, array ($this, $function == '' ? $filter : $function), $priority, $accepted_args);\n\t}\n\n\n\t/**\n\t * De-register a WordPress filter and map it back to the calling object\n\t *\n\t * @param string $action Name of the action\n\t * @param string $function Function name (optional)\n\t * @param int $priority WordPress priority (optional)\n\t * @param int $accepted_args Number of arguments the function accepts (optional)\n\t * @return void\n\t * @author © John Godley\n\t **/\n\tfunction remove_filter ($filter, $function = '', $priority = 10, $accepted_args = 1) {\n\t\tremove_filter ($filter, array ($this, $function == '' ? $filter : $function), $priority, $accepted_args);\n\t}\n\n\n\t/**\n\t * Special activation function that takes into account the plugin directory\n\t *\n\t * @param string $pluginfile The plugin file location (i.e. __FILE__)\n\t * @param string $function Optional function name, or default to 'activate'\n\t * @return void\n\t * @author © John Godley\n\t **/\n\tfunction register_activation ( $pluginfile = __FILE__, $function = '' ) {\n\t\tif ( $this->type == 'plugin' ) {\n\t\t\tadd_action ('activate_'.basename (dirname ($pluginfile)).'/'.basename ($pluginfile), array ($this, $function == '' ? 'activate' : $function));\n\t\t} elseif ( $this->type == 'theme' ) {\n\t\t\t$this->theme_activation_function = ( $function ) ? $function : 'activate';\n\t\t\tadd_action ('load-themes.php', array ( $this, 'theme_activation' ) );\n\t\t}\n\t}\n\t\n\t/**\n\t * Hack to catch theme activation. We hook the load-themes.php action, look for the\n\t * \"activated\" GET param and make a big fat assumption if we find it.\n\t * \n\t * @return void\n\t * @author Simon Wheatley\n\t **/\n\tpublic function theme_activation() {\n\t\t$activated = (bool) @ $_GET[ 'activated' ];\n\t\tif ( ! $activated )\n\t\t\treturn;\n\t\tif ( ! $this->theme_activation_function )\n\t\t\treturn;\n\t\t// Looks like the theme might just have been activated, call the registered function\n\t\t$this->{$this->theme_activation_function}();\n\t}\n\t\n\t/**\n\t * Special deactivation function that takes into account the plugin directory\n\t *\n\t * @param string $pluginfile The plugin file location (i.e. __FILE__)\n\t * @param string $function Optional function name, or default to 'deactivate'\n\t * @return void\n\t * @author © John Godley\n\t **/\n\tfunction register_deactivation ($pluginfile, $function = '') {\n\t\tadd_action ('deactivate_'.basename (dirname ($pluginfile)).'/'.basename ($pluginfile), array ($this, $function == '' ? 'deactivate' : $function));\n\t}\n\n\t/**\n\t * Renders a template, looking first for the template file in the theme directory\n\t * and afterwards in this plugin's /theme/ directory.\n\t *\n\t * @return void\n\t * @author Simon Wheatley\n\t **/\n\tprotected function render( $template_file, $vars = null ) {\n\t\t// Maybe override the template with our own file\n\t\t$template_file = $this->locate_template( $template_file );\n\t\t// Ensure we have the same vars as regular WP templates\n\t\tglobal $posts, $post, $wp_did_header, $wp_did_template_redirect, $wp_query, $wp_rewrite, $wpdb, $wp_version, $wp, $id, $comment, $user_ID;\n\n\t\tif ( is_array($wp_query->query_vars) )\n\t\t\textract($wp_query->query_vars, EXTR_SKIP);\n\n\t\t// Plus our specific template vars\n\t\tif ( is_array( $vars ) )\n\t\t\textract( $vars );\n\t\t\n\t\trequire( $template_file );\n\t}\n\n\t/**\n\t * Renders an admin template from this plugin's /templates-admin/ directory.\n\t *\n\t * @return void\n\t * @author Simon Wheatley\n\t **/\n\tprotected function render_admin( $template_file, $vars = null ) {\n\t\t// Plus our specific template vars\n\t\tif ( is_array( $vars ) )\n\t\t\textract( $vars );\n\n\t\t// Try to render\n\t\tif ( file_exists( $this->dir( \"templates-admin/$template_file\" ) ) ) {\n\t\t\trequire( $this->dir( \"templates-admin/$template_file\" ) );\n\t\t} else {\n\t\t\t$msg = sprintf( __( \"This plugin admin template could not be found: %s\" ), $this->dir( \"templates-admin/$template_file\" ) );\n\t\t\terror_log( \"Plugin template error: $msg\" );\n\t\t\techo \"<p style='background-color: #ffa; border: 1px solid red; color: #300; padding: 10px;'>$msg</p>\";\n\t\t}\n\t}\n\t\n\t/**\n\t * Returns a section of user display code, returning the rendered markup.\n\t *\n\t * @param string $ug_name Name of the admin file (without extension)\n\t * @param string $array Array of variable name=>value that is available to the display code (optional)\n\t * @return void\n\t * @author © John Godley\n\t **/\n\tprotected function capture( $template_file, $vars = null ) {\n\t\tob_start();\n\t\t$this->render( $template_file, $vars );\n\t\t$output = ob_get_contents();\n\t\tob_end_clean();\n\t\treturn $output;\n\t}\n\t\n\t/**\n\t * Returns a section of user display code, returning the rendered markup.\n\t *\n\t * @param string $ug_name Name of the admin file (without extension)\n\t * @param string $array Array of variable name=>value that is available to the display code (optional)\n\t * @return void\n\t * @author © John Godley\n\t **/\n\tprotected function capture_admin( $template_file, $vars = null ) {\n\t\tob_start();\n\t\t$this->render_admin( $template_file, $vars );\n\t\t$output = ob_get_contents();\n\t\tob_end_clean();\n\t\treturn $output;\n\t}\n\t\n\t/**\n\t * Hooks the WP admin_notices action to render any notices\n\t * that have been set with the set_admin_notice method.\n\t *\n\t * @return void\n\t * @author Simon Wheatley\n\t **/\n\tpublic function _admin_notices() {\n\t\t$notices = $this->get_option( 'admin_notices' );\n\t\t$errors = $this->get_option( 'admin_errors' );\n\t\tif ( $errors ) {\n\t\t\tforeach ( $errors as $error ) {\n\t\t\t\t$this->render_admin_error( $error );\n\t\t\t\t$this->delete_option( 'admin_errors' );\n\t\t\t}\n\t\t}\n\t\tif ( $notices ) {\n\t\t\tforeach ( $notices as $notice ) {\n\t\t\t\t$this->render_admin_notice( $notice );\n\t\t\t\t$this->delete_option( 'admin_notices' );\n\t\t\t}\n\t\t}\n\t}\n\t\n\t/**\n\t * Echoes some HTML for an admin notice.\n\t *\n\t * @param string $notice The notice \n\t * @return void\n\t * @author Simon Wheatley\n\t **/\n\tprotected function render_admin_notice( $notice ) {\n\t\techo \"<div class='updated'><p>$notice</p></div>\";\n\t}\n\t\n\t/**\n\t * Echoes some HTML for an admin error.\n\t *\n\t * @param string $error The error \n\t * @return void\n\t * @author Simon Wheatley\n\t **/\n\tprotected function render_admin_error( $error ) {\n\t\techo \"<div class='error'><p>$error</p></div>\";\n\t}\n\t\n\t/**\n\t * Sets a string as an admin notice.\n\t *\n\t * @param string $msg A *localised* admin notice message \n\t * @return void\n\t * @author Simon Wheatley\n\t **/\n\tprotected function set_admin_notice( $msg ) {\n\t\t$notices = (array) $this->get_option( 'admin_notices' );\n\t\t$notices[] = $msg;\n\t\t$this->update_option( 'admin_notices', $notices );\n\t}\n\t\n\t/**\n\t * Sets a string as an admin error.\n\t *\n\t * @param string $msg A *localised* admin error message \n\t * @return void\n\t * @author Simon Wheatley\n\t **/\n\tprotected function set_admin_error( $msg ) {\n\t\t$errors = (array) $this->get_option( 'admin_errors' );\n\t\t$errors[] = $msg;\n\t\t$this->update_option( 'admin_errors', $errors );\n\t}\n\t\n\t/**\n\t * Takes a filename and attempts to find that in the designated plugin templates\n\t * folder in the theme (defaults to main theme directory, but uses a custom filter\n\t * to allow theme devs to specify a sub-folder for all plugin template files using\n\t * this system).\n\t * \n\t * Searches in the STYLESHEETPATH before TEMPLATEPATH to cope with themes which\n\t * inherit from a parent theme by just overloading one file.\n\t *\n\t * @param string $template_file A template filename to search for \n\t * @return string The path to the template file to use\n\t * @author Simon Wheatley\n\t **/\n\tprotected function locate_template( $template_file ) {\n\t\t$located = '';\n\t\t$sub_dir = apply_filters( 'sw_plugin_tpl_dir', '' );\n\t\tif ( $sub_dir )\n\t\t\t$sub_dir = trailingslashit( $sub_dir );\n\t\t// If there's a tpl in a (child theme or theme with no child)\n\t\tif ( file_exists( STYLESHEETPATH . \"/$sub_dir\" . $template_file ) )\n\t\t\treturn STYLESHEETPATH . \"/$sub_dir\" . $template_file;\n\t\t// If there's a tpl in the parent of the current child theme\n\t\telse if ( file_exists( TEMPLATEPATH . \"/$sub_dir\" . $template_file ) )\n\t\t\treturn TEMPLATEPATH . \"/$sub_dir\" . $template_file;\n\t\t// Fall back on the bundled plugin template (N.B. no filtered subfolder involved)\n\t\telse if ( file_exists( $this->dir( \"templates/$template_file\" ) ) )\n\t\t\treturn $this->dir( \"templates/$template_file\" );\n\t\t// Oh dear. We can't find the template.\n\t\t$msg = sprintf( __( \"This plugin template could not be found, perhaps you need to hook `sil_plugins_dir` and `sil_plugins_url`: %s\" ), $this->dir( \"templates/$template_file\" ) );\n\t\terror_log( \"Template error: $msg\" );\n\t\techo \"<p style='background-color: #ffa; border: 1px solid red; color: #300; padding: 10px;'>$msg</p>\";\n\t}\n\t\n\t/**\n\t * Register a WordPress meta box\n\t *\n\t * @param string $id ID for the box, also used as a function name if none is given\n\t * @param string $title Title for the box\n\t * @param int $page The type of edit page on which to show the box (post, page, link).\n\t * @param string $function Function name (optional)\n\t * @param string $context e.g. 'advanced' or 'core' (optional)\n\t * @param int $priority Priority, rough effect on the ordering (optional)\n\t * @param mixed $args Some arguments to pass to the callback function as part of a larger object (optional)\n\t * @return void\n\t * @author © John Godley\n\t **/\n\tfunction add_meta_box( $id, $title, $function = '', $page, $context = 'advanced', $priority = 'default', $args = null )\n\t{\n\t\trequire_once( ABSPATH . 'wp-admin/includes/template.php' );\n\t\tadd_meta_box( $id, $title, array( $this, $function == '' ? $id : $function ), $page, $context, $priority, $args );\n\t}\n\n\t/**\n\t * Add hook for shortcode tag.\n\t *\n\t * There can only be one hook for each shortcode. Which means that if another\n\t * plugin has a similar shortcode, it will override yours or yours will override\n\t * theirs depending on which order the plugins are included and/or ran.\n\t *\n\t * @param string $tag Shortcode tag to be searched in post content.\n\t * @param callable $func Hook to run when shortcode is found.\n\t */\n\tprotected function add_shortcode( $tag, $function = null ) {\n\t\tadd_shortcode( $tag, array( $this, $function == '' ? $tag : $function ) );\n\t}\n\t\n\t/**\n\t * Returns the filesystem path for a file/dir within this plugin.\n\t *\n\t * @param $path string The path within this plugin, e.g. '/js/clever-fx.js'\n\t * @return string Filesystem path\n\t * @author Simon Wheatley\n\t **/\n\tprotected function dir( $path ) {\n\t\treturn trailingslashit( $this->dir ) . trim( $path, '/' );\n\t}\n\n\t/**\n\t * Returns the URL for for a file/dir within this plugin.\n\t *\n\t * @param $path string The path within this plugin, e.g. '/js/clever-fx.js'\n\t * @return string URL\n\t * @author Simon Wheatley\n\t **/\n\tprotected function url( $path ) {\n\t\treturn esc_url( trailingslashit( $this->url ) . trim( $path, '/' ) );\n\t}\n\t\n\t/**\n\t * Gets the value of an option named as per this plugin.\n\t *\n\t * @return mixed Whatever \n\t * @author Simon Wheatley\n\t **/\n\tprotected function get_all_options() {\n\t\treturn get_option( $this->name );\n\t}\n\t\n\t/**\n\t * Sets the value of an option named as per this plugin.\n\t *\n\t * @return mixed Whatever \n\t * @author Simon Wheatley\n\t **/\n\tprotected function update_all_options( $value ) {\n\t\treturn update_option( $this->name, $value );\n\t}\n\t\n\t/**\n\t * Gets the value from an array index on an option named as per this plugin.\n\t *\n\t * @param string $key A string \n\t * @return mixed Whatever \n\t * @author Simon Wheatley\n\t **/\n\tpublic function get_option( $key, $value = null ) {\n\t\t$option = get_option( $this->name );\n\t\tif ( ! is_array( $option ) || ! isset( $option[ $key ] ) )\n\t\t\treturn $value;\n\t\treturn $option[ $key ];\n\t}\n\t\n\t/**\n\t * Sets the value on an array index on an option named as per this plugin.\n\t *\n\t * @param string $key A string \n\t * @param mixed $value Whatever\n\t * @return bool False if option was not updated and true if option was updated.\n\t * @author Simon Wheatley\n\t **/\n\tprotected function update_option( $key, $value ) {\n\t\t$option = get_option( $this->name );\n\t\t$option[ $key ] = $value;\n\t\treturn update_option( $this->name, $option );\n\t}\n\t\n\t/**\n\t * Deletes the array index on an option named as per this plugin.\n\t *\n\t * @param string $key A string \n\t * @return bool False if option was not updated and true if option was updated.\n\t * @author Simon Wheatley\n\t **/\n\tprotected function delete_option( $key ) {\n\t\t$option = get_option( $this->name );\n\t\tif ( isset( $option[ $key ] ) )\n\t\t\tunset( $option[ $key ] );\n\t\treturn update_option( $this->name, $option );\n\t}\n\t\n\t/**\n\t * Echoes out some JSON indicating that stuff has gone wrong.\n\t *\n\t * @param string $msg The error message \n\t * @return void\n\t * @author Simon Wheatley\n\t **/\n\tprotected function ajax_die( $msg ) {\n\t\t$data = array( 'msg' => $msg, 'success' => false );\n\t\techo json_encode( $data );\n\t\t// N.B. No 500 header\n\t\texit;\n\t}\n\t\n\t/**\n\t * Truncates a string in a human friendly way.\n\t *\n\t * @param string $str The string to truncate \n\t * @param int $num_words The number of words to truncate to\n\t * @return string The truncated string\n\t * @author Simon Wheatley\n\t **/\n\tprotected function truncate( $str, $num_words )\n\t{\n\t\t$str = strip_tags( $str );\n\t\t$words = explode(' ', $str );\n\t\tif ( count( $words ) > $num_words) {\n\t\t\t$k = $num_words;\n\t\t\t$use_dotdotdot = 1;\n\t\t} else {\n\t\t\t$k = count( $words );\n\t\t\t$use_dotdotdot = 0;\n\t\t}\n\t\t$words  = array_slice( $words, 0, $k );\n\t\t$excerpt = trim( join( ' ', $words ) );\n\t\t$excerpt .= ($use_dotdotdot) ? '…' : '';\n\t\treturn $excerpt;\n\t}\n\t\n\n} // END Babble_Plugin class \n\n?>"
  },
  {
    "path": "class-post-public.php",
    "content": "<?php\n\n/**\n * Class for handling the public, content handling post types.\n *\n * @package Babble\n * @since 0.1\n */\nclass Babble_Post_Public extends Babble_Plugin {\n\n\t/**\n\t * A simple flag to stop infinite recursion when syncing\n\t * post meta places.\n\t *\n\t * @var boolean\n\t **/\n\tprotected $no_meta_recursion;\n\n\t/**\n\t * A simple flag to stop infinite recursion in various\n\t * places (except for post meta).\n\t *\n\t * @var boolean\n\t **/\n\tprotected $no_recursion;\n\n\t/**\n\t * The shadow (translated) post types created by this plugin.\n\t *\n\t * @var array\n\t **/\n\tprotected $post_types;\n\n\t/**\n\t * A structure describing the languages served by various post types.\n\t *\n\t * @var array\n\t **/\n\tprotected $lang_map;\n\n\t/**\n\t * Another structure describing the languages served by various post types.\n\t *\n\t * @var array\n\t **/\n\tprotected $lang_map2;\n\n\t/**\n\t * A version number to use for cache busting, database updates, etc\n\t *\n\t * @var int\n\t **/\n\tprotected $version;\n\n\t/**\n\t * An array of query_vars and slugs for our shadow post types,\n\t * we use changes to this to determine if rewrite rules\n\t * need flushing.\n\t *\n\t * @var array\n\t **/\n\tprotected $slugs_and_vars;\n\n\n\t/**\n\t * An array of Post IDs for posts that are in the process of\n\t * being deleted.\n\t *\n\t * @var array\n\t **/\n\tprotected $deleting_post_ids;\n\n\t/**\n\t * An array of meta_keys, indexed by meta_key, containing\n\t * meta_keys we KNOW to be added as unique.\n\t *\n\t * @var array\n\t **/\n\tprotected $unique_meta_keys;\n\n\tpublic function __construct() {\n\t\t$this->setup( 'babble-post-public', 'plugin' );\n\n\t\t$this->add_action( 'added_post_meta', null, null, 4 );\n\t\t$this->add_action( 'admin_init' );\n\t\t$this->add_action( 'clean_post_cache' );\n\t\t$this->add_action( 'body_class', null, null, 2 );\n\t\t$this->add_action( 'before_delete_post', 'clean_post_cache' );\n\t\t$this->add_action( 'deleted_post', 'clean_post_cache' );\n\t\t$this->add_action( 'deleted_post_meta', null, null, 4 );\n\t\t$this->add_action( 'load-post-new.php', 'load_post_new' );\n\t\t$this->add_action( 'manage_pages_custom_column', 'manage_posts_custom_column', null, 2 );\n\t\t$this->add_action( 'manage_posts_custom_column', 'manage_posts_custom_column', null, 2 );\n\t\t$this->add_action( 'parse_request' );\n\t\t$this->add_action( 'post_updated' );\n\t\t$this->add_action( 'pre_get_posts', null, 11 );\n\t\t$this->add_action( 'registered_post_type', null, null, 2 );\n\t\t$this->add_action( 'transition_post_status', null, null, 3 );\n\t\t$this->add_action( 'updated_post_meta', null, null, 4 );\n\t\t$this->add_action( 'wp_before_admin_bar_render' );\n\t\t$this->add_filter( 'add_menu_classes' );\n\t\t$this->add_filter( 'add_post_metadata', null, null, 5 );\n\t\t$this->add_filter( 'bbl_sync_meta_key', 'sync_meta_key', null, 2 );\n\t\t$this->add_filter( 'manage_posts_columns', 'manage_posts_columns', null, 2 );\n\t\t$this->add_filter( 'page_link', null, null, 2 );\n\t\t$this->add_filter( 'post_link', 'post_type_link', null, 3 );\n\t\t$this->add_filter( 'post_type_archive_link', null, null, 2 );\n\t\t$this->add_filter( 'post_type_link', null, null, 3 );\n\t\t$this->add_filter( 'get_sample_permalink', null, null, 5 );\n\t\t$this->add_filter( 'single_template' );\n\t\t$this->add_filter( 'the_posts', null, null, 2 );\n\t\t$this->add_filter( 'bbl_translated_taxonomy', null, null, 2 );\n\t\t$this->add_filter( 'admin_body_class' );\n\n\t\t$this->initiate();\n\t}\n\t/**\n\t * Initiates\n\t *\n\t * @return void\n\t **/\n\tpublic function initiate() {\n\t\t$this->lang_map = array();\n\t\t$this->post_types = array();\n\t\t$this->slugs_and_vars = array();\n\t\t$this->no_meta_recursion = false;\n\t\t$this->deleting_post_ids = array();\n\n\t\t$this->version = 9;\n\n\t\t// Ensure we catch any existing language shadow post_types already registered\n\t\t$core_post_types = array( 'post', 'page', 'attachment' );\n\t\tif ( is_array( $this->post_types ) )\n\t\t\t$post_types = array_merge( $core_post_types, array_keys( $this->post_types ) );\n\t\telse\n\t\t\t$post_types = $core_post_types;\n\n\t\tregister_taxonomy( 'post_translation', $post_types, array(\n\t\t\t'rewrite' => false,\n\t\t\t'public' => false,\n\t\t\t'show_ui' => false,\n\t\t\t'show_in_nav_menus' => false,\n\t\t) );\n\t}\n\n\t/**\n\t * Hooks the WP admin_init action to\n\t *\n\t * @return void\n\t **/\n\tpublic function admin_init() {\n\t\t$this->maybe_upgrade();\n\t\t$post_type = false;\n\t\tif ( isset( $_GET[ 'post_type' ] ) ) {\n\t\t\t$post_type = $_GET[ 'post_type' ];\n\t\t} else if ( isset( $_GET[ 'post' ] ) ) {\n\t\t\t$post = (int) $_GET[ 'post' ];\n\t\t\t$post = get_post( $post );\n\t\t\t$post_type = $post->post_type;\n\t\t}\n\t\t$menu_id = false;\n\t\tif ( isset( $this->post_types[ $post_type ] ) )\n\t\t\t$menu_id = '#menu-posts-' . $this->post_types[ $post_type ];\n\n\t\t$data = array(\n\t\t\t'menu_id' => $menu_id,\n\t\t\t'is_default_lang' => (bool) ( bbl_get_current_lang_code() == bbl_get_default_lang_code() ),\n\t\t\t'is_bbl_post_type' => (bool) ( 0 === strpos( $post_type, 'bbl_' ) ),\n\t\t);\n\t\twp_enqueue_script( 'post-public-admin', $this->url( 'js/post-public-admin.js' ), array( 'jquery' ), filemtime( $this->dir( 'js/post-public-admin.js' ) ) );\n\t\twp_localize_script( 'post-public-admin', 'bbl_post_public', $data );\n\t}\n\n\t/**\n\t * Initialise a translation for the given post.\n\t *\n\t * @param  WP_Post|int $origin_post The origin post object or post ID\n\t * @param  string      $lang_code   The language code for the new translation\n\t * @return WP_Post The translation post\n\t */\n\tpublic function initialise_translation( $origin_post, $lang_code ) {\n\n\t\t$origin_post   = get_post( $origin_post );\n\t\t$new_post_type = $this->get_post_type_in_lang( $origin_post->post_type, $lang_code );\n\t\t$transid       = $this->get_transid( $origin_post->ID );\n\n\t\t// Insert translation:\n\t\t$this->no_recursion = true;\n\t\t$new_post_id = wp_insert_post( array(\n\t\t\t'post_type'   => $new_post_type,\n\t\t\t'post_status' => 'draft',\n\t\t), true );\n\t\t$this->no_recursion = false;\n\n\t\t$new_post = get_post( $new_post_id );\n\n\t\t// Assign transid to translation:\n\t\t$this->set_transid( $new_post, $transid );\n\n\t\t// Copy all the metadata across\n\t\t$this->sync_post_meta( $new_post->ID );\n\n\t\t// Copy the various core post properties across\n\t\t$this->sync_properties( $origin_post->ID, $new_post->ID );\n\n\t\tdo_action( 'bbl_created_new_shadow_post', $new_post->ID, $origin_post->ID );\n\n\t\treturn $new_post;\n\n\t}\n\n\t/**\n\t * Hooks the WP load-post-new.php action to stop translators\n\t * creating new posts in languages other than the default.\n\t *\n\t * @return void\n\t **/\n\tpublic function load_post_new() {\n\t\t$screen = get_current_screen();\n\t\tif ( 'post' != $screen->base || 'add' != $screen->action )\n\t\t\treturn;\n\t\tif ( bbl_get_current_lang_code() == bbl_get_default_lang_code() )\n\t\t\treturn;\n\t\tif ( !bbl_is_translated_post_type( $screen->post_type ) )\n\t\t\treturn;\n\n\t\twp_die( __( 'You can only create content in your site\\'s default language. Please consult your editorial team.', 'babble' ), '', array( 'back_link' => true ) );\n\t}\n\n\t/**\n\t * Hooks the WP wp_before_admin_bar_render action\n\t * to prune out unneeded post type add controls from\n\t * the add menu.\n\t *\n\t * @return void\n\t **/\n\tpublic function wp_before_admin_bar_render() {\n\t\tglobal $wp_admin_bar;\n\t\tif ( ! bbl_is_default_lang() )\n\t\t\t$wp_admin_bar->remove_node( 'new-content' );\n\t}\n\n\t/**\n\t * Hooks the WP registered_post_type action.\n\t *\n\t * @param string $post_type The post type which has just been registered.\n\t * @param object $args The arguments with which the post type was registered\n\t * @return void\n\t **/\n\tpublic function registered_post_type( $post_type, $args ) {\n\t\t// Don't bother with non-public post_types for now\n\t\t// @FIXME: This may need to change for menus?\n\t\tif ( false === $args->public )\n\t\t\treturn;\n\n\t\t// Don't shadow shadow post types, it's going to get silly\n\t\tif ( in_array( $post_type, $this->post_types ) )\n\t\t\treturn;\n\n\t\tif ( $this->no_recursion )\n\t\t\treturn;\n\n\t\t$this->no_recursion = 'registered_post_type';\n\n\t\t$langs = bbl_get_active_langs();\n\n\t\t// Lose the default language as any existing post types are in that language\n\t\tunset( $langs[ bbl_get_default_lang_url_prefix() ] );\n\n\t\t// $args is an object at this point, but register_post_type needs an array\n\t\t$args = get_object_vars( $args );\n\t\t// @FIXME: Is it reckless to convert ALL object instances in $args to an array?\n\t\tforeach ( $args as $key => & $arg ) {\n\t\t\tif ( is_object( $arg ) )\n\t\t\t\t$arg = get_object_vars( $arg );\n\t\t\t// Don't set any args reserved for built-in post_types\n\t\t\tif ( '_' == substr( $key, 0, 1 ) )\n\t\t\t\tunset( $args[ $key ] );\n\t\t}\n\n\t\t$features = $this->get_features_supported_by_post_type( $post_type );\n\t\t$args[ 'supports' ] = array();\n\t\tforeach ( $features as $feature => $true )\n\t\t\t$args[ 'supports' ][] = $feature;\n\n\t\t// I am a little concerned that this argument may make things\n\t\t// brittle, e.g. the UI might stop showing up in the shadow\n\t\t// post type edit screens, p'raps.\n\t\t$args[ 'show_ui' ] = true;\n\n\t\t$slug = ( $args[ 'rewrite' ][ 'slug' ] ) ? $args[ 'rewrite' ][ 'slug' ] : $post_type;\n\t\t$archive_slug = false;\n\t\tif ( $archive_slug = $args[ 'has_archive' ] )\n\t\t\tif ( ! is_string( $args[ 'has_archive' ] ) )\n\t\t\t\t$archive_slug = $slug;\n\n\t\t$current_lang_code = bbl_get_current_lang_code();\n\n\t\tforeach ( $langs as $lang ) {\n\t\t\t$new_args = $args;\n\n\n\t\t\t// @FIXME: We are in danger of a post_type name being longer than 20 chars\n\t\t\t// I would prefer to keep the post_type human readable, as human devs and sysadmins always\n\t\t\t// end up needing to read this kind of thing.\n\t\t\t// @FIXME: Should I be sanitising these values?\n\t\t\t$new_post_type = strtolower( \"{$post_type}_{$lang->code}\" );\n\n\t\t\tif ( strlen( $new_post_type ) > 20 ) {\n\t\t\t\ttrigger_error( sprintf( __( 'Warning: The translated name for the post type %s is longer than %d characters. This *will* cause problems.', 'babble' ),\n\t\t\t\t\tesc_html( $post_type ),\n\t\t\t\t\t20\n\t\t\t\t) );\n\t\t\t}\n\n\t\t\tif ( false !== $args[ 'rewrite' ] ) {\n\t\t\t\tif ( ! is_array( $new_args[ 'rewrite' ] ) )\n\t\t\t\t\t$new_args[ 'rewrite' ] = array();\n\t\t\t\t$new_args[ 'query_var' ] = $new_args[ 'rewrite' ][ 'slug' ] = $this->get_slug_in_lang( $slug, $lang, $args );\n\t\t\t\t$new_args[ 'has_archive' ] = $this->get_slug_in_lang( $archive_slug, $lang );\n\t\t\t}\n\t\t\t$this->slugs_and_vars[ $lang->code . '_' . $post_type ] = array(\n\t\t\t\t'query_var' => $new_args[ 'query_var' ],\n\t\t\t\t'has_archive' => $new_args[ 'has_archive' ],\n\t\t\t);\n\n\t\t\t// Don't let the translated post types show up in the search if their\n\t\t\t// language is not the current language.\n\t\t\tif ( $lang->code != $current_lang_code ) {\n\t\t\t\t$new_args['exclude_from_search'] = true;\n\t\t\t\t$new_args['capabilities']['create_posts'] = 'do_not_allow';\n\t\t\t}\n\n\t\t\t$result = register_post_type( $new_post_type, $new_args );\n\t\t\tif ( is_wp_error( $result ) ) {\n\t\t\t\terror_log( \"Error creating shadow post_type for $new_post_type: \" . print_r( $result, true ) );\n\t\t\t} else {\n\t\t\t\t$this->post_types[ $new_post_type ] = $post_type;\n\t\t\t\t$this->lang_map[ $new_post_type ] = $lang->code;\n\n\t\t\t\t// @TODO: Refactor the $this::lang_map array so we can use this new structure instead\n\t\t\t\tif ( ! isset( $this->lang_map2[ $lang->code ] ) || ! is_array( $this->lang_map2[ $lang->code ] ) )\n\t\t\t\t\t$this->lang_map2[ $lang->code ] = array();\n\t\t\t\t$this->lang_map2[ $lang->code ][ $post_type ] = $new_post_type;\n\n\t\t\t\t// This will not work until init has run at the early priority used\n\t\t\t\t// to register the post_translation taxonomy. However we catch all the\n\t\t\t\t// post_types registered before the hook runs, so we don't miss any\n\t\t\t\t// (take a look at where we register post_translation for more info).\n\t\t\t\tregister_taxonomy_for_object_type( 'post_translation', $new_post_type );\n\t\t\t}\n\t\t}\n\n\t\t// Exclude the registered post type from search if it's language isn't\n\t\t// the current language.\n\t\tif ( $current_lang_code != bbl_get_default_lang_code() ) {\n\t\t\t$post_type_obj = get_post_type_object( $post_type );\n\t\t\t$post_type_obj->exclude_from_search = true;\n\t\t}\n\n\t\tdo_action( 'bbl_registered_shadow_post_types', $post_type );\n\n\t\t$this->no_recursion = false;\n\t}\n\n\t/**\n\t * Store whether a particular meta_key is unique or not. Pretty hacky.\n\t *\n\t * @param null $null Follows a pattern for actions/filters relating to meta, but meta ID not set yet so null\n\t * @param int $post_id The ID for the WordPress Post object this meta relates to\n\t * @param string $meta_key The key for this meta entry\n\t * @param mixed $meta_value The new value for this meta entry\n\t * @param bool $unique Whether the meta_key should be unique\n\t * @return null Always return null, or we are bypassing the meta save to DB\n\t **/\n\tpublic function add_post_metadata( $null, $post_id, $meta_key, $meta_value, $unique ) {\n\t\tif ( $unique ) {\n\t\t\t$this->unique_meta_keys[ $meta_key ] = $meta_key;\n\t\t}\n\t\treturn null;\n\t}\n\n\t/**\n\t * Hooks the WP added_post_meta action to sync metadata across to the\n\t * translations in shadow post types.\n\t *\n\t * @param int $meta_id The ID for this meta entry\n\t * @param int $post_id The ID for the WordPress Post object this meta relates to\n\t * @param string $meta_key The key for this meta entry\n\t * @param mixed $meta_value The new value for this meta entry\n\t * @return void\n\t **/\n\tpublic function added_post_meta( $meta_id, $post_id, $meta_key, $meta_value ) {\n\t\t// Some metadata shouldn't be synced\n\t\tif ( ! apply_filters( 'bbl_sync_meta_key', true, $meta_key ) )\n\t\t\treturn;\n\n\t\tif ( $this->no_meta_recursion )\n\t\t\treturn;\n\t\t$this->no_meta_recursion = 'added_post_meta';\n\n\t\t$unique = isset( $this->unique_meta_keys[ $meta_key ] );\n\n\t\t$translations = $this->get_post_translations( $post_id );\n\t\tforeach ( $translations as $lang_code => & $translation ) {\n\t\t\tif ( $this->get_post_lang_code( $post_id ) == $lang_code )\n\t\t\t\tcontinue;\n\t\t\tadd_post_meta( $translation->ID, $meta_key, $meta_value, $unique );\n\t\t}\n\n\t\t$this->no_meta_recursion = false;\n\t}\n\n\t/**\n\t * Hooks the WP updated_post_meta action to sync metadata across to the\n\t * translations in shadow post types.\n\t *\n\t * @param int $meta_id The ID for this meta entry\n\t * @param int $post_id The ID for the WordPress Post object this meta relates to\n\t * @param string $meta_key The key for this meta entry\n\t * @param mixed $meta_value The new value for this meta entry\n\t * @return void\n\t **/\n\tpublic function updated_post_meta( $meta_id, $post_id, $meta_key, $meta_value ) {\n\t\t// Some metadata shouldn't be synced\n\t\tif ( ! apply_filters( 'bbl_sync_meta_key', true, $meta_key ) )\n\t\t\treturn;\n\n\t\tif ( $this->no_meta_recursion )\n\t\t\treturn;\n\t\t$this->no_meta_recursion = 'updated_post_meta';\n\n\t\t$translations = $this->get_post_translations( $post_id );\n\t\tforeach ( $translations as $lang_code => & $translation ) {\n\t\t\tif ( $this->get_post_lang_code( $post_id ) == $lang_code )\n\t\t\t\tcontinue;\n\t\t\tupdate_post_meta( $translation->ID, $meta_key, $meta_value );\n\t\t}\n\n\t\t$this->no_meta_recursion = false;\n\t}\n\n\t/**\n\t * Hooks the WP deleted_post_meta action to sync metadata across to the\n\t * translations in shadow post types.\n\t *\n\t * @param int $meta_id The ID for this meta entry\n\t * @param int $post_id The ID for the WordPress Post object this meta relates to\n\t * @param string $meta_key The key for this meta entry\n\t * @param mixed $meta_value The new value for this meta entry\n\t * @return void\n\t **/\n\tpublic function deleted_post_meta( $meta_id, $post_id, $meta_key, $meta_value ) {\n\n\t\t// When we are deleting posts, we don't want to sync\n\t\t// the metadata deletion across the other posts in\n\t\t// the same translation group\n\t\tif ( in_array( $post_id, $this->deleting_post_ids ) )\n\t\t\treturn;\n\n\t\t// Some metadata shouldn't be synced\n\t\tif ( ! apply_filters( 'bbl_sync_meta_key', true, $meta_key ) )\n\t\t\treturn;\n\n\t\tif ( $this->no_meta_recursion )\n\t\t\treturn;\n\t\t$this->no_meta_recursion = 'deleted_post_meta';\n\n\t\t$translations = $this->get_post_translations( $post_id );\n\t\tforeach ( $translations as $lang_code => & $translation ) {\n\t\t\tif ( $this->get_post_lang_code( $post_id ) == $lang_code )\n\t\t\t\tcontinue;\n\t\t\tdelete_post_meta( $translation->ID, $meta_key );\n\t\t}\n\n\t\t$this->no_meta_recursion = false;\n\t}\n\n\t/**\n\t * Hooks the WP pre_get_posts ref action in the WP_Query,\n\t * for the main query it does nothing, for other queries\n\t * it switches the post types to our shadow post types.\n\t *\n\t * @param WP_Query $wp_query A WP_Query object, passed by reference\n\t * @return void (param passed by reference)\n\t **/\n\tpublic function pre_get_posts( WP_Query & $query ) {\n\t\tif ( false === $query->get( 'bbl_translate' ) ) {\n\t\t\treturn;\n\t\t}\n\t\tif ( $query->is_main_query() ) {\n\t\t\treturn;\n\t\t}\n\t\t# @TODO we should scrap this and more intelligently filter the QVs rather than basing it on whether we're on a media tab\n\t\tif ( $this->is_media_upload_tab( 'gallery' ) ) {\n\t\t\treturn;\n\t\t}\n\t\tif ( $this->is_media_manager() ) {\n\t\t\treturn;\n\t\t}\n\n\t\t$query->query_vars = $this->translate_query_vars( $query->query_vars );\n\t}\n\n\t/**\n\t * Hooks the WP parse_request action\n\t *\n\t * FIXME: Should I be extending and replacing the WP class?\n\t *\n\t * @param WP $wp WP object, passed by reference (so no need to return)\n\t * @return void\n\t **/\n\tpublic function parse_request( WP & $wp ) {\n\n\t\tif ( isset( $wp->query_vars['bbl_translate'] ) and ( false === $wp->query_vars['bbl_translate'] ) ) {\n\t\t\treturn;\n\t\t}\n\n\t\tif ( is_admin() ) {\n\t\t\treturn;\n\t\t}\n\n\t\t$wp->query_vars = $this->translate_query_vars( $wp->query_vars, $wp->request );\n\t}\n\n\t/**\n\t * Hooks the WP the_posts filter on WP_Query.\n\t *\n\t * Check the post_title, post_excerpt, post_content and substitute from\n\t * the default language where appropriate.\n\t *\n\t * @param array $posts The posts retrieved by WP_Query, passed by reference\n\t * @param WP_Query $wp_query The WP_Query, passed by reference\n\t * @return array The posts\n\t **/\n\tpublic function the_posts( array $posts, WP_Query & $wp_query ) {\n\t\tif ( is_admin() )\n\t\t\treturn $posts;\n\n\t\t// Get fallback content in the default language\n\t\t$subs_index = array();\n\t\tforeach ( $posts as & $post ) {\n\t\t\tif ( empty( $post->post_title ) || empty( $post->post_excerpt ) || empty( $post->post_content ) ) {\n\t\t\t\tif ( $default_post = bbl_get_default_lang_post( $post->ID ) )\n\t\t\t\t\t$subs_index[ $post->ID ] = $default_post->ID;\n\t\t\t}\n\t\t\tif ( ! $this->get_transid( $post ) && bbl_get_default_lang_code() == bbl_get_post_lang_code( $post ) )\n\t\t\t\t$this->set_transid( $post );\n\t\t}\n\t\tif ( ! $subs_index )\n\t\t\treturn $posts;\n\n\t\t$subs_posts = get_posts( array( 'include' => array_values( $subs_index ), 'post_status' => 'publish' ) );\n\t\t// @FIXME: Check the above get_posts call results are cached somewhere… I think they are\n\t\t// @FIXME: Alternative approach: hook on save_post to save the current value to the translation, BUT content could get out of date – in post_content_filtered\n\t\tforeach ( $posts as & $post ) {\n\t\t\t// @TODO why does this only override the title/excerpt/content? Why not override the post object entirely?\n\t\t\t// @FIXME: I'm assuming this get_post call is cached, which it seems to be\n\t\t\tif( isset( $subs_index[ $post->ID ] ) ) {\n\t\t\t\t$default_post = get_post( $subs_index[ $post->ID ] );\n\t\t\t\tif ( empty( $post->post_title ) )\n\t\t\t\t\t$post->post_title = $default_post->post_title;\n\t\t\t\tif ( empty( $post->post_excerpt ) )\n\t\t\t\t\t$post->post_excerpt = $default_post->post_excerpt;\n\t\t\t\tif ( empty( $post->post_content ) )\n\t\t\t\t\t$post->post_content = $default_post->post_content;\n\t\t\t}\n\t\t}\n\t\treturn $posts;\n\t}\n\n\t/**\n\t * Hooks the WP body_class filter to add classes to the\n\t * body element.\n\t *\n\t * @param array $classes An array of class strings, poss with some indexes containing more than one space separated class\n\t * @param string|array $class One or more classes which have been added to the class list.\n\t * @return array An array of class strings, poss with some indexes containing more than one space separated class\n\t **/\n\tpublic function body_class( array $classes, $class ) {\n\t\t// Shadow post_type archives also get the post_type class for\n\t\t// the default language\n\t\tif ( is_post_type_archive() && ! bbl_is_default_lang() )\n\t\t\t$classes[] = 'post-type-archive-' . bbl_get_post_type_in_lang( get_query_var( 'post_type' ), bbl_get_default_lang_code() );\n\t\tif ( is_single() )\n\t\t\t$classes[] = 'single-' . bbl_get_base_post_type( get_post_type() );\n\t\treturn $classes;\n\t}\n\n\t/**\n\t * Hooks the WP post_type_link filter\n\t *\n\t * @param string $post_link The permalink\n\t * @param object $post The WP Post object being linked to\n\t * @return string The permalink\n\t **/\n\tpublic function post_type_link( $post_link, $post, $leavename ) {\n\t\tglobal $wp_rewrite;\n\n\t\t// Regular ol' post types, and other types added by other plugins, etc\n\t\tif ( 'post' == $post->post_type || 'page' == $post->post_type || ! isset( $this->post_types[ $post->post_type ] ) )\n\t\t\treturn user_trailingslashit( $post_link );\n\n\t\t// Deal with our shadow post types\n\t\tif ( ! ( $base_post_type = $this->get_base_post_type( $post->post_type ) ) )\n\t\t\treturn user_trailingslashit( $post_link );\n\n\t\t// Deal with post_types shadowing the post post_type\n\t\tif ( 'post' == $base_post_type ) {\n\t\t\t// @FIXME: Probably move this into another function\n\t\t\t// @FIXME: Is there any way I can provide an appropriate permastruct so I can avoid having to copy all this code, with the associated maintenance headaches?\n\t\t\t// START copying from get_permalink function\n\t\t\t// N.B. The $permalink var is replaced with $post_link\n\t\t\t$rewritecode = array(\n\t\t\t\t'%year%',\n\t\t\t\t'%monthnum%',\n\t\t\t\t'%day%',\n\t\t\t\t'%hour%',\n\t\t\t\t'%minute%',\n\t\t\t\t'%second%',\n\t\t\t\t'%postname%',\n\t\t\t\t'%post_id%',\n\t\t\t\t'%category%',\n\t\t\t\t'%author%',\n\t\t\t\t'%pagename%',\n\t\t\t);\n\n\t\t\t$post_link = get_option('permalink_structure');\n\n\t\t\t// @FIXME: Should I somehow fake this, so plugin authors who hook it still get some consequence?\n\t\t\t// $post_link = apply_filters('pre_post_link', $post_link, $post, $leavename);\n\n\t\t\tif ( '' != $post_link && ! in_array( $post->post_status, array( 'draft', 'pending', 'auto-draft' ) ) ) {\n\t\t\t\t$unixtime = strtotime($post->post_date);\n\n\t\t\t\t$category = '';\n\t\t\t\tif ( strpos($post_link, '%category%') !== false ) {\n\t\t\t\t\t$cats = get_the_category($post->ID);\n\t\t\t\t\tif ( $cats ) {\n\t\t\t\t\t\tusort($cats, '_usort_terms_by_ID'); // order by ID\n\t\t\t\t\t\t$category = $cats[0]->slug;\n\t\t\t\t\t\tif ( $parent = $cats[0]->parent )\n\t\t\t\t\t\t\t$category = get_category_parents($parent, false, '/', true) . $category;\n\t\t\t\t\t}\n\t\t\t\t\t// show default category in permalinks, without\n\t\t\t\t\t// having to assign it explicitly\n\t\t\t\t\tif ( empty($category) ) {\n\t\t\t\t\t\t$default_category = get_category( get_option( 'default_category' ) );\n\t\t\t\t\t\t$category = is_wp_error( $default_category ) ? '' : $default_category->slug;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t$author = '';\n\t\t\t\tif ( strpos($post_link, '%author%') !== false ) {\n\t\t\t\t\t$authordata = get_userdata($post->post_author);\n\t\t\t\t\t$author = $authordata->user_nicename;\n\t\t\t\t}\n\n\t\t\t\t$date = explode(\" \",date('Y m d H i s', $unixtime));\n\t\t\t\t$rewritereplace =\n\t\t\t\tarray(\n\t\t\t\t\t$date[0],\n\t\t\t\t\t$date[1],\n\t\t\t\t\t$date[2],\n\t\t\t\t\t$date[3],\n\t\t\t\t\t$date[4],\n\t\t\t\t\t$date[5],\n\t\t\t\t\t$post->post_name,\n\t\t\t\t\t$post->ID,\n\t\t\t\t\t$category,\n\t\t\t\t\t$author,\n\t\t\t\t\t$post->post_name,\n\t\t\t\t);\n\t\t\t\t$lang = bbl_get_post_lang_code( $post );\n\t\t\t\tbbl_switch_to_lang( $lang );\n\t\t\t\t$post_link = home_url( str_replace( $rewritecode, $rewritereplace, $post_link ) );\n\t\t\t\tbbl_restore_lang();\n\t\t\t\t$post_link = user_trailingslashit($post_link, 'single');\n\t\t\t\t// END copying from get_permalink function\n\t\t\t\treturn $post_link;\n\t\t\t} else { // if they're not using the fancy permalink option the link won't work. Known bug. :)\n\t\t\t\treturn $post_link;\n\t\t\t}\n\n\t\t} else if ( 'page' == $base_post_type ) {\n\t\t\treturn get_page_link( $post->ID, $leavename );\n\t\t}\n\n\t\treturn user_trailingslashit( $post_link );\n\t}\n\n\t/**\n\t * Hooks the get_sample_permalink filter to provide a correct sample permalink\n\t * in situations where the post_name has been hacked for a particular context.\n\t *\n\t * @filter get_sample_permalink (not yet in existence, see http://core.trac.wordpress.org/attachment/ticket/22338)\n\t *\n\t * @param array $permalink The array, like array( $permalink, $post_name )\n\t * @param string $title A desired title (could be null)\n\t * @param string $name A desired post name (could be null)\n\t * @param int $id The Post ID\n\t * @param object $post A (hacked) Post object\n\t * @return array The array, like array( $permalink, $post_name )\n\t */\n\tpublic function get_sample_permalink( $permalink, $title, $name, $id, $post ) {\n\t\t$permalink[ 0 ] = $this->post_type_link( $permalink[ 0 ], $post, $leavename );\n\t\treturn $permalink;\n\t}\n\n\t/**\n\t * Hooks the WP page_link filter to ensure correct virtual language directory prefix, etc.\n\t *\n\t * @param string $link The permalink for the page\n\t * @param int $id The ID for the post represented by this permalink\n\t * @return string\n\t **/\n\tpublic function page_link( $link, $post_id ) {\n\t\tif ( $this->no_recursion )\n\t\t\treturn $link;\n\n\t\t// Deal with the language front pages\n\t\tif ( 'page' == get_option('show_on_front') && $page_on_front = get_option( 'page_on_front' ) ) {\n\t\t\t$front_page_transid = $this->get_transid( $page_on_front );\n\t\t\t$this_transid = $this->get_transid( $post_id );\n\t\t\tif ( $front_page_transid == $this_transid ) {\n\t\t\t\tbbl_switch_to_lang( bbl_get_post_lang_code( $post_id ) );\n\t\t\t\t$link = home_url();\n\t\t\t\tbbl_restore_lang();\n\t\t\t\treturn $link;\n\t\t\t}\n\t\t}\n\n\t\t$this->no_recursion = 'page_link';\n\t\t$lang = bbl_get_post_lang_code( $post_id );\n\t\tbbl_switch_to_lang( $lang );\n\t\t$link = get_page_link( $post_id );\n\t\tbbl_restore_lang();\n\t\t$this->no_recursion = false;\n\t\treturn $link;\n\t}\n\n\t/**\n\t * Hooks the WP post_type_archive_link filter to return the correct\n\t * post type archive link for the current language.\n\t *\n\t * @param string $link The link to the post type archive (probably wrong for this language)\n\t * @param string $post_type The post_type we need an archive for (though we'll probably need to use a translated (shadow) post_type)\n\t * @return string A URL for the translated (shadow) post_type archive\n\t **/\n\tpublic function post_type_archive_link( $link, $post_type ) {\n\t\tif ( $this->no_recursion )\n\t\t\treturn $link;\n\t\t$this->no_recursion = 'post_type_archive_link';\n\n\t\t$lang_post_type = $this->get_post_type_in_lang( $post_type, bbl_get_current_lang_code() );\n\n\t\tbbl_switch_to_lang( bbl_get_current_lang_code() );\n\t\t$link = get_post_type_archive_link( $lang_post_type );\n\t\tbbl_restore_lang();\n\n\t\t$this->no_recursion = false;\n\t\treturn $link;\n\t}\n\n\t/**\n\t * Hooks the WP clean_post_cache action to clear the Babble\n\t * post translation and transid caches.\n\t *\n\t * Occasionally called directly by within this class.\n\t *\n\t * @param int $post_id The ID of the post to clear the caches for\n\t * @return void\n\t **/\n\tfunction clean_post_cache( $post_id ) {\n\t\twp_cache_delete( $post_id, 'bbl_post_transids' );\n\t\t// clean_post_cache gets called in some situations where\n\t\t// the post is already deleted, in which case do not\n\t\t// force the creation of a transid.\n\t\tif ( ! $transid = $this->get_transid( $post_id, false ) ) {\n\t\t\treturn;\n\t\t}\n\t\twp_cache_delete( $transid, 'bbl_post_translations' );\n\t}\n\n\t/**\n\t * Hooks the WP post_updated action to ensure that the\n\t * required properties are copied to the other posts in\n\t * this translation group.\n\t *\n\t * @param int $post_id The ID of the post being updated\n\t * @return void\n\t **/\n\tpublic function post_updated( $post_id ) {\n\t\tif ( $this->no_recursion )\n\t\t\treturn;\n\t\t$this->no_recursion = 'post_updated';\n\n\t\t$transid = $this->get_transid( $post_id );\n\n\t\t$this->clean_post_cache( $post_id );\n\n\t\t$translations = $this->get_post_translations( $post_id );\n\t\tforeach ( $translations as $lang_code => & $translation ) {\n\t\t\tif ( $translation->ID == $post_id )\n\t\t\t\tcontinue;\n\t\t\t// Copy the various core post properties across\n\t\t\t$this->sync_properties( $post_id, $translation->ID );\n\t\t}\n\n\t\t// Revert comment status, which often gets turned off by\n\t\t// auto drafts.\n\t\t$post_lang_code = bbl_get_post_lang_code( $post_id );\n\t\tif ( bbl_get_default_lang_code() != $post_lang_code ) {\n\t\t\t$source_post = bbl_get_post_in_lang( $post_id, bbl_get_default_lang_code() );\n\t\t\t$target_post = get_post( $post_id );\n\t\t\t$post_data = array(\n\t\t\t\t'ID' => $post_id,\n\t\t\t\t'comment_status' => $source_post->comment_status,\n\t\t\t\t'post_modified' => $target_post->post_modified,\n\t\t\t\t'post_modified_gmt' => $target_post->post_modified_gmt,\n\t\t\t);\n\t\t\twp_update_post( $post_data );\n\t\t}\n\n\t\t$this->no_recursion = false;\n\t}\n\n\t/**\n\t * Hooks the WP transition_post_status action which fires whenever\n\t * a post status changes through use of wp_transition_post_status.\n\t *\n\t * @param string $new_status The new status\n\t * @param string $old_status The old status\n\t * @param object $post The post object\n\t * @return void\n\t **/\n\tpublic function transition_post_status( $new_status, $old_status, $post ) {\n\t\tif ( $new_status == $old_status )\n\t\t\treturn;\n\n\t\tif ( $this->no_recursion ) {\n\t\t\treturn;\n\t\t}\n\t\t$this->no_recursion = 'transition_post_status';\n\n\t\tif ( 'publish' == $new_status && $new_status != $old_status ) {\n\t\t\t// Ensure the date of publication of a translation gets\n\t\t\t// sync'd immediately with the original language post.\n\t\t\tif ( bbl_get_default_lang_code() != bbl_get_post_lang_code( $post->ID ) ) {\n\t\t\t\t$source_post = bbl_get_post_in_lang( $post->ID, bbl_get_default_lang_code() );\n\t\t\t\t$postdata = array(\n\t\t\t\t\t'ID' => $post->ID,\n\t\t\t\t\t'post_date' =>$source_post->post_date,\n\t\t\t\t);\n\t\t\t\twp_update_post( $postdata );\n\t\t\t}\n\t\t}\n\n\t\t$this->no_recursion = false;\n\t}\n\n\t/**\n\t * Hooks the WP add_menu_classes filter to fixup the side\n\t * admin menu.\n\t *\n\t * @param array $menu The WP admin menu\n\t * @return array The WP admin menu\n\t **/\n\tpublic function add_menu_classes( $menu ) {\n\t\tglobal $submenu;\n\t\t$lang = bbl_get_current_lang_code();\n\t\t$default = bbl_get_default_lang_code();\n\t\t// Remove \"new post\" links from submenu(s) for non-default languages\n\t\tif ( $lang != $default ) {\n\t\t\tforeach ( $submenu as $parent => $items ) {\n\t\t\t\tforeach ( $items as $key => $item ) {\n\t\t\t\t\tif ( 'post-new.php' == substr( $item[ 2 ], 0, 12 ) ) {\n\t\t\t\t\t\tunset( $submenu[ $parent ][ $key ] );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// Remove links to shadow post types\n\t\tforeach ( $menu as $key => $item ) {\n\t\t\t$vars = array();\n\t\t\t$url_info = parse_url( $item[ 2 ] );\n\t\t\tif ( ! isset( $url_info[ 'query' ] ) )\n\t\t\t\tcontinue;\n\t\t\tparse_str( $url_info[ 'query' ], $vars );\n\t\t\tif ( ! isset( $vars[ 'post_type' ] ) || ! isset( $this->post_types[ $vars[ 'post_type' ] ] ) )\n\t\t\t\tcontinue;\n\t\t\tunset( $menu[ $key ] );\n\t\t}\n\t\treturn $menu;\n\t}\n\n\t/**\n\t * Hooks the WP filter single_template to deal with the shadow post\n\t * types for pages and singular templates, ensuring they use the\n\t * right template.\n\t *\n\t * @param string $template Path to a template file\n\t * @return Path to a template file\n\t **/\n\tpublic function single_template( $template ) {\n\t\tif( bbl_is_default_lang() )\n\t\t\treturn $template;\n\n\t\t// Deal with the language front pages and custom page templates\n\t\t$post = get_post( get_the_ID() );\n\t\tif ( 'page' == get_option('show_on_front') ) {\n\t\t\t$front_page_transid = $this->get_transid( get_option( 'page_on_front' ) );\n\t\t\t$this_transid = $this->get_transid( get_the_ID() );\n\t\t\t// Check if this is a translation of the page on the front of the site\n\t\t\tif ( $front_page_transid == $this_transid ) {\n\t\t\t\t// global $wp_query, $wp;\n\t\t\t\tif ( 'page' == $this->get_base_post_type( $post->post_type ) ) {\n\t\t\t\t\tif ( $custom_page_template = get_post_meta( get_option( 'page_on_front' ), '_wp_page_template', true ) )\n\t\t\t\t\t\t$templates = (array) $custom_page_template;\n\t\t\t\t\telse\n\t\t\t\t\t\t$templates = (array) 'page.php';\n\t\t\t\t\tif ( $_template = locate_template( $templates ) ) {\n\t\t\t\t\t\treturn $_template;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// Check if we're dealing with a page or a translation of a page\n\t\tif ( 'page' == $this->get_base_post_type( $post->post_type ) ) {\n\t\t\t$custom_page_template = get_post_meta( get_the_ID(), '_wp_page_template', true );\n\t\t\tif ( $custom_page_template && 'default' != $custom_page_template )\n\t\t\t\t$templates = (array) $custom_page_template;\n\t\t\telse\n\t\t\t\t$templates = array( 'page.php' );\n\t\t\tif ( $_template = locate_template( $templates ) ) {\n\t\t\t\treturn $_template;\n\t\t\t}\n\t\t}\n\n\t\t$templates[] = \"single-{$this->get_base_post_type($post->post_type)}.php\";\n\t\t$templates[] = \"single.php\";\n\t\t$template = get_query_template( 'bbl-single', $templates );\n\n\t\treturn $template;\n\t}\n\n\t/**\n\t * Hooks the bbl_sync_meta_key filter from this class which checks\n\t * if a meta_key should be synced. If we return false, it won't be.\n\t *\n\t * @TODO correct inline docs\n\t **/\n\tfunction sync_meta_key( $sync, $meta_key ) {\n\t\t$sync_not = array(\n\t\t\t'_edit_last', // Related to edit lock, should be individual to translations\n\t\t\t'_edit_lock', // The edit lock, should be individual to translations\n\t\t\t'_bbl_default_text_direction', // The text direction, should be individual to translations\n\t\t\t'_wp_trash_meta_status',\n\t\t\t'_wp_trash_meta_time',\n\t\t);\n\t\tif ( in_array( $meta_key, $sync_not ) )\n\t\t\t$sync = false;\n\t\treturn $sync;\n\t}\n\n\t/**\n\t * Hooks the WP manage_posts_columns filter to add our “link” column.\n\t *\n\t * @param array $cols The columns for this post type lists table\n\t * @param string $post_type The post type for this lists table\n\t * @return array The columns\n\t **/\n\tpublic function manage_posts_columns( array $columns, $post_type ) {\n\t\t// Insert our cols just before comments, or date.\n\t\tif ( $post_type == bbl_get_post_type_in_lang( $post_type, bbl_get_default_lang_code() ) )\n\t\t\treturn $columns;\n\t\t# @TODO is this phrase localisable? Might need changing.\n\t\t$columns[ 'bbl_link' ] = __( 'Translation of', 'babble' );\n\t\treturn $columns;\n\t}\n\n\t/**\n\t * Hooks the WP manage_posts_custom_column action to add our “link” content.\n\t *\n\t * @param string $column_name The name of this column\n\t * @param int $post_id The ID for the post for the row which parents this column\n\t * @return void\n\t **/\n\tpublic function manage_posts_custom_column( $column_name, $post_id ) {\n\t\tif ( 'bbl_link' != $column_name )\n\t\t\treturn;\n\t\t$default_post = bbl_get_post_in_lang( $post_id, bbl_get_default_lang_code() );\n\t\tif ( ! $default_post ) {\n\t\t\techo '<em style=\"color: #bc0b0b\">' . __( 'No link', 'babble' ) . '</em>';\n\t\t\treturn;\n\t\t}\n\t\t$edit_link = get_edit_post_link( $default_post->ID );\n\t\t$edit_link = add_query_arg( array( 'lang' => bbl_get_default_lang_code() ), $edit_link );\n\t\tbbl_switch_to_lang( bbl_get_default_lang_code() );\n\t\t$view_link = get_permalink( $default_post->ID );\n\t\tbbl_restore_lang();\n\t\t$edit_title = esc_attr( sprintf( __( 'Edit the originating post: “%s”', 'babble' ), get_the_title( $default_post->ID ) ) );\n\t\t$view_title = esc_attr( sprintf( __( 'View the originating post: “%s”', 'babble' ), get_the_title( $default_post->ID ) ) );\n\t\techo \"<a href='$view_link' title='$view_title'>\" . __( 'View', 'babble' ) . \"</a> | <a href='$edit_link' title='$edit_title'>\" . __( 'Edit', 'babble' ) . \"</a>\";\n\t}\n\n\t// PUBLIC METHODS\n\t// ==============\n\n\tpublic function bbl_translated_taxonomy( $translated, $taxonomy ) {\n\t\tif ( 'post_translation' == $taxonomy )\n\t\t\treturn false;\n\t\treturn $translated;\n\t}\n\n\tpublic function admin_body_class( $class ) {\n\n\t\t$post_type = get_current_screen() ? get_current_screen()->post_type : null;\n\t\tif ( $post_type )\n\t\t\t$class .= ' bbl-post-type-' . $post_type;\n\n\t\treturn $class;\n\n\t}\n\n\t/**\n\t * Takes a set of query vars and amends them to show the content\n\t * in the current language.\n\t *\n\t * @param array $query_vars A set of WordPress query vars (sometimes called query arguments)\n\t * @param string|boolean $request If this is called on the parse_request hook, $request contains the root relative URL\n\t * @return array $query_vars A set of WordPress query vars\n\t **/\n\tprotected function translate_query_vars( array $query_vars, $request = false ) {\n\n\t\t// Sequester the original query, in case we need it to get the default content later\n\t\t$query_vars[ 'bbl_original_query' ] = $query_vars;\n\n\t\t// We've done this already (avoid re-translating the vars)\n\t\tif ( isset( $query_vars[ 'bbl_done_translation' ] ) && $query_vars[ 'bbl_done_translation' ] )\n\t\t\treturn $query_vars;\n\t\t$query_vars[ 'bbl_done_translation' ] = true;\n\n\t\t$lang_url_prefix = isset( $query_vars[ 'lang_url_prefix' ] ) ? $query_vars[ 'lang_url_prefix' ] : get_query_var( 'lang_url_prefix' );\n\t\t$lang = isset( $query_vars[ 'lang' ] ) ? $query_vars[ 'lang' ] : get_query_var( 'lang' );\n\n\t\t// Detect language specific homepages\n\t\tif ( $request == $lang_url_prefix ) {\n\t\t\tunset( $query_vars[ 'error' ] );\n\n\t\t\t// @FIXME: Cater for front pages which don't list the posts\n\t\t\tif ( 'page' == get_option('show_on_front') && $page_on_front = get_option('page_on_front') ) {\n\t\t\t\t// @TODO: Get translated page ID\n\t\t\t\t$query_vars[ 'p' ] = $this->get_post_in_lang( $page_on_front, bbl_get_current_lang_code() )->ID;\n\t\t\t\t$query_vars[ 'post_type' ] = $this->get_post_type_in_lang( 'page', bbl_get_current_lang_code() );\n\t\t\t\treturn $query_vars;\n\t\t\t}\n\n\t\t\t// Trigger the archive listing for the relevant shadow post type\n\t\t\t// of 'post' for this language.\n\t\t\tif ( bbl_get_default_lang_code() != $lang && empty( $query_vars['s'] ) ) {\n\t\t\t\t$post_type = isset( $query_vars[ 'post_type' ] ) ? $query_vars[ 'post_type' ] : 'post';\n\n\t\t\t\t$query_vars[ 'post_type' ] = $this->get_post_type_in_lang( $post_type, bbl_get_current_lang_code() );\n\n\t\t\t}\n\n\t\t\treturn $query_vars;\n\t\t}\n\n\t\t// If we're asking for the default content, it's fine\n\t\tif ( bbl_get_default_lang_code() == $lang ) {\n\t\t\treturn $query_vars;\n\t\t}\n\n\t\t// Now swap the query vars so we get the content in the right language post_type\n\n\t\t// @FIXME: Do I need to change $wp->matched query? I think $wp->matched_rule is fine?\n\t\t// @FIXME: Danger of post type slugs clashing??\n\t\tif ( isset( $query_vars[ 'pagename' ] ) && $query_vars[ 'pagename' ] ) {\n\t\t\t// Substitute post_type for\n\t\t\t$query_vars[ 'name' ] = $query_vars[ 'pagename' ];\n\t\t\t$query_vars[ bbl_get_post_type_in_lang( 'page', $query_vars[ 'lang' ] ) ] = $query_vars[ 'pagename' ];\n\t\t\t$query_vars[ 'post_type' ] = bbl_get_post_type_in_lang( 'page', bbl_get_current_lang_code() );\n\t\t\t// Trigger a listing of translated posts if this is meant to\n\t\t\t// be the blog page.\n\t\t\tif ( 'page' == get_option( 'show_on_front' ) ) {\n\t\t\t\t// Test if the current page is in the same translation group as\n\t\t\t\t// the 'page_for_posts.\n\t\t\t\t$current_post = get_page_by_path( $query_vars[ 'pagename' ], null, $query_vars[ 'post_type' ] );\n\t\t\t\tif ( $this->get_transid( get_option( 'page_for_posts' ) ) == $this->get_transid( $current_post ) ) {\n\t\t\t\t\t$query_vars[ 'post_type' ] = bbl_get_post_type_in_lang( 'post', bbl_get_current_lang_code() );\n\t\t\t\t\tunset( $query_vars[ 'name' ] );\n\t\t\t\t\tunset( $query_vars[ bbl_get_post_type_in_lang( 'page', $query_vars[ 'lang' ] ) ] );\n\t\t\t\t}\n\t\t\t}\n\t\t\tunset( $query_vars[ 'page' ] );\n\t\t\tunset( $query_vars[ 'pagename' ] );\n\t\t} elseif ( isset( $query_vars[ 'year' ] ) && $query_vars[ 'year' ] ) {\n\t\t\t// @FIXME: This is not a reliable way to detect queries for the 'post' post_type.\n\t\t\t$query_vars[ 'post_type' ] =  bbl_get_post_type_in_lang( 'post', bbl_get_current_lang_code() );\n\t\t} elseif ( isset( $query_vars[ 'post_type' ] ) ) {\n\t\t\tif ( is_array( $query_vars[ 'post_type' ] ) ) {\n\t\t\t\t$new_post_types = array();\n\t\t\t\tforeach ( $query_vars[ 'post_type' ] as $post_type ) {\n\t\t\t\t\t$new_post_types[] = bbl_get_post_type_in_lang( $post_type, bbl_get_current_lang_code() );\n\t\t\t\t}\n\t\t\t\t$query_vars[ 'post_type' ] = $new_post_types;\n\t\t\t} else {\n\t\t\t\t$query_vars[ 'post_type' ] = bbl_get_post_type_in_lang( $query_vars[ 'post_type' ], bbl_get_current_lang_code() );\n\t\t\t}\n\t\t} else {\n\t\t\t$query_vars[ 'post_type' ] = bbl_get_post_type_in_lang( 'post', bbl_get_current_lang_code() );\n\t\t}\n\n\t\treturn $query_vars;\n\t}\n\n\t/**\n\t * Discover whether a post is set as the front page\n\t * for the site in a particular language.\n\t *\n\t * @param int $post_id The ID of a post\n\t * @return boolean True if this post is used as the front page of the site for a language\n\t **/\n\tpublic function is_language_front_page( $post_id = null, $lang_code = null ) {\n\t\tif ( 'page' != get_option('show_on_front') )\n\t\t \treturn false;\n\n\t\t$post = get_post( $post_id );\n\t\t// If we have a lang code, and it doesn't match the requested post lang then this\n\t\t// is not the right front page\n\t\tif ( ! is_null( $lang_code ) && $lang_code != $this->get_post_lang_code( $post->ID ) )\n\t\t\treturn false;\n\n\t\t$front_page_transid = $this->get_transid( get_option( 'page_on_front' ) );\n\t\t$this_transid = $this->get_transid( get_the_ID() );\n\t\tif ( $front_page_transid != $this_transid )\n\t\t\treturn false;\n\n\t\treturn true;\n\t}\n\n\t/**\n\t * Return the language code for the language a given post is written for/in.\n\t *\n\t * @param int|object $post Either a WP Post object, or a post ID\n\t * @return string|object Either a language code, or a WP_Error object\n\t * @access public\n\t **/\n\tpublic function get_post_lang_code( $post ) {\n\t\t$post = get_post( $post );\n\t\tif ( ! $post )\n\t\t\treturn new WP_Error( 'bbl_invalid_post', __( 'Invalid Post passed to get_post_lang_code', 'babble' ) );\n\t\tif ( isset( $this->lang_map[ $post->post_type ] ) )\n\t\t\treturn $this->lang_map[ $post->post_type ];\n\t\treturn bbl_get_default_lang_code();\n\t}\n\n\t/**\n\t * Return the admin URL to create a new translation for a post in a\n\t * particular language.\n\t *\n\t * @param int|object $default_post The post in the default language to create a new translation for, either WP Post object or post ID\n\t * @param string $lang The language code\n\t * @return string The admin URL to create the new translation\n\t **/\n\tpublic function get_new_post_translation_url( $default_post, $lang_code ) {\n\t\t$default_post = get_post( $default_post );\n\t\t$url = admin_url( 'post-new.php' );\n\t\t$args = array(\n\t\t\t'bbl_origin_post' => $default_post->ID,\n\t\t\t'lang'            => $lang_code,\n\t\t\t'post_type'       => 'bbl_job',\n\t\t);\n\t\t$url = add_query_arg( $args, $url );\n\t\treturn $url;\n\t}\n\n\t/**\n\t * Returns the post ID for the post in the default language from which\n\t * this post was translated.\n\t *\n\t * @param int|WP_Post $post Either a WP Post object, or a post ID\n\t * @return int The ID of the default language equivalent post\n\t **/\n\tpublic function get_default_lang_post( $post ) {\n\t\t$post = get_post( $post );\n\t\t$translations = bbl_get_post_translations( $post->ID );\n\t\tif ( isset( $translations[ bbl_get_default_lang_code() ] ) )\n\t\t\treturn $translations[ bbl_get_default_lang_code() ];\n\t\treturn false;\n\t}\n\n\t/**\n\t * Get the posts which are the translations for the provided\n\t * post ID. N.B. The returned array of post objects (and false\n\t * values) will include the post for the post ID passed.\n\t *\n\t * @param int|WP_Post $post Either a WP Post object, or a post ID\n\t * @return array Either an array keyed by the site languages, each key containing false (if no translation) or a WP Post object\n\t **/\n\tpublic function get_post_translations( $post ) {\n\t\t$post = get_post( $post );\n\t\t// @FIXME: Is it worth caching here, or can we just rely on the caching in get_objects_in_term and get_posts?\n\t\t$transid = $this->get_transid( $post );\n\n\t\tif ( $translations = wp_cache_get( $transid, 'bbl_post_translations' ) ) {\n\t\t\treturn $translations;\n\t\t}\n\n\t\t# @TODO A transid should never be a wp_error. Check and fix.\n\t\tif ( is_wp_error( $transid ) )\n\t\t\terror_log( \"Error getting transid: \" . print_r( $transid, true ) );\n\t\t$post_ids = get_objects_in_term( $transid, 'post_translation' );\n\n\t\t// Work out all the translated equivalent post types\n\t\t$post_types = array();\n\t\t$langs = bbl_get_active_langs();\n\t\tforeach ( $langs as $lang )\n\t\t\t$post_types[] = bbl_get_post_type_in_lang( $post->post_type, $lang->code );\n\n\t\t// Get all the translations in one cached DB query\n\t\t$args = array(\n\t\t\t// We want a clean listing, without any particular language\n\t\t\t'bbl_translate' => false,\n\t\t\t'include' => $post_ids,\n\t\t\t'post_type' => $post_types,\n\t\t\t'post_status' => array( 'publish', 'pending', 'draft', 'future' ),\n\t\t);\n\t\t$posts = get_posts( $args );\n\t\t$translations = array();\n\t\tforeach ( $posts as $post )\n\t\t\t$translations[ $this->get_post_lang_code( $post ) ] = $post;\n\n\t\twp_cache_add( $transid, $translations, 'bbl_post_translations' );\n\n\t\treturn $translations;\n\t}\n\n\t/**\n\t * Return the base post type (in the default language) for a\n\t * provided post type.\n\t *\n\t * @param string $post_type The name of a post type\n\t * @return string The name of the base post type\n\t **/\n\tpublic function get_base_post_type( $post_type ) {\n\t\tif ( ! isset( $this->post_types[ $post_type ] ) )\n\t\t\treturn $post_type;\n\t\treturn $this->post_types[ $post_type ];\n\t}\n\n\t/**\n \t * Return all the base post types (in the default language).\n \t *\n \t * @return array An array of post_type objects\n\t **/\n\tpublic function get_base_post_types() {\n\t\t$post_types = array();\n\t\tforeach ( $this->post_types as $post_type )\n\t\t\t$post_types[ $post_type ] = get_post_type_object( $post_type );\n\t\treturn $post_types;\n\t}\n\n\t/**\n\t * Returns the equivalent post_type in the specified language.\n\t *\n\t * @param string $post_type A post_type to return in a given language\n\t * @param string $lang_code The language code for the required language \n\t * @return string The equivalent post_type name, or given post_type if it doesn't exist\n\t **/\n\tpublic function get_post_type_in_lang( $post_type, $lang_code ) {\n\t\t$base_post_type = $this->get_base_post_type( $post_type );\n\n\t\tif ( bbl_get_default_lang_code() == $lang_code ) {\n\t\t\treturn $base_post_type;\n\t\t}\n\n\t\t// Some post types are untranslated…\n\t\tif ( ! bbl_is_translated_post_type( $post_type ) ) {\n\t\t\treturn $post_type;\n\t\t}\n\n\t\t// Return the original post type if we couldn't find it in our array\n\t\tif ( ! isset( $this->lang_map2[ $lang_code ][ $base_post_type ] ) ) {\n\t\t\treturn $post_type;\n\t\t}\n\n\t\treturn $this->lang_map2[ $lang_code ][ $base_post_type ];\n\t}\n\n\t/**\n\t * Returns an array of all the shadow post types associated with\n\t * this post type.\n\t *\n\t * @param string $base_post_type The post type to look up shadow post types for\n\t * @return array The names of all the related shadow post types\n\t **/\n\tpublic function get_shadow_post_types( $base_post_type ) {\n\t\t$post_types = array();\n\t\t$langs = bbl_get_active_langs();\n\t\tforeach ( $langs as $lang ) {\n\t\t\tif ( isset( $this->lang_map2[ $lang->code ][ $base_post_type ] ) )\n\t\t\t\t$post_types[] = $this->lang_map2[ $lang->code ][ $base_post_type ];\n\t\t}\n\t\treturn $post_types;\n\t}\n\n\t/**\n\t * Returns the post in a particular language, or the fallback content\n\t * if there's no post available.\n\t *\n\t * @param int|WP_Post $post Either a WP Post object, or a post ID\n\t * @param string $lang_code The language code for the required language\n\t * @param boolean $fallback If true: if a post is not available, fallback to the default language content (defaults to true)\n\t * @return object|boolean The WP Post object, or if $fallback was false and no post then returns false\n\t **/\n\tpublic function get_post_in_lang( $post, $lang_code, $fallback = true ) {\n\t\t$translations = $this->get_post_translations( $post );\n\t\tif ( isset( $translations[ $lang_code ] ) ) {\n\t\t\treturn $translations[ $lang_code ];\n\t\t}\n\t\tif ( ! $fallback ) {\n\t\t\treturn false;\n\t\t}\n\t\treturn $translations[ bbl_get_default_lang_code() ];\n\t}\n\n\t/**\n\t * Returns a slug translated into a particular language.\n\t *\n\t * @param string $slug The slug to translate\n\t * @param string $lang A Babble language object\n\t * @param array $post_type_args The args for the post type associated with this post type\n\t * @return void\n\t **/\n\tpublic function get_slug_in_lang( $slug, $lang ) {\n\t\t$_slug = mb_strtolower( apply_filters( 'bbl_translate_post_type_slug', $slug, $lang->code ) );\n\t\t// @FIXME: For some languages the translation might be the same as the original\n\t\tif ( $_slug &&  $_slug != $slug )\n\t\t\treturn $_slug;\n\t\t// FIXME: Do we need to check that the slug is unique at this point?\n\t\treturn mb_strtolower( \"{$_slug}_{$lang->code}\" );\n\t}\n\n\t// PRIVATE/PROTECTED METHODS\n\t// =========================\n\n\t/**\n\t * Copy various properties from one post to another.\n\t *\n\t * @param int $source_id The source post, to copy FROM\n\t * @param int $target_id The target post, to copy TO\n\t * @return void\n\t **/\n\tpublic function sync_properties( $source_id, $target_id ) {\n\t\tif ( ! ( $source_post = get_post( $source_id ) ) )\n\t\t\treturn;\n\n\t\t$source_lang_code = bbl_get_post_lang_code( $source_id );\n\n\t\t$target_lang_code = bbl_get_post_lang_code( $target_id );\n\n\t\t$target_parent_post = false;\n\t\tif ( $source_post->post_parent ) {\n\t\t\t$source_parent_post = $this->get_post_in_lang( $source_post->post_parent, $source_lang_code );\n\t\t\t$target_parent_post = $this->get_post_in_lang( $source_parent_post, $target_lang_code );\n\t\t}\n\n\t\t$target_post = get_post( $target_id );\n\n\t\t$postdata = array(\n\t\t\t'ID' => $target_id,\n\t\t\t'post_author' => $source_post->post_author,\n\t\t\t'post_modified' => $target_post->post_modified,\n\t\t\t'post_modified_gmt' => $target_post->post_modified_gmt,\n\t\t\t'ping_status' => $source_post->ping_status,\n\t\t\t'post_password' => $source_post->post_password,\n\t\t\t'menu_order' => $source_post->menu_order,\n\t\t\t'post_mime_type' => $source_post->post_mime_type,\n\t\t);\n\n\t\tif ( $target_parent_post )\n\t\t\t$postdata[ 'post_parent' ] = $target_parent_post->ID;\n\t\telse\n\t\t\t$postdata[ 'post_parent' ] = 0;\n\n\t\tif ( bbl_get_default_lang_code() == $source_lang_code ) {\n\t\t\t$postdata[ 'post_date' ] = $source_post->post_date;\n\t\t\t$postdata[ 'post_date_gmt' ] = $source_post->post_date_gmt;\n\t\t}\n\n\t\t// Comment status only synced when going from the default lang code\n\t\tif ( bbl_get_default_lang_code() == $source_lang_code )\n\t\t\t$postdata[ 'comment_status' ] = $source_post->comment_status;\n\n\t\t$postdata = apply_filters( 'bbl_pre_sync_properties', $postdata, $source_id );\n\n\t\twp_update_post( $postdata );\n\t}\n\n\t/**\n\t * Resync all (synced) post meta data from the post in\n\t * the default language to this post.\n\t *\n\t * @param $int The post ID to sync TO\n\t * @return void\n\t **/\n\tfunction sync_post_meta( $post_id ) {\n\t\tif ( $this->no_meta_recursion )\n\t\t\treturn;\n\t\t$this->no_meta_recursion = 'updated_post_meta';\n\n\t\t// First delete all the synced meta from this post\n\t\t$current_metas = (array) get_post_meta( $post_id );\n\t\tforeach ( $current_metas as $current_meta_key => & $current_meta_values ) {\n\t\t\t// Some metadata shouldn't be synced, this filter allows a dev to return\n\t\t\t// false if the particular meta_key is one which shouldn't be synced.\n\t\t\t// If you find a core meta_key which is currently synced and should NOT be,\n\t\t\t// please submit a patch to the sync_meta_key method on this class. Thanks.\n\t\t\tif ( apply_filters( 'bbl_sync_meta_key', true, $current_meta_key ) )\n\t\t\t\tdelete_post_meta( $post_id, $current_meta_key );\n\t\t}\n\n\t\t// Now add meta in again from the origin post\n\t\t$origin_post = bbl_get_post_in_lang( $post_id, bbl_get_default_lang_code() );\n\n\t\t$metas = get_post_meta( $origin_post->ID );\n\t\tif ( ! $metas )\n\t\t\treturn;\n\n\t\tforeach ( $metas as $meta_key => & $meta_value ) {\n\t\t\t// Some metadata shouldn't be synced\n\t\t\tif ( ! apply_filters( 'bbl_sync_meta_key', true, $meta_key ) )\n\t\t\t\tcontinue;\n\t\t\t// The meta could be an array stored in a single postmeta row or an\n\t\t\t// array of values from multiple rows; work out which we have.\n\t\t\t$val_multi = get_post_meta( $origin_post->ID, $meta_key );\n\t\t\tforeach ( $val_multi as & $val_single ) {\n\t\t\t\tadd_post_meta( $post_id, $meta_key, $val_single );\n\t\t\t}\n\t\t}\n\t\t$this->no_meta_recursion = false;\n\t}\n\n\t/**\n\t * Get the transID for this post, this is an identifier linking all the translations\n\t * for a single piece of content together.\n\t *\n\t * Marked private as we may change how translations are linked. Please use API, or\n\t * raise an issue.\n\t *\n\t * @param int|object $post The WP Post object, or the ID of a post\n\t * @return string The transid\n\t * @access private\n\t **/\n\tfunction get_transid( $post, $create = true ) {\n\t\t$post = get_post( $post );\n\n\t\tif ( ! $post->ID )\n\t\t\treturn false;\n\n\t\tif ( $transid = wp_cache_get( $post->ID, 'bbl_post_transids' ) ) {\n\t\t\treturn $transid;\n\t\t}\n\n\t\t$transids = (array) wp_get_object_terms( $post->ID, 'post_translation', array( 'fields' => 'ids' ) );\n\t\t// \"There can be only one\" (so we'll just drop the others)\n\t\t$transid = false;\n\t\tif ( isset( $transids[ 0 ] ) ) {\n\t\t\t$transid = $transids[ 0 ];\n\t\t} else {\n\t\t\tif ( $create ) {\n\t\t\t\t$transid = $this->set_transid( $post );\n\t\t\t}\n\t\t}\n\n\t\tif ( ! $transid ) {\n\t\t\treturn false;\n\t\t}\n\n\t\twp_cache_add( $post->ID, $transid, 'bbl_post_transids' );\n\n\t\treturn $transid;\n\t}\n\n\t/**\n\t * Create and assign a new TransID to a post.\n\t *\n\t * @param int|object $post Either a Post ID or a WP Post object\n\t * @param string $transid (optional) A transid to associate with the post\n\t * @return string The transid which has just been set\n\t * @access private\n\t **/\n\tfunction set_transid( $post, $transid = false ) {\n\t\t$post = get_post( $post );\n\t\tif ( ! isset( $post->ID ) )\n\t\t\treturn false;\n\t\t// @FIXME: Abstract the code for generating and associating a new TransID\n\t\tif ( ! $transid ) {\n\t\t\t$transid_name = 'post_transid_' . uniqid();\n\t\t\t$result = wp_insert_term( $transid_name, 'post_translation', array() );\n\t\t\tif ( is_wp_error( $result ) )\n\t\t\t\terror_log( \"Problem creating a new Post TransID: \" . print_r( $result, true ) );\n\t\t\telse\n\t\t\t\t$transid = $result[ 'term_id' ];\n\t\t\t// Delete anything in there currently\n\t\t\twp_cache_delete( $transid, 'bbl_post_translations' );\n\t\t}\n\t\t$result = wp_set_object_terms( $post->ID, $transid, 'post_translation' );\n\t\tif ( is_wp_error( $result ) )\n\t\t\terror_log( \"Problem associating TransID with new posts: \" . print_r( $result, true ) );\n\n\t\t$this->clean_post_cache( $post->ID );\n\n\t\treturn $transid;\n\t}\n\n\t/**\n\t * Return a list of features supported by a post_type.\n\t *\n\t * Hello there, code investigator. I imagine you're wondering\n\t * why I'm accessing a global prefixed by an underscore? I realise\n\t * these are nominally private variables, prone to change, but\n\t * I need to access a list of all features supported by a post\n\t * type, in order to shadow it for the various translations,\n\t * and there's no core function to allow me to do this.\n\t *\n\t * @TODO: Raise a Trac ticket for adding this functionality to the post type API\n\t *\n\t * @param string $post_type The name of the post type for which to get the features supported\n\t * @return array An array of features supported by this post type\n\t **/\n\tprotected function get_features_supported_by_post_type( $post_type ) {\n\t\tglobal $_wp_post_type_features;\n\t\treturn (array) $_wp_post_type_features[$post_type];\n\t}\n\n\t/**\n\t * Are we on the media upload gallery tab?\n\t *\n\t * @param string $tab A specific tab to detect\n\t * @return boolean True if we are on media upload generally, and the specific tab if specified\n\t **/\n\tprotected function is_media_upload_tab( $tab = null ) {\n\t\tif ( ! is_admin() )\n\t\t\treturn false;\n\t\tif ( 'media-upload.php' != basename( $_SERVER[ 'SCRIPT_NAME' ] ) ) {\n\t\t\treturn false;\n\t\t}\n\t\tif ( is_null( $tab ) ) {\n\t\t\treturn true;\n\t\t}\n\t\tif ( isset( $_GET[ 'tab' ] ) || $tab == $_GET[ 'tab' ] ) {\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t}\n\n\t/**\n\t * Are we viewing the (3.5+) media manager?\n\t *\n\t * @return boolean True if we are viewing the media manager\n\t **/\n\tprotected function is_media_manager() {\n\t\tif ( ! is_admin() )\n\t\t\treturn false;\n\t\tif ( !isset( $_POST['action'] ) ) {\n\t\t\treturn false;\n\t\t}\n\t\tif ( 'query-attachments' == $_POST['action'] ) {\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t}\n\n\t/**\n\t * Remove over-synced post metas.\n\t *\n\t * @return void\n\t **/\n\tprotected function prune_post_meta() {\n\t\tglobal $wpdb;\n\t\t$meta_keys = array(\n\t\t\t'_thumbnail_id',\n\t\t\t'_wp_old_slug' ,\n\t\t\t'_wp_page_template',\n\t\t\t'_wp_trash_meta_status',\n\t\t\t'_wp_trash_meta_time',\n\t\t);\n\t\tforeach ( $meta_keys as $meta_key ) {\n\t\t\t$prepared_sql = $wpdb->prepare( \"SELECT COUNT(*) AS count, post_id, meta_key, meta_value FROM $wpdb->postmeta WHERE meta_key = %s GROUP BY post_id, meta_key, meta_value HAVING count > 1\", $meta_key );\n\t\t\t$metas = $wpdb->get_results( $prepared_sql );\n\t\t\tforeach ( $metas as $meta ) {\n\t\t\t\tif ( $meta->count < 2 ) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\t$prepared_sql = $wpdb->prepare( \"DELETE FROM $wpdb->postmeta WHERE post_id = %d AND meta_key = %s AND meta_value = %s LIMIT %d\", $meta->post_id, $meta->meta_key, $meta->meta_value, (int) $meta->count - 1 );\n\t\t\t\t$wpdb->query( $prepared_sql );\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Checks the DB structure is up to date, rewrite rules,\n\t * theme image size options are set, etc.\n\t *\n\t * @return void\n\t **/\n\tprotected function maybe_upgrade() {\n\t\tglobal $wpdb;\n\t\t$option_name = 'bbl_post_public_version';\n\t\t$version = get_option( $option_name, 0 );\n\n\t\tif ( $version == $this->version )\n\t\t\treturn;\n\n\t\tif ( $start_time = get_option( \"{$option_name}_running\", false ) ) {\n\t\t\t$time_diff = time() - $start_time;\n\t\t\t// Check the lock is less than 30 mins old, and if it is, bail\n\t\t\tif ( $time_diff < ( 60 * 30 ) ) {\n\t\t\t\terror_log( \"Babble Post Public: Existing update routine has been running for less than 30 minutes\" );\n\t\t\t\treturn;\n\t\t\t}\n\t\t\terror_log( \"Babble Post Public: Update routine is running, but older than 30 minutes; going ahead regardless\" );\n\t\t} else {\n\t\t\tadd_option( \"{$option_name}_running\", time(), null, 'no' );\n\t\t}\n\n\t\tif ( $version < 9 ) {\n\t\t\terror_log( \"Babble Post Public: Start pruning metadata\" );\n\t\t\t$this->prune_post_meta();\n\t\t\terror_log( \"Babble Post Public: Remove excess post meta\" );\n\t\t}\n\n\t\t// N.B. Remember to increment $this->version above when you add a new IF\n\n\t\tupdate_option( $option_name, $this->version );\n\t\tdelete_option( \"{$option_name}_running\", true, null, 'no' );\n\t\terror_log( \"Babble Post Public: Done upgrade, now at version \" . $this->version );\n\t}\n\n\t/**\n\t * Checks for duplicated metadata in some key meta_keys.\n\t *\n\t * @return boolean\n\t * @author Simon Wheatley\n\t */\n\tfunction have_duplicate_metadata() {\n\t\tglobal $wpdb;\n\t\t$sql = \"\n\t\t\tSELECT COUNT(*) AS count, post_id, meta_key, meta_value\n\t\t\tFROM $wpdb->postmeta\n\t\t\tWHERE meta_key IN (\n\t\t\t\t'_extmedia-youtube', '_extmedia-duration', '_thumbnail_id', '_wp_trash_meta_time', '_wp_page_template', '_wp_trash_meta_status'\n\t\t\t)\n\t\t\tGROUP BY post_id, meta_key, meta_value\n\t\t\tHAVING count > 1\n\t\t\tORDER BY count, post_id, meta_key\n\t\t\";\n\t\treturn (bool) count( $wpdb->get_results( $sql ) );\n\t}\n\n\n}\n\nglobal $bbl_post_public;\n$bbl_post_public = new Babble_Post_Public();\n"
  },
  {
    "path": "class-switcher-content.php",
    "content": "<?php\n\n/**\n * Class for providing the switch/create links for the current \n * content items. Works in the admin or public areas of the site.\n * \n * Used by the admin bar class, for example.\n *\n * @package Babble\n * @since 0.2\n */\nclass Babble_Switcher_Menu {\n\t\n\t/**\n\t * The translations for the current content item.\n\t *\n\t * @var array\n\t **/\n\tprotected $translations;\n\n\t/**\n\t * A multi-dimensional array of the links for the current \n\t * translation structure.\n\t *\n\t * @var array\n\t **/\n\tprotected $links;\n\t\n\t/**\n\t * The WP Screen object\n\t *\n\t * @var object\n\t **/\n\tprotected $screen;\n\t\n\t// PUBLIC METHODS\n\t// ==============\n\n\t/**\n\t * Returns an array of links to the other objects\n\t * in this translation group. Each element in the array\n\t * looks like:\n\t * array (\n\t * \t\t\n\t * )\n\t *\n\t * @param string $id_prefix A prefix to the ID for each item\n\t * @return array An array of link info for the objects in this current translation group.\n\t **/\n\tpublic function get_switcher_links( $id_prefix ) {\n\t\t$this->populate_links();\n\t\treturn $this->links;\n\t}\n\n\t\n\t// PRIVATE/PROTECTED METHODS\n\t// =========================\n\n\t/**\n\t * undocumented function\n\t *\n\t * @return void\n\t **/\n\tprotected function populate_links() {\n\t\tif ( is_array( $this->links ) && ! empty( $this->links ) )\n\t\t\treturn; // Already done\n\t\t\n\t\t$this->links = array();\n\n\t\t// @FIXME: Not sure this is the best way to specify languages\n\t\t$alt_langs = bbl_get_active_langs();\n\n\t\t$this->screen = is_admin() ? get_current_screen() : false;\n\n\t\t// Create a handy flag for whether we're editing a post or listing posts\n\t\t$editing_post = false;\n\t\t$listing_posts = false;\n\t\tif ( is_admin() ) {\n\t\t\t$editing_post = ( is_admin() && 'post' == $this->screen->base && isset( $_GET[ 'post' ] ) );\n\t\t\t$listing_posts = ( is_admin() && 'edit' == $this->screen->base && ! isset( $_GET[ 'post' ] ) );\n\t\t}\n\n\t\t// Create a handy flag for whether we're editing a term or listing terms\n\t\t$editing_term = false;\n\t\t$listing_terms = false;\n\t\tif ( is_admin() ) {\n\t\t\t$editing_term = ( is_admin() && 'edit-tags' == $this->screen->base && isset( $_GET[ 'tag_ID' ] ) );\n\t\t\t$listing_terms = ( is_admin() && 'edit-tags' == $this->screen->base && ! isset( $_GET[ 'tag_ID' ] ) );\n\t\t}\n\n\t\tif ( is_singular() || is_single() || $editing_post ) {\n\t\t\t$this->translations = bbl_get_post_translations( get_the_ID() );\n\t\t\t$this->jobs         = bbl_get_incomplete_post_jobs( get_the_ID() );\n\t\t} else if ( 'page' == get_option( 'show_on_front' ) && is_home() ) {\n\t\t\t$this->translations = bbl_get_post_translations( get_option( 'page_for_posts' ) );\n\t\t\t$this->jobs         = bbl_get_incomplete_post_jobs( get_option( 'page_for_posts' ) );\n\t\t} else if ( ( !is_admin() and ( is_tax() || is_category() ) ) || $editing_term ) {\n\t\t\tif ( isset( $_REQUEST[ 'tag_ID' ] ) )\n\t\t\t\t$term = get_term( (int) @ $_REQUEST[ 'tag_ID' ], $this->screen->taxonomy );\n\t\t\telse\n\t\t\t\t$term = get_queried_object();\n\t\t\t$this->translations = bbl_get_term_translations( $term->term_id, $term->taxonomy );\n\t\t\t$this->jobs         = bbl_get_term_jobs( $term->term_id, $term->taxonomy );\n\t\t}\n\n\t\tforeach ( $alt_langs as $i => & $alt_lang ) {\n\t\t\t// @TODO: Convert to a switch statement, convert all the vars to a single property on the class\n\t\t\tif ( is_admin() ) {\n\t\t\t\tif ( $editing_post ) {\t\t\t// Admin: Editing post link\n\t\t\t\t\t$this->add_admin_post_link( $alt_lang );\n\t\t\t\t} else if ( $editing_term ) {\t// Admin: Editing term link\n\t\t\t\t\t$this->add_admin_term_link( $alt_lang );\n\t\t\t\t} else if ( $listing_posts ) {\t// Admin: Listing posts link\n\t\t\t\t\t$this->add_admin_list_posts_link( $alt_lang );\n\t\t\t\t} else if ( $listing_terms ) {\t// Admin: Listing terms link\n\t\t\t\t\t$this->add_admin_list_terms_link( $alt_lang );\n\t\t\t\t} else {\t\t\t\t\t\t// Admin: Generic link link\n\t\t\t\t\t$this->add_admin_generic_link( $alt_lang );\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t\n\t\t\tif ( \n\t\t\t\tis_singular() || is_single() ||\n\t\t\t\t( 'page' == get_option( 'show_on_front' ) && is_home() )\n\t\t\t) {\t// Single posts, pages, blog homepage\n\t\t\t\t$this->add_post_link( $alt_lang );\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t\n\t\t\t// Don't add a switcher link if the language is not public and\n\t\t\t// the user cannot edit any posts (as a rough guide to whether \n\t\t\t// they are more than just a subscriber).\n\t\t\t// @TODO this cap check should move into each add_*_link() method:\n\t\t\tif ( ! bbl_is_public_lang( $alt_lang->code ) && ! current_user_can( 'edit_posts' ) )\n\t\t\t\tcontinue;\n\t\t\t\n\t\t\tif ( is_front_page() ) { \t\t\t\t// Language homepage\n\t\t\t\t// is_front_page works for language homepages, phew\n\t\t\t\t$this->add_front_page_link( $alt_lang );\n\t\t\t} else if ( is_post_type_archive() ) {\t\t\t// Post type archives\n\t\t\t\t$this->add_post_type_archive_link( $alt_lang );\n\t\t\t} else if ( is_tax() || is_category() ) { \t\t// Category or taxonomy archive\n\t\t\t\t$this->add_taxonomy_archive_link( $alt_lang );\n\t\t\t} else { // 404's, amongst other things\n\t\t\t\t$this->add_arbitrary_link( $alt_lang );\n\t\t\t}\n\t\t}\n\n\t\t// Make up the class attribute on all links\n\t\tforeach ( $this->links as $lang_code => & $link ){\n\t\t\t$link[ 'class' ]  = implode( ' ', $link[ 'classes' ] );\n\t\t\t$link[ 'active' ] = $lang_code == bbl_get_current_lang_code();\n\t\t}\n\t}\n\n\t/**\n\t * Add an admin link to the same page, but with the language switch GET\n\t * parameter set.\n\t *\n\t * @param object $lang A Babble language object for this link\n\t * @return void\n\t **/\n\tprotected function add_admin_generic_link( $lang ) {\n\t\t$classes = array();\n\t\t$href = add_query_arg( array( 'lang' => $lang->code ) );\n\t\t$href = apply_filters( 'bbl_switch_admin_generic_link', $href, $lang );\n\t\t$title = sprintf( __( 'Switch to %s', 'babble' ), $lang->display_name );\n\t\t$classes[] = \"bbl-lang-$lang->code bbl-lang-$lang->url_prefix\";\n\t\t$classes[] = 'bbl-admin';\n\t\t$classes[] = 'bbl-admin-generic';\n\t\t$classes[] = 'bbl-lang';\n\t\tif ( $lang->code == bbl_get_current_lang_code() )\n\t\t\t$classes[] = 'bbl-active';\n\n\t\t// Preventing errors on initial plugin load - before settings saved the first time\n\t\tif ( empty( $lang->display_name ) ) {\n\t\t\t$lang->display_name = '';\n\t\t}\n\n\t\t$this->links[ $lang->code ] = array(\n\t\t\t'classes' => $classes,\n\t\t\t'href' => $href,\n\t\t\t'id' => $lang->url_prefix,\n\t\t\t'meta' => array( 'class' => strtolower( join( ' ', array_unique( $classes ) ) ) ),\n\t\t\t'title' => $title,\n\t\t\t'lang' => $lang,\n\t\t);\n\t}\n\n\t/**\n\t * Add an admin term link to the parent link provided (by reference).\n\t *\n\t * @param object $lang A Babble language object for this link\n\t * @return void\n\t **/\n\tprotected function add_admin_term_link( $lang ) {\n\t\t$classes = array();\n\t\tif ( isset( $this->translations[ $lang->code ]->term_id ) ) { // Translation exists\n\t\t\t$args = array( \n\t\t\t\t'lang' => $lang->code, \n\t\t\t\t'taxonomy' => $this->translations[ $lang->code ]->taxonomy, \n\t\t\t\t'tag_ID' => $this->translations[ $lang->code ]->term_id \n\t\t\t);\n\t\t\t$href = add_query_arg( $args );\n\t\t\t$title = sprintf( __( 'Switch to %s', 'babble' ), $lang->display_name );\n\t\t\t$classes[] = 'bbl-existing-edit';\n\t\t\t$classes[] = 'bbl-existing-edit-term';\n\t\t} else if ( isset( $this->jobs[ $lang->code ]->ID ) ) { // Translation job exists\n\t\t\t$href = get_edit_post_link( $this->jobs[ $lang->code ]->ID, 'url' );\n\t\t\t$title = sprintf( _x( '%s: %s', 'Translation job status and language (example: In Progress: French)', 'babble' ), get_post_status_object( $this->jobs[ $lang->code ]->post_status )->label, $lang->display_name );\n\t\t\t$classes[] = 'bbl-job-edit';\n\t\t\t$classes[] = 'bbl-job-edit-term';\n\t\t} else { // Translation does not exist\n\t\t\t$default_term = (int) $_GET[ 'tag_ID' ];\n\t\t\t$href = bbl_get_new_term_translation_url( $default_term, $lang->code, $this->screen->taxonomy );\n\t\t\t$title = sprintf( __( 'Create for %s', 'babble' ), $lang->display_name );\n\t\t\t$classes[] = 'bbl-add';\n\t\t\t$classes[] = 'bbl-add-term';\n\t\t}\n\t\t$href = apply_filters( 'bbl_switch_admin_term_link', $href, $lang, $this->translations );\n\t\t$classes[] = \"bbl-lang-$lang->code bbl-lang-$lang->url_prefix\";\n\t\t$classes[] = 'bbl-admin';\n\t\t$classes[] = 'bbl-admin-taxonomy';\n\t\t$classes[] = 'bbl-admin-edit-term';\n\t\t$classes[] = 'bbl-lang';\n\t\tif ( $lang->code == bbl_get_current_lang_code() )\n\t\t\t$classes[] = 'bbl-active';\n\t\t$this->links[ $lang->code ] = array(\n\t\t\t'classes' => $classes,\n\t\t\t'href' => $href,\n\t\t\t'id' => $lang->url_prefix,\n\t\t\t'meta' => array( 'class' => strtolower( join( ' ', array_unique( $classes ) ) ) ),\n\t\t\t'title' => $title,\n\t\t\t'lang' => $lang,\n\t\t);\n\t}\n\n\t/**\n\t * Add an admin list terms screen link to the parent link provided (by reference).\n\t *\n\t * @param object $lang A Babble language object for this link\n\t * @return void\n\t **/\n\tprotected function add_admin_list_terms_link( $lang ) {\n\t\t$classes = array();\n\t\t$args = array( \n\t\t\t'lang' => $lang->code, \n\t\t\t'taxonomy' => bbl_get_taxonomy_in_lang( $this->screen->taxonomy, $lang->code ),\n\t\t);\n\t\t$href = add_query_arg( $args );\n\t\t$href = apply_filters( 'bbl_switch_admin_list_terms_link', $href, $lang );\n\t\t$title = sprintf( __( 'Switch to %s', 'babble' ), $lang->display_name );\n\t\t$classes[] = \"bbl-lang-$lang->code bbl-lang-$lang->url_prefix\";\n\t\t$classes[] = 'bbl-admin';\n\t\t$classes[] = 'bbl-admin-taxonomy';\n\t\t$classes[] = 'bbl-admin-list-terms';\n\t\t$classes[] = 'bbl-lang';\n\t\tif ( $lang->code == bbl_get_current_lang_code() )\n\t\t\t$classes[] = 'bbl-active';\n\n\t\t$this->links[ $lang->code ] = array(\n\t\t\t'classes' => $classes,\n\t\t\t'href' => $href,\n\t\t\t'id' => $lang->url_prefix,\n\t\t\t'meta' => array( 'class' => strtolower( join( ' ', array_unique( $classes ) ) ) ),\n\t\t\t'title' => $title,\n\t\t\t'lang' => $lang,\n\t\t);\n\t}\n\n\t/**\n\t * Add an admin post link to the parent link provided (by reference)\n\t *\n\t * @param object $lang A Babble language object for this link\n\t * @return void\n\t **/\n\tprotected function add_admin_post_link( $lang ) {\n\t\t$classes = array();\n\t\tif ( isset( $this->translations[ $lang->code ]->ID ) ) { // Translation exists\n\t\t\t$href = add_query_arg( array( 'lang' => $lang->code, 'post' => $this->translations[ $lang->code ]->ID ) );\n\t\t\t$href = remove_query_arg( 'message', $href );\n\t\t\t$title = sprintf( __( 'Switch to %s', 'babble' ), $lang->display_name );\n\t\t\t$classes[] = 'bbl-existing-edit';\n\t\t\t$classes[] = 'bbl-existing-edit-post';\n\t\t} else if ( isset( $this->jobs[ $lang->code ]->ID ) ) { // Translation job exists\n\t\t\t$href = add_query_arg( array( 'lang' => $lang->code, 'post' => $this->jobs[ $lang->code ]->ID ) );\n\t\t\t$href = remove_query_arg( 'message', $href );\n\t\t\t$title = sprintf( _x( '%s: %s', 'Translation job status and language (example: In Progress: French)', 'babble' ), get_post_status_object( $this->jobs[ $lang->code ]->post_status )->label, $lang->display_name );\n\t\t\t$classes[] = 'bbl-job-edit';\n\t\t\t$classes[] = 'bbl-job-edit-post';\n\t\t} else { // Translation does not exist\n\t\t\tif ( isset( $this->translations[ bbl_get_default_lang_code() ] ) ) {\n\t\t\t\t$default_post = $this->translations[ bbl_get_default_lang_code() ];\n\t\t\t\t$href = bbl_get_new_post_translation_url( $default_post, $lang->code );\n\t\t\t\t$title = sprintf( __( 'Create for %s', 'babble' ), $lang->display_name );\n\t\t\t\t$classes[] = 'bbl-add';\n\t\t\t\t$classes[] = 'bbl-add-post';\n\t\t\t} else {\n\t\t\t\treturn; // Don't create the switcher menu items yet\n\t\t\t}\n\t\t}\n\t\t$href = apply_filters( 'bbl_switch_admin_post_link', $href, $lang, $this->translations );\n\t\t$classes[] = \"bbl-lang-$lang->code bbl-lang-$lang->url_prefix\";\n\t\t$classes[] = 'bbl-admin';\n\t\t$classes[] = 'bbl-admin-edit-post';\n\t\t$classes[] = 'bbl-admin-post-type';\n\t\t$classes[] = 'bbl-lang';\n\t\t$classes[] = 'bbl-post';\n\t\tif ( $lang->code == bbl_get_current_lang_code() )\n\t\t\t$classes[] = 'bbl-active';\n\t\t$this->links[ $lang->code ] = array(\n\t\t\t'classes' => $classes,\n\t\t\t'href' => $href,\n\t\t\t'id' => $lang->url_prefix,\n\t\t\t'meta' => array( 'class' => strtolower( join( ' ', array_unique( $classes ) ) ) ),\n\t\t\t'title' => $title,\n\t\t\t'lang' => $lang,\n\t\t);\n\t}\n\n\t/**\n\t * Add an admin list posts screen link to the parent link provided (by reference).\n\t *\n\t * @param object $lang A Babble language object for this link\n\t * @return void\n\t **/\n\tprotected function add_admin_list_posts_link( $lang ) {\n\t\t$classes = array();\n\t\t$args = array( \n\t\t\t'lang' => $lang->code, \n\t\t\t'post_type' => bbl_get_post_type_in_lang( $this->screen->post_type, $lang->code ),\n\t\t);\n\t\t$href = add_query_arg( $args );\n\t\t$href = apply_filters( 'bbl_switch_admin_list_posts_link', $href, $lang );\n\t\t$title = sprintf( __( 'Switch to %s', 'babble' ), $lang->display_name );\n\t\t$classes[] = \"bbl-lang-$lang->code bbl-lang-$lang->url_prefix\";\n\t\t$classes[] = 'bbl-admin';\n\t\t$classes[] = 'bbl-admin-edit-post';\n\t\t$classes[] = 'bbl-admin-post-type';\n\t\t$classes[] = 'bbl-lang';\n\t\tif ( $lang->code == bbl_get_current_lang_code() )\n\t\t\t$classes[] = 'bbl-active';\n\n\t\t$this->links[ $lang->code ] = array(\n\t\t\t'classes' => $classes,\n\t\t\t'href' => $href,\n\t\t\t'id' => $lang->url_prefix,\n\t\t\t'meta' => array( 'class' => strtolower( join( ' ', array_unique( $classes ) ) ) ),\n\t\t\t'title' => $title,\n\t\t\t'lang' => $lang,\n\t\t);\n\t}\n\n\t/**\n\t * Add a post link to the parent link provided (by reference)\n\t *\n\t * @param object $lang A Babble language object for this link\n\t * @return void\n\t **/\n\tprotected function add_post_link( $lang ) {\n\t\t$classes = array();\n\t\tif ( isset( $this->translations[ $lang->code ] ) ) { // Translation exists\n\t\t\t// Don't add this link if the user cannot edit THIS post and\n\t\t\t// the language is not public.\n\t\t\tif ( \n\t\t\t\t! bbl_is_public_lang( $lang->code ) && \n\t\t\t\t! current_user_can( 'edit_post', $this->translations[ $lang->code ]->ID ) \n\t\t\t) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tbbl_switch_to_lang( $lang->code );\n\t\t\t$href = get_permalink( $this->translations[ $lang->code ]->ID );\n\t\t\tbbl_restore_lang();\n\t\t\t$title = sprintf( __( 'Switch to %s', 'babble' ), $lang->display_name );\n\t\t\t$classes[] = 'bbl-existing';\n\t\t\t$classes[] = 'bbl-existing-post';\n\t\t} else if ( current_user_can( 'edit_post', $this->translations[ bbl_get_default_lang_code() ]->ID ) ) {\n\t\t\t// Generate a URL to create the translation\n\t\t\t$default_post = $this->translations[ bbl_get_default_lang_code() ];\n\t\t\t$href = bbl_get_new_post_translation_url( $default_post, $lang->code );\n\t\t\t$title = sprintf( __( 'Create for %s', 'babble' ), $lang->display_name );\n\t\t\t$classes[] = 'bbl-add';\n\t\t\t$classes[] = 'bbl-add-post';\n\t\t} else {\n\t\t\t// Don't show links to non-public languages\n\t\t\tif ( ! bbl_is_public_lang( $lang->code ) )\n\t\t\t\treturn;\n\n\t\t\t// Show a blank link for unavailable translations\n\t\t\t$href = false;\n\t\t\t$title = sprintf( __( 'This content is unavailable in %s', 'babble' ), $lang->display_name );\n\t\t\t$classes[] = 'bbl-unavailable';\n\t\t}\n\t\t$href = apply_filters( 'bbl_switch_post_link', $href, $lang, $this->translations );\n\t\t$classes[] = \"bbl-lang-$lang->code bbl-lang-$lang->url_prefix\";\n\t\t$classes[] = 'bbl-lang';\n\t\t$classes[] = 'bbl-post';\n\t\tif ( $lang->code == bbl_get_current_lang_code() )\n\t\t\t$classes[] = 'bbl-active';\n\t\t$this->links[ $lang->code ] = array(\n\t\t\t'classes' => $classes,\n\t\t\t'href' => $href,\n\t\t\t'id' => $lang->url_prefix,\n\t\t\t'meta' => array( 'class' => strtolower( join( ' ', array_unique( $classes ) ) ) ),\n\t\t\t'title' => $title,\n\t\t\t'lang' => $lang,\n\t\t);\n\t}\n\n\t/**\n\t * Add a link to a language specific front page.\n\t *\n\t * @TODO: Is this any different from a regular post link?\n\t *\n\t * @param object $lang A Babble language object for this link\n\t * @return void\n\t **/\n\tprotected function add_front_page_link( $lang ) {\n\t\tglobal $bbl_locale;\n\t\t$classes = array();\n\t\tremove_filter( 'home_url', array( $bbl_locale, 'home_url'), null, 2 );\n\t\t$href = home_url( \"$lang->url_prefix/\" );\n\t\tadd_filter( 'home_url', array( $bbl_locale, 'home_url'), null, 2 );\n\t\t$href = apply_filters( 'bbl_switch_front_page_link', $href, $lang );\n\t\t$title = sprintf( __( 'Switch to %s', 'babble' ), $lang->display_name );\n\t\t$classes[] = \"bbl-lang-$lang->code bbl-lang-$lang->url_prefix\";\n\t\t$classes[] = 'bbl-existing';\n\t\t$classes[] = 'bbl-front-page';\n\t\t$classes[] = 'bbl-lang';\n\t\tif ( $lang->code == bbl_get_current_lang_code() )\n\t\t\t$classes[] = 'bbl-active';\n\t\t$this->links[ $lang->code ] = array(\n\t\t\t'classes' => $classes,\n\t\t\t'id' => $lang->url_prefix,\n\t\t\t'href' => $href,\n\t\t\t'meta' => array( 'class' => strtolower( join( ' ', array_unique( $classes ) ) ) ),\n\t\t\t'title' => $title,\n\t\t\t'lang' => $lang,\n\t\t);\n\t}\n\n\t/**\n\t * Add a link to a post_type archive.\n\t *\n\t * @param object $lang A Babble language object for this link\n\t * @return void\n\t **/\n\tprotected function add_post_type_archive_link( $lang ) {\n\t\t$classes = array();\n\t\tbbl_switch_to_lang( $lang->code );\n\t\t$href = get_post_type_archive_link( get_query_var( 'post_type' ) );\n\t\tbbl_restore_lang();\n\t\t$href = apply_filters( 'bbl_switch_post_type_archive_link', $href, $lang );\n\t\t$title = sprintf( __( 'Switch to %s', 'babble' ), $lang->display_name );\n\t\t$classes[] = \"bbl-lang-$lang->code bbl-lang-$lang->url_prefix\";\n\t\t$classes[] = 'bbl-existing';\n\t\t$classes[] = 'bbl-post-type-archive';\n\t\t$classes[] = 'bbl-lang';\n\t\tif ( $lang->code == bbl_get_current_lang_code() )\n\t\t\t$classes[] = 'bbl-active';\n\t\t$this->links[ $lang->code ] = array(\n\t\t\t'classes' => $classes,\n\t\t\t'id' => $lang->url_prefix,\n\t\t\t'href' => $href,\n\t\t\t'meta' => array( 'class' => strtolower( join( ' ', array_unique( $classes ) ) ) ),\n\t\t\t'title' => $title,\n\t\t\t'lang' => $lang,\n\t\t);\n\t}\n\n\t/**\n\t * Add a link to a taxonomy archive.\n\t *\n\t * @param object $lang A Babble language object for this link\n\t * @return void\n\t **/\n\tprotected function add_taxonomy_archive_link( $lang ) {\n\t\t$classes = array();\n\t\t$queried_object = get_queried_object();\n\t\tif ( ! bbl_is_translated_taxonomy( $queried_object->taxonomy ) ) {\n\t\t\t$this->add_arbitrary_link( $lang );\n\t\t\treturn;\n\t\t} elseif ( isset( $this->translations[ $lang->code ]->term_id ) ) { // Translation exists\n\t\t\tbbl_switch_to_lang( $lang->code );\n\t\t\t$href = get_term_link( $this->translations[ $lang->code ], bbl_get_base_taxonomy( $queried_object->taxonomy ) );\n\t\t\tbbl_restore_lang();\n\t\t\t$title = sprintf( __( 'Switch to %s', 'babble' ), $lang->display_name );\n\t\t\t$classes[] = 'bbl-existing';\n\t\t\t$classes[] = 'bbl-existing-term';\n\t\t} else { // Translation does not exist\n\t\t\t// Generate a URL to create the translation\n\t\t\t$default_term = $this->translations[ bbl_get_default_lang_code() ];\n\t\t\t$href = bbl_get_new_term_translation_url( $default_term->term_id, $lang->code, $default_term->taxonomy );\n\t\t\t$title = sprintf( __( 'Create for %s', 'babble' ), $lang->display_name );\n\t\t\t$classes[] = 'bbl-add';\n\t\t\t$classes[] = 'bbl-add-term';\n\t\t}\n\t\t$href = apply_filters( 'bbl_switch_taxonomy_archive_link', $href, $lang, $this->translations );\n\t\t$classes[] = \"bbl-lang-$lang->code bbl-lang-$lang->url_prefix\";\n\t\t$classes[] = 'bbl-lang';\n\t\t$classes[] = 'bbl-term';\n\t\tif ( $lang == bbl_get_current_lang_code() )\n\t\t\t$classes[] = 'bbl-active';\n\t\t$this->links[ $lang->code ] = array(\n\t\t\t'classes' => $classes,\n\t\t\t'href' => $href,\n\t\t\t'id' => $lang->url_prefix,\n\t\t\t'meta' => array( 'class' => strtolower( join( ' ', array_unique( $classes ) ) ) ),\n\t\t\t'title' => $title,\n\t\t\t'lang' => $lang,\n\t\t);\n\t}\n\n\t/**\n\t * Add a link to an arbitrary link, e.g. 404, within the site.\n\t *\n\t * @param object $lang A Babble language object for this link\n\t * @return void\n\t **/\n\tprotected function add_arbitrary_link( $lang ) {\n\t\t$classes = array();\n\t\tif ( ! preg_match( '|^/[^/]+/(.*)?|', $_SERVER[ 'REQUEST_URI' ], $matches ) )\n\t\t\treturn;\n\t\tbbl_switch_to_lang( $lang->code );\n\t\t$href = home_url( $matches[ 1 ] );\n\t\tbbl_restore_lang();\n\t\t$href = apply_filters( 'bbl_switch_arbitrary_link', $href, $lang );\n\t\t$title = sprintf( __( 'Switch to %s', 'babble' ), $lang->display_name );\n\t\t$classes[] = 'bbl-existing';\n\t\t$classes[] = 'bbl-existing-term';\n\t\t$classes[] = \"bbl-lang-$lang->code bbl-lang-$lang->url_prefix\";\n\t\t$classes[] = 'bbl-lang';\n\t\t$classes[] = 'bbl-term';\n\t\tif ( $lang == bbl_get_current_lang_code() )\n\t\t\t$classes[] = 'bbl-active';\n\t\t$this->links[ $lang->code ] = array(\n\t\t\t'classes' => $classes,\n\t\t\t'href' => $href,\n\t\t\t'id' => $lang->url_prefix,\n\t\t\t'meta' => array( 'class' => strtolower( join( ' ', array_unique( $classes ) ) ) ),\n\t\t\t'title' => $title,\n\t\t\t'lang' => $lang,\n\t\t);\n\t}\n\t\n}\n\nglobal $bbl_switcher_menu;\n$bbl_switcher_menu = new Babble_Switcher_Menu();\n"
  },
  {
    "path": "class-switcher-interface.php",
    "content": "<?php\r\n\r\n/**\r\n * Class for providing the user interface switching option on the user profile screen\r\n *\r\n * @package Babble\r\n * @since 1.4\r\n */\r\nclass Babble_Switcher_Interface extends Babble_Plugin {\r\n\t\r\n\t// PUBLIC METHODS\r\n\t// ==============\r\n\t\r\n\tpublic function __construct() {\r\n\t\t$this->setup( 'babble-switcher-interface', 'plugin' );\r\n\r\n\t\t$this->add_action( 'personal_options' );\r\n\t}\r\n\r\n\tpublic function personal_options( WP_User $user ) {\r\n\t\t$langs   = bbl_get_active_langs();\r\n\t\t$current = bbl_get_current_interface_lang_code();\r\n\r\n\t\tif ( empty( $langs ) )\r\n\t\t\treturn;\r\n\r\n\t\t$vars = compact( 'langs', 'current' );\r\n\t\t$this->render_admin( 'switcher-interface.php', $vars );\r\n\r\n\t}\r\n\r\n}\r\n\r\nglobal $bbl_switcher_interface;\r\n$bbl_switcher_interface = new Babble_Switcher_Interface;\r\n"
  },
  {
    "path": "class-taxonomy.php",
    "content": "<?php\n\n/**\n * Manages the translations for taxonomies.\n *\n * @package Babble\n * @since Alpha 1.2\n */\nclass Babble_Taxonomies extends Babble_Plugin {\n\t\n\t/**\n\t * A simple flag to stop infinite recursion in various places.\n\t *\n\t * @var boolean\n\t **/\n\tprotected $no_recursion;\n\t\n\t/**\n\t * The current version for purposes of rewrite rules, any \n\t * DB updates, cache busting, etc\n\t *\n\t * @var int\n\t **/\n\tprotected $version = 1;\n\n\t/**\n\t * The shadow taxonomies created to handle the translated terms.\n\t *\n\t * @var array\n\t **/\n\tprotected $taxonomies;\n\n\t/**\n\t * The languages represented by each of the shadow taxonomies.\n\t *\n\t * @var array\n\t **/\n\tprotected $lang_map;\n\t\n\t/**\n\t * Setup any add_action or add_filter calls. Initiate properties.\n\t *\n\t * @return void\n\t **/\n\tpublic function __construct() {\n\t\t$this->setup( 'babble-taxonomy', 'plugin' );\n\t\t$this->add_action( 'bbl_created_new_shadow_post', 'created_new_shadow_post', null, 2 );\n\t\t$this->add_action( 'bbl_registered_shadow_post_types', 'registered_shadow_post_types' );\n\t\t$this->add_action( 'init', 'init_early', 0 );\n\t\t$this->add_action( 'parse_request' );\n\t\t$this->add_action( 'registered_taxonomy', null, null, 3 );\n\t\t$this->add_action( 'save_post', null, null, 2 );\n\t\t$this->add_action( 'set_object_terms', null, null, 5 );\n\t\t$this->add_filter( 'get_terms' );\n\t\t$this->add_filter( 'term_link', null, null, 3 );\n\t\t$this->add_filter( 'bbl_translated_taxonomy', null, null, 2 );\n\t\t$this->add_filter( 'body_class', null, null, 2 );\n\t\t$this->add_filter( 'taxonomy_template' );\n\t\t$this->add_filter( 'admin_body_class' );\n\n\t}\n\t\n\t// WP HOOKS\n\t// ========\n\n\t/**\n\t * Hooks the WP init action early\n\t *\n\t * @return void\n\t **/\n\tpublic function init_early() {\n\t\t// This translation will connect each term with it's translated equivalents\n\t\tregister_taxonomy( 'term_translation', array(), array(\n\t\t\t'rewrite' => false,\n\t\t\t'public' => false,\n\t\t\t'show_ui' => false,\n\t\t\t'show_in_nav_menus' => false,\n\t\t\t'label' => __( 'Term Translation ID', 'babble' ),\n\t\t) );\n\t}\n\t\n\t/**\n\t * Hooks the WP registered_taxonomy action \n\t *\n\t * @param string $taxonomy The name of the newly registered taxonomy \n\t * @param string|array $args The object_type(s)\n\t * @param array $args The args passed to register the taxonomy\n\t * @return void\n\t **/\n\tpublic function registered_taxonomy( $taxonomy, $object_type, $args ) {\n\t\tif ( in_array( $taxonomy, $this->ignored_taxonomies() )  ) {\n\t\t\treturn;\n\t\t}\n\n\t\tif ( $this->no_recursion ) {\n\t\t\treturn;\n\t\t}\n\n\t\t$this->no_recursion = true;\n\n\t\tif ( ! is_array( $object_type ) ) {\n\t\t\t$object_type = array_unique( (array) $object_type );\n\t\t}\n\n\t\t// Use the Babble term counting function, unless the taxonomy registrant\n\t\t// has defined their own – in which case we'll just have to hope against \n\t\t// hope that it's Babble aware :S\n\t\t// FIXME: Setting this in the following fashion seems hacky… I feel uncomfortable.\n\t\tif ( empty( $GLOBALS[ 'wp_taxonomies' ][ $taxonomy ]->update_count_callback ) ) {\n\t\t\t$GLOBALS[ 'wp_taxonomies' ][ $taxonomy ]->update_count_callback = array( $this, 'update_post_term_count' );\n\t\t}\n\n\t\t// Untranslated taxonomies do not have shadow equivalents in each language,\n\t\t// but do apply to the bast post_type and all it's shadow post_types.\n\t\tif ( ! $this->is_taxonomy_translated( $taxonomy ) ) {\n\t\t\t// Apply this taxonomy to all the shadow post types\n\t\t\t// of all of the base post_types it applies to.\n\t\t\tforeach ( $object_type as $ot ) {\n\t\t\t\tif ( ! ( $base_post_type = bbl_get_base_post_type( $ot ) ) ) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\t$shadow_post_types = bbl_get_shadow_post_types( $base_post_type );\n\t\t\t\tforeach ( $shadow_post_types as $shadow_post_type ) {\n\t\t\t\t\tregister_taxonomy_for_object_type( $taxonomy, $shadow_post_type );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t$this->no_recursion = false;\n\t\t\treturn;\n\t\t}\n\n\t\t// @FIXME: Not sure this is the best way to specify languages\n\t\t$langs = bbl_get_active_langs();\n\n\t\t// Lose the default language as any existing taxonomies are in that language\n\t\tunset( $langs[ bbl_get_default_lang_url_prefix() ] );\n\n\t\t// @FIXME: Is it reckless to convert ALL object instances in $args to an array?\n\t\tforeach ( $args as $key => & $arg ) {\n\t\t\tif ( is_object( $arg ) )\n\t\t\t\t$arg = get_object_vars( $arg );\n\t\t\t// Don't set any args reserved for built-in post_types\n\t\t\tif ( '_' == substr( $key, 0, 1 ) )\n\t\t\t\tunset( $args[ $key ] );\n\t\t}\n\n\t\t#$args[ 'rewrite' ] = false;\n\t\tunset( $args[ 'name' ] );\n\t\tunset( $args[ 'object_type' ] );\n\n\t\t$slug = ( $args[ 'rewrite' ][ 'slug' ] ) ? $args[ 'rewrite' ][ 'slug' ] : $taxonomy;\n\n\t\tforeach ( $langs as $lang ) {\n\t\t\t$new_args = $args;\n\t\t\t$new_object_type = array();\n\t\t\t// N.B. Here we assume that the taxonomy is on a post type\n\t\t\tforeach( $object_type as $ot )\n\t\t\t\t$new_object_type[] = bbl_get_post_type_in_lang( $ot, $lang->code );\n\n\t\t\tif ( false !== $args[ 'rewrite' ] ) {\n\t\t\t\tif ( ! is_array( $new_args[ 'rewrite' ] ) )\n\t\t\t\t\t$new_args[ 'rewrite' ] = array();\n\t\t\t\t// Do I not need to add this query_var into the query_vars filter? It seems not.\n\t\t\t\t$new_args[ 'query_var' ] = $new_args[ 'rewrite' ][ 'slug' ] = $this->get_slug_in_lang( $slug, $lang->code );\n\t\t\t}\n\n\t\t\t// @FIXME: Note currently we are in danger of a taxonomy name being longer than 32 chars\n\t\t\t// Perhaps we need to create some kind of map like (taxonomy) + (lang) => (shadow translated taxonomy)\n\t\t\t$new_taxonomy = strtolower( \"{$taxonomy}_{$lang->code}\" );\n\n\t\t\t$this->taxonomies[ $new_taxonomy ] = $taxonomy;\n\t\t\tif ( ! isset( $this->lang_map[ $lang->code ] ) || ! is_array( $this->lang_map[ $lang->code ] ) )\n\t\t\t\t$this->lang_map[ $lang->code ] = array();\n\t\t\t$this->lang_map[ $lang->code ][ $taxonomy ] = $new_taxonomy;\n\t\t\t\n\t\t\tregister_taxonomy( $new_taxonomy, $new_object_type, $new_args );\n\t\t\t\n\t\t}\n\t\t// bbl_stop_logging();\n\n\t\t$this->no_recursion = false;\n\t}\n\n\tpublic function ignored_taxonomies() {\n\t\treturn array( 'post_translation', 'term_translation' );\n\t}\n\n\tpublic function is_taxonomy_translated( $taxonomy ) {\n\t\tif( in_array( $taxonomy, $this->ignored_taxonomies() ) ) {\n\t\t\treturn false;\n\t\t}\n\n\t\t// @FIXME: Remove this when menu's are translatable\n\t\tif( 'nav_menu' == $taxonomy ) {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn apply_filters( 'bbl_translated_taxonomy', true, $taxonomy );\n\t}\n\n\t/**\n\t * Hooks the WP bbl_registered_shadow_post_types action to check that we've applied\n\t * all untranslated taxonomies to the shadow post types created for this base\n\t * post type. \n\t * \n\t * @param string $post_type The post type for which the shadow post types have been registered. \n\t * @return void\n\t **/\n\tpublic function registered_shadow_post_types( $post_type ) {\n\t\t$taxonomies = get_object_taxonomies( $post_type );\n\n\t\t$object_type = (array) $post_type;\n\t\t\n\t\tforeach ( $taxonomies as $taxonomy ) {\n\t\t\t// Untranslated taxonomies do not have shadow equivalents in each language,\n\t\t\t// but do apply to the bast post_type and all it's shadow post_types.\n\t\t\tif ( ! $this->is_taxonomy_translated( $taxonomy ) ) {\n\t\t\t\t// Apply this taxonomy to all the shadow post types\n\t\t\t\t// of all of the base post_types it applies to.\n\t\t\t\tforeach ( $object_type as $ot ) {\n\t\t\t\t\tif ( ! ( $base_post_type = bbl_get_base_post_type( $ot ) ) ) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\t$shadow_post_types = bbl_get_shadow_post_types( $base_post_type );\n\t\t\t\t\tforeach ( $shadow_post_types as $shadow_post_type ) {\n\t\t\t\t\t\tregister_taxonomy_for_object_type( $taxonomy, $shadow_post_type );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Hooks the Babble action bbl_created_new_shadow_post, which is fired\n\t * when a new translation post is created, to sync any existing untranslated\n\t * taxonomy terms.\n\t *\n\t * @param int $new_post_id The ID of the new post (to sync to)\n\t * @param int $origin_post_id The ID of the originating post (to sync from)\n\t * @return void\n\t **/\n\tpublic function created_new_shadow_post( $new_post_id, $origin_post_id ) {\n\t\t$new_post = get_post( $new_post_id );\n\t\tif ( ! ( $origin_post = get_post( $origin_post_id ) ) ) {\n\t\t\treturn;\n\t\t}\n\t\t\n\t\tif ( $this->no_recursion ) {\n\t\t\treturn;\n\t\t}\n\t\t$this->no_recursion = true;\n\t\t\n\t\t$taxonomies = get_object_taxonomies( $origin_post->post_type );\n\n\t\tforeach ( $taxonomies as $taxonomy ) {\n\t\t\tif ( ! $this->is_taxonomy_translated( $taxonomy ) ) {\n\t\t\t\t$term_ids = wp_get_object_terms( $origin_post->ID, $taxonomy, array( 'fields' => 'ids' ) );\n\t\t\t\t$term_ids = array_map( 'absint', $term_ids );\n\t\t\t\twp_set_object_terms( $new_post->ID, $term_ids, $taxonomy );\n\t\t\t}\n\t\t}\n\n\t\t$this->no_recursion = false;\n\t}\n\n\t/**\n\t * Hooks the WP save_post action to resync data\n\t * when requested.\n\t *\n\t * @param int $post_id The ID of the WP post\n\t * @param object $post The WP Post object \n\t * @return void\n\t **/\n\tpublic function save_post( $post_id, $post ) {\n\t\t$this->maybe_resync_terms( $post_id, $post );\n\t}\n\n\t/**\n\t * Hooks the WordPress term_link filter to provide functions to provide\n\t * appropriate links for the shadow taxonomies. \n\t *\n\t * @see get_term_link from whence much of this was copied\n\t *\n\t * @param string $termlink The currently generated term URL\n\t * @param object $term The WordPress term object we're generating a link for\n\t * @param string $taxonomy The \n\t * @return string The term link\n\t **/\n\tpublic function term_link( $termlink, $term, $taxonomy ) {\n\t\t$taxonomy = strtolower( $taxonomy );\n\t\t// No need to worry about the built in taxonomies\n\t\tif ( 'post_tag' == $taxonomy || 'category' == $taxonomy || ! isset( $this->taxonomies[ $taxonomy ] ) ) {\n\t\t\treturn $termlink;\n\t\t}\n\t\n\t\t// Deal with our shadow taxonomies\n\t\tif ( ! ( $base_taxonomy = $this->get_base_taxonomy( $taxonomy ) ) ) {\n\t\t\treturn $termlink;\n\t\t}\n\t\n\t\t// START copying from get_term_link, replacing $taxonomy with $base_taxonomy\n\t\tglobal $wp_rewrite;\n\t\n\t\tif ( !is_object($term) ) {\n\t\t\tif ( is_int($term) ) {\n\t\t\t\t$term = &get_term($term, $base_taxonomy);\n\t\t\t} else {\n\t\t\t\t$term = &get_term_by('slug', $term, $base_taxonomy);\n\t\t\t}\n\t\t}\n\t\n\t\tif ( !is_object($term) ) {\n\t\t\t$term = new WP_Error('invalid_term', __('Empty Term', 'babble'));\n\t\t}\n\t\n\t\tif ( is_wp_error( $term ) ) {\n\t\t\treturn $term;\n\t\t}\n\t\n\t\t$termlink = $wp_rewrite->get_extra_permastruct($base_taxonomy);\n\t\n\t\t$slug = $term->slug;\n\t\t$t = get_taxonomy($base_taxonomy);\n\t\n\t\tif ( empty($termlink) ) {\n\t\t\tif ( 'category' == $base_taxonomy ) {\n\t\t\t\t$termlink = '?cat=' . $term->term_id;\n\t\t\t} elseif ( $t->query_var ) {\n\t\t\t\t$termlink = \"?$t->query_var=$slug\";\n\t\t\t} else {\n\t\t\t\t$termlink = \"?taxonomy=$base_taxonomy&term=$slug\";\n\t\t\t}\n\t\t\t$termlink = home_url($termlink);\n\t\t} else {\n\t\t\tif ( $t->rewrite['hierarchical'] ) {\n\t\t\t\t$hierarchical_slugs = array();\n\t\t\t\t$ancestors = get_ancestors($term->term_id, $base_taxonomy);\n\t\t\t\tforeach ( (array)$ancestors as $ancestor ) {\n\t\t\t\t\t$ancestor_term = get_term($ancestor, $base_taxonomy);\n\t\t\t\t\t$hierarchical_slugs[] = $ancestor_term->slug;\n\t\t\t\t}\n\t\t\t\t$hierarchical_slugs = array_reverse($hierarchical_slugs);\n\t\t\t\t$hierarchical_slugs[] = $slug;\n\t\t\t\t$termlink = str_replace(\"%$base_taxonomy%\", implode('/', $hierarchical_slugs), $termlink);\n\t\t\t} else {\n\t\t\t\t$termlink = str_replace(\"%$base_taxonomy%\", $slug, $termlink);\n\t\t\t}\n\t\t\t$termlink = home_url( user_trailingslashit($termlink, 'category') );\n\t\t}\n\t\t// STOP copying from get_term_link\n\t\n\t\treturn $termlink;\n\t}\n\n\t/**\n\t * Hooks the WP get_terms filter to ensure the terms all have transids.\n\t *\n\t * @param array $terms The terms which have been got \n\t * @return array The terms which were got\n\t **/\n\tpublic function get_terms( $terms ) {\n\t\tforeach ( $terms as $term ) {\n\t\t\tif ( empty( $term ) ) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif ( isset( $this->taxonomies ) ) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif ( isset( $this->taxonomies[ $term->taxonomy ] ) ) {\n\t\t\t\tif ( ! $this->get_transid( $term->term_id ) ) {\n\t\t\t\t\tthrow new exception( \"ERROR: Translated term ID $term->term_id does not have a transid\" );\n\t\t\t\t} else {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif ( ! $this->get_transid( $term->term_id ) ) {\n\t\t\t\t$this->set_transid( $term->term_id );\n\t\t\t}\n\t\t}\n\t\treturn $terms;\n\t}\n\n\t/**\n\t * Hooks the WP parse_request action \n\t *\n\t * FIXME: Should I be extending and replacing the WP class?\n\t *\n\t * @param object $wp WP object, passed by reference (so no need to return)\n\t * @return void\n\t **/\n\tpublic function parse_request( $wp ) {\n\n\t\tif ( is_admin() ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Sequester the original query, in case we need it to get the default content later\n\t\tif ( ! isset( $wp->query_vars[ 'bbl_tax_original_query' ] ) ) {\n\t\t\t$wp->query_vars[ 'bbl_tax_original_query' ] = $wp->query_vars;\n\t\t}\n\n\t\t$taxonomy \t= false;\n\t\t$terms \t\t= false;\n\n\t\t$taxonomies = get_taxonomies( null, 'objects' );\n\t\t$lang_taxonomies = array();\n\t\tforeach ( $taxonomies as $taxonomy => $tax_obj ) {\n\t\t\t$tax = $this->get_taxonomy_in_lang( $taxonomy, bbl_get_current_lang_code() );\n\t\t\t$lang_taxonomies[ $tax_obj->rewrite[ 'slug' ] ] = $tax;\n\t\t}\n\n\t\tif ( isset( $wp->query_vars[ 'tag' ] ) ) {\n\t\t\t$taxonomy = $this->get_taxonomy_in_lang( 'post_tag', $wp->query_vars[ 'lang' ] );\n\t\t\t$terms = $wp->query_vars[ 'tag' ];\n\t\t\tunset( $wp->query_vars[ 'tag' ] );\n\t\t} else if ( isset( $wp->query_vars[ 'category_name' ] ) ) {\n\t\t\t$taxonomy = $this->get_taxonomy_in_lang( 'category', $wp->query_vars[ 'lang' ] );\n\t\t\t$terms = $wp->query_vars[ 'category_name' ];\n\t\t\tunset( $wp->query_vars[ 'category_name' ] );\n\t\t} else {\n\t\t\t$taxonomies = array();\n\t\t\tforeach ( $lang_taxonomies as $slug => $tax ) {\n\t\t\t\tif ( isset( $wp->query_vars[ $slug ] ) ) {\n\t\t\t\t\t$taxonomies[] = $tax;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\t\n\t\t\tif ( $taxonomies ) {\n\t\t\t\t$post_types = array();\n\t\t\t\tforeach ( $taxonomies as $taxonomy ) {\n\t\t\t\t\t$taxonomy = get_taxonomy( $taxonomy );\n\t\t\t\t\t$post_types = array_merge( $post_types, $taxonomy->object_type );\n\t\t\t\t\t// Filter out the post_types not in this language\n\t\t\t\t\tforeach ( $post_types as & $post_type ) {\n\t\t\t\t\t\t$post_type = bbl_get_post_type_in_lang( $post_type );\n\t\t\t\t\t}\n\t\t\t\t\t$post_types = array_unique( $post_types );\n\t\t\t\t}\n\t\t\t\t$wp->query_vars[ 'post_type' ] = $post_types;\n\t\t\t}\n\t\t}\n\n\t\tif ( $taxonomy && $terms ) {\n\n\t\t\tif ( ! isset( $wp->query_vars[ 'tax_query' ] ) || ! is_array( $wp->query_vars[ 'tax_query' ] ) ) {\n\t\t\t\t$wp->query_vars[ 'tax_query' ] = array();\n\t\t\t}\n\t\t\n\t\t\t$wp->query_vars[ 'tax_query' ][] = array(\n\t\t\t\t'taxonomy' => $taxonomy,\n\t\t\t\t'field' => 'slug',\n\t\t\t\t'terms' => $terms,\n\t\t\t);\n\t\t\n\t\t}\n\t}\n\n\t/**\n\t * Hooks the WP set_object_terms action to sync any untranslated\n\t * taxonomies across to the translations.\n\t *\n\t * @param int $object_id The object to relate to\n\t * @param array $terms The slugs or ids of the terms\n\t * @param array $tt_ids The term_taxonomy_ids\n\t * @param string $taxonomy The name of the taxonomy for which terms are being set\n\t * @param bool $append If false will delete difference of terms\n\t * @return void\n\t **/\n\tpublic function set_object_terms( $object_id, $terms, $tt_ids, $taxonomy, $append ) {\n\t\tif ( $this->no_recursion ) {\n\t\t\treturn;\n\t\t}\n\t\t$this->no_recursion = true;\n\n\t\t// DO NOT SYNC THE TRANSID TAXONOMIES!!\n\t\tif ( in_array( $taxonomy, $this->ignored_taxonomies() ) ) {\n\t\t\t$this->no_recursion = false;\n\t\t\treturn;\n\t\t}\n\n\t\tif ( $this->is_taxonomy_translated( $taxonomy ) ) {\n\t\t\t\n\t\t\t// Here we assume that this taxonomy is on a post type\n\t\t\t$translations = bbl_get_post_translations( $object_id );\n\n\t\t\tforeach ( $translations as $lang_code => & $translation ) {\n\n\t\t\t\tif ( bbl_get_post_lang_code( $object_id ) == $lang_code ) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t$translated_taxonomy = bbl_get_taxonomy_in_lang( $taxonomy, $lang_code );\n\t\t\t\t$translated_terms = array();\n\n\t\t\t\tforeach ( $terms as $term ) {\n\n\t\t\t\t\tif ( is_int( $term ) ) {\n\t\t\t\t\t\t$_term = get_term( $term, $taxonomy );\n\t\t\t\t\t} else {\n\t\t\t\t\t\t$_term = get_term_by( 'name', $term, $taxonomy );\n\t\t\t\t\t}\n\t\t\t\t\tif ( is_wp_error( $_term ) or empty( $_term ) ) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\t$translated_term = $this->get_term_in_lang( $_term->term_id, $taxonomy, $lang_code, false );\n\t\t\t\t\t$translated_terms[] = (int) $translated_term->term_id;\n\n\t\t\t\t}\n\n\t\t\t\t$result = wp_set_object_terms( $translation->ID, $translated_terms, $translated_taxonomy, $append );\n\t\t\t}\n\t\t\t\n\t\t} else {\n\n\t\t\t// Here we assume that this taxonomy is on a post type\n\t\t\t$translations = bbl_get_post_translations( $object_id );\n\t\t\tforeach ( $translations as $lang_code => & $translation ) {\n\t\t\t\tif ( bbl_get_post_lang_code( $object_id ) == $lang_code ) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\twp_set_object_terms( $translation->ID, $terms, $taxonomy, $append );\n\t\t\t}\n\n\t\t}\n\n\t\t$this->no_recursion = false;\n\t}\n\n\t/**\n\t * Hooks the WP body_class filter to add classes to the\n\t * body element.\n\t *\n\t * @param array $classes An array of class strings, poss with some indexes containing more than one space separated class \n\t * @param string|array $class One or more classes which have been added to the class list.\n\t * @return array An array of class strings, poss with some indexes containing more than one space separated class \n\t **/\n\tpublic function body_class( array $classes, $class ) {\n\t\tif ( is_tax() ) {\n\t\t\t$taxonomy      = get_queried_object();\n\t\t\t$base_taxonomy = bbl_get_term_in_lang( get_queried_object(), $taxonomy->taxonomy, bbl_get_default_lang_code() );\n\n\t\t\tif ( 'category' == $base_taxonomy->taxonomy ) {\n\t\t\t\t$classes[] = 'category';\n\n\t\t\t\tif ( isset( $base_taxonomy->term_id ) ) {\n\t\t\t\t\t$classes[] = 'category-' . sanitize_html_class( $base_taxonomy->slug, $base_taxonomy->term_id );\n\t\t\t\t\t$classes[] = 'category-' . $base_taxonomy->term_id;\n\t\t\t\t}\n\t\t\t} elseif ( 'post_tag' == $base_taxonomy->taxonomy ) {\n\t\t\t\t$classes[] = 'tag';\n\n\t\t\t\tif ( isset( $base_taxonomy->term_id ) ) {\n\t\t\t\t\t$classes[] = 'tag-' . sanitize_html_class( $base_taxonomy->slug, $base_taxonomy->term_id );\n\t\t\t\t\t$classes[] = 'tag-' . $base_taxonomy->term_id;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif ( isset( $base_taxonomy->term_id ) ) {\n\t\t\t\t\t$classes[] = 'tax-' . sanitize_html_class( $base_taxonomy->taxonomy );\n\t\t\t\t\t$classes[] = 'term-' . sanitize_html_class( $base_taxonomy->slug, $base_taxonomy->term_id );\n\t\t\t\t\t$classes[] = 'term-' . $base_taxonomy->term_id;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn $classes;\n\t}\n\n\t/**\n\t * Hooks the WP filter taxonomy_template to deal with the shadow terms,\n\t * ensuring they use the right template.\n\t *\n\t * @param string $template Path to a template file \n\t * @return Path to a template file\n\t **/\n\tpublic function taxonomy_template( $template ) {\n\t\tif( bbl_is_default_lang() ) {\n\t\t\treturn $template;\n\t\t}\n\n\t\t$term          = get_queried_object();\n\t\t$base_taxonomy = $this->get_base_taxonomy( $term->taxonomy );\n\n\t\tif ( 'category' == $base_taxonomy ) {\n\t\t\tif ( ! empty( $term->slug ) ) {\n\t\t\t\t$templates[] = \"category-{$term->slug}.php\";\n\t\t\t\t$templates[] = \"category-{$term->term_id}.php\";\n\t\t\t}\n\n\t\t\t$templates[] = 'category.php';\n\t\t}\n\t\telse if ( 'post_tag' == $base_taxonomy ) {\n\t\t\tif ( ! empty( $term->slug ) ) {\n\t\t\t\t$templates[] = \"tag-{$term->slug}.php\";\n\t\t\t\t$templates[] = \"tag-{$term->term_id}.php\";\n\t\t\t}\n\t\t\t$templates[] = 'tag.php';\n\t\t}\n\t\telse {\n\t\t\tif ( ! empty( $term->slug ) ) {\n\t\t\t\t$taxonomy    = $term->taxonomy;\n\t\t\t\t$templates[] = \"taxonomy-$taxonomy-{$term->slug}.php\";\n\t\t\t\t$templates[] = \"taxonomy-$taxonomy.php\";\n\t\t\t}\n\t\t\t$templates[] = 'taxonomy.php';\n\t\t}\n\n\t\t$template = get_query_template( 'bbl-taxonomy', $templates );\n\n\t\treturn $template;\n\t}\n\t\n\t// CALLBACKS\n\t// =========\n\t\n\t// PUBLIC METHODS\n\t// ==============\n\n\tpublic function admin_body_class( $class ) {\n\n\t\t$taxonomy = get_current_screen() ? get_current_screen()->taxonomy : null;\n\t\tif ( $taxonomy ) {\n\t\t\t$class .= ' bbl-taxonomy-' . $taxonomy;\n\t\t}\n\n\t\treturn $class;\n\n\t}\n\n\tpublic function bbl_translated_taxonomy( $translated, $taxonomy ) {\n\t\tif ( 'term_translation' == $taxonomy ) {\n\t\t\treturn false;\n\t\t}\n\t\tif ( 'nav_menu' == $taxonomy ) {\n\t\t\treturn false;\n\t\t}\n\t\tif ( 'link_category' == $taxonomy ) {\n\t\t\treturn false;\n\t\t}\n\t\tif ( 'post_format' == $taxonomy ) {\n\t\t\treturn false;\n\t\t}\n\t\treturn $translated;\n\t}\n\n\t/**\n\t * Provided with a taxonomy name, e.g. `post_tag`, and a language\n\t * code, will return the shadow taxonomy in that language.\n\t *\n\t * @param string $taxonomy The origin taxonomy \n\t * @param string $lang_code The target language code\n\t * @return string The taxonomy name in that language\n\t **/\n\tpublic function translated_taxonomy( $origin_taxonomy, $lang_code ) {\n\t\treturn strtolower( \"{$origin_taxonomy}_{$lang_code}\" );\n\t}\n\n\t/**\n\t * Get the terms which are the translations for the provided \n\t * term ID. N.B. The returned array of term objects (and false \n\t * values) will include the term for the term ID passed.\n\t * \n\t * @FIXME: We should cache the translation groups, as we do for posts\n\t *\n\t * @param int|object $term Either a WP Term object, or a term_id \n\t * @return array Either an array keyed by the site languages, each key containing false (if no translation) or a WP Term object\n\t **/\n\tpublic function get_term_translations( $term, $taxonomy ) {\n\t\t$term = get_term( $term, $taxonomy );\n\n\t\t$langs = bbl_get_active_langs();\n\t\t$translations = array();\n\t\tforeach ( $langs as $lang ) {\n\t\t\t$translations[ $lang->code ] = false;\n\t\t}\n\n\t\t$transid = $this->get_transid( $term->term_id );\n\t\t// I thought the fracking bug where the get_objects_in_term function returned integers\n\t\t// as strings was fixed. Seems not. See #17646 for details. Argh.\n\t\t$term_ids = array_map( 'absint', get_objects_in_term( $transid, 'term_translation' ) );\n\n\t\t// We're dealing with terms across multiple taxonomies\n\t\t$base_taxonomy = isset( $this->taxonomies[ $taxonomy ] ) ? $this->taxonomies[ $taxonomy ] : $taxonomy ;\n\t\t$taxonomies = array();\n\t\t$taxonomies[] = $base_taxonomy;\n\t\tforeach ( $this->lang_map as $lang_taxes ) {\n\t\t\tif ( $lang_taxes[ $base_taxonomy ] ) {\n\t\t\t\t$taxonomies[] = $lang_taxes[ $base_taxonomy ];\n\t\t\t}\n\t\t}\n\n\t\t// Get all the translations in one cached DB query\n\t\t$existing_terms = get_terms( $taxonomies, array( 'include' => $term_ids, 'hide_empty' => false ) );\n\n\t\t// Finally, we're ready to return the terms in this \n\t\t// translation group.\n\t\t$terms = array();\n\t\tforeach ( $existing_terms as $t ) {\n\t\t\t$terms[ $this->get_taxonomy_lang_code( $t->taxonomy ) ] = $t;\n\t\t}\n\t\treturn $terms;\n\t}\n\n\t/**\n\t * Returns the term in a particular language, or the fallback content\n\t * if there's no term available.\n\t *\n\t * @param int|object $term Either a WP Term object, or a term_id \n\t * @param string $lang_code The language code for the required language \n\t * @param boolean $fallback If true: if a term is not available, fallback to the default language content (defaults to true)\n\t * @return object|boolean The WP Term object, or if $fallback was false and no post then returns false\n\t **/\n\tpublic function get_term_in_lang( $term, $taxonomy, $lang_code, $fallback = true  ) {\n\t\t$translations = $this->get_term_translations( $term, $taxonomy );\n\t\tif ( isset( $translations[ $lang_code ] ) ) {\n\t\t\treturn $translations[ $lang_code ];\n\t\t}\n\t\tif ( ! $fallback ) {\n\t\t\treturn false;\n\t\t}\n\t\treturn $translations[ bbl_get_default_lang_code() ];\n\t}\n\n\t/**\n\t * Return the admin URL to create a new translation for a term in a\n\t * particular language.\n\t *\n\t * @param int|object $default_term The term in the default language to create a new translation for, either WP Post object or post ID\n\t * @param string $lang The language code \n\t * @return string The admin URL to create the new translation\n\t * @access public\n\t **/\n\tpublic function get_new_term_translation_url( $default_term, $lang_code, $taxonomy = null ) {\n\t\tif ( ! is_int( $default_term ) && is_null( $taxonomy ) ) {\n\t\t\tthrow new exception( 'get_new_term_translation_url: Cannot get term from term_id without taxonomy' );\n\t\t}\n\t\tif ( ! is_null( $taxonomy ) ) {\n\t\t\t$default_term = get_term( $default_term, $taxonomy );\n\t\t}\n\t\tif ( is_wp_error( $default_term ) ) {\n\t\t\tthrow new exception( 'get_new_term_translation_url: Error getting term from term_id and taxonomy: ' . print_r( $default_term, true ) );\n\t\t}\n\t\t$url = admin_url( 'post-new.php' );\n\t\t$args = array( \n\t\t\t'bbl_origin_term' => $default_term->term_id,\n\t\t\t'bbl_origin_taxonomy' => $default_term->taxonomy,\n\t\t\t'lang'            => $lang_code,\n\t\t\t'post_type'       => 'bbl_job',\n\t\t);\n\t\t$url = add_query_arg( $args, $url );\n\t\treturn $url;\n\t}\n\n\t/**\n\t * Returns the language code associated with a particular taxonomy.\n\t *\n\t * @param string $taxonomy The taxonomy to get the language for \n\t * @return string The lang code\n\t **/\n\tpublic function get_taxonomy_lang_code( $taxonomy ) {\n\t\tif ( ! isset( $this->taxonomies[ $taxonomy ] ) ) {\n\t\t\treturn bbl_get_default_lang_code();\n\t\t}\n\t\tforeach ( $this->lang_map as $lang => $data ) {\n\t\t\tforeach ( $data as $trans_tax ) {\n\t\t\t\tif ( $taxonomy == $trans_tax ) {\n\t\t\t\t\treturn $lang;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn false;\n\t}\n\n\t/**\n\t * Return the base taxonomy (in the default language) for a \n\t * provided taxonomy.\n\t *\n\t * @param string $taxonomy The name of a taxonomy \n\t * @return string The name of the base taxonomy\n\t **/\n\tpublic function get_base_taxonomy( $taxonomy ) {\n\t\tif ( ! isset( $this->taxonomies[ $taxonomy ] ) ) {\n\t\t\treturn $taxonomy;\n\t\t}\n\t\treturn $this->taxonomies[ $taxonomy ];\n\t}\n\n\t/**\n\t * Returns the equivalent taxonomy in the specified language.\n\t *\n\t * @param string $taxonomy A taxonomy to return in a given language\n\t * @param string $lang_code The language code for the required language (optional, defaults to current)\n\t * @return boolean|string The taxonomy name, or false if no taxonomy was specified\n\t **/\n\tpublic function get_taxonomy_in_lang( $taxonomy, $lang_code = null ) {\n\t\t// Some taxonomies are untranslated…\n\t\tif ( ! $this->is_taxonomy_translated( $taxonomy ) ) {\n\t\t\treturn $taxonomy;\n\t\t}\n\t\t\t\n\t\tif ( ! $taxonomy ) {\n\t\t\treturn false; // @FIXME: Should I actually be throwing an error here?\n\t\t}\n\n\t\tif ( is_null( $lang_code ) ) {\n\t\t\t$lang_code = bbl_get_current_lang_code();\n\t\t}\n\n\t\t$base_taxonomy = $this->get_base_taxonomy( $taxonomy );\n\n\t\tif ( bbl_get_default_lang_code() == $lang_code ) {\n\t\t\treturn $base_taxonomy;\n\t\t}\n\n\t\treturn $this->lang_map[ $lang_code ][ $base_taxonomy ];\n\t}\n\n\t/**\n\t * Returns a slug translated into a particular language.\n\t *\n\t * @TODO: This is more or less the same method as Babble_Post_Public::get_taxonomy_lang_code, do I need to DRY that up?\n\t *\n\t * @param string $slug The slug to translate\n\t * @param string $lang_code The language code for the required language (optional, defaults to current)\n\t * @return string A translated slug\n\t **/\n\tpublic function get_slug_in_lang( $slug, $lang_code = null ) {\n\t\tif ( is_null( $lang_code ) ) {\n\t\t\t$lang_code = bbl_get_current_lang_code();\n\t\t}\n\t\t$_slug = mb_strtolower( apply_filters( 'bbl_translate_taxonomy_slug', $slug, $lang_code ) );\n\t\t// @FIXME: For some languages the translation might be the same as the original\n\t\tif ( $_slug &&  $_slug != $slug ) {\n\t\t\treturn $_slug;\n\t\t}\n\t\t// Do we need to check that the slug is unique at this point?\n\t\treturn mb_strtolower( \"{$_slug}_{$lang_code}\" );\n\t}\n\t\n\n\tpublic function initialise_translation( $origin_term, $taxonomy, $lang_code ) {\n\n\t\t$new_taxonomy = $this->get_slug_in_lang( $taxonomy, $lang_code );\n\n\t\t$transid = $this->get_transid( $origin_term->term_id );\n\n\t\t// Insert translation:\n\t\t$this->no_recursion = true;\n\t\t$new_term_id = wp_insert_term( $origin_term->name . ' - ' . $lang_code, $new_taxonomy );\n\t\t$this->no_recursion = false;\n\n\t\t$new_term = get_term( $new_term_id['term_id'], $new_taxonomy );\n\n\t\t// Assign transid to translation:\n\t\t$this->set_transid( $new_term_id['term_id'], $transid );\n\n\t\treturn $new_term;\n\n\t}\n\n\t// PRIVATE/PROTECTED METHODS\n\t// =========================\n\n\t/**\n\t * Will update term count based on object types of the current \n\t * taxonomy. Will only count the post(s) in the default language.\n\t *\n\t * Private function for the default callback for post_tag and category\n\t * taxonomies.\n\t *\n\t * @param array $terms List of Term taxonomy IDs\n\t * @param object $taxonomy Current taxonomy object of terms\n\t */\n\tfunction update_post_term_count( $terms, $taxonomy ) {\n\t\tglobal $wpdb;\n\n\t\t$object_types = (array) $taxonomy->object_type;\n\n\t\tforeach ( $object_types as &$object_type ) {\n\t\t\tlist( $object_type ) = explode( ':', $object_type );\n\t\t\t// Babble specific code, to only count in primary language\n\t\t\t$object_type = bbl_get_post_type_in_lang( $object_type, bbl_get_default_lang_code() );\n\t\t}\n\n\t\t$object_types = array_unique( $object_types );\n\n\t\tif ( false !== ( $check_attachments = array_search( 'attachment', $object_types ) ) ) {\n\t\t\tunset( $object_types[ $check_attachments ] );\n\t\t\t$check_attachments = true;\n\t\t}\n\n\t\tif ( $object_types ) {\n\t\t\t$object_types = esc_sql( array_filter( $object_types, 'post_type_exists' ) );\n\t\t}\n\t\tforeach ( (array) $terms as $term ) {\n\t\t\t$count = 0;\n\n\t\t\t// Attachments can be 'inherit' status, we need to base count off the parent's status if so\n\t\t\tif ( $check_attachments ) {\n\t\t\t\t$count += (int) $wpdb->get_var( $wpdb->prepare( \"SELECT COUNT(*) FROM $wpdb->term_relationships, $wpdb->posts p1 WHERE p1.ID = $wpdb->term_relationships.object_id AND ( post_status = 'publish' OR ( post_status = 'inherit' AND post_parent > 0 AND ( SELECT post_status FROM $wpdb->posts WHERE ID = p1.post_parent ) = 'publish' ) ) AND post_type = 'attachment' AND term_taxonomy_id = %d\", $term ) );\n\t\t\t}\n\n\t\t\tif ( $object_types ) {\n\t\t\t\t$count += (int) $wpdb->get_var( $wpdb->prepare( \"SELECT COUNT(*) FROM $wpdb->term_relationships, $wpdb->posts WHERE $wpdb->posts.ID = $wpdb->term_relationships.object_id AND post_status = 'publish' AND post_type IN ('\" . implode(\"', '\", $object_types ) . \"') AND term_taxonomy_id = %d\", $term ) );\n\t\t\t}\n\n\t\t\tdo_action( 'edit_term_taxonomy', $term, $taxonomy );\n\t\t\t$wpdb->update( $wpdb->term_taxonomy, compact( 'count' ), array( 'term_taxonomy_id' => $term ) );\n\t\t\tdo_action( 'edited_term_taxonomy', $term, $taxonomy );\n\t\t}\n\t}\n\n\t/**\n\t * Return the translation group ID (a term ID) that the given term ID \n\t * belongs to.\n\t *\n\t * @param int $target_term_id The term ID to find the translation group for \n\t * @return int The transID the target term belongs to\n\t **/\n\tpublic function get_transid( $target_term_id ) {\n\t\tif ( $transid = wp_cache_get( $target_term_id, 'bbl_term_transids' ) ) {\n\t\t\treturn $transid;\n\t\t}\n\n\t\tif ( ! $target_term_id ) {\n\t\t\tthrow new exception( \"Please specify a target term_id\" );\n\t\t}\n\n\t\t$transids = wp_get_object_terms( $target_term_id, 'term_translation', array( 'fields' => 'ids' ) );\n\t\t// \"There can be only one\" (so we'll just drop the others)\n\t\tif ( isset( $transids[ 0 ] ) ) {\n\t\t\t$transid = $transids[ 0 ];\n\t\t} else {\n\t\t\t$transid = $this->set_transid( $target_term_id );\n\t\t}\n\n\t\twp_cache_add( $target_term_id, $transid, 'bbl_term_transids' );\n\n\t\treturn $transid;\n\t}\n\n\t/**\n\t * Set the translation group ID (a term ID) that the given term ID \n\t * belongs to.\n\t *\n\t * @param int $target_term_id The term ID to set the translation group for\n\t * @param int $translation_group_id The ID of the translation group to add this \n\t * @return int The transID the target term belongs to\n\t **/\n\tpublic function set_transid( $target_term_id, $transid = null ) {\n\t\tif ( ! $target_term_id ) {\n\t\t\tthrow new exception( \"Please specify a target term_id\" );\n\t\t}\n\n\t\tif ( ! $transid ) {\n\t\t\t$transid_name = 'term_transid_' . uniqid();\n\t\t\t$result = wp_insert_term( $transid_name, 'term_translation', array() );\n\t\t\tif ( is_wp_error( $result ) ) {\n\t\t\t\terror_log( \"Problem creating a new Term TransID: \" . print_r( $result, true ) );\n\t\t\t} else {\n\t\t\t\t$transid = $result[ 'term_id' ];\n\t\t\t}\n\t\t}\n\n\t\t$result = wp_set_object_terms( $target_term_id, absint( $transid ), 'term_translation' );\n\t\tif ( is_wp_error( $result ) ) {\n\t\t\terror_log( \"Problem associating TransID with new posts: \" . print_r( $result, true ) );\n\t\t}\n\n\t\twp_cache_delete( $target_term_id, 'bbl_term_transids' );\n\t\t\n\t\treturn $transid;\n\t}\n\n\t/**\n\t * Checks for the relevant POSTed field, then \n\t * resyncs the terms.\n\t *\n\t * @param int $post_id The ID of the WP post\n\t * @param object $post The WP Post object \n\t * @return void\n\t **/\n\tprotected function maybe_resync_terms( $post_id, $post ) {\n\t\t// Check that the fields were included on the screen, we\n\t\t// can do this by checking for the presence of the nonce.\n\t\t$nonce = isset( $_POST[ '_bbl_metabox_resync' ] ) ? $_POST[ '_bbl_metabox_resync' ] : false;\n\t\t\n\t\t\n\t\tif ( ! in_array( $post->post_status, array( 'draft', 'publish' ) ) ) {\n\t\t\treturn;\n\t\t}\n\t\t\n\t\tif ( ! $nonce ) {\n\t\t\treturn;\n\t\t}\n\t\t\t\n\t\t$posted_id = isset( $_POST[ 'post_ID' ] ) ? $_POST[ 'post_ID' ] : 0;\n\t\tif ( $posted_id != $post_id ) {\n\t\t\treturn;\n\t\t}\n\t\t// While we're at it, let's check the nonce\n\t\tcheck_admin_referer( \"bbl_resync_translation-$post_id\", '_bbl_metabox_resync' );\n\t\t\n\t\tif ( $this->no_recursion ) {\n\t\t\treturn;\n\t\t}\n\t\t$this->no_recursion = true;\n\n\t\t$taxonomies = get_object_taxonomies( $post->post_type );\n\t\t$origin_post = bbl_get_post_in_lang( $post_id, bbl_get_default_lang_code() );\n\n\t\t// First dissociate all the terms from synced taxonomies from this post\n\t\twp_delete_object_term_relationships( $post_id, $taxonomies );\n\n\t\t// Now associate terms from synced taxonomies in from the origin post\n\t\tforeach ( $taxonomies as $taxonomy ) {\n\t\t\t$origin_taxonomy = $taxonomy;\n\t\t\tif ( $this->is_taxonomy_translated( $taxonomy ) ) {\n\t\t\t\t$origin_taxonomy = bbl_get_taxonomy_in_lang( $taxonomy, bbl_get_default_lang_code() );\n\t\t\t}\n\t\t\t$term_ids = wp_get_object_terms( $origin_post->ID, $origin_taxonomy, array( 'fields' => 'ids' ) );\n\t\t\t$term_ids = array_map( 'absint', $term_ids );\n\t\t\t$result = wp_set_object_terms( $post_id, $term_ids, $taxonomy );\n\t\t\tif ( is_wp_error( $result, true ) ) {\n\t\t\t\tthrow new exception( \"Problem syncing terms: \" . print_r( $terms, true ), \" Error: \" . print_r( $result, true ) );\n\t\t\t}\n\t\t}\n\t}\n\n}\n\nglobal $bbl_taxonomies;\n$bbl_taxonomies = new Babble_Taxonomies();\n"
  },
  {
    "path": "class-translator.php",
    "content": "<?php\n/**\n * Class for managing the translator role and associated caps\n *\n * @package Babble\n * @since 1.4\n */\nclass Babble_Translator extends Babble_Plugin {\n    \n\t/**\n\t * A version number used for cachebusting, rewrite rule\n\t * flushing, etc.\n\t *\n\t * @var float\n\t **/\n\tprotected $version;\n\t\n    public function __construct() {\n        $this->setup( 'babble-translator', 'plugin' );\n        \n        $this->add_action( 'admin_init', 'maybe_upgrade' );\n\n\t\t$this->version = 1;\n    }\n\n\t/**\n\t * Called by admin_init, this method ensures we are all up to date and \n\t * so on.\n\t *\n\t * @return void\n\t **/\n\tpublic function maybe_upgrade() {\n\n\t\t# @TODO should we amalgamate each class' version numbers into one?\n\t\t$option    = 'bbl-translator-version';\n\t\t$role_name = _x( 'Translator', 'Translator role', 'babble' );\n\n\t\tswitch ( get_option( $option, 0 ) ) {\n\n\t\t\tcase 0:\n\n\t\t\t\tif ( !$role = get_role( 'translator' ) )\n\t\t\t\t\t$role = add_role( 'translator', $role_name );\n\n\t\t\t\t$role->add_cap( 'read' );\n\t\t\t\t$role->add_cap( 'edit_bbl_jobs' );\n\t\t\t\t$role->add_cap( 'edit_others_bbl_jobs' );\n\t\t\t\t$role->add_cap( 'edit_published_bbl_jobs' );\n\t\t\t\t$role->add_cap( 'edit_private_bbl_jobs' );\n\t\t\t\t$role->add_cap( 'publish_bbl_jobs' );\n\t\t\t\t$role->add_cap( 'delete_bbl_jobs' );\n\t\t\t\t$role->add_cap( 'delete_others_bbl_jobs' );\n\t\t\t\t$role->add_cap( 'delete_published_bbl_jobs' );\n\t\t\t\t$role->add_cap( 'delete_private_bbl_jobs' );\n\n\t\t\t\tupdate_option( $option, $this->version );\n\t\t\t\tbreak;\n\n\t\t}\n\n\t}\n\n}\n\nglobal $bbl_translator;\n$bbl_translator = new Babble_translator();\n"
  },
  {
    "path": "class-updates.php",
    "content": "<?php\n\n/**\n * Class for providing updates to Babble via GitHub rather than wordpress.org\n *\n * @package Babble\n * @since 1.4.1\n */\nclass Babble_Updates extends Babble_Plugin {\n\t\n\tpublic function __construct() {\n\t\t$this->setup( 'babble-updates', 'plugin' );\n\n\t\t$this->add_action( 'plugins_loaded' );\n\t\t$this->add_filter( 'euapi_plugin_handler', null, 10, 2 );\n\t}\n\n\t/**\n\t * Include the EUAPI if it's not already present.\n\t */\n\tpublic function plugins_loaded() {\n\t\t$dir = dirname( __FILE__ );\n\t\tif ( !class_exists( 'EUAPI' ) ) {\n\t\t\tinclude_once $dir . '/external-update-api/external-update-api.php';\n\t\t}\n\t\tregister_activation_hook( $dir . '/babble.php',   'euapi_flush_transients' );\n\t\tregister_deactivation_hook( $dir . '/babble.php', 'euapi_flush_transients' );\n\t}\n\n\t/**\n\t * Hooks into the EUAPI update mechanism and tells it to fetch Babble updates from GitHub.\n\t *\n\t * @param  EUAPI_Handler|null $handler Usually null. Can be an EUAPI_Handler object if one has been set.\n\t * @param  EUAPI_Item         $item    An EUAPI_Item for the current plugin.\n\t * @return EUAPI_Handler|null          An EUAPI_Handler if we're overriding updates for this plugin, null if not.\n\t */\n\tpublic function euapi_plugin_handler( EUAPI_Handler $handler = null, EUAPI_Item $item ) {\n\t\tif ( 'http://babbleplugin.com/' == $item->url ) {\n\n\t\t\t$handler = new EUAPI_Handler_GitHub( array(\n\t\t\t\t'type'       => $item->type,\n\t\t\t\t'file'       => $item->file,\n\t\t\t\t'github_url' => 'https://github.com/cftp/babble',\n\t\t\t\t'http'       => array(\n\t\t\t\t\t'sslverify' => false,\n\t\t\t\t),\n\t\t\t) );\n\n\t\t}\n\n\t\treturn $handler;\n\t}\n\n}\n\nglobal $bbl_updates;\n$bbl_updates = new Babble_Updates;\n"
  },
  {
    "path": "composer.json",
    "content": "{\n    \"name\": \"cftp/babble\",\n    \"description\": \"A WordPress plugin to handle translating content into a variety of languages.\",\n    \"license\": \"GPL-2.0+\",\n    \"type\": \"wordpress-plugin\",\n    \"authors\": [\n        {\n            \"name\": \"Code For The People\",\n            \"email\": \"hello@codeforthepeople.com\"\n        }\n    ],\n    \"require\": {\n        \"composer/installers\": \"~1.0\"\n    }\n}\n"
  },
  {
    "path": "css/jobs-admin.css",
    "content": "\nbody.bbl-post-type-bbl_job .postbox-container {\n\tdisplay: none;\n}\n\nbody.bbl-post-type-bbl_job #poststuff #post-body {\n\tmargin-right: 0;\n}\n\n#bbl-translation-editor .bbl-translation-property {\n\tfloat: left;\n\twidth: 49%;\n}\n\n#bbl-translation-editor .bbl-translation-original {\n\tfloat: right;\n\twidth: 49%;\n}\n\n#bbl-translation-editor .bbl-translation-group {\n\tborder-top: 1px solid #ddd; /* FIXME: We should hook into the WP colours at this point, particularly with MP6 */\n\tmargin-top: 20px;\n\tpadding-top: 15px;\n}\n\n#bbl-translation-editor .bbl-translation-group:first-child {\n\tborder-top: none;\n\tmargin-top: 0;\n\tpadding-top: 0;\n}\n\n#bbl-translation-editor .bbl-translation-section {\n\tclear: both;\n\t/* @TODO change to clearfix */\n\toverflow: auto;\n\tmargin-bottom: 10px;\n}\n\n#bbl-translation-editor .bbl-translation-section-post_title,\n#bbl-translation-editor .bbl-translation-section-post_content {\n\tmargin-bottom: 30px;\n}\n\n\n#bbl-translation-editor .bbl-translation-property textarea,\n#bbl-translation-editor .bbl-translation-original textarea {\n\tbox-sizing: border-box;\n\tresize: vertical;\n\theight: 12em;\n\twidth: 100%;\n\tmargin: 0;\n\tdisplay: block;\n}\n\n#bbl-translation-editor .bbl-translation-property input[type=\"text\"],\n#bbl-translation-editor .bbl-translation-original input[type=\"text\"] {\n\tbox-sizing: border-box;\n\twidth: 100%;\n\tmargin: 0;\n}\n\n#bbl-translation-editor .bbl-translation-property-post_content textarea,\n#bbl-translation-editor .bbl-translation-original-post_content textarea {\n\theight: auto;\n}\n\n#bbl-translation-editor .bbl-translation-property-post_title input {\n\twidth: 100%;\n\tbox-sizing: border-box;\n\tpadding: 3px 8px;\n\tfont-size: 1.7em;\n\tline-height: 115%;\n\tmargin: 0;\n}\n\n#bbl-translation-editor .bbl-translation-original .quicktags-toolbar input,\n#bbl-translation-editor .bbl-translation-original .mce-btn-group {\n\tvisibility: hidden;\n}\n\n#bbl-translation-editor .bbl-translation-original-post_title {\n\tfont-size: 1.7em;\n\tline-height: 115%;\n\tpadding-top: 4px;\n}\n\n#bbl-translation-editor .bbl-translation-submit {\n\twidth: 50%;\n\t/*float: right;*/\n}\n\n#bbl-translation-editor .bbl-translation-original-term_name {\n\tpadding-top: 4px;\n}\n\n#bbl-translation-editor .meta-box-sortables .inside {\n\t/* @TODO change to clearfix */\n\toverflow: auto;;\n}\n\n#bbl-translation-editor .meta-box-sortables .inside h4 {\n\tmargin: 5px 0 0;\n}\n"
  },
  {
    "path": "css/languages-options.css",
    "content": "\ntable.babble_languages .column-active {\n\twidth: 10%;\n}\n\ntable.babble_languages .column-public {\n\twidth: 10%;\n}\n\ntable.babble_languages .column-language-code {\n\twidth: 10%;\n}\n\ntable.babble_languages .column-language {\n\twidth: 20%;\n}\n\ntable.babble_languages .column-display_name {\n\twidth: 20%;\n}\n\ntable.babble_languages .column-url_prefix {\n\twidth: 15%;\n}\n\ntable.babble_languages .column-text_direction {\n\twidth: 15%;\n}\n\n.lang-rtl {\n\tdirection: rtl !important; /* Forgive me this one !important, please */\n\ttext-align: right;\n}\n\n.lang-ltr {\n\tdirection: ltr !important; /* Forgive me this one !important, please */\n\ttext-align: left;\n}\n\ninput.babble-error {\n\tbackground-color: #ffebe8;\n\tborder-color: #c00;\n\tcolor: #000;\n}"
  },
  {
    "path": "deprecated.php",
    "content": "<?php\n\n/**\n * Deprecated API functions.\n *\n * @since 1.4\n * @package Babble\n */\n\n/*  Copyright 2013 Code For The People Ltd\n\nThis program is free software; you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation; either version 2 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA\n\n*/\n\nglobal $bbl_translating;\n$bbl_translating = true;\n\n/**\n * Start doing translation.\n *\n * @return void \n **/\nfunction bbl_start_translating() {\n\tglobal $bbl_translating;\n\t_deprecated_function( __FUNCTION__, 1.4 );\n\t$bbl_translating = true;\n}\n\n/**\n * Stop doing any translation.\n *\n * @return void \n **/\nfunction bbl_stop_translating() {\n\tglobal $bbl_translating;\n\t_doing_it_wrong( __FUNCTION__, __( 'Instead of calling this function, you should pass a `bbl_translate` argument to `get_posts()` with a value of boolean false.', 'babble' ), 1.4 );\n\t$bbl_translating = false;\n}\n\n/**\n * Should we be doing any translation.\n *\n * @return boolean True for yes \n **/\nfunction bbl_translating() {\n\tglobal $bbl_translating;\n\t_deprecated_function( __FUNCTION__, 1.4 );\n\treturn $bbl_translating;\n}\n"
  },
  {
    "path": "external-update-api/external-update-api/euapi.php",
    "content": "<?php\n\ndefined( 'ABSPATH' ) or die();\n\nif ( ! class_exists( 'EUAPI' ) ) :\n\n/**\n * The EUAPI plugin class.\n */\nclass EUAPI {\n\n\tprotected $handlers = array();\n\n\t/**\n\t * Class constructor. Sets up some actions and filters.\n\t *\n\t * @author John Blackbourn\n\t */\n\tprivate function __construct() {\n\n\t\tadd_filter( 'http_request_args',                     array( $this, 'filter_http_request_args' ), 20, 2 );\n\n\t\tadd_filter( 'pre_set_site_transient_update_plugins', array( $this, 'filter_update_plugins' ) );\n\t\tadd_filter( 'pre_set_site_transient_update_themes',  array( $this, 'filter_update_themes' ) );\n\n\t\tadd_filter( 'plugins_api',                           array( $this, 'filter_plugins_api' ), 10, 3 );\n\t\tadd_filter( 'themes_api',                            array( $this, 'filter_themes_api' ), 10, 3 );\n\n\t\tadd_filter( 'upgrader_pre_install',                  array( $this, 'filter_upgrader_pre_install' ), 10, 2 );\n\t\tadd_filter( 'upgrader_post_install',                 array( $this, 'filter_upgrader_post_install' ), 10, 3 );\n\n\t\tadd_filter( 'euapi_plugin_handler',                  array( $this, 'filter_euapi_plugin_handler' ), 10, 2 );\n\n\t}\n\n\t/**\n\t * Filter the arguments for HTTP requests. If the request is to a URL that's part of\n\t * something we're handling then filter the arguments accordingly.\n\t *\n\t * @author John Blackbourn\n\t * @param  array  $args HTTP request arguments.\n\t * @param  string $url  HTTP request URL.\n\t * @return array        Updated array of arguments.\n\t */\n\tpublic function filter_http_request_args( array $args, $url ) {\n\n\t\tif ( preg_match( '#://api\\.wordpress\\.org/(?P<type>plugins|themes)/update-check/(?P<version>[0-9\\.]+)/#', $url, $matches ) ) {\n\n\t\t\tswitch ( $matches['type'] ) {\n\n\t\t\t\tcase 'plugins':\n\t\t\t\t\treturn $this->plugin_request( $args, floatval( $matches['version'] ) );\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase 'themes':\n\t\t\t\t\treturn $this->theme_request( $args, floatval( $matches['version'] ) );\n\t\t\t\t\tbreak;\n\n\t\t\t}\n\n\t\t}\n\n\t\t$query = parse_url( $url, PHP_URL_QUERY );\n\n\t\tif ( empty( $query ) ) {\n\t\t\treturn $args;\n\t\t}\n\n\t\tparse_str( $query, $query );\n\n\t\tif ( !isset( $query['_euapi_type'] ) or !isset( $query['_euapi_file'] ) ) {\n\t\t\treturn $args;\n\t\t}\n\n\t\tif ( !( $handler = $this->get_handler( $query['_euapi_type'], $query['_euapi_file'] ) ) ) {\n\t\t\treturn $args;\n\t\t}\n\n\t\t$args = array_merge( $args, $handler->config['http'] );\n\n\t\treturn $args;\n\n\t}\n\n\t/**\n\t * Filters the arguments for HTTP requests to the plugin update check API.\n\t *\n\t * Here we loop over each plugin in the update check request and remove ones for which we're\n\t * handling or excluding updates.\n\t *\n\t * @author John Blackbourn\n\t * @param  array $args    HTTP request arguments.\n\t * @param  float $version The API request version number.\n\t * @return array          Updated array of arguments.\n\t */\n\tprotected function plugin_request( array $args, $version ) {\n\n\t\tswitch ( $version ) {\n\n\t\t\tcase 1.0:\n\t\t\t\t_doing_it_wrong( __METHOD__, sprintf( __( 'External Update API is not compatible with version %s of the WordPress Plugin API. Please update to the latest version of WordPress.', 'euapi' ), $version ), 0.4 );\n\t\t\t\treturn $args;\n\t\t\t\tbreak;\n\n\t\t\tcase 1.1:\n\t\t\tdefault:\n\t\t\t\t$plugins = json_decode( $args['body']['plugins'] );\n\t\t\t\tbreak;\n\n\t\t}\n\n\t\tif ( ! is_object( $plugins ) or empty( $plugins->plugins ) ) {\n\t\t\treturn $args;\n\t\t}\n\n\t\tforeach ( $plugins->plugins as $plugin => $data ) {\n\n\t\t\tif ( !is_object( $data ) ) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$data    = get_object_vars( $data );\n\t\t\t$item    = new EUAPI_Item_Plugin( $plugin, $data );\n\t\t\t$handler = $this->get_handler( 'plugin', $plugin, $item );\n\n\t\t\tif ( null === $handler ) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif ( is_a( $handler, 'EUAPI_Handler' ) ) {\n\t\t\t\t$handler->item = $item;\n\t\t\t}\n\n\t\t\tunset( $plugins->plugins->{$plugin} );\n\n\t\t}\n\n\t\t$args['body']['plugins'] = json_encode( $plugins );\n\n\t\treturn $args;\n\n\t}\n\n\t/**\n\t * Filters the arguments for HTTP requests to the theme update check API.\n\t *\n\t * Here we loop over each theme in the update check request and remove ones for which we're\n\t * handling or excluding updates.\n\t *\n\t * @author John Blackbourn\n\t * @param  array $args    HTTP request arguments.\n\t * @param  float $version The API request version number.\n\t * @return array          Updated array of arguments.\n\t */\n\tprotected function theme_request( array $args, $version ) {\n\n\t\tswitch ( $version ) {\n\n\t\t\tcase 1.0:\n\t\t\t\t_doing_it_wrong( __METHOD__, sprintf( __( 'External Update API is not compatible with version %s of the WordPress Theme API. Please update to the latest version of WordPress.', 'euapi' ), $version ), 0.4 );\n\t\t\t\treturn $args;\n\t\t\t\tbreak;\n\n\t\t\tcase 1.1:\n\t\t\tdefault:\n\t\t\t\t$themes = json_decode( $args['body']['themes'] );\n\t\t\t\tbreak;\n\n\t\t}\n\n\t\tif ( ! is_object( $themes ) or empty( $themes->themes ) ) {\n\t\t\treturn $args;\n\t\t}\n\n\t\tforeach ( $themes->themes as $theme => $data ) {\n\n\t\t\tif ( !is_object( $data ) ) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$data = get_object_vars( $data );\n\n\t\t\tif ( !isset( $data['ThemeURI'] ) ) {\n\t\t\t\t# ThemeURI is missing from $data by default for some reason\n\t\t\t\t$data['ThemeURI'] = wp_get_theme( $data['Template'] )->get( 'ThemeURI' );\n\t\t\t}\n\n\t\t\t$item    = new EUAPI_Item_Theme( $theme, $data );\n\t\t\t$handler = $this->get_handler( 'theme', $theme, $item );\n\n\t\t\tif ( null === $handler ) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif ( is_a( $handler, 'EUAPI_Handler' ) ) {\n\t\t\t\t$handler->item = $item;\n\t\t\t}\n\n\t\t\tunset( $themes->themes->{$theme} );\n\n\t\t}\n\n\t\t$args['body']['themes'] = json_encode( $themes );\n\n\t\treturn $args;\n\n\t}\n\n\t/**\n\t * Called immediately before the plugin update check results are saved in a transient.\n\t *\n\t * We use this to fire off update checks to each of the plugins we're handling updates\n\t * for, and populate the results in the update check object.\n\t *\n\t * @author John Blackbourn\n\t * @param  object $update The plugin update check object.\n\t * @return object         The updated update check object.\n\t */\n\tpublic function filter_update_plugins( $update ) {\n\t\tif ( !isset( $this->handlers['plugin'] ) ) {\n\t\t\treturn $update;\n\t\t}\n\t\treturn self::check( $update, $this->handlers['plugin'] );\n\t}\n\n\t/**\n\t * Called immediately before the theme update check results are saved in a transient.\n\t *\n\t * We use this to fire off update checks to each of the themes we're handling updates\n\t * for, and populate the results in the update check object.\n\t *\n\t * @author John Blackbourn\n\t * @param  object $update Theme update check object.\n\t * @return object         Updated update check object.\n\t */\n\tpublic function filter_update_themes( $update ) {\n\t\tif ( !isset( $this->handlers['theme'] ) ) {\n\t\t\treturn $update;\n\t\t}\n\t\treturn self::check( $update, $this->handlers['theme'] );\n\t}\n\n\t/**\n\t * Fire off update checks for each of the handlers specified and populate the results in\n\t * the update check object.\n\t *\n\t * @author John Blackbourn\n\t * @param  object $update   Update check object.\n\t * @param  array  $handlers Handlers that we're interested in.\n\t * @return object           Updated update check object.\n\t */\n\tpublic static function check( $update, array $handlers ) {\n\n\t\tif ( empty( $update->checked ) ) {\n\t\t\treturn $update;\n\t\t}\n\n\t\tforeach ( array_filter( $handlers ) as $handler ) {\n\n\t\t\t$handler_update = $handler->get_update();\n\n\t\t\tif ( $handler_update->get_new_version() and 1 === version_compare( $handler_update->get_new_version(), $handler->get_current_version() ) ) {\n\t\t\t\tif ( 'plugin' == $handler->get_type() ) {\n\t\t\t\t\t$update->response[ $handler->get_file() ] = (object) $handler_update->get_data_to_store();\n\t\t\t\t} else {\n\t\t\t\t\t$update->response[ $handler->get_file() ] = $handler_update->get_data_to_store();\n\t\t\t\t}\n\t\t\t}\n\n\t\t}\n\n\t\treturn $update;\n\n\t}\n\n\t/**\n\t * Get the update handler for the given item, if one is present.\n\t *\n\t * @author John Blackbourn\n\t * @param  string             $type Handler type (either 'plugin' or 'theme').\n\t * @param  string             $file Item base file name.\n\t * @param  EUAPI_Item|null    $item Item object for the plugin/theme. Optional.\n\t * @return EUAPI_Handler|null       Update handler object, or null if no update handler is present.\n\t */\n\tpublic function get_handler( $type, $file, $item = null ) {\n\n\t\tif ( isset( $this->handlers[$type] ) and array_key_exists( $file, $this->handlers[$type] ) ) {\n\t\t\treturn $this->handlers[$type][$file];\n\t\t}\n\n\t\tif ( !$item ) {\n\t\t\t$item = self::populate_item( $type, $file );\n\t\t}\n\n\t\tif ( ! is_a( $item, 'EUAPI_Item' ) ) {\n\t\t\t$handler = null;\n\t\t} else {\n\t\t\t$handler = apply_filters( \"euapi_{$type}_handler\", null, $item );\n\t\t}\n\n\t\t$this->handlers[$type][$file] = $handler;\n\n\t\treturn $handler;\n\n\t}\n\n\t/**\n\t * Returns the item data for a given item, typically by reading the item file header\n\t * and populating its data.\n\t *\n\t * @author John Blackbourn\n\t * @param  string          $type Handler type (either 'plugin' or 'theme').\n\t * @param  string          $file Item base file name.\n\t * @return EUAPI_Item|null       Item object or null on failure.\n\t */\n\tprotected static function populate_item( $type, $file ) {\n\n\t\tswitch ( $type ) {\n\n\t\t\tcase 'plugin':\n\t\t\t\tif ( $data = self::get_plugin_data( $file ) ) {\n\t\t\t\t\treturn new EUAPI_Item_Plugin( $file, $data );\n\t\t\t\t}\n\t\t\t\tbreak;\n\n\t\t\tcase 'theme':\n\t\t\t\tif ( $data = self::get_theme_data( $file ) ) {\n\t\t\t\t\treturn new EUAPI_Item_Theme( $file, $data );\n\t\t\t\t}\n\t\t\t\tbreak;\n\n\t\t}\n\n\t\treturn null;\n\n\t}\n\n\t/**\n\t * Get data for a plugin by reading its file header.\n\t *\n\t * @param  string      $file Plugin base file name.\n\t * @return array|false       Array of plugin data, or false on failure.\n\t */\n\tpublic static function get_plugin_data( $file ) {\n\n\t\trequire_once ABSPATH . '/wp-admin/includes/plugin.php';\n\n\t\tif ( file_exists( $plugin =  WP_PLUGIN_DIR . '/' . $file ) ) {\n\t\t\treturn get_plugin_data( $plugin );\n\t\t}\n\n\t\treturn false;\n\n\t}\n\n\t/**\n\t * Get data for a theme by reading its file header.\n\t *\n\t * @param  string      $file Theme directory name.\n\t * @return array|false       Array of theme data, or false on failure.\n\t */\n\tpublic static function get_theme_data( $file ) {\n\n\t\t$theme = wp_get_theme( $file );\n\n\t\tif ( !$theme->exists() ) {\n\t\t\treturn false;\n\t\t}\n\n\t\t$data = array(\n\t\t\t'Name'        => '',\n\t\t\t'ThemeURI'    => '',\n\t\t\t'Description' => '',\n\t\t\t'Author'      => '',\n\t\t\t'AuthorURI'   => '',\n\t\t\t'Version'     => '',\n\t\t\t'Template'    => '',\n\t\t\t'Status'      => '',\n\t\t\t'Tags'        => '',\n\t\t\t'TextDomain'  => '',\n\t\t\t'DomainPath'  => '',\n\t\t);\n\n\t\tforeach ( $data as $k => $v ) {\n\t\t\t$data[$k] = $theme->get( $k );\n\t\t}\n\n\t\treturn $data;\n\n\t}\n\n\t/**\n\t * Before the Plugin API performs an action, this short-circuit callback is fired, allowing us to override the\n\t * API method for a given action.\n\t *\n\t * Here, we override the action which fetches plugin information from the wp.org API\n\t * and return our own plugin information if necessary.\n\t *\n\t * @param  bool|object              $default Default return value for this request. Usually boolean false.\n\t * @param  string                   $action  API function being performed.\n\t * @param  object                   $plugin  Plugin Info API object.\n\t * @return bool|WP_Error|EUAPI_Info          EUAPI Info object, WP_Error object on failure, $default if we're not interfering.\n\t */\n\tpublic function filter_plugins_api( $default, $action, $plugin ) {\n\n\t\tif ( 'plugin_information' != $action ) {\n\t\t\treturn $default;\n\t\t}\n\t\tif ( false === strpos( $plugin->slug, '/' ) ) {\n\t\t\treturn $default;\n\t\t}\n\n\t\t$handler = $this->get_handler( 'plugin', $plugin->slug );\n\n\t\tif ( ! is_a( $handler, 'EUAPI_Handler' ) ) {\n\t\t\treturn $default;\n\t\t}\n\n\t\treturn $handler->get_info();\n\n\t}\n\n\t/**\n\t * Before the Theme API performs an action, this short-circuit callback is fired, allowing us to override the\n\t * API method for a given action.\n\t *\n\t * Here, we override the action which fetches theme information from the wp.org API\n\t * and return our own theme information if necessary.\n\t *\n\t * @param  bool|object              $default Default return value for this request. Usually boolean false.\n\t * @param  string                   $action  API function being performed.\n\t * @param  object                   $theme   Theme Info API object.\n\t * @return bool|WP_Error|EUAPI_Info          EUAPI Info object, WP_Error object on failure, $default if we're not interfering.\n\t */\n\tpublic function filter_themes_api( $default, $action, $theme ) {\n\n\t\tif ( 'theme_information' != $action ) {\n\t\t\treturn $default;\n\t\t}\n\n\t\t$handler = $this->get_handler( 'theme', $theme->slug );\n\n\t\tif ( ! is_a( $handler, 'EUAPI_Handler' ) ) {\n\t\t\treturn $default;\n\t\t}\n\n\t\treturn $handler->get_info();\n\n\t}\n\n\t/**\n\t * Fetch the contents of a URL.\n\t *\n\t * @author John Blackbourn\n\t * @param  string   $url   URL to fetch.\n\t * @param  array    $args  Array of arguments passed to wp_remote_get().\n\t * @return WP_Error|string WP_Error object on failure, string contents of URL body on success.\n\t */\n\tpublic static function fetch( $url, array $args = array() ) {\n\n\t\t$args = array_merge( array(\n\t\t\t'timeout' => 5\n\t\t), $args );\n\n\t\t$response = wp_remote_get( $url, $args );\n\n\t\tif ( is_wp_error( $response ) ) {\n\t\t\treturn $response;\n\t\t}\n\n\t\t$code    = wp_remote_retrieve_response_code( $response );\n\t\t$message = wp_remote_retrieve_response_message( $response );\n\n\t\tif ( 200 != $code ) {\n\t\t\treturn new WP_Error( 'fetch_failed', esc_html( $code . ' ' . $message ) );\n\t\t}\n\n\t\treturn wp_remote_retrieve_body( $response );\n\n\t}\n\n\t/**\n\t * Parse a plugin or theme file to fetch its header values.\n\t *\n\t * Based on WordPress' `get_file_data()` function.\n\t * \n\t * @param  string $content     The file content.\n\t * @param  array  $all_headers The headers to return.\n\t * @return array               The header values.\n\t */\n\tpublic static function get_content_data( $content, array $all_headers ) {\n\n\t\t// Pull only the first 8kiB of the file in.\n\t\tif ( function_exists( 'mb_substr' ) ) {\n\t\t\t$file_data = mb_substr( $content, 0, 8192 );\n\t\t} else {\n\t\t\t$file_data = substr( $content, 0, 8192 );\n\t\t}\n\n\t\t// Make sure we catch CR-only line endings.\n\t\t$file_data = str_replace( \"\\r\", \"\\n\", $file_data );\n\n\t\tforeach ( $all_headers as $field => $regex ) {\n\t\t\tif ( preg_match( '/^[ \\t\\/*#@]*' . preg_quote( $regex, '/' ) . ':(.*)$/mi', $file_data, $match ) && $match[1] ) {\n\t\t\t\t$all_headers[ $field ] = _cleanup_header_comment( $match[1] );\n\t\t\t} else {\n\t\t\t\t$all_headers[ $field ] = '';\n\t\t\t}\n\t\t}\n\n\t\treturn $all_headers;\n\t}\n\n\t/**\n\t * Pre-load our handlers so the plugin/theme update filters can function.\n\t * \n\t * @param  bool|WP_Error $default    Default return value for the update. Usually boolean true.\n\t * @param  array         $hook_extra Extra arguments passed to hooked filters.\n\t * @return bool|WP_Error             Boolean true or a WP_Error object.\n\t */\n\tpublic function filter_upgrader_pre_install( $default, array $hook_extra ) {\n\n\t\tif ( isset( $hook_extra['plugin'] ) ) {\n\t\t\t$this->get_handler( 'plugin', $hook_extra['plugin'] );\n\t\t} else if ( isset( $hook_extra['theme'] ) ) {\n\t\t\t$this->get_handler( 'theme', $hook_extra['theme'] );\n\t\t}\n\n\t\treturn $default;\n\n\t}\n\n\t/**\n\t * If we have a handler for this update, do some post-processing after the update.\n\t * \n\t * @param  bool|WP_Error $default    Default return value for the update. Usually boolean true.\n\t * @param  array         $hook_extra Extra arguments passed to hooked filters.\n\t * @param  array         $result     Installation result data.\n\t * @return bool|WP_Error             Boolean true or a WP_Error object.\n\t */\n\tpublic function filter_upgrader_post_install( $default, array $hook_extra, array $result ) {\n\n\t\tglobal $wp_filesystem;\n\n\t\tif ( isset( $hook_extra['plugin'] ) ) {\n\t\t\t$handler = $this->get_handler( 'plugin', $hook_extra['plugin'] );\n\t\t} else if ( isset( $hook_extra['theme'] ) ) {\n\t\t\t$handler = $this->get_handler( 'theme', $hook_extra['theme'] );\n\t\t} else {\n\t\t\treturn $default;\n\t\t}\n\n\t\tif ( ! is_a( $handler, 'EUAPI_Handler' ) ) {\n\t\t\treturn $default;\n\t\t}\n\n\t\tswitch ( $handler->get_type() ) {\n\n\t\t\tcase 'plugin':\n\t\t\t\t$proper_destination = WP_PLUGIN_DIR . '/' . $handler->config['folder_name'];\n\t\t\t\tbreak;\n\t\t\tcase 'theme':\n\t\t\t\t$proper_destination = get_theme_root() . '/' . $handler->config['folder_name'];\n\t\t\t\tbreak;\n\n\t\t}\n\n\t\t// Move\n\t\t$wp_filesystem->move( $result['destination'], $proper_destination );\n\n\t\treturn $default;\n\n\t}\n\n\t/**\n\t * Singleton instantiator.\n\t * \n\t * @return EUAPI Our instance of the EUAPI class.\n\t */\n\tpublic static function init() {\n\n\t\tstatic $instance = null;\n\n\t\tif ( !$instance )\n\t\t\t$instance = new EUAPI;\n\n\t\treturn $instance;\n\n\t}\n\n\t/**\n\t * Eat our own dog food. Handle updates to EUAPI through GitHub.\n\t * \n\t * @param  EUAPI_Handler|null $handler The handler object for this item, or null if a handler isn't set.\n\t * @param  EUAPI_Item         $item    The item in question.\n\t * @return EUAPI_Handler|null The handler for this item, or null.\n\t */\n\tpublic function filter_euapi_plugin_handler( EUAPI_Handler $handler = null, EUAPI_Item $item ) {\n\n\t\tif ( 'https://github.com/cftp/external-update-api' == $item->url ) {\n\n\t\t\t$handler = new EUAPI_Handler_GitHub( array(\n\t\t\t\t'type'       => $item->type,\n\t\t\t\t'file'       => $item->file,\n\t\t\t\t'github_url' => $item->url,\n\t\t\t\t'http'       => array(\n\t\t\t\t\t'sslverify' => false,\n\t\t\t\t),\n\t\t\t) );\n\n\t\t}\n\n\t\treturn $handler;\n\n\t}\n\n}\n\nendif; // endif class exists\n"
  },
  {
    "path": "external-update-api/external-update-api/handler-files.php",
    "content": "<?php\n\ndefined( 'ABSPATH' ) or die();\n\nif ( ! class_exists( 'EUAPI_Handler_Files' ) ) :\n\n/**\n * EUAPI handler for plugins and themes where the plugin or theme updates are fetched simply by reading\n * the plugin or theme files from a URL (as opposed to communicating with an update API).\n * \n */\nabstract class EUAPI_Handler_Files extends EUAPI_Handler {\n\n\t/**\n\t * Class constructor\n\t *\n\t * @param array $config {\n\t *     Configuration for the handler.\n\t *\n\t *     @type string $file The EUAPI_Item file name.\n\t *     @type string $type The item type. Accepts 'plugin' or 'theme'.\n\t *     @type array  $http Array of args to pass to any HTTP requests relating to this handler. Optional.\n\t * }\n\t */\n\tpublic function __construct( array $config ) {\n\n\t\t$defaults = array(\n\t\t\t'http' => array(\n\t\t\t\t'timeout' => 5,\n\t\t\t),\n\t\t);\n\n\t\t// Back-compat with earlier versions where we had these values in the root of the $config array.\n\t\tif ( isset( $config['sslverify'] ) ) {\n\t\t\t$config['http']['sslverify'] = $config['sslverify'];\n\t\t}\n\t\tif ( isset( $config['timeout'] ) ) {\n\t\t\t$config['http']['timeout'] = $config['timeout'];\n\t\t}\n\n\t\tparent::__construct( array_merge( $defaults, $config ) );\n\n\t}\n\n\t/**\n\t * Returns the URL of the plugin or theme file.\n\t *\n\t * @author John Blackbourn\n\t * @param  string $file Optional file name. Defaults to base plugin file or theme stylesheet.\n\t * @return string URL of the plugin file.\n\t */\n\tabstract public function get_file_url( $file = null );\n\n\t/**\n\t * Fetch the latest version number. Does this by fetching the plugin\n\t * file and then parsing the header to get the version number.\n\t *\n\t * @author John Blackbourn\n\t * @return string|false Version number, or false on failure.\n\t */\n\tfinal public function fetch_new_version() {\n\n\t\t$response = EUAPI::fetch( $this->get_file_url(), $this->config['http'] );\n\n\t\tif ( is_wp_error( $response ) ) {\n\t\t\treturn false;\n\t\t}\n\n\t\t$data = EUAPI::get_content_data( $response, array(\n\t\t\t'version' => 'Version'\n\t\t) );\n\n\t\tif ( empty( $data['version'] ) ) {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn $data['version'];\n\n\t}\n\n\t/**\n\t * Fetch info about the latest version of the item.\n\t *\n\t * @author John Blackbourn\n\t * @return EUAPI_Info|WP_Error An EUAPI_Info object, or a WP_Error object on failure.\n\t */\n\tfinal public function fetch_info() {\n\n\t\t$fields = array(\n\t\t\t'author'      => 'Author',\n\t\t\t'description' => 'Description'\n\t\t);\n\n\t\tswitch ( $this->get_type() ) {\n\n\t\t\tcase 'plugin':\n\t\t\t\t$file = $this->get_file_url();\n\t\t\t\t$fields['plugin_name'] = 'Plugin Name';\n\t\t\t\tbreak;\n\n\t\t\tcase 'theme':\n\t\t\t\t$file = $this->get_file_url( 'style.css' );\n\t\t\t\t$fields['theme_name'] = 'Theme Name';\n\t\t\t\tbreak;\n\n\t\t}\n\n\t\t$response = EUAPI::fetch( $file, $this->config['http'] );\n\n\t\tif ( is_wp_error( $response ) ) {\n\t\t\treturn $response;\n\t\t}\n\n\t\t$data = EUAPI::get_content_data( $response, $fields );\n\n\t\t$info = array_merge( $data, array(\n\n\t\t\t'slug'          => $this->get_file(),\n\t\t\t'version'       => $this->get_new_version(),\n\t\t\t'homepage'      => $this->get_homepage_url(),\n\t\t\t'download_link' => $this->get_package_url(),\n\t#\t\t'requires'      => '',\n\t#\t\t'tested'        => '',\n\t#\t\t'last_updated'  => '',\n\t\t\t'downloaded'    => 0,\n\t\t\t'sections'      => array(\n\t\t\t\t'description' => $data['description'],\n\t\t\t),\n\n\t\t) );\n\n\t\treturn new EUAPI_Info( $info );\n\n\t}\n\n}\n\nendif; // endif class exists\n"
  },
  {
    "path": "external-update-api/external-update-api/handler-github.php",
    "content": "<?php\n\ndefined( 'ABSPATH' ) or die();\n\nif ( ! class_exists( 'EUAPI_Handler_GitHub' ) ) :\n\n/**\n * EUAPI handler for plugins and themes hosted on GitHub.com.\n * \n * Supports public and private repos.\n * \n * If a repo is private then a valid OAuth access token must be passed in the 'access_token' argument.\n * See http://developer.github.com/v3/oauth/ for details.\n */\nclass EUAPI_Handler_GitHub extends EUAPI_Handler_Files {\n\n\t/**\n\t * Class constructor\n\t *\n\t * @param array $config {\n\t *     Configuration for the handler.\n\t *\n\t *     @type string $github_url   The URL of the repo homepage.\n\t *     @type string $file         The EUAPI_Item file name.\n\t *     @type string $type The item type. Accepts 'plugin' or 'theme'.\n\t *     @type string $access_token A GitHub API access token if this is a handler for a private repo. Optional.\n\t *     @type array  $http         Array of args to pass to any HTTP requests relating to this handler. Optional.\n\t * }\n\t */\n\tpublic function __construct( array $config ) {\n\n\t\tif ( !isset( $config['github_url'] ) or !isset( $config['file'] ) ) {\n\t\t\treturn;\n\t\t}\n\n\t\t$defaults = array(\n\t\t\t'access_token' => null,\n\t\t);\n\n\t\t$path = trim( parse_url( $config['github_url'], PHP_URL_PATH ), '/' );\n\t\tlist( $username, $repo ) = explode( '/', $path, 2 );\n\n\t\t$defaults['base_url'] = sprintf( 'https://raw.githubusercontent.com/%1$s/%2$s/master',\n\t\t\t$username,\n\t\t\t$repo\n\t\t);\n\t\t$defaults['package_url'] = sprintf( 'https://api.github.com/repos/%1$s/%2$s/zipball',\n\t\t\t$username,\n\t\t\t$repo\n\t\t);\n\n\t\tparent::__construct( array_merge( $defaults, $config ) );\n\n\t}\n\n\t/**\n\t * Returns the URL of the plugin or theme's homepage.\n\t *\n\t * @author John Blackbourn\n\t * @return string URL of the plugin or theme's homepage.\n\t */\n\tpublic function get_homepage_url() {\n\n\t\treturn $this->config['github_url'];\n\n\t}\n\n\t/**\n\t * Returns the URL of the plugin or theme file on GitHub, with access token appended if relevant.\n\t *\n\t * @author John Blackbourn\n\t * @param  string $file Optional file name. Defaults to base plugin file or theme stylesheet.\n\t * @return string URL of the plugin file.\n\t */\n\tpublic function get_file_url( $file = null ) {\n\n\t\tif ( empty( $file ) ) {\n\t\t\t$file = $this->config['file_name'];\n\t\t}\n\n\t\t$url = trailingslashit( $this->config['base_url'] ) . $file;\n\n\t\tif ( !empty( $this->config['access_token'] ) ) {\n\t\t\t$url = add_query_arg( array(\n\t\t\t\t'access_token' => $this->config['access_token']\n\t\t\t), $url );\n\t\t}\n\n\t\treturn $url;\n\t}\n\n\t/**\n\t * Returns the URL of the plugin or theme's ZIP package on GitHub, with access token appended if relevant.\n\t *\n\t * @author John Blackbourn\n\t * @return string URL of the plugin or theme's ZIP package.\n\t */\n\tpublic function get_package_url() {\n\n\t\t$url = $this->config['package_url'];\n\n\t\tif ( !empty( $this->config['access_token'] ) ) {\n\t\t\t$url = add_query_arg( array(\n\t\t\t\t'access_token' => $this->config['access_token']\n\t\t\t), $url );\n\t\t}\n\n\t\treturn $url;\n\n\t}\n\n}\n\nendif; // endif class exists\n"
  },
  {
    "path": "external-update-api/external-update-api/handler.php",
    "content": "<?php\n\ndefined( 'ABSPATH' ) or die();\n\nif ( ! class_exists( 'EUAPI_Handler' ) ) :\n\n/**\n * Abstract class upon which to build update handlers.\n */\nabstract class EUAPI_Handler {\n\n\t/**\n\t * Class constructor\n\t *\n\t * @param array $config {\n\t *     Configuration for the handler.\n\t *\n\t *     @type string $file The EUAPI_Item file name.\n\t *     @type string $type The item type. Accepts 'plugin' or 'theme'.\n\t * }\n\t */\n\tpublic function __construct( array $config ) {\n\t\t$defaults = array();\n\n\t\tif ( 'theme' === $config['type'] ) {\n\t\t\t$defaults['folder_name'] = $config['file'];\n\t\t\t$defaults['file_name']   = 'style.css';\n\t\t} else if ( 'plugin' === $config['type'] ) {\n\t\t\t$defaults['folder_name'] = dirname( $config['file'] );\n\t\t\t$defaults['file_name']   = basename( $config['file'] );\n\t\t}\n\n\t\t// @TODO document this filter name\n\t\t$this->config = apply_filters( \"euapi_{$config['type']}_handler_config\", array_merge( $defaults, $config ) );\n\t}\n\n\t/**\n\t * Return the URL of the item's homepage.\n\t *\n\t * @abstract\n\t * @return string URL of the item's homepage.\n\t */\n\tabstract public function get_homepage_url();\n\n\t/**\n\t * Return the URL of the item's ZIP package.\n\t *\n\t * @abstract\n\t * @return string URL of the item's ZIP package.\n\t */\n\tabstract public function get_package_url();\n\n\t/**\n\t * Fetch the latest version number of the item, typically from an external location.\n\t *\n\t * @abstract\n\t * @return string|false Version number, or false on failure.\n\t */\n\tabstract public function fetch_new_version();\n\n\t/**\n\t * Fetch info about the latest version of the item.\n\t *\n\t * @abstract\n\t * @return EUAPI_Info|WP_Error An EUAPI_Info object, or a WP_Error object on failure.\n\t */\n\tabstract public function fetch_info();\n\n\t/**\n\t * Fetch the upgrade notice for the item, typically from an external location.\n\t *\n\t * @return string|false Upgrade notice, or false on failure.\n\t */\n\tpublic function fetch_upgrade_notice(){\n\t\treturn false;\n\t}\n\n\t/**\n\t * Get the current item's base file name (eg. my-plugin/my-plugin.php or my-theme/style.css).\n\t *\n\t * @author John Blackbourn\n\t * @return string File name\n\t */\n\tfinal public function get_file() {\n\t\treturn $this->config['file'];\n\t}\n\n\t/**\n\t * Get the current installed version number of the item.\n\t *\n\t * @author John Blackbourn\n\t * @return string|false Version number, or false on failure.\n\t */\n\tfinal public function get_current_version() {\n\n\t\tif ( isset( $this->item ) ) {\n\t\t\treturn $this->item->get_version();\n\t\t} else {\n\t\t\treturn false;\n\t\t}\n\n\t}\n\n\t/**\n\t * Get the latest version number of the item.\n\t *\n\t * @author John Blackbourn\n\t * @return string|false Version number, or false on failure.\n\t */\n\tfinal public function get_new_version() {\n\n\t\tif ( !isset( $this->new_version ) ) {\n\t\t\t$this->new_version = $this->fetch_new_version();\n\t\t}\n\n\t\treturn $this->new_version;\n\n\t}\n\n\t/**\n\t * Get the upgrade notice for the item.\n\t *\n\t * @author John Blackbourn\n\t * @return string|false Upgrade notice, or false on failure.\n\t */\n\tfinal public function get_upgrade_notice() {\n\n\t\tif ( !isset( $this->upgrade_notice ) ) {\n\t\t\t$this->upgrade_notice = $this->fetch_upgrade_notice();\n\t\t}\n\n\t\treturn $this->upgrade_notice;\n\n\t}\n\n\t/**\n\t * Get the update object for the item.\n\t *\n\t * @author John Blackbourn\n\t * @return EUAPI_Update Object containing various info about the latest update.\n\t */\n\tfinal public function get_update() {\n\n\t\tif ( isset( $this->update ) ) {\n\t\t\treturn $this->update;\n\t\t}\n\n\t\t$package = add_query_arg( array(\n\t\t\t'_euapi_type' => $this->get_type(),\n\t\t\t'_euapi_file' => $this->get_file()\n\t\t), $this->get_package_url() );\n\n\t\treturn $this->update = new EUAPI_Update( array(\n\t\t\t'slug'           => $this->get_file(),\n\t\t\t'new_version'    => $this->get_new_version(),\n\t\t\t'upgrade_notice' => $this->get_upgrade_notice(),\n\t\t\t'url'            => $this->get_homepage_url(),\n\t\t\t'package'        => $package,\n\t\t\t'config'         => $this->get_config(),\n\t\t) );\n\n\t}\n\n\t/**\n\t * Get the info object for the item.\n\t *\n\t * @author John Blackbourn\n\t * @return EUAPI_Info|WP_Error An EUAPI_Info object, or a WP_Error object on failure.\n\t */\n\tfinal public function get_info() {\n\n\t\tif ( !isset( $this->info ) ) {\n\t\t\t$this->info = $this->fetch_info();\n\t\t}\n\n\t\treturn $this->info;\n\n\t}\n\n\t/**\n\t * Helper function to get the current item config.\n\t *\n\t * @author John Blackbourn\n\t * @return array Config array.\n\t */\n\tfinal public function get_config() {\n\t\treturn $this->config;\n\t}\n\n\t/**\n\t * Helper function to get the handler type (either 'plugin' or 'theme').\n\t *\n\t * @author John Blackbourn\n\t * @return string Handler type.\n\t */\n\tfinal public function get_type() {\n\t\tif ( !in_array( $this->config['type'], array( 'plugin', 'theme' ), true ) ) {\n\t\t\treturn 'plugin';\n\t\t}\n\t\treturn $this->config['type'];\n\t}\n\n}\n\nendif; // endif class exists\n"
  },
  {
    "path": "external-update-api/external-update-api/info.php",
    "content": "<?php\n\ndefined( 'ABSPATH' ) or die();\n\nif ( ! class_exists( 'EUAPI_Info' ) ) :\n\n/**\n * EUAPI Info class.\n */\nclass EUAPI_Info {\n\n\tpublic $external = true;\n\n\tpublic function __construct( array $args ) {\n\n\t\tforeach ( $args as $k => $v ) {\n\t\t\t$this->$k = $v;\n\t\t}\n\n\t}\n\n}\n\nendif;\n"
  },
  {
    "path": "external-update-api/external-update-api/item-plugin.php",
    "content": "<?php\n\ndefined( 'ABSPATH' ) or die();\n\nif ( ! class_exists( 'EUAPI_Item_Plugin' ) ) :\n\n/**\n * EUAPI plugin item. A simple container for plugin information, usually fetched priorly via\n * file headers or an external source.\n */\nclass EUAPI_Item_Plugin extends EUAPI_Item {\n\n\tpublic $type = 'plugin';\n\n\tpublic function __construct( $plugin, array $data ) {\n\n\t\t$this->file    = $plugin;\n\t\t$this->url     = $data['PluginURI'];\n\t\t$this->version = $data['Version'];\n\t\t$this->data    = $data;\n\n\t}\n\n}\n\nendif;\n"
  },
  {
    "path": "external-update-api/external-update-api/item-theme.php",
    "content": "<?php\n\ndefined( 'ABSPATH' ) or die();\n\nif ( ! class_exists( 'EUAPI_Item_Theme' ) ) :\n\n/**\n * EUAPI theme item. A simple container for theme information, usually fetched priorly via\n * file headers or an external source.\n */\nclass EUAPI_Item_Theme extends EUAPI_Item {\n\n\tpublic $type = 'theme';\n\n\tpublic function __construct( $theme, array $data ) {\n\n\t\t$this->file    = $theme;\n\t\t$this->url     = $data['ThemeURI'];\n\t\t$this->version = $data['Version'];\n\t\t$this->data    = $data;\n\n\t}\n\n}\n\nendif;\n"
  },
  {
    "path": "external-update-api/external-update-api/item.php",
    "content": "<?php\n\ndefined( 'ABSPATH' ) or die();\n\nif ( ! class_exists( 'EUAPI_Item' ) ) :\n\n/**\n * Abstract EUAPI Item class upon which to build a plugin item or theme item.\n */\nabstract class EUAPI_Item {\n\n\tpublic function get_version() {\n\t\treturn $this->version;\n\t}\n\n\tpublic function get_url() {\n\t\treturn $this->url;\n\t}\n\n}\n\nendif;\n"
  },
  {
    "path": "external-update-api/external-update-api/update.php",
    "content": "<?php\n\ndefined( 'ABSPATH' ) or die();\n\nif ( ! class_exists( 'EUAPI_Update' ) ) :\n\n/**\n * EUAPI update item. Contains information about an available update.\n */\nclass EUAPI_Update {\n\n\tpublic function __construct( array $args ) {\n\n\t\t$this->slug           = $args['slug'];\n\t\t$this->new_version    = $args['new_version'];\n\t\t$this->upgrade_notice = $args['upgrade_notice'];\n\t\t$this->url            = $args['url'];\n\t\t$this->package        = $args['package'];\n\n\t}\n\n\tpublic function get_data_to_store() {\n\t\treturn get_object_vars( $this );\n\t}\n\n\tpublic function get_new_version() {\n\t\treturn $this->new_version;\n\t}\n\n}\n\nendif;\n"
  },
  {
    "path": "external-update-api/external-update-api.php",
    "content": "<?php\n/*\nPlugin Name:  External Update API\nPlugin URI:   https://github.com/cftp/external-update-api\nDescription:  Add support for updating themes and plugins via external sources instead of wordpress.org\nVersion:      0.5\nAuthor:       Code For The People\nAuthor URI:   http://codeforthepeople.com/\nText Domain:  euapi\nDomain Path:  /languages/\nLicense:      GPL v2 or later\nNetwork:      true\n\n            _____________          \n           /      ____   \\         \n     _____/       \\   \\   \\        Copyright © 2014 Code For The People Ltd\n    /\\    \\        \\___\\   \\       \n   /  \\    \\                \\      This program is free software; you can redistribute it and/or modify\n  /   /    /          _______\\     it under the terms of the GNU General Public License as published by\n /   /    /          \\       /     the Free Software Foundation; either version 2 of the License, or\n/   /    /            \\     /      (at your option) any later version.\n\\   \\    \\ _____    ___\\   /       \n \\   \\    /\\    \\  /       \\       This program is distributed in the hope that it will be useful,\n  \\   \\  /  \\____\\/    _____\\      but WITHOUT ANY WARRANTY; without even the implied warranty of\n   \\   \\/        /    /    / \\     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    \\           /____/    /___\\    GNU General Public License for more details.\n     \\                        /    \n      \\______________________/     \n\n*/\n\ndefined( 'ABSPATH' ) or die();\n\n/**\n * EUAPI class autoloader\n *\n * @author John Blackbourn\n * @param  string $class Class name\n * @return null\n */\nfunction euapi_autoloader( $class ) {\n\n\tif ( 0 !== strpos( $class, 'EUAPI' ) ) {\n\t\treturn;\n\t}\n\n\t$name = str_replace( 'EUAPI_', '', $class );\n\t$name = str_replace( '_', '-', $name );\n\t$name = strtolower( $name );\n\n\t$file = sprintf( '%1$s/external-update-api/%2$s.php',\n\t\tdirname( __FILE__ ),\n\t\t$name\n\t);\n\n\tif ( is_readable( $file ) ) {\n\t\tinclude $file;\n\t}\n\n}\n\n/**\n * Flush the site's plugin and theme update transients. Fired on activation and deactivation.\n *\n * @author John Blackbourn\n */\nfunction euapi_flush_transients() {\n\tdelete_site_transient( 'update_plugins' );\n\tdelete_site_transient( 'update_themes' );\n}\nregister_activation_hook( __FILE__,   'euapi_flush_transients' );\nregister_deactivation_hook( __FILE__, 'euapi_flush_transients' );\n\nspl_autoload_register( 'euapi_autoloader' );\n\nEUAPI::init();\n"
  },
  {
    "path": "external-update-api/readme.md",
    "content": "# External Update API #\n\n**Contributors:** codeforthepeople, johnbillion  \n**Tags:** updates, github  \n**Requires at least:** 3.7  \n**Tested up to:** 3.9  \n**Stable tag:** 0.5  \n**License:** GPL v2 or later  \n\nAdd support for updating themes and plugins via external sources. Includes an update handler for plugins and themes hosted in public or private repos on GitHub.\n\n## Description ##\n\nAdd support for updating themes and plugins via external sources instead of the WordPress.org repos. Includes an update handler for plugins and themes hosted on GitHub.\n\n## Installation ##\n\n1. Download the plugin ZIP file and extract it into your plugins directory, or clone the repo into your plugins directory with `git clone git@github.com:cftp/external-update-api`.\n2. Activate the plugin.\n3. See the Usage section below.\n\n### Usage ###\n\nThe plugin comes bundled with an update handler for GitHub. To add a handler for a different external source, see the 'Writing a new Handler' section below.\n\nYou can tell the update API to use a public or private GitHub repo to update a plugin or theme on your site. To do this, hook into the `euapi_plugin_handler` or `euapi_theme_handler` hook, respectively, and return a handler for your plugin or theme.\n\nPlugin Example:\n\n```\nfunction my_plugin_update_handler( EUAPI_Handler $handler = null, EUAPI_Item_Plugin $item ) {\n\n\tif ( 'my-plugin/my-plugin.php' == $item->file ) {\n\n\t\t$handler = new EUAPI_Handler_GitHub( array(\n\t\t\t'type'       => $item->type,\n\t\t\t'file'       => $item->file,\n\t\t\t'github_url' => 'https://github.com/my-username/my-plugin',\n\t\t\t'http'       => array(\n\t\t\t\t'sslverify' => false,\n\t\t\t),\n\t\t) );\n\n\t}\n\n\treturn $handler;\n\n}\nadd_filter( 'euapi_plugin_handler', 'my_plugin_update_handler', 10, 2 );\n```\n\nTheme Example:\n\n```\nfunction my_theme_update_handler( EUAPI_Handler $handler = null, EUAPI_Item_Theme $item ) {\n\n\tif ( 'my-theme/style.css' == $item->file ) {\n\n\t\t$handler = new EUAPI_Handler_GitHub( array(\n\t\t\t'type'       => $item->type,\n\t\t\t'file'       => $item->file,\n\t\t\t'github_url' => 'https://github.com/my-username/my-theme',\n\t\t\t'http'       => array(\n\t\t\t\t'sslverify' => false,\n\t\t\t),\n\t\t) );\n\n\t}\n\n\treturn $handler;\n\n}\nadd_filter( 'euapi_theme_handler', 'my_theme_update_handler', 10, 2 );\n```\n\nIf your repo is private then you'll need to pass in an additional `access_token` parameter that contains your OAuth access token.\n\nYou can see some more example handlers in our [CFTP Updater repo](https://github.com/cftp/cftp-updater).\n\n### Writing a new Handler ###\n\nTo write a new handler, your best bet is to copy the `EUAPI_Handler_GitHub` class included in the plugin and go from there. See the `EUAPI_Handler` class (and, optionally, the `EUAPI_Handler_Files` class) for the abstract methods which must be defined in your class.\n\n## Frequently Asked Questions ##\n\nNone yet.\n\n## Upgrade Notice ##\n\n### 0.5 ###\n\n* Fix integration with theme updates.\n* EUAPI is now a network-only plugin when used on Multisite.\n* Eat our own dog food. EUAPI now handles its own updates through GitHub.\n* At long-last fix the wonky compatibility with WordPress 3.7+.\n* Increase the minimum required WordPress version to 3.7.\n\n## Changelog ##\n\n### 0.5 ###\n\n* Fix integration with theme updates.\n* EUAPI is now a network-only plugin when used on Multisite.\n* Eat our own dog food. EUAPI now handles its own updates through GitHub.\n* Add support for an upgrade notice (not used by default).\n* Introduce an abstract `EUAPI_Handler_Files` class to simplify extension by other handlers.\n* Inline docs improvements.\n\n### 0.4 ###\n\n* At long-last fix the wonky compatibility with WordPress 3.7+.\n* Increase the minimum required WordPress version to 3.7.\n\n### 0.3.5 ###\n\n* Support JSON-encoded API requests in addition to serialisation. This is pre-emptive support for WordPress 3.7.\n\n### 0.3.4 ###\n\n* Support the upcoming SSL communication with `api.wordpress.org`\n\n### 0.3.3 ###\n\n* Correct a method name in the `EUAPI_Handler` class.\n\n### 0.3.2 ###\n\n* Change a method name and inline docs to clarify that both plugins and themes are supported.\n\n### 0.3.1 ###\n\n* Prevent false positives when reporting available updates.\n* Prevent multiple simultaneous updates breaking due to a variable name clash.\n\n### 0.3 ###\n\n* Allow a handler to return boolean false to prevent update checks being performed altogether.\n\n### 0.2.4 ###\n\n* First public release.\n\n## Screenshots ##\n\nNone yet.\n"
  },
  {
    "path": "js/post-public-admin.js",
    "content": "jQuery( function ( $ ) {\n\t'use strict';\n\tif ( ! bbl_post_public.is_default_lang ) {\n\t\t// Fixup the side admin menu, which is confused by our additional language post types.\n\t\tif ( bbl_post_public.menu_id ) {\n\t\t\t$( bbl_post_public.menu_id + ', ' + bbl_post_public.menu_id + '>a' )\n\t\t\t\t.addClass( 'wp-has-current-submenu wp-menu-open' )\n\t\t\t\t.removeClass( 'wp-not-current-submenu' );\n\t\t}\n\t}\n\n\tif ( bbl_post_public.is_bbl_post_type || ! bbl_post_public.is_default_lang ) {\n\t\t// Remove the add button next to the title for non-default languages\n\t\t$( 'h2 .add-new-h2' ).remove();\n\t\t// Remove Bulk Edit and Quick Edit options\n\t\t$( '#posts-filter option[value=\"edit\"], #posts-filter td.column-title span.inline' ).remove();\n\t}\n\n\t$( '#original_post_content' ).prop( 'readOnly', true );\n\n} );\n"
  },
  {
    "path": "languages/fa_IR.php",
    "content": "<?php $text_direction = \"rtl\"; ?>"
  },
  {
    "path": "languages/pt_BR.po",
    "content": "# Translation of Babble in Portuguese (Portugal)\n# This file is distributed under the same license as the Babble package.\nmsgid \"\"\nmsgstr \"\"\n\"PO-Revision-Date: 2012-05-31 13:31:37+0000\\n\"\n\"MIME-Version: 1.0\\n\"\n\"Content-Type: text/plain; charset=UTF-8\\n\"\n\"Content-Transfer-Encoding: 8bit\\n\"\n\"Plural-Forms: nplurals=2; plural=n != 1;\\n\"\n\"X-Generator: GlotPress/0.1\\n\"\n\"Project-Id-Version: Babble\\n\"\n\n#: class-jobs.php:50\nmsgid \"Status\"\nmsgstr \"Status\"\n\n#: class-jobs.php:51\nmsgid \"Details\"\nmsgstr \"Detalhes\"\n\n#: class-jobs.php:63\nmsgctxt \"translation jobs general name\"\nmsgid \"Jobs\"\nmsgstr \"Trabalhos\"\n\n#: class-jobs.php:64\nmsgctxt \"translation jobs singular name\"\nmsgid \"Job\"\nmsgstr \"Trabalho\"\n\n#: class-jobs.php:65\nmsgctxt \"translation job\"\nmsgid \"Add New\"\nmsgstr \"Adicione um novo\"\n\n#: class-jobs.php:66\nmsgctxt \"translation job\"\nmsgid \"Create New Job\"\nmsgstr \"Criar um novo trabalho\"\n\n#: class-jobs.php:67\nmsgctxt \"translation job\"\nmsgid \"Edit Job\"\nmsgstr \"Editar trabalho\"\n\n#: class-jobs.php:68\nmsgctxt \"translation job\"\nmsgid \"New Job\"\nmsgstr \"Novo trabalho\"\n\n#: class-jobs.php:69\nmsgctxt \"translation job\"\nmsgid \"View Job\"\nmsgstr \"Visualizar trabalho\"\n\n#: class-jobs.php:70\nmsgctxt \"translation job\"\nmsgid \"Search Jobs\"\nmsgstr \"Procurar trabalho\"\n\n#: class-jobs.php:71\nmsgctxt \"translation job\"\nmsgid \"No jobs found.\"\nmsgstr \"Nenhum trabalho encontrado\"\n\n#: class-jobs.php:72\nmsgctxt \"translation job\"\nmsgid \"No jobs found in Trash.\"\nmsgstr \"Nenhum trabalho encontrado na lixeira\"\n\n#: class-jobs.php:73\nmsgctxt \"translation job\"\nmsgid \"All Jobs\"\nmsgstr \"Todos os trabalhos\"\n\n#: class-jobs.php:76\nmsgid \"Content, both posts and taxonomy terms, which need to be translated.\"\nmsgstr \"Conteúdo que precisa ser traduzido (tanto postagens quanto termos de classificação)\"\n\n#: class-jobs.php:88\nmsgctxt \"statuses of translation jobs, general name\"\nmsgid \"Statuses\"\nmsgstr \"Status\"\n\n#: class-jobs.php:89\nmsgctxt \"statuses of translation jobs, singular name\"\nmsgid \"Status\"\nmsgstr \"Status\"\n\n#: class-jobs.php:90\nmsgctxt \"status of translation jobs\"\nmsgid \"Status\"\nmsgstr \"Status\"\n\n#: class-jobs.php:91\nmsgctxt \"status of translation jobs\"\nmsgid \"Popular Statuses\"\nmsgstr \"Status populares\"\n\n#: class-jobs.php:92\nmsgctxt \"status of translation jobs\"\nmsgid \"All Statuses\"\nmsgstr \"Todos os status\"\n\n#: class-jobs.php:93\nmsgctxt \"status of translation jobs\"\nmsgid \"Edit Status\"\nmsgstr \"Editar status\"\n\n#: class-jobs.php:94\nmsgctxt \"status of translation jobs\"\nmsgid \"View Status\"\nmsgstr \"Ver status\"\n\n#: class-jobs.php:95\nmsgctxt \"status of translation jobs\"\nmsgid \"Update Status\"\nmsgstr \"Atualizar status\"\n\n#: class-jobs.php:96\nmsgctxt \"status of translation jobs\"\nmsgid \"Add New Status\"\nmsgstr \"Adicionar status\"\n\n#: class-jobs.php:97\nmsgctxt \"status of translation jobs\"\nmsgid \"New Status Name\"\nmsgstr \"Novo nome para status\"\n\n#: class-jobs.php:98\nmsgctxt \"status of translation jobs\"\nmsgid \"Separate statuses with commas\"\nmsgstr \"Separar diferentes status com vírgulas\"\n\n#: class-jobs.php:99\nmsgctxt \"status of translation jobs\"\nmsgid \"Add or remove statuses\"\nmsgstr \"Adicionar ou remover status\"\n\n#: class-jobs.php:100\nmsgctxt \"status of translation jobs\"\nmsgid \"Choose from the most used statuses\"\nmsgstr \"Escolher status mais populares\"\n\n#: class-jobs.php:110\nmsgctxt \"language for translation jobs, general name\"\nmsgid \"Languages\"\nmsgstr \"Línguas\"\n\n#: class-jobs.php:111 class-jobs.php:112\nmsgctxt \"language for translation jobs, general name\"\nmsgid \"Language\"\nmsgstr \"Língua\"\n\n#: class-jobs.php:113\nmsgctxt \"language for translation jobs, general name\"\nmsgid \"Popular Languages\"\nmsgstr \"Línguas populares\"\n\n#: class-jobs.php:114\nmsgctxt \"language for translation jobs, general name\"\nmsgid \"All Languages\"\nmsgstr \"Todas as línguas\"\n\n#: class-jobs.php:115\nmsgctxt \"language for translation jobs, general name\"\nmsgid \"Edit Language\"\nmsgstr \"Editar língua\"\n\n#: class-jobs.php:116\nmsgctxt \"language for translation jobs, general name\"\nmsgid \"View Language\"\nmsgstr \"Ver língua\"\n\n#: class-jobs.php:117\nmsgctxt \"language for translation jobs, general name\"\nmsgid \"Update Language\"\nmsgstr \"Atualizar língua\"\n\n#: class-jobs.php:118\nmsgctxt \"language for translation jobs, general name\"\nmsgid \"Add New Language\"\nmsgstr \"Adicionar nova língua\"\n\n#: class-jobs.php:119\nmsgctxt \"language for translation jobs, general name\"\nmsgid \"New Language Name\"\nmsgstr \"Novo nome de língua\"\n\n#: class-jobs.php:120\nmsgctxt \"language for translation jobs, general name\"\nmsgid \"Separate languages with commas\"\nmsgstr \"Separar línguas com vírgulas\"\n\n#: class-jobs.php:121\nmsgctxt \"language for translation jobs, general name\"\nmsgid \"Add or remove languages\"\nmsgstr \"Adicionar ou remover línguas\"\n\n#: class-jobs.php:122\nmsgctxt \"language for translation jobs, general name\"\nmsgid \"Choose from the most used languages\"\nmsgstr \"Escolher a partir das línguas mais usadas\"\n\n#: class-jobs.php:145 class-jobs.php:215\nmsgid \"Save\"\nmsgstr \"Salvar\"\n\n#: class-languages.php:90\nmsgid \"<strong>Babble problem:</strong> Please visit the <a href=\\\"%s\\\">Available Languages settings</a> and setup your available languages and the default language.\"\nmsgstr \"<strong>Problema no Babble:</strong> Por favor, visite o <a href=\\\"%s\\\">Available Languages settings</a> e configure suas línguas disponíveis e a língua padrão.\"\n\n#: class-languages.php:100 templates-admin/options-available-languages.php:11\nmsgid \"Available Languages\"\nmsgstr \"Línguas disponíveis\"\n\n#: class-languages.php:290\nmsgid \"The languages \\\"%1$s\\\" and \\\"%2$s\\\" are using the same URL Prefix. Each URL prefix should be unique.\"\nmsgstr \"As línguas \\\"%1$s\\\" e \\\"%2$s\\\" estão utilizando o mesmo  prefixo URL. Cada prefixo URL deve ser único.\"\n\n#: class-languages.php:311\nmsgid \"You must set at least two languages as active.\"\nmsgstr \"Você deve selecionar ao menos duas línguas como ativas.\"\n\n#: class-languages.php:332\nmsgid \"Your language settings have been saved.\"\nmsgstr \"\"\n\n#: class-locale.php:91\nmsgid \"<strong>Babble problem:</strong> Fancy permalinks are disabled. Please enable them in order to have language prefixed URLs work correctly.\"\nmsgstr \"\"\n\n#: class-plugin.php:311\nmsgid \"This plugin admin template could not be found: %s\"\nmsgstr \"\"\n\n#: class-plugin.php:445\nmsgid \"This plugin template could not be found, perhaps you need to hook `sil_plugins_dir` and `sil_plugins_url`: %s\"\nmsgstr \"\"\n\n#: class-post-public.php:142 class-taxonomy.php:83\nmsgid \"Term Translation ID\"\nmsgstr \"\"\n\n#: class-post-public.php:157\nmsgid \"Post Translation ID\"\nmsgstr \"\"\n\n#: class-post-public.php:988\nmsgid \"Invalid Post\"\nmsgstr \"\"\n\n#: class-switcher-menu.php:149 class-switcher-menu.php:181\n#: class-switcher-menu.php:221 class-switcher-menu.php:250\n#: class-switcher-menu.php:295 class-switcher-menu.php:326\n#: class-switcher-menu.php:366 class-switcher-menu.php:395\n#: class-switcher-menu.php:424 class-switcher-menu.php:463\nmsgid \"Switch to %s\"\nmsgstr \"\"\n\n#: class-switcher-menu.php:187 class-switcher-menu.php:257\n#: class-switcher-menu.php:333 class-switcher-menu.php:431\nmsgid \"Create for %s\"\nmsgstr \"\"\n\n#: class-taxonomy.php:375\nmsgid \"Empty Term\"\nmsgstr \"\"\n\n#: templates-admin/options-available-languages.php:20\nmsgid \"Please select the languages you wish to translate this site into, you should select at least two, and select \\\"Save Changes\\\" below the languages table.\"\nmsgstr \"\"\n\n#: templates-admin/options-available-languages.php:23\nmsgid \"Default language:\"\nmsgstr \"\"\n\n#: templates-admin/options-available-languages.php:36\n#: templates-admin/options-available-languages.php:51\nmsgid \"Active\"\nmsgstr \"\"\n\n#: templates-admin/options-available-languages.php:40\n#: templates-admin/options-available-languages.php:55\nmsgid \"Code\"\nmsgstr \"\"\n\n#: templates-admin/options-available-languages.php:41\n#: templates-admin/options-available-languages.php:56\nmsgid \"Name(s)\"\nmsgstr \"\"\n\n#: templates-admin/options-available-languages.php:42\n#: templates-admin/options-available-languages.php:57\nmsgid \"Display Name\"\nmsgstr \"\"\n\n#: templates-admin/options-available-languages.php:43\n#: templates-admin/options-available-languages.php:58\nmsgid \"URL Prefix\"\nmsgstr \"\"\n\n#: templates-admin/options-available-languages.php:44\n#: templates-admin/options-available-languages.php:59\nmsgid \"Text Direction\"\nmsgstr \"\"\n\n#: templates-admin/options-available-languages.php:68\n#: templates-admin/options-available-languages.php:73\nmsgid \"Enable \\\"%s\\\" on this site\"\nmsgstr \"\"\n\n#: templates-admin/options-available-languages.php:79\nmsgid \"Display name for \\\"%s\\\"\"\nmsgstr \"\"\n\n#: templates-admin/options-available-languages.php:85\nmsgid \"URL prefix for \\\"%s\\\"\"\nmsgstr \"\"\n\n#: templates-admin/options-available-languages.php:91\nmsgid \"<strong>Left</strong> to right\"\nmsgstr \"\"\n\n#: templates-admin/options-available-languages.php:93\nmsgid \"<strong>Right</strong> to left\"\nmsgstr \"\"\n\nmsgid \"Babble\"\nmsgstr \"\"\n\nmsgid \"http://simonwheatley.co.uk/wordpress/babble\"\nmsgstr \"\"\n\nmsgid \"Now with Taxonomies!\"\nmsgstr \"\"\n\nmsgid \"Simon Wheatley\"\nmsgstr \"\"\n\nmsgid \"http://simonwheatley.co.uk/wordpress/\"\nmsgstr \"\""
  },
  {
    "path": "languages/tr.po",
    "content": "# Translation of Babble in Turkish\n# This file is distributed under the same license as the Babble package.\nmsgid \"\"\nmsgstr \"\"\n\"PO-Revision-Date: 2012-05-31 13:26:47+0000\\n\"\n\"MIME-Version: 1.0\\n\"\n\"Content-Type: text/plain; charset=UTF-8\\n\"\n\"Content-Transfer-Encoding: 8bit\\n\"\n\"Plural-Forms: nplurals=2; plural=(n > 1);\\n\"\n\"X-Generator: GlotPress/0.1\\n\"\n\"Project-Id-Version: Babble\\n\"\n\n#: class-jobs.php:50\nmsgid \"Status\"\nmsgstr \"Durum\"\n\n#: class-jobs.php:51\nmsgid \"Details\"\nmsgstr \"Detaylar\"\n\n#: class-jobs.php:63\nmsgctxt \"translation jobs general name\"\nmsgid \"Jobs\"\nmsgstr \"İşler\"\n\n#: class-jobs.php:64\nmsgctxt \"translation jobs singular name\"\nmsgid \"Job\"\nmsgstr \"İş\"\n\n#: class-jobs.php:65\nmsgctxt \"translation job\"\nmsgid \"Add New\"\nmsgstr \"Yeni Ekle\"\n\n#: class-jobs.php:66\nmsgctxt \"translation job\"\nmsgid \"Create New Job\"\nmsgstr \"Yeni İş Ekle\"\n\n#: class-jobs.php:67\nmsgctxt \"translation job\"\nmsgid \"Edit Job\"\nmsgstr \"İşi Düzelt\"\n\n#: class-jobs.php:68\nmsgctxt \"translation job\"\nmsgid \"New Job\"\nmsgstr \"Yeni iş\"\n\n#: class-jobs.php:69\nmsgctxt \"translation job\"\nmsgid \"View Job\"\nmsgstr \"İşi Görüntüle\"\n\n#: class-jobs.php:70\nmsgctxt \"translation job\"\nmsgid \"Search Jobs\"\nmsgstr \"İşlerde Araştır\"\n\n#: class-jobs.php:71\nmsgctxt \"translation job\"\nmsgid \"No jobs found.\"\nmsgstr \"İş bulunamadı.\"\n\n#: class-jobs.php:72\nmsgctxt \"translation job\"\nmsgid \"No jobs found in Trash.\"\nmsgstr \"Çöp kutusunda iş bulunamadı.\"\n\n#: class-jobs.php:73\nmsgctxt \"translation job\"\nmsgid \"All Jobs\"\nmsgstr \"Bütün İşler\"\n\n#: class-jobs.php:76\nmsgid \"Content, both posts and taxonomy terms, which need to be translated.\"\nmsgstr \"İçeriklerin, hem iletilerin hem de sınıflandırmaların, çevrilmesi gerekmektedir.\"\n\n#: class-jobs.php:88\nmsgctxt \"statuses of translation jobs, general name\"\nmsgid \"Statuses\"\nmsgstr \"Durumlar\"\n\n#: class-jobs.php:89\nmsgctxt \"statuses of translation jobs, singular name\"\nmsgid \"Status\"\nmsgstr \"Durum\"\n\n#: class-jobs.php:90\nmsgctxt \"status of translation jobs\"\nmsgid \"Status\"\nmsgstr \"Durum\"\n\n#: class-jobs.php:91\nmsgctxt \"status of translation jobs\"\nmsgid \"Popular Statuses\"\nmsgstr \"Popüler Durumlar\"\n\n#: class-jobs.php:92\nmsgctxt \"status of translation jobs\"\nmsgid \"All Statuses\"\nmsgstr \"Bütün Durumlar\"\n\n#: class-jobs.php:93\nmsgctxt \"status of translation jobs\"\nmsgid \"Edit Status\"\nmsgstr \"Durumu Düzenle\"\n\n#: class-jobs.php:94\nmsgctxt \"status of translation jobs\"\nmsgid \"View Status\"\nmsgstr \"Durumu Görüntüle\"\n\n#: class-jobs.php:95\nmsgctxt \"status of translation jobs\"\nmsgid \"Update Status\"\nmsgstr \"Durumu Güncelle\"\n\n#: class-jobs.php:96\nmsgctxt \"status of translation jobs\"\nmsgid \"Add New Status\"\nmsgstr \"Yeni Durum Ekle\"\n\n#: class-jobs.php:97\nmsgctxt \"status of translation jobs\"\nmsgid \"New Status Name\"\nmsgstr \"Yeni Durumun Adı\"\n\n#: class-jobs.php:98\nmsgctxt \"status of translation jobs\"\nmsgid \"Separate statuses with commas\"\nmsgstr \"Durumları virgülle ayırın\"\n\n#: class-jobs.php:99\nmsgctxt \"status of translation jobs\"\nmsgid \"Add or remove statuses\"\nmsgstr \"Durum ekleyin ya da çıkarın\"\n\n#: class-jobs.php:100\nmsgctxt \"status of translation jobs\"\nmsgid \"Choose from the most used statuses\"\nmsgstr \"En çok kullanılan durumlardan birini seç\"\n\n#: class-jobs.php:110\nmsgctxt \"language for translation jobs, general name\"\nmsgid \"Languages\"\nmsgstr \"Diller\"\n\n#: class-jobs.php:111 class-jobs.php:112\nmsgctxt \"language for translation jobs, general name\"\nmsgid \"Language\"\nmsgstr \"Dil\"\n\n#: class-jobs.php:113\nmsgctxt \"language for translation jobs, general name\"\nmsgid \"Popular Languages\"\nmsgstr \"Popüler Diller\"\n\n#: class-jobs.php:114\nmsgctxt \"language for translation jobs, general name\"\nmsgid \"All Languages\"\nmsgstr \"Bütün Diller\"\n\n#: class-jobs.php:115\nmsgctxt \"language for translation jobs, general name\"\nmsgid \"Edit Language\"\nmsgstr \"Dili Düzenle\"\n\n#: class-jobs.php:116\nmsgctxt \"language for translation jobs, general name\"\nmsgid \"View Language\"\nmsgstr \"Dili Görüntüle\"\n\n#: class-jobs.php:117\nmsgctxt \"language for translation jobs, general name\"\nmsgid \"Update Language\"\nmsgstr \"Dili Güncelle\"\n\n#: class-jobs.php:118\nmsgctxt \"language for translation jobs, general name\"\nmsgid \"Add New Language\"\nmsgstr \"Yeni bir Dil Ekle\"\n\n#: class-jobs.php:119\nmsgctxt \"language for translation jobs, general name\"\nmsgid \"New Language Name\"\nmsgstr \"Yeni Dilin İsmi\"\n\n#: class-jobs.php:120\nmsgctxt \"language for translation jobs, general name\"\nmsgid \"Separate languages with commas\"\nmsgstr \"Dilleri virgülle ayır\"\n\n#: class-jobs.php:121\nmsgctxt \"language for translation jobs, general name\"\nmsgid \"Add or remove languages\"\nmsgstr \"Yeni bir dil ekle ya da çıkar\"\n\n#: class-jobs.php:122\nmsgctxt \"language for translation jobs, general name\"\nmsgid \"Choose from the most used languages\"\nmsgstr \"En çok kullanılan dillerden birini seç\"\n\n#: class-jobs.php:145 class-jobs.php:215\nmsgid \"Save\"\nmsgstr \"Kaydet\"\n\n#: class-languages.php:90\nmsgid \"<strong>Babble problem:</strong> Please visit the <a href=\\\"%s\\\">Available Languages settings</a> and setup your available languages and the default language.\"\nmsgstr \"<strong>Babble problem:</strong> Lütfen <a href=\\\"%s\\\">Available Languages settings</a> i ziyaret edin, ve kullanmak istediğiniz diller ile sitenizde otomatik olarak çalışmasını istediğiniz dili seçin. \"\n\n#: class-languages.php:100 templates-admin/options-available-languages.php:11\nmsgid \"Available Languages\"\nmsgstr \"Dil Seçenekleri \"\n\n#: class-languages.php:290\nmsgid \"The languages \\\"%1$s\\\" and \\\"%2$s\\\" are using the same URL Prefix. Each URL prefix should be unique.\"\nmsgstr \" \\\"%1$s\\\" ve \\\"%2$s\\\" dilleri aynı internet adresi kodunu kullanmaktadır. Her internet adresinin kodu birbirinden farklı olmalıdır.\"\n\n#: class-languages.php:311\nmsgid \"You must set at least two languages as active.\"\nmsgstr \"En az iki dili kullanılabilir dilleriniz olarak seçmelisiniz.\"\n\n#: class-languages.php:332\nmsgid \"Your language settings have been saved.\"\nmsgstr \"Dil seçenekleriniz kaydedildi.\"\n\n#: class-locale.php:91\nmsgid \"<strong>Babble problem:</strong> Fancy permalinks are disabled. Please enable them in order to have language prefixed URLs work correctly.\"\nmsgstr \"<strong> Anlaşılmazlık problemi :</strong> Düzenlenmiş linkler engellenmiş.Dil kodlarının bulunduğu internet adreslerinin düzgün çalışabilmesi için lütfen bu linkleri etkinleştirin.\"\n\n#: class-plugin.php:311\nmsgid \"This plugin admin template could not be found: %s\"\nmsgstr \"Bu eklenti paketi bulunamadı: %s\"\n\n#: class-plugin.php:445\nmsgid \"This plugin template could not be found, perhaps you need to hook `sil_plugins_dir` and `sil_plugins_url`: %s\"\nmsgstr \"Bu eklenti paketi bulunamadı, `sil_plugins_dir`ve` sil_plugins_url adreslerini birbirine bağlamayı deneyin %s\"\n\n#: class-post-public.php:142 class-taxonomy.php:83\nmsgid \"Term Translation ID\"\nmsgstr \"Terim Çeviri No\"\n\n#: class-post-public.php:157\nmsgid \"Post Translation ID\"\nmsgstr \"İleti Çeviri No\"\n\n#: class-post-public.php:988\nmsgid \"Invalid Post\"\nmsgstr \"Geçersiz İleti\"\n\n#: class-switcher-menu.php:149 class-switcher-menu.php:181\n#: class-switcher-menu.php:221 class-switcher-menu.php:250\n#: class-switcher-menu.php:295 class-switcher-menu.php:326\n#: class-switcher-menu.php:366 class-switcher-menu.php:395\n#: class-switcher-menu.php:424 class-switcher-menu.php:463\nmsgid \"Switch to %s\"\nmsgstr \"%s e geçin\"\n\n#: class-switcher-menu.php:187 class-switcher-menu.php:257\n#: class-switcher-menu.php:333 class-switcher-menu.php:431\nmsgid \"Create for %s\"\nmsgstr \"%s için oluşturun\"\n\n#: class-taxonomy.php:375\nmsgid \"Empty Term\"\nmsgstr \"Boş terim\"\n\n#: templates-admin/options-available-languages.php:20\nmsgid \"Please select the languages you wish to translate this site into, you should select at least two, and select \\\"Save Changes\\\" below the languages table.\"\nmsgstr \"Lütfen siteyi çevirmek istediğiniz dilleri seçin. En az iki dil belirtmelisiniz ve seçimlerinizi diller tablosunun altındaki \\\"Değişiklikleri Kaydet\\\" tuşuna basarak kaydediniz.\"\n\n#: templates-admin/options-available-languages.php:23\nmsgid \"Default language:\"\nmsgstr \"Otomatik dil:\"\n\n#: templates-admin/options-available-languages.php:36\n#: templates-admin/options-available-languages.php:51\nmsgid \"Active\"\nmsgstr \"Etkin\"\n\n#: templates-admin/options-available-languages.php:40\n#: templates-admin/options-available-languages.php:55\nmsgid \"Code\"\nmsgstr \"Kod\"\n\n#: templates-admin/options-available-languages.php:41\n#: templates-admin/options-available-languages.php:56\nmsgid \"Name(s)\"\nmsgstr \"Ad(lar)\"\n\n#: templates-admin/options-available-languages.php:42\n#: templates-admin/options-available-languages.php:57\nmsgid \"Display Name\"\nmsgstr \"Kullanıcı Adı\"\n\n#: templates-admin/options-available-languages.php:43\n#: templates-admin/options-available-languages.php:58\nmsgid \"URL Prefix\"\nmsgstr \"URL önkodu\"\n\n#: templates-admin/options-available-languages.php:44\n#: templates-admin/options-available-languages.php:59\nmsgid \"Text Direction\"\nmsgstr \"Yazı yönü\"\n\n#: templates-admin/options-available-languages.php:68\n#: templates-admin/options-available-languages.php:73\nmsgid \"Enable \\\"%s\\\" on this site\"\nmsgstr \"Bu sitede \\\"%s\\\"i etkinleştir\"\n\n#: templates-admin/options-available-languages.php:79\nmsgid \"Display name for \\\"%s\\\"\"\nmsgstr \"\\\"%s\\\"in Kullanıcı adı\"\n\n#: templates-admin/options-available-languages.php:85\nmsgid \"URL prefix for \\\"%s\\\"\"\nmsgstr \"\\\"%s\\\"in URL önkodu\"\n\n#: templates-admin/options-available-languages.php:91\nmsgid \"<strong>Left</strong> to right\"\nmsgstr \"<strong>Left</strong> sağa doğru\"\n\n#: templates-admin/options-available-languages.php:93\nmsgid \"<strong>Right</strong> to left\"\nmsgstr \"<strong>Right</strong> sola doğru\"\n\nmsgid \"Babble\"\nmsgstr \"Anlaşılmazlık\"\n\nmsgid \"http://simonwheatley.co.uk/wordpress/babble\"\nmsgstr \"http://simonwheatley.co.uk/wordpress/babble\"\n\nmsgid \"Now with Taxonomies!\"\nmsgstr \"Şimdi de sınıflandırmalar!\"\n\nmsgid \"Simon Wheatley\"\nmsgstr \"Simon Wheatley\"\n\nmsgid \"http://simonwheatley.co.uk/wordpress/\"\nmsgstr \"http://simonwheatley.co.uk/wordpress/\""
  },
  {
    "path": "miscellaneous.php",
    "content": "<?php\n\n/**\n * Functions and (mainly) hooks which don't fit in the various \n * classes for whatever reason. Consider these various things\n * Private access, for this plugin only, please.\n *\n * Will try to keep the functions in here to an absolute minumum.\n *\n * @package Babble\n * @since Alpha 1.1\n */\n\nif ( !is_admin() ) {\n\tforeach ( array( 'admin_bar_init', 'admin_bar_menu' ) as $hook ) {\n\t\tadd_action( $hook, 'bbl_load_interface_textdomain', -9999 );\n\t}\n\tforeach ( array( 'add_admin_bar_menus', 'wp_after_admin_bar_render' ) as $hook ) {\n\t\tadd_action( $hook, 'bbl_load_content_textdomain', 9999 );\n\t}\n}\n\n/**\n * Load the textdomain for Babble's interface language.\n *\n * This is used to attempt to ensure the interface language is used for the admin toolbar. Effective for core, but not themes or plugins which add items to the admin toolbar.\n */\nfunction bbl_load_interface_textdomain() {\n\tload_default_textdomain( bbl_get_current_interface_lang_code() );\n\t$GLOBALS['wp_locale'] = new WP_Locale();\n}\n\n/**\n * Load the textdomain for Babble's content language.\n *\n */\nfunction bbl_load_content_textdomain() {\n\tload_default_textdomain( bbl_get_current_content_lang_code() );\n\t$GLOBALS['wp_locale'] = new WP_Locale();\n}\n\n/**\n * Hooks the WP admin_init action to redirect any requests accessing\n * content which is not in the current language.\n *\n * @return void\n **/\nfunction bbl_admin_init() {\n\tglobal $pagenow;\n\n\t$taxonomy = isset( $_GET[ 'taxonomy' ] ) ? $_GET[ 'taxonomy' ] : false;\n\t$post_type = isset( $_GET[ 'post_type' ] ) ? $_GET[ 'post_type' ] : false;\n\n\t// Deal with the special URL case of the listing screens for vanilla posts\n\tif ( ! $post_type && 'edit.php' == $pagenow )\n\t\t$post_type = 'post';\n\n\t$cur_lang_code = bbl_get_current_lang_code();\n\n\tif ( $taxonomy ) {\n\t\t$new_taxonomy = bbl_get_taxonomy_in_lang( $taxonomy, $cur_lang_code );\n\t\tif ( $taxonomy != $new_taxonomy ) {\n\t\t\t$url = add_query_arg( array( 'taxonomy' => $new_taxonomy ) );\n\t\t\twp_redirect( $url );\n\t\t\texit;\n\t\t}\n\t}\n\tif ( $post_type ) {\n\t\t$new_post_type = bbl_get_post_type_in_lang( $post_type, $cur_lang_code );\n\t\tif ( $post_type != $new_post_type ) {\n\t\t\t$url = add_query_arg( array( 'post_type' => $new_post_type ) );\n\t\t\twp_redirect( $url );\n\t\t\texit;\n\t\t}\n\t}\n}\nadd_action( 'admin_init', 'bbl_admin_init' );\n\n\n/**\n * Replicates the core comments_template function, but uses the API\n * to fetch the comments and includes more filters.\n * \n * Loads the comment template specified in $file.\n *\n * Will not display the comments template if not on single post or page, or if\n * the post does not have comments.\n *\n * Uses the WordPress database object to query for the comments. The comments\n * are passed through the 'comments_array' filter hook with the list of comments\n * and the post ID respectively.\n *\n * The $file path is passed through a filter hook called, 'comments_template'\n * which includes the TEMPLATEPATH and $file combined. Tries the $filtered path\n * first and if it fails it will require the default comment template from the\n * default theme. If either does not exist, then the WordPress process will be\n * halted. It is advised for that reason, that the default theme is not deleted.\n *\n * @since 1.5.0\n * @global array $comment List of comment objects for the current post\n * @uses $wpdb\n * @uses $post\n * @uses $withcomments Will not try to get the comments if the post has none.\n * \n * @see comments_template()\n *\n * @param string $file Optional, default '/comments.php'. The file to load\n * @param bool $separate_comments Optional, whether to separate the comments by comment type. Default is false.\n * @return null Returns null if no comments appear\n */\nfunction bbl_comments_template( $file = '/comments.php', $separate_comments = false ) {\n\tglobal $wp_query, $withcomments, $post, $wpdb, $id, $comment, $user_login, $user_ID, $user_identity, $overridden_cpage;\n\n\tif ( !(is_single() || is_page() || $withcomments) || empty($post) )\n\t\treturn;\n\n\tif ( empty($file) )\n\t\t$file = '/comments.php';\n\n\t$req = get_option('require_name_email');\n\n\t/**\n\t * Comment author information fetched from the comment cookies.\n\t *\n\t * @uses wp_get_current_commenter()\n\t */\n\t$commenter = wp_get_current_commenter();\n\n\t/**\n\t * The name of the current comment author escaped for use in attributes.\n\t */\n\t$comment_author = $commenter['comment_author']; // Escaped by sanitize_comment_cookies()\n\n\t/**\n\t * The email address of the current comment author escaped for use in attributes.\n\t */\n\t$comment_author_email = $commenter['comment_author_email'];  // Escaped by sanitize_comment_cookies()\n\n\t/**\n\t * The url of the current comment author escaped for use in attributes.\n\t */\n\t$comment_author_url = esc_url($commenter['comment_author_url']);\n\n\t$query = new Bbl_Comment_Query;\n\t$args = array( \n\t\t'order' => 'ASC',\n\t\t'post_id' => $post->ID, \n\t\t'status' => 'approve', \n\t\t'status' => 'approve', \n\t);\n\tif ( $user_ID) {\n\t\t$args[ 'unapproved_user_id' ] = $user_ID;\n\t} else if ( ! empty($comment_author) ) {\n\t\t$args[ 'unapproved_author' ] = wp_specialchars_decode($comment_author,ENT_QUOTES);\n\t\t$args[ 'unapproved_author_email' ] = $comment_author_email;\n\t}\n\t$args = apply_filters( 'comments_template_args', $args );\n\t$comments = $query->query( $args );\n\n\t// keep $comments for legacy's sake\n\t$wp_query->comments = apply_filters( 'comments_array', $comments, $post->ID );\n\t$comments = &$wp_query->comments;\n\t$wp_query->comment_count = count($wp_query->comments);\n\tupdate_comment_cache($wp_query->comments);\n\n\tif ( $separate_comments ) {\n\t\t$wp_query->comments_by_type = &separate_comments($comments);\n\t\t$comments_by_type = &$wp_query->comments_by_type;\n\t}\n\n\t$overridden_cpage = FALSE;\n\tif ( '' == get_query_var('cpage') && get_option('page_comments') ) {\n\t\tset_query_var( 'cpage', 'newest' == get_option('default_comments_page') ? get_comment_pages_count() : 1 );\n\t\t$overridden_cpage = TRUE;\n\t}\n\n\tif ( !defined('COMMENTS_TEMPLATE') || !COMMENTS_TEMPLATE)\n\t\tdefine('COMMENTS_TEMPLATE', true);\n\n\t$include = apply_filters('comments_template', STYLESHEETPATH . $file );\n\tif ( file_exists( $include ) )\n\t\trequire $include;\n\telseif ( file_exists( TEMPLATEPATH . $file ) )\n\t\trequire TEMPLATEPATH .  $file;\n\telse // Backward compat code will be removed in a future release\n\t\trequire ABSPATH . WPINC . '/theme-compat/comments.php';\n}\n\n\n/**\n * WordPress Comment Query class.\n *\n * See Trac: http://core.trac.wordpress.org/ticket/19623\n *\n * @since 3.1.0\n */\nclass Bbl_Comment_Query {\n\n\t/**\n\t * Execute the query\n\t *\n\t * @since 3.1.0\n\t *\n\t * @param string|array $query_vars\n\t * @return int|array\n\t */\n\tfunction query( $query_vars ) {\n\t\tglobal $wpdb;\n\n\t\t$defaults = array(\n\t\t\t'author_email' => '',\n\t\t\t'ID' => '',\n\t\t\t'karma' => '',\n\t\t\t'number' => '',\n\t\t\t'offset' => '',\n\t\t\t'orderby' => '',\n\t\t\t'order' => 'DESC',\n\t\t\t'parent' => '',\n\t\t\t'post_ID' => '',\n\t\t\t'post_id' => '',\n\t\t\t'post__in' => '',\n\t\t\t'post_author' => '',\n\t\t\t'post_name' => '',\n\t\t\t'post_parent' => '',\n\t\t\t'post_status' => '',\n\t\t\t'post_type' => '',\n\t\t\t'status' => '',\n\t\t\t'type' => '',\n\t\t\t'unapproved_author' => '',\n\t\t\t'unapproved_author_email' => '',\n\t\t\t'unapproved_user_id' => '',\n\t\t\t'user_id' => '',\n\t\t\t'search' => '',\n\t\t\t'count' => false,\n\t\t);\n\n\t\t$this->query_vars = wp_parse_args( $query_vars, $defaults );\n\t\tdo_action_ref_array( 'pre_get_comments', array( $this ) );\n\t\textract( $this->query_vars, EXTR_SKIP );\n\n\t\t// $args can be whatever, only use the args defined in defaults to compute the key\n\t\t$key = md5( serialize( compact(array_keys($defaults)) )  );\n\t\t$last_changed = wp_cache_get('last_changed', 'comment');\n\t\tif ( !$last_changed ) {\n\t\t\t$last_changed = time();\n\t\t\twp_cache_set('last_changed', $last_changed, 'comment');\n\t\t}\n\t\t$cache_key = \"get_comments:$key:$last_changed\";\n\n\t\tif ( $cache = wp_cache_get( $cache_key, 'comment' ) ) {\n\t\t\treturn $cache;\n\t\t}\n\n\t\tif ( empty( $post_id ) && empty( $post__in ) )\n\t\t\t$post_id = 0;\n\n\t\t$post_id = absint($post_id);\n\n\t\t$where = '';\n\n\t\t$show_unapproved = ( '' != $unapproved_user_id || '' !== $unapproved_author || '' != $unapproved_author_email );\n\n\t\tif ( $show_unapproved ) {\n\t\t\t$where .= ' ( ';\n\t\t}\n\t\t\n\t\tif ( 'hold' == $status )\n\t\t\t$where .= \"comment_approved = '0'\";\n\t\telseif ( 'approve' == $status )\n\t\t\t$where .= \"comment_approved = '1'\";\n\t\telseif ( 'spam' == $status )\n\t\t\t$where .= \"comment_approved = 'spam'\";\n\t\telseif ( 'trash' == $status )\n\t\t\t$where .= \"comment_approved = 'trash'\";\n\t\telse\n\t\t\t$where .= \"( comment_approved = '0' OR comment_approved = '1' )\";\n\n\t\tif ( $show_unapproved ) {\n\t\t\t$where .= ' OR ( comment_approved = 0 ';\n\t\t\tif ( '' !== $unapproved_author )\n\t\t\t\t$where .= $wpdb->prepare( ' AND comment_author = %s', $unapproved_author );\n\t\t\tif ( '' !== $unapproved_author_email )\n\t\t\t\t$where .= $wpdb->prepare( ' AND comment_author_email = %s', $unapproved_author_email );\n\t\t\tif ( '' !== $unapproved_user_id )\n\t\t\t\t$where .= $wpdb->prepare( ' AND user_id = %d', $unapproved_user_id );\n\t\t\t$where .= ' ) ) ';\n\t\t}\n\t\t\n\t\t$order = ( 'ASC' == strtoupper($order) ) ? 'ASC' : 'DESC';\n\n\t\tif ( ! empty( $orderby ) ) {\n\t\t\t$ordersby = is_array($orderby) ? $orderby : preg_split('/[,\\s]/', $orderby);\n\t\t\t$ordersby = array_intersect(\n\t\t\t\t$ordersby,\n\t\t\t\tarray(\n\t\t\t\t\t'comment_agent',\n\t\t\t\t\t'comment_approved',\n\t\t\t\t\t'comment_author',\n\t\t\t\t\t'comment_author_email',\n\t\t\t\t\t'comment_author_IP',\n\t\t\t\t\t'comment_author_url',\n\t\t\t\t\t'comment_content',\n\t\t\t\t\t'comment_date',\n\t\t\t\t\t'comment_date_gmt',\n\t\t\t\t\t'comment_ID',\n\t\t\t\t\t'comment_karma',\n\t\t\t\t\t'comment_parent',\n\t\t\t\t\t'comment_post_ID',\n\t\t\t\t\t'comment_type',\n\t\t\t\t\t'user_id',\n\t\t\t\t)\n\t\t\t);\n\t\t\t$orderby = empty( $ordersby ) ? 'comment_date_gmt' : implode(', ', $ordersby);\n\t\t} else {\n\t\t\t$orderby = 'comment_date_gmt';\n\t\t}\n\n\t\t$number = absint($number);\n\t\t$offset = absint($offset);\n\n\t\tif ( !empty($number) ) {\n\t\t\tif ( $offset )\n\t\t\t\t$limits = 'LIMIT ' . $offset . ',' . $number;\n\t\t\telse\n\t\t\t\t$limits = 'LIMIT ' . $number;\n\t\t} else {\n\t\t\t$limits = '';\n\t\t}\n\n\t\tif ( $count )\n\t\t\t$fields = 'COUNT(*)';\n\t\telse\n\t\t\t$fields = '*';\n\n\t\t$join = '';\n\n\t\tif ( ! empty($post_id) ) {\n\t\t\t$where .= $wpdb->prepare( ' AND comment_post_ID = %d', $post_id );\n\t\t} else if ( '' != $post__in ) {\n\t\t\t$_post__in = implode(',', array_map( 'absint', $post__in ));\n\t\t\t$where .= \" AND comment_post_ID IN ($_post__in)\";\n\t\t} \n\t\tif ( '' !== $author_email )\n\t\t\t$where .= $wpdb->prepare( ' AND comment_author_email = %s', $author_email );\n\t\tif ( '' !== $karma )\n\t\t\t$where .= $wpdb->prepare( ' AND comment_karma = %d', $karma );\n\t\tif ( 'comment' == $type ) {\n\t\t\t$where .= \" AND comment_type = ''\";\n\t\t} elseif( 'pings' == $type ) {\n\t\t\t$where .= ' AND comment_type IN (\"pingback\", \"trackback\")';\n\t\t} elseif ( ! empty( $type ) ) {\n\t\t\t$where .= $wpdb->prepare( ' AND comment_type = %s', $type );\n\t\t}\n\t\tif ( '' !== $parent )\n\t\t\t$where .= $wpdb->prepare( ' AND comment_parent = %d', $parent );\n\t\tif ( '' !== $user_id )\n\t\t\t$where .= $wpdb->prepare( ' AND user_id = %d', $user_id );\n\t\tif ( '' !== $search )\n\t\t\t$where .= $this->get_search_sql( $search, array( 'comment_author', 'comment_author_email', 'comment_author_url', 'comment_author_IP', 'comment_content' ) );\n\n\t\t$post_fields = array_filter( compact( array( 'post_author', 'post_name', 'post_parent', 'post_status', 'post_type', ) ) );\n\t\tif ( ! empty( $post_fields ) ) {\n\t\t\t$join = \"JOIN $wpdb->posts ON $wpdb->posts.ID = $wpdb->comments.comment_post_ID\";\n\t\t\tforeach( $post_fields as $field_name => $field_value )\n\t\t\t\t$where .= $wpdb->prepare( \" AND {$wpdb->posts}.{$field_name} = %s\", $field_value );\n\t\t}\n\n\t\t$pieces = array( 'fields', 'join', 'where', 'orderby', 'order', 'limits' );\n\t\t$clauses = apply_filters_ref_array( 'comments_clauses', array( compact( $pieces ), $this ) );\n\t\tforeach ( $pieces as $piece )\n\t\t\t$$piece = isset( $clauses[ $piece ] ) ? $clauses[ $piece ] : '';\n\n\t\t$query = \"SELECT $fields FROM $wpdb->comments $join WHERE $where ORDER BY $orderby $order $limits\";\n\n\t\tif ( $count )\n\t\t\treturn $wpdb->get_var( $query );\n\n\t\t$comments = $wpdb->get_results( $query );\n\t\t$comments = apply_filters_ref_array( 'the_comments', array( $comments, $this ) );\n\n\t\twp_cache_add( $cache_key, $comments, 'comment' );\n\n\t\treturn $comments;\n\t}\n}\n\n?>"
  },
  {
    "path": "readme.md",
    "content": "# Babble\n\n* Tags: translations, translation, multilingual, i18n, l10n, localisation\n* Requires at least: 3.5.1\n* Tested up to: 4.1.1\n* Stable tag: 1.5.1\n* License: GPLv2 or later\n* License URI: http://www.gnu.org/licenses/gpl-2.0.html\n\nMultilingual WordPress done right.\n\n## Description\n\nThis plugin is at a beta stage for translating:\n\n * Posts\n * Pages\n * Custom post types\n * Categories\n * Tags\n * Custom taxonomies.\n\nIt is powering a live site at http://freespeechdebate.com/.\n\nThe plugin was built with an aversion to both additional database tables, additional columns \nor column changes and a desire to keep additional queries to a minimum. The plugin is (in theory)\ncompatible with WordPress.com VIP and was built with this platform in mind.\n\nThere are a **lot** of `@FIXME` comments, expressing doubts, fears, uncertainties and \nunknowns; feel free to weigh in on any of them.\n\nPlease add bugs and contribute patches and pull requests to https://github.com/cftp/babble/issues\n\n**Pull requests on the *develop* branch, please, not the master branch.**\n\nContributors: Simon Wheatley, John Blackbourn, Scott Evans, Simon Dickson, Marko Heijnen, Tom Nowell, Gary Jones, Emyr Thomas\n\n## Installation\n\nInstallation is fairly standard:\n\n1. Upload the `babble` directory to the `/wp-content/plugins/` directory\n2. Ensure pretty permalinks are activated in Settings -> Permalinks, not sure how it will cope without these!\n3. Activate the plugin through the 'Plugins' menu in WordPress\n4. You'll now be prompted to set the languages you want, you can pick from any of the language packs you've got installed\n5. You'll notice the language switcher menu in the admin bar, use this to switch languages and (depending on context) to create new versions of the content you are looking at (from the front end) or editing (from the admin area)\n\n## Screenshots\n\n![Trigger a translation from the post edit screen](screenshot-1.png \"Trigger a translation from the post edit screen\")     \n_Trigger a translation from the post edit screen_\n\n![View the jobs in the translation queue](screenshot-2.png \"View the jobs in the translation queue\")     \n_View the jobs in the translation queue_\n\n![Add the translation on the translation editor](screenshot-3.png \"Add the translation on the translation editor\")     \n_Add the translation on the translation editor_\n\n## Changelog\n\n### 1.5.1\n\n* Fix for not syncing when updating post meta (#261)\n\n### 1.5\n\n* New functionality for translating post meta data.\n* New option to immediately create blank translations when a translation job is open.\n* Use the default language text direction for translations which have empty content.\n* Visual tweaks to the translation editing screen.\n\n### 1.4.4\n\n* Load the interface textdomain when loading and displaying the admin toolbar on the front end.\n* Correctly set the interface language and content language separately from each other.\n* The WPLANG constant is deprecated since WP 4.0. We now give preference to the WPLANG option when setting the defaults.\n* Fix registration of 'term_translation' taxonomy.\n* Fix breaking rewrite rules starting with ^.\n* Various corrections to templates and body classes.\n* Various smaller bug fixes.\n\n### 1.4.3\n\n* Fix the single post template names\n* Update the bundled External Update API library\n\n### 1.4.2\n\n* Fix the language switcher widget so it correctly displays language names\n\n### 1.4.1\n\n* Add the External Update API library to serve updates to Babble from GitHub\n\n### 1.4\n\n* ENHANCEMENT: Translation job UI and workflow\n* ENHANCEMENT: Separation of admin language setting from the content language\n* Various fixes and enhancements\n\n*gap of time while we add various things*\n\n### alpha 1.1 \n\n* Taxonomy terms.\n\n### alpha 1 \n\n* Proof of concept concentrating on the translation of posts. Taxonomies and menus are not handled yet. Widgets are out of scope completely for this phase of work.\n"
  },
  {
    "path": "readme.txt",
    "content": "=== Babble ===\n\nContributors: Automattic, simonwheatley\nTags: translations, translation, multilingual, i18n, l10n, localisation\nRequires at least: 3.5.1\nTested up to: 4.1.1\nStable tag: 1.5.1\nLicense: GPLv2 or later\nLicense URI: http://www.gnu.org/licenses/gpl-2.0.html\n\nMultilingual WordPress done right. A plugin to facilitate translation of content within a single WordPress site.\n\n== Description ==\n\nThis plugin is at a beta stage for translating:\n\n * Posts\n * Pages\n * Custom post types\n * Categories\n * Tags\n * Custom taxonomies.\n\nIt is powering a live site at http://freespeechdebate.com/.\n\nThe plugin was built with an aversion to both additional database tables, additional columns \nor column changes and a desire to keep additional queries to a minimum. The plugin is (in theory)\ncompatible with WordPress.com VIP and was built with this platform in mind.\n\nThere are a **lot** of `@FIXME` comments, expressing doubts, fears, uncertainties and \nunknowns; feel free to weigh in on any of them.\n\nPlease add bugs and contribute patches and pull requests to https://github.com/cftp/babble/issues\n\n**Pull requests on the *develop* branch, please, not the master branch.**\n\nContributors: Simon Wheatley, John Blackbourn, Scott Evans, Simon Dickson, Marko Heijnen, Tom Nowell, Gary Jones, Emyr Thomas\n\n== Installation ==\n\nInstallation is fairly standard:\n\n1. Upload the `babble` directory to the `/wp-content/plugins/` directory\n2. Ensure pretty permalinks are activated in Settings -> Permalinks, not sure how it will cope without these!\n3. Activate the plugin through the 'Plugins' menu in WordPress\n4. You'll now be prompted to set the languages you want, you can pick from any of the language packs you've got installed\n5. You'll notice the language switcher menu in the admin bar, use this to switch languages and (depending on context) to create new versions of the content you are looking at (from the front end) or editing (from the admin area)\n\n== Screenshots ==\n\n1. Trigger a translation from the post edit screen\n2. View the jobs in the translation queue\n3. Add the translation on the translation editor\n\n== Changelog ==\n\n= 1.5.1 =\n\n* Fix for not syncing when updating post meta (#261)\n\n= 1.5 =\n\n* New functionality for translating post meta data.\n* New option to immediately create blank translations when a translation job is open.\n* Use the default language text direction for translations which have empty content.\n* Visual tweaks to the translation editing screen.\n\n= 1.4.4 =\n\n* Load the interface textdomain when loading and displaying the admin toolbar on the front end.\n* Correctly set the interface language and content language separately from each other.\n* The WPLANG constant is deprecated since WP 4.0. We now give preference to the WPLANG option when setting the defaults.\n* Fix registration of 'term_translation' taxonomy.\n* Fix breaking rewrite rules starting with ^.\n* Various corrections to templates and body classes.\n* Various smaller bug fixes.\n\n= 1.4.3 =\n\n* Fix the single post template names\n* Update the bundled External Update API library\n\n= 1.4.2 =\n\n* Fix the language switcher widget so it correctly displays language names\n\n= 1.4.1 =\n\n* Add the External Update API library to serve updates to Babble from GitHub\n\n= 1.4 =\n\n* ENHANCEMENT: Translation job UI and workflow\n* ENHANCEMENT: Separation of admin language setting from the content language\n* Various fixes and enhancements\n\n*gap of time while we add various things*\n\n= alpha 1.1 =\n\n* Taxonomy terms.\n\n= alpha 1 =\n\n* Proof of concept concentrating on the translation of posts. Taxonomies and menus are not handled yet. Widgets are out of scope completely for this phase of work.\n"
  },
  {
    "path": "templates-admin/options-available-languages.php",
    "content": "<?php \n\t/**\n\t * HTML template for the Available Languages screen in the admin area.\n\t *\n\t * @package Babble\n\t * @subpackage Templates\n\t * @since Alpha 1.1\n\t */\n ?>\n<div class=\"wrap\">\n<div id=\"icon-tools\" class=\"icon32\"><br></div><h2><?php _e( 'Available Languages', 'babble' ); ?></h2>\n\n<form action=\"\" method=\"post\">\n\n<?php \n\t// @FIXME: This contains no element, like a post ID in a publish/update post nonce, which is unique to this request\n\twp_nonce_field( 'babble_lang_prefs', '_babble_nonce' ); \n?>\n\n<p><?php _e( 'Please select the languages you wish to translate this site into. You should select at least two, and select \"Save Changes\" below the languages table.', 'babble' ); ?></p>\n\n<p>\n\t<label for=\"default_lang\"><?php _e( 'Default Language:', 'babble' ); ?></label> \n\t<select name=\"default_lang\" id=\"default_lang\">\n\t\t<?php foreach( $active_langs as $lang ) : ?>\n\t\t\t<option value=\"<?php echo esc_attr( $lang->code ); ?>\" <?php selected( $lang->code, $default_lang ); ?>><?php echo esc_html( $lang->name ); ?></option>\n\t\t<?php endforeach; ?>\n\t</select>\n</p>\n\n<table class=\"wp-list-table widefat fixed babble_languages\" cellspacing=\"0\">\n\t<thead>\n\t<tr>\n\t\t<th scope=\"col\" id=\"language\" class=\"manage-column column-language\"><?php _e( 'Language', 'babble' ); ?></th>\n\t\t<th scope=\"col\" id=\"active\" class=\"manage-column column-active\"><?php _e( 'Active', 'babble' ); ?></th>\n\t\t<th scope=\"col\" id=\"public\" class=\"manage-column column-public\"><?php _e( 'Public', 'babble' ); ?></th>\n\t\t<th scope=\"col\" id=\"lang_code\" class=\"manage-column column-language-code\"><?php _e( 'Code', 'babble' ); ?></th>\n\t\t<th scope=\"col\" id=\"display_name\" class=\"manage-column column-display_name\"><?php _e( 'Display Name', 'babble' ); ?></th>\n\t\t<th scope=\"col\" id=\"url_prefix\" class=\"manage-column column-url_prefix\"><?php _e( 'URL Prefix', 'babble' ); ?></th>\n\t\t<th scope=\"col\" id=\"text_direction\" class=\"manage-column column-text_direction\"><?php _e( 'Text Direction', 'babble' ); ?></th>\n\t</thead>\n\n\t<tfoot>\n\t<tr>\n\t\t<th scope=\"col\" class=\"manage-column column-language\"><?php _e( 'Language', 'babble' ); ?></th>\n\t\t<th scope=\"col\" class=\"manage-column column-active\"><?php _e( 'Active', 'babble' ); ?></th>\n\t\t<th scope=\"col\" class=\"manage-column column-public\"><?php _e( 'Public', 'babble' ); ?></th>\n\t\t<th scope=\"col\" class=\"manage-column column-language-code\"><?php _e( 'Code', 'babble' ); ?></th>\n\t\t<th scope=\"col\" class=\"manage-column column-display_name\"><?php _e( 'Display Name', 'babble' ); ?></th>\n\t\t<th scope=\"col\" class=\"manage-column column-url_prefix\"><?php _e( 'URL Prefix', 'babble' ); ?></th>\n\t\t<th scope=\"col\" class=\"manage-column column-text_direction\"><?php _e( 'Text Direction', 'babble' ); ?></th>\n\t</tr>\n\t</tfoot>\n\n\t<tbody id=\"the-list\">\n\t\t<?php foreach ( $langs as $lang ) : ?>\n\t\t<tr id=\"language-<?php echo esc_attr( $lang->code ); ?>\">\n\t\t\t<th scope=\"row\" class=\"manage-column column-language\">\n\t\t\t\t<?php echo esc_html( $lang->name ); ?>\n\t\t\t</th>\n\t\t\t<td class=\"manage-column column-active\">\n\t\t\t\t<label for=\"enable_<?php echo esc_attr( $lang->code ); ?>\" title=\"<?php echo esc_attr( sprintf( __( 'Enable \"%s\" on this site', 'babble' ), $lang->name ) ); ?>\">\n\t\t\t\t\t<input type=\"checkbox\" name=\"active_langs[]\" value=\"<?php echo esc_attr( $lang->code ); ?>\" id=\"enable_<?php echo esc_attr( $lang->code ); ?>\" <?php checked( $lang->active ); ?>>\n\t\t\t\t</label>\n\t\t\t</td>\n\t\t\t<td class=\"manage-column column-public\">\n\t\t\t\t<label for=\"public_<?php echo esc_attr( $lang->code ); ?>\" title=\"<?php echo esc_attr( sprintf( __( 'Show \"%s\" on this site', 'babble' ), $lang->name ) ); ?>\">\n\t\t\t\t\t<input type=\"checkbox\" name=\"public_langs[]\" value=\"<?php echo esc_attr( $lang->code ); ?>\" id=\"public_<?php echo esc_attr( $lang->code ); ?>\" <?php checked( in_array( $lang->code, $this->public_langs ) ); ?>>\n\t\t\t\t</label>\n\t\t\t</td>\n\t\t\t<td class=\"manage-column column-language-code\">\n\t\t\t\t<?php echo esc_html( $lang->code ); ?>\n\t\t\t</td>\n\t\t\t<td class=\"manage-column column-display_name\">\n\t\t\t\t<label class=\"screen-reader-text\" for=\"display_name_<?php echo esc_attr( $lang->code ); ?>\">\n\t\t\t\t\t<?php echo esc_html( sprintf( __( 'Display name for \"%s\"', 'babble' ), $lang->name ) ); ?>\n\t\t\t\t</label>\n\t\t\t\t<input type=\"text\" name=\"display_name_<?php echo esc_attr( $lang->code ); ?>\" value=\"<?php echo esc_attr( $lang->display_name ); ?>\" id=\"display_name_<?php echo esc_attr( $lang->code ); ?>\" class=\"<?php echo esc_attr( $lang->input_lang_class ); ?>\">\n\t\t\t</td>\n\t\t\t<td class=\"manage-column column-url_prefix\">\n\t\t\t\t<label class=\"screen-reader-text\" for=\"url_prefix_<?php echo esc_attr( $lang->code ); ?>\">\n\t\t\t\t\t<?php echo esc_html( sprintf( __( 'URL prefix for \"%s\"', 'babble' ), $lang->name ) ); ?>\n\t\t\t\t</label>\n\t\t\t\t<input type=\"text\" name=\"url_prefix_<?php echo esc_attr( $lang->code ); ?>\" value=\"<?php echo esc_attr( $lang->url_prefix ); ?>\" id=\"url_prefix_<?php echo esc_attr( $lang->code ); ?>\" class=\"small-text <?php echo esc_attr( $lang->url_prefix_error ); ?>\">\n\t\t\t</td>\n\t\t\t<td class=\"manage-column column-text_direction\">\n\t\t\t\t<?php if ( 'ltr' == $lang->text_direction ) : ?>\n\t\t\t\t\t<?php _e( '<strong>Left</strong> to right', 'babble' ); ?>\n\t\t\t\t<?php else : ?>\n\t\t\t\t\t<?php _e( '<strong>Right</strong> to left', 'babble' ); ?>\n\t\t\t\t<?php endif; ?>\n\t\t\t</td>\n\t\t</tr>\n\t\t<?php endforeach; ?>\n\t</tbody>\n</table>\n\n<?php submit_button( __( 'Save Changes', 'babble' ) ); ?>\n\n</form>\n\n</div>"
  },
  {
    "path": "templates-admin/switcher-interface.php",
    "content": "<tr>\n\t<th scope=\"row\"><?php _e( 'Interface Language', 'babble' ); ?></th>\n\t<td><select name=\"interface_lang\">\n\t\t<?php foreach ( $langs as $lang ) { ?>\n\t\t\t<option value=\"<?php echo esc_attr( $lang->code ); ?>\" <?php selected( $lang->code, $current ); ?>><?php echo esc_html( $lang->display_name ); ?></option>\n\t\t<?php } ?>\n\t</select></td>\n</tr>"
  },
  {
    "path": "templates-admin/translation-editor-meta.php",
    "content": "<?php\r\n$original    = $meta['original'];\r\n$translation = $meta['translation'];\r\n$key         = $original->get_key();\r\n?>\r\n\r\n<div class=\"bbl-translation-group\">\r\n\r\n\t<div class=\"bbl-translation-section\">\r\n\r\n\t\t<div class=\"bbl-translation-property bbl-translation-property-meta_key\">\r\n\t\t\t<?php echo $original->get_input( \"bbl_translation[meta][{$key}]\", $translation ); ?>\r\n\t\t</div>\r\n\t\t<div class=\"bbl-translation-original bbl-translation-original-meta_key\">\r\n\t\t\t<?php echo $original->get_output(); ?>\r\n\t\t</div>\r\n\r\n\t</div>\r\n\r\n</div>\r\n"
  },
  {
    "path": "templates-admin/translation-editor-post-excerpt.php",
    "content": "<div class=\"bbl-translation-property bbl-translation-property-post_excerpt\">\r\n\t<textarea class=\"regular-text\" name=\"bbl_translation[post][post_excerpt]\"><?php echo esc_textarea( $translation->post_excerpt ); ?></textarea>\r\n</div>\r\n<div class=\"bbl-translation-original bbl-translation-original-post_excerpt\">\r\n\t<textarea class=\"regular-text\" readonly><?php echo esc_textarea( $original->post_excerpt ); ?></textarea>\r\n</div>"
  },
  {
    "path": "templates-admin/translation-editor-terms.php",
    "content": "<?php foreach ( $terms as $term ) { ?>\r\n\r\n\t<?php\r\n\t$original    = $term['original'];\r\n\t$translation = $term['translation'];\r\n\t?>\r\n\r\n\t<div class=\"bbl-translation-group\">\r\n\r\n\t\t<div class=\"bbl-translation-section\">\r\n\r\n\t\t\t<h4><?php _e( 'Name', 'babble' ); ?></h4>\r\n\t\t\t<div class=\"bbl-translation-property bbl-translation-property-term_name\">\r\n\t\t\t\t<input type=\"text\" class=\"regular-text\" name=\"bbl_translation[terms][<?php echo $original->term_id; ?>][name]\" value=\"<?php echo esc_attr( $translation->name ); ?>\">\r\n\t\t\t</div>\r\n\t\t\t<div class=\"bbl-translation-original bbl-translation-original-term_name\">\r\n\t\t\t\t<?php echo esc_html( $original->name ); ?>\r\n\t\t\t</div>\r\n\r\n\t\t</div>\r\n\r\n\t\t<?php if ( !empty( $original->slug ) or !empty( $translation->slug ) ) { ?>\r\n\t\t\t<div class=\"bbl-translation-section\">\r\n\r\n\t\t\t\t<h4><?php _e( 'Slug (optional)', 'babble' ); ?></h4>\r\n\t\t\t\t<div class=\"bbl-translation-property bbl-translation-property-term_slug\">\r\n\t\t\t\t\t<input type=\"text\" class=\"regular-text\" name=\"bbl_translation[terms][<?php echo $original->term_id; ?>][slug]\" value=\"<?php echo esc_attr( $translation->slug ); ?>\">\r\n\t\t\t\t</div>\r\n\t\t\t\t<div class=\"bbl-translation-original bbl-translation-original-term_slug\">\r\n\t\t\t\t\t<?php echo esc_html( $original->slug ); ?>\r\n\t\t\t\t</div>\r\n\r\n\t\t\t</div>\r\n\t\t<?php } ?>\r\n\r\n\t\t<?php if ( !empty( $original->description ) or !empty( $translation->description ) ) { ?>\r\n\t\t\t<div class=\"bbl-translation-section\">\r\n\r\n\t\t\t\t<h4><?php _e( 'Description', 'babble' ); ?></h4>\r\n\t\t\t\t<div class=\"bbl-translation-property bbl-translation-property-term_description\">\r\n\t\t\t\t\t<textarea class=\"regular-text\" name=\"bbl_translation[terms][<?php echo $original->term_id; ?>][description]\"><?php echo esc_textarea( $translation->description ); ?></textarea>\r\n\t\t\t\t</div>\r\n\t\t\t\t<div class=\"bbl-translation-original bbl-translation-original-term_description\">\r\n\t\t\t\t\t<textarea class=\"regular-text\" readonly><?php echo esc_textarea( $original->description ); ?></textarea>\r\n\t\t\t\t</div>\r\n\r\n\t\t\t</div>\r\n\t\t<?php } ?>\r\n\r\n\t</div>\r\n\r\n<?php } ?>"
  },
  {
    "path": "templates-admin/translation-editor.php",
    "content": "<div id=\"bbl-translation-editor\">\r\n\r\n\t<?php\r\n\t\tif ( isset( $lang_code ) ) {\r\n\t\t\twp_nonce_field( \"bbl_translation_lang_code_{$job->ID}\", '_bbl_translation_lang_code' );\r\n\t\t\techo '<input type=\"hidden\" name=\"bbl_lang_code\" value=\"' . esc_attr( $lang_code ) . '\">';\r\n\t\t}\r\n\t?>\r\n\r\n\t<?php if ( isset( $items['post'] ) ) { ?>\r\n\r\n\t\t<?php\r\n\t\r\n\t\t\tif ( isset( $origin_post ) ) {\r\n\t\t\t\twp_nonce_field( \"bbl_translation_origin_post_{$job->ID}\", '_bbl_translation_origin_post' );\r\n\t\t\t\techo '<input type=\"hidden\" name=\"bbl_origin_post\" value=\"' . absint( $origin_post ) . '\">';\r\n\t\t\t}\r\n\r\n\t\t\twp_nonce_field( \"bbl_translation_edit_post_{$job->ID}\", '_bbl_translation_edit_post' );\r\n\r\n\t\t\t$original    = $items['post']['original'];\r\n\t\t\t$translation = $items['post']['translation'];\r\n\t\t\t$original_cpto = get_post_type_object( $original->post_type );\r\n\r\n\t\t\tdo_action( 'bbl_translation_post_meta_boxes', 'bbl_translation_editor_post', $original, $translation );\r\n\r\n\t\t?>\r\n\r\n\t\t<div class=\"bbl-translation-item bbl-translation-item-post\">\r\n\r\n\t\t\t<?php if ( !empty( $original->post_title ) or !empty( $translation->post_title ) ) { ?>\r\n\r\n\t\t\t\t<div class=\"bbl-translation-section bbl-translation-section-post_title\">\r\n\t\t\t\t\t<div class=\"bbl-translation-property bbl-translation-property-post_title\">\r\n\t\t\t\t\t\t<input type=\"text\" class=\"regular-text\" name=\"bbl_translation[post][post_title]\" value=\"<?php echo esc_attr( $translation->post_title ); ?>\" placeholder=\"<?php echo esc_attr( apply_filters( 'enter_title_here', __( 'Enter title here', 'babble' ), $original ) ); ?>\">\r\n\t\t\t\t\t</div>\r\n\t\t\t\t\t<div class=\"bbl-translation-original bbl-translation-original-post_title\">\r\n\t\t\t\t\t\t<?php echo esc_html( $original->post_title ); ?>\r\n\t\t\t\t\t</div>\r\n\t\t\t\t</div>\r\n\r\n\t\t\t<?php } ?>\r\n\r\n\t\t\t<?php if ( !empty( $original->post_name ) or !empty( $translation->post_name ) ) { ?>\r\n\r\n\t\t\t\t<div class=\"bbl-translation-section bbl-translation-section-post_name\">\r\n\t\t\t\t\t<div class=\"bbl-translation-property bbl-translation-property-post_name\">\r\n\t\t\t\t\t\t<input type=\"text\" class=\"regular-text\" name=\"bbl_translation[post][post_name]\" value=\"<?php echo esc_attr( $translation->post_name ); ?>\" placeholder=\"<?php echo esc_attr( apply_filters( 'enter_name_here', sprintf( __( 'Enter the %s slug here', 'babble' ), $original_cpto->labels->singular_name ), $original ) ); ?>\">\r\n\t\t\t\t\t</div>\r\n\t\t\t\t\t<div class=\"bbl-translation-original bbl-translation-original-post_name\">\r\n\t\t\t\t\t\t<?php echo esc_html( $original->post_name ); ?>\r\n\t\t\t\t\t</div>\r\n\t\t\t\t</div>\r\n\r\n\t\t\t<?php } ?>\r\n\r\n\t\t\t<?php if ( !empty( $original->post_content ) or !empty( $translation->post_content ) ) { ?>\r\n\r\n\t\t\t\t<?php\r\n\t\t\t\t\t# We have two bugs here that require Trac tickets:\r\n\t\t\t\t\t# 1. The 'readonly' setting for TinyMCE affects subsequent editors, when it shouldn't. This makes the order of these calls to wp_editor() important.\r\n\t\t\t\t\t# 2. The 'buttons' => true argument in the quicktags settings is a hack to hide the Quicktags buttons but retain the Visual/Text tabs.\r\n\t\t\t\t?>\r\n\r\n\t\t\t\t<div class=\"bbl-translation-section bbl-translation-section-post_content\">\r\n\t\t\t\t\t<div class=\"bbl-translation-property bbl-translation-property-post_content\">\r\n\t\t\t\t\t\t<?php wp_editor( $translation->post_content, 'translation_post_content', array(\r\n\t\t\t\t\t\t\t'textarea_name' => 'bbl_translation[post][post_content]',\r\n\t\t\t\t\t\t) ); ?>\r\n\t\t\t\t\t</div>\r\n\t\t\t\t\t<div class=\"bbl-translation-original bbl-translation-original-post_content\">\r\n\t\t\t\t\t\t<?php wp_editor( $original->post_content, 'original_post_content', array(\r\n\t\t\t\t\t\t\t'textarea_name' => 'bbl_original[post][post_content]',\r\n\t\t\t\t\t\t\t'media_buttons' => false,\r\n\t\t\t\t\t\t\t'tinymce'       => array(\r\n\t\t\t\t\t\t\t\t'readonly' => 1,\r\n\t\t\t\t\t\t\t),\r\n\t\t\t\t\t\t) ); ?>\r\n\t\t\t\t\t</div>\r\n\t\t\t\t</div>\r\n\r\n\t\t\t<?php } ?>\r\n\r\n\t\t\t<?php do_meta_boxes( 'bbl_translation_editor_post', 'post', compact( 'original', 'translation' ) ); ?>\r\n\r\n\t\t</div>\r\n\r\n\t<?php } ?>\r\n\r\n\t<?php if ( isset( $items['terms'] ) ) { ?>\r\n\r\n\t\t<?php\r\n\r\n\t\tif ( isset( $origin_term ) ) {\r\n\t\t\twp_nonce_field( \"bbl_translation_origin_term_{$job->ID}\", '_bbl_translation_origin_term' );\r\n\t\t\techo '<input type=\"hidden\" name=\"bbl_origin_term\" value=\"' . absint( $origin_term ) . '\">';\r\n\t\t\techo '<input type=\"hidden\" name=\"bbl_origin_taxonomy\" value=\"' . absint( $origin_taxonomy ) . '\">';\r\n\t\t}\r\n\r\n\t\twp_nonce_field( \"bbl_translation_edit_terms_{$job->ID}\", '_bbl_translation_edit_terms' );\r\n\r\n\t\tdo_action( 'bbl_translation_terms_meta_boxes', 'bbl_translation_editor_terms', $items['terms'] );\r\n\r\n\t\t?>\r\n\t\t<div class=\"bbl-translation-item bbl-translation-item-terms\">\r\n\t\t\t<?php\r\n\r\n\t\t\tforeach ( $items['terms'] as $taxo => $terms )\r\n\t\t\t\tdo_meta_boxes( 'bbl_translation_editor_terms', $taxo, compact( 'taxo', 'terms' ) );\r\n\r\n\t\t\t?>\r\n\t\t</div>\r\n\r\n\t<?php } ?>\r\n\r\n\t<?php if ( isset( $items['meta'] ) ) { ?>\r\n\r\n\t\t<?php\r\n\r\n\t\twp_nonce_field( \"bbl_translation_edit_meta_{$job->ID}\", '_bbl_translation_edit_meta' );\r\n\r\n\t\tdo_action( 'bbl_translation_meta_meta_boxes', 'bbl_translation_editor_meta', $items['meta'] );\r\n\r\n\t\t?>\r\n\t\t<div class=\"bbl-translation-item bbl-translation-item-meta\">\r\n\t\t\t<?php\r\n\t\t\tforeach ( $items['meta'] as $meta_key => $meta ) {\r\n\t\t\t\tdo_meta_boxes( 'bbl_translation_editor_meta', $meta_key, compact( 'meta_key', 'meta' ) );\r\n\t\t\t}\r\n\t\t\t?>\r\n\t\t</div>\r\n\r\n\t<?php } ?>\r\n\r\n\t<div class=\"bbl-translation-submit\">\r\n\r\n\t\t<select name=\"post_status\">\r\n\t\t\t<?php foreach ( $statuses as $status => $label ) { ?>\r\n\t\t\t\t<option value=\"<?php echo esc_attr( $status ); ?>\" <?php selected( $job->post_status, $status ); ?>><?php echo esc_html( $label ); ?></option>\r\n\t\t\t<?php } ?>\r\n\t\t</select>\r\n\t\t<?php submit_button( __( 'Update', 'babble' ), 'primary large', 'submit', false ); ?>\r\n\r\n\t</div>\r\n\r\n</div>"
  },
  {
    "path": "templates-admin/translation-groups.php",
    "content": "<div class=\"wrap\">\n\t<?php screen_icon(); ?>\n\t<h2>Translation Groups</h2>\n\n\t<script type=\"text/javascript\" charset=\"utf-8\">\n\t\tjQuery( document ).ready( function( $ ) {\n\t\t\t// Check for duplicate post IDs in translation groups\n\t\t\tvar dupes = new Object();\n\t\t\t$( '#translation-groups .post-id' ).each( function () {\n\t\t\t\tvar post_id = $( this ).text();\n\t\t\t\tif ( $( '#translation-groups .post-id-' + post_id ).length > 1 ) {\n\t\t\t\t\t$( '#translation-groups .post-id-' + post_id ).css( 'color', 'red' ). css( 'font-weight', 'bold' );\n\t\t\t\t\tdupes[ 'post-id-' + post_id ] = post_id;\n\t\t\t\t}\n\t\t\t} );\n\t\t\tif ( ! $.isEmptyObject( dupes ) ) {\n\t\t\t\tvar msg = '';\n\t\t\t\tvar counter = 0;\n\t\t\t\tvar dupe_ids = new Array();\n\t\t\t\tfor ( i in dupes ) {\n\t\t\t\t\tcounter++;\n\t\t\t\t\tdupe_ids.push( dupes[ i ] );\n\t\t\t\t}\n\t\t\t\tmsg += 'Got ' + counter + ' duplicate post IDs, look for the red: ' + dupe_ids.join( ', ' );\n\t\t\t\talert( msg );\n\t\t\t}\n\t\t} );\n\t</script>\n\n\t<div>\n\t\t<form action=\"\" method=\"get\">\n\t\t\t<input type=\"hidden\" name=\"page\" value=\"btgt\" />\n\t\t\t<p><?php _e( 'Show only the following statuses:', 'babble' ); ?></p>\n\t\t\t<p><?php \n\t\t\t\t$stati = get_post_stati( null, 'objects' ); \n\t\t\t\t$selected_stati = ( isset( $_GET[ 'bbl_stati' ] ) ) ? $_GET[ 'bbl_stati' ] : array( 'publish', 'private', 'draft', 'private', 'future', 'pending' );\n\t\t\t\tforeach ( $stati as $status => $status_obj ) : ?>\n\t\t\t\t<label for=\"status-<?php echo esc_attr( $status ); ?>\"><input type=\"checkbox\" name=\"bbl_stati[]\" value=\"<?php echo esc_attr( $status ); ?>\" id=\"status-<?php echo esc_attr( $status ); ?>\" <?php checked( in_array( $status, $selected_stati ) ); ?> /> <?php echo esc_html( $status_obj->label ); ?> (<?php echo $status_obj->public ? __( 'public', 'babble' ) : __( 'hidden', 'babble' ); ?>)</label><br />\n\t\t\t<?php endforeach; ?></p>\n\t\t\t<?php submit_button( __( 'Filter', 'babble' ) ); ?>\n\t\t</form>\n\t</div>\n\n\t<?php\n\t\n\t\t$terms = get_terms( 'post_translation' );\n\t\n\t\tif ( $terms ) : ?>\n\t\t\t<table class=\"wp-list-table widefat fixed translation-groups\" cellspacing=\"0\" id=\"translation-groups\">\n\t\t\t\t<thead>\n\t\t\t\t\t<tr>\n\t\t\t\t\t\t<th scope=\"col\" id=\"id\" class=\"manage-column column-id\" style=\"\"><span>ID</span></th>\n\t\t\t\t\t\t<th scope=\"col\" id=\"type\" class=\"manage-column column-type\" style=\"\"><span>Type</span></th>\n\t\t\t\t\t\t<th scope=\"col\" id=\"status\" class=\"manage-column column-status\" style=\"\"><span>Status</span></th>\n\t\t\t\t\t\t<th scope=\"col\" id=\"lang\" class=\"manage-column column-lang\" style=\"\"><span>Lang</span></th>\n\t\t\t\t\t</tr>\n\t\t\t\t</thead>\n\t\t\t\t<tfoot>\n\t\t\t\t\t<tr>\n\t\t\t\t\t\t<th scope=\"col\" class=\"manage-column column-id\" style=\"\"><span>ID</span></th>\n\t\t\t\t\t\t<th scope=\"col\" class=\"manage-column column-type\" style=\"\"><span>Type</span></th>\n\t\t\t\t\t\t<th scope=\"col\" class=\"manage-column column-status\" style=\"\"><span>Status</span></th>\n\t\t\t\t\t\t<th scope=\"col\" class=\"manage-column column-lang\" style=\"\"><span>Lang</span></th>\n\t\t\t\t\t</tr>\n\t\t\t\t</tfoot>\n\t\t\t<?php endif; foreach ( $terms as $term ) : ?>\n\t\t\t\t<tbody id=\"tg-<?php echo esc_attr( $term->term_id ); ?>\">\n\t\t\t\t<tr>\n\t\t\t\t\t<th colspan=\"4\"><h3>Translation Group: <?php echo $term->term_id; ?></h3></th>\n\t\t\t\t</tr>\n\t\t\t\t\t<?php \n\t\t\t\t\t\t$post_ids = get_objects_in_term( $term->term_id, 'post_translation' );\n\t\t\t\t\t\t$posts = array();\n\t\t\t\t\t\tforeach ( $post_ids as $post_id )\n\t\t\t\t\t\t\t$posts[] = get_post( $post_id );\n\t\t\t\t\t\tusort( $posts, array( 'SortPosts', 'post_type_descending' ) );\n\t\t\t\t\t\tif ( $posts ) : \n\t\t\t\t\t?>\n\t\t\t\t\t<?php foreach ( $posts as $post ) : if ( ! in_array( $post->post_status, $selected_stati ) ) continue; ?>\n\t\t\t\t\t\t<?php if ( ! $post ) : ?>\n\t\t\t\t\t\t\t<tr>\n\t\t\t\t\t\t\t\t<th colspan=\"4\">\n\t\t\t\t\t\t\t\t\t<span class=\"error\"><strong>WARNING:</strong> Post <?php echo $post_id ?> does not exist</span> –\n\t\t\t\t\t\t\t\t\t<a href=\"<?php echo $this->get_action_link( $post_id, 'delete_from_groups' ); ?>\">remove from all groups</a>\n\t\t\t\t\t\t\t\t</th>\n\t\t\t\t\t\t\t</tr>\n\t\t\t\t\t\t<?php else : ?>\n\t\t\t\t\t\t\t<tr class=\"post-id-<?php echo esc_attr( $post->ID ); ?>\">\n\t\t\t\t\t\t\t\t<th scope=\"row\" class=\"manage-column column-id\">\n\t\t\t\t\t\t\t\t\t<span class=\"post-id\"><?php echo $post->ID ?></span><br /> \n\t\t\t\t\t\t\t\t\t<a href=\"<?php echo add_query_arg( array( 'lang' => bbl_get_post_lang_code( $post->ID ) ), get_edit_post_link( $post->ID ) ); ?>\">edit</a> |\n\t\t\t\t\t\t\t\t\t<a href=\"<?php echo $this->get_action_link( $post->ID, 'delete_post', \"tg-$term->term_id\" ); ?>\">delete</a> | \n\t\t\t\t\t\t\t\t\t<a href=\"<?php echo $this->get_action_link( $post->ID, 'trash_post', \"tg-$term->term_id\" ); ?>\">trash</a> | \n\t\t\t\t\t\t\t\t\t<?php if ( bbl_get_default_lang_code() == bbl_get_post_lang_code( $post->ID ) ) : ?>\n\t\t\t\t\t\t\t\t\t\t<a href=\"<?php echo $this->get_action_link( $post->ID, 'delete_from_groups', \"tg-$term->term_id\" ); ?>\">remove from group</a>\n\t\t\t\t\t\t\t\t\t<?php endif; ?>\n\t\t\t\t\t\t\t\t</th>\n\t\t\t\t\t\t\t\t<td class=\"manage-column column-type\"><?php echo $post->post_type ?></td>\n\t\t\t\t\t\t\t\t<td class=\"manage-column column-status\"><?php echo $post->post_status ?></td>\n\t\t\t\t\t\t\t\t<td class=\"manage-column column-lang\"><?php echo bbl_get_post_lang_code( $post->ID ) ?></td>\n\t\t\t\t\t\t\t</tr>\n\t\t\t\t\t<?php endif; ?>\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t<?php endforeach; ?>\n\t\t\t\t\t\t<?php else : ?>\n\t\t\t\t\t\t\t\n\t\t\t\t<tr><td colspan=\"4\"><em>no posts found for this translation group</em></td></tr>\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t<?php endif; // if $post_ids ?>\n\t\t</tbody><?php endforeach; // foreach $terms ?>\n\t\t<?php if ( $terms ) : ?>\n\t\t\t</table>\n\t\t<?php else : ?>\n\t\t\t<p><em>No translation groups found.</em></p>\n\t\t<?php endif;\n\t?>\n\n</div>"
  },
  {
    "path": "translation-fields.php",
    "content": "<?php\n/*\nPlugin Name: Babble Translation Fields\nPlugin URI:  http://babbleplugin.com/\nDescription: Support for translating meta fields in various popular plugins.\nVersion:     1.0\nAuthor:      Automattic\nAuthor URI:  https://automattic.com/\nText Domain: babble\nDomain Path: /languages/\nLicense:     GPL v2 or later\n\nThis program is free software; you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation; either version 2 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA\n\n*/\n\nfunction bbl_wpseo_meta_fields( array $fields, WP_Post $post ) {\n\tif ( class_exists( 'WPSEO_Meta' ) ) {\n\n\t\t$focuskw  = WPSEO_Meta::$meta_prefix . 'focuskw';\n\t\t$title    = WPSEO_Meta::$meta_prefix . 'title';\n\t\t$metadesc = WPSEO_Meta::$meta_prefix . 'metadesc';\n\n\t\t$fields[ $focuskw ]  = new Babble_Meta_Field_Text( $post, $focuskw, _x( 'SEO Focus Keyword', 'WordPress SEO plugin meta field', 'babble' ) );\n\t\t$fields[ $title ]    = new Babble_Meta_Field_Text( $post, $title, _x( 'SEO Title', 'WordPress SEO plugin meta field', 'babble' ) );\n\t\t$fields[ $metadesc ] = new Babble_Meta_Field_Textarea( $post, $metadesc, _x( 'Meta Description', 'WordPress SEO plugin meta field', 'babble' ) );\n\n\t\tforeach ( array(\n\t\t\t'opengraph'   => __( 'Facebook', 'wordpress-seo' ),\n\t\t\t'twitter'     => __( 'Twitter', 'wordpress-seo' ),\n\t\t\t'google-plus' => __( 'Google+', 'wordpress-seo' ),\n\t\t) as $network => $label ) {\n\n\t\t\t$title = WPSEO_Meta::$meta_prefix . $network . '-title';\n\t\t\t$desc  = WPSEO_Meta::$meta_prefix . $network . '-description';\n\n\t\t\t$fields[ $title ] = new Babble_Meta_Field_Text( $post, $title, sprintf( __( '%s Title', 'wordpress-seo' ), $label ) );\n\t\t\t$fields[ $desc ]  = new Babble_Meta_Field_Text( $post, $desc, sprintf( __( '%s Description', 'wordpress-seo' ), $label ) );\n\n\t\t}\n\n\t}\n\treturn $fields;\n}\n\nadd_filter( 'bbl_translated_meta_fields', 'bbl_wpseo_meta_fields', 10, 2 );\n"
  },
  {
    "path": "translation-group-tool-sorter.php",
    "content": "<?php \n\n// From: http://seancode.blogspot.com/2008/01/php-usort-sort-array-of-objects.html\n\nclass SortPosts {\n\tfunction post_type_descending( $m, $n ) {\n\t\tif ( $m->post_type == $n->post_type )\n\t\t\treturn 0;\n\n\t\treturn ( $m->post_type < $n->post_type ) ? -1 : 1;\n\t}\n}\n\n?>"
  },
  {
    "path": "translation-group-tool.php",
    "content": "<?php\n/*\nPlugin Name: Babble: Translation Group Tool\nPlugin URI: http://simonwheatley.co.uk/wordpress/btgt\nDescription: This provides a page in Admin > Tools which allows you to see and edit Babble translation associations.\nVersion: 0.1\nAuthor: Simon Wheatley\nAuthor URI: http://simonwheatley.co.uk/wordpress/\n*/\n \n/*  Copyright 2012 Simon Wheatley\n\nThis program is free software; you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation; either version 2 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA\n\n*/\n\nrequire_once 'class-plugin.php';\n\n/**\n * Handles the display and functionality of the translation group tool.\n * \n * @package BabbleTranslationGroupTool\n * @author Simon Wheatley\n **/\nclass BabbleTranslationGroupTool extends Babble_Plugin {\n\t\n\n\t/**\n\t * Initiate!\n\t *\n\t * @return void\n\t * @access public\n\t **/\n\tfunction __construct() {\n\t\t$this->setup( 'babble-tgt', 'plugin' );\n\t\t$this->add_action( 'admin_menu' );\n\t\t$this->add_action( 'load-post-new.php', 'load_post' );\n\t\t$this->add_action( 'load-post.php', 'load_post' );\n\t\t$this->add_action( 'load-tools_page_btgt', 'load_tools_page' );\n\t\t$this->add_action( 'save_post', null, null, 2 );\n\t\t$this->add_filter( 'bbl_metaboxes_for_translators', 'metaboxes_for_translators' );\n\t\t$this->add_filter( 'bbl_pre_sync_properties', 'pre_sync_properties', null, 2 );\n\t}\n\t\n\t// HOOKS AND ALL THAT\n\t// ==================\n\t\n\t/**\n\t * Hooks the WP admin_menu action to add a menu to\n\t * the Tools section.\n\t *\n\t * @return void\n\t **/\n\tpublic function admin_menu() {\n\t\tadd_management_page( __( 'Translation Groups', 'babble-tgt' ), __( 'Translation Groups', 'babble-tgt' ), 'manage_options', 'btgt', array( $this, 'tools_page' ) );\n\t}\n\n\t/**\n\t * Hooks the dynamic load-* action called when\n\t * this tools page loads.\n\t *\n\t * @return void\n\t **/\n\tpublic function load_tools_page() {\n\t\tif ( ! $action = ( isset( $_GET[ 'btgt_action' ] ) ) ? $_GET[ 'btgt_action' ] : false )\n\t\t\treturn;\n\t\t\n\t\t$obj_id = ( isset( $_GET[ 'obj_id' ] ) ) ? $_GET[ 'obj_id' ] : false;\n\t\t$wp_nonce = ( isset( $_GET[ '_wpnonce' ] ) ) ? $_GET[ '_wpnonce' ] : false;\n\t\tswitch ( $action ) {\n\t\t\tcase 'delete_from_groups':\n\t\t\t\tif ( ! wp_verify_nonce( $wp_nonce, \"btgt_delete_from_groups_$obj_id\" ) ) {\n\t\t\t\t\t$this->set_admin_error( 'Sorry, went wrong. Please try again.' );\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\twp_delete_object_term_relationships( $obj_id, 'post_translation' );\n\t\t\t\t$this->set_admin_notice( \"Deleted term relationships for $obj_id\" );\n\t\t\t\tbreak;\n\t\t\tcase 'delete_post':\n\t\t\t\twp_delete_object_term_relationships( $obj_id, 'post_translation' );\n\t\t\t\twp_delete_post( $obj_id, true );\n\t\t\t\tbreak;\n\t\t\tcase 'trash_post':\n\t\t\t\twp_delete_object_term_relationships( $obj_id, 'post_translation' );\n\t\t\t\twp_trash_post( $obj_id );\n\t\t\t\tbreak;\n\t\t}\n\t\t$args = array(\n\t\t\t'page' => 'btgt',\n\t\t\t'lang' => bbl_get_default_lang_code(),\n\t\t);\n\t\t$url = add_query_arg( $args, admin_url( 'tools.php' ) );\n\t\t$url .= '#' . $_GET[ 'anchor' ];\n\t\twp_redirect( $url );\n\t}\n\n\t/**\n\t * Hooks the various dynamic actions fired when the edit post and \n\t * edit new post screens are loaded. Determines if the post to be \n\t * edited has become disconnected from it's translation group,\n\t * and shows the Reconnect metabox if it has.\n\t *\n\t * @return void\n\t **/\n\tpublic function load_post() {\n\t\t$screen = get_current_screen();\n\t\tif ( ! $post_id = isset( $_GET[ 'post' ] ) ? $_GET[ 'post' ] : false )\n\t\t\treturn;\n\t\t$post = get_post( $post_id );\n\t\tif ( ! in_array( $post->post_status, array( 'draft', 'pending', 'publish' ) ) )\n\t\t\treturn;\n\t\tif ( bbl_get_post_lang_code( $post ) == bbl_get_default_lang_code() )\n\t\t\treturn;\n\t\tif ( $default_lang_post = bbl_get_post_in_lang( $post, bbl_get_default_lang_code() ) )\n\t\t\treturn;\n\t\t$this->add_meta_box( 'bbl_reconnect', 'Reconnect Translation', 'metabox_reconnect', $post->post_type, 'side' );\n\t}\n\n\t/**\n\t * Hooks the WP save_post action \n\t *\n\t * @param int $post_id The ID of the post being saved \n\t * @param object $post The WordPress post object being saved\n\t * @return void\n\t **/\n\tfunction save_post( $post_id, $post ) {\n\t\tif ( ! in_array( $post->post_status, array( 'draft', 'publish' ) ) )\n\t\t\treturn;\n\t\t\n\t\tif ( ! isset( $_POST[ '_bbl_reconnect_nonce' ] ) )\n\t\t\treturn;\n\t\t\t\n\t\t$posted_id = isset( $_POST[ 'post_ID' ] ) ? $_POST[ 'post_ID' ] : 0;\n\t\tif ( $posted_id != $post_id )\n\t\t\treturn;\n\t\t// While we're at it, let's check the nonce\n\t\tcheck_admin_referer( \"bbl_reconnect_translation_$post_id\", '_bbl_reconnect_nonce' );\n\t\t\t\n\t\t// Check the user has set a transid\n\t\tif ( ! $transid = isset( $_POST[ 'bbl_transid' ] ) ? (int) $_POST[ 'bbl_transid' ] : false )\n\t\t\treturn;\n\n\t\t// Check the transid the user has set actually exists\n\t\tif ( ! term_exists( $transid, 'post_translation' ) ) {\n\t\t\t$this->set_admin_error( __( 'The TransID you want to reconnect this content to does not exist. Please check the Translation Group information and try again.', 'babble' ) );\n\t\t\treturn;\n\t\t}\n\t\t\n\t\tglobal $bbl_post_public;\n\t\t$bbl_post_public->set_transid( $post, $transid );\n\t}\n\n\t/**\n\t * Hooks the Babble bbl_pre_sync_properties filter to\n\t * log any changes to parent. We're not making changes\n\t * to the data, just logging significant changes for\n\t * debug purposes.\n\t *\n\t * @param array $postdata The data which will be applied to the post as part of the sync\n\t * @param int $origin_id The ID of the post we are syncing from\n\t * @return array The data which will be applied to the post as part of the sync\n\t **/\n\tpublic function pre_sync_properties( $postdata, $origin_id ) {\n\t\t$current_post = get_post( $postdata[ 'ID' ] );\n\t\t$origin_post = get_post( $origin_id );\n\t\tif ( $current_post->post_parent != $postdata[ 'post_parent' ] ) {\n\t\t\t$user = wp_get_current_user();\n\t\t\t$remote_ip = $_SERVER[ 'REMOTE_ADDR' ];\n\t\t\t$referer = $_SERVER[ 'HTTP_REFERER' ];\n\t\t\t$lang = bbl_get_current_lang_code();\n\t\t\t$origin_lang = bbl_get_post_lang_code( $origin_id );\n\t\t\terror_log( \"Babble: $user->user_login has changed {$postdata[ 'ID' ]} parent from $current_post->post_parent ($current_post->post_type) to {$postdata[ 'post_parent' ]}. \\tOrigin: $origin_id. Origin lang: $origin_lang. IP $remote_ip. User lang: $lang. Referer $referer.\" );\n\t\t}\n\t\treturn $postdata;\n\t}\n\n\t/**\n\t * Hooks the Babble bbl_metaboxes_for_translators filter to \n\t * add the bbl_reconnect metabox to the list of boxes allowed\n\t * on translator screens.\n\t *\n\t * @param array $boxes The array of box names which are allowed \n\t * @return array The array of box names which are allowed \n\t **/\n\tfunction metaboxes_for_translators( $boxes ) {\n\t\t$boxes[] = 'bbl_reconnect';\n\t\treturn $boxes;\n\t}\n\n\t// CALLBACKS\n\t// =========\n\t\n\t/**\n\t * The callback function which provides HTML for the Babble \n\t * Translation Reconnection metabox, which allows an admin\n\t * to reconnect a post to the equivalent post in the\n\t * default language.\n\t *\n\t * @param object $post The WP Post object being edited\n\t * @param array $metabox The args and params for this metabox\n\t * @return void (echoes HTML)\n\t **/\n\tpublic function metabox_reconnect( $post, $metabox ) {\n\t\t\twp_nonce_field( \"bbl_reconnect_translation_{$post->ID}\", '_bbl_reconnect_nonce' );\n\t\t?>\n\t\t\t<p>\n\t\t\t\t<label for=\"bbl_transid\">TransID\n\t\t\t\t\t<input type=\"text\" name=\"bbl_transid\" value=\"\" id=\"bbl_transid\" />\n\t\t\t\t</label><br />\n\t\t\t\t<span class=\"description\">The option to specify a TransID will only show up if something terminal has happened to this translation. Do not change this if you do not know what you are doing.</span>\n\t\t\t</p>\n\t\t<?php\n\t}\n\n\t/**\n\t * Callback function for the HTML for the tools page.\n\t *\n\t * @return void\n\t **/\n\tpublic function tools_page() {\n\t\trequire_once 'translation-group-tool-sorter.php';\n\t\t$vars = array();\n\t\t$this->render_admin( 'translation-groups.php', $vars );\n\t}\n\n\t// UTILITIES\n\t// =========\n\n\t/**\n\t * Get a link to trash a particular post.\n\t *\n\t * @param int $post_id The ID of the post to trash\n\t * @param string $action The action for this link\n\t * @return string A Nonced action URL\n\t **/\n\tprotected function get_action_link( $obj_id, $action, $anchor = null ) {\n\t\t$args = array( \n\t\t\t'btgt_action' => $action,\n\t\t\t'obj_id' => $obj_id,\n\t\t\t'lang' => bbl_get_default_lang_code(), \n\t\t);\n\t\tif ( ! is_null( $anchor ) )\n\t\t\t$args[ 'anchor' ] = $anchor;\n\t\treturn wp_nonce_url( add_query_arg( $args ), \"btgt_{$action}_$obj_id\" );\n\t}\n\n\n} // END BabbleTranslationGroupTool class \n\n$bbl_translation_group_tool = new BabbleTranslationGroupTool();\n\n?>"
  },
  {
    "path": "widget.php",
    "content": "<?php\n\nadd_action( 'widgets_init', 'babble_widget' );\nfunction babble_widget() {\n\tregister_widget( 'Babble_Widget' );\n}\n\nclass Babble_Widget extends WP_Widget {\n\n\tpublic $defaults = array(\n\t\t'show_if_unavailable' => 'off',\n\t\t'show_as'             => 'dropdown',\n\t);\n\n\tfunction __construct() {\n\t\tparent::__construct(\n\t\t\t'bbl_widget', // Base ID\n\t\t\t__('Language Switcher','babble'), // Name\n\t\t\tarray( 'description' => __('Displays a list of links to translations / equivalent pages.', 'babble') ) // Args\n\t\t);\n\t}\n\n\tfunction widget( $args, $instance ) {\n\n\t\t$args = array_merge( array(\n\t\t\t'show_as' => 'dropdown',\n\t\t), $args );\n\n\t\techo $args['before_widget'];\n\n\t\techo $args['before_title'] . __( 'Languages', 'babble' ) . $args['after_title'];\n\n\t\t$list = bbl_get_switcher_links();\n\n\t\tswitch ( $instance['show_as'] ) {\n\n\t\tcase 'dropdown':\n\t\t\techo '<select onchange=\"document.location.href=this.options[this.selectedIndex].value;\">';\n\t\t\tforeach ( $list as $item ) :\n\n\t\t\t\tif ( $item['active'] ) {\n\t\t\t\t\t$selected = 'selected=\"selected\" ';\n\t\t\t\t} else {\n\t\t\t\t\t$selected = '';\n\t\t\t\t}\n\n\t\t\t\tif ( in_array( 'bbl-add',$item['classes'] ) ) {\n\t\t\t\t\t/*\n\t\t\t\t\t\tWe're logged in, there's no translation\n\t\t\t\t\t\tof this page as yet, but the user has the\n\t\t\t\t\t\tability to create one; so here's a link\n\t\t\t\t\t\tto allow him/her to do so\n\t\t\t\t\t*/\n\t\t\t\t\techo '<option ' . $selected . 'class=\"' . esc_attr( $item[ 'class' ] ) . '\" value=\"' . esc_url( $item[ 'href' ] ) . '\">' . esc_html( $item[ 'lang' ]->display_name ) . ' [' . __('Add') . ']</option>';\n\t\t\t\t}\n\t\t\t\telseif ( $item[ 'href'] ) {\n\t\t\t\t\t/*\n\t\t\t\t\t\tMeans there is a translation of this page\n\t\t\t\t\t\tinto the language in question\n\t\t\t\t\t*/\n\t\t\t\t\techo '<option ' . $selected . 'class=\"' . esc_attr( $item[ 'class' ] ) . '\" value=\"' . esc_url( $item[ 'href' ] ) . '\">' . esc_html( $item[ 'lang' ]->display_name ) . '</option>';\n\t\t\t\t}\n\t\t\t\telseif ( 'on' === $instance['show_if_unavailable'] ) {\n\t\t\t\t\t/*\n\t\t\t\t\t\tWe're on the front end, but there is\n\t\t\t\t\t\tno translation into this language as yet;\n\t\t\t\t\t\tthe user has said 'Show if unavailable'\n\t\t\t\t\t\tin the widget management screen.\n\t\t\t\t\t\tApplies a no-translation class to the <li>,\n\t\t\t\t\t\tand a bbl-no-translation class to the <div>\n\t\t\t\t\t\tin case you want to 'grey it out' somehow\n\t\t\t\t\t*/\n\t\t\t\t\techo '<option disabled class=\"' . esc_attr( $item[ 'class' ] ) . '\" value=\"\">' . esc_html( $item[ 'lang' ]->display_name ) . '</option>';\n\t\t\t\t}\n\t\t\tendforeach;\n\t\t\techo '</select>';\n\t\tbreak;\n\n\t\tcase 'list':\n\t\t\techo '<ul class=\"languages_list\">';\n\t\t\tforeach ( $list as $item ) :\n\n\t\t\t\tif ( in_array( 'bbl-add',$item['classes'] ) ) {\n\t\t\t\t\t/*\n\t\t\t\t\t\tWe're logged in, there's no translation\n\t\t\t\t\t\tof this page as yet, but the user has the\n\t\t\t\t\t\tability to create one; so here's a link\n\t\t\t\t\t\tto allow him/her to do so\n\t\t\t\t\t*/\n\t\t\t\t\techo '<li><a href=\"' . esc_url( $item[ 'href' ] ) . '\" class=\"' . esc_attr( $item[ 'class' ] ) . '\">' . esc_html( $item[ 'lang' ]->display_name ) . ' [' . __( 'Add', 'babble' ) . ']</a></li>';\n\t\t\t\t}\n\t\t\t\telseif ( $item[ 'href'] ) {\n\t\t\t\t\t/*\n\t\t\t\t\t\tMeans there is a translation of this page\n\t\t\t\t\t\tinto the language in question\n\t\t\t\t\t*/\n\t\t\t\t\techo '<li><a href=\"' . esc_url( $item[ 'href' ] ) . '\" class=\"' . esc_attr( $item[ 'class' ] ) . '\">' . esc_html( $item[ 'lang' ]->display_name ) . '</a></li>';\n\t\t\t\t}\n\t\t\t\telseif ( 'on' === $instance['show_if_unavailable'] ) {\n\t\t\t\t\t/*\n\t\t\t\t\t\tWe're on the front end, but there is\n\t\t\t\t\t\tno translation into this language as yet;\n\t\t\t\t\t\tthe user has said 'Show if unavailable'\n\t\t\t\t\t\tin the widget management screen.\n\t\t\t\t\t\tApplies a no-translation class to the <li>,\n\t\t\t\t\t\tand a bbl-no-translation class to the <div>\n\t\t\t\t\t\tin case you want to 'grey it out' somehow\n\t\t\t\t\t*/\n\t\t\t\t\techo '<li class=\"no-translation\"><div class=\"bbl-no-translation\">' . esc_html( $item[ 'lang' ]->display_name ) . '</div></li>';\n\t\t\t\t}\n\n\t\t\tendforeach;\n\t\t\techo '</ul>';\n\t\tbreak;\n\n\t\t}\n\n\t\techo $args['after_widget'];\n\t}\n\n\tfunction update( $new_instance, $old_instance ) {\n\n\t\t$new_instance = array_merge( $this->defaults, $new_instance );\n\t\t$new_instance['show_as'] = strip_tags( $new_instance['show_as'] );\n\t\t$new_instance['show_if_unavailable'] = strip_tags( $new_instance['show_if_unavailable'] );\n\n\t\treturn $new_instance;\n\n\t}\n\n\tfunction form( $instance ) {\n\n\t\tglobal $wpdb;\n\t\t$instance = wp_parse_args( $instance, $this->defaults );\n\n\t\t?>\n\t\t<p>\n\t\t\t<?php _e('Show as:','babble'); ?>\n\t\t\t<select id=\"<?php echo $this->get_field_id('show_as'); ?>\" name=\"<?php echo $this->get_field_name('show_as'); ?>\">\n\t\t\t\t<option value=\"dropdown\" <?php selected( $instance['show_as'],'dropdown' ); ?>><?php _e('Dropdown','babble'); ?></option>\n\t\t\t\t<option value=\"list\" <?php selected( $instance['show_as'],'list' ); ?>><?php _e('List','babble'); ?></option>\n\t\t\t</select>\n\t\t</p>\n\t\t<p>\n\t\t\t<input id=\"<?php echo $this->get_field_id('show_if_unavailable'); ?>\" name=\"<?php echo $this->get_field_name('show_if_unavailable'); ?>\" type=\"checkbox\" <?php checked( 'on', $instance['show_if_unavailable'] ); ?> />\n\t\t\t<label for=\"<?php echo $this->get_field_id('show_if_unavailable'); ?>\"><?php _e('Show all languages in widget, even if there is no translation', 'babble'); ?></label>\n\t\t</p>\n\t\t<p class=\"description\">\n\t\t\t<?php _e(\"Don't worry: if there's no equivalent page, the link won't be clickable.\",\"babble\"); ?>\n\t\t</p>\n\t\t<p class=\"description\">\n\t\t\t<?php _e(\"Links allowing logged-in administrators to add translations will always be shown.\",\"babble\"); ?>\n\t\t</p>\n\n\t\t<?php\n\t}\n}\n"
  }
]