diff --git a/ChangeLog b/ChangeLog
index f52fbb55668..95a4a7a3ae6 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -16,7 +16,6 @@ NEW: A lot of fix into english text after a small proofreading campaign (still n
NEW: All main menu entries are using the picto of the module
NEW: Add a copy to clipboard button on some fields
NEW: Add an example of scheduled job to send email reminder for unpaid invoices
-NEW: Can make massive stock transfers from a CSV file.
NEW: Accountancy - Add FEC import
NEW: Accountancy - Add a confirmation form with options on export
NEW: Accountancy - Add select date from/to in already bind customer and supplier list
@@ -32,58 +31,39 @@ NEW: Add the column "Office phone" and "User mobile" in user list
NEW: Add the column "Price level"in thirdparty list
NEW: Add some company information in the dropdown login menu
NEW: Add constant MAIN_BUGTRACK_URL to set a custom url to redirect to when clicking on link "declare a bug"
-NEW: Add contact tag and bulk email status on the thirparty+contact create form
-NEW: Add db fields note_public and note_private for ECM module
+NEW: Add contact tag and bulk email status on the thirdparty + contact create form
NEW: Support down payment on supplier invoice (& somes ajustments)
NEW: Add edit/delete action icons on categories list pages
NEW: Add hidden option to auto load input line extrafield into new lines
NEW: Add import profile to import BOM
-NEW: Add last date of modification for website pages in the list of pages
NEW: Add link picto to the stock movement on the detail of production
NEW: Add mass action "Set tag" for product/service, user, thirdparty, warehouse, project, bank account, members
NEW: Add bulk action Validate and Set to billed on order list.
-NEW: add bulk action to set a commercial proposal to status "Refused"
-NEW: Add option CONTRACT_ALLOW_EXTERNAL_DOWNLOAD and SUPPLIER_PROPOSAL_ALLOW_EXTERNAL_DOWNLOAD to make generated doc automatically shared.
-NEW: Add option MAIN_SECURITY_ANTI_SSRF_SERVER_IP to define list of IPs that are local IPs
-NEW: Add option SOCIETE_DISABLE_WORKFORCE to hide staff field
-NEW: Add a new permission "Export website"
NEW: Add possibility to exports userGroups fields on user exports
NEW: Add price min and price min including tax into product export
NEW: Can search on lots or serials into the quick search bar
NEW: Add support for Friday as a non working day
NEW: Add a total in page of margin by user
NEW: Add the total of margin in invoice list
-NEW: Can set a Warehouse on a Thirparty
NEW: auto notification with module Notification can use templated emails
NEW: Bank Entries : display user linked to a salary or a taxes
-NEW: Can set a warehouse in a proposal
-NEW: Can clone a cron job.
+NEW: Can clone a cron job
NEW: Can create a lot from the list view (Look and feel standardization).
-NEW: Can filter files in GED on status Shared/Not shared
NEW: Can filter on extrafields date on lists
-NEW: Can filter on rowid in list of blocked logs.
-NEW: Can hide columns "time consumed" on timesheet per week.
+NEW: Can filter on rowid in list of blocked logs
+NEW: Can hide columns "time consumed" on timesheet per week
NEW: Can set an employee on each social contribution
NEW: Can set a percentage when creating an invoice from another object
-NEW: Can set if a ticket group is visible on public interface or not.
NEW: Can set order of execution of hooks
-NEW: Can show the leave dates/holidays on the agenda view.
-NEW: Can show the project ref into PDF documents
+NEW: Can show the leave dates/holidays on the agenda view
NEW: Can toggle FCKeditor on public/private notes
-NEW: Can use captcha on public page to create a ticket
NEW: Check update availability for externals modules using a button on module page
NEW: Choose lines to use while creating intervention card from origin
-NEW: Column shipment method, payment mode, payment term in proposal and order list
+NEW: Columns shipment method, payment mode, payment term in proposal and order list
NEW: Conf for default actioncomm status
NEW: customer ref for product customer prices
-NEW: date and user signature on proposal (Issue 16062)
+NEW: date and user signature on proposal (Issue 16062) #16980
NEW: Dictionary for availability - Add a column position
-NEW: TAKEPOS delayed payment in TakePOS
-NEW: TAKEPOS display date range if exist in TakePOS
-NEW: TAKEPOS display resiliate status in TakePOS for member
-NEW: TAKEPOS Edit sales lines rights in TakePOS
-NEW: TAKEPOS Option for Takepos to show the total price without tax
-NEW: TAKEPOS More permission in TakePOS (Can edit added line, can modify once order sent to kitchen)
NEW: Can set a user related to a social contribution
NEW: ICS Direct debit can be set with a different value for each bank account
NEW: LDAP: usergroup search can be filtered
@@ -94,37 +74,83 @@ NEW: Add bulk actions for Bank Transfer
NEW: Enhance the multicurrency rate editor
NEW: Multiselect ledger account code filter on book keeping list
NEW: Normalyse Type company field with ajax combobox
-NEW: option to automatically close an open project when all its tasks are done (=progress 100%)
-NEW: option to select membership type on the online payment page for membership subscription or renewal
NEW: preload product description on selection for customer propal/order/invoice
NEW: Add a ref in product customer price
-NEW: Save old page with .old extension on disk when editing a website page
NEW: Search usergroups & resources
NEW: Set status of all variants when changing status of parent
NEW: Setup Page for module creation with module builder enhancement #FoundationFunding
NEW: Show picto of module into the list of dictionaries
NEW: Show the total of payment on the payment confirmation page
-NEW: Stock movement list - Add more complete date field
NEW: Support color for types of event
NEW: The global setup for Mandatory fields can now be done on combo list too.
NEW: translate in "en_US" to complete PR 16980
NEW: Update the list of taxes available by default for France
-NEW: Add captcha on public page to create a ticket
NEW: Salary payment request and Salary payment are 2 different steps in workflow on Salary payment recording
NEW: VAT payment request and VAT payment are now 2 different steps in workflow on VAT payment recording
NEW: VAT report - Optimisation & collapse by rate
-NEW: When we add contacts/users to a project, ask to also affect them on tasks.
NEW: When a doc file is shared, link is visible from the main page of doc.
-NEW: Add option in Workflow module to set a shipment as closed.
-NEW: Option to automatically create a login/user when a new subscription of a member is done online
NEW: #16378 More E-Mail Contact substitution Values for better salutation
NEW: option to keep the "Automatically create a total payment" checkbox empty on the tax creation page
-NEW: #17113 Can upload a favicon in website module
+
+ ECM/GED
+NEW: Add db fields note_public and note_private for ECM module
+NEW: Can filter files in GED on status Shared/Not shared
+
+ Members
NEW: #17292 default subscription amount by adherent type
+NEW: Option to automatically create a login/user when a new subscription of a member is done online
+NEW: option to select membership type on the online payment page for membership subscription or renewal
+
+ Projects/Tasks
+NEW: option to automatically close an open project when all its tasks are done (=progress 100%)
+NEW: can show the project ref into PDF documents
+NEW: when we add contacts/users to a project, ask to also affect them on tasks
+
+ Proposals
+NEW: add bulk action to set a commercial proposal to status "Refused"
+NEW: can set a warehouse in a proposal
+
+ Shipment
+NEW: add option in Workflow module to set a shipment as closed
+
+ TakePOS
+NEW: delayed payment in TakePOS #14456?
+NEW: display date range if exist in TakePOS
+NEW: display resiliate status in TakePOS for member
+NEW: edit sales lines rights in TakePOS
+NEW: option for TakePOS to show the total price without tax
+NEW: more permissions in TakePOS (can edit added line, can modify once order sent to kitchen)
+
+ Third-Parties
+NEW: Can set a Warehouse on a Thirdparty
+
+ Tickets
+NEW: can use captcha on public page to create a ticket #16347
+NEW: can set if a ticket group is visible on public interface or not
+
+ Warehouse
+NEW: Can make massive stock transfers from a CSV file
+NEW: Stock movement list - Add more complete date field
+NEW: Can set a Warehouse on a Thirdparty
+NEW: can set a warehouse in a proposal
+
+ Website Module
+NEW: #17113 Can upload a favicon in website module
+NEW: add a new permission "Export website"
+NEW: add last date of modification for website pages in the list of pages
+NEW: Save old page with .old extension on disk when editing a website page
+
+ new Modules
NEW: start new experimental module Event Organization Management
NEW: start new experimental module Partnership Management
NEW: start new experimental module Knowledge Management
-NEW: start new experimental module Workstations management
+NEW: start new experimental module Workstations Management
+
+ new Options
+NEW: Add option CONTRACT_ALLOW_EXTERNAL_DOWNLOAD to make generated doc automatically shared
+NEW: Add option SUPPLIER_PROPOSAL_ALLOW_EXTERNAL_DOWNLOAD to make generated doc automatically shared
+NEW: Add option MAIN_SECURITY_ANTI_SSRF_SERVER_IP to define list of IPs that are local IPs
+NEW: Add option SOCIETE_DISABLE_WORKFORCE to hide staff field
For developers:
@@ -146,8 +172,6 @@ NEW: Support sepa_debit in stripe paymentmethods list
NEW: Update doleditor.class.php for easily activate SCAYT
NEW: Add triggers in the function add_object_linked(), updateObjectLinked() and deleteObjectLinked()
NEW: Add triggers OBJECT_LINK_INSERT, OBJECT_LINK_UPDATE et OBJECT_LINK_DELETE in the function add_object_linked(), updateObjectLinked() and deleteObjectLinked()
-NEW: API Add option $includeifobjectisused to get a product
-NEW: API Get the list of product ids only
NEW: Can set a target image in dolcropresize function.
NEW: Can set a label as placeholder for combo lists.
NEW: Add pagination on Get Products response API
@@ -156,20 +180,25 @@ NEW: Extrafields of documents lines are inside the lines, not any more on separa
NEW: unit selection on object edit line
NEW: #13739 #17390 Product API route added to get product stock and product with or without variants
+ APIs
+NEW: API Add option $includeifobjectisused to get a product
+NEW: API Get the list of product ids only
+
WARNING:
Following changes may create regressions for some external modules, but were necessary to make Dolibarr better:
* Module SimplePOS is deprecated. If you need a POS module, please use the module TakePOS.
-* The ICS value for direct debit or credit transfer is now store on each bank account instead of into the global setup.
+* The ICS value for direct debit or credit transfer is now stored on each bank account instead of into the global setup.
* API /setup/shipment_methods has been replaced with API /setup/shipping_methods
-* Field "total" renamed into to "total_ht" for table llx_facture, llx_facture_rec for better field name consistency
-* Field "tva" renamed into "total_tva" for table llx_propal, llx_supplier_proposal, llx_commande, llx_commande_fournisseur for better field name consistency
-* Field "total" renamed into "total_ttc" for table lx_propal, llx_supplier_proposal for better field name consistency
-* If your database is PostgreSql, you must use version 9.1.0 or more (Dolibarr need the SQL function CONCAT)
-* If your database is MySql or MariaDB, you need at least version 5.1
+* Field "total" renamed into "total_ht" for table llx_facture, llx_facture_rec for better field name consistency
+* Field "tva" renamed into "total_tva" for table llx_propal, llx_supplier_proposal, llx_commande, llx_commande_fournisseur for better field name consistency
+* Field "total" renamed into "total_ttc" for table llx_propal, llx_supplier_proposal for better field name consistency
+* If your database is PostgreSQL, you must use version 9.1.0 or more (Dolibarr need the SQL function CONCAT)
+* If your database is MySQL or MariaDB, you need at least version 5.1
* Function set_price_level() has been renamed into setPriceLevel() to follow camelcase rules
-* Remove deprecated subtituion key __REFCLIENT__ (Replaced with __REF_CLIENT__)
+* removed deprecated subtituion key __REFCLIENT__ (replaced with __REF_CLIENT__)
+
***** ChangeLog for 13.0.3 compared to 13.0.2 *****
@@ -193,7 +222,7 @@ FIX: create sociales : keep values error form
FIX: dol_print_date for %a and %b with some timezone
FIX: email is not case sensitive
FIX: error for duplicate thirdparty found correctly returned by ws
-FIX: Espadon PDF shippment model with long public note now wroking
+FIX: Espadon PDF shippment model with long public note now working
FIX: esupplier order: error 500 when using packaging with product where it is not defined
FIX: Filter on debit/credit
FIX: Filter on supplier payment list
diff --git a/README.md b/README.md
index 736925bec72..ad349f713c1 100644
--- a/README.md
+++ b/README.md
@@ -106,7 +106,7 @@ See the [ChangeLog](https://github.com/Dolibarr/dolibarr/blob/develop/ChangeLog)
- Barcodes
- Batches / Lots / Serials
- Product Variants
-- Bill of Materials
+- Bill of Materials (BOM)
- Manufacturing Orders
Customer/Sales Management
@@ -124,9 +124,10 @@ See the [ChangeLog](https://github.com/Dolibarr/dolibarr/blob/develop/ChangeLog)
Supplier/Purchase Management
- Suppliers/Vendors + Contacts
- Supplier (price) requests
-- Purchase Order management
+- Purchase Orders management
- Delivery/Receiption
- Supplier Invoices/credit notes and payment management
+- INCOTERMS
Finance / Accounting
- Invoices / Payments
@@ -138,25 +139,24 @@ See the [ChangeLog](https://github.com/Dolibarr/dolibarr/blob/develop/ChangeLog)
- Margins
- Reports
-
+ Collaboration
- Shared calendar/agenda (with ical and vcal export for third party tools integration)
- Projects & Tasks management
- Ticket System
+- Surveys
+ HR
- Employee's leave requests management
- Expense reports
- Recruitment management
- Timesheets
-- (around 100 modules available by default, 1000+ on the addon market place)
-
### Other application/modules
- Electronic Document Management (EDM)
- Bookmarks management
- Reporting
-- Surveys
- Data export/import
- Barcodes
- Margin calculations
@@ -165,8 +165,12 @@ See the [ChangeLog](https://github.com/Dolibarr/dolibarr/blob/develop/ChangeLog)
- Mass emailing
- RSS integration
- Skype integration
+- Social platforms linking
- Payment platforms integration (PayPal, Stripe, Paybox...)
--
+- Email-Collector
+
+(around 100 modules available by default, 1000+ on the addon market place)
+
### Other general features
diff --git a/dev/examples/zapier/creates/contact.js b/dev/examples/zapier/creates/contact.js
new file mode 100644
index 00000000000..bcb849ad63d
--- /dev/null
+++ b/dev/examples/zapier/creates/contact.js
@@ -0,0 +1,74 @@
+/*jshint esversion: 6 */
+// create a particular contact by name
+const createContact = async (z, bundle) => {
+ const apiurl = bundle.authData.url + '/api/index.php/contacts';
+
+ const response = await z.request({
+ method: 'POST',
+ url: apiurl,
+ body: {
+ name: bundle.inputData.name,
+ name_alias: bundle.inputData.name_alias,
+ ref_ext: bundle.inputData.ref_ext,
+ ref_int: bundle.inputData.ref_int,
+ address: bundle.inputData.address,
+ zip: bundle.inputData.zip,
+ town: bundle.inputData.town,
+ country_code: bundle.inputData.country_code,
+ country_id: bundle.inputData.country_id,
+ country: bundle.inputData.country,
+ phone: bundle.inputData.phone,
+ email: bundle.inputData.email,
+ sens: 'fromzapier'
+ }
+ });
+ const result = z.JSON.parse(response.content);
+ // api returns an integer when ok, a json when ko
+ return result.response || {id: response};
+};
+
+module.exports = {
+ key: 'contact',
+ noun: 'Contact',
+
+ display: {
+ label: 'Create Contact',
+ description: 'Creates a contact.'
+ },
+
+ operation: {
+ inputFields: [
+ {key: 'name', required: true},
+ {key: 'name_alias', required: false},
+ {key: 'address', required: false},
+ {key: 'zip', required: false},
+ {key: 'town', required: false},
+ {key: 'email', required: false}
+ ],
+ perform: createContact,
+
+ sample: {
+ id: 1,
+ name: 'DUPOND',
+ name_alias: 'DUPOND Ltd',
+ address: 'Rue des Canaries',
+ zip: '34090',
+ town: 'MONTPELLIER',
+ phone: '0123456789',
+ fax: '2345678901',
+ email: 'robot@domain.com'
+ },
+
+ outputFields: [
+ {key: 'id', type: "integer", label: 'ID'},
+ {key: 'name', label: 'Name'},
+ {key: 'name_alias', label: 'Name alias'},
+ {key: 'address', label: 'Address'},
+ {key: 'zip', label: 'Zip'},
+ {key: 'town', label: 'Town'},
+ {key: 'phone', label: 'Phone'},
+ {key: 'fax', label: 'Fax'},
+ {key: 'email', label: 'Email'}
+ ]
+ }
+};
diff --git a/dev/examples/zapier/creates/member.js b/dev/examples/zapier/creates/member.js
new file mode 100644
index 00000000000..152f1129e79
--- /dev/null
+++ b/dev/examples/zapier/creates/member.js
@@ -0,0 +1,74 @@
+/*jshint esversion: 6 */
+// create a particular member by name
+const createMember = async (z, bundle) => {
+ const apiurl = bundle.authData.url + '/api/index.php/members';
+
+ const response = await z.request({
+ method: 'POST',
+ url: apiurl,
+ body: {
+ name: bundle.inputData.name,
+ name_alias: bundle.inputData.name_alias,
+ ref_ext: bundle.inputData.ref_ext,
+ ref_int: bundle.inputData.ref_int,
+ address: bundle.inputData.address,
+ zip: bundle.inputData.zip,
+ town: bundle.inputData.town,
+ country_code: bundle.inputData.country_code,
+ country_id: bundle.inputData.country_id,
+ country: bundle.inputData.country,
+ phone: bundle.inputData.phone,
+ email: bundle.inputData.email,
+ sens: 'fromzapier'
+ }
+ });
+ const result = z.JSON.parse(response.content);
+ // api returns an integer when ok, a json when ko
+ return result.response || {id: response};
+};
+
+module.exports = {
+ key: 'member',
+ noun: 'Member',
+
+ display: {
+ label: 'Create Member',
+ description: 'Creates a member.'
+ },
+
+ operation: {
+ inputFields: [
+ {key: 'name', required: true},
+ {key: 'name_alias', required: false},
+ {key: 'address', required: false},
+ {key: 'zip', required: false},
+ {key: 'town', required: false},
+ {key: 'email', required: false}
+ ],
+ perform: createMember,
+
+ sample: {
+ id: 1,
+ name: 'DUPOND',
+ name_alias: 'DUPOND Ltd',
+ address: 'Rue des Canaries',
+ zip: '34090',
+ town: 'MONTPELLIER',
+ phone: '0123456789',
+ fax: '2345678901',
+ email: 'robot@domain.com'
+ },
+
+ outputFields: [
+ {key: 'id', type: "integer", label: 'ID'},
+ {key: 'name', label: 'Name'},
+ {key: 'name_alias', label: 'Name alias'},
+ {key: 'address', label: 'Address'},
+ {key: 'zip', label: 'Zip'},
+ {key: 'town', label: 'Town'},
+ {key: 'phone', label: 'Phone'},
+ {key: 'fax', label: 'Fax'},
+ {key: 'email', label: 'Email'}
+ ]
+ }
+};
diff --git a/dev/examples/zapier/index.js b/dev/examples/zapier/index.js
index d1897673b39..fdd1ed29a53 100644
--- a/dev/examples/zapier/index.js
+++ b/dev/examples/zapier/index.js
@@ -2,12 +2,18 @@
const triggerAction = require('./triggers/action');
const triggerOrder = require('./triggers/order');
const triggerThirdparty = require('./triggers/thirdparty');
+const triggerContact = require('./triggers/contact');
const triggerTicket = require('./triggers/ticket');
const triggerUser = require('./triggers/user');
+const triggerMember = require('./triggers/member');
const searchThirdparty = require('./searches/thirdparty');
+const searchContact = require('./searches/contact');
+const searchMember = require('./searches/member');
const createThirdparty = require('./creates/thirdparty');
+const createContact = require('./creates/contact');
+const createMember = require('./creates/member');
const {
config: authentication,
@@ -62,18 +68,24 @@ const App = {
[triggerAction.key]: triggerAction,
[triggerOrder.key]: triggerOrder,
[triggerThirdparty.key]: triggerThirdparty,
+ [triggerContact.key]: triggerContact,
[triggerTicket.key]: triggerTicket,
[triggerUser.key]: triggerUser,
+ [triggerMember.key]: triggerMember,
},
// If you want your searches to show up, you better include it here!
searches: {
[searchThirdparty.key]: searchThirdparty,
+ [searchContact.key]: searchContact,
+ [searchMember.key]: searchMember,
},
// If you want your creates to show up, you better include it here!
creates: {
[createThirdparty.key]: createThirdparty,
+ [createContact.key]: createContact,
+ [createMember.key]: createMember,
}
};
diff --git a/dev/examples/zapier/package.json b/dev/examples/zapier/package.json
index 4d5c5daa867..a9d519dec69 100644
--- a/dev/examples/zapier/package.json
+++ b/dev/examples/zapier/package.json
@@ -1,6 +1,6 @@
{
"name": "dolibarr",
- "version": "1.13.0",
+ "version": "1.14.0",
"description": "An app for connecting Dolibarr to the Zapier platform.",
"repository": "Dolibarr/dolibarr",
"homepage": "https://www.dolibarr.org/",
@@ -11,7 +11,7 @@
"test": "mocha --recursive"
},
"engines": {
- "node": "8.10.0",
+ "node": "14.0.0",
"npm": ">=5.6.0"
},
"dependencies": {
diff --git a/dev/examples/zapier/searches/contact.js b/dev/examples/zapier/searches/contact.js
new file mode 100644
index 00000000000..b52b8d3e367
--- /dev/null
+++ b/dev/examples/zapier/searches/contact.js
@@ -0,0 +1,95 @@
+module.exports = {
+ key: 'contact',
+
+ // You'll want to provide some helpful display labels and descriptions
+ // for users. Zapier will put them into the UX.
+ noun: 'Contact',
+ display: {
+ label: 'Find a Contact',
+ description: 'Search for contact.'
+ },
+
+ // `operation` is where we make the call to your API to do the search
+ operation: {
+ // This search only has one search field. Your searches might have just one, or many
+ // search fields.
+ inputFields: [
+ {
+ key: 'lastname',
+ type: 'string',
+ label: 'Lastname',
+ helpText: 'Lastname to limit to the search to (i.e. The company or %company%).'
+ },
+ {
+ key: 'email',
+ type: 'string',
+ label: 'Email',
+ helpText: 'Email to limit to the search to.'
+ }
+ ],
+
+ perform: async (z, bundle) => {
+ const url = bundle.authData.url + '/api/index.php/contacts/';
+
+ // Put the search value in a query param. The details of how to build
+ // a search URL will depend on how your API works.
+ let filter = '';
+ if (bundle.inputData.lastname) {
+ filter = "t.lastname like \'%"+bundle.inputData.name+"%\'";
+ }
+ if (bundle.inputData.email) {
+ if (bundle.inputData.lastname) {
+ filter += " and ";
+ }
+ filter += "t.email like \'"+bundle.inputData.email+"\'";
+ }
+ const response = await z.request({
+ url: url,
+ // this parameter avoid throwing errors and let us manage them
+ skipThrowForStatus: true,
+ params: {
+ sqlfilters: filter
+ }
+ });
+ //z.console.log(response);
+ if (response.status != 200) {
+ return [];
+ }
+ return response.json;
+ },
+
+ // In cases where Zapier needs to show an example record to the user, but we are unable to get a live example
+ // from the API, Zapier will fallback to this hard-coded sample. It should reflect the data structure of
+ // returned records, and have obviously dummy values that we can show to any user.
+ sample: {
+ id: 1,
+ createdAt: 1472069465,
+ name: 'DOE',
+ firstname: 'John',
+ authorId: 1,
+ directions: '1. Boil Noodles\n2.Serve with sauce',
+ style: 'italian'
+ },
+
+ // If the resource can have fields that are custom on a per-user basis, define a function to fetch the custom
+ // field definitions. The result will be used to augment the sample.
+ // outputFields: () => { return []; }
+ // Alternatively, a static field definition should be provided, to specify labels for the fields
+ outputFields: [
+ {
+ key: 'id',
+ type: "integer",
+ label: 'ID'
+ },
+ {key: 'createdAt', type: "integer", label: 'Created At'},
+ {key: 'name', label: 'Name'},
+ {key: 'firstname', label: 'Firstname'},
+ {key: 'directions', label: 'Directions'},
+ {key: 'authorId', type: "integer", label: 'Author ID'},
+ {
+ key: 'style',
+ label: 'Style'
+ }
+ ]
+ }
+};
diff --git a/dev/examples/zapier/searches/member.js b/dev/examples/zapier/searches/member.js
new file mode 100644
index 00000000000..f1a84061146
--- /dev/null
+++ b/dev/examples/zapier/searches/member.js
@@ -0,0 +1,88 @@
+module.exports = {
+ key: 'member',
+
+ // You'll want to provide some helpful display labels and descriptions
+ // for users. Zapier will put them into the UX.
+ noun: 'Member',
+ display: {
+ label: 'Find a Member',
+ description: 'Search for member.'
+ },
+
+ // `operation` is where we make the call to your API to do the search
+ operation: {
+ // This search only has one search field. Your searches might have just one, or many
+ // search fields.
+ inputFields: [
+ {
+ key: 'lastname',
+ type: 'string',
+ label: 'Lastname',
+ helpText: 'Lastname to limit to the search to (i.e. The company or %company%).'
+ },
+ {
+ key: 'email',
+ type: 'string',
+ label: 'Email',
+ helpText: 'Email to limit to the search to.'
+ }
+ ],
+
+ perform: async (z, bundle) => {
+ const url = bundle.authData.url + '/api/index.php/members/';
+
+ // Put the search value in a query param. The details of how to build
+ // a search URL will depend on how your API works.
+ let filter = '';
+ if (bundle.inputData.lastname) {
+ filter = "t.lastname like \'%" + bundle.inputData.name + "%\'";
+ }
+ if (bundle.inputData.email) {
+ if (bundle.inputData.lastname) {
+ filter += " and ";
+ }
+ filter += "t.email like \'" + bundle.inputData.email + "\'";
+ }
+ const response = await z.request({
+ url: url,
+ // this parameter avoid throwing errors and let us manage them
+ skipThrowForStatus: true,
+ params: {
+ sqlfilters: filter
+ }
+ });
+ //z.console.log(response);
+ if (response.status != 200) {
+ return [];
+ }
+ return response.json;
+ },
+
+ // In cases where Zapier needs to show an example record to the user, but we are unable to get a live example
+ // from the API, Zapier will fallback to this hard-coded sample. It should reflect the data structure of
+ // returned records, and have obviously dummy values that we can show to any user.
+ sample: {
+ id: 1,
+ createdAt: 1472069465,
+ name: 'DOE',
+ firstname: 'John',
+ authorId: 1,
+ },
+
+ // If the resource can have fields that are custom on a per-user basis, define a function to fetch the custom
+ // field definitions. The result will be used to augment the sample.
+ // outputFields: () => { return []; }
+ // Alternatively, a static field definition should be provided, to specify labels for the fields
+ outputFields: [
+ {
+ key: 'id',
+ type: "integer",
+ label: 'ID'
+ },
+ { key: 'createdAt', type: "integer", label: 'Created At' },
+ { key: 'name', label: 'Name' },
+ { key: 'firstname', label: 'Firstname' },
+ { key: 'authorId', type: "integer", label: 'Author ID' },
+ ]
+ }
+};
diff --git a/dev/examples/zapier/searches/thirdparty.js b/dev/examples/zapier/searches/thirdparty.js
index 8f72b9270e5..e1e6878f5b3 100644
--- a/dev/examples/zapier/searches/thirdparty.js
+++ b/dev/examples/zapier/searches/thirdparty.js
@@ -19,21 +19,43 @@ module.exports = {
type: 'string',
label: 'Name',
helpText: 'Name to limit to the search to (i.e. The company or %company%).'
+ },
+ {
+ key: 'email',
+ type: 'string',
+ label: 'Email',
+ helpText: 'Email to limit to the search to.'
}
],
- perform: (z, bundle) => {
+ perform: async (z, bundle) => {
const url = bundle.authData.url + '/api/index.php/thirdparties/';
// Put the search value in a query param. The details of how to build
// a search URL will depend on how your API works.
- const options = {
- params: {
- sqlfilters: "t.nom like \'%"+bundle.inputData.name+"%\'"
+ let filter = '';
+ if (bundle.inputData.name) {
+ filter = "t.nom like \'%"+bundle.inputData.name+"%\'";
+ }
+ if (bundle.inputData.email) {
+ if (bundle.inputData.name) {
+ filter += " and ";
}
- };
-
- return z.request(url, options).then(response => JSON.parse(response.content));
+ filter += "t.email like \'"+bundle.inputData.email+"\'";
+ }
+ const response = await z.request({
+ url: url,
+ // this parameter avoid throwing errors and let us manage them
+ skipThrowForStatus: true,
+ params: {
+ sqlfilters: filter
+ }
+ });
+ //z.console.log(response);
+ if (response.status != 200) {
+ return [];
+ }
+ return response.json;
},
// In cases where Zapier needs to show an example record to the user, but we are unable to get a live example
diff --git a/dev/examples/zapier/triggers/contact.js b/dev/examples/zapier/triggers/contact.js
new file mode 100644
index 00000000000..2ba3bd226f8
--- /dev/null
+++ b/dev/examples/zapier/triggers/contact.js
@@ -0,0 +1,171 @@
+const subscribeHook = (z, bundle) => {
+ // `z.console.log()` is similar to `console.log()`.
+ z.console.log('suscribing hook!');
+
+ // bundle.targetUrl has the Hook URL this app should call when an action is created.
+ const data = {
+ url: bundle.targetUrl,
+ event: bundle.event,
+ module: 'contact',
+ action: bundle.inputData.action
+ };
+
+ const url = bundle.authData.url + '/api/index.php/zapierapi/hook';
+
+ // You can build requests and our client will helpfully inject all the variables
+ // you need to complete. You can also register middleware to control this.
+ const options = {
+ url: url,
+ method: 'POST',
+ body: data,
+ };
+
+ // You may return a promise or a normal data structure from any perform method.
+ return z.request(options).then((response) => JSON.parse(response.content));
+};
+
+const unsubscribeHook = (z, bundle) => {
+ // bundle.subscribeData contains the parsed response JSON from the subscribe
+ // request made initially.
+ z.console.log('unsuscribing hook!');
+
+ // You can build requests and our client will helpfully inject all the variables
+ // you need to complete. You can also register middleware to control this.
+ const options = {
+ url: bundle.authData.url + '/api/index.php/zapierapi/hook/' + bundle.subscribeData.id,
+ method: 'DELETE',
+ };
+
+ // You may return a promise or a normal data structure from any perform method.
+ return z.request(options).then((response) => JSON.parse(response.content));
+};
+
+const getContact = (z, bundle) => {
+ // bundle.cleanedRequest will include the parsed JSON object (if it's not a
+ // test poll) and also a .querystring property with the URL's query string.
+ const contact = {
+ id: bundle.cleanedRequest.id,
+ name: bundle.cleanedRequest.name,
+ name_alias: bundle.cleanedRequest.name_alias,
+ firstname: bundle.cleanedRequest.firstname,
+ address: bundle.cleanedRequest.address,
+ zip: bundle.cleanedRequest.zip,
+ town: bundle.cleanedRequest.town,
+ email: bundle.cleanedRequest.email,
+ phone_pro: bundle.cleanedRequest.phone_pro,
+ phone_perso: bundle.cleanedRequest.phone_perso,
+ phone_mobile: bundle.cleanedRequest.phone_mobile,
+ authorId: bundle.cleanedRequest.authorId,
+ createdAt: bundle.cleanedRequest.createdAt,
+ action: bundle.cleanedRequest.action
+ };
+
+ return [contact];
+};
+
+const getFallbackRealContact = (z, bundle) => {
+ // For the test poll, you should get some real data, to aid the setup process.
+ const module = bundle.inputData.module;
+ const options = {
+ url: bundle.authData.url + '/api/index.php/contacts/0',
+ };
+
+ return z.request(options).then((response) => [JSON.parse(response.content)]);
+};
+
+// const getModulesChoices = (z/*, bundle*/) => {
+// // For the test poll, you should get some real data, to aid the setup process.
+// const options = {
+// url: bundle.authData.url + '/api/index.php/zapierapi/getmoduleschoices',
+// };
+
+// return z.request(options).then((response) => JSON.parse(response.content));
+// };
+// const getModulesChoices = () => {
+// return {
+// orders: "Order",
+// invoices: "Invoice",
+// contacts: "Contact",
+// contacts: "Contacts"
+// };
+// };
+
+// const getActionsChoices = (z, bundle) => {
+// // For the test poll, you should get some real data, to aid the setup process.
+// const module = bundle.inputData.module;
+// const options = {
+// url: url: bundle.authData.url + '/api/index.php/zapierapi/getactionschoices/thirparty`,
+// };
+
+// return z.request(options).then((response) => JSON.parse(response.content));
+// };
+
+// We recommend writing your triggers separate like this and rolling them
+// into the App definition at the end.
+module.exports = {
+ key: 'contact',
+
+ // You'll want to provide some helpful display labels and descriptions
+ // for users. Zapier will put them into the UX.
+ noun: 'Contact',
+ display: {
+ label: 'New Contact',
+ description: 'Triggers when a new contact action is done in Dolibarr.'
+ },
+
+ // `operation` is where the business logic goes.
+ operation: {
+
+ // `inputFields` can define the fields a user could provide,
+ // we'll pass them in as `bundle.inputData` later.
+ inputFields: [
+ {
+ key: 'action',
+ required: true,
+ type: 'string',
+ helpText: 'Which action of contact this should trigger on.',
+ choices: {
+ create: "Create",
+ modify: "Modify",
+ validate: "Validate",
+ }
+ }
+ ],
+
+ type: 'hook',
+
+ performSubscribe: subscribeHook,
+ performUnsubscribe: unsubscribeHook,
+
+ perform: getContact,
+ performList: getFallbackRealContact,
+
+ // In cases where Zapier needs to show an example record to the user, but we are unable to get a live example
+ // from the API, Zapier will fallback to this hard-coded sample. It should reflect the data structure of
+ // returned records, and have obviously dummy values that we can show to any user.
+ sample: {
+ id: 1,
+ createdAt: 1472069465,
+ lastname: 'DOE',
+ firstname: 'John',
+ authorId: 1,
+ action: 'create'
+ },
+
+ // If the resource can have fields that are custom on a per-user basis, define a function to fetch the custom
+ // field definitions. The result will be used to augment the sample.
+ // outputFields: () => { return []; }
+ // Alternatively, a static field definition should be provided, to specify labels for the fields
+ outputFields: [
+ {key: 'id', type: "integer", label: 'ID'},
+ {key: 'createdAt', label: 'Created At'},
+ {key: 'lastname', label: 'Lastname'},
+ {key: 'firstname', label: 'Firstname'},
+ {key: 'phone', label: 'Phone pro'},
+ {key: 'phone_perso', label: 'Phone perso'},
+ {key: 'phone_mobile', label: 'Phone mobile'},
+ {key: 'authorId', type: "integer", label: 'Author ID'},
+ {key: 'action', label: 'Action'}
+ ]
+ }
+};
diff --git a/dev/examples/zapier/triggers/member.js b/dev/examples/zapier/triggers/member.js
new file mode 100644
index 00000000000..3385cdca625
--- /dev/null
+++ b/dev/examples/zapier/triggers/member.js
@@ -0,0 +1,171 @@
+const subscribeHook = (z, bundle) => {
+ // `z.console.log()` is similar to `console.log()`.
+ z.console.log('suscribing hook!');
+
+ // bundle.targetUrl has the Hook URL this app should call when an action is created.
+ const data = {
+ url: bundle.targetUrl,
+ event: bundle.event,
+ module: 'member',
+ action: bundle.inputData.action
+ };
+
+ const url = bundle.authData.url + '/api/index.php/zapierapi/hook';
+
+ // You can build requests and our client will helpfully inject all the variables
+ // you need to complete. You can also register middleware to control this.
+ const options = {
+ url: url,
+ method: 'POST',
+ body: data,
+ };
+
+ // You may return a promise or a normal data structure from any perform method.
+ return z.request(options).then((response) => JSON.parse(response.content));
+};
+
+const unsubscribeHook = (z, bundle) => {
+ // bundle.subscribeData contains the parsed response JSON from the subscribe
+ // request made initially.
+ z.console.log('unsuscribing hook!');
+
+ // You can build requests and our client will helpfully inject all the variables
+ // you need to complete. You can also register middleware to control this.
+ const options = {
+ url: bundle.authData.url + '/api/index.php/zapierapi/hook/' + bundle.subscribeData.id,
+ method: 'DELETE',
+ };
+
+ // You may return a promise or a normal data structure from any perform method.
+ return z.request(options).then((response) => JSON.parse(response.content));
+};
+
+const getMember = (z, bundle) => {
+ // bundle.cleanedRequest will include the parsed JSON object (if it's not a
+ // test poll) and also a .querystring property with the URL's query string.
+ const member = {
+ id: bundle.cleanedRequest.id,
+ name: bundle.cleanedRequest.name,
+ name_alias: bundle.cleanedRequest.name_alias,
+ firstname: bundle.cleanedRequest.firstname,
+ address: bundle.cleanedRequest.address,
+ zip: bundle.cleanedRequest.zip,
+ town: bundle.cleanedRequest.town,
+ email: bundle.cleanedRequest.email,
+ phone_pro: bundle.cleanedRequest.phone_pro,
+ phone_perso: bundle.cleanedRequest.phone_perso,
+ phone_mobile: bundle.cleanedRequest.phone_mobile,
+ authorId: bundle.cleanedRequest.authorId,
+ createdAt: bundle.cleanedRequest.createdAt,
+ action: bundle.cleanedRequest.action
+ };
+
+ return [member];
+};
+
+const getFallbackRealMember = (z, bundle) => {
+ // For the test poll, you should get some real data, to aid the setup process.
+ const module = bundle.inputData.module;
+ const options = {
+ url: bundle.authData.url + '/api/index.php/members/0',
+ };
+
+ return z.request(options).then((response) => [JSON.parse(response.content)]);
+};
+
+// const getModulesChoices = (z/*, bundle*/) => {
+// // For the test poll, you should get some real data, to aid the setup process.
+// const options = {
+// url: bundle.authData.url + '/api/index.php/zapierapi/getmoduleschoices',
+// };
+
+// return z.request(options).then((response) => JSON.parse(response.content));
+// };
+// const getModulesChoices = () => {
+// return {
+// orders: "Order",
+// invoices: "Invoice",
+// members: "Member",
+// members: "Members"
+// };
+// };
+
+// const getActionsChoices = (z, bundle) => {
+// // For the test poll, you should get some real data, to aid the setup process.
+// const module = bundle.inputData.module;
+// const options = {
+// url: url: bundle.authData.url + '/api/index.php/zapierapi/getactionschoices/thirparty`,
+// };
+
+// return z.request(options).then((response) => JSON.parse(response.content));
+// };
+
+// We recommend writing your triggers separate like this and rolling them
+// into the App definition at the end.
+module.exports = {
+ key: 'member',
+
+ // You'll want to provide some helpful display labels and descriptions
+ // for users. Zapier will put them into the UX.
+ noun: 'Member',
+ display: {
+ label: 'New Member',
+ description: 'Triggers when a new member action is done in Dolibarr.'
+ },
+
+ // `operation` is where the business logic goes.
+ operation: {
+
+ // `inputFields` can define the fields a user could provide,
+ // we'll pass them in as `bundle.inputData` later.
+ inputFields: [
+ {
+ key: 'action',
+ required: true,
+ type: 'string',
+ helpText: 'Which action of member this should trigger on.',
+ choices: {
+ create: "Create",
+ modify: "Modify",
+ validate: "Validate",
+ }
+ }
+ ],
+
+ type: 'hook',
+
+ performSubscribe: subscribeHook,
+ performUnsubscribe: unsubscribeHook,
+
+ perform: getMember,
+ performList: getFallbackRealMember,
+
+ // In cases where Zapier needs to show an example record to the user, but we are unable to get a live example
+ // from the API, Zapier will fallback to this hard-coded sample. It should reflect the data structure of
+ // returned records, and have obviously dummy values that we can show to any user.
+ sample: {
+ id: 1,
+ createdAt: 1472069465,
+ lastname: 'DOE',
+ firstname: 'John',
+ authorId: 1,
+ action: 'create'
+ },
+
+ // If the resource can have fields that are custom on a per-user basis, define a function to fetch the custom
+ // field definitions. The result will be used to augment the sample.
+ // outputFields: () => { return []; }
+ // Alternatively, a static field definition should be provided, to specify labels for the fields
+ outputFields: [
+ {key: 'id', type: "integer", label: 'ID'},
+ {key: 'createdAt', label: 'Created At'},
+ {key: 'lastname', label: 'Lastname'},
+ {key: 'firstname', label: 'Firstname'},
+ {key: 'phone', label: 'Phone pro'},
+ {key: 'phone_perso', label: 'Phone perso'},
+ {key: 'phone_mobile', label: 'Phone mobile'},
+ {key: 'authorId', type: "integer", label: 'Author ID'},
+ {key: 'action', label: 'Action'}
+ ]
+ }
+};
diff --git a/dev/examples/zapier/triggers/thirdparty.js b/dev/examples/zapier/triggers/thirdparty.js
index 0fecd4434ce..76194acbc9a 100644
--- a/dev/examples/zapier/triggers/thirdparty.js
+++ b/dev/examples/zapier/triggers/thirdparty.js
@@ -118,7 +118,7 @@ module.exports = {
noun: 'Thirdparty',
display: {
label: 'New Thirdparty',
- description: 'Triggers when a new thirdpaty action is done in Dolibarr.'
+ description: 'Triggers when a new thirdparty action is done in Dolibarr.'
},
// `operation` is where the business logic goes.
diff --git a/doc/install/README b/doc/install/README
index 0c7341b196c..0192ff27521 100644
--- a/doc/install/README
+++ b/doc/install/README
@@ -19,7 +19,7 @@ Download
Install
--------------------------------
-* For a Quick guide, take a look at README file into root directory.
+* For a Quick guide, take a look at README.md file into root directory.
* More complete documentations are also available on line on the Dolibarr Wiki:
https://wiki.dolibarr.org
diff --git a/doc/install/README-DE b/doc/install/README-DE
index 047e1915d18..f4cb3c1a6f5 100644
--- a/doc/install/README-DE
+++ b/doc/install/README-DE
@@ -23,7 +23,7 @@ Download / Herunterladen
Installation / Hilfe
------------------------------------
-* Für eine kurze Einleitung schau in die README Datei im Hauptverzeichnis.
+* Für eine kurze Einleitung schau in die README.md Datei im Hauptverzeichnis.
* Umfangreiche Dokumentationen sind im Dolibarr Wiki zu finden:
https://wiki.dolibarr.org/index.php/Hauptseite
diff --git a/doc/install/README-FR b/doc/install/README-FR
index 5872ec0e3d1..c362316b39c 100644
--- a/doc/install/README-FR
+++ b/doc/install/README-FR
@@ -20,9 +20,7 @@ https://www.dolistore.com
Documentation utilisateur
--------------------------------
-* Pour une prise en main et installation rapide, consultez le fichier
-README-FR à la racine.
+* Pour une prise en main et installation rapide, consultez le fichier README-FR.md à la racine.
-* Une documentation utilisateur francophone plus consistante est disponible en
-ligne sur le wiki de Dolibarr à l'adresse:
-https://wiki.dolibarr.org/index.php/Accueil
+* Une documentation utilisateur francophone plus consistante est disponible en ligne sur le wiki de Dolibarr à l'adresse:
+ https://wiki.dolibarr.org
diff --git a/doc/user/README b/doc/user/README
index 129dff11058..ecde765cde3 100644
--- a/doc/user/README
+++ b/doc/user/README
@@ -4,4 +4,5 @@ User guide
--------------------------------
* All Dolibarr guides are available, on line, on the Dolibarr Web site:
+
https://www.dolibarr.org
diff --git a/doc/user/README-DE.md b/doc/user/README-DE
similarity index 99%
rename from doc/user/README-DE.md
rename to doc/user/README-DE
index 4c7a635a522..336e7ab7085 100644
--- a/doc/user/README-DE.md
+++ b/doc/user/README-DE
@@ -7,9 +7,7 @@ Benutzeranleitung
Alle Dolibarr-Informationen sind online verfuegbar ueber die Webseiten:
-
https://www.dolibarr.de (de) oder https://www.dolibarr.org (intl)
https://wiki.dolibarr.org/index.php/Hauptseite (de)
-
diff --git a/doc/user/README-FR b/doc/user/README-FR
index fbf67fd89bc..f5cb72ea1c6 100644
--- a/doc/user/README-FR
+++ b/doc/user/README-FR
@@ -3,9 +3,6 @@ README (french)
Documentation utilisateur
--------------------------------
-* Pour une prise en main et installation rapide, consultez le fichier
-README-FR à la racine.
+La documentation utilisateur francophone est disponible en ligne sur le site Web de Dolibarr à l'adresse:
-* Une documentation utilisateur francophone plus consistante est disponible en
-ligne sur le site Web de Dolibarr à l'adresse:
https://www.dolibarr.fr
diff --git a/htdocs/accountancy/bookkeeping/balance.php b/htdocs/accountancy/bookkeeping/balance.php
index a35333020cd..7d6d71d49a8 100644
--- a/htdocs/accountancy/bookkeeping/balance.php
+++ b/htdocs/accountancy/bookkeeping/balance.php
@@ -401,14 +401,14 @@ if ($action != 'export_csv') {
print '
';
print '
'.$langs->trans("SubTotal").':
';
if (!empty($conf->global->ACCOUNTANCY_SHOW_OPENING_BALANCE)) {
- print '
'.price($sous_total_opening_balance).'
';
+ print '
'.price($sous_total_opening_balance).'
';
}
- print '
'.price($sous_total_debit).'
';
- print '
'.price($sous_total_credit).'
';
+ print '
'.price($sous_total_debit).'
';
+ print '
'.price($sous_total_credit).'
';
if (!empty($conf->global->ACCOUNTANCY_SHOW_OPENING_BALANCE)) {
- print '
';
- // TODO For the moment we keep a free input text instead of a combo. The select_auxaccount has problem because:
- // It does not use the setup of "key pressed" to select a thirdparty and this hang browser on large databases.
- // Also, it is not possible to use a value that is not in the list.
- // Also, the label is not automatically filled when a value is selected.
- if (!empty($conf->global->ACCOUNTANCY_COMBO_FOR_AUX)) {
- print $formaccounting->select_auxaccount('', 'subledger_account', 1);
- } else {
- print '';
+ if (empty($object->date_export) || empty($object->date_validation)) {
+ if ($action == "" || $action == 'add') {
+ print '
';
+ // TODO For the moment we keep a free input text instead of a combo. The select_auxaccount has problem because:
+ // It does not use the setup of "key pressed" to select a thirdparty and this hang browser on large databases.
+ // Also, it is not possible to use a value that is not in the list.
+ // Also, the label is not automatically filled when a value is selected.
+ if (!empty($conf->global->ACCOUNTANCY_COMBO_FOR_AUX)) {
+ print $formaccounting->select_auxaccount('', 'subledger_account', 1);
+ } else {
+ print '';
+ }
+ print ' ';
+ print '
';
if ($conf->global->GEOIP_VERSION == 'php') {
print 'Using geoip PHP internal functions. Value must be '.geoip_db_filename(GEOIP_COUNTRY_EDITION).' or '.geoip_db_filename(GEOIP_CITY_EDITION_REV1).' or /pathtodatafile/GeoLite2-Country.mmdb ';
}
-print '';
+print '';
print '
';
- $this->info_box_contents[][]=array(
+ $this->info_box_contents[][] = array(
'td' => 'class="center"',
'text' => $stringtoprint
);
diff --git a/htdocs/core/boxes/box_mos.php b/htdocs/core/boxes/box_mos.php
index 43d1cd411e4..18a19da93dc 100644
--- a/htdocs/core/boxes/box_mos.php
+++ b/htdocs/core/boxes/box_mos.php
@@ -97,7 +97,7 @@ class box_mos extends ModeleBoxes
$sql .= " WHERE c.fk_product = p.rowid";
$sql .= " AND c.entity = ".$conf->entity;
$sql .= " ORDER BY c.tms DESC, c.ref DESC";
- $sql .= " ".$this->db->plimit($max, 0);
+ $sql .= $this->db->plimit($max, 0);
$result = $this->db->query($sql);
if ($result) {
diff --git a/htdocs/core/class/CSMSFile.class.php b/htdocs/core/class/CSMSFile.class.php
index ca221a366ed..8d5bcf7dc50 100644
--- a/htdocs/core/class/CSMSFile.class.php
+++ b/htdocs/core/class/CSMSFile.class.php
@@ -83,7 +83,7 @@ class CSMSFile
return -1;
}
- dol_syslog("CSMSFile::CSMSFile: MAIN_SMS_SENDMODE=".$conf->global->MAIN_SMS_SENDMODE." charset=".$conf->file->character_set_client." from=".$from.", to=".$to.", msg length=".count($msg), LOG_DEBUG);
+ dol_syslog("CSMSFile::CSMSFile: MAIN_SMS_SENDMODE=".$conf->global->MAIN_SMS_SENDMODE." charset=".$conf->file->character_set_client." from=".$from.", to=".$to.", msg length=".strlen($msg), LOG_DEBUG);
dol_syslog("CSMSFile::CSMSFile: deferred=".$deferred." priority=".$priority." class=".$class, LOG_DEBUG);
// Action according to choosed sending method
diff --git a/htdocs/core/class/commonobject.class.php b/htdocs/core/class/commonobject.class.php
index f3c68711edd..c927b833c71 100644
--- a/htdocs/core/class/commonobject.class.php
+++ b/htdocs/core/class/commonobject.class.php
@@ -505,8 +505,8 @@ abstract class CommonObject
/**
* @var array List of child tables. To know object to delete on cascade.
- * If name matches '@ClassNAme:FilePathClass;ParentFkFieldName' it will
- * call method deleteByParentField(parentId, ParentFkFieldName) to fetch and delete child object
+ * If name is like '@ClassName:FilePathClass:ParentFkFieldName', it will
+ * call method deleteByParentField(parentId, ParentFkFieldName) to fetch and delete child object.
*/
protected $childtablesoncascade = array();
@@ -3541,17 +3541,16 @@ abstract class CommonObject
if (empty($nodatabaseupdate)) {
$sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element.' SET';
- $sql .= " ".$fieldht."='".price2num($this->total_ht)."',";
- $sql .= " ".$fieldtva."='".price2num($this->total_tva)."',";
- $sql .= " ".$fieldlocaltax1."='".price2num($this->total_localtax1)."',";
- $sql .= " ".$fieldlocaltax2."='".price2num($this->total_localtax2)."',";
- $sql .= " ".$fieldttc."='".price2num($this->total_ttc)."'";
- $sql .= ", multicurrency_total_ht='".price2num($this->multicurrency_total_ht, 'MT', 1)."'";
- $sql .= ", multicurrency_total_tva='".price2num($this->multicurrency_total_tva, 'MT', 1)."'";
- $sql .= ", multicurrency_total_ttc='".price2num($this->multicurrency_total_ttc, 'MT', 1)."'";
+ $sql .= " ".$fieldht." = ".price2num($this->total_ht).",";
+ $sql .= " ".$fieldtva." = ".price2num($this->total_tva).",";
+ $sql .= " ".$fieldlocaltax1." = ".price2num($this->total_localtax1).",";
+ $sql .= " ".$fieldlocaltax2." = ".price2num($this->total_localtax2).",";
+ $sql .= " ".$fieldttc." = ".price2num($this->total_ttc);
+ $sql .= ", multicurrency_total_ht = ".price2num($this->multicurrency_total_ht, 'MT', 1);
+ $sql .= ", multicurrency_total_tva = ".price2num($this->multicurrency_total_tva, 'MT', 1);
+ $sql .= ", multicurrency_total_ttc = ".price2num($this->multicurrency_total_ttc, 'MT', 1);
$sql .= ' WHERE rowid = '.$this->id;
-
dol_syslog(get_class($this)."::update_price", LOG_DEBUG);
$resql = $this->db->query($sql);
if (!$resql) {
@@ -6625,6 +6624,7 @@ abstract class CommonObject
} else {
$InfoFieldList[4] = str_replace('$ID$', '0', $InfoFieldList[4]);
}
+
//We have to join on extrafield table
if (strpos($InfoFieldList[4], 'extra') !== false) {
$sql .= ' as main, '.MAIN_DB_PREFIX.$InfoFieldList[0].'_extrafields as extra';
@@ -6972,10 +6972,11 @@ abstract class CommonObject
$form = new Form($this->db);
}
- $objectid = $this->id;
- $label = $val['label'];
- $type = $val['type'];
- $size = $val['css'];
+ $objectid = $this->id; // Not used ???
+
+ $label = empty($val['label']) ? '' : $val['label'];
+ $type = empty($val['type']) ? '' : $val['type'];
+ $size = empty($val['css']) ? '' : $val['css'];
$reg = array();
// Convert var to be able to share same code than showOutputField of extrafields
@@ -6992,10 +6993,10 @@ abstract class CommonObject
$type = 'link';
}
- $default = $val['default'];
- $computed = $val['computed'];
- $unique = $val['unique'];
- $required = $val['required'];
+ $default = empty($val['default']) ? '' : $val['default'];
+ $computed = empty($val['computed']) ? '' : $val['computed'];
+ $unique = empty($val['unique']) ? '' : $val['unique'];
+ $required = empty($val['required']) ? '' : $val['required'];
$param = array();
$param['options'] = array();
@@ -7016,9 +7017,9 @@ abstract class CommonObject
$type = 'sellist';
}
- $langfile = $val['langfile'];
- $list = $val['list'];
- $help = $val['help'];
+ $langfile = empty($val['langfile']) ? '' : $val['langfile'];
+ $list = (empty($val['list']) ? '' : $val['list']);
+ $help = (empty($val['help']) ? '' : $val['help']);
$hidden = (($val['visible'] == 0) ? 1 : 0); // If zero, we are sure it is hidden, otherwise we show. If it depends on mode (view/create/edit form or list, this must be filtered by caller)
if ($hidden) {
@@ -7044,9 +7045,9 @@ abstract class CommonObject
} elseif ($type == 'boolean') {
$morecss = '';
} else {
- if (round($size) < 12) {
+ if (is_numeric($size) && round($size) < 12) {
$morecss = 'minwidth100';
- } elseif (round($size) <= 48) {
+ } elseif (is_numeric($size) && round($size) <= 48) {
$morecss = 'minwidth200';
} else {
$morecss = 'minwidth400';
diff --git a/htdocs/core/class/discount.class.php b/htdocs/core/class/discount.class.php
index aaf0f315c1b..2aa829f4090 100644
--- a/htdocs/core/class/discount.class.php
+++ b/htdocs/core/class/discount.class.php
@@ -257,11 +257,11 @@ class DiscountAbsolute
$sql .= " multicurrency_amount_ht, multicurrency_amount_tva, multicurrency_amount_ttc,";
$sql .= " fk_facture_source, fk_invoice_supplier_source";
$sql .= ")";
- $sql .= " VALUES (".$conf->entity.", '".$this->db->idate($this->datec != '' ? $this->datec : dol_now())."', ".$this->fk_soc.", ".(empty($this->discount_type) ? 0 : intval($this->discount_type)).", ".$userid.", '".$this->db->escape($this->description)."',";
- $sql .= " ".$this->amount_ht.", ".$this->amount_tva.", ".$this->amount_ttc.", ".$this->tva_tx.", '".$this->db->escape($this->vat_src_code)."',";
- $sql .= " ".$this->multicurrency_amount_ht.", ".$this->multicurrency_amount_tva.", ".$this->multicurrency_amount_ttc.", ";
- $sql .= " ".($this->fk_facture_source ? "'".$this->db->escape($this->fk_facture_source)."'" : "null").",";
- $sql .= " ".($this->fk_invoice_supplier_source ? "'".$this->db->escape($this->fk_invoice_supplier_source)."'" : "null");
+ $sql .= " VALUES (".$conf->entity.", '".$this->db->idate($this->datec != '' ? $this->datec : dol_now())."', ".((int) $this->fk_soc).", ".(empty($this->discount_type) ? 0 : intval($this->discount_type)).", ".((int) $userid).", '".$this->db->escape($this->description)."',";
+ $sql .= " ".price2num($this->amount_ht).", ".price2num($this->amount_tva).", ".price2num($this->amount_ttc).", ".price2num($this->tva_tx).", '".$this->db->escape($this->vat_src_code)."',";
+ $sql .= " ".price2num($this->multicurrency_amount_ht).", ".price2num($this->multicurrency_amount_tva).", ".price2num($this->multicurrency_amount_ttc).", ";
+ $sql .= " ".($this->fk_facture_source ? ((int) $this->fk_facture_source) : "null").",";
+ $sql .= " ".($this->fk_invoice_supplier_source ? ((int) $this->fk_invoice_supplier_source) : "null");
$sql .= ")";
dol_syslog(get_class($this)."::create", LOG_DEBUG);
@@ -531,7 +531,7 @@ class DiscountAbsolute
//$obj = $this->db->fetch_object($resql);
//}
if ($multicurrency) {
- return $obj->amount_multicurrency;
+ return $obj->multicurrency_amount;
}
return $obj->amount;
diff --git a/htdocs/core/class/events.class.php b/htdocs/core/class/events.class.php
index 1771f0d18f0..c8799ebbd5e 100644
--- a/htdocs/core/class/events.class.php
+++ b/htdocs/core/class/events.class.php
@@ -163,7 +163,7 @@ class Events // extends CommonObject
$sql .= "prefix_session";
$sql .= ") VALUES (";
$sql .= " '".$this->db->escape($this->type)."',";
- $sql .= " ".$conf->entity.",";
+ $sql .= " ".((int) $conf->entity).",";
$sql .= " '".$this->db->escape(getUserRemoteIP())."',";
$sql .= " ".($this->user_agent ? "'".$this->db->escape(dol_trunc($this->user_agent, 250))."'" : 'NULL').",";
$sql .= " '".$this->db->idate($this->dateevent)."',";
diff --git a/htdocs/core/class/extrafields.class.php b/htdocs/core/class/extrafields.class.php
index 59311b707dd..16d58f51eb3 100644
--- a/htdocs/core/class/extrafields.class.php
+++ b/htdocs/core/class/extrafields.class.php
@@ -472,14 +472,14 @@ class ExtraFields
$sql .= " VALUES('".$this->db->escape($attrname)."',";
$sql .= " '".$this->db->escape($label)."',";
$sql .= " '".$this->db->escape($type)."',";
- $sql .= " ".$pos.",";
+ $sql .= " ".((int) $pos).",";
$sql .= " '".$this->db->escape($size)."',";
$sql .= " ".($entity === '' ? $conf->entity : $entity).",";
$sql .= " '".$this->db->escape($elementtype)."',";
- $sql .= " ".$unique.",";
- $sql .= " ".$required.",";
+ $sql .= " ".((int) $unique).",";
+ $sql .= " ".((int) $required).",";
$sql .= " '".$this->db->escape($params)."',";
- $sql .= " ".$alwayseditable.",";
+ $sql .= " ".((int) $alwayseditable).",";
$sql .= " ".($perms ? "'".$this->db->escape($perms)."'" : "null").",";
$sql .= " ".($langfile ? "'".$this->db->escape($langfile)."'" : "null").",";
$sql .= " '".$this->db->escape($list)."',";
diff --git a/htdocs/core/class/html.form.class.php b/htdocs/core/class/html.form.class.php
index e084757fe28..ebda2face9e 100644
--- a/htdocs/core/class/html.form.class.php
+++ b/htdocs/core/class/html.form.class.php
@@ -1849,7 +1849,7 @@ class Form
* @param string $force_entity '0' or Ids of environment to force
* @param int $maxlength Maximum length of string into list (0=no limit)
* @param int $showstatus 0=show user status only if status is disabled, 1=always show user status into label, -1=never show user status
- * @param string $morefilter Add more filters into sql request (Example: 'employee = 1')
+ * @param string $morefilter Add more filters into sql request (Example: 'employee = 1'). This value must not come from user input.
* @param integer $show_every 0=default list, 1=add also a value "Everybody" at beginning of list
* @param string $enableonlytext If option $enableonlytext is set, we use this text to explain into label why record is disabled. Not used if enableonly is empty.
* @param string $morecss More css
@@ -1903,9 +1903,9 @@ class Form
}
$sql .= " FROM ".MAIN_DB_PREFIX."user as u";
if (!empty($conf->multicompany->enabled) && $conf->entity == 1 && $user->admin && !$user->entity) {
- $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."entity as e ON e.rowid=u.entity";
+ $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."entity as e ON e.rowid = u.entity";
if ($force_entity) {
- $sql .= " WHERE u.entity IN (0, ".$force_entity.")";
+ $sql .= " WHERE u.entity IN (0, ".$this->db->sanitize($force_entity).")";
} else {
$sql .= " WHERE u.entity IS NOT NULL";
}
@@ -1919,7 +1919,7 @@ class Form
}
}
if (!empty($user->socid)) {
- $sql .= " AND u.fk_soc = ".$user->socid;
+ $sql .= " AND u.fk_soc = ".((int) $user->socid);
}
if (is_array($exclude) && $excludeUsers) {
$sql .= " AND u.rowid NOT IN (".$this->db->sanitize($excludeUsers).")";
@@ -3052,20 +3052,20 @@ class Form
/**
* Return list of suppliers products
*
- * @param int $socid Id societe fournisseur (0 pour aucun filtre)
- * @param int $selected Product price pre-selected (must be 'id' in product_fournisseur_price or 'idprod_IDPROD')
- * @param string $htmlname Nom de la zone select
- * @param string $filtertype Filter on product type (''=nofilter, 0=product, 1=service)
- * @param string $filtre Pour filtre sql
- * @param string $filterkey Filtre des produits
- * @param int $statut -1=Return all products, 0=Products not on buy, 1=Products on buy
- * @param int $outputmode 0=HTML select string, 1=Array
- * @param int $limit Limit of line number
+ * @param int $socid Id of supplier thirdparty (0 = no filter)
+ * @param int $selected Product price pre-selected (must be 'id' in product_fournisseur_price or 'idprod_IDPROD')
+ * @param string $htmlname Name of HTML select
+ * @param string $filtertype Filter on product type (''=nofilter, 0=product, 1=service)
+ * @param string $filtre Generic filter. Data must not come from user input.
+ * @param string $filterkey Filter of produdts
+ * @param int $statut -1=Return all products, 0=Products not on buy, 1=Products on buy
+ * @param int $outputmode 0=HTML select string, 1=Array
+ * @param int $limit Limit of line number
* @param int $alsoproductwithnosupplierprice 1=Add also product without supplier prices
- * @param string $morecss Add more CSS
+ * @param string $morecss Add more CSS
* @param int $showstockinlist Show stock information (slower).
- * @param string $placeholder Placeholder
- * @return array Array of keys for json
+ * @param string $placeholder Placeholder
+ * @return array Array of keys for json
*/
public function select_produits_fournisseurs_list($socid, $selected = '', $htmlname = 'productid', $filtertype = '', $filtre = '', $filterkey = '', $statut = -1, $outputmode = 0, $limit = 100, $alsoproductwithnosupplierprice = 0, $morecss = '', $showstockinlist = 0, $placeholder = '')
{
@@ -3102,8 +3102,8 @@ class Form
}
$sql .= " FROM ".MAIN_DB_PREFIX."product as p";
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product_fournisseur_price as pfp ON ( p.rowid = pfp.fk_product AND pfp.entity IN (".getEntity('product').") )";
- if ($socid) {
- $sql .= " AND pfp.fk_soc = ".$socid;
+ if ($socid > 0) {
+ $sql .= " AND pfp.fk_soc = ".((int) $socid);
}
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON pfp.fk_soc = s.rowid";
// Units
@@ -3115,7 +3115,7 @@ class Form
$sql .= " AND p.tobuy = ".((int) $statut);
}
if (strval($filtertype) != '') {
- $sql .= " AND p.fk_product_type=".$this->db->escape($filtertype);
+ $sql .= " AND p.fk_product_type = ".((int) $filtertype);
}
if (!empty($filtre)) {
$sql .= " ".$filtre;
@@ -7408,7 +7408,7 @@ class Form
}
}
- $lis = '';
+ $listoffieldsforselection = '';
$listcheckedstring = '';
foreach ($array as $key => $val) {
@@ -7419,13 +7419,18 @@ class Form
unset($array[$key]); // We don't want this field
continue;
}
+ if (!empty($val['type']) && $val['type'] == 'separate') {
+ // Field remains in array but we don't add it into $listoffieldsforselection
+ //$listoffieldsforselection .= '
-----
';
+ continue;
+ }
if ($val['label']) {
if (!empty($val['langfile']) && is_object($langs)) {
$langs->load($val['langfile']);
}
// Note: $val['checked'] <> 0 means we must show the field into the combo list
- $lis .= '';
+ $listoffieldsforselection .= '';
$listcheckedstring .= (empty($val['checked']) ? '' : $key.',');
}
}
@@ -7442,7 +7447,7 @@ class Form
- '.$lis.'
+ '.$listoffieldsforselection.'
diff --git a/htdocs/core/class/html.formcompany.class.php b/htdocs/core/class/html.formcompany.class.php
index 4fa2f87f636..bb99b2a32e9 100644
--- a/htdocs/core/class/html.formcompany.class.php
+++ b/htdocs/core/class/html.formcompany.class.php
@@ -45,7 +45,7 @@ class FormCompany extends Form
* Return list of labels (translated) of third parties type
*
* @param int $mode 0=Return id+label, 1=Return code+label
- * @param string $filter Add a SQL filter to select
+ * @param string $filter Add a SQL filter to select. Data must not come from user input.
* @return array Array of types
*/
public function typent_array($mode = 0, $filter = '')
@@ -96,7 +96,7 @@ class FormCompany extends Form
* Renvoie la liste des types d'effectifs possibles (pas de traduction car nombre)
*
* @param int $mode 0=renvoi id+libelle, 1=renvoi code+libelle
- * @param string $filter Add a SQL filter to select
+ * @param string $filter Add a SQL filter to select. Data must not come from user input.
* @return array Array of types d'effectifs
*/
public function effectif_array($mode = 0, $filter = '')
@@ -509,7 +509,7 @@ class FormCompany extends Form
*
* @param string $selected Preselected code of juridical type
* @param int $country_codeid 0=list for all countries, otherwise list only country requested
- * @param string $filter Add a SQL filter on list
+ * @param string $filter Add a SQL filter on list. Data must not come from user input.
* @param string $htmlname HTML name of select
* @param string $morecss More CSS
* @return string String with HTML select
diff --git a/htdocs/core/class/html.formmail.class.php b/htdocs/core/class/html.formmail.class.php
index 00c1c4236c4..06b6a18b870 100644
--- a/htdocs/core/class/html.formmail.class.php
+++ b/htdocs/core/class/html.formmail.class.php
@@ -4,7 +4,7 @@
* Copyright (C) 2010-2011 Juanjo Menent
* Copyright (C) 2015-2017 Marcos García
* Copyright (C) 2015-2017 Nicolas ZABOURI
- * Copyright (C) 2018-2019 Frédéric France
+ * Copyright (C) 2018-2021 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
@@ -584,7 +584,10 @@ class FormMail extends Form
$liste['company'] = $conf->global->MAIN_INFO_SOCIETE_NOM.' <'.$conf->global->MAIN_INFO_SOCIETE_MAIL.'>';
// Add also email aliases if there is some
- $listaliases = array('user_aliases'=>$user->email_aliases, 'global_aliases'=>$conf->global->MAIN_INFO_SOCIETE_MAIL_ALIASES);
+ $listaliases = array(
+ 'user_aliases' => (empty($user->email_aliases) ? '' : $user->email_aliases),
+ 'global_aliases' => getDolGlobalString('MAIN_INFO_SOCIETE_MAIL_ALIASES'),
+ );
// Also add robot email
if (!empty($this->fromalsorobot)) {
diff --git a/htdocs/core/db/DoliDB.class.php b/htdocs/core/db/DoliDB.class.php
index 2ed93a9b5f4..b93a3f40cea 100644
--- a/htdocs/core/db/DoliDB.class.php
+++ b/htdocs/core/db/DoliDB.class.php
@@ -108,7 +108,7 @@ abstract class DoliDB implements Database
* Sanitize a string for SQL forging
*
* @param string $stringtosanitize String to escape
- * @param int $allowsimplequote Allow simple quote
+ * @param int $allowsimplequote 1=Allow simple quotes in string. When string is used as a list of SQL string ('aa', 'bb', ...)
* @return string String escaped
*/
public function sanitize($stringtosanitize, $allowsimplequote = 0)
diff --git a/htdocs/core/lib/functions.lib.php b/htdocs/core/lib/functions.lib.php
index 29a27ccd38b..16b28814f64 100644
--- a/htdocs/core/lib/functions.lib.php
+++ b/htdocs/core/lib/functions.lib.php
@@ -5190,7 +5190,7 @@ function price($amount, $form = 0, $outlangs = '', $trunc = 1, $rounding = -1, $
* @param int $option Put 1 if you know that content is already universal format number (so no correction on decimal will be done)
* Put 2 if you know that number is a user input (so we know we don't have to fix decimal separator).
* @return string Amount with universal numeric format (Example: '99.99999').
- * If conversion fails, it return text unchanged if $rounding = '' or '0' if $rounding is defined.
+ * If conversion fails, it return text unchanged if ($rounding = '' and $option = 1) or '0' if ($rounding is defined and $option = 1).
* If amount is null or '', it returns '' if $rounding = '' or '0' if $rounding is defined..
*
* @see price() Opposite function of price2num
@@ -7139,14 +7139,14 @@ function getCommonSubstitutionArray($outputlangs, $onlykey = 0, $exclude = null,
$substitutionarray['__AMOUNT_TAX3__'] = is_object($object) ? $object->total_localtax2 : '';
}
- $substitutionarray['__AMOUNT_FORMATED__'] = is_object($object) ? ($object->total_ttc ? price($object->total_ttc, 0, $outputlangs, 0, 0, -1, $conf->currency) : null) : '';
- $substitutionarray['__AMOUNT_EXCL_TAX_FORMATED__'] = is_object($object) ? ($object->total_ht ? price($object->total_ht, 0, $outputlangs, 0, 0, -1, $conf->currency) : null) : '';
- $substitutionarray['__AMOUNT_VAT_FORMATED__'] = is_object($object) ? (isset($object->total_vat) ? price($object->total_vat, 0, $outputlangs, 0, 0, -1, $conf->currency) : ($object->total_tva ? price($object->total_tva, 0, $outputlangs, 0, 0, -1, $conf->currency) : null)) : '';
+ $substitutionarray['__AMOUNT_FORMATED__'] = is_object($object) ? ($object->total_ttc ? price($object->total_ttc, 0, $outputlangs, 0, -1, -1, $conf->currency) : null) : '';
+ $substitutionarray['__AMOUNT_EXCL_TAX_FORMATED__'] = is_object($object) ? ($object->total_ht ? price($object->total_ht, 0, $outputlangs, 0, -1, -1, $conf->currency) : null) : '';
+ $substitutionarray['__AMOUNT_VAT_FORMATED__'] = is_object($object) ? (isset($object->total_vat) ? price($object->total_vat, 0, $outputlangs, 0, -1, -1, $conf->currency) : ($object->total_tva ? price($object->total_tva, 0, $outputlangs, 0, -1, -1, $conf->currency) : null)) : '';
if ($onlykey != 2 || $mysoc->useLocalTax(1)) {
- $substitutionarray['__AMOUNT_TAX2_FORMATED__'] = is_object($object) ? ($object->total_localtax1 ? price($object->total_localtax1, 0, $outputlangs, 0, 0, -1, $conf->currency) : null) : '';
+ $substitutionarray['__AMOUNT_TAX2_FORMATED__'] = is_object($object) ? ($object->total_localtax1 ? price($object->total_localtax1, 0, $outputlangs, 0, -1, -1, $conf->currency) : null) : '';
}
if ($onlykey != 2 || $mysoc->useLocalTax(2)) {
- $substitutionarray['__AMOUNT_TAX3_FORMATED__'] = is_object($object) ? ($object->total_localtax2 ? price($object->total_localtax2, 0, $outputlangs, 0, 0, -1, $conf->currency) : null) : '';
+ $substitutionarray['__AMOUNT_TAX3_FORMATED__'] = is_object($object) ? ($object->total_localtax2 ? price($object->total_localtax2, 0, $outputlangs, 0, -1, -1, $conf->currency) : null) : '';
}
$substitutionarray['__AMOUNT_MULTICURRENCY__'] = (is_object($object) && isset($object->multicurrency_total_ttc)) ? $object->multicurrency_total_ttc : '';
@@ -8009,7 +8009,7 @@ function dol_eval($s, $returnvalue = 0, $hideerrors = 1)
$forbiddenphpstrings = array_merge($forbiddenphpstrings, array("fopen(", "file_put_contents(", "fputs(", "fputscsv(", "fwrite(", "fpassthru(", "unlink(", "mkdir(", "rmdir(", "symlink(", "touch(", "umask("));
$forbiddenphpstrings = array_merge($forbiddenphpstrings, array('function(', '$$', 'call_user_func('));
$forbiddenphpstrings = array_merge($forbiddenphpstrings, array('_ENV', '_SESSION', '_COOKIE', '_GET', '_POST', '_REQUEST'));
- $forbiddenphpregex = array('global\s+\$');
+ $forbiddenphpregex = 'global\s+\$';
do {
$oldstringtoclean = $s;
$s = str_ireplace($forbiddenphpstrings, '__forbiddenstring__', $s);
diff --git a/htdocs/core/lib/functions2.lib.php b/htdocs/core/lib/functions2.lib.php
index 37f78563c02..6f5d2ccb891 100644
--- a/htdocs/core/lib/functions2.lib.php
+++ b/htdocs/core/lib/functions2.lib.php
@@ -1043,7 +1043,7 @@ function get_next_value($db, $mask, $table, $field, $where = '', $objsoc = '', $
$regType = array();
if (preg_match('/\{(t+)\}/i', $mask, $regType)) {
$masktype = $regType[1];
- $masktype_value = substr(preg_replace('/^TE_/', '', $objsoc->typent_code), 0, dol_strlen($regType[1])); // get n first characters of thirdpaty typent_code (where n is length in mask)
+ $masktype_value = substr(preg_replace('/^TE_/', '', $objsoc->typent_code), 0, dol_strlen($regType[1])); // get n first characters of thirdparty typent_code (where n is length in mask)
$masktype_value = str_pad($masktype_value, dol_strlen($regType[1]), "#", STR_PAD_RIGHT); // we fill on right with # to have same number of char than into mask
} else {
$masktype = '';
diff --git a/htdocs/core/lib/images.lib.php b/htdocs/core/lib/images.lib.php
index 0a0ebe3ce02..0ccb6a415fc 100644
--- a/htdocs/core/lib/images.lib.php
+++ b/htdocs/core/lib/images.lib.php
@@ -590,7 +590,7 @@ function vignette($file, $maxWidth = 160, $maxHeight = 120, $extName = '_small',
break;
}
- if (!is_resource($img)) {
+ if (!is_resource($img) && !($img instanceof \GdImage)) {
dol_syslog('Failed to detect type of image. We found infoImg[2]='.$infoImg[2], LOG_WARNING);
return 0;
}
diff --git a/htdocs/core/lib/invoice.lib.php b/htdocs/core/lib/invoice.lib.php
index c328d659b93..3838829f8cd 100644
--- a/htdocs/core/lib/invoice.lib.php
+++ b/htdocs/core/lib/invoice.lib.php
@@ -508,16 +508,16 @@ function getNumberInvoicesPieChart($mode)
while ($i < $num) {
$obj = $db->fetch_object($resql);
- $dataseries = array(array($langs->trans('InvoiceLate30Days'),$obj->nblate30)
- ,array($langs->trans('InvoiceLate15Days'),$obj->nblate15-$obj->nblate30)
- ,array($langs->trans('InvoiceLateMinus15Days'),$obj->nblatenow-$obj->nblate15)
- ,array($langs->trans('InvoiceNotLate'),$obj->nbnotlatenow-$obj->nbnotlate15)
- ,array($langs->trans('InvoiceNotLate15Days'),$obj->nbnotlate15-$obj->nbnotlate30)
- ,array($langs->trans('InvoiceNotLate30Days'),$obj->nbnotlate30));
+ $dataseries = array(array($langs->trans('InvoiceLate30Days'), $obj->nblate30)
+ ,array($langs->trans('InvoiceLate15Days'), $obj->nblate15 - $obj->nblate30)
+ ,array($langs->trans('InvoiceLateMinus15Days'), $obj->nblatenow - $obj->nblate15)
+ ,array($langs->trans('InvoiceNotLate'), $obj->nbnotlatenow - $obj->nbnotlate15)
+ ,array($langs->trans('InvoiceNotLate15Days'), $obj->nbnotlate15 - $obj->nbnotlate30)
+ ,array($langs->trans('InvoiceNotLate30Days'), $obj->nbnotlate30));
$i++;
}
foreach ($dataseries as $key=>$value) {
- $total+=$value[1];
+ $total += $value[1];
}
$colorseries = array($badgeStatus8, $badgeStatus1, $badgeStatus3, $badgeStatus4, $badgeStatus11, '-'.$badgeStatus11);
@@ -615,7 +615,7 @@ function getCustomerInvoiceDraftTable($maxCount = 500, $socid = 0)
$sql .= " s.nom, s.rowid, s.email, s.code_client, s.code_compta, s.code_fournisseur, s.code_compta_fournisseur,";
$sql .= " cc.rowid, cc.code";
if (!$user->rights->societe->client->voir && !$socid) {
- $sql.= ", sc.fk_soc, sc.fk_user";
+ $sql .= ", sc.fk_soc, sc.fk_user";
}
// Add Group from hooks
diff --git a/htdocs/core/lib/pdf.lib.php b/htdocs/core/lib/pdf.lib.php
index 880c63970f7..4821bfde2b9 100644
--- a/htdocs/core/lib/pdf.lib.php
+++ b/htdocs/core/lib/pdf.lib.php
@@ -705,8 +705,8 @@ function pdf_pagehead(&$pdf, $outputlangs, $page_height)
{
global $conf;
- // Add a background image on document
- if (!empty($conf->global->MAIN_USE_BACKGROUND_ON_PDF)) { // Warning, this option make TCPDF generation being crazy and some content disappeared behind the image
+ // Add a background image on document only if good setup of const
+ if (!empty($conf->global->MAIN_USE_BACKGROUND_ON_PDF) && ($conf->global->MAIN_USE_BACKGROUND_ON_PDF != '-1')) { // Warning, this option make TCPDF generation being crazy and some content disappeared behind the image
$pdf->SetAutoPageBreak(0, 0); // Disable auto pagebreak before adding image
$pdf->Image($conf->mycompany->dir_output.'/logos/'.$conf->global->MAIN_USE_BACKGROUND_ON_PDF, (isset($conf->global->MAIN_USE_BACKGROUND_ON_PDF_X) ? $conf->global->MAIN_USE_BACKGROUND_ON_PDF_X : 0), (isset($conf->global->MAIN_USE_BACKGROUND_ON_PDF_Y) ? $conf->global->MAIN_USE_BACKGROUND_ON_PDF_Y : 0), 0, $page_height);
$pdf->SetAutoPageBreak(1, 0); // Restore pagebreak
@@ -1328,7 +1328,7 @@ function pdf_getlinedesc($object, $i, $outputlangs, $hideref = 0, $hidedesc = 0,
// ($textwasnotmodified is replaced with $textwasmodifiedorcompleted and we add completion).
// Set label
- // If we want another language, and if label is same than default language (we did force it to a specific value), we can use translation.
+ // If we want another language, and if label is same than default language (we did not force it to a specific value), we can use translation.
//var_dump($outputlangs->defaultlang.' - '.$langs->defaultlang.' - '.$label.' - '.$prodser->label);exit;
$textwasnotmodified = ($label == $prodser->label);
if (!empty($prodser->multilangs[$outputlangs->defaultlang]["label"]) && ($textwasnotmodified || $translatealsoifmodified)) {
@@ -1354,9 +1354,7 @@ function pdf_getlinedesc($object, $i, $outputlangs, $hideref = 0, $hidedesc = 0,
}
}
} elseif ($object->element == 'facture' || $object->element == 'facturefourn') {
- if ($object->type == $object::TYPE_DEPOSIT) {
- $desc = str_replace('(DEPOSIT)', $outputlangs->trans('Deposit'), $desc);
- }
+ $desc = str_replace('(DEPOSIT)', $outputlangs->trans('Deposit'), $desc);
}
// Description short of product line
diff --git a/htdocs/core/lib/product.lib.php b/htdocs/core/lib/product.lib.php
index 9825b2dac2c..64cc72a8879 100644
--- a/htdocs/core/lib/product.lib.php
+++ b/htdocs/core/lib/product.lib.php
@@ -360,7 +360,8 @@ function product_lot_admin_prepare_head()
*/
function show_stats_for_company($product, $socid)
{
- global $conf, $langs, $user, $db;
+ global $conf, $langs, $user, $db, $hookmanager;
+
$form = new Form($db);
$nblines = 0;
@@ -558,6 +559,12 @@ function show_stats_for_company($product, $socid)
print '';
print '';
}
+ $parameters = array('socid'=>$socid);
+ $reshook = $hookmanager->executeHooks('addMoreProductStat', $parameters, $product, $nblines); // Note that $action and $object may have been modified by some hooks
+ if ($reshook < 0) setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
+
+ print $hookmanager->resPrint;
+
return $nblines++;
}
diff --git a/htdocs/core/modules/barcode/mod_barcode_product_standard.php b/htdocs/core/modules/barcode/mod_barcode_product_standard.php
index 7bbedd0aa67..0d1bee3c28a 100644
--- a/htdocs/core/modules/barcode/mod_barcode_product_standard.php
+++ b/htdocs/core/modules/barcode/mod_barcode_product_standard.php
@@ -107,7 +107,7 @@ class mod_barcode_product_standard extends ModeleNumRefBarCode
// Mask parameter
//$texte.= '
';
$texte .= '';
diff --git a/htdocs/core/modules/member/modules_cards.php b/htdocs/core/modules/member/modules_cards.php
index 0e041a17559..937e8a06870 100644
--- a/htdocs/core/modules/member/modules_cards.php
+++ b/htdocs/core/modules/member/modules_cards.php
@@ -48,7 +48,7 @@ class ModelePDFCards
* @param integer $maxfilenamelength Max length of value to show
* @return array List of templates
*/
- public function liste_modeles($db, $maxfilenamelength = 0)
+ public static function liste_modeles($db, $maxfilenamelength = 0)
{
// phpcs:enable
global $conf;
diff --git a/htdocs/core/modules/modFacture.class.php b/htdocs/core/modules/modFacture.class.php
index b296fa3b32c..a9765f45ada 100644
--- a/htdocs/core/modules/modFacture.class.php
+++ b/htdocs/core/modules/modFacture.class.php
@@ -120,8 +120,36 @@ class modFacture extends DolibarrModules
$arraydate = dol_getdate(dol_now());
$datestart = dol_mktime(23, 0, 0, $arraydate['mon'], $arraydate['mday'], $arraydate['year']);
$this->cronjobs = array(
- 0=>array('label'=>'RecurringInvoices', 'jobtype'=>'method', 'class'=>'compta/facture/class/facture-rec.class.php', 'objectname'=>'FactureRec', 'method'=>'createRecurringInvoices', 'parameters'=>'', 'comment'=>'Generate recurring invoices', 'frequency'=>1, 'unitfrequency'=>3600 * 24, 'priority'=>50, 'status'=>1, 'test'=>'$conf->facture->enabled', 'datestart'=>$datestart),
- 1=>array('label'=>'SendEmailsRemindersOnInvoiceDueDate', 'jobtype'=>'method', 'class'=>'compta/facture/class/facture.class.php', 'objectname'=>'Facture', 'method'=>'sendEmailsRemindersOnInvoiceDueDate', 'parameters'=>"10,all,EmailTemplateCode", 'comment'=>'Send an emails when the unpaid invoices reach a due date + n days = today. First param is the offset n of days, second parameter is "all" or a payment mode code, last paramater is the code of email template to use (an email template with EmailTemplateCode must exists. the version in the language of the thirdparty will be used in priority).', 'frequency'=>1, 'unitfrequency'=>3600 * 24, 'priority'=>50, 'status'=>0, 'test'=>'$conf->facture->enabled', 'datestart'=>$datestart),
+ 0 => array(
+ 'label'=>'RecurringInvoices',
+ 'jobtype'=>'method',
+ 'class'=>'compta/facture/class/facture-rec.class.php',
+ 'objectname'=>'FactureRec',
+ 'method'=>'createRecurringInvoices',
+ 'parameters'=>'',
+ 'comment'=>'Generate recurring invoices',
+ 'frequency'=>1,
+ 'unitfrequency'=>3600 * 24,
+ 'priority'=>50,
+ 'status'=>1,
+ 'test'=>'$conf->facture->enabled',
+ 'datestart'=>$datestart
+ ),
+ 1 => array(
+ 'label'=>'SendEmailsRemindersOnInvoiceDueDate',
+ 'jobtype'=>'method',
+ 'class'=>'compta/facture/class/facture.class.php',
+ 'objectname'=>'Facture',
+ 'method'=>'sendEmailsRemindersOnInvoiceDueDate',
+ 'parameters'=>"10,all,EmailTemplateCode",
+ 'comment'=>'Send an emails when the unpaid invoices reach a due date + n days = today. First param is the offset n of days, second parameter is "all" or a payment mode code, last paramater is the code of email template to use (an email template with EmailTemplateCode must exists. the version in the language of the thirdparty will be used in priority).',
+ 'frequency'=>1,
+ 'unitfrequency'=>3600 * 24,
+ 'priority'=>50,
+ 'status'=>0,
+ 'test'=>'$conf->facture->enabled',
+ 'datestart'=>$datestart
+ ),
);
// Permissions
diff --git a/htdocs/core/modules/modFournisseur.class.php b/htdocs/core/modules/modFournisseur.class.php
index 71f101014ad..fae1cfc0eb8 100644
--- a/htdocs/core/modules/modFournisseur.class.php
+++ b/htdocs/core/modules/modFournisseur.class.php
@@ -488,7 +488,8 @@ class modFournisseur extends DolibarrModules
$tmp = '';
$tmpparam = unserialize($obj->param); // $tmp ay be array 'options' => array 'c_currencies:code_iso:code_iso' => null
if ($tmpparam['options'] && is_array($tmpparam['options'])) {
- $tmp = array_shift(array_keys($tmpparam['options']));
+ $array_keys = array_keys($tmpparam['options']);
+ $tmp = array_shift($array_keys);
}
if (preg_match('/[a-z0-9_]+:[a-z0-9_]+:[a-z0-9_]+/', $tmp)) {
$typeFilter = "List:".$tmp;
diff --git a/htdocs/core/modules/mrp/doc/doc_generic_mo_odt.modules.php b/htdocs/core/modules/mrp/doc/doc_generic_mo_odt.modules.php
index 374ae5b4337..860e07d7d36 100644
--- a/htdocs/core/modules/mrp/doc/doc_generic_mo_odt.modules.php
+++ b/htdocs/core/modules/mrp/doc/doc_generic_mo_odt.modules.php
@@ -158,7 +158,7 @@ class doc_generic_mo_odt extends ModelePDFMo
$texte .= $conf->global->MRP_MO_ADDON_PDF_ODT_PATH;
$texte .= '';
$texte .= '
';
- $texte .= '';
+ $texte .= '';
$texte .= '
';
// Scan directories
diff --git a/htdocs/core/modules/mrp/mod_mo_advanced.php b/htdocs/core/modules/mrp/mod_mo_advanced.php
index 3d70ded1ba2..14292f7f896 100644
--- a/htdocs/core/modules/mrp/mod_mo_advanced.php
+++ b/htdocs/core/modules/mrp/mod_mo_advanced.php
@@ -80,7 +80,7 @@ class mod_mo_advanced extends ModeleNumRefMos
// Parametrage du prefix
$texte .= '
'.$langs->trans("Mask").':
';
- $texte .= '
'.$form->textwithpicto('', $tooltip, 1, 1).'
';
+ $texte .= '
'.$form->textwithpicto('', $tooltip, 1, 1).'
';
$texte .= '
';
diff --git a/htdocs/core/modules/payment/mod_payment_ant.php b/htdocs/core/modules/payment/mod_payment_ant.php
index 10926805721..dca32b26505 100644
--- a/htdocs/core/modules/payment/mod_payment_ant.php
+++ b/htdocs/core/modules/payment/mod_payment_ant.php
@@ -82,7 +82,7 @@ class mod_payment_ant extends ModeleNumRefPayments
// Parametrage du prefix
$texte .= '