├── .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 ) );
--------------------------------------------------------------------------------