diff --git a/.forgejo/workflows/generate-release-zipfile.yml b/.forgejo/workflows/generate-release-zipfile.yml new file mode 100644 index 0000000..43b7ab2 --- /dev/null +++ b/.forgejo/workflows/generate-release-zipfile.yml @@ -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 </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 diff --git a/ChangeLog.md b/ChangeLog.md index be22db7..cdf58df 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -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` + + +## 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 diff --git a/README.md b/README.md index 6cde6c6..2f736af 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/class/actions_sendrecurringinvoicebymail.class.php b/class/actions_sendrecurringinvoicebymail.class.php index 0209d6b..ceb5417 100644 --- a/class/actions_sendrecurringinvoicebymail.class.php +++ b/class/actions_sendrecurringinvoicebymail.class.php @@ -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, diff --git a/core/modules/modsendrecurringinvoicebymail.class.php b/core/modules/modsendrecurringinvoicebymail.class.php index 724b0d1..4a2cc1f 100644 --- a/core/modules/modsendrecurringinvoicebymail.class.php +++ b/core/modules/modsendrecurringinvoicebymail.class.php @@ -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'; diff --git a/fiche-rec-tab1.php b/fiche-rec-tab1.php index 1e8ac04..69fcb98 100644 --- a/fiche-rec-tab1.php +++ b/fiche-rec-tab1.php @@ -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 .= '
' . $langs->trans("Options") . "
\n"; $output .= '
'; + if (function_exists('newToken')) { + $output .= ''; // CSRF protection + } else { + // Used before Dolibar 13 + $output .= ''; // CSRF protection + } $output .= ''; $output .= ''; $output .= ' \n"; @@ -300,21 +306,16 @@ do { $output .= '\n\n"; // body_ishtml
'; $output .= $form->textwithpicto($langs->trans("MailText"), $helpforsubstitution, 1, 'help', '', 0, 2, 'substittooltipfrombody'); $output .= ""; - - + /* + // doleditor does some weird stuff, adding
and newlines, I'll get more into it when I have time. + // fallback to simple \n"; $output .= "