Showing preview only (372K chars total). Download the full file or copy to clipboard to get everything.
Repository: Automattic/babble
Branch: main
Commit: 7f89ef408984
Files: 54
Total size: 354.0 KB
Directory structure:
gitextract_5i66y00t/
├── CONTRIBUTING.md
├── api.php
├── babble.php
├── class-admin-bar.php
├── class-babble-log.php
├── class-comment.php
├── class-jobs.php
├── class-languages.php
├── class-locale.php
├── class-meta.php
├── class-plugin.php
├── class-post-public.php
├── class-switcher-content.php
├── class-switcher-interface.php
├── class-taxonomy.php
├── class-translator.php
├── class-updates.php
├── composer.json
├── css/
│ ├── jobs-admin.css
│ └── languages-options.css
├── deprecated.php
├── external-update-api/
│ ├── external-update-api/
│ │ ├── euapi.php
│ │ ├── handler-files.php
│ │ ├── handler-github.php
│ │ ├── handler.php
│ │ ├── info.php
│ │ ├── item-plugin.php
│ │ ├── item-theme.php
│ │ ├── item.php
│ │ └── update.php
│ ├── external-update-api.php
│ └── readme.md
├── js/
│ └── post-public-admin.js
├── languages/
│ ├── fa_IR.mo
│ ├── fa_IR.php
│ ├── fr_FR.mo
│ ├── pt_BR.mo
│ ├── pt_BR.po
│ ├── tr.mo
│ └── tr.po
├── miscellaneous.php
├── readme.md
├── readme.txt
├── templates-admin/
│ ├── options-available-languages.php
│ ├── switcher-interface.php
│ ├── translation-editor-meta.php
│ ├── translation-editor-post-excerpt.php
│ ├── translation-editor-terms.php
│ ├── translation-editor.php
│ └── translation-groups.php
├── translation-fields.php
├── translation-group-tool-sorter.php
├── translation-group-tool.php
└── widget.php
================================================
FILE CONTENTS
================================================
================================================
FILE: CONTRIBUTING.md
================================================
Contribute
==========
First off: thanks for looking into contributing to Babble, we appreciate your help.
Setting up
----------
1. Clone this git repository on your WordPress development site.
2. Switch to the develop branch
Submitting patches
------------------
Whether you want to fix a bug or implement a new feature, the process is much the same:
0. [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.
1. [Fork](https://github.com/cftp/babble/fork) the repository.
2. Push the code changes from your local clone to your fork.
3. Open a pull request to the *develop* branch.
It doesn't matter if the code isn't perfect. The idea is to get it reviewed early and iterate on it.
Please follow the [WordPress Coding Standards](http://make.wordpress.org/core/handbook/coding-standards/).
Licence
-------
The Babble plugin is released under the [GPLv2 or later](http://www.gnu.org/licenses/gpl-2.0.html),
by contributing code to Babble you grant its use under the GNU General Public License v2 (or later).
================================================
FILE: api.php
================================================
<?php
/**
* Translations and languages API.
*
* @package Babble
* @since Alpha 1
*/
/* Copyright 2013 Code for the People
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/**
* Returns the current content language code.
*
* @FIXME: Currently does not check for language validity, though perhaps we should check that elsewhere and redirect?
*
* @return string A language code
* @access public
**/
function bbl_get_current_content_lang_code() {
global $bbl_locale;
return $bbl_locale->get_content_lang();
}
/**
* Returns the current interface language code.
*
* @FIXME: Currently does not check for language validity, though perhaps we should check that elsewhere and redirect?
*
* @return string A language code
* @access public
**/
function bbl_get_current_interface_lang_code() {
global $bbl_locale;
return $bbl_locale->get_interface_lang();
}
/**
* Returns the current (content) language code.
*
* @return string A language code
* @access public
**/
function bbl_get_current_lang_code() {
return bbl_get_current_content_lang_code();
}
/**
* Given a lang object or lang code, this checks whether the
* language is public or not.
*
* @param string $lang_code A language code
* @return boolean True if public
* @access public
**/
function bbl_is_public_lang( $lang_code ) {
global $bbl_languages;
return $bbl_languages->is_public_lang( $lang_code );
}
/**
* Set the current (content) lang.
*
* @uses Babble_Locale::switch_to_lang to do the actual work
* @see switch_to_blog for similarities
*
* @param string $lang The language code to switch to
* @return void
**/
function bbl_switch_to_lang( $lang ) {
global $bbl_locale;
$bbl_locale->switch_to_lang( $lang );
}
/**
* Restore the previous lang.
*
* @uses Babble_Locale::restore_lang to do the actual work
* @see restore_current_blog for similarities
*
* @return void
**/
function bbl_restore_lang() {
global $bbl_locale;
$bbl_locale->restore_lang();
}
/**
* Get the terms which are the translations for the provided
* term ID. N.B. The returned array of term objects (and false
* values) will include the term for the term ID passed.
*
* @param int|object $term Either a WP Term object, or a term_id
* @return array Either an array keyed by the site languages, each key containing false (if no translation) or a WP Post object
* @access public
**/
function bbl_get_term_translations( $term, $taxonomy ) {
global $bbl_taxonomies;
return $bbl_taxonomies->get_term_translations( $term, $taxonomy );
}
/**
* Get the posts which are the translation jobs for the provided
* term ID.
*
* @param int|object $term Either a WP Term object, or a term_id
* @return array An array keyed by the site languages, each key containing a WP Post object
* @access public
**/
function bbl_get_term_jobs( $term, $taxonomy ) {
global $bbl_jobs;
return $bbl_jobs->get_term_jobs( $term, $taxonomy );
}
/**
* Return the admin URL to create a new translation for a term in a
* particular language.
*
* @param int|object $default_term The term in the default language to create a new translation for, either WP Post object or post ID
* @param string $lang The language code
* @param string $taxonomy The taxonomy
* @return string The admin URL to create the new translation
* @access public
**/
function bbl_get_new_term_translation_url( $default_term, $lang, $taxonomy = null ) {
global $bbl_taxonomies;
return $bbl_taxonomies->get_new_term_translation_url( $default_term, $lang, $taxonomy );
}
/**
* Returns the language code associated with a particular taxonomy.
*
* @param string $taxonomy The taxonomy to get the language for
* @return string The lang code
**/
function bbl_get_taxonomy_lang_code( $taxonomy ) {
global $bbl_taxonomies;
return $bbl_taxonomies->get_taxonomy_lang_code( $taxonomy );
}
/**
* Return the base taxonomy (in the default language) for a
* provided taxonomy.
*
* @param string $taxonomy The name of a taxonomy
* @return string The name of the base taxonomy
**/
function bbl_get_base_taxonomy( $taxonomy ) {
global $bbl_taxonomies;
return $bbl_taxonomies->get_base_taxonomy( $taxonomy );
}
/**
* Returns the equivalent taxonomy in the specified language.
*
* @param string $taxonomy A taxonomy to return in a given language
* @param string $lang_code The language code for the required language (optional, defaults to current)
* @return string The taxonomy name
**/
function bbl_get_taxonomy_in_lang( $taxonomy, $lang_code = null ) {
global $bbl_taxonomies;
return $bbl_taxonomies->get_taxonomy_in_lang( $taxonomy, $lang_code );
}
/**
* Test whether a particular taxonomy is translated or not.
*
* @param string $taxonomy The name of the taxonomy to check
* @return bool True if this is a translated taxonomy
*/
function bbl_is_translated_taxonomy( $taxonomy ) {
return (bool) apply_filters( 'bbl_translated_taxonomy', true, $taxonomy );
}
/**
* Test whether a particular post type is translated or not.
*
* @param string $post_type The name of the post type to check
* @return bool True if this is a translated post type
*/
function bbl_is_translated_post_type( $post_type ) {
return (bool) apply_filters( 'bbl_translated_post_type', true, $post_type );
}
/**
* Returns a taxonomy slug translated into a particular language.
*
* @param string $slug The slug to translate
* @param string $lang_code The language code for the required language (optional, defaults to current)
* @return string A translated slug
**/
function bbl_get_taxonomy_slug_in_lang( $slug, $lang_code = null ) {
global $bbl_taxonomies;
return $bbl_taxonomies->get_slug_in_lang( $slug, $lang_code );
}
/**
* Get the posts which are the translations for the provided
* post ID. N.B. The returned array of post objects (and false
* values) will include the post for the post ID passed.
*
* @param int|object $post Either a WP Post object, or a post ID
* @return array Either an array keyed by the site languages, each key containing false (if no translation) or a WP Post object
* @access public
**/
function bbl_get_post_translations( $post ) {
global $bbl_post_public;
return $bbl_post_public->get_post_translations( $post );
}
/**
* Get the posts which are the translation jobs for the provided
* post ID.
*
* @param int|object $post Either a WP Post object, or a post ID
* @return array Either an array keyed by the site languages, each key containing a WP Post object
* @access public
**/
function bbl_get_incomplete_post_jobs( $post ) {
global $bbl_jobs;
return $bbl_jobs->get_incomplete_post_jobs( $post );
}
/**
* Returns the post ID for the post in the default language from which
* this post was translated.
*
* @param int|object $post Either a WP Post object, or a post ID
* @return int The ID of the default language equivalent post
* @access public
**/
function bbl_get_default_lang_post( $post ) {
global $bbl_post_public;
return $bbl_post_public->get_default_lang_post( $post );
}
/**
* Return the language code for the language a given post is written for/in.
*
* @param int|object $post Either a WP Post object, or a post ID
* @return string|object Either a language code, or a WP_Error object
* @access public
**/
function bbl_get_post_lang_code( $post ) {
global $bbl_post_public;
return $bbl_post_public->get_post_lang_code( $post );
}
/**
* Return the admin URL to create a new translation for a post in a
* particular language.
*
* @param int|object $default_post The post in the default language to create a new translation for, either WP Post object or post ID
* @param string $lang The language code
* @return string The admin URL to create the new translation
* @access public
**/
function bbl_get_new_post_translation_url( $default_post, $lang ) {
global $bbl_post_public;
return $bbl_post_public->get_new_post_translation_url( $default_post, $lang );
}
/**
* Return the post type name for the equivalent post type for the
* supplied original post type in the requested language.
*
* @param string $post_type The originating post type
* @param string $lang_code The language code for the required language (optional, defaults to current)
* @return string A post type name, e.g. "page" or "post"
**/
function bbl_get_post_type_in_lang( $original_post_type, $lang_code = null ) {
global $bbl_post_public;
if ( is_null( $lang_code ) )
$lang_code = bbl_get_current_lang_code();
return $bbl_post_public->get_post_type_in_lang( $original_post_type, $lang_code );
}
add_filter( 'bbl_get_content_post_type', 'bbl_get_post_type_in_lang' );
/**
* Is the query for a single page or translation or a single page?
*
* If the $page parameter is specified, this function will additionally
* check if the query is for one of the pages specified.
*
* @see is_page()
*
* @param mixed $page Page ID, title, slug, or array of such.
* @return bool
*/
function bbl_is_page( $page = '' ) {
$base_page = bbl_get_post_in_lang( get_the_ID(), bbl_get_default_lang_code() );
if ( ! $page )
return 'page' == $base_page->post_type;
if ( is_int( $page ) )
return $page == $base_page->ID;
if ( $page == $base_page->post_name )
return true;
if ( $page == $base_page->post_title )
return true;
if ( $page == (string) $base_page->ID )
return true;
return false;
}
/**
* Returns the post in a particular language
*
* @param int|object $post Either a WP Post object, or a post ID
* @param string $lang_code The language code for the required language
* @param boolean $fallback If true: if a post is not available, fallback to the default language content (defaults to true)
* @return object|boolean The WP Post object, or if $fallback was false and no post then returns false
**/
function bbl_get_post_in_lang( $post, $lang_code, $fallback = true ) {
global $bbl_post_public;
return $bbl_post_public->get_post_in_lang( $post, $lang_code, $fallback );
}
/**
* Returns the term in a particular language
*
* @param int|object $term Either a term object, or a term ID
* @param string $taxonomy The term taxonomy
* @param string $lang_code The language code for the required language
* @param boolean $fallback If true: if a term is not available, fallback to the default language content (defaults to true)
* @return object|boolean The term object, or if $fallback was false and no term then returns false
**/
function bbl_get_term_in_lang( $term, $taxonomy, $lang_code, $fallback = true ) {
global $bbl_taxonomies;
return $bbl_taxonomies->get_term_in_lang( $term, $taxonomy, $lang_code, $fallback );
}
/**
* Returns a post_type slug translated into a particular language.
*
* @param string $slug The slug to translate
* @param string $lang_code The language code for the required language (optional, defaults to current)
* @return string A translated slug
**/
function bbl_get_post_type_slug_in_lang( $slug, $lang_code = null ) {
global $bbl_post_public;
$lang = bbl_get_lang( $lang_code );
return $bbl_post_public->get_slug_in_lang( $slug, $lang );
}
/**
* Echoes the title of a post, in the requested language (if available).
*
* @param int|object $post Either a WP Post object, or a post ID
* @param string $lang_code The code for the language the title is requested in
* @param bool $fallback Whether to provide a fallback title in the default language if the requested language is unavailable (defaults to false)
* @return void
**/
function bbl_the_title_in_lang( $post = null, $lang_code = null, $fallback = false ) {
echo bbl_get_the_title_in_lang( $post, $lang_code, $fallback );
}
/**
* Returns the title of a post, in the requested language (if available).
*
* @param int|object $post Either a WP Post object, or a post ID
* @param string $lang_code The code for the language the title is requested in
* @param bool $fallback Whether to provide a fallback title in the default language if the requested language is unavailable (defaults to false)
* @return void
**/
function bbl_get_the_title_in_lang( $post = null, $lang_code = null, $fallback = false ) {
$post = get_post( $post );
if ( is_null( $lang_code ) )
$lang_code = bbl_get_current_lang_code();
// Hopefully we find the post in the right language
if ( $lang_post = bbl_get_post_in_lang( $post, $lang_code, $fallback ) )
return apply_filters( 'bbl_the_title_in_lang', get_the_title( $lang_post->ID ), $lang_code );
// We have failed…
return '';
}
/**
* Echoes the permalink of a post, in the requested language (if available).
*
* @param int|object $post Either a WP Post object, or a post ID
* @param string $lang_code The code for the language the title is requested in
* @param bool $fallback Whether to provide a fallback title in the default language if the requested language is unavailable (defaults to false)
* @return void
**/
function bbl_the_permalink_in_lang( $post = null, $lang_code = null, $fallback = false ) {
echo bbl_get_the_permalink_in_lang( $post, $lang_code, $fallback );
}
/**
* Returns the permalink of a post, in the requested language (if available).
*
* @param int|object $post Either a WP Post object, or a post ID
* @param string $lang_code The code for the language the title is requested in
* @param bool $fallback Whether to provide a fallback title in the default language if the requested language is unavailable (defaults to false)
* @return void
**/
function bbl_get_the_permalink_in_lang( $post = null, $lang_code = null, $fallback = false ) {
$post = get_post( $post );
if ( is_null( $lang_code ) )
$lang_code = bbl_get_current_lang_code();
// Hopefully we find the post in the right language
if ( $lang_post = bbl_get_post_in_lang( $post, $lang_code, $fallback ) )
return apply_filters( 'bbl_permalink_in_lang', get_permalink( $lang_post->ID ), $lang_code );
// We have failed…
return '';
}
/**
* Returns the link to a post type in a particular language.
*
* @param string $post_type A post type for which you want a translated archive link
* @param string $lang_code The code for the language the link is requested in
* @return void
**/
function bbl_get_post_type_archive_link_in_lang( $post_type, $lang_code = null ) {
if ( is_null( $lang_code ) )
$lang_code = bbl_get_current_lang_code();
bbl_switch_to_lang( $lang_code );
$lang_post_type = bbl_get_post_type_in_lang( $post_type, $lang_code );
$link = get_post_type_archive_link( $lang_post_type );
bbl_restore_lang();
return apply_filters( 'bbl_post_type_archive_link_in_lang', $link );
}
/**
* Return the base post type (in the default language) for a
* provided post type.
*
* @param string $post_type The name of a post type
* @return string The name of the base post type
**/
function bbl_get_base_post_type( $post_type ) {
global $bbl_post_public;
return $bbl_post_public->get_base_post_type( $post_type );
}
/**
* Return all the base post types (in the default language).
*
* @return array An array of post_type objects
**/
function bbl_get_base_post_types() {
global $bbl_post_public;
return $bbl_post_public->get_base_post_types();
}
/**
* Returns an array of all the shadow post types associated with
* this post type.
*
* @param string $base_post_type The post type to look up shadow post types for
* @return array The names of all the related shadow post types
**/
function bbl_get_shadow_post_types( $base_post_type ) {
global $bbl_post_public;
return $bbl_post_public->get_shadow_post_types( $base_post_type );
}
/**
* Return the active language objects for the current site. A
* language object looks like:
* 'ar' =>
* object(stdClass)
* public 'name' => string 'Arabic'
* public 'code' => string 'ar'
* public 'url_prefix' => string 'ar'
* public 'text_direction' => string 'rtl'
* public 'display_name' => string 'Arabic'
*
* @uses Babble_Languages::get_active_langs to do the actual work
*
* @return array An array of Babble language objects
**/
function bbl_get_active_langs() {
global $bbl_languages;
return $bbl_languages->get_active_langs();
}
/**
* Returns the requested language object.
*
* @param string $code A language code, e.g. "fr_BE"
* @return object|boolean A Babble language object
**/
function bbl_get_lang( $lang_code ) {
global $bbl_languages;
return $bbl_languages->get_lang( $lang_code );
}
/**
* Returns the current language object, respecting any
* language switches; i.e. if your request was for
* Arabic, but the language is currently switched to
* French, this will return French.
*
* @return object A Babble language object
**/
function bbl_get_current_lang() {
global $bbl_languages;
return $bbl_languages->get_current_lang();
}
/**
* Returns the default language code for this site.
*
* @return string A language code, e.g. "he_IL"
**/
function bbl_get_default_lang_code() {
global $bbl_languages;
return $bbl_languages->get_default_lang_code();
}
/**
* Returns the default language for this site.
*
* @return object A language object
**/
function bbl_get_default_lang() {
global $bbl_languages;
return $bbl_languages->get_default_lang();
}
/**
* Checks whether either the provided language code,
* if provided, or the current language code are
* the default language.
*
* i.e. is this language the default language
*
* n.b. the current language could have been switched
* using bbl_switch_to_lang
*
* @param string $lang_code The language code to check (optional)
* @return bool True if the default language
**/
function bbl_is_default_lang( $lang_code = null ) {
if ( is_null( $lang_code ) )
$lang = bbl_get_current_lang();
else if ( is_string( $lang_code ) ) // In case someone passes a lang object
$lang = bbl_get_lang( $lang_code );
return ( bbl_get_default_lang_code() == $lang->code );
}
/**
* Returns the default language code for this site.
*
* @return string The language URL prefix set by the admin, e.g. "de"
**/
function bbl_get_default_lang_url_prefix() {
global $bbl_languages;
$code = $bbl_languages->get_default_lang_code();
return $bbl_languages->get_url_prefix_from_code( $code );
}
/**
* Returns the language code for the provided URL prefix.
*
* @param string $url_prefix The URL prefix to find the language code for
* @return string The language code, or false
**/
function bbl_get_lang_from_prefix( $url_prefix ) {
global $bbl_languages;
return $bbl_languages->get_code_from_url_prefix( $url_prefix );
}
/**
* Returns the language code for the provided URL prefix.
*
* @param string $lang_code The language code to look up
* @return string The language URL prefix set by the admin, e.g. "de"
**/
function bbl_get_prefix_from_lang_code( $lang_code ) {
global $bbl_languages;
return $bbl_languages->get_url_prefix_from_code( $lang_code );
}
/**
* Returns the switch links for the current content.
*
* @param string $id_prefix A prefix to the ID for each item
* @return array An array of admin menu nodes
**/
function bbl_get_switcher_links( $id_prefix = '' ) {
global $bbl_switcher_menu;
return $bbl_switcher_menu->get_switcher_links( $id_prefix );
}
/**
* Start logging for Babble
*
* @return void
**/
function bbl_start_logging() {
global $bbl_log;
$bbl_log->logging = true;
}
/**
* Stop logging for Babble
*
* @return void
**/
function bbl_stop_logging() {
global $bbl_log;
$bbl_log->logging = false;
}
/**
* Log a message.
*
* @param string $msg Log this message
* @return void
**/
function bbl_log( $msg ) {
global $bbl_log;
if ( $bbl_log )
$bbl_log->log( $msg );
else
error_log( "Full Babble logging unavailable: $msg" );
}
/**
* Whether Babble is logging right now.
*
* @return boolean True for yes, natch
**/
function bbl_is_logging() {
global $bbl_log;
return $bbl_log->logging;
}
?>
================================================
FILE: babble.php
================================================
<?php
/*
Plugin Name: Babble
Plugin URI: http://babbleplugin.com/
Description: Multilingual WordPress done right
Version: 1.5.1
Author: Automattic
Author URI: https://automattic.com/
Text Domain: babble
Domain Path: /languages/
License: GPL v2 or later
Copyright 2011-2015 Simon Wheatley, Code For The People Ltd, & Automattic Ltd
_____________
/ ____ \
_____/ \ \ \
/\ \ \___\ \
/ \ \ \
/ / / _______\
/ / / \ /
/ / / \ /
\ \ \ _____ ___\ /
\ \ /\ \ / \
\ \ / \____\/ _____\
\ \/ / / / \
\ /____/ /___\
\ /
\______________________/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/**
* Main plugin information and requires.
*
* @package Babble
* @since Alpha 1
* @copyright Copyright (c) Simon Wheatley & Code For The People Ltd (except where noted)
*/
require_once 'class-babble-log.php';
require_once 'api.php';
require_once 'deprecated.php';
require_once 'widget.php';
require_once 'class-plugin.php';
require_once 'class-jobs.php';
require_once 'class-meta.php';
require_once 'class-languages.php';
require_once 'class-locale.php';
require_once 'class-post-public.php';
require_once 'class-comment.php';
require_once 'class-taxonomy.php';
require_once 'class-switcher-content.php';
require_once 'class-switcher-interface.php';
require_once 'class-admin-bar.php';
require_once 'class-translator.php';
require_once 'class-updates.php';
require_once 'miscellaneous.php';
================================================
FILE: class-admin-bar.php
================================================
<?php
/**
* Class to handle adding our links to the admin bar.
*
* @package Babble
* @since 0.2
*/
class Babble_Admin_bar extends Babble_Plugin {
function __construct() {
$this->setup( 'babble-switcher-menu', 'plugin' );
$this->add_action( 'admin_bar_menu', null, 100 );
}
/**
* Hooks the WP admin_bar_menu action
*
* @param object $wp_admin_bar The WP Admin Bar, passed by reference
* @return void
**/
public function admin_bar_menu( $wp_admin_bar ) {
$links = bbl_get_switcher_links( 'bbl-admin-bar' );
$current_lang = bbl_get_current_lang();
// Remove the current language
unset( $links[ $current_lang->code ] );
$parent_id = "bbl-admin-bar-{$current_lang->url_prefix}";
$wp_admin_bar->add_menu( array(
'children' => array(),
'href' => '#',
'id' => $parent_id,
'meta' => array( 'class' => "bbl_lang_{$current_lang->code} bbl_lang" ),
'title' => $current_lang->display_name,
'parent' => false,
) );
foreach ( $links as & $link ) {
$link[ 'parent' ] = $parent_id;
$wp_admin_bar->add_menu( $link );
}
}
}
global $bbl_admin_bar;
$bbl_admin_bar = new Babble_Admin_bar();
================================================
FILE: class-babble-log.php
================================================
<?php
/**
* Manages the locale currently set for the site.
*
* @package Babble
* @since Alpha 1
*/
class Babble_Log {
/**
* Whether to log or not.
*
* @var boolean
**/
public $logging = false;
/**
* A unique ID so we can identify different sessions in
* the error log.
*
* @var string
**/
protected $session;
/**
* Construction time!
*
* @return void
**/
public function __construct() {
$this->session = uniqid();
}
/**
* Hooks the WP admin_init action
*
* @return void
**/
public function log( $msg ) {
if ( $this->logging )
error_log( "[$this->session] BABBLE LOG: $msg" );
}
}
global $bbl_log;
$bbl_log = new Babble_Log();
?>
================================================
FILE: class-comment.php
================================================
<?php
/**
* Class for handling comments.
*
* @package Babble
* @since 0.1
*/
class Babble_Comment extends Babble_Plugin {
public function __construct() {
$this->setup( 'babble-comment', 'plugin' );
$this->add_filter( 'comments_template_args' );
$this->add_filter( 'preprocess_comment' );
$this->add_filter( 'get_comments_number', null, null, 2 );
}
/**
* Hooks the comments_template_args on Bbl_Comment_Query,
* and hopefully soon on WP_Comment_Query (Trac #19623),
* in order to ensure we get the comments from all the
* translated posts in this translation group.
*
* @param array $args The args for WP_Comment_Query in comments_template
* @return array The args for WP_Comment_Query in comments_template
**/
public function comments_template_args( $args ) {
if ( isset( $args[ 'post_id' ] ) && ! empty( $args[ 'post_id' ] ) ) {
$posts = bbl_get_post_translations( $args[ 'post_id' ] );
if ( isset( $args[ 'post__in' ] ) && ! is_array( $args[ 'post__in' ] ) )
$args[ 'post__in' ] = array();
foreach ( $posts as & $post )
$args[ 'post__in' ][] = $post->ID;
unset( $args[ 'post_id' ] );
}
return $args;
}
/**
* Hooks the WP preprocess_comment filter to ensure that when someone
* replies to a comment which has been included in a merged comment
* stream on a post in a different language, the reply is assigned
* language post of the parent comment.
*
* @param array $comment_data The comment data
* @return void
**/
public function preprocess_comment( $comment_data ) {
// If comment_post_ID exists in the data, the only acceptable
// value is the same as the parent comment's comment_post_ID
$parent_comment = get_comment( $comment_data[ 'comment_parent' ] );
if ( $parent_comment && $comment_data[ 'comment_post_ID' ] )
$comment_data[ 'comment_post_ID' ] = $parent_comment->comment_post_ID;
return $comment_data;
}
/**
* Hooks the WP get_comments_number filter to get the number of comments
* across all posts in the translation group.
*
* @param int $count The number of comments on the single translation
* @param int $post_id The post ID of the single translation
* @return int The count of all comments on published posts in this translation group
**/
public function get_comments_number( $count, $post_id ) {
$translations = bbl_get_post_translations( $post_id );
$count = 0;
foreach ( $translations as & $translation ) {
$post_status = get_post_status_object( $translation->post_status );
// FIXME: I'm not entirely sure about using publicly_queryable here… what I want to avoid is draft, private, etc statii.
if ( $post_status->publicly_queryable )
$count += $translation->comment_count;
}
return $count;
}
// PUBLIC METHODS
// ==============
// PRIVATE/PROTECTED METHODS
// =========================
}
global $bbl_comment;
$bbl_comment = new Babble_Comment();
?>
================================================
FILE: class-jobs.php
================================================
<?php
/**
* Class for handling jobs for the various language
* translation teams.
*
* @package Babble
* @since 1.4
*/
class Babble_Jobs extends Babble_Plugin {
/**
* A version number used for cachebusting, rewrite rule
* flushing, etc.
*
* @var int
**/
protected $version;
/**
* A simple flag to stop infinite recursion in various places.
*
* @var boolean
**/
protected $no_recursion;
public function __construct() {
$this->setup( 'babble-job', 'plugin' );
$this->add_action( 'add_meta_boxes' );
$this->add_action( 'add_meta_boxes_bbl_job', null, 999 );
$this->add_action( 'admin_init' );
$this->add_action( 'admin_menu' );
$this->add_action( 'babble_create_empty_translation', 'create_empty_translation' );
$this->add_action( 'bbl_translation_post_meta_boxes', null, 10, 3 );
$this->add_action( 'bbl_translation_submit_meta_boxes', null, 10, 2 );
$this->add_action( 'bbl_translation_terms_meta_boxes', null, 10, 2 );
$this->add_action( 'bbl_translation_meta_meta_boxes', null, 10, 2 );
$this->add_action( 'edit_form_after_title' );
$this->add_action( 'init', 'init_early', 0 );
$this->add_action( 'load-post.php', 'load_post_edit' );
$this->add_action( 'manage_bbl_job_posts_custom_column', 'action_column', null, 2 );
$this->add_action( 'pre_get_posts' );
$this->add_action( 'save_post', 'save_job', null, 2 );
$this->add_action( 'save_post', null, null, 2 );
$this->add_action( 'wp_before_admin_bar_render' );
$this->add_filter( 'admin_title', null, null, 2 );
$this->add_filter( 'bbl_translated_post_type', null, null, 2 );
$this->add_filter( 'bbl_translated_taxonomy', null, null, 2 );
$this->add_filter( 'get_edit_post_link', null, null, 3 );
$this->add_filter( 'manage_bbl_job_posts_columns', 'filter_columns' );
$this->add_filter( 'post_updated_messages' );
$this->add_filter( 'query_vars' );
$this->add_filter( 'user_has_cap', null, null, 3 );
$this->add_filter( 'wp_insert_post_empty_content', null, null, 2 );
$this->version = 1.1;
}
public function add_meta_boxes_bbl_job( WP_Post $post ) {
# Unapologetically remove all meta boxes from the translation screen:
global $wp_meta_boxes;
unset( $wp_meta_boxes['bbl_job'] );
}
public function wp_insert_post_empty_content( $maybe_empty, $postarr ) {
// Allow translations to have empty content
if ( bbl_get_base_post_type( $postarr['post_type'] ) != $postarr['post_type'] )
return false;
return $maybe_empty;
}
public function bbl_translated_post_type( $translated, $post_type ) {
if ( 'bbl_job' == $post_type )
return false;
return $translated;
}
public function bbl_translated_taxonomy( $translated, $taxonomy ) {
if ( 'bbl_job_language' == $taxonomy )
return false;
return $translated;
}
/**
* Add our post type updated messages.
*
* The messages are as follows:
*
* 1 => "Post updated. {View Post}"
* 2 => "Custom field updated."
* 3 => "Custom field deleted."
* 4 => "Post updated."
* 5 => "Post restored to revision from [date]."
* 6 => "Post published. {View post}"
* 7 => "Post saved."
* 8 => "Post submitted. {Preview post}"
* 9 => "Post scheduled for: [date]. {Preview post}"
* 10 => "Post draft updated. {Preview post}"
*
* @param array $messages An associative array of post updated messages with post type as keys.
* @return array Updated array of post updated messages.
*/
public function post_updated_messages( array $messages ) {
$messages['bbl_job'] = array(
1 => __( 'Translation job updated.', 'babble' ),
4 => __( 'Translation job updated.', 'babble' ),
8 => __( 'Translation job submitted.', 'babble' ),
10 => __( 'Translation job draft updated.', 'babble' ),
);
return $messages;
}
/**
* Hooks the WP admin_init action to enqueue some stuff.
*
* @return void
**/
public function admin_init() {
wp_enqueue_style( 'bbl-jobs-admin', $this->url( 'css/jobs-admin.css' ), array(), filemtime( $this->dir( 'css/jobs-admin.css' ) ) );
}
/**
* Hooks the WP action load-post.php to detect people
* trying to edit translated posts, and instead kick
* redirect them to an existing translation job or
* create a translation job and direct them to that.
*
* @TODO this should be in the post-public class
*
* @action load-post.php
*
* @return void
**/
public function load_post_edit() {
$post_id = isset( $_GET[ 'post' ] ) ? absint( $_GET[ 'post' ] ) : false;
if ( ! $post_id )
$post_id = isset( $_POST[ 'post_ID' ] ) ? absint( $_POST[ 'post_ID' ] ) : false;
$translated_post = get_post( $post_id );
if ( ! $translated_post )
return;
if ( ! bbl_is_translated_post_type( $translated_post->post_type ) )
return;
$canonical_post = bbl_get_default_lang_post( $translated_post );
$lang_code = bbl_get_post_lang_code( $translated_post );
if ( bbl_get_default_lang_code() == $lang_code )
return;
// @TODO Check capabilities include editing a translation post
// - If not, the button shouldn't be on the Admin Bar
// - But we also need to not process at this point
$existing_jobs = $this->get_incomplete_post_jobs( $canonical_post );
if ( isset( $existing_jobs[ $lang_code ] ) ) {
$url = get_edit_post_link( $existing_jobs[ $lang_code ], 'url' );
wp_redirect( $url );
exit;
}
// Create a new translation job for the current language
$lang_codes = array( $lang_code );
$jobs = $this->create_post_jobs( $canonical_post, $lang_codes );
// Redirect to the translation job
$url = get_edit_post_link( $jobs[0], 'url' );
wp_redirect( $url );
exit;
}
/**
* Hooks the WP admin_title filter to give some context to the
* page titles.
*
* @filter admin_title
*
* @param string $admin_title The admin title (for the TITLE element)
* @param string $title The title used in the H2 element above the edit form
* @return string The admin title
**/
public function admin_title( $admin_title, $title ) {
$screen = get_current_screen();
if ( 'post' == $screen->base && 'bbl_job' == $screen->post_type ) {
$pto = get_post_type_object( 'bbl_job' );
$job = get_post();
if ( 'add' == $screen->action ) {
if ( isset( $_GET['lang'] ) ) {
$lang = bbl_get_lang( $_GET['lang'] );
$admin_title = sprintf( $pto->labels->add_item_context, $lang->display_name );
}
} else {
$lang = $this->get_job_language( $job );
$admin_title = sprintf( $pto->labels->edit_item_context, $lang->display_name );
}
$GLOBALS[ 'title' ] = $admin_title;
}
return $admin_title;
}
/**
* Filters the public query vars and adds some of our own
*
* @filter query_vars
* @param array $vars Public query vars
* @return array Updated public query vars
*/
public function query_vars( array $vars ) {
if ( is_admin() ) {
$vars[] = 'bbl_job_post';
$vars[] = 'bbl_job_term';
$vars[] = 'bbl_job_meta';
}
return $vars;
}
/**
* Filter the user's capabilities so they can be added/removed on the fly.
*
* @TODO description of what this does
*
* @filter user_has_cap
* @param array $user_caps User's capabilities
* @param array $required_caps Actual required capabilities for the requested capability
* @param array $args Arguments that accompany the requested capability check:
* [0] => Requested capability from current_user_can()
* [1] => Current user ID
* [2] => Optional second parameter from current_user_can()
* @return array User's capabilities
*/
public function user_has_cap( array $user_caps, array $required_caps, array $args ) {
$user = new WP_User( $args[1] );
switch ( $args[0] ) {
case 'edit_post':
case 'edit_bbl_job':
case 'delete_post':
case 'delete_bbl_job':
case 'publish_post':
case 'publish_bbl_job':
$job = get_post( $args[2] );
if ( ! $job or ( 'bbl_job' != $job->post_type ) ) {
break;
}
$objects = $this->get_job_objects( $job );
$pto = get_post_type_object( $job->post_type );
$cap = str_replace( 'bbl_job', 'post', $args[0] );
if ( isset( $objects['post'] ) && $objects['post']->post_type != 'bbl_job' ) {
# This directly maps the ability to edit/delete/publish the job with the ability to do the same to the job's post:
$can = user_can( $user, $cap, $objects['post']->ID );
foreach ( $required_caps as $required ) {
if ( ! isset( $user_caps[$required] ) ) {
$user_caps[$required] = $can;
}
}
} else { # else if isset object terms
}
break;
case 'edit_bbl_jobs':
# Special case for displaying the admin menu:
# By default, Translators will have this cap:
if ( isset( $user_caps[$args[0]] ) )
break;
# 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
foreach ( get_post_types( array( 'show_ui' => true ), 'objects' ) as $pto ) {
// Don't check the capability we already checked.
if ( $args[0] == $pto->cap->edit_posts ) {
continue;
}
if ( user_can( $user, $pto->cap->edit_posts ) ) {
$user_caps[$args[0]] = true;
break;
}
}
break;
}
return $user_caps;
}
/**
* Hooks the WP pre_get_posts ref action in the WP_Query. Sets the meta query
* that's necessary for filtering jobs by their objects.
*
* @param WP_Query $wp_query A WP_Query object, passed by reference
* @return void (param passed by reference)
**/
public function pre_get_posts( WP_Query & $query ) {
if ( $job_post = $query->get( 'bbl_job_post' ) ) {
$query->set( 'meta_key', 'bbl_job_post' );
$query->set( 'meta_value', $job_post );
} else if ( $job_term = $query->get( 'bbl_job_term' ) ) {
$query->set( 'meta_key', 'bbl_job_term' );
$query->set( 'meta_value', $job_term );
} else if ( $job_meta = $query->get( 'bbl_job_meta' ) ) {
$query->set( 'meta_key', 'bbl_job_meta' );
$query->set( 'meta_value', $job_meta );
}
}
/**
* Hooks the WP filter get_edit_post_link
*
* @filter get_edit_post_link
* @param string $url The edit post link URL
* @param int $post_ID The ID of the post to edit
* @param string $context The link context.
*
* @return string The edit post link URL
* @author Simon Wheatley
**/
public function get_edit_post_link( $url, $post_ID, $context ) {
if ( $this->no_recursion ) {
return $url;
}
if ( bbl_get_default_lang_code() == bbl_get_post_lang_code( $post_ID ) ) {
return $url;
}
$completed_jobs = $this->get_completed_post_jobs( bbl_get_default_lang_post( $post_ID ) );
if ( ! isset( $completed_jobs[ bbl_get_current_lang_code() ] ) ) {
return $url;
}
$job = $completed_jobs[ bbl_get_current_lang_code() ];
if ( ! current_user_can( 'publish_post', $job->ID ) ) {
return $url;
}
$this->no_recursion = true;
$url = get_edit_post_link( $completed_jobs[ bbl_get_current_lang_code() ]->ID );
$this->no_recursion = false;
return $url;
}
public function edit_form_after_title() {
$screen = get_current_screen();
if ( 'bbl_job' != $screen->post_type ) {
return;
}
$job = get_post();
$items = $objects = $vars = array();
if ( ( 'add' == $screen->action ) and isset( $_GET['lang'] ) ) {
$vars['lang_code'] = stripslashes( $_GET['lang'] );
if ( isset( $_GET['bbl_origin_post'] ) ) {
$post = get_post( absint( $_GET['bbl_origin_post' ] ) );
$terms = $this->get_post_terms_to_translate( $post, $_GET['lang'] );
$meta = $this->get_post_meta_to_translate( $post, $_GET['lang'] );
$objects['post'] = $post;
if ( !empty( $terms ) ) {
$objects['terms'] = $terms;
}
if ( !empty( $meta ) ) {
$objects['meta'] = $meta;
}
$vars['origin_post'] = $post->ID;
} else if ( isset( $_GET['bbl_origin_term'] ) and isset( $_GET['bbl_origin_taxonomy'] ) ) {
$term = get_term( $_GET['bbl_origin_term'], $_GET['bbl_origin_taxonomy'] );
$objects['terms'][$term->taxonomy][$term->term_id] = $term;
$vars['origin_term'] = $term->term_id;
$vars['origin_taxonomy'] = $term->taxonomy;
}
} else {
$objects = $this->get_job_objects( $job );
}
if ( isset( $objects['post'] ) ) {
$post = $objects['post'];
$post_translation = get_post_meta( $job->ID, "bbl_post_{$post->ID}", true );
if ( empty( $post_translation ) ) {
$post_translation = get_default_post_to_edit( $post->post_type );
}
$items['post'] = array(
'original' => $post,
'translation' => (object) $post_translation,
);
}
if ( isset( $objects['meta'] ) ) {
foreach ( $objects['meta'] as $meta_key => $meta_field ) {
$meta_translation = get_post_meta( $job->ID, "bbl_meta_{$meta_key}", true );
if ( empty( $meta_translation ) ) {
$meta_translation = '';
}
$items['meta'][$meta_key] = array(
'original' => $meta_field,
'translation' => $meta_translation,
);
}
}
if ( isset( $objects['terms'] ) ) {
foreach ( $objects['terms'] as $taxo => $terms ) {
foreach ( $terms as $term ) {
$term_translation = get_post_meta( $job->ID, "bbl_term_{$term->term_id}", true );
if ( empty( $term_translation ) ) {
$term_translation = array( 'name' => '', 'slug' => '' );
}
$items['terms'][$taxo][] = array(
'original' => $term,
'translation' => (object) $term_translation,
);
}
}
}
$statuses = array(
'in-progress' => get_post_status_object( 'in-progress' )->label,
);
if ( ( 'pending' == $job->post_status ) or !current_user_can( 'publish_post', $job->ID ) ) {
$statuses['pending'] = get_post_status_object( 'pending' )->label;
}
if ( current_user_can( 'publish_post', $job->ID ) ) {
$statuses['complete'] = get_post_status_object( 'complete' )->label;
}
$statuses = apply_filters( 'bbl_job_statuses', $statuses, $job, $objects );
$vars['job'] = $job;
$vars['items'] = $items;
$vars['statuses'] = $statuses;
$this->render_admin( 'translation-editor.php', $vars );
}
public function admin_menu() {
# Remove the 'Add New' submenu for Translations.
remove_submenu_page( 'edit.php?post_type=bbl_job', 'post-new.php?post_type=bbl_job' );
}
public function wp_before_admin_bar_render() {
global $wp_admin_bar;
# Remove the '+New -> Translation Job' admin bar menu.
$wp_admin_bar->remove_node( 'new-bbl_job' );
}
/**
* undocumented function
*
* @param
* @return void
**/
public function add_meta_boxes( $post_type ) {
if ( bbl_is_translated_post_type( $post_type ) ) {
add_meta_box( 'bbl_translations', _x( 'Translations', 'Translations meta box title', 'babble' ), array( $this, 'metabox_post_translations' ), $post_type, 'side', 'high' );
}
}
public function bbl_translation_post_meta_boxes( $type, $original, $translation ) {
if ( !empty( $original->post_excerpt ) or !empty( $translation->post_excerpt ) ) {
add_meta_box( 'postexcerpt', __( 'Excerpt', 'babble' ), array( $this, 'metabox_translation_post_excerpt' ), $type, 'post' );
}
}
public function bbl_translation_terms_meta_boxes( $type, $items ) {
foreach ( $items as $taxo => $terms ) {
$tax = get_taxonomy( $taxo );
add_meta_box( "{$taxo}_terms", $tax->labels->name, array( $this, 'metabox_translation_terms' ), $type, $taxo );
}
}
public function bbl_translation_meta_meta_boxes( $type, $items ) {
$i = 0;
foreach ( $items as $meta_key => $meta_field ) {
add_meta_box( "meta_{$i}", esc_html( $meta_field['original']->get_title() ), array( $this, 'metabox_translation_meta' ), $type, $meta_key );
$i++;
}
}
public function bbl_translation_submit_meta_boxes( $type, $job ) {
add_meta_box( 'bbl_job_submit', __( 'Save Translation' , 'babble'), array( $this, 'metabox_translation_submit' ), $type, 'submit' );
}
public function metabox_translation_terms( array $items ) {
$vars = $items;
$this->render_admin( 'translation-editor-terms.php', $vars );
}
public function metabox_translation_meta( array $items ) {
$vars = $items;
$this->render_admin( 'translation-editor-meta.php', $vars );
}
public function metabox_translation_post_excerpt( array $items ) {
$vars = $items;
$this->render_admin( 'translation-editor-post-excerpt.php', $vars );
}
public function metabox_translation_submit( array $items ) {
$vars = $items;
$this->render_admin( 'translation-editor-submit.php', $vars );
}
public function save_job( $job_id, WP_Post $job ) {
global $bbl_post_public, $bbl_taxonomies;
if ( $this->no_recursion )
return;
if ( 'bbl_job' != $job->post_type )
return;
$edit_post_nonce = isset( $_POST[ '_bbl_translation_edit_post' ] ) ? $_POST[ '_bbl_translation_edit_post' ] : false;
$edit_terms_nonce = isset( $_POST[ '_bbl_translation_edit_terms' ] ) ? $_POST[ '_bbl_translation_edit_terms' ] : false;
$edit_meta_nonce = isset( $_POST[ '_bbl_translation_edit_meta' ] ) ? $_POST[ '_bbl_translation_edit_meta' ] : false;
$origin_post_nonce = isset( $_POST[ '_bbl_translation_origin_post' ] ) ? $_POST[ '_bbl_translation_origin_post' ] : false;
$origin_term_nonce = isset( $_POST[ '_bbl_translation_origin_term' ] ) ? $_POST[ '_bbl_translation_origin_term' ] : false;
$lang_code_nonce = isset( $_POST[ '_bbl_translation_lang_code' ] ) ? $_POST[ '_bbl_translation_lang_code' ] : false;
if ( $lang_code_nonce and wp_verify_nonce( $lang_code_nonce, "bbl_translation_lang_code_{$job->ID}" ) ) {
wp_set_object_terms( $job->ID, stripslashes( $_POST['bbl_lang_code'] ), 'bbl_job_language', false );
}
$language = get_the_terms( $job, 'bbl_job_language' );
if ( empty( $language ) )
return false;
else
$lang_code = reset( $language )->name;
if ( $origin_post_nonce and wp_verify_nonce( $origin_post_nonce, "bbl_translation_origin_post_{$job->ID}") ) {
if ( $origin_post = get_post( absint( $_POST['bbl_origin_post'] ) ) ) {
add_post_meta( $job->ID, 'bbl_job_post', "{$origin_post->post_type}|{$origin_post->ID}", true );
foreach ( $this->get_post_terms_to_translate( $origin_post, $lang_code ) as $taxo => $terms ) {
foreach ( $terms as $term_id => $term )
add_post_meta( $job->ID, 'bbl_job_term', "{$taxo}|{$term_id}", false );
}
foreach ( $this->get_post_meta_to_translate( $origin_post, $lang_code ) as $key => $field ) {
add_post_meta( $job->ID, 'bbl_job_meta', $key, false );
}
}
# @TODO else wp_die()?
}
# @TODO not implemented:
if ( $origin_term_nonce and wp_verify_nonce( $origin_term_nonce, "bbl_translation_origin_term_{$job->ID}") ) {
if ( $origin_term = get_term( absint( $_POST['bbl_origin_term'] ), $_POST['bbl_origin_taxonomy'] ) )
add_post_meta( $job->ID, 'bbl_job_term', "{$origin_term->taxonomy}|{$origin_term->term_id}", false );
# @TODO else wp_die()?
}
if ( $edit_post_nonce and wp_verify_nonce( $edit_post_nonce, "bbl_translation_edit_post_{$job->ID}" ) ) {
$post_data = stripslashes_deep( $_POST['bbl_translation']['post'] );
if ( $post_data['post_name'] )
$post_data['post_name'] = sanitize_title( $post_data['post_name'] );
$post_info = get_post_meta( $job->ID, 'bbl_job_post', true );
list( $post_type, $post_id ) = explode( '|', $post_info );
$post = get_post( $post_id );
update_post_meta( $job->ID, "bbl_post_{$post_id}", $post_data );
if ( 'pending' == $job->post_status ) {
# Nothing.
}
if ( 'complete' == $job->post_status ) {
# The ability to complete a translation of a post directly
# maps to the ability to publish the origin post.
if ( current_user_can( 'publish_post', $job->ID ) ) {
if ( !$trans = $bbl_post_public->get_post_in_lang( $post, $lang_code, false ) )
$trans = $bbl_post_public->initialise_translation( $post, $lang_code );
$post_data['ID'] = $trans->ID;
$post_data['post_status'] = $post->post_status;
$this->no_recursion = true;
wp_update_post( $post_data, true );
$this->no_recursion = false;
} else {
# Just in case. Switch the job back to in-progress status.
# It would be nice to be able to use the 'publish' status because then we get the built-in
# publish_post cap checks, but we can't control the post status label on a per-post-type basis yet.
$this->no_recursion = true;
wp_update_post( array(
'ID' => $job->ID,
'post_status' => 'in-progress',
), true );
$this->no_recursion = false;
}
}
if ( $edit_meta_nonce and wp_verify_nonce( $edit_meta_nonce, "bbl_translation_edit_meta_{$job->ID}" ) ) {
$meta_data = stripslashes_deep( $_POST['bbl_translation']['meta'] );
foreach ( $meta_data as $meta_key => $meta_value ) {
update_post_meta( $job->ID, "bbl_meta_{$meta_key}", $meta_value );
if ( 'complete' == $job->post_status ) {
if ( current_user_can( 'publish_post', $job->ID ) ) {
update_post_meta( $trans->ID, $meta_key, $meta_value );
}
}
}
}
}
if ( $edit_terms_nonce and wp_verify_nonce( $edit_terms_nonce, "bbl_translation_edit_terms_{$job->ID}") ) {
$terms_data = stripslashes_deep( $_POST['bbl_translation']['terms'] );
$terms = get_post_meta( $job->ID, 'bbl_job_term', false );
foreach ( $terms as $term_info ) {
list( $taxo, $term_id ) = explode( '|', $term_info );
$term = get_term( $term_id, $taxo );
$terms_data[$term_id]['slug'] = sanitize_title( $terms_data[$term_id]['slug'] );
update_post_meta( $job->ID, "bbl_term_{$term_id}", $terms_data[$term_id] );
if ( 'complete' == $job->post_status ) {
# @TODO if current user can edit term
$trans = $bbl_taxonomies->get_term_in_lang( $term, $taxo, $lang_code, false );
if ( !$trans )
$trans = $bbl_taxonomies->initialise_translation( $term, $taxo, $lang_code );
$terms_data[$term->term_id]['term_id'] = $trans->term_id;
$args = array(
'name' => $terms_data[$term->term_id]['name'],
'slug' => '',
);
wp_update_term( absint( $trans->term_id ), $trans->taxonomy, $args );
}
}
}
}
public function save_post( $post_id, WP_Post $post ) {
if ( $this->no_recursion )
return;
if ( !bbl_is_translated_post_type( $post->post_type ) )
return;
$nonce = isset( $_POST[ '_bbl_ready_for_translation' ] ) ? $_POST[ '_bbl_ready_for_translation' ] : false;
if ( !$nonce )
return;
if ( !wp_verify_nonce( $nonce, "bbl_ready_for_translation-{$post->ID}" ) )
return;
if ( !isset( $_POST['babble_ready_for_translation'] ) )
return;
# @TODO individual language selection when marking post as translation ready
$langs = bbl_get_active_langs();
$lang_codes = wp_list_pluck( $langs, 'code' );
$this->create_post_jobs( $post->ID, $lang_codes );
}
/**
* Hooks the WP init action early to register the
* job post type.
*
* @return void
**/
public function init_early() {
$labels = array(
'name' => _x( 'Translation Jobs', 'translation jobs general name', 'babble' ),
'singular_name' => _x( 'Translation Job', 'translation jobs singular name', 'babble' ),
'menu_name' => _x( 'Translations', 'translation jobs menu name', 'babble' ),
'add_new' => _x( 'Add New', 'translation job', 'babble' ),
'add_new_item' => _x( 'Create New Job', 'translation job', 'babble' ),
'add_item_context' => _x( 'Add Translation Job (%s)', 'translation job; e.g. "Add Translation Job (French)"', 'babble' ),
'edit_item' => _x( 'Edit Translation', 'translation job', 'babble' ),
'edit_item_context' => _x( 'Edit Translation (%s)', 'translation job; e.g. "Edit Translation (French)"', 'babble' ),
'new_item' => _x( 'New Job', 'translation job', 'babble' ),
'view_item' => _x( 'View Job', 'translation job', 'babble' ),
'search_items' => _x( 'Search Jobs', 'translation job', 'babble' ),
'not_found' => _x( 'No translation jobs found.', 'translation job', 'babble' ),
'not_found_in_trash' => _x( 'No translation jobs found in Trash.', 'translation job', 'babble' ),
'all_items' => _x( 'All Translation Jobs', 'translation job', 'babble' ),
);
$args = array(
'public' => false,
'publicly_queryable' => false,
'show_ui' => true,
'show_in_menu' => true,
'menu_icon' => 'dashicons-clipboard',
'query_var' => false,
'labels' => $labels,
'can_export' => true,
'supports' => false,
'capability_type' => 'bbl_job',
'map_meta_cap' => true,
);
register_post_type( 'bbl_job', $args );
register_post_status( 'new', array(
'label' => __( 'New', 'babble' ),
'public' => false,
'exclude_from_search' => false,
'show_in_admin_all_list' => true,
'label_count' => _n_noop( 'New <span class="count">(%s)</span>', 'New <span class="count">(%s)</span>', 'babble' ),
'protected' => true,
) );
register_post_status( 'in-progress', array(
'label' => __( 'In Progress', 'babble' ),
'public' => false,
'exclude_from_search' => false,
'show_in_admin_all_list' => true,
'label_count' => _n_noop( 'In Progress <span class="count">(%s)</span>', 'In Progress <span class="count">(%s)</span>', 'babble' ),
'protected' => true,
) );
register_post_status( 'complete', array(
'label' => __( 'Complete', 'babble' ),
'public' => false,
'exclude_from_search' => false,
'show_in_admin_all_list' => true,
'label_count' => _n_noop( 'Complete <span class="count">(%s)</span>', 'Complete <span class="count">(%s)</span>', 'babble' ),
'protected' => true,
) );
$args = array(
'public' => false,
'show_ui' => false,
);
register_taxonomy( 'bbl_job_language', array( 'bbl_job' ), $args );
}
// CALLBACKS
// =========
public function filter_columns( $cols ) {
$new_cols = array();
foreach ( $cols as $col_id => $col ) {
if ( 'date' != $col_id ) {
$new_cols[$col_id] = $col;
} else {
$new_cols['bbl_language'] = __( 'Language', 'babble' );
$new_cols['bbl_type'] = __( 'Items', 'babble' );
$new_cols['bbl_status'] = __( 'Status', 'babble' );
$new_cols['date'] = $col;
}
}
return $new_cols;
}
public function action_column( $col, $post_id ) {
$post = get_post( $post_id );
$status = get_post_status_object( $post->post_status );
switch ( $col ) {
case 'bbl_language':
echo $this->get_job_language( $post )->display_name;
break;
case 'bbl_type':
echo implode( ', ', $this->get_job_type( $post ) );
break;
case 'bbl_status':
echo $status->label;
break;
}
}
public function metabox_post_translations( WP_Post $post, array $metabox ) {
$trans = bbl_get_post_translations( $post );
$incomplete_jobs = $this->get_incomplete_post_jobs( $post );
$completed_jobs = $this->get_completed_post_jobs( $post );
$default = bbl_get_default_lang_code();
# The ability to create a translation of a post directly
# maps to the ability to publish the canonical post.
$capable = current_user_can( 'publish_post', $post->ID );
unset( $trans[$default] );
if ( !empty( $trans ) ) {
if ( !empty( $completed_jobs ) and $capable ) {
?><h4><?php _e( 'Complete:', 'babble' ); ?></h4><?php
}
foreach ( $completed_jobs as $lang_code => $job ) {
$lang = bbl_get_lang( $lang_code );
?>
<p><?php printf( '%s: <a href="%s">%s</a>', $lang->display_name, get_edit_post_link( $job->ID ), __( 'View', 'babble' ) ); ?>
<?php
}
}
if ( !empty( $incomplete_jobs ) and $capable ) {
?><h4><?php _e( 'Pending:', 'babble' ); ?></h4><?php
foreach ( $incomplete_jobs as $job ) {
$lang = $this->get_job_language( $job );
$status = get_post_status_object( $job->post_status );
?>
<p><?php printf( '%s (%s)', $lang->display_name, $status->label ); ?>
<?php
}
$args = array(
'post_type' => 'bbl_job',
'bbl_job_post' => "{$post->post_type}|{$post->ID}",
);
?>
<p><a href="<?php echo add_query_arg( $args, admin_url( 'edit.php' ) ); ?>"><?php _e( 'View pending translation jobs »', 'babble' ); ?></a></p>
<?php
} else if ( $capable ) {
wp_nonce_field( "bbl_ready_for_translation-{$post->ID}", '_bbl_ready_for_translation' );
?>
<p><label><input type="checkbox" name="babble_ready_for_translation" value="<?php echo absint( $post->ID ); ?>" /> <?php _e( 'Ready for translation', 'babble' ); ?></label></p>
<?php
} else {
?>
<p><?php _ex( 'None', 'No translations', 'babble' ); ?></p>
<?php
}
}
// PUBLIC METHODS
// ==============
/**
* Return the array of incomplete jobs for a Post, keyed
* by lang code.
*
* @param WP_Post|int $post A WP Post object or a post ID
* @return array An array of WP Translation Job Post objects
*/
public function get_incomplete_post_jobs( $post ) {
$post = get_post( $post );
return $this->get_object_jobs( $post->ID, 'post', $post->post_type, array( 'new', 'in-progress' ) );
}
/**
* Return the array of completed jobs for a Post, keyed
* by lang code.
*
* @param WP_Post|int $post A WP Post object or a post ID
* @return array An array of WP Translation Job Post objects
*/
public function get_completed_post_jobs( $post ) {
$post = get_post( $post );
return $this->get_object_jobs( $post->ID, 'post', $post->post_type, array( 'complete' ) );
}
/**
* Return the array of jobs for a Term, keyed
* by lang code.
*
* @param object $term A WP Term object or a term ID
* @return array An array of WP Translation Job Post objects
*/
public function get_term_jobs( $term, $taxonomy ) {
$term = get_term( $term, $taxonomy );
return $this->get_object_jobs( $term->term_id, 'term', $term->taxonomy );
}
/**
* Return the array of jobs for a term or post, keyed
* by lang code.
*
* @param int The ID of the object (eg. post ID or term ID)
* @param string $type Either 'term' or 'post'
* @param string $name The post type name or the term's taxonomy name
* @return array An array of translation job WP_Post objects
*/
public function get_object_jobs( $id, $type, $name, $statuses = array( 'new', 'in-progress', 'complete' ) ) {
$jobs = get_posts( array(
'bbl_translate' => false,
'post_type' => 'bbl_job',
'post_status' => $statuses,
'meta_key' => "bbl_job_{$type}",
'meta_value' => "{$name}|{$id}",
'posts_per_page' => -1,
) );
if ( empty( $jobs ) )
return array();
$return = array();
foreach ( $jobs as $job ) {
if ( $lang = $this->get_job_language( $job ) )
$return[$lang->code] = $job;
}
return $return;
}
public function get_job_language( $job ) {
$job = get_post( $job );
$languages = get_the_terms( $job, 'bbl_job_language' );
if ( empty( $languages ) )
return false;
return bbl_get_lang( reset( $languages )->name );
}
public function get_job_type( $job ) {
$job = get_post( $job );
$post = get_post_meta( $job->ID, 'bbl_job_post', true );
$terms = get_post_meta( $job->ID, 'bbl_job_term', false );
$return = array();
if ( !empty( $post ) ) {
list( $post_type, $post_id ) = explode( '|', $post );
$return[] = get_post_type_object( $post_type )->labels->singular_name;
}
if ( !empty( $terms ) ) {
foreach ( $terms as $term ) {
list( $taxonomy, $term_id ) = explode( '|', $term );
$return[] = get_taxonomy( $taxonomy )->labels->name;
}
}
return array_unique( $return );
}
public function get_job_objects( $job ) {
$job = get_post( $job );
$post = get_post_meta( $job->ID, 'bbl_job_post', true );
$terms = get_post_meta( $job->ID, 'bbl_job_term', false );
$meta = get_post_meta( $job->ID, 'bbl_job_meta', false );
$lang = $this->get_job_language( $job );
$return = array();
if ( !empty( $post ) ) {
list( $post_type, $post_id ) = explode( '|', $post );
# @TODO in theory a translation job could actually include more than one post.
# we should implement this earlier rather than later to save potential headaches down the road.
$return['post'] = get_post( $post_id );
}
if ( !empty( $terms ) ) {
foreach ( $terms as $term ) {
list( $taxonomy, $term_id ) = explode( '|', $term );
$return['terms'][$taxonomy][] = get_term( $term_id, $taxonomy );
}
}
if ( !empty( $meta ) ) {
$post = get_post_meta( $job->ID, 'bbl_job_post', true );
list( $post_type, $post_id ) = explode( '|', $post );
$post = get_post( $post_id );
foreach ( $this->get_post_meta_to_translate( $post, $lang->code ) as $meta_key => $meta_field ) {
$return['meta'][$meta_key] = $meta_field;
}
}
return $return;
}
/**
* Create empty translations of a post for all languages. Called via WP-Cron on the `babble_create_empty_translation` hook.
*
* @param array $args Args array containing a `post_id` element.
*/
public function create_empty_translation( array $args ) {
global $bbl_post_public;
if ( !$post = get_post( $args['post_id'] ) ) {
return;
}
foreach ( bbl_get_active_langs() as $lang ) {
if ( !$trans = $bbl_post_public->get_post_in_lang( $post, $lang->code, false ) ) {
$trans = $bbl_post_public->initialise_translation( $post, $lang->code );
}
$post_data = array(
'ID' => $trans->ID,
'post_status' => $post->post_status,
'post_name' => $post->post_name,
);
$this->no_recursion = true;
wp_update_post( $post_data, true );
$this->no_recursion = false;
}
}
/**
* Create some translation jobs.
*
* @param int $post_id The ID of the post to create translation jobs for
* @param array $lang_codes The language codes to create translation jobs of this post for
* @return array An array of Translation Job post IDs
**/
public function create_post_jobs( $post_id, array $lang_codes ) {
$post = get_post( $post_id );
// @TODO Validate that the $post is in the default language, otherwise fail
$jobs = array();
foreach ( $lang_codes as $lang_code ) {
if ( bbl_get_default_lang_code() == $lang_code )
continue;
if ( apply_filters( 'bbl_create_empty_translation', false, $post ) ) {
wp_schedule_single_event( time(), 'babble_create_empty_translation', array(
array(
'post_id' => $post->ID,
)
) );
}
$this->no_recursion = true;
$job = wp_insert_post( array(
'post_type' => 'bbl_job',
'post_status' => 'new',
'post_author' => get_current_user_id(),
'post_title' => get_the_title( $post ),
) );
$this->no_recursion = false;
// @TODO If a translation already exists, populate the translation job with the translation
$jobs[] = $job;
add_post_meta( $job, 'bbl_job_post', "{$post->post_type}|{$post->ID}", true );
wp_set_object_terms( $job, $lang_code, 'bbl_job_language' );
foreach ( $this->get_post_terms_to_translate( $post, $lang_code ) as $taxo => $terms ) {
foreach ( $terms as $term_id => $term )
add_post_meta( $job, 'bbl_job_term', "{$taxo}|{$term_id}", false );
}
foreach ( $this->get_post_meta_to_translate( $post, $lang_code ) as $key => $field ) {
add_post_meta( $job, 'bbl_job_meta', $key, false );
}
}
return $jobs;
}
public function get_post_terms_to_translate( $post_id, $lang_code ) {
$post = get_post( $post_id );
$taxos = get_object_taxonomies( $post->post_type );
$trans_terms = array();
foreach ( $taxos as $key => $taxo ) {
if ( !bbl_is_translated_taxonomy( $taxo ) )
continue;
$terms = get_the_terms( $post, $taxo );
if ( empty( $terms ) )
continue;
foreach ( $terms as $term ) {
$trans = bbl_get_term_translations( $term->term_id, $term->taxonomy );
if ( !isset( $trans[$lang_code] ) )
$trans_terms[$taxo][$term->term_id] = $term;
}
}
return $trans_terms;
}
/**
* Return an array of a post's meta fields which are to be translated. The array keys are the post meta keys and the
* array values are the meta value for that key.
*
* @param WP_Post $post_id A post object.
* @param string $lang_code The language code for the translation job for this post.
* @return array An array of post meta keys which should be translated for this post.
*/
public function get_post_meta_to_translate( WP_Post $post, $lang_code ) {
$meta = get_post_meta( $post->ID );
if ( empty( $meta ) ) {
return array();
}
$fields = $this->get_translated_meta_fields( $post );
return array_intersect_key( $fields, $meta );
}
/**
* Return an array of meta field keys which should be translated. Array contains `Babble_Meta_Field` objects with
* the meta keys as the array keys.
*
* @param WP_Post A post object.
* @return Babble_Meta_Field[] An array of Babble meta field handlers.
*/
public function get_translated_meta_fields( WP_Post $post ) {
$fields = (array) apply_filters( 'bbl_translated_meta_fields', array(), $post );
foreach ( $fields as $key => $field ) {
if ( ! ( $field instanceof Babble_Meta_Field ) ) {
unset( $fields[ $key ] );
}
}
return $fields;
}
}
global $bbl_jobs;
$bbl_jobs = new Babble_Jobs();
================================================
FILE: class-languages.php
================================================
<?php
/**
* Manages the languages available for the site.
*
* @package Babble
* @since Alpha 1.1
*/
class Babble_Languages extends Babble_Plugin {
/**
* The languages available within the system.
*
* @var array
**/
protected $available_langs;
/**
* The language preferences set for this site,
* i.e. url prefixes and display names.
*
* @var array
**/
protected $lang_prefs;
/**
* An array of language codes, keyed by language prefix
* for all the languages selected as ACTIVE for this site.
*
* Active languages are available to admins to add content.
*
* @var array
**/
protected $active_langs;
/**
* An array of language codes, keyed by language prefix
* for all the languages selected as PUBLIC for this site.
*
* Public languages are available to readers of the site
* to read.
*
* @var array
**/
protected $public_langs;
/**
* The language code for the default language.
*
* @var string
**/
protected $default_lang;
/**
* The current version for purposes of rewrite rules, any
* DB updates, cache busting, etc
*
* @var int
**/
protected $version = 1;
/**
* Any fields to show errors on, currently only used by URL Prefix fields.
*
* @var array
**/
protected $errors;
/**
* Setup any add_action or add_filter calls. Initiate properties.
*
* @return void
**/
public function __construct() {
$this->setup( 'babble-languages', 'plugin' );
$this->add_action( 'admin_menu', 'admin_menu' );
$this->add_action( 'admin_notices', 'admin_notices' );
$this->add_action( 'load-settings_page_babble_languages', 'load_options' );
$this->initiate();
}
/**
* (Re)initiates the properties of this object.
*
* @return void
**/
public function initiate() {
if ( ! ( $this->available_langs = $this->get_option( 'available_langs', false ) ) ) {
$this->parse_available_languages();
}
$this->active_langs = $this->get_option( 'active_langs', array() );
$this->langs = $this->get_option( 'langs', array() );
$this->lang_prefs = $this->get_option( 'lang_prefs', array() );
$this->langs = $this->merge_lang_sets( $this->langs, $this->lang_prefs );
$this->default_lang = $this->get_option( 'default_lang', 'en_US' );
$this->public_langs = $this->get_option( 'public_langs', array( $this->default_lang ) );
// @FIXME: Add something in so the user gets setup with the single language they are currently using
if ( ! $this->get_option( 'active_langs', false ) || ! $this->get_option( 'default_lang', false ) )
$this->set_defaults();
}
// WP HOOKS
// ========
/**
* Hooks the WP admin_notices action to warn the admin
* if the available languages need to be set up.
*
* @return void
**/
public function admin_notices() {
if ( get_current_screen()->id == 'settings_page_babble_languages' )
return;
if ( ! $this->get_option( 'active_langs', false ) || ! $this->get_option( 'default_lang', false ) ) {
printf( '<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' ) ) );
}
}
/**
* Hooks the WP admin_menu action
*
* @return void
**/
public function admin_menu() {
add_options_page( __( 'Available Languages', 'babble' ), __( 'Available Languages' , 'babble'), 'manage_options', 'babble_languages', array( $this, 'options' ) );
}
/**
* Hooks the load action for the options page.
*
* @return void
**/
public function load_options() {
wp_enqueue_style( 'babble_languages_options', $this->url( '/css/languages-options.css' ), null, filemtime( $this->dir( 'css/languages-options.css' ) ) );
$this->maybe_process_languages();
}
// CALLBACKS
// =========
/**
* Callback function to provide the HTML for the "Available Languages"
* options page.
*
* @return void
**/
public function options() {
// Refresh the current languages
$this->parse_available_languages();
// Merge in our previously set language settings
$langs = $this->merge_lang_sets( $this->available_langs, $this->lang_prefs );
// Merge in any POSTed field values
foreach ( $langs as $code => & $lang ) {
$lang->url_prefix = ( @ isset( $_POST[ 'url_prefix_' . $code ] ) ) ? $_POST[ "url_prefix_$code" ] : @ $lang->url_prefix;
if ( ! $lang->url_prefix )
$lang->url_prefix = $lang->url_prefix;
$lang->text_direction = $lang->text_direction;
// This line must come after the text direction value is set
$lang->input_lang_class = ( 'rtl' == $lang->text_direction ) ? 'lang-rtl' : 'lang-ltr' ;
$lang->display_name = ( @ isset( $_POST[ "display_name_$code" ] ) ) ? $_POST[ "display_name_$code" ] : @ $lang->display_name;
if ( ! $lang->display_name )
$lang->display_name = $lang->name;
// Note any url_prefix errors
$lang->url_prefix_error = ( @ $this->errors[ "url_prefix_$code" ] ) ? 'babble-error' : '0' ;
// Flag the active languages
$lang->active = false;
if ( in_array( $code, $this->active_langs ) )
$lang->active = true;
}
$vars = array();
$vars[ 'langs' ] = $langs;
$vars[ 'default_lang' ] = $this->default_lang;
$vars[ 'active_langs' ] = $this->get_active_langs();
$this->render_admin( 'options-available-languages.php', $vars );
}
// PUBLIC METHODS
// ==============
/**
* Set the active language objects for the current site, keyed
* by URL prefix.
*
* @return array An array of Babble language objects
**/
public function set_active_langs( $lang_codes ) {
$this->parse_available_languages();
error_log( "SW: WP_LANG_DIR: " . WP_LANG_DIR );
$this->active_langs = $lang_codes;
}
/**
* Return the active language objects for the current site, keyed
* by URL prefix. A language object looks like:
* 'ar' =>
* object(stdClass)
* public 'name' => string 'Arabic'
* public 'code' => string 'ar'
* public 'url_prefix' => string 'ar'
* public 'text_direction' => string 'rtl'
* public 'display_name' => string 'Arabic'
*
* @return array An array of Babble language objects
**/
public function get_active_langs() {
$langs = array();
foreach ( $this->active_langs as $url_prefix => $code )
$langs[ $url_prefix ] = $this->langs[ $code ];
return $langs;
}
/**
* Given a lang object or lang code, this checks whether the
* language is public or not.
*
* @param string $lang_code A language code
* @return boolean True if public
**/
public function is_public_lang( $lang_code ) {
if ( ! is_string( $lang_code ) )
throw new exception( 'Please provide a lang_code for the is_public_lang method.' );
return in_array( $lang_code, $this->public_langs );
}
/**
* Returns the requested language object.
*
* @param string $code A language code, e.g. "fr_BE"
* @return object|boolean A Babble language object
**/
public function get_lang( $lang_code ) {
if ( ! isset( $this->langs[ $lang_code ] ) )
return false;
return $this->langs[ $lang_code ];
}
/**
* Returns the current language object, respecting any
* language switches; i.e. if your request was for
* Arabic, but the language is currently switched to
* French, this will return French.
*
* @return object|boolean A Babble language object
**/
public function get_current_lang() {
global $bbl_locale;
return $this->get_lang( $bbl_locale->get_lang() );
}
/**
* Returns the default language code for this site.
*
* @return string A language code, e.g. "he_IL"
**/
public function get_default_lang_code() {
return $this->default_lang;
}
/**
* Returns the default language for this site.
*
* @return object The language object for the default language
**/
public function get_default_lang() {
return bbl_get_lang( $this->default_lang );
}
/**
* Given a language code, return the URL prefix.
*
* @param string $code A language code, e.g. "fr_BE"
* @return bool|string A URL prefix, as set by the admin when editing the lang prefs, or false if no language
**/
public function get_url_prefix_from_code( $code ) {
if ( ! isset( $this->langs[ $code ]->url_prefix ) )
return false;
return $this->langs[ $code ]->url_prefix;
}
/**
* Given a URL prefix, return the language code.
*
* @param string $code A URL prefix, e.g. "de", as set by the admin
* @return bool|string A language code, e.g. "de_DE", or false if no language
**/
public function get_code_from_url_prefix( $url_prefix ) {
if ( ! isset( $this->active_langs[ $url_prefix ] ) )
return false;
return $this->active_langs[ $url_prefix ];
}
// PRIVATE/PROTECTED METHODS
// =========================
/**
* Merge two arrays of language objects. If a language exists in
* $langs_b that doesn't in $langs_a, it will be added to the
* final array. If a language has a property in both arrays, the
* property value from $langs_b will overwrite the property value
* in $langs_a. If a language in $langs_b has a property that
* doesn't exist in $langs_a then it will be added to that
* language in the final array.
*
* @param array $langs_a An array of language objects
* @param array $langs_b An array of language objects
* @return array An array of language objects
**/
protected function merge_lang_sets( $langs_a, $langs_b ) {
$langs = array();
foreach ( $langs_a as $code => $lang_a ) {
// Langs only in A get copied from A, simple.
if ( ! isset( $langs_b[ $code ] ) ) {
$langs[ $code ] = $lang_a;
continue;
}
// The properties of langs in both A & B are merged
$langs[ $code ] = $lang_a;
$lang_b = $langs_b[ $code ];
foreach ( $lang_b as $p => $v )
$langs[ $code ]->$p = $v;
}
return $langs;
}
/**
* Checks if there is a POSTed request to process. Checks it's properly
* nonced up. Processes it. Redirects if there's no errors.
*
* @return void
**/
protected function maybe_process_languages() {
if ( ! isset( $_POST[ '_babble_nonce' ] ) )
return;
check_admin_referer( 'babble_lang_prefs', '_babble_nonce' );
// Now save the language preferences for all languages
$lang_prefs = array();
$url_prefixes = array();
foreach ( $this->available_langs as $code => $lang ) {
$lang_pref = new stdClass;
$lang_pref->display_name = @ $_POST[ 'display_name_' . $code ];
$lang_pref->url_prefix = @ $_POST[ 'url_prefix_' . $code ];
// Check we don't have more than one language using the same url prefix
if ( array_key_exists( $lang_pref->url_prefix, $url_prefixes ) ) {
$lang_1 = $this->format_code_lang( $code );
$lang_2 = $this->format_code_lang( $url_prefixes[ $lang_pref->url_prefix ] );
$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 );
$this->set_admin_error( $msg );
$this->errors[ 'url_prefix_' . $lang_pref->url_prefix ] = true;
$this->errors[ "url_prefix_$code" ] = true;
} else {
$url_prefixes[ $lang_pref->url_prefix ] = $code;
}
$lang_prefs[ $code ] = $lang_pref;
}
error_log( "SW: Available langs: " . print_r( $this->available_langs, true ) );
error_log( "SW: Lang prefs: " . print_r( $lang_prefs, true ) );
// Now save the active languages, i.e. the selected languages
if ( ! $this->errors ) {
$langs = $this->merge_lang_sets( $this->available_langs, $this->lang_prefs );
$active_langs = array();
foreach ( (array) @ $_POST[ 'active_langs' ] as $code )
$active_langs[ $langs[ $code ]->url_prefix ] = $code;
if ( count( $active_langs ) < 2 ) {
$this->set_admin_error( __( 'You must set at least two languages as active.', 'babble' ) );
} else {
$this->active_langs = $active_langs;
$this->update_option( 'active_langs', $this->active_langs );
$this->langs = $langs;
$this->update_option( 'langs', $this->langs );
}
if ( ! isset( $_POST[ 'public_langs' ] ) ) {
$this->set_admin_error( __( 'You must set at least your default language as public.', 'babble' ) );
} else {
$public_langs = (array) $_POST[ 'public_langs' ];
if ( ! in_array( @ $_POST[ 'default_lang' ], $public_langs ) )
$this->set_admin_error( __( 'You must set your default language as public.', 'babble' ) );
}
}
// Finish up, redirecting if we're all OK
if ( ! $this->errors ) {
// Save the public languages
$this->update_option( 'public_langs', $public_langs );
// First the default language
$default_lang = @ $_POST[ 'default_lang' ];
$this->update_option( 'default_lang', $default_lang );
// Now the prefs
$this->update_option( 'lang_prefs', $lang_prefs );
// Now set a reassuring message and redirect back to the clean settings page
$this->set_admin_notice( __( 'Your language settings have been saved.', 'babble' ) );
$url = admin_url( 'options-general.php?page=babble_languages' );
wp_redirect( $url );
exit;
}
}
/**
* Parse the files in wp-content/languages and work out what
* languages we've got available. Populates self::available_langs
* with an array of language objects which look like:
* 'ar' =>
* object(stdClass)
* public 'name' => string 'Arabic'
* public 'code' => string 'ar'
* public 'url_prefix' => string 'ar'
* public 'text_direction' => string 'rtl'
*
* @return void
**/
protected function parse_available_languages() {
unset( $this->available_langs );
$this->available_langs = array();
foreach ( get_available_languages() as $lang_code ) {
list( $prefix ) = explode( '_', $lang_code );
$lang = array(
'name' => $this->format_code_lang( $prefix ),
'code' => $lang_code,
'url_prefix' => $prefix,
'text_direction' => $this->is_rtl( $lang_code ),
);
// Cast to an object, in case we want to start using actual classes
// at some point in the future.
$this->available_langs[ $lang_code ] = (object) $lang;
}
// Add in US English, which is the default on WordPress and has no language files
$en = new stdClass;
$en->name = 'English (US)';
$en->code = 'en_US';
$en->url_prefix = 'en';
$en->text_direction = 'ltr';
$this->available_langs[ 'en_US' ] = $en;
$this->available_langs = apply_filters( 'bbl_available_langs', $this->available_langs );
ksort( $this->available_langs );
$this->update_option( 'available_langs', $this->available_langs );
}
/**
* Parse (DON'T require or include) the [lang_code].php locale file in the languages
* directory to work if the specified language is right to left. (We can't include or
* require because it may contain function names which clash with other locale files.)
*
* @param string $lang The language code to retrieve RTL info for
* @return bool True if the language is RTL
**/
protected function is_rtl( $lang ) {
$locale_file = WP_LANG_DIR . "/$lang.php";
if ( ( 0 === validate_file( $lang ) ) && is_readable( $locale_file ) ) {
$locale_file_code = file_get_contents( $locale_file );
// Regex to find something looking like: $text_direction = 'rtl';
return ( (bool) preg_match( '/\$text_direction\s?=\s?[\'|"]rtl[\'|"]\s?;/i', $locale_file_code ) ) ? 'rtl' : 'ltr';
}
return 'ltr';
}
/**
* Return the language name for the provided language code.
*
* This method is an identical copy of format_code_lang
* in wp-admin/includes/ms.php which is only available on Multisite.
*
* @FIXME: We end up with a load of anglicised names, which doesn't seem super-friendly, internationally speaking.
*
* @see format_code_lang()
*
* @param string $lang_short The language short code, e.g. 'en' (not 'en_GB')
* @return string The language name, e.g. 'English'
**/
protected function format_code_lang( $code ) {
$code = strtolower( substr( $code, 0, 2 ) );
$lang_codes = array(
'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',
'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',
'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',
'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',
'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',
'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',
'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',
'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',
'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',
'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',
've' => 'Venda', 'vi' => 'Vietnamese', 'vo' => 'Volapük', 'cy' => 'Welsh','wa' => 'Walloon','wo' => 'Wolof', 'xh' => 'Xhosa', 'yi' => 'Yiddish', 'yo' => 'Yoruba', 'za' => 'Zhuang; Chuang', 'zu' => 'Zulu' );
$lang_codes = apply_filters( 'lang_codes', $lang_codes, $code );
$lang_codes = apply_filters( 'bbl_lang_codes', $lang_codes, $code );
return strtr( $code, $lang_codes );
}
/**
* Setup some initial language data, so the user's site doesn't immediately
* fail when the plugin is activated.
*
* @return void
**/
protected function set_defaults() {
$locale = get_option( 'WPLANG' );
if ( empty( $locale ) and is_multisite() ) {
$locale = get_site_option( 'WPLANG' );
}
if ( empty( $locale ) and defined( 'WPLANG' ) ) {
// The WPLANG constant is deprecated since WordPress 4.0.
$locale = WPLANG;
}
if ( empty( $locale ) ) {
$locale = 'en_US';
}
$url_prefix = strtolower( substr( $locale, 0, 2 ) );
$this->active_langs = array( $url_prefix => $locale );
$this->langs = array( $locale => $this->available_langs[ $locale ] );
$this->langs[ $locale ]->url_prefix = $url_prefix;
$this->langs[ $locale ]->display_name = $this->langs[ $locale ]->name;
$this->default_lang = $locale;
$this->public_langs = array( $locale );
}
}
global $bbl_languages;
$bbl_languages = new Babble_Languages();
================================================
FILE: class-locale.php
================================================
<?php
/**
* Manages the locale currently set for the site.
*
* @package Babble
* @since Alpha 1
*/
class Babble_Locale {
/**
* A regex to get the language code prefix from
* a URL.
*
* @var string
**/
protected $lang_regex = '|^[^/]+|i';
/**
* The language for the content of the current request.
*
* @var string
**/
protected $content_lang;
/**
* The interface language for the current request.
*
* @var string
**/
protected $interface_lang;
/**
* The locale for the current request.
*
* @var string
**/
protected $locale;
/**
* The URL prefix for the current request
*
* @var string
**/
protected $url_prefix;
/**
* A simple flag to stop infinite recursion in various places.
*
* @var boolean
**/
protected $no_recursion;
/**
* The languages that we've switched to, in order.
*
* @var array
**/
protected $lang_stack;
/**
* The current version for purposes of rewrite rules, any
* DB updates, cache busting, etc
*
* @var int
**/
protected $version = 2;
/**
* Setup any add_action or add_filter calls. Initiate properties.
*
* @return void
**/
function __construct() {
add_action( 'plugins_loaded', array( $this, 'plugins_loaded' ), 0 );
add_action( 'admin_init', array( $this, 'admin_init' ) );
add_action( 'admin_notices', array( $this, 'admin_notices' ) );
add_action( 'parse_request', array( $this, 'parse_request_early' ), 0 );
add_action( 'pre_comment_on_post', array( $this, 'pre_comment_on_post' ) );
add_filter( 'body_class', array( $this, 'body_class' ) );
add_filter( 'locale', array( $this, 'set_locale' ) );
add_filter( 'mod_rewrite_rules', array( $this, 'mod_rewrite_rules' ) );
add_filter( 'post_class', array( $this, 'post_class' ), null, 3 );
add_filter( 'pre_update_option_rewrite_rules', array( $this, 'internal_rewrite_rules_filter' ) );
add_filter( 'query_vars', array( $this, 'query_vars' ) );
}
public function plugins_loaded() {
global $wpdb;
# @TODO this exposes the $wpdb prefix. We should set the cookie path to the site path instead
# (example.com/site or site.example.com) so the cookie is only set for the current site on a multisite install
# @TODO actually, both of these should be user preferences, not cookies.
$this->content_lang_cookie = $wpdb->prefix . '_bbl_content_lang_' . COOKIEHASH;
$this->interface_lang_cookie = $wpdb->prefix . '_bbl_interface_lang_' . COOKIEHASH;
}
/**
* Hooks the WP admin_init action
*
* @return void
**/
public function admin_init() {
add_filter( 'home_url', array( $this, 'home_url' ), null, 2 );
$this->maybe_update();
$this->maybe_set_cookie_content_lang();
$this->maybe_set_cookie_interface_lang();
}
/**
* Hooks the WP admin_notices action to warn the admin
* if the permalinks aren't pretty enough.
*
* @return void
**/
public function admin_notices() {
if ( ! get_option( 'permalink_structure' ) ) {
printf( '<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' ) ) );
}
}
/**
* Ensure we keep the standard WP rewrite rules.
*
* @param string $rules The mod_rewrite rules block generated by WP
* @return string A mod_rewrite rules block
**/
public function mod_rewrite_rules( $rules ) {
global $wp_rewrite;
if ( $this->no_recursion )
return $rules;
$this->no_recursion = true;
// We need the WP_Rewrite mod_rewrite_rules method to run
// home_url without a lang query var set, or it generates
// an inaccurate RewriteBase and last RewriteRule.
remove_filter( 'home_url', array( $this, 'home_url' ), null, 2 );
$rules = $wp_rewrite->mod_rewrite_rules();
add_filter( 'home_url', array( $this, 'home_url' ), null, 2 );
$this->no_recursion = false;
return $rules;
}
/**
* Hooks the WP pre_update_option_rewrite_rules filter to add
* a prefix to the URL to pick up the virtual sub-dir specifying
* the language. The redirect portion can and should remain perfectly
* ignorant of it though, as we change it in parse_request.
*
* @param array $langs The language codes
* @return array An array of language codes utilised for this site.
**/
public function internal_rewrite_rules_filter( $rules ){
global $wp_rewrite;
// Some rules need to be at the root of the site, without a
// language prefix, e.g. http://www.example.com/humans.txt.
// The following filter allows plugin and theme devs to add
// to this list of site root level URLs which are untranslated.
$non_translated_rewrite_rules = apply_filters( 'bbl_non_translated_queries', array(
'humans\.txt$',
'robots\.txt$',
) );
foreach( (array) $rules as $regex => $query ) {
if ( in_array( $regex, $non_translated_rewrite_rules ) ) {
$new_rules[ $regex ] = $query;
continue;
}
if ( substr( $regex, 0, 1 ) == '^' ) {
$new_rules[ '^[a-zA-Z_]+/' . substr( $regex, 1 ) ] = $query;
}
else {
$new_rules[ '[a-zA-Z_]+/' . $regex ] = $query;
}
}
// The WP robots.txt rewrite rule will not have worked, as the
// code objects to the language prefix. Here we add it in again.
$hooked = false;
if ( has_filter( 'home_url' ) ) {
remove_filter( 'home_url', array( $this, 'home_url' ), null, 2 );
$hooked = true;
}
$home_path = parse_url( home_url() );
if ( $hooked ) {
add_filter( 'home_url', array( $this, 'home_url' ), null, 2 );
}
if ( empty( $home_path['path'] ) || '/' == $home_path['path'] ) {
$new_rules[ 'robots\.txt$' ] = $wp_rewrite->index . '?robots=1';
}
return $new_rules;
}
/**
* Hooks the WP locale filter to switch locales whenever we gosh darned want.
*
* @param string $locale The locale
* @return string The locale
**/
public function set_locale( $locale ) {
// Deal with the special case of wp-comments-post.php
if ( false !== stristr( $_SERVER[ 'REQUEST_URI' ], 'wp-comments-post.php' ) ) {
// @TODO we should be able to hook into an action here (pre_comment_post) rather than looking at the URL.
if ( $comment_post_ID = ( isset( $_POST[ 'comment_post_ID' ] ) ) ? (int) $_POST[ 'comment_post_ID' ] : false ) {
if ( ! isset( $this->content_lang ) ) {
$this->set_content_lang( bbl_get_post_lang_code( $comment_post_ID ) );
}
return $this->content_lang;
}
}
if ( is_admin() ) {
if ( isset( $this->interface_lang ) ) {
return $this->interface_lang;
}
} else {
if ( isset( $this->content_lang ) ) {
return $this->content_lang;
}
}
// $current_user = wp_get_current_user();
if ( $lang = $this->get_cookie_interface_lang() ) {
$this->set_interface_lang( $lang );
}
// $current_user = wp_get_current_user();
if ( $lang = $this->get_cookie_content_lang() ) {
$this->set_content_lang( $lang );
}
if ( is_admin() ) {
// @FIXME: At this point a mischievous XSS "attack" could set a user's admin area language for them
if ( isset( $_POST[ 'interface_lang' ] ) ) {
$this->set_interface_lang( $_POST[ 'interface_lang' ] );
}
// @FIXME: At this point a mischievous XSS "attack" could set a user's content language for them
if ( isset( $_GET[ 'lang' ] ) ) {
$this->set_content_lang( $_GET[ 'lang' ] );
}
} else { // Front end
// @FIXME: Should probably check the available languages here
if ( preg_match( $this->lang_regex, $this->get_request_string(), $matches ) )
$this->set_content_lang_from_prefix( $matches[ 0 ] );
}
if ( ! isset( $this->content_lang ) || ! $this->content_lang )
$this->set_content_lang( bbl_get_default_lang_code() );
if ( ! isset( $this->interface_lang ) || ! $this->interface_lang )
$this->set_interface_lang( bbl_get_default_lang_code() );
if ( is_admin() )
return $this->interface_lang;
else
return $this->content_lang;
}
/**
* Hooks the WP parse_request action
*
* FIXME: Should I be extending and replacing the WP class?
*
* @param WP $wp The WP object, passed by reference (so no need to return)
* @return void
**/
public function parse_request_early( WP $wp ) {
// If this is the site root, redirect to default language homepage
if ( ! $wp->request ) {
remove_filter( 'home_url', array( $this, 'home_url' ), null, 2 );
wp_redirect( home_url( bbl_get_default_lang_url_prefix() ) );
exit;
}
// Otherwise, simply set the lang for this request
$wp->query_vars[ 'lang' ] = $this->content_lang;
$wp->query_vars[ 'lang_url_prefix' ] = $this->url_prefix;
}
/**
* Hooks the WP query_vars filter to add the home_url filter.
*
* @param array $query_vars An array of the public query vars
* @return array An array of the public query vars
**/
public function query_vars( array $query_vars ) {
# @TODO why is this here?
add_filter( 'home_url', array( $this, 'home_url' ), null, 2 );
return array_merge( $query_vars, array( 'lang', 'lang_url_prefix' ) );
}
/**
* Hooks the WP pre_comment_on_post action to add the
* home_url filter.
*
* @return void
**/
public function pre_comment_on_post() {
# @TODO why is this here?
add_filter( 'home_url', array( $this, 'home_url' ), null, 2 );
}
/**
* Hooks the WP home_url action
*
* Hackity hack: this function is attached with add_filter within
* the query_vars filter and the pre_comment_on_post action.
* @TODO: Can't remember why this is attached like this… investigate.
*
* @param string $url The URL
* @param string $path The path
* @param string $orig_scheme The original scheme
* @param int $blog_id The ID of the blog
* @return string The URL
**/
public function home_url( $url, $path ) {
$base_url = get_option( 'home' );
$url = trailingslashit( $base_url ) . $this->url_prefix;
if ( $path && is_string( $path ) )
$url .= '/' . ltrim( $path, '/' );
return $url;
}
/**
* Hooks the WP body_class filter to add some language specific classes.
*
* @param array $classes The body classes
* @return array The body classes
**/
public function body_class( array $classes ) {
$lang = bbl_get_current_lang();
$classes[] = 'bbl-' . $lang->text_direction;
# @TODO I don't think this class should be included:
$classes[] = 'bbl-' . sanitize_title( $lang->name );
$classes[] = 'bbl-' . sanitize_title( $lang->url_prefix );
$classes[] = 'bbl-' . sanitize_title( $lang->code );
# @TODO I don't think this class should be included:
$classes[] = 'bbl-' . sanitize_title( $lang->display_name );
return $classes;
}
/**
* Hooks the WP post_class filter to add some language specific classes.
*
* @param array $classes The post classes
* @param array $class One or more classes which have been added to the class list.
* @param int $post_id The ID of the post we're providing classes for
* @return array The body classes
**/
public function post_class( array $classes, $class, $post_id ) {
$post = get_post( $post_id );
$post_lang_code = bbl_get_post_lang_code( $post );
$lang = bbl_get_lang( $post_lang_code );
if ( self::use_default_text_direction( $post ) ) {
$default_lang = bbl_get_default_lang();
$classes[] = 'bbl-post-' . $default_lang->text_direction;
} else {
$classes[] = 'bbl-post-' . $lang->text_direction;
}
# @TODO I don't think this class should be included:
$classes[] = 'bbl-post-' . sanitize_title( $lang->name );
$classes[] = 'bbl-post-' . sanitize_title( $lang->url_prefix );
$classes[] = 'bbl-post-' . sanitize_title( $lang->code );
# @TODO I don't think this class should be included:
$classes[] = 'bbl-post-' . sanitize_title( $lang->display_name );
return $classes;
}
// Public Methods
// --------------
/**
* Return whether the post should use the default language's text direction or not.
*
* @param WP_Post $post The post object.
* @return bool True if the post should use the default language text direction. False if not.
*/
public static function use_default_text_direction( WP_Post $post ) {
if ( get_post_meta( $post->ID, '_bbl_default_text_direction', true ) ) {
return true;
} else if ( empty( $post->post_content ) ) {
return true;
}
return false;
}
/**
* Get the current (content) lang for this class, which is also the
* current lang in the Query Vars.
*
* @TODO deprecate
*
* @return string
**/
public function get_lang() {
return $this->get_content_lang();
}
/**
* Get the current content lang for this class, which is also the
* current lang in the Query Vars.
*
* @return string
**/
public function get_content_lang() {
return $this->content_lang;
}
/**
* Get the current interface lang for this class.
*
* @return string
**/
public function get_interface_lang() {
return $this->interface_lang;
}
/**
* Set the current (content) lang for this class, and in Query Vars.
*
* @param string $lang The language code to switch to
* @return void
**/
public function switch_to_lang( $lang ) {
// @FIXME: Need to validate language here
if ( ! is_array( $this->lang_stack ) )
$this->lang_stack = array();
$this->lang_stack[] = $this->content_lang;
$this->set_content_lang( $lang );
set_query_var( 'lang', $this->content_lang );
}
/**
* Restore the previous lang from the switched stack.
*
* @return void
**/
public function restore_lang() {
$this->set_content_lang( array_pop( $this->lang_stack ) );
set_query_var( 'lang', $this->content_lang );
}
// Non-public Methods
// ------------------
/**
* Set the content language code and URL prefix for any
* subsequent requests.
*
* @FIXME: Currently we don't check that the language is valid
*
* @param string $code A language code
* @return void
**/
protected function set_content_lang( $code ) {
global $bbl_languages;
// Set the content language in the application
$this->content_lang = $code;
$this->url_prefix = $bbl_languages->get_url_prefix_from_code( $this->content_lang );
}
/**
* Set the interace language code.
*
* @FIXME: Currently we don't check that the language is valid
*
* @param string $code A language code
* @return void
**/
protected function set_interface_lang( $code ) {
// Set the interface language in the application
$this->interface_lang = $code;
}
/**
* Set the content language for the URL prefix provided.
*
* @param string $url_prefix A URL prefix, e.g. "de"
* @return void
**/
protected function set_content_lang_from_prefix( $url_prefix ) {
global $bbl_languages;
$this->set_content_lang( bbl_get_lang_from_prefix( $url_prefix ) );
}
/**
* Get the request string for the request, using code copied
* straight from WP->parse_request.
*
* @return string The request
**/
protected function get_request_string() {
global $wp_rewrite;
// @FIXME: Copying a huge hunk of code from WP->parse_request here, feels ugly.
// START: Huge hunk of WP->parse_request
if ( isset($_SERVER['PATH_INFO']) )
$pathinfo = $_SERVER['PATH_INFO'];
else
$pathinfo = '';
$pathinfo_array = explode('?', $pathinfo);
$pathinfo = str_replace("%", "%25", $pathinfo_array[0]);
$req_uri = $_SERVER['REQUEST_URI'];
$req_uri_array = explode('?', $req_uri);
$req_uri = $req_uri_array[0];
$self = $_SERVER['PHP_SELF'];
$home_path = parse_url(home_url());
if ( isset($home_path['path']) )
$home_path = $home_path['path'];
else
$home_path = '';
$home_path = trim($home_path, '/');
// Trim path info from the end and the leading home path from the
// front. For path info requests, this leaves us with the requesting
// filename, if any. For 404 requests, this leaves us with the
// requested permalink.
$req_uri = str_replace($pathinfo, '', $req_uri);
$req_uri = trim($req_uri, '/');
$req_uri = preg_replace("|^$home_path|", '', $req_uri);
$req_uri = trim($req_uri, '/');
$pathinfo = trim($pathinfo, '/');
$pathinfo = preg_replace("|^$home_path|", '', $pathinfo);
$pathinfo = trim($pathinfo, '/');
$self = trim($self, '/');
$self = preg_replace("|^$home_path|", '', $self);
$self = trim($self, '/');
// The requested permalink is in $pathinfo for path info requests and
// $req_uri for other requests.
if ( ! empty($pathinfo) && !preg_match('|^.*' . $wp_rewrite->index . '$|', $pathinfo) ) {
$request = $pathinfo;
} else {
// If the request uri is the index, blank it out so that we don't try to match it against a rule.
if ( is_object( $wp_rewrite ) && $req_uri == $wp_rewrite->index )
$req_uri = '';
$request = $req_uri;
}
// END: Huge hunk of WP->parse_request
return $request;
}
/**
* Sets the content language cookie where necessary. We are using cookies
* as we cannot get userdata at the set_locale action, which is where
* we need to read the user's language.
*
* @return void
**/
protected function maybe_set_cookie_content_lang() {
// @FIXME: At this point a mischievous XSS "attack" could set a user's content language for them
if ( $requested_lang = ( isset( $_GET[ 'lang' ] ) ) ? $_GET[ 'lang' ] : false )
setcookie( $this->content_lang_cookie, $requested_lang, time() + 31536000, COOKIEPATH, COOKIE_DOMAIN);
}
/**
* Sets the admin language cookie where necessary. We are using cookies
* as we cannot get userdata at the set_locale action, which is where
* we need to read the user's language.
*
* @return void
**/
protected function maybe_set_cookie_interface_lang() {
// @FIXME: At this point a mischievous XSS "attack" could set a user's admin area language for them
if ( $requested_lang = ( isset( $_POST[ 'interface_lang' ] ) ) ? $_POST[ 'interface_lang' ] : false )
setcookie( $this->interface_lang_cookie, $requested_lang, time() + 31536000, COOKIEPATH, COOKIE_DOMAIN);
}
/**
* Gets the language code from the content language cookie.
*
* @TODO: This should use a cookie that's keyed to the current user when present
*
* @return string A language code
**/
protected function get_cookie_content_lang() {
return ( isset( $_COOKIE[ $this->content_lang_cookie ] ) ) ? $_COOKIE[ $this->content_lang_cookie ] : '';
}
/**
* Gets the language code from the interface language cookie.
*
* @TODO: This should use a cookie that's keyed to the current user when present
*
* @return string A language code
**/
protected function get_cookie_interface_lang() {
return ( isset( $_COOKIE[ $this->interface_lang_cookie] ) ) ? $_COOKIE[ $this->interface_lang_cookie ] : '';
}
/**
* Checks the DB structure is up to date.
*
* @return void
* @author Simon Wheatley
**/
protected function maybe_update() {
global $wpdb;
$option_name = 'bbl-locale-version';
$version = get_option( $option_name, 0 );
if ( $this->version == $version )
return;
if ( $version < 1 ) {
error_log( "Babble Locale: Flushing rewrite rules" );
flush_rewrite_rules();
}
error_log( "Babble Locale: Done updates" );
update_option( $option_name, $this->version );
}
}
global $bbl_locale;
$bbl_locale = new Babble_Locale();
================================================
FILE: class-meta.php
================================================
<?php
/**
* Class for handling post meta translations.
*
* @package Babble
* @since 1.5
*/
abstract class Babble_Meta_Field {
public function __construct( WP_Post $post, $meta_key, $meta_title, array $args = array() ) {
$this->post = $post;
$this->meta_key = $meta_key;
$this->meta_title = $meta_title;
$this->meta_value = get_post_meta( $this->post->ID, $this->meta_key, true );
$this->args = $args;
}
abstract public function get_input( $name, $value );
public function get_output() {
return esc_html( $this->get_value() );
}
public function get_title() {
return $this->meta_title;
}
public function get_value() {
return $this->meta_value;
}
public function get_key() {
return $this->meta_key;
}
public function update( $value, WP_Post $job ) {
return $value;
}
}
class Babble_Meta_Field_Text extends Babble_Meta_Field {
public function get_input( $name, $value ) {
return sprintf( '<input type="text" name="%s" value="%s">',
esc_attr( $name ),
esc_attr( $value )
);
}
}
class Babble_Meta_Field_Textarea extends Babble_Meta_Field {
public function get_input( $name, $value ) {
return sprintf( '<textarea name="%s" rows="10">%s</textarea>',
esc_attr( $name ),
esc_textarea( $value )
);
}
public function get_output() {
return nl2br( esc_html( $this->get_value() ) );
}
}
class Babble_Meta_Field_Editor extends Babble_Meta_Field {
public function get_input( $name, $value ) {
$args = array(
'textarea_name' => $name,
);
# see _WP_Editors()::parse_settings() for available editor settings
if ( !empty( $this->args['editor_settings'] ) ) {
$args = array_merge( $args, $this->args['editor_settings'] );
}
ob_start();
wp_editor( $value, sprintf( 'meta-input-%s', $this->get_key() ), $args );
return ob_get_clean();
}
public function get_output() {
$args = array(
'textarea_name' => 'doesnotmatter',
'media_buttons' => false,
'tinymce' => array(
'readonly' => 1,
),
);
ob_start();
wp_editor( $this->get_value(), sprintf( 'meta-output-%s', $this->get_key() ), $args );
return ob_get_clean();
}
}
================================================
FILE: class-plugin.php
================================================
<?php
// ======================================================================================
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
// ======================================================================================
// @author Simon Wheatley (http://simonwheatley.co.uk)
// @version 1.0
// @copyright Copyright © 2010 Simon Wheatley, All Rights Reserved
// @copyright Some parts Copyright © 2007 John Godley, All Rights Reserved
// ======================================================================================
// 1.0 - Initial release
// 1.01 - Added add_shortcode
// 1.10 - Added code to allow the base class to be used in a theme
// 1.2 - Truncate helper method, admin notices/errors, throw error if not provided
// with name in setup method call, default $pluginfile to __FILE__, bugfix around
// option key in delete_option method.
// 1.3 - Locale stuff
// - Fix for get_option
// 1.31 - Attempt to cope with Win32 directory separators
// 1.32 - Add a remove_filter method
// 1.33 - Add `sil_plugins_dir` and `sil_plugins_url` filters, to allow placement
// outside the `wp-content/plugins/` folder, for example using `require_once`
// to include from the theme `functions.php`.
// ======================================================================================
/**
* Wraps up several useful functions for WordPress plugins and provides a method to separate
* display HTML from PHP code.
*
* <h4>Display Rendering</h4>
*
* The class uses a similar technique to Ruby On Rails views, whereby the display HTML is kept
* in a separate directory and file from the main code. A display is 'rendered' (sent to the browser)
* or 'captured' (returned to the calling function).
*
* Template files are separated into two areas: admin and user. Admin templates are only for display in
* the WordPress admin interface, while user templates are typically for display on the site (although neither
* of these are enforced). All templates are PHP code, but are referred to without .php extension.
*
* The reason for this separation is that one golden rule of plugin creation is that someone will
* always want to change the formatting and style of your output. Rather than forcing them to
* modify the plugin (bad), or modify files within the plugin (equally bad), the class allows
* user templates to be overridden with files contained within the theme.
*
* An additional benefit is that it leads to code re-use, especially with regards to Ajax (i.e.
* your display code can be called from many locations)
*
* @package Babble
* @author Simon Wheatley
* @copyright Copyright (C) Simon Wheatley (except where noted)
**/
class Babble_Plugin {
/**
* The name of this plugin
*
* @var string
**/
protected $name;
/**
* The filepath to the directory containing this plugin
*
* @var string
**/
protected $dir;
/**
* The URL for the directory containing this plugin
*
* @var string
**/
protected $url;
/**
* Useful for switching between debug and compressed scripts.
*
* @var string
**/
protected $suffix;
/**
* Records the type of this class, either 'plugin' or 'theme'.
*
* @var string
**/
protected $type;
/**
* Note the name of the function to call when the theme is activated.
*
* @var string
**/
protected $theme_activation_function;
/**
* Initiate!
*
* @return void
* @author Simon Wheatley
**/
public function setup( $name = '', $type = null ) {
if ( ! $name )
throw new exception( "Please pass the name parameter into the setup method." );
$this->name = $name;
// Attempt to handle a Windows
$ds = ( defined( 'DIRECTORY_SEPARATOR' ) ) ? DIRECTORY_SEPARATOR : '\\';
$file = str_replace( $ds, '/', __FILE__ );
$plugins_dir = str_replace( $ds, '/', dirname( __FILE__ ) );
// Setup the dir and url for this plugin/theme
if ( 'theme' == $type ) {
// This is a theme
$this->type = 'theme';
$this->dir = get_stylesheet_directory();
$this->url = get_stylesheet_directory_uri();
} elseif ( stripos( $file, $plugins_dir ) !== false || 'plugin' == $type ) {
// This is a plugin
$this->type = 'plugin';
// Allow someone to override the assumptions we're making here about where
// the plugin is held. For example, if this plugin is included as part of
// the files for a theme, in wp-content/themes/[your theme]/plugins/ then
// you could hook `sil_plugins_dir` and `sil_plugins_url` to correct
// our assumptions.
// N.B. Because this code is running when the file is required, other plugins
// may not be loaded and able to hook these filters!
$plugins_dir = apply_filters( 'sil_plugins_dir', $plugins_dir, $this->name );
$plugins_url = apply_filters( 'sil_plugins_url', plugins_url( '', __FILE__ ), $this->name );
$this->dir = trailingslashit( $plugins_dir );
$this->url = trailingslashit( $plugins_url );
} else {
// WTF?
error_log( 'PLUGIN/THEME ERROR: Cannot find ' . $plugins_dir . ' or "themes" in ' . $file );
}
// Suffix for enqueuing
$this->suffix = defined('SCRIPT_DEBUG') && SCRIPT_DEBUG ? '.dev' : '';
if ( is_admin() ) {
// Admin notices
$this->add_action( 'admin_notices', '_admin_notices' );
}
$this->add_action( 'init', 'load_locale' );
}
/**
* Hook called to change the locale directory.
*
* @return void
* @author © John Godley
**/
function load_locale() {
// Here we manually fudge the plugin locale as WP doesnt allow many options
$locale = get_locale();
if( empty( $locale ) )
$locale = 'en_US';
$mofile = $this->dir( "/locale/$locale.mo" );
load_textdomain( $this->name, $mofile );
}
/**
* Register a WordPress action and map it back to the calling object
*
* @param string $action Name of the action
* @param string $function Function name (optional)
* @param int $priority WordPress priority (optional)
* @param int $accepted_args Number of arguments the function accepts (optional)
* @return void
* @author © John Godley
**/
function add_action ($action, $function = '', $priority = 10, $accepted_args = 1) {
if ( $priority === null )
$priority = 10;
add_action ($action, array ($this, $function == '' ? $action : $function), $priority, $accepted_args);
}
/**
* Register a WordPress filter and map it back to the calling object
*
* @param string $action Name of the action
* @param string $function Function name (optional)
* @param int $priority WordPress priority (optional)
* @param int $accepted_args Number of arguments the function accepts (optional)
* @return void
* @author © John Godley
**/
function add_filter ($filter, $function = '', $priority = 10, $accepted_args = 1) {
add_filter ($filter, array ($this, $function == '' ? $filter : $function), $priority, $accepted_args);
}
/**
* De-register a WordPress filter and map it back to the calling object
*
* @param string $action Name of the action
* @param string $function Function name (optional)
* @param int $priority WordPress priority (optional)
* @param int $accepted_args Number of arguments the function accepts (optional)
* @return void
* @author © John Godley
**/
function remove_filter ($filter, $function = '', $priority = 10, $accepted_args = 1) {
remove_filter ($filter, array ($this, $function == '' ? $filter : $function), $priority, $accepted_args);
}
/**
* Special activation function that takes into account the plugin directory
*
* @param string $pluginfile The plugin file location (i.e. __FILE__)
* @param string $function Optional function name, or default to 'activate'
* @return void
* @author © John Godley
**/
function register_activation ( $pluginfile = __FILE__, $function = '' ) {
if ( $this->type == 'plugin' ) {
add_action ('activate_'.basename (dirname ($pluginfile)).'/'.basename ($pluginfile), array ($this, $function == '' ? 'activate' : $function));
} elseif ( $this->type == 'theme' ) {
$this->theme_activation_function = ( $function ) ? $function : 'activate';
add_action ('load-themes.php', array ( $this, 'theme_activation' ) );
}
}
/**
* Hack to catch theme activation. We hook the load-themes.php action, look for the
* "activated" GET param and make a big fat assumption if we find it.
*
* @return void
* @author Simon Wheatley
**/
public function theme_activation() {
$activated = (bool) @ $_GET[ 'activated' ];
if ( ! $activated )
return;
if ( ! $this->theme_activation_function )
return;
// Looks like the theme might just have been activated, call the registered function
$this->{$this->theme_activation_function}();
}
/**
* Special deactivation function that takes into account the plugin directory
*
* @param string $pluginfile The plugin file location (i.e. __FILE__)
* @param string $function Optional function name, or default to 'deactivate'
* @return void
* @author © John Godley
**/
function register_deactivation ($pluginfile, $function = '') {
add_action ('deactivate_'.basename (dirname ($pluginfile)).'/'.basename ($pluginfile), array ($this, $function == '' ? 'deactivate' : $function));
}
/**
* Renders a template, looking first for the template file in the theme directory
* and afterwards in this plugin's /theme/ directory.
*
* @return void
* @author Simon Wheatley
**/
protected function render( $template_file, $vars = null ) {
// Maybe override the template with our own file
$template_file = $this->locate_template( $template_file );
// Ensure we have the same vars as regular WP templates
global $posts, $post, $wp_did_header, $wp_did_template_redirect, $wp_query, $wp_rewrite, $wpdb, $wp_version, $wp, $id, $comment, $user_ID;
if ( is_array($wp_query->query_vars) )
extract($wp_query->query_vars, EXTR_SKIP);
// Plus our specific template vars
if ( is_array( $vars ) )
extract( $vars );
require( $template_file );
}
/**
* Renders an admin template from this plugin's /templates-admin/ directory.
*
* @return void
* @author Simon Wheatley
**/
protected function render_admin( $template_file, $vars = null ) {
// Plus our specific template vars
if ( is_array( $vars ) )
extract( $vars );
// Try to render
if ( file_exists( $this->dir( "templates-admin/$template_file" ) ) ) {
require( $this->dir( "templates-admin/$template_file" ) );
} else {
$msg = sprintf( __( "This plugin admin template could not be found: %s" ), $this->dir( "templates-admin/$template_file" ) );
error_log( "Plugin template error: $msg" );
echo "<p style='background-color: #ffa; border: 1px solid red; color: #300; padding: 10px;'>$msg</p>";
}
}
/**
* Returns a section of user display code, returning the rendered markup.
*
* @param string $ug_name Name of the admin file (without extension)
* @param string $array Array of variable name=>value that is available to the display code (optional)
* @return void
* @author © John Godley
**/
protected function capture( $template_file, $vars = null ) {
ob_start();
$this->render( $template_file, $vars );
$output = ob_get_contents();
ob_end_clean();
return $output;
}
/**
* Returns a section of user display code, returning the rendered markup.
*
* @param string $ug_name Name of the admin file (without extension)
* @param string $array Array of variable name=>value that is available to the display code (optional)
* @return void
* @author © John Godley
**/
protected function capture_admin( $template_file, $vars = null ) {
ob_start();
$this->render_admin( $template_file, $vars );
$output = ob_get_contents();
ob_end_clean();
return $output;
}
/**
* Hooks the WP admin_notices action to render any notices
* that have been set with the set_admin_notice method.
*
* @return void
* @author Simon Wheatley
**/
public function _admin_notices() {
$notices = $this->get_option( 'admin_notices' );
$errors = $this->get_option( 'admin_errors' );
if ( $errors ) {
foreach ( $errors as $error ) {
$this->render_admin_error( $error );
$this->delete_option( 'admin_errors' );
}
}
if ( $notices ) {
foreach ( $notices as $notice ) {
$this->render_admin_notice( $notice );
$this->delete_option( 'admin_notices' );
}
}
}
/**
* Echoes some HTML for an admin notice.
*
* @param string $notice The notice
* @return void
* @author Simon Wheatley
**/
protected function render_admin_notice( $notice ) {
echo "<div class='updated'><p>$notice</p></div>";
}
/**
* Echoes some HTML for an admin error.
*
* @param string $error The error
* @return void
* @author Simon Wheatley
**/
protected function render_admin_error( $error ) {
echo "<div class='error'><p>$error</p></div>";
}
/**
* Sets a string as an admin notice.
*
* @param string $msg A *localised* admin notice message
* @return void
* @author Simon Wheatley
**/
protected function set_admin_notice( $msg ) {
$notices = (array) $this->get_option( 'admin_notices' );
$notices[] = $msg;
$this->update_option( 'admin_notices', $notices );
}
/**
* Sets a string as an admin error.
*
* @param string $msg A *localised* admin error message
* @return void
* @author Simon Wheatley
**/
protected function set_admin_error( $msg ) {
$errors = (array) $this->get_option( 'admin_errors' );
$errors[] = $msg;
$this->update_option( 'admin_errors', $errors );
}
/**
* Takes a filename and attempts to find that in the designated plugin templates
* folder in the theme (defaults to main theme directory, but uses a custom filter
* to allow theme devs to specify a sub-folder for all plugin template files using
* this system).
*
* Searches in the STYLESHEETPATH before TEMPLATEPATH to cope with themes which
* inherit from a parent theme by just overloading one file.
*
* @param string $template_file A template filename to search for
* @return string The path to the template file to use
* @author Simon Wheatley
**/
protected function locate_template( $template_file ) {
$located = '';
$sub_dir = apply_filters( 'sw_plugin_tpl_dir', '' );
if ( $sub_dir )
$sub_dir = trailingslashit( $sub_dir );
// If there's a tpl in a (child theme or theme with no child)
if ( file_exists( STYLESHEETPATH . "/$sub_dir" . $template_file ) )
return STYLESHEETPATH . "/$sub_dir" . $template_file;
// If there's a tpl in the parent of the current child theme
else if ( file_exists( TEMPLATEPATH . "/$sub_dir" . $template_file ) )
return TEMPLATEPATH . "/$sub_dir" . $template_file;
// Fall back on the bundled plugin template (N.B. no filtered subfolder involved)
else if ( file_exists( $this->dir( "templates/$template_file" ) ) )
return $this->dir( "templates/$template_file" );
// Oh dear. We can't find the template.
$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" ) );
error_log( "Template error: $msg" );
echo "<p style='background-color: #ffa; border: 1px solid red; color: #300; padding: 10px;'>$msg</p>";
}
/**
* Register a WordPress meta box
*
* @param string $id ID for the box, also used as a function name if none is given
* @param string $title Title for the box
* @param int $page The type of edit page on which to show the box (post, page, link).
* @param string $function Function name (optional)
* @param string $context e.g. 'advanced' or 'core' (optional)
* @param int $priority Priority, rough effect on the ordering (optional)
* @param mixed $args Some arguments to pass to the callback function as part of a larger object (optional)
* @return void
* @author © John Godley
**/
function add_meta_box( $id, $title, $function = '', $page, $context = 'advanced', $priority = 'default', $args = null )
{
require_once( ABSPATH . 'wp-admin/includes/template.php' );
add_meta_box( $id, $title, array( $this, $function == '' ? $id : $function ), $page, $context, $priority, $args );
}
/**
* Add hook for shortcode tag.
*
* There can only be one hook for each shortcode. Which means that if another
* plugin has a similar shortcode, it will override yours or yours will override
* theirs depending on which order the plugins are included and/or ran.
*
* @param string $tag Shortcode tag to be searched in post content.
* @param callable $func Hook to run when shortcode is found.
*/
protected function add_shortcode( $tag, $function = null ) {
add_shortcode( $tag, array( $this, $function == '' ? $tag : $function ) );
}
/**
* Returns the filesystem path for a file/dir within this plugin.
*
* @param $path string The path within this plugin, e.g. '/js/clever-fx.js'
* @return string Filesystem path
* @author Simon Wheatley
**/
protected function dir( $path ) {
return trailingslashit( $this->dir ) . trim( $path, '/' );
}
/**
* Returns the URL for for a file/dir within this plugin.
*
* @param $path string The path within this plugin, e.g. '/js/clever-fx.js'
* @return string URL
* @author Simon Wheatley
**/
protected function url( $path ) {
return esc_url( trailingslashit( $this->url ) . trim( $path, '/' ) );
}
/**
* Gets the value of an option named as per this plugin.
*
* @return mixed Whatever
* @author Simon Wheatley
**/
protected function get_all_options() {
return get_option( $this->name );
}
/**
* Sets the value of an option named as per this plugin.
*
* @return mixed Whatever
* @author Simon Wheatley
**/
protected function update_all_options( $value ) {
return update_option( $this->name, $value );
}
/**
* Gets the value from an array index on an option named as per this plugin.
*
* @param string $key A string
* @return mixed Whatever
* @author Simon Wheatley
**/
public function get_option( $key, $value = null ) {
$option = get_option( $this->name );
if ( ! is_array( $option ) || ! isset( $option[ $key ] ) )
return $value;
return $option[ $key ];
}
/**
* Sets the value on an array index on an option named as per this plugin.
*
* @param string $key A string
* @param mixed $value Whatever
* @return bool False if option was not updated and true if option was updated.
* @author Simon Wheatley
**/
protected function update_option( $key, $value ) {
$option = get_option( $this->name );
$option[ $key ] = $value;
return update_option( $this->name, $option );
}
/**
* Deletes the array index on an option named as per this plugin.
*
* @param string $key A string
* @return bool False if option was not updated and true if option was updated.
* @author Simon Wheatley
**/
protected function delete_option( $key ) {
$option = get_option( $this->name );
if ( isset( $option[ $key ] ) )
unset( $option[ $key ] );
return update_option( $this->name, $option );
}
/**
* Echoes out some JSON indicating that stuff has gone wrong.
*
* @param string $msg The error message
* @return void
* @author Simon Wheatley
**/
protected function ajax_die( $msg ) {
$data = array( 'msg' => $msg, 'success' => false );
echo json_encode( $data );
// N.B. No 500 header
exit;
}
/**
* Truncates a string in a human friendly way.
*
* @param string $str The string to truncate
* @param int $num_words The number of words to truncate to
* @return string The truncated string
* @author Simon Wheatley
**/
protected function truncate( $str, $num_words )
{
$str = strip_tags( $str );
$words = explode(' ', $str );
if ( count( $words ) > $num_words) {
$k = $num_words;
$use_dotdotdot = 1;
} else {
$k = count( $words );
$use_dotdotdot = 0;
}
$words = array_slice( $words, 0, $k );
$excerpt = trim( join( ' ', $words ) );
$excerpt .= ($use_dotdotdot) ? '…' : '';
return $excerpt;
}
} // END Babble_Plugin class
?>
================================================
FILE: class-post-public.php
================================================
<?php
/**
* Class for handling the public, content handling post types.
*
* @package Babble
* @since 0.1
*/
class Babble_Post_Public extends Babble_Plugin {
/**
* A simple flag to stop infinite recursion when syncing
* post meta places.
*
* @var boolean
**/
protected $no_meta_recursion;
/**
* A simple flag to stop infinite recursion in various
* places (except for post meta).
*
* @var boolean
**/
protected $no_recursion;
/**
* The shadow (translated) post types created by this plugin.
*
* @var array
**/
protected $post_types;
/**
* A structure describing the languages served by various post types.
*
* @var array
**/
protected $lang_map;
/**
* Another structure describing the languages served by various post types.
*
* @var array
**/
protected $lang_map2;
/**
* A version number to use for cache busting, database updates, etc
*
* @var int
**/
protected $version;
/**
* An array of query_vars and slugs for our shadow post types,
* we use changes to this to determine if rewrite rules
* need flushing.
*
* @var array
**/
protected $slugs_and_vars;
/**
* An array of Post IDs for posts that are in the process of
* being deleted.
*
* @var array
**/
protected $deleting_post_ids;
/**
* An array of meta_keys, indexed by meta_key, containing
* meta_keys we KNOW to be added as unique.
*
* @var array
**/
protected $unique_meta_keys;
public function __construct() {
$this->setup( 'babble-post-public', 'plugin' );
$this->add_action( 'added_post_meta', null, null, 4 );
$this->add_action( 'admin_init' );
$this->add_action( 'clean_post_cache' );
$this->add_action( 'body_class', null, null, 2 );
$this->add_action( 'before_delete_post', 'clean_post_cache' );
$this->add_action( 'deleted_post', 'clean_post_cache' );
$this->add_action( 'deleted_post_meta', null, null, 4 );
$this->add_action( 'load-post-new.php', 'load_post_new' );
$this->add_action( 'manage_pages_custom_column', 'manage_posts_custom_column', null, 2 );
$this->add_action( 'manage_posts_custom_column', 'manage_posts_custom_column', null, 2 );
$this->add_action( 'parse_request' );
$this->add_action( 'post_updated' );
$this->add_action( 'pre_get_posts', null, 11 );
$this->add_action( 'registered_post_type', null, null, 2 );
$this->add_action( 'transition_post_status', null, null, 3 );
$this->add_action( 'updated_post_meta', null, null, 4 );
$this->add_action( 'wp_before_admin_bar_render' );
$this->add_filter( 'add_menu_classes' );
$this->add_filter( 'add_post_metadata', null, null, 5 );
$this->add_filter( 'bbl_sync_meta_key', 'sync_meta_key', null, 2 );
$this->add_filter( 'manage_posts_columns', 'manage_posts_columns', null, 2 );
$this->add_filter( 'page_link', null, null, 2 );
$this->add_filter( 'post_link', 'post_type_link', null, 3 );
$this->add_filter( 'post_type_archive_link', null, null, 2 );
$this->add_filter( 'post_type_link', null, null, 3 );
$this->add_filter( 'get_sample_permalink', null, null, 5 );
$this->add_filter( 'single_template' );
$this->add_filter( 'the_posts', null, null, 2 );
$this->add_filter( 'bbl_translated_taxonomy', null, null, 2 );
$this->add_filter( 'admin_body_class' );
$this->initiate();
}
/**
* Initiates
*
* @return void
**/
public function initiate() {
$this->lang_map = array();
$this->post_types = array();
$this->slugs_and_vars = array();
$this->no_meta_recursion = false;
$this->deleting_post_ids = array();
$this->version = 9;
// Ensure we catch any existing language shadow post_types already registered
$core_post_types = array( 'post', 'page', 'attachment' );
if ( is_array( $this->post_types ) )
$post_types = array_merge( $core_post_types, array_keys( $this->post_types ) );
else
$post_types = $core_post_types;
register_taxonomy( 'post_translation', $post_types, array(
'rewrite' => false,
'public' => false,
'show_ui' => false,
'show_in_nav_menus' => false,
) );
}
/**
* Hooks the WP admin_init action to
*
* @return void
**/
public function admin_init() {
$this->maybe_upgrade();
$post_type = false;
if ( isset( $_GET[ 'post_type' ] ) ) {
$post_type = $_GET[ 'post_type' ];
} else if ( isset( $_GET[ 'post' ] ) ) {
$post = (int) $_GET[ 'post' ];
$post = get_post( $post );
$post_type = $post->post_type;
}
$menu_id = false;
if ( isset( $this->post_types[ $post_type ] ) )
$menu_id = '#menu-posts-' . $this->post_types[ $post_type ];
$data = array(
'menu_id' => $menu_id,
'is_default_lang' => (bool) ( bbl_get_current_lang_code() == bbl_get_default_lang_code() ),
'is_bbl_post_type' => (bool) ( 0 === strpos( $post_type, 'bbl_' ) ),
);
wp_enqueue_script( 'post-public-admin', $this->url( 'js/post-public-admin.js' ), array( 'jquery' ), filemtime( $this->dir( 'js/post-public-admin.js' ) ) );
wp_localize_script( 'post-public-admin', 'bbl_post_public', $data );
}
/**
* Initialise a translation for the given post.
*
* @param WP_Post|int $origin_post The origin post object or post ID
* @param string $lang_code The language code for the new translation
* @return WP_Post The translation post
*/
public function initialise_translation( $origin_post, $lang_code ) {
$origin_post = get_post( $origin_post );
$new_post_type = $this->get_post_type_in_lang( $origin_post->post_type, $lang_code );
$transid = $this->get_transid( $origin_post->ID );
// Insert translation:
$this->no_recursion = true;
$new_post_id = wp_insert_post( array(
'post_type' => $new_post_type,
'post_status' => 'draft',
), true );
$this->no_recursion = false;
$new_post = get_post( $new_post_id );
// Assign transid to translation:
$this->set_transid( $new_post, $transid );
// Copy all the metadata across
$this->sync_post_meta( $new_post->ID );
// Copy the various core post properties across
$this->sync_properties( $origin_post->ID, $new_post->ID );
do_action( 'bbl_created_new_shadow_post', $new_post->ID, $origin_post->ID );
return $new_post;
}
/**
* Hooks the WP load-post-new.php action to stop translators
* creating new posts in languages other than the default.
*
* @return void
**/
public function load_post_new() {
$screen = get_current_screen();
if ( 'post' != $screen->base || 'add' != $screen->action )
return;
if ( bbl_get_current_lang_code() == bbl_get_default_lang_code() )
return;
if ( !bbl_is_translated_post_type( $screen->post_type ) )
return;
wp_die( __( 'You can only create content in your site\'s default language. Please consult your editorial team.', 'babble' ), '', array( 'back_link' => true ) );
}
/**
* Hooks the WP wp_before_admin_bar_render action
* to prune out unneeded post type add controls from
* the add menu.
*
* @return void
**/
public function wp_before_admin_bar_render() {
global $wp_admin_bar;
if ( ! bbl_is_default_lang() )
$wp_admin_bar->remove_node( 'new-content' );
}
/**
* Hooks the WP registered_post_type action.
*
* @param string $post_type The post type which has just been registered.
* @param object $args The arguments with which the post type was registered
* @return void
**/
public function registered_post_type( $post_type, $args ) {
// Don't bother with non-public post_types for now
// @FIXME: This may need to change for menus?
if ( false === $args->public )
return;
// Don't shadow shadow post types, it's going to get silly
if ( in_array( $post_type, $this->post_types ) )
return;
if ( $this->no_recursion )
return;
$this->no_recursion = 'registered_post_type';
$langs = bbl_get_active_langs();
// Lose the default language as any existing post types are in that language
unset( $langs[ bbl_get_default_lang_url_prefix() ] );
// $args is an object at this point, but register_post_type needs an array
$args = get_object_vars( $args );
// @FIXME: Is it reckless to convert ALL object instances in $args to an array?
foreach ( $args as $key => & $arg ) {
if ( is_object( $arg ) )
$arg = get_object_vars( $arg );
// Don't set any args reserved for built-in post_types
if ( '_' == substr( $key, 0, 1 ) )
unset( $args[ $key ] );
}
$features = $this->get_features_supported_by_post_type( $post_type );
$args[ 'supports' ] = array();
foreach ( $features as $feature => $true )
$args[ 'supports' ][] = $feature;
// I am a little concerned that this argument may make things
// brittle, e.g. the UI might stop showing up in the shadow
// post type edit screens, p'raps.
$args[ 'show_ui' ] = true;
$slug = ( $args[ 'rewrite' ][ 'slug' ] ) ? $args[ 'rewrite' ][ 'slug' ] : $post_type;
$archive_slug = false;
if ( $archive_slug = $args[ 'has_archive' ] )
if ( ! is_string( $args[ 'has_archive' ] ) )
$archive_slug = $slug;
$current_lang_code = bbl_get_current_lang_code();
foreach ( $langs as $lang ) {
$new_args = $args;
// @FIXME: We are in danger of a post_type name being longer than 20 chars
// I would prefer to keep the post_type human readable, as human devs and sysadmins always
// end up needing to read this kind of thing.
// @FIXME: Should I be sanitising these values?
$new_post_type = strtolower( "{$post_type}_{$lang->code}" );
if ( strlen( $new_post_type ) > 20 ) {
trigger_error( sprintf( __( 'Warning: The translated name for the post type %s is longer than %d characters. This *will* cause problems.', 'babble' ),
esc_html( $post_type ),
20
) );
}
if ( false !== $args[ 'rewrite' ] ) {
if ( ! is_array( $new_args[ 'rewrite' ] ) )
$new_args[ 'rewrite' ] = array();
$new_args[ 'query_var' ] = $new_args[ 'rewrite' ][ 'slug' ] = $this->get_slug_in_lang( $slug, $lang, $args );
$new_args[ 'has_archive' ] = $this->get_slug_in_lang( $archive_slug, $lang );
}
$this->slugs_and_vars[ $lang->code . '_' . $post_type ] = array(
'query_var' => $new_args[ 'query_var' ],
'has_archive' => $new_args[ 'has_archive' ],
);
// Don't let the translated post types show up in the search if their
// language is not the current language.
if ( $lang->code != $current_lang_code ) {
$new_args['exclude_from_search'] = true;
$new_args['capabilities']['create_posts'] = 'do_not_allow';
}
$result = register_post_type( $new_post_type, $new_args );
if ( is_wp_error( $result ) ) {
error_log( "Error creating shadow post_type for $new_post_type: " . print_r( $result, true ) );
} else {
$this->post_types[ $new_post_type ] = $post_type;
$this->lang_map[ $new_post_type ] = $lang->code;
// @TODO: Refactor the $this::lang_map array so we can use this new structure instead
if ( ! isset( $this->lang_map2[ $lang->code ] ) || ! is_array( $this->lang_map2[ $lang->code ] ) )
$this->lang_map2[ $lang->code ] = array();
$this->lang_map2[ $lang->code ][ $post_type ] = $new_post_type;
// This will not work until init has run at the early priority used
// to register the post_translation taxonomy. However we catch all the
// post_types registered before the hook runs, so we don't miss any
// (take a look at where we register post_translation for more info).
register_taxonomy_for_object_type( 'post_translation', $new_post_type );
}
}
// Exclude the registered post type from search if it's language isn't
// the current language.
if ( $current_lang_code != bbl_get_default_lang_code() ) {
$post_type_obj = get_post_type_object( $post_type );
$post_type_obj->exclude_from_search = true;
}
do_action( 'bbl_registered_shadow_post_types', $post_type );
$this->no_recursion = false;
}
/**
* Store whether a particular meta_key is unique or not. Pretty hacky.
*
* @param null $null Follows a pattern for actions/filters relating to meta, but meta ID not set yet so null
* @param int $post_id The ID for the WordPress Post object this meta relates to
* @param string $meta_key The key for this meta entry
* @param mixed $meta_value The new value for this meta entry
* @param bool $unique Whether the meta_key should be unique
* @return null Always return null, or we are bypassing the meta save to DB
**/
public function add_post_metadata( $null, $post_id, $meta_key, $meta_value, $unique ) {
if ( $unique ) {
$this->unique_meta_keys[ $meta_key ] = $meta_key;
}
return null;
}
/**
* Hooks the WP added_post_meta action to sync metadata across to the
* translations in shadow post types.
*
* @param int $meta_id The ID for this meta entry
* @param int $post_id The ID for the WordPress Post object this meta relates to
* @param string $meta_key The key for this meta entry
* @param mixed $meta_value The new value for this meta entry
* @return void
**/
public function added_post_meta( $meta_id, $post_id, $meta_key, $meta_value ) {
// Some metadata shouldn't be synced
if ( ! apply_filters( 'bbl_sync_meta_key', true, $meta_key ) )
return;
if ( $this->no_meta_recursion )
return;
$this->no_meta_recursion = 'added_post_meta';
$unique = isset( $this->unique_meta_keys[ $meta_key ] );
$translations = $this->get_post_translations( $post_id );
foreach ( $translations as $lang_code => & $translation ) {
if ( $this->get_post_lang_code( $post_id ) == $lang_code )
continue;
add_post_meta( $translation->ID, $meta_key, $meta_value, $unique );
}
$this->no_meta_recursion = false;
}
/**
* Hooks the WP updated_post_meta action to sync metadata across to the
* translations in shadow post types.
*
* @param int $meta_id The ID for this meta entry
* @param int $post_id The ID for the WordPress Post object this meta relates to
* @param string $meta_key The key for this meta entry
* @param mixed $meta_value The new value for this meta entry
* @return void
**/
public function updated_post_meta( $meta_id, $post_id, $meta_key, $meta_value ) {
// Some metadata shouldn't be synced
if ( ! apply_filters( 'bbl_sync_meta_key', true, $meta_key ) )
return;
if ( $this->no_meta_recursion )
return;
$this->no_meta_recursion = 'updated_post_meta';
$translations = $this->get_post_translations( $post_id );
foreach ( $translations as $lang_code => & $translation ) {
if ( $this->get_post_lang_code( $post_id ) == $lang_code )
continue;
update_post_meta( $translation->ID, $meta_key, $meta_value );
}
$this->no_meta_recursion = false;
}
/**
* Hooks the WP deleted_post_meta action to sync metadata across to the
* translations in shadow post types.
*
* @param int $meta_id The ID for this meta entry
* @param int $post_id The ID for the WordPress Post object this meta relates to
* @param string $meta_key The key for this meta entry
* @param mixed $meta_value The new value for this meta entry
* @return void
**/
public function deleted_post_meta( $meta_id, $post_id, $meta_key, $meta_value ) {
// When we are deleting posts, we don't want to sync
// the metadata deletion across the other posts in
// the same translation group
if ( in_array( $post_id, $this->deleting_post_ids ) )
return;
// Some metadata shouldn't be synced
if ( ! apply_filters( 'bbl_sync_meta_key', true, $meta_key ) )
return;
if ( $this->no_meta_recursion )
return;
$this->no_meta_recursion = 'deleted_post_meta';
$translations = $this->get_post_translations( $post_id );
foreach ( $translations as $lang_code => & $translation ) {
if ( $this->get_post_lang_code( $post_id ) == $lang_code )
continue;
delete_post_meta( $translation->ID, $meta_key );
}
$this->no_meta_recursion = false;
}
/**
* Hooks the WP pre_get_posts ref action in the WP_Query,
* for the main query it does nothing, for other queries
* it switches the post types to our shadow post types.
*
* @param WP_Query $wp_query A WP_Query object, passed by reference
* @return void (param passed by reference)
**/
public function pre_get_posts( WP_Query & $query ) {
if ( false === $query->get( 'bbl_translate' ) ) {
return;
}
if ( $query->is_main_query() ) {
return;
}
# @TODO we should scrap this and more intelligently filter the QVs rather than basing it on whether we're on a media tab
if ( $this->is_media_upload_tab( 'gallery' ) ) {
return;
}
if ( $this->is_media_manager() ) {
return;
}
$query->query_vars = $this->translate_query_vars( $query->query_vars );
}
/**
* Hooks the WP parse_request action
*
* FIXME: Should I be extending and replacing the WP class?
*
* @param WP $wp WP object, passed by reference (so no need to return)
* @return void
**/
public function parse_request( WP & $wp ) {
if ( isset( $wp->query_vars['bbl_translate'] ) and ( false === $wp->query_vars['bbl_translate'] ) ) {
return;
}
if ( is_admin() ) {
return;
}
$wp->query_vars = $this->translate_query_vars( $wp->query_vars, $wp->request );
}
/**
* Hooks the WP the_posts filter on WP_Query.
*
* Check the post_title, post_excerpt, post_content and substitute from
* the default language where appropriate.
*
* @param array $posts The posts retrieved by WP_Query, passed by reference
* @param WP_Query $wp_query The WP_Query, passed by reference
* @return array The posts
**/
public function the_posts( array $posts, WP_Query & $wp_query ) {
if ( is_admin() )
return $posts;
// Get fallback content in the default language
$subs_index = array();
foreach ( $posts as & $post ) {
if ( empty( $post->post_title ) || empty( $post->post_excerpt ) || empty( $post->post_content ) ) {
if ( $default_post = bbl_get_default_lang_post( $post->ID ) )
$subs_index[ $post->ID ] = $default_post->ID;
}
if ( ! $this->get_transid( $post ) && bbl_get_default_lang_code() == bbl_get_post_lang_code( $post ) )
$this->set_transid( $post );
}
if ( ! $subs_index )
return $posts;
$subs_posts = get_posts( array( 'include' => array_values( $subs_index ), 'post_status' => 'publish' ) );
// @FIXME: Check the above get_posts call results are cached somewhere… I think they are
// @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
foreach ( $posts as & $post ) {
// @TODO why does this only override the title/excerpt/content? Why not override the post object entirely?
// @FIXME: I'm assuming this get_post call is cached, which it seems to be
if( isset( $subs_index[ $post->ID ] ) ) {
$default_post = get_post( $subs_index[ $post->ID ] );
if ( empty( $post->post_title ) )
$post->post_title = $default_post->post_title;
if ( empty( $post->post_excerpt ) )
$post->post_excerpt = $default_post->post_excerpt;
if ( empty( $post->post_content ) )
$post->post_content = $default_post->post_content;
}
}
return $posts;
}
/**
* Hooks the WP body_class filter to add classes to the
* body element.
*
* @param array $classes An array of class strings, poss with some indexes containing more than one space separated class
* @param string|array $class One or more classes which have been added to the class list.
* @return array An array of class strings, poss with some indexes containing more than one space separated class
**/
public function body_class( array $classes, $class ) {
// Shadow post_type archives also get the post_type class for
// the default language
if ( is_post_type_archive() && ! bbl_is_default_lang() )
$classes[] = 'post-type-archive-' . bbl_get_post_type_in_lang( get_query_var( 'post_type' ), bbl_get_default_lang_code() );
if ( is_single() )
$classes[] = 'single-' . bbl_get_base_post_type( get_post_type() );
return $classes;
}
/**
* Hooks the WP post_type_link filter
*
* @param string $post_link The permalink
* @param object $post The WP Post object being linked to
* @return string The permalink
**/
public function post_type_link( $post_link, $post, $leavename ) {
global $wp_rewrite;
// Regular ol' post types, and other types added by other plugins, etc
if ( 'post' == $post->post_type || 'page' == $post->post_type || ! isset( $this->post_types[ $post->post_type ] ) )
return user_trailingslashit( $post_link );
// Deal with our shadow post types
if ( ! ( $base_post_type = $this->get_base_post_type( $post->post_type ) ) )
return user_trailingslashit( $post_link );
// Deal with post_types shadowing the post post_type
if ( 'post' == $base_post_type ) {
// @FIXME: Probably move this into another function
// @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?
// START copying from get_permalink function
// N.B. The $permalink var is replaced with $post_link
$rewritecode = array(
'%year%',
'%monthnum%',
'%day%',
'%hour%',
'%minute%',
'%second%',
'%postname%',
'%post_id%',
'%category%',
'%author%',
'%pagename%',
);
$post_link = get_option('permalink_structure');
// @FIXME: Should I somehow fake this, so plugin authors who hook it still get some consequence?
// $post_link = apply_filters('pre_post_link', $post_link, $post, $leavename);
if ( '' != $post_link && ! in_array( $post->post_status, array( 'draft', 'pending', 'auto-draft' ) ) ) {
$unixtime = strtotime($post->post_date);
$category = '';
if ( strpos($post_link, '%category%') !== false ) {
$cats = get_the_category($post->ID);
if ( $cats ) {
usort($cats, '_usort_terms_by_ID'); // order by ID
$category = $cats[0]->slug;
if ( $parent = $cats[0]->parent )
$category = get_category_parents($parent, false, '/', true) . $category;
}
// show default category in permalinks, without
// having to assign it explicitly
if ( empty($category) ) {
$default_category = get_category( get_option( 'default_category' ) );
$category = is_wp_error( $default_category ) ? '' : $default_category->slug;
}
}
$author = '';
if ( strpos($post_link, '%author%') !== false ) {
$authordata = get_userdata($post->post_author);
$author = $authordata->user_nicename;
}
$date = explode(" ",date('Y m d H i s', $unixtime));
$rewritereplace =
array(
$date[0],
$date[1],
$date[2],
$date[3],
$date[4],
$date[5],
$post->post_name,
$post->ID,
$category,
$author,
$post->post_name,
);
$lang = bbl_get_post_lang_code( $post );
bbl_switch_to_lang( $lang );
$post_link = home_url( str_replace( $rewritecode, $rewritereplace, $post_link ) );
bbl_restore_lang();
$post_link = user_trailingslashit($post_link, 'single');
// END copying from get_permalink function
return $post_link;
} else { // if they're not using the fancy permalink option the link won't work. Known bug. :)
return $post_link;
}
} else if ( 'page' == $base_post_type ) {
return get_page_link( $post->ID, $leavename );
}
return user_trailingslashit( $post_link );
}
/**
* Hooks the get_sample_permalink filter to provide a correct sample permalink
* in situations where the post_name has been hacked for a particular context.
*
* @filter get_sample_permalink (not yet in existence, see http://core.trac.wordpress.org/attachment/ticket/22338)
*
* @param array $permalink The array, like array( $permalink, $post_name )
* @param string $title A desired title (could be null)
* @param string $name A desired post name (could be null)
* @param int $id The Post ID
* @param object $post A (hacked) Post object
* @return array The array, like array( $permalink, $post_name )
*/
public function get_sample_permalink( $permalink, $title, $name, $id, $post ) {
$permalink[ 0 ] = $this->post_type_link( $permalink[ 0 ], $post, $leavename );
return $permalink;
}
/**
* Hooks the WP page_link filter to ensure correct virtual language directory prefix, etc.
*
* @param string $link The permalink for the page
* @param int $id The ID for the post represented by this permalink
* @return string
**/
public function page_link( $link, $post_id ) {
if ( $this->no_recursion )
return $link;
// Deal with the language front pages
if ( 'page' == get_option('show_on_front') && $page_on_front = get_option( 'page_on_front' ) ) {
$front_page_transid = $this->get_transid( $page_on_front );
$this_transid = $this->get_transid( $post_id );
if ( $front_page_transid == $this_transid ) {
bbl_switch_to_lang( bbl_get_post_lang_code( $post_id ) );
$link = home_url();
bbl_restore_lang();
return $link;
}
}
$this->no_recursion = 'page_link';
$lang = bbl_get_post_lang_code( $post_id );
bbl_switch_to_lang( $lang );
$link = get_page_link( $post_id );
bbl_restore_lang();
$this->no_recursion = false;
return $link;
}
/**
* Hooks the WP post_type_archive_link filter to return the correct
* post type archive link for the current language.
*
* @param string $link The link to the post type archive (probably wrong for this language)
* @param string $post_type The post_type we need an archive for (though we'll probably need to use a translated (shadow) post_type)
* @return string A URL for the translated (shadow) post_type archive
**/
public function post_type_archive_link( $link, $post_type ) {
if ( $this->no_recursion )
return $link;
$this->no_recursion = 'post_type_archive_link';
$lang_post_type = $this->get_post_type_in_lang( $post_type, bbl_get_current_lang_code() );
bbl_switch_to_lang( bbl_get_current_lang_code() );
$link = get_post_type_archive_link( $lang_post_type );
bbl_restore_lang();
$this->no_recursion = false;
return $link;
}
/**
* Hooks the WP clean_post_cache action to clear the Babble
* post translation and transid caches.
*
* Occasionally called directly by within this class.
*
* @param int $post_id The ID of the post to clear the caches for
* @return void
**/
function clean_post_cache( $post_id ) {
wp_cache_delete( $post_id, 'bbl_post_transids' );
// clean_post_cache gets called in some situations where
// the post is already deleted, in which case do not
// force the creation of a transid.
if ( ! $transid = $this->get_transid( $post_id, false ) ) {
return;
}
wp_cache_delete( $transid, 'bbl_post_translations' );
}
/**
* Hooks the WP post_updated action to ensure that the
* required properties are copied to the other posts in
* this translation group.
*
* @param int $post_id The ID of the post being updated
* @return void
**/
public function post_updated( $post_id ) {
if ( $this->no_recursion )
return;
$this->no_recursion = 'post_updated';
$transid = $this->get_transid( $post_id );
$this->clean_post_cache( $post_id );
$translations = $this->get_post_translations( $post_id );
foreach ( $translations as $lang_code => & $translation ) {
if ( $translation->ID == $post_id )
continue;
// Copy the various core post properties across
$this->sync_properties( $post_id, $translation->ID );
}
// Revert comment status, which often gets turned off by
// auto drafts.
$post_lang_code = bbl_get_post_lang_code( $post_id );
if ( bbl_get_default_lang_code() != $post_lang_code ) {
$source_post = bbl_get_post_in_lang( $post_id, bbl_get_default_lang_code() );
$target_post = get_post( $post_id );
$post_data = array(
'ID' => $post_id,
'comment_status' => $source_post->comment_status,
'post_modified' => $target_post->post_modified,
'post_modified_gmt' => $target_post->post_modified_gmt,
);
wp_update_post( $post_data );
}
$this->no_recursion = false;
}
/**
* Hooks the WP transition_post_status action which fires whenever
* a post status changes through use of wp_transition_post_status.
*
* @param string $new_status The new status
* @param string $old_status The old status
* @param object $post The post object
* @return void
**/
public function transition_post_status( $new_status, $old_status, $post ) {
if ( $new_status == $old_status )
return;
if ( $this->no_recursion ) {
return;
}
$this->no_recursion = 'transition_post_status';
if ( 'publish' == $new_status && $new_status != $old_status ) {
// Ensure the date of publication of a translation gets
// sync'd immediately with the original language post.
if ( bbl_get_default_lang_code() != bbl_get_post_lang_code( $post->ID ) ) {
$source_post = bbl_get_post_in_lang( $post->ID, bbl_get_default_lang_code() );
$postdata = array(
'ID' => $post->ID,
'post_date' =>$source_post->post_date,
);
wp_update_post( $postdata );
}
}
$this->no_recursion = false;
}
/**
* Hooks the WP add_menu_classes filter to fixup the side
* admin menu.
*
* @param array $menu The WP admin menu
* @return array The WP admin menu
**/
public function add_menu_classes( $menu ) {
global $submenu;
$lang = bbl_get_current_lang_code();
$default = bbl_get_default_lang_code();
// Remove "new post" links from submenu(s) for non-default languages
if ( $lang != $default ) {
foreach ( $submenu as $parent => $items ) {
foreach ( $items as $key => $item ) {
if ( 'post-new.php' == substr( $item[ 2 ], 0, 12 ) ) {
unset( $submenu[ $parent ][ $key ] );
}
}
}
}
// Remove links to shadow post types
foreach ( $menu as $key => $item ) {
$vars = array();
$url_info = parse_url( $item[ 2 ] );
if ( ! isset( $url_info[ 'query' ] ) )
continue;
parse_str( $url_info[ 'query' ], $vars );
if ( ! isset( $vars[ 'post_type' ] ) || ! isset( $this->post_types[ $vars[ 'post_type' ] ] ) )
continue;
unset( $menu[ $key ] );
}
return $menu;
}
/**
* Hooks the WP filter single_template to deal with the shadow post
* types for pages and singular templates, ensuring they use the
* right template.
*
* @param string $template Path to a template file
* @return Path to a template file
**/
public function single_template( $template ) {
if( bbl_is_default_lang() )
return $template;
// Deal with the language front pages and custom page templates
$post = get_post( get_the_ID() );
if ( 'page' == get_option('show_on_front') ) {
$front_page_transid = $this->get_transid( get_option( 'page_on_front' ) );
$this_transid = $this->get_transid( get_the_ID() );
// Check if this is a translation of the page on the front of the site
if ( $front_page_transid == $this_transid ) {
// global $wp_query, $wp;
if ( 'page' == $this->get_base_post_type( $post->post_type ) ) {
if ( $custom_page_template = get_post_meta( get_option( 'page_on_front' ), '_wp_page_template', true ) )
$templates = (array) $custom_page_template;
else
$templates = (array) 'page.php';
if ( $_template = locate_template( $templates ) ) {
return $_template;
}
}
}
}
// Check if we're dealing with a page or a translation of a page
if ( 'page' == $this->get_base_post_type( $post->post_type ) ) {
$custom_page_template = get_post_meta( get_the_ID(), '_wp_page_template', true );
if ( $custom_page_template && 'default' != $custom_page_template )
$templates = (array) $custom_page_template;
else
$templates = array( 'page.php' );
if ( $_template = locate_template( $templates ) ) {
return $_template;
}
}
$templates[] = "single-{$this->get_base_post_type($post->post_type)}.php";
$templates[] = "single.php";
$template = get_query_template( 'bbl-single', $templates );
return $template;
}
/**
* Hooks the bbl_sync_meta_key filter from this class which checks
* if a meta_key should be synced. If we return false, it won't be.
*
* @TODO correct inline docs
**/
function sync_meta_key( $sync, $meta_key ) {
$sync_not = array(
'_edit_last', // Related to edit lock, should be individual to translations
'_edit_lock', // The edit lock, should be individual to translations
'_bbl_default_text_direction', // The text direction, should be individual to translations
'_wp_trash_meta_status',
'_wp_trash_meta_time',
);
if ( in_array( $meta_key, $sync_not ) )
$sync = false;
return $sync;
}
/**
* Hooks the WP manage_posts_columns filter to add our “link” column.
*
* @param array $cols The columns for this post type lists table
* @param string $post_type The post type for this lists table
* @return array The columns
**/
public function manage_posts_columns( array $columns, $post_type ) {
// Insert our cols just before comments, or date.
if ( $post_type == bbl_get_post_type_in_lang( $post_type, bbl_get_default_lang_code() ) )
return $columns;
# @TODO is this phrase localisable? Might need changing.
$columns[ 'bbl_link' ] = __( 'Translation of', 'babble' );
return $columns;
}
/**
* Hooks the WP manage_posts_custom_column action to add our “link” content.
*
* @param string $column_name The name of this column
* @param int $post_id The ID for the post for the row which parents this column
* @return void
**/
public function manage_posts_custom_column( $column_name, $post_id ) {
if ( 'bbl_link' != $column_name )
return;
$default_post = bbl_get_post_in_lang( $post_id, bbl_get_default_lang_code() );
if ( ! $default_post ) {
echo '<em style="color: #bc0b0b">' . __( 'No link', 'babble' ) . '</em>';
return;
}
$edit_link = get_edit_post_link( $default_post->ID );
$edit_link = add_query_arg( array( 'lang' => bbl_get_default_lang_code() ), $edit_link );
bbl_switch_to_lang( bbl_get_default_lang_code() );
$view_link = get_permalink( $default_post->ID );
bbl_restore_lang();
$edit_title = esc_attr( sprintf( __( 'Edit the originating post: “%s”', 'babble' ), get_the_title( $default_post->ID ) ) );
$view_title = esc_attr( sprintf( __( 'View the originating post: “%s”', 'babble' ), get_the_title( $default_post->ID ) ) );
echo "<a href='$view_link' title='$view_title'>" . __( 'View', 'babble' ) . "</a> | <a href='$edit_link' title='$edit_title'>" . __( 'Edit', 'babble' ) . "</a>";
}
// PUBLIC METHODS
// ==============
public function bbl_translated_taxonomy( $translated, $taxonomy ) {
if ( 'post_translation' == $taxonomy )
return false;
return $translated;
}
public function admin_body_class( $class ) {
$post_type = get_current_screen() ? get_current_screen()->post_type : null;
if ( $post_type )
$class .= ' bbl-post-type-' . $post_type;
return $class;
}
/**
* Takes a set of query vars and amends them to show the content
* in the current language.
*
* @param array $query_vars A set of WordPress query vars (sometimes called query arguments)
* @param string|boolean $request If this is called on the parse_request hook, $request contains the root relative URL
* @return array $query_vars A set of WordPress query vars
**/
protected function translate_query_vars( array $query_vars, $request = false ) {
// Sequester the original query, in case we need it to get the default content later
$query_vars[ 'bbl_original_query' ] = $query_vars;
// We've done this already (avoid re-translating the vars)
if ( isset( $query_vars[ 'bbl_done_translation' ] ) && $query_vars[ 'bbl_done_translation' ] )
return $query_vars;
$query_vars[ 'bbl_done_translation' ] = true;
$lang_url_prefix = isset( $query_vars[ 'lang_url_prefix' ] ) ? $query_vars[ 'lang_url_prefix' ] : get_query_var( 'lang_url_prefix' );
$lang = isset( $query_vars[ 'lang' ] ) ? $query_vars[ 'lang' ] : get_query_var( 'lang' );
// Detect language specific homepages
if ( $request == $lang_url_prefix ) {
unset( $query_vars[ 'error' ] );
// @FIXME: Cater for front pages which don't list the posts
if ( 'page' == get_option('show_on_front') && $page_on_front = get_option('page_on_front') ) {
// @TODO: Get translated page ID
$query_vars[ 'p' ] = $this->get_post_in_lang( $page_on_front, bbl_get_current_lang_code() )->ID;
$query_vars[ 'post_type' ] = $this->get_post_type_in_lang( 'page', bbl_get_current_lang_code() );
return $query_vars;
}
// Trigger the archive listing for the relevant shadow post type
// of 'post' for this language.
if ( bbl_get_default_lang_code() != $lang && empty( $query_vars['s'] ) ) {
$post_type = isset( $query_vars[ 'post_type' ] ) ? $query_vars[ 'post_type' ] : 'post';
$query_vars[ 'post_type' ] = $this->get_post_type_in_lang( $post_type, bbl_get_current_lang_code() );
}
return $query_vars;
}
// If we're asking for the default content, it's fine
if ( bbl_get_default_lang_code() == $lang ) {
return $query_vars;
}
// Now swap the query vars so we get the content in the right language post_type
// @FIXME: Do I need to change $wp->matched query? I think $wp->matched_rule is fine?
// @FIXME: Danger of post type slugs clashing??
if ( isset( $query_vars[ 'pagename' ] ) && $query_vars[ 'pagename' ] ) {
// Substitute post_type for
$query_vars[ 'name' ] = $query_vars[ 'pagename' ];
$query_vars[ bbl_get_post_type_in_lang( 'page', $query_vars[ 'lang' ] ) ] = $query_vars[ 'pagename' ];
$query_vars[ 'post_type' ] = bbl_get_post_type_in_lang( 'page', bbl_get_current_lang_code() );
// Trigger a listing of translated posts if this is meant to
// be the blog page.
if ( 'page' == get_option( 'show_on_front' ) ) {
// Test if the current page is in the same translation group as
// the 'page_for_posts.
$current_post = get_page_by_path( $query_vars[ 'pagename' ], null, $query_vars[ 'post_type' ] );
if ( $this->get_transid( get_option( 'page_for_posts' ) ) == $this->get_transid( $current_post ) ) {
$query_vars[ 'post_type' ] = bbl_get_post_type_in_lang( 'post', bbl_get_current_lang_code() );
unset( $query_vars[ 'name' ] );
unset( $query_vars[ bbl_get_post_type_in_lang( 'page', $query_vars[ 'lang' ] ) ] );
}
}
unset( $query_vars[ 'page' ] );
unset( $query_vars[ 'pagename' ] );
} elseif ( isset( $query_vars[ 'year' ] ) && $query_vars[ 'year' ] ) {
// @FIXME: This is not a reliable way to detect queries for the 'post' post_type.
$query_vars[ 'post_type' ] = bbl_get_post_type_in_lang( 'post', bbl_get_current_lang_code() );
} elseif ( isset( $query_vars[ 'post_type' ] ) ) {
if ( is_array( $query_vars[ 'post_type' ] ) ) {
$new_post_types = array();
foreach ( $query_vars[ 'post_type' ] as $post_type ) {
$new_post_types[] = bbl_get_post_type_in_lang( $post_type, bbl_get_current_lang_code() );
}
$query_vars[ 'post_type' ] = $new_post_types;
} else {
$query_vars[ 'post_type' ] = bbl_get_post_type_in_lang( $query_vars[ 'post_type' ], bbl_get_current_lang_code() );
}
} else {
$query_vars[ 'post_type' ] = bbl_get_post_type_in_lang( 'post', bbl_get_current_lang_code() );
}
return $query_vars;
}
/**
* Discover whether a post is set as the front page
* for the site in a particular language.
*
* @param int $post_id The ID of a post
* @return boolean True if this post is used as the front page of the site for a language
**/
public function is_language_front_page( $post_id = null, $lang_code = null ) {
if ( 'page' != get_option('show_on_front') )
return false;
$post = get_post( $post_id );
// If we have a lang code, and it doesn't match the requested post lang then this
// is not the right front page
if ( ! is_null( $lang_code ) && $lang_code != $this->get_post_lang_code( $post->ID ) )
return false;
$front_page_transid = $this->get_transid( get_option( 'page_on_front' ) );
$this_transid = $this->get_transid( get_the_ID() );
if ( $front_page_transid != $this_transid )
return false;
return true;
}
/**
* Return the language code for the language a given post is written for/in.
*
* @param int|object $post Either a WP Post object, or a post ID
* @return string|object Either a language code, or a WP_Error object
* @access public
**/
public function get_post_lang_code( $post ) {
$post = get_post( $post );
if ( ! $post )
return new WP_Error( 'bbl_invalid_post', __( 'Invalid Post passed to get_post_lang_code', 'babble' ) );
if ( isset( $this->lang_map[ $post->post_type ] ) )
return $this->lang_map[ $post->post_type ];
return bbl_get_default_lang_code();
}
/**
* Return the admin URL to create a new translation for a post in a
* particular language.
*
* @param int|object $default_post The post in the default language to create a new translation for, either WP Post object or post ID
* @param string $lang The language code
* @return string The admin URL to create the new translation
**/
public function get_new_post_translation_url( $default_post, $lang_code ) {
$default_post = get_post( $default_post );
$url = admin_url( 'post-new.php' );
$args = array(
'bbl_origin_post' => $default_post->ID,
'lang' => $lang_code,
'post_type' => 'bbl_job',
);
$url = add_query_arg( $args, $url );
return $url;
}
/**
* Returns the post ID for the post in the default language from which
* this post was translated.
*
* @param int|WP_Post $post Either a WP Post object, or a post ID
* @return int The ID of the default language equivalent post
**/
public function get_default_lang_post( $post ) {
$post = get_post( $post );
$translations = bbl_get_post_translations( $post->ID );
if ( isset( $translations[ bbl_get_default_lang_code() ] ) )
return $translations[ bbl_get_default_lang_code() ];
return false;
}
/**
* Get the posts which are the translations for the provided
* post ID. N.B. The returned array of post objects (and false
* values) will include the post for the post ID passed.
*
* @param int|WP_Post $post Either a WP Post object, or a post ID
* @return array Either an array keyed by the site languages, each key containing false (if no translation) or a WP Post object
**/
public function get_post_translations( $post ) {
$post = get_post( $post );
// @FIXME: Is it worth caching here, or can we just rely on the caching in get_objects_in_term and get_posts?
$transid = $this->get_transid( $post );
if ( $translations = wp_cache_get( $transid, 'bbl_post_translations' ) ) {
return $translations;
}
# @TODO A transid should never be a wp_error. Check and fix.
if ( is_wp_error( $transid ) )
error_log( "Error getting transid: " . print_r( $transid, true ) );
$post_ids = get_objects_in_term( $transid, 'post_translation' );
// Work out all the translated equivalent post types
$post_types = array();
$langs = bbl_get_active_langs();
foreach ( $langs as $lang )
$post_types[] = bbl_get_post_type_in_lang( $post->post_type, $lang->code );
// Get all the translations in one cached DB query
$args = array(
// We want a clean listing, without any particular language
'bbl_translate' => false,
'include' => $post_ids,
'post_type' => $post_types,
'post_status' => array( 'publish', 'pending', 'draft', 'future' ),
);
$posts = get_posts( $args );
$translations = array();
foreach ( $posts as $post )
$translations[ $this->get_post_lang_code( $post ) ] = $post;
wp_cache_add( $transid, $translations, 'bbl_post_translations' );
return $translations;
}
/**
* Return the base post type (in the default language) for a
* provided post type.
*
* @param string $post_type The name of a post type
* @return string The name of the base post type
**/
public function get_base_post_type( $post_type ) {
if ( ! isset( $this->post_types[ $post_type ] ) )
return $post_type;
return $this->post_types[ $post_type ];
}
/**
* Return all the base post types (in the default language).
*
* @return array An array of post_type objects
**/
public function get_base_post_types() {
$post_types = array();
foreach ( $this->post_types as $post_type )
$post_types[ $post_type ] = get_post_type_object( $post_type );
return $post_types;
}
/**
* Returns the equivalent post_type in the specified language.
*
* @param string $post_type A post_type to return in a given language
* @param string $lang_code The language code for the required language
* @return string The equivalent post_type name, or given post_type if it doesn't exist
**/
public function get_post_type_in_lang( $post_type, $lang_code ) {
$base_post_type = $this->get_base_post_type( $post_type );
if ( bbl_get_default_lang_code() == $lang_code ) {
return $base_post_type;
}
// Some post types are untranslated…
if ( ! bbl_is_translated_post_type( $post_type ) ) {
return $post_type;
}
// Return the original post type if we couldn't find it in our array
if ( ! isset( $this->lang_map2[ $lang_code ][ $base_post_type ] ) ) {
return $post_type;
}
return $this->lang_map2[ $lang_code ][ $base_post_type ];
}
/**
* Returns an array of all the shadow post types associated with
* this post type.
*
* @param string $base_post_type The post type to look up shadow post types for
* @return array The names of all the related shadow post types
**/
public function get_shadow_post_types( $base_post_type ) {
$post_types = array();
$langs = bbl_get_active_langs();
foreach ( $langs as $lang ) {
if ( isset( $this->lang_map2[ $lang->code ][ $base_post_type ] ) )
$post_types[] = $this->lang_map2[ $lang->code ][ $base_post_type ];
}
return $post_types;
}
/**
* Returns the post in a particular language, or the fallback content
* if there's no post available.
*
* @param int|WP_Post $post Either a WP Post object, or a post ID
* @param string $lang_code The language code for the required language
* @param boolean $fallback If true: if a post is not available, fallback to the default language content (defaults to true)
* @return object|boolean The WP Post object, or if $fallback was false and no post then returns false
**/
public function get_post_in_lang( $post, $lang_code, $fallback = true ) {
$translations = $this->get_post_translations( $post );
if ( isset( $translations[ $lang_code ] ) ) {
return $translations[ $lang_code ];
}
if ( ! $fallback ) {
return false;
}
return $translations[ bbl_get_default_lang_code() ];
}
/**
* Returns a slug translated into a particular language.
*
* @param string $slug The slug to translate
* @param string $lang A Babble language object
* @param array $post_type_args The args for the post type associated with this post type
* @return void
**/
public function get_slug_in_lang( $slug, $lang ) {
$_slug = mb_strtolower( apply_filters( 'bbl_translate_post_type_slug', $slug, $lang->code ) );
// @FIXME: For some languages the translation might be the same as the original
if ( $_slug && $_slug != $slug )
return $_slug;
// FIXME: Do we need to check that the slug is unique at this point?
return mb_strtolower( "{$_slug}_{$lang->code}" );
}
// PRIVATE/PROTECTED METHODS
// =========================
/**
* Copy various properties from one post to another.
*
* @param int $source_id The source post, to copy FROM
* @param int $target_id The target post, to copy TO
* @return void
**/
public function sync_properties( $source_id, $target_id ) {
if ( ! ( $source_post = get_post( $source_id ) ) )
return;
$source_lang_code = bbl_get_post_lang_code( $source_id );
$target_lang_code = bbl_get_post_lang_code( $target_id );
$target_parent_post = false;
if ( $source_post->post_parent ) {
$source_parent_post = $this->get_post_in_lang( $source_post->post_parent, $source_lang_code );
$target_parent_post = $this->get_post_in_lang( $source_parent_post, $target_lang_code );
}
$target_post = get_post( $target_id );
$postdata = array(
'ID' => $target_id,
'post_author' => $source_post->post_author,
'post_modified' => $target_post->post_modified,
'post_modified_gmt' => $target_post->post_modified_gmt,
'ping_status' => $source_post->ping_status,
'post_password' => $source_post->post_password,
'menu_order' => $source_post->menu_order,
'post_mime_type' => $source_post->post_mime_type,
);
if ( $target_parent_post )
$postdata[ 'post_parent' ] = $target_parent_post->ID;
else
$postdata[ 'post_parent' ] = 0;
if ( bbl_get_default_lang_code() == $source_lang_code ) {
$postdata[ 'post_date' ] = $source_post->post_date;
$postdata[ 'post_date_gmt' ] = $source_post->post_date_gmt;
}
// Comment status only synced when going from the default lang code
if ( bbl_get_default_lang_code() == $source_lang_code )
$postdata[ 'comment_status' ] = $source_post->comment_status;
$postdata = apply_filters( 'bbl_pre_sync_properties', $postdata, $source_id );
wp_update_post( $postdata );
}
/**
* Resync all (synced) post meta data from the post in
* the default language to this post.
*
* @param $int The post ID to sync TO
* @return void
**/
function sync_post_meta( $post_id ) {
if ( $this->no_meta_recursion )
return;
$this->no_meta_recursion = 'updated_post_meta';
// First delete all the synced meta from this post
$current_metas = (array) get_post_meta( $post_id );
foreach ( $current_metas as $current_meta_key => & $current_meta_values ) {
// Some metadata shouldn't be synced, this filter allows a dev to return
// false if the particular meta_key is one which shouldn't be synced.
// If you find a core meta_key which is currently synced and should NOT be,
// please submit a patch to the sync_meta_key method on this class. Thanks.
if ( apply_filters( 'bbl_sync_meta_key', true, $current_meta_key ) )
delete_post_meta( $post_id, $current_meta_key );
}
// Now add meta in again from the origin post
$origin_post = bbl_get_post_in_lang( $post_id, bbl_get_default_lang_code() );
$metas = get_post_meta( $origin_post->ID );
if ( ! $metas )
return;
foreach ( $metas as $meta_key => & $meta_value ) {
// Some metadata shouldn't be synced
if ( ! apply_filters( 'bbl_sync_meta_key', true, $meta_key ) )
continue;
// The meta could be an array stored in a single postmeta row or an
// array of values from multiple rows; work out which we have.
$val_multi = get_post_meta( $origin_post->ID, $meta_key );
foreach ( $val_multi as & $val_single ) {
add_post_meta( $post_id, $meta_key, $val_single );
}
}
$this->no_meta_recursion = false;
}
/**
* Get the transID for this post, this is an identifier linking all the translations
* for a single piece of content together.
*
* Marked private as we may change how translations are linked. Please use API, or
* raise an issue.
*
* @param int|object $post The WP Post object, or the ID of a post
* @return string The transid
* @access private
**/
function get_transid( $post, $create = true ) {
$post = get_post( $post );
if ( ! $post->ID )
return false;
if ( $transid = wp_cache_get( $post->ID, 'bbl_post_transids' ) ) {
return $transid;
}
$transids = (array) wp_get_object_terms( $post->ID, 'post_translation', array( 'fields' => 'ids' ) );
// "There can be only one" (so we'll just drop the others)
$transid = false;
if ( isset( $transids[ 0 ] ) ) {
$transid = $transids[ 0 ];
} else {
if ( $create ) {
$transid = $this->set_transid( $post );
}
}
if ( ! $transid ) {
return false;
}
wp_cache_add( $post->ID, $transid, 'bbl_post_transids' );
return $transid;
}
/**
* Create and assign a new TransID to a post.
*
* @param int|object $post Either a Post ID or a WP Post object
* @param string $transid (optional) A transid to associate with the post
* @return string The transid which has just been set
* @access private
**/
function set_transid( $post, $transid = false ) {
$post = get_post( $post );
if ( ! isset( $post->ID ) )
return false;
// @FIXME: Abstract the code for generating and associating a new TransID
if ( ! $transid ) {
$transid_name = 'post_transid_' . uniqid();
$result = wp_insert_term( $transid_name, 'post_translation', array() );
if ( is_wp_error( $result ) )
error_log( "Problem creating a new Post TransID: " . print_r( $result, true ) );
else
$transid = $result[ 'term_id' ];
// Delete anything in there currently
wp_cache_delete( $transid, 'bbl_post_translations' );
}
$result = wp_set_object_terms( $post->ID, $transid, 'post_translation' );
if ( is_wp_error( $result ) )
error_log( "Problem associating TransID with new posts: " . print_r( $result, true ) );
$this->clean_post_cache( $post->ID );
return $transid;
}
/**
* Return a list of features supported by a post_type.
*
* Hello there, code investigator. I imagine you're wondering
* why I'm accessing a global prefixed by an underscore? I realise
* these are nominally private variables, prone to change, but
* I need to access a list of all features supported by a post
* type, in order to shadow it for the various translations,
* and there's no core function to allow me to do this.
*
* @TODO: Raise a Trac ticket for adding this functionality to the post type API
*
* @param string $post_type The name of the post type for which to get the features supported
* @return array An array of features supported by this post type
**/
protected function get_features_supported_by_post_type( $post_type ) {
global $_wp_post_type_features;
return (array) $_wp_post_type_features[$post_type];
}
/**
* Are we on the media upload gallery tab?
*
* @param string $tab A specific tab to detect
* @return boolean True if we are on media upload generally, and the specific tab if specified
**/
protected function is_media_upload_tab( $tab = null ) {
if ( ! is_admin() )
return false;
if ( 'media-upload.php' != basename( $_SERVER[ 'SCRIPT_NAME' ] ) ) {
return false;
}
if ( is_null( $tab ) ) {
return true;
}
if ( isset( $_GET[ 'tab' ] ) || $tab == $_GET[ 'tab' ] ) {
return true;
}
return false;
}
/**
* Are we viewing the (3.5+) media manager?
*
* @return boolean True if we are viewing the media manager
**/
protected function is_media_manager() {
if ( ! is_admin() )
return false;
if ( !isset( $_POST['action'] ) ) {
return false;
}
if ( 'query-attachments' == $_POST['action'] ) {
return true;
}
return false;
}
/**
* Remove over-synced post metas.
*
* @return void
**/
protected function prune_post_meta() {
global $wpdb;
$meta_keys = array(
'_thumbnail_id',
'_wp_old_slug' ,
'_wp_page_template',
'_wp_trash_meta_status',
'_wp_trash_meta_time',
);
foreach ( $meta_keys as $meta_key ) {
$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 );
$metas = $wpdb->get_results( $prepared_sql );
foreach ( $metas as $meta ) {
if ( $meta->count < 2 ) {
continue;
}
$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 );
$wpdb->query( $prepared_sql );
}
}
}
/**
* Checks the DB structure is up to date, rewrite rules,
* theme image size options are set, etc.
*
* @return void
**/
protected function maybe_upgrade() {
global $wpdb;
$option_name = 'bbl_post_public_version';
$version = get_option( $option_name, 0 );
if ( $version == $this->version )
return;
if ( $start_time = get_option( "{$option_name}_running", false ) ) {
$time_diff = time() - $start_time;
// Check the lock is less than 30 mins old, and if it is, bail
if ( $time_diff < ( 60 * 30 ) ) {
error_log( "Babble Post Public: Existing update routine has been running for less than 30 minutes" );
return;
}
error_log( "Babble Post Public: Update routine is running, but older than 30 minutes; going ahead regardless" );
} else {
add_option( "{$option_name}_running", time(), null, 'no' );
}
if ( $version < 9 ) {
error_log( "Babble Post Public: Start pruning metadata" );
$this->prune_post_meta();
error_log( "Babble Post Public: Remove excess post meta" );
}
// N.B. Remember to increment $this->version above when you add a new IF
update_option( $option_name, $this->version );
delete_option( "{$option_name}_running", true, null, 'no' );
error_log( "Babble Post Public: Done upgrade, now at version " . $this->version );
}
/**
* Checks for duplicated metadata in some key meta_keys.
*
* @return boolean
* @author Simon Wheatley
*/
function have_duplicate_metadata() {
global $wpdb;
$sql = "
SELECT COUNT(*) AS count, post_id, meta_key, meta_value
FROM $wpdb->postmeta
WHERE meta_key IN (
'_extmedia-youtube', '_extmedia-duration', '_thumbnail_id', '_wp_trash_meta_time', '_wp_page_template', '_wp_trash_meta_status'
)
GROUP BY post_id, meta_key, meta_value
HAVING count > 1
ORDER BY count, post_id, meta_key
";
return (bool) count( $wpdb->get_results( $sql ) );
}
}
global $bbl_post_public;
$bbl_post_public = new Babble_Post_Public();
================================================
FILE: class-switcher-content.php
================================================
<?php
/**
* Class for providing the switch/create links for the current
* content items. Works in the admin or public areas of the site.
*
* Used by the admin bar class, for example.
*
* @package Babble
* @since 0.2
*/
class Babble_Switcher_Menu {
/**
* The translations for the current content item.
*
* @var array
**/
protected $translations;
/**
* A multi-dimensional array of the links for the current
* translation structure.
*
* @var array
**/
protected $links;
/**
* The WP Screen object
*
* @var object
**/
protected $screen;
// PUBLIC METHODS
// ==============
/**
* Returns an array of links to the other objects
* in this translation group. Each element in the array
* looks like:
* array (
*
* )
*
* @param string $id_prefix A prefix to the ID for each item
* @return array An array of link info for the objects in this current translation group.
**/
public function get_switcher_links( $id_prefix ) {
$this->populate_links();
return $this->links;
}
// PRIVATE/PROTECTED METHODS
// =========================
/**
* undocumented function
*
* @return void
**/
protected function populate_links() {
if ( is_array( $this->links ) && ! empty( $this->links ) )
return; // Already done
$this->links = array();
// @FIXME: Not sure this is the best way to specify languages
$alt_langs = bbl_get_active_langs();
$this->screen = is_admin() ? get_current_screen() : false;
// Create a handy flag for whether we're editing a post or listing posts
$editing_post = false;
$listing_posts = false;
if ( is_admin() ) {
$editing_post = ( is_admin() && 'post' == $this->screen->base && isset( $_GET[ 'post' ] ) );
$listing_posts = ( is_admin() && 'edit' == $this->screen->base && ! isset( $_GET[ 'post' ] ) );
}
// Create a handy flag for whether we're editing a term or listing terms
$editing_term = false;
$listing_terms = false;
if ( is_admin() ) {
$editing_term = ( is_admin() && 'edit-tags' == $this->screen->base && isset( $_GET[ 'tag_ID' ] ) );
$listing_terms = ( is_admin() && 'edit-tags' == $this->screen->base && ! isset( $_GET[ 'tag_ID' ] ) );
}
if ( is_singular() || is_single() || $editing_post ) {
$this->translations = bbl_get_post_translations( get_the_ID() );
$this->jobs = bbl_get_incomplete_post_jobs( get_the_ID() );
} else if ( 'page' == get_option( 'show_on_front' ) && is_home() ) {
$this->translations = bbl_get_post_translations( get_option( 'page_for_posts' ) );
$this->jobs = bbl_get_incomplete_post_jobs( get_option( 'page_for_posts' ) );
} else if ( ( !is_admin() and ( is_tax() || is_category() ) ) || $editing_term ) {
if ( isset( $_REQUEST[ 'tag_ID' ] ) )
$term = get_term( (int) @ $_REQUEST[ 'tag_ID' ], $this->screen->taxonomy );
else
$term = get_queried_object();
$this->translations = bbl_get_term_translations( $term->term_id, $term->taxonomy );
$this->jobs = bbl_get_term_jobs( $term->term_id, $term->taxonomy );
}
foreach ( $alt_langs as $i => & $alt_lang ) {
// @TODO: Convert to a switch statement, convert all the vars to a single property on the class
if ( is_admin() ) {
if ( $editing_post ) { // Admin: Editing post link
$this->add_admin_post_link( $alt_lang );
} else if ( $editing_term ) { // Admin: Editing term link
$this->add_admin_term_link( $alt_lang );
} else if ( $listing_posts ) { // Admin: Listing posts link
$this->add_admin_list_posts_link( $alt_lang );
} else if ( $listing_terms ) { // Admin: Listing terms link
$this->add_admin_list_terms_link( $alt_lang );
} else { // Admin: Generic link link
$this->add_admin_generic_link( $alt_lang );
}
continue;
}
if (
is_singular() || is_single() ||
( 'page' == get_option( 'show_on_front' ) && is_home() )
) { // Single posts, pages, blog homepage
$this->add_post_link( $alt_lang );
continue;
}
// Don't add a switcher link if the language is not public and
// the user cannot edit any posts (as a rough guide to whether
// they are more than just a subscriber).
// @TODO this cap check should move into each add_*_link() method:
if ( ! bbl_is_public_lang( $alt_lang->code ) && ! current_user_can( 'edit_posts' ) )
continue;
if ( is_front_page() ) { // Language homepage
// is_front_page works for language homepages, phew
$this->add_front_page_link( $alt_lang );
} else if ( is_post_type_archive() ) { // Post type archives
$this->add_post_type_archive_link( $alt_lang );
} else if ( is_tax() || is_category() ) { // Category or taxonomy archive
$this->add_taxonomy_archive_link( $alt_lang );
} else { // 404's, amongst other things
$this->add_arbitrary_link( $alt_lang );
}
}
// Make up the class attribute on all links
foreach ( $this->links as $lang_code => & $link ){
$link[ 'class' ] = implode( ' ', $link[ 'classes' ] );
$link[ 'active' ] = $lang_code == bbl_get_current_lang_code();
}
}
/**
* Add an admin link to the same page, but with the language switch GET
* parameter set.
*
* @param object $lang A Babble language object for this link
* @return void
**/
protected function add_admin_generic_link( $lang ) {
$classes = array();
$href = add_query_arg( array( 'lang' => $lang->code ) );
$href = apply_filters( 'bbl_switch_admin_generic_link', $href, $lang );
$title = sprintf( __( 'Switch to %s', 'babble' ), $lang->display_name );
$classes[] = "bbl-lang-$lang->code bbl-lang-$lang->url_prefix";
$classes[] = 'bbl-admin';
$classes[] = 'bbl-admin-generic';
$classes[] = 'bbl-lang';
if ( $lang->code == bbl_get_current_lang_code() )
$classes[] = 'bbl-active';
// Preventing errors on initial plugin load - before settings saved the first time
if ( empty( $lang->display_name ) ) {
$lang->display_name = '';
}
$this->links[ $lang->code ] = array(
'classes' => $classes,
'href' => $href,
'id' => $lang->url_prefix,
'meta' => array( 'class' => strtolower( join( ' ', array_unique( $classes ) ) ) ),
'title' => $title,
'lang' => $lang,
);
}
/**
* Add an admin term link to the parent link provided (by reference).
*
* @param object $lang A Babble language object for this link
* @return void
**/
protected function add_admin_term_link( $lang ) {
$classes = array();
if ( isset( $this->translations[ $lang->code ]->term_id ) ) { // Translation exists
$args = array(
'lang' => $lang->code,
'taxonomy' => $this->translations[ $lang->code ]->taxonomy,
'tag_ID' => $this->translations[ $lang->code ]->term_id
);
$href = add_query_arg( $args );
$title = sprintf( __( 'Switch to %s', 'babble' ), $lang->display_name );
$classes[] = 'bbl-existing-edit';
$classes[] = 'bbl-existing-edit-term';
} else if ( isset( $this->jobs[ $lang->code ]->ID ) ) { // Translation job exists
$href = get_edit_post_link( $this->jobs[ $lang->code ]->ID, 'url' );
$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 );
$classes[] = 'bbl-job-edit';
$classes[] = 'bbl-job-edit-term';
} else { // Translation does not exist
$default_term = (int) $_GET[ 'tag_ID' ];
$href = bbl_get_new_term_translation_url( $default_term, $lang->code, $this->screen->taxonomy );
$title = sprintf( __( 'Create for %s', 'babble' ), $lang->display_name );
$classes[] = 'bbl-add';
$classes[] = 'bbl-add-term';
}
$href = apply_filters( 'bbl_switch_admin_term_link', $href, $lang, $this->translations );
$classes[] = "bbl-lang-$lang->code bbl-lang-$lang->url_prefix";
$classes[] = 'bbl-admin';
$classes[] = 'bbl-admin-taxonomy';
$classes[] = 'bbl-admin-edit-term';
$classes[] = 'bbl-lang';
if ( $lang->code == bbl_get_current_lang_code() )
$classes[] = 'bbl-active';
$this->links[ $lang->code ] = array(
'classes' => $classes,
'href' => $href,
'id' => $lang->url_prefix,
'meta' => array( 'class' => strtolower( join( ' ', array_unique( $classes ) ) ) ),
'title' => $title,
'lang' => $lang,
);
}
/**
* Add an admin list terms screen link to the parent link provided (by reference).
*
* @param object $lang A Babble language object for this link
* @return void
**/
protected function add_admin_list_terms_link( $lang ) {
$classes = array();
$args = array(
'lang' => $lang->code,
'taxonomy' => bbl_get_taxonomy_in_lang( $this->screen->taxonomy, $lang->code ),
);
$href = add_query_arg( $args );
$href = apply_filters( 'bbl_switch_admin_list_terms_link', $href, $lang );
$title = sprintf( __( 'Switch to %s', 'babble' ), $lang->display_name );
$classes[] = "bbl-lang-$lang->code bbl-lang-$lang->url_prefix";
$classes[] = 'bbl-admin';
$classes[] = 'bbl-admin-taxonomy';
$classes[] = 'bbl-admin-list-terms';
$classes[] = 'bbl-lang';
if ( $lang->code == bbl_get_current_lang_code() )
$classes[] = 'bbl-active';
$this->links[ $lang->code ] = array(
'classes' => $classes,
'href' => $href,
'id' => $lang->url_prefix,
'meta' => array( 'class' => strtolower( join( ' ', array_unique( $classes ) ) ) ),
'title' => $title,
'lang' => $lang,
);
}
/**
* Add an admin post link to the parent link provided (by reference)
*
* @param object $lang A Babble language object for this link
* @return void
**/
protected function add_admin_post_link( $lang ) {
$classes = array();
if ( isset( $this->translations[ $lang->code ]->ID ) ) { // Translation exists
$href = add_query_arg( array( 'lang' => $lang->code, 'post' => $this->translations[ $lang->code ]->ID ) );
$href = remove_query_arg( 'message', $href );
$title = sprintf( __( 'Switch to %s', 'babble' ), $lang->display_name );
$classes[] = 'bbl-existing-edit';
$classes[] = 'bbl-existing-edit-post';
} else if ( isset( $this->jobs[ $lang->code ]->ID ) ) { // Translation job exists
$href = add_query_arg( array( 'lang' => $lang->code, 'post' => $this->jobs[ $lang->code ]->ID ) );
$href = remove_query_arg( 'message', $href );
$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 );
$classes[] = 'bbl-job-edit';
$classes[] = 'bbl-job-edit-post';
} else { // Translation does not exist
if ( isset( $this->translations[ bbl_get_default_lang_code() ] ) ) {
$default_post = $this->translations[ bbl_get_default_lang_code() ];
$href = bbl_get_new_post_translation_url( $default_post, $lang->code );
$title = sprintf( __( 'Create for %s', 'babble' ), $lang->display_name );
$classes[] = 'bbl-add';
$classes[] = 'bbl-add-post';
} else {
return; // Don't create the switcher menu items yet
}
}
$href = apply_filters( 'bbl_switch_admin_post_link', $href, $lang, $this->translations );
$classes[] = "bbl-lang-$lang->code bbl-lang-$lang->url_prefix";
$classes[] = 'bbl-admin';
$classes[] = 'bbl-admin-edi
gitextract_5i66y00t/ ├── CONTRIBUTING.md ├── api.php ├── babble.php ├── class-admin-bar.php ├── class-babble-log.php ├── class-comment.php ├── class-jobs.php ├── class-languages.php ├── class-locale.php ├── class-meta.php ├── class-plugin.php ├── class-post-public.php ├── class-switcher-content.php ├── class-switcher-interface.php ├── class-taxonomy.php ├── class-translator.php ├── class-updates.php ├── composer.json ├── css/ │ ├── jobs-admin.css │ └── languages-options.css ├── deprecated.php ├── external-update-api/ │ ├── external-update-api/ │ │ ├── euapi.php │ │ ├── handler-files.php │ │ ├── handler-github.php │ │ ├── handler.php │ │ ├── info.php │ │ ├── item-plugin.php │ │ ├── item-theme.php │ │ ├── item.php │ │ └── update.php │ ├── external-update-api.php │ └── readme.md ├── js/ │ └── post-public-admin.js ├── languages/ │ ├── fa_IR.mo │ ├── fa_IR.php │ ├── fr_FR.mo │ ├── pt_BR.mo │ ├── pt_BR.po │ ├── tr.mo │ └── tr.po ├── miscellaneous.php ├── readme.md ├── readme.txt ├── templates-admin/ │ ├── options-available-languages.php │ ├── switcher-interface.php │ ├── translation-editor-meta.php │ ├── translation-editor-post-excerpt.php │ ├── translation-editor-terms.php │ ├── translation-editor.php │ └── translation-groups.php ├── translation-fields.php ├── translation-group-tool-sorter.php ├── translation-group-tool.php └── widget.php
SYMBOL INDEX (393 symbols across 31 files)
FILE: api.php
function bbl_get_current_content_lang_code (line 37) | function bbl_get_current_content_lang_code() {
function bbl_get_current_interface_lang_code (line 50) | function bbl_get_current_interface_lang_code() {
function bbl_get_current_lang_code (line 61) | function bbl_get_current_lang_code() {
function bbl_is_public_lang (line 73) | function bbl_is_public_lang( $lang_code ) {
function bbl_switch_to_lang (line 87) | function bbl_switch_to_lang( $lang ) {
function bbl_restore_lang (line 100) | function bbl_restore_lang() {
function bbl_get_term_translations (line 114) | function bbl_get_term_translations( $term, $taxonomy ) {
function bbl_get_term_jobs (line 127) | function bbl_get_term_jobs( $term, $taxonomy ) {
function bbl_get_new_term_translation_url (line 142) | function bbl_get_new_term_translation_url( $default_term, $lang, $taxono...
function bbl_get_taxonomy_lang_code (line 153) | function bbl_get_taxonomy_lang_code( $taxonomy ) {
function bbl_get_base_taxonomy (line 165) | function bbl_get_base_taxonomy( $taxonomy ) {
function bbl_get_taxonomy_in_lang (line 177) | function bbl_get_taxonomy_in_lang( $taxonomy, $lang_code = null ) {
function bbl_is_translated_taxonomy (line 188) | function bbl_is_translated_taxonomy( $taxonomy ) {
function bbl_is_translated_post_type (line 198) | function bbl_is_translated_post_type( $post_type ) {
function bbl_get_taxonomy_slug_in_lang (line 209) | function bbl_get_taxonomy_slug_in_lang( $slug, $lang_code = null ) {
function bbl_get_post_translations (line 223) | function bbl_get_post_translations( $post ) {
function bbl_get_incomplete_post_jobs (line 236) | function bbl_get_incomplete_post_jobs( $post ) {
function bbl_get_default_lang_post (line 249) | function bbl_get_default_lang_post( $post ) {
function bbl_get_post_lang_code (line 261) | function bbl_get_post_lang_code( $post ) {
function bbl_get_new_post_translation_url (line 275) | function bbl_get_new_post_translation_url( $default_post, $lang ) {
function bbl_get_post_type_in_lang (line 288) | function bbl_get_post_type_in_lang( $original_post_type, $lang_code = nu...
function bbl_is_page (line 308) | function bbl_is_page( $page = '' ) {
function bbl_get_post_in_lang (line 331) | function bbl_get_post_in_lang( $post, $lang_code, $fallback = true ) {
function bbl_get_term_in_lang (line 345) | function bbl_get_term_in_lang( $term, $taxonomy, $lang_code, $fallback =...
function bbl_get_post_type_slug_in_lang (line 357) | function bbl_get_post_type_slug_in_lang( $slug, $lang_code = null ) {
function bbl_the_title_in_lang (line 371) | function bbl_the_title_in_lang( $post = null, $lang_code = null, $fallba...
function bbl_get_the_title_in_lang (line 383) | function bbl_get_the_title_in_lang( $post = null, $lang_code = null, $fa...
function bbl_the_permalink_in_lang (line 404) | function bbl_the_permalink_in_lang( $post = null, $lang_code = null, $fa...
function bbl_get_the_permalink_in_lang (line 416) | function bbl_get_the_permalink_in_lang( $post = null, $lang_code = null,...
function bbl_get_post_type_archive_link_in_lang (line 436) | function bbl_get_post_type_archive_link_in_lang( $post_type, $lang_code ...
function bbl_get_base_post_type (line 453) | function bbl_get_base_post_type( $post_type ) {
function bbl_get_base_post_types (line 463) | function bbl_get_base_post_types() {
function bbl_get_shadow_post_types (line 475) | function bbl_get_shadow_post_types( $base_post_type ) {
function bbl_get_active_langs (line 495) | function bbl_get_active_langs() {
function bbl_get_lang (line 506) | function bbl_get_lang( $lang_code ) {
function bbl_get_current_lang (line 519) | function bbl_get_current_lang() {
function bbl_get_default_lang_code (line 529) | function bbl_get_default_lang_code() {
function bbl_get_default_lang (line 539) | function bbl_get_default_lang() {
function bbl_is_default_lang (line 557) | function bbl_is_default_lang( $lang_code = null ) {
function bbl_get_default_lang_url_prefix (line 570) | function bbl_get_default_lang_url_prefix() {
function bbl_get_lang_from_prefix (line 582) | function bbl_get_lang_from_prefix( $url_prefix ) {
function bbl_get_prefix_from_lang_code (line 593) | function bbl_get_prefix_from_lang_code( $lang_code ) {
function bbl_get_switcher_links (line 604) | function bbl_get_switcher_links( $id_prefix = '' ) {
function bbl_start_logging (line 614) | function bbl_start_logging() {
function bbl_stop_logging (line 624) | function bbl_stop_logging() {
function bbl_log (line 635) | function bbl_log( $msg ) {
function bbl_is_logging (line 648) | function bbl_is_logging() {
FILE: class-admin-bar.php
class Babble_Admin_bar (line 9) | class Babble_Admin_bar extends Babble_Plugin {
method __construct (line 11) | function __construct() {
method admin_bar_menu (line 22) | public function admin_bar_menu( $wp_admin_bar ) {
FILE: class-babble-log.php
class Babble_Log (line 9) | class Babble_Log {
method __construct (line 31) | public function __construct() {
method log (line 40) | public function log( $msg ) {
FILE: class-comment.php
class Babble_Comment (line 9) | class Babble_Comment extends Babble_Plugin {
method __construct (line 11) | public function __construct() {
method comments_template_args (line 28) | public function comments_template_args( $args ) {
method preprocess_comment (line 49) | public function preprocess_comment( $comment_data ) {
method get_comments_number (line 66) | public function get_comments_number( $count, $post_id ) {
FILE: class-jobs.php
class Babble_Jobs (line 9) | class Babble_Jobs extends Babble_Plugin {
method __construct (line 26) | public function __construct() {
method add_meta_boxes_bbl_job (line 60) | public function add_meta_boxes_bbl_job( WP_Post $post ) {
method wp_insert_post_empty_content (line 69) | public function wp_insert_post_empty_content( $maybe_empty, $postarr ) {
method bbl_translated_post_type (line 76) | public function bbl_translated_post_type( $translated, $post_type ) {
method bbl_translated_taxonomy (line 82) | public function bbl_translated_taxonomy( $translated, $taxonomy ) {
method post_updated_messages (line 107) | public function post_updated_messages( array $messages ) {
method admin_init (line 125) | public function admin_init() {
method load_post_edit (line 141) | public function load_post_edit() {
method admin_title (line 182) | public function admin_title( $admin_title, $title ) {
method query_vars (line 208) | public function query_vars( array $vars ) {
method user_has_cap (line 231) | public function user_has_cap( array $user_caps, array $required_caps, ...
method pre_get_posts (line 308) | public function pre_get_posts( WP_Query & $query ) {
method get_edit_post_link (line 334) | public function get_edit_post_link( $url, $post_ID, $context ) {
method edit_form_after_title (line 358) | public function edit_form_after_title() {
method admin_menu (line 485) | public function admin_menu() {
method wp_before_admin_bar_render (line 490) | public function wp_before_admin_bar_render() {
method add_meta_boxes (line 502) | public function add_meta_boxes( $post_type ) {
method bbl_translation_post_meta_boxes (line 510) | public function bbl_translation_post_meta_boxes( $type, $original, $tr...
method bbl_translation_terms_meta_boxes (line 518) | public function bbl_translation_terms_meta_boxes( $type, $items ) {
method bbl_translation_meta_meta_boxes (line 527) | public function bbl_translation_meta_meta_boxes( $type, $items ) {
method bbl_translation_submit_meta_boxes (line 538) | public function bbl_translation_submit_meta_boxes( $type, $job ) {
method metabox_translation_terms (line 544) | public function metabox_translation_terms( array $items ) {
method metabox_translation_meta (line 552) | public function metabox_translation_meta( array $items ) {
method metabox_translation_post_excerpt (line 560) | public function metabox_translation_post_excerpt( array $items ) {
method metabox_translation_submit (line 568) | public function metabox_translation_submit( array $items ) {
method save_job (line 576) | public function save_job( $job_id, WP_Post $job ) {
method save_post (line 735) | public function save_post( $post_id, WP_Post $post ) {
method init_early (line 763) | public function init_early() {
method filter_columns (line 828) | public function filter_columns( $cols ) {
method action_column (line 843) | public function action_column( $col, $post_id ) {
method metabox_post_translations (line 866) | public function metabox_post_translations( WP_Post $post, array $metab...
method get_incomplete_post_jobs (line 941) | public function get_incomplete_post_jobs( $post ) {
method get_completed_post_jobs (line 953) | public function get_completed_post_jobs( $post ) {
method get_term_jobs (line 965) | public function get_term_jobs( $term, $taxonomy ) {
method get_object_jobs (line 979) | public function get_object_jobs( $id, $type, $name, $statuses = array(...
method get_job_language (line 1004) | public function get_job_language( $job ) {
method get_job_type (line 1012) | public function get_job_type( $job ) {
method get_job_objects (line 1038) | public function get_job_objects( $job ) {
method create_empty_translation (line 1085) | public function create_empty_translation( array $args ) {
method create_post_jobs (line 1119) | public function create_post_jobs( $post_id, array $lang_codes ) {
method get_post_terms_to_translate (line 1168) | public function get_post_terms_to_translate( $post_id, $lang_code ) {
method get_post_meta_to_translate (line 1207) | public function get_post_meta_to_translate( WP_Post $post, $lang_code ) {
method get_translated_meta_fields (line 1226) | public function get_translated_meta_fields( WP_Post $post ) {
FILE: class-languages.php
class Babble_Languages (line 9) | class Babble_Languages extends Babble_Plugin {
method __construct (line 74) | public function __construct() {
method initiate (line 89) | public function initiate() {
method admin_notices (line 113) | public function admin_notices() {
method admin_menu (line 126) | public function admin_menu() {
method load_options (line 135) | public function load_options() {
method options (line 149) | public function options() {
method set_active_langs (line 189) | public function set_active_langs( $lang_codes ) {
method get_active_langs (line 208) | public function get_active_langs() {
method is_public_lang (line 222) | public function is_public_lang( $lang_code ) {
method get_lang (line 234) | public function get_lang( $lang_code ) {
method get_current_lang (line 248) | public function get_current_lang() {
method get_default_lang_code (line 258) | public function get_default_lang_code() {
method get_default_lang (line 267) | public function get_default_lang() {
method get_url_prefix_from_code (line 277) | public function get_url_prefix_from_code( $code ) {
method get_code_from_url_prefix (line 289) | public function get_code_from_url_prefix( $url_prefix ) {
method merge_lang_sets (line 311) | protected function merge_lang_sets( $langs_a, $langs_b ) {
method maybe_process_languages (line 334) | protected function maybe_process_languages() {
method parse_available_languages (line 418) | protected function parse_available_languages() {
method is_rtl (line 453) | protected function is_rtl( $lang ) {
method format_code_lang (line 476) | protected function format_code_lang( $code ) {
method set_defaults (line 501) | protected function set_defaults() {
FILE: class-locale.php
class Babble_Locale (line 9) | class Babble_Locale {
method __construct (line 74) | function __construct() {
method plugins_loaded (line 89) | public function plugins_loaded() {
method admin_init (line 104) | public function admin_init() {
method admin_notices (line 117) | public function admin_notices() {
method mod_rewrite_rules (line 129) | public function mod_rewrite_rules( $rules ) {
method internal_rewrite_rules_filter (line 153) | public function internal_rewrite_rules_filter( $rules ){
method set_locale (line 207) | public function set_locale( $locale ) {
method parse_request_early (line 273) | public function parse_request_early( WP $wp ) {
method query_vars (line 291) | public function query_vars( array $query_vars ) {
method pre_comment_on_post (line 303) | public function pre_comment_on_post() {
method home_url (line 321) | public function home_url( $url, $path ) {
method body_class (line 337) | public function body_class( array $classes ) {
method post_class (line 357) | public function post_class( array $classes, $class, $post_id ) {
method use_default_text_direction (line 385) | public static function use_default_text_direction( WP_Post $post ) {
method get_lang (line 402) | public function get_lang() {
method get_content_lang (line 412) | public function get_content_lang() {
method get_interface_lang (line 421) | public function get_interface_lang() {
method switch_to_lang (line 431) | public function switch_to_lang( $lang ) {
method restore_lang (line 445) | public function restore_lang() {
method set_content_lang (line 462) | protected function set_content_lang( $code ) {
method set_interface_lang (line 477) | protected function set_interface_lang( $code ) {
method set_content_lang_from_prefix (line 488) | protected function set_content_lang_from_prefix( $url_prefix ) {
method get_request_string (line 499) | protected function get_request_string() {
method maybe_set_cookie_content_lang (line 556) | protected function maybe_set_cookie_content_lang() {
method maybe_set_cookie_interface_lang (line 569) | protected function maybe_set_cookie_interface_lang() {
method get_cookie_content_lang (line 582) | protected function get_cookie_content_lang() {
method get_cookie_interface_lang (line 593) | protected function get_cookie_interface_lang() {
method maybe_update (line 603) | protected function maybe_update() {
FILE: class-meta.php
class Babble_Meta_Field (line 8) | abstract class Babble_Meta_Field {
method __construct (line 10) | public function __construct( WP_Post $post, $meta_key, $meta_title, ar...
method get_input (line 18) | abstract public function get_input( $name, $value );
method get_output (line 20) | public function get_output() {
method get_title (line 24) | public function get_title() {
method get_value (line 28) | public function get_value() {
method get_key (line 32) | public function get_key() {
method update (line 36) | public function update( $value, WP_Post $job ) {
class Babble_Meta_Field_Text (line 42) | class Babble_Meta_Field_Text extends Babble_Meta_Field {
method get_input (line 44) | public function get_input( $name, $value ) {
class Babble_Meta_Field_Textarea (line 53) | class Babble_Meta_Field_Textarea extends Babble_Meta_Field {
method get_input (line 55) | public function get_input( $name, $value ) {
method get_output (line 62) | public function get_output() {
class Babble_Meta_Field_Editor (line 68) | class Babble_Meta_Field_Editor extends Babble_Meta_Field {
method get_input (line 70) | public function get_input( $name, $value ) {
method get_output (line 85) | public function get_output() {
FILE: class-plugin.php
class Babble_Plugin (line 61) | class Babble_Plugin {
method setup (line 111) | public function setup( $name = '', $type = null ) {
method load_locale (line 163) | function load_locale() {
method add_action (line 183) | function add_action ($action, $function = '', $priority = 10, $accepte...
method add_filter (line 200) | function add_filter ($filter, $function = '', $priority = 10, $accepte...
method remove_filter (line 215) | function remove_filter ($filter, $function = '', $priority = 10, $acce...
method register_activation (line 228) | function register_activation ( $pluginfile = __FILE__, $function = '' ) {
method theme_activation (line 244) | public function theme_activation() {
method register_deactivation (line 262) | function register_deactivation ($pluginfile, $function = '') {
method render (line 273) | protected function render( $template_file, $vars = null ) {
method render_admin (line 295) | protected function render_admin( $template_file, $vars = null ) {
method capture (line 318) | protected function capture( $template_file, $vars = null ) {
method capture_admin (line 334) | protected function capture_admin( $template_file, $vars = null ) {
method _admin_notices (line 349) | public function _admin_notices() {
method render_admin_notice (line 373) | protected function render_admin_notice( $notice ) {
method render_admin_error (line 384) | protected function render_admin_error( $error ) {
method set_admin_notice (line 395) | protected function set_admin_notice( $msg ) {
method set_admin_error (line 408) | protected function set_admin_error( $msg ) {
method locate_template (line 427) | protected function locate_template( $template_file ) {
method add_meta_box (line 460) | function add_meta_box( $id, $title, $function = '', $page, $context = ...
method add_shortcode (line 476) | protected function add_shortcode( $tag, $function = null ) {
method dir (line 487) | protected function dir( $path ) {
method url (line 498) | protected function url( $path ) {
method get_all_options (line 508) | protected function get_all_options() {
method update_all_options (line 518) | protected function update_all_options( $value ) {
method get_option (line 529) | public function get_option( $key, $value = null ) {
method update_option (line 544) | protected function update_option( $key, $value ) {
method delete_option (line 557) | protected function delete_option( $key ) {
method ajax_die (line 571) | protected function ajax_die( $msg ) {
method truncate (line 586) | protected function truncate( $str, $num_words )
FILE: class-post-public.php
class Babble_Post_Public (line 9) | class Babble_Post_Public extends Babble_Plugin {
method __construct (line 81) | public function __construct() {
method initiate (line 122) | public function initiate() {
method admin_init (line 151) | public function admin_init() {
method initialise_translation (line 181) | public function initialise_translation( $origin_post, $lang_code ) {
method load_post_new (line 218) | public function load_post_new() {
method wp_before_admin_bar_render (line 237) | public function wp_before_admin_bar_render() {
method registered_post_type (line 250) | public function registered_post_type( $post_type, $args ) {
method add_post_metadata (line 376) | public function add_post_metadata( $null, $post_id, $meta_key, $meta_v...
method added_post_meta (line 393) | public function added_post_meta( $meta_id, $post_id, $meta_key, $meta_...
method updated_post_meta (line 424) | public function updated_post_meta( $meta_id, $post_id, $meta_key, $met...
method deleted_post_meta (line 453) | public function deleted_post_meta( $meta_id, $post_id, $meta_key, $met...
method pre_get_posts (line 487) | public function pre_get_posts( WP_Query & $query ) {
method parse_request (line 513) | public function parse_request( WP & $wp ) {
method the_posts (line 536) | public function the_posts( array $posts, WP_Query & $wp_query ) {
method body_class (line 580) | public function body_class( array $classes, $class ) {
method post_type_link (line 597) | public function post_type_link( $post_link, $post, $leavename ) {
method get_sample_permalink (line 705) | public function get_sample_permalink( $permalink, $title, $name, $id, ...
method page_link (line 717) | public function page_link( $link, $post_id ) {
method post_type_archive_link (line 750) | public function post_type_archive_link( $link, $post_type ) {
method clean_post_cache (line 774) | function clean_post_cache( $post_id ) {
method post_updated (line 793) | public function post_updated( $post_id ) {
method transition_post_status (line 837) | public function transition_post_status( $new_status, $old_status, $pos...
method add_menu_classes (line 869) | public function add_menu_classes( $menu ) {
method single_template (line 905) | public function single_template( $template ) {
method sync_meta_key (line 953) | function sync_meta_key( $sync, $meta_key ) {
method manage_posts_columns (line 973) | public function manage_posts_columns( array $columns, $post_type ) {
method manage_posts_custom_column (line 989) | public function manage_posts_custom_column( $column_name, $post_id ) {
method bbl_translated_taxonomy (line 1010) | public function bbl_translated_taxonomy( $translated, $taxonomy ) {
method admin_body_class (line 1016) | public function admin_body_class( $class ) {
method translate_query_vars (line 1034) | protected function translate_query_vars( array $query_vars, $request =...
method is_language_front_page (line 1126) | public function is_language_front_page( $post_id = null, $lang_code = ...
method get_post_lang_code (line 1151) | public function get_post_lang_code( $post ) {
method get_new_post_translation_url (line 1168) | public function get_new_post_translation_url( $default_post, $lang_cod...
method get_default_lang_post (line 1187) | public function get_default_lang_post( $post ) {
method get_post_translations (line 1203) | public function get_post_translations( $post ) {
method get_base_post_type (line 1248) | public function get_base_post_type( $post_type ) {
method get_base_post_types (line 1259) | public function get_base_post_types() {
method get_post_type_in_lang (line 1273) | public function get_post_type_in_lang( $post_type, $lang_code ) {
method get_shadow_post_types (line 1300) | public function get_shadow_post_types( $base_post_type ) {
method get_post_in_lang (line 1319) | public function get_post_in_lang( $post, $lang_code, $fallback = true ) {
method get_slug_in_lang (line 1338) | public function get_slug_in_lang( $slug, $lang ) {
method sync_properties (line 1357) | public function sync_properties( $source_id, $target_id ) {
method sync_post_meta (line 1410) | function sync_post_meta( $post_id ) {
method get_transid (line 1458) | function get_transid( $post, $create = true ) {
method set_transid (line 1496) | function set_transid( $post, $transid = false ) {
method get_features_supported_by_post_type (line 1535) | protected function get_features_supported_by_post_type( $post_type ) {
method is_media_upload_tab (line 1546) | protected function is_media_upload_tab( $tab = null ) {
method is_media_manager (line 1566) | protected function is_media_manager() {
method prune_post_meta (line 1583) | protected function prune_post_meta() {
method maybe_upgrade (line 1611) | protected function maybe_upgrade() {
method have_duplicate_metadata (line 1650) | function have_duplicate_metadata() {
FILE: class-switcher-content.php
class Babble_Switcher_Menu (line 12) | class Babble_Switcher_Menu {
method get_switcher_links (line 50) | public function get_switcher_links( $id_prefix ) {
method populate_links (line 64) | protected function populate_links() {
method add_admin_generic_link (line 164) | protected function add_admin_generic_link( $lang ) {
method add_admin_term_link (line 197) | protected function add_admin_term_link( $lang ) {
method add_admin_list_terms_link (line 245) | protected function add_admin_list_terms_link( $lang ) {
method add_admin_post_link (line 278) | protected function add_admin_post_link( $lang ) {
method add_admin_list_posts_link (line 328) | protected function add_admin_list_posts_link( $lang ) {
method add_post_link (line 361) | protected function add_post_link( $lang ) {
method add_front_page_link (line 420) | protected function add_front_page_link( $lang ) {
method add_post_type_archive_link (line 450) | protected function add_post_type_archive_link( $lang ) {
method add_taxonomy_archive_link (line 479) | protected function add_taxonomy_archive_link( $lang ) {
method add_arbitrary_link (line 522) | protected function add_arbitrary_link( $lang ) {
FILE: class-switcher-interface.php
class Babble_Switcher_Interface (line 9) | class Babble_Switcher_Interface extends Babble_Plugin {
method __construct (line 14) | public function __construct() {
method personal_options (line 20) | public function personal_options( WP_User $user ) {
FILE: class-taxonomy.php
class Babble_Taxonomies (line 9) | class Babble_Taxonomies extends Babble_Plugin {
method __construct (line 45) | public function __construct() {
method init_early (line 71) | public function init_early() {
method registered_taxonomy (line 90) | public function registered_taxonomy( $taxonomy, $object_type, $args ) {
method ignored_taxonomies (line 184) | public function ignored_taxonomies() {
method is_taxonomy_translated (line 188) | public function is_taxonomy_translated( $taxonomy ) {
method registered_shadow_post_types (line 209) | public function registered_shadow_post_types( $post_type ) {
method created_new_shadow_post (line 242) | public function created_new_shadow_post( $new_post_id, $origin_post_id...
method save_post (line 274) | public function save_post( $post_id, $post ) {
method term_link (line 289) | public function term_link( $termlink, $term, $taxonomy ) {
method get_terms (line 361) | public function get_terms( $terms ) {
method parse_request (line 391) | public function parse_request( $wp ) {
method set_object_terms (line 470) | public function set_object_terms( $object_id, $terms, $tt_ids, $taxono...
method body_class (line 539) | public function body_class( array $classes, $class ) {
method taxonomy_template (line 576) | public function taxonomy_template( $template ) {
method admin_body_class (line 619) | public function admin_body_class( $class ) {
method bbl_translated_taxonomy (line 630) | public function bbl_translated_taxonomy( $translated, $taxonomy ) {
method translated_taxonomy (line 654) | public function translated_taxonomy( $origin_taxonomy, $lang_code ) {
method get_term_translations (line 668) | public function get_term_translations( $term, $taxonomy ) {
method get_term_in_lang (line 713) | public function get_term_in_lang( $term, $taxonomy, $lang_code, $fallb...
method get_new_term_translation_url (line 733) | public function get_new_term_translation_url( $default_term, $lang_cod...
method get_taxonomy_lang_code (line 760) | public function get_taxonomy_lang_code( $taxonomy ) {
method get_base_taxonomy (line 781) | public function get_base_taxonomy( $taxonomy ) {
method get_taxonomy_in_lang (line 795) | public function get_taxonomy_in_lang( $taxonomy, $lang_code = null ) {
method get_slug_in_lang (line 827) | public function get_slug_in_lang( $slug, $lang_code = null ) {
method initialise_translation (line 841) | public function initialise_translation( $origin_term, $taxonomy, $lang...
method update_post_term_count (line 874) | function update_post_term_count( $terms, $taxonomy ) {
method get_transid (line 920) | public function get_transid( $target_term_id ) {
method set_transid (line 950) | public function set_transid( $target_term_id, $transid = null ) {
method maybe_resync_terms (line 983) | protected function maybe_resync_terms( $post_id, $post ) {
FILE: class-translator.php
class Babble_Translator (line 8) | class Babble_Translator extends Babble_Plugin {
method __construct (line 18) | public function __construct() {
method maybe_upgrade (line 32) | public function maybe_upgrade() {
FILE: class-updates.php
class Babble_Updates (line 9) | class Babble_Updates extends Babble_Plugin {
method __construct (line 11) | public function __construct() {
method plugins_loaded (line 21) | public function plugins_loaded() {
method euapi_plugin_handler (line 37) | public function euapi_plugin_handler( EUAPI_Handler $handler = null, E...
FILE: deprecated.php
function bbl_start_translating (line 36) | function bbl_start_translating() {
function bbl_stop_translating (line 47) | function bbl_stop_translating() {
function bbl_translating (line 58) | function bbl_translating() {
FILE: external-update-api/external-update-api.php
function euapi_autoloader (line 41) | function euapi_autoloader( $class ) {
function euapi_flush_transients (line 67) | function euapi_flush_transients() {
FILE: external-update-api/external-update-api/euapi.php
class EUAPI (line 10) | class EUAPI {
method __construct (line 19) | private function __construct() {
method filter_http_request_args (line 45) | public function filter_http_request_args( array $args, $url ) {
method plugin_request (line 96) | protected function plugin_request( array $args, $version ) {
method theme_request (line 155) | protected function theme_request( array $args, $version ) {
method filter_update_plugins (line 219) | public function filter_update_plugins( $update ) {
method filter_update_themes (line 236) | public function filter_update_themes( $update ) {
method check (line 252) | public static function check( $update, array $handlers ) {
method get_handler (line 285) | public function get_handler( $type, $file, $item = null ) {
method populate_item (line 316) | protected static function populate_item( $type, $file ) {
method get_plugin_data (line 344) | public static function get_plugin_data( $file ) {
method get_theme_data (line 362) | public static function get_theme_data( $file ) {
method filter_plugins_api (line 404) | public function filter_plugins_api( $default, $action, $plugin ) {
method filter_themes_api (line 435) | public function filter_themes_api( $default, $action, $theme ) {
method fetch (line 459) | public static function fetch( $url, array $args = array() ) {
method get_content_data (line 491) | public static function get_content_data( $content, array $all_headers ) {
method filter_upgrader_pre_install (line 521) | public function filter_upgrader_pre_install( $default, array $hook_ext...
method filter_upgrader_post_install (line 541) | public function filter_upgrader_post_install( $default, array $hook_ex...
method init (line 580) | public static function init() {
method filter_euapi_plugin_handler (line 598) | public function filter_euapi_plugin_handler( EUAPI_Handler $handler = ...
FILE: external-update-api/external-update-api/handler-files.php
class EUAPI_Handler_Files (line 12) | abstract class EUAPI_Handler_Files extends EUAPI_Handler {
method __construct (line 25) | public function __construct( array $config ) {
method get_file_url (line 52) | abstract public function get_file_url( $file = null );
method fetch_new_version (line 61) | final public function fetch_new_version() {
method fetch_info (line 87) | final public function fetch_info() {
FILE: external-update-api/external-update-api/handler-github.php
class EUAPI_Handler_GitHub (line 15) | class EUAPI_Handler_GitHub extends EUAPI_Handler_Files {
method __construct (line 30) | public function __construct( array $config ) {
method get_homepage_url (line 62) | public function get_homepage_url() {
method get_file_url (line 75) | public function get_file_url( $file = null ) {
method get_package_url (line 98) | public function get_package_url() {
FILE: external-update-api/external-update-api/handler.php
class EUAPI_Handler (line 10) | abstract class EUAPI_Handler {
method __construct (line 22) | public function __construct( array $config ) {
method get_homepage_url (line 43) | abstract public function get_homepage_url();
method get_package_url (line 51) | abstract public function get_package_url();
method fetch_new_version (line 59) | abstract public function fetch_new_version();
method fetch_info (line 67) | abstract public function fetch_info();
method fetch_upgrade_notice (line 74) | public function fetch_upgrade_notice(){
method get_file (line 84) | final public function get_file() {
method get_current_version (line 94) | final public function get_current_version() {
method get_new_version (line 110) | final public function get_new_version() {
method get_upgrade_notice (line 126) | final public function get_upgrade_notice() {
method get_update (line 142) | final public function get_update() {
method get_info (line 170) | final public function get_info() {
method get_config (line 186) | final public function get_config() {
method get_type (line 196) | final public function get_type() {
FILE: external-update-api/external-update-api/info.php
class EUAPI_Info (line 10) | class EUAPI_Info {
method __construct (line 14) | public function __construct( array $args ) {
FILE: external-update-api/external-update-api/item-plugin.php
class EUAPI_Item_Plugin (line 11) | class EUAPI_Item_Plugin extends EUAPI_Item {
method __construct (line 15) | public function __construct( $plugin, array $data ) {
FILE: external-update-api/external-update-api/item-theme.php
class EUAPI_Item_Theme (line 11) | class EUAPI_Item_Theme extends EUAPI_Item {
method __construct (line 15) | public function __construct( $theme, array $data ) {
FILE: external-update-api/external-update-api/item.php
class EUAPI_Item (line 10) | abstract class EUAPI_Item {
method get_version (line 12) | public function get_version() {
method get_url (line 16) | public function get_url() {
FILE: external-update-api/external-update-api/update.php
class EUAPI_Update (line 10) | class EUAPI_Update {
method __construct (line 12) | public function __construct( array $args ) {
method get_data_to_store (line 22) | public function get_data_to_store() {
method get_new_version (line 26) | public function get_new_version() {
FILE: miscellaneous.php
function bbl_load_interface_textdomain (line 28) | function bbl_load_interface_textdomain() {
function bbl_load_content_textdomain (line 37) | function bbl_load_content_textdomain() {
function bbl_admin_init (line 48) | function bbl_admin_init() {
function bbl_comments_template (line 111) | function bbl_comments_template( $file = '/comments.php', $separate_comme...
class Bbl_Comment_Query (line 197) | class Bbl_Comment_Query {
method query (line 207) | function query( $query_vars ) {
FILE: translation-fields.php
function bbl_wpseo_meta_fields (line 29) | function bbl_wpseo_meta_fields( array $fields, WP_Post $post ) {
FILE: translation-group-tool-sorter.php
class SortPosts (line 5) | class SortPosts {
method post_type_descending (line 6) | function post_type_descending( $m, $n ) {
FILE: translation-group-tool.php
class BabbleTranslationGroupTool (line 37) | class BabbleTranslationGroupTool extends Babble_Plugin {
method __construct (line 46) | function __construct() {
method admin_menu (line 66) | public function admin_menu() {
method load_tools_page (line 76) | public function load_tools_page() {
method load_post (line 117) | public function load_post() {
method save_post (line 138) | function save_post( $post_id, $post ) {
method pre_sync_properties (line 175) | public function pre_sync_properties( $postdata, $origin_id ) {
method metaboxes_for_translators (line 197) | function metaboxes_for_translators( $boxes ) {
method metabox_reconnect (line 215) | public function metabox_reconnect( $post, $metabox ) {
method tools_page (line 232) | public function tools_page() {
method get_action_link (line 248) | protected function get_action_link( $obj_id, $action, $anchor = null ) {
FILE: widget.php
function babble_widget (line 4) | function babble_widget() {
class Babble_Widget (line 8) | class Babble_Widget extends WP_Widget {
method __construct (line 15) | function __construct() {
method widget (line 23) | function widget( $args, $instance ) {
method update (line 121) | function update( $new_instance, $old_instance ) {
method form (line 131) | function form( $instance ) {
Condensed preview — 54 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (395K chars).
[
{
"path": "CONTRIBUTING.md",
"chars": 1151,
"preview": "Contribute\n==========\n\nFirst off: thanks for looking into contributing to Babble, we appreciate your help.\n\nSetting up\n-"
},
{
"path": "api.php",
"chars": 20432,
"preview": "<?php\n\n/**\n * Translations and languages API.\n *\n * @package Babble\n * @since Alpha 1\n */\n\n/* Copyright 2013 Code for t"
},
{
"path": "babble.php",
"chars": 2340,
"preview": "<?php\n/*\nPlugin Name: Babble\nPlugin URI: http://babbleplugin.com/\nDescription: Multilingual WordPress done right\nVersio"
},
{
"path": "class-admin-bar.php",
"chars": 1151,
"preview": "<?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_Ad"
},
{
"path": "class-babble-log.php",
"chars": 696,
"preview": "<?php\n\n/**\n * Manages the locale currently set for the site.\n *\n * @package Babble\n * @since Alpha 1\n */\nclass Babble_Lo"
},
{
"path": "class-comment.php",
"chars": 2931,
"preview": "<?php\n\n/**\n * Class for handling comments.\n *\n * @package Babble\n * @since 0.1\n */\nclass Babble_Comment extends Babble_P"
},
{
"path": "class-jobs.php",
"chars": 37563,
"preview": "<?php\n/**\n * Class for handling jobs for the various language\n * translation teams.\n *\n * @package Babble\n * @since 1.4\n"
},
{
"path": "class-languages.php",
"chars": 21154,
"preview": "<?php\n\n/**\n * Manages the languages available for the site.\n *\n * @package Babble\n * @since Alpha 1.1\n */\nclass Babble_L"
},
{
"path": "class-locale.php",
"chars": 19421,
"preview": "<?php\n\n/**\n * Manages the locale currently set for the site.\n *\n * @package Babble\n * @since Alpha 1\n */\nclass Babble_Lo"
},
{
"path": "class-meta.php",
"chars": 2151,
"preview": "<?php\n/**\n * Class for handling post meta translations.\n *\n * @package Babble\n * @since 1.5\n */\nabstract class Babble_Me"
},
{
"path": "class-plugin.php",
"chars": 20587,
"preview": "<?php \n\n// ======================================================================================\n// This library is fre"
},
{
"path": "class-post-public.php",
"chars": 57569,
"preview": "<?php\n\n/**\n * Class for handling the public, content handling post types.\n *\n * @package Babble\n * @since 0.1\n */\nclass "
},
{
"path": "class-switcher-content.php",
"chars": 19948,
"preview": "<?php\n\n/**\n * Class for providing the switch/create links for the current \n * content items. Works in the admin or publi"
},
{
"path": "class-switcher-interface.php",
"chars": 784,
"preview": "<?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 Bab"
},
{
"path": "class-taxonomy.php",
"chars": 33349,
"preview": "<?php\n\n/**\n * Manages the translations for taxonomies.\n *\n * @package Babble\n * @since Alpha 1.2\n */\nclass Babble_Taxono"
},
{
"path": "class-translator.php",
"chars": 1541,
"preview": "<?php\n/**\n * Class for managing the translator role and associated caps\n *\n * @package Babble\n * @since 1.4\n */\nclass Ba"
},
{
"path": "class-updates.php",
"chars": 1638,
"preview": "<?php\n\n/**\n * Class for providing updates to Babble via GitHub rather than wordpress.org\n *\n * @package Babble\n * @since"
},
{
"path": "composer.json",
"chars": 388,
"preview": "{\n \"name\": \"cftp/babble\",\n \"description\": \"A WordPress plugin to handle translating content into a variety of lang"
},
{
"path": "css/jobs-admin.css",
"chars": 2327,
"preview": "\nbody.bbl-post-type-bbl_job .postbox-container {\n\tdisplay: none;\n}\n\nbody.bbl-post-type-bbl_job #poststuff #post-body {\n\t"
},
{
"path": "css/languages-options.css",
"chars": 720,
"preview": "\ntable.babble_languages .column-active {\n\twidth: 10%;\n}\n\ntable.babble_languages .column-public {\n\twidth: 10%;\n}\n\ntable.b"
},
{
"path": "deprecated.php",
"chars": 1592,
"preview": "<?php\n\n/**\n * Deprecated API functions.\n *\n * @since 1.4\n * @package Babble\n */\n\n/* Copyright 2013 Code For The People "
},
{
"path": "external-update-api/external-update-api/euapi.php",
"chars": 17118,
"preview": "<?php\n\ndefined( 'ABSPATH' ) or die();\n\nif ( ! class_exists( 'EUAPI' ) ) :\n\n/**\n * The EUAPI plugin class.\n */\nclass EUAP"
},
{
"path": "external-update-api/external-update-api/handler-files.php",
"chars": 3309,
"preview": "<?php\n\ndefined( 'ABSPATH' ) or die();\n\nif ( ! class_exists( 'EUAPI_Handler_Files' ) ) :\n\n/**\n * EUAPI handler for plugin"
},
{
"path": "external-update-api/external-update-api/handler-github.php",
"chars": 2946,
"preview": "<?php\n\ndefined( 'ABSPATH' ) or die();\n\nif ( ! class_exists( 'EUAPI_Handler_GitHub' ) ) :\n\n/**\n * EUAPI handler for plugi"
},
{
"path": "external-update-api/external-update-api/handler.php",
"chars": 4738,
"preview": "<?php\n\ndefined( 'ABSPATH' ) or die();\n\nif ( ! class_exists( 'EUAPI_Handler' ) ) :\n\n/**\n * Abstract class upon which to b"
},
{
"path": "external-update-api/external-update-api/info.php",
"chars": 275,
"preview": "<?php\n\ndefined( 'ABSPATH' ) or die();\n\nif ( ! class_exists( 'EUAPI_Info' ) ) :\n\n/**\n * EUAPI Info class.\n */\nclass EUAPI"
},
{
"path": "external-update-api/external-update-api/item-plugin.php",
"chars": 501,
"preview": "<?php\n\ndefined( 'ABSPATH' ) or die();\n\nif ( ! class_exists( 'EUAPI_Item_Plugin' ) ) :\n\n/**\n * EUAPI plugin item. A simpl"
},
{
"path": "external-update-api/external-update-api/item-theme.php",
"chars": 493,
"preview": "<?php\n\ndefined( 'ABSPATH' ) or die();\n\nif ( ! class_exists( 'EUAPI_Item_Theme' ) ) :\n\n/**\n * EUAPI theme item. A simple "
},
{
"path": "external-update-api/external-update-api/item.php",
"chars": 321,
"preview": "<?php\n\ndefined( 'ABSPATH' ) or die();\n\nif ( ! class_exists( 'EUAPI_Item' ) ) :\n\n/**\n * Abstract EUAPI Item class upon wh"
},
{
"path": "external-update-api/external-update-api/update.php",
"chars": 616,
"preview": "<?php\n\ndefined( 'ABSPATH' ) or die();\n\nif ( ! class_exists( 'EUAPI_Update' ) ) :\n\n/**\n * EUAPI update item. Contains inf"
},
{
"path": "external-update-api/external-update-api.php",
"chars": 2405,
"preview": "<?php\n/*\nPlugin Name: External Update API\nPlugin URI: https://github.com/cftp/external-update-api\nDescription: Add s"
},
{
"path": "external-update-api/readme.md",
"chars": 4407,
"preview": "# External Update API #\n\n**Contributors:** codeforthepeople, johnbillion \n**Tags:** updates, github \n**Requires at lea"
},
{
"path": "js/post-public-admin.js",
"chars": 783,
"preview": "jQuery( function ( $ ) {\n\t'use strict';\n\tif ( ! bbl_post_public.is_default_lang ) {\n\t\t// Fixup the side admin menu, whic"
},
{
"path": "languages/fa_IR.php",
"chars": 33,
"preview": "<?php $text_direction = \"rtl\"; ?>"
},
{
"path": "languages/pt_BR.po",
"chars": 8983,
"preview": "# Translation of Babble in Portuguese (Portugal)\n# This file is distributed under the same license as the Babble package"
},
{
"path": "languages/tr.po",
"chars": 9871,
"preview": "# Translation of Babble in Turkish\n# This file is distributed under the same license as the Babble package.\nmsgid \"\"\nmsg"
},
{
"path": "miscellaneous.php",
"chars": 12048,
"preview": "<?php\n\n/**\n * Functions and (mainly) hooks which don't fit in the various \n * classes for whatever reason. Consider thes"
},
{
"path": "readme.md",
"chars": 3902,
"preview": "# Babble\n\n* Tags: translations, translation, multilingual, i18n, l10n, localisation\n* Requires at least: 3.5.1\n* Tested "
},
{
"path": "readme.txt",
"chars": 3675,
"preview": "=== Babble ===\n\nContributors: Automattic, simonwheatley\nTags: translations, translation, multilingual, i18n, l10n, local"
},
{
"path": "templates-admin/options-available-languages.php",
"chars": 5359,
"preview": "<?php \n\t/**\n\t * HTML template for the Available Languages screen in the admin area.\n\t *\n\t * @package Babble\n\t * @subpack"
},
{
"path": "templates-admin/switcher-interface.php",
"chars": 343,
"preview": "<tr>\n\t<th scope=\"row\"><?php _e( 'Interface Language', 'babble' ); ?></th>\n\t<td><select name=\"interface_lang\">\n\t\t<?php fo"
},
{
"path": "templates-admin/translation-editor-meta.php",
"chars": 530,
"preview": "<?php\r\n$original = $meta['original'];\r\n$translation = $meta['translation'];\r\n$key = $original->get_key();\r\n?>"
},
{
"path": "templates-admin/translation-editor-post-excerpt.php",
"chars": 423,
"preview": "<div class=\"bbl-translation-property bbl-translation-property-post_excerpt\">\r\n\t<textarea class=\"regular-text\" name=\"bbl_"
},
{
"path": "templates-admin/translation-editor-terms.php",
"chars": 2016,
"preview": "<?php foreach ( $terms as $term ) { ?>\r\n\r\n\t<?php\r\n\t$original = $term['original'];\r\n\t$translation = $term['translation"
},
{
"path": "templates-admin/translation-editor.php",
"chars": 5630,
"preview": "<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_co"
},
{
"path": "templates-admin/translation-groups.php",
"chars": 5357,
"preview": "<div class=\"wrap\">\n\t<?php screen_icon(); ?>\n\t<h2>Translation Groups</h2>\n\n\t<script type=\"text/javascript\" charset=\"utf-8"
},
{
"path": "translation-fields.php",
"chars": 2379,
"preview": "<?php\n/*\nPlugin Name: Babble Translation Fields\nPlugin URI: http://babbleplugin.com/\nDescription: Support for translati"
},
{
"path": "translation-group-tool-sorter.php",
"chars": 268,
"preview": "<?php \n\n// From: http://seancode.blogspot.com/2008/01/php-usort-sort-array-of-objects.html\n\nclass SortPosts {\n\tfunction "
},
{
"path": "translation-group-tool.php",
"chars": 8875,
"preview": "<?php\n/*\nPlugin Name: Babble: Translation Group Tool\nPlugin URI: http://simonwheatley.co.uk/wordpress/btgt\nDescription: "
},
{
"path": "widget.php",
"chars": 5430,
"preview": "<?php\n\nadd_action( 'widgets_init', 'babble_widget' );\nfunction babble_widget() {\n\tregister_widget( 'Babble_Widget' );\n}\n"
}
]
// ... and 4 more files (download for full content)
About this extraction
This page contains the full source code of the Automattic/babble GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 54 files (354.0 KB), approximately 104.4k tokens, and a symbol index with 393 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.