├── index.php ├── .exclude-list ├── assets ├── readme.txt ├── woo-poly-features.gif ├── woo-poly-metas1.gif ├── woo-poly-metas2.gif ├── product-attributes.gif ├── product-categories.gif ├── polylang-settings-sync.gif ├── polylang-settings-tax.gif └── strings-translations-tax.gif ├── languages ├── woo-poly-integration-ar.mo └── woo-poly-integration-es_ES.mo ├── src └── Hyyan │ └── WPI │ ├── Views │ ├── Admin │ │ ├── main.php │ │ ├── support.php │ │ ├── getHelp.php │ │ └── about.php │ ├── Messages │ │ ├── endpointsTranslations.php │ │ ├── activateError.php │ │ └── support.php │ ├── badges.php │ ├── admin.php │ └── social.php │ ├── Taxonomies │ ├── Tags.php │ ├── ShippingCalss.php │ ├── TaxonomiesInterface.php │ ├── Categories.php │ ├── Attributes.php │ └── Taxonomies.php │ ├── MessagesInterface.php │ ├── Admin │ ├── SettingsInterface.php │ ├── StatusReport.php │ ├── AbstractSettings.php │ ├── MetasList.php │ ├── Settings.php │ └── Features.php │ ├── Login.php │ ├── Ajax.php │ ├── Autoloader.php │ ├── Media.php │ ├── Gateways │ ├── GatewayCOD.php │ ├── GatewayCheque.php │ └── GatewayBACS.php │ ├── Permalinks.php │ ├── Widgets │ ├── LayeredNav.php │ └── SearchWidget.php │ ├── Product │ ├── Duplicator.php │ ├── Stock.php │ └── Product.php │ ├── Language.php │ ├── Breadcrumb.php │ ├── Privacy.php │ ├── Tools │ ├── FlashMessages.php │ └── TranslationsDownloader.php │ ├── Tax.php │ ├── Order.php │ ├── LocaleNumbers.php │ ├── Pages.php │ ├── Shipping.php │ ├── Endpoints.php │ ├── Gateways.php │ ├── HooksInterface.php │ ├── Reports.php │ └── Cart.php ├── .travis.yml ├── .gitattributes ├── LICENSE ├── public └── js │ ├── Variables.min.js │ ├── Cart.min.js │ ├── Variables.js │ └── Cart.js ├── __init__.php ├── deploy.sh └── README.md /index.php: -------------------------------------------------------------------------------- 1 | 6 | 7 | show_navigation(); ?> 8 |
9 | show_forms(); ?> 10 |
11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | install: true 2 | language: php 3 | dist: precise 4 | sudo: true 5 | 6 | php: 7 | - 7.0 8 | 9 | before_script: 10 | - sudo apt-get install subversion 11 | 12 | script: echo "skip sript" 13 | deploy: 14 | provider: script 15 | skip_cleanup: true 16 | script: chmod +x deploy.sh && sh deploy.sh 17 | on: 18 | branch: master 19 | php: 7.0 20 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text eol=lf 2 | 3 | *.jpg binary 4 | *.png binary 5 | 6 | /.github export-ignore 7 | /.gitignore export-ignore 8 | /composer.json export-ignore 9 | /nbproject export-ignore 10 | /screenshot-1.png export-ignore 11 | /screenshot-2.png export-ignore 12 | /screenshot-3.png export-ignore 13 | /screenshot-4.png export-ignore 14 | /screenshot-5.png export-ignore 15 | /screenshot-6.png export-ignore 16 | -------------------------------------------------------------------------------- /src/Hyyan/WPI/Views/Messages/endpointsTranslations.php: -------------------------------------------------------------------------------- 1 | 7 | 8 | %s', 'woo-poly-integration'), 12 | add_query_arg( 13 | array( 14 | 'page' => 'mlang_strings', 15 | 'group' => \Hyyan\WPI\Endpoints::getPolylangStringSection(), 16 | ), 17 | admin_url( 'admin.php' ) 18 | ), 19 | __( 'Translate', 'woo-poly-integration' ) 20 | ) 21 | ?> 22 | -------------------------------------------------------------------------------- /src/Hyyan/WPI/Views/badges.php: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/Hyyan/WPI/Taxonomies/Tags.php: -------------------------------------------------------------------------------- 1 | . 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Hyyan\WPI\Taxonomies; 12 | 13 | /** 14 | * Tags. 15 | * 16 | * @author Hyyan Abo Fakher 17 | */ 18 | class Tags implements TaxonomiesInterface 19 | { 20 | /** 21 | * {@inheritdoc} 22 | */ 23 | public static function getNames() 24 | { 25 | return array('product_tag'); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Hyyan/WPI/Taxonomies/ShippingCalss.php: -------------------------------------------------------------------------------- 1 | . 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Hyyan\WPI\Taxonomies; 12 | 13 | /** 14 | * ShippingCalss. 15 | * 16 | * @author Hyyan Abo Fakher 17 | */ 18 | class ShippingCalss implements TaxonomiesInterface 19 | { 20 | /** 21 | * {@inheritdoc} 22 | */ 23 | public static function getNames() 24 | { 25 | return array('product_shipping_class'); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Hyyan/WPI/Taxonomies/TaxonomiesInterface.php: -------------------------------------------------------------------------------- 1 | . 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Hyyan\WPI\Taxonomies; 12 | 13 | /** 14 | * TaxonomiesInterface. 15 | * 16 | * @author Hyyan Abo Fakher 17 | */ 18 | interface TaxonomiesInterface 19 | { 20 | /** 21 | * Get array of taxonomies names. 22 | * 23 | * @return array array of taxonmies names to manage by polylang 24 | */ 25 | public static function getNames(); 26 | } 27 | -------------------------------------------------------------------------------- /src/Hyyan/WPI/MessagesInterface.php: -------------------------------------------------------------------------------- 1 | . 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Hyyan\WPI; 12 | 13 | /** 14 | * Messages Interface. 15 | * 16 | * Constains messages IDS used in this plugin 17 | * 18 | * @author Hyyan Abo Fakher 19 | */ 20 | interface MessagesInterface 21 | { 22 | /** 23 | * Support message. 24 | */ 25 | const MSG_SUPPORT = 'wpi-support'; 26 | 27 | /** 28 | * Activate Error Message. 29 | */ 30 | const MSG_ACTIVATE_ERROR = 'wpi-activate-error'; 31 | 32 | /** 33 | * Endpoints translations message. 34 | */ 35 | const MSG_ENDPOINTS_TRANSLATION = 'wpi-endpoints-translations'; 36 | } 37 | -------------------------------------------------------------------------------- /src/Hyyan/WPI/Views/Admin/support.php: -------------------------------------------------------------------------------- 1 | 6 |

7 | 8 | 9 | 10 |

11 |
12 | I will never ask you for donation, now or in the future, 14 | but the plugin still needs your support. 15 | please support by rating this plugin on 16 | Wordpress Repository, 17 | or by giving the plugin a star on Github. 18 |

19 | If you speak a language other than English, 20 | you can support the plugin by extending the 21 | translation list and your name will be added 22 | to the translators list', 'woo-poly-integration' 23 | ); 24 | ?> 25 |

26 | 27 |
28 | -------------------------------------------------------------------------------- /src/Hyyan/WPI/Admin/SettingsInterface.php: -------------------------------------------------------------------------------- 1 | . 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Hyyan\WPI\Admin; 12 | 13 | /** 14 | * SettingsInterface. 15 | * 16 | * @author Hyyan Abo Fakher 17 | */ 18 | interface SettingsInterface 19 | { 20 | /** 21 | * Get sections array. 22 | * 23 | * @param array $sections current registered sections 24 | * 25 | * @return array array of sections 26 | */ 27 | public function getSections(array $sections); 28 | 29 | /** 30 | * Get fields array. 31 | * 32 | * @param array $fields current registered fields 33 | * 34 | * @return array 35 | */ 36 | public function getFields(array $fields); 37 | 38 | /** 39 | * Get settings ID. 40 | * 41 | * @return string setting ID 42 | */ 43 | public static function getID(); 44 | } 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Hyyan Abo Fakher 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /src/Hyyan/WPI/Views/Admin/getHelp.php: -------------------------------------------------------------------------------- 1 | 6 |

8 |

9 | 10 |
11 |

12 | 17 |

18 |
    19 |
  1. 20 | 21 | 22 | 23 |
  2. 24 | 29 |
  3. 30 | 31 | 32 | 33 |
  4. 34 |
35 |
36 | -------------------------------------------------------------------------------- /src/Hyyan/WPI/Views/Admin/about.php: -------------------------------------------------------------------------------- 1 | 6 |

7 | 8 | 9 | 10 |

11 |
12 |
13 | WooCommerce 17 | and Polylang', 'woo-poly-integration' 18 | ); 19 | echo('. '); 20 | _e('For more information please see:', 'woo-poly-integration'); 21 | echo(' '); 22 | _e('documentation pages', 'woo-poly-integration'); 23 | echo('.'); 24 | ?> 25 |

26 | 27 |

28 | 29 | Hyyan Abo Fakher 30 |

31 | 32 | 33 | 34 |
35 |
36 | -------------------------------------------------------------------------------- /public/js/Variables.min.js: -------------------------------------------------------------------------------- 1 | !function(i,t,e){function n(t,i,e){this.$=t,this.document=i,this.data=e,this._dialog=this._createDialog()}n.prototype={constructor:n,init:function(){var t=this;i("#post_lang_choice").on("change",function(){t.shouldAlert()}),i("#product-type").on("change",function(){t.shouldAlert()})},shouldAlert:function(){var t=this.getCurrentProduct(),i=this.getCurrentLanguage();return"variable"===t&&i!==this.data.defaultLang?(this._dialog.dialog("open"),this._changeSaveBoxState(!0),!0):(this._changeSaveBoxState(!1),!1)},getCurrentLanguage:function(){return this.$("#post_lang_choice").val()},getCurrentProduct:function(){return this.$("#product-type").val()},_changeSaveBoxState:function(t){this.$("#submitdiv *").prop("disabled",t)},_createDialog:function(){this.$("").appendTo(i("head"));var t=this.$("
").html("

"+this.data.content+"

").attr("title",this.data.title).appendTo("body");return t.dialog({dialogClass:"wp-dialog",autoOpen:!1,modal:!0,width:400,height:250,position:{my:"center",at:"center"},buttons:[{text:"Got It",click:function(){i(this).dialog("close")}}]}),t}},i(function(){new n(i,t,e).init()})}(jQuery,document,HYYAN_WPI_VARIABLES); 2 | -------------------------------------------------------------------------------- /src/Hyyan/WPI/Login.php: -------------------------------------------------------------------------------- 1 | . 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Hyyan\WPI; 12 | 13 | /** 14 | * Login. 15 | * 16 | * Handle login 17 | * 18 | * @author Hyyan Abo Fakher 19 | */ 20 | class Login 21 | { 22 | 23 | /** 24 | * Construct object. 25 | */ 26 | public function __construct() 27 | { 28 | add_filter( 29 | 'woocommerce_login_redirect', array($this, 'getLoginRedirectPermalink'), 10, 2 30 | ); 31 | } 32 | 33 | /** 34 | * Find the correct login redirect permalink. 35 | * 36 | * @param string $to redirect url 37 | * 38 | * @return string redirect url 39 | */ 40 | public function getLoginRedirectPermalink($to) 41 | { 42 | $ID = url_to_postid($to); 43 | $translatedID = pll_get_post($ID); 44 | 45 | if ($translatedID) { 46 | return get_permalink($translatedID); 47 | } 48 | 49 | return $to; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Hyyan/WPI/Views/Messages/activateError.php: -------------------------------------------------------------------------------- 1 | 6 |

7 | 10 |

11 |

12 | '); 19 | _e('Installation Guide', 'woo-poly-integration'); 20 | echo('.'); 21 | ?> 22 |

23 |


24 | 25 | 26 | 27 | 28 | | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/Hyyan/WPI/Views/admin.php: -------------------------------------------------------------------------------- 1 | 6 |
7 |

8 |
9 |
10 | 11 | 12 |
13 | 14 |
15 | 16 | 17 |
18 | 19 | 20 |
21 | 22 |
23 | 24 | 25 |
26 | 27 |
28 | 29 | 30 |
31 | 32 |
33 |
34 |
35 |
36 |
37 | -------------------------------------------------------------------------------- /src/Hyyan/WPI/Ajax.php: -------------------------------------------------------------------------------- 1 | . 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Hyyan\WPI; 12 | 13 | /** 14 | * Ajax. 15 | * 16 | * Handle Ajax 17 | * 18 | * @author Marian Kadanka 19 | */ 20 | class Ajax 21 | { 22 | 23 | /** 24 | * Construct object. 25 | */ 26 | public function __construct() 27 | { 28 | add_filter('woocommerce_ajax_get_endpoint', array($this, 'filter_woocommerce_ajax_get_endpoint'), 10, 2); 29 | } 30 | 31 | /** 32 | * Filter woocommerce_ajax_get_endpoint URL - replace the path part 33 | * with the correct relative home URL according to the current language 34 | * and append the query string 35 | * 36 | * @param string $url WC AJAX endpoint URL to filter 37 | * @param string $request 38 | * 39 | * @return string filtered WC AJAX endpoint URL 40 | */ 41 | public function filter_woocommerce_ajax_get_endpoint($url, $request) 42 | { 43 | global $polylang; 44 | $lang = ( $polylang->curlang ) ? $polylang->curlang : $polylang->pref_lang; 45 | return parse_url($polylang->filters_links->links->get_home_url($lang), PHP_URL_PATH) . '?' . parse_url($url, PHP_URL_QUERY); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /__init__.php: -------------------------------------------------------------------------------- 1 | . 24 | * 25 | * For the full copyright and license information, please view the LICENSE 26 | * file that was distributed with this source code. 27 | */ 28 | if (!defined('ABSPATH')) { 29 | exit('restricted access'); 30 | } 31 | 32 | define('Hyyan_WPI_DIR', __FILE__); 33 | define('Hyyan_WPI_URL', plugin_dir_url(__FILE__)); 34 | 35 | require_once ABSPATH . 'wp-admin/includes/plugin.php'; 36 | require_once __DIR__ . '/vendor/class.settings-api.php'; 37 | require_once __DIR__ . '/src/Hyyan/WPI/Autoloader.php'; 38 | 39 | /* register the autoloader */ 40 | new Hyyan\WPI\Autoloader(__DIR__ . '/src/'); 41 | 42 | /* bootstrap the plugin */ 43 | new Hyyan\WPI\Plugin(); 44 | 45 | 46 | /* 47 | * called when plugin is activated in settings, plugins 48 | */ 49 | function onActivate() { 50 | update_option( 'wpi_wcpagecheck_passed', false ); 51 | update_option( 'hyyan-wpi-flash-messages', '' ); 52 | } 53 | 54 | register_activation_hook( __FILE__, 'onActivate' ); 55 | 56 | -------------------------------------------------------------------------------- /src/Hyyan/WPI/Views/Messages/support.php: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 12 | 36 | 37 |
9 | 11 | 13 |

14 | 19 |

20 |

21 |

22 | Hyyan Abo Fakher, and I am the developer 24 | of plugin Hyyan WooCommerce Polylang Integration.
25 | If you like this plugin, please write a few words about it 26 | at the wordpress.org 27 | or twitter 28 | It will help other people 29 | find this useful plugin more quickly.
Thank you!', 'woo-poly-integration' 30 | ); 31 | ?> 32 |

33 |


34 | 35 |
38 | -------------------------------------------------------------------------------- /src/Hyyan/WPI/Autoloader.php: -------------------------------------------------------------------------------- 1 | . 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Hyyan\WPI; 12 | 13 | /** 14 | * Plugin Namespace Autoloader. 15 | * 16 | * @author Hyyan Abo Fakher 17 | */ 18 | final class Autoloader 19 | { 20 | /** 21 | * @var string 22 | */ 23 | private $base; 24 | 25 | /** 26 | * Construct the autoloader class. 27 | * 28 | * @param string $base the base path to use 29 | * 30 | * @throws \Exception when the autloader can not register itself 31 | */ 32 | public function __construct($base) 33 | { 34 | $this->base = $base; 35 | spl_autoload_register(array($this, 'handle'), true, true); 36 | } 37 | 38 | /** 39 | * Handle autoloading. 40 | * 41 | * @param string $className class or inteface name 42 | * 43 | * @return bool true if class or interface exists , false otherwise 44 | */ 45 | public function handle($className) 46 | { 47 | if (stripos($className, "Hyyan\WPI") === false) { 48 | return; 49 | } 50 | 51 | $filename = $this->base.str_replace('\\', '/', $className).'.php'; 52 | if (file_exists($filename)) { 53 | require_once $filename; 54 | if ( 55 | class_exists($className) || 56 | interface_exists($className) 57 | ) { 58 | return true; 59 | } 60 | } 61 | 62 | return false; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | rm -rf build 3 | 4 | # prepare files to upload 5 | rsync -avh . ./build --exclude-from '.exclude-list' --delete 6 | 7 | # change to build 8 | cd build 9 | 10 | # Checkout the SVN repo 11 | svn co -q "http://svn.wp-plugins.org/woo-poly-integration" svn 12 | 13 | # Move out the trunk directory to a temp location 14 | #mv ./svn/trunk ./svn-trunk 15 | 16 | # Create trunk directory 17 | mkdir -p svn/trunk 18 | 19 | # Copy our new version of the plugin into trunk 20 | rsync -r -p ./* svn/trunk 21 | 22 | # # Copy all the .svn folders from the checked out copy of trunk to the new trunk. 23 | # # This is necessary as the Travis container runs Subversion 1.6 which has .svn dirs in every sub dir 24 | # cd svn/trunk/ 25 | # TARGET=$(pwd) 26 | # cd ../../svn-trunk/ 27 | 28 | # # Find all .svn dirs in sub dirs 29 | # SVN_DIRS=`find . -type d -iname .svn` 30 | 31 | # for SVN_DIR in $SVN_DIRS; do 32 | # SOURCE_DIR=${SVN_DIR/.} 33 | # TARGET_DIR=$TARGET${SOURCE_DIR/.svn} 34 | # TARGET_SVN_DIR=$TARGET${SVN_DIR/.} 35 | # if [ -d "$TARGET_DIR" ]; then 36 | # # Copy the .svn directory to trunk dir 37 | # cp -r $SVN_DIR $TARGET_SVN_DIR 38 | # fi 39 | # done 40 | 41 | # # Back to builds dir 42 | # cd ../ 43 | 44 | # Remove checked out dir 45 | #rm -fR svn-trunk 46 | 47 | # Add new version tag 48 | #mkdir -p ./svn/tags/$TRAVIS_TAG 49 | #rsync -r -p . svn/tags/$TRAVIS_TAG 50 | 51 | # Add new files to SVN 52 | svn stat svn | grep '^?' | awk '{print $2}' | xargs -I x svn add x@ 53 | # Remove deleted files from SVN 54 | svn stat svn | grep '^!' | awk '{print $2}' | xargs -I x svn rm --force x@ 55 | svn stat svn 56 | 57 | # Commit to SVN 58 | svn ci --no-auth-cache --username hyyan --password $WP_ORG_PASSWORD svn -m "build version $TRAVIS_TAG" 59 | 60 | -------------------------------------------------------------------------------- /src/Hyyan/WPI/Media.php: -------------------------------------------------------------------------------- 1 | . 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Hyyan\WPI; 12 | 13 | /** 14 | * Media. 15 | * 16 | * Handle products media translation 17 | * 18 | * @author Hyyan Abo Fakher 19 | */ 20 | class Media 21 | { 22 | 23 | /** 24 | * Construct object. 25 | */ 26 | public function __construct() 27 | { 28 | if (static::isMediaTranslationEnabled()) { 29 | add_filter( 30 | 'woocommerce_product_get_gallery_image_ids', 31 | array($this, 'translateGallery') 32 | ); 33 | } 34 | } 35 | 36 | /** 37 | * Check if media translation is enable in polylang settings. 38 | * 39 | * @return bool true if enabled , false otherwise 40 | */ 41 | public static function isMediaTranslationEnabled() 42 | { 43 | $options = get_option('polylang'); 44 | 45 | return $options['media_support']; 46 | } 47 | 48 | /** 49 | * Translate product gallery. 50 | * 51 | * @param array $IDS current attachment IDS 52 | * 53 | * @return array translated attachment IDS 54 | */ 55 | public function translateGallery(array $IDS) 56 | { 57 | $translations = array(); 58 | foreach ($IDS as $ID) { 59 | $tr = pll_get_post($ID); 60 | if ($tr) { 61 | $translations [] = $tr; 62 | continue; 63 | } 64 | $translations [] = $ID; 65 | } 66 | 67 | return $translations; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/Hyyan/WPI/Views/social.php: -------------------------------------------------------------------------------- 1 | 8 | 9 | 20 | 21 | 32 |
33 |
34 | 35 |
36 |
37 |
38 |
39 |
40 |
41 | 42 |
43 |
44 |
45 | -------------------------------------------------------------------------------- /src/Hyyan/WPI/Gateways/GatewayCOD.php: -------------------------------------------------------------------------------- 1 | . 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Hyyan\WPI\Gateways; 12 | 13 | use Hyyan\WPI\Utilities; 14 | 15 | /** 16 | * Gateways Cash on Delivery. 17 | * 18 | * Handle Payment Gateways Cash on Delivery 19 | * 20 | * @author Antonio de Carvalho 21 | */ 22 | class GatewayCOD extends \WC_Gateway_COD 23 | { 24 | /** 25 | * Output for the order received page. 26 | * 27 | * Note 1: The difference to WC_Gateway_COD is that we use pll__() or __() 28 | * before passing the string through wptexturize() and wpautop(). 29 | * 30 | * Note 2: We wrap the intructions strings with wp_kses_post() to sanitize 31 | * content for allowed HTML tags like BACS does it. 32 | */ 33 | public function thankyou_page() 34 | { 35 | if ($this->instructions) { 36 | echo wpautop(wptexturize(wp_kses_post(function_exists('pll__') ? pll__($this->instructions) : __($this->instructions, 'woocommerce')))); 37 | } 38 | } 39 | 40 | /** 41 | * Add content to the WC emails. 42 | * 43 | * Note: The difference from WC_Gateway_COD is that we use __() before 44 | * passing the string through wptexturize() and wpautop(). 45 | * 46 | * @param WC_Order $order 47 | * @param bool $sent_to_admin 48 | * @param bool $plain_text 49 | */ 50 | public function email_instructions($order, $sent_to_admin, $plain_text = false) 51 | { 52 | if ($this->instructions && !$sent_to_admin && 'cod' === Utilities::get_payment_method($order)) { 53 | echo wpautop(wptexturize(function_exists('pll__') ? pll__($this->instructions) : __($this->instructions, 'woocommerce'))).PHP_EOL; 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Hyyan/WPI/Permalinks.php: -------------------------------------------------------------------------------- 1 | . 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Hyyan\WPI; 12 | 13 | /** 14 | * Permalinks. 15 | * 16 | * @author Hyyan Abo Fakher 17 | */ 18 | class Permalinks 19 | { 20 | const PRODUCT_BASE = '/product'; 21 | const PRODUCT_CATEGORY_BASE = 'product-category'; 22 | const PRODUCT_TAG_BASE = 'product-tags'; 23 | 24 | /** 25 | * Construct object. 26 | */ 27 | public function __construct() 28 | { 29 | add_action('init', array($this, 'setDefaultPermalinks'), 11); 30 | } 31 | 32 | /** 33 | * Set default permalinks. 34 | * 35 | * Setup the right permalinks to work with polylang if used permalinks is the 36 | * default woocommerce permalinks 37 | * (This was getting called too often, also a situation occurred where these got set to boolean...) 38 | */ 39 | public function setDefaultPermalinks() 40 | { 41 | $permalinks = get_option('woocommerce_permalinks'); 42 | $was_set=false; 43 | if (! isset($permalinks['category_base']) || (is_bool($permalinks['category_base']))) { 44 | $permalinks['category_base'] = self::PRODUCT_CATEGORY_BASE; 45 | $was_set=true; 46 | } 47 | if (! isset($permalinks['tag_base']) || (is_bool($permalinks['tag_base']))) { 48 | $permalinks['tag_base'] = self::PRODUCT_TAG_BASE; 49 | $was_set=true; 50 | } 51 | if (! isset($permalinks['product_base']) || (is_bool($permalinks['product_base']))) { 52 | $permalinks['product_base'] = self::PRODUCT_BASE; 53 | $was_set=true; 54 | } 55 | 56 | if ($was_set) { 57 | update_option('woocommerce_permalinks', $permalinks); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Hyyan/WPI/Gateways/GatewayCheque.php: -------------------------------------------------------------------------------- 1 | . 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Hyyan\WPI\Gateways; 12 | 13 | use Hyyan\WPI\Utilities; 14 | 15 | /** 16 | * Gateways Cheque. 17 | * 18 | * Handle Payment Gateways Cheque 19 | * 20 | * @author Antonio de Carvalho 21 | */ 22 | class GatewayCheque extends \WC_Gateway_Cheque 23 | { 24 | /** 25 | * Output for the order received page. 26 | * 27 | * Note 1: The difference to WC_Gateway_Cheque is that we use pll__() or __() 28 | * before passing the string through wptexturize() and wpautop(). 29 | * 30 | * Note 2: We wrap the intructions strings with wp_kses_post() to sanitize 31 | * content for allowed HTML tags like BACS does it. 32 | */ 33 | public function thankyou_page() 34 | { 35 | if ($this->instructions) { 36 | echo wpautop(wptexturize(wp_kses_post(function_exists('pll__') ? pll__($this->instructions) : __($this->instructions, 'woocommerce')))); 37 | } 38 | } 39 | 40 | /** 41 | * Add content to the WC emails. 42 | * 43 | * Note: The difference from WC_Gateway_Cheque is that we use __() before 44 | * passing the string through wptexturize() and wpautop(). 45 | * 46 | * @param WC_Order $order 47 | * @param bool $sent_to_admin 48 | * @param bool $plain_text 49 | */ 50 | public function email_instructions($order, $sent_to_admin, $plain_text = false) 51 | { 52 | if ($this->instructions && !$sent_to_admin && 'cheque' === Utilities::get_payment_method($order) && $order->has_status('on-hold')) { 53 | echo wpautop(wptexturize(function_exists('pll__') ? pll__($this->instructions) : __($this->instructions, 'woocommerce'))).PHP_EOL; 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Hyyan/WPI/Widgets/LayeredNav.php: -------------------------------------------------------------------------------- 1 | . 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Hyyan\WPI\Widgets; 12 | 13 | /** 14 | * LayeredNav. 15 | * 16 | * @author Hyyan Abo Fakher 17 | */ 18 | class LayeredNav 19 | { 20 | /** 21 | * Construct object. 22 | */ 23 | public function __construct() 24 | { 25 | add_action('init', array($this, 'layeredNavInit'), 1000); 26 | } 27 | 28 | /** 29 | * Layered Nav Init. 30 | * 31 | * @global array $_chosen_attributes 32 | * 33 | * @return false if not layered nav filter 34 | */ 35 | public function layeredNavInit() 36 | { 37 | if ( 38 | !(is_active_widget(false, false, 'woocommerce_layered_nav', true) && 39 | !is_admin()) 40 | ) { 41 | return false; 42 | } 43 | 44 | global $_chosen_attributes; 45 | 46 | $attributes = wc_get_attribute_taxonomies(); 47 | foreach ($attributes as $tax) { 48 | $attribute = wc_sanitize_taxonomy_name($tax->attribute_name); 49 | $taxonomy = wc_attribute_taxonomy_name($attribute); 50 | $name = 'filter_'.$attribute; 51 | 52 | if (!(!empty($_GET[$name]) && taxonomy_exists($taxonomy))) { 53 | continue; 54 | } 55 | 56 | $terms = explode(',', $_GET[$name]); 57 | $termsTranslations = array(); 58 | 59 | foreach ($terms as $ID) { 60 | $translation = pll_get_term($ID); 61 | $termsTranslations [] = $translation ? $translation : $ID; 62 | } 63 | 64 | $_GET[$name] = implode(',', $termsTranslations); 65 | $_chosen_attributes[$taxonomy]['terms'] = $termsTranslations; 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Hyyan/WPI/Widgets/SearchWidget.php: -------------------------------------------------------------------------------- 1 | . 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Hyyan\WPI\Widgets; 12 | 13 | /** 14 | * SearchWidget. 15 | * 16 | * @author Hyyan Abo Fakher 17 | */ 18 | class SearchWidget 19 | { 20 | /** 21 | * Constuct object. 22 | */ 23 | public function __construct() 24 | { 25 | add_filter('get_product_search_form', array( 26 | $this, 'fixSearchForm', 27 | )); 28 | } 29 | 30 | /** 31 | * Fix search form to avoid duplicated products results. 32 | * 33 | * @global \Polylang $polylang 34 | * 35 | * @param string $form 36 | * 37 | * @return string modified form 38 | */ 39 | public function fixSearchForm($form) 40 | { 41 | global $polylang; 42 | 43 | if ($form) { 44 | if ((isset($polylang->links_model)) && ($polylang->links_model->using_permalinks)) { 45 | 46 | /* Take care to modify only the url in the
tag */ 47 | preg_match('##', $form, $matches); 48 | $old = reset($matches); 49 | $new = preg_replace( 50 | '#'.$polylang->links_model->home.'\/?#', $polylang->curlang->search_url, $old 51 | ); 52 | 53 | $form = str_replace($old, $new, $form); 54 | } else { 55 | if (isset($polylang->curlang, $polylang->curlang->slug)) { 56 | $form = str_replace( 57 | '', '', $form 60 | ); 61 | } 62 | } 63 | } 64 | 65 | return $form; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/Hyyan/WPI/Product/Duplicator.php: -------------------------------------------------------------------------------- 1 | . 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Hyyan\WPI\Product; 12 | 13 | /** 14 | * Duplicator. 15 | * 16 | * @author Hyyan Abo Fakher 17 | */ 18 | class Duplicator 19 | { 20 | /** 21 | * Construct object. 22 | */ 23 | public function __construct() 24 | { 25 | add_action('woocommerce_product_duplicate', array( 26 | $this, 'unlinkOrginalProductTranslations', 27 | ), 10, 3); 28 | 29 | add_action('woocommerce_product_duplicate_before_save', array( 30 | $this, 'unlinkCopiedVariations', 31 | ), 10, 3); 32 | } 33 | 34 | /** 35 | * Unlink new variation copies from previous variation 36 | * 37 | * @param WC_Product $child_duplicate the new variation 38 | * @param WC_Product $child the original variation 39 | */ 40 | public function unlinkCopiedVariations($child_duplicate, $child) 41 | { 42 | //clear the reference to previous variation 43 | if ($child_duplicate instanceof \WC_Product_Variation) { 44 | //at this point is not saved, no id, so remove the key reference 45 | //(there is no alternative after-save filter) 46 | $child_duplicate->delete_meta_data(Variation::DUPLICATE_KEY); 47 | //later the existing code will get false checking for DUPLICATE_KEY and reset it to the new variation id 48 | } 49 | } 50 | 51 | /** 52 | * Unlink original product translations from the new copy. 53 | * 54 | * @param WC_Product $duplicate the new product 55 | * @param WC_Product $product the original product 56 | * 57 | */ 58 | public function unlinkOrginalProductTranslations($duplicate, $product) 59 | { 60 | global $polylang; 61 | //deprecated in Polylang 1.8 [currently 2.1.4], use PLL()->model->post->delete_translation() instead 62 | //$polylang->model->delete_translation('post', $ID); 63 | $polylang->model->post->delete_translation($duplicate->get_id()); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Hyyan/WPI/Admin/StatusReport.php: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 50 | 51 | 52 |

:
:
: 'locale' ) ); 34 | 35 | foreach ( $langs as $langId => $langLocale ) { 36 | echo($langLocale); 37 | $downloaded = TranslationsDownloader::isDownloaded( $langLocale ); 38 | $location = sprintf( 39 | trailingslashit( WP_LANG_DIR ) 40 | . 'plugins/woocommerce-%s.mo', $langLocale 41 | ); 42 | if ( $downloaded ) { 43 | echo(' (WooCommerce translation file found OK at ' . $location . ') '); 44 | } else { 45 | echo(' Warning - missing WooCommerce translation file NOT found at ' . $location . ' '); 46 | } 47 | echo '
'; 48 | } 49 | ?>
53 | 54 | -------------------------------------------------------------------------------- /src/Hyyan/WPI/Admin/AbstractSettings.php: -------------------------------------------------------------------------------- 1 | . 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Hyyan\WPI\Admin; 12 | 13 | use Hyyan\WPI\HooksInterface; 14 | 15 | /** 16 | * AbstractSettings. 17 | * 18 | * @author Hyyan 19 | * @author Hyyan Abo Fakher 20 | */ 21 | abstract class AbstractSettings implements SettingsInterface 22 | { 23 | /** 24 | * Construct object. 25 | */ 26 | public function __construct() 27 | { 28 | add_filter( 29 | HooksInterface::SETTINGS_SECTIONS_FILTER, array($this, 'getSections') 30 | ); 31 | add_filter( 32 | HooksInterface::SETTINGS_FIELDS_FILTER, array($this, 'getFields') 33 | ); 34 | } 35 | 36 | /** 37 | * {@inheritdoc} 38 | */ 39 | public function getSections(array $sections) 40 | { 41 | $new = array(); 42 | $current = apply_filters( 43 | $this->getSectionsFilterName(), (array) $this->doGetSections() 44 | ); 45 | 46 | foreach ($current as $def) { 47 | $def['id'] = static::getID(); 48 | $new[static::getID()] = $def; 49 | } 50 | 51 | return array_merge($sections, $new); 52 | } 53 | 54 | /** 55 | * {@inheritdoc} 56 | */ 57 | public function getFields(array $fields) 58 | { 59 | return array_merge($fields, array( 60 | static::getID() => apply_filters( 61 | $this->getFieldsFilterName(), (array) $this->doGetFields() 62 | ), 63 | )); 64 | } 65 | 66 | /** 67 | * Get sections filter name. 68 | * 69 | * @return string 70 | */ 71 | protected function getSectionsFilterName() 72 | { 73 | return sprintf('woo-poly.settings.%s_sections', static::getID()); 74 | } 75 | 76 | /** 77 | * Get sections filter name. 78 | * 79 | * @return string 80 | */ 81 | protected function getFieldsFilterName() 82 | { 83 | return sprintf('woo-poly.settings.%s_fields', static::getID()); 84 | } 85 | 86 | /** 87 | * @see SettingsInterface::getSections() 88 | */ 89 | abstract protected function doGetSections(); 90 | 91 | /** 92 | * @see SettingsInterface::getFields() 93 | */ 94 | abstract protected function doGetFields(); 95 | } 96 | -------------------------------------------------------------------------------- /public/js/Cart.min.js: -------------------------------------------------------------------------------- 1 | jQuery(function(e){if("undefined"==typeof wc_cart_fragments_params)return!1;var t=!0,r=wc_cart_fragments_params.cart_hash_key;try{t="sessionStorage"in window&&null!==window.sessionStorage,window.sessionStorage.setItem("wc","test"),window.sessionStorage.removeItem("wc"),window.localStorage.setItem("wc","test"),window.localStorage.removeItem("wc")}catch(e){t=!1}function a(){t&&sessionStorage.setItem("wc_cart_created",(new Date).getTime())}function o(e){t&&(localStorage.setItem(r,e),sessionStorage.setItem(r,e))}var n={url:wc_cart_fragments_params.wc_ajax_url.toString().replace("%%endpoint%%","get_refreshed_fragments"),type:"POST",data:{time:(new Date).getTime()},timeout:wc_cart_fragments_params.request_timeout,success:function(r){r&&r.fragments&&(e.each(r.fragments,function(t,r){e(t).replaceWith(r)}),t&&(sessionStorage.setItem(wc_cart_fragments_params.fragment_name,JSON.stringify(r.fragments)),o(r.cart_hash),r.cart_hash&&a()),e(document.body).trigger("wc_fragments_refreshed"))},error:function(){e(document.body).trigger("wc_fragments_ajax_error")}};function s(){e.ajax(n)}if(t){var i=null;e(document.body).on("wc_fragment_refresh updated_wc_div",function(){s()}),e(document.body).on("added_to_cart removed_from_cart",function(e,t,n){var s=sessionStorage.getItem(r);null!=s&&""!==s||a(),sessionStorage.setItem(wc_cart_fragments_params.fragment_name,JSON.stringify(t)),o(n)}),e(document.body).on("wc_fragments_refreshed",function(){clearTimeout(i),i=setTimeout(s,864e5)}),e(window).on("storage onstorage",function(e){r===e.originalEvent.key&&localStorage.getItem(r)!==sessionStorage.getItem(r)&&s()}),e(window).on("pageshow",function(t){t.originalEvent.persisted&&(e(".widget_shopping_cart_content").empty(),e(document.body).trigger("wc_fragment_refresh"))});try{var c=JSON.parse(sessionStorage.getItem(wc_cart_fragments_params.fragment_name)),g=sessionStorage.getItem(r),_=Cookies.get("woocommerce_cart_hash"),m=sessionStorage.getItem("wc_cart_created");if(null!=g&&""!==g||(g=""),null!=_&&""!==_||(_=""),g&&(null==m||""===m))throw"No cart_created";if(m){var d=1*m+864e5,w=(new Date).getTime();if(d0?e(".hide_cart_widget_if_empty").closest(".widget_shopping_cart").show():e(".hide_cart_widget_if_empty").closest(".widget_shopping_cart").hide(),e(document.body).on("adding_to_cart",function(){e(".hide_cart_widget_if_empty").closest(".widget_shopping_cart").show()}),"undefined"!=typeof wp&&wp.customize&&wp.customize.selectiveRefresh&&wp.customize.widgetsPreview&&wp.customize.widgetsPreview.WidgetPartial&&wp.customize.selectiveRefresh.bind("partial-content-rendered",function(){s()})}); -------------------------------------------------------------------------------- /src/Hyyan/WPI/Admin/MetasList.php: -------------------------------------------------------------------------------- 1 | . 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Hyyan\WPI\Admin; 12 | 13 | use Hyyan\WPI\Product\Meta; 14 | 15 | /** 16 | * MetasList. 17 | * 18 | * @author Hyyan Abo Fakher 19 | */ 20 | class MetasList extends AbstractSettings 21 | { 22 | /** 23 | * {@inheritdoc} 24 | */ 25 | public static function getID() 26 | { 27 | return 'wpi-metas-list'; 28 | } 29 | 30 | /** 31 | * {@inheritdoc} 32 | */ 33 | protected function doGetSections() 34 | { 35 | return array( 36 | array( 37 | 'title' => __('Metas List', 'woo-poly-integration'), 38 | 'desc' => __( 39 | 'The section will allow you to control which metas should be 40 | synced between products and their translations. The default 41 | values are appropriate for the large majority of the users. 42 | It is safe to ignore these settings if you do not understand 43 | their meaning. Please ignore this section if you do not 44 | understand the meaning of this. 45 | ', 'woo-poly-integration' 46 | ) . ' ' . __( 47 | 'For more information please see:', 'woo-poly-integration' 48 | ) . ' ' . 49 | __('documentation pages', 'woo-poly-integration') . '.' 50 | , 51 | ), 52 | ); 53 | } 54 | 55 | /** 56 | * {@inheritdoc} 57 | */ 58 | protected function doGetFields() 59 | { 60 | 61 | /* Metas list */ 62 | $metas = Meta::getProductMetaToCopy(array(), false); 63 | $fields = array(); 64 | foreach ($metas as $ID => $value) { 65 | $fields[] = array( 66 | 'name' => $ID, 67 | 'label' => $value['name'], 68 | 'desc' => $value['desc'], 69 | 'type' => 'multicheck', 70 | 'default' => array_combine($value['metas'], $value['metas']), 71 | 'options' => array_combine( 72 | $value['metas'], array_map(array(__CLASS__, 'normalize'), $value['metas']) 73 | ), 74 | ); 75 | } 76 | 77 | return $fields; 78 | } 79 | 80 | /** 81 | * Normalize string by removing "_", and leading and trailing spaces from string. 82 | * 83 | * @param string $string 84 | * 85 | * @return string 86 | */ 87 | public static function normalize($string) 88 | { 89 | return ucwords(trim(str_replace('_', ' ', $string))); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/Hyyan/WPI/Language.php: -------------------------------------------------------------------------------- 1 | . 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Hyyan\WPI; 12 | 13 | use Hyyan\WPI\Admin\Settings; 14 | use Hyyan\WPI\Admin\Features; 15 | use Hyyan\WPI\Tools\TranslationsDownloader; 16 | 17 | /** 18 | * Language. 19 | * 20 | * @author Hyyan Abo Fakher 21 | */ 22 | class Language 23 | { 24 | /** 25 | * Construct object. 26 | */ 27 | public function __construct() 28 | { 29 | add_action('load-settings_page_mlang', array( 30 | $this, 'downlaodWhenPolylangAddLangauge', 31 | )); 32 | 33 | add_action( 'pll_add_language', array( 34 | $this, 'downlaodWhenPolylangAddLangauge', 35 | ) ); 36 | 37 | add_action('woo-poly.settings.wpi-features_fields', array( 38 | $this, 'addSettingFields', 39 | )); 40 | } 41 | 42 | /** 43 | * Add setting fields. 44 | * 45 | * Add langauge setting fields 46 | * 47 | * @param array $fields 48 | * 49 | * @return array 50 | */ 51 | public function addSettingFields(array $fields) 52 | { 53 | $fields [] = array( 54 | 'name' => 'language-downloader', 55 | 'type' => 'checkbox', 56 | 'default' => 'on', 57 | 'label' => __('Translation Downloader', 'woo-poly-integration'), 58 | 'desc' => __( 59 | 'Download Woocommerce translations when a new polylang language is added', 'woo-poly-integration' 60 | ), 61 | ); 62 | 63 | return $fields; 64 | } 65 | 66 | /** 67 | * Download Translation. 68 | * 69 | * Download woocommerce translation when polylang add new langauge 70 | * 71 | * @return bool true if action executed successfully , false otherwise 72 | */ 73 | public function downlaodWhenPolylangAddLangauge() 74 | { 75 | if ('off' === Settings::getOption('language-downloader', Features::getID(), 'on')) { 76 | return false; 77 | } 78 | 79 | if ( 80 | !isset($_REQUEST['pll_action']) || 81 | 'add' !== esc_attr($_REQUEST['pll_action']) 82 | ) { 83 | return false; 84 | } 85 | 86 | $name = esc_attr($_REQUEST['name']); 87 | $locale = esc_attr($_REQUEST['locale']); 88 | 89 | if ('en_us' === strtolower($locale)) { 90 | return true; 91 | } 92 | 93 | try { 94 | return TranslationsDownloader::download($locale, $name); 95 | } catch (\RuntimeException $ex) { 96 | add_settings_error( 97 | 'general', $ex->getCode(), $ex->getMessage() 98 | ); 99 | 100 | return false; 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/Hyyan/WPI/Breadcrumb.php: -------------------------------------------------------------------------------- 1 | . 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Hyyan\WPI; 12 | 13 | /** 14 | * Breadcrumb. 15 | * 16 | * Handle Breadcrumb translation 17 | * 18 | * @author Antonio de Carvalho 19 | */ 20 | class Breadcrumb 21 | { 22 | /** 23 | * Construct object. 24 | */ 25 | public function __construct() 26 | { 27 | add_filter('woocommerce_breadcrumb_home_url', array($this, 'translateBreadrumbHomeUrl'), 10, 1); 28 | add_filter('woocommerce_get_breadcrumb', array($this, 'wpi_shopbreadcrumb'), 10, 2); 29 | } 30 | 31 | /* 32 | * Issue #542 Shop page omitted from breadcrumb in secondary languages 33 | * @param array $crumbs already populated breadcrumb array 34 | * @param WC_Breadcrumb $breadcrumb WC_Breadcrumb object 35 | * @return array return modified array of breadcrumbs 36 | */ 37 | public function wpi_shopbreadcrumb($crumbs, $breadcrumb){ 38 | $permalinks = wc_get_permalink_structure(); 39 | $curLang = pll_current_language(); 40 | $baseLang = pll_default_language(); 41 | if ($curLang != $baseLang){ 42 | //get shop page in current language 43 | $shop_page_translated_id = wc_get_page_id( 'shop' ); 44 | $shop_page_translated = get_post( $shop_page_translated_id ); 45 | //also get shop page in base language for the woocommerce test 46 | $shop_page_id = pll_get_post($shop_page_translated_id, $baseLang); 47 | $shop_page = get_post( $shop_page_id ); 48 | 49 | //perform same check whether shop page should be added as prepend_shop_page, but on the base language page 50 | if ( $shop_page_id && $shop_page && isset( $permalinks['product_base'] ) && strstr( $permalinks['product_base'], '/' . $shop_page->post_name ) && intval( get_option( 'page_on_front' ) ) !== $shop_page_id ) { 51 | //add breadcrumb for translated shop page as second item in breadcrumb array 52 | $homecrumb = array_shift($crumbs); 53 | array_unshift($crumbs, 54 | array( 55 | wp_strip_all_tags( get_the_title( $shop_page_translated ) ), 56 | get_permalink( $shop_page_translated ) 57 | ) 58 | ); 59 | array_unshift($crumbs, $homecrumb); 60 | } 61 | } 62 | return $crumbs; 63 | } 64 | 65 | 66 | /** 67 | * Translate WooCommerce Breadcrumbs home url. 68 | * 69 | * @return string translated home url 70 | */ 71 | public function translateBreadrumbHomeUrl($home) 72 | { 73 | if (function_exists('pll_home_url')) { 74 | return pll_home_url(); 75 | } 76 | 77 | return $home; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/Hyyan/WPI/Admin/Settings.php: -------------------------------------------------------------------------------- 1 | . 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Hyyan\WPI\Admin; 12 | 13 | use Hyyan\WPI\HooksInterface; 14 | 15 | /** 16 | * Settings. 17 | * 18 | * Admin settings page 19 | * 20 | * @author Hyyan Abo Fakher 21 | */ 22 | class Settings extends \WeDevs_Settings_API 23 | { 24 | /** 25 | * Construct object. 26 | */ 27 | public function __construct() 28 | { 29 | parent::__construct(); 30 | add_action('admin_init', array($this, 'init')); 31 | add_action('admin_menu', array($this, 'registerMenu')); 32 | 33 | new Features(); 34 | new MetasList(); 35 | } 36 | 37 | /** 38 | * Initialize settings. 39 | */ 40 | public function init() 41 | { 42 | /* Set the settings */ 43 | $this->set_sections($this->getSections()); 44 | $this->set_fields($this->getFields()); 45 | 46 | /* Initialize settings */ 47 | $this->admin_init(); 48 | } 49 | 50 | /** 51 | * Add plugin menu. 52 | */ 53 | public function registerMenu() 54 | { 55 | if ( current_user_can( 'manage_options' ) ) { 56 | add_options_page( 57 | __('Hyyan WooCommerce Polylang Integration', 'woo-poly-integration'), __('WooPoly', 'woo-poly-integration'), 'delete_posts', 'hyyan-wpi', array($this, 'outputPage') 58 | ); 59 | } 60 | } 61 | 62 | /** 63 | * Get sections. 64 | * 65 | * Get setting sections array to register 66 | * 67 | * @return array 68 | */ 69 | public function getSections() 70 | { 71 | return apply_filters(HooksInterface::SETTINGS_SECTIONS_FILTER, array()); 72 | } 73 | 74 | /** 75 | * Returns all the settings fields. 76 | * 77 | * @return array settings fields 78 | */ 79 | public function getFields() 80 | { 81 | return apply_filters(HooksInterface::SETTINGS_FIELDS_FILTER, array()); 82 | } 83 | 84 | /** 85 | * Output page content. 86 | */ 87 | public function outputPage() 88 | { 89 | echo \Hyyan\WPI\Plugin::getView('admin', array( 90 | 'self' => $this, 91 | )); 92 | } 93 | 94 | /** 95 | * Get the value of a settings field. 96 | * 97 | * @param string $option settings field name 98 | * @param string $section the section name this field belongs to 99 | * @param string $default default text if it's not found 100 | * 101 | * @return mixed 102 | */ 103 | public static function getOption($option, $section, $default = '') 104 | { 105 | $options = get_option($section); 106 | 107 | if (!empty($options[$option])) { // equivalent to: isset($options[$option]) && $options[$option] 108 | return $options[$option]; 109 | } // when all settings are disabled 110 | elseif (isset($options[$option])) { 111 | return array(); 112 | } else { 113 | return $default; 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/Hyyan/WPI/Privacy.php: -------------------------------------------------------------------------------- 1 | . 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | namespace Hyyan\WPI; 10 | 11 | /** 12 | * Privacy. 13 | * 14 | */ 15 | class Privacy 16 | { 17 | 18 | /** 19 | * Construct initiated on init by Plugin.php 20 | */ 21 | public function __construct() 22 | { 23 | $this->registerPrivacyStrings(); 24 | add_filter('woocommerce_get_privacy_policy_text', array($this, 'translatePrivacyPolicyText'), 10, 2); 25 | add_filter( 'woocommerce_demo_store', array( $this, 'translateDemoStoreNotice' ), 10, 2 ); 26 | add_filter( 'woocommerce_get_terms_and_conditions_checkbox_text', array( $this, 'translateText' ), 10, 1 ); 27 | } 28 | 29 | /** 30 | * Register woocommerce privacy policy messages in polylang strings 31 | * translations table. 32 | */ 33 | public function registerPrivacyStrings() 34 | { 35 | $this->registerString('woocommerce_checkout_privacy_policy_text', get_option('woocommerce_checkout_privacy_policy_text', sprintf(__('Your personal data will be used to process your order, support your experience throughout this website, and for other purposes described in our %s.', 'woocommerce'), '[privacy_policy]'))); 36 | $this->registerString('woocommerce_registration_privacy_policy_text', get_option('woocommerce_registration_privacy_policy_text', sprintf(__('Your personal data will be used to support your experience throughout this website, to manage access to your account, and for other purposes described in our %s.', 'woocommerce'), '[privacy_policy]'))); 37 | $this->registerString( 'woocommerce_store_notice', get_option( 'woocommerce_demo_store_notice' ) ); 38 | $this->registerString( 'woocommerce_checkout_terms_and_conditions_checkbox_text', get_option( 'woocommerce_checkout_terms_and_conditions_checkbox_text' ) ); 39 | } 40 | 41 | 42 | /** 43 | * Register setting and value in polylang strings translations table. 44 | * 45 | * @param string $setting Name of setting/string to translate 46 | * @param string $value Value in base language 47 | */ 48 | private function registerString($setting, $value) 49 | { 50 | if (function_exists('pll_register_string')) { 51 | pll_register_string($setting, $value, __('WooCommerce Privacy', 'woo-poly-integration'), true); 52 | } 53 | } 54 | 55 | /** 56 | * Register setting and value in polylang strings translations table. 57 | * 58 | * @param string $text Text to be translated 59 | * @param string $type Name of privacy policy type 'checkout' or ‘registration’ 60 | */ 61 | public function translatePrivacyPolicyText($text, $type) 62 | { 63 | return $this->translateText($text); 64 | } 65 | public function translateText( $text ) 66 | { 67 | if (function_exists('pll_register_string')) { 68 | $trans = pll__($text); 69 | return ($trans) ? $trans : $text; 70 | } else { 71 | return $text; 72 | } 73 | } 74 | 75 | public function translateDemoStoreNotice( $html, $notice ) { 76 | $trans = ''; 77 | if ( function_exists( 'pll_register_string' ) ) { 78 | $trans = pll__( $notice ); 79 | return '

' . wp_kses_post( $trans ) . ' ' . esc_html__( 'Dismiss', 'woocommerce' ) . '

'; 80 | } else { 81 | return $html; 82 | } 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WordPress WooCommerce Polylang Integration 2 | 3 | [![project status](http://www.repostatus.org/badges/latest/active.svg)](http://www.gitchecker.com/hyyan/woo-poly-integration) 4 | [![Latest Stable Version](https://poser.pugx.org/hyyan/woo-poly-integration/v/stable.svg)](https://packagist.org/packages/hyyan/woo-poly-integration) 5 | [![Wordpress plugin](http://img.shields.io/wordpress/plugin/v/woo-poly-integration.svg)](https://wordpress.org/plugins/woo-poly-integration/) 6 | [![Wordpress](http://img.shields.io/wordpress/plugin/dt/woo-poly-integration.svg)](https://wordpress.org/plugins/woo-poly-integration/) 7 | [![Wordpress rating](http://img.shields.io/wordpress/plugin/r/woo-poly-integration.svg)](https://wordpress.org/plugins/woo-poly-integration/) 8 | [![License](https://poser.pugx.org/hyyan/woo-poly-integration/license.svg)](https://packagist.org/packages/hyyan/woo-poly-integration) 9 | 10 | **Given that I am not using Wordpress these days and I haven't really been using WooPoly for a while. I am looking for maintainers to take over this project. 11 | If you're interested, please reply to this [issue](https://github.com/hyyan/woo-poly-integration/issues/410) or get in touch with me via email: hyyanaf [at] gmail [dot] com** 12 | 13 | --- 14 | 15 | [This plugin](https://github.com/hyyan/woo-poly-integration/) makes it possible to run multilingual e-commerce sites using 16 | WooCommerce and Polylang.It makes products and store pages translatable, lets 17 | visitors switch languages and order products in their language. and all that from 18 | the same interface you love. 19 | 20 | [Read the full docs](https://github.com/hyyan/woo-poly-integration/wiki) 21 | 22 | ## Features 23 | 24 | - [x] Auto Download Woocommerce Translation Files 25 | - [x] Page Translation 26 | - [x] Endpoints Translation 27 | - [x] Product Translation 28 | - [x] Categories 29 | - [x] Tags 30 | - [x] Attributes 31 | - [x] Shipping Classes 32 | - [x] Meta Synchronization 33 | - [x] Variation Product 34 | - [x] Product Gallery 35 | - [x] Order Translation 36 | - [x] Stock Synchronization 37 | - [x] Cart Synchronization 38 | - [x] Coupon Synchronization 39 | - [x] Emails 40 | - [x] Reports 41 | - [x] Filter by language 42 | - [x] Combine reports for all languages 43 | 44 | 45 | ## What you need to know about this plugin 46 | 47 | 1. WooCommerce and therefore this plugin needs `PHP7 and above` 48 | 2. This plugin is developed in sync with [Polylang](https://wordpress.org/plugins/polylang) 49 | and [WooCommerce](https://wordpress.org/plugins/woocommerce/) latest version 50 | 3. The plugin support variable products , but using them will `disallow you to 51 | change the default language` , because of the way the plugin implements this 52 | support. So you have to make sure to choose the default language before you start 53 | adding new variable products. 54 | 4. Polylang URL modifications method `The language is set from content` is not 55 | supported 56 | 57 | ## How to install 58 | 59 | ### Classical way 60 | 61 | 1. Download the plugin as zip archive and then upload it to your wordpress plugins folder and 62 | extract it there. 63 | 2. Activate the plugin from your admin panel 64 | 65 | ### Composer way 66 | 67 | 1. run composer command : ``` composer require hyyan/woo-poly-integration``` 68 | 69 | In all cases please do ensure you have Polylang and WooCommerce activated and setup 70 | before you Activate this plugin from your admin panel 71 | 72 | Please note the getting started notes: 73 | https://github.com/hyyan/woo-poly-integration/wiki/Getting-Started 74 | 75 | 76 | ## Setup your environment 77 | 78 | 1. You need to translate woocommerce pages by yourself 79 | 2. The plugin will handle the rest for you 80 | 81 | ## Translations 82 | 83 | * Arabic by [Hyyan Abo Fakher](https://github.com/hyyan) 84 | * Spanish by [nunhes](https://github.com/nunhes) 85 | 86 | ## Contributing 87 | 88 | Everyone is welcome to help contribute and improve this plugin. There are several 89 | ways you can contribute: 90 | 91 | * Reporting issues (please read [issue guidelines](https://github.com/necolas/issue-guidelines)) 92 | * Suggesting new features 93 | * Writing or refactoring code 94 | * Fixing [issues](https://github.com/hyyan/woo-poly-integration/issues) 95 | -------------------------------------------------------------------------------- /src/Hyyan/WPI/Tools/FlashMessages.php: -------------------------------------------------------------------------------- 1 | . 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Hyyan\WPI\Tools; 12 | 13 | /** 14 | * FlashMessages. 15 | * 16 | * @author Hyyan Abo Fakher 17 | */ 18 | final class FlashMessages 19 | { 20 | /** 21 | * Register the falsh messages. 22 | */ 23 | public static function register() 24 | { 25 | add_action('admin_notices', array( 26 | __CLASS__, 'display', 27 | )); 28 | } 29 | 30 | /** 31 | * Queue flash message. 32 | * 33 | * @param string $id message id 34 | * @param string $message message content 35 | * @param array $classes array of classes to used for this message wrapper 36 | * @param bool $persist should persist the message between sessions 37 | */ 38 | public static function add($id, $message, array $classes = array('updated'), $persist = false) 39 | { 40 | $messages = static::getMessages(); 41 | $data = array( 42 | 'id' => $id, 43 | 'message' => $message, 44 | 'classes' => $classes, 45 | 'persist' => $persist, 46 | ); 47 | 48 | /* Add the new message data */ 49 | if (isset($messages[$id])) { 50 | $messages[$id] = array_replace_recursive($messages[$id], $data); 51 | } else { 52 | $messages[$id] = $data; 53 | } 54 | 55 | update_option(static::getOptionName(), $messages); 56 | } 57 | 58 | /** 59 | * Remove message by its id. 60 | * 61 | * @param string $id message id 62 | * 63 | * @return bool true if removed , false otherwise 64 | */ 65 | public static function remove($id) 66 | { 67 | $messages = static::getMessages(); 68 | if (isset($messages[$id])) { 69 | unset($messages[$id]); 70 | update_option(static::getOptionName(), $messages); 71 | 72 | return true; 73 | } 74 | 75 | return false; 76 | } 77 | 78 | /** 79 | * Display all flash messages. 80 | */ 81 | public static function display() 82 | { 83 | $messages = static::getMessages(); 84 | 85 | foreach ($messages as $id => $message) { 86 | $display = true; 87 | if (!isset($message['displayed'])) { 88 | $display = true; 89 | } elseif ($message['persist'] === false && $message['displayed'] === true) { 90 | $display = false; 91 | } 92 | 93 | $messages[$id]['displayed'] = (true === $message['persist']) ? 94 | false : true; 95 | 96 | if (true === $display || !@$message['displayed']) { 97 | $message['classes'][] = 'is-dismissible notice'; 98 | $classesString = implode(' ', $message['classes']); 99 | printf( 100 | '

%s

', $classesString, $message['message'] 101 | ); 102 | } 103 | } 104 | 105 | update_option(static::getOptionName(), $messages); 106 | } 107 | 108 | /** 109 | * Clear all messages. 110 | */ 111 | public static function clearMessages() 112 | { 113 | delete_option(static::getOptionName()); 114 | } 115 | 116 | /** 117 | * Get option name used to save flash messages to database. 118 | * 119 | * @return string 120 | */ 121 | public static function getOptionName() 122 | { 123 | return 'hyyan-wpi-flash-messages'; 124 | } 125 | 126 | /** 127 | * Get messages. 128 | * 129 | * Get flash messages array 130 | * 131 | * @return array 132 | */ 133 | private static function getMessages() 134 | { 135 | $messages = get_option( static::getOptionName(), array() ); 136 | if ( ! is_array( $messages ) ) { 137 | $messages = array(); 138 | } 139 | return $messages; 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /public/js/Variables.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the hyyan/woo-poly-integration plugin. 3 | * (c) Hyyan Abo Fakher 4 | * 5 | * For the full copyright and license information, please view the LICENSE 6 | * file that was distributed with this source code. 7 | */ 8 | 9 | (function ($, document, HYYAN_WPI_VARIABLES) { 10 | 11 | /** 12 | * Construct variables 13 | * 14 | * @param {jQuery} $ 15 | * @param {document} document 16 | * @param {object} data 17 | * 18 | * @returns {Variables} 19 | */ 20 | var Variables = function ($, document, data) { 21 | this.$ = $; 22 | this.document = document; 23 | this.data = data; 24 | this._dialog = this._createDialog(); 25 | }; 26 | 27 | Variables.prototype = { 28 | /** Constructor */ 29 | constructor: Variables, 30 | init: function () { 31 | 32 | var self = this; 33 | $('#post_lang_choice').on('change', function () { 34 | self.shouldAlert(); 35 | }); 36 | $('#product-type').on('change', function () { 37 | self.shouldAlert(); 38 | }); 39 | 40 | } 41 | /** 42 | * Check if we should alert the user 43 | * 44 | * @returns {Boolean} 45 | */ 46 | , shouldAlert: function () { 47 | var type = this.getCurrentProduct(); 48 | var lang = this.getCurrentLanguage(); 49 | if (type === 'variable' && lang !== this.data['defaultLang']) { 50 | this._dialog.dialog('open'); 51 | /* Disable Saving */ 52 | this._changeSaveBoxState(true); 53 | return true; 54 | } 55 | this._changeSaveBoxState(false); 56 | return false; 57 | } 58 | /** 59 | * Get current product language 60 | * 61 | * @returns {String} 62 | */ 63 | , getCurrentLanguage: function () { 64 | return this.$('#post_lang_choice').val(); 65 | } 66 | /** 67 | * Get current product type 68 | * 69 | * @returns {string} 70 | */ 71 | , getCurrentProduct: function () { 72 | return this.$('#product-type').val(); 73 | } 74 | /** 75 | * Change the state of save box from disable to enable and vs 76 | * 77 | * @param {boolean} enable 78 | */ 79 | , _changeSaveBoxState: function (enable) { 80 | this.$('#submitdiv *').prop('disabled', enable); 81 | } 82 | /** 83 | * Create dialog 84 | * 85 | * @returns {objet} 86 | */ 87 | , _createDialog: function () { 88 | 89 | /* This variable */ 90 | var self = this; 91 | 92 | /* Fix the z-index */ 93 | this.$('') 94 | .appendTo($('head')); 95 | 96 | /* Create dialog */ 97 | var dialog = this.$("
") 98 | .html('

' + self.data['content'] + '

') 99 | .attr('title', self.data['title']) 100 | .appendTo("body"); 101 | 102 | 103 | dialog.dialog({ 104 | dialogClass: "wp-dialog", 105 | autoOpen: false, 106 | modal: true, 107 | width: 400, 108 | height: 250, 109 | position: { 110 | my: "center", 111 | at: "center" 112 | }, 113 | buttons: [ 114 | { 115 | text: 'Got It', 116 | click: function () { 117 | $(this).dialog('close'); 118 | } 119 | } 120 | ] 121 | }); 122 | 123 | return dialog; 124 | } 125 | }; 126 | 127 | // bootstrap 128 | $(function() { 129 | new Variables($, document, HYYAN_WPI_VARIABLES).init(); 130 | }); 131 | 132 | 133 | })(jQuery, document, HYYAN_WPI_VARIABLES); 134 | -------------------------------------------------------------------------------- /src/Hyyan/WPI/Tax.php: -------------------------------------------------------------------------------- 1 | . 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Hyyan\WPI; 12 | 13 | use Hyyan\WPI\Utilities; 14 | 15 | /** 16 | * Tax. 17 | * 18 | * @author Jonathan Moore 19 | */ 20 | class Tax 21 | { 22 | 23 | /** 24 | * Construct object. 25 | */ 26 | public function __construct() 27 | { 28 | add_filter('woocommerce_get_price_suffix', array($this, 'filterPriceSuffix'), 10, 4); 29 | $this->registerTaxStringsForTranslation(); 30 | } 31 | 32 | /** 33 | * Register woocommerce email subjects and headings in polylang strings 34 | * translations table. 35 | */ 36 | public function registerTaxStringsForTranslation() 37 | { 38 | if (wc_tax_enabled() && 'taxable') { 39 | $suffix = get_option('woocommerce_price_display_suffix'); 40 | if ($suffix) { 41 | $this->registerTaxString('woocommerce_price_display_suffix', $suffix); 42 | } 43 | } 44 | } 45 | 46 | /** 47 | * Register common strings for all wooCommerce taxes for translation in Polylang 48 | * Strings Translations table. 49 | * 50 | * @param string $setting Option to save 51 | * @param string $sufix Additional string variation, e.g. invoice paid vs invoice 52 | */ 53 | public function registerTaxString($setting, $default = '') 54 | { 55 | if (function_exists('pll_register_string')) { 56 | $value = get_option($setting); 57 | if (!($value)) { 58 | $value = $default; 59 | } 60 | if ($value) { 61 | pll_register_string($setting, $value, __('Woocommerce Taxes', 'woo-poly-integration')); 62 | } 63 | } 64 | } 65 | 66 | /* translate string 67 | * 68 | * @param string $subject Email subject in default language 69 | * @param WC_Order $order Order object 70 | * 71 | * @return string Translated subject 72 | */ 73 | 74 | public function translateTaxString($tax_string) 75 | { 76 | if (function_exists('pll_register_string')) { 77 | $lang = pll_current_language('locale'); 78 | $trans = pll__($tax_string); 79 | if ($trans) { 80 | return $trans; 81 | } else { 82 | return $tax_string; 83 | } 84 | } 85 | } 86 | 87 | /** 88 | * hook for woocommerce_get_price_suffix to get translated price string. 89 | * note for variable products this is skipped and instead calls get_price_html 90 | * which has filter woocommerce_get_price_html 91 | * 92 | * @param string $html default price html provided by wooCommerce 93 | * @param WC_Product $instance current product 94 | * 95 | * @return array 96 | */ 97 | public function filterPriceSuffix($html, $instance, $price, $qty) 98 | { 99 | $html = ''; 100 | 101 | if (($suffix = get_option('woocommerce_price_display_suffix')) && wc_tax_enabled() && 'taxable' === $instance->get_tax_status()) { 102 | 103 | //the rest of this function is the same as the wooCommerce code, here just translating the suffix 104 | //before expanding any suffix parameters 105 | $suffix = $this->translateTaxString($suffix); 106 | 107 | if ('' === $price) { 108 | $price = $instance->get_price(); 109 | } 110 | $replacements = array( 111 | '{price_including_tax}' => wc_price(wc_get_price_including_tax($instance, array('qty' => $qty, 'price' => $price))), 112 | '{price_excluding_tax}' => wc_price(wc_get_price_excluding_tax($instance, array('qty' => $qty, 'price' => $price))), 113 | ); 114 | $html = str_replace(array_keys($replacements), array_values($replacements), ' ' . wp_kses_post($suffix) . ''); 115 | } 116 | 117 | return $html; 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/Hyyan/WPI/Taxonomies/Categories.php: -------------------------------------------------------------------------------- 1 | . 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Hyyan\WPI\Taxonomies; 12 | 13 | use Hyyan\WPI\HooksInterface; 14 | use Hyyan\WPI\Utilities; 15 | 16 | /** 17 | * Categories. 18 | * 19 | * @author Hyyan Abo Fakher 20 | */ 21 | class Categories implements TaxonomiesInterface 22 | { 23 | /** 24 | * Construct object. 25 | */ 26 | public function __construct() 27 | { 28 | /* Handle product category custom fiedlds */ 29 | add_action( 30 | 'product_cat_add_form_fields', array($this, 'copyProductCatCustomFields'), 11 31 | ); 32 | add_action( 33 | 'created_term', array($this, 'syncProductCatCustomFields'), 11, 3 34 | ); 35 | add_action( 36 | 'edit_term', array($this, 'syncProductCatCustomFields'), 11, 3 37 | ); 38 | } 39 | 40 | /** 41 | * Sync Product Category Custom Fields. 42 | * 43 | * Keep product categories translation synced 44 | * 45 | * @param int $termID the term id 46 | * @param int $ttID ? 47 | * @param string $taxonomy the taxonomy name 48 | */ 49 | public function syncProductCatCustomFields($termID, $ttID = '', $taxonomy = '') 50 | { 51 | if (isset($_POST['display_type']) && 'product_cat' === $taxonomy) { 52 | $this->doSyncProductCatCustomFields( 53 | $termID, 'display_type', esc_attr($_POST['display_type']) 54 | ); 55 | } 56 | if (isset($_POST['product_cat_thumbnail_id']) && 'product_cat' === $taxonomy) { 57 | $this->doSyncProductCatCustomFields( 58 | $termID, 'thumbnail_id', absint($_POST['product_cat_thumbnail_id']) 59 | ); 60 | } 61 | 62 | if ('product_cat' === $taxonomy) { 63 | /* Allow other plugins to check for category custom fields */ 64 | do_action( 65 | HooksInterface::PRODUCT_SYNC_CATEGORY_CUSTOM_FIELDS, $this, $termID, $taxonomy 66 | ); 67 | } 68 | } 69 | 70 | /** 71 | * Copy product Category Custom fields. 72 | * 73 | * Copy the category custom fields from orginal category to its translations 74 | * when we start adding new category translation 75 | * 76 | * @return bool false if this action must not be executed 77 | */ 78 | public function copyProductCatCustomFields() 79 | { 80 | 81 | /* We sync custom fields only for translation */ 82 | if (!(isset($_GET['from_tag']) && isset($_GET['new_lang']))) { 83 | return false; 84 | } 85 | 86 | $ID = esc_attr($_GET['from_tag']); 87 | $type = get_term_meta($ID, 'display_type', true); 88 | $thumbID = absint(get_term_meta($ID, 'thumbnail_id', true)); 89 | $image = $thumbID ? 90 | wp_get_attachment_thumb_url($thumbID) : 91 | wc_placeholder_img_src(); ?> 92 | 103 | . 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Hyyan\WPI\Taxonomies; 12 | 13 | use Hyyan\WPI\Utilities; 14 | 15 | /** 16 | * Attributes. 17 | * 18 | * @author Hyyan Abo Fakher 19 | */ 20 | class Attributes implements TaxonomiesInterface 21 | { 22 | /** 23 | * Construct object. 24 | */ 25 | public function __construct() 26 | { 27 | /* Manage attributes label translation */ 28 | add_action( 29 | 'init', 30 | array($this, 'manageAttrLablesTranslation'), 31 | 11, 32 | 2 33 | ); 34 | add_filter( 35 | 'woocommerce_attribute_label', 36 | array($this, 'translateAttrLable') 37 | ); 38 | add_action( 39 | 'admin_print_scripts', 40 | array($this, 'addAttrsTranslateLinks'), 41 | 100 42 | ); 43 | } 44 | 45 | /** 46 | * Make all attributes lables managed by polylang string translation. 47 | * 48 | * @global \Polylang $polylang 49 | * @global \WooCommerce $woocommerce 50 | * 51 | * @return bool false if polylang or woocommerce can not be found 52 | */ 53 | public function manageAttrLablesTranslation() 54 | { 55 | global $polylang, $woocommerce; 56 | 57 | if (!$polylang || !$woocommerce) { 58 | return false; 59 | } 60 | 61 | $attrs = wc_get_attribute_taxonomies(); 62 | $section = __('WooCommerce Attributes', 'woo-poly-integration'); 63 | foreach ($attrs as $attr) { 64 | pll_register_string( 65 | $attr->attribute_label, 66 | $attr->attribute_label, 67 | $section 68 | ); 69 | } 70 | } 71 | 72 | /** 73 | * Translate the attribute label. 74 | * 75 | * @param string $label original attribute label 76 | * 77 | * @return string translated attribute label 78 | */ 79 | public function translateAttrLable($label) 80 | { 81 | return pll__($label); 82 | } 83 | 84 | /** 85 | * Add a button before the attributes table to let the user know how to 86 | * translate the attributes labels. 87 | * 88 | * @global type $pagenow 89 | * 90 | * @return bool false if not attributes page 91 | */ 92 | public function addAttrsTranslateLinks() 93 | { 94 | global $pagenow; 95 | if ($pagenow !== 'edit.php') { 96 | return false; 97 | } 98 | 99 | $isAttrPage = isset($_GET['page']) && 100 | esc_attr($_GET['page']) === 'product_attributes'; 101 | 102 | if (!$isAttrPage) { 103 | return false; 104 | } 105 | 106 | $stringTranslationURL = add_query_arg(array( 107 | 'page' => 'mlang_strings', 108 | //'tab' => 'strings', 109 | 'group' => __('WooCommerce Attributes', 'woo-poly-integration'), 110 | ), admin_url('admin.php')); 111 | 112 | /* Add attribute translate button */ 113 | $buttonID = 'attrs-label-translation-button'; 114 | $buttonCode = sprintf( 115 | '$("%s

")' 116 | .' .insertBefore(".attributes-table");', 117 | $stringTranslationURL, 118 | __('Translate Attributes Labels', 'woo-poly-integration') 119 | ); 120 | 121 | /* Add attribute translate search link */ 122 | $searchLinkID = 'attr-label-translate-search-link'; 123 | $searchLinkCode = sprintf( 124 | "$('.attributes-table .row-actions').each(function () {\n" 125 | .' var $this = $(this);' 126 | .' var attrName = $this.parent().find("strong a").text();' 127 | .' var attrTranslateUrl = "%s&s="+attrName ;' 128 | .' var attrTranslateHref = ' 129 | .' ""' 130 | .' + "| "' 131 | .' + "%s"' 132 | .' + "";' 133 | .' $this.append(attrTranslateHref);' 134 | ."\n});\n", 135 | $stringTranslationURL, 136 | __('Translate', 'woo-poly-integration') 137 | ); 138 | 139 | /* Output code */ 140 | Utilities::jsScriptWrapper($buttonID, $buttonCode); 141 | Utilities::jsScriptWrapper($searchLinkID, $searchLinkCode); 142 | } 143 | 144 | /** 145 | * {@inheritdoc} 146 | */ 147 | public static function getNames() 148 | { 149 | return wc_get_attribute_taxonomy_names(); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/Hyyan/WPI/Order.php: -------------------------------------------------------------------------------- 1 | . 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Hyyan\WPI; 12 | 13 | use Hyyan\WPI\Utilities; 14 | 15 | /** 16 | * Order. 17 | * 18 | * Handle order language 19 | * 20 | * @author Hyyan Abo Fakher 21 | */ 22 | class Order 23 | { 24 | 25 | /** 26 | * Construct object. 27 | */ 28 | public function __construct() 29 | { 30 | 31 | /* Manage order translation */ 32 | add_filter( 33 | 'pll_get_post_types', array($this, 'manageOrderTranslation') 34 | ); 35 | /* Save the order language with every checkout */ 36 | add_action( 37 | 'woocommerce_checkout_update_order_meta', array($this, 'saveOrderLanguage') 38 | ); 39 | 40 | if (is_admin()) { 41 | $this->limitPolylangFeaturesForOrders(); 42 | } 43 | 44 | /* For the query used to get orders in my accout page */ 45 | add_filter('woocommerce_my_account_my_orders_query', array($this, 'correctMyAccountOrderQuery') 46 | ); 47 | 48 | /* Translate products in order details */ 49 | add_filter( 50 | 'woocommerce_order_item_product', array($this, 'translateProductsInOrdersDetails'), 10, 3 51 | ); 52 | } 53 | 54 | /** 55 | * Notifty polylang about order custom post. 56 | * 57 | * @param array $types array of custom post names managed by polylang 58 | * 59 | * @return array 60 | */ 61 | public function manageOrderTranslation(array $types) 62 | { 63 | $options = get_option('polylang'); 64 | $postTypes = $options['post_types']; 65 | if (!in_array('shop_order', $postTypes)) { 66 | $options['post_types'][] = 'shop_order'; 67 | update_option('polylang', $options); 68 | } 69 | 70 | $types [] = 'shop_order'; 71 | 72 | return $types; 73 | } 74 | 75 | /** 76 | * Save the order language with every checkout. 77 | * 78 | * @param int $order the order object 79 | */ 80 | public function saveOrderLanguage($order) 81 | { 82 | $current = pll_current_language(); 83 | if ($current) { 84 | pll_set_post_language($order, $current); 85 | } 86 | } 87 | 88 | /** 89 | * Translate products in order details pages. 90 | * 91 | * @param \WC_Product $product 92 | * 93 | * @return \WC_Product 94 | */ 95 | public function translateProductsInOrdersDetails($product) 96 | { 97 | if ($product) { 98 | return Utilities::getProductTranslationByObject($product); 99 | } else { 100 | return false; 101 | } 102 | } 103 | 104 | /** 105 | * Correct My account order query. 106 | * 107 | * Will correct the query to display orders from all languages 108 | * 109 | * @param array $query query arguments 110 | * 111 | * @return array 112 | */ 113 | public function correctMyAccountOrderQuery(array $query) 114 | { 115 | add_filter('woocommerce_order_data_store_cpt_get_orders_query', array($this, 'correctGetOrderQuery'), 10, 2); 116 | $query['lang'] = implode(',', pll_languages_list()); 117 | 118 | return $query; 119 | } 120 | 121 | /** 122 | * Correct wc_get_orders query for the My Account view orders page. 123 | * 124 | * Will correct the query to display orders from all languages 125 | * 126 | * @param array $query WP_Query arguments 127 | * @param array $args wc_get_orders query args 128 | * 129 | * @return array 130 | */ 131 | public function correctGetOrderQuery($query, $args) 132 | { 133 | if (isset($args['lang'])) { 134 | $query['lang'] = $args['lang']; 135 | } 136 | return $query; 137 | } 138 | 139 | /** 140 | * Disallow the user to create translations for this post type. 141 | */ 142 | public function limitPolylangFeaturesForOrders() 143 | { 144 | add_action('current_screen', function () { 145 | $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : false; 146 | 147 | if ( $screen && $screen->post_type === 'shop_order' ) { 148 | add_action('admin_print_scripts', function () { 149 | $jsID = 'order-translations-buttons'; 150 | $code = '$(".pll_icon_add,#post-translations").fadeOut()'; 151 | 152 | Utilities::jsScriptWrapper($jsID, $code); 153 | }, 100); 154 | } 155 | }); 156 | } 157 | 158 | /** 159 | * Get the order language. 160 | * 161 | * @param int $ID order ID 162 | * 163 | * @return string|false language in success , false otherwise 164 | */ 165 | public static function getOrderLangauge($ID) 166 | { 167 | return pll_get_post_language($ID); 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /src/Hyyan/WPI/Product/Stock.php: -------------------------------------------------------------------------------- 1 | . 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | * 10 | * This file was written by J.Moore to replace the existing Stock.php file which had 11 | * multiple issues: this version takes a different approach 12 | */ 13 | 14 | namespace Hyyan\WPI\Product; 15 | 16 | use Hyyan\WPI\Utilities; 17 | use Hyyan\WPI\Product\Variation; 18 | 19 | class Stock { 20 | public function __construct() { 21 | 22 | //disable on product edit screen as this has its own synchronisation on save 23 | $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : false; 24 | if ( ($screen && $screen->post_type === 'product') || isset( $_POST[ 'product-type' ] ) ) { 25 | return; 26 | } 27 | 28 | // sync stock 29 | add_action( 30 | 'woocommerce_product_set_stock', array( __CLASS__, 'SyncStockSimple' ) 31 | ); 32 | add_action( 33 | 'woocommerce_variation_set_stock', array( __CLASS__, 'SyncStockVariation' ) 34 | ); 35 | } 36 | 37 | /* 38 | * Unhook the action, synchronise the stock and rehook the action 39 | * 40 | * @param \WC_Product $product the product which has had stock updated 41 | */ 42 | public static function SyncStockSimple( \WC_Product $product ) { 43 | //first remove this action to avoid recusive call when setting translation stock 44 | remove_action( 'woocommerce_product_set_stock', array( __CLASS__, __FUNCTION__ ), 10 ); 45 | static::SyncStock( $product ); 46 | add_action( 'woocommerce_product_set_stock', array( __CLASS__, __FUNCTION__ ), 10 ); 47 | } 48 | 49 | /* 50 | * Unhook the action, synchronise the stock and rehook the action 51 | * 52 | * @param \WC_Product $product the product which has had stock updated 53 | */ 54 | public static function SyncStockVariation( \WC_Product $product ) { 55 | //first remove this action to avoid recusive call when setting translation stock 56 | remove_action( 'woocommerce_variation_set_stock', array( __CLASS__, __FUNCTION__ ), 10 ); 57 | static::SyncStock( $product ); 58 | add_action( 'woocommerce_variation_set_stock', array( __CLASS__, __FUNCTION__ ), 10 ); 59 | } 60 | 61 | /* 62 | * Synchronise stock levels across translations any time stock is updated 63 | * through the product api [always now via wc_update_product_stock()] 64 | * 65 | * @param \WC_Product $product the product which has had stock updated 66 | */ 67 | public static function SyncStock( \WC_Product $product ) { 68 | //use same logic as wc_update_product_stock to get the product which is actually managing the stock 69 | $product_id_with_stock = $product->get_stock_managed_by_id(); 70 | $product_with_stock = $product_id_with_stock !== $product->get_id() ? wc_get_product( $product_id_with_stock ) : $product; 71 | 72 | //skip if not a valid product 73 | if ( $product_with_stock && $product_with_stock->get_id() ) { 74 | $targetValue = $product_with_stock->get_stock_quantity(); 75 | 76 | //update all the translations to the same stock level.. 77 | $product_translations = []; 78 | if ($product_with_stock->is_type( 'variation' )) { 79 | $base_variation_id = Utilities::get_translated_variation($product_with_stock->get_id(),pll_default_language()); 80 | $product_translations = Variation::getRelatedVariation( 81 | get_post_meta( $base_variation_id, Variation::DUPLICATE_KEY, true ) 82 | , true ); 83 | if ($base_variation_id!=$product_id_with_stock){ 84 | if (($key = array_search($product_id_with_stock, $product_translations)) !== false) { 85 | unset($product_translations[$key]); 86 | } 87 | $key = array_search($base_variation_id, $product_translations); 88 | if ( $key === false ) { 89 | $product_translations[]=$base_variation_id; 90 | } 91 | } 92 | } else { 93 | $product_translations = Utilities::getProductTranslationsArrayByObject( $product_with_stock ); 94 | } 95 | 96 | if ( $product_translations ) { 97 | $target_status=$product_with_stock->get_stock_status(); 98 | foreach ( $product_translations as $product_translation ) { 99 | if ( $product_translation != $product_with_stock->get_id() ) { 100 | $translation = wc_get_product( $product_translation ); 101 | if ( $translation ) { 102 | //here the product stock is updated without saving then wc_update_product_stock_status will update and save status 103 | wc_update_product_stock( $translation, $targetValue, 'set', true ); 104 | wc_update_product_stock_status ($product_translation, $target_status); 105 | if ($translation->get_parent_id()) { 106 | $prodparent = wc_get_product($translation->get_parent_id()); 107 | $parentstatus = $prodparent->get_stock_status(); 108 | wc_update_product_stock_status ($translation->get_parent_id(), $target_status); 109 | } 110 | } 111 | } 112 | } 113 | } 114 | } 115 | } 116 | 117 | } 118 | -------------------------------------------------------------------------------- /src/Hyyan/WPI/Tools/TranslationsDownloader.php: -------------------------------------------------------------------------------- 1 | . 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Hyyan\WPI\Tools; 12 | 13 | use Hyyan\WPI\HooksInterface; 14 | 15 | /** 16 | * TranslationsDownloader. 17 | * 18 | * @author Hyyan Abo Fakher 19 | */ 20 | class TranslationsDownloader 21 | { 22 | /** 23 | * Download translation files from woocommerce repo. 24 | * 25 | * @global \WP_Filesystem_Base $wp_filesystem 26 | * 27 | * @param string $locale locale 28 | * @param string $name language name 29 | * 30 | * @return bool true when the translation is downloaded successfully 31 | * 32 | * @throws \RuntimeException on errors 33 | */ 34 | public static function download($locale, $name) 35 | { 36 | /* Check if already downloaded */ 37 | if (static::isDownloaded($locale)) { 38 | return true; 39 | } 40 | 41 | /* Check if we can download */ 42 | if (!static::isAvaliable($locale)) { 43 | $notAvaliable = sprintf( 44 | __( 45 | 'WooCommerce translation %s can not be found in : %2$s', 'woo-poly-integration' 46 | ), sprintf('%s(%s)', $name, $locale), static::getRepoUrl() 47 | ); 48 | 49 | throw new \RuntimeException($notAvaliable); 50 | } 51 | 52 | /* Download the language pack */ 53 | $cantDownload = sprintf( 54 | __('Unable to download WooCommerce translation %s from : %2$s', 'woo-poly-integration'), sprintf('%s(%s)', $name, $locale), static::getRepoUrl() 55 | ); 56 | $response = wp_remote_get( 57 | sprintf('%s/%s.zip', static::getRepoUrl(), $locale), array('sslverify' => false, 'timeout' => 200) 58 | ); 59 | 60 | if ( 61 | !is_wp_error($response) && 62 | ($response['response']['code'] >= 200 && 63 | $response['response']['code'] < 300) 64 | ) { 65 | 66 | /* Initialize the WP filesystem, no more using 'file-put-contents' function */ 67 | global $wp_filesystem; 68 | if (empty($wp_filesystem)) { 69 | require_once ABSPATH.'/wp-admin/includes/file.php'; 70 | 71 | if (false === ($creds = request_filesystem_credentials('', '', false, false, null))) { 72 | throw new \RuntimeException($cantDownload); 73 | } 74 | 75 | if (!WP_Filesystem($creds)) { 76 | throw new \RuntimeException($cantDownload); 77 | } 78 | } 79 | 80 | $uploadDir = wp_upload_dir(); 81 | $file = trailingslashit($uploadDir['path']).$locale.'.zip'; 82 | 83 | /* Save the zip file */ 84 | if (!$wp_filesystem->put_contents($file, $response['body'], FS_CHMOD_FILE)) { 85 | throw new \RuntimeException($cantDownload); 86 | } 87 | 88 | /* Unzip the file to wp-content/languages/plugins directory */ 89 | $dir = trailingslashit(WP_LANG_DIR).'plugins/'; 90 | $unzip = unzip_file($file, $dir); 91 | if (true !== $unzip) { 92 | throw new \RuntimeException($cantDownload); 93 | } 94 | 95 | /* Delete the package file */ 96 | $wp_filesystem->delete($file); 97 | 98 | return true; 99 | } else { 100 | throw new \RuntimeException($cantDownload); 101 | } 102 | } 103 | 104 | /** 105 | * Check if the language pack is avaliable in the language repo. 106 | * 107 | * @param string $locale locale 108 | * 109 | * @return bool true if exists , false otherwise 110 | */ 111 | public static function isAvaliable($locale) 112 | { 113 | $response = wp_remote_get( 114 | sprintf('%s/%s.zip', static::getRepoUrl(), $locale), array('sslverify' => false, 'timeout' => 200) 115 | ); 116 | 117 | if ( 118 | !is_wp_error($response) && 119 | ($response['response']['code'] >= 200 && 120 | $response['response']['code'] < 300) 121 | ) { 122 | return true; 123 | } else { 124 | return false; 125 | } 126 | } 127 | 128 | /** 129 | * Check if WooCommerce language file is already downloaded. 130 | * 131 | * @param string $locale locale 132 | * 133 | * @return bool true if downloded , false otherwise 134 | */ 135 | public static function isDownloaded($locale) 136 | { 137 | return file_exists( 138 | sprintf( 139 | trailingslashit(WP_LANG_DIR) 140 | .'plugins/woocommerce-%s.mo', $locale 141 | ) 142 | ); 143 | } 144 | 145 | /** 146 | * Get language repo URL. 147 | * 148 | * @return string 149 | */ 150 | public static function getRepoUrl() 151 | { 152 | $url = sprintf( 153 | 'https://downloads.wordpress.org/translation/plugin/woocommerce/%s', WC()->version 154 | ); 155 | 156 | return apply_filters(HooksInterface::LANGUAGE_REPO_URL_FILTER, $url); 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/Hyyan/WPI/Gateways/GatewayBACS.php: -------------------------------------------------------------------------------- 1 | . 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Hyyan\WPI\Gateways; 12 | 13 | use Hyyan\WPI\Utilities; 14 | 15 | /** 16 | * Gateways BACS. 17 | * 18 | * Handle Payment Gateways BACS 19 | * 20 | * @author Antonio de Carvalho 21 | */ 22 | class GatewayBACS extends \WC_Gateway_BACS 23 | { 24 | /** 25 | * Output for the order received page. 26 | * 27 | * Note: The difference to WC_Gateway_BACS is that we use pll__() or __() 28 | * before passing the string through wptexturize() and wpautop(). 29 | * 30 | * @param int $order_id 31 | */ 32 | public function thankyou_page($order_id) 33 | { 34 | if ($this->instructions) { 35 | echo wpautop(wptexturize(wp_kses_post(function_exists('pll__') ? pll__($this->instructions) : __($this->instructions, 'woocommerce')))); 36 | } 37 | $this->bank_details($order_id); 38 | } 39 | 40 | /** 41 | * Add content to the WC emails. 42 | * 43 | * Note: The difference from WC_Gateway_BACS is that we use __() before 44 | * passing the string through wptexturize() and wpautop(). 45 | * 46 | * @param WC_Order $order 47 | * @param bool $sent_to_admin 48 | * @param bool $plain_text 49 | */ 50 | public function email_instructions($order, $sent_to_admin, $plain_text = false) 51 | { 52 | if (!$sent_to_admin && 'bacs' === Utilities::get_payment_method($order) && $order->has_status('on-hold')) { 53 | if ($this->instructions) { 54 | echo wpautop(wptexturize(function_exists('pll__') ? pll__($this->instructions) : __($this->instructions, 'woocommerce'))).PHP_EOL; 55 | } 56 | $this->bank_details(Utilities::get_orderid($order)); 57 | } 58 | } 59 | 60 | /** 61 | * Get bank details and place into a list format. 62 | * Updated from wooCommerce 3.1 63 | * 64 | * Note: Since this is declared as a private function in WC_Gateway_BACS, it needs 65 | * to be copied here 1:1 66 | * 67 | * @param int $order_id 68 | */ 69 | private function bank_details($order_id = '') 70 | { 71 | if (empty($this->account_details)) { 72 | return; 73 | } 74 | 75 | // Get order and store in $order 76 | $order = wc_get_order($order_id); 77 | 78 | // Get the order country and country $locale 79 | $country = $order->get_billing_country(); 80 | $locale = $this->get_country_locale(); 81 | 82 | // Get sortcode label in the $locale array and use appropriate one 83 | $sortcode = isset($locale[ $country ]['sortcode']['label']) ? $locale[ $country ]['sortcode']['label'] : __('Sort code', 'woocommerce'); 84 | 85 | $bacs_accounts = apply_filters('woocommerce_bacs_accounts', $this->account_details); 86 | 87 | if (! empty($bacs_accounts)) { 88 | $account_html = ''; 89 | $has_details = false; 90 | 91 | foreach ($bacs_accounts as $bacs_account) { 92 | $bacs_account = (object) $bacs_account; 93 | 94 | if ($bacs_account->account_name) { 95 | $account_html .= '' . PHP_EOL; 96 | } 97 | 98 | $account_html .= '
    ' . PHP_EOL; 99 | 100 | // BACS account fields shown on the thanks page and in emails 101 | $account_fields = apply_filters('woocommerce_bacs_account_fields', array( 102 | 'bank_name' => array( 103 | 'label' => __('Bank', 'woocommerce'), 104 | 'value' => $bacs_account->bank_name, 105 | ), 106 | 'account_number' => array( 107 | 'label' => __('Account number', 'woocommerce'), 108 | 'value' => $bacs_account->account_number, 109 | ), 110 | 'sort_code' => array( 111 | 'label' => $sortcode, 112 | 'value' => $bacs_account->sort_code, 113 | ), 114 | 'iban' => array( 115 | 'label' => __('IBAN', 'woocommerce'), 116 | 'value' => $bacs_account->iban, 117 | ), 118 | 'bic' => array( 119 | 'label' => __('BIC', 'woocommerce'), 120 | 'value' => $bacs_account->bic, 121 | ), 122 | ), $order_id); 123 | 124 | foreach ($account_fields as $field_key => $field) { 125 | if (! empty($field['value'])) { 126 | $account_html .= '
  • ' . wp_kses_post($field['label']) . ': ' . wp_kses_post(wptexturize($field['value'])) . '
  • ' . PHP_EOL; 127 | $has_details = true; 128 | } 129 | } 130 | 131 | $account_html .= '
'; 132 | } 133 | 134 | if ($has_details) { 135 | echo '

' . __('Our bank details', 'woocommerce') . '

' . PHP_EOL . $account_html . '
'; 136 | } 137 | } 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/Hyyan/WPI/LocaleNumbers.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | 9 | namespace Hyyan\WPI; 10 | 11 | use Hyyan\WPI\Admin\Settings; 12 | use Hyyan\WPI\Admin\Features; 13 | 14 | class LocaleNumbers 15 | { 16 | 17 | /** 18 | * Hook relevant WooCommerce filters to apply localisation according to Polylang locale. 19 | */ 20 | public function __construct() 21 | { 22 | if ( 23 | class_exists('\NumberFormatter') && 24 | 'on' === Settings::getOption('localenumbers', Features::getID(), 'on') 25 | ) { 26 | 27 | //localise standard price formatting arguments 28 | add_filter('wc_get_price_decimal_separator', array($this, 'getLocaleDecimalSeparator'), 10, 1); 29 | add_filter('wc_get_price_thousand_separator', array($this, 'getLocaleThousandSeparator'), 10, 1); 30 | add_filter('wc_price_args', array($this, 'filterPriceArgs'), 10, 1); 31 | 32 | //WooCommerce 3.1 unreleased checkin https://github.com/woocommerce/woocommerce/pull/15628 33 | add_filter('woocommerce_format_localized_decimal', array($this, 'getLocalizedDecimal'), 10, 2); 34 | //no additional override on finished price format as no currency paramber available 35 | //add_filter('woocommerce_format_localized_price', array($this, 'getLocalizedPrice'), 10, 2); 36 | } 37 | } 38 | 39 | /* 40 | * Filter WooCommerce pricing arguments to localize 41 | * see https://github.com/hyyan/woo-poly-integration/wiki/Price-Localization for notes 42 | * 43 | * @param Array $args arguments used with wc_price 44 | * 'ex_tax_label' => false, 45 | * 'currency' => '', 46 | * 'decimal_separator' => wc_get_price_decimal_separator(), 47 | * 'thousand_separator' => wc_get_price_thousand_separator(), 48 | * 'decimals' => wc_get_price_decimals(), 49 | * 'price_format' => get_woocommerce_price_format(), 50 | * 51 | * @return Array the arguments 52 | */ 53 | 54 | public function filterPriceArgs($args) 55 | { 56 | 57 | //if there is a currency provided, attempt a full reset of formatting parameters 58 | if ((isset($args['currency'])) && ($args['currency'] != '')) { 59 | $currency = $args['currency']; 60 | $locale = pll_current_language('locale'); 61 | $formatter = new \NumberFormatter($locale . '@currency=' . $currency, \NumberFormatter::CURRENCY); 62 | $args['decimal_separator'] = $formatter->getSymbol(\NumberFormatter::DECIMAL_SEPARATOR_SYMBOL); 63 | $args['thousand_separator'] = $formatter->getSymbol(\NumberFormatter::GROUPING_SEPARATOR_SYMBOL); 64 | $args['decimals'] = $formatter->getAttribute(\NumberFormatter::FRACTION_DIGITS); 65 | $prefix = $formatter->getTextAttribute(\NumberFormatter::POSITIVE_PREFIX); 66 | if (strlen($prefix)) { 67 | $args['price_format'] = '%1$s%2$s'; 68 | } else { 69 | $args['price_format'] = '%2$s%1$s'; 70 | } 71 | } else { 72 | //otherwise if no currency is set, get localized separators only as other parms depend on currency 73 | $args['decimal_separator'] = $this->getLocaleDecimalSeparator($args['decimal_separator']); 74 | $args['thousand_separator'] = $this->getLocaleThousandSeparator($args['decimal_separator']); 75 | } 76 | return $args; 77 | } 78 | 79 | /* 80 | * get localized getLocalizedDecimal 81 | * 82 | * @param string default WooCommerce formatting of value 83 | * @param string $input value to be formatted 84 | * 85 | * @return string formatted number 86 | */ 87 | 88 | public function getLocalizedDecimal($wooFormattedValue, $input) 89 | { 90 | //default to return unmodified wooCommerce value 91 | $retval = $wooFormattedValue; 92 | 93 | //don't touch values on admin screens, save as plain number using woo defaults 94 | if ((!is_admin()) || isset($_REQUEST['get_product_price_by_ajax'])) { 95 | $a = new \NumberFormatter(pll_current_language('locale'), \NumberFormatter::DECIMAL); 96 | if ($a) { 97 | $retval = $a->format($input, \NumberFormatter::TYPE_DOUBLE); 98 | } 99 | } 100 | return $retval; 101 | } 102 | 103 | /* 104 | * get localized decimal separator 105 | * 106 | * @param string $input WooCommerce configured value 107 | * 108 | * @return string formatted number 109 | */ 110 | 111 | public function getLocaleDecimalSeparator($separator) 112 | { 113 | $retval = $separator; 114 | //don't touch values on admin screens, save as plain number using woo defaults 115 | if ((!is_admin()) || isset($_REQUEST['get_product_price_by_ajax'])) { 116 | $locale = pll_current_language('locale'); 117 | $a = new \NumberFormatter($locale, \NumberFormatter::DECIMAL); 118 | if ($a) { 119 | $locale_result = $a->getSymbol(\NumberFormatter::DECIMAL_SEPARATOR_SYMBOL); 120 | if ($locale_result) { 121 | $retval = $locale_result; 122 | } 123 | } 124 | } 125 | return $retval; 126 | } 127 | 128 | /* 129 | * get localized thousand separator 130 | * 131 | * @param string $input WooCommerce configured value 132 | * 133 | * @return string formatted number 134 | */ 135 | 136 | public function getLocaleThousandSeparator($separator) 137 | { 138 | $retval = $separator; 139 | //don't touch values on admin screens, save as plain number using woo defaults 140 | if (!is_admin()) { 141 | $a = new \NumberFormatter(pll_current_language('locale'), \NumberFormatter::DECIMAL); 142 | if ($a) { 143 | $retval = $a->getSymbol(\NumberFormatter::GROUPING_SEPARATOR_SYMBOL); 144 | } 145 | } 146 | return $retval; 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/Hyyan/WPI/Pages.php: -------------------------------------------------------------------------------- 1 | . 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Hyyan\WPI; 12 | 13 | /** 14 | * Pages. 15 | * 16 | * Handle page translations 17 | * 18 | * @author Hyyan Abo Fakher 19 | */ 20 | class Pages 21 | { 22 | 23 | /** 24 | * Construct object. 25 | */ 26 | public function __construct() 27 | { 28 | $method = array($this, 'getPostTranslationID'); 29 | $pages = apply_filters(HooksInterface::PAGES_LIST, array( 30 | 'shop', 31 | 'cart', 32 | 'checkout', 33 | 'terms', 34 | 'myaccount', 35 | )); 36 | 37 | foreach ($pages as $page) { 38 | add_filter(sprintf('woocommerce_get_%s_page_id', $page), $method); 39 | add_filter(sprintf('option_woocommerce_%s_page_id', $page), $method); 40 | } 41 | 42 | /* To generate the correct url for shop page */ 43 | add_filter( 44 | 'pll_get_archive_url', array($this, 'translateShopUrl'), 10, 2 45 | ); 46 | 47 | if (!is_admin()) { 48 | 49 | /* To get product from current language in the shop page */ 50 | add_filter('parse_request', array($this, 'correctShopPage')); 51 | } 52 | 53 | add_filter( 54 | 'woocommerce_shortcode_products_query', array($this, 'addShortcodeLanguageFilter'), 10, 2 55 | ); 56 | add_filter( 'shortcode_atts_product_categories', array( $this, 'addShortcodeLanguageFilterCategories' ), 10, 4 ); 57 | } 58 | 59 | /** 60 | * Get the id of translated post. 61 | * 62 | * @param int $id the post to get translation id for 63 | * 64 | * @return int 65 | */ 66 | public function getPostTranslationID($id) 67 | { 68 | if (!function_exists('pll_get_post')) { 69 | return $id; 70 | } 71 | 72 | $translatedID = pll_get_post($id); 73 | 74 | if ($translatedID) { 75 | return $translatedID; 76 | } 77 | 78 | return $id; 79 | } 80 | 81 | /** 82 | * Correct the shop page to display products from currrent language only. 83 | * 84 | * @param \WP $wp wordpress instance 85 | * 86 | * @return bool false if the current language is the same as default 87 | * language or if the "pagename" var is empty 88 | */ 89 | public function correctShopPage(\WP $wp) 90 | { 91 | global $polylang; 92 | 93 | $shopID = wc_get_page_id('shop'); 94 | $shopOnFront = ('page' === get_option('show_on_front')) && in_array( 95 | get_option('page_on_front'), PLL()->model->post->get_translations( 96 | $shopID 97 | )); 98 | 99 | $vars = array('pagename', 'page', 'name'); 100 | foreach ($vars as $var) { 101 | if (isset($wp->query_vars[$var])) { 102 | $shopOnFront = false; 103 | break; 104 | } 105 | } 106 | if (!$shopOnFront) { 107 | if (!empty($wp->query_vars['pagename'])) { 108 | $shopPage = get_post($shopID); 109 | 110 | /* Explode by / for children page */ 111 | $page = explode('/', $wp->query_vars['pagename']); 112 | 113 | if ( 114 | isset($shopPage->post_name) && 115 | $shopPage->post_name == $page[count($page) - 1] 116 | ) { 117 | unset($wp->query_vars['page']); 118 | unset($wp->query_vars['pagename']); 119 | $wp->query_vars['post_type'] = 'product'; 120 | } 121 | } 122 | } 123 | } 124 | 125 | /** 126 | * Translate the shop page name in the given shop url. 127 | * 128 | * @param string $url complete url 129 | * @param string $language the current language 130 | * 131 | * @return string translated url 132 | */ 133 | public function translateShopUrl($url, $language) 134 | { 135 | $result = $url; 136 | 137 | if (!is_post_type_archive('product')) { 138 | return $result; 139 | } 140 | 141 | if ( is_front_page() ) { 142 | return $result; 143 | } 144 | 145 | $shopPageID = get_option('woocommerce_shop_page_id'); 146 | $shopPage = get_post($shopPageID); 147 | 148 | if ($shopPage) { 149 | $shopPageTranslatedID = pll_get_post($shopPageID, $language); 150 | $shopPageTranslation = get_post($shopPageTranslatedID); 151 | 152 | if ($shopPageTranslation) { 153 | $slug = $shopPage->post_name; 154 | if ( $slug != $shopPageTranslation->post_name ) { 155 | $result = substr_replace( $url, $shopPageTranslation->post_name, strrpos( $url, $slug ), strlen( $slug ) ); 156 | } 157 | } 158 | } 159 | 160 | return $result; 161 | } 162 | 163 | /** 164 | * Add Shortcode Language Filter 165 | * 166 | * Fix shortcodes to include language filter. 167 | * 168 | * @param array $query_args 169 | * @param array $atts 170 | * @param string $loop_name -- not provided by some shortcodes 171 | * 172 | * @return string modified form 173 | */ 174 | public function addShortcodeLanguageFilter($query_args, $atts) 175 | { 176 | if (isset($atts['ids']) && strlen($atts['ids'])) { 177 | $ids = explode(',', $atts['ids']); 178 | $transIds = array(); 179 | foreach ($ids as $id) { 180 | array_push($transIds, pll_get_post($id)); 181 | } 182 | 183 | $atts['ids'] = implode($transIds, ','); 184 | $query_args['post__in'] = $transIds; 185 | if ( isset( $query_args[ 'p' ] ) && ( $query_args[ 'p' ] != $transIds) ) { 186 | unset( $query_args[ 'p' ] ); 187 | } 188 | } else { 189 | $query_args['lang'] = isset($query_args['lang']) ? 190 | $query_args['lang'] : pll_current_language(); 191 | } 192 | 193 | return $query_args; 194 | } 195 | 196 | public function addShortcodeLanguageFilterCategories( $out, $pairs, $atts, $shortcode ) { 197 | 198 | if ( isset( $atts[ 'ids' ] ) && strlen( $atts[ 'ids' ] ) ) { 199 | $ids = explode( ',', $atts[ 'ids' ] ); 200 | $transIds = array(); 201 | foreach ( $ids as $id ) { 202 | array_push( $transIds, pll_get_term( $id ) ); 203 | } 204 | $out[ 'ids' ] = implode( $transIds, ',' ); 205 | } 206 | 207 | if ( isset( $atts[ 'parent' ] ) && strlen( $atts[ 'parent' ] ) ) { 208 | $transParent = pll_get_term( $atts[ 'parent' ] ); 209 | if ( $transParent ) { 210 | $out[ 'parent' ] = $transParent; 211 | } 212 | } 213 | return $out; 214 | } 215 | 216 | } 217 | -------------------------------------------------------------------------------- /src/Hyyan/WPI/Shipping.php: -------------------------------------------------------------------------------- 1 | . 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Hyyan\WPI; 12 | 13 | use Hyyan\WPI\Admin\Settings; 14 | use Hyyan\WPI\Admin\Features; 15 | use Hyyan\WPI\Utilities; 16 | 17 | /** 18 | * Shipping. 19 | * 20 | * Handle Shipping Methods 21 | * 22 | * @author Antonio de Carvalho 23 | */ 24 | class Shipping 25 | { 26 | 27 | /** 28 | * Construct object. 29 | */ 30 | public function __construct() 31 | { 32 | 33 | // Register WooCommerce shipping method custom names in polylang strings translations table 34 | // called only after Wordpress is loaded 35 | add_action('wp_loaded', array($this, 'registerShippingStringsForTranslation')); 36 | 37 | // Shipping method in the Cart and Checkout pages 38 | add_filter('woocommerce_shipping_rate_label', array($this, 'translateShippingLabel'), 10, 1); 39 | 40 | // Shipping method in My Account page, Order Emails and Paypal requests 41 | add_filter('woocommerce_order_shipping_method', array($this, 'translateOrderShippingMethod'), 10, 2); 42 | } 43 | 44 | /** 45 | * Disable Settings. 46 | * 47 | * @return false if the current post type is not "product" 48 | */ 49 | public function disableSettings() 50 | { 51 | $currentScreen = function_exists( 'get_current_screen' ) ? get_current_screen() : false; 52 | if ($currentScreen && $currentScreen->id !== 'settings_page_hyyan-wpi') { 53 | return false; 54 | } 55 | 56 | add_action('admin_print_scripts', array($this, 'disableShippingClassFeature'), 100); 57 | } 58 | 59 | /** 60 | * Add the disable Shipping Class translation feature script. 61 | * 62 | * The script will disable enabling the Shipping Class translation feature 63 | */ 64 | public function disableShippingClassFeature() 65 | { 66 | $jsID = 'shipping-class-translation-disabled'; 67 | $code = '$( "#wpuf-wpi-features\\\[shipping-class\\\]" ).prop( "disabled", true );'; 68 | 69 | // To use any of the meta-characters ( such as !"#$%&'()*+,./:;<=>?@[]^`{|}~ ) 70 | // as a literal part of a name, it must be escaped with with two backslashes: \\. 71 | // Because jsScriptWrapper() uses sprintf() it will treat one backslash as escape 72 | // character, so we need to add a 3rd (crazy!) backslashes. 73 | 74 | Utilities::jsScriptWrapper($jsID, $code); 75 | } 76 | 77 | /** 78 | * Helper function - Gets the shipping methods enabled in the shop. 79 | * 80 | * @return array $active_methods The id and respective plugin id of all active methods 81 | */ 82 | private function getActiveShippingMethods() 83 | { 84 | $active_methods = array(); 85 | 86 | // Format: $shipping_methods[method_id] => shipping_method_object 87 | // where methods_id is e.g. flat_rate, free_shiping, local_pickup, etc 88 | $shipping_methods = $this->getZonesShippingMethods(); 89 | 90 | foreach ($shipping_methods as $id => $shipping_method) { 91 | if (isset($shipping_method->enabled) && 'yes' === $shipping_method->enabled) { 92 | $active_methods[$id] = $shipping_method->plugin_id; 93 | } 94 | } 95 | 96 | return $active_methods; 97 | } 98 | 99 | /** 100 | * Get the shipping methods for all shipping zones. 101 | * 102 | * Note: WooCommerce 2.6 intoduces the concept of Shipping Zones 103 | * 104 | * @return array (Array of) all shipping methods instances 105 | */ 106 | public function getZonesShippingMethods() 107 | { 108 | $zones = array(); 109 | 110 | // Rest of the World zone 111 | $zone = new \WC_Shipping_Zone(); 112 | $zones[$zone->get_id()] = $zone->get_data(); 113 | $zones[$zone->get_id()]['formatted_zone_location'] = $zone->get_formatted_location(); 114 | $zones[$zone->get_id()]['shipping_methods'] = $zone->get_shipping_methods(); 115 | 116 | // Add user configured zones 117 | $zones = array_merge($zones, \WC_Shipping_Zones::get_zones()); 118 | 119 | $shipping_methods = array(); 120 | 121 | // Format: $shipping_methods[zone_name_method_id] => shipping_method_object 122 | // where zone_name is e.g. domestic, europe, rest_of_the_world, and 123 | // methods_id is e.g. flat_rate, free_shiping, local_pickup, etc 124 | foreach ($zones as $zone) { 125 | foreach ($zone['shipping_methods'] as $instance_id => $shipping_method) { 126 | // Zone names are converted to all lower-case and spaces replaced with 127 | $shipping_methods[$shipping_method->id . '_' . $instance_id] = $shipping_method; 128 | } 129 | } 130 | 131 | return $shipping_methods; 132 | } 133 | 134 | /** 135 | * Register shipping method custom titles in Polylang's Strings translations table. 136 | */ 137 | public function registerShippingStringsForTranslation() 138 | { 139 | if (function_exists('pll_register_string')) { 140 | $shipping_methods = $this->getActiveShippingMethods(); 141 | 142 | foreach ($shipping_methods as $method_id => $plugin_id) { 143 | $setting = get_option($plugin_id . $method_id . '_settings'); 144 | 145 | if ($setting && isset($setting['title'])) { 146 | pll_register_string($plugin_id . $method_id . '_shipping_method', $setting['title'], __('WooCommerce Shipping Methods', 'woo-poly-integration')); 147 | } 148 | } 149 | } 150 | } 151 | 152 | /** 153 | * Translate shipping label in the Cart and Checkout pages. 154 | * 155 | * @param string $label Shipping method label 156 | * 157 | * @return string Translated label 158 | */ 159 | public function translateShippingLabel($label) 160 | { 161 | return function_exists('pll__') ? pll__($label) : __($label, 'woocommerce'); 162 | } 163 | 164 | /** 165 | * Translate shipping method title in My Account page, Order Emails and Paypal requests. 166 | * 167 | * @param string $implode Comma separated string of shipping methods used in order 168 | * @param WC_Order $instance Order instance 169 | * 170 | * @return string Comma separated string of translated shipping methods' titles 171 | */ 172 | public function translateOrderShippingMethod($implode, $instance) 173 | { 174 | 175 | // Convert the imploded array again to an array that is easy to manipulate 176 | $shipping_methods = explode(', ', $implode); 177 | 178 | // Array with translated shipping methods 179 | $translated = array(); 180 | 181 | foreach ($shipping_methods as $shipping) { 182 | if (function_exists('pll__')) { 183 | $translated[] = pll__($shipping); 184 | } else { 185 | $translated[] = __($shipping, 'woocommerce'); 186 | } 187 | } 188 | 189 | // Implode array to string again 190 | $translated_implode = implode(', ', $translated); 191 | 192 | return $translated_implode; 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /public/js/Cart.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the hyyan/woo-poly-integration plugin. 3 | * (c) Hyyan Abo Fakher 4 | * 5 | * For the full copyright and license information, please view the LICENSE 6 | * file that was distributed with this source code. 7 | * 8 | * Modified WooCommerce cart-fragments.js script to break HTML5 fragment caching. 9 | * Useful when switching languages. Adds support new Cart page ajax. 10 | * 11 | * Updated in line with WooCommerce 3.6.3 cart-fragments.js, 12 | * only difference is the additional Polylang lines... and these commments 13 | **/ 14 | 15 | /* global wc_cart_fragments_params, Cookies */ 16 | jQuery( function( $ ) { 17 | 18 | // wc_cart_fragments_params is required to continue, ensure the object exists 19 | if ( typeof wc_cart_fragments_params === 'undefined' ) { 20 | return false; 21 | } 22 | 23 | /* Storage Handling */ 24 | var $supports_html5_storage = true, 25 | cart_hash_key = wc_cart_fragments_params.cart_hash_key; 26 | 27 | try { 28 | $supports_html5_storage = ( 'sessionStorage' in window && window.sessionStorage !== null ); 29 | window.sessionStorage.setItem( 'wc', 'test' ); 30 | window.sessionStorage.removeItem( 'wc' ); 31 | window.localStorage.setItem( 'wc', 'test' ); 32 | window.localStorage.removeItem( 'wc' ); 33 | } catch( err ) { 34 | $supports_html5_storage = false; 35 | } 36 | 37 | /* Cart session creation time to base expiration on */ 38 | function set_cart_creation_timestamp() { 39 | if ( $supports_html5_storage ) { 40 | sessionStorage.setItem( 'wc_cart_created', ( new Date() ).getTime() ); 41 | } 42 | } 43 | 44 | /** Set the cart hash in both session and local storage */ 45 | function set_cart_hash( cart_hash ) { 46 | if ( $supports_html5_storage ) { 47 | localStorage.setItem( cart_hash_key, cart_hash ); 48 | sessionStorage.setItem( cart_hash_key, cart_hash ); 49 | } 50 | } 51 | 52 | /* Get current Polylang language */ 53 | function get_pll_language() { 54 | var pll_lang = $.cookie('pll_language'); 55 | 56 | if (pll_lang === null || pll_lang === undefined || pll_lang === '') { 57 | pll_lang = ''; 58 | } 59 | 60 | return pll_lang; 61 | } 62 | 63 | var $fragment_refresh = { 64 | url: wc_cart_fragments_params.wc_ajax_url.toString().replace( '%%endpoint%%', 'get_refreshed_fragments' ), 65 | type: 'POST', 66 | data: { 67 | time: new Date().getTime() 68 | }, 69 | timeout: wc_cart_fragments_params.request_timeout, 70 | success: function( data ) { 71 | if ( data && data.fragments ) { 72 | 73 | $.each( data.fragments, function( key, value ) { 74 | $( key ).replaceWith( value ); 75 | }); 76 | 77 | if ( $supports_html5_storage ) { 78 | sessionStorage.setItem( wc_cart_fragments_params.fragment_name, JSON.stringify( data.fragments ) ); 79 | set_cart_hash( data.cart_hash ); 80 | 81 | if ( data.cart_hash ) { 82 | set_cart_creation_timestamp(); 83 | } 84 | } 85 | 86 | $( document.body ).trigger( 'wc_fragments_refreshed' ); 87 | } 88 | }, 89 | error: function() { 90 | $( document.body ).trigger( 'wc_fragments_ajax_error' ); 91 | } 92 | }; 93 | 94 | /* Named callback for refreshing cart fragment */ 95 | function refresh_cart_fragment() { 96 | $.ajax( $fragment_refresh ); 97 | } 98 | 99 | /* Cart Handling */ 100 | if ( $supports_html5_storage ) { 101 | 102 | var cart_timeout = null, 103 | day_in_ms = ( 24 * 60 * 60 * 1000 ); 104 | 105 | $( document.body ).on( 'wc_fragment_refresh updated_wc_div', function() { 106 | refresh_cart_fragment(); 107 | }); 108 | 109 | $( document.body ).on( 'added_to_cart removed_from_cart', function( event, fragments, cart_hash ) { 110 | var prev_cart_hash = sessionStorage.getItem( cart_hash_key ); 111 | 112 | if ( prev_cart_hash === null || prev_cart_hash === undefined || prev_cart_hash === '' ) { 113 | set_cart_creation_timestamp(); 114 | } 115 | 116 | sessionStorage.setItem( wc_cart_fragments_params.fragment_name, JSON.stringify( fragments ) ); 117 | set_cart_hash( cart_hash ); 118 | }); 119 | 120 | $( document.body ).on( 'wc_fragments_refreshed', function() { 121 | clearTimeout( cart_timeout ); 122 | cart_timeout = setTimeout( refresh_cart_fragment, day_in_ms ); 123 | } ); 124 | 125 | // Refresh when storage changes in another tab 126 | $( window ).on( 'storage onstorage', function ( e ) { 127 | if ( 128 | cart_hash_key === e.originalEvent.key && localStorage.getItem( cart_hash_key ) !== sessionStorage.getItem( cart_hash_key ) 129 | ) { 130 | refresh_cart_fragment(); 131 | } 132 | }); 133 | 134 | // Refresh when page is shown after back button (safari) 135 | $( window ).on( 'pageshow' , function( e ) { 136 | if ( e.originalEvent.persisted ) { 137 | $( '.widget_shopping_cart_content' ).empty(); 138 | $( document.body ).trigger( 'wc_fragment_refresh' ); 139 | } 140 | } ); 141 | 142 | try { 143 | var wc_fragments = JSON.parse( sessionStorage.getItem( wc_cart_fragments_params.fragment_name ) ), 144 | cart_hash = sessionStorage.getItem( cart_hash_key ), 145 | cookie_hash = Cookies.get( 'woocommerce_cart_hash'), 146 | cart_created = sessionStorage.getItem( 'wc_cart_created' ); 147 | 148 | if ( cart_hash === null || cart_hash === undefined || cart_hash === '' ) { 149 | cart_hash = ''; 150 | } 151 | 152 | if ( cookie_hash === null || cookie_hash === undefined || cookie_hash === '' ) { 153 | cookie_hash = ''; 154 | } 155 | 156 | if ( cart_hash && ( cart_created === null || cart_created === undefined || cart_created === '' ) ) { 157 | throw 'No cart_created'; 158 | } 159 | 160 | if ( cart_created ) { 161 | var cart_expiration = ( ( 1 * cart_created ) + day_in_ms ), 162 | timestamp_now = ( new Date() ).getTime(); 163 | if ( cart_expiration < timestamp_now ) { 164 | throw 'Fragment expired'; 165 | } 166 | cart_timeout = setTimeout( refresh_cart_fragment, ( cart_expiration - timestamp_now ) ); 167 | } 168 | 169 | if ( wc_fragments && wc_fragments['div.widget_shopping_cart_content'] && cart_hash === cookie_hash ) { 170 | 171 | $.each( wc_fragments, function( key, value ) { 172 | $( key ).replaceWith(value); 173 | }); 174 | 175 | $( document.body ).trigger( 'wc_fragments_loaded' ); 176 | } else { 177 | throw 'No fragment'; 178 | } 179 | 180 | // Refresh when the display language changes 181 | var prev_pll_lang = sessionStorage.getItem('pll_language'), 182 | pll_lang = get_pll_language(); 183 | 184 | if (prev_pll_lang === null || prev_pll_lang === undefined || prev_pll_lang === '') { 185 | prev_pll_lang = ''; 186 | } 187 | 188 | if (pll_lang) { 189 | if (!prev_pll_lang || prev_pll_lang !== pll_lang) { 190 | sessionStorage.setItem('pll_language', pll_lang); 191 | throw 'Language changed'; 192 | } 193 | } else { 194 | throw 'Language not found'; 195 | } 196 | 197 | } catch (err) { 198 | refresh_cart_fragment(); 199 | } 200 | 201 | } else { 202 | refresh_cart_fragment(); 203 | } 204 | 205 | /* Cart Hiding */ 206 | if ( Cookies.get( 'woocommerce_items_in_cart' ) > 0 ) { 207 | $( '.hide_cart_widget_if_empty' ).closest( '.widget_shopping_cart' ).show(); 208 | } else { 209 | $( '.hide_cart_widget_if_empty' ).closest( '.widget_shopping_cart' ).hide(); 210 | } 211 | 212 | $( document.body ).on( 'adding_to_cart', function() { 213 | $( '.hide_cart_widget_if_empty' ).closest( '.widget_shopping_cart' ).show(); 214 | }); 215 | 216 | // Customiser support. 217 | var hasSelectiveRefresh = ( 218 | 'undefined' !== typeof wp && 219 | wp.customize && 220 | wp.customize.selectiveRefresh && 221 | wp.customize.widgetsPreview && 222 | wp.customize.widgetsPreview.WidgetPartial 223 | ); 224 | if ( hasSelectiveRefresh ) { 225 | wp.customize.selectiveRefresh.bind( 'partial-content-rendered', function() { 226 | refresh_cart_fragment(); 227 | } ); 228 | } 229 | }); 230 | -------------------------------------------------------------------------------- /src/Hyyan/WPI/Admin/Features.php: -------------------------------------------------------------------------------- 1 | . 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Hyyan\WPI\Admin; 12 | 13 | use Hyyan\WPI\Utilities; 14 | 15 | /** 16 | * Features. 17 | * 18 | * @author Hyyan Abo Fakher 19 | */ 20 | class Features extends AbstractSettings 21 | { 22 | /** 23 | * {@inheritdoc} 24 | */ 25 | public static function getID() 26 | { 27 | return 'wpi-features'; 28 | } 29 | 30 | /** 31 | * {@inheritdoc} 32 | */ 33 | protected function doGetSections() 34 | { 35 | return array( 36 | array( 37 | 'title' => __('Features', 'woo-poly-integration'), 38 | 'desc' => __( 39 | ' The section will allow you to Enable/Disable 40 | Plugin Features.', 'woo-poly-integration' 41 | ) . ' ' . __( 42 | 'For more information please see:', 'woo-poly-integration' 43 | ) . ' ' . 44 | __('documentation pages', 'woo-poly-integration') . '.', 45 | ), 46 | ); 47 | } 48 | 49 | /** 50 | * {@inheritdoc} 51 | */ 52 | protected function doGetFields() 53 | { 54 | return array( 55 | array( 56 | 'name' => 'fields-locker', 57 | 'type' => 'checkbox', 58 | 'default' => 'on', 59 | 'label' => __('Fields Locker', 'woo-poly-integration'), 60 | 'desc' => __( 61 | 'Locks Meta fields which are set to be synchronized.', 'woo-poly-integration' 62 | ), 63 | ), 64 | array( 65 | 'name' => 'emails', 66 | 'type' => 'checkbox', 67 | 'default' => 'on', 68 | 'label' => __('Emails', 'woo-poly-integration'), 69 | 'desc' => __( 70 | 'Use order language whenever WooCommerce sends order emails', 'woo-poly-integration' 71 | ), 72 | ), 73 | array( 74 | 'name' => 'reports', 75 | 'type' => 'checkbox', 76 | 'default' => 'on', 77 | 'label' => __('Reports', 'woo-poly-integration'), 78 | 'desc' => __( 79 | 'Enable reports language filtering and combining', 'woo-poly-integration' 80 | ), 81 | ), 82 | array( 83 | 'name' => 'coupons', 84 | 'type' => 'checkbox', 85 | 'default' => 'on', 86 | 'label' => __('Coupons Sync', 'woo-poly-integration'), 87 | 'desc' => __( 88 | 'Apply coupons rules for product and its translations', 'woo-poly-integration' 89 | ), 90 | ), 91 | array( 92 | 'name' => 'stock', 93 | 'type' => 'checkbox', 94 | 'default' => 'on', 95 | 'label' => __('Stock Sync', 'woo-poly-integration'), 96 | 'desc' => ' '. 97 | __( 98 | 'Sync stock for product and its translations', 'woo-poly-integration' 99 | ) . '. ' . __( 100 | 'Note: this setting affects user actions on stock, to control synchronisation when editing products check the settings for Metas List, Stock Metas.', 'woo-poly-integration', 'woo-poly-integration' 101 | ) . '', 102 | ), 103 | array( 104 | 'name' => 'categories', 105 | 'type' => 'checkbox', 106 | 'default' => 'on', 107 | 'label' => __('Translate Categories', 'woo-poly-integration'), 108 | 'desc' => ' '. 109 | __( 110 | 'Enable categories translations', 'woo-poly-integration' 111 | ) . '.', 112 | ), 113 | array( 114 | 'name' => 'tags', 115 | 'type' => 'checkbox', 116 | 'default' => 'on', 117 | 'label' => __('Translate Tags', 'woo-poly-integration'), 118 | 'desc' => ' '. 119 | __( 120 | 'Enable tags translations', 'woo-poly-integration' 121 | ) . '.', 122 | ), 123 | array( 124 | 'name' => 'attributes', 125 | 'type' => 'checkbox', 126 | 'default' => 'on', 127 | 'label' => __('Translate Attributes', 'woo-poly-integration'), 128 | 'desc' => ' '. 129 | __( 130 | 'Enable attributes translations', 'woo-poly-integration' 131 | ) . '.', 132 | ), 133 | 134 | array( 135 | 'name' => 'new-translation-defaults', 136 | 'type' => 'radio', 137 | 'default' => '0', //starting this off for backwards compatibility, users should test before turning on 138 | 'label' => __('New Translation Behaviour', 'woo-poly-integration'), 139 | 'desc' => __( 140 | 'When creating new translations, start with blank text, copy or machine translation?' . 141 | ' (You may want to turn this off if using Polylang Pro, Lingotek or other automatic copy-or-translation solution.) ', 'woo-poly-integration' 142 | ), 143 | 'options' => array( 144 | '0' => 'Blank Text', 145 | '1' => __('Copy Source', 'woo-poly-integration'), 146 | '2' => __('Translate Source', 'woo-poly-integration') . ' (coming soon.. until available will use Copy Source) ', 147 | ) 148 | ), 149 | array( 150 | 'name' => 'localenumbers', 151 | 'type' => 'checkbox', 152 | 'default' => 'on', 153 | 'label' => __('Use locale number formats', 'woo-poly-integration'), 154 | 'desc' => ' ' . 155 | __( 156 | 'Format numbers according to the convention for current language', 'woo-poly-integration') 157 | . '.' 158 | , 159 | ), 160 | array( 161 | 'name' => 'importsync', 162 | 'type' => 'checkbox', 163 | 'default' => 'on', 164 | 'label' => __('Synchronize on Import', 'woo-poly-integration'), 165 | 'desc' => ' ' . 166 | __( 167 | 'When using WooCommerce 3.1 importer to importing updates to existing items, apply synchronization rules to update any existing translations.', 'woo-poly-integration') 168 | . '.' 169 | , 170 | ), 171 | array( 172 | 'name' => 'checkpages', 173 | 'type' => 'checkbox', 174 | 'default' => 'off', 175 | 'label' => __( 'Check WooCommerce Pages', 'woo-poly-integration' ), 176 | 'desc' => '' . 177 | __( 178 | 'Check if WooCommerce pages are present and correctly translated and published. Especially helpul on initial setup', 'woo-poly-integration' ) 179 | . '.' 180 | , 181 | ), 182 | ); 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /src/Hyyan/WPI/Taxonomies/Taxonomies.php: -------------------------------------------------------------------------------- 1 | . 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Hyyan\WPI\Taxonomies; 12 | 13 | use Hyyan\WPI\Admin\Settings; 14 | use Hyyan\WPI\Admin\Features; 15 | 16 | /** 17 | * Taxonomies. 18 | * 19 | * @author Hyyan Abo Fakher 20 | */ 21 | class Taxonomies 22 | { 23 | /** 24 | * Managed taxonomies. 25 | * 26 | * @var array 27 | */ 28 | protected $managed = array(); 29 | 30 | /** 31 | * Construct object. 32 | */ 33 | public function __construct() 34 | { 35 | /* Just to prepare taxonomies */ 36 | $this->prepareAndGet(); 37 | 38 | /* Manage taxonomies translation */ 39 | add_filter( 40 | 'pll_get_taxonomies', array($this, 'getAllTranslateableTaxonomies'), 10, 2 41 | ); 42 | 43 | add_action('update_option_wpi-features', array(__CLASS__, 'updatePolyLangFromWooPolyFeatures'), 10, 3); 44 | 45 | add_action('update_option_wpi-metas-list', array(__CLASS__, 'updatePolyLangFromWooPolyMetas'), 10, 3); 46 | } 47 | 48 | /** 49 | * All this function needs to do is: 50 | * if called requesting all available settings 51 | * return all taxonomies enabled in woo-poly 52 | * This is because Polylang only saves the options which are turned on in Polylang so needs to 53 | * be told about the others. 54 | * 55 | * @param array $taxonomies array of cutoms taxonomies managed by polylang 56 | * @param bool $is_settings true when displaying the list of custom taxonomies in Polylang settings 57 | * 58 | * @return array 59 | */ 60 | public function getAllTranslateableTaxonomies($taxonomies, $is_settings) 61 | { 62 | //if not called to get all settings, simply return the input 63 | if (!($is_settings)) { 64 | return $taxonomies; 65 | } 66 | 67 | //otherwise, called by Polylang Settings, return translatable taxonomies 68 | $add = array(); 69 | $tax_types = array( 70 | 'attributes' => 'Hyyan\WPI\Taxonomies\Attributes', 71 | 'categories' => 'Hyyan\WPI\Taxonomies\Categories', 72 | 'tags' => 'Hyyan\WPI\Taxonomies\Tags', 73 | //'shipping-class' => 'Hyyan\WPI\Taxonomies\ShippingCalss', 74 | ); 75 | 76 | //for each type, add it 77 | foreach ($tax_types as $tax_type => $class) { 78 | $names = $class::getNames(); 79 | if ('on' === Settings::getOption($tax_type, Features::getID(), 'on')) { 80 | $add = array_merge($add, $names); 81 | } 82 | } 83 | 84 | return array_merge($taxonomies, $add); 85 | } 86 | 87 | 88 | 89 | /** 90 | * Hook to allow some customization when WooPoly Settings are saved, 91 | * for example some settings should be updated in Polylang Settings 92 | * [we could also catch some mutually incompatible woopoly settings, 93 | * by hooking pre_update_option_wpi-metas-list] 94 | * 95 | * @param array $old_value previous WooPoly settings 96 | * @param array $new_value new WooPoly settings 97 | * @param string $option option name 98 | * 99 | * @return array 100 | */ 101 | public static function updatePolyLangFromWooPolyMetas($old_value, $new_value, $option) 102 | { 103 | //we could update Polylang settings for Featured Image, Comment Status, Page Order 104 | //if the WooPoly settings have changed, but note this would also affect Posts 105 | return true; 106 | } 107 | 108 | /** 109 | * When WooPoly settings are saved, we should try to update the related Polylang Settings 110 | * 111 | * @param array $old_value previous WooPoly settings 112 | * @param array $new_value new WooPoly settings 113 | * @param string $option option name 114 | * 115 | * @return array 116 | */ 117 | public static function updatePolyLangFromWooPolyFeatures($old_value, $new_value, $option) 118 | { 119 | $polylang_options = get_option('polylang'); 120 | $polylang_taxs = $polylang_options['taxonomies']; 121 | $update=false; 122 | 123 | //check Polylang is in sync for Product category and tag translation 124 | if ((isset($new_value['categories'])) && ($new_value['categories']=='on')) { 125 | if (! in_array('product_cat', $polylang_taxs)) { 126 | $polylang_options['taxonomies'][] = 'product_cat'; 127 | $update=true; 128 | } 129 | } else { 130 | $key = array_search('product_cat', $polylang_taxs); 131 | if ($key!==false) { //key may be zero which is different from false 132 | unset($polylang_options['taxonomies'][$key]); 133 | $update=true; 134 | } 135 | } 136 | if ((isset($new_value['tags'])) && ($new_value['tags']=='on')) { 137 | if (! in_array('product_tag', $polylang_taxs)) { 138 | $polylang_options['taxonomies'][] = 'product_tag'; 139 | $update=true; 140 | } 141 | } else { 142 | $key = array_search('product_tag', $polylang_taxs); 143 | if ($key!==false) { 144 | unset($polylang_options['taxonomies'][$key]); 145 | $update=true; 146 | } 147 | } 148 | 149 | //for attributes don't force on for all attributes but do force off when disabled 150 | if (isset($old_value['attributes']) && isset($new_value['attributes'])) { 151 | $old_attr_sync = $old_value['attributes']; 152 | $new_attr_sync = $new_value['attributes']; 153 | if ($old_attr_sync != $new_attr_sync) { 154 | //if we are just turning the attributes on, old behaviour is to force add to translation 155 | //now we will not force translation on, only force off, ie: 156 | // remove from Polylang if disabling translation 157 | if ($new_attr_sync!='on') { 158 | $remove = Attributes::getNames(); 159 | foreach ($remove as $tax) { 160 | if (in_array($tax, $polylang_taxs)) { 161 | $polylang_options['taxonomies'] = array_flip($polylang_options['taxonomies']); 162 | unset($polylang_options['taxonomies'][$tax]); 163 | $polylang_options['taxonomies'] = array_flip($polylang_options['taxonomies']); 164 | $update = true; 165 | } //if Product Attribute was previously translated 166 | } //for each Product Attribute 167 | } //if wooPoly Translate Product Attributes is turned On 168 | } //if attributes setting has changed 169 | } //if attributes are set 170 | if ($update) { 171 | update_option('polylang', $polylang_options); 172 | } 173 | } 174 | 175 | 176 | /** 177 | * Get managed taxonomies. 178 | * 179 | * @return array taxonomies that must be added and removed to polylang 180 | */ 181 | protected function prepareAndGet() 182 | { 183 | $add = array(); 184 | $remove = array(); 185 | $supported = array( 186 | 'attributes' => 'Hyyan\WPI\Taxonomies\Attributes', 187 | 'categories' => 'Hyyan\WPI\Taxonomies\Categories', 188 | 'tags' => 'Hyyan\WPI\Taxonomies\Tags', 189 | //'shipping-class' => 'Hyyan\WPI\Taxonomies\ShippingCalss', 190 | ); 191 | 192 | foreach ($supported as $option => $class) { 193 | $names = $class::getNames(); 194 | if ('on' === Settings::getOption($option, Features::getID(), 'on')) { 195 | $add = array_merge($add, $names); 196 | if (!isset($this->managed[$class])) { 197 | $this->managed[$class] = new $class(); 198 | } 199 | } else { 200 | $remove = array_merge($remove, $names); 201 | } 202 | } 203 | 204 | return array($add, $remove); 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /src/Hyyan/WPI/Endpoints.php: -------------------------------------------------------------------------------- 1 | . 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Hyyan\WPI; 12 | 13 | use Hyyan\WPI\Tools\FlashMessages; 14 | 15 | /** 16 | * Endpoints. 17 | * 18 | * @author Hyyan Abo Fakher 19 | */ 20 | class Endpoints 21 | { 22 | /** 23 | * Array of current found endpoints. 24 | * 25 | * @var array 26 | */ 27 | protected $endpoints = array(); 28 | 29 | /** 30 | * Construct object. 31 | */ 32 | public function __construct() 33 | { 34 | 35 | /* Register endpoints to translate as polulang strings */ 36 | $this->regsiterEndpointsTranslations(); 37 | 38 | add_action( 39 | 'init', array($this, 'rewriteEndpoints'), 11 40 | ); 41 | add_action( 42 | 'woocommerce_update_options', array($this, 'addEndpoints') 43 | ); 44 | add_filter( 45 | 'pre_update_option_rewrite_rules', array($this, 'updateRules'), 100, 2 46 | ); 47 | add_filter( 48 | 'pll_the_language_link', array($this, 'correctPolylangSwitcherLinks'), 10, 2 49 | ); 50 | add_filter( 51 | 'wp_get_nav_menu_items', array($this, 'fixMyAccountLinkInMenus') 52 | ); 53 | add_action( 54 | 'current_screen', array($this, 'showFlashMessages') 55 | ); 56 | } 57 | 58 | 59 | 60 | /** 61 | * Rewrite endpoints. 62 | * 63 | * Add all endpoints to polylang strings table 64 | */ 65 | public function rewriteEndpoints() 66 | { 67 | $this->addEndpoints(); 68 | //flush_rewrite_rules(); 69 | } 70 | 71 | /** 72 | * Register endpoints translations. 73 | * 74 | * Find all woocomerce endpoints and register them with polylang to be 75 | * translated as polylang strings 76 | * 77 | * @global \Polylang $polylang 78 | * @global \WooCommerce $woocommerce 79 | * 80 | * @return false if missing polylang or woocommerce 81 | */ 82 | public function regsiterEndpointsTranslations() 83 | { 84 | global $polylang, $woocommerce; 85 | if (!$polylang || !$woocommerce) { 86 | return false; 87 | } 88 | 89 | $vars = WC()->query->get_query_vars(); 90 | foreach ($vars as $key => $value) { 91 | WC()->query->query_vars[$key] = $this->getEndpointTranslation($value); 92 | } 93 | } 94 | 95 | /** 96 | * Get endpoint translations. 97 | * 98 | * Register endpoint as polylang string if not registered and returne the 99 | * endpoint translation for the current langauge 100 | * 101 | * @global \Polylang $polylang 102 | * 103 | * @param string $endpoint the endpoint name 104 | * 105 | * @return string endpoint translation 106 | */ 107 | public function getEndpointTranslation($endpoint) 108 | { 109 | pll_register_string( 110 | $endpoint, $endpoint, static::getPolylangStringSection() 111 | ); 112 | 113 | $this->endpoints [] = $endpoint; 114 | 115 | return pll__($endpoint); 116 | } 117 | 118 | /** 119 | * Update Rules. 120 | * 121 | * Update the endpoint rule with new value and flush the rewrite rules 122 | * 123 | * @param string $value endpoint name 124 | * 125 | * @return string endpoint name 126 | */ 127 | public function updateRules($value) 128 | { 129 | remove_filter( 130 | 'pre_update_option_rewrite_rules', array($this, __FUNCTION__), 100, 2 131 | ); 132 | $this->addEndpoints(); 133 | flush_rewrite_rules(); 134 | 135 | return $value; 136 | } 137 | 138 | /** 139 | * Add endpoints. 140 | * 141 | * Add all endpoints translation in the current langauge 142 | */ 143 | public function addEndpoints() 144 | { 145 | $langs = pll_languages_list(); 146 | foreach ($this->endpoints as $endpoint) { 147 | foreach ($langs as $lang) { 148 | add_rewrite_endpoint(pll_translate_string($endpoint, $lang), EP_ROOT | EP_PAGES); 149 | } 150 | } 151 | } 152 | 153 | /** 154 | * Get Endpoint Url. 155 | * 156 | * Rebuild permalink with corrent endpoint translation 157 | * 158 | * @param string $endpoint endpoint name 159 | * @param string $value 160 | * @param string $permalink orginal permalink 161 | * 162 | * @return string final permalink 163 | */ 164 | public function rebuildUrl($endpoint, $value = '', $permalink = '') 165 | { 166 | if (get_option('permalink_structure')) { 167 | if (strstr($permalink, '?')) { 168 | $query_string = '?'.parse_url($permalink, PHP_URL_QUERY); 169 | $permalink = current(explode('?', $permalink)); 170 | } else { 171 | $query_string = ''; 172 | } 173 | $url = trailingslashit($permalink) 174 | .$endpoint 175 | .'/' 176 | .$query_string; 177 | } else { 178 | $url = add_query_arg($endpoint, $value, $permalink); 179 | } 180 | 181 | return $url; 182 | } 183 | 184 | /** 185 | * Correct Polylang Switcher Links. 186 | * 187 | * Add the correct endpoint translations for polylang switcher links 188 | * 189 | * @global \WP $wp 190 | * 191 | * @param string $link link 192 | * @param string $slug langauge 193 | * 194 | * @return string final link 195 | */ 196 | public function correctPolylangSwitcherLinks($link, $slug) 197 | { 198 | global $wp; 199 | $endpoints = WC()->query->get_query_vars(); 200 | foreach ($endpoints as $key => $value) { 201 | if (isset($wp->query_vars[$key])) { 202 | $link = str_replace( 203 | $value, pll_translate_string($key, $slug), $link 204 | ); 205 | break; 206 | } 207 | } 208 | 209 | return $link; 210 | } 211 | 212 | /** 213 | * Fix My Account Link In Menus. 214 | * 215 | * The method will remove endpoints from my account page link in wp menus 216 | * 217 | * @global \Polylang $polylang 218 | * 219 | * @param array $items menu items 220 | * 221 | * @return array menu items 222 | * 223 | * @todo Find a better solution 224 | */ 225 | public function fixMyAccountLinkInMenus(array $items = array()) 226 | { 227 | global $polylang; 228 | $translations = PLL()->model->post->get_translations( 229 | wc_get_page_id('myaccount') 230 | ); 231 | 232 | foreach ($items as $item) { 233 | if (in_array($item->object_id, $translations)) { 234 | $vars = WC()->query->get_query_vars(); 235 | foreach ($vars as $key => $value) { 236 | if ($value && false !== ($pos = strpos($item->url, $value))) { 237 | $item->url = substr($item->url, 0, $pos); 238 | } 239 | } 240 | } 241 | } 242 | 243 | return $items; 244 | } 245 | 246 | /** 247 | * Show flash messages. 248 | * 249 | * Show endpoints flash messages in defined screens only 250 | */ 251 | public function showFlashMessages() 252 | { 253 | $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : false; 254 | /* 255 | * this only gets shown once before being dismissed so show only in the relevant place 256 | */ 257 | if ( $screen && $screen->id == 'woocommerce_page_wc-settings' && isset( $_GET[ 'tab' ] ) && $_GET[ 'tab' ] == 'advanced' ) { 258 | FlashMessages::add( 259 | MessagesInterface::MSG_ENDPOINTS_TRANSLATION, Plugin::getView( 'Messages/endpointsTranslations' ) 260 | ); 261 | } 262 | } 263 | 264 | /** 265 | * Get polylang StringSection. 266 | * 267 | * @return string section name 268 | */ 269 | public static function getPolylangStringSection() 270 | { 271 | return __('WooCommerce Endpoints', 'woo-poly-integration'); 272 | } 273 | } 274 | -------------------------------------------------------------------------------- /src/Hyyan/WPI/Gateways.php: -------------------------------------------------------------------------------- 1 | . 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Hyyan\WPI; 12 | 13 | /** 14 | * Gateways. 15 | * 16 | * Handle Payment Gateways 17 | * 18 | * @author Nicolas Joannès 19 | */ 20 | class Gateways 21 | { 22 | /** @var array Array of enabled gateways */ 23 | public $enabledGateways; 24 | 25 | /** 26 | * Construct object. 27 | */ 28 | public function __construct() 29 | { 30 | add_filter('woocommerce_paypal_args', array($this, 'setPaypalLocalCode')); 31 | 32 | //key construction actions moved to wp_loaded as many payment gateways not ready before then.. 33 | add_action('wp_loaded', array($this, 'loadOnWpLoaded')); // called only after Wordpress is loaded 34 | 35 | // Payment gateway title and respective description 36 | add_filter('woocommerce_gateway_title', array($this, 'translatePaymentGatewayTitle'), 10, 2); 37 | add_filter('woocommerce_gateway_description', array($this, 'translatePaymentGatewayDescription'), 10, 2); 38 | 39 | // Payment method in Thank You and Order View pages 40 | //add_filter( 'woocommerce_get_order_item_totals', array( $this, 'translateWoocommerceOrderPaymentMethod' ), 10, 2 ); // @todo: Needs further testing before enabling 41 | } 42 | 43 | /** 44 | * Move initialisation code to run on wp_loaded instead of constructor 45 | */ 46 | public function loadOnWpLoaded() 47 | { 48 | // Set enabled payment gateways 49 | $this->enabledGateways = $this->getEnabledPaymentGateways(); 50 | // Register WooCommerce Payment Gateway custom titles and descriptions in Polylang's Strings translations table 51 | $this->registerGatewayStringsForTranslation(); 52 | 53 | // Load payment gateways extensions (gateway intructions translation) 54 | $this->loadPaymentGatewaysExtentions(); 55 | } 56 | 57 | /** 58 | * Set the PayPal checkout locale code. 59 | * 60 | * @param array $args the current paypal request args array 61 | */ 62 | public function setPaypalLocalCode($args) 63 | { 64 | $lang = pll_current_language('locale'); 65 | $args['locale.x'] = $lang; 66 | 67 | return $args; 68 | } 69 | 70 | /** 71 | * Get enabled payment gateways. 72 | * 73 | * @return array Array of enabled gateways 74 | */ 75 | public function getEnabledPaymentGateways() 76 | { 77 | $_enabledGateways = array(); 78 | 79 | $gateways = \WC_Payment_Gateways::instance(); 80 | 81 | if (sizeof($gateways->payment_gateways) > 0) { 82 | foreach ($gateways->payment_gateways() as $gateway) { 83 | if ($this->isEnabled($gateway)) { 84 | $_enabledGateways[ $gateway->id ] = $gateway; 85 | } 86 | } 87 | } 88 | 89 | return $_enabledGateways; 90 | } 91 | 92 | /** 93 | * Is payment gateway enabled? 94 | * 95 | * @param WC_Payment_Gateway $gateway 96 | * 97 | * @return bool True if gateway enabled, false otherwise 98 | */ 99 | public function isEnabled($gateway) 100 | { 101 | return 'yes' === $gateway->enabled; 102 | } 103 | 104 | /** 105 | * Load payment gateways extentions. 106 | * 107 | * Manage the gateways intructions translation in the Thank You page and 108 | * Order emails. This is required because the strings are defined in the Construct 109 | * object and no filters are available. 110 | */ 111 | public function loadPaymentGatewaysExtentions() 112 | { 113 | 114 | // Remove the gateway construct actions to avoid duplications 115 | $this->removeGatewayActions(); 116 | 117 | // Load our custom extensions with Polylang support 118 | foreach ($this->enabledGateways as $gateway) { 119 | switch ($gateway->id) { 120 | case 'bacs': 121 | new Gateways\GatewayBACS(); 122 | break; 123 | case 'cheque': 124 | new Gateways\GatewayCheque(); 125 | break; 126 | case 'cod': 127 | new Gateways\GatewayCOD(); 128 | break; 129 | default: 130 | break; 131 | } 132 | 133 | // Allows other plugins to load payment gateways class extentions or change the gateway object 134 | do_action(HooksInterface::GATEWAY_LOAD_EXTENTION.$gateway->id, $gateway, $this->enabledGateways); 135 | } 136 | } 137 | 138 | /** 139 | * Remove the gateway construct actions to avoid duplications when we instanciate 140 | * the class extentions to add polylang support that doesn't have a __construct 141 | * function and will use the parent's function and set all these actions again. 142 | */ 143 | public function removeGatewayActions() 144 | { 145 | $default_gateways = array('bacs', 'cheque', 'cod'); 146 | 147 | foreach ($this->enabledGateways as $gateway) { 148 | if (in_array($gateway->id, $default_gateways)) { 149 | remove_action('woocommerce_email_before_order_table', array($gateway, 'email_instructions')); 150 | remove_action('woocommerce_thankyou_'.$gateway->id, array($gateway, 'thankyou_page')); 151 | //remove_action( 'woocommerce_update_options_payment_gateways_' . $gateway->id, array( $gateway, 'process_admin_options' ) ); 152 | } 153 | if ('bacs' == $gateway->id) { 154 | remove_action('woocommerce_update_options_payment_gateways_'.$gateway->id, array($gateway, 'save_account_details')); 155 | } 156 | } 157 | } 158 | 159 | /** 160 | * Register Woocommerce Payment Gateway custom titles, descriptions and 161 | * instructions in Polylang's Strings translations table. 162 | */ 163 | public function registerGatewayStringsForTranslation() 164 | { 165 | if (function_exists('pll_register_string') && !empty($this->enabledGateways)) { 166 | foreach ($this->enabledGateways as $gateway) { 167 | $settings = get_option($gateway->plugin_id.$gateway->id.'_settings'); 168 | 169 | if (!empty($settings)) { 170 | if (isset($settings['title'])) { 171 | pll_register_string($gateway->plugin_id.$gateway->id.'_gateway_title', $settings['title'], __('WooCommerce Payment Gateways', 'woo-poly-integration')); 172 | } 173 | if (isset($settings['description'])) { 174 | pll_register_string($gateway->plugin_id.$gateway->id.'_gateway_description', $settings['description'], __('WooCommerce Payment Gateways', 'woo-poly-integration')); 175 | } 176 | if (isset($settings['instructions'])) { 177 | pll_register_string($gateway->plugin_id.$gateway->id.'_gateway_instructions', $settings['instructions'], __('WooCommerce Payment Gateways', 'woo-poly-integration')); 178 | } 179 | } 180 | } 181 | } 182 | } 183 | 184 | /** 185 | * Translate Payment gateway title. 186 | * 187 | * @param string Gateway title 188 | * @param int Gateway id 189 | * 190 | * @return string Translated title 191 | */ 192 | public function translatePaymentGatewayTitle($title, $id) 193 | { 194 | return function_exists('pll__') ? pll__($title) : __($title, 'woocommerce'); 195 | } 196 | 197 | /** 198 | * Translate Payment gateway description. 199 | * 200 | * @param string Gateway description 201 | * @param int Gateway id 202 | * 203 | * @return string Translated description 204 | */ 205 | public function translatePaymentGatewayDescription($description, $id) 206 | { 207 | return function_exists('pll__') ? pll__($description) : __($description, 'woocommerce'); 208 | } 209 | 210 | /** 211 | * Translate the payment method in Thank You and Order View pages. 212 | * 213 | * @param array $total_rows Array of the order item totals 214 | * @param WC_Order $order Order object 215 | * 216 | * @return array Order item totals with translated payment method 217 | */ 218 | public function translateWoocommerceOrderPaymentMethod($total_rows, $order) 219 | { 220 | if (isset($total_rows['payment_method']['value'])) { 221 | $total_rows['payment_method']['value'] = function_exists('pll__') ? pll__($total_rows['payment_method']['value']) : __($total_rows['payment_method']['value'], 'woocommerce'); 222 | } 223 | 224 | return $total_rows; 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /src/Hyyan/WPI/HooksInterface.php: -------------------------------------------------------------------------------- 1 | . 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Hyyan\WPI; 12 | 13 | /** 14 | * Plugin Hooks Interface. 15 | * 16 | * @author Hyyan Abo Fakher 17 | */ 18 | interface HooksInterface 19 | { 20 | /** 21 | * Product Meta Sync Filter. 22 | * 23 | * The filter is fired before product meta array is passed to polylang 24 | * to handle sync. 25 | * 26 | * The filter receive one parameter which is the meta array 27 | * 28 | * for instance : 29 | * 30 | * add_filter(Hyyan\WPI\HooksInterface::PRODUCT_META_SYNC_FILTER,function($meta=array()) { 31 | * 32 | * // do whatever you want 33 | * 34 | * return $meta; 35 | * }); 36 | * 37 | */ 38 | const PRODUCT_META_SYNC_FILTER = 'woo-poly.product.metaSync'; 39 | 40 | /** 41 | * Fields Locker Selectors Filter. 42 | * 43 | * The filter will be fired when the fields locker builds its selectors list 44 | * allowing other plugins to extend this list 45 | * 46 | * for instance : 47 | * 48 | * add_filter(HooksInterface::FIELDS_LOCKER_SELECTORS_FILTER,function($selectors=array()) { 49 | * 50 | * $selectors[] = '.my_field_to_lock'; 51 | * 52 | * return $selectors; 53 | * }); 54 | */ 55 | const FIELDS_LOCKER_SELECTORS_FILTER = 'woo-poly.fieldsLockerSelectors'; 56 | 57 | /** 58 | * Fields Locker Variable Exclude Selectors Filter. 59 | * 60 | * This filter may be used to exclude some variation attributes from being locked. 61 | */ 62 | const FIELDS_LOCKER_VARIABLE_EXCLUDE_SELECTORS_FILTER = 'woo-poly.fieldsLockerVariableExcludeSelectors'; 63 | 64 | /** 65 | * Product Sync Category Custom Fields Action. 66 | * 67 | * The action will be fired when the plugin attemps to sync default product 68 | * category custom fields (dispplay_type,thumbinal_id) 69 | * 70 | * The action can be used to update extra custom fields if they exist 71 | * 72 | * for instance : 73 | * 74 | * 75 | * add_action( 76 | * HooksInterface::PRODUCT_SYNC_CATEGORY_CUSTOM_FIELDS, 77 | * function (\Hyyan\WPI\Taxonomies $tax , $termID) { 78 | * 79 | * if (isset($_POST['my_field_name'])) { 80 | * $tax->doSyncProductCatCustomFields( 81 | * $termID 82 | * , 'my_field_name' 83 | * , esc_attr($_POST['my_field_name']) 84 | * ); 85 | * } 86 | * 87 | * } 88 | * ); 89 | * 90 | */ 91 | const PRODUCT_SYNC_CATEGORY_CUSTOM_FIELDS = 'woo-poly.product.syncCategoryCustomFields'; 92 | 93 | /** 94 | * Product Copy Category Custom Fields. 95 | * 96 | * The action is fired when new translatin is being added for product category 97 | * 98 | * The action can be used to copy catefory custom fields from give category 99 | * ID to its transation 100 | * 101 | * for instance : 102 | * 103 | * 104 | * add_action(HooksInterface::PRODUCT_COPY_CATEGORY_CUSTOM_FIELDS,function ($categoryID) { 105 | * 106 | * // do whatever you want here 107 | * }); 108 | * 109 | */ 110 | const PRODUCT_COPY_CATEGORY_CUSTOM_FIELDS = 'woo-poly.product.copyCategoryCustomFields'; 111 | 112 | /** 113 | * Pages List. 114 | * 115 | * The filter id fired before the list of woocommerce page names are passed 116 | * to ploylang in order to handle their translation 117 | * 118 | * for instance : 119 | * 120 | * add_filter(Hyyan\WPI\HooksInterface::PAGES_LIST,function (array $pages) { 121 | * 122 | * // do whatever you want 123 | * $pages [] = 'shop'; 124 | * 125 | * return $pages; 126 | * }); 127 | * 128 | */ 129 | const PAGES_LIST = 'woo-poly.pages.list'; 130 | 131 | /** 132 | * Settings Sections Filter. 133 | * 134 | * The filter is fired when settings section are being built, to ler other 135 | * plugins add their own sections 136 | * 137 | * for instance : 138 | * 139 | * add_filter(HooksInterface::SETTINGS_SECTIONS_FILTER,function (array $sections) { 140 | * 141 | * // Add your section 142 | * 143 | * return $sections; 144 | * }); 145 | * 146 | */ 147 | const SETTINGS_SECTIONS_FILTER = 'woo-poly.settings.sections'; 148 | 149 | /** 150 | * Settings Fields Filter. 151 | * 152 | * The filter is fired when settings fields are being built, to ler other 153 | * plugins add their own fields 154 | * 155 | * for instance : 156 | * 157 | * add_filter(HooksInterface::SETTINGS_FIELDS_FILTER,function (array $fields) { 158 | * 159 | * // Add your fields 160 | * 161 | * return $fields; 162 | * }); 163 | * 164 | */ 165 | const SETTINGS_FIELDS_FILTER = 'woo-poly.settings.fields'; 166 | 167 | /** 168 | * Language Repo URL Filter. 169 | * 170 | * The filter is fired before using the default language repo url. 171 | */ 172 | const LANGUAGE_REPO_URL_FILTER = 'woo-poly.language.repoUrl'; 173 | 174 | /** 175 | * Load Payment Gateway Extention. 176 | * 177 | * The action is fired when this plugin is initialised and allows other plugins 178 | * to load payment gateways extentions or change the gateway object to 179 | * enable Polylang support. 180 | * 181 | * The action can be used to load a class extention for a given payment gateway 182 | * 183 | * for instance : 184 | * 185 | * 186 | * add_action(HooksInterface::GATEWAY_LOAD_EXTENTION . $gateway->id,function ($gateway, $available_gateways) { 187 | * 188 | * // do whatever you want here 189 | * }); 190 | * 191 | */ 192 | const GATEWAY_LOAD_EXTENTION = 'woo-poly.gateway.loadClassExtention.'; 193 | 194 | /** 195 | * Product Variation Copy Meta Action. 196 | * 197 | * This action gets fired as soon as variation meta is being copied. 198 | */ 199 | const PRODUCT_VARIATION_COPY_META_ACTION = 'woo-poly.product.variation.copyMeta'; 200 | 201 | /** 202 | * Product Disabled Meta Sync Filter. 203 | * 204 | * The filter is fired while excluding certain meta from being handled by PolyLang. 205 | * 206 | * The filter receives one parameter which is the meta array 207 | * 208 | * for instance : 209 | * 210 | * add_filter(Hyyan\WPI\HooksInterface::PRODUCT_DISABLED_META_SYNC_FILTER,function($meta=array()) { 211 | * 212 | * // do whatever you want 213 | * 214 | * return $meta; 215 | * }); 216 | * 217 | */ 218 | const PRODUCT_DISABLED_META_SYNC_FILTER = 'woo-poly.product.disabledMetaSync'; 219 | 220 | /** 221 | * Emails Translatable Filter. 222 | * 223 | * This filter may be used to support translation for custom email templates (ids). 224 | */ 225 | const EMAILS_TRANSLATABLE_FILTER = 'woo-poly.Emails.translatableEmails'; 226 | 227 | /** 228 | * Emails Default Settings Filter. 229 | * 230 | * NO LONGER USED, SORRY, REPLACED WITH SINGLE SETTING FILTER. 231 | */ 232 | const EMAILS_DEFAULT_SETTINGS_FILTER = 'woo-poly.Emails.defaultSettings'; 233 | 234 | /** 235 | * Emails Default Settings Filter. 236 | * 237 | * This filter may be used to adjust default settings for email templates. 238 | */ 239 | const EMAILS_DEFAULT_SETTING_FILTER = 'woo-poly.Emails.defaultSetting'; 240 | 241 | /** 242 | * Emails Translatable Action. 243 | * 244 | * This action fires after our plugin has added filters to translate subjects and headings for email. 245 | */ 246 | const EMAILS_TRANSLATION_ACTION = 'woo-poly.Emails.translation'; 247 | 248 | /** 249 | * Emails Switch Language Action. 250 | * 251 | * This filter fires before the language is being switched so that plugins may unload their language file. 252 | */ 253 | const EMAILS_SWITCH_LANGUAGE_ACTION = 'woo-poly.Emails.switchLanguage'; 254 | 255 | /** 256 | * Emails After Switch Language Action. 257 | * 258 | * This filter fires after the language has been switched so that plugins may reload their language file. 259 | */ 260 | const EMAILS_AFTER_SWITCH_LANGUAGE_ACTION = 'woo-poly.Emails.afterSwitchLanguage'; 261 | 262 | /** 263 | * Emails Order Find Replace Find Filter. 264 | * 265 | * This filter may be used to support custom variables in subjects/headings (find). 266 | */ 267 | const EMAILS_ORDER_FIND_REPLACE_FIND_FILTER = 'woo-poly.Emails.orderFindReplaceFind'; 268 | 269 | /** 270 | * Emails Order Find Replace Replace Filter. 271 | * 272 | * This filter may be used to support custom variables in subjects/headings (replace). 273 | */ 274 | const EMAILS_ORDER_FIND_REPLACE_REPLACE_FILTER = 'woo-poly.Emails.orderFindReplaceReplace'; 275 | 276 | /** 277 | * Cart switched item filter. 278 | * 279 | * Used when a language variant of the original item will be shown. 280 | * This filter can customise the product to be shown 281 | * 282 | * two parameters, $newItem, $originalItem, $cartItem 283 | */ 284 | const CART_SWITCHED_ITEM = 'woo-poly.Cart.switchedItem'; 285 | 286 | } 287 | -------------------------------------------------------------------------------- /src/Hyyan/WPI/Product/Product.php: -------------------------------------------------------------------------------- 1 | . 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Hyyan\WPI\Product; 12 | 13 | use Hyyan\WPI\Admin\Settings; 14 | use Hyyan\WPI\Admin\Features; 15 | use Hyyan\WPI\Utilities; 16 | 17 | /** 18 | * Product. 19 | * 20 | * Handle product translation 21 | * 22 | * @author Hyyan Abo Fakher 23 | */ 24 | class Product 25 | { 26 | /** 27 | * Construct object. 28 | */ 29 | public function __construct() 30 | { 31 | 32 | // manage product translation 33 | add_filter( 34 | 'pll_get_post_types', array($this, 'manageProductTranslation') 35 | ); 36 | 37 | // sync post parent (good for grouped products) 38 | add_filter('admin_init', array($this, 'syncPostParent')); 39 | 40 | //Product title/description sync/translate, defaults to 0-Off for back-compatiblity 41 | $translate_option = Settings::getOption('new-translation-defaults', Features::getID(), 0); 42 | if ($translate_option) { 43 | add_filter('default_title', array($this, 'wpi_editor_title')); 44 | add_filter('default_content', array($this, 'wpi_editor_content')); 45 | add_filter('default_excerpt', array($this, 'wpi_editor_excerpt')); 46 | } 47 | 48 | //TODO: this filter appears to be unnecessary - remove 49 | //woocommerce_product_attribute_terms is already getting terms for a particular attribute 50 | //which is already the language version of the attribute ... 51 | // get attributes in current language 52 | /* 53 | * 54 | add_filter( 55 | 'woocommerce_product_attribute_terms', array($this, 'getProductAttributesInLanguage') 56 | ); 57 | */ 58 | //show cross-sells and up-sells in correct language 59 | add_filter('woocommerce_product_get_upsell_ids', array($this, 'getUpsellsInLanguage'), 10, 2); 60 | add_filter('woocommerce_product_get_cross_sell_ids', array($this, 'getCrosssellsInLanguage'), 10, 2); 61 | add_filter('woocommerce_product_get_children', array($this, 'getChildrenInLanguage'), 10, 2); 62 | 63 | //for this ajax call our action has to come before the woocommerce action because the woocommerce action does a redirect 64 | add_action( 'wp_ajax_woocommerce_feature_product', array( __CLASS__, 'sync_ajax_woocommerce_feature_product' ), 5 ); 65 | 66 | new Meta(); 67 | new Variable(); 68 | new Duplicator(); 69 | 70 | if ( ('on' === Settings::getOption( 'stock', Features::getID(), 'on' )) && 71 | ( 'yes' === get_option( 'woocommerce_manage_stock' ) ) ) 72 | { 73 | new Stock(); 74 | } 75 | } 76 | 77 | 78 | /* 79 | * #234 WooCommerce allows featured to be toggled in the products admin list 80 | * by clicking on the star 81 | */ 82 | public static function sync_ajax_woocommerce_feature_product() { 83 | $metas = Meta::getDisabledProductMetaToCopy(); 84 | if ( in_array( '_visibility', $metas ) ) { 85 | return; 86 | } 87 | 88 | $product = wc_get_product( absint( $_GET[ 'product_id' ] ) ); 89 | if ( $product ) { 90 | //woocommerce action runs last so we need to set translation feature to the opposite of current value 91 | $targetValue = ! $product->get_featured(); 92 | 93 | $product_translations = Utilities::getProductTranslationsArrayByObject( $product ); 94 | foreach ( $product_translations as $product_translation ) { 95 | if ( $product_translation != $product->get_id() ) { 96 | $translation = wc_get_product( $product_translation ); 97 | if ( $translation ) { 98 | $translation->set_featured( $targetValue ); 99 | $translation->save(); 100 | } 101 | } 102 | } 103 | } 104 | } 105 | 106 | 107 | /** 108 | * filter child ids of Grouped Product 109 | * 110 | * @param array $related_ids array of product ids 111 | * @param WC_Product $product current product 112 | * 113 | * @return array filtered result 114 | */ 115 | public function getChildrenInLanguage($relatedIds, $product) 116 | { 117 | return $this->getProductIdsInLanguage($relatedIds, $product); 118 | } 119 | /** 120 | * filter upsells display 121 | * 122 | * @param array $related_ids array of product ids 123 | * @param WC_Product $product current product 124 | * 125 | * @return array filtered result 126 | */ 127 | public function getUpsellsInLanguage($relatedIds, $product) 128 | { 129 | return $this->getProductIdsInLanguage($relatedIds, $product); 130 | } 131 | /** 132 | * filter Cross-sells display 133 | * 134 | * @param array $related_ids array of product ids 135 | * @param WC_Product $product current product 136 | * 137 | * @return array filtered result 138 | */ 139 | public function getCrosssellsInLanguage($relatedIds, $product) 140 | { 141 | return $this->getProductIdsInLanguage($relatedIds, $product); 142 | } 143 | /** 144 | * filter product ids 145 | * 146 | * @param array $product_ids array of product ids 147 | * @param WC_Product $product current product 148 | * 149 | * @return array filtered result 150 | */ 151 | public function getProductIdsInLanguage($productIds, $product) 152 | { 153 | $productLang = pll_get_post_language($product->get_id()); 154 | $mappedIds = array(); 155 | foreach ($productIds as $productId) { 156 | $correctLanguageId = pll_get_post($productId, $productLang); 157 | if ($correctLanguageId) { 158 | $mappedIds[]=$correctLanguageId; 159 | } else { 160 | //what do you want to do if product not available in current display language? 161 | //allow the available product language to be returned 162 | $mappedIds[]=$productId; 163 | } 164 | } 165 | return $mappedIds; 166 | } 167 | 168 | 169 | 170 | /** 171 | * Notifty polylang about product custom post. 172 | * 173 | * @param array $types array of custom post names managed by polylang 174 | * 175 | * @return array 176 | */ 177 | public function manageProductTranslation(array $types) 178 | { 179 | $options = get_option('polylang'); 180 | $postTypes = $options['post_types']; 181 | if (!in_array('product', $postTypes)) { 182 | $options['post_types'][] = 'product'; 183 | update_option('polylang', $options); 184 | } 185 | if ( ! in_array( 'product_variation', $postTypes ) ) { 186 | $options[ 'post_types' ][] = 'product_variation'; 187 | update_option( 'polylang', $options ); 188 | } 189 | 190 | $types [] = 'product'; 191 | 192 | return $types; 193 | } 194 | 195 | /** 196 | * Tell polylang to sync the post parent. 197 | */ 198 | public function syncPostParent() 199 | { 200 | $options = get_option('polylang'); 201 | $sync = $options['sync']; 202 | if (!in_array('post_parent', $sync)) { 203 | $options['sync'][] = 'post_parent'; 204 | update_option('polylang', $options); 205 | } 206 | } 207 | 208 | /** 209 | * Get product attributes in right language. 210 | * @param array $args array of arguments for get_terms function in WooCommerce 211 | * attributes html markup 212 | * 213 | * @return array 214 | */ 215 | public function getProductAttributesInLanguage($args) 216 | { 217 | global $post; 218 | $lang = ''; 219 | 220 | if (isset($_GET['new_lang'])) { 221 | $lang = esc_attr($_GET['new_lang']); 222 | } elseif (!empty($post)) { 223 | $lang = pll_get_post_language($post->ID); 224 | } else { 225 | $lang = PLL()->pref_lang; 226 | } 227 | 228 | $args['lang'] = $lang; 229 | 230 | return $args; 231 | } 232 | 233 | 234 | // Make sure Polylang copies the title when creating a translation 235 | public function wpi_editor_title($title) 236 | { 237 | // Polylang sets the 'from_post' parameter 238 | if (isset($_GET['from_post'])) { 239 | $my_post = get_post($_GET['from_post']); 240 | if ($my_post) { 241 | return $my_post->post_title; 242 | } 243 | } 244 | return $title; 245 | } 246 | 247 | // Make sure Polylang copies the content when creating a translation 248 | public function wpi_editor_content($content) 249 | { 250 | // Polylang sets the 'from_post' parameter 251 | if (isset($_GET['from_post'])) { 252 | $my_post = get_post($_GET['from_post']); 253 | if ($my_post) { 254 | return $my_post->post_content; 255 | } 256 | } 257 | return $content; 258 | } 259 | 260 | // Make sure Polylang copies the excerpt [woocommerce short description] when creating a translation 261 | public function wpi_editor_excerpt($excerpt) 262 | { 263 | // Polylang sets the 'from_post' parameter 264 | if (isset($_GET['from_post'])) { 265 | $my_post = get_post($_GET['from_post']); 266 | if ($my_post) { 267 | return $my_post->post_excerpt; 268 | } 269 | } 270 | return $excerpt; 271 | } 272 | } 273 | -------------------------------------------------------------------------------- /src/Hyyan/WPI/Reports.php: -------------------------------------------------------------------------------- 1 | . 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Hyyan\WPI; 12 | 13 | use Hyyan\WPI\Admin\Settings; 14 | use Hyyan\WPI\Admin\Features; 15 | use Hyyan\WPI\Utilities; 16 | 17 | /** 18 | * Reports. 19 | * 20 | * @author Hyyan Abo Fakher 21 | */ 22 | class Reports 23 | { 24 | /** 25 | * Tab name. 26 | * 27 | * @var string 28 | */ 29 | protected $tab; 30 | 31 | /** 32 | * Report type. 33 | * 34 | * @var string 35 | */ 36 | protected $report; 37 | 38 | /** 39 | * Construct object. 40 | */ 41 | public function __construct() 42 | { 43 | $this->tab = isset($_GET['tab']) ? esc_attr($_GET['tab']) : false; 44 | $this->report = isset($_GET['report']) ? esc_attr($_GET['report']) : false; 45 | 46 | if ('off' === Settings::getOption('reports', Features::getID(), 'on')) { 47 | return; 48 | } 49 | 50 | /* Handle products filtering and combining */ 51 | if ('orders' == $this->tab || false === $this->report) { 52 | add_filter( 53 | 'woocommerce_reports_get_order_report_data', array($this, 'combineProductsByLanguage') 54 | ); 55 | add_filter( 56 | 'woocommerce_reports_get_order_report_query', array($this, 'filterProductByLanguage') 57 | ); 58 | } 59 | 60 | /* handle stock table filtering */ 61 | add_filter( 62 | 'woocommerce_report_most_stocked_query_from', array($this, 'filterStockByLanguage') 63 | ); 64 | add_filter( 65 | 'woocommerce_report_out_of_stock_query_from', array($this, 'filterStockByLanguage') 66 | ); 67 | add_filter( 68 | 'woocommerce_report_low_in_stock_query_from', array($this, 'filterStockByLanguage') 69 | ); 70 | 71 | /* Combine product report with its translation */ 72 | add_action('admin_init', array($this, 'translateProductIDS')); 73 | 74 | /* Combine product category report with its translation */ 75 | add_action('admin_init', array($this, 'translateCategoryIDS')); 76 | add_filter( 77 | 'woocommerce_report_sales_by_category_get_products_in_category', array($this, 'addProductsInCategoryTranslations'), 10, 2 78 | ); 79 | } 80 | 81 | /** 82 | * Filter by language. 83 | * 84 | * Filter report data according to choosen language 85 | * 86 | * @param array $query 87 | * 88 | * @return array final report query 89 | */ 90 | public function filterProductByLanguage(array $query) 91 | { 92 | $reports = array( 93 | 'sales_by_product', 94 | 'sales_by_category', 95 | ); 96 | if (!in_array($this->report, $reports)) { 97 | return $query; 98 | } 99 | 100 | /* Check for product_ids */ 101 | if (isset($_GET['product_ids'])) { 102 | return $query; 103 | } 104 | 105 | $lang = ($current = pll_current_language()) ? 106 | array($current) : 107 | pll_languages_list(); 108 | 109 | $query['join'] .= PLL()->model->post->join_clause('posts'); 110 | $query['where'] .= PLL()->model->post->where_clause($lang, 'post'); 111 | 112 | return $query; 113 | } 114 | 115 | /** 116 | * Combine products by language. 117 | * 118 | * @param array $results 119 | * 120 | * @return array 121 | */ 122 | public function combineProductsByLanguage($results) 123 | { 124 | if (!is_array($results)) { 125 | return $results; 126 | } 127 | 128 | if (isset($results['0']->order_item_qty)) { 129 | $mode = 'top_sellers'; 130 | } elseif (is_array($results) && isset($results['0']->order_item_total)) { 131 | $mode = 'top_earners'; 132 | } else { 133 | return $results; 134 | } 135 | 136 | $translated = array(); 137 | $lang = pll_current_language() ?: 138 | get_user_meta(get_current_user_id(), 'user_lang', true); 139 | 140 | /* Filter data by language */ 141 | foreach ($results as $data) { 142 | $translation = Utilities::getProductTranslationByID( 143 | $data->product_id, $lang 144 | ); 145 | 146 | if ($translation) { 147 | $data->from = $data->product_id; 148 | $data->product_id = $translation->get_id(); 149 | } 150 | $translated [] = $data; 151 | } 152 | 153 | /* Unique product IDS */ 154 | $unique = array(); 155 | 156 | foreach ($translated as $data) { 157 | if (!isset($unique[$data->product_id])) { 158 | $unique[$data->product_id] = $data; 159 | continue; 160 | } 161 | 162 | $property = ''; 163 | switch ($mode) { 164 | case 'top_sellers': 165 | $property = 'order_item_qty'; 166 | break; 167 | case 'top_earners': 168 | $property = 'order_item_total'; 169 | break; 170 | default: 171 | break; 172 | } 173 | 174 | $unique[$data->product_id]->$property += $data->$property; 175 | } 176 | 177 | return array_values($unique); 178 | } 179 | 180 | /** 181 | * Filter stock by language. 182 | * 183 | * Filter the stock table according to choosen language 184 | * 185 | * @param string $query stock query 186 | * 187 | * @return string final stock query 188 | */ 189 | public function filterStockByLanguage($query) 190 | { 191 | $lang = ($current = pll_current_language()) ? 192 | array($current) : 193 | pll_languages_list(); 194 | 195 | $join = PLL()->model->post->join_clause('posts'); 196 | $where = PLL()->model->post->where_clause($lang, 'post'); 197 | 198 | return str_replace('WHERE 1=1', "{$join} WHERE 1=1 {$where}", $query); 199 | } 200 | 201 | /** 202 | * Translate product IDS for product report. 203 | * 204 | * @global \Polylang $polylang 205 | * @global \WooCommerce $woocommerce 206 | * 207 | * @return false if woocommerce or polylang not found 208 | */ 209 | public function translateProductIDS() 210 | { 211 | global $polylang, $woocommerce; 212 | if (!$polylang || !$woocommerce) { 213 | return false; 214 | } 215 | 216 | /* Check for product_ids */ 217 | if (!isset($_GET['product_ids'])) { 218 | return false; 219 | } 220 | 221 | $IDS = (array) $_GET['product_ids']; 222 | $extendedIDS = array(); 223 | 224 | if (static::isCombine()) { 225 | foreach ($IDS as $ID) { 226 | $translations = Utilities::getProductTranslationsArrayByID($ID); 227 | $extendedIDS = array_merge($extendedIDS, $translations); 228 | } 229 | } elseif ( 230 | isset($_GET['lang']) && 231 | esc_attr($_GET['lang']) !== 'all' 232 | ) { 233 | $lang = esc_attr($_GET['lang']); 234 | foreach ($IDS as $ID) { 235 | $translation = Utilities::getProductTranslationByID($ID, $lang); 236 | $extendedIDS[] = $translation->id; 237 | } 238 | } 239 | 240 | /* Update with extended list */ 241 | if (!empty($extendedIDS)) { 242 | $_GET['product_ids'] = $extendedIDS; 243 | } 244 | } 245 | 246 | /** 247 | * Translate Category IDS for category report. 248 | * 249 | * @global \Polylang $polylang 250 | * @global \WooCommerce $woocommerce 251 | * 252 | * @return false if woocommerce or polylang not found 253 | */ 254 | public function translateCategoryIDS() 255 | { 256 | global $polylang, $woocommerce; 257 | if (!$polylang || !$woocommerce) { 258 | return false; 259 | } 260 | 261 | /* Check for product_ids */ 262 | if (!isset($_GET['show_categories'])) { 263 | return false; 264 | } 265 | 266 | if ( 267 | !static::isCombine() && 268 | (isset($_GET['lang']) && esc_attr($_GET['lang']) !== 'all') 269 | ) { 270 | $IDS = (array) $_GET['show_categories']; 271 | $extendedIDS = array(); 272 | $lang = esc_attr($_GET['lang']); 273 | 274 | foreach ($IDS as $ID) { 275 | $translation = pll_get_term($ID, $lang); 276 | if ($translation) { 277 | $extendedIDS[] = $translation; 278 | } 279 | } 280 | 281 | if (!empty($extendedIDS)) { 282 | $_GET['show_categories'] = $extendedIDS; 283 | } 284 | } 285 | } 286 | 287 | /** 288 | * Collect products from category translations. 289 | * 290 | * Add all products in the given category translations 291 | * 292 | * @param array $productIDS array of products in the given category 293 | * @param int $categoryID category ID 294 | * 295 | * @return array array of producs in the given category and its translations 296 | */ 297 | public function addProductsInCategoryTranslations($productIDS, $categoryID) 298 | { 299 | if (static::isCombine()) { 300 | 301 | /* Find the category translations */ 302 | $translations = Utilities::getTermTranslationsArrayByID($categoryID); 303 | 304 | foreach ($translations as $slug => $ID) { 305 | if ($ID === $categoryID) { 306 | continue; 307 | } 308 | 309 | $termIDS = get_term_children($ID, 'product_cat'); 310 | $termIDS[] = $ID; 311 | $productIDS = array_merge( 312 | $productIDS, (array) get_objects_in_term($termIDS, 'product_cat') 313 | ); 314 | } 315 | } 316 | 317 | return $productIDS; 318 | } 319 | 320 | /** 321 | * Is combine. 322 | * 323 | * Check if combine mode is requested 324 | * 325 | * @return bool true if combine mode , false otherwise 326 | */ 327 | public static function isCombine() 328 | { 329 | return !pll_current_language() || 330 | (isset($_GET['lang']) && esc_attr($_GET['lang']) === 'all'); 331 | } 332 | } 333 | -------------------------------------------------------------------------------- /src/Hyyan/WPI/Cart.php: -------------------------------------------------------------------------------- 1 | . 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | namespace Hyyan\WPI; 10 | 11 | use Hyyan\WPI\Product\Variation; 12 | use Hyyan\WPI\Product\Meta; 13 | use Hyyan\WPI\Utilities; 14 | 15 | /** 16 | * Cart. 17 | * 18 | * Handle cart translation 19 | * 20 | * @author Hyyan Abo Fakher 21 | */ 22 | class Cart 23 | { 24 | 25 | const ADD_TO_CART_HANDLER_VARIABLE = 'wpi_variable'; 26 | 27 | /** 28 | * Construct object. 29 | */ 30 | public function __construct() 31 | { 32 | // Handle add to cart 33 | add_filter('woocommerce_add_to_cart_product_id', array($this, 'addToCart'), 10, 1); 34 | 35 | // Handle the translation of displayed porducts in cart 36 | add_filter('woocommerce_cart_item_product', array($this, 'translateCartItemProduct'), 10, 2); 37 | add_filter('woocommerce_cart_item_product_id', array($this, 'translateCartItemProductId'), 10, 1); 38 | add_filter('woocommerce_cart_item_permalink', array($this, 'translateCartItemPermalink'), 10, 2); 39 | add_filter('woocommerce_get_item_data', array($this, 'translateCartItemData'), 10, 2); 40 | 41 | // Handle the update of cart widget when language is switched 42 | add_action('wp_enqueue_scripts', array($this, 'replaceCartFragmentsScript'), 100); 43 | 44 | } 45 | 46 | /** 47 | * Add to cart. 48 | * 49 | * The function will make sure that products won't be duplicated for each 50 | * language 51 | * 52 | * @param int $ID the current product ID 53 | * 54 | * @return int the final product ID 55 | */ 56 | public function addToCart($ID) 57 | { 58 | $result = $ID; 59 | 60 | // get the product translations 61 | $IDS = Utilities::getProductTranslationsArrayByID($ID); 62 | 63 | // check if any of product's translation is already in cart 64 | foreach (WC()->cart->get_cart() as $values) { 65 | $product = $values['data']; 66 | 67 | if (in_array($product->get_id(), $IDS)) { 68 | $result = $product->get_id(); 69 | break; 70 | } 71 | } 72 | 73 | return $result; 74 | } 75 | 76 | /** 77 | * Translate displayed product in cart. 78 | * 79 | * @param \WC_Product|\WC_Product_Variation $cart_item_data 80 | * @param array $cart_item 81 | * 82 | * @return \WC_Product|\WC_Product_Variation 83 | */ 84 | public function translateCartItemProduct($cart_item_data, $cart_item) 85 | { 86 | $cart_product_id = isset($cart_item['product_id']) ? $cart_item['product_id'] : 0; 87 | $cart_variation_id = isset($cart_item['variation_id']) ? $cart_item['variation_id'] : 0; 88 | 89 | // By default, returns the same input 90 | $cart_item_data_translation = $cart_item_data; 91 | 92 | switch ($cart_item_data->get_type()) { 93 | case 'variation': 94 | $variation_translation = $this->getVariationTranslation($cart_variation_id); 95 | if ($variation_translation && $variation_translation->get_id() != $cart_variation_id) { 96 | $cart_item_data_translation = $variation_translation; 97 | } 98 | break; 99 | 100 | case 'simple': 101 | default: 102 | $product_translation = Utilities::getProductTranslationByID($cart_product_id); 103 | if ($product_translation && $product_translation->get_id() != $cart_product_id) { 104 | $cart_item_data_translation = $product_translation; 105 | } 106 | break; 107 | } 108 | 109 | 110 | // If we are changing the product to the right language 111 | if ($cart_item_data_translation->get_id() != $cart_item_data->get_id()) { 112 | $cart_item_data_translation = apply_filters(HooksInterface::CART_SWITCHED_ITEM, $cart_item_data_translation, $cart_item_data, $cart_item); 113 | } 114 | 115 | return $cart_item_data_translation; 116 | } 117 | 118 | /** 119 | * Replace products id in cart with id of product translation. 120 | * 121 | * @param int $cart_product_id Product Id 122 | * 123 | * @return int Id of the product translation 124 | */ 125 | public function translateCartItemProductId($cart_product_id) 126 | { 127 | $translation_id = pll_get_post($cart_product_id); 128 | return $translation_id ? $translation_id : $cart_product_id; 129 | } 130 | 131 | /** 132 | * Translate product attributes in the product permalink querystring. 133 | * 134 | * @param string $item_permalink Product permalink 135 | * @param array $cart_item Cart item 136 | * 137 | * @return string Translated permalink 138 | */ 139 | public function translateCartItemPermalink($item_permalink, $cart_item) 140 | { 141 | $cart_variation_id = isset($cart_item['variation_id']) ? $cart_item['variation_id'] : 0; 142 | 143 | if ($cart_variation_id !== 0) { 144 | // Variation 145 | $variation_translation = $this->getVariationTranslation($cart_variation_id); 146 | return $variation_translation ? $variation_translation->get_permalink() : $item_permalink; 147 | } 148 | 149 | return $item_permalink; 150 | } 151 | 152 | /** 153 | * Translate product variation attributes. 154 | * 155 | * @param array $item_data Variation attributes 156 | * @param array $cart_item Cart item 157 | * 158 | * @return array Translated attributes 159 | */ 160 | public function translateCartItemData($item_data, $cart_item) 161 | { 162 | // We don't translate the variation attributes if the product in the cart 163 | // is not a product variation, and in case of a product variation, it 164 | // doesn't have a translation in the current language. 165 | $cart_variation_id = isset($cart_item['variation_id']) ? $cart_item['variation_id'] : 0; 166 | 167 | if ($cart_variation_id == 0) { 168 | // Not a variation product 169 | return $item_data; 170 | } elseif ($cart_variation_id != 0 && false == $this->getVariationTranslation($cart_variation_id)) { 171 | // Variation product without translation in current language 172 | return $item_data; 173 | } 174 | 175 | $item_data_translation = array(); 176 | 177 | foreach ($item_data as $data) { 178 | $term_id = null; 179 | 180 | foreach ($cart_item['variation'] as $tax => $term_slug) { 181 | $tax = str_replace('attribute_', '', $tax); 182 | $term = get_term_by('slug', $term_slug, $tax); 183 | 184 | if ($term && isset($data['value']) && $term->name === $data['value']) { 185 | $term_id = $term->term_id; 186 | break; 187 | } 188 | } 189 | 190 | if ($term_id !== 0 && $term_id !== null) { 191 | // Product attribute is a taxonomy term - check if Polylang has a translation 192 | $term_id_translation = pll_get_term($term_id); 193 | 194 | if ($term_id_translation == $term_id) { 195 | // Already showing the attribute (term) in the correct language 196 | $item_data_translation[] = $data; 197 | } else { 198 | // Get term translation from id 199 | $term_translation = get_term($term_id_translation); 200 | 201 | $error = get_class($term_translation) == 'WP_Error'; 202 | 203 | $item_data_translation[] = array('key' => $data['key'], 'value' => !$error ? $term_translation->name : $data['value']); // On error return same 204 | } 205 | } else { 206 | // Product attribute is post metadata and not translatable - return same 207 | $item_data_translation[] = $data; 208 | } 209 | } 210 | 211 | return !empty($item_data_translation) ? $item_data_translation : $item_data; 212 | } 213 | 214 | /** 215 | * Replace woo fragments script. 216 | * 217 | * To update cart widget when language is switched 218 | */ 219 | public function replaceCartFragmentsScript() 220 | { 221 | /* remove the orginal wc-cart-fragments.js and register ours */ 222 | wp_deregister_script('wc-cart-fragments'); 223 | $suffix = defined('SCRIPT_DEBUG') && SCRIPT_DEBUG ? '' : '.min'; 224 | wp_enqueue_script( 225 | 'wc-cart-fragments', plugins_url('public/js/Cart' . $suffix . '.js', Hyyan_WPI_DIR), array('jquery', 'jquery-cookie'), Plugin::getVersion(), true 226 | ); 227 | } 228 | 229 | /** 230 | * Get product variation translation. 231 | * 232 | * Returns the product variation object for a given language. 233 | * 234 | * @param int $variation_id (required) Id of the variation to translate 235 | * @param string $lang (optional) 2-letters code of the language 236 | * like Polylang 237 | * language slugs, defaults to current language 238 | * 239 | * @return \WP_Product_Variation Product variation object for the given 240 | * language, false on error or if doesn't exists. 241 | */ 242 | public function getVariationTranslation($variation_id, $lang = '') 243 | { 244 | $_variation = false; 245 | 246 | // Get parent product translation id for the given language 247 | $variation = wc_get_product($variation_id); 248 | $parentid = Utilities::get_variation_parentid($variation); 249 | $_product_id = pll_get_post($parentid, $lang); 250 | 251 | // Get variation translation using the duplication metadata value 252 | $meta = get_post_meta($variation_id, Variation::DUPLICATE_KEY, true); 253 | 254 | if ($_product_id && $meta) { 255 | // Get posts (variations) with duplication metadata value 256 | $variation_post = get_posts(array( 257 | 'meta_key' => Variation::DUPLICATE_KEY, 258 | 'meta_value' => $meta, 259 | 'post_type' => 'product_variation', 260 | 'post_parent' => $_product_id 261 | )); 262 | 263 | // Get variation translation 264 | if ($variation_post && count($variation_post) == 1) { 265 | $_variation = wc_get_product($variation_post[0]->ID); 266 | } 267 | } 268 | 269 | return $_variation; 270 | } 271 | } 272 | --------------------------------------------------------------------------------