CompositedProductHooks.php
4 weeks ago
Factory.php
4 weeks ago
JobHooks.php
4 weeks ago
MulticurrencyHooks.php
4 weeks ago
class-wcml-composite-products.php
4 weeks ago
class-wcml-composite-products.php
468 lines
| 1 | <?php |
| 2 | |
| 3 | use WCML\Options\WPML; |
| 4 | use WPML\FP\Fns; |
| 5 | use WPML\FP\Obj; |
| 6 | use WPML\FP\Str; |
| 7 | |
| 8 | class WCML_Composite_Products extends WCML_Compatibility_Helper implements \IWPML_Action { |
| 9 | |
| 10 | const META_KEY_DATA = '_bto_data'; |
| 11 | const META_KEY_SCENARIO = '_bto_scenario_data'; |
| 12 | const FIELD_TYPE_PREFIX = 'wc_composite'; |
| 13 | |
| 14 | private $sitepress; |
| 15 | |
| 16 | public function __construct( SitePress $sitepress ) { |
| 17 | $this->sitepress = $sitepress; |
| 18 | } |
| 19 | |
| 20 | public function add_hooks(){ |
| 21 | |
| 22 | add_filter( 'woocommerce_composite_component_default_option', [ $this, 'woocommerce_composite_component_default_option' ], 10, 3 ); |
| 23 | add_filter( 'wcml_cart_contents', [ $this, 'wpml_composites_compat' ], 11, 4 ); |
| 24 | add_filter( 'woocommerce_composite_component_options_query_args', [ $this, 'wpml_composites_transients_cache_per_language' ], 10, 3 ); |
| 25 | add_action( 'wcml_before_sync_product_data', [ $this, 'sync_composite_data_across_translations' ], 10, 2 ); |
| 26 | add_action( 'wpml_translation_job_saved', [ $this, 'save_composite_data_translation' ], 10, 3 ); |
| 27 | add_filter( 'wpml_tm_translation_job_data', [ $this, 'append_composite_data_translation_package' ], 10, 2 ); |
| 28 | |
| 29 | if( is_admin() ){ |
| 30 | if ( ! WPML::useAte() ) { |
| 31 | add_action( 'wcml_gui_additional_box_html', [ $this, 'custom_box_html' ], 10, 3 ); |
| 32 | add_filter( 'wcml_gui_additional_box_data', [ $this, 'custom_box_html_data' ], 10, 4 ); |
| 33 | add_action( 'wcml_update_extra_fields', [ $this, 'update_component_strings' ], 10, 4 ); |
| 34 | } |
| 35 | |
| 36 | add_filter( 'woocommerce_json_search_found_products', [ $this, 'woocommerce_json_search_found_products' ] ); |
| 37 | |
| 38 | add_filter( 'wcml_js_lock_fields_input_names', [ $this, 'wcml_js_lock_fields_input_names' ] ); |
| 39 | add_filter( 'wcml_js_lock_fields_ids', [ $this, 'wcml_js_lock_fields_ids' ] ); |
| 40 | add_filter( 'wcml_after_load_lock_fields_js', [ $this, 'localize_lock_fields_js' ] ); |
| 41 | add_action( 'init', [ $this, 'load_assets' ] ); |
| 42 | |
| 43 | add_filter( 'wcml_do_not_display_custom_fields_for_product', [ $this, 'replace_tm_editor_custom_fields_with_own_sections' ] ); |
| 44 | } |
| 45 | } |
| 46 | |
| 47 | public function woocommerce_composite_component_default_option($selected_value, $component_id, $object) { |
| 48 | |
| 49 | if( !empty( $selected_value ) ) |
| 50 | $selected_value = apply_filters( 'wpml_object_id', $selected_value, 'product', true ); |
| 51 | |
| 52 | |
| 53 | return $selected_value; |
| 54 | } |
| 55 | |
| 56 | public function wpml_composites_compat( $new_cart_data, $cart_contents, $key, $new_key ) { |
| 57 | |
| 58 | if ( isset( $cart_contents[ $key ][ 'composite_children' ] ) || isset( $cart_contents[ $key ][ 'composite_parent' ] ) ) { |
| 59 | |
| 60 | $buff = $new_cart_data[ $new_key ]; |
| 61 | |
| 62 | unset( $new_cart_data[ $new_key ] ); |
| 63 | |
| 64 | $new_cart_data[ $key ] = $buff; |
| 65 | } |
| 66 | |
| 67 | return $new_cart_data; |
| 68 | } |
| 69 | |
| 70 | public function wpml_composites_transients_cache_per_language( $args, $query_args, $component_data ) { |
| 71 | |
| 72 | $args[ 'wpml_lang' ] = apply_filters( 'wpml_current_language', NULL ); |
| 73 | |
| 74 | return $args; |
| 75 | } |
| 76 | |
| 77 | public function sync_composite_data_across_translations( $original_product_id, $current_product_id ){ |
| 78 | |
| 79 | if( $this->get_product_type( $original_product_id ) == 'composite' ){ |
| 80 | |
| 81 | $composite_data = $this->get_composite_data( $original_product_id ); |
| 82 | $composite_scenarios_meta = $this->get_composite_scenarios_meta( $original_product_id ); |
| 83 | |
| 84 | $product_trid = $this->sitepress->get_element_trid( $original_product_id, 'post_product' ); |
| 85 | $product_translations = $this->sitepress->get_element_translations( $product_trid, 'post_product' ); |
| 86 | |
| 87 | foreach ( $product_translations as $product_translation ) { |
| 88 | |
| 89 | if ( empty($product_translation->original) ) { |
| 90 | |
| 91 | $translated_composite_data = $this->get_composite_data( $product_translation->element_id ); |
| 92 | |
| 93 | foreach ( $composite_data as $component_id => $component ) { |
| 94 | |
| 95 | if( isset( $translated_composite_data[$component_id]['title'] ) ){ |
| 96 | $composite_data[$component_id]['title'] = $translated_composite_data[$component_id]['title']; |
| 97 | } |
| 98 | |
| 99 | if( isset( $translated_composite_data[$component_id]['description'] ) ){ |
| 100 | $composite_data[$component_id]['description'] = $translated_composite_data[$component_id]['description']; |
| 101 | } |
| 102 | |
| 103 | if ( $component['query_type'] == 'product_ids' ) { |
| 104 | |
| 105 | foreach ( $component['assigned_ids'] as $idx => $assigned_id ) { |
| 106 | $composite_data[$component_id]['assigned_ids'][$idx] = |
| 107 | apply_filters( 'wpml_object_id', $assigned_id, 'product', true, $product_translation->language_code ); |
| 108 | } |
| 109 | |
| 110 | } elseif( $component['query_type'] == 'category_ids' ){ |
| 111 | |
| 112 | foreach ( $component['assigned_category_ids'] as $idx => $assigned_id ) { |
| 113 | $composite_data[$component_id]['assigned_category_ids'][$idx] = |
| 114 | apply_filters( 'wpml_object_id', $assigned_id, 'product_cat', true, $product_translation->language_code ); |
| 115 | |
| 116 | } |
| 117 | |
| 118 | } |
| 119 | |
| 120 | if ( isset( $component['default_id'] ) && $component['default_id'] ) { |
| 121 | $translated_default_id = apply_filters( 'wpml_object_id', $component['default_id'], get_post_type( $component['default_id'] ), false, $product_translation->language_code ); |
| 122 | if ( $translated_default_id ) { |
| 123 | $composite_data[ $component_id ]['default_id'] = $translated_default_id; |
| 124 | } |
| 125 | } |
| 126 | |
| 127 | } |
| 128 | |
| 129 | update_post_meta( $product_translation->element_id, self::META_KEY_DATA, $composite_data ); |
| 130 | |
| 131 | if ( $composite_scenarios_meta ) { |
| 132 | $translate_product_ids = function ( $component_data ) use ( $product_translation ) { |
| 133 | $translate_assigned_product_id = function( $assigned_product_id ) use ( $product_translation ) { |
| 134 | return apply_filters( 'wpml_object_id', $assigned_product_id, get_post_type( $assigned_product_id ), false, $product_translation->language_code ) ?: $assigned_product_id; |
| 135 | }; |
| 136 | |
| 137 | return wpml_collect( (array) $component_data ) |
| 138 | ->map( Fns::map( $translate_assigned_product_id ) ) |
| 139 | ->toArray(); |
| 140 | }; |
| 141 | |
| 142 | $composite_scenarios_meta = wpml_collect( $composite_scenarios_meta ) |
| 143 | ->map( Obj::over( Obj::lensPath( [ 'component_data' ] ), $translate_product_ids ) ) |
| 144 | ->map( Obj::over( Obj::lensPath( [ 'scenario_actions', 'conditional_options', 'component_data' ] ), $translate_product_ids ) ) |
| 145 | ->toArray(); |
| 146 | |
| 147 | update_post_meta( $product_translation->element_id, self::META_KEY_SCENARIO, $composite_scenarios_meta ); |
| 148 | } |
| 149 | } |
| 150 | } |
| 151 | |
| 152 | } |
| 153 | } |
| 154 | |
| 155 | public function custom_box_html( $obj, $product_id, $data ){ |
| 156 | |
| 157 | if( $this->get_product_type( $product_id ) == 'composite' ){ |
| 158 | |
| 159 | $composite_data = $this->get_composite_data( $product_id ); |
| 160 | |
| 161 | $composite_section = new WPML_Editor_UI_Field_Section( __( 'Composite Products ( Components )', 'woocommerce-multilingual' ) ); |
| 162 | end( $composite_data ); |
| 163 | $last_key = key( $composite_data ); |
| 164 | $divider = true; |
| 165 | foreach( $composite_data as $component_id => $component ) { |
| 166 | if( $component_id == $last_key ){ |
| 167 | $divider = false; |
| 168 | } |
| 169 | $group = new WPML_Editor_UI_Field_Group( '', $divider ); |
| 170 | $composite_field = new WPML_Editor_UI_Single_Line_Field( 'composite_'.$component_id.'_title', __( 'Name', 'woocommerce-multilingual' ), $data, false ); |
| 171 | $group->add_field( $composite_field ); |
| 172 | $composite_field = new WPML_Editor_UI_Single_Line_Field( 'composite_'.$component_id.'_description' , __( 'Description', 'woocommerce-multilingual' ), $data, false ); |
| 173 | $group->add_field( $composite_field ); |
| 174 | $composite_section->add_field( $group ); |
| 175 | |
| 176 | } |
| 177 | |
| 178 | if( $composite_data ){ |
| 179 | $obj->add_field( $composite_section ); |
| 180 | } |
| 181 | |
| 182 | $composite_scenarios_meta = $this->get_composite_scenarios_meta( $product_id ); |
| 183 | if( $composite_scenarios_meta ){ |
| 184 | |
| 185 | $composite_scenarios = new WPML_Editor_UI_Field_Section( __( 'Composite Products ( Scenarios )', 'woocommerce-multilingual' ) ); |
| 186 | end( $composite_scenarios_meta ); |
| 187 | $last_key = key( $composite_scenarios_meta ); |
| 188 | $divider = true; |
| 189 | foreach( $composite_scenarios_meta as $scenario_key => $scenario_meta ) { |
| 190 | if( $scenario_key == $last_key ){ |
| 191 | $divider = false; |
| 192 | } |
| 193 | $group = new WPML_Editor_UI_Field_Group( '', $divider ); |
| 194 | $composite_scenario_field = new WPML_Editor_UI_Single_Line_Field( 'composite_scenario_'.$scenario_key.'_title', __( 'Name', 'woocommerce-multilingual' ), $data, false ); |
| 195 | $group->add_field( $composite_scenario_field ); |
| 196 | $composite_scenario_field = new WPML_Editor_UI_Single_Line_Field( 'composite_scenario_'.$scenario_key.'_description' , __( 'Description', 'woocommerce-multilingual' ), $data, false ); |
| 197 | $group->add_field( $composite_scenario_field ); |
| 198 | $composite_scenarios->add_field( $group ); |
| 199 | |
| 200 | } |
| 201 | |
| 202 | $obj->add_field( $composite_scenarios ); |
| 203 | |
| 204 | } |
| 205 | |
| 206 | } |
| 207 | |
| 208 | } |
| 209 | |
| 210 | public function custom_box_html_data( $data, $product_id, $translation, $lang ){ |
| 211 | |
| 212 | if( $this->get_product_type( $product_id ) == 'composite' ){ |
| 213 | |
| 214 | $composite_data = $this->get_composite_data( $product_id ); |
| 215 | |
| 216 | foreach( $composite_data as $component_id => $component ) { |
| 217 | |
| 218 | $data['composite_'.$component_id.'_title'] = [ 'original' => |
| 219 | isset( $composite_data[$component_id]['title'] ) ? $composite_data[$component_id]['title'] : '' ]; |
| 220 | |
| 221 | $data['composite_'.$component_id.'_description'] = [ 'original' => |
| 222 | isset( $composite_data[$component_id]['description'] ) ? $composite_data[$component_id]['description'] : '' ]; |
| 223 | |
| 224 | } |
| 225 | |
| 226 | $composite_scenarios_meta = $this->get_composite_scenarios_meta( $product_id ); |
| 227 | if( $composite_scenarios_meta ){ |
| 228 | foreach( $composite_scenarios_meta as $scenario_key => $scenario_meta ){ |
| 229 | $data[ 'composite_scenario_'.$scenario_key.'_title' ] = [ |
| 230 | 'original' => isset( $scenario_meta['title'] ) ? $scenario_meta['title'] : '', |
| 231 | 'translation' => '' |
| 232 | ]; |
| 233 | |
| 234 | $data[ 'composite_scenario_'.$scenario_key.'_description' ] = [ |
| 235 | 'original' => isset( $scenario_meta['description'] ) ? $scenario_meta['description'] : '', |
| 236 | 'translation' => '' |
| 237 | ]; |
| 238 | } |
| 239 | } |
| 240 | |
| 241 | if ( is_object( $translation ) ) { |
| 242 | $translated_composite_data = $this->get_composite_data( $translation->ID ); |
| 243 | |
| 244 | foreach( $composite_data as $component_id => $component ){ |
| 245 | |
| 246 | $data['composite_'.$component_id.'_title'][ 'translation' ] = |
| 247 | isset( $translated_composite_data[$component_id]['title'] ) ? $translated_composite_data[$component_id]['title'] : ''; |
| 248 | |
| 249 | $data['composite_'.$component_id.'_description'][ 'translation' ] = |
| 250 | isset( $translated_composite_data[$component_id]['description'] ) ? $translated_composite_data[$component_id]['description'] : ''; |
| 251 | |
| 252 | } |
| 253 | |
| 254 | $translated_composite_scenarios_meta = $this->get_composite_scenarios_meta( $translation->ID ); |
| 255 | if( $translated_composite_scenarios_meta ){ |
| 256 | foreach( $translated_composite_scenarios_meta as $scenario_key => $translated_scenario_meta ){ |
| 257 | $data[ 'composite_scenario_'.$scenario_key.'_title' ][ 'translation' ] = |
| 258 | isset( $translated_scenario_meta['title'] ) ? $translated_scenario_meta['title'] : ''; |
| 259 | |
| 260 | $data[ 'composite_scenario_'.$scenario_key.'_description' ][ 'translation' ] = |
| 261 | isset( $translated_scenario_meta['description'] ) ? $translated_scenario_meta['description'] : ''; |
| 262 | } |
| 263 | } |
| 264 | |
| 265 | } |
| 266 | |
| 267 | } |
| 268 | |
| 269 | return $data; |
| 270 | } |
| 271 | |
| 272 | public function update_component_strings( $original_product_id, $product_id, $data, $language ){ |
| 273 | |
| 274 | $composite_data = $this->get_composite_data( $product_id ); |
| 275 | |
| 276 | foreach( $composite_data as $component_id => $component ) { |
| 277 | |
| 278 | if(!empty($data[ md5( 'composite_'.$component_id.'_title' ) ] ) ){ |
| 279 | $composite_data[$component_id]['title'] = $data[ md5( 'composite_'.$component_id.'_title' ) ]; |
| 280 | } |
| 281 | |
| 282 | if(!empty($data[ md5( 'composite_'.$component_id.'_description' ) ])) { |
| 283 | $composite_data[$component_id]['description'] = $data[ md5( 'composite_'.$component_id.'_description' ) ]; |
| 284 | } |
| 285 | |
| 286 | } |
| 287 | |
| 288 | update_post_meta( $product_id, self::META_KEY_DATA, $composite_data ); |
| 289 | |
| 290 | $composite_scenarios_meta = $this->get_composite_scenarios_meta( $product_id ); |
| 291 | if( $composite_scenarios_meta ){ |
| 292 | foreach( $composite_scenarios_meta as $scenario_key => $scenario_meta ){ |
| 293 | if( !empty( $data[ md5( 'composite_scenario_'.$scenario_key.'_title' ) ] ) ){ |
| 294 | $composite_scenarios_meta[ $scenario_key ][ 'title' ] = $data[ md5( 'composite_scenario_'.$scenario_key.'_title' ) ]; |
| 295 | } |
| 296 | |
| 297 | if( !empty( $data[ md5( 'composite_scenario_'.$scenario_key.'_description' ) ])) { |
| 298 | $composite_scenarios_meta[ $scenario_key ][ 'description' ] = $data[ md5( 'composite_scenario_'.$scenario_key.'_description' ) ]; |
| 299 | } |
| 300 | } |
| 301 | } |
| 302 | |
| 303 | update_post_meta( $product_id, self::META_KEY_SCENARIO, $composite_scenarios_meta ); |
| 304 | } |
| 305 | |
| 306 | public function append_composite_data_translation_package( $package, $post ){ |
| 307 | if( 'product' === $post->post_type ) { |
| 308 | $add_fields_to_package = function( $data, $fields, $subtype ) use ( &$package ) { |
| 309 | if ( $data ) { |
| 310 | foreach( $data as $key => $meta ){ |
| 311 | foreach( $fields as $field ) { |
| 312 | if ( ! empty( $meta[ $field ] ) ) { |
| 313 | $package['contents'][ self::get_field_name( $key, $field, $subtype ) ] = [ |
| 314 | 'translate' => 1, |
| 315 | 'data' => base64_encode( $meta[ $field ] ), |
| 316 | 'format' => 'base64', |
| 317 | ]; |
| 318 | } |
| 319 | } |
| 320 | } |
| 321 | } |
| 322 | }; |
| 323 | |
| 324 | $add_fields_to_package( $this->get_composite_data( $post->ID ), [ 'title', 'description' ], '' ); |
| 325 | $add_fields_to_package( $this->get_composite_scenarios_meta( $post->ID ), [ 'title', 'description' ], 'scenario' ); |
| 326 | } |
| 327 | |
| 328 | return $package; |
| 329 | } |
| 330 | |
| 331 | private static function get_field_name( $component_id, $field, $subtype ) { |
| 332 | $subtype = $subtype ? $subtype . ':' : $subtype; |
| 333 | return self::FIELD_TYPE_PREFIX . ':' . $subtype . $component_id . ':' . $field; |
| 334 | } |
| 335 | |
| 336 | public function save_composite_data_translation( $post_id, $data, $job ){ |
| 337 | if ( |
| 338 | Str::startsWith( 'post_', $job->original_post_type ) |
| 339 | && 'product' === get_post_type( $job->original_doc_id ) |
| 340 | ) { |
| 341 | $composite_data = $this->get_composite_data( $job->original_doc_id ); |
| 342 | |
| 343 | if ( $composite_data ) { |
| 344 | $get_translation = function ( $name ) use ( $data ) { |
| 345 | return Obj::path( [ $name, 'data' ], $data ); |
| 346 | }; |
| 347 | |
| 348 | $apply_translations = function ( $data, $fields, $subtype ) use ( $get_translation ) { |
| 349 | foreach ( $data as $key => $meta ) { |
| 350 | foreach ( $fields as $field ) { |
| 351 | $translation = $get_translation( self::get_field_name( $key, $field, $subtype ) ); |
| 352 | |
| 353 | if ( $translation ) { |
| 354 | $data[ $key ][ $field ] = $translation; |
| 355 | } |
| 356 | } |
| 357 | } |
| 358 | |
| 359 | return $data; |
| 360 | }; |
| 361 | |
| 362 | $adjust_ids = function ( $data, $lang ) { |
| 363 | foreach ( $data as $key => $meta ) { |
| 364 | $ids_key = $cpt = null; |
| 365 | |
| 366 | if ( 'product_ids' === $meta['query_type'] ) { |
| 367 | $ids_key = 'assigned_ids'; |
| 368 | $cpt = 'product'; |
| 369 | } elseif ( 'category_ids' === $meta['query_type'] ) { |
| 370 | $ids_key = 'assigned_category_ids'; |
| 371 | $cpt = 'product_cat'; |
| 372 | } |
| 373 | |
| 374 | if ( $ids_key && $cpt ) { |
| 375 | foreach ( $meta[ $ids_key ] as $idx => $assigned_id ) { |
| 376 | $data[ $key ][ $ids_key ][ $idx ] = apply_filters( 'wpml_object_id', $assigned_id, $cpt, true, $lang ); |
| 377 | } |
| 378 | } |
| 379 | } |
| 380 | |
| 381 | return $data; |
| 382 | }; |
| 383 | |
| 384 | update_post_meta( |
| 385 | $post_id, |
| 386 | self::META_KEY_DATA, |
| 387 | $adjust_ids( $apply_translations( $composite_data, [ 'title', 'description' ], '' ), $job->language_code ) |
| 388 | ); |
| 389 | |
| 390 | update_post_meta( |
| 391 | $post_id, |
| 392 | self::META_KEY_SCENARIO, |
| 393 | $apply_translations( $this->get_composite_scenarios_meta( $job->original_doc_id ), [ 'title', 'description' ], 'scenario' ) |
| 394 | ); |
| 395 | } |
| 396 | } |
| 397 | } |
| 398 | |
| 399 | public function wcml_js_lock_fields_input_names( $names ){ |
| 400 | |
| 401 | $names[] = '_base_regular_price'; |
| 402 | $names[] = '_base_sale_price'; |
| 403 | $names[] = 'bto_style'; |
| 404 | |
| 405 | return $names; |
| 406 | } |
| 407 | |
| 408 | public function wcml_js_lock_fields_ids( $names ){ |
| 409 | |
| 410 | $names[] = '_per_product_pricing_bto'; |
| 411 | $names[] = '_per_product_shipping_bto'; |
| 412 | $names[] = '_bto_hide_shop_price'; |
| 413 | |
| 414 | return $names; |
| 415 | } |
| 416 | |
| 417 | public function localize_lock_fields_js(){ |
| 418 | wp_localize_script( 'wcml-composite-js', 'lock_settings' , [ 'lock_fields' => 1 ] ); |
| 419 | } |
| 420 | |
| 421 | public function load_assets( ){ |
| 422 | global $pagenow; |
| 423 | |
| 424 | $is_composite_edit_page = false; |
| 425 | |
| 426 | if( $pagenow == 'post.php' && isset( $_GET[ 'post' ] ) ){ |
| 427 | $wc_product = wc_get_product( $_GET[ 'post' ] ); |
| 428 | if( $wc_product && $wc_product->get_type() === 'composite' ){ |
| 429 | $is_composite_edit_page = true; |
| 430 | } |
| 431 | } |
| 432 | |
| 433 | if( $is_composite_edit_page || $pagenow == 'post-new.php' ){ |
| 434 | wp_register_script( 'wcml-composite-js', WCML_PLUGIN_URL . '/compatibility/res/js/wcml-composite.js', [ 'jquery' ], WCML_VERSION, true ); |
| 435 | wp_enqueue_script( 'wcml-composite-js' ); |
| 436 | |
| 437 | } |
| 438 | |
| 439 | } |
| 440 | |
| 441 | public function woocommerce_json_search_found_products( $found_products ){ |
| 442 | global $wpml_post_translations; |
| 443 | |
| 444 | foreach( $found_products as $id => $product_name ){ |
| 445 | if( $wpml_post_translations->get_element_lang_code ( $id ) != $this->sitepress->get_current_language() ){ |
| 446 | unset( $found_products[ $id ] ); |
| 447 | } |
| 448 | } |
| 449 | |
| 450 | return $found_products; |
| 451 | } |
| 452 | |
| 453 | public function get_composite_scenarios_meta( $product_id ){ |
| 454 | return get_post_meta( $product_id, self::META_KEY_SCENARIO, true ); |
| 455 | } |
| 456 | |
| 457 | public function get_composite_data( $product_id ){ |
| 458 | return get_post_meta( $product_id, self::META_KEY_DATA, true ) ?: []; |
| 459 | } |
| 460 | |
| 461 | public function replace_tm_editor_custom_fields_with_own_sections( $fields ){ |
| 462 | $fields[] = self::META_KEY_DATA; |
| 463 | $fields[] = self::META_KEY_SCENARIO; |
| 464 | |
| 465 | return $fields; |
| 466 | } |
| 467 | } |
| 468 |