diff --git a/dev/build/phpstan/phpstan-baseline.neon b/dev/build/phpstan/phpstan-baseline.neon index 148231a66ca..f227902c745 100644 --- a/dev/build/phpstan/phpstan-baseline.neon +++ b/dev/build/phpstan/phpstan-baseline.neon @@ -26256,12 +26256,6 @@ parameters: count: 4 path: ../../../htdocs/societe/website.php - - - message: '#^Parameter \#1 \$params of static method Stripe\\Terminal\\Location\:\:all\(\) expects array\|null, string given\.$#' - identifier: argument.type - count: 1 - path: ../../../htdocs/stripe/admin/stripe.php - - message: '#^Call to function is_object\(\) with Facture will always evaluate to true\.$#' identifier: function.alreadyNarrowedType @@ -26448,24 +26442,6 @@ parameters: count: 6 path: ../../../htdocs/supplier_proposal/class/supplier_proposal.class.php - - - message: '#^Parameter \#3 \$remise_percent_ligne of function calcul_price_total expects float, string given\.$#' - identifier: argument.type - count: 2 - path: ../../../htdocs/supplier_proposal/class/supplier_proposal.class.php - - - - message: '#^Parameter \#5 \$uselocaltax1_rate of function calcul_price_total expects float, string given\.$#' - identifier: argument.type - count: 2 - path: ../../../htdocs/supplier_proposal/class/supplier_proposal.class.php - - - - message: '#^Parameter \#6 \$uselocaltax2_rate of function calcul_price_total expects float, string given\.$#' - identifier: argument.type - count: 2 - path: ../../../htdocs/supplier_proposal/class/supplier_proposal.class.php - - message: '#^Property SupplierProposal\:\:\$statut \(int\) in isset\(\) is not nullable\.$#' identifier: isset.property @@ -26604,12 +26580,6 @@ parameters: count: 1 path: ../../../htdocs/takepos/admin/terminal.php - - - message: '#^Parameter \#1 \$params of static method Stripe\\Terminal\\Reader\:\:all\(\) expects array\|null, string given\.$#' - identifier: argument.type - count: 2 - path: ../../../htdocs/takepos/admin/terminal.php - - message: '#^Right side of && is always true\.$#' identifier: booleanAnd.rightAlwaysTrue @@ -26664,18 +26634,6 @@ parameters: count: 1 path: ../../../htdocs/takepos/index.php - - - message: '#^Variable \$CUSTOMER_DISPLAY_line1 might not be defined\.$#' - identifier: variable.undefined - count: 1 - path: ../../../htdocs/takepos/invoice.php - - - - message: '#^Variable \$CUSTOMER_DISPLAY_line2 might not be defined\.$#' - identifier: variable.undefined - count: 1 - path: ../../../htdocs/takepos/invoice.php - - message: '#^Variable \$batch might not be defined\.$#' identifier: variable.undefined @@ -26688,30 +26646,12 @@ parameters: count: 1 path: ../../../htdocs/takepos/invoice.php - - - message: '#^Variable \$creditnote might not be defined\.$#' - identifier: variable.undefined - count: 2 - path: ../../../htdocs/takepos/invoice.php - - - - message: '#^Variable \$fk_source might not be defined\.$#' - identifier: variable.undefined - count: 1 - path: ../../../htdocs/takepos/invoice.php - - message: '#^Variable \$footer_ticket might not be defined\.$#' identifier: variable.undefined count: 1 path: ../../../htdocs/takepos/invoice.php - - - message: '#^Variable \$footerorder might not be defined\.$#' - identifier: variable.undefined - count: 2 - path: ../../../htdocs/takepos/invoice.php - - message: '#^Variable \$header_soc might not be defined\.$#' identifier: variable.undefined @@ -26724,42 +26664,12 @@ parameters: count: 1 path: ../../../htdocs/takepos/invoice.php - - - message: '#^Variable \$headerorder might not be defined\.$#' - identifier: variable.undefined - count: 2 - path: ../../../htdocs/takepos/invoice.php - - - - message: '#^Variable \$idoflineadded might not be defined\.$#' - identifier: variable.undefined - count: 1 - path: ../../../htdocs/takepos/invoice.php - - - - message: '#^Variable \$printer might not be defined\.$#' - identifier: variable.undefined - count: 6 - path: ../../../htdocs/takepos/invoice.php - - - - message: '#^Variable \$sectionwithinvoicelink might not be defined\.$#' - identifier: variable.undefined - count: 1 - path: ../../../htdocs/takepos/invoice.php - - message: '#^Variable \$ticket_total might not be defined\.$#' identifier: variable.undefined count: 1 path: ../../../htdocs/takepos/invoice.php - - - message: '#^Variable \$tva_npr might not be defined\.$#' - identifier: variable.undefined - count: 2 - path: ../../../htdocs/takepos/invoice.php - - message: '#^Variable \$warehouseid might not be defined\.$#' identifier: variable.undefined @@ -26784,12 +26694,6 @@ parameters: count: 3 path: ../../../htdocs/takepos/pay.php - - - message: '#^Variable \$stripeacc might not be defined\.$#' - identifier: variable.undefined - count: 1 - path: ../../../htdocs/takepos/pay.php - - message: '#^Variable \$user might not be defined\.$#' identifier: variable.undefined @@ -26814,18 +26718,6 @@ parameters: count: 1 path: ../../../htdocs/takepos/split.php - - - message: '#^Variable \$invoice might not be defined\.$#' - identifier: variable.undefined - count: 4 - path: ../../../htdocs/takepos/split.php - - - - message: '#^Variable \$placeid might not be defined\.$#' - identifier: variable.undefined - count: 2 - path: ../../../htdocs/takepos/split.php - - message: '#^Variable \$fontlist might not be defined\.$#' identifier: variable.undefined diff --git a/htdocs/accountancy/admin/fiscalyear.php b/htdocs/accountancy/admin/fiscalyear.php index f163ca00544..525e02edd37 100644 --- a/htdocs/accountancy/admin/fiscalyear.php +++ b/htdocs/accountancy/admin/fiscalyear.php @@ -1,6 +1,6 @@ - * Copyright (C) 2024 Frédéric France +/* Copyright (C) 2013-2025 Alexandre Spangaro + * Copyright (C) 2024 Frédéric France * * 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 @@ -24,6 +24,8 @@ // Load Dolibarr environment require '../../main.inc.php'; + +require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php'; require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php'; require_once DOL_DOCUMENT_ROOT.'/core/class/fiscalyear.class.php'; @@ -83,7 +85,25 @@ if (!$user->hasRight('accounting', 'fiscalyear', 'write')) { // If /* * Actions */ +if ($action == 'setdefault') { + // Set a fiscal year as default + $error = 0; + $defaultFiscalYear = GETPOSTINT('value'); + $defaultFiscalYearLabel = GETPOST('label', 'alpha'); + + if (!empty($defaultFiscalYear)) { + dolibarr_set_const($db, 'ACCOUNTANCY_FISCALYEAR_DEFAULT', $defaultFiscalYear, 'chaine', 0, '', $conf->entity); + } else { + $error++; + } + + if (!$error) { + setEventMessages($langs->trans("FiscalYearSetAsDefault", $defaultFiscalYearLabel), null, 'mesgs'); + } else { + setEventMessages($langs->trans("Error"), null, 'errors'); + } +} /* @@ -148,6 +168,7 @@ if ($result) { print ''.$langs->trans("DateEnd").''; print ''.$langs->trans("NumberOfAccountancyEntries").''; print ''.$langs->trans("NumberOfAccountancyMovements").''; + print ''.$langs->trans("Default").''; print ''.$langs->trans("Status").''; print ''; @@ -160,6 +181,7 @@ if ($result) { $fiscalyearstatic->ref = $obj->rowid; $fiscalyearstatic->id = $obj->rowid; + $fiscalyearstatic->label = $obj->label; $fiscalyearstatic->date_start = $obj->date_start; $fiscalyearstatic->date_end = $obj->date_end; $fiscalyearstatic->statut = $obj->status; @@ -174,12 +196,22 @@ if ($result) { print ''.dol_print_date($db->jdate($obj->date_end), 'day').''; print ''.$object->getAccountancyEntriesByFiscalYear($obj->date_start, $obj->date_end).''; print ''.$object->getAccountancyMovementsByFiscalYear($obj->date_start, $obj->date_end).''; + + // Default + print ''; + if (getDolGlobalString('ACCOUNTANCY_FISCALYEAR_DEFAULT') == (int) $fiscalyearstatic->ref) { + print img_picto($langs->trans("Default"), 'on'); + } else { + print 'ref).'&label='.urlencode($fiscalyearstatic->label).'" alt="'.$langs->trans("Default").'">'.img_picto($langs->trans("SetAsDefault"), 'off').''; + } + print ''; + print ''.$fiscalyearstatic->LibStatut($obj->status, 5).''; print ''; $i++; } } else { - print ''.$langs->trans("NoRecordFound").''; + print ''.$langs->trans("NoRecordFound").''; } print ''; print ''; diff --git a/htdocs/accountancy/bookkeeping/balance.php b/htdocs/accountancy/bookkeeping/balance.php index 3d8d469b63f..5e640d1f98c 100644 --- a/htdocs/accountancy/bookkeeping/balance.php +++ b/htdocs/accountancy/bookkeeping/balance.php @@ -1,7 +1,7 @@ * Copyright (C) 2016 Florian Henry - * Copyright (C) 2016-2024 Alexandre Spangaro + * Copyright (C) 2016-2025 Alexandre Spangaro * Copyright (C) 2018-2024 Frédéric France * Copyright (C) 2024 MDW * @@ -101,8 +101,13 @@ $formaccounting = new FormAccounting($db); $form = new Form($db); if (empty($search_date_start) && !GETPOSTISSET('formfilteraction')) { - $sql = "SELECT date_start, date_end from ".MAIN_DB_PREFIX."accounting_fiscalyear "; - $sql .= " WHERE date_start < '".$db->idate(dol_now())."' AND date_end > '".$db->idate(dol_now())."'"; + $sql = "SELECT date_start, date_end"; + $sql .=" FROM ".MAIN_DB_PREFIX."accounting_fiscalyear "; + if (getDolGlobalInt('ACCOUNTANCY_FISCALYEAR_DEFAULT')) { + $sql .= " WHERE rowid = " . getDolGlobalInt('ACCOUNTANCY_FISCALYEAR_DEFAULT'); + } else { + $sql .= " WHERE date_start < '" . $db->idate(dol_now()) . "' and date_end > '" . $db->idate(dol_now()) . "'"; + } $sql .= $db->plimit(1); $res = $db->query($sql); diff --git a/htdocs/accountancy/bookkeeping/export.php b/htdocs/accountancy/bookkeeping/export.php index 67a6155ef7c..e866ea95980 100644 --- a/htdocs/accountancy/bookkeeping/export.php +++ b/htdocs/accountancy/bookkeeping/export.php @@ -1,7 +1,7 @@ * Copyright (C) 2013-2016 Florian Henry - * Copyright (C) 2013-2024 Alexandre Spangaro + * Copyright (C) 2013-2025 Alexandre Spangaro * Copyright (C) 2022 Lionel Vessiller * Copyright (C) 2016-2017 Laurent Destailleur * Copyright (C) 2018-2024 Frédéric France @@ -170,9 +170,15 @@ $form = new Form($db); if (!in_array($action, array('export_file', 'delmouv', 'delmouvconfirm')) && !GETPOSTISSET('begin') && !GETPOSTISSET('formfilteraction') && GETPOSTINT('page') == '' && !GETPOSTINT('noreset') && $user->hasRight('accounting', 'mouvements', 'export')) { if (empty($search_date_start) && empty($search_date_end) && !GETPOSTISSET('restore_lastsearch_values') && !GETPOST('search_accountancy_code_start')) { - $query = "SELECT date_start, date_end from ".MAIN_DB_PREFIX."accounting_fiscalyear "; - $query .= " where date_start < '".$db->idate(dol_now())."' and date_end > '".$db->idate(dol_now())."' limit 1"; - $res = $db->query($query); + $sql = "SELECT date_start, date_end"; + $sql .=" FROM ".MAIN_DB_PREFIX."accounting_fiscalyear "; + if (getDolGlobalInt('ACCOUNTANCY_FISCALYEAR_DEFAULT')) { + $sql .= " WHERE rowid = " . getDolGlobalInt('ACCOUNTANCY_FISCALYEAR_DEFAULT'); + } else { + $sql .= " WHERE date_start < '" . $db->idate(dol_now()) . "' and date_end > '" . $db->idate(dol_now()) . "'"; + } + $sql .= $db->plimit(1); + $res = $db->query($sql); if ($db->num_rows($res) > 0) { $fiscalYear = $db->fetch_object($res); diff --git a/htdocs/accountancy/bookkeeping/list.php b/htdocs/accountancy/bookkeeping/list.php index 7ccdfc2354d..a0ee90823aa 100644 --- a/htdocs/accountancy/bookkeeping/list.php +++ b/htdocs/accountancy/bookkeeping/list.php @@ -1,7 +1,7 @@ * Copyright (C) 2013-2016 Florian Henry - * Copyright (C) 2013-2024 Alexandre Spangaro + * Copyright (C) 2013-2024 Alexandre Spangaro * Copyright (C) 2022 Lionel Vessiller * Copyright (C) 2016-2017 Laurent Destailleur * Copyright (C) 2018-2025 Frédéric France @@ -149,14 +149,21 @@ if ($sortfield == "") { $object = new BookKeeping($db); $hookmanager->initHooks(array('bookkeepinglist')); +$formfiscalyear = new FormFiscalYear($db); $formaccounting = new FormAccounting($db); $form = new Form($db); if (!in_array($action, array('delmouv', 'delmouvconfirm')) && !GETPOSTISSET('begin') && !GETPOSTISSET('formfilteraction') && GETPOST('page', 'alpha') == '' && !GETPOSTINT('noreset') && $user->hasRight('accounting', 'mouvements', 'export')) { if (empty($search_date_start) && empty($search_date_end) && !GETPOSTISSET('restore_lastsearch_values') && !GETPOST('search_accountancy_code_start')) { - $query = "SELECT date_start, date_end from ".MAIN_DB_PREFIX."accounting_fiscalyear "; - $query .= " where date_start < '".$db->idate(dol_now())."' and date_end > '".$db->idate(dol_now())."' limit 1"; - $res = $db->query($query); + $sql = "SELECT date_start, date_end"; + $sql .=" FROM ".MAIN_DB_PREFIX."accounting_fiscalyear "; + if (getDolGlobalInt('ACCOUNTANCY_FISCALYEAR_DEFAULT')) { + $sql .= " WHERE rowid = " . getDolGlobalInt('ACCOUNTANCY_FISCALYEAR_DEFAULT'); + } else { + $sql .= " WHERE date_start < '" . $db->idate(dol_now()) . "' and date_end > '" . $db->idate(dol_now()) . "'"; + } + $sql .= $db->plimit(1); + $res = $db->query($sql); if ($db->num_rows($res) > 0) { $fiscalYear = $db->fetch_object($res); diff --git a/htdocs/accountancy/bookkeeping/listbyaccount.php b/htdocs/accountancy/bookkeeping/listbyaccount.php index 5abd6f84719..d52a1c747e3 100644 --- a/htdocs/accountancy/bookkeeping/listbyaccount.php +++ b/htdocs/accountancy/bookkeeping/listbyaccount.php @@ -2,7 +2,7 @@ /* Copyright (C) 2016 Neil Orley * Copyright (C) 2013-2016 Olivier Geffroy * Copyright (C) 2013-2020 Florian Henry - * Copyright (C) 2013-2024 Alexandre Spangaro + * Copyright (C) 2013-2025 Alexandre Spangaro * Copyright (C) 2018-2024 Frédéric France * Copyright (C) 2024 MDW * @@ -162,8 +162,13 @@ $formaccounting = new FormAccounting($db); $form = new Form($db); if (empty($search_date_start) && empty($search_date_end) && !GETPOSTISSET('search_date_startday') && !GETPOSTISSET('search_date_startmonth') && !GETPOSTISSET('search_date_starthour')) { - $sql = "SELECT date_start, date_end from ".MAIN_DB_PREFIX."accounting_fiscalyear "; - $sql .= " where date_start < '".$db->idate(dol_now())."' and date_end > '".$db->idate(dol_now())."'"; + $sql = "SELECT date_start, date_end"; + $sql .=" FROM ".MAIN_DB_PREFIX."accounting_fiscalyear "; + if (getDolGlobalInt('ACCOUNTANCY_FISCALYEAR_DEFAULT')) { + $sql .= " WHERE rowid = " . getDolGlobalInt('ACCOUNTANCY_FISCALYEAR_DEFAULT'); + } else { + $sql .= " WHERE date_start < '" . $db->idate(dol_now()) . "' and date_end > '" . $db->idate(dol_now()) . "'"; + } $sql .= $db->plimit(1); $res = $db->query($sql); diff --git a/htdocs/core/class/html.formfiscalyear.class.php b/htdocs/core/class/html.formfiscalyear.class.php new file mode 100644 index 00000000000..9f49aa16f41 --- /dev/null +++ b/htdocs/core/class/html.formfiscalyear.class.php @@ -0,0 +1,112 @@ + + * + * 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 . + */ + +/** + * \file htdocs/core/class/html.formfiscalyear.class.php + * \ingroup Accountancy (Double entries) + * \brief File of class with all html predefined components + */ +require_once DOL_DOCUMENT_ROOT.'/core/class/html.form.class.php'; + +/** + * Class to manage generation of HTML components for accounting management + */ +class FormFiscalYear extends Form +{ + /** + * @var DoliDB Database handler. + */ + public $db; + + /** + * @var string Error code (or message) + */ + public $error = ''; + + /** + * Constructor + * + * @param DoliDB $db Database handler + */ + public function __construct($db) + { + $this->db = $db; + } + + /** + * Return list of fiscal year + * + * @param int $selected Preselected type + * @param string $htmlname Name of field in form + * @param int $useempty Set to 1 if we want an empty value + * @param int $maxlen Max length of text in combo box + * @param int $help Add or not the admin help picto + * @return void|string HTML component with the select + */ + public function selectFiscalYear($selected = 0, $htmlname = 'fiscalyear', $useempty = 0, $maxlen = 0, $help = 1) + { + global $conf, $langs; + + $out = ''; + + $sql = "SELECT f.rowid, f.label, f.date_start, f.date_end, f.statut as status"; + $sql .= " FROM ".$this->db->prefix()."accounting_fiscalyear as f"; + $sql .= " WHERE f.entity = ".$conf->entity; + $sql .= " ORDER BY f.date_start ASC"; + + dol_syslog(get_class($this).'::'.__METHOD__, LOG_DEBUG); + $resql = $this->db->query($sql); + if ($resql) { + $num = $this->db->num_rows($resql); + if ($num) { + $out .= ''; + //if ($user->admin && $help) $out .= info_admin($langs->trans("YouCanChangeValuesForThisListFromDictionarySetup"),1); + + $out .= ajax_combobox($htmlname, array()); + } else { + $out .= ''.$langs->trans("ErrorNoFiscalyearDefined", $langs->transnoentitiesnoconv("Accounting"), $langs->transnoentitiesnoconv("Setup"), $langs->transnoentitiesnoconv("Fiscalyear")).''; + } + } else { + dol_print_error($this->db); + } + + return $out; + } +} diff --git a/htdocs/fourn/commande/card.php b/htdocs/fourn/commande/card.php index d22e57f50d1..5b9c3d99afc 100644 --- a/htdocs/fourn/commande/card.php +++ b/htdocs/fourn/commande/card.php @@ -2656,6 +2656,18 @@ if ($action == 'create') { } } + // To avoid delete order with dispatched lines + if (getDolGlobalString('STOCK_CALCULATE_ON_SUPPLIER_DISPATCH_ORDER')) { + if (empty($object->lines)) { + $object->fetch_lines(); + } + $dispachedLines = $object->getDispachedLines(1); + $nbDispachedLines = count($dispachedLines); + if ($nbDispachedLines > 0) { + $hasreception = 1; + } + } + if (in_array($object->statut, array(3, 4, 5))) { if (isModEnabled("supplier_order") && $usercanreceive) { print ''; diff --git a/htdocs/langs/en_US/compta.lang b/htdocs/langs/en_US/compta.lang index 723bb8cf0ae..d79ed8a9596 100644 --- a/htdocs/langs/en_US/compta.lang +++ b/htdocs/langs/en_US/compta.lang @@ -283,6 +283,9 @@ ImportDataset_tax_contrib=Social/fiscal taxes ImportDataset_tax_vat=VAT payments ErrorBankAccountNotFound=Error: Bank account not found FiscalPeriod=Accounting period +ErrorNoFiscalyearDefined=Error, no fiscal year defined (See %s - %s - %s) +FiscalYearSetAsDefault=Fiscal year (%s) defined as default +FiscalYearFromTo=Period from %s to %s FiscalYearOpened=Fiscal year opened FiscalYearClosed=Fiscal year closed FiscalYearOpenedShort=Opened diff --git a/htdocs/modulebuilder/template/build/buildzip.php b/htdocs/modulebuilder/template/build/buildzip.php new file mode 100644 index 00000000000..3508bbbec79 --- /dev/null +++ b/htdocs/modulebuilder/template/build/buildzip.php @@ -0,0 +1,316 @@ +#!/usr/bin/env php -d memory_limit=256M + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/* + The goal of that php CLI script is to make zip package of your module + as an alternative to web "build zip" or "perl script makepack" +*/ + +// ============================================= configuration + +/** + * list of files & dirs of your module + * + * @var string[] + */ +$listOfModuleContent = [ + 'admin', + 'ajax', + 'backport', + 'class', + 'css', + 'COPYING', + 'core', + 'img', + 'js', + 'langs', + 'lib', + 'sql', + 'tpl', + '*.md', + '*.json', + '*.php', + 'modulebuilder.txt', +]; + +/** + * if you want to exclude some files from your zip + * + * @var string[] + */ +$exclude_list = [ + '/^.git$/', + '/.*js.map/', + '/DEV.md/' +]; + +// ============================================= end of configuration + +/** + * auto detect module name and version from file name + * + * @return (string|string)[] module name and module version + */ +function detectModule() +{ + $name = $version = ""; + $tab = glob("core/modules/mod*.class.php"); + if (count($tab) == 0) { + echo "[fail] Error on auto detect data : there is no mod*.class.php file into core/modules dir\n"; + exit(-1); + } + if (count($tab) == 1) { + $file = $tab[0]; + $pattern = "/.*mod(?.*)\.class\.php/"; + if (preg_match_all($pattern, $file, $matches)) { + $name = strtolower(reset($matches['mod'])); + } + + echo "extract data from $file\n"; + if (!file_exists($file) || $name == "") { + echo "[fail] Error on auto detect data\n"; + exit(-2); + } + } else { + echo "[fail] Error there is more than one mod*.class.php file into core/modules dir\n"; + exit(-3); + } + + //extract version from file + $contents = file_get_contents($file); + $pattern = "/^.*this->version\s*=\s*'(?.*)'\s*;.*\$/m"; + + // search, and store all matching occurrences in $matches + if (preg_match_all($pattern, $contents, $matches)) { + $version = reset($matches['version']); + } + + if (version_compare($version, '0.0.1', '>=') != 1) { + echo "[fail] Error auto extract version fail\n"; + exit(-4); + } + + echo "module name = $name, version = $version\n"; + return [(string) $name, (string) $version]; +} + +/** + * delete recursively a directory + * + * @param string $dir dir path to delete + * + * @return bool true on success or false on failure. + */ +function delTree($dir) +{ + $files = array_diff(scandir($dir), array('.', '..')); + foreach ($files as $file) { + (is_dir("$dir/$file")) ? delTree("$dir/$file") : secureUnlink("$dir/$file"); + } + return rmdir($dir); +} + + +/** + * do a secure delete file/dir with double check + * (don't trust unlink return) + * + * @param string $path full path to delete + * + * @return bool true on success ($path does not exists at the end of process), else exit + */ +function secureUnlink($path) +{ + if (file_exists($path)) { + if (unlink($path)) { + //then check if really deleted + clearstatcache(); + if (file_exists($path)) { // @phpstan-ignore-line + echo "[fail] unlink of $path fail !\n"; + exit(-5); + } + } else { + echo "[fail] unlink of $path fail !\n"; + exit(-6); + } + } + return true; +} + +/** + * create a directory and check if dir exists + * + * @param string $path path to make + * + * @return bool true on success ($path exists at the end of process), else exit + */ +function mkdirAndCheck($path) +{ + if (mkdir($path)) { + clearstatcache(); + if (is_dir($path)) { + return true; + } + } + echo "[fail] Error on $path (mkdir)\n"; + exit(7); +} + +/** + * check if that filename is concerned by exclude filter + * + * @param string $filename file name to check + * + * @return bool true if file is in excluded list + */ +function is_excluded($filename) +{ + global $exclude_list; + $count = 0; + $notused = preg_filter($exclude_list, '1', $filename, -1, $count); + if ($count > 0) { + echo " - exclude $filename\n"; + return true; + } + return false; +} + +/** + * recursive copy files & dirs + * + * @param string $src source dir + * @param string $dst target dir + * + * @return bool true on success or false on failure. + */ +function rcopy($src, $dst) +{ + if (is_dir($src)) { + // Make the destination directory if not exist + mkdirAndCheck($dst); + // open the source directory + $dir = opendir($src); + + // Loop through the files in source directory + while ($file = readdir($dir)) { + if (($file != '.') && ($file != '..')) { + if (is_dir($src . '/' . $file)) { + // Recursively calling custom copy function + // for sub directory + if (!rcopy($src . '/' . $file, $dst . '/' . $file)) { + return false; + } + } else { + if (!is_excluded($file)) { + if (!copy($src . '/' . $file, $dst . '/' . $file)) { + return false; + } + } + } + } + } + closedir($dir); + } elseif (is_file($src)) { + if (!is_excluded($src)) { + if (!copy($src, $dst)) { + return false; + } + } + } + return true; +} + +/** + * build a zip file with only php code and no external depends + * on "zip" exec for example + * + * @param string $folder folder to use as zip root + * @param ZipArchive $zip zip object (ZipArchive) + * @param string $root relative root path into the zip + * + * @return bool true on success or false on failure. + */ +function zipDir($folder, &$zip, $root = "") +{ + foreach (new \DirectoryIterator($folder) as $f) { + if ($f->isDot()) { + continue; + } //skip . .. + $src = $folder . '/' . $f; + $dst = substr($f->getPathname(), strlen($root)); + if ($f->isDir()) { + if ($zip->addEmptyDir($dst)) { + if (zipDir($src, $zip, $root)) { + continue; + } else { + return false; + } + } else { + return false; + } + } + if ($f->isFile()) { + if (! $zip->addFile($src, $dst)) { + return false; + } + } + } + return true; +} + +/** + * main part of script + */ + +list($mod, $version) = detectModule(); +$outzip = sys_get_temp_dir() . "/module_" . $mod . "-" . $version . ".zip"; +if (file_exists($outzip)) { + secureUnlink($outzip); +} + +//copy all sources into system temp directory +$tmpdir = tempnam(sys_get_temp_dir(), $mod . "-module"); +secureUnlink($tmpdir); +mkdirAndCheck($tmpdir); +$dst = $tmpdir . "/" . $mod; +mkdirAndCheck($dst); + +foreach ($listOfModuleContent as $moduleContent) { + foreach (glob($moduleContent) as $entry) { + if (!rcopy($entry, $dst . '/' . $entry)) { + echo "[fail] Error on copy " . $entry . " to " . $dst . "/" . $entry . "\n"; + echo "Please take time to analyze the problem and fix the bug\n"; + exit(-8); + } + } +} + +$z = new ZipArchive(); +$z->open($outzip, ZIPARCHIVE::CREATE); +zipDir($tmpdir, $z, $tmpdir . '/'); +$z->close(); +delTree($tmpdir); +if (file_exists($outzip)) { + echo "[success] module archive is ready : $outzip ...\n"; +} else { + echo "[fail] build zip error\n"; + exit(-9); +}