<?php
/*
Plugin Name: Polylang
Plugin URI: http://polylang.wordpress.com/
Version: 1.1
Author: Frédéric Demarle
Description: Adds multilingual capability to WordPress
Text Domain: polylang
Domain Path: /languages
*/
/*
* Copyright 2011-2013 F. Demarle
*
* 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., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*
*/
define('POLYLANG_VERSION', '1.1');
define('PLL_MIN_WP_VERSION', '3.1');
define('POLYLANG_DIR', dirname(__FILE__)); // our directory
define('PLL_INC', POLYLANG_DIR.'/include');
if (!defined('PLL_LOCAL_DIR'))
define('PLL_LOCAL_DIR', WP_CONTENT_DIR.'/polylang'); // default directory to store user data such as custom flags
if (file_exists(PLL_LOCAL_DIR.'/pll-config.php'))
include_once(PLL_LOCAL_DIR.'/pll-config.php'); // includes local config file if exists
define('POLYLANG_URL', plugins_url('/'.basename(POLYLANG_DIR))); // our url. Don't use WP_PLUGIN_URL http://wordpress.org/support/topic/ssl-doesnt-work-properly
if (!defined('PLL_LOCAL_URL'))
define('PLL_LOCAL_URL', content_url('/polylang')); // default url to access user data such as custom flags
if (!defined('PLL_COOKIE'))
define('PLL_COOKIE', 'pll_language'); // cookie name. no cookie will be used if set to false
if (!defined('PLL_SEARCH_FORM_JS') && version_compare($GLOBALS['wp_version'], '3.6', '<'))
define('PLL_SEARCH_FORM_JS', false); // the search form js is no more needed in WP 3.6+ except if the search form is hardcoded elsewhere than in searchform.php
require_once(PLL_INC.'/base.php');
// controls the plugin, deals with activation, deactivation, upgrades, initialization as well as rewrite rules
class Polylang extends Polylang_Base {
function __construct() {
parent::__construct();
global $polylang; // globalize the variable to access it in the API
// manages plugin activation and deactivation
register_activation_hook( __FILE__, array(&$this, 'activate'));
register_deactivation_hook( __FILE__, array(&$this, 'deactivate'));
// stopping here if we upgraded from a too old version
if ($this->options && version_compare($this->options['version'], '0.8', '<')) {
add_action('all_admin_notices', array(&$this, 'admin_notices'));
return;
}
// stopping here if we are going to deactivate the plugin (avoids breaking rewrite rules)
if (isset($_GET['action']) && $_GET['action'] == 'deactivate' && isset($_GET['plugin']) && $_GET['plugin'] == 'polylang/polylang.php')
return;
// blog creation on multisite
add_action('wpmu_new_blog', array(&$this, 'wpmu_new_blog'));
// manages plugin upgrade
add_action('admin_init', array(&$this, 'admin_init'));
// plugin and widget initialization
add_action('setup_theme', array(&$this, 'init'), 1);
add_action('widgets_init', array(&$this, 'widgets_init'));
add_action('wp_loaded', array(&$this, 'prepare_rewrite_rules'), 5); // after Polylang_base::add_post_types_taxonomies
// separate admin and frontend
if (is_admin() && isset($_GET['page']) && $_GET['page'] == 'mlang') {
require_once(PLL_INC.'/admin-base.php');
require_once(PLL_INC.'/admin.php');
$polylang = new Polylang_Admin();
}
elseif ($this->is_admin) {
require_once(PLL_INC.'/admin-base.php');
require_once(PLL_INC.'/admin-filters.php');
$polylang = new Polylang_Admin_Filters();
}
else {
require_once(PLL_INC.'/core.php');
$polylang = new Polylang_Core();
// auto translate posts and terms ids in term
if (!defined('PLL_AUTO_TRANSLATE') || PLL_AUTO_TRANSLATE)
require_once(PLL_INC.'/auto-translate.php');
}
// loads the API
require_once(PLL_INC.'/api.php');
// nav menus
require_once(PLL_INC.'/nav-menu.php');
// WPML API + wpml-config.xml
if (!defined('PLL_WPML_COMPAT') || PLL_WPML_COMPAT)
require_once(PLL_INC.'/wpml-compat.php');
// extra code for compatibility with some plugins
if (!defined('PLL_PLUGINS_COMPAT') || PLL_PLUGINS_COMPAT)
require_once(PLL_INC.'/plugins-compat.php');
}
// plugin activation for multisite
function activate() {
global $wp_version, $wpdb;
load_plugin_textdomain('polylang', false, basename(POLYLANG_DIR).'/languages'); // plugin i18n
if (version_compare($wp_version, PLL_MIN_WP_VERSION , '<'))
die (sprintf('<p style = "font-family: sans-serif; font-size: 12px; color: #333; margin: -5px">%s</p>',
sprintf(__('You are using WordPress %s. Polylang requires at least WordPress %s.', 'polylang'),
esc_html($wp_version),
PLL_MIN_WP_VERSION
)
));
// check if it is a network activation - if so, run the activation function for each blog
if (is_multisite() && isset($_GET['networkwide']) && ($_GET['networkwide'] == 1)) {
foreach ($wpdb->get_col("SELECT blog_id FROM $wpdb->blogs") as $blog_id) {
switch_to_blog($blog_id);
$this->_activate();
}
restore_current_blog();
}
else
$this->_activate();
}
// plugin activation
function _activate() {
// create the termmeta table - not provided by WP by default - if it does not already exists
// uses exactly the same model as other meta tables to be able to use access functions provided by WP
global $wpdb;
$charset_collate = empty($wpdb->charset) ? '' : "DEFAULT CHARACTER SET $wpdb->charset";
$charset_collate .= empty($wpdb->collate) ? '' : " COLLATE $wpdb->collate";
$table = $wpdb->prefix . 'termmeta';
$r = $wpdb->query("
CREATE TABLE IF NOT EXISTS $table (
meta_id bigint(20) unsigned NOT NULL auto_increment,
term_id bigint(20) unsigned NOT NULL default '0',
meta_key varchar(255) default NULL,
meta_value longtext,
PRIMARY KEY (meta_id),
KEY term_id (term_id),
KEY meta_key (meta_key)
) $charset_collate;");
if ($r === false)
die (sprintf(
'<p style = "font-family: sans-serif; font-size: 12px; color: #333; margin: -5px">%s</p>',
__('For some reasons, Polylang could not create a table in your database.', 'polylang')
));
// codex tells to use the init action to call register_taxonomy but I need it now for my rewrite rules
register_taxonomy('language', null , array('label' => false, 'query_var'=>'lang'));
// defines default values for options in case this is the first installation
if (!get_option('polylang')) {
$options['browser'] = 1; // default language for the front page is set by browser preference
$options['rewrite'] = 1; // remove /language/ in permalinks (was the opposite before 0.7.2)
$options['hide_default'] = 0; // do not remove URL language information for default language
$options['force_lang'] = 0; // do not add URL language information when useless
$options['redirect_lang'] = 0; // do not redirect the language page to the homepage
$options['media_support'] = 1; // support languages and translation for media by default
$options['sync'] = array_keys($this->list_metas_to_sync()); // synchronisation is enabled by default
$options['post_types'] = array_values(get_post_types(array('_builtin' => false, 'show_ui => true')));
$options['taxonomies'] = array_values(get_taxonomies(array('_builtin' => false, 'show_ui => true')));
$options['version'] = POLYLANG_VERSION;
update_option('polylang', $options);
}
// add our rewrite rules
$this->add_post_types_taxonomies();
$this->prepare_rewrite_rules();
flush_rewrite_rules();
}
// plugin deactivation for multisite
function deactivate() {
global $wpdb;
// check if it is a network deactivation - if so, run the deactivation function for each blog
if (is_multisite() && isset($_GET['networkwide']) && ($_GET['networkwide'] == 1)) {
foreach ($wpdb->get_col("SELECT blog_id FROM $wpdb->blogs") as $blog_id) {
switch_to_blog($blog_id);
$this->_deactivate();
}
restore_current_blog();
}
else
$this->_deactivate();
}
// plugin deactivation
function _deactivate() {
flush_rewrite_rules();
}
// blog creation on multisite
function wpmu_new_blog($blog_id) {
switch_to_blog($blog_id);
$r = $this->_activate();
restore_current_blog();
}
// displays a notice when ugrading from a too old version
function admin_notices() {
load_plugin_textdomain('polylang', false, basename(POLYLANG_DIR).'/languages');
printf(
'<div class="error"><p>%s</p><p>%s</p></div>',
__('Polylang has been deactivated because you upgraded from a too old version.', 'polylang'),
sprintf(
__('Please upgrade first to %s before ugrading to %s.', 'polylang'),
'<strong>0.9.8</strong>',
POLYLANG_VERSION
)
);
}
// manage upgrade even when it is done manually
function admin_init() {
if (version_compare($this->options['version'], POLYLANG_VERSION, '<')) {
if (version_compare($this->options['version'], '0.9', '<'))
$this->options['sync'] = defined('PLL_SYNC') && !PLL_SYNC ? 0 : 1; // the option replaces PLL_SYNC in 0.9
if (version_compare($this->options['version'], '1.0', '<')) {
// the option replaces PLL_MEDIA_SUPPORT in 1.0
$this->options['media_support'] = defined('PLL_MEDIA_SUPPORT') && !PLL_MEDIA_SUPPORT ? 0 : 1;
// split the synchronization options in 1.0
$this->options['sync'] = empty($this->options['sync']) ? array() : array_keys($this->list_metas_to_sync());
// set default values for post types and taxonomies to translate
$this->options['post_types'] = array_values(get_post_types(array('_builtin' => false, 'show_ui => true')));
$this->options['taxonomies'] = array_values(get_taxonomies(array('_builtin' => false, 'show_ui => true')));
flush_rewrite_rules(); // rewrite rules have been modified in 1.0
}
if (version_compare($this->options['version'], '1.0.2', '<'))
// set the option again in case it was not in 1.0
if (!isset($this->options['media_support']))
$this->options['media_support'] = defined('PLL_MEDIA_SUPPORT') && !PLL_MEDIA_SUPPORT ? 0 : 1;
if (version_compare($this->options['version'], '1.1', '<')) {
// update strings register with icl_register_string
$strings = get_option('polylang_wpml_strings');
if ($strings) {
foreach ($strings as $key => $string)
$strings[$key]['icl'] = 1;
update_option('polylang_wpml_strings', $strings);
}
// move polylang_widgets options
if ($widgets = get_option('polylang_widgets')) {
$this->options['widgets'] = $widgets;
delete_option('polylang_widgets');
}
// update nav menus
if ($menu_lang = get_option('polylang_nav_menus')) {
foreach ($menu_lang as $location => $arr) {
if (!in_array($location, array_keys(get_registered_nav_menus())))
continue;
$switch_options = array_slice($arr, -5, 5);
$translations = array_diff_key($arr, $switch_options);
$has_switcher = array_shift($switch_options);
foreach ($this->get_languages_list() as $lang) {
$menu_locations[$location.(pll_default_language() == $lang->slug ? '' : '#' . $lang->slug)] = empty($translations[$lang->slug]) ? 0 : $translations[$lang->slug];
// create the menu items
if (!empty($has_switcher)) {
$menu_item_db_id = wp_update_nav_menu_item($translations[$lang->slug], 0, array(
'menu-item-title' => __('Language switcher', 'polylang'),
'menu-item-url' => '#pll_switcher',
'menu-item-status' => 'publish'
));
update_post_meta($menu_item_db_id, '_pll_menu_item', $switch_options);
}
}
}
if (!empty($menu_locations))
set_theme_mod('nav_menu_locations', $menu_locations);
delete_option('polylang_nav_menus');
}
}
$this->options['version'] = POLYLANG_VERSION;
update_option('polylang', $this->options);
}
}
// some initialization
function init() {
global $wpdb;
$wpdb->termmeta = $wpdb->prefix . 'termmeta'; // registers the termmeta table in wpdb
if ($this->is_admin)
load_plugin_textdomain('polylang', false, basename(POLYLANG_DIR).'/languages'); // plugin i18n, only needed for backend
// registers the language taxonomy
// codex: use the init action to call this function
// object types will be set later once all custom post types are registered
register_taxonomy('language', null, array(
'labels' => array(
'name' => __('Languages', 'polylang'),
'singular_name' => __('Language', 'polylang'),
'all_items' => __('All languages', 'polylang'),
),
'public' => false, // avoid displaying the 'like post tags text box' in the quick edit
'query_var' => 'lang',
'update_count_callback' => '_update_post_term_count'
));
// optionaly removes 'language' in permalinks so that we get http://www.myblog/en/ instead of http://www.myblog/language/en/
// language information always in front of the uri ('with_front' => false)
// the 3rd parameter structure has been modified in WP 3.4
add_permastruct('language', $this->options['rewrite'] ? '%language%' : 'language/%language%',
version_compare($GLOBALS['wp_version'], '3.4' , '<') ? false : array('with_front' => false));
}
// registers our widgets
function widgets_init() {
require_once(PLL_INC.'/widget.php');
register_widget('Polylang_Widget');
// overwrites the calendar widget to filter posts by language
if (!defined('PLL_WIDGET_CALENDAR') || PLL_WIDGET_CALENDAR) {
require_once(PLL_INC.'/calendar.php'); // loads this only now otherwise it breaks widgets not registered in a widgets_init hook
unregister_widget('WP_Widget_Calendar');
register_widget('Polylang_Widget_Calendar');
}
}
// complete our taxonomy and add rewrite rules filters once custom post types and taxonomies are registered (normally in init)
function prepare_rewrite_rules() {
foreach ($this->post_types as $post_type)
register_taxonomy_for_object_type('language', $post_type);
// don't modify the rules if there is no languages created yet
if (!$this->get_languages_list())
return;
$types = array_values(array_merge($this->post_types, $this->taxonomies)); // supported post types and taxonomies
$types = array_merge(array('date', 'root', 'comments', 'search', 'author', 'language', 'post_format'), $types);
$types = apply_filters('pll_rewrite_rules', $types); // allow plugins to add rewrite rules to the language filter
foreach ($types as $type)
add_filter($type . '_rewrite_rules', array(&$this, 'rewrite_rules'));
add_filter('rewrite_rules_array', array(&$this, 'rewrite_rules')); // needed for post type archives
}
// the rewrite rules !
// always make sure the default language is at the end in case the language information is hidden for default language
// thanks to brbrbr http://wordpress.org/support/topic/plugin-polylang-rewrite-rules-not-correct
function rewrite_rules($rules) {
$filter = str_replace('_rewrite_rules', '', current_filter());
// suppress the rules created by WordPress for our taxonomy
if ($filter == 'language')
return array();
global $wp_rewrite;
$always_rewrite = in_array($filter, array('date', 'root', 'comments', 'author', 'post_format'));
$newrules = array();
foreach ($this->get_languages_list() as $language)
if (!$this->options['hide_default'] || $this->options['default_lang'] != $language->slug)
$languages[] = $language->slug;
if (isset($languages))
$slug = $wp_rewrite->root . ($this->options['rewrite'] ? '' : 'language/') . '('.implode('|', $languages).')/';
foreach ($rules as $key => $rule) {
// we don't need the lang parameter for post types and taxonomies
// moreover adding it would create issues for pages and taxonomies
if ($this->options['force_lang'] && in_array($filter, array_merge($this->post_types, $this->taxonomies))) {
if (isset($slug))
$newrules[$slug.str_replace($wp_rewrite->root, '', $key)] = str_replace(
array('[8]', '[7]', '[6]', '[5]', '[4]', '[3]', '[2]', '[1]'),
array('[9]', '[8]', '[7]', '[6]', '[5]', '[4]', '[3]', '[2]'),
$rule
); // hopefully it is sufficient!
if ($this->options['hide_default']) {
$newrules[$key] = $rules[$key];
// unset only if we hide the code for the default language as check_language_code_in_url will do its job in other cases
unset($rules[$key]);
}
}
// rewrite rules filtered by language
elseif ($always_rewrite || (strpos($rule, 'post_type=') && !strpos($rule, 'name=')) || ($filter != 'rewrite_rules_array' && $this->options['force_lang'])) {
if (isset($slug))
$newrules[$slug.str_replace($wp_rewrite->root, '', $key)] = str_replace(
array('[8]', '[7]', '[6]', '[5]', '[4]', '[3]', '[2]', '[1]', '?'),
array('[9]', '[8]', '[7]', '[6]', '[5]', '[4]', '[3]', '[2]', '?lang=$matches[1]&'),
$rule
); // should be enough!
if ($this->options['hide_default'])
$newrules[$key] = str_replace('?', '?lang='.$this->options['default_lang'].'&', $rule);
unset($rules[$key]); // now useless
}
}
// the home rewrite rule
if ($filter == 'root' && isset($slug))
$newrules[$slug.'?$'] = $wp_rewrite->index.'?lang=$matches[1]';
return $newrules + $rules;
}
} // class Polylang
new Polylang();