diff --git a/.github/workflows/ci-cache-clean-pr.yml b/.github/workflows/ci-cache-clean-pr.yml index 9713af46134..5b6866b52c3 100644 --- a/.github/workflows/ci-cache-clean-pr.yml +++ b/.github/workflows/ci-cache-clean-pr.yml @@ -18,7 +18,7 @@ jobs: contents: read steps: - name: Check out code - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Cleanup run: | gh extension install actions/gh-actions-cache diff --git a/.github/workflows/ci-checkfilesetlock.yml b/.github/workflows/ci-checkfilesetlock.yml new file mode 100644 index 00000000000..bfc1f9fe5b8 --- /dev/null +++ b/.github/workflows/ci-checkfilesetlock.yml @@ -0,0 +1,29 @@ +--- +# This is a basic workflow to check the lock on major version (to lock some files on certified versions) +name: Check fileset lock + +on: [push, pull_request] + +concurrency: + group: check-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + checkmajorversion: + name: Check lock on fileset unalterable_files with generate_filelist_xml.php + runs-on: ubuntu-latest + # Do not run schedule on forks + if: | + github.repository == 'Dolibarr/dolibarr' + || github.event.schedule == false + steps: + - uses: actions/checkout@v6 + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: 8.2 + coverage: none # disable xdebug, pcov + - name: Run generate_filelist_xml.php + run: | + # shellcheck disable=2086 + dev/build/generate_filelist_xml.php checklock=auto unalterable_files diff --git a/.github/workflows/ci-phpstan_baseline.yml b/.github/workflows/ci-phpstan_baseline.yml index a68414b62df..692d259d1d8 100644 --- a/.github/workflows/ci-phpstan_baseline.yml +++ b/.github/workflows/ci-phpstan_baseline.yml @@ -33,7 +33,7 @@ jobs: pull-requests: write steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Setup PHP uses: shivammathur/setup-php@v2 with: diff --git a/.github/workflows/phan.yml b/.github/workflows/phan.yml index 7004f68d1ea..9accfc8212d 100644 --- a/.github/workflows/phan.yml +++ b/.github/workflows/phan.yml @@ -33,7 +33,7 @@ jobs: github.repository == 'Dolibarr/dolibarr' || github.event.schedule == false steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Setup PHP uses: shivammathur/setup-php@v2 with: diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml index d41d022a643..671326f83c1 100644 --- a/.github/workflows/phpstan.yml +++ b/.github/workflows/phpstan.yml @@ -37,7 +37,7 @@ jobs: # Steps represent a sequence of tasks that will be executed as part of the job steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 # Get PHP and addons - name: Setup PHP id: setup-php diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index e23c86871a0..7cc7ed6f379 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -30,7 +30,7 @@ jobs: # if: false # Checkout git sources to analyze - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 # Try to get the list of modified files into steps.changed-php.outputs.all_changed_files #- name: Get changed files @@ -109,6 +109,10 @@ jobs: coverage: none # disable xdebug, pcov tools: phpcs + # Install perltidy and perlcritic + - name: Install perltidy and perlcritic + run: sudo apt-get update && sudo apt-get install -y perltidy libperl-critic-perl + # Run all the precommit tools (defined into pre-commit-config.yaml). # We can force exclusion of some of them here. - name: Run pre-commit hooks diff --git a/.github/workflows/windows-ci.yml b/.github/workflows/windows-ci.yml index cd772236d67..781a433bfbc 100644 --- a/.github/workflows/windows-ci.yml +++ b/.github/workflows/windows-ci.yml @@ -37,7 +37,7 @@ jobs: runs-on: ${{ matrix.os }} steps: - name: Checkout code - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Setup PHP uses: shivammathur/setup-php@v2 with: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ef5560682ac..91babc17efe 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -274,3 +274,21 @@ repos: |htdocs/install/pgsql/functions/functions.*\.sql |htdocs/modulebuilder/template/sql/.*\.sql )$ + + - repo: https://github.com/perltidy/perltidy + rev: '20250105.03' + hooks: + - id: perltidy + # virtualmin excuded - reason https://github.com/Dolibarr/dolibarr/pull/36370#issuecomment-3565101823 + exclude: (?x)^ + (dev/build/perl/virtualmin/dolibarr.pl + )$ + args: [ --tabs, --nola ] + - repo: https://github.com/henryykt/pre-commit-perl + rev: v0.0.5 + hooks: + - id: perlcritic + # virtualmin excuded - reason https://github.com/Dolibarr/dolibarr/pull/36370#issuecomment-3565101823 + exclude: (?x)^ + (dev/build/perl/virtualmin/dolibarr.pl + )$ diff --git a/ChangeLog b/ChangeLog index 693af740add..9dd3eefde10 100644 --- a/ChangeLog +++ b/ChangeLog @@ -6,36 +6,212 @@ English Dolibarr ChangeLog For users: ---------- - +NEW: Need PHP 7.2 as minimum version +NEW: Module datapolicy moved as stable (for anonimization features) +NEW: #31723 - Improve project overview: Hide paid orders (#35524) +NEW: #35700 : Throw an error when validating a propal, order, supplier with a product no more in sale/purchase… (#35709) +NEW: Accountancy - Accounting by payment type (#34729) +NEW: Accountancy - Add accounting for discounts (#35977) +NEW: Accountancy - Add a protection on various payment for auxiliary account on general account not centralized (#35720) +NEW: Accountancy - Add field centralized on import/export (#35872) +NEW: Accountancy - Add hook on export filename (#35188) +NEW: Accountancy - Add reconcile on general accounting account - SQL part (#35994) +NEW: Accountancy - Analytical axis (SQL Structure) (#34738) +NEW: Accountancy - Manual input - Add script to greyed out subledger_account if general ledger is not centralized (#35855) +NEW: Accountancy - Transaction - Add verification on centralized account (#35824) +NEW: Accountancy - Various payment - Add script to greyed out subledger_account if general ledger is not centralized (#35842) +NEW: Add a boolean for lines in api and $properties (#34293) +NEW: Add accounting export mode for ISTEA (#36006) +NEW: add a limit to avoid too many answer in agenda view. Add warning if limit has been reached. +NEW: Add a page to edit http security headers of application (#34941) +NEW: Add auto-reference generation for tasks (like in project) in API (#35981) +NEW: Add column ref_ext and note_private for membership +NEW: Add column thirdparty ref_customer and ref_supplier in project list +NEW: Add column title in emailing and add more filters +NEW: add combining characters (accents, cedilla...) codes in dol_string_unaccent() (#35130) +NEW: add company date birth (SQL structure) (#34854) (UI) (#34861) +NEW: add conditional supplier price display (#35900) +NEW: Add configuration for default timesheet menu (#35805) +NEW: Add contact tab on product service (#35914) +NEW: Add directory navigation to Web Portal Shared Documents (#35443) +NEW: Added Messaging and agenda tabs on order and shipments (#34859) +NEW: Add event when installing a module in the security event list. +NEW: add extrafield option "empty on clone" (#34866) +NEW: add fields usage_opportunity, usage_task, usage_bill_time for project import (#35301) +NEW: Add filter on agenda event progression on agenda page +NEW: add free numbering module for members (#35636) +NEW: add global search for resource object (#36043) +NEW: add hidden const to get response header in geturlcontent function (#34781) (#34824) +NEW: add hook getListOfModels (#34626) +NEW: Add hook on calcula_price() and get_default_tva() +NEW: Add hooks in webportal (#35326) +NEW: add hook when printing new card button on thirdparty list (#36350) +NEW: add hourly rate in list of users +NEW: Add missing parameters for menus on webportal hook (#35550) +NEW: Add option PDF_PURCHASE_INVOICE_HIDE_VAT +NEW: Add option PROJECT_CAN_ALWAYS_LINK_TO_ALL_CUSTOMERS +NEW: Add option to create simple standalone shipment of non origin (#35651) +NEW: Add option to create standalone reception (#36134) +NEW: add private and public note on ticket (#35303) +NEW: add product_type field on fichinter (preparing subtotal) (#36196) +NEW: Add regions for CONGO, THE DEMOCRATIC REPUBLIC OF THE;CD (#36340) +NEW: ADD Send mail for reception / Delivery (#34829) +NEW: Add setup page to concat natively files on invoice PDF. +NEW: add shipping address to propal (#34441) +NEW: Add Sign feature on shipments (#34640) +NEW: Add sms reminder in reminder of agenda events (#35239) +NEW: Add SQL table for expensereport line extrafields support (#36251) +NEW: add supplier invoice, order and supplier order tag filter and bulk insert and statistics order and supplier order (#35399) +NEW: add supplier payment mail template (#35877) +NEW: add tags on proposals and supplier proposals and in statistics (#35553) +NEW: Add task categories 1/3 (#35848) +NEW: Add tasks card hooks (#35616) +NEW: Add the "Dispute status" in list of invoice. +NEW: add the option to not synchronize thirdparty <--> member (#36033) +NEW: Add the widget funnel of opportunities +NEW: Add tpl files for standalone shipment (#35624) +NEW: Add tpl loader for discounts.tpl.php (#34798) +NEW: Add Transfer Number (#35665) +NEW: Add Type, Description columns to Project Overview Expense Reports (#36214) +NEW: Add user permission for create/edit/delete supplier prices (#35940) +NEW: Allow omission of ODT template name when generating ODT and PDF (#35701) +NEW: Better navigation and report into database admin tools +NEW: Button to create a proposal and sale order from a contract always on +NEW: Can add info of main IT service provider in setup. +NEW: can admin payments extrafields (#34822) +NEW: Can drag and drop in BOM card +NEW: Can force_install_dolibarrpassword for automatic installation (#34537) +NEW: Can have a tooltip picto on title of column and keep autotruncation of label. Tooltip is show at begin of text. +NEW: Can show both currency code and symbol into the select of currency +NEW: Can sort on employee in holiday balance. Add link to go to history. +NEW: Can update value of timespent with last task hourly rate (#36018) +NEW: Can urlencode substitution variable of constants +NEW: Can view/list/edit the dispute status of an invoice +NEW: Change the path for the mailing files (#34878) +NEW: Constant ORDER_MASS_ACTION_BILLED_LINK_EXPEDITIONS (#34617) +NEW: const MENU_HIDE_EMAIL_TEMPLATES to hide email templates setup in Tools menu (#35739) +NEW: Create simple shipment of non origin (#35604) +NEW: Currency for the Democratic Republic of Congo added. (#36104) +NEW: Customized step in duration select (#34652) +NEW: Dashboard - Add option in ihm to disable MRP thumb (#36185) +NEW: date function related to holiday can accept country id in addition to country code +NEW: DEV Can set color of the on/off button. +NEW: Disable by default obfuscation methods and function in extrafields evaluable strings. Can re-enable with MAIN_ALLOW_OBFUSCATION_METHODS_IN_DOL_EVAL=1 +NEW: Discount split more than two parts (#34782) +NEW: Display company logo on kanban view (#34520) +NEW: dol_sort_array can be used with 2 sorting criteria. +NEW: Enhanced layout feature for emailing +NEW: Enhance popup for image preview (show size in title, can restore small view, always show the Rotate button). +NEW: Enhance the system for warnings on module activation +NEW: execute hook addMoreActionsButtons on bank card (#35598) +NEW: extrafields: add field to link a field to a module (#34416) +NEW: Feature to merge duplicate members (#35308) +NEW: generate renewal proposal for contracts (#35120) +NEW: Holiday - Allow to specify a specific mail address from (#36184) +NEW: hook allowing external modules to replace the behavior of fetchObjectLinked() (#34724) +NEW: hooks `showInputExtraField` and `showOutputExtraField` to override ExtraFields::show(Input|Output)Field (#35496) +NEW: implement box on product and interventional index page (#34629) +NEW: import subscriptions (#35612) +NEW: Introduce getCurrency(). $conf is no more allowed into computed formulae. +NEW: Invert logic of default date in proposal/order/invoice creation: Need option to NOT autofill instead of the opposite. +NEW: line input multicurrency price with tax (#35064) +NEW: Major overhaul of DataPolicyCron and add Recruitment policy (#34704) +NEW: Make the public contact form with experimental status +NEW: Minimal version of PHP is now 7.2 +NEW: More index for memberships table +NEW: More information on the user credential section +NEW: More webportal feature - Can add shared files and thirdparty documents (#35391) +NEW: New hook mergePdf (#34707) +NEW: On invoice, show also nb of credit notes notyet converted for consumption +NEW: Option to clone parent categories on variant creation (#35806) +NEW: Output of category tag is nicer for long subcategories. +NEW: Parent project column in list of projects (#36177) +NEW: Paymentok validate invoice if not already done (#35564) +NEW: PDF Show customer balance on invoice date (#34800) +NEW: possibility to define global entity in user param (#35908) +NEW: Add messaging and agenda features to proposals (#34883) +NEW: public and private note can be shown on contact list +NEW: Public donation page (#35565) +NEW: public pages donation, ticket and member use captcha setup (#35913) +NEW: Rework of the management of the card and fields on the web portal (#36076) +NEW: Save the BAN and RUM for SEPA into database not just file. +NEW: search all facture rec (#34563) +NEW: Show cron last result and output in info (#36028) +NEW: Show full date with seconds in the tooltip of date of event +NEW: Show the link to download the zip of a module on module setup +NEW: Show warning on banner when an email is not valid +NEW: The check file feature can limit check on unalterable files only +NEW: The flag "Dispute open" make the status in Red. +NEW: The PHP info is in a popup in install page. +NEW: update country list (#34865) +NEW: Update Incoterms to 2025 standards and add new terms (#36041) +NEW: Upgrade ACE editor to 1.43.12 +NEW: User/Date in the Follow tab are more condensed. +NEW: We added a hook to allow us to modify the Prospect Customer drop down… (#25635) +NEW: Withholdingtax how VatRefund (#34649) For developers: --------------- -* Introducing the TRIGGER_PREFIX property to force developers to use unique triggerkey per business object, limit also code to business CRUD events and identity when it is not. - +NEW: Introducing the TRIGGER_PREFIX property to force developers to use unique triggerkey per business object, to limit code to business CRUD events and report warning when it is not. +NEW: add extraparams field in llx_categorie (#35975) +NEW: Add prepare() method to DoliDB class (rebuild) (#35249) +NEW: Add function to split a discount in 2 by API REST (#34786) +NEW: Add a new API "product lots" (#36243) +NEW: Add API for Holidays/Leaves +NEW: add api for members statistics (#35851) +NEW: add api List VAT (#35920) +NEW: add api_paiements.class.php (#34756) +NEW: Add contact management on project Api (#35459) +NEW: Add contact support on products in REST API (#35925) +NEW: Add country ID resolution from country code for thirdparty api (#36345) +NEW: Add getcontacts on api of interventional and proposal (#35589) +NEW: Add hook initialization for interventions API (#35203) +NEW: Add option API_ENABLE_COUNT_CALLS +NEW: Add thirdparty search on api list (#34634) +NEW: add timespent API endpoints for projects and tasks add also cascading assignment of contacts to tasks (#35897) +NEW: add upload api feature for shipment (#34639) +NEW: Allow creating contact via api with ISO code (#36322) +NEW: API endpoint for getting products in a warehouse (#35918) +NEW: API for getting, adding, deleting and/or modifying email templates (#35853) +NEW: API for handling mass mailing targets (#35603) +NEW: API GET endpoint for thirdparties types listing (c_typent) (#34751) +NEW: Api mass emailing (#35531) +NEW: API user/groups/ POST, PUT, DELETE + some hurl tests (#35903) +NEW: API User - Remove user from group (#35453) +NEW: Implement listTimespent method in api_projects.class.php (#36093) +NEW: qual fixes on api contract (#36066) +NEW: stock API GET movement (#36193) WARNING: -------- The following changes may create regressions for some external modules, but were necessary to make Dolibarr better: -* The deprecated column egroupware_id has been dropped. +* The deprecated column "egroupware_id" has been dropped from table llx_user. * The property $sumpayed (duplicated of $totalpaid), $sumdeposit (duplicate of $totaldeposits) and $sumcreditnote (duplicate of $totalcreditnotes) has been removed (there are replaced with the property that was a duplication of (same for $sumpayed_multicurrency, $sumdeposit_multicurrency, $sumcreditnote_multicurrency). * Parameters $maxlen and $notooltip of Contract have been inverted to follow the standard. It was breaking the common use of getNomUrl() but if you were - using the parameter maxlen (rare) by using the old signature, result may be a tooltip that is no more visible on ref printed by your module. -* Removed array $MAP_ID_TO_CODE that was a duplicate of array "array_flip($categ->MAP_ID)" + using the parameter maxlen (rare) by using the old signature, result may be just a tooltip that is no more visible when mous over the contract ref shown by your module. +* Removed array $MAP_ID_TO_CODE that was a duplicate of the array "array_flip($categ->MAP_ID)" * The signature of the Sale order ->cancel() method and shipment ->cancel() has been modified to introduce the $user param like for other methods that modify a status. -* Adding new document templates must be done by adding files into the mymodule/core/modules/xxx/doc directory. Adding files into custom directory with the - same path than the core path, without using a "mymodule" directory is now forbidden. -* The directory theme/common/octicons has been removed -* The library timepicker.js has been removed. Was no more used by Dolibarr. -* Because of new TRIGGER_PREFIX property triggers SUPPLIER_PRODUCT_BUYPRICE_XXX are renamed to PRODUCT_BUYPRICE_XXX. -* Function img_pdf() has been removed. Replace it with img_picto('', 'pdf.png') if you were using it. +* Adding new "document generation templates" must be done by adding files into the mymodule/core/modules/xxx/doc directory. Adding files into custom directory with the + same path than the core path, without using a "mymodule" directory, is now forbidden. +* The directory theme/common/octicons has been removed. No resource was used by Dolibarrfrom this directory. +* The library timepicker.js has been removed. Was no more used by Dolibarr since a long time. +* Because of the new TRIGGER_PREFIX property, the triggers SUPPLIER_PRODUCT_BUYPRICE_XXX are renamed into PRODUCT_BUYPRICE_XXX. +* Function img_pdf() has been removed. Replace it with img_picto('', 'pdf') if you were using it. * The method run_trigger() was deprecated 10+ years ago in favor of runTrigger(). It has been removed. Change your trigger file if you still use it. * Property ->picto of module descriptors must contains the image extension if it is not a font awesome tag. Example: $this->picto="mymoduleimg.png"; * Stock movement API GET method output variable names has been harmonized with POST input parameter names -* $conf is no more allowed into computed formulae. You ca replace use of $conf->currency by NEW Introduce getCurrency() and $conf->global->xxx by getDolGlobalString('xxx') - +* Concatenation into computed property of extrafields is off by default. You can enable it from conf.php file by adding dol_concat to list of allowed function in $dolibarr_main_restrict_eval_methods, + For example: $dolibarr_main_restrict_eval_methods='getDolGlobalString,getDolGlobalInt,getDolCurrency,fetchNoCompute,hasRight,isModEnabled,isStringVarMatching,abs,min,max,round,dol_now,dol_concat,preg_match'; +* Old variable $obj and $object are no more allowed into on the fly evaluated strings like computed or conditions on extrafields. Use $objectoffield to get current object + Also if you were using temporary variables int a computed extrafields, the nameot temporary variable must match $var123. +* The hidden constant MAIN_ALLOW_UNSECURED_SPECIAL_CHARS_IN_DOL_EVAL in database has been replaced with the + variable $dolibarr_main_allow_unsecured_special_chars_in_dol_eval into file conf/conf.php +* $conf use into "computed formulae" of etrafields is now deprecated (not yet forbidden). You can replace use of $conf->currency by the new method getCurrency() and $conf->global->xxx by getDolGlobalString('xxx'). +* $user->rights->module->perms is also deprecated. You can use $user->hasRight() instead. +* The API endpoint /proposals/{id}/contact/{contactid}/{type}/{source} is now {id}/contact/{contactid}/{type} to match same behaviour than order and invoices. ***** ChangeLog for 22.0.3 compared to 22.0.2 ***** diff --git a/README.md b/README.md index 3c2b2e0ac43..8577be43b8b 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ![Downloads per day](https://img.shields.io/sourceforge/dw/dolibarr.svg) ![Docker hub pulls](https://img.shields.io/docker/pulls/dolibarr/dolibarr.svg) -[![Minimum PHP Version](https://img.shields.io/badge/php-%3E%3D%207.1-8892BF.svg?style=flat-square)](https://php.net/) +[![Minimum PHP Version](https://img.shields.io/badge/php-%3E%3D%207.2-8892BF.svg?style=flat-square)](https://php.net/) [![GitHub release](https://img.shields.io/github/v/release/Dolibarr/dolibarr)](https://github.com/Dolibarr/dolibarr) [![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/5521/badge)](https://bestpractices.coreinfrastructure.org/projects/5521) diff --git a/dev/build/doxygen/dolibarr-doxygen-build.pl b/dev/build/doxygen/dolibarr-doxygen-build.pl index 08deb42d6cd..345a1831075 100755 --- a/dev/build/doxygen/dolibarr-doxygen-build.pl +++ b/dev/build/doxygen/dolibarr-doxygen-build.pl @@ -1,54 +1,94 @@ #!/usr/bin/perl +##no critic (InputOutput::RequireBriefOpen) +use strict; +use warnings; + #-------------------------------------------------------------------- # Start the generation of the development documentation with doxygen #-------------------------------------------------------------------- # Determine the patho of this script -($DIR=$0) =~ s/([^\/\\]+)$//; -$DIR||='.'; +( my $DIR = $0 ) =~ s/([^\/\\]+)$//; +$DIR ||= '.'; $DIR =~ s/([^\/\\])[\\\/]+$/$1/; -$OPTIONS=""; +my $OPTIONS = ""; + #$OPTIONS="-d Preprocessor"; -$CONFFILE="dolibarr-doxygen.doxyfile"; +my $CONFFILE = "dolibarr-doxygen.doxyfile"; use Cwd; my $dir = getcwd; print "Current dir is: $dir\n"; + #print "Running dir for doxygen must be: $DIR\n"; -if (! -s "dev/build/doxygen/$CONFFILE") -{ - print "Error: current directory for building Dolibarr doxygen documentation is not correct.\n"; - print "\n"; +if ( !-s "dev/build/doxygen/$CONFFILE" ) { + print +"Error: current directory for building Dolibarr doxygen documentation is not correct.\n"; + print "\n"; print "Change your current directory then, to launch the script, run:\n"; - print '> perl .\dolibarr-doxygen-build.pl (on Windows)'."\n"; - print '> perl ../dolibarr-doxygen-build.pl (on Linux or BSD)'."\n"; - sleep 4; - exit 1; + print '> perl .\dolibarr-doxygen-build.pl (on Windows)' . "\n"; + print '> perl ../dolibarr-doxygen-build.pl (on Linux or BSD)' . "\n"; + sleep 4; + exit 1; } -$SOURCE="."; +my $SOURCE = "."; # Get version $MAJOR, $MINOR and $BUILD -$result = open( IN, "< " . $SOURCE . "/htdocs/filefunc.inc.php" ); -if ( !$result ) { die "Error: Can't open descriptor file " . $SOURCE . "/htdocs/filefunc.inc.php\n"; } -while () { - if ( $_ =~ /define\('DOL_VERSION', '([\d\.a-z\-]+)'\)/ ) { $PROJVERSION = $1; break; } +my $result = open( my $IN, "<", $SOURCE . "/htdocs/filefunc.inc.php" ); +if ( !$result ) { + die "Error: Can't open descriptor file " . $SOURCE + . "/htdocs/filefunc.inc.php\n"; } -close IN; -($MAJOR,$MINOR,$BUILD)=split(/\./,$PROJVERSION,3); -if ($MINOR eq '') { die "Error can't detect version into ".$SOURCE . "/htdocs/filefunc.inc.php"; } +my $PROJVERSION = ""; +while (<$IN>) { + if ( $_ =~ /define\('DOL_VERSION', '([\d\.a-z\-]+)'\)/ ) { + $PROJVERSION = $1; + last; + } +} +close $IN; +if ( $PROJVERSION eq "" ) { + my $DOL_MAJOR_VERSION; + my $DOL_MINOR_VERSION; + my @VERSION_FILES = ( "filefunc.inc.php", "version.inc.php" ); + foreach my $file (@VERSION_FILES) { + $result = open( my $IN, "<", $SOURCE . "/htdocs/$file" ); + if ( !$result ) { + die "Error: Can't open descriptor file " . $SOURCE + . "/htdocs/$file\n"; + } + while (<$IN>) { + if ( $_ =~ /define\('DOL_MAJOR_VERSION', '([\d\.a-z\-]+)'\)/ ) { + $DOL_MAJOR_VERSION = $1; + } + if ( $_ =~ /define\('DOL_MINOR_VERSION', '([\d\.a-z\-]+)'\)/ ) { + $DOL_MINOR_VERSION = $1; + } + } + close $IN; + } + $PROJVERSION = $DOL_MAJOR_VERSION . '.' . $DOL_MINOR_VERSION; +} -$version=$MAJOR.".".$MINOR.".".$BUILD; +( my $MAJOR, my $MINOR, my $BUILD ) = split( /\./, $PROJVERSION, 3 ); +if ( !defined($MINOR) || $MINOR eq '' ) { + die "Error can't detect version from " . $SOURCE + . "/htdocs/filefunc.inc.php"; +} +my $version = $MAJOR . "." . $MINOR . "." . $BUILD; -print "Running doxygen for version ".$version.", please wait...\n"; -print "cat dev/build/doxygen/$CONFFILE | sed -e 's/x\.y\.z/".$version."/' | doxygen $OPTIONS - 2>&1\n"; -$result=`cat dev/build/doxygen/$CONFFILE | sed -e 's/x\.y\.z/$version/' | doxygen $OPTIONS - 2>&1`; +print "Running doxygen for version " . $version . ", please wait...\n"; +print "cat dev/build/doxygen/$CONFFILE | sed -e 's/x\.y\.z/" . $version + . "/' | doxygen $OPTIONS - 2>&1\n"; +$result = +`cat dev/build/doxygen/$CONFFILE | sed -e 's/x\.y\.z/$version/' | doxygen $OPTIONS - 2>&1`; print $result; diff --git a/dev/build/doxygen/dolibarr-doxygen-filter.pl b/dev/build/doxygen/dolibarr-doxygen-filter.pl index 9233bd9e77d..1c7f27569e0 100755 --- a/dev/build/doxygen/dolibarr-doxygen-filter.pl +++ b/dev/build/doxygen/dolibarr-doxygen-filter.pl @@ -4,85 +4,79 @@ # on PHP source files before running Doxygen. # \author Laurent Destailleur #-------------------------------------------------------------------- +## no critic (InputOutput::RequireBriefOpen) + +use strict; +use warnings; # Usage: dolibarr-doxygen-filter.pl pathtofilefromdolibarrroot -$file=$ARGV[0]; -if (! $file) -{ +my $file = $ARGV[0]; +if ( !$file ) { print "Usage: dolibarr-doxygen-filter.pl pathtofilefromdolibarrroot\n"; exit; } -open(FILE,$file) || die "Failed to open file $file"; -while () -{ - if ($_ =~ /\\version\s/i) - { +open( my $fh, "<", $file ) || die "Failed to open file $file"; +while (<$fh>) { + if ( $_ =~ /\\version\s/i ) { $_ =~ s/\$Id://i; $_ =~ s/(Exp|)\s\$$//i; $_ =~ s/(\\version\s+)[^\s]+\s/$1/i; $_ =~ s/(\w)\s(\w)/$1_$2/g; } $_ =~ s/exit\s*;/exit(0);/i; - $i=0; - $len=length($_); - $s=""; - $insidequote=0; - $insidedquote=0; - $ignore=""; - while ($i < $len) - { - $c=substr($_,$i,1); - if ($c eq "\\") - { - if ($insidequote) { $ignore="'"; }; - if ($insidedquote) { $ignore="\""; }; + my $i = 0; + my $len = length($_); + my $s = ""; + my $insidequote = 0; + my $insidedquote = 0; + my $ignore = ""; + + while ( $i < $len ) { + my $c = substr( $_, $i, 1 ); + if ( $c eq "\\" ) { + if ($insidequote) { $ignore = "'"; } + if ($insidedquote) { $ignore = "\""; } } - else - { - if ($c eq "'") - { - if (! $insidedquote) - { - $c="\""; + else { + if ( $c eq "'" ) { + if ( !$insidedquote ) { + $c = "\""; + #print "X".$ignore; - if ($ignore ne "'") - { + if ( $ignore ne "'" ) { + #print "Z".$ignore; $insidequote++; - if ($insidequote == 2) - { - $insidequote=0; + if ( $insidequote == 2 ) { + $insidequote = 0; } } } + #print "X".$insidequote; } - elsif ($c eq "\"") - { + elsif ( $c eq "\"" ) { + #print "Y".$insidequote; - if ($insidequote) - { - $c="'"; + if ($insidequote) { + $c = "'"; } - else - { - if ($ignore ne "\"") - { + else { + if ( $ignore ne "\"" ) { $insidedquote++; - if ($insidedquote == 2) - { - $insidedquote=0; + if ( $insidedquote == 2 ) { + $insidedquote = 0; } } } } - $ignore=""; + $ignore = ""; } - $s.=$c; + $s .= $c; $i++; } print $s; } -close(FILE); +close($fh); diff --git a/dev/build/doxygen/dolibarr-doxygen-getversion.pl b/dev/build/doxygen/dolibarr-doxygen-getversion.pl index 31c52434d1f..3c98835ff22 100755 --- a/dev/build/doxygen/dolibarr-doxygen-getversion.pl +++ b/dev/build/doxygen/dolibarr-doxygen-getversion.pl @@ -1,4 +1,7 @@ #!/usr/bin/perl +use strict; +use warnings; + #-------------------------------------------------------------------- # Script to get version of a source file # Does not work with cygwin cvs command on Windows. @@ -7,15 +10,18 @@ # Usage: dolibarr-doxygen-getversion.pl pathtofilefromdolibarrroot -$file=$ARGV[0]; -if (! $file) -{ +$file = $ARGV[0]; +if ( !$file ) { print "Usage: dolibarr-doxygen-getversion.pl pathtofilefromdolibarrroot\n"; exit; } -$commande='cvs status "'.$file.'" | sed -n \'s/^[ \]*Working revision:[ \t]*\([0-9][0-9\.]*\).*/\1/p\''; +$commande = + 'cvs status "' + . $file + . '" | sed -n \'s/^[ \]*Working revision:[ \t]*\([0-9][0-9\.]*\).*/\1/p\''; + #print $commande; -$result=`$commande 2>&1`; +$result = `$commande 2>&1`; print $result; diff --git a/dev/build/generate_filelist_xml.php b/dev/build/generate_filelist_xml.php index 87378a45b03..9ecf3751344 100755 --- a/dev/build/generate_filelist_xml.php +++ b/dev/build/generate_filelist_xml.php @@ -26,6 +26,7 @@ if (!defined('NOREQUIREDB')) { define('NOREQUIREDB', '1'); // Do not create database handler $db } +define('NOREQUIREVIRTUALURL', 1); $sapi_type = php_sapi_name(); $script_file = basename(__FILE__); @@ -37,6 +38,8 @@ if (substr($sapi_type, 0, 3) == 'cgi') { exit(1); } +define('DOL_DOCUMENT_ROOT', dirname(dirname($path)).'/htdocs'); + require_once $path."../../htdocs/master.inc.php"; require_once DOL_DOCUMENT_ROOT."/core/lib/files.lib.php"; @@ -200,6 +203,9 @@ if ($release) { } +$needtoclose = 0; + + // Build the XML file if ($release) { $checksumconcat = array(); @@ -227,8 +233,6 @@ if ($release) { fputs($fp, ''."\n"); fputs($fp, ''."\n"); - $needtoclose = 0; - foreach ($includeconstants as $countrycode => $tmp) { fputs($fp, ''."\n"); foreach ($tmp as $constname => $constvalue) { @@ -382,6 +386,7 @@ foreach ($arrayofunalterablefiles as $entry) { } } else { $file = $entry['dir'].'/'.$entry['file']; + $dir = ''; $newdir = str_replace(DOL_DOCUMENT_ROOT, '', dirname($file)); $newdir = str_replace(dirname(__FILE__).'/../../htdocs', '', dirname($file)); if (!file_exists($file)) { @@ -461,24 +466,29 @@ if ($release) { if ($checklock) { print "Signature for unalterable files: ".$md5unalterable_files."\n"; + $lockedfile = DOL_DOCUMENT_ROOT.'/../dev/lockedfiles.txt'; $checksuminlockedfile = ''; - // Now we check the content of lockedfiles.txt - $arraylocked = file(DOL_DOCUMENT_ROOT.'/../dev/lockedfiles.txt'); - foreach ($arraylocked as $line) { - $tmparray = preg_split("/\s+/", $line, 3); - if ($tmparray[0] == $checklockmajorversion) { - $checksuminlockedfile = $tmparray[2]; + if (!file_exists($lockedfile)) { + print "Can't find the file ".$lockedfile.". No checksum to check\n"; + } else { + // Now we check the content of lockedfiles.txt + $arraylocked = file($lockedfile); + foreach ($arraylocked as $line) { + $tmparray = preg_split("/\s+/", $line, 3); + if ($tmparray[0] == $checklockmajorversion) { + $checksuminlockedfile = $tmparray[2]; + } } - } - if (empty($checksuminlockedfile)) { - print "The major version ".$checklockmajorversion." is not locked on the scope ".$checksource." (no entry found into dev/lockedfiles.txt).\n"; - } elseif ($checksuminlockedfile != $md5unalterable_files) { - print "The major version ".$checklockmajorversion." is locked on scope '".$checksource."' to checksum ".$checksuminlockedfile."\n"; - if ($checklockmajorversion != $checksource) { - print "The checksum now differs from the locked one, so we return an error.\n"; - print "\n"; - exit(10); + if (empty($checksuminlockedfile)) { + print "The major version ".$checklockmajorversion." is not locked on the scope ".$checksource." (file found but no matching entry found into dev/lockedfiles.txt).\n"; + } elseif ($checksuminlockedfile != $md5unalterable_files) { + print "The major version ".$checklockmajorversion." is locked on scope '".$checksource."' to checksum ".$checksuminlockedfile."\n"; + if ($checklockmajorversion != $checksource) { + print "The checksum now differs from the locked one, so we return an error.\n"; + print "\n"; + exit(10); + } } } } diff --git a/dev/build/gource/getavatars.pl b/dev/build/gource/getavatars.pl index 1f9b2ceffd5..e82ba51be65 100755 --- a/dev/build/gource/getavatars.pl +++ b/dev/build/gource/getavatars.pl @@ -1,4 +1,5 @@ #!/usr/bin/perl +## no critic (InputOutput::RequireBriefOpen) #fetch Gravatars use strict; @@ -10,39 +11,44 @@ use Digest::MD5 qw(md5_hex); my $size = 90; my $output_dir = './avatars'; -die("no .git/ directory found in current path\n") unless -d './avatars'; +die("no .git repository found in current path\n") unless -r './.git'; mkdir($output_dir) unless -d $output_dir; -open(GITLOG, q/git log --pretty=format:"%ae|%an" |/) or die("failed to read git-log: $!\n"); +open( my $GITLOG, '-|', q/git log --pretty=format:"%ae|%an" --reverse/ ) + or die("failed to read git-log: $!\n"); my %processed_authors; -while() { - chomp; - my($email, $author) = split(/\|/, $_); +while (<$GITLOG>) { + chomp; + my ( $email, $author ) = split( /\|/, $_ ); - next if $processed_authors{$author}++; + next if $processed_authors{$author}++; - my $author_image_file = $output_dir . '/' . $author . '.png'; + my $author_image_file = $output_dir . '/' . $author . '.png'; - #skip images we have - next if -e $author_image_file; + #skip images we have + next if -e $author_image_file; - #try and fetch image + #try and fetch image - my $grav_url = "http://www.gravatar.com/avatar/".md5_hex(lc $email)."?d=404&size=".$size; + my $grav_url = + "https://www.gravatar.com/avatar/" + . md5_hex( lc $email ) + . "?d=404&size=" + . $size; - warn "fetching image for '$author' $email ($grav_url)...\n"; + warn "fetching image for '$author' $email ($grav_url)...\n"; - my $rc = getstore($grav_url, $author_image_file); + my $rc = getstore( $grav_url, $author_image_file ); - sleep(1); + sleep(1); - if($rc != 200) { - unlink($author_image_file); - next; - } + if ( $rc != 200 ) { + unlink($author_image_file); + next; + } } -close GITLOG; +close $GITLOG; diff --git a/dev/build/makepack-dolibarr.pl b/dev/build/makepack-dolibarr.pl index 3c5325b971e..6aa331f050d 100755 --- a/dev/build/makepack-dolibarr.pl +++ b/dev/build/makepack-dolibarr.pl @@ -12,77 +12,88 @@ #DESTIDOLIMEDMODULES='/media/HDDATA1_LD/Mes Sites/Web/DoliCloud/dolimed.com/htdocs/files/modules' #DESTIDOLIMEDSTABLE='/media/HDDATA1_LD/Mes Sites/Web/DoliCloud/dolimed.com/htdocs/files/stable' #---------------------------------------------------------------------------- +## no critic (InputOutput::ProhibitExplicitStdin,InputOutput::RequireBriefOpen) +use strict; +use warnings; use Cwd; use Term::ANSIColor; # Change this to defined target for option 98 and 99 -$PROJECT="dolibarr"; +$PROJECT = "dolibarr"; -$PUBLISHBETARC="$ENV{'DESTIASSOLOGIN'}\@vmprod1.dolibarr.org:/home/dolibarr/asso.dolibarr.org/dolibarr_documents/website/www.dolibarr.org/files"; -$PUBLISHSTABLE="$ENV{'DESTISFLOGIN'}\@frs.sourceforge.net:/home/frs/project/dolibarr"; +$PUBLISHBETARC = +"$ENV{'DESTIASSOLOGIN'}\@vmprod1.dolibarr.org:/home/dolibarr/asso.dolibarr.org/dolibarr_documents/website/www.dolibarr.org/files"; +$PUBLISHSTABLE = + "$ENV{'DESTISFLOGIN'}\@frs.sourceforge.net:/home/frs/project/dolibarr"; # due to implicit origin on git commands, example: implicit origin, lionel upstream, eric dolibarr -$GITREMOTENAME="$ENV{'GITREMOTENAME'}"; +$GITREMOTENAME = "$ENV{'GITREMOTENAME'}"; + #@LISTETARGET=("TGZ","ZIP","RPM_GENERIC","RPM_FEDORA","RPM_MANDRIVA","RPM_OPENSUSE","DEB","EXEDOLIWAMP","SNAPSHOT"); # Possible packages -@LISTETARGET=("TGZ","ZIP","RPM_GENERIC","RPM_FEDORA","RPM_MANDRIVA","RPM_OPENSUSE","DEB","EXEDOLIWAMP","SNAPSHOT"); # Possible packages -%REQUIREMENTPUBLISH=( -"SF"=>"git ssh rsync", -"ASSO"=>"git ssh rsync" +@LISTETARGET = ( + "TGZ", "ZIP", "RPM_GENERIC", "RPM_FEDORA", + "RPM_MANDRIVA", "RPM_OPENSUSE", "DEB", "EXEDOLIWAMP", + "SNAPSHOT" +); # Possible packages +%REQUIREMENTPUBLISH = ( + "SF" => "git ssh rsync", + "ASSO" => "git ssh rsync" ); -%REQUIREMENTTARGET=( # Tool requirement for each package -"TGZ"=>"tar", -"ZIP"=>"7z", -"XZ"=>"xz", -"RPM_GENERIC"=>"rpmbuild", -"RPM_FEDORA"=>"rpmbuild", -"RPM_MANDRIVA"=>"rpmbuild", -"RPM_OPENSUSE"=>"rpmbuild", -"DEB"=>"dpkg,po2debconf", # need also debhelper -"FLATPACK"=>"flatpack", -"EXEDOLIWAMP"=>"ISCC.exe", -"SNAPSHOT"=>"tar" +%REQUIREMENTTARGET = ( # Tool requirement for each package + "TGZ" => "tar", + "ZIP" => "7z", + "XZ" => "xz", + "RPM_GENERIC" => "rpmbuild", + "RPM_FEDORA" => "rpmbuild", + "RPM_MANDRIVA" => "rpmbuild", + "RPM_OPENSUSE" => "rpmbuild", + "DEB" => "dpkg,po2debconf", # need also debhelper + "FLATPACK" => "flatpack", + "EXEDOLIWAMP" => "ISCC.exe", + "SNAPSHOT" => "tar" ); -%ALTERNATEPATH=( -"7z"=>"7-ZIP", -"makensis.exe"=>"NSIS" +%ALTERNATEPATH = ( + "7z" => "7-ZIP", + "makensis.exe" => "NSIS" ); -$RPMSUBVERSION="auto"; # auto use value found into BUILD -if (-d "/usr/src/redhat") { $RPMDIR="/usr/src/redhat"; } # redhat -if (-d "/usr/src/packages") { $RPMDIR="/usr/src/packages"; } # opensuse -if (-d "/usr/src/RPM") { $RPMDIR="/usr/src/RPM"; } # mandrake - +$RPMSUBVERSION = "auto"; # auto use value found into BUILD +if ( -d "/usr/src/redhat" ) { $RPMDIR = "/usr/src/redhat"; } # redhat +if ( -d "/usr/src/packages" ) { $RPMDIR = "/usr/src/packages"; } # opensuse +if ( -d "/usr/src/RPM" ) { $RPMDIR = "/usr/src/RPM"; } # mandrake use vars qw/ $REVISION $VERSION /; -$VERSION="4.0"; - - +$VERSION = "4.0"; #------------------------------------------------------------------------------ # MAIN #------------------------------------------------------------------------------ -($DIR=$0) =~ s/([^\/\\]+)$//; ($PROG=$1) =~ s/\.([^\.]*)$//; $Extension=$1; -$DIR||='.'; $DIR =~ s/([^\/\\])[\\\/]+$/$1/; +( $DIR = $0 ) =~ s/([^\/\\]+)$//; +( $PROG = $1 ) =~ s/\.([^\.]*)$//; +$Extension = $1; +$DIR ||= '.'; +$DIR =~ s/([^\/\\])[\\\/]+$/$1/; -$SOURCE="$DIR/../.."; -$DESTI="$SOURCE/build"; -if ($SOURCE !~ /^\// && $SOURCE !~ /^[a-z]:/i) -{ - print "Error: Launch the script $PROG.$Extension with its full path from /.\n"; +$SOURCE = "$DIR/../.."; +$DESTI = "$SOURCE/build"; +if ( $SOURCE !~ /^\// && $SOURCE !~ /^[a-z]:/i ) { + print + "Error: Launch the script $PROG.$Extension with its full path from /.\n"; print "$PROG.$Extension aborted.\n"; sleep 2; exit 1; } -if (! $ENV{"DESTIASSOLOGIN"} || ! $ENV{"DESTISFLOGIN"}) -{ +if ( !$ENV{"DESTIASSOLOGIN"} || !$ENV{"DESTISFLOGIN"} ) { print "Error: Missing environment variables.\n"; - print "You must define the environment variable DESTIASSOLOGIN and DESTISFLOGIN to define your login to connect to the dolibarr foundation server and/or mirrors servers.\n"; + print +"You must define the environment variable DESTIASSOLOGIN and DESTISFLOGIN to define your login to connect to the dolibarr foundation server and/or mirrors server.\n"; print "$PROG.$Extension aborted.\n"; print "\n"; print "You can set them with\n"; print "On Linux:\n"; - print "export DESTIASSOLOGIN='mylogin'; export DESTISFLOGIN='mylogin,project';\n"; + print +"export DESTIASSOLOGIN='mylogin'; export DESTISFLOGIN='mylogin,project';\n"; print "On Windows:\n"; print "set DESTIASSOLOGIN=mylogin\n"; print "set DESTISFLOGIN=mylogin,project\n"; @@ -93,10 +104,10 @@ if (! $ENV{"DESTIASSOLOGIN"} || ! $ENV{"DESTISFLOGIN"}) sleep 2; exit 1; } -if (! $ENV{"DESTIBETARC"} || ! $ENV{"DESTISTABLE"}) -{ +if ( !$ENV{"DESTIBETARC"} || !$ENV{"DESTISTABLE"} ) { print "Error: Missing environment variables.\n"; - print "You must define the environment variable DESTIBETARC and DESTISTABLE to point to the\ndirectories where you want to save the generated packages.\n"; + print +"You must define the environment variable DESTIBETARC and DESTISTABLE to point to the\ndirectories where you want to save the generated packages.\n"; print "$PROG.$Extension aborted.\n"; print "\n"; print "You can set them with\n"; @@ -107,21 +118,24 @@ if (! $ENV{"DESTIBETARC"} || ! $ENV{"DESTISTABLE"}) print "set DESTISTABLE=c:/tmp\n"; print "\n"; print "Example in .bashrc:\n"; - print "export DESTIBETARC='/mnt/HDDATA1_LD/Mes Archives/Doli/dolibarr/lastbuild'\n"; - print "export DESTISTABLE='/mnt/HDDATA1_LD/Mes Archives/Doli/dolibarr/stable'\n"; + print +"export DESTIBETARC='/mnt/HDDATA1_LD/Mes Archives/Doli/dolibarr/lastbuild'\n"; + print +"export DESTISTABLE='/mnt/HDDATA1_LD/Mes Archives/Doli/dolibarr/stable'\n"; sleep 2; exit 1; } -if (! -d $ENV{"DESTIBETARC"} || ! -d $ENV{"DESTISTABLE"}) -{ - print "Error: Directory of environment variable DESTIBETARC ($ENV{'DESTIBETARC'}) or DESTISTABLE ($ENV{'DESTISTABLE'}) does not exist.\n"; +if ( !-d $ENV{"DESTIBETARC"} || !-d $ENV{"DESTISTABLE"} ) { + print +"Error: Directory of environment variable DESTIBETARC ($ENV{'DESTIBETARC'}) or DESTISTABLE ($ENV{'DESTISTABLE'}) does not exist.\n"; print "$PROG.$Extension aborted.\n"; sleep 2; exit 1; } -if (! $ENV{"GITREMOTENAME"}) { - print "Error: environment variable GITREMOTENAME does not exist. You can set it to 'origin' or any other git remote name.\n"; +if ( !$ENV{"GITREMOTENAME"} ) { + print +"Error: environment variable GITREMOTENAME does not exist. You can set it to 'origin' or any other git remote name.\n"; print "$PROG.$Extension aborted.\n"; sleep 2; exit 1; @@ -129,10 +143,16 @@ if (! $ENV{"GITREMOTENAME"}) { # Detect OS type # -------------- -if ("$^O" =~ /linux/i || (-d "/etc" && -d "/var" && "$^O" !~ /cygwin/i)) { $OS='linux'; $CR=''; } -elsif (-d "/etc" && -d "/Users") { $OS='macosx'; $CR=''; } -elsif ("$^O" =~ /cygwin/i || "$^O" =~ /win32/i || "$^O" =~ /msys/i) { $OS='windows'; $CR="\r"; } -if (! $OS) { +if ( "$^O" =~ /linux/i || ( -d "/etc" && -d "/var" && "$^O" !~ /cygwin/i ) ) { + $OS = 'linux'; + $CR = ''; +} +elsif ( -d "/etc" && -d "/Users" ) { $OS = 'macosx'; $CR = ''; } +elsif ( "$^O" =~ /cygwin/i || "$^O" =~ /win32/i || "$^O" =~ /msys/i ) { + $OS = 'windows'; + $CR = "\r"; +} +if ( !$OS ) { print "Error: Can't detect your OS.\n"; print "Can't continue.\n"; print "$PROG.$Extension aborted.\n"; @@ -142,35 +162,52 @@ if (! $OS) { # Define buildroot # ---------------- -if ($OS =~ /linux/) { - $TEMP=$ENV{"TEMP"}||$ENV{"TMP"}||"/tmp"; +if ( $OS =~ /linux/ ) { + $TEMP = $ENV{"TEMP"} || $ENV{"TMP"} || "/tmp"; } -if ($OS =~ /macos/) { - $TEMP=$ENV{"TEMP"}||$ENV{"TMP"}||"/tmp"; +if ( $OS =~ /macos/ ) { + $TEMP = $ENV{"TEMP"} || $ENV{"TMP"} || "/tmp"; } -if ($OS =~ /windows/) { - $TEMP=$ENV{"TEMP"}||$ENV{"TMP"}||"c:/temp"; - $PROGPATH=$ENV{"ProgramFiles"}; +if ( $OS =~ /windows/ ) { + $TEMP = $ENV{"TEMP"} || $ENV{"TMP"} || "c:/temp"; + $PROGPATH = $ENV{"ProgramFiles"}; } -if (! $TEMP || ! -d $TEMP) { +if ( !$TEMP || !-d $TEMP ) { print "Error: A temporary directory can not be find.\n"; print "Check that TEMP or TMP environment variable is set correctly.\n"; print "$PROG.$Extension aborted.\n"; sleep 2; exit 2; } -$BUILDROOT="$TEMP/buildroot"; - +$BUILDROOT = "$TEMP/buildroot"; # Get version $MAJOR, $MINOR and $BUILD -$result = open( IN, "<" . $SOURCE . "/htdocs/version.inc.php" ); -if ( !$result ) { die "Error: Can't open descriptor file " . $SOURCE . "/htdocs/version.inc.php\n"; } -while () { - if ( $_ =~ /define\('DOL_VERSION',\s*'([\d\.a-z\-]+)'\)/ ) { $PROJVERSION = $1; break; } +open( my $IN, "<", $SOURCE . "/htdocs/version.inc.php" ) + or die "Error: Can't open version file " + . $SOURCE + . "/htdocs/version.inc.php\n"; +while (<$IN>) { + if ( $_ =~ /define\('DOL_MAJOR_VERSION',\s*'([\d\.a-z\-]+)'\)/ ) { + $MAJORVERSION = $1; + break; + } } -close IN; -($MAJOR,$MINOR,$BUILD)=split(/\./,$PROJVERSION,3); -if ($MINOR eq '') { die "Error can't detect version into ".$SOURCE . "/htdocs/version.inc.php"; } +close $IN; +open( my $IN2, "<", $SOURCE . "/htdocs/filefunc.inc.php" ) + or die "Error: Can't open version file " + . $SOURCE + . "/htdocs/filefunc.inc.php\n"; +while (<$IN2>) { + if ( $_ =~ /define\('DOL_MINOR_VERSION',\s*'([\d\.a-z\-]+)'\)/ ) { + $MINORVERSION = $1; + break; + } +} +close $IN2; +$PROJVERSION = $MAJORVERSION . "." . $MINORVERSION; + +( $MAJOR, $MINOR, $BUILD ) = split( /\./, $PROJVERSION, 3 ); +if ( $MINOR eq '' ) { die "Error can't detect version"; } # Set vars for packaging $FILENAME = "$PROJECT"; @@ -180,321 +217,415 @@ $FILENAMEZIP = "$PROJECT-$MAJOR.$MINOR.$BUILD"; $FILENAMEXZ = "$PROJECT-$MAJOR.$MINOR.$BUILD"; $FILENAMEDEB = "see later"; $FILENAMEEXEDOLIWAMP = "DoliWamp-$MAJOR.$MINOR.$BUILD"; + # For RPM -$ARCH='noarch'; +$ARCH = 'noarch'; $newbuild = $BUILD; -$newbuild =~ s/(dev|alpha)/0.1.a/gi; # dev (fedora) -$newbuild =~ s/beta(.?)/0.2.beta/gi; # beta (fedora) (we want beta1, beta2, betax to be same package name) -$newbuild =~ s/rc(.?)/0.3.rc/gi; # rc (fedora) (we want rc1, rc2, rcx to be same package name) -if ($newbuild !~ /-/) { $newbuild.='-0.4'; } # finale (fedora) +$newbuild =~ s/(dev|alpha)/0.1.a/gi; # dev (fedora) +$newbuild =~ s/beta(.?)/0.2.beta/gi + ; # beta (fedora) (we want beta1, beta2, betax to be same package name) +$newbuild =~ s/rc(.?)/0.3.rc/gi + ; # rc (fedora) (we want rc1, rc2, rcx to be same package name) +if ( $newbuild !~ /-/ ) { $newbuild .= '-0.4'; } # finale (fedora) + #$newbuild =~ s/(dev|alpha)/0/gi; # dev #$newbuild =~ s/beta/1/gi; # beta #$newbuild =~ s/rc./2/gi; # rc #if ($newbuild !~ /-/) { $newbuild.='-3'; } # finale -$REL1 = $newbuild; $REL1 =~ s/-.*$//gi; -if ($RPMSUBVERSION eq 'auto') { $RPMSUBVERSION = $newbuild; $RPMSUBVERSION =~ s/^.*-//gi; } -$FILENAMETGZ2="$PROJECT-$MAJOR.$MINOR.$REL1"; -$FILENAMERPM=$FILENAMETGZ2."-".$RPMSUBVERSION.".".$ARCH.".rpm"; -$FILENAMERPMSRC=$FILENAMETGZ2."-".$RPMSUBVERSION.".src.rpm"; +$REL1 = $newbuild; +$REL1 =~ s/-.*$//gi; +if ( $RPMSUBVERSION eq 'auto' ) { + $RPMSUBVERSION = $newbuild; + $RPMSUBVERSION =~ s/^.*-//gi; +} +$FILENAMETGZ2 = "$PROJECT-$MAJOR.$MINOR.$REL1"; +$FILENAMERPM = $FILENAMETGZ2 . "-" . $RPMSUBVERSION . "." . $ARCH . ".rpm"; +$FILENAMERPMSRC = $FILENAMETGZ2 . "-" . $RPMSUBVERSION . ".src.rpm"; + # For Deb $newbuild = $BUILD; -$newbuild =~ s/(dev|alpha)/1/gi; # dev -$newbuild =~ s/beta(.?)/2/gi; # beta (we want beta1, beta2, betax to be same package name) -$newbuild =~ s/rc(.?)/3/gi; # rc (we want rc1, rc2, rcx to be same package name) -if ($newbuild !~ /-/) { $newbuild.='-4'; } # finale is same than rc. +$newbuild =~ s/(dev|alpha)/1/gi; # dev +$newbuild =~ s/beta(.?)/2/gi + ; # beta (we want beta1, beta2, betax to be same package name) +$newbuild =~ + s/rc(.?)/3/gi; # rc (we want rc1, rc2, rcx to be same package name) +if ( $newbuild !~ /-/ ) { $newbuild .= '-4'; } # finale is same than rc. + # now newbuild is 0-1 or 0-4 for example. Note that for native package (see debian/source/format), we should not use a dash part but to get a better version management $build = $newbuild; $build =~ s/-.*$//g; + # now build is 0 for example # $build .= '+nmu1'; # now build is 0+nmu1 for example -$FILENAMEDEBNATIVE="${PROJECT}_${MAJOR}.${MINOR}.${build}"; -$FILENAMEDEB="${PROJECT}_${MAJOR}.${MINOR}.${newbuild}"; -$FILENAMEDEBSHORT="${PROJECT}_${MAJOR}.${MINOR}.${build}"; +$FILENAMEDEBNATIVE = "${PROJECT}_${MAJOR}.${MINOR}.${build}"; +$FILENAMEDEB = "${PROJECT}_${MAJOR}.${MINOR}.${newbuild}"; +$FILENAMEDEBSHORT = "${PROJECT}_${MAJOR}.${MINOR}.${build}"; - -my $copyalreadydone=0; -my $batch=0; -for (0..@ARGV-1) { - if ($ARGV[$_] =~ /^-*target=(\w+)/i) { $target=$1; $batch=1; } - if ($ARGV[$_] =~ /^-*desti=(.+)/i) { $DESTI=$1; } - if ($ARGV[$_] =~ /^-*prefix=(.+)/i) { - $PREFIX=$1; - $FILENAMESNAPSHOT.="-".$PREFIX; +my $copyalreadydone = 0; +my $batch = 0; +for ( 0 .. @ARGV - 1 ) { + if ( $ARGV[$_] =~ /^-*target=(\w+)/i ) { $target = $1; $batch = 1; } + if ( $ARGV[$_] =~ /^-*desti=(.+)/i ) { $DESTI = $1; } + if ( $ARGV[$_] =~ /^-*prefix=(.+)/i ) { + $PREFIX = $1; + $FILENAMESNAPSHOT .= "-" . $PREFIX; } } -if ($ENV{"DESTIBETARC"} && $BUILD =~ /[a-z]/i) { $DESTI = $ENV{"DESTIBETARC"}; } # Force output dir if env DESTIBETARC is defined -if ($ENV{"DESTISTABLE"} && $BUILD =~ /^[0-9]+$/) { $DESTI = $ENV{"DESTISTABLE"}; } # Force output dir if env DESTISTABLE is defined -if ($ENV{"PUBLISHBETARC"} && $BUILD =~ /[a-z]/i) { $PUBLISHBETARC = $ENV{"PUBLISHBETARC"}; } # Force target site for publishing if env PUBLISHBETARC is defined -if ($ENV{"PUBLISHSTABLE"} && $BUILD =~ /^[0-9]+$/) { $PUBLISHSTABLE = $ENV{"PUBLISHSTABLE"}; } # Force target site for publishing if env PUBLISHSTABLE is defined +if ( $ENV{"DESTIBETARC"} && $BUILD =~ /[a-z]/i ) { + $DESTI = $ENV{"DESTIBETARC"}; +} # Force output dir if env DESTIBETARC is defined +if ( $ENV{"DESTISTABLE"} && $BUILD =~ /^[0-9]+$/ ) { + $DESTI = $ENV{"DESTISTABLE"}; +} # Force output dir if env DESTISTABLE is defined +if ( $ENV{"PUBLISHBETARC"} && $BUILD =~ /[a-z]/i ) { + $PUBLISHBETARC = $ENV{"PUBLISHBETARC"}; +} # Force target site for publishing if env PUBLISHBETARC is defined +if ( $ENV{"PUBLISHSTABLE"} && $BUILD =~ /^[0-9]+$/ ) { + $PUBLISHSTABLE = $ENV{"PUBLISHSTABLE"}; +} # Force target site for publishing if env PUBLISHSTABLE is defined print "Makepack version $VERSION\n"; print "Building/publishing package name: $PROJECT\n"; print "Building/publishing package version: $MAJOR.$MINOR.$BUILD\n"; print "Source directory (SOURCE): $SOURCE\n"; print "Target directory (DESTI) : $DESTI\n"; -#print "Publishing target (PUBLISH): $PUBLISH\n"; +#print "Publishing target (PUBLISH): $PUBLISH\n"; # Choose package targets #----------------------- if ($target) { - if ($target eq "ALL") { + if ( $target eq "ALL" ) { foreach my $key (@LISTETARGET) { - if ($key ne 'SNAPSHOT' && $key ne 'SF' && $key ne 'ASSO') { $CHOOSEDTARGET{$key}=1; } + if ( $key ne 'SNAPSHOT' && $key ne 'SF' && $key ne 'ASSO' ) { + $CHOOSEDTARGET{$key} = 1; + } } } - if ($target ne "ALL" && $target ne "SF" && $target ne "ASSO") { $CHOOSEDTARGET{uc($target)}=1; } - if ($target eq "SF") { $CHOOSEDPUBLISH{"SF"}=1; } - if ($target eq "ASSO") { $CHOOSEDPUBLISH{"ASSO"}=1; } + if ( $target ne "ALL" && $target ne "SF" && $target ne "ASSO" ) { + $CHOOSEDTARGET{ uc($target) } = 1; + } + if ( $target eq "SF" ) { $CHOOSEDPUBLISH{"SF"} = 1; } + if ( $target eq "ASSO" ) { $CHOOSEDPUBLISH{"ASSO"} = 1; } } else { - my $found=0; + my $found = 0; my $NUM_SCRIPT; - my $cpt=0; - while (! $found) { - $cpt=0; - printf(" %2d - %-14s (%s)\n",$cpt,"ALL (1..10)","Need ".join(",",values %REQUIREMENTTARGET)); + my $cpt = 0; + while ( !$found ) { + $cpt = 0; + printf( " %2d - %-14s (%s)\n", + $cpt, "ALL (1..10)", + "Need " . join( ",", values %REQUIREMENTTARGET ) ); $cpt++; - printf(" %2d - %-14s\n",$cpt,"Generate check file"); + printf( " %2d - %-14s\n", $cpt, "Generate check file" ); foreach my $target (@LISTETARGET) { $cpt++; - printf(" %2d - %-14s (%s)\n",$cpt,$target,"Need ".$REQUIREMENTTARGET{$target}); + printf( " %2d - %-14s (%s)\n", + $cpt, $target, "Need " . $REQUIREMENTTARGET{$target} ); } - $cpt=98; - printf(" %2d - %-14s (%s)\n",$cpt,"ASSO (publish)","Need ".$REQUIREMENTPUBLISH{"ASSO"}); - $cpt=99; - printf(" %2d - %-14s (%s)\n",$cpt,"SF (publish)","Need ".$REQUIREMENTPUBLISH{"SF"}); + $cpt = 98; + printf( + " %2d - %-14s (%s)\n", + $cpt, + "ASSO (publish)", + "Need " . $REQUIREMENTPUBLISH{"ASSO"} + ); + $cpt = 99; + printf( " %2d - %-14s (%s)\n", + $cpt, "SF (publish)", "Need " . $REQUIREMENTPUBLISH{"SF"} ); # Ask which target to build - print "Choose one target number or several separated with space (0 - ".$cpt."): "; - $NUM_SCRIPT=; + print "Choose one target number or several separated with space (0 - " + . $cpt . "): "; + $NUM_SCRIPT = ; chomp($NUM_SCRIPT); - if ($NUM_SCRIPT !~ /^[0-9\s]+$/) - { + if ( $NUM_SCRIPT !~ /^[0-9\s]+$/ ) { print "This is not a valid package number list.\n"; $found = 0; } - else - { + else { $found = 1; } } print "\n"; - if ($NUM_SCRIPT eq "98") { - $CHOOSEDPUBLISH{"ASSO"}=1; + if ( $NUM_SCRIPT eq "98" ) { + $CHOOSEDPUBLISH{"ASSO"} = 1; } - elsif ($NUM_SCRIPT eq "99") { - $CHOOSEDPUBLISH{"SF"}=1; + elsif ( $NUM_SCRIPT eq "99" ) { + $CHOOSEDPUBLISH{"SF"} = 1; } - elsif ($NUM_SCRIPT eq "0") { - $CHOOSEDTARGET{"-CHKSUM"}=1; + elsif ( $NUM_SCRIPT eq "0" ) { + $CHOOSEDTARGET{"-CHKSUM"} = 1; foreach my $key (@LISTETARGET) { - if ($key ne 'SNAPSHOT' && $key ne 'ASSO' && $key ne 'SF') { $CHOOSEDTARGET{$key}=1; } + if ( $key ne 'SNAPSHOT' && $key ne 'ASSO' && $key ne 'SF' ) { + $CHOOSEDTARGET{$key} = 1; + } } } - elsif ($NUM_SCRIPT eq "1") { - $CHOOSEDTARGET{"-CHKSUM"}=1 + elsif ( $NUM_SCRIPT eq "1" ) { + $CHOOSEDTARGET{"-CHKSUM"} = 1; } else { - foreach my $num (split(/\s+/,$NUM_SCRIPT)) { - $CHOOSEDTARGET{$LISTETARGET[$num-2]}=1; + foreach my $num ( split( /\s+/, $NUM_SCRIPT ) ) { + $CHOOSEDTARGET{ $LISTETARGET[ $num - 2 ] } = 1; } } } - # Test if requirement is ok #-------------------------- -$atleastonerpm=0; -foreach my $target (sort keys %CHOOSEDTARGET) { - if ($target =~ /RPM/i) - { - if ($atleastonerpm && ($DESTI eq "$SOURCE/build")) - { - print "Error: You asked creation of several rpms. Because all rpm have same name, you must defined an environment variable DESTI to tell packager where it can create subdirs for each generated package.\n"; +$atleastonerpm = 0; +foreach my $target ( sort keys %CHOOSEDTARGET ) { + if ( $target =~ /RPM/i ) { + if ( $atleastonerpm && ( $DESTI eq "$SOURCE/build" ) ) { + print +"Error: You asked creation of several rpms. Because all rpm have same name, you must defined an environment variable DESTI to tell packager where it can create subdirs for each generated package.\n"; exit; } - $atleastonerpm=1; + $atleastonerpm = 1; } - foreach my $req (split(/[,\s]/,$REQUIREMENTTARGET{$target})) - { + foreach my $req ( split( /[,\s]/, $REQUIREMENTTARGET{$target} ) ) { + # Test print "Test requirement for target $target: Search '$req'... "; - $newreq=$req; $newparam=''; - if ($newreq eq 'zip') { $newparam.='-h'; } - if ($newreq eq 'xz') { $newparam.='-h'; } - $cmd="\"$newreq\" $newparam 2>&1"; - print "Test command ".$cmd."... "; - $ret=`$cmd`; - $coderetour=$?; $coderetour2=$coderetour>>8; - if ($coderetour != 0 && (($coderetour2 == 1 && $OS =~ /windows/ && $ret !~ /Usage/i) || ($coderetour2 == 127 && $OS !~ /windows/)) && $PROGPATH) { + $newreq = $req; + $newparam = ''; + if ( $newreq eq 'zip' ) { $newparam .= '-h'; } + if ( $newreq eq 'xz' ) { $newparam .= '-h'; } + $cmd = "\"$newreq\" $newparam 2>&1"; + print "Test command " . $cmd . "... "; + $ret = `$cmd`; + $coderetour = $?; + $coderetour2 = $coderetour >> 8; + + if ( + $coderetour != 0 + && ( ( $coderetour2 == 1 && $OS =~ /windows/ && $ret !~ /Usage/i ) + || ( $coderetour2 == 127 && $OS !~ /windows/ ) ) + && $PROGPATH + ) + { # Not found error, we try in PROGPATH - $ret=`"$PROGPATH/$ALTERNATEPATH{$req}/$req\" 2>&1`; - $coderetour=$?; $coderetour2=$coderetour>>8; - $REQUIREMENTTARGET{$target}="$PROGPATH/$ALTERNATEPATH{$req}/$req"; + $ret = `"$PROGPATH/$ALTERNATEPATH{$req}/$req\" 2>&1`; + $coderetour = $?; + $coderetour2 = $coderetour >> 8; + $REQUIREMENTTARGET{$target} = "$PROGPATH/$ALTERNATEPATH{$req}/$req"; } - if ($coderetour != 0 && (($coderetour2 == 1 && $OS =~ /windows/ && $ret !~ /Usage/i) || ($coderetour2 == 127 && $OS !~ /windows/))) { + if ( + $coderetour != 0 + && ( ( $coderetour2 == 1 && $OS =~ /windows/ && $ret !~ /Usage/i ) + || ( $coderetour2 == 127 && $OS !~ /windows/ ) ) + ) + { # Not found error - print "Not found\nCan't build target $target. Requirement '$req' not found in PATH\n"; - $CHOOSEDTARGET{$target}=-1; + print +"Not found\nCan't build target $target. Requirement '$req' not found in PATH\n"; + $CHOOSEDTARGET{$target} = -1; last; - } else { + } + else { # Pas erreur ou erreur autre que programme absent - print " Found ".$req."\n"; + print " Found " . $req . "\n"; } } } print "\n"; - #print join(',',sort keys %CHOOSEDTARGET)."\n"; # Check if there is at least one target to build #---------------------------------------------- -$nboftargetok=0; -$nboftargetneedbuildroot=0; -$nbofpublishneedtag=0; -$nbofpublishneedchangelog=0; +$nboftargetok = 0; +$nboftargetneedbuildroot = 0; +$nbofpublishneedtag = 0; +$nbofpublishneedchangelog = 0; -foreach my $target (sort keys %CHOOSEDTARGET) { - if ($target eq '-CHKSUM') { $nbofpublishneedchangelog++; } - if ($CHOOSEDTARGET{$target} < 0) { next; } - if ($target ne 'EXE' && $target ne 'EXEDOLIWAMP' && $target ne '-CHKSUM') +foreach my $target ( sort keys %CHOOSEDTARGET ) { + if ( $target eq '-CHKSUM' ) { $nbofpublishneedchangelog++; } + if ( $CHOOSEDTARGET{$target} < 0 ) { next; } + if ( $target ne 'EXE' && $target ne 'EXEDOLIWAMP' && $target ne '-CHKSUM' ) { $nboftargetneedbuildroot++; } $nboftargetok++; } -foreach my $target (sort keys %CHOOSEDPUBLISH) { - if ($CHOOSEDPUBLISH{$target} < 0) { next; } - if ($target eq 'ASSO') { $nbofpublishneedchangelog++; } - if ($target eq 'SF') { $nbofpublishneedchangelog++; $nbofpublishneedtag++; } +foreach my $target ( sort keys %CHOOSEDPUBLISH ) { + if ( $CHOOSEDPUBLISH{$target} < 0 ) { next; } + if ( $target eq 'ASSO' ) { $nbofpublishneedchangelog++; } + if ( $target eq 'SF' ) { + $nbofpublishneedchangelog++; + $nbofpublishneedtag++; + } $nboftargetok++; } - if ($nboftargetok) { # Check Changelog #---------------- - if ($nbofpublishneedchangelog) - { + if ($nbofpublishneedchangelog) { + # Test that the ChangeLog is ok - $TMPBUILDTOCHECKCHANGELOG=$BUILD; + $TMPBUILDTOCHECKCHANGELOG = $BUILD; $TMPBUILDTOCHECKCHANGELOG =~ s/\-rc\d*//; $TMPBUILDTOCHECKCHANGELOG =~ s/\-beta\d*//; $TMPBUILDTOCHECKCHANGELOG =~ s/\-alpha\d*//; - print "\nCheck if ChangeLog is ok for version $MAJOR.$MINOR\.$TMPBUILDTOCHECKCHANGELOG\n"; - $ret=`grep "ChangeLog for $MAJOR.$MINOR\.$TMPBUILDTOCHECKCHANGELOG" "$SOURCE/ChangeLog" 2>&1`; - if (! $ret) - { - print color("yellow"), "Error: The ChangeLogFile was not updated. Run the following command before building package for $MAJOR.$MINOR.$BUILD:\n", color('reset'); + print +"\nCheck if ChangeLog is ok for version $MAJOR.$MINOR\.$TMPBUILDTOCHECKCHANGELOG\n"; + $ret = +`grep "ChangeLog for $MAJOR.$MINOR\.$TMPBUILDTOCHECKCHANGELOG" "$SOURCE/ChangeLog" 2>&1`; + if ( !$ret ) { + print color("yellow"), +"Error: The ChangeLogFile was not updated. Run the following command before building package for $MAJOR.$MINOR.$BUILD:\n", + color('reset'); } - else - { - print "ChangeLog for $MAJOR.$MINOR\.$BUILD was found into '$SOURCE/ChangeLog'. But you can regenerate it with command:\n"; + else { + print +"ChangeLog for $MAJOR.$MINOR\.$BUILD was found into '$SOURCE/ChangeLog'. But you can regenerate it with command:\n"; } - if (! $BUILD || $BUILD eq '0-alpha' || $BUILD eq '0-beta' || $BUILD eq '0-rc') # For a major or future version + if ( !$BUILD + || $BUILD eq '0-alpha' + || $BUILD eq '0-beta' + || $BUILD eq '0-rc' ) # For a major or future version { print "Building changeLog file for a major version:\n"; print 'cd ~/git/dolibarr_dev; '; - #print 'git log `git rev-list --boundary '.$MAJOR.'.'.$MINOR.'..origin/develop | grep ^- | cut -c2- | head -n 1`.. --no-merges --pretty=short --oneline | sed -e "s/^[0-9a-z]* //" | grep -e \'^FIX\|NEW\|PERF\|SEC\|QUAL\|CLOSE\' | sort -u | sed \'s/FIXED:/FIX:/g\' | sed \'s/FIXED :/FIX:/g\' | sed \'s/FIX :/FIX:/g\' | sed \'s/FIX /FIX: /g\' | sed \'s/CLOSE/NEW/g\' | sed \'s/NEW :/NEW:/g\' | sed \'s/NEW /NEW: /g\' | sed \'s/^* //g\' > /tmp/aaa'; - print 'git log `diff -u <(git rev-list --first-parent '.($MAJOR - 1).'.0) <(git rev-list --first-parent develop) | sed -ne \'s/^ //p\' | head -1`.. --no-merges --pretty=short --oneline | sed -e "s/^[0-9a-z]* //" | grep -e \'^FIX\|NEW\' | sort -u | sed \'s/FIXED:/FIX:/g\' | sed \'s/FIXED :/FIX:/g\' | sed \'s/FIX :/FIX:/g\' | sed \'s/FIX /FIX: /g\' | sed \'s/NEW :/NEW:/g\' | sed \'s/NEW /NEW: /g\' > /tmp/changelogtocopy' + +#print 'git log `git rev-list --boundary '.$MAJOR.'.'.$MINOR.'..origin/develop | grep ^- | cut -c2- | head -n 1`.. --no-merges --pretty=short --oneline | sed -e "s/^[0-9a-z]* //" | grep -e \'^FIX\|NEW\|PERF\|SEC\|QUAL\|CLOSE\' | sort -u | sed \'s/FIXED:/FIX:/g\' | sed \'s/FIXED :/FIX:/g\' | sed \'s/FIX :/FIX:/g\' | sed \'s/FIX /FIX: /g\' | sed \'s/CLOSE/NEW/g\' | sed \'s/NEW :/NEW:/g\' | sed \'s/NEW /NEW: /g\' | sed \'s/^* //g\' > /tmp/aaa'; + print 'git log `diff -u <(git rev-list --first-parent ' + . ( $MAJOR - 1 ) + . '.0) <(git rev-list --first-parent develop) | sed -ne \'s/^ //p\' | head -1`.. --no-merges --pretty=short --oneline | sed -e "s/^[0-9a-z]* //" | grep -e \'^FIX\|NEW\' | sort -u | sed \'s/FIXED:/FIX:/g\' | sed \'s/FIXED :/FIX:/g\' | sed \'s/FIX :/FIX:/g\' | sed \'s/FIX /FIX: /g\' | sed \'s/NEW :/NEW:/g\' | sed \'s/NEW /NEW: /g\' > /tmp/changelogtocopy'; } - else # For a maintenance release + else # For a maintenance release { print "Building changeLog file for a maintenance version:\n"; - #print 'cd ~/git/dolibarr_'.$MAJOR.'.'.$MINOR.'; git log '.$MAJOR.'.'.$MINOR.'.'.($BUILD-1).'.. --no-merges --pretty=short --oneline | sed -e "s/^[0-9a-z]* //" | grep -e \'^FIX\|NEW\|PERF\|SEC\|QUAL\|CLOSE\' | sort -u | sed \'s/FIXED:/FIX:/g\' | sed \'s/FIXED :/FIX:/g\' | sed \'s/FIX :/FIX:/g\' | sed \'s/FIX /FIX: /g\' | sed \'s/CLOSE/NEW/g\'| sed \'s/NEW :/NEW:/g\' | sed \'s/NEW /NEW: /g\' > /tmp/aaa'; - print 'cd ~/git/dolibarr_'.$MAJOR.'.'.$MINOR.'; git log '.$MAJOR.'.'.$MINOR.'.'.($BUILD-1).'.. | grep -v "Merge branch" | grep -v "Merge pull" | grep "^ " | sed -e "s/^[0-9a-z]* *//" | grep -e \'^FIX\|NEW\|PERF\|SEC\|QUAL\|CLOSE\' | sort -u | sed \'s/FIXED:/FIX:/g\' | sed \'s/FIXED :/FIX:/g\' | sed \'s/FIX :/FIX:/g\' | sed \'s/FIX /FIX: /g\' | sed \'s/CLOSE/NEW/g\' | sed \'s/NEW :/NEW:/g\' | sed \'s/NEW /NEW: /g\' | sed \'s/^* //g\' > /tmp/aaa'; + +#print 'cd ~/git/dolibarr_'.$MAJOR.'.'.$MINOR.'; git log '.$MAJOR.'.'.$MINOR.'.'.($BUILD-1).'.. --no-merges --pretty=short --oneline | sed -e "s/^[0-9a-z]* //" | grep -e \'^FIX\|NEW\|PERF\|SEC\|QUAL\|CLOSE\' | sort -u | sed \'s/FIXED:/FIX:/g\' | sed \'s/FIXED :/FIX:/g\' | sed \'s/FIX :/FIX:/g\' | sed \'s/FIX /FIX: /g\' | sed \'s/CLOSE/NEW/g\'| sed \'s/NEW :/NEW:/g\' | sed \'s/NEW /NEW: /g\' > /tmp/aaa'; + print 'cd ~/git/dolibarr_' + . $MAJOR . '.' + . $MINOR + . '; git log ' + . $MAJOR . '.' + . $MINOR . '.' + . ( $BUILD - 1 ) + . '.. | grep -v "Merge branch" | grep -v "Merge pull" | grep "^ " | sed -e "s/^[0-9a-z]* *//" | grep -e \'^FIX\|NEW\|PERF\|SEC\|QUAL\|CLOSE\' | sort -u | sed \'s/FIXED:/FIX:/g\' | sed \'s/FIXED :/FIX:/g\' | sed \'s/FIX :/FIX:/g\' | sed \'s/FIX /FIX: /g\' | sed \'s/CLOSE/NEW/g\' | sed \'s/NEW :/NEW:/g\' | sed \'s/NEW /NEW: /g\' | sed \'s/^* //g\' > /tmp/aaa'; } print "\n"; - if (! $ret) - { - print "\nPress F to force and continue anyway (or other key to stop)... "; - my $WAITKEY=; + if ( !$ret ) { + print +"\nPress F to force and continue anyway (or other key to stop)... "; + my $WAITKEY = ; chomp($WAITKEY); - if ($WAITKEY ne 'F') - { + if ( $WAITKEY ne 'F' ) { print "Canceled.\n"; exit; } } + else { + print "\nPress a key to continue (or CTRL+C to stop)... "; + my $WAITKEY = ; + chomp($WAITKEY); + } } # Build xml check file #----------------------- - if ($CHOOSEDTARGET{'-CHKSUM'}) - { + if ( $CHOOSEDTARGET{'-CHKSUM'} ) { print "Go to directory $SOURCE\n"; - $olddir=getcwd(); + $olddir = getcwd(); chdir("$SOURCE"); print "Clean $SOURCE/htdocs/includes/autoload.php\n"; - $ret=`rm -f $SOURCE/htdocs/includes/autoload.php`; + $ret = `rm -f $SOURCE/htdocs/includes/autoload.php`; - $ret=`git ls-files . --exclude-standard --others`; - if ($ret) - { - print "Some files exists in source directory and are not indexed neither excluded in .gitignore.\n"; - print $ret; - print "Canceled.\n"; - exit; + $ret = `git ls-files . --exclude-standard --others`; + if ($ret) { + print +"Some files exists in source directory and are not indexed neither excluded in .gitignore.\n"; + print $ret; + print "Canceled.\n"; + exit; } - print 'Create xml check file with md5 checksum with command php '.$SOURCE.'/dev/build/generate_filelist_xml.php release='.$MAJOR.'.'.$MINOR.'.'.$BUILD."\n"; - $ret=`php $SOURCE/dev/build/generate_filelist_xml.php release=$MAJOR.$MINOR.$BUILD`; - my $retcode=$?; - if ($retcode!=0) - { - print "Error running generate_filelist_xml.php please check\n"; - print $ret; - print "Canceled.\n"; - exit; + print 'Create xml check file with md5 checksum with command php ' + . $SOURCE + . '/dev/build/generate_filelist_xml.php release=' + . $MAJOR . '.' + . $MINOR . '.' + . $BUILD . "\n"; + $ret = +`php $SOURCE/dev/build/generate_filelist_xml.php release=$MAJOR.$MINOR.$BUILD`; + my $retcode = $?; + if ( $retcode != 0 ) { + print "Error running generate_filelist_xml.php please check\n"; + print $ret; + print "Canceled.\n"; + exit; } - print $ret."\n"; - # Copy to final dir - $NEWDESTI=$DESTI; + print $ret. "\n"; + + # Copy to final dir + $NEWDESTI = $DESTI; if ( !-d "$NEWDESTI/signatures" ) { use File::Path qw( make_path ); - make_path "$NEWDESTI/signatures" or die "Failed to create path: $NEWDESTI/signatures"; + make_path "$NEWDESTI/signatures" + or die "Failed to create path: $NEWDESTI/signatures"; } - print "Copy \"$SOURCE/htdocs/install/filelist-$MAJOR.$MINOR.$BUILD.xml\" to $NEWDESTI/signatures/filelist-$MAJOR.$MINOR.$BUILD.xml\n"; - use File::Copy qw(copy); - copy "$SOURCE/htdocs/install/filelist-$MAJOR.$MINOR.$BUILD.xml", "$NEWDESTI/signatures/filelist-$MAJOR.$MINOR.$BUILD.xml"; + print +"Copy \"$SOURCE/htdocs/install/filelist-$MAJOR.$MINOR.$BUILD.xml\" to $NEWDESTI/signatures/filelist-$MAJOR.$MINOR.$BUILD.xml\n"; + use File::Copy qw(copy); + copy "$SOURCE/htdocs/install/filelist-$MAJOR.$MINOR.$BUILD.xml", + "$NEWDESTI/signatures/filelist-$MAJOR.$MINOR.$BUILD.xml"; } # Update GIT tag if required #--------------------------- - if ($nbofpublishneedtag) - { + if ($nbofpublishneedtag) { print "Go to directory $SOURCE\n"; - $olddir=getcwd(); + $olddir = getcwd(); chdir("$SOURCE"); - print 'Run git tag -a -m "'.$MAJOR.'.'.$MINOR.'.'.$BUILD.'" "'.$MAJOR.'.'.$MINOR.'.'.$BUILD.'"'."\n"; - $ret=`git tag -a -m "$MAJOR.$MINOR.$BUILD" "$MAJOR.$MINOR.$BUILD" 2>&1`; - if ($ret =~ /(already exists|existe déjà)/) - { - print "WARNING: Tag ".$MAJOR.'.'.$MINOR.'.'.$BUILD." already exists. Overwrite (y/N) ? "; - $QUESTIONOVERWRITETAG=; + print 'Run git tag -a -m "' + . $MAJOR . '.' + . $MINOR . '.' + . $BUILD . '" "' + . $MAJOR . '.' + . $MINOR . '.' + . $BUILD . '"' . "\n"; + $ret = + `git tag -a -m "$MAJOR.$MINOR.$BUILD" "$MAJOR.$MINOR.$BUILD" 2>&1`; + if ( $ret =~ /(already exists|existe déjà)/ ) { + print "WARNING: Tag " . $MAJOR . '.' . $MINOR . '.' . $BUILD + . " already exists. Overwrite (y/N) ? "; + $QUESTIONOVERWRITETAG = ; chomp($QUESTIONOVERWRITETAG); - if ($QUESTIONOVERWRITETAG =~ /(o|y)/) - { - print 'Run git tag -a -f -m "'.$MAJOR.'.'.$MINOR.'.'.$BUILD.'" "'.$MAJOR.'.'.$MINOR.'.'.$BUILD.'"'."\n"; - $ret=`git tag -a -f -m "$MAJOR.$MINOR.$BUILD" "$MAJOR.$MINOR.$BUILD"`; - print 'Run git push '.$GITREMOTENAME.' -f "$MAJOR.$MINOR.$BUILD"'."\n"; - $ret=`git push $GITREMOTENAME -f -"$MAJOR.$MINOR.$BUILD"`; + if ( $QUESTIONOVERWRITETAG =~ /(o|y)/ ) { + print 'Run git tag -a -f -m "' + . $MAJOR . '.' + . $MINOR . '.' + . $BUILD . '" "' + . $MAJOR . '.' + . $MINOR . '.' + . $BUILD . '"' . "\n"; + $ret = +`git tag -a -f -m "$MAJOR.$MINOR.$BUILD" "$MAJOR.$MINOR.$BUILD"`; + print 'Run git push ' + . $GITREMOTENAME + . ' -f "$MAJOR.$MINOR.$BUILD"' . "\n"; + $ret = `git push $GITREMOTENAME -f -"$MAJOR.$MINOR.$BUILD"`; + #$ret=`git push -f origin "$MAJOR.$MINOR.$BUILD"`; } } - else - { + else { print "Run git push $GITREMOTENAME --tags\n"; - $ret=`git push $GITREMOTENAME --tags`; + $ret = `git push $GITREMOTENAME --tags`; + #$ret=`git push origin "$MAJOR.$MINOR.$BUILD"`; } chdir("$olddir"); @@ -502,443 +633,546 @@ if ($nboftargetok) { # Update buildroot if required #----------------------------- - if ($nboftargetneedbuildroot) - { - if (! $copyalreadydone) { + if ($nboftargetneedbuildroot) { + if ( !$copyalreadydone ) { print "Creation of a buildroot used for all packages\n"; print "Delete directory $BUILDROOT\n"; - $ret=`rm -fr "$BUILDROOT"`; + $ret = `rm -fr "$BUILDROOT"`; mkdir "$BUILDROOT"; mkdir "$BUILDROOT/$PROJECT"; print "Copy $SOURCE into $BUILDROOT/$PROJECT\n"; - $ret=`cp -pr "$SOURCE" "$BUILDROOT/$PROJECT"`; + $ret = `cp -pr "$SOURCE" "$BUILDROOT/$PROJECT"`; - #print "Copy $SOURCE/dev/build/debian/apache/.htaccess into $BUILDROOT/$PROJECT/dev/build/debian/apache/.htaccess\n"; - #$ret=`cp -pr "$SOURCE/dev/build/debian/apache/.htaccess" "$BUILDROOT/$PROJECT/dev/build/debian/apache/.htaccess"`; +#print "Copy $SOURCE/dev/build/debian/apache/.htaccess into $BUILDROOT/$PROJECT/dev/build/debian/apache/.htaccess\n"; +#$ret=`cp -pr "$SOURCE/dev/build/debian/apache/.htaccess" "$BUILDROOT/$PROJECT/dev/build/debian/apache/.htaccess"`; } print "Clean $BUILDROOT\n"; - $ret=`rm -f $BUILDROOT/$PROJECT/.buildpath`; - $ret=`rm -fr $BUILDROOT/$PROJECT/.cache`; - $ret=`rm -fr $BUILDROOT/$PROJECT/.codeclimate.yml`; - $ret=`rm -fr $BUILDROOT/$PROJECT/.externalToolBuilders`; - $ret=`rm -fr $BUILDROOT/$PROJECT/.git*`; - $ret=`rm -fr $BUILDROOT/$PROJECT/.mailmap`; - $ret=`rm -fr $BUILDROOT/$PROJECT/.phpunit.result.cache`; - $ret=`rm -fr $BUILDROOT/$PROJECT/.project`; - $ret=`rm -fr $BUILDROOT/$PROJECT/.pydevproject`; - $ret=`rm -fr $BUILDROOT/$PROJECT/.settings`; - $ret=`rm -fr $BUILDROOT/$PROJECT/.scrutinizer.yml`; - $ret=`rm -fr $BUILDROOT/$PROJECT/.stickler.yml`; - $ret=`rm -fr $BUILDROOT/$PROJECT/.travis.yml`; - $ret=`rm -fr $BUILDROOT/$PROJECT/.tx`; - $ret=`rm -fr $BUILDROOT/$PROJECT/.pre-commit-config.yaml`; - $ret=`rm -fr $BUILDROOT/$PROJECT/.phan`; - $ret=`rm -f $BUILDROOT/$PROJECT/build.xml`; - $ret=`rm -f $BUILDROOT/$PROJECT/phpstan.neon`; - $ret=`rm -fr $BUILDROOT/$PROJECT/phpstan.neon.dist`; - $ret=`rm -f $BUILDROOT/$PROJECT/pom.xml`; - $ret=`rm -f $BUILDROOT/$PROJECT/README-*.md`; + $ret = `rm -f $BUILDROOT/$PROJECT/.buildpath`; + $ret = `rm -fr $BUILDROOT/$PROJECT/.cache`; + $ret = `rm -fr $BUILDROOT/$PROJECT/.codeclimate.yml`; + $ret = `rm -fr $BUILDROOT/$PROJECT/.externalToolBuilders`; + $ret = `rm -fr $BUILDROOT/$PROJECT/.git*`; + $ret = `rm -fr $BUILDROOT/$PROJECT/.mailmap`; + $ret = `rm -fr $BUILDROOT/$PROJECT/.phpunit.result.cache`; + $ret = `rm -fr $BUILDROOT/$PROJECT/.project`; + $ret = `rm -fr $BUILDROOT/$PROJECT/.pydevproject`; + $ret = `rm -fr $BUILDROOT/$PROJECT/.settings`; + $ret = `rm -fr $BUILDROOT/$PROJECT/.scrutinizer.yml`; + $ret = `rm -fr $BUILDROOT/$PROJECT/.stickler.yml`; + $ret = `rm -fr $BUILDROOT/$PROJECT/.travis.yml`; + $ret = `rm -fr $BUILDROOT/$PROJECT/.tx`; + $ret = `rm -fr $BUILDROOT/$PROJECT/.pre-commit-config.yaml`; + $ret = `rm -fr $BUILDROOT/$PROJECT/.phan`; + $ret = `rm -f $BUILDROOT/$PROJECT/build.xml`; + $ret = `rm -f $BUILDROOT/$PROJECT/phpstan.neon`; + $ret = `rm -fr $BUILDROOT/$PROJECT/phpstan.neon.dist`; + $ret = `rm -f $BUILDROOT/$PROJECT/pom.xml`; + $ret = `rm -f $BUILDROOT/$PROJECT/README-*.md`; - $ret=`rm -fr $BUILDROOT/$PROJECT/dev/build/html`; - $ret=`rm -f $BUILDROOT/$PROJECT/dev/build/Doli*-*`; - $ret=`rm -f $BUILDROOT/$PROJECT/dev/build/dolibarr_*.deb`; - $ret=`rm -f $BUILDROOT/$PROJECT/dev/build/dolibarr_*.dsc`; - $ret=`rm -f $BUILDROOT/$PROJECT/dev/build/dolibarr_*.tar.gz`; - $ret=`rm -f $BUILDROOT/$PROJECT/dev/build/dolibarr_*.tar.xz`; - $ret=`rm -f $BUILDROOT/$PROJECT/dev/build/dolibarr-*.deb`; - $ret=`rm -f $BUILDROOT/$PROJECT/dev/build/dolibarr-*.rpm`; - $ret=`rm -f $BUILDROOT/$PROJECT/dev/build/dolibarr-*.tar`; - $ret=`rm -f $BUILDROOT/$PROJECT/dev/build/dolibarr-*.tar.gz`; - $ret=`rm -f $BUILDROOT/$PROJECT/dev/build/dolibarr-*.tar.xz`; - $ret=`rm -f $BUILDROOT/$PROJECT/dev/build/dolibarr-*.tgz`; - $ret=`rm -f $BUILDROOT/$PROJECT/dev/build/dolibarr-*.xz`; - $ret=`rm -f $BUILDROOT/$PROJECT/dev/build/dolibarr-*.zip`; - $ret=`rm -f $BUILDROOT/$PROJECT/dev/build/doxygen/doxygen_warnings.log`; - $ret=`rm -fr $BUILDROOT/$PROJECT/dev/build/phpstan/phpstan`; - $ret=`rm -f $BUILDROOT/$PROJECT/htdocs/cache.manifest`; - $ret=`rm -f $BUILDROOT/$PROJECT/htdocs/conf/conf.php`; - $ret=`rm -f $BUILDROOT/$PROJECT/htdocs/conf/conf.php.mysql`; - $ret=`rm -f $BUILDROOT/$PROJECT/htdocs/conf/conf.php.nova*`; - $ret=`rm -f $BUILDROOT/$PROJECT/htdocs/conf/conf.php.old`; - $ret=`rm -f $BUILDROOT/$PROJECT/htdocs/conf/conf.php.pgsql`; - $ret=`rm -f $BUILDROOT/$PROJECT/htdocs/conf/conf*sav*`; + $ret = `rm -fr $BUILDROOT/$PROJECT/dev/build/html`; + $ret = `rm -f $BUILDROOT/$PROJECT/dev/build/Doli*-*`; + $ret = `rm -f $BUILDROOT/$PROJECT/dev/build/dolibarr_*.deb`; + $ret = `rm -f $BUILDROOT/$PROJECT/dev/build/dolibarr_*.dsc`; + $ret = `rm -f $BUILDROOT/$PROJECT/dev/build/dolibarr_*.tar.gz`; + $ret = `rm -f $BUILDROOT/$PROJECT/dev/build/dolibarr_*.tar.xz`; + $ret = `rm -f $BUILDROOT/$PROJECT/dev/build/dolibarr-*.deb`; + $ret = `rm -f $BUILDROOT/$PROJECT/dev/build/dolibarr-*.rpm`; + $ret = `rm -f $BUILDROOT/$PROJECT/dev/build/dolibarr-*.tar`; + $ret = `rm -f $BUILDROOT/$PROJECT/dev/build/dolibarr-*.tar.gz`; + $ret = `rm -f $BUILDROOT/$PROJECT/dev/build/dolibarr-*.tar.xz`; + $ret = `rm -f $BUILDROOT/$PROJECT/dev/build/dolibarr-*.tgz`; + $ret = `rm -f $BUILDROOT/$PROJECT/dev/build/dolibarr-*.xz`; + $ret = `rm -f $BUILDROOT/$PROJECT/dev/build/dolibarr-*.zip`; + $ret = + `rm -f $BUILDROOT/$PROJECT/dev/build/doxygen/doxygen_warnings.log`; + $ret = `rm -fr $BUILDROOT/$PROJECT/dev/build/phpstan/phpstan`; + $ret = `rm -f $BUILDROOT/$PROJECT/htdocs/cache.manifest`; + $ret = `rm -f $BUILDROOT/$PROJECT/htdocs/conf/conf.php`; + $ret = `rm -f $BUILDROOT/$PROJECT/htdocs/conf/conf.php.mysql`; + $ret = `rm -f $BUILDROOT/$PROJECT/htdocs/conf/conf.php.nova*`; + $ret = `rm -f $BUILDROOT/$PROJECT/htdocs/conf/conf.php.old`; + $ret = `rm -f $BUILDROOT/$PROJECT/htdocs/conf/conf.php.pgsql`; + $ret = `rm -f $BUILDROOT/$PROJECT/htdocs/conf/conf*sav*`; - $ret=`rm -f $BUILDROOT/$PROJECT/htdocs/install/mssql/README`; - $ret=`rm -f $BUILDROOT/$PROJECT/htdocs/install/mysql/README`; - $ret=`rm -f $BUILDROOT/$PROJECT/htdocs/install/pgsql/README`; + $ret = `rm -f $BUILDROOT/$PROJECT/htdocs/install/mssql/README`; + $ret = `rm -f $BUILDROOT/$PROJECT/htdocs/install/mysql/README`; + $ret = `rm -f $BUILDROOT/$PROJECT/htdocs/install/pgsql/README`; - $ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/install/mssql`; - $ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/install/sqlite3`; + $ret = `rm -fr $BUILDROOT/$PROJECT/htdocs/install/mssql`; + $ret = `rm -fr $BUILDROOT/$PROJECT/htdocs/install/sqlite3`; - $ret=`rm -fr $BUILDROOT/$PROJECT/node_modules`; + $ret = `rm -fr $BUILDROOT/$PROJECT/node_modules`; - $ret=`rm -fr $BUILDROOT/$PROJECT/dev/ansible`; - $ret=`rm -fr $BUILDROOT/$PROJECT/dev/codesniffer`; - $ret=`rm -fr $BUILDROOT/$PROJECT/dev/codetemplates`; - $ret=`rm -fr $BUILDROOT/$PROJECT/dev/examples/ldap`; - $ret=`rm -fr $BUILDROOT/$PROJECT/dev/examples/zapier`; - $ret=`rm -fr $BUILDROOT/$PROJECT/dev/initdata`; - $ret=`rm -fr $BUILDROOT/$PROJECT/dev/initdemo`; - $ret=`rm -fr $BUILDROOT/$PROJECT/dev/resources/dbmodel`; - $ret=`rm -fr $BUILDROOT/$PROJECT/dev/resources/iso-normes`; - $ret=`rm -fr $BUILDROOT/$PROJECT/dev/resources/licence`; - $ret=`rm -fr $BUILDROOT/$PROJECT/dev/mail`; - $ret=`rm -fr $BUILDROOT/$PROJECT/dev/multitail`; - $ret=`rm -fr $BUILDROOT/$PROJECT/dev/phpcheckstyle`; - $ret=`rm -fr $BUILDROOT/$PROJECT/dev/phpunit`; - $ret=`rm -fr $BUILDROOT/$PROJECT/dev/security`; - $ret=`rm -fr $BUILDROOT/$PROJECT/dev/spec`; - $ret=`rm -fr $BUILDROOT/$PROJECT/dev/test`; - $ret=`rm -fr $BUILDROOT/$PROJECT/dev/tools/php-cs-fixer/vendor`; - $ret=`rm -fr $BUILDROOT/$PROJECT/dev/tools/rector/vendor`; - $ret=`rm -fr $BUILDROOT/$PROJECT/dev/uml`; - $ret=`rm -fr $BUILDROOT/$PROJECT/dev/vagrant`; - $ret=`rm -fr $BUILDROOT/$PROJECT/dev/xdebug`; - $ret=`rm -f $BUILDROOT/$PROJECT/dev/dolibarr_changes.txt`; - $ret=`rm -f $BUILDROOT/$PROJECT/dev/README`; - $ret=`rm -f $BUILDROOT/$PROJECT/doc/images/dolibarr_screenshot2.png`; - $ret=`rm -f $BUILDROOT/$PROJECT/doc/images/dolibarr_screenshot3.png`; - $ret=`rm -f $BUILDROOT/$PROJECT/doc/images/dolibarr_screenshot4.png`; - $ret=`rm -f $BUILDROOT/$PROJECT/doc/images/dolibarr_screenshot5.png`; - $ret=`rm -f $BUILDROOT/$PROJECT/doc/images/dolibarr_screenshot6.png`; - $ret=`rm -f $BUILDROOT/$PROJECT/doc/images/dolibarr_screenshot7.png`; - $ret=`rm -f $BUILDROOT/$PROJECT/doc/images/dolibarr_screenshot8.png`; - $ret=`rm -f $BUILDROOT/$PROJECT/doc/images/dolibarr_screenshot9.png`; - $ret=`rm -f $BUILDROOT/$PROJECT/doc/images/dolibarr_screenshot10.png`; - $ret=`rm -f $BUILDROOT/$PROJECT/doc/images/dolibarr_screenshot11.png`; - $ret=`rm -f $BUILDROOT/$PROJECT/doc/images/dolibarr_screenshot12.png`; + $ret = `rm -fr $BUILDROOT/$PROJECT/dev/ansible`; + $ret = `rm -fr $BUILDROOT/$PROJECT/dev/codesniffer`; + $ret = `rm -fr $BUILDROOT/$PROJECT/dev/codetemplates`; + $ret = `rm -fr $BUILDROOT/$PROJECT/dev/examples/ldap`; + $ret = `rm -fr $BUILDROOT/$PROJECT/dev/examples/zapier`; + $ret = `rm -fr $BUILDROOT/$PROJECT/dev/initdata`; + $ret = `rm -fr $BUILDROOT/$PROJECT/dev/initdemo`; + $ret = `rm -fr $BUILDROOT/$PROJECT/dev/resources/dbmodel`; + $ret = `rm -fr $BUILDROOT/$PROJECT/dev/resources/iso-normes`; + $ret = `rm -fr $BUILDROOT/$PROJECT/dev/resources/licence`; + $ret = `rm -fr $BUILDROOT/$PROJECT/dev/mail`; + $ret = `rm -fr $BUILDROOT/$PROJECT/dev/multitail`; + $ret = `rm -fr $BUILDROOT/$PROJECT/dev/phpcheckstyle`; + $ret = `rm -fr $BUILDROOT/$PROJECT/dev/phpunit`; + $ret = `rm -fr $BUILDROOT/$PROJECT/dev/security`; + $ret = `rm -fr $BUILDROOT/$PROJECT/dev/spec`; + $ret = `rm -fr $BUILDROOT/$PROJECT/dev/test`; + $ret = `rm -fr $BUILDROOT/$PROJECT/dev/tools/php-cs-fixer/vendor`; + $ret = `rm -fr $BUILDROOT/$PROJECT/dev/tools/rector/vendor`; + $ret = `rm -fr $BUILDROOT/$PROJECT/dev/uml`; + $ret = `rm -fr $BUILDROOT/$PROJECT/dev/vagrant`; + $ret = `rm -fr $BUILDROOT/$PROJECT/dev/xdebug`; + $ret = `rm -f $BUILDROOT/$PROJECT/dev/dolibarr_changes.txt`; + $ret = `rm -f $BUILDROOT/$PROJECT/dev/README`; + $ret = `rm -f $BUILDROOT/$PROJECT/doc/images/dolibarr_screenshot2.png`; + $ret = `rm -f $BUILDROOT/$PROJECT/doc/images/dolibarr_screenshot3.png`; + $ret = `rm -f $BUILDROOT/$PROJECT/doc/images/dolibarr_screenshot4.png`; + $ret = `rm -f $BUILDROOT/$PROJECT/doc/images/dolibarr_screenshot5.png`; + $ret = `rm -f $BUILDROOT/$PROJECT/doc/images/dolibarr_screenshot6.png`; + $ret = `rm -f $BUILDROOT/$PROJECT/doc/images/dolibarr_screenshot7.png`; + $ret = `rm -f $BUILDROOT/$PROJECT/doc/images/dolibarr_screenshot8.png`; + $ret = `rm -f $BUILDROOT/$PROJECT/doc/images/dolibarr_screenshot9.png`; + $ret = + `rm -f $BUILDROOT/$PROJECT/doc/images/dolibarr_screenshot10.png`; + $ret = + `rm -f $BUILDROOT/$PROJECT/doc/images/dolibarr_screenshot11.png`; + $ret = + `rm -f $BUILDROOT/$PROJECT/doc/images/dolibarr_screenshot12.png`; # Security to avoid to package data files - print "Remove documents dir\n"; - $ret=`rm -fr $BUILDROOT/$PROJECT/document`; - $ret=`rm -fr $BUILDROOT/$PROJECT/documents`; - $ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/document`; - $ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/documents`; + print "Remove documents dir\n"; + $ret = `rm -fr $BUILDROOT/$PROJECT/document`; + $ret = `rm -fr $BUILDROOT/$PROJECT/documents`; + $ret = `rm -fr $BUILDROOT/$PROJECT/htdocs/document`; + $ret = `rm -fr $BUILDROOT/$PROJECT/htdocs/documents`; - print "Remove subdir of custom dir\n"; - print "find $BUILDROOT/$PROJECT/htdocs/custom/* -type d -exec rm -fr {} \\;\n"; - $ret=`find $BUILDROOT/$PROJECT/htdocs/custom/* -type d -exec rm -fr {} \\; >/dev/null 2>&1`; # For custom we want to remove all subdirs but not files - print "find $BUILDROOT/$PROJECT/htdocs/custom/* -type l -exec rm -fr {} \\;\n"; - $ret=`find $BUILDROOT/$PROJECT/htdocs/custom/* -type l -exec rm -fr {} \\; >/dev/null 2>&1`; # For custom we want to remove all subdirs, even symbolic links, but not files + print "Remove subdir of custom dir\n"; + print +"find $BUILDROOT/$PROJECT/htdocs/custom/* -type d -exec rm -fr {} \\;\n"; + $ret = +`find $BUILDROOT/$PROJECT/htdocs/custom/* -type d -exec rm -fr {} \\; >/dev/null 2>&1` + ; # For custom we want to remove all subdirs but not files + print +"find $BUILDROOT/$PROJECT/htdocs/custom/* -type l -exec rm -fr {} \\;\n"; + $ret = +`find $BUILDROOT/$PROJECT/htdocs/custom/* -type l -exec rm -fr {} \\; >/dev/null 2>&1` + ; # For custom we want to remove all subdirs, even symbolic links, but not files - # Removed known external modules to avoid any error when packaging from env where external modules are tested - $ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/abricot*`; - $ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/accountingexport*`; - $ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/allscreens*`; - $ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/ancotec*`; - $ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/cabinetmed*`; - $ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/calling*`; - $ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/bootstrap*`; - $ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/dolimed*`; - $ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/dolimod*`; - $ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/factory*`; - $ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/forceproject*`; - $ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/lead*`; - $ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/langs/*/README.md`; - $ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/management*`; - $ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/multicompany*`; - $ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/ndf*`; - $ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/nltechno*`; - $ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/nomenclature*`; - $ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/of/`; - $ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/oscim*`; - $ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/pos*`; - $ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/teclib*`; - $ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/timesheet*`; - $ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/webmail*`; - $ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/theme/common/fontawesome-5/svgs`; +# Removed known external modules to avoid any error when packaging from env where external modules are tested + $ret = `rm -fr $BUILDROOT/$PROJECT/htdocs/abricot*`; + $ret = `rm -fr $BUILDROOT/$PROJECT/htdocs/accountingexport*`; + $ret = `rm -fr $BUILDROOT/$PROJECT/htdocs/allscreens*`; + $ret = `rm -fr $BUILDROOT/$PROJECT/htdocs/ancotec*`; + $ret = `rm -fr $BUILDROOT/$PROJECT/htdocs/cabinetmed*`; + $ret = `rm -fr $BUILDROOT/$PROJECT/htdocs/calling*`; + $ret = `rm -fr $BUILDROOT/$PROJECT/htdocs/bootstrap*`; + $ret = `rm -fr $BUILDROOT/$PROJECT/htdocs/dolimed*`; + $ret = `rm -fr $BUILDROOT/$PROJECT/htdocs/dolimod*`; + $ret = `rm -fr $BUILDROOT/$PROJECT/htdocs/factory*`; + $ret = `rm -fr $BUILDROOT/$PROJECT/htdocs/forceproject*`; + $ret = `rm -fr $BUILDROOT/$PROJECT/htdocs/lead*`; + $ret = `rm -fr $BUILDROOT/$PROJECT/htdocs/langs/*/README.md`; + $ret = `rm -fr $BUILDROOT/$PROJECT/htdocs/management*`; + $ret = `rm -fr $BUILDROOT/$PROJECT/htdocs/multicompany*`; + $ret = `rm -fr $BUILDROOT/$PROJECT/htdocs/ndf*`; + $ret = `rm -fr $BUILDROOT/$PROJECT/htdocs/nltechno*`; + $ret = `rm -fr $BUILDROOT/$PROJECT/htdocs/nomenclature*`; + $ret = `rm -fr $BUILDROOT/$PROJECT/htdocs/of/`; + $ret = `rm -fr $BUILDROOT/$PROJECT/htdocs/oscim*`; + $ret = `rm -fr $BUILDROOT/$PROJECT/htdocs/pos*`; + $ret = `rm -fr $BUILDROOT/$PROJECT/htdocs/teclib*`; + $ret = `rm -fr $BUILDROOT/$PROJECT/htdocs/timesheet*`; + $ret = `rm -fr $BUILDROOT/$PROJECT/htdocs/webmail*`; + $ret = + `rm -fr $BUILDROOT/$PROJECT/htdocs/theme/common/fontawesome-5/svgs`; # Removed other test files - $ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/public/test`; - $ret=`rm -fr $BUILDROOT/$PROJECT/test`; - $ret=`rm -fr $BUILDROOT/$PROJECT/Thumbs.db $BUILDROOT/$PROJECT/*/Thumbs.db $BUILDROOT/$PROJECT/*/*/Thumbs.db $BUILDROOT/$PROJECT/*/*/*/Thumbs.db $BUILDROOT/$PROJECT/*/*/*/*/Thumbs.db`; - $ret=`rm -f $BUILDROOT/$PROJECT/.cvsignore $BUILDROOT/$PROJECT/*/.cvsignore $BUILDROOT/$PROJECT/*/*/.cvsignore $BUILDROOT/$PROJECT/*/*/*/.cvsignore $BUILDROOT/$PROJECT/*/*/*/*/.cvsignore $BUILDROOT/$PROJECT/*/*/*/*/*/.cvsignore $BUILDROOT/$PROJECT/*/*/*/*/*/*/.cvsignore`; - $ret=`rm -f $BUILDROOT/$PROJECT/.gitignore $BUILDROOT/$PROJECT/*/.gitignore $BUILDROOT/$PROJECT/*/*/.gitignore $BUILDROOT/$PROJECT/*/*/*/.gitignore $BUILDROOT/$PROJECT/*/*/*/*/.gitignore $BUILDROOT/$PROJECT/*/*/*/*/*/.gitignore $BUILDROOT/$PROJECT/*/*/*/*/*/*/.gitignore`; + $ret = `rm -fr $BUILDROOT/$PROJECT/htdocs/public/test`; + $ret = `rm -fr $BUILDROOT/$PROJECT/test`; + $ret = +`rm -fr $BUILDROOT/$PROJECT/Thumbs.db $BUILDROOT/$PROJECT/*/Thumbs.db $BUILDROOT/$PROJECT/*/*/Thumbs.db $BUILDROOT/$PROJECT/*/*/*/Thumbs.db $BUILDROOT/$PROJECT/*/*/*/*/Thumbs.db`; + $ret = +`rm -f $BUILDROOT/$PROJECT/.cvsignore $BUILDROOT/$PROJECT/*/.cvsignore $BUILDROOT/$PROJECT/*/*/.cvsignore $BUILDROOT/$PROJECT/*/*/*/.cvsignore $BUILDROOT/$PROJECT/*/*/*/*/.cvsignore $BUILDROOT/$PROJECT/*/*/*/*/*/.cvsignore $BUILDROOT/$PROJECT/*/*/*/*/*/*/.cvsignore`; + $ret = +`rm -f $BUILDROOT/$PROJECT/.gitignore $BUILDROOT/$PROJECT/*/.gitignore $BUILDROOT/$PROJECT/*/*/.gitignore $BUILDROOT/$PROJECT/*/*/*/.gitignore $BUILDROOT/$PROJECT/*/*/*/*/.gitignore $BUILDROOT/$PROJECT/*/*/*/*/*/.gitignore $BUILDROOT/$PROJECT/*/*/*/*/*/*/.gitignore`; # Removed files installed by the awful composer - $ret=`rm -f $BUILDROOT/$PROJECT/htdocs/includes/geoip/sample*.*`; - $ret=`rm -f $BUILDROOT/$PROJECT/htdocs/includes/bin`; - $ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/includes/ckeditor/ckeditor/adapters`; # Keep this removal in case we embed libraries - $ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/includes/ckeditor/ckeditor/samples`; # Keep this removal in case we embed libraries - $ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/includes/ckeditor/_source`; # _source must be kept into tarball for official debian, not for the rest - $ret=`rm -f $BUILDROOT/$PROJECT/htdocs/includes/composer`; - $ret=`rm -f $BUILDROOT/$PROJECT/htdocs/includes/doctrine`; - $ret=`rm -f $BUILDROOT/$PROJECT/htdocs/includes/jquery/plugins/multiselect/MIT-LICENSE.txt`; - $ret=`rm -f $BUILDROOT/$PROJECT/htdocs/includes/jquery/plugins/select2/release.sh`; - $ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/includes/mike42/escpos-php/doc`; - $ret=`rm -f $BUILDROOT/$PROJECT/htdocs/includes/mike42/escpos-php/example`; - $ret=`rm -f $BUILDROOT/$PROJECT/htdocs/includes/mike42/escpos-php/test`; - $ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/includes/mobiledetect/mobiledetectlib/.gitmodules`; - $ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/includes/mobiledetect/mobiledetectlib/docs`; - $ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/includes/nnnick/chartjs/.github`; - $ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/includes/nnnick/chartjs/docs`; - $ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/includes/nnnick/chartjs/samples`; - $ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/includes/nnnick/chartjs/scripts`; - $ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/includes/nnnick/chartjs/src`; - $ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/includes/nnnick/chartjs/test`; - $ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/includes/nusoap/samples`; - $ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/includes/php-iban/docs`; - $ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/includes/sabre/sabre/*/tests`; - $ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/includes/stripe/tests`; - $ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/includes/stripe/LICENSE`; - $ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/includes/tecnickcom/tcpdf/fonts/dejavu-fonts-ttf-*`; - $ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/includes/tecnickcom/tcpdf/fonts/freefont-*`; - $ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/includes/tecnickcom/tcpdf/fonts/ae_fonts_*`; - $ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/includes/tecnickcom/tcpdf/fonts/utils`; - $ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/includes/tecnickcom/tcpdf/examples`; - $ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/includes/tecnickcom/tcpdf/tools`; - $ret=`rm -f $BUILDROOT/$PROJECT/htdocs/includes/vendor`; - $ret=`rm -f $BUILDROOT/$PROJECT/htdocs/includes/webmozart`; - $ret=`rm -f $BUILDROOT/$PROJECT/htdocs/includes/autoload.php`; + $ret = `rm -f $BUILDROOT/$PROJECT/htdocs/includes/geoip/sample*.*`; + $ret = `rm -f $BUILDROOT/$PROJECT/htdocs/includes/bin`; + $ret = +`rm -fr $BUILDROOT/$PROJECT/htdocs/includes/ckeditor/ckeditor/adapters` + ; # Keep this removal in case we embed libraries + $ret = + `rm -fr $BUILDROOT/$PROJECT/htdocs/includes/ckeditor/ckeditor/samples` + ; # Keep this removal in case we embed libraries + $ret = `rm -fr $BUILDROOT/$PROJECT/htdocs/includes/ckeditor/_source` + ; # _source must be kept into tarball for official debian, not for the rest + $ret = `rm -f $BUILDROOT/$PROJECT/htdocs/includes/composer`; + $ret = `rm -f $BUILDROOT/$PROJECT/htdocs/includes/doctrine`; + $ret = +`rm -f $BUILDROOT/$PROJECT/htdocs/includes/jquery/plugins/multiselect/MIT-LICENSE.txt`; + $ret = +`rm -f $BUILDROOT/$PROJECT/htdocs/includes/jquery/plugins/select2/release.sh`; + $ret = + `rm -fr $BUILDROOT/$PROJECT/htdocs/includes/mike42/escpos-php/doc`; + $ret = +`rm -f $BUILDROOT/$PROJECT/htdocs/includes/mike42/escpos-php/example`; + $ret = + `rm -f $BUILDROOT/$PROJECT/htdocs/includes/mike42/escpos-php/test`; + $ret = +`rm -fr $BUILDROOT/$PROJECT/htdocs/includes/mobiledetect/mobiledetectlib/.gitmodules`; + $ret = +`rm -fr $BUILDROOT/$PROJECT/htdocs/includes/mobiledetect/mobiledetectlib/docs`; + $ret = + `rm -fr $BUILDROOT/$PROJECT/htdocs/includes/nnnick/chartjs/.github`; + $ret = `rm -fr $BUILDROOT/$PROJECT/htdocs/includes/nnnick/chartjs/docs`; + $ret = + `rm -fr $BUILDROOT/$PROJECT/htdocs/includes/nnnick/chartjs/samples`; + $ret = + `rm -fr $BUILDROOT/$PROJECT/htdocs/includes/nnnick/chartjs/scripts`; + $ret = `rm -fr $BUILDROOT/$PROJECT/htdocs/includes/nnnick/chartjs/src`; + $ret = `rm -fr $BUILDROOT/$PROJECT/htdocs/includes/nnnick/chartjs/test`; + $ret = `rm -fr $BUILDROOT/$PROJECT/htdocs/includes/nusoap/samples`; + $ret = `rm -fr $BUILDROOT/$PROJECT/htdocs/includes/php-iban/docs`; + $ret = `rm -fr $BUILDROOT/$PROJECT/htdocs/includes/sabre/sabre/*/tests`; + $ret = `rm -fr $BUILDROOT/$PROJECT/htdocs/includes/stripe/tests`; + $ret = `rm -fr $BUILDROOT/$PROJECT/htdocs/includes/stripe/LICENSE`; + $ret = +`rm -fr $BUILDROOT/$PROJECT/htdocs/includes/tecnickcom/tcpdf/fonts/dejavu-fonts-ttf-*`; + $ret = +`rm -fr $BUILDROOT/$PROJECT/htdocs/includes/tecnickcom/tcpdf/fonts/freefont-*`; + $ret = +`rm -fr $BUILDROOT/$PROJECT/htdocs/includes/tecnickcom/tcpdf/fonts/ae_fonts_*`; + $ret = +`rm -fr $BUILDROOT/$PROJECT/htdocs/includes/tecnickcom/tcpdf/fonts/utils`; + $ret = +`rm -fr $BUILDROOT/$PROJECT/htdocs/includes/tecnickcom/tcpdf/examples`; + $ret = + `rm -fr $BUILDROOT/$PROJECT/htdocs/includes/tecnickcom/tcpdf/tools`; + $ret = `rm -f $BUILDROOT/$PROJECT/htdocs/includes/vendor`; + $ret = `rm -f $BUILDROOT/$PROJECT/htdocs/includes/webmozart`; + $ret = `rm -f $BUILDROOT/$PROJECT/htdocs/includes/autoload.php`; - $ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/includes/sabre/sabre/bin`; - $ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/includes/sabre/sabre/*/bin`; - $ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/includes/sabre/sabre/*/*/bin`; - $ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/includes/sabre/sabre/*/*/*/bin`; - $ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/includes/sabre/sabre/*/*/*/*/bin`; + $ret = `rm -fr $BUILDROOT/$PROJECT/htdocs/includes/sabre/sabre/bin`; + $ret = `rm -fr $BUILDROOT/$PROJECT/htdocs/includes/sabre/sabre/*/bin`; + $ret = `rm -fr $BUILDROOT/$PROJECT/htdocs/includes/sabre/sabre/*/*/bin`; + $ret = + `rm -fr $BUILDROOT/$PROJECT/htdocs/includes/sabre/sabre/*/*/*/bin`; + $ret = + `rm -fr $BUILDROOT/$PROJECT/htdocs/includes/sabre/sabre/*/*/*/*/bin`; } # Build package for each target #------------------------------ - foreach my $target (sort keys %CHOOSEDTARGET) - { - if ($CHOOSEDTARGET{$target} < 0) { next; } - if ($target eq '-CHKSUM') { next; } + foreach my $target ( sort keys %CHOOSEDTARGET ) { + if ( $CHOOSEDTARGET{$target} < 0 ) { next; } + if ( $target eq '-CHKSUM' ) { next; } print "\nBuild package for target $target\n"; - if ($target eq 'SNAPSHOT') - { - $NEWDESTI=$DESTI; + if ( $target eq 'SNAPSHOT' ) { + $NEWDESTI = $DESTI; print "Remove target $FILENAMESNAPSHOT.tgz...\n"; unlink("$NEWDESTI/$FILENAMESNAPSHOT.tgz"); #rmdir "$BUILDROOT/$FILENAMESNAPSHOT"; - $ret=`rm -fr $BUILDROOT/$FILENAMESNAPSHOT`; + $ret = `rm -fr $BUILDROOT/$FILENAMESNAPSHOT`; print "Copy $BUILDROOT/$PROJECT to $BUILDROOT/$FILENAMESNAPSHOT\n"; - $cmd="cp -pr \"$BUILDROOT/$PROJECT\" \"$BUILDROOT/$FILENAMESNAPSHOT\""; - $ret=`$cmd`; + $cmd = + "cp -pr \"$BUILDROOT/$PROJECT\" \"$BUILDROOT/$FILENAMESNAPSHOT\""; + $ret = `$cmd`; print "Compress $BUILDROOT into $FILENAMESNAPSHOT.tgz...\n"; - $cmd="tar --exclude doli*.tgz --exclude doli*.deb --exclude doli*.exe --exclude doli*.xz --exclude doli*.zip --exclude doli*.rpm --exclude .cache --exclude .settings --exclude conf.php --exclude conf.php.mysql --exclude conf.php.old --exclude conf.php.postgres --directory \"$BUILDROOT\" --mode=go-w --group=500 --owner=500 -czvf \"$FILENAMESNAPSHOT.tgz\" $FILENAMESNAPSHOT"; - print $cmd."\n"; - $ret=`$cmd`; + $cmd = +"tar --exclude doli*.tgz --exclude doli*.deb --exclude doli*.exe --exclude doli*.xz --exclude doli*.zip --exclude doli*.rpm --exclude .cache --exclude .settings --exclude conf.php --exclude conf.php.mysql --exclude conf.php.old --exclude conf.php.postgres --directory \"$BUILDROOT\" --mode=go-w --group=500 --owner=500 -czvf \"$FILENAMESNAPSHOT.tgz\" $FILENAMESNAPSHOT"; + print $cmd. "\n"; + $ret = `$cmd`; # Move to final dir - print "Move $FILENAMESNAPSHOT.tgz to $NEWDESTI/$FILENAMESNAPSHOT.tgz\n"; - $ret=`mv "$FILENAMESNAPSHOT.tgz" "$NEWDESTI/$FILENAMESNAPSHOT.tgz"`; + print + "Move $FILENAMESNAPSHOT.tgz to $NEWDESTI/$FILENAMESNAPSHOT.tgz\n"; + $ret = + `mv "$FILENAMESNAPSHOT.tgz" "$NEWDESTI/$FILENAMESNAPSHOT.tgz"`; next; } - if ($target eq 'TGZ') - { - $NEWDESTI=$DESTI; - if ($NEWDESTI =~ /stable/) - { - mkdir($DESTI.'/standard'); - if (-d $DESTI.'/standard') { $NEWDESTI=$DESTI.'/standard'; } + if ( $target eq 'TGZ' ) { + $NEWDESTI = $DESTI; + if ( $NEWDESTI =~ /stable/ ) { + mkdir( $DESTI . '/standard' ); + if ( -d $DESTI . '/standard' ) { + $NEWDESTI = $DESTI . '/standard'; + } } print "Remove target $FILENAMETGZ.tgz...\n"; unlink("$NEWDESTI/$FILENAMETGZ.tgz"); #rmdir "$BUILDROOT/$FILENAMETGZ"; - $ret=`rm -fr $BUILDROOT/$FILENAMETGZ`; + $ret = `rm -fr $BUILDROOT/$FILENAMETGZ`; print "Copy $BUILDROOT/$PROJECT/ to $BUILDROOT/$FILENAMETGZ\n"; - $cmd="cp -pr \"$BUILDROOT/$PROJECT/\" \"$BUILDROOT/$FILENAMETGZ\""; - $ret=`$cmd`; + $cmd = + "cp -pr \"$BUILDROOT/$PROJECT/\" \"$BUILDROOT/$FILENAMETGZ\""; + $ret = `$cmd`; - $ret=`rm -fr $BUILDROOT/$FILENAMETGZ/dev/build/exe`; - $ret=`rm -fr $BUILDROOT/$FILENAMETGZ/htdocs/includes/ckeditor/_source`; # We can't remove it with exclude file, we need it for some tarball packages + $ret = `rm -fr $BUILDROOT/$FILENAMETGZ/dev/build/exe`; + $ret = + `rm -fr $BUILDROOT/$FILENAMETGZ/htdocs/includes/ckeditor/_source` + ; # We can't remove it with exclude file, we need it for some tarball packages print "Compress $FILENAMETGZ into $FILENAMETGZ.tgz...\n"; - $cmd="tar --exclude-vcs --exclude-from \"$BUILDROOT/$PROJECT/dev/build/tgz/tar_exclude.txt\" --directory \"$BUILDROOT\" --mode=go-w --group=500 --owner=500 -czvf \"$BUILDROOT/$FILENAMETGZ.tgz\" $FILENAMETGZ"; + $cmd = +"tar --exclude-vcs --exclude-from \"$BUILDROOT/$PROJECT/dev/build/tgz/tar_exclude.txt\" --directory \"$BUILDROOT\" --mode=go-w --group=500 --owner=500 -czvf \"$BUILDROOT/$FILENAMETGZ.tgz\" $FILENAMETGZ"; print "$cmd\n"; - $ret=`$cmd`; + $ret = `$cmd`; # Move to final dir - print "Move $BUILDROOT/$FILENAMETGZ.tgz to $NEWDESTI/$FILENAMETGZ.tgz\n"; - $ret=`mv "$BUILDROOT/$FILENAMETGZ.tgz" "$NEWDESTI/$FILENAMETGZ.tgz"`; + print +"Move $BUILDROOT/$FILENAMETGZ.tgz to $NEWDESTI/$FILENAMETGZ.tgz\n"; + $ret = + `mv "$BUILDROOT/$FILENAMETGZ.tgz" "$NEWDESTI/$FILENAMETGZ.tgz"`; next; } - if ($target eq 'XZ') - { - $NEWDESTI=$DESTI; - if ($NEWDESTI =~ /stable/) - { - mkdir($DESTI.'/standard'); - if (-d $DESTI.'/standard') { $NEWDESTI=$DESTI.'/standard'; } + if ( $target eq 'XZ' ) { + $NEWDESTI = $DESTI; + if ( $NEWDESTI =~ /stable/ ) { + mkdir( $DESTI . '/standard' ); + if ( -d $DESTI . '/standard' ) { + $NEWDESTI = $DESTI . '/standard'; + } } print "Remove target $FILENAMEXZ.xz...\n"; unlink("$NEWDESTI/$FILENAMEXZ.xz"); #rmdir "$BUILDROOT/$FILENAMEXZ"; - $ret=`rm -fr $BUILDROOT/$FILENAMEXZ`; + $ret = `rm -fr $BUILDROOT/$FILENAMEXZ`; print "Copy $BUILDROOT/$PROJECT to $BUILDROOT/$FILENAMEXZ\n"; - $cmd="cp -pr \"$BUILDROOT/$PROJECT\" \"$BUILDROOT/$FILENAMEXZ\""; - $ret=`$cmd`; + $cmd = "cp -pr \"$BUILDROOT/$PROJECT\" \"$BUILDROOT/$FILENAMEXZ\""; + $ret = `$cmd`; - $ret=`rm -fr $BUILDROOT/$FILENAMEXZ/dev/build/exe`; - $ret=`rm -fr $BUILDROOT/$FILENAMEXZ/htdocs/includes/ckeditor/_source`; # We can't remove it with exclude file, we need it for some tarball packages + $ret = `rm -fr $BUILDROOT/$FILENAMEXZ/dev/build/exe`; + $ret = + `rm -fr $BUILDROOT/$FILENAMEXZ/htdocs/includes/ckeditor/_source` + ; # We can't remove it with exclude file, we need it for some tarball packages print "Compress $FILENAMEXZ into $FILENAMEXZ.xz...\n"; print "Go to directory $BUILDROOT\n"; - $olddir=getcwd(); + $olddir = getcwd(); chdir("$BUILDROOT"); - $cmd= "xz -9 -r $BUILDROOT/$FILENAMEXZ.xz \*"; - print $cmd."\n"; - $ret= `$cmd`; + $cmd = "xz -9 -r $BUILDROOT/$FILENAMEXZ.xz \*"; + print $cmd. "\n"; + $ret = `$cmd`; chdir("$olddir"); # Move to final dir print "Move $FILENAMEXZ.xz to $NEWDESTI/$FILENAMEXZ.xz\n"; - $ret=`mv "$BUILDROOT/$FILENAMEXZ.xz" "$NEWDESTI/$FILENAMEXZ.xz"`; + $ret = `mv "$BUILDROOT/$FILENAMEXZ.xz" "$NEWDESTI/$FILENAMEXZ.xz"`; next; } - if ($target eq 'ZIP') - { - $NEWDESTI=$DESTI; - if ($NEWDESTI =~ /stable/) - { - mkdir($DESTI.'/standard'); - if (-d $DESTI.'/standard') { $NEWDESTI=$DESTI.'/standard'; } + if ( $target eq 'ZIP' ) { + $NEWDESTI = $DESTI; + if ( $NEWDESTI =~ /stable/ ) { + mkdir( $DESTI . '/standard' ); + if ( -d $DESTI . '/standard' ) { + $NEWDESTI = $DESTI . '/standard'; + } } print "Remove target $FILENAMEZIP.zip...\n"; unlink("$NEWDESTI/$FILENAMEZIP.zip"); #rmdir "$BUILDROOT/$FILENAMEZIP"; - $ret=`rm -fr $BUILDROOT/$FILENAMEZIP`; + $ret = `rm -fr $BUILDROOT/$FILENAMEZIP`; print "Copy $BUILDROOT/$PROJECT to $BUILDROOT/$FILENAMEZIP\n"; - $cmd="cp -pr \"$BUILDROOT/$PROJECT\" \"$BUILDROOT/$FILENAMEZIP\""; - $ret=`$cmd`; + $cmd = "cp -pr \"$BUILDROOT/$PROJECT\" \"$BUILDROOT/$FILENAMEZIP\""; + $ret = `$cmd`; - $ret=`rm -fr $BUILDROOT/$FILENAMEZIP/dev/build/exe`; - $ret=`rm -fr $BUILDROOT/$FILENAMEZIP/htdocs/includes/ckeditor/_source`; # We can't remove it with exclude file, we need it for some tarball packages + $ret = `rm -fr $BUILDROOT/$FILENAMEZIP/dev/build/exe`; + $ret = + `rm -fr $BUILDROOT/$FILENAMEZIP/htdocs/includes/ckeditor/_source` + ; # We can't remove it with exclude file, we need it for some tarball packages print "Compress $FILENAMEZIP into $FILENAMEZIP.zip...\n"; print "Go to directory $BUILDROOT\n"; - $olddir=getcwd(); + $olddir = getcwd(); chdir("$BUILDROOT"); - $cmd= "7z a -r -tzip -xr\@\"$BUILDROOT\/$FILENAMEZIP\/dev\/build\/zip\/zip_exclude.txt\" -mx $BUILDROOT/$FILENAMEZIP.zip $FILENAMEZIP\/*"; - print $cmd."\n"; - $ret= `$cmd`; + $cmd = +"7z a -r -tzip -xr\@\"$BUILDROOT\/$FILENAMEZIP\/dev\/build\/zip\/zip_exclude.txt\" -mx $BUILDROOT/$FILENAMEZIP.zip $FILENAMEZIP\/*"; + print $cmd. "\n"; + $ret = `$cmd`; chdir("$olddir"); # Move to final dir print "Move $FILENAMEZIP.zip to $NEWDESTI/$FILENAMEZIP.zip\n"; - $ret=`mv "$BUILDROOT/$FILENAMEZIP.zip" "$NEWDESTI/$FILENAMEZIP.zip"`; + $ret = + `mv "$BUILDROOT/$FILENAMEZIP.zip" "$NEWDESTI/$FILENAMEZIP.zip"`; next; } - if ($target =~ /RPM/) # Linux only + if ( $target =~ /RPM/ ) # Linux only { - $NEWDESTI=$DESTI; - $subdir="package_rpm_generic"; - if ($target =~ /FEDO/i) { $subdir="package_rpm_redhat-fedora"; } - if ($target =~ /MAND/i) { $subdir="package_rpm_mandriva"; } - if ($target =~ /OPEN/i) { $subdir="package_rpm_opensuse"; } - if ($NEWDESTI =~ /stable/) - { - mkdir($DESTI.'/'.$subdir); - if (-d $DESTI.'/'.$subdir) { $NEWDESTI=$DESTI.'/'.$subdir; } + $NEWDESTI = $DESTI; + $subdir = "package_rpm_generic"; + if ( $target =~ /FEDO/i ) { $subdir = "package_rpm_redhat-fedora"; } + if ( $target =~ /MAND/i ) { $subdir = "package_rpm_mandriva"; } + if ( $target =~ /OPEN/i ) { $subdir = "package_rpm_opensuse"; } + if ( $NEWDESTI =~ /stable/ ) { + mkdir( $DESTI . '/' . $subdir ); + if ( -d $DESTI . '/' . $subdir ) { + $NEWDESTI = $DESTI . '/' . $subdir; + } } - if ($RPMDIR eq "") { $RPMDIR=$ENV{'HOME'}."/rpmbuild"; } + if ( $RPMDIR eq "" ) { $RPMDIR = $ENV{'HOME'} . "/rpmbuild"; } print "Version is $MAJOR.$MINOR.$REL1-$RPMSUBVERSION\n"; - print "Remove target ".$FILENAMERPM."...\n"; - unlink("$NEWDESTI/".$FILENAMERPM); - print "Remove target ".$FILENAMERPMSRC."...\n"; - unlink("$NEWDESTI/".$FILENAMERPMSRC); + print "Remove target " . $FILENAMERPM . "...\n"; + unlink( "$NEWDESTI/" . $FILENAMERPM ); + print "Remove target " . $FILENAMERPMSRC . "...\n"; + unlink( "$NEWDESTI/" . $FILENAMERPMSRC ); print "Create directory $BUILDROOT/$FILENAMETGZ2\n"; - $ret=`rm -fr $BUILDROOT/$FILENAMETGZ2`; + $ret = `rm -fr $BUILDROOT/$FILENAMETGZ2`; print "Copy $BUILDROOT/$PROJECT to $BUILDROOT/$FILENAMETGZ2\n"; - $cmd="cp -pr '$BUILDROOT/$PROJECT' '$BUILDROOT/$FILENAMETGZ2'"; - $ret=`$cmd`; + $cmd = "cp -pr '$BUILDROOT/$PROJECT' '$BUILDROOT/$FILENAMETGZ2'"; + $ret = `$cmd`; - # Removed files we don't need (already removed before) - #$ret=`rm -fr $BUILDROOT/$FILENAMETGZ2/htdocs/includes/ckeditor/_source`; + # Removed files we don't need (already removed before) + #$ret=`rm -fr $BUILDROOT/$FILENAMETGZ2/htdocs/includes/ckeditor/_source`; print "Set permissions on files/dir\n"; - $ret=`chmod -R 755 $BUILDROOT/$FILENAMETGZ2`; - $cmd="find $BUILDROOT/$FILENAMETGZ2 -type f -exec chmod 644 {} \\; "; - $ret=`$cmd`; + $ret = `chmod -R 755 $BUILDROOT/$FILENAMETGZ2`; + $cmd = + "find $BUILDROOT/$FILENAMETGZ2 -type f -exec chmod 644 {} \\; "; + $ret = `$cmd`; # Build tgz print "Compress $FILENAMETGZ2 into $FILENAMETGZ2.tgz...\n"; - $ret=`tar --exclude-from "$SOURCE/dev/build/tgz/tar_exclude.txt" --directory "$BUILDROOT" -czvf "$BUILDROOT/$FILENAMETGZ2.tgz" $FILENAMETGZ2`; + $ret = +`tar --exclude-from "$SOURCE/dev/build/tgz/tar_exclude.txt" --directory "$BUILDROOT" -czvf "$BUILDROOT/$FILENAMETGZ2.tgz" $FILENAMETGZ2`; - print "Move $BUILDROOT/$FILENAMETGZ2.tgz to $RPMDIR/SOURCES/$FILENAMETGZ2.tgz\n"; - $cmd="mv $BUILDROOT/$FILENAMETGZ2.tgz $RPMDIR/SOURCES/$FILENAMETGZ2.tgz"; - $ret=`$cmd`; + print +"Move $BUILDROOT/$FILENAMETGZ2.tgz to $RPMDIR/SOURCES/$FILENAMETGZ2.tgz\n"; + $cmd = +"mv $BUILDROOT/$FILENAMETGZ2.tgz $RPMDIR/SOURCES/$FILENAMETGZ2.tgz"; + $ret = `$cmd`; - $BUILDFIC="${FILENAME}.spec"; - $BUILDFICSRC="${FILENAME}_generic.spec"; - if ($target =~ /FEDO/i) { $BUILDFICSRC="${FILENAME}_fedora.spec"; } - if ($target =~ /MAND/i) { $BUILDFICSRC="${FILENAME}_mandriva.spec"; } - if ($target =~ /OPEN/i) { $BUILDFICSRC="${FILENAME}_opensuse.spec"; } + $BUILDFIC = "${FILENAME}.spec"; + $BUILDFICSRC = "${FILENAME}_generic.spec"; + if ( $target =~ /FEDO/i ) { + $BUILDFICSRC = "${FILENAME}_fedora.spec"; + } + if ( $target =~ /MAND/i ) { + $BUILDFICSRC = "${FILENAME}_mandriva.spec"; + } + if ( $target =~ /OPEN/i ) { + $BUILDFICSRC = "${FILENAME}_opensuse.spec"; + } use Date::Language; - $lang=Date::Language->new('English'); - $datestring = $lang->time2str("%a %b %e %Y", time); - $changelogstring="* ".$datestring." Laurent Destailleur (eldy) $MAJOR.$MINOR.$REL1-$RPMSUBVERSION\n- Upstream release\n"; + $lang = Date::Language->new('English'); + $datestring = $lang->time2str( "%a %b %e %Y", time ); + $changelogstring = "* " + . $datestring + . " Laurent Destailleur (eldy) $MAJOR.$MINOR.$REL1-$RPMSUBVERSION\n- Upstream release\n"; - print "Generate file $BUILDROOT/$BUILDFIC from $SOURCE/dev/build/rpm/${BUILDFICSRC}\n"; - open (SPECFROM,"<$SOURCE/dev/build/rpm/${BUILDFICSRC}") || die "Error"; - open (SPECTO,">$BUILDROOT/$BUILDFIC") || die "Error"; - while () { + print +"Generate file $BUILDROOT/$BUILDFIC from $SOURCE/dev/build/rpm/${BUILDFICSRC}\n"; + open( my $SPECFROM, "<", "$SOURCE/dev/build/rpm/${BUILDFICSRC}" ) + or die "Error"; + open( my $SPECTO, ">", "$BUILDROOT/$BUILDFIC" ) or die "Error"; + while (<$SPECFROM>) { $_ =~ s/__FILENAMETGZ__/$FILENAMETGZ/; $_ =~ s/__VERSION__/$MAJOR.$MINOR.$REL1/; $_ =~ s/__RELEASE__/$RPMSUBVERSION/; - $_ =~ s/__CHANGELOGSTRING__/$changelogstring/; - print SPECTO $_; + $_ =~ s/__CHANGELOGSTRING__/$changelogstring/; + print $SPECTO $_; } - close SPECFROM; - close SPECTO; + close $SPECFROM; + close $SPECTO; print "Copy patch file to $RPMDIR/SOURCES\n"; - $ret=`cp "$SOURCE/dev/build/rpm/dolibarr-forrpm.patch" "$RPMDIR/SOURCES"`; - $ret=`chmod 644 $RPMDIR/SOURCES/dolibarr-forrpm.patch`; + $ret = +`cp "$SOURCE/dev/build/rpm/dolibarr-forrpm.patch" "$RPMDIR/SOURCES"`; + $ret = `chmod 644 $RPMDIR/SOURCES/dolibarr-forrpm.patch`; + + print +"Launch RPM build (rpmbuild --clean -ba $BUILDROOT/${BUILDFIC})\n"; - print "Launch RPM build (rpmbuild --clean -ba $BUILDROOT/${BUILDFIC})\n"; #$ret=`rpmbuild -vvvv --clean -ba $BUILDROOT/${BUILDFIC}`; - $ret=`rpmbuild --clean -ba $BUILDROOT/${BUILDFIC}`; + $ret = `rpmbuild --clean -ba $BUILDROOT/${BUILDFIC}`; # Move to final dir - print "Move $RPMDIR/RPMS/".$ARCH."/".$FILENAMETGZ2."-".$RPMSUBVERSION."*.".$ARCH.".rpm into $NEWDESTI/".$FILENAMETGZ2."-".$RPMSUBVERSION."*.".$ARCH.".rpm\n"; - $cmd="mv $RPMDIR/RPMS/".$ARCH."/".$FILENAMETGZ2."-".$RPMSUBVERSION."*.".$ARCH.".rpm \"$NEWDESTI/\""; - $ret=`$cmd`; - print "Move $RPMDIR/SRPMS/".$FILENAMETGZ2."-".$RPMSUBVERSION."*.src.rpm into $NEWDESTI/".$FILENAMETGZ2."-".$RPMSUBVERSION."*.src.rpm\n"; - $cmd="mv $RPMDIR/SRPMS/".$FILENAMETGZ2."-".$RPMSUBVERSION."*.src.rpm \"$NEWDESTI/\""; - $ret=`$cmd`; - print "Move $RPMDIR/SOURCES/".$FILENAMETGZ2.".tgz into $NEWDESTI/".$FILENAMETGZ2.".tgz\n"; - $cmd="mv \"$RPMDIR/SOURCES/".$FILENAMETGZ2.".tgz\" \"$NEWDESTI/".$FILENAMETGZ2.".tgz\""; + print "Move $RPMDIR/RPMS/" + . $ARCH . "/" + . $FILENAMETGZ2 . "-" + . $RPMSUBVERSION . "*." + . $ARCH + . ".rpm into $NEWDESTI/" + . $FILENAMETGZ2 . "-" + . $RPMSUBVERSION . "*." + . $ARCH + . ".rpm\n"; + $cmd = + "mv $RPMDIR/RPMS/" + . $ARCH . "/" + . $FILENAMETGZ2 . "-" + . $RPMSUBVERSION . "*." + . $ARCH + . ".rpm \"$NEWDESTI/\""; + $ret = `$cmd`; + print "Move $RPMDIR/SRPMS/" + . $FILENAMETGZ2 . "-" + . $RPMSUBVERSION + . "*.src.rpm into $NEWDESTI/" + . $FILENAMETGZ2 . "-" + . $RPMSUBVERSION + . "*.src.rpm\n"; + $cmd = + "mv $RPMDIR/SRPMS/" + . $FILENAMETGZ2 . "-" + . $RPMSUBVERSION + . "*.src.rpm \"$NEWDESTI/\""; + $ret = `$cmd`; + print "Move $RPMDIR/SOURCES/" + . $FILENAMETGZ2 + . ".tgz into $NEWDESTI/" + . $FILENAMETGZ2 + . ".tgz\n"; + $cmd = + "mv \"$RPMDIR/SOURCES/" + . $FILENAMETGZ2 + . ".tgz\" \"$NEWDESTI/" + . $FILENAMETGZ2 + . ".tgz\""; + #$ret=`$cmd`; next; } - if ($target eq 'DEB') - { - $NEWDESTI=$DESTI; - if ($NEWDESTI =~ /stable/) - { - mkdir($DESTI.'/package_debian-ubuntu'); - if (-d $DESTI.'/package_debian-ubuntu') { $NEWDESTI=$DESTI.'/package_debian-ubuntu'; } + if ( $target eq 'DEB' ) { + $NEWDESTI = $DESTI; + if ( $NEWDESTI =~ /stable/ ) { + mkdir( $DESTI . '/package_debian-ubuntu' ); + if ( -d $DESTI . '/package_debian-ubuntu' ) { + $NEWDESTI = $DESTI . '/package_debian-ubuntu'; + } } - $olddir=getcwd(); + $olddir = getcwd(); print "Remove target ${FILENAMEDEB}_all.deb...\n"; unlink("$NEWDESTI/${FILENAMEDEB}_all.deb"); @@ -955,365 +1189,496 @@ if ($nboftargetok) { print "Remove target ${FILENAMEDEBNATIVE}.orig.tar.gz...\n"; unlink("$NEWDESTI/${FILENAMEDEBNATIVE}.orig.tar.gz"); - $ret=`rm -fr $BUILDROOT/$PROJECT.tmp`; - $ret=`rm -fr $BUILDROOT/$PROJECT-$MAJOR.$MINOR.$build`; + $ret = `rm -fr $BUILDROOT/$PROJECT.tmp`; + $ret = `rm -fr $BUILDROOT/$PROJECT-$MAJOR.$MINOR.$build`; print "Copy $BUILDROOT/$PROJECT to $BUILDROOT/$PROJECT.tmp\n"; - $cmd="cp -pr \"$BUILDROOT/$PROJECT\" \"$BUILDROOT/$PROJECT.tmp\""; - $ret=`$cmd`; - $cmd="cp -pr \"$BUILDROOT/$PROJECT/dev/build/debian/apache/.htaccess\" \"$BUILDROOT/$PROJECT.tmp/dev/build/debian/apache/.htaccess\""; - $ret=`$cmd`; + $cmd = "cp -pr \"$BUILDROOT/$PROJECT\" \"$BUILDROOT/$PROJECT.tmp\""; + $ret = `$cmd`; + $cmd = +"cp -pr \"$BUILDROOT/$PROJECT/dev/build/debian/apache/.htaccess\" \"$BUILDROOT/$PROJECT.tmp/dev/build/debian/apache/.htaccess\""; + $ret = `$cmd`; print "Remove other files\n"; - $ret=`rm -f $BUILDROOT/$PROJECT.tmp/README-FR.md`; - $ret=`rm -f $BUILDROOT/$PROJECT.tmp/dev/build/README`; - $ret=`rm -f $BUILDROOT/$PROJECT.tmp/dev/build/README-FR`; - $ret=`rm -fr $BUILDROOT/$PROJECT.tmp/dev/build/aps`; - $ret=`rm -fr $BUILDROOT/$PROJECT.tmp/dev/build/dmg`; - $ret=`rm -f $BUILDROOT/$PROJECT.tmp/dev/build/pad/README`; - $ret=`rm -f $BUILDROOT/$PROJECT.tmp/dev/build/tgz/README`; - #$ret=`rm -fr $BUILDROOT/$PROJECT.tmp/dev/build/debian`; - $ret=`rm -fr $BUILDROOT/$PROJECT.tmp/dev/build/debian/po`; - $ret=`rm -fr $BUILDROOT/$PROJECT.tmp/dev/build/debian/source`; - $ret=`rm -f $BUILDROOT/$PROJECT.tmp/dev/build/debian/changelog`; - $ret=`rm -f $BUILDROOT/$PROJECT.tmp/dev/build/debian/compat`; - $ret=`rm -f $BUILDROOT/$PROJECT.tmp/dev/build/debian/control*`; - $ret=`rm -f $BUILDROOT/$PROJECT.tmp/dev/build/debian/copyright`; - $ret=`rm -f $BUILDROOT/$PROJECT.tmp/dev/build/debian/dolibarr.config`; - $ret=`rm -f $BUILDROOT/$PROJECT.tmp/dev/build/debian/dolibarr.desktop`; - $ret=`rm -f $BUILDROOT/$PROJECT.tmp/dev/build/debian/dolibarr.docs`; - $ret=`rm -f $BUILDROOT/$PROJECT.tmp/dev/build/debian/dolibarr.install`; - $ret=`rm -f $BUILDROOT/$PROJECT.tmp/dev/build/debian/dolibarr.lintian-overrides`; - $ret=`rm -f $BUILDROOT/$PROJECT.tmp/dev/build/debian/dolibarr.postrm`; - $ret=`rm -f $BUILDROOT/$PROJECT.tmp/dev/build/debian/dolibarr.postinst`; - $ret=`rm -f $BUILDROOT/$PROJECT.tmp/dev/build/debian/dolibarr.templates`; - $ret=`rm -f $BUILDROOT/$PROJECT.tmp/dev/build/debian/dolibarr.templates.futur`; - $ret=`rm -f $BUILDROOT/$PROJECT.tmp/dev/build/debian/rules`; - $ret=`rm -f $BUILDROOT/$PROJECT.tmp/dev/build/debian/README.Debian`; - $ret=`rm -f $BUILDROOT/$PROJECT.tmp/dev/build/debian/README.howto`; - $ret=`rm -f $BUILDROOT/$PROJECT.tmp/dev/build/debian/watch`; - $ret=`rm -fr $BUILDROOT/$PROJECT.tmp/dev/build/doap`; - $ret=`rm -fr $BUILDROOT/$PROJECT.tmp/dev/build/exe`; - $ret=`rm -fr $BUILDROOT/$PROJECT.tmp/dev/build/launchpad`; - $ret=`rm -fr $BUILDROOT/$PROJECT.tmp/dev/build/live`; - $ret=`rm -fr $BUILDROOT/$PROJECT.tmp/dev/build/patch`; - $ret=`rm -fr $BUILDROOT/$PROJECT.tmp/dev/build/perl`; - $ret=`rm -fr $BUILDROOT/$PROJECT.tmp/dev/build/rpm`; - $ret=`rm -fr $BUILDROOT/$PROJECT.tmp/dev/build/zip`; - # Removed duplicate license files - $ret=`rm -fr $BUILDROOT/$PROJECT.tmp/htdocs/includes/ckeditor/ckeditor/_source/LICENSE.md`; - $ret=`rm -fr $BUILDROOT/$PROJECT.tmp/htdocs/includes/ckeditor/ckeditor/_source/plugins/scayt/LICENSE.md`; - $ret=`rm -fr $BUILDROOT/$PROJECT.tmp/htdocs/includes/ckeditor/ckeditor/_source/plugins/wsc/LICENSE.md`; - $ret=`rm -fr $BUILDROOT/$PROJECT.tmp/htdocs/includes/ckeditor/ckeditor/LICENSE.md`; - $ret=`rm -fr $BUILDROOT/$PROJECT.tmp/htdocs/includes/ckeditor/ckeditor/plugins/scayt/LICENSE.md`; - $ret=`rm -fr $BUILDROOT/$PROJECT.tmp/htdocs/includes/ckeditor/ckeditor/plugins/wsc/LICENSE.md`; - $ret=`rm -fr $BUILDROOT/$PROJECT.tmp/htdocs/includes/php-iban/LICENSE`; - $ret=`rm -fr $BUILDROOT/$PROJECT.tmp/htdocs/includes/jquery/plugins/flot/LICENSE.txt`; - $ret=`rm -fr $BUILDROOT/$PROJECT.tmp/htdocs/includes/jquery/plugins/datatables/extensions/ColReorder/License.txt`; - $ret=`rm -fr $BUILDROOT/$PROJECT.tmp/htdocs/includes/jquery/plugins/datatables/extensions/ColVis/License.txt`; - $ret=`rm -fr $BUILDROOT/$PROJECT.tmp/htdocs/includes/jquery/plugins/datatables/extensions/FixedColumns/License.txt`; - $ret=`rm -fr $BUILDROOT/$PROJECT.tmp/htdocs/includes/jquery/plugins/datatables/extensions/Responsive/License.txt`; - $ret=`rm -fr $BUILDROOT/$PROJECT.tmp/htdocs/includes/jquery/plugins/datatables/license.txt`; - $ret=`rm -fr $BUILDROOT/$PROJECT.tmp/htdocs/includes/jquery/plugins/select2/LICENSE`; - $ret=`rm -fr $BUILDROOT/$PROJECT.tmp/htdocs/includes/mike42/escpos-php/LICENSE.md`; - $ret=`rm -fr $BUILDROOT/$PROJECT.tmp/htdocs/includes/mobiledetect/mobiledetectlib/LICENSE.txt`; + $ret = `rm -f $BUILDROOT/$PROJECT.tmp/README-FR.md`; + $ret = `rm -f $BUILDROOT/$PROJECT.tmp/dev/build/README`; + $ret = `rm -f $BUILDROOT/$PROJECT.tmp/dev/build/README-FR`; + $ret = `rm -fr $BUILDROOT/$PROJECT.tmp/dev/build/aps`; + $ret = `rm -fr $BUILDROOT/$PROJECT.tmp/dev/build/dmg`; + $ret = `rm -f $BUILDROOT/$PROJECT.tmp/dev/build/pad/README`; + $ret = `rm -f $BUILDROOT/$PROJECT.tmp/dev/build/tgz/README`; - # Removed files we don't need (already removed) - #$ret=`rm -fr $BUILDROOT/$PROJECT.tmp/htdocs/includes/ckeditor/ckeditor/_source`; - $ret=`rm -fr $BUILDROOT/$PROJECT.tmp/.codeclimate.yml`; - $ret=`rm -fr $BUILDROOT/$PROJECT.tmp/.pre-commit-config.yaml`; - $ret=`rm -fr $BUILDROOT/$PROJECT.tmp/.vscode`; - $ret=`find $BUILDROOT/$PROJECT.tmp/ -type f -name '.editorconfig' -exec rm {} \\;`; - $ret=`find $BUILDROOT/$PROJECT.tmp/ -type f -name '.travis.yml' -exec rm {} \\;`; + #$ret=`rm -fr $BUILDROOT/$PROJECT.tmp/dev/build/debian`; + $ret = `rm -fr $BUILDROOT/$PROJECT.tmp/dev/build/debian/po`; + $ret = `rm -fr $BUILDROOT/$PROJECT.tmp/dev/build/debian/source`; + $ret = `rm -f $BUILDROOT/$PROJECT.tmp/dev/build/debian/changelog`; + $ret = `rm -f $BUILDROOT/$PROJECT.tmp/dev/build/debian/compat`; + $ret = `rm -f $BUILDROOT/$PROJECT.tmp/dev/build/debian/control*`; + $ret = `rm -f $BUILDROOT/$PROJECT.tmp/dev/build/debian/copyright`; + $ret = + `rm -f $BUILDROOT/$PROJECT.tmp/dev/build/debian/dolibarr.config`; + $ret = +`rm -f $BUILDROOT/$PROJECT.tmp/dev/build/debian/dolibarr.desktop`; + $ret = + `rm -f $BUILDROOT/$PROJECT.tmp/dev/build/debian/dolibarr.docs`; + $ret = +`rm -f $BUILDROOT/$PROJECT.tmp/dev/build/debian/dolibarr.install`; + $ret = +`rm -f $BUILDROOT/$PROJECT.tmp/dev/build/debian/dolibarr.lintian-overrides`; + $ret = + `rm -f $BUILDROOT/$PROJECT.tmp/dev/build/debian/dolibarr.postrm`; + $ret = +`rm -f $BUILDROOT/$PROJECT.tmp/dev/build/debian/dolibarr.postinst`; + $ret = +`rm -f $BUILDROOT/$PROJECT.tmp/dev/build/debian/dolibarr.templates`; + $ret = +`rm -f $BUILDROOT/$PROJECT.tmp/dev/build/debian/dolibarr.templates.futur`; + $ret = `rm -f $BUILDROOT/$PROJECT.tmp/dev/build/debian/rules`; + $ret = + `rm -f $BUILDROOT/$PROJECT.tmp/dev/build/debian/README.Debian`; + $ret = + `rm -f $BUILDROOT/$PROJECT.tmp/dev/build/debian/README.howto`; + $ret = `rm -f $BUILDROOT/$PROJECT.tmp/dev/build/debian/watch`; + $ret = `rm -fr $BUILDROOT/$PROJECT.tmp/dev/build/doap`; + $ret = `rm -fr $BUILDROOT/$PROJECT.tmp/dev/build/exe`; + $ret = `rm -fr $BUILDROOT/$PROJECT.tmp/dev/build/launchpad`; + $ret = `rm -fr $BUILDROOT/$PROJECT.tmp/dev/build/live`; + $ret = `rm -fr $BUILDROOT/$PROJECT.tmp/dev/build/patch`; + $ret = `rm -fr $BUILDROOT/$PROJECT.tmp/dev/build/perl`; + $ret = `rm -fr $BUILDROOT/$PROJECT.tmp/dev/build/rpm`; + $ret = `rm -fr $BUILDROOT/$PROJECT.tmp/dev/build/zip`; + + # Removed duplicate license files + $ret = +`rm -fr $BUILDROOT/$PROJECT.tmp/htdocs/includes/ckeditor/ckeditor/_source/LICENSE.md`; + $ret = +`rm -fr $BUILDROOT/$PROJECT.tmp/htdocs/includes/ckeditor/ckeditor/_source/plugins/scayt/LICENSE.md`; + $ret = +`rm -fr $BUILDROOT/$PROJECT.tmp/htdocs/includes/ckeditor/ckeditor/_source/plugins/wsc/LICENSE.md`; + $ret = +`rm -fr $BUILDROOT/$PROJECT.tmp/htdocs/includes/ckeditor/ckeditor/LICENSE.md`; + $ret = +`rm -fr $BUILDROOT/$PROJECT.tmp/htdocs/includes/ckeditor/ckeditor/plugins/scayt/LICENSE.md`; + $ret = +`rm -fr $BUILDROOT/$PROJECT.tmp/htdocs/includes/ckeditor/ckeditor/plugins/wsc/LICENSE.md`; + $ret = + `rm -fr $BUILDROOT/$PROJECT.tmp/htdocs/includes/php-iban/LICENSE`; + $ret = +`rm -fr $BUILDROOT/$PROJECT.tmp/htdocs/includes/jquery/plugins/flot/LICENSE.txt`; + $ret = +`rm -fr $BUILDROOT/$PROJECT.tmp/htdocs/includes/jquery/plugins/datatables/extensions/ColReorder/License.txt`; + $ret = +`rm -fr $BUILDROOT/$PROJECT.tmp/htdocs/includes/jquery/plugins/datatables/extensions/ColVis/License.txt`; + $ret = +`rm -fr $BUILDROOT/$PROJECT.tmp/htdocs/includes/jquery/plugins/datatables/extensions/FixedColumns/License.txt`; + $ret = +`rm -fr $BUILDROOT/$PROJECT.tmp/htdocs/includes/jquery/plugins/datatables/extensions/Responsive/License.txt`; + $ret = +`rm -fr $BUILDROOT/$PROJECT.tmp/htdocs/includes/jquery/plugins/datatables/license.txt`; + $ret = +`rm -fr $BUILDROOT/$PROJECT.tmp/htdocs/includes/jquery/plugins/select2/LICENSE`; + $ret = +`rm -fr $BUILDROOT/$PROJECT.tmp/htdocs/includes/mike42/escpos-php/LICENSE.md`; + $ret = +`rm -fr $BUILDROOT/$PROJECT.tmp/htdocs/includes/mobiledetect/mobiledetectlib/LICENSE.txt`; + +# Removed files we don't need (already removed) +#$ret=`rm -fr $BUILDROOT/$PROJECT.tmp/htdocs/includes/ckeditor/ckeditor/_source`; + $ret = `rm -fr $BUILDROOT/$PROJECT.tmp/.codeclimate.yml`; + $ret = `rm -fr $BUILDROOT/$PROJECT.tmp/.pre-commit-config.yaml`; + $ret = `rm -fr $BUILDROOT/$PROJECT.tmp/.vscode`; + $ret = +`find $BUILDROOT/$PROJECT.tmp/ -type f -name '.editorconfig' -exec rm {} \\;`; + $ret = +`find $BUILDROOT/$PROJECT.tmp/ -type f -name '.travis.yml' -exec rm {} \\;`; # Rename upstream changelog to match debian rules - $ret=`mv $BUILDROOT/$PROJECT.tmp/ChangeLog $BUILDROOT/$PROJECT.tmp/changelog`; + $ret = +`mv $BUILDROOT/$PROJECT.tmp/ChangeLog $BUILDROOT/$PROJECT.tmp/changelog`; # Prepare source package (init debian dir) print "Create directory $BUILDROOT/$PROJECT.tmp/debian\n"; - $ret=`mkdir "$BUILDROOT/$PROJECT.tmp/debian"`; - print "Copy $SOURCE/dev/build/debian/xxx to $BUILDROOT/$PROJECT.tmp/debian\n"; - # Add files for dpkg-source (changelog) - #$ret=`cp -f "$SOURCE/dev/build/debian/changelog" "$BUILDROOT/$PROJECT.tmp/debian"`; - open (SPECFROM,"<$SOURCE/dev/build/debian/changelog") || die "Error"; - open (SPECTO,">$BUILDROOT/$PROJECT.tmp/debian/changelog") || die "Error"; - while () { + $ret = `mkdir "$BUILDROOT/$PROJECT.tmp/debian"`; + print +"Copy $SOURCE/dev/build/debian/xxx to $BUILDROOT/$PROJECT.tmp/debian\n"; + +# Add files for dpkg-source (changelog) +#$ret=`cp -f "$SOURCE/dev/build/debian/changelog" "$BUILDROOT/$PROJECT.tmp/debian"`; + open( my $SPECFROM2, "<", "$SOURCE/dev/build/debian/changelog" ) + or die "Error"; + open( my $SPECTO2, ">", "$BUILDROOT/$PROJECT.tmp/debian/changelog" ) + or die "Error"; + while (<$SPECFROM2>) { $_ =~ s/__VERSION__/$MAJOR.$MINOR.$newbuild/; - print SPECTO $_; + print $SPECTO2 $_; } - close SPECFROM; - close SPECTO; + close $SPECFROM2; + close $SPECTO2; + # Add files for dpkg-source - $ret=`cp -f "$SOURCE/dev/build/debian/compat" "$BUILDROOT/$PROJECT.tmp/debian"`; - $ret=`cp -f "$SOURCE/dev/build/debian/control" "$BUILDROOT/$PROJECT.tmp/debian"`; - $ret=`cp -f "$SOURCE/dev/build/debian/copyright" "$BUILDROOT/$PROJECT.tmp/debian"`; - $ret=`cp -f "$SOURCE/dev/build/debian/dolibarr.desktop" "$BUILDROOT/$PROJECT.tmp/debian"`; - $ret=`cp -f "$SOURCE/dev/build/debian/dolibarr.docs" "$BUILDROOT/$PROJECT.tmp/debian"`; - $ret=`cp -f "$SOURCE/dev/build/debian/dolibarr.install" "$BUILDROOT/$PROJECT.tmp/debian"`; - $ret=`cp -f "$SOURCE/dev/build/debian/dolibarr.lintian-overrides" "$BUILDROOT/$PROJECT.tmp/debian"`; - $ret=`cp -f "$SOURCE/dev/build/debian/dolibarr.xpm" "$BUILDROOT/$PROJECT.tmp/debian"`; - $ret=`cp -f "$SOURCE/dev/build/debian/rules" "$BUILDROOT/$PROJECT.tmp/debian"`; - $ret=`cp -f "$SOURCE/dev/build/debian/watch" "$BUILDROOT/$PROJECT.tmp/debian"`; - $ret=`cp -fr "$SOURCE/dev/build/debian/patches" "$BUILDROOT/$PROJECT.tmp/debian"`; - $ret=`cp -fr "$SOURCE/dev/build/debian/po" "$BUILDROOT/$PROJECT.tmp/debian"`; - $ret=`cp -fr "$SOURCE/dev/build/debian/source" "$BUILDROOT/$PROJECT.tmp/debian"`; - $ret=`cp -fr "$SOURCE/dev/build/debian/apache" "$BUILDROOT/$PROJECT.tmp/debian/apache"`; - $ret=`cp -f "$SOURCE/dev/build/debian/apache/.htaccess" "$BUILDROOT/$PROJECT.tmp/debian/apache"`; - $ret=`cp -fr "$SOURCE/dev/build/debian/lighttpd" "$BUILDROOT/$PROJECT.tmp/debian/lighttpd"`; + $ret = +`cp -f "$SOURCE/dev/build/debian/compat" "$BUILDROOT/$PROJECT.tmp/debian"`; + $ret = +`cp -f "$SOURCE/dev/build/debian/control" "$BUILDROOT/$PROJECT.tmp/debian"`; + $ret = +`cp -f "$SOURCE/dev/build/debian/copyright" "$BUILDROOT/$PROJECT.tmp/debian"`; + $ret = +`cp -f "$SOURCE/dev/build/debian/dolibarr.desktop" "$BUILDROOT/$PROJECT.tmp/debian"`; + $ret = +`cp -f "$SOURCE/dev/build/debian/dolibarr.docs" "$BUILDROOT/$PROJECT.tmp/debian"`; + $ret = +`cp -f "$SOURCE/dev/build/debian/dolibarr.install" "$BUILDROOT/$PROJECT.tmp/debian"`; + $ret = +`cp -f "$SOURCE/dev/build/debian/dolibarr.lintian-overrides" "$BUILDROOT/$PROJECT.tmp/debian"`; + $ret = +`cp -f "$SOURCE/dev/build/debian/dolibarr.xpm" "$BUILDROOT/$PROJECT.tmp/debian"`; + $ret = +`cp -f "$SOURCE/dev/build/debian/rules" "$BUILDROOT/$PROJECT.tmp/debian"`; + $ret = +`cp -f "$SOURCE/dev/build/debian/watch" "$BUILDROOT/$PROJECT.tmp/debian"`; + $ret = +`cp -fr "$SOURCE/dev/build/debian/patches" "$BUILDROOT/$PROJECT.tmp/debian"`; + $ret = +`cp -fr "$SOURCE/dev/build/debian/po" "$BUILDROOT/$PROJECT.tmp/debian"`; + $ret = +`cp -fr "$SOURCE/dev/build/debian/source" "$BUILDROOT/$PROJECT.tmp/debian"`; + $ret = +`cp -fr "$SOURCE/dev/build/debian/apache" "$BUILDROOT/$PROJECT.tmp/debian/apache"`; + $ret = +`cp -f "$SOURCE/dev/build/debian/apache/.htaccess" "$BUILDROOT/$PROJECT.tmp/debian/apache"`; + $ret = +`cp -fr "$SOURCE/dev/build/debian/lighttpd" "$BUILDROOT/$PROJECT.tmp/debian/lighttpd"`; + # Add files also required to build binary package - $ret=`cp -f "$SOURCE/dev/build/debian/dolibarr.config" "$BUILDROOT/$PROJECT.tmp/debian"`; - $ret=`cp -f "$SOURCE/dev/build/debian/dolibarr.postinst" "$BUILDROOT/$PROJECT.tmp/debian"`; - $ret=`cp -f "$SOURCE/dev/build/debian/dolibarr.postrm" "$BUILDROOT/$PROJECT.tmp/debian"`; - $ret=`cp -f "$SOURCE/dev/build/debian/dolibarr.templates" "$BUILDROOT/$PROJECT.tmp/debian"`; - $ret=`cp -f "$SOURCE/dev/build/debian/install.forced.php.install" "$BUILDROOT/$PROJECT.tmp/debian"`; + $ret = +`cp -f "$SOURCE/dev/build/debian/dolibarr.config" "$BUILDROOT/$PROJECT.tmp/debian"`; + $ret = +`cp -f "$SOURCE/dev/build/debian/dolibarr.postinst" "$BUILDROOT/$PROJECT.tmp/debian"`; + $ret = +`cp -f "$SOURCE/dev/build/debian/dolibarr.postrm" "$BUILDROOT/$PROJECT.tmp/debian"`; + $ret = +`cp -f "$SOURCE/dev/build/debian/dolibarr.templates" "$BUILDROOT/$PROJECT.tmp/debian"`; + $ret = +`cp -f "$SOURCE/dev/build/debian/install.forced.php.install" "$BUILDROOT/$PROJECT.tmp/debian"`; # Set owners and permissions #print "Set owners on files/dir\n"; #$ret=`chown -R root.root $BUILDROOT/$PROJECT.tmp`; print "Set permissions on files/dir\n"; - $ret=`chmod -R 755 $BUILDROOT/$PROJECT.tmp`; - $cmd="find $BUILDROOT/$PROJECT.tmp -type f -exec chmod 644 {} \\; "; - $ret=`$cmd`; - $cmd="find $BUILDROOT/$PROJECT.tmp/dev/build -name '*.php' -type f -exec chmod 755 {} \\; "; - $ret=`$cmd`; - $cmd="find $BUILDROOT/$PROJECT.tmp/dev/build -name '*.dpatch' -type f -exec chmod 755 {} \\; "; - $ret=`$cmd`; - $cmd="find $BUILDROOT/$PROJECT.tmp/dev/build -name '*.pl' -type f -exec chmod 755 {} \\; "; - $ret=`$cmd`; - $cmd="find $BUILDROOT/$PROJECT.tmp/dev -name '*.php' -type f -exec chmod 755 {} \\; "; - $ret=`$cmd`; - $ret=`chmod 755 $BUILDROOT/$PROJECT.tmp/debian/rules`; - $ret=`chmod -R 644 $BUILDROOT/$PROJECT.tmp/dev/translation/autotranslator.class.php`; - $ret=`chmod -R 644 $BUILDROOT/$PROJECT.tmp/htdocs/modulebuilder/template/class/actions_mymodule.class.php`; - $ret=`chmod -R 644 $BUILDROOT/$PROJECT.tmp/htdocs/modulebuilder/template/class/api_mymodule.class.php`; - $ret=`chmod -R 644 $BUILDROOT/$PROJECT.tmp/htdocs/modulebuilder/template/class/myobject.class.php`; - $ret=`chmod -R 644 $BUILDROOT/$PROJECT.tmp/htdocs/modulebuilder/template/core/modules/modMyModule.class.php`; - $ret=`chmod -R 644 $BUILDROOT/$PROJECT.tmp/htdocs/modulebuilder/template/mymoduleindex.php`; - $ret=`chmod -R 644 $BUILDROOT/$PROJECT.tmp/htdocs/modulebuilder/template/myobject_card.php`; - $ret=`chmod -R 644 $BUILDROOT/$PROJECT.tmp/htdocs/modulebuilder/template/myobject_list.php`; - $ret=`chmod -R 755 $BUILDROOT/$PROJECT.tmp/htdocs/modulebuilder/template/scripts/mymodule.php`; - $cmd="find $BUILDROOT/$PROJECT.tmp/scripts -name '*.php' -type f -exec chmod 755 {} \\; "; - $ret=`$cmd`; - $cmd="find $BUILDROOT/$PROJECT.tmp/scripts -name '*.sh' -type f -exec chmod 755 {} \\; "; - $ret=`$cmd`; - - - print "Rename directory $BUILDROOT/$PROJECT.tmp into $BUILDROOT/$PROJECT-$MAJOR.$MINOR.$build\n"; - $cmd="mv $BUILDROOT/$PROJECT.tmp $BUILDROOT/$PROJECT-$MAJOR.$MINOR.$build"; - $ret=`$cmd`; + $ret = `chmod -R 755 $BUILDROOT/$PROJECT.tmp`; + $cmd = + "find $BUILDROOT/$PROJECT.tmp -type f -exec chmod 644 {} \\; "; + $ret = `$cmd`; + $cmd = +"find $BUILDROOT/$PROJECT.tmp/dev/build -name '*.php' -type f -exec chmod 755 {} \\; "; + $ret = `$cmd`; + $cmd = +"find $BUILDROOT/$PROJECT.tmp/dev/build -name '*.dpatch' -type f -exec chmod 755 {} \\; "; + $ret = `$cmd`; + $cmd = +"find $BUILDROOT/$PROJECT.tmp/dev/build -name '*.pl' -type f -exec chmod 755 {} \\; "; + $ret = `$cmd`; + $cmd = +"find $BUILDROOT/$PROJECT.tmp/dev -name '*.php' -type f -exec chmod 755 {} \\; "; + $ret = `$cmd`; + $ret = `chmod 755 $BUILDROOT/$PROJECT.tmp/debian/rules`; + $ret = +`chmod -R 644 $BUILDROOT/$PROJECT.tmp/dev/translation/autotranslator.class.php`; + $ret = +`chmod -R 644 $BUILDROOT/$PROJECT.tmp/htdocs/modulebuilder/template/class/actions_mymodule.class.php`; + $ret = +`chmod -R 644 $BUILDROOT/$PROJECT.tmp/htdocs/modulebuilder/template/class/api_mymodule.class.php`; + $ret = +`chmod -R 644 $BUILDROOT/$PROJECT.tmp/htdocs/modulebuilder/template/class/myobject.class.php`; + $ret = +`chmod -R 644 $BUILDROOT/$PROJECT.tmp/htdocs/modulebuilder/template/core/modules/modMyModule.class.php`; + $ret = +`chmod -R 644 $BUILDROOT/$PROJECT.tmp/htdocs/modulebuilder/template/mymoduleindex.php`; + $ret = +`chmod -R 644 $BUILDROOT/$PROJECT.tmp/htdocs/modulebuilder/template/myobject_card.php`; + $ret = +`chmod -R 644 $BUILDROOT/$PROJECT.tmp/htdocs/modulebuilder/template/myobject_list.php`; + $ret = +`chmod -R 755 $BUILDROOT/$PROJECT.tmp/htdocs/modulebuilder/template/scripts/mymodule.php`; + $cmd = +"find $BUILDROOT/$PROJECT.tmp/scripts -name '*.php' -type f -exec chmod 755 {} \\; "; + $ret = `$cmd`; + $cmd = +"find $BUILDROOT/$PROJECT.tmp/scripts -name '*.sh' -type f -exec chmod 755 {} \\; "; + $ret = `$cmd`; + print +"Rename directory $BUILDROOT/$PROJECT.tmp into $BUILDROOT/$PROJECT-$MAJOR.$MINOR.$build\n"; + $cmd = +"mv $BUILDROOT/$PROJECT.tmp $BUILDROOT/$PROJECT-$MAJOR.$MINOR.$build"; + $ret = `$cmd`; print "Go into directory $BUILDROOT\n"; chdir("$BUILDROOT"); - # We need a tarball to be able to build "quilt" debian package (not required for native but we need patch so it is not a native) - print "Compress $BUILDROOT/$PROJECT-$MAJOR.$MINOR.$build into $BUILDROOT/$FILENAMEDEBNATIVE.orig.tar.gz...\n"; - $cmd="tar --exclude-vcs --exclude-from \"$BUILDROOT/$PROJECT/dev/build/tgz/tar_exclude.txt\" --directory \"$BUILDROOT\" --mode=go-w --group=500 --owner=500 -czvf \"$BUILDROOT/$FILENAMEDEBNATIVE.orig.tar.gz\" $PROJECT-$MAJOR.$MINOR.$build"; - print $cmd."\n"; - $ret=`$cmd`; +# We need a tarball to be able to build "quilt" debian package (not required for native but we need patch so it is not a native) + print +"Compress $BUILDROOT/$PROJECT-$MAJOR.$MINOR.$build into $BUILDROOT/$FILENAMEDEBNATIVE.orig.tar.gz...\n"; + $cmd = +"tar --exclude-vcs --exclude-from \"$BUILDROOT/$PROJECT/dev/build/tgz/tar_exclude.txt\" --directory \"$BUILDROOT\" --mode=go-w --group=500 --owner=500 -czvf \"$BUILDROOT/$FILENAMEDEBNATIVE.orig.tar.gz\" $PROJECT-$MAJOR.$MINOR.$build"; + print $cmd. "\n"; + $ret = `$cmd`; # Creation of source package - print "Go into directory $BUILDROOT/$PROJECT-$MAJOR.$MINOR.$build\n"; + print + "Go into directory $BUILDROOT/$PROJECT-$MAJOR.$MINOR.$build\n"; chdir("$BUILDROOT/$PROJECT-$MAJOR.$MINOR.$build"); + #$cmd="dpkg-source -b $BUILDROOT/$PROJECT-$MAJOR.$MINOR.$build"; - $cmd="dpkg-buildpackage -us -uc --compression=gzip"; + $cmd = "dpkg-buildpackage -us -uc --compression=gzip"; print "Launch DEB build ($cmd)\n"; - $ret=`$cmd 2>&1 3>&1`; - print $ret."\n"; + $ret = `$cmd 2>&1 3>&1`; + print $ret. "\n"; chdir("$olddir"); - print "You can check bin package with lintian --pedantic -E -I \"$NEWDESTI/${FILENAMEDEB}_all.deb\"\n"; - print "You can check src package with lintian --pedantic -E -I \"$NEWDESTI/${FILENAMEDEB}.dsc\"\n"; + print +"You can check bin package with lintian --pedantic -E -I \"$NEWDESTI/${FILENAMEDEB}_all.deb\"\n"; + print +"You can check src package with lintian --pedantic -E -I \"$NEWDESTI/${FILENAMEDEB}.dsc\"\n"; # Move to final dir print "Move *_all.deb *.dsc *.orig.tar.gz *.changes to $NEWDESTI\n"; - $ret=`mv $BUILDROOT/*_all.deb "$NEWDESTI/"`; - $ret=`mv $BUILDROOT/*.dsc "$NEWDESTI/"`; - $ret=`mv $BUILDROOT/*.orig.tar.gz "$NEWDESTI/"`; - #$ret=`mv $BUILDROOT/*.debian.tar.xz "$NEWDESTI/"`; # xz file is generated when build/debian/sources/option - $ret=`mv $BUILDROOT/*.debian.tar.gz "$NEWDESTI/"`; - $ret=`mv $BUILDROOT/*.changes "$NEWDESTI/"`; + $ret = `mv $BUILDROOT/*_all.deb "$NEWDESTI/"`; + $ret = `mv $BUILDROOT/*.dsc "$NEWDESTI/"`; + $ret = `mv $BUILDROOT/*.orig.tar.gz "$NEWDESTI/"`; + +#$ret=`mv $BUILDROOT/*.debian.tar.xz "$NEWDESTI/"`; # xz file is generated when build/debian/sources/option + $ret = `mv $BUILDROOT/*.debian.tar.gz "$NEWDESTI/"`; + $ret = `mv $BUILDROOT/*.changes "$NEWDESTI/"`; next; } - if ($target eq 'EXEDOLIWAMP') - { - $NEWDESTI=$DESTI; - if ($NEWDESTI =~ /stable/) - { - mkdir($DESTI.'/package_windows'); - if (-d $DESTI.'/package_windows') { $NEWDESTI=$DESTI.'/package_windows'; } + if ( $target eq 'EXEDOLIWAMP' ) { + $NEWDESTI = $DESTI; + if ( $NEWDESTI =~ /stable/ ) { + mkdir( $DESTI . '/package_windows' ); + if ( -d $DESTI . '/package_windows' ) { + $NEWDESTI = $DESTI . '/package_windows'; + } } - print "Remove target $NEWDESTI/$FILENAMEEXEDOLIWAMP.exe...\n"; - unlink "$NEWDESTI/$FILENAMEEXEDOLIWAMP.exe"; + print "Remove target $NEWDESTI/$FILENAMEEXEDOLIWAMP.exe...\n"; + unlink "$NEWDESTI/$FILENAMEEXEDOLIWAMP.exe"; - if ($OS eq 'windows') { - print "Check that ISCC.exe is in your PATH.\n"; - } else { - print "Check that in your Wine setup, you have created a Z: drive that point to your / directory.\n"; + if ( $OS eq 'windows' ) { + print "Check that ISCC.exe is in your PATH.\n"; + } + else { + print +"Check that in your Wine setup, you have created a Z: drive that point to your / directory.\n"; } - $SOURCEBACK=$SOURCE; - $SOURCEBACK =~ s/\//\\/g; + $SOURCEBACK = $SOURCE; + $SOURCEBACK =~ s/\//\\/g; - print "Prepare file \"$SOURCEBACK\\dev\\build\\exe\\doliwamp\\doliwamp.tmp.iss\" from \"$SOURCEBACK\\dev\\build\\exe\\doliwamp\\doliwamp.iss\"\n"; + print +"Prepare file \"$SOURCEBACK\\dev\\build\\exe\\doliwamp\\doliwamp.tmp.iss\" from \"$SOURCEBACK\\dev\\build\\exe\\doliwamp\\doliwamp.iss\"\n"; - #$ret=`cat "$SOURCE/dev/build/exe/doliwamp/doliwamp.iss" | sed -e 's/__FILENAMEEXEDOLIWAMP__/$FILENAMEEXEDOLIWAMP/g' > "$SOURCE/build/exe/doliwamp/doliwamp.tmp.iss"`; - open(IN, '<' . $SOURCE."/dev/build/exe/doliwamp/doliwamp.iss") or die $!; - open(OUT, '>' . "$SOURCE/dev/build/exe/doliwamp/doliwamp.tmp.iss") or die $!; - while() - { - $_ =~ s/__FILENAMEEXEDOLIWAMP__/$FILENAMEEXEDOLIWAMP/g; - print OUT $_; +#$ret=`cat "$SOURCE/dev/build/exe/doliwamp/doliwamp.iss" | sed -e 's/__FILENAMEEXEDOLIWAMP__/$FILENAMEEXEDOLIWAMP/g' > "$SOURCE/build/exe/doliwamp/doliwamp.tmp.iss"`; + open( my $IN3, '<', + $SOURCE . "/dev/build/exe/doliwamp/doliwamp.iss" ) + or die $!; + open( my $OUT, '>', + "$SOURCE/dev/build/exe/doliwamp/doliwamp.tmp.iss" ) + or die $!; + while (<$IN3>) { + $_ =~ s/__FILENAMEEXEDOLIWAMP__/$FILENAMEEXEDOLIWAMP/g; + print $OUT $_; } - close(IN); - close(OUT); + close($IN3); + close($OUT); - print "Compil exe $FILENAMEEXEDOLIWAMP.exe file from iss file \"$SOURCEBACK\\dev\\build\\exe\\doliwamp\\doliwamp.tmp.iss\" on OS $OS\n"; + print +"Compil exe $FILENAMEEXEDOLIWAMP.exe file from iss file \"$SOURCEBACK\\dev\\build\\exe\\doliwamp\\doliwamp.tmp.iss\" on OS $OS\n"; - if ($OS eq 'windows') { - $cmd= "ISCC.exe \"$SOURCEBACK\\dev\\build\\exe\\doliwamp\\doliwamp.tmp.iss\""; - } else { - #$cmd= "wine ISCC.exe \"Z:$SOURCEBACK\\dev\\build\\exe\\doliwamp\\doliwamp.tmp.iss\""; - } + if ( $OS eq 'windows' ) { + $cmd = +"ISCC.exe \"$SOURCEBACK\\dev\\build\\exe\\doliwamp\\doliwamp.tmp.iss\""; + } + else { +#$cmd= "wine ISCC.exe \"Z:$SOURCEBACK\\dev\\build\\exe\\doliwamp\\doliwamp.tmp.iss\""; + } print "$cmd\n"; - $ret= `$cmd`; + $ret = `$cmd`; print "ret=$ret\n"; # Move to final dir - print "Move \"$SOURCE\\dev\\build\\$FILENAMEEXEDOLIWAMP.exe\" to $NEWDESTI/$FILENAMEEXEDOLIWAMP.exe\n"; - rename("$SOURCE/dev/build/$FILENAMEEXEDOLIWAMP.exe","$NEWDESTI/$FILENAMEEXEDOLIWAMP.exe"); - print "Move $SOURCE/dev/build/$FILENAMEEXEDOLIWAMP.exe to $NEWDESTI/$FILENAMEEXEDOLIWAMP.exe\n"; + print +"Move \"$SOURCE\\dev\\build\\$FILENAMEEXEDOLIWAMP.exe\" to $NEWDESTI/$FILENAMEEXEDOLIWAMP.exe\n"; + rename( + "$SOURCE/dev/build/$FILENAMEEXEDOLIWAMP.exe", + "$NEWDESTI/$FILENAMEEXEDOLIWAMP.exe" + ); + print +"Move $SOURCE/dev/build/$FILENAMEEXEDOLIWAMP.exe to $NEWDESTI/$FILENAMEEXEDOLIWAMP.exe\n"; - use File::Copy; + use File::Copy; - #$ret=`mv "$SOURCE/dev/build/$FILENAMEEXEDOLIWAMP.exe" "$NEWDESTI/$FILENAMEEXEDOLIWAMP.exe"`; - $ret=move("$SOURCE/dev/build/$FILENAMEEXEDOLIWAMP.exe", "$NEWDESTI/$FILENAMEEXEDOLIWAMP.exe"); +#$ret=`mv "$SOURCE/dev/build/$FILENAMEEXEDOLIWAMP.exe" "$NEWDESTI/$FILENAMEEXEDOLIWAMP.exe"`; + $ret = move( + "$SOURCE/dev/build/$FILENAMEEXEDOLIWAMP.exe", + "$NEWDESTI/$FILENAMEEXEDOLIWAMP.exe" + ); - print "Remove tmp file $SOURCE/dev/build/exe/doliwamp/doliwamp.tmp.iss\n"; - #$ret=`rm "$SOURCE/dev/build/exe/doliwamp/doliwamp.tmp.iss"`; - $ret=unlink("$SOURCE/dev/build/exe/doliwamp/doliwamp.tmp.iss"); + print +"Remove tmp file $SOURCE/dev/build/exe/doliwamp/doliwamp.tmp.iss\n"; - next; - } - } + #$ret=`rm "$SOURCE/dev/build/exe/doliwamp/doliwamp.tmp.iss"`; + $ret = unlink("$SOURCE/dev/build/exe/doliwamp/doliwamp.tmp.iss"); + + next; + } + } # Publish package for each target #-------------------------------- - foreach my $target (sort keys %CHOOSEDPUBLISH) - { - if ($CHOOSEDPUBLISH{$target} < 0) { next; } + foreach my $target ( sort keys %CHOOSEDPUBLISH ) { + if ( $CHOOSEDPUBLISH{$target} < 0 ) { next; } print "\nList of files to publish (BUILD=$BUILD)\n"; - %filestoscansf=( - "$DESTI/signatures/filelist-$MAJOR.$MINOR.$BUILD.xml"=>'none', # none means it won't be published on SF - "$DESTI/package_rpm_generic/$FILENAMERPM"=>'Dolibarr installer for Fedora-Redhat-Mandriva-Opensuse (DoliRpm)', - "$DESTI/package_rpm_generic/$FILENAMERPMSRC"=>'none', # none means it won't be published on SF - "$DESTI/package_debian-ubuntu/${FILENAMEDEB}_all.deb"=>'Dolibarr installer for Debian-Ubuntu (DoliDeb)', - "$DESTI/package_debian-ubuntu/${FILENAMEDEB}_amd64.changes"=>'none', # none means it won't be published on SF - "$DESTI/package_debian-ubuntu/${FILENAMEDEB}.dsc"=>'none', # none means it won't be published on SF - #"$DESTI/package_debian-ubuntu/${FILENAMEDEB}.debian.tar.xz"=>'none', # none means it won't be published on SF - "$DESTI/package_debian-ubuntu/${FILENAMEDEB}.debian.tar.gz"=>'none', # none means it won't be published on SF - "$DESTI/package_debian-ubuntu/${FILENAMEDEBSHORT}.orig.tar.gz"=>'none', # none means it won't be published on SF - "$DESTI/package_windows/$FILENAMEEXEDOLIWAMP.exe"=>'Dolibarr installer for Windows (DoliWamp)', - "$DESTI/standard/$FILENAMETGZ.tgz"=>'Dolibarr ERP-CRM', - "$DESTI/standard/$FILENAMETGZ.zip"=>'Dolibarr ERP-CRM' + %filestoscansf = ( + "$DESTI/signatures/filelist-$MAJOR.$MINOR.$BUILD.xml" => + 'none', # none means it won't be published on SF + "$DESTI/package_rpm_generic/$FILENAMERPM" => +'Dolibarr installer for Fedora-Redhat-Mandriva-Opensuse (DoliRpm)', + "$DESTI/package_rpm_generic/$FILENAMERPMSRC" => + 'none', # none means it won't be published on SF + "$DESTI/package_debian-ubuntu/${FILENAMEDEB}_all.deb" => + 'Dolibarr installer for Debian-Ubuntu (DoliDeb)', + "$DESTI/package_debian-ubuntu/${FILENAMEDEB}_amd64.changes" => + 'none', # none means it won't be published on SF + "$DESTI/package_debian-ubuntu/${FILENAMEDEB}.dsc" => + 'none', # none means it won't be published on SF + #"$DESTI/package_debian-ubuntu/${FILENAMEDEB}.debian.tar.xz"=>'none', # none means it won't be published on SF + "$DESTI/package_debian-ubuntu/${FILENAMEDEB}.debian.tar.gz" => + 'none', # none means it won't be published on SF + "$DESTI/package_debian-ubuntu/${FILENAMEDEBSHORT}.orig.tar.gz" => + 'none', # none means it won't be published on SF + "$DESTI/package_windows/$FILENAMEEXEDOLIWAMP.exe" => + 'Dolibarr installer for Windows (DoliWamp)', + "$DESTI/standard/$FILENAMETGZ.tgz" => 'Dolibarr ERP-CRM', + "$DESTI/standard/$FILENAMETGZ.zip" => 'Dolibarr ERP-CRM' ); - %filestoscanstableasso=( - "$DESTI/signatures/filelist-$MAJOR.$MINOR.$BUILD.xml"=>'signatures', - "$DESTI/package_rpm_generic/$FILENAMERPM"=>'package_rpm_generic', - "$DESTI/package_rpm_generic/$FILENAMERPMSRC"=>'package_rpm_generic', - "$DESTI/package_debian-ubuntu/${FILENAMEDEB}_all.deb"=>'package_debian-ubuntu', - "$DESTI/package_debian-ubuntu/${FILENAMEDEB}_amd64.changes"=>'package_debian-ubuntu', - "$DESTI/package_debian-ubuntu/${FILENAMEDEB}.dsc"=>'package_debian-ubuntu', - #"$DESTI/package_debian-ubuntu/${FILENAMEDEB}.debian.tar.xz"=>'package_debian-ubuntu', - "$DESTI/package_debian-ubuntu/${FILENAMEDEB}.debian.tar.gz"=>'package_debian-ubuntu', - "$DESTI/package_debian-ubuntu/${FILENAMEDEBSHORT}.orig.tar.gz"=>'package_debian-ubuntu', - "$DESTI/package_windows/$FILENAMEEXEDOLIWAMP.exe"=>'package_windows', - "$DESTI/standard/$FILENAMETGZ.tgz"=>'standard', - "$DESTI/standard/$FILENAMETGZ.zip"=>'standard' + %filestoscanstableasso = ( + "$DESTI/signatures/filelist-$MAJOR.$MINOR.$BUILD.xml" => + 'signatures', + "$DESTI/package_rpm_generic/$FILENAMERPM" => 'package_rpm_generic', + "$DESTI/package_rpm_generic/$FILENAMERPMSRC" => + 'package_rpm_generic', + "$DESTI/package_debian-ubuntu/${FILENAMEDEB}_all.deb" => + 'package_debian-ubuntu', + "$DESTI/package_debian-ubuntu/${FILENAMEDEB}_amd64.changes" => + 'package_debian-ubuntu', + "$DESTI/package_debian-ubuntu/${FILENAMEDEB}.dsc" => + 'package_debian-ubuntu', + +#"$DESTI/package_debian-ubuntu/${FILENAMEDEB}.debian.tar.xz"=>'package_debian-ubuntu', + "$DESTI/package_debian-ubuntu/${FILENAMEDEB}.debian.tar.gz" => + 'package_debian-ubuntu', + "$DESTI/package_debian-ubuntu/${FILENAMEDEBSHORT}.orig.tar.gz" => + 'package_debian-ubuntu', + "$DESTI/package_windows/$FILENAMEEXEDOLIWAMP.exe" => + 'package_windows', + "$DESTI/standard/$FILENAMETGZ.tgz" => 'standard', + "$DESTI/standard/$FILENAMETGZ.zip" => 'standard' ); - if ($target eq 'ASSO' && $BUILD =~ /[a-z]/i) { # Not stable - %filestoscansf=( - "$DESTI/$FILENAMERPM"=>'Dolibarr installer for Fedora-Redhat-Mandriva-Opensuse (DoliRpm)', - "$DESTI/${FILENAMEDEB}_all.deb"=>'Dolibarr installer for Debian-Ubuntu (DoliDeb)', - "$DESTI/$FILENAMEEXEDOLIWAMP.exe"=>'Dolibarr installer for Windows (DoliWamp)', - "$DESTI/$FILENAMETGZ.tgz"=>'Dolibarr ERP-CRM', - "$DESTI/$FILENAMETGZ.zip"=>'Dolibarr ERP-CRM' + if ( $target eq 'ASSO' && $BUILD =~ /[a-z]/i ) { # Not stable + %filestoscansf = ( + "$DESTI/$FILENAMERPM" => +'Dolibarr installer for Fedora-Redhat-Mandriva-Opensuse (DoliRpm)', + "$DESTI/${FILENAMEDEB}_all.deb" => + 'Dolibarr installer for Debian-Ubuntu (DoliDeb)', + "$DESTI/$FILENAMEEXEDOLIWAMP.exe" => + 'Dolibarr installer for Windows (DoliWamp)', + "$DESTI/$FILENAMETGZ.tgz" => 'Dolibarr ERP-CRM', + "$DESTI/$FILENAMETGZ.zip" => 'Dolibarr ERP-CRM' ); - %filestoscanstableasso=( - "$DESTI/$FILENAMERPM"=>'', - "$DESTI/${FILENAMEDEB}_all.deb"=>'', - "$DESTI/$FILENAMEEXEDOLIWAMP.exe"=>'', - "$DESTI/$FILENAMETGZ.tgz"=>'', - "$DESTI/$FILENAMETGZ.zip"=>'' + %filestoscanstableasso = ( + "$DESTI/$FILENAMERPM" => '', + "$DESTI/${FILENAMEDEB}_all.deb" => '', + "$DESTI/$FILENAMEEXEDOLIWAMP.exe" => '', + "$DESTI/$FILENAMETGZ.tgz" => '', + "$DESTI/$FILENAMETGZ.zip" => '' ); } use POSIX qw/strftime/; - foreach my $file (sort keys %filestoscansf) - { - $found=0; + foreach my $file ( sort keys %filestoscansf ) { + $found = 0; my $filesize = -s $file; - my $filedate = (stat $file)[9]; - print $file." ".($filesize?"(found)":"(not found)"); - print ($filesize?" - ".$filesize:""); - print ($filedate?" - ".strftime("%Y-%m-%d %H:%M:%S",localtime($filedate)):""); + my $filedate = ( stat $file )[9]; + print $file. " " . ( $filesize ? "(found)" : "(not found)" ); + print( $filesize? " - " . $filesize : "" ); + print( $filedate + ? " - " . strftime( "%Y-%m-%d %H:%M:%S", localtime($filedate) ) + : "" + ); print "\n"; } - if ($target eq 'SF' || $target eq 'ASSO') - { + if ( $target eq 'SF' || $target eq 'ASSO' ) { print "\n"; - if ($target eq 'SF') { $PUBLISH = $PUBLISHSTABLE; } - if ($target eq 'ASSO' && $BUILD =~ /[a-z]/i) { $PUBLISH = $PUBLISHBETARC.'/lastbuild'; } - if ($target eq 'ASSO' && $BUILD =~ /^[0-9]+$/) { $PUBLISH = $PUBLISHBETARC.'/stable'; } + if ( $target eq 'SF' ) { $PUBLISH = $PUBLISHSTABLE; } + if ( $target eq 'ASSO' && $BUILD =~ /[a-z]/i ) { + $PUBLISH = $PUBLISHBETARC . '/lastbuild'; + } + if ( $target eq 'ASSO' && $BUILD =~ /^[0-9]+$/ ) { + $PUBLISH = $PUBLISHBETARC . '/stable'; + } - $NEWPUBLISH=$PUBLISH; + $NEWPUBLISH = $PUBLISH; print "Publish to target $NEWPUBLISH. Click enter or CTRL+C...\n"; # Ask which target to build - $NUM_SCRIPT=; + $NUM_SCRIPT = ; chomp($NUM_SCRIPT); - print "Create empty dir /tmp/emptydir. We need it to create target dir using rsync.\n"; - $ret=`mkdir -p "/tmp/emptydir/"`; + print +"Create empty dir /tmp/emptydir. We need it to create target dir using rsync.\n"; + $ret = `mkdir -p "/tmp/emptydir/"`; - %filestoscan=%filestoscansf; + %filestoscan = %filestoscansf; - foreach my $file (sort keys %filestoscan) - { - $found=0; + foreach my $file ( sort keys %filestoscan ) { + $found = 0; my $filesize = -s $file; - if (! $filesize) { next; } + if ( !$filesize ) { next; } - if ($target eq 'SF') { - if ($filestoscan{$file} eq 'none') { - next; - } - $destFolder="$NEWPUBLISH/$filestoscan{$file}/".$MAJOR.'.'.$MINOR.'.'.$BUILD; - } - elsif ($target eq 'ASSO' and $NEWPUBLISH =~ /stable/) { - $destFolder="$NEWPUBLISH/$filestoscanstableasso{$file}"; - } - elsif ($target eq 'ASSO' and $NEWPUBLISH !~ /stable/) { - $destFolder="$NEWPUBLISH"; - } - else # No more used - { - $dirnameonly=$file; - $dirnameonly =~ s/.*\/([^\/]+)\/[^\/]+$/$1/; - $filenameonly=$file; - $filenameonly =~ s/.*\/[^\/]+\/([^\/])+$/$1/; - $destFolder="$NEWPUBLISH/$dirnameonly"; - } + if ( $target eq 'SF' ) { + if ( $filestoscan{$file} eq 'none' ) { + next; + } + $destFolder = + "$NEWPUBLISH/$filestoscan{$file}/" + . $MAJOR . '.' + . $MINOR . '.' + . $BUILD; + } + elsif ( $target eq 'ASSO' and $NEWPUBLISH =~ /stable/ ) { + $destFolder = "$NEWPUBLISH/$filestoscanstableasso{$file}"; + } + elsif ( $target eq 'ASSO' and $NEWPUBLISH !~ /stable/ ) { + $destFolder = "$NEWPUBLISH"; + } + else # No more used + { + $dirnameonly = $file; + $dirnameonly =~ s/.*\/([^\/]+)\/[^\/]+$/$1/; + $filenameonly = $file; + $filenameonly =~ s/.*\/[^\/]+\/([^\/])+$/$1/; + $destFolder = "$NEWPUBLISH/$dirnameonly"; + } print "\n"; - print "Publish file ".$file." to ".$destFolder."\n"; + print "Publish file " . $file . " to " . $destFolder . "\n"; # mkdir #my $ssh = Net::SSH::Perl->new("frs.sourceforge.net"); @@ -1321,21 +1686,23 @@ if ($nboftargetok) { #use String::ShellQuote qw( shell_quote ); #$ssh->cmd('mkdir '.shell_quote($destFolder).' && exit'); - #use Net::SFTP::Foreign; - #my $sftp = Net::SFTP::Foreign->new($ip, user => $user, password => $pass, autodie => 1); - #$sftp->mkdir($destFolder) +#use Net::SFTP::Foreign; +#my $sftp = Net::SFTP::Foreign->new($ip, user => $user, password => $pass, autodie => 1); +#$sftp->mkdir($destFolder) - #$command="ssh eldy,dolibarr\@frs.sourceforge.net mkdir -p \"$destFolder\""; - #print "$command\n"; - #my $ret=`$command 2>&1`; + #$command="ssh eldy,dolibarr\@frs.sourceforge.net mkdir -p \"$destFolder\""; + #print "$command\n"; + #my $ret=`$command 2>&1`; - $command="rsync -s -e 'ssh' --recursive /tmp/emptydir/ \"".$destFolder."\""; + $command = "rsync -s -e 'ssh' --recursive /tmp/emptydir/ \"" + . $destFolder . "\""; print "$command\n"; - my $ret=`$command 2>&1`; + my $ret = `$command 2>&1`; - $command="rsync -s -e 'ssh' \"$file\" \"".$destFolder."\""; + $command = + "rsync -s -e 'ssh' \"$file\" \"" . $destFolder . "\""; print "$command\n"; - my $ret2=`$command 2>&1`; + my $ret2 = `$command 2>&1`; print "$ret2\n"; } } @@ -1343,18 +1710,19 @@ if ($nboftargetok) { } print "\n----- Summary -----\n"; -foreach my $target (sort keys %CHOOSEDTARGET) { - if ($target eq '-CHKSUM') { print "Checksum was generated\n"; next; } - if ($CHOOSEDTARGET{$target} < 0) { +foreach my $target ( sort keys %CHOOSEDTARGET ) { + if ( $target eq '-CHKSUM' ) { print "Checksum was generated\n"; next; } + if ( $CHOOSEDTARGET{$target} < 0 ) { print "Package $target not built (bad requirement).\n"; - } else { + } + else { print "Package $target built successfully in $DESTI\n"; } } -if (! $batch) { +if ( !$batch ) { print "\nPress key to finish..."; - my $WAITKEY=; + my $WAITKEY = ; } 0; diff --git a/dev/build/makepack-dolibarrmodule.pl b/dev/build/makepack-dolibarrmodule.pl index 11c12c3ea89..653c26aca3e 100755 --- a/dev/build/makepack-dolibarrmodule.pl +++ b/dev/build/makepack-dolibarrmodule.pl @@ -5,223 +5,288 @@ # \author (c)2005-2014 Laurent Destailleur # \contributor (c)2017 Nicolas ZABOURI #---------------------------------------------------------------------------- +## no critic (InputOutput::ProhibitExplicitStdin,InputOutput::RequireBriefOpen) +use strict; +use warnings; use Cwd; use Term::ANSIColor; -$OWNER="ldestailleur"; -$GROUP="ldestailleur"; +$OWNER = "ldestailleur"; +$GROUP = "ldestailleur"; - -@LISTETARGET=("ZIP"); # Possible packages -%REQUIREMENTTARGET=( # Tool requirement for each package -"TGZ"=>"tar", -"ZIP"=>"7z" +@LISTETARGET = ("ZIP"); # Possible packages +%REQUIREMENTTARGET = ( # Tool requirement for each package + "TGZ" => "tar", + "ZIP" => "7z" ); -%ALTERNATEPATH=( -); - +%ALTERNATEPATH = (); use vars qw/ $REVISION $VERSION /; -$REVISION='1.0'; -$VERSION="3.5 (build $REVISION)"; - - +$REVISION = '1.0'; +$VERSION = "3.5 (build $REVISION)"; #------------------------------------------------------------------------------ # MAIN #------------------------------------------------------------------------------ -($DIR=$0) =~ s/([^\/\\]+)$//; ($PROG=$1) =~ s/\.([^\.]*)$//; $Extension=$1; -$DIR||='.'; $DIR =~ s/([^\/\\])[\\\/]+$/$1/; +( $DIR = $0 ) =~ s/([^\/\\]+)$//; +( $PROG = $1 ) =~ s/\.([^\.]*)$//; +$Extension = $1; +$DIR ||= '.'; +$DIR =~ s/([^\/\\])[\\\/]+$/$1/; # Detect OS type # -------------- -if ("$^O" =~ /linux/i || (-d "/etc" && -d "/var" && "$^O" !~ /cygwin/i)) { $OS='linux'; $CR=''; } -elsif (-d "/etc" && -d "/Users") { $OS='macosx'; $CR=''; } -elsif ("$^O" =~ /cygwin/i || "$^O" =~ /win32/i) { $OS='windows'; $CR="\r"; } -if (! $OS) { - print "$PROG.$Extension was not able to detect your OS.\n"; +if ( "$^O" =~ /linux/i || ( -d "/etc" && -d "/var" && "$^O" !~ /cygwin/i ) ) { + $OS = 'linux'; + $CR = ''; +} +elsif ( -d "/etc" && -d "/Users" ) { $OS = 'macosx'; $CR = ''; } +elsif ( "$^O" =~ /cygwin/i || "$^O" =~ /win32/i ) { + $OS = 'windows'; + $CR = "\r"; +} +if ( !$OS ) { + print "$PROG.$Extension was not able to detect your OS.\n"; print "Can't continue.\n"; print "$PROG.$Extension aborted.\n"; - sleep 2; + sleep 2; exit 1; } # Define buildroot # ---------------- -if ($OS =~ /linux/) { - $TEMP=$ENV{"TEMP"}||$ENV{"TMP"}||"/tmp"; +if ( $OS =~ /linux/ ) { + $TEMP = $ENV{"TEMP"} || $ENV{"TMP"} || "/tmp"; } -if ($OS =~ /macos/) { - $TEMP=$ENV{"TEMP"}||$ENV{"TMP"}||"/tmp"; +if ( $OS =~ /macos/ ) { + $TEMP = $ENV{"TEMP"} || $ENV{"TMP"} || "/tmp"; } -if ($OS =~ /windows/) { - $TEMP=$ENV{"TEMP"}||$ENV{"TMP"}||"c:/temp"; - $PROGPATH=$ENV{"ProgramFiles"}; +if ( $OS =~ /windows/ ) { + $TEMP = $ENV{"TEMP"} || $ENV{"TMP"} || "c:/temp"; + $PROGPATH = $ENV{"ProgramFiles"}; } -if (! $TEMP || ! -d $TEMP) { - print "Error: A temporary directory can not be find.\n"; - print "Check that TEMP or TMP environment variable is set correctly.\n"; +if ( !$TEMP || !-d $TEMP ) { + print "Error: A temporary directory can not be find.\n"; + print "Check that TEMP or TMP environment variable is set correctly.\n"; print "$PROG.$Extension aborted.\n"; - sleep 2; - exit 2; + sleep 2; + exit 2; } -$BUILDROOT="$TEMP/dolibarr-buildroot"; +$BUILDROOT = "$TEMP/dolibarr-buildroot"; +my $copyalreadydone = 0; +my $batch = 0; -my $copyalreadydone=0; -my $batch=0; - -for (0..@ARGV-1) { - if ($ARGV[$_] =~ /^-*target=(\w+)/i) { $target=$1; $batch=1; } - if ($ARGV[$_] =~ /^-*desti=(.+)/i) { $DESTI=$1; } - if ($ARGV[$_] =~ /^-*prefix=(.+)/i) { - $PREFIX=$1; - $FILENAMESNAPSHOT.="-".$PREFIX; - } +for ( 0 .. @ARGV - 1 ) { + if ( $ARGV[$_] =~ /^-*target=(\w+)/i ) { $target = $1; $batch = 1; } + if ( $ARGV[$_] =~ /^-*desti=(.+)/i ) { $DESTI = $1; } + if ( $ARGV[$_] =~ /^-*prefix=(.+)/i ) { + $PREFIX = $1; + $FILENAMESNAPSHOT .= "-" . $PREFIX; + } } -$SOURCE="$DIR/../.."; -$DESTI="$SOURCE/dev/build"; -if ($ENV{"DESTIMODULES"}) { $DESTI = $ENV{"DESTIMODULES"}; } # Force output dir if env DESTIMODULES is defined -$NEWDESTI=$DESTI; - +$SOURCE = "$DIR/../.."; +$DESTI = "$SOURCE/dev/build"; +if ( $ENV{"DESTIMODULES"} ) { + $DESTI = $ENV{"DESTIMODULES"}; +} # Force output dir if env DESTIMODULES is defined +$NEWDESTI = $DESTI; print "Makepack for modules version $VERSION\n"; print "Source directory: $SOURCE\n"; print "Target directory: $NEWDESTI\n"; - # Ask module -print "Enter name for your module (mymodule, mywonderfulmondule, ... or 'all') : "; -$PROJECTINPUT=; +print + "Enter name for your module (mymodule, mywonderfullmodule, ... or 'all') : "; +my $PROJECTINPUT = ; chomp($PROJECTINPUT); -print "Move to ".$DIR." directory.\n"; +print "Move to " . $DIR . " directory.\n"; chdir($DIR); - -my @PROJECTLIST=(); -if ($PROJECTINPUT eq "all") -{ - opendir(DIR, $DIR) || return; - local @rv = grep { /^makepack\-(.*)\.conf$/ } sort readdir(DIR); - closedir(DIR); - foreach my $xxx (0..@rv-1) { - if ($rv[$xxx] =~ /^makepack\-(.*)\.conf$/) - { - @PROJECTLIST[$xxx]=$1; - } - } +my @PROJECTLIST = (); +if ( $PROJECTINPUT eq "all" ) { + opendir( my $DIR, $DIR ) or return; + local @rv = grep { /^makepack\-(.*)\.conf$/ } sort readdir($DIR); + closedir($DIR); + foreach my $xxx ( 0 .. @rv - 1 ) { + if ( $rv[$xxx] =~ /^makepack\-(.*)\.conf$/ ) { + @PROJECTLIST[$xxx] = $1; + } + } } -else -{ - @PROJECTLIST=($PROJECTINPUT); +else { + @PROJECTLIST = ($PROJECTINPUT); } - # Loop on each projects foreach my $PROJECT (@PROJECTLIST) { - $PROJECTLC=lc($PROJECT); + $PROJECTLC = lc($PROJECT); - if (! -f "makepack-".$PROJECT.".conf") - { - print "Error: can't open conf file makepack-".$PROJECT.".conf\n"; + if ( !-f "makepack-" . $PROJECT . ".conf" ) { + print "Error: can't open conf file makepack-" . $PROJECT . ".conf\n"; print "\n"; print "For help on building a module package, see web page\n"; - print "http://wiki.dolibarr.org/index.php/Module_development#Create_a_package_to_distribute_and_install_your_module\n"; + print +"http://wiki.dolibarr.org/index.php/Module_development#Create_a_package_to_distribute_and_install_your_module\n"; print "makepack-dolibarrmodule.pl aborted.\n"; - sleep 2; - exit 2; + sleep 2; + exit 2; } # Get version $MAJOR, $MINOR and $BUILD - print "Version detected for module ".$PROJECT." in file ".$SOURCE."/htdocs/".$PROJECTLC."/core/modules/mod".ucfirst($PROJECT).".class.php"; - $result=open(IN,"<".$SOURCE."/htdocs/".$PROJECTLC."/core/modules/mod".ucfirst($PROJECT).".class.php"); - $custom=false; - if (! $result) { - $result=open(IN,"<".$SOURCE."/htdocs/custom/".$PROJECTLC."/core/modules/mod".ucfirst($PROJECT).".class.php"); - if (! $result) { - die "Error: Can't open descriptor file ".$SOURCE."/htdocs/(or /htdocs/custom/)".$PROJECTLC."/core/modules/mod".ucfirst($PROJECT).".class.php for reading.\n"; - }else{ - $custom = true; - } - } - while() - { - if ($_ =~ /this->version\s*=\s*'([\d\.]+)'/) { $PROJVERSION=$1; break; } - } - close IN; - print $PROJVERSION."\n"; + print "Version detected for module " + . $PROJECT + . " in file " + . $SOURCE + . "/htdocs/" + . $PROJECTLC + . "/core/modules/mod" + . ucfirst($PROJECT) + . ".class.php"; + $result = open( + my $IN, + "<", + $SOURCE + . "/htdocs/" + . $PROJECTLC + . "/core/modules/mod" + . ucfirst($PROJECT) + . ".class.php" + ); + $custom = false; + if ( !$result ) { + $result = open( + my $IN, + "<", + $SOURCE + . "/htdocs/custom/" + . $PROJECTLC + . "/core/modules/mod" + . ucfirst($PROJECT) + . ".class.php" + ); + if ( !$result ) { + die "Error: Can't open descriptor file " + . $SOURCE + . "/htdocs/(or /htdocs/custom/)" + . $PROJECTLC + . "/core/modules/mod" + . ucfirst($PROJECT) + . ".class.php for reading.\n"; + } + } + else { + $custom = true; + } + while (<$IN>) { + if ( $_ =~ /this->version\s*=\s*'([\d\.]+)'/ ) { + $PROJVERSION = $1; + break; + } + } + close $IN; + print $PROJVERSION. "\n"; - ($MAJOR,$MINOR,$BUILD)=split(/\./,$PROJVERSION,3); - if ($MINOR eq '') - { - print "Enter value for minor version for module ".$PROJECT.": "; - $MINOR=; - chomp($MINOR); + ( $MAJOR, $MINOR, $BUILD ) = split( /\./, $PROJVERSION, 3 ); + if ( $MINOR eq '' ) { + print "Enter value for minor version for module " . $PROJECT . ": "; + $MINOR = ; + chomp($MINOR); } - $FILENAME="$PROJECTLC"; - $FILENAMETGZ="module_$PROJECTLC-$MAJOR.$MINOR".($BUILD ne ''?".$BUILD":""); - $FILENAMEZIP="module_$PROJECTLC-$MAJOR.$MINOR".($BUILD ne ''?".$BUILD":""); - if (-d "/usr/src/redhat") { - # redhat - $RPMDIR="/usr/src/redhat"; - } - if (-d "/usr/src/RPM") { - # mandrake - $RPMDIR="/usr/src/RPM"; - } + $FILENAME = "$PROJECTLC"; + $FILENAMETGZ = + "module_$PROJECTLC-$MAJOR.$MINOR" . ( $BUILD ne '' ? ".$BUILD" : "" ); + $FILENAMEZIP = + "module_$PROJECTLC-$MAJOR.$MINOR" . ( $BUILD ne '' ? ".$BUILD" : "" ); + if ( -d "/usr/src/redhat" ) { + # redhat + $RPMDIR = "/usr/src/redhat"; + } + if ( -d "/usr/src/RPM" ) { + + # mandrake + $RPMDIR = "/usr/src/RPM"; + } # Choose package targets #----------------------- - $target="ZIP"; # Dolibarr modules are this format - $CHOOSEDTARGET{uc($target)}=1; - + $target = "ZIP"; # Dolibarr modules are this format + $CHOOSEDTARGET{ uc($target) } = 1; # Test if requirement is ok #-------------------------- - foreach my $target (keys %CHOOSEDTARGET) { - foreach my $req (split(/[,\s]/,$REQUIREMENTTARGET{$target})) { - # Test - print "Test requirement for target $target: Search '$req'... "; - $ret=`"$req" 2>&1`; - $coderetour=$?; $coderetour2=$coderetour>>8; - if ($coderetour != 0 && (($coderetour2 == 1 && $OS =~ /windows/ && $ret !~ /Usage/i) || ($coderetour2 == 127 && $OS !~ /windows/)) && $PROGPATH) { - # Not found error, we try in PROGPATH - $ret=`"$PROGPATH/$ALTERNATEPATH{$req}/$req\" 2>&1`; - $coderetour=$?; $coderetour2=$coderetour>>8; - $REQUIREMENTTARGET{$target}="$PROGPATH/$ALTERNATEPATH{$req}/$req"; - } + foreach my $target ( keys %CHOOSEDTARGET ) { + foreach my $req ( split( /[,\s]/, $REQUIREMENTTARGET{$target} ) ) { - if ($coderetour != 0 && (($coderetour2 == 1 && $OS =~ /windows/ && $ret !~ /Usage/i) || ($coderetour2 == 127 && $OS !~ /windows/))) { - # Not found error - print "Not found\nCan't build target $target. Requirement '$req' not found in PATH\n"; - $CHOOSEDTARGET{$target}=-1; - last; - } else { - # Pas erreur ou erreur autre que programme absent - print " Found ".$REQUIREMENTTARGET{$target}."\n"; - } - } + # Test + print "Test requirement for target $target: Search '$req'... "; + $ret = `"$req" 2>&1`; + $coderetour = $?; + $coderetour2 = $coderetour >> 8; + if ( + $coderetour != 0 + && ( + ( + $coderetour2 == 1 + && $OS =~ /windows/ + && $ret !~ /Usage/i + ) + || ( $coderetour2 == 127 && $OS !~ /windows/ ) + ) + && $PROGPATH + ) + { + # Not found error, we try in PROGPATH + $ret = `"$PROGPATH/$ALTERNATEPATH{$req}/$req\" 2>&1`; + $coderetour = $?; + $coderetour2 = $coderetour >> 8; + $REQUIREMENTTARGET{$target} = + "$PROGPATH/$ALTERNATEPATH{$req}/$req"; + } + + if ( + $coderetour != 0 + && ( + ( + $coderetour2 == 1 + && $OS =~ /windows/ + && $ret !~ /Usage/i + ) + || ( $coderetour2 == 127 && $OS !~ /windows/ ) + ) + ) + { + # Not found error + print +"Not found\nCan't build target $target. Requirement '$req' not found in PATH\n"; + $CHOOSEDTARGET{$target} = -1; + last; + } + else { + # Pas erreur ou erreur autre que programme absent + print " Found " . $REQUIREMENTTARGET{$target} . "\n"; + } + } } print "\n"; - # Check if there is at least on target to build + # Check if there is at least one target to build #---------------------------------------------- - $nboftargetok=0; - $nboftargetneedbuildroot=0; - $nboftargetneedcvs=0; - foreach my $target (keys %CHOOSEDTARGET) { - if ($CHOOSEDTARGET{$target} < 0) { next; } - if ($target ne 'EXE' && $target ne 'EXEDOLIWAMP') - { + $nboftargetok = 0; + $nboftargetneedbuildroot = 0; + $nboftargetneedcvs = 0; + foreach my $target ( keys %CHOOSEDTARGET ) { + if ( $CHOOSEDTARGET{$target} < 0 ) { next; } + if ( $target ne 'EXE' && $target ne 'EXEDOLIWAMP' ) { $nboftargetneedbuildroot++; } - if ($target eq 'SNAPSHOT') - { + if ( $target eq 'SNAPSHOT' ) { $nboftargetneedcvs++; } $nboftargetok++; @@ -229,178 +294,211 @@ foreach my $PROJECT (@PROJECTLIST) { if ($nboftargetok) { - # Update CVS if required - #----------------------- - if ($nboftargetneedcvs) - { - print "Go to directory $SOURCE\n"; - $olddir=getcwd(); - chdir("$SOURCE"); - print "Run cvs update -P -d\n"; - $ret=`cvs update -P -d 2>&1`; - chdir("$olddir"); + # Update CVS if required + #----------------------- + if ($nboftargetneedcvs) { + print "Go to directory $SOURCE\n"; + $olddir = getcwd(); + chdir("$SOURCE"); + print "Run cvs update -P -d\n"; + $ret = `cvs update -P -d 2>&1`; + chdir("$olddir"); } - # Update buildroot if required - #----------------------------- - if ($nboftargetneedbuildroot) - { - if (! $copyalreadydone) { - print "Delete directory $BUILDROOT\n"; - $ret=`rm -fr "$BUILDROOT"`; + # Update buildroot if required + #----------------------------- + if ($nboftargetneedbuildroot) { + if ( !$copyalreadydone ) { + print "Delete directory $BUILDROOT\n"; + $ret = `rm -fr "$BUILDROOT"`; - mkdir "$BUILDROOT"; - mkdir "$BUILDROOT/$PROJECTLC"; + mkdir "$BUILDROOT"; + mkdir "$BUILDROOT/$PROJECTLC"; - print "Now, we will copy all files declared in the makepack-".$PROJECT.".conf into the directory $BUILDROOT\n"; + print "Now, we will copy all files declared in the makepack-" + . $PROJECT + . ".conf into the directory $BUILDROOT\n"; - $result=open(IN,") - { - $entry=$_; + open( my $IN2, "<", "makepack-" . $PROJECT . ".conf" ) + or die "Error: Can't open conf file makepack-" + . $PROJECT + . ".conf for reading.\n"; + while (<$IN2>) { + $entry = $_; - if ($entry =~ /^#/) { next; } # Do not process comments + if ( $entry =~ /^#/ ) { next; } # Do not process comments $entry =~ s/\n//; - if ($entry =~ /^!(.*)$/) # Exclude so remove file/dir - { - print "Remove $BUILDROOT/$PROJECTLC/$1\n"; - $ret=`rm -fr "$BUILDROOT/$PROJECTLC/"$1`; - if ($? != 0) { die "Failed to delete a file to exclude declared into makepack-".$PROJECT.".conf file (Failed on the line ".$entry.")\n"; } - next; - } + if ( $entry =~ /^!(.*)$/ ) # Exclude so remove file/dir + { + print "Remove $BUILDROOT/$PROJECTLC/$1\n"; + $ret = `rm -fr "$BUILDROOT/$PROJECTLC/"$1`; + if ( $? != 0 ) { + die +"Failed to delete a file to exclude declared into makepack-" + . $PROJECT + . ".conf file (Failed on the line " + . $entry . ")\n"; + } + next; + } $entry =~ /^(.*)\/[^\/]+/; - print "Create directory $BUILDROOT/$PROJECTLC/$1\n"; - $ret=`mkdir -p "$BUILDROOT/$PROJECTLC/$1"`; - if ($entry !~ /version\-/) - { - print "Copy $SOURCE/$entry into $BUILDROOT/$PROJECTLC/$entry\n"; - $ret=`cp -pr "$SOURCE/$entry" "$BUILDROOT/$PROJECTLC/$entry"`; - if ($? != 0) { die "Failed to make copy of a file declared into makepack-".$PROJECT.".conf file (Failed on the line '".$entry."')\n"; } - } + print "Create directory $BUILDROOT/$PROJECTLC/$1\n"; + $ret = `mkdir -p "$BUILDROOT/$PROJECTLC/$1"`; + if ( $entry !~ /version\-/ ) { + print +"Copy $SOURCE/$entry into $BUILDROOT/$PROJECTLC/$entry\n"; + $ret = +`cp -pr "$SOURCE/$entry" "$BUILDROOT/$PROJECTLC/$entry"`; + if ( $? != 0 ) { + die +"Failed to make copy of a file declared into makepack-" + . $PROJECT + . ".conf file (Failed on the line '" + . $entry . "')\n"; + } + } } - close IN; + close $IN2; - @timearray=localtime(time()); - $fulldate=($timearray[5]+1900).'-'.($timearray[4]+1).'-'.$timearray[3].' '.$timearray[2].':'.$timearray[1]; - #open(VF,">$BUILDROOT/$PROJECTLC/dev/build/version-".$PROJECTLC.".txt"); - #print "Create version file $BUILDROOT/$PROJECTLC/dev/build/version-".$PROJECTLC.".txt with date ".$fulldate."\n"; - #$ret=`mkdir -p "$BUILDROOT/$PROJECTLC/dev/build"`; - #print VF "Version: ".$MAJOR.".".$MINOR.($BUILD ne ''?".$BUILD":"")."\n"; - #print VF "Build : ".$fulldate."\n"; - #close VF; - } - print "Clean $BUILDROOT\n"; - $ret=`rm -fr $BUILDROOT/$PROJECTLC/.cache`; - $ret=`rm -fr $BUILDROOT/$PROJECTLC/.git`; - $ret=`rm -fr $BUILDROOT/$PROJECTLC/.project`; - $ret=`rm -fr $BUILDROOT/$PROJECTLC/.settings`; - $ret=`rm -fr $BUILDROOT/$PROJECTLC/index.php`; - $ret=`rm -fr $BUILDROOT/$PROJECTLC/dev/build/html`; - $ret=`rm -fr $BUILDROOT/$PROJECTLC/documents`; - $ret=`rm -fr $BUILDROOT/$PROJECTLC/document`; - $ret=`rm -fr $BUILDROOT/$PROJECTLC/htdocs/conf/conf.php.mysql`; - $ret=`rm -fr $BUILDROOT/$PROJECTLC/htdocs/conf/conf.php.old`; - $ret=`rm -fr $BUILDROOT/$PROJECTLC/htdocs/conf/conf.php.postgres`; - $ret=`rm -fr $BUILDROOT/$PROJECTLC/htdocs/conf/conf*sav*`; - if ($custom) { - $ret=`cp -r $BUILDROOT/$PROJECTLC/htdocs/custom/* $BUILDROOT/$PROJECTLC/htdocs/.`; - } - $ret=`rm -fr $BUILDROOT/$PROJECTLC/htdocs/custom`; - $ret=`rm -fr $BUILDROOT/$PROJECTLC/htdocs/custom2`; - $ret=`rm -fr $BUILDROOT/$PROJECTLC/test`; - $ret=`rm -fr $BUILDROOT/$PROJECTLC/Thumbs.db $BUILDROOT/$PROJECTLC/*/Thumbs.db $BUILDROOT/$PROJECTLC/*/*/Thumbs.db $BUILDROOT/$PROJECTLC/*/*/*/Thumbs.db $BUILDROOT/$PROJECTLC/*/*/*/*/Thumbs.db`; - $ret=`rm -fr $BUILDROOT/$PROJECTLC/CVS* $BUILDROOT/$PROJECTLC/*/CVS* $BUILDROOT/$PROJECTLC/*/*/CVS* $BUILDROOT/$PROJECTLC/*/*/*/CVS* $BUILDROOT/$PROJECTLC/*/*/*/*/CVS* $BUILDROOT/$PROJECTLC/*/*/*/*/*/CVS*`; + @timearray = localtime( time() ); + $fulldate = + ( $timearray[5] + 1900 ) . '-' + . ( $timearray[4] + 1 ) . '-' + . $timearray[3] . ' ' + . $timearray[2] . ':' + . $timearray[1]; + +#open(VF,">$BUILDROOT/$PROJECTLC/dev/build/version-".$PROJECTLC.".txt"); +#print "Create version file $BUILDROOT/$PROJECTLC/dev/build/version-".$PROJECTLC.".txt with date ".$fulldate."\n"; +#$ret=`mkdir -p "$BUILDROOT/$PROJECTLC/dev/build"`; +#print VF "Version: ".$MAJOR.".".$MINOR.($BUILD ne ''?".$BUILD":"")."\n"; +#print VF "Build : ".$fulldate."\n"; +#close VF; + } + print "Clean $BUILDROOT\n"; + $ret = `rm -fr $BUILDROOT/$PROJECTLC/.cache`; + $ret = `rm -fr $BUILDROOT/$PROJECTLC/.git`; + $ret = `rm -fr $BUILDROOT/$PROJECTLC/.project`; + $ret = `rm -fr $BUILDROOT/$PROJECTLC/.settings`; + $ret = `rm -fr $BUILDROOT/$PROJECTLC/index.php`; + $ret = `rm -fr $BUILDROOT/$PROJECTLC/dev/build/html`; + $ret = `rm -fr $BUILDROOT/$PROJECTLC/documents`; + $ret = `rm -fr $BUILDROOT/$PROJECTLC/document`; + $ret = `rm -fr $BUILDROOT/$PROJECTLC/htdocs/conf/conf.php.mysql`; + $ret = `rm -fr $BUILDROOT/$PROJECTLC/htdocs/conf/conf.php.old`; + $ret = `rm -fr $BUILDROOT/$PROJECTLC/htdocs/conf/conf.php.postgres`; + $ret = `rm -fr $BUILDROOT/$PROJECTLC/htdocs/conf/conf*sav*`; + + if ($custom) { + $ret = +`cp -r $BUILDROOT/$PROJECTLC/htdocs/custom/* $BUILDROOT/$PROJECTLC/htdocs/.`; + } + $ret = `rm -fr $BUILDROOT/$PROJECTLC/htdocs/custom`; + $ret = `rm -fr $BUILDROOT/$PROJECTLC/htdocs/custom2`; + $ret = `rm -fr $BUILDROOT/$PROJECTLC/test`; + $ret = +`rm -fr $BUILDROOT/$PROJECTLC/Thumbs.db $BUILDROOT/$PROJECTLC/*/Thumbs.db $BUILDROOT/$PROJECTLC/*/*/Thumbs.db $BUILDROOT/$PROJECTLC/*/*/*/Thumbs.db $BUILDROOT/$PROJECTLC/*/*/*/*/Thumbs.db`; + $ret = +`rm -fr $BUILDROOT/$PROJECTLC/CVS* $BUILDROOT/$PROJECTLC/*/CVS* $BUILDROOT/$PROJECTLC/*/*/CVS* $BUILDROOT/$PROJECTLC/*/*/*/CVS* $BUILDROOT/$PROJECTLC/*/*/*/*/CVS* $BUILDROOT/$PROJECTLC/*/*/*/*/*/CVS*`; } - # Build package for each target - #------------------------------ - foreach my $target (keys %CHOOSEDTARGET) { - if ($CHOOSEDTARGET{$target} < 0) { next; } + # Build package for each target + #------------------------------ + foreach my $target ( keys %CHOOSEDTARGET ) { + if ( $CHOOSEDTARGET{$target} < 0 ) { next; } - print "\nBuild package for target $target\n"; + print "\nBuild package for target $target\n"; - if ($target eq 'TGZ') { - $NEWDESTI=$DESTI; - if (-d $DESTI.'/../modules') { $NEWDESTI=$DESTI.'/../modules'; } + if ( $target eq 'TGZ' ) { + $NEWDESTI = $DESTI; + if ( -d $DESTI . '/../modules' ) { + $NEWDESTI = $DESTI . '/../modules'; + } - print "Remove target $FILENAMETGZ.tgz...\n"; - unlink("$NEWDESTI/$FILENAMETGZ.tgz"); - print "Compress $BUILDROOT/* into $FILENAMETGZ.tgz...\n"; - $cmd="tar --exclude-vcs --exclude *.tgz --directory \"$BUILDROOT\" --mode=go-w --group=500 --owner=500 -czvf \"$FILENAMETGZ.tgz\" ."; - $ret=`$cmd`; - if ($OS =~ /windows/i) { - print "Move $FILENAMETGZ.tgz to $NEWDESTI/$FILENAMETGZ.tgz\n"; - $ret=`mv "$FILENAMETGZ.tgz" "$NEWDESTI/$FILENAMETGZ.tgz"`; - } - else - { - $ret=`mv "$FILENAMETGZ.tgz" "$NEWDESTI/$FILENAMETGZ.tgz"`; - } - next; - } + print "Remove target $FILENAMETGZ.tgz...\n"; + unlink("$NEWDESTI/$FILENAMETGZ.tgz"); + print "Compress $BUILDROOT/* into $FILENAMETGZ.tgz...\n"; + $cmd = +"tar --exclude-vcs --exclude *.tgz --directory \"$BUILDROOT\" --mode=go-w --group=500 --owner=500 -czvf \"$FILENAMETGZ.tgz\" ."; + $ret = `$cmd`; + if ( $OS =~ /windows/i ) { + print + "Move $FILENAMETGZ.tgz to $NEWDESTI/$FILENAMETGZ.tgz\n"; + $ret = `mv "$FILENAMETGZ.tgz" "$NEWDESTI/$FILENAMETGZ.tgz"`; + } + else { + $ret = `mv "$FILENAMETGZ.tgz" "$NEWDESTI/$FILENAMETGZ.tgz"`; + } + next; + } - if ($target eq 'ZIP') { - $NEWDESTI=$DESTI; - if (-d $DESTI.'/../modules') { $NEWDESTI=$DESTI.'/../modules'; } + if ( $target eq 'ZIP' ) { + $NEWDESTI = $DESTI; + if ( -d $DESTI . '/../modules' ) { + $NEWDESTI = $DESTI . '/../modules'; + } - print "Remove target $FILENAMEZIP.zip...\n"; - unlink "$NEWDESTI/$FILENAMEZIP.zip"; - print "Compress $FILENAMEZIP into $FILENAMEZIP.zip...\n"; + print "Remove target $FILENAMEZIP.zip...\n"; + unlink "$NEWDESTI/$FILENAMEZIP.zip"; + print "Compress $FILENAMEZIP into $FILENAMEZIP.zip...\n"; - print "Go to directory $BUILDROOT/$PROJECTLC\n"; - $olddir=getcwd(); - chdir("$BUILDROOT/$PROJECTLC"); - $cmd= "7z a -r -tzip -mx $BUILDROOT/$FILENAMEZIP.zip *"; - print $cmd."\n"; - $ret= `$cmd`; - chdir("$olddir"); + print "Go to directory $BUILDROOT/$PROJECTLC\n"; + $olddir = getcwd(); + chdir("$BUILDROOT/$PROJECTLC"); + $cmd = "7z a -r -tzip -mx $BUILDROOT/$FILENAMEZIP.zip *"; + print $cmd. "\n"; + $ret = `$cmd`; + chdir("$olddir"); - print "Move $FILENAMEZIP.zip to $NEWDESTI/$FILENAMEZIP.zip\n"; - $ret=`mv "$BUILDROOT/$FILENAMEZIP.zip" "$NEWDESTI/$FILENAMEZIP.zip"`; - $ret=`chown $OWNER:$GROUP "$NEWDESTI/$FILENAMEZIP.zip"`; - next; - } + print "Move $FILENAMEZIP.zip to $NEWDESTI/$FILENAMEZIP.zip\n"; + $ret = +`mv "$BUILDROOT/$FILENAMEZIP.zip" "$NEWDESTI/$FILENAMEZIP.zip"`; + $ret = `chown $OWNER:$GROUP "$NEWDESTI/$FILENAMEZIP.zip"`; + next; + } - if ($target eq 'EXE') { - $NEWDESTI=$DESTI; - if (-d $DESTI.'/../modules') { $NEWDESTI=$DESTI.'/../modules'; } + if ( $target eq 'EXE' ) { + $NEWDESTI = $DESTI; + if ( -d $DESTI . '/../modules' ) { + $NEWDESTI = $DESTI . '/../modules'; + } - print "Remove target $FILENAMEEXE.exe...\n"; - unlink "$NEWDESTI/$FILENAMEEXE.exe"; - print "Compress into $FILENAMEEXE.exe by $FILENAMEEXE.nsi...\n"; - $command="\"$REQUIREMENTTARGET{$target}\" /DMUI_VERSION_DOT=$MAJOR.$MINOR.$BUILD /X\"SetCompressor bzip2\" \"$SOURCE\\dev\\build\\exe\\$FILENAME.nsi\""; - print "$command\n"; - $ret=`$command`; - print "Move $FILENAMEEXE.exe to $NEWDESTI\n"; - rename("$SOURCE\\dev\\build\\exe\\$FILENAMEEXE.exe","$NEWDESTI/$FILENAMEEXE.exe"); - next; - } + print "Remove target $FILENAMEEXE.exe...\n"; + unlink "$NEWDESTI/$FILENAMEEXE.exe"; + print "Compress into $FILENAMEEXE.exe by $FILENAMEEXE.nsi...\n"; + $command = +"\"$REQUIREMENTTARGET{$target}\" /DMUI_VERSION_DOT=$MAJOR.$MINOR.$BUILD /X\"SetCompressor bzip2\" \"$SOURCE\\dev\\build\\exe\\$FILENAME.nsi\""; + print "$command\n"; + $ret = `$command`; + print "Move $FILENAMEEXE.exe to $NEWDESTI\n"; + rename( "$SOURCE\\dev\\build\\exe\\$FILENAMEEXE.exe", + "$NEWDESTI/$FILENAMEEXE.exe" ); + next; + } - } + } } print "\n----- Summary -----\n"; - foreach my $target (keys %CHOOSEDTARGET) { - if ($CHOOSEDTARGET{$target} < 0) { - print "Package $target not built (bad requirement).\n"; - } else { - print "Package $target built successfully in $NEWDESTI\n"; - } + foreach my $target ( keys %CHOOSEDTARGET ) { + if ( $CHOOSEDTARGET{$target} < 0 ) { + print "Package $target not built (bad requirement).\n"; + } + else { + print "Package $target built successfully in $NEWDESTI\n"; + } } - } - -if (! $batch) { - print "\nPress key to finish..."; - my $WAITKEY=; +if ( !$batch ) { + print "\nPress key to finish..."; + my $WAITKEY = ; } 0; diff --git a/dev/build/makepack-dolibarrtheme.pl b/dev/build/makepack-dolibarrtheme.pl index 7a6c31fb0f8..ff7adf1fad5 100755 --- a/dev/build/makepack-dolibarrtheme.pl +++ b/dev/build/makepack-dolibarrtheme.pl @@ -4,268 +4,294 @@ # \brief Script to build a theme Package for Dolibarr # \author (c)2005-2009 Laurent Destailleur #----------------------------------------------------------------------------- +## no critic (InputOutput::ProhibitExplicitStdin) +use strict; +use warnings; use Cwd; use Term::ANSIColor; -$PROJECT="dolibarr"; +$PROJECT = "dolibarr"; -@LISTETARGET=("TGZ"); # Possible packages -%REQUIREMENTTARGET=( # Tool requirement for each package -"TGZ"=>"tar", -"ZIP"=>"7z", -"RPM"=>"rpmbuild", -"DEB"=>"dpkg-buildpackage", -"EXE"=>"makensis.exe" +@LISTETARGET = ("TGZ"); # Possible packages +%REQUIREMENTTARGET = ( # Tool requirement for each package + "TGZ" => "tar", + "ZIP" => "7z", + "RPM" => "rpmbuild", + "DEB" => "dpkg-buildpackage", + "EXE" => "makensis.exe" ); -%ALTERNATEPATH=( -"7z"=>"7-ZIP", -"makensis.exe"=>"NSIS" +%ALTERNATEPATH = ( + "7z" => "7-ZIP", + "makensis.exe" => "NSIS" ); - use vars qw/ $REVISION $VERSION /; -$REVISION='1.11'; -$VERSION="1.0 (build $REVISION)"; - - +$REVISION = '1.11'; +$VERSION = "1.0 (build $REVISION)"; #------------------------------------------------------------------------------ # MAIN #------------------------------------------------------------------------------ -($DIR=$0) =~ s/([^\/\\]+)$//; ($PROG=$1) =~ s/\.([^\.]*)$//; $Extension=$1; -$DIR||='.'; $DIR =~ s/([^\/\\])[\\\/]+$/$1/; +( $DIR = $0 ) =~ s/([^\/\\]+)$//; +( $PROG = $1 ) =~ s/\.([^\.]*)$//; +$Extension = $1; +$DIR ||= '.'; +$DIR =~ s/([^\/\\])[\\\/]+$/$1/; # Detect OS type # -------------- -if ("$^O" =~ /linux/i || (-d "/etc" && -d "/var" && "$^O" !~ /cygwin/i)) { $OS='linux'; $CR=''; } -elsif (-d "/etc" && -d "/Users") { $OS='macosx'; $CR=''; } -elsif ("$^O" =~ /cygwin/i || "$^O" =~ /win32/i) { $OS='windows'; $CR="\r"; } -if (! $OS) { - print "$PROG.$Extension was not able to detect your OS.\n"; +if ( "$^O" =~ /linux/i || ( -d "/etc" && -d "/var" && "$^O" !~ /cygwin/i ) ) { + $OS = 'linux'; + $CR = ''; +} +elsif ( -d "/etc" && -d "/Users" ) { $OS = 'macosx'; $CR = ''; } +elsif ( "$^O" =~ /cygwin/i || "$^O" =~ /win32/i ) { + $OS = 'windows'; + $CR = "\r"; +} +if ( !$OS ) { + print "$PROG.$Extension was not able to detect your OS.\n"; print "Can't continue.\n"; print "$PROG.$Extension aborted.\n"; - sleep 2; + sleep 2; exit 1; } # Define buildroot # ---------------- -if ($OS =~ /linux/) { - $TEMP=$ENV{"TEMP"}||$ENV{"TMP"}||"/tmp"; +if ( $OS =~ /linux/ ) { + $TEMP = $ENV{"TEMP"} || $ENV{"TMP"} || "/tmp"; } -if ($OS =~ /macos/) { - $TEMP=$ENV{"TEMP"}||$ENV{"TMP"}||"/tmp"; +if ( $OS =~ /macos/ ) { + $TEMP = $ENV{"TEMP"} || $ENV{"TMP"} || "/tmp"; } -if ($OS =~ /windows/) { - $TEMP=$ENV{"TEMP"}||$ENV{"TMP"}||"c:/temp"; - $PROGPATH=$ENV{"ProgramFiles"}; +if ( $OS =~ /windows/ ) { + $TEMP = $ENV{"TEMP"} || $ENV{"TMP"} || "c:/temp"; + $PROGPATH = $ENV{"ProgramFiles"}; } -if (! $TEMP || ! -d $TEMP) { - print "Error: A temporary directory can not be find.\n"; - print "Check that TEMP or TMP environment variable is set correctly.\n"; +if ( !$TEMP || !-d $TEMP ) { + print "Error: A temporary directory can not be find.\n"; + print "Check that TEMP or TMP environment variable is set correctly.\n"; print "makepack-dolibarrtheme.pl aborted.\n"; - sleep 2; - exit 2; + sleep 2; + exit 2; } -$BUILDROOT="$TEMP/dolibarr-buildroot"; +$BUILDROOT = "$TEMP/dolibarr-buildroot"; - -my $copyalreadydone=0; -my $batch=0; +my $copyalreadydone = 0; +my $batch = 0; print "Makepack theme version $VERSION\n"; print "Enter name of theme(s) to package (separated with space): "; -$PROJECT=; +$PROJECT = ; chomp($PROJECT); -@PROJECTLIST=split(/ /,$PROJECT); -$PROJECT=join('',@PROJECTLIST); - +@PROJECTLIST = split( / /, $PROJECT ); +$PROJECT = join( '', @PROJECTLIST ); # Ask and set version $MAJOR and $MINOR print "Enter value for version: "; -$PROJVERSION=; +$PROJVERSION = ; chomp($PROJVERSION); -($MAJOR,$MINOR)=split(/\./,$PROJVERSION,2); -if ($MINOR eq '') -{ +( $MAJOR, $MINOR ) = split( /\./, $PROJVERSION, 2 ); +if ( $MINOR eq '' ) { print "Enter value for minor version: "; - $MINOR=; + $MINOR = ; chomp($MINOR); } +$FILENAME = "$PROJECT"; +$FILENAMETGZ = "theme_$PROJECT-$MAJOR.$MINOR"; +$FILENAMEZIP = "theme_$PROJECT-$MAJOR.$MINOR"; -$FILENAME="$PROJECT"; -$FILENAMETGZ="theme_$PROJECT-$MAJOR.$MINOR"; -$FILENAMEZIP="theme_$PROJECT-$MAJOR.$MINOR"; +if ( -d "/usr/src/redhat" ) { -if (-d "/usr/src/redhat") { - # redhat - $RPMDIR="/usr/src/redhat"; + # redhat + $RPMDIR = "/usr/src/redhat"; } -if (-d "/usr/src/RPM") { - # mandrake - $RPMDIR="/usr/src/RPM"; +if ( -d "/usr/src/RPM" ) { + + # mandrake + $RPMDIR = "/usr/src/RPM"; } -$SOURCE="$DIR/../.."; -$DESTI="$SOURCE/build"; - +$SOURCE = "$DIR/../.."; +$DESTI = "$SOURCE/build"; # Choose package targets #----------------------- -$target="ZIP"; # Packages uses this format +$target = "ZIP"; # Packages uses this format if ($target) { - $CHOOSEDTARGET{uc($target)}=1; + $CHOOSEDTARGET{ uc($target) } = 1; } else { - my $found=0; - my $NUM_SCRIPT; - while (! $found) { - my $cpt=0; - printf(" %d - %3s (%s)\n",$cpt,"All","Need ".join(",",values %REQUIREMENTTARGET)); - foreach my $target (@LISTETARGET) { - $cpt++; - printf(" %d - %3s (%s)\n",$cpt,$target,"Need ".$REQUIREMENTTARGET{$target}); - } + my $found = 0; + my $NUM_SCRIPT; + while ( !$found ) { + my $cpt = 0; + printf( " %d - %3s (%s)\n", + $cpt, "All", "Need " . join( ",", values %REQUIREMENTTARGET ) ); + foreach my $target (@LISTETARGET) { + $cpt++; + printf( " %d - %3s (%s)\n", + $cpt, $target, "Need " . $REQUIREMENTTARGET{$target} ); + } - # Are asked to select the file to move - print "Choose one package number or several separated with space: "; - $NUM_SCRIPT=; - chomp($NUM_SCRIPT); - if ($NUM_SCRIPT =~ s/-//g) { - # Do not do copy - $copyalreadydone=1; - } - if ($NUM_SCRIPT !~ /^[0-$cpt\s]+$/) - { - print "This is not a valid package number list.\n"; - $found = 0; - } - else - { - $found = 1; - } - } - print "\n"; - if ($NUM_SCRIPT) { - foreach my $num (split(/\s+/,$NUM_SCRIPT)) { - $CHOOSEDTARGET{$LISTETARGET[$num-1]}=1; - } - } - else { - foreach my $key (@LISTETARGET) { - $CHOOSEDTARGET{$key}=1; - } - } + # Are asked to select the file to move + print "Choose one package number or several separated with space: "; + $NUM_SCRIPT = ; + chomp($NUM_SCRIPT); + if ( $NUM_SCRIPT =~ s/-//g ) { + + # Do not do copy + $copyalreadydone = 1; + } + if ( $NUM_SCRIPT !~ /^[0-$cpt\s]+$/ ) { + print "This is not a valid package number list.\n"; + $found = 0; + } + else { + $found = 1; + } + } + print "\n"; + if ($NUM_SCRIPT) { + foreach my $num ( split( /\s+/, $NUM_SCRIPT ) ) { + $CHOOSEDTARGET{ $LISTETARGET[ $num - 1 ] } = 1; + } + } + else { + foreach my $key (@LISTETARGET) { + $CHOOSEDTARGET{$key} = 1; + } + } } # Test if requirement is ok #-------------------------- -foreach my $target (keys %CHOOSEDTARGET) { - foreach my $req (split(/[,\s]/,$REQUIREMENTTARGET{$target})) { - # Test - print "Test requirement for target $target: Search '$req'... "; - $ret=`"$req" 2>&1`; - $coderetour=$?; $coderetour2=$coderetour>>8; - if ($coderetour != 0 && (($coderetour2 == 1 && $OS =~ /windows/ && $ret !~ /Usage/i) || ($coderetour2 == 127 && $OS !~ /windows/)) && $PROGPATH) { - # Not found error, we try in PROGPATH - $ret=`"$PROGPATH/$ALTERNATEPATH{$req}/$req\" 2>&1`; - $coderetour=$?; $coderetour2=$coderetour>>8; - $REQUIREMENTTARGET{$target}="$PROGPATH/$ALTERNATEPATH{$req}/$req"; - } +foreach my $target ( keys %CHOOSEDTARGET ) { + foreach my $req ( split( /[,\s]/, $REQUIREMENTTARGET{$target} ) ) { - if ($coderetour != 0 && (($coderetour2 == 1 && $OS =~ /windows/ && $ret !~ /Usage/i) || ($coderetour2 == 127 && $OS !~ /windows/))) { - # Not found error - print "Not found\nCan't build target $target. Requirement '$req' not found in PATH\n"; - $CHOOSEDTARGET{$target}=-1; - last; - } else { - # Pas erreur ou erreur autre que programme absent - print " Found ".$REQUIREMENTTARGET{$target}."\n"; - } - } + # Test + print "Test requirement for target $target: Search '$req'... "; + $ret = `"$req" 2>&1`; + $coderetour = $?; + $coderetour2 = $coderetour >> 8; + if ( + $coderetour != 0 + && ( ( $coderetour2 == 1 && $OS =~ /windows/ && $ret !~ /Usage/i ) + || ( $coderetour2 == 127 && $OS !~ /windows/ ) ) + && $PROGPATH + ) + { + # Not found error, we try in PROGPATH + $ret = `"$PROGPATH/$ALTERNATEPATH{$req}/$req\" 2>&1`; + $coderetour = $?; + $coderetour2 = $coderetour >> 8; + $REQUIREMENTTARGET{$target} = "$PROGPATH/$ALTERNATEPATH{$req}/$req"; + } + + if ( + $coderetour != 0 + && ( ( $coderetour2 == 1 && $OS =~ /windows/ && $ret !~ /Usage/i ) + || ( $coderetour2 == 127 && $OS !~ /windows/ ) ) + ) + { + # Not found error + print +"Not found\nCan't build target $target. Requirement '$req' not found in PATH\n"; + $CHOOSEDTARGET{$target} = -1; + last; + } + else { + # Pas erreur ou erreur autre que programme absent + print " Found " . $REQUIREMENTTARGET{$target} . "\n"; + } + } } print "\n"; # Check if there is at least on target to build #---------------------------------------------- -$nboftargetok=0; -foreach my $target (keys %CHOOSEDTARGET) { - if ($CHOOSEDTARGET{$target} < 0) { next; } - $nboftargetok++; +$nboftargetok = 0; +foreach my $target ( keys %CHOOSEDTARGET ) { + if ( $CHOOSEDTARGET{$target} < 0 ) { next; } + $nboftargetok++; } if ($nboftargetok) { - # Update buildroot - #----------------- - if (! $copyalreadydone) { - print "Delete directory $BUILDROOT\n"; - $ret=`rm -fr "$BUILDROOT"`; - mkdir "$BUILDROOT"; - mkdir "$BUILDROOT/htdocs"; - mkdir "$BUILDROOT/htdocs/theme"; + # Update buildroot + #----------------- + if ( !$copyalreadydone ) { + print "Delete directory $BUILDROOT\n"; + $ret = `rm -fr "$BUILDROOT"`; + mkdir "$BUILDROOT"; + mkdir "$BUILDROOT/htdocs"; + mkdir "$BUILDROOT/htdocs/theme"; - print "Copy $SOURCE into $BUILDROOT\n"; - mkdir "$BUILDROOT"; - foreach my $tmp (@PROJECTLIST) - { - $ret=`cp -pr "$SOURCE/htdocs/theme/$tmp" "$BUILDROOT/htdocs/theme"`; + print "Copy $SOURCE into $BUILDROOT\n"; + mkdir "$BUILDROOT"; + foreach my $tmp (@PROJECTLIST) { + $ret = + `cp -pr "$SOURCE/htdocs/theme/$tmp" "$BUILDROOT/htdocs/theme"`; } - } - print "Clean $BUILDROOT\n"; - $ret=`rm -fr $BUILDROOT/htdocs/theme/$PROJECT/Thumbs.db $BUILDROOT/htdocs/theme/$PROJECT/*/Thumbs.db $BUILDROOT/htdocs/theme/$PROJECT/*/*/Thumbs.db $BUILDROOT/htdocs/theme/$PROJECT/*/*/*/Thumbs.db`; - $ret=`rm -fr $BUILDROOT/htdocs/theme/$PROJECT/CVS* $BUILDROOT/htdocs/theme/$PROJECT/*/CVS* $BUILDROOT/htdocs/theme/$PROJECT/*/*/CVS* $BUILDROOT/htdocs/theme/$PROJECT/*/*/*/CVS* $BUILDROOT/htdocs/theme/$PROJECT/*/*/*/*/CVS* $BUILDROOT/htdocs/theme/$PROJECT/*/*/*/*/*/CVS*`; + } + print "Clean $BUILDROOT\n"; + $ret = +`rm -fr $BUILDROOT/htdocs/theme/$PROJECT/Thumbs.db $BUILDROOT/htdocs/theme/$PROJECT/*/Thumbs.db $BUILDROOT/htdocs/theme/$PROJECT/*/*/Thumbs.db $BUILDROOT/htdocs/theme/$PROJECT/*/*/*/Thumbs.db`; + $ret = +`rm -fr $BUILDROOT/htdocs/theme/$PROJECT/CVS* $BUILDROOT/htdocs/theme/$PROJECT/*/CVS* $BUILDROOT/htdocs/theme/$PROJECT/*/*/CVS* $BUILDROOT/htdocs/theme/$PROJECT/*/*/*/CVS* $BUILDROOT/htdocs/theme/$PROJECT/*/*/*/*/CVS* $BUILDROOT/htdocs/theme/$PROJECT/*/*/*/*/*/CVS*`; + # Build package for each target + #------------------------------ + foreach my $target ( keys %CHOOSEDTARGET ) { + if ( $CHOOSEDTARGET{$target} < 0 ) { next; } - # Build package for each target - #------------------------------ - foreach my $target (keys %CHOOSEDTARGET) { - if ($CHOOSEDTARGET{$target} < 0) { next; } + print "\nBuild package for target $target\n"; - print "\nBuild package for target $target\n"; + if ( $target eq 'TGZ' ) { + unlink $FILENAMETGZ . tgz; + print "Compress $BUILDROOT/htdocs into $FILENAMETGZ.tgz...\n"; + $cmd = +"tar --exclude-vcs --exclude-from \"$DESTI/tgz/tar_exclude.txt\" --directory \"$BUILDROOT\" --mode=go-w --group=500 --owner=500 -czvf \"$FILENAMETGZ.tgz\" htdocs"; + $ret = `$cmd`; + if ( $OS =~ /windows/i ) { + print "Move $FILENAMETGZ.tgz to $DESTI/$FILENAMETGZ.tgz\n"; + $ret = `mv "$FILENAMETGZ.tgz" "$DESTI/$FILENAMETGZ.tgz"`; + } + next; + } - if ($target eq 'TGZ') { - unlink $FILENAMETGZ.tgz; - print "Compress $BUILDROOT/htdocs into $FILENAMETGZ.tgz...\n"; - $cmd="tar --exclude-vcs --exclude-from \"$DESTI/tgz/tar_exclude.txt\" --directory \"$BUILDROOT\" --mode=go-w --group=500 --owner=500 -czvf \"$FILENAMETGZ.tgz\" htdocs"; - $ret=`$cmd`; - if ($OS =~ /windows/i) { - print "Move $FILENAMETGZ.tgz to $DESTI/$FILENAMETGZ.tgz\n"; - $ret=`mv "$FILENAMETGZ.tgz" "$DESTI/$FILENAMETGZ.tgz"`; - } - next; - } - - if ($target eq 'ZIP') { - unlink $FILENAMEZIP.zip; - print "Compress $FILENAMETGZ into $FILENAMEZIP.zip...\n"; - chdir("$BUILDROOT"); - $ret=`7z a -r -tzip -mx $BUILDROOT/$FILENAMEZIP.zip htdocs`; + if ( $target eq 'ZIP' ) { + unlink $FILENAMEZIP . zip; + print "Compress $FILENAMETGZ into $FILENAMEZIP.zip...\n"; + chdir("$BUILDROOT"); + $ret = `7z a -r -tzip -mx $BUILDROOT/$FILENAMEZIP.zip htdocs`; print "Move $FILENAMEZIP.zip to $DESTI\n"; - $ret=`mv "$FILENAMEZIP.zip" "$DESTI/$FILENAMEZIP.zip"`; - next; - } + $ret = `mv "$FILENAMEZIP.zip" "$DESTI/$FILENAMEZIP.zip"`; + next; + } - } + } } print "\n----- Summary -----\n"; -foreach my $target (keys %CHOOSEDTARGET) { - if ($CHOOSEDTARGET{$target} < 0) { - print "Package $target not built (bad requirement).\n"; - } else { - print "Package $target built successfully in $DESTI\n"; - } +foreach my $target ( keys %CHOOSEDTARGET ) { + if ( $CHOOSEDTARGET{$target} < 0 ) { + print "Package $target not built (bad requirement).\n"; + } + else { + print "Package $target built successfully in $DESTI\n"; + } } -if (! $btach) { - print "\nPress key to finish..."; - my $WAITKEY=; +if ( !$btach ) { + print "\nPress key to finish..."; + my $WAITKEY = ; } 0; diff --git a/dev/build/perl/virtualmin/dolibarr.pl b/dev/build/perl/virtualmin/dolibarr.pl index 6ac8b9449cb..d17a3614778 100644 --- a/dev/build/perl/virtualmin/dolibarr.pl +++ b/dev/build/perl/virtualmin/dolibarr.pl @@ -1,7 +1,7 @@ #---------------------------------------------------------------------------- # \file dolibarr.pl # \brief Dolibarr script install for Virtualmin Pro -# \author (c)2009-2020 Regis Houssin +# \author (c)2009-2025 Regis Houssin #---------------------------------------------------------------------------- @@ -30,7 +30,15 @@ return "Regis Houssin"; # script_dolibarr_versions() sub script_dolibarr_versions { -return ( "14.0.5", "13.0.5", "12.0.5", "11.0.5", "10.0.7", "9.0.4", "8.0.6", "7.0.5" ); +return ( "22.0.3", "21.0.4", "20.0.4", "19.0.4", "18.0.8", "17.0.4", "16.0.5" ); +} + +sub script_dolibarr_version_desc +{ +local ($ver) = @_; +my ($major_ver) = $ver =~ /^(\d+)\..*/; +return $major_ver == 22 ? "$ver (Latest)" : + $major_ver == 18 ? "$ver (LTS)" : "$ver"; } sub script_dolibarr_release @@ -38,6 +46,11 @@ sub script_dolibarr_release return 2; # for mysqli fix } +sub script_dolibarr_testable +{ +return 1; +} + sub script_dolibarr_category { return "Commerce"; @@ -45,14 +58,23 @@ return "Commerce"; sub script_dolibarr_php_vers { -return ( 5 ); +return ( 7 ); } +sub script_dolibarr_php_vars +{ +return ( [ 'memory_limit', '128M', '+' ] ); +} + + sub script_dolibarr_php_modules { local ($d, $ver, $phpver, $opts) = @_; local ($dbtype, $dbname) = split(/_/, $opts->{'db'}, 2); -return $dbtype eq "mysql" ? ("mysql") : ("pgsql"); +local @modules = ("xml", "mbstring", "gd", "iconv", + "curl", "intl", "zip"); +push(@modules, ($dbtype eq "mysql" ? "mysql" : "pgsql")); +return @modules; } sub script_dolibarr_dbs @@ -61,34 +83,10 @@ local ($d, $ver) = @_; return ("mysql", "postgres"); } -# script_dolibarr_depends(&domain, version) -sub script_dolibarr_depends +sub script_dolibarr_php_fullver { -local ($d, $ver, $sinfo, $phpver) = @_; -local @rv; - -if ($ver >= 3.6) { - # Check for PHP 5.3+ - local $phpv = &get_php_version($phpver || 5, $d); - if (!$phpv) { - push(@rv, "Could not work out exact PHP version"); - } - elsif ($phpv < 5.3) { - push(@rv, "Dolibarr requires PHP version 5.3 or later"); - } - } -if ($ver >= 12.0) { - # Check for PHP 5.6+ - local $phpv = &get_php_version($phpver || 5, $d); - if (!$phpv) { - push(@rv, "Could not work out exact PHP version"); - } - elsif ($phpv < 5.6) { - push(@rv, "Dolibarr requires PHP version 5.6 or later"); - } - } - -return @rv; +local ($d, $ver, $sinfo) = @_; +return "7.1"; } # script_dolibarr_params(&domain, version, &upgrade-info) @@ -195,7 +193,7 @@ local $dbpass = $dbtype eq "mysql" ? &mysql_pass($d) : &postgres_pass($d, 1); local $dbphptype = $dbtype eq "mysql" && $version < 3.6 ? "mysql" : $dbtype eq "mysql" ? "mysqli" : "pgsql"; local $dbhost = &get_database_host($dbtype, $d); -local $dberr = &check_script_db_connection($dbtype, $dbname, $dbuser, $dbpass); +local $dberr = &check_script_db_connection($d, $dbtype, $dbname, $dbuser, $dbpass); return (0, "Database connection failed : $dberr") if ($dberr); # Extract tar file to temp dir and copy to target @@ -252,7 +250,7 @@ if ($upgrade) { ©_source_dest_as_domain_user($d, $oldcfile, $cfile); ©_source_dest_as_domain_user($d, $olddocdir, $docdir); ©_source_dest_as_domain_user($d, $oldaltdir, $altdir); - + # First page (Update database schema) local @params = ( [ "action", "upgrade" ], [ "versionfrom", $upgrade->{'version'} ], @@ -260,7 +258,7 @@ if ($upgrade) { ); local $err = &call_dolibarr_wizard_page(\@params, "upgrade", $d, $opts); return (-1, "Dolibarr wizard failed : $err") if ($err); - + # Second page (Migrate some data) local @params = ( [ "action", "upgrade" ], [ "versionfrom", $upgrade->{'version'} ], @@ -268,7 +266,7 @@ if ($upgrade) { ); local $err = &call_dolibarr_wizard_page(\@params, "upgrade2", $d, $opts); return (-1, "Dolibarr wizard failed : $err") if ($err); - + # Third page (Update version number) local @params = ( [ "action", "upgrade" ], [ "versionfrom", $upgrade->{'version'} ], @@ -278,12 +276,12 @@ if ($upgrade) { local $p = $ver >= 3.8 ? "step5" : "etape5"; local $err = &call_dolibarr_wizard_page(\@params, $p, $d, $opts); return (-1, "Dolibarr wizard failed : $err") if ($err); - + # Remove the installation directory. (deprecated) # local $dinstall = "$opts->{'dir'}/install"; # $dinstall =~ s/\/$//; # $out = &run_as_domain_user($d, "rm -rf ".quotemeta($dinstall)); - + } else { # First page (Db connection and config file creation) @@ -295,6 +293,9 @@ else { [ "db_name", $dbname ], [ "db_user", $dbuser ], [ "db_pass", $dbpass ], + [ "db_prefix", 'llx_' ], + [ "db_port", '3306' ], + [ "selectlang", 'en_US' ], [ "action", "set" ], [ "main_force_https", $opts->{'forcehttps'} ], [ "dolibarr_main_db_character_set", $charset ], @@ -305,13 +306,13 @@ else { local $p = $ver >= 3.8 ? "step1" : "etape1"; local $err = &call_dolibarr_wizard_page(\@params, $p, $d, $opts); return (-1, "Dolibarr wizard failed : $err") if ($err); - + # Second page (Populate database) local @params = ( [ "action", "set" ] ); local $p = $ver >= 3.8 ? "step2" : "etape2"; local $err = &call_dolibarr_wizard_page(\@params, $p, $d, $opts); return (-1, "Dolibarr wizard failed : $err") if ($err); - + # Third page (Add administrator account) local @params = ( [ "action", "set" ], [ "login", "admin" ], @@ -322,17 +323,17 @@ else { local $p = $ver >= 3.8 ? "step5" : "etape5"; local $err = &call_dolibarr_wizard_page(\@params, $p, $d, $opts); return (-1, "Dolibarr wizard failed : $err") if ($err); - + # Remove the installation directory (deprecated) # local $dinstall = "$opts->{'dir'}/install"; # $dinstall =~ s/\/$//; # $out = &run_as_domain_user($d, "rm -rf ".quotemeta($dinstall)); - + # Protect config file &set_permissions_as_domain_user($d, 0644, $cfile); &set_permissions_as_domain_user($d, 0755, $cfiledir); } - + # Return a URL for the user local $rp = $opts->{'dir'}; $rp =~ s/^$d->{'home'}\///; @@ -400,24 +401,32 @@ sub script_dolibarr_check_latest { local ($ver) = @_; local @vers = &osdn_package_versions("dolibarr", - $ver >= 14.0 ? "dolibarr\\-(12\\.0\\.[0-9\\.]+)\\.tgz" : - $ver >= 13.0 ? "dolibarr\\-(12\\.0\\.[0-9\\.]+)\\.tgz" : - $ver >= 12.0 ? "dolibarr\\-(12\\.0\\.[0-9\\.]+)\\.tgz" : - $ver >= 11.0 ? "dolibarr\\-(11\\.0\\.[0-9\\.]+)\\.tgz" : - $ver >= 10.0 ? "dolibarr\\-(10\\.0\\.[0-9\\.]+)\\.tgz" : - $ver >= 9.0 ? "dolibarr\\-(9\\.0\\.[0-9\\.]+)\\.tgz" : - $ver >= 8.0 ? "dolibarr\\-(8\\.0\\.[0-9\\.]+)\\.tgz" : - $ver >= 7.0 ? "dolibarr\\-(7\\.0\\.[0-9\\.]+)\\.tgz" : - $ver >= 6.0 ? "dolibarr\\-(6\\.0\\.[0-9\\.]+)\\.tgz" : - $ver >= 5.0 ? "dolibarr\\-(5\\.0\\.[0-9\\.]+)\\.tgz" : - $ver >= 4.0 ? "dolibarr\\-(4\\.0\\.[0-9\\.]+)\\.tgz" : - $ver >= 3.9 ? "dolibarr\\-(3\\.9\\.[0-9\\.]+)\\.tgz" : - $ver >= 3.8 ? "dolibarr\\-(3\\.8\\.[0-9\\.]+)\\.tgz" : - $ver >= 3.7 ? "dolibarr\\-(3\\.7\\.[0-9\\.]+)\\.tgz" : - $ver >= 3.6 ? "dolibarr\\-(3\\.6\\.[0-9\\.]+)\\.tgz" : - $ver >= 3.5 ? "dolibarr\\-(3\\.5\\.[0-9\\.]+)\\.tgz" : - $ver >= 2.9 ? "dolibarr\\-(2\\.9\\.[0-9\\.]+)\\.tgz" : - "dolibarr\\-(2\\.8\\.[0-9\\.]+)\\.tgz"); + $ver >= 22.0 ? "dolibarr\\-(22\\.0\\.[0-9\\.]+)\\.tgz" : + $ver >= 21.0 ? "dolibarr\\-(21\\.0\\.[0-9\\.]+)\\.tgz" : + $ver >= 20.0 ? "dolibarr\\-(20\\.0\\.[0-9\\.]+)\\.tgz" : + $ver >= 19.0 ? "dolibarr\\-(19\\.0\\.[0-9\\.]+)\\.tgz" : + $ver >= 18.0 ? "dolibarr\\-(18\\.0\\.[0-9\\.]+)\\.tgz" : + $ver >= 17.0 ? "dolibarr\\-(17\\.0\\.[0-9\\.]+)\\.tgz" : + $ver >= 16.0 ? "dolibarr\\-(16\\.0\\.[0-9\\.]+)\\.tgz" : + $ver >= 15.0 ? "dolibarr\\-(15\\.0\\.[0-9\\.]+)\\.tgz" : + $ver >= 14.0 ? "dolibarr\\-(14\\.0\\.[0-9\\.]+)\\.tgz" : + $ver >= 13.0 ? "dolibarr\\-(13\\.0\\.[0-9\\.]+)\\.tgz" : + $ver >= 12.0 ? "dolibarr\\-(12\\.0\\.[0-9\\.]+)\\.tgz" : + $ver >= 11.0 ? "dolibarr\\-(11\\.0\\.[0-9\\.]+)\\.tgz" : + $ver >= 10.0 ? "dolibarr\\-(10\\.0\\.[0-9\\.]+)\\.tgz" : + $ver >= 9.0 ? "dolibarr\\-(9\\.0\\.[0-9\\.]+)\\.tgz" : + $ver >= 8.0 ? "dolibarr\\-(8\\.0\\.[0-9\\.]+)\\.tgz" : + $ver >= 7.0 ? "dolibarr\\-(7\\.0\\.[0-9\\.]+)\\.tgz" : + $ver >= 6.0 ? "dolibarr\\-(6\\.0\\.[0-9\\.]+)\\.tgz" : + $ver >= 5.0 ? "dolibarr\\-(5\\.0\\.[0-9\\.]+)\\.tgz" : + $ver >= 4.0 ? "dolibarr\\-(4\\.0\\.[0-9\\.]+)\\.tgz" : + $ver >= 3.9 ? "dolibarr\\-(3\\.9\\.[0-9\\.]+)\\.tgz" : + $ver >= 3.8 ? "dolibarr\\-(3\\.8\\.[0-9\\.]+)\\.tgz" : + $ver >= 3.7 ? "dolibarr\\-(3\\.7\\.[0-9\\.]+)\\.tgz" : + $ver >= 3.6 ? "dolibarr\\-(3\\.6\\.[0-9\\.]+)\\.tgz" : + $ver >= 3.5 ? "dolibarr\\-(3\\.5\\.[0-9\\.]+)\\.tgz" : + $ver >= 2.9 ? "dolibarr\\-(2\\.9\\.[0-9\\.]+)\\.tgz" : + "dolibarr\\-(2\\.8\\.[0-9\\.]+)\\.tgz"); return "Failed to find versions" if (!@vers); return $ver eq $vers[0] ? undef : $vers[0]; } @@ -432,4 +441,26 @@ sub script_dolibarr_passmode return 2; } +sub script_dolibarr_db_conn_desc +{ +my $db_conn_desc = + { 'conf/conf.php' => + { + 'dbpass' => + { + 'func' => 'php_quotemeta', + 'func_params' => 1, + 'replace' => [ '\$dolibarr_main_db_pass\s*=' => + '$dolibarr_main_db_pass=\'$$sdbpass\';' ], + }, + 'dbuser' => + { + 'replace' => [ '\$dolibarr_main_db_user\s*=' => + '$dolibarr_main_db_user=\'$$sdbuser\';' ], + }, + } + }; +return $db_conn_desc; +} + 1; diff --git a/dev/build/phpstan/phpstan-baseline.neon b/dev/build/phpstan/phpstan-baseline.neon index 9ed766a4c54..8b5d79a5e40 100644 --- a/dev/build/phpstan/phpstan-baseline.neon +++ b/dev/build/phpstan/phpstan-baseline.neon @@ -663,7 +663,7 @@ parameters: - message: '#^If condition is always true\.$#' identifier: if.alwaysTrue - count: 4 + count: 3 path: ../../../htdocs/admin/mails_templates.php - @@ -1314,12 +1314,6 @@ parameters: count: 1 path: ../../../htdocs/barcode/printsheet.php - - - message: '#^Variable \$contextpage in empty\(\) always exists and is not falsy\.$#' - identifier: empty.variable - count: 1 - path: ../../../htdocs/blockedlog/admin/blockedlog_list.php - - message: '#^Strict comparison using \=\=\= between ''facture'' and ''facture'' will always evaluate to true\.$#' identifier: identical.alwaysTrue @@ -8346,12 +8340,6 @@ parameters: count: 1 path: ../../../htdocs/core/tpl/document_actions_post_headers.tpl.php - - - message: '#^Variable \$modulepart might not be defined\.$#' - identifier: variable.undefined - count: 3 - path: ../../../htdocs/core/tpl/document_actions_post_headers.tpl.php - - message: '#^Variable \$permissiontoadd might not be defined\.$#' identifier: variable.undefined @@ -8370,12 +8358,6 @@ parameters: count: 1 path: ../../../htdocs/core/tpl/document_actions_post_headers.tpl.php - - - message: '#^Variable \$upload_dir might not be defined\.$#' - identifier: variable.undefined - count: 1 - path: ../../../htdocs/core/tpl/document_actions_post_headers.tpl.php - - message: '#^Variable \$object might not be defined\.$#' identifier: variable.undefined @@ -8418,12 +8400,6 @@ parameters: count: 1 path: ../../../htdocs/core/tpl/filemanager.tpl.php - - - message: '#^Variable \$limit might not be defined\.$#' - identifier: variable.undefined - count: 2 - path: ../../../htdocs/core/tpl/list_print_total.tpl.php - - message: '#^Variable \$num might not be defined\.$#' identifier: variable.undefined @@ -9630,12 +9606,6 @@ parameters: count: 2 path: ../../../htdocs/fourn/facture/card.php - - - message: '#^Property CommonInvoice\:\:\$subtype \(int\|null\) does not accept string\.$#' - identifier: assign.propertyType - count: 1 - path: ../../../htdocs/fourn/facture/card.php - - message: '#^Property FactureFournisseur\:\:\$type \(int\) does not accept string\.$#' identifier: assign.propertyType @@ -11784,18 +11754,6 @@ parameters: count: 1 path: ../../../htdocs/product/class/api_products.class.php - - - message: '#^Method Products\:\:getPurchasePrices\(\) should return array\ but returns object\.$#' - identifier: return.type - count: 1 - path: ../../../htdocs/product/class/api_products.class.php - - - - message: '#^Parameter \#1 \$object of method Products\:\:_cleanObjectDatas\(\) expects object, array\\|int given\.$#' - identifier: argument.type - count: 1 - path: ../../../htdocs/product/class/api_products.class.php - - message: '#^Strict comparison using \=\=\= between 2 and 2 will always evaluate to true\.$#' identifier: identical.alwaysTrue @@ -12763,22 +12721,10 @@ parameters: path: ../../../htdocs/projet/card.php - - message: '#^Parameter \#1 \$object of method Projects\:\:_cleanObjectDatas\(\) expects object, string given\.$#' - identifier: argument.type - count: 1 - path: ../../../htdocs/projet/class/api_projects.class.php - - - - message: '#^Method Tasks\:\:getRoles\(\) should return array\ but returns list\\.$#' + message: '#^Method Projects\:\:getRoles\(\) should return array\ but returns list\\.$#' identifier: return.type count: 1 - path: ../../../htdocs/projet/class/api_tasks.class.php - - - - message: '#^Parameter \#1 \$object of method Tasks\:\:_cleanObjectDatas\(\) expects object, string given\.$#' - identifier: argument.type - count: 1 - path: ../../../htdocs/projet/class/api_tasks.class.php + path: ../../../htdocs/projet/class/api_projects.class.php - message: '#^Call to function method_exists\(\) with \$this\(Project\) and ''getLibStatut'' will always evaluate to true\.$#' diff --git a/dev/build/rpm/httpd-dolibarr.conf b/dev/build/rpm/httpd-dolibarr.conf index a33cf6db84b..41c1a404beb 100644 --- a/dev/build/rpm/httpd-dolibarr.conf +++ b/dev/build/rpm/httpd-dolibarr.conf @@ -30,27 +30,17 @@ Alias /dolibarr /usr/share/dolibarr/htdocs ErrorDocument 404 /public/error-404.php - # OPTIMIZE: To use gzip compressed files (for Dolibarr already compressed files). - # Note that constant MAIN_OPTIMIZE_SPEED must have a value with bit 0 set. - #AddType text/javascript .jgz - #AddEncoding gzip .jgz - - # OPTIMIZE: To use gzip compression (on the fly). - # Note that you must also enable the module mod_deflate. - # You can also set this with constant MAIN_OPTIMIZE_SPEED and bit 2 set. - #TODO - - # OPTIMIZE: To use cache on static pages (A259200 = 1 month). - # Note that you must also enable the module mod_expires. - #ExpiresActive On - #ExpiresByType image/x-icon A2592000 - #ExpiresByType image/gif A2592000 - #ExpiresByType image/png A2592000 - #ExpiresByType image/jpeg A2592000 - #ExpiresByType text/css A2592000 - #ExpiresByType text/javascript A2592000 - #ExpiresByType application/x-javascript A2592000 - #ExpiresByType application/javascript A2592000 + # OPTIMIZE: To use cache on static pages (A259200 = 1 month). + # Note that you must also enable the module mod_expires. + #ExpiresActive On + #ExpiresByType image/x-icon A2592000 + #ExpiresByType image/gif A2592000 + #ExpiresByType image/png A2592000 + #ExpiresByType image/jpeg A2592000 + #ExpiresByType text/css A2592000 + #ExpiresByType text/javascript A2592000 + #ExpiresByType application/x-javascript A2592000 + #ExpiresByType application/javascript A2592000 diff --git a/dev/resources/dbmodel/.gitignore b/dev/resources/dbmodel/.gitignore new file mode 100644 index 00000000000..6e466c45234 --- /dev/null +++ b/dev/resources/dbmodel/.gitignore @@ -0,0 +1,3 @@ +/dolibarr_schema.mwb.bak +/dolibarr_schema.mwb.sav.mwb +/dolibarr_schema.mwb.sav.mwb.bak diff --git a/dev/resources/dbmodel/dolibarr_schema.mwb b/dev/resources/dbmodel/dolibarr_schema.mwb index 37929762bab..54f5f065b39 100644 Binary files a/dev/resources/dbmodel/dolibarr_schema.mwb and b/dev/resources/dbmodel/dolibarr_schema.mwb differ diff --git a/dev/tools/api/macos/README b/dev/tools/api/macos/README new file mode 100644 index 00000000000..9cfe230aede --- /dev/null +++ b/dev/tools/api/macos/README @@ -0,0 +1,33 @@ +README (English) +-------------------------------- + +############## +RapidAPI +############## + +RapidAPI for Mac is a full-featured HTTP client that let's you test and describe the APIs you build or consume. +It has a beautiful native macOS interface to compose requests, inspect server responses, generate client code and export API definitions. + +https://paw.cloud/ + + + + @@@ + (@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@% + @@@@@@, @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@, + @@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + (@@@@@@( @@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@* @@@@@@@@@@@. /@@@@@@@@@@@@@@@@@( + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@* @@@@@@@@@@@. @@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@ /@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@ @@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@ @@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@# @@@@@@@@@@@ @@@@@@@@@@@@& + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@ @@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@ &@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@ &@@@@@@@@@@@@@@@@@@@@/ + @@@@@@@@@@@@@@@@@@@@@@@@@@& .@@@@@@@@@@@ &@@@@@@@@@@@@@@@@@@/ + (@@@@@@@@@@@@@@@@@@@@@@@ .@@@@@@@@@@@ &@@@@@@@@@@@@@@@( + @@@@@ @@@@@ .@@@@@@@@@@@ @@@@@@@@@@@* + + diff --git a/dev/tools/dolibarr-mysql2pgsql.pl b/dev/tools/dolibarr-mysql2pgsql.pl index 5e7d87d1234..2d04c872499 100755 --- a/dev/tools/dolibarr-mysql2pgsql.pl +++ b/dev/tools/dolibarr-mysql2pgsql.pl @@ -11,351 +11,386 @@ # Pour les cles autoincrement: rowid integer AUTO_INCREMENT PRIMARY KEY, # Mettre les index dans fichier.key.sql #------------------------------------------------------------------------------ +## no critic (InputOutput::ProhibitExplicitStdin,InputOutput::RequireBriefOpen) use Data::Dumper; use Getopt::Long; use strict; +use warnings; use vars qw/ $DIR $PROG $Extension $SOURCE $DESTI %filelist $stop /; # command line options -my( $opt_debug, $opt_help); +my ( $opt_debug, $opt_help ); + # general values -my ($out, $size); +my ( $out, $size ); + # variables for constructing pre-create-table entities -my $create_sql=''; # if empty we are not making a create statement -my $create_index=''; # if empty we are not making a create statement -my %enum_datafield=(); # holds enumeration choices -my (@column_values,$enum_column, $seq); -my $table=""; - - +my $create_sql = ''; # if empty we are not making a create statement +my $create_index = ''; # if empty we are not making a create statement +my %enum_datafield = (); # holds enumeration choices +my ( @column_values, $enum_column, $seq ); +my $table = ""; #------------------------------------------------------------------------------ # MAIN #------------------------------------------------------------------------------ -($DIR=$0) =~ s/([^\/\\]+)$//; ($PROG=$1) =~ s/\.([^\.]*)$//; $Extension=$1; -$DIR||='.'; $DIR =~ s/([^\/\\])[\\\/]+$/$1/; +( $DIR = $0 ) =~ s/([^\/\\]+)$//; +( $PROG = $1 ) =~ s/\.([^\.]*)$//; +$Extension = $1; +$DIR ||= '.'; +$DIR =~ s/([^\/\\])[\\\/]+$/$1/; -$SOURCE="$DIR/install/mysql/tables"; -$DESTI="$DIR/install/pgsql/tables"; +$SOURCE = "$DIR/install/mysql/tables"; +$DESTI = "$DIR/install/pgsql/tables"; # Recherche tous les fichiers .sql -opendir(DIR, $SOURCE); - foreach my $file (readdir(DIR)) { - if ($file =~ /\.sql$/ && -f "$SOURCE/$file") { - print "Found file $file\n"; - $filelist{$file}=1; - } - } -closedir(DIR); - +opendir( my $dir, $SOURCE ); +foreach my $file ( readdir($dir) ) { + if ( $file =~ /\.sql$/ && -f "$SOURCE/$file" ) { + print "Found file $file\n"; + $filelist{$file} = 1; + } +} +closedir($dir); # Boucle sur tous les fichiers de SOURCE #--------------------------------------- -foreach my $file (keys %filelist) { +foreach my $file ( keys %filelist ) { - $ARGV[0]="$SOURCE/$file"; - $ARGV[1]="$DESTI/$file"; + local $ARGV[0] = "$SOURCE/$file"; + local $ARGV[1] = "$DESTI/$file"; - print "Convert file $ARGV[0] into $ARGV[1]\n"; + print "Convert file $ARGV[0] into $ARGV[1]\n"; - # MySQL to PostgreSQL dump file converter - # - # For usage: perl mysql2pgsql.perl --help - # - # homepage: http://www.rot13.org/~dpavlin/projects.html - # 1999-12-15 DbP -- Dobrica Pavlinusic - # 1999-12-26 DbP don't make serial from auto_increment, create all manually - # (to set start value right) - # 2000-01-11 DbP now creates sequences with correct value - # 2000-04-25 DbP import into CVS (at cvs.linux.hr) - # 2001-01-29 tpo -- Tomas Pospisek : - # 1) make script comply to usage: - # 2) make script output to STDOUT instead of STERR - # 3) change verbosity behaveour - # 4) add debug option - # see rest of changelog at http://cvs.linux.hr/cvsweb.cgi/sql/mysql2pgsql - # 2003-12-16 jsp -- Joe Speigle : - # converts: s/\) *Type=MyISAM;/);/i, enum data type -> references, - # auto_increment->sequences - # 2004-01-13 jsp -- moved project to gborg; both the above declined ownership - # 2004-06-29 converts: year(4), year(2) - # homepage: gborg.postgresql.org + # MySQL to PostgreSQL dump file converter + # + # For usage: perl mysql2pgsql.perl --help + # + # homepage: http://www.rot13.org/~dpavlin/projects.html + # 1999-12-15 DbP -- Dobrica Pavlinusic + # 1999-12-26 DbP don't make serial from auto_increment, create all manually + # (to set start value right) + # 2000-01-11 DbP now creates sequences with correct value + # 2000-04-25 DbP import into CVS (at cvs.linux.hr) + # 2001-01-29 tpo -- Tomas Pospisek : + # 1) make script comply to usage: + # 2) make script output to STDOUT instead of STERR + # 3) change verbosity behaveour + # 4) add debug option + # see rest of changelog at http://cvs.linux.hr/cvsweb.cgi/sql/mysql2pgsql + # 2003-12-16 jsp -- Joe Speigle : + # converts: s/\) *Type=MyISAM;/);/i, enum data type -> references, + # auto_increment->sequences + # 2004-01-13 jsp -- moved project to gborg; both the above declined ownership + # 2004-06-29 converts: year(4), year(2) + # homepage: gborg.postgresql.org - GetOptions("debug", "help"); + GetOptions( "debug", "help" ); - my $DEBUG = $opt_debug || 0; - my $HELP = $opt_help || 0; + my $DEBUG = $opt_debug || 0; + my $HELP = $opt_help || 0; + if ( ($HELP) || !defined( $ARGV[0] ) || !defined( $ARGV[1] ) ) { + print +"Usage: perl $0 {--verbose|--help|--debug} mysql_dump_file.sql pg_dump_file.sql\n"; + print "\t* OPTIONS\n"; + print + "\t--verbose tees to pg_dump_file.sql and STDOUT during conversion\n"; + print "\t--debug does ?? \n"; + print "\t--help prints this message \n"; + print "\t* REQUIRED ARGUMENTS\n"; + if ( defined( $ARGV[0] ) ) { + print "\tmysql_dump_file.sql ($ARGV[0])\n"; + } + else { + print "\tmysql_dump_file.sql (undefined)\n"; + } + if ( defined( $ARGV[1] ) ) { + print "\tpg_dump_file.sql ($ARGV[1])\n"; + } + else { + print "\tpg_dump_file.sql (undefined)\n"; + } + exit 1; + } - if (($HELP) || ! defined($ARGV[0]) || ! defined($ARGV[1])) { - print "Usage: perl $0 {--verbose|--help|--debug} mysql_dump_file.sql pg_dump_file.sql\n"; - print "\t* OPTIONS\n"; - print "\t--verbose tees to pg_dump_file.sql and STDOUT during conversion\n"; - print "\t--debug does ?? \n"; - print "\t--help prints this message \n"; - print "\t* REQUIRED ARGUMENTS\n"; - if (defined ($ARGV[0])) { - print "\tmysql_dump_file.sql ($ARGV[0])\n"; - } else { - print "\tmysql_dump_file.sql (undefined)\n"; - } - if (defined ($ARGV[1])) { - print "\tpg_dump_file.sql ($ARGV[1])\n"; - } else { - print "\tpg_dump_file.sql (undefined)\n"; - } - exit 1; - } + open( my $in, "<", "$ARGV[0]" ) + || die "can't open mysql dump file $ARGV[0]"; + open( my $out, ">", "$ARGV[1]" ) || die "can't open pg dump file $ARGV[1]"; + print $out "-- Generated by $PROG\n"; + print $out "-- (c) 2004, PostgreSQL Inc.\n"; + print $out "-- (c) 2005, Laurent Destailleur.\n"; + print $out "\n"; - open(IN,"<$ARGV[0]") || die "can't open mysql dump file $ARGV[0]"; - open(OUT,">$ARGV[1]") || die "can't open pg dump file $ARGV[1]"; - print OUT "-- Generated by $PROG\n"; - print OUT "-- (c) 2004, PostgreSQL Inc.\n"; - print OUT "-- (c) 2005, Laurent Destailleur.\n"; - print OUT "\n"; + # Output for create table and create index + sub output_create { - # Output for create table and create index - sub output_create { - # If command ends with "xxx,);", we change to "xxx);" - $create_sql =~ s/,(\s*)\);/$1\);/m; - # If command ends with "xxx, -- yyy );", we change to "xxx -- yyy);" - $create_sql =~ s/,(\s*\-\-[^\)\n]*)(\s*)\);/$1\n\);/m; + # If command ends with "xxx,);", we change to "xxx);" + $create_sql =~ s/,(\s*)\);/$1\);/m; - print OUT $create_sql; - if ($create_index) { - print OUT "\n"; - print OUT $create_index; - } - } + # If command ends with "xxx, -- yyy );", we change to "xxx -- yyy);" + $create_sql =~ s/,(\s*\-\-[^\)\n]*)(\s*)\);/$1\n\);/m; - # Reset when moving from each "create table" to "insert" part of dump - sub reset_vars() { - $create_sql=""; - $create_index=""; - %enum_datafield=(); - $enum_column=''; - } + print $out $create_sql; + if ($create_index) { + print $out "\n"; + print $out $create_index; + } + return; + } + # Reset when moving from each "create table" to "insert" part of dump + sub reset_vars() { + $create_sql = ""; + $create_index = ""; + %enum_datafield = (); + $enum_column = ''; + return; + } - # Boucle sur contenu fichier source - #---------------------------------- - while() { + # Boucle sur contenu fichier source + #---------------------------------- + while (<$in>) { # comments or empty lines - if (/^-- \$Id/) { - $_ =~ s/\$//g; - print OUT $_; + if (/^-- \$Id/) { + $_ =~ s/\$//g; + print $out $_; next; } + # comments or empty lines - if (/^#/ || /^$/ || /^--/) { - print OUT $_; - next; - } - if (/^USE\s*([^;]*);/) { - print OUT "\\c ". $1; - next; - } - if ($create_sql ne "") { # we are inside create table statement so let's process datatypes + if ( /^#/ || /^$/ || /^--/ ) { + print $out $_; + next; + } + if (/^USE\s*([^;]*);/) { + print $out "\\c " . $1; + next; + } + if ( $create_sql ne "" ) + { # we are inside create table statement so let's process datatypes - if (/\);/i) { # end of create table sequence - $create_sql =~ s/,$//g; # strip last , inside create table - &output_create; - &reset_vars(); - next; - # LDR Added "innodb" and "engine" - } - elsif (/(ISAM|innodb)/i) { # end of create table sequence - s/\) *type=(MyISAM|innodb);/);/i; - s/\) *engine=(MyISAM|innodb);/);/i; - $create_sql =~ s/,$//g; # strip last , inside create table - $create_sql .= $_; - &output_create; - &reset_vars(); - next; - } + if (/\);/i) { # end of create table sequence + $create_sql =~ s/,$//g; # strip last , inside create table + &output_create; + &reset_vars(); + next; - # enum -> check - if (/([\w\"]*)\s+enum\s*\(((?:['"][\?\w]+['"]\s*,)+['"][\?\w]+['"])\)(.*)$/i) { - $enum_column=$1; - $enum_datafield{$enum_column}=$2; # 'abc','def', ... - my $suite=$3; - my $maxlength=0; - foreach my $enum (split(',',$enum_datafield{$enum_column})) { - $enum =~ s/[\"\']//g; - if ($maxlength serial - } elsif (/^[\s\t]*(\w*)\s*.*int.*auto_increment/i) { - $seq = qq~${table}_${1}_seq~; - s/[\s\t]*([a-zA-Z_0-9]*)\s*.*int.*auto_increment[^,]*/ $1 SERIAL PRIMARY KEY/ig; - $create_sql.=$_; - next; - # int type conversion - } elsif (/(\w*)int\(\d+\)/i) { - $size=$1; - $size =~ tr [A-Z] [a-z]; - if ($size eq "tiny" || $size eq "small") { - $out = "int2"; - } elsif ($size eq "big") { - $out = "int8"; - } else { - $out = "int4"; - } - s/\w*int\(\d+\)/$out/g; - } - # tinyint -> smallint - elsif (/tinyint/i) { - s/tinyint/smallint/g; - } + # LDR Added "innodb" and "engine" + } + elsif (/(ISAM|innodb)/i) { # end of create table sequence + s/\) *type=(MyISAM|innodb);/);/i; + s/\) *engine=(MyISAM|innodb);/);/i; + $create_sql =~ s/,$//g; # strip last , inside create table + $create_sql .= $_; + &output_create; + &reset_vars(); + next; + } - # nuke unsigned - s/(int\w+|smallint)\s+unsigned/$1/gi; + # enum -> check + if ( +/([\w\"]*)\s+enum\s*\(((?:['"][\?\w]+['"]\s*,)+['"][\?\w]+['"])\)(.*)$/i + ) + { + $enum_column = $1; + $enum_datafield{$enum_column} = $2; # 'abc','def', ... + my $suite = $3; + my $maxlength = 0; + foreach my $enum ( split( ',', $enum_datafield{$enum_column} ) ) + { + $enum =~ s/[\"\']//g; + if ( $maxlength < length($enum) ) { + $maxlength = length($enum); + } + } + $enum_datafield{$enum_column} =~ s/\"/\'/g; + $_ = +qq~ $enum_column CHAR($maxlength) CHECK ($enum_column IN ($enum_datafield{$enum_column})) $suite\n~; + # int, auto_increment -> serial + } + elsif (/^[\s\t]*(\w*)\s*.*int.*auto_increment/i) { + $seq = qq~${table}_${1}_seq~; +s/[\s\t]*([a-zA-Z_0-9]*)\s*.*int.*auto_increment[^,]*/ $1 SERIAL PRIMARY KEY/ig; + $create_sql .= $_; + next; - # blob -> text - s/\w*blob/text/gi; + # int type conversion + } + elsif (/(\w*)int\(\d+\)/i) { + $size = $1; + $size =~ tr [A-Z] [a-z]; + if ( $size eq "tiny" || $size eq "small" ) { + $out = "int2"; + } + elsif ( $size eq "big" ) { + $out = "int8"; + } + else { + $out = "int4"; + } + s/\w*int\(\d+\)/$out/g; + } - # tinytext/mediumtext -> text - s/tinytext/text/gi; - s/mediumtext/text/gi; + # tinyint -> smallint + elsif (/tinyint/i) { + s/tinyint/smallint/g; + } - # char -> varchar - # PostgreSQL would otherwise pad with spaces as opposed - # to MySQL! Your user interface may depend on this! - s/(\s+)char/${1}varchar/gi; + # nuke unsigned + s/(int\w+|smallint)\s+unsigned/$1/gi; - # nuke date representation (not supported in PostgreSQL) - s/datetime default '[^']+'/datetime/i; - s/date default '[^']+'/datetime/i; - s/time default '[^']+'/datetime/i; + # blob -> text + s/\w*blob/text/gi; - # change not null datetime field to null valid ones - # (to support remapping of "zero time" to null - s/datetime not null/datetime/i; - s/datetime/timestamp/i; + # tinytext/mediumtext -> text + s/tinytext/text/gi; + s/mediumtext/text/gi; - # nuke size of timestamp - s/timestamp\([^)]*\)/timestamp/i; + # char -> varchar + # PostgreSQL would otherwise pad with spaces as opposed + # to MySQL! Your user interface may depend on this! + s/(\s+)char/${1}varchar/gi; - # double -> numeric - s/^double/numeric/i; - s/(\s*)double/${1}numeric/i; + # nuke date representation (not supported in PostgreSQL) + s/datetime default '[^']+'/datetime/i; + s/date default '[^']+'/datetime/i; + s/time default '[^']+'/datetime/i; - # float -> numeric - s/^float/numeric/i; - s/(\s*)float/${1}numeric/i; + # change not null datetime field to null valid ones + # (to support remapping of "zero time" to null + s/datetime not null/datetime/i; + s/datetime/timestamp/i; - # unique key(field1,field2) - if (/unique key\s*\((\w+\s*,\s*\w+)\)/i) { - s/unique key\s*\((\w+\s*,\s*\w+)\)/UNIQUE\($1\)/i; - $create_sql.=$_; - next; - } - # unique index(field1,field2) - if (/unique index\s*\((\w+\s*,\s*\w+)\)/i) { - s/unique index\s*\((\w+\s*,\s*\w+)\)/UNIQUE\($1\)/i; - $create_sql.=$_; - next; - } + # nuke size of timestamp + s/timestamp\([^)]*\)/timestamp/i; - # unique key [name] (field) - if (/unique key\s*(\w*)\s*\((\w+)\)/i) { - s/unique key\s*(\w*)\s*\((\w+)\)/UNIQUE\($2\)/i; - my $idxname=($1?"$1":"idx_${table}_$2"); - $create_sql.=$_; - $create_index .= "CREATE INDEX $idxname ON $table ($2);\n"; - next; - } - # unique index [name] (field) - if (/unique index\s*(\w*)\s*\((\w+)\)/i) { - s/unique index\s*(\w*)\s*\((\w+)\)/UNIQUE\($2\)/i; - my $idxname=($1?"$1":"idx_${table}_$2"); - $create_sql.=$_; - $create_index .= "CREATE INDEX $idxname ON $table ($2);\n"; - next; - } - # unique (field) et unique (field1, field2 ...) - if (/unique\s*\(([\w,\s]+)\)/i) { - s/unique\s*\(([\w,\s]+)\)/UNIQUE\($1\)/i; - my $fieldlist="$1"; - my $idxname="idx_${table}_${fieldlist}"; - $idxname =~ s/\W/_/g; $idxname =~ tr/_/_/s; - $create_sql.=$_; - $create_index .= "CREATE INDEX $idxname ON $table ($fieldlist);\n"; - next; - } + # double -> numeric + s/^double/numeric/i; + s/(\s*)double/${1}numeric/i; - # index(field) - if (/index\s*(\w*)\s*\((\w+)\)/i) { - my $idxname=($1?"$1":"idx_${table}_$2"); - $create_index .= "CREATE INDEX $idxname ON $table ($2);\n"; - next; - } + # float -> numeric + s/^float/numeric/i; + s/(\s*)float/${1}numeric/i; - # primary key - if (/\bkey\b/i && !/^\s+primary key\s+/i) { - s/KEY(\s+)[^(]*(\s+)/$1 UNIQUE $2/i; # hack off name of the non-primary key - } + # unique key(field1,field2) + if (/unique key\s*\((\w+\s*,\s*\w+)\)/i) { + s/unique key\s*\((\w+\s*,\s*\w+)\)/UNIQUE\($1\)/i; + $create_sql .= $_; + next; + } - # key(xxx) - if (/key\s*\((\w+)\)/i) { - my $idxname="idx_${table}_$1"; - $create_index .= "CREATE INDEX $idxname ON $table ($1);\n"; - next; - } + # unique index(field1,field2) + if (/unique index\s*\((\w+\s*,\s*\w+)\)/i) { + s/unique index\s*\((\w+\s*,\s*\w+)\)/UNIQUE\($1\)/i; + $create_sql .= $_; + next; + } - # Quote column names - s/(^\s*)([^\s\-\(]+)(\s*)/$1"$2"$3/gi if (!/\bkey\b/i); + # unique key [name] (field) + if (/unique key\s*(\w*)\s*\((\w+)\)/i) { + s/unique key\s*(\w*)\s*\((\w+)\)/UNIQUE\($2\)/i; + my $idxname = ( $1 ? "$1" : "idx_${table}_$2" ); + $create_sql .= $_; + $create_index .= "CREATE INDEX $idxname ON $table ($2);\n"; + next; + } - # Remap columns with names of existing system attribute - if (/"oid"/i) { - s/"oid"/"_oid"/g; - print STDERR "WARNING: table $table uses column \"oid\" which is renamed to \"_oid\"\nYou should fix application manually! Press return to continue."; - my $wait=; - } - s/oid/_oid/i if (/key/i && /oid/i); # fix oid in key - $create_sql.=$_; - } # END of if ($create_sql ne "") i.e. were inside create table statement so processed datatypes - else { # not inside create table - #---- fix data in inserted data: (from MS world) - # FIX: disabled for now - if (00 && /insert into/i) { - s!\x96!-!g; # -- - s!\x93!"!g; # `` - s!\x94!"!g; # '' - s!\x85!... !g; # \ldots - s!\x92!`!g; - } + # unique index [name] (field) + if (/unique index\s*(\w*)\s*\((\w+)\)/i) { + s/unique index\s*(\w*)\s*\((\w+)\)/UNIQUE\($2\)/i; + my $idxname = ( $1 ? "$1" : "idx_${table}_$2" ); + $create_sql .= $_; + $create_index .= "CREATE INDEX $idxname ON $table ($2);\n"; + next; + } - # fix dates '0000-00-00 00:00:00' (should be null) - s/'0000-00-00 00:00:00'/null/gi; - s/'0000-00-00'/null/gi; - s/'00:00:00'/null/gi; - s/([12]\d\d\d)([01]\d)([0-3]\d)([0-2]\d)([0-6]\d)([0-6]\d)/'$1-$2-$3 $4:$5:$6'/; + # unique (field) et unique (field1, field2 ...) + if (/unique\s*\(([\w,\s]+)\)/i) { + s/unique\s*\(([\w,\s]+)\)/UNIQUE\($1\)/i; + my $fieldlist = "$1"; + my $idxname = "idx_${table}_${fieldlist}"; + $idxname =~ s/\W/_/g; + $idxname =~ tr/_/_/s; + $create_sql .= $_; + $create_index .= + "CREATE INDEX $idxname ON $table ($fieldlist);\n"; + next; + } - if (/create\s+table\s+(\w+)/i) { - $create_sql = $_; - /create\s*table\s*(\w+)/i; - $table=$1 if (defined($1)); - } else { - print OUT $_; - } - } # end of if inside create_table - } # END while() + # index(field) + if (/index\s*(\w*)\s*\((\w+)\)/i) { + my $idxname = ( $1 ? "$1" : "idx_${table}_$2" ); + $create_index .= "CREATE INDEX $idxname ON $table ($2);\n"; + next; + } - close IN; - close OUT; + # primary key + if ( /\bkey\b/i && !/^\s+primary key\s+/i ) { + s/KEY(\s+)[^(]*(\s+)/$1 UNIQUE $2/i + ; # hack off name of the non-primary key + } + + # key(xxx) + if (/key\s*\((\w+)\)/i) { + my $idxname = "idx_${table}_$1"; + $create_index .= "CREATE INDEX $idxname ON $table ($1);\n"; + next; + } + + # Quote column names + s/(^\s*)([^\s\-\(]+)(\s*)/$1"$2"$3/gi if ( !/\bkey\b/i ); + + # Remap columns with names of existing system attribute + if (/"oid"/i) { + s/"oid"/"_oid"/g; + print STDERR +"WARNING: table $table uses column \"oid\" which is renamed to \"_oid\"\nYou should fix application manually! Press return to continue."; + my $wait = ; + } + s/oid/_oid/i if ( /key/i && /oid/i ); # fix oid in key + $create_sql .= $_; + } # END of if ($create_sql ne "") i.e. were inside create table statement so processed datatypes + else { # not inside create table + #---- fix data in inserted data: (from MS world) + # FIX: disabled for now + if ( 00 && /insert into/i ) { + s!\x96!-!g; # -- + s!\x93!"!g; # `` + s!\x94!"!g; # '' + s!\x85!... !g; # \ldots + s!\x92!`!g; + } + + # fix dates '0000-00-00 00:00:00' (should be null) + s/'0000-00-00 00:00:00'/null/gi; + s/'0000-00-00'/null/gi; + s/'00:00:00'/null/gi; +s/([12]\d\d\d)([01]\d)([0-3]\d)([0-2]\d)([0-6]\d)([0-6]\d)/'$1-$2-$3 $4:$5:$6'/; + + if (/create\s+table\s+(\w+)/i) { + $create_sql = $_; + /create\s*table\s*(\w+)/i; + $table = $1 if ( defined($1) ); + } + else { + print $out $_; + } + } # end of if inside create_table + } # END while() + + close $in; + close $out; } print "\n"; -print "Build ".(scalar keys %filelist)." file(s).\n"; +print "Build " . ( scalar keys %filelist ) . " file(s).\n"; print "\n"; print "Press a key to finish...\n"; -$stop=; +$stop = ; 0; diff --git a/dev/tools/github_commits_byversion.sh b/dev/tools/github_commits_byversion.sh index 465bc30f9da..8903b4d94e5 100755 --- a/dev/tools/github_commits_byversion.sh +++ b/dev/tools/github_commits_byversion.sh @@ -12,19 +12,24 @@ cp "$0" /tmp/github_commits_perversion.sh TEMP_DIR=/tmp/git DOL_GIT="$TEMP_DIR/dolibarr" + if ! git rev-parse ; then - echo "Delete $TEMP_DIR" + echo "/tmp/git/dolibarr is not a git repo. Delete $TEMP_DIR" rm -fr "$TEMP_DIR" echo "Create '$TEMP_DIR' and cd to it" - mkdir "$TEMP_DIR" + mkdir -p "$TEMP_DIR" cd "$TEMP_DIR" || exit git clone https://github.com/Dolibarr/dolibarr.git cd "${DOL_GIT}" || exit else + echo "/tmp/git/dolibarr is a git repo." + mkdir -p ${DOL_GIT} if [ -r "${DOL_GIT}" ] ; then + echo git worktree remove "${DOL_GIT}" git worktree remove "${DOL_GIT}" rm -rf "${DOL_GIT}" >& /dev/null fi + echo git worktree add --force "${DOL_GIT}" develop git worktree add --force "${DOL_GIT}" develop cd "$DOL_GIT" || exit git pull @@ -36,7 +41,10 @@ Releases=("3.9" "4.0" "5.0" "6.0" "7.0" "8.0" "9.0" "10.0" "11.0" "12.0" "13.0" target_version=$(sed -n "s/.*define('DOL_VERSION',[[:space:]]*'\\([0-9]*\\.[0-9]*\\).*/\\1/p" htdocs/version.inc.php) # Default target version in case getting it from filefunc.inc failed -target_version=${target_version:=20.0} +target_version=${target_version:=23} + +echo "Last version to test target_version = $target_version"; + # Setup loop to append required versions target_major=${target_version%%.*} @@ -90,8 +98,9 @@ do done # Clean up git directory if it is a worktree -if [ "$(git rev-parse --git-dir)" != "$(git rev-parse --git-common-dir)" ] ; then - cd "$TEMP_DIR" || exit - git -C "$DOL_GIT" worktree remove "$DOL_GIT" -fi +#if [ "$(git rev-parse --git-dir)" != "$(git rev-parse --git-common-dir)" ] ; then +# cd "$TEMP_DIR" || exit +# git -C "$DOL_GIT" worktree remove "$DOL_GIT" +#fi + exit diff --git a/dev/tools/phan/baseline.txt b/dev/tools/phan/baseline.txt index fce5357e334..80342faec76 100644 --- a/dev/tools/phan/baseline.txt +++ b/dev/tools/phan/baseline.txt @@ -10,13 +10,13 @@ return [ // # Issue statistics: // PhanUndeclaredProperty : 420+ occurrences - // PhanTypeMismatchProperty : 100+ occurrences + // PhanTypeMismatchProperty : 95+ occurrences // PhanTypeMismatchArgument : 65+ occurrences // PhanUndeclaredGlobalVariable : 60+ occurrences // PhanTypeMismatchArgumentNullable : 20+ occurrences // PhanTypeInvalidDimOffset : 15+ occurrences // PhanTypeMismatchDimFetch : 10+ occurrences - // PhanUndeclaredMethod : 7 occurrences + // PhanUndeclaredMethod : 6 occurrences // PhanTypeArraySuspiciousNull : 5 occurrences // PhanTypeExpectedObjectPropAccess : 5 occurrences // PhanPluginDuplicateArrayKey : 4 occurrences @@ -76,7 +76,6 @@ return [ 'htdocs/core/ajax/selectobject.php' => ['PhanTypeMismatchArgumentNullable'], 'htdocs/core/class/CMailFile.class.php' => ['PhanTypeMismatchArgument'], 'htdocs/core/class/canvas.class.php' => ['PhanUndeclaredMethod'], - 'htdocs/core/class/ccountry.class.php' => ['PhanUndeclaredProperty'], 'htdocs/core/class/cgenericdic.class.php' => ['PhanUndeclaredProperty'], 'htdocs/core/class/commonobject.class.php' => ['PhanParamTooMany', 'PhanTypeMismatchArgument', 'PhanUndeclaredProperty'], 'htdocs/core/class/commonpeople.class.php' => ['PhanUndeclaredProperty'], @@ -146,7 +145,6 @@ return [ 'htdocs/core/tpl/objectline_view.tpl.php' => ['PhanUndeclaredProperty'], 'htdocs/core/tpl/passwordreset.tpl.php' => ['PhanUndeclaredGlobalVariable'], 'htdocs/core/tpl/resource_view.tpl.php' => ['PhanUndeclaredProperty'], - 'htdocs/core/triggers/interface_20_modWorkflow_WorkflowManager.class.php' => ['PhanUndeclaredProperty'], 'htdocs/core/triggers/interface_50_modAgenda_ActionsAuto.class.php' => ['PhanUndeclaredProperty'], 'htdocs/delivery/class/delivery.class.php' => ['PhanUndeclaredProperty'], 'htdocs/emailcollector/class/emailcollector.class.php' => ['PhanUndeclaredProperty'], @@ -165,7 +163,7 @@ return [ 'htdocs/fourn/class/fournisseur.commande.class.php' => ['PhanUndeclaredProperty'], 'htdocs/fourn/commande/card.php' => ['PhanUndeclaredProperty'], 'htdocs/fourn/facture/card-rec.php' => ['PhanTypeMismatchArgument', 'PhanUndeclaredGlobalVariable', 'PhanUndeclaredProperty'], - 'htdocs/fourn/facture/card.php' => ['PhanTypeMismatchArgument', 'PhanTypeMismatchProperty'], + 'htdocs/fourn/facture/card.php' => ['PhanTypeMismatchArgument'], 'htdocs/fourn/facture/rapport.php' => ['PhanTypeMismatchArgument'], 'htdocs/fourn/facture/tpl/linkedobjectblock.tpl.php' => ['PhanUndeclaredProperty'], 'htdocs/holiday/card_group.php' => ['PhanTypeMismatchArgument'], @@ -255,11 +253,9 @@ return [ 'htdocs/user/class/usergroup.class.php' => ['PhanUndeclaredProperty'], 'htdocs/variants/tpl/productattributevalueline_edit.tpl.php' => ['PhanUndeclaredProperty'], 'htdocs/variants/tpl/productattributevalueline_view.tpl.php' => ['PhanUndeclaredProperty'], - 'htdocs/viewimage.php' => ['PhanUndeclaredMethod'], 'htdocs/webhook/class/target.class.php' => ['PhanUndeclaredMethod'], 'htdocs/webhook/target_card.php' => ['PhanUndeclaredGlobalVariable'], 'htdocs/webportal/admin/setup.php' => ['PhanTypeMismatchArgument'], - 'htdocs/webportal/class/html.formcardwebportal.class.php' => ['PhanTypeMismatchArgument', 'PhanUndeclaredProperty'], 'htdocs/webportal/class/html.formlistwebportal.class.php' => ['PhanUndeclaredProperty'], 'htdocs/webportal/class/webportalpropal.class.php' => ['PhanUndeclaredProperty'], 'htdocs/webservices/server_project.php' => ['PhanUndeclaredProperty'], diff --git a/htdocs/accountancy/admin/fiscalyear_card.php b/htdocs/accountancy/admin/fiscalyear_card.php index f1e079d33e4..381659a1b08 100644 --- a/htdocs/accountancy/admin/fiscalyear_card.php +++ b/htdocs/accountancy/admin/fiscalyear_card.php @@ -46,7 +46,7 @@ $ref = GETPOST('ref', 'alpha'); $action = GETPOST('action', 'aZ09'); $confirm = GETPOST('confirm', 'alpha'); -$cancel = GETPOST('cancel', 'aZ09'); +$cancel = GETPOST('cancel', 'alpha'); $contextpage = GETPOST('contextpage', 'aZ') ? GETPOST('contextpage', 'aZ') : str_replace('_', '', basename(dirname(__FILE__)).basename(__FILE__, '.php')); // To manage different context of search $backtopage = GETPOST('backtopage', 'alpha'); // if not set, a default page will be used $backtopageforcancel = GETPOST('backtopageforcancel', 'alpha'); // if not set, $backtopage will be used diff --git a/htdocs/accountancy/bookkeeping/card.php b/htdocs/accountancy/bookkeeping/card.php index 463c5cee16d..d7fb7d2a3fd 100644 --- a/htdocs/accountancy/bookkeeping/card.php +++ b/htdocs/accountancy/bookkeeping/card.php @@ -50,7 +50,7 @@ require_once DOL_DOCUMENT_ROOT.'/accountancy/class/lettering.class.php'; $langs->loadLangs(array("accountancy", "bills", "compta")); $action = GETPOST('action', 'aZ09'); -$cancel = GETPOST('cancel', 'aZ09'); +$cancel = GETPOST('cancel', 'alpha'); $confirm = GETPOST('confirm', 'alpha'); $type = GETPOST('type', 'alpha'); @@ -682,7 +682,7 @@ if ($action == 'create') { print ''; - if ($action != 'editref') { + if ($action != 'editref' && empty($object->date_validation)) { print '
'; print $langs->trans('Ref'); print ''; if ($permissiontoadd && $numRefModel === 'mod_bookkeeping_neon') { print 'piece_num).'&mode='.urlencode((string) $mode).'">'.img_edit($langs->transnoentitiesnoconv('Edit'), 1).''; @@ -691,7 +691,7 @@ if ($action == 'create') { } print '
'; print ''; - if ($action == 'editref') { + if ($action == 'editref' && empty($object->date_validation)) { print '
'; if ($optioncss != '') { print ''; @@ -717,14 +717,14 @@ if ($action == 'create') { print ''; if ($action != 'editdocref') { print ''; - if ($permissiontoadd) { + if ($permissiontoadd && empty($object->date_validation)) { print 'piece_num).'&mode='.urlencode((string) $mode).'">'.img_edit($langs->transnoentitiesnoconv('Edit'), 1).''; } print ''; } print ''; print ''; - if ($action == 'editdocref') { + if ($action == 'editdocref' && empty($object->date_validation)) { print ''; if ($optioncss != '') { print ''; @@ -750,14 +750,14 @@ if ($action == 'create') { print ''; if ($action != 'editdate') { print ''; - if ($permissiontoadd) { + if ($permissiontoadd && empty($object->date_validation)) { print 'piece_num).'&mode='.urlencode((string) $mode).'">'.img_edit($langs->transnoentitiesnoconv('SetDate'), 1).''; } print ''; } print ''; print ''; - if ($action == 'editdate') { + if ($action == 'editdate' && empty($object->date_validation)) { print ''; if ($optioncss != '') { print ''; @@ -783,14 +783,14 @@ if ($action == 'create') { print ''; if ($action != 'editjournal') { print ''; - if ($permissiontoadd) { + if ($permissiontoadd && empty($object->date_validation)) { print 'piece_num).'&mode='.urlencode((string) $mode).'">'.img_edit($langs->transnoentitiesnoconv('Edit'), 1).''; } print ''; } print ''; print ''; - if ($action == 'editjournal') { + if ($action == 'editjournal' && empty($object->date_validation)) { print ''; if ($optioncss != '') { print ''; @@ -851,7 +851,7 @@ if ($action == 'create') { print ''; print '' . $langs->trans("DateExport") . ''; print ''; - print $object->date_export ? dol_print_date($object->date_export, 'dayhour') : ' '; + print $object->date_export ? img_picto($langs->trans("TransactionExportDesc"), 'fa-file-export', 'class="pictofixedwidth"').dol_print_date($object->date_export, 'dayhour') : ' '; print ''; print ''; @@ -859,7 +859,7 @@ if ($action == 'create') { print ''; print '' . $langs->trans("DateValidation") . ''; print ''; - print $object->date_validation ? dol_print_date($object->date_validation, 'dayhour') : ' '; + print $object->date_validation ? img_picto($langs->trans("TransactionBlockedLockedDesc"), 'fa-lock', 'class="pictofixedwidth"').dol_print_date($object->date_validation, 'dayhour') : ' '; print ''; print ''; @@ -966,21 +966,23 @@ if ($action == 'create') { // List of movements print load_fiche_titre($langs->trans("ListeMvts"), '', ''); - print ''; - if ($optioncss != '') { - print ''; + if (empty($object->date_validation)) { + print ''; + if ($optioncss != '') { + print ''; + } + print ''; + print '' . "\n"; + print '' . "\n"; + print '' . "\n"; + print '' . "\n"; + print '' . "\n"; + print '' . "\n"; + print '' . "\n"; + print '' . "\n"; + print ''; + print ''; } - print ''; - print ''."\n"; - print ''."\n"; - print ''."\n"; - print ''."\n"; - print ''."\n"; - print ''."\n"; - print ''."\n"; - print ''."\n"; - print ''; - print ''; if (count($object->linesmvt) > 0) { print '
'; @@ -1005,7 +1007,7 @@ if ($action == 'create') { print "\n"; // Add an empty line if there is not yet - if (!empty($object->linesmvt[0])) { + if (!empty($object->linesmvt[0]) && empty($object->date_validation)) { $tmpline = $object->linesmvt[0]; if (!empty($tmpline->numero_compte)) { $line = new BookKeepingLine($db); @@ -1017,7 +1019,7 @@ if ($action == 'create') { $total_debit += $line->debit; $total_credit += $line->credit; - if ($action == 'update' && $line->id == $id) { + if ($action == 'update' && $line->id == $id && empty($object->date_validation)) { print ''; print ''; print ''; @@ -1044,7 +1046,7 @@ if ($action == 'create') { print ''; print ''; print "\n"; - } elseif (empty($line->numero_compte) || (empty($line->debit) && empty($line->credit))) { + } elseif ((empty($line->numero_compte) || (empty($line->debit) && empty($line->credit))) && empty($object->date_validation)) { if (($action == "" || $action == 'add') && $permissiontoadd) { print ''; print ''; diff --git a/htdocs/accountancy/bookkeeping/listbyaccount.php b/htdocs/accountancy/bookkeeping/listbyaccount.php index 13474bd597f..9de49b6aa8c 100644 --- a/htdocs/accountancy/bookkeeping/listbyaccount.php +++ b/htdocs/accountancy/bookkeeping/listbyaccount.php @@ -1420,6 +1420,12 @@ while ($i < min($num, $limit)) { $object->piece_num = $line->piece_num; $object->ref = $line->ref; print $object->getNomUrl(1, '', 0, '', 1); + if (!empty($line->date_export)) { + print img_picto($langs->trans("DateExport").": ".dol_print_date($line->date_export, 'dayhour')." (".$langs->trans("TransactionExportDesc").")", 'fa-file-export', 'class="paddingleft pictofixedwidth"'); + } + if (!empty($line->date_validation)) { + print img_picto($langs->trans("DateValidation").": ".dol_print_date($line->date_validation, 'dayhour')." (".$langs->trans("TransactionBlockedLockedDesc").")", 'fa-lock', 'class="paddingleft pictofixedwidth"'); + } print ''; if (!$i) { $totalarray['nbfield']++; diff --git a/htdocs/accountancy/class/accountingjournal.class.php b/htdocs/accountancy/class/accountingjournal.class.php index 8f79952eeb2..1e0ead82205 100644 --- a/htdocs/accountancy/class/accountingjournal.class.php +++ b/htdocs/accountancy/class/accountingjournal.class.php @@ -759,7 +759,7 @@ class AccountingJournal extends CommonObject } // Build SQL - Customer invoices closed by discount - $sql = "SELECT f.rowid, f.ref, f.datef, f.fk_soc, f.total_ttc"; + $sql = "SELECT f.rowid, f.ref, f.datef, f.date_closing, f.fk_soc, f.total_ttc"; $sql .= " FROM ".MAIN_DB_PREFIX."facture as f"; $sql .= " WHERE f.entity IN (".getEntity('invoice', 0).')'; // We don't share object for accountancy, we use source object sharing $sql .= " AND f.fk_statut > 0"; @@ -770,19 +770,21 @@ class AccountingJournal extends CommonObject } $sql .= " AND f.close_code = 'discount_vat'"; if ($date_start && $date_end) { - $sql .= " AND f.datef >= '".$this->db->idate($date_start)."' AND f.datef <= '".$this->db->idate($date_end)."'"; + $sql .= " AND f.date_closing >= '".$this->db->idate($date_start)."' AND f.date_closing <= '".$this->db->idate($date_end)."'"; } if (getDolGlobalString('ACCOUNTING_DATE_START_BINDING')) { - $sql .= " AND f.datef >= '".$this->db->idate(getDolGlobalInt('ACCOUNTING_DATE_START_BINDING'))."'"; + $sql .= " AND f.date_closing >= '".$this->db->idate(getDolGlobalInt('ACCOUNTING_DATE_START_BINDING'))."'"; } if ($in_bookkeeping == 'already') { $sql .= " AND EXISTS (SELECT 1 FROM ".MAIN_DB_PREFIX."accounting_bookkeeping ab"; - $sql .= " WHERE ab.doc_type = 'customer_invoice' AND ab.fk_doc = f.rowid AND ab.piece_num LIKE 'OD-ESC-%')"; + $sql .= " WHERE ab.doc_type = 'customer_invoice' AND ab.fk_doc = f.rowid"; + $sql .= " AND ab.code_journal = '".$this->db->escape($this->code)."')"; } elseif ($in_bookkeeping == 'notyet') { $sql .= " AND NOT EXISTS (SELECT 1 FROM ".MAIN_DB_PREFIX."accounting_bookkeeping ab"; - $sql .= " WHERE ab.doc_type = 'customer_invoice' AND ab.fk_doc = f.rowid AND ab.piece_num LIKE 'OD-ESC-%')"; + $sql .= " WHERE ab.doc_type = 'customer_invoice' AND ab.fk_doc = f.rowid"; + $sql .= " AND ab.code_journal = '".$this->db->escape($this->code)."')"; } - $sql .= " ORDER BY f.datef"; + $sql .= " ORDER BY f.date_closing"; dol_syslog(__METHOD__, LOG_DEBUG); $resql = $this->db->query($sql); @@ -825,7 +827,8 @@ class AccountingJournal extends CommonObject } $bookkeeping_static = new BookKeeping($this->db); - $label_discount = $bookkeeping_static->accountingLabelForOperation($customer_static->getNomUrl(1, 'customer'), $invoice_static->ref, $langs->trans('DiscountGranted')); + $thirdpartyname = (string) $customer_static->name; + $label_discount = $bookkeeping_static->accountingLabelForOperation($thirdpartyname, $invoice_static->ref, $langs->trans('DiscountGranted')); // Distribution including VAT by rate $ttcByRate = array(); @@ -853,7 +856,9 @@ class AccountingJournal extends CommonObject 'blocks' => array(), ); - $docdate = $this->db->jdate($obj->datef); + $closingdate = !empty($obj->date_closing) ? $obj->date_closing : $obj->datef; + + $docdate = $this->db->jdate($closingdate); $docdate_fmt = dol_print_date($docdate, 'day'); $sumTTC = 0.0; @@ -1063,7 +1068,7 @@ class AccountingJournal extends CommonObject } // SQL - Supplier invoices closed by discount - $sql = "SELECT ff.rowid, ff.ref, ff.datef, ff.fk_soc, ff.total_ttc"; + $sql = "SELECT ff.rowid, ff.ref, ff.datef, ff.date_closing, ff.fk_soc, ff.total_ttc"; $sql .= " FROM ".MAIN_DB_PREFIX."facture_fourn as ff"; $sql .= " WHERE ff.entity IN (".getEntity('facture_fourn', 0).")"; // We don't share object for accountancy $sql .= " AND ff.fk_statut > 0"; @@ -1074,19 +1079,21 @@ class AccountingJournal extends CommonObject } $sql .= " AND ff.close_code = 'discount_vat'"; if ($date_start && $date_end) { - $sql .= " AND ff.datef >= '".$this->db->idate($date_start)."' AND ff.datef <= '".$this->db->idate($date_end)."'"; + $sql .= " AND ff.date_closing >= '".$this->db->idate($date_start)."' AND ff.date_closing <= '".$this->db->idate($date_end)."'"; } if (getDolGlobalString('ACCOUNTING_DATE_START_BINDING')) { - $sql .= " AND ff.datef >= '".$this->db->idate(getDolGlobalInt('ACCOUNTING_DATE_START_BINDING'))."'"; + $sql .= " AND ff.date_closing >= '".$this->db->idate(getDolGlobalInt('ACCOUNTING_DATE_START_BINDING'))."'"; } if ($in_bookkeeping == 'already') { $sql .= " AND EXISTS (SELECT 1 FROM ".MAIN_DB_PREFIX."accounting_bookkeeping ab"; - $sql .= " WHERE ab.doc_type = 'supplier_invoice' AND ab.fk_doc = ff.rowid AND ab.piece_num LIKE 'OD-ESC-FRS-%')"; + $sql .= " WHERE ab.doc_type = 'supplier_invoice' AND ab.fk_doc = ff.rowid"; + $sql .= " AND ab.code_journal = '".$this->db->escape($this->code)."')"; } elseif ($in_bookkeeping == 'notyet') { $sql .= " AND NOT EXISTS (SELECT 1 FROM ".MAIN_DB_PREFIX."accounting_bookkeeping ab"; - $sql .= " WHERE ab.doc_type = 'supplier_invoice' AND ab.fk_doc = ff.rowid AND ab.piece_num LIKE 'OD-ESC-FRS-%')"; + $sql .= " WHERE ab.doc_type = 'supplier_invoice' AND ab.fk_doc = ff.rowid"; + $sql .= " AND ab.code_journal = '".$this->db->escape($this->code)."')"; } - $sql .= " ORDER BY ff.datef"; + $sql .= " ORDER BY ff.date_closing"; dol_syslog(__METHOD__, LOG_DEBUG); $resql = $this->db->query($sql); @@ -1129,7 +1136,8 @@ class AccountingJournal extends CommonObject } $bookkeeping_static = new BookKeeping($this->db); - $label_discount = $bookkeeping_static->accountingLabelForOperation($supplier_static->getNomUrl(1, 'supplier'), $invoicesupplier_static->ref, $langs->trans('DiscountReceived')); + $thirdpartyname = (string) $supplier_static->name; + $label_discount = $bookkeeping_static->accountingLabelForOperation($thirdpartyname, $invoicesupplier_static->ref, $langs->trans('DiscountReceived')); // Distribution including VAT by rate $ttcByRate = array(); @@ -1157,7 +1165,9 @@ class AccountingJournal extends CommonObject 'blocks' => array(), ); - $docdate = $this->db->jdate($obj->datef); + $closingdate = !empty($obj->date_closing) ? $obj->date_closing : $obj->datef; + + $docdate = $this->db->jdate($closingdate); $docdate_fmt = dol_print_date($docdate, 'day'); $sumTTC = 0.0; diff --git a/htdocs/accountancy/journal/expensereportsjournal.php b/htdocs/accountancy/journal/expensereportsjournal.php index 892c80528d8..07dfa1e0032 100644 --- a/htdocs/accountancy/journal/expensereportsjournal.php +++ b/htdocs/accountancy/journal/expensereportsjournal.php @@ -3,7 +3,7 @@ * Copyright (C) 2007-2010 Jean Heimburger * Copyright (C) 2011 Juanjo Menent * Copyright (C) 2012 Regis Houssin - * Copyright (C) 2013-2024 Alexandre Spangaro + * Copyright (C) 2013-2025 Alexandre Spangaro * Copyright (C) 2013-2016 Olivier Geffroy * Copyright (C) 2013-2016 Florian Henry * Copyright (C) 2018-2025 Frédéric France @@ -30,15 +30,6 @@ * \brief Page with expense reports journal */ require '../../main.inc.php'; -require_once DOL_DOCUMENT_ROOT.'/core/lib/report.lib.php'; -require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php'; -require_once DOL_DOCUMENT_ROOT.'/core/lib/accounting.lib.php'; -require_once DOL_DOCUMENT_ROOT.'/accountancy/class/accountingjournal.class.php'; -require_once DOL_DOCUMENT_ROOT.'/accountancy/class/accountingaccount.class.php'; -require_once DOL_DOCUMENT_ROOT.'/expensereport/class/expensereport.class.php'; -require_once DOL_DOCUMENT_ROOT.'/user/class/user.class.php'; -require_once DOL_DOCUMENT_ROOT.'/accountancy/class/bookkeeping.class.php'; - /** * @var Conf $conf * @var DoliDB $db @@ -47,6 +38,14 @@ require_once DOL_DOCUMENT_ROOT.'/accountancy/class/bookkeeping.class.php'; * @var Translate $langs * @var User $user */ +require_once DOL_DOCUMENT_ROOT.'/core/lib/report.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/accounting.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/accountancy/class/accountingjournal.class.php'; +require_once DOL_DOCUMENT_ROOT.'/accountancy/class/accountingaccount.class.php'; +require_once DOL_DOCUMENT_ROOT.'/expensereport/class/expensereport.class.php'; +require_once DOL_DOCUMENT_ROOT.'/user/class/user.class.php'; +require_once DOL_DOCUMENT_ROOT.'/accountancy/class/bookkeeping.class.php'; // Load translation files required by the page $langs->loadLangs(array("commercial", "compta", "bills", "other", "accountancy", "trips", "errors")); @@ -356,6 +355,16 @@ if ($action == 'writebookkeeping' && !$error && $user->hasRight('accounting', 'b if (!$errorforline) { foreach ($tabht[$key] as $k => $mt) { if ($mt) { + if (empty($conf->cache['accountingaccountincurrententity'][$k])) { + $accountingaccount = new AccountingAccount($db); + $accountingaccount->fetch(0, $k, true); + $conf->cache['accountingaccountincurrententity'][$k] = $accountingaccount; + } else { + $accountingaccount = $conf->cache['accountingaccountincurrententity'][$k]; + } + + $account_label = $accountingaccount->label; + // get compte id and label if ($accountingaccount->fetch(0, $k, true)) { $bookkeeping = new BookKeeping($db); @@ -370,9 +379,10 @@ if ($action == 'writebookkeeping' && !$error && $user->hasRight('accounting', 'b $bookkeeping->subledger_label = ''; $bookkeeping->numero_compte = $k; - $bookkeeping->label_compte = $accountingaccount->label; + $bookkeeping->label_compte = $account_label; + + $bookkeeping->label_operation = $bookkeepingstatic->accountingLabelForOperation($userstatic->name, '', $account_label); - $bookkeeping->label_operation = $bookkeepingstatic->accountingLabelForOperation($userstatic->name, '', $accountingaccount->label); $bookkeeping->montant = $mt; $bookkeeping->sens = ($mt < 0) ? 'C' : 'D'; $bookkeeping->debit = ($mt > 0) ? $mt : 0; @@ -418,12 +428,12 @@ if ($action == 'writebookkeeping' && !$error && $user->hasRight('accounting', 'b foreach ($arrayofvat[$key] as $k => $mt) { if ($mt) { - if (empty($conf->cache['accountingaccountincurrententity'][$k])) { + if (empty($conf->cache['accountingaccountincurrententity_vat'][$k])) { $accountingaccount = new AccountingAccount($db); $accountingaccount->fetch(0, $k, true); - $conf->cache['accountingaccountincurrententity'][$k] = $accountingaccount; + $conf->cache['accountingaccountincurrententity_vat'][$k] = $accountingaccount; } else { - $accountingaccount = $conf->cache['accountingaccountincurrententity'][$k]; + $accountingaccount = $conf->cache['accountingaccountincurrententity_vat'][$k]; } $account_label = $accountingaccount->label; diff --git a/htdocs/accountancy/journal/purchasesjournal.php b/htdocs/accountancy/journal/purchasesjournal.php index 623c1b3d087..8299b2326ff 100644 --- a/htdocs/accountancy/journal/purchasesjournal.php +++ b/htdocs/accountancy/journal/purchasesjournal.php @@ -666,7 +666,14 @@ if ($action == 'writebookkeeping' && !$error && $user->hasRight('accounting', 'b foreach ($arrayofvat[$key] as $k => $mt) { if ($mt) { - $accountingaccount->fetch(0, $k, true); // TODO Use a cache for label + if (empty($conf->cache['accountingaccountincurrententity_vat'][$k])) { + $accountingaccount = new AccountingAccount($db); + $accountingaccount->fetch(0, $k, true); + $conf->cache['accountingaccountincurrententity_vat'][$k] = $accountingaccount; + } else { + $accountingaccount = $conf->cache['accountingaccountincurrententity_vat'][$k]; + } + $label_account = $accountingaccount->label; $bookkeeping = new BookKeeping($db); diff --git a/htdocs/accountancy/journal/sellsjournal.php b/htdocs/accountancy/journal/sellsjournal.php index 3cbd02dd329..45a7aea352d 100644 --- a/htdocs/accountancy/journal/sellsjournal.php +++ b/htdocs/accountancy/journal/sellsjournal.php @@ -4,7 +4,7 @@ * Copyright (C) 2011 Juanjo Menent * Copyright (C) 2012 Regis Houssin * Copyright (C) 2013 Christophe Battarel - * Copyright (C) 2013-2024 Alexandre Spangaro + * Copyright (C) 2013-2025 Alexandre Spangaro * Copyright (C) 2013-2016 Florian Henry * Copyright (C) 2013-2016 Olivier Geffroy * Copyright (C) 2014 Raphaël Doursenaud @@ -747,7 +747,14 @@ if ($action == 'writebookkeeping' && !$error && $user->hasRight('accounting', 'b foreach ($arrayofvat[$key] as $k => $mt) { if ($mt) { - $accountingaccount->fetch(0, $k, true); // TODO Use a cache for label + if (empty($conf->cache['accountingaccountincurrententity_vat'][$k])) { + $accountingaccount = new AccountingAccount($db); + $accountingaccount->fetch(0, $k, true); + $conf->cache['accountingaccountincurrententity_vat'][$k] = $accountingaccount; + } else { + $accountingaccount = $conf->cache['accountingaccountincurrententity_vat'][$k]; + } + $label_account = $accountingaccount->label; $bookkeeping = new BookKeeping($db); @@ -808,7 +815,14 @@ if ($action == 'writebookkeeping' && !$error && $user->hasRight('accounting', 'b if (isset($tabrevenuestamp[$key]) && is_array($tabrevenuestamp[$key])) { foreach ($tabrevenuestamp[$key] as $k => $mt) { if ($mt) { - $accountingaccount->fetch(0, $k, true); // TODO Use a cache for label + if (empty($conf->cache['accountingaccountincurrententity_rs'][$k])) { + $accountingaccount = new AccountingAccount($db); + $accountingaccount->fetch(0, $k, true); + $conf->cache['accountingaccountincurrententity_rs'][$k] = $accountingaccount; + } else { + $accountingaccount = $conf->cache['accountingaccountincurrententity_rs'][$k]; + } + $label_account = $accountingaccount->label; $bookkeeping = new BookKeeping($db); diff --git a/htdocs/adherents/admin/website.php b/htdocs/adherents/admin/website.php index 34fe35153ca..1c032c39207 100644 --- a/htdocs/adherents/admin/website.php +++ b/htdocs/adherents/admin/website.php @@ -93,7 +93,9 @@ if ($action == 'update') { $res = dolibarr_set_const($db, "MEMBER_MIN_AMOUNT", $minamount, 'chaine', 0, '', $conf->entity); $res = dolibarr_set_const($db, "MEMBER_COUNTERS_ARE_PUBLIC", $publiccounters, 'chaine', 0, '', $conf->entity); $res = dolibarr_set_const($db, "MEMBER_SKIP_TABLE", $showtable ? 0 : 1, 'chaine', 0, '', $conf->entity); // Logic is reversed for retrocompatibility: "skip -> show" - $res = dolibarr_set_const($db, "MEMBER_HIDE_VOTE_ALLOWED", $showvoteallowed ? 0 : 1, 'chaine', 0, '', $conf->entity); // Logic is reversed for retrocompatibility: "hide -> show" + if (GETPOSTISSET('MEMBER_HIDE_VOTE_ALLOWED')) { + $res = dolibarr_set_const($db, "MEMBER_HIDE_VOTE_ALLOWED", $showvoteallowed ? 0 : 1, 'chaine', 0, '', $conf->entity); // Logic is reversed for retrocompatibility: "hide -> show" + } $res = dolibarr_set_const($db, "MEMBER_NEWFORM_PAYONLINE", $payonline, 'chaine', 0, '', $conf->entity); if ($forcetype < 0) { $res = dolibarr_del_const($db, "MEMBER_NEWFORM_FORCETYPE", $conf->entity); @@ -225,6 +227,24 @@ if (getDolGlobalString('MEMBER_ENABLE_PUBLIC')) { print ''; print "\n"; + // Show the table of all available membership types. If not, show a form (as the default was for Dolibarr <=16.0) + $skiptable = getDolGlobalInt('MEMBER_SKIP_TABLE'); + print ''; + print $langs->trans("MembersShowMembershipTypesTable"); + print ''; + print $form->selectyesno("MEMBER_SHOW_TABLE", (int) !$skiptable, 1, false, 0, 1); // Reverse the logic "hide -> show" for retrocompatibility + print "\n"; + + // Show "vote allowed" setting for membership types + if (!$skiptable) { + $hidevoteallowed = getDolGlobalInt('MEMBER_HIDE_VOTE_ALLOWED'); + print ''; + print $langs->trans("MembersShowVotesAllowed"); + print ''; + print $form->selectyesno("MEMBER_SHOW_VOTE_ALLOWED", (int) !$hidevoteallowed, 1, false, 0, 1); // Reverse the logic "hide -> show" for retrocompatibility + print "\n"; + } + // Force Type $adht = new AdherentType($db); print ''; @@ -272,22 +292,6 @@ if (getDolGlobalString('MEMBER_ENABLE_PUBLIC')) { print $form->selectyesno("MEMBER_COUNTERS_ARE_PUBLIC", getDolGlobalInt('MEMBER_COUNTERS_ARE_PUBLIC'), 1, false, 0, 1); print "\n"; - // Show the table of all available membership types. If not, show a form (as the default was for Dolibarr <=16.0) - $skiptable = getDolGlobalInt('MEMBER_SKIP_TABLE'); - print ''; - print $langs->trans("MembersShowMembershipTypesTable"); - print ''; - print $form->selectyesno("MEMBER_SHOW_TABLE", (int) !$skiptable, 1, false, 0, 1); // Reverse the logic "hide -> show" for retrocompatibility - print "\n"; - - // Show "vote allowed" setting for membership types - $hidevoteallowed = getDolGlobalInt('MEMBER_HIDE_VOTE_ALLOWED'); - print ''; - print $langs->trans("MembersShowVotesAllowed"); - print ''; - print $form->selectyesno("MEMBER_SHOW_VOTE_ALLOWED", (int) !$hidevoteallowed, 1, false, 0, 1); // Reverse the logic "hide -> show" for retrocompatibility - print "\n"; - // Jump to an online payment page print ''; print $langs->trans("MEMBER_NEWFORM_PAYONLINE"); diff --git a/htdocs/adherents/agenda.php b/htdocs/adherents/agenda.php index be8e016718f..2cafcc707d0 100644 --- a/htdocs/adherents/agenda.php +++ b/htdocs/adherents/agenda.php @@ -30,12 +30,6 @@ // Load Dolibarr environment require '../main.inc.php'; -require_once DOL_DOCUMENT_ROOT.'/adherents/class/adherent.class.php'; -require_once DOL_DOCUMENT_ROOT.'/adherents/class/adherent_type.class.php'; -require_once DOL_DOCUMENT_ROOT.'/contact/class/contact.class.php'; -require_once DOL_DOCUMENT_ROOT.'/core/lib/member.lib.php'; -require_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php'; - /** * @var Conf $conf * @var DoliDB $db @@ -43,10 +37,33 @@ require_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php'; * @var Translate $langs * @var User $user */ +require_once DOL_DOCUMENT_ROOT.'/adherents/class/adherent.class.php'; +require_once DOL_DOCUMENT_ROOT.'/adherents/class/adherent_type.class.php'; +require_once DOL_DOCUMENT_ROOT.'/contact/class/contact.class.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/member.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php'; // Load translation files required by the page $langs->loadLangs(array('companies', 'members')); +$action = GETPOST('action', 'aZ09'); +$contextpage = GETPOST('contextpage', 'aZ') ? GETPOST('contextpage', 'aZ') : getDolDefaultContextPage(__FILE__); + +if (GETPOSTISARRAY('actioncode')) { + $actioncode = GETPOST('actioncode', 'array:alpha', 3); + if (!count($actioncode)) { + $actioncode = '0'; + } +} else { + $actioncode = GETPOST("actioncode", "alpha", 3) ? GETPOST("actioncode", "alpha", 3) : (GETPOST("actioncode") == '0' ? '0' : getDolGlobalString('AGENDA_DEFAULT_FILTER_TYPE_FOR_OBJECT')); +} + +$search_rowid = GETPOST('search_rowid'); +$search_agenda_label = GETPOST('search_agenda_label'); +$search_complete = GETPOST('search_complete'); +$search_dateevent_start = GETPOSTDATE('dateevent_start'); +$search_dateevent_end = GETPOSTDATE('dateevent_end'); + // Get Parameters $id = GETPOSTINT('id') ? GETPOSTINT('id') : GETPOSTINT('rowid'); @@ -66,20 +83,9 @@ if (!$sortfield) { $sortfield = 'a.datep,a.id'; } if (!$sortorder) { - $sortorder = 'DESC'; + $sortorder = 'DESC,DESC'; } -if (GETPOST('actioncode', 'array')) { - $actioncode = GETPOST('actioncode', 'array', 3); - if (!count($actioncode)) { - $actioncode = '0'; - } -} else { - $actioncode = GETPOST("actioncode", "alpha", 3) ? GETPOST("actioncode", "alpha", 3) : (GETPOST("actioncode") == '0' ? '0' : getDolGlobalString('AGENDA_DEFAULT_FILTER_TYPE_FOR_OBJECT')); -} -$search_rowid = GETPOST('search_rowid'); -$search_agenda_label = GETPOST('search_agenda_label'); - // Get object canvas (By default, this is not defined, so standard usage of dolibarr) $objcanvas = null; @@ -104,7 +110,7 @@ if ($result > 0) { * Actions */ -$parameters = array('id'=>$id, 'objcanvas'=>$objcanvas); +$parameters = array('id' => $id, 'objcanvas' => $objcanvas); $reshook = $hookmanager->executeHooks('doActions', $parameters, $object, $action); // Note that $action and $object may have been modified by some hooks if ($reshook < 0) { setEventMessages($hookmanager->error, $hookmanager->errors, 'errors'); @@ -122,6 +128,7 @@ if (empty($reshook)) { $actioncode = ''; $search_rowid = ''; $search_agenda_label = ''; + $search_complete = ''; } } @@ -172,6 +179,8 @@ if ($object->id > 0) { print '
'; + print '
'; + print dol_get_fiche_end(); @@ -187,7 +196,7 @@ if ($object->id > 0) { $newcardbutton .= dolGetButtonTitle($langs->trans('MessageListViewType'), '', 'fa fa-bars imgforviewmode', $messagingUrl, '', 2); if (isModEnabled('agenda')) { - $newcardbutton .= dolGetButtonTitle($langs->trans('AddAction'), '', 'fa fa-plus-circle', dolBuildUrl(DOL_URL_ROOT.'/comm/action/card.php', ['action' => 'create', 'backtopage' => dolBuildUrl($_SERVER['PHP_SELF'], ['id' => $object->id, 'origin' => 'member', 'originid' => $id])])); + $newcardbutton .= dolGetButtonTitle($langs->trans('AddAction'), '', 'fa fa-plus-circle', dolBuildUrl(DOL_URL_ROOT.'/comm/action/card.php', ['action' => 'create', 'origin' => 'member', 'originid' => $id, 'backtopage' => dolBuildUrl($_SERVER['PHP_SELF'], ['id' => $object->id, 'origin' => 'member', 'originid' => $id])])); } if (isModEnabled('agenda') && ($user->hasRight('agenda', 'myactions', 'read') || $user->hasRight('agenda', 'allactions', 'read'))) { @@ -195,12 +204,35 @@ if ($object->id > 0) { $param = '&id='.$id; if (!empty($contextpage) && $contextpage != $_SERVER["PHP_SELF"]) { - $param .= '&contextpage='.$contextpage; + $param .= '&contextpage='.urlencode($contextpage); } if ($limit > 0 && $limit != $conf->liste_limit) { - $param .= '&limit='.$limit; + $param .= '&limit='.((int) $limit); + } + if ($search_rowid) { + $param .= '&search_rowid='.urlencode($search_rowid); + } + if ($actioncode !== '' && $actioncode !== '-1') { + $param .= '&actioncode='.urlencode($actioncode); + } + if ($search_agenda_label) { + $param .= '&search_agenda_label='.urlencode($search_agenda_label); + } + if ($search_complete != '') { + $param .= '&search_complete='.urlencode($search_complete); + } + if ($search_dateevent_start != '') { + $param .= '&dateevent_startyear='.GETPOSTINT('dateevent_startyear'); + $param .= '&dateevent_startmonth='.GETPOSTINT('dateevent_startmonth'); + $param .= '&dateevent_startday='.GETPOSTINT('dateevent_startday'); + } + if ($search_dateevent_end != '') { + $param .= '&dateevent_endyear='.GETPOSTINT('dateevent_endyear'); + $param .= '&dateevent_endmonth='.GETPOSTINT('dateevent_endmonth'); + $param .= '&dateevent_endday='.GETPOSTINT('dateevent_endday'); } + // Try to know count of actioncomm from cache require_once DOL_DOCUMENT_ROOT.'/core/lib/memory.lib.php'; $cachekey = 'count_events_member_'.$object->id; $nbEvent = dol_getcache($cachekey); @@ -210,15 +242,16 @@ if ($object->id > 0) { $titlelist = $langs->trans("Actions").(is_numeric($nbEvent) ? '('.$nbEvent.')' : ''); } - print_barre_liste($titlelist, 0, $_SERVER["PHP_SELF"], '', $sortfield, $sortorder, '', 0, -1, '', 0, $newcardbutton, '', 0, 1, 0); + print_barre_liste($titlelist, 0, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, '', 0, -1, '', 0, $newcardbutton, '', 0, 1, 0); // List of all actions $filters = array(); $filters['search_agenda_label'] = $search_agenda_label; $filters['search_rowid'] = $search_rowid; + $filters['search_complete'] = $search_complete; // Can be 'na', '0', '100', '50' // TODO Replace this with same code than into list.php - show_actions_done($conf, $langs, $db, $object, null, 0, $actioncode, '', $filters, $sortfield, $sortorder); + show_actions_done($conf, $langs, $db, $object, null, 0, $actioncode, '', $filters, $sortfield, $sortorder, $object->module); } } diff --git a/htdocs/adherents/card.php b/htdocs/adherents/card.php index afe34edbf78..fd6a11a71e1 100644 --- a/htdocs/adherents/card.php +++ b/htdocs/adherents/card.php @@ -1914,16 +1914,16 @@ if (is_object($objcanvas) && $objcanvas->displayCanvasExists($action)) { // Login if (!getDolGlobalString('ADHERENT_LOGIN_NOT_REQUIRED')) { - print ''.$langs->trans("Login").' / '.$langs->trans("Id").''.dol_escape_htmltag($object->login).''; + print ''.$langs->trans("Login").' / '.$langs->trans("Id").''.dol_escape_htmltag($object->login).''; } // Type - print ''.$langs->trans("Type").''; + print ''.$langs->trans("Type").''; print ''.$adht->getNomUrl(1)."\n"; // Morphy print ''.$langs->trans("MemberNature").''; - print ''.$object->getmorphylib('', 1).''; + print ''.$object->getmorphylib('', 1).''; print ''; // Company @@ -1988,13 +1988,13 @@ if (is_object($objcanvas) && $objcanvas->displayCanvasExists($action)) { // Tags / Categories if (isModEnabled('category') && $user->hasRight('categorie', 'lire')) { print ''.$langs->trans("Categories").''; - print ''; + print ''; print $form->showCategories($object->id, Categorie::TYPE_MEMBER, 1); print ''; } // Birth Date - print ''.$langs->trans("DateOfBirth").''.dol_print_date($object->birth, 'day').''; + print ''.$langs->trans("DateOfBirth").''.dol_print_date($object->birth, 'day').''; // Default language if (getDolGlobalInt('MAIN_MULTILANGS')) { @@ -2095,7 +2095,7 @@ if (is_object($objcanvas) && $objcanvas->displayCanvasExists($action)) { // Send if (empty($user->socid)) { if (Adherent::STATUS_VALIDATED == $object->status) { - print ' $object->id, 'action' => 'presend', 'mode' => 'init'], true).'#formmailbeforetitle">'.$langs->trans('SendMail').''."\n"; + print dolGetButtonAction('', $langs->trans('SendMail'), 'email', dolBuildUrl($_SERVER["PHP_SELF"], ['id' => $object->id, 'action' => 'presend', 'mode' => 'init'], true).'#formmailbeforetitle', ''); } } diff --git a/htdocs/adherents/class/adherent.class.php b/htdocs/adherents/class/adherent.class.php index 2d8f2d8dd35..0d161ce3834 100644 --- a/htdocs/adherents/class/adherent.class.php +++ b/htdocs/adherents/class/adherent.class.php @@ -2164,6 +2164,7 @@ class Adherent extends CommonObject // Generate PDF (whatever is option MAIN_DISABLE_PDF_AUTOUPDATE) so we can include it into email //if (!getDolGlobalString('MAIN_DISABLE_PDF_AUTOUPDATE')) + $invoice->fetch($invoice->id); // Reload invoice object data $invoice->generateDocument($invoice->model_pdf, $outputlangs); } } diff --git a/htdocs/adherents/class/adherent_type.class.php b/htdocs/adherents/class/adherent_type.class.php index d04e3f77006..f03d1099c41 100644 --- a/htdocs/adherents/class/adherent_type.class.php +++ b/htdocs/adherents/class/adherent_type.class.php @@ -674,7 +674,7 @@ class AdherentType extends CommonObject * Return the array of all amounts per membership type id * * @param int $status Filter on status of type - * @return array Array of membership type + * @return array Array of membership type */ public function amountByType($status = null) { @@ -696,7 +696,7 @@ class AdherentType extends CommonObject while ($i < $nump) { $obj = $this->db->fetch_object($resql); - $amountbytype[$obj->rowid] = $obj->amount; + $amountbytype[$obj->rowid] = (float) $obj->amount; $i++; } } diff --git a/htdocs/adherents/class/api_members.class.php b/htdocs/adherents/class/api_members.class.php index 4f7224cc5ee..5125d008ffa 100644 --- a/htdocs/adherents/class/api_members.class.php +++ b/htdocs/adherents/class/api_members.class.php @@ -544,9 +544,12 @@ class Members extends DolibarrApi // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore /** * Clean sensible object datas + * @phpstan-template T * * @param Object $object Object to clean * @return Object Object with cleaned properties + * @phpstan-param T $object + * @phpstan-return T */ public function _cleanObjectDatas($object) { diff --git a/htdocs/adherents/class/api_memberstypes.class.php b/htdocs/adherents/class/api_memberstypes.class.php index 68d24732235..b42d7410dae 100644 --- a/htdocs/adherents/class/api_memberstypes.class.php +++ b/htdocs/adherents/class/api_memberstypes.class.php @@ -289,9 +289,12 @@ class MembersTypes extends DolibarrApi // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore /** * Clean sensible object datas + * @phpstan-template T * * @param Object $object Object to clean * @return Object Object with cleaned properties + * @phpstan-param T $object + * @phpstan-return T */ protected function _cleanObjectDatas($object) { diff --git a/htdocs/adherents/note.php b/htdocs/adherents/note.php index 817220acc66..69b8565babe 100644 --- a/htdocs/adherents/note.php +++ b/htdocs/adherents/note.php @@ -147,11 +147,11 @@ if (is_object($adht)) { } // Type - print ''.$langs->trans("Type").''; + print ''.$langs->trans("Type").''; print ''.$adht->getNomUrl(1)."\n"; // Morphy - print ''.$langs->trans("MemberNature").''; + print ''.$langs->trans("MemberNature").''; print ''.$object->getmorphylib('', 1).''; print ''; diff --git a/htdocs/adherents/partnership.php b/htdocs/adherents/partnership.php index a9873aa6a08..0ecb764e7fd 100644 --- a/htdocs/adherents/partnership.php +++ b/htdocs/adherents/partnership.php @@ -50,7 +50,7 @@ $id = GETPOSTINT('rowid') ? GETPOSTINT('rowid') : GETPOSTINT('id'); $ref = GETPOST('ref', 'alpha'); $action = GETPOST('action', 'aZ09'); $confirm = GETPOST('confirm', 'alpha'); -$cancel = GETPOST('cancel', 'aZ09'); +$cancel = GETPOST('cancel', 'alpha'); $contextpage = GETPOST('contextpage', 'aZ') ? GETPOST('contextpage', 'aZ') : 'partnershipcard'; // To manage different context of search $backtopage = GETPOST('backtopage', 'alpha'); $backtopageforcancel = GETPOST('backtopageforcancel', 'alpha'); diff --git a/htdocs/adherents/stats/byproperties.php b/htdocs/adherents/stats/byproperties.php index c383045835f..ac6dcf77df6 100644 --- a/htdocs/adherents/stats/byproperties.php +++ b/htdocs/adherents/stats/byproperties.php @@ -26,9 +26,6 @@ // Load Dolibarr environment require '../../main.inc.php'; -require_once DOL_DOCUMENT_ROOT.'/core/lib/member.lib.php'; -require_once DOL_DOCUMENT_ROOT.'/adherents/class/adherent.class.php'; - /** * @var Conf $conf * @var DoliDB $db @@ -36,6 +33,8 @@ require_once DOL_DOCUMENT_ROOT.'/adherents/class/adherent.class.php'; * @var Translate $langs * @var User $user */ +require_once DOL_DOCUMENT_ROOT.'/core/lib/member.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/adherents/class/adherent.class.php'; $graphwidth = 700; $mapratio = 0.5; @@ -191,9 +190,9 @@ foreach ($data as $val) { print ''.$memberstatic->getmorphylib($val['label']).''; print ''.$nb.''; print ''.$nbactive.''; - print ''.dol_print_date($val['lastdate'], 'dayhour').''; + print ''.dol_print_date($val['lastdate'], 'dayhour', 'auto', null, false, 1).''; print ''.$nbsubscriptions.''; - print ''.dol_print_date($val['lastsubscriptiondate'], 'dayhour').''; + print ''.dol_print_date($val['lastsubscriptiondate'], 'dayhour', 'auto', null, false, 1).''; print ''; } diff --git a/htdocs/adherents/stats/geo.php b/htdocs/adherents/stats/geo.php index 6e327200032..dd46030985f 100644 --- a/htdocs/adherents/stats/geo.php +++ b/htdocs/adherents/stats/geo.php @@ -26,11 +26,6 @@ // Load Dolibarr environment require '../../main.inc.php'; -require_once DOL_DOCUMENT_ROOT.'/core/lib/member.lib.php'; -require_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php'; -require_once DOL_DOCUMENT_ROOT.'/core/class/dolgraph.class.php'; -require_once DOL_DOCUMENT_ROOT.'/adherents/class/adherent.class.php'; - /** * @var Conf $conf * @var DoliDB $db @@ -38,12 +33,16 @@ require_once DOL_DOCUMENT_ROOT.'/adherents/class/adherent.class.php'; * @var Translate $langs * @var User $user */ +require_once DOL_DOCUMENT_ROOT.'/core/lib/member.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/class/dolgraph.class.php'; +require_once DOL_DOCUMENT_ROOT.'/adherents/class/adherent.class.php'; $graphwidth = DolGraph::getDefaultGraphSizeForStats('width', '700'); $mapratio = 0.5; $graphheight = round($graphwidth * $mapratio); -$mode = GETPOST('mode') ? GETPOST('mode') : ''; +$mode = GETPOST('mode'); // Security check @@ -54,7 +53,7 @@ if ($user->socid > 0) { restrictedArea($user, 'adherent', '', '', 'cotisation'); $year = (int) dol_print_date(dol_now('gmt'), "%Y", 'gmt'); -$startyear = $year - (!getDolGlobalString('MAIN_STATS_GRAPHS_SHOW_N_YEARS') ? 2 : max(1, min(10, getDolGlobalString('MAIN_STATS_GRAPHS_SHOW_N_YEARS')))); +$startyear = $year - (getDolGlobalString('MAIN_STATS_GRAPHS_SHOW_N_YEARS') ? max(1, min(10, getDolGlobalString('MAIN_STATS_GRAPHS_SHOW_N_YEARS'))) : 2); $endyear = $year; // Load translation files required by the page @@ -322,8 +321,8 @@ if ($mode) { print ''.$val['label2'].''; } print ''.$val['nb'].''; - print ''.dol_print_date($val['lastdate'], 'dayhour').''; - print ''.dol_print_date($val['lastsubscriptiondate'], 'dayhour').''; + print ''.dol_print_date($val['lastdate'], 'dayhour', 'auto', null, false, 1).''; + print ''.dol_print_date($val['lastsubscriptiondate'], 'dayhour', 'auto', null, false, 1).''; print ''; } diff --git a/htdocs/adherents/subscription.php b/htdocs/adherents/subscription.php index 75999a51ef7..e1fcda0e00c 100644 --- a/htdocs/adherents/subscription.php +++ b/htdocs/adherents/subscription.php @@ -31,6 +31,14 @@ // Load Dolibarr environment require '../main.inc.php'; +/** + * @var Conf $conf + * @var DoliDB $db + * @var HookManager $hookmanager + * @var Societe $mysoc + * @var Translate $langs + * @var User $user + */ require_once DOL_DOCUMENT_ROOT.'/core/lib/member.lib.php'; require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php'; require_once DOL_DOCUMENT_ROOT.'/adherents/class/adherent.class.php'; @@ -41,15 +49,6 @@ require_once DOL_DOCUMENT_ROOT.'/compta/bank/class/account.class.php'; require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php'; require_once DOL_DOCUMENT_ROOT.'/accountancy/class/accountingjournal.class.php'; -/** - * @var Conf $conf - * @var DoliDB $db - * @var HookManager $hookmanager - * @var Societe $mysoc - * @var Translate $langs - * @var User $user - */ - $langs->loadLangs(array("companies", "bills", "members", "users", "mails", 'other')); $action = GETPOST('action', 'aZ09'); @@ -61,7 +60,7 @@ $id = GETPOSTINT('rowid') ? GETPOSTINT('rowid') : GETPOSTINT('id'); $rowid = $id; $ref = GETPOST('ref', 'alphanohtml'); $typeid = GETPOSTINT('typeid'); -$cancel = GETPOST('cancel'); +$cancel = GETPOST('cancel', 'alpha'); // Load variable for pagination $limit = GETPOSTINT('limit') ? GETPOSTINT('limit') : $conf->liste_limit; @@ -509,11 +508,11 @@ print ''; // Login if (!getDolGlobalString('ADHERENT_LOGIN_NOT_REQUIRED')) { - print ''; + print ''; } // Type -print ''; +print ''; print '\n"; // Morphy @@ -589,7 +588,7 @@ if (isModEnabled('category') && $user->hasRight('categorie', 'lire')) { } // Birth Date -print ''; +print ''; // Default language if (getDolGlobalInt('MAIN_MULTILANGS')) { @@ -1021,7 +1020,7 @@ if (($action == 'addsubscription' || $action == 'create_thirdparty') && $user->h if ($adht->subscription) { // Amount print ''; - print ''; + print ''; // Label print ''; diff --git a/htdocs/adherents/type.php b/htdocs/adherents/type.php index 08639d66233..9d489f3581f 100644 --- a/htdocs/adherents/type.php +++ b/htdocs/adherents/type.php @@ -485,7 +485,13 @@ if (!$rowid && $action != 'create' && $action != 'edit') { print ''; } if (!empty($arrayfields['t.amount']['checked'])) { - print ''; + print ''; } if (!empty($arrayfields['t.caneditamount']['checked'])) { print ''; @@ -645,7 +651,11 @@ if ($rowid > 0) { // Amount print ''; print ''; print ''; diff --git a/htdocs/admin/company.php b/htdocs/admin/company.php index 9a6a93531cf..c5f11806f63 100644 --- a/htdocs/admin/company.php +++ b/htdocs/admin/company.php @@ -470,7 +470,7 @@ print ''."\n"; // Country diff --git a/htdocs/admin/emailcollector_card.php b/htdocs/admin/emailcollector_card.php index b14439fc7cd..c757e94c53c 100644 --- a/htdocs/admin/emailcollector_card.php +++ b/htdocs/admin/emailcollector_card.php @@ -67,7 +67,7 @@ $id = GETPOSTINT('id'); $ref = GETPOST('ref', 'alpha'); $action = GETPOST('action', 'aZ09'); $confirm = GETPOST('confirm', 'alpha'); -$cancel = GETPOST('cancel', 'aZ09'); +$cancel = GETPOST('cancel', 'alpha'); $contextpage = GETPOST('contextpage', 'aZ') ? GETPOST('contextpage', 'aZ') : 'emailcollectorcard'; // To manage different context of search $backtopage = GETPOST('backtopage', 'alpha'); diff --git a/htdocs/admin/eventorganization.php b/htdocs/admin/eventorganization.php index 483e8d5007e..51cb257e16b 100644 --- a/htdocs/admin/eventorganization.php +++ b/htdocs/admin/eventorganization.php @@ -44,7 +44,7 @@ $langs->loadLangs(array("admin", "eventorganization", "categories")); // Parameters $action = GETPOST('action', 'aZ09'); -$cancel = GETPOST('cancel', 'aZ09'); +$cancel = GETPOST('cancel', 'alpha'); $backtopage = GETPOST('backtopage', 'alpha'); $value = GETPOST('value', 'alpha'); diff --git a/htdocs/admin/eventorganization_public.php b/htdocs/admin/eventorganization_public.php index a189e0e863e..da50f4da354 100644 --- a/htdocs/admin/eventorganization_public.php +++ b/htdocs/admin/eventorganization_public.php @@ -46,7 +46,7 @@ $langs->loadLangs(array("admin", "eventorganization", "categories")); // Parameters $action = GETPOST('action', 'aZ09'); -$cancel = GETPOST('cancel', 'aZ09'); +$cancel = GETPOST('cancel', 'alpha'); $backtopage = GETPOST('backtopage', 'alpha'); $value = GETPOST('value', 'alpha'); diff --git a/htdocs/admin/expensereport_rules.php b/htdocs/admin/expensereport_rules.php index 1ec1adaa852..435607a1519 100644 --- a/htdocs/admin/expensereport_rules.php +++ b/htdocs/admin/expensereport_rules.php @@ -341,7 +341,7 @@ foreach ($rules as $rule) { if ($action == 'edit' && $object->id == $rule->id) { echo ''; } else { - echo price($rule->amount, 0, $langs, 1, -1, -1, $conf->currency); + echo price($rule->amount, 0, $langs, 1, -1, -1, getDolCurrency()); } echo ''; diff --git a/htdocs/admin/index.php b/htdocs/admin/index.php index dc753792530..6c30affaba5 100644 --- a/htdocs/admin/index.php +++ b/htdocs/admin/index.php @@ -83,12 +83,23 @@ if (getDolGlobalString('MAIN_MOTD_SETUPPAGE')) { print ''; print $langs->trans("SetupDescription1").'
'; //print $langs->trans("AreaForAdminOnly").' '; -print '
'; -print $langs->trans("SetupDescription2", $langs->transnoentities("MenuCompanySetup"), $langs->transnoentities("Modules")); + +if (!getDolGlobalString('MAIN_INFO_SOCIETE_NOM') || !getDolGlobalString('MAIN_INFO_SOCIETE_COUNTRY') || getDolGlobalString('MAIN_INFO_SOCIETE_SETUP_TODO_WARNING')) { + $setupcompanynotcomplete = 1; +} else { + $setupcompanynotcomplete = 0; +} + +if ($setupcompanynotcomplete) { + print '
'; + print $langs->trans("SetupDescription2", $langs->transnoentities("MenuCompanySetup"), $langs->transnoentities("Modules")); +} + print "

"; print '
'; + // Show info depending on country if defined $constkey = 'MAIN_INFO_SETUP_FOR_COUNTRY_'.$mysoc->country_code; //$conf->global->$constkey = 'rrr'; @@ -104,12 +115,6 @@ print '
'; // Show info setup company -if (!getDolGlobalString('MAIN_INFO_SOCIETE_NOM') || !getDolGlobalString('MAIN_INFO_SOCIETE_COUNTRY') || getDolGlobalString('MAIN_INFO_SOCIETE_SETUP_TODO_WARNING')) { - $setupcompanynotcomplete = 1; -} else { - $setupcompanynotcomplete = 0; -} - print '
'; print img_picto('', 'company', 'class="paddingright valignmiddle double"'); diff --git a/htdocs/admin/limits.php b/htdocs/admin/limits.php index c5b732a51d3..b1baceffa20 100644 --- a/htdocs/admin/limits.php +++ b/htdocs/admin/limits.php @@ -42,12 +42,12 @@ require_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php'; $langs->loadLangs(array('companies', 'products', 'admin')); $action = GETPOST('action', 'aZ09'); -$cancel = GETPOST('cancel', 'aZ09'); +$cancel = GETPOST('cancel', 'alpha'); $currencycode = GETPOST('currencycode', 'alpha'); if (isModEnabled('multicompany') && getDolGlobalString('MULTICURRENCY_USE_LIMIT_BY_CURRENCY')) { // When MULTICURRENCY_USE_LIMIT_BY_CURRENCY is on, we use always a defined currency code instead of '' even for default. - $currencycode = (!empty($currencycode) ? $currencycode : $conf->currency); + $currencycode = (!empty($currencycode) ? $currencycode : getDolCurrency()); } $mainmaxdecimalsunit = 'MAIN_MAX_DECIMALS_UNIT'.(!empty($currencycode) ? '_'.$currencycode : ''); @@ -137,14 +137,14 @@ llxHeader('', $title, $help_url, '', 0, 0, '', '', '', 'mod-admin page-limits'); print load_fiche_titre($title, '', 'title_setup'); -$aCurrencies = array($conf->currency); // Default currency always first position +$aCurrencies = array(getDolCurrency()); // Default currency always first position if (isModEnabled('multicompany') && getDolGlobalString('MULTICURRENCY_USE_LIMIT_BY_CURRENCY')) { require_once DOL_DOCUMENT_ROOT . '/core/lib/multicurrency.lib.php'; $sql = "SELECT rowid, code FROM " . MAIN_DB_PREFIX . "multicurrency"; $sql .= " WHERE entity = " . ((int) $conf->entity); - $sql .= " AND code <> '" . $db->escape($conf->currency) . "'"; // Default currency always first position + $sql .= " AND code <> '" . $db->escape(getDolCurrency()) . "'"; // Default currency always first position $resql = $db->query($sql); if ($resql) { while ($obj = $db->fetch_object($resql)) { diff --git a/htdocs/admin/mails.php b/htdocs/admin/mails.php index d343d27c874..e0e67787509 100644 --- a/htdocs/admin/mails.php +++ b/htdocs/admin/mails.php @@ -45,7 +45,7 @@ require_once DOL_DOCUMENT_ROOT.'/core/lib/geturl.lib.php'; $langs->loadLangs(array("companies", "products", "admin", "mails", "other", "errors")); $action = GETPOST('action', 'aZ09'); -$cancel = GETPOST('cancel', 'aZ09'); +$cancel = GETPOST('cancel', 'alpha'); $trackid = GETPOST('trackid'); diff --git a/htdocs/admin/mails_emailing.php b/htdocs/admin/mails_emailing.php index 9c7e23b8457..d24ba593a13 100644 --- a/htdocs/admin/mails_emailing.php +++ b/htdocs/admin/mails_emailing.php @@ -42,7 +42,7 @@ require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php'; $langs->loadLangs(array('companies', 'products', 'admin', 'mails', 'other', 'errors')); $action = GETPOST('action', 'aZ09'); -$cancel = GETPOST('cancel', 'aZ09'); +$cancel = GETPOST('cancel', 'alpha'); $usersignature = $user->signature; // For action = test or send, we ensure that content is not html, even for signature, because this we want a test with NO html. diff --git a/htdocs/admin/mails_ingoing.php b/htdocs/admin/mails_ingoing.php index a206f4bed29..4011022b448 100644 --- a/htdocs/admin/mails_ingoing.php +++ b/htdocs/admin/mails_ingoing.php @@ -41,7 +41,7 @@ require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php'; $langs->loadLangs(array("companies", "products", "admin", "mails", "other", "errors")); $action = GETPOST('action', 'aZ09'); -$cancel = GETPOST('cancel', 'aZ09'); +$cancel = GETPOST('cancel', 'alpha'); $trackid = GETPOST('trackid'); diff --git a/htdocs/admin/mails_passwordreset.php b/htdocs/admin/mails_passwordreset.php index 81f7e481aab..d146f38a90d 100644 --- a/htdocs/admin/mails_passwordreset.php +++ b/htdocs/admin/mails_passwordreset.php @@ -42,7 +42,7 @@ require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php'; $langs->loadLangs(array('companies', 'products', 'admin', 'mails', 'other', 'errors')); $action = GETPOST('action', 'aZ09'); -$cancel = GETPOST('cancel', 'aZ09'); +$cancel = GETPOST('cancel', 'alpha'); $usersignature = $user->signature; // For action = test or send, we ensure that content is not html, even for signature, because this we want a test with NO html. diff --git a/htdocs/admin/mails_templates.php b/htdocs/admin/mails_templates.php index 7618a1f20b2..832d883305b 100644 --- a/htdocs/admin/mails_templates.php +++ b/htdocs/admin/mails_templates.php @@ -38,6 +38,13 @@ // Load Dolibarr environment require '../main.inc.php'; +/** + * @var Conf $conf + * @var DoliDB $db + * @var HookManager $hookmanager + * @var Translate $langs + * @var User $user + */ require_once DOL_DOCUMENT_ROOT.'/core/class/html.formadmin.class.php'; require_once DOL_DOCUMENT_ROOT.'/core/class/html.formcompany.class.php'; require_once DOL_DOCUMENT_ROOT.'/core/class/html.formmail.class.php'; @@ -47,14 +54,6 @@ require_once DOL_DOCUMENT_ROOT.'/core/class/doleditor.class.php'; require_once DOL_DOCUMENT_ROOT.'/core/lib/accounting.lib.php'; require_once DOL_DOCUMENT_ROOT.'/core/class/cemailtemplate.class.php'; -/** - * @var Conf $conf - * @var DoliDB $db - * @var HookManager $hookmanager - * @var Translate $langs - * @var User $user - */ - // Load translation files required by the page $langsArray = array("errors", "admin", "mails", "languages"); @@ -115,6 +114,26 @@ if (empty($sortorder)) { // Initialize a technical object to manage hooks of page. Note that conf->hooks_modules contains an array of hook context $hookmanager->initHooks(array('emailtemplates')); +$object = new CEmailTemplate($db); + +// Definition of array of fields for columns from ->fields +$tableprefix = 't'; +$arrayfields = array(); +foreach ($object->fields as $key => $val) { + // If $val['visible']==0, then we never show the field + if (!empty($val['visible'])) { + $visible = (int) dol_eval((string) $val['visible'], 1); + $arrayfields[$tableprefix.'.'.$key] = array( + 'label' => $val['label'], + 'checked' => (($visible < 0) ? '0' : '1'), + 'enabled' => (string) (int) (abs($visible) != 3 && (bool) dol_eval((string) $val['enabled'], 1)), + 'position' => $val['position'], + 'help' => isset($val['help']) ? $val['help'] : '' + ); + } +} + +// Old way to define field. // Name of SQL tables of dictionaries $tabname = array(); @@ -123,7 +142,7 @@ $tabname[25] = MAIN_DB_PREFIX."c_email_templates"; // Nom des champs en resultat de select pour affichage du dictionnaire // Names of fields in select results for dictionary display (AI translated) $tabfield = array(); -$tabfield[25] = "label,lang,type_template,fk_user,private,position,module,topic,joinfiles,defaultfortype,content"; +$tabfield[25] = "label,lang,type_template,fk_user,position,module,topic,joinfiles,defaultfortype,content"; if (getDolGlobalString('MAIN_EMAIL_TEMPLATES_FOR_OBJECT_LINES')) { $tabfield[25] .= ',content_lines'; } @@ -145,10 +164,6 @@ if (getDolGlobalString('MAIN_EMAIL_TEMPLATES_FOR_OBJECT_LINES')) { } $tabfieldinsert[25] .= ',entity'; // Must be at end because not into other arrays -// Condition to show dictionary in setup page -$tabcond = array(); -$tabcond[25] = true; - // List of help for fields // Set MAIN_EMAIL_TEMPLATES_FOR_OBJECT_LINES to allow edit of template for lines require_once DOL_DOCUMENT_ROOT.'/core/class/html.formmail.class.php'; @@ -638,7 +653,8 @@ if (!empty($user->admin) && (empty($_SESSION['leftmenu']) || $_SESSION['leftmenu $morejs = array(); $morecss = array(); -$sql = "SELECT rowid as rowid, module, label, type_template, lang, fk_user, private, position, topic, email_from,joinfiles, defaultfortype, content_lines, content, enabled, active, tms, datec"; +$sql = "SELECT rowid as rowid, module, label, type_template, lang, fk_user, private, position, topic, email_from, joinfiles, defaultfortype,"; +$sql .= " content_lines, content, enabled, active, tms, datec"; $sql .= " FROM ".MAIN_DB_PREFIX."c_email_templates"; $sql .= " WHERE entity IN (".getEntity('email_template').")"; if (!$user->admin) { @@ -1025,9 +1041,14 @@ foreach ($fieldlist as $field => $value) { }*/ // Status print '
'; + // Have to expand the id="Title line with search boxes" with 2 extra fields because the line below id="Title of lines" are 2 fields longer -print ''; // tms / Modif. date -print ''; // datec / Date creation +if (!empty($arrayfields['t.tms']['checked'])) { + print ''; // tms / Modif. date +} +if (!empty($arrayfields['t.datec']['checked'])) { + print ''; // datec / Date creation +} // Action column if (!getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) { print ''; + // Action column if (getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) { print ''; $colspan++; } @@ -1348,6 +1378,11 @@ if ($num) { $fuser = new User($db); $fuser->fetch($valuetoshow); $valuetoshow = $fuser->getNomUrl(-1); + + if ($obj->private) { + $valuetoshow = img_picto($langs->transnoentitiesnoconv("Private"), 'lock', 'class="pictofixedwidth"').$valuetoshow; + } + $class .= ' tdoverflowmax100'; } } @@ -1375,6 +1410,13 @@ if ($num) { $class .= ' '.$css; } + if ($value == 'tms' && empty($arrayfields['t'.$value]['checked'])) { + $showfield = 0; + } + if ($value == 'datec' && empty($arrayfields['t.'.$value]['checked'])) { + $showfield = 0; + } + // Show value for field if ($showfield) { print ''; diff --git a/htdocs/admin/mails_ticket.php b/htdocs/admin/mails_ticket.php index 6320898456c..a8bb56a3ee8 100644 --- a/htdocs/admin/mails_ticket.php +++ b/htdocs/admin/mails_ticket.php @@ -42,7 +42,7 @@ require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php'; $langs->loadLangs(array('companies', 'products', 'admin', 'mails', 'other', 'errors')); $action = GETPOST('action', 'aZ09'); -$cancel = GETPOST('cancel', 'aZ09'); +$cancel = GETPOST('cancel', 'alpha'); $usersignature = $user->signature; // For action = test or send, we ensure that content is not html, even for signature, because this we want a test with NO html. diff --git a/htdocs/admin/modules.php b/htdocs/admin/modules.php index 988658ce690..f7623cf5dde 100644 --- a/htdocs/admin/modules.php +++ b/htdocs/admin/modules.php @@ -724,9 +724,6 @@ if ($mode == 'common' || $mode == 'commonkanban') { $deschelp .= '
'; } } -//if ($mode == 'marketplace') { -// $deschelp = '
'.$langs->trans("ModulesMarketPlaceDesc")."

\n"; -//} if ($mode == 'deploy') { $deschelp = '
'.$langs->trans("ModulesDeployDesc", $langs->transnoentitiesnoconv("AvailableModules"))."

\n"; } @@ -801,7 +798,7 @@ if ($mode == 'common' || $mode == 'commonkanban') { $moreforfilter .= ''; if ($search_keyword || ($search_nature && $search_nature != '-1') || ($search_version && $search_version != '-1') || ($search_status && $search_status != '-1')) { $moreforfilter .= ' '; - $moreforfilter .= ''; + $moreforfilter .= ''; } $moreforfilter .= ''; $moreforfilter .= ''; @@ -1421,7 +1418,7 @@ if ($mode == 'marketplace') {
style="width:100%;">
'.$langs->trans("Login").' / '.$langs->trans("Id").''.dol_escape_htmltag($object->login).'
'.$langs->trans("Login").' / '.$langs->trans("Id").''.dol_escape_htmltag($object->login).'
'.$langs->trans("Type").'
'.$langs->trans("Type").''.$adht->getNomUrl(1)."
'.$langs->trans("DateOfBirth").''.dol_print_date($object->birth, 'day').'
'.$langs->trans("DateOfBirth").''.dol_print_date($object->birth, 'day').'
'.$langs->trans("Amount").' '.$langs->trans("Currency".$conf->currency) .'
'.$langs->trans("Currency".getDolCurrency()) .'
'.$langs->trans("Label").''.yn($objp->subscription).''.(is_null($objp->amount) || $objp->amount === '' ? '' : price($objp->amount)).''; + $amount = (is_null($objp->amount) || $objp->amount === '' ? '' : price($objp->amount)); + print ''.$amount.''; + if ($amount && $amount < (float) getDolGlobalInt("MEMBER_MIN_AMOUNT")) { + print img_warning('Amount lower than minimum of '.price(getDolGlobalInt("MEMBER_MIN_AMOUNT")).' defined in setup'); + } + print ''.yn($objp->caneditamount).'
'.$langs->trans("Amount").''; - print((is_null($object->amount) || $object->amount === '') ? '' : ''.price($object->amount).''); + $amount = ((is_null($object->amount) || $object->amount === '') ? '' : price($object->amount)); + print ''.$amount.''; + if ($amount && $amount < (float) getDolGlobalInt("MEMBER_MIN_AMOUNT")) { + print ' '.img_warning('Amount lower than minimum of '.price(getDolGlobalInt("MEMBER_MIN_AMOUNT")).' defined in setup'); + } print '
'.$form->textwithpicto($langs->trans("CanEditAmountShort"), $langs->transnoentities("CanEditAmount")).''; @@ -944,7 +954,6 @@ if ($rowid > 0) { $adh->firstname = $objp->firstname; $adh->datefin = $datefin; $adh->need_subscription = $objp->subscription; - $adh->statut = $objp->status; $adh->status = $objp->status; $adh->email = $objp->email; $adh->photo = $objp->photo; @@ -1089,8 +1098,9 @@ if ($rowid > 0) { print '
'.$langs->trans("Amount").''; + $amount = ((is_null($object->amount) || $object->amount === '') ? '' : price($object->amount)); print ''; print '
'; print img_picto('', 'multicurrency', 'class="pictofixedwidth"'); -print $form->selectCurrency($conf->currency, "currency", 2); +print $form->selectCurrency(getDolCurrency(), "currency", 2); print '
'; @@ -1104,6 +1125,12 @@ foreach ($fieldlist as $field => $value) { $valuetoshow = $langs->trans("ContentForLines"); $showfield = 0; } + if ($value == 'tms' && empty($arrayfields['t'.$value]['checked'])) { + $showfield = 0; + } + if ($value == 'datec' && empty($arrayfields['t.'.$value]['checked'])) { + $showfield = 0; + } // Show fields if ($showfield) { @@ -1153,16 +1180,19 @@ if ($num) { $colspan = 0; + print '
'; + print ''; + print ''; + print '
'; + if ($action == 'edit') { + print ''; + } + print ''; + print '
'; - print ''; - print ''; - if ($action == 'edit') { - print ''; - } - print '
'; - print ''; print '
- get_products($nbmaxtoshow); ?> +
diff --git a/htdocs/admin/multicurrency.php b/htdocs/admin/multicurrency.php index 97ac1454da3..016631e0c44 100644 --- a/htdocs/admin/multicurrency.php +++ b/htdocs/admin/multicurrency.php @@ -289,7 +289,7 @@ print ''; print ''; print ''."\n"; -print ''."\n"; +print ''."\n"; print ''; print ''; @@ -308,10 +308,10 @@ print ''; // Main currency print ''; -print ''; @@ -319,7 +319,7 @@ print ''; print ''; foreach ($TCurrency as &$currency) { - if ($currency->code == $conf->currency) { + if ($currency->code == getDolCurrency()) { continue; } @@ -334,7 +334,7 @@ foreach ($TCurrency as &$currency) { print ''; print ''; print ''; - print '1 '.$conf->currency.' = '; + print '1 '.getDolCurrency().' = '; print ' '.$currency->code.' '; print ' '; print ''; diff --git a/htdocs/admin/pdf_other.php b/htdocs/admin/pdf_other.php index e81d7cc6cc0..64abf680e24 100644 --- a/htdocs/admin/pdf_other.php +++ b/htdocs/admin/pdf_other.php @@ -168,7 +168,7 @@ if ($action == 'update') { } // add file to concat foreach (array('MAIN_INFO_PROPAL_TERMSOFSALE', 'MAIN_INFO_ORDER_TERMSOFSALE', 'MAIN_INFO_INVOICE_TERMSOFSALE') as $varname) { - if ($_FILES[$varname]["name"]) { + if (isset($_FILES[$varname]) && $_FILES[$varname]["name"]) { if (!preg_match('/(\.pdf)$/i', $_FILES[$varname]["name"])) { // Document can be used on a lot of different places. Only pdf can be supported. $langs->load("errors"); setEventMessages($langs->trans("ErrorBadFormat"), null, 'errors'); diff --git a/htdocs/admin/remotestore/class/externalModules.class.php b/htdocs/admin/remotestore/class/externalModules.class.php index 6a9d38dbe0d..4fe93e2ff7f 100644 --- a/htdocs/admin/remotestore/class/externalModules.class.php +++ b/htdocs/admin/remotestore/class/externalModules.class.php @@ -366,6 +366,22 @@ class ExternalModules $this->numberTotalOfProducts = 0; + // Special case of category goodies + if ($this->categorie == 87) { + $html = ''; + + return $html; + } + // Fetch the products from Dolistore source $dolistoreProducts = array(); @@ -638,7 +654,7 @@ class ExternalModules $this->numberOfProducts = count($this->products); - return $html ; + return $html; } /** diff --git a/htdocs/admin/remotestore/css/store.css b/htdocs/admin/remotestore/css/store.css index a6ed0e6d4c2..94645994e57 100644 --- a/htdocs/admin/remotestore/css/store.css +++ b/htdocs/admin/remotestore/css/store.css @@ -195,17 +195,48 @@ textarea.row4{ h2.appTitle small{ font-weight: normal; } +/* tr.NotCompatible{ - opacity: 0.5; + opacity: 1; } tr.NotCompatible:hover{ - opacity: 0.7; + opacity: 0.9; } +*/ span.details{ font-size: 1em; margin-left: 10px; vertical-align: super; } +.storedesc { + opacity: 0.5; +} + +.shop-container { + border-top: none; + overflow: hidden; + text-align: center; + background-color: #fff; + box-shadow: 0 8px 20px rgba(0, 0, 0, .1); + transition: transform .3s; + transform: scale(1); +} +.shop-button { + position: absolute; + top: 20%; + left: 50%; + padding: 10px 20px; + border: none; + border-radius: 8px; + text-transform: uppercase; + font-size: 16px; + color: #fff; + background-color: #ff5f57; + box-shadow: 0 5px 15px rgba(255, 95, 87, .3); + transition: background-color .3s, transform .3s; + transform: translate(-50%, -50%); + cursor: pointer; +} @media only screen and (min-width: 1150px) { diff --git a/htdocs/admin/security_headers_http.php b/htdocs/admin/security_headers_http.php index 856e18018e8..1478f89baf7 100644 --- a/htdocs/admin/security_headers_http.php +++ b/htdocs/admin/security_headers_http.php @@ -45,7 +45,7 @@ if (!$user->admin) { } $action = GETPOST('action', 'aZ09'); -$cancel = GETPOST('cancel', 'aZ09'); +$cancel = GETPOST('cancel', 'alpha'); $forceCSP = getDolGlobalString("MAIN_SECURITY_FORCECSP"); $selectarrayCSPDirectives = GetContentPolicyDirectives(); diff --git a/htdocs/admin/sms.php b/htdocs/admin/sms.php index 92aa921b482..0c44762db70 100644 --- a/htdocs/admin/sms.php +++ b/htdocs/admin/sms.php @@ -39,7 +39,7 @@ require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php'; $langs->loadLangs(array("companies", "admin", "products", "sms", "other", "errors")); $action = GETPOST('action', 'aZ09'); -$cancel = GETPOST('cancel', 'aZ09'); +$cancel = GETPOST('cancel', 'alpha'); if (!$user->admin) { accessforbidden(); diff --git a/htdocs/admin/system/security.php b/htdocs/admin/system/security.php index 3043afd5c63..b50ead06957 100644 --- a/htdocs/admin/system/security.php +++ b/htdocs/admin/system/security.php @@ -35,6 +35,13 @@ require '../../main.inc.php'; * @var string $conffile // $conffile is defined into filefunc.inc.php * @var string $dolibarr_main_prod * @var string $dolibarr_main_document_root + * @var string $dolibarr_main_restrict_os_commands + * @var string $dolibarr_main_restrict_eval_methods + * @var string $dolibarr_main_restrict_ip + * @var string $dolibarr_main_db_pass + * @var string $dolibarr_main_db_encrypted_pass + * @var string $dolibarr_main_stream_to_disable + * @var string $dolibarr_nocsrfcheck */ require_once DOL_DOCUMENT_ROOT.'/core/lib/memory.lib.php'; require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php'; @@ -387,6 +394,15 @@ if (empty($dolibarr_main_restrict_os_commands)) { print '     ('.$langs->trans("RecommendedValueIs", 'mysqldump, mysql, pg_dump, pg_restore, mariadb, mariadb-dump, clamdscan').')'; print '
'; +print '$dolibarr_main_restrict_eval_methods: '; +if (empty($dolibarr_main_restrict_eval_methods)) { + print $langs->trans("None"); +} else { + print $dolibarr_main_restrict_eval_methods; +} +print '     ('.$langs->trans("RecommendedValueIs", 'getDolGlobalString,getDolGlobalInt,getDolCurrency,fetchNoCompute,hasRight,isAdmin,isModEnabled,isStringVarMatching,abs,min,max,round,dol_now,dol_concat,preg_match').')'; +print '
'; + if (!getDolGlobalString('SECURITY_DISABLE_TEST_ON_OBFUSCATED_CONF')) { print '$dolibarr_main_db_pass: '; if (!empty($dolibarr_main_db_pass) && empty($dolibarr_main_db_encrypted_pass)) { @@ -964,16 +980,16 @@ print 'MAIN_RESTRICTHTML_REMOVE_ALSO_BAD_ATTRIBUTES = '.(getDol print '   ('.$langs->trans("Recommended").": 1 - does not work on HTML5 with some old libxml libs)
"; print '
'; -// MAIN_DISALLOW_URL_INTO_DESCRIPTIONS = 1, disallow url links except if on /medias -// MAIN_DISALLOW_URL_INTO_DESCRIPTIONS = 2, disallow all external urls link -print 'MAIN_DISALLOW_URL_INTO_DESCRIPTIONS = '.getDolGlobalString('MAIN_DISALLOW_URL_INTO_DESCRIPTIONS', ''.$langs->trans("Undefined").'   ('.$langs->trans("Recommended").': 1=only local links allowed or 2=no links at all)')."
"; +// MAIN_DISALLOW_URL_INTO_DESCRIPTIONS = 1, disallow url links except if on the local wrapper document.php or viewimage.php +// MAIN_DISALLOW_URL_INTO_DESCRIPTIONS = 2, disallow all urls link +print 'MAIN_DISALLOW_URL_INTO_DESCRIPTIONS = '.getDolGlobalString('MAIN_DISALLOW_URL_INTO_DESCRIPTIONS', ''.$langs->trans("Undefined").'   ('.$langs->trans("Recommended").': 1=only local links allowed (to wrapper document.php or image.php) or 2=no links at all)')."
"; print '
'; print 'MAIN_ALLOW_SVG_FILES_AS_EXTERNAL_LINKS = '.getDolGlobalString('MAIN_ALLOW_SVG_FILES_AS_EXTERNAL_LINKS', ''.$langs->trans("Undefined").'   ('.$langs->trans("Recommended").': '.$langs->trans("Undefined").' '.$langs->trans("or").' 0)')."
"; print '
'; -print 'MAIN_DISALLOW_STRING_OBFUSCATION_IN_DOL_EVAL = '.(getDolGlobalString('MAIN_DISALLOW_STRING_OBFUSCATION_IN_DOL_EVAL') ? '1' : ''.$langs->trans("Undefined").''); -print '   ('.$langs->trans("Recommended").": 1 - may break use of concatenation function like . or dol_concatdesc into extra fields conditions or formula)
"; +print 'MAIN_ALLOW_OBFUSCATION_METHODS_IN_DOL_EVAL = '.getDolGlobalString('MAIN_ALLOW_OBFUSCATION_METHODS_IN_DOL_EVAL', ''.$langs->trans("Undefined").''); +print '   ('.$langs->trans("Recommended").": 0 - The value 1 allows the use of concatenation functions like . or dol_concat into extra fields conditions or formula but is not secured)
"; print '
'; diff --git a/htdocs/admin/tools/listevents.php b/htdocs/admin/tools/listevents.php index a4d470bfe3f..5056c87d665 100644 --- a/htdocs/admin/tools/listevents.php +++ b/htdocs/admin/tools/listevents.php @@ -494,9 +494,9 @@ if ($result) { $userstatic->email = $obj->email; if (isModEnabled('multicompany') && $userstatic->admin && !$userstatic->entity) { - print img_picto($langs->trans("SuperAdministratorDesc"), 'redstar', 'class="valignmiddle paddingright"'); + print img_picto($langs->trans("SuperAdministratorDesc"), 'superadmin', 'class="valignmiddle paddingright"'); } elseif ($userstatic->admin) { - print img_picto($langs->trans("AdministratorDesc"), 'star', 'class="valignmiddle paddingright"'); + print img_picto($langs->trans("AdministratorDesc"), 'admin', 'class="valignmiddle paddingright"'); } //print $userstatic->getLoginUrl(-1); diff --git a/htdocs/admin/tools/ui/class/documentation.class.php b/htdocs/admin/tools/ui/class/documentation.class.php index 8c797b2b2a4..341ee034f84 100644 --- a/htdocs/admin/tools/ui/class/documentation.class.php +++ b/htdocs/admin/tools/ui/class/documentation.class.php @@ -252,6 +252,34 @@ class Documentation 'submenu' => array(), 'summary' => array(), ), + 'UxDolibarrContext' => array( + 'url' => dol_buildpath($this->baseUrl.'/experimental/experiments/dolibarr-context/index.php', 1), + 'icon' => 'fas fa-flask', + 'submenu' => array( + 'UxDolibarrContextHowItWork' => array( + 'url' => dol_buildpath($this->baseUrl.'/experimental/experiments/dolibarr-context/index.php', 1), + 'icon' => 'fas fa-flask', + 'submenu' => array(), + 'summary' => array( + 'Introduction' => '#titlesection-basicusage', + 'ConsoleHelp' => '#titlesection-console-help', + 'JSDolibarrhooks' => '#titlesection-hooks', + 'JSDolibarrhooksReadyVsInit' => '#titlesection-event-init-vs-ready', + 'JSDolibarrAwaitHooks' => '#titlesection-await-hooks', + 'ExampleOfCreatingNewContextTool' => '#titlesection-create-tool-example', + 'SetEventMessageTool' => '#titlesection-tool-seteventmessage', + 'SetAndUseContextVars' => '#titlesection-contextvars', + ), + ), + 'UxDolibarrContextLangsTool' => array( + 'url' => dol_buildpath($this->baseUrl.'/experimental/experiments/dolibarr-context/langs-tool.php', 1), + 'icon' => 'fas fa-flask', + 'submenu' => array(), + 'summary' => array(), + ), + ), + 'summary' => array(), + ), ) ); @@ -470,7 +498,13 @@ class Documentation if ($showsubmenu && !empty($menu['submenu'])) { foreach ($menu['submenu'] as $key => $item) { print '
  • '; + + if (!empty($item['url'])) { + print '

    '.$langs->trans($key).'

    '; + } else { print '

    '.$langs->trans($key).'

    '; + } + if ($showsubmenu_summary) { $this->displaySummary($item, $level); } diff --git a/htdocs/admin/tools/ui/experimental/experiments/dolibarr-context/assets/dolibarr-context.mock.js b/htdocs/admin/tools/ui/experimental/experiments/dolibarr-context/assets/dolibarr-context.mock.js new file mode 100644 index 00000000000..65c8c32390b --- /dev/null +++ b/htdocs/admin/tools/ui/experimental/experiments/dolibarr-context/assets/dolibarr-context.mock.js @@ -0,0 +1,171 @@ +/** This file is purely for IDE autocompletion and developer convenience. + * It is never executed or loaded in Dolibarr itself. + * + * MOCK DEFINITION: Dolibarr.tools + * This mock helps your code editor understand the structure of Dolibarr.tools + * and provides autocomplete hints, parameter hints, and inline documentation. + * You can safely edit this file to add all standard Dolibarr tools for autocompletion. + * + * @SEE dolibarr-context.umd.js + * +*/ + +var Dolibarr = { + tools: { + + /** + * Displays a Dolibarr notification message (success, warning, or error). + * This is the JavaScript equivalent of the PHP setEventMessage tool. + * + * @param {string} msg The message text to display + * @param {string=} type Optional: 'mesgs' (default), 'warnings', or 'errors' + * @param {boolean=} sticky Optional: true if the message should stay until manually closed + * + * Example usage in your IDE: + * Dolibarr.tools.setEventMessage('Operation successful', 'success'); + */ + setEventMessage: function(msg, type, sticky) {}, + + /** + * TThe langs tool + */ + langs: { + /** + * Load a single locale from cache or fetch + * @param {string} domain + * @param {string} locale + * @returns {Promise} translation object + */ + loadLocale(domain, locale) {}, + + /** + * Load translations for a domain (multiple locales) + * @param {string} domain + * @param {string} locales - comma-separated list + * @returns {Promise} + */ + load(domain, locales = currentLocale) {}, + + /** + * Set the current locale to use for translations + * @param {string} locale + */ + setLocale(locale) {}, + + /** + * Translate a key using current locale + * Supports placeholders like %s, %d, %f (simple sprintf) + * @param {string} key + * @param {...any} args + * @returns {string} + */ + trans(key, ...args) {}, + }, + + // You can add more standard Dolibarr tools here for IDE autocompletion. + // Example: + // alertUser: function(msg) {}, + }, + + /** + * Defines a new secure tool. + * @param {string} name Name of the tool + * @param {*} value Function, class or object + * @param {boolean} overwrite Explicitly allow overwriting an existing tool + * + * See also dolibarr-context.mock.js for defining all standard Dolibarr tools and creating mock implementations to improve code completion and editor support. + */ + defineTool(name, value, overwrite = false, triggerHook = true) {}, + + /** + * Check if tool exists + * @param {string} name Tool name + * @returns {boolean} true if exists + */ + checkToolExist(name) {}, + + /** + * Get read-only snapshot of context variables + */ + ContextVars() {}, + + /** + * Defines a new context variable. + * @param {string} key + * @param {string|number|boolean} value + * @param {boolean} overwrite Allow overwriting existing value + */ + setContextVar(key, value, overwrite = false) {}, + + /** + * Set multiple context variables + * @param {Object} vars Object of key/value pairs + * @param {boolean} overwrite Allow overwriting existing values + */ + setContextVars(vars, overwrite = false) {}, + + /** + * Get a context variable safely + * @param {string} key + * @param {*} fallback Optional fallback if variable not set + * @returns {*} + */ + getContextVar(key, fallback = null) {}, + + /** + * Enable or disable debug mode + * @param {boolean} state + */ + debugMode(state) {}, + + /** + * Enable or disable debug mode + * @returns {int} + */ + getDebugMode() {}, + + /** + * Internal logger + * Only prints when debug mode is enabled + * @param {string} msg + */ + log(msg) {}, + + /** + * Executes a hook-like JS event with CustomEvent. + * @param {string} hookName Hook identifier + * @param {object} data Extra information passed to listeners + */ + executeHook(hookName, data = {}) {}, + + /** + * Registers an event listener. + * @param {string} eventName Event to listen to + * @param {function} callback Listener function + */ + on(eventName, callback) {}, + + /** + * Unregister an event listener + * @param {string} eventName + * @param {function} callback + */ + off(eventName, callback) {}, + + /** + * Register an asynchronous hook + * @param {string} eventName + * @param {function} fn Async function receiving previous result + * @param {Object} opts Optional {before, after, id} to control order + * @returns {string} The hook ID + */ + onAwait(eventName, fn, opts = {}) {}, + + /** + * Execute async hooks sequentially + * @param {string} eventName + * @param {*} data Input data for first hook + * @returns {Promise<*>} Final result after all hooks + */ + async executeHookAwait(eventName, data) {}, +}; diff --git a/htdocs/admin/tools/ui/experimental/experiments/dolibarr-context/assets/dolibarr-context.umd.js b/htdocs/admin/tools/ui/experimental/experiments/dolibarr-context/assets/dolibarr-context.umd.js new file mode 100644 index 00000000000..109780cd787 --- /dev/null +++ b/htdocs/admin/tools/ui/experimental/experiments/dolibarr-context/assets/dolibarr-context.umd.js @@ -0,0 +1,443 @@ +// CustomEvent doesn’t show up until IE 11 and Safari 10. Fortunately a simple polyfill pushes support back to any IE 9. +(function () { + if ( typeof window.CustomEvent === "function" ) return false; + function CustomEvent ( event, params ) { + params = params || { bubbles: false, cancelable: false, detail: undefined }; + var evt = document.createEvent( 'CustomEvent' ); + evt.initCustomEvent( event, params.bubbles, params.cancelable, params.detail ); + return evt; + } + CustomEvent.prototype = window.Event.prototype; + window.CustomEvent = CustomEvent; +})(); +// End old browsers support + +/** + * Dolibarr Global Context (UMD) + * Provides a secure global object window.Dolibarr + * with non-replaceable tools, events and debug mode. + * + * See also dolibarr-context.mock.js for defining all standard Dolibarr tools and creating mock implementations to improve code completion and editor support. + * + */ +(function (root, factory) { + // Support AMD + if (typeof define === "function" && define.amd) { + define([], factory); + + // Support CommonJS (Node, bundlers) + } else if (typeof exports === "object") { + module.exports = factory(); + + // Fallback global (browser) + } else { + root.Dolibarr = root.Dolibarr || factory(); + } +})(typeof self !== "undefined" ? self : this, function () { + + // Prevent double initialization if script loaded twice + if (typeof window !== "undefined" && window.Dolibarr) { + return window.Dolibarr; + } + + // Private storage for secure tools (non-replaceable) + const _tools = {}; + + // Private storage for secure context vars or constants (non-replaceable) + const _contextVars = {}; + + // Internal map to track proxies for events + const _proxies = new Map(); + + // Native event dispatcher (standard DOM) + const _events = new EventTarget(); + + const _awaitHooks = {}; // Async hooks storage + + // Debug flag (disabled by default) + let _debug = false; + + // ------------------------- + // Internal helper functions + // ------------------------- + function _ensureEvent(name) { if (!_awaitHooks[name]) _awaitHooks[name] = []; } + function _generateId() { return 'hook_' + Math.random().toString(36).slice(2); } + function _idExists(name, id) { return _awaitHooks[name].some(h => h.id === id); } + + /** + * Insert a new hook entry in the array respecting optional before/after lists + */ + function _insertWithOrder(arr, entry, beforeList, afterList) { + if ((!beforeList || beforeList.length === 0) && (!afterList || afterList.length === 0)) { + arr.push(entry); + return arr; + } + + let ordered = [...arr]; + let index = ordered.length; + + if (beforeList && beforeList.length > 0) { + for (const target of beforeList) { + const i = ordered.findIndex(h => h.id === target); + if (i !== -1 && i < index) index = i; + } + } + + if (afterList && afterList.length > 0) { + for (const target of afterList) { + const i = ordered.findIndex(h => h.id === target); + if (i !== -1 && i >= index) index = i + 1; + } + } + + if (index > ordered.length) index = ordered.length; + ordered.splice(index, 0, entry); + return ordered; + } + + // ------------------------- + // Dolibarr object + // ------------------------- + const Dolibarr = { + + /** + * Returns a frozen copy of the registered tools. + * Tools cannot be modified or replaced from outside. + */ + get tools() { + return Object.freeze({ ..._tools }); + }, + + /** + * Defines a new secure tool. + * @param {string} name Name of the tool + * @param {*} value Function, class or object + * @param {boolean} overwrite Explicitly allow overwriting an existing tool + * + * See also dolibarr-context.mock.js for defining all standard Dolibarr tools and creating mock implementations to improve code completion and editor support. + */ + defineTool(name, value, overwrite = false, triggerHook = true) { + // Prevent silent overrides unless "overwrite" is true + if (!overwrite && this.checkToolExist(name)) { + throw new Error(`Dolibarr: Tool '${name}' already defined`); + } + + // Define the tool as read-only and non-configurable + Object.defineProperty(_tools, name, { + value, + writable: false, + configurable: false, + enumerable: true, + }); + + this.log(`Tool defined: ${name}, triggerHook: ${triggerHook}, overwrite: ${overwrite} `); + if(triggerHook) { + this.executeHook('defineTool', { toolName: name, overwrite }); + } + }, + + /** + * Check if tool exists + * @param {string} name Tool name + * @returns {boolean} true if exists + */ + checkToolExist(name) { + return Object.prototype.hasOwnProperty.call(_tools, name); + }, + + /** + * Get read-only snapshot of context variables + */ + get ContextVars() { + return Object.freeze({ ..._contextVars }); + }, + + /** + * Defines a new context variable. + * @param {string} key + * @param {string|number|boolean} value + * @param {boolean} overwrite Allow overwriting existing value + */ + setContextVar(key, value, overwrite = false) { + // Accept only string, number, or boolean + const type = typeof value; + if (type !== 'string' && type !== 'number' && type !== 'boolean') { + throw new TypeError(`Dolibarr: ContextVar '${key}' must be a string, number, or boolean`); + } + + if (!overwrite && _contextVars.hasOwnProperty(key)) { + throw new Error(`Dolibarr: ContextVar '${key}' already defined`); + } + + Object.defineProperty(_contextVars, key, { + value, + writable: false, + configurable: false, + enumerable: true + }); + + this.log(`ContextVar set: ${key} = ${value} (overwrite: ${overwrite})`); + this.executeHook('setContextVar', { key, value, overwrite }); + }, + + + /** + * Set multiple context variables + * @param {Object} vars Object of key/value pairs + * @param {boolean} overwrite Allow overwriting existing values + */ + setContextVars(vars, overwrite = false) { + if (typeof vars !== 'object' || vars === null) { + throw new Error('Dolibarr: setContextVars expects an object'); + } + + for (const [key, value] of Object.entries(vars)) { + this.setContextVar(key, value, overwrite); + } + }, + + /** + * Get a context variable safely + * @param {string} key + * @param {*} fallback Optional fallback if variable not set + * @returns {*} + */ + getContextVar(key, fallback = null) { + return _contextVars.hasOwnProperty(key) ? _contextVars[key] : fallback; + }, + + /** + * Enable or disable debug mode + * @param {boolean} state + */ + debugMode(state) { + _debug = !!state; + // save in localStorage + if (typeof window !== "undefined" && window.localStorage) { + localStorage.setItem('DolibarrDebugMode', _debug ? '1' : '0'); + } + this.log(`Debug mode: ${_debug}`); + }, + + /** + * Enable or disable debug mode + * @returns {int} + */ + getDebugMode() { + return _debug ? 1 : 0 + }, + + /** + * Internal logger + * Only prints when debug mode is enabled + * @param {string} msg + */ + log(msg) { + if (_debug) console.log(`Dolibarr: ${msg}`); + }, + + /** + * Executes a hook-like JS event with CustomEvent. + * @param {string} hookName Hook identifier + * @param {object} data Extra information passed to listeners + */ + executeHook(hookName, data = {}) { + this.log(`Hook executed: ${hookName}`); + + const ev = new CustomEvent(hookName, { detail: data }); + + // Dispatch on internal EventTarget + _events.dispatchEvent(ev); + + // Dispatch globally on document for backward compatibility + if (typeof document !== "undefined") { + document.dispatchEvent(new CustomEvent('Dolibarr:' + hookName, { detail: data })); + } + + // Notify Dolibarr.on() listeners with data directly + const listeners = _events.listeners?.[hookName] || []; + listeners.forEach(fn => fn(data)); + }, + + /** + * Registers an event listener. + * @param {string} eventName Event to listen to + * @param {function} callback Listener function + */ + on(eventName, callback) { + // Create a proxy to extract e.detail + const proxy = function(e) { + callback(e.detail); + }; + + // Store the proxy so we can remove it later + if (!_proxies.has(eventName)) _proxies.set(eventName, new Map()); + _proxies.get(eventName).set(callback, proxy); + + // Attach proxy to the internal EventTarget + _events.addEventListener(eventName, proxy); + }, + + /** + * Unregister an event listener + * @param {string} eventName + * @param {function} callback + */ + off(eventName, callback) { + const map = _proxies.get(eventName); + if (!map) return; + + const proxy = map.get(callback); + if (!proxy) return; + + // Remove proxy from EventTarget + _events.removeEventListener(eventName, proxy); + map.delete(callback); + + // Cleanup if no proxies remain for this event + if (map.size === 0) _proxies.delete(eventName); + }, + + /** + * Register an asynchronous hook + * @param {string} eventName + * @param {function} fn Async function receiving previous result + * @param {Object} opts Optional {before, after, id} to control order + * @returns {string} The hook ID + */ + onAwait(eventName, fn, opts = {}) { + _ensureEvent(eventName); + let id = opts.id || _generateId(); + if (_idExists(eventName, id)) throw new Error(`onAwait: ID '${id}' already used for '${eventName}'`); + const before = Array.isArray(opts.before) ? opts.before : (opts.before ? [opts.before] : []); + const after = Array.isArray(opts.after) ? opts.after : (opts.after ? [opts.after] : []); + _awaitHooks[eventName] = _insertWithOrder(_awaitHooks[eventName], { id, fn }, before, after); + return id; + }, + + /** + * Execute async hooks sequentially + * @param {string} eventName + * @param {*} data Input data for first hook + * @returns {Promise<*>} Final result after all hooks + */ + async executeHookAwait(eventName, data) { + this.log(`Await Hook executed: ${eventName}`); + + _ensureEvent(eventName); + let result = data; + for (const h of _awaitHooks[eventName]) { + result = await h.fn(result); + } + return result; + } + }; + + // Lock Dolibarr core object + Object.freeze(Dolibarr); + + // Expose Dolibarr to window in a protected, non-writable way + if (typeof window !== "undefined") { + Object.defineProperty(window, "Dolibarr", { + value: Dolibarr, + writable: false, + configurable: false, + enumerable: true, + }); + } + + // Restore debug mode from localStorage + if (typeof window !== "undefined" && window.localStorage) { + const saved = localStorage.getItem('DolibarrDebugMode'); + if (saved === '1') { + Dolibarr.debugMode(true); + } + } + + + // Force initialise hook init and Ready in good execution order + (function triggerDolibarrHooks() { + // Fire Init first + const fireInit = () => { + Dolibarr.executeHook('Init', { context: Dolibarr }); + Dolibarr.log('Context Init done'); + + // Only after Init is done, fire Ready + fireReady(); + }; + + const fireReady = () => { + Dolibarr.executeHook('Ready', { context: Dolibarr }); + Dolibarr.log('Context Ready done'); + }; + + if (document.readyState === 'complete' || document.readyState === 'interactive') { + // DOM already ready, trigger Init -> Ready in order + fireInit(); + } else { + // Wait for DOM ready, then trigger Init -> Ready + document.addEventListener('DOMContentLoaded', fireInit); + } + })(); + + /** + * Display help in console log + */ + Dolibarr.defineTool('showConsoleHelp', () => { + + console.groupCollapsed( + "%cDolibarr JS Developers HELP", + "background-color: #95cf04; color: #ffffff; font-weight: bold; padding: 4px;" + ); + + console.log("Show this help : %cDolibarr.tools.showConsoleHelp();","font-weight: bold;"); + console.log(`Documentation for admin only on : %cModule builder ➜ UX Components Doc`,"font-weight: bold;"); + + // DEBUG MODE + console.groupCollapsed("Dolibarr debug mode"); + + console.log( + "When help was displayed, status was: %c" + (Dolibarr.getDebugMode() ? "ENABLED" : "DISABLED"), + "font-weight: bold; color:" + (Dolibarr.getDebugMode() ? "green" : "red") + ";" + ); + + console.log( + "Activate debug mode : %cDolibarr.debugMode(true);", + "font-weight: bold;" + ); + + console.log( + "Disable debug mode : %cDolibarr.debugMode(false);", + "font-weight: bold;" + ); + + console.log("Note : debug mode status is persistent."); + console.groupEnd(); + + // HOOKS + console.groupCollapsed("Hooks helpers"); + + console.log( + "Run a hook manually : %cDolibarr.executeHook('hookName', {...})", + "font-weight: bold;" + ); + + console.log( + "Run await hooks manually : %cawait Dolibarr.executeHookAwait('hookName', {...})", + "font-weight: bold;" + ); + + console.groupEnd(); + + + console.groupEnd(); // END MAIN GROUP + }, false, false); + + + + +// Auto-show help when console is opened + Dolibarr.tools.showConsoleHelp(); + + return Dolibarr; +}); diff --git a/htdocs/admin/tools/ui/experimental/experiments/dolibarr-context/assets/dolibarr-tool.langs.js b/htdocs/admin/tools/ui/experimental/experiments/dolibarr-context/assets/dolibarr-tool.langs.js new file mode 100644 index 00000000000..adeefc27ddb --- /dev/null +++ b/htdocs/admin/tools/ui/experimental/experiments/dolibarr-context/assets/dolibarr-tool.langs.js @@ -0,0 +1,232 @@ +document.addEventListener('Dolibarr:Init', function(e) { + /** + * Dolibarr.tools.langs + * -------------------- + * Manage translations in JS context with IndexedDB cache, multi-locale support and fallback. + * Parallel loading of language files for performance. + * Automatic cache invalidation if Dolibarr version changes. + * + * Require Dolibarr context vars + * DOL_LANG_INTERFACE_URL, MAIN_LANG_DEFAULT, DOL_VERSION + * + */ + const langs = function() { + + const ONE_DAY = 86400000; + let currentLocale = Dolibarr.getContextVar('MAIN_LANG_DEFAULT', 'en_US'); + let translations = {}; // { en_US: {KEY: TEXT}, fr_FR: {...} } + let domainsLoaded = {}; // { en_US: Set(['main','other']), fr_FR: Set([...]) } + if (!domainsLoaded[currentLocale]) domainsLoaded[currentLocale] = new Set(); + let domainsRequested = new Set(); // Set of domain names that were requested at least once + + /** + * Open or create IndexedDB for caching translations + * @returns {Promise} + */ + async function openDB() { + return new Promise((resolve, reject) => { + const request = indexedDB.open('DolibarrLangs', 1); + request.onupgradeneeded = e => { + const db = e.target.result; + if (!db.objectStoreNames.contains('langs')) db.createObjectStore('langs'); + }; + request.onsuccess = () => resolve(request.result); + request.onerror = () => reject(request.error); + }); + } + + /** + * Get cached translation for a domain + locale + * @param {string} domain + * @param {string} locale + * @returns {Promise} + */ + async function getCache(domain, locale) { + try { + const db = await openDB(); + const tx = db.transaction('langs', 'readonly'); + const store = tx.objectStore('langs'); + return new Promise((resolve, reject) => { + const request = store.get(`${domain}@${locale}`); + request.onsuccess = () => resolve(request.result); + request.onerror = () => reject(request.error); + }); + } catch (err) { + return null; + } + } + + /** + * Set cached translation for a domain + locale + * @param {string} domain + * @param {string} locale + * @param {Object} data + */ + async function setCache(domain, locale, data) { + try { + const db = await openDB(); + const tx = db.transaction('langs', 'readwrite'); + const store = tx.objectStore('langs'); + const dolibarrVersion = Dolibarr.getContextVar('DOL_VERSION', 0); + await store.put({ key: `${domain}@${locale}`, data, timestamp: Date.now(), dolibarrVersion }, `${domain}@${locale}`); + } catch (err) { + // fail silently + Dolibarr.log('Save langs in cache fail'); + } + } + + /** + * Clear all cached translations in IndexedDB and in-memory + */ + async function clearCache(clearMemory = false) { + if(clearMemory) { + translations = {}; + domainsLoaded = {}; + } + + try { + const db = await openDB(); + const tx = db.transaction('langs', 'readwrite'); + const store = tx.objectStore('langs'); + await store.clear(); + Dolibarr.log('Dolibarr.tools.langs: cache cleared'); + } catch (err) { + console.error('Dolibarr.tools.langs: failed to clear cache', err); + } + } + + /** + * Load a single locale from cache or fetch + * @param {string} domain + * @param {string} locale + * @returns {Promise} translation object + */ + async function loadLocale(domain, locale) { + const cache = await getCache(domain, locale); + const now = Date.now(); + const dolibarrVersion = Dolibarr.getContextVar('DOL_VERSION', 0); + + if (cache && cache.data && (now - cache.timestamp < ONE_DAY) && cache.dolibarrVersion === dolibarrVersion) { + Dolibarr.log('Langs tool : Load lang from cache'); + return cache.data; + } + + const langInterfaceUrl = Dolibarr.getContextVar('DOL_LANG_INTERFACE_URL', false); + if(!langInterfaceUrl) { + console.error('Dolibarr langs: missing DOL_LANG_INTERFACE_URL') + return; + } + + Dolibarr.log('Langs tool : Load lang from interface'); + const params = new URLSearchParams({ domain, local: locale }); + const resp = await fetch(`${langInterfaceUrl}?${params.toString()}`); + const json = await resp.json(); + const data = json[locale] || {}; + await setCache(domain, locale, data); + return data; + } + + /** + * Load translations for a domain (multiple locales) + * @param {string} domain + * @param {string} locales - comma-separated list + * @returns {Promise} + */ + async function load(domain, locales = currentLocale) { + const list = locales.split(','); + + // flag domaine as requested for future load when local change + domainsRequested.add(domain); + + const results = await Promise.all(list.map(loc => loadLocale(domain, loc))); + + list.forEach((loc, i) => { + if (!translations[loc]) translations[loc] = {}; + Object.assign(translations[loc], results[i]); + + if (!domainsLoaded[loc]) domainsLoaded[loc] = new Set(); + domainsLoaded[loc].add(domain); + }); + + return translations; + } + + /** + * Set the current locale to use for translations + * @param {string} locale + */ + async function setLocale(locale, noDomainReload = false) { + if (!locale || locale === currentLocale) return; + + const prev = currentLocale; + currentLocale = locale; + + if (!domainsLoaded[locale]) domainsLoaded[locale] = new Set(); + + if (!noDomainReload) { + // priorité : domainsLoaded[prev], sinon fallback sur domainsRequested + let toReload = Array.from(domainsLoaded[prev] || []); + if (toReload.length === 0) { + // aucun domaine marqué comme "loaded" pour prev : utiliser la liste des domaines demandés + toReload = Array.from(domainsRequested); + } + + for (const domain of toReload) { + // load(domain, locale) accepte le param locale ; l'appel charge et met domainsLoaded + if (domainsLoaded[locale].size === 0) { + await load(domain, locale); + } + } + } + + Dolibarr.log(`Locale changed: ${prev} -> ${locale}`); + } + + + /** + * Translate a key using current locale + * Supports placeholders like %s, %d, %f (simple sprintf) + * @param {string} key + * @param {...any} args + * @returns {string} + */ + function trans(key, ...args) { + const text = translations[currentLocale]?.[key] || key; + if (!args.length) return text; + + // Utilisation de la fonction sprintf pour le formatage + return sprintf(text, ...args); + } + + function sprintf(fmt, ...args) { + let i = 0; + return fmt.replace(/%[%bcdeEfFgGosuxX]/g, (match) => { + if (match === '%%') return '%'; + const arg = args[i++]; + switch (match) { + case '%s': return String(arg); + case '%d': + case '%u': return Number(arg); + case '%f': + case '%F': return parseFloat(arg); + case '%b': return Number(arg).toString(2); + case '%o': return Number(arg).toString(8); + case '%x': return Number(arg).toString(16); + case '%X': return Number(arg).toString(16).toUpperCase(); + case '%c': return String.fromCharCode(Number(arg)); + default: return match; + } + }); + } + + return { + load, + clearCache, + setLocale, + trans, + get currentLocale() { return currentLocale; } + }; + }; + + Dolibarr.defineTool('langs',langs()); +}); diff --git a/htdocs/admin/tools/ui/experimental/experiments/dolibarr-context/assets/dolibarr-tool.seteventmessage.js b/htdocs/admin/tools/ui/experimental/experiments/dolibarr-context/assets/dolibarr-tool.seteventmessage.js new file mode 100644 index 00000000000..6e8214af2f5 --- /dev/null +++ b/htdocs/admin/tools/ui/experimental/experiments/dolibarr-context/assets/dolibarr-tool.seteventmessage.js @@ -0,0 +1,53 @@ +document.addEventListener('Dolibarr:Init', function(e) { + // this tool allow overwrite because of DISABLE_JQUERY_JNOTIFY conf + /** + * status : 'mesgs' by default, 'warnings', 'errors' + */ + Dolibarr.defineTool('setEventMessage', (msg, status = 'mesgs', sticky = false) =>{ + + // Normalize status to match jNotify expected values + const normalizeStatus = (s) => { + s = (s || '').toLowerCase(); + if (s === 'error' || s === 'errors') return 'error'; + if (s === 'warning' || s === 'warnings') return 'warning'; + return ''; + }; + + const type = normalizeStatus(status); + + let jnotifyConf = { + delay: 1500 // the default time to show each notification (in milliseconds) + , type : type + , sticky: sticky // determines if the message should be considered "sticky" (user must manually close notification) + , closeLabel: "×" // the HTML to use for the "Close" link + , showClose: true // determines if the "Close" link should be shown if notification is also sticky + , fadeSpeed: 150 // the speed to fade messages out (in milliseconds) + , slideSpeed: 250 // the speed used to slide messages out (in milliseconds) + } + + if(msg.length > 0){ + if (typeof $.jnotify === "function") { + $.jnotify(msg, jnotifyConf); + } else { + const container = document.getElementById('alert-message-container'); + if (container) { + // Add message to #alert-message-container if exist + const div = document.createElement('div'); + div.className = type; // error, warning, success + div.textContent = msg; // safer than innerHTML + container.appendChild(div); + } else { + console.warn("jnotify is missing and setEventMessage tool wasn't replaced so use alert fallback instead"); + // fallback prefix + let prefix = ''; + if (type === 'error') prefix = 'Error: '; + else if (type === 'warning') prefix = 'Warning: '; + window.alert(prefix + msg); + } + } + } + else{ + Dolibarr.log('setEventMessage : Message is empty'); + } + }, true); +}); diff --git a/htdocs/admin/tools/ui/experimental/experiments/dolibarr-context/index.php b/htdocs/admin/tools/ui/experimental/experiments/dolibarr-context/index.php new file mode 100644 index 00000000000..2dc299f64ab --- /dev/null +++ b/htdocs/admin/tools/ui/experimental/experiments/dolibarr-context/index.php @@ -0,0 +1,680 @@ + + * Copyright (C) 2025 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 + * 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 . + */ + +// Load Dolibarr environment +require '../../../../../../main.inc.php'; + +/** + * @var DoliDB $db + * @var HookManager $hookmanager + * @var Translate $langs + * @var User $user + */ + +// Protection if external user +if ($user->socid > 0) { + accessforbidden(); +} + +// Includes +require_once DOL_DOCUMENT_ROOT . '/admin/tools/ui/class/documentation.class.php'; + +// Load documentation translations +$langs->load('uxdocumentation'); + +// +$documentation = new Documentation($db); +$group = 'ExperimentalUx'; +$experimentName = 'UxDolibarrContext'; + +$experimentAssetsPath = $documentation->baseUrl . '/experimental/experiments/dolibarr-context/assets/'; +$js = [ + '/includes/ace/src/ace.js', + '/includes/ace/src/ext-statusbar.js', + '/includes/ace/src/ext-language_tools.js', + $experimentAssetsPath . '/dolibarr-context.umd.js', + $experimentAssetsPath . '/dolibarr-tool.seteventmessage.js', +]; +$css = []; + +// Output html head + body - Param is Title +$documentation->docHeader($langs->trans($experimentName, $group), $js, $css); + +// Set view for menu and breadcrumb +$documentation->view = [$group, $experimentName]; + +// Output sidebar +$documentation->showSidebar(); ?> + +
    + + showBreadCrumb(); ?> + +
    + +

    trans($experimentName); ?> : trans('UxDolibarrContextHowItWork'); ?>

    + + showSummary(); ?> + +
    +

    Introduction

    + +

    + DolibarrContext is a secure global JavaScript context for Dolibarr. + It provides a single global object window.Dolibarr, which cannot be replaced. + It allows defining non-replaceable tools and managing hooks/events in a modular and secure way. +

    + +

    + This system is designed to provide long-term flexibility and maintainability. You can define reusable tools + that encapsulate functionality such as standardized AJAX requests and responses, ensuring consistent data handling across Dolibarr modules. + For example, tools can be created to wrap API calls and automatically process returned data in a uniform format, + reducing repetitive code and preventing errors. +

    + +

    + Beyond DOM-based events, DolibarrContext allows monitoring and reacting to business events using a + hook-like mechanism. For instance, you can listen to events such as + Dolibarr.on('addline:load:productPricesList', function(data) { ... }); + without relying on DOM changes. This enables creating logic that reacts directly to application state changes. +

    + +

    + Similarly, you can define tools that act as global helpers, like Dolibarr.tools.setEventMessage(). + This tool can display notifications (similar to PHP's setEventMessage() in Dolibarr), + initially using jNotify or any other library. In the future, the underlying library can change without affecting + the way modules or external code call this tool, maintaining compatibility and reducing maintenance. +

    + +

    + In summary, DolibarrContext provides a secure, extensible foundation for adding tools, monitoring business events, + and standardizing interactions across Dolibarr's frontend modules. +

    +
    + +
    +

    Console help

    + +

    + Open your browser console with F12 to view the available commands.
    + If the help does not appear automatically, type Dolibarr.tools.showConsoleHelp(); in the console to display it. +

    +
    + +
    +

    Dolibarr.log() and debugMode()

    + +

    + Dolibarr.log() is a lightweight logging utility provided by the Dolibarr JS context. + It does not replace console.log(), but it gives an important advantage: + you can enable or disable all logs globally using Dolibarr.debugMode(). +

    + +

    Why use Dolibarr.log() instead of console.log()?

    +
      +
    • You can enable or disable logging dynamically from the browser console.
    • +
    • Avoid polluting the console for end-users: logs appear only when debug mode is enabled.
    • +
    • Ideal for module development: switch between quiet mode and verbose mode instantly.
    • +
    • Useful in production debugging: you can activate logs without modifying any code.
    • +
    + +

    How it works

    +

    + When debugMode is disabled (default state), calls to Dolibarr.log() do nothing. + When enabled, Dolibarr.log() behaves like console.log(). +

    + +
    + ', + ' // Log something (will only appear if debug mode is ON)', + ' Dolibarr.log("My debug message");', + '', + ' // Enable verbose logs', + ' Dolibarr.debugMode(true);', + '', + ' // Disable logs again', + ' Dolibarr.debugMode(false);', + '', + ); + echo $documentation->showCode($lines, 'php'); + ?> +
    + +

    Summary

    +
      +
    • console.log() → always prints messages, noisy, not controllable, but useful during active development and debugging.
    • +
    • Dolibarr.log() → prints messages only when debug mode is ON; fully controllable. Ideal for Dolibarr core logs or when you want to keep logs available but silent in production.
    • +
    • Dolibarr.debugMode(true) → enable verbose logs, activating Dolibarr.log() output.
    • +
    • Dolibarr.debugMode(false) → disable all Dolibarr.log() output, silencing debug messages.
    • +
    + +
    + + +
    +

    JS Dolibarr hooks

    + +

    + Dolibarr provides a hook system to allow modules and scripts to communicate with each other + through named events. There are two ways to listen to these events in JS: + Dolibarr.on() and document.addEventListener(). +

    + +

    Event listener example

    +

    + You can use Dolibarr.on() to listen to a hook. The main difference with standard + document events is that the callback receives directly the data object passed + when the hook is executed, without needing to access e.detail. +

    +

    + For backward compatibility and standard DOM integration, the same hook can also be caught + using document.addEventListener(), but in this case the data is inside + e.detail and the event name is prefixed by Dolibarr: so for a hook named A event name is Dolibarr:A +

    + +
    + ', + ' // Add a listener to the Dolibarr theEventName event', + ' Dolibarr.on(\'theEventName\', function(data) {', + ' console.log(\'Dolibarr theEventName\', data);', + ' });', + '', + ' // But this work too on document', + ' document.addEventListener(\'Dolibarr:theEventName\', function(e) {', + ' console.log(\'Dolibarr theEventName\', e.detail);', + ' });', + '', + ); + echo $documentation->showCode($lines, 'php'); ?> +
    + +

    Practical usage

    +

    + When Dolibarr is ready (DOM loaded and JS context initialized), you can register your hooks + or trigger them. Both Dolibarr.on() and document.addEventListener() + are valid, but Dolibarr.on() is simpler and more convenient because you get the + data directly. +

    +
    + ', + ' document.addEventListener(\'Dolibarr:Ready\', function(e) {', + ' // the dom is ready and you are sure Dolibarr js context is loaded', + ' ...', + ' // Do your stuff', + ' ...', + '', + ' // Add a listener to the yourCustomHookName event with dolibarr.on()', + ' Dolibarr.on(\'yourCustomHookName\', function(data) {', + ' console.log(\'With Dolibarr.on : data will contain { data01: \'stuff\', data02: \'other stuff\' }\', data);', + ' });', + '', + ' // Or you can do : Add a listener to the yourCustomHookName document.addEventListener()', + ' document.addEventListener(\'Dolibarr:yourCustomHookName\', function(e) {', + ' console.log(\'With document.addEventListener : e.detail will contain { data01: \'stuff\', data02: \'other stuff\' }\', e.detail);', + ' });', + '', + ' // you can trigger js hooks', + ' document.getElementById(\'try-event-yourCustomHookName\').addEventListener(\'click\', function(e) {', + ' Dolibarr.executeHook(\'yourCustomHookName\', { data01: \'stuff\', data02: \'other stuff\' })', + ' });', + '', + ' ...', + ' // Do your stuff', + ' ...', + + ' });', + '', + ); + echo $documentation->showCode($lines, 'php'); ?> + + Open your console F12 and click on + + +
    + +
    + + + +
    +

    Difference between Dolibarr:Init and Dolibarr:Ready event

    + +

    + Dolibarr provides two main initialization events for its JavaScript context: Dolibarr:Init and Dolibarr:Ready. + Understanding their difference is important when developing modules or tools. +

    + +
      +
    • + Dolibarr:Init is triggered immediately when the Dolibarr context is created. + This event is intended for: +
        +
      • Defining or registering new tools via Dolibarr.defineTool().
      • +
      • Setting context variables (Dolibarr.setContextVar() / Dolibarr.setContextVars()).
      • +
      • Preparing configuration that must be available before the DOM is fully loaded.
      • +
      + It occurs before Dolibarr:Ready, so it is ideal for setup tasks that other tools may depend on. +
    • + +
    • + Dolibarr:Ready is triggered once the DOM is ready, similar to $(document).ready() in jQuery. + This event is intended for: +
        +
      • Running code that interacts with the DOM.
      • +
      • Attaching event listeners to elements on the page.
      • +
      • Executing functionality that requires all tools and context variables to be fully initialized.
      • +
      +
    • +
    + +

    + In short, use Dolibarr:Init for setting up tools and context variables, and Dolibarr:Ready for code that needs the DOM and fully initialized context. +

    + +

    Examples of usage

    +
    + ', + ' // Example: Dolibarr:Init - define a tool and set context variables early', + ' document.addEventListener(\'Dolibarr:Init\', function(e) {', + ' console.log("Init event fired, Dolibarr is initialised and receive context vars a tools");', + ' });', + '', + ' // Example: Dolibarr:Ready - interact with DOM and use tools', + ' document.addEventListener(\'Dolibarr:Ready\', function(e) {', + ' console.log("Ready event fired, DOM is ready");', + '', + '', + ' // Attach event listener to a DOM element', + ' const btn = document.getElementById("myButton");', + ' if(btn) {', + ' btn.addEventListener("click", function() {', + ' alert("Button clicked! Context value: " + Dolibarr.getContextVar("mySetting"));', + ' });', + ' }', + ' });', + '', + ); + echo $documentation->showCode($lines, 'php'); ?> +
    + +

    + In summary: +

      +
    • Dolibarr:Init → early setup, tools, context variables, configuration.
    • +
    • Dolibarr:Ready → DOM is ready, safe to manipulate elements and use tools defined in Init.
    • +
    +

    +
    + +
    +

    Async Hooks (Await Hooks) - sequential execution

    + +

    + Dolibarr supports asynchronous hooks using Dolibarr.onAwait() and Dolibarr.executeHookAwait(). + These hooks allow you to register functions that execute in sequence and can modify data before passing it to the next hook. + They are useful for complex workflows where multiple modules or scripts need to process or enrich the same data asynchronously. +

    + +

    + Each hook can optionally specify before or after to control the execution order relative to other hooks. + Every hook registration returns a unique id, which can be used to reference or unregister the hook later. +

    + +

    + Unlike standard synchronous hooks registered with Dolibarr.on(), await hooks return a Promise when executed. + This means you can await their results in your code, and any asynchronous operations inside a hook (e.g., API calls, timers) will be handled correctly before moving to the next hook. +

    + +
    + ">', + ' document.addEventListener(\'Dolibarr:Ready\', async function(e) {', + '', + ' // Register async hooks will be executed in first place', + ' Dolibarr.onAwait(\'calculateDiscount\', async function(order) {', + ' order.total *= 0.9; // Apply 10% discount', + ' return order;', + ' }, { id: \'discount10\' });', + '', + ' // Register async hooks will be executed in third place', + ' Dolibarr.onAwait(\'calculateDiscount\', async function(order) {', + ' if(order.total > 1000) order.total -= 50; // Extra discount over 1000', + ' return order;', + ' }, { id: \'discountOver1000\', after: \'discount10\' });', + '', + ' // Register async hooks will be executed in second place', + ' // this hook item as no id so plus10HookItemId will receive a unique random id ', + ' let plus10HookItemId = Dolibarr.onAwait(\'calculateDiscount\', async function(order) {', + ' order.newObjectAttribute = \'My value\';', + ' order.total += 10;', + ' return order;', + ' }, { before: \'discountOver1000\' });', + '', + ' document.getElementById(\'try-event-yourCustomAwaitHookName\').addEventListener(\'click\', async function(e) {', + ' // Execute all registered await hooks sequentially', + ' let order = {total: 1200};', + ' order = await Dolibarr.executeHookAwait(\'calculateDiscount\', order);', + ' console.log(order); // order.total : 1200 -> 1080 -> 1090 -> 1040', + ' });', + '', + ' });', + '', + ); + echo $documentation->showCode($lines, 'php'); ?> + + Open your console F12 and click on + + +
    + +
    + + +
    +

    Example of creating a new context tool

    + +

    Defining Tools

    +

    + You can define reusable and protected tools in the Dolibarr context using Dolibarr.defineTool. +

    +

    See also dolibarr-context.mock.js for defining all standard Dolibarr tools and creating mock implementations to improve code completion and editor support.

    +

    Note : a tool can be a class not only a function

    + +
    + ', + 'document.addEventListener(\'Dolibarr:Init\', function(e) {', + ' // Define a simple tool', + ' let overwrite = false; // Once a tool is defined, it cannot be replaced.', + ' Dolibarr.defineTool(\'alertUser\', (msg) => alert(\'[Dolibarr] \' + msg), overwrite);', + '});', + '', + 'document.addEventListener(\'Dolibarr:Ready\', function(e) {', + ' // Use the tool', + ' Dolibarr.tools.alertUser(\'hello world\');', + '});', + '', + ); + echo $documentation->showCode($lines, 'php'); ?> +
    + +

    Protected Tools

    +

    + Once a tool is defined on overwrite false, it cannot be replaced. Attempting to redefine it without overwrite will throw an error: +

    + +
    + ', + ' try {', + ' Dolibarr.defineTool(\'alertUser\', () => {});', + ' } catch (e) {', + ' console.error(e.message);', + ' }', + '', + ); + echo $documentation->showCode($lines, 'php'); ?> +
    + +

    Reading Tools

    +

    + You can read the list of available tools using Dolibarr.tools. It returns a frozen copy: +

    + +
    + ', + ' console.log(Dolibarr.tools);', + ' if(Dolibarr.checkToolExist(\'Tool name to check\')){/* ... */}else{/* ... */}; ', + '', + ); + echo $documentation->showCode($lines, 'php'); ?> +
    + +
    + +
    +

    Set event message tool

    + +

    + Instead of calling JNotify directly in your code, use Dolibarr’s setEventMessage tool. + Dolibarr provides the configuration option DISABLE_JQUERY_JNOTIFY, which disables the jQuery JNotify system, usually because another notification library will be used instead. +

    + +

    + If you rely on Dolibarr.tools.setEventMessage(), your code remains compatible even if the underlying notification system changes. + The setEventMessage tool can be replaced internally without requiring any changes in your modules or custom scripts. +

    +

    + This means all developers can write features without worrying about frontend compatibility or future library replacements. Enjoy! + +

    + +
    + " >', + ' document.addEventListener(\'Dolibarr:Ready\', function(e) {', + '', + ' document.getElementById(\'setEventMessage-success\').addEventListener(\'click\', function(e) {', + ' Dolibarr.tools.setEventMessage(\'Success Test\');', + ' });', + '', + ' document.getElementById(\'setEventMessage-error\').addEventListener(\'click\', function(e) {', + ' Dolibarr.tools.setEventMessage(\'Error Test\', \'errors\');', + ' });', + '', + ' document.getElementById(\'setEventMessage-error-sticky\').addEventListener(\'click\', function(e) {', + ' Dolibarr.tools.setEventMessage(\'Error Test\', \'errors\', true);', + ' });', + '', + ' document.getElementById(\'setEventMessage-warning\').addEventListener(\'click\', function(e) {', + ' Dolibarr.tools.setEventMessage(\'Warning Test\', \'warnings\');', + ' });', + '', + ' });', + '', + ); + echo $documentation->showCode($lines, 'php'); ?> + + + + + +
    + +
    + + + +
    +

    Set and use context vars

    + +

    + The Context Vars system allows you to define and manage variables that are globally accessible within the Dolibarr JavaScript context. These variables can store configuration data, URLs, tokens, user IDs, object references, or any other values needed by your frontend code and tools. + By using context vars, you can: +

      +
    • Pass server-side data (from PHP) to JavaScript safely and consistently.
    • +
    • Provide reusable configuration for Dolibarr tools, widgets, or modules without hardcoding values.
    • +
    • Define overridable or non-overridable vars to protect critical values while allowing flexible overrides when necessary.
    • +
    • Use Dolibarr.setContextVar for single values or Dolibarr.setContextVars to pass multiple values at once.
    • +
    • Access these variables anywhere in your code via Dolibarr.getContextVar(key).
    • +
    • Ensure that all your modules and tools can rely on consistent and up-to-date context information, improving maintainability and interoperability.
    • +
    + This system is particularly useful for setting up base URLs, API endpoints, user-specific information, or runtime data that needs to be shared across multiple Dolibarr frontend tools. +

    + +

    Add context var (overridable or not)

    +
    + " >', + ' document.addEventListener(\'Dolibarr:Init\', function(e) {', + ' // Add no overridable context var', + ' Dolibarr.setContextVar(\'yourKey\', \'YourValue\');', + '', + ' // Add overridable context var', + ' Dolibarr.setContextVar(\'yourKey2\', \'YourValue\', true);', + ' });', + '', + ); + echo $documentation->showCode($lines, 'php'); + ?> +
    + + +

    Add multiple context vars (overridable or not)

    +
    + DOL_URL_ROOT,', + ' \'token\' => newToken(),', + ' \'cardObjectElement\' => $object->element,', + ' \'cardObjectId\' => $object->id,', + ' \'currentUserId\' => $user->id', + ' // ...', + ' ];', + '', + ' $contextVars = [', + ' \'lastCardDataRefresh\' => time(),', + ' // ...', + ' ]', + '?>', + '', + ); + echo $documentation->showCode($lines, 'php'); + ?> +
    + +

    Get context var

    +
    + " >', + ' document.addEventListener(\'Dolibarr:Ready\', function(e) {', + ' let url = Dolibarr.getContextVar(\'DOL_URL_ROOT\', \'The optional fallback value\'));', + ' console.log(url);', + ' });', + '', + ); + echo $documentation->showCode($lines, 'php'); + ?> +
    + +
    + +
    + + + + +
    + + +docFooter(); +?> diff --git a/htdocs/admin/tools/ui/experimental/experiments/dolibarr-context/langs-tool-interface.php b/htdocs/admin/tools/ui/experimental/experiments/dolibarr-context/langs-tool-interface.php new file mode 100644 index 00000000000..2404220fc31 --- /dev/null +++ b/htdocs/admin/tools/ui/experimental/experiments/dolibarr-context/langs-tool-interface.php @@ -0,0 +1,110 @@ +. + */ + +//if (! defined('NOREQUIREDB')) define('NOREQUIREDB', '1'); // Do not create database handler $db +//if (! defined('NOREQUIREUSER')) define('NOREQUIREUSER', '1'); // Do not load object $user +if (! defined('NOREQUIRESOC')) define('NOREQUIRESOC', '1'); // Do not load object $mysoc +//if (! defined('NOREQUIRETRAN')) define('NOREQUIRETRAN', '1'); // Do not load object $langs +//if (! defined('NOSCANGETFORINJECTION')) define('NOSCANGETFORINJECTION', '1'); // Do not check injection attack on GET parameters +//if (! defined('NOSCANPOSTFORINJECTION')) define('NOSCANPOSTFORINJECTION', '1'); // Do not check injection attack on POST parameters +//if (! defined('NOTOKENRENEWAL')) define('NOTOKENRENEWAL', '1'); // Do not roll the Anti CSRF token (used if MAIN_SECURITY_CSRF_WITH_TOKEN is on) +//if (! defined('NOSTYLECHECK')) define('NOSTYLECHECK', '1'); // Do not check style html tag into posted data +if (! defined('NOREQUIREMENU')) define('NOREQUIREMENU', '1'); // If there is no need to load and show top and left menu +//if (! defined('NOREQUIREHTML')) define('NOREQUIREHTML', '1'); // If we don't need to load the html.form.class.php +//if (! defined('NOREQUIREAJAX')) define('NOREQUIREAJAX', '1'); // Do not load ajax.lib.php library +//if (! defined("NOLOGIN")) define("NOLOGIN", '1'); // If this page is public (can be called outside logged session). This include the NOIPCHECK too. +//if (! defined('NOIPCHECK')) define('NOIPCHECK', '1'); // Do not check IP defined into conf $dolibarr_main_restrict_ip +//if (! defined("MAIN_LANG_DEFAULT")) define('MAIN_LANG_DEFAULT', 'auto'); // Force lang to a particular value +//if (! defined("MAIN_AUTHENTICATION_MODE")) define('MAIN_AUTHENTICATION_MODE', 'aloginmodule'); // Force authentication handler +//if (! defined('CSRFCHECK_WITH_TOKEN')) define('CSRFCHECK_WITH_TOKEN', '1'); // Force use of CSRF protection with tokens even for GET +//if (! defined('NOBROWSERNOTIF')) define('NOBROWSERNOTIF', '1'); // Disable browser notification + + +// Load Dolibarr environment +require '../../../../../../main.inc.php'; + + +/** + * @var Conf $conf + * @var DoliDB $db + * @var HookManager $hookmanager + * @var Translate $langs + * @var User $user + */ + + +/** + * INTERFACE FOR JS CONTEXT LANG + */ + +if (empty($dolibarr_nocache)) { + $delaycache = '86400'; + header('Cache-Control: max-age=' . $delaycache . ', public, must-revalidate'); + header('Pragma: cache'); // This is to avoid to have Pragma: no-cache set by proxy or web server + header('Expires: ' . gmdate('D, d M Y H:i:s', time() + (int) $delaycache) . ' GMT'); // This is to avoid to have Expires set by proxy or web server +} else { + // If any cache on files were disable by config file (for test purpose) + header('Cache-Control: no-cache'); +} +header('Content-Type: application/json; charset=utf-8'); + + +$local = GETPOST('local'); +if (empty($local)) { + $local = $langs->getDefaultLang(); +} + +$domain = GETPOST('domain'); +if (empty($domain)) { + echo json_encode(['error' => 'Missing domain']); + exit; +} + +if (!preg_match('/^[A-Za-z0-9_-]+(?:@[A-Za-z0-9_-]+)?$/', $domain)) { + echo json_encode(['error' => 'Invalid domain']); + exit; +} + + +if (!preg_match('/^[a-z]{1,2}_[A-Z]{1,2}(?:,[a-z]{1,2}_[A-Z]{1,2})*$/', $local)) { + echo json_encode(['error' => 'Invalid langs codes']); + exit; +} + +/* +Format for JS: +{ + fr_FR : {KEY:"TEXT", ...}, + en_US : {KEY:"TEXT", ...} +} +*/ + +$locals = explode(',', $local); +$json = new stdClass(); + +foreach ($locals as $langCode) { + $json->$langCode = []; + $outputlangs = new Translate("", $conf); + $outputlangs->setDefaultLang($langCode); + $outputlangs->load($domain); + foreach ($outputlangs->tab_translate as $k => $v) { + $json->$langCode[$k] = dolPrintHTML($v); // to escape js and other stuff + } +} + +print json_encode($json, JSON_PRETTY_PRINT); diff --git a/htdocs/admin/tools/ui/experimental/experiments/dolibarr-context/langs-tool.php b/htdocs/admin/tools/ui/experimental/experiments/dolibarr-context/langs-tool.php new file mode 100644 index 00000000000..696ed2e48a6 --- /dev/null +++ b/htdocs/admin/tools/ui/experimental/experiments/dolibarr-context/langs-tool.php @@ -0,0 +1,207 @@ + + * Copyright (C) 2025 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 + * 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 . + */ + +// Load Dolibarr environment +require '../../../../../../main.inc.php'; + +/** + * @var DoliDB $db + * @var HookManager $hookmanager + * @var Translate $langs + * @var User $user + */ + +// Protection if external user +if ($user->socid > 0) { + accessforbidden(); +} + +// Includes +require_once DOL_DOCUMENT_ROOT . '/admin/tools/ui/class/documentation.class.php'; + +// Load documentation translations +$langs->load('uxdocumentation'); + +// +$documentation = new Documentation($db); +$group = 'ExperimentalUx'; +$experimentName = 'UxDolibarrContextLangsTool'; + +$experimentAssetsPath = $documentation->baseUrl . '/experimental/experiments/dolibarr-context/assets/'; +$js = [ + '/includes/ace/src/ace.js', + '/includes/ace/src/ext-statusbar.js', + '/includes/ace/src/ext-language_tools.js', + $experimentAssetsPath . '/dolibarr-context.umd.js', + $experimentAssetsPath . '/dolibarr-tool.langs.js', + $experimentAssetsPath . '/dolibarr-tool.seteventmessage.js', +]; +$css = []; + +// Output html head + body - Param is Title +$documentation->docHeader($langs->trans($experimentName, $group), $js, $css); + +// Set view for menu and breadcrumb +$documentation->view = [$group, 'UxDolibarrContext', $experimentName]; + +// Output sidebar +$documentation->showSidebar(); ?> + +
    + + showBreadCrumb(); ?> + +
    + +

    trans($experimentName); ?>

    + + showSummary(); ?> + +
    +

    Introduction

    + +

    + The Dolibarr Context Langs Tool is a powerful JavaScript utility to manage translations and locales dynamically.
    + It allows you to load translation domains, set the current language, clear cache, and retrieve translated strings in your scripts. +

    + +
    + +
    +

    Setup Context Variables

    +

    + Before using the tool, you should declare the necessary context variables on your page.
    + These variables allow the tool to know the current Dolibarr version, the default language, and the interface URL used to fetch translations. +

    +

    + However, like the setEventMessage tool, the Langs tool is a core tool and is always loaded by Dolibarr.
    + Therefore, in most cases, you do not need to set these variables manually, as they are already defined. +

    +
    + + ', + 'Dolibarr.setContextVars( DOL_VERSION,', + ' \'MAIN_LANG_DEFAULT\' => $langs->getDefaultLang(),', + ' \'DOL_LANG_INTERFACE_URL\' => dol_buildpath(\'admin/tools/ui/experimental/experiments/dolibarr-context/langs-tool-interface.php\',1),', + ']) ?>);', + '', + ); + echo $documentation->showCode($lines, 'php'); + ?> +
    +
    + +
    +

    Basic Usage

    +

    + The main features of the Langs tool are: +

    +
      +
    • Load translations for a specific domain with caching
    • +
    • Set or change the current locale
    • +
    • Clear cached translations
    • +
    • Retrieve a translated string by key
    • +
    + +

    Example:

    + +
    + ', + 'document.addEventListener(\'Dolibarr:Ready\', async function(e) {', + '', + ' if(Dolibarr.checkToolExist(\'langs\')){ // not mandatory because langs tool will be a core tool', + '', + ' // Load langs', + ' Dolibarr.tools.langs.load(\'uxdocumentation\'); // will use cache but need to load lang in new local', + '', + ' // Clear cache', + ' document.getElementById(\'clearCache\').addEventListener(\'click\', async function(e) {', + ' await Dolibarr.tools.langs.clearCache();', + ' const txt = Dolibarr.tools.langs.trans(\'CacheCleared\');', + ' Dolibarr.tools.setEventMessage(txt);', + ' });', + '', + ' // SET lang in fr_FR', + ' document.getElementById(\'setlangFr\').addEventListener(\'click\', async function(e) {', + ' await Dolibarr.tools.langs.setLocale(\'fr_FR\');', + ' const txt = Dolibarr.tools.langs.trans(\'LangsLocalChangedTo\', \'fr_FR\');', + ' Dolibarr.tools.setEventMessage(txt);', + ' });', + '', + ' // SET lang in en_US', + ' document.getElementById(\'setlangEn\').addEventListener(\'click\', async function(e) {', + ' await Dolibarr.tools.langs.setLocale(\'en_US\');', + ' const txt = Dolibarr.tools.langs.trans(\'LangsLocalChangedTo\', \'en_US\');', + ' Dolibarr.tools.setEventMessage(txt);', + ' });', + '', + ' // pop a message in current lang', + ' document.getElementById(\'popmessage\').addEventListener(\'click\', async function(e) {', + ' const txt = Dolibarr.tools.langs.trans(\'ContextLangToolTest\');', + ' Dolibarr.tools.setEventMessage(txt);', + ' });', + ' }', + '});', + '', + ); + echo $documentation->showCode($lines, 'php'); + + print implode("\n", $lines); + ?> +

    1. Set the current lang

    +
    + + + +
    + +

    2. Pop translated message

    +
    + +
    +
    + +
    + + + + +
    + + + + +
    + + +docFooter(); +?> diff --git a/htdocs/ai/admin/custom_prompt.php b/htdocs/ai/admin/custom_prompt.php index def949824c9..cf6664c6690 100644 --- a/htdocs/ai/admin/custom_prompt.php +++ b/htdocs/ai/admin/custom_prompt.php @@ -46,7 +46,7 @@ $arrayofai = getListOfAIServices(); // Parameters $action = GETPOST('action', 'aZ09'); $backtopage = GETPOST('backtopage', 'alpha'); -$cancel = GETPOST('cancel'); +$cancel = GETPOST('cancel', 'alpha'); $modulepart = GETPOST('modulepart', 'aZ09'); // Used by actions_setmoduleoptions.inc.php $functioncode = GETPOST('functioncode', 'alpha'); diff --git a/htdocs/api/admin/index.php b/htdocs/api/admin/index.php index 017874a088d..f314ea73d9f 100644 --- a/htdocs/api/admin/index.php +++ b/htdocs/api/admin/index.php @@ -29,6 +29,10 @@ // Load Dolibarr environment require '../../main.inc.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/api.lib.php'; + /** * @var Conf $conf * @var DoliDB $db @@ -117,6 +121,10 @@ $linkback = ''.$langs->trans("ApiDesc")."
    \n"; print "
    \n"; diff --git a/htdocs/api/admin/token_list.php b/htdocs/api/admin/token_list.php new file mode 100644 index 00000000000..b16cb2e8311 --- /dev/null +++ b/htdocs/api/admin/token_list.php @@ -0,0 +1,319 @@ + + * Copyright (C) 2005-2016 Laurent Destailleur + * Copyright (C) 2011 Juanjo Menent + * Copyright (C) 2012-2018 Regis Houssin + * Copyright (C) 2015 Jean-François Ferry + * Copyright (C) 2024 MDW + * 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 + * 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/api/admin/index.php + * \ingroup api + * \brief Page to setup Webservices REST module + */ + +// Load Dolibarr environment +require '../../main.inc.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/api.lib.php'; + +/** + * @var Conf $conf + * @var DoliDB $db + * @var Form $form + * @var HookManager $hookmanager + * @var Translate $langs + * @var User $user + * + * @var string $dolibarr_main_url_root + */ + +// Load translation files required by the page +$langs->loadLangs(array('admin', 'users')); +$error = 0; + +if (!$user->admin) { + accessforbidden(); +} + +// Retrieve needed GETPOSTS for this file +// Action / Massaction +$action = GETPOST('action', 'aZ09'); +$massaction = GETPOST('massaction', 'alpha'); +$confirm = GETPOST('confirm', 'alpha'); +$toselect = GETPOST('toselect', 'array'); + +// List filters +$search_user = GETPOST('search_user', 'alpha'); +$search_entity = GETPOST('search_entity', 'alpha'); +$search_datec_startday = GETPOSTINT('search_datec_startday'); +$search_datec_startmonth = GETPOSTINT('search_datec_startmonth'); +$search_datec_startyear = GETPOSTINT('search_datec_startyear'); +$search_datec_endday = GETPOSTINT('search_datec_endday'); +$search_datec_endmonth = GETPOSTINT('search_datec_endmonth'); +$search_datec_endyear = GETPOSTINT('search_datec_endyear'); +$search_datec_start = dol_mktime(0, 0, 0, $search_datec_startmonth, $search_datec_startday, $search_datec_startyear); +$search_datec_end = dol_mktime(23, 59, 59, $search_datec_endmonth, $search_datec_endday, $search_datec_endyear); +$search_tms_startday = GETPOSTINT('search_tms_startday'); +$search_tms_startmonth = GETPOSTINT('search_tms_startmonth'); +$search_tms_startyear = GETPOSTINT('search_tms_startyear'); +$search_tms_endday = GETPOSTINT('search_tms_endday'); +$search_tms_endmonth = GETPOSTINT('search_tms_endmonth'); +$search_tms_endyear = GETPOSTINT('search_tms_endyear'); +$search_tms_start = dol_mktime(0, 0, 0, $search_tms_startmonth, $search_tms_startday, $search_tms_startyear); +$search_tms_end = dol_mktime(23, 59, 59, $search_tms_endmonth, $search_tms_endday, $search_tms_endyear); + +// Pagination +$limit = GETPOSTINT('limit') ? GETPOSTINT('limit') : $conf->liste_limit; +$sortfield = GETPOST('sortfield', 'aZ09comma'); +$sortorder = GETPOST('sortorder', 'aZ09comma'); +$page = GETPOSTISSET('pageplusone') ? (GETPOSTINT('pageplusone') - 1) : GETPOSTINT("page"); +if (empty($page) || $page < 0 || GETPOST('button_search', 'alpha') || GETPOST('button_removefilter', 'alpha')) { + $page = 0; +} +$offset = $limit * $page; +$pageprev = $page - 1; +$pagenext = $page + 1; + +if (!$sortfield) { + $sortfield = 'oat.tms'; +} +if (!$sortorder) { + $sortorder = 'DESC'; +} + +$arrayfields = array( + 'u.login' => array('label' => "User", 'checked' => '1'), + 'e.label' => array('label' => "Entity", 'checked' => '1'), + 'oat.datec' => array('label' => "DateCreation", 'checked' => '1'), + 'oat.tms' => array('label' => "DateModification", 'checked' => '1'), +); + +/* + * Action + */ +if (GETPOST('button_removefilter_x', 'alpha') || GETPOST('button_removefilter.x', 'alpha') || GETPOST('button_removefilter', 'alpha')) { // All tests are required to be compatible with all browsers + $search_user = ''; + $search_entity = ''; + $search_datec_startday = ''; + $search_datec_startmonth = ''; + $search_datec_startyear = ''; + $search_datec_endday = ''; + $search_datec_endmonth = ''; + $search_datec_endyear = ''; + $search_datec_start = ''; + $search_datec_end = ''; + $search_tms_startday = ''; + $search_tms_startmonth = ''; + $search_tms_startyear = ''; + $search_tms_endday = ''; + $search_tms_endmonth = ''; + $search_tms_endyear = ''; + $search_tms_start = ''; + $search_tms_end = ''; + + $toselect = array(); +} +if (GETPOST('button_removefilter_x', 'alpha') || GETPOST('button_removefilter.x', 'alpha') || GETPOST('button_removefilter', 'alpha') + || GETPOST('button_search_x', 'alpha') || GETPOST('button_search.x', 'alpha') || GETPOST('button_search', 'alpha')) { + $massaction = ''; // Protection to avoid mass action if we force a new search during a mass action confirmation +} +if (($action == 'delete' && $confirm == 'yes')) { + $db->begin(); + + $nbok = 0; + $TMsg = array(); + + //$toselect could contain duplicate entries, cf https://github.com/Dolibarr/dolibarr/issues/26244 + $unique_arr = array_unique($toselect); + foreach ($unique_arr as $toselectid) { + $sql = "DELETE FROM ".MAIN_DB_PREFIX."oauth_token"; + $sql .= " WHERE rowid = ".((int) $toselectid); + $sql .= " AND service = 'dolibarr_rest_api'"; + + $result = $db->query($sql); + + if ($result > 0) { + $nbok++; + } else { + setEventMessages($db->error(), null, 'errors'); + $error++; + break; + } + } + + if (empty($error)) { + // Message for elements well deleted + if ($nbok > 1) { + setEventMessages($langs->trans("RecordsDeleted", $nbok), null, 'mesgs'); + } elseif ($nbok > 0) { + setEventMessages($langs->trans("RecordDeleted", $nbok), null, 'mesgs'); + } else { + setEventMessages($langs->trans("NoRecordDeleted"), null, 'mesgs'); + } + $db->commit(); + } else { + $db->rollback(); + } + + //var_dump($listofobjectthirdparties);exit; +} + +/* + * View + */ + +$nbtotalofrecords = ''; +if (!getDolGlobalInt('MAIN_DISABLE_FULL_SCANLIST')) { + /* The fast and low memory method to get and count full list converts the sql into a sql count */ + $sqlforcount = 'SELECT COUNT(*) as nbtotalofrecords'; + $sqlforcount .= " FROM ".MAIN_DB_PREFIX."oauth_token as oat"; + $sqlforcount .= " WHERE entity IN (0, ".((int) $conf->entity).")"; + $sqlforcount .= " AND service = 'dolibarr_rest_api'"; + $resql = $db->query($sqlforcount); + if ($resql) { + $objforcount = $db->fetch_object($resql); + $nbtotalofrecords = $objforcount->nbtotalofrecords; + } else { + dol_print_error($db); + } + + if (($page * $limit) > $nbtotalofrecords) { // if total resultset is smaller then paging size (filtering), goto and load page 0 + $page = 0; + $offset = 0; + } + $db->free($resql); +} + +$sql = "SELECT oat.rowid, oat.tokenstring, oat.entity, oat.state as rights, oat.fk_user, oat.datec as date_creation, oat.tms as date_modification,"; +$sql .= " oat.lastaccess, oat.apicount_total"; +$sql .= " FROM ".MAIN_DB_PREFIX."oauth_token as oat"; +$sql .= " WHERE service = 'dolibarr_rest_api'"; +$sql .= " AND EXISTS(SELECT 'exist' FROM llx_user as u WHERE u.api_key IS NOT NULL AND u.rowid = oat.fk_user)"; +if ($search_user) { + $sql .= " AND EXISTS (SELECT 'exist' FROM ".MAIN_DB_PREFIX."user u"; + $sql .= " WHERE (u.lastname LIKE '%".$db->escape($search_user)."%'"; + $sql .= " OR u.firstname LIKE '%".$db->escape($search_user)."%')"; + $sql .= " AND oat.fk_user = u.rowid))"; +} +if ($search_datec_start) { + $sql .= " AND oat.datec >= '".$db->idate($search_datec_start)."'"; +} +if ($search_datec_end) { + $sql .= " AND oat.datec <= '".$db->idate($search_datec_end)."'"; +} +if ($search_tms_start) { + $sql .= " AND oat.tms >= '".$db->idate($search_tms_start)."'"; +} +if ($search_tms_end) { + $sql .= " AND oat.tms <= '".$db->idate($search_tms_end)."'"; +} +$sql .= $db->order($sortfield, $sortorder); +if ($limit) { + $sql .= $db->plimit($limit + 1, $offset); +} + +$resql = $db->query($sql); + +$num = $db->num_rows($resql); + +llxHeader('', '', '', '', 0, 0, '', '', '', 'mod-api page-admin-index'); + +$param = ''; +if ($limit > 0 && $limit != $conf->liste_limit) { + $param .= '&limit='.((int) $limit); +} +if ($search_datec_startday) { + $param .= '&search_date_startday='.urlencode((string) ($search_datec_startday)); +} +if ($search_datec_startmonth) { + $param .= '&search_date_startmonth='.urlencode((string) ($search_datec_startmonth)); +} +if ($search_datec_startyear) { + $param .= '&search_date_startyear='.urlencode((string) ($search_datec_startyear)); +} +if ($search_datec_endday) { + $param .= '&search_date_endday='.urlencode((string) ($search_datec_endday)); +} +if ($search_datec_endmonth) { + $param .= '&search_date_endmonth='.urlencode((string) ($search_datec_endmonth)); +} +if ($search_datec_endyear) { + $param .= '&search_date_endyear='.urlencode((string) ($search_datec_endyear)); +} +if ($search_tms_startday) { + $param .= '&search_date_startday='.urlencode((string) ($search_tms_startday)); +} +if ($search_tms_startmonth) { + $param .= '&search_date_startmonth='.urlencode((string) ($search_tms_startmonth)); +} +if ($search_tms_startyear) { + $param .= '&search_date_startyear='.urlencode((string) ($search_tms_startyear)); +} +if ($search_tms_endday) { + $param .= '&search_date_endday='.urlencode((string) ($search_tms_endday)); +} +if ($search_tms_endmonth) { + $param .= '&search_date_endmonth='.urlencode((string) ($search_tms_endmonth)); +} +if ($search_tms_endyear) { + $param .= '&search_date_endyear='.urlencode((string) ($search_tms_endyear)); +} + +$arrayofselected = is_array($toselect) ? $toselect : array(); + +$linkback = '
    '.$langs->trans("BackToModuleList").''; +print load_fiche_titre($langs->trans("ApiSetup"), $linkback, 'title_setup'); + +$head = api_admin_prepare_head(); + +print dol_get_fiche_head($head, 'token_list', '', -1); + +$arrayofmassactions = array( + 'predelete' => img_picto('', 'delete', 'class="pictofixedwidth"').$langs->trans("Delete") +); + +if (GETPOSTINT('nomassaction') || in_array($massaction, array('presend', 'predelete'))) { + $arrayofmassactions = array(); +} +$massactionbutton = $form->selectMassAction('', $arrayofmassactions); + +$morehtmlright = ''; +$tmpurlforbutton = DOL_URL_ROOT.'/user/api_token/card.php?action=create&backtopage='.urlencode(DOL_URL_ROOT.'/api/admin/token_list.php'); +$morehtmlright .= dolGetButtonTitle($langs->trans('New'), '', 'fa fa-plus-circle', $tmpurlforbutton); + +print '
    '; +print ''; +print ''; +print ''; +print ''; +print ''; + +// @phan-suppress-next-line PhanPluginSuspiciousParamOrder +print_barre_liste($langs->trans("ListOfTokensForAllUsers"), $page, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, $massactionbutton, $num, $nbtotalofrecords, 'fa-at', 0, $morehtmlright, '', $limit, 0, 0, 1); + +include DOL_DOCUMENT_ROOT.'/core/tpl/massactions_pre.tpl.php'; + +$colspan = 6; // Base colspan for empty list + +include DOL_DOCUMENT_ROOT.'/core/tpl/apitoken_list.tpl.php'; + +print ''; + +llxFooter(); +$db->close(); diff --git a/htdocs/api/class/api_access.class.php b/htdocs/api/class/api_access.class.php index 854a5ff0dd0..1f9ef89f12d 100644 --- a/htdocs/api/class/api_access.class.php +++ b/htdocs/api/class/api_access.class.php @@ -91,7 +91,7 @@ class DolibarrApiAccess implements iAuthenticate public function __isAllowed() { // phpcs:enable - global $conf, $db, $langs, $user; + global $conf, $langs, $user; $login = ''; $stored_key = ''; @@ -132,20 +132,34 @@ class DolibarrApiAccess implements iAuthenticate if ($api_key) { $userentity = 0; + $token_rowid = 0; - $sql = "SELECT u.login, u.datec, u.api_key,"; - $sql .= " u.tms as date_modification, u.entity"; - $sql .= " FROM ".MAIN_DB_PREFIX."user as u"; - $sql .= " WHERE u.api_key = '".$this->db->escape($api_key)."' OR u.api_key = '".$this->db->escape(dolEncrypt($api_key, '', '', 'dolibarr'))."'"; + if (!getDolGlobalString('API_IN_TOKEN_TABLE')) { + $sql = "SELECT u.login, u.datec, u.api_key as use_api, u.entity, u.api_key as api_key, u.entity as token_entity, 0 as token_rowid,"; + $sql .= " u.tms as date_modification"; + $sql .= " FROM ".MAIN_DB_PREFIX."user as u"; + $sql .= " WHERE u.api_key = '".$this->db->escape($api_key)."' OR u.api_key = '".$this->db->escape(dolEncrypt($api_key, '', '', 'dolibarr'))."'"; + } else { + $sql = "SELECT u.login, u.datec, u.api_key as use_api, u.entity, oat.tokenstring as api_key, oat.entity as token_entity, rowid as token_rowid,"; + $sql .= " oat.tms as date_modification"; + $sql .= " FROM ".MAIN_DB_PREFIX."oauth_token AS oat"; + $sql .= " JOIN ".MAIN_DB_PREFIX."user AS u ON u.rowid = oat.fk_user"; + $sql .= " WHERE (oat.tokenstring = '".$this->db->escape($api_key)."'"; + $sql .= " OR oat.tokenstring = '".$this->db->escape(dolEncrypt($api_key, '', '', 'dolibarr'))."')"; + $sql .= " AND oat.service = 'dolibarr_rest_api'"; + } $result = $this->db->query($sql); if ($result) { $nbrows = $this->db->num_rows($result); if ($nbrows == 1) { $obj = $this->db->fetch_object($result); + $login = $obj->login; $stored_key = dolDecrypt($obj->api_key); $userentity = $obj->entity; + $token_entity = $obj->token_entity; + $token_rowid = $obj->token_rowid; if (!defined("DOLENTITY") && $conf->entity != ($obj->entity ? $obj->entity : 1)) { // If API was not forced with HTTP_DOLENTITY, and user is on another entity, so we reset entity to entity of user $conf->entity = ($obj->entity ? $obj->entity : 1); @@ -157,7 +171,7 @@ class DolibarrApiAccess implements iAuthenticate // see master.inc.php require_once DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php'; - $fmysoc = new Societe($db); + $fmysoc = new Societe($this->db); $fmysoc->setMysoc($conf); // We set some specific default values according to country @@ -194,7 +208,7 @@ class DolibarrApiAccess implements iAuthenticate $mysoc = $fmysoc; // Reload langs - $langcode = (!getDolGlobalString('MAIN_LANG_DEFAULT') ? 'auto' : $conf->global->MAIN_LANG_DEFAULT); + $langcode = getDolGlobalString('MAIN_LANG_DEFAULT', 'auto'); if (!empty($user->conf->MAIN_LANG_DEFAULT)) { $langcode = $user->conf->MAIN_LANG_DEFAULT; } @@ -204,6 +218,10 @@ class DolibarrApiAccess implements iAuthenticate $langs->loadLangs(array('main')); } } + + if ($conf->entity != ($token_entity ? $token_entity : 1)) { + throw new RestException(401, "functions_isallowed::check_user_api_key Authentication KO for '".$login."': Token not valid (may be a typo or a wrong entity)"); + } } elseif ($nbrows > 1) { throw new RestException(503, 'Error when fetching user api_key : More than 1 user with this apikey'); } @@ -256,12 +274,27 @@ class DolibarrApiAccess implements iAuthenticate throw new RestException(401, $genericmessageerroruser); } - // TODO // Increase counter of API access if (getDolGlobalString('API_COUNTER_ENABLED')) { - include DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php'; - dolibarr_set_const($this->db, 'API_COUNTER_COUNT', getDolGlobalInt('API_COUNTER_COUNT') + 1); - //var_dump('eeee');exit; + if (!getDolGlobalString('API_IN_TOKEN_TABLE')) { + // Update the counter into table llx_const + include DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php'; + dolibarr_set_const($this->db, 'API_COUNTER_COUNT', getDolGlobalInt('API_COUNTER_COUNT') + 1); + //var_dump('eeee');exit; + } else { + // Update the counter into table llx_oauth_token + $tmpnow = dol_getdate(dol_now('gmt'), true, 'gmt'); + + $sqlforcounter = "UPDATE ".$this->db->prefix()."oauth_token SET "; + $sqlforcounter .= " apicount_total = apicount_total + 1,"; + $sqlforcounter .= " apicount_month = apicount_month + 1,"; + // if last access was done during previous month, we save pageview_month into pageviews_previous_month + $sqlforcounter .= " pageviews_previous_month = ".$this->db->ifsql("lastaccess < '".$this->db->idate(dol_mktime(0, 0, 0, $tmpnow['mon'], 1, $tmpnow['year'], 'gmt', 0), 'gmt')."'", 'apicount_month', 'apicount_previous_month').","; + $sqlforcounter .= " lastaccess = '".$this->db->idate(dol_now('gmt'), 'gmt')."'"; + $sqlforcounter .= " WHERE rowid = ".((int) $token_rowid); + + $this->db->query($sqlforcounter); + } } // User seems valid diff --git a/htdocs/api/class/api_emailtemplates.class.php b/htdocs/api/class/api_emailtemplates.class.php index dacc7294bdf..86205906f62 100644 --- a/htdocs/api/class/api_emailtemplates.class.php +++ b/htdocs/api/class/api_emailtemplates.class.php @@ -1,6 +1,7 @@ + * Copyright (C) 2025 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 @@ -500,14 +501,15 @@ class EmailTemplates extends DolibarrApi // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore /** * Clean sensible object datas + * @phpstan-template T * * @param Object $object Object to clean * @phan-param CEmailTemplate $object - * @phpstan-param CEmailTemplate $object + * @phpstan-param T $object * * @return Object Object with cleaned properties * @phan-return CEmailTemplate - * @phpstan-return CEmailTemplate + * @phpstan-return T */ protected function _cleanObjectDatas($object) { diff --git a/htdocs/api/class/api_objectlinks.class.php b/htdocs/api/class/api_objectlinks.class.php index b800a4d8507..8811ad6f15a 100644 --- a/htdocs/api/class/api_objectlinks.class.php +++ b/htdocs/api/class/api_objectlinks.class.php @@ -408,14 +408,15 @@ class ObjectLinks extends DolibarrApi // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore /** * Clean sensible object datas + * @phpstan-template T * * @param Object $object Object to clean * @phan-param ObjectLink $object - * @phpstan-param ObjectLink $object + * @phpstan-param T $object * * @return Object Object with cleaned properties * @phan-return ObjectLink - * @phpstan-return ObjectLink + * @phpstan-return T */ protected function _cleanObjectDatas($object) { diff --git a/htdocs/api/class/api_setup.class.php b/htdocs/api/class/api_setup.class.php index b137148c319..fbd951ba5b8 100644 --- a/htdocs/api/class/api_setup.class.php +++ b/htdocs/api/class/api_setup.class.php @@ -1,13 +1,13 @@ - * Copyright (C) 2016 Laurent Destailleur - * Copyright (C) 2017 Regis Houssin - * Copyright (C) 2017 Neil Orley - * Copyright (C) 2018-2025 Frédéric France - * Copyright (C) 2018-2022 Thibault FOUCART - * Copyright (C) 2024 Jon Bendtsen +/* Copyright (C) 2016 Xebax Christy + * Copyright (C) 2016 Laurent Destailleur + * Copyright (C) 2017-2025 Regis Houssin + * Copyright (C) 2017 Neil Orley + * Copyright (C) 2018-2025 Frédéric France + * Copyright (C) 2018-2022 Thibault FOUCART + * Copyright (C) 2024 Jon Bendtsen * Copyright (C) 2024-2025 MDW - * Copyright (C) 2025 Charlene Benke + * Copyright (C) 2025 Charlene Benke * * * This program is free software; you can redistribute it and/or modify @@ -836,9 +836,12 @@ class Setup extends DolibarrApi // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore /** * Clean sensible object datas + * @phpstan-template T * * @param Object $object Object to clean * @return Object Object with cleaned properties + * @phpstan-param T $object + * @phpstan-return T */ protected function _cleanObjectDatas($object) { @@ -974,6 +977,10 @@ class Setup extends DolibarrApi { $list = array(); + if (!DolibarrApiAccess::$user->hasRight('expensereport', 'lire')) { + throw new RestException(403); + } + $sql = "SELECT id, code, label, accountancy_code, active, module, position"; $sql .= " FROM ".MAIN_DB_PREFIX."c_type_fees as t"; $sql .= " WHERE t.active = ".((int) $active); @@ -1016,6 +1023,165 @@ class Setup extends DolibarrApi return $list; } + /** + * Get the list of holiday types. + * + * @param string $sortfield Sort field + * @param string $sortorder Sort order + * @param int $limit Number of items per page + * @param int $page Page number (starting from zero) + * @param string $fk_country To filter on country + * @param int $active Holiday is active or not {@min 0} {@max 1} + * @param string $lang Code of the language the label of the holiday must be translated to + * @param string $sqlfilters Other criteria to filter answers separated by a comma. Syntax example "(t.code:like:'A%') and (t.active:>=:0)" + * @return array List of holiday types + * @phan-return array + * @phpstan-return array + * + * @url GET dictionary/holiday_types + * + * @throws RestException 400 Bad value for sqlfilters + * @throws RestException 503 Error when retrieving list of holiday types + */ + public function getListOfHolidayTypes($sortfield = "sortorder", $sortorder = 'ASC', $limit = 100, $page = 0, $fk_country = '', $active = 1, $lang = '', $sqlfilters = '') + { + global $langs; + $langs->loadLangs(array('holiday')); + + if (!DolibarrApiAccess::$user->hasRight('holiday', 'lire')) { + throw new RestException(403); + } + + $list = array(); + + $sql = "SELECT rowid, code, label, affect, delay, newbymonth, fk_country"; + $sql .= " FROM ".MAIN_DB_PREFIX."c_holiday_types as t"; + $sql .= " WHERE t.entity IN (".getEntity('c_holiday_types').")"; + $sql .= " AND t.active = ".((int) $active); + if ($fk_country) { + $sql .= " AND (t.fk_country = ".((int) $fk_country); + $sql .= " OR t.fk_country is null)"; + } + // Add sql filters + if ($sqlfilters) { + $errormessage = ''; + $sql .= forgeSQLFromUniversalSearchCriteria($sqlfilters, $errormessage); + if ($errormessage) { + throw new RestException(400, 'Error when validating parameter sqlfilters -> '.$errormessage); + } + } + + $sql .= $this->db->order($sortfield, $sortorder); + + if ($limit) { + if ($page < 0) { + $page = 0; + } + $offset = $limit * $page; + + $sql .= $this->db->plimit($limit, $offset); + } + + $result = $this->db->query($sql); + + if ($result) { + $num = $this->db->num_rows($result); + $min = min($num, ($limit <= 0 ? $num : $limit)); + for ($i = 0; $i < $min; $i++) { + $holiday = $this->db->fetch_object($result); + $tmplabel = $langs->trans($holiday->code); + if ($tmplabel != $holiday->code) { + $holiday->label = $tmplabel; + } + //$this->translateLabel($holiday, $lang, 'Holiday', array('dict')); + $list[] = $holiday; + } + } else { + throw new RestException(503, 'Error when retrieving list of holiday : '.$this->db->lasterror()); + } + + return $list; + } + + /** + * Get the list of public holiday. + * + * @param string $sortfield Sort field + * @param string $sortorder Sort order + * @param int $limit Number of items per page + * @param int $page Page number (starting from zero) + * @param string $fk_country To filter on country + * @param int $active Holiday is active or not {@min 0} {@max 1} + * @param string $lang Code of the language the label of the holiday must be translated to + * @param string $sqlfilters Other criteria to filter answers separated by a comma. Syntax example "(t.code:like:'A%') and (t.active:>=:0)" + * @return array List of public holiday + * @phan-return array + * @phpstan-return array + * + * @url GET dictionary/public_holiday + * + * @throws RestException 400 Bad value for sqlfilters + * @throws RestException 503 Error when retrieving list of holiday types + */ + public function getListOfPublicHolidays($sortfield = "code", $sortorder = 'ASC', $limit = 100, $page = 0, $fk_country = '', $active = 1, $lang = '', $sqlfilters = '') + { + global $langs; + $langs->loadLangs(array('hrm')); + + if (!DolibarrApiAccess::$user->hasRight('holiday', 'lire')) { + throw new RestException(403); + } + + $list = array(); + + $sql = "SELECT id, code, dayrule, day, month, year, fk_country, code as label"; + $sql .= " FROM ".MAIN_DB_PREFIX."c_hrm_public_holiday as t"; + $sql .= " WHERE t.entity IN (".getEntity('c_hrm_public_holiday').")"; + $sql .= " AND t.active = ".((int) $active); + if ($fk_country) { + $sql .= " AND (t.fk_country = ".((int) $fk_country); + $sql .= " OR t.fk_country is null)"; + } + // Add sql filters + if ($sqlfilters) { + $errormessage = ''; + $sql .= forgeSQLFromUniversalSearchCriteria($sqlfilters, $errormessage); + if ($errormessage) { + throw new RestException(400, 'Error when validating parameter sqlfilters -> '.$errormessage); + } + } + + $sql .= $this->db->order($sortfield, $sortorder); + + if ($limit) { + if ($page < 0) { + $page = 0; + } + $offset = $limit * $page; + + $sql .= $this->db->plimit($limit, $offset); + } + + $result = $this->db->query($sql); + + if ($result) { + $num = $this->db->num_rows($result); + $min = min($num, ($limit <= 0 ? $num : $limit)); + for ($i = 0; $i < $min; $i++) { + $holiday = $this->db->fetch_object($result); + $tmplabel = $langs->trans($holiday->code); + if ($tmplabel != $holiday->code) { + $holiday->label = $tmplabel; + } + //$this->translateLabel($holiday, $lang, 'Holiday', array('dict')); + $list[] = $holiday; + } + } else { + throw new RestException(503, 'Error when retrieving list of public holiday : '.$this->db->lasterror()); + } + + return $list; + } /** * Get the list of contacts types. diff --git a/htdocs/asset/accountancy_codes.php b/htdocs/asset/accountancy_codes.php index 3684eb407d8..f8cd918f988 100644 --- a/htdocs/asset/accountancy_codes.php +++ b/htdocs/asset/accountancy_codes.php @@ -45,7 +45,7 @@ $langs->loadLangs(array("assets", "companies")); $id = GETPOSTINT('id'); $ref = GETPOST('ref', 'alpha'); $action = GETPOST('action', 'aZ09'); -$cancel = GETPOST('cancel', 'aZ09'); +$cancel = GETPOST('cancel', 'alpha'); $backtopage = GETPOST('backtopage', 'alpha'); $backtopageforcancel = GETPOST('backtopageforcancel', 'alpha'); diff --git a/htdocs/asset/agenda.php b/htdocs/asset/agenda.php index f389e962202..54166d6011e 100644 --- a/htdocs/asset/agenda.php +++ b/htdocs/asset/agenda.php @@ -46,7 +46,7 @@ $langs->loadLangs(array("assets", "other")); $id = GETPOSTINT('id'); $ref = GETPOST('ref', 'alpha'); $action = GETPOST('action', 'aZ09'); -$cancel = GETPOST('cancel', 'aZ09'); +$cancel = GETPOST('cancel', 'alpha'); $backtopage = GETPOST('backtopage', 'alpha'); if (GETPOST('actioncode', 'array')) { diff --git a/htdocs/asset/card.php b/htdocs/asset/card.php index 39fe2f99db5..6d7c7b9ad60 100644 --- a/htdocs/asset/card.php +++ b/htdocs/asset/card.php @@ -47,7 +47,7 @@ $id = GETPOSTINT('id'); $ref = GETPOST('ref', 'alpha'); $action = GETPOST('action', 'aZ09'); $confirm = GETPOST('confirm', 'alpha'); -$cancel = GETPOST('cancel', 'aZ09'); +$cancel = GETPOST('cancel', 'alpha'); $contextpage = GETPOST('contextpage', 'aZ') ? GETPOST('contextpage', 'aZ') : 'assetcard'; // To manage different context of search $backtopage = GETPOST('backtopage', 'alpha'); $backtopageforcancel = GETPOST('backtopageforcancel', 'alpha'); @@ -408,7 +408,7 @@ if ($object->id > 0 && (empty($action) || ($action != 'edit' && $action != 'crea if (empty($reshook)) { // Send if (empty($user->socid)) { - print dolGetButtonAction('', $langs->trans('SendMail'), 'default', $_SERVER["PHP_SELF"] . '?id=' . $object->id . '&action=presend&mode=init&token=' . newToken() . '#formmailbeforetitle'); + print dolGetButtonAction('', $langs->trans('SendMail'), 'email', $_SERVER["PHP_SELF"] . '?id=' . $object->id . '&action=presend&mode=init&token=' . newToken() . '#formmailbeforetitle'); } // Back to draft diff --git a/htdocs/asset/depreciation.php b/htdocs/asset/depreciation.php index cb8bd3164d5..a0715f1aff9 100644 --- a/htdocs/asset/depreciation.php +++ b/htdocs/asset/depreciation.php @@ -45,7 +45,7 @@ $langs->loadLangs(array("assets", "companies")); $id = GETPOSTINT('id'); $ref = GETPOST('ref', 'alpha'); $action = GETPOST('action', 'aZ09'); -$cancel = GETPOST('cancel', 'aZ09'); +$cancel = GETPOST('cancel', 'alpha'); $backtopage = GETPOST('backtopage', 'alpha'); // Initialize a technical objects diff --git a/htdocs/asset/depreciation_options.php b/htdocs/asset/depreciation_options.php index d4b4ca2d3fe..97483a0ad17 100644 --- a/htdocs/asset/depreciation_options.php +++ b/htdocs/asset/depreciation_options.php @@ -44,7 +44,7 @@ $langs->loadLangs(array("assets", "companies")); $id = GETPOSTINT('id'); $ref = GETPOST('ref', 'alpha'); $action = GETPOST('action', 'aZ09'); -$cancel = GETPOST('cancel', 'aZ09'); +$cancel = GETPOST('cancel', 'alpha'); $backtopage = GETPOST('backtopage', 'alpha'); $backtopageforcancel = GETPOST('backtopageforcancel', 'alpha'); // if not set, $backtopage will be used diff --git a/htdocs/asset/disposal.php b/htdocs/asset/disposal.php index 13b5ef338ad..1b1934827e6 100644 --- a/htdocs/asset/disposal.php +++ b/htdocs/asset/disposal.php @@ -44,7 +44,7 @@ $langs->loadLangs(array("assets", "companies")); $id = GETPOSTINT('id'); $ref = GETPOST('ref', 'alpha'); $action = GETPOST('action', 'aZ09'); -$cancel = GETPOST('cancel', 'aZ09'); +$cancel = GETPOST('cancel', 'alpha'); $backtopage = GETPOST('backtopage', 'alpha'); // Initialize a technical objects diff --git a/htdocs/asset/model/accountancy_codes.php b/htdocs/asset/model/accountancy_codes.php index f38871b0418..5c9e07546ca 100644 --- a/htdocs/asset/model/accountancy_codes.php +++ b/htdocs/asset/model/accountancy_codes.php @@ -44,7 +44,7 @@ $langs->loadLangs(array("assets", "companies")); $id = GETPOSTINT('id'); $ref = GETPOST('ref', 'alpha'); $action = GETPOST('action', 'aZ09'); -$cancel = GETPOST('cancel', 'aZ09'); +$cancel = GETPOST('cancel', 'alpha'); $backtopage = GETPOST('backtopage', 'alpha'); // Initialize a technical objects diff --git a/htdocs/asset/model/agenda.php b/htdocs/asset/model/agenda.php index 3b47e4a9bb4..182a9d29a05 100644 --- a/htdocs/asset/model/agenda.php +++ b/htdocs/asset/model/agenda.php @@ -46,7 +46,7 @@ $langs->loadLangs(array("assets", "other")); $id = GETPOSTINT('id'); $ref = GETPOST('ref', 'alpha'); $action = GETPOST('action', 'aZ09'); -$cancel = GETPOST('cancel', 'aZ09'); +$cancel = GETPOST('cancel', 'alpha'); $backtopage = GETPOST('backtopage', 'alpha'); if (GETPOST('actioncode', 'array')) { diff --git a/htdocs/asset/model/card.php b/htdocs/asset/model/card.php index 9a13bc43fce..2de69000e54 100644 --- a/htdocs/asset/model/card.php +++ b/htdocs/asset/model/card.php @@ -48,7 +48,7 @@ $id = GETPOSTINT('id'); $ref = GETPOST('ref', 'alpha'); $action = GETPOST('action', 'aZ09'); $confirm = GETPOST('confirm', 'alpha'); -$cancel = GETPOST('cancel', 'aZ09'); +$cancel = GETPOST('cancel', 'alpha'); $contextpage = GETPOST('contextpage', 'aZ') ? GETPOST('contextpage', 'aZ') : 'assetmodelcard'; // To manage different context of search $backtopage = GETPOST('backtopage', 'alpha'); $backtopageforcancel = GETPOST('backtopageforcancel', 'alpha'); diff --git a/htdocs/asset/model/depreciation_options.php b/htdocs/asset/model/depreciation_options.php index 2db908b2ab6..38c599c0324 100644 --- a/htdocs/asset/model/depreciation_options.php +++ b/htdocs/asset/model/depreciation_options.php @@ -44,7 +44,7 @@ $langs->loadLangs(array("assets", "companies")); $id = GETPOSTINT('id'); $ref = GETPOST('ref', 'alpha'); $action = GETPOST('action', 'aZ09'); -$cancel = GETPOST('cancel', 'aZ09'); +$cancel = GETPOST('cancel'); $backtopage = GETPOST('backtopage', 'alpha'); $backtopageforcancel = GETPOST('backtopageforcancel', 'alpha'); diff --git a/htdocs/asset/model/note.php b/htdocs/asset/model/note.php index 4dea4c8b258..5fa63a2f7a8 100644 --- a/htdocs/asset/model/note.php +++ b/htdocs/asset/model/note.php @@ -43,7 +43,7 @@ $langs->loadLangs(array("assets", "companies")); $id = GETPOSTINT('id'); $ref = GETPOST('ref', 'alpha'); $action = GETPOST('action', 'aZ09'); -$cancel = GETPOST('cancel', 'aZ09'); +$cancel = GETPOST('cancel', 'alpha'); $backtopage = GETPOST('backtopage', 'alpha'); // Initialize a technical objects diff --git a/htdocs/asset/note.php b/htdocs/asset/note.php index 41ff1ffcd2c..a9e73605252 100644 --- a/htdocs/asset/note.php +++ b/htdocs/asset/note.php @@ -43,7 +43,7 @@ $langs->loadLangs(array("assets", "companies")); $id = GETPOSTINT('id'); $ref = GETPOST('ref', 'alpha'); $action = GETPOST('action', 'aZ09'); -$cancel = GETPOST('cancel', 'aZ09'); +$cancel = GETPOST('cancel', 'alpha'); $backtopage = GETPOST('backtopage', 'alpha'); // Initialize a technical objects diff --git a/htdocs/blockedlog/admin/blockedlog_archives.php b/htdocs/blockedlog/admin/blockedlog_archives.php index bc3811c0458..7dc47a0fbfc 100644 --- a/htdocs/blockedlog/admin/blockedlog_archives.php +++ b/htdocs/blockedlog/admin/blockedlog_archives.php @@ -41,22 +41,21 @@ require_once DOL_DOCUMENT_ROOT.'/blockedlog/lib/blockedlog.lib.php'; require_once DOL_DOCUMENT_ROOT.'/blockedlog/class/blockedlog.class.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/lib/files.lib.php'; require_once DOL_DOCUMENT_ROOT.'/core/class/html.formother.class.php'; // Load translation files required by the page $langs->loadLangs(array('admin', 'banks', 'bills', 'blockedlog', 'other')); -// Access Control -if ((!$user->admin && !$user->hasRight('blockedlog', 'read')) || empty($conf->blockedlog->enabled)) { - accessforbidden(); -} - // Get Parameters $action = GETPOST('action', 'aZ09'); +$confirm = GETPOST('confirm', 'aZ09'); $contextpage = GETPOST('contextpage', 'aZ') ? GETPOST('contextpage', 'aZ') : getDolDefaultContextPage(__FILE__); // To manage different context of search $backtopage = GETPOST('backtopage', 'alpha'); // Go back to a dedicated page $optioncss = GETPOST('optioncss', 'aZ'); // Option for the css output (always '' except when 'print') +$hmacexportkey = GETPOST('hmacexportkey', 'password'); + $search_showonlyerrors = GETPOSTINT('search_showonlyerrors'); if ($search_showonlyerrors < 0) { $search_showonlyerrors = 0; @@ -113,6 +112,14 @@ if (empty($sortorder)) { $block_static = new BlockedLog($db); $block_static->loadTrackedEvents(); +// Access Control +if ((!$user->admin && !$user->hasRight('blockedlog', 'read')) || !isModEnabled('blockedlog')) { + accessforbidden(); +} + +// We force also permission to write because it does not exists and we need it to upload a file +$user->rights->blockedlog->create = 1; + $result = restrictedArea($user, 'blockedlog', 0, ''); // Execution Time @@ -126,6 +133,14 @@ if ($max_time && $max_time < $max_execution_time_for_importexport) { $MAXLINES = getDolGlobalInt('BLOCKEDLOG_MAX_LINES', 10000); $MAXFORSHOWNLINKS = getDolGlobalInt('BLOCKEDLOG_MAX_FOR_SHOWN_LINKS', 100); +$permission = $user->hasRight('blockedlog', 'read'); +$permissiontoadd = $user->hasRight('blockedlog', 'read'); // Permission is to upload new files to scan them +$permtoedit = $permissiontoadd; + +$upload_dir = getMultidirOutput($block_static, 'blockedlog').'/archives'; + +dol_mkdir($upload_dir); + /* * Actions @@ -152,7 +167,9 @@ if (GETPOST('button_removefilter_x', 'alpha') || GETPOST('button_removefilter.x' $search_array_options = array(); } -if (GETPOST('downloadcsv', 'alpha')) { +include DOL_DOCUMENT_ROOT.'/core/actions_linkedfiles.inc.php'; + +if (GETPOST('action') == 'upload' && $user->hasRight('blockedlog', 'read')) { // read is read/upload for blockedlog $error = 0; $previoushash = ''; @@ -161,7 +178,13 @@ if (GETPOST('downloadcsv', 'alpha')) { if (! (GETPOSTINT('yeartoexport') > 0)) { setEventMessages($langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("Year")), null, "errors"); $error++; - } else { + } + if (empty($hmacexportkey)) { + setEventMessages($langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("Password")), null, "errors"); + $error++; + } + + if (!$error) { // Get the ID of the first line qualified $sql = "SELECT rowid,date_creation,tms,user_fullname,action,amounts,element,fk_object,date_object,ref_object,signature,fk_user,object_data"; $sql .= " FROM ".MAIN_DB_PREFIX."blockedlog"; @@ -191,7 +214,7 @@ if (GETPOST('downloadcsv', 'alpha')) { } } - if (! $error) { + if (!$error) { // We record the export as a new line into the unalterable logs require_once DOL_DOCUMENT_ROOT.'/blockedlog/class/blockedlog.class.php'; $b = new BlockedLog($db); @@ -237,28 +260,32 @@ if (GETPOST('downloadcsv', 'alpha')) { $resql = $db->query($sql); if ($resql) { - $nameofdownoadedfile = "unalterable-log-archive-".$dolibarr_main_db_name."-".(GETPOSTINT('yeartoexport') > 0 ? GETPOSTINT('yeartoexport').(GETPOSTINT('monthtoexport') > 0 ? sprintf("%02d", GETPOSTINT('monthtoexport')) : '').'-' : '').dol_print_date(dol_now(), 'dayhourlog', 'gmt').'UTC-DONOTMODIFY'; + $yearmonthtoexport = GETPOSTINT('yeartoexport').(GETPOSTINT('monthtoexport') > 0 ? sprintf("%02d", GETPOSTINT('monthtoexport')) : ''); + $yearmonthdateofexport = dol_print_date(dol_now(), 'dayhourlog', 'gmt'); - $tmpfile = $conf->admin->dir_temp.'/unalterable-log-archive-tmp-'.$user->id.'.csv'; + $nameofdownoadedfile = "unalterable-log-archive-".$dolibarr_main_db_name."-".$yearmonthtoexport.'-'.$yearmonthdateofexport.'UTC-DONOTMODIFY.csv'; + + //$tmpfile = $conf->admin->dir_temp.'/unalterable-log-archive-tmp-'.$user->id.'.csv'; + $tmpfile = getMultidirOutput($block_static, 'blockedlog').'/archives/'.$nameofdownoadedfile; $fh = fopen($tmpfile, 'w'); // Print line with title - fwrite($fh, $langs->transnoentities('Id') + fwrite($fh, "BEGIN - date=".$yearmonthdateofexport + .';'.$langs->transnoentities('Id') + .';'.$langs->transnoentities('DateCreation') + .';'.$langs->transnoentities('Action') + .';'.$langs->transnoentities('Amounts') + .';'.$langs->transnoentities('Ref') .';'.$langs->transnoentities('Date') .';'.$langs->transnoentities('User') - .';'.$langs->transnoentities('Action') - .';'.$langs->transnoentities('Element') - .';'.$langs->transnoentities('Amounts') - .';'.$langs->transnoentities('ObjectId') - .';'.$langs->transnoentities('Date') - .';'.$langs->transnoentities('Ref') + .';'.$langs->transnoentities('LinkTo') + .';'.$langs->transnoentities('LinkType') + .';'.$langs->transnoentities('FullData') + .';'.$langs->transnoentities('Version') .';'.$langs->transnoentities('Fingerprint') .';'.$langs->transnoentities('Status') - .';'.$langs->transnoentities('Note') - .';'.$langs->transnoentities('Version') - .';'.$langs->transnoentities('FullData') - .';'.$langs->transnoentities('DebugInfo') + .';'.$langs->transnoentities('FingerprintExport') ."\n"); $loweridinerror = 0; @@ -323,21 +350,24 @@ if (GETPOST('downloadcsv', 'alpha')) { $statusofrecordnote = $langs->trans("PreviousFingerprint").': '.$previoushash.($statusofrecordnote ? ' - '.$statusofrecordnote : ''); } - fwrite($fh, $block_static->id + $signatureexport = 'TODO'; + + fwrite($fh, + ';'.$block_static->id .';'.$block_static->date_creation - .';"'.str_replace('"', '""', $block_static->user_fullname).'";' - .$block_static->action - .';'.$block_static->element + .';'.$block_static->action .';'.$block_static->amounts // Can be 1.20000000 with 8 digits. TODO Clean to have 8 digits in V1 - .';'.$block_static->fk_object - .';'.$block_static->date_object - .';"'.str_replace('"', '""', $block_static->ref_object).'";"' - .$block_static->signature.'";' - .$statusofrecord - .';'.$statusofrecordnote - .';'.$block_static->object_version - .';"'.str_replace('"', '""', $obj->object_data).'"' // We must the string to decode into object with dolDecodeBlockedData - .';"'.str_replace('"', '""', $block_static->debuginfo).'"' + .';"'.str_replace('"', '""', $block_static->ref_object).'";' + .$block_static->date_object + .';"'.str_replace('"', '""', $block_static->user_fullname).'";"' + .str_replace('"', '""', $block_static->linktoref).'";"' + .str_replace('"', '""', $block_static->linktype).'";"' + .str_replace('"', '""', $obj->object_data).'"' // We must the string to decode into object with dolDecodeBlockedData + .';"'.str_replace('"', '""', $block_static->object_version).'";"' + .str_replace('"', '""', $block_static->signature).'";"' + .str_replace('"', '""', $statusofrecord).'";"' + .str_replace('"', '""', $signatureexport).'"' + //.';'.$statusofrecordnote ."\n"); // Set new previous hash for next fetch @@ -349,18 +379,14 @@ if (GETPOST('downloadcsv', 'alpha')) { fclose($fh); // Calculate the md5 of the file (the last line has a return line) - $md5value = md5_file($tmpfile); + $algo = 'sha256'; + $secretkey = 'TODOASKBEFOREEXPORT'; + $hmacsha256 = hash_hmac_file($algo, $tmpfile, $secretkey); // Now add a signature to check integrity at end of file - file_put_contents($tmpfile, 'END - md5='.$md5value, FILE_APPEND); + file_put_contents($tmpfile, 'END - hmac_sha256='.$hmacsha256, FILE_APPEND); - header('Content-Type: application/octet-stream'); - header("Content-Transfer-Encoding: Binary"); - header("Content-disposition: attachment; filename=\"".$nameofdownoadedfile.".csv\""); - - readfile($tmpfile); - - exit; + setEventMessages($langs->trans("FileGenerated"), null); } else { setEventMessages($db->lasterror, null, 'errors'); } @@ -476,14 +502,37 @@ if (GETPOST('withtab', 'alpha')) { // Add $param from extra fields //include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_list_search_param.tpl.php'; -print '
    '; +if ($action == 'deletefile') { + $langs->load("companies"); // Need for string DeleteFile+ConfirmDeleteFiles + print $form->formconfirm( + $_SERVER["PHP_SELF"].'?urlfile='.urlencode(GETPOST("urlfile")).'&linkid='.GETPOSTINT('linkid').(empty($param) ? '' : $param), + $langs->trans('DeleteFile'), + $langs->trans('ConfirmDeleteFile'), + 'confirm_deletefile', + '', + '', + 1 + ); +} + + +print ''; print ''; +print ''; print '
    '; + print $langs->trans("RestrictYearToExport").': '; // Month -print $formother->select_month((string) GETPOSTINT('monthtoexport'), 'monthtoexport', 1, 0, 'minwidth50 maxwidth75imp valignmiddle', true); +print $formother->select_month((string) GETPOSTINT('monthtoexport'), 'monthtoexport', $langs->trans("Month"), 0, 'minwidth50 maxwidth75imp valignmiddle', true); print ''; + +print ' '; + +print ''; + +print ' '; + print ''; print ''; /*if (getDolGlobalString('BLOCKEDLOG_USE_REMOTE_AUTHORITY')) { @@ -493,6 +542,8 @@ print '

    '; print ''; + +/* print '
    '; if ($optioncss != '') { @@ -508,13 +559,85 @@ print ''; print ''; print '
    '; // You can use div-table-responsive-no-min if you don't need reserved height for your table +*/ -// TODO List of archive files into blockedlog/archives +$filearray = dol_dir_list($upload_dir, 'files', 0, '', null, 'name', SORT_ASC, 1); +$modulepart = 'blockedlog'; +$relativepathwithnofile = 'archives/'; +$disablemove = 1; +/* +$param = '&id='.$object->id.'&entity='.(empty($object->entity) ? getDolEntity() : $object->entity); +include DOL_DOCUMENT_ROOT.'/core/tpl/document_actions_post_headers.tpl.php'; +*/ +include_once DOL_DOCUMENT_ROOT.'/core/class/html.formfile.class.php'; +$formfile = new FormFile($db); + +$savingdocmask = ''; + +$object = $block_static; + +// Get the form to add files (upload and links) +$tmparray = $formfile->form_attach_new_file( + $_SERVER["PHP_SELF"], + '', + 0, + 0, + $permission, + $conf->browser->layout == 'phone' ? 40 : 60, + $object, + '', + 1, + $savingdocmask, + 1, + 'formuserfile', + '', + '', + 0, + 0, + 0, + 2 +); + +$formToUploadAFile = ''; + +if (is_array($tmparray) && !empty($tmparray)) { + $formToUploadAFile = $tmparray['formToUploadAFile']; +} + +// List of document +// TODO Replace with specific code to list files with mass action, ... +$formfile->list_of_documents( + $filearray, + null, + $modulepart, + $param, + 0, + $relativepathwithnofile, // relative path with no file. For example "0/1" + $permission, + 0, + '', + 0, + $langs->transnoentitiesnoconv('Archives'), + '', + 0, + $permtoedit, + $upload_dir, + $sortfield, + $sortorder, + $disablemove, + 0, + -1, + '', + array('afteruploadtitle' => $formToUploadAFile, 'showhideaddbutton' => 1) +); + +/* print '
    '; print ''; +*/ // Javascript to manage the showinfo popup print ''; + + '; - //print ''; + // print ''; } print ''; @@ -1700,7 +1707,7 @@ if ($action == 'create') { $percent = 100; } } - $formactions->form_select_status_action('formaction', $percent, 1, 'complete', 0, 0, 'minwidth150 maxwidth300'); + print $formactions->form_select_status_action('formaction', $percent, 1, 'complete', 0, 0, 'minwidth150 maxwidth300', 1); print ''; if (isModEnabled("societe")) { @@ -1774,18 +1781,20 @@ if ($action == 'create') { $url = dol_buildpath('comm/action/card.php', 2).$urloption; // update task list - print "\n".''."\n"; + ?> + + '; @@ -1931,39 +1940,37 @@ if ($action == 'create') { }); })'; print ''."\n"; - - print "\n".''."\n"; + ?> + + 0 && $action != 'create') { $url = DOL_URL_ROOT.'/comm/action/card.php'.$urloption; // update task list - print "\n".''."\n"; + ?> + + selectTasks((!empty($societe->id) ? $societe->id : -1), $object->elementid, 'fk_element', 24, 0, '', 1, 0, 0, 'maxwidth500', (string) $object->fk_project, 'all', null, 1); print ''; @@ -2372,18 +2381,20 @@ if ($id > 0 && $action != 'create') { print '
  • '.$form->textwithpicto($langs->trans("CurrenciesUsed"), $langs->transnoentitiesnoconv("CurrenciesUsed_help_to_add")).''.$langs->trans("Rate").' / '.$langs->getCurrencySymbol($conf->currency).''.$langs->trans("Rate").' / '.$langs->getCurrencySymbol(getDolCurrency()).'
    '.$conf->currency; -print ' ('.$langs->getCurrencySymbol($conf->currency).')'; +print ''.getDolCurrency(); +print ' ('.$langs->getCurrencySymbol(getDolCurrency()).')'; print $form->textwithpicto(' ', $langs->trans("BaseCurrency")); -if (!empty($TAvailableCurrency[$conf->currency]) && empty($TAvailableCurrency[$conf->currency]['active'])) { +if (!empty($TAvailableCurrency[getDolCurrency()]) && empty($TAvailableCurrency[getDolCurrency()]['active'])) { print img_warning('Warning: This code has been disabled into Home - Setup - Dictionaries - Currencies'); } print '1
    '; // update task list - print "\n".''."\n"; + }); + + 0) { @@ -2409,7 +2420,7 @@ if ($id > 0 && $action != 'create') { // Description print '
    '.$langs->trans("Description").''; - // Editeur wysiwyg + // Wysiwyg editor require_once DOL_DOCUMENT_ROOT.'/core/class/doleditor.class.php'; $doleditor = new DolEditor('note', $object->note_private, '', 120, 'dolibarr_notes', 'In', true, true, isModEnabled('fckeditor'), ROWS_4, '90%'); $doleditor->Create(); @@ -2487,28 +2498,27 @@ if ($id > 0 && $action != 'create') { } print '
    '; - - print "\n".''."\n"; + ?> + + db); $tmpactioncommreminder->id = $obj->id; $tmpactioncommreminder->typeremind = $obj->typeremind; - $tmpactioncommreminder->dateremind = $obj->dateremind; + $tmpactioncommreminder->dateremind = $this->db->jdate($obj->dateremind); $tmpactioncommreminder->offsetvalue = $obj->offsetvalue; $tmpactioncommreminder->offsetunit = $obj->offsetunit; $tmpactioncommreminder->status = $obj->status; diff --git a/htdocs/comm/action/class/actioncommreminder.class.php b/htdocs/comm/action/class/actioncommreminder.class.php index 197abd94ae0..b6cdcfd2710 100644 --- a/htdocs/comm/action/class/actioncommreminder.class.php +++ b/htdocs/comm/action/class/actioncommreminder.class.php @@ -1,7 +1,7 @@ - * Copyright (C) 2024-2025 Frédéric France - * Copyright (C) 2024-2025 MDW +/* Copyright (C) 2017 Laurent Destailleur + * Copyright (C) 2024-2025 Frédéric France + * Copyright (C) 2024-2025 MDW * * 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 @@ -51,6 +51,13 @@ class ActionCommReminder extends CommonObject const STATUS_DONE = 1; const STATUS_ERROR = -1; + /** @var int reminder for user */ + const TYPE_USER = 0; + /** @var int reminder for customer */ + const TYPE_CUSTOMER = 1; + /** @var int reminder for contact */ + const TYPE_CONTACT = 2; + /** * 'type' if the field format. @@ -93,7 +100,7 @@ class ActionCommReminder extends CommonObject public $rowid; /** - * @var int date remind + * @var int|'' date remind */ public $dateremind; diff --git a/htdocs/comm/action/class/api_agendaevents.class.php b/htdocs/comm/action/class/api_agendaevents.class.php index ebe9174c1b1..371082df46f 100644 --- a/htdocs/comm/action/class/api_agendaevents.class.php +++ b/htdocs/comm/action/class/api_agendaevents.class.php @@ -3,6 +3,7 @@ * Copyright (C) 2016 Laurent Destailleur * Copyright (C) 2024-2025 MDW * Copyright (C) 2025 William Mead + * Copyright (C) 2025 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 @@ -404,9 +405,12 @@ class AgendaEvents extends DolibarrApi // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore /** * Clean sensible object datas + * @phpstan-template T * * @param Object $object Object to clean * @return Object Object with cleaned properties + * @phpstan-param T $object + * @phpstan-return T */ protected function _cleanObjectDatas($object) { diff --git a/htdocs/comm/mailing/card.php b/htdocs/comm/mailing/card.php index 8cf9cc0611d..ddcc3c17afc 100644 --- a/htdocs/comm/mailing/card.php +++ b/htdocs/comm/mailing/card.php @@ -62,7 +62,7 @@ $id = (GETPOSTINT('mailid') ? GETPOSTINT('mailid') : GETPOSTINT('id')); $action = GETPOST('action', 'aZ09'); $confirm = GETPOST('confirm', 'alpha'); -$cancel = GETPOST('cancel', 'aZ09'); +$cancel = GETPOST('cancel', 'alpha'); $urlfrom = GETPOST('urlfrom'); $projectid = GETPOSTINT('projectid'); $backtopage = GETPOST('backtopage'); diff --git a/htdocs/comm/mailing/class/api_mailings.class.php b/htdocs/comm/mailing/class/api_mailings.class.php index 26aa006f5fc..c8b8f7dc870 100644 --- a/htdocs/comm/mailing/class/api_mailings.class.php +++ b/htdocs/comm/mailing/class/api_mailings.class.php @@ -1,6 +1,7 @@ + * Copyright (C) 2025 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 @@ -1147,9 +1148,12 @@ class Mailings extends DolibarrApi // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore /** * Clean sensible object datas + * @phpstan-template T * * @param Object $object Object to clean * @return Object Object with cleaned properties + * @phpstan-param T $object + * @phpstan-return T */ protected function _cleanObjectDatas($object) { diff --git a/htdocs/comm/mailing/list.php b/htdocs/comm/mailing/list.php index 4511934692c..c56c0f734aa 100644 --- a/htdocs/comm/mailing/list.php +++ b/htdocs/comm/mailing/list.php @@ -5,6 +5,7 @@ * Copyright (C) 2024 Alexandre Spangaro * Copyright (C) 2024 Frédéric France * Copyright (C) 2025 Jon Bendtsen + * Copyright (C) 2025 Josep Lluís Amador Teruel * * 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 @@ -36,9 +37,7 @@ require '../../main.inc.php'; * @var User $user */ require_once DOL_DOCUMENT_ROOT.'/comm/mailing/class/mailing.class.php'; -if (isModEnabled('project')) { - require_once DOL_DOCUMENT_ROOT . '/projet/class/project.class.php'; -} +require_once DOL_DOCUMENT_ROOT . '/projet/class/project.class.php'; // Load translation files required by the page $langs->load('mails'); diff --git a/htdocs/comm/mailing/note.php b/htdocs/comm/mailing/note.php index 1446b646e6c..b72bd4b87aa 100644 --- a/htdocs/comm/mailing/note.php +++ b/htdocs/comm/mailing/note.php @@ -43,7 +43,7 @@ $langs->loadLangs(array("mails", "mailing", "companies")); $id = GETPOSTINT('id'); $ref = GETPOST('ref', 'alpha'); $action = GETPOST('action', 'aZ09'); -$cancel = GETPOST('cancel', 'aZ09'); +$cancel = GETPOST('cancel', 'alpha'); $backtopage = GETPOST('backtopage', 'alpha'); // Initialize a technical objects diff --git a/htdocs/comm/propal/agenda.php b/htdocs/comm/propal/agenda.php index 2e19d87d799..4b906ce8065 100644 --- a/htdocs/comm/propal/agenda.php +++ b/htdocs/comm/propal/agenda.php @@ -45,7 +45,7 @@ $langs->loadLangs(array("propal", "other")); $id = GETPOSTINT('id'); $ref = GETPOST('ref', 'alpha'); $action = GETPOST('action', 'aZ09'); -$cancel = GETPOST('cancel', 'aZ09'); +$cancel = GETPOST('cancel', 'alpha'); $contextpage = GETPOST('contextpage', 'aZ') ? GETPOST('contextpage', 'aZ') : str_replace('_', '', basename(dirname(__FILE__)) . basename(__FILE__, '.php')); // To manage different context of search $backtopage = GETPOST('backtopage', 'alpha'); diff --git a/htdocs/comm/propal/card.php b/htdocs/comm/propal/card.php index 793bffe0467..e5d201387b3 100644 --- a/htdocs/comm/propal/card.php +++ b/htdocs/comm/propal/card.php @@ -2206,7 +2206,7 @@ $now = dol_now(); // Add new proposal if ($action == 'create') { - $currency_code = $conf->currency; + $currency_code = getDolCurrency(); print load_fiche_titre($langs->trans("NewProp"), '', 'propal'); @@ -2215,8 +2215,6 @@ if ($action == 'create') { $res = $soc->fetch($socid); } - $currency_code = $conf->currency; - $cond_reglement_id = GETPOSTINT('cond_reglement_id'); $deposit_percent = GETPOSTFLOAT('cond_reglement_id_deposit_percent'); $mode_reglement_id = GETPOSTINT('mode_reglement_id'); @@ -3580,7 +3578,7 @@ if ($action == 'create') { // Send if (empty($user->socid)) { if ($object->status == Propal::STATUS_VALIDATED || $object->status == Propal::STATUS_SIGNED || getDolGlobalString('PROPOSAL_SENDBYEMAIL_FOR_ALL_STATUS')) { - print dolGetButtonAction('', $langs->trans('SendMail'), 'default', $_SERVER["PHP_SELF"] . '?action=presend&token=' . newToken() . '&id=' . $object->id . '&mode=init#formmailbeforetitle', '', $usercansend); + print dolGetButtonAction('', $langs->trans('SendMail'), 'email', $_SERVER["PHP_SELF"] . '?action=presend&token=' . newToken() . '&id=' . $object->id . '&mode=init#formmailbeforetitle', '', $usercansend); } } diff --git a/htdocs/comm/propal/class/api_proposals.class.php b/htdocs/comm/propal/class/api_proposals.class.php index be57b1dbd69..7789f01a656 100644 --- a/htdocs/comm/propal/class/api_proposals.class.php +++ b/htdocs/comm/propal/class/api_proposals.class.php @@ -136,7 +136,9 @@ class Proposals extends DolibarrApi if (!DolibarrApiAccess::$user->hasRight('propal', 'lire')) { throw new RestException(403); } - + if ($id == 0) { + throw new RestException(400, 'No proposal with id=0 can exist'); + } $result = $this->propal->fetch($id, $ref, $ref_ext); if (!$result) { throw new RestException(404, 'Commercial Proposal not found'); @@ -308,6 +310,7 @@ class Proposals extends DolibarrApi */ public function post($request_data = null) { + global $conf; if (!DolibarrApiAccess::$user->hasRight('propal', 'creer')) { throw new RestException(403, "Insufficiant rights"); } @@ -320,6 +323,12 @@ class Proposals extends DolibarrApi $this->propal->context['caller'] = sanitizeVal($request_data['caller'], 'aZ09'); continue; } + if ($field == 'id') { + throw new RestException(400, 'Creating with id field is forbidden'); + } + if ($field == 'entity' && ((int) $value) != ((int) $conf->entity)) { + throw new RestException(403, 'Creating with entity='.((int) $value).' MUST be the same entity='.((int) $conf->entity).' as your API user/key belongs to'); + } $this->propal->$field = $this->_checkValForAPI($field, $value, $this->propal); } @@ -700,48 +709,117 @@ class Proposals extends DolibarrApi * * @since 10.0.0 Initial implementation * - * @param int $id Id of commercial proposal to update - * @param int $contactid Id of external or internal contact to add - * @param string $type Type of the external contact (BILLING, SHIPPING, CUSTOMER), internal contact (SALESREPFOLL) - * @param string $source Source of the contact (internal, external) + * @param int $id Id of commercial proposal to update + * @param int $contactid Id of contact to add + * @param string $type Type (code in dictionary) of the contact (BILLING, SHIPPING, CUSTOMER + possibly your own) + * @param string $source internal=Contact intern (llx_user), external=Contact extern (llx_socpeople) + * @param int $notrigger 0=Enable all triggers (default), 1=Disable all triggers * @return array * @phan-return array{success:array{code:int,message:string}} * @phpstan-return array{success:array{code:int,message:string}} * - * @url POST {id}/contact/{contactid}/{type}/{source} + * @url POST {id}/contact/{contactid}/{type} * + * @throws RestException 400 * @throws RestException 401 * @throws RestException 404 + * @throws RestException 503 */ - public function postContact($id, $contactid, $type, $source = 'external') + public function postContact($id, $contactid, $type, $source = 'external', $notrigger = 0) { if (!DolibarrApiAccess::$user->hasRight('propal', 'creer')) { throw new RestException(403); } - $result = $this->propal->fetch($id); + // test source + if (empty($source)) { + throw new RestException(400, 'Source can not be empty'); + } + $sql_distinct_source = "SELECT DISTINCT source"; + $sql_distinct_source .= " FROM ".MAIN_DB_PREFIX."c_type_contact"; + $sql_distinct_source .= " WHERE element LIKE 'propal'"; + $sql_distinct_source .= " AND source is NOT NULL"; + $sql_distinct_source .= " AND active != 0"; + $source_result = $this->db->query($sql_distinct_source); + $source_array = array(); + if ($source_result) { + $num = $this->db->num_rows($source_result); + $i = 0; + while ($i < $num) { + $obj = $this->db->fetch_object($source_result); + $source_kind = (string) $obj->source; + array_push($source_array, $source_kind); + dol_syslog("source_kind=".$source_kind); + $i++; + } + } else { + throw new RestException(503, 'Error when retrieving a list of propal contact sources: '.$this->db->lasterror()); + } + if (!in_array($source, (array) $source_array, true)) { + throw new RestException(400, 'Combo of Source='.$source.' and Type='.$type.' not found in dictionary with active proposal contact types'); + } + + // test type + if (empty($type)) { + throw new RestException(400, 'type can not be empty'); + } + // variable called type here, but code in dictionary and database + $sql_distinct_type = "SELECT DISTINCT code"; + $sql_distinct_type .= " FROM ".MAIN_DB_PREFIX."c_type_contact"; + $sql_distinct_type .= " WHERE element LIKE 'propal'"; + $sql_distinct_type .= " AND source='".$this->db->escape($source)."'"; + $sql_distinct_type .= " AND code is NOT NULL"; + $sql_distinct_type .= " AND active != 0"; + $type_result = $this->db->query($sql_distinct_type); + $type_array = array(); + + if ($type_result) { + $num = $this->db->num_rows($type_result); + $i = 0; + while ($i < $num) { + $obj = $this->db->fetch_object($type_result); + // variable called type here, but code in dictionary and database + $type_kind = (string) $obj->code; + array_push($type_array, $type_kind); + dol_syslog("type_kind=".$type_kind); + $i++; + } + } else { + throw new RestException(503, 'Error when retrieving a list of propal contact types: '.$this->db->lasterror()); + } + if (!in_array($type, (array) $type_array, true)) { + throw new RestException(400, 'Combo of Type='.$type.' and Source='.$source.' not found in dictionary with active proposal contact types'); + } + + // tests done, let's get it + $result = $this->propal->fetch($id); if (!$result) { throw new RestException(404, 'Proposal not found'); } - - if (!in_array($source, array('internal', 'external'), true)) { - throw new RestException(500, 'Availables sources: internal OR external'); - } - - if ($source == 'external' && !in_array($type, array('BILLING', 'SHIPPING', 'CUSTOMER'), true)) { - throw new RestException(500, 'Availables external types: BILLING, SHIPPING OR CUSTOMER'); - } - - if ($source == 'internal' && !in_array($type, array('SALESREPFOLL'), true)) { - throw new RestException(500, 'Availables internal types: SALESREPFOLL'); - } - if (!DolibarrApi::_checkAccessToResource('propal', $this->propal->id)) { throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login); } - $result = $this->propal->add_contact($contactid, $type, $source); + $result = $this->propal->add_contact($contactid, $type, $source, $notrigger); + + if ($result == 0) { + throw new RestException(400, 'Already exists: Contact='.$contactid.' is already linked to the proposal='.$id.' as source='.$source.' and type='.$type); + } elseif ($result == -1) { + throw new RestException(400, 'Wrong contact='.$contactid); + } elseif ($result == -2) { + throw new RestException(400, 'Wrong type='.$type); + } elseif ($result == -3) { + throw new RestException(400, 'Not allowed contacts'); + } elseif ($result == -4) { + throw new RestException(400, 'ErrorCommercialNotAllowedForThirdparty'); + } elseif ($result == -5) { + throw new RestException(400, 'Trigger failed'); + } elseif ($result == -6) { + throw new RestException(400, 'DB_ERROR_RECORD_ALREADY_EXISTS'); + } elseif ($result == -7) { + throw new RestException(400, 'Some other error'); + } if (!$result) { throw new RestException(500, 'Error when added the contact'); @@ -750,7 +828,7 @@ class Proposals extends DolibarrApi return array( 'success' => array( 'code' => 200, - 'message' => 'Contact linked to the proposal' + 'message' => 'Contact='.$contactid.' linked to the proposal='.$id.' as '.$source.' '.$type ) ); } @@ -819,7 +897,9 @@ class Proposals extends DolibarrApi if (!DolibarrApiAccess::$user->hasRight('propal', 'creer')) { throw new RestException(403); } - + if ($id == 0) { + throw new RestException(400, 'No proposal with id=0 can exist'); + } $result = $this->propal->fetch($id); if (!$result) { throw new RestException(404, 'Proposal not found'); @@ -881,6 +961,10 @@ class Proposals extends DolibarrApi if (!DolibarrApiAccess::$user->hasRight('propal', 'supprimer')) { throw new RestException(403); } + if ($id == 0) { + throw new RestException(400, 'No proposal with id=0 can exist'); + } + $result = $this->propal->fetch($id); if (!$result) { throw new RestException(404, 'Commercial Proposal not found'); @@ -1137,9 +1221,12 @@ class Proposals extends DolibarrApi // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore /** * Clean sensible object datas + * @phpstan-template T * * @param Object $object Object to clean * @return Object Object with cleaned properties + * @phpstan-param T $object + * @phpstan-return T */ protected function _cleanObjectDatas($object) { diff --git a/htdocs/comm/propal/class/propal.class.php b/htdocs/comm/propal/class/propal.class.php index a08c41c1f40..9de54f75831 100644 --- a/htdocs/comm/propal/class/propal.class.php +++ b/htdocs/comm/propal/class/propal.class.php @@ -1155,7 +1155,8 @@ class Propal extends CommonObject */ public function create($user, $notrigger = 0) { - global $conf, $hookmanager, $mysoc; + global $mysoc; + $error = 0; $now = dol_now(); @@ -1179,7 +1180,7 @@ class Propal extends CommonObject $this->fk_multicurrency = MultiCurrency::getIdFromCode($this->db, $this->multicurrency_code); } if (empty($this->fk_multicurrency)) { - $this->multicurrency_code = (string) $conf->currency; + $this->multicurrency_code = getDolCurrency(); $this->fk_multicurrency = 0; $this->multicurrency_tx = 1; } @@ -1406,22 +1407,10 @@ class Propal extends CommonObject } } - // Set delivery address - /*if (! $error && $this->fk_delivery_address) { - $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element; - $sql.= " SET fk_delivery_address = ".((int) $this->fk_delivery_address); - $sql.= " WHERE ref = '".$this->db->escape($this->ref)."'"; - $sql.= " AND entity = ".setEntity($this); - - $result=$this->db->query($sql); - }*/ - if (!$error) { // Update denormalized data $resql = $this->update_price(1, 'auto', 0, $mysoc); if ($resql) { - $action = 'update'; - // Actions on extra fields $result = $this->insertExtraFields(); if ($result < 0) { @@ -2191,7 +2180,6 @@ class Propal extends CommonObject $this->ref = $num; $this->statut = self::STATUS_VALIDATED; - $this->status = self::STATUS_VALIDATED; $this->user_validation_id = $user->id; $this->datev = $now; $this->date_validation = $now; @@ -3642,7 +3630,7 @@ class Propal extends CommonObject $this->note_private = 'This is a comment (private)'; $this->multicurrency_tx = 1; - $this->multicurrency_code = $conf->currency; + $this->multicurrency_code = getDolCurrency(); // Lines $nbp = min(1000, GETPOSTINT('nblines') ? GETPOSTINT('nblines') : 5); // We can force the nb of lines to test from command line (but not more than 1000) diff --git a/htdocs/comm/propal/stats/index.php b/htdocs/comm/propal/stats/index.php index 0b88546e524..1640abf5f44 100644 --- a/htdocs/comm/propal/stats/index.php +++ b/htdocs/comm/propal/stats/index.php @@ -346,6 +346,7 @@ if (!in_array($nowyear, $arrayyears)) { $arrayyears[$nowyear] = $nowyear; } arsort($arrayyears); +print img_picto('', 'calendar', 'class="pictofixedwidth"'); print $form->selectarray('year', $arrayyears, $year, 0, 0, 0, '', 0, 0, 0, '', 'width75'); print ''; print ''; diff --git a/htdocs/commande/agenda.php b/htdocs/commande/agenda.php index 9a98571fa13..0222528929d 100644 --- a/htdocs/commande/agenda.php +++ b/htdocs/commande/agenda.php @@ -49,7 +49,9 @@ $id = GETPOSTINT('id'); $ref = GETPOST('ref', 'alpha'); $socid = GETPOSTINT('socid'); $action = GETPOST('action', 'aZ09'); +$cancel = GETPOST('cancel', 'alpha'); $contextpage = GETPOST('contextpage', 'aZ09'); +$backtopage = GETPOST('backtopage', 'alpha'); $limit = GETPOSTINT('limit') ? GETPOSTINT('limit') : $conf->liste_limit; $sortfield = GETPOST("sortfield", "aZ09comma"); diff --git a/htdocs/commande/class/api_orders.class.php b/htdocs/commande/class/api_orders.class.php index c62359d0f94..c928135487a 100644 --- a/htdocs/commande/class/api_orders.class.php +++ b/htdocs/commande/class/api_orders.class.php @@ -124,7 +124,9 @@ class Orders extends DolibarrApi if (!DolibarrApiAccess::$user->hasRight('commande', 'lire')) { throw new RestException(403); } - + if ($id == 0) { + throw new RestException(400, 'No order with id=0 can exist'); + } $result = $this->commande->fetch($id, $ref, $ref_ext); if (!$result) { throw new RestException(404, 'Order not found'); @@ -313,6 +315,7 @@ class Orders extends DolibarrApi */ public function post($request_data = null) { + global $conf; if (!DolibarrApiAccess::$user->hasRight('commande', 'creer')) { throw new RestException(403, "Insufficiant rights"); } @@ -325,6 +328,12 @@ class Orders extends DolibarrApi $this->commande->context['caller'] = sanitizeVal($request_data['caller'], 'aZ09'); continue; } + if ($field == 'id') { + throw new RestException(400, 'Creating with id field is forbidden'); + } + if ($field == 'entity' && ((int) $value) != ((int) $conf->entity)) { + throw new RestException(403, 'Creating with entity='.((int) $value).' MUST be the same entity='.((int) $conf->entity).' as your API user/key belongs to'); + } $this->commande->$field = $this->_checkValForAPI($field, $value, $this->commande); } @@ -622,19 +631,21 @@ class Orders extends DolibarrApi /** * Add a contact type of given order * - * @param int $id Id of order to update - * @param int $contactid Id of contact to add - * @param string $type Type of the contact (BILLING, SHIPPING, CUSTOMER) - * @param string $source external=Contact extern (llx_socpeople), internal=Contact intern (llx_user) - * @param int $notrigger Disable all triggers + * @param int $id Id of order to update + * @param int $contactid Id of contact to add + * @param string $type Type (code in dictionary) of the contact (BILLING, SHIPPING, CUSTOMER + possibly your own) + * @param string $source internal=Contact intern (llx_user), external=Contact extern (llx_socpeople) + * @param int $notrigger 0=Enable all triggers (default), 1=Disable all triggers * @return array * @phan-return array{success:array{code:int,message:string}} * @phpstan-return array{success:array{code:int,message:string}} * * @url POST {id}/contact/{contactid}/{type} * + * @throws RestException 400 * @throws RestException 401 * @throws RestException 404 + * @throws RestException 503 */ public function postContact($id, $contactid, $type, $source = "external", $notrigger = 0) { @@ -642,29 +653,100 @@ class Orders extends DolibarrApi throw new RestException(403); } + // test source + if (empty($source)) { + throw new RestException(400, 'Source can not be empty'); + } + $sql_distinct_source = "SELECT DISTINCT source"; + $sql_distinct_source .= " FROM ".MAIN_DB_PREFIX."c_type_contact"; + $sql_distinct_source .= " WHERE element LIKE 'commande'"; + $sql_distinct_source .= " AND source is NOT NULL"; + $sql_distinct_source .= " AND active != 0"; + $source_result = $this->db->query($sql_distinct_source); + $source_array = array(); + + if ($source_result) { + $num = $this->db->num_rows($source_result); + $i = 0; + while ($i < $num) { + $obj = $this->db->fetch_object($source_result); + $source_kind = (string) $obj->source; + array_push($source_array, $source_kind); + dol_syslog("source_kind=".$source_kind); + $i++; + } + } else { + throw new RestException(503, 'Error when retrieving a list of order contact sources: '.$this->db->lasterror()); + } + if (!in_array($source, (array) $source_array, true)) { + throw new RestException(400, 'Combo of Source='.$source.' and Type='.$type.' not found in dictionary with active order contact types'); + } + + // test type + if (empty($type)) { + throw new RestException(400, 'type can not be empty'); + } + // variable called type here, but code in dictionary and database + $sql_distinct_type = "SELECT DISTINCT code"; + $sql_distinct_type .= " FROM ".MAIN_DB_PREFIX."c_type_contact"; + $sql_distinct_type .= " WHERE element LIKE 'commande'"; + $sql_distinct_type .= " AND source='".$this->db->escape($source)."'"; + $sql_distinct_type .= " AND code is NOT NULL"; + $sql_distinct_type .= " AND active != 0"; + $type_result = $this->db->query($sql_distinct_type); + $type_array = array(); + + if ($type_result) { + $num = $this->db->num_rows($type_result); + $i = 0; + while ($i < $num) { + $obj = $this->db->fetch_object($type_result); + // variable called type here, but code in dictionary and database + $type_kind = (string) $obj->code; + array_push($type_array, $type_kind); + dol_syslog("type_kind=".$type_kind); + $i++; + } + } else { + throw new RestException(503, 'Error when retrieving a list of order contact types: '.$this->db->lasterror()); + } + if (!in_array($type, (array) $type_array, true)) { + throw new RestException(400, 'Combo of Type='.$type.' and Source='.$source.' not found in dictionary with active order contact types'); + } + + // tests done, let's get it $result = $this->commande->fetch($id); if (!$result) { throw new RestException(404, 'Order not found'); } - if (!DolibarrApi::_checkAccessToResource('commande', $this->commande->id)) { throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login); } $result = $this->commande->add_contact($contactid, $type, $source, $notrigger); - if ($result < 0) { - throw new RestException(500, 'Error when added the contact'); - } - if ($result == 0) { - throw new RestException(304, 'contact already added'); + throw new RestException(400, 'Already exists: Contact='.$contactid.' is already linked to the order='.$id.' as source='.$source.' and type='.$type); + } elseif ($result == -1) { + throw new RestException(400, 'Wrong contact='.$contactid); + } elseif ($result == -2) { + throw new RestException(400, 'Wrong type='.$type); + } elseif ($result == -3) { + throw new RestException(400, 'Not allowed contacts'); + } elseif ($result == -4) { + throw new RestException(400, 'ErrorCommercialNotAllowedForThirdparty'); + } elseif ($result == -5) { + throw new RestException(400, 'Trigger failed'); + } elseif ($result == -6) { + throw new RestException(400, 'DB_ERROR_RECORD_ALREADY_EXISTS'); + } elseif ($result == -7) { + throw new RestException(400, 'Some other error'); } return array( 'success' => array( 'code' => 200, - 'message' => 'Contact linked to the order' + 'message' => 'Contact='.$contactid.' linked to the order='.$id.' as '.$source.' '.$type ) ); } @@ -736,7 +818,9 @@ class Orders extends DolibarrApi if (!DolibarrApiAccess::$user->hasRight('commande', 'creer')) { throw new RestException(403); } - + if ($id == 0) { + throw new RestException(400, 'No order with id=0 can exist'); + } $result = $this->commande->fetch($id); if (!$result) { throw new RestException(404, 'Order not found'); @@ -791,6 +875,9 @@ class Orders extends DolibarrApi if (!DolibarrApiAccess::$user->hasRight('commande', 'supprimer')) { throw new RestException(403); } + if ($id == 0) { + throw new RestException(400, 'No order with id=0 can exist'); + } $result = $this->commande->fetch($id); if (!$result) { throw new RestException(404, 'Order not found'); @@ -1193,9 +1280,12 @@ class Orders extends DolibarrApi // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore /** * Clean sensible object datas + * @phpstan-template T * * @param Object $object Object to clean * @return Object Object with cleaned properties + * @phpstan-param T $object + * @phpstan-return T */ protected function _cleanObjectDatas($object) { diff --git a/htdocs/commande/document.php b/htdocs/commande/document.php index bee1eb2c27c..33849aa4365 100644 --- a/htdocs/commande/document.php +++ b/htdocs/commande/document.php @@ -30,6 +30,13 @@ // Load Dolibarr environment require '../main.inc.php'; +/** + * @var Conf $conf + * @var DoliDB $db + * @var HookManager $hookmanager + * @var Translate $langs + * @var User $user + */ require_once DOL_DOCUMENT_ROOT.'/core/lib/order.lib.php'; require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php'; require_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php'; @@ -39,14 +46,6 @@ if (isModEnabled('project')) { require_once DOL_DOCUMENT_ROOT.'/projet/class/project.class.php'; } -/** - * @var Conf $conf - * @var DoliDB $db - * @var HookManager $hookmanager - * @var Translate $langs - * @var User $user - */ - // Load translation files required by the page $langs->loadLangs(array('companies', 'other', 'bills', 'orders')); @@ -113,6 +112,7 @@ include DOL_DOCUMENT_ROOT.'/core/actions_linkedfiles.inc.php'; /* * View */ + $title = $object->ref." - ".$langs->trans('Documents'); $help_url = 'EN:Customers_Orders|FR:Commandes_Clients|ES:Pedidos de clientes|DE:Modul_Kundenaufträge'; llxHeader('', $title, $help_url, '', 0, 0, '', '', '', 'mod-order page-card_documents'); diff --git a/htdocs/commande/stats/index.php b/htdocs/commande/stats/index.php index 7bac8f13265..484c4c482b8 100644 --- a/htdocs/commande/stats/index.php +++ b/htdocs/commande/stats/index.php @@ -416,6 +416,7 @@ if (!in_array($nowyear, $arrayyears)) { $arrayyears[$nowyear] = $nowyear; } arsort($arrayyears); +print img_picto('', 'calendar', 'class="pictofixedwidth"'); print $form->selectarray('year', $arrayyears, $year, 0, 0, 0, '', 0, 0, 0, '', 'width75'); print ''; print ''; diff --git a/htdocs/compta/bank/class/account.class.php b/htdocs/compta/bank/class/account.class.php index c63232d54df..39b2f413653 100644 --- a/htdocs/compta/bank/class/account.class.php +++ b/htdocs/compta/bank/class/account.class.php @@ -712,9 +712,9 @@ class Account extends CommonObject /** * Create bank account into database * - * @param User $user Object user making creation - * @param int $notrigger 1=Disable triggers - * @return int Return integer < 0 if KO, > 0 if OK + * @param User $user Object user making creation + * @param int<0,1> $notrigger 1=Disable triggers + * @return int Return integer < 0 if KO, > 0 if OK */ public function create($user, $notrigger = 0) { diff --git a/htdocs/compta/bank/class/api_bankaccounts.class.php b/htdocs/compta/bank/class/api_bankaccounts.class.php index eef3b502124..9a72fe8b157 100644 --- a/htdocs/compta/bank/class/api_bankaccounts.class.php +++ b/htdocs/compta/bank/class/api_bankaccounts.class.php @@ -428,9 +428,12 @@ class BankAccounts extends DolibarrApi // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore /** * Clean sensible object datas + * @phpstan-template T * * @param Object $object Object to clean * @return Object Object with cleaned properties + * @phpstan-param T $object + * @phpstan-return T */ protected function _cleanObjectDatas($object) { diff --git a/htdocs/compta/bank/various_payment/card.php b/htdocs/compta/bank/various_payment/card.php index 7d7c58d756d..cfc85efc308 100644 --- a/htdocs/compta/bank/various_payment/card.php +++ b/htdocs/compta/bank/various_payment/card.php @@ -55,7 +55,7 @@ $langs->loadLangs(array("accountancy", "banks", "bills", "categories", "compta", $id = GETPOSTINT('id'); $action = GETPOST('action', 'alpha'); $confirm = GETPOST('confirm'); -$cancel = GETPOST('cancel', 'aZ09'); +$cancel = GETPOST('cancel', 'alpha'); $backtopage = GETPOST('backtopage', 'alpha'); $accountid = GETPOSTINT("accountid") > 0 ? GETPOSTINT("accountid") : 0; diff --git a/htdocs/compta/cashcontrol/report.php b/htdocs/compta/cashcontrol/report.php index 676f2a1293f..888b83f68bf 100644 --- a/htdocs/compta/cashcontrol/report.php +++ b/htdocs/compta/cashcontrol/report.php @@ -234,7 +234,7 @@ if ($resql) { $transactionspertype = array(); $amountpertype = array(); - $totalarray = array('nbfield' => 0, 'pos' => array()); + $totalarray = array('nbfield' => 0, 'pos' => array(), 'val' => array('totaldebfield' => 0, 'totalcredfield' => 0)); while ($i < $num) { $objp = $db->fetch_object($resql); diff --git a/htdocs/compta/deplacement/stats/index.php b/htdocs/compta/deplacement/stats/index.php index 5c4c622cc55..6e7b8fe418d 100644 --- a/htdocs/compta/deplacement/stats/index.php +++ b/htdocs/compta/deplacement/stats/index.php @@ -273,6 +273,7 @@ if (!in_array($year, $arrayyears)) { $arrayyears[$year] = $year; } arsort($arrayyears); +print img_picto('', 'calendar', 'class="pictofixedwidth"'); print $form->selectarray('year', $arrayyears, $year, 0, 0, 0, '', 0, 0, 0, '', 'width75'); print ''; print ''; diff --git a/htdocs/compta/facture/agenda-rec.php b/htdocs/compta/facture/agenda-rec.php index 81065ef50d3..b8db81fa560 100644 --- a/htdocs/compta/facture/agenda-rec.php +++ b/htdocs/compta/facture/agenda-rec.php @@ -46,7 +46,7 @@ $langs->loadLangs(array("bills", "other")); $id = GETPOSTINT('id'); $ref = GETPOST('ref', 'alpha'); $action = GETPOST('action', 'aZ09'); -$cancel = GETPOST('cancel', 'aZ09'); +$cancel = GETPOST('cancel', 'alpha'); $contextpage = GETPOST('contextpage', 'aZ') ? GETPOST('contextpage', 'aZ') : str_replace('_', '', basename(dirname(__FILE__)).basename(__FILE__, '.php')); // To manage different context of search $backtopage = GETPOST('backtopage', 'alpha'); diff --git a/htdocs/compta/facture/agenda.php b/htdocs/compta/facture/agenda.php index eb09109c14d..17b1d76836b 100644 --- a/htdocs/compta/facture/agenda.php +++ b/htdocs/compta/facture/agenda.php @@ -45,7 +45,7 @@ $langs->loadLangs(array("bills", "other")); $id = GETPOSTINT('id'); $ref = GETPOST('ref', 'alpha'); $action = GETPOST('action', 'aZ09'); -$cancel = GETPOST('cancel', 'aZ09'); +$cancel = GETPOST('cancel', 'alpha'); $contextpage = GETPOST('contextpage', 'aZ') ? GETPOST('contextpage', 'aZ') : str_replace('_', '', basename(dirname(__FILE__)).basename(__FILE__, '.php')); // To manage different context of search $backtopage = GETPOST('backtopage', 'alpha'); diff --git a/htdocs/compta/facture/card.php b/htdocs/compta/facture/card.php index d07be12a739..8c928751d46 100644 --- a/htdocs/compta/facture/card.php +++ b/htdocs/compta/facture/card.php @@ -5857,6 +5857,7 @@ if ($action == 'create') { print ''.$langs->trans('ListOfSituationInvoices').''; print ''; print ''.$langs->trans('Situation').''; + if (isModEnabled("bank")) { print ''; } @@ -5957,7 +5958,6 @@ if ($action == 'create') { $totalpaid = $next_invoice->getSommePaiement(0); $totalcreditnotes = $next_invoice->getSumCreditNotesUsed(0); $totaldeposits = $next_invoice->getSumDepositsUsed(0); - $total_next_ht += $next_invoice->total_ht; $total_next_ttc += $next_invoice->total_ttc; @@ -6603,14 +6603,15 @@ if ($action == 'create') { if (empty($user->socid)) { if (($object->status == Facture::STATUS_VALIDATED || $object->status == Facture::STATUS_CLOSED) || getDolGlobalString('FACTURE_SENDBYEMAIL_FOR_ALL_STATUS')) { if ($objectidnext) { - print ''.$langs->trans('SendMail').''; + $params['attr']['title'] = $langs->trans("DisabledBecauseReplacedInvoice"); + print dolGetButtonAction('', $langs->trans('SendMail'), 'email', '#', '', false, $params); } else { if ($usercansend) { unset($params['attr']['title']); - print dolGetButtonAction('', $langs->trans('SendMail'), 'default', $_SERVER['PHP_SELF'].'?facid='.$object->id.'&action=presend&mode=init#formmailbeforetitle', '', true, $params); + print dolGetButtonAction('', $langs->trans('SendMail'), 'email', $_SERVER['PHP_SELF'].'?facid='.$object->id.'&action=presend&mode=init#formmailbeforetitle', '', true, $params); } else { unset($params['attr']['title']); - print dolGetButtonAction('', $langs->trans('SendMail'), 'default', '#', '', false, $params); + print dolGetButtonAction('', $langs->trans('SendMail'), 'email', '#', '', false, $params); } } } @@ -6750,12 +6751,6 @@ if ($action == 'create') { } } - // Clone - if (($object->type == Facture::TYPE_STANDARD || $object->type == Facture::TYPE_DEPOSIT || $object->type == Facture::TYPE_PROFORMA) && $usercancreate) { - unset($params['attr']['title']); - print dolGetButtonAction($langs->trans('ToClone'), '', 'default', $_SERVER['PHP_SELF'].'?facid='.$object->id.'&action=clone&object=invoice&token='.newToken(), '', true, $params); - } - // Clone as predefined / Create template if (($object->type == Facture::TYPE_STANDARD || $object->type == Facture::TYPE_DEPOSIT || $object->type == Facture::TYPE_PROFORMA) && $object->status == 0 && $usercancreate) { if (!$objectidnext && count($object->lines) > 0) { @@ -6764,6 +6759,12 @@ if ($action == 'create') { } } + // Clone + if (($object->type == Facture::TYPE_STANDARD || $object->type == Facture::TYPE_DEPOSIT || $object->type == Facture::TYPE_PROFORMA) && $usercancreate) { + unset($params['attr']['title']); + print dolGetButtonAction($langs->trans('ToClone'), '', 'clone', $_SERVER['PHP_SELF'].'?facid='.$object->id.'&action=clone&object=invoice&token='.newToken(), '', true, $params); + } + // Remove situation from cycle if (in_array($object->status, array(Facture::STATUS_CLOSED, Facture::STATUS_VALIDATED)) && $object->isSituationInvoice() diff --git a/htdocs/compta/facture/class/api_invoices.class.php b/htdocs/compta/facture/class/api_invoices.class.php index 2e91b4e2c7a..65f4fca3a3d 100644 --- a/htdocs/compta/facture/class/api_invoices.class.php +++ b/htdocs/compta/facture/class/api_invoices.class.php @@ -141,7 +141,9 @@ class Invoices extends DolibarrApi if (!DolibarrApiAccess::$user->hasRight('facture', 'lire')) { throw new RestException(403); } - + if ($id == 0) { + throw new RestException(400, 'No invoice with id=0 can exist'); + } $result = $this->invoice->fetch($id, $ref, $ref_ext); if (!$result) { throw new RestException(404, 'Invoice not found'); @@ -347,6 +349,7 @@ class Invoices extends DolibarrApi */ public function post($request_data = null) { + global $conf; if (!DolibarrApiAccess::$user->hasRight('facture', 'creer')) { throw new RestException(403, "Insufficiant rights"); } @@ -364,6 +367,12 @@ class Invoices extends DolibarrApi $this->invoice->context['caller'] = sanitizeVal($request_data['caller'], 'aZ09'); continue; } + if ($field == 'id') { + throw new RestException(400, 'Creating with id field is forbidden'); + } + if ($field == 'entity' && ((int) $value) != ((int) $conf->entity)) { + throw new RestException(403, 'Creating with entity='.((int) $value).' MUST be the same entity='.((int) $conf->entity).' as your API user/key belongs to'); + } $this->invoice->$field = $this->_checkValForAPI($field, $value, $this->invoice); } @@ -580,12 +589,12 @@ class Invoices extends DolibarrApi /** * Add a contact type of given invoice * - * @param int $id Id of invoice to update - * @param int $contactid Id of contact to add - * @param string $type Type of the contact (BILLING, SHIPPING, CUSTOMER) - * @param string $source external=Contact extern (llx_socpeople), internal=Contact intern (llx_user) - * @param int $notrigger Disable all triggers - * @return array + * @param int $id Id of invoice to update + * @param int $contactid Id of contact to add + * @param string $type Type (code in dictionary) of the contact (BILLING, SHIPPING, CUSTOMER + possibly your own) + * @param string $source internal=Contact intern (llx_user), external=Contact extern (llx_socpeople) + * @param int $notrigger 0=Enable all triggers (default), 1=Disable all triggers + * @return array * @phan-return array{success:array{code:int,message:string}} * @phpstan-return array{success:array{code:int,message:string}} * @@ -600,22 +609,96 @@ class Invoices extends DolibarrApi throw new RestException(403); } - $result = $this->invoice->fetch($id); + // test source + if (empty($source)) { + throw new RestException(400, 'Source can not be empty'); + } + $sql_distinct_source = "SELECT DISTINCT source"; + $sql_distinct_source .= " FROM ".MAIN_DB_PREFIX."c_type_contact"; + $sql_distinct_source .= " WHERE element LIKE 'facture'"; + $sql_distinct_source .= " AND source is NOT NULL"; + $sql_distinct_source .= " AND active != 0"; + $source_result = $this->db->query($sql_distinct_source); + $source_array = array(); + if ($source_result) { + $num = $this->db->num_rows($source_result); + $i = 0; + while ($i < $num) { + $obj = $this->db->fetch_object($source_result); + $source_kind = (string) $obj->source; + array_push($source_array, $source_kind); + dol_syslog("source_kind=".$source_kind); + $i++; + } + } else { + throw new RestException(503, 'Error when retrieving a list of invoice contact sources: '.$this->db->lasterror()); + } + if (!in_array($source, (array) $source_array, true)) { + throw new RestException(400, 'Combo of Source='.$source.' and Type='.$type.' not found in dictionary with active invoice contact types'); + } + + // test type + if (empty($type)) { + throw new RestException(400, 'type can not be empty'); + } + // variable called type here, but code in dictionary and database + $sql_distinct_type = "SELECT DISTINCT code"; + $sql_distinct_type .= " FROM ".MAIN_DB_PREFIX."c_type_contact"; + $sql_distinct_type .= " WHERE element LIKE 'facture'"; + $sql_distinct_type .= " AND source='".$this->db->escape($source)."'"; + $sql_distinct_type .= " AND code is NOT NULL"; + $sql_distinct_type .= " AND active != 0"; + $type_result = $this->db->query($sql_distinct_type); + $type_array = array(); + + if ($type_result) { + $num = $this->db->num_rows($type_result); + $i = 0; + while ($i < $num) { + $obj = $this->db->fetch_object($type_result); + // variable called type here, but code in dictionary and database + $type_kind = (string) $obj->code; + array_push($type_array, $type_kind); + dol_syslog("type_kind=".$type_kind); + $i++; + } + } else { + throw new RestException(503, 'Error when retrieving a list of invoice contact types: '.$this->db->lasterror()); + } + if (!in_array($type, (array) $type_array, true)) { + throw new RestException(400, 'Combo of Type='.$type.' and Source='.$source.' not found in dictionary with active invoice contact types'); + } + + // tests done, let's get it + $result = $this->invoice->fetch($id); if (!$result) { throw new RestException(404, 'Invoice not found'); } - - if (!in_array($type, array('BILLING', 'SHIPPING', 'CUSTOMER'), true)) { - throw new RestException(500, 'Availables types: BILLING, SHIPPING OR CUSTOMER'); - } - if (!DolibarrApi::_checkAccessToResource('invoice', $this->invoice->id)) { throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login); } $result = $this->invoice->add_contact($contactid, $type, $source, $notrigger); + if ($result == 0) { + throw new RestException(400, 'Already exists: Contact='.$contactid.' is already linked to the invoice='.$id.' as source='.$source.' and type='.$type); + } elseif ($result == -1) { + throw new RestException(400, 'Wrong contact='.$contactid); + } elseif ($result == -2) { + throw new RestException(400, 'Wrong type='.$type); + } elseif ($result == -3) { + throw new RestException(400, 'Not allowed contacts'); + } elseif ($result == -4) { + throw new RestException(400, 'ErrorCommercialNotAllowedForThirdparty'); + } elseif ($result == -5) { + throw new RestException(400, 'Trigger failed'); + } elseif ($result == -6) { + throw new RestException(400, 'DB_ERROR_RECORD_ALREADY_EXISTS'); + } elseif ($result == -7) { + throw new RestException(400, 'Some other error'); + } + if (!$result) { throw new RestException(500, 'Error when added the contact'); } @@ -623,7 +706,7 @@ class Invoices extends DolibarrApi return array( 'success' => array( 'code' => 200, - 'message' => 'Contact linked to the invoice' + 'message' => 'Contact='.$contactid.' linked to the invoice='.$id.' as '.$source.' '.$type ) ); } @@ -766,7 +849,9 @@ class Invoices extends DolibarrApi if (!DolibarrApiAccess::$user->hasRight('facture', 'creer')) { throw new RestException(403); } - + if ($id == 0) { + throw new RestException(400, 'No invoice with id=0 can exist'); + } $result = $this->invoice->fetch($id); if (!$result) { throw new RestException(404, 'Invoice not found'); @@ -827,6 +912,9 @@ class Invoices extends DolibarrApi if (!DolibarrApiAccess::$user->hasRight('facture', 'supprimer')) { throw new RestException(403); } + if ($id == 0) { + throw new RestException(400, 'No invoice with id=0 can exist'); + } $result = $this->invoice->fetch($id); if (!$result) { throw new RestException(404, 'Invoice not found'); @@ -1887,9 +1975,12 @@ class Invoices extends DolibarrApi // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore /** * Clean sensible object datas + * @phpstan-template T * * @param Object $object Object to clean * @return Object Object with cleaned properties + * @phpstan-param T $object + * @phpstan-return T */ protected function _cleanObjectDatas($object) { @@ -1949,6 +2040,146 @@ class Invoices extends DolibarrApi return $this->_fetchTemplateInvoice($id, '', '', $contact_list); } + + /** + * List template invoices + * + * Get a list of template invoices + * + * @param string $sortfield Sort field + * @param string $sortorder Sort order + * @param int $limit Limit for list + * @param int $page Page number + * @param string $thirdparty_ids Thirdparty ids to filter orders of (example '1' or '1,2,3') {@pattern /^[0-9,]*$/i} + * @param string $status Filter by template status: draft | active | suspended + * @param string $sqlfilters Other criteria to filter answers separated by a comma. Syntax example "(t.ref:like:'SO-%') and (t.date_creation:<:'20160101')" + * @param string $properties Restrict the data returned to these properties. Ignored if empty. Comma separated list of properties names + * @param bool $pagination_data If this parameter is set to true the response will include pagination data. Default value is false. Page starts from 0 + * @param int $loadlinkedobjects Load also linked object + * @param bool $withLines true or false to display or hide lines + * @return array Array of recurring invoice objects + * @phan-return FactureRec[]|array{data:FactureRec[],pagination:array{total:int,page:int,page_count:int,limit:int}} + * @phpstan-return FactureRec[]|array{data:FactureRec[],pagination:array{total:int,page:int,page_count:int,limit:int}} + * + * @url GET templates + * + * @throws RestException 404 Not found + * @throws RestException 503 Error + */ + public function indexTemplateInvoices($sortfield = "t.rowid", $sortorder = 'ASC', $limit = 100, $page = 0, $thirdparty_ids = '', $status = '', $sqlfilters = '', $properties = '', $pagination_data = false, $loadlinkedobjects = 0, $withLines = true) + { + if (!DolibarrApiAccess::$user->hasRight('facture', 'lire')) { + throw new RestException(403); + } + + $obj_ret = array(); + + // case of external user, $thirdparty_ids param is ignored and replaced by user's socid + $socids = DolibarrApiAccess::$user->socid ?: $thirdparty_ids; + + + // If the internal user must only see his customers, force searching by him + $search_sale = 0; + if (!DolibarrApiAccess::$user->hasRight('societe', 'client', 'voir') && !$socids) { + $search_sale = DolibarrApiAccess::$user->id; + } + + $sql = "SELECT t.rowid"; + $sql .= " FROM ".MAIN_DB_PREFIX."facture_rec AS t"; + $sql .= " INNER JOIN ".MAIN_DB_PREFIX."societe AS s ON (s.rowid = t.fk_soc)"; + $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."facture_rec_extrafields AS ef ON (ef.fk_object = t.rowid)"; + $sql .= ' WHERE t.entity IN ('.getEntity('invoice').')'; + if ($socids) { + $sql .= " AND t.fk_soc IN (".$this->db->sanitize($socids).")"; + } + + // Search on sale representative + if ($search_sale && $search_sale != '-1') { + if ($search_sale == -2) { + $sql .= " AND NOT EXISTS (SELECT sc.fk_soc FROM ".MAIN_DB_PREFIX."societe_commerciaux AS sc WHERE sc.fk_soc = t.fk_soc)"; + } elseif ($search_sale > 0) { + $sql .= " AND EXISTS (SELECT sc.fk_soc FROM ".MAIN_DB_PREFIX."societe_commerciaux AS sc WHERE sc.fk_soc = t.fk_soc AND sc.fk_user = ".((int) $search_sale).")"; + } + } + + // Filter by status + if ($status == 'active') { + $sql .= " AND t.suspended = 0 AND t.frequency IS NOT NULL"; + } + if ($status == 'suspended') { + $sql .= " AND t.suspended = 1 AND t.frequency IS NOT NULL"; + } + if ($status == 'draft') { + $sql .= " AND t.frequency IS NULL"; + } + // add sql filters + if ($sqlfilters) { + $errormessage = ''; + $sql .= forgeSQLFromUniversalSearchCriteria($sqlfilters, $errormessage); + if ($errormessage) { + throw new RestException(400, 'Error when validating parameter sqlfilters -> '.$errormessage); + } + } + + //this query will return total template invoices with the filters given + $sqlTotals = str_replace('SELECT t.rowid', 'SELECT count(t.rowid) as total', $sql); + + $sql .= $this->db->order($sortfield, $sortorder); + if ($limit) { + if ($page < 0) { + $page = 0; + } + $offset = $limit * $page; + + $sql .= $this->db->plimit($limit + 1, $offset); + } + + $result = $this->db->query($sql); + if ($result) { + $i = 0; + $num = $this->db->num_rows($result); + $min = min($num, ($limit <= 0 ? $num : $limit)); + while ($i < $min) { + $obj = $this->db->fetch_object($result); + $factureRec = new FactureRec($this->db); + if ($factureRec->fetch($obj->rowid) > 0) { + if ($loadlinkedobjects) { + // retrieve linked objects + $factureRec->fetchObjectLinked(); + } + + if (!$withLines) { + unset($factureRec->lines); + } + + $obj_ret[] = $this->_filterObjectProperties($this->_cleanTemplateObjectDatas($factureRec), $properties); + } + $i++; + } + } else { + throw new RestException(503, 'Error when retrieving recurring invoice templates: '.$this->db->lasterror()); + } + + //if $pagination_data is true the response will contain element data with all values and element pagination with pagination data(total,page,limit) + if ($pagination_data) { + $totalsResult = $this->db->query($sqlTotals); + $total = $this->db->fetch_object($totalsResult)->total; + + $tmp = $obj_ret; + $obj_ret = array(); + + $obj_ret['data'] = $tmp; + $obj_ret['pagination'] = array( + 'total' => (int) $total, + 'page' => $page, + 'page_count' => ceil((int) $total / $limit), + 'limit' => $limit + ); + } + + return $obj_ret; + } + /** * Get properties of an invoice object * diff --git a/htdocs/compta/facture/class/api_paiements.class.php b/htdocs/compta/facture/class/api_paiements.class.php index 540bb13e8bf..09c60369132 100644 --- a/htdocs/compta/facture/class/api_paiements.class.php +++ b/htdocs/compta/facture/class/api_paiements.class.php @@ -1,6 +1,6 @@ - * Copyright (C) 2024 Frédéric France + * Copyright (C) 2024-2025 Frédéric France * Copyright (C) 2025 Charlene Benke * * This program is free software; you can redistribute it and/or modify @@ -257,7 +257,7 @@ class Paiements extends DolibarrApi // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore /** * Clean sensitive object data fields - * @phpstan-template T of Object + * @phpstan-template T * * @param Object $object Object to clean * @return Object Object with cleaned properties diff --git a/htdocs/compta/facture/document.php b/htdocs/compta/facture/document.php index 4ce67b885ea..be7b93b61cc 100644 --- a/htdocs/compta/facture/document.php +++ b/htdocs/compta/facture/document.php @@ -78,11 +78,13 @@ if (!$sortfield) { } $object = new Facture($db); -if ($object->fetch($id, $ref)) { +if ($object->fetch($id, $ref) > 0) { $object->fetch_thirdparty(); - $upload_dir = $conf->facture->dir_output."/".dol_sanitizeFileName($object->ref); } +$upload_dir = $conf->invoice->multidir_output[empty($object->entity) ? 1 : $object->entity].'/'.dol_sanitizeFileName($object->ref); + + $permissiontoadd = $user->hasRight('facture', 'creer'); // Security check @@ -136,7 +138,7 @@ if ($id > 0 || !empty($ref)) { if ($object->fetch($id, $ref) > 0) { $object->fetch_thirdparty(); - $upload_dir = $conf->facture->multidir_output[$object->entity ?? $conf->entity].'/'.dol_sanitizeFileName($object->ref); + $upload_dir = $conf->invoice->multidir_output[$object->entity ?? $conf->entity].'/'.dol_sanitizeFileName($object->ref); $head = facture_prepare_head($object); print dol_get_fiche_head($head, 'documents', $langs->trans('InvoiceCustomer'), -1, $object->picto); diff --git a/htdocs/compta/facture/messaging.php b/htdocs/compta/facture/messaging.php index ebc1dd025f6..360109da756 100644 --- a/htdocs/compta/facture/messaging.php +++ b/htdocs/compta/facture/messaging.php @@ -45,7 +45,7 @@ $langs->loadLangs(array("bills", "other")); $id = GETPOSTINT('id'); $ref = GETPOST('ref', 'alpha'); $action = GETPOST('action', 'aZ09'); -$cancel = GETPOST('cancel', 'aZ09'); +$cancel = GETPOST('cancel', 'alpha'); $contextpage = GETPOST('contextpage', 'aZ') ? GETPOST('contextpage', 'aZ') : str_replace('_', '', basename(dirname(__FILE__)).basename(__FILE__, '.php')); // To manage different context of search $backtopage = GETPOST('backtopage', 'alpha'); diff --git a/htdocs/compta/facture/stats/index.php b/htdocs/compta/facture/stats/index.php index eaecc303649..aaeb4e5841f 100644 --- a/htdocs/compta/facture/stats/index.php +++ b/htdocs/compta/facture/stats/index.php @@ -395,6 +395,7 @@ if (!in_array($nowyear, $arrayyears)) { $arrayyears[$nowyear] = $nowyear; } arsort($arrayyears); +print img_picto('', 'calendar', 'class="pictofixedwidth"'); print $form->selectarray('year', $arrayyears, $year, 0, 0, 0, '', 0, 0, 0, '', 'width75'); print ''; print ''; diff --git a/htdocs/compta/localtax/card.php b/htdocs/compta/localtax/card.php index a0038878946..c796c1702ee 100644 --- a/htdocs/compta/localtax/card.php +++ b/htdocs/compta/localtax/card.php @@ -44,7 +44,7 @@ $langs->loadLangs(array('compta', 'banks', 'bills')); $id = GETPOSTINT("id"); $action = GETPOST("action", "aZ09"); -$cancel = GETPOST('cancel', 'aZ09'); +$cancel = GETPOST('cancel', 'alpha'); $refund = GETPOSTINT("refund"); if (empty($refund)) { diff --git a/htdocs/compta/paiement_charge.php b/htdocs/compta/paiement_charge.php index be46091846d..40ab68b1077 100644 --- a/htdocs/compta/paiement_charge.php +++ b/htdocs/compta/paiement_charge.php @@ -42,7 +42,7 @@ $langs->loadLangs(array("banks", "bills", "compta")); $action = GETPOST('action', 'aZ09'); $confirm = GETPOST('confirm', 'alpha'); -$cancel = GETPOST('cancel'); +$cancel = GETPOST('cancel', 'alpha'); $chid = GETPOSTINT("id"); $amounts = array(); diff --git a/htdocs/compta/paiement_vat.php b/htdocs/compta/paiement_vat.php index 7dd9aaabb5a..12725f32559 100644 --- a/htdocs/compta/paiement_vat.php +++ b/htdocs/compta/paiement_vat.php @@ -43,7 +43,7 @@ $langs->loadLangs(array("banks", "bills")); $action = GETPOST('action', 'alpha'); $confirm = GETPOST('confirm', 'alpha'); -$cancel = GETPOST('cancel'); +$cancel = GETPOST('cancel', 'alpha'); $chid = GETPOSTINT("id"); $amounts = array(); diff --git a/htdocs/compta/sociales/card.php b/htdocs/compta/sociales/card.php index 444cf66ee75..38f3e84985a 100644 --- a/htdocs/compta/sociales/card.php +++ b/htdocs/compta/sociales/card.php @@ -59,7 +59,7 @@ $id = GETPOSTINT('id'); $ref = GETPOST('ref', 'alpha'); $action = GETPOST('action', 'aZ09'); $confirm = GETPOST('confirm', 'alpha'); -$cancel = GETPOST('cancel', 'aZ09'); +$cancel = GETPOST('cancel', 'alpha'); $contextpage = GETPOST('contextpage', 'aZ') ? GETPOST('contextpage', 'aZ') : 'myobjectcard'; // To manage different context of search $backtopage = GETPOST('backtopage', 'alpha'); $backtopageforcancel = GETPOST('backtopageforcancel', 'alpha'); diff --git a/htdocs/compta/sociales/list.php b/htdocs/compta/sociales/list.php index 318e44dc892..72c00d61c3e 100644 --- a/htdocs/compta/sociales/list.php +++ b/htdocs/compta/sociales/list.php @@ -31,13 +31,6 @@ // Load Dolibarr environment require '../../main.inc.php'; -require_once DOL_DOCUMENT_ROOT.'/compta/sociales/class/chargesociales.class.php'; -require_once DOL_DOCUMENT_ROOT.'/compta/bank/class/account.class.php'; -require_once DOL_DOCUMENT_ROOT.'/core/class/html.formsocialcontrib.class.php'; -require_once DOL_DOCUMENT_ROOT.'/core/class/html.formother.class.php'; -require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php'; -require_once DOL_DOCUMENT_ROOT.'/projet/class/project.class.php'; - /** * @var Conf $conf * @var DoliDB $db @@ -45,6 +38,12 @@ require_once DOL_DOCUMENT_ROOT.'/projet/class/project.class.php'; * @var Translate $langs * @var User $user */ +require_once DOL_DOCUMENT_ROOT.'/compta/sociales/class/chargesociales.class.php'; +require_once DOL_DOCUMENT_ROOT.'/compta/bank/class/account.class.php'; +require_once DOL_DOCUMENT_ROOT.'/core/class/html.formsocialcontrib.class.php'; +require_once DOL_DOCUMENT_ROOT.'/core/class/html.formother.class.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/projet/class/project.class.php'; // Load translation files required by the page $langs->loadLangs(array('compta', 'banks', 'bills', 'hrm', 'projects')); @@ -103,15 +102,13 @@ if (!$sortorder) { $sortorder = "DESC"; } -$filtre = GETPOSTINT("filtre"); - $arrayfields = array( 'cs.rowid' => array('label' => "Ref", 'checked' => '1', 'position' => 10), 'cs.libelle' => array('label' => "Label", 'checked' => '1', 'position' => 20), 'cs.fk_type' => array('label' => "Type", 'checked' => '1', 'position' => 30), 'cs.date_ech' => array('label' => "Date", 'checked' => '1', 'position' => 40), 'cs.periode' => array('label' => "PeriodEndDate", 'checked' => '1', 'position' => 50), - 'p.ref' => array('label' => "ProjectRef", 'checked' => '1', 'position' => 60, 'enabled' => (string) (int) (isModEnabled('project'))), + 'p.ref' => array('label' => "ProjectRef", 'checked' => '-1', 'position' => 60, 'enabled' => (string) (int) (isModEnabled('project'))), 'cs.fk_user' => array('label' => "Employee", 'checked' => '1', 'position' => 70), 'cs.fk_mode_reglement' => array('checked' => '-1', 'position' => 80, 'label' => "DefaultPaymentMode"), 'cs.amount' => array('label' => "Amount", 'checked' => '1', 'position' => 100), @@ -521,21 +518,21 @@ if (!empty($arrayfields['p.ref']['checked'])) { if (!empty($arrayfields['cs.fk_user']['checked'])) { // Employee print ''; - print $form->select_dolusers($search_users, 'search_users', 1, null, 0, '', '', '0', 0, 0, '', 0, '', 'maxwidth150', 0, 0, true); + print $form->select_dolusers($search_users, 'search_users', 1, null, 0, '', '', '0', 0, 0, '', 0, '', 'maxwidth125', 0, 0, true); print ''; } // Filter: Type if (!empty($arrayfields['cs.fk_mode_reglement']['checked'])) { print ''; - print $form->select_types_paiements($search_type, 'search_type', '', 0, 1, 1, 0, 1, 'maxwidth150', 1); + print $form->select_types_paiements($search_type, 'search_type', '', 0, 1, 1, 0, 1, 'maxwidth125', 1); print ''; } // Filter: Bank Account if (!empty($arrayfields['cs.fk_account']['checked'])) { print ''; - $form->select_comptes($search_account, 'search_account', 0, '', 1, '', 0, 'maxwidth150'); + $form->select_comptes($search_account, 'search_account', 0, '', 1, '', 0, 'maxwidth125'); print ''; } diff --git a/htdocs/compta/sociales/note.php b/htdocs/compta/sociales/note.php index d48e87dd33b..8b689163370 100644 --- a/htdocs/compta/sociales/note.php +++ b/htdocs/compta/sociales/note.php @@ -47,7 +47,7 @@ $langs->loadLangs(array('compta', 'bills')); $id = GETPOSTINT('id'); $ref = GETPOST('ref', 'alpha'); $action = GETPOST('action', 'aZ09'); -$cancel = GETPOST('cancel', 'aZ09'); +$cancel = GETPOST('cancel', 'alpha'); $backtopage = GETPOST('backtopage', 'alpha'); $object = new ChargeSociales($db); diff --git a/htdocs/compta/tva/card.php b/htdocs/compta/tva/card.php index cafcc04e377..310cf7d8ec0 100644 --- a/htdocs/compta/tva/card.php +++ b/htdocs/compta/tva/card.php @@ -56,7 +56,7 @@ $id = GETPOSTINT('id'); $ref = GETPOST('ref', 'alpha'); $action = GETPOST('action', 'aZ09'); $confirm = GETPOST('confirm', 'alpha'); -$cancel = GETPOST('cancel', 'aZ09'); +$cancel = GETPOST('cancel', 'alpha'); $contextpage = GETPOST('contextpage', 'aZ') ? GETPOST('contextpage', 'aZ') : 'myobjectcard'; // To manage different context of search $backtopage = GETPOST('backtopage', 'alpha'); $backtopageforcancel = GETPOST('backtopageforcancel', 'alpha'); diff --git a/htdocs/conf/conf.php.example b/htdocs/conf/conf.php.example index 294aac01619..045198af867 100644 --- a/htdocs/conf/conf.php.example +++ b/htdocs/conf/conf.php.example @@ -312,6 +312,16 @@ $dolibarr_main_prod='1'; // $dolibarr_main_restrict_os_commands='mariadb-dump, mariadb, mysqldump, mysql, pg_dump, pg_restore, clamdscan, clamdscan.exe'; +// dolibarr_main_restrict_eval_methods +// ================================== +// A whitelist of functions and methods to restrict the commands you can execute in a custom calculated fields, like "computed fields" of +// extrafields or string conditions of extrafields. +// Default value: 'getDolGlobalString, getDolGlobalInt, getDolCurrency, fetchNoCompute, hasRight, isAdmin, isModEnabled, isStringVarMatching, abs, round, dol_now, preg_match' +// Examples: +// $dolibarr_main_restrict_eval_methods='getDolGlobalString, getDolGlobalInt, getDolCurrency, fetchNoCompute, hasRight, isAdmin, isModEnabled, isStringVarMatching, abs, min, max, round, dol_now, dol_concat, preg_match'; +// +$dolibarr_main_restrict_eval_methods='getDolGlobalString, getDolGlobalInt, getDolCurrency, fetchNoCompute, hasRight, isAdmin, isModEnabled, isStringVarMatching, abs, min, max, round, dol_now, preg_match'; + // dolibarr_main_disabled_modules // ================================== // To restrict the activation and use of certain potentially security-sensitive modules. diff --git a/htdocs/contact/agenda.php b/htdocs/contact/agenda.php index 2853f39b99b..95c538e58b6 100644 --- a/htdocs/contact/agenda.php +++ b/htdocs/contact/agenda.php @@ -32,6 +32,13 @@ // Load Dolibarr environment require '../main.inc.php'; +/** + * @var Conf $conf + * @var DoliDB $db + * @var HookManager $hookmanager + * @var Translate $langs + * @var User $user + */ require_once DOL_DOCUMENT_ROOT.'/comm/action/class/actioncomm.class.php'; require_once DOL_DOCUMENT_ROOT.'/contact/class/contact.class.php'; require_once DOL_DOCUMENT_ROOT.'/core/lib/contact.lib.php'; @@ -46,14 +53,6 @@ require_once DOL_DOCUMENT_ROOT.'/user/class/user.class.php'; require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php'; require_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php'; -/** - * @var Conf $conf - * @var DoliDB $db - * @var HookManager $hookmanager - * @var Translate $langs - * @var User $user - */ - // Load translation files required by the page $langs->loadLangs(array('companies', 'users', 'other', 'commercial')); @@ -87,8 +86,8 @@ if (!empty($canvas)) { $objcanvas->getCanvas('contact', 'contactcard', $canvas); } -if (GETPOST('actioncode', 'array')) { - $actioncode = GETPOST('actioncode', 'array', 3); +if (GETPOSTISARRAY('actioncode')) { + $actioncode = GETPOST('actioncode', 'array:alpha', 3); if (!count($actioncode)) { $actioncode = '0'; } @@ -97,6 +96,9 @@ if (GETPOST('actioncode', 'array')) { } $search_rowid = GETPOST('search_rowid'); $search_agenda_label = GETPOST('search_agenda_label'); +$search_complete = GETPOST('search_complete'); +$search_dateevent_start = GETPOSTDATE('dateevent_start'); +$search_dateevent_end = GETPOSTDATE('dateevent_end'); // Security check if ($user->socid) { @@ -120,10 +122,10 @@ $offset = $limit * $page; $pageprev = $page - 1; $pagenext = $page + 1; if (!$sortfield) { - $sortfield = 'a.datep, a.id'; + $sortfield = 'a.datep,a.id'; } if (!$sortorder) { - $sortorder = 'DESC'; + $sortorder = 'DESC,DESC'; } @@ -145,9 +147,11 @@ if (empty($reshook)) { } // Purge search criteria - if (GETPOST('button_removefilter_x', 'alpha') || GETPOST('button_removefilter.x', 'alpha') || GETPOST('button_removefilter', 'alpha')) { // All test are required to be compatible with all browsers + if (GETPOST('button_removefilter_x', 'alpha') || GETPOST('button_removefilter.x', 'alpha') || GETPOST('button_removefilter', 'alpha')) { // All tests are required to be compatible with all browsers $actioncode = ''; + $search_rowid = ''; $search_agenda_label = ''; + $search_complete = ''; } } @@ -262,6 +266,8 @@ if (is_object($objcanvas) && $objcanvas->displayCanvasExists($action)) { print ''; + print '
    '; + print dol_get_fiche_end(); @@ -285,7 +291,7 @@ if (is_object($objcanvas) && $objcanvas->displayCanvasExists($action)) { $out .= '&originid='.$objthirdparty->id.($objthirdparty->id > 0 ? '&socid='.$objthirdparty->id : ''); } $out .= (!empty($objcon->id) ? '&contactid='.$objcon->id : '').'&origin=contact&originid='.$object->id.'&backtopage='.urlencode($_SERVER['PHP_SELF'].($objcon->id > 0 ? '?id='.$objcon->id : '')); - $out .= '&datep='.urlencode(dol_print_date(dol_now(), 'dayhourlog')); + $out .= '&datep='.urlencode(dol_print_date(dol_now(), 'dayhourlog', 'tzuserrel')); } if ($user->hasRight('agenda', 'myactions', 'create') || $user->hasRight('agenda', 'allactions', 'create')) { @@ -298,21 +304,54 @@ if (is_object($objcanvas) && $objcanvas->displayCanvasExists($action)) { $param = '&id='.$id; if (!empty($contextpage) && $contextpage != $_SERVER["PHP_SELF"]) { - $param .= '&contextpage='.$contextpage; + $param .= '&contextpage='.urlencode($contextpage); } if ($limit > 0 && $limit != $conf->liste_limit) { - $param .= '&limit='.$limit; + $param .= '&limit='.((int) $limit); + } + if ($search_rowid) { + $param .= '&search_rowid='.urlencode($search_rowid); + } + if ($actioncode !== '' && $actioncode !== '-1') { + $param .= '&actioncode='.urlencode($actioncode); + } + if ($search_agenda_label) { + $param .= '&search_agenda_label='.urlencode($search_agenda_label); + } + if ($search_complete != '') { + $param .= '&search_complete='.urlencode($search_complete); + } + if ($search_dateevent_start != '') { + $param .= '&dateevent_startyear='.GETPOSTINT('dateevent_startyear'); + $param .= '&dateevent_startmonth='.GETPOSTINT('dateevent_startmonth'); + $param .= '&dateevent_startday='.GETPOSTINT('dateevent_startday'); + } + if ($search_dateevent_end != '') { + $param .= '&dateevent_endyear='.GETPOSTINT('dateevent_endyear'); + $param .= '&dateevent_endmonth='.GETPOSTINT('dateevent_endmonth'); + $param .= '&dateevent_endday='.GETPOSTINT('dateevent_endday'); } - print load_fiche_titre($langs->trans("ActionsOnContact"), $newcardbutton, ''); - //print_barre_liste($langs->trans("ActionsOnCompany"), 0, $_SERVER["PHP_SELF"], '', $sortfield, $sortorder, $morehtmlcenter, 0, -1, '', '', '', '', 0, 1, 1); + // Try to know count of actioncomm from cache + require_once DOL_DOCUMENT_ROOT.'/core/lib/memory.lib.php'; + $cachekey = 'count_events_contact_'.$object->id; + $nbEvent = dol_getcache($cachekey); + + $titlelist = $langs->trans("ActionsOnContact").(is_numeric($nbEvent) ? '('.$nbEvent.')' : ''); + if (!empty($conf->dol_optimize_smallscreen)) { + $titlelist = $langs->trans("Actions").(is_numeric($nbEvent) ? '('.$nbEvent.')' : ''); + } + + print_barre_liste($titlelist, 0, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, '', 0, -1, '', 0, $newcardbutton, '', 0, 1, 0); // List of all actions $filters = array(); $filters['search_agenda_label'] = $search_agenda_label; $filters['search_rowid'] = $search_rowid; + $filters['search_complete'] = $search_complete; // Can be 'na', '0', '100', '50' - show_actions_done($conf, $langs, $db, $objthirdparty, $object, 0, $actioncode, '', $filters, $sortfield, $sortorder); + // TODO Replace this with same code than into list.php + show_actions_done($conf, $langs, $db, $objthirdparty, $object, 0, $actioncode, '', $filters, $sortfield, $sortorder, $object->module); } } } diff --git a/htdocs/contact/card.php b/htdocs/contact/card.php index 5aa7de7c68a..a5a5a0f6ac7 100644 --- a/htdocs/contact/card.php +++ b/htdocs/contact/card.php @@ -1538,10 +1538,10 @@ if (is_object($objcanvas) && $objcanvas->displayCanvasExists($action)) { if (empty($user->socid)) { if (!empty($object->email)) { $langs->load("mails"); - print ''; + print dolGetButtonAction('', $langs->trans('SendMail'), 'email', dolBuildUrl($_SERVER["PHP_SELF"], ['id' => $object->id, 'action' => 'presend', 'mode' => 'init'], true).'#formmailbeforetitle', ''); } else { $langs->load("mails"); - print ''; + print dolGetButtonAction('', $langs->trans('SendMail'), 'email', '#', '', false); } } diff --git a/htdocs/contact/list.php b/htdocs/contact/list.php index e6352f9666f..bafa70f8bf6 100644 --- a/htdocs/contact/list.php +++ b/htdocs/contact/list.php @@ -1001,16 +1001,22 @@ print ''; print ''; -$newcardbutton = ''; -$newcardbutton .= dolGetButtonTitle($langs->trans('ViewList'), '', 'fa fa-bars imgforviewmode', $_SERVER["PHP_SELF"].'?mode=common'.preg_replace('/(&|\?)*mode=[^&]+/', '', $param), '', ((empty($mode) || $mode == 'common') ? 2 : 1), array('morecss' => 'reposition')); -$newcardbutton .= dolGetButtonTitle($langs->trans('ViewKanban'), '', 'fa fa-th-list imgforviewmode', $_SERVER["PHP_SELF"].'?mode=kanban'.preg_replace('/(&|\?)*mode=[^&]+/', '', $param), '', ($mode == 'kanban' ? 2 : 1), array('morecss' => 'reposition')); -$newcardbutton .= dolGetButtonTitleSeparator(); -if ($contextpage != 'poslist') { - $newcardbutton .= dolGetButtonTitle($langs->trans('NewContactAddress'), '', 'fa fa-plus-circle', DOL_URL_ROOT.'/contact/card.php?action=create', '', $permissiontoadd); -} elseif ($user->hasRight('societe', 'contact', 'creer')) { - $url = DOL_URL_ROOT . '/contact/card.php?action=create&type=t&contextpage=poslist&optioncss=print&backtopage=' . urlencode($_SERVER["PHP_SELF"] . '?token=' . newToken() . 'type=t&contextpage=poslist&nomassaction=1&optioncss=print&place='.$place); - $label = 'MenuNewCustomer'; - $newcardbutton .= dolGetButtonTitle($langs->trans($label), '', 'fa fa-plus-circle', $url); +$newcardbutton = ''; +$parameters = array(); +$reshook = $hookmanager->executeHooks('printNewCardButton', $parameters, $object); +if (empty($reshook)) { + $newcardbutton .= dolGetButtonTitle($langs->trans('ViewList'), '', 'fa fa-bars imgforviewmode', $_SERVER["PHP_SELF"].'?mode=common'.preg_replace('/(&|\?)*mode=[^&]+/', '', $param), '', ((empty($mode) || $mode == 'common') ? 2 : 1), array('morecss' => 'reposition')); + $newcardbutton .= dolGetButtonTitle($langs->trans('ViewKanban'), '', 'fa fa-th-list imgforviewmode', $_SERVER["PHP_SELF"].'?mode=kanban'.preg_replace('/(&|\?)*mode=[^&]+/', '', $param), '', ($mode == 'kanban' ? 2 : 1), array('morecss' => 'reposition')); + $newcardbutton .= dolGetButtonTitleSeparator(); + if ($contextpage != 'poslist') { + $newcardbutton .= dolGetButtonTitle($langs->trans('NewContactAddress'), '', 'fa fa-plus-circle', DOL_URL_ROOT.'/contact/card.php?action=create', '', $permissiontoadd); + } elseif ($user->hasRight('societe', 'contact', 'creer')) { + $url = DOL_URL_ROOT . '/contact/card.php?action=create&type=t&contextpage=poslist&optioncss=print&backtopage=' . urlencode($_SERVER["PHP_SELF"] . '?token=' . newToken() . 'type=t&contextpage=poslist&nomassaction=1&optioncss=print&place='.$place); + $label = 'MenuNewCustomer'; + $newcardbutton .= dolGetButtonTitle($langs->trans($label), '', 'fa fa-plus-circle', $url); + } +} else { + $newcardbutton = $hookmanager->resPrint; } print_barre_liste($title, $page, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, (string) $massactionbutton, $num, $nbtotalofrecords, 'address', 0, $newcardbutton, '', $limit, 0, 0, 1); diff --git a/htdocs/contrat/card.php b/htdocs/contrat/card.php index 12de36e36b6..5cadeaad743 100644 --- a/htdocs/contrat/card.php +++ b/htdocs/contrat/card.php @@ -2253,9 +2253,9 @@ if ($action == 'create') { if (empty($user->socid)) { if ($object->status == $object::STATUS_VALIDATED) { if ((!getDolGlobalString('MAIN_USE_ADVANCED_PERMS') || $user->hasRight('contrat', 'creer'))) { - print dolGetButtonAction('', $langs->trans('SendMail'), 'default', $_SERVER["PHP_SELF"].'?id='.$object->id.'&action=presend&token='.newToken().'&mode=init#formmailbeforetitle', '', true, $params); + print dolGetButtonAction('', $langs->trans('SendMail'), 'email', $_SERVER["PHP_SELF"].'?id='.$object->id.'&action=presend&token='.newToken().'&mode=init#formmailbeforetitle', '', true, $params); } else { - print dolGetButtonAction('', $langs->trans('SendMail'), 'default', '#', '', false, $params); + print dolGetButtonAction('', $langs->trans('SendMail'), 'email', '#', '', false, $params); } } } diff --git a/htdocs/contrat/class/api_contracts.class.php b/htdocs/contrat/class/api_contracts.class.php index 8a1ef37975f..45c35e6d7b3 100644 --- a/htdocs/contrat/class/api_contracts.class.php +++ b/htdocs/contrat/class/api_contracts.class.php @@ -1,7 +1,7 @@ * Copyright (C) 2016 Laurent Destailleur - * Copyright (C) 2018-2024 Frédéric France + * Copyright (C) 2018-2025 Frédéric France * Copyright (C) 2025 MDW * * This program is free software; you can redistribute it and/or modify @@ -935,9 +935,12 @@ class Contracts extends DolibarrApi // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore /** * Clean sensible object datas + * @phpstan-template T * * @param Object $object Object to clean * @return Object Object with cleaned properties + * @phpstan-param T $object + * @phpstan-return T */ protected function _cleanObjectDatas($object) { diff --git a/htdocs/contrat/class/contrat.class.php b/htdocs/contrat/class/contrat.class.php index d791e1f7456..26bc371351f 100644 --- a/htdocs/contrat/class/contrat.class.php +++ b/htdocs/contrat/class/contrat.class.php @@ -459,18 +459,20 @@ class Contrat extends CommonObject * @param User $user Object User making action * @param int $notrigger 1=Does not execute triggers, 0=Execute triggers * @param string $comment Comment + * @param int $nofetchlines Use 1 to avoid to do a fetch_lines() on contract if you know it was already done * @return int Return integer <0 if KO, >0 if OK * @see activateAll() */ - public function closeAll(User $user, $notrigger = 0, $comment = '') + public function closeAll(User $user, $notrigger = 0, $comment = '', $nofetchlines = 0) { dol_syslog("closeAll begin", LOG_DEBUG, 1); $this->db->begin(); // Load lines - // TODO Should be useless if object was fetched without the noline param. - $this->fetch_lines(); + if (empty($nofetchlines)) { + $this->fetch_lines(); + } $now = dol_now(); diff --git a/htdocs/core/actions_extrafields.inc.php b/htdocs/core/actions_extrafields.inc.php index 5cbb0e3567c..64f76e3b793 100644 --- a/htdocs/core/actions_extrafields.inc.php +++ b/htdocs/core/actions_extrafields.inc.php @@ -494,7 +494,7 @@ if ($action == 'encrypt') { $sql .= " AND te.".$attributekey." IS NOT NULL"; $sql .= " AND te.".$attributekey." <> ''"; if ($extrafields->attributes[$elementtype]['entityid'][$attributekey] == $conf->entity) { - $sql .= " AND t.entity = ".getEntity($arrayofelement['table_element'], 0); + $sql .= " AND t.entity = ".getEntity($arrayofelement['element'], 0); } //print $sql; diff --git a/htdocs/core/actions_linkedfiles.inc.php b/htdocs/core/actions_linkedfiles.inc.php index 4b28e937b97..e59fc651416 100644 --- a/htdocs/core/actions_linkedfiles.inc.php +++ b/htdocs/core/actions_linkedfiles.inc.php @@ -157,7 +157,6 @@ if ($action == 'confirm_deletefile' && $confirm == 'yes' && !empty($permissionto } } $linkid = GETPOSTINT('linkid'); - if ($urlfile) { // delete of a file $dir = dirname($file).'/'; // Chemin du dossier contenant l'image d'origine diff --git a/htdocs/core/actions_printing.inc.php b/htdocs/core/actions_printing.inc.php index 8226dcae418..52fc3fa27d9 100644 --- a/htdocs/core/actions_printing.inc.php +++ b/htdocs/core/actions_printing.inc.php @@ -20,13 +20,12 @@ /** * \file htdocs/core/actions_printing.inc.php - * \brief Code for actions print_file to print file with calling trigger + * \ingroup core + * \brief Code for actions print_file to print file (with calling trigger) when using the Direct Print feature. + * The relative filename to print must be provided into GETPOST('file', 'alpha') parameter */ -// $action must be defined -// $db, $user, $conf, $langs must be defined -// Filename to print must be provided into 'file' parameter /** * @var Conf $conf * @var DoliDB $db @@ -36,6 +35,7 @@ * * @var string $action */ + // Print file if ($action == 'print_file' && $user->hasRight('printing', 'read')) { $langs->load("printing"); @@ -44,7 +44,6 @@ if ($action == 'print_file' && $user->hasRight('printing', 'read')) { $list = $objectprint->listDrivers($db, 10); $dirmodels = array_merge(array('/core/modules/printing/'), (array) $conf->modules_parts['printing']); if (!empty($list)) { - $errorprint = 0; $printerfound = 0; foreach ($list as $driver) { foreach ($dirmodels as $dir) { @@ -80,7 +79,26 @@ if ($action == 'print_file' && $user->hasRight('printing', 'read')) { break; } try { - $ret = $printer->printFile(GETPOST('file', 'alpha'), $module, $subdir); + // Case of printing an invoice + $filetoprint = GETPOST('file', 'alpha'); //Example FAYYMM-123/FAYYMM-123-xxx.pdf + if ($module == 'facture') { + require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php'; + $refinvoice = preg_replace('/[\/\\\\].*$/', '', $filetoprint); + $tmpinvoice = new Facture($db); + $tmpinvoice->fetch(0, $refinvoice); + if ($tmpinvoice->id > 0) { + // Increase counter by 1 + $sql = "UPDATE ".MAIN_DB_PREFIX."facture SET pos_print_counter = pos_print_counter + 1"; + $sql .= " WHERE rowid = ".((int) $tmpinvoice->id); + $db->query($sql); + + //$tmpinvoice->pos_print_counter += 1; + //$tmpinvoice->update($user, 1); // We disable trigger here because we already call the trigger $action = DOC_PREVIEW or DOC_DOWNLOAD just after + } + } + + + $ret = $printer->printFile($filetoprint, $module, $subdir); if ($ret > 0) { //print '
    '.print_r($printer->errors, true).'
    '; setEventMessages($printer->error, $printer->errors, 'errors'); diff --git a/htdocs/core/actions_sendmails.inc.php b/htdocs/core/actions_sendmails.inc.php index beeae4b07b8..56abcb90ae5 100644 --- a/htdocs/core/actions_sendmails.inc.php +++ b/htdocs/core/actions_sendmails.inc.php @@ -453,6 +453,13 @@ if (($action == 'send' || $action == 'relance') && !GETPOST('addfile') && !GETPO // Call of triggers (you should have set $triggersendname to execute trigger. if (!empty($triggersendname)) { + if ($triggersendname == 'BILL_SENTBYMAIL' && $object instanceof Facture) { + // If sending email for invoice, we increase the counter of invoices sent by email + $sql = "UPDATE ".MAIN_DB_PREFIX."facture SET email_sent_counter = email_sent_counter + 1"; + $sql .= " WHERE rowid = ".((int) $object->id); + $db->query($sql); + } + $result = $object->call_trigger($triggersendname, $user); // @phan-suppress-current-line PhanPossiblyUndeclaredGlobalVariable if ($result < 0) { $error++; diff --git a/htdocs/core/ajax/ajaxfield.php b/htdocs/core/ajax/ajaxfield.php new file mode 100644 index 00000000000..8eb40de4a42 --- /dev/null +++ b/htdocs/core/ajax/ajaxfield.php @@ -0,0 +1,150 @@ + + * 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 + * 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/ajax/ajaxfield.php + * \ingroup core + * \brief This script returns content of extrafield. See extrafield to update value. + */ + +if (!defined('NOTOKENRENEWAL')) { + // Disables token renewal + define('NOTOKENRENEWAL', 1); +} +if (!defined('NOREQUIREMENU')) { + define('NOREQUIREMENU', '1'); +} +if (!defined('NOREQUIREHTML')) { + define('NOREQUIREHTML', '1'); +} +if (!defined('NOREQUIREAJAX')) { + define('NOREQUIREAJAX', '1'); +} +if (!defined('NOHEADERNOFOOTER')) { + define('NOHEADERNOFOOTER', '1'); +} + +require '../../main.inc.php'; +require_once DOL_DOCUMENT_ROOT . '/core/class/extrafields.class.php'; +require_once DOL_DOCUMENT_ROOT . '/core/class/fieldsmanager.class.php'; + +/** + * @var Conf $conf + * @var DoliDB $db + * @var Translate $langs + * @var User $user + */ + +// object id +$objectid = GETPOST('objectid', 'aZ09'); +// 'module' or 'myobject@mymodule', 'mymodule_myobject' +$objecttype = GETPOST('objecttype', 'aZ09arobase'); +$objectkey = GETPOST('objectkey', 'restricthtml'); +$search = GETPOST('search', 'restricthtml'); +$page = GETPOSTINT('page'); +$mode = GETPOST('mode', 'aZ09'); +$value = GETPOST('value', 'alphanohtml'); +$dependencyvalue = GETPOST('dependencyvalue', 'alphanohtml'); +$limit = 10; +$element_ref = ''; +if (is_numeric($objectid)) { + $objectid = (int) $objectid; +} else { + $element_ref = $objectid; + $objectid = 0; +} +// Load object according to $element +$object = fetchObjectByElement($objectid, $objecttype, $element_ref); +if (empty($object->element)) { + httponly_accessforbidden('Failed to get object with fetchObjectByElement(id=' . $objectid . ', objecttype=' . $objecttype . ')'); +} + +$module = $object->module; +$element = $object->element; + +$usesublevelpermission = ($module != $element ? $element : ''); +if ($usesublevelpermission && !$user->hasRight($module, $element)) { // There is no permission on object defined, we will check permission on module directly + $usesublevelpermission = ''; +} + +// print $object->id.' - '.$object->module.' - '.$object->element.' - '.$object->table_element.' - '.$usesublevelpermission."\n"; + +// Security check +restrictedArea($user, $object->module, $object, $object->table_element, $usesublevelpermission); + + +/* + * View + */ + +top_httphead(); + +$data = [ + 'results' => [], + 'pagination' => [ + 'more' => true, + ] +]; +$nbResult = 0; +if ($object instanceof CommonObject) { + $extrafields = new ExtraFields($db); + $extrafields->fetch_name_optionals_label($object->table_element); + + $fieldManager = new FieldsManager($db); + $fieldInfos = $fieldManager->getFieldsInfos($objectkey, $object, $extrafields, $mode); + $field = $fieldManager->getFieldClass($fieldInfos->type); + if (isset($field)) { + if (method_exists($field, 'getOptions')) { + $fieldInfos->optionsSqlPage = $page; + $fieldInfos->optionsSqlLimit = $limit; + if ($dependencyvalue !== '') { + $fieldInfos->optionsSqlDependencyValue = $dependencyvalue; + } + + /** + * @var CommonSellistField $field + */ + '@phan-var-force CommonSellistField $field'; + $options = $field->getOptions($fieldInfos, $objectkey, $page == 1, true); + if (is_array($options)) { + $nbResult = count($options); + foreach ($options as $key => $option) { + $data['results'][] = [ + 'id' => $key, + 'text' => $option['label'], + ]; + } + } else { + dol_syslog('htdocs/core/ajax/ajaxfield.php ' . $field->errorsToString(), LOG_ERR); + } + } else { + dol_syslog('htdocs/core/ajax/ajaxfield.php method getOptions() don\'t exist in class ' . get_class($field), LOG_ERR); + } + } else { + dol_syslog('htdocs/core/ajax/ajaxfield.php ' . $fieldManager->errorsToString(), LOG_ERR); + } +} + +if ($page > 1 && $nbResult < 10) { + $data['pagination'] = [ + 'more' => false, + ]; +} +print json_encode($data); + +$db->close(); diff --git a/htdocs/core/boxes/modules_boxes.php b/htdocs/core/boxes/modules_boxes.php index d160f915d11..7a81d210e88 100644 --- a/htdocs/core/boxes/modules_boxes.php +++ b/htdocs/core/boxes/modules_boxes.php @@ -185,7 +185,7 @@ class ModeleBoxes // Can't be abstract as it is instantiated to build "empty" bo /** * Load data for box to show them later * - * @param int $max Maximum number of records to load + * @param int<0,max> $max Maximum number of records to load * @return void */ public function loadBox($max = 5) diff --git a/htdocs/core/class/cleadstatus.class.php b/htdocs/core/class/cleadstatus.class.php index d06061d233f..46122f12a54 100644 --- a/htdocs/core/class/cleadstatus.class.php +++ b/htdocs/core/class/cleadstatus.class.php @@ -58,6 +58,13 @@ class CLeadStatus extends CommonDict */ public $percent; + /** + * @var array|string,position:int,notnull?:int,visible:int<-5,5>|string,alwayseditable?:int<0,1>,noteditable?:int<0,1>,default?:string,index?:int,foreignkey?:string,searchall?:int<0,1>,isameasure?:int<0,1>,css?:string,csslist?:string,help?:string,showoncombobox?:int<0,4>,disabled?:int<0,1>,arrayofkeyval?:array,autofocusoncreate?:int<0,1>,comment?:string,copytoclipboard?:int<1,2>,validate?:int<0,1>,showonheader?:int<0,1>}> + */ + public $fields = array( + 'rowid' => array('type' => 'integer', 'label' => 'TechnicalID', 'enabled' => 1, 'position' => 1, 'notnull' => 1, 'visible' => 0, 'noteditable' => 1, 'index' => 1, 'css' => 'left', 'comment' => "Id"), + 'label' => array('type' => 'varchar(128)', 'label' => 'Label', 'enabled' => 1, 'position' => 20, 'notnull' => 1, 'visible' => 1, 'index' => 1, 'searchall' => 1, 'showoncombobox' => 1, 'comment' => "Label of status"), + ); /** * Constructor diff --git a/htdocs/core/class/commondocgenerator.class.php b/htdocs/core/class/commondocgenerator.class.php index 0b2c9c07515..8b54dd7fd61 100644 --- a/htdocs/core/class/commondocgenerator.class.php +++ b/htdocs/core/class/commondocgenerator.class.php @@ -1080,10 +1080,12 @@ abstract class CommonDocGenerator $array_shipment = $this->fill_substitutionarray_with_extrafields($object, $array_shipment, $extrafields, $array_key, $outputlangs); } - // Add info from $object->xxx where xxx has been loaded by fetch_origin() of shipment - if (is_object($object->commande) && !empty($object->commande->ref)) { - $array_shipment['order_ref'] = $object->commande->ref; - $array_shipment['order_ref_customer'] = $object->commande->ref_customer; + // 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 @@ -1302,7 +1304,7 @@ abstract class CommonDocGenerator * @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 $hidetop 1=Hide top bar of array and title, 0=Hide nothing, -1=Hide only title + * @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 @@ -2028,9 +2030,9 @@ abstract class CommonDocGenerator /** * Define Array Column Field for extrafields * - * @param object $object common object det + * @param CommonObject $object common object det * @param Translate $outputlangs langs - * @param int $hidedetails Do not show line details + * @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) @@ -2118,11 +2120,11 @@ abstract class CommonDocGenerator * Define Array Column Field into $this->cols * This method must be implemented by the module that generate the document with its own columns. * - * @param Object $object Common object + * @param CommonObject $object Common object * @param Translate $outputlangs Langs - * @param int $hidedetails Do not show line details - * @param int $hidedesc Do not show desc - * @param int $hideref Do not show ref + * @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) diff --git a/htdocs/core/class/commoninvoice.class.php b/htdocs/core/class/commoninvoice.class.php index 8451b6c3395..0e9440a8f0e 100644 --- a/htdocs/core/class/commoninvoice.class.php +++ b/htdocs/core/class/commoninvoice.class.php @@ -1898,8 +1898,14 @@ abstract class CommonInvoice extends CommonObject { global $mysoc; - // Convert total_ttc to a string with 2 decimal places - $totalTTCString = number_format($this->total_ttc, 2, '.', ''); + // Get the amount to pay + $amount_to_pay = $this->getRemainToPay(); + + // Prevent negative values (e.g. overpayments) + $amount_to_pay = max(0, $amount_to_pay); + + // Ensure numeric formatting for EPC QR code + $amount_to_pay = price2num($amount_to_pay, 'MT'); // Initialize an array to hold the lines of the QR code $lines = array(); @@ -1940,7 +1946,7 @@ abstract class CommonInvoice extends CommonObject } // Add the amount and reference - $lines[] = 'EUR' . $totalTTCString; // Amount (optional) + $lines[] = 'EUR' . $amount_to_pay; // Amount (optional) $lines[] = ''; // Purpose (optional) $lines[] = ''; // Payment reference (optional) $lines[] = $this->ref; // Remittance Information (optional) diff --git a/htdocs/core/class/commonobject.class.php b/htdocs/core/class/commonobject.class.php index 29379743f84..39ef25cb706 100644 --- a/htdocs/core/class/commonobject.class.php +++ b/htdocs/core/class/commonobject.class.php @@ -215,7 +215,7 @@ abstract class CommonObject public $linked_objects; /** - * @var int[][] Array of linked objects ids. Loaded by ->fetchObjectLinked + * @var array> Array of linked objects ids. Loaded by ->fetchObjectLinked */ public $linkedObjectsIds; @@ -805,7 +805,7 @@ abstract class CommonObject public $totalpaid; /** - * @var int|float|null Amount already paid from getSommePaiement(), like $totalpaid, but in the foreign currency + * @var int|float|null Amount already paid from getSommePaiement(), like `$totalpaid`, but in the foreign currency * @see $totalpaid, $alreadypaid */ public $totalpaid_multicurrency; @@ -1283,8 +1283,8 @@ abstract class CommonObject if ($this->restrictiononfksoc && property_exists($this, 'socid') && !empty($this->socid) && !$user->hasRight('societe', 'client', 'voir')) { $sql_allowed_contacts = 'SELECT COUNT(*) as cnt FROM '.$this->db->prefix().'societe_commerciaux as sc'; - $sql_allowed_contacts.= ' WHERE sc.fk_soc = '.(int) $this->socid; - $sql_allowed_contacts.= ' AND sc.fk_user = '.(int) $user->id; + $sql_allowed_contacts .= ' WHERE sc.fk_soc = '.(int) $this->socid; + $sql_allowed_contacts .= ' AND sc.fk_user = '.(int) $user->id; $resql_allowed_contacts = $this->db->query($sql_allowed_contacts); @@ -1296,7 +1296,7 @@ abstract class CommonObject $langs->load("companies"); $this->error = $langs->trans("ErrorCommercialNotAllowedForThirdparty", $user->id); dol_syslog(get_class($this)."::add_contact ".$this->error, LOG_ERR); - return -3; + return -4; } } } @@ -1357,7 +1357,7 @@ abstract class CommonObject $result = $this->call_trigger($triggerPrefix.'_ADD_CONTACT', $user); if ($result < 0) { $this->db->rollback(); - return -1; + return -5; } } @@ -1367,11 +1367,11 @@ abstract class CommonObject if ($this->db->errno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') { $this->error = $this->db->errno(); $this->db->rollback(); - return -2; + return -6; } else { $this->error = $this->db->lasterror(); $this->db->rollback(); - return -1; + return -7; } } } else { @@ -2158,20 +2158,21 @@ abstract class CommonObject public function fetch_origin() { // phpcs:enable - $origin = $this->origin ? $this->origin : $this->origin_type; + $tmpclassname = $this->origin ? $this->origin : $this->origin_type; // Manage classes with non standard name - if ($origin == 'shipping') { - $origin = 'expedition'; + if ($tmpclassname == 'shipping') { + $tmpclassname = 'Expedition'; } - if ($origin == 'delivery') { - $origin = 'livraison'; + if ($tmpclassname == 'delivery') { + $tmpclassname = 'Livraison'; } - if ($origin == 'order_supplier' || $origin == 'supplier_order') { - $origin = 'commandeFournisseur'; + if ($tmpclassname == 'order_supplier' || $tmpclassname == 'supplier_order') { + $tmpclassname = 'CommandeFournisseur'; } - $classname = ucfirst($origin); + $classname = ucfirst($tmpclassname); + $this->origin_object = new $classname($this->db); // @phan-suppress-next-line PhanPluginUnknownObjectMethodCall $this->origin_object->fetch($this->origin_id); @@ -5379,7 +5380,7 @@ abstract class CommonObject * @param string $action Action code * @param Societe $seller Object of seller third party * @param ?Societe $buyer Object of buyer third party - * @param int $selected ID line selected + * @param int<0,max> $selected ID line selected * @param int $dateSelector 1=Show also date range input fields * @param string $defaulttpldir Directory where to find the template * @return void @@ -5478,7 +5479,7 @@ abstract class CommonObject * @param int $dateSelector 1=Show also date range input fields * @param Societe $seller Object of seller third party * @param ?Societe $buyer Object of buyer third party - * @param int $selected ID line selected + * @param int<0,max> $selected ID line selected * @param ?ExtraFields $extrafields Object of extrafields * @param string $defaulttpldir Directory where to find the template (deprecated) * @return void @@ -8368,7 +8369,7 @@ abstract class CommonObject $isDependList = 1; } - $data[(int) $obj->rowid] = $labeltoshow; + $data[$obj->rowid] = $labeltoshow; // Warning: $obj->rowid is an alias and can be an int, but also a string ref. } $i++; diff --git a/htdocs/core/class/dolgraph.class.php b/htdocs/core/class/dolgraph.class.php index b8866064d59..2d17e02ec1c 100644 --- a/htdocs/core/class/dolgraph.class.php +++ b/htdocs/core/class/dolgraph.class.php @@ -750,7 +750,9 @@ class DolGraph foreach ($this->data as $x) { // Loop on each x for ($i = 0; $i < $nbseries; $i++) { // Loop on each series if (is_null($max)) { - $max = $x[$i + 1]; // $i+1 because the index 0 is the legend + if (isset($x[$i + 1])) { + $max = $x[$i + 1]; // $i+1 because the index 0 is the legend + } } elseif ($max < $x[$i + 1]) { $max = $x[$i + 1]; } @@ -780,7 +782,9 @@ class DolGraph foreach ($this->data as $x) { // Loop on each x for ($i = 0; $i < $nbseries; $i++) { // Loop on each series if (is_null($min)) { - $min = $x[$i + 1]; // $i+1 because the index 0 is the legend + if (isset($x[$i + 1])) { + $min = $x[$i + 1]; // $i+1 because the index 0 is the legend + } } elseif ($min > $x[$i + 1]) { $min = $x[$i + 1]; } diff --git a/htdocs/core/class/extrafields.class.php b/htdocs/core/class/extrafields.class.php index 4149a4ebbe4..fe85a429379 100644 --- a/htdocs/core/class/extrafields.class.php +++ b/htdocs/core/class/extrafields.class.php @@ -6,7 +6,7 @@ * Copyright (C) 2009-2012 Laurent Destailleur * Copyright (C) 2009-2012 Regis Houssin * Copyright (C) 2013 Florian Henry - * Copyright (C) 2015-2023 Charlene BENKE + * Copyright (C) 2015-2025 Charlene BENKE * Copyright (C) 2016 Raphaël Doursenaud * Copyright (C) 2017 Nicolas ZABOURI * Copyright (C) 2018-2025 Frédéric France @@ -1987,8 +1987,8 @@ class ExtraFields $element = 'project'; } - //$objectdesc = $param_list[0]; // Example: 'ObjectName:classPath:1:(status:=:1)' Replaced by next line: this was propagated also a filter by ajax call that was blocked by some WAF - $objectdesc = $tmparray[0]; // Example: 'ObjectName:classPath' To not propagate any filter (selectForForms do ajax call and propagating SQL filter is blocked by some WAF). Also we should use the one into the definition in the ->fields of $elem if found. + //$objectdesc = $param_list[0]; // Example: 'ObjectName:classPath:1:(status:=:1)' Replaced by next line: old line propagated also the filter to ajax call that was blocked by some WAF + $objectdesc = $tmparray[0].(empty($tmparray[1]) ? "" : ":".$tmparray[1]); // Example: 'ObjectName:classPath' To not propagate any filter (selectForForms do ajax call and propagating SQL filter is blocked by some WAF). Also we should use the filter into the definition in the ->fields of $elem if found. $objectfield = $element.':options_'.$key; // Example: 'actioncomm:options_fff' To be used in priority to know object linked with all its definition (including filters) $out = $form->selectForForms($objectdesc, $keyprefix.$key.$keysuffix, $value, $showempty, '', '', $morecss, '', 0, 0, '', $objectfield); diff --git a/htdocs/core/class/fieldinfos.class.php b/htdocs/core/class/fieldinfos.class.php new file mode 100644 index 00000000000..86b9369138e --- /dev/null +++ b/htdocs/core/class/fieldinfos.class.php @@ -0,0 +1,325 @@ + + * Copyright (C) 2002-2003 Jean-Louis Bergamo + * Copyright (C) 2004 Sebastien Di Cintio + * Copyright (C) 2004 Benoit Mortier + * Copyright (C) 2009-2012 Laurent Destailleur + * Copyright (C) 2009-2012 Regis Houssin + * Copyright (C) 2013 Florian Henry + * Copyright (C) 2015 Charles-Fr BENKE + * Copyright (C) 2016 Raphaël Doursenaud + * Copyright (C) 2017 Nicolas ZABOURI + * Copyright (C) 2018-2022 Frédéric France + * Copyright (C) 2022 Antonin MARCHAL + * + * 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/fieldinfos.class.php + * \ingroup core + * \brief File of class to stock field infos + */ + + +/** + * Class to stock field infos + */ +class FieldInfos +{ + /** + * @var CommonObject|null Object handler (by reference) + */ + public $object = null; + + /** + * @var string Display mode ('create', 'edit', 'view', 'list') + */ + public $mode = ''; + + /** + * @var int Type origin (object or extra field) + */ + public $fieldType = self::FIELD_TYPE_OBJECT; + + /** + * @var string Key name of the field + */ + public $key = ''; + + /** + * @var string Field origin type + * 'integer', 'integer:ObjectClass:PathToClass[:AddCreateButtonOrNot[:Filter[:Sortfield]]]', + * 'select' (list of values are in 'options'. for integer list of values are in 'arrayofkeyval'), + * 'sellist:TableName:LabelFieldName[:KeyFieldName[:KeyFieldParent[:Filter[:CategoryIdType[:CategoryIdList[:SortField]]]]]]', + * 'chkbxlst:...', + * 'varchar(x)', + * 'text', 'text:none', 'html', + * 'double(24,8)', 'real', 'price', 'stock', + * 'date', 'datetime', 'timestamp', 'duration', + * 'boolean', 'checkbox', 'radio', 'array', + * 'email', 'phone', 'url', 'password', 'ip' + * Note: Filter must be a Dolibarr Universal Filter syntax string. Example: "(t.ref:like:'SO-%') or (t.date_creation:<:'20160101') or (t.status:!=:0) or (t.nature:is:NULL)" + */ + public $originType = ''; + + /** + * @var string Field type (The type name. ex: int, varchar, sellist, boolean, ... ) Used as class name, each type have this class. Ex: IntField, ... + */ + public $type = ''; + + /** + * @var string|null Name of the field in the class + */ + public $nameInClass = null; + + /** + * @var string|null Name of the field in the table + */ + public $nameInTable = null; + + /** + * @var string Field label (the translation key) + */ + public $label = ''; + + /** + * @var string Language file to load + */ + public $langFile = ''; + + /** + * @var string Field picto (code of a picto to show before value in forms) + */ + public $picto = ''; + + /** + * @var int Field position + */ + public $position = 0; + + /** + * @var bool Field required + */ + public $required = false; + + /** + * @var bool Field visible + */ + public $visible = true; + + /** + * @var bool Field visible in the header + */ + public $showOnHeader = false; + + /** + * @var bool Field editable + */ + public $editable = true; + + /** + * @var bool|null Field always editable + */ + public $alwaysEditable = null; + + /** + * @var string Field default value (to be converted in function of the type of the field) + */ + public $defaultValue = ''; + + /** + * @var int|null Field string min length + */ + public $minLength = null; + + /** + * @var int|null Field string max length + */ + public $maxLength = null; + + /** + * @var double|null Field numeric min value + */ + public $minValue = null; + + /** + * @var double|null Field numeric max value + */ + public $maxValue = null; + + /** + * @var string Field size (Example: 255, '24,8') + */ + public $size = ''; + + /** + * @var array Field options (for select, sellist, ...) + */ + public $options = array(); + + /** + * @var array List of value deemed as empty (null always deemed as empty) + */ + public $emptyValues = array(); + + /** + * @var string Specific check for GETPOST() when get field value (for type 'text' and 'html') + */ + public $getPostCheck = ''; + + /** + * @var string CSS for the input field + */ + public $css = ''; + + /** + * @var string CSS for the td + */ + public $tdCss = ''; + + /** + * @var string CSS for the input field in view mode + */ + public $viewCss = ''; + + /** + * @var string CSS for the input field in list + */ + public $listCss = ''; + + /** + * @var string Input placeholder + */ + public $inputPlaceholder = ''; + + /** + * @var string Field help + */ + public $help = ''; + + /** + * @var string Field comment. Is not used. You can store here any text of your choice. It is not used by application. + */ + public $comment = ''; + + /** + * @var string Prompt for AI + */ + public $aiPrompt = ''; + + /** + * @var bool If value of the field must be visible into the label of the combobox that list record + */ + public $showOnComboBox = false; + + /** + * @var bool If displayed in documents + */ + public $printable = false; + + /** + * @var bool If field set to null on clone + */ + public $emptyOnClone = false; + + /** + * @var bool If value must be unique + */ + public $unique = false; + + /** + * @var string If value is computed. (eval the provided string) + */ + public $computed = ''; + + /** + * @var bool Field must be validated + */ + public $validateField = false; + + /** + * @var int Is 1 or 2 to allow to add a picto to copy value into clipboard (1=picto after label, 2=picto after value) + */ + public $copyToClipboard = 0; + + /** + * @var bool Disable the input + */ + public $inputDisabled = false; + + /** + * @var bool Autofocus on the input + */ + public $inputAutofocus = false; + + /** + * @var bool Multi input on text type + */ + public $multiInput = false; + + /** + * @var string Field help in list + */ + public $listHelp = ''; + + /** + * @var bool Add total in list footer + */ + public $listTotalizable = false; + + /** + * @var bool Field checked in the list + */ + public $listChecked = true; + + /** + * @var string|null Alias table used for sql request (ex 't.') + */ + public $sqlAlias = null; + + /** + * @var string|null Dependency value (used for filter list from ajax) + */ + public $optionsSqlDependencyValue = null; + + /** + * @var int|null Current page when get sql result for options on 'sellist' and 'chkbxlst' field type + */ + public $optionsSqlPage = null; + + /** + * @var int|null Current offset when get sql result for options on 'sellist' and 'chkbxlst' field type + */ + public $optionsSqlOffset = null; + + /** + * @var int|null Current limit when get sql result for options on 'sellist' and 'chkbxlst' field type + */ + public $optionsSqlLimit = null; + + /** + * @var string|null getNameUrl() parameters 'xxx:xxx:xxx:...' + */ + public $getNameUrlParams = null; + + /** + * @var array Other parameters + */ + public $otherParams = array(); + + + const FIELD_TYPE_OBJECT = 0; + const FIELD_TYPE_EXTRA_FIELD = 1; +} diff --git a/htdocs/core/class/fields/booleanfield.class.php b/htdocs/core/class/fields/booleanfield.class.php new file mode 100644 index 00000000000..fb768b1703a --- /dev/null +++ b/htdocs/core/class/fields/booleanfield.class.php @@ -0,0 +1,243 @@ + + * + * 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/fields/booleanfield.class.php + * \ingroup core + * \brief File of class to boolean field + */ + +require_once DOL_DOCUMENT_ROOT . '/core/class/fields/commonfield.class.php'; + + +/** + * Class to boolean field + */ +class BooleanField extends CommonField +{ + /** + * @var array List of value deemed as empty (null always deemed as empty) + */ + public $emptyValues = array('', '-1', -1); + + + /** + * Return HTML string to put an input search field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printInputSearchField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + return self::$form->selectyesno($htmlName, $value, 1, false, 1, 1, 'width75 yesno'); + } + + /** + * Return HTML string to put an input field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printInputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $moreAttrib = trim((string) $moreAttrib); + if (empty($moreAttrib)) $moreAttrib = ' ' . $moreAttrib; + $checked = empty($value) ? '' : ' checked="checked"'; + $htmlName = $keyPrefix . $key . $keySuffix; + + $out = self::$form->inputType('checkbox', $htmlName, '1', $htmlName, $moreCss, $moreAttrib . $checked); + $out .= self::$form->inputType('hidden', $htmlName . '_boolean', '1'); // A hidden field ending with "_boolean" that is always set to 1. + + return $out; + } + + /** + * Return HTML string to show a field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printOutputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + if (getDolGlobalInt('MAIN_OPTIMIZEFORTEXTBROWSER') < 2) { + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $moreAttrib = trim((string) $moreAttrib); + if (empty($moreAttrib)) $moreAttrib = ' ' . $moreAttrib; + $checked = empty($value) ? '' : ' checked="checked"'; + $value = self::$form->inputType('checkbox', '', '1', '', $moreCss, $checked . $moreAttrib . ' readonly disabled'); + } else { + $value = yn($value ? 1 : 0); + } + + return $value; + } + + /** + * Get input CSS + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $moreCss Value for css to define style/length of field. + * @param string $defaultCss Default value for css to define style/length of field. + * @return string + * @see self::printInputSearchField(), self::printInputField() + */ + public function getInputCss($fieldInfos, $moreCss = '', $defaultCss = '') + { + if (empty($moreCss)) $moreCss = $defaultCss; + $moreCss = trim((string) $moreCss); + + return empty($moreCss) ? '' : ' ' . $moreCss; + } + + /** + * Verify if the field value is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Value to check (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @return bool + * @see self::printInputField() + */ + public function verifyFieldValue($fieldInfos, $key, $value) + { + $result = parent::verifyFieldValue($fieldInfos, $key, $value); + if ($result && !$this->isEmptyValue($fieldInfos, $value)) { + if (!self::$validator->isBool($value)) { + return false; + } + + $result = true; + } + + return $result; + } + + /** + * Verify if the field value from GET/POST is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return bool + * @see self::printInputField() + */ + public function verifyPostFieldValue($fieldInfos, $key, $keyPrefix = '', $keySuffix = '') + { + return parent::verifyPostFieldValue($fieldInfos, $key, $keyPrefix, $keySuffix); + } + + /** + * Get field value from GET/POST + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $defaultValue Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return mixed + * @see self::printInputField() + */ + public function getPostFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + // We test on a hidden field named "..._boolean" that is always set to 1 if param is in form so + // when nothing is provided we can make a difference between noparam in the form and param was set to nothing. + if (!GETPOSTISSET($htmlName . "_boolean")) { + $value = $defaultValue; + } elseif (GETPOSTISSET($htmlName)) { + $value = GETPOSTINT($htmlName) == 1 ? 1 : 0; + } else { + $value = $defaultValue; + } + + return $value; + } + + /** + * Get search field value from GET/POST + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $defaultValue Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return mixed + * @see self::printInputSearchField() + */ + public function getPostSearchFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $value = GETPOSTINT($htmlName); + } else { + $value = $defaultValue; + } + + return $value; + } + + /** + * Get sql filter for search field + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @return string + * @see self::printInputSearchField(), self::getPostSearchFieldValue() + */ + public function sqlFilterSearchField($fieldInfos, $key, $value) + { + if (!$this->isEmptyValue($fieldInfos, $value)) { + $alias = $fieldInfos->sqlAlias ?? 't.'; + $field = $this->db->sanitize($alias . ($fieldInfos->nameInTable ?? $key)); + + $sql = " AND (" . $field . " = '" . $this->db->escape($value) . "'"; + if ($value == '0') { + $sql .= " OR " . $field . " IS NULL"; + } + $sql .= ")"; + + return $sql; + } + + return ''; + } +} diff --git a/htdocs/core/class/fields/checkboxfield.class.php b/htdocs/core/class/fields/checkboxfield.class.php new file mode 100644 index 00000000000..ea58047ddad --- /dev/null +++ b/htdocs/core/class/fields/checkboxfield.class.php @@ -0,0 +1,271 @@ + + * + * 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/fields/checkboxfield.class.php + * \ingroup core + * \brief File of class to checkbox field(multiselect) + */ + +require_once DOL_DOCUMENT_ROOT . '/core/class/fields/commonselectfield.class.php'; + + +/** + * Class to checkbox field (multiselect) + */ +class CheckboxField extends CommonSelectField +{ + /** + * @var array List of value deemed as empty (null always deemed as empty) + */ + public $emptyValues = array(array(), ''); + + + /** + * Return HTML string to put an input search field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printInputSearchField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + return $this->printInputField($fieldInfos, $key, $value, $keyPrefix, $keySuffix, $moreCss, $moreAttrib); + } + + /** + * Return HTML string to put an input field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printInputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + global $conf; + + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $moreAttrib = trim((string) $moreAttrib); + if (empty($moreAttrib)) $moreAttrib = ' ' . $moreAttrib; + $htmlName = $keyPrefix . $key . $keySuffix; + $values = $this->isEmptyValue($fieldInfos, $value) ? array() : (is_string($value) ? explode(',', $value) : (is_array($value) ? $value: array($value))); + + $optionsList = array(); + $options = $this->getOptions($fieldInfos, $key); + + return self::$form->multiselectarray($htmlName, $options, $values, 0, 0, $moreCss, 0, 0, $moreAttrib, '', '', (int) (!empty($conf->use_javascript_ajax) && !getDolGlobalString('MAIN_EXTRAFIELDS_DISABLE_SELECT2'))); + } + + /** + * Return HTML string to show a field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printOutputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + global $langs; + $values = $this->isEmptyValue($fieldInfos, $value) ? array() : (is_string($value) ? explode(',', $value) : (is_array($value) ? $value: array($value))); + + $out = ''; + if (!$this->isEmptyValue($fieldInfos, $values)) { + $options = $this->getOptions($fieldInfos, $key); + $toPrint = array(); + foreach ($values as $val) { + $valueToPrint = null; + foreach ($options as $optionKey => $optionInfos) { + if (((string) $optionKey) == $val) { + $valueToPrint = $optionInfos['label']; + break; + } + } + if (!isset($valueToPrint)) { + $langs->load("errors"); + $valueToPrint = $langs->trans('ErrorRecordNotFound') . ' ( ' . $val . ' )'; + } + $toPrint[] = $valueToPrint; + } + $out = self::$form->outputMultiValues($toPrint); + } + + return $out; + } + + /** + * Get input CSS + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $moreCss Value for css to define style/length of field. + * @param string $defaultCss Default value for css to define style/length of field. + * @return string + * @see self::printInputSearchField(), self::printInputField() + */ + public function getInputCss($fieldInfos, $moreCss = '', $defaultCss = '') + { + return parent::getInputCss($fieldInfos, $moreCss, $defaultCss ? $defaultCss : 'minwidth400'); + } + + /** + * Verify if the field value is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Value to check (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @return bool + * @see self::printInputField() + */ + public function verifyFieldValue($fieldInfos, $key, $value) + { + global $langs; + $values = $this->isEmptyValue($fieldInfos, $value) ? array() : (is_string($value) ? explode(',', $value) : (is_array($value) ? $value: array($value))); + + $result = parent::verifyFieldValue($fieldInfos, $key, $values); + if ($result && !$this->isEmptyValue($fieldInfos, $values)) { + $options = $this->getOptions($fieldInfos, $key); + foreach ($values as $val) { + $newVal = trim((string) $val); + if (!isset($options[$newVal])) { + self::$validator->error = $langs->trans('RequireValidValue'); + return false; + } + } + + $result = true; + } + + return $result; + } + + /** + * Verify if the field value from GET/POST is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return bool + * @see self::printInputField() + */ + public function verifyPostFieldValue($fieldInfos, $key, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + $values = GETPOST($htmlName, 'array'); + + return $this->verifyFieldValue($fieldInfos, $key, $values); + } + + /** + * Get field value from GET/POST + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $defaultValue Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return mixed + * @see self::printInputField() + */ + public function getPostFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $values = GETPOST($htmlName, 'array'); + if (is_array($values)) $values = implode(',', $values); + } else { + $values = $defaultValue; + } + + return $values; + } + + /** + * Get search field value from GET/POST + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $defaultValue Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return mixed + * @see self::printInputSearchField() + */ + public function getPostSearchFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $values = GETPOST($htmlName, 'array'); + } else { + $values = $defaultValue; + } + + return $values; + } + + /** + * Get sql filter for search field + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @return string + * @see self::printInputSearchField(), self::getPostSearchFieldValue() + */ + public function sqlFilterSearchField($fieldInfos, $key, $value) + { + if (!$this->isEmptyValue($fieldInfos, $value) && is_array($value)) { + $alias = $fieldInfos->sqlAlias ?? 't.'; + $field = $this->db->sanitize($alias . ($fieldInfos->nameInTable ?? $key)); + + $tmp = "'" . implode("','", array_map(array($this->db, 'escape'), $value)) . "'"; + return " AND " . $field . " IN (" . $this->db->sanitize($tmp, 1) . ")"; + } + + return ''; + } + + /** + * Get list of options + * + * @param FieldInfos $fieldInfos Array of properties for field to show + * @param string $key Key of field + * @param bool $addEmptyValue Add also empty value if needed + * @param bool $reload Force reload options + * @return array + */ + public function getOptions($fieldInfos, $key, $addEmptyValue = false, $reload = false) + { + return parent::getOptions($fieldInfos, $key, $addEmptyValue, $reload); + } +} diff --git a/htdocs/core/class/fields/chkbxlstfield.class.php b/htdocs/core/class/fields/chkbxlstfield.class.php new file mode 100644 index 00000000000..a88a9badc9c --- /dev/null +++ b/htdocs/core/class/fields/chkbxlstfield.class.php @@ -0,0 +1,288 @@ + + * + * 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/fields/chkbxlstfield.class.php + * \ingroup core + * \brief File of class to chkbxlst field(multiselect) + */ + +require_once DOL_DOCUMENT_ROOT . '/core/class/fields/commonsellistfield.class.php'; + + +/** + * Class to chkbxlst field (multiselect) + */ +class ChkbxlstField extends CommonSellistField +{ + /** + * @var array List of value deemed as empty (null always deemed as empty) + */ + public $emptyValues = array(array(), ''); + + + /** + * Return HTML string to put an input search field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printInputSearchField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + global $conf; + + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $moreAttrib = trim((string) $moreAttrib); + if (empty($moreAttrib)) $moreAttrib = ' ' . $moreAttrib; + $htmlName = $keyPrefix . $key . $keySuffix; + $values = $this->isEmptyValue($fieldInfos, $value) ? array() : (is_string($value) ? explode(',', $value) : (is_array($value) ? $value: array($value))); + + $optionsList = array(); + $options = $this->getOptions($fieldInfos, $key); + foreach ($options as $optionKey => $optionInfos) { + $options[$optionKey] = $optionInfos['label']; + } + + return self::$form->multiselectarray($htmlName, $optionsList, $values, 0, 0, $moreCss, 0, 0, $moreAttrib, '', '', (int) (!empty($conf->use_javascript_ajax) && !getDolGlobalString('MAIN_EXTRAFIELDS_DISABLE_SELECT2'))); + } + + /** + * Return HTML string to put an input field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printInputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + global $conf; + + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $moreAttrib = trim((string) $moreAttrib); + if (empty($moreAttrib)) $moreAttrib = ' ' . $moreAttrib; + $htmlName = $keyPrefix . $key . $keySuffix; + $values = $this->isEmptyValue($fieldInfos, $value) ? array() : (is_string($value) ? explode(',', $value) : (is_array($value) ? $value: array($value))); + + $options = $this->getOptions($fieldInfos, $key); + + return self::$form->multiselectarray($htmlName, $options, $values, 0, 0, $moreCss, 0, 0, $moreAttrib, '', '', (int) (!empty($conf->use_javascript_ajax) && !getDolGlobalString('MAIN_EXTRAFIELDS_DISABLE_SELECT2'))); + } + + /** + * Return HTML string to show a field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printOutputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $values = $this->isEmptyValue($fieldInfos, $value) ? array() : (is_string($value) ? explode(',', $value) : (is_array($value) ? $value: array($value))); + + $out = ''; + if (!$this->isEmptyValue($fieldInfos, $values)) { + $options = $this->getOptions($fieldInfos, $key, false, false, $values); + $optionParams = $this->getOptionsParams($fieldInfos->options); + $isCategory = $optionParams['tableName'] == 'categorie' && !empty($optionParams['categoryType']); + + $toPrint = array(); + foreach ($values as $val) { + $valueToPrint = ''; + $colorToPrint = 'bbb'; + if (isset($options[$val])) { + $valueToPrint = $options[$val]['label']; + + if ($isCategory) { + require_once DOL_DOCUMENT_ROOT . '/categories/class/categorie.class.php'; + $c = new Categorie($this->db); + $c->fetch($val); + $colorToPrint = $c->color ? $c->color : 'bbb'; + $valueToPrint = img_object('', 'category') . ' ' . $valueToPrint; + } + } else { + $valueToPrint = $val; + } + $toPrint[] = '
  • ' . $valueToPrint . '
  • '; + } + if (!empty($toPrint)) { + $out = '
      ' . implode('', $toPrint) . '
    '; + } + } + + return $out; + } + + /** + * Get input CSS + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $moreCss Value for css to define style/length of field. + * @param string $defaultCss Default value for css to define style/length of field. + * @return string + * @see self::printInputSearchField(), self::printInputField() + */ + public function getInputCss($fieldInfos, $moreCss = '', $defaultCss = '') + { + return parent::getInputCss($fieldInfos, $moreCss, $defaultCss ? $defaultCss : 'minwidth400'); + } + + /** + * Verify if the field value is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Value to check (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @return bool + * @see self::printInputField() + */ + public function verifyFieldValue($fieldInfos, $key, $value) + { + $values = $this->isEmptyValue($fieldInfos, $value) ? array() : (is_string($value) ? explode(',', $value) : (is_array($value) ? $value: array($value))); + + $result = parent::verifyFieldValue($fieldInfos, $key, $values); + if ($result && !$this->isEmptyValue($fieldInfos, $values)) { + $optionParams = $this->getOptionsParams($fieldInfos->options); + if (!self::$validator->isInDb($values, $optionParams['tableName'], $optionParams['keyField'])) { + return false; + } + + $result = true; + } + + return $result; + } + + /** + * Verify if the field value from GET/POST is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return bool + * @see self::printInputField() + */ + public function verifyPostFieldValue($fieldInfos, $key, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + $values = GETPOST($htmlName, 'array'); + + return $this->verifyFieldValue($fieldInfos, $key, $values); + } + + /** + * Get field value from GET/POST + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $defaultValue Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return mixed + * @see self::printInputField() + */ + public function getPostFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $values = GETPOST($htmlName, 'array'); + if (is_array($values)) $values = implode(',', $values); + } else { + $values = $defaultValue; + } + + return $values; + } + + /** + * Get search field value from GET/POST + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $defaultValue Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return mixed + * @see self::printInputSearchField() + */ + public function getPostSearchFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $value = GETPOST($htmlName, 'array'); + } else { + $value = $defaultValue; + } + + return $value; + } + + /** + * Get sql filter for search field + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @return string + * @see self::printInputSearchField(), self::getPostSearchFieldValue() + */ + public function sqlFilterSearchField($fieldInfos, $key, $value) + { + if (!empty($value) && is_array($value)) { + $alias = $fieldInfos->sqlAlias ?? 't.'; + $field = $this->db->sanitize($alias . ($fieldInfos->nameInTable ?? $key)); + + $tmp = "'" . implode("','", array_map(array($this->db, 'escape'), $value)) . "'"; + return " AND " . $field . " IN (" . $this->db->sanitize($tmp, 1) . ")"; + } + + return ''; + } + + /** + * Get list of options + * + * @param FieldInfos $fieldInfos Array of properties for field to show + * @param string $key Key of field + * @param bool $addEmptyValue Add also empty value if needed + * @param bool $reload Force reload options + * @param string|array $selectedValues Only selected values + * @return array + */ + public function getOptions($fieldInfos, $key, $addEmptyValue = false, $reload = false, $selectedValues = array()) + { + return parent::getOptions($fieldInfos, $key, $addEmptyValue, $reload, $selectedValues); + } +} diff --git a/htdocs/core/class/fields/commonfield.class.php b/htdocs/core/class/fields/commonfield.class.php new file mode 100644 index 00000000000..870c29d54e2 --- /dev/null +++ b/htdocs/core/class/fields/commonfield.class.php @@ -0,0 +1,371 @@ + + * + * 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/fields/commonfield.class.php + * \ingroup core + * \brief File of class to common field + */ + +require_once DOL_DOCUMENT_ROOT . '/core/class/html.form.class.php'; +require_once DOL_DOCUMENT_ROOT . '/core/class/validate.class.php'; + + +/** + * Class to common field + */ +abstract class CommonField +{ + /** + * @var DoliDB Database handler. + */ + public $db; + + /** + * @var string Error code (or message) + */ + public $error = ''; + + /** + * @var string[] Array of Error code (or message) + */ + public $errors = array(); + + /** + * @var string Type + */ + public $type; + + /** + * @var string Label + */ + public $label; + + /** + * @var Form|null Form handler. + */ + public static $form; + + /** + * @var Validate|null Validate handler. + */ + public static $validator; + + /** + * @var array List of value deemed as empty (null always deemed as empty) + */ + public $emptyValues = array('', 0, array()); + + + /** + * Constructor + * + * @param DoliDB $db Database handler + */ + public function __construct($db) + { + global $form, $langs; + + $this->db = $db; + $this->error = ''; + $this->errors = array(); + + // Type and label + $this->type = strtolower(substr(get_class($this), 0, -5)); + $this->label = 'FieldLabel' . ucfirst($this->type); + + if (!isset(self::$form)) { + if (!is_object($form)) { + $form = new Form($this->db); + } + self::setForm($form); + } + + if (!isset(self::$validator)) { + // Use Validate class to allow external Modules to use data validation part instead of concentrate all test here (factoring) or just for reuse + $validator = new Validate($this->db, $langs); + self::setValidator($validator); + } + } + + /** + * Set form used for print the field + * + * @param Form $form Form handler + * @return void + */ + public static function setForm(&$form) + { + self::$form = &$form; + } + + /** + * Set validator used for check the field value + * + * @param Validate $validator Validate handler + * @return void + */ + public static function setValidator(&$validator) + { + self::$validator = &$validator; + } + + /** + * clear errors + * + * @return void + */ + public function clearErrors() + { + $this->error = ''; + $this->errors = array(); + } + + /** + * Method to output saved errors + * + * @param string $separator Separator between each error + * @return string String with errors + */ + public function errorsToString($separator = ', ') + { + return $this->error . (is_array($this->errors) ? (!empty($this->error) ? $separator : '') . implode($separator, $this->errors) : ''); + } + + /** + * Check if the value is deemed as empty + * + * @param FieldInfos $fieldInfos Properties of the field + * @param mixed $value Value to check (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param array $emptyValues List of value deemed as empty + * @return bool + */ + public function isEmptyValue($fieldInfos, $value, $emptyValues = null) + { + if (!isset($value)) { + return true; + } + + if (!is_array($emptyValues)) { + $emptyValues = !empty($fieldInfos->emptyValues) && is_array($fieldInfos->emptyValues) ? $fieldInfos->emptyValues : $this->emptyValues; + } + + foreach ($emptyValues as $val) { + if ($val === $value) { + return true; + } + } + + return false; + } + + /** + * Return HTML string to put an input search field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printInputSearchField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + return ''; + } + + /** + * Return HTML string to put an input field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printInputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + return ''; + } + + /** + * Return HTML string to show a field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printOutputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + return ''; + } + + /** + * Get input CSS + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $moreCss Value for css to define style/length of field. + * @param string $defaultCss Default value for css to define style/length of field. + * @return string + * @see self::printInputSearchField(), self::printInputField() + */ + public function getInputCss($fieldInfos, $moreCss = '', $defaultCss = 'minwidth400') + { + if (empty($moreCss)) { + if (!empty($fieldInfos->css)) { + $moreCss = $fieldInfos->css; + } elseif (!empty($defaultCss)) { + $moreCss = $defaultCss; + } + } + $moreCss = trim((string) $moreCss); + + return empty($moreCss) ? '' : ' ' . $moreCss; + } + + /** + * Verify if the field value is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Value to check (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @return bool + * @see self::printInputField() + */ + public function verifyFieldValue($fieldInfos, $key, $value) + { + global $langs; + + $required = $fieldInfos->required; + $minLength = $fieldInfos->minLength ?? 0; + $maxLength = $fieldInfos->maxLength ?? 0; + //$emptyValues = !empty($fieldInfos->emptyValues) ? $fieldInfos->emptyValues : $this->emptyValues; + + // Clear error + self::$validator->error = ''; + + // Todo move this in validate class ? + // Required test and empty value + if ($this->isEmptyValue($fieldInfos, $value)) { + if ($required) { + self::$validator->error = $langs->trans('RequireANotEmptyValue'); + return false; + } else { + // if no value sent and the field is not mandatory, no need to perform tests + return true; + } + } + + // MIN Size test + if (!empty($minLength) && is_string($value) && !self::$validator->isMinLength($value, $minLength)) { + return false; + } + + // MAX Size test + if (!empty($maxLength) && is_string($value) && !self::$validator->isMaxLength($value, $maxLength)) { + return false; + } + + // Todo move this in validate class ? + // MIN Value test + if (isset($fieldInfos->minValue) && is_numeric($value) && ((double) $value) < $fieldInfos->minValue) { + self::$validator->error = $langs->trans('RequireMinValue', $fieldInfos->minValue); + return false; + } + + // MAX Value test + if (isset($fieldInfos->maxValue) && is_numeric($value) && ((double) $value) > $fieldInfos->maxValue) { + self::$validator->error = $langs->trans('RequireMaxValue', $fieldInfos->maxValue); + return false; + } + + return true; + } + + /** + * Verify if the field value from GET/POST is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return bool + * @see self::printInputField() + */ + public function verifyPostFieldValue($fieldInfos, $key, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + return $this->verifyFieldValue($fieldInfos, $key, GETPOST($htmlName, 'restricthtml')); + } + + /** + * Get field value from GET/POST + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $defaultValue Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return mixed + * @see self::printInputField() + */ + public function getPostFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + return $defaultValue; + } + + /** + * Get search field value from GET/POST + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $defaultValue Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return mixed + * @see self::printInputSearchField() + */ + public function getPostSearchFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + return $defaultValue; + } + + /** + * Get sql filter for search field + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @return string + * @see self::printInputSearchField(), self::getPostSearchFieldValue() + */ + public function sqlFilterSearchField($fieldInfos, $key, $value) + { + return ''; + } +} diff --git a/htdocs/core/class/fields/commongeofield.class.php b/htdocs/core/class/fields/commongeofield.class.php new file mode 100644 index 00000000000..0e739bd297e --- /dev/null +++ b/htdocs/core/class/fields/commongeofield.class.php @@ -0,0 +1,195 @@ + + * + * 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/fields/commongeofield.class.php + * \ingroup core + * \brief File of class to common geo field + */ + +require_once DOL_DOCUMENT_ROOT . '/core/class/fields/commonfield.class.php'; + + +/** + * Class to common geo field (for linestrg, multipts, point, polygon, ...) + */ +class CommonGeoField extends CommonField +{ + /** + * @var array List of value deemed as empty (null always deemed as empty) + */ + public $emptyValues = array(''); + + + /** + * Return HTML string to put an input search field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printInputSearchField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + return ''; + } + + /** + * Return HTML string to put an input field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printInputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + return self::$form->inputGeoPoint($htmlName, (string) $value, $fieldInfos->type); + } + + /** + * Return HTML string to show a field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printOutputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + return !$this->isEmptyValue($fieldInfos, $value) ? self::$form->outputGeoPoint((string) $value, $fieldInfos->type) : ''; + } + + /** + * Get input CSS + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $moreCss Value for css to define style/length of field. + * @param string $defaultCss Default value for css to define style/length of field. + * @return string + * @see self::printInputSearchField(), self::printInputField() + */ + public function getInputCss($fieldInfos, $moreCss = '', $defaultCss = '') + { + return parent::getInputCss($fieldInfos, $moreCss, $defaultCss); + } + + /** + * Verify if the field value is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Value to check (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @return bool + * @see self::printInputField() + */ + public function verifyFieldValue($fieldInfos, $key, $value) + { + // Todo make the validator test for geo point + return parent::verifyFieldValue($fieldInfos, $key, $value); + } + + /** + * Verify if the field value from GET/POST is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return bool + * @see self::printInputField() + */ + public function verifyPostFieldValue($fieldInfos, $key, $keyPrefix = '', $keySuffix = '') + { + return parent::verifyPostFieldValue($fieldInfos, $key, $keyPrefix, $keySuffix); + } + + /** + * Get field value from GET/POST + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $defaultValue Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return mixed + * @see self::printInputField() + */ + public function getPostFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $value = GETPOST($htmlName, 'restricthtml'); + if ($value != '{}') { + require_once DOL_DOCUMENT_ROOT . '/core/class/dolgeophp.class.php'; + $dolgeophp = new DolGeoPHP($this->db); + $value = $dolgeophp->getWkt($value); + } else { + $value = ''; + } + } else { + $value = $defaultValue; + } + + return $value; + } + + /** + * Get search field value from GET/POST + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $defaultValue Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return mixed + * @see self::printInputSearchField() + */ + public function getPostSearchFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + return ''; + } + + /** + * Get sql filter for search field + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @return string + * @see self::printInputSearchField(), self::getPostSearchFieldValue() + */ + public function sqlFilterSearchField($fieldInfos, $key, $value) + { + return ''; + } +} diff --git a/htdocs/core/class/fields/commonselectfield.class.php b/htdocs/core/class/fields/commonselectfield.class.php new file mode 100644 index 00000000000..e640d33008b --- /dev/null +++ b/htdocs/core/class/fields/commonselectfield.class.php @@ -0,0 +1,96 @@ + + * + * 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/fields/commonselectfield.class.php + * \ingroup core + * \brief File of class to common select field + */ + +require_once DOL_DOCUMENT_ROOT . '/core/class/fields/commonfield.class.php'; + + +/** + * Class to common select field + */ +class CommonSelectField extends CommonField +{ + /** + * @var array> Options cached + */ + public static $options = array(); + + /** + * Get list of options + * + * @param FieldInfos $fieldInfos Array of properties for field to show + * @param string $key Key of field + * @param bool $addEmptyValue Add also empty value if needed + * @param bool $reload Force reload options + * @return array + */ + public function getOptions($fieldInfos, $key, $addEmptyValue = false, $reload = false) + { + global $langs; + + if (!isset(self::$options[$key]) || $reload) { + $options = array(); + if (!empty($fieldInfos->options) && is_array($fieldInfos->options)) { + foreach ($fieldInfos->options as $optionKey => $optionLabel) { + $optionKey = (string) $optionKey; + $optionLabel = (string) $optionLabel; + if ($optionKey == '') { + continue; + } + + // Manage dependency list + $fieldValueParent = ''; + if (strpos($optionLabel, "|") !== false) { + list($optionLabel, $valueParent) = explode('|', $optionLabel); + $fieldValueParent = trim($fieldValueParent); + } + + if (empty($optionLabel)) { + $optionLabel = '(not defined)'; + } else { + $optionLabel = $langs->trans($optionLabel); + } + + $options[$optionKey] = array( + 'id' => $optionKey, + 'label' => $optionLabel, + 'parent' => $fieldValueParent, + ); + } + } + if ($addEmptyValue && (!$fieldInfos->required || count($options) > 1)) { + // For preserve the numeric key indexes + $options = array( + '' => array( + 'id' => '', + 'label' => ' ', + 'parent' => '', + ) + ) + $options; + } + + self::$options[$key] = $options; + } + + return self::$options[$key]; + } +} diff --git a/htdocs/core/class/fields/commonsellistfield.class.php b/htdocs/core/class/fields/commonsellistfield.class.php new file mode 100644 index 00000000000..b67805db073 --- /dev/null +++ b/htdocs/core/class/fields/commonsellistfield.class.php @@ -0,0 +1,373 @@ + + * + * 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/fields/commonsellistfield.class.php + * \ingroup core + * \brief File of class to common sellist field + */ + +require_once DOL_DOCUMENT_ROOT . '/core/class/fields/commonfield.class.php'; + + +/** + * Class to common sellist field + */ +class CommonSellistField extends CommonField +{ + /** + * @var string Url of the AJAX page for get options of the sellist + */ + public static $ajaxUrl = DOL_URL_ROOT . '/core/ajax/ajaxfield.php'; + + /** + * @var array> Options cached + */ + public static $options = array(); + + /** + * @var array Code mapping from ID. For backward compatibility + */ + const MAP_ID_TO_CODE = array( + 0 => 'product', + 1 => 'supplier', + 2 => 'customer', + 3 => 'member', + 4 => 'contact', + 5 => 'bank_account', + 6 => 'project', + 7 => 'user', + 8 => 'bank_line', + 9 => 'warehouse', + 10 => 'actioncomm', + 11 => 'website_page', + 12 => 'ticket', + 13 => 'knowledgemanagement', + 14 => 'fichinter', + 16 => 'order', + 17 => 'invoice', + 20 => 'supplier_order', + 21 => 'supplier_invoice' + ); + + /** + * Get all parameters in the options + * + * @param array $options Options of the field + * @return array{all:string,tableName:string,labelFullFields:string[],labelFields:string[],labelAlias:string[],keyField:string,parentName:string,parentFullField:string,parentField:string,parentAlias:string,filter:string,categoryType:string,categoryRoots:string,sortField:string} + */ + public function getOptionsParams($options) + { + $options = is_array($options) ? $options : array(); + $paramList = array_keys($options); + $paramList = preg_split('/[\r\n]+/', $paramList[0]); + // 0 : tableName + // 1 : label field name + // 2 : key fields name (if different of rowid) + // optional parameters... + // 3 : key field parent (for dependent lists). (= 'parentName|parentField'; parentName: Name of the input field (ex: ref or options_code); parentField: Name of the field in the table for getting the value) + // Only the value who is equal to the selected value of the parentName input with the value of the parentField is displayed in this select options + // 4 : where clause filter on column or table extrafield, syntax field='value' or extra.field=value. Or use USF on the second line. + // 5 : string category type. This replace the filter. + // 6 : ids categories list separated by comma for category root. This replace the filter. + // 7 : sort field (Don't manage ASC or DESC) + + $all = (string) $paramList[0]; + $InfoFieldList = explode(":", $all, 5); + + // If there is a filter, we extract it by taking all content inside parenthesis. + if (!empty($InfoFieldList[4])) { + $pos = 0; // $pos will be position of ending filter + $parenthesisopen = 0; + while (substr($InfoFieldList[4], $pos, 1) !== '' && ($parenthesisopen || $pos == 0 || substr($InfoFieldList[4], $pos, 1) != ':')) { + if (substr($InfoFieldList[4], $pos, 1) == '(') { + $parenthesisopen++; + } + if (substr($InfoFieldList[4], $pos, 1) == ')') { + $parenthesisopen--; + } + $pos++; + } + $tmpbefore = substr($InfoFieldList[4], 0, $pos); + $tmpafter = substr($InfoFieldList[4], $pos + 1); + $InfoFieldList[4] = $tmpbefore; + if ($tmpafter !== '') { + $InfoFieldList = array_merge($InfoFieldList, explode(':', $tmpafter)); + } + + // Fix better compatibility with some old extrafield syntax filter "(field=123)" + $reg = array(); + if (preg_match('/^\(?([a-z0-9]+)([=<>]+)(\d+)\)?$/i', $InfoFieldList[4], $reg)) { + $InfoFieldList[4] = '(' . $reg[1] . ':' . $reg[2] . ':' . $reg[3] . ')'; + } + } + + $tableName = (string) ($InfoFieldList[0] ?? ''); + $labelFullFields = (string) ($InfoFieldList[1] ?? ''); + // @phpstan-ignore-next-line + $labelFullFields = array_filter(array_map('trim', explode('|', $labelFullFields)), 'strlen'); + $labelFields = array(); + $labelAlias = array(); + foreach ($labelFullFields as $labelFullField) { + $tmp = $this->getSqlFieldInfo($labelFullField); + $labelFields[] = $tmp['field']; + $labelAlias[] = $tmp['alias']; + } + $keyField = (string) ($InfoFieldList[2] ?? ''); + if (empty($keyField)) $keyField = 'rowid'; + $keyFieldParent = (string) ($InfoFieldList[3] ?? ''); + $tmp = array_map('trim', explode('|', $keyFieldParent)); + $parentName = (string) ($tmp[0] ?? ''); + $parentFullField = (string) ($tmp[1] ?? ''); + $tmp = $this->getSqlFieldInfo($parentFullField); + $parentField = $tmp['field']; + $parentAlias = $tmp['alias']; + $filter = (string) ($InfoFieldList[4] ?? ''); + $categoryType = (string) ($InfoFieldList[5] ?? ''); + if (is_numeric($categoryType)) { // deprecated: must use the category code instead of id. For backward compatibility. + require_once DOL_DOCUMENT_ROOT . '/categories/class/categorie.class.php'; + $categoryType = self::MAP_ID_TO_CODE[(int) $categoryType] ?? ''; + } + $categoryRoots = (string) ($InfoFieldList[6] ?? ''); + $sortField = (string) ($InfoFieldList[7] ?? ''); + + return array( + 'all' => $all, + 'tableName' => $tableName, + 'labelFullFields' => $labelFullFields, + 'labelFields' => $labelFields, + 'labelAlias' => $labelAlias, + 'keyField' => $keyField, + 'parentName' => $parentName, + 'parentFullField' => $parentFullField, + 'parentField' => $parentField, + 'parentAlias' => $parentAlias, + 'filter' => $filter, + 'categoryType' => $categoryType, + 'categoryRoots' => $categoryRoots, + 'sortField' => $sortField, + ); + } + + /** + * Get sql info of the full field + * + * @param string $fullField Full field (ex: p.test AS label or f(a,b,c) AS label) + * @return array{field:string,alias:string} + */ + public function getSqlFieldInfo($fullField) + { + if (preg_match('/(.*)\s+AS\s+(\w+)$/i', $fullField, $matches)) { + $field = $matches[1]; + $alias = $matches[2]; + } else { + $field = $fullField; + $alias = $fullField; + } + + if (preg_match('/^\w+\.(.*)/i', $field, $matches)) { + $alias = $matches[1]; + } + + return array( + 'field' => $field, + 'alias' => $alias, + ); + } + + /** + * Get list of options + * + * @param FieldInfos $fieldInfos Array of properties for field to show + * @param string $key Key of field + * @param bool $addEmptyValue Add also empty value if needed + * @param bool $reload Force reload options + * @param string|array $selectedValues Only selected values + * @return array|null Return null if error + */ + public function getOptions($fieldInfos, $key, $addEmptyValue = false, $reload = false, $selectedValues = array()) + { + global $conf, $langs; + + $selectedValues = array_map('trim', is_array($selectedValues) ? $selectedValues : array($selectedValues)); + + if (!isset(self::$options[$key]) || $reload) { + $options = array(); + if (!empty($fieldInfos->options) && is_array($fieldInfos->options)) { + $optionsParams = $this->getOptionsParams($fieldInfos->options); + + if ($optionsParams['tableName'] == 'categorie' && !empty($optionsParams['categoryType'])) { + $data = self::$form->select_all_categories($optionsParams['categoryType'], '', 'parent', 64, $optionsParams['categoryRoots'], 1, 1); + if (is_array($data)) { + foreach ($data as $data_key => $data_value) { + $options[$data_key] = array( + 'label' => $data_value, + 'parent' => '', + ); + } + } + } else { + $filter = $optionsParams['filter']; + $hasExtra = !empty($filter) && strpos($filter, 'extra.') !== false; + $keyField = ($hasExtra ? 'main.' : '') . $optionsParams['keyField']; + + $keyList = $keyField . ' AS rowid'; + if (!empty($optionsParams['parentFullField'])) { + $keyList .= ', ' . $optionsParams['parentFullField']; + } + if (!empty($optionsParams['labelFullFields'])) { + $keyList .= ', ' . implode(', ', $optionsParams['labelFullFields']); + } + + $sql = "SELECT " . $keyList; + $sql .= " FROM " . $this->db->sanitize($this->db->prefix() . $optionsParams['tableName']); + if ($hasExtra) { + $sql .= " AS main"; + $sql .= " LEFT JOIN " . $this->db->sanitize($this->db->prefix() . $optionsParams['tableName']) . "_extrafields AS extra ON extra.fk_object = " . $keyField; + } + + // Add filter from 4th field + if (!empty($filter)) { + // can use current entity filter + if (strpos($filter, '$ENTITY$') !== false) { + $filter = str_replace('$ENTITY$', (string) $conf->entity, $filter); + } + // can use SELECT request + if (strpos($filter, '$SEL$') !== false && !getDolGlobalString("MAIN_DISALLOW_UNSECURED_SELECT_INTO_EXTRAFIELDS_FILTER")) { + $filter = str_replace('$SEL$', 'SELECT', $filter); + } + // can use MODE parameter (list or view) + if (strpos($filter, '$MODE$') !== false) { + $filter = str_replace('$MODE$', empty($fieldInfos->mode) ? 'view' : $fieldInfos->mode, $filter); + } + + // Current object id can be used into filter + $objectid = isset($fieldInfos->otherParams['objectId']) ? (int) $fieldInfos->otherParams['objectId'] : (isset($fieldInfos->object) && is_object($fieldInfos->object) ? (int) $fieldInfos->object->id : 0); + if (strpos($filter, '$ID$') !== false && !empty($objectid)) { + $filter = str_replace('$ID$', (string) $objectid, $filter); + } elseif (substr($_SERVER["PHP_SELF"], -8) == 'list.php') { + // In filters of list views, we do not want $ID$ replaced by 0. So we remove the '=' condition. + // Do nothing if condition is using 'IN' keyword + // Replace 'column = $ID$' by "word" + $filter = preg_replace('#\b([a-zA-Z0-9-\.-_]+)\b *= *\$ID\$#', '$1', $filter); + // Replace '$ID$ = column' by "word" + $filter = preg_replace('#\$ID\$ *= *\b([a-zA-Z0-9-\.-_]+)\b#', '$1', $filter); + } else { + $filter = str_replace('$ID$', '0', $filter); + } + + // can use filter on any field of object + if (isset($fieldInfos->object) && is_object($fieldInfos->object)) { + $object = $fieldInfos->object; + $tags = []; + preg_match_all('/\$(.*?)\$/', $filter, $tags); // Example: $filter is ($dateadh$:<=:CURRENT_DATE) + foreach ($tags[0] as $keytag => $valuetag) { + $property = preg_replace('/[^a-z0-9_]/', '', strtolower($tags[1][$keytag])); + if (strpos($filter, $valuetag) !== false && property_exists($object, $property) && !empty($object->$property)) { + $filter = str_replace($valuetag, (string) $object->$property, $filter); + } else { + $filter = str_replace($valuetag, '0', $filter); + } + } + } + + $errstr = ''; + $sql .= " WHERE " . forgeSQLFromUniversalSearchCriteria($filter, $errstr, 1); + } else { + $sql .= ' WHERE 1=1'; + } + // Some tables may have field, some other not. For the moment we disable it. + if (in_array($optionsParams['tableName'], array('tablewithentity'))) { + $sql .= " AND entity = " . ((int) $conf->entity); + } + // Manage dependency list (from AJAX) + if (isset($fieldInfos->optionsSqlDependencyValue)) { + // TODO rework for dependency with a date or a multiselect + $sql .= " AND " . $optionsParams['parentField'] . " = '" . $this->db->escape($fieldInfos->optionsSqlDependencyValue) . "'"; + } + // Only selected values + if (!empty($selectedValues)) { + $tmp = "'" . implode("','", array_map(array($this->db, 'escape'), $selectedValues)) . "'"; + $sql .= " AND " . $keyField . " IN (" . $this->db->sanitize($tmp, 1) . ")"; + } + + // Note: $InfoFieldList can be 'sellist:TableName:LabelFieldName[:KeyFieldName[:KeyFieldParent[:Filter[:CategoryIdType[:CategoryIdList[:Sortfield]]]]]]' + if (preg_match('/^[a-z0-9_\-,]+$/i', $optionsParams['sortField'])) { + $sql .= $this->db->order($optionsParams['sortField']); + } else { + $sql .= $this->db->order(implode(', ', $optionsParams['labelFields'])); + } + + $limit = getDolGlobalInt('MAIN_EXTRAFIELDS_LIMIT_SELLIST_SQL', $fieldInfos->optionsSqlLimit ?? 1000); + $offset = $fieldInfos->optionsSqlOffset ?? (isset($fieldInfos->optionsSqlPage) ? (((int) $fieldInfos->optionsSqlPage) - 1) * $limit : 0); + $sql .= $this->db->plimit($limit, $offset); + + dol_syslog(get_class($this) . '::getOptions', LOG_DEBUG); + $resql = $this->db->query($sql); + if ($resql) { + while ($obj = $this->db->fetch_object($resql)) { + $optionKey = (string) $obj->rowid; + + $toPrint = array(); + foreach ($optionsParams['labelAlias'] as $fieldToShow) { + $toPrint[] = is_string($obj->$fieldToShow) ? $langs->trans($obj->$fieldToShow) : $obj->$fieldToShow; + } + $optionLabel = implode(' ', $toPrint); + + if (empty($optionLabel)) { + $optionLabel = '(not defined)'; + } + + // Manage dependency list + $fieldValueParent = !empty($optionsParams['parentName']) && !empty($optionsParams['parentAlias']) ? $optionsParams['parentName'] . ':' . ((string) $obj->{$optionsParams['parentAlias']}) : ''; + + $options[$optionKey] = array( + 'id' => $optionKey, + 'label' => $optionLabel, + 'parent' => $fieldValueParent, + ); + } + $this->db->free($resql); + } else { + $this->error = 'Error in request ' . $sql . ' ' . $this->db->lasterror() . '. Check setup of extra parameters.
    '; + return null; + } + } + } + if ($addEmptyValue && (!$fieldInfos->required || count($options) > 1)) { + // For preserve the numeric key indexes + $options = array( + '' => array( + 'id' => '', + 'label' => ' ', + 'parent' => '', + ) + ) + $options; + } + + self::$options[$key] = $options; + } + + $options = self::$options[$key]; + // Only selected values + if (!empty($selectedValues)) { + $options = array_intersect_key($options, array_flip($selectedValues)); + } + + return $options; + } +} diff --git a/htdocs/core/class/fields/datefield.class.php b/htdocs/core/class/fields/datefield.class.php new file mode 100644 index 00000000000..699431a93c3 --- /dev/null +++ b/htdocs/core/class/fields/datefield.class.php @@ -0,0 +1,268 @@ + + * + * 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/fields/datefield.class.php + * \ingroup core + * \brief File of class to date field + */ + +require_once DOL_DOCUMENT_ROOT . '/core/class/fields/commonfield.class.php'; + + +/** + * Class to date field + */ +class DateField extends CommonField +{ + /** + * @var array List of value deemed as empty (null always deemed as empty) + */ + public $emptyValues = array(''); + + /** + * Return HTML string to put an input search field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printInputSearchField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + global $langs; + + // TODO Must also support $moreCss ? + + $htmlName = $keyPrefix . $key . $keySuffix; + $prefill = array( + 'start' => $value['start'] ?? '', + 'end' => $value['end'] ?? '', + ); + + // Search filter on a date field shows two inputs to select a date range + $out = '
    '; + $out .= self::$form->selectDate($prefill['start'], $htmlName . '_start', 0, 0, 1, '', 1, 0, 0, '', '', '', '', 1, '', $langs->trans("From")); + $out .= '
    '; + $out .= self::$form->selectDate($prefill['end'], $htmlName . '_end', 0, 0, 1, '', 1, 0, 0, '', '', '', '', 1, '', $langs->trans("to")); + $out .= '
    '; + + return $out; + } + + /** + * Return HTML string to put an input field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printInputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $required = $fieldInfos->required ? 1 : 0; + $htmlName = $keyPrefix . $key . $keySuffix; + + // Do not show current date when field not required (see selectDate() method) + if (!$required && $this->isEmptyValue($fieldInfos, $value)) { + $value = '-1'; + } + + // Do not whow "add now link" (eg for birthday date) + $addnowlink = 1; + if (in_array($fieldInfos->key, array('birth'))) { + $addnowlink = 0; + } + + // TODO Must also support $moreAttrib and $moreCss ? + return self::$form->selectDate($value, $htmlName, 0, 0, $required, '', 1, $addnowlink, 0, 1); + } + + /** + * Return HTML string to show a field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printOutputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + // We suppose dates without time are always gmt (storage of course + output) + return !$this->isEmptyValue($fieldInfos, $value) ? dol_print_date($value, 'day') : ''; + } + + /** + * Get input CSS + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $moreCss Value for css to define style/length of field. + * @param string $defaultCss Default value for css to define style/length of field. + * @return string + * @see self::printInputSearchField(), self::printInputField() + */ + public function getInputCss($fieldInfos, $moreCss = '', $defaultCss = '') + { + return parent::getInputCss($fieldInfos, $moreCss, $defaultCss ? $defaultCss : 'minwidth100imp'); + } + + /** + * Verify if the field value is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Value to check (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @return bool + * @see self::printInputField() + */ + public function verifyFieldValue($fieldInfos, $key, $value) + { + $result = parent::verifyFieldValue($fieldInfos, $key, $value); + if ($result && !$this->isEmptyValue($fieldInfos, $value)) { + // FIXME this check is not valid + /*if (!self::$validator->isTimestamp($value)) { + return false; + }*/ + + $result = true; + } + + return $result; + } + + /** + * Verify if the field value from GET/POST is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return bool + * @see self::printInputField() + */ + public function verifyPostFieldValue($fieldInfos, $key, $keyPrefix = '', $keySuffix = '') + { + return parent::verifyPostFieldValue($fieldInfos, $key, $keyPrefix, $keySuffix); + } + + /** + * Get field value from GET/POST + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $defaultValue Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return mixed + * @see self::printInputField() + */ + public function getPostFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName . 'month') || GETPOSTISSET($htmlName . 'day') || GETPOSTISSET($htmlName . 'year')) { + $value = dol_mktime(12, 0, 0, GETPOSTINT($htmlName . 'month'), GETPOSTINT($htmlName . 'day'), GETPOSTINT($htmlName . 'year')); // for date without hour, we use gmt + } else { + $value = $defaultValue; + } + + return $value; + } + + /** + * Get search field value from GET/POST + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $defaultValue Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return mixed + * @see self::printInputSearchField() + */ + public function getPostSearchFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName . '_startmonth') || GETPOSTISSET($htmlName . '_startday') || GETPOSTISSET($htmlName . '_startyear')) { + $start = dol_mktime(0, 0, 0, GETPOSTINT($htmlName . '_startmonth'), GETPOSTINT($htmlName . '_startday'), GETPOSTINT($htmlName . '_startyear')); + } else { + $start = is_array($defaultValue) && isset($defaultValue['start']) ? $defaultValue['start'] : ''; + } + + if (GETPOSTISSET($htmlName . '_endmonth') || GETPOSTISSET($htmlName . '_endday') || GETPOSTISSET($htmlName . '_endyear')) { + $end = dol_mktime(23, 59, 59, GETPOSTINT($htmlName . '_endmonth'), GETPOSTINT($htmlName . '_endday'), GETPOSTINT($htmlName . '_endyear')); + } else { + $end = is_array($defaultValue) && isset($defaultValue['end']) ? $defaultValue['end'] : ''; + } + + return array( + 'start' => $start, + 'end' => $end, + ); + } + + /** + * Get sql filter for search field + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @return string + * @see self::printInputSearchField(), self::getPostSearchFieldValue() + */ + public function sqlFilterSearchField($fieldInfos, $key, $value) + { + if (!$this->isEmptyValue($fieldInfos, $value)) { + $alias = $fieldInfos->sqlAlias ?? 't.'; + $field = $this->db->sanitize($alias . ($fieldInfos->nameInTable ?? $key)); + $sql = ''; + + if (is_array($value)) { + $hasStart = !is_null($value['start']) && $value['start'] !== ''; + $hasEnd = !is_null($value['start']) && $value['start'] !== ''; + if ($hasStart && $hasEnd) { + $sql = " AND (" . $field . " BETWEEN '" . $this->db->idate($value['start']) . "' AND '" . $this->db->idate($value['end']) . "')"; + } elseif ($hasStart) { + $sql = " AND " . $field . " >= '" . $this->db->idate($value['start']) . "'"; + } elseif ($hasEnd) { + $sql = " AND " . $field . " <= '" . $this->db->idate($value['end']) . "'"; + } + } elseif (is_numeric($value)) { + include_once DOL_DOCUMENT_ROOT . '/core/lib/date.lib.php'; + $value = dol_get_first_hour($value); + $sql = " AND " . $field . " = '" . $this->db->idate($value) . "'"; + } + + return $sql; + } + + return ''; + } +} diff --git a/htdocs/core/class/fields/datetimefield.class.php b/htdocs/core/class/fields/datetimefield.class.php new file mode 100644 index 00000000000..6e29a76fb7a --- /dev/null +++ b/htdocs/core/class/fields/datetimefield.class.php @@ -0,0 +1,258 @@ + + * + * 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/fields/datetimefield.class.php + * \ingroup core + * \brief File of class to datetime field + */ + +require_once DOL_DOCUMENT_ROOT . '/core/class/fields/commonfield.class.php'; + + +/** + * Class to datetime field + */ +class DatetimeField extends CommonField +{ + /** + * @var array List of value deemed as empty (null always deemed as empty) + */ + public $emptyValues = array(''); + + /** + * Return HTML string to put an input search field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printInputSearchField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + global $langs; + + // TODO Must also support $moreCss ? + + $htmlName = $keyPrefix . $key . $keySuffix; + $prefill = array( + 'start' => $value['start'] ?? '', + 'end' => $value['end'] ?? '', + ); + + // Search filter on a date field shows two inputs to select a date range + $out = '
    '; + $out .= self::$form->selectDate($prefill['start'], $htmlName . '_start', 1, 1, 1, '', 1, 0, 0, '', '', '', '', 1, '', $langs->trans("From"), 'tzuserrel'); + $out .= '
    '; + $out .= self::$form->selectDate($prefill['end'], $htmlName . '_end', 1, 1, 1, '', 1, 0, 0, '', '', '', '', 1, '', $langs->trans("to"), 'tzuserrel'); + $out .= '
    '; + + return $out; + } + + /** + * Return HTML string to put an input field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printInputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $required = $fieldInfos->required ? 1 : 0; + $htmlName = $keyPrefix . $key . $keySuffix; + + // Do not show current date when field not required (see selectDate() method) + if (!$required && $this->isEmptyValue($fieldInfos, $value)) { + $value = '-1'; + } + + // TODO Must also support $moreAttrib and $moreCss ? + return self::$form->selectDate($value, $htmlName, 1, 1, $required, '', 1, 1, 0, 1, '', '', '', 1, '', '', 'tzuserrel'); + } + + /** + * Return HTML string to show a field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printOutputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + return !$this->isEmptyValue($fieldInfos, $value) ? dol_print_date($value, 'dayhour', 'tzuserrel') : ''; + } + + /** + * Get input CSS + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $moreCss Value for css to define style/length of field. + * @param string $defaultCss Default value for css to define style/length of field. + * @return string + * @see self::printInputSearchField(), self::printInputField() + */ + public function getInputCss($fieldInfos, $moreCss = '', $defaultCss = '') + { + return parent::getInputCss($fieldInfos, $moreCss, $defaultCss ? $defaultCss : 'minwidth200imp'); + } + + /** + * Verify if the field value is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Value to check (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @return bool + * @see self::printInputField() + */ + public function verifyFieldValue($fieldInfos, $key, $value) + { + $result = parent::verifyFieldValue($fieldInfos, $key, $value); + if ($result && !$this->isEmptyValue($fieldInfos, $value)) { + if (!self::$validator->isTimestamp($value)) { + return false; + } + + $result = true; + } + + return $result; + } + + /** + * Verify if the field value from GET/POST is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return bool + * @see self::printInputField() + */ + public function verifyPostFieldValue($fieldInfos, $key, $keyPrefix = '', $keySuffix = '') + { + return parent::verifyPostFieldValue($fieldInfos, $key, $keyPrefix, $keySuffix); + } + + /** + * Get field value from GET/POST + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $defaultValue Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return mixed + * @see self::printInputField() + */ + public function getPostFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName . 'hour') || GETPOSTISSET($htmlName . 'min') || GETPOSTISSET($htmlName . 'sec') || GETPOSTISSET($htmlName . 'month') || GETPOSTISSET($htmlName . 'day') || GETPOSTISSET($htmlName . 'year')) { + $value = dol_mktime(GETPOSTINT($htmlName . 'hour'), GETPOSTINT($htmlName . 'min'), GETPOSTINT($htmlName . 'sec'), GETPOSTINT($htmlName . 'month'), GETPOSTINT($htmlName . 'day'), GETPOSTINT($htmlName . 'year'), 'tzuserrel'); // for date without hour, we use gmt + } else { + $value = $defaultValue; + } + + return $value; + } + + /** + * Get search field value from GET/POST + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $defaultValue Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return mixed + * @see self::printInputSearchField() + */ + public function getPostSearchFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName . '_startmonth') || GETPOSTISSET($htmlName . '_startday') || GETPOSTISSET($htmlName . '_startyear')) { + $start = dol_mktime(0, 0, 0, GETPOSTINT($htmlName . '_startmonth'), GETPOSTINT($htmlName . '_startday'), GETPOSTINT($htmlName . '_startyear'), 'tzuserrel'); + } else { + $start = is_array($defaultValue) && isset($defaultValue['start']) ? $defaultValue['start'] : ''; + } + + if (GETPOSTISSET($htmlName . '_endmonth') || GETPOSTISSET($htmlName . '_endday') || GETPOSTISSET($htmlName . '_endyear')) { + $end = dol_mktime(23, 59, 59, GETPOSTINT($htmlName . '_endmonth'), GETPOSTINT($htmlName . '_endday'), GETPOSTINT($htmlName . '_endyear'), 'tzuserrel'); + } else { + $end = is_array($defaultValue) && isset($defaultValue['end']) ? $defaultValue['end'] : ''; + } + + return array( + 'start' => $start, + 'end' => $end, + ); + } + + /** + * Get sql filter for search field + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @return string + * @see self::printInputSearchField(), self::getPostSearchFieldValue() + */ + public function sqlFilterSearchField($fieldInfos, $key, $value) + { + if (!$this->isEmptyValue($fieldInfos, $value)) { + $alias = $fieldInfos->sqlAlias ?? 't.'; + $field = $this->db->sanitize($alias . ($fieldInfos->nameInTable ?? $key)); + $sql = ''; + + if (is_array($value)) { + $hasStart = !is_null($value['start']) && $value['start'] !== ''; + $hasEnd = !is_null($value['start']) && $value['start'] !== ''; + if ($hasStart && $hasEnd) { + $sql = " AND (" . $field . " BETWEEN '" . $this->db->idate($value['start']) . "' AND '" . $this->db->idate($value['end']) . "')"; + } elseif ($hasStart) { + $sql = " AND " . $field . " >= '" . $this->db->idate($value['start']) . "'"; + } elseif ($hasEnd) { + $sql = " AND " . $field . " <= '" . $this->db->idate($value['end']) . "'"; + } + } elseif (is_numeric($value)) { + $sql = " AND " . $field . " = '" . $this->db->idate($value) . "'"; + } + + return $sql; + } + + return ''; + } +} diff --git a/htdocs/core/class/fields/datetimegmtfield.class.php b/htdocs/core/class/fields/datetimegmtfield.class.php new file mode 100644 index 00000000000..445b92d4349 --- /dev/null +++ b/htdocs/core/class/fields/datetimegmtfield.class.php @@ -0,0 +1,258 @@ + + * + * 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/fields/datetimegmtfield.class.php + * \ingroup core + * \brief File of class to datetimegmt field + */ + +require_once DOL_DOCUMENT_ROOT . '/core/class/fields/commonfield.class.php'; + + +/** + * Class to datetimegmt field + */ +class DatetimegmtField extends CommonField +{ + /** + * @var array List of value deemed as empty (null always deemed as empty) + */ + public $emptyValues = array(''); + + /** + * Return HTML string to put an input search field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printInputSearchField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + global $langs; + + // TODO Must also support $moreCss ? + + $htmlName = $keyPrefix . $key . $keySuffix; + $prefill = array( + 'start' => $value['start'] ?? '', + 'end' => $value['end'] ?? '', + ); + + // Search filter on a date field shows two inputs to select a date range + $out = '
    '; + $out .= self::$form->selectDate($prefill['start'], $htmlName . '_start', 1, 1, 1, '', 1, 0, 0, '', '', '', '', 1, '', $langs->trans("From"), 'gmt'); + $out .= '
    '; + $out .= self::$form->selectDate($prefill['end'], $htmlName . '_end', 1, 1, 1, '', 1, 0, 0, '', '', '', '', 1, '', $langs->trans("to"), 'gmt'); + $out .= '
    '; + + return $out; + } + + /** + * Return HTML string to put an input field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printInputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $required = $fieldInfos->required ? 1 : 0; + $htmlName = $keyPrefix . $key . $keySuffix; + + // Do not show current date when field not required (see selectDate() method) + if (!$required && $this->isEmptyValue($fieldInfos, $value)) { + $value = '-1'; + } + + // TODO Must also support $moreAttrib and $moreCss ? + return self::$form->selectDate($value, $htmlName, 1, 1, $required, '', 1, 1, 0, 1, '', '', '', 1, '', '', 'gmt'); + } + + /** + * Return HTML string to show a field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printOutputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + return !$this->isEmptyValue($fieldInfos, $value) ? dol_print_date($value, 'dayhour', 'gmt') : ''; + } + + /** + * Get input CSS + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $moreCss Value for css to define style/length of field. + * @param string $defaultCss Default value for css to define style/length of field. + * @return string + * @see self::printInputSearchField(), self::printInputField() + */ + public function getInputCss($fieldInfos, $moreCss = '', $defaultCss = '') + { + return parent::getInputCss($fieldInfos, $moreCss, $defaultCss ? $defaultCss : 'minwidth200imp'); + } + + /** + * Verify if the field value is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Value to check (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @return bool + * @see self::printInputField() + */ + public function verifyFieldValue($fieldInfos, $key, $value) + { + $result = parent::verifyFieldValue($fieldInfos, $key, $value); + if ($result && !$this->isEmptyValue($fieldInfos, $value)) { + if (!self::$validator->isTimestamp($value)) { + return false; + } + + $result = true; + } + + return $result; + } + + /** + * Verify if the field value from GET/POST is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return bool + * @see self::printInputField() + */ + public function verifyPostFieldValue($fieldInfos, $key, $keyPrefix = '', $keySuffix = '') + { + return parent::verifyPostFieldValue($fieldInfos, $key, $keyPrefix, $keySuffix); + } + + /** + * Get field value from GET/POST + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $defaultValue Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return mixed + * @see self::printInputField() + */ + public function getPostFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName . 'hour') || GETPOSTISSET($htmlName . 'min') || GETPOSTISSET($htmlName . 'sec') || GETPOSTISSET($htmlName . 'month') || GETPOSTISSET($htmlName . 'day') || GETPOSTISSET($htmlName . 'year')) { + $value = dol_mktime(GETPOSTINT($htmlName . 'hour'), GETPOSTINT($htmlName . 'min'), GETPOSTINT($htmlName . 'sec'), GETPOSTINT($htmlName . 'month'), GETPOSTINT($htmlName . 'day'), GETPOSTINT($htmlName . 'year'), 'gmt'); // for date without hour, we use gmt + } else { + $value = $defaultValue; + } + + return $value; + } + + /** + * Get search field value from GET/POST + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $defaultValue Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return mixed + * @see self::printInputSearchField() + */ + public function getPostSearchFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName . '_startmonth') || GETPOSTISSET($htmlName . '_startday') || GETPOSTISSET($htmlName . '_startyear')) { + $start = dol_mktime(0, 0, 0, GETPOSTINT($htmlName . '_startmonth'), GETPOSTINT($htmlName . '_startday'), GETPOSTINT($htmlName . '_startyear'), 'gmt'); + } else { + $start = is_array($defaultValue) && isset($defaultValue['start']) ? $defaultValue['start'] : ''; + } + + if (GETPOSTISSET($htmlName . '_endmonth') || GETPOSTISSET($htmlName . '_endday') || GETPOSTISSET($htmlName . '_endyear')) { + $end = dol_mktime(23, 59, 59, GETPOSTINT($htmlName . '_endmonth'), GETPOSTINT($htmlName . '_endday'), GETPOSTINT($htmlName . '_endyear'), 'gmt'); + } else { + $end = is_array($defaultValue) && isset($defaultValue['end']) ? $defaultValue['end'] : ''; + } + + return array( + 'start' => $start, + 'end' => $end, + ); + } + + /** + * Get sql filter for search field + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @return string + * @see self::printInputSearchField(), self::getPostSearchFieldValue() + */ + public function sqlFilterSearchField($fieldInfos, $key, $value) + { + if (!$this->isEmptyValue($fieldInfos, $value)) { + $alias = $fieldInfos->sqlAlias ?? 't.'; + $field = $this->db->sanitize($alias . ($fieldInfos->nameInTable ?? $key)); + $sql = ''; + + if (is_array($value)) { + $hasStart = !is_null($value['start']) && $value['start'] !== ''; + $hasEnd = !is_null($value['start']) && $value['start'] !== ''; + if ($hasStart && $hasEnd) { + $sql = " AND (" . $field . " BETWEEN '" . $this->db->idate($value['start']) . "' AND '" . $this->db->idate($value['end']) . "')"; + } elseif ($hasStart) { + $sql = " AND " . $field . " >= '" . $this->db->idate($value['start']) . "'"; + } elseif ($hasEnd) { + $sql = " AND " . $field . " <= '" . $this->db->idate($value['end']) . "'"; + } + } elseif (is_numeric($value)) { + $sql = " AND " . $field . " = '" . $this->db->idate($value) . "'"; + } + + return $sql; + } + + return ''; + } +} diff --git a/htdocs/core/class/fields/doublefield.class.php b/htdocs/core/class/fields/doublefield.class.php new file mode 100644 index 00000000000..c1a80ee2414 --- /dev/null +++ b/htdocs/core/class/fields/doublefield.class.php @@ -0,0 +1,220 @@ + + * + * 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/fields/doublefield.class.php + * \ingroup core + * \brief File of class to double field + */ + +require_once DOL_DOCUMENT_ROOT . '/core/class/fields/commonfield.class.php'; + + +/** + * Class to double field + */ +class DoubleField extends CommonField +{ + /** + * @var array List of value deemed as empty (null always deemed as empty) + */ + public $emptyValues = array(''); + + + /** + * Return HTML string to put an input search field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printInputSearchField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $htmlName = $keyPrefix . $key . $keySuffix; + + return self::$form->inputType('text', $htmlName, $value, $htmlName, $moreCss, $moreAttrib); + } + + /** + * Return HTML string to put an input field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printInputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $autoFocus = $fieldInfos->inputAutofocus ? ' autofocus' : ''; + $value = !$this->isEmptyValue($fieldInfos, $value) ? price((double) $value) : ''; + $htmlName = $keyPrefix . $key . $keySuffix; + + return self::$form->inputType('text', $htmlName, $value, $htmlName, $moreCss, $moreAttrib . $autoFocus); + } + + /** + * Return HTML string to show a field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printOutputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + return !$this->isEmptyValue($fieldInfos, $value) ? price((double) $value) : ''; + } + + /** + * Get input CSS + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $moreCss Value for css to define style/length of field. + * @param string $defaultCss Default value for css to define style/length of field. + * @return string + * @see self::printInputSearchField(), self::printInputField() + */ + public function getInputCss($fieldInfos, $moreCss = '', $defaultCss = '') + { + return parent::getInputCss($fieldInfos, $moreCss, $defaultCss ? $defaultCss : 'maxwidth75'); + } + + /** + * Verify if the field value is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Value to check (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @return bool + * @see self::printInputField() + */ + public function verifyFieldValue($fieldInfos, $key, $value) + { + $result = parent::verifyFieldValue($fieldInfos, $key, $value); + if ($result && !$this->isEmptyValue($fieldInfos, $value)) { + if (!self::$validator->isNumeric($value)) { + return false; + } + + $result = true; + } + + return $result; + } + + /** + * Verify if the field value from GET/POST is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return bool + * @see self::printInputField() + */ + public function verifyPostFieldValue($fieldInfos, $key, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + $value = GETPOST($htmlName, 'restricthtml'); + $value = str_replace(',', '.', $value); + + return $this->verifyFieldValue($fieldInfos, $key, $value); + } + + /** + * Get field value from GET/POST + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $defaultValue Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return mixed + * @see self::printInputField() + */ + public function getPostFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $value = (double) price2num(GETPOST($htmlName, 'alphanohtml')); + } else { + $value = $defaultValue; + } + + return $value; + } + + /** + * Get search field value from GET/POST + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $defaultValue Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return mixed + * @see self::printInputSearchField() + */ + public function getPostSearchFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $value = GETPOST($htmlName, 'alpha'); + } else { + $value = $defaultValue; + } + + return $value; + } + + /** + * Get sql filter for search field + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @return string + * @see self::printInputSearchField(), self::getPostSearchFieldValue() + */ + public function sqlFilterSearchField($fieldInfos, $key, $value) + { + if (!$this->isEmptyValue($fieldInfos, $value)) { + $alias = $fieldInfos->sqlAlias ?? 't.'; + + return natural_search($alias . ($fieldInfos->nameInTable ?? $key), $value, 1); + } + + return ''; + } +} diff --git a/htdocs/core/class/fields/durationfield.class.php b/htdocs/core/class/fields/durationfield.class.php new file mode 100644 index 00000000000..3ccc6235ff5 --- /dev/null +++ b/htdocs/core/class/fields/durationfield.class.php @@ -0,0 +1,207 @@ + + * + * 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/fields/durationfield.class.php + * \ingroup core + * \brief File of class to duration field + */ + +require_once DOL_DOCUMENT_ROOT . '/core/class/fields/commonfield.class.php'; +require_once DOL_DOCUMENT_ROOT . '/core/lib/date.lib.php'; + + +/** + * Class to duration field + */ +class DurationField extends CommonField +{ + /** + * @var array List of value deemed as empty (null always deemed as empty) + */ + public $emptyValues = array(''); + + + /** + * Return HTML string to put an input search field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printInputSearchField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + // Todo make filter with min / max ? or same as a number ? + return $this->printInputField($fieldInfos, $key, $value, $keyPrefix, $keySuffix, $moreCss, $moreAttrib); + } + + /** + * Return HTML string to put an input field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printInputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + // TODO Must also support $moreAttrib and $moreCss ? + return self::$form->select_duration($htmlName, (int) $value, 0, 'text', 0, 1); + } + + /** + * Return HTML string to show a field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printOutputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + return !$this->isEmptyValue($fieldInfos, $value) ? convertSecondToTime((int) $value, 'allhourmin') : ''; + } + + /** + * Get input CSS + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $moreCss Value for css to define style/length of field. + * @param string $defaultCss Default value for css to define style/length of field. + * @return string + * @see self::printInputSearchField(), self::printInputField() + */ + public function getInputCss($fieldInfos, $moreCss = '', $defaultCss = '') + { + return parent::getInputCss($fieldInfos, $moreCss, $defaultCss); + } + + /** + * Verify if the field value is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Value to check (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @return bool + * @see self::printInputField() + */ + public function verifyFieldValue($fieldInfos, $key, $value) + { + $result = parent::verifyFieldValue($fieldInfos, $key, $value); + if ($result && !$this->isEmptyValue($fieldInfos, $value)) { + if (!self::$validator->isDuration($value)) { + return false; + } + + $result = true; + } + + return $result; + } + + /** + * Verify if the field value from GET/POST is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return bool + * @see self::printInputField() + */ + public function verifyPostFieldValue($fieldInfos, $key, $keyPrefix = '', $keySuffix = '') + { + return parent::verifyPostFieldValue($fieldInfos, $key, $keyPrefix, $keySuffix); + } + + /** + * Get field value from GET/POST + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $defaultValue Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return mixed + * @see self::printInputField() + */ + public function getPostFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName . 'hour') || GETPOSTISSET($htmlName . 'min')) { + $value_hours = GETPOSTINT($htmlName . "hour"); + $value_minutes = GETPOSTINT($htmlName . "min"); + $value = $value_hours * 3600 + $value_minutes * 60; + } else { + $value = $defaultValue; + } + + return $value; + } + + /** + * Get search field value from GET/POST + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $defaultValue Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return mixed + * @see self::printInputSearchField() + */ + public function getPostSearchFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + return $this->getPostFieldValue($fieldInfos, $key, $defaultValue, $keyPrefix, $keySuffix); + } + + /** + * Get sql filter for search field + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @return string + * @see self::printInputSearchField(), self::getPostSearchFieldValue() + */ + public function sqlFilterSearchField($fieldInfos, $key, $value) + { + if (!$this->isEmptyValue($fieldInfos, $value)) { + $alias = $fieldInfos->sqlAlias ?? 't.'; + + return natural_search($alias . ($fieldInfos->nameInTable ?? $key), $value, 0); + } + + return ''; + } +} diff --git a/htdocs/core/class/fields/emailfield.class.php b/htdocs/core/class/fields/emailfield.class.php new file mode 100644 index 00000000000..f2b73df8461 --- /dev/null +++ b/htdocs/core/class/fields/emailfield.class.php @@ -0,0 +1,217 @@ + + * + * 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/fields/emailfield.class.php + * \ingroup core + * \brief File of class to email field + */ + +require_once DOL_DOCUMENT_ROOT . '/core/class/fields/commonfield.class.php'; + + +/** + * Class to email field + */ +class EmailField extends CommonField +{ + /** + * @var array List of value deemed as empty (null always deemed as empty) + */ + public $emptyValues = array(''); + + + /** + * Return HTML string to put an input search field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printInputSearchField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $htmlName = $keyPrefix . $key . $keySuffix; + + return self::$form->inputType('text', $htmlName, $value, $htmlName, $moreCss, $moreAttrib); + } + + /** + * Return HTML string to put an input field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printInputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $moreAttrib = trim((string) $moreAttrib); + if (empty($moreAttrib)) $moreAttrib = ' ' . $moreAttrib; + $autoFocus = $fieldInfos->inputAutofocus ? ' autofocus' : ''; + $htmlName = $keyPrefix . $key . $keySuffix; + + return self::$form->inputType('text', $htmlName, (string) $value, $htmlName, $moreCss, $moreAttrib . $autoFocus); + } + + /** + * Return HTML string to show a field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printOutputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + return !$this->isEmptyValue($fieldInfos, $value) ? dol_print_email((string) $value, 0, 0, 0, 64, 1, 1) : ''; + } + + /** + * Get input CSS + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $moreCss Value for css to define style/length of field. + * @param string $defaultCss Default value for css to define style/length of field. + * @return string + * @see self::printInputSearchField(), self::printInputField() + */ + public function getInputCss($fieldInfos, $moreCss = '', $defaultCss = '') + { + return parent::getInputCss($fieldInfos, $moreCss, $defaultCss); + } + + /** + * Verify if the field value is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Value to check (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @return bool + * @see self::printInputField() + */ + public function verifyFieldValue($fieldInfos, $key, $value) + { + $result = parent::verifyFieldValue($fieldInfos, $key, $value); + if ($result && !$this->isEmptyValue($fieldInfos, $value)) { + if (!self::$validator->isEmail($value)) { + return false; + } + + $result = true; + } + + return $result; + } + + /** + * Verify if the field value from GET/POST is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return bool + * @see self::printInputField() + */ + public function verifyPostFieldValue($fieldInfos, $key, $keyPrefix = '', $keySuffix = '') + { + return parent::verifyPostFieldValue($fieldInfos, $key, $keyPrefix, $keySuffix); + } + + /** + * Get field value from GET/POST + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $defaultValue Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return mixed + * @see self::printInputField() + */ + public function getPostFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $value = GETPOST($htmlName, 'alphanohtml'); + } else { + $value = $defaultValue; + } + + return $value; + } + + /** + * Get search field value from GET/POST + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $defaultValue Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return mixed + * @see self::printInputSearchField() + */ + public function getPostSearchFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $value = GETPOST($htmlName, 'alpha'); + } else { + $value = $defaultValue; + } + + return $value; + } + + /** + * Get sql filter for search field + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @return string + * @see self::printInputSearchField(), self::getPostSearchFieldValue() + */ + public function sqlFilterSearchField($fieldInfos, $key, $value) + { + if (!$this->isEmptyValue($fieldInfos, $value)) { + $alias = $fieldInfos->sqlAlias ?? 't.'; + + return natural_search($alias . ($fieldInfos->nameInTable ?? $key), $value, 0); + } + + return ''; + } +} diff --git a/htdocs/core/class/fields/htmlfield.class.php b/htdocs/core/class/fields/htmlfield.class.php new file mode 100644 index 00000000000..58095d9f529 --- /dev/null +++ b/htdocs/core/class/fields/htmlfield.class.php @@ -0,0 +1,217 @@ + + * + * 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/fields/htmlfield.class.php + * \ingroup core + * \brief File of class to html field + */ + +require_once DOL_DOCUMENT_ROOT . '/core/class/fields/commonfield.class.php'; + + +// TODO same as text field ? + +/** + * Class to html field + */ +class HtmlField extends CommonField +{ + /** + * @var array List of value deemed as empty (null always deemed as empty) + */ + public $emptyValues = array(''); + + + /** + * Return HTML string to put an input search field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printInputSearchField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $htmlName = $keyPrefix . $key . $keySuffix; + + return self::$form->inputType('text', $htmlName, $value, $htmlName, $moreCss, $moreAttrib); + } + + /** + * Return HTML string to put an input field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printInputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $moreAttrib = trim((string) $moreAttrib); + if (empty($moreAttrib)) $moreAttrib = ' ' . $moreAttrib; + $htmlName = $keyPrefix . $key . $keySuffix; + + return self::$form->inputHtml($htmlName, $value, $moreCss, $moreAttrib); + } + + /** + * Return HTML string to show a field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printOutputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + if (!$this->isEmptyValue($fieldInfos, $value)) { + if (!empty($fieldInfos->options)) { + $value = str_replace(',', "\n", (string) $value); + } + + return dol_htmlentitiesbr((string) $value); + } + + return ''; + } + + /** + * Get input CSS + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $moreCss Value for css to define style/length of field. + * @param string $defaultCss Default value for css to define style/length of field. + * @return string + * @see self::printInputSearchField(), self::printInputField() + */ + public function getInputCss($fieldInfos, $moreCss = '', $defaultCss = '') + { + return parent::getInputCss($fieldInfos, $moreCss, $defaultCss); + } + + /** + * Verify if the field value is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Value to check (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @return bool + * @see self::printInputField() + */ + public function verifyFieldValue($fieldInfos, $key, $value) + { + return parent::verifyFieldValue($fieldInfos, $key, $value); + } + + /** + * Verify if the field value from GET/POST is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return bool + * @see self::printInputField() + */ + public function verifyPostFieldValue($fieldInfos, $key, $keyPrefix = '', $keySuffix = '') + { + return parent::verifyPostFieldValue($fieldInfos, $key, $keyPrefix, $keySuffix); + } + + /** + * Get field value from GET/POST + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $defaultValue Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return mixed + * @see self::printInputField() + */ + public function getPostFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $value = GETPOST($htmlName, empty($fieldInfos->getPostCheck) ? 'restricthtml' : $fieldInfos->getPostCheck); + } else { + $value = $defaultValue; + } + + return $value; + } + + /** + * Get search field value from GET/POST + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $defaultValue Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return mixed + * @see self::printInputSearchField() + */ + public function getPostSearchFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $value = GETPOST($htmlName, 'alpha'); + } else { + $value = $defaultValue; + } + + return $value; + } + + /** + * Get sql filter for search field + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @return string + * @see self::printInputSearchField(), self::getPostSearchFieldValue() + */ + public function sqlFilterSearchField($fieldInfos, $key, $value) + { + if (!$this->isEmptyValue($fieldInfos, $value)) { + $alias = $fieldInfos->sqlAlias ?? 't.'; + + return natural_search($alias . ($fieldInfos->nameInTable ?? $key), $value, 0); + } + + return ''; + } +} diff --git a/htdocs/core/class/fields/iconfield.class.php b/htdocs/core/class/fields/iconfield.class.php new file mode 100644 index 00000000000..8d1c9b9d8ed --- /dev/null +++ b/htdocs/core/class/fields/iconfield.class.php @@ -0,0 +1,211 @@ + + * + * 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/fields/iconfield.class.php + * \ingroup core + * \brief File of class to icon field + */ + +require_once DOL_DOCUMENT_ROOT . '/core/class/fields/commonfield.class.php'; + + +/** + * Class to icon field + */ +class IconField extends CommonField +{ + /** + * @var array List of value deemed as empty (null always deemed as empty) + */ + public $emptyValues = array(''); + + + /** + * Return HTML string to put an input search field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printInputSearchField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $htmlName = $keyPrefix . $key . $keySuffix; + + return self::$form->inputType('text', $htmlName, (string) $value, $htmlName, $moreCss, $moreAttrib); + } + + /** + * Return HTML string to put an input field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printInputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $moreAttrib = trim((string) $moreAttrib); + if (empty($moreAttrib)) $moreAttrib = ' ' . $moreAttrib; + $placeHolder = $fieldInfos->inputPlaceholder; + if (!empty($placeHolder)) $placeHolder = ' placeholder="' . dolPrintHTMLForAttribute($placeHolder) . '"'; + $autoFocus = $fieldInfos->inputAutofocus ? ' autofocus' : ''; + $htmlName = $keyPrefix . $key . $keySuffix; + + return self::$form->inputIcon($htmlName, (string) $value, $moreCss, $moreAttrib . $placeHolder . $autoFocus); + } + + /** + * Return HTML string to show a field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printOutputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + return !$this->isEmptyValue($fieldInfos, $value) ? self::$form->outputIcon((string) $value) : ''; + } + + /** + * Get input CSS + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $moreCss Value for css to define style/length of field. + * @param string $defaultCss Default value for css to define style/length of field. + * @return string + * @see self::printInputSearchField(), self::printInputField() + */ + public function getInputCss($fieldInfos, $moreCss = '', $defaultCss = '') + { + return parent::getInputCss($fieldInfos, $moreCss, $defaultCss); + } + + /** + * Verify if the field value is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Value to check (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @return bool + * @see self::printInputField() + */ + public function verifyFieldValue($fieldInfos, $key, $value) + { + return parent::verifyFieldValue($fieldInfos, $key, $value); + } + + /** + * Verify if the field value from GET/POST is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return bool + * @see self::printInputField() + */ + public function verifyPostFieldValue($fieldInfos, $key, $keyPrefix = '', $keySuffix = '') + { + return parent::verifyPostFieldValue($fieldInfos, $key, $keyPrefix, $keySuffix); + } + + /** + * Get field value from GET/POST + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $defaultValue Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return mixed + * @see self::printInputField() + */ + public function getPostFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $check = $key == 'lang' ? 'aZ09' : 'alphanohtml'; + $value = GETPOST($htmlName, $check); + } else { + $value = $defaultValue; + } + + return $value; + } + + /** + * Get search field value from GET/POST + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $defaultValue Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return mixed + * @see self::printInputSearchField() + */ + public function getPostSearchFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $value = GETPOST($htmlName, 'alpha'); + } else { + $value = $defaultValue; + } + + return $value; + } + + /** + * Get sql filter for search field + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @return string + * @see self::printInputSearchField(), self::getPostSearchFieldValue() + */ + public function sqlFilterSearchField($fieldInfos, $key, $value) + { + if (!$this->isEmptyValue($fieldInfos, $value)) { + $alias = $fieldInfos->sqlAlias ?? 't.'; + + return natural_search($alias . ($fieldInfos->nameInTable ?? $key), $value, 0); + } + + return ''; + } +} diff --git a/htdocs/core/class/fields/intfield.class.php b/htdocs/core/class/fields/intfield.class.php new file mode 100644 index 00000000000..605edd47ce9 --- /dev/null +++ b/htdocs/core/class/fields/intfield.class.php @@ -0,0 +1,218 @@ + + * + * 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/fields/intfield.class.php + * \ingroup core + * \brief File of class to int field + */ + +require_once DOL_DOCUMENT_ROOT . '/core/class/fields/commonfield.class.php'; + + +/** + * Class to int field + */ +class IntField extends CommonField +{ + /** + * @var array List of value deemed as empty (null always deemed as empty) + */ + public $emptyValues = array(''); + + + /** + * Return HTML string to put an input search field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printInputSearchField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $htmlName = $keyPrefix . $key . $keySuffix; + + return self::$form->inputType('text', $htmlName, (string) $value, $htmlName, $moreCss, $moreAttrib); + } + + /** + * Return HTML string to put an input field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printInputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $size = (int) $fieldInfos->size; + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $maxLength = ''; // $size > 0 ? ' maxlength="' . $size . '"' : ''; // TODO rework, wrong method + $autoFocus = $fieldInfos->inputAutofocus ? ' autofocus' : ''; + $htmlName = $keyPrefix . $key . $keySuffix; + $value = !is_null($value) && $value !== '' ? (int) $value : ''; + + return self::$form->inputType('text', $htmlName, (string) $value, $htmlName, $moreCss, $maxLength . $moreAttrib . $autoFocus); + } + + /** + * Return HTML string to show a field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printOutputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + return !$this->isEmptyValue($fieldInfos, $value) ? (string) $value : ''; + } + + /** + * Get input CSS + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $moreCss Value for css to define style/length of field. + * @param string $defaultCss Default value for css to define style/length of field. + * @return string + * @see self::printInputSearchField(), self::printInputField() + */ + public function getInputCss($fieldInfos, $moreCss = '', $defaultCss = '') + { + return parent::getInputCss($fieldInfos, $moreCss, $defaultCss ? $defaultCss : 'maxwidth75'); + } + + /** + * Verify if the field value is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Value to check (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @return bool + * @see self::printInputField() + */ + public function verifyFieldValue($fieldInfos, $key, $value) + { + $result = parent::verifyFieldValue($fieldInfos, $key, $value); + if ($result && !$this->isEmptyValue($fieldInfos, $value)) { + if (!self::$validator->isNumeric($value)) { + return false; + } + + $result = true; + } + + return $result; + } + + /** + * Verify if the field value from GET/POST is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return bool + * @see self::printInputField() + */ + public function verifyPostFieldValue($fieldInfos, $key, $keyPrefix = '', $keySuffix = '') + { + return parent::verifyPostFieldValue($fieldInfos, $key, $keyPrefix, $keySuffix); + } + + /** + * Get field value from GET/POST + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $defaultValue Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return mixed + * @see self::printInputField() + */ + public function getPostFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $value = (int) price2num(GETPOSTINT($htmlName)); + } else { + $value = $defaultValue; + } + + return $value; + } + + /** + * Get search field value from GET/POST + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $defaultValue Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return mixed + * @see self::printInputSearchField() + */ + public function getPostSearchFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $value = GETPOST($htmlName, 'alpha'); + } else { + $value = $defaultValue; + } + + return $value; + } + + /** + * Get sql filter for search field + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @return string + * @see self::printInputSearchField(), self::getPostSearchFieldValue() + */ + public function sqlFilterSearchField($fieldInfos, $key, $value) + { + if (!$this->isEmptyValue($fieldInfos, $value)) { + $alias = $fieldInfos->sqlAlias ?? 't.'; + + return natural_search($alias . ($fieldInfos->nameInTable ?? $key), $value, 1); + } + + return ''; + } +} diff --git a/htdocs/core/class/fields/ipfield.class.php b/htdocs/core/class/fields/ipfield.class.php new file mode 100644 index 00000000000..9484705a0c4 --- /dev/null +++ b/htdocs/core/class/fields/ipfield.class.php @@ -0,0 +1,222 @@ + + * + * 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/fields/ipfield.class.php + * \ingroup core + * \brief File of class to ip field + */ + +require_once DOL_DOCUMENT_ROOT . '/core/class/fields/commonfield.class.php'; + + +/** + * Class to ip field + */ +class IpField extends CommonField +{ + /** + * @var array List of value deemed as empty (null always deemed as empty) + */ + public $emptyValues = array(''); + + + /** + * Return HTML string to put an input search field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printInputSearchField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $htmlName = $keyPrefix . $key . $keySuffix; + + return self::$form->inputType('text', $htmlName, (string) $value, $htmlName, $moreCss, $moreAttrib); + } + + /** + * Return HTML string to put an input field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printInputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $moreAttrib = trim((string) $moreAttrib); + if (empty($moreAttrib)) $moreAttrib = ' ' . $moreAttrib; + $autoFocus = $fieldInfos->inputAutofocus ? ' autofocus' : ''; + $htmlName = $keyPrefix . $key . $keySuffix; + + return self::$form->inputType('text', $htmlName, (string) $value, $htmlName, $moreCss, $moreAttrib . $autoFocus); + } + + /** + * Return HTML string to show a field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printOutputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + return !$this->isEmptyValue($fieldInfos, $value) ? dol_print_ip((string) $value, 0) : ''; + } + + /** + * Get input CSS + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $moreCss Value for css to define style/length of field. + * @param string $defaultCss Default value for css to define style/length of field. + * @return string + * @see self::printInputSearchField(), self::printInputField() + */ + public function getInputCss($fieldInfos, $moreCss = '', $defaultCss = '') + { + return parent::getInputCss($fieldInfos, $moreCss, $defaultCss); + } + + /** + * Verify if the field value is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Value to check (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @return bool + * @see self::printInputField() + */ + public function verifyFieldValue($fieldInfos, $key, $value) + { + global $langs; + + $result = parent::verifyFieldValue($fieldInfos, $key, $value); + if ($result && !$this->isEmptyValue($fieldInfos, $value)) { + // TODO move to class Validate + if (!filter_var($value, FILTER_VALIDATE_IP)) { + self::$validator->error = $langs->trans('RequireValidValue'); + return false; + } + + $result = true; + } + + return $result; + } + + /** + * Verify if the field value from GET/POST is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return bool + * @see self::printInputField() + */ + public function verifyPostFieldValue($fieldInfos, $key, $keyPrefix = '', $keySuffix = '') + { + return parent::verifyPostFieldValue($fieldInfos, $key, $keyPrefix, $keySuffix); + } + + /** + * Get field value from GET/POST + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $defaultValue Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return mixed + * @see self::printInputField() + */ + public function getPostFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $value = GETPOST($htmlName, 'alphanohtml'); + } else { + $value = $defaultValue; + } + + return $value; + } + + + /** + * Get search field value from GET/POST + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $defaultValue Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return mixed + * @see self::printInputSearchField() + */ + public function getPostSearchFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $value = GETPOST($htmlName, 'alpha'); + } else { + $value = $defaultValue; + } + + return $value; + } + + /** + * Get sql filter for search field + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @return string + * @see self::printInputSearchField(), self::getPostSearchFieldValue() + */ + public function sqlFilterSearchField($fieldInfos, $key, $value) + { + if (!$this->isEmptyValue($fieldInfos, $value)) { + $alias = $fieldInfos->sqlAlias ?? 't.'; + + return natural_search($alias . ($fieldInfos->nameInTable ?? $key), $value, 0); + } + + return ''; + } +} diff --git a/htdocs/core/class/fields/linestrgfield.class.php b/htdocs/core/class/fields/linestrgfield.class.php new file mode 100644 index 00000000000..8bab9b03677 --- /dev/null +++ b/htdocs/core/class/fields/linestrgfield.class.php @@ -0,0 +1,32 @@ + + * + * 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/fields/linestrgfield.class.php + * \ingroup core + * \brief File of class to linestrg field + */ + +require_once DOL_DOCUMENT_ROOT . '/core/class/fields/commongeofield.class.php'; + + +/** + * Class to linestrg field + */ +class LinestrgField extends CommonGeoField +{ +} diff --git a/htdocs/core/class/fields/linkfield.class.php b/htdocs/core/class/fields/linkfield.class.php new file mode 100644 index 00000000000..caaa372c172 --- /dev/null +++ b/htdocs/core/class/fields/linkfield.class.php @@ -0,0 +1,437 @@ + + * + * 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/fields/linkfield.class.php + * \ingroup core + * \brief File of class to link field + */ + +require_once DOL_DOCUMENT_ROOT . '/core/class/fields/commonfield.class.php'; + + +/** + * Class to link field + */ +class LinkField extends CommonField +{ + /** + * @var array List of value deemed as empty (null always deemed as empty) + */ + public $emptyValues = array('', '-1', '0', 0); + + + /** + * Return HTML string to put an input search field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printInputSearchField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $htmlName = $keyPrefix . $key . $keySuffix; + + $optionParams = $this->getOptionsParams($fieldInfos->options); + + if (version_compare(DOL_VERSION, '19.0.0') < 0) { + // Example: 'ObjectName:classPath:1:(status:=:1)' + $objectDesc = $optionParams['all']; + if (strpos($objectDesc, '$ID$') !== false && !empty($fieldInfos->object->id)) { + $objectDesc = str_replace('$ID$', (string) $fieldInfos->object->id, $objectDesc); + } + + $out = self::$form->selectForForms($objectDesc, $htmlName, (int) $value, 0, '', '', $moreCss, $moreAttrib); + } else { + // Example: 'ObjectName:classPath' To not propagate any filter (selectForForms do ajax call and propagating SQL filter is blocked by some WAF). + // Also we should use the one into the definition in the ->fields of $elem if found. + $objectDesc = $optionParams['objectClass'] . ':' . $optionParams['pathToClass']; + + // Example: 'actioncomm:options_fff' To be used in priority to know object linked with all its definition (including filters) + // The selectForForms is called with parameter $objectfield defined, so the app can retrieve the filter inside the ajax component instead of being provided as parameters. The + // filter was used to pass SQL requests leading to serious SQL injection problem. This should not be possible. Also the call of the ajax was broken by some WAF. + $objectField = isset($fieldInfos->object) ? $fieldInfos->object->element . (!empty($fieldInfos->object->module) ? '@' . $fieldInfos->object->module : '') . ':' . ($fieldInfos->fieldType == FieldInfos::FIELD_TYPE_EXTRA_FIELD ? 'options_' : '') . $fieldInfos->nameInClass : ''; + + $out = self::$form->selectForForms($objectDesc, $htmlName, (int) $value, 0, '', '', $moreCss, $moreAttrib, 0, 0, '', $objectField); + } + + return $out; + } + + /** + * Return HTML string to put an input field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printInputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + global $langs; + + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $moreAttrib = trim((string) $moreAttrib); + if (empty($moreAttrib)) $moreAttrib = ' ' . $moreAttrib; + $placeHolder = $fieldInfos->inputPlaceholder; + $autoFocus = $fieldInfos->inputAutofocus ? ' autofocus' : ''; + $htmlName = $keyPrefix . $key . $keySuffix; + $showEmpty = $fieldInfos->required && !$this->isEmptyValue($fieldInfos, $fieldInfos->defaultValue) ? 0 : 1; + + $optionParams = $this->getOptionsParams($fieldInfos->options); + + // If we have to add a create button + if ($optionParams['addCreateButton']) { + if (!empty($fieldInfos->picto)) { + $moreCss .= ' widthcentpercentminusxx'; + } else { + $moreCss .= ' widthcentpercentminusx'; + } + } elseif (!empty($fieldInfos->picto)) { + $moreCss .= ' widthcentpercentminusx'; + } + + if (version_compare(DOL_VERSION, '19.0.0') < 0) { + // Example: 'ObjectName:classPath:1:(status:=:1)' + $objectDesc = $optionParams['all']; + if (strpos($objectDesc, '$ID$') !== false && !empty($fieldInfos->object->id)) { + $objectDesc = str_replace('$ID$', (string) $fieldInfos->object->id, $objectDesc); + } + + $out = self::$form->selectForForms($objectDesc, $htmlName, (int) $value, $showEmpty, '', $placeHolder, $moreCss, $moreAttrib . $autoFocus, 0, $fieldInfos->inputDisabled ? 1 : 0); + } else { + // Example: 'ObjectName:classPath' To not propagate any filter (selectForForms do ajax call and propagating SQL filter is blocked by some WAF). + // Also we should use the one into the definition in the ->fields of $elem if found. + $objectDesc = $optionParams['objectClass'] . ':' . $optionParams['pathToClass']; + + // Example: 'actioncomm:options_fff' To be used in priority to know object linked with all its definition (including filters) + // The selectForForms is called with parameter $objectfield defined, so the app can retrieve the filter inside the ajax component instead of being provided as parameters. The + // filter was used to pass SQL requests leading to serious SQL injection problem. This should not be possible. Also the call of the ajax was broken by some WAF. + $objectField = isset($fieldInfos->object) ? $fieldInfos->object->element . (!empty($fieldInfos->object->module) ? '@' . $fieldInfos->object->module : '') . ':' . ($fieldInfos->fieldType == FieldInfos::FIELD_TYPE_EXTRA_FIELD ? 'options_' : '') . $fieldInfos->nameInClass : ''; + + $out = self::$form->selectForForms($objectDesc, $htmlName, (int) $value, $showEmpty, '', $placeHolder, $moreCss, $moreAttrib . $autoFocus, 0, $fieldInfos->inputDisabled ? 1 : 0, '', $objectField); + } + + if ($optionParams['addCreateButton'] && // If we have to add a create button + (!GETPOSTISSET('backtopage') || strpos(GETPOST('backtopage'), $_SERVER['PHP_SELF']) === 0) && // To avoid to open several times the 'Plus' button (we accept only one level) + !$fieldInfos->inputDisabled && // To avoid to show the button if the field is protected by a "disabled". + empty($fieldInfos->otherParams['nonewbutton']) // manually disable new button + ) { + $class = $optionParams['objectClass']; + $classfile = $optionParams['pathToClass']; + $classpath = dirname(dirname($classfile)); + if (file_exists(dol_buildpath($classpath . '/card.php'))) { + $url_path = dol_buildpath($classpath . '/card.php', 1); + } else { + $url_path = dol_buildpath($classpath . '/' . strtolower($class) . '_card.php', 1); + } + $paramforthenewlink = ''; + $paramforthenewlink .= (GETPOSTISSET('action') ? '&action=' . GETPOST('action', 'aZ09') : ''); + $paramforthenewlink .= (GETPOSTISSET('id') ? '&id=' . GETPOSTINT('id') : ''); + $paramforthenewlink .= (GETPOSTISSET('origin') ? '&origin=' . GETPOST('origin', 'aZ09') : ''); + $paramforthenewlink .= (GETPOSTISSET('originid') ? '&originid=' . GETPOSTINT('originid') : ''); + $paramforthenewlink .= '&fk_' . strtolower($class) . '=--IDFORBACKTOPAGE--'; + // TODO Add JavaScript code to add input fields already filled into $paramforthenewlink so we won't loose them when going back to main page + $out .= ''; + } + + return $out; + } + + /** + * Return HTML string to show a field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printOutputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + if (!$this->isEmptyValue($fieldInfos, $value)) { + $optionParams = $this->getOptionsParams($fieldInfos->options); + + $classpath = $optionParams['pathToClass']; + if (!empty($classpath)) { + $classname = $optionParams['objectClass']; + + $object = $this->getObject($classname, $classpath); + if (isset($object)) { + '@phan-var-force CommonObject $object'; + if ($object->element === 'product') { // Special case for product because default valut of fetch are wrong + '@phan-var-force Product $object'; + $result = $object->fetch((int) $value, '', '', '', 0, 1, 1); + } else { + $result = $object->fetch($value); + } + if ($result > 0) { + $getNomUrlParam1 = $optionParams['getNomUrlParam1']; + $getNomUrlParam2 = $optionParams['getNomUrlParam2']; + + if ($object->element === 'product') { + '@phan-var-force Product $object'; + $get_name_url_param_arr = array($getNomUrlParam1, $getNomUrlParam2, 0, -1, 0, '', 0, ' - '); + if (isset($fieldInfos->getNameUrlParams)) { + $get_name_url_params = explode(':', $fieldInfos->getNameUrlParams); + if (!empty($get_name_url_params)) { + $param_num_max = count($get_name_url_param_arr) - 1; + foreach ($get_name_url_params as $param_num => $param_value) { + if ($param_num > $param_num_max) { + break; + } + $get_name_url_param_arr[$param_num] = $param_value; + } + } + } + + /** + * @var Product $object + */ + return self::$form->getNomUrl($object, (int) $get_name_url_param_arr[0], $get_name_url_param_arr[1], (int) $get_name_url_param_arr[2], (int) $get_name_url_param_arr[3], (int) $get_name_url_param_arr[4], $get_name_url_param_arr[5], (int) $get_name_url_param_arr[6], $get_name_url_param_arr[7]); + } elseif (get_class($object) == 'Categorie') { + // For category object, rendering must use the same method than the one deinfed into showCategories() + $color = $object->color; + $sfortag = ''; + $sfortag .= self::$form->getNomUrl($object, (int) $getNomUrlParam1, $getNomUrlParam2); + $sfortag .= ''; + return $sfortag; + } else { + return self::$form->getNomUrl($object, (int) $getNomUrlParam1, $getNomUrlParam2); + } + } + } else { + dol_syslog('Error bad setup of field : ' . $key, LOG_WARNING); + return 'Error bad setup of field'; + } + } else { + dol_syslog('Error bad setup of field : ' . $key, LOG_WARNING); + return 'Error bad setup of field'; + } + } + + return ''; + } + + /** + * Get input CSS + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $moreCss Value for css to define style/length of field. + * @param string $defaultCss Default value for css to define style/length of field. + * @return string + * @see self::printInputSearchField(), self::printInputField() + */ + public function getInputCss($fieldInfos, $moreCss = '', $defaultCss = '') + { + return parent::getInputCss($fieldInfos, $moreCss, $defaultCss ? $defaultCss : 'minwidth200imp'); + } + + /** + * Verify if the field value is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Value to check (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @return bool + * @see self::printInputField() + */ + public function verifyFieldValue($fieldInfos, $key, $value) + { + $result = parent::verifyFieldValue($fieldInfos, $key, $value); + if ($result && !$this->isEmptyValue($fieldInfos, $value)) { + $optionParams = $this->getOptionsParams($fieldInfos->options); + $classname = $optionParams['objectClass']; + $classpath = $optionParams['pathToClass']; + $object = $this->getObject($classname, $classpath); + if (isset($object) && method_exists($object, 'isExistingObject') && !self::$validator->isFetchable((int) $value, $classname, $classpath) // All class don't have isExistingObject function ... + && (version_compare(DOL_VERSION, '19.0.0') < 0 || !self::$validator->isFetchableElement((int) $value, $classname)) // from V19 of Dolibarr, In some cases link use element instead of class, example project_task + ) { + return false; + } + + $result = true; + } + + return $result; + } + + /** + * Verify if the field value from GET/POST is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return bool + * @see self::printInputField() + */ + public function verifyPostFieldValue($fieldInfos, $key, $keyPrefix = '', $keySuffix = '') + { + return parent::verifyPostFieldValue($fieldInfos, $key, $keyPrefix, $keySuffix); + } + + /** + * Get field value from GET/POST + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $defaultValue Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return mixed + * @see self::printInputField() + */ + public function getPostFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $value = GETPOSTINT($htmlName); + } else { + $value = $defaultValue; + } + + return $value; + } + + /** + * Get search field value from GET/POST + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $defaultValue Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return mixed + * @see self::printInputSearchField() + */ + public function getPostSearchFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $value = GETPOST($htmlName, 'alphanohtml'); + } else { + $value = $defaultValue; + } + + return $value; + } + + /** + * Get sql filter for search field + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @return string + * @see self::printInputSearchField(), self::getPostSearchFieldValue() + */ + public function sqlFilterSearchField($fieldInfos, $key, $value) + { + if (!$this->isEmptyValue($fieldInfos, $value)) { + $alias = $fieldInfos->sqlAlias ?? 't.'; + + return natural_search($alias . ($fieldInfos->nameInTable ?? $key), $value, 2); + } + + return ''; + } + + /** + * Get all parameters in the options + * + * @param array $options Options of the field + * @return array{all:string,objectClass:string,pathToClass:string,addCreateButton:bool,getNomUrlParam1:string,getNomUrlParam2:string,filter:string,sortField:string} + */ + public function getOptionsParams($options) + { + $options = is_array($options) ? $options : array(); + $paramList = array_keys($options); + // Example: $paramList[0] = 'ObjectClass:PathToClass[:AddCreateButtonOrNot[:Filter[:Sortfield]]]' + // Example: $paramList[0] = 'ObjectClass:PathToClass:#getnomurlparam1=-1#getnomurlparam2=customer' + // Example: $paramList[0] = 'ObjectName:classPath' but can also be 'ObjectName:classPath:1:(status:=:1)' + + $all = (string) $paramList[0]; + $InfoFieldList = explode(":", $all); + + $objectClass = (string) ($InfoFieldList[0] ?? ''); + $pathToClass = (string) ($InfoFieldList[1] ?? ''); + $addCreateButton = !empty($InfoFieldList[2]) && is_numeric($InfoFieldList[2]); + $getNomUrlParam1 = 3; + $getNomUrlParam2 = ''; + if (preg_match('/#getnomurlparam1=([^#:]*)/', $all, $matches)) { + $getNomUrlParam1 = $matches[1]; + } + if (preg_match('/#getnomurlparam2=([^#:]*)/', $all, $matches)) { + $getNomUrlParam2 = $matches[1]; + } + $filter = (string) ($InfoFieldList[3] ?? ''); + $sortField = (string) ($InfoFieldList[4] ?? ''); + + return array( + 'all' => $all, + 'objectClass' => $objectClass, + 'pathToClass' => $pathToClass, + 'addCreateButton' => $addCreateButton, + 'getNomUrlParam1' => $getNomUrlParam1, + 'getNomUrlParam2' => $getNomUrlParam2, + 'filter' => $filter, + 'sortField' => $sortField, + ); + } + + /** + * Get object handler + * + * @param string $objectClass Class name + * @param string $pathToClass Path to the class + * @return CommonObject|null + */ + public function getObject($objectClass, $pathToClass) + { + dol_include_once($pathToClass); + if ($objectClass && !class_exists($objectClass)) { + // from V19 of Dolibarr, In some cases link use element instead of class, example project_task + // TODO use newObjectByElement() introduce in V20 by PR #30036 for better errors management + $element_prop = getElementProperties($objectClass); + if ($element_prop) { + $objectClass = $element_prop['classname']; + } + } + + if ($objectClass && class_exists($objectClass)) { + return new $objectClass($this->db); + } + + return null; + } +} diff --git a/htdocs/core/class/fields/multiptsfield.class.php b/htdocs/core/class/fields/multiptsfield.class.php new file mode 100644 index 00000000000..4cceca7da80 --- /dev/null +++ b/htdocs/core/class/fields/multiptsfield.class.php @@ -0,0 +1,32 @@ + + * + * 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/fields/multiptsfield.class.php + * \ingroup core + * \brief File of class to multipts field + */ + +require_once DOL_DOCUMENT_ROOT . '/core/class/fields/commongeofield.class.php'; + + +/** + * Class to multipts field + */ +class MultiptsField extends CommonGeoField +{ +} diff --git a/htdocs/core/class/fields/passwordfield.class.php b/htdocs/core/class/fields/passwordfield.class.php new file mode 100644 index 00000000000..5d555fb01f5 --- /dev/null +++ b/htdocs/core/class/fields/passwordfield.class.php @@ -0,0 +1,249 @@ + + * + * 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/fields/passwordfield.class.php + * \ingroup core + * \brief File of class to password field + */ + +require_once DOL_DOCUMENT_ROOT . '/core/class/fields/commonfield.class.php'; + + +/** + * Class to password field + */ +class PasswordField extends CommonField +{ + /** + * @var array List of value deemed as empty (null always deemed as empty) + */ + public $emptyValues = array(''); + + + /** + * Return HTML string to put an input search field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printInputSearchField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $htmlName = $keyPrefix . $key . $keySuffix; + + return self::$form->inputType('text', $htmlName, (string) $value, $htmlName, $moreCss, $moreAttrib); + } + + /** + * Return HTML string to put an input field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printInputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $moreAttrib = trim((string) $moreAttrib); + if (empty($moreAttrib)) $moreAttrib = ' ' . $moreAttrib; + $autoFocus = $fieldInfos->inputAutofocus ? ' autofocus' : ''; + $htmlName = $keyPrefix . $key . $keySuffix; + + $out = ''; // Hidden field to reduce impact of evil Google Chrome autopopulate bug. + if ($htmlName == 'pass_crypted') { + $out .= self::$form->inputType('password', 'pass', '', 'pass', $moreCss, ' autocomplete="new-password"' . $moreAttrib . $autoFocus); + $out .= self::$form->inputType('hidden', 'pass_crypted', (string) $value, 'pass_crypted', $moreCss, $moreAttrib); + } else { + $out .= self::$form->inputType('password', $htmlName, (string) $value, $htmlName, $moreCss, ' autocomplete="new-password"' . $moreAttrib . $autoFocus); + } + + return $out; + } + + /** + * Return HTML string to show a field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printOutputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + global $langs; + + return !$this->isEmptyValue($fieldInfos, $value) ? '' . $langs->trans("Encrypted") . '' : ''; + } + + /** + * Get input CSS + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $moreCss Value for css to define style/length of field. + * @param string $defaultCss Default value for css to define style/length of field. + * @return string + * @see self::printInputSearchField(), self::printInputField() + */ + public function getInputCss($fieldInfos, $moreCss = '', $defaultCss = '') + { + return parent::getInputCss($fieldInfos, $moreCss, $defaultCss ? $defaultCss : 'maxwidth100'); + } + + /** + * Verify if the field value is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Value to check (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @return bool + * @see self::printInputField() + */ + public function verifyFieldValue($fieldInfos, $key, $value) + { + global $conf, $langs, $user; + + $result = parent::verifyFieldValue($fieldInfos, $key, $value); + if ($result && !$this->isEmptyValue($fieldInfos, $value)) { + // Todo do we use other method ? + if (getDolGlobalString('USER_PASSWORD_GENERATED')) { + // Add a check on rules for password syntax using the setup of the password generator + $modGeneratePassClass = 'modGeneratePass' . ucfirst(getDolGlobalString('USER_PASSWORD_GENERATED')); + + include_once DOL_DOCUMENT_ROOT . '/core/modules/security/generate/' . $modGeneratePassClass . '.class.php'; + if (class_exists($modGeneratePassClass)) { + $modGeneratePass = new $modGeneratePassClass($this->db, $conf, $langs, $user); + '@phan-var-force ModeleGenPassword $modGeneratePass'; + + // To check an input user password, we disable the cleaning on ambiguous characters (this is used only for auto-generated password) + $modGeneratePass->WithoutAmbi = 0; + + // Call to validatePassword($password) to check pass match rules + $testpassword = $modGeneratePass->validatePassword($value); + if (!$testpassword) { + self::$validator->error = $langs->trans('RequireValidValue'); + return false; + } + } + } + + $result = true; + } + + return $result; + } + + /** + * Verify if the field value from GET/POST is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return bool + * @see self::printInputField() + */ + public function verifyPostFieldValue($fieldInfos, $key, $keyPrefix = '', $keySuffix = '') + { + return parent::verifyPostFieldValue($fieldInfos, $key, $keyPrefix, $keySuffix); + } + + /** + * Get field value from GET/POST + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $defaultValue Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return mixed + * @see self::printInputField() + */ + public function getPostFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $value = GETPOST($htmlName, 'password'); + } else { + $value = $defaultValue; + } + + return $value; + } + + + /** + * Get search field value from GET/POST + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $defaultValue Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return mixed + * @see self::printInputSearchField() + */ + public function getPostSearchFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $value = GETPOST($htmlName, 'alpha'); + } else { + $value = $defaultValue; + } + + return $value; + } + + /** + * Get sql filter for search field + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @return string + * @see self::printInputSearchField(), self::getPostSearchFieldValue() + */ + public function sqlFilterSearchField($fieldInfos, $key, $value) + { + if (!$this->isEmptyValue($fieldInfos, $value)) { + $alias = $fieldInfos->sqlAlias ?? 't.'; + + // TODO rework search on crypt password + return natural_search($alias . ($fieldInfos->nameInTable ?? $key), $value, 0); + } + + return ''; + } +} diff --git a/htdocs/core/class/fields/phonefield.class.php b/htdocs/core/class/fields/phonefield.class.php new file mode 100644 index 00000000000..a02e6ce6252 --- /dev/null +++ b/htdocs/core/class/fields/phonefield.class.php @@ -0,0 +1,217 @@ + + * + * 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/fields/phonefield.class.php + * \ingroup core + * \brief File of class to phone field + */ + +require_once DOL_DOCUMENT_ROOT . '/core/class/fields/commonfield.class.php'; + + +/** + * Class to phone field + */ +class PhoneField extends CommonField +{ + /** + * @var array List of value deemed as empty (null always deemed as empty) + */ + public $emptyValues = array(''); + + + /** + * Return HTML string to put an input search field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printInputSearchField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $htmlName = $keyPrefix . $key . $keySuffix; + + return self::$form->inputType('text', $htmlName, (string) $value, $htmlName, $moreCss, $moreAttrib); + } + + /** + * Return HTML string to put an input field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printInputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $moreAttrib = trim((string) $moreAttrib); + if (empty($moreAttrib)) $moreAttrib = ' ' . $moreAttrib; + $autoFocus = $fieldInfos->inputAutofocus ? ' autofocus' : ''; + $htmlName = $keyPrefix . $key . $keySuffix; + + return self::$form->inputType('text', $htmlName, (string) $value, $htmlName, $moreCss, $moreAttrib . $autoFocus); + } + + /** + * Return HTML string to show a field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printOutputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + return !$this->isEmptyValue($fieldInfos, $value) ? dol_print_phone((string) $value, '', 0, 0, '', ' ', 'phone') : ''; + } + + /** + * Get input CSS + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $moreCss Value for css to define style/length of field. + * @param string $defaultCss Default value for css to define style/length of field. + * @return string + * @see self::printInputSearchField(), self::printInputField() + */ + public function getInputCss($fieldInfos, $moreCss = '', $defaultCss = '') + { + return parent::getInputCss($fieldInfos, $moreCss, $defaultCss); + } + + /** + * Verify if the field value is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Value to check (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @return bool + * @see self::printInputField() + */ + public function verifyFieldValue($fieldInfos, $key, $value) + { + $result = parent::verifyFieldValue($fieldInfos, $key, $value); + if ($result && !$this->isEmptyValue($fieldInfos, $value)) { + if (!self::$validator->isPhone($value)) { + return false; + } + + $result = true; + } + + return $result; + } + + /** + * Verify if the field value from GET/POST is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return bool + * @see self::printInputField() + */ + public function verifyPostFieldValue($fieldInfos, $key, $keyPrefix = '', $keySuffix = '') + { + return parent::verifyPostFieldValue($fieldInfos, $key, $keyPrefix, $keySuffix); + } + + /** + * Get field value from GET/POST + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $defaultValue Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return mixed + * @see self::printInputField() + */ + public function getPostFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $value = GETPOST($htmlName, 'alphanohtml'); + } else { + $value = $defaultValue; + } + + return $value; + } + + /** + * Get search field value from GET/POST + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $defaultValue Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return mixed + * @see self::printInputSearchField() + */ + public function getPostSearchFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $value = GETPOST($htmlName, 'alpha'); + } else { + $value = $defaultValue; + } + + return $value; + } + + /** + * Get sql filter for search field + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @return string + * @see self::printInputSearchField(), self::getPostSearchFieldValue() + */ + public function sqlFilterSearchField($fieldInfos, $key, $value) + { + if (!$this->isEmptyValue($fieldInfos, $value)) { + $alias = $fieldInfos->sqlAlias ?? 't.'; + + return natural_search($alias . ($fieldInfos->nameInTable ?? $key), $value, 0); + } + + return ''; + } +} diff --git a/htdocs/core/class/fields/pointfield.class.php b/htdocs/core/class/fields/pointfield.class.php new file mode 100644 index 00000000000..cecf6087507 --- /dev/null +++ b/htdocs/core/class/fields/pointfield.class.php @@ -0,0 +1,32 @@ + + * + * 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/fields/pointfield.class.php + * \ingroup core + * \brief File of class to point field + */ + +require_once DOL_DOCUMENT_ROOT . '/core/class/fields/commongeofield.class.php'; + + +/** + * Class to point field + */ +class PointField extends CommonGeoField +{ +} diff --git a/htdocs/core/class/fields/polygonfield.class.php b/htdocs/core/class/fields/polygonfield.class.php new file mode 100644 index 00000000000..dd067293ea6 --- /dev/null +++ b/htdocs/core/class/fields/polygonfield.class.php @@ -0,0 +1,32 @@ + + * + * 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/fields/polygonfield.class.php + * \ingroup core + * \brief File of class to polygon field + */ + +require_once DOL_DOCUMENT_ROOT . '/core/class/fields/commongeofield.class.php'; + + +/** + * Class to polygon field + */ +class PolygonField extends CommonGeoField +{ +} diff --git a/htdocs/core/class/fields/pricecyfield.class.php b/htdocs/core/class/fields/pricecyfield.class.php new file mode 100644 index 00000000000..1e64f20ffff --- /dev/null +++ b/htdocs/core/class/fields/pricecyfield.class.php @@ -0,0 +1,316 @@ + + * + * 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/fields/pricecyfield.class.php + * \ingroup core + * \brief File of class to pricecy field (price with currency) + */ + +require_once DOL_DOCUMENT_ROOT . '/core/class/fields/commonfield.class.php'; + + +/** + * Class to pricecy field (price with currency) + */ +class PricecyField extends CommonField +{ + /** + * @var array List of value deemed as empty (null always deemed as empty) + */ + public $emptyValues = array(''); + + + /** + * Return HTML string to put an input search field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printInputSearchField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $htmlName = $keyPrefix . $key . $keySuffix; + $tmp = $this->getPriceAndCurrencyFromValue($fieldInfos, $value); + $value = $tmp['price']; + $currency = $tmp['currency']; + + $tmp = $this->getPriceAndCurrencyAliasAndField($fieldInfos, $key); + $aliasPrice = $tmp['aliasPrice']; + $fieldPrice = $tmp['fieldPrice']; + $aliasCurrency = $tmp['aliasCurrency']; + $fieldCurrency = $tmp['fieldCurrency']; + + $out = self::$form->inputType('text', $htmlName, (string) $value, $htmlName, $moreCss, $moreAttrib); + if (!empty($fieldCurrency)) { + $out .= self::$form->selectCurrency($currency, $htmlName . 'currency_id'); + } + + return $out; + } + + /** + * Return HTML string to put an input field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printInputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $autoFocus = $fieldInfos->inputAutofocus ? ' autofocus' : ''; + $htmlName = $keyPrefix . $key . $keySuffix; + $tmp = $this->getPriceAndCurrencyFromValue($fieldInfos, $value); + $value = $tmp['price']; + $currency = $tmp['currency']; + $value = !$this->isEmptyValue($fieldInfos, $value) ? price($value) : ''; + + $out = self::$form->inputType('text', $htmlName, (string) $value, $htmlName, $moreCss, $moreAttrib . $autoFocus); + $out .= self::$form->selectCurrency($currency, $htmlName . 'currency_id'); + + return $out; + } + + /** + * Return HTML string to show a field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printOutputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + global $langs; + $tmp = $this->getPriceAndCurrencyFromValue($fieldInfos, $value); + $value = $tmp['price']; + $currency = $tmp['currency']; + + return !$this->isEmptyValue($fieldInfos, $value) ? price($value, 0, $langs, 0, getDolGlobalInt('MAIN_MAX_DECIMALS_TOT'), -1, $currency) : ''; + } + + /** + * Get input CSS + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $moreCss Value for css to define style/length of field. + * @param string $defaultCss Default value for css to define style/length of field. + * @return string + * @see self::printInputSearchField(), self::printInputField() + */ + public function getInputCss($fieldInfos, $moreCss = '', $defaultCss = '') + { + return parent::getInputCss($fieldInfos, $moreCss, $defaultCss ? $defaultCss : 'maxwidth75'); + } + + /** + * Verify if the field value is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Value to check (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @return bool + * @see self::printInputField() + */ + public function verifyFieldValue($fieldInfos, $key, $value) + { + $result = parent::verifyFieldValue($fieldInfos, $key, $value); + if ($result && !$this->isEmptyValue($fieldInfos, $value)) { + $tmp = $this->getPriceAndCurrencyFromValue($fieldInfos, $value); + $value = $tmp['price']; + $currency = $tmp['currency']; + if (!self::$validator->isNumeric($value)) { + return false; + } + + $result = true; + } + + return $result; + } + + /** + * Verify if the field value from GET/POST is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return bool + * @see self::printInputField() + */ + public function verifyPostFieldValue($fieldInfos, $key, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + $value = GETPOST($htmlName, 'restricthtml') . ':' . GETPOST($htmlName . "currency_id", 'restricthtml'); + $value = str_replace(',', '.', $value); + + return $this->verifyFieldValue($fieldInfos, $key, $value); + } + + /** + * Get field value from GET/POST + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $defaultValue Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return mixed + * @see self::printInputField() + */ + public function getPostFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $value = price2num(GETPOST($htmlName, 'alphanohtml')) . ':' . GETPOST($htmlName . "currency_id", 'alpha'); + } else { + $value = $defaultValue; + } + + return $value; + } + + /** + * Get search field value from GET/POST + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $defaultValue Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return mixed + * @see self::printInputSearchField() + */ + public function getPostSearchFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $value = array( + 'value' => GETPOST($htmlName, 'alphanohtml'), + 'currency' => GETPOST($htmlName . "currency_id", 'alpha'), + ); + } else { + $value = $defaultValue; + } + + return $value; + } + + /** + * Get sql filter for search field + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @return string + * @see self::printInputSearchField(), self::getPostSearchFieldValue() + */ + public function sqlFilterSearchField($fieldInfos, $key, $value) + { + $filterValue = $value['value'] ?? ''; + $filterCurrency = $value['currency'] ?? ''; + if ($filterCurrency == '-1') $filterCurrency = ''; + + $tmp = $this->getPriceAndCurrencyAliasAndField($fieldInfos, $key); + $aliasPrice = $tmp['aliasPrice']; + $fieldPrice = $tmp['fieldPrice']; + $aliasCurrency = $tmp['aliasCurrency']; + $fieldCurrency = $tmp['fieldCurrency']; + + if (!empty($filterValue)) { + return natural_search($aliasPrice . $fieldPrice, $filterValue, 1); + } + if (!empty($filterCurrency) && !empty($fieldCurrency)) { + return natural_search($aliasCurrency . $fieldCurrency, $filterCurrency, 0); + } + + return ''; + } + + /** + * Get price and currency from value + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $value Value in memory is a php string like '0.01:EUR' + * @return array{price:double,currency:string} + */ + public function getPriceAndCurrencyFromValue($fieldInfos, $value) + { + global $conf; + + if ($this->isEmptyValue($fieldInfos, $value)) { + $price = ''; + $currency = $conf->currency; + } else { + // $value in memory is a php string like '10.01:USD' + $tmp = explode(':', $value); + $price = $this->isEmptyValue($fieldInfos, $tmp[0] ?? '') ? '' : $tmp[0]; + $currency = !empty($tmp[1]) ? $tmp[1] : $conf->currency; + } + + return array( + 'price' => (double) $price, + 'currency' => $currency + ); + } + + /** + * Get alias and field name in table for price and currency + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @return array{aliasPrice:string,aliasCurrency:string,fieldPrice:string,fieldCurrency:string} + */ + public function getPriceAndCurrencyAliasAndField($fieldInfos, $key) + { + $alias = $fieldInfos->sqlAlias ?? 't.'; + $tmp = explode(':', $alias); + $aliasPrice = $tmp[0] ?? ''; + $aliasCurrency = $tmp[1] ?? ''; + + $field = $fieldInfos->nameInTable ?? $key; + $tmp = explode(':', $field); + $fieldPrice = $tmp[0] ?? ''; + $fieldCurrency = $tmp[1] ?? ''; + + return array( + 'aliasPrice' => trim($aliasPrice), + 'aliasCurrency' => trim($aliasCurrency), + 'fieldPrice' => trim($fieldPrice), + 'fieldCurrency' => trim($fieldCurrency), + ); + } +} diff --git a/htdocs/core/class/fields/pricefield.class.php b/htdocs/core/class/fields/pricefield.class.php new file mode 100644 index 00000000000..8d848b92348 --- /dev/null +++ b/htdocs/core/class/fields/pricefield.class.php @@ -0,0 +1,224 @@ + + * + * 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/fields/pricefield.class.php + * \ingroup core + * \brief File of class to price field + */ + +require_once DOL_DOCUMENT_ROOT . '/core/class/fields/commonfield.class.php'; + + +/** + * Class to price field + */ +class PriceField extends CommonField +{ + /** + * @var array List of value deemed as empty (null always deemed as empty) + */ + public $emptyValues = array(''); + + + /** + * Return HTML string to put an input search field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printInputSearchField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $htmlName = $keyPrefix . $key . $keySuffix; + + return self::$form->inputType('text', $htmlName, (string) $value, $htmlName, $moreCss, $moreAttrib); + } + + /** + * Return HTML string to put an input field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printInputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + global $conf, $langs; + + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $autoFocus = $fieldInfos->inputAutofocus ? ' autofocus' : ''; + $value = !$this->isEmptyValue($fieldInfos, $value) ? price((double) $value) : ''; + $htmlName = $keyPrefix . $key . $keySuffix; + + return self::$form->inputType('text', $htmlName, (string) $value, $htmlName, $moreCss, $moreAttrib . $autoFocus) . $langs->getCurrencySymbol($conf->currency); + } + + /** + * Return HTML string to show a field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printOutputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + global $conf, $langs; + + return !$this->isEmptyValue($fieldInfos, $value) ? price((double) $value, 0, $langs, 0, 0, -1, $conf->currency) : ''; + } + + /** + * Get input CSS + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $moreCss Value for css to define style/length of field. + * @param string $defaultCss Default value for css to define style/length of field. + * @return string + * @see self::printInputSearchField(), self::printInputField() + */ + public function getInputCss($fieldInfos, $moreCss = '', $defaultCss = '') + { + return parent::getInputCss($fieldInfos, $moreCss, $defaultCss ? $defaultCss : 'maxwidth75'); + } + + /** + * Verify if the field value is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Value to check (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @return bool + * @see self::printInputField() + */ + public function verifyFieldValue($fieldInfos, $key, $value) + { + $result = parent::verifyFieldValue($fieldInfos, $key, $value); + if ($result && !$this->isEmptyValue($fieldInfos, $value)) { + if (!self::$validator->isNumeric($value)) { + return false; + } + + $result = true; + } + + return $result; + } + + /** + * Verify if the field value from GET/POST is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return bool + * @see self::printInputField() + */ + public function verifyPostFieldValue($fieldInfos, $key, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + $value = GETPOST($htmlName, 'restricthtml'); + $value = str_replace(',', '.', $value); + + return $this->verifyFieldValue($fieldInfos, $key, $value); + } + + /** + * Get field value from GET/POST + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $defaultValue Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return mixed + * @see self::printInputField() + */ + public function getPostFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $value = (double) price2num(GETPOST($htmlName, 'alphanohtml')); + } else { + $value = $defaultValue; + } + + return $value; + } + + /** + * Get search field value from GET/POST + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $defaultValue Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return mixed + * @see self::printInputSearchField() + */ + public function getPostSearchFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $value = GETPOST($htmlName, 'alpha'); + } else { + $value = $defaultValue; + } + + return $value; + } + + /** + * Get sql filter for search field + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @return string + * @see self::printInputSearchField(), self::getPostSearchFieldValue() + */ + public function sqlFilterSearchField($fieldInfos, $key, $value) + { + if (!$this->isEmptyValue($fieldInfos, $value)) { + $alias = $fieldInfos->sqlAlias ?? 't.'; + + return natural_search($alias . ($fieldInfos->nameInTable ?? $key), $value, 1); + } + + return ''; + } +} diff --git a/htdocs/core/class/fields/radiofield.class.php b/htdocs/core/class/fields/radiofield.class.php new file mode 100644 index 00000000000..ca7ee2e7af4 --- /dev/null +++ b/htdocs/core/class/fields/radiofield.class.php @@ -0,0 +1,276 @@ + + * + * 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/fields/radiofield.class.php + * \ingroup core + * \brief File of class to radio field + */ + +require_once DOL_DOCUMENT_ROOT . '/core/class/fields/commonselectfield.class.php'; + + +/** + * Class to radio field + */ +class RadioField extends CommonSelectField +{ + /** + * @var array List of value deemed as empty (null always deemed as empty) + */ + public $emptyValues = array(''); + + + /** + * Return HTML string to put an input search field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printInputSearchField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + global $conf; + + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $moreAttrib = trim((string) $moreAttrib); + if (empty($moreAttrib)) $moreAttrib = ' ' . $moreAttrib; + $htmlName = $keyPrefix . $key . $keySuffix; + $value = $this->isEmptyValue($fieldInfos, $value) ? array() : (is_string($value) ? explode(',', $value) : (is_array($value) ? $value: array($value))); + + $optionsList = array(); + $options = $this->getOptions($fieldInfos, $key); + foreach ($options as $optionKey => $optionInfos) { + $options[$optionKey] = $optionInfos['label']; + } + + return self::$form->multiselectarray($htmlName, $optionsList, $value, 0, 0, $moreCss, 0, 0, $moreAttrib, '', '', (int) (!empty($conf->use_javascript_ajax) && !getDolGlobalString('MAIN_EXTRAFIELDS_DISABLE_SELECT2'))); + } + + /** + * Return HTML string to put an input field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printInputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $moreAttrib = trim((string) $moreAttrib); + if (empty($moreAttrib)) $moreAttrib = ' ' . $moreAttrib; + $htmlName = $keyPrefix . $key . $keySuffix; + + $selectedValue = $this->isEmptyValue($fieldInfos, $value) ? '' : (string) $value; + + $options = $this->getOptions($fieldInfos, $key, true); + $values = array(); + foreach ($options as $optionKey => $optionInfos) { + $values[$optionKey] = $optionInfos['label']; + } + + return self::$form->inputRadio($htmlName, $values, $selectedValue, $moreCss, $moreAttrib); + } + + /** + * Return HTML string to show a field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printOutputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + global $langs; + $value = (string) $value; + + if ($fieldInfos->required && $this->isEmptyValue($fieldInfos, $value)) { + $langs->load('errors'); + $value = '' . $langs->trans('ErrorNoValueForRadioType') . ''; + } else { + $options = $this->getOptions($fieldInfos, $key); + foreach ($options as $optionKey => $optionInfos) { + if (((string) $optionKey) == $value) { + $value = $optionInfos['label']; + break; + } + } + } + + return $value; + } + + /** + * Get input CSS + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $moreCss Value for css to define style/length of field. + * @param string $defaultCss Default value for css to define style/length of field. + * @return string + * @see self::printInputSearchField(), self::printInputField() + */ + public function getInputCss($fieldInfos, $moreCss = '', $defaultCss = '') + { + return parent::getInputCss($fieldInfos, $moreCss, $defaultCss ? $defaultCss : 'width25'); + } + + /** + * Verify if the field value is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Value to check (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @return bool + * @see self::printInputField() + */ + public function verifyFieldValue($fieldInfos, $key, $value) + { + global $langs; + $value = (string) $value; + + $result = parent::verifyFieldValue($fieldInfos, $key, $value); + if ($result && !$this->isEmptyValue($fieldInfos, $value)) { + $options = $this->getOptions($fieldInfos, $key); + if (!isset($options[$value])) { + self::$validator->error = $langs->trans('RequireValidValue'); + return false; + } + + $result = true; + } + + return $result; + } + + /** + * Verify if the field value from GET/POST is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return bool + * @see self::printInputField() + */ + public function verifyPostFieldValue($fieldInfos, $key, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + $value = GETPOST($htmlName, 'restricthtml'); + $value = trim($value); + + return $this->verifyFieldValue($fieldInfos, $key, $value); + } + + /** + * Get field value from GET/POST + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $defaultValue Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return mixed + * @see self::printInputField() + */ + public function getPostFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $value = GETPOST($htmlName, 'alphanohtml'); + } else { + $value = $defaultValue; + } + + return $value; + } + + /** + * Get search field value from GET/POST + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $defaultValue Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return mixed + * @see self::printInputSearchField() + */ + public function getPostSearchFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $value = GETPOST($htmlName, 'array'); + } else { + $value = $defaultValue; + } + + return $value; + } + + /** + * Get sql filter for search field + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @return string + * @see self::printInputSearchField(), self::getPostSearchFieldValue() + */ + public function sqlFilterSearchField($fieldInfos, $key, $value) + { + if (!$this->isEmptyValue($fieldInfos, $value) && is_array($value)) { + $alias = $fieldInfos->sqlAlias ?? 't.'; + $field = $this->db->sanitize($alias . ($fieldInfos->nameInTable ?? $key)); + + $tmp = "'" . implode("','", array_map(array($this->db, 'escape'), $value)) . "'"; + return " AND " . $field . " IN (" . $this->db->sanitize($tmp, 1) . ")"; + } + + return ''; + } + + /** + * Get list of options + * + * @param FieldInfos $fieldInfos Array of properties for field to show + * @param string $key Key of field + * @param bool $addEmptyValue Add also empty value if needed + * @param bool $reload Force reload options + * @return array + */ + public function getOptions($fieldInfos, $key, $addEmptyValue = false, $reload = false) + { + return parent::getOptions($fieldInfos, $key, $addEmptyValue, $reload); + } +} diff --git a/htdocs/core/class/fields/realfield.class.php b/htdocs/core/class/fields/realfield.class.php new file mode 100644 index 00000000000..0e3707e31fb --- /dev/null +++ b/htdocs/core/class/fields/realfield.class.php @@ -0,0 +1,220 @@ + + * + * 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/fields/realfield.class.php + * \ingroup core + * \brief File of class to real field + */ + +require_once DOL_DOCUMENT_ROOT . '/core/class/fields/commonfield.class.php'; + + +/** + * Class to real field + */ +class RealField extends CommonField +{ + /** + * @var array List of value deemed as empty (null always deemed as empty) + */ + public $emptyValues = array(''); + + + /** + * Return HTML string to put an input search field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printInputSearchField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $htmlName = $keyPrefix . $key . $keySuffix; + + return self::$form->inputType('text', $htmlName, (string) $value, $htmlName, $moreCss, $moreAttrib); + } + + /** + * Return HTML string to put an input field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printInputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $autoFocus = $fieldInfos->inputAutofocus ? ' autofocus' : ''; + $value = !$this->isEmptyValue($fieldInfos, $value) ? price((double) $value) : ''; + $htmlName = $keyPrefix . $key . $keySuffix; + + return self::$form->inputType('text', $htmlName, (string) $value, $htmlName, $moreCss, $moreAttrib . $autoFocus); + } + + /** + * Return HTML string to show a field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printOutputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + return !$this->isEmptyValue($fieldInfos, $value) ? price((double) $value) : ''; + } + + /** + * Get input CSS + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $moreCss Value for css to define style/length of field. + * @param string $defaultCss Default value for css to define style/length of field. + * @return string + * @see self::printInputSearchField(), self::printInputField() + */ + public function getInputCss($fieldInfos, $moreCss = '', $defaultCss = '') + { + return parent::getInputCss($fieldInfos, $moreCss, $defaultCss ? $defaultCss : 'maxwidth75'); + } + + /** + * Verify if the field value is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Value to check (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @return bool + * @see self::printInputField() + */ + public function verifyFieldValue($fieldInfos, $key, $value) + { + $result = parent::verifyFieldValue($fieldInfos, $key, $value); + if ($result && !$this->isEmptyValue($fieldInfos, $value)) { + if (!self::$validator->isNumeric($value)) { + return false; + } + + $result = true; + } + + return $result; + } + + /** + * Verify if the field value from GET/POST is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return bool + * @see self::printInputField() + */ + public function verifyPostFieldValue($fieldInfos, $key, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + $value = GETPOST($htmlName, 'restricthtml'); + $value = str_replace(',', '.', $value); + + return $this->verifyFieldValue($fieldInfos, $key, $value); + } + + /** + * Get field value from GET/POST + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $defaultValue Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return mixed + * @see self::printInputField() + */ + public function getPostFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $value = (double) price2num(GETPOST($htmlName, 'alphanohtml')); + } else { + $value = $defaultValue; + } + + return $value; + } + + /** + * Get search field value from GET/POST + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $defaultValue Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return mixed + * @see self::printInputSearchField() + */ + public function getPostSearchFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $value = GETPOST($htmlName, 'alpha'); + } else { + $value = $defaultValue; + } + + return $value; + } + + /** + * Get sql filter for search field + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @return string + * @see self::printInputSearchField(), self::getPostSearchFieldValue() + */ + public function sqlFilterSearchField($fieldInfos, $key, $value) + { + if (!$this->isEmptyValue($fieldInfos, $value)) { + $alias = $fieldInfos->sqlAlias ?? 't.'; + + return natural_search($alias . ($fieldInfos->nameInTable ?? $key), $value, 1); + } + + return ''; + } +} diff --git a/htdocs/core/class/fields/selectfield.class.php b/htdocs/core/class/fields/selectfield.class.php new file mode 100644 index 00000000000..45182fbcb64 --- /dev/null +++ b/htdocs/core/class/fields/selectfield.class.php @@ -0,0 +1,271 @@ + + * + * 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/fields/selectfield.class.php + * \ingroup core + * \brief File of class to select field + */ + +require_once DOL_DOCUMENT_ROOT . '/core/class/fields/commonselectfield.class.php'; + + +/** + * Class to select field + */ +class SelectField extends CommonSelectField +{ + /** + * @var array List of value deemed as empty (null always deemed as empty) + */ + public $emptyValues = array(''); + + + /** + * Return HTML string to put an input search field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printInputSearchField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + global $conf; + + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $moreAttrib = trim((string) $moreAttrib); + if (empty($moreAttrib)) $moreAttrib = ' ' . $moreAttrib; + $htmlName = $keyPrefix . $key . $keySuffix; + $value = $this->isEmptyValue($fieldInfos, $value) ? array() : (is_string($value) ? explode(',', $value) : (is_array($value) ? $value: array($value))); + + $optionsList = array(); + $options = $this->getOptions($fieldInfos, $key); + foreach ($options as $optionKey => $optionInfos) { + $options[$optionKey] = $optionInfos['label']; + } + + return self::$form->multiselectarray($htmlName, $optionsList, $value, 0, 0, $moreCss, 0, 0, $moreAttrib, '', '', (int) (!empty($conf->use_javascript_ajax) && !getDolGlobalString('MAIN_EXTRAFIELDS_DISABLE_SELECT2'))); + } + + /** + * Return HTML string to put an input field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printInputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $moreAttrib = trim((string) $moreAttrib); + if (empty($moreAttrib)) $moreAttrib = ' ' . $moreAttrib; + $placeHolder = $fieldInfos->inputPlaceholder; + if (!empty($placeHolder)) $placeHolder = ' placeholder="' . dolPrintHTMLForAttribute($placeHolder) . '"'; + $autoFocus = $fieldInfos->inputAutofocus ? ' autofocus' : ''; + $htmlName = $keyPrefix . $key . $keySuffix; + + $selectedValue = is_null($value) || $this->isEmptyValue($fieldInfos, $value) ? '' : (string) $value; + + $options = $this->getOptions($fieldInfos, $key, true); + + return self::$form->selectarray($htmlName, $options, $selectedValue, 0, 0, 0, $moreAttrib . $placeHolder . $autoFocus, 0, 0, 0, '', $moreCss); + } + + /** + * Return HTML string to show a field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printOutputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $value = (string) $value; + + if (!$this->isEmptyValue($fieldInfos, $value)) { + $options = $this->getOptions($fieldInfos, $key); + foreach ($options as $optionKey => $optionInfos) { + if (((string) $optionKey) == $value) { + $value = $optionInfos['label']; + break; + } + } + } + + return $value; + } + + /** + * Get input CSS + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $moreCss Value for css to define style/length of field. + * @param string $defaultCss Default value for css to define style/length of field. + * @return string + * @see self::printInputSearchField(), self::printInputField() + */ + public function getInputCss($fieldInfos, $moreCss = '', $defaultCss = '') + { + return parent::getInputCss($fieldInfos, $moreCss, $defaultCss ? $defaultCss : 'minwidth400'); + } + + /** + * Verify if the field value is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Value to check (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @return bool + * @see self::printInputField() + */ + public function verifyFieldValue($fieldInfos, $key, $value) + { + global $langs; + $value = (string) $value; + + $result = parent::verifyFieldValue($fieldInfos, $key, $value); + if ($result && !$this->isEmptyValue($fieldInfos, $value)) { + $options = $this->getOptions($fieldInfos, $key); + if (!isset($options[$value])) { + self::$validator->error = $langs->trans('RequireValidValue'); + return false; + } + + $result = true; + } + + return $result; + } + + /** + * Verify if the field value from GET/POST is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return bool + * @see self::printInputField() + */ + public function verifyPostFieldValue($fieldInfos, $key, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + $value = GETPOST($htmlName, 'restricthtml'); + $value = trim($value); + + return $this->verifyFieldValue($fieldInfos, $key, $value); + } + + /** + * Get field value from GET/POST + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $defaultValue Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return mixed + * @see self::printInputField() + */ + public function getPostFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $value = GETPOST($htmlName, 'alphanohtml'); + } else { + $value = $defaultValue; + } + + return $this->isEmptyValue($fieldInfos, $value) ? '' : $value; + } + + /** + * Get search field value from GET/POST + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $defaultValue Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return mixed + * @see self::printInputSearchField() + */ + public function getPostSearchFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $value = GETPOST($htmlName, 'array'); + } else { + $value = $defaultValue; + } + + return $value; + } + + /** + * Get sql filter for search field + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @return string + * @see self::printInputSearchField(), self::getPostSearchFieldValue() + */ + public function sqlFilterSearchField($fieldInfos, $key, $value) + { + if (!$this->isEmptyValue($fieldInfos, $value) && is_array($value)) { + $alias = $fieldInfos->sqlAlias ?? 't.'; + $field = $this->db->sanitize($alias . ($fieldInfos->nameInTable ?? $key)); + + $tmp = "'" . implode("','", array_map(array($this->db, 'escape'), $value)) . "'"; + return " AND " . $field . " IN (" . $this->db->sanitize($tmp, 1) . ")"; + } + + return ''; + } + + /** + * Get list of options + * + * @param FieldInfos $fieldInfos Array of properties for field to show + * @param string $key Key of field + * @param bool $addEmptyValue Add also empty value if needed + * @param bool $reload Force reload options + * @return array + */ + public function getOptions($fieldInfos, $key, $addEmptyValue = false, $reload = false) + { + return parent::getOptions($fieldInfos, $key, $addEmptyValue, $reload); + } +} diff --git a/htdocs/core/class/fields/sellistfield.class.php b/htdocs/core/class/fields/sellistfield.class.php new file mode 100644 index 00000000000..61e5ab09469 --- /dev/null +++ b/htdocs/core/class/fields/sellistfield.class.php @@ -0,0 +1,298 @@ + + * + * 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/fields/sellistfield.class.php + * \ingroup core + * \brief File of class to sellist field + */ + +require_once DOL_DOCUMENT_ROOT . '/core/class/fields/commonsellistfield.class.php'; + + +/** + * Class to sellist field + */ +class SellistField extends CommonSellistField +{ + /** + * @var array List of value deemed as empty (null always deemed as empty) + */ + public $emptyValues = array('', array()); + + + /** + * Return HTML string to put an input search field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printInputSearchField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + global $conf; + + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $moreAttrib = trim((string) $moreAttrib); + if (empty($moreAttrib)) $moreAttrib = ' ' . $moreAttrib; + $htmlName = $keyPrefix . $key . $keySuffix; + + $optionsList = array(); + $options = $this->getOptions($fieldInfos, $key); + foreach ($options as $optionKey => $optionInfos) { + $options[$optionKey] = $optionInfos['label']; + } + + return self::$form->multiselectarray($htmlName, $optionsList, $value, 0, 0, $moreCss, 0, 0, $moreAttrib, '', '', (int) (!empty($conf->use_javascript_ajax) && !getDolGlobalString('MAIN_EXTRAFIELDS_DISABLE_SELECT2'))); + } + + /** + * Return HTML string to put an input field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printInputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + global $conf; + + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $moreAttrib = trim((string) $moreAttrib); + if (empty($moreAttrib)) $moreAttrib = ' ' . $moreAttrib; + $placeHolder = $fieldInfos->inputPlaceholder; + if (!empty($placeHolder)) $placeHolder = ' placeholder="' . dolPrintHTMLForAttribute($placeHolder) . '"'; + $autoFocus = $fieldInfos->inputAutofocus ? ' autofocus' : ''; + $htmlName = $keyPrefix . $key . $keySuffix; + $selectedValue = is_null($value) || $this->isEmptyValue($fieldInfos, $value) ? '' : (string) $value; + + if (!empty($conf->use_javascript_ajax) && getDolGlobalString('MAIN_EXTRAFIELDS_ENABLE_NEW_SELECT2')) { + $objectId = isset($fieldInfos->otherParams['objectId']) ? (int) $fieldInfos->otherParams['objectId'] : (isset($fieldInfos->object) && is_object($fieldInfos->object) ? $fieldInfos->object->id : 0); + $objectType = isset($fieldInfos->otherParams['objectType']) ? (int) $fieldInfos->otherParams['objectType'] : (isset($fieldInfos->object) && is_object($fieldInfos->object) ? $fieldInfos->object->element : ''); + $printMode = empty($fieldInfos->mode) ? 'view' : $fieldInfos->mode; + $options = $this->getOptions($fieldInfos, $key, false, false, $selectedValue); + + // TODO add dependency support (set dependencyvalue with dependency field value get by JS) + $ajaxData = array( + 'objecttype' => $objectType, + 'objectid' => $objectId, + 'objectkey' => $key, + 'mode' => $printMode, + 'value' => $selectedValue, + 'dependencyvalue' => '', + ); + + $out = self::$form->inputSelectAjax($htmlName, $options, $selectedValue, self::$ajaxUrl, $ajaxData, $moreCss, $moreAttrib . $placeHolder . $autoFocus); + } else { + $options = $this->getOptions($fieldInfos, $key, true); + + $out = self::$form->selectarray($htmlName, $options, $selectedValue, 0, 0, 0, $moreAttrib . $placeHolder . $autoFocus, 0, 0, 0, '', $moreCss); + } + + return $out; + } + + /** + * Return HTML string to show a field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printOutputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $value = (string) $value; + + if (!$this->isEmptyValue($fieldInfos, $value)) { + $options = $this->getOptions($fieldInfos, $key, false, false, $value); + if (isset($options[$value])) { + $out = $options[$value]['label']; + + $optionParams = $this->getOptionsParams($fieldInfos->options); + if ($optionParams['tableName'] == 'categorie' && !empty($optionParams['categoryType'])) { + require_once DOL_DOCUMENT_ROOT . '/categories/class/categorie.class.php'; + $c = new Categorie($this->db); + $c->fetch((int) $value); + $color = ' style="background: #' . ($c->color ? $c->color : 'bbb') . ';"'; + $label = img_object('', 'category') . ' ' . $value; + $out = '
    • ' . $label . '
    '; + } + + $value = $out; + } + } + + return $value; + } + + /** + * Get input CSS + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $moreCss Value for css to define style/length of field. + * @param string $defaultCss Default value for css to define style/length of field. + * @return string + * @see self::printInputSearchField(), self::printInputField() + */ + public function getInputCss($fieldInfos, $moreCss = '', $defaultCss = '') + { + return parent::getInputCss($fieldInfos, $moreCss, $defaultCss ? $defaultCss : 'minwidth400'); + } + + /** + * Verify if the field value is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Value to check (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @return bool + * @see self::printInputField() + */ + public function verifyFieldValue($fieldInfos, $key, $value) + { + $result = parent::verifyFieldValue($fieldInfos, $key, $value); + if ($result && !$this->isEmptyValue($fieldInfos, $value)) { + $optionParams = $this->getOptionsParams($fieldInfos->options); + if (!self::$validator->isInDb($value, $optionParams['tableName'], $optionParams['keyField'])) { + return false; + } + + $result = true; + } + + return $result; + } + + /** + * Verify if the field value from GET/POST is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return bool + * @see self::printInputField() + */ + public function verifyPostFieldValue($fieldInfos, $key, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + $value = GETPOST($htmlName, 'restricthtml'); + $value = trim($value); + + return $this->verifyFieldValue($fieldInfos, $key, $value); + } + + /** + * Get field value from GET/POST + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $defaultValue Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return mixed + * @see self::printInputField() + */ + public function getPostFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $value = GETPOST($htmlName, 'alphanohtml'); + } else { + $value = $defaultValue; + } + + return $value; + } + + /** + * Get search field value from GET/POST + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $defaultValue Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return mixed + * @see self::printInputSearchField() + */ + public function getPostSearchFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $value = GETPOST($htmlName, 'array'); + } else { + $value = $defaultValue; + } + + return $value; + } + + /** + * Get sql filter for search field + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @return string + * @see self::printInputSearchField(), self::getPostSearchFieldValue() + */ + public function sqlFilterSearchField($fieldInfos, $key, $value) + { + if (!empty($value) && is_array($value)) { + $alias = $fieldInfos->sqlAlias ?? 't.'; + $field = $this->db->sanitize($alias . ($fieldInfos->nameInTable ?? $key)); + + $tmp = "'" . implode("','", array_map(array($this->db, 'escape'), $value)) . "'"; + return " AND " . $field . " IN (" . $this->db->sanitize($tmp, 1) . ")"; + } + + return ''; + } + + /** + * Get list of options + * + * @param FieldInfos $fieldInfos Array of properties for field to show + * @param string $key Key of field + * @param bool $addEmptyValue Add also empty value if needed + * @param bool $reload Force reload options + * @param string|array $selectedValues Only selected values + * @return array + */ + public function getOptions($fieldInfos, $key, $addEmptyValue = false, $reload = false, $selectedValues = array()) + { + return parent::getOptions($fieldInfos, $key, $addEmptyValue, $reload, $selectedValues); + } +} diff --git a/htdocs/core/class/fields/starsfield.class.php b/htdocs/core/class/fields/starsfield.class.php new file mode 100644 index 00000000000..37187235bed --- /dev/null +++ b/htdocs/core/class/fields/starsfield.class.php @@ -0,0 +1,242 @@ + + * + * 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/fields/starsfield.class.php + * \ingroup core + * \brief File of class to stars field + */ + +require_once DOL_DOCUMENT_ROOT . '/core/class/fields/commonfield.class.php'; + + +/** + * Class to stars field + */ +class StarsField extends CommonField +{ + /** + * @var array List of value deemed as empty (null always deemed as empty) + */ + public $emptyValues = array('', -1); + + + /** + * Return HTML string to put an input search field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printInputSearchField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + global $langs; + + $size = (int) $fieldInfos->size; + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $moreAttrib = trim((string) $moreAttrib); + if (empty($moreAttrib)) $moreAttrib = ' ' . $moreAttrib; + $value = (int) $value; + $htmlName = $keyPrefix . $key . $keySuffix; + + $options = array(); + for ($i = 0; $i <= $size; $i++) { + // TODO ajouter la tranduction + $options[$i] = $langs->trans("StarsFieldValue", $i); + } + + return self::$form->selectarray($htmlName, $options, $value, 1, 0, 0, $moreAttrib, 0, 0, 0, '', $moreCss); + } + + /** + * Return HTML string to put an input field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printInputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $size = (int) $fieldInfos->size; + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $moreAttrib = trim((string) $moreAttrib); + if (empty($moreAttrib)) $moreAttrib = ' ' . $moreAttrib; + $autoFocus = $fieldInfos->inputAutofocus ? ' autofocus' : ''; + $value = (int) $value; + $htmlName = $keyPrefix . $key . $keySuffix; + + return self::$form->inputStars($htmlName, $size, $value, $moreCss, $moreAttrib . $autoFocus); + } + + /** + * Return HTML string to show a field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printOutputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $size = (int) $fieldInfos->size; + $value = (int) $value; + + return self::$form->outputStars($size, $value); + } + + /** + * Get input CSS + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $moreCss Value for css to define style/length of field. + * @param string $defaultCss Default value for css to define style/length of field. + * @return string + * @see self::printInputSearchField(), self::printInputField() + */ + public function getInputCss($fieldInfos, $moreCss = '', $defaultCss = '') + { + return parent::getInputCss($fieldInfos, $moreCss, $defaultCss); + } + + /** + * Verify if the field value is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Value to check (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @return bool + * @see self::printInputField() + */ + public function verifyFieldValue($fieldInfos, $key, $value) + { + $fieldInfos->minValue = 0; + $fieldInfos->maxValue = (int) $fieldInfos->size; + + $result = parent::verifyFieldValue($fieldInfos, $key, $value); + if ($result && !$this->isEmptyValue($fieldInfos, $value)) { + if (!self::$validator->isNumeric($value)) { + return false; + } + + $result = true; + } + + return $result; + } + + /** + * Verify if the field value from GET/POST is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return bool + * @see self::printInputField() + */ + public function verifyPostFieldValue($fieldInfos, $key, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + return $this->verifyFieldValue($fieldInfos, $key, GETPOST($htmlName, 'restricthtml')); + } + + /** + * Get field value from GET/POST + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $defaultValue Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return mixed + * @see self::printInputField() + */ + public function getPostFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $value = GETPOSTINT($htmlName); + } else { + $value = $defaultValue; + } + + return $value; + } + + /** + * Get search field value from GET/POST + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $defaultValue Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return mixed + * @see self::printInputSearchField() + */ + public function getPostSearchFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $value = GETPOSTINT($htmlName); + } else { + $value = $defaultValue; + } + + return $value; + } + + /** + * Get sql filter for search field + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @return string + * @see self::printInputSearchField(), self::getPostSearchFieldValue() + */ + public function sqlFilterSearchField($fieldInfos, $key, $value) + { + if (!$this->isEmptyValue($fieldInfos, $value)) { + $alias = $fieldInfos->sqlAlias ?? 't.'; + $value = (int) $value; + + if ($value >= 0) { + return natural_search($alias . ($fieldInfos->nameInTable ?? $key), (string) $value, 1); + } + } + + return ''; + } +} diff --git a/htdocs/core/class/fields/textfield.class.php b/htdocs/core/class/fields/textfield.class.php new file mode 100644 index 00000000000..e588de8fcf5 --- /dev/null +++ b/htdocs/core/class/fields/textfield.class.php @@ -0,0 +1,229 @@ + + * + * 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/fields/textfield.class.php + * \ingroup core + * \brief File of class to text field + */ + +require_once DOL_DOCUMENT_ROOT . '/core/class/fields/commonfield.class.php'; +require_once DOL_DOCUMENT_ROOT . '/core/class/doleditor.class.php'; + + +/** + * Class to text field + */ +class TextField extends CommonField +{ + /** + * @var array List of value deemed as empty (null always deemed as empty) + */ + public $emptyValues = array(''); + + + /** + * Return HTML string to put an input search field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printInputSearchField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $htmlName = $keyPrefix . $key . $keySuffix; + + return self::$form->inputType('text', $htmlName, (string) $value, $htmlName, $moreCss, $moreAttrib); + } + + /** + * Return HTML string to put an input field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printInputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $moreAttrib = trim((string) $moreAttrib); + if (empty($moreAttrib)) $moreAttrib = ' ' . $moreAttrib; + $htmlName = $keyPrefix . $key . $keySuffix; + + return self::$form->inputText($htmlName, (string) $value, $moreCss, $moreAttrib, $fieldInfos->options); + } + + /** + * Return HTML string to show a field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printOutputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + if (!$this->isEmptyValue($fieldInfos, $value)) { + if (!empty($fieldInfos->options)) { + $value = str_replace(',', "\n", (string) $value); + } + + return dol_htmlentitiesbr((string) $value); + } + + return ''; + } + + /** + * Get input CSS + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $moreCss Value for css to define style/length of field. + * @param string $defaultCss Default value for css to define style/length of field. + * @return string + * @see self::printInputSearchField(), self::printInputField() + */ + public function getInputCss($fieldInfos, $moreCss = '', $defaultCss = '') + { + return parent::getInputCss($fieldInfos, $moreCss, $defaultCss); + } + + /** + * Verify if the field value is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Value to check (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @return bool + * @see self::printInputField() + */ + public function verifyFieldValue($fieldInfos, $key, $value) + { + return parent::verifyFieldValue($fieldInfos, $key, $value); + } + + /** + * Verify if the field value from GET/POST is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return bool + * @see self::printInputField() + */ + public function verifyPostFieldValue($fieldInfos, $key, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + $value = GETPOST($htmlName, 'restricthtml'); + + return $this->verifyFieldValue($fieldInfos, $key, $value); + } + + /** + * Get field value from GET/POST + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $defaultValue Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return mixed + * @see self::printInputField() + */ + public function getPostFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + if (!empty($fieldInfos->getPostCheck)) { + $value = GETPOST($htmlName, $fieldInfos->getPostCheck); + } else { + $value = GETPOST($htmlName, 'nohtml'); + if (!empty($fieldInfos->options) && !empty($fieldInfos->multiInput)) { + $tmpArrayMultiSelect = GETPOST($htmlName . '_multiselect', 'array'); + foreach ($tmpArrayMultiSelect as $tmpValue) { + $value .= (!empty($value) ? "," : "") . $tmpValue; + } + } + } + } else { + $value = $defaultValue; + } + + return $value; + } + + /** + * Get search field value from GET/POST + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $defaultValue Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return mixed + * @see self::printInputSearchField() + */ + public function getPostSearchFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $value = GETPOST($htmlName, 'alpha'); + } else { + $value = $defaultValue; + } + + return $value; + } + + /** + * Get sql filter for search field + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @return string + * @see self::printInputSearchField(), self::getPostSearchFieldValue() + */ + public function sqlFilterSearchField($fieldInfos, $key, $value) + { + if (!$this->isEmptyValue($fieldInfos, $value)) { + $alias = $fieldInfos->sqlAlias ?? 't.'; + + return natural_search($alias . ($fieldInfos->nameInTable ?? $key), $value, 0); + } + + return ''; + } +} diff --git a/htdocs/core/class/fields/timestampfield.class.php b/htdocs/core/class/fields/timestampfield.class.php new file mode 100644 index 00000000000..288c9254fcc --- /dev/null +++ b/htdocs/core/class/fields/timestampfield.class.php @@ -0,0 +1,258 @@ + + * + * 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/fields/timestampfield.class.php + * \ingroup core + * \brief File of class to timestamp field + */ + +require_once DOL_DOCUMENT_ROOT . '/core/class/fields/commonfield.class.php'; + + +/** + * Class to timestamp field + */ +class TimestampField extends CommonField +{ + /** + * @var array List of value deemed as empty (null always deemed as empty) + */ + public $emptyValues = array(''); + + /** + * Return HTML string to put an input search field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printInputSearchField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + global $langs; + + // TODO Must also support $moreCss ? + + $htmlName = $keyPrefix . $key . $keySuffix; + $prefill = array( + 'start' => $value['start'] ?? '', + 'end' => $value['end'] ?? '', + ); + + // Search filter on a date field shows two inputs to select a date range + $out = '
    '; + $out .= self::$form->selectDate($prefill['start'], $htmlName . '_start', 1, 1, 1, '', 1, 0, 0, '', '', '', '', 1, '', $langs->trans("From"), 'tzuserrel'); + $out .= '
    '; + $out .= self::$form->selectDate($prefill['end'], $htmlName . '_end', 1, 1, 1, '', 1, 0, 0, '', '', '', '', 1, '', $langs->trans("to"), 'tzuserrel'); + $out .= '
    '; + + return $out; + } + + /** + * Return HTML string to put an input field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printInputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $required = $fieldInfos->required ? 1 : 0; + $htmlName = $keyPrefix . $key . $keySuffix; + + // Do not show current date when field not required (see selectDate() method) + if (!$required && $this->isEmptyValue($fieldInfos, $value)) { + $value = '-1'; + } + + // TODO Must also support $moreAttrib and $moreCss ? + return self::$form->selectDate($value, $htmlName, 1, 1, $required, '', 1, 1, 0, 1, '', '', '', 1, '', '', 'tzuserrel'); + } + + /** + * Return HTML string to show a field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printOutputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + return !$this->isEmptyValue($fieldInfos, $value) ? dol_print_date($value, 'dayhour', 'tzuserrel') : ''; + } + + /** + * Get input CSS + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $moreCss Value for css to define style/length of field. + * @param string $defaultCss Default value for css to define style/length of field. + * @return string + * @see self::printInputSearchField(), self::printInputField() + */ + public function getInputCss($fieldInfos, $moreCss = '', $defaultCss = '') + { + return parent::getInputCss($fieldInfos, $moreCss, $defaultCss ? $defaultCss : 'minwidth200imp'); + } + + /** + * Verify if the field value is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Value to check (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @return bool + * @see self::printInputField() + */ + public function verifyFieldValue($fieldInfos, $key, $value) + { + $result = parent::verifyFieldValue($fieldInfos, $key, $value); + if ($result && !$this->isEmptyValue($fieldInfos, $value)) { + if (!self::$validator->isTimestamp($value)) { + return false; + } + + $result = true; + } + + return $result; + } + + /** + * Verify if the field value from GET/POST is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return bool + * @see self::printInputField() + */ + public function verifyPostFieldValue($fieldInfos, $key, $keyPrefix = '', $keySuffix = '') + { + return parent::verifyPostFieldValue($fieldInfos, $key, $keyPrefix, $keySuffix); + } + + /** + * Get field value from GET/POST + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $defaultValue Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return mixed + * @see self::printInputField() + */ + public function getPostFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName . 'hour') || GETPOSTISSET($htmlName . 'min') || GETPOSTISSET($htmlName . 'sec') || GETPOSTISSET($htmlName . 'month') || GETPOSTISSET($htmlName . 'day') || GETPOSTISSET($htmlName . 'year')) { + $value = dol_mktime(GETPOSTINT($htmlName . 'hour'), GETPOSTINT($htmlName . 'min'), GETPOSTINT($htmlName . 'sec'), GETPOSTINT($htmlName . 'month'), GETPOSTINT($htmlName . 'day'), GETPOSTINT($htmlName . 'year'), 'tzuserrel'); // for date without hour, we use gmt + } else { + $value = $defaultValue; + } + + return $value; + } + + /** + * Get search field value from GET/POST + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $defaultValue Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return mixed + * @see self::printInputSearchField() + */ + public function getPostSearchFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName . '_startmonth') || GETPOSTISSET($htmlName . '_startday') || GETPOSTISSET($htmlName . '_startyear')) { + $start = dol_mktime(0, 0, 0, GETPOSTINT($htmlName . '_startmonth'), GETPOSTINT($htmlName . '_startday'), GETPOSTINT($htmlName . '_startyear'), 'tzuserrel'); + } else { + $start = is_array($defaultValue) && isset($defaultValue['start']) ? $defaultValue['start'] : ''; + } + + if (GETPOSTISSET($htmlName . '_endmonth') || GETPOSTISSET($htmlName . '_endday') || GETPOSTISSET($htmlName . '_endyear')) { + $end = dol_mktime(23, 59, 59, GETPOSTINT($htmlName . '_endmonth'), GETPOSTINT($htmlName . '_endday'), GETPOSTINT($htmlName . '_endyear'), 'tzuserrel'); + } else { + $end = is_array($defaultValue) && isset($defaultValue['end']) ? $defaultValue['end'] : ''; + } + + return array( + 'start' => $start, + 'end' => $end, + ); + } + + /** + * Get sql filter for search field + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @return string + * @see self::printInputSearchField(), self::getPostSearchFieldValue() + */ + public function sqlFilterSearchField($fieldInfos, $key, $value) + { + if (!$this->isEmptyValue($fieldInfos, $value)) { + $alias = $fieldInfos->sqlAlias ?? 't.'; + $field = $this->db->sanitize($alias . ($fieldInfos->nameInTable ?? $key)); + $sql = ''; + + if (is_array($value)) { + $hasStart = !is_null($value['start']) && $value['start'] !== ''; + $hasEnd = !is_null($value['start']) && $value['start'] !== ''; + if ($hasStart && $hasEnd) { + $sql = " AND (" . $field . " BETWEEN '" . $this->db->idate($value['start']) . "' AND '" . $this->db->idate($value['end']) . "')"; + } elseif ($hasStart) { + $sql = " AND " . $field . " >= '" . $this->db->idate($value['start']) . "'"; + } elseif ($hasEnd) { + $sql = " AND " . $field . " <= '" . $this->db->idate($value['end']) . "'"; + } + } elseif (is_numeric($value)) { + $sql = " AND " . $field . " = '" . $this->db->idate($value) . "'"; + } + + return $sql; + } + + return ''; + } +} diff --git a/htdocs/core/class/fields/urlfield.class.php b/htdocs/core/class/fields/urlfield.class.php new file mode 100644 index 00000000000..20040274607 --- /dev/null +++ b/htdocs/core/class/fields/urlfield.class.php @@ -0,0 +1,217 @@ + + * + * 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/fields/urlfield.class.php + * \ingroup core + * \brief File of class to url field + */ + +require_once DOL_DOCUMENT_ROOT . '/core/class/fields/commonfield.class.php'; + + +/** + * Class to url field + */ +class UrlField extends CommonField +{ + /** + * @var array List of value deemed as empty (null always deemed as empty) + */ + public $emptyValues = array(''); + + + /** + * Return HTML string to put an input search field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printInputSearchField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $htmlName = $keyPrefix . $key . $keySuffix; + + return self::$form->inputType('text', $htmlName, (string) $value, $htmlName, $moreCss, $moreAttrib); + } + + /** + * Return HTML string to put an input field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printInputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $moreAttrib = trim((string) $moreAttrib); + if (empty($moreAttrib)) $moreAttrib = ' ' . $moreAttrib; + $autoFocus = $fieldInfos->inputAutofocus ? ' autofocus' : ''; + $htmlName = $keyPrefix . $key . $keySuffix; + + return self::$form->inputType('text', $htmlName, (string) $value, $htmlName, $moreCss, $moreAttrib . $autoFocus); + } + + /** + * Return HTML string to show a field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printOutputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + return !$this->isEmptyValue($fieldInfos, $value) ? dol_print_url((string) $value, '_blank', 32, 1) : ''; + } + + /** + * Get input CSS + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $moreCss Value for css to define style/length of field. + * @param string $defaultCss Default value for css to define style/length of field. + * @return string + * @see self::printInputSearchField(), self::printInputField() + */ + public function getInputCss($fieldInfos, $moreCss = '', $defaultCss = '') + { + return parent::getInputCss($fieldInfos, $moreCss, $defaultCss); + } + + /** + * Verify if the field value is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Value to check (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @return bool + * @see self::printInputField() + */ + public function verifyFieldValue($fieldInfos, $key, $value) + { + $result = parent::verifyFieldValue($fieldInfos, $key, $value); + if ($result && !$this->isEmptyValue($fieldInfos, $value)) { + if (!self::$validator->isUrl($value)) { + return false; + } + + $result = true; + } + + return $result; + } + + /** + * Verify if the field value from GET/POST is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return bool + * @see self::printInputField() + */ + public function verifyPostFieldValue($fieldInfos, $key, $keyPrefix = '', $keySuffix = '') + { + return parent::verifyPostFieldValue($fieldInfos, $key, $keyPrefix, $keySuffix); + } + + /** + * Get field value from GET/POST + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $defaultValue Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return mixed + * @see self::printInputField() + */ + public function getPostFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $value = GETPOST($htmlName, 'alphanohtml'); + } else { + $value = $defaultValue; + } + + return $value; + } + + /** + * Get search field value from GET/POST + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $defaultValue Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return mixed + * @see self::printInputSearchField() + */ + public function getPostSearchFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $value = GETPOST($htmlName, 'alpha'); + } else { + $value = $defaultValue; + } + + return $value; + } + + /** + * Get sql filter for search field + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @return string + * @see self::printInputSearchField(), self::getPostSearchFieldValue() + */ + public function sqlFilterSearchField($fieldInfos, $key, $value) + { + if (!$this->isEmptyValue($fieldInfos, $value)) { + $alias = $fieldInfos->sqlAlias ?? 't.'; + + return natural_search($alias . ($fieldInfos->nameInTable ?? $key), $value, 0); + } + + return ''; + } +} diff --git a/htdocs/core/class/fields/varcharfield.class.php b/htdocs/core/class/fields/varcharfield.class.php new file mode 100644 index 00000000000..15c4dc042a8 --- /dev/null +++ b/htdocs/core/class/fields/varcharfield.class.php @@ -0,0 +1,213 @@ + + * + * 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/fields/varcharfield.class.php + * \ingroup core + * \brief File of class to varchar field + */ + +require_once DOL_DOCUMENT_ROOT . '/core/class/fields/commonfield.class.php'; + + +/** + * Class to varchar field + */ +class VarcharField extends CommonField +{ + /** + * @var array List of value deemed as empty (null always deemed as empty) + */ + public $emptyValues = array(''); + + + /** + * Return HTML string to put an input search field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printInputSearchField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $htmlName = $keyPrefix . $key . $keySuffix; + + return self::$form->inputType('text', $htmlName, (string) $value, $htmlName, $moreCss, $moreAttrib); + } + + /** + * Return HTML string to put an input field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printInputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $size = (int) $fieldInfos->size; + $maxLength = ''; // $size > 0 ? ' maxlength="' . $size . '"' : ''; // TODO rework, wrong method + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $moreAttrib = trim((string) $moreAttrib); + if (empty($moreAttrib)) $moreAttrib = ' ' . $moreAttrib; + $placeHolder = $fieldInfos->inputPlaceholder; + if (!empty($placeHolder)) $placeHolder = ' placeholder="' . dolPrintHTMLForAttribute($placeHolder) . '"'; + $autoFocus = $fieldInfos->inputAutofocus ? ' autofocus' : ''; + $htmlName = $keyPrefix . $key . $keySuffix; + + return self::$form->inputType('text', $htmlName, (string) $value, $htmlName, $moreCss, $maxLength . $moreAttrib . $placeHolder . $autoFocus); + } + + /** + * Return HTML string to show a field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printOutputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + return !$this->isEmptyValue($fieldInfos, $value) ? dol_htmlentitiesbr((string) $value) : ''; + } + + /** + * Get input CSS + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $moreCss Value for css to define style/length of field. + * @param string $defaultCss Default value for css to define style/length of field. + * @return string + * @see self::printInputSearchField(), self::printInputField() + */ + public function getInputCss($fieldInfos, $moreCss = '', $defaultCss = '') + { + return parent::getInputCss($fieldInfos, $moreCss, $defaultCss); + } + + /** + * Verify if the field value is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Value to check (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @return bool + * @see self::printInputField() + */ + public function verifyFieldValue($fieldInfos, $key, $value) + { + return parent::verifyFieldValue($fieldInfos, $key, $value); + } + + /** + * Verify if the field value from GET/POST is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return bool + * @see self::printInputField() + */ + public function verifyPostFieldValue($fieldInfos, $key, $keyPrefix = '', $keySuffix = '') + { + return parent::verifyPostFieldValue($fieldInfos, $key, $keyPrefix, $keySuffix); + } + + /** + * Get field value from GET/POST + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $defaultValue Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return mixed + * @see self::printInputField() + */ + public function getPostFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $check = $key == 'lang' ? 'aZ09' : 'alphanohtml'; + $value = GETPOST($htmlName, $check); + } else { + $value = $defaultValue; + } + + return $value; + } + + /** + * Get search field value from GET/POST + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $defaultValue Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return mixed + * @see self::printInputSearchField() + */ + public function getPostSearchFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $value = GETPOST($htmlName, 'alpha'); + } else { + $value = $defaultValue; + } + + return $value; + } + + /** + * Get sql filter for search field + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @return string + * @see self::printInputSearchField(), self::getPostSearchFieldValue() + */ + public function sqlFilterSearchField($fieldInfos, $key, $value) + { + if (!$this->isEmptyValue($fieldInfos, $value)) { + $alias = $fieldInfos->sqlAlias ?? 't.'; + + return natural_search($alias . ($fieldInfos->nameInTable ?? $key), $value, 0); + } + + return ''; + } +} diff --git a/htdocs/core/class/fieldsmanager.class.php b/htdocs/core/class/fieldsmanager.class.php new file mode 100644 index 00000000000..deb92f82546 --- /dev/null +++ b/htdocs/core/class/fieldsmanager.class.php @@ -0,0 +1,1311 @@ + + * Copyright (C) 2002-2003 Jean-Louis Bergamo + * Copyright (C) 2004 Sebastien Di Cintio + * Copyright (C) 2004 Benoit Mortier + * Copyright (C) 2009-2012 Laurent Destailleur + * Copyright (C) 2009-2012 Regis Houssin + * Copyright (C) 2013 Florian Henry + * Copyright (C) 2015 Charles-Fr BENKE + * Copyright (C) 2016 Raphaël Doursenaud + * Copyright (C) 2017 Nicolas ZABOURI + * Copyright (C) 2018-2022 Frédéric France + * Copyright (C) 2022 Antonin MARCHAL + * + * 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/fieldsmanager.class.php + * \ingroup core + * \brief File of class to manage fields + */ + +require_once DOL_DOCUMENT_ROOT . '/core/class/fieldinfos.class.php'; +require_once DOL_DOCUMENT_ROOT . '/core/class/fields/commonfield.class.php'; + + +/** + * Class to manage fields + */ +class FieldsManager +{ + /** + * @var DoliDB Database handler. + */ + public $db; + + /** + * @var string Error code (or message) + */ + public $error = ''; + + /** + * @var string[] Array of Error code (or message) + */ + public $errors = array(); + + /** + * @var array To store error results of ->validateField() + */ + public $validateFieldsErrors = array(); + + /** + * @var string Path to fields classes + */ + public $fieldsPath = '/core/class/fields/'; + + /** + * @var array Field classes cached + */ + public static $fieldClasses = array(); + + /** + * @var array,extraField:array}>> Field infos cached (array,extraField:array}>>) + */ + public static $fieldInfos = array(); + + /** + * @var array>|null Array with boolean of status of groups + */ + public $expand_display = array(); + + ///** + // * @var array Array of type to label + // */ + //public static $type2label = array( + // 'varchar' => 'String1Line', + // 'text' => 'TextLongNLines', + // 'html' => 'HtmlText', + // 'int' => 'Int', + // 'double' => 'Float', + // 'date' => 'Date', + // 'datetime' => 'DateAndTime', + // 'duration' => 'Duration', + // //'datetimegmt'=>'DateAndTimeUTC', + // 'boolean' => 'Boolean', + // 'price' => 'ExtrafieldPrice', + // 'pricecy' => 'ExtrafieldPriceWithCurrency', + // 'phone' => 'ExtrafieldPhone', + // 'email' => 'ExtrafieldMail', + // 'url' => 'ExtrafieldUrl', + // 'ip' => 'ExtrafieldIP', + // 'icon' => 'Icon', + // 'password' => 'ExtrafieldPassword', + // 'radio' => 'ExtrafieldRadio', + // 'select' => 'ExtrafieldSelect', + // 'sellist' => 'ExtrafieldSelectList', + // 'checkbox' => 'ExtrafieldCheckBox', + // 'chkbxlst' => 'ExtrafieldCheckBoxFromList', + // 'link' => 'ExtrafieldLink', + // 'point' => 'ExtrafieldPointGeo', + // 'multipts' => 'ExtrafieldMultiPointGeo', + // 'linestrg' => 'ExtrafieldLinestringGeo', + // 'polygon' => 'ExtrafieldPolygonGeo', + // 'separate' => 'ExtrafieldSeparator', + // 'stars' => 'ExtrafieldStars', + // //'real' => 'ExtrafieldReal', + //); + + + /** + * Constructor + * + * @param DoliDB $db Database handler + * @param Form|null $form Specific form handler + */ + public function __construct($db, $form = null) + { + $this->db = $db; + $this->error = ''; + $this->errors = array(); + + if (isset($form)) { + CommonField::setForm($form); + } + } + + /** + * Get field handler for the provided type + * + * @param string $type Field type + * @return CommonField|null + */ + public function getFieldClass($type) + { + global $hookmanager, $langs; + + $type = trim($type); + + if (!isset(self::$fieldClasses[$type])) { + $field = null; + $parameters = array( + 'type' => $type, + // @phan-suppress-next-line PhanPluginConstantVariableNull + 'field' => &$field, + ); + + $hookmanager->executeHooks('getFieldClass', $parameters, $this); // Note that $object may have been modified by hook + // @phpstan-ignore-next-line @phan-suppress-next-line PhanPluginConstantVariableNull + if (isset($field) && is_object($field)) { + self::$fieldClasses[$type] = $field; + } else { + $filename = strtolower($type) . 'field.class.php'; + $classname = ucfirst($type) . 'Field'; + + // Load class file + dol_include_once(rtrim($this->fieldsPath, '/') . '/' . $filename); + if (!class_exists($classname)) { + @include_once DOL_DOCUMENT_ROOT . '/core/class/fields/' . $filename; + } + + if (class_exists($classname)) { + self::$fieldClasses[$type] = new $classname($this->db); + } else { + $langs->load("errors"); + $this->errors[] = $langs->trans('ErrorFieldClassNotFoundForClassName', $classname, $type); + return null; + } + } + } + + $field = self::$fieldClasses[$type]; + $field->clearErrors(); + + return $field; + } + + /** + * Get all fields handler available + * + * @return array + */ + public function getAllFields() + { + // Todo to make + return self::$fieldClasses; + } + + /** + * clear errors + * + * @return void + */ + public function clearErrors() + { + $this->error = ''; + $this->errors = array(); + } + + /** + * Method to output saved errors + * + * @param string $separator Separator between each error + * @return string String with errors + */ + public function errorsToString($separator = ', ') + { + return $this->error . (is_array($this->errors) ? (!empty($this->error) ? $separator : '') . implode($separator, $this->errors) : ''); + } + + /** + * clear validation message result for a field + * + * @param string $fieldKey Key of attribute to clear + * @return void + */ + public function clearFieldError($fieldKey) + { + $this->error = ''; + unset($this->validateFieldsErrors[$fieldKey]); + } + + /** + * set validation error message a field + * + * @param string $fieldKey Key of attribute + * @param string $msg the field error message + * @return void + */ + public function setFieldError($fieldKey, $msg = '') + { + global $langs; + if (empty($msg)) { + $msg = $langs->trans("UnknownError"); + } + + $this->error = $this->validateFieldsErrors[$fieldKey] = $msg; + } + + /** + * get field error message + * + * @param string $fieldKey Key of attribute + * @return string Error message of validation ('' if no error) + */ + public function getFieldError($fieldKey) + { + if (!empty($this->validateFieldsErrors[$fieldKey])) { + return $this->validateFieldsErrors[$fieldKey]; + } + return ''; + } + + /** + * get field error icon + * + * @param string $fieldValidationErrorMsg message to add in tooltip + * @return string html output + */ + public function getFieldErrorIcon($fieldValidationErrorMsg) + { + $out = ''; + + if (!empty($fieldValidationErrorMsg) && function_exists('getFieldErrorIcon')) { + $out .= ' ' . getFieldErrorIcon($fieldValidationErrorMsg); + } + + return $out; + } + + /** + * Get list of fields infos for the provided mode into X columns + * + * @param CommonObject $object Object handler + * @param ExtraFields $extrafields ExtraFields handler + * @param string $mode Get the fields infos for the provided mode ('create', 'edit', 'view', 'list') + * @param int $nbColumn Split fields infos into X columns + * @param array $breakKeys Key used for break on each column (ex: array(1 => 'total_ht', ...)) + * @param array $params Other params + * @return array{columns:array>,hiddenFields:array} List of fields info by column and hidden + */ + public function getAllFieldsInfos(&$object, &$extrafields = null, $mode = 'view', $nbColumn = 2, $breakKeys = array(), $params = array()) + { + global $hookmanager, $langs; + + // Get object fields + $fields = $this->getAllObjectFieldsInfos($object, $mode, $params); + + // Old sort + if (!getDolGlobalInt('MAIN_FIELDS_SORT_WITH_EXTRA_FIELDS')) { + $fields = dol_sort_array($fields, 'position'); + } + + // Get extra fields + $fields2 = $this->getAllExtraFieldsInfos($object, $extrafields, $mode, $params); + $fields = array_merge($fields, $fields2); + + // New sort + if (getDolGlobalInt('MAIN_FIELDS_SORT_WITH_EXTRA_FIELDS')) { + $fields = dol_sort_array($fields, 'position'); + } + + // Split in columns + $idxColumn = 1; + $columns = array(); + $hiddenFields = array(); + $columns[$idxColumn] = array(); + $nbVisibleFields = 0; + foreach ($fields as $field) { + if ($field->visible) { + $nbVisibleFields++; + } + } + $nbFieldsByColumn = ceil($nbVisibleFields / $nbColumn); + $breakKey = $breakKeys[$idxColumn] ?? ''; + $idxField = 0; + foreach ($fields as $key => $field) { + if ($idxColumn < $nbColumn && ((!empty($breakKey) && $key == $breakKey) || (empty($breakKey) && $idxField == $nbFieldsByColumn))) { + $idxColumn++; + $idxField = 0; + $columns[$idxColumn] = array(); + } + + if ($field->visible) { + if ($field->type != 'separate') { + $idxField++; + } + + // Add field into column + $columns[$idxColumn][$key] = $field; + } else { + $hiddenFields[$key] = $field; + } + } + + // Add column not created + for ($idxColumn = 1; $idxColumn <= $nbColumn; $idxColumn++) { + if (!isset($columns[$idxColumn])) { + $columns[$idxColumn] = array(); + } + } + + $parameters = array( + 'object' => &$object, + 'extrafields' => &$extrafields, + 'mode' => $mode, + 'nbColumn' => $nbColumn, + 'breakKeys' => $breakKeys, + 'params' => $params, + 'columns' => &$columns, + 'hiddenFields' => &$hiddenFields, + ); + + $hookmanager->executeHooks('getFieldInfosFromObjectField', $parameters, $this); // Note that $object may have been modified by hook + + return array( + 'columns' => $columns, + 'hiddenFields' => $hiddenFields, + ); + } + + /** + * Get list of object fields infos + * + * @param CommonObject $object Object handler + * @param string $mode Get the fields infos for the provided mode ('create', 'edit', 'view', 'list') + * @param array $params Other params + * @return array List of fields infos + */ + public function getAllObjectFieldsInfos(&$object, $mode = 'view', $params = array()) + { + global $hookmanager; + + // Get object fields + $fields = array(); + // @phpstan-ignore-next-line + if (isset($object->fields) && is_array($object->fields)) { + $keyPrefix = getDolGlobalInt('MAIN_FIELDS_NEW_OBJECT_KEY_PREFIX') ? 'object_' : ''; + foreach ($object->fields as $key => $field) { + $fieldInfos = $this->getFieldInfosFromObjectField($object, $key, $mode, $params); + $fields[$keyPrefix . $key] = $fieldInfos; + } + } + + $parameters = array( + 'object' => &$object, + 'mode' => $mode, + 'params' => $params, + 'fields' => &$fields, + ); + + $hookmanager->executeHooks('getAllObjectFieldsInfos', $parameters, $this); // Note that $object may have been modified by hook + + return $fields; + } + + /** + * Get list of extra fields infos + * + * @param CommonObject $object Object handler + * @param ExtraFields $extrafields ExtraFields handler + * @param string $mode Get the fields infos for the provided mode ('create', 'edit', 'view', 'list') + * @param array $params Other params + * @return array List of fields infos + */ + public function getAllExtraFieldsInfos(&$object, &$extrafields = null, $mode = 'view', $params = array()) + { + global $hookmanager; + + // Get extra fields + $fields = array(); + if (isset($extrafields->attributes[$object->table_element]) && is_array($extrafields->attributes[$object->table_element])) { + if (isset($extrafields->attributes[$object->table_element]['label']) && is_array($extrafields->attributes[$object->table_element]['label'])) { + $keyPrefix = 'options_'; + foreach ($extrafields->attributes[$object->table_element]['label'] as $key => $label) { + $fieldInfos = $this->getFieldInfosFromExtraField($object, $extrafields, $key, $mode, $params); + $fields[$keyPrefix . $key] = $fieldInfos; + } + } + } + + $parameters = array( + 'object' => &$object, + 'extrafields' => &$extrafields, + 'mode' => $mode, + 'params' => $params, + 'fields' => &$fields, + ); + + $hookmanager->executeHooks('getAllExtraFieldsInfos', $parameters, $this); // Note that $object may have been modified by hook + + return $fields; + } + + /** + * Get list of fields infos for the provided mode into X columns + * + * @param string $key Field key (begin by object_ for object or options_ for extrafields) + * @param CommonObject $object Object handler + * @param ExtraFields $extrafields ExtraFields handler + * @param string $mode Get the fields infos for the provided mode ('create', 'edit', 'view', 'list') + * @param array $params Other params + * @return FieldInfos|null Get field info or null if not found + */ + public function getFieldsInfos($key, &$object, &$extrafields = null, $mode = 'view', $params = array()) + { + global $langs; + + $fieldInfos = null; + + $patternObjectPrefix = getDolGlobalInt('MAIN_FIELDS_NEW_OBJECT_KEY_PREFIX') ? 'object_' : ''; + if (preg_match('/^options_(.*)/i', $key, $matches)) { + $fieldKey = $matches[1]; + $fieldInfos = $this->getFieldInfosFromExtraField($object, $extrafields, $fieldKey, $mode, $params); + } elseif (preg_match('/^' . $patternObjectPrefix . '(.*)/i', $key, $matches)) { + $fieldKey = $matches[2]; + $fieldInfos = $this->getFieldInfosFromObjectField($object, $fieldKey, $mode, $params); + } + + return $fieldInfos; + } + + /** + * Get field infos from object field infos + * + * @param CommonObject $object Object handler + * @param string $key Field key + * @param string $mode Get the fields infos for the provided mode ('create', 'edit', 'view', 'list') + * @param array $params Other params + * @return FieldInfos|null Properties of the field or null if field not found + */ + public function getFieldInfosFromObjectField(&$object, $key, $mode = 'view', $params = array()) + { + global $hookmanager; + + if (!isset($object->fields[$key])) { + return null; + } + + if (isset(self::$fieldInfos[$object->element][$mode]['object'][$key])) { + return self::$fieldInfos[$object->element][$mode]['object'][$key]; + } + + $attributes = $object->fields[$key]; + + $fieldInfos = new FieldInfos(); + $fieldInfos->fieldType = FieldInfos::FIELD_TYPE_OBJECT; + $fieldInfos->originType = $attributes['type'] ?? ''; + $fieldInfos->size = $attributes['length'] ?? ''; + $fieldInfos->label = $attributes['label'] ?? ''; + $fieldInfos->langFile = $attributes['langfile'] ?? ''; + $fieldInfos->sqlAlias = $attributes['alias'] ?? null; + $fieldInfos->picto = $attributes['picto'] ?? ''; + $fieldInfos->position = $attributes['position'] ?? 0; + $fieldInfos->required = ($attributes['notnull'] ?? 0) > 0; + $fieldInfos->alwaysEditable = !empty($attributes['alwayseditable']); + $fieldInfos->defaultValue = $attributes['default'] ?? ''; + $fieldInfos->css = $attributes['css'] ?? ''; + $fieldInfos->viewCss = $attributes['cssview'] ?? ''; + $fieldInfos->listCss = $attributes['csslist'] ?? ''; + $fieldInfos->inputPlaceholder = $attributes['placeholder'] ?? ''; + $fieldInfos->help = $attributes['help'] ?? ''; + $fieldInfos->listHelp = $attributes['helplist'] ?? ''; + $fieldInfos->showOnComboBox = !empty($attributes['showoncombobox']); + $fieldInfos->inputDisabled = !empty($attributes['disabled']); + $fieldInfos->inputAutofocus = !empty($attributes['autofocusoncreate']) && $mode == 'create'; + $fieldInfos->comment = $attributes['comment'] ?? ''; + $fieldInfos->listTotalizable = !empty($attributes['isameasure']) && $attributes['isameasure'] == 1; + $fieldInfos->validateField = !empty($attributes['validate']); + $fieldInfos->copyToClipboard = $attributes['copytoclipboard'] ?? 0; + $fieldInfos->tdCss = $attributes['tdcss'] ?? ''; + $fieldInfos->multiInput = !empty($attributes['multiinput']); + $fieldInfos->nameInClass = $attributes['nameinclass'] ?? $key; + $fieldInfos->nameInTable = $attributes['nameintable'] ?? $key; + $fieldInfos->getNameUrlParams = $attributes['get_name_url_params'] ?? null; + $fieldInfos->showOnHeader = !empty($attributes['showonheader']); + + // TODO set nameinclass = "id" in fields "rowid" + if ($fieldInfos->nameInClass == 'rowid') { + $fieldInfos->nameInClass = 'id'; + } + + $enabled = $attributes['enabled'] ?? '1'; + $visibility = $attributes['visible'] ?? '1'; + $perms = empty($attributes['noteditable']) ? '1' : '0'; + + $this->setCommonFieldInfos($fieldInfos, $object, $extrafields, $key, $mode, $enabled, $visibility, $perms, $params); + + // Special case that force options and type ($type can be integer, varchar, ...) + if (!empty($attributes['arrayofkeyval']) && is_array($attributes['arrayofkeyval'])) { + $fieldInfos->options = $attributes['arrayofkeyval']; + // Special case that prevent to force $type to have multiple input @phan-suppress-next-line PhanTypeMismatchProperty + if (!$fieldInfos->multiInput) { + $fieldInfos->type = (($fieldInfos->type == 'checkbox') ? $fieldInfos->type : 'select'); + } + } + + $parameters = array( + 'object' => &$object, + 'key' => $key, + 'mode' => $mode, + 'fieldInfos' => &$fieldInfos, + ); + + $hookmanager->executeHooks('getFieldInfosFromObjectField', $parameters, $this); // Note that $object may have been modified by hook + + self::$fieldInfos[$object->element][$mode]['object'][$key] = $fieldInfos; + return $fieldInfos; + } + + /** + * Get field infos from extra field infos + * + * @param CommonObject $object Object handler + * @param ExtraFields $extrafields Extrafields handler + * @param string $key Field key + * @param string $mode Get the fields infos for the provided mode ('create', 'edit', 'view', 'list') + * @param array $params Other params + * @return FieldInfos|null Properties of the field or null if not found + */ + public function getFieldInfosFromExtraField(&$object, &$extrafields, $key, $mode = 'view', $params = array()) + { + global $hookmanager; + + if (!isset($extrafields->attributes[$object->table_element]['label'][$key])) { + return null; + } + + if (isset(self::$fieldInfos[$object->element][$mode]['extraField'][$key])) { + return self::$fieldInfos[$object->element][$mode]['extraField'][$key]; + } + + $attributes = $extrafields->attributes[$object->table_element]; + + $fieldInfos = new FieldInfos(); + $fieldInfos->fieldType = FieldInfos::FIELD_TYPE_EXTRA_FIELD; + $fieldInfos->originType = $attributes['type'][$key] ?? ''; + $fieldInfos->label = $attributes['label'][$key] ?? ''; + $fieldInfos->position = $attributes['pos'][$key] ?? 0; + $fieldInfos->required = !empty($attributes['required'][$key]); + $fieldInfos->defaultValue = $attributes['default'][$key] ?? ''; + $fieldInfos->css = $attributes['css'][$key] ?? ''; + $fieldInfos->help = $attributes['help'][$key] ?? ''; + $fieldInfos->size = $attributes['size'][$key] ?? ''; + $fieldInfos->computed = $attributes['computed'][$key] ?? ''; + $fieldInfos->unique = !empty($attributes['unique'][$key]); + $fieldInfos->alwaysEditable = !empty($attributes['alwayseditable'][$key]); + $fieldInfos->emptyOnClone = !empty($attributes['emptyonclone'][$key]); + $fieldInfos->langFile = $attributes['langfile'][$key] ?? ''; + $fieldInfos->printable = !empty($attributes['printable'][$key]); + $fieldInfos->aiPrompt = $attributes['aiprompt'][$key] ?? ''; + $fieldInfos->viewCss = $attributes['cssview'][$key] ?? ''; + $fieldInfos->listCss = $attributes['csslist'][$key] ?? ''; + $fieldInfos->listTotalizable = !empty($attributes['totalizable'][$key]); + $fieldInfos->options = array_diff_assoc($attributes['param'][$key]['options'] ?? array(), array('' => null)); // For remove case when not defined + $fieldInfos->nameInClass = $key; + $fieldInfos->nameInTable = $key; + + $enabled = $attributes['enabled'][$key] ?? '1'; + $visibility = $attributes['list'][$key] ?? '1'; + $perms = $attributes['perms'][$key] ?? null; + + $this->setCommonFieldInfos($fieldInfos, $object, $extrafields, $key, $mode, $enabled, $visibility, $perms, $params); + + $parameters = array( + 'object' => &$object, + 'extraFields' => &$extrafields, + 'key' => $key, + 'mode' => $mode, + 'fieldInfos' => &$fieldInfos, + ); + + $hookmanager->executeHooks('getFieldInfosFromExtraField', $parameters, $this); // Note that $object may have been modified by hook + + self::$fieldInfos[$object->element][$mode]['extraField'][$key] = $fieldInfos; + return $fieldInfos; + } + + /** + * Set common field infos + * + * @param FieldInfos $fieldInfos Field infos to set with common infos + * @param CommonObject $object Object handler + * @param ExtraFields $extrafields Extrafields handler + * @param string $key Field key + * @param string $mode Get the fields infos for the provided mode ('create', 'edit', 'view', 'list') + * @param string $enabled Condition when the field must be managed (Example: 1 or 'getDolGlobalInt("MY_SETUP_PARAM")' or 'isModEnabled("multicurrency")' ...) + * @param string $visibility Condition when the field must be visible (Examples: 0=Not visible, 1=Visible on list and create/update/view forms, 2=Visible on list only, 3=Visible on create/update/view form only (not list), 4=Visible on list and update/view form (not create). 5=Visible on list and view form (not create/not update). 6=visible on list and update/view form (not create). Using a negative value means field is not shown by default on list but can be selected for viewing) + * @param string $perms Condition when the field must be editable + * @param array $params Other params + * @return void + */ + public function setCommonFieldInfos(&$fieldInfos, &$object, &$extrafields, $key, $mode = 'view', $enabled = '1', $visibility = '', $perms = null, $params = array()) + { + global $user; + + $fieldInfos->object = &$object; + $fieldInfos->mode = preg_replace('/[^a-z0-9_]/i', '', $mode); + $fieldInfos->type = $fieldInfos->originType; + $fieldInfos->key = $key; + $fieldInfos->otherParams = $params; + + if (preg_match('/^(integer|link):(.*):(.*):(.*):(.*)/i', $fieldInfos->originType, $reg)) { + $fieldInfos->options = array($reg[2] . ':' . $reg[3] . ':' . $reg[4] . ':' . $reg[5] => 'N'); + $fieldInfos->type = 'link'; + } elseif (preg_match('/^(integer|link):(.*):(.*):(.*)/i', $fieldInfos->originType, $reg)) { + $fieldInfos->options = array($reg[2] . ':' . $reg[3] . ':' . $reg[4] => 'N'); + $fieldInfos->type = 'link'; + } elseif (preg_match('/^(integer|link):(.*):(.*)/i', $fieldInfos->originType, $reg)) { + $fieldInfos->options = array($reg[2] . ':' . $reg[3] . ($reg[1] == 'User' ? ':#getnomurlparam1=-1' : '') => 'N'); + $fieldInfos->type = 'link'; + } elseif (preg_match('/^(sellist):(.*):(.*):(.*):(.*)/i', $fieldInfos->originType, $reg)) { + $fieldInfos->options = array($reg[2] . ':' . $reg[3] . ':' . $reg[4] . ':' . $reg[5] => 'N'); + $fieldInfos->type = 'sellist'; + } elseif (preg_match('/^(sellist):(.*):(.*):(.*)/i', $fieldInfos->originType, $reg)) { + $fieldInfos->options = array($reg[2] . ':' . $reg[3] . ':' . $reg[4] => 'N'); + $fieldInfos->type = 'sellist'; + } elseif (preg_match('/^(sellist):(.*):(.*)/i', $fieldInfos->originType, $reg)) { + $fieldInfos->options = array($reg[2] . ':' . $reg[3] => 'N'); + $fieldInfos->type = 'sellist'; + } elseif (preg_match('/^chkbxlst:(.*)/i', $fieldInfos->originType, $reg)) { + $fieldInfos->options = array($reg[1] => 'N'); + $fieldInfos->type = 'chkbxlst'; + } elseif (preg_match('/varchar\((\d+)\)/', $fieldInfos->originType, $reg)) { + $fieldInfos->options = array(); + $fieldInfos->type = 'varchar'; + $fieldInfos->size = $reg[1]; + $fieldInfos->maxLength = (int) $reg[1]; + } elseif (preg_match('/varchar/', $fieldInfos->originType)) { + $fieldInfos->options = array(); + $fieldInfos->type = 'varchar'; + } elseif (preg_match('/stars\((\d+)\)/', $fieldInfos->originType, $reg)) { + $fieldInfos->options = array(); + $fieldInfos->type = 'stars'; + $fieldInfos->size = $reg[1]; + } elseif (preg_match('/integer/', $fieldInfos->originType)) { + $fieldInfos->type = 'int'; + } elseif ($fieldInfos->originType == 'mail') { + $fieldInfos->type = 'email'; + } elseif (preg_match('/^(text):(.*)/i', $fieldInfos->originType, $reg)) { + $fieldInfos->type = 'text'; + $fieldInfos->getPostCheck = $reg[2]; + } elseif (preg_match('/^(html):(.*)/i', $fieldInfos->originType, $reg)) { + $fieldInfos->type = 'html'; + $fieldInfos->getPostCheck = $reg[2]; + } elseif (preg_match('/^double\(([0-9]+,[0-9]+)\)/', $fieldInfos->originType, $reg)) { + $fieldInfos->type = 'double'; + $fieldInfos->size = $reg[1]; + } + + // Set visibility + $visibility = (int) dol_eval((string) $visibility, 1, 1, '2'); + $absVisibility = abs($visibility); + $enabled = (int) dol_eval((string) $enabled, 1, 1, '2'); + $fieldInfos->visible = true; + if (empty($visibility) || + empty($enabled) || + ($mode == 'create' && !in_array($absVisibility, array(1, 3, 6))) || + ($mode == 'edit' && !in_array($absVisibility, array(1, 3, 4))) || + ($mode == 'view' && (!in_array($absVisibility, array(1, 3, 4, 5)) || $fieldInfos->showOnHeader)) || + ($mode == 'list' && $absVisibility == 3) + ) { + $fieldInfos->visible = false; + } + + // Set edit perms + if (isset($perms)) { + $perms = (int) dol_eval((string) $perms, 1, 1, '2'); + } else { + //TODO Improve element and rights detection + $mappingKeyForPerm = array( + 'fichinter' => 'ficheinter', + 'product' => 'produit', + 'project' => 'projet', + 'order_supplier' => 'supplier_order', + 'invoice_supplier' => 'supplier_invoice', + 'shipping' => 'expedition', + 'productlot' => 'stock', + 'facturerec' => 'facture', + 'mo' => 'mrp', + 'salary' => 'salaries', + 'member' => 'adherent', + ); + $keyForPerm = $mappingKeyForPerm[$object->element] ?? $object->element; + + $perms = false; + if (isset($user->rights->$keyForPerm)) { + $perms = $user->hasRight($keyForPerm, 'creer') || $user->hasRight($keyForPerm, 'create') || $user->hasRight($keyForPerm, 'write'); + } + if ($object->element == 'order_supplier' && !getDolGlobalString('MAIN_USE_NEW_SUPPLIERMOD')) { + $perms = $user->hasRight('fournisseur', 'commande', 'creer'); + } elseif ($object->element == 'invoice_supplier' && !getDolGlobalString('MAIN_USE_NEW_SUPPLIERMOD')) { + $perms = $user->hasRight('fournisseur', 'facture', 'creer'); + } elseif ($object->element == 'delivery') { + $perms = $user->hasRight('expedition', 'delivery', 'creer'); + } elseif ($object->element == 'contact') { + $perms = $user->hasRight('societe', 'contact', 'creer'); + } + } + // Manage always editable of extra field + $isDraft = ((isset($object->statut) && $object->statut == 0) || (isset($object->status) && $object->status == 0)); + if ($mode == 'view' && !$isDraft && !$fieldInfos->alwaysEditable) { + $perms = false; + } + // Case visible only in view so not editable + if ($mode == 'view' && $absVisibility == 5) { + $perms = false; + } + // Case field computed + if (!empty($fieldInfos->computed)) { + $perms = false; + } + $fieldInfos->editable = !empty($perms); + + // Set list info 'checked' + $fieldInfos->listChecked = $mode == 'list' && $visibility > 0; + } + + /** + * Set all values of the object (with extra field) from POST + * + * @param CommonObject $object Object handler + * @param ExtraFields $extrafields Extrafields handler + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $mode Get the fields infos for the provided mode ('create', 'edit', 'view', 'list') + * @param array $params Other params + * @return int Result <0 if KO, >0 if OK + */ + public function setFieldValuesFromPost(&$object, &$extrafields, $keyPrefix = '', $keySuffix = '', $mode = 'view', $params = array()) + { + $result = $this->setObjectFieldValuesFromPost($object, $keyPrefix, $keySuffix, $mode, $params); + $result2 = $this->setExtraFieldValuesFromPost($object, $extrafields, $keyPrefix, $keySuffix, $mode, $params); + + return $result > 0 && $result2 > 0 ? 1 : -1; + } + + /** + * Set all object values of the object from POST + * + * @param CommonObject $object Object handler + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $mode Get the fields infos for the provided mode ('create', 'edit', 'view', 'list') + * @param array $params Other params + * @return int Result <0 if KO, >0 if OK + */ + public function setObjectFieldValuesFromPost(&$object, $keyPrefix = '', $keySuffix = '', $mode = 'view', $params = array()) + { + $fields = $this->getAllObjectFieldsInfos($object, $mode, $params); + + $error = 0; + foreach ($fields as $fieldKey => $fieldInfos) { + $check = true; + $key = $fieldInfos->nameInClass ?? $fieldInfos->key; + if ($fieldInfos->visible) { + $check = $this->verifyPostFieldValue($fieldInfos, $fieldKey, $keyPrefix, $keySuffix); + } + if ($check) { + $object->$key = $this->getPostFieldValue($fieldInfos, $fieldKey, $object->$key, $keyPrefix, $keySuffix); + } + if (!$fieldInfos->visible) { + $check = $this->verifyFieldValue($fieldInfos, $fieldKey, $object->$key); + } + if (!$check) { + $error++; + } + } + + return $error ? -1 : 1; + } + + /** + * Set all extra field values of the object from POST + * + * @param CommonObject $object Object handler + * @param ExtraFields $extrafields Extrafields handler + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $mode Get the fields infos for the provided mode ('create', 'edit', 'view', 'list') + * @param array $params Other params + * @return int Result <0 if KO, >0 if OK + */ + public function setExtraFieldValuesFromPost(&$object, &$extrafields, $keyPrefix = '', $keySuffix = '', $mode = 'view', $params = array()) + { + $fields = $this->getAllExtraFieldsInfos($object, $extrafields, $mode, $params); + + $error = 0; + foreach ($fields as $fieldKey => $fieldInfos) { + $check = true; + $key = 'options_' . ($fieldInfos->nameInClass ?? $fieldInfos->key); + if ($fieldInfos->visible) { + $check = $this->verifyPostFieldValue($fieldInfos, $fieldKey, $keyPrefix, $keySuffix); + } + if ($check) { + $object->array_options[$key] = $this->getPostFieldValue($fieldInfos, $fieldKey, $object->array_options[$key], $keyPrefix, $keySuffix); + } + if (!$fieldInfos->visible) { + $check = $this->verifyFieldValue($fieldInfos, $fieldKey, $object->array_options[$key]); + } + if (!$check) { + $error++; + } + } + + return $error ? -1 : 1; + } + + /** + * Verify if the field value is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of attribute + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return bool + */ + public function verifyPostFieldValue($fieldInfos, $key, $keyPrefix = '', $keySuffix = '') + { + global $hookmanager; + + $result = true; + if (getDolGlobalInt('MAIN_FEATURES_LEVEL') >= 1 || getDolGlobalString('MAIN_ACTIVATE_VALIDATION_RESULT')) { + $parameters = array( + 'fieldInfos' => &$fieldInfos, + 'key' => $key, + 'keyPrefix' => $keyPrefix, + 'keySuffix' => $keySuffix, + ); + + $reshook = $hookmanager->executeHooks('verifyPostFieldValue', $parameters, $this); // Note that $object may have been modified by hook + if ($reshook > 0) { + return (bool) $hookmanager->resPrint; + } + + $this->clearErrors(); + $field = $this->getFieldClass($fieldInfos->type); + if (isset($field)) { + $this->clearFieldError($key); + $result = $field->verifyPostFieldValue($fieldInfos, $key, $keyPrefix, $keySuffix); + if (!$result) { + $this->setFieldError($key, CommonField::$validator->error); + } + } + } + + return $result; + } + + /** + * Verify if the field value is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $value Value to check (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @return bool + */ + public function verifyFieldValue($fieldInfos, $key, $value) + { + global $hookmanager; + + $result = true; + if (getDolGlobalInt('MAIN_FEATURES_LEVEL') >= 1 || getDolGlobalString('MAIN_ACTIVATE_VALIDATION_RESULT')) { + $parameters = array( + 'fieldInfos' => &$fieldInfos, + 'key' => $key, + 'value' => $value, + ); + + $reshook = $hookmanager->executeHooks('verifyFieldValue', $parameters, $this); // Note that $object may have been modified by hook + if ($reshook > 0) { + return (bool) $hookmanager->resPrint; + } + + $this->clearErrors(); + $field = $this->getFieldClass($fieldInfos->type); + if (isset($field)) { + $this->clearFieldError($key); + $result = $field->verifyFieldValue($fieldInfos, $key, $value); + if (!$result) { + $this->setFieldError($key, CommonField::$validator->error); + } + } + } + + return $result; + } + + /** + * Get field value from GET/POST + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $defaultValue Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return mixed + */ + public function getPostFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + global $hookmanager; + + $value = ''; + $parameters = array( + 'fieldInfos' => &$fieldInfos, + 'key' => $key, + 'value' => &$value, + 'keyPrefix' => $keyPrefix, + 'keySuffix' => $keySuffix, + ); + + $reshook = $hookmanager->executeHooks('getPostFieldValue', $parameters, $this); // Note that $object may have been modified by hook + if ($reshook > 0) { + return $value; + } + + $this->clearErrors(); + $field = $this->getFieldClass($fieldInfos->type); + if (isset($field)) { + $value = $field->getPostFieldValue($fieldInfos, $key, $defaultValue, $keyPrefix, $keySuffix); + } else { + $value = $defaultValue; + } + + return $value; + } + + /** + * Get search field value from GET/POST + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @param mixed $defaultValue Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @return mixed + */ + public function getPostSearchFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + global $hookmanager; + + $value = ''; + $parameters = array( + 'fieldInfos' => &$fieldInfos, + 'key' => $key, + 'value' => &$value, + 'keyPrefix' => $keyPrefix, + 'keySuffix' => $keySuffix, + ); + + $reshook = $hookmanager->executeHooks('getPostSearchFieldValue', $parameters, $this); // Note that $object may have been modified by hook + if ($reshook > 0) { + return $value; + } + + $this->clearErrors(); + $field = $this->getFieldClass($fieldInfos->type); + if (isset($field)) { + $value = $field->getPostSearchFieldValue($fieldInfos, $key, $defaultValue, $keyPrefix, $keySuffix); + } else { + $value = $defaultValue; + } + + return $value; + } + + /** + * Return HTML string to put an input search field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of attribute + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @param int<0,1> $noNewButton Force to not show the new button on field that are links to object + * @return string + */ + public function printInputSearchField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '', $noNewButton = 0) + { + global $hookmanager; + + $overwrite_before = ''; + $overwrite_content = ''; + $overwrite_after = ''; + + $parameters = array( + 'fieldInfos' => &$fieldInfos, + 'key' => $key, + 'value' => &$value, + 'keyPrefix' => $keyPrefix, + 'keySuffix' => $keySuffix, + 'moreCss' => $moreCss, + 'moreAttrib' => $moreAttrib, + 'noNewButton' => $noNewButton, + 'overwrite_before' => &$overwrite_before, + 'overwrite_content' => &$overwrite_content, + 'overwrite_after' => &$overwrite_after, + ); + + $hookmanager->executeHooks('printInputSearchField', $parameters, $this); // Note that $this may have been modified by hook + + if (!empty($fieldInfos->computed)) { + return ''; + } + + $out = $overwrite_before; + if (empty($overwrite_content)) { + $this->clearErrors(); + $field = $this->getFieldClass($fieldInfos->type); + if (isset($field)) { + $moreCss = $field->getInputCss($fieldInfos, $moreCss); + + $out .= $field->printInputSearchField($fieldInfos, $key, $value, $keyPrefix, $keySuffix, $moreCss, $moreAttrib); + } else { + $out .= $this->errorsToString(); + } + } else { + $out .= $overwrite_content; + } + $out .= $overwrite_after; + + return $out; + } + + /** + * Return HTML string to put an input field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of attribute + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @param int<0,1> $noNewButton Force to not show the new button on field that are links to object + * @return string + */ + public function printInputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '', $noNewButton = 0) + { + global $hookmanager, $langs; + + $overwrite_before = ''; + $overwrite_content = ''; + $overwrite_after = ''; + + $parameters = array( + 'fieldInfos' => &$fieldInfos, + 'key' => $key, + 'value' => &$value, + 'keyPrefix' => $keyPrefix, + 'keySuffix' => $keySuffix, + 'moreCss' => $moreCss, + 'moreAttrib' => $moreAttrib, + 'noNewButton' => $noNewButton, + 'overwrite_before' => &$overwrite_before, + 'overwrite_content' => &$overwrite_content, + 'overwrite_after' => &$overwrite_after, + ); + + $hookmanager->executeHooks('printInputField', $parameters, $this); // Note that $this may have been modified by hook + + if (!empty($fieldInfos->computed)) { + return '' . $langs->trans("AutomaticallyCalculated") . ''; + } + + if (!$fieldInfos->editable) { + return $this->printOutputField($fieldInfos, $key, $value, $keyPrefix, $keySuffix, $moreCss, $moreAttrib); + } + + // Get validation error + $fieldValidationErrorMsg = $this->getFieldError($key); + + $out = $overwrite_before; + if (empty($overwrite_content)) { + $this->clearErrors(); + $field = $this->getFieldClass($fieldInfos->type); + if (isset($field)) { + $moreCss = $field->getInputCss($fieldInfos, $moreCss); + + // Add validation state class + if (!empty($fieldValidationErrorMsg)) { + $moreCss .= ' --error'; // the -- is use as class state in css : .--error can't be be defined alone it must be define with another class like .my-class.--error or input.--error + } else { + $moreCss .= ' --success'; // the -- is use as class state in css : .--success can't be be defined alone it must be define with another class like .my-class.--success or input.--success + } + + $out .= $field->printInputField($fieldInfos, $key, $value, $keyPrefix, $keySuffix, $moreCss, $moreAttrib); + } else { + $out .= $this->errorsToString(); + } + } else { + $out .= $overwrite_content; + } + if (empty($overwrite_after)) { + // Display error message for field + $out .= $this->getFieldErrorIcon($fieldValidationErrorMsg); + } else { + $out .= $overwrite_after; + } + + return $out; + } + + /** + * Return HTML string to show a field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of attribute + * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) + * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) + * @param string $moreCss Value for css to define style/length of field. + * @param string $moreAttrib To add more attributes on html input tag + * @return string + */ + public function printOutputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + global $hookmanager; + + $overwrite_before = ''; + $overwrite_content = ''; + $overwrite_after = ''; + + $parameters = array( + 'fieldInfos' => &$fieldInfos, + 'key' => $key, + 'value' => &$value, + 'keyPrefix' => $keyPrefix, + 'keySuffix' => $keySuffix, + 'moreCss' => $moreCss, + 'moreAttrib' => $moreAttrib, + 'overwrite_before' => &$overwrite_before, + 'overwrite_content' => &$overwrite_content, + 'overwrite_after' => &$overwrite_after, + ); + + $hookmanager->executeHooks('printOutputField', $parameters, $this); // Note that $object may have been modified by hook + + $out = $overwrite_before; + if (empty($overwrite_content)) { + $this->clearErrors(); + $field = $this->getFieldClass($fieldInfos->type); + if (isset($field)) { + $moreCss = $field->getInputCss($fieldInfos, $moreCss); + + $out .= $field->printOutputField($fieldInfos, $key, $value, $keyPrefix, $keySuffix, $moreCss, $moreAttrib); + } else { + $out .= $this->errorsToString(); + } + } else { + $out .= $overwrite_content; + } + $out .= $overwrite_after; + + return $out; + } + + /** + * Return HTML string to print separator field + * + * @param string $key Key of attribute + * @param object $object Object + * @param int $colspan Value of colspan to use (it must include the first column with title) + * @param string $display_type "card" for form display, "line" for document line display + * @param string $mode Show output ('view') or input ('create' or 'edit') for field + * @return string HTML code with line for separator + */ + public function printSeparator($key, &$object, $colspan = 2, $display_type = 'card', $mode = 'view') + { + global $conf, $langs; + + // TODO to adapt for field and not extra fields only + $out = ''; + + /*$tagtype = 'tr'; + $tagtype_dyn = 'td'; + + if ($display_type == 'line') { + $tagtype = 'div'; + $tagtype_dyn = 'span'; + $colspan = 0; + } + + $extrafield_param = $this->attributes[$object->table_element]['param'][$key]; + $extrafield_param_list = array(); + if (!empty($extrafield_param) && is_array($extrafield_param)) { + $extrafield_param_list = array_keys($extrafield_param['options']); + } + + // Set $extrafield_collapse_display_value (do we have to collapse/expand the group after the separator) + $extrafield_collapse_display_value = -1; + $expand_display = false; + if (is_array($extrafield_param_list) && count($extrafield_param_list) > 0) { + $extrafield_collapse_display_value = intval($extrafield_param_list[0]); + $expand_display = ((isset($_COOKIE['DOLUSER_COLLAPSE_' . $object->table_element . '_extrafields_' . $key]) || GETPOSTINT('ignorecollapsesetup')) ? (!empty($_COOKIE['DOLUSER_COLLAPSE_' . $object->table_element . '_extrafields_' . $key])) : !($extrafield_collapse_display_value == 2)); + } + $disabledcookiewrite = 0; + if ($mode == 'create') { + // On create mode, force separator group to not be collapsible + $extrafield_collapse_display_value = 1; + $expand_display = true; // We force group to be shown expanded + $disabledcookiewrite = 1; // We keep status of group unchanged into the cookie + } + + $out = '<' . $tagtype . ' id="trextrafieldseparator' . $key . (!empty($object->id) ? '_' . $object->id : '') . '" class="trextrafieldseparator trextrafieldseparator' . $key . (!empty($object->id) ? '_' . $object->id : '') . '">'; + $out .= '<' . $tagtype_dyn . ' ' . (!empty($colspan) ? 'colspan="' . $colspan . '"' : '') . '>'; + // Some js code will be injected here to manage the collapsing of fields + // Output the picto + $out .= ''; + $out .= ' '; + $out .= ''; + $out .= $langs->trans($this->attributes[$object->table_element]['label'][$key]); + $out .= ''; + $out .= ''; + $out .= ''; + + $collapse_group = $key . (!empty($object->id) ? '_' . $object->id : ''); + //$extrafields_collapse_num = $this->attributes[$object->table_element]['pos'][$key].(!empty($object->id)?'_'.$object->id:''); + + if ($extrafield_collapse_display_value == 1 || $extrafield_collapse_display_value == 2) { + // Set the collapse_display status to cookie in priority or if ignorecollapsesetup is 1, if cookie and ignorecollapsesetup not defined, use the setup. + $this->expand_display[$collapse_group] = $expand_display; + + if (!empty($conf->use_javascript_ajax)) { + $out .= '' . "\n"; + $out .= '' . "\n"; + } + } else { + $this->expand_display[$collapse_group] = 1; + }*/ + + return $out; + } +} diff --git a/htdocs/core/class/html.form.class.php b/htdocs/core/class/html.form.class.php index 35d55254b45..52e1f42ed13 100644 --- a/htdocs/core/class/html.form.class.php +++ b/htdocs/core/class/html.form.class.php @@ -74,18 +74,25 @@ class Form /** @var array */ public $result; - /** @var int Number of line returned by method to generate combo select */ + /** @var int Number of lines returned by method to generate combo select */ public $num; // Cache arrays + /** @var array */ public $cache_types_paiements = array(); + /** @var array */ public $cache_conditions_paiements = array(); + /** @var array */ public $cache_transport_mode = array(); /** @var array */ public $cache_availability = array(); + /** @var array */ public $cache_demand_reason = array(); + /** @var array */ public $cache_types_fees = array(); + /** @var array */ public $cache_vatrates = array(); + /** @var array */ public $cache_invoice_subtype = array(); /** @var array */ public $cache_rule_for_lines_dates = array(); @@ -2593,8 +2600,10 @@ class Form } $out .= '
  • '; + $userstatic->fetch($value['id']); - $out .= $userstatic->getNomUrl(-1); + $out .= $userstatic->getNomUrl(-4); + if ($i == 0) { $ownerid = $value['id']; $out .= ' (' . $langs->trans("Owner") . ')'; @@ -4753,7 +4762,7 @@ class Form $label = $langs->trans($obj->code); // So translation key SRC_XXX will work } - $tmparray[$obj->rowid]['id'] = $obj->rowid; + $tmparray[$obj->rowid]['id'] = (int) $obj->rowid; $tmparray[$obj->rowid]['code'] = $obj->code; $tmparray[$obj->rowid]['label'] = $label; $i++; @@ -7202,7 +7211,7 @@ class Form * @param string $selected Preselected currency code * @param string $htmlname Name of HTML select list * @param integer $useempty 1=Add empty line - * @param string $filter Optional filters criteras (example: 'code <> x', ' in (1,3)') + * @param string $filter Optional filters criteras (example: 'code <> x', ' in (1,3)'). Do not use external string here. * @param bool $excludeConfCurrency false = If company current currency not in table, we add it into list. Should always be available. * true = we are in currency_rate update , we don't want to see conf->currency in select * @param string $morecss More css @@ -7298,7 +7307,7 @@ class Form $obj = $this->db->fetch_object($resql); $tmparray = array(); - $tmparray['rowid'] = $obj->rowid; + $tmparray['rowid'] = (int) $obj->rowid; $tmparray['type_vat'] = ($obj->type_vat <= 0 ? 0 : $obj->type_vat); // Some version have type_vat corrupted with value -1 $tmparray['code'] = $obj->code; $tmparray['txtva'] = $obj->taux; @@ -7791,11 +7800,13 @@ class Form $reduceformat = (!empty($conf->dol_optimize_smallscreen) ? 1 : 0); // Test on original $format param. if ($reduceformat) { $format = str_replace('%Y', '%y', $langs->transnoentitiesnoconv("FormatDateShortInput")); // FormatDateShortInput for dol_print_date / FormatDateShortJavaInput that is same for javascript + $formatjslong = $langs->transnoentitiesnoconv("FormatDateShortJavaInput"); // don't trust the name $formatjs = str_replace('yyyy', 'yy', $langs->transnoentitiesnoconv("FormatDateShortJavaInput")); $formatjquery = str_replace('yyyy', 'yy', $langs->trans("FormatDateShortJQueryInput")); } else { - $format = $langs->transnoentitiesnoconv("FormatDateShortInput"); // FormatDateShortInput for dol_print_date / FormatDateShortJavaInput that is same for javascript - $formatjs = $langs->transnoentitiesnoconv("FormatDateShortJavaInput"); // FormatDateShortInput for dol_print_date / FormatDateShortJavaInput that is same for javascript + $format = $langs->transnoentitiesnoconv("FormatDateShortInput"); // FormatDateShortInput for dol_print_date is same than FormatDateShortJavaInput for javascript + $formatjslong = $langs->transnoentitiesnoconv("FormatDateShortJavaInput"); // don't trust the name + $formatjs = $langs->transnoentitiesnoconv("FormatDateShortJavaInput"); // FormatDateShortInput for dol_print_date is same than FormatDateShortJavaInput for javascript $formatjquery = $langs->trans("FormatDateShortJQueryInput"); } @@ -7808,13 +7819,12 @@ class Form // Calendrier popup version eldy if ($usecalendar == "eldy") { // To have this manager working back, you must retrieve all functions showDP child found into the lib_head.js of v4 for example - // and use load the js so the call of showDP will works. - + // and load the js that contains them so the call of showDP will works. /* // Input area to enter date manually $retstring .= 'trans("FormatDateShortJavaInput")) . '\'); "'; // FormatDateShortInput for dol_print_date / FormatDateShortJavaInput that is same for javascript + $retstring .= ' onChange="dpChangeDay(\'' . dol_escape_js($prefix) . '\',\'' . dol_escape_js($formatjslong")) . '\'); "'; // FormatDateShortInput for dol_print_date is same than FormatDateShortJavaInput for javascript $retstring .= ' autocomplete="off">'; // Icon calendar @@ -7873,13 +7883,13 @@ class Form $retstring .= '' . img_object($langs->trans("Disabled"), 'calendarday', 'class="datecallink"') . ''; - $retstring = $retstringbutton . $retstring; + $retstringbutton = ''; + $retstring .= $retstringbutton; } $retstring .= ''; @@ -9249,7 +9259,7 @@ class Form $parent_properties = getElementProperties($objecttmp->parent_element); $sql .= " INNER JOIN " . $this->db->prefix() . $this->db->sanitize($parent_properties['table_element']) . " as o ON o.rowid = t.".$objecttmp->fk_parent_attribute; } - if (in_array($objecttmp->parent_element, ['commande', 'propal', 'facture', 'expedition'])) { + if (!empty($objecttmp->parent_element) && in_array($objecttmp->parent_element, ['commande', 'propal', 'facture', 'expedition'])) { $sql .= " LEFT JOIN " . $this->db->prefix() . "product as p ON p.rowid = t.fk_product"; } if (!empty($objecttmp->ismultientitymanaged)) { @@ -9279,7 +9289,7 @@ class Form // If table need a multientity restriction if (!empty($objecttmp->ismultientitymanaged)) { if ($objecttmp->ismultientitymanaged == 1) { - $sql .= " AND t.entity IN (" . getEntity($objecttmp->table_element) . ")"; + $sql .= " AND t.entity IN (" . getEntity($objecttmp->element) . ")"; } if (!is_numeric($objecttmp->ismultientitymanaged)) { $sql .= " AND parenttable.entity = t." . $this->db->sanitize($tmparray[0]); @@ -10860,6 +10870,15 @@ class Form $object->load_previous_next_ref((isset($object->next_prev_filter) ? $object->next_prev_filter : ''), $fieldid, $nodbprefix); $navurl = $_SERVER["PHP_SELF"]; + + // Special case for token card + if ($paramid == 'api_token_card') { + if (preg_match('/\/user\/api_token/', $navurl)) { + $navurl = preg_replace('/card/', 'list', $navurl); + $paramid = 'id'; + } + } + // Special case for project/task page if ($paramid == 'project_ref') { if (preg_match('/\/tasks\/(task|contact|note|document)\.php/', $navurl)) { // TODO Remove this when nav with project_ref on task pages are ok @@ -12296,7 +12315,7 @@ class Form $ret .= ''; $ret .= "\n"; - $ret .= ''; + $ret .= ''; $ret .= ''; $ret .= ''; @@ -12847,4 +12866,417 @@ class Form return '
    '; } + + /** + * Html for input with label + * + * @param string $type Type of input : button, checkbox, color, email, hidden, month, number, password, radio, range, tel, text, time, url, week + * @param string $name Name + * @param string $value [=''] Value + * @param string $id [=''] Id + * @param string $morecss [=''] Class + * @param string $moreparam [=''] Add attributes (checked, required, etc) + * @param string $label [=''] Label + * @param string $addInputLabel [=''] Add label for input + * @return string Html for input with label + */ + public function inputType($type, $name, $value = '', $id = '', $morecss = '', $moreparam = '', $label = '', $addInputLabel = '') + { + $out = ''; + if ($label != '') { + $out .= '
  • ' . $value . '
  • '; + } + if (!empty($toPrint)) { + $out = '
      ' . implode(' ', $toPrint) . '
    '; + } + + return $out; + } + + /** + * Html for show stars + * + * @param int $size Number of stars + * @param int $value Value + * @return string Html for show stars + */ + public function outputStars($size, $value) + { + $out = '
    '; + for ($i = 1; $i <= $size; $i++) { + $out .= '' . img_picto('', 'fontawesome_star_fas') . ''; + } + $out .= '
    '; + + return $out; + } + + /** + * Html for show icon + * + * @param string $value Value + * @return string Html for show icon + */ + public function outputIcon($value) + { + $out = ''; + + return $out; + } + + /** + * Html for show geo point + * + * @param string $value Value + * @param string $type Type (linestrg, multipts, point, polygon) + * @return string Html for show geo point + */ + public function outputGeoPoint($value, $type) + { + $out = ''; + + if (!empty($value)) { + require_once DOL_DOCUMENT_ROOT . '/core/class/dolgeophp.class.php'; + $dolgeophp = new DolGeoPHP($this->db); + if ($type == 'point') { + $out = $dolgeophp->getXYString($value); + } else { // multipts, linestrg, polygon + $out = $dolgeophp->getPointString($value); + } + } + + return $out; + } + + /** + * Return link of object + * + * @param CommonObject $object Object handler + * @param int $withpicto Add picto into link + * @param string $option Where point the link ('stock', 'composition', 'category', 'supplier', '') + * @param int $maxlength Maxlength of ref + * @param int $save_lastsearch_value -1=Auto, 0=No save of lastsearch_values when clicking, 1=Save lastsearch_values whenclicking + * @param int $notooltip No tooltip + * @param string $morecss ''=Add more css on link + * @param int $add_label 0=Default, 1=Add label into string, >1=Add first chars into string + * @param string $sep ' - '=Separator between ref and label if option 'add_label' is set + * @return string String with URL + */ + public function getNomUrl(&$object, $withpicto = 0, $option = '', $maxlength = 0, $save_lastsearch_value = -1, $notooltip = 0, $morecss = '', $add_label = 0, $sep = ' - ') + { + if (is_object($object) && method_exists($object, 'getNomUrl')) { + $out = $object->getNomUrl($withpicto, $option, $maxlength, $save_lastsearch_value, $notooltip, $morecss, $add_label, $sep); + return $out; + } else { + return ''; + } + } } diff --git a/htdocs/core/class/html.formactions.class.php b/htdocs/core/class/html.formactions.class.php index 927e697b98e..b77624dff39 100644 --- a/htdocs/core/class/html.formactions.class.php +++ b/htdocs/core/class/html.formactions.class.php @@ -2,7 +2,7 @@ /* Copyright (c) 2008-2012 Laurent Destailleur * Copyright (C) 2010-2012 Regis Houssin * Copyright (C) 2010-2018 Juanjo Menent - * Copyright (C) 2024 Frédéric France + * Copyright (C) 2024-2025 Frédéric France * Copyright (C) 2024 MDW * * This program is free software; you can redistribute it and/or modify @@ -57,62 +57,69 @@ class FormActions /** * Show list of action status * - * @param string $formname Name of form where select is included - * @param string $selected Preselected value (-1..100) - * @param int $canedit 1=can edit, 0=read only - * @param string $htmlname Name of html prefix for html fields (selectX and valX) - * @param integer $showempty Show an empty line if select is used - * @param integer $onlyselect 0=Standard, 1=Hide percent of completion and force usage of a select list, 2=Same than 1 and add "Incomplete (Todo+Running) - * @param string $morecss More css on select field - * @return void + * @param string $formname Name of form where select is included + * @param string $selected Preselected value (-1..100) + * @param int<0,1> $canedit 1=can edit, 0=read only + * @param string $htmlname Name of html prefix for html fields (selectX and valX) + * @param integer $showempty Show an empty line if select is used + * @param integer $onlyselect 0=Standard, 1=Hide percent of completion and force usage of a select list, 2=Same than 1 and add "Incomplete (Todo+Running) + * @param string $morecss More css on select field + * @param int<0,1> $nooutput 1=No output, return string. 0=Print on output + * @return void|string + * @phpstan-return ($nooutput is 1 ? void : string) */ - public function form_select_status_action($formname, $selected, $canedit = 1, $htmlname = 'complete', $showempty = 0, $onlyselect = 0, $morecss = 'maxwidth100') + public function form_select_status_action($formname, $selected, $canedit = 1, $htmlname = 'complete', $showempty = 0, $onlyselect = 0, $morecss = 'maxwidth100', $nooutput = 0) { // phpcs:enable global $langs, $conf; - $listofstatus = array( + $listofstatus = [ 'na' => $langs->trans("ActionNotApplicable"), '0' => $langs->trans("ActionsToDoShort"), '50' => $langs->trans("ActionRunningShort"), '100' => $langs->trans("ActionDoneShort") - ); + ]; // +ActionUncomplete + $out = ''; + if (!empty($conf->use_javascript_ajax) || $onlyselect) { //var_dump($selected); if ($selected == 'done') { $selected = '100'; } - print ''; if ($showempty) { - print ''; + $out .= ''; } foreach ($listofstatus as $key => $val) { - print ''; - if ($key == '50' && $onlyselect == 2) { - print ''; + $out .= ''; + // Add a choice "Incomplete" at second position + if ($key === 'na' && $onlyselect == 2) { + $out .= ''; } } - print ''; + $out .= ''; if ($selected == 0 || $selected == 100) { $canedit = 0; } - print ajax_combobox('select'.$htmlname, array(), 0, 0, 'resolve', '-1', $morecss); + $out .= ajax_combobox('select'.$htmlname, array(), 0, 0, 'resolve', '-1', $morecss); if (empty($onlyselect)) { - print ' = 0) ? '' : ' disabled').'>'; - print '%'; + $out .= ' = 0) ? '' : ' disabled').'>'; + $out .= '%'; } } else { - print ' %'; + $out .= ' %'; } if (!empty($conf->use_javascript_ajax)) { - print "\n"; - print '\n"; } + + if ($nooutput) { + return $out; + } else { + print $out; + } } @@ -284,16 +297,39 @@ class FormActions print ''.$actioncomm->getNomUrl(1, -1).''; // Date - print ''.dol_print_date($actioncomm->datep, 'dayhourreduceformat', 'tzuserrel'); - if ($actioncomm->datef) { + print ''; + if ($actioncomm->datef) { // There is also a end date $tmpa = dol_getdate($actioncomm->datep); $tmpb = dol_getdate($actioncomm->datef); if ($tmpa['mday'] == $tmpb['mday'] && $tmpa['mon'] == $tmpb['mon'] && $tmpa['year'] == $tmpb['year']) { + // The same day if ($tmpa['hours'] != $tmpb['hours'] || $tmpa['minutes'] != $tmpb['minutes']) { - print '-'.dol_print_date($actioncomm->datef, 'hour', 'tzuserrel'); + print dol_print_date($actioncomm->datep, 'dayreduceformat', 'tzuserrel'); + print '
    '; + print dol_print_date($actioncomm->datep, 'hourreduceformat', 'tzuserrel'); + print '-'.dol_print_date($actioncomm->datef, 'hourreduceformat', 'tzuserrel'); + print ''; + } else { + print dol_print_date($actioncomm->datep, 'dayreduceformat', 'tzuserrel'); + print '
    '; + print dol_print_date($actioncomm->datep, 'hourreduceformat', 'tzuserrel'); + print ''; } } else { - print '-'.dol_print_date($actioncomm->datef, 'dayhourreduceformat', 'tzuserrel'); + // Not the same day + print '
    '; + print dol_print_date($actioncomm->datep, 'dayreduceformat', 'tzuserrel'); + print '
    '; + print dol_print_date($actioncomm->datep, 'hourreduceformat', 'tzuserrel'); + print ''; + print '
    '; + print ' '; + print '
    '; + print dol_print_date($actioncomm->datef, 'dayreduceformat', 'tzuserrel'); + print '
    '; + print dol_print_date($actioncomm->datef, 'hourreduceformat', 'tzuserrel'); + print ''; + print '
    '; } } print ''; diff --git a/htdocs/core/class/html.formfile.class.php b/htdocs/core/class/html.formfile.class.php index 0e9573aba58..6ab8eaca887 100644 --- a/htdocs/core/class/html.formfile.class.php +++ b/htdocs/core/class/html.formfile.class.php @@ -160,7 +160,7 @@ class FormFile * @param int $sectionid If upload must be done inside a particular ECM section (is sectionid defined, sectiondir must not be) * @param int $perm Value of permission to allow upload * @param int $size Length of input file area. Deprecated. - * @param ?CommonObject $object Object to use (when attachment is done on an element) + * @param CommonObject|BlockedLog|null $object Object to use (when attachment is done on an element) * @param string $options Add an option column * @param int<0,1> $useajax Use fileupload ajax (0=never, 1=if enabled, 2=always whatever is option). * Deprecated 2 should never be used and if 1 is used, option should not be enabled. @@ -1041,7 +1041,7 @@ class FormFile $fulllink = $urlwithroot.'/document.php'.($paramlink ? '?'.$paramlink : ''); $out .= ''.img_picto($langs->trans("FileSharedViaALink"), 'globe').' '; - $out .= ''; + $out .= ''; $out .= ajax_autoselect('downloadlink'.$file['rowid']); } else { //print ''.$langs->trans("FileNotShared").''; @@ -1408,6 +1408,7 @@ class FormFile print load_fiche_titre($title ? $title : $langs->trans("AttachedFiles"), $morehtmlright, 'file-upload', 0, '', 'table-list-of-attached-files'); } if (!empty($moreoptions) && $moreoptions['afteruploadtitle']) { + print ''; print '
    '.$moreoptions['afteruploadtitle'].'
    '; } @@ -1652,7 +1653,7 @@ class FormFile print ''; print ''.img_picto($langs->trans("FileSharedViaALink"), 'globe').' '; - print ''; + print ''; } else { //print ''.$langs->trans("FileNotShared").''; } @@ -2179,7 +2180,7 @@ class FormFile print ''; print img_picto($langs->trans("FileSharedViaALink"), 'globe').' '; - print ''; + print ''; } //if (!empty($useinecm) && $useinecm != 6) print ''.img_picto($langs->trans("FileSharedViaALink"), 'globe').' '; - print ''; + print ''; } print ''; print ''; diff --git a/htdocs/core/class/html.formother.class.php b/htdocs/core/class/html.formother.class.php index 2f54fe25b77..ec659d26a3b 100644 --- a/htdocs/core/class/html.formother.class.php +++ b/htdocs/core/class/html.formother.class.php @@ -1082,7 +1082,7 @@ class FormOther * * @param int|string $selected Preselected value * @param string $htmlname Name of HTML select object - * @param int $useempty Show empty in list + * @param int|string $useempty Show empty in list * @param int $longlabel Show long label (1) or short label (0) * @param string $morecss More Css * @param bool $addjscombo Add js combo @@ -1103,7 +1103,11 @@ class FormOther $select_month = ''; $out .= ''; $out .= ''; - $out .= ''; + // Status ($percent can be 'na'or < 100 or 100) + $out .= ''; + $out .= $formactions->form_select_status_action('formaction', $percent, 1, 'search_complete', 1, 2, 'search_status width100 onrightofpage', 1); + $out .= ''; // Action column if (!getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) { $out .= ''; @@ -2387,6 +2399,61 @@ function show_actions_done($conf, $langs, $db, $filterobj, $objcon = null, $nopr $out .= ''; // Date + $out .= ''; + if ($histo[$key]['dateend']) { // There is also a end date + $tmpa = dol_getdate($histo[$key]['datestart']); + $tmpb = dol_getdate($histo[$key]['dateend']); + if ($tmpa['mday'] == $tmpb['mday'] && $tmpa['mon'] == $tmpb['mon'] && $tmpa['year'] == $tmpb['year']) { + // The same day + if ($tmpa['hours'] != $tmpb['hours'] || $tmpa['minutes'] != $tmpb['minutes']) { + $out .= dol_print_date($histo[$key]['datestart'], 'dayreduceformat', 'tzuserrel'); + $out .= '
    '; + $out .= dol_print_date($histo[$key]['datestart'], 'hourreduceformat', 'tzuserrel'); + $out .= '-'.dol_print_date($histo[$key]['dateend'], 'hourreduceformat', 'tzuserrel'); + $out .= ''; + } else { + $out .= dol_print_date($histo[$key]['datestart'], 'dayreduceformat', 'tzuserrel'); + $out .= '
    '; + $out .= dol_print_date($histo[$key]['datestart'], 'hourreduceformat', 'tzuserrel'); + $out .= ''; + } + } else { + // Not the same day + $out .= '
    '; + $out .= dol_print_date($histo[$key]['datestart'], 'dayreduceformat', 'tzuserrel'); + $out .= '
    '; + $out .= dol_print_date($histo[$key]['datestart'], 'hourreduceformat', 'tzuserrel'); + $out .= ''; + $out .= '
    '; + $out .= ' '; + $out .= '
    '; + $out .= dol_print_date($histo[$key]['dateend'], 'dayreduceformat', 'tzuserrel'); + $out .= '
    '; + $out .= dol_print_date($histo[$key]['dateend'], 'hourreduceformat', 'tzuserrel'); + $out .= ''; + $out .= '
    '; + } + } + // Add the late warning + $late = 0; + if ($histo[$key]['percent'] == 0 && $histo[$key]['datestart'] && $histo[$key]['datestart'] < ($now - $delay_warning)) { + $late = 1; + } + if ($histo[$key]['percent'] == 0 && !$histo[$key]['datestart'] && $histo[$key]['dateend'] && $histo[$key]['datestart'] < ($now - $delay_warning)) { + $late = 1; + } + if ($histo[$key]['percent'] > 0 && $histo[$key]['percent'] < 100 && $histo[$key]['dateend'] && $histo[$key]['dateend'] < ($now - $delay_warning)) { + $late = 1; + } + if ($histo[$key]['percent'] > 0 && $histo[$key]['percent'] < 100 && !$histo[$key]['dateend'] && $histo[$key]['datestart'] && $histo[$key]['datestart'] < ($now - $delay_warning)) { + $late = 1; + } + if ($late) { + $out .= img_warning($langs->trans("Late")) . ' '; + } + $out .= ''; + + /* $out .= ''; $out .= dol_print_date($histo[$key]['datestart'], 'dayhour', 'tzuserrel'); if ($histo[$key]['dateend'] && $histo[$key]['dateend'] != $histo[$key]['datestart']) { @@ -2398,6 +2465,7 @@ function show_actions_done($conf, $langs, $db, $filterobj, $objcon = null, $nopr $out .= '-' . dol_print_date($histo[$key]['dateend'], 'dayhour', 'tzuserrel'); } } + // Add the late warning $late = 0; if ($histo[$key]['percent'] == 0 && $histo[$key]['datestart'] && $histo[$key]['datestart'] < ($now - $delay_warning)) { $late = 1; @@ -2415,6 +2483,7 @@ function show_actions_done($conf, $langs, $db, $filterobj, $objcon = null, $nopr $out .= img_warning($langs->trans("Late")) . ' '; } $out .= "\n"; + */ // Author of event $out .= ''; @@ -2461,9 +2530,7 @@ function show_actions_done($conf, $langs, $db, $filterobj, $objcon = null, $nopr $out .= ''; $out .= $actionstatic->getTypePicto(); - //if (empty($conf->dol_optimize_smallscreen)) { $out .= $labelOfTypeToShow; - //} $out .= ''; // Title/Label of event @@ -2537,7 +2604,9 @@ function show_actions_done($conf, $langs, $db, $filterobj, $objcon = null, $nopr } // Status / Progression - $out .= '' . $actionstatic->LibStatut($histo[$key]['percent'], 2, 0, $histo[$key]['datestart']) . ''; + $out .= ''; + $out .= $actionstatic->LibStatut($histo[$key]['percent'], 2, 0, $histo[$key]['datestart']); + $out .= ''; // Action column if (!getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) { @@ -2576,8 +2645,6 @@ function show_actions_done($conf, $langs, $db, $filterobj, $objcon = null, $nopr */ function show_subsidiaries($conf, $langs, $db, $object) { - global $user; - $i = -1; $sql = "SELECT s.rowid, s.client, s.fournisseur, s.nom as name, s.name_alias, s.email, s.address, s.zip, s.town, s.code_client, s.code_fournisseur, s.code_compta, s.code_compta_fournisseur, s.canvas, s.status"; @@ -2694,7 +2761,7 @@ function addEventTypeSQL(&$sql, $actioncode, $sqlANDOR = "AND") } /** - * Add Event Type SQL + * Add more SQL filters for event list * * @param string $sql $sql modified * @param string $donetodo donetodo @@ -2711,6 +2778,21 @@ function addOtherFilterSQL(&$sql, $donetodo, $now, $filters) } elseif ($donetodo == 'done') { $sql .= " AND (a.percent = 100 OR (a.percent = -1 AND a.datep <= '" . $db->idate($now) . "'))"; } + if (is_array($filters) && isset($filters['search_complete']) && $filters['search_complete'] === 'na') { + $sql .= " AND a.percent = -1"; + } + if (is_array($filters) && isset($filters['search_complete']) && $filters['search_complete'] === '0') { + $sql .= " AND a.percent = 0"; + } + if (is_array($filters) && isset($filters['search_complete']) && $filters['search_complete'] === '50') { + $sql .= " AND a.percent > 0 AND a.percent < 100"; + } + if (is_array($filters) && isset($filters['search_complete']) && $filters['search_complete'] === 'todo') { + $sql .= " AND a.percent >= 0 AND a.percent < 100"; + } + if (is_array($filters) && isset($filters['search_complete']) && $filters['search_complete'] === '100') { + $sql .= " AND a.percent = 100"; + } if (is_array($filters) && !empty($filters['search_agenda_label'])) { $sql .= natural_search('a.label', $filters['search_agenda_label']); } @@ -2864,7 +2946,7 @@ function htmlPrintOnlineFooter($fromcompany, $langs, $addformmessage = 0, $suffi } // Capital if ($fromcompany->capital) { - $line1 .= ($line1 ? " - " : "") . $langs->transnoentities("CapitalOf", (string) $fromcompany->capital) . " " . $langs->transnoentities("Currency" . $conf->currency); + $line1 .= ($line1 ? " - " : "") . $langs->transnoentities("CapitalOf", (string) $fromcompany->capital) . " " . $langs->transnoentities("Currency" . getDolCurrency()); } // Prof Id 1 if ($fromcompany->idprof1 && ($fromcompany->country_code != 'FR' || !$fromcompany->idprof2)) { diff --git a/htdocs/core/lib/files.lib.php b/htdocs/core/lib/files.lib.php index 32559cd332e..8c68f4f7c48 100644 --- a/htdocs/core/lib/files.lib.php +++ b/htdocs/core/lib/files.lib.php @@ -47,9 +47,9 @@ function dol_basename($pathfile) * @param string $utf8_path Starting path from which to search. This is a full path. * @param string $types Can be "directories", "files", or "all" * @param int $recursive Determines whether subdirectories are searched - * @param string|string[] $filter Regex or Array of Regex filter to restrict list. The regex value must be escaped for '/' by doing preg_quote($var,'/'), since this char is used for preg_match function, - * but must NOT contains the start and end '/'. Filter is checked into basename only. - * @param string|string[] $excludefilter Array of Regex for exclude filter (example: array('(\.meta|_preview.*\.png)$','^\.')). Exclude is checked both into fullpath and into basename (So '^xxx' may exclude 'xxx/dirscanned/...' and dirscanned/xxx'). + * @param string|string[]|null $filter Regex or Array of Regex filter to restrict list. The regex value must be escaped for '/' by doing preg_quote($var,'/'), since this char is used for preg_match function, + * but must NOT contains the start and end '/'. Filter is checked into basename only. + * @param string|string[]|null $excludefilter Array of Regex for exclude filter (example: array('(\.meta|_preview.*\.png)$','^\.')). Exclude is checked both into fullpath and into basename (So '^xxx' may exclude 'xxx/dirscanned/...' and dirscanned/xxx'). * @param string $sortcriteria Sort criteria ('','fullname','relativename','name','date','size') * @param int $sortorder Sort order (SORT_ASC, SORT_DESC) * @param int $mode 0=Return array minimum keys loaded (faster), 1=Force all keys like date and size to be loaded (slower), 2=Force load of date only, 3=Force load of size only, 4=Force load of perm @@ -70,7 +70,7 @@ function dol_dir_list($utf8_path, $types = "all", $recursive = 0, $filter = "", // Verify filters (only on the first call of the function) $filter_ok = true; - if (!is_array($filter)) { + if (!empty($filter) && !is_array($filter)) { if (strlen($filter) > 25000) { // Note that limit depends on syntax of filter dol_syslog("Value for filter is too large", LOG_ERR); $filter_ok = false; @@ -3641,6 +3641,30 @@ function dol_check_secure_access_document($modulepart, $original_file, $entity, $accessallowed = 1; } $original_file = $conf->member->dir_output.'/'.$original_file; + } elseif ($modulepart == 'ticket' && !empty($conf->ticket->multidir_output[$entity])) { + // Wrapping for events + if ($fuser->hasRight('ticket', $read)) { + $accessallowed = 1; + } + if (!isset($_SESSION['email_customer'])) { + $sqlprotectagainstexternals = ''; + } else { + $email_split = explode('@', $_SESSION['email_customer']); + + $sqlprotectagainstexternals = 'SELECT t.rowid, t.fk_soc FROM '.MAIN_DB_PREFIX.'ticket t'; + $sqlprotectagainstexternals.= ' LEFT JOIN '.MAIN_DB_PREFIX.'element_contact ec ON ec.element_id = t.rowid'; + $sqlprotectagainstexternals.= ' LEFT JOIN '.MAIN_DB_PREFIX.'socpeople c ON c.rowid = ec.fk_socpeople'; + $sqlprotectagainstexternals.= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_type_contact tc ON tc.element = "ticket" AND tc.rowid = ec.fk_c_type_contact'; + $sqlprotectagainstexternals.= ' WHERE t.ref LIKE "'.$db->sanitize($refname).'"'; + $sqlprotectagainstexternals.= ' AND ('; + $sqlprotectagainstexternals.= ' ('; + $sqlprotectagainstexternals.= ' tc.rowid IS NOT NULL'; + $sqlprotectagainstexternals.= ' AND c.email = "'.$db->sanitize($email_split[0]).'@'.$db->sanitize($email_split[1]).'"'; + $sqlprotectagainstexternals.= ' )'; + $sqlprotectagainstexternals.= ' OR t.origin_email = "'.$db->sanitize($email_split[0]).'@'.$db->sanitize($email_split[1]).'"'; + $sqlprotectagainstexternals.= ' )'; + } + $original_file = $conf->ticket->multidir_output[$entity].'/'.$original_file; // If modulepart=module_user_temp Allows any module to open a file if file is in directory called DOL_DATA_ROOT/modulepart/temp/iduser // If modulepart=module_temp Allows any module to open a file if file is in directory called DOL_DATA_ROOT/modulepart/temp // If modulepart=module_user Allows any module to open a file if file is in directory called DOL_DATA_ROOT/modulepart/iduser diff --git a/htdocs/core/lib/functions.lib.php b/htdocs/core/lib/functions.lib.php index f3fc86144f8..a2136039999 100644 --- a/htdocs/core/lib/functions.lib.php +++ b/htdocs/core/lib/functions.lib.php @@ -126,11 +126,11 @@ if (!function_exists('str_contains')) { * Return the full path of the directory where a module (or an object of a module) stores its files. * Path may depends on the entity if a multicompany module is enabled. * - * @param CommonObject $object Dolibarr common object. - * @param string $module Override object element, for example to use 'mycompany' instead of 'societe' - * @param int $forobject Return the more complete path for the given object instead of for the module only. - * @param string $mode 'output' (full main dir) or 'outputrel' (relative dir) or 'temp' (full dir for temporary files) or 'version' (full dir for archived files) - * @return string|null The path of the relative directory of the module, ending with / + * @param CommonObject|BlockedLog $object Dolibarr common object. + * @param string $module Override object element, for example to use 'mycompany' instead of 'societe' + * @param int $forobject Return the more complete path for the given object instead of for the module only. + * @param string $mode 'output' (full main dir) or 'outputrel' (relative dir) or 'temp' (full dir for temporary files) or 'version' (full dir for archived files) + * @return string|null The path of the relative directory of the module, ending with / * @since Dolibarr V18 */ function getMultidirOutput($object, $module = '', $forobject = 0, $mode = 'output') @@ -297,6 +297,17 @@ function getDolCurrency() return (string) $conf->currency; } +/** + * Return the current entity + * + * @return int Value returned + */ +function getDolEntity() +{ + global $conf; + return (int) $conf->entity; +} + /** * Return the default context page string * @@ -383,6 +394,7 @@ define( /** * Is Dolibarr module enabled + * Note: "isModEnabled('delivery_note')" must be replacedwith "isModEnabled('shipping') && getDolGlobalString('MAIN_SUBMODULE_EXPEDITION')" * * @param string $module Module name to check * @return boolean True if module is enabled @@ -400,16 +412,6 @@ function isModEnabled($module) $arrayconv['supplier_order'] = 'fournisseur'; $arrayconv['supplier_invoice'] = 'fournisseur'; } - // Special case. - // @TODO Replace isModEnabled('delivery_note') with - // isModEnabled('shipping') && getDolGlobalString('MAIN_SUBMODULE_EXPEDITION') - if ($module == 'delivery_note') { - if (!getDolGlobalString('MAIN_SUBMODULE_EXPEDITION')) { - return false; - } else { - $module = 'shipping'; - } - } $module_alt = $module; if (!empty($arrayconv[$module])) { @@ -421,7 +423,6 @@ function isModEnabled($module) } return !empty($conf->modules[$module]) || !empty($conf->modules[$module_alt]) || !empty($conf->modules[$module_bis]); - //return !empty($conf->$module->enabled); } /** @@ -1772,34 +1773,34 @@ function dol_get_object_properties($obj, $properties = []) * * @template T * - * @param T $object Object to clone - * @param int $native 0=Full isolation method, 1=Native PHP method, 2=Full isolation method keeping only scalar and array properties (recommended) - * @return T Clone object + * @param T $srcobject Object to clone + * @param int $native 0=Full isolation method, 1=Native PHP method, 2=Full isolation method keeping only scalar and array properties (recommended) + * @return T Clone object * * @see https://php.net/manual/language.oop5.cloning.php * @phan-suppress PhanTypeExpectedObjectPropAccess */ -function dol_clone($object, $native = 2) +function dol_clone($srcobject, $native = 2) { if ($native == 0) { // deprecated method, use the method with native = 2 instead dol_syslog("Warning, call to dol_clone() with the deprecated parameter native=0, use 2 instead", LOG_WARNING); $tmpsavdb = null; - if (isset($object->db) && isset($object->db->db) && is_object($object->db->db) && get_class($object->db->db) == 'PgSql\Connection') { - $tmpsavdb = $object->db; - unset($object->db); // Such property can not be serialized with pgsl (when object->db->db = 'PgSql\Connection') + if (isset($srcobject->db) && isset($srcobject->db->db) && is_object($srcobject->db->db) && get_class($srcobject->db->db) == 'PgSql\Connection') { + $tmpsavdb = $srcobject->db; + unset($srcobject->db); // Such property can not be serialized with pgsl (when object->db->db = 'PgSql\Connection') } - $myclone = unserialize(serialize($object)); // serialize then unserialize is a hack to be sure to have a new object for all fields + $myclone = unserialize(serialize($srcobject)); // serialize then unserialize is a hack to be sure to have a new object for all fields if (!empty($tmpsavdb)) { - $object->db = $tmpsavdb; + $srcobject->db = $tmpsavdb; } } elseif ($native == 2) { // recommended method to have a full secured isolated cloned object $myclone = new stdClass(); - $tmparray = get_object_vars($object); // return only public properties + $tmparray = get_object_vars($srcobject); // return only public properties if (is_array($tmparray)) { foreach ($tmparray as $propertykey => $propertyval) { @@ -1809,12 +1810,42 @@ function dol_clone($object, $native = 2) } } } else { - $myclone = clone $object; // PHP clone is a shallow copy only, not a real clone, so properties of references will keep the reference (referring to the same target/variable) + $myclone = clone $srcobject; // PHP clone is a shallow copy only, not a real clone, so properties of references will keep the reference (referring to the same target/variable) } return $myclone; } + +/** + * Create a clone of instance of object into a full array, using recursive call. + * It also cleans some properties. + * + * @param Object $srcobject Object to clone + * @param int $startlevel Start level to track recursive depth + * @return array Array + */ +function dol_clone_in_array($srcobject, $startlevel = 0) +{ + if (is_object($srcobject)) { + $srcobject = get_object_vars($srcobject); // exclude private/protected properties + } + + if (is_array($srcobject)) { + $result = []; + foreach ($srcobject as $key => $value) { + if (in_array($key, array('db', 'fields', 'error', 'errorhidden', 'errors', 'oldcopy', 'linkedObjects', 'linked_objects'))) { + continue; + } + $result[$key] = dol_clone_in_array($value, $startlevel + 1); + } + return $result; + } + + return $srcobject; +} + + /** * Optimize a size for some browsers (phone, smarphone...) * @@ -3763,24 +3794,25 @@ function dol_strftime($fmt, $ts = false, $is_gmt = false) * Output date in a string format according to outputlangs (or langs if not defined). * Return charset is always UTF-8, except if encodetoouput is defined. In this case charset is output charset * - * @param null|int|string $time GM Timestamps date - * @param string $format Output date format (tag of strftime function) - * "%d %b %Y", - * "%d/%m/%Y %H:%M", - * "%d/%m/%Y %H:%M:%S", - * "%B"=Long text of month, "%A"=Long text of day, "%b"=Short text of month, "%a"=Short text of day - * "day", "daytext", "dayhour", "dayhourldap", "dayhourtext", "dayrfc", "dayhourrfc", "...inputnoreduce", "...reduceformat" - * @param string|bool $tzoutput true or 'gmt' => string is for Greenwich location - * false or 'tzserver' => output string is for local PHP server TZ usage - * 'tzuser' => output string is for user TZ (current browser TZ with current dst) => In a future, we should have same behaviour than 'tzuserrel' - * 'tzuserrel' => output string is for user TZ (current browser TZ with dst or not, depending on date position) - * @param ?Translate $outputlangs Object lang that contains language for text translation. - * @param boolean $encodetooutput false=no convert into output pagecode - * @return string Formatted date or '' if time is null + * @param null|int|string $time GM Timestamps date + * @param string $format Output date format (tag of strftime function) + * "%d %b %Y", + * "%d/%m/%Y %H:%M", + * "%d/%m/%Y %H:%M:%S", + * "%B"=Long text of month, "%A"=Long text of day, "%b"=Short text of month, "%a"=Short text of day + * "day", "daytext", "dayhour", "dayhourldap", "dayhourtext", "dayrfc", "dayhourrfc", "...inputnoreduce", "...reduceformat" + * @param string|bool $tzoutput true or 'gmt' => string is for Greenwich location + * false or 'tzserver' => output string is for local PHP server TZ usage + * 'tzuser' => output string is for user TZ (current browser TZ with current dst) => In a future, we should have same behaviour than 'tzuserrel' + * 'tzuserrel' => output string is for user TZ (current browser TZ with dst or not, depending on date position) + * @param ?Translate $outputlangs Object lang that contains language for text translation. + * @param boolean $encodetooutput Use true to convert/encode string into the HTML rendering pagecode (false=keep UTF8 by default) + * @param int $decorate Use 1 to apply a HTML css style to decorate the date + * @return string Formatted date or '' if time is null * * @see dol_mktime(), dol_stringtotime(), dol_getdate(), selectDate() */ -function dol_print_date($time, $format = '', $tzoutput = 'auto', $outputlangs = null, $encodetooutput = false) +function dol_print_date($time, $format = '', $tzoutput = 'auto', $outputlangs = null, $encodetooutput = false, $decorate = 0) { global $conf, $langs; @@ -3808,7 +3840,8 @@ function dol_print_date($time, $format = '', $tzoutput = 'auto', $outputlangs = $offsetdst = 0; // Dst offset with server timezone (because to_gmt is false), so 0 } elseif ($tzoutput == 'tzuser' || $tzoutput == 'tzuserrel') { $to_gmt = true; - $offsettzstring = (empty($_SESSION['dol_tz_string']) ? 'UTC' : $_SESSION['dol_tz_string']); // Example 'Europe/Berlin' or 'Indian/Reunion' + // if no session (by example in cron) may use MAIN_DOLIBARR_USER_TIMEZONE instead UTC + $offsettzstring = (empty($_SESSION['dol_tz_string']) ? getDolGlobalString('MAIN_DOLIBARR_USER_TIMEZONE', 'UTC') : $_SESSION['dol_tz_string']); // Example 'Europe/Berlin' or 'Indian/Reunion' if (class_exists('DateTimeZone')) { try { @@ -4022,6 +4055,11 @@ function dol_print_date($time, $format = '', $tzoutput = 'auto', $outputlangs = $ret = str_replace('__a__', dol_substr($dayweek, 0, 3), $ret); } + if ($decorate) { + $ret = preg_replace('/(\d\d:\d\d [AP]M)$/', '\1', $ret); + $ret = preg_replace('/(\d\d:\d\d)$/', '\1', $ret); + } + return $ret; } @@ -5653,6 +5691,7 @@ function img_picto($titlealt, $picto, $moreatt = '', $pictoisfullpath = 0, $srco 'accounting_account' => 'infobox-bank_account', 'accountline' => 'infobox-bank_account', 'accountancy' => 'infobox-bank_account', + 'admin'=> 'opacitymedium', 'asset' => 'infobox-bank_account', 'bank_account' => 'infobox-bank_account', 'bill' => 'infobox-commande', @@ -5891,6 +5930,7 @@ function getImgPictoConv($mode = 'fa') 'add' => 'plus-circle', 'address' => 'address-book', 'ai' => 'magic', + 'admin' => 'star', 'asset' => 'money-check-alt', 'autofill' => 'fill', 'back' => 'arrow-left', @@ -5968,6 +6008,7 @@ function getImgPictoConv($mode = 'fa') 'group' => 'users', 'movement' => 'people-carry', 'sign-out' => 'sign-out-alt', + 'superadmin' => 'star', 'switch_off' => 'toggle-off', 'switch_off_grey' => 'toggle-off', 'switch_off_warning' => 'toggle-off', @@ -7717,7 +7758,7 @@ function price2num($amount, $rounding = '', $option = 0) } elseif ($rounding == 'MT') { $nbofdectoround = getDolGlobalInt('MAIN_MAX_DECIMALS_TOT'); // usually 2 or 3 } elseif ($rounding == 'MS') { - $nbofdectoround = isset($conf->global->MAIN_MAX_DECIMALS_STOCK) ? getDolGlobalInt('MAIN_MAX_DECIMALS_STOCK') : 5; + $nbofdectoround = getDolGlobalInt('MAIN_MAX_DECIMALS_STOCK', 5); } elseif ($rounding == 'CU') { $nbofdectoround = getDolGlobalInt('MAIN_MAX_DECIMALS_CURRENCY_UNIT', getDolGlobalInt('MAIN_MAX_DECIMALS_UNIT')); // TODO Use param of currency } elseif ($rounding == 'CT') { @@ -7812,8 +7853,8 @@ function showDimensionInBestUnit($dimension, $unit, $type, $outputlangs, $round /** - * Return localtax rate for a particular vat, when selling a product with vat $vatrate, from a $thirdparty_buyer to a $thirdparty_seller - * Note: This function applies same rules than get_default_tva + * Return localtax rate for a particular VAT rate, when selling a product with vat $vatrate, from a $thirdparty_buyer to a $thirdparty_seller + * Note: This function get information into the table llx_tva using the VAT rate as key. * * @param float|string $vatrate Vat rate. Can be '8.5' or '8.5 (VATCODEX)' for example * @param int $local Local tax to search and return (1 or 2 return only tax rate 1 or tax rate 2) @@ -7821,7 +7862,7 @@ function showDimensionInBestUnit($dimension, $unit, $type, $outputlangs, $round * @param ?Societe $thirdparty_seller Object of selling third party ($mysoc if not defined) * @param int<0,1> $vatnpr If vat rate is NPR or not * @return int<0,0>|string 0 if not found, localtax rate if found (Can be '20', '-19:-15:-9') - * @see get_default_tva() + * @see get_default_tva(), get_default_localtax() */ function get_localtax($vatrate, $local, $thirdparty_buyer = null, $thirdparty_seller = null, $vatnpr = 0) { @@ -8239,6 +8280,7 @@ function get_product_vat_for_country($idprod, $thirdpartytouseforcountry, $idpro } dol_syslog("get_product_vat_for_country: ret=" . $ret); + return $ret; } @@ -8327,7 +8369,7 @@ function get_product_localtax_for_country($idprod, $local, $thirdpartytouseforco */ function get_default_tva(Societe $thirdparty_seller, Societe $thirdparty_buyer, $idprod = 0, $idprodfournprice = 0) { - global $mysoc, $db; + global $mysoc, $db, $hookmanager; require_once DOL_DOCUMENT_ROOT . '/core/lib/company.lib.php'; @@ -8348,6 +8390,9 @@ function get_default_tva(Societe $thirdparty_seller, Societe $thirdparty_buyer, dol_syslog("get_default_tva: seller use vat=" . $seller_use_vat . ", seller country=" . $seller_country_code . ", seller in cee=" . ((string) (int) $seller_in_cee) . ", buyer vat number=" . $thirdparty_buyer->tva_intra . " buyer country=" . $buyer_country_code . ", buyer state=" . $thirdparty_buyer->state_id . " buyer in cee=" . ((string) (int) $buyer_in_cee) . ", idprod=" . $idprod . ", idprodfournprice=" . $idprodfournprice . ", SERVICE_ARE_ECOMMERCE_200238EC=" . getDolGlobalString('SERVICE_ARE_ECOMMERCE_200238EC')); + $vatvalue = 0; + $vatrule = ''; + // If services are eServices according to EU Council Directive 2002/38/EC (http://ec.europa.eu/taxation_customs/taxation/vat/traders/e-commerce/article_1610_en.htm) // we use the buyer VAT. if (getDolGlobalString('SERVICE_ARE_ECOMMERCE_200238EC')) { @@ -8361,25 +8406,26 @@ function get_default_tva(Societe $thirdparty_seller, Societe $thirdparty_buyer, } if (!$isacompany) { - //print 'VATRULE 0'; - return get_product_vat_for_country($idprod, $thirdparty_buyer, $idprodfournprice); + $vatvalue = get_product_vat_for_country($idprod, $thirdparty_buyer, $idprodfournprice); + $vatrule = 'VATRULE 0'; } } } // If seller does not use VAT, default VAT is 0. End of rule. - if (!$seller_use_vat) { + if (empty($vatrule) && !$seller_use_vat) { //print 'VATRULE 1'; // TODO get the VAT Code of exemption asked into setup if country isInEEC (from an array list of possible // values like VATEX-EU-132-*, VATEX-FR-FRANCHISE, VATEX-EU-AE... // When we had recorded it, we also added a corresponding entry into table of vat code if it does not exists yet. // Here we test if entry for the VAT exemption code exists in llx_vat, we can return '0 (VATEX-EU-132-xx)' // If not, we add it and we return '0 (VATEX-EU-132-xx)' - return 0; + $vatvalue = 0; + $vatrule = 'VATRULE 1'; } // 'VATRULE 2' - Force VAT if a buyer department is defined on vat rates dictionary - if (!empty($thirdparty_buyer->state_id)) { + if (empty($vatrule) && !empty($thirdparty_buyer->state_id)) { $sql = "SELECT d.rowid, t.taux as vat_default_rate, t.code as vat_default_code "; $sql .= " FROM " . $db->prefix() . "c_tva as t"; $sql .= " INNER JOIN " . $db->prefix() . "c_departements as d ON t.fk_department_buyer = d.rowid"; @@ -8390,17 +8436,19 @@ function get_default_tva(Societe $thirdparty_seller, Societe $thirdparty_buyer, if ($res) { if ($db->num_rows($res)) { $obj = $db->fetch_object($res); - return $obj->vat_default_rate . ' (' . $obj->vat_default_code . ')'; + + $vatvalue = $obj->vat_default_rate . ' (' . $obj->vat_default_code . ')'; + $vatrule = 'VATRULE 2'; } $db->free($res); } } // If the (seller country = buyer country) then the default VAT = VAT of the product sold. End of rule. - if (($seller_country_code == $buyer_country_code) + if (empty($vatrule) && (($seller_country_code == $buyer_country_code) || (in_array($seller_country_code, array('FR', 'MC')) && in_array($buyer_country_code, array('FR', 'MC'))) || (in_array($seller_country_code, array('MQ', 'GP')) && in_array($buyer_country_code, array('MQ', 'GP'))) // We should be able to manage the case of MQ, GP, ... with a deicated vat rate at previous step. - ) { // Warning ->country_code not always defined + )) { // Warning ->country_code not always defined //print 'VATRULE 3'; $tmpvat = get_product_vat_for_country($idprod, $thirdparty_seller, $idprodfournprice); @@ -8417,7 +8465,8 @@ function get_default_tva(Societe $thirdparty_seller, Societe $thirdparty_buyer, } } - return $tmpvat; + $vatvalue = $tmpvat; + $vatrule = 'VATRULE 3b'; } // If (seller and buyer in the European Community) and (property sold = new means of transport such as car, boat, plane) then VAT by default = 0 (VAT must be paid by the buyer to the tax center of his country and not to the seller). End of rule. @@ -8425,7 +8474,7 @@ function get_default_tva(Societe $thirdparty_seller, Societe $thirdparty_buyer, // If (seller and buyer in the European Community) and (buyer = individual) then VAT by default = VAT of the product sold. End of rule // If (seller and buyer in European Community) and (buyer = company) then VAT by default=0. End of rule - if (($seller_in_cee && $buyer_in_cee)) { + if (empty($vatrule) && ($seller_in_cee && $buyer_in_cee)) { $isacompany = $thirdparty_buyer->isACompany(); if ($isacompany && !getDolGlobalString('MAIN_USE_VAT_ZERO_FOR_COMPANIES_IN_EEC_EVEN_IF_VAT_ID_UNKNOWN')) { require_once DOL_DOCUMENT_ROOT . '/core/lib/functions2.lib.php'; @@ -8436,22 +8485,25 @@ function get_default_tva(Societe $thirdparty_seller, Societe $thirdparty_buyer, if (!$isacompany) { //print 'VATRULE 5'; - return get_product_vat_for_country($idprod, $thirdparty_seller, $idprodfournprice); + $vatvalue = get_product_vat_for_country($idprod, $thirdparty_seller, $idprodfournprice); + $vatrule = 'VATRULE 5'; } else { //print 'VATRULE 6'; // TODO This is the case of VAT exemption 'VATEX-EU-IC' // If entry for the VAT exemption code exists in llx_vat, we can return '0 (VATEX-EU-IC)' // If not, we add it and we return '0 (VATEX-EU-IC)' - return 0; + $vatvalue = 0; + $vatrule = 'VATRULE 6'; } } // If (seller in the European Community and buyer outside the European Community and private buyer) then VAT by default = VAT of the product sold. End of rule // I don't see any use case that need this rule, this case is on only if MAIN_USE_VAT_OF_PRODUCT_FOR_INDIVIDUAL_CUSTOMER_OUT_OF_EEC set - if (getDolGlobalString('MAIN_USE_VAT_OF_PRODUCT_FOR_INDIVIDUAL_CUSTOMER_OUT_OF_EEC') && empty($buyer_in_cee)) { + if (empty($vatrule) && getDolGlobalString('MAIN_USE_VAT_OF_PRODUCT_FOR_INDIVIDUAL_CUSTOMER_OUT_OF_EEC') && empty($buyer_in_cee)) { $isacompany = $thirdparty_buyer->isACompany(); if (!$isacompany) { - return get_product_vat_for_country($idprod, $thirdparty_seller, $idprodfournprice); + $vatvalue = get_product_vat_for_country($idprod, $thirdparty_seller, $idprodfournprice); + $vatrule = 'VATRULE extra'; //print 'VATRULE extra'; } } @@ -8462,7 +8514,18 @@ function get_default_tva(Societe $thirdparty_seller, Societe $thirdparty_buyer, // TODO This is the case of VAT exemption 'VATEX-EU-G' // If entry for the VAT exemption code exists in llx_vat, we can return '0 (VATEX-xxx)' // If not, we add it and we return '0 (VATEX-xxx)' - return 0; + + // Allow an external module to bypass the calculation of prices + $parameters = array('vatvalue' => $vatvalue, 'vatrule' => $vatrule); + $tmpobject = null; $tmpaction = ''; + // @phan-suppress-next-line PhanPluginConstantVariableNull + $reshook = $hookmanager->executeHooks('get_default_tva', $parameters, $tmpobject, $tmpaction); // @phan-suppress-current-line PhanPluginConstantVariableNull + if ($reshook > 0 && !empty($hookmanager->resArray['vatvalue'])) { + $vatvalue = $hookmanager->resArray['vatvalue']; + $vatrule = $hookmanager->resArray['vatrule']; // For information + } + + return $vatvalue; } @@ -8654,7 +8717,7 @@ function get_exdir($num, $level, $alpha, $withoutslash, $object, $modulepart = ' $path = ''; // Define $arrayforoldpath that is module path using a hierarchy on more than 1 level. - $arrayforoldpath = array('cheque' => 2, 'category' => 2, 'holiday' => 2, 'supplier_invoice' => 2, 'invoice_supplier' => 2, 'mailing' => 2, 'supplier_payment' => 2); + $arrayforoldpath = array('cheque' => 2, 'category' => 2, 'supplier_invoice' => 2, 'invoice_supplier' => 2, 'mailing' => 2, 'supplier_payment' => 2); if (getDolGlobalInt('PRODUCT_USE_OLD_PATH_FOR_PHOTO')) { $arrayforoldpath['product'] = 2; } @@ -9477,22 +9540,54 @@ function dol_htmlwithnojs($stringtoencode, $nouseofiframesandbox = 0, $check = ' $out = 'ErrorHTMLLinksNotAllowed'; } } elseif (getDolGlobalInt('MAIN_DISALLOW_URL_INTO_DESCRIPTIONS') == 1) { + // Refuse any links except it they are to the wrapper document.php or viewimage.php $nblinks = 0; + // Loop on each url in src= and url( $pattern = '/src=["\']?(http[^"\']+)|url\(["\']?(http[^\)]+)/'; + global $dolibarr_main_url_root; + $matches = array(); if (preg_match_all($pattern, $out, $matches)) { - // URLs are into $matches[1] - $urls = $matches[1]; + // URLs are into $matches[1] or $matches[2] + $urls = array(); + foreach ($matches[1] as $tmpval) { + if (!empty($tmpval)) { + $urls[] = $tmpval; + } + } + foreach ($matches[2] as $tmpval) { + if (!empty($tmpval)) { + $urls[] = $tmpval; + } + } - // Affiche les URLs + // Show URLs + $firstexturl = ''; + $secondexturl = ''; foreach ($urls as $url) { - $nblinks++; - echo "Found url = " . $url . "\n"; + $urlok = 0; + $parsedurl = parse_url($url); + if (!empty($parsedurl)) { + if (preg_match('/'.preg_quote($dolibarr_main_url_root, '/').'/', $url) + //&& preg_match('/(document|viewimage)\.php$/', $parsedurl['path']) && preg_match('/modulepart=(media|mycompany)/', $parsedurl['query']) + ) { + $urlok = 1; + } + } + if (!$urlok) { + $nblinks++; + if (empty($firstexturl)) { + $firstexturl = $url; + } elseif (empty($secondexturl)) { + $secondexturl = $url; + } + //echo "Found url = ".$url . "\n"; + } } if ($nblinks > 0) { - $out = 'ErrorHTMLExternalLinksNotAllowed'; + $out = 'ErrorHTMLExternalLinksNotAllowed (Example: '.$firstexturl.($secondexturl ? ' '.$secondexturl : '').')'; } } } @@ -9805,7 +9900,18 @@ function dol_concatdesc($text1, $text2, $forxml = false, $invert = false) return $ret; } - +/** + * Concat 2 strings. Can be used for dol_eval strings for example. + * + * @param string $text1 Text 1 + * @param string $text2 Text 2 + * @return string Text 1 + new line + Text2 + * @see dol_textishtml() + */ +function dol_concat($text1, $text2) +{ + return $text1.$text2; +} /** * Return array of possible common substitutions. This includes several families like: 'system', 'mycompany', 'object', 'objectamount', 'date', 'user' @@ -10437,6 +10543,8 @@ function getCommonSubstitutionArray($outputlangs, $onlykey = 0, $exclude = null, $substitutionarray['__EVENT_TYPE__'] = $outputlangs->trans("Action" . $object->type_code); $substitutionarray['__EVENT_DATE__'] = dol_print_date($object->datep, 'day', 'auto', $outputlangs); $substitutionarray['__EVENT_TIME__'] = dol_print_date($object->datep, 'hour', 'auto', $outputlangs); + $substitutionarray['__EVENT_DATE_TZUSER__'] = dol_print_date($object->datep, 'day', 'tzuserrel', $outputlangs); + $substitutionarray['__EVENT_TIME_TZUSER__'] = dol_print_date($object->datep, 'hour', 'tzuserrel', $outputlangs); } } } @@ -11467,13 +11575,13 @@ function dol_getIdFromCode($db, $key, $tablename, $fieldkey = 'code', $fieldid = } /** - * Check if a variable with name $var startx with $text. + * Check if a variable with name $var start with $regextext. * Can be used to forge dol_eval() conditions. * - * @param string $var Variable - * @param string $regextext Text that must be a valid regex string - * @param int<0,1> $matchrule 1=Test if start with, 0=Test if equal - * @return boolean|string True or False, text if bad usage. + * @param string $var Variable + * @param string $regextext Text that must be a valid regex string + * @param int<0,1> $matchrule 1=Test if start with, 0=Test if equal + * @return boolean|string True or False, text if bad usage. */ function isStringVarMatching($var, $regextext, $matchrule = 1) { @@ -11488,7 +11596,7 @@ function isStringVarMatching($var, $regextext, $matchrule = 1) return 'This variable is not accessible with dol_eval'; } } else { - return 'This value for matchrule is not implemented'; + return 'This value '.$matchrule.' for param $matchrule is not yet implemented'; } } @@ -11524,7 +11632,7 @@ function verifCond($strToEvaluate, $onlysimplestring = '1') * @param int<0,1> $returnvalue 0=No return (deprecated, used to execute eval($a=something)). 1=Value of eval is returned (used to eval($something)). * @param int<0,1> $hideerrors 1=Hide errors * @param string $onlysimplestring '0' (deprecated, do not use it anymore) = Accept all chars, - * '1' (most common use) = Accept only simple string with char 'a-z0-9\s^$_+-.*>&|=!?():"\',/@';', + * '1' (most common use, recommended) = Accept only simple string with char 'a-z0-9\s^$_+-.*>&|=!?():"\',/@';', * '2' (used for example for the compute property of extrafields) = Accept also '<[]' * @return string Return result of eval (even if type can be int, it is safer to assume string and find all potential typing issues as abs(dol_eval(...)). * @see verifCond(), checkPHPCode() to see sanitizing rules that should be very close. @@ -11539,7 +11647,7 @@ function dol_eval($s, $returnvalue = 1, $hideerrors = 1, $onlysimplestring = '1' if (getDolGlobalString("MAIN_USE_DOL_EVAL_NEW")) { return dol_eval_new($s); } else { - return dol_eval_standard($s, $returnvalue, $hideerrors, $onlysimplestring); + return dol_eval_standard($s, $hideerrors, $onlysimplestring); } } @@ -11820,8 +11928,7 @@ function dol_eval_new($s) * Replace eval function to add more security. * This function is called by dol_eval(), itself called by verifCond() or trans() and transnoentitiesnoconv(). * - * @param string $s String to evaluate - * @param int<0,1> $returnvalue 0=No return (deprecated, used to execute eval($a=something)). 1=Value of eval is returned (used to eval($something)). + * @param string $s String to evaluate with eval($something) * @param int<0,1> $hideerrors 1=Hide errors * @param string $onlysimplestring '0' (deprecated, do not use it anymore)=Accept all chars, * '1' (most common use)=Accept only simple string with char 'a-z0-9\s^$_+-.*>&|=!?():"\',/@';', @@ -11830,81 +11937,89 @@ function dol_eval_new($s) * @see verifCond(), checkPHPCode() to see sanitizing rules that should be very close. * @phan-suppress PhanPluginUnsafeEval */ -function dol_eval_standard($s, $returnvalue = 1, $hideerrors = 1, $onlysimplestring = '1') +function dol_eval_standard($s, $hideerrors = 1, $onlysimplestring = '1') { // Only this global variables can be read by eval function and returned to caller - //global $conf; // Disabled. Read of const is done with getDolGlobalString() and read of $conf->currency is done with getDolCurrency() - global $db; - global $langs, $user, $website, $websitepage; + // The less we have, the better it is. + + global $conf; // TODO Remove this to exclude $conf. We can read $conf->module->enabled with isModEnabled(), $conf->global->xxx properties with getDolGlobalString(), $conf->currency with getDolCurrency(), $conf->entity with getDolEntity() + global $db, $langs, $user, $website, $websitepage; global $action, $mainmenu, $leftmenu; global $mysoc; global $objectoffield; // To allow the use of $objectoffield in computed fields - // Old variables used (deprecated) - global $object; - global $obj; // To get $obj used into list when dol_eval() is used for computed fields and $obj is not yet $object + // Old variables (deprecated) + if (getDolGlobalString('MAIN_ALLOW_OLD_VAR_OBJ_IN_DOL_EVAL')) { + global $object; + global $obj; // To get $obj used into list when dol_eval() is used for computed fields and $obj is not yet $objectoffield + } - $isObBufferActive = false; // When true, the ObBuffer must be cleaned in the exception handler - if (!in_array($onlysimplestring, array('0', '1', '2'))) { - return "Bad call of dol_eval. Parameter onlysimplestring must be '0' (deprecated), '1' or '2'"; + $isObBufferActive = false; // When true, the ObBuffer must be cleaned in the exception handler + if ($onlysimplestring == '0') { // '0' is deprecated, we process it as the more secured '1' + $onlysimplestring = '1'; + } + if (!in_array($onlysimplestring, array('1', '2'))) { + return "Bad call of dol_eval. Parameter onlysimplestring must be '1' or '2'."; + } + if (!is_scalar($s)) { + return "Bad call of dol_eval. First parameter must be a string, found ".var_export($s, true); } try { + global $dolibarr_main_restrict_eval_methods; + + // Set $dolibarr_main_restrict_eval_methods_array + if (!isset($dolibarr_main_restrict_eval_methods)) { + $dolibarr_main_restrict_eval_methods = 'getDolGlobalString, getDolGlobalInt, getDolCurrency, fetchNoCompute, hasRight, isAdmin, isModEnabled, isStringVarMatching, abs, min, max, round, dol_now, preg_match'; + } + //print '$dolibarr_main_restrict_eval_methods = '.$dolibarr_main_restrict_eval_methods."\n"; + $dolibarr_main_restrict_eval_methods_array = explode(',', str_replace(" ", "", $dolibarr_main_restrict_eval_methods)); + // Test on dangerous char (used for RCE), we allow only characters to make PHP variable testing - if ($onlysimplestring == '1' || $onlysimplestring == '2') { - // We must accept with 1: '1 && getDolGlobalInt("doesnotexist1") && getDolGlobalString("MAIN_FEATURES_LEVEL")' - // We must accept with 1: '$user->hasRight("cabinetmed", "read") && !$object->canvas=="patient@cabinetmed"' - // We must accept with 2: (($reloadedobj = new Task($db)) && ($reloadedobj->fetchNoCompute($object->id) <= 99) && ($secondloadedobj = new Project($db)) && ($secondloadedobj->fetchNoCompute($reloadedobj->fk_project) > 0)) ? $secondloadedobj->ref : "Parent project not found" + // We must accept with 1: '1 && getDolGlobalInt("doesnotexist1") && getDolGlobalString("MAIN_FEATURES_LEVEL")' + // We must accept with 1: '$user->hasRight("cabinetmed", "read") && !$objectoffield->canvas == "patient@cabinetmed"' + // We must accept with 2: (($var1 = new Task($db)) && ($var1->fetchNoCompute($object->id) <= 99) && ($var2 = new Project($db)) && ($var2->fetchNoCompute($var1->fk_project) > 0)) ? $var2->ref : "Parent project not found" - // Check if there is dynamic call (first we check chars are all into a whitelist chars) - $specialcharsallowed = '^$_+-.*>&|=!?():"\',/@'; - if ($onlysimplestring == '2') { - $specialcharsallowed .= '<[]'; - } - if (getDolGlobalString('MAIN_ALLOW_UNSECURED_SPECIAL_CHARS_IN_DOL_EVAL')) { - $specialcharsallowed .= getDolGlobalString('MAIN_ALLOW_UNSECURED_SPECIAL_CHARS_IN_DOL_EVAL'); - } - if (preg_match('/[^a-z0-9\s' . preg_quote($specialcharsallowed, '/') . ']/i', $s)) { - if ($returnvalue) { - return 'Bad string syntax to evaluate (found chars that are not chars for a simple one line clean eval string): ' . $s; - } else { - dol_syslog('Bad string syntax to evaluate (found chars that are not chars for a simple one line clean eval string): ' . $s, LOG_WARNING); - return ''; - } - } + // Check if there is dynamic call (first we check chars are all into a whitelist chars) + $specialcharsallowed = '^$_+-.*>&|=!?():"\',/@'; + if ($onlysimplestring == '2') { + $specialcharsallowed .= '<[]'; // Later we check that < has space before and after + } + global $dolibarr_main_allow_unsecured_special_chars_in_dol_eval; + if (!empty($dolibarr_main_allow_unsecured_special_chars_in_dol_eval)) { + $specialcharsallowed .= (string) $dolibarr_main_allow_unsecured_special_chars_in_dol_eval; + } + if (preg_match('/[^a-z0-9\s' . preg_quote($specialcharsallowed, '/') . ']/i', $s)) { + return 'Bad string syntax to evaluate (found chars that are not chars for a simple one line clean eval string): ' . $s; + } - // Check if we found a ? without a space before and after - $tmps = str_replace(' ? ', '__XXX__', $s); - if (strpos($tmps, '?') !== false) { - if ($returnvalue) { - return 'Bad string syntax to evaluate (The char ? can be used only with a space before and after): ' . $s; - } else { - dol_syslog('Bad string syntax to evaluate (The char ? can be used only with a space before and after): ' . $s, LOG_WARNING); - return ''; - } - } + // Check if we found a | without a space before and after + /* Disabled to allow preg_match('/(AAA|BBB)/') + $tmps = str_replace(' || ', '__XXX__', $s); + if (strpos($tmps, '|') !== false) { + return 'Bad string syntax to evaluate (The char | can be used only when duplicated || with a space before and after): ' . $s; + } + */ - // Check if there is a < or <= without spaces before/after - if (preg_match('/<=?[^\s]/', $s)) { - if ($returnvalue) { - return 'Bad string syntax to evaluate (mode ' . $onlysimplestring . ', found a < or <= without space before and after): ' . $s; - } else { - dol_syslog('Bad string syntax to evaluate (mode ' . $onlysimplestring . ', found a < or <= without space before and after): ' . $s, LOG_WARNING); - return ''; - } - } + // Check if we found a ? without a space before and after + $tmps = str_replace(' ? ', '__XXX__', $s); + if (strpos($tmps, '?') !== false) { + return 'Bad string syntax to evaluate (The char ? can be used only with a space before and after): ' . $s; + } - // Check if there is dynamic call (first we use black list patterns) - if (preg_match('/\$[\w]*\s*\(/', $s)) { - if ($returnvalue) { - return 'Bad string syntax to evaluate (mode ' . $onlysimplestring . ', found a call using "$abc(" or "$abc (" instead of using the direct name of the function): ' . $s; - } else { - dol_syslog('Bad string syntax to evaluate (mode ' . $onlysimplestring . ', found a call using "$abc(" or "$abc (" instead of using the direct name of the function): ' . $s, LOG_WARNING); - return ''; - } - } + // Check if there is a < or <= without spaces after + if (preg_match('/<=?[^\s]/', $s)) { + return 'Bad string syntax to evaluate (mode ' . $onlysimplestring . ', found a < or <= without space after): ' . $s; + } + + // Check if there is dynamic call (first we use black list patterns) + if (preg_match('/\$[\w]*\s*\(/', $s)) { + return 'Bad string syntax to evaluate (mode ' . $onlysimplestring . ', found a call using "$abc(" or "$abc (" instead of using the direct name of the function): ' . $s; + } + + if (empty($dolibarr_main_restrict_eval_methods)) { + // If $dolibarr_main_restrict_eval_methods was set to '', we must check if we try dynamic call - // Now we check if we try dynamic call // First we remove white list pattern of using parenthesis then testing if one open parenthesis exists $savescheck = ''; $scheck = $s; @@ -11925,138 +12040,161 @@ function dol_eval_standard($s, $returnvalue = 1, $hideerrors = 1, $onlysimplestr // Now test if it remains 1 open parenthesis. if (strpos($scheck, '(') !== false) { - if ($returnvalue) { - return 'Bad string syntax to evaluate (mode ' . $onlysimplestring . ', found call of a function or method without using the direct name of the function): ' . $s; - } else { - dol_syslog('Bad string syntax to evaluate (mode ' . $onlysimplestring . ', found call of a function or method without using the direct name of the function): ' . $s, LOG_WARNING); - return ''; - } - } - - // TODO - // We can exclude $ char that are not in dol_eval global, so that are not: - // $db, $langs, $leftmenu, $topmenu, $user, $langs, $objectoffield, $object, $obj, ..., - } - if (is_array($s) || $s === 'Array') { - if ($returnvalue) { - return 'Bad string syntax to evaluate (value is Array): ' . var_export($s, true); - } else { - dol_syslog('Bad string syntax to evaluate (value is Array): ' . var_export($s, true), LOG_WARNING); - return ''; - } - } - - if (!getDolGlobalString('MAIN_ALLOW_DOUBLE_COLON_IN_DOL_EVAL') && strpos($s, '::') !== false) { - if ($returnvalue) { - return 'Bad string syntax to evaluate (double : char is forbidden without setting MAIN_ALLOW_DOUBLE_COLON_IN_DOL_EVAL): ' . $s; - } else { - dol_syslog('Bad string syntax to evaluate (double : char is forbidden without setting MAIN_ALLOW_DOUBLE_COLON_IN_DOL_EVAL): ' . $s, LOG_WARNING); - return ''; + return 'Bad string syntax to evaluate (mode ' . $onlysimplestring . ', found call of a function or method without using the direct name of the function): ' . $s; } } if (strpos($s, '`') !== false) { - if ($returnvalue) { - return 'Bad string syntax to evaluate (backtick char is forbidden): ' . $s; - } else { - dol_syslog('Bad string syntax to evaluate (backtick char is forbidden): ' . $s, LOG_WARNING); - return ''; + return 'Bad string syntax to evaluate (backtick char is forbidden): ' . $s; + } + + // Disallow also concat operator + if (!getDolGlobalString('MAIN_ALLOW_OBFUSCATION_METHODS_IN_DOL_EVAL')) { + if (preg_match('/[^0-9]+\.[^0-9]+/', $s)) { // We refuse . if not between 2 numbers + return 'Bad string syntax to evaluate (dot char is forbidden if not strictly between 2 numbers): ' . $s; } } - // Disallow also concat - if (getDolGlobalString('MAIN_DISALLOW_STRING_OBFUSCATION_IN_DOL_EVAL')) { - if (preg_match('/[^0-9]+\.[^0-9]+/', $s)) { // We refuse . if not between 2 numbers - if ($returnvalue) { - return 'Bad string syntax to evaluate (dot char is forbidden): ' . $s; - } else { - dol_syslog('Bad string syntax to evaluate (dot char is forbidden): ' . $s, LOG_WARNING); - return ''; - } + // We exclude string using a $ character that are not an expected global or temporary vars, so that are not: + // $db, $langs, $leftmenu, $topmenu, $user, $langs, $objectoffield, $var.... + $savescheck = ''; + $scheck = $s; + while ($scheck && $savescheck != $scheck) { + $savescheck = $scheck; + $scheck = preg_replace('/\$conf->[a-z\_]+->enabled/', '__VARCONFENABLED__', $scheck); // Remove this once $user->module->enabled has been replaced everywhere with isModEnabled. + $scheck = preg_replace('/\$user->hasRight/', '__VARUSERHASRIGHT__', $scheck); + $scheck = preg_replace('/\$user->rights/', '__VARUSERHASRIGHT__', $scheck); // Remove this once $user->rights->xxx is replaced everywhere with $user->hasRight() + $scheck = preg_replace('/\$user->admin/', '__VARUSERISADMIN__', $scheck); // Remove this once $user->admin is replaced everywhere with $user->isAdmin() + $scheck = preg_replace('/\(\$db\)/', '__VARDB__', $scheck); + $scheck = preg_replace('/\$langs/', '__VARLANGSTRANS__', $scheck); + $scheck = preg_replace('/\$mysoc/', '__VARMYSOC__', $scheck); + $scheck = preg_replace('/\$action/', '__VARACTION__', $scheck); + $scheck = preg_replace('/\$mainmenu/', '__VARMAINMENU__', $scheck); // Remove this once all tests on $mainmenu has been replaced with isStringVarMatching + $scheck = preg_replace('/\$leftmenu/', '__VARLEFTMENU__', $scheck); // Remove this once all tests on $mainmenu has been replaced with isStringVarMatching + $scheck = preg_replace('/\$websitepage/', '__VARWEBSITEPAGE__', $scheck); + $scheck = preg_replace('/\$website/', '__VARWEBSITE__', $scheck); + $scheck = preg_replace('/\$objectoffield/', '__VAROBJECTOFFIELD__', $scheck); + $scheck = preg_replace('/\$var/', '__VARVAR__', $scheck); + + // Now test if it remains 1 $ + if (strpos($scheck, '$') !== false) { + return 'Bad string syntax to evaluate (found use of $ that does not match one of the following pattern: $user->hasRight, ($db), $langs, $mysoc, $action, $mainmenu, $leftmenu, $website, $websitepage, $objectoffield or $var123): ' . $s; } } // We block use of php exec or php file functions - $forbiddenphpstrings = array('$$', '$_', '}[', ')('); - $forbiddenphpstrings = array_merge($forbiddenphpstrings, array('_ENV', '_SESSION', '_COOKIE', '_GET', '_GLOBAL', '_POST', '_REQUEST', 'ReflectionFunction')); + $forbiddenphpstrings = array('}[', ')('); + $forbiddenphpstrings = array_merge($forbiddenphpstrings, array('_ENV', '_SESSION', '_COOKIE', '_GET', '_GLOBAL', '_POST', '_REQUEST', 'ReflectionFunction', 'SplFileObject', 'SplTempFileObject')); - // We list all forbidden function as keywords we don't want to see (we don't mind it if is "kewyord(" or just "keyword", we don't want "keyword" at all) + // We list all forbidden function as keywords we don't want to see (we don't mind it if is "keyword(" or just "keyword", we don't want "keyword" at all) // We must exclude all functions that allow to execute another function. This includes all function that has a parameter with type "callable" to avoid things // like we can do with array_map and its callable parameter: dol_eval('json_encode(array_map(implode("",["ex","ec"]), ["id"]))', 1, 1, '0') $forbiddenphpfunctions = array(); - $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("override_function", "session_id", "session_create_id", "session_regenerate_id")); - $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("get_defined_functions", "get_defined_vars", "get_defined_constants", "get_declared_classes")); - $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("function", "call_user_func", "call_user_func_array")); + $forbiddenphpmethods = array(); - $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("array_all", "array_any", "array_diff_ukey", "array_filter", "array_find", "array_find_key", "array_map", "array_reduce", "array_intersect_uassoc", "array_intersect_ukey", "array_walk", "array_walk_recursive")); - $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("usort", "uasort", "uksort", "preg_replace_callback", "preg_replace_callback_array", "header_register_callback")); - $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("set_error_handler", "set_exception_handler", "libxml_set_external_entity_loader", "register_shutdown_function", "register_tick_function", "unregister_tick_function")); - $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("spl_autoload_register", "spl_autoload_unregister", "iterator_apply", "session_set_save_handler")); - $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("forward_static_call", "forward_static_call_array", "register_postsend_function")); + if (empty($dolibarr_main_restrict_eval_methods)) { // If forced to '' + $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("override_function", "session_id", "session_create_id", "session_regenerate_id")); + $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("get_defined_functions", "get_defined_vars", "get_defined_constants", "get_declared_classes")); + $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("function", "call_user_func", "call_user_func_array")); - $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("ob_start")); + $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("array_all", "array_any", "array_diff_ukey", "array_filter", "array_find", "array_find_key", "array_map", "array_reduce", "array_intersect_uassoc", "array_intersect_ukey", "array_walk", "array_walk_recursive")); + $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("usort", "uasort", "uksort", "preg_replace_callback", "preg_replace_callback_array", "header_register_callback")); + $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("error_log", "set_error_handler", "set_exception_handler", "libxml_set_external_entity_loader", "register_shutdown_function", "register_tick_function", "unregister_tick_function")); + $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("spl_autoload_register", "spl_autoload_unregister", "iterator_apply", "session_set_save_handler")); + $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("forward_static_call", "forward_static_call_array", "register_postsend_function")); - $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("require", "include", "require_once", "include_once")); - $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("exec", "passthru", "shell_exec", "system", "proc_open", "popen")); - $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("dol_eval", "dol_eval_new", "dol_eval_standard", "dol_concatdesc", "executeCLI", "verifCond", "GETPOST")); // native dolibarr functions - $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("eval", "create_function", "assert", "mb_ereg_replace")); // function with eval capabilities - $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("readline_completion_function", "readline_callback_handler_install")); - $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("dol_compress_dir", "dol_decode", "dol_delete_file", "dol_delete_dir", "dol_delete_dir_recursive", "dol_copy", "archiveOrBackupFile")); // more dolibarr functions - $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("fopen", "file_put_contents", "fputs", "fputscsv", "fwrite", "fpassthru", "mkdir", "rmdir", "symlink", "touch", "unlink", "umask")); - $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("require", "include")); - if (getDolGlobalString('MAIN_DISALLOW_STRING_OBFUSCATION_IN_DOL_EVAL')) { // We disabllow all function that allow to obfuscate the real name of a function - // @phpcs:ignore - $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("base64" . "_" . "decode", "rawurl" . "decode", "url" . "decode", "str" . "_rot13", "hex" . "2bin")); // name of forbidden functions are split to avoid false positive - $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("dol_concatdesc")); // native dolibarr functions - } + $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("ob_start")); - $forbiddenphpmethods = array('invoke', 'invokeArgs'); // Method of ReflectionFunction to execute a function + $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("require", "include", "require_once", "include_once")); + $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("exec", "passthru", "shell_exec", "system", "proc_open", "popen")); + $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("dol_eval", "dol_eval_new", "dol_eval_standard", "executeCLI", "verifCond", "GETPOST", "dolEncrypt", "dolDecrypt")); // native dolibarr functions + $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("eval", "create_function", "assert", "mb_ereg_replace")); // function with eval capabilities + $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("readline_completion_function", "readline_callback_handler_install")); + $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("dol_compress_dir", "dol_decode", "dol_dir_list", "dol_dir_list_in_database", "dol_delete_file", "dol_delete_dir", "dol_delete_dir_recursive", "dol_copy", "archiveOrBackupFile")); // more dolibarr functions + $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("chdir", "dir", "fopen", "file", "file_exists", "file_get_contents", "file_put_contents", "fget", "fgetc", "fgetcsv", "fputs", "fputscsv", "fpassthru", "fscanf", "fseek", "fwrite", "is_file", "is_dir", "is_link", "mkdir", "opendir", "rmdir", "scandir", "symlink", "touch", "unlink", "umask")); + $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("require", "include")); + if (!getDolGlobalString('MAIN_ALLOW_OBFUSCATION_METHODS_IN_DOL_EVAL')) { // We disallow all function that allow to obfuscate the real name of a function + // @phpcs:ignore + $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("base64" . "_" . "decode", "rawurl" . "decode", "url" . "decode", "str" . "_rot13", "hex" . "2bin", "printf", "sprintf")); // name of forbidden functions are split to avoid false positive + $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("dol_concat", "dol_concatdesc")); // native dolibarr functions + } + // Remove from blacklist the function that are into the whitelist + /*foreach ($forbiddenphpfunctions as $key => $forbiddenphpfunction) { + if (in_array($forbiddenphpfunction, $dolibarr_main_restrict_eval_methods_array)) { + unset($forbiddenphpfunctions[$key]); + } + }*/ - $forbiddenphpregex = 'global\s*\$'; - $forbiddenphpregex .= '|'; - $forbiddenphpregex .= '\b(' . implode('|', $forbiddenphpfunctions) . ')\b'; + $forbiddenphpmethods = array_merge($forbiddenphpmethods, array('invoke', 'invokeArgs')); // Methods of ReflectionFunction to execute a function + // Remove from blacklist the function that are into the whitelist + /*foreach ($forbiddenphpmethods as $key => $forbiddenphpmethod) { + if (in_array($forbiddenphpmethod, $dolibarr_main_restrict_eval_methods_array)) { + unset($forbiddenphpmethods[$key]); + } + }*/ - $forbiddenphpmethodsregex = '->(' . implode('|', $forbiddenphpmethods) . ')'; + $forbiddenphpregex = 'global\s*\$'; + $forbiddenphpregex .= '|'; + $forbiddenphpregex .= '\b(' . implode('|', $forbiddenphpfunctions) . ')\b'; - do { - $oldstringtoclean = $s; - $s = str_ireplace($forbiddenphpstrings, '__forbiddenstring__', $s); - $s = preg_replace('/' . $forbiddenphpregex . '/i', '__forbiddenstring__', $s); - $s = preg_replace('/' . $forbiddenphpmethodsregex . '/i', '__forbiddenstring__', $s); - //$s = preg_replace('/\$[a-zA-Z0-9_\->\$]+\(/i', '', $s); // Remove $function( call and $mycall->mymethod( - } while ($oldstringtoclean != $s); + $forbiddenphpmethodsregex = '->(' . implode('|', $forbiddenphpmethods) . ')'; + // Now scan all forbidden patterns + do { + $oldstringtoclean = $s; + $s = str_ireplace($forbiddenphpstrings, '__forbiddenstring__', $s); + $s = preg_replace('/' . $forbiddenphpregex . '/i', '__forbiddenstring__', $s); + $s = preg_replace('/' . $forbiddenphpmethodsregex . '/i', '__forbiddenstring__', $s); + //$s = preg_replace('/\$[a-zA-Z0-9_\->\$]+\(/i', '', $s); // Remove $function( call and $mycall->mymethod( + } while ($oldstringtoclean != $s); - if (strpos($s, '__forbiddenstring__') !== false) { - dol_syslog('Bad string syntax to evaluate: ' . $s, LOG_WARNING); - if ($returnvalue) { + if (strpos($s, '__forbiddenstring__') !== false) { + dol_syslog('Bad string syntax to evaluate: ' . $s, LOG_WARNING); return 'Bad string syntax to evaluate: ' . $s; - } else { - dol_syslog('Bad string syntax to evaluate: ' . $s); - return ''; + } + } else { + // Accept only white-listed allowed function and classes + // TODO Get all pattern '/([\s\w]+)\(/', then check that $reg[1] is a defined class or a function into a given list + $pattern = '/([\s\w\'\]\"]+)\(/'; + + $matches = array(); + preg_match_all($pattern, $s, $matches); + + if (count($matches)) { + foreach ($matches[1] as $m) { + $m = trim($m); + if (empty($m)) { + continue; + } + $reg = array(); + if (!preg_match('/new ([A-Z][\w]+)/i', $m, $reg)) { + if (!in_array($m, $dolibarr_main_restrict_eval_methods_array)) { + if ($m != "'" && $m != '"') { + dol_syslog('Bad string syntax to evaluate: ' . $s, LOG_WARNING); + return 'Bad string syntax to evaluate. A function or method "'.$m.'" was called and is not into the parameter $dolibarr_main_restrict_eval_methods of white-listed functions and methods: ' . $s; + } + } + } else { + if ($reg[1] == 'ReflectionFunction') { + dol_syslog('Bad string syntax to evaluate: Class ReflectionFunction is not allowed. ' . $s, LOG_WARNING); + return 'Bad string syntax to evaluate. Class ReflectionFunction is not allowed. ' . $s; + } + } + } } } + //print $s."
    \n"; - if ($returnvalue) { - ob_start(); // An evaluation has no reason to output data - $isObBufferActive = true; - $tmps = $hideerrors ? @eval('return ' . $s . ';') : eval('return ' . $s . ';'); - $tmpo = ob_get_clean(); - $isObBufferActive = false; - if ($tmpo) { - print 'Bad string syntax to evaluate. Some data were output when it should not when evaluating: ' . $s; - } - return $tmps; - } else { - dol_syslog('Do not use anymore dol_eval with param returnvalue=0', LOG_WARNING); - if ($hideerrors) { - @eval($s); - } else { - eval($s); - } - return ''; + ob_start(); // An evaluation has no reason to output data + $isObBufferActive = true; + $tmps = $hideerrors ? @eval('return ' . $s . ';') : eval('return ' . $s . ';'); + $tmpo = ob_get_clean(); + $isObBufferActive = false; + if ($tmpo) { + print 'Bad string syntax to evaluate. Some data were output when it should not when evaluating: ' . $s; } + return $tmps; } catch (Error $e) { if ($isObBufferActive) { // Clean up buffer which was left behind due to exception. @@ -12066,11 +12204,7 @@ function dol_eval_standard($s, $returnvalue = 1, $hideerrors = 1, $onlysimplestr $error = 'dol_eval try/catch error : '; $error .= $e->getMessage(); dol_syslog($error, LOG_WARNING); - if ($returnvalue) { - return 'Exception during evaluation: ' . $s; - } else { - return ''; - } + return 'Exception during evaluation: ' . $s; } } @@ -13971,7 +14105,7 @@ function dolGetStatus($statusLabel = '', $statusLabelShort = '', $html = '', $st * * @param string $label Label or tooltip of button if $text is provided. Also used as tooltip in title attribute. Can be escaped HTML content or full simple text. * @param string $text Optional : short label on button. Can be escaped HTML content or full simple text. - * @param string $actionType 'default', 'danger', 'email', 'clone', 'cancel', 'delete', ... + * @param string $actionType 'default', 'edit', 'danger', 'email', 'clone', 'cancel', 'delete', ... * @param string|array}> $url Url for link or array of subbutton description * Example when an array is used: * $arrayforbutaction = array( @@ -13984,19 +14118,19 @@ function dolGetStatus($statusLabel = '', $statusLabelShort = '', $html = '', $st * // phpcs:disable * @param array{confirm?:array{url?:string,title?:string,content?:string,use_unsecured_unescapedattr?:bool|string[],action-btn-label?:string,cancel-btn-label?:string,modal?:bool},attr?:array,areDropdownButtons?:bool,backtopage?:string,lang?:string,enabled?:bool,perm?:int<0,1>,label?:string,url?:string,isDropdown?:int<0,1>,isDropDown?:int<0,1>} $params = [ // Various params for future : recommended rather than adding more function arguments * 'attr' => [ // to add or override button attributes - * 'xxxxx' => '', // your xxxxx attribute you want - * 'class' => 'reposition', // to add more css class to the button class attribute - * 'classOverride' => '' // to replace class attribute of the button + * 'xxxxx' => '', // your xxxxx attribute you want + * 'class' => 'reposition', // to add more css class to the button class attribute + * 'classOverride' => '' // to replace class attribute of the button * ], * 'confirm' => [ - * 'url' => 'http://', // Override Url to go when user click on action btn, if empty default url is $url.?confirm=yes, for no js compatibility use $url for fallback confirm. - * 'title' => '', // Override title of modal, if empty default title use "ConfirmBtnCommonTitle" lang key - * 'action-btn-label' => '', // Override label of action button, if empty default label use "Confirm" lang key - * 'cancel-btn-label' => '', // Override label of cancel button, if empty default label use "CloseDialog" lang key - * 'content' => '', // Override text of content, if empty default content use "ConfirmBtnCommonContent" lang key - * 'modal' => true, // true|false to display dialog as a modal (with dark background) - * 'isDropDown' => false, // true|false to display dialog as a dropdown list (css dropdown-item with dark background) - * ], + * 'url' => 'http://', // Override Url to go when user click on action btn, if empty default url is $url.?confirm=yes, for no js compatibility use $url for fallback confirm. + * 'title' => '', // Override title of modal, if empty default title use "ConfirmBtnCommonTitle" lang key + * 'action-btn-label' => '', // Override label of action button, if empty default label use "Confirm" lang key + * 'cancel-btn-label' => '', // Override label of cancel button, if empty default label use "CloseDialog" lang key + * 'content' => '', // Override text of content, if empty default content use "ConfirmBtnCommonContent" lang key + * 'modal' => true, // true|false to display dialog as a modal (with dark background) + * 'isDropDown' => false, // true|false to display dialog as a dropdown list (css dropdown-item with dark background) + * ], * ] * // phpcs:enable * @return string html button @@ -14081,13 +14215,18 @@ function dolGetButtonAction($label, $text = '', $actionType = 'default', $url = } // Here, $url is a simple link - if (!empty($params['isDropdown']) || !empty($params['isDropDown'])) { // Use the dropdown-item style (not for action button) $class = "dropdown-item"; } else { $class = 'butAction'; - if ($actionType == 'danger' || $actionType == 'delete') { - $class = 'butActionDelete'; + if ($actionType == 'edit') { + $class = 'butAction butActionEdit'; + } elseif ($actionType == 'email') { + $class = 'butAction butActionEmail'; + } elseif ($actionType == 'clone') { + $class = 'butAction butActionClone'; + } elseif ($actionType == 'danger' || $actionType == 'delete') { + $class = 'butAction butActionDelete'; if (!empty($url) && strpos($url, 'token=') === false) { $url .= '&token=' . newToken(); } @@ -14221,7 +14360,7 @@ function dolGetButtonAction($label, $text = '', $actionType = 'default', $url = * using `dolPrintHTMLForAttributeUrl()`. All other attributes are escaped using * `dolPrintHTMLForAttribute()`. * - * ⚠️ Note: Disabling escaping (via `$unescapedAttr`) is **not recommended** unless you + * Note: Disabling escaping (via `$unescapedAttr`) is **not recommended** unless you * fully trust the input data, as it may lead to XSS vulnerabilities. * * Example: @@ -14550,7 +14689,7 @@ function getElementProperties($elementType) $classpath = $module . '/class'; $classfile = $module; $classname = preg_replace('/det$/', 'Line', $element); - if (in_array($module, array('expedition', 'propale', 'facture', 'contrat', 'fichinter', 'commandefournisseur'))) { + if (in_array($module, array('expedition', 'propale', 'facture', 'contrat', 'fichinter', 'supplier_order', 'commandefournisseur'))) { $classname = preg_replace('/det$/', 'Ligne', $element); } } @@ -16360,14 +16499,15 @@ function show_actions_messaging($conf, $langs, $db, $filterobj, $objcon = null, // Date $out .= ' '; - $out .= dol_print_date($histo[$key]['datestart'], 'dayhour', 'tzuserrel'); + $out .= dol_print_date($histo[$key]['datestart'], 'day', 'tzuserrel'); + $out .= '   '.dol_print_date($histo[$key]['datestart'], 'hour', 'tzuserrel', null, false, 1); if ($histo[$key]['dateend'] && $histo[$key]['dateend'] != $histo[$key]['datestart']) { $tmpa = dol_getdate($histo[$key]['datestart'], true); $tmpb = dol_getdate($histo[$key]['dateend'], true); if ($tmpa['mday'] == $tmpb['mday'] && $tmpa['mon'] == $tmpb['mon'] && $tmpa['year'] == $tmpb['year']) { - $out .= '-' . dol_print_date($histo[$key]['dateend'], 'hour', 'tzuserrel'); + $out .= '-' . dol_print_date($histo[$key]['dateend'], 'hour', 'tzuserrel', null, false, 1); } else { - $out .= '-' . dol_print_date($histo[$key]['dateend'], 'dayhour', 'tzuserrel'); + $out .= '-' . dol_print_date($histo[$key]['dateend'], 'dayhour', 'tzuserrel', null, false, 1); } } $late = 0; diff --git a/htdocs/core/lib/geturl.lib.php b/htdocs/core/lib/geturl.lib.php index 21b278f5f85..0ae26918a4f 100644 --- a/htdocs/core/lib/geturl.lib.php +++ b/htdocs/core/lib/geturl.lib.php @@ -30,7 +30,7 @@ * - you can set MAIN_SECURITY_ANTI_SSRF_SERVER_IP to set static ip of server * - common local lookup ips like 127.*.*.* are automatically added * - * You can enable constant MAIN_CURL_DEBUG to get detail of output/input into dolibarr_curl.logfile. + * You can enable constant MAIN_CURL_DEBUG to get detail of output/input into dolibarr_curl.log file. * * @param string $url URL to call. * @param 'POST'|'GET'|'HEAD'|'PUT'|'PATCH'|'PUTALREADYFORMATED'|'POSTALREADYFORMATED'|'PATCHALREADYFORMATED'|'DELETE' $postorget 'POST', 'GET', 'HEAD', 'PUT', 'PATCH', 'PUTALREADYFORMATED', 'POSTALREADYFORMATED', 'PATCHALREADYFORMATED', 'DELETE' @@ -54,9 +54,15 @@ function getURLContent($url, $postorget = 'GET', $param = '', $followlocation = $PROXY_USER = getDolGlobalString('MAIN_PROXY_USER'); $PROXY_PASS = getDolGlobalString('MAIN_PROXY_PASS'); - dol_syslog("getURLContent postorget=".$postorget." URL=".$url." param=".$param); + dol_syslog("getURLContent postorget=".$postorget." URL=".$url); + if (getDolGlobalInt('MAIN_CURL_DEBUG')) { + dol_syslog("getURLContent postorget=".$postorget." URL=".$url." param=".$param, LOG_DEBUG, 0, '_curl'); + } if (!function_exists('curl_init')) { + if (getDolGlobalInt('MAIN_CURL_DEBUG')) { + dol_syslog("getURLContent PHP curl library must be installed", LOG_DEBUG, 0, '_curl'); + } return array('http_code' => 500, 'content' => '', 'curl_error_no' => 1, 'curl_error_msg' => 'PHP curl library must be installed'); } @@ -133,7 +139,9 @@ function getURLContent($url, $postorget = 'GET', $param = '', $followlocation = $newtimeoutconnect = ($timeoutconnect ? $timeoutconnect : getDolGlobalInt('MAIN_USE_CONNECT_TIMEOUT', 5)); $newtimeoutresponse = ($timeoutresponse ? $timeoutresponse : getDolGlobalInt('MAIN_USE_RESPONSE_TIMEOUT', 30)); - dol_syslog("getURLContent newtimeoutconnect=".$newtimeoutconnect." newtimeoutresponse=".$newtimeoutresponse); + if (getDolGlobalInt('MAIN_CURL_DEBUG')) { + dol_syslog("getURLContent newtimeoutconnect=".$newtimeoutconnect." newtimeoutresponse=".$newtimeoutresponse, LOG_DEBUG, 0, '_curl'); + } curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $newtimeoutconnect); curl_setopt($ch, CURLOPT_TIMEOUT, $newtimeoutresponse); @@ -213,6 +221,9 @@ function getURLContent($url, $postorget = 'GET', $param = '', $followlocation = do { if ($maxRedirection < 1) { + if (getDolGlobalInt('MAIN_CURL_DEBUG')) { + dol_syslog("getURLContent http_code=400 Maximum number of redirections reached", LOG_DEBUG, 0, '_curl'); + } return array('http_code' => 400, 'content' => 'Maximum number of redirections reached', 'curl_error_no' => 1, 'curl_error_msg' => 'Maximum number of redirections reached'); } @@ -227,6 +238,9 @@ function getURLContent($url, $postorget = 'GET', $param = '', $followlocation = if (in_array($hosttocheck, array('metadata.google.internal'))) { $info['http_code'] = 400; $info['content'] = 'Error bad hostname '.$hosttocheck.' (Used by Google metadata). This value for hostname is not allowed.'; + if (getDolGlobalInt('MAIN_CURL_DEBUG')) { + dol_syslog("getURLContent http_code=400 ".$info['content'], LOG_DEBUG, 0, '_curl'); + } return array('http_code' => 400, 'content' => $info['content'], 'curl_error_no' => 1, 'curl_error_msg' => $info['content']); } @@ -255,6 +269,9 @@ function getURLContent($url, $postorget = 'GET', $param = '', $followlocation = if ($tmpresult) { $info['http_code'] = 400; $info['content'] = $tmpresult; + if (getDolGlobalInt('MAIN_CURL_DEBUG')) { + dol_syslog("getURLContent http_code=400 ".$info['content'], LOG_DEBUG, 0, '_curl'); + } return array('http_code' => 400, 'content' => $tmpresult, 'curl_error_no' => 1, 'curl_error_msg' => $tmpresult); } } @@ -293,15 +310,15 @@ function getURLContent($url, $postorget = 'GET', $param = '', $followlocation = } $http_code = 0; - } while ($http_code); + } while ($http_code); // Stop if http_code is 0 $request = curl_getinfo($ch, CURLINFO_HEADER_OUT); // Reading of request must be done after sending request - dol_syslog("getURLContent request=".$request); + dol_syslog("getURLContent request without content body=".$request); if (getDolGlobalInt('MAIN_CURL_DEBUG')) { // This may contains binary data, so we don't output response by default. - dol_syslog("getURLContent request=".$request, LOG_DEBUG, 0, '_curl'); - dol_syslog("getURLContent response =".$response, LOG_DEBUG, 0, '_curl'); + dol_syslog("getURLContent request without body=".$request, LOG_DEBUG, 0, '_curl'); + dol_syslog("getURLContent response=".$response, LOG_DEBUG, 0, '_curl'); } dol_syslog("getURLContent response size=".strlen($response)); // This may contains binary data, so we don't output it @@ -319,6 +336,9 @@ function getURLContent($url, $postorget = 'GET', $param = '', $followlocation = $rep['curl_error_msg'] = curl_error($ch); dol_syslog("getURLContent response array is ".implode(',', $rep)); + if (getDolGlobalInt('MAIN_CURL_DEBUG')) { + dol_syslog("getURLContent curl_error_no=".$rep['curl_error_no']." curl_error_msg=".$rep['curl_error_msg'], LOG_DEBUG, 0, '_curl'); + } } else { //$info = curl_getinfo($ch); diff --git a/htdocs/core/lib/invoice.lib.php b/htdocs/core/lib/invoice.lib.php index c545f33e9a6..a6a60c9fe3d 100644 --- a/htdocs/core/lib/invoice.lib.php +++ b/htdocs/core/lib/invoice.lib.php @@ -110,6 +110,9 @@ function facture_prepare_head($object) require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php'; require_once DOL_DOCUMENT_ROOT.'/core/class/link.class.php'; $upload_dir = $conf->facture->dir_output."/".dol_sanitizeFileName($object->ref); + if (!empty($conf->facture->multidir_output[$object->entity])) { + $upload_dir = $conf->facture->multidir_output[$object->entity]."/".dol_sanitizeFileName($object->ref); + } $nbFiles = count(dol_dir_list($upload_dir, 'files', 0, '', '(\.meta|_preview.*\.png)$')); $nbLinks = Link::count($db, $object->element, $object->id); $head[$h][0] = dolBuildUrl(DOL_URL_ROOT.'/compta/facture/document.php', ['id' => $object->id]); diff --git a/htdocs/core/lib/pdf.lib.php b/htdocs/core/lib/pdf.lib.php index b458c807a74..8073d8c3dff 100644 --- a/htdocs/core/lib/pdf.lib.php +++ b/htdocs/core/lib/pdf.lib.php @@ -1157,7 +1157,7 @@ function pdf_pagefoot(&$pdf, $outputlangs, $paramfreetext, $fromcompany, $marge_ if (!empty($fromcompany->capital)) { $tmpamounttoshow = price2num($fromcompany->capital); // This field is a free string or a float if (is_numeric($tmpamounttoshow) && $tmpamounttoshow > 0) { - $line3 .= ($line3 ? " - " : "").$outputlangs->transnoentities("CapitalOf", price($tmpamounttoshow, 0, $outputlangs, 0, 0, 0, $conf->currency)); + $line3 .= ($line3 ? " - " : "").$outputlangs->transnoentities("CapitalOf", price($tmpamounttoshow, 0, $outputlangs, 0, 0, 0, getDolCurrency())); } elseif (!empty($fromcompany->capital)) { $line3 .= ($line3 ? " - " : "").$outputlangs->transnoentities("CapitalOf", (string) $fromcompany->capital); } @@ -2092,7 +2092,7 @@ function pdf_getlinevatrate($object, $i, $outputlangs, $hidedetails = 0) /** * Return line unit price excluding tax * - * @param SupplierProposal|CommandeFournisseur|Propal|Facture|FactureFournisseur|Commande|StockTransfer $object Object + * @param CommonObject|SupplierProposal|CommandeFournisseur|Propal|Facture|FactureFournisseur|Commande|StockTransfer $object Object * @param int $i Current line number * @param Translate $outputlangs Object langs for output * @param int<0,2> $hidedetails Hide details (0=no, 1=yes, 2=just special lines) diff --git a/htdocs/core/lib/price.lib.php b/htdocs/core/lib/price.lib.php index 14fbeead6b8..1d39ec0c7a7 100644 --- a/htdocs/core/lib/price.lib.php +++ b/htdocs/core/lib/price.lib.php @@ -89,7 +89,7 @@ */ function calcul_price_total($qty, $pu, $remise_percent_ligne, $txtva, $uselocaltax1_rate, $uselocaltax2_rate, $remise_percent_global, $price_base_type, $info_bits, $type, $seller = null, $localtaxes_array = [], $progress = 100, $multicurrency_tx = 1, $pu_devise = 0, $multicurrency_code = '') // @phpstan-ignore-line { - global $conf, $mysoc, $db; + global $conf, $mysoc, $db, $hookmanager; $result = array(); @@ -387,16 +387,16 @@ function calcul_price_total($qty, $pu, $remise_percent_ligne, $txtva, $uselocalt // If rounding is not using base 10 (rare) if (getDolGlobalString('MAIN_ROUNDING_RULE_TOT')) { if ($price_base_type == 'HT') { - $result[0] = price2num(round((float) $result[0] / (float) $conf->global->MAIN_ROUNDING_RULE_TOT, 0) * (float) $conf->global->MAIN_ROUNDING_RULE_TOT, 'MT'); - $result[1] = price2num(round((float) $result[1] / (float) $conf->global->MAIN_ROUNDING_RULE_TOT, 0) * (float) $conf->global->MAIN_ROUNDING_RULE_TOT, 'MT'); - $result[9] = price2num(round((float) $result[9] / (float) $conf->global->MAIN_ROUNDING_RULE_TOT, 0) * (float) $conf->global->MAIN_ROUNDING_RULE_TOT, 'MT'); - $result[10] = price2num(round((float) $result[10] / (float) $conf->global->MAIN_ROUNDING_RULE_TOT, 0) * (float) $conf->global->MAIN_ROUNDING_RULE_TOT, 'MT'); + $result[0] = price2num(round((float) $result[0] / getDolGlobalFloat('MAIN_ROUNDING_RULE_TOT'), 0) * getDolGlobalFloat('MAIN_ROUNDING_RULE_TOT'), 'MT'); + $result[1] = price2num(round((float) $result[1] / getDolGlobalFloat('MAIN_ROUNDING_RULE_TOT'), 0) * getDolGlobalFloat('MAIN_ROUNDING_RULE_TOT'), 'MT'); + $result[9] = price2num(round((float) $result[9] / getDolGlobalFloat('MAIN_ROUNDING_RULE_TOT'), 0) * getDolGlobalFloat('MAIN_ROUNDING_RULE_TOT'), 'MT'); + $result[10] = price2num(round((float) $result[10] / getDolGlobalFloat('MAIN_ROUNDING_RULE_TOT'), 0) * getDolGlobalFloat('MAIN_ROUNDING_RULE_TOT'), 'MT'); $result[2] = price2num((float) $result[0] + (float) $result[1] + (float) $result[9] + (float) $result[10], 'MT'); } else { - $result[1] = price2num(round((float) $result[1] / (float) $conf->global->MAIN_ROUNDING_RULE_TOT, 0) * (float) $conf->global->MAIN_ROUNDING_RULE_TOT, 'MT'); - $result[2] = price2num(round((float) $result[2] / (float) $conf->global->MAIN_ROUNDING_RULE_TOT, 0) * (float) $conf->global->MAIN_ROUNDING_RULE_TOT, 'MT'); - $result[9] = price2num(round((float) $result[9] / (float) $conf->global->MAIN_ROUNDING_RULE_TOT, 0) * (float) $conf->global->MAIN_ROUNDING_RULE_TOT, 'MT'); - $result[10] = price2num(round((float) $result[10] / (float) $conf->global->MAIN_ROUNDING_RULE_TOT, 0) * (float) $conf->global->MAIN_ROUNDING_RULE_TOT, 'MT'); + $result[1] = price2num(round((float) $result[1] / getDolGlobalFloat('MAIN_ROUNDING_RULE_TOT'), 0) * getDolGlobalFloat('MAIN_ROUNDING_RULE_TOT'), 'MT'); + $result[2] = price2num(round((float) $result[2] / getDolGlobalFloat('MAIN_ROUNDING_RULE_TOT'), 0) * getDolGlobalFloat('MAIN_ROUNDING_RULE_TOT'), 'MT'); + $result[9] = price2num(round((float) $result[9] / getDolGlobalFloat('MAIN_ROUNDING_RULE_TOT'), 0) * getDolGlobalFloat('MAIN_ROUNDING_RULE_TOT'), 'MT'); + $result[10] = price2num(round((float) $result[10] / getDolGlobalFloat('MAIN_ROUNDING_RULE_TOT'), 0) * getDolGlobalFloat('MAIN_ROUNDING_RULE_TOT'), 'MT'); $result[0] = price2num((float) $result[2] - (float) $result[1] - (float) $result[9] - (float) $result[10], 'MT'); } } @@ -453,7 +453,17 @@ function calcul_price_total($qty, $pu, $remise_percent_ligne, $txtva, $uselocalt $result[25] = $result[9]; $result[26] = $result[10]; } + dol_syslog('Price.lib::calcul_price_total MAIN_ROUNDING_RULE_TOT='.getDolGlobalString('MAIN_ROUNDING_RULE_TOT').' pu='.$pu.' qty='.$qty.' price_base_type='.$price_base_type.' total_ht='.$result[0].'-total_vat='.$result[1].'-total_ttc='.$result[2]); + // Allow an external module to bypass the calculation of prices + $parameters = array('result' => $result); + $tmpobject = null; $tmpaction = ''; + // @phan-suppress-next-line PhanPluginConstantVariableNull + $reshook = $hookmanager->executeHooks('calcul_price_total', $parameters, $tmpobject, $tmpaction); // @phan-suppress-current-line PhanPluginConstantVariableNull + if ($reshook > 0 && !empty($hookmanager->resArray['result'])) { + $result = $hookmanager->resArray['result']; + } + return $result; } diff --git a/htdocs/core/lib/security.lib.php b/htdocs/core/lib/security.lib.php index 985760d1342..d09e2bd784a 100644 --- a/htdocs/core/lib/security.lib.php +++ b/htdocs/core/lib/security.lib.php @@ -515,6 +515,7 @@ function restrictedArea(User $user, $features, $object = 0, $tableandshare = '', $tableandshare = 'paiementcharge'; $parentfortableentity = 'fk_charge@chargesociales'; } + // if commonObjectLine : Using many2one related commonObject // @see commonObjectLine::parentElement if (in_array($features, ['commandedet', 'propaldet', 'facturedet', 'supplier_proposaldet', 'evaluationdet', 'skilldet', 'deliverydet', 'contratdet'])) { @@ -526,6 +527,11 @@ function restrictedArea(User $user, $features, $object = 0, $tableandshare = '', } elseif ($features == 'invoice_supplier_det_rec') { $features = 'invoice_supplier_rec'; } + if ($features == 'evaluation') { + $features = 'hrm'; + $feature2 = 'evaluation'; + } + // @todo check : project_task // @todo possible ? // elseif (substr($features, -3, 3) == 'det') { @@ -993,8 +999,11 @@ function checkUserAccessToObject($user, array $featuresarray, $object = 0, $tabl if ($feature == 'project') { $feature = 'projet'; } - if ($feature == 'task') { - $feature = 'projet_task'; + if ($feature == 'projet' && !empty($feature2) && is_array($feature2) && !empty(array_intersect(array('project_task', 'projet_task'), $feature2))) { + $feature = 'project_task'; + } + if ($feature == 'task' || $feature == 'projet_task') { + $feature = 'project_task'; } if ($feature == 'eventorganization') { $feature = 'agenda'; @@ -1015,8 +1024,8 @@ function checkUserAccessToObject($user, array $featuresarray, $object = 0, $tabl $checksoc = array('societe'); // Test for object Societe $checkparentsoc = array('agenda', 'contact', 'contrat'); // Test on entity + link to third party on field $dbt_keyfield. Allowed if link is empty (Ex: contacts...). $checkproject = array('projet', 'project'); // Test for project object - $checktask = array('projet_task'); // Test for task object - $checkhierarchy = array('expensereport', 'holiday'); // check permission among the hierarchy of user + $checktask = array('projet_task', 'project_task'); // Test for task object + $checkhierarchy = array('expensereport', 'holiday', 'hrm'); // check permission among the hierarchy of user $checkuser = array('bookmark'); // check permission among the fk_user (must be myself or null) $nocheck = array('barcode', 'stock', 'webhook'); // No test @@ -1155,6 +1164,7 @@ function checkUserAccessToObject($user, array $featuresarray, $object = 0, $tabl return false; } } else { + $sharedelement = 'project'; // for multicompany compatibility $sql = "SELECT COUNT(dbt.".$dbt_select.") as nb"; $sql .= " FROM ".MAIN_DB_PREFIX.$dbtablename." as dbt"; $sql .= " WHERE dbt.".$dbt_select." IN (".$db->sanitize($objectid, 1).")"; @@ -1243,6 +1253,20 @@ function checkUserAccessToObject($user, array $featuresarray, $object = 0, $tabl } } } + if ($feature == 'hrm' && in_array('evaluation', $feature2)) { + $useridtocheck = $object->fk_user; + + if ($user->hasRight('hrm', 'evaluation', 'readall')) { + // the user can view evaluations for anyone + return true; + } + if (!$user->hasRight('hrm', 'evaluation', 'read')) { + // the user can't view any evaluations + return false; + } + // the user can only their own evaluations or their subordinates' + return in_array($useridtocheck, $childids); + } } // For some object, we also have to check it is public or owned by user diff --git a/htdocs/core/lib/usergroups.lib.php b/htdocs/core/lib/usergroups.lib.php index 702d963b00a..0a164de2404 100644 --- a/htdocs/core/lib/usergroups.lib.php +++ b/htdocs/core/lib/usergroups.lib.php @@ -159,6 +159,15 @@ function user_prepare_head(User $object) $h++; } + /* + if (isModEnabled('api') && !empty($object->api_key) && ($user->admin || $user->id == $object->id)) { + $head[$h][0] = DOL_URL_ROOT.'/user/api_token/list.php?id='.$object->id; + $head[$h][1] = $langs->trans("ApiTokens"); + $head[$h][2] = 'apitoken'; + $h++; + } + */ + // Such info on users is visible only by internal user if (empty($user->socid)) { // Notes diff --git a/htdocs/core/lib/website.lib.php b/htdocs/core/lib/website.lib.php index 0ad8b7d05a1..adc96d61e41 100644 --- a/htdocs/core/lib/website.lib.php +++ b/htdocs/core/lib/website.lib.php @@ -729,7 +729,7 @@ function getStructuredData($type, $data = array()) "offers": { "@type": "Offer", "price": "'.dol_escape_json($data['price']).'", - "priceCurrency": "'.dol_escape_json($data['currency'] ? $data['currency'] : $conf->currency).'" + "priceCurrency": "'.dol_escape_json($data['currency'] ? $data['currency'] : getDolCurrency()).'" } }'."\n"; $ret .= ''."\n"; @@ -864,7 +864,7 @@ function getStructuredData($type, $data = array()) "offers": { "@type": "Offer", "url": "https://example.com/anvil", - "priceCurrency": "'.dol_escape_json($data['currency'] ? $data['currency'] : $conf->currency).'", + "priceCurrency": "'.dol_escape_json($data['currency'] ? $data['currency'] : getDolCurrency()).'", "price": "'.dol_escape_json($data['price']).'", "itemCondition": "https://schema.org/UsedCondition", "availability": "https://schema.org/InStock", diff --git a/htdocs/core/menus/standard/auguria.lib.php b/htdocs/core/menus/standard/auguria.lib.php index 416fefdace3..8bbfebe19e1 100644 --- a/htdocs/core/menus/standard/auguria.lib.php +++ b/htdocs/core/menus/standard/auguria.lib.php @@ -191,7 +191,6 @@ function print_auguria_menu($db, $atarget, $type_user, &$tabMenu, &$menu, $noout foreach ($menu->liste as $menuval) { print_start_menu_entry_auguria($menuval['idsel'], $menuval['classname'], $menuval['enabled']); - // @phan-ignore-next-line // @phpstan-ignore-next-line print_text_menu_entry_auguria($menuval['titre'], $menuval['enabled'], ($menuval['url'] != '#' ? DOL_URL_ROOT : '').$menuval['url'], $menuval['id'], $menuval['idsel'], $menuval['classname'], ($menuval['target'] ? $menuval['target'] : $atarget), $menuval); print_end_menu_entry_auguria($menuval['enabled']); diff --git a/htdocs/core/menus/standard/eldy.lib.php b/htdocs/core/menus/standard/eldy.lib.php index 1490cf5d460..b24aa604341 100644 --- a/htdocs/core/menus/standard/eldy.lib.php +++ b/htdocs/core/menus/standard/eldy.lib.php @@ -608,7 +608,6 @@ function print_eldy_menu($db, $atarget, $type_user, &$tabMenu, &$menu, $noout = //var_dump($menu->liste); foreach ($menu->liste as $menuval) { print_start_menu_entry($menuval['idsel'], $menuval['classname'], $menuval['enabled']); - // @phan-ignore-next-line // @phpstan-ignore-next-line print_text_menu_entry($menuval['titre'], $menuval['enabled'], (($menuval['url'] != '#' && !preg_match('/^(http:\/\/|https:\/\/)/i', $menuval['url'])) ? DOL_URL_ROOT : '').$menuval['url'], $menuval['id'], $menuval['idsel'], $menuval['classname'], ($menuval['target'] ? $menuval['target'] : $atarget), $menuval); print_end_menu_entry($menuval['enabled']); diff --git a/htdocs/core/modules/DolibarrModules.class.php b/htdocs/core/modules/DolibarrModules.class.php index 2315a2e013a..b425ea09617 100644 --- a/htdocs/core/modules/DolibarrModules.class.php +++ b/htdocs/core/modules/DolibarrModules.class.php @@ -139,16 +139,16 @@ class DolibarrModules // Can not be abstract, because we need to instantiate it */ public $rights_class; - const URL_FOR_BLACKLISTED_MODULES = 'https://ping.dolibarr.org/modules-blacklist.txt'; + public const URL_FOR_BLACKLISTED_MODULES = 'https://ping.dolibarr.org/modules-blacklist.txt'; - const KEY_ID = 0; - const KEY_LABEL = 1; - const KEY_TYPE = 2; // deprecated - const KEY_DEFAULT = 3; - const KEY_FIRST_LEVEL = 4; - const KEY_SECOND_LEVEL = 5; - const KEY_MODULE = 6; - const KEY_ENABLED = 7; + public const KEY_ID = 0; + public const KEY_LABEL = 1; + public const KEY_TYPE = 2; // deprecated + public const KEY_DEFAULT = 3; + public const KEY_FIRST_LEVEL = 4; + public const KEY_SECOND_LEVEL = 5; + public const KEY_MODULE = 6; + public const KEY_ENABLED = 7; /** * @var array|int<1,1> Module menu entries (1 means the menu entries are not declared into module descriptor but are hardcoded into menu manager) @@ -400,6 +400,11 @@ class DolibarrModules // Can not be abstract, because we need to instantiate it */ public $disabled; + /** + * @var array Array [''=>''] + */ + public $automatic_activation = array(); + /** * @var int Module is enabled globally (Multicompany support) */ diff --git a/htdocs/core/modules/accountancy/doc/pdf_balance.modules.php b/htdocs/core/modules/accountancy/doc/pdf_balance.modules.php index 1a68c6054f5..4c4fef9846a 100644 --- a/htdocs/core/modules/accountancy/doc/pdf_balance.modules.php +++ b/htdocs/core/modules/accountancy/doc/pdf_balance.modules.php @@ -725,11 +725,11 @@ class pdf_balance extends ModelePdfAccountancy /** * Define Array Column Field * - * @param BookKeeping $object common object - * @param Translate $outputlangs langs - * @param int $hidedetails Do not show line details - * @param int $hidedesc Do not show desc - * @param int $hideref Do not show ref + * @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) diff --git a/htdocs/core/modules/accountancy/doc/pdf_bookkeeping.modules.php b/htdocs/core/modules/accountancy/doc/pdf_bookkeeping.modules.php index e0c501a6a66..997111b4cea 100644 --- a/htdocs/core/modules/accountancy/doc/pdf_bookkeeping.modules.php +++ b/htdocs/core/modules/accountancy/doc/pdf_bookkeeping.modules.php @@ -706,11 +706,11 @@ class pdf_bookkeeping extends ModelePdfAccountancy /** * Define Array Column Field * - * @param BookKeeping $object common object - * @param Translate $outputlangs langs - * @param int $hidedetails Do not show line details - * @param int $hidedesc Do not show desc - * @param int $hideref Do not show ref + * @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) diff --git a/htdocs/core/modules/accountancy/doc/pdf_ledger.modules.php b/htdocs/core/modules/accountancy/doc/pdf_ledger.modules.php index 0890ed5631e..78792e65cb7 100644 --- a/htdocs/core/modules/accountancy/doc/pdf_ledger.modules.php +++ b/htdocs/core/modules/accountancy/doc/pdf_ledger.modules.php @@ -752,7 +752,7 @@ class pdf_ledger extends ModelePdfAccountancy * @param TCPDF $pdf PDF * @param BookKeeping $object Object to show * @param Translate $outputlangs Object lang for output - * @param int $hidefreetext 1=Hide free text + * @param int<0,1> $hidefreetext 1=Hide free text * @return int Return height of bottom margin including footer text */ protected function _pagefoot(&$pdf, $object, $outputlangs, $hidefreetext = 0) @@ -764,11 +764,11 @@ class pdf_ledger extends ModelePdfAccountancy /** * Define Array Column Field * - * @param BookKeeping $object common object - * @param Translate $outputlangs langs - * @param int $hidedetails Do not show line details - * @param int $hidedesc Do not show desc - * @param int $hideref Do not show ref + * @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) diff --git a/htdocs/core/modules/asset/doc/pdf_standard_asset.modules.php b/htdocs/core/modules/asset/doc/pdf_standard_asset.modules.php index 496b4e7b795..643498355d1 100644 --- a/htdocs/core/modules/asset/doc/pdf_standard_asset.modules.php +++ b/htdocs/core/modules/asset/doc/pdf_standard_asset.modules.php @@ -756,7 +756,7 @@ class pdf_standard_asset extends ModelePDFAsset $hidetop = -1; } - $currency = !empty($currency) ? $currency : $conf->currency; + $currency = !empty($currency) ? $currency : getDolCurrency(); $default_font_size = pdf_getPDFFontSize($outputlangs); // Amount in (at tab_top - 1) @@ -1065,7 +1065,7 @@ class pdf_standard_asset extends ModelePDFAsset /** * Define Array Column Field * - * @param Asset $object common object + * @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 diff --git a/htdocs/core/modules/barcode/modules_barcode.class.php b/htdocs/core/modules/barcode/modules_barcode.class.php index 0c1caa9cd3e..7743913fbdc 100644 --- a/htdocs/core/modules/barcode/modules_barcode.class.php +++ b/htdocs/core/modules/barcode/modules_barcode.class.php @@ -75,6 +75,21 @@ abstract class ModeleBarCode return -1; // Error by default, this method must be implemented by the driver } + /** + * Return an image file on the fly (no need to write on disk) with the HTTP content-type of image. + * + * @param string $code Value to encode + * @param string $encoding Mode of encoding + * @param string $readable Code can be read (What is this ? is this used ?) + * @param integer $scale Scale + * @param integer $nooutputiferror No output if error + * @return int Return integer <0 if KO, >0 if OK + */ + public function buildBarCode($code, $encoding, $readable = 'Y', $scale = 1, $nooutputiferror = 0) + { + return 1; + } + /** * Return true if encoding is supported * diff --git a/htdocs/core/modules/commande/doc/pdf_einstein.modules.php b/htdocs/core/modules/commande/doc/pdf_einstein.modules.php index 58c551b49e0..7acda8d89bf 100644 --- a/htdocs/core/modules/commande/doc/pdf_einstein.modules.php +++ b/htdocs/core/modules/commande/doc/pdf_einstein.modules.php @@ -1256,7 +1256,7 @@ class pdf_einstein extends ModelePDFCommandes $hidetop = -1; } - $currency = !empty($currency) ? $currency : $conf->currency; + $currency = !empty($currency) ? $currency : getDolCurrency(); $default_font_size = pdf_getPDFFontSize($outputlangs); // Amount in (at tab_top - 1) diff --git a/htdocs/core/modules/commande/doc/pdf_eratosthene.modules.php b/htdocs/core/modules/commande/doc/pdf_eratosthene.modules.php index 9a72cd5c7e5..30b5db2cc44 100644 --- a/htdocs/core/modules/commande/doc/pdf_eratosthene.modules.php +++ b/htdocs/core/modules/commande/doc/pdf_eratosthene.modules.php @@ -723,7 +723,7 @@ class pdf_eratosthene extends ModelePDFCommandes $total_excl_tax = pdf_getlinetotalexcltax($object, $i, $outputlangs, $hidedetails); $this->printStdColumnContent($pdf, $curY, 'totalexcltax', $total_excl_tax); } elseif ($object->lines[$i]->qty < 0 && isset($sub_options['subtotalshowtotalexludingvatonpdf'])) { - if (isModEnabled('multicurrency') && $object->multicurrency_code != $conf->currency) { + if (isModEnabled('multicurrency') && $object->multicurrency_code != getDolCurrency()) { $total_excl_tax = $object->getSubtotalLineMulticurrencyAmount($object->lines[$i]); } else { $total_excl_tax = $object->getSubtotalLineAmount($object->lines[$i]); @@ -1535,7 +1535,7 @@ class pdf_eratosthene extends ModelePDFCommandes $hidetop = -1; } - $currency = !empty($currency) ? $currency : $conf->currency; + $currency = !empty($currency) ? $currency : getDolCurrency(); $default_font_size = pdf_getPDFFontSize($outputlangs); // Amount in (at tab_top - 1) @@ -1934,7 +1934,7 @@ class pdf_eratosthene extends ModelePDFCommandes /** * Define Array Column Field * - * @param Commande $object common object + * @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 diff --git a/htdocs/core/modules/delivery/doc/pdf_storm.modules.php b/htdocs/core/modules/delivery/doc/pdf_storm.modules.php index 6084109b0e0..b467dd600a6 100644 --- a/htdocs/core/modules/delivery/doc/pdf_storm.modules.php +++ b/htdocs/core/modules/delivery/doc/pdf_storm.modules.php @@ -883,11 +883,11 @@ class pdf_storm extends ModelePDFDeliveryOrder /** * Define Array Column Field * - * @param Delivery $object common object - * @param Translate $outputlangs langs - * @param int $hidedetails Do not show line details - * @param int $hidedesc Do not show desc - * @param int $hideref Do not show ref + * @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) diff --git a/htdocs/core/modules/dons/html_cerfafr.modules.php b/htdocs/core/modules/dons/html_cerfafr.modules.php index 609443fa8f0..7148abfa637 100644 --- a/htdocs/core/modules/dons/html_cerfafr.modules.php +++ b/htdocs/core/modules/dons/html_cerfafr.modules.php @@ -5,7 +5,7 @@ * Copyright (C) 2012 Marcos García * Copyright (C) 2014-2020 Alexandre Spangaro * Copyright (C) 2015 Benoit Bruchard - * Copyright (C) 2024 MDW + * Copyright (C) 2024-2025 MDW * Copyright (C) 2025 Frédéric France * * This program is free software; you can redistribute it and/or modify @@ -172,7 +172,7 @@ class html_cerfafr extends ModeleDon $form = str_replace('__DONATOR_ADDRESS__', $don->address, $form); $form = str_replace('__DONATOR_ZIP__', $don->zip, $form); $form = str_replace('__DONATOR_TOWN__', $don->town, $form); - $form = str_replace('__PAYMENTMODE_LIB__ ', $paymentmode, $form); + $form = str_replace('__PAYMENTMODE_LIB__ ', (string) $paymentmode, $form); $form = str_replace('__NOW__', dol_print_date($now, 'day', false, $outputlangs), $form); $form = str_replace('__DonationRef__', $outputlangs->trans("DonationRef"), $form); $form = str_replace('__DonationTitle__', $outputlangs->trans("DonationTitle"), $form); diff --git a/htdocs/core/modules/dons/html_generic.modules.php b/htdocs/core/modules/dons/html_generic.modules.php index 622c8139cc0..5f7499cb2e5 100644 --- a/htdocs/core/modules/dons/html_generic.modules.php +++ b/htdocs/core/modules/dons/html_generic.modules.php @@ -6,7 +6,7 @@ * Copyright (C) 2014-2020 Alexandre Spangaro * Copyright (C) 2015 Benoit Bruchard * Copyright (C) 2015 Benjamin Neumann - * Copyright (C) 2024 MDW + * Copyright (C) 2024-2025 MDW * * 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 @@ -97,7 +97,7 @@ class html_generic extends ModeleDon $formclass->load_cache_types_paiements(); if ($don->mode_reglement_id) { - $paymentmode = $formclass->cache_types_paiements[$don->mode_reglement_id]['label']; + $paymentmode = (string) $formclass->cache_types_paiements[$don->mode_reglement_id]['label']; } else { $paymentmode = ''; } diff --git a/htdocs/core/modules/expedition/doc/pdf_espadon.modules.php b/htdocs/core/modules/expedition/doc/pdf_espadon.modules.php index c88488d32a5..d08d0b34bf5 100644 --- a/htdocs/core/modules/expedition/doc/pdf_espadon.modules.php +++ b/htdocs/core/modules/expedition/doc/pdf_espadon.modules.php @@ -1317,11 +1317,11 @@ class pdf_espadon extends ModelePdfExpedition /** * Define Array Column Field * - * @param Expedition $object common object - * @param Translate $outputlangs langs - * @param int $hidedetails Do not show line details - * @param int $hidedesc Do not show desc - * @param int $hideref Do not show ref + * @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) diff --git a/htdocs/core/modules/facture/doc/doc_generic_invoice_odt.modules.php b/htdocs/core/modules/facture/doc/doc_generic_invoice_odt.modules.php index 80e81be745a..bb9155f66b2 100644 --- a/htdocs/core/modules/facture/doc/doc_generic_invoice_odt.modules.php +++ b/htdocs/core/modules/facture/doc/doc_generic_invoice_odt.modules.php @@ -1,10 +1,11 @@ - * Copyright (C) 2012 Regis Houssin - * Copyright (C) 2014 Marcos García - * Copyright (C) 2016 Charlie Benke - * Copyright (C) 2018-2025 Frédéric France +/* Copyright (C) 2010-2012 Laurent Destailleur + * Copyright (C) 2012 Regis Houssin + * Copyright (C) 2014 Marcos García + * Copyright (C) 2016 Charlie Benke + * Copyright (C) 2018-2025 Frédéric France * Copyright (C) 2024 MDW + * Copyright (C) 2025 Alexandre Spangaro * * 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 @@ -344,6 +345,19 @@ class doc_generic_invoice_odt extends ModelePDFFactures $nbProduct = 0; $nbService = 0; foreach ($object->lines as $line) { + // Do not take into account lines of the type “deposit.” + $is_deposit = false; + if (preg_match('/^\((.*)\)$/', $line->desc, $reg)) { + if ($reg[1] == 'DEPOSIT') { + $is_deposit = true; + } + } + + // If DEPOSIT, this line is completely ignored for calculations. + if ($is_deposit) { + continue; + } + // determine category of operation if ($categoryOfOperation < 2) { $lineProductType = $line->product_type; diff --git a/htdocs/core/modules/facture/doc/pdf_crabe.modules.php b/htdocs/core/modules/facture/doc/pdf_crabe.modules.php index 8ca3be705c0..29b1eadf415 100644 --- a/htdocs/core/modules/facture/doc/pdf_crabe.modules.php +++ b/htdocs/core/modules/facture/doc/pdf_crabe.modules.php @@ -13,7 +13,7 @@ * Copyright (C) 2022 Charlene Benke * Copyright (C) 2024-2025 MDW * Copyright (C) 2024-2025 Nick Fragoulis - * Copyright (C) 2024 Alexandre Spangaro + * Copyright (C) 2024-2025 Alexandre Spangaro * * 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 @@ -388,6 +388,19 @@ class pdf_crabe extends ModelePDFFactures $this->atleastonediscount++; } + // Do not take into account lines of the type “deposit.” + $is_deposit = false; + if (preg_match('/^\((.*)\)$/', $object->lines[$i]->desc, $reg)) { + if ($reg[1] == 'DEPOSIT') { + $is_deposit = true; + } + } + + // If DEPOSIT, this line is completely ignored for calculations. + if ($is_deposit) { + continue; + } + // determine category of operation if ($categoryOfOperation < 2) { $lineProductType = $object->lines[$i]->product_type; @@ -880,12 +893,20 @@ class pdf_crabe extends ModelePDFFactures $pdf->AliasNbPages(); // @phan-suppress-current-line PhanUndeclaredMethod } + if (getDolGlobalString('INVOICE_ADD_SWISS_QR_CODE') == 'bottom') { + $result = $this->addBottomQRInvoice($pdf, $object, $outputlangs); + if (!$result) { + $pdf->Close(); + return 0; + } + } + // Add terms to sale - if (!empty($mysoc->termsofsale) && getDolGlobalInt('MAIN_PDF_ADD_TERMSOFSALE_INVOICE')) { + if (getDolGlobalInt('MAIN_PDF_ADD_TERMSOFSALE_INVOICE')) { $termsofsalefilename = getDolGlobalString('MAIN_INFO_INVOICE_TERMSOFSALE'); - $termsofsale = $conf->mycompany->dir_output.'/'.$termsofsalefilename; - if (!empty($conf->mycompany->multidir_output[$object->entity ?? $conf->entity])) { - $termsofsale = $conf->mycompany->multidir_output[$object->entity ?? $conf->entity].'/'.$mysoc->termsofsale; + $termsofsale = $conf->invoice->dir_output.'/'.$termsofsalefilename; + if (!empty($conf->invoice->multidir_output[$object->entity ?? $conf->entity])) { + $termsofsale = $conf->invoice->multidir_output[$object->entity ?? $conf->entity].'/'.$termsofsalefilename; } if (file_exists($termsofsale) && is_readable($termsofsale)) { $pagecount = $pdf->setSourceFile($termsofsale); @@ -901,13 +922,7 @@ class pdf_crabe extends ModelePDFFactures } } } - if (getDolGlobalString('INVOICE_ADD_SWISS_QR_CODE') == 'bottom') { - $result = $this->addBottomQRInvoice($pdf, $object, $outputlangs); - if (!$result) { - $pdf->Close(); - return 0; - } - } + $pdf->Close(); $pdf->Output($file, 'F'); diff --git a/htdocs/core/modules/facture/doc/pdf_octopus.modules.php b/htdocs/core/modules/facture/doc/pdf_octopus.modules.php index 21b4dd1a0ab..3a2339b6b07 100644 --- a/htdocs/core/modules/facture/doc/pdf_octopus.modules.php +++ b/htdocs/core/modules/facture/doc/pdf_octopus.modules.php @@ -1,17 +1,17 @@ - * Copyright (C) 2005-2012 Regis Houssin - * Copyright (C) 2008 Raphael Bertrand - * Copyright (C) 2010-2014 Juanjo Menent - * Copyright (C) 2012 Christophe Battarel - * Copyright (C) 2012 Cédric Salvador - * Copyright (C) 2012-2014 Raphaël Doursenaud - * Copyright (C) 2015 Marcos Garcia - * Copyright (C) 2017 Ferran Marcet - * Copyright (C) 2018-2025 Frédéric France - * Copyright (C) 2022 Anthony Berton - * Copyright (C) 2022-2025 Alexandre Spangaro - * Copyright (C) 2022-2024 Eric Seigne +/* Copyright (C) 2004-2014 Laurent Destailleur + * Copyright (C) 2005-2012 Regis Houssin + * Copyright (C) 2008 Raphael Bertrand + * Copyright (C) 2010-2014 Juanjo Menent + * Copyright (C) 2012 Christophe Battarel + * Copyright (C) 2012 Cédric Salvador + * Copyright (C) 2012-2014 Raphaël Doursenaud + * Copyright (C) 2015 Marcos Garcia + * Copyright (C) 2017 Ferran Marcet + * Copyright (C) 2018-2025 Frédéric France + * Copyright (C) 2022 Anthony Berton + * Copyright (C) 2022-2025 Alexandre Spangaro + * Copyright (C) 2022-2024 Eric Seigne * Copyright (C) 2024-2025 MDW * Copyright (C) 2024-2025 Nick Fragoulis * @@ -488,6 +488,19 @@ class pdf_octopus extends ModelePDFFactures $this->atleastonediscount++; } + // Do not take into account lines of the type “deposit.” + $is_deposit = false; + if (preg_match('/^\((.*)\)$/', $object->lines[$i]->desc, $reg)) { + if ($reg[1] == 'DEPOSIT') { + $is_deposit = true; + } + } + + // If DEPOSIT, this line is completely ignored for calculations. + if ($is_deposit) { + continue; + } + // determine category of operation if ($categoryOfOperation < 2) { $lineProductType = $object->lines[$i]->product_type; @@ -1177,11 +1190,11 @@ class pdf_octopus extends ModelePDFFactures $pdf->AliasNbPages(); // @phan-suppress-current-line PhanUndeclaredMethod } // Add terms to sale - if (!empty($mysoc->termsofsale) && getDolGlobalInt('MAIN_PDF_ADD_TERMSOFSALE_INVOICE')) { + if (getDolGlobalInt('MAIN_PDF_ADD_TERMSOFSALE_INVOICE')) { $termsofsalefilename = getDolGlobalString('MAIN_INFO_INVOICE_TERMSOFSALE'); - $termsofsale = $conf->mycompany->dir_output.'/'.$termsofsalefilename; - if (!empty($conf->mycompany->multidir_output[$object->entity ?? $conf->entity])) { - $termsofsale = $conf->mycompany->multidir_output[$object->entity ?? $conf->entity].'/'.$mysoc->termsofsale; + $termsofsale = $conf->invoice->dir_output.'/'.$termsofsalefilename; + if (!empty($conf->invoice->multidir_output[$object->entity ?? $conf->entity])) { + $termsofsale = $conf->invoice->multidir_output[$object->entity ?? $conf->entity].'/'.$termsofsalefilename; } if (file_exists($termsofsale) && is_readable($termsofsale)) { $pagecount = $pdf->setSourceFile($termsofsale); @@ -1206,6 +1219,28 @@ class pdf_octopus extends ModelePDFFactures $this->addBottomQRInvoice($pdf, $object, $outputlangs); } + // Add terms to sale + if (getDolGlobalInt('MAIN_PDF_ADD_TERMSOFSALE_INVOICE')) { + $termsofsalefilename = getDolGlobalString('MAIN_INFO_INVOICE_TERMSOFSALE'); + $termsofsale = $conf->invoice->dir_output.'/'.$termsofsalefilename; + if (!empty($conf->invoice->multidir_output[$object->entity ?? $conf->entity])) { + $termsofsale = $conf->invoice->multidir_output[$object->entity ?? $conf->entity].'/'.$termsofsalefilename; + } + if (file_exists($termsofsale) && is_readable($termsofsale)) { + $pagecount = $pdf->setSourceFile($termsofsale); + for ($i = 1; $i <= $pagecount; $i++) { + $tplIdx = $pdf->importPage($i); + if ($tplIdx !== false) { + $s = $pdf->getTemplatesize($tplIdx); + $pdf->AddPage($s['h'] > $s['w'] ? 'P' : 'L'); + $pdf->useTemplate($tplIdx); + } else { + setEventMessages(null, array($termsofsale.' cannot be added, probably protected PDF'), 'warnings'); + } + } + } + } + $pdf->Close(); $pdf->Output($file, 'F'); @@ -2722,11 +2757,11 @@ class pdf_octopus extends ModelePDFFactures /** * Define Array Column Field * - * @param Facture $object common object - * @param Translate $outputlangs langs - * @param int $hidedetails Do not show line details - * @param int $hidedesc Do not show desc - * @param int $hideref Do not show ref + * @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) diff --git a/htdocs/core/modules/facture/doc/pdf_sponge.modules.php b/htdocs/core/modules/facture/doc/pdf_sponge.modules.php index 1b04a373dd6..209ead0c0b6 100644 --- a/htdocs/core/modules/facture/doc/pdf_sponge.modules.php +++ b/htdocs/core/modules/facture/doc/pdf_sponge.modules.php @@ -1,17 +1,17 @@ - * Copyright (C) 2005-2012 Regis Houssin - * Copyright (C) 2008 Raphael Bertrand - * Copyright (C) 2010-2014 Juanjo Menent - * Copyright (C) 2012 Christophe Battarel - * Copyright (C) 2012 Cédric Salvador - * Copyright (C) 2012-2014 Raphaël Doursenaud - * Copyright (C) 2015 Marcos García - * Copyright (C) 2017 Ferran Marcet - * Copyright (C) 2018-2025 Frédéric France +/* Copyright (C) 2004-2024 Laurent Destailleur + * Copyright (C) 2005-2012 Regis Houssin + * Copyright (C) 2008 Raphael Bertrand + * Copyright (C) 2010-2014 Juanjo Menent + * Copyright (C) 2012 Christophe Battarel + * Copyright (C) 2012 Cédric Salvador + * Copyright (C) 2012-2014 Raphaël Doursenaud + * Copyright (C) 2015 Marcos García + * Copyright (C) 2017 Ferran Marcet + * Copyright (C) 2018-2025 Frédéric France * Copyright (C) 2018-2024 Anthony Berton - * Copyright (C) 2022-2024 Alexandre Spangaro - * Copyright (C) 2024-2025 MDW + * Copyright (C) 2022-2025 Alexandre Spangaro + * Copyright (C) 2024-2025 MDW * Copyright (C) 2024-2025 Nick Fragoulis * Copyright (C) 2024 Franck Moreau * @@ -419,6 +419,19 @@ class pdf_sponge extends ModelePDFFactures $this->atleastonediscount++; } + // Do not take into account lines of the type “deposit.” + $is_deposit = false; + if (preg_match('/^\((.*)\)$/', $object->lines[$i]->desc, $reg)) { + if ($reg[1] == 'DEPOSIT') { + $is_deposit = true; + } + } + + // If DEPOSIT, this line is completely ignored for calculations. + if ($is_deposit) { + continue; + } + // determine category of operation if ($categoryOfOperation < 2) { $lineProductType = $object->lines[$i]->product_type; @@ -1095,11 +1108,11 @@ class pdf_sponge extends ModelePDFFactures $pdf->AliasNbPages(); // @phan-suppress-current-line PhanUndeclaredMethod } // Add terms to sale - if (!empty($mysoc->termsofsale) && getDolGlobalInt('MAIN_PDF_ADD_TERMSOFSALE_INVOICE')) { + if (getDolGlobalInt('MAIN_PDF_ADD_TERMSOFSALE_INVOICE')) { $termsofsalefilename = getDolGlobalString('MAIN_INFO_INVOICE_TERMSOFSALE'); - $termsofsale = $conf->mycompany->dir_output.'/'.$termsofsalefilename; - if (!empty($conf->mycompany->multidir_output[$object->entity ?? $conf->entity])) { - $termsofsale = $conf->mycompany->multidir_output[$object->entity ?? $conf->entity].'/'.$mysoc->termsofsale; + $termsofsale = $conf->invoice->dir_output.'/'.$termsofsalefilename; + if (!empty($conf->invoice->multidir_output[$object->entity ?? $conf->entity])) { + $termsofsale = $conf->invoice->multidir_output[$object->entity ?? $conf->entity].'/'.$termsofsalefilename; } if (file_exists($termsofsale) && is_readable($termsofsale)) { $pagecount = $pdf->setSourceFile($termsofsale); @@ -1658,9 +1671,9 @@ class pdf_sponge extends ModelePDFFactures $posy += 2; - // SHOW EPC QR CODE - if (getDolGlobalString('INVOICE_ADD_EPC_QR_CODE') == 'bottom') { - $qrPosX = 120; + // SHOW EPC QR CODE at bottom, but only if unpaid amount exists + if ((getDolGlobalString('INVOICE_ADD_EPC_QR_CODE') == 'bottom') && ($object->getRemainToPay() > 0)) { + $qrPosX = $this->marge_gauche + 5; $qrPosY = $posy; $qrCodeColor = array('25', '25', '25'); $styleQr = array( @@ -1673,9 +1686,9 @@ class pdf_sponge extends ModelePDFFactures ); $EPCQrCodeString = $object->buildEPCQrCodeString(); - $pdf->write2DBarcode($EPCQrCodeString, 'QRCODE,M', $qrPosX, $qrPosY, 25, 25, $styleQr, 'N'); + $pdf->write2DBarcode($EPCQrCodeString, 'QRCODE,M', $qrPosX, $qrPosY, 20, 20, $styleQr, 'N'); - $pdf->SetXY($qrPosX + 30, $posy + 5); + $pdf->SetXY($qrPosX + 25, $qrPosY + 5); $pdf->SetFont('', '', $default_font_size - 5); $pdf->MultiCell(30, 3, $outputlangs->transnoentitiesnoconv("INVOICE_ADD_EPC_QR_CODEPay"), 0, 'L', false); $posy = $pdf->GetY() + 2; @@ -2843,11 +2856,11 @@ class pdf_sponge extends ModelePDFFactures /** * Define Array Column Field * - * @param Facture $object common object - * @param Translate $outputlangs langs - * @param int $hidedetails Do not show line details - * @param int $hidedesc Do not show desc - * @param int $hideref Do not show ref + * @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) diff --git a/htdocs/core/modules/modBarcode.class.php b/htdocs/core/modules/modBarcode.class.php index 537146ff9ac..e3a6995aeae 100644 --- a/htdocs/core/modules/modBarcode.class.php +++ b/htdocs/core/modules/modBarcode.class.php @@ -128,7 +128,7 @@ class modBarcode extends DolibarrModules 'url'=>'/barcode/codeinit.php?mainmenu=home&leftmenu=admintools', 'langs'=>'products', // Lang file to use (without .lang) by module. File must be in langs/code_CODE/ directory. 'position'=>300, - 'enabled'=>'isModEnabled("barcode") && preg_match(\'/^(admintools|all)/\',$leftmenu)', // Define condition to show or hide menu entry. Use '$conf->mymodule->enabled' if entry must be visible if module is enabled. Use '$leftmenu==\'system\'' to show if leftmenu system is selected. + 'enabled'=>'isModEnabled("barcode") && preg_match(\'/^(admintools|all)/\', $leftmenu)', // Define condition to show or hide menu entry. Use '$conf->mymodule->enabled' if entry must be visible if module is enabled. Use '$leftmenu==\'system\'' to show if leftmenu system is selected. 'perms'=>'$user->admin', 'target'=>'', 'user'=>0, // 0=Menu for internal users, 1=external users, 2=both diff --git a/htdocs/core/modules/modBookCal.class.php b/htdocs/core/modules/modBookCal.class.php index 99d9f267961..3e9bdd1abc1 100644 --- a/htdocs/core/modules/modBookCal.class.php +++ b/htdocs/core/modules/modBookCal.class.php @@ -323,7 +323,7 @@ class modBookCal extends DolibarrModules 'langs' => 'bookcal', 'position' => 1100 + $r, 'enabled' => '1', - 'perms' => '$user->rights->bookcal->calendar->read', + 'perms' => '$user->hasRight("bookcal", "calendar", "read")', 'user' => 0 ); @@ -340,9 +340,9 @@ class modBookCal extends DolibarrModules 'langs' => 'bookcal', 'position' => 1100 + $r, // Define condition to show or hide menu entry. Use '$conf->bookcal->enabled' if entry must be visible if module is enabled. Use '$leftmenu==\'system\'' to show if leftmenu system is selected. - 'enabled' => '$conf->bookcal->enabled', + 'enabled' => 'isModEnabled("bookcal")', // Use 'perms'=>'$user->rights->bookcal->level1->level2' if you want your menu with a permission rules - 'perms' => '$user->rights->bookcal->calendar->read', + 'perms' => '$user->hasRight("bookcal", "calendar", "read")', 'target' => '', // 0=Menu for internal users, 1=external users, 2=both 'user' => 2, @@ -360,9 +360,9 @@ class modBookCal extends DolibarrModules 'langs' => 'bookcal', 'position' => 1100 + $r, // Define condition to show or hide menu entry. Use '$conf->bookcal->enabled' if entry must be visible if module is enabled. Use '$leftmenu==\'system\'' to show if leftmenu system is selected. - 'enabled' => '$conf->bookcal->enabled', + 'enabled' => 'isModEnabled("bookcal")', // Use 'perms'=>'$user->rights->bookcal->level1->level2' if you want your menu with a permission rules - 'perms' => '$user->rights->bookcal->calendar->read', + 'perms' => '$user->hasRight("bookcal", "calendar", "read")', 'target' => '', // 0=Menu for internal users, 1=external users, 2=both 'user' => 2 @@ -384,9 +384,9 @@ class modBookCal extends DolibarrModules 'langs' => 'bookcal', 'position' => 1200 + $r, // Define condition to show or hide menu entry. Use '$conf->bookcal->enabled' if entry must be visible if module is enabled. Use '$leftmenu==\'system\'' to show if leftmenu system is selected. - 'enabled' => '$conf->bookcal->enabled', + 'enabled' => 'isModEnabled("bookcal")', // Use 'perms'=>'$user->rights->bookcal->level1->level2' if you want your menu with a permission rules - 'perms' => '$user->rights->bookcal->availabilities->read', + 'perms' => '$user->hasRight("bookcal", "availabilities", "read")', 'target' => '', // 0=Menu for internal users, 1=external users, 2=both 'user' => 2, @@ -404,9 +404,9 @@ class modBookCal extends DolibarrModules 'langs' => 'bookcal', 'position' => 1200 + $r, // Define condition to show or hide menu entry. Use '$conf->bookcal->enabled' if entry must be visible if module is enabled. Use '$leftmenu==\'system\'' to show if leftmenu system is selected. - 'enabled' => '$conf->bookcal->enabled', + 'enabled' => 'isModEnabled("bookcal")', // Use 'perms'=>'$user->rights->bookcal->level1->level2' if you want your menu with a permission rules - 'perms' => '$user->rights->bookcal->availabilities->read', + 'perms' => '$user->hasRight("bookcal", "availabilities", "read")', 'target' => '', // 0=Menu for internal users, 1=external users, 2=both 'user' => 2 diff --git a/htdocs/core/modules/modMultiCurrency.class.php b/htdocs/core/modules/modMultiCurrency.class.php index 607cb67468e..d70ea600eb9 100644 --- a/htdocs/core/modules/modMultiCurrency.class.php +++ b/htdocs/core/modules/modMultiCurrency.class.php @@ -291,15 +291,15 @@ class modMultiCurrency extends DolibarrModules */ private function createFirstCurrency() { - global $conf, $user, $langs; + global $user, $langs; $multicurrency = new MultiCurrency($this->db); - if (! $multicurrency->checkCodeAlreadyExists($conf->currency)) { + if (! $multicurrency->checkCodeAlreadyExists(getDolCurrency())) { $langs->loadCacheCurrencies(''); - $multicurrency->code = $conf->currency; - $multicurrency->name = $langs->cache_currencies[$conf->currency]['label'].' ('.$langs->getCurrencySymbol($conf->currency).')'; + $multicurrency->code = getDolCurrency(); + $multicurrency->name = $langs->cache_currencies[getDolCurrency()]['label'].' ('.$langs->getCurrencySymbol(getDolCurrency()).')'; $r = $multicurrency->create($user); if ($r > 0) { diff --git a/htdocs/core/modules/modPartnership.class.php b/htdocs/core/modules/modPartnership.class.php index e6af2370a62..658720029bc 100644 --- a/htdocs/core/modules/modPartnership.class.php +++ b/htdocs/core/modules/modPartnership.class.php @@ -339,8 +339,8 @@ class modPartnership extends DolibarrModules 'url' => '/partnership/partnership_list.php', 'langs' => 'partnership', // Lang file to use (without .lang) by module. File must be in langs/code_CODE/ directory. 'position' => 1100 + $r, - 'enabled' => '$conf->partnership->enabled', // Define condition to show or hide menu entry. Use '$conf->partnership->enabled' if entry must be visible if module is enabled. - 'perms' => '$user->rights->partnership->read', // Use 'perms'=>'$user->rights->partnership->level1->level2' if you want your menu with a permission rules + 'enabled' => 'isModEnabled("partnership")', // Define condition to show or hide menu entry. Use '$conf->partnership->enabled' if entry must be visible if module is enabled. + 'perms' => '$user->hasRight("partnership", "read")', // Use 'perms'=>'$user->rights->partnership->level1->level2' if you want your menu with a permission rules 'target' => '', 'user' => 2, // 0=Menu for internal users, 1=external users, 2=both ); @@ -353,8 +353,8 @@ class modPartnership extends DolibarrModules 'url' => '/partnership/partnership_card.php?action=create', 'langs' => 'partnership', // Lang file to use (without .lang) by module. File must be in langs/code_CODE/ directory. 'position' => 1100 + $r, - 'enabled' => '$conf->partnership->enabled', // Define condition to show or hide menu entry. Use '$conf->partnership->enabled' if entry must be visible if module is enabled. Use '$leftmenu==\'system\'' to show if leftmenu system is selected. - 'perms' => '$user->rights->partnership->write', // Use 'perms'=>'$user->rights->partnership->level1->level2' if you want your menu with a permission rules + 'enabled' => 'isModEnabled("partnership")', // Define condition to show or hide menu entry. Use '$conf->partnership->enabled' if entry must be visible if module is enabled. Use '$leftmenu==\'system\'' to show if leftmenu system is selected. + 'perms' => '$user->hasRight("partnership", "write")', // Use 'perms'=>'$user->rights->partnership->level1->level2' if you want your menu with a permission rules 'target' => '', 'user' => 2, // 0=Menu for internal users, 1=external users, 2=both ); @@ -367,8 +367,8 @@ class modPartnership extends DolibarrModules 'url' => '/partnership/partnership_list.php', 'langs' => 'partnership', // Lang file to use (without .lang) by module. File must be in langs/code_CODE/ directory. 'position' => 1100 + $r, - 'enabled' => '$conf->partnership->enabled', // Define condition to show or hide menu entry. Use '$conf->partnership->enabled' if entry must be visible if module is enabled. Use '$leftmenu==\'system\'' to show if leftmenu system is selected. - 'perms' => '$user->rights->partnership->read', // Use 'perms'=>'$user->rights->partnership->level1->level2' if you want your menu with a permission rules + 'enabled' => 'isModEnabled("partnership")', // Define condition to show or hide menu entry. Use '$conf->partnership->enabled' if entry must be visible if module is enabled. Use '$leftmenu==\'system\'' to show if leftmenu system is selected. + 'perms' => '$user->hasRight("partnership", "write")', // Use 'perms'=>'$user->rights->partnership->level1->level2' if you want your menu with a permission rules 'target' => '', 'user' => 2, // 0=Menu for internal users, 1=external users, 2=both ); diff --git a/htdocs/core/modules/modStock.class.php b/htdocs/core/modules/modStock.class.php index 34365f2d71f..1b9f280a125 100644 --- a/htdocs/core/modules/modStock.class.php +++ b/htdocs/core/modules/modStock.class.php @@ -474,7 +474,7 @@ class modStock extends DolibarrModules $this->import_label[$r] = "Warehouses"; // Translation key $this->import_icon[$r] = "warehouse"; $this->import_entities_array[$r] = array(); // We define here only fields that use another icon that the one defined into import_icon - $this->import_tables_array[$r] = array('e' => MAIN_DB_PREFIX.'entrepot'); + $this->import_tables_array[$r] = array('e' => MAIN_DB_PREFIX.'entrepot', 'extra' => MAIN_DB_PREFIX.'entrepot_extrafields'); $this->import_tables_creator_array[$r] = array('e' => 'fk_user_author'); $this->import_fields_array[$r] = array('e.ref' => "LocationSummary*", 'e.description' => "DescWareHouse", @@ -495,7 +495,22 @@ class modStock extends DolibarrModules 'e.fk_parent' => array('rule' => 'fetchidfromref', 'classfile' => '/product/stock/class/entrepot.class.php', 'class' => 'Entrepot', 'method' => 'fetch', 'element' => 'ref') ); $this->import_regex_array[$r] = array('e.statut' => '^[0|1]'); - $this->import_examplevalues_array[$r] = array('e.ref' => "ALM001", + // Add extra fields + $import_extrafield_sample = array(); + $sql = "SELECT name, label, fieldrequired FROM ".MAIN_DB_PREFIX."extrafields WHERE type <> 'separate' AND elementtype = 'entrepot' AND entity IN (0, ".$conf->entity.")"; + $resql = $this->db->query($sql); + if ($resql) { // This can fail when class is used on old database (during migration for example) + while ($obj = $this->db->fetch_object($resql)) { + $fieldname = 'extra.'.$obj->name; + $fieldlabel = ucfirst($obj->label); + $this->import_fields_array[$r][$fieldname] = $fieldlabel.($obj->fieldrequired ? '*' : ''); + $import_extrafield_sample[$fieldname] = $fieldlabel; + } + } + // End add extra fields + $this->import_fieldshidden_array[$r] = array('extra.fk_object' => 'lastrowid-'.MAIN_DB_PREFIX.'entrepot'); // aliastable.field => ('user->id' or 'lastrowid-'.tableparent) + + $this->import_examplevalues_array[$r] = array_merge(array('e.ref' => "ALM001", 'e.description' => "Central Warehouse", 'e.lieu' => "Central", 'e.address' => "Route 66", @@ -506,7 +521,7 @@ class modStock extends DolibarrModules 'e.fax' => '(+33)(0)123456790', 'e.statut' => '1', 'e.fk_parent' => 'id or ref of warehouse' - ); + ), $import_extrafield_sample); $this->import_updatekeys_array[$r] = array('p.ref' => 'Ref'); // Import stocks diff --git a/htdocs/core/modules/mrp/doc/pdf_vinci.modules.php b/htdocs/core/modules/mrp/doc/pdf_vinci.modules.php index ea4cc56e23c..7b95c0dbac8 100644 --- a/htdocs/core/modules/mrp/doc/pdf_vinci.modules.php +++ b/htdocs/core/modules/mrp/doc/pdf_vinci.modules.php @@ -1301,11 +1301,11 @@ class pdf_vinci extends ModelePDFMo /** * Define Array Column Field * - * @param Mo $object common object + * @param CommonObject $object common object * @param Translate $outputlangs langs - * @param int $hidedetails Do not show line details - * @param int $hidedesc Do not show desc - * @param int $hideref Do not show ref + * @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) diff --git a/htdocs/core/modules/propale/doc/pdf_cyan.modules.php b/htdocs/core/modules/propale/doc/pdf_cyan.modules.php index 3b27aa87ea6..07bac951771 100644 --- a/htdocs/core/modules/propale/doc/pdf_cyan.modules.php +++ b/htdocs/core/modules/propale/doc/pdf_cyan.modules.php @@ -2066,11 +2066,11 @@ class pdf_cyan extends ModelePDFPropales /** * Define Array Column Field * - * @param Propal $object object proposal + * @param CommonObject $object object proposal * @param Translate $outputlangs langs - * @param int $hidedetails Do not show line details - * @param int $hidedesc Do not show desc - * @param int $hideref Do not show ref + * @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) diff --git a/htdocs/core/modules/security/captcha/modCaptchaStandard.class.php b/htdocs/core/modules/security/captcha/modCaptchaStandard.class.php index bf5c6250141..fad0bc2648e 100644 --- a/htdocs/core/modules/security/captcha/modCaptchaStandard.class.php +++ b/htdocs/core/modules/security/captcha/modCaptchaStandard.class.php @@ -123,6 +123,8 @@ class modCaptchaStandard extends ModeleCaptcha { global $langs; + $idofbutton ="actionlogin"; + // Output the image by calling /core/antispamimage.php // This antispamimage also record the value of code into $_SESSION['dol_antispam_value'] so we will be able to validate by calling // validateCodeAfterLoginSubmit() later when we submit the login form. @@ -154,8 +156,12 @@ class modCaptchaStandard extends ModeleCaptcha // Submit the form if found if (form) { - console.log("we set actionlogin to value \"disabled\""); - document.getElementById("actionlogin").value = "disabled"; + console.log(\'we set '.dol_escape_js($idofbutton).' to value "disabled" if found\'); /* TODO Why this ? #actionlogn seems to not exists */ + elementid = document.getElementById(\''.dol_escape_js($idofbutton).'\'); + console.log(elementid); + if (elementid) { + elementid.value = "disabled"; + } form.submit(); } diff --git a/htdocs/core/modules/stocktransfer/doc/pdf_eagle_proforma.modules.php b/htdocs/core/modules/stocktransfer/doc/pdf_eagle_proforma.modules.php index 139729e3d66..45ca1dcdf14 100644 --- a/htdocs/core/modules/stocktransfer/doc/pdf_eagle_proforma.modules.php +++ b/htdocs/core/modules/stocktransfer/doc/pdf_eagle_proforma.modules.php @@ -1511,9 +1511,9 @@ class pdf_eagle_proforma extends ModelePDFStockTransfer * * @param CommonObject $object common object * @param Translate $outputlangs langs - * @param int $hidedetails Do not show line details - * @param int $hidedesc Do not show desc - * @param int $hideref Do not show ref + * @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) diff --git a/htdocs/core/modules/supplier_order/doc/pdf_cornas.modules.php b/htdocs/core/modules/supplier_order/doc/pdf_cornas.modules.php index 3f2e6f12702..187d35df89f 100644 --- a/htdocs/core/modules/supplier_order/doc/pdf_cornas.modules.php +++ b/htdocs/core/modules/supplier_order/doc/pdf_cornas.modules.php @@ -1501,16 +1501,16 @@ class pdf_cornas extends ModelePDFSuppliersOrders /** * Define Array Column Field * - * @param CommandeFournisseur $object common object + * @param CommonObject $object common object * @param Translate $outputlangs langs - * @param int $hidedetails Do not show line details - * @param int $hidedesc Do not show desc - * @param int $hideref Do not show ref + * @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) { - global $conf, $hookmanager; + global $hookmanager; // Default field style for content $this->defaultContentsFieldsStyle = array( diff --git a/htdocs/core/modules/supplier_proposal/doc/pdf_zenith.modules.php b/htdocs/core/modules/supplier_proposal/doc/pdf_zenith.modules.php index 95bcc5d05d5..dc15e72c366 100644 --- a/htdocs/core/modules/supplier_proposal/doc/pdf_zenith.modules.php +++ b/htdocs/core/modules/supplier_proposal/doc/pdf_zenith.modules.php @@ -1467,16 +1467,16 @@ class pdf_zenith extends ModelePDFSupplierProposal /** * Define Array Column Field * - * @param SupplierProposal $object common object + * @param CommonObject $object common object * @param Translate $outputlangs langs - * @param int $hidedetails Do not show line details - * @param int $hidedesc Do not show desc - * @param int $hideref Do not show ref + * @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) { - global $conf, $hookmanager; + global $hookmanager; // Default field style for content $this->defaultContentsFieldsStyle = array( diff --git a/htdocs/core/tpl/apitoken_list.tpl.php b/htdocs/core/tpl/apitoken_list.tpl.php new file mode 100644 index 00000000000..63ba78a2fc0 --- /dev/null +++ b/htdocs/core/tpl/apitoken_list.tpl.php @@ -0,0 +1,229 @@ + + * Copyright (C) 2024 MDW + * 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 + * 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 . + * or see https://www.gnu.org/ + */ + +/** + * @var CommonObject $object + * @var DoliDB $db + * @var Form $form + * @var Translate $langs + * @var string $search_user + * @var string $search_entity + * @var string $search_datec_start + * @var string $search_datec_end + * @var string $search_tms_start + * @var string $search_tms_end + * @var string $param + * @var string $sortfield + * @var string $sortorder + * @var int $limit + * @var mysqli_result $resql + * @var string $massactionbutton + * @var string $massaction + * @var array $arrayofselected + * @var int $colspan + * + * @var int $num + */ + +' +@phan-var-force Propal|Contrat|Commande|Facture|Expedition|Delivery|FactureFournisseur|FactureFournisseur|SupplierProposal $object +@phan-var-force array $arrayfields +@phan-var-force int $num +@phan-var-force string $search_user +@phan-var-force string $search_entity +@phan-var-force string $search_datec_start +@phan-var-force string $search_datec_end +@phan-var-force string $search_tms_start +@phan-var-force string $search_tms_end +@phan-var-force int $colspan +'; + +echo "\n"; + +print ''; + +print ''; + +// Action buttons +if (getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) { + print ''; +} + +// Token string +// We don't search out tokens because it is encrypted in database +print ''; + +// User +if (!empty($arrayfields['u.login']['checked'])) { + print ''; +} + +// Number of perms +// We don't search out number of perms because it is a string field, +// and we don't want to count into it with sql query +print ''; + +// Date creation +if (!empty($arrayfields['oat.datec']['checked'])) { + print ''; +} + +// Date modification +if (!empty($arrayfields['oat.tms']['checked'])) { + print ''; +} + +// Action buttons +if (!getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) { + print ''; +} + +print ""; + +print ''; +if (getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) { + print ''; +} +print ''; +if (!empty($arrayfields['u.login']['checked'])) { + // @phan-suppress-next-line PhanTypeInvalidDimOffset + print_liste_field_titre($arrayfields['u.login']['label'], $_SERVER["PHP_SELF"], 'u.login', '', $param, '', $sortfield, $sortorder); +} +print ''; +if (!empty($arrayfields['oat.datec']['checked'])) { + print_liste_field_titre($arrayfields['oat.datec']['label'], $_SERVER["PHP_SELF"], 'oat.datec', '', $param, '', $sortfield, $sortorder, 'center '); +} +if (!empty($arrayfields['oat.tms']['checked'])) { + print_liste_field_titre($arrayfields['oat.tms']['label'], $_SERVER["PHP_SELF"], 'oat.tms', '', $param, '', $sortfield, $sortorder, 'center '); +} +if (!getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) { + print ''; +} +print ''; + +// List of tokens of user +$i = 0; +$imaxinloop = ($limit ? min($num, $limit) : $num); +if ($num > 0) { + while ($i < $imaxinloop) { + // Compute number of perms + $obj = $db->fetch_object($resql); + + $useridparam = isset($obj->fk_user) ? $obj->fk_user : $object->id; + + if (isset($obj->fk_user)) { + $currentuser = new User($db); + $currentuser->fetch($obj->fk_user); + } else { + $currentuser = $object; + } + + /* + $numperms = 0; + if (!empty($obj->rights)) { + $numperms = count(explode(",", $obj->rights)); + } elseif (!(strlen($obj->rights) == 1 && substr($obj->rights, 0, 1) == 0)) { + $currentuser->loadRights(); + $numperms = $currentuser->nb_rights; + } + */ + + print ''; + // Action column + if (getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) { + print ''; + } + print ''; + if (!empty($arrayfields['u.login']['checked'])) { + print ''; + } + print ''; + print ''; + print ''; + if (!getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) { + print ''; + } + print ''; + $i++; + } +} else { + if (isModEnabled('multicompany')) { + $colspan++; + } + print ''; +} + +print "
    '; + $searchpicto = $form->showFilterButtons('left'); + print $searchpicto; + print ''; + print ''; + print ''; + print '
    '; + print $form->selectDate($search_datec_start ? $search_datec_start : -1, 'search_datec_start', 0, 0, 1, '', 1, 0, 0, '', '', '', '', 1, '', $langs->trans('From')); + print '
    '; + print '
    '; + print $form->selectDate($search_datec_end ? $search_datec_end : -1, 'search_datec_end', 0, 0, 1, '', 1, 0, 0, '', '', '', '', 1, '', $langs->trans('to')); + print '
    '; + print '
    '; + print '
    '; + print $form->selectDate($search_tms_start ? $search_tms_start : -1, 'search_tms_start', 0, 0, 1, '', 1, 0, 0, '', '', '', '', 1, '', $langs->trans('From')); + print '
    '; + print '
    '; + print $form->selectDate($search_tms_end ? $search_tms_end : -1, 'search_tms_end', 0, 0, 1, '', 1, 0, 0, '', '', '', '', 1, '', $langs->trans('to')); + print '
    '; + print '
    '; + $searchpicto = $form->showFilterButtons('left'); + print $searchpicto; + print '
    '; + print $form->showCheckAddButtons('checkforselect', 1); + print ''.$langs->trans("Token").''.$langs->trans("LastAccess").''; + print $form->showCheckAddButtons('checkforselect', 1); + print '
    '; + if ($massactionbutton || $massaction) { // If we are in select mode (massactionbutton defined) or if we have already selected and sent an action ($massaction) defined + $selected = 0; + if (in_array($obj->rowid, $arrayofselected)) { + $selected = 1; + } + print ''; + } + print ''; + print ''; + print dolDecrypt($obj->tokenstring); + print ''; + print ''; + print ''; + print $currentuser->getNomUrl(1); + print ''; + print ''; + print dol_print_date($db->jdate($obj->lastaccess)); + print ''; + print dol_print_date($db->jdate($obj->date_creation), 'dayhour'); + print ''; + print dol_print_date($db->jdate($obj->date_modification), 'dayhour'); + print ''; + if ($massactionbutton || $massaction) { + $selected = 0; + if (in_array($obj->rowid, $arrayofselected)) { + $selected = 1; + } + print ''; + } + print '
    '.$langs->trans("None").'
    "; diff --git a/htdocs/core/tpl/document_actions_post_headers.tpl.php b/htdocs/core/tpl/document_actions_post_headers.tpl.php index deb67b5acff..0f1b77c39b1 100644 --- a/htdocs/core/tpl/document_actions_post_headers.tpl.php +++ b/htdocs/core/tpl/document_actions_post_headers.tpl.php @@ -24,13 +24,8 @@ */ // Following var can be set -// $modulepart = for download -// $param = param to add to download links -// $moreparam = param to add to download link for the form_attach_new_file function -// $upload_dir // $object // $filearray -// $savingdocmask = dol_sanitizeFileName($object->ref).'-__file__'; /** * @var Conf $conf @@ -40,9 +35,16 @@ * @var HookManager $hookmanager * @var Translate $langs * - * @var string $action - * @var string $relativepathwithnofile - * @var int $permisstiontoadd Permission or not to add a file (can use also $permission) and permission or not to edit file name or crop file (can use also $permtoedit) + * @var FormFile $formfile + * @var string $action + * @var string $modulepart + * @var string $upload_dir + * @var string $param + * @var string $moreparam = param to add to download link for the form_attach_new_file function + * @var string $relativepathwithnofile + * @var int $permisstiontoadd Permission or not to add a file (can use also $permission) and permission or not to edit file name or crop file (can use also $permtoedit) + * @var string $savingdocmask For example dol_sanitizeFileName($object->ref).'-__file__'; + * @var int $withproject */ // Protection to avoid direct call of template @@ -146,6 +148,7 @@ if (!isset($savingdocmask) || getDolGlobalString('MAIN_DISABLE_SUGGEST_REF_AS_PR } if (empty($formfile) || !is_object($formfile)) { + include_once DOL_DOCUMENT_ROOT.'/core/class/html.formfile.class.php'; $formfile = new FormFile($db); } diff --git a/htdocs/core/tpl/list_print_total.tpl.php b/htdocs/core/tpl/list_print_total.tpl.php index d20416864b0..9310bcfce8c 100644 --- a/htdocs/core/tpl/list_print_total.tpl.php +++ b/htdocs/core/tpl/list_print_total.tpl.php @@ -23,12 +23,15 @@ * @var Form $form * @var Translate $langs * - * @var int $trforbreaknobg + * @var int $trforbreaknobg + * @var ?int $limit + * @var ?int $offset * @var array{nbfield:int,type?:array,pos?:array,val?:array} $totalarray */ ' @phan-var-force array{nbfield:int,type?:array,pos?:array,val?:array} $totalarray @phan-var-force ?string $sqlfields +@phan-var-force ?int $limit '; if (!function_exists('printTotalValCell')) { // allow two list with total on same screen @@ -89,7 +92,7 @@ if (isset($totalarray['pos'])) { printTotalValCell($totalarray['type'][$i] ?? '', empty($totalarray['val'][$totalarray['pos'][$i]]) ? '0' : (string) $totalarray['val'][$totalarray['pos'][$i]]); } else { if ($i == 1) { - if ((is_null($limit) || $num < $limit) && empty($offset)) { + if ((!isset($limit) || $num < $limit) && empty($offset)) { print ''.$langs->trans("Total").''; } else { print ''; diff --git a/htdocs/core/tpl/login.tpl.php b/htdocs/core/tpl/login.tpl.php index 9f925ef514b..f1648667642 100644 --- a/htdocs/core/tpl/login.tpl.php +++ b/htdocs/core/tpl/login.tpl.php @@ -301,7 +301,7 @@ if ($disablenofollow) { } ?> -" name="username" class="flat input-icon-user minwidth150" value="" tabindex="1" autofocus="autofocus" autocapitalize="off" autocomplete="on" spellcheck="false" autocorrect="off" /> +" name="username" class="flat input-icon-user minwidth150 input-nobottom" value="" tabindex="1" autofocus="autofocus" autocapitalize="off" autocomplete="on" spellcheck="false" autocorrect="off" /> @@ -313,7 +313,7 @@ if ($disablenofollow) { } ?> -" name="password" class="flat input-icon-password minwidth150" value="" tabindex="2" autocomplete="" /> +" name="password" class="flat input-icon-password minwidth150 input-nobottom" value="" tabindex="2" autocomplete="" /> element == 'propal') { '."\n"; print ''."\n"; print ''."\n"; - print ''."\n"; } - // JS wrapper to add log when clicking on download or preview + // JS wrapper to add an unalterable log when clicking on Download or Preview + // This is done on customer invoices only. + // This add a log and increase the pos_print_counter too (done by block-add.php). if (isModEnabled('blockedlog') && is_object($object) && !empty($object->id) && $object->id > 0) { if (in_array($object->element, array('facture')) && $object->statut > 0) { // Restrict for the moment to element 'facture' print "\n\n"; @@ -3689,9 +3693,9 @@ if (!function_exists("llxFooter")) { console.log("Call /blockedlog/ajax/block-add on a.documentpreview"); $.post('' , { - id:id; ?> - , element:'element) ?>' - , action:'DOC_PREVIEW' + id: id; ?> + , element: 'element) ?>' + , action: 'DOC_PREVIEW' , token: '' } ); @@ -3700,9 +3704,9 @@ if (!function_exists("llxFooter")) { console.log("Call /blockedlog/ajax/block-add a.documentdownload"); $.post('' , { - id:id; ?> - , element:'element) ?>' - , action:'DOC_DOWNLOAD' + id: id; ?> + , element: 'element) ?>' + , action: 'DOC_DOWNLOAD' , token: '' } ); diff --git a/htdocs/margin/customerMargins.php b/htdocs/margin/customerMargins.php index c24407c16db..10cfcb27e4e 100644 --- a/htdocs/margin/customerMargins.php +++ b/htdocs/margin/customerMargins.php @@ -198,7 +198,7 @@ print ''; // Total Margin print ''; // Margin Rate diff --git a/htdocs/margin/productMargins.php b/htdocs/margin/productMargins.php index 77cce2d27b8..38aab2d3049 100644 --- a/htdocs/margin/productMargins.php +++ b/htdocs/margin/productMargins.php @@ -162,7 +162,7 @@ print '
    '.$langs->trans("TotalMargin").''; -print ' '.$langs->getCurrencySymbol($conf->currency).''; // set by jquery (see below) +print ' '.$langs->getCurrencySymbol(getDolCurrency()).''; // set by jquery (see below) print '
    '; // Total Margin print ''; // Margin Rate diff --git a/htdocs/margin/tabs/productMargins.php b/htdocs/margin/tabs/productMargins.php index 280ccc458ca..ec89b565df3 100644 --- a/htdocs/margin/tabs/productMargins.php +++ b/htdocs/margin/tabs/productMargins.php @@ -499,7 +499,7 @@ if ($id > 0 || !empty($ref)) { print ' '; -?> + +// Wrapper to manage document_preview and modal_card +if (empty($conf->browser->layout) || $conf->browser->layout != 'phone') { ?> + + + +
    +
    + diff --git a/htdocs/public/webportal/tpl/header.tpl.php b/htdocs/public/webportal/tpl/header.tpl.php index e1788702c22..07b05a30609 100644 --- a/htdocs/public/webportal/tpl/header.tpl.php +++ b/htdocs/public/webportal/tpl/header.tpl.php @@ -78,6 +78,10 @@ top_httphead(); $jNotifyJSUrl = dirname($context->rootUrl).'/includes/jquery/plugins/jnotify/jquery.jnotify.min.js'; print ''."\n"; + // Modal script + $ModalJSUrl = $context->rootUrl.'js/modal.js'; + print ''."\n"; + // Common dolibarr js functions $jQueryUIJSUrl = $context->rootUrl.'js/lib_head.js.php'; print ''."\n"; diff --git a/htdocs/public/webportal/webportal.main.inc.php b/htdocs/public/webportal/webportal.main.inc.php index 6dc0594d0fb..4e219eedf40 100644 --- a/htdocs/public/webportal/webportal.main.inc.php +++ b/htdocs/public/webportal/webportal.main.inc.php @@ -107,6 +107,7 @@ require_once DOL_DOCUMENT_ROOT . '/webportal/class/webportalpartnership.class.ph //} $context = Context::getInstance(); +$context->initController(); $hookmanager->initHooks(array('main', 'webportal')); @@ -154,6 +155,7 @@ if (getDolGlobalInt('WEBPORTAL_LOGIN_BY_MODULE') && !empty($conf->modules_parts[ } elseif (empty($reshook)) { $admin_error_messages = array(); $webportal_logged_thirdparty_account_id = isset($_SESSION["webportal_logged_thirdparty_account_id"]) && $_SESSION["webportal_logged_thirdparty_account_id"] > 0 ? $_SESSION["webportal_logged_thirdparty_account_id"] : 0; + $webportal_logged_member_account_id = isset($_SESSION["webportal_logged_member_account_id"]) && $_SESSION["webportal_logged_member_account_id"] > 0 ? $_SESSION["webportal_logged_member_account_id"] : 0; if (!$context->userIsLog()) { // It is not already authenticated and it requests the login / password @@ -187,23 +189,41 @@ if (getDolGlobalInt('WEBPORTAL_LOGIN_BY_MODULE') && !empty($conf->modules_parts[ // $error++; //} - if (!$error) { + if (!$error && (isModEnabled('societe') && !getDolGlobalInt('WEBPORTAL_LOGIN_BY_MEMBER_ACCOUNT'))) { // fetch third-party account from login and account type $thirdparty_account_id = $context->getThirdPartyAccountFromLogin($login, $password); if ($thirdparty_account_id <= 0) { $error++; dol_syslog($langs->transnoentitiesnoconv('WebPortalErrorFetchThirdPartyAccountFromLogin', $login), LOG_WARNING); - $context->setEventMessage($langs->transnoentitiesnoconv('WebPortalErrorAuthentication'), 'errors'); } else { $_SESSION["webportal_logged_thirdparty_account_id"] = $thirdparty_account_id; $webportal_logged_thirdparty_account_id = $thirdparty_account_id; $context->controller = 'default'; $context->initController(); } + } elseif (!$error && isModEnabled('member') && getDolGlobalInt('WEBPORTAL_LOGIN_BY_MEMBER_ACCOUNT') && !getDolGlobalString('ADHERENT_LOGIN_NOT_REQUIRED')) { + // fetch member account from login + $member_account_id = $context->getMemberAccountFromLogin($login, $password); + if ($member_account_id <= 0) { + $error++; + dol_syslog($langs->transnoentitiesnoconv('WebPortalErrorFetchMemberAccountFromLogin', $login), LOG_WARNING); + } else { + $_SESSION["webportal_logged_member_account_id"] = $member_account_id; + $webportal_logged_member_account_id = $member_account_id; + $context->controller = 'default'; + $context->initController(); + } + } else { + $error++; + dol_syslog($langs->transnoentitiesnoconv('WebPortalErrorNoFetchMethod'), LOG_WARNING); + } + + if ($error) { + $context->setEventMessage($langs->transnoentitiesnoconv('WebPortalErrorAuthentication'), 'errors'); } } - if (empty($webportal_logged_thirdparty_account_id)) { + if (empty($webportal_logged_thirdparty_account_id) && empty($webportal_logged_member_account_id)) { // Set cookie for timeout management if (getDolGlobalString('MAIN_SESSION_TIMEOUT')) { dolSetCookie($sessiontimeout, getDolGlobalString('MAIN_SESSION_TIMEOUT'), 0); @@ -215,21 +235,43 @@ if (getDolGlobalInt('WEBPORTAL_LOGIN_BY_MODULE') && !empty($conf->modules_parts[ } if (!$error && $context->userIsLog()) { + $logged_member = null; + $websiteaccount = null; // We are already into an authenticated session - $websiteaccount = new SocieteAccount($db); - $result = $websiteaccount->fetch($webportal_logged_thirdparty_account_id); + if (isModEnabled('member') && getDolGlobalInt('WEBPORTAL_LOGIN_BY_MEMBER_ACCOUNT') && !getDolGlobalString('ADHERENT_LOGIN_NOT_REQUIRED')) { + // get member + $logged_member = new WebPortalMember($db); + $result = $logged_member->fetch($webportal_logged_member_account_id); + if ($result <= 0) { + $error++; - if ($result <= 0) { - $error++; + // Account has been removed after login + dol_syslog("Can't load member account (ID: $webportal_logged_member_account_id) even if session logged.", LOG_WARNING); + session_destroy(); + session_set_cookie_params(0, '/', null, !empty($dolibarr_main_force_https), true); // Add tag secure and httponly on session cookie + session_name($sessionname); + session_start(); - // Account has been removed after login - dol_syslog("Can't load third-party account (ID: $webportal_logged_thirdparty_account_id) even if session logged.", LOG_WARNING); - session_destroy(); - session_set_cookie_params(0, '/', null, !empty($dolibarr_main_force_https), true); // Add tag secure and httponly on session cookie - session_name($sessionname); - session_start(); + $error_msg = $langs->transnoentitiesnoconv('WebPortalErrorFetchLoggedMember', (string) $webportal_logged_member_account_id); + dol_syslog($error_msg, LOG_ERR); + $context->setEventMessage($error_msg, 'errors'); + } + } else { + $websiteaccount = new SocieteAccount($db); + $result = $websiteaccount->fetch($webportal_logged_thirdparty_account_id); - $context->setEventMessage($langs->transnoentitiesnoconv('WebPortalErrorFetchLoggedThirdPartyAccount', $webportal_logged_thirdparty_account_id), 'errors'); + if ($result <= 0) { + $error++; + + // Account has been removed after login + dol_syslog("Can't load third-party account (ID: $webportal_logged_thirdparty_account_id) even if session logged.", LOG_WARNING); + session_destroy(); + session_set_cookie_params(0, '/', null, !empty($dolibarr_main_force_https), true); // Add tag secure and httponly on session cookie + session_name($sessionname); + session_start(); + + $context->setEventMessage($langs->transnoentitiesnoconv('WebPortalErrorFetchLoggedThirdPartyAccount', $webportal_logged_thirdparty_account_id), 'errors'); + } } if (!$error) { @@ -252,69 +294,76 @@ if (getDolGlobalInt('WEBPORTAL_LOGIN_BY_MODULE') && !empty($conf->modules_parts[ } } - if (!$error) { - // get third-party - $logged_thirdparty = $websiteaccount->thirdparty; - if (!$logged_thirdparty || !($logged_thirdparty->id > 0)) { - $result = $websiteaccount->fetch_thirdparty(); - - if ($result < 0) { - $error_msg = $langs->transnoentitiesnoconv('WebPortalErrorFetchLoggedThirdParty', (string) $websiteaccount->fk_soc); - //dol_syslog("Can't load third-party (ID: ".$websiteaccount->fk_soc.") even if session logged.", LOG_ERR); - dol_syslog($error_msg, LOG_ERR); - $context->setEventMessage($error_msg, 'errors'); - $error++; - } - } - + if (!getDolGlobalInt('WEBPORTAL_LOGIN_BY_MEMBER_ACCOUNT')) { if (!$error) { + // get third-party $logged_thirdparty = $websiteaccount->thirdparty; + if (!$logged_thirdparty || !($logged_thirdparty->id > 0)) { + $result = $websiteaccount->fetch_thirdparty(); - // get member - $logged_member = new WebPortalMember($db); - $result = $logged_member->fetch(0, '', $websiteaccount->thirdparty->id); - if ($result < 0) { - $error++; - $error_msg = $langs->transnoentitiesnoconv('WebPortalErrorFetchLoggedMember', (string) $websiteaccount->thirdparty->id); - dol_syslog($error_msg, LOG_ERR); - $context->setEventMessage($error_msg, 'errors'); - } - - if (isModEnabled('partnership') && !$error && $logged_member->id > 0) { - // get partnership - $logged_partnership = new WebPortalPartnership($db); - // @phan-suppress-next-line PhanPluginSuspiciousParamPosition - $result = $logged_partnership->fetch(0, '', $logged_member->id, $websiteaccount->thirdparty->id); if ($result < 0) { - $error++; - $error_msg = $langs->transnoentitiesnoconv('WebPortalErrorFetchLoggedPartnership', (string) $websiteaccount->thirdparty->id, (string) $logged_member->id); + $error_msg = $langs->transnoentitiesnoconv('WebPortalErrorFetchLoggedThirdParty', (string) $websiteaccount->fk_soc); + //dol_syslog("Can't load third-party (ID: ".$websiteaccount->fk_soc.") even if session logged.", LOG_ERR); dol_syslog($error_msg, LOG_ERR); $context->setEventMessage($error_msg, 'errors'); + $error++; } } if (!$error) { - if ($logged_thirdparty->default_lang != $langs->defaultlang && !defined('WEBPORTAL_NOREQUIRETRAN')) { - if (!is_object($langs)) { // This can occurs when calling page with NOREQUIRETRAN defined, however we need langs for error messages. - include_once DOL_DOCUMENT_ROOT . '/core/class/translate.class.php'; - $langs = new Translate("", $conf); - $langs->setDefaultLang($logged_thirdparty->default_lang); + $logged_thirdparty = $websiteaccount->thirdparty; + + // get member + $logged_member = new WebPortalMember($db); + $result = $logged_member->fetch(0, '', $websiteaccount->thirdparty->id); + if ($result < 0) { + $error++; + $error_msg = $langs->transnoentitiesnoconv('WebPortalErrorFetchLoggedMember', (string) $websiteaccount->thirdparty->id); + dol_syslog($error_msg, LOG_ERR); + $context->setEventMessage($error_msg, 'errors'); + } + + if (isModEnabled('partnership') && !$error && $logged_member->id > 0) { + // get partnership + $logged_partnership = new WebPortalPartnership($db); + // @phan-suppress-next-line PhanPluginSuspiciousParamPosition + $result = $logged_partnership->fetch(0, '', $logged_member->id, $websiteaccount->thirdparty->id); + if ($result < 0) { + $error++; + $error_msg = $langs->transnoentitiesnoconv('WebPortalErrorFetchLoggedPartnership', (string) $websiteaccount->thirdparty->id, (string) $logged_member->id); + dol_syslog($error_msg, LOG_ERR); + $context->setEventMessage($error_msg, 'errors'); } - $langs->loadLangs(array('website', 'main')); } - $context->logged_user = $logged_user; - $context->logged_thirdparty = $logged_thirdparty; - $context->logged_member = $logged_member; - if (!empty($logged_partnership)) { - $context->logged_partnership = $logged_partnership; - } + if (!$error) { + if ($logged_thirdparty->default_lang != $langs->defaultlang && !defined('WEBPORTAL_NOREQUIRETRAN')) { + if (!is_object($langs)) { // This can occurs when calling page with NOREQUIRETRAN defined, however we need langs for error messages. + include_once DOL_DOCUMENT_ROOT . '/core/class/translate.class.php'; + $langs = new Translate("", $conf); + $langs->setDefaultLang($logged_thirdparty->default_lang); + } + $langs->loadLangs(array('website', 'main')); + } - global $user; // set global user as logged user (used for hooks in external modules) - $user = $context->logged_user; + $context->logged_thirdparty = $logged_thirdparty; + if (!empty($logged_partnership)) { + $context->logged_partnership = $logged_partnership; + } + } } } } + + if (!$error) { + $context->logged_user = $logged_user; + if (!empty($logged_member)) { + $context->logged_member = $logged_member; + } + + global $user; // set global user as logged user (used for hooks in external modules) + $user = $context->logged_user; + } } } } diff --git a/htdocs/reception/card.php b/htdocs/reception/card.php index 55cbb825ee2..e8d9a23780e 100644 --- a/htdocs/reception/card.php +++ b/htdocs/reception/card.php @@ -1917,7 +1917,6 @@ if ($action == 'create' && $permissiontoadd) { $typeobject = ''; if (!empty($object->origin) && $object->origin_id > 0) { - $object->origin = 'CommandeFournisseur'; $typeobject = $object->origin; $origin = $object->origin; $origin_id = $object->origin_id; @@ -1999,7 +1998,7 @@ if ($action == 'create' && $permissiontoadd) { $objectsrc = new Propal($db); $objectsrc->fetch($object->origin_object->id); } - if ($typeobject == 'CommandeFournisseur' && $object->origin_object->id && isModEnabled("supplier_order")) { + if (($typeobject == 'supplier_order' || $typeobject == 'CommandeFournisseur') && $object->origin_object->id && isModEnabled("supplier_order")) { $objectsrc = new CommandeFournisseur($db); $objectsrc->fetch($object->origin_object->id); } @@ -2086,7 +2085,7 @@ if ($action == 'create' && $permissiontoadd) { print "\n"; print ''; } - if ($typeobject == 'CommandeFournisseur' && $object->origin_object->id && isModEnabled("propal")) { + if (($typeobject == 'supplier_order' || $typeobject == 'CommandeFournisseur') && $object->origin_object->id && isModEnabled("propal")) { print ''; print '\n"; print ''; } - if ($typeobject == 'CommandeFournisseur' && $object->origin_object->id && isModEnabled("propal")) { + if (($typeobject == 'supplier_order' || $typeobject == 'order_supplier') && $object->origin_object->id && isModEnabled("propal")) { print ''; print ''; print ''; diff --git a/htdocs/recruitment/class/api_recruitments.class.php b/htdocs/recruitment/class/api_recruitments.class.php index 628522d84c7..1acf80676d7 100644 --- a/htdocs/recruitment/class/api_recruitments.class.php +++ b/htdocs/recruitment/class/api_recruitments.class.php @@ -1,6 +1,7 @@ * Copyright (C) 2024-2025 MDW + * Copyright (C) 2025 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 @@ -618,9 +619,12 @@ class Recruitments extends DolibarrApi // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore /** * Clean sensible object datas + * @phpstan-template T * * @param Object $object Object to clean * @return Object Object with cleaned properties + * @phpstan-param T $object + * @phpstan-return T */ protected function _cleanObjectDatas($object) { diff --git a/htdocs/recruitment/core/modules/recruitment/doc/pdf_standard_recruitmentjobposition.modules.php b/htdocs/recruitment/core/modules/recruitment/doc/pdf_standard_recruitmentjobposition.modules.php index f0beb83b971..c0b22d89e2a 100644 --- a/htdocs/recruitment/core/modules/recruitment/doc/pdf_standard_recruitmentjobposition.modules.php +++ b/htdocs/recruitment/core/modules/recruitment/doc/pdf_standard_recruitmentjobposition.modules.php @@ -681,15 +681,13 @@ class pdf_standard_recruitmentjobposition extends ModelePDFRecruitmentJobPositio */ protected function _tableau(&$pdf, $tab_top, $tab_height, $nexY, $outputlangs, $hidetop = 0, $hidebottom = 0, $currency = '', $outputlangsbis = null) { - global $conf; - // Force to disable hidetop and hidebottom $hidebottom = 0; if ($hidetop) { $hidetop = -1; } - $currency = !empty($currency) ? $currency : $conf->currency; + $currency = !empty($currency) ? $currency : getDolCurrency(); $default_font_size = pdf_getPDFFontSize($outputlangs); // Amount in (at tab_top - 1) @@ -978,7 +976,7 @@ class pdf_standard_recruitmentjobposition extends ModelePDFRecruitmentJobPositio /** * Define Array Column Field * - * @param object $object common object + * @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 @@ -987,7 +985,7 @@ class pdf_standard_recruitmentjobposition extends ModelePDFRecruitmentJobPositio */ public function defineColumnField($object, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0) { - global $conf, $hookmanager; + global $hookmanager; // Default field style for content $this->defaultContentsFieldsStyle = array( diff --git a/htdocs/recruitment/recruitmentcandidature_agenda.php b/htdocs/recruitment/recruitmentcandidature_agenda.php index f1ea34f37fa..8f1482bd953 100644 --- a/htdocs/recruitment/recruitmentcandidature_agenda.php +++ b/htdocs/recruitment/recruitmentcandidature_agenda.php @@ -47,7 +47,7 @@ $langs->loadLangs(array("recruitment", "other")); $id = GETPOSTINT('id'); $ref = GETPOST('ref', 'alpha'); $action = GETPOST('action', 'aZ09'); -$cancel = GETPOST('cancel', 'aZ09'); +$cancel = GETPOST('cancel'); $backtopage = GETPOST('backtopage', 'alpha'); $socid = GETPOSTINT('socid'); $contextpage = GETPOST('contextpage', 'aZ') ? GETPOST('contextpage', 'aZ') : str_replace('_', '', basename(dirname(__FILE__)).basename(__FILE__, '.php')); // To manage different context of search diff --git a/htdocs/recruitment/recruitmentcandidature_card.php b/htdocs/recruitment/recruitmentcandidature_card.php index 3b0a50771db..c6113539b99 100644 --- a/htdocs/recruitment/recruitmentcandidature_card.php +++ b/htdocs/recruitment/recruitmentcandidature_card.php @@ -49,7 +49,7 @@ $id = GETPOSTINT('id'); $ref = GETPOST('ref', 'alpha'); $action = GETPOST('action', 'aZ09'); $confirm = GETPOST('confirm', 'alpha'); -$cancel = GETPOST('cancel', 'aZ09'); +$cancel = GETPOST('cancel'); $contextpage = GETPOST('contextpage', 'aZ') ? GETPOST('contextpage', 'aZ') : 'recruitmentcandidaturecard'; // To manage different context of search $backtopage = GETPOST('backtopage', 'alpha'); $backtopageforcancel = GETPOST('backtopageforcancel', 'alpha'); @@ -555,7 +555,7 @@ if ($object->id > 0 && (empty($action) || ($action != 'edit' && $action != 'crea if (empty($reshook)) { // Send if (empty($user->socid)) { - print 'email).'#formmailbeforetitle">'.$langs->trans('SendMail').''."\n"; + print dolGetButtonAction('', $langs->trans('SendMail'), 'email', dolBuildUrl($_SERVER["PHP_SELF"], ['id' => $object->id, 'action' => 'presend', 'mode' => 'init'], true).'#formmailbeforetitle', ''); } // Back to draft diff --git a/htdocs/recruitment/recruitmentcandidature_note.php b/htdocs/recruitment/recruitmentcandidature_note.php index 937680bb8c5..27396b78628 100644 --- a/htdocs/recruitment/recruitmentcandidature_note.php +++ b/htdocs/recruitment/recruitmentcandidature_note.php @@ -44,7 +44,7 @@ $langs->loadLangs(array("recruitment", "companies")); $id = GETPOSTINT('id'); $ref = GETPOST('ref', 'alpha'); $action = GETPOST('action', 'aZ09'); -$cancel = GETPOST('cancel', 'aZ09'); +$cancel = GETPOST('cancel'); $backtopage = GETPOST('backtopage', 'alpha'); // Initialize a technical objects diff --git a/htdocs/recruitment/recruitmentjobposition_agenda.php b/htdocs/recruitment/recruitmentjobposition_agenda.php index 517b50e9dee..4243c78eee9 100644 --- a/htdocs/recruitment/recruitmentjobposition_agenda.php +++ b/htdocs/recruitment/recruitmentjobposition_agenda.php @@ -47,7 +47,7 @@ $langs->loadLangs(array("recruitment", "other")); $id = GETPOSTINT('id'); $ref = GETPOST('ref', 'alpha'); $action = GETPOST('action', 'aZ09'); -$cancel = GETPOST('cancel', 'aZ09'); +$cancel = GETPOST('cancel'); $backtopage = GETPOST('backtopage', 'alpha'); $contextpage = GETPOST('contextpage', 'aZ') ? GETPOST('contextpage', 'aZ') : str_replace('_', '', basename(dirname(__FILE__)).basename(__FILE__, '.php')); // To manage different context of search diff --git a/htdocs/recruitment/recruitmentjobposition_applications.php b/htdocs/recruitment/recruitmentjobposition_applications.php index 235c0d852a0..d27e0fb1c49 100644 --- a/htdocs/recruitment/recruitmentjobposition_applications.php +++ b/htdocs/recruitment/recruitmentjobposition_applications.php @@ -48,7 +48,7 @@ $id = GETPOSTINT('id'); $ref = GETPOST('ref', 'alpha'); $action = GETPOST('action', 'aZ09'); $confirm = GETPOST('confirm', 'alpha'); -$cancel = GETPOST('cancel', 'aZ09'); +$cancel = GETPOST('cancel'); $contextpage = GETPOST('contextpage', 'aZ') ? GETPOST('contextpage', 'aZ') : 'recruitmentjobpositioncard'; // To manage different context of search $backtopage = GETPOST('backtopage', 'alpha'); $backtopageforcancel = GETPOST('backtopageforcancel', 'alpha'); diff --git a/htdocs/recruitment/recruitmentjobposition_card.php b/htdocs/recruitment/recruitmentjobposition_card.php index 5eed04f6306..daaf988f516 100644 --- a/htdocs/recruitment/recruitmentjobposition_card.php +++ b/htdocs/recruitment/recruitmentjobposition_card.php @@ -48,7 +48,7 @@ $id = GETPOSTINT('id'); $ref = GETPOST('ref', 'alpha'); $action = GETPOST('action', 'aZ09'); $confirm = GETPOST('confirm', 'alpha'); -$cancel = GETPOST('cancel', 'aZ09'); +$cancel = GETPOST('cancel'); $contextpage = GETPOST('contextpage', 'aZ') ? GETPOST('contextpage', 'aZ') : 'recruitmentjobpositioncard'; // To manage different context of search $backtopage = GETPOST('backtopage', 'alpha'); $backtopageforcancel = GETPOST('backtopageforcancel', 'alpha'); @@ -403,7 +403,7 @@ if ($object->id > 0 && (empty($action) || ($action != 'edit' && $action != 'crea if (empty($reshook)) { // Send if (empty($user->socid)) { - print dolGetButtonAction('', $langs->trans('SendMail'), 'default', $_SERVER["PHP_SELF"].'?id='.$object->id.'&action=presend&mode=init&token='.newToken().'#formmailbeforetitle'); + print dolGetButtonAction('', $langs->trans('SendMail'), 'email', $_SERVER["PHP_SELF"].'?id='.$object->id.'&action=presend&mode=init&token='.newToken().'#formmailbeforetitle'); } // Back to draft diff --git a/htdocs/recruitment/recruitmentjobposition_note.php b/htdocs/recruitment/recruitmentjobposition_note.php index 4941ac43c30..0dcaf314c4a 100644 --- a/htdocs/recruitment/recruitmentjobposition_note.php +++ b/htdocs/recruitment/recruitmentjobposition_note.php @@ -43,7 +43,7 @@ $langs->loadLangs(array("recruitment", "companies")); $id = GETPOSTINT('id'); $ref = GETPOST('ref', 'alpha'); $action = GETPOST('action', 'aZ09'); -$cancel = GETPOST('cancel', 'aZ09'); +$cancel = GETPOST('cancel'); $backtopage = GETPOST('backtopage', 'alpha'); // Initialize a technical objects diff --git a/htdocs/resource/agenda.php b/htdocs/resource/agenda.php index 2955d0a08df..f438977533d 100644 --- a/htdocs/resource/agenda.php +++ b/htdocs/resource/agenda.php @@ -52,7 +52,7 @@ $langs->load('companies'); $id = GETPOSTINT('id'); $ref = GETPOST('ref', 'alpha'); $action = GETPOST('action', 'aZ09'); -$cancel = GETPOST('cancel', 'aZ09'); +$cancel = GETPOST('cancel'); $backtopage = GETPOST('backtopage', 'alpha'); if (GETPOST('actioncode', 'array')) { diff --git a/htdocs/salaries/class/api_salaries.class.php b/htdocs/salaries/class/api_salaries.class.php index d4a047384e4..2b06b36069c 100644 --- a/htdocs/salaries/class/api_salaries.class.php +++ b/htdocs/salaries/class/api_salaries.class.php @@ -2,6 +2,7 @@ /* * Copyright (C) 2023 Marc Chenebaux * Copyright (C) 2025 MDW + * Copyright (C) 2025 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 @@ -478,9 +479,12 @@ class Salaries extends DolibarrApi // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore /** * Clean sensible object datas + * @phpstan-template T * * @param Object $object Object to clean * @return Object Object with cleaned properties + * @phpstan-param T $object + * @phpstan-return T */ protected function _cleanObjectDatas($object) { diff --git a/htdocs/salaries/stats/index.php b/htdocs/salaries/stats/index.php index fcaecd3e57e..ccf7a004fa7 100644 --- a/htdocs/salaries/stats/index.php +++ b/htdocs/salaries/stats/index.php @@ -238,6 +238,7 @@ if (!in_array($year, $arrayyears)) { $arrayyears[$year] = $year; } arsort($arrayyears); +print img_picto('', 'calendar', 'class="pictofixedwidth"'); print $form->selectarray('year', $arrayyears, $year, 0, 0, 0, '', 0, 0, 0, '', 'width75'); print ''; print ''; diff --git a/htdocs/societe/agenda.php b/htdocs/societe/agenda.php index 407542626b4..40c58463304 100644 --- a/htdocs/societe/agenda.php +++ b/htdocs/societe/agenda.php @@ -30,12 +30,6 @@ // Load Dolibarr environment require '../main.inc.php'; -require_once DOL_DOCUMENT_ROOT.'/contact/class/contact.class.php'; -require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php'; -require_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php'; -require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php'; -require_once DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php'; - /** * @var Conf $conf * @var DoliDB $db @@ -43,14 +37,20 @@ require_once DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php'; * @var Translate $langs * @var User $user */ +require_once DOL_DOCUMENT_ROOT.'/contact/class/contact.class.php'; +require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php'; // Load translation files required by the page $langs->loadLangs(array('agenda', 'bills', 'companies', 'orders', 'propal')); +$action = GETPOST('action', 'aZ09'); $contextpage = GETPOST('contextpage', 'aZ') ? GETPOST('contextpage', 'aZ') : 'thirdpartyagenda'; -if (GETPOST('actioncode', 'array')) { - $actioncode = GETPOST('actioncode', 'array', 3); +if (GETPOSTISARRAY('actioncode')) { + $actioncode = GETPOST('actioncode', 'array:alpha', 3); if (!count($actioncode)) { $actioncode = '0'; } @@ -60,14 +60,18 @@ if (GETPOST('actioncode', 'array')) { $search_rowid = GETPOST('search_rowid'); $search_agenda_label = GETPOST('search_agenda_label'); +$search_complete = GETPOST('search_complete'); +$search_dateevent_start = GETPOSTDATE('dateevent_start'); +$search_dateevent_end = GETPOSTDATE('dateevent_end'); $limit = GETPOSTINT('limit') ? GETPOSTINT('limit') : $conf->liste_limit; $sortfield = GETPOST('sortfield', 'aZ09comma'); $sortorder = GETPOST('sortorder', 'aZ09comma'); -$page = GETPOSTISSET('pageplusone') ? (GETPOSTINT('pageplusone') - 1) : GETPOSTINT("page"); -if (empty($page) || $page == -1) { +$page = GETPOSTISSET('pageplusone') ? (GETPOSTINT('pageplusone') - 1) : GETPOSTINT('page'); +if (empty($page) || $page < 0 || GETPOST('button_search', 'alpha') || GETPOST('button_removefilter', 'alpha')) { + // If $page is not defined, or '' or -1 or if we click on clear filters $page = 0; -} // If $page is not defined, or '' or -1 +} $offset = $limit * $page; $pageprev = $page - 1; $pagenext = $page + 1; @@ -78,6 +82,15 @@ if (!$sortorder) { $sortorder = 'DESC,DESC'; } +if (GETPOST('actioncode', 'array')) { + $actioncode = GETPOST('actioncode', 'array', 3); + if (!count($actioncode)) { + $actioncode = '0'; + } +} else { + $actioncode = GETPOST("actioncode", "alpha", 3) ? GETPOST("actioncode", "alpha", 3) : (GETPOST("actioncode") == '0' ? '0' : getDolGlobalString('AGENDA_DEFAULT_FILTER_TYPE_FOR_OBJECT')); +} + // Initialize a technical objects $object = new Societe($db); @@ -119,7 +132,9 @@ if (empty($reshook)) { // Purge search criteria if (GETPOST('button_removefilter_x', 'alpha') || GETPOST('button_removefilter.x', 'alpha') || GETPOST('button_removefilter', 'alpha')) { // All tests are required to be compatible with all browsers $actioncode = ''; + $search_rowid = ''; $search_agenda_label = ''; + $search_complete = ''; } } @@ -158,6 +173,8 @@ dol_print_object_info($object, 1); print ''; +print '
    '; + print dol_get_fiche_end(); @@ -210,6 +227,28 @@ if (isModEnabled('agenda') && ($user->hasRight('agenda', 'myactions', 'read') || if ($limit > 0 && $limit != $conf->liste_limit) { $param .= '&limit='.((int) $limit); } + if ($search_rowid) { + $param .= '&search_rowid='.urlencode($search_rowid); + } + if ($actioncode !== '' && $actioncode !== '-1') { + $param .= '&actioncode='.urlencode($actioncode); + } + if ($search_agenda_label) { + $param .= '&search_agenda_label='.urlencode($search_agenda_label); + } + if ($search_complete != '') { + $param .= '&search_complete='.urlencode($search_complete); + } + if ($search_dateevent_start != '') { + $param .= '&dateevent_startyear='.GETPOSTINT('dateevent_startyear'); + $param .= '&dateevent_startmonth='.GETPOSTINT('dateevent_startmonth'); + $param .= '&dateevent_startday='.GETPOSTINT('dateevent_startday'); + } + if ($search_dateevent_end != '') { + $param .= '&dateevent_endyear='.GETPOSTINT('dateevent_endyear'); + $param .= '&dateevent_endmonth='.GETPOSTINT('dateevent_endmonth'); + $param .= '&dateevent_endday='.GETPOSTINT('dateevent_endday'); + } // Try to know count of actioncomm from cache require_once DOL_DOCUMENT_ROOT.'/core/lib/memory.lib.php'; @@ -221,12 +260,13 @@ if (isModEnabled('agenda') && ($user->hasRight('agenda', 'myactions', 'read') || $titlelist = $langs->trans("Actions").(is_numeric($nbEvent) ? '('.$nbEvent.')' : ''); } - print_barre_liste($titlelist, 0, $_SERVER["PHP_SELF"], '', $sortfield, $sortorder, '', 0, -1, '', 0, $morehtmlright, '', 0, 1, 0); + print_barre_liste($titlelist, 0, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, '', 0, -1, '', 0, $morehtmlright, '', 0, 1, 0); // List of all actions $filters = array(); $filters['search_agenda_label'] = $search_agenda_label; $filters['search_rowid'] = $search_rowid; + $filters['search_complete'] = $search_complete; // Can be 'na', '0', '100', '50' // TODO Replace this with same code than into list.php show_actions_done($conf, $langs, $db, $object, null, 0, $actioncode, '', $filters, $sortfield, $sortorder, $object->module); diff --git a/htdocs/societe/class/api_contacts.class.php b/htdocs/societe/class/api_contacts.class.php index 5a449dc3a81..f497704568d 100644 --- a/htdocs/societe/class/api_contacts.class.php +++ b/htdocs/societe/class/api_contacts.class.php @@ -1,6 +1,6 @@ - * Copyright (C) 2019-2024 Frédéric France + * Copyright (C) 2019-2025 Frédéric France * Copyright (C) 2024 MDW * Copyright (C) 2025 William Mead * @@ -343,6 +343,16 @@ class Contacts extends DolibarrApi // Check mandatory fields $result = $this->_validate($request_data); + // External api user does not know internal country ID + if (!isset($request_data['country_id']) && isset($request_data['country_code'])) { + $field = strlen($request_data['country_code']) > 2 ? 'code_iso' : 'code'; + $id = dol_getIdFromCode($this->db, $request_data['country_code'], "c_country", $field, "rowid"); + if ($id < 0) { + throw new RestException(404, 'Country code not found in database: ' . $this->db->error); + } + $request_data['country_id'] = $id; + } + foreach ($request_data as $field => $value) { if ($field === 'caller') { // Add a mention of caller so on trigger called after action, we can filter to avoid a loop if we try to sync back again with the caller @@ -642,9 +652,12 @@ class Contacts extends DolibarrApi // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore /** * Clean sensible object data + * @phpstan-template T * * @param Object $object Object to clean * @return Object Object with cleaned properties + * @phpstan-param T $object + * @phpstan-return T */ protected function _cleanObjectDatas($object) { diff --git a/htdocs/societe/class/api_thirdparties.class.php b/htdocs/societe/class/api_thirdparties.class.php index e31a5198bc9..4e83728ff7d 100644 --- a/htdocs/societe/class/api_thirdparties.class.php +++ b/htdocs/societe/class/api_thirdparties.class.php @@ -2,7 +2,7 @@ /* Copyright (C) 2015 Jean-François Ferry * Copyright (C) 2018 Pierre Chéné * Copyright (C) 2019 Cedric Ancelin - * Copyright (C) 2020-2024 Frédéric France + * Copyright (C) 2020-2025 Frédéric France * Copyright (C) 2023 Alexandre Janniaux * Copyright (C) 2024-2025 MDW * Copyright (C) 2024 Jon Bendtsen @@ -307,6 +307,17 @@ class Thirdparties extends DolibarrApi if (!DolibarrApiAccess::$user->hasRight('societe', 'creer')) { throw new RestException(403); } + + // External api user does not know internal country ID + if (!isset($request_data['country_id']) && isset($request_data['country_code'])) { + $field = strlen($request_data['country_code']) > 2 ? 'code_iso' : 'code'; + $id = dol_getIdFromCode($this->db, $request_data['country_code'], "c_country", $field, "rowid"); + if ($id < 0) { + throw new RestException(404, 'Country code not found in database: ' . $this->db->error); + } + $request_data['country_id'] = $id; + } + // Check mandatory fields $result = $this->_validate($request_data); @@ -316,6 +327,12 @@ class Thirdparties extends DolibarrApi $this->company->context['caller'] = sanitizeVal($request_data['caller'], 'aZ09'); continue; } + if ($field == 'array_options' && is_array($value)) { + foreach ($value as $index => $val) { + $this->company->array_options[$index] = $this->_checkValForAPI('extrafields', $val, $this->company); + } + continue; + } $this->company->$field = $this->_checkValForAPI($field, $value, $this->company); } @@ -2479,9 +2496,12 @@ class Thirdparties extends DolibarrApi // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore /** * Clean sensible object datas + * @phpstan-template T * * @param Object $object Object to clean * @return Object Object with cleaned properties + * @phpstan-param T $object + * @phpstan-return T */ protected function _cleanObjectDatas($object) { diff --git a/htdocs/stripe/admin/stripe.php b/htdocs/stripe/admin/stripe.php index 8544f97d547..a2e791b855d 100644 --- a/htdocs/stripe/admin/stripe.php +++ b/htdocs/stripe/admin/stripe.php @@ -307,7 +307,7 @@ if (empty($conf->stripeconnect->enabled)) { print price(getDolGlobalString('STRIPE_APPLICATION_FEE_PERCENT')); print '% + '; print price(getDolGlobalString('STRIPE_APPLICATION_FEE')); - print ' '.$langs->getCurrencySymbol($conf->currency).' '.$langs->trans("minimum").' '.price(getDolGlobalString('STRIPE_APPLICATION_FEE_MINIMAL')).' '.$langs->getCurrencySymbol($conf->currency); + print ' '.$langs->getCurrencySymbol(getDolCurrency()).' '.$langs->trans("minimum").' '.price(getDolGlobalString('STRIPE_APPLICATION_FEE_MINIMAL')).' '.$langs->getCurrencySymbol(getDolCurrency()); print ''; } diff --git a/htdocs/stripe/lib/stripe.lib.php b/htdocs/stripe/lib/stripe.lib.php index 0db5a3ce787..50146c10804 100644 --- a/htdocs/stripe/lib/stripe.lib.php +++ b/htdocs/stripe/lib/stripe.lib.php @@ -74,7 +74,7 @@ function html_print_stripe_footer($fromcompany, $langs) } // Capital if ($fromcompany->capital) { - $line1 .= ($line1 ? " - " : "").$langs->transnoentities("CapitalOf", (string) $fromcompany->capital)." ".$langs->transnoentities("Currency".$conf->currency); + $line1 .= ($line1 ? " - " : "").$langs->transnoentities("CapitalOf", (string) $fromcompany->capital)." ".$langs->transnoentities("Currency".getDolCurrency()); } $reg = array(); diff --git a/htdocs/subtotals/class/commonsubtotal.class.php b/htdocs/subtotals/class/commonsubtotal.class.php index 00fbbd4583a..62392fca143 100644 --- a/htdocs/subtotals/class/commonsubtotal.class.php +++ b/htdocs/subtotals/class/commonsubtotal.class.php @@ -2,6 +2,8 @@ /* Copyright (C) 2014-2017 Laurent Destailleur * Copyright (C) 2024 MDW * Copyright (C) 2024-2025 Frédéric France + * Copyright (C) 2025 Charlene Benke + * * 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 @@ -76,17 +78,13 @@ trait CommonSubtotal public function addSubtotalLine($langs, $desc, $depth, $options = array(), $parent_line = 0) { if (empty($desc)) { - if (isset($this->errors)) { - $this->errors[] = $langs->trans("TitleNeedDesc"); - } + $this->errors[] = $langs->trans("TitleNeedDesc"); return -1; } $current_module = $this->element; // Ensure the object is one of the supported types if (!in_array($current_module, self::$ALLOWED_TYPES)) { - if (isset($this->errors)) { - $this->errors[] = $langs->trans("UnsupportedModuleError"); - } + $this->errors[] = $langs->trans("UnsupportedModuleError"); return -1; // Unsupported type } $error = 0; @@ -123,9 +121,7 @@ trait CommonSubtotal if ($max_existing_level+1 < $depth) { $depth = $max_existing_level+1; - if (isset($this->errors)) { - $this->errors[] = $langs->trans("TitleAddedLevelTooHigh", $depth); - } + $this->errors[] = $langs->trans("TitleAddedLevelTooHigh", $depth); $error ++; } @@ -216,8 +212,21 @@ trait CommonSubtotal SUBTOTALS_SPECIAL_CODE // Special code ); $this->fetch_lines(); + } elseif ($current_module == 'fichinter') { + $result = $this->addline( // @phpstan-ignore-line + $user, // user @phpstan-ignore-line + $this->id, // fk_fichinter @phpstan-ignore-line + $desc, // Description @phpstan-ignore-line + 0, // dateintervention @phpstan-ignore-line + $depth, // duration @phpstan-ignore-line + [], // arrayoption @phpstan-ignore-line + self::$PRODUCT_TYPE, // Type @phpstan-ignore-line + $rang, // Rang @phpstan-ignore-line + SUBTOTALS_SPECIAL_CODE // Special code @phpstan-ignore-line + ); } + if ($current_module != 'shipping') { foreach ($this->lines as $line) { '@phan-var-force CommonObjectLine $line'; @@ -254,9 +263,7 @@ trait CommonSubtotal $current_module = $this->element; // Ensure the object is one of the supported types if (!in_array($current_module, self::$ALLOWED_TYPES)) { - if (isset($this->errors)) { - $this->errors[] = $langs->trans("UnsupportedModuleError"); - } + $this->errors[] = $langs->trans("UnsupportedModuleError"); return -1; // Unsupported type } @@ -320,9 +327,7 @@ trait CommonSubtotal $current_module = $this->element; // Ensure the object is one of the supported types if (!in_array($current_module, self::$ALLOWED_TYPES)) { - if (isset($this->errors)) { - $this->errors[] = $langs->trans("UnsupportedModuleError"); - } + $this->errors[] = $langs->trans("UnsupportedModuleError"); return -1; // Unsupported type } @@ -341,9 +346,7 @@ trait CommonSubtotal if ($max_existing_level+1 < $depth) { $depth = $max_existing_level+1; - if (isset($this->errors)) { - $this->errors[] = $langs->trans("TitleEditedLevelTooHigh"); - } + $this->errors[] = $langs->trans("TitleEditedLevelTooHigh"); $error ++; } @@ -484,9 +487,7 @@ trait CommonSubtotal $current_module = $this->element; // Ensure the object is one of the supported types if (!in_array($current_module, self::$ALLOWED_TYPES)) { - if (isset($this->errors)) { - $this->errors[] = $langs->trans("UnsupportedModuleError"); - } + $this->errors[] = $langs->trans("UnsupportedModuleError"); return -1; // Unsupported type } diff --git a/htdocs/subtotals/core/substitutions/functions_subtotals.lib.php b/htdocs/subtotals/core/substitutions/functions_subtotals.lib.php index c3629a280ab..39a76dc8441 100644 --- a/htdocs/subtotals/core/substitutions/functions_subtotals.lib.php +++ b/htdocs/subtotals/core/substitutions/functions_subtotals.lib.php @@ -39,7 +39,7 @@ function subtotals_completesubstitutionarray_lines(&$substitutionarray, $langs, $substitutionarray['is_subtotals_title'] = (($line->special_code == SUBTOTALS_SPECIAL_CODE) && $line->qty > 0); $substitutionarray['is_subtotals_subtotal'] = (($line->special_code == SUBTOTALS_SPECIAL_CODE) && $line->qty < 0); $subtotal_total = 0; - if (isModEnabled('multicurrency') && $object->multicurrency_code != $conf->currency) { + if (isModEnabled('multicurrency') && $object->multicurrency_code != getDolCurrency()) { $subtotal_total = $object->getSubtotalLineMulticurrencyAmount($line); // @phan-suppress-current-line PhanPluginUnknownObjectMethodCall } else { $subtotal_total = $object->getSubtotalLineAmount($line); // @phan-suppress-current-line PhanPluginUnknownObjectMethodCall diff --git a/htdocs/supplier_proposal/card.php b/htdocs/supplier_proposal/card.php index 44773f6791d..868b2adbba6 100644 --- a/htdocs/supplier_proposal/card.php +++ b/htdocs/supplier_proposal/card.php @@ -2073,9 +2073,9 @@ if ($action == 'create') { if (empty($user->socid)) { if ($object->status == SupplierProposal::STATUS_VALIDATED || $object->status == SupplierProposal::STATUS_SIGNED) { if ($usercansend) { - print ''; + print dolGetButtonAction('', $langs->trans('SendMail'), 'email', dolBuildUrl($_SERVER["PHP_SELF"], ['id' => $object->id, 'action' => 'presend', 'mode' => 'init'], true).'#formmailbeforetitle', ''); } else { - print ''; + print dolGetButtonAction('', $langs->trans('SendMail'), 'email', '#', '', false); } } } diff --git a/htdocs/supplier_proposal/class/api_supplier_proposals.class.php b/htdocs/supplier_proposal/class/api_supplier_proposals.class.php index 5762b6e683e..5c3fc674400 100644 --- a/htdocs/supplier_proposal/class/api_supplier_proposals.class.php +++ b/htdocs/supplier_proposal/class/api_supplier_proposals.class.php @@ -2,6 +2,7 @@ /* Copyright (C) 2015 Jean-François Ferry * Copyright (C) 2016 Laurent Destailleur * Copyright (C) 2025 MDW + * Copyright (C) 2025 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 @@ -348,9 +349,12 @@ class SupplierProposals extends DolibarrApi // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore /** * Clean sensible object datas + * @phpstan-template T * * @param Object $object Object to clean * @return Object Object with cleaned properties + * @phpstan-param T $object + * @phpstan-return T */ protected function _cleanObjectDatas($object) { diff --git a/htdocs/supplier_proposal/class/supplier_proposal.class.php b/htdocs/supplier_proposal/class/supplier_proposal.class.php index 5ddc3a29f63..5bfd230eb8f 100644 --- a/htdocs/supplier_proposal/class/supplier_proposal.class.php +++ b/htdocs/supplier_proposal/class/supplier_proposal.class.php @@ -985,7 +985,7 @@ class SupplierProposal extends CommonObject list($this->fk_multicurrency, $this->multicurrency_tx) = MultiCurrency::getIdAndTxFromCode($this->db, $this->multicurrency_code, $now); } if (empty($this->fk_multicurrency)) { - $this->multicurrency_code = $conf->currency; + $this->multicurrency_code = getDolCurrency(); $this->fk_multicurrency = 0; $this->multicurrency_tx = 1; } @@ -2439,7 +2439,7 @@ class SupplierProposal extends CommonObject $this->note_private = 'This is a comment (private)'; $this->multicurrency_tx = 1; - $this->multicurrency_code = $conf->currency; + $this->multicurrency_code = getDolCurrency(); // Lines $nbp = min(1000, GETPOSTINT('nblines') ? GETPOSTINT('nblines') : 5); // We can force the nb of lines to test from command line (but not more than 1000) @@ -2600,13 +2600,13 @@ class SupplierProposal extends CommonObject $datas['ref_supplier'] = '
    '.$langs->trans('RefSupplier').': '.$this->ref_fourn; } if (!empty($this->total_ht)) { - $datas['amount_ht'] = '
    '.$langs->trans('AmountHT').': '.price($this->total_ht, 0, $langs, 0, -1, -1, $conf->currency); + $datas['amount_ht'] = '
    '.$langs->trans('AmountHT').': '.price($this->total_ht, 0, $langs, 0, -1, -1, getDolCurrency()); } if (!empty($this->total_tva)) { - $datas['amount_vat'] = '
    '.$langs->trans('VAT').': '.price($this->total_tva, 0, $langs, 0, -1, -1, $conf->currency); + $datas['amount_vat'] = '
    '.$langs->trans('VAT').': '.price($this->total_tva, 0, $langs, 0, -1, -1, getDolCurrency()); } if (!empty($this->total_ttc)) { - $datas['amount_ttc'] = '
    '.$langs->trans('AmountTTC').': '.price($this->total_ttc, 0, $langs, 0, -1, -1, $conf->currency); + $datas['amount_ttc'] = '
    '.$langs->trans('AmountTTC').': '.price($this->total_ttc, 0, $langs, 0, -1, -1, getDolCurrency()); } return $datas; diff --git a/htdocs/takepos/ajax/ajax.php b/htdocs/takepos/ajax/ajax.php index 67ef1f7be28..613956d9034 100644 --- a/htdocs/takepos/ajax/ajax.php +++ b/htdocs/takepos/ajax/ajax.php @@ -41,9 +41,6 @@ if (!defined('NOBROWSERNOTIF')) { // Load Dolibarr environment require '../../main.inc.php'; // Load $user and permissions -require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php'; -require_once DOL_DOCUMENT_ROOT."/product/class/product.class.php"; -require_once DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php'; /** * @var Conf $conf * @var DoliDB $db @@ -51,6 +48,9 @@ require_once DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php'; * @var Translate $langs * @var User $user */ +require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php'; +require_once DOL_DOCUMENT_ROOT."/product/class/product.class.php"; +require_once DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php'; $category = GETPOST('category', 'alphanohtml'); // Can be id of category or 'supplements' $action = GETPOST('action', 'aZ09'); @@ -443,7 +443,8 @@ if ($action == 'getProducts' && $user->hasRight('takepos', 'run')) { if ((getDolGlobalInt('TAKEPOS_PRINTER_TO_USE'.$term) > 0 || getDolGlobalString('TAKEPOS_PRINT_METHOD') == "takeposconnector") && getDolGlobalInt('TAKEPOS_TEMPLATE_TO_USE_FOR_INVOICES'.$term) > 0) { $object = new Facture($db); $object->fetch($id); - $ret = $printer->sendToPrinter($object, getDolGlobalInt('TAKEPOS_TEMPLATE_TO_USE_FOR_INVOICES'.$term), getDolGlobalInt('TAKEPOS_PRINTER_TO_USE'.$term)); + + $printer->sendToPrinter($object, getDolGlobalInt('TAKEPOS_TEMPLATE_TO_USE_FOR_INVOICES'.$term), getDolGlobalInt('TAKEPOS_PRINTER_TO_USE'.$term)); } } elseif ($action == 'getInvoice' && $user->hasRight('takepos', 'run')) { top_httphead('application/json'); @@ -459,12 +460,11 @@ if ($action == 'getProducts' && $user->hasRight('takepos', 'run')) { } elseif ($action == 'thecheck' && $user->hasRight('takepos', 'run')) { top_httphead('application/html'); - $place = GETPOST('place', 'alpha'); require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php'; require_once DOL_DOCUMENT_ROOT.'/core/class/dolreceiptprinter.class.php'; $object = new Facture($db); - $printer = new dolReceiptPrinter($db); + $printer->sendToPrinter($object, getDolGlobalInt('TAKEPOS_TEMPLATE_TO_USE_FOR_INVOICES'.$term), getDolGlobalInt('TAKEPOS_PRINTER_TO_USE'.$term)); } diff --git a/htdocs/takepos/index.php b/htdocs/takepos/index.php index 55904820121..90271271790 100644 --- a/htdocs/takepos/index.php +++ b/htdocs/takepos/index.php @@ -478,7 +478,7 @@ function MoreProducts(moreorless) { var nb_cat_shown = $('.div5 div.wrapper2[data-iscat=1]').length; var offset = * pageproducts - nb_cat_shown; // Only show products for sale (tosell=1) - $.getJSON('/takepos/ajax/ajax.php?action=getProducts&token=&category='+currentcat+'&tosell=1&limit='+limit+'&offset='+offset, function(data) { + $.getJSON('/takepos/ajax/ajax.php?action=getProducts&token=&thirdpartyid=' + jQuery('#thirdpartyid').val() + '&category='+currentcat+'&tosell=1&limit='+limit+'&offset='+offset, function(data) { console.log("Call ajax.php (in MoreProducts) to get Products of category "+currentcat); if (typeof (data[0]) == "undefined" && moreorless=="more"){ // Return if no more pages diff --git a/htdocs/takepos/phone.php b/htdocs/takepos/phone.php index 062bdc20275..bd2d6413486 100644 --- a/htdocs/takepos/phone.php +++ b/htdocs/takepos/phone.php @@ -111,7 +111,7 @@ if ($action == "productinfo" && $user->hasRight('takepos', 'run')) { print "
    ".$prod->label."
    "; print ''; print "
    ".$prod->description; - print "
    ".price($prod->price_ttc, 1, $langs, 1, -1, -1, $conf->currency).""; + print "
    ".price($prod->price_ttc, 1, $langs, 1, -1, -1, getDolCurrency()).""; print '
    '; } elseif ($action == "editline" && $user->hasRight('takepos', 'run')) { $placeid = GETPOSTINT('placeid'); @@ -125,7 +125,7 @@ if ($action == "productinfo" && $user->hasRight('takepos', 'run')) { print "".$prod->label."
    "; print ''; print "
    ".$prod->description; - print "
    ".price($prod->price_ttc, 1, $langs, 1, -1, -1, $conf->currency).""; + print "
    ".price($prod->price_ttc, 1, $langs, 1, -1, -1, getDolCurrency()).""; print '
    '; print ''; print ''; diff --git a/htdocs/takepos/receipt.php b/htdocs/takepos/receipt.php index 70b7a4b291f..471ccda6e60 100644 --- a/htdocs/takepos/receipt.php +++ b/htdocs/takepos/receipt.php @@ -111,9 +111,12 @@ if ($facid > 0 && !GETPOST('specimen')) { } print ''; + // Record entry in blocked logs each time we print a receipt +// // This will also increase the counter of printings of the receipt // DOL_DOCUMENT_ROOT.'/blockedlog/ajax/block-add.php?id='.$object->id.'&element='.$object->element.'&action=DOC_PREVIEW&token='.newToken(); + print " '; - - print ''; - - $parameters = array(); - $reshook = $hookmanager->executeHooks('insertExtraFooter', $parameters, $object, $action); // Note that $action and $object may have been modified by some hooks - if ($reshook < 0) { - setEventMessages($hookmanager->error, $hookmanager->errors, 'errors'); - } - - print dol_get_fiche_end(); + $i++; } +print '
    '.$langs->trans("TotalMargin").''; -print ' '.$langs->getCurrencySymbol($conf->currency).''; // set by jquery (see below) +print ' '.$langs->getCurrencySymbol(getDolCurrency()).''; // set by jquery (see below) print '
    '; print $langs->trans("SupplierOrder").''; @@ -2477,26 +2476,25 @@ if ($action == 'create' && $permissiontoadd) { // Get list of products already sent for same source object into $alreadysent $alreadysent = array(); - //$origin = 'commande_fournisseur'; + if (empty($origin)) { + $origin = 'supplier_order'; + } - if ($origin && $origin_id > 0) { + if ($origin_id > 0) { $sql = "SELECT obj.rowid, obj.fk_product, obj.label, obj.description, obj.product_type as fk_product_type, obj.qty as qty_asked, obj.date_start, obj.date_end"; $sql .= ", ed.rowid as receptionline_id, ed.qty, ed.fk_reception as reception_id, ed.fk_entrepot"; $sql .= ", e.rowid as reception_id, e.ref as reception_ref, e.date_creation, e.date_valid, e.date_delivery, e.date_reception"; - //if (getDolGlobalInt('MAIN_SUBMODULE_DELIVERY')) $sql .= ", l.rowid as livraison_id, l.ref as livraison_ref, l.date_delivery, ld.qty as qty_received"; $sql .= ', p.label as product_label, p.ref, p.fk_product_type, p.rowid as prodid, p.tobatch as product_tobatch'; $sql .= ', p.description as product_desc'; $sql .= " FROM ".MAIN_DB_PREFIX."receptiondet_batch as ed"; $sql .= ", ".MAIN_DB_PREFIX."reception as e"; - $sql .= ", ".MAIN_DB_PREFIX.$origin."det as obj"; - //if (getDolGlobalInt('MAIN_SUBMODULE_DELIVERY')) $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."delivery as l ON l.fk_reception = e.rowid LEFT JOIN ".MAIN_DB_PREFIX."deliverydet as ld ON ld.fk_delivery = l.rowid AND obj.rowid = ld.fk_origin_line"; + $sql .= ", ".MAIN_DB_PREFIX.(($origin == 'supplier_order') ? 'commande_fournisseur' : $origin)."det as obj"; $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product as p ON obj.fk_product = p.rowid"; $sql .= " WHERE e.entity IN (".getEntity('reception').")"; $sql .= " AND obj.fk_commande = ".((int) $origin_id); $sql .= " AND obj.rowid = ed.fk_elementdet"; $sql .= " AND ed.fk_reception = e.rowid"; $sql .= " AND ed.fk_reception !=".((int) $object->id); - //if ($filter) $sql.= $filter; $sql .= " ORDER BY obj.fk_product"; dol_syslog("get list of reception lines", LOG_DEBUG); diff --git a/htdocs/reception/class/api_receptions.class.php b/htdocs/reception/class/api_receptions.class.php index b6f6b233d07..5dc1cccdd51 100644 --- a/htdocs/reception/class/api_receptions.class.php +++ b/htdocs/reception/class/api_receptions.class.php @@ -2,6 +2,7 @@ /* Copyright (C) 2022 Quatadah Nasdami * Copyright (C) 2022 Laurent Destailleur * Copyright (C) 2024-2025 MDW + * Copyright (C) 2025 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 @@ -729,9 +730,12 @@ class Receptions extends DolibarrApi // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore /** * Clean sensible object datas + * @phpstan-template T * * @param Object $object Object to clean * @return Object Object with cleaned properties + * @phpstan-param T $object + * @phpstan-return T */ protected function _cleanObjectDatas($object) { diff --git a/htdocs/reception/class/reception.class.php b/htdocs/reception/class/reception.class.php index d1f535a4059..a6cdddd9c0e 100644 --- a/htdocs/reception/class/reception.class.php +++ b/htdocs/reception/class/reception.class.php @@ -336,7 +336,7 @@ class Reception extends CommonObject $sql .= ", fk_incoterms, location_incoterms"; $sql .= ") VALUES ("; $sql .= "'(PROV)'"; - $sql .= ", ".((int) $conf->entity); + $sql .= ", ".((int) $this->entity); $sql .= ", ".($this->ref_supplier ? "'".$this->db->escape($this->ref_supplier)."'" : "null"); $sql .= ", '".$this->db->idate($this->date_creation)."'"; $sql .= ", ".((int) $user->id); @@ -746,7 +746,7 @@ class Reception extends CommonObject } } - if (!$error) { + if (!$error && $this->origin_id > 0) { // Change status of purchase order to "reception in process" or "totally received" $status = $this->getStatusDispatch(); if ($status < 0) { @@ -1776,7 +1776,7 @@ class Reception extends CommonObject $return .= '
    '.$this->thirdparty->getNomUrl(1).'
    '; } /*if (property_exists($this, 'total_ht')) { - $return .= '
    '.price($this->total_ht, 0, $langs, 0, -1, -1, $conf->currency).' '.$langs->trans('HT').'
    '; + $return .= '
    '.price($this->total_ht, 0, $langs, 0, -1, -1, getDolCurrency()).' '.$langs->trans('HT').'
    '; }*/ if (method_exists($this, 'getLibStatut')) { $return .= '
    '.$this->getLibStatut(3).'
    '; diff --git a/htdocs/reception/class/receptionlinebatch.class.php b/htdocs/reception/class/receptionlinebatch.class.php index c4df1cc15be..4e5b1ad0217 100644 --- a/htdocs/reception/class/receptionlinebatch.class.php +++ b/htdocs/reception/class/receptionlinebatch.class.php @@ -3,7 +3,7 @@ * Copyright (C) 2014 Juanjo Menent * Copyright (C) 2024-2025 Frédéric France * Copyright (C) 2024 Christophe Battarel - * Copyright (C) 2024 MDW + * Copyright (C) 2024-2025 MDW * Copyright (C) 2025 Nick Fragoulis * * This program is free software; you can redistribute it and/or modify @@ -590,8 +590,8 @@ class ReceptionLineBatch extends CommonObjectLine $sql .= " status=".(isset($this->status) ? $this->status : "null").","; $sql .= " tms=".(dol_strlen((string) $this->tms) != 0 ? "'".$this->db->idate($this->tms)."'" : 'null').","; $sql .= " batch=".(isset($this->batch) ? "'".$this->db->escape($this->batch)."'" : "null").","; - $sql .= " eatby=".(dol_strlen((string) $this->eatby) != 0 ? "'".$this->db->idate($this->eatby)."'" : 'null').","; - $sql .= " sellby=".(dol_strlen((string) $this->sellby) != 0 ? "'".$this->db->idate($this->sellby)."'" : 'null').","; + $sql .= " eatby=".(dol_strlen((string) $this->eatby) != 0 ? "'".$this->db->idate((int) $this->eatby)."'" : 'null').","; + $sql .= " sellby=".(dol_strlen((string) $this->sellby) != 0 ? "'".$this->db->idate((int) $this->sellby)."'" : 'null').","; $sql .= " fk_unit = ".((int) $this->fk_unit); $sql .= " WHERE rowid=".((int) $this->id); diff --git a/htdocs/reception/dispatch.php b/htdocs/reception/dispatch.php index e901cde50c8..f1bb7e61a6b 100644 --- a/htdocs/reception/dispatch.php +++ b/htdocs/reception/dispatch.php @@ -322,7 +322,6 @@ llxHeader('', $title, $help_url, '', 0, 0, $morejs, '', '', 'mod-reception page- if ($id > 0 || !empty($ref)) { $typeobject = ''; if (!empty($object->origin) && $object->origin_id > 0) { - $object->origin = 'CommandeFournisseur'; $typeobject = $object->origin; $origin = $object->origin; $origin_id = $object->origin_id; @@ -418,7 +417,7 @@ if ($id > 0 || !empty($ref)) { print "
    '; print $langs->trans("SupplierOrder").''; diff --git a/htdocs/reception/stats/index.php b/htdocs/reception/stats/index.php index 7903e9b06b0..c604f948398 100644 --- a/htdocs/reception/stats/index.php +++ b/htdocs/reception/stats/index.php @@ -254,6 +254,7 @@ if (!in_array($nowyear, $arrayyears)) { $arrayyears[$nowyear] = $nowyear; } arsort($arrayyears); +print img_picto('', 'calendar', 'class="pictofixedwidth"'); print $form->selectarray('year', $arrayyears, $year, 0, 0, 0, '', 0, 0, 0, '', 'width75'); print '
    '; +print ''; + +print ''; + +print ''; + +$parameters = array(); +$reshook = $hookmanager->executeHooks('insertExtraFooter', $parameters, $object, $action); // Note that $action and $object may have been modified by some hooks +if ($reshook < 0) { + setEventMessages($hookmanager->error, $hookmanager->errors, 'errors'); +} + +print dol_get_fiche_end(); + + // End of page llxFooter(); $db->close(); diff --git a/htdocs/user/hierarchy.php b/htdocs/user/hierarchy.php index 74ec7bdabe9..cd98b2a87db 100644 --- a/htdocs/user/hierarchy.php +++ b/htdocs/user/hierarchy.php @@ -176,9 +176,9 @@ if (!is_array($user_arbo) && $user_arbo < 0) { $li = $userstatic->getNomUrl(-1, '', 0, 1); if (isModEnabled('multicompany') && $userstatic->admin && !$userstatic->entity) { - $li .= img_picto($langs->trans("SuperAdministratorDesc"), 'redstar', 'class="valignmiddle paddingright paddingleft"'); + $li .= img_picto($langs->trans("SuperAdministratorDesc"), 'superadmin', 'class="valignmiddle paddingright paddingleft"'); } elseif ($userstatic->admin) { - $li .= img_picto($langs->trans("AdministratorDesc"), 'star', 'class="valignmiddle paddingright paddingleft"'); + $li .= img_picto($langs->trans("AdministratorDesc"), 'admin', 'class="valignmiddle paddingright paddingleft"'); } $li .= ' ('.$val['login'].($entitystring ? ' - '.$entitystring : '').')'; @@ -233,9 +233,9 @@ if (!is_array($user_arbo) && $user_arbo < 0) { $li = ''; $li .= $userstatic->getNomUrl(-1, '', 0, 1); if (isModEnabled('multicompany') && $userstatic->admin && !$userstatic->entity) { - $li .= img_picto($langs->trans("SuperAdministrator"), 'redstar'); + $li .= img_picto($langs->trans("SuperAdministrator"), 'superadmin'); } elseif ($userstatic->admin) { - $li .= img_picto($langs->trans("Administrator"), 'star'); + $li .= img_picto($langs->trans("Administrator"), 'admin'); } $li .= ' ('.$val['login'].($entitystring ? ' - '.$entitystring : '').')'; $li .= ' - '.$langs->trans("ExcludedByFilter").''; diff --git a/htdocs/user/home.php b/htdocs/user/home.php index 33a89c37c79..d7818d2c782 100644 --- a/htdocs/user/home.php +++ b/htdocs/user/home.php @@ -189,9 +189,9 @@ if ($resql) { $lastcreatedbox .= ''; $lastcreatedbox .= $fuserstatic->getNomUrl(-1); if (isModEnabled('multicompany') && $obj->admin && !$obj->entity) { - $lastcreatedbox .= img_picto($langs->trans("SuperAdministratorDesc"), 'redstar'); + $lastcreatedbox .= img_picto($langs->trans("SuperAdministratorDesc"), 'superadmin'); } elseif ($obj->admin) { - $lastcreatedbox .= img_picto($langs->trans("AdministratorDesc"), 'star'); + $lastcreatedbox .= img_picto($langs->trans("AdministratorDesc"), 'admin'); } $lastcreatedbox .= ""; $lastcreatedbox .= ''.dol_escape_htmltag($obj->login).''; @@ -287,7 +287,7 @@ if ($permissiontoreadgroup) { $lastgroupbox .= ''; $lastgroupbox .= $grouptemp->getNomUrl(1); if (!$obj->entity) { - $lastgroupbox .= img_picto($langs->trans("GlobalGroup"), 'redstar'); + $lastgroupbox .= img_picto($langs->trans("GlobalGroup"), 'superadmin'); } $lastgroupbox .= ""; if (isModEnabled('multicompany') && is_object($mc)) { diff --git a/htdocs/user/list.php b/htdocs/user/list.php index 0f780c8a89f..605803e3120 100644 --- a/htdocs/user/list.php +++ b/htdocs/user/list.php @@ -1120,9 +1120,9 @@ while ($i < $imaxinloop) { print ''; print $li; if (isModEnabled('multicompany') && $obj->admin && !$obj->entity) { - print img_picto($langs->trans("SuperAdministratorDesc"), 'redstar', 'class="valignmiddle paddingright paddingleft"'); + print img_picto($langs->trans("SuperAdministratorDesc"), 'superadmin', 'class="valignmiddle paddingright paddingleft"'); } elseif ($obj->admin) { - print img_picto($langs->trans("AdministratorDesc"), 'star', 'class="valignmiddle paddingright paddingleft"'); + print img_picto($langs->trans("AdministratorDesc"), 'admin', 'class="valignmiddle paddingright paddingleft"'); } print ''; if (!$i) { @@ -1194,9 +1194,9 @@ while ($i < $imaxinloop) { $user2->statut = $obj->status2; $user2->status = $obj->status2; if (isModEnabled('multicompany') && $obj->admin2 && !$obj->entity2) { - print img_picto($langs->trans("SuperAdministratorDesc"), 'redstar', 'class="valignmiddle paddingright"'); + print img_picto($langs->trans("SuperAdministratorDesc"), 'superadmin', 'class="valignmiddle paddingright"'); } elseif ($obj->admin2) { - print img_picto($langs->trans("AdministratorDesc"), 'star', 'class="valignmiddle paddingright"'); + print img_picto($langs->trans("AdministratorDesc"), 'admin', 'class="valignmiddle paddingright"'); } print $user2->getNomUrl(-1, '', 0, 0, 24, 0, '', '', 1); } diff --git a/htdocs/user/note.php b/htdocs/user/note.php index 6f8ec6e9179..90ff138efd0 100644 --- a/htdocs/user/note.php +++ b/htdocs/user/note.php @@ -134,9 +134,9 @@ if ($id) { print ''; $addadmin = ''; if (isModEnabled('multicompany') && !empty($object->admin) && empty($object->entity)) { - $addadmin .= img_picto($langs->trans("SuperAdministratorDesc"), "redstar", 'class="paddingleft valignmiddle"'); + $addadmin .= img_picto($langs->trans("SuperAdministratorDesc"), "superadmin", 'class="paddingleft valignmiddle"'); } elseif (!empty($object->admin)) { - $addadmin .= img_picto($langs->trans("AdministratorDesc"), "star", 'class="paddingleft valignmiddle"'); + $addadmin .= img_picto($langs->trans("AdministratorDesc"), "admin", 'class="paddingleft valignmiddle"'); } print showValueWithClipboardCPButton($object->login).$addadmin; print ''; diff --git a/htdocs/user/notify/card.php b/htdocs/user/notify/card.php index 17a4ffdc24f..14742c13239 100644 --- a/htdocs/user/notify/card.php +++ b/htdocs/user/notify/card.php @@ -195,9 +195,9 @@ if ($result > 0) { $addadmin = ''; if (property_exists($object, 'admin')) { if (isModEnabled('multicompany') && !empty($object->admin) && empty($object->entity)) { - $addadmin .= img_picto($langs->trans("SuperAdministratorDesc"), "redstar", 'class="paddingleft valignmiddle"'); + $addadmin .= img_picto($langs->trans("SuperAdministratorDesc"), "superadmin", 'class="paddingleft valignmiddle"'); } elseif (!empty($object->admin)) { - $addadmin .= img_picto($langs->trans("AdministratorDesc"), "star", 'class="paddingleft valignmiddle"'); + $addadmin .= img_picto($langs->trans("AdministratorDesc"), "admin", 'class="paddingleft valignmiddle"'); } } print showValueWithClipboardCPButton($object->login).$addadmin; diff --git a/htdocs/user/param_ihm.php b/htdocs/user/param_ihm.php index c1313a3779d..c6f7304feff 100644 --- a/htdocs/user/param_ihm.php +++ b/htdocs/user/param_ihm.php @@ -27,11 +27,6 @@ // Load Dolibarr environment require '../main.inc.php'; -require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php'; -require_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php'; -require_once DOL_DOCUMENT_ROOT.'/core/lib/usergroups.lib.php'; -require_once DOL_DOCUMENT_ROOT.'/core/class/html.formadmin.class.php'; - /** * @var Conf $conf * @var DoliDB $db @@ -39,6 +34,10 @@ require_once DOL_DOCUMENT_ROOT.'/core/class/html.formadmin.class.php'; * @var Translate $langs * @var User $user */ +require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/usergroups.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/class/html.formadmin.class.php'; // Load translation files required by page $langs->loadLangs(array('companies', 'products', 'admin', 'users', 'languages', 'projects', 'members')); @@ -357,9 +356,9 @@ if ($action == 'edit') { print ''; $addadmin = ''; if (isModEnabled('multicompany') && !empty($object->admin) && empty($object->entity)) { - $addadmin .= img_picto($langs->trans("SuperAdministratorDesc"), "redstar", 'class="paddingleft valignmiddle"'); + $addadmin .= img_picto($langs->trans("SuperAdministratorDesc"), "superadmin", 'class="paddingleft valignmiddle"'); } elseif (!empty($object->admin)) { - $addadmin .= img_picto($langs->trans("AdministratorDesc"), "star", 'class="paddingleft valignmiddle"'); + $addadmin .= img_picto($langs->trans("AdministratorDesc"), "admin", 'class="paddingleft valignmiddle"'); } print showValueWithClipboardCPButton($object->login).$addadmin; print ''; @@ -524,9 +523,9 @@ if ($action == 'edit') { print ''; $addadmin = ''; if (isModEnabled('multicompany') && !empty($object->admin) && empty($object->entity)) { - $addadmin .= img_picto($langs->trans("SuperAdministratorDesc"), "redstar", 'class="paddingleft valignmiddle"'); + $addadmin .= img_picto($langs->trans("SuperAdministratorDesc"), "superadmin", 'class="paddingleft valignmiddle"'); } elseif (!empty($object->admin)) { - $addadmin .= img_picto($langs->trans("AdministratorDesc"), "star", 'class="paddingleft valignmiddle"'); + $addadmin .= img_picto($langs->trans("AdministratorDesc"), "admin", 'class="paddingleft valignmiddle"'); } print showValueWithClipboardCPButton($object->login).$addadmin; print ''; diff --git a/htdocs/user/perms.php b/htdocs/user/perms.php index fe728e3fc6a..b8630f53739 100644 --- a/htdocs/user/perms.php +++ b/htdocs/user/perms.php @@ -379,9 +379,9 @@ if (!empty($object->ldap_sid) && $object->status == 0) { print ''; $addadmin = ''; if (isModEnabled('multicompany') && !empty($object->admin) && empty($object->entity)) { - $addadmin .= img_picto($langs->trans("SuperAdministratorDesc"), "redstar", 'class="paddingleft valignmiddle"'); + $addadmin .= img_picto($langs->trans("SuperAdministratorDesc"), "superadmin", 'class="paddingleft valignmiddle"'); } elseif (!empty($object->admin)) { - $addadmin .= img_picto($langs->trans("AdministratorDesc"), "star", 'class="paddingleft valignmiddle"'); + $addadmin .= img_picto($langs->trans("AdministratorDesc"), "admin", 'class="paddingleft valignmiddle"'); } print showValueWithClipboardCPButton($object->login).$addadmin; print ''; @@ -657,7 +657,7 @@ foreach ($arrayofpermission as $i => $obj) { print ''; if ($caneditperms) { print ''; - print img_picto($langs->trans("AdministratorDesc"), 'star', 'class="paddingleft valignmiddle"'); + print img_picto($langs->trans("AdministratorDesc"), 'admin', 'class="paddingleft valignmiddle"'); print ''; } else { print ''; diff --git a/htdocs/variants/class/ProductAttribute.class.php b/htdocs/variants/class/ProductAttribute.class.php index dbd78a01203..769af77e2f5 100644 --- a/htdocs/variants/class/ProductAttribute.class.php +++ b/htdocs/variants/class/ProductAttribute.class.php @@ -1359,7 +1359,7 @@ class ProductAttribute extends CommonObject * @param string $action Action code * @param Societe $seller Object of seller third party * @param ?Societe $buyer Object of buyer third party - * @param int $selected Object line selected + * @param int<0,max> $selected Object line selected * @param int $dateSelector 1=Show also date range input fields * @param string $defaulttpldir Directory where to find the template * @param int $addcreateline 1=Add create line @@ -1446,7 +1446,7 @@ class ProductAttribute extends CommonObject * @param int $dateSelector 1=Show also date range input fields * @param Societe $seller Object of seller third party * @param ?Societe $buyer Object of buyer third party - * @param int<0,1> $selected Object line selected + * @param int<0,max> $selected ID line selected * @param ?Extrafields $extrafields Object of extrafields * @param string $defaulttpldir Directory where to find the template (deprecated) * @return void diff --git a/htdocs/webhook/class/api_webhook.class.php b/htdocs/webhook/class/api_webhook.class.php index 674e75cc399..9943cdc3354 100644 --- a/htdocs/webhook/class/api_webhook.class.php +++ b/htdocs/webhook/class/api_webhook.class.php @@ -1,6 +1,7 @@ * Copyright (C) 2025 MDW + * Copyright (C) 2025 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 @@ -322,9 +323,12 @@ class Webhook extends DolibarrApi // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore /** * Clean sensible object datas + * @phpstan-template T * * @param Object $object Object to clean * @return Object Object with cleaned properties + * @phpstan-param T $object + * @phpstan-return T */ protected function _cleanObjectDatas($object) { diff --git a/htdocs/webhook/class/triggerhistory.class.php b/htdocs/webhook/class/triggerhistory.class.php index 307ca714999..2ade08da1fc 100644 --- a/htdocs/webhook/class/triggerhistory.class.php +++ b/htdocs/webhook/class/triggerhistory.class.php @@ -674,7 +674,7 @@ class TriggerHistory extends CommonObject } /*if (property_exists($this, 'amount')) { $return .= '
    '; - $return .= ''.price($this->amount, 0, $langs, 1, -1, -1, $conf->currency).''; + $return .= ''.price($this->amount, 0, $langs, 1, -1, -1, getDolCurrency()).''; }*/ $return .= '
    '.$this->getLibStatut(3).'
    '; $return .= ''; diff --git a/htdocs/webhook/lib/webhook.lib.php b/htdocs/webhook/lib/webhook.lib.php index d5de6d8568a..07972b470ea 100644 --- a/htdocs/webhook/lib/webhook.lib.php +++ b/htdocs/webhook/lib/webhook.lib.php @@ -30,7 +30,7 @@ */ function webhookAdminPrepareHead() { - global $langs, $conf; + global $langs, $conf, $form; $h = 0; $head = array(); @@ -45,7 +45,7 @@ function webhookAdminPrepareHead() $h++; $head[$h][0] = DOL_URL_ROOT . '/webhook/triggerhistory_list.php?mode=modulesetup'; - $head[$h][1] = $langs->trans("TriggerHistory"); + $head[$h][1] = $form->textwithpicto($langs->trans("TriggerHistory"), $langs->trans("TriggerHistoryAltWithLog")); $head[$h][2] = 'triggerhistory'; $h++; diff --git a/htdocs/webhook/target_card.php b/htdocs/webhook/target_card.php index 47dac321fa0..341dd9db66b 100644 --- a/htdocs/webhook/target_card.php +++ b/htdocs/webhook/target_card.php @@ -49,7 +49,7 @@ $id = GETPOSTINT('id'); $ref = GETPOST('ref', 'alpha'); $action = GETPOST('action', 'aZ09'); $confirm = GETPOST('confirm', 'alpha'); -$cancel = GETPOST('cancel', 'aZ09'); +$cancel = GETPOST('cancel'); $contextpage = GETPOST('contextpage', 'aZ') ? GETPOST('contextpage', 'aZ') : 'targetcard'; // To manage different context of search $backtopage = GETPOST('backtopage', 'alpha'); $backtopageforcancel = GETPOST('backtopageforcancel', 'alpha'); @@ -473,7 +473,7 @@ if ($object->id > 0 && (empty($action) || ($action != 'edit' && $action != 'crea if (empty($reshook)) { // Send /*if (empty($user->socid)) { - print dolGetButtonAction('', $langs->trans('SendMail'), 'default', $_SERVER["PHP_SELF"].'?id='.$object->id.'&action=presend&mode=init&token='.newToken().'#formmailbeforetitle'); + print dolGetButtonAction('', $langs->trans('SendMail'), 'email', $_SERVER["PHP_SELF"].'?id='.$object->id.'&action=presend&mode=init&token='.newToken().'#formmailbeforetitle'); }*/ print dolGetButtonAction('', $langs->trans('Modify'), 'default', $_SERVER["PHP_SELF"].'?id='.$object->id.'&action=edit&token='.newToken(), '', $permissiontoadd); diff --git a/htdocs/webhook/triggerhistory_card.php b/htdocs/webhook/triggerhistory_card.php index 1056895b2d9..a6faa40cf15 100644 --- a/htdocs/webhook/triggerhistory_card.php +++ b/htdocs/webhook/triggerhistory_card.php @@ -73,7 +73,7 @@ $lineid = GETPOSTINT('lineid'); $action = GETPOST('action', 'aZ09'); $confirm = GETPOST('confirm', 'alpha'); -$cancel = GETPOST('cancel', 'aZ09'); +$cancel = GETPOST('cancel', 'alpha'); $contextpage = GETPOST('contextpage', 'aZ') ? GETPOST('contextpage', 'aZ') : str_replace('_', '', basename(dirname(__FILE__)).basename(__FILE__, '.php')); // To manage different context of search $backtopage = GETPOST('backtopage', 'alpha'); // if not set, a default page will be used $backtopageforcancel = GETPOST('backtopageforcancel', 'alpha'); // if not set, $backtopage will be used @@ -489,7 +489,7 @@ if ($object->id > 0 && (empty($action) || ($action != 'edit' && $action != 'crea if (empty($reshook)) { /*// Send if (empty($user->socid)) { - print dolGetButtonAction('', $langs->trans('SendMail'), 'default', $_SERVER["PHP_SELF"].'?id='.$object->id.'&action=presend&token='.newToken().'&mode=init#formmailbeforetitle'); + print dolGetButtonAction('', $langs->trans('SendMail'), 'email', $_SERVER["PHP_SELF"].'?id='.$object->id.'&action=presend&token='.newToken().'&mode=init#formmailbeforetitle'); } // Back to draft diff --git a/htdocs/webhook/triggerhistory_note.php b/htdocs/webhook/triggerhistory_note.php index 2e82e71b875..ffd3b533c55 100644 --- a/htdocs/webhook/triggerhistory_note.php +++ b/htdocs/webhook/triggerhistory_note.php @@ -65,7 +65,7 @@ $langs->loadLangs(array("webhook@webhook", "companies", "admin")); $id = GETPOSTINT('id'); $ref = GETPOST('ref', 'alpha'); $action = GETPOST('action', 'aZ09'); -$cancel = GETPOST('cancel', 'aZ09'); +$cancel = GETPOST('cancel', 'alpha'); $backtopage = GETPOST('backtopage', 'alpha'); // Initialize a technical objects diff --git a/htdocs/webportal/class/context.class.php b/htdocs/webportal/class/context.class.php index 899ddf1e034..11eea65bafd 100644 --- a/htdocs/webportal/class/context.class.php +++ b/htdocs/webportal/class/context.class.php @@ -202,13 +202,13 @@ class Context //$this->generateNewToken(); - $this->initController(); + $this->initController(false); // Init of base URL. Must be the public URL. $this->rootUrl = self::getRootConfigUrl(); - $this->theme = new WebPortalTheme(); + $this->theme = new WebPortalTheme(false); } /** @@ -228,9 +228,10 @@ class Context /** * Init controller * + * @param bool $init_theme Init theme properties * @return void */ - public function initController() + public function initController($init_theme = true) { global $hookmanager; @@ -249,6 +250,7 @@ class Context $this->addControllerDefinition('documentlist', $defaultControllersPath . 'documentlist.controller.class.php', 'DocumentListController'); //** Below is the addition to the menu of the DocumentUtileController.class.php controller in order to share via the GED (documents) "Documentscomptes" $this->addControllerDefinition('documentutile', $defaultControllersPath . 'documentutile.controller.class.php', 'DocumentUtileController'); + $this->addControllerDefinition('viewimage', $defaultControllersPath . 'viewimage.controller.class.php', 'ViewImageController'); // Hooks for init controller $hookmanager->initHooks(array('webportaldao')); @@ -265,6 +267,10 @@ class Context $this->setControllerFound(); } } + + if ($init_theme) { + $this->theme->init(); + } } /** @@ -470,6 +476,8 @@ class Context if (!empty($_SESSION["webportal_logged_thirdparty_account_id"])) { return true; + } elseif (!empty($_SESSION["webportal_logged_member_account_id"])) { + return true; } else { return false; } @@ -714,29 +722,16 @@ class Context if ($obj) { $passcrypted = $obj->pass_crypted; - // Check crypted password - $cryptType = ''; - if (getDolGlobalString('DATABASE_PWD_ENCRYPTED')) { - $cryptType = getDolGlobalString('DATABASE_PWD_ENCRYPTED'); - } - - // By default, we use default setup for encryption rule - if (!in_array($cryptType, array('auto'))) { - $cryptType = 'auto'; - } - // Check crypted password according to crypt algorithm - if ($cryptType == 'auto') { - if ($passcrypted && dol_verifyHash($pass, $passcrypted, '0')) { - $passok = true; - } + if ($passcrypted && dol_verifyHash($pass, $passcrypted, '0')) { + $passok = true; } // Password ok ? if ($passok) { $id = $obj->id; } else { - dol_syslog(__METHOD__ .' Authentication KO bad password for ' . $login . ', cryptType=' . $cryptType, LOG_NOTICE); + dol_syslog(__METHOD__ .' Authentication KO bad password for ' . $login . ', cryptType=auto', LOG_NOTICE); sleep(1); // Brut force protection. Must be same delay when login is not valid return -3; } @@ -752,4 +747,56 @@ class Context return $id; } + + /** + * Try to find the member account id from + * + * @param string $login Login + * @param string $pass Password + * @return int Member account id || <0 if error + */ + public function getMemberAccountFromLogin($login, $pass) + { + $id = 0; + + $sql = "SELECT a.rowid as id, a.pass_crypted"; + $sql .= " FROM " . $this->db->prefix() . "adherent as a"; + $sql .= " WHERE a.login = '" . $this->db->escape($login) . "'"; + $sql .= " AND a.statut = 1"; + $sql .= " AND a.entity IN (" . getEntity('member') . ")"; + + dol_syslog(__METHOD__ . ' Try to find the member account id for login"' . $login . '"', LOG_DEBUG); + $result = $this->db->query($sql); + if ($result) { + if ($this->db->num_rows($result) == 1) { + $passok = false; + $obj = $this->db->fetch_object($result); + if ($obj) { + $passcrypted = $obj->pass_crypted; + + // Check crypted password according to crypt algorithm + if ($passcrypted && dol_verifyHash($pass, $passcrypted, '0')) { + $passok = true; + } + + // Password ok ? + if ($passok) { + $id = $obj->id; + } else { + dol_syslog(__METHOD__ .' Authentication KO bad password for ' . $login . ', cryptType=auto', LOG_NOTICE); + sleep(1); // Brut force protection. Must be same delay when login is not valid + return -3; + } + } + } else { + dol_syslog(__METHOD__ . ' Many member account found for login"' . $login . '"', LOG_ERR); + return -2; + } + } else { + $this->error = $this->db->lasterror(); + return -1; + } + + return $id; + } } diff --git a/htdocs/webportal/class/html.formcardwebportal.class.php b/htdocs/webportal/class/html.formcardwebportal.class.php index 38b8480c191..adcbbbd9fd3 100644 --- a/htdocs/webportal/class/html.formcardwebportal.class.php +++ b/htdocs/webportal/class/html.formcardwebportal.class.php @@ -26,6 +26,8 @@ */ require_once DOL_DOCUMENT_ROOT . '/webportal/class/html.formwebportal.class.php'; +require_once DOL_DOCUMENT_ROOT . '/webportal/class/webportalfieldsmanager.class.php'; +require_once DOL_DOCUMENT_ROOT . '/core/class/extrafields.class.php'; /** * Class to manage generation of HTML components @@ -64,7 +66,7 @@ class FormCardWebPortal public $elementEn = ''; /** - * @var Form Instance of the Form + * @var FormWebPortal Instance of the Form */ public $form; @@ -78,6 +80,11 @@ class FormCardWebPortal */ public $object; + /** + * @var ExtraFields Extra fields + */ + public $extrafields; + /** * @var int Permission to read */ @@ -118,6 +125,27 @@ class FormCardWebPortal */ public $titleDescKey = ''; + /** + * @var string Object field key to break on the view page + */ + public $key_for_break = ''; + + /** + * @var bool The card displayed is in modal + */ + public $modal = false; + + /** + * @var FieldsManager Fields manager + */ + public $fieldsmanager; + + /** + * @var int Update function type (1: update(user), 2: update(id, user)) + */ + public $updateType = 1; + + /** * Constructor * @@ -127,6 +155,7 @@ class FormCardWebPortal { $this->db = $db; $this->form = new FormWebPortal($this->db); + $this->fieldsmanager = new WebPortalFieldsManager($this->db, $this->form); } /** @@ -153,6 +182,8 @@ class FormCardWebPortal accessforbidden(); } + $context = Context::getInstance(); + // load module libraries dol_include_once('/webportal/class/webportal' . $elementEn . '.class.php'); @@ -164,18 +195,22 @@ class FormCardWebPortal $ref = GETPOST('ref', 'alpha'); $action = GETPOST('action', 'aZ09'); $confirm = GETPOST('confirm', 'alpha'); - $cancel = GETPOST('cancel', 'aZ09'); + $cancel = GETPOST('cancel'); $contextpage = GETPOST('contextpage', 'aZ') ? GETPOST('contextpage', 'aZ') : 'webportal' . $elementEn . 'card'; // To manage different context of search $backtopage = GETPOST('backtopage', 'alpha'); // if not set, a default page will be used $backtopageforcancel = GETPOST('backtopageforcancel', 'alpha'); // if not set, $backtopage will be used + $modal = GETPOSTINT('modal') == 1; // Initialize a technical objects $object = new $objectclass($this->db); + '@phan-var-force CommonObject $object'; + /** @var CommonObject $object */ //$extrafields = new ExtraFields($db); $hookmanager->initHooks(array('webportal' . $elementEn . 'card', 'globalcard')); // Note that conf->hooks_modules contains array // Fetch optionals attributes and labels - //$extrafields->fetch_name_optionals_label($object->table_element); + $this->extrafields = new ExtraFields($this->db); + $this->extrafields->fetch_name_optionals_label($object->table_element); //$search_array_options = $extrafields->getOptionalsFromPost($object->table_element, '', 'search_'); if (empty($action) && empty($id) && empty($ref)) { @@ -183,7 +218,28 @@ class FormCardWebPortal } // Load object - include DOL_DOCUMENT_ROOT . '/core/actions_fetchobject.inc.php'; // Must be 'include', not 'include_once'. + if (($id > 0 || (!empty($ref) && !in_array($action, array('create', 'createtask', 'add')))) && (empty($cancel) || $id > 0)) { + if (($id > 0 && is_numeric((string) $id)) || !empty($ref)) { // To discard case when id is list of ids like '1,2,3...' + if ($object->element == 'usergroup') { + $ret = $object->fetch($id, (empty($ref) ? '' : $ref), true); // to load $object->members + } else { + $ret = $object->fetch($id, (empty($ref) ? '' : $ref)); + } + if ($ret > 0) { + $object->fetch_thirdparty(); + $id = $object->id; + } else { + if (empty($object->error) && !count($object->errors)) { + if ($ret < 0) { // if $ret == 0, it means not found. + $context->setEventMessages('Fetch on object (type ' . get_class($object) . ') return an error without filling $object->error nor $object->errors', null, 'errors'); + } + } else { + $context->setEventMessages($object->error, $object->errors, 'errors'); + } + $action = ''; + } + } + } // Security check (enable the most restrictive one) if (!isModEnabled('webportal')) { @@ -208,6 +264,7 @@ class FormCardWebPortal $this->permissiondellink = $permissiondellink; $this->titleKey = $objectclass . 'CardTitle'; $this->ref = $ref; + $this->modal = $modal; } /** @@ -217,8 +274,6 @@ class FormCardWebPortal */ public function doActions() { - global $langs; - // initialize $action = $this->action; $backtopage = $this->backtopage; @@ -230,8 +285,6 @@ class FormCardWebPortal //$permissiontoread = $this->permissiontoread; $permissiontoadd = $this->permissiontoadd; - $error = 0; - $context = Context::getInstance(); $backurlforlist = $context->getControllerUrl('default'); @@ -257,597 +310,38 @@ class FormCardWebPortal // Action to update record if ($action == 'update' && !empty($permissiontoadd)) { - foreach ($object->fields as $key => $val) { - // Check if field was submitted to be edited - if ($object->fields[$key]['type'] == 'duration') { - if (!GETPOSTISSET($key . 'hour') || !GETPOSTISSET($key . 'min')) { - continue; // The field was not submitted to be saved - } - } elseif ($object->fields[$key]['type'] == 'boolean') { - if (!GETPOSTISSET($key)) { - $object->$key = 0; // use 0 instead null if the field is defined as not null - continue; - } - } else { - if (!GETPOSTISSET($key) && !preg_match('/^chkbxlst:/', $object->fields[$key]['type']) && $object->fields[$key]['type'] !== 'checkbox') { - continue; // The field was not submitted to be saved - } - } - // Ignore special fields - if (in_array($key, array('rowid', 'entity', 'import_key'))) { - continue; - } - if (in_array($key, array('date_creation', 'tms', 'fk_user_creat', 'fk_user_modif'))) { - if (!in_array(abs($val['visible']), array(1, 3, 4))) { - continue; // Only 1 and 3 and 4, that are cases to update - } - } + $error = 0; - // Set value to update - if (preg_match('/^text/', $object->fields[$key]['type'])) { - $tmparray = explode(':', $object->fields[$key]['type']); - if (!empty($tmparray[1])) { - $value = GETPOST($key, $tmparray[1]); - } else { - $value = GETPOST($key, 'nohtml'); - } - } elseif (preg_match('/^html/', $object->fields[$key]['type'])) { - $tmparray = explode(':', $object->fields[$key]['type']); - if (!empty($tmparray[1])) { - $value = GETPOST($key, $tmparray[1]); - } else { - $value = GETPOST($key, 'restricthtml'); - } - } elseif (in_array($object->fields[$key]['type'], array('date', 'datetime'))) { - $dateYear = 0; - $dateMonth = 0; - $dateDay = 0; - if (GETPOSTINT($key.'day')) { - $dateDay = GETPOSTINT($key.'day'); - } - if (GETPOSTINT($key.'month')) { - $dateMonth = GETPOSTINT($key.'month'); - } - if (GETPOSTINT($key.'year')) { - $dateYear = GETPOSTINT($key.'year'); - } - // extract time HH:ii:ss for hours, minutes and seconds - $timeHours = 0; - $timeMinutes = 0; - $timeSeconds = 0; - if (GETPOSTINT($key.'hour')) { - $timeHours = GETPOSTINT($key.'hour'); - } - if (GETPOSTINT($key.'min')) { - $timeMinutes = GETPOSTINT($key.'min'); - } - if (GETPOSTINT($key.'sec')) { - $timeSeconds = GETPOSTINT($key.'sec'); - } - $value = dol_mktime($timeHours, $timeMinutes, $timeSeconds, $dateMonth, $dateDay, $dateYear); - } elseif ($object->fields[$key]['type'] == 'duration') { - if (GETPOSTINT($key . 'hour') != '' || GETPOSTINT($key . 'min') != '') { - $value = 60 * 60 * GETPOSTINT($key . 'hour') + 60 * GETPOSTINT($key . 'min'); - } else { - $value = ''; - } - } elseif (preg_match('/^(integer|price|real|double)/', $object->fields[$key]['type'])) { - $value = price2num(GETPOST($key, 'alphanohtml')); // To fix decimal separator according to lang setup - } elseif ($object->fields[$key]['type'] == 'boolean') { - $value = ((GETPOST($key, 'aZ09') == 'on' || GETPOST($key, 'aZ09') == '1') ? 1 : 0); - //} - //elseif ($object->fields[$key]['type'] == 'reference') { - // $value = array_keys($object->param_list)[GETPOST($key)].','.GETPOST($key.'2'); - } elseif (preg_match('/^chkbxlst:/', $object->fields[$key]['type']) || $object->fields[$key]['type'] == 'checkbox') { - $value = ''; - $values_arr = GETPOST($key, 'array'); - if (!empty($values_arr)) { - $value = implode(',', $values_arr); - } - } else { - if ($key == 'lang') { - $value = GETPOST($key, 'aZ09'); - } else { - $value = GETPOST($key, 'alphanohtml'); - } + $result = $this->fieldsmanager->setFieldValuesFromPost($object, $this->extrafields, '', '', 'edit'); + if ($result < 0) { + $error++; + $context->setEventMessages($this->fieldsmanager->error, $this->fieldsmanager->errors, 'errors'); + } else { + if ($this->updateType == 2) { + $result = $object->update($this->id, $context->logged_user); + } else { // $this->updateType == 1 + $result = $object->update($context->logged_user); } - if (preg_match('/^integer:/i', $object->fields[$key]['type']) && $value == '-1') { - $value = ''; // This is an implicit foreign key field - } - if (!empty($object->fields[$key]['foreignkey']) && $value == '-1') { - $value = ''; // This is an explicit foreign key field - } - - $object->$key = $value; - if (!empty($val['notnull']) && $val['notnull'] > 0 && $object->$key == '' && is_null($val['default'])) { + if ($result < 0) { $error++; - $context->setEventMessages($langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv($val['label'])), null, 'errors'); - } - - // Validation of fields values - if (getDolGlobalInt('MAIN_FEATURES_LEVEL') >= 2 || getDolGlobalString('MAIN_ACTIVATE_VALIDATION_RESULT')) { - if (!$error && !empty($val['validate']) && is_callable(array($object, 'validateField'))) { - if (!$object->validateField($object->fields, $key, $value)) { - $error++; - } - } - } - - if (isModEnabled('category')) { - $categories = GETPOST('categories', 'array:int'); - if (method_exists($object, 'setCategories')) { - $object->setCategories($categories); - } + $context->setEventMessages($object->error, $object->errors, 'errors'); } } - // Fill array 'array_options' with data from add form - //if (!$error) { - // $ret = $extrafields->setOptionalsFromPost(null, $object, '@GETPOSTISSET'); - // if ($ret < 0) { - // $error++; - // } - //} - - if (!$error) { - $result = $object->update($context->logged_user); - if ($result >= 0) { - $action = 'view'; - $urltogo = $backtopage ? str_replace('__ID__', $result, $backtopage) : $backurlforlist; - $urltogo = preg_replace('/--IDFORBACKTOPAGE--/', (string) $object->id, $urltogo); // New method to autoselect project after a New on another form object creation - if ($urltogo && empty($noback)) { - header("Location: " . $urltogo); - exit; - } - } else { - $error++; - // Creation KO - $context->setEventMessages($object->error, $object->errors, 'errors'); - $action = 'edit'; - } - } else { + if ($error) { $action = 'edit'; + } else { + $action = 'view'; + $urltogo = $backtopage ? str_replace('__ID__', (string) $object->id, $backtopage) : $backurlforlist; + $urltogo = preg_replace('/--IDFORBACKTOPAGE--/', (string) $object->id, $urltogo); // New method to autoselect project after a New on another form object creation + if ($urltogo && empty($noback)) { + header("Location: " . $urltogo); + exit; + } } } $this->object = $object; $this->action = $action; } - - /** - * Html for header - * - * @param Context $context Context object - * @return string - */ - protected function header($context) - { - global $langs; - - $html = ''; - - // initialize - $object = $this->object; - $addgendertxt = ''; - //if (property_exists($object, 'gender') && !empty($object->gender)) { - // switch ($object->gender) { - // case 'man': - // $addgendertxt .= ''; - // break; - // case 'woman': - // $addgendertxt .= ''; - // break; - // case 'other': - // $addgendertxt .= ''; - // break; - // } - //} - - $html .= ''; - $html .= '
    '; - - // Left block - begin - $html .= '
    '; - $html .= '
    '; - - // logo or photo - $form = new Form($this->db); - $html .= '
    '; - $html .= '
    '; - $width = (getDolGlobalString('MEMBER_PHOTO_WIDTH_IN_WEBPORTAL') ? getDolGlobalString('MEMBER_PHOTO_WIDTH_IN_WEBPORTAL') : 128); - if (!getDolGlobalString('MEMBER_PHOTO_ALLOW_EXTERNAL_DOWNLOAD')) { - $width = 0; - $object->photo = ''; // to avoid access error - } - $html .= $form->showphoto('memberphoto', $object, $width, 0, 0, 'photowithmargin photoref', 'small', 1, 0, 1); - //include DOL_DOCUMENT_ROOT.'/core/lib/website.lib.php'; - //$html .= getImagePublicURLOfObject($object, 1, '_small'); - $html .= '
    '; - $html .= '
    '; - - // main information - begin - $html .= '
    '; - // ref - $html .= '
    ' . $langs->trans("Ref").' : '.dol_escape_htmltag($object->ref) . '
    '; - // full name - $fullname = ''; - if (method_exists($object, 'getFullName')) { - $fullname = $object->getFullName($langs); - } - $html .= '
    '; - if ($object->element == 'member') { - '@phan-var-force Adherent $object'; - /** @var Adherent $object */ - if ($object->morphy == 'mor' && !empty($object->company)) { - $html .= dol_htmlentities((string) $object->company); - $html .= (!empty($fullname) && $object->company != $fullname) ? ' (' . dol_htmlentities($fullname) . $addgendertxt . ')' : ''; - } else { - $html .= dol_htmlentities($fullname) . $addgendertxt; - if (empty($object->socid)) { - $html .= (!empty($object->company) && $object->company != $fullname) ? ' (' . dol_htmlentities((string) $object->company) . ')' : ''; - } - } - } else { - $html .= dol_htmlentities(!empty($object->ref) ? $object->ref : ''); - } - $html .= '
    '; - // address - if (method_exists($object, 'getBannerAddressForWebPortal')) { - $moreaddress = $object->getBannerAddressForWebPortal('refaddress'); - if ($moreaddress) { - $html .= '
    '; - $html .= $moreaddress; - $html .= '
    '; - } - } - $html .= '
    '; - // main information - end - - $html .= '
    '; - $html .= '
    '; - // Left block - end - - // Right block - begin - $html .= '
    '; - // show status - $htmlStatus = $object->getLibStatut(6); - if (empty($htmlStatus) || $htmlStatus == $object->getLibStatut(3)) { - $htmlStatus = $object->getLibStatut(5); - } - $html .= $htmlStatus; - $html .= '
    '; - // Right block - end - - $html .= '
    '; - - return $html; - } - - /** - * Html for body (view mode) - * @param string $keyforbreak [=''] Key for break left block - * @return string Html for body - */ - protected function bodyView($keyforbreak = '') - { - global $langs; - - $html = ''; - - // initialize - $object = $this->object; - - $object->fields = dol_sort_array($object->fields, 'position'); - - // separate fields to show on the left and on the right - $fieldShowList = array(); - foreach ($object->fields as $key => $val) { - // discard if it's a hidden field on form - if (abs($val['visible']) != 1 && abs($val['visible']) != 3 && abs($val['visible']) != 4 && abs($val['visible']) != 5) { - continue; - } - - if (array_key_exists('enabled', $val) && isset($val['enabled']) && !verifCond($val['enabled'])) { - continue; // we don't want this field - } - - if (!empty($val['showonheader'])) { - continue; // already on header - } - - $fieldShowList[$key] = $val; - } - - $nbFieldShow = count($fieldShowList); - $lastKeyFieldLeft = $keyforbreak; - $lastNumFieldLeft = 0; - if ($lastKeyFieldLeft == '') { - $lastNumFieldLeft = ceil($nbFieldShow / 2); - } - $numField = 0; - $html .= '
    '; - $html .= '
    '; - foreach ($object->fields as $key => $val) { - if (!array_key_exists($key, $fieldShowList)) { - continue; // not to show - } - - $value = $object->$key; - - $html .= '
    '; - - $html .= '
    '; - $labeltoshow = ''; - $labeltoshow .= '' . $langs->trans($val['label']) . ''; - $html .= $labeltoshow; - $html .= '
    '; - - $html .= '
    '; - if ($key == 'lang') { - $langs->load('languages'); - $labellang = ($value ? $langs->trans('Language_' . $value) : ''); - //$html .= picto_from_langcode($value, 'class="paddingrightonly saturatemedium opacitylow"'); - $html .= $labellang; - } else { - $html .= $this->form->showOutputFieldForObject($object, $val, $key, $value, '', '', '', 0); - } - $html .= '
    '; - - $html .= '
    '; - - $numField++; - - // fields on the right - $cardRight = false; - if ($keyforbreak != '') { - if ($key == $keyforbreak) { - $cardRight = true; - } - } else { - if ($numField == $lastNumFieldLeft) { - $cardRight = true; - } - } - if ($cardRight) { - $html .= '
    '; - $html .= '
    '; - } - } - $html .= '
    '; - $html .= '
    '; - - return $html; - } - - /** - * Html for body (edit mode) - * - * @return string - */ - protected function bodyEdit() - { - global $langs; - - $html = ''; - - // initialize - $object = $this->object; - - $object->fields = dol_sort_array($object->fields, 'position'); - - foreach ($object->fields as $key => $val) { - // Discard if filed is a hidden field on form - if (abs($val['visible']) != 1 && abs($val['visible']) != 3 && abs($val['visible']) != 4) { - continue; - } - - if (array_key_exists('enabled', $val) && isset($val['enabled']) && !verifCond($val['enabled'])) { - continue; // We don't want this field - } - - $html .= '
    '; - $html .= '
    '; - $html .= $langs->trans($val['label']); - $html .= '
    '; - - $html .= '
    '; - if (in_array($val['type'], array('int', 'integer'))) { - $value = GETPOSTISSET($key) ? GETPOSTINT($key) : $object->$key; - } elseif ($val['type'] == 'double') { - $value = GETPOSTISSET($key) ? price2num(GETPOST($key, 'alphanohtml')) : $object->$key; - } elseif (preg_match('/^text/', $val['type'])) { - $tmparray = explode(':', $val['type']); - if (!empty($tmparray[1])) { - $check = $tmparray[1]; - } else { - $check = 'nohtml'; - } - $value = GETPOSTISSET($key) ? GETPOST($key, $check) : $object->$key; - } elseif (preg_match('/^html/', $val['type'])) { - $tmparray = explode(':', $val['type']); - if (!empty($tmparray[1])) { - $check = $tmparray[1]; - } else { - $check = 'restricthtml'; - } - $value = GETPOSTISSET($key) ? GETPOST($key, $check) : $object->$key; - } elseif (in_array($val['type'], array('date', 'datetime'))) { - $isPostDate = GETPOSTISSET($key); - $isPostTime = GETPOSTISSET($key . '_time'); - if ($isPostDate) { - $postDate = GETPOST($key, 'alphanohtml'); - if ($isPostTime) { - $postTime = GETPOST($key . '_time', 'alphanohtml') . ':00'; - } else { - $postTime = '00:00:00'; - } - $valueDateTimeStr = $postDate . ' ' . $postTime; - } else { - // format date timestamp to YYYY-MM-DD HH:ii:ss - $valueDateTimeStr = dol_print_date($object->$key, '%Y-%m-%d %H:%M:%S'); - } - - $value = $valueDateTimeStr; - } elseif ($val['type'] == 'price') { - $value = GETPOSTISSET($key) ? price2num(GETPOST($key)) : price2num($object->$key); - } elseif ($key == 'lang') { - $value = GETPOSTISSET($key) ? GETPOST($key, 'aZ09') : $object->lang; - } else { - $value = GETPOSTISSET($key) ? GETPOST($key, 'alphanohtml') : $object->$key; - } - - if (!empty($val['noteditable'])) { - $html .= $this->form->showOutputFieldForObject($object, $val, $key, $value, '', '', '', 0); - //$html .= $object->showOutputFieldForObject($object, $val, $key, $value, '', '', '', 0); - } else { - $html .= $this->form->showInputFieldForObject($object, $val, $key, $value, '', '', '', ''); - //$html .= $object->showInputField($val, $key, $value, '', '', '', ''); - } - $html .= '
    '; - $html .= '
    '; - } - - return $html; - } - - /** - * Html for footer - * - * @return string - */ - protected function footer() - { - $html = ''; - $html .= '
    '; - $html .= '
    '; - - return $html; - } - - /** - * Card for an element in the page context - * - * @param Context $context Context object - * @return string Html output - */ - public function elementCard($context) - { - global $hookmanager, $langs; - - $html = ''; - - // initialize - $action = $this->action; - $backtopage = $this->backtopage; - $backtopageforcancel = $this->backtopageforcancel; - //$elementEn = $this->elementEn; - $id = $this->id; - $object = $this->object; - //$permissiontoread = $this->permissiontoread; - $permissiontoadd = $this->permissiontoadd; - $ref = $this->ref; - $titleKey = $this->titleKey; - $title = $langs->trans($titleKey); - - // Part to edit record - if (($id || $ref) && $action == 'edit') { - $html .= '
    '; - //$html .= load_fiche_titre($title, '', 'object_'.$object->picto); - $html .= '
    '; - $html .= '

    ' . $title . '

    '; - $html .= '
    '; - - $url_file = $context->getControllerUrl($context->controller, '', false); - $html .= '
    '; - $html .= $context->getFormToken(); - $html .= ''; - $html .= ''; - if ($backtopage) { - $html .= ''; - } - if ($backtopageforcancel) { - $html .= ''; - } - - //$html .= ''."\n"; - // Common attributes - //include DOL_DOCUMENT_ROOT.'/core/tpl/commonfields_edit.tpl.php'; - $html .= $this->bodyEdit(); - - // Other attributes - //include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_edit.tpl.php'; - //$html .= '
    '; - - // Save and Cancel buttons - $html .= '
    '; - $html .= '
    '; - $html .= '
    '; - $html .= '
    '; - - $html .= '
    '; - $html .= '
    '; - } - - // Part to show record - if ($object->id > 0 && (empty($action) || ($action != 'edit' && $action != 'create'))) { - $html .= '
    '; - - $formconfirm = ''; - - // Call Hook formConfirm - $parameters = array('formConfirm' => $formconfirm); - $reshook = $hookmanager->executeHooks('formConfirm', $parameters, $object, $action); // Note that $action and $object may have been modified by hook - if (empty($reshook)) { - $formconfirm .= $hookmanager->resPrint; - } elseif ($reshook > 0) { - $formconfirm = $hookmanager->resPrint; - } - - // Print form confirm - $html .= $formconfirm; - - // Object card - // ------------------------------------------------------------ - $html .= $this->header($context); - - // Common attributes - $keyforbreak = ''; - $html .= $this->bodyView($keyforbreak); - - // Other attributes. Fields from hook formObjectOptions and Extrafields. - //include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_view.tpl.php'; - - //$html .= $this->footer(); - $html .= '
    '; - - // Buttons for actions - if ($action != 'presend' && $action != 'editline') { - $html .= '
    ' . "\n"; - $parameters = array(); - $reshook = $hookmanager->executeHooks('addMoreActionsButtons', $parameters, $object, $action); // Note that $action and $object may have been modified by hook - if ($reshook < 0) { - $context->setEventMessages($hookmanager->error, $hookmanager->errors, 'errors'); - } - - if (empty($reshook)) { - if ($permissiontoadd) { - $url_file = $context->getControllerUrl($context->controller, '', false); - $html .= '' . $langs->trans('Modify') . ''; - } - } - $html .= '
    ' . "\n"; - } - } - - return $html; - } } diff --git a/htdocs/webportal/class/html.formwebportal.class.php b/htdocs/webportal/class/html.formwebportal.class.php index 9d90a5883d8..35e3a8f9508 100644 --- a/htdocs/webportal/class/html.formwebportal.class.php +++ b/htdocs/webportal/class/html.formwebportal.class.php @@ -55,41 +55,6 @@ class FormWebPortal extends Form $this->db = $db; } - /** - * Html for input with label - * - * @param string $type Type of input : button, checkbox, color, email, hidden, month, number, password, radio, range, tel, text, time, url, week - * @param string $name Name - * @param string $value [=''] Value - * @param string $id [=''] Id - * @param string $morecss [=''] Class - * @param string $moreparam [=''] Add attributes (checked, required, etc) - * @param string $label [=''] Label - * @param string $addInputLabel [=''] Add label for input - * @return string Html for input with label - */ - public function inputType($type, $name, $value = '', $id = '', $morecss = '', $moreparam = '', $label = '', $addInputLabel = '') - { - $out = ''; - if ($label != '') { - $out .= '