├── .github └── FUNDING.yml ├── README.md ├── assets └── customizer-polylang.gif ├── customizer-polylang.php └── js └── customizer-polylang.js /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: #soderlind 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | custom: https://paypal.me/PerSoderlind # Replace with a single custom sponsorship URL 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Add Polylang to WordPress Customizer 2 | 3 | ## (February 2019) Refactored by [Peder Andreas Nielsen](https://github.com/pederan) at [Dekode](https://en.dekode.no/?noredirect=en_US) 4 | 5 | 6 | 7 | This add-in gives you full [Polylang](https://wordpress.org/plugins/polylang/) support in WordPress customizer. By full support I mean that you customize each language site differently. 8 | 9 | For backward compatibility I've elected to not use customizer changesets (this time). 10 | 11 | ## Prerequisite 12 | 13 | 1. Polylang must be installed and activated. 14 | 1. Add languages in Admin->Languages. 15 | 1. If you have a static front page: 16 | 1. Create a front page per language. 17 | 1. In Admin->Settings-Reading, per language, select the front page. 18 | 1. Expect customizer to use setting type = theme_mod (the customizer default) as in: 19 | ```php 20 | $wp_customize->add_setting( 'setting_id', [ 21 | 'type' => 'theme_mod', // the default, you don't have to set this 22 | ] ); 23 | ``` 24 | 25 | ## Install 26 | 1. Clone or download this repository into your child theme root folder 27 | 1. In your child theme functions.php add customizer-polylang.php: 28 | 29 | `require_once get_stylesheet_directory() . '/customizer-polylang.php';` 30 | 31 | # Credits 32 | 33 | I got the idea from the [customizer-export-import](https://github.com/fastlinemedia/customizer-export-import) plugin. 34 | 35 | I did this during work hours at the [Norwegian Government Security and Service Organisation](https://dss.dep.no/english) (DSS). We at DSS believe in sharing code. 36 | 37 | # Copyright and License 38 | 39 | customizer-polylang.php and js/customizer-polylang.js is copyright 2017 Per Soderlind 40 | 41 | customizer-polylang.php and js/customizer-polylang.js 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. 42 | 43 | customizer-polylang.php and js/customizer-polylang.js 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. 44 | 45 | You should have received a copy of the GNU Lesser General Public License along with the Extension. If not, see http://www.gnu.org/licenses/. 46 | -------------------------------------------------------------------------------- /assets/customizer-polylang.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soderlind/customizer-polylang/853f8b52014a9a0d6eeba6f9bd3455e98b72ad82/assets/customizer-polylang.gif -------------------------------------------------------------------------------- /customizer-polylang.php: -------------------------------------------------------------------------------- 1 | current_lang_not_default() ) { 132 | $data = $this->get_custom_customizer_option(); 133 | 134 | // Found the custom option. Move on. 135 | if ( is_array( $data ) && isset( $data['options'] ) && isset( $data['options'][ $option ] ) ) { 136 | return $data['options'][ $option ]; 137 | } 138 | } 139 | 140 | return $default; 141 | } 142 | 143 | /** 144 | * Update the custom db field on get_option hook. 145 | * If the current language is not default, then return old value to prevent from saving to default wp option. 146 | * 147 | * @param mixed $value The new, unserialized option value. 148 | * @param mixed $old_value The old option value. 149 | * @param string $option Option name. 150 | * 151 | * @return mixed 152 | */ 153 | public function on_wp_option_update( $value, $old_value, $option ) { 154 | // Fetch custom option db field. 155 | $data = $this->get_custom_customizer_option(); 156 | $theme_slug = get_option( 'template' ); 157 | // If false, the field hasn't been created yet, so it must be created. 158 | if ( false === $data ) { 159 | $data = [ 160 | 'template' => $theme_slug, 161 | 'mods' => [], 162 | 'options' => [], 163 | ]; 164 | } 165 | 166 | // Make sure the options array exists. We are going to use it soon. 167 | if ( ! isset( $data['options'] ) ) { 168 | $data['options'] = []; 169 | } 170 | 171 | $data['options'][ $option ] = $value; 172 | 173 | // Update option value in custom db field. (Not necessary to save for default language since it uses default wp option fields for values when get option). 174 | $this->update_custom_customizer_option( $data ); 175 | 176 | // If the current language is not the default language, prevent saving to option table by passing the old value back. It will then exit after the filter. 177 | if ( $this->current_lang_not_default() ) { 178 | return $old_value; 179 | } 180 | 181 | return $value; 182 | } 183 | 184 | /** 185 | * Check the custom db field on get_option customizer field option name hook to be able to return custom language value. 186 | * Parse arguments with default wp customizer values to make sure all are present in the return. 187 | * 188 | * @param array $value The customizer settings. 189 | * 190 | * @return array 191 | */ 192 | public function on_option_theme_mods_get( $value ) { 193 | $data = $this->get_custom_customizer_option(); 194 | 195 | if ( isset( $data['mods'] ) && is_array( $data['mods'] ) && ! empty( $data['mods'] ) ) { 196 | $value = wp_parse_args( $data['mods'], $value ); 197 | } 198 | 199 | // Remove members with integer key from mods. 200 | foreach ( $value as $key => $mod ) { 201 | if ( is_numeric( $key ) ) { 202 | unset( $value[ $key ] ); 203 | } 204 | } 205 | 206 | return $value; 207 | } 208 | 209 | /** 210 | * Update custom customizer option. 211 | * If the current language is not default, then return old value to prevent from saving to customizer wp option. 212 | * 213 | * @param mixed $value The new, unserialized option value. 214 | * @param mixed $old_value The old option value. 215 | */ 216 | public function on_option_theme_mods_update( $value, $old_value ) { 217 | 218 | $current_data = $this->get_custom_customizer_option(); 219 | $theme_slug = get_option( 'template' ); 220 | 221 | $data = [ 222 | 'template' => $theme_slug, 223 | 'mods' => isset( $current_data['mods'] ) ? $current_data['mods'] : [], 224 | 'options' => isset( $current_data['options'] ) ? $current_data['options'] : [], 225 | ]; 226 | 227 | if ( is_array( $value ) && ! empty( $value ) ) { 228 | foreach ( $value as $key => $val ) { 229 | $data['mods'][ $key ] = $val; 230 | } 231 | } 232 | $this->update_custom_customizer_option( $data ); 233 | 234 | if ( $this->current_lang_not_default() ) { 235 | return $old_value; 236 | } 237 | 238 | return $value; 239 | } 240 | 241 | /** 242 | * If Polylang activated, set the preview url and add select language control 243 | * 244 | * @author soderlind 245 | * @version 1.0.0 246 | * @link https://gist.github.com/soderlind/1908634f5eb0c1f69428666dd2a291d0 247 | */ 248 | public function add_lang_to_customizer_previewer() { 249 | 250 | $handle = 'dss-add-lang-to-template'; 251 | $src = get_stylesheet_directory_uri() . '/js/customizer-polylang.js'; 252 | $deps = [ 'customize-controls' ]; 253 | $version = rand(); 254 | $in_footer = 1; 255 | wp_enqueue_script( $handle, $src, $deps, $version, $in_footer ); 256 | $language = ( empty( $_REQUEST['lang'] ) ) ? pll_current_language() : $_REQUEST['lang']; 257 | 258 | if ( empty( $language ) ) { 259 | $language = pll_default_language(); 260 | } 261 | 262 | if ( ! empty( $_REQUEST['url'] ) ) { 263 | $current_url = add_query_arg( 'lang', $language, $_REQUEST['url'] ); 264 | } else { 265 | $current_url = add_query_arg( 'lang', $language, pll_home_url( $language ) ); 266 | } 267 | wp_add_inline_script( 268 | $handle, 269 | sprintf( 270 | 'PSPolyLang.init( %s );', 271 | wp_json_encode( 272 | [ 273 | 'url' => $current_url, 274 | 'languages' => get_transient( 'pll_languages_list' ), 275 | 'current_language' => $language, 276 | ] 277 | ) 278 | ), 279 | 'after' 280 | ); 281 | } 282 | 283 | /** 284 | * Append lang="contrycode" to the customizer url in the adminbar 285 | * 286 | * @return void 287 | */ 288 | public function on_wp_before_admin_bar_render() { 289 | global $wp_admin_bar; 290 | $customize_node = $wp_admin_bar->get_node( 'customize' ); 291 | if ( ! empty( $customize_node ) ) { 292 | $customize_node->href = add_query_arg( 'lang', pll_current_language(), $customize_node->href ); 293 | $wp_admin_bar->add_node( $customize_node ); 294 | } 295 | } 296 | 297 | /** 298 | * Append lang="contrycode" to the customizer url in the Admin->Apperance->Customize menu 299 | * 300 | * @return void 301 | */ 302 | public function on_admin_menu() { 303 | global $submenu; 304 | $parent = 'themes.php'; 305 | if ( ! isset( $submenu[ $parent ] ) ) { 306 | return; 307 | } 308 | foreach ( $submenu[ $parent ] as $k => $d ) { 309 | if ( 'customize' === $d['1'] ) { 310 | $submenu[ $parent ][ $k ]['2'] = add_query_arg( 'lang', pll_current_language(), $submenu[ $parent ][ $k ]['2'] ); 311 | break; 312 | } 313 | } 314 | } 315 | 316 | } 317 | 318 | if ( class_exists( 'WP_Customize_Setting' ) ) { 319 | /** 320 | * A class that extends WP_Customize_Setting so we can access 321 | * the protected updated method when importing options. 322 | * 323 | * @since 0.3 324 | */ 325 | final class CustomizerPolylangOption extends \WP_Customize_Setting { 326 | 327 | 328 | /** 329 | * Import an option value for this setting. 330 | * 331 | * @since 0.3 332 | * 333 | * @param mixed $value The option value. 334 | * 335 | * @return void 336 | */ 337 | public function import( $value ) { 338 | $this->update( $value ); 339 | } 340 | } 341 | } 342 | 343 | /** 344 | * Polylang register strings. 345 | */ 346 | 347 | if ( function_exists( 'pll_register_string' ) ) { 348 | 349 | /** 350 | * Register fields for Polylang string translations 351 | * 352 | * @param string $option_name Option name 353 | * @param array $fields Field names 354 | * 355 | * @return void 356 | */ 357 | function register_polylang_column_strings( $option_name, $fields ) { 358 | $columns = get_option( $option_name ); 359 | $theme_name = wp_get_theme()->get( 'Name' ); 360 | if ( ! empty( $columns ) ) : 361 | for ( $i = 0; $i < $columns; $i ++ ) : 362 | foreach ( $fields as $field ) { 363 | pll_register_string( $option_name . '_' . $i . '_' . $field, get_option( $option_name . '_' . $i . '_' . $field ), $theme_name, true ); 364 | } 365 | endfor; 366 | endif; 367 | } 368 | 369 | register_polylang_column_strings( 'options_footer_columns', [ 'title', 'text' ] ); 370 | register_polylang_column_strings( 'options_footer_logos', [ 'image', 'url' ] ); 371 | } 372 | -------------------------------------------------------------------------------- /js/customizer-polylang.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | /* global wp, jQuery */ 5 | /* exported PluginCustomizer */ 6 | var PSPolyLang = (function( api, $ ) { 7 | 'use strict'; 8 | 9 | var component = { 10 | data: { 11 | url: null, 12 | languages: null, 13 | current_language: null, 14 | } 15 | }; 16 | 17 | /** 18 | * Initialize functionality. 19 | * 20 | * @param {object} args Args. 21 | * @param {string} args.url Preview URL. 22 | * @returns {void} 23 | */ 24 | component.init = function init( pll ) { 25 | _.extend(component.data, pll ); 26 | if (!pll || !pll.url || !pll.languages || !pll.current_language ) { 27 | throw new Error( 'Missing args' ); 28 | } 29 | 30 | api.bind( 'ready', function(){ 31 | api.previewer.previewUrl.set( pll.url ); 32 | 33 | var languages = pll.languages; 34 | var current_language = pll.current_language; 35 | var current_language_name = ''; 36 | 37 | var html = 'Language: '; 38 | html += ''; 46 | $(html).prependTo('#customize-header-actions'); 47 | 48 | 49 | $('body').on('change', '#pll-language-select', function () { 50 | var language = $(this).val(); 51 | var old_url = window.location.href; 52 | window.location.href = updateQueryStringParameter(window.location.href, 'lang', language); 53 | }); 54 | }); 55 | 56 | function updateQueryStringParameter(uri, key, value) { 57 | var re = new RegExp("([?&])" + key + "=.*?(&|$)", "i"); 58 | var separator = uri.indexOf('?') !== -1 ? "&" : "?"; 59 | if (uri.match(re)) { 60 | return uri.replace(re, '$1' + key + "=" + value + '$2'); 61 | } else { 62 | return uri + separator + key + "=" + value; 63 | } 64 | } 65 | }; 66 | 67 | return component; 68 | } ( wp.customize, jQuery ) ); --------------------------------------------------------------------------------