<?php
class envoimoinscher_model{

    /** @var array DB updates and callbacks that need to be run per version */
	public static $db_updates = array(
		'1.1.6' => array(
			'emc_update_116_carrier_display_option',
			'emc_update_116_db_version',
		),
		'1.2.6' => array(
			'emc_update_126_pickup_option',
			'emc_update_126_label_delivery_date_options',
			'emc_update_126_tracking_urls',
			'emc_update_126_db_version',
		),
		'1.2.9' => array(
			'emc_update_129_db_drop_columns',
			'emc_update_129_carriers_update',
			'emc_update_129_options',
			'emc_update_129_db_version',
		),
		'1.3.0' => array(
			'emc_update_130_unserializing_multilingual_fields_services',
			'emc_update_130_unserializing_multilingual_fields_option',
			'emc_update_130_convert_insurance_to_ids',
			'emc_update_130_db_version',
		),
		'1.4.0' => array(
			'emc_update_140_convert_scales_to_new_format',
			'emc_update_140_delete_table_emc_scales',
			'emc_update_140_log_option',
			'emc_update_140_db_version',
		),
		'2.0.0' => array(
			'emc_update_200_add_default_address',
			'emc_update_200_post_metas',
            'emc_update_200_alter_table',
			'emc_update_200_options',
			'emc_update_200_db_version',
		),
        '2.1.0' => array(
			'emc_update_210_update_options',
			'emc_update_210_update_authorized_categories',
			'emc_update_210_db_version',
		),
	);

	public static $logger;

    public static $tracking_urls = array(
        "CHRP_Chrono13" => "http://www.chronopost.fr/expedier/inputLTNumbersNoJahia.do?lang=fr_FR&listeNumeros=@",
        "CHRP_Chrono13Samedi" => "http://www.chronopost.fr/expedier/inputLTNumbersNoJahia.do?lang=fr_FR&listeNumeros=@",
        "CHRP_Chrono18" => "http://www.chronopost.fr/expedier/inputLTNumbersNoJahia.do?lang=fr_FR&listeNumeros=@",
        "CHRP_ChronoRelais" => "http://www.chronopost.fr/expedier/inputLTNumbersNoJahia.do?lang=fr_FR&listeNumeros=@",
        "CHRP_ChronoRelaisEurope" => "http://www.chronopost.fr/expedier/inputLTNumbersNoJahia.do?lang=fr_FR&listeNumeros=@",
        "CHRP_ChronoInternationalClassic" => "http://www.chronopost.fr/expedier/inputLTNumbersNoJahia.do?lang=fr_FR&listeNumeros=@",
        "MONR_CpourToi" => "http://www.mondialrelay.fr/ww2/public/mr_suivi.aspx?cab=@",
        "MONR_CpourToiEurope" => "http://www.mondialrelay.fr/ww2/public/mr_suivi.aspx?cab=@",
        "MONR_DomicileEurope" => "http://www.mondialrelay.fr/ww2/public/mr_suivi.aspx?cab=@",
        "SOGP_RelaisColis" => "http://relaiscolis.envoimoinscher.com/suivi-colis.html?reference=@",
        "POFR_ColissimoAccess" => "http://www.colissimo.fr/portail_colissimo/suivreResultat.do?parcelnumber=@",
        "POFR_ColissimoExpert" => "http://www.colissimo.fr/portail_colissimo/suivreResultat.do?parcelnumber=@",
        "IMXE_PackSuiviEurope" => "http://www.happy-post.com/envoyer-colis/followUp/",
        "TNTE_ExpressNational" => "http://www.tnt.fr/public/suivi_colis/recherche/visubontransport.do?radiochoixrecherche=BT&bonTransport=@",
        "TNTE_EconomyExpressInternational" => "http://www.tnt.fr/public/suivi_colis/recherche/visubontransport.do?radiochoixrecherche=BT&bonTransport=@",
        "FEDX_InternationalEconomy" => "https://www.fedex.com/apps/fedextrack/?tracknumbers=@",
        "FEDX_InternationalPriorityCC" => "https://www.fedex.com/apps/fedextrack/?tracknumbers=@",
        "SODX_ExpressStandardInterColisMarch" => "http://www.sodexi.fr/fr/services/tracing/102.html",
        "DHLE_DomesticExpress" => "http://www.dhl.fr/content/fr/fr/dhl_express/suivi_expedition.shtml?brand=DHL&AWB=@",
        "DHLE_ExpressWorldwide" => "http://www.dhl.fr/content/fr/fr/dhl_express/suivi_expedition.shtml?brand=DHL&AWB=@",
        "DHLE_EconomySelect" => "http://www.dhl.fr/content/fr/fr/dhl_express/suivi_expedition.shtml?brand=DHL&AWB=@",
        "UPSE_ExpressSaver" => "https://wwwapps.ups.com/WebTracking/track?HTMLVersion=5.0&loc=fr_FR&Requester=UPSHome&WBPM_lid=homepage%252Fct1.html_pnl_trk&track.x=Suivi&trackNums=@",
        "UPSE_Standard" => "https://wwwapps.ups.com/WebTracking/track?HTMLVersion=5.0&loc=fr_FR&Requester=UPSHome&WBPM_lid=homepage%252Fct1.html_pnl_trk&track.x=Suivi&trackNums=@",
        "UPSE_StandardAP" => "https://wwwapps.ups.com/WebTracking/track?HTMLVersion=5.0&loc=fr_FR&Requester=UPSHome&WBPM_lid=homepage%252Fct1.html_pnl_trk&track.x=Suivi&trackNums=@",
        "UPSE_StandardEs" => "https://www.ups.com/WebTracking/track?HTMLVersion=5.0&loc=es_ES&Requester=UPSHome&WBPM_lid=homepage%252Fct1.html_pnl_trk&track.x=Suivi&trackNums=@",
        "UPSE_ExpressSaverEs" => "https://www.ups.com/WebTracking/track?HTMLVersion=5.0&loc=es_ES&Requester=UPSHome&WBPM_lid=homepage%252Fct1.html_pnl_trk&track.x=Suivi&trackNums=@",
        "PUNT_EntregaenPointsRelais" => "http://www.puntopack.es/seguir-mi-envio/?NumeroExpedition=@&CodePostal=",
        "PUNT_EntregaenPuntosPack" => "http://www.puntopack.es/seguir-mi-envio/?NumeroExpedition=@&CodePostal=",
        "PUNT_Domicilio" => "http://www.puntopack.es/seguir-mi-envio/?NumeroExpedition=@&CodePostal=",
        "SEUR_SeurClassic" => "http://www.seur.com/seguimiento-online.do",
        "SEUR_SeurInternational" => "http://www.seur.com/seguimiento-online.do",
        "SEUR_SeurNational" => "http://www.seur.com/seguimiento-online.do",
        "CREO_CorreosPaq72" => "http://aplicacionesweb.correos.es/localizadorenvios/track.asp?numero=@"
    );

    public static $max_dimensions = array(
        "CHRP_Chrono13" => array(
            'max_weight' => 30,
            'longest_side' => 140,
            'formula' => '{length}+2*{width}+2*{height}',
            'formula_max_value' => 300,
        ),
        "CHRP_Chrono13Samedi" => array(
            'max_weight' => 30,
            'longest_side' => 140,
            'formula' => '{length}+2*{width}+2*{height}',
            'formula_max_value' => 300,
        ),
        "CHRP_Chrono18" => array(
            'max_weight' => 30,
            'longest_side' => 140,
            'formula' => '{length}+2*{width}+2*{height}',
            'formula_max_value' => 300,
        ),
        "CHRP_ChronoRelais" => array(
            'max_weight' => 20,
            'longest_side' => 140,
            'formula' => '{length}+2*{width}+2*{height}',
            'formula_max_value' => 300,
        ),
        "CHRP_ChronoRelaisEurope" => array(
            'max_weight' => 20,
            'longest_side' => 140,
            'formula' => '{length}+2*{width}+2*{height}',
            'formula_max_value' => 300,
        ),
        "CHRP_ChronoInternationalClassic" => array(
            'max_weight' => 30,
            'longest_side' => 140,
            'formula' => '{length}+2*{width}+2*{height}',
            'formula_max_value' => 300,
        ),
        "MONR_CpourToi" => array(
            'max_weight' => 30,
            'longest_side' => 130,
            'formula' => '{length}+{width}+{height}',
            'formula_max_value' => 150,
        ),
        "MONR_CpourToiEurope" => array(
            'max_weight' => 30,
            'longest_side' => 130,
            'formula' => '{length}+{width}+{height}',
            'formula_max_value' => 150,
        ),
        "MONR_DomicileEurope" => array(
            'max_weight' => 30,
            'longest_side' => 130,
            'formula' => '{length}+{width}+{height}',
            'formula_max_value' => 150,
        ),
        "SOGP_RelaisColis" => array(
            'max_weight' => 15,
            'longest_side' => 130,
            'formula' => '{length}+{width}+{height}',
            'formula_max_value' => 170,
        ),
        "POFR_ColissimoAccess" => array(
            'max_weight' => 30,
            'longest_side' => 100,
            'formula' => '{length}+{width}+{height}',
            'formula_max_value' => 150,
        ),
        "POFR_ColissimoExpert" => array(
            'max_weight' => 30,
            'longest_side' => 100,
            'formula' => '{length}+{width}+{height}',
            'formula_max_value' => 150,
        ),
        "IMXE_PackSuiviEurope" => array(
            'max_weight' => 20,
            'longest_side' => 120,
            'formula' => '{length}+{width}+{height}',
            'formula_max_value' => 180,
        ),
        "TNTE_ExpressNational" => array(
            'max_weight' => 30,
            'longest_side' => 130,
            'formula' => '{length}+{width}+{height}',
            'formula_max_value' => 170,
        ),
        "TNTE_EconomyExpressInternational" => array(
            'max_weight' => 70,
            'longest_side' => 100,
            'formula' => '{length}+2*{width}+2*{height}',
            'formula_max_value' => 300,
        ),
        "FEDX_InternationalEconomy" => array(
            'max_weight' => 65,
            'longest_side' => 175,
            'formula' => '{length}+2*{width}+2*{height}',
            'formula_max_value' => 330,
        ),
        "FEDX_InternationalPriorityCC" => array(
            'max_weight' => 65,
            'longest_side' => 175,
            'formula' => '{length}+2*{width}+2*{height}',
            'formula_max_value' => 330,
        ),
        "SODX_ExpressStandardInterColisMarch" => array(
            'max_weight' => 65,
            'longest_side' => 175,
            'formula' => '{length}+2*{width}+2*{height}',
            'formula_max_value' => 300,
        ),
        "DHLE_DomesticExpress" => array(
            'max_weight' => 65,
            'longest_side' => 115,
            'formula' => '{length}+2*{width}+2*{height}',
            'formula_max_value' => 310,
        ),
        "DHLE_ExpressWorldwide" => array(
            'max_weight' => 30,
            'longest_side' => 120,
            'formula' => '{length}+2*{width}+2*{height}',
            'formula_max_value' => 270,
        ),
        "DHLE_EconomySelect" => array(
            'max_weight' => 70,
            'longest_side' => 120,
            'formula' => '{length}+2*{width}+2*{height}',
            'formula_max_value' => 270,
        ),
        "UPSE_ExpressSaver" => array(
            'max_weight' => 70,
            'longest_side' => 270,
            'formula' => '{length}+2*{width}+2*{height}',
            'formula_max_value' => 419,
        ),
        "UPSE_Standard" => array(
            'max_weight' => 70,
            'longest_side' => 270,
            'formula' => '{length}+2*{width}+2*{height}',
            'formula_max_value' => 419,
        ),
        "UPSE_StandardAP" => array(
            'max_weight' => 20,
            'longest_side' => 80,
            'formula' => '{length}+2*{width}+2*{height}',
            'formula_max_value' => 300,
        ),
        "UPSE_StandardEs" => array(
            'max_weight' => 70,
            'longest_side' => 270,
            'formula' => '{length}+2*{width}+2*{height}',
            'formula_max_value' => 419,
        ),
        "UPSE_ExpressSaverEs" => array(
            'max_weight' => 70,
            'longest_side' => 270,
            'formula' => '{length}+2*{width}+2*{height}',
            'formula_max_value' => 419,
        ),
        "PUNT_EntregaenPuntosPack" => array(
            'max_weight' => 30,
            'longest_side' => 130,
            'formula' => '{length}+{width}+{height}',
            'formula_max_value' => 150,
        ),
        "PUNT_EntregaenPointsRelais" => array(
            'max_weight' => 30,
            'longest_side' => 130,
            'formula' => '{length}+{width}+{height}',
            'formula_max_value' => 150,
        ),
        "PUNT_Domicilio" => array(
            'max_weight' => 30,
            'longest_side' => 130,
            'formula' => '{length}+{width}+{height}',
            'formula_max_value' => 150,
        ),
        "SEUR_SeurClassic" => array(
            'max_weight' => 30,
            'longest_side' => 150,
            'formula' => '{length}+{width}+{height}',
            'formula_max_value' => 150,
        ),
        "SEUR_SeurInternational" => array(
            'max_weight' => 30,
            'longest_side' => 150,
            'formula' => '{length}+{width}+{height}',
            'formula_max_value' => 150,
        ),
        "SEUR_SeurNational" => array(
            'max_weight' => 30,
            'longest_side' => 150,
            'formula' => '{length}+{width}+{height}',
            'formula_max_value' => 150,
        ),
        "CREO_CorreosPaq72" => array(
            'max_weight' => 30,
            'longest_side' => 120,
            'formula' => '{length}+{width}+{height}',
            'formula_max_value' => 210,
        ),
    );

    // used on install
    public static $default_categories = array(
        array(
            "cat_id" => 10000,
            "cat_group" => 0,
            "cat_name" => "Books and documents",
            "cat_prohibited" => 0
        ),
        array(
            "cat_id" => 10100,
            "cat_group" => 10000,
            "cat_name" => "Documents without commercial value",
            "cat_prohibited" => 0
        ),
        array(
            "cat_id" => 10120,
            "cat_group" => 10000,
            "cat_name" => "Newspapers",
            "cat_prohibited" => 0
        ),
        array(
            "cat_id" => 10130,
            "cat_group" => 10000,
            "cat_name" => "Magazines, journals",
            "cat_prohibited" => 0
        ),
        array(
            "cat_id" => 10140,
            "cat_group" => 10000,
            "cat_name" => "Technical manuals",
            "cat_prohibited" => 0
        ),
        array(
            "cat_id" => 10150,
            "cat_group" => 10000,
            "cat_name" => "Books",
            "cat_prohibited" => 0
        ),
        array(
            "cat_id" => 10160,
            "cat_group" => 10000,
            "cat_name" => "Passports",
            "cat_prohibited" => 0
        ),
        array(
            "cat_id" => 10170,
            "cat_group" => 10000,
            "cat_name" => "Plane tickets",
            "cat_prohibited" => 0
        ),
        array(
            "cat_id" => 10180,
            "cat_group" => 10000,
            "cat_name" => "X-rays",
            "cat_prohibited" => 0
        ),
        array(
            "cat_id" => 10190,
            "cat_group" => 10000,
            "cat_name" => "Photographs",
            "cat_prohibited" => 0
        ),
        array(
            "cat_id" => 10200,
            "cat_group" => 10000,
            "cat_name" => "Internal company mail",
            "cat_prohibited" => 0
        ),
        array(
            "cat_id" => 10210,
            "cat_group" => 10000,
            "cat_name" => "Business proposals",
            "cat_prohibited" => 0
        ),
        array(
            "cat_id" => 10220,
            "cat_group" => 10000,
            "cat_name" => "Promotional materials",
            "cat_prohibited" => 0
        ),
        array(
            "cat_id" => 10230,
            "cat_group" => 10000,
            "cat_name" => "Catalogues, annual reports",
            "cat_prohibited" => 0
        ),
        array(
            "cat_id" => 10240,
            "cat_group" => 10000,
            "cat_name" => "Computer printouts",
            "cat_prohibited" => 0
        ),
        array(
            "cat_id" => 10250,
            "cat_group" => 10000,
            "cat_name" => "Plans, designs",
            "cat_prohibited" => 0
        ),
        array(
            "cat_id" => 10260,
            "cat_group" => 10000,
            "cat_name" => "Printed documents",
            "cat_prohibited" => 0
        ),
        array(
            "cat_id" => 10280,
            "cat_group" => 10000,
            "cat_name" => "Templates",
            "cat_prohibited" => 0
        ),
        array(
            "cat_id" => 10290,
            "cat_group" => 10000,
            "cat_name" => "Labels, stickers",
            "cat_prohibited" => 0
        ),
        array(
            "cat_id" => 10300,
            "cat_group" => 10000,
            "cat_name" => "Tender documents",
            "cat_prohibited" => 0
        ),
        array(
            "cat_id" => 20000,
            "cat_group" => 0,
            "cat_name" => "Food and perishables",
            "cat_prohibited" => 0
        ),
        array(
            "cat_id" => 20100,
            "cat_group" => 20000,
            "cat_name" => "Non-perishable foodstuffs",
            "cat_prohibited" => 0
        ),
        array(
            "cat_id" => 20102,
            "cat_group" => 20000,
            "cat_name" => "Fresh and perishable produce",
            "cat_prohibited" => 1
        ),
        array(
            "cat_id" => 20103,
            "cat_group" => 20000,
            "cat_name" => "Refrigerated products",
            "cat_prohibited" => 1
        ),
        array(
            "cat_id" => 20105,
            "cat_group" => 20000,
            "cat_name" => "Frozen products",
            "cat_prohibited" => 1
        ),
        array(
            "cat_id" => 20110,
            "cat_group" => 20000,
            "cat_name" => "Non-alcoholic beverages",
            "cat_prohibited" => 0
        ),
        array(
            "cat_id" => 20120,
            "cat_group" => 20000,
            "cat_name" => "Alcoholic beverages",
            "cat_prohibited" => 0
        ),
        array(
            "cat_id" => 20130,
            "cat_group" => 20000,
            "cat_name" => "Plants, flowers, seeds",
            "cat_prohibited" => 1
        ),
        array(
            "cat_id" => 30000,
            "cat_group" => 0,
            "cat_name" => "Products",
            "cat_prohibited" => 0
        ),
        array(
            "cat_id" => 30100,
            "cat_group" => 30000,
            "cat_name" => "Cosmetics, well-being products",
            "cat_prohibited" => 0
        ),
        array(
            "cat_id" => 30200,
            "cat_group" => 30000,
            "cat_name" => "Pharmaceuticals, medications",
            "cat_prohibited" => 1
        ),
        array(
            "cat_id" => 30300,
            "cat_group" => 30000,
            "cat_name" => "Chemicals, drugs, cleaning products",
            "cat_prohibited" => 0
        ),
        array(
            "cat_id" => 50190,
            "cat_group" => 30000,
            "cat_name" => "Tobacco",
            "cat_prohibited" => 1
        ),
        array(
            "cat_id" => 50200,
            "cat_group" => 30000,
            "cat_name" => "Perfumes",
            "cat_prohibited" => 1
        ),
        array(
            "cat_id" => 40000,
            "cat_group" => 0,
            "cat_name" => "Clothing and accessories",
            "cat_prohibited" => 0
        ),
        array(
            "cat_id" => 40100,
            "cat_group" => 40000,
            "cat_name" => "Shoes",
            "cat_prohibited" => 0
        ),
        array(
            "cat_id" => 40110,
            "cat_group" => 40000,
            "cat_name" => "Fabrics, new clothes",
            "cat_prohibited" => 0
        ),
        array(
            "cat_id" => 40120,
            "cat_group" => 40000,
            "cat_name" => "Used clothes",
            "cat_prohibited" => 0
        ),
        array(
            "cat_id" => 40125,
            "cat_group" => 40000,
            "cat_name" => "Clothing/fashion accessories",
            "cat_prohibited" => 0
        ),
        array(
            "cat_id" => 40130,
            "cat_group" => 40000,
            "cat_name" => "Leather, skins, leather goods",
            "cat_prohibited" => 0
        ),
        array(
            "cat_id" => 40150,
            "cat_group" => 40000,
            "cat_name" => "Costume jewellery",
            "cat_prohibited" => 0
        ),
        array(
            "cat_id" => 50160,
            "cat_group" => 40000,
            "cat_name" => "Jewellery, precious objects",
            "cat_prohibited" => 1
        ),
        array(
            "cat_id" => 50000,
            "cat_group" => 0,
            "cat_name" => "Equipment and appliances",
            "cat_prohibited" => 0
        ),
        array(
            "cat_id" => 50100,
            "cat_group" => 50000,
            "cat_name" => "Medical equipment",
            "cat_prohibited" => 0
        ),
        array(
            "cat_id" => 50110,
            "cat_group" => 50000,
            "cat_name" => "IT, high-tech and fixed-line telephony equipment",
            "cat_prohibited" => 0
        ),
        array(
            "cat_id" => 50113,
            "cat_group" => 50000,
            "cat_name" => "Mobile telephony and accessories",
            "cat_prohibited" => 0
        ),
        array(
            "cat_id" => 50114,
            "cat_group" => 50000,
            "cat_name" => "Televisions, computer screens",
            "cat_prohibited" => 0
        ),
        array(
            "cat_id" => 50120,
            "cat_group" => 50000,
            "cat_name" => "Other devices and equipment",
            "cat_prohibited" => 0
        ),
        array(
            "cat_id" => 50130,
            "cat_group" => 50000,
            "cat_name" => "Digital media, CD, DVD",
            "cat_prohibited" => 0
        ),
        array(
            "cat_id" => 50140,
            "cat_group" => 50000,
            "cat_name" => "Spare parts and accessories (auto)",
            "cat_prohibited" => 0
        ),
        array(
            "cat_id" => 50150,
            "cat_group" => 50000,
            "cat_name" => "Spare parts and accessories (other)",
            "cat_prohibited" => 0
        ),
        array(
            "cat_id" => 50170,
            "cat_group" => 50000,
            "cat_name" => "Watches, timepieces (excluding jewellery)",
            "cat_prohibited" => 0
        ),
        array(
            "cat_id" => 50330,
            "cat_group" => 50000,
            "cat_name" => "Camping and fishing items",
            "cat_prohibited" => 0
        ),
        array(
            "cat_id" => 50350,
            "cat_group" => 50000,
            "cat_name" => "Sporting apparel (excluding clothing)",
            "cat_prohibited" => 0
        ),
        array(
            "cat_id" => 50360,
            "cat_group" => 50000,
            "cat_name" => "Musical instruments and accessories",
            "cat_prohibited" => 0
        ),
        array(
            "cat_id" => 50380,
            "cat_group" => 50000,
            "cat_name" => "Heating and boiler equipment",
            "cat_prohibited" => 0
        ),
        array(
            "cat_id" => 50390,
            "cat_group" => 50000,
            "cat_name" => "Laboratory, optical and measuring equipment",
            "cat_prohibited" => 0
        ),
        array(
            "cat_id" => 50395,
            "cat_group" => 50000,
            "cat_name" => "Electrical equipment, transformers, cables",
            "cat_prohibited" => 0
        ),
        array(
            "cat_id" => 50400,
            "cat_group" => 50000,
            "cat_name" => "Office supplies, stationery, refills",
            "cat_prohibited" => 0
        ),
        array(
            "cat_id" => 50420,
            "cat_group" => 50000,
            "cat_name" => "Engines, gearboxes",
            "cat_prohibited" => 0
        ),
        array(
            "cat_id" => 50430,
            "cat_group" => 50000,
            "cat_name" => "Motor cycles, scooters",
            "cat_prohibited" => 1
        ),
        array(
            "cat_id" => 50440,
            "cat_group" => 50000,
            "cat_name" => "Bicycles, non-motorised cycles",
            "cat_prohibited" => 0
        ),
        array(
            "cat_id" => 50450,
            "cat_group" => 50000,
            "cat_name" => "Tooling, tools, DIY equipment",
            "cat_prohibited" => 0
        ),
        array(
            "cat_id" => 50490,
            "cat_group" => 50000,
            "cat_name" => "Plumbing equipment, plastic tubes",
            "cat_prohibited" => 0
        ),
        array(
            "cat_id" => 50500,
            "cat_group" => 50000,
            "cat_name" => "Hardware, valves, locks",
            "cat_prohibited" => 0
        ),
        array(
            "cat_id" => 60000,
            "cat_group" => 0,
            "cat_name" => "Furniture and decoration",
            "cat_prohibited" => 0
        ),
        array(
            "cat_id" => 60100,
            "cat_group" => 60000,
            "cat_name" => "Home furniture",
            "cat_prohibited" => 0
        ),
        array(
            "cat_id" => 60102,
            "cat_group" => 60000,
            "cat_name" => "Office furniture",
            "cat_prohibited" => 0
        ),
        array(
            "cat_id" => 60105,
            "cat_group" => 60000,
            "cat_name" => "Dismantled and packaged furniture",
            "cat_prohibited" => 0
        ),
        array(
            "cat_id" => 60108,
            "cat_group" => 60000,
            "cat_name" => "Old (antique) furniture",
            "cat_prohibited" => 0
        ),
        array(
            "cat_id" => 60110,
            "cat_group" => 60000,
            "cat_name" => "Household electrical goods",
            "cat_prohibited" => 1
        ),
        array(
            "cat_id" => 60112,
            "cat_group" => 60000,
            "cat_name" => "Small household electrical goods, small domestic appliances",
            "cat_prohibited" => 0
        ),
        array(
            "cat_id" => 60120,
            "cat_group" => 60000,
            "cat_name" => "Listed objects or paintings, collectible items, mirrors, windows",
            "cat_prohibited" => 1
        ),
        array(
            "cat_id" => 60122,
            "cat_group" => 60000,
            "cat_name" => "Works of art and paintings of low value",
            "cat_prohibited" => 0
        ),
        array(
            "cat_id" => 60124,
            "cat_group" => 60000,
            "cat_name" => "Lamps, lighting",
            "cat_prohibited" => 0
        ),
        array(
            "cat_id" => 60126,
            "cat_group" => 60000,
            "cat_name" => "Carpets",
            "cat_prohibited" => 0
        ),
        array(
            "cat_id" => 60128,
            "cat_group" => 60000,
            "cat_name" => "Linens, curtains, sheets",
            "cat_prohibited" => 0
        ),
        array(
            "cat_id" => 60129,
            "cat_group" => 60000,
            "cat_name" => "Toilets, glasses, crystal glassware, dishware, ornaments",
            "cat_prohibited" => 0
        ),
        array(
            "cat_id" => 60130,
            "cat_group" => 60000,
            "cat_name" => "Other fragile objects and sculptures",
            "cat_prohibited" => 0
        ),
        array(
            "cat_id" => 70000,
            "cat_group" => 0,
            "cat_name" => "Personal belongings, gifts",
            "cat_prohibited" => 0
        ),
        array(
            "cat_id" => 50180,
            "cat_group" => 70000,
            "cat_name" => "Gifts, corporate gifts",
            "cat_prohibited" => 0
        ),
        array(
            "cat_id" => 70100,
            "cat_group" => 70000,
            "cat_name" => "Luggage, suitcases, trunks",
            "cat_prohibited" => 0
        ),
        array(
            "cat_id" => 70200,
            "cat_group" => 70000,
            "cat_name" => "Small-scale removals, boxes, personal effects",
            "cat_prohibited" => 0
        ),
        array(
            "cat_id" => 80000,
            "cat_group" => 0,
            "cat_name" => "Sports and leisure items",
            "cat_prohibited" => 0
        ),
        array(
            "cat_id" => 80100,
            "cat_group" => 80000,
            "cat_name" => "Cultural products: books, games, CDs, DVDs etc.",
            "cat_prohibited" => 0
        ),
        array(
            "cat_id" => 80200,
            "cat_group" => 80000,
            "cat_name" => "Electronic devices, audiovisual equipment etc.",
            "cat_prohibited" => 0
        ),
        array(
            "cat_id" => 80300,
            "cat_group" => 80000,
            "cat_name" => "Well-being and healthcare products: creams, oils, appliances etc.",
            "cat_prohibited" => 0
        ),
        array(
            "cat_id" => 80400,
            "cat_group" => 80000,
            "cat_name" => "Childcare-related items, toys, items for children etc.",
            "cat_prohibited" => 0
        ),
        array(
            "cat_id" => 80500,
            "cat_group" => 80000,
            "cat_name" => "Creative leisure-related items, materials, arts and crafts equipment etc.",
            "cat_prohibited" => 0
        ),
    );

    // used on install
    public static $default_dimensions = array(
        array(
            "dim_id" => 1,
            "dim_length" => 18,
            "dim_width" => 18,
            "dim_height" => 18,
            "dim_weight_from" => 0,
            "dim_weight" => 1
        ),
        array(
            "dim_id" => 2,
            "dim_length" => 22,
            "dim_width" => 22,
            "dim_height" => 22,
            "dim_weight_from" => 1,
            "dim_weight" => 2
        ),
        array(
            "dim_id" => 3,
            "dim_length" => 26,
            "dim_width" => 26,
            "dim_height" => 26,
            "dim_weight_from" => 2,
            "dim_weight" => 3
        ),
        array(
            "dim_id" => 4,
            "dim_length" => 28,
            "dim_width" => 28,
            "dim_height" => 28,
            "dim_weight_from" => 3,
            "dim_weight" => 4
        ),
        array(
            "dim_id" => 5,
            "dim_length" => 31,
            "dim_width" => 31,
            "dim_height" => 31,
            "dim_weight_from" => 4,
            "dim_weight" => 5
        ),
        array(
            "dim_id" => 6,
            "dim_length" => 33,
            "dim_width" => 33,
            "dim_height" => 33,
            "dim_weight_from" => 5,
            "dim_weight" => 6
        ),
        array(
            "dim_id" => 7,
            "dim_length" => 34,
            "dim_width" => 34,
            "dim_height" => 34,
            "dim_weight_from" => 6,
            "dim_weight" => 7
        ),
        array(
            "dim_id" => 8,
            "dim_length" => 36,
            "dim_width" => 36,
            "dim_height" => 36,
            "dim_weight_from" => 7,
            "dim_weight" => 8
        ),
        array(
            "dim_id" => 9,
            "dim_length" => 37,
            "dim_width" => 37,
            "dim_height" => 37,
            "dim_weight_from" => 8,
            "dim_weight" => 9
        ),
        array(
            "dim_id" => 10,
            "dim_length" => 39,
            "dim_width" => 39,
            "dim_height" => 39,
            "dim_weight_from" => 9,
            "dim_weight" => 10
        ),
        array(
            "dim_id" => 11,
            "dim_length" => 44,
            "dim_width" => 44,
            "dim_height" => 44,
            "dim_weight_from" => 10,
            "dim_weight" => 15
        ),
        array(
            "dim_id" => 12,
            "dim_length" => 47,
            "dim_width" => 47,
            "dim_height" => 47,
            "dim_weight_from" => 15,
            "dim_weight" => 20
        ),
        array(
            "dim_id" => 13,
            "dim_length" => 49,
            "dim_width" => 49,
            "dim_height" => 49,
            "dim_weight_from" => 20,
            "dim_weight" => 50
        ),
        array(
            "dim_id" => 14,
            "dim_length" => 70,
            "dim_width" => 70,
            "dim_height" => 70,
            "dim_weight_from" => 50,
            "dim_weight" => 70
        ),
    );


	/**
	 * Create a new logger for the module
	 */
	static function init_logger() {
		self::$logger = new WC_Logger();
	}

	/**
	 * Remove the plugin's tables
	 */
	static function delete_database() {
		global $wpdb;
		// delete module tables inserted from create_database
		$sql = array();
		$sql[] = 'DROP TABLE IF EXISTS `'.$wpdb->prefix.'emc_addresses`';
		$sql[] = 'DROP TABLE IF EXISTS `'.$wpdb->prefix.'emc_categories`';
		$sql[] = 'DROP TABLE IF EXISTS `'.$wpdb->prefix.'emc_dimensions`';
		$sql[] = 'DROP TABLE IF EXISTS `'.$wpdb->prefix.'emc_operators`';
		$sql[] = 'DROP TABLE IF EXISTS `'.$wpdb->prefix.'emc_operators_authorized_contents`';
		$sql[] = 'DROP TABLE IF EXISTS `'.$wpdb->prefix.'emc_services`';
		$sql[] = 'DROP TABLE IF EXISTS `'.$wpdb->prefix.'emc_orders`';
        $sql[] = 'DROP TABLE IF EXISTS `'.$wpdb->prefix.'emc_scales`';
        $sql[] = 'DROP TABLE IF EXISTS `'.$wpdb->prefix.'emc_pricing`';

		foreach($sql as $query) {
			$wpdb->query($query,OBJECT);
			if ($wpdb->last_error != '') {
				self::handle_sql_error();
			}
		}

		// delete cache
		self::flush_rates_cache();

		// delete configuration
		$query = 'DELETE FROM `'.$wpdb->options.'` WHERE option_name LIKE "EMC_%";';

		$wpdb->query($query,OBJECT);
		if ($wpdb->last_error != '') {
			self::handle_sql_error();
		}

		return true;
	}

	/**
	 * Create the plugin's tables
	 */
	static function create_database() {
        require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );

        $tables = self::get_schema();
        $result = dbDelta( $tables );

        if (count($result) > 0)
        {
          self::log("create_database result : \n".implode("\n",$result),true);
        }
    }

    /**
	 * Get Table schema.
	 * @return string
	 */
	static function get_schema() {
        global $wpdb;

        $collate = '';

		if ( $wpdb->has_cap( 'collation' ) ) {
			$collate = $wpdb->get_charset_collate();
		}

		/*
		 * Indexes have a maximum size of 767 bytes. Historically, we haven't need to be concerned about that.
		 * As of WordPress 4.2, however, we moved to utf8mb4, which uses 4 bytes per character. This means that an index which
		 * used to have room for floor(767/3) = 255 characters, now only has room for floor(767/4) = 191 characters.
		 *
		 * This may cause duplicate index notices in logs due to https://core.trac.wordpress.org/ticket/34870 but dropping
		 * indexes first causes too much load on some servers/larger DB.
		 */
		$max_index_length = 191;

		$tables = "
CREATE TABLE {$wpdb->prefix}emc_addresses (
    id int(11) NOT NULL AUTO_INCREMENT,
    type enum('default','additional') NOT NULL,
    active tinyint(1) NOT NULL,
    label varchar(100) NOT NULL,
    gender enum('M','Mme') NOT NULL,
    first_name varchar(100) NOT NULL,
    last_name varchar(100) NOT NULL,
    company varchar(100) NOT NULL,
    address varchar(1024) NOT NULL,
    zipcode varchar(20) NOT NULL,
    city varchar(100) NOT NULL,
    phone varchar(50) NOT NULL,
    email varchar(512) NOT NULL,
    add_info varchar(1024) DEFAULT NULL,
    dispo_hde enum('12:00','12:15','12:30','12:45','13:00','13:15','13:30','13:45','14:00','14:15','14:30','14:45','15:00','15:15','15:30','15:45','16:00','16:15','16:30','16:45','17:00') NOT NULL,
    dispo_hle enum('17:00','17:15','17:30','17:45','18:00','18:15','18:30','18:45','19:00','19:15','19:30','19:45','20:00','20:15','20:30','20:45','21:00') NOT NULL,
    pickup_day_1 int(2) NOT NULL DEFAULT '2',
    pickup_day_2 int(2) NOT NULL DEFAULT '3',
    pickup_split int(2) NOT NULL DEFAULT '17',
    shipping_classes text,
    dropoff_points text,
    PRIMARY KEY  (id)
) $collate;
CREATE TABLE {$wpdb->prefix}emc_categories (
    cat_id int(11) NOT NULL,
    cat_group int(11) NOT NULL,
    cat_name varchar(100) NOT NULL,
    cat_prohibited int(1) NOT NULL DEFAULT 0,
    PRIMARY KEY  (cat_id)
) $collate;
CREATE TABLE {$wpdb->prefix}emc_dimensions (
    dim_id int(3) NOT NULL,
    dim_length int(3) NOT NULL,
    dim_width int(3) NOT NULL,
    dim_height int(3) NOT NULL,
    dim_weight_from float NOT NULL,
    dim_weight float NOT NULL,
    PRIMARY KEY  (dim_id)
) $collate;
CREATE TABLE {$wpdb->prefix}emc_operators (
    ope_id int(2) NOT NULL AUTO_INCREMENT,
    ope_name varchar(100) NOT NULL,
    ope_code char(4) NOT NULL,
    PRIMARY KEY  (ope_id)
) $collate;
CREATE TABLE {$wpdb->prefix}emc_services (
    srv_id int(11) NOT NULL AUTO_INCREMENT,
    srv_ope_id int(11) NOT NULL,
    srv_code varchar(128) NOT NULL,
    srv_name varchar(1024) NOT NULL,
    srv_name_bo varchar(1024) NOT NULL,
    srv_description varchar(1024) NOT NULL,
    srv_pickup_point int(1) NOT NULL,
    srv_pickup_place varchar(1024) NOT NULL,
    srv_dropoff_point int(1) NOT NULL,
    srv_dropoff_place varchar(1024) NOT NULL,
    srv_family int(1) NOT NULL,
    srv_zone_fr int(1) NOT NULL,
    srv_zone_es int(1) NOT NULL,
    srv_zone_eu int(1) NOT NULL,
    srv_zone_int int(1) NOT NULL,
    srv_zone_restriction varchar(1024) DEFAULT NULL,
    srv_details text DEFAULT NULL,
    srv_delivery_due_time varchar(1024) NOT NULL,
    srv_delivery_type int(1) NOT NULL,
    PRIMARY KEY  (srv_id)
) $collate;
CREATE TABLE {$wpdb->prefix}emc_operators_authorized_contents (
    ope_code char(4) NOT NULL,
    cat_id int(11) NOT NULL,
    UNIQUE KEY pkey (ope_code,cat_id)
) $collate;
CREATE TABLE {$wpdb->prefix}emc_pricing (
    pricing_id int(3) NOT NULL,
    carrier_code varchar(256) NOT NULL,
    price_from float DEFAULT NULL,
    price_to float DEFAULT NULL,
    weight_from float DEFAULT NULL,
    weight_to float DEFAULT NULL,
    shipping_class text NOT NULL,
    zones text NOT NULL,
    handling_fees varchar(128) DEFAULT NULL,
    pricing enum('live','rate','free','deactivate') NOT NULL,
    flat_rate float DEFAULT NULL,
    UNIQUE KEY pkey (pricing_id,carrier_code($max_index_length))
) $collate;
    ";

		return $tables;
	}

    /**
	 * Adds default database data if tables are empty
	 * @access public
	 */
	static function add_database_default_info() {
        global $wpdb;

        $count = $wpdb->get_var("SELECT COUNT(*)
            FROM {$wpdb->prefix}emc_categories WHERE cat_id IS NOT NULL");

        if($count == 0) {
            foreach (self::$default_categories as $category) {
                $wpdb->insert(
                    $wpdb->prefix.'emc_categories',
                    $category,
                    array(
                        '%d',
                        '%d',
                        '%s',
                        '%d'
                    )
                );
            }
        }

        $count = $wpdb->get_var("SELECT COUNT(*)
            FROM {$wpdb->prefix}emc_dimensions WHERE dim_id IS NOT NULL");

        if($count == 0) {
            foreach (self::$default_dimensions as $dimension) {
                $wpdb->insert(
                    $wpdb->prefix.'emc_dimensions',
                    $dimension,
                    array(
                        '%d',
                        '%d',
                        '%d',
                        '%d',
                        '%f',
                        '%f'
                    )
                );
            }
        }
    }

	/**
	 * Default options
	 *
	 * Sets up the default options used on the settings page
	 *
	 * @access public
	 */
	static function create_options() {
		// Include settings so that we can run through defaults
		include_once( 'includes/admin/class-emc-admin-menus.php' );

        // add options not added by settings API
        add_option('EMC_CARRIER_DISPLAY','yes');

        $menus = emc_admin_menus::instance();

		$menus->load_settings();

		foreach ( $menus->settings as $setting_page ) {
			$sections = $setting_page->get_settings(true);

            foreach ($sections as $section) {
                foreach ($section['fields'] as $field) {
                    if ( isset( $field['default'] ) && isset( $field['id'] ) && !(isset ($field['donotsave']) && $field['donotsave'] == true)) {
                        $autoload = isset( $field['autoload'] ) ? (bool) $field['autoload'] : true;
                        add_option( $field['id'], $field['default'], '', ( $autoload ? 'yes' : 'no' ) );
                    }
                }
            }
		}
		return true;
	}

	/**************************/
	/*********** DB ***********/
	/**************************/

	/**
	 * @return list of all shipping categories
	 */
	static function get_categories() {
		global $wpdb;
		$result = $wpdb->get_results('SELECT * FROM '.$wpdb->prefix.'emc_categories',OBJECT);
		self::handle_sql_error();
        $categories = array();
        $category_groups = array();
        if (!empty($result)) {
            foreach($result as $category) {
                if ($category->cat_group != 0) {
                    $categories[$category->cat_group]['categories'][$category->cat_id] = array(
                        'label' => __($category->cat_name, 'envoimoinscher'),
                        'forbidden' => $category->cat_prohibited
                    );
                } else {
                    $category_groups[$category->cat_id] = __($category->cat_name, 'envoimoinscher');
                }
            }
        }

        // alphabetical sorting
        $return = array();
        asort($category_groups);
        foreach ($category_groups as $category_group_id => $category_name) {
            $return[$category_group_id]['name'] = $category_name;
            $return[$category_group_id]['categories'] = $categories[$category_group_id]['categories'];
            asort($return[$category_group_id]['categories']);
        }

		return $return;
	}

	/**
	 * @return list of all emc services
	 */
	static function get_services() {
		global $wpdb;
		$result = $wpdb->get_results('SELECT * FROM '.$wpdb->prefix.'emc_services AS services JOIN '.$wpdb->prefix.'emc_operators AS operators ON srv_ope_id = ope_id',OBJECT);
		self::handle_sql_error();
		return $result;
	}

	/**
	 * @return list of all emc operators
	 */
	static function get_operators() {
		global $wpdb;
		$result = $wpdb->get_results( 'SELECT * FROM '.$wpdb->prefix.'emc_operators', OBJECT );
		self::handle_sql_error();
		return $result;
	}

	/**
	 * Get all enable shipping methods
	 * @param $emc_only : true if only envoimoinscher carriers are wanted
	 * @return list of all enabled shipping methods
	 */
	static function get_enabled_shipping_methods( $emc_only = true ) {
		$shipping_methods = WC()->shipping->load_shipping_methods();
		$enabled_methods = array();

		foreach( $shipping_methods as $method ) {
			if ( $method->enabled == 'yes' ) {
				if ( $emc_only && self::get_service_by_carrier_code( $method->id ) ) {
					array_push( $enabled_methods, $method->id );
				}
				elseif ( $emc_only ) {}
				else {
					array_push( $enabled_methods, $method->id );
				}
			}
		}

		return $enabled_methods;
	}

	/**
	 * Get all emc active shipping methods
	 * @return list of all emc active shipping methods
	 */
	static function get_emc_active_shipping_methods() {

		$emc_services = get_option( 'EMC_SERVICES' , array ( 1 => array(), 2 => array() ) );

		$active_methods = array();

		if (is_array($emc_services)) {
			foreach($emc_services[1] as $value) {
				array_push($active_methods, $value);
			}
			foreach($emc_services[2] as $value) {
				array_push($active_methods, $value);
			}
		}

		return $active_methods;
	}

	/**
	 * Get a service by his id
	 * @param $id : service id
	 * @return the asked service
	 */
	static function get_service_by_id($id) {
		global $wpdb;
		$result = $wpdb->get_results($wpdb->prepare('SELECT * FROM '.$wpdb->prefix.'emc_services WHERE srv_id = %d',array($id)),OBJECT);
		self::handle_sql_error();
		return $result;
	}

	/**
	 * Get a service by his carrier code
	 * @param $carrier_code : operator_service (ex: MONR_CpourToi)
	 * @return the asked service if it exists or false if not
	 */
	static function get_service_by_carrier_code($carrier_code) {
		global $wpdb;
		$carrier = explode("_", $carrier_code);
		if ( count($carrier) != 2 ) return false;
		$sql = $wpdb->prepare('SELECT * FROM '.$wpdb->prefix.'emc_services JOIN '.$wpdb->prefix.'emc_operators ON srv_ope_id = ope_id WHERE ope_code = %s AND srv_code = %s',array($carrier[0], $carrier[1]));
		$result = $wpdb->get_results($sql,OBJECT);
		self::handle_sql_error();
		return isset( $result[0] ) ? $result[0] : false;
	}

	/**
	 * Checks if a service is emc
	 * @string $carrier_code
	 * @return boolean
	 */
	static function is_emc_service($carrier_code) {
		if( !self::get_service_by_carrier_code($carrier_code)){
			return false;
		}
		else {
			return true;
		}
	}

	/**
	 * Get an operator from a service's id
	 * @param $id : id of the service associated to the operator
	 * @return the operator associated to the given service
	 */
	static function get_operator_by_service_id($id) {
		global $wpdb;
		$result = $wpdb->get_results($wpdb->prepare('SELECT o.* FROM '.$wpdb->prefix.'emc_operators o join '.$wpdb->prefix.'emc_services s on o.ope_id = s.srv_ope_id where srv_id = %d',array($id)),OBJECT);
		self::handle_sql_error();
		return $result;
	}
	/**
	 * Get an operator from his code
	 * @param $code : operator's code
	 * @return the operator associated to the given code
	 */
	static function get_operator_by_code($code) {
		global $wpdb;
		$result = $wpdb->get_results($wpdb->prepare('SELECT * FROM '.$wpdb->prefix.'emc_operators o WHERE o.ope_code = %s',array($code)),OBJECT);
		self::handle_sql_error();
		return $result;
	}

	/**
	* Gets all dimensions.
	* @return array List with dimensions.
	*/
	static function get_dimensions() {
		global $wpdb;
		$result = $wpdb->get_results('SELECT * FROM '.$wpdb->prefix.'emc_dimensions', OBJECT);
		self::handle_sql_error();
		return $result;
	}

    /**
	* Gets dimensions row by id.
	* @return object dimension row.
	*/
	static function get_dimension_by_id($dim_id) {
		global $wpdb;
		$result = $wpdb->get_row('SELECT * FROM '.$wpdb->prefix.'emc_dimensions where dim_id='.$dim_id, OBJECT);
		self::handle_sql_error();
		return $result;
	}

	/**
	 * Get the dimensions to use for a parcel's weight
	 * @param $weight : weight of the parcel
	 * @return dimensions to use with the parcel
	 */
	static function get_dim_from_weight($weight) {
		global $wpdb;
		$result = $wpdb->get_results($wpdb->prepare('SELECT dim_length AS length, dim_width AS width, dim_height AS height FROM '.$wpdb->prefix.'emc_dimensions WHERE dim_weight_from < %f AND dim_weight >= %f',array($weight,$weight)),OBJECT);
		self::handle_sql_error();
        if (isset($result[0])) {
            return $result[0];
        } else {
            return false;
        }
	}

	/**
	 * Get the weight from order
	 * @param $order_id
	 * @return weigth in kg
	 */
	static function get_weight_from_order($order) {

		$total_weight = 0;
		foreach( $order->get_items( 'line_item' ) as $item ) {
			$product_id = ( $item['variation_id'] != 0 ? $item['variation_id'] : $item['product_id'] );
			$product_weight=self::get_product_weight($product_id);
			$total_weight += (int)$item['qty'] * (float)$product_weight;
		}
		return (float)$total_weight;
	}

	/**
	 * Load all available carriers
	 */
	static function load_carrier_list_api($ajax = true, $override = true) {
		global $wpdb;
		$result = array();

		// check services already installed
		$services = self::get_services();
		$operators = self::get_operators();

		// get services from envoimoinscher server
		$lib = new Emc\CarriersList();
		$lib->setPlatformParams(EMC_PLATFORM, WC_VERSION, EMC_VERSION);
		$lib->setLocale('fr-fr'); // better to check in french to have all variables up to date
		$upload_dir = wp_upload_dir();
        $lib->setUploadDir($upload_dir['basedir']);
		$lib->getCarriersList(EMC_PLATFORM, EMC_VERSION);

		if ($lib->curl_error) {
			$return = __('An error occurred while updating your carrier list: ', 'envoimoinscher');
			foreach ($lib->resp_errors_list as $message) {
				$return .= '<br />'.$message['message'];
			}
			if ($ajax) {
				ob_end_clean();
				echo $return;
				die();
			}
			else {
				return $return;
			}
		}
		else if ($lib->resp_error) {
			$return = __('An error occurred while updating your carrier list: ', 'envoimoinscher');
			foreach ($lib->resp_errors_list as $message) {
				$return .= '<br />'.$message['message'];
			}
			if ($ajax) {
				ob_end_clean();
				echo $return;
				die();
			}
			else
				return $return;
		}

		$ope_no_change = array();
		$ope_to_delete = array();
		$ope_to_update = array();
		$ope_to_insert = array();
		$srv_no_change = array();
		$srv_to_delete = array();
		$srv_to_update = array();
		$srv_to_insert = array();

		$op_found = -1;
		$srv_found = -1;
        
        // empty emc_operators_authorized_contents table 
        $table  = $wpdb->prefix . 'emc_operators_authorized_contents';
        $wpdb->query("TRUNCATE TABLE $table");

		// sort transporters to add, to delete, to modify or to leave as is
		$last_ope_seen = ''; // in order to discard duplicates
		foreach ($lib->carriers as $carrier) {
			// we check if operator is different
			if ($last_ope_seen != $carrier['ope_code']) {
				$last_ope_seen = $carrier['ope_code'];
				// we compare the operator with the one in the list
				$op_found = -1;
				foreach ($operators as $id => $operator) {
					if ($operator->ope_code == $carrier['ope_code']) {
						$op_found = $id;
						if ($operator->ope_name != $carrier['ope_name'])
							$ope_to_update[count($ope_to_update)] = $carrier;
						else
							$ope_no_change[count($ope_no_change)] = $carrier;
						break;
					}
				}
				if ($op_found == -1) {
					$ope_to_insert[count($ope_to_insert)] = $carrier;
				} else {
					unset($operators[$op_found]);
                }
			}

			// we compare the service with the one in the list
			$srv_found = -1;
			foreach ($services as $id => $service) {
				if ($service->ope_code == $carrier['ope_code'] && $service->srv_code == $carrier['srv_code'])	{
					$srv_found = $id;
					// we check if service is different
					if ($service->srv_name != serialize($carrier['translations']['srv_name_fo']) ||
                        $service->srv_name_bo != serialize($carrier['translations']['srv_name_bo']) ||
                        $service->srv_description != serialize($carrier['translations']['description']) ||
                        $service->srv_pickup_point != $carrier['parcel_pickup_point'] ||
                        $service->srv_pickup_place != serialize($carrier['translations']['pickup_place']) ||
                        $service->srv_dropoff_point != $carrier['parcel_dropoff_point'] ||
                        $service->srv_dropoff_place != serialize($carrier['translations']['dropoff_place']) ||
                        $service->srv_family != $carrier['family'] ||
                        $service->srv_zone_fr != $carrier['zone_fr'] ||
                        $service->srv_zone_es != $carrier['zone_es'] ||
                        $service->srv_zone_eu != $carrier['zone_eu'] ||
                        $service->srv_zone_int != $carrier['zone_int'] ||
                        $service->srv_zone_restriction != serialize($carrier['translations']['zone_restriction']) ||
                        $service->srv_details != serialize($carrier['translations']['details']) ||
                        $service->srv_delivery_due_time != serialize($carrier['translations']['delivery_due_time']) ||
                        $service->srv_delivery_type != $carrier['delivery_type'])
						$srv_to_update[] = $carrier;
					else
						$srv_no_change[] = $carrier;
					break;
				}
			}
			if ($srv_found == -1) {
				$srv_to_insert[] = $carrier;
			} else {
				unset($services[$srv_found]);
            }

            // fill in emc_operators_authorized_contents table
            if (!isset($previous_operator) || $previous_operator != $carrier['ope_code']) {
                foreach($carrier['allowed_content'] as $allowed_content) {
                    
                    $wpdb->insert(
                        $wpdb->prefix.'emc_operators_authorized_contents',
                        array(
                            'ope_code' => $carrier['ope_code'],
                            'cat_id' => $allowed_content['id'],
                        ),
                        array(
                            '%s',
                            '%d',
                            '%s',
                        )
                    );
                }
            }
            $previous_operator = $carrier['ope_code'];
		}

		$srv_to_delete = $services;
		$ope_to_delete = $operators;


		// We update carrier information in database
		// Insert operators request
		if (count($ope_to_insert) > 0) {
			foreach ($ope_to_insert as $operator) {
                $wpdb->insert(
                    $wpdb->prefix.'emc_operators',
                    array(
                        'ope_name' => $operator['ope_name'],
                        'ope_code' => $operator['ope_code']
                    ),
                    array(
                        '%s',
                        '%s'
                    )
                );
			}
            if ($wpdb->last_error != '') {
				self::handle_sql_error();
				return $wpdb->last_error;
			}
		}

		// Requête update operateurs
		foreach ($ope_to_update as $operator) {
            $wpdb->update(
                $wpdb->prefix.'emc_operators',
                array(
                    'ope_name' => $operator['ope_name']
                ),
                array(
                    'ope_code' => $operator['ope_code']
                ),
                array(
                    '%s'
                ),
                array(
                    '%s'
                )
            );
            if ($wpdb->last_error != '') {
				self::handle_sql_error();
				return $wpdb->last_error;
			}
		}

		// Requête delete operateurs
		if (count($ope_to_delete) > 0) {
			foreach ($ope_to_delete as $operator)	{
                $wpdb->delete(
                    $wpdb->prefix.'emc_operators',
                    array(
                        'ope_id' => $operator->ope_id
                    ),
                    array(
                        '%d'
                    )
                );
			}
            if ($wpdb->last_error != '') {
				self::handle_sql_error();
				return $wpdb->last_error;
			}
		}

		$ope_ids = array();
		$new_operators = self::get_operators();
		foreach($new_operators as $value) {
			$ope_ids[$value->ope_code] = $value->ope_id;
		}

		// Insert services request
		if (count($srv_to_insert) > 0) {
			foreach ($srv_to_insert as $service) {
                $wpdb->insert(
                    $wpdb->prefix.'emc_services',
                    array(
                        'srv_ope_id' => $ope_ids[$service['ope_code']],
                        'srv_code' => $service['srv_code'],
                        'srv_name' => serialize($service['translations']['srv_name_fo']),
                        'srv_name_bo' => serialize($service['translations']['srv_name_bo']),
                        'srv_description' => serialize($service['translations']['description']),
                        'srv_pickup_point' => $service['parcel_pickup_point'],
                        'srv_pickup_place' => serialize($service['translations']['pickup_place']),
                        'srv_dropoff_point' => $service['parcel_dropoff_point'],
                        'srv_dropoff_place' => serialize($service['translations']['dropoff_place']),
                        'srv_family' => $service['family'],
                        'srv_zone_fr' => $service['zone_fr'],
                        'srv_zone_es' => $service['zone_es'],
                        'srv_zone_eu' => $service['zone_eu'],
                        'srv_zone_int' => $service['zone_int'],
                        'srv_zone_restriction' => serialize($service['translations']['zone_restriction']),
                        'srv_details' => serialize($service['translations']['details']),
                        'srv_delivery_due_time' => serialize($service['translations']['delivery_due_time']),
                        'srv_delivery_type' => $service['delivery_type'],
                    ),
                    array(
                        '%d',
                        '%s',
                        '%s',
                        '%s',
                        '%s',
                        '%d',
                        '%s',
                        '%d',
                        '%s',
                        '%d',
                        '%d',
                        '%d',
                        '%d',
                        '%d',
                        '%s',
                        '%s',
                        '%s',
                        '%d',
                    )
                );
			}
            if ($wpdb->last_error != '') {
				self::handle_sql_error();
				return $wpdb->last_error;
			}
		}

		// Requête update services
		foreach ($srv_to_update as $service) {
            $wpdb->update(
                $wpdb->prefix.'emc_services',
                array(
                    'srv_name' => serialize($service['translations']['srv_name_fo']),
                    'srv_name_bo' => serialize($service['translations']['srv_name_bo']),
                    'srv_description' => serialize($service['translations']['description']),
                    'srv_pickup_point' => $service['parcel_pickup_point'],
                    'srv_pickup_place' => serialize($service['translations']['pickup_place']),
                    'srv_dropoff_point' => $service['parcel_dropoff_point'],
                    'srv_dropoff_place' => serialize($service['translations']['dropoff_place']),
                    'srv_family' => $service['family'],
                    'srv_zone_fr' => $service['zone_fr'],
                    'srv_zone_es' => $service['zone_es'],
                    'srv_zone_eu' => $service['zone_eu'],
                    'srv_zone_int' => $service['zone_int'],
                    'srv_zone_restriction' => serialize($service['translations']['zone_restriction']),
                    'srv_details' => serialize($service['translations']['details']),
                    'srv_delivery_due_time' => serialize($service['translations']['delivery_due_time']),
                    'srv_delivery_type' => $service['delivery_type'],
                ),
                array(
                    'srv_code' => $service['srv_code'],
                    'srv_ope_id' => $ope_ids[$service['ope_code']],
                ),
                array(
                    '%s',
                    '%s',
                    '%s',
                    '%d',
                    '%s',
                    '%d',
                    '%s',
                    '%d',
                    '%d',
                    '%d',
                    '%d',
                    '%d',
                    '%s',
                    '%s',
                    '%s',
                    '%d',
                ),
                array(
                    '%s',
                    '%d',
                )
            );
            if ($wpdb->last_error != '') {
				self::handle_sql_error();
				return $wpdb->last_error;
			}
		}

		// Requête delete services
		if (count($srv_to_delete) > 0) {
			foreach ($srv_to_delete as $service) {
                $wpdb->delete(
                    $wpdb->prefix.'emc_services',
                    array(
                        'srv_id' => $service->srv_id
                    ),
                    array(
                        '%d'
                    )
                );
                if ($wpdb->last_error != '') {
                    self::handle_sql_error();
                    return $wpdb->last_error;
                }
			}
		}

		$result = array();
		$result['offers_added'] = array();
		$result['offers_updated'] = array();
		$result['offers_deleted'] = array();
		foreach ($srv_to_insert as $service) {
			$result['offers_added'][count($result['offers_added'])] = $service['srv_name_fo'];
		}
		foreach ($srv_to_update as $service) {
			$result['offers_updated'][count($result['offers_updated'])] = $service['srv_name_fo'];
		}
		foreach ($srv_to_delete as $service) {
			$result['offers_deleted'][count($result['offers_deleted'])] = $service->srv_name;
		}

		$date = new DateTime();
		update_option( 'EMC_LAST_CARRIER_UPDATE', $date->format('Y-m-d') );

        // if override is set, we update all FO labels and descriptions
        if($override) {
            foreach ($lib->carriers as $carrier) {
                $current_settings = get_option('woocommerce_'.$carrier['ope_code'].'_'.$carrier['srv_code'].'_settings');
                if($current_settings !== false) {
                    $current_settings['srv_name'] = $carrier['translations']['srv_name_fo'];
                    $current_settings['srv_description'] = $carrier['translations']['description'];
                    update_option('woocommerce_'.$carrier['ope_code'].'_'.$carrier['srv_code'].'_settings', $current_settings);
                }
            }
        }

		if ($ajax) {
			ob_end_clean();
			echo json_encode($result);
			die();
		} else {
			return true;
        }
    }
    
    /**
	 * Load all available carriers
	 */
	static function update_categories() {
		global $wpdb;
        
		// get services from envoimoinscher server
		$lib = new Emc\ContentCategory();
		$lib->setPlatformParams(EMC_PLATFORM, WC_VERSION, EMC_VERSION);
		$lib->setLocale('en-US'); // check in english for translation afterwards
		$upload_dir = wp_upload_dir();
        $lib->setUploadDir($upload_dir['basedir']);
		$lib->getCategories(); // load all content categories
        $lib->getContents(); // load all content types

		if ($lib->curl_error) {
			$return = __('An error occurred while updating shipment content types: ', 'envoimoinscher');
			foreach ($lib->resp_errors_list as $message) {
				$return .= '<br />'.$message['message'];
			}
            return $return;
		} else if ($lib->resp_error) {
			$return = __('An error occurred while updating shipment content types: ', 'envoimoinscher');
			foreach ($lib->resp_errors_list as $message) {
				$return .= '<br />'.$message['message'];
			}
			return $return;
		}
        
        
        // first let's empty table
        $table  = $wpdb->prefix . 'emc_categories';
        $wpdb->query("TRUNCATE TABLE $table");
        
        foreach ($lib->categories as $c => $category) {
            
            // let's insert category
            $wpdb->insert(
                $wpdb->prefix.'emc_categories',
                array(
                    'cat_id' => $category['code'],
                    'cat_group' => 0,
                    'cat_name' => $category['label'],
                    'cat_prohibited' => 0,
                ),
                array(
                    '%d',
                    '%d',
                    '%s',
                    '%d',
                )
            );
            if ($wpdb->last_error != '') {
				self::handle_sql_error();
				return $wpdb->last_error;
			}
            
            // let's insert corresponding content list
            foreach ($lib->contents[$category['code']] as $ch => $child) {
                $wpdb->insert(
                    $wpdb->prefix.'emc_categories',
                    array(
                        'cat_id' => $child['code'],
                        'cat_group' => $child['category'],
                        'cat_name' => $child['label'],
                        'cat_prohibited' => $child['prohibited'],
                    ),
                    array(
                        '%d',
                        '%d',
                        '%s',
                        '%d',
                    )
                );
                if ($wpdb->last_error != '') {
                    self::handle_sql_error();
                    return $wpdb->last_error;
                }
            }
        }
        return true;
    }
    
    /**
	 * Gets authorized operators for chosen content type
	 */
	static function get_operators_by_content($current_content) {
		global $wpdb;
        
        // if emc_operators_authorized_contents is empty, return false to activate all carriers
        $count = $wpdb->get_var("SELECT COUNT(*)
            FROM {$wpdb->prefix}emc_operators_authorized_contents WHERE cat_id IS NOT NULL");

        if($count == 0) {
            return false;
        }
        
		$results = $wpdb->get_results( 
            $wpdb->prepare(
                "SELECT ope_code 
                FROM {$wpdb->prefix}emc_operators_authorized_contents
                WHERE cat_id = %d",
                $current_content
            ),
            ARRAY_A
        );
        $return = array();
        foreach($results as $row) {
            array_push($return, $row['ope_code']);
        }
        return $return;
    }
    
    /**
	 * Gets current content label
	 */
	static function get_current_content() {
        global $wpdb;
        $emc_nature = get_option('EMC_NATURE');

        if ($emc_nature == false) {
            return false;
        }
        
        $results = $wpdb->get_row(
            $wpdb->prepare(
                "SELECT cat_id, cat_name, cat_prohibited 
                FROM {$wpdb->prefix}emc_categories
                WHERE cat_id = %d",
                $emc_nature
            ),
            ARRAY_A
        );
        if (isset($results['cat_name'])) {
            return array(
                'id' => $results['cat_id'],
                'label' => $results['cat_name'],
                'prohibited' => $results['cat_prohibited']
            );
        } else {
            return false;
        }
    }
    
    /**
	 * Updates carrier list according to forbidden categories
	 */
	static function remove_carriers_forbidden_categories() {
        $current_content = envoimoinscher_model::get_current_content();
        // check if content is set
        if (isset($current_content['id'])) {
            // check if category is forbidden
            if ($current_content['prohibited'] == true) {
                $emc_services = array ( 1 => array(), 2 => array() );
            } else {
                // check authorized operators for the category
                $authorized_operators = envoimoinscher_model::get_operators_by_content($current_content['id']);
                $emc_services = get_option( 'EMC_SERVICES' , array ( 1 => array(), 2 => array() ) );

                // check if authorized categories have been loaded
                if ($authorized_operators !== false) {
                    // deactivate carriers not available for selected category
                    foreach ( $emc_services as $f => $family ) {
                        foreach ( $family as $c => $carrier_code ) {
                            $ope_code = substr($carrier_code, 0, 4);
                            if (!in_array($ope_code, $authorized_operators)) {
                                unset($emc_services[$f][$c]);
                            }
                        }
                    }
                }
            }
            update_option( 'EMC_SERVICES', $emc_services );
        }
    }
    
	/**
	 * Get all parcel points for an address and a carrier
	 */
	static function get_pickup_points($carrier_code, $city, $postal_code, $country) {

		$env = get_option('EMC_ENV');
		$parcels_code = 'emc_parcels_'.md5($carrier_code.$city.$postal_code.$country.$env);
		$parcels = get_transient($parcels_code);

		if ($parcels) {
			return $parcels;
		}

        // relay point info is store on quotation, but in any case, the code below can get the information as well
		$lib = new Emc\ListPoints();

		$params = array(
			'collecte'=> 'dest',
			'pays' => $country,
			'cp' => $postal_code,
			'ville' => $city
		);

        $language = get_locale();
        $lib->setLocale(str_replace('_', '-', $language));
		$upload_dir = wp_upload_dir();
        $lib->setUploadDir($upload_dir['basedir']);
		$lib->setPlatformParams(EMC_PLATFORM, WC_VERSION, EMC_VERSION);

		$lib->getListPoints(array($carrier_code), $params);

		if($lib->curl_error || $lib->resp_error || !isset($lib->list_points[0]) || !isset($lib->list_points[0]['points'])) {
			self::log('Failed to load parcel points for "'.$carrier_code.'" for the address {city="'.$city.'",country="'.$country.'",postal_code="'.$postal_code.'"}');
			if ($lib->resp_error) {
				self::log('The library return the error : '.print_r($lib->resp_errors_list,true));
			} else if($lib->curl_error) {
				self::log('Curl return the error : '.$lib->curl_error_text);
			}
			return array();
		}
		else {
			set_transient($parcels_code, $lib->list_points[0]['points'], 3600);
			return $lib->list_points[0]['points'];
		}
	}

	/**
	 * Get offers from cache.
	 * @param array $params API request params.
	 * @returns md5 of relevant order params
	 */
	static function get_pricing_code($from, $to, $parcels, $params, $curlMulti = null) {

		$code  = isset($from['zipcode']) ? $from['zipcode'] : '';
		$code .= isset($from['city']) ? $from['city'] : '';
		$code .= isset($from['country']) ? $from['country'] : '';
		$code .= isset($from['type']) ? $from['type'] : '';
		$code .= isset($to['zipcode']) ? $to['zipcode'] : '';
		$code .= isset($to['city']) ? $to['city'] : '';
		$code .= isset($to['country']) ? $to['country'] : '';
		$code .= isset($to['type']) ? $to['type'] : '';
		$code .= serialize($parcels);
		$code .= serialize($params);
		$code .= $curlMulti ? '1' : '0';

		return 'emc_quote_'.md5($code);
	}

    /**
	 * Sends multiple quotation if necessary and returns the quotation for the information given
	 *
	 * @param $to : delivery address
	 * @param $package : cart info
	 * @return mixed : if the quotation worked, return an array with all the available offers, if there is an error, return a string with the error message
	 */
	static function split_package_get_quotation($to, $package, $cache = true) {

        $multiparcels_stored = array();

        // get an array of products indexed by shipping classes
        $package_by_shipping_classes = array();
        foreach ($package['contents'] as $cart_item) {
            $shipping_class = $cart_item['data']->get_shipping_class();
            if ($shipping_class == '') {
                $shipping_class = 'emc-no-shipping-class'; // let's choose a name unlikely to be used by a user for the "no shipping class" possibility
            }
            if (!isset($package_by_shipping_classes[$shipping_class])) {
                $package_by_shipping_classes[$shipping_class] = array();
            }
            array_push($package_by_shipping_classes[$shipping_class], $cart_item);
        }

        // get available addresses with their shipping classes ('emc-no-shipping-class' for default address is already included)
        $available_addresses = self::get_classes_by_addresses();

        // find all classes not in available_addresses and add them to default address subset
        $all_classes = array_keys($package_by_shipping_classes);
        $remaining_classes = $all_classes;
        foreach ($available_addresses as $address_id => $shipping_classes) {
            // get default address id while we at it
            if ($shipping_classes == array('emc-no-shipping-class')) {
                $default_address_id = $address_id;
            }

            foreach ($shipping_classes as $shipping_class) {
                if (($key = array_search($shipping_class, $remaining_classes)) !== false) {
                    unset($remaining_classes[$key]);
                }
            }
        }

        // define default address with emc-no-shipping-class and remaining shipping classes
        $available_addresses[$default_address_id] = array_merge(array('emc-no-shipping-class'), $remaining_classes);

        $used_addresses = self::array_set_cover($available_addresses, $all_classes);

        self::log('Package sent from '.count($used_addresses).' address(es)');

        // now we get package by address
        $package_by_address = array();
        $addressbook = self::get_addresses("all", true);
        foreach ($used_addresses as $address_id) {
            // get address from addressbook
            foreach($addressbook as $item) {
                if ($item['id'] == $address_id) {
                    $address = $item;
                }
            }

            // case of default address
            if ($address['type'] == 'default') {
                foreach($package_by_shipping_classes as $class => $cart_items) {
                    // $available_addresses[$default_address_id] still has classes for default address
                    if (in_array($class, $available_addresses[$default_address_id])) {
                        if (!isset($package_by_address[$default_address_id])) {
                            $package_by_address[$default_address_id] = array();
                        }
                        $package_by_address[$default_address_id] = array_merge_recursive($package_by_address[$default_address_id], $cart_items);
                    }
                }
            // other cases
            } else {
                $shipping_classes_for_address = explode(',', $address['shipping_classes']);
                foreach($package_by_shipping_classes as $class => $cart_items) {
                    if (in_array($class, $shipping_classes_for_address)) {
                        if (!isset($package_by_address[$address_id])) {
                            $package_by_address[$address_id] = array();
                        }
                        $package_by_address[$address_id] = array_merge_recursive($package_by_address[$address_id], $cart_items);
                    }
                }
            }
            // store product distribution by address for later use
            $multiparcels_stored[$address_id]["products"] = array();
            foreach ($package_by_address[$address_id] as $cart_item) {
                array_push($multiparcels_stored[$address_id]["products"], array(
                    'id' => $cart_item["variation_id"] == 0 ? $cart_item["product_id"]:$cart_item["variation_id"],
                    'qty' => $cart_item["quantity"]
                ));
            }
        }

        // add all not represented products with 0 quantity in multiparcel variables
        foreach ($package['contents'] as $cart_item) {
            $check_id = $cart_item["variation_id"] == 0 ? $cart_item["product_id"]:$cart_item["variation_id"];
            foreach ($multiparcels_stored as $address_id => $multiparcel_info) {
                $not_available_for_address = true;
                foreach ($multiparcel_info['products'] as $product) {
                    if ($product['id'] == $check_id) {
                        $not_available_for_address = false;
                    }
                }
                if ($not_available_for_address) {
                    array_push($multiparcels_stored[$address_id]["products"], array(
                        'id' => $check_id,
                        'qty' => 0
                    ));
                }
            }
        }

        // prepare get_quotation_multiple function params
        $additional_params = array();
        foreach($package_by_address as $address_id => $cart_items) {
            $weight = self::get_weight_from_product_set($cart_items);

            // is multiparcel option activated?
            if (get_option('EMC_PARCEL_SPLIT') == 1) {
                // get maximum parcel weight
                $dim_id = get_option('EMC_PARCEL_MAX_DIM');
                $dim = envoimoinscher_model::get_dimension_by_id($dim_id);

                // compute the best items configuration using the maximum parcel weight
                $best_fits = self::parcel_weight_best_fit($cart_items, $dim->dim_weight);
            }
            else
            {
                $best_fits = array($cart_items);
            }

            $multiparcels_stored[$address_id]["parcels"] = array();

            // compute all parcels dimensions using the items configuration
            for($i = 0, $max = count($best_fits); $i < $max ; $i++)
            {
                $fit_weight = 0;

                foreach($best_fits[$i] as $fit_item)
                {
                    $product_id = $fit_item['variation_id'] != 0 ? $fit_item['variation_id'] : $fit_item['product_id'];
                    $fit_weight += (float)self::get_product_weight($product_id) * $fit_item['quantity'];
                }

                $dims = self::get_dim_from_weight($fit_weight);

                if ($dims === false) {
                    self::log('Unable to return dimensions for weight: '.$fit_weight.'kg. Please check weight options table.');
                    return;
                }

                $multiparcels_stored[$address_id]['parcels'][$i+1] = array(
                    'weight' => $fit_weight,
                    'length' => $dims->length,
                    'width' => $dims->width,
                    'height' => $dims->height
                );
            }
            self::log('Package sent from address '.$address_id.' was split in '.count($multiparcels_stored[$address_id]['parcels']).' parcel(s)');

            // each multiparcel gets a unique key prefixed by address id and all order post meta will get indexed by this key
            $multiparcels_stored[$address_id]["suborder_key"] = uniqid($address_id.'_');

            // additional parameters
            $additional_params[$multiparcels_stored[$address_id]["suborder_key"]] = array(
                'collecte' => date( 'Y-m-d', self::set_collect_date($address_id) ),
                'delai' => 'aucun',
                'code_contenu' => get_option('EMC_NATURE'),
                'colis.valeur' => self::get_price_from_product_set($cart_items)
            );
        }

        $multiparcels = array();
        foreach ($multiparcels_stored as $address_id => $multiparcel_info) {
            $multiparcel = array();
            $multiparcel["address_id"] = $address_id;
            $multiparcel["products"] = $multiparcel_info["products"];
            $multiparcel["parcels"] = $multiparcel_info["parcels"];
            $multiparcel["suborder_key"] = $multiparcel_info["suborder_key"];
            array_push($multiparcels, $multiparcel);
        }

        // store parcel distribution in session for later use if session is not null (simulator check)
        if (WC()->session) {
            WC()->session->set("multiparcels", $multiparcels);
        }

        return self::get_quotation_multiple($to, $multiparcels, $additional_params, $cache);
    }

    /**
	 * Sends multiple quotation if necessary and returns the quotation for the information given
	 *
	 * @param array $to : delivery address
	 * @param array $multiparcels : the multiparcel info
     * @param array $additional_params : additional parameters indexed by address_id
     * @param boolean $cache
	 * @return array : array with all the available offers indexed by address_id, or string with error message
	 */
	static function get_quotation_multiple($to, $multiparcels, $additional_params, $cache = true) {

        // now we can quote each package for each used address
        self::log('------------------------------------------------------------------------------------------------');
		self::log('Quotation - to '.$to['city'].' '.$to['zipcode']);

        // Create quotation object
		$lib = new Emc\Quotation();
        $language = get_locale();
		$lib->setLocale(str_replace('_', '-', $language));
		$upload_dir = wp_upload_dir();
        $lib->setUploadDir($upload_dir['basedir']);
		$lib->setPlatformParams(EMC_PLATFORM, WC_VERSION, EMC_VERSION);
        $offers = array();
        $curl_counter = array(); // we use this to associate request and curl response
        $addressbook = self::get_addresses("all", null);
        $services = self::get_emc_active_shipping_methods();
        $multirequest = array();

        if (count($services) == 0){
            self::log('Quotation - No service activated');
            return $offers;
        }

        foreach($multiparcels as $multiparcel_info) {

            $suborder_key = $multiparcel_info['suborder_key'];

            $address_id = $multiparcel_info["address_id"];
            // sender information
            foreach($addressbook as $item) {
                if ($item['id'] == $address_id) {
                    $address = $item;
                }
            }
            $from = array(
                'country' => get_option('EMC_COUNTRY', 'FR'),
                'zipcode' => $address['zipcode'],
                'city' => stripslashes($address['city']),
                'type' => 'company',
                'societe' => stripslashes($address['company']),
                'address' => stripslashes($address['address']),
                'title' => $address['gender'],
                'firstname' => stripslashes($address['first_name']),
                'lastname' => stripslashes($address['last_name']),
                'email' => $address['email'],
                'phone' => self::normalize_telephone($address['phone']),
                'infos' => stripslashes($address['add_info'])
            );

            $parcels = array();
            foreach($multiparcel_info['parcels'] as $i => $parcel) {
                $parcels[$i] = array(
                    'poids'    => $parcel["weight"],
                    'longueur' => $parcel["length"],
                    'largeur'  => $parcel["width"],
                    'hauteur'  => $parcel["height"]
                );
            }

            $params = $additional_params[$suborder_key];

            // manage cache for each quotation individually
            if ($cache) {
                $pricing_code[$suborder_key] = self::get_pricing_code($from, $to, $parcels, $params);
                $offers[$suborder_key] = get_transient($pricing_code[$suborder_key]);
            } else {
                $offers[$suborder_key] = false;
            }

            if ($offers[$suborder_key] !== false) {
                self::log('Quotation - cache used for suborder '.$suborder_key);
            } else {
                $curl_counter[] = $suborder_key;
                $offers[$suborder_key] = array();

                $params['offers'] = array();
                foreach ($services as $carrier_code) {
                    list($operator, $service) = explode('_', $carrier_code);
                    array_push($params['offers'], $operator.$service);
                }

                array_push(
                    $multirequest,
                    array(
                        'from' => $from,
                        'to' => $to,
                        'parcels' => array(
                            'type' => 'colis',
                            'dimensions' => $parcels
                        ),
                        'additional_params' => $params
                    )
                );
            }
        }

        if (!empty($multirequest)) {
            $lib->getQuotationMulti($multirequest);
        }

        if(!$lib->curl_error) {
            foreach($curl_counter as $counter => $suborder_key) {
                if (isset($lib->resp_errors_list[$counter])) {
                    // case resp error
                    $offers[$suborder_key] = '';
                    foreach($lib->resp_errors_list[$counter] as $e => $error) {
                        if ($e == 0) {
                            $offers[$suborder_key] .= 'invalid request : ';
                        } else {
                            $offers[$suborder_key] .= '<br/>';
                        }
                        $offers[$suborder_key] .= $error['message'].' ('.$error['code'].')';
                    }
                    self::log('Quotation - suborder '.$suborder_key.' '.$offers[$suborder_key]);
                } else {
                    // offers data is ready to be stored
                    $activated_services = get_option('EMC_SERVICES');
                    foreach($lib->offers[$counter] as $offer) {
                        $carrier_code = $offer['operator']['code'].'_'.$offer['service']['code'];

                        // if offer is already set, we check price and keep the offer with minimum price
                        if (isset($offers[$suborder_key][$carrier_code])) {
                            if ($offers[$suborder_key][$carrier_code]['price']['tax-exclusive'] < $offer['price']['tax-exclusive']) {
                                continue;
                            }
                        }

                        $offers[$suborder_key][$carrier_code] = $offer;

                        // store maximum delivery date for display in FO
                        if( isset($offer['delivery']['date']) ) {
                            $last_delivery_date = get_option('_delivery_date_' . $carrier_code, date('Y-m-d'));
                            if (strtotime($last_delivery_date) < strtotime($offer['delivery']['date'])) {
                                update_option( '_delivery_date_' . $carrier_code, $offer['delivery']['date'] );
                            }
                        }

                        // store relay point information (should be the same for every order)
                        if (isset($offer['mandatory']['retrait.pointrelais'])) {
                            $parcels_code = 'emc_parcels_'.md5($carrier_code.$to['city'].$to['zipcode'].$to['country'].get_option('EMC_ENV'));
                            $points = $offer['mandatory']['retrait.pointrelais']['array'];
                            set_transient($parcels_code, $points, 3600);
                        }
                    }
                    self::log('Quotation - suborder '.$suborder_key.' found '.count($offers[$suborder_key]). ' offer(s)');
                    if ($cache) {
                        set_transient($pricing_code[$suborder_key], $offers[$suborder_key], 4 * WEEK_IN_SECONDS );
                    }
                }
            }
            return $offers;
		} else {
            // case curl error
			$message =  'Invalid request : ' . $lib->curl_error_text;
			self::log('Quotation - result : '.$message);
			return $message;
		}
    }

    /**
     * Sort an array of array by an index
     *
     * @param array $array : array to sort
     * @param string $on   : array's index to use
     * @param int $order   : sort order (SORT_ASC / SORC_DESC)
     * @return sorted array
     */
    static function array_sort($array, $on, $order=SORT_ASC)
    {
        $new_array = array();
        $sortable_array = array();

        if (count($array) > 0) {

            // build a temporary array with the same indexes as the original but with the $on index as value
            foreach ($array as $k => $v) {
                if (is_array($v)) {
                    foreach ($v as $k2 => $v2) {
                        if ($k2 == $on) {
                            $sortable_array[$k] = $v2;
                        }
                    }
                } else {
                    $sortable_array[$k] = $v;
                }
            }

            // sort the array by it's values
            switch ($order) {
                case SORT_ASC:
                    asort($sortable_array);
                break;
                case SORT_DESC:
                    arsort($sortable_array);
                break;
            }

            // build back the original array with the temporary array order
            foreach ($sortable_array as $k => $v) {
                $new_array[$k] = $array[$k];
            }
        }

        return $new_array;
    }

    /**
     * Group items into parcels of maximum $maximum_weigt kg using the best_fit algorithm (https://fr.wikipedia.org/wiki/Probl%C3%A8me_de_bin_packing)
     * If an item has a weight superior to the maximum weight, it will be added alone in a parcel
     *
     * @param array $items : array of items to group
     * @param $maximum_weight
     * @return array of array of items, each array represent a parcel
     */
	static function parcel_weight_best_fit($items, $maximum_weight)
	{
		$parcels = array();

        // add weight to items
        foreach($items as $k => $item)
        {
            $product_id = ( $item['variation_id'] != 0 ? $item['variation_id'] : $item['product_id'] );
            $items[$k]['weight'] = self::get_product_weight($product_id);
        }

        // sort array by weight
        $items = self::array_sort($items,'weight',SORT_DESC);

        // loop over all items (one per one)
		foreach($items as $item)
		{
            for ($i = 0 ; $i < $item['quantity'] ; $i++)
            {
                // try to fit the item into each parcel
        		$best_fit = -1;
        		$best_fit_value = -1;
    			foreach($parcels as $pos => $parcel)
    			{
                    $parcel_weight = 0.0;

                    // compute parcel weight
                    foreach ($parcel as $parcel_item)
                    {
                        $parcel_weight += $parcel_item['weight'] * $parcel_item['quantity'];
                    }
                    $sum = $item['weight'] + $parcel_weight;

                    // does the item fit in ?
                    if ($sum <= $maximum_weight)
                    {
                        $new_value = $maximum_weight - $sum;
                        if ($best_fit_value == -1 || $new_value < $best_fit_value)
                        {
                            $best_fit_value = $new_value;
                            $best_fit = $pos;
                        }
                    }
    			}

                // we found a best place, let's add the item there
                if ($best_fit != -1)
                {
                    $added = false;
                    // check if there is already an item with the same id
                    foreach ($parcels[$best_fit] as $k => $parcel_item)
                    {
                        if ($parcel_item['product_id'] == $item['product_id'] && $parcel_item['variation_id'] == $item['variation_id'])
                        {
                            $parcels[$best_fit][$k]['quantity']++;
                            $added = true;
                            break;
                        }
                    }

                    if (!$added)
                    {
                        $new_item = $item;
                        $new_item['quantity'] = 1;
                        $parcels[$best_fit][] = $new_item;
                    }
                }

                // no best place found, let's add another parcel
                else
                {
                    $new_item = $item;
                    $new_item['quantity'] = 1;
                    $parcels[] = array($new_item);
                }
            }
		}

        return $parcels;
	}

	/**
	 * Return article weight
	 * @return if exist, return article weight
	 */
	static function get_product_weight($product_id) {
		if( isset( $product_id ) && !empty( $product_id ) ) {
			if (function_exists('wc_get_product')) {
                $product = wc_get_product( $product_id );
            } else {
                // fix for WC < 2.5
                $product = WC()->product_factory->get_product( $product_id );
            }

			if( $product->get_weight() && $product->get_weight() != '' ) {
				$weight = wc_format_decimal( wc_get_weight($product->get_weight(),'kg'), 2 );
			} else{
				$weight = get_option('EMC_AVERAGE_WEIGHT');
			}
			return $weight;
		}
	}

	/**
	 * Return the sum of all articles weight
	 * @return cart's weight
	 */
	static function get_cart_weight() {
		$weight = 0;
		foreach ( WC()->cart->get_cart() as $item_id => $item ) {
			$_product = $item['data'];
			if($item['data']->needs_shipping()){
                $product_id = ( $item['variation_id'] != 0 ? $item['variation_id'] : $item['product_id'] );
				$weight += self::get_product_weight($product_id)*$item['quantity'];
			}
		}
		return $weight;
	}

    /**
	 * Return the weight of a product set
	 * @return weight
	 */
	static function get_weight_from_product_set($product_set) {
		$weight = 0;
		foreach ($product_set as $item) {
			if($item['data']->needs_shipping()) {
                $product_id = ( $item['variation_id'] != 0 ? $item['variation_id'] : $item['product_id'] );
				$weight += self::get_product_weight($product_id)*$item['quantity'];
			}
		}
		return $weight;
	}

    /**
	 * Return the price of a product set
	 * @return price
	 */
	static function get_price_from_product_set($product_set) {
		$price = 0;
		foreach ($product_set as $item) {
            
            if (method_exists($item['data'], 'get_price')) {
                $price += $item['data']->get_price()*$item['quantity'];
            } else {    
                $price += $item['data']->price*$item['quantity'];
            }
		}
		return $price;
	}

	/**
	 * @return recipient's address for the current cart
	 */
	static function get_recipient_address() {
		$address = WC()->customer->get_shipping_address();
		$postcode = WC()->customer->get_shipping_postcode();
		$city = WC()->customer->get_shipping_city();
		$country = WC()->customer->get_shipping_country();

		return array(
			'firstname' => wp_get_current_user()->user_firstname,
			'lastname' => wp_get_current_user()->user_lastname,
			'email' => wp_get_current_user()->user_email,
			'address' => !empty($address) ? $address : '15 rue Marsollier',
			'zipcode' => !empty($postcode) ? $postcode : '75002',
			'city' => !empty($city) ? $city : 'Paris',
			'country' => !empty($country) ? $country : 'FR',
			'type' => 'individual'
		);
	}

	/**
	 * Processes orders.
	 * @param array $order.
	 * @param array $suborder_key. Give key in order to send only this suborder.
	 * @param boolean $exclude_already_sent. Whether to exclude or not already sent suborders.
	 * @returns boolean
	 */
	static function make_order( $order, $suborder_key = null, $exclude_already_sent = true ) {

        if ($suborder_key == null) {
            self::initialize_default_params( $order );
        } else {
            self::initialize_default_params( $order, $suborder_key );
        }
        $order_id = self::envoimoinscher_get_order_id($order);

        // Get values
        $items = $order->get_items();
        $multiparcels = get_post_meta( $order_id, '_multiparcels', true );
        $addressbook = self::get_addresses("all", null);
        $_insurance = get_post_meta( $order_id, '_insurance', true );
		$_assurance_emballage = get_post_meta( $order_id, '_assurance_emballage', true );
        $_assurance_materiau = get_post_meta( $order_id, '_assurance_materiau', true );
        $_assurance_protection = get_post_meta( $order_id, '_assurance_protection', true );
        $_assurance_fermeture = get_post_meta( $order_id, '_assurance_fermeture', true );
        $_proforma_items = get_post_meta( $order_id, '_proforma_items', true );
        $_proforma_reason = get_post_meta( $order_id, '_proforma_reason', true );
        $_emc_ref = get_post_meta( $order_id, '_emc_ref', true );
        if ($_emc_ref == '') {
            $_emc_ref = array();
        }
        $return = array();

        foreach ($multiparcels as $multiparcel_info) {
            $cotCl = new Emc\Quotation();
            $language = get_locale();
            $cotCl->setLocale(str_replace('_', '-', $language));
            $upload_dir = wp_upload_dir();
            $cotCl->setUploadDir($upload_dir['basedir']);
            $cotCl->setPlatformParams(EMC_PLATFORM, WC_VERSION, EMC_VERSION);

            self::log('-------------------------------------------------------------------------------------------------------');

            // send only suborder if $suborder_key is not null
            if ($suborder_key != null & $suborder_key != $multiparcel_info['suborder_key']) {
                continue;
            } elseif($exclude_already_sent == true && isset($_emc_ref[$multiparcel_info['suborder_key']])) {
                $suborder = $multiparcel_info['suborder_key'];
                $return[$suborder] = array("already" => $_emc_ref[$suborder]);
                self::log('Make order - order '.$order_id.' suborder '.$suborder.' was already sent with reference '.$_emc_ref[$suborder].' and will not be sent again');
                continue;
            } else {
                $suborder = $multiparcel_info['suborder_key'];
                self::log('Make order - order '.$order_id.' processing suborder '.$suborder);
            }

            $quot_info = self::get_order_params( $order, $suborder );
            // if no carrier selected, return error
            if (!isset($quot_info['params']['operator']) || !isset($quot_info['params']['service'])) {
                $message = __('no emc carrier selected', 'envoimoinscher');
                $return[$suborder] = array("error" => $message);
                self::log('Make order - order '.$order_id.' suborder '.$suborder.' has no emc carrier selected');
                continue;
            }

            if ( $_insurance[$suborder] ) {
                $quot_info['params']['assurance.selection'] = 1;
                $quot_info['params']['assurance.emballage'] = $_assurance_emballage[$suborder];
                $quot_info['params']['assurance.materiau'] = $_assurance_materiau[$suborder];
                $quot_info['params']['assurance.protection'] = $_assurance_protection[$suborder];
                $quot_info['params']['assurance.fermeture'] = $_assurance_fermeture[$suborder];
            }
            else{
                $quot_info['params']['assurance.selection'] = 0;
            }

            // get shipping address
            $address_id = $multiparcel_info["address_id"];
            foreach($addressbook as $item) {
                if ($item['id'] == $address_id) {
                    $address = $item;
                }
            }

            // if address is no longer active, send an error
            if ($address['active'] == 0) {
                $message = __('address is no longer active', 'envoimoinscher');
                $return[$suborder] = array("error" => $message);
                self::log('Make order - order '.$order_id.' suborder '.$suborder.' has no emc carrier selected');
                continue;
            }

            // sender information
            $from = array(
                'country' => get_option('EMC_COUNTRY', 'FR'),
                'zipcode' => $address['zipcode'],
                'city' => stripslashes($address['city']),
                'type' => 'company',
                'societe' => stripslashes($address['company']),
                'address' => stripslashes($address['address']),
                'title' => $address['gender'],
                'firstname' => stripslashes($address['first_name']),
                'lastname' => stripslashes($address['last_name']),
                'email' => $address['email'],
                'phone' => self::normalize_telephone($address['phone']),
                'infos' => stripslashes($address['add_info'])
            );

            $parcels = array();
            foreach($multiparcel_info['parcels'] as $k => $parcel_info) {
                $parcels[$k] = array(
                    'poids' => $parcel_info["weight"],
                    'longueur' => $parcel_info["length"],
                    'largeur' => $parcel_info["width"],
                    'hauteur' => $parcel_info["height"]
                );
            }

            // add proforma
            $proforma = array();
            $i = 1;
            foreach ( $items as $item ) {

                $check_id = $item["variation_id"] == 0 ? $item["product_id"] : $item["variation_id"];

                $weight = envoimoinscher_model::get_product_weight($check_id);
                if( $weight == 0) {
                    self::log('make order: weight for proforma is null!');
                    $weight = 0.02;
                }

                // get quantity
                $qty = 0;
                foreach ($multiparcel_info['products'] as $product) {
                    if ($product['id'] == $check_id) {
                        $qty = $product['qty'];
                    }
                }
                if ($qty != 0) {
                    $proforma[$i] = array(
                        "description_en" => $_proforma_items[$suborder][$check_id]['post_title_en'],
                        "description_fr" => $_proforma_items[$suborder][$check_id]['post_title_fr'],
                        "nombre" => $qty,
                        "valeur" => (float) (esc_html( $item['line_subtotal'] ) / esc_html( $item['qty'] )),
                        "origine" => $_proforma_items[$suborder][$check_id]['origin'],
                        "poids" => (float)$weight - 0.01
                    );
                    $i++;
                }
            }
            $quot_info['params']['raison'] = $_proforma_reason[$suborder];
            $cotCl->setProforma($proforma);

            $orderPassed = $cotCl->makeOrder($from, $quot_info['to'], array('type' => 'colis', 'dimensions' => $parcels), $quot_info['params']);

            if (!$cotCl->curl_error) {
                if(!$cotCl->resp_error) {
                    // success
                    $_emc_ref[$suborder] = $cotCl->order['ref'];
                    $order->add_order_note( sprintf(__( '[suborder %s]', 'envoimoinscher' ), $suborder ).' '.sprintf(__( 'Suborder passed with the reference %s.', 'envoimoinscher' ), $cotCl->order['ref'] ) );
                    self::log('Make order - '.print_r($cotCl->order,true));
                    $return[$suborder] = array("success" => $_emc_ref[$suborder]);
                } else {
                    $message = __('Invalid request: ');
                    foreach($cotCl->resp_errors_list as $key => $error_message){
                        if ($key > 0) {
                            $message .= ', ';
                        }
                        $message .= $error_message['message'].' ('.$error_message['code'].')';
                    }
                    self::log('Make order - '.$message);
                    $return[$suborder] = array("error" => $message);
                }
            } else {
                // cURL error
                $message =  __('Invalid request: ') . $cotCl->curl_error_text;
                self::log('Make order - '.$message);
                $return[$suborder] = array("error" => $message);
            }
        }
        
        update_post_meta( $order_id, '_emc_ref', $_emc_ref );
        // reload order otherwise update_status will save order with current data and erase _emc_ref
        $order = self::envoimoinscher_get_order($order_id);

        // check if all suborders have been sent
        $all_suborders_processed = true;
        foreach ($multiparcels as $multiparcel_info) {
            if (!isset($_emc_ref[$multiparcel_info['suborder_key']]) || $_emc_ref[$multiparcel_info['suborder_key']] == '') {
                $all_suborders_processed = false;
            }
        }

        if ($all_suborders_processed) {
            $order->update_status( 'wc-awaiting-shipment' );
        }

        return $return;
	}

	/**
	 * Check rates for order.
	 * @param array $offer.
	 * @returns rate
	 */
	static function check_rates_from_order ( $order ) {

		self::initialize_default_params( $order );
        $order_id = self::envoimoinscher_get_order_id($order);

        $multiparcels = get_post_meta( $order_id, '_multiparcels', true );

        $additional_params = array();
        foreach ($multiparcels as $multiparcel_info) {
            $suborder_key = $multiparcel_info['suborder_key'];
            $result = self::get_order_params( $order, $suborder_key );
            $to = $result['to']; // the same in both cases
            $additional_params[$suborder_key] = $result['params'];
            // insurance needs to be off in order to get select options
            $additional_params[$suborder_key]['assurance.selection'] = 0;
		}

		// if collect date is set in the past, set it to today, otherwise, leave it in the future
		/*$planned_date = strtotime($quot_info['params']['collecte']);
		if ( $planned_date < time() ) {
			$quot_info['params']['collecte'] = date('Y-m-d');
		}*/

		self::log('Quot info - '.print_r($additional_params,true));
		$multiparcels = get_post_meta($order_id, '_multiparcels', true);

		return self::get_quotation_multiple($to, $multiparcels, $additional_params, true);
	}

	/**
	 * Initialize default params for order.
	 * @param array $order.
	 * @param array $new_suborder_key. To initialize default only for a new suborder.
	 * @param bool $reset. To reset values for whole order.
	 * @returns array API params
	 */
	static function initialize_default_params( $order, $new_suborder_key = null, $reset = false ) {

        global $wpdb;
        $order_id = self::envoimoinscher_get_order_id($order);
        $multiparcels = get_post_meta( $order_id, '_multiparcels', true );

        if ( $multiparcels == '' || $reset) {
            $multiparcels = array();
            $default_address = self::get_addresses("default", true);
            $default_address = current($default_address);
            $default_address_id = $default_address['id'];
            $weight = self::get_weight_from_order($order);

			if( $weight != 0) {
				$dims = self::get_dim_from_weight($weight);
                $multiparcels[0] = array(
                    "suborder_key" => uniqid($default_address_id.'_'),
                    "address_id" => $default_address_id,
                    "parcels" => array(
                        1 => array(
                            'weight' => $weight,
                            'length' => $dims === false ? false:$dims->length,
                            'width' => $dims === false ? false:$dims->width,
                            'height' => $dims === false ? false:$dims->height,
                        )
                    ),
                    "products" => array()
                );
                foreach ($order->get_items() as $item) {
                    $check_id = $item["variation_id"] == 0 ? $item["product_id"] : $item["variation_id"];
                    array_push($multiparcels[0]["products"], array(
                        'id' => $check_id,
                        'qty' => $item['qty']
                    ));
                }
				update_post_meta( $order_id, '_multiparcels', $multiparcels );
			} else {
				self::log('initialize_default_params: weight is null!');
			}
        }

		if ( get_post_meta( $order_id, '_desc_content', true ) == '' || $reset) {
            $description = array();
            // if default content description is defined in settings
            if( get_option('EMC_CONTENT_AS_DESC') == 'yes' && get_option('EMC_NATURE') != '' ) {
                $results = $wpdb->get_results( 'SELECT cat_name FROM '.$wpdb->prefix.'emc_categories WHERE cat_id = '.$wpdb->prepare('%s', get_option('EMC_NATURE')) ,OBJECT);
                self::handle_sql_error();
                foreach ($multiparcels as $multiparcel_info) {
                    $suborder_key = $multiparcel_info['suborder_key'];
                    $description[$suborder_key] = $results[0]->cat_name;
                }
            } else {
                // else concatenate product info
                foreach ($multiparcels as $multiparcel_info) {
                    $suborder_key = $multiparcel_info['suborder_key'];
                    $description[$suborder_key] = '';
                    foreach ($multiparcel_info["products"] as $product) {
                        $check_id = $product['id'];
                        if ($product['qty'] > 0) {
                            if (function_exists('wc_get_product')) {
                                $product = wc_get_product( $check_id );
                            } else {
                                // fix for WC < 2.5
                                $product = WC()->product_factory->get_product( $check_id );
                            }
                            $title = $product->get_title();

                            $product_type = self::envoimoinscher_get_product_property($product, 'type');
                            if ( $product_type == 'variation' ) {
                                $parent_id = $product->parent->id;
                                if (function_exists('wc_get_product')) {
                                    $parent_product = wc_get_product( $parent_id );
                                } else {
                                    // fix for WC < 2.5
                                    $parent_product = WC()->product_factory->get_product( $parent_id );
                                }
                                foreach($parent_product->get_available_variations() as $variation) {
                                    if ($variation['variation_id'] == $check_id) {
                                        foreach($variation['attributes'] as $attributes){
                                            $title .= ' ' . $attributes;
                                        }
                                    }
                                }
                            }
                            $description[$suborder_key] .= $title . ';';
                        }
                    }
                }
            }
			update_post_meta( $order_id, '_desc_content', $description );
		} elseif ($new_suborder_key != null) {
            $_desc_content = get_post_meta( $order_id, '_desc_content', true );
            if (!isset($_desc_content[$new_suborder_key])) {
                if( get_option('EMC_CONTENT_AS_DESC') == 'yes' && get_option('EMC_NATURE') != '' ) {
                    $results = $wpdb->get_results( 'SELECT cat_name FROM '.$wpdb->prefix.'emc_categories WHERE cat_id = '.$wpdb->prepare('%s', get_option('EMC_NATURE')) ,OBJECT);
                    self::handle_sql_error();
                    $_desc_content[$new_suborder_key] = $results[0]->cat_name;
                } else {
                    // else concatenate product info
                    foreach ($multiparcels as $multiparcel_info) {
                        if ($multiparcel_info['suborder_key'] != $new_suborder_key) {
                            continue;
                        }
                        $_desc_content[$new_suborder_key] = '';
                        foreach ($multiparcel_info["products"] as $product) {
                            $check_id = $product['id'];
                            if ($product['qty'] > 0) {
                                if (function_exists('wc_get_product')) {
                                    $product = wc_get_product( $check_id );
                                } else {
                                    // fix for WC < 2.5
                                    $product = WC()->product_factory->get_product( $check_id );
                                }
                                $title = $product->get_title();
                                $product_type = self::envoimoinscher_get_product_property($product, 'type');
                                if ( $product_type == 'variation' ) {
                                    $parent_id = $product->parent->id;
                                    if (function_exists('wc_get_product')) {
                                        $parent_product = wc_get_product( $parent_id );
                                    } else {
                                        // fix for WC < 2.5
                                        $parent_product = WC()->product_factory->get_product( $parent_id );
                                    }
                                    foreach($parent_product->get_available_variations() as $variation) {
                                        if ($variation['variation_id'] == $check_id) {
                                            foreach($variation['attributes'] as $attributes){
                                                $title .= ' ' . $attributes;
                                            }
                                        }
                                    }
                                }
                                $_desc_content[$new_suborder_key] .= $title . ';';
                            }
                        }
                    }
                }
                update_post_meta( $order_id, '_desc_content', $_desc_content );
            }
        }

		if ( get_post_meta( $order_id, '_desc_value', true ) == '' || $reset) {
            $subtotal = array();
            foreach ($multiparcels as $multiparcel_info) {
                $suborder_key = $multiparcel_info['suborder_key'];
                $subtotal[$suborder_key] = 0;
                foreach ( $order->get_items() as $item ) {
                    $check_id = $item["variation_id"] == 0 ? $item["product_id"] : $item["variation_id"];
                    $unit_price = esc_html( $item['line_subtotal'] ) / esc_html( $item['qty'] );

                    foreach ($multiparcel_info["products"] as $product) {
                        if ( $check_id == $product["id"] ) {
                            $subtotal[$suborder_key] += $unit_price * $product["qty"];
                        }
                    }
                }
            }
			update_post_meta( $order_id, '_desc_value', $subtotal );
		} elseif ($new_suborder_key != null) {
            $_desc_value = get_post_meta( $order_id, '_desc_value', true );
            if (!isset($_desc_value[$new_suborder_key])) {
                foreach ($multiparcels as $multiparcel_info) {
                    if ($multiparcel_info['suborder_key'] != $new_suborder_key) {
                        continue;
                    }
                    $_desc_value[$new_suborder_key] = 0;
                    foreach ( $order->get_items() as $item ) {
                        $check_id = $item["variation_id"] == 0 ? $item["product_id"] : $item["variation_id"];
                        $unit_price = esc_html( $item['line_subtotal'] ) / esc_html( $item['qty'] );

                        foreach ($multiparcel_info["products"] as $product) {
                            if ( $check_id == $product["id"] ) {
                                $_desc_value[$new_suborder_key] += $unit_price * $product["qty"];
                            }
                        }
                    }
                }
                update_post_meta( $order_id, '_desc_value', $_desc_value );
            }
        }


        if ( get_post_meta( $order_id, '_wrapping', true ) == '' || $reset) {
            $wrapping = array();
            foreach ($multiparcels as $multiparcel_info) {
                $suborder_key = $multiparcel_info['suborder_key'];
                $wrapping[$suborder_key] = get_option('EMC_WRAPPING', '1-Boîte');
            }
			update_post_meta( $order_id, '_wrapping', $wrapping );
		} elseif ($new_suborder_key != null) {
            $_wrapping = get_post_meta( $order_id, '_wrapping', true );
            if (!isset($_wrapping[$new_suborder_key])) {
                $_wrapping[$new_suborder_key] = get_option('EMC_WRAPPING', '1-Boîte');
                update_post_meta( $order_id, '_wrapping', $_wrapping );
            }
        }

        // initialize _pickup_point for new shipment only because _pickup_point is set up in hook_new_order
        // or reset: try and find the value in the post meta through consecutive unserializing
        if ($reset) {
            $pickup_point = get_post_meta( $order_id, '_pickup_point', true );
            while(is_array($pickup_point)) {
                $pickup_point = array_pop($pickup_point);
            }
            $pickup_point_array = array();
            foreach ($multiparcels as $multiparcel_info) {
                $suborder_key = $multiparcel_info['suborder_key'];
                $pickup_point_array[$suborder_key] = $pickup_point;
            }
			update_post_meta( $order_id, '_pickup_point', $pickup_point_array );
        } else if ($new_suborder_key != null) {
            $pickup_point = get_post_meta( $order_id, '_pickup_point', true );

            if (!isset($pickup_point[$new_suborder_key])) {
                // we get the permanently stored pickup point
                $default_pickup = get_post_meta( $order_id, '_pickup_point_client_choice', true );
                $pickup_point[$new_suborder_key] = $default_pickup;
                update_post_meta( $order_id, '_pickup_point', $pickup_point );

                // update pickup point for carrier as well
                foreach ($order->get_shipping_methods() as $key => $value ) {
                    $carrier_code = $value['method_id'];
                    $service = envoimoinscher_model::get_service_by_carrier_code($carrier_code);
                }
                $pickup_point_carrier = get_post_meta( $order_id, '_pickup_point_' . $carrier_code , true );
                if (!isset($pickup_point_carrier[$new_suborder_key])) {
                    $pickup_point_carrier[$new_suborder_key] = $default_pickup;
                    update_post_meta( $order_id, '_pickup_point_'.$carrier_code, $pickup_point_carrier );
                }
            }
        }

		if ( get_post_meta( $order_id, '_dropoff_point', true ) == '' || $reset) {
            $dropoff_point = array();
            $addressbook = self::get_addresses("all", true);
            foreach ($order->get_shipping_methods() as $key => $value ) {
                $carrier_code = $value['method_id'];
                $service = envoimoinscher_model::get_service_by_carrier_code($carrier_code);
            }
            if (self::is_emc_service($carrier_code)) {
                foreach ($multiparcels as $multiparcel_info) {
                    // get address from addressbook
                    foreach($addressbook as $item) {
                        if ($item['id'] == $multiparcel_info['address_id']) {
                            $address = $item;
                        }
                    }
                    $suborder_key = $multiparcel_info['suborder_key'];
                    if ($service->srv_dropoff_point == 1) {
                        $dropoff_points = @unserialize($address['dropoff_points']);
                        list($ope, $srv) = explode('_', $carrier_code);
                        $dropoff_point[$suborder_key] = isset($dropoff_points[$ope]) ? $dropoff_points[$ope] : '';
                    } else {
                        $dropoff_point[$suborder_key] = 'POST';
                    }
                }
            }
			update_post_meta( $order_id, '_dropoff_point', $dropoff_point );
            // initialize dropoff point for carrier as well
            update_post_meta( $order_id, '_dropoff_point_'.$carrier_code, $dropoff_point );
		} elseif ($new_suborder_key != null) {
            $_dropoff_point = get_post_meta( $order_id, '_dropoff_point', true );
            if (!isset($_dropoff_point[$new_suborder_key])) {
                $addressbook = self::get_addresses("all", true);
                foreach ($order->get_shipping_methods() as $key => $value ) {
                    $carrier_code = $value['method_id'];
                    $service = envoimoinscher_model::get_service_by_carrier_code($carrier_code);
                }
                foreach($addressbook as $item) {
                    if ($item['id'] == $multiparcel_info['address_id']) {
                        $address = $item;
                    }
                }
                if ($service->srv_dropoff_point == 1) {
                    $dropoff_points = @unserialize($address['dropoff_points']);
                    list($ope, $srv) = explode('_', $carrier_code);
                    $_dropoff_point[$new_suborder_key] = isset($dropoff_points[$ope]) ? $dropoff_points[$ope] : '';
                } else {
                    $_dropoff_point[$new_suborder_key] = 'POST';
                }
                update_post_meta( $order_id, '_dropoff_point', $_dropoff_point );
                // initialize dropoff point for carrier as well
                $dropoff_point_carrier = get_post_meta( $order_id, '_dropoff_point_' . $carrier_code , true );
                $dropoff_point_carrier[$new_suborder_key] = $_dropoff_point[$new_suborder_key];
                update_post_meta( $order_id, '_dropoff_point_'.$carrier_code, $dropoff_point_carrier );
            }
        }

		if ( get_post_meta( $order_id, '_pickup_date', true ) == '' || $reset) {
            foreach ($multiparcels as $multiparcel_info) {
                $suborder_key = $multiparcel_info['suborder_key'];
                $date =	self::set_collect_date($multiparcel_info['address_id']);
                $pickup_date[$suborder_key] = $date;
            }
			update_post_meta( $order_id, '_pickup_date', $pickup_date );
		} elseif ($new_suborder_key != null) {
            $_pickup_date = get_post_meta( $order_id, '_pickup_date', true );
            if (!isset($_pickup_date[$new_suborder_key])) {
                foreach ($multiparcels as $multiparcel_info) {
                    if($multiparcel_info['suborder_key'] == $new_suborder_key) {
                        $date =	self::set_collect_date($multiparcel_info['address_id']);
                    }
                }
                $_pickup_date[$new_suborder_key] = $date;
                update_post_meta( $order_id, '_pickup_date', $_pickup_date );
            }
        }

		if ( get_post_meta( $order_id, '_insurance', true ) == '' || $reset) {
            $insurance = array();
			if( get_option ('EMC_ASSU') == 'yes' ){
                foreach ($multiparcels as $multiparcel_info) {
                    $suborder_key = $multiparcel_info['suborder_key'];
                    $insurance[$suborder_key] = 1;
                }
			}
			else {
				foreach ($multiparcels as $multiparcel_info) {
                    $suborder_key = $multiparcel_info['suborder_key'];
                    $insurance[$suborder_key] = 0;
                }
			}
            update_post_meta( $order_id, '_insurance', $insurance );
            $assurance_emballage = array();
            $assurance_materiau = array();
            $assurance_protection = array();
            $assurance_fermeture = array();
            foreach ($multiparcels as $multiparcel_info) {
                $suborder_key = $multiparcel_info['suborder_key'];
                $assurance_emballage[$suborder_key] = 'Boîte';
                $assurance_materiau[$suborder_key] = 'Carton';
                $assurance_protection[$suborder_key] = 'Sans protection particulière';
                $assurance_fermeture[$suborder_key] = 'Fermeture autocollante';
            }
			update_post_meta( $order_id, '_assurance_emballage', $assurance_emballage );
			update_post_meta( $order_id, '_assurance_materiau', $assurance_materiau );
			update_post_meta( $order_id, '_assurance_protection', $assurance_protection );
			update_post_meta( $order_id, '_assurance_fermeture', $assurance_fermeture );
		} elseif ($new_suborder_key != null) {
            $_insurance = get_post_meta( $order_id, '_insurance', true );
            if (!isset($_insurance[$new_suborder_key])) {
                if( get_option ('EMC_ASSU') == 'yes' ){
                    $_insurance[$new_suborder_key] = 1;
                }
                else {
                    $_insurance[$new_suborder_key] = 0;
                }
                update_post_meta( $order_id, '_insurance', $_insurance );
            }

            $_assurance_emballage = get_post_meta( $order_id, '_assurance_emballage', true );
            $_assurance_materiau = get_post_meta( $order_id, '_assurance_materiau', true );
            $_assurance_protection = get_post_meta( $order_id, '_assurance_protection', true );
            $_assurance_fermeture = get_post_meta( $order_id, '_assurance_fermeture', true );

            if (!isset($_assurance_emballage[$new_suborder_key])) {
                $_assurance_emballage[$new_suborder_key] = 'Boîte';
                update_post_meta( $order_id, '_assurance_emballage', $_assurance_emballage );
            }
            if (!isset($_assurance_materiau[$new_suborder_key])) {
                $_assurance_materiau[$new_suborder_key] = 'Carton';
                update_post_meta( $order_id, '_assurance_materiau', $_assurance_materiau );
            }
            if (!isset($_assurance_protection[$new_suborder_key])) {
                $_assurance_protection[$new_suborder_key] = 'Sans protection particulière';
                update_post_meta( $order_id, '_assurance_protection', $_assurance_protection );
            }
            if (!isset($_assurance_fermeture[$new_suborder_key])) {
                $_assurance_fermeture[$new_suborder_key] = 'Fermeture autocollante';
                update_post_meta( $order_id, '_assurance_fermeture', $_assurance_fermeture );
            }
        }

		if ( get_post_meta( $order_id, '_url_push', true ) == '' || $reset) {
            $key_push = array();
            $url_push = array();
            foreach ($multiparcels as $multiparcel_info) {
                $suborder_key = $multiparcel_info['suborder_key'];
                $url_push[$suborder_key] = home_url('/').'?wc-api=envoimoinscher&order='.$order_id.'&suborder_key='.$suborder_key;
            }
			update_post_meta( $order_id, '_url_push', $url_push );
		} elseif ($new_suborder_key != null) {
            $_url_push = get_post_meta( $order_id, '_url_push', true );

            if (!isset($_url_push[$new_suborder_key])) {
                $_url_push[$new_suborder_key] = home_url('/').'?wc-api=envoimoinscher&order='.$order_id.'&suborder_key='.$new_suborder_key;
                update_post_meta( $order_id, '_url_push', $_url_push );
            }
        }

		// add proforma default fields
		if ( get_post_meta( $order_id, '_proforma_reason', true ) == '' || $reset) {
            $reason = array();
            foreach ($multiparcels as $multiparcel_info) {
                $suborder_key = $multiparcel_info['suborder_key'];
                $reason[$suborder_key] = 'sale';
            }
            update_post_meta( $order_id, '_proforma_reason', $reason);
		} elseif ($new_suborder_key != null) {
            $_proforma_reason = get_post_meta( $order_id, '_proforma_reason', true );
            if (!isset($_proforma_reason[$new_suborder_key])) {
                $_proforma_reason[$new_suborder_key] = 'sale';
                update_post_meta( $order_id, '_proforma_reason', $_proforma_reason);
            }
        }

        if ( get_post_meta( $order_id, '_proforma_items', true ) == '' || $reset) {
            // Get order items
            $items = $order->get_items();
            $proforma = array();

            foreach ($multiparcels as $multiparcel_info) {
                $suborder_key = $multiparcel_info['suborder_key'];
                foreach ( $items as $item ) {
                    $check_id = $item["variation_id"] == 0 ? $item["product_id"] : $item["variation_id"];
                    if (function_exists('wc_get_product')) {
                        $product = wc_get_product( $check_id );
                    } else {
                        // fix for WC < 2.5
                        $product = WC()->product_factory->get_product( $check_id );
                    }
                    $title = $product->get_title();
                    // add attributes to title for variations
                    $product_type = self::envoimoinscher_get_product_property($product, 'type');
                    if ( $product_type == 'variation' ) {
                        $parent_id = $product->parent->id;
                        if (function_exists('wc_get_product')) {
                            $parent_product = wc_get_product( $parent_id );
                        } else {
                            // fix for WC < 2.5
                            $parent_product = WC()->product_factory->get_product( $parent_id );
                        }
                        foreach($parent_product->get_available_variations() as $variation) {
                            if ($variation['variation_id'] == $check_id) {
                                foreach($variation['attributes'] as $attributes){
                                    $title .= ' ' . $attributes;
                                }
                            }
                        }
                    }
                    $proforma[$suborder_key][$check_id] = array(
                        'post_title_en' => esc_html( $title ),
                        'post_title_fr' => esc_html( $title ),
                        'origin' => __( 'France', 'envoimoinscher' ),
                    );
                }
            }
            update_post_meta( $order_id, '_proforma_items', $proforma);
        } elseif ($new_suborder_key != null) {
            // Get order items
            $items = $order->get_items();
            $_proforma_items = get_post_meta( $order_id, '_proforma_items', true );

            if (!isset($_proforma_items[$new_suborder_key])) {
                foreach ( $items as $item ) {
                    $check_id = $item["variation_id"] == 0 ? $item["product_id"] : $item["variation_id"];

                    if (function_exists('wc_get_product')) {
                        $product = wc_get_product( $check_id );
                    } else {
                        // fix for WC < 2.5
                        $product = WC()->product_factory->get_product( $check_id );
                    }
                    $title = $product->get_title();
                    // add attributes to title for variations
                    $product_type = self::envoimoinscher_get_product_property($product, 'type');
                    if ( $product_type == 'variation' ) {
                        $parent_id = $product->parent->id;
                        if (function_exists('wc_get_product')) {
                            $parent_product = wc_get_product( $parent_id );
                        } else {
                            // fix for WC < 2.5
                            $parent_product = WC()->product_factory->get_product( $parent_id );
                        }
                        foreach($parent_product->get_available_variations() as $variation) {
                            if ($variation['variation_id'] == $check_id) {
                                foreach($variation['attributes'] as $attributes){
                                    $title .= ' ' . $attributes;
                                }
                            }
                        }
                    }

                    $_proforma_items[$new_suborder_key][$check_id] = array(
                        'post_title_en' => esc_html( $title ),
                        'post_title_fr' => esc_html( $title ),
                        'origin' => __( 'France', 'envoimoinscher' ),
                    );
                }

                update_post_meta( $order_id, '_proforma_items', $_proforma_items);
            }
        }
        
        if ( get_post_meta( $order_id, '_carrier_ref', true ) == '' || $reset) {
            $_carrier_ref = array();
            foreach ($multiparcels as $multiparcel_info) {
                $suborder_key = $multiparcel_info['suborder_key'];
                $_carrier_ref[$suborder_key] = '';
            }
            update_post_meta( $order_id, '_carrier_ref', $_carrier_ref);
		} elseif ($new_suborder_key != null) {
            $_carrier_ref = get_post_meta( $order_id, '_carrier_ref', true );
            if (!isset($_carrier_ref[$new_suborder_key])) {
                $_carrier_ref[$new_suborder_key] = '';
                update_post_meta( $order_id, '_carrier_ref', $_carrier_ref);
            }
        }
        
        if ( get_post_meta( $order_id, '_label_url', true ) == '' || $reset) {
            $_label_url = array();
            foreach ($multiparcels as $multiparcel_info) {
                $suborder_key = $multiparcel_info['suborder_key'];
                $_label_url[$suborder_key] = '';
            }
            update_post_meta( $order_id, '_label_url', $_label_url);
		} elseif ($new_suborder_key != null) {
            $_label_url = get_post_meta( $order_id, '_label_url', true );
            if (!isset($_label_url[$new_suborder_key])) {
                $_label_url[$new_suborder_key] = '';
                update_post_meta( $order_id, '_label_url', $_label_url);
            }
        }
        
        if ( get_post_meta( $order_id, '_remise', true ) == '' || $reset) {
            $_remise = array();
            foreach ($multiparcels as $multiparcel_info) {
                $suborder_key = $multiparcel_info['suborder_key'];
                $_remise[$suborder_key] = '';
            }
            update_post_meta( $order_id, '_remise', $_remise);
        } elseif ($new_suborder_key != null) {
            $_remise = get_post_meta( $order_id, '_remise', true );
            if (!isset($_remise[$new_suborder_key])) {
                $_remise[$new_suborder_key] = '';
                update_post_meta( $order_id, '_remise', $_remise);
            }
        }
        
        if ( get_post_meta( $order_id, '_manifest', true ) == '' || $reset) {
            $_manifest = array();
            foreach ($multiparcels as $multiparcel_info) {
                $suborder_key = $multiparcel_info['suborder_key'];
                $_manifest[$suborder_key] = '';
            }
            update_post_meta( $order_id, '_manifest', $_manifest);
        } elseif ($new_suborder_key != null) {
            $_manifest = get_post_meta( $order_id, '_manifest', true );
            if (!isset($_manifest[$new_suborder_key])) {
                $_manifest[$new_suborder_key] = '';
                update_post_meta( $order_id, '_manifest', $_manifest);
            }
        }
        
        if ( get_post_meta( $order_id, '_connote', true ) == '' || $reset) {
            $_connote = array();
            foreach ($multiparcels as $multiparcel_info) {
                $suborder_key = $multiparcel_info['suborder_key'];
                $_connote[$suborder_key] = '';
            }
            update_post_meta( $order_id, '_connote', $_connote);
        } elseif ($new_suborder_key != null) {
            $_connote = get_post_meta( $order_id, '_connote', true );
            if (!isset($_connote[$new_suborder_key])) {
                $_connote[$new_suborder_key] = '';
                update_post_meta( $order_id, '_connote', $_connote);
            }
        }
        
        if ( get_post_meta( $order_id, '_proforma', true ) == '' || $reset) {
            $_proforma = array();
            foreach ($multiparcels as $multiparcel_info) {
                $suborder_key = $multiparcel_info['suborder_key'];
                $_proforma[$suborder_key] = '';
            }
            update_post_meta( $order_id, '_proforma', $_proforma);
        } elseif ($new_suborder_key != null) {
            $_proforma = get_post_meta( $order_id, '_proforma', true );
            if (!isset($_proforma[$new_suborder_key])) {
                $_proforma[$new_suborder_key] = '';
                update_post_meta( $order_id, '_proforma', $_proforma);
            }
        }
        
        if ( get_post_meta( $order_id, '_b13a', true ) == '' || $reset) {
            $_b13a = array();
            foreach ($multiparcels as $multiparcel_info) {
                $suborder_key = $multiparcel_info['suborder_key'];
                $_b13a[$suborder_key] = '';
            }
            update_post_meta( $order_id, '_b13a', $_b13a);
        } elseif ($new_suborder_key != null) {
            $_b13a = get_post_meta( $order_id, '_b13a', true );
            if (!isset($_b13a[$new_suborder_key])) {
                $_b13a[$new_suborder_key] = '';
                update_post_meta( $order_id, '_b13a', $_b13a);
            }
        }
        
        if (get_post_meta( $order_id, '_emc_carrier', true ) == '') {
            $emc_carrier_array = array();
            foreach ($multiparcels as $multiparcel_info) {
                $suborder_key = $multiparcel_info['suborder_key'];
                $emc_carrier_array[$suborder_key] = '';
            }
            update_post_meta( $order_id, '_emc_carrier', $emc_carrier_array);
        } elseif ($new_suborder_key != null) {
            $emc_carrier_array = get_post_meta( $order_id, '_emc_carrier', true );
            if (!isset($emc_carrier_array[$new_suborder_key])) {
                $emc_carrier_array[$new_suborder_key] = '';
                update_post_meta( $order_id, '_emc_carrier', $emc_carrier_array);
            }
        }
        
        // reset: try and find the value for _emc_carrier in the post meta through consecutive unserializing
        if ($reset) {
            $emc_carrier = get_post_meta( $order_id, '_emc_carrier', true );
            while(is_array($emc_carrier)) {
                $emc_carrier = array_pop($emc_carrier);
            }
            $emc_carrier_array = array();
            foreach ($multiparcels as $multiparcel_info) {
                $suborder_key = $multiparcel_info['suborder_key'];
                $emc_carrier_array[$suborder_key] = $emc_carrier;
            }
			update_post_meta( $order_id, '_emc_carrier', $emc_carrier_array );
        }
	}

	/**
	 * Get API params from order for one address.
	 * @param $order : the order we need to extract the parameters from.
	 * @param $address_id : the suborder address id.
	 * @returns array of all API params
	 */
	static function get_order_params( $order, $suborder_key ) {

		$result = array();
        $order_id = self::envoimoinscher_get_order_id($order);

        $colis_description = "";
        $colis_description_array = get_post_meta( $order_id, '_desc_content', true );
        if (is_array($colis_description_array) && isset($colis_description_array[$suborder_key])) {
            $colis_description = $colis_description_array[$suborder_key];
        }

        $wrapping = "";
        $wrapping_array = get_post_meta( $order_id, '_wrapping', true );
        if (is_array($wrapping_array) && isset($wrapping_array[$suborder_key])) {
            $wrapping = $wrapping_array[$suborder_key];
        }

        $desc_value = "";
        $desc_value_array = get_post_meta( $order_id, '_desc_value', true );
        if (is_array($desc_value_array) && isset($desc_value_array[$suborder_key])) {
            $desc_value = $desc_value_array[$suborder_key];
        }

        $url_push = "";
        $url_push_array = get_post_meta( $order_id, '_url_push', true );
        if (is_array($url_push_array) && isset($url_push_array[$suborder_key])) {
            $url_push = $url_push_array[$suborder_key];
        }

		$result['params'] = array(
			'code_contenu' => get_option( 'EMC_NATURE' ),
			'colis.description' => $colis_description,
			'module' => EMC_PLATFORM,
			'version' => WC_VERSION,
			'type_emballage.emballage' => $wrapping,
			'partnership' => get_option('EMC_PARTNERSHIP', null),
			'raison' => 'vente',
			'colis.valeur' => $desc_value,
			'delai' => 'aucun',
			'url_push' => $url_push,
		);

        // set dispo hde & hle from address
        $multiparcels = get_post_meta( $order_id, '_multiparcels', true );
        $addressbook = self::get_addresses("all", null);
        foreach ($multiparcels as $multiparcel_info) {
            if ($multiparcel_info['suborder_key'] == $suborder_key) {
                $address_id = $multiparcel_info["address_id"];
                // sender information
                foreach($addressbook as $item) {
                    if ($item['id'] == $address_id) {
                        $address = $item;
                    }
                }
                $result['params']['disponibilite.HDE'] = $address['dispo_hde'];
                $result['params']['disponibilite.HLE'] = $address['dispo_hle'];
            }
        }

        // set collect date to today if in the past
        $collect_date = "";
        $collect_date_array = get_post_meta( $order_id, '_pickup_date', true );
        if (is_array($collect_date_array) && isset($collect_date_array[$suborder_key])) {
            $collect_date = $collect_date_array[$suborder_key];
        }
        if ($collect_date < strtotime('Today')) {
            $result['params']['collecte'] = date('Y-m-d' , strtotime('Today') );
            $collect_date_array[$suborder_key] = strtotime('Today');
            update_post_meta( $order_id, '_pickup_date', $collect_date_array );

        } else {
            $result['params']['collecte'] = date('Y-m-d' , $collect_date );
        }

        $emc_carrier = "";
        $emc_carrier_array = get_post_meta( $order_id, '_emc_carrier', true );
        if (is_array($emc_carrier_array) && isset($emc_carrier_array[$suborder_key])) {
            $emc_carrier = $emc_carrier_array[$suborder_key];
        }
		if ( $emc_carrier != "") {
			$carrier_code = $emc_carrier;
			list($operator, $service) = explode("_", $carrier_code);

			$result['params']['operator'] = $operator;
			$result['params']['service'] = $service;
            $carrier = self::get_service_by_carrier_code($carrier_code);

			if( $carrier->srv_dropoff_point == 1) {
                $dropoff_point = "";
                $dropoff_point_array = get_post_meta( $order_id, '_dropoff_point', true );
                if (is_array($dropoff_point_array) && isset($dropoff_point_array[$suborder_key])) {
                    $dropoff_point = $dropoff_point_array[$suborder_key];
                }
				$result['params']['depot.pointrelais'] = $operator.'-'.$dropoff_point;
			} else {
				$result['params']['depot.pointrelais'] = $operator.'-POST';
			}

			if( $carrier->srv_pickup_point ){
                $pickup_point = "";
                $pickup_point_array = get_post_meta( $order_id, '_pickup_point', true );
                if (is_array($pickup_point_array) && isset($pickup_point_array[$suborder_key])) {
                    $pickup_point = $pickup_point_array[$suborder_key];
                }
				$result['params']['retrait.pointrelais']	= $operator.'-'.$pickup_point;
			}
		}

		$dest_type = 'individual';

		$result['to'] = array(
			'country' => get_post_meta( $order_id, '_shipping_country', true ),
			'zipcode' => get_post_meta( $order_id, '_shipping_postcode', true ) ? get_post_meta( $order_id, '_shipping_postcode', true ) : '',
			'city' => get_post_meta( $order_id, '_shipping_city', true ),
			'type' => $dest_type,
			'address' => get_post_meta( $order_id, '_shipping_address_1', true )."|".get_post_meta( $order_id, '_shipping_address_2', true ),
			'title' => 'M',
			'firstname' => get_post_meta( $order_id, '_shipping_first_name', true ),
			'lastname' => get_post_meta( $order_id, '_shipping_last_name', true ),
			'email' => get_post_meta( $order_id, '_billing_email', true ),
			'societe' => get_post_meta( $order_id, '_shipping_company', true ),
			'phone' => self::normalize_telephone( get_post_meta( $order_id, '_billing_phone', true ) ),
			'infos' => '',
		);

        // add state only for countries managed by API
        $managed_countries = array("AR", "AU", "BR", "CA", "US", "MX");
        if (in_array(get_post_meta( $order_id, '_shipping_country', true ), $managed_countries)) {
            if (get_post_meta($order_id, '_shipping_state', true) && strlen(get_post_meta($order_id, '_shipping_state', true)) == 2) {
                $result['to']['etat'] = get_post_meta( $order_id, '_shipping_state', true );
            }
        }

		return $result;
	}

	/**
	 * Normalize telephone number (removes all non numerical characters).
	 * @access public
	 * @param string $tel Number to normalize
	 * @return string Telephone number normalized
	 */
	static function normalize_telephone($tel)
	{
		$tel = strtolower(iconv('UTF-8', 'ASCII//TRANSLIT', $tel));
		$tel = preg_replace('/[^0-9]/', '', $tel);
		return $tel;
	}

	/**
	 * Make collect date. We can't collect on Sunday.
	 * @access public
	 * @var int $address_id the address id.
	 * @return String Collect date.
	 */
	static function set_collect_date($address_id)
	{
        $address = self::get_address_by_id($address_id);
        if (!$address) return false;

		$today = strtotime('Today');
		$time = strtotime(date('Y-m-d H:i'));

        $split = strtotime(date('Y-m-d', $today).' '.(int)$address['pickup_split'].':00');

        if ($time >= $split) {
            $days_delay = isset($address['pickup_day_2']) ? $address['pickup_day_2'] : 3;
        } else {
            $days_delay = isset($address['pickup_day_1']) ? $address['pickup_day_1'] : 2;
        }

		$result = strtotime('+'.$days_delay.'days', $time);

		if (date('N', $result) == 7)
			$result = strtotime('+1 day', $result);

		return $result;
	}

	/**************************/
	/********** LOGS **********/
	/**************************/

	/**
	 * Check if there is an SQL error on the last request, if there is one, report it
	 */
	static function handle_sql_error() {
		global $wpdb;
		if ($wpdb->last_error != '') {
			self::log('Error on the last SQL query: '.$wpdb->last_error);
			self::log('The query was: '.$wpdb->last_query);
		}
	}

	/**
	 * Add the message to woocommerce log system
	 * Woocommerce add itself the message's date
	 * logs are stored in wp-content/uploads/wc-logs/envoimoinscher-%md5(stuff)%.log
	 * @param $message : message to log
	 * @param $override : whether to override EMC_ENABLED_LOGS value and log it anyway
	 */
	static function log($message, $override = false) {
        if (is_null(self::$logger)) {
            self::init_logger();
        }

        if ($override || get_option('EMC_ENABLED_LOGS') == 'yes') {
            self::$logger->add('envoimoinscher',$message);
        }
	}

	/**
	 * Insert documents links into emc_documents table
	 * @param array $params : links to save
	 * @return void
	 */
	public static function insert_documents_links( $query_string ){

		$params = array();
		parse_str( $query_string, $params );
        $order_id = $params['order'];

        // we add this case for orders completed before the plugin update (should be able to delete this part after a few weeks).
        // These orders should contains only one suborder.
        if (!isset($params['suborder_key'])) {
            // get suborder_key
            $multiparcels = get_post_meta($order_id, '_multiparcels', true);
            $suborder_key = '';
            foreach ($multiparcels as $multiparcel_info) {
                $suborder_key = $multiparcel_info["suborder_key"];
            }

            if ($suborder_key == '') {
                exit;
            }

            if ( isset( $params['carrier_reference'] ) ) {
                update_post_meta($order_id, '_carrier_ref', array($suborder_key => urldecode( $params['carrier_reference'] )) );
            }
            if ( isset( $params['label_url'] ) ) {
                update_post_meta($order_id, '_label_url', array($suborder_key => urldecode( $params['label_url'] )) );
            }
            if ( isset( $params['remise'] ) ) {
                update_post_meta($order_id, '_remise', array($suborder_key => urldecode( $params['remise'] )));
            }
            if ( isset( $params['manifest'] ) ) {
                update_post_meta($order_id, '_manifest', array($suborder_key => urldecode( $params['manifest'] )));
            }
            if ( isset( $params['connote'] ) ) {
                update_post_meta($order_id, '_connote', array($suborder_key => urldecode( $params['connote'] )));
            }
            if ( isset( $params['proforma'] ) ) {
                update_post_meta($order_id, '_proforma', array($suborder_key => urldecode( $params['proforma'] )));
            }
            if ( isset( $params['b13a'] ) ) {
                update_post_meta($order_id, '_b13a', array($suborder_key => urldecode( $params['b13a'] )));
            }
        } else {
            $suborder_key = $params['suborder_key'];

            if ( isset( $params['carrier_reference'] ) ) {
                $_carrier_ref = get_post_meta($order_id, '_carrier_ref', true);
                $_carrier_ref[$suborder_key] = urldecode( $params['carrier_reference'] );
                update_post_meta($order_id, '_carrier_ref', $_carrier_ref);
            }

            if ( isset( $params['label_url'] ) ) {
                $_label_url = get_post_meta($order_id, '_label_url', true);
                $_label_url[$suborder_key] = urldecode( $params['label_url'] );
                update_post_meta($order_id, '_label_url', $_label_url);
            }

            if ( isset( $params['remise'] ) ) {
                $_remise = get_post_meta($order_id, '_remise', true);
                $_remise[$suborder_key] = urldecode( $params['remise'] );
                update_post_meta($order_id, '_remise', $_remise);
            }

            if ( isset( $params['manifest'] ) ) {
                $_manifest = get_post_meta($order_id, '_manifest', true);
                $_manifest[$suborder_key] = urldecode( $params['manifest'] );
                update_post_meta($order_id, '_manifest', $_manifest);
            }

            if ( isset( $params['connote'] ) ) {
                $_connote = get_post_meta($order_id, '_connote', true);
                $_connote[$suborder_key] = urldecode( $params['connote'] );
                update_post_meta($order_id, '_connote', $_connote);
            }

            if ( isset( $params['proforma'] ) ) {
                $_proforma = get_post_meta($order_id, '_proforma', true);
                $_proforma[$suborder_key] = urldecode( $params['proforma'] );
                update_post_meta($order_id, '_proforma', $_proforma);
            }

            if ( isset( $params['b13a'] ) ) {
                $_b13a = get_post_meta($order_id, '_b13a', true);
                $_b13a[$suborder_key] = urldecode( $params['b13a'] );
                update_post_meta($order_id, '_b13a', $_b13a);
            }
        }
        
        // in case _emc_ref has not been update on order, we update it here
        // prevents a weird case where it was not correctly saved during make_order
        $emc_ref_array = get_post_meta( $order_id, '_emc_ref', true );
        if (!isset($emc_ref_array[$suborder_key]) && isset($params['emc_reference'])) {
            if (!is_array($emc_ref_array)) {
                $emc_ref_array = array();
            }            
            $emc_ref_array[$suborder_key] = $params['emc_reference'];
            update_post_meta( $order_id, '_emc_ref', $emc_ref_array );
        }

		exit;
	}

	/**
	 * Insert tracking informations into emc_tracking table
	 * @param array $params : links to save
	 * @return void
	 */
	public static function insert_tracking_informations( $query_string ){

		$params = array();
		parse_str( $query_string, $params );

		// we add this case for orders completed before the plugin update (should be able to delete this part after a few weeks).
        // These orders should contains only one suborder.
        if (!isset($params['suborder_key'])) {
            // get suborder_key
            $multiparcels = get_post_meta($params['order'], '_multiparcels', true);
            $suborder_key = '';
            foreach ($multiparcels as $multiparcel_info) {
                $suborder_key = $multiparcel_info["suborder_key"];
            }

            if ($suborder_key == '') {
                exit;
            }

			$current_track_info = get_post_meta( $params['order'], '_emc_tracking', true );
			if ($current_track_info == '') $current_track_info = array();
            
            $text = isset($params['text']) ? urldecode( $params['text'] ) : '';
			$order = self::envoimoinscher_get_order($params['order']);

			// Generate a tracking message if no one was given
			if ( $text == '' ) {
				switch ( $params['etat'] ) {
					case 'CMD':
						$text = __( 'Order tracking: ordered.', 'envoimoinscher' );
						break;
					case 'ENV':
						$text = __( 'Order tracking: processing.', 'envoimoinscher' );
						break;
					case 'ANN':
						$text = __( 'Order tracking: cancelled.', 'envoimoinscher' );
						break;
					case 'LIV':
						$text = __( 'Order tracking: delivered.', 'envoimoinscher' );
						break;
					default:
						return false;
				}
			}
			$text = sprintf(__( '[suborder %s]', 'envoimoinscher' ), $suborder_key ).' '.$text;

			// Generate the default date if no one was given
			$date = strtotime( $params['date'] );
			if ( $date == false) {
				$date = time();
			}

			// New tracking information
			$new_track_info = array(
				'trk_state' => $params['etat'],
				'trk_date' => date( 'Y-m-d H:i:s', $date ),
				'trk_text' => $text,
				'trk_localisation' => isset($params['localisation']) ? urldecode( $params['localisation'] ) : '',
			);

			if(isset($current_track_info[$suborder_key]['trk_state'])) {
				// update order status and/or add order note
				switch ( $params['etat'] ) {
					case 'CMD':
						if($current_track_info[$suborder_key]['trk_date'] < $new_track_info['trk_date'] && $current_track_info[$suborder_key]['trk_text'] != $new_track_info['trk_text']) {
							$order->add_order_note( $text );
						}
						break;
					case 'ENV':
						if ($current_track_info[$suborder_key]['trk_date'] <= $new_track_info['trk_date'] && $current_track_info[$suborder_key]['trk_state'] != $new_track_info['trk_state']) {
							$order->update_status( 'wc-shipped', $text );
						} elseif ($current_track_info[$suborder_key]['trk_date'] < $new_track_info['trk_date'] && $current_track_info[$suborder_key]['trk_text'] != $new_track_info['trk_text']) {
							$order->add_order_note( $text );
						}
						break;
					case 'ANN':
						if($current_track_info[$suborder_key]['trk_date'] < $new_track_info['trk_date'] && $current_track_info[$suborder_key]['trk_text'] != $new_track_info['trk_text']) {
							$order->add_order_note( $text );
						}
						break;
					case 'LIV':
						if ($current_track_info[$suborder_key]['trk_date'] <= $new_track_info['trk_date'] && $current_track_info[$suborder_key]['trk_state'] != $new_track_info['trk_state']) {
							$order->update_status( 'wc-completed', $text );
						} elseif ($current_track_info[$suborder_key]['trk_date'] < $new_track_info['trk_date'] && $current_track_info[$suborder_key]['trk_text'] != $new_track_info['trk_text']) {
							$order->add_order_note( $text );
						}
						break;
					default:
						break;
				}

				// update tracking info post meta if more current
				if ($current_track_info[$suborder_key]['trk_date'] <= $new_track_info['trk_date']) {
					update_post_meta( $params['order'], '_emc_tracking', array($suborder_key => $new_track_info));
				}
			} else { // no previous tracking information
				$order->add_order_note( $text );

				// update tracking info post meta
				update_post_meta( $params['order'], '_emc_tracking', array($suborder_key => $new_track_info) );

                // update status
                switch ( $params['etat'] ) {
					case 'ENV':
						$order->update_status( 'wc-shipped', $text );
						break;
					case 'LIV':
						$order->update_status( 'wc-completed', $text );
						break;
					default:
						return false;
				}
			}
		} else {
            $suborder_key = $params['suborder_key'];

            $_emc_tracking = get_post_meta( $params['order'], '_emc_tracking', true );
            if ($_emc_tracking == '') $_emc_tracking = array();
            
			$text = isset($params['text']) ? urldecode( $params['text'] ) : '';
			$order = self::envoimoinscher_get_order($params['order']);

			// Generate a tracking message if no one was given
			if ( $text == '' ) {
				switch ( $params['etat'] ) {
					case 'CMD':
						$text = __( 'Order tracking: ordered.', 'envoimoinscher' );
						break;
					case 'ENV':
						$text = __( 'Order tracking: processing.', 'envoimoinscher' );
						break;
					case 'ANN':
						$text = __( 'Order tracking: cancelled.', 'envoimoinscher' );
						break;
					case 'LIV':
						$text = __( 'Order tracking: delivered.', 'envoimoinscher' );
						break;
					default:
						return false;
				}
			}
            $text = sprintf(__( '[suborder %s]', 'envoimoinscher' ), $suborder_key ).' '.$text;

			// Generate the default date if no one was given
			$date = strtotime( $params['date'] );
			if ( $date == false) {
				$date = time();
			}

			// New tracking information
			$new_track_info = array(
				'trk_state' => $params['etat'],
				'trk_date' => date( 'Y-m-d H:i:s', $date ),
				'trk_text' => $text,
				'trk_localisation' => isset($params['localisation']) ? urldecode( $params['localisation'] ) : '',
			);

			if(isset($_emc_tracking[$suborder_key]) && isset($_emc_tracking[$suborder_key]['trk_state'])) {
				// add order note
                if($_emc_tracking[$suborder_key]['trk_date'] < $new_track_info['trk_date'] && $_emc_tracking[$suborder_key]['trk_text'] != $new_track_info['trk_text']) {
                    $order->add_order_note( $text );
                }

				// update tracking info post meta if more current
				if ($_emc_tracking[$suborder_key]['trk_date'] <= $new_track_info['trk_date']) {
                    $_emc_tracking[$suborder_key] = $new_track_info;
					update_post_meta( $params['order'], '_emc_tracking', $_emc_tracking);
				}
			} else { // no previous tracking information
				$order->add_order_note( $text );

				// update tracking info post meta
                $_emc_tracking[$suborder_key] = $new_track_info;
				update_post_meta( $params['order'], '_emc_tracking', $_emc_tracking );
			}

            // check statuses for all suborders and update if all suborders have reached the same status
            $multiparcels = get_post_meta($params['order'], '_multiparcels', true);
            $update = true;
            switch ( $params['etat'] ) {
                case 'ENV':
                    foreach ($multiparcels as $multiparcel_info) {
                        if (!isset($_emc_tracking[$multiparcel_info['suborder_key']]) || $_emc_tracking[$multiparcel_info['suborder_key']]['trk_state'] == 'CMD') {
                            $update = false;
                        }
                    }
                    if ($update) {
                        $order->update_status( 'wc-shipped');
                    }
                    break;
                case 'LIV':
                    foreach ($multiparcels as $multiparcel_info) {
                        if (!isset($_emc_tracking[$multiparcel_info['suborder_key']]) || $_emc_tracking[$multiparcel_info['suborder_key']]['trk_state'] == 'CMD' ||
                        $_emc_tracking[$multiparcel_info['suborder_key']]['trk_state'] == 'ENV' ) {
                            $update = false;
                        }
                    }
                    if ($update) {
                        $order->update_status( 'wc-completed');
                    }
                    break;
                default:
                    break;
            }
        }

		exit;
	}

	/**
	 * Function to flush rates cache
	 *
	 * @return void
	 */
	static function flush_rates_cache() {
		global $wpdb;

		$return = $wpdb->query(
			"DELETE FROM `$wpdb->options` WHERE `option_name` LIKE ('_transient_emc_quote_%')
				OR `option_name` LIKE ('_transient_timeout_emc_quote_%')
				OR `option_name` LIKE ('_transient_emc_parcels_%')
				OR `option_name` LIKE ('_transient_timeout_emc_parcels_%')
				OR `option_name` LIKE ('_transient_wc_ship_%')
				OR `option_name` LIKE ('_transient_timeout_wc_ship_%')"
		);

		// delete offers delivery dates
		$return &= $wpdb->query('DELETE FROM `'.$wpdb->options.'` WHERE option_name LIKE "_delivery_date_%";');

		// Check if log exists for uninstall hook
		if( !isset(self::$logger) )	self::$logger = new WC_Logger();

		if ( $return ) {
			self::log('Offers cache was successfully flushed. ' . intval($return). ' rows where deleted.');
		}
		else{
			self::log('Offers cache is empty or couldn\'t be deleted.');
		}

		return $return;
	}
    
    /**
     * Function to flush user details cache
     *
     * @return void
     */
    static function flush_user_details_cache() {
        global $wpdb;

        $return = $wpdb->query(
            "DELETE FROM `$wpdb->options` WHERE `option_name` = ('EMC_PARTNERSHIP_UPDATE')
                OR `option_name` = ('EMC_USER_DETAILS_UPDATE')"
        );

        // Check if log exists for uninstall hook
        if( !isset(self::$logger) )	self::$logger = new WC_Logger();

        if ( $return ) {
            self::log('User details cache was successfully flushed.');
        }
        else{
            self::log('User details cache is empty or couldn\'t be deleted.');
        }

        return $return;
    }

    /**
	 * Fix for WC < 2.5
	 * @param $order + same as has_status function
	 * @return boolean
	 */
	static function order_has_status($order, $statuses) {
        if(method_exists($order, 'has_status')) {
            return $order->has_status($statuses);
        } else {
            return in_array($order->status, $statuses);
        }
	}

    /**
	 * Fix for WC < 2.5
	 * @param $order_id
	 * @return $order
	 */
	static function envoimoinscher_get_order($order_id) {
        if (function_exists('wc_get_order')) {
            $order = wc_get_order( $order_id );
        } else {
            // fix for WC < 2.5
            if(WC()->order_factory !== false) {
                $order = WC()->order_factory->get_order( $order_id );
            } else {
                global $theorder;

                if ( ! is_object( $theorder ) )
                    $theorder = new WC_Order( $order_id );

                $order = $theorder;
            }
        }
        return $order;
	}
    
    /**
	 * Fix for WC > 3.1
	 * @param $order
	 * @return $order_id
	 */
	static function envoimoinscher_get_order_id($order) {
        if (method_exists($order, 'get_order_number')) {
            return $order->get_order_number();
        } else {
            return $order->id;
        }
	}
    
    /**
     * Fix for WC > 3.1
     * @param $order
     * @param string $property
     * @return $value
     */
    static function envoimoinscher_get_order_property($order, $property) {
        switch($property) {
            case('shipping_city'):
                if (method_exists($order, 'get_shipping_city')) {
                    return $order->get_shipping_city();
                } else {
                    return $order->shipping_city;
                }
                break;
                
            case('shipping_postcode'):
                if (method_exists($order, 'get_shipping_postcode')) {
                    return $order->get_shipping_postcode();
                } else {
                    return $order->shipping_postcode;
                }
                break;
                
            case('shipping_country'):
                if (method_exists($order, 'get_shipping_country')) {
                    return $order->get_shipping_country();
                } else {
                    return $order->shipping_country;
                }
                break;
                
            default:
                return "add property to envoimoinscher_get_order_property!";
                break;
        }
        
    }
    
    /**
     * Fix for WC > 3.1
     * @param $product
     * @param string $property
     * @return $value
     */
    static function envoimoinscher_get_product_property($product, $property) {
        switch($property) {
            case('type'):
                if (method_exists($product, 'get_type')) {
                    return $product->get_type();
                } else {
                    return $product->product_type;
                }
                break;
                
            default:
                return "add property to envoimoinscher_get_product_property!";
                break;
        }
        
    }

    /**
	 * Removes previous pricing info and adds new pricing info in emc_pricing for a given service
	 * @param string $carrier_code : the carrier code (ex: MONR_CpourToi)
	 * @param array $pricing : the pricing info, containing all pricing items
     * @return true or error
	 */
	static function add_pricing($carrier_code, $pricing_items) {
        global $wpdb;
        self::delete_pricing ($carrier_code);

        if (empty($pricing_items)) {
            return true;
        }

        foreach ($pricing_items as $id => $pricing_item) {
            $pricing = array(
                'values' => array(
                    'pricing_id' => $id,
                    'carrier_code' => $carrier_code,
                    'shipping_class' => serialize($pricing_item->shipping_class),
                    'zones' => serialize(($pricing_item->zones == '') ? array() : $pricing_item->zones),
                    'pricing' => $pricing_item->pricing,
                ),
                'format' => array(
                    '%d',
                    '%s',
                    '%s',
                    '%s',
                    '%s',
                )
            );

            // add not null values only
            if ($pricing_item->price_from != '') {
                $pricing['values']['price_from'] = $pricing_item->price_from;
                array_push($pricing['format'], '%f');
            }
            if ($pricing_item->price_to != '') {
                $pricing['values']['price_to'] = $pricing_item->price_to;
                array_push($pricing['format'], '%f');
            }
            if ($pricing_item->weight_from != '') {
                $pricing['values']['weight_from'] = $pricing_item->weight_from;
                array_push($pricing['format'], '%f');
            }
            if ($pricing_item->weight_to != '') {
                $pricing['values']['weight_to'] = $pricing_item->weight_to;
                array_push($pricing['format'], '%f');
            }
            if ($pricing_item->handling_fees != '') {
                $pricing['values']['handling_fees'] = $pricing_item->handling_fees;
                array_push($pricing['format'], '%s');
            }
            if ($pricing_item->flat_rate != '') {
                $pricing['values']['flat_rate'] = $pricing_item->flat_rate;
                array_push($pricing['format'], '%f');
            }

            $wpdb->insert(
                $wpdb->prefix.'emc_pricing',
                $pricing['values'],
                $pricing['format']
            );
            if ($wpdb->last_error != '') {
                self::handle_sql_error();
                return $wpdb->last_error;
            }
        }

        return true;
    }

    /**
	 * Deletes pricing info for a given service
	 * @param string $carrier_code : the carrier code (ex: MONR_CpourToi)
     * @return true or error
	 */
	static function delete_pricing($carrier_code) {
        global $wpdb;
        $wpdb->delete(
            $wpdb->prefix.'emc_pricing',
            array(
                'carrier_code' => $carrier_code
            ),
            array(
                '%s'
            )
        );
        if ($wpdb->last_error != '') {
            self::handle_sql_error();
            return $wpdb->last_error;
        } else {
            return true;
        }
    }

    /**
	 * Gets pricing info for a given service
	 * @param string $carrier_code : the carrier code (ex: MONR_CpourToi)
     * @return array $return pricing items
	 */
	static function get_pricing($carrier_code) {
        global $wpdb;
        $result = $wpdb->get_results('SELECT * FROM '.$wpdb->prefix.'emc_pricing WHERE carrier_code='.$wpdb->prepare('%s', $carrier_code),OBJECT);
        $return = array();
        if (!empty($result) && is_array($result)) {
            foreach ($result as $pricing_item) {
                $return[$pricing_item->pricing_id] = array(
                    'price-from' => $pricing_item->price_from,
                    'price-to' => $pricing_item->price_to,
                    'weight-from' => $pricing_item->weight_from,
                    'weight-to' => $pricing_item->weight_to,
                    'shipping-class' => unserialize($pricing_item->shipping_class),
                    'zones' => unserialize($pricing_item->zones),
                    'handling-fees' => $pricing_item->handling_fees,
                    'pricing' => $pricing_item->pricing,
                    'flat-rate' => $pricing_item->flat_rate

                );
            }
            ksort($return);
        }
        return $return;
    }

    /**
     * @return true if zones are available in the current woocommerce version
     */
    static function is_zones_enabled() {
        return class_exists('WC_Shipping_Zones');
    }

    /**
     * Get all zones associated with a shipping method
     * If no carrier code is given, then it will return all zones
     *
     * @param $carrier_code : carrier code of a shipping method
     * @return array : all available zones (including default zone "rest of the world") for a service
	 */
    static function get_zones($carrier_code = false) {
        $carrier_zones = array();

        // load all zones (0 = default)
        $zones = WC_Shipping_Zones::get_zones();
        // only way for now to add the default zone with it's data as an array (see wp-content/plugins/woocommerce/includes/class-wc-shipping-zones.php)
        $default_zone = WC_Shipping_Zones::get_zone(0);
        $zones[ self::get_zone_id($default_zone) ] = $default_zone->get_data();
        $zones[ self::get_zone_id($default_zone) ]['formatted_zone_location'] = $default_zone->get_formatted_location();
        $zones[ self::get_zone_id($default_zone) ]['shipping_methods'] = $default_zone->get_shipping_methods();

        if ($carrier_code === false) {
            $carrier_zones = $zones;
        }
        else {
            foreach($zones as $zone) {
                foreach($zone['shipping_methods'] as $shipping_method) {
                    if (method_exists($shipping_method, 'getCalledClass') && $shipping_method->getCalledClass() == $carrier_code){
                        $carrier_zones[] = $zone;
                        break;
                    }
                }
            }
        }

        return $carrier_zones;
    }
    
    /**
     * Gets zone id
     *
     * @param $zone
     * @return int : zone id
	 */
    static function get_zone_id($zone) {
        if (method_exists($zone, 'get_id')) {
            return $zone->get_id();
        } else {
            return $zone->get_zone_id();
        }
    }

    /**
	 * Gets translation for custom multilingual field, returns french (or english) if translation is empty or does not exist
	 * @param mixed $field : the multilingual field serialized (string) or not (array)
	 * @param string $lang : the target language, 5 characters string (fr_FR, en_US, ...)
	 * @param boolean $lang : the target language, 5 characters string (fr_FR, en_US, ...)
	 * @return string $translation
	 */
	static function get_translation($field, $language, $serialized = false) {

        if ($serialized) {
            $unserialized_field = @unserialize($field);
            if ($unserialized_field == false) {
                return $field;
            }
        } else {
            $unserialized_field = $field;
        }

        if (!isset($unserialized_field[$language])) {
            // return any target language
            foreach ($unserialized_field as $lang => $value) {
                if ((strtolower(substr($language, 0, 2)) == strtolower(substr($lang, 0, 2))) && $value != '') {
                    return $value;
                }
            }
            // return any french language
            foreach ($unserialized_field as $lang => $value) {
                if ('fr' == strtolower(substr($lang, 0, 2))) {
                    return $value;
                }
            }
            // return any english language
            foreach ($unserialized_field as $lang => $value) {
                if ('en' == strtolower(substr($lang, 0, 2))) {
                    return $value;
                }
            }
            // return any target language if empty
            foreach ($unserialized_field as $lang => $value) {
                if (strtolower(substr($language, 0, 2)) == strtolower(substr($lang, 0, 2))) {
                    return $value;
                }
            }
            // return any language
            return current($unserialized_field);
        } else {
            return $unserialized_field[$language];
        }
	}

    /**
	 * Gets minimum set of addresses to cover all shipping classes
	 * @param array $classes_by_address : array indexed by address id containing array of shipping classes relative to this address
	 * @param array $classes : all classes we want to cover
	 * @return array $translation
	 */
    static function array_set_cover($classes_by_address, $classes){
        $count_n = count($classes_by_address);
        $count = pow(2, $count_n);

        // we get all possible combinations
        for ($i = 1; $i <= $count; $i++) {
            $total = array();
            $addresses = array();
            $k = $i;
            foreach ($classes_by_address as $address_id => $classes_for_address) {
                if ($k%2) {
                    $total = array_merge($total, $classes_for_address);
                    $addresses[] = $address_id;
                }
                $k = $k>>1;
            }
            $total = array_unique($total);
            // if all classes are represented we store the result with all addresses used
            if(count(array_diff($classes, $total)) < 1){
                $result[$i] = $addresses;
            }
        }

        // now we look for the minimum set
        $minimum_set = $count_n;
        $returned_address_set = array_keys($classes_by_address);
        foreach ($result as $address_set) {
            if (count($address_set) < $minimum_set) {
                $minimum_set = count($address_set);
                $returned_address_set = $address_set;
            }
        }
        return $returned_address_set;
    }

    /**
	 * Returns an array indexed by address id containing array of shipping classes relative to this address
	 * The default address is attributed the class 'emc-no-shipping-class'
	 * @return array $translation
	 */
    static function get_classes_by_addresses() {
        $addressbook = self::get_addresses("all", true);
        $return = array();
        foreach($addressbook as $address) {
            if ($address['shipping_classes'] != "") {
                $return[$address['id']] = explode(',', $address['shipping_classes']);
            } elseif($address['type'] == "default") {
                $return[$address['id']] = array('emc-no-shipping-class');
            }
        }
        return $return;
    }

    /**
	 * Returns emc addresses
     * @param string $type : "default" or "additional" or "all"
     * @param boolean $active : if true returns active addresses, if false returns inactive addresses, if null, returns all addresses
	 * @return array addresses
	 */
    static function get_addresses($type, $active = null) {
        global $wpdb;
        $subrequest = array();

        if ($active === true) {
            $subrequest[] = "active=1";
        } elseif ($active === false) {
            $subrequest[] = "active=0";
        }

        switch ($type) {
            case "default":
                $subrequest[] = 'type="default"';
                break;

            case "additional":
                $subrequest[] = 'type="additional"';
                break;

            case "all":
                break;
        }

        $query = 'SELECT * FROM '.$wpdb->prefix.'emc_addresses';
        if (!empty($subrequest)) {
            $query .= ' where '.implode(' AND ', $subrequest);
        }

        $results = $wpdb->get_results($query, ARRAY_A);

        return $results;
    }

    /**
	 * Returns emc address by id
     * @param int $address_id
	 * @return array address
	 */
    static function get_address_by_id($address_id) {
        global $wpdb;

        $query = $wpdb->prepare('SELECT * FROM '.$wpdb->prefix.'emc_addresses where id=%d', $address_id);

        $result = $wpdb->get_row($query, ARRAY_A);

        return $result;
    }

    /**
	 * Is address active?
     * @param int $address_id
	 * @return boolean true if address is active, false if not
	 */
    static function is_address_active($address_id) {
        global $wpdb;
        $results = $wpdb->get_row($wpdb->prepare('SELECT active FROM '.$wpdb->prefix.'emc_addresses WHERE id = %s',$address_id), ARRAY_A);
        return $results['active'];
    }

    /**
	 * Checks if a default address is already in place
	 * @return boolean
	 */
    static function has_default_address() {
        global $wpdb;
        $results = $wpdb->get_row('SELECT count(*) as addCount FROM '.$wpdb->prefix.'emc_addresses WHERE type = "default" AND active = 1', ARRAY_A);
        return (null !== $results && isset($results['addCount']) && $results['addCount'] > 0) ? true : false;
    }

    /**
	 * Update DB version to current.
	 * @param string $version
	 */
	static function update_db_version( $version = null ) {
		delete_option( 'EMC_DB_VERSION' );
		add_option( 'EMC_DB_VERSION', is_null( $version ) ? EMC()->version : $version );
	}
    
    /**
	 * Helper function to get checkout url.
	 * @return string
	 */
    static function get_checkout_url() {
        if (function_exists('wc_get_checkout_url')) {
            return wc_get_checkout_url();
        } else {
            return WC()->cart->get_checkout_url();
        }
    }
    
    /**
	 * Helper function to check is this is the checkout page.
	 * @return boolean
	 */
    static function is_checkout_page() {
        if (in_the_loop()) {
            return (int) get_option( 'woocommerce_checkout_page_id' ) === get_the_ID();
        }
        
        if ( isset( $_SERVER['REQUEST_URI'] ) ) {
            $checkout_url = self::get_checkout_url();
            $request_uri  = $_SERVER['REQUEST_URI'];
            
            if (self::remove_query_string($checkout_url) === self::remove_query_string($request_uri)) {
                return true;
            }
     
            if (isset($_SERVER['HTTP_REFERER']) && false !== strpos($checkout_url, $_SERVER['HTTP_REFERER']) && false === strpos(WC()->cart->get_cart_url(), $request_uri) ) {
                return true;
            }
        }
        return false;
    }
    
    /**
	 * Helper function to remove query string in url.
     *
     * @param string $url
	 * @return boolean
	 */
    static function remove_query_string($url) {
        if (strpos($url, '?') !== false) {
            $url = substr($url, 0, strpos($url, '?'));
        }
        return $url;
    }
}
