88namespace OCA \Forms \Service ;
99
1010use OCA \Forms \Activity \ActivityManager ;
11+ use OCA \Forms \BackgroundJob \SendConfirmationMailJob ;
1112use OCA \Forms \Constants ;
1213use OCA \Forms \Db \AnswerMapper ;
1314use OCA \Forms \Db \Form ;
2627use OCP \AppFramework \Db \IMapperException ;
2728use OCP \AppFramework \Http ;
2829use OCP \AppFramework \OCS \OCSForbiddenException ;
30+ use OCP \BackgroundJob \IJobList ;
2931use OCP \EventDispatcher \IEventDispatcher ;
3032use OCP \Files \IRootFolder ;
3133use OCP \Files \NotFoundException ;
34+ use OCP \ICacheFactory ;
3235use OCP \IGroup ;
3336use OCP \IGroupManager ;
3437use OCP \IL10N ;
38+ use OCP \IMemcache ;
3539use OCP \IUser ;
3640use OCP \IUserManager ;
3741use OCP \IUserSession ;
5357class FormsService {
5458 private ?IUser $ currentUser ;
5559
60+ private const EMAIL_RATE_LIMIT = 3 ;
61+ private const EMAIL_RATE_LIMIT_TTL = 86400 ; // 24 hours
62+
5663 public function __construct (
5764 IUserSession $ userSession ,
5865 private ActivityManager $ activityManager ,
@@ -73,6 +80,8 @@ public function __construct(
7380 private IMailer $ mailer ,
7481 private IEmailValidator $ emailValidator ,
7582 private AnswerMapper $ answerMapper ,
83+ private IJobList $ jobList ,
84+ private ICacheFactory $ cacheFactory ,
7685 ) {
7786 $ this ->currentUser = $ userSession ->getUser ();
7887 }
@@ -762,18 +771,23 @@ private function sendConfirmationEmail(Form $form, Submission $submission): void
762771 return ;
763772 }
764773
774+ if (!$ this ->configService ->getAllowConfirmationEmail ()) {
775+ $ this ->logger ->debug ('Confirmation email feature is disabled by administrator ' , [
776+ 'formId ' => $ form ->getId (),
777+ ]);
778+ return ;
779+ }
780+
765781 $ subject = $ form ->getConfirmationEmailSubject ();
766782 $ body = $ form ->getConfirmationEmailBody ();
767783
768- // If no subject or body is set, use defaults
769784 if (empty ($ subject )) {
770785 $ subject = $ this ->l10n ->t ('Thank you for your submission ' );
771786 }
772787 if (empty ($ body )) {
773788 $ body = $ this ->l10n ->t ('Thank you for submitting the form "%s". ' , [$ form ->getTitle ()]);
774789 }
775790
776- // Get questions and answers
777791 $ questions = $ this ->getQuestions ($ form ->getId ());
778792 $ answers = $ this ->answerMapper ->findBySubmission ($ submission ->getId ());
779793
@@ -805,61 +819,74 @@ private function sendConfirmationEmail(Form $form, Submission $submission): void
805819 return ;
806820 }
807821
822+ $ cacheKey = 'email_rl_ ' . hash ('sha256 ' , $ form ->getId () . ': ' . strtolower ($ recipientEmail ));
823+ $ cache = $ this ->cacheFactory ->createDistributed ('forms_confirmation_email ' );
824+ if (!$ cache instanceof IMemcache) {
825+ $ this ->logger ->warning ('Distributed cache does not support atomic increments for confirmation email rate limiting ' , [
826+ 'formId ' => $ form ->getId (),
827+ 'submissionId ' => $ submission ->getId (),
828+ ]);
829+ return ;
830+ }
831+
832+ if ($ cache ->add ($ cacheKey , 1 , self ::EMAIL_RATE_LIMIT_TTL )) {
833+ $ count = 1 ;
834+ } else {
835+ $ count = $ cache ->inc ($ cacheKey );
836+ if (!is_int ($ count )) {
837+ $ this ->logger ->warning ('Failed to increment confirmation email rate limit counter ' , [
838+ 'formId ' => $ form ->getId (),
839+ 'submissionId ' => $ submission ->getId (),
840+ ]);
841+ return ;
842+ }
843+ }
844+
845+ if ($ count > self ::EMAIL_RATE_LIMIT ) {
846+ $ this ->logger ->warning ('Per-recipient confirmation email rate limit reached ' , [
847+ 'formId ' => $ form ->getId (),
848+ 'submissionId ' => $ submission ->getId (),
849+ ]);
850+ return ;
851+ }
852+
808853 // Replace placeholders in subject and body
809854 $ replacements = [
810855 '{formTitle} ' => $ form ->getTitle (),
811856 '{formDescription} ' => $ form ->getDescription () ?? '' ,
812857 ];
813858
814- // Add field placeholders (e.g., {name}, {email})
815859 foreach ($ questions as $ question ) {
816860 $ questionId = $ question ['id ' ];
817861 $ questionName = $ question ['name ' ] ?? '' ;
818862 $ questionText = $ question ['text ' ] ?? '' ;
819863
820- // Use question name if available, otherwise use text
821864 $ fieldKey = !empty ($ questionName ) ? $ questionName : $ questionText ;
822- // Sanitize field key for placeholder (remove special chars, lowercase)
823865 $ fieldKey = strtolower (preg_replace ('/[^a-zA-Z0-9]/ ' , '' , $ fieldKey ));
824866
825867 if (!empty ($ answerMap [$ questionId ])) {
826868 $ answerValue = implode ('; ' , $ answerMap [$ questionId ]);
827869 $ replacements ['{ ' . $ fieldKey . '} ' ] = $ answerValue ;
828- // Also support {questionName} format
829870 if (!empty ($ questionName )) {
830871 $ replacements ['{ ' . strtolower (preg_replace ('/[^a-zA-Z0-9]/ ' , '' , $ questionName )) . '} ' ] = $ answerValue ;
831872 }
832873 }
833874 }
834875
835- // Apply replacements
836876 $ subject = str_replace (array_keys ($ replacements ), array_values ($ replacements ), $ subject );
837877 $ body = str_replace (array_keys ($ replacements ), array_values ($ replacements ), $ body );
838878
839- try {
840- $ message = $ this ->mailer ->createMessage ();
841- $ message ->setSubject ($ subject );
842- $ message ->setPlainBody ($ body );
843- $ message ->setTo ([$ recipientEmail ]);
844-
845- $ this ->mailer ->send ($ message );
846- $ this ->logger ->debug ('Confirmation email sent successfully ' , [
847- 'formId ' => $ form ->getId (),
848- 'submissionId ' => $ submission ->getId (),
849- 'recipient ' => $ recipientEmail ,
850- ]);
851- } catch (\Exception $ e ) {
852- // Handle exceptions silently, as this is not critical.
853- // We don't want to break the submission process just because of an email error.
854- $ this ->logger ->error (
855- 'Error while sending confirmation email ' ,
856- [
857- 'exception ' => $ e ,
858- 'formId ' => $ form ->getId (),
859- 'submissionId ' => $ submission ->getId (),
860- ]
861- );
862- }
879+ $ this ->jobList ->add (SendConfirmationMailJob::class, [
880+ 'recipient ' => $ recipientEmail ,
881+ 'subject ' => $ subject ,
882+ 'body ' => $ body ,
883+ 'formId ' => $ form ->getId (),
884+ 'submissionId ' => $ submission ->getId (),
885+ ]);
886+ $ this ->logger ->debug ('Confirmation email queued ' , [
887+ 'formId ' => $ form ->getId (),
888+ 'submissionId ' => $ submission ->getId (),
889+ ]);
863890 }
864891
865892 /**
0 commit comments