diff --git a/htdocs/compta/paiement.php b/htdocs/compta/paiement.php index ebf0fe6ad3d..c3ac6c18d80 100644 --- a/htdocs/compta/paiement.php +++ b/htdocs/compta/paiement.php @@ -240,7 +240,7 @@ if (empty($reshook)) if (! empty($conf->banque->enabled)) { // Si module bank actif, un compte est obligatoire lors de la saisie d'un paiement - if (GETPOST('accountid','int') <= 0) + if (GETPOST('accountid', 'int') <= 0) { setEventMessages($langs->trans('ErrorFieldRequired', $langs->transnoentities('AccountToCredit')), null, 'errors'); $error++; diff --git a/htdocs/core/class/html.form.class.php b/htdocs/core/class/html.form.class.php index 89c1746ea76..3ed57a60aec 100644 --- a/htdocs/core/class/html.form.class.php +++ b/htdocs/core/class/html.form.class.php @@ -1470,43 +1470,43 @@ class Form $contactstatic->id=$obj->rowid; $contactstatic->lastname=$obj->lastname; $contactstatic->firstname=$obj->firstname; - if ($obj->statut == 1){ - if ($htmlname != 'none') - { - $disabled=0; - if (is_array($exclude) && count($exclude) && in_array($obj->rowid, $exclude)) $disabled=1; - if (is_array($limitto) && count($limitto) && ! in_array($obj->rowid, $limitto)) $disabled=1; - if (!empty($selected) && in_array($obj->rowid, $selected)) - { - $out.= ''; - } - else - { - $out.= ''; - } - } - else - { - if (in_array($obj->rowid, $selected)) - { - $out.= $contactstatic->getFullName($langs); - if ($showfunction && $obj->poste) $out.= ' ('.$obj->poste.')'; - if (($showsoc > 0) && $obj->company) $out.= ' - ('.$obj->company.')'; - } - } - } + if ($obj->statut == 1) { + if ($htmlname != 'none') + { + $disabled=0; + if (is_array($exclude) && count($exclude) && in_array($obj->rowid, $exclude)) $disabled=1; + if (is_array($limitto) && count($limitto) && ! in_array($obj->rowid, $limitto)) $disabled=1; + if (!empty($selected) && in_array($obj->rowid, $selected)) + { + $out.= ''; + } + else + { + $out.= ''; + } + } + else + { + if (in_array($obj->rowid, $selected)) + { + $out.= $contactstatic->getFullName($langs); + if ($showfunction && $obj->poste) $out.= ' ('.$obj->poste.')'; + if (($showsoc > 0) && $obj->company) $out.= ' - ('.$obj->company.')'; + } + } + } $i++; } } diff --git a/htdocs/core/lib/functions.lib.php b/htdocs/core/lib/functions.lib.php index d7fb9322766..8918b361bde 100644 --- a/htdocs/core/lib/functions.lib.php +++ b/htdocs/core/lib/functions.lib.php @@ -4007,4 +4007,3898 @@ function print_titre($title) * Show a title with picto * * @param string $title Title to show - * @pa \ No newline at end of file + * @param string $mesg Added message to show on right + * @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 int $id To force an id on html objects + * @return void + * @deprecated Use print load_fiche_titre instead + */ +function print_fiche_titre($title, $mesg = '', $picto = 'title_generic.png', $pictoisfullpath = 0, $id = '') +{ + print load_fiche_titre($title, $mesg, $picto, $pictoisfullpath, $id); +} + +/** + * Load a title with picto + * + * @param string $titre Title to show + * @param string $morehtmlright Added message to show on right + * @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 $id To force an id on html objects + * @param string $morecssontable More css on table + * @param string $morehtmlcenter Added message to show on center + * @return string + * @see print_barre_liste + */ +function load_fiche_titre($titre, $morehtmlright = '', $picto = 'title_generic.png', $pictoisfullpath = 0, $id = '', $morecssontable = '', $morehtmlcenter = '') +{ + global $conf; + + $return=''; + + if ($picto == 'setup') $picto='title_generic.png'; + + $return.= "\n"; + $return.= ''; // maring bottom must be same than into print_barre_list + if ($picto) $return.= ''; + $return.= ''; + if (dol_strlen($morehtmlcenter)) + { + $return.= ''; + } + $return.= '
'.img_picto('', $picto, 'class="valignmiddle widthpictotitle pictotitle"', $pictoisfullpath).''; + $return.= '
'.$titre.'
'; + $return.= '
'.$morehtmlright.'
'."\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 ''; + print ''; + + // Center + if ($morehtmlcenter) + { + print ''; + } + + // Right + print ''; + + print '
'.img_picto('', $picto, 'id="pictotitle"', $pictoisfullpath).''; + if ($picto && $titre) print img_picto('', $picto, 'class="hideonsmartphone valignmiddle opacityhigh pictotitle widthpictotitle"', $pictoisfullpath); + print '
'.$titre; + if (!empty($titre) && $savtotalnboflines >= 0 && (string) $savtotalnboflines != '') print ' ('.$totalnboflines.')'; + print '
'.$morehtmlcenter.''; + 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.= 'dol_use_jmobile != 4)?' class="pagination"':'').'>1'; + if ($cpt > 2) $pagelist.='dol_use_jmobile != 4)?' class="pagination"':'').'>dol_use_jmobile != 4)?'class="inactive"':'').'>...'; + elseif ($cpt == 2) $pagelist.='dol_use_jmobile != 4)?' class="pagination"':'').'>2'; + } + + do + { + if ($cpt==$page) + { + $pagelist.= 'dol_use_jmobile != 4)?' class="pagination"':'').'>dol_use_jmobile != 4)?'class="active"':'').'>'.($page+1).''; + } + else + { + $pagelist.= 'dol_use_jmobile != 4)?' class="pagination"':'').'>'.($cpt+1).''; + } + $cpt++; + } + while ($cpt < $nbpages && $cpt<=$page+$maxnbofpage); + + if ($cpt<$nbpages) + { + if ($cpt<$nbpages-2) $pagelist.= 'dol_use_jmobile != 4)?' class="pagination"':'').'>dol_use_jmobile != 4)?'class="inactive"':'').'>...'; + elseif ($cpt == $nbpages-2) $pagelist.= 'dol_use_jmobile != 4)?' class="pagination"':'').'>'.($nbpages - 1).''; + $pagelist.= 'dol_use_jmobile != 4)?' class="pagination"':'').'>'.$nbpages.''; + } + } + else + { + $pagelist.= 'dol_use_jmobile != 4)?' class="pagination"':'').'>dol_use_jmobile != 4)?'class="active"':'').'>'.($page+1).""; + } + } + + 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 '
'."\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 ''."\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>
    /', '
    ', $mesgstring); + $mesgstring=preg_replace('/
    /', '', $mesgstring); + $mesgstring=preg_replace('/<\/div>/', '', $mesgstring); + // Remove div from texts array + if (is_array($mesgarray)) + { + $newmesgarray=array(); + foreach($mesgarray as $val) + { + $tmpmesgstring=preg_replace('/<\/div>
    /', '
    ', $val); + $tmpmesgstring=preg_replace('/
    /', '', $tmpmesgstring); + $tmpmesgstring=preg_replace('/<\/div>/', '', $tmpmesgstring); + $newmesgarray[]=$tmpmesgstring; + } + $mesgarray=$newmesgarray; + } + print get_htmloutput_mesg($mesgstring, $mesgarray, ($iserror?'error':'warning'), $keepembedded); + } + else print get_htmloutput_mesg($mesgstring, $mesgarray, 'ok', $keepembedded); +} + +/** + * Print 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 void + * + * @see dol_print_error + * @see dol_htmloutput_mesg + */ +function dol_htmloutput_errors($mesgstring = '', $mesgarray = array(), $keepembedded = 0) +{ + dol_htmloutput_mesg($mesgstring, $mesgarray, 'error', $keepembedded); +} + +/** + * Advanced sort array by second index function, which produces ascending (default) + * or descending output and uses optionally natural case insensitive sorting (which + * can be optionally case sensitive as well). + * + * @param array $array Array to sort (array of array('key','otherkey1','otherkey2'...)) + * @param string $index Key in array to use for sorting criteria + * @param int $order Sort order ('asc' or 'desc') + * @param int $natsort 1=use "natural" sort (natsort), 0=use "standard" sort (asort) + * @param int $case_sensitive 1=sort is case sensitive, 0=not case sensitive + * @param int $keepindex If 0 and index key of array to sort is a numeric, than index will be rewrote. If 1 or index key is not numeric, key for index is kept after sorting. + * @return array Sorted array + */ +function dol_sort_array(&$array, $index, $order = 'asc', $natsort = 0, $case_sensitive = 0, $keepindex = 0) +{ + // Clean parameters + $order=strtolower($order); + + if (is_array($array)) + { + $sizearray=count($array); + if ($sizearray>0) + { + $temp = array(); + foreach(array_keys($array) as $key) $temp[$key]=$array[$key][$index]; + + if (!$natsort) ($order=='asc') ? asort($temp) : arsort($temp); + else + { + ($case_sensitive) ? natsort($temp) : natcasesort($temp); + if($order!='asc') $temp=array_reverse($temp, true); + } + + $sorted = array(); + + foreach(array_keys($temp) as $key) + { + (is_numeric($key) && empty($keepindex)) ? $sorted[]=$array[$key] : $sorted[$key]=$array[$key]; + } + + return $sorted; + } + } + return $array; +} + + +/** + * Check if a string is in UTF8 + * + * @param string $str String to check + * @return boolean True if string is UTF8 or ISO compatible with UTF8, False if not (ISO with special char or Binary) + */ +function utf8_check($str) +{ + // We must use here a binary strlen function (so not dol_strlen) + $strLength = dol_strlen($str); + for ($i=0; $i<$strLength; $i++) + { + if (ord($str[$i]) < 0x80) continue; // 0bbbbbbb + elseif ((ord($str[$i]) & 0xE0) == 0xC0) $n=1; // 110bbbbb + elseif ((ord($str[$i]) & 0xF0) == 0xE0) $n=2; // 1110bbbb + elseif ((ord($str[$i]) & 0xF8) == 0xF0) $n=3; // 11110bbb + elseif ((ord($str[$i]) & 0xFC) == 0xF8) $n=4; // 111110bb + elseif ((ord($str[$i]) & 0xFE) == 0xFC) $n=5; // 1111110b + else return false; // Does not match any model + for ($j=0; $j<$n; $j++) { // n bytes matching 10bbbbbb follow ? + if ((++$i == strlen($str)) || ((ord($str[$i]) & 0xC0) != 0x80)) + return false; + } + } + return true; +} + + +/** + * Return a string encoded into OS filesystem encoding. This function is used to define + * value to pass to filesystem PHP functions. + * + * @param string $str String to encode (UTF-8) + * @return string Encoded string (UTF-8, ISO-8859-1) + */ +function dol_osencode($str) +{ + global $conf; + + $tmp=ini_get("unicode.filesystem_encoding"); // Disponible avec PHP 6.0 + if (empty($tmp) && ! empty($_SERVER["WINDIR"])) $tmp='iso-8859-1'; // By default for windows + if (empty($tmp)) $tmp='utf-8'; // By default for other + if (! empty($conf->global->MAIN_FILESYSTEM_ENCODING)) $tmp=$conf->global->MAIN_FILESYSTEM_ENCODING; + + if ($tmp == 'iso-8859-1') return utf8_decode($str); + return $str; +} + + +/** + * Return an id or code from a code or id. + * Store also Code-Id into a cache to speed up next request on same key. + * + * @param DoliDB $db Database handler + * @param string $key Code or Id to get Id or Code + * @param string $tablename Table name without prefix + * @param string $fieldkey Field to search the key into + * @param string $fieldid Field to get + * @param int $entityfilter Filter by entity + * @return int <0 if KO, Id of code if OK + * @see $langs->getLabelFromKey + */ +function dol_getIdFromCode($db, $key, $tablename, $fieldkey = 'code', $fieldid = 'id', $entityfilter = 0) +{ + global $cache_codes; + + // If key empty + if ($key == '') return ''; + + // Check in cache + if (isset($cache_codes[$tablename][$key][$fieldid])) // Can be defined to 0 or '' + { + return $cache_codes[$tablename][$key][$fieldid]; // Found in cache + } + + dol_syslog('dol_getIdFromCode (value not found into cache)', LOG_DEBUG); + + $sql = "SELECT ".$fieldid." as valuetoget"; + $sql.= " FROM ".MAIN_DB_PREFIX.$tablename; + $sql.= " WHERE ".$fieldkey." = '".$db->escape($key)."'"; + if (! empty($entityfilter)) + $sql.= " AND entity IN (" . getEntity($tablename) . ")"; + + $resql = $db->query($sql); + if ($resql) + { + $obj = $db->fetch_object($resql); + if ($obj) $cache_codes[$tablename][$key][$fieldid]=$obj->valuetoget; + else $cache_codes[$tablename][$key][$fieldid]=''; + $db->free($resql); + return $cache_codes[$tablename][$key][$fieldid]; + } + else + { + return -1; + } +} + +/** + * Verify if condition in string is ok or not + * + * @param string $strRights String with condition to check + * @return boolean True or False. Return True if strRights is '' + */ +function verifCond($strRights) +{ + global $user,$conf,$langs; + global $leftmenu; + global $rights; // To export to dol_eval function + + //print $strRights."
    \n"; + $rights = true; + if ($strRights != '') + { + $str = 'if(!(' . $strRights . ')) { $rights = false; }'; + dol_eval($str); // The dol_eval must contains all the global $xxx used into a condition + } + return $rights; +} + +/** + * Replace eval function to add more security. + * This function is called by verifCond() or trans() and transnoentitiesnoconv(). + * + * @param string $s String to evaluate + * @param int $returnvalue 0=No return (used to execute eval($a=something)). 1=Value of eval is returned (used to eval($something)). + * @param int $hideerrors 1=Hide errors + * @return mixed Nothing or return of eval + */ +function dol_eval($s, $returnvalue = 0, $hideerrors = 1) +{ + // Only global variables can be changed by eval function and returned to caller + global $db, $langs, $user, $conf, $website, $websitepage; + global $action, $mainmenu, $leftmenu; + global $rights; + global $object; + global $mysoc; + + global $obj; // To get $obj used into list when dol_eval is used for computed fields and $obj is not yet $object + global $soc; // For backward compatibility + + //print $s."
    \n"; + if ($returnvalue) + { + if ($hideerrors) return @eval('return '.$s.';'); + else return eval('return '.$s.';'); + } + else + { + if ($hideerrors) @eval($s); + else eval($s); + } +} + +/** + * Return if var element is ok + * + * @param string $element Variable to check + * @return boolean Return true of variable is not empty + */ +function dol_validElement($element) +{ + return (trim($element) != ''); +} + +/** + * Return img flag of country for a language code or country code + * + * @param string $codelang Language code (en_IN, fr_CA...) or Country code (IN, FR) + * @param string $moreatt Add more attribute on img tag (For example 'style="float: right"') + * @return string HTML img string with flag. + */ +function picto_from_langcode($codelang, $moreatt = '') +{ + global $langs; + + if (empty($codelang)) return ''; + + if ($codelang == 'auto') + { + return ''; + } + + $langtocountryflag = array( + 'ar_AR' => '', + 'ca_ES' => 'catalonia', + 'da_DA' => 'dk', + 'fr_CA' => 'mq', + 'sv_SV' => 'se' + ); + + if (isset($langtocountryflag[$codelang])) $flagImage = $langtocountryflag[$codelang]; + else + { + $tmparray = explode('_', $codelang); + $flagImage = empty($tmparray[1]) ? $tmparray[0] : $tmparray[1]; + } + + return img_picto_common($codelang, 'flags/'.strtolower($flagImage).'.png', $moreatt); +} + +/** + * Return default language from country code + * + * @param string $countrycode Country code like 'US', 'FR', 'CA', ... + * @return string Value of locale like 'en_US', 'fr_FR', ... + */ +function getLanguageCodeFromCountryCode($countrycode) +{ + global $mysoc; + + if (strtoupper($countrycode) == 'MQ') return 'fr_CA'; + if (strtoupper($countrycode) == 'SE') return 'sv_SE'; // se_SE is Sami/Sweden, and we want in priority sv_SE for SE country + if (strtoupper($countrycode) == 'CH') + { + if ($mysoc->country_code == 'FR') return 'fr_CH'; + if ($mysoc->country_code == 'DE') return 'de_CH'; + } + + // Locale list taken from: + // http://stackoverflow.com/questions/3191664/ + // list-of-all-locales-and-their-short-codes + $locales = array( + 'af-ZA', + 'am-ET', + 'ar-AE', + 'ar-BH', + 'ar-DZ', + 'ar-EG', + 'ar-IQ', + 'ar-JO', + 'ar-KW', + 'ar-LB', + 'ar-LY', + 'ar-MA', + 'ar-OM', + 'ar-QA', + 'ar-SA', + 'ar-SY', + 'ar-TN', + 'ar-YE', + 'as-IN', + 'ba-RU', + 'be-BY', + 'bg-BG', + 'bn-BD', + 'bn-IN', + 'bo-CN', + 'br-FR', + 'ca-ES', + 'co-FR', + 'cs-CZ', + 'cy-GB', + 'da-DK', + 'de-AT', + 'de-CH', + 'de-DE', + 'de-LI', + 'de-LU', + 'dv-MV', + 'el-GR', + 'en-AU', + 'en-BZ', + 'en-CA', + 'en-GB', + 'en-IE', + 'en-IN', + 'en-JM', + 'en-MY', + 'en-NZ', + 'en-PH', + 'en-SG', + 'en-TT', + 'en-US', + 'en-ZA', + 'en-ZW', + 'es-AR', + 'es-BO', + 'es-CL', + 'es-CO', + 'es-CR', + 'es-DO', + 'es-EC', + 'es-ES', + 'es-GT', + 'es-HN', + 'es-MX', + 'es-NI', + 'es-PA', + 'es-PE', + 'es-PR', + 'es-PY', + 'es-SV', + 'es-US', + 'es-UY', + 'es-VE', + 'et-EE', + 'eu-ES', + 'fa-IR', + 'fi-FI', + 'fo-FO', + 'fr-BE', + 'fr-CA', + 'fr-CH', + 'fr-FR', + 'fr-LU', + 'fr-MC', + 'fy-NL', + 'ga-IE', + 'gd-GB', + 'gl-ES', + 'gu-IN', + 'he-IL', + 'hi-IN', + 'hr-BA', + 'hr-HR', + 'hu-HU', + 'hy-AM', + 'id-ID', + 'ig-NG', + 'ii-CN', + 'is-IS', + 'it-CH', + 'it-IT', + 'ja-JP', + 'ka-GE', + 'kk-KZ', + 'kl-GL', + 'km-KH', + 'kn-IN', + 'ko-KR', + 'ky-KG', + 'lb-LU', + 'lo-LA', + 'lt-LT', + 'lv-LV', + 'mi-NZ', + 'mk-MK', + 'ml-IN', + 'mn-MN', + 'mr-IN', + 'ms-BN', + 'ms-MY', + 'mt-MT', + 'nb-NO', + 'ne-NP', + 'nl-BE', + 'nl-NL', + 'nn-NO', + 'oc-FR', + 'or-IN', + 'pa-IN', + 'pl-PL', + 'ps-AF', + 'pt-BR', + 'pt-PT', + 'rm-CH', + 'ro-RO', + 'ru-RU', + 'rw-RW', + 'sa-IN', + 'se-FI', + 'se-NO', + 'se-SE', + 'si-LK', + 'sk-SK', + 'sl-SI', + 'sq-AL', + 'sv-FI', + 'sv-SE', + 'sw-KE', + 'ta-IN', + 'te-IN', + 'th-TH', + 'tk-TM', + 'tn-ZA', + 'tr-TR', + 'tt-RU', + 'ug-CN', + 'uk-UA', + 'ur-PK', + 'vi-VN', + 'wo-SN', + 'xh-ZA', + 'yo-NG', + 'zh-CN', + 'zh-HK', + 'zh-MO', + 'zh-SG', + 'zh-TW', + 'zu-ZA', + ); + + $buildprimarykeytotest = strtolower($countrycode).'-'.strtoupper($countrycode); + if (in_array($buildprimarykeytotest, $locales)) return strtolower($countrycode).'_'.strtoupper($countrycode); + + foreach ($locales as $locale) + { + $locale_language = locale_get_primary_language($locale); + $locale_region = locale_get_region($locale); + if (strtoupper($countrycode) == $locale_region) + { + //var_dump($locale.'-'.$locale_language.'-'.$locale_region); + return strtolower($locale_language).'_'.strtoupper($locale_region); + } + } + + return null; +} + +/** + * Complete or removed entries into a head array (used to build tabs). + * For example, with value added by external modules. Such values are declared into $conf->modules_parts['tab']. + * Or by change using hook completeTabsHead + * + * @param Conf $conf Object conf + * @param Translate $langs Object langs + * @param object|null $object Object object + * @param array $head Object head + * @param int $h New position to fill + * @param string $type Value for object where objectvalue can be + * 'thirdparty' to add a tab in third party view + * 'intervention' to add a tab in intervention view + * 'supplier_order' to add a tab in supplier order view + * 'supplier_invoice' to add a tab in supplier invoice view + * 'invoice' to add a tab in customer invoice view + * 'order' to add a tab in customer order view + * 'contract' to add a tabl in contract view + * 'product' to add a tab in product view + * 'propal' to add a tab in propal view + * 'user' to add a tab in user view + * 'group' to add a tab in group view + * 'member' to add a tab in fundation member view + * 'categories_x' to add a tab in category view ('x': type of category (0=product, 1=supplier, 2=customer, 3=member) + * 'ecm' to add a tab for another ecm view + * 'stock' to add a tab for warehouse view + * @param string $mode 'add' to complete head, 'remove' to remove entries + * @return void + */ +function complete_head_from_modules($conf, $langs, $object, &$head, &$h, $type, $mode = 'add') +{ + global $hookmanager; + + if (isset($conf->modules_parts['tabs'][$type]) && is_array($conf->modules_parts['tabs'][$type])) + { + foreach ($conf->modules_parts['tabs'][$type] as $value) + { + $values=explode(':', $value); + + if ($mode == 'add' && ! preg_match('/^\-/', $values[1])) + { + if (count($values) == 6) // new declaration with permissions: $value='objecttype:+tabname1:Title1:langfile@mymodule:$user->rights->mymodule->read:/mymodule/mynewtab1.php?id=__ID__' + { + if ($values[0] != $type) continue; + + if (verifCond($values[4])) + { + if ($values[3]) $langs->load($values[3]); + if (preg_match('/SUBSTITUTION_([^_]+)/i', $values[2], $reg)) + { + $substitutionarray=array(); + complete_substitutions_array($substitutionarray, $langs, $object, array('needforkey'=>$values[2])); + $label=make_substitutions($reg[1], $substitutionarray); + } + else $label=$langs->trans($values[2]); + + $head[$h][0] = dol_buildpath(preg_replace('/__ID__/i', ((is_object($object) && ! empty($object->id))?$object->id:''), $values[5]), 1); + $head[$h][1] = $label; + $head[$h][2] = str_replace('+', '', $values[1]); + $h++; + } + } + elseif (count($values) == 5) // deprecated + { + dol_syslog('Passing 5 values in tabs module_parts is deprecated. Please update to 6 with permissions.', LOG_WARNING); + + if ($values[0] != $type) continue; + if ($values[3]) $langs->load($values[3]); + if (preg_match('/SUBSTITUTION_([^_]+)/i', $values[2], $reg)) + { + $substitutionarray=array(); + complete_substitutions_array($substitutionarray, $langs, $object, array('needforkey'=>$values[2])); + $label=make_substitutions($reg[1], $substitutionarray); + } + else $label=$langs->trans($values[2]); + + $head[$h][0] = dol_buildpath(preg_replace('/__ID__/i', ((is_object($object) && ! empty($object->id))?$object->id:''), $values[4]), 1); + $head[$h][1] = $label; + $head[$h][2] = str_replace('+', '', $values[1]); + $h++; + } + } + elseif ($mode == 'remove' && preg_match('/^\-/', $values[1])) + { + if ($values[0] != $type) continue; + $tabname=str_replace('-', '', $values[1]); + foreach($head as $key => $val) + { + $condition = (! empty($values[3]) ? verifCond($values[3]) : 1); + //var_dump($key.' - '.$tabname.' - '.$head[$key][2].' - '.$values[3].' - '.$condition); + if ($head[$key][2]==$tabname && $condition) + { + unset($head[$key]); + break; + } + } + } + } + } + + // No need to make a return $head. Var is modified as a reference + if (! empty($hookmanager)) + { + $parameters=array('object' => $object, 'mode' => $mode, 'head' => $head); + $reshook=$hookmanager->executeHooks('completeTabsHead', $parameters); + if ($reshook > 0) + { + $head = $hookmanager->resArray; + $h = count($head); + } + } +} + +/** + * Print common footer : + * conf->global->MAIN_HTML_FOOTER + * js for switch of menu hider + * js for conf->global->MAIN_GOOGLE_AN_ID + * js for conf->global->MAIN_SHOW_TUNING_INFO or $_SERVER["MAIN_SHOW_TUNING_INFO"] + * js for conf->logbuffer + * + * @param string $zone 'private' (for private pages) or 'public' (for public pages) + * @return void + */ +function printCommonFooter($zone = 'private') +{ + global $conf, $hookmanager, $user; + global $action; + global $micro_start_time; + + if ($zone == 'private') print "\n".''."\n"; + else print "\n".''."\n"; + + // A div to store page_y POST parameter so we can read it using javascript + print "\n\n"; + print ''."\n"; + + $parameters=array(); + $reshook=$hookmanager->executeHooks('printCommonFooter', $parameters); // Note that $action and $object may have been modified by some hooks + if (empty($reshook)) + { + if (! empty($conf->global->MAIN_HTML_FOOTER)) print $conf->global->MAIN_HTML_FOOTER."\n"; + + print "\n"; + if (! empty($conf->use_javascript_ajax)) + { + print ''."\n"; + } + + // Add Xdebug coverage of code + if (defined('XDEBUGCOVERAGE')) + { + print_r(xdebug_get_code_coverage()); + } + + // If there is some logs in buffer to show + if (count($conf->logbuffer)) + { + print "\n"; + print "\n"; + } + } +} + +/** + * Split a string with 2 keys into key array. + * For example: "A=1;B=2;C=2" is exploded into array('A'=>1,'B'=>2,'C'=>3) + * + * @param string $string String to explode + * @param string $delimiter Delimiter between each couple of data + * @param string $kv Delimiter between key and value + * @return array Array of data exploded + */ +function dolExplodeIntoArray($string, $delimiter = ';', $kv = '=') +{ + if ($a = explode($delimiter, $string)) + { + $ka = array(); + foreach ($a as $s) { // each part + if ($s) { + if ($pos = strpos($s, $kv)) { // key/value delimiter + $ka[trim(substr($s, 0, $pos))] = trim(substr($s, $pos + strlen($kv))); + } else { // key delimiter not found + $ka[] = trim($s); + } + } + } + return $ka; + } + return array(); +} + + +/** + * Set focus onto field with selector (similar behaviour of 'autofocus' HTML5 tag) + * + * @param string $selector Selector ('#id' or 'input[name="ref"]') to use to find the HTML input field that must get the autofocus. You must use a CSS selector, so unique id preceding with the '#' char. + * @return string HTML code to set focus + */ +function dol_set_focus($selector) +{ + print "\n".''."\n"; + print ''."\n"; +} + + +/** + * Return getmypid() or random PID when function is disabled + * Some web hosts disable this php function for security reasons + * and sometimes we can't redeclare function + * + * @return int + */ +function dol_getmypid() +{ + if (! function_exists('getmypid')) { + return mt_rand(1, 32768); + } else { + return getmypid(); + } +} + + +/** + * Generate natural SQL search string for a criteria (this criteria can be tested on one or several fields) + * + * @param string|string[] $fields String or array of strings, filled with the name of all fields in the SQL query we must check (combined with a OR). Example: array("p.field1","p.field2") + * @param string $value The value to look for. + * If param $mode is 0, can contains several keywords separated with a space or | + * like "keyword1 keyword2" = We want record field like keyword1 AND field like keyword2 + * or like "keyword1|keyword2" = We want record field like keyword1 OR field like keyword2 + * If param $mode is 1, can contains an operator <, > or = like "<10" or ">=100.5 < 1000" + * If param $mode is 2, can contains a list of int id separated by comma like "1,3,4" + * If param $mode is 3, can contains a list of string separated by comma like "a,b,c" + * @param integer $mode 0=value is list of keyword strings, 1=value is a numeric test (Example ">5.5 <10"), 2=value is a list of ID separated with comma (Example '1,3,4') + * 3=value is list of string separated with comma (Example 'text 1,text 2'), 4=value is a list of ID separated with comma (Example '1,3,4') for search into a multiselect string ('1,2') + * @param integer $nofirstand 1=Do not output the first 'AND' + * @return string $res The statement to append to the SQL query + */ +function natural_search($fields, $value, $mode = 0, $nofirstand = 0) +{ + global $db,$langs; + + $value=trim($value); + + if ($mode == 0) + { + $value=preg_replace('/\*/', '%', $value); // Replace * with % + } + if ($mode == 1) + { + $value=preg_replace('/([<>=]+)\s+([0-9'.preg_quote($langs->trans("DecimalSeparator"), '/').'\-])/', '\1\2', $value); // Clean string '< 10' into '<10' so we can the explode on space to get all tests to do + } + + $value = preg_replace('/\s*\|\s*/', '|', $value); + + $crits = explode(' ', $value); + $res = ''; + if (! is_array($fields)) $fields = array($fields); + + $nboffields = count($fields); + $end2 = count($crits); + $j = 0; + foreach ($crits as $crit) + { + $i = 0; $i2 = 0; + $newres = ''; + foreach ($fields as $field) + { + if ($mode == 1) + { + $operator='='; + $newcrit = preg_replace('/([<>=]+)/', '', trim($crit)); + + preg_match('/([<>=]+)/', trim($crit), $reg); + if ($reg[1]) + { + $operator = $reg[1]; + } + if ($newcrit != '') + { + $numnewcrit = price2num($newcrit); + if (is_numeric($numnewcrit)) + { + $newres .= ($i2 > 0 ? ' OR ' : '') . $field . ' '.$operator.' '.$numnewcrit; + } + else + { + $newres .= ($i2 > 0 ? ' OR ' : '') . '1 = 2'; // force false + } + $i2++; // a criteria was added to string + } + } + elseif ($mode == 2) + { + $newres .= ($i2 > 0 ? ' OR ' : '') . $field . " IN (" . $db->escape(trim($crit)) . ")"; + $i2++; // a criteria was added to string + } + elseif ($mode == 3) + { + $tmparray=explode(',', trim($crit)); + if (count($tmparray)) + { + $listofcodes=''; + foreach($tmparray as $val) + { + if ($val) + { + $listofcodes.=($listofcodes?',':''); + $listofcodes.="'".$db->escape(trim($val))."'"; + } + } + $newres .= ($i2 > 0 ? ' OR ' : '') . $field . " IN (" . $listofcodes . ")"; + $i2++; // a criteria was added to string + } + } + elseif ($mode == 4) + { + $tmparray=explode(',', trim($crit)); + if (count($tmparray)) + { + $listofcodes=''; + foreach($tmparray as $val) + { + if ($val) + { + $newres .= ($i2 > 0 ? ' OR (' : '(') . $field . ' LIKE \'' . $db->escape(trim($val)) . ',%\''; + $newres .= ' OR '. $field . ' = \'' . $db->escape(trim($val)) . '\''; + $newres .= ' OR '. $field . ' LIKE \'%,' . $db->escape(trim($val)) . '\''; + $newres .= ' OR '. $field . ' LIKE \'%,' . $db->escape(trim($val)) . ',%\''; + $newres .= ')'; + $i2++; + } + } + } + } + else // $mode=0 + { + $textcrit = ''; + $tmpcrits = explode('|', $crit); + $i3 = 0; + foreach($tmpcrits as $tmpcrit) + { + if(empty($tmpcrit)) continue; + + $newres .= (($i2 > 0 || $i3 > 0) ? ' OR ' : ''); + + if (preg_match('/\.(id|rowid)$/', $field)) // Special case for rowid that is sometimes a ref so used as a search field + { + $newres .= $field . " = " . (is_numeric(trim($tmpcrit))?trim($tmpcrit):'0'); + } + else + { + $newres .= $field . " LIKE '"; + + $tmpcrit=trim($tmpcrit); + $tmpcrit2=$tmpcrit; + $tmpbefore='%'; $tmpafter='%'; + if (preg_match('/^[\^\$]/', $tmpcrit)) + { + $tmpbefore=''; + $tmpcrit2 = preg_replace('/^[\^\$]/', '', $tmpcrit2); + } + if (preg_match('/[\^\$]$/', $tmpcrit)) + { + $tmpafter=''; + $tmpcrit2 = preg_replace('/[\^\$]$/', '', $tmpcrit2); + } + $newres .= $tmpbefore; + $newres .= $db->escape($tmpcrit2); + $newres .= $tmpafter; + $newres .= "'"; + if ($tmpcrit2 == '') + { + $newres .= ' OR ' . $field . " IS NULL"; + } + } + + $i3++; + } + $i2++; // a criteria was added to string + } + $i++; + } + if ($newres) $res = $res . ($res ? ' AND ' : '') . ($i2 > 1 ? '(' : '') .$newres . ($i2 > 1 ? ')' : ''); + $j++; + } + $res = ($nofirstand?"":" AND ")."(" . $res . ")"; + //print 'xx'.$res.'yy'; + return $res; +} + +/** + * Return string with full Url. The file qualified is the one defined by relative path in $object->last_main_doc + * + * @param Object $object Object + * @return string Url string + */ +function showDirectDownloadLink($object) +{ + global $conf, $langs; + + $out=''; + $url = $object->getLastMainDocLink($object->element); + + if ($url) + { + $out.= img_picto('', 'object_globe.png').' '.$langs->trans("DirectDownloadLink").'
    '; + $out.= ''; + $out.= ajax_autoselect("directdownloadlink", 0); + } + return $out; +} + +/** + * Return the filename of file to get the thumbs + * + * @param string $file Original filename (full or relative path) + * @param string $extName Extension to differenciate thumb file name ('', '_small', '_mini') + * @param string $extImgTarget Force image extension for thumbs. Use '' to keep same extension than original image (default). + * @return string New file name (full or relative path, including the thumbs/) + */ +function getImageFileNameForSize($file, $extName, $extImgTarget = '') +{ + $dirName = dirname($file); + if ($dirName == '.') $dirName=''; + + $fileName = preg_replace('/(\.gif|\.jpeg|\.jpg|\.png|\.bmp)$/i', '', $file); // We remove extension, whatever is its case + $fileName = basename($fileName); + + if (empty($extImgTarget)) $extImgTarget = (preg_match('/\.jpg$/i', $file)?'.jpg':''); + if (empty($extImgTarget)) $extImgTarget = (preg_match('/\.jpeg$/i', $file)?'.jpeg':''); + if (empty($extImgTarget)) $extImgTarget = (preg_match('/\.gif$/i', $file)?'.gif':''); + if (empty($extImgTarget)) $extImgTarget = (preg_match('/\.png$/i', $file)?'.png':''); + if (empty($extImgTarget)) $extImgTarget = (preg_match('/\.bmp$/i', $file)?'.bmp':''); + + if (! $extImgTarget) return $file; + + $subdir=''; + if ($extName) $subdir = 'thumbs/'; + + return ($dirName?$dirName.'/':'').$subdir.$fileName.$extName.$extImgTarget; // New filename for thumb +} + + +/** + * Return URL we can use for advanced preview links + * + * @param string $modulepart propal, facture, facture_fourn, ... + * @param string $relativepath Relative path of docs. + * @param int $alldata Return array with all components (1 is recommended, then use a simple a href link with the class, target and mime attribute added. 'documentpreview' css class is handled by jquery code into main.inc.php) + * @param string $param More param on http links + * @return string|array Output string with href link or array with all components of link + */ +function getAdvancedPreviewUrl($modulepart, $relativepath, $alldata = 0, $param = '') +{ + global $conf, $langs; + + if (empty($conf->use_javascript_ajax)) return ''; + + $mime_preview = array('bmp', 'jpeg', 'png', 'gif', 'tiff', 'pdf', 'plain', 'css', 'svg+xml'); + //$mime_preview[]='vnd.oasis.opendocument.presentation'; + //$mime_preview[]='archive'; + $num_mime = array_search(dol_mimetype($relativepath, '', 1), $mime_preview); + + if ($alldata == 1) + { + if ($num_mime !== false) return array('target'=>'_blank', 'css'=>'documentpreview', 'url'=>DOL_URL_ROOT.'/document.php?modulepart='.$modulepart.'&attachment=0&file='.urlencode($relativepath).($param?'&'.$param:''), 'mime'=>dol_mimetype($relativepath), ); + else return array(); + } + + // old behavior + if ($num_mime !== false) return 'javascript:document_preview(\''.dol_escape_js(DOL_URL_ROOT.'/document.php?modulepart='.$modulepart.'&attachment=0&file='.urlencode($relativepath).($param?'&'.$param:'')).'\', \''.dol_mimetype($relativepath).'\', \''.dol_escape_js($langs->trans('Preview')).'\')'; + else return ''; +} + + +/** + * Make content of an input box selected when we click into input field. + * + * @param string $htmlname Id of html object + * @param string $addlink Add a 'link to' after + * @return string + */ +function ajax_autoselect($htmlname, $addlink = '') +{ + global $langs; + $out = ''; + if ($addlink) $out.='
    '.$langs->trans("Link").''; + return $out; +} + + +/** + * Return mime type of a file + * + * @param string $file Filename we looking for MIME type + * @param string $default Default mime type if extension not found in known list + * @param int $mode 0=Return full mime, 1=otherwise short mime string, 2=image for mime type, 3=source language, 4=css of font fa + * @return string Return a mime type family (text/xxx, application/xxx, image/xxx, audio, video, archive) + * @see image_format_supported (images.lib.php) + */ +function dol_mimetype($file, $default = 'application/octet-stream', $mode = 0) +{ + $mime=$default; + $imgmime='other.png'; + $famime='file-o'; + $srclang=''; + + $tmpfile=preg_replace('/\.noexe$/', '', $file); + + // Text files + if (preg_match('/\.txt$/i', $tmpfile)) { $mime='text/plain'; $imgmime='text.png'; $famime='file-text-o'; } + if (preg_match('/\.rtx$/i', $tmpfile)) { $mime='text/richtext'; $imgmime='text.png'; $famime='file-text-o'; } + if (preg_match('/\.csv$/i', $tmpfile)) { $mime='text/csv'; $imgmime='text.png'; $famime='file-text-o'; } + if (preg_match('/\.tsv$/i', $tmpfile)) { $mime='text/tab-separated-values'; $imgmime='text.png'; $famime='file-text-o'; } + if (preg_match('/\.(cf|conf|log)$/i', $tmpfile)) { $mime='text/plain'; $imgmime='text.png'; $famime='file-text-o'; } + if (preg_match('/\.ini$/i', $tmpfile)) { $mime='text/plain'; $imgmime='text.png'; $srclang='ini'; $famime='file-text-o'; } + if (preg_match('/\.css$/i', $tmpfile)) { $mime='text/css'; $imgmime='css.png'; $srclang='css'; $famime='file-text-o'; } + // Certificate files + if (preg_match('/\.(crt|cer|key|pub)$/i', $tmpfile)) { $mime='text/plain'; $imgmime='text.png'; $famime='file-text-o'; } + // HTML/XML + if (preg_match('/\.(html|htm|shtml)$/i', $tmpfile)) { $mime='text/html'; $imgmime='html.png'; $srclang='html'; $famime='file-text-o'; } + if (preg_match('/\.(xml|xhtml)$/i', $tmpfile)) { $mime='text/xml'; $imgmime='other.png'; $srclang='xml'; $famime='file-text-o'; } + // Languages + if (preg_match('/\.bas$/i', $tmpfile)) { $mime='text/plain'; $imgmime='text.png'; $srclang='bas'; $famime='file-code-o'; } + if (preg_match('/\.(c)$/i', $tmpfile)) { $mime='text/plain'; $imgmime='text.png'; $srclang='c'; $famime='file-code-o'; } + if (preg_match('/\.(cpp)$/i', $tmpfile)) { $mime='text/plain'; $imgmime='text.png'; $srclang='cpp'; $famime='file-code-o'; } + if (preg_match('/\.(h)$/i', $tmpfile)) { $mime='text/plain'; $imgmime='text.png'; $srclang='h'; $famime='file-code-o'; } + if (preg_match('/\.(java|jsp)$/i', $tmpfile)) { $mime='text/plain'; $imgmime='text.png'; $srclang='java'; $famime='file-code-o'; } + if (preg_match('/\.php([0-9]{1})?$/i', $tmpfile)) { $mime='text/plain'; $imgmime='php.png'; $srclang='php'; $famime='file-code-o'; } + if (preg_match('/\.phtml$/i', $tmpfile)) { $mime='text/plain'; $imgmime='php.png'; $srclang='php'; $famime='file-code-o'; } + if (preg_match('/\.(pl|pm)$/i', $tmpfile)) { $mime='text/plain'; $imgmime='pl.png'; $srclang='perl'; $famime='file-code-o'; } + if (preg_match('/\.sql$/i', $tmpfile)) { $mime='text/plain'; $imgmime='text.png'; $srclang='sql'; $famime='file-code-o'; } + if (preg_match('/\.js$/i', $tmpfile)) { $mime='text/x-javascript'; $imgmime='jscript.png'; $srclang='js'; $famime='file-code-o'; } + // Open office + if (preg_match('/\.odp$/i', $tmpfile)) { $mime='application/vnd.oasis.opendocument.presentation'; $imgmime='ooffice.png'; $famime='file-powerpoint-o'; } + if (preg_match('/\.ods$/i', $tmpfile)) { $mime='application/vnd.oasis.opendocument.spreadsheet'; $imgmime='ooffice.png'; $famime='file-excel-o'; } + if (preg_match('/\.odt$/i', $tmpfile)) { $mime='application/vnd.oasis.opendocument.text'; $imgmime='ooffice.png'; $famime='file-word-o'; } + // MS Office + if (preg_match('/\.mdb$/i', $tmpfile)) { $mime='application/msaccess'; $imgmime='mdb.png'; $famime='file-o'; } + if (preg_match('/\.doc(x|m)?$/i', $tmpfile)) { $mime='application/msword'; $imgmime='doc.png'; $famime='file-word-o'; } + if (preg_match('/\.dot(x|m)?$/i', $tmpfile)) { $mime='application/msword'; $imgmime='doc.png'; $famime='file-word-o'; } + if (preg_match('/\.xlt(x)?$/i', $tmpfile)) { $mime='application/vnd.ms-excel'; $imgmime='xls.png'; $famime='file-excel-o'; } + if (preg_match('/\.xla(m)?$/i', $tmpfile)) { $mime='application/vnd.ms-excel'; $imgmime='xls.png'; $famime='file-excel-o'; } + if (preg_match('/\.xls$/i', $tmpfile)) { $mime='application/vnd.ms-excel'; $imgmime='xls.png'; $famime='file-excel-o'; } + if (preg_match('/\.xls(b|m|x)$/i', $tmpfile)) { $mime='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'; $imgmime='xls.png'; $famime='file-excel-o'; } + if (preg_match('/\.pps(m|x)?$/i', $tmpfile)) { $mime='application/vnd.ms-powerpoint'; $imgmime='ppt.png'; $famime='file-powerpoint-o'; } + if (preg_match('/\.ppt(m|x)?$/i', $tmpfile)) { $mime='application/x-mspowerpoint'; $imgmime='ppt.png'; $famime='file-powerpoint-o'; } + // Other + if (preg_match('/\.pdf$/i', $tmpfile)) { $mime='application/pdf'; $imgmime='pdf.png'; $famime='file-pdf-o'; } + // Scripts + if (preg_match('/\.bat$/i', $tmpfile)) { $mime='text/x-bat'; $imgmime='script.png'; $srclang='dos'; $famime='file-code-o'; } + if (preg_match('/\.sh$/i', $tmpfile)) { $mime='text/x-sh'; $imgmime='script.png'; $srclang='bash'; $famime='file-code-o'; } + if (preg_match('/\.ksh$/i', $tmpfile)) { $mime='text/x-ksh'; $imgmime='script.png'; $srclang='bash'; $famime='file-code-o'; } + if (preg_match('/\.bash$/i', $tmpfile)) { $mime='text/x-bash'; $imgmime='script.png'; $srclang='bash'; $famime='file-code-o'; } + // Images + if (preg_match('/\.ico$/i', $tmpfile)) { $mime='image/x-icon'; $imgmime='image.png'; $famime='file-image-o'; } + if (preg_match('/\.(jpg|jpeg)$/i', $tmpfile)) { $mime='image/jpeg'; $imgmime='image.png'; $famime='file-image-o'; } + if (preg_match('/\.png$/i', $tmpfile)) { $mime='image/png'; $imgmime='image.png'; $famime='file-image-o'; } + if (preg_match('/\.gif$/i', $tmpfile)) { $mime='image/gif'; $imgmime='image.png'; $famime='file-image-o'; } + if (preg_match('/\.bmp$/i', $tmpfile)) { $mime='image/bmp'; $imgmime='image.png'; $famime='file-image-o'; } + if (preg_match('/\.(tif|tiff)$/i', $tmpfile)) { $mime='image/tiff'; $imgmime='image.png'; $famime='file-image-o'; } + if (preg_match('/\.svg$/i', $tmpfile)) { $mime='image/svg+xml';$imgmime='image.png'; $famime='file-image-o'; } + // Calendar + if (preg_match('/\.vcs$/i', $tmpfile)) { $mime='text/calendar'; $imgmime='other.png'; $famime='file-text-o'; } + if (preg_match('/\.ics$/i', $tmpfile)) { $mime='text/calendar'; $imgmime='other.png'; $famime='file-text-o'; } + // Other + if (preg_match('/\.torrent$/i', $tmpfile)) { $mime='application/x-bittorrent'; $imgmime='other.png'; $famime='file-o'; } + // Audio + if (preg_match('/\.(mp3|ogg|au|wav|wma|mid)$/i', $tmpfile)) { $mime='audio'; $imgmime='audio.png'; $famime='file-audio-o'; } + // Video + if (preg_match('/\.ogv$/i', $tmpfile)) { $mime='video/ogg'; $imgmime='video.png'; $famime='file-video-o'; } + if (preg_match('/\.webm$/i', $tmpfile)) { $mime='video/webm'; $imgmime='video.png'; $famime='file-video-o'; } + if (preg_match('/\.avi$/i', $tmpfile)) { $mime='video/x-msvideo'; $imgmime='video.png'; $famime='file-video-o'; } + if (preg_match('/\.divx$/i', $tmpfile)) { $mime='video/divx'; $imgmime='video.png'; $famime='file-video-o'; } + if (preg_match('/\.xvid$/i', $tmpfile)) { $mime='video/xvid'; $imgmime='video.png'; $famime='file-video-o'; } + if (preg_match('/\.(wmv|mpg|mpeg)$/i', $tmpfile)) { $mime='video'; $imgmime='video.png'; $famime='file-video-o'; } + // Archive + if (preg_match('/\.(zip|rar|gz|tgz|z|cab|bz2|7z|tar|lzh)$/i', $tmpfile)) { $mime='archive'; $imgmime='archive.png'; $famime='file-archive-o'; } // application/xxx where zzz is zip, ... + // Exe + if (preg_match('/\.(exe|com)$/i', $tmpfile)) { $mime='application/octet-stream'; $imgmime='other.png'; $famime='file-o'; } + // Lib + if (preg_match('/\.(dll|lib|o|so|a)$/i', $tmpfile)) { $mime='library'; $imgmime='library.png'; $famime='file-o'; } + // Err + if (preg_match('/\.err$/i', $tmpfile)) { $mime='error'; $imgmime='error.png'; $famime='file-text-o'; } + + // Return string + if ($mode == 1) + { + $tmp=explode('/', $mime); + return (! empty($tmp[1])?$tmp[1]:$tmp[0]); + } + if ($mode == 2) + { + return $imgmime; + } + if ($mode == 3) + { + return $srclang; + } + if ($mode == 4) + { + return $famime; + } + return $mime; +} + +/** + * Return value from dictionary + * + * @param string $tablename name of dictionary + * @param string $field the value to return + * @param int $id id of line + * @param bool $checkentity add filter on entity + * @param string $rowidfield name of the column rowid + * @return string + */ +function getDictvalue($tablename, $field, $id, $checkentity = false, $rowidfield = 'rowid') +{ + global $dictvalues,$db,$langs; + + if (!isset($dictvalues[$tablename])) + { + $dictvalues[$tablename] = array(); + $sql = 'SELECT * FROM '.$tablename.' WHERE 1'; + if ($checkentity) $sql.= ' AND entity IN (0,'.getEntity($tablename).')'; + + $resql = $db->query($sql); + if ($resql) + { + while ($obj = $db->fetch_object($resql)) + { + $dictvalues[$tablename][$obj->{$rowidfield}] = $obj; + } + } + else + { + dol_print_error($db); + } + } + + if (!empty($dictvalues[$tablename][$id])) return $dictvalues[$tablename][$id]->{$field}; // Found + else // Not found + { + if ($id > 0) return $id; + return ''; + } +} + +/** + * Return true if the color is light + * + * @param string $stringcolor String with hex (FFFFFF) or comma RGB ('255,255,255') + * @return int -1 : Error with argument passed |0 : color is dark | 1 : color is light + */ +function colorIsLight($stringcolor) +{ + $res = -1; + if (!empty($stringcolor)) + { + $res = 0; + $tmp=explode(',', $stringcolor); + if (count($tmp) > 1) // This is a comma RGB ('255','255','255') + { + $r = $tmp[0]; + $g = $tmp[1]; + $b = $tmp[2]; + } + else + { + $hexr=$stringcolor[0].$stringcolor[1]; + $hexg=$stringcolor[2].$stringcolor[3]; + $hexb=$stringcolor[4].$stringcolor[5]; + $r = hexdec($hexr); + $g = hexdec($hexg); + $b = hexdec($hexb); + } + $bright = (max($r, $g, $b) + min($r, $g, $b)) / 510.0; // HSL algorithm + if ($bright > 0.6) $res = 1; + } + return $res; +} + +/** + * Function to test if an entry is enabled or not + * + * @param string $type_user 0=We test for internal user, 1=We test for external user + * @param array $menuentry Array for feature entry to test + * @param array $listofmodulesforexternal Array with list of modules allowed to external users + * @return int 0=Hide, 1=Show, 2=Show gray + */ +function isVisibleToUserType($type_user, &$menuentry, &$listofmodulesforexternal) +{ + global $conf; + + //print 'type_user='.$type_user.' module='.$menuentry['module'].' enabled='.$menuentry['enabled'].' perms='.$menuentry['perms']; + //print 'ok='.in_array($menuentry['module'], $listofmodulesforexternal); + if (empty($menuentry['enabled'])) return 0; // Entry disabled by condition + if ($type_user && $menuentry['module']) + { + $tmploops=explode('|', $menuentry['module']); + $found=0; + foreach($tmploops as $tmploop) + { + if (in_array($tmploop, $listofmodulesforexternal)) { + $found++; break; + } + } + if (! $found) return 0; // Entry is for menus all excluded to external users + } + if (! $menuentry['perms'] && $type_user) return 0; // No permissions and user is external + if (! $menuentry['perms'] && ! empty($conf->global->MAIN_MENU_HIDE_UNAUTHORIZED)) return 0; // No permissions and option to hide when not allowed, even for internal user, is on + if (! $menuentry['perms']) return 2; // No permissions and user is external + return 1; +} + +/** + * Round to next multiple. + * + * @param double $n Number to round up + * @param integer $x Multiple. For example 60 to round up to nearest exact minute for a date with seconds. + * @return integer Value rounded. + */ +function roundUpToNextMultiple($n, $x = 5) +{ + return (ceil($n)%$x === 0) ? ceil($n) : round(($n+$x/2)/$x)*$x; +} diff --git a/htdocs/core/modules/facture/doc/pdf_crabe.modules.php b/htdocs/core/modules/facture/doc/pdf_crabe.modules.php index bb9542d69f9..6d30a300726 100644 --- a/htdocs/core/modules/facture/doc/pdf_crabe.modules.php +++ b/htdocs/core/modules/facture/doc/pdf_crabe.modules.php @@ -1312,7 +1312,6 @@ class pdf_crabe extends ModelePDFFactures $pdf->MultiCell($largcol2, $tab2_hl, price($tvaval, 0, $outputlangs), 0, 'R', 1); } } - } //} // Revenue stamp