Compare commits

..

11 commits

Author SHA1 Message Date
Chl
c26013ea03 Preparing 0.3.6 release
All checks were successful
/ GenerateReleaseZipfile (push) Successful in 1m22s
2024-08-27 01:46:31 +02:00
Chl
26d25135d6 Add workflow for generating zip file 2024-08-27 01:43:39 +02:00
Chl
f94ba084a3 Disable Dolibarr filter on email addresses and subject
'was wondering if the filter change of Dolibarr 13 might have affected
something else and, well, yes...

So, following e1ac0a6d69, we also disable the
filter for the email addresses, else we get something like
"Webmaster <webmaster@bugness.org>" becoming "Webmaster".
2024-08-25 15:10:07 +02:00
Chl
ab5a9c2861 Preparing 0.3.5 release 2024-08-17 21:37:00 +02:00
Chl
e1ac0a6d69 Fix GH-11: HTML silently removed with Dolibarr 13+
The behaviour of GETPOST(..., 'alpha') changed with Dolibarr 13, copying
'alphanohtml'. Unfortunately, there is no retro-compatible option. Thus the
'none' filter seems the better call since there doesn't seem to have any big
attack involving emails' body (except HTML+JS...)

For the next big version, maybe use the 'restricthtml' filter, but it only
appeared in Dolibarr 12.
2024-08-17 21:28:02 +02:00
Chl
44c54ab3f7 $conf->global->MAIN_MAIL_ERRORS_TO is not always set 2024-03-15 01:22:23 +01:00
Chl
0b08f125c9 readme: specify 'customer invoice'
Following the arrival of supplier invoice templates in Dolibarr 16, it's better
to specify than this module only manage customer invoices.

Adding the management of supplier invoice seems possible but I don't really see
a use case at the moment.
2024-03-13 18:54:01 +01:00
Chl
a7532db483 Execute the hook only for customer invoices
With the introduction of supplier invoice templates in Dolibarr 16, with the
same hook but different table, this module tried to load the customer invoice
having the same id than the supplier invoice template being treated. This could
result in severe information disclosure.

Fixes gh-10
2024-03-13 18:29:27 +01:00
Chl
d3bac76b2e CSRF-protect the form + update the version
Fix #8
2023-12-17 00:22:26 +01:00
Chl
cdde859aea Correct the module's family
CRM -> Financial, with the invoice module.
2023-06-05 12:39:50 +02:00
Chl
704fd77e82 Little rewrite on why there is no dedicated scheduled job 2022-01-11 18:11:12 +01:00
6 changed files with 133 additions and 27 deletions

View file

@ -0,0 +1,74 @@
# Creates a module_sendrecurringinvoicebymail-X.Y.Z.zip file when pushing a tag
#
# This job is mainly useless (Forgejo already creates a usable .zip archive,
# minus the name) and serves more as a warmup for a decent CI/CD tryout.
on:
push:
tags:
# For the time being, we only trigger on the tags clearly matching a
# '1.2.3' version pattern.
- '[0-9]+.[0-9]+.[0-9]+'
env:
MYFILENAME: "module_sendrecurringinvoicebymail-${{ github.ref_name }}"
jobs:
GenerateReleaseZipfile:
runs-on: docker
container:
image: code.bugness.org/chl/alpine-wget-git-zip:latest
steps:
- name: Download the automatic repository archive
run: |
# In case the repository is private, we build an authenticated URL
# with our action token.
MY_GITHUB_AUTHENTICATED_URL="$( echo "$GITHUB_SERVER_URL" | sed "s#^\(https\?://\)#\1$GITHUB_TOKEN\@#" )"
wget -O "$MYFILENAME.zip" "$MY_GITHUB_AUTHENTICATED_URL"/"$GITHUB_REPOSITORY"/archive/"$GITHUB_REF_NAME".zip
- name: A bit of useless cleanup
run: |
#apk add zip
# On Forgejo, GITHUB_REPOSITORY="owner/repo" (and we just want the 'repo' part)
MY_REPOSITORY="$( echo "$GITHUB_REPOSITORY" | sed 's/.*\///' )"
zip -d "$MYFILENAME.zip" \
"$MY_REPOSITORY/.editorconfig" \
"$MY_REPOSITORY/.gitattributes" \
"$MY_REPOSITORY/.gitignore" \
"$MY_REPOSITORY/.tx*"
- name: Upload artifact (using v4)
run: |
set -ex
# The busybox version of wget does not offer --method=PUT as of 2024-08-26
#apk add wget
# We extract the Actions.Results:22:33 from ACTIONS_RUNTIME_TOKEN
# (base64 -d doesn't like when the '==' padding is missing, so 2>/dev/null and relying on the piping to forget about non-zero return code...)
read WORKFLOW_RUN_BACKEND_ID WORKFLOW_JOB_RUN_BACKEND_ID <<EOF
$( echo "$ACTIONS_RUNTIME_TOKEN" | sed 's/.*\.\(.*\)\..*/\1/' | base64 -d 2>/dev/null | sed 's/.*Actions.Results:\([^:]\+\):\([^:" ]\+\).*/\1 \2/' )
EOF
# Get the upload URL
# note: we use the name without .zip, it seems to be added automatically.
RESPONSE="$( wget -O - \
--header 'Content-Type:application/json' \
--header "Authorization: Bearer $GITHUB_TOKEN" \
--post-data "$( printf '{"version":4, "name":"%s", "workflow_run_backend_id":"%s", "workflow_job_run_backend_id":"%s"}' "$MYFILENAME" "$WORKFLOW_RUN_BACKEND_ID" "$WORKFLOW_JOB_RUN_BACKEND_ID" )" \
"$GITHUB_SERVER_URL"/twirp/github.actions.results.api.v1.ArtifactService/CreateArtifact
)"
# We get a JSON with an signedUploadUrl similar to :
# https://entrepot.xlii.si/twirp/github.actions.results.api.v1.ArtifactService/UploadArtifact?sig=yWWEI8tIIECp8D7E5TVh4_6G2pZxWaVdQcSYaCsx5s0=&expires=2024-08-26+07%3A20%3A49.886890537+%2B0200+CEST&artifactName=mymodule-1.2.3.zip&taskID=63
SIGNED_UPLOAD_URL="$( echo "$RESPONSE" | sed -n 's/.*"signedUploadUrl" *: *"\([^"]\+\)".*/\1/p' )"
# Upload our file
# (note: adding '&comp=block' at the end of the URL)
wget --method PUT --body-file "$MYFILENAME.zip" "$SIGNED_UPLOAD_URL&comp=block"
# Finalize the artifact
wget -O - \
--header 'Content-Type:application/json' \
--header "Authorization: Bearer $GITHUB_TOKEN" \
--post-data "$( printf '{"hash":"sha256:%s", "name":"%s", "size":"%d", "workflow_run_backend_id":"%s", "workflow_job_run_backend_id":"%s"}' "$( sha256sum $MYFILENAME.zip | sed 's/[[:space:]]\+.*//' )" "$MYFILENAME" "$( stat -c %s $MYFILENAME.zip )" "$WORKFLOW_RUN_BACKEND_ID" "$WORKFLOW_JOB_RUN_BACKEND_ID" )" \
"$GITHUB_SERVER_URL"/twirp/github.actions.results.api.v1.ArtifactService/FinalizeArtifact

View file

@ -1,5 +1,28 @@
# CHANGELOG SENDRECURRINGINVOICEBYMAIL FOR [DOLIBARR ERP CRM](https://www.dolibarr.org)
## 0.3.6
Fix: freeform email addresses being "html-filtered" `Postmaster <postmaster@bugness.org>``Postmaster`
## 0.3.5
Fix:
* HTML formating was silently removed when used on Dolibarr v.13+ (GH-11)
* `$conf->global->MAIN_MAIL_ERRORS_TO` might not always be set (cause not found)
## 0.3.4
Fix: the hook was also triggered by supplier invoices.
Thanks to jpardenoy for the report and the fix.
## 0.3.3
Fix: adds CSRF protection.
## 0.3.2

View file

@ -2,9 +2,9 @@
## Features
(en) This module sends by email the invoice generated with recurring invoices via scheduled jobs.
(en) This module sends by email the customer invoice generated with a recurring invoice template via scheduled jobs.
(fr) Ce module envoie par mail les factures générées automatiquement par les travaux planifiés et les factures modèles.
(fr) Ce module envoie par mail les factures clientes générées automatiquement par les travaux planifiés et les factures modèles.
You can customize the mail globally or by recurring invoice.
@ -14,7 +14,7 @@ To edit the default global mail template, go to Home > Setup > Emails > Email te
To edit the default sender address, go to Home > Setup > Emails, and edit the `Sender email for automatic emails` field.
This module is triggered by the cron (Scheduled jobs module) and will not send emails when manually generating an invoice.
This module hooks himself on the end of the `Recurring invoices` job from the Scheduled jobs (aka. `cron`) module. It will only be triggered via this Scheduled job and will not send mail when manually generating an invoice from a recurring invoice template.
## Requirements

View file

@ -84,6 +84,14 @@ class Actionssendrecurringinvoicebymail
$error = 0; // Error counter
$facturerec = $parameters['facturerec'];
// Since Dolibarr 16, this hook is also used for the FactureFournisseurRec class.
if (! $facturerec instanceof FactureRec) {
return 0;
}
// Load our own object, linked to this facture
// (if it doesn't exist in database, fetch(,,true) will fill the object
// from the global mail template)
$mailObject = new SRIBMCustomMailInfo($this->db);
if ($mailObject->fetch(null, $facturerec->id, true) != 1) {
dol_syslog("Error loading SRIBMCustomMailInfo for facture rec " . (isset($facturerec->id) ? $facturerec->id : "(facturerec->id not set ??)"));
@ -112,8 +120,8 @@ class Actionssendrecurringinvoicebymail
'to' => implode(', ', $mailObject->compileEmails('to', true)),
'cc' => implode(', ', $mailObject->compileEmails('cc', true)),
'bcc' => implode(', ', $mailObject->compileEmails('bcc', true)),
'errorsTo' => $conf->global->MAIN_MAIL_ERRORS_TO,
'replyTo' => $conf->global->MAIN_MAIL_ERRORS_TO,
'errorsTo' => (isset($conf->global->MAIN_MAIL_ERRORS_TO) ? $conf->global->MAIN_MAIL_ERRORS_TO : ''),
'replyTo' => (isset($conf->global->MAIN_MAIL_ERRORS_TO) ? $conf->global->MAIN_MAIL_ERRORS_TO : ''),
'subject' => $mailObject->subject,
'message' => $mailObject->body,
'ishtml' => $mailObject->body_ishtml,

View file

@ -52,7 +52,7 @@ class modsendrecurringinvoicebymail extends DolibarrModules
// Family can be 'base' (core modules),'crm','financial','hr','projects','products','ecm','technic' (transverse modules),'interface' (link with external tools),'other','...'
// It is used to group modules by family in module setup page
$this->family = "crm";
$this->family = "financial";
// Module position in the family on 2 digits ('01', '10', '20', ...)
$this->module_position = '90';
// Gives the possibility for the module, to provide his own family info and position of this family (Overwrite $this->family and $this->module_position. Avoid this)
@ -69,7 +69,7 @@ class modsendrecurringinvoicebymail extends DolibarrModules
$this->editor_url = 'https://code.bugness.org/Dolibarr/sendrecurringinvoicebymail';
// Possible values for version are: 'development', 'experimental', 'dolibarr', 'dolibarr_deprecated' or a version string like 'x.y.z'
$this->version = '0.3.3';
$this->version = '0.3.6';
//Url to the file with your last numberversion of this module
//$this->url_last_version = 'http://www.example.com/versionmodule.txt';

View file

@ -103,7 +103,7 @@ do {
if (GETPOST('save')) {
do {
// Validate input data
if (! array_key_exists(GETPOST('fromtype', 'alpha'), $listFrom)) {
if (! array_key_exists(GETPOST('fromtype'), $listFrom)) {
setEventMessages('Unexpected from value', null, 'errors');
break;
}
@ -116,13 +116,13 @@ do {
break;
}
// Validate some non-breaking stuff after feeding
if (empty(GETPOST('sendto_free', 'alpha')) && empty(GETPOST('sendto_socpeople', 'array'))) {
if (empty(GETPOST('sendto_free', 'none')) && empty(GETPOST('sendto_socpeople', 'array'))) {
// Kinda weird behaviour from CMailFile but better alert the user beforehand
// FIXME: check if there is a workaround ?
setEventMessages("In some configuration, CMailFile doesn't allow empty 'to' recipient. You should set at least one.", null, 'warnings');
//break;
}
if (! strlen(GETPOST('subject', 'alpha'))) {
if (! strlen(GETPOST('subject', 'none'))) {
// Kinda weird behaviour from CMailFile but better alert the user beforehand
// FIXME: check if there is a workaround ?
setEventMessages("In some configuration, CMailFile doesn't allow empty subject. You should set one.", null, 'warnings');
@ -140,14 +140,14 @@ do {
$mailObject->fromtype = GETPOST('fromtype', 'alpha');
$mailObject->frommail = $listFrom[$mailObject->fromtype];
$mailObject->sendto_free = GETPOST('sendto_free', 'alpha');
$mailObject->sendto_free = GETPOST('sendto_free', 'none');
$mailObject->sendto_thirdparty = in_array('thirdparty', GETPOST('sendto_socpeople', 'array'));
$mailObject->sendcc_free = GETPOST('sendcc_free', 'alpha');
$mailObject->sendcc_free = GETPOST('sendcc_free', 'none');
$mailObject->sendcc_thirdparty = in_array('thirdparty', GETPOST('sendcc_socpeople', 'array'));
$mailObject->subject = GETPOST('subject', 'alpha');
$mailObject->body = GETPOST('body', 'restricthtml');
$mailObject->subject = GETPOST('subject', 'none');
$mailObject->body = GETPOST('body', 'none');
$mailObject->body_ishtml = (int)GETPOST('body_ishtml', 'int');
// Save into database
@ -234,6 +234,12 @@ do {
$output .= '<div class="titre inline-block">' . $langs->trans("Options") . "</div>\n";
$output .= '<form id="sribmform" name="sribmform" method="POST" action="#sribmform">';
if (function_exists('newToken')) {
$output .= '<input type="hidden" name="token" value="'.newToken().'">'; // CSRF protection
} else {
// Used before Dolibar 13
$output .= '<input type="hidden" name="token" value="'.$_SESSION['newtoken'].'">'; // CSRF protection
}
$output .= '<table class="liste" summary="mail options"><tbody>';
$output .= '<tr class="oddeven">';
$output .= ' <td><label for="active">' . $langs->trans('OptionEnable') . "</label></td>\n";
@ -300,21 +306,16 @@ do {
$output .= '<tr><td class="minwidth200" valign="top">';
$output .= $form->textwithpicto($langs->trans("MailText"), $helpforsubstitution, 1, 'help', '', 0, 2, 'substittooltipfrombody');
$output .= "</td>\n<td>";
/*
// doleditor does some weird stuff, adding <br> and newlines, I'll get more into it when I have time.
// fallback to simple <textarea> for the time being.
require_once DOL_DOCUMENT_ROOT.'/core/class/doleditor.class.php';
$doleditor = new DolEditor(
'body',
(GETPOST('body', 'alpha') ? GETPOST('body', 'alpha') : $mailObject->body),
'',
280,
'dolibarr_mailings', // toolbar name
'In', // toolbar location
false, // toolbar start expanded
true, // use local browser
!empty($conf->global->FCKEDITOR_ENABLE_MAIL) // follow global conf about using ckeditor for mails.
);
$doleditor = new DolEditor('body_plaintext', (GETPOST('body_plaintext', 'alpha') ? GETPOST('body_plaintext', 'alpha') : $mailObject->body_plaintext), '', 280);
$output .= $doleditor->Create(1);
*/
$output .= '<textarea id="body" name="body" rows="14" cols="80" class="flat">';
$output .= htmlentities(GETPOST('body', 'alpha') ? GETPOST('body', 'alpha') : $mailObject->body);
$output .= "</textarea>\n";
$output .= "</td></tr>\n";
// body_ishtml