';
+ if (dol_strlen($morehtmlcenter))
+ {
+ $return.= '
'.$morehtmlright.'
';
+ }
+ $return.= '
'."\n";
+
+ return $return;
+}
+
+/**
+ * Print a title with navigation controls for pagination
+ *
+ * @param string $titre Title to show (required)
+ * @param int $page Numero of page to show in navigation links (required)
+ * @param string $file Url of page (required)
+ * @param string $options More parameters for links ('' by default, does not include sortfield neither sortorder). Value must be 'urlencoded' before calling function.
+ * @param string $sortfield Field to sort on ('' by default)
+ * @param string $sortorder Order to sort ('' by default)
+ * @param string $morehtmlcenter String in the middle ('' by default). We often find here string $massaction comming from $form->selectMassAction()
+ * @param int $num Number of records found by select with limit+1
+ * @param int|string $totalnboflines Total number of records/lines for all pages (if known). Use a negative value of number to not show number. Use '' if unknown.
+ * @param string $picto Icon to use before title (should be a 32x32 transparent png file)
+ * @param int $pictoisfullpath 1=Icon name is a full absolute url of image
+ * @param string $morehtmlright More html to show
+ * @param string $morecss More css to the table
+ * @param int $limit Max number of lines (-1 = use default, 0 = no limit, > 0 = limit).
+ * @param int $hideselectlimit Force to hide select limit
+ * @param int $hidenavigation Force to hide all navigation tools
+ * @return void
+ */
+function print_barre_liste($titre, $page, $file, $options = '', $sortfield = '', $sortorder = '', $morehtmlcenter = '', $num = -1, $totalnboflines = '', $picto = 'title_generic.png', $pictoisfullpath = 0, $morehtmlright = '', $morecss = '', $limit = -1, $hideselectlimit = 0, $hidenavigation = 0)
+{
+ global $conf,$langs;
+
+ $savlimit = $limit;
+ $savtotalnboflines = $totalnboflines;
+ $totalnboflines=abs($totalnboflines);
+
+ if ($picto == 'setup') $picto='title_setup.png';
+ if (($conf->browser->name == 'ie') && $picto=='title_generic.png') $picto='title.gif';
+ if ($limit < 0) $limit = $conf->liste_limit;
+ if ($savlimit != 0 && (($num > $limit) || ($num == -1) || ($limit == 0)))
+ {
+ $nextpage = 1;
+ }
+ else
+ {
+ $nextpage = 0;
+ }
+ //print 'totalnboflines='.$totalnboflines.'-savlimit='.$savlimit.'-limit='.$limit.'-num='.$num.'-nextpage='.$nextpage;
+
+ print "\n";
+ print "\n";
+ print '
'; // maring bottom must be same than into load_fiche_tire
+
+ // Left
+ //if ($picto && $titre) print '
';
+
+ // Center
+ if ($morehtmlcenter)
+ {
+ print '
'.$morehtmlcenter.'
';
+ }
+
+ // Right
+ print '
';
+ if ($sortfield) $options .= "&sortfield=".urlencode($sortfield);
+ if ($sortorder) $options .= "&sortorder=".urlencode($sortorder);
+ // Show navigation bar
+ $pagelist = '';
+ if ($savlimit != 0 && ($page > 0 || $num > $limit))
+ {
+ if ($totalnboflines) // If we know total nb of lines
+ {
+ // Define nb of extra page links before and after selected page + ... + first or last
+ $maxnbofpage=(empty($conf->dol_optimize_smallscreen) ? 4 : 1);
+
+ if ($limit > 0) $nbpages=ceil($totalnboflines/$limit);
+ else $nbpages=1;
+ $cpt=($page-$maxnbofpage);
+ if ($cpt < 0) { $cpt=0; }
+
+ if ($cpt>=1)
+ {
+ $pagelist.= '
";
+ }
+ }
+
+ print_fleche_navigation($page, $file, $options, $nextpage, $pagelist, $morehtmlright, $savlimit, $totalnboflines, $hideselectlimit); // output the div and ul for previous/last completed with page numbers into $pagelist
+
+ print '
';
+
+ print '
'."\n";
+ print "\n\n";
+}
+
+/**
+ * Function to show navigation arrows into lists
+ *
+ * @param int $page Number of page
+ * @param string $file Page URL (in most cases provided with $_SERVER["PHP_SELF"])
+ * @param string $options Other url paramaters to propagate ("" by default, may include sortfield and sortorder)
+ * @param integer $nextpage Do we show a next page button
+ * @param string $betweenarrows HTML content to show between arrows. MUST contains '
' tags or '
'.
+ * @param string $afterarrows HTML content to show after arrows. Must NOT contains '
' tags.
+ * @param int $limit Max nb of record to show (-1 = no combo with limit, 0 = no limit, > 0 = limit)
+ * @param int $totalnboflines Total number of records/lines for all pages (if known)
+ * @param int $hideselectlimit Force to hide select limit
+ * @return void
+ */
+function print_fleche_navigation($page, $file, $options = '', $nextpage = 0, $betweenarrows = '', $afterarrows = '', $limit = -1, $totalnboflines = 0, $hideselectlimit = 0)
+{
+ global $conf, $langs;
+
+ print '
';
+ if ((int) $limit >= 0 && empty($hideselectlimit))
+ {
+ $pagesizechoices='10:10,15:15,20:20,30:30,40:40,50:50,100:100,250:250,500:500,1000:1000,5000:5000';
+ //$pagesizechoices.=',0:'.$langs->trans("All"); // Not yet supported
+ //$pagesizechoices.=',2:2';
+ if (! empty($conf->global->MAIN_PAGESIZE_CHOICES)) $pagesizechoices=$conf->global->MAIN_PAGESIZE_CHOICES;
+
+ print '
'."\n";
+}
+
+
+/**
+ * Return a string with VAT rate label formated for view output
+ * Used into pdf and HTML pages
+ *
+ * @param string $rate Rate value to format ('19.6', '19,6', '19.6%', '19,6%', '19.6 (CODEX)', ...)
+ * @param boolean $addpercent Add a percent % sign in output
+ * @param int $info_bits Miscellaneous information on vat (0=Default, 1=French NPR vat)
+ * @param int $usestarfornpr -1=Never show, 0 or 1=Use '*' for NPR vat rates
+ * @return string String with formated amounts ('19,6' or '19,6%' or '8.5% (NPR)' or '8.5% *' or '19,6 (CODEX)')
+ */
+function vatrate($rate, $addpercent = false, $info_bits = 0, $usestarfornpr = 0)
+{
+ $morelabel='';
+
+ if (preg_match('/%/', $rate))
+ {
+ $rate=str_replace('%', '', $rate);
+ $addpercent=true;
+ }
+ if (preg_match('/\((.*)\)/', $rate, $reg))
+ {
+ $morelabel=' ('.$reg[1].')';
+ $rate=preg_replace('/\s*'.preg_quote($morelabel, '/').'/', '', $rate);
+ }
+ if (preg_match('/\*/', $rate))
+ {
+ $rate=str_replace('*', '', $rate);
+ $info_bits |= 1;
+ }
+
+ // If rate is '9/9/9' we don't change it. If rate is '9.000' we apply price()
+ if (! preg_match('/\//', $rate)) $ret=price($rate, 0, '', 0, 0).($addpercent?'%':'');
+ else
+ {
+ // TODO Split on / and output with a price2num to have clean numbers without ton of 000.
+ $ret=$rate.($addpercent?'%':'');
+ }
+ if (($info_bits & 1) && $usestarfornpr >= 0) $ret.=' *';
+ $ret.=$morelabel;
+ return $ret;
+}
+
+
+/**
+ * Function to format a value into an amount for visual output
+ * Function used into PDF and HTML pages
+ *
+ * @param float $amount Amount to format
+ * @param integer $form Type of format, HTML or not (not by default)
+ * @param Translate $outlangs Object langs for output
+ * @param int $trunc 1=Truncate if there is more decimals than MAIN_MAX_DECIMALS_SHOWN (default), 0=Does not truncate. Deprecated because amount are rounded (to unit or total amount accurancy) before beeing inserted into database or after a computation, so this parameter should be useless.
+ * @param int $rounding Minimum number of decimal to show. If 0, no change, if -1, we use min($conf->global->MAIN_MAX_DECIMALS_UNIT,$conf->global->MAIN_MAX_DECIMALS_TOT)
+ * @param int $forcerounding Force the number of decimal to forcerounding decimal (-1=do not force)
+ * @param string $currency_code To add currency symbol (''=add nothing, 'auto'=Use default currency, 'XXX'=add currency symbols for XXX currency)
+ * @return string Chaine avec montant formate
+ *
+ * @see price2num() Revert function of price
+ */
+function price($amount, $form = 0, $outlangs = '', $trunc = 1, $rounding = -1, $forcerounding = -1, $currency_code = '')
+{
+ global $langs,$conf;
+
+ // Clean parameters
+ if (empty($amount)) $amount=0; // To have a numeric value if amount not defined or = ''
+ $amount = (is_numeric($amount)?$amount:0); // Check if amount is numeric, for example, an error occured when amount value = o (letter) instead 0 (number)
+ if ($rounding < 0) $rounding=min($conf->global->MAIN_MAX_DECIMALS_UNIT, $conf->global->MAIN_MAX_DECIMALS_TOT);
+ $nbdecimal=$rounding;
+
+ // Output separators by default (french)
+ $dec=','; $thousand=' ';
+
+ // If $outlangs not forced, we use use language
+ if (! is_object($outlangs)) $outlangs=$langs;
+
+ if ($outlangs->transnoentitiesnoconv("SeparatorDecimal") != "SeparatorDecimal") $dec=$outlangs->transnoentitiesnoconv("SeparatorDecimal");
+ if ($outlangs->transnoentitiesnoconv("SeparatorThousand")!= "SeparatorThousand") $thousand=$outlangs->transnoentitiesnoconv("SeparatorThousand");
+ if ($thousand == 'None') $thousand='';
+ elseif ($thousand == 'Space') $thousand=' ';
+ //print "outlangs=".$outlangs->defaultlang." amount=".$amount." html=".$form." trunc=".$trunc." nbdecimal=".$nbdecimal." dec='".$dec."' thousand='".$thousand."' ";
+
+ //print "amount=".$amount."-";
+ $amount = str_replace(',', '.', $amount); // should be useless
+ //print $amount."-";
+ $datas = explode('.', $amount);
+ $decpart = isset($datas[1])?$datas[1]:'';
+ $decpart = preg_replace('/0+$/i', '', $decpart); // Supprime les 0 de fin de partie decimale
+ //print "decpart=".$decpart." ";
+ $end='';
+
+ // We increase nbdecimal if there is more decimal than asked (to not loose information)
+ if (dol_strlen($decpart) > $nbdecimal) $nbdecimal=dol_strlen($decpart);
+ // Si on depasse max
+ if ($trunc && $nbdecimal > $conf->global->MAIN_MAX_DECIMALS_SHOWN)
+ {
+ $nbdecimal=$conf->global->MAIN_MAX_DECIMALS_SHOWN;
+ if (preg_match('/\.\.\./i', $conf->global->MAIN_MAX_DECIMALS_SHOWN))
+ {
+ // Si un affichage est tronque, on montre des ...
+ $end='...';
+ }
+ }
+
+ // If force rounding
+ if ($forcerounding >= 0) $nbdecimal = $forcerounding;
+
+ // Format number
+ $output=number_format($amount, $nbdecimal, $dec, $thousand);
+ if ($form)
+ {
+ $output=preg_replace('/\s/', ' ', $output);
+ $output=preg_replace('/\'/', ''', $output);
+ }
+ // Add symbol of currency if requested
+ $cursymbolbefore=$cursymbolafter='';
+ if ($currency_code)
+ {
+ if ($currency_code == 'auto') $currency_code=$conf->currency;
+
+ $listofcurrenciesbefore=array('USD','GBP','AUD','MXN','PEN','CNY');
+ $listoflanguagesbefore=array('nl_NL');
+ if (in_array($currency_code, $listofcurrenciesbefore) || in_array($outlangs->defaultlang, $listoflanguagesbefore))
+ {
+ $cursymbolbefore.=$outlangs->getCurrencySymbol($currency_code);
+ }
+ else
+ {
+ $tmpcur=$outlangs->getCurrencySymbol($currency_code);
+ $cursymbolafter.=($tmpcur == $currency_code ? ' '.$tmpcur : $tmpcur);
+ }
+ }
+ $output=$cursymbolbefore.$output.$end.($cursymbolafter?' ':'').$cursymbolafter;
+
+ return $output;
+}
+
+/**
+ * Function that return a number with universal decimal format (decimal separator is '.') from an amount typed by a user.
+ * Function to use on each input amount before any numeric test or database insert
+ *
+ * @param float $amount Amount to convert/clean
+ * @param string $rounding ''=No rounding
+ * 'MU'=Round to Max unit price (MAIN_MAX_DECIMALS_UNIT)
+ * 'MT'=Round to Max for totals with Tax (MAIN_MAX_DECIMALS_TOT)
+ * 'MS'=Round to Max for stock quantity (MAIN_MAX_DECIMALS_STOCK)
+ * Numeric = Nb of digits for rounding
+ * @param int $alreadysqlnb Put 1 if you know that content is already universal format number
+ * @return string Amount with universal numeric format (Example: '99.99999') or unchanged text if conversion fails. If amount is null or '', it returns ''.
+ *
+ * @see price Opposite function of price2num
+ */
+function price2num($amount, $rounding = '', $alreadysqlnb = 0)
+{
+ global $langs,$conf;
+
+ // Round PHP function does not allow number like '1,234.56' nor '1.234,56' nor '1 234,56'
+ // Numbers must be '1234.56'
+ // Decimal delimiter for PHP and database SQL requests must be '.'
+ $dec=','; $thousand=' ';
+ if ($langs->transnoentitiesnoconv("SeparatorDecimal") != "SeparatorDecimal") $dec=$langs->transnoentitiesnoconv("SeparatorDecimal");
+ if ($langs->transnoentitiesnoconv("SeparatorThousand")!= "SeparatorThousand") $thousand=$langs->transnoentitiesnoconv("SeparatorThousand");
+ if ($thousand == 'None') $thousand='';
+ elseif ($thousand == 'Space') $thousand=' ';
+ //print "amount=".$amount." html=".$form." trunc=".$trunc." nbdecimal=".$nbdecimal." dec='".$dec."' thousand='".$thousand."' ";
+
+ // Convert value to universal number format (no thousand separator, '.' as decimal separator)
+ if ($alreadysqlnb != 1) // If not a PHP number or unknown, we change format
+ {
+ //print 'PP'.$amount.' - '.$dec.' - '.$thousand.' - '.intval($amount).' ';
+
+ // Convert amount to format with dolibarr dec and thousand (this is because PHP convert a number
+ // to format defined by LC_NUMERIC after a calculation and we want source format to be like defined by Dolibarr setup.
+ if (is_numeric($amount))
+ {
+ // We put in temps value of decimal ("0.00001"). Works with 0 and 2.0E-5 and 9999.10
+ $temps=sprintf("%0.10F", $amount-intval($amount)); // temps=0.0000000000 or 0.0000200000 or 9999.1000000000
+ $temps=preg_replace('/([\.1-9])0+$/', '\\1', $temps); // temps=0. or 0.00002 or 9999.1
+ $nbofdec=max(0, dol_strlen($temps)-2); // -2 to remove "0."
+ $amount=number_format($amount, $nbofdec, $dec, $thousand);
+ }
+ //print "QQ".$amount.' ';
+
+ // Now make replace (the main goal of function)
+ if ($thousand != ',' && $thousand != '.') $amount=str_replace(',', '.', $amount); // To accept 2 notations for french users
+ $amount=str_replace(' ', '', $amount); // To avoid spaces
+ $amount=str_replace($thousand, '', $amount); // Replace of thousand before replace of dec to avoid pb if thousand is .
+ $amount=str_replace($dec, '.', $amount);
+ }
+
+ // Now, make a rounding if required
+ if ($rounding)
+ {
+ $nbofdectoround='';
+ if ($rounding == 'MU') $nbofdectoround=$conf->global->MAIN_MAX_DECIMALS_UNIT;
+ elseif ($rounding == 'MT') $nbofdectoround=$conf->global->MAIN_MAX_DECIMALS_TOT;
+ elseif ($rounding == 'MS') $nbofdectoround=empty($conf->global->MAIN_MAX_DECIMALS_STOCK)?5:$conf->global->MAIN_MAX_DECIMALS_STOCK;
+ elseif (is_numeric($rounding)) $nbofdectoround=$rounding;
+ //print "RR".$amount.' - '.$nbofdectoround.' ';
+ if (dol_strlen($nbofdectoround)) $amount = round($amount, $nbofdectoround); // $nbofdectoround can be 0.
+ else return 'ErrorBadParameterProvidedToFunction';
+ //print 'SS'.$amount.' - '.$nbofdec.' - '.$dec.' - '.$thousand.' - '.$nbofdectoround.' ';
+
+ // Convert amount to format with dolibarr dec and thousand (this is because PHP convert a number
+ // to format defined by LC_NUMERIC after a calculation and we want source format to be defined by Dolibarr setup.
+ if (is_numeric($amount))
+ {
+ // We put in temps value of decimal ("0.00001"). Works with 0 and 2.0E-5 and 9999.10
+ $temps=sprintf("%0.10F", $amount-intval($amount)); // temps=0.0000000000 or 0.0000200000 or 9999.1000000000
+ $temps=preg_replace('/([\.1-9])0+$/', '\\1', $temps); // temps=0. or 0.00002 or 9999.1
+ $nbofdec=max(0, dol_strlen($temps)-2); // -2 to remove "0."
+ $amount=number_format($amount, min($nbofdec, $nbofdectoround), $dec, $thousand); // Convert amount to format with dolibarr dec and thousand
+ }
+ //print "TT".$amount.' ';
+
+ // Always make replace because each math function (like round) replace
+ // with local values and we want a number that has a SQL string format x.y
+ if ($thousand != ',' && $thousand != '.') $amount=str_replace(',', '.', $amount); // To accept 2 notations for french users
+ $amount=str_replace(' ', '', $amount); // To avoid spaces
+ $amount=str_replace($thousand, '', $amount); // Replace of thousand before replace of dec to avoid pb if thousand is .
+ $amount=str_replace($dec, '.', $amount);
+ }
+
+ return $amount;
+}
+
+
+/**
+ * Output a dimension with best unit
+ *
+ * @param float $dimension Dimension
+ * @param int $unit Unit of dimension (Example: 0=kg, -3=g, 98=ounce, 99=pound, ...)
+ * @param string $type 'weight', 'volume', ...
+ * @param Translate $outputlangs Translate language object
+ * @param int $round -1 = non rounding, x = number of decimal
+ * @param string $forceunitoutput 'no' or numeric (-3, -6, ...) compared to $unit (In most case, this value is value defined into $conf->global->MAIN_WEIGHT_DEFAULT_UNIT)
+ * @return string String to show dimensions
+ */
+function showDimensionInBestUnit($dimension, $unit, $type, $outputlangs, $round = -1, $forceunitoutput = 'no')
+{
+ require_once DOL_DOCUMENT_ROOT.'/core/lib/product.lib.php';
+
+ if (($forceunitoutput == 'no' && $dimension < 1/10000 && $unit < 90) || (is_numeric($forceunitoutput) && $forceunitoutput == -6))
+ {
+ $dimension = $dimension * 1000000;
+ $unit = $unit - 6;
+ }
+ elseif (($forceunitoutput == 'no' && $dimension < 1/10 && $unit < 90) || (is_numeric($forceunitoutput) && $forceunitoutput == -3))
+ {
+ $dimension = $dimension * 1000;
+ $unit = $unit - 3;
+ }
+ elseif (($forceunitoutput == 'no' && $dimension > 100000000 && $unit < 90) || (is_numeric($forceunitoutput) && $forceunitoutput == 6))
+ {
+ $dimension = $dimension / 1000000;
+ $unit = $unit + 6;
+ }
+ elseif (($forceunitoutput == 'no' && $dimension > 100000 && $unit < 90) || (is_numeric($forceunitoutput) && $forceunitoutput == 3))
+ {
+ $dimension = $dimension / 1000;
+ $unit = $unit + 3;
+ }
+ // Special case when we want output unit into pound or ounce
+ /* TODO
+ if ($unit < 90 && $type == 'weight' && is_numeric($forceunitoutput) && (($forceunitoutput == 98) || ($forceunitoutput == 99))
+ {
+ $dimension = // convert dimension from standard unit into ounce or pound
+ $unit = $forceunitoutput;
+ }
+ if ($unit > 90 && $type == 'weight' && is_numeric($forceunitoutput) && $forceunitoutput < 90)
+ {
+ $dimension = // convert dimension from standard unit into ounce or pound
+ $unit = $forceunitoutput;
+ }*/
+
+ $ret=price($dimension, 0, $outputlangs, 0, 0, $round).' '.measuring_units_string($unit, $type);
+
+ return $ret;
+}
+
+
+/**
+ * Return localtax rate for a particular vat, when selling a product with vat $vatrate, from a $thirdparty_buyer to a $thirdparty_seller
+ * Note: This function applies same rules than get_default_tva
+ *
+ * @param float $vatrate Vat rate. Can be '8.5' or '8.5 (VATCODEX)' for example
+ * @param int $local Local tax to search and return (1 or 2 return only tax rate 1 or tax rate 2)
+ * @param Societe $thirdparty_buyer Object of buying third party
+ * @param Societe $thirdparty_seller Object of selling third party ($mysoc if not defined)
+ * @param int $vatnpr If vat rate is NPR or not
+ * @return mixed 0 if not found, localtax rate if found
+ * @see get_default_tva
+ */
+function get_localtax($vatrate, $local, $thirdparty_buyer = "", $thirdparty_seller = "", $vatnpr = 0)
+{
+ global $db, $conf, $mysoc;
+
+ if (empty($thirdparty_seller) || ! is_object($thirdparty_seller)) $thirdparty_seller=$mysoc;
+
+ dol_syslog("get_localtax tva=".$vatrate." local=".$local." thirdparty_buyer id=".(is_object($thirdparty_buyer)?$thirdparty_buyer->id:'')."/country_code=".(is_object($thirdparty_buyer)?$thirdparty_buyer->country_code:'')." thirdparty_seller id=".$thirdparty_seller->id."/country_code=".$thirdparty_seller->country_code." thirdparty_seller localtax1_assuj=".$thirdparty_seller->localtax1_assuj." thirdparty_seller localtax2_assuj=".$thirdparty_seller->localtax2_assuj);
+
+ $vatratecleaned = $vatrate;
+ if (preg_match('/^(.*)\s*\((.*)\)$/', $vatrate, $reg)) // If vat is "xx (yy)"
+ {
+ $vatratecleaned = trim($reg[1]);
+ $vatratecode = $reg[2];
+ }
+
+ /*if ($thirdparty_buyer->country_code != $thirdparty_seller->country_code)
+ {
+ return 0;
+ }*/
+
+ // Some test to guess with no need to make database access
+ if ($mysoc->country_code == 'ES') // For spain localtaxes 1 and 2, tax is qualified if buyer use local tax
+ {
+ if ($local == 1)
+ {
+ if (! $mysoc->localtax1_assuj || (string) $vatratecleaned == "0") return 0;
+ if ($thirdparty_seller->id == $mysoc->id)
+ {
+ if (! $thirdparty_buyer->localtax1_assuj) return 0;
+ }
+ else
+ {
+ if (! $thirdparty_seller->localtax1_assuj) return 0;
+ }
+ }
+
+ if ($local == 2)
+ {
+ //if (! $mysoc->localtax2_assuj || (string) $vatratecleaned == "0") return 0;
+ if (! $mysoc->localtax2_assuj) return 0; // If main vat is 0, IRPF may be different than 0.
+ if ($thirdparty_seller->id == $mysoc->id)
+ {
+ if (! $thirdparty_buyer->localtax2_assuj) return 0;
+ }
+ else
+ {
+ if (! $thirdparty_seller->localtax2_assuj) return 0;
+ }
+ }
+ }
+ else
+ {
+ if ($local == 1 && ! $thirdparty_seller->localtax1_assuj) return 0;
+ if ($local == 2 && ! $thirdparty_seller->localtax2_assuj) return 0;
+ }
+
+ // For some country MAIN_GET_LOCALTAXES_VALUES_FROM_THIRDPARTY is forced to on.
+ if (in_array($mysoc->country_code, array('ES')))
+ {
+ $conf->global->MAIN_GET_LOCALTAXES_VALUES_FROM_THIRDPARTY = 1;
+ }
+
+ // Search local taxes
+ if (! empty($conf->global->MAIN_GET_LOCALTAXES_VALUES_FROM_THIRDPARTY))
+ {
+ if ($local==1)
+ {
+ if ($thirdparty_seller != $mysoc)
+ {
+ if (!isOnlyOneLocalTax($local)) // TODO We should provide $vatrate to search on correct line and not always on line with highest vat rate
+ {
+ return $thirdparty_seller->localtax1_value;
+ }
+ }
+ else // i am the seller
+ {
+ if (!isOnlyOneLocalTax($local)) // TODO If seller is me, why not always returning this, even if there is only one locatax vat.
+ {
+ return $conf->global->MAIN_INFO_VALUE_LOCALTAX1;
+ }
+ }
+ }
+ if ($local==2)
+ {
+ if ($thirdparty_seller != $mysoc)
+ {
+ if (!isOnlyOneLocalTax($local)) // TODO We should provide $vatrate to search on correct line and not always on line with highest vat rate
+ // TODO We should also return value defined on thirdparty only if defined
+ {
+ return $thirdparty_seller->localtax2_value;
+ }
+ }
+ else // i am the seller
+ {
+ if (in_array($mysoc->country_code, array('ES')))
+ {
+ return $thirdparty_buyer->localtax2_value;
+ }
+ else
+ {
+ return $conf->global->MAIN_INFO_VALUE_LOCALTAX2;
+ }
+ }
+ }
+ }
+
+ // By default, search value of local tax on line of common tax
+ $sql = "SELECT t.localtax1, t.localtax2, t.localtax1_type, t.localtax2_type";
+ $sql .= " FROM ".MAIN_DB_PREFIX."c_tva as t, ".MAIN_DB_PREFIX."c_country as c";
+ $sql .= " WHERE t.fk_pays = c.rowid AND c.code = '".$thirdparty_seller->country_code."'";
+ $sql .= " AND t.taux = ".((float) $vatratecleaned)." AND t.active = 1";
+ if ($vatratecode) $sql.= " AND t.code ='".$vatratecode."'"; // If we have the code, we use it in priority
+ else $sql.= " AND t.recuperableonly ='".$vatnpr."'";
+ dol_syslog("get_localtax", LOG_DEBUG);
+ $resql=$db->query($sql);
+
+ if ($resql)
+ {
+ $obj = $db->fetch_object($resql);
+ if ($local==1) return $obj->localtax1;
+ elseif ($local==2) return $obj->localtax2;
+ }
+
+ return 0;
+}
+
+
+/**
+ * Return true if LocalTax (1 or 2) is unique.
+ * Example: If localtax1 is 5 on line with highest common vat rate, return true
+ * Example: If localtax1 is 5:8:15 on line with highest common vat rate, return false
+ *
+ * @param int $local Local tax to test (1 or 2)
+ * @return boolean True if LocalTax have multiple values, False if not
+ */
+function isOnlyOneLocalTax($local)
+{
+ $tax=get_localtax_by_third($local);
+
+ $valors=explode(":", $tax);
+
+ if (count($valors)>1)
+ {
+ return false;
+ }
+ else
+ {
+ return true;
+ }
+}
+
+/**
+ * Get values of localtaxes (1 or 2) for company country for the common vat with the highest value
+ *
+ * @param int $local LocalTax to get
+ * @return number Values of localtax
+ */
+function get_localtax_by_third($local)
+{
+ global $db, $mysoc;
+ $sql ="SELECT t.localtax1, t.localtax2 ";
+ $sql.=" FROM ".MAIN_DB_PREFIX."c_tva as t inner join ".MAIN_DB_PREFIX."c_country as c ON c.rowid=t.fk_pays";
+ $sql.=" WHERE c.code = '".$mysoc->country_code."' AND t.active = 1 AND t.taux=(";
+ $sql.=" SELECT max(tt.taux) FROM ".MAIN_DB_PREFIX."c_tva as tt inner join ".MAIN_DB_PREFIX."c_country as c ON c.rowid=tt.fk_pays";
+ $sql.=" WHERE c.code = '".$mysoc->country_code."' AND tt.active = 1";
+ $sql.=" )";
+
+ $resql=$db->query($sql);
+ if ($resql)
+ {
+ $obj = $db->fetch_object($resql);
+ if ($local==1) return $obj->localtax1;
+ elseif ($local==2) return $obj->localtax2;
+ }
+
+ return 0;
+}
+
+
+/**
+ * Get vat main information from Id.
+ * You can call getLocalTaxesFromRate after to get other fields.
+ *
+ * @param int|string $vatrate VAT ID or Rate. Value can be value or the string with code into parenthesis or rowid if $firstparamisid is 1. Example: '8.5' or '8.5 (8.5NPR)' or 123.
+ * @param Societe $buyer Company object
+ * @param Societe $seller Company object
+ * @param int $firstparamisid 1 if first param is id into table (use this if you can)
+ * @return array array('rowid'=> , 'code'=> ...)
+ * @see getLocalTaxesFromRate
+ */
+function getTaxesFromId($vatrate, $buyer = null, $seller = null, $firstparamisid = 1)
+{
+ global $db, $mysoc;
+
+ dol_syslog("getTaxesFromId vat id or rate = ".$vatrate);
+
+ // Search local taxes
+ $sql = "SELECT t.rowid, t.code, t.taux as rate, t.recuperableonly as npr, t.accountancy_code_sell, t.accountancy_code_buy";
+ $sql.= " FROM ".MAIN_DB_PREFIX."c_tva as t";
+ if ($firstparamisid) $sql.= " WHERE t.rowid = ".(int) $vatrate;
+ else
+ {
+ $vatratecleaned = $vatrate;
+ $vatratecode = '';
+ if (preg_match('/^(.*)\s*\((.*)\)$/', $vatrate, $reg)) // If vat is "xx (yy)"
+ {
+ $vatratecleaned = $reg[1];
+ $vatratecode = $reg[2];
+ }
+
+ $sql.=", ".MAIN_DB_PREFIX."c_country as c";
+ /*if ($mysoc->country_code == 'ES') $sql.= " WHERE t.fk_pays = c.rowid AND c.code = '".$buyer->country_code."'"; // vat in spain use the buyer country ??
+ else $sql.= " WHERE t.fk_pays = c.rowid AND c.code = '".$seller->country_code."'";*/
+ $sql.= " WHERE t.fk_pays = c.rowid AND c.code = '".$seller->country_code."'";
+ $sql.= " AND t.taux = ".((float) $vatratecleaned)." AND t.active = 1";
+ if ($vatratecode) $sql.= " AND t.code = '".$vatratecode."'";
+ }
+
+ $resql=$db->query($sql);
+ if ($resql)
+ {
+ $obj = $db->fetch_object($resql);
+ if ($obj) return array('rowid'=>$obj->rowid, 'code'=>$obj->code, 'rate'=>$obj->rate, 'npr'=>$obj->npr, 'accountancy_code_sell'=>$obj->accountancy_code_sell, 'accountancy_code_buy'=>$obj->accountancy_code_buy);
+ else return array();
+ }
+ else dol_print_error($db);
+
+ return array();
+}
+
+/**
+ * Get type and rate of localtaxes for a particular vat rate/country of a thirdparty.
+ * This does not take into account the seller setup if subject to vat or not, only country.
+ * TODO
+ * This function is ALSO called to retrieve type for building PDF. Such call of function must be removed.
+ * Instead this function must be called when adding a line to get the array of localtax and type, and then
+ * provide it to the function calcul_price_total.
+ *
+ * @param int|string $vatrate VAT ID or Rate+Code. Value can be value or the string with code into parenthesis or rowid if $firstparamisid is 1. Example: '8.5' or '8.5 (8.5NPR)' or 123.
+ * @param int $local Number of localtax (1 or 2, or 0 to return 1 & 2)
+ * @param Societe $buyer Company object
+ * @param Societe $seller Company object
+ * @param int $firstparamisid 1 if first param is ID into table instead of Rate+code (use this if you can)
+ * @return array array(localtax_type1(1-6/0 if not found), rate localtax1, localtax_type2, rate localtax2, accountancycodecust, accountancycodesupp)
+ * @see getTaxesFromId
+ */
+function getLocalTaxesFromRate($vatrate, $local, $buyer, $seller, $firstparamisid = 0)
+{
+ global $db, $mysoc;
+
+ dol_syslog("getLocalTaxesFromRate vatrate=".$vatrate." local=".$local);
+
+ // Search local taxes
+ $sql = "SELECT t.localtax1, t.localtax1_type, t.localtax2, t.localtax2_type, t.accountancy_code_sell, t.accountancy_code_buy";
+ $sql .= " FROM ".MAIN_DB_PREFIX."c_tva as t";
+ if ($firstparamisid) $sql.= " WHERE t.rowid = ".(int) $vatrate;
+ else
+ {
+ $vatratecleaned = $vatrate;
+ $vatratecode = '';
+ if (preg_match('/^(.*)\s*\((.*)\)$/', $vatrate, $reg)) // If vat is "x.x (yy)"
+ {
+ $vatratecleaned = $reg[1];
+ $vatratecode = $reg[2];
+ }
+
+ $sql.=", ".MAIN_DB_PREFIX."c_country as c";
+ if ($mysoc->country_code == 'ES') $sql .= " WHERE t.fk_pays = c.rowid AND c.code = '".$buyer->country_code."'"; // local tax in spain use the buyer country ??
+ else $sql .= " WHERE t.fk_pays = c.rowid AND c.code = '".$seller->country_code."'";
+ $sql.= " AND t.taux = ".((float) $vatratecleaned)." AND t.active = 1";
+ if ($vatratecode) $sql.= " AND t.code = '".$vatratecode."'";
+ }
+
+ $resql=$db->query($sql);
+ if ($resql)
+ {
+ $obj = $db->fetch_object($resql);
+ if ($local == 1)
+ {
+ return array($obj->localtax1_type, get_localtax($vatrate, $local, $buyer, $seller), $obj->accountancy_code_sell, $obj->accountancy_code_buy);
+ }
+ elseif ($local == 2)
+ {
+ return array($obj->localtax2_type, get_localtax($vatrate, $local, $buyer, $seller),$obj->accountancy_code_sell, $obj->accountancy_code_buy);
+ }
+ else
+ {
+ return array($obj->localtax1_type, get_localtax($vatrate, 1, $buyer, $seller), $obj->localtax2_type, get_localtax($vatrate, 2, $buyer, $seller), $obj->accountancy_code_sell,$obj->accountancy_code_buy);
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * Return vat rate of a product in a particular selling country or default country vat if product is unknown
+ * Function called by get_default_tva
+ *
+ * @param int $idprod Id of product or 0 if not a predefined product
+ * @param Societe $thirdparty_seller Thirdparty with a ->country_code defined (FR, US, IT, ...)
+ * @param int $idprodfournprice Id product_fournisseur_price (for "supplier" proposal/order/invoice)
+ * @return float|string Vat rate to use with format 5.0 or '5.0 (XXX)'
+ * @see get_product_localtax_for_country
+ */
+function get_product_vat_for_country($idprod, $thirdparty_seller, $idprodfournprice = 0)
+{
+ global $db,$conf,$mysoc;
+
+ require_once DOL_DOCUMENT_ROOT . '/product/class/product.class.php';
+
+ $ret=0;
+ $found=0;
+
+ if ($idprod > 0)
+ {
+ // Load product
+ $product=new Product($db);
+ $result=$product->fetch($idprod);
+
+ if ($mysoc->country_code == $thirdparty_seller->country_code) // If selling country is ours
+ {
+ if ($idprodfournprice > 0) // We want vat for product for a "supplier" object
+ {
+ $product->get_buyprice($idprodfournprice, 0, 0, 0);
+ $ret=$product->vatrate_supplier;
+ if ($product->default_vat_code) $ret.=' ('.$product->default_vat_code.')';
+ }
+ else
+ {
+ $ret=$product->tva_tx; // Default vat of product we defined
+ if ($product->default_vat_code) $ret.=' ('.$product->default_vat_code.')';
+ }
+ $found=1;
+ }
+ else
+ {
+ // TODO Read default product vat according to countrycode and product. Vat for couple countrycode/product is a feature not implemeted yet.
+ // May be usefull/required if hidden option SERVICE_ARE_ECOMMERCE_200238EC is on
+ }
+ }
+
+ if (! $found)
+ {
+ if (empty($conf->global->MAIN_VAT_DEFAULT_IF_AUTODETECT_FAILS))
+ {
+ // If vat of product for the country not found or not defined, we return the first higher vat of country.
+ $sql = "SELECT t.taux as vat_rate, t.code as default_vat_code";
+ $sql.= " FROM ".MAIN_DB_PREFIX."c_tva as t, ".MAIN_DB_PREFIX."c_country as c";
+ $sql.= " WHERE t.active=1 AND t.fk_pays = c.rowid AND c.code='".$thirdparty_seller->country_code."'";
+ $sql.= " ORDER BY t.taux DESC, t.code ASC, t.recuperableonly ASC";
+ $sql.= $db->plimit(1);
+
+ $resql=$db->query($sql);
+ if ($resql)
+ {
+ $obj=$db->fetch_object($resql);
+ if ($obj)
+ {
+ $ret=$obj->vat_rate;
+ if ($obj->default_vat_code) $ret.=' ('.$obj->default_vat_code.')';
+ }
+ $db->free($sql);
+ }
+ else dol_print_error($db);
+ }
+ else $ret=$conf->global->MAIN_VAT_DEFAULT_IF_AUTODETECT_FAILS; // Forced value if autodetect fails
+ }
+
+ dol_syslog("get_product_vat_for_country: ret=".$ret);
+ return $ret;
+}
+
+/**
+ * Return localtax vat rate of a product in a particular selling country or default country vat if product is unknown
+ *
+ * @param int $idprod Id of product
+ * @param int $local 1 for localtax1, 2 for localtax 2
+ * @param Societe $thirdparty_seller Thirdparty with a ->country_code defined (FR, US, IT, ...)
+ * @return int <0 if KO, Vat rate if OK
+ * @see get_product_vat_for_country
+ */
+function get_product_localtax_for_country($idprod, $local, $thirdparty_seller)
+{
+ global $db,$mysoc;
+
+ if (! class_exists('Product')) {
+ require_once DOL_DOCUMENT_ROOT . '/product/class/product.class.php';
+ }
+
+ $ret=0;
+ $found=0;
+
+ if ($idprod > 0)
+ {
+ // Load product
+ $product=new Product($db);
+ $result=$product->fetch($idprod);
+
+ if ($mysoc->country_code == $thirdparty_seller->country_code) // If selling country is ours
+ {
+ /* Not defined yet, so we don't use this
+ if ($local==1) $ret=$product->localtax1_tx;
+ elseif ($local==2) $ret=$product->localtax2_tx;
+ $found=1;
+ */
+ }
+ else
+ {
+ // TODO Read default product vat according to countrycode and product
+ }
+ }
+
+ if (! $found)
+ {
+ // If vat of product for the country not found or not defined, we return higher vat of country.
+ $sql = "SELECT taux as vat_rate, localtax1, localtax2";
+ $sql.= " FROM ".MAIN_DB_PREFIX."c_tva as t, ".MAIN_DB_PREFIX."c_country as c";
+ $sql.= " WHERE t.active=1 AND t.fk_pays = c.rowid AND c.code='".$thirdparty_seller->country_code."'";
+ $sql.= " ORDER BY t.taux DESC, t.recuperableonly ASC";
+ $sql.= $db->plimit(1);
+
+ $resql=$db->query($sql);
+ if ($resql)
+ {
+ $obj=$db->fetch_object($resql);
+ if ($obj)
+ {
+ if ($local==1) $ret=$obj->localtax1;
+ elseif ($local==2) $ret=$obj->localtax2;
+ }
+ }
+ else dol_print_error($db);
+ }
+
+ dol_syslog("get_product_localtax_for_country: ret=".$ret);
+ return $ret;
+}
+
+/**
+ * Function that return vat rate of a product line (according to seller, buyer and product vat rate)
+ * Si vendeur non assujeti a TVA, TVA par defaut=0. Fin de regle.
+ * Si le (pays vendeur = pays acheteur) alors TVA par defaut=TVA du produit vendu. Fin de regle.
+ * Si (vendeur et acheteur dans Communaute europeenne) et (bien vendu = moyen de transports neuf comme auto, bateau, avion) alors TVA par defaut=0 (La TVA doit etre paye par acheteur au centre d'impots de son pays et non au vendeur). Fin de regle.
+ * Si (vendeur et acheteur dans Communaute europeenne) et (acheteur = particulier ou entreprise sans num TVA intra) alors TVA par defaut=TVA du produit vendu. Fin de regle
+ * Si (vendeur et acheteur dans Communaute europeenne) et (acheteur = entreprise avec num TVA) intra alors TVA par defaut=0. Fin de regle
+ * Sinon TVA proposee par defaut=0. Fin de regle.
+ *
+ * @param Societe $thirdparty_seller Objet societe vendeuse
+ * @param Societe $thirdparty_buyer Objet societe acheteuse
+ * @param int $idprod Id product
+ * @param int $idprodfournprice Id product_fournisseur_price (for supplier order/invoice)
+ * @return float|string Vat rate to use with format 5.0 or '5.0 (XXX)', -1 if we can't guess it
+ * @see get_default_npr, get_default_localtax
+ */
+function get_default_tva(Societe $thirdparty_seller, Societe $thirdparty_buyer, $idprod = 0, $idprodfournprice = 0)
+{
+ global $conf;
+
+ require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php';
+
+ // Note: possible values for tva_assuj are 0/1 or franchise/reel
+ $seller_use_vat=((is_numeric($thirdparty_seller->tva_assuj) && ! $thirdparty_seller->tva_assuj) || (! is_numeric($thirdparty_seller->tva_assuj) && $thirdparty_seller->tva_assuj=='franchise'))?0:1;
+
+ $seller_country_code = $thirdparty_seller->country_code;
+ $seller_in_cee = isInEEC($thirdparty_seller);
+
+ $buyer_country_code = $thirdparty_buyer->country_code;
+ $buyer_in_cee = isInEEC($thirdparty_buyer);
+
+ dol_syslog("get_default_tva: seller use vat=".$seller_use_vat.", seller country=".$seller_country_code.", seller in cee=".$seller_in_cee.", buyer vat number=".$thirdparty_buyer->tva_intra." buyer country=".$buyer_country_code.", buyer in cee=".$buyer_in_cee.", idprod=".$idprod.", idprodfournprice=".$idprodfournprice.", SERVICE_ARE_ECOMMERCE_200238EC=".(! empty($conf->global->SERVICES_ARE_ECOMMERCE_200238EC)?$conf->global->SERVICES_ARE_ECOMMERCE_200238EC:''));
+
+ // If services are eServices according to EU Council Directive 2002/38/EC (http://ec.europa.eu/taxation_customs/taxation/vat/traders/e-commerce/article_1610_en.htm)
+ // we use the buyer VAT.
+ if (! empty($conf->global->SERVICE_ARE_ECOMMERCE_200238EC))
+ {
+ if ($seller_in_cee && $buyer_in_cee && ! $thirdparty_buyer->isACompany())
+ {
+ //print 'VATRULE 0';
+ return get_product_vat_for_country($idprod, $thirdparty_buyer, $idprodfournprice);
+ }
+ }
+
+ // If seller does not use VAT
+ if (! $seller_use_vat)
+ {
+ //print 'VATRULE 1';
+ return 0;
+ }
+
+ // Le test ci-dessus ne devrait pas etre necessaire. Me signaler l'exemple du cas juridique concerne si le test suivant n'est pas suffisant.
+
+ // Si le (pays vendeur = pays acheteur) alors la TVA par defaut=TVA du produit vendu. Fin de regle.
+ if (($seller_country_code == $buyer_country_code)
+ || (in_array($seller_country_code, array('FR,MC')) && in_array($buyer_country_code, array('FR','MC')))) // Warning ->country_code not always defined
+ {
+ //print 'VATRULE 2';
+ return get_product_vat_for_country($idprod, $thirdparty_seller, $idprodfournprice);
+ }
+
+ // Si (vendeur et acheteur dans Communaute europeenne) et (bien vendu = moyen de transports neuf comme auto, bateau, avion) alors TVA par defaut=0 (La TVA doit etre paye par l'acheteur au centre d'impots de son pays et non au vendeur). Fin de regle.
+ // Not supported
+
+ // Si (vendeur et acheteur dans Communaute europeenne) et (acheteur = entreprise) alors TVA par defaut=0. Fin de regle
+ // Si (vendeur et acheteur dans Communaute europeenne) et (acheteur = particulier) alors TVA par defaut=TVA du produit vendu. Fin de regle
+ if (($seller_in_cee && $buyer_in_cee))
+ {
+ $isacompany=$thirdparty_buyer->isACompany();
+ if ($isacompany)
+ {
+ //print 'VATRULE 3';
+ return 0;
+ }
+ else
+ {
+ //print 'VATRULE 4';
+ return get_product_vat_for_country($idprod, $thirdparty_seller, $idprodfournprice);
+ }
+ }
+
+ // Si (vendeur en France et acheteur hors Communaute europeenne et acheteur particulier) alors TVA par defaut=TVA du produit vendu. Fin de regle
+ if (! empty($conf->global->MAIN_USE_VAT_OF_PRODUCT_FOR_INDIVIDUAL_CUSTOMER_OUT_OF_EEC) && empty($buyer_in_cee) && !$thirdparty_buyer->isACompany()) {
+ return get_product_vat_for_country($idprod, $thirdparty_seller, $idprodfournprice);
+ }
+
+ // Sinon la TVA proposee par defaut=0. Fin de regle.
+ // Rem: Cela signifie qu'au moins un des 2 est hors Communaute europeenne et que le pays differe
+ //print 'VATRULE 5';
+ return 0;
+}
+
+
+/**
+ * Fonction qui renvoie si tva doit etre tva percue recuperable
+ *
+ * @param Societe $thirdparty_seller Thirdparty seller
+ * @param Societe $thirdparty_buyer Thirdparty buyer
+ * @param int $idprod Id product
+ * @param int $idprodfournprice Id supplier price for product
+ * @return float 0 or 1
+ * @see get_default_tva, get_default_localtax
+ */
+function get_default_npr(Societe $thirdparty_seller, Societe $thirdparty_buyer, $idprod = 0, $idprodfournprice = 0)
+{
+ global $db;
+
+ if ($idprodfournprice > 0)
+ {
+ if (! class_exists('ProductFournisseur'))
+ require_once DOL_DOCUMENT_ROOT . '/fourn/class/fournisseur.product.class.php';
+ $prodprice = new ProductFournisseur($db);
+ $prodprice->fetch_product_fournisseur_price($idprodfournprice);
+ return $prodprice->fourn_tva_npr;
+ }
+ elseif ($idprod > 0)
+ {
+ if (! class_exists('Product'))
+ require_once DOL_DOCUMENT_ROOT . '/product/class/product.class.php';
+ $prod = new Product($db);
+ $prod->fetch($idprod);
+ return $prod->tva_npr;
+ }
+
+ return 0;
+}
+
+/**
+ * Function that return localtax of a product line (according to seller, buyer and product vat rate)
+ * Si vendeur non assujeti a TVA, TVA par defaut=0. Fin de regle.
+ * Si le (pays vendeur = pays acheteur) alors TVA par defaut=TVA du produit vendu. Fin de regle.
+ * Sinon TVA proposee par defaut=0. Fin de regle.
+ *
+ * @param Societe $thirdparty_seller Thirdparty seller
+ * @param Societe $thirdparty_buyer Thirdparty buyer
+ * @param int $local Localtax to process (1 or 2)
+ * @param int $idprod Id product
+ * @return integer localtax, -1 si ne peut etre determine
+ * @see get_default_tva, get_default_npr
+ */
+function get_default_localtax($thirdparty_seller, $thirdparty_buyer, $local, $idprod = 0)
+{
+ global $mysoc;
+
+ if (!is_object($thirdparty_seller)) return -1;
+ if (!is_object($thirdparty_buyer)) return -1;
+
+ if ($local==1) // Localtax 1
+ {
+ if ($mysoc->country_code == 'ES')
+ {
+ if (is_numeric($thirdparty_buyer->localtax1_assuj) && ! $thirdparty_buyer->localtax1_assuj) return 0;
+ }
+ else
+ {
+ // Si vendeur non assujeti a Localtax1, localtax1 par default=0
+ if (is_numeric($thirdparty_seller->localtax1_assuj) && ! $thirdparty_seller->localtax1_assuj) return 0;
+ if (! is_numeric($thirdparty_seller->localtax1_assuj) && $thirdparty_seller->localtax1_assuj=='localtax1off') return 0;
+ }
+ }
+ elseif ($local==2) //I Localtax 2
+ {
+ // Si vendeur non assujeti a Localtax2, localtax2 par default=0
+ if (is_numeric($thirdparty_seller->localtax2_assuj) && ! $thirdparty_seller->localtax2_assuj) return 0;
+ if (! is_numeric($thirdparty_seller->localtax2_assuj) && $thirdparty_seller->localtax2_assuj=='localtax2off') return 0;
+ }
+
+ if ($thirdparty_seller->country_code == $thirdparty_buyer->country_code)
+ {
+ return get_product_localtax_for_country($idprod, $local, $thirdparty_seller);
+ }
+
+ return 0;
+}
+
+/**
+ * Return yes or no in current language
+ *
+ * @param string $yesno Value to test (1, 'yes', 'true' or 0, 'no', 'false')
+ * @param integer $case 1=Yes/No, 0=yes/no, 2=Disabled checkbox, 3=Disabled checkbox + Yes/No
+ * @param int $color 0=texte only, 1=Text is formated with a color font style ('ok' or 'error'), 2=Text is formated with 'ok' color.
+ * @return string HTML string
+ */
+function yn($yesno, $case = 1, $color = 0)
+{
+ global $langs;
+ $result='unknown'; $classname='';
+ if ($yesno == 1 || strtolower($yesno) == 'yes' || strtolower($yesno) == 'true') // A mettre avant test sur no a cause du == 0
+ {
+ $result=$langs->trans('yes');
+ if ($case == 1 || $case == 3) $result=$langs->trans("Yes");
+ if ($case == 2) $result='';
+ if ($case == 3) $result=' '.$result;
+
+ $classname='ok';
+ }
+ elseif ($yesno == 0 || strtolower($yesno) == 'no' || strtolower($yesno) == 'false')
+ {
+ $result=$langs->trans("no");
+ if ($case == 1 || $case == 3) $result=$langs->trans("No");
+ if ($case == 2) $result='';
+ if ($case == 3) $result=' '.$result;
+
+ if ($color == 2) $classname='ok';
+ else $classname='error';
+ }
+ if ($color) return ''.$result.'';
+ return $result;
+}
+
+
+/**
+ * Return a path to have a the directory according to object where files are stored.
+ * New usage: $conf->module->multidir_output[$object->entity].'/'.get_exdir(0, 0, 0, 1, $object, $modulepart)
+ * or: $conf->module->dir_output.'/'.get_exdir(0, 0, 0, 1, $object, $modulepart) if multidir_output not defined.
+ * Example our with new usage: $object is invoice -> 'INYYMM-ABCD'
+ * Example our with old usage: '015' with level 3->"0/1/5/", '015' with level 1->"5/", 'ABC-1' with level 3 ->"0/0/1/"
+ *
+ * @param string $num Id of object (deprecated, $object will be used in future)
+ * @param int $level Level of subdirs to return (1, 2 or 3 levels). (deprecated, global option will be used in future)
+ * @param int $alpha 0=Keep number only to forge path, 1=Use alpha part afer the - (By default, use 0). (deprecated, global option will be used in future)
+ * @param int $withoutslash 0=With slash at end (except if '/', we return ''), 1=without slash at end
+ * @param Object $object Object
+ * @param string $modulepart Type of object ('invoice_supplier, 'donation', 'invoice', ...')
+ * @return string Dir to use ending. Example '' or '1/' or '1/2/'
+ */
+function get_exdir($num, $level, $alpha, $withoutslash, $object, $modulepart)
+{
+ global $conf;
+
+ $path = '';
+
+ $arrayforoldpath=array('cheque','user','category','holiday','supplier_invoice','invoice_supplier','mailing','supplier_payment');
+ if (! empty($conf->global->PRODUCT_USE_OLD_PATH_FOR_PHOTO)) $arrayforoldpath[]='product';
+ if (! empty($level) && in_array($modulepart, $arrayforoldpath))
+ {
+ // This part should be removed once all code is using "get_exdir" to forge path, with all parameters provided.
+ if (empty($alpha)) $num = preg_replace('/([^0-9])/i', '', $num);
+ else $num = preg_replace('/^.*\-/i', '', $num);
+ $num = substr("000".$num, -$level);
+ if ($level == 1) $path = substr($num, 0, 1);
+ if ($level == 2) $path = substr($num, 1, 1).'/'.substr($num, 0, 1);
+ if ($level == 3) $path = substr($num, 2, 1).'/'.substr($num, 1, 1).'/'.substr($num, 0, 1);
+ }
+ else
+ {
+ // TODO
+ // We will enhance here a common way of forging path for document storage
+ // Here, object->id, object->ref and modulepart are required.
+ //var_dump($modulepart);
+ if (in_array($modulepart, array('thirdparty','contact','member','propal','proposal','commande','order','facture','invoice',
+ 'supplier_order','supplier_proposal','shipment','contract','expensereport')))
+ {
+ $path=($object->ref?$object->ref:$object->id);
+ }
+ }
+
+ if (empty($withoutslash) && ! empty($path)) $path.='/';
+
+ return $path;
+}
+
+/**
+ * Creation of a directory (this can create recursive subdir)
+ *
+ * @param string $dir Directory to create (Separator must be '/'. Example: '/mydir/mysubdir')
+ * @param string $dataroot Data root directory (To avoid having the data root in the loop. Using this will also lost the warning on first dir PHP has no permission when open_basedir is used)
+ * @param int $newmask Mask for new file (Defaults to $conf->global->MAIN_UMASK or 0755 if unavailable). Example: '0444'
+ * @return int < 0 if KO, 0 = already exists, > 0 if OK
+ */
+function dol_mkdir($dir, $dataroot = '', $newmask = null)
+{
+ global $conf;
+
+ dol_syslog("functions.lib::dol_mkdir: dir=".$dir, LOG_INFO);
+
+ $dir_osencoded=dol_osencode($dir);
+ if (@is_dir($dir_osencoded)) return 0;
+
+ $nberr=0;
+ $nbcreated=0;
+
+ $ccdir='';
+ if (! empty($dataroot)) {
+ // Remove data root from loop
+ $dir = str_replace($dataroot.'/', '', $dir);
+ $ccdir = $dataroot.'/';
+ }
+
+ $cdir = explode("/", $dir);
+ $num=count($cdir);
+ for ($i = 0; $i < $num; $i++)
+ {
+ if ($i > 0) $ccdir .= '/'.$cdir[$i];
+ else $ccdir .= $cdir[$i];
+ if (preg_match("/^.:$/", $ccdir, $regs)) continue; // Si chemin Windows incomplet, on poursuit par rep suivant
+
+ // Attention, le is_dir() peut echouer bien que le rep existe.
+ // (ex selon config de open_basedir)
+ if ($ccdir)
+ {
+ $ccdir_osencoded=dol_osencode($ccdir);
+ if (! @is_dir($ccdir_osencoded))
+ {
+ dol_syslog("functions.lib::dol_mkdir: Directory '".$ccdir."' does not exists or is outside open_basedir PHP setting.", LOG_DEBUG);
+
+ umask(0);
+ $dirmaskdec=octdec($newmask);
+ if (empty($newmask)) {
+ $dirmaskdec = empty( $conf->global->MAIN_UMASK ) ? octdec( '0755' ) : octdec( $conf->global->MAIN_UMASK );
+ }
+ $dirmaskdec |= octdec('0111'); // Set x bit required for directories
+ if (! @mkdir($ccdir_osencoded, $dirmaskdec))
+ {
+ // Si le is_dir a renvoye une fausse info, alors on passe ici.
+ dol_syslog("functions.lib::dol_mkdir: Fails to create directory '".$ccdir."' or directory already exists.", LOG_WARNING);
+ $nberr++;
+ }
+ else
+ {
+ dol_syslog("functions.lib::dol_mkdir: Directory '".$ccdir."' created", LOG_DEBUG);
+ $nberr=0; // On remet a zero car si on arrive ici, cela veut dire que les echecs precedents peuvent etre ignore
+ $nbcreated++;
+ }
+ }
+ else
+ {
+ $nberr=0; // On remet a zero car si on arrive ici, cela veut dire que les echecs precedents peuvent etre ignores
+ }
+ }
+ }
+ return ($nberr ? -$nberr : $nbcreated);
+}
+
+
+/**
+ * Return picto saying a field is required
+ *
+ * @return string Chaine avec picto obligatoire
+ */
+function picto_required()
+{
+ return '*';
+}
+
+
+/**
+ * Clean a string from all HTML tags and entities.
+ * This function differs from strip_tags because:
+ * - are replaced with \n if removelinefeed=0 or 1
+ * - if entities are found, they are decoded BEFORE the strip
+ * - you can decide to convert line feed into a space
+ *
+ * @param string $stringtoclean String to clean
+ * @param integer $removelinefeed 1=Replace all new lines by 1 space, 0=Only ending new lines are removed others are replaced with \n, 2=Ending new lines are removed but others are kept with a same number of \n than nb of when there is both "... \n..."
+ * @param string $pagecodeto Encoding of input/output string
+ * @param integer $strip_tags 0=Use internal strip, 1=Use strip_tags() php function (bugged when text contains a < char that is not for a html tag)
+ * @return string String cleaned
+ *
+ * @see dol_escape_htmltag strip_tags dol_string_onlythesehtmltags dol_string_neverthesehtmltags
+ */
+function dol_string_nohtmltag($stringtoclean, $removelinefeed = 1, $pagecodeto = 'UTF-8', $strip_tags = 0)
+{
+ if ($removelinefeed == 2) $stringtoclean = preg_replace('/ ]*>\n+/ims', ' ', $stringtoclean);
+ $temp = preg_replace('/ ]*>/i', "\n", $stringtoclean);
+
+ if ($strip_tags) {
+ $temp = strip_tags($temp);
+ } else {
+ $pattern = "/<[^<>]+>/";
+ // Exemple of $temp: 0000-021
+ $temp = preg_replace($pattern, "", $temp); // pass 1
+ // $temp after pass 1: 0000-021
+ $temp = preg_replace($pattern, "", $temp); // pass 2
+ // $temp after pass 2: 0000-021
+ }
+
+ $temp = dol_html_entity_decode($temp, ENT_COMPAT, $pagecodeto);
+
+ // Supprime aussi les retours
+ if ($removelinefeed == 1) $temp=str_replace(array("\r\n","\r","\n"), " ", $temp);
+
+ // et les espaces doubles
+ while (strpos($temp, " "))
+ {
+ $temp = str_replace(" ", " ", $temp);
+ }
+
+ return trim($temp);
+}
+
+/**
+ * Clean a string to keep only desirable HTML tags.
+ *
+ * @param string $stringtoclean String to clean
+ * @return string String cleaned
+ *
+ * @see dol_escape_htmltag strip_tags dol_string_nohtmltag dol_string_neverthesehtmltags
+ */
+function dol_string_onlythesehtmltags($stringtoclean)
+{
+ $allowed_tags = array(
+ "html", "head", "meta", "body", "article", "a", "b", "br", "div", "em", "font", "img", "ins", "hr", "i", "li", "link",
+ "ol", "p", "s", "section", "span", "strong", "title",
+ "table", "tr", "th", "td", "u", "ul"
+ );
+
+ $allowed_tags_string = join("><", $allowed_tags);
+ $allowed_tags_string = preg_replace('/^>/', '', $allowed_tags_string);
+ $allowed_tags_string = preg_replace('/<$/', '', $allowed_tags_string);
+
+ $temp = strip_tags($stringtoclean, $allowed_tags_string);
+
+ return $temp;
+}
+
+/**
+ * Clean a string from some undesirable HTML tags.
+ *
+ * @param string $stringtoclean String to clean
+ * @param array $disallowed_tags Array of tags not allowed
+ * @return string String cleaned
+ *
+ * @see dol_escape_htmltag strip_tags dol_string_nohtmltag dol_string_onlythesehtmltags
+ */
+function dol_string_neverthesehtmltags($stringtoclean, $disallowed_tags = array('textarea'))
+{
+ $temp = $stringtoclean;
+ foreach($disallowed_tags as $tagtoremove)
+ {
+ $temp = preg_replace('/<\/?'.$tagtoremove.'>/', '', $temp);
+ $temp = preg_replace('/<\/?'.$tagtoremove.'\s+[^>]*>/', '', $temp);
+ }
+ return $temp;
+}
+
+
+/**
+ * Return first line of text. Cut will depends if content is HTML or not.
+ *
+ * @param string $text Input text
+ * @param int $nboflines Nb of lines to get (default is 1 = first line only)
+ * @return string Output text
+ * @see dol_nboflines_bis, dol_string_nohtmltag, dol_escape_htmltag
+ */
+function dolGetFirstLineOfText($text, $nboflines = 1)
+{
+ if ($nboflines == 1)
+ {
+ if (dol_textishtml($text))
+ {
+ $firstline=preg_replace('/ ]*>.*$/s', '', $text); // The s pattern modifier means the . can match newline characters
+ $firstline=preg_replace('/
]*>.*$/s', '', $firstline); // The s pattern modifier means the . can match newline characters
+ }
+ else
+ {
+ $firstline=preg_replace('/[\n\r].*/', '', $text);
+ }
+ return $firstline.((strlen($firstline) != strlen($text))?'...':'');
+ }
+ else
+ {
+ $ishtml=0;
+ if (dol_textishtml($text))
+ {
+ $text=preg_replace('/\n/', '', $text);
+ $ishtml=1;
+ $repTable = array("\t" => " ", "\n" => " ", "\r" => " ", "\0" => " ", "\x0B" => " ");
+ }
+ else
+ {
+ $repTable = array("\t" => " ", "\n" => " ", "\r" => " ", "\0" => " ", "\x0B" => " ");
+ }
+
+ $text = strtr($text, $repTable);
+ if ($charset == 'UTF-8') { $pattern = '/( ]*>)/Uu'; } // /U is to have UNGREEDY regex to limit to one html tag. /u is for UTF8 support
+ else $pattern = '/( ]*>)/U'; // /U is to have UNGREEDY regex to limit to one html tag.
+ $a = preg_split($pattern, $text, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
+
+ $firstline='';
+ $i=0;
+ $nba = count($a); // 2x nb of lines in $a because $a contains also a line for each new line separator
+ while (($i < $nba) && ($i < ($nboflines * 2)))
+ {
+ if ($i % 2 == 0) $firstline .= $a[$i];
+ elseif (($i < (($nboflines * 2) - 1)) && ($i < ($nba - 1))) $firstline .= ($ishtml?" \n":"\n");
+ $i++;
+ }
+ unset($a);
+ return $firstline.(($i < $nba)?'...':'');
+ }
+}
+
+
+/**
+ * Replace CRLF in string with a HTML BR tag
+ *
+ * @param string $stringtoencode String to encode
+ * @param int $nl2brmode 0=Adding br before \n, 1=Replacing \n by br
+ * @param bool $forxml false=Use , true=Use
+ * @return string String encoded
+ * @see dol_nboflines, dolGetFirstLineOfText
+ */
+function dol_nl2br($stringtoencode, $nl2brmode = 0, $forxml = false)
+{
+ if (!$nl2brmode) {
+ return nl2br($stringtoencode, $forxml);
+ } else {
+ $ret=preg_replace('/(\r\n|\r|\n)/i', ($forxml?' ':' '), $stringtoencode);
+ return $ret;
+ }
+}
+
+
+/**
+ * This function is called to encode a string into a HTML string but differs from htmlentities because
+ * a detection is done before to see if text is already HTML or not. Also, all entities but &,<,> are converted.
+ * This permits to encode special chars to entities with no double encoding for already encoded HTML strings.
+ * This function also remove last EOL or BR if $removelasteolbr=1 (default).
+ * For PDF usage, you can show text by 2 ways:
+ * - writeHTMLCell -> param must be encoded into HTML.
+ * - MultiCell -> param must not be encoded into HTML.
+ * Because writeHTMLCell convert also \n into , if function
+ * is used to build PDF, nl2brmode must be 1.
+ *
+ * @param string $stringtoencode String to encode
+ * @param int $nl2brmode 0=Adding br before \n, 1=Replacing \n by br (for use with FPDF writeHTMLCell function for example)
+ * @param string $pagecodefrom Pagecode stringtoencode is encoded
+ * @param int $removelasteolbr 1=Remove last br or lasts \n (default), 0=Do nothing
+ * @return string String encoded
+ */
+function dol_htmlentitiesbr($stringtoencode, $nl2brmode = 0, $pagecodefrom = 'UTF-8', $removelasteolbr = 1)
+{
+ $newstring=$stringtoencode;
+ if (dol_textishtml($stringtoencode)) // Check if text is already HTML or not
+ {
+ $newstring=preg_replace('/ /i', ' ', $newstring); // Replace " " by " ". It's same and avoid pb with FPDF.
+ if ($removelasteolbr) $newstring=preg_replace('/ $/i', '', $newstring); // Remove last (remove only last one)
+ $newstring=strtr($newstring, array('&'=>'__and__','<'=>'__lt__','>'=>'__gt__','"'=>'__dquot__'));
+ $newstring=dol_htmlentities($newstring, ENT_COMPAT, $pagecodefrom); // Make entity encoding
+ $newstring=strtr($newstring, array('__and__'=>'&','__lt__'=>'<','__gt__'=>'>','__dquot__'=>'"'));
+ }
+ else
+ {
+ if ($removelasteolbr) $newstring=preg_replace('/(\r\n|\r|\n)$/i', '', $newstring); // Remove last \n (may remove several)
+ $newstring=dol_nl2br(dol_htmlentities($newstring, ENT_COMPAT, $pagecodefrom), $nl2brmode);
+ }
+ // Other substitutions that htmlentities does not do
+ //$newstring=str_replace(chr(128),'€',$newstring); // 128 = 0x80. Not in html entity table. // Seems useles with TCPDF. Make bug with UTF8 languages
+ return $newstring;
+}
+
+/**
+ * This function is called to decode a HTML string (it decodes entities and br tags)
+ *
+ * @param string $stringtodecode String to decode
+ * @param string $pagecodeto Page code for result
+ * @return string String decoded
+ */
+function dol_htmlentitiesbr_decode($stringtodecode, $pagecodeto = 'UTF-8')
+{
+ $ret=dol_html_entity_decode($stringtodecode, ENT_COMPAT, $pagecodeto);
+ $ret=preg_replace('/'."\r\n".' /i', " ", $ret);
+ $ret=preg_replace('/ '."\r\n".'/i', "\r\n", $ret);
+ $ret=preg_replace('/ '."\n".'/i', "\n", $ret);
+ $ret=preg_replace('/ /i', "\n", $ret);
+ return $ret;
+}
+
+/**
+ * This function remove all ending \n and br at end
+ *
+ * @param string $stringtodecode String to decode
+ * @return string String decoded
+ */
+function dol_htmlcleanlastbr($stringtodecode)
+{
+ $ret=preg_replace('/( | |'."\n".'|'."\r".')+$/i', "", $stringtodecode);
+ return $ret;
+}
+
+/**
+ * Replace html_entity_decode functions to manage errors
+ *
+ * @param string $a Operand a
+ * @param string $b Operand b (ENT_QUOTES=convert simple and double quotes)
+ * @param string $c Operand c
+ * @return string String decoded
+ */
+function dol_html_entity_decode($a, $b, $c = 'UTF-8')
+{
+ return html_entity_decode($a, $b, $c);
+}
+
+/**
+ * Replace htmlentities functions.
+ * Goal of this function is to be sure to have default values of htmlentities that match what we need.
+ *
+ * @param string $string The input string to encode
+ * @param int $flags Flags (see PHP doc above)
+ * @param string $encoding Encoding page code
+ * @param bool $double_encode When double_encode is turned off, PHP will not encode existing html entities
+ * @return string $ret Encoded string
+ */
+function dol_htmlentities($string, $flags = null, $encoding = 'UTF-8', $double_encode = false)
+{
+ return htmlentities($string, $flags, $encoding, $double_encode);
+}
+
+/**
+ * Check if a string is a correct iso string
+ * If not, it will we considered not HTML encoded even if it is by FPDF.
+ * Example, if string contains euro symbol that has ascii code 128
+ *
+ * @param string $s String to check
+ * @return int 0 if bad iso, 1 if good iso
+ */
+function dol_string_is_good_iso($s)
+{
+ $len=dol_strlen($s);
+ $ok=1;
+ for($scursor=0;$scursor<$len;$scursor++)
+ {
+ $ordchar=ord($s{$scursor});
+ //print $scursor.'-'.$ordchar.' ';
+ if ($ordchar < 32 && $ordchar != 13 && $ordchar != 10) { $ok=0; break; }
+ if ($ordchar > 126 && $ordchar < 160) { $ok=0; break; }
+ }
+ return $ok;
+}
+
+
+/**
+ * Return nb of lines of a clear text
+ *
+ * @param string $s String to check
+ * @param int $maxchar Not yet used
+ * @return int Number of lines
+ * @see dol_nboflines_bis, dolGetFirstLineOfText
+ */
+function dol_nboflines($s, $maxchar = 0)
+{
+ if ($s == '') return 0;
+ $arraystring=explode("\n", $s);
+ $nb=count($arraystring);
+
+ return $nb;
+}
+
+
+/**
+ * Return nb of lines of a formated text with \n and (WARNING: string must not have mixed \n and br separators)
+ *
+ * @param string $text Text
+ * @param int $maxlinesize Largeur de ligne en caracteres (ou 0 si pas de limite - defaut)
+ * @param string $charset Give the charset used to encode the $text variable in memory.
+ * @return int Number of lines
+ * @see dol_nboflines, dolGetFirstLineOfText
+ */
+function dol_nboflines_bis($text, $maxlinesize = 0, $charset = 'UTF-8')
+{
+ $repTable = array("\t" => " ", "\n" => " ", "\r" => " ", "\0" => " ", "\x0B" => " ");
+ if (dol_textishtml($text)) $repTable = array("\t" => " ", "\n" => " ", "\r" => " ", "\0" => " ", "\x0B" => " ");
+
+ $text = strtr($text, $repTable);
+ if ($charset == 'UTF-8') { $pattern = '/( ]*>)/Uu'; } // /U is to have UNGREEDY regex to limit to one html tag. /u is for UTF8 support
+ else $pattern = '/( ]*>)/U'; // /U is to have UNGREEDY regex to limit to one html tag.
+ $a = preg_split($pattern, $text, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
+
+ $nblines = (int) floor((count($a)+1)/2);
+ // count possible auto line breaks
+ if($maxlinesize)
+ {
+ foreach ($a as $line)
+ {
+ if (dol_strlen($line)>$maxlinesize)
+ {
+ //$line_dec = html_entity_decode(strip_tags($line));
+ $line_dec = html_entity_decode($line);
+ if(dol_strlen($line_dec)>$maxlinesize)
+ {
+ $line_dec=wordwrap($line_dec, $maxlinesize, '\n', true);
+ $nblines+=substr_count($line_dec, '\n');
+ }
+ }
+ }
+ }
+
+ unset($a);
+ return $nblines;
+}
+
+/**
+ * Same function than microtime in PHP 5 but compatible with PHP4
+ *
+ * @return float Time (millisecondes) with microsecondes in decimal part
+ * @deprecated Dolibarr does not support PHP4, you should use native function
+ * @see microtime()
+ */
+function dol_microtime_float()
+{
+ dol_syslog(__FUNCTION__ . " is deprecated", LOG_WARNING);
+
+ return microtime(true);
+}
+
+/**
+ * Return if a text is a html content
+ *
+ * @param string $msg Content to check
+ * @param int $option 0=Full detection, 1=Fast check
+ * @return boolean true/false
+ * @see dol_concatdesc
+ */
+function dol_textishtml($msg, $option = 0)
+{
+ if ($option == 1)
+ {
+ if (preg_match('//i', $msg)) return true;
+ elseif (preg_match('/ /i', $msg)) return true;
+ elseif (preg_match('/<(br|div|font|li|p|span|strong|table)>/i', $msg)) return true;
+ elseif (preg_match('/<(br|div|font|li|p|span|strong|table)\s+[^<>\/]*>/i', $msg)) return true;
+ elseif (preg_match('/<(br|div|font|li|p|span|strong|table)\s+[^<>\/]*\/>/i', $msg)) return true;
+ elseif (preg_match('/]*src[^<>]*>/i', $msg)) return true; // must accept
+ elseif (preg_match('/]*href[^<>]*>/i', $msg)) return true; // must accept
+ elseif (preg_match('//i', $msg)) return true;
+ elseif (preg_match('/&[A-Z0-9]{1,6};/i', $msg)) return true; // Html entities names (http://www.w3schools.com/tags/ref_entities.asp)
+ elseif (preg_match('/[0-9]{2,3};/i', $msg)) return true; // Html entities numbers (http://www.w3schools.com/tags/ref_entities.asp)
+
+ return false;
+ }
+}
+
+/**
+ * Concat 2 descriptions with a new line between them (second operand after first one with appropriate new line separator)
+ * text1 html + text2 html => text1 + ' ' + text2
+ * text1 html + text2 txt => text1 + ' ' + dol_nl2br(text2)
+ * text1 txt + text2 html => dol_nl2br(text1) + ' ' + text2
+ * text1 txt + text2 txt => text1 + '\n' + text2
+ *
+ * @param string $text1 Text 1
+ * @param string $text2 Text 2
+ * @param bool $forxml false=Use instead of \n if html content detected, true=Use instead of \n if html content detected
+ * @param bool $invert invert order of description lines if CONF CHANGE_ORDER_CONCAT_DESCRIPTION is active
+ * @return string Text 1 + new line + Text2
+ * @see dol_textishtml
+ */
+function dol_concatdesc($text1, $text2, $forxml = false, $invert = false)
+{
+ if (!empty($invert))
+ {
+ $tmp = $text1;
+ $text1 = $text2;
+ $text2 = $tmp;
+ }
+
+ $ret='';
+ $ret.= (! dol_textishtml($text1) && dol_textishtml($text2))?dol_nl2br($text1, 0, $forxml):$text1;
+ $ret.= (! empty($text1) && ! empty($text2)) ? ((dol_textishtml($text1) || dol_textishtml($text2))?($forxml?" \n":" \n") : "\n") : "";
+ $ret.= (dol_textishtml($text1) && ! dol_textishtml($text2))?dol_nl2br($text2, 0, $forxml):$text2;
+ return $ret;
+}
+
+
+
+/**
+ * Return array of possible common substitutions. This includes several families like: 'system', 'mycompany', 'object', 'objectamount', 'date', 'user'
+ *
+ * @param Translate $outputlangs Output language
+ * @param int $onlykey 1=Do not calculate some heavy values of keys (performance enhancement when we need only the keys), 2=Values are trunc and html sanitized (to use for help tooltip)
+ * @param array $exclude Array of family keys we want to exclude. For example array('system', 'mycompany', 'object', 'objectamount', 'date', 'user', ...)
+ * @param Object $object Object for keys on object
+ * @return array Array of substitutions
+ * @see setSubstitFromObject
+ */
+function getCommonSubstitutionArray($outputlangs, $onlykey = 0, $exclude = null, $object = null)
+{
+ global $db, $conf, $mysoc, $user, $extrafields;
+
+ $substitutionarray=array();
+
+ if (empty($exclude) || ! in_array('user', $exclude))
+ {
+ // Add SIGNATURE into substitutionarray first, so, when we will make the substitution,
+ // this will include signature content first and then replace var found into content of signature
+ $signature = $user->signature;
+ $substitutionarray=array_merge($substitutionarray, array(
+ '__USER_SIGNATURE__' => (string) (($signature && empty($conf->global->MAIN_MAIL_DO_NOT_USE_SIGN)) ? ($onlykey == 2 ? dol_trunc(dol_string_nohtmltag($signature), 30) : $signature) : '')
+ )
+ );
+ // For backward compatibility
+ if ($onlykey != 2)
+ {
+ $substitutionarray['__SIGNATURE__'] = (string) (($signature && empty($conf->global->MAIN_MAIL_DO_NOT_USE_SIGN)) ? ($onlykey == 2 ? dol_trunc(dol_string_nohtmltag($signature), 30) : $signature) : '');
+ }
+
+ $substitutionarray=array_merge($substitutionarray, array(
+ '__USER_ID__' => (string) $user->id,
+ '__USER_LOGIN__' => (string) $user->login,
+ '__USER_LASTNAME__' => (string) $user->lastname,
+ '__USER_FIRSTNAME__' => (string) $user->firstname,
+ '__USER_FULLNAME__' => (string) $user->getFullName($outputlangs),
+ '__USER_SUPERVISOR_ID__' => (string) ($user->fk_user ? $user->fk_user : '0'),
+ '__USER_REMOTE_IP__' => (string) getUserRemoteIP()
+ )
+ );
+ }
+ if ((empty($exclude) || ! in_array('mycompany', $exclude)) && is_object($mysoc))
+ {
+ $substitutionarray=array_merge($substitutionarray, array(
+ '__MYCOMPANY_NAME__' => $mysoc->name,
+ '__MYCOMPANY_EMAIL__' => $mysoc->email,
+ '__MYCOMPANY_PROFID1__' => $mysoc->idprof1,
+ '__MYCOMPANY_PROFID2__' => $mysoc->idprof2,
+ '__MYCOMPANY_PROFID3__' => $mysoc->idprof3,
+ '__MYCOMPANY_PROFID4__' => $mysoc->idprof4,
+ '__MYCOMPANY_PROFID5__' => $mysoc->idprof5,
+ '__MYCOMPANY_PROFID6__' => $mysoc->idprof6,
+ '__MYCOMPANY_CAPITAL__' => $mysoc->capital,
+ '__MYCOMPANY_FULLADDRESS__' => $mysoc->getFullAddress(1, ', '),
+ '__MYCOMPANY_ADDRESS__' => $mysoc->address,
+ '__MYCOMPANY_ZIP__' => $mysoc->zip,
+ '__MYCOMPANY_TOWN__' => $mysoc->town,
+ '__MYCOMPANY_COUNTRY__' => $mysoc->country,
+ '__MYCOMPANY_COUNTRY_ID__' => $mysoc->country_id,
+ '__MYCOMPANY_CURRENCY_CODE__' => $conf->currency
+ ));
+ }
+
+ if (($onlykey || is_object($object)) && (empty($exclude) || ! in_array('object', $exclude)))
+ {
+ if ($onlykey)
+ {
+ $substitutionarray['__ID__'] = '__ID__';
+ $substitutionarray['__REF__'] = '__REF__';
+ $substitutionarray['__REF_CLIENT__'] = '__REF_CLIENT__';
+ $substitutionarray['__REF_SUPPLIER__'] = '__REF_SUPPLIER__';
+ $substitutionarray['__EXTRAFIELD_XXX__'] = '__EXTRAFIELD_XXX__';
+
+ $substitutionarray['__THIRDPARTY_ID__'] = '__THIRDPARTY_ID__';
+ $substitutionarray['__THIRDPARTY_NAME__'] = '__THIRDPARTY_NAME__';
+ $substitutionarray['__THIRDPARTY_NAME_ALIAS__'] = '__THIRDPARTY_NAME_ALIAS__';
+ $substitutionarray['__THIRDPARTY_EMAIL__'] = '__THIRDPARTY_EMAIL__';
+
+ if (! empty($conf->adherent->enabled))
+ {
+ $substitutionarray['__MEMBER_ID__'] = '__MEMBER_ID__';
+ $substitutionarray['__MEMBER_CIVILITY__'] = '__MEMBER_CIVILITY__';
+ $substitutionarray['__MEMBER_FIRSTNAME__'] = '__MEMBER_FIRSTNAME__';
+ $substitutionarray['__MEMBER_LASTNAME__'] = '__MEMBER_LASTNAME__';
+ }
+ $substitutionarray['__PROJECT_ID__'] = '__PROJECT_ID__';
+ $substitutionarray['__PROJECT_REF__'] = '__PROJECT_REF__';
+ $substitutionarray['__PROJECT_NAME__'] = '__PROJECT_NAME__';
+
+ $substitutionarray['__CONTRACT_HIGHEST_PLANNED_START_DATE__'] = 'Highest date planned for a service start';
+ $substitutionarray['__CONTRACT_HIGHEST_PLANNED_START_DATETIME__'] = 'Highest date and hour planned for service start';
+ $substitutionarray['__CONTRACT_LOWEST_EXPIRATION_DATE__'] = 'Lowest data for planned expiration of service';
+ $substitutionarray['__CONTRACT_LOWEST_EXPIRATION_DATETIME__'] = 'Lowest date and hour for planned expiration of service';
+
+ $substitutionarray['__ONLINE_PAYMENT_URL__'] = 'UrlToPayOnlineIfApplicable';
+ $substitutionarray['__ONLINE_PAYMENT_TEXT_AND_URL__'] = 'TextAndUrlToPayOnlineIfApplicable';
+ $substitutionarray['__SECUREKEYPAYMENT__'] = 'Security key (if key is not unique per record)';
+ $substitutionarray['__SECUREKEYPAYMENT_MEMBER__'] = 'Security key for payment on a member subscription (one key per member)';
+ $substitutionarray['__SECUREKEYPAYMENT_ORDER__'] = 'Security key for payment on an order';
+ $substitutionarray['__SECUREKEYPAYMENT_INVOICE__'] = 'Security key for payment on an invoice';
+ $substitutionarray['__SECUREKEYPAYMENT_CONTRACTLINE__'] = 'Security key for payment on a a service';
+
+ $substitutionarray['__DIRECTDOWNLOAD_URL_PROPOSAL__'] = 'Direct download url of a proposal';
+ $substitutionarray['__DIRECTDOWNLOAD_URL_ORDER__'] = 'Direct download url of an order';
+ $substitutionarray['__DIRECTDOWNLOAD_URL_INVOICE__'] = 'Direct download url of an invoice';
+
+ if (! empty($conf->expedition->enabled))
+ {
+ $substitutionarray['__SHIPPINGTRACKNUM__']='Shipping tacking number';
+ $substitutionarray['__SHIPPINGTRACKNUMURL__']='Shipping tracking url';
+ }
+ }
+ else
+ {
+ $substitutionarray['__ID__'] = $object->id;
+ $substitutionarray['__REF__'] = $object->ref;
+ $substitutionarray['__REF_CLIENT__'] = (isset($object->ref_client) ? $object->ref_client : (isset($object->ref_customer) ? $object->ref_customer : null));
+ $substitutionarray['__REF_SUPPLIER__'] = (isset($object->ref_supplier) ? $object->ref_supplier : null);
+ // For backward compatibility
+ $substitutionarray['__REFCLIENT__'] = (isset($object->ref_client) ? $object->ref_client : (isset($object->ref_customer) ? $object->ref_customer : null));
+ $substitutionarray['__REFSUPPLIER__'] = (isset($object->ref_supplier) ? $object->ref_supplier : null);
+ $substitutionarray['__SUPPLIER_ORDER_DATE_DELIVERY__'] = (isset($object->date_livraison) ? dol_print_date($object->date_livraison, 'day', 0, $outputlangs): '');
+
+ // TODO Remove this
+ $msgishtml = 0;
+
+ $birthday = dol_print_date($object->birth, 'day');
+
+ if ($object->id > 0)
+ {
+ $substitutionarray['__MEMBER_ID__']=$object->id;
+ if (method_exists($object, 'getCivilityLabel')) $substitutionarray['__MEMBER_CIVILITY__'] = $object->getCivilityLabel();
+ $substitutionarray['__MEMBER_FIRSTNAME__']=$msgishtml?dol_htmlentitiesbr($object->firstname):$object->firstname;
+ $substitutionarray['__MEMBER_LASTNAME__']=$msgishtml?dol_htmlentitiesbr($object->lastname):$object->lastname;
+ if (method_exists($object, 'getFullName')) $substitutionarray['__MEMBER_FULLNAME__']=$msgishtml?dol_htmlentitiesbr($object->getFullName($outputlangs)):$object->getFullName($outputlangs);
+ $substitutionarray['__MEMBER_COMPANY__']=$msgishtml?dol_htmlentitiesbr($object->societe):$object->societe;
+ $substitutionarray['__MEMBER_ADDRESS__']=$msgishtml?dol_htmlentitiesbr($object->address):$object->address;
+ $substitutionarray['__MEMBER_ZIP__']=$msgishtml?dol_htmlentitiesbr($object->zip):$object->zip;
+ $substitutionarray['__MEMBER_TOWN__']=$msgishtml?dol_htmlentitiesbr($object->town):$object->town;
+ $substitutionarray['__MEMBER_COUNTRY__']=$msgishtml?dol_htmlentitiesbr($object->country):$object->country;
+ $substitutionarray['__MEMBER_EMAIL__']=$msgishtml?dol_htmlentitiesbr($object->email):$object->email;
+ $substitutionarray['__MEMBER_BIRTH__']=$msgishtml?dol_htmlentitiesbr($birthday):$birthday;
+ $substitutionarray['__MEMBER_PHOTO__']=$msgishtml?dol_htmlentitiesbr($object->photo):$object->photo;
+ $substitutionarray['__MEMBER_LOGIN__']=$msgishtml?dol_htmlentitiesbr($object->login):$object->login;
+ $substitutionarray['__MEMBER_PASSWORD__']=$msgishtml?dol_htmlentitiesbr($object->pass):$object->pass;
+ $substitutionarray['__MEMBER_PHONE__']=$msgishtml?dol_htmlentitiesbr($object->phone):$object->phone;
+ $substitutionarray['__MEMBER_PHONEPRO__']=$msgishtml?dol_htmlentitiesbr($object->phone_perso):$object->phone_perso;
+ $substitutionarray['__MEMBER_PHONEMOBILE__']=$msgishtml?dol_htmlentitiesbr($object->phone_mobile):$object->phone_mobile;
+ $substitutionarray['__MEMBER_FIRST_SUBSCRIPTION_DATE__'] = dol_print_date($object->first_subscription_date, 'dayrfc');
+ $substitutionarray['__MEMBER_FIRST_SUBSCRIPTION_DATE_START__'] = dol_print_date($object->first_subscription_date_start, 'dayrfc');
+ $substitutionarray['__MEMBER_FIRST_SUBSCRIPTION_DATE_END__'] = dol_print_date($object->first_subscription_date_end, 'dayrfc');
+ $substitutionarray['__MEMBER_LAST_SUBSCRIPTION_DATE__'] = dol_print_date($object->last_subscription_date, 'dayrfc');
+ $substitutionarray['__MEMBER_LAST_SUBSCRIPTION_DATE_START__'] = dol_print_date($object->last_subscription_date_start, 'dayrfc');
+ $substitutionarray['__MEMBER_LAST_SUBSCRIPTION_DATE_END__'] = dol_print_date($object->last_subscription_date_end, 'dayrfc');
+ }
+
+ if (is_object($object) && $object->element == 'societe')
+ {
+ $substitutionarray['__THIRDPARTY_ID__'] = (is_object($object)?$object->id:'');
+ $substitutionarray['__THIRDPARTY_NAME__'] = (is_object($object)?$object->name:'');
+ $substitutionarray['__THIRDPARTY_NAME_ALIAS__'] = (is_object($object)?$object->name_alias:'');
+ $substitutionarray['__THIRDPARTY_EMAIL__'] = (is_object($object)?$object->email:'');
+ }
+ elseif (is_object($object->thirdparty) && $object->thirdparty->id > 0)
+ {
+ $substitutionarray['__THIRDPARTY_ID__'] = (is_object($object->thirdparty)?$object->thirdparty->id:'');
+ $substitutionarray['__THIRDPARTY_NAME__'] = (is_object($object->thirdparty)?$object->thirdparty->name:'');
+ $substitutionarray['__THIRDPARTY_NAME_ALIAS__'] = (is_object($object->thirdparty)?$object->thirdparty->name_alias:'');
+ $substitutionarray['__THIRDPARTY_EMAIL__'] = (is_object($object->thirdparty)?$object->thirdparty->email:'');
+ }
+
+ if (is_object($object->projet) && $object->projet->id > 0)
+ {
+ $substitutionarray['__PROJECT_ID__'] = (is_object($object->projet)?$object->projet->id:'');
+ $substitutionarray['__PROJECT_REF__'] = (is_object($object->projet)?$object->projet->ref:'');
+ $substitutionarray['__PROJECT_NAME__'] = (is_object($object->projet)?$object->projet->title:'');
+ }
+
+ if (is_object($object) && $object->element == 'shipping')
+ {
+ $substitutionarray['__SHIPPINGTRACKNUM__']=$object->tracking_number;
+ $substitutionarray['__SHIPPINGTRACKNUMURL__']=$object->tracking_url;
+ }
+
+ if (is_object($object) && $object->element == 'contrat' && is_array($object->lines))
+ {
+ if ($object->id > 0)
+ {
+ $dateplannedstart='';
+ $datenextexpiration='';
+ foreach($object->lines as $line)
+ {
+ if ($line->date_ouverture_prevue > $dateplannedstart) $dateplannedstart = $line->date_ouverture_prevue;
+ if ($line->statut == 4 && $line->date_fin_prevue && (! $datenextexpiration || $line->date_fin_prevue < $datenextexpiration)) $datenextexpiration = $line->date_fin_prevue;
+ }
+ $substitutionarray['__CONTRACT_HIGHEST_PLANNED_START_DATE__'] = dol_print_date($dateplannedstart, 'dayrfc');
+ $substitutionarray['__CONTRACT_HIGHEST_PLANNED_START_DATETIME__'] = dol_print_date($dateplannedstart, 'standard');
+ $substitutionarray['__CONTRACT_LOWEST_EXPIRATION_DATE__'] = dol_print_date($datenextexpiration, 'dayrfc');
+ $substitutionarray['__CONTRACT_LOWEST_EXPIRATION_DATETIME__'] = dol_print_date($datenextexpiration, 'standard');
+ }
+ }
+
+ // Create dynamic tags for __EXTRAFIELD_FIELD__
+ if ($object->table_element && $object->id > 0)
+ {
+ if (! is_object($extrafields)) $extrafields = new ExtraFields($db);
+ $extrafields->fetch_name_optionals_label($object->table_element, true);
+
+ if ($object->fetch_optionals() > 0)
+ {
+ if (is_array($extrafields->attributes[$object->table_element]['label']) && count($extrafields->attributes[$object->table_element]['label']) > 0)
+ {
+ foreach ($extrafields->attributes[$object->table_element]['label'] as $key => $label) {
+ $substitutionarray['__EXTRAFIELD_' . strtoupper($key) . '__'] = $object->array_options['options_' . $key];
+ }
+ }
+ }
+ }
+
+ // Complete substitution array with the url to make online payment
+ $paymenturl='';
+ if (empty($substitutionarray['__REF__']))
+ {
+ $paymenturl='';
+ }
+ else
+ {
+ // Set the online payment url link into __ONLINE_PAYMENT_URL__ key
+ require_once DOL_DOCUMENT_ROOT.'/core/lib/payments.lib.php';
+ $outputlangs->loadLangs(array('paypal','other'));
+ $typeforonlinepayment='free';
+ if (is_object($object) && $object->element == 'commande') $typeforonlinepayment='order';
+ if (is_object($object) && $object->element == 'facture') $typeforonlinepayment='invoice';
+ if (is_object($object) && $object->element == 'member') $typeforonlinepayment='member';
+ $url=getOnlinePaymentUrl(0, $typeforonlinepayment, $substitutionarray['__REF__']);
+ $paymenturl=$url;
+ }
+
+ if ($object->id > 0)
+ {
+ $substitutionarray['__ONLINE_PAYMENT_TEXT_AND_URL__']=($paymenturl?str_replace('\n', "\n", $outputlangs->trans("PredefinedMailContentLink", $paymenturl)):'');
+ $substitutionarray['__ONLINE_PAYMENT_URL__']=$paymenturl;
+
+ if (! empty($conf->global->PROPOSAL_ALLOW_EXTERNAL_DOWNLOAD) && is_object($object) && $object->element == 'propal')
+ {
+ $substitutionarray['__DIRECTDOWNLOAD_URL_PROPOSAL__'] = $object->getLastMainDocLink($object->element);
+ }
+ else $substitutionarray['__DIRECTDOWNLOAD_URL_PROPOSAL__'] = '';
+ if (! empty($conf->global->ORDER_ALLOW_EXTERNAL_DOWNLOAD) && is_object($object) && $object->element == 'commande')
+ {
+ $substitutionarray['__DIRECTDOWNLOAD_URL_ORDER__'] = $object->getLastMainDocLink($object->element);
+ }
+ else $substitutionarray['__DIRECTDOWNLOAD_URL_ORDER__'] = '';
+ if (! empty($conf->global->INVOICE_ALLOW_EXTERNAL_DOWNLOAD) && is_object($object) && $object->element == 'facture')
+ {
+ $substitutionarray['__DIRECTDOWNLOAD_URL_INVOICE__'] = $object->getLastMainDocLink($object->element);
+ }
+ else $substitutionarray['__DIRECTDOWNLOAD_URL_INVOICE__'] = '';
+ }
+ }
+ }
+ if (empty($exclude) || ! in_array('objectamount', $exclude))
+ {
+ $substitutionarray['__DATE_YMD__'] = is_object($object)?(isset($object->date) ? dol_print_date($object->date, 'day', 0, $outputlangs) : null) : '';
+ $substitutionarray['__DATE_DUE_YMD__'] = is_object($object)?(isset($object->date_lim_reglement) ? dol_print_date($object->date_lim_reglement, 'day', 0, $outputlangs) : null) : '';
+
+ $substitutionarray['__AMOUNT__'] = is_object($object)?$object->total_ttc:'';
+ $substitutionarray['__AMOUNT_EXCL_TAX__'] = is_object($object)?$object->total_ht:'';
+ $substitutionarray['__AMOUNT_VAT__'] = is_object($object)?($object->total_vat?$object->total_vat:$object->total_tva):'';
+ if ($onlykey != 2 || $mysoc->useLocalTax(1)) $substitutionarray['__AMOUNT_TAX2__'] = is_object($object)?$object->total_localtax1:'';
+ if ($onlykey != 2 || $mysoc->useLocalTax(2)) $substitutionarray['__AMOUNT_TAX3__'] = is_object($object)?$object->total_localtax2:'';
+
+ $substitutionarray['__AMOUNT_FORMATED__'] = is_object($object)?($object->total_ttc ? price($object->total_ttc, 0, $outputlangs, 0, 0, -1, $conf->currency) : null):'';
+ $substitutionarray['__AMOUNT_EXCL_TAX_FORMATED__'] = is_object($object)?($object->total_ht ? price($object->total_ht, 0, $outputlangs, 0, 0, -1, $conf->currency) : null):'';
+ $substitutionarray['__AMOUNT_VAT_FORMATED__'] = is_object($object)?($object->total_vat ? price($object->total_vat, 0, $outputlangs, 0, 0, -1, $conf->currency): ($object->total_tva ? price($object->total_tva, 0, $outputlangs, 0, 0, -1, $conf->currency) : null)):'';
+ if ($onlykey != 2 || $mysoc->useLocalTax(1)) $substitutionarray['__AMOUNT_TAX2_FORMATED__'] = is_object($object)? ($object->total_localtax1 ? price($object->total_localtax1, 0, $outputlangs, 0, 0, -1, $conf->currency) : null):'';
+ if ($onlykey != 2 || $mysoc->useLocalTax(2)) $substitutionarray['__AMOUNT_TAX3_FORMATED__'] = is_object($object)? ($object->total_localtax2 ? price($object->total_localtax2, 0, $outputlangs, 0, 0, -1, $conf->currency) : null):'';
+
+ // TODO Add keys for foreign multicurrency
+
+ // For backward compatibility
+ if ($onlykey != 2)
+ {
+ $substitutionarray['__TOTAL_TTC__'] = is_object($object)?$object->total_ttc:'';
+ $substitutionarray['__TOTAL_HT__'] = is_object($object)?$object->total_ht:'';
+ $substitutionarray['__TOTAL_VAT__'] = is_object($object)?($object->total_vat?$object->total_vat:$object->total_tva):'';
+ }
+ }
+
+ //var_dump($substitutionarray['__AMOUNT_FORMATED__']);
+ if (empty($exclude) || ! in_array('date', $exclude))
+ {
+ include_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
+
+ $tmp=dol_getdate(dol_now(), true);
+ $tmp2=dol_get_prev_day($tmp['mday'], $tmp['mon'], $tmp['year']);
+ $tmp3=dol_get_prev_month($tmp['mon'], $tmp['year']);
+ $tmp4=dol_get_next_day($tmp['mday'], $tmp['mon'], $tmp['year']);
+ $tmp5=dol_get_next_month($tmp['mon'], $tmp['year']);
+
+ $substitutionarray=array_merge($substitutionarray, array(
+ '__DAY__' => (string) $tmp['mday'],
+ '__DAY_TEXT__' => $outputlangs->trans('Day'.$tmp['wday']), // Monday
+ '__DAY_TEXT_SHORT__' => $outputlangs->trans($tmp['weekday'].'Min'), // Mon
+ '__DAY_TEXT_MIN__' => $outputlangs->trans('Short'.$tmp['weekday']), // M
+ '__MONTH__' => (string) $tmp['mon'],
+ '__MONTH_TEXT__' => $outputlangs->trans('Month'.sprintf("%02d", $tmp['mon'])),
+ '__MONTH_TEXT_SHORT__' => $outputlangs->trans('MonthShort'.sprintf("%02d", $tmp['mon'])),
+ '__MONTH_TEXT_MIN__' => $outputlangs->trans('MonthVeryShort'.sprintf("%02d", $tmp['mon'])),
+ '__YEAR__' => (string) $tmp['year'],
+ '__PREVIOUS_DAY__' => (string) $tmp2['day'],
+ '__PREVIOUS_MONTH__' => (string) $tmp3['month'],
+ '__PREVIOUS_YEAR__' => (string) ($tmp['year'] - 1),
+ '__NEXT_DAY__' => (string) $tmp4['day'],
+ '__NEXT_MONTH__' => (string) $tmp5['month'],
+ '__NEXT_YEAR__' => (string) ($tmp['year'] + 1),
+ ));
+ }
+
+ if (! empty($conf->multicompany->enabled))
+ {
+ $substitutionarray=array_merge($substitutionarray, array('__ENTITY_ID__' => $conf->entity));
+ }
+ if (empty($exclude) || ! in_array('system', $exclude))
+ {
+ $substitutionarray['__DOL_MAIN_URL_ROOT__']=DOL_MAIN_URL_ROOT;
+ $substitutionarray['__(AnyTranslationKey)__']=$outputlangs->trans('TranslationOfKey');
+ $substitutionarray['__[AnyConstantKey]__']=$outputlangs->trans('ValueOfConstantKey');
+ }
+
+ return $substitutionarray;
+}
+
+/**
+ * Make substitution into a text string, replacing keys with vals from $substitutionarray (oldval=>newval),
+ * and texts like __(TranslationKey|langfile)__ and __[ConstantKey]__ are also replaced.
+ * Example of usage:
+ * $substitutionarray = getCommonSubstitutionArray($langs, 0, null, $thirdparty);
+ * complete_substitutions_array($substitutionarray, $langs, $thirdparty);
+ * $mesg = make_substitutions($mesg, $substitutionarray, $langs);
+ *
+ * @param string $text Source string in which we must do substitution
+ * @param array $substitutionarray Array with key->val to substitute. Example: array('__MYKEY__' => 'MyVal', ...)
+ * @param Translate $outputlangs Output language
+ * @return string Output string after substitutions
+ * @see complete_substitutions_array, getCommonSubstitutionArray
+ */
+function make_substitutions($text, $substitutionarray, $outputlangs = null)
+{
+ global $conf, $langs;
+
+ if (! is_array($substitutionarray)) return 'ErrorBadParameterSubstitutionArrayWhenCalling_make_substitutions';
+
+ if (empty($outputlangs)) $outputlangs=$langs;
+
+ // Make substitution for language keys
+ if (is_object($outputlangs))
+ {
+ while (preg_match('/__\(([^\)]+)\)__/', $text, $reg))
+ {
+ $msgishtml = 0;
+ if (dol_textishtml($text, 1)) $msgishtml = 1;
+
+ // If key is __(TranslationKey|langfile)__, then force load of langfile.lang
+ $tmp=explode('|', $reg[1]);
+ if (! empty($tmp[1])) $outputlangs->load($tmp[1]);
+
+ $text = preg_replace('/__\('.preg_quote($reg[1], '/').'\)__/', $msgishtml?dol_htmlentitiesbr($outputlangs->transnoentitiesnoconv($reg[1])):$outputlangs->transnoentitiesnoconv($reg[1]), $text);
+ }
+ }
+
+ // Make substitution for constant keys. Must be after the substitution of translation, so if text of translation contains a constant,
+ // it is also converted.
+ while (preg_match('/__\[([^\]]+)\]__/', $text, $reg))
+ {
+ $msgishtml = 0;
+ if (dol_textishtml($text, 1)) $msgishtml = 1;
+
+ $keyfound = $reg[1];
+ if (preg_match('/(_pass|password|secret|_key|key$)/i', $keyfound)) $newval = '*****forbidden*****';
+ else $newval=empty($conf->global->$keyfound)?'':$conf->global->$keyfound;
+ $text = preg_replace('/__\['.preg_quote($keyfound, '/').'\]__/', $msgishtml?dol_htmlentitiesbr($newval):$newval, $text);
+ }
+
+ // Make substitition for array $substitutionarray
+ foreach ($substitutionarray as $key => $value)
+ {
+ if (! isset($value)) continue; // If value is null, it same than not having substitution key at all into array, we do not replace.
+
+ if ($key == '__SIGNATURE__' && (! empty($conf->global->MAIN_MAIL_DO_NOT_USE_SIGN))) $value=''; // Protection
+ if ($key == '__USER_SIGNATURE__' && (! empty($conf->global->MAIN_MAIL_DO_NOT_USE_SIGN))) $value=''; // Protection
+
+ $text=str_replace("$key", "$value", $text); // We must keep the " to work when value is 123.5 for example
+ }
+
+ return $text;
+}
+
+/**
+ * Complete the $substitutionarray with more entries coming from external module that had set the "substitutions=1" into module_part array.
+ * In this case, method completesubstitutionarray provided by module is called.
+ *
+ * @param array $substitutionarray Array substitution old value => new value value
+ * @param Translate $outputlangs Output language
+ * @param Object $object Source object
+ * @param mixed $parameters Add more parameters (useful to pass product lines)
+ * @param string $callfunc What is the name of the custom function that will be called? (default: completesubstitutionarray)
+ * @return void
+ * @see make_substitutions
+ */
+function complete_substitutions_array(&$substitutionarray, $outputlangs, $object = null, $parameters = null, $callfunc = "completesubstitutionarray")
+{
+ global $conf,$user;
+
+ require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
+
+ // Add a substitution key for each extrafields, using key __EXTRA_XXX__
+ // TODO Remove this. Already available into the getCommonSubstitutionArray used to build the substitution array.
+ /*if (is_object($object) && is_array($object->array_options))
+ {
+ foreach($object->array_options as $key => $val)
+ {
+ $keyshort=preg_replace('/^(options|extra)_/','',$key);
+ $substitutionarray['__EXTRAFIELD_'.$keyshort.'__']=$val;
+ // For backward compatibiliy
+ $substitutionarray['%EXTRA_'.$keyshort.'%']=$val;
+ }
+ }*/
+
+ // Check if there is external substitution to do, requested by plugins
+ $dirsubstitutions=array_merge(array(), (array) $conf->modules_parts['substitutions']);
+
+ foreach($dirsubstitutions as $reldir)
+ {
+ $dir=dol_buildpath($reldir, 0);
+
+ // Check if directory exists
+ if (! dol_is_dir($dir)) continue;
+
+ $substitfiles=dol_dir_list($dir, 'files', 0, 'functions_');
+ foreach($substitfiles as $substitfile)
+ {
+ if (preg_match('/functions_(.*)\.lib\.php/i', $substitfile['name'], $reg))
+ {
+ $module=$reg[1];
+
+ dol_syslog("Library ".$substitfile['name']." found into ".$dir);
+ // Include the user's functions file
+ require_once $dir.$substitfile['name'];
+ // Call the user's function, and only if it is defined
+ $function_name=$module."_".$callfunc;
+ if (function_exists($function_name)) $function_name($substitutionarray, $outputlangs, $object, $parameters);
+ }
+ }
+ }
+}
+
+/**
+ * Format output for start and end date
+ *
+ * @param int $date_start Start date
+ * @param int $date_end End date
+ * @param string $format Output format
+ * @param Translate $outputlangs Output language
+ * @return void
+ */
+function print_date_range($date_start, $date_end, $format = '', $outputlangs = '')
+{
+ print get_date_range($date_start, $date_end, $format, $outputlangs);
+}
+
+/**
+ * Format output for start and end date
+ *
+ * @param int $date_start Start date
+ * @param int $date_end End date
+ * @param string $format Output format
+ * @param Translate $outputlangs Output language
+ * @param integer $withparenthesis 1=Add parenthesis, 0=non parenthesis
+ * @return string String
+ */
+function get_date_range($date_start, $date_end, $format = '', $outputlangs = '', $withparenthesis = 1)
+{
+ global $langs;
+
+ $out='';
+
+ if (! is_object($outputlangs)) $outputlangs=$langs;
+
+ if ($date_start && $date_end)
+ {
+ $out.= ($withparenthesis?' (':'').$outputlangs->transnoentitiesnoconv('DateFromTo', dol_print_date($date_start, $format, false, $outputlangs), dol_print_date($date_end, $format, false, $outputlangs)).($withparenthesis?')':'');
+ }
+ if ($date_start && ! $date_end)
+ {
+ $out.= ($withparenthesis?' (':'').$outputlangs->transnoentitiesnoconv('DateFrom', dol_print_date($date_start, $format, false, $outputlangs)).($withparenthesis?')':'');
+ }
+ if (! $date_start && $date_end)
+ {
+ $out.= ($withparenthesis?' (':'').$outputlangs->transnoentitiesnoconv('DateUntil', dol_print_date($date_end, $format, false, $outputlangs)).($withparenthesis?')':'');
+ }
+
+ return $out;
+}
+
+/**
+ * Return firstname and lastname in correct order
+ *
+ * @param string $firstname Firstname
+ * @param string $lastname Lastname
+ * @param int $nameorder -1=Auto, 0=Lastname+Firstname, 1=Firstname+Lastname, 2=Firstname, 3=Firstname if defined else lastname
+ * @return string Firstname + lastname or Lastname + firstname
+ */
+function dolGetFirstLastname($firstname, $lastname, $nameorder = -1)
+{
+ global $conf;
+
+ $ret='';
+ // If order not defined, we use the setup
+ if ($nameorder < 0) $nameorder=(empty($conf->global->MAIN_FIRSTNAME_NAME_POSITION)?1:0);
+ if ($nameorder && $nameorder != 2 && $nameorder != 3)
+ {
+ $ret.=$firstname;
+ if ($firstname && $lastname) $ret.=' ';
+ $ret.=$lastname;
+ }
+ elseif ($nameorder == 2 || $nameorder == 3)
+ {
+ $ret.=$firstname;
+ if (empty($ret) && $nameorder == 3)
+ {
+ $ret.=$lastname;
+ }
+ }
+ else
+ {
+ $ret.=$lastname;
+ if ($firstname && $lastname) $ret.=' ';
+ $ret.=$firstname;
+ }
+ return $ret;
+}
+
+
+/**
+ * Set event message in dol_events session object. Will be output by calling dol_htmloutput_events.
+ * Note: Calling dol_htmloutput_events is done into pages by standard llxFooter() function.
+ * Note: Prefer to use setEventMessages instead.
+ *
+ * @param mixed $mesgs Message string or array
+ * @param string $style Which style to use ('mesgs' by default, 'warnings', 'errors')
+ * @return void
+ * @see dol_htmloutput_events
+ */
+function setEventMessage($mesgs, $style = 'mesgs')
+{
+ //dol_syslog(__FUNCTION__ . " is deprecated", LOG_WARNING); This is not deprecated, it is used by setEventMessages function
+ if (! is_array($mesgs)) // If mesgs is a string
+ {
+ if ($mesgs) $_SESSION['dol_events'][$style][] = $mesgs;
+ }
+ else // If mesgs is an array
+ {
+ foreach($mesgs as $mesg)
+ {
+ if ($mesg) $_SESSION['dol_events'][$style][] = $mesg;
+ }
+ }
+}
+
+/**
+ * Set event messages in dol_events session object. Will be output by calling dol_htmloutput_events.
+ * Note: Calling dol_htmloutput_events is done into pages by standard llxFooter() function.
+ *
+ * @param string $mesg Message string
+ * @param array $mesgs Message array
+ * @param string $style Which style to use ('mesgs' by default, 'warnings', 'errors')
+ * @return void
+ * @see dol_htmloutput_events
+ */
+function setEventMessages($mesg, $mesgs, $style = 'mesgs')
+{
+ if (empty($mesg) && empty($mesgs))
+ {
+ dol_syslog("Try to add a message in stack with empty message", LOG_WARNING);
+ }
+ else
+ {
+ if (! in_array((string) $style, array('mesgs','warnings','errors'))) dol_print_error('', 'Bad parameter style='.$style.' for setEventMessages');
+ if (empty($mesgs)) setEventMessage($mesg, $style);
+ else
+ {
+ if (! empty($mesg) && ! in_array($mesg, $mesgs)) setEventMessage($mesg, $style); // Add message string if not already into array
+ setEventMessage($mesgs, $style);
+ }
+ }
+}
+
+/**
+ * Print formated messages to output (Used to show messages on html output).
+ * Note: Calling dol_htmloutput_events is done into pages by standard llxFooter() function, so there is
+ * no need to call it explicitely.
+ *
+ * @param int $disabledoutputofmessages Clear all messages stored into session without diplaying them
+ * @return void
+ * @see dol_htmloutput_mesg
+ */
+function dol_htmloutput_events($disabledoutputofmessages = 0)
+{
+ // Show mesgs
+ if (isset($_SESSION['dol_events']['mesgs'])) {
+ if (empty($disabledoutputofmessages)) dol_htmloutput_mesg('', $_SESSION['dol_events']['mesgs']);
+ unset($_SESSION['dol_events']['mesgs']);
+ }
+
+ // Show errors
+ if (isset($_SESSION['dol_events']['errors'])) {
+ if (empty($disabledoutputofmessages)) dol_htmloutput_mesg('', $_SESSION['dol_events']['errors'], 'error');
+ unset($_SESSION['dol_events']['errors']);
+ }
+
+ // Show warnings
+ if (isset($_SESSION['dol_events']['warnings'])) {
+ if (empty($disabledoutputofmessages)) dol_htmloutput_mesg('', $_SESSION['dol_events']['warnings'], 'warning');
+ unset($_SESSION['dol_events']['warnings']);
+ }
+}
+
+/**
+ * Get formated messages to output (Used to show messages on html output).
+ * This include also the translation of the message key.
+ *
+ * @param string $mesgstring Message string or message key
+ * @param string[] $mesgarray Array of message strings or message keys
+ * @param string $style Style of message output ('ok' or 'error')
+ * @param int $keepembedded Set to 1 in error message must be kept embedded into its html place (this disable jnotify)
+ * @return string Return html output
+ *
+ * @see dol_print_error
+ * @see dol_htmloutput_errors
+ * @see setEventMessages
+ */
+function get_htmloutput_mesg($mesgstring = '', $mesgarray = '', $style = 'ok', $keepembedded = 0)
+{
+ global $conf, $langs;
+
+ $ret=0; $return='';
+ $out='';
+ $divstart=$divend='';
+
+ // If inline message with no format, we add it.
+ if ((empty($conf->use_javascript_ajax) || ! empty($conf->global->MAIN_DISABLE_JQUERY_JNOTIFY) || $keepembedded) && ! preg_match('/
/i', $out))
+ {
+ $divstart='
';
+ $divend='
';
+ }
+
+ if ((is_array($mesgarray) && count($mesgarray)) || $mesgstring)
+ {
+ $langs->load("errors");
+ $out.=$divstart;
+ if (is_array($mesgarray) && count($mesgarray))
+ {
+ foreach($mesgarray as $message)
+ {
+ $ret++;
+ $out.= $langs->trans($message);
+ if ($ret < count($mesgarray)) $out.= " \n";
+ }
+ }
+ if ($mesgstring)
+ {
+ $langs->load("errors");
+ $ret++;
+ $out.= $langs->trans($mesgstring);
+ }
+ $out.=$divend;
+ }
+
+ if ($out)
+ {
+ if (! empty($conf->use_javascript_ajax) && empty($conf->global->MAIN_DISABLE_JQUERY_JNOTIFY) && empty($keepembedded))
+ {
+ $return = '';
+ }
+ else
+ {
+ $return = $out;
+ }
+ }
+
+ return $return;
+}
+
+/**
+ * Get formated error messages to output (Used to show messages on html output).
+ *
+ * @param string $mesgstring Error message
+ * @param array $mesgarray Error messages array
+ * @param int $keepembedded Set to 1 in error message must be kept embedded into its html place (this disable jnotify)
+ * @return string Return html output
+ *
+ * @see dol_print_error
+ * @see dol_htmloutput_mesg
+ */
+function get_htmloutput_errors($mesgstring = '', $mesgarray = array(), $keepembedded = 0)
+{
+ return get_htmloutput_mesg($mesgstring, $mesgarray, 'error', $keepembedded);
+}
+
+/**
+ * Print formated messages to output (Used to show messages on html output).
+ *
+ * @param string $mesgstring Message string or message key
+ * @param string[] $mesgarray Array of message strings or message keys
+ * @param string $style Which style to use ('ok', 'warning', 'error')
+ * @param int $keepembedded Set to 1 if message must be kept embedded into its html place (this disable jnotify)
+ * @return void
+ *
+ * @see dol_print_error
+ * @see dol_htmloutput_errors
+ * @see setEventMessages
+ */
+function dol_htmloutput_mesg($mesgstring = '', $mesgarray = array(), $style = 'ok', $keepembedded = 0)
+{
+ if (empty($mesgstring) && (! is_array($mesgarray) || count($mesgarray) == 0)) return;
+
+ $iserror=0;
+ $iswarning=0;
+ if (is_array($mesgarray))
+ {
+ foreach($mesgarray as $val)
+ {
+ if ($val && preg_match('/class="error"/i', $val)) { $iserror++; break; }
+ if ($val && preg_match('/class="warning"/i', $val)) { $iswarning++; break; }
+ }
+ }
+ elseif ($mesgstring && preg_match('/class="error"/i', $mesgstring)) $iserror++;
+ elseif ($mesgstring && preg_match('/class="warning"/i', $mesgstring)) $iswarning++;
+ if ($style=='error') $iserror++;
+ if ($style=='warning') $iswarning++;
+
+ if ($iserror || $iswarning)
+ {
+ // Remove div from texts
+ $mesgstring=preg_replace('/<\/div>