├── 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 |
28 | 29 | Hyyan Abo Fakher 30 |
31 | 32 | 33 | 34 |"+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 Fakher12 | '); 19 | _e('Installation Guide', 'woo-poly-integration'); 20 | echo('.'); 21 | ?> 22 |
23 |
|
9 | |
12 |
13 | 14 | 19 |20 | 21 |
22 | Hyyan Abo Fakher, and I am the developer
24 | of plugin Hyyan WooCommerce Polylang Integration. 33 | 34 | 35 | |
36 |
| 16 | | ||
|---|---|---|
| : | 21 |22 | | 23 | |
| : | 26 |27 | | 28 | |
| : | 31 |32 | | '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 | ?> |
50 |
' . 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 | [](http://www.gitchecker.com/hyyan/woo-poly-integration) 4 | [](https://packagist.org/packages/hyyan/woo-poly-integration) 5 | [](https://wordpress.org/plugins/woo-poly-integration/) 6 | [](https://wordpress.org/plugins/woo-poly-integration/) 7 | [](https://wordpress.org/plugins/woo-poly-integration/) 8 | [](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%s
' + 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
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 |
--------------------------------------------------------------------------------