dolibarr/htdocs/core/class/commondocgenerator.class.php
Vincent Maury 9e6804de64
NEW add method confirmProduced in MOs API (#39324)
* FIX commande/stats: do not default "Created by" filter to current user (#39256)

The order statistics query is not filtered on the current user on first
page load, so the results shown are global. However the "Created by"
selector defaulted to the current user (select_dolusers auto-selects
$user->id when the passed value is empty), wrongly suggesting the
displayed figures were limited to that user.

Pass -1 instead of 0 when no user is selected so the combo keeps its
empty entry, matching the unfiltered query. The empty option posts -1,
which the query turns back into "no user filter", so the round-trip
stays consistent after a refresh.

* FIX Missing translation for "Weight"

* Fix/supplier doc keep project (#39306)

* FIX : situation invoice credit note wrong amount in progressive mode

When creating a credit note "with same lines" from a situation invoice,
the code always subtracts the previous situation invoice's line values
before inverting the sign, assuming lines are stored cumulative
(INVOICE_USE_SITUATION = 1, legacy mode).

In progressive mode (INVOICE_USE_SITUATION = 2), each situation invoice
line already stores its own delta amount and percent, so this extra
subtraction credits the wrong amount and percent (e.g. 60% / 1272
instead of the correct 20% / 424 on a real cycle).

Skip the subtraction in progressive mode so the credit note reuses the
source line's own delta values, as expected.

* FIX supplier order/proposal loses project after supplier selection

On the create form of a supplier order (fourn/commande/card.php) and a supplier
price request (supplier_proposal/card.php), the project select was filtered on
the supplier third party as soon as one was chosen: the `if ($socid > 0)` branch
took precedence over the PROJECT_CAN_ALWAYS_LINK_TO_ALL_SUPPLIERS constant.

As a result, a supplier document created from a customer project lost its
preselected project when the page reloaded after picking the supplier (the
customer project is not linked to the supplier, so it was filtered out).

This aligns the behavior with the supplier invoice (fourn/facture/card.php),
which gives priority to PROJECT_CAN_ALWAYS_LINK_TO_ALL_SUPPLIERS. The external
user restriction now targets $user->socid (the real external-user marker)
instead of $socid > 0, which also matched internal users after selecting a
supplier.

* Fix Standalone reception creation (#39299)

Fatal error: Uncaught Error: Call to a member function fetch_optionals() on null

* Fix missing include

* add method confirmProduced in MOs API

which was missing

---------

Co-authored-by: atm-corentin <165782689+atm-corentin@users.noreply.github.com>
Co-authored-by: Alexandre SPANGARO <alexandre.spangaro@gmail.com>
Co-authored-by: Alexandre SPANGARO <aspangaro.dolibarr@gmail.com>
Co-authored-by: atm-jonathan <146709163+atm-jonathan@users.noreply.github.com>
Co-authored-by: sonikf <93765174+sonikf@users.noreply.github.com>
Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
2026-07-30 18:22:44 +02:00

2187 lines
82 KiB
PHP

<?php
/* Copyright (C) 2003-2005 Rodolphe Quiedeville <rodolphe@quiedeville.org>
* Copyright (C) 2004-2010 Laurent Destailleur <eldy@users.sourceforge.net>
* Copyright (C) 2004 Eric Seigne <eric.seigne@ryxeo.com>
* Copyright (C) 2005-2012 Regis Houssin <regis.houssin@inodbox.com>
* Copyright (C) 2015 Marcos García <marcosgdf@gmail.com>
* Copyright (C) 2016-2025 Charlene Benke <charlene@patas-monkey.com>
* Copyright (C) 2018-2026 Frédéric France <frederic.france@free.fr>
* Copyright (C) 2020 Josep Lluís Amador <joseplluis@lliuretic.cat>
* Copyright (C) 2024-2025 MDW <mdeweerd@users.noreply.github.com>
* Copyright (C) 2024 Mélina Joum <melina.joum@altairis.fr>
* Copyright (C) 2024 Nick Fragoulis
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
* or see https://www.gnu.org/
*/
/**
* \file htdocs/core/class/commondocgenerator.class.php
* \ingroup core
* \brief File of parent class for documents generators
*/
/**
* Parent class for documents (PDF, ODT, ...) generators
*/
abstract class CommonDocGenerator
{
/**
* @var string Model name
*/
public $name = '';
/**
* @var string Version, possible values are: 'development', 'experimental', 'dolibarr', 'dolibarr_deprecated' or a version string like 'x.y.z'''|'development'|'dolibarr'|'experimental' Version
*/
public $version = '';
/**
* @var string Error code (or message)
*/
public $error = '';
/**
* @var string[] Array of error strings
*/
public $errors = array();
/**
* @var string[] Array of warnings strings
*/
public $warnings = array();
/**
* @var DoliDB Database handler.
*/
protected $db;
/**
* @var ?ExtraFields object
*/
public $extrafieldsCache;
/**
* @var int If set to 1, save the fullname of generated file with path as the main doc when generating a doc with this template.
*/
public $update_main_doc_field;
/**
* @var string The name of constant to use to scan ODT files (Example: 'COMMANDE_ADDON_PDF_ODT_PATH')
*/
public $scandir;
/**
* @var string model description (short text)
*/
public $description;
/**
* @var array{0:float,1:float}
*/
public $format;
/**
* @var string pdf, odt, etc
*/
public $type;
/**
* @var float page height
*/
public $page_hauteur;
/**
* @var float page wicth
*/
public $page_largeur;
/**
* @var float left margin
*/
public $marge_gauche;
/**
* @var float right margin
*/
public $marge_droite;
/**
* @var float top margin
*/
public $marge_haute;
/**
* @var float bottom margin
*/
public $marge_basse;
/**
* @var int corner radius
*/
public $corner_radius;
/**
* @var int<0,1> option logo
*/
public $option_logo;
/**
* @var int<0,1>
*/
public $option_tva;
/**
* @var int<0,1>
*/
public $option_multilang;
/**
* @var int<0,1>
*/
public $option_freetext;
/**
* @var int<0,1>
*/
public $option_draft_watermark;
/**
* @var string
*/
public $watermark;
/**
* @var int<0,1>
*/
public $option_modereg;
/**
* @var int<0,1>
*/
public $option_condreg;
/**
* @var int<0,1>
*/
public $option_escompte;
/**
* @var int<0,1>
*/
public $option_credit_note;
/**
* @var array<string,float>
*/
public $tva;
/**
* @var array<string,array{amount:float}>
*/
public $tva_array;
/**
* Local tax rates Array[tax_type][tax_rate]
*
* @var array<int,array<string,float>>
*/
public $localtax1;
/**
* Local tax rates Array[tax_type][tax_rate]
*
* @var array<int,array<string,float>>
*/
public $localtax2;
/**
* @var int Tab Title Height
*/
public $tabTitleHeight;
/**
* @var array{align?:'R'|'C'|'L',padding?:array<float|int>} default title fields style
*/
public $defaultTitlesFieldsStyle;
/**
* @var array{align?:'R'|'C'|'L',padding?:array<float|int>} default content fields style
*/
public $defaultContentsFieldsStyle;
/**
* @var Societe Issuer of document
*/
public $emetteur;
/**
* @var array{0:int,1:int} Minimum version of PHP required by module.
* e.g.: PHP ≥ 7.1 = array(7, 1)
*/
public $phpmin = array(7, 1);
/**
* @var array<string,array{rank:int,width:float|false,status:bool|int<0,1>,border-left?:bool,title:array{textkey:string,label?:string,align?:string,padding?:array{0:float,1:float,2:float,3:float}},content?:array{align?:string,padding?:array{0:float,1:float,2:float,3:float}}}> Array of document table columns
*/
public $cols;
/**
* @var array<string,array{page:int,y:float|int}> Array of position data
*/
public $afterColsLinePositions;
/**
* @var array{fullpath:string} Array with result of doc generation. content is array('fullpath'=>$file)
*/
public $result;
/**
* @var float
*/
public $posxlabel;
/**
* @var float
*/
public $posxup;
/**
* @var float
*/
public $posxref;
/**
* @var float
*/
public $posxpicture; // For picture
/**
* @var float
*/
public $posxdesc; // For description
/**
* @var float
*/
public $posxqty;
/**
* @var float
*/
public $posxpuht;
/**
* @var float
*/
public $posxtva;
/**
* @var float|int
*/
public $posxtotalht;
/**
* @var float
*/
public $postotalht;
/**
* @var float
*/
public $posxunit;
/**
* @var float
*/
public $posxdiscount;
/**
* @var float
*/
public $posxworkload;
/**
* @var float
*/
public $posxtimespent;
/**
* @var float
*/
public $posxprogress;
/**
* @var bool
*/
public $atleastonephoto;
/**
* @var int<0,1>
*/
public $atleastoneratenotnull;
/**
* @var bool|int<0,1>
*/
public $atleastonediscount;
/**
* Constructor
*
* @param DoliDB $db Database handler
*/
public function __construct($db)
{
$this->db = $db;
}
// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
/**
* Define array with couple substitution key => substitution value
*
* @param User $user User
* @param Translate $outputlangs Language object for output
* @return array<string,float|string> Array of substitution key->code
*/
public function get_substitutionarray_user($user, $outputlangs)
{
// phpcs:enable
global $conf, $extrafields;
$logotouse = $conf->user->dir_output . '/' . get_exdir(0, 0, 0, 0, $user, 'user') . 'photos/' . getImageFileNameForSize($user->photo, '_small');
$array_user = array(
'myuser_lastname' => (string) $user->lastname,
'myuser_firstname' => (string) $user->firstname,
'myuser_fullname' => $user->getFullName($outputlangs, 1),
'myuser_login' => (string) $user->login,
'myuser_phone' => (string) $user->office_phone,
'myuser_address' => (string) $user->address,
'myuser_zip' => (string) $user->zip,
'myuser_town' => (string) $user->town,
'myuser_country' => (string) $user->country,
'myuser_country_code' => (string) $user->country_code,
'myuser_state' => (string) $user->state,
'myuser_state_code' => (string) $user->state_code,
'myuser_fax' => (string) $user->office_fax,
'myuser_mobile' => (string) $user->user_mobile,
'myuser_email' => (string) $user->email,
'myuser_logo' => (string) $logotouse,
'myuser_job' => (string) $user->job,
'myuser_web' => '', // url not exist in $user object
'myuser_birth' => dol_print_date($user->birth, 'day', 'gmt'),
'myuser_dateemployment' => dol_print_date($user->dateemployment, 'day', 'tzuser'),
'myuser_dateemploymentend' => dol_print_date($user->dateemploymentend, 'day', 'tzuser'),
'myuser_gender' => (string) $user->gender,
);
// Retrieve extrafields
if (is_array($user->array_options) && count($user->array_options)) {
if (empty($extrafields->attributes[$user->table_element])) {
$extrafields->fetch_name_optionals_label($user->table_element);
}
$array_user = $this->fill_substitutionarray_with_extrafields($user, $array_user, $extrafields, 'myuser', $outputlangs);
}
return $array_user;
}
/**
* Define array with couple substitution key => substitution value
*
* @param Adherent $member Member
* @param Translate $outputlangs Language object for output
* @return array<string,float|string> Array of substitution key->code
*/
public function getSubstitutionarrayMember($member, $outputlangs)
{
global $conf, $extrafields;
if ($member->photo) {
$logotouse = $conf->member->dir_output.'/'.get_exdir(0, 0, 0, 1, $member, 'user').'/photos/'.$member->photo;
} else {
$logotouse = DOL_DOCUMENT_ROOT.'/public/theme/common/nophoto.png';
}
$array_member = array(
'mymember_lastname' => (string) $member->lastname,
'mymember_firstname' => (string) $member->firstname,
'mymember_fullname' => $member->getFullName($outputlangs, 1),
'mymember_login' => (string) $member->login,
'mymember_address' => (string) $member->address,
'mymember_zip' => (string) $member->zip,
'mymember_town' => (string) $member->town,
'mymember_country_code' => (string) $member->country_code,
'mymember_country' => (string) $member->country,
'mymember_state_code' => (string) $member->state_code,
'mymember_state' => (string) $member->state,
'mymember_phone_perso' => (string) $member->phone_perso,
'mymember_phone_pro' => (string) $member->phone,
'mymember_phone_mobile' => (string) $member->phone_mobile,
'mymember_email' => (string) $member->email,
'mymember_logo' => $logotouse,
'mymember_gender' => (string) $member->gender,
'mymember_birth_locale' => dol_print_date($member->birth, 'day', 'tzuser', $outputlangs),
'mymember_birth' => dol_print_date($member->birth, 'day', 'tzuser'),
);
// Retrieve extrafields
if (is_array($member->array_options) && count($member->array_options)) {
$array_member = $this->fill_substitutionarray_with_extrafields($member, $array_member, $extrafields, 'mymember', $outputlangs);
}
return $array_member;
}
// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
/**
* Define array with couple substitution key => substitution value
*
* @param Societe $mysoc Object thirdparty
* @param Translate $outputlangs Language object for output
* @return array<string,mixed> Array of substitution key->code
*/
public function get_substitutionarray_mysoc($mysoc, $outputlangs)
{
// phpcs:enable
global $conf;
if (empty($mysoc->forme_juridique) && !empty($mysoc->forme_juridique_code)) {
$mysoc->forme_juridique = getFormeJuridiqueLabel((string) $mysoc->forme_juridique_code);
}
if (empty($mysoc->country) && !empty($mysoc->country_code)) {
$mysoc->country = $outputlangs->transnoentitiesnoconv("Country".$mysoc->country_code);
}
if (empty($mysoc->state) && !empty($mysoc->state_code)) {
$state_id = dol_getIdFromCode($this->db, $mysoc->state_code, 'c_departements', 'code_departement', 'rowid');
$mysoc->state = getState($state_id, '0');
}
$logotouse = $conf->mycompany->dir_output.'/logos/thumbs/'.$mysoc->logo_small;
return array(
'mycompany_logo' => $logotouse,
'mycompany_name' => $mysoc->name,
'mycompany_email' => $mysoc->email,
'mycompany_phone' => $mysoc->phone,
'mycompany_fax' => $mysoc->fax,
'mycompany_address' => $mysoc->address,
'mycompany_zip' => $mysoc->zip,
'mycompany_town' => $mysoc->town,
'mycompany_country' => $mysoc->country,
'mycompany_country_code' => $mysoc->country_code,
'mycompany_state' => $mysoc->state,
'mycompany_state_code' => $mysoc->state_code,
'mycompany_web' => $mysoc->url,
'mycompany_juridicalstatus' => $mysoc->forme_juridique,
'mycompany_managers' => $mysoc->managers,
'mycompany_capital' => $mysoc->capital,
'mycompany_barcode' => $mysoc->barcode,
'mycompany_idprof1' => $mysoc->idprof1,
'mycompany_idprof2' => $mysoc->idprof2,
'mycompany_idprof3' => $mysoc->idprof3,
'mycompany_idprof4' => $mysoc->idprof4,
'mycompany_idprof5' => $mysoc->idprof5,
'mycompany_idprof6' => $mysoc->idprof6,
'mycompany_vatnumber' => $mysoc->tva_intra,
'mycompany_socialobject' => $mysoc->socialobject,
'mycompany_note_private' => $mysoc->note_private,
//'mycompany_note_public'=>$mysoc->note_public, // Only private not exists for "mysoc" but both for thirdparties
);
}
// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
/**
* Define array with couple substitution key => substitution value
* For example {company_name}, {company_name_alias}
*
* @param ?Societe $object Object
* @param Translate $outputlangs Language object for output
* @param string $array_key Name of the key for return array
* @return array<string,mixed> Array of substitution key->code
*/
public function get_substitutionarray_thirdparty($object, $outputlangs, $array_key = 'company')
{
// phpcs:enable
global $extrafields;
if (!is_object($object)) {
return array();
}
if (empty($object->country) && !empty($object->country_code)) {
$object->country = $outputlangs->transnoentitiesnoconv("Country".$object->country_code);
}
if (empty($object->state) && !empty($object->state_code)) {
$state_id = dol_getIdFromCode($this->db, $object->state_code, 'c_departements', 'code_departement', 'rowid');
$object->state = getState($state_id, '0');
}
$array_thirdparty = array(
'company_name' => (string) $object->name,
'company_name_alias' => (string) $object->name_alias,
'company_email' => (string) $object->email,
'company_phone' => (string) $object->phone,
'company_fax' => (string) $object->fax,
'company_address' => (string) $object->address,
'company_zip' => (string) $object->zip,
'company_town' => (string) $object->town,
'company_country' => (string) $object->country,
'company_country_code' => (string) $object->country_code,
'company_state' => (string) $object->state,
'company_state_code' => (string) $object->state_code,
'company_web' => (string) $object->url,
'company_barcode' => (string) $object->barcode,
'company_vatnumber' => (string) $object->tva_intra,
'company_customercode' => (string) $object->code_client,
'company_suppliercode' => (string) $object->code_fournisseur,
'company_customeraccountancycode' => (string) $object->code_compta_client,
'company_supplieraccountancycode' => (string) $object->code_compta_fournisseur,
'company_juridicalstatus' => (string) $object->forme_juridique,
'company_outstanding_limit' => (string) $object->outstanding_limit,
'company_capital' => (string) $object->capital,
'company_capital_formated' => price($object->capital, 0, '', 1, -1),
'company_idprof1' => (string) $object->idprof1,
'company_idprof2' => (string) $object->idprof2,
'company_idprof3' => (string) $object->idprof3,
'company_idprof4' => (string) $object->idprof4,
'company_idprof5' => (string) $object->idprof5,
'company_idprof6' => (string) $object->idprof6,
'company_note_public' => (string) $object->note_public,
'company_note_private' => (string) $object->note_private,
'company_default_bank_iban' => (is_object($object->bank_account) ? (string) $object->bank_account->iban : ''),
'company_default_bank_bic' => (is_object($object->bank_account) ? (string) $object->bank_account->bic : '')
);
// Retrieve extrafields
if (is_array($object->array_options) && count($object->array_options)) {
$object->fetch_optionals();
$array_thirdparty = $this->fill_substitutionarray_with_extrafields($object, $array_thirdparty, $extrafields, $array_key, $outputlangs);
}
return $array_thirdparty;
}
// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
/**
* Define array with couple substitution key => substitution value
*
* @param Contact $object contact
* @param Translate $outputlangs object for output
* @param string $array_key Name of the key for return array
* @return array<string,mixed> Array of substitution key->code
*/
public function get_substitutionarray_contact($object, $outputlangs, $array_key = 'object')
{
// phpcs:enable
global $extrafields;
if (empty($object->country) && !empty($object->country_code)) {
$object->country = $outputlangs->transnoentitiesnoconv("Country".$object->country_code);
}
if (empty($object->state) && !empty($object->state_code)) {
$state_id = dol_getIdFromCode($this->db, $object->state_code, 'c_departements', 'code_departement', 'rowid');
$object->state = getState($state_id, '0');
}
$array_contact = array(
$array_key.'_fullname' => $object->getFullName($outputlangs, 1),
$array_key.'_lastname' => $object->lastname,
$array_key.'_firstname' => $object->firstname,
$array_key.'_address' => $object->address,
$array_key.'_zip' => $object->zip,
$array_key.'_town' => $object->town,
$array_key.'_state_id' => $object->state_id,
$array_key.'_state_code' => $object->state_code,
$array_key.'_state' => $object->state,
$array_key.'_country_id' => $object->country_id,
$array_key.'_country_code' => $object->country_code,
$array_key.'_country' => $object->country,
$array_key.'_poste' => $object->poste,
$array_key.'_socid' => $object->socid,
$array_key.'_statut' => $object->statut ? $object->statut : $object->status,
$array_key.'_code' => $object->code,
$array_key.'_email' => $object->email,
$array_key.'_phone_pro' => $object->phone_pro,
$array_key.'_phone_perso' => $object->phone_perso,
$array_key.'_phone_mobile' => $object->phone_mobile,
$array_key.'_fax' => $object->fax,
$array_key.'_birthday_locale' => (!empty($object->birthday) ? dol_print_date($object->birthday, 'day', false, $outputlangs) : ''),
$array_key.'_birthday' => $object->birthday,
$array_key.'_default_lang' => $object->default_lang,
$array_key.'_note_public' => $object->note_public,
$array_key.'_note_private' => $object->note_private,
$array_key.'_civility' => $object->civility,
);
// Retrieve extrafields
if (is_array($object->array_options) && count($object->array_options)) {
$object->fetch_optionals();
$array_contact = $this->fill_substitutionarray_with_extrafields($object, $array_contact, $extrafields, $array_key, $outputlangs);
}
return $array_contact;
}
// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
/**
* Define array with couple substitution key => substitution value
*
* @param Translate $outputlangs Language object for output
* @return array<string,mixed> Array of substitution key->code
*/
public function get_substitutionarray_other($outputlangs)
{
// phpcs:enable
global $conf;
$now = dol_now('gmt'); // gmt
$array_other = array(
// Date in default language
'current_date' => dol_print_date($now, 'day', 'tzuser'),
'current_datehour' => dol_print_date($now, 'dayhour', 'tzuser'),
'current_server_date' => dol_print_date($now, 'day', 'tzserver'),
'current_server_datehour' => dol_print_date($now, 'dayhour', 'tzserver'),
// Date in requested output language
'current_date_locale' => dol_print_date($now, 'day', 'tzuser', $outputlangs),
'current_datehour_locale' => dol_print_date($now, 'dayhour', 'tzuser', $outputlangs),
'current_server_date_locale' => dol_print_date($now, 'day', 'tzserver', $outputlangs),
'current_server_datehour_locale' => dol_print_date($now, 'dayhour', 'tzserver', $outputlangs),
);
foreach ($conf->global as $key => $val) {
if (isASecretKey($key)) {
$newval = '*****forbidden*****';
} else {
$newval = $val;
}
$array_other['__['.$key.']__'] = $newval;
}
return $array_other;
}
// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
/**
* Define array with couple substitution key => substitution value
* Note that vars into substitutions array are formatted.
*
* @param CommonObject $object Main object to use as data source
* @param Translate $outputlangs Lang object to use for output
* @param string $array_key Name of the key for return array
* @return array<string,mixed> Array of substitution
*/
public function get_substitutionarray_object($object, $outputlangs, $array_key = 'object')
{
// phpcs:enable
global $extrafields;
$totalpaid = $totaldeposits = $totalcreditnotes = '';
$already_payed_all = 0;
if ($object->element == 'facture') {
/** @var Facture $object */
'@phan-var-force Facture $object';
$invoice_source = new Facture($this->db);
if ($object->fk_facture_source > 0) {
$invoice_source->fetch($object->fk_facture_source);
}
$totalpaid = $object->getSommePaiement();
$totaldeposits = $object->getSumDepositsUsed();
$totalcreditnotes = $object->getSumCreditNotesUsed();
$already_payed_all = $totalpaid + $totaldeposits + $totalcreditnotes;
}
// Ignore notice for deprecated date - @phan-suppress-next-line PhanUndeclaredProperty
$date = (isset($object->element) && $object->element == 'contrat' && isset($object->date_contrat)) ? $object->date_contrat : (isset($object->date) ? $object->date : null);
if ($object instanceof CommandeFournisseur) {
$object->date_validation = $object->date_valid;
$object->date_commande = $object->date;
}
$resarray = array(
$array_key.'_id' => $object->id,
$array_key.'_ref' => (property_exists($object, 'ref') ? $object->ref : ''),
$array_key.'_label' => (property_exists($object, 'label') ? $object->label : ''), // @phan-suppress-current-line PhanUndeclaredProperty
$array_key.'_ref_ext' => (property_exists($object, 'ref_ext') ? $object->ref_ext : ''),
$array_key.'_ref_customer' => (!empty($object->ref_client) ? $object->ref_client : (empty($object->ref_customer) ? '' : $object->ref_customer)),
$array_key.'_ref_supplier' => (!empty($object->ref_fournisseur) ? $object->ref_fournisseur : (empty($object->ref_supplier) ? '' : $object->ref_supplier)), // @phan-suppress-current-line PhanUndeclaredProperty
$array_key.'_source_invoice_ref' => ((empty($invoice_source) || empty($invoice_source->ref)) ? '' : $invoice_source->ref),
// Dates
$array_key.'_hour' => dol_print_date($date, 'hour'),
$array_key.'_date' => dol_print_date($date, 'day'),
$array_key.'_date_rfc' => dol_print_date($date, 'dayrfc'),
$array_key.'_date_limit' => (!empty($object->date_lim_reglement) ? dol_print_date($object->date_lim_reglement, 'day') : ''),
$array_key.'_date_limit_rfc' => (!empty($object->date_lim_reglement) ? dol_print_date($object->date_lim_reglement, 'dayrfc') : ''),
$array_key.'_date_end' => (!empty($object->fin_validite) ? dol_print_date($object->fin_validite, 'day') : ''), // @phan-suppress-current-line PhanUndeclaredProperty
$array_key.'_date_creation' => dol_print_date($object->date_creation, 'day'),
$array_key.'_date_modification' => (!empty($object->date_modification) ? dol_print_date($object->date_modification, 'day') : ''),
$array_key.'_date_validation' => (!empty($object->date_validation) ? dol_print_date($object->date_validation, 'dayhour') : ''),
$array_key.'_date_approve' => (!empty($object->date_approve) ? dol_print_date($object->date_approve, 'day') : ''),
$array_key.'_date_delivery_planed' => (!empty($object->delivery_date) ? dol_print_date($object->delivery_date, 'day') : ''),
$array_key.'_date_close' => (!empty($object->date_cloture) ? dol_print_date($object->date_cloture, 'dayhour') : ''),
$array_key.'_payment_mode_code' => $object->mode_reglement_code,
$array_key.'_payment_mode' => ($outputlangs->transnoentitiesnoconv('PaymentType'.$object->mode_reglement_code) != 'PaymentType'.$object->mode_reglement_code ? $outputlangs->transnoentitiesnoconv('PaymentType'.$object->mode_reglement_code) : $object->mode_reglement),
$array_key.'_payment_term_code' => $object->cond_reglement_code,
$array_key.'_payment_term' => ($outputlangs->transnoentitiesnoconv('PaymentCondition'.$object->cond_reglement_code) != 'PaymentCondition'.$object->cond_reglement_code ? $outputlangs->transnoentitiesnoconv('PaymentCondition'.$object->cond_reglement_code) : $object->cond_reglement_doc),
$array_key.'_incoterms' => (method_exists($object, 'display_incoterms') ? $object->display_incoterms() : ''),
$array_key.'_total_ht_locale' => price($object->total_ht, 0, $outputlangs),
$array_key.'_total_vat_locale' => (!empty($object->total_vat) ? price($object->total_vat, 0, $outputlangs) : price($object->total_tva, 0, $outputlangs)), // @phan-suppress-current-line PhanUndeclaredProperty
$array_key.'_total_localtax1_locale' => price($object->total_localtax1, 0, $outputlangs),
$array_key.'_total_localtax2_locale' => price($object->total_localtax2, 0, $outputlangs),
$array_key.'_total_ttc_locale' => price($object->total_ttc, 0, $outputlangs),
$array_key.'_total_ht' => price2num($object->total_ht),
$array_key.'_total_vat' => (!empty($object->total_vat) ? price2num($object->total_vat) : price2num($object->total_tva)), // @phan-suppress-current-line PhanUndeclaredProperty
$array_key.'_total_localtax1' => price2num($object->total_localtax1),
$array_key.'_total_localtax2' => price2num($object->total_localtax2),
$array_key.'_total_ttc' => price2num($object->total_ttc),
$array_key.'_multicurrency_code' => $object->multicurrency_code,
$array_key.'_multicurrency_tx' => price2num($object->multicurrency_tx),
$array_key.'_multicurrency_total_ht' => price2num($object->multicurrency_total_ht),
$array_key.'_multicurrency_total_tva' => price2num($object->multicurrency_total_tva),
$array_key.'_multicurrency_total_ttc' => price2num($object->multicurrency_total_ttc),
$array_key.'_multicurrency_total_ht_locale' => price($object->multicurrency_total_ht, 0, $outputlangs),
$array_key.'_multicurrency_total_tva_locale' => price($object->multicurrency_total_tva, 0, $outputlangs),
$array_key.'_multicurrency_total_ttc_locale' => price($object->multicurrency_total_ttc, 0, $outputlangs),
$array_key.'_note_private' => $object->note_private,
$array_key.'_note_public' => $object->note_public,
$array_key.'_note' => $object->note_public, // For backward compatibility
// Payments
$array_key.'_already_payed_locale' => price($totalpaid, 0, $outputlangs),
$array_key.'_already_payed' => price2num($totalpaid),
$array_key.'_already_deposit_locale' => price($totaldeposits, 0, $outputlangs),
$array_key.'_already_deposit' => price2num($totaldeposits),
$array_key.'_already_creditnote_locale' => price($totalcreditnotes, 0, $outputlangs),
$array_key.'_already_creditnote' => price2num($totalcreditnotes),
$array_key.'_already_payed_all_locale' => price(price2num($already_payed_all, 'MT'), 0, $outputlangs),
$array_key.'_already_payed_all' => price2num($already_payed_all, 'MT'),
// Remain to pay with all known information (except open direct debit requests)
$array_key.'_remain_to_pay_locale' => price(price2num($object->total_ttc - $already_payed_all, 'MT'), 0, $outputlangs),
$array_key.'_remain_to_pay' => price2num($object->total_ttc - $already_payed_all, 'MT')
);
if (in_array($object->element, array('facture', 'invoice', 'supplier_invoice', 'facture_fournisseur', 'commande'))) {
$bank_account = null;
if (property_exists($object, 'fk_account') && $object->fk_account > 0) {
require_once DOL_DOCUMENT_ROOT.'/compta/bank/class/account.class.php';
$bank_account = new Account($this->db);
$bank_account->fetch((int) $object->fk_account);
}
$resarray[$array_key.'_bank_iban'] = (empty($bank_account) ? '' : $bank_account->iban);
$resarray[$array_key.'_bank_bic'] = (empty($bank_account) ? '' : $bank_account->bic);
$resarray[$array_key.'_bank_label'] = (empty($bank_account) ? '' : $bank_account->label);
$resarray[$array_key.'_bank_number'] = (empty($bank_account) ? '' : $bank_account->number);
$resarray[$array_key.'_bank_proprio'] = (empty($bank_account) ? '' : $bank_account->owner_name);
$resarray[$array_key.'_bank_address'] = (empty($bank_account) ? '' : $bank_account->address);
$resarray[$array_key.'_bank_state'] = (empty($bank_account) ? '' : $bank_account->state);
$resarray[$array_key.'_bank_country'] = (empty($bank_account) ? '' : $bank_account->country);
}
if (method_exists($object, 'getTotalDiscount') && in_array(get_class($object), array('Propal', 'Proposal', 'Commande', 'Facture', 'SupplierProposal', 'CommandeFournisseur', 'FactureFournisseur'))) {
$resarray[$array_key.'_total_discount_ht_locale'] = price($object->getTotalDiscount(), 0, $outputlangs);
$resarray[$array_key.'_total_discount_ht'] = price2num($object->getTotalDiscount());
} else {
$resarray[$array_key.'_total_discount_ht_locale'] = '';
$resarray[$array_key.'_total_discount_ht'] = '';
}
if ($object->element == 'facture' || $object->element == 'invoice_supplier') {
if ($object->type == 0) {
$resarray[$array_key.'_type_label'] = $outputlangs->transnoentities("PdfInvoiceTitle");
} else {
$resarray[$array_key.'_type_label'] = (empty($object)) ? '' : $object->getLibType(0);
}
}
// Fetch project information if there is a project assigned to this object
if ($object->element != "project" && !empty($object->fk_project) && $object->fk_project > 0) {
if (!is_object($object->project)) {
$object->fetchProject();
}
$resarray[$array_key.'_project_ref'] = $object->project->ref;
$resarray[$array_key.'_project_title'] = $object->project->title;
$resarray[$array_key.'_project_description'] = $object->project->description;
$resarray[$array_key.'_project_date_start'] = dol_print_date($object->project->date_start, 'day');
$resarray[$array_key.'_project_date_end'] = dol_print_date($object->project->date_end, 'day');
} else { // empty replacement
$resarray[$array_key.'_project_ref'] = '';
$resarray[$array_key.'_project_title'] = '';
$resarray[$array_key.'_project_description'] = '';
$resarray[$array_key.'_project_date_start'] = '';
$resarray[$array_key.'_project_date_end'] = '';
}
// Add vat by rates
if (is_array($object->lines) && count($object->lines) > 0) {
$totalUp = 0;
// Set substitution keys for different VAT rates
foreach ($object->lines as $line) {
// $line->tva_tx format depends on database field accuracy, no reliable. This is kept for backward compatibility
if (empty($resarray[$array_key.'_total_vat_'.$line->tva_tx])) {
$resarray[$array_key.'_total_vat_'.$line->tva_tx] = 0;
}
$resarray[$array_key.'_total_vat_'.$line->tva_tx] += $line->total_tva;
$resarray[$array_key.'_total_vat_locale_'.$line->tva_tx] = price($resarray[$array_key.'_total_vat_'.$line->tva_tx]);
// $vatformated is vat without not expected chars (so 20, or 8.5 or 5.99 for example)
$vatformated = vatrate($line->tva_tx);
if (empty($resarray[$array_key.'_total_vat_'.$vatformated])) {
$resarray[$array_key.'_total_vat_'.$vatformated] = 0;
}
$resarray[$array_key.'_total_vat_'.$vatformated] += $line->total_tva;
$resarray[$array_key.'_total_vat_locale_'.$vatformated] = price($resarray[$array_key.'_total_vat_'.$vatformated]);
$totalUp += $line->subprice * $line->qty;
}
// Calculate total up and total discount percentage
// Note that this added fields does not match a field into database in Dolibarr (Dolibarr manage discount on lines not as a global property of object)
$resarray['object_total_up'] = $totalUp;
$resarray['object_total_up_locale'] = price($resarray['object_total_up'], 0, $outputlangs);
if (method_exists($object, 'getTotalDiscount') && in_array(get_class($object), array('Propal', 'Proposal', 'Commande', 'Facture', 'SupplierProposal', 'CommandeFournisseur', 'FactureFournisseur'))) {
$totalDiscount = $object->getTotalDiscount();
} else {
$totalDiscount = 0;
}
if (!empty($totalUp) && !empty($totalDiscount)) {
$resarray['object_total_discount'] = round(100 / $totalUp * $totalDiscount, 2);
$resarray['object_total_discount_locale'] = price($resarray['object_total_discount'], 0, $outputlangs);
} else {
$resarray['object_total_discount'] = '';
$resarray['object_total_discount_locale'] = '';
}
}
// Retrieve extrafields
if (is_array($object->array_options) && count($object->array_options)) {
$object->fetch_optionals();
$resarray = $this->fill_substitutionarray_with_extrafields($object, $resarray, $extrafields, $array_key, $outputlangs);
}
return $resarray;
}
// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
/**
* Define array with couple substitution key => substitution value
* Note that vars into substitutions array are formatted.
*
* @param CommonObjectLine $line Object line
* @param Translate $outputlangs Translate object to use for output
* @param int $linenumber The number of the line for the substitution of "object_line_pos"
* @return array<string,mixed> Return a substitution array
*/
public function get_substitutionarray_lines($line, $outputlangs, $linenumber = 0)
{
// phpcs:enable
$resarray = array(
'line_pos' => $linenumber,
'line_fulldesc' => doc_getlinedesc($line, $outputlangs),
'line_product_ref' => (empty($line->product_ref) ? '' : $line->product_ref),
'line_product_label' => (empty($line->product_label) ? '' : $line->product_label),
'line_product_type' => (empty($line->product_type) ? '' : $line->product_type),
'line_product_barcode' => (empty($line->product_barcode) ? '' : $line->product_barcode),
'line_product_desc' => (empty($line->product_desc) ? '' : $line->product_desc),
'line_desc' => $line->desc,
'line_vatrate' => vatrate($line->tva_tx, true, (int) $line->info_bits),
'line_up' => price2num($line->subprice),
'line_up_locale' => price($line->subprice, 0, $outputlangs),
'line_total_up' => price2num($line->subprice * (float) $line->qty),
'line_total_up_locale' => price($line->subprice * (float) $line->qty, 0, $outputlangs),
'line_qty' => $line->qty,
'line_qty_locale' => price($line->qty),
'line_discount_percent' => ($line->remise_percent ? $line->remise_percent.'%' : ''),
'line_price_ht' => price2num($line->total_ht),
'line_price_ttc' => price2num($line->total_ttc),
'line_price_vat' => price2num($line->total_tva),
'line_price_ht_locale' => price($line->total_ht, 0, $outputlangs),
'line_price_ttc_locale' => price($line->total_ttc, 0, $outputlangs),
'line_price_vat_locale' => price($line->total_tva, 0, $outputlangs),
'line_multicurrency_code' => price2num($line->multicurrency_code),
'line_multicurrency_subprice' => price2num($line->multicurrency_subprice),
'line_multicurrency_total_ht' => price2num($line->multicurrency_total_ht),
'line_multicurrency_total_tva' => price2num($line->multicurrency_total_tva),
'line_multicurrency_total_ttc' => price2num($line->multicurrency_total_ttc),
'line_multicurrency_subprice_locale' => price($line->multicurrency_subprice, 0, $outputlangs),
'line_multicurrency_total_ht_locale' => price($line->multicurrency_total_ht, 0, $outputlangs),
'line_multicurrency_total_tva_locale' => price($line->multicurrency_total_tva, 0, $outputlangs),
'line_multicurrency_total_ttc_locale' => price($line->multicurrency_total_ttc, 0, $outputlangs),
);
if (property_exists($line, 'ref_fourn')) {
$resarray['line_product_ref_fourn'] = $line->ref_fourn; // for supplier doc lines @phan-suppress-current-line PhanUndeclaredProperty
} else {
$resarray['line_product_ref_fourn'] = '';
}
$vat_specs = array(
'line_localtax1_rate' => 'localtax1_tx',
'line_localtax2_rate' => 'localtax2_tx',
);
foreach ($vat_specs as $vat_key => $vat_spec) {
if (property_exists($line, $vat_spec)) {
// @phan-suppress-next-line PhanUndeclaredProperty
$resarray[$vat_key] = vatrate($line->$vat_spec);
}
}
// Dates (fields not available on all line classes)
$date_specs = array(
array('line_date_start', 'date_start', 'day', 'auto', null),
array('line_date_start_locale', 'date_start', 'day', 'tzserver', $outputlangs),
array('line_date_start_rfc', 'date_start', 'dayrfc', 'auto', null),
array('line_date_start_real', 'date_start_real', 'day', 'auto', null),
array('line_date_start_real_locale', 'date_start_real', 'day', 'tzserver', $outputlangs),
array('line_date_start_real_rfc', 'date_start_real', 'dayrfc', 'auto', null),
array('line_date_end', 'date_end', 'day', 'auto', null),
array('line_date_end_locale', 'date_end', 'day', 'tzserver', $outputlangs),
array('line_date_end_rfc', 'date_end', 'dayrfc', 'auto', null)
);
foreach ($date_specs as $date_spec) {
$propertyname = $date_spec[1];
if (property_exists($line, $propertyname)) {
// @phan-suppress-next-line PhanUndeclaredProperty
$resarray[$date_spec[0]] = dol_print_date($line->$propertyname, $date_spec[2], $date_spec[3], $date_spec[4]);
}
}
// Units
if (getDolGlobalInt('PRODUCT_USE_UNITS')) {
$resarray['line_unit'] = $line->getLabelOfUnit('long', $outputlangs);
$resarray['line_unit_short'] = $line->getLabelOfUnit('short', $outputlangs);
//$resarray['line_unit_code'] = $line->getLabelOfUnit('code', $outputlangs);
}
// Retrieve extrafields
$extrafieldkey = $line->table_element;
$array_key = "line";
require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
$extrafields = new ExtraFields($this->db);
$extrafields->fetch_name_optionals_label($extrafieldkey, true);
$line->fetch_optionals();
$resarray = $this->fill_substitutionarray_with_extrafields($line, $resarray, $extrafields, $array_key, $outputlangs);
// Check if the current line belongs to a supplier order
if (get_class($line) == 'CommandeFournisseurLigne') {
// Add the product supplier extrafields to the substitutions
$extrafields->fetch_name_optionals_label("product_fournisseur_price");
$extralabels = $extrafields->attributes["product_fournisseur_price"]['label'];
if (!empty($extralabels) && is_array($extralabels)) {
$columns = "";
foreach ($extralabels as $key => $label) {
$columns .= "$key, ";
}
if ($columns != "") {
$columns = substr($columns, 0, strlen($columns) - 2);
$resql = $this->db->query("SELECT ".$columns." FROM ".$this->db->prefix()."product_fournisseur_price_extrafields AS ex INNER JOIN ".$this->db->prefix()."product_fournisseur_price AS f ON ex.fk_object = f.rowid WHERE f.ref_fourn = '".$this->db->escape($line->ref_supplier)."'");
if ($this->db->num_rows($resql) > 0) {
$resql = $this->db->fetch_object($resql);
foreach ($extralabels as $key => $label) {
$resarray['line_product_supplier_'.$key] = $resql->$key;
}
}
}
}
}
// Check if the current line belongs to a shipment
if (get_class($line) == 'ExpeditionLigne') {
$resarray['line_qty_shipped'] = $line->qty_shipped;
$resarray['line_qty_asked'] = $line->qty_asked;
$resarray['line_weight'] = empty($line->weight) ? '' : $line->weight * $line->qty_shipped.' '.measuringUnitString(0, 'weight', $line->weight_units);
$resarray['line_length'] = empty($line->length) ? '' : $line->length * $line->qty_shipped.' '.measuringUnitString(0, 'size', $line->length_units);
$resarray['line_surface'] = empty($line->surface) ? '' : $line->surface * $line->qty_shipped.' '.measuringUnitString(0, 'surface', $line->surface_units);
$resarray['line_volume'] = empty($line->volume) ? '' : $line->volume * $line->qty_shipped.' '.measuringUnitString(0, 'volume', $line->volume_units);
}
// Load product data optional fields to the line -> enables to use "line_options_{extrafield}"
if (isset($line->fk_product) && $line->fk_product > 0) {
$tmpproduct = new Product($this->db);
$result = $tmpproduct->fetch($line->fk_product);
if (!empty($tmpproduct->array_options) && is_array($tmpproduct->array_options)) {
foreach ($tmpproduct->array_options as $key => $label) {
$resarray["line_product_".$key] = $label;
}
}
} else {
// Set unused placeholders as blank
$extrafields->fetch_name_optionals_label("product");
if ($extrafields->attributes["product"]['count'] > 0) {
$extralabels = $extrafields->attributes["product"]['label'];
foreach ($extralabels as $key => $label) {
$resarray['line_product_options_'.$key] = '';
}
}
}
return $resarray;
}
// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
/**
* Define array with couple substitution key => substitution value
* Note that vars into substitutions array are formatted.
*
* @param Expedition $object Main object to use as data source
* @param Translate $outputlangs Lang object to use for output
* @param string $array_key Name of the key for return array
* @return array<string,mixed> Array of substitution
*/
public function get_substitutionarray_shipment($object, $outputlangs, $array_key = 'object')
{
// phpcs:enable
global $extrafields;
include_once DOL_DOCUMENT_ROOT.'/core/lib/product.lib.php';
$object->list_delivery_methods((int) $object->shipping_method_id);
$calculatedVolume = ((float) $object->trueWidth * (float) $object->trueHeight * (float) $object->trueDepth);
$array_shipment = array(
$array_key.'_id' => $object->id,
$array_key.'_ref' => $object->ref,
$array_key.'_ref_ext' => $object->ref_ext,
$array_key.'_ref_customer' => $object->ref_customer,
$array_key.'_date_delivery' => dol_print_date($object->date_delivery, 'day'), // note: for shipment, delivery and reception: date_delivery, for orders: delivery_date
$array_key.'_hour_delivery' => dol_print_date($object->date_delivery, 'hour'),
$array_key.'_date_creation' => dol_print_date($object->date_creation, 'day'),
$array_key.'_total_ht' => price($object->total_ht),
$array_key.'_total_vat' => price($object->total_tva),
$array_key.'_total_ttc' => price($object->total_ttc),
$array_key.'_total_discount_ht' => price($object->getTotalDiscount()),
$array_key.'_note_private' => $object->note_private,
$array_key.'_note' => $object->note_public,
$array_key.'_tracking_number' => $object->tracking_number,
$array_key.'_tracking_url' => $object->tracking_url,
$array_key.'_shipping_method' => $object->listmeths[0]['libelle'],
$array_key.'_weight' => $object->trueWeight.' '.measuringUnitString(0, 'weight', $object->weight_units),
$array_key.'_width' => $object->trueWidth.' '.measuringUnitString(0, 'size', $object->width_units),
$array_key.'_height' => $object->trueHeight.' '.measuringUnitString(0, 'size', $object->height_units),
$array_key.'_depth' => $object->trueDepth.' '.measuringUnitString(0, 'size', $object->depth_units),
$array_key.'_size' => $calculatedVolume.' '.measuringUnitString(0, 'volume'),
);
// Add vat by rates
foreach ($object->lines as $line) {
if (empty($array_shipment[$array_key.'_total_vat_'.$line->tva_tx])) {
$array_shipment[$array_key.'_total_vat_'.$line->tva_tx] = 0;
}
$array_shipment[$array_key.'_total_vat_'.$line->tva_tx] += $line->total_tva;
}
// Retrieve extrafields
if (is_array($object->array_options) && count($object->array_options)) {
$object->fetch_optionals();
$array_shipment = $this->fill_substitutionarray_with_extrafields($object, $array_shipment, $extrafields, $array_key, $outputlangs);
}
// Add info from $object->origin_object which has been loaded by fetch() of shipment
if ($object->origin_type == 'commande' && is_object($object->origin_object) && !empty($object->origin_object->ref)) {
$originOrder = $object->origin_object;
'@phan-var-force Commande $originOrder';
$array_shipment['order_ref'] = $originOrder->ref;
$array_shipment['order_ref_customer'] = $originOrder->ref_customer;
}
// Load dim data
$tmparray = $object->getTotalWeightVolume();
$totalWeight = $tmparray['weight'];
$totalVolume = $tmparray['volume'];
$totalOrdered = $tmparray['ordered'];
$totalToShip = $tmparray['toship'];
// Set trueVolume and volume_units not currently stored into database
if ($object->trueWidth && $object->trueHeight && $object->trueDepth) {
$object->trueVolume = $object->trueWidth * $object->trueHeight * $object->trueDepth;
$object->volume_units = $object->size_units * 3;
}
$array_shipment[$array_key.'_total_ordered'] = (string) $totalOrdered;
$array_shipment[$array_key.'_total_toship'] = (string) $totalToShip;
if ($object->trueWeight) {
$array_shipment[$array_key.'_total_weight'] = (empty($totalWeight)) ? '' : showDimensionInBestUnit($object->trueWeight, (int) $object->weight_units, "weight", $outputlangs);
} elseif (!empty($totalWeight)) {
$array_shipment[$array_key.'_total_weight'] = showDimensionInBestUnit($totalWeight, 0, "weight", $outputlangs, -1, 'no', 1);
} else {
$array_shipment[$array_key.'_total_weight'] = "";
}
if (!empty($object->trueVolume)) {
if ($object->volume_units < 50) {
$array_shipment[$array_key.'_total_volume'] = (empty($totalVolume)) ? '' : showDimensionInBestUnit($object->trueVolume, $object->volume_units, "volume", $outputlangs);
} else {
$array_shipment[$array_key.'_total_volume'] = (empty($totalVolume)) ? '' : price($object->trueVolume, 0, $outputlangs, 0, 0).' '.measuringUnitString(0, "volume", $object->volume_units);
}
} elseif (!empty($totalVolume)) {
$array_shipment[$array_key.'_total_volume'] = showDimensionInBestUnit($totalVolume, 0, "volume", $outputlangs, -1, 'no', 1);
} else {
$array_shipment[$array_key.'_total_volume'] = "";
}
return $array_shipment;
}
// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
/**
* Define array with couple substitution key => substitution value
* @phpstan-template T
*
* @phpstan-param T $object
* @param array<string,CommonObject|float|int|string>|CommonObject $object Dolibarr Object
* @param Translate $outputlangs Language object for output
* @param boolean|int $recursive Want to fetch child array or child object.
* @return array<string,mixed> Array of substitution key->code
*/
public function get_substitutionarray_each_var_object(&$object, $outputlangs, $recursive = 1)
{
// phpcs:enable
$array_other = array();
if ((is_array($object) && count($object)) || is_object($object)) {
// Loop on each entry of array or on each property of object
foreach ($object as $key => $value) {
if (in_array($key, array('db', 'fields', 'lines', 'modelpdf', 'model_pdf'))) { // discard some properties
continue;
}
if (!empty($value)) {
if (!is_array($value) && !is_object($value)) {
$array_other['object_'.$key] = $value;
} elseif (is_array($value) && $recursive) {
$tmparray = $this->get_substitutionarray_each_var_object($value, $outputlangs, 0);
if (!empty($tmparray) && is_array($tmparray)) {
foreach ($tmparray as $key2 => $value2) {
$array_other['object_'.$key.'_'.preg_replace('/^object_/', '', $key2)] = $value2;
}
}
} elseif (is_object($value) && $recursive) {
$tmparray = $this->get_substitutionarray_each_var_object($value, $outputlangs, 0);
if (!empty($tmparray) && is_array($tmparray)) {
foreach ($tmparray as $key2 => $value2) {
$array_other['object_'.$key.'_'.preg_replace('/^object_/', '', $key2)] = $value2;
}
}
}
}
}
}
return $array_other;
}
// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
/**
* Fill array with couple extrafield key => extrafield value
* Note that vars into substitutions array are formatted.
*
* @param CommonObject $object Object with extrafields (must have $object->array_options filled)
* @param array<string,null|int|float|string> $array_to_fill Substitution array
* @param ExtraFields $extrafields ExtraFields object
* @param string $array_key Prefix for name of the keys into returned array
* @param Translate $outputlangs Lang object to use for output
* @return array<string,float|string> Substitution array
*/
public function fill_substitutionarray_with_extrafields($object, $array_to_fill, $extrafields, $array_key, $outputlangs)
{
// phpcs:enable
global $conf;
if ($extrafields->attributes[$object->table_element]['count'] > 0) {
foreach ($extrafields->attributes[$object->table_element]['label'] as $key => $label) {
$formatedarrayoption = $object->array_options;
if ($extrafields->attributes[$object->table_element]['type'][$key] == 'price') {
$formatedarrayoption['options_'.$key] = price2num($formatedarrayoption['options_'.$key]);
$formatedarrayoption['options_'.$key.'_currency'] = price($formatedarrayoption['options_'.$key], 0, $outputlangs, 0, 0, -1, $conf->currency);
//Add value to store price with currency
$array_to_fill = array_merge($array_to_fill, array($array_key.'_options_'.$key.'_currency' => $formatedarrayoption['options_'.$key.'_currency']));
} elseif ($extrafields->attributes[$object->table_element]['type'][$key] == 'select') {
$valueofselectkey = $formatedarrayoption['options_'.$key];
if (array_key_exists($valueofselectkey, $extrafields->attributes[$object->table_element]['param'][$key]['options'])) {
$formatedarrayoption['options_'.$key] = $extrafields->attributes[$object->table_element]['param'][$key]['options'][$valueofselectkey];
} else {
$formatedarrayoption['options_'.$key] = '';
}
} elseif ($extrafields->attributes[$object->table_element]['type'][$key] == 'checkbox') {
$valArray = explode(',', $formatedarrayoption['options_'.$key]);
$output = array();
foreach ($extrafields->attributes[$object->table_element]['param'][$key]['options'] as $keyopt => $valopt) {
if (in_array($keyopt, $valArray)) {
$output[] = $valopt;
}
}
$formatedarrayoption['options_'.$key] = implode(', ', $output);
} elseif ($extrafields->attributes[$object->table_element]['type'][$key] == 'date') {
if (strlen($formatedarrayoption['options_'.$key]) > 0) {
$date = $formatedarrayoption['options_'.$key];
$formatedarrayoption['options_'.$key] = dol_print_date($date, 'day'); // using company output language
$formatedarrayoption['options_'.$key.'_locale'] = dol_print_date($date, 'day', 'tzserver', $outputlangs); // using output language format
$formatedarrayoption['options_'.$key.'_rfc'] = dol_print_date($date, 'dayrfc'); // international format
} else {
$formatedarrayoption['options_'.$key] = '';
$formatedarrayoption['options_'.$key.'_locale'] = '';
$formatedarrayoption['options_'.$key.'_rfc'] = '';
}
$array_to_fill = array_merge($array_to_fill, array($array_key.'_options_'.$key.'_locale' => $formatedarrayoption['options_'.$key.'_locale']));
$array_to_fill = array_merge($array_to_fill, array($array_key.'_options_'.$key.'_rfc' => $formatedarrayoption['options_'.$key.'_rfc']));
} elseif ($extrafields->attributes[$object->table_element]['type'][$key] == 'datetime') {
$datetime = $formatedarrayoption['options_'.$key];
$formatedarrayoption['options_'.$key] = ($datetime != "0000-00-00 00:00:00" ? dol_print_date($datetime, 'dayhour') : ''); // using company output language
$formatedarrayoption['options_'.$key.'_locale'] = ($datetime != "0000-00-00 00:00:00" ? dol_print_date($datetime, 'dayhour', 'tzserver', $outputlangs) : ''); // using output language format
$formatedarrayoption['options_'.$key.'_rfc'] = ($datetime != "0000-00-00 00:00:00" ? dol_print_date($datetime, 'dayhourrfc') : ''); // international format
$array_to_fill = array_merge($array_to_fill, array($array_key.'_options_'.$key.'_locale' => $formatedarrayoption['options_'.$key.'_locale']));
$array_to_fill = array_merge($array_to_fill, array($array_key.'_options_'.$key.'_rfc' => $formatedarrayoption['options_'.$key.'_rfc']));
} elseif ($extrafields->attributes[$object->table_element]['type'][$key] == 'link') {
$id = $formatedarrayoption['options_'.$key];
if ($id != "") {
$param = $extrafields->attributes[$object->table_element]['param'][$key];
$param_list = array_keys($param['options']); // $param_list='ObjectName:classPath'
$InfoFieldList = explode(":", $param_list[0]);
$classname = $InfoFieldList[0];
$classpath = $InfoFieldList[1];
if (!empty($classpath)) {
dol_include_once($InfoFieldList[1]);
if ($classname && class_exists($classname)) {
$tmpobject = new $classname($this->db);
'@phan-var-force CommonObject $tmpobject';
$tmpobject->fetch($id);
// completely replace the id with the linked object name
$formatedarrayoption['options_'.$key] = $tmpobject->name;
}
}
}
}
if (array_key_exists('options_'.$key, $formatedarrayoption)) {
$array_to_fill = array_merge($array_to_fill, array($array_key.'_options_'.$key => $formatedarrayoption['options_'.$key]));
} else {
$array_to_fill = array_merge($array_to_fill, array($array_key.'_options_'.$key => ''));
}
}
}
return $array_to_fill;
}
/**
* Rect pdf
*
* @param TCPDI|TCPDF $pdf Pdf object
* @param float $x Abscissa of first point
* @param float $y Ordinate of first point
* @param float $l ??
* @param float $h ??
* @param int<-1,1> $hidetop 1=Hide top bar of array and title, 0=Hide nothing, -1=Hide only title
* @param int<0,1> $hidebottom Hide bottom
* @return void
*/
public function printRect($pdf, $x, $y, $l, $h, $hidetop = 0, $hidebottom = 0)
{
if (empty($hidetop) || $hidetop == -1) {
$pdf->line($x, $y, $x + $l, $y);
}
$pdf->line($x + $l, $y, $x + $l, $y + $h);
if (empty($hidebottom)) {
$pdf->line($x + $l, $y + $h, $x, $y + $h);
}
$pdf->line($x, $y + $h, $x, $y);
}
/**
* Print a rounded rectangle on the PDF
*
* @param TCPDF $pdf Object PDF
* @param float $x Abscissa of first point
* @param float $y Ordinate of first point
* @param float $w Width of the rectangle
* @param float $h Height of the rectangle
* @param float $r Corner radius (can be an array for different radii per corner)
* @param int<-1,1> $hidetop 1=Hide top bar of array and title, 0=Hide nothing, -1=Hide only title
* @param int $hidebottom Hide bottom
* @param string $style Draw style (e.g. 'D' for draw, 'F' for fill, 'DF' for both)
* @return void
*/
public function printRoundedRect($pdf, $x, $y, $w, $h, $r, $hidetop = 0, $hidebottom = 0, $style = 'D')
{
// Top line
if (empty($hidetop) || $hidetop == -1) {
$pdf->RoundedRect($x, $y, $w, $h, $r, '1111', $style);
} else {
// Draw rounded rectangle with hidden top side
$pdf->RoundedRect($x, $y, $w, $h, $r, '0111', $style);
}
if (!empty($hidebottom)) {
$pdf->RoundedRect($x, $y, $w, $h, $r, '1101', $style);
}
}
/**
* Get position in PDF after col display
* @return false|array{page:int,y:float|int,col:string}
*/
public function getMaxAfterColsLinePositionsData()
{
if (empty($this->afterColsLinePositions) || !is_array($this->afterColsLinePositions)) {
return false;
}
$colId = '';
$maxPage = $maxY = 0;
foreach ($this->afterColsLinePositions as $colKey => $value) {
if ($value['page'] > $maxPage) {
$colId = $colKey;
$maxPage = $value['page'];
$maxY = $value['y']; // if page is higher we need to reset y to new max page y
} elseif ($value['page'] == $maxPage) {
$maxY = max($value['y'], $maxY);
$colId = $colKey;
}
}
return [
'col' => $colId,
'y' => $maxY,
'page' => $maxPage
];
}
/**
* Used for reset afterColsLinePositions var in start of a new pdf draw line loop
* @param float $y the new $y position usually get by TCPDF::GetY()
* @param int $pageNumb the page number to reset at
* @return void
*/
public function resetAfterColsLinePositionsData(float $y, int $pageNumb)
{
$this->afterColsLinePositions = [];
$this->setAfterColsLinePositionsData('startLine', $y, $pageNumb);
}
/**
* Used for to set afterColsLinePositions var in a pdf draw line loop
* @param string $colId the column id used as key in $this->cols or an unique id code like startLine or separateLine ....
* @param float $y the $y position usually get by TCPDF::GetY() where print data ended
* @param int $pageNumb the page number where print data ended
* @return void
*/
public function setAfterColsLinePositionsData(string $colId, float $y, int $pageNumb)
{
$this->afterColsLinePositions[$colId] = [
'page' => $pageNumb,
'y' => $y
];
}
/**
* uasort callback function to Sort columns fields
*
* @param array{rank?:int} $a PDF lines array fields configs
* @param array{rank?:int} $b PDF lines array fields configs
* @return int<-1,1> Return compare result
*/
public function columnSort($a, $b)
{
if (empty($a['rank'])) {
$a['rank'] = 0;
}
if (empty($b['rank'])) {
$b['rank'] = 0;
}
if ($a['rank'] == $b['rank']) {
return 0;
}
return ($a['rank'] > $b['rank']) ? -1 : 1;
}
/**
* Prepare Array Column Field
*
* @param CommonObject $object common object
* @param Translate $outputlangs langs
* @param int<0,1> $hidedetails Do not show line details
* @param int<0,1> $hidedesc Do not show desc
* @param int<0,1> $hideref Do not show ref
* @return void
*/
public function prepareArrayColumnField($object, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0)
{
$this->defineColumnField($object, $outputlangs, $hidedetails, $hidedesc, $hideref);
// Sorting
uasort($this->cols, array($this, 'columnSort'));
// Positioning
$curX = $this->page_largeur - $this->marge_droite; // start from right
// Array width
$arrayWidth = $this->page_largeur - $this->marge_droite - $this->marge_gauche;
// Count flexible column
$totalDefinedColWidth = 0;
$countFlexCol = 0;
foreach ($this->cols as $colKey => & $colDef) {
if (!$this->getColumnStatus($colKey)) {
continue; // continue if disabled
}
if (!empty($colDef['scale'])) {
// In case of column width is defined by percentage
$colDef['width'] = abs($arrayWidth * $colDef['scale'] / 100);
}
if (empty($colDef['width'])) {
$countFlexCol++;
} else {
$totalDefinedColWidth += $colDef['width'];
}
}
foreach ($this->cols as $colKey => & $colDef) {
// setting empty conf with default
if (!empty($colDef['title'])) {
$colDef['title'] = array_replace($this->defaultTitlesFieldsStyle, $colDef['title']);
} else {
$colDef['title'] = $this->defaultTitlesFieldsStyle;
}
// setting empty conf with default
if (!empty($colDef['content'])) {
$colDef['content'] = array_replace($this->defaultContentsFieldsStyle, $colDef['content']);
} else {
$colDef['content'] = $this->defaultContentsFieldsStyle;
}
if ($this->getColumnStatus($colKey)) {
// In case of flexible column
if (empty($colDef['width'])) {
$colDef['width'] = abs(($arrayWidth - $totalDefinedColWidth)) / $countFlexCol;
}
// Set positions
$lastX = $curX;
$curX = $lastX - $colDef['width'];
$colDef['xStartPos'] = $curX;
$colDef['xEndPos'] = $lastX;
}
}
}
/**
* get column content width from column key
*
* @param string $colKey the column key
* @return float width in mm
*/
public function getColumnContentWidth($colKey)
{
$colDef = $this->cols[$colKey];
return $colDef['width'] - $colDef['content']['padding'][3] - $colDef['content']['padding'][1];
}
/**
* get column content X (abscissa) left position from column key
*
* @param string $colKey the column key
* @return float X position in mm
*/
public function getColumnContentXStart($colKey)
{
$colDef = (isset($this->cols[$colKey]) ? $this->cols[$colKey] : null);
return (is_array($colDef) ? ((isset($colDef['xStartPos']) ? $colDef['xStartPos'] : 0) + $colDef['content']['padding'][3]) : 0);
}
/**
* get column position rank from column key
*
* @param string $colKey the column key
* @return int rank on success and -1 on error
*/
public function getColumnRank($colKey)
{
if (!isset($this->cols[$colKey]['rank'])) {
return -1;
}
return $this->cols[$colKey]['rank'];
}
/**
* get column position rank from column key
*
* @param string $newColKey the new column key
* @param array{rank?:int} $defArray a single column definition array
* @param string $targetCol target column used to place the new column beside
* @param bool $insertAfterTarget insert before or after target column ?
* @return int new rank on success and -1 on error
*/
public function insertNewColumnDef($newColKey, $defArray, $targetCol = '', $insertAfterTarget = false)
{
// prepare wanted rank
$rank = -1;
// try to get rank from target column
if (!empty($targetCol)) {
$rank = $this->getColumnRank($targetCol);
if ($rank >= 0 && $insertAfterTarget) {
$rank++;
}
}
// get rank from new column definition
if ($rank < 0 && !empty($defArray['rank'])) {
$rank = $defArray['rank'];
}
// error: no rank
if ($rank < 0) {
return -1;
}
foreach ($this->cols as $colKey => & $colDef) {
if ($rank <= $colDef['rank']) {
$colDef['rank'] += 1;
}
}
$defArray['rank'] = $rank;
$this->cols[$newColKey] = $defArray; // array_replace is used to preserve keys
return $rank;
}
/**
* print standard column content
*
* @param TCPDI|TCPDF $pdf Pdf object
* @param float $curY current Y position
* @param string $colKey the column key
* @param string $columnText column text
* @return int Return integer <0 if KO, >= if OK
*/
public function printStdColumnContent($pdf, &$curY, $colKey, $columnText = '')
{
global $hookmanager;
$parameters = array(
'curY' => &$curY,
'columnText' => &$columnText,
'colKey' => $colKey,
'pdf' => &$pdf,
);
$reshook = $hookmanager->executeHooks('printStdColumnContent', $parameters, $this); // Note that $action and $object may have been modified by hook
if ($reshook > 0 && isset($hookmanager->resArray['columnText'])) {
$columnText = $hookmanager->resArray['columnText'];
}
if ($reshook < 0) {
setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
}
if (!$reshook || $reshook > 0) {
if (empty($columnText)) {
return 0;
}
$pdf->SetXY($this->getColumnContentXStart($colKey), $curY); // Set current position
$colDef = $this->cols[$colKey];
// save current cell padding
$curentCellPaddinds = $pdf->getCellPaddings();
// set cell padding with column content definition
$pdf->setCellPaddings(isset($colDef['content']['padding'][3]) ? $colDef['content']['padding'][3] : 0, isset($colDef['content']['padding'][0]) ? $colDef['content']['padding'][0] : 0, isset($colDef['content']['padding'][1]) ? $colDef['content']['padding'][1] : 0, isset($colDef['content']['padding'][2]) ? $colDef['content']['padding'][2] : 0);
$pdf->writeHTMLCell($colDef['width'], 2, isset($colDef['xStartPos']) ? $colDef['xStartPos'] : 0, $curY, $columnText, 0, 1, false, true, $colDef['content']['align']);
$this->setAfterColsLinePositionsData($colKey, $pdf->GetY(), $pdf->getPage());
// restore cell padding
$pdf->setCellPaddings($curentCellPaddinds['L'], $curentCellPaddinds['T'], $curentCellPaddinds['R'], $curentCellPaddinds['B']);
}
return 0;
}
/**
* print description column content
*
* @param TCPDI|TCPDF $pdf Pdf object
* @param float $curY current Y position
* @param string $colKey the column key
* @param CommonObject $object CommonObject
* @param int $i the $object->lines array key
* @param Translate $outputlangs Output language
* @param int<0,1> $hideref hide ref
* @param int<0,1> $hidedesc hide desc
* @param int<0,1> $issupplierline if object needx supplier product
* @return void
*/
public function printColDescContent($pdf, &$curY, $colKey, $object, $i, $outputlangs, $hideref = 0, $hidedesc = 0, $issupplierline = 0)
{
global $hookmanager;
// load desc col params
$colDef = $this->cols[$colKey];
// save current cell padding
$curentCellPaddinds = $pdf->getCellPaddings();
// set cell padding with column content definition
$pdf->setCellPaddings($colDef['content']['padding'][3], $colDef['content']['padding'][0], $colDef['content']['padding'][1], $colDef['content']['padding'][2]);
// line description
pdf_writelinedesc($pdf, $object, $i, $outputlangs, $colDef['width'], 3, $colDef['xStartPos'], $curY, $hideref, $hidedesc, $issupplierline, empty($colDef['content']['align']) ? 'J' : $colDef['content']['align']);
$posYAfterDescription = $pdf->GetY() - $colDef['content']['padding'][0];
// restore cell padding
$pdf->setCellPaddings($curentCellPaddinds['L'], $curentCellPaddinds['T'], $curentCellPaddinds['R'], $curentCellPaddinds['B']);
// Display extrafield if needed
$params = array(
'display' => 'list',
'printableEnable' => array(3),
'printableEnableNotEmpty' => array(4)
);
$extrafieldDesc = $this->getExtrafieldsInHtml($object->lines[$i], $outputlangs, $params);
if (!empty($extrafieldDesc)) {
$this->printStdColumnContent($pdf, $posYAfterDescription, $colKey, $extrafieldDesc);
}
$parameters = array(
'curY' => &$curY,
'colKey' => $colKey,
'object' => $object,
'i' => $i,
'outputlangs' => $outputlangs,
'pdf' => &$pdf,
);
$reshook = $hookmanager->executeHooks('printColDescContent', $parameters, $this); // Note that $action and $object may have been modified by hook
if ($reshook < 0) {
setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
}
}
/**
* get extrafield content for pdf writeHtmlCell compatibility
* usage for PDF line columns and object note block
*
* @param CommonObject $object Common object
* @param string $extrafieldKey The extrafield key
* @param Translate $outputlangs The output langs (if value is __(XXX)__ we use it to translate it).
* @return string
*/
public function getExtrafieldContent($object, $extrafieldKey, $outputlangs = null)
{
global $hookmanager;
if (empty($object->table_element)) {
return '';
}
$extrafieldsKeyPrefix = "options_";
// Cleanup extrafield key to remove prefix if present
$pos = strpos($extrafieldKey, $extrafieldsKeyPrefix);
if ($pos === 0) {
$extrafieldKey = substr($extrafieldKey, strlen($extrafieldsKeyPrefix));
}
$extrafieldOptionsKey = $extrafieldsKeyPrefix.$extrafieldKey;
// Load extra fields if they haven't been loaded already.
if (is_null($this->extrafieldsCache)) {
$this->extrafieldsCache = new ExtraFields($this->db);
}
if (empty($this->extrafieldsCache->attributes[$object->table_element])) {
$this->extrafieldsCache->fetch_name_optionals_label($object->table_element);
}
$extrafields = $this->extrafieldsCache;
$extrafieldOutputContent = '';
if (isset($object->array_options[$extrafieldOptionsKey])) {
$extrafieldOutputContent = $extrafields->showOutputField($extrafieldKey, $object->array_options[$extrafieldOptionsKey], '', $object->table_element, $outputlangs);
}
// TODO : allow showOutputField to be pdf public friendly, ex: in a link to object, clean getNomUrl to remove link and images... like a getName methode ...
if ($extrafields->attributes[$object->table_element]['type'][$extrafieldKey] == 'link') {
// for lack of anything better we cleanup all html tags
$extrafieldOutputContent = dol_string_nohtmltag($extrafieldOutputContent);
}
// Display stars extrafield as simple string
if ($extrafields->attributes[$object->table_element]['type'][$extrafieldKey] == 'stars') {
$extrafieldOutputContent = '';
for ($i = 0; $i < $object->array_options[$extrafieldOptionsKey]; $i++) {
$extrafieldOutputContent .= ' *';
}
}
$parameters = array(
'object' => $object,
'extrafields' => $extrafields,
'extrafieldKey' => $extrafieldKey,
'extrafieldOutputContent' => & $extrafieldOutputContent
);
$reshook = $hookmanager->executeHooks('getPDFExtrafieldContent', $parameters, $this); // Note that $action and $object may have been modified by hook
if ($reshook < 0) {
setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
}
if ($reshook) {
$extrafieldOutputContent = $hookmanager->resPrint;
}
return $extrafieldOutputContent;
}
/**
* Display extrafields columns content on documents
*
* @param CommonObject|CommonObjectLine $object line of common object
* @param Translate $outputlangs Output language
* @param array<string,mixed> $params array of additional parameters
* @return string Html string
*/
public function getExtrafieldsInHtml($object, $outputlangs, $params = array())
{
global $hookmanager;
if (empty($object->table_element)) {
return "";
}
// Load extrafields if not already done
if (is_null($this->extrafieldsCache)) {
include_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
$this->extrafieldsCache = new ExtraFields($this->db);
}
if (empty($this->extrafieldsCache->attributes[$object->table_element])) {
$this->extrafieldsCache->fetch_name_optionals_label($object->table_element);
}
$extrafields = $this->extrafieldsCache;
$defaultParams = array(
'style' => '',
'display' => 'auto', // auto, table, list
'printableEnable' => array(1),
'printableEnableNotEmpty' => array(2),
'table' => array(
'maxItemsInRow' => 2,
'cellspacing' => 0,
'cellpadding' => 0,
'border' => 0,
'labelcolwidth' => '25%',
'arrayOfLineBreakType' => array('text', 'html')
),
'list' => array(
'separator' => '<br>'
),
'auto' => array(
'list' => 0, // 0 for default
'table' => 4 // if there more than x extrafield to display
),
);
$params += $defaultParams;
/**
* @var ExtraFields $extrafields
*/
$html = '';
$fields = array();
if (!empty($extrafields->attributes[$object->table_element]['label']) && is_array($extrafields->attributes[$object->table_element]['label'])) {
foreach ($extrafields->attributes[$object->table_element]['label'] as $key => $label) {
// Enable extrafield ?
$enabled = 0;
if (!empty($extrafields->attributes[$object->table_element]['enabled'][$key])) {
$enabled = (int) dol_eval((string) $extrafields->attributes[$object->table_element]['enabled'][$key], 1, 1, '2');
}
if (!$enabled) {
continue;
}
// Reset enabled: only printable attribute determines PDF visibility
$enabled = 0;
$disableOnEmpty = 0;
$printable = 0;
if (!empty($extrafields->attributes[$object->table_element]['printable'][$key])) {
$printable = (int) $extrafields->attributes[$object->table_element]['printable'][$key];
if (in_array($printable, $params['printableEnable']) || in_array($printable, $params['printableEnableNotEmpty'])) {
$enabled = 1;
}
if (in_array($printable, $params['printableEnableNotEmpty'])) {
$disableOnEmpty = 1;
}
}
if (empty($enabled) || empty($printable)) {
continue;
}
// Load language if required
if (!empty($extrafields->attributes[$object->table_element]['langfile'][$key])) {
$outputlangs->load($extrafields->attributes[$object->table_element]['langfile'][$key]);
}
$field = new stdClass();
$field->rank = intval($extrafields->attributes[$object->table_element]['pos'][$key]);
$field->content = $this->getExtrafieldContent($object, $key, $outputlangs);
if (isset($extrafields->attributes[$object->table_element]['langfile'][$key])) {
$outputlangs->load($extrafields->attributes[$object->table_element]['langfile'][$key]);
}
$field->label = $outputlangs->transnoentities($label);
$field->type = $extrafields->attributes[$object->table_element]['type'][$key];
// don't display if empty
if ($disableOnEmpty && empty($field->content)) {
continue;
}
$fields[] = $field;
}
}
if (!empty($fields)) {
// Sort extrafields by rank
uasort(
$fields,
/**
* @param stdClass $a
* @param stdClass $b
* @return int<-1,1>
*/
static function ($a, $b) {
return ($a->rank > $b->rank) ? 1 : -1;
}
);
// define some HTML content with style
$html .= !empty($params['style']) ? '<style>'.$params['style'].'</style>' : '';
// auto select display format
if ($params['display'] == 'auto') {
$lastNnumbItems = 0;
foreach ($params['auto'] as $display => $numbItems) {
if ($lastNnumbItems <= $numbItems && count($fields) > $numbItems) {
$lastNnumbItems = $numbItems;
$params['display'] = $display;
}
}
}
if ($params['display'] == 'list') {
// Display in list format
$i = 0;
foreach ($fields as $field) {
$html .= !empty($i) ? $params['list']['separator'] : '';
$html .= '<strong>'.$field->label.' : </strong>';
$html .= $field->content;
$i++;
}
} elseif ($params['display'] == 'table') {
// Display in table format
$html .= '<table class="extrafield-table" cellspacing="'.$params['table']['cellspacing'].'" cellpadding="'.$params['table']['cellpadding'].'" border="'.$params['table']['border'].'">';
$html .= "<tr>";
$itemsInRow = 0;
$maxItemsInRow = $params['table']['maxItemsInRow'];
foreach ($fields as $field) {
//$html.= !empty($html)?'<br>':'';
if ($itemsInRow >= $maxItemsInRow) {
// start a new line
$html .= "</tr><tr>";
$itemsInRow = 0;
}
// for some type we need line break
if (in_array($field->type, $params['table']['arrayOfLineBreakType'])) {
if ($itemsInRow > 0) {
// close table row and empty cols
for ($i = $itemsInRow; $i <= $maxItemsInRow; $i++) {
$html .= "<td></td><td></td>";
}
$html .= "</tr>";
// start a new line
$html .= "<tr>";
}
$itemsInRow = $maxItemsInRow;
$html .= '<td colspan="'.($maxItemsInRow * 2 - 1).'">';
$html .= '<strong>'.$field->label.' :</strong> ';
$html .= $field->content;
$html .= "</td>";
} else {
$itemsInRow++;
$html .= '<td width="'.$params['table']['labelcolwidth'].'" class="extrafield-label">';
$html .= '<strong>'.$field->label.' :</strong>';
$html .= "</td>";
$html .= '<td class="extrafield-content">';
$html .= $field->content;
$html .= "</td>";
}
}
$html .= "</tr>";
$html .= '</table>';
}
}
return $html;
}
/**
* get column status from column key
*
* @param string $colKey the column key
* @return boolean true if column on
*/
public function getColumnStatus($colKey)
{
if (!empty($this->cols[$colKey]['status'])) {
return true;
} else {
return false;
}
}
/**
* Print standard column content
*
* @param TCPDI|TCPDF $pdf Pdf object
* @param float $tab_top Tab top position
* @param float $tab_height Default tab height
* @param Translate $outputlangs Output language
* @param int<0,1> $hidetop Hide top
* @return float Height of col tab titles
*/
public function pdfTabTitles(&$pdf, $tab_top, $tab_height, $outputlangs, $hidetop = 0)
{
global $hookmanager, $conf;
foreach ($this->cols as $colKey => $colDef) {
$parameters = array(
'colKey' => $colKey,
'pdf' => $pdf,
'outputlangs' => $outputlangs,
'tab_top' => $tab_top,
'tab_height' => $tab_height,
'hidetop' => $hidetop
);
$reshook = $hookmanager->executeHooks('pdfTabTitles', $parameters, $this); // Note that $object may have been modified by hook
if ($reshook < 0) {
setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
} elseif (empty($reshook)) {
if (!$this->getColumnStatus($colKey)) {
continue;
}
// get title label
$colDef['title']['label'] = !empty($colDef['title']['label']) ? $colDef['title']['label'] : $outputlangs->transnoentities($colDef['title']['textkey']);
// Add column separator
if (!empty($colDef['border-left']) && isset($colDef['xStartPos'])) {
$pdf->line($colDef['xStartPos'], $tab_top, $colDef['xStartPos'], $tab_top + $tab_height);
}
if (empty($hidetop)) {
// save current cell padding
$curentCellPaddinds = $pdf->getCellPaddings();
// Add space for lines (more if we need to show a second alternative language)
global $outputlangsbis;
if (is_object($outputlangsbis)) {
// set cell padding with column title definition
$pdf->setCellPaddings($colDef['title']['padding'][3], $colDef['title']['padding'][0], $colDef['title']['padding'][1], 0.5);
} else {
// set cell padding with column title definition
$pdf->setCellPaddings($colDef['title']['padding'][3], $colDef['title']['padding'][0], $colDef['title']['padding'][1], $colDef['title']['padding'][2]);
}
if (isset($colDef['title']['align'])) {
$align = $colDef['title']['align'];
} else {
$align = '';
}
$pdf->SetXY($colDef['xStartPos'], $tab_top);
$textWidth = $colDef['width'];
$pdf->MultiCell($textWidth, 2, $colDef['title']['label'], '', $align);
// Add variant of translation if $outputlangsbis is an object
if (is_object($outputlangsbis) && trim($colDef['title']['label'])) {
$pdf->setCellPaddings($colDef['title']['padding'][3], 0, $colDef['title']['padding'][1], $colDef['title']['padding'][2]);
$pdf->SetXY($colDef['xStartPos'], $pdf->GetY());
$textbis = $outputlangsbis->transnoentities($colDef['title']['textkey']);
$pdf->MultiCell($textWidth, 2, $textbis, '', $align);
}
$this->tabTitleHeight = max($pdf->GetY() - $tab_top, $this->tabTitleHeight);
// restore cell padding
$pdf->setCellPaddings($curentCellPaddinds['L'], $curentCellPaddinds['T'], $curentCellPaddinds['R'], $curentCellPaddinds['B']);
}
}
}
return $this->tabTitleHeight;
}
/**
* Define Array Column Field for extrafields
*
* @param CommonObject $object common object det
* @param Translate $outputlangs langs
* @param int<0,1> $hidedetails Do not show line details
* @return int Return integer <0 if KO, >=0 if OK
*/
public function defineColumnExtrafield($object, $outputlangs, $hidedetails = 0)
{
if (!empty($hidedetails)) {
return 0;
}
if (empty($object->table_element)) {
return 0;
}
// Load extra fields if they haven't been loaded already.
if (is_null($this->extrafieldsCache)) {
$this->extrafieldsCache = new ExtraFields($this->db);
}
if (empty($this->extrafieldsCache->attributes[$object->table_element])) {
$this->extrafieldsCache->fetch_name_optionals_label($object->table_element);
}
$extrafields = $this->extrafieldsCache;
if (!empty($extrafields->attributes[$object->table_element]) && is_array($extrafields->attributes[$object->table_element]) && array_key_exists('label', $extrafields->attributes[$object->table_element]) && is_array($extrafields->attributes[$object->table_element]['label'])) {
foreach ($extrafields->attributes[$object->table_element]['label'] as $key => $label) {
// Don't display separator yet even is set to be displayed (not compatible yet)
if ($extrafields->attributes[$object->table_element]['type'][$key] == 'separate') {
continue;
}
// Enable extrafield ?
$enabled = 0;
if (!empty($extrafields->attributes[$object->table_element]['printable'][$key])) {
$printable = intval($extrafields->attributes[$object->table_element]['printable'][$key]);
if ($printable === 1 || $printable === 2) {
$enabled = 1;
}
// Note : if $printable === 3 or 4 so, it's displayed after line description not in cols
}
if (!$enabled) {
continue;
} // don't waste resources if we don't need them...
// Load language if required
if (!empty($extrafields->attributes[$object->table_element]['langfile'][$key])) {
$outputlangs->load($extrafields->attributes[$object->table_element]['langfile'][$key]);
}
// TODO : add more extrafield customisation capacities for PDF like width, rank...
// set column definition
$def = array(
'rank' => intval($extrafields->attributes[$object->table_element]['pos'][$key]),
'width' => 25, // in mm
'status' => (bool) $enabled,
'title' => array(
'label' => $outputlangs->transnoentities($label)
),
'content' => array(
'align' => 'C'
),
'border-left' => true, // add left line separator
);
$alignTypeRight = array('double', 'int', 'price');
if (in_array($extrafields->attributes[$object->table_element]['type'][$key], $alignTypeRight)) {
$def['content']['align'] = 'R';
}
$alignTypeLeft = array('text', 'html');
if (in_array($extrafields->attributes[$object->table_element]['type'][$key], $alignTypeLeft)) {
$def['content']['align'] = 'L';
}
// for extrafields we use rank of extrafield to place it on PDF
$this->insertNewColumnDef("options_".$key, $def);
}
}
return 1;
}
/**
* Define Array Column Field into $this->cols
* This method must be implemented by the module that generate the document with its own columns.
*
* @param CommonObject $object Common object
* @param Translate $outputlangs Langs
* @param int<0,1> $hidedetails Do not show line details
* @param int<0,1> $hidedesc Do not show desc
* @param int<0,1> $hideref Do not show ref
* @return void
*/
public function defineColumnField($object, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0)
{
// Default field style for content
$this->defaultContentsFieldsStyle = array(
'align' => 'R', // R,C,L
'padding' => array(1, 0.5, 1, 0.5), // Like css 0 => top , 1 => right, 2 => bottom, 3 => left
);
// Default field style for content
$this->defaultTitlesFieldsStyle = array(
'align' => 'C', // R,C,L
'padding' => array(0.5, 0, 0.5, 0), // Like css 0 => top , 1 => right, 2 => bottom, 3 => left
);
// Example
/*
$rank = 0; // do not use negative rank
$this->cols['desc'] = array(
'rank' => $rank,
'width' => false, // only for desc
'status' => true,
'title' => array(
'textkey' => 'Designation', // use lang key is useful in some case with module
'align' => 'L',
// 'textkey' => 'yourLangKey', // if there is no label, yourLangKey will be translated to replace label
// 'label' => ' ', // the final label
'padding' => array(0.5, 0.5, 0.5, 0.5), // Like css 0 => top , 1 => right, 2 => bottom, 3 => left
),
'content' => array(
'align' => 'L',
'padding' => array(1, 0.5, 1, 1.5), // Like css 0 => top , 1 => right, 2 => bottom, 3 => left
),
);
*/
}
}