Index: admin/system_presets/simple/form_submissions_formsubs.php =================================================================== --- admin/system_presets/simple/form_submissions_formsubs.php (revision 13377) +++ admin/system_presets/simple/form_submissions_formsubs.php (working copy) @@ -8,7 +8,7 @@ // 'formsubs_list' => Array ('edit', 'delete', 'dbl-click'), // editing form -// 'formsubs_view' => Array ('cancel', 'prev', 'next'), +// 'formsubs_view' => Array ('select', 'cancel', 'prev', 'next'), ); // fields to hide Index: core/admin_templates/forms/form_edit_emails.tpl =================================================================== --- core/admin_templates/forms/form_edit_emails.tpl (revision 0) +++ core/admin_templates/forms/form_edit_emails.tpl (revision 0) @@ -0,0 +1,105 @@ + + + + + + + + + + + + +
+ +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + \ No newline at end of file Index: core/admin_templates/forms/form_field_edit.tpl =================================================================== --- core/admin_templates/forms/form_field_edit.tpl (revision 13377) +++ core/admin_templates/forms/form_field_edit.tpl (working copy) @@ -1,3 +1,5 @@ + + @@ -37,7 +39,7 @@ - + @@ -47,7 +49,12 @@ + + + + + Index: core/admin_templates/forms/forms_edit.tpl =================================================================== --- core/admin_templates/forms/forms_edit.tpl (revision 13377) +++ core/admin_templates/forms/forms_edit.tpl (working copy) @@ -60,9 +60,29 @@ + +
+ + \ No newline at end of file Index: core/admin_templates/img/itemicons/icon16_bounce.gif =================================================================== Cannot display: file marked as a binary type. svn:mime-type = application/octet-stream Property changes on: core\admin_templates\img\itemicons\icon16_bounce.gif ___________________________________________________________________ Added: svn:mime-type + application/octet-stream Index: core/admin_templates/img/itemicons/icon16_new_email.gif =================================================================== Cannot display: file marked as a binary type. svn:mime-type = application/octet-stream Property changes on: core\admin_templates\img\itemicons\icon16_new_email.gif ___________________________________________________________________ Added: svn:mime-type + application/octet-stream Index: core/admin_templates/img/itemicons/icon16_not_replied.gif =================================================================== Cannot display: file marked as a binary type. svn:mime-type = application/octet-stream Property changes on: core\admin_templates\img\itemicons\icon16_not_replied.gif ___________________________________________________________________ Added: svn:mime-type + application/octet-stream Index: core/admin_templates/img/itemicons/icon16_replied.gif =================================================================== Cannot display: file marked as a binary type. svn:mime-type = application/octet-stream Property changes on: core\admin_templates\img\itemicons\icon16_replied.gif ___________________________________________________________________ Added: svn:mime-type + application/octet-stream Index: core/admin_templates/img/toolbar/tool_reply.gif =================================================================== Cannot display: file marked as a binary type. svn:mime-type = application/octet-stream Property changes on: core\admin_templates\img\toolbar\tool_reply.gif ___________________________________________________________________ Added: svn:mime-type + application/octet-stream Index: core/admin_templates/img/toolbar/tool_reply_f2.gif =================================================================== Cannot display: file marked as a binary type. svn:mime-type = application/octet-stream Property changes on: core\admin_templates\img\toolbar\tool_reply_f2.gif ___________________________________________________________________ Added: svn:mime-type + application/octet-stream Index: core/admin_templates/img/toolbar/tool_reply_f3.gif =================================================================== Cannot display: file marked as a binary type. svn:mime-type = application/octet-stream Property changes on: core\admin_templates\img\toolbar\tool_reply_f3.gif ___________________________________________________________________ Added: svn:mime-type + application/octet-stream Index: core/admin_templates/img/toolbar/tool_resend.gif =================================================================== Cannot display: file marked as a binary type. svn:mime-type = application/octet-stream Property changes on: core\admin_templates\img\toolbar\tool_resend.gif ___________________________________________________________________ Added: svn:mime-type + application/octet-stream Index: core/admin_templates/img/toolbar/tool_resend_f2.gif =================================================================== Cannot display: file marked as a binary type. svn:mime-type = application/octet-stream Property changes on: core\admin_templates\img\toolbar\tool_resend_f2.gif ___________________________________________________________________ Added: svn:mime-type + application/octet-stream Index: core/admin_templates/img/toolbar/tool_resend_f3.gif =================================================================== Cannot display: file marked as a binary type. svn:mime-type = application/octet-stream Property changes on: core\admin_templates\img\toolbar\tool_resend_f3.gif ___________________________________________________________________ Added: svn:mime-type + application/octet-stream Index: core/admin_templates/submissions/submission_edit_logs.tpl =================================================================== --- core/admin_templates/submissions/submission_edit_logs.tpl (revision 0) +++ core/admin_templates/submissions/submission_edit_logs.tpl (revision 0) @@ -0,0 +1,149 @@ + + + + + + + + + + + + +
+ + + + +
+ +
+
+ + + + + + + + + + + + + \ No newline at end of file Index: core/admin_templates/submissions/submission_log_edit.tpl =================================================================== --- core/admin_templates/submissions/submission_log_edit.tpl (revision 0) +++ core/admin_templates/submissions/submission_log_edit.tpl (revision 0) @@ -0,0 +1,145 @@ + + + + + + + + + + + + +
+ + + + +
+ + +
+
+ + + + + + + + + + + + + + + + + [] +  [] + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + \ No newline at end of file Index: core/admin_templates/submissions/submission_view.tpl =================================================================== --- core/admin_templates/submissions/submission_view.tpl (revision 13377) +++ core/admin_templates/submissions/submission_view.tpl (working copy) @@ -1,8 +1,14 @@ + + - - + + + + + + @@ -11,11 +17,16 @@ - name="" id="_" value="">  + name="" id="_" value="">  - + -
- - - - + + + + + + - + + + + + + + - + + + + + +
- : + +  *:  
Index: core/admin_templates/submissions/submissions_list.tpl =================================================================== --- core/admin_templates/submissions/submissions_list.tpl (revision 13377) +++ core/admin_templates/submissions/submissions_list.tpl (working copy) @@ -12,7 +12,8 @@ //do not rename - this function is used in default grid for double click! function edit() { - std_edit_item('formsubs', 'submissions/submission_view'); + // don't use temp tables, since we can receive user replies while reviewing form submission + std_edit_temp_item('formsubs', 'submissions/submission_view'); } var a_toolbar = new ToolBar(); Index: core/install/english.lang =================================================================== --- core/install/english.lang (revision 13377) +++ core/install/english.lang (working copy) @@ -20,6 +20,7 @@ Q2hhbmdl Q29udGVudCBNb2Rl RGVsZXRl + RGVsZXRl RGVzaWduIE1vZGU= RG93bg== RWRpdA== @@ -33,6 +34,7 @@ U2VjdGlvbiBQcm9wZXJ0aWVz U2VjdGlvbiBUZW1wbGF0ZQ== VXA= + VXNl Q2FuY2Vs U2VjdGlvbg== TnVtYmVyIG9mIGRheXMgZm9yIGEgY2F0LiB0byBiZSBORVc= @@ -61,10 +63,12 @@ RHVyYXRpb24= RWZmZWN0aXZl RW1haWw= + RS1tYWlsIENvbW11bmljYXRpb24gUm9sZQ== UXVldWU= U2VudA== VG90YWw= RW5hYmxlZA== + RW5hYmxlIEUtbWFpbCBDb21tdW5pY2F0aW9u RW5kIERhdGU= Jm5ic3A7 RXZlbnQ= @@ -75,6 +79,7 @@ RmlsZW5hbWU= UGF0aA== Rmlyc3QgTmFtZQ== + RnJvbSBFLW1haWw= RnJvbSAvIFRvIFVzZXI= RnJvbnQtRW5kIE9ubHk= QWxsb3cgUmVnaXN0cmF0aW9u @@ -106,6 +111,7 @@ TGFzdCBSdW4gT24= TGFzdCBSdW4gU3RhdHVz TGFzdCBBdHRlbXB0 + TGFzdCBVcGRhdGVkIE9u TGluayBVUkw= TmFtZQ== TG9jYXRpb24= @@ -114,6 +120,7 @@ TWFzdGVyIElE TWFzdGVyIFByZWZpeA== TWVtYmVyc2hpcCBFeHBpcmVz + TWVzc2FnZQ== TWVzc2FnZSBIZWFkZXJz SFRNTA== UGxhaW4gVGV4dA== @@ -145,7 +152,11 @@ UmF0aW5n UmVjaXBpZW50IFR5cGU= UmVmZXJlcg== + UmVmZXJyZXIgVVJM UmVsYXRpb24gVHlwZQ== + UmVwbGllZCBPbg== + UmVwbGllZA== + UmVxdWlyZSBMb2dpbg== UmVzZXQgdG8gZGVmYXVsdA== Q29tbWVudHM= Q3JlYXRlZCBieQ== @@ -156,6 +167,8 @@ U2VhcmNoIFRlcm0= U2VsZWN0b3I= QXR0ZW1wdHMg + U2VudCBPbg== + U2VudA== U2Vzc2lvbiBFbmQ= U2Vzc2lvbiBMb2cgSUQ= U2Vzc2lvbiBTdGFydA== @@ -173,13 +186,16 @@ VGhlc2F1cnVzIFRlcm0= VGhlc2F1cnVzIFR5cGU= VGl0bGU= + VG8gRS1tYWls VmFsdWU= VHlwZQ== VXNlcnM= TGFzdG5hbWUgRmlyc3RuYW1l VXNlcm5hbWU= + VXNlIFNlY3VyaXR5IEltYWdl RmllbGQgVmFsdWU= VmVyc2lvbg== + VmlzaWJpbGl0eQ== VmlzaWJsZQ== VmlzaXQgRGF0ZQ== QXNjZW5kaW5n @@ -504,6 +520,7 @@ RG93bmxvYWQgQ1NW RG93bmxvYWQgRXhwb3J0IEZpbGU= RG93bmxvYWQgTGFuZ3VhZ2UgRXhwb3J0 + RHJhZnQgQXZhaWxhYmxl Q29udGVudCBFZGl0b3I= WW91IGhhdmUgbm90IHNhdmVkIGNoYW5nZXMgdG8gdGhlIGl0ZW0geW91IGFyZSBlZGl0aW5nITxiciAvPkNsaWNrIE9LIHRvIGxvb3NlIGNoYW5nZXMgYW5kIGdvIHRvIHRoZSBzZWxlY3RlZCBzZWN0aW9uPGJyIC8+b3IgQ2FuY2VsIHRvIHN0YXkgaW4gdGhlIGN1cnJlbnQgc2VjdGlvbi4= RGVmYXVsdCB0ZXh0 @@ -512,11 +529,13 @@ RmlsZSBpcyBlbXB0eQ== RW5hYmxlZA== Q2FuJ3Qgc2F2ZSBhIGZpbGU= + Q29ubmVjdGlvbiBGYWlsZWQ= RXJyb3IgY29weWluZyBzdWJzZWN0aW9ucw== Q3VzdG9tIGZpZWxkIHdpdGggaWRlbnRpY2FsIG5hbWUgYWxyZWFkeSBleGlzdHM= RmlsZSBpcyB0b28gbGFyZ2U= SW52YWxpZCBGaWxlIEZvcm1hdA== aW52YWxpZCBvcHRpb24= + TG9naW4gRmFpbGVk RXJyb3IgbW92aW5nIHN1YnNlY3Rpb24= Q2FuJ3QgaW5oZXJpdCB0ZW1wbGF0ZSBmcm9tIHRvcCBjYXRlZ29yeQ== UGFzc3dvcmRzIGRvIG5vdCBtYXRjaCE= @@ -570,17 +589,22 @@ QmFja2dyb3VuZCBJbWFnZQ== QmFja2dyb3VuZCBQb3NpdGlvbg== QmFja2dyb3VuZCBSZXBlYXQ= + QmNj RWxlbWVudCBQb3NpdGlvbg== Qm9yZGVyIEJvdHRvbQ== Qm9yZGVyIExlZnQ= Qm9yZGVyIFJpZ2h0 Qm9yZGVycw== Qm9yZGVyIFRvcA== + Qm91bmNlIERhdGU= + Qm91bmNlIEVtYWls + Qm91bmNlIEluZm8= U2VjdGlvbg== U2VjdGlvbiBGb3JtYXQ= U2VjdGlvbiBJRA== U2VjdGlvbiBzZXBhcmF0b3I= U2VjdGlvbiBUZW1wbGF0ZQ== + Q2M= Q2hhbmdlcw== Q2hhcnNldA== Q2hlY2sgRHVwbGljYXRlcyBieQ== @@ -607,9 +631,11 @@ RWRpdG9ycyBQaWNr RWxhcHNlZCBUaW1l RS1tYWls + RS1tYWlsIENvbW11bmljYXRpb24gUm9sZQ== RW1haWxzIGluIFF1ZXVl RW1haWxzIFNlbnQ= RW1haWxzIFRvdGFs + RW5hYmxl RW5hYmxlZA== RXJyb3IgVGFn RXN0aW1hdGVkIFRpbWU= @@ -647,6 +673,7 @@ T25saW5lIEZvcm0= T25saW5lIEZvcm0gU3VibWl0dGVkIFRlbXBsYXRl U2hvcnQgVVJM + RnJvbSBFbWFpbA== RnJvbSAvIFRvIFVzZXI= RnJvbnQtRW5kIE9ubHk= QWxsb3cgUmVnaXN0cmF0aW9uIG9uIEZyb250LWVuZA== @@ -668,6 +695,7 @@ SW5wdXQgVGltZSBGb3JtYXQ= SW5zdGFsbCBNb2R1bGVz SW5zdGFsbCBQaHJhc2UgVHlwZXM= + SVAgQWRkcmVzcw== VXNlIGN1cnJlbnQgc2VjdGlvbiBhcyByb290IGZvciB0aGUgZXhwb3J0 UHJpbWFyeQ== UmVxdWlyZWQ= @@ -682,6 +710,7 @@ TGFzdCBOYW1l TGFzdCBSdW4gT24= TGFzdCBSdW4gU3RhdHVz + TGFzdCBVcGRhdGVkIE9u TGVmdA== TGluZSBlbmRpbmdz TGluZSBFbmRpbmdzIEluc2lkZSBGaWVsZHM= @@ -702,6 +731,7 @@ TWF4aW11bSBudW1iZXIgb2YgU2VjdGlvbnMgb24gSXRlbSBjYW4gYmUgYWRkZWQgdG8= Q3VzdG9tIE1lbnUgSWNvbiAoaWUuIGltZy9tZW51X3Byb2R1Y3RzLmdpZik= TWVudSBTdGF0dXM= + TWVzc2FnZQ== TWVzc2FnZSBCb2R5 UGxhaW4gVGV4dCBWZXJzaW9u TWVzc2FnZSBUeXBl @@ -715,6 +745,7 @@ TmFtZQ== TmV3 TmV4dCBSdW4gT24= + Tm90ZXM= T2NjdXJlZCBPbg== T3B0aW9ucw== T3B0aW9uIFRpdGxl @@ -735,6 +766,7 @@ UGhyYXNlIFR5cGU= UG9w UG9wdWxhcg== + UG9ydA== UG9zaXRpb24= UHJlZml4 UHJpbWFyeQ== @@ -743,11 +775,19 @@ UHJpbWFyeSBMYW5ndWFnZSBQaHJhc2U= T3JkZXI= UmF0aW5n + UmVmZXJyZXIgVVJM S2V5d29yZA== VHlwZQ== UmVtb3RlIFVSTA== UmVwbGFjZSBEdXBsaWNhdGVz UmVwbGFjZW1lbnQgVGFncw== + UmVwbGllZCBPbg== + UmVwbHkgQmNj + UmVwbHkgQ2M= + UmVwbHkgRnJvbSBFLW1haWw= + UmVwbHkgRnJvbSBOYW1l + UmVwbHkgTWVzc2FnZSBTaWduYXR1cmU= + UmVwbGllZA== UmVxdWlyZWQ= Q29tbWVudA== UnVsZSBUeXBl @@ -760,6 +800,9 @@ U3R5bGU= U2VsZWN0b3IgSUQ= U2VsZWN0b3IgTmFtZQ== + U2VudCBPbg== + U2VudA== + U2VydmVy U2Vzc2lvbiBMb2cgSUQ= U2ltcGxlIFNlYXJjaA== TmFtZQ== @@ -786,6 +829,7 @@ VGltZSBGb3JtYXQ= VGl0bGU= VG8= + VG8gRS1tYWls VG9w VHJhY2tpbmcgQ29kZQ== UGhyYXNl @@ -798,6 +842,7 @@ VXNlciBEb2N1bWVudGF0aW9uIFVSTA== VXNlciBHcm91cHM= VXNlcm5hbWU= + VXNlIFNlY3VyaXR5IEltYWdl UmUtZW50ZXIgUGFzc3dvcmQ= VmVyc2lvbg== VmlzaWJpbGl0eQ== @@ -815,6 +860,8 @@ QWxsIEZpbGVz Q1NWIEZpbGVz SW1hZ2UgRmlsZXM= + UE9QMyBTZXJ2ZXIgUG9ydC4gRm9yIGV4LiAiMTEwIiBmb3IgcmVndWxhciBjb25uZWN0aW9uLCAiOTk1IiBmb3Igc2VjdXJlIGNvbm5lY3Rpb24u + UE9QMyBTZXJ2ZXIgQWRkcmVzcy4gRm9yIGV4LiB1c2UgInNzbDovL3BvcC5nbWFpbC5jb20iIGZvciBHbWFpbCwgInBvcC5tYWlsLnlhaG9vLmNvbSIgZm9yIFlhaG9vLg== SG90 SFRNTA== SUQgRmllbGQ= @@ -880,6 +927,7 @@ QWZ0ZXI= QWxsb3c= QmVmb3Jl + Qm91bmNlZA== Q2FuY2VsZWQ= Q2l0eQ== Q29sb24= @@ -891,11 +939,15 @@ RGVzY3JpcHRpb24= RGlzYWJsZWQ= RWRpdG9yJ3MgUGljaw== - RW1haWw= + RS1tYWls + RS1tYWlsIEJvZHk= + RS1tYWlsIFN1YmplY3Q= + RXZlcnlvbmU= RXhhY3Q= RXhwaXJlZA== RmFpbGVk Rmlyc3QgTmFtZQ== + R3Vlc3RzIE9ubHk= aG91cihzKQ== SW5oZXJpdCBmcm9tIFBhcmVudA== SVAgQWRkcmVzcw== @@ -904,12 +956,17 @@ bWludXRlKHMp TW9kYWwgV2luZG93 bW9udGgocyk= + TmV3IEUtbWFpbA== Tm90IFByb2Nlc3NlZA== + Tm90IFJlcGxpZWQ= UGFydGlhbGx5IFByb2Nlc3NlZA== UGhvbmU= UG9wdXAgV2luZG93 UHJvY2Vzc2Vk UmF0aW5n + UmVjaXBpZW50IEUtbWFpbA== + UmVjaXBpZW50IE5hbWU= + UmVwbGllZA== UnVubmluZw== U2FtZSBXaW5kb3c= c2Vjb25kKHMp @@ -1114,6 +1171,7 @@ U1NMIFNldHRpbmdz U3lzdGVtIFNldHRpbmdz V2Vic2l0ZSBTZXR0aW5ncw== + U3VibWlzc2lvbiBOb3Rlcw== VGVtcGxhdGVz VGh1bWJuYWlsIEltYWdl VHJhbnNsYXRpb24= @@ -1231,6 +1289,7 @@ R2VuZXJhbA== Q3VzdG9t RS1tYWlsIFRlbXBsYXRlcw== + RS1tYWlsIENvbW11bmljYXRpb24= RW1haWwgRXZlbnRz RS1tYWlsIExvZw== RW1haWwgUXVldWU= @@ -1245,6 +1304,7 @@ SW1wb3J0IERhdGE= SXRlbXM= TGFiZWxz + TWVzc2FnZXM= UGFja2FnZSBDb250ZW50 UGVybWlzc2lvbnM= UHJvcGVydGllcw== @@ -1374,6 +1434,7 @@ QWdlbnRz QmFzZSBTdHlsZXM= QmxvY2sgU3R5bGVz + Qm91bmNlIFBPUDMgU2VydmVyIFNldHRpbmdz U2VjdGlvbnM= U2VsZWN0IHNlY3Rpb24= Q29sdW1uIFBpY2tlcg== @@ -1413,6 +1474,7 @@ RWRpdGluZyBTdHlsZXNoZWV0 RWRpdGluZyBUaGVtZQ== RWRpdGluZyBVc2Vy + RS1tYWlsIENvbW11bmljYXRpb24= RS1tYWlsIEV2ZW50cw== RS1tYWlscw== RS1tYWlsIFNldHRpbmdz @@ -1434,9 +1496,11 @@ TGFuZ3VhZ2VzIE1hbmFnZW1lbnQ= TG9hZGluZyAuLi4= TWFpbGluZ3M= + TWVzc2FnZXM= TW9kdWxlcw== TmV3IEFnZW50 TmV3IEZpbGU= + TmV3IFJlcGx5 TmV3IFRoZW1l TmV3IFRoZW1lIFRlbXBsYXRl TmV3IEJhc2UgU3R5bGU= @@ -1454,6 +1518,7 @@ UHJvcGVydGllcw== UmVsYXRlZCBTZWFyY2hlcw== UmVsYXRpb25z + UmVwbHkgUE9QMyBTZXJ2ZXIgU2V0dGluZ3M= Q29tbWVudHM= U2VsZWN0IEdyb3VwKHMp U2VsZWN0IFVzZXI= @@ -1471,6 +1536,7 @@ VXNlcnM= Vmlld2luZyBmb3JtIHN1Ym1pc3Npb24= Vmlld2luZyBNYWlsaW5nIExpc3Q= + Vmlld2luZyBSZXBseQ== VmlzaXRz V2Vic2l0ZQ== Q3Vyci4gU2VjdGlvbg== @@ -1537,12 +1603,15 @@ UmVidWlsZCBTZWN0aW9uIENhY2hl UmVjYWxjdWxhdGUgUHJpb3JpdGllcw== UmVmcmVzaA== + UmVwbHk= UmVzY2FuIFRoZW1lcw== + UmVzZW5k UmVzZXQ= UmVzZXQgUGVyc2lzdGVudCBTZXR0aW5ncw== UmVzZXQgVG8gQmFzZQ== UnVuIFNRTA== U2F2ZQ== + U2F2ZSBhcyBEcmFmdA== U2VhcmNo UmVzZXQ= U2VsZWN0IFVzZXI= @@ -1662,6 +1731,9 @@ U3ViamVjdDogQSBjYXRlZ29yeSBoYXMgYmVlbiBhcHByb3ZlZAoKWW91ciBzdWdnZXN0ZWQgY2F0ZWdvcnkgIjxpbnAyOmNfRmllbGQgbmFtZT0iTmFtZSIvPiIgaGFzIGJlZW4gYXBwcm92ZWQu U3ViamVjdDogWW91ciBDYXRlZ29yeSAiPGlucDI6Y19GaWVsZCBuYW1lPSJOYW1lIi8+IiBoYXMgYmVlbiBEZW5pZWQKCllvdXIgY2F0ZWdvcnkgc3VnZ2VzdGlvbiAiPGlucDI6Y19GaWVsZCBuYW1lPSJOYW1lIi8+IiBoYXMgYmVlbiBkZW5pZWQu U3ViamVjdDogQ29tbW9uIEZvb3RlciBUZW1wbGF0ZQoKPGJyLz48YnIvPg0KDQpTaW5jZXJlbHksPGJyLz48YnIvPg0KDQpXZWJzaXRlIGFkbWluaXN0cmF0aW9uLg== + U3ViamVjdDogTmV3IEVtYWlsIFJFUExZIFJlY2VpdmVkIGluICJGZWVkYmFjayBNYW5hZ2VyIiAoPGlucDI6Zm9ybXN1YnMuLWl0ZW1fRmllbGQgbmFtZT0iRm9ybVN1Ym1pc3Npb25JZCIvPikKCk5ldyBFbWFpbCBSRVBMWSBSZWNlaXZlZCBpbiAmcXVvdDtGZWVkYmFjayBNYW5hZ2VyJnF1b3Q7LjxiciAvPg0KPGJyIC8+DQpPcmlnaW5hbCBGZWVkYmFja0lkOiA8aW5wMjpmb3Jtc3Vicy4taXRlbV9GaWVsZCBuYW1lPSJGb3JtU3VibWlzc2lvbklkIi8+IDxiciAvPg0KT3JpZ2luYWwgU3ViamVjdDogPGlucDI6Zm9ybXN1YnMuLWl0ZW1fRm9ybUZpZWxkIHJvbGU9InN1YmplY3QiLz4gPGJyIC8+DQo8YnIgLz4NClBsZWFzZSBwcm9jZWVkIHRvIHRoZSBBZG1pbiBDb25zb2xlIGluIG9yZGVyIHRvIHJldmlldyBhbmQgcmVwbHkgdG8gdGhlIHVzZXIu + U3ViamVjdDogTmV3IEVtYWlsIC0gRGVsaXZlcnkgRmFpbHVyZSBSZWNlaXZlZCBpbiAiRmVlZGJhY2sgTWFuYWdlciIgKDxpbnAyOmZvcm1zdWJzLi1pdGVtX0ZpZWxkIG5hbWU9IkZvcm1TdWJtaXNzaW9uSWQiLz4pCgpOZXcgRW1haWwgRGVsaXZlcnkgRmFpbHVyZSBSZWNlaXZlZCBpbiAmcXVvdDtGZWVkYmFjayBNYW5hZ2VyJnF1b3Q7LjxiciAvPg0KPGJyIC8+DQpPcmlnaW5hbCBGZWVkYmFja0lkOiA8aW5wMjpmb3Jtc3Vicy4taXRlbV9GaWVsZCBuYW1lPSJGb3JtU3VibWlzc2lvbklkIi8+IDxiciAvPg0KT3JpZ2luYWwgU3ViamVjdDogPGlucDI6Zm9ybXN1YnMuLWl0ZW1fRm9ybUZpZWxkIHJvbGU9InN1YmplY3QiLz4gPGJyIC8+DQo8YnIgLz4NClBsZWFzZSBwcm9jZWVkIHRvIHRoZSBBZG1pbiBDb25zb2xlIGluIG9yZGVyIHRvIHJldmlldyBhbmQgcmVwbHkgdG8gdGhlIHVzZXIu + U3ViamVjdDogPGlucDI6bV9QYXJhbSBuYW1lPSJzdWJqZWN0Ii8+ICN2ZXJpZnk8aW5wMjpzdWJtaXNzaW9uLWxvZ19GaWVsZCBuYW1lPSJWZXJpZnlDb2RlIi8+Cgo8aW5wMjptX1BhcmFtIG5hbWU9Im1lc3NhZ2UiLz4= U3ViamVjdDogVGhhbmsgWW91IGZvciBDb250YWN0aW5nIFVzIQoKPHA+VGhhbmsgeW91IGZvciBjb250YWN0aW5nIHVzLiBXZSdsbCBiZSBpbiB0b3VjaCB3aXRoIHlvdSBzaG9ydGx5ITwvcD4= U3ViamVjdDogTmV3IGZvcm0gc3VibWlzc2lvbgoKPHA+Rm9ybSBoYXMgYmVlbiBzdWJtaXR0ZWQuIFBsZWFzZSBwcm9jZWVkIHRvIHRoZSBBZG1pbiBDb25zb2xlIHRvIHJldmlldyB0aGUgc3VibWlzc2lvbiE8L3A+ U3ViamVjdDogSW4tcG9ydGFsIHJlZ2lzdHJhdGlvbgoKRGVhciA8aW5wMjp1X0ZpZWxkIG5hbWU9IkZpcnN0TmFtZSIgLz4gPGlucDI6dV9GaWVsZCBuYW1lPSJMYXN0TmFtZSIgLz4sDQoNClRoYW5rIHlvdSBmb3IgcmVnaXN0ZXJpbmcgb24gPGlucDI6bV9CYXNlVXJsLz4uIFlvdXIgcmVnaXN0cmF0aW9uIGlzIG5vdyBhY3RpdmUu Index: core/install/install_data.sql =================================================================== --- core/install/install_data.sql (revision 13377) +++ core/install/install_data.sql (working copy) @@ -160,6 +160,9 @@ INSERT INTO Events (EventId, Event, ReplacementTags, Enabled, FrontEndOnly, FromUserId, Module, Description, Type) VALUES(DEFAULT, 'COMMON.FOOTER', NULL, 1, 0, NULL, 'Core', 'Common Footer Template', 1); INSERT INTO Events (EventId, Event, ReplacementTags, Enabled, FrontEndOnly, FromUserId, Module, Description, Type) VALUES(DEFAULT, 'FORM.SUBMITTED', NULL, 1, 0, NULL, 'Core:Category', 'This e-mail is sent to a user after filling in the Contact Us form', 1); INSERT INTO Events (EventId, Event, ReplacementTags, Enabled, FrontEndOnly, FromUserId, Module, Description, Type) VALUES(DEFAULT, 'FORM.SUBMITTED', NULL, 1, 0, NULL, 'Core:Category', 'This e-mail is sent to a user after filling in the Contact Us form', 0); +INSERT INTO Events (EventId, Event, ReplacementTags, Enabled, FrontEndOnly, FromUserId, Module, Description, Type) VALUES(DEFAULT, 'FORM.SUBMISSION.REPLY.TO.USER', NULL, 1, 0, NULL, 'Core:Category', 'Admin Reply to User Form Submission', 1); +INSERT INTO Events (EventId, Event, ReplacementTags, Enabled, FrontEndOnly, FromUserId, Module, Description, Type) VALUES(DEFAULT, 'FORM.SUBMISSION.REPLY.FROM.USER', NULL, 1, 0, NULL, 'Core:Category', 'User Replied to It\'s Form Submission', 1); +INSERT INTO Events (EventId, Event, ReplacementTags, Enabled, FrontEndOnly, FromUserId, Module, Description, Type) VALUES(DEFAULT, 'FORM.SUBMISSION.REPLY.FROM.USER.BOUNCED', NULL, 1, 0, NULL, 'Core:Category', 'Form Submission Admin Reply Delivery Failure', 1); INSERT INTO IdGenerator VALUES ('100'); Index: core/install/install_schema.sql =================================================================== --- core/install/install_schema.sql (revision 13377) +++ core/install/install_schema.sql (working copy) @@ -1064,28 +1064,102 @@ DisplayInGrid tinyint(1) NOT NULL DEFAULT '1', DefaultValue text, Validation tinyint(4) NOT NULL DEFAULT '0', + Visibility tinyint(4) NOT NULL DEFAULT '1', + EmailCommunicationRole tinyint(4) NOT NULL DEFAULT '0', PRIMARY KEY (FormFieldId), KEY `Type` (`Type`), KEY FormId (FormId), KEY Priority (Priority), KEY IsSystem (IsSystem), - KEY DisplayInGrid (DisplayInGrid) + KEY DisplayInGrid (DisplayInGrid), + KEY Visibility (Visibility), + KEY EmailCommunicationRole (EmailCommunicationRole) ); CREATE TABLE FormSubmissions ( FormSubmissionId int(11) NOT NULL AUTO_INCREMENT, FormId int(11) NOT NULL DEFAULT '0', SubmissionTime int(11) DEFAULT NULL, + IPAddress varchar(15) NOT NULL DEFAULT '', + ReferrerURL varchar(255) NOT NULL DEFAULT '', + LogStatus tinyint(3) unsigned NOT NULL DEFAULT '2', + LastUpdatedOn int(10) unsigned DEFAULT NULL, + Notes text, PRIMARY KEY (FormSubmissionId), KEY FormId (FormId), - KEY SubmissionTime (SubmissionTime) + KEY SubmissionTime (SubmissionTime), + KEY LogStatus (LogStatus), + KEY LastUpdatedOn (LastUpdatedOn) ); +CREATE TABLE SubmissionLog ( + SubmissionLogId int(11) NOT NULL AUTO_INCREMENT, + FormSubmissionId int(10) unsigned NOT NULL, + FromEmail varchar(255) NOT NULL DEFAULT '', + ToEmail varchar(255) NOT NULL DEFAULT '', + Cc text, + Bcc text, + `Subject` varchar(255) NOT NULL DEFAULT '', + Message text, + Attachment text, + ReplyStatus tinyint(3) unsigned NOT NULL DEFAULT '0', + SentStatus tinyint(3) unsigned NOT NULL DEFAULT '0', + SentOn int(10) unsigned DEFAULT NULL, + RepliedOn int(10) unsigned DEFAULT NULL, + VerifyCode varchar(32) NOT NULL DEFAULT '', + DraftId int(10) unsigned NOT NULL DEFAULT '0', + MessageId varchar(255) NOT NULL DEFAULT '', + BounceInfo text, + BounceDate int(11) DEFAULT NULL, + PRIMARY KEY (SubmissionLogId), + KEY FormSubmissionId (FormSubmissionId), + KEY ReplyStatus (ReplyStatus), + KEY SentStatus (SentStatus), + KEY SentOn (SentOn), + KEY RepliedOn (RepliedOn), + KEY VerifyCode (VerifyCode), + KEY DraftId (DraftId), + KEY BounceDate (BounceDate), + KEY MessageId (MessageId) +); + +CREATE TABLE Drafts ( + DraftId int(11) NOT NULL AUTO_INCREMENT, + FormSubmissionId int(10) unsigned NOT NULL DEFAULT '0', + CreatedOn int(10) unsigned DEFAULT NULL, + CreatedById int(11) NOT NULL, + Message text, + PRIMARY KEY (DraftId), + KEY FormSubmissionId (FormSubmissionId), + KEY CreatedOn (CreatedOn), + KEY CreatedById (CreatedById) +); + CREATE TABLE Forms ( - FormId int(11) NOT NULL auto_increment, - Title VARCHAR(255) NOT NULL DEFAULT '', + FormId int(11) NOT NULL AUTO_INCREMENT, + Title varchar(255) NOT NULL DEFAULT '', Description text, - PRIMARY KEY (FormId) + RequireLogin tinyint(4) NOT NULL DEFAULT '0', + UseSecurityImage tinyint(4) NOT NULL DEFAULT '0', + EnableEmailCommunication tinyint(4) NOT NULL DEFAULT '0', + ReplyFromName varchar(255) NOT NULL DEFAULT '', + ReplyFromEmail varchar(255) NOT NULL DEFAULT '', + ReplyCc varchar(255) NOT NULL DEFAULT '', + ReplyBcc varchar(255) NOT NULL DEFAULT '', + ReplyMessageSignature text, + ReplyServer varchar(255) NOT NULL DEFAULT '', + ReplyPort int(11) NOT NULL DEFAULT '110', + ReplyUsername varchar(255) NOT NULL DEFAULT '', + ReplyPassword varchar(255) NOT NULL DEFAULT '', + BounceEmail varchar(255) NOT NULL DEFAULT '', + BounceServer varchar(255) NOT NULL DEFAULT '', + BouncePort int(11) NOT NULL DEFAULT '110', + BounceUsername varchar(255) NOT NULL DEFAULT '', + BouncePassword varchar(255) NOT NULL DEFAULT '', + PRIMARY KEY (FormId), + KEY UseSecurityImage (UseSecurityImage), + KEY RequireLogin (RequireLogin), + KEY EnableEmailCommunication (EnableEmailCommunication) ); CREATE TABLE Semaphores ( Index: core/install/remove_schema.sql =================================================================== --- core/install/remove_schema.sql (revision 13377) +++ core/install/remove_schema.sql (working copy) @@ -65,6 +65,8 @@ DROP TABLE PageContent; DROP TABLE FormFields; DROP TABLE FormSubmissions; +DROP TABLE SubmissionLog; +DROP TABLE Drafts; DROP TABLE Forms; DROP TABLE Semaphores; DROP TABLE CachedUrls; Index: core/install/upgrades.sql =================================================================== --- core/install/upgrades.sql (revision 13377) +++ core/install/upgrades.sql (working copy) @@ -1710,4 +1710,88 @@ cv.GroupDisplayOrder = (SELECT ca7.GroupDisplayOrder FROM <%TABLE_PREFIX%>ConfigurationAdmin ca7 WHERE ca7.VariableName = cv.VariableName), cv.`Install` = (SELECT ca8.`Install` FROM <%TABLE_PREFIX%>ConfigurationAdmin ca8 WHERE ca8.VariableName = cv.VariableName); -DROP TABLE ConfigurationAdmin; \ No newline at end of file +DROP TABLE ConfigurationAdmin; + +ALTER TABLE Forms + ADD RequireLogin TINYINT NOT NULL DEFAULT '0', + ADD INDEX (RequireLogin), + ADD UseSecurityImage TINYINT NOT NULL DEFAULT '0', + ADD INDEX (UseSecurityImage), + ADD EnableEmailCommunication TINYINT NOT NULL DEFAULT '0', + ADD INDEX (EnableEmailCommunication), + ADD ReplyFromName VARCHAR(255) NOT NULL DEFAULT '', + ADD ReplyFromEmail VARCHAR(255) NOT NULL DEFAULT '', + ADD ReplyCc VARCHAR(255) NOT NULL DEFAULT '', + ADD ReplyBcc VARCHAR(255) NOT NULL DEFAULT '', + ADD ReplyMessageSignature TEXT, + ADD ReplyServer VARCHAR(255) NOT NULL DEFAULT '', + ADD ReplyPort INT(10) NOT NULL DEFAULT '110', + ADD ReplyUsername VARCHAR(255) NOT NULL DEFAULT '', + ADD ReplyPassword VARCHAR(255) NOT NULL DEFAULT '' + ADD BounceEmail VARCHAR(255) NOT NULL DEFAULT '', + ADD BounceServer VARCHAR(255) NOT NULL DEFAULT '', + ADD BouncePort INT(10) NOT NULL DEFAULT '110', + ADD BounceUsername VARCHAR(255) NOT NULL DEFAULT '', + ADD BouncePassword VARCHAR(255) NOT NULL DEFAULT ''; + +ALTER TABLE FormFields + ADD Visibility TINYINT NOT NULL DEFAULT '1', + ADD INDEX (Visibility), + ADD EmailCommunicationRole TINYINT NOT NULL DEFAULT '0', + ADD INDEX (EmailCommunicationRole); + +ALTER TABLE FormSubmissions + ADD IPAddress VARCHAR(15) NOT NULL DEFAULT '' AFTER SubmissionTime, + ADD ReferrerURL VARCHAR(255) NOT NULL DEFAULT '' AFTER IPAddress, + ADD LogStatus TINYINT UNSIGNED NOT NULL DEFAULT '2' AFTER ReferrerURL, + ADD LastUpdatedOn INT UNSIGNED NULL AFTER LogStatus, + ADD Notes TEXT NULL AFTER LastUpdatedOn, + ADD INDEX (LogStatus), + ADD INDEX (LastUpdatedOn); + +CREATE TABLE SubmissionLog ( + SubmissionLogId int(11) NOT NULL AUTO_INCREMENT, + FormSubmissionId int(10) unsigned NOT NULL, + FromEmail varchar(255) NOT NULL DEFAULT '', + ToEmail varchar(255) NOT NULL DEFAULT '', + Cc text, + Bcc text, + `Subject` varchar(255) NOT NULL DEFAULT '', + Message text, + Attachment text, + ReplyStatus tinyint(3) unsigned NOT NULL DEFAULT '0', + SentStatus tinyint(3) unsigned NOT NULL DEFAULT '0', + SentOn int(10) unsigned DEFAULT NULL, + RepliedOn int(10) unsigned DEFAULT NULL, + VerifyCode varchar(32) NOT NULL DEFAULT '', + DraftId int(10) unsigned NOT NULL DEFAULT '0', + MessageId varchar(255) NOT NULL DEFAULT '', + BounceInfo text, + BounceDate int(11) DEFAULT NULL, + PRIMARY KEY (SubmissionLogId), + KEY FormSubmissionId (FormSubmissionId), + KEY ReplyStatus (ReplyStatus), + KEY SentStatus (SentStatus), + KEY SentOn (SentOn), + KEY RepliedOn (RepliedOn), + KEY VerifyCode (VerifyCode), + KEY DraftId (DraftId), + KEY BounceDate (BounceDate), + KEY MessageId (MessageId) +); + +CREATE TABLE Drafts ( + DraftId int(11) NOT NULL AUTO_INCREMENT, + FormSubmissionId int(10) unsigned NOT NULL DEFAULT '0', + CreatedOn int(10) unsigned DEFAULT NULL, + CreatedById int(11) NOT NULL, + Message text, + PRIMARY KEY (DraftId), + KEY FormSubmissionId (FormSubmissionId), + KEY CreatedOn (CreatedOn), + KEY CreatedById (CreatedById) +); + +INSERT INTO Events (EventId, Event, ReplacementTags, Enabled, FrontEndOnly, FromUserId, Module, Description, Type) VALUES(DEFAULT, 'FORM.SUBMISSION.REPLY.TO.USER', NULL, 1, 0, NULL, 'Core:Category', 'Admin Reply to User Form Submission', 1); +INSERT INTO Events (EventId, Event, ReplacementTags, Enabled, FrontEndOnly, FromUserId, Module, Description, Type) VALUES(DEFAULT, 'FORM.SUBMISSION.REPLY.FROM.USER', NULL, 1, 0, NULL, 'Core:Category', 'User Replied to It\'s Form Submission', 1); +INSERT INTO Events (EventId, Event, ReplacementTags, Enabled, FrontEndOnly, FromUserId, Module, Description, Type) VALUES(DEFAULT, 'FORM.SUBMISSION.REPLY.FROM.USER.BOUNCED', NULL, 1, 0, NULL, 'Core:Category', 'Form Submission Admin Reply Delivery Failure', 1); \ No newline at end of file Index: core/kernel/constants.php =================================================================== --- core/kernel/constants.php (revision 13377) +++ core/kernel/constants.php (working copy) @@ -129,3 +129,28 @@ define('SESSION_LOG_ACTIVE', 0); define('SESSION_LOG_LOGGED_OUT', 1); define('SESSION_LOG_EXPIRED', 2); + + // form field visibility + define('FORM_FIELD_EVERYONE', 1); + define('FORM_FIELD_UNREGISTERED', 2); + + // form field e-mail communication roles + define('EMAIL_COMMUNICATION_ROLE_NAME', 1); + define('EMAIL_COMMUNICATION_ROLE_EMAIL', 2); + define('EMAIL_COMMUNICATION_ROLE_SUBJECT', 3); + define('EMAIL_COMMUNICATION_ROLE_BODY', 4); + + // form submission statuses + define('SUBMISSION_REPLIED', 1); // submission was replied by admin + define('SUBMISSION_NOT_REPLIED', 2); // submission has no client replies (no messages at all) + define('SUBMISSION_NEW_EMAIL', 3); // submission have new reply/email from client + define('SUBMISSION_BOUNCE', 4); // submission have bounce from client + + // submission log statuses + define('SUBMISSION_LOG_SENT', 1); + define('SUBMISSION_LOG_BOUNCE', 2); + define('SUBMISSION_LOG_REPLIED', 1); + + define('SUBMISSION_LOG_ATTACHMENT_PATH', WRITEBALE_BASE . '/user_files/submission_log/'); + + define('TIMENOW', adodb_mktime()); // for faster message processing \ No newline at end of file Index: core/units/drafts/draft_eh.php =================================================================== --- core/units/drafts/draft_eh.php (revision 0) +++ core/units/drafts/draft_eh.php (revision 0) @@ -0,0 +1,43 @@ +getObject(); + /* @var $object kDBItem */ + + $user_id = $this->Application->RecallVar('user_id'); + + $object->SetDBField('CreatedById', $user_id); + } + + /** + * Allows to load draft, that best matches given form submission + * + * @param kEvent $event + * @return int + */ + function getPassedID(&$event) + { + if ($event->Special == 'related') { + $form_submission =& $this->Application->recallObject('formsubs'); + /* @var $form_submission kDBItem */ + + return Array ( + 'FormSubmissionId' => $form_submission->GetID(), + 'CreatedById' => $this->Application->RecallVar('user_id'), + ); + } + + return parent::getPassedID($event); + } + } \ No newline at end of file Index: core/units/drafts/drafts_config.php =================================================================== --- core/units/drafts/drafts_config.php (revision 0) +++ core/units/drafts/drafts_config.php (revision 0) @@ -0,0 +1,48 @@ + 'draft', + 'ItemClass' => Array ('class' => 'kDBItem', 'file' => '', 'build_event' => 'OnItemBuild'), + 'ListClass' => Array ('class' => 'kDBList', 'file' => '', 'build_event' => 'OnListBuild'), + 'EventHandlerClass' => Array ('class' => 'DraftEventHandler', 'file' => 'draft_eh.php', 'build_event' => 'OnBuild'), + 'TagProcessorClass' => Array ('class' => 'kDBTagProcessor', 'file' => '', 'build_event' => 'OnBuild'), + + 'AutoLoad' => true, + + 'QueryString' => Array ( + 1 => 'id', + 2 => 'Page', + 3 => 'event', + 4 => 'mode', + ), + + 'IDField' => 'DraftId', + + 'ParentPrefix' => 'formsubs', + 'ForeignKey' => 'FormSubmissionId', + 'ParentTableKey' => 'FormSubmissionId', + 'AutoDelete' => true, + 'AutoClone' => true, + + 'TableName' => TABLE_PREFIX . 'Drafts', + + 'TitleField' => 'DraftId', + + 'ListSQLs' => Array ( + '' => ' SELECT %1$s.* %2$s FROM %1$s', + ), + + 'ListSortings' => Array ( + '' => Array ( + 'Sorting' => Array ('DraftId' => 'desc'), + ) + ), + + 'Fields' => Array ( + 'DraftId' => Array ('type' => 'int', 'not_null' => 1, 'default' => 0), + 'FormSubmissionId' => Array ('type' => 'int', 'not_null' => 1, 'default' => 0), + 'CreatedOn' => Array ('type' => 'int', 'formatter' => 'kDateFormatter', 'default' => '#NOW#'), + 'CreatedById' => Array ('type' => 'int', 'not_null' => 1, 'default' => 0), + 'Message' => Array ('type' => 'string', 'default' => NULL), + ), + ); \ No newline at end of file Index: core/units/form_fields/form_field_eh.php =================================================================== --- core/units/form_fields/form_field_eh.php (revision 0) +++ core/units/form_fields/form_field_eh.php (revision 0) @@ -0,0 +1,35 @@ +Application->LoggedIn() && !$this->Application->isAdminUser) { + return '%1$s.Visibility = ' . FORM_FIELD_EVERYONE; + } + + return ''; + } + + /** + * Shows fields based on user logged-in status + * + * @param kEvent $event + */ + function SetCustomQuery(&$event) + { + parent::SetCustomQuery($event); + + $object =& $event->getObject(); + /* @var $object kDBList */ + + $visibility_filter = $this->getVisiblilityFilter(); + + $object->addFilter('visibility_filter', $visibility_filter); + } + } Index: core/units/form_fields/form_fields_config.php =================================================================== --- core/units/form_fields/form_fields_config.php (revision 13377) +++ core/units/form_fields/form_fields_config.php (working copy) @@ -18,7 +18,7 @@ 'Prefix' => 'formflds', 'ItemClass' => Array('class'=>'kDBItem','file'=>'','build_event'=>'OnItemBuild'), 'ListClass' => Array('class'=>'kDBList','file'=>'','build_event'=>'OnListBuild'), - 'EventHandlerClass' => Array('class'=>'kDBEventHandler','file'=>'','build_event'=>'OnBuild'), + 'EventHandlerClass' => Array('class'=>'FormFieldEventHandler','file'=>'form_field_eh.php','build_event'=>'OnBuild'), 'TagProcessorClass' => Array('class'=>'FormFieldsTagProcessor','file'=>'form_fields_tp.php'), 'AutoLoad' => true, 'QueryString' => Array( @@ -77,6 +77,16 @@ 'DisplayInGrid' => Array('type' => 'int', 'formatter' => 'kOptionsFormatter', 'options' => Array(0 => 'la_No', 1 => 'la_Yes'), 'use_phrases' => 1, 'not_null' => 1, 'default' => 1), 'DefaultValue' => Array('type' => 'string', 'default' => NULL), 'Validation' => Array('type' => 'int', 'formatter' => 'kOptionsFormatter', 'options' => Array(0 => 'la_None', 1 => 'la_ValidationEmail'), 'use_phrases' => 1, 'not_null' => 1, 'default' => 0), + 'Visibility' => Array ( + 'type' => 'int', + 'formatter' => 'kOptionsFormatter', 'options' => Array (1 => 'la_opt_Everyone', 2 => 'la_opt_GuestsOnly'), 'use_phrases' => 1, + 'not_null' => 1, 'default' => 1 + ), + 'EmailCommunicationRole' => Array ( + 'type' => 'int', + 'formatter' => 'kOptionsFormatter', 'options' => Array (1 => 'la_opt_RecipientName', 2 => 'la_opt_RecipientEmail', 3 => 'la_opt_EmailSubject', 4 => 'la_opt_EmailBody'), 'use_phrases' => 1, + 'not_null' => 1, 'default' => 0 + ), ), 'VirtualFields' => Array( @@ -96,6 +106,8 @@ 'ElementType' => Array('title' => 'la_prompt_ElementType', 'filter_block' => 'grid_options_filter'), 'Required' => Array('title' => 'la_prompt_Required', 'filter_block' => 'grid_options_filter'), 'DisplayInGrid' => Array('title' => 'la_prompt_DisplayInGrid', 'filter_block' => 'grid_options_filter', 'width' => 150 ), + 'Visibility' => Array('title' => 'la_col_Visibility', 'filter_block' => 'grid_options_filter', 'width' => 100), + 'EmailCommunicationRole' => Array('title' => 'la_col_EmailCommunicationRole', 'filter_block' => 'grid_options_filter', 'width' => 150), ), ), ), Index: core/units/form_fields/form_fields_tp.php =================================================================== --- core/units/form_fields/form_fields_tp.php (revision 13377) +++ core/units/form_fields/form_fields_tp.php (working copy) @@ -45,7 +45,17 @@ */ function PrepareListElementParams(&$object, &$block_params) { - $object->SetDBField('DirectOptions', false); + if (!array_key_exists('SourcePrefix', $block_params)) { + // don't have source prefix in administrative console + $object->SetDBField('DirectOptions', false); + return ; + } + + $submission =& $this->Application->recallObject( $block_params['SourcePrefix'] ); + /* @var $submission kDBItem */ + + $options = $submission->GetFieldOptions('fld_' . $object->GetID()); + $object->SetDBField('DirectOptions', array_key_exists('options', $options) ? $options['options'] : false); } } \ No newline at end of file Index: core/units/form_submissions/form_submission_tp.php =================================================================== --- core/units/form_submissions/form_submission_tp.php (revision 13377) +++ core/units/form_submissions/form_submission_tp.php (working copy) @@ -19,4 +19,28 @@ return $this->Application->Phrase($phrase_name); } + + /** + * Allows to retrieve for submission field by it's name or role in email communications + * + * @param Array $params + * @return string + */ + function FormField($params) + { + $object =& $this->getObject($params); + /* @var $object kDBItem */ + + $form_submission_helper =& $this->Application->recallObject('FormSubmissionHelper'); + /* @var $form_submission_helper FormSubmissionHelper */ + + $formatted = !(array_key_exists('db', $params) && $params['db']); + $format = $formatted ? (array_key_exists('format', $params) ? $params['format'] : null) : null; + + if (array_key_exists('role', $params)) { + return $form_submission_helper->getFieldByRole($object, $params['role'], $formatted, $format); + } + + return $form_submission_helper->getFieldByName($params['name'], $formatted, $format); + } } Index: core/units/form_submissions/form_submissions_config.php =================================================================== --- core/units/form_submissions/form_submissions_config.php (revision 13377) +++ core/units/form_submissions/form_submissions_config.php (working copy) @@ -32,39 +32,70 @@ Array( 'Mode' => hAFTER, 'Conditional' => false, - 'HookToPrefix' => 'formsubs', //self + 'HookToPrefix' => '', 'HookToSpecial' => '*', 'HookToEvent' => Array('OnAfterConfigRead'), 'DoPrefix' => '', 'DoSpecial' => '', 'DoEvent' => 'OnBuildFormFields', ), - ), - 'TitlePresets' => Array( - 'default' => Array( 'new_status_labels' => Array('form'=>'!la_title_Adding_Form!'), - 'edit_status_labels' => Array('form'=>'!la_title_Editing_Form!'), - 'new_titlefield' => Array('form'=>''), + // Captcha processing + Array ( + 'Mode' => hAFTER, + 'Conditional' => false, + 'HookToPrefix' => '', + 'HookToSpecial' => '*', + 'HookToEvent' => Array('OnAfterConfigRead'), + 'DoPrefix' => 'captcha', + 'DoSpecial' => '*', + 'DoEvent' => 'OnPrepareCaptcha', + ), ), + 'TitlePresets' => Array( + 'default' => Array( + 'new_status_labels' => Array('form'=>'!la_title_Adding_Form!'), + 'edit_status_labels' => Array('form'=>'!la_title_Editing_Form!'), + ), + 'formsubs_list' => Array ( - 'prefixes' => Array('form', 'formsubs_List'), - 'format' => "!la_title_FormSubmissions! '#form_titlefield#'", - 'toolbar_buttons' => Array ('edit', 'delete', 'dbl-click'), - ), + 'prefixes' => Array('form', 'formsubs_List'), + 'format' => "!la_title_FormSubmissions! '#form_titlefield#'", + 'toolbar_buttons' => Array ('edit', 'delete', 'dbl-click'), + ), 'formsubs_view' => Array( - 'prefixes' => Array('formsubs'), - 'format' => "!la_title_ViewingFormSubmission!", - 'toolbar_buttons' => Array ('cancel', 'prev', 'next'), - ), + 'prefixes' => Array('formsubs'), + 'format' => "!la_title_ViewingFormSubmission!", + 'toolbar_buttons' => Array ('select', 'cancel', 'prev', 'next'), + ), - ), + 'submission_edit_logs' => Array ( + 'prefixes' => Array ('formsubs', 'submission-log_List'), + 'format' => "!la_title_ViewingFormSubmission! - !la_title_Messages! (#submission-log_recordcount#)" + ), + 'submission_log_edit' => Array ( + 'new_status_labels' => Array ('submission-log' => '!la_title_NewReply!'), + 'edit_status_labels' => Array ('submission-log' => '!la_title_ViewingReply!'), + + 'prefixes' => Array ('submission-log'), 'format' => "!la_title_ViewingFormSubmission! - #submission-log_status#" + ), + ), + + 'EditTabPresets' => Array ( + 'Default' => Array ( + Array ('title' => 'la_tab_General', 't' => 'submissions/submission_view', 'priority' => 1), + Array ('title' => 'la_tab_Messages', 't' => 'submissions/submission_edit_logs', 'priority' => 2), + ), + ), + 'PermSection' => Array('main' => 'in-portal:submissions'), 'IDField' => 'FormSubmissionId', /*'TitleField' => 'Name',*/ + 'StatusField' => Array ('LogStatus'), 'TableName' => TABLE_PREFIX.'FormSubmissions', 'ListSQLs' => Array( ''=>' SELECT %1$s.* %2$s FROM %1$s', @@ -79,16 +110,29 @@ 'AutoDelete' => true, 'AutoClone' => true,*/ + 'SubItems' => Array ('submission-log', 'draft'), + 'ListSortings' => Array( '' => Array( 'Sorting' => Array('SubmissionTime' => 'desc'), ) ), - 'Fields' => Array( - 'FormSubmissionId' => Array('type' => 'int', 'not_null' => 1,'default' => 0), - 'FormId' => Array('type' => 'int','not_null' => '1','default' => 0), - 'SubmissionTime' => Array('type' => 'int', 'formatter' => 'kDateFormatter', 'default' => '#NOW#'), + 'Fields' => Array( + 'FormSubmissionId' => Array('type' => 'int', 'not_null' => 1,'default' => 0), + 'FormId' => Array('type' => 'int','not_null' => '1','default' => 0), + 'SubmissionTime' => Array('type' => 'int', 'formatter' => 'kDateFormatter', 'default' => '#NOW#'), + 'IPAddress' => Array ('type' => 'string', 'max_len' => 15, 'not_null' => 1, 'default' => ''), + 'ReferrerURL' => Array ('type' => 'string', 'max_len' => 255, 'not_null' => 1, 'default' => ''), + + 'LogStatus' => Array ( + 'type' => 'int', + 'formatter' => 'kOptionsFormatter', 'options' => Array (1 => 'la_opt_Replied', 2 => 'la_opt_NotReplied', 3 => 'la_opt_NewEmail', 4 => 'la_opt_Bounce'), 'use_phrases' => 1, + 'not_null' => 1, 'required' => 1, 'default' => 2 + ), + + 'LastUpdatedOn' => Array ('type' => 'int', 'formatter' => 'kDateFormatter', 'default' => NULL), + 'Notes' => Array ('type' => 'string', 'default' => NULL), ), 'VirtualFields' => Array( ), @@ -96,10 +140,14 @@ ), 'Grids' => Array( 'Default' => Array( - 'Icons' => Array('default' => 'icon16_item.png'), + 'Icons' => Array('default' => 'icon16_item.png', 1 => 'icon16_replied.gif', 2 => 'icon16_not_replied.gif', 3 => 'icon16_new_email.gif', 4 => 'icon16_bounce.gif'), 'Fields' => Array( 'FormSubmissionId' => Array( 'title'=>'la_col_Id', 'data_block' => 'grid_checkbox_td', 'sort_field' => 'FormFieldId', 'filter_block' => 'grid_range_filter', 'width' => 60 ), 'SubmissionTime' => Array( 'title'=>'la_prompt_SumbissionTime', 'filter_block' => 'grid_date_range_filter', 'width' => 145 ), + 'IPAddress' => Array ('title' => 'la_col_IPAddress', 'filter_block' => 'grid_like_filter', 'width' => 100 ), + 'ReferrerURL' => Array ('title' => 'la_col_ReferrerURL', 'filter_block' => 'grid_like_filter', 'first_chars' => 100, 'width' => 200 ), + 'LogStatus' => Array ('title' => 'la_col_Status', 'filter_block' => 'grid_options_filter', 'width' => 100 ), + 'LastUpdatedOn' => Array ('title' => 'la_col_LastUpdatedOn', 'filter_block' => 'grid_date_range_filter', 'width' => 145 ), ), ), ), Index: core/units/form_submissions/form_submissions_eh.php =================================================================== --- core/units/form_submissions/form_submissions_eh.php (revision 13377) +++ core/units/form_submissions/form_submissions_eh.php (working copy) @@ -24,15 +24,28 @@ return true; } } + + $section = $event->getSection(); + $form_id = $this->Application->GetVar('form_id'); + + $event->setEventParam('PermSection', $section . ':' . $form_id); + return parent::CheckPermission($event); } + /** + * Always allow to view feedback form + * + */ function mapPermissions() { parent::mapPermissions(); + $permissions = Array( - 'OnEdit' => Array('self' => 'view', 'subitem' => 'view'), + 'OnItemBuild' => Array ('self' => true), + 'OnEdit' => Array ('self' => 'view', 'subitem' => 'view'), ); + $this->permMapping = array_merge($this->permMapping, $permissions); } @@ -66,6 +79,7 @@ $conf_grids = $this->Application->getUnitOption($event->Prefix, 'Grids'); $helper =& $this->Application->recallObject('InpCustomFieldsHelper'); + /* @var $helper InpCustomFieldsHelper */ $sql = 'SELECT * FROM ' . TABLE_PREFIX . 'FormFields @@ -73,36 +87,58 @@ ORDER BY Priority DESC'; $fields = $this->Conn->Query($sql, 'FormFieldId'); + $use_options = Array ('radio', 'select', 'checkbox'); + $check_visibility = $this->Application->LoggedIn() && !$this->Application->isAdminUser; + foreach ($fields as $field_id => $options) { - $conf_fields['fld_'.$field_id] = Array('type'=>'string', 'default'=>$options['DefaultValue']); - if ($options['Required']) { - $conf_fields['fld_'.$field_id]['required'] = 1; + $field_visible = $check_visibility ? $options['Visibility'] == FORM_FIELD_EVERYONE : true; + $field_options = Array('type' => 'string', 'default' => $options['DefaultValue']); + + if ($options['Required'] && $field_visible) { + $field_options['required'] = 1; } + if ($options['Validation'] == 1) { - $conf_fields['fld_'.$field_id]['formatter'] = 'kFormatter'; - $conf_fields['fld_'.$field_id]['regexp'] = '/^(' . REGEX_EMAIL_USER . '@' . REGEX_EMAIL_DOMAIN . ')$/i'; + $field_options['formatter'] = 'kFormatter'; + $field_options['regexp'] = '/^(' . REGEX_EMAIL_USER . '@' . REGEX_EMAIL_DOMAIN . ')$/i'; } + if ($options['DisplayInGrid']) { $title = $options['Prompt']; - if (substr($title, 0,1) == '+') { - $this->Application->Phrases->AddCachedPhrase('form_col_title'.$field_id, substr($title,1)); - $title = 'form_col_title'.$field_id; + + if (substr($title, 0, 1) == '+') { + $this->Application->Phrases->AddCachedPhrase('form_col_title' . $field_id, substr($title, 1)); + $title = 'form_col_title' . $field_id; } - $conf_grids['Default']['Fields']['fld_'.$field_id] = Array('title'=>$title, 'no_special' => 1, 'nl2br' => 1, 'first_chars' => 200, 'filter_block' => $this->_getFilterBlock($options['ElementType'])); - if ($options['Validation'] == 1) - { - $conf_grids['Default']['Fields']['fld_'.$field_id]['data_block'] = 'grid_email_td'; + + $conf_grids['Default']['Fields']['fld_' . $field_id] = Array ( + 'title' => $title, 'no_special' => 1, 'nl2br' => 1, 'first_chars' => 200, + 'filter_block' => $this->_getFilterBlock($options['ElementType']) + ); + + if ($options['Validation'] == 1) { + $conf_grids['Default']['Fields']['fld_' . $field_id]['data_block'] = 'grid_email_td'; } } - if ($options['ElementType'] == 'radio' || $options['ElementType'] == 'select') { - $conf_fields['fld_'.$field_id]['options'] = $helper->GetValuesHash( $options['ValueList'] ); - $conf_fields['fld_'.$field_id]['formatter'] = 'kOptionsFormatter'; + + if ($options['ElementType'] == 'checkbox' && !$options['ValueList']) { + // fix case, when user haven't defined any options for checkbox + $options['ValueList'] = '1=la_Yes||0=la_No'; } + + if (in_array($options['ElementType'], $use_options) && $options['ValueList']) { + // field type can have options and user have defined them too + $field_options['options'] = $helper->GetValuesHash( $options['ValueList'] ); + $field_options['formatter'] = 'kOptionsFormatter'; + } + if ($options['ElementType'] == 'password') { - $conf_fields['fld_'.$field_id]['formatter'] = 'kPasswordFormatter'; - $conf_fields['fld_'.$field_id]['encryption_method'] = 'plain'; - $conf_fields['fld_'.$field_id]['verify_field'] = 'fld_'.$field_id.'_verify'; + $field_options['formatter'] = 'kPasswordFormatter'; + $field_options['encryption_method'] = 'plain'; + $field_options['verify_field'] = 'fld_' . $field_id . '_verify'; } + + $conf_fields['fld_' . $field_id] = $field_options; } $this->Application->setUnitOption($event->Prefix, 'Fields', $conf_fields); @@ -116,14 +152,26 @@ $object->addFilter('form_filter','%1$s.FormId = '.$form_id); } + /** + * Allows user to see it's last feedback form data + * + * @param kEvent $event + * @return int + */ function getPassedID(&$event) { - if (!$this->Application->isAdminUser) { - // no way to see other user's form submission by giving it's ID directly in url - return 0; + if ($event->Special == 'last') { + // allow user to see his last submitted form + return $this->Application->RecallVar('last_submission_id'); } - return parent::getPassedID($event); + if ($this->Application->isAdminUser) { + // don't check ids in admin + return parent::getPassedID($event); + } + + // no way to see other user's form submission by giving it's ID directly in url + return 0; } /** @@ -139,6 +187,12 @@ return ; } + $object =& $event->getObject(); + /* @var $object kDBItem */ + + // allows user to view only it's last submission + $this->Application->StoreVar('last_submission_id', $object->GetID()); + $this->Application->EmailEventAdmin('FORM.SUBMITTED'); // $this->Application->EmailEventUser('FORM.SUBMITTED', null, 'to_email' => ''); @@ -153,4 +207,62 @@ $event->redirect = $alias_template ? $alias_template : $template; } + + /** + * Processes Captcha code + * + * @param kEvent $event + */ + function OnBeforeItemCreate(&$event) + { + parent::OnBeforeItemCreate($event); + + $object =& $event->getObject(); + /* @var $object kDBItem */ + + $object->SetDBField('IPAddress', $_SERVER['REMOTE_ADDR']); + $object->SetDBField('ReferrerURL', $_SERVER['HTTP_REFERER']); + + $form_submission_helper =& $this->Application->recallObject('FormSubmissionHelper'); + /* @var $form_submission_helper FormSubmissionHelper */ + + $form =& $form_submission_helper->getForm($object); + + // validate captcha code + if ($form->GetDBField('UseSecurityImage') && !$this->Application->LoggedIn()) { + $captcha_helper =& $this->Application->recallObject('CaptchaHelper'); + /* @var $captcha_helper kCaptchaHelper */ + + $captcha_helper->validateCode($event, false); + } + } + + /** + * Passes form_id, when using "Prev"/"Next" toolbar buttons + * + * @param kEvent $event + */ + function OnPreSaveAndGo(&$event) + { + parent::OnPreSaveAndGo($event); + + if ($event->status == erSUCCESS) { + $event->SetRedirectParam('pass', 'm,form,formsubs'); + } + } + + /** + * Saves edited item in temp table and goes + * to passed tabs, by redirecting to it with OnPreSave event + * + * @param kEvent $event + */ + function OnPreSaveAndGoToTab(&$event) + { + parent::OnPreSaveAndGoToTab($event); + + if ($event->status == erSUCCESS) { + $event->SetRedirectParam('pass', 'm,form,formsubs'); + } + } } \ No newline at end of file Index: core/units/forms/forms_config.php =================================================================== --- core/units/forms/forms_config.php (revision 13377) +++ core/units/forms/forms_config.php (working copy) @@ -20,6 +20,7 @@ 'ListClass' => Array('class'=>'kDBList','file'=>'','build_event'=>'OnListBuild'), 'EventHandlerClass' => Array('class'=>'FormsEventHandler','file'=>'forms_eh.php','build_event'=>'OnBuild'), 'TagProcessorClass' => Array('class'=>'FormsTagProcessor','file'=>'forms_tp.php','build_event'=>'OnBuild'), + 'AutoLoad' => true, 'QueryString' => Array( 1 => 'id', @@ -28,6 +29,12 @@ 4 => 'event', 5 => 'mode', ), + + 'RegularEvents' => Array ( + 'check_submission_repies' => Array('EventName' => 'OnProcessReplies', 'RunInterval' => 3600, 'Type' => reAFTER), + 'check_bounced_submission_repies' => Array('EventName' => 'OnProcessBouncedReplies', 'RunInterval' => 18000, 'Type' => reAFTER), + ), + 'Hooks' => Array( Array( 'Mode' => hAFTER, @@ -82,6 +89,12 @@ 'toolbar_buttons' => Array('select', 'cancel', 'prev', 'next', 'new_item', 'edit', 'delete', 'move_up', 'move_down', 'view', 'dbl-click'), ), + 'form_edit_emails' => Array ( + 'prefixes' => Array('form'), + 'format' => "#form_status# '#form_titlefield#' - !la_title_EmailCommunication!", + 'toolbar_buttons' => Array('select', 'cancel', 'prev', 'next'), + ), + 'form_field_edit' => Array ( 'prefixes' => Array('form', 'formflds'), 'new_status_labels' => Array('formflds'=>"!la_title_Adding_FormField!"), @@ -100,6 +113,7 @@ 'Default' => Array ( 'general' => Array ('title' => 'la_tab_General', 't' => 'forms/forms_edit', 'priority' => 1), 'fields' => Array ('title' => 'la_tab_Fields', 't' => 'forms/forms_edit_fields', 'priority' => 2), + 'emails' => Array ('title' => 'la_tab_EmailCommunication', 't' => 'forms/form_edit_emails', 'priority' => 3), ), ), @@ -118,10 +132,65 @@ ) ), - 'Fields' => Array( + 'Fields' => Array( 'FormId' => Array('type' => 'int', 'not_null' => 1, 'default' => 0, 'filter_type' => 'equals'), 'Title' => Array('type' => 'string','not_null' => 1, 'default' => '','required' => 1), 'Description' => Array('type' => 'string', 'formatter' => 'kFormatter', 'using_fck' => 1, 'default' => null), + 'RequireLogin' => Array ( + 'type' => 'int', + 'formatter' => 'kOptionsFormatter', 'options' => Array (1 => 'la_Yes', 0 => 'la_No'), 'use_phrases' => 1, + 'not_null' => 1, 'default' => 0 + ), + 'UseSecurityImage' => Array ( + 'type' => 'int', + 'formatter' => 'kOptionsFormatter', 'options' => Array (1 => 'la_Yes', 0 => 'la_No'), 'use_phrases' => 1, + 'not_null' => 1, 'default' => 0 + ), + 'EnableEmailCommunication' => Array ( + 'type' => 'int', + 'formatter' => 'kOptionsFormatter', 'options' => Array (1 => 'la_Yes', 0 => 'la_No'), 'use_phrases' => 1, + 'not_null' => 1, 'default' => 0 + ), + 'ReplyFromName' => Array ('type' => 'string', 'max_len' => 255, 'not_null' => 1, 'default' => ''), + 'ReplyFromEmail' => Array ('type' => 'string', 'max_len' => 255, 'not_null' => 1, 'default' => ''), + 'ReplyCc' => Array ('type' => 'string', 'max_len' => 255, 'not_null' => 1, 'default' => ''), + 'ReplyBcc' => Array ('type' => 'string', 'max_len' => 255, 'not_null' => 1, 'default' => ''), + 'ReplyMessageSignature' => Array ('type' => 'string', 'default' => NULL), + 'ReplyServer' => Array ( + 'type' => 'string', 'max_len' => 255, + 'error_msgs' => Array ( + 'connection_failed' => '!la_error_ConnectionFailed!', + 'message_listing_failed' => '!la_error_MessageListingFailed!', + ), + 'not_null' => 1, 'default' => '' + ), + 'ReplyPort' => Array ('type' => 'int', 'not_null' => 1, 'default' => 110), + 'ReplyUsername' => Array ( + 'type' => 'string', 'max_len' => 255, + 'error_msgs' => Array ( + 'login_failed' => '!la_error_LoginFailed!', + ), + 'not_null' => 1, 'default' => '' + ), + 'ReplyPassword' => Array ('type' => 'string', 'max_len' => 255, 'not_null' => 1, 'default' => ''), + 'BounceEmail' => Array ('type' => 'string', 'max_len' => 255, 'not_null' => 1, 'default' => ''), + 'BounceServer' => Array ( + 'type' => 'string', 'max_len' => 255, + 'error_msgs' => Array ( + 'connection_failed' => '!la_error_ConnectionFailed!', + 'message_listing_failed' => '!la_error_MessageListingFailed!', + ), + 'not_null' => 1, 'default' => '' + ), + 'BouncePort' => Array ('type' => 'int', 'not_null' => 1, 'default' => 110), + 'BounceUsername' => Array ( + 'type' => 'string', 'max_len' => 255, + 'error_msgs' => Array ( + 'login_failed' => '!la_error_LoginFailed!', + ), + 'not_null' => 1, 'default' => '' + ), + 'BouncePassword' => Array ('type' => 'string', 'max_len' => 255, 'not_null' => 1, 'default' => ''), ), 'Grids' => Array( 'Default' => Array( @@ -133,8 +202,11 @@ ), 'Fields' => Array( 'FormId' => Array( 'title'=>'la_col_Id', 'data_block' => 'grid_checkbox_td', 'filter_block' => 'grid_range_filter', 'width' => 60, ), - 'Title' => Array( 'title' => 'la_col_Title', 'filter_block' => 'grid_like_filter', 'width' => 250, ), + 'Title' => Array( 'title' => 'la_col_Title', 'filter_block' => 'grid_like_filter', 'width' => 220, ), 'Description' => Array( 'title' => 'la_col_Description', 'filter_block' => 'grid_like_filter', 'width' => 300, ), + 'RequireLogin' => Array ('title' => 'la_col_RequireLogin', 'filter_block' => 'grid_options_filter', 'width' => 80,), + 'UseSecurityImage' => Array ('title' => 'la_col_UseSecurityImage', 'filter_block' => 'grid_options_filter', 'width' => 110,), + 'EnableEmailCommunication' => Array ('title' => 'la_col_EnableEmailCommunication', 'filter_block' => 'grid_options_filter', 'width' => 120,), ), ), ), Index: core/units/forms/forms_eh.php =================================================================== --- core/units/forms/forms_eh.php (revision 13377) +++ core/units/forms/forms_eh.php (working copy) @@ -96,55 +96,43 @@ } /** - * Dynamically fills customdata config - * - * @param kEvent $event - */ - function OnCreateFormFields(&$event) - { - $cur_fields = $this->Conn->Query('DESCRIBE '.TABLE_PREFIX.'FormSubmissions', 'Field'); - $cur_fields = array_keys($cur_fields); + * Dynamically fills customdata config + * + * @param kEvent $event + */ + function OnCreateFormFields(&$event) + { + $cur_fields = $this->Conn->Query('DESCRIBE '.TABLE_PREFIX.'FormSubmissions', 'Field'); + $cur_fields = array_keys($cur_fields); - // keep all fields, that are not created on the fly (includes ones, that are added during customizations) - foreach ($cur_fields as $field_index => $field_name) { - if (!preg_match('/^fld_[\d]+/', $field_name)) { - unset($cur_fields[$field_index]); - } + // keep all fields, that are not created on the fly (includes ones, that are added during customizations) + foreach ($cur_fields as $field_index => $field_name) { + if (!preg_match('/^fld_[\d]+/', $field_name)) { + unset($cur_fields[$field_index]); } + } - $desired_fields = $this->Conn->GetCol('SELECT CONCAT(\'fld_\', FormFieldId) FROM '.TABLE_PREFIX.'FormFields ORDER BY FormFieldId'); + $desired_fields = $this->Conn->GetCol('SELECT CONCAT(\'fld_\', FormFieldId) FROM '.TABLE_PREFIX.'FormFields ORDER BY FormFieldId'); - $sql = array(); + $sql = array(); - $fields_to_add = array_diff($desired_fields, $cur_fields); - foreach ($fields_to_add as $field) { - $field_expression = $field.' Text NULL'; - $sql[] = 'ADD COLUMN '.$field_expression; - } + $fields_to_add = array_diff($desired_fields, $cur_fields); + foreach ($fields_to_add as $field) { + $field_expression = $field.' Text NULL'; + $sql[] = 'ADD COLUMN '.$field_expression; + } - $fields_to_drop = array_diff($cur_fields, $desired_fields); - foreach ($fields_to_drop as $field) { - $sql[] = 'DROP COLUMN '.$field; - } + $fields_to_drop = array_diff($cur_fields, $desired_fields); + foreach ($fields_to_drop as $field) { + $sql[] = 'DROP COLUMN '.$field; + } - if ($sql) { - $query = 'ALTER TABLE '.TABLE_PREFIX.'FormSubmissions '.implode(', ', $sql); - $this->Conn->Query($query); - } + if ($sql) { + $query = 'ALTER TABLE '.TABLE_PREFIX.'FormSubmissions '.implode(', ', $sql); + $this->Conn->Query($query); } - - /*function GetPassedId($event) - { - return 0; } - function LoadItem(&$event) - { - $object =& $event->getObject(); - $object->SetField('Id',0); - $object->SetId(0); - } -*/ /** * Enter description here... * @@ -210,4 +198,310 @@ $event->status = erFAIL; } } + + /** + * Don't use security image, when form requires login + * + * @param kEvent $event + */ + function OnBeforeItemCreate(&$event) + { + parent::OnBeforeItemCreate($event); + + $this->_validatePopSettings($event); + $this->_disableSecurityImage($event); + $this->_setRequired($event); + } + + /** + * Don't use security image, when form requires login + * + * @param kEvent $event + */ + function OnBeforeItemUpdate(&$event) + { + parent::OnBeforeItemUpdate($event); + + $this->_validatePopSettings($event); + $this->_disableSecurityImage($event); + $this->_setRequired($event); + } + + /** + * Validates POP3 settings (performs test connect) + * + * @param kEvent $event + */ + function _validatePopSettings(&$event) + { + $object =& $event->getObject(); + /* @var $object kDBItem */ + + $modes = Array ('Reply', 'Bounce'); + $fields = Array ('Server', 'Port', 'Username', 'Password'); + $changed_fields = array_keys( $object->GetChangedFields() ); + + foreach ($modes as $mode) { + $set = true; + $changed = false; + + foreach ($fields as $field) { + $value = $object->GetDBField($mode . $field); + + if (strlen( trim($value) ) == 0) { + $set = false; + break; + } + + if (!$changed && in_array($mode . $field, $changed_fields)) { + $changed = true; + } + } + + if ($set && $changed) { + // fields are set and at least on of them is changed + $connection_info = Array (); + + foreach ($fields as $field) { + $connection_info[ strtolower($field) ] = $object->GetDBField($mode . $field); + } + + $pop3_helper =& $this->Application->makeClass('POP3Helper', $connection_info, 10); + /* @var $pop3_helper POP3Helper */ + + switch ( $pop3_helper->initMailbox(true) ) { + case 'socket': + $object->SetError($mode . 'Server', 'connection_failed'); + break; + + case 'login': + $object->SetError($mode . 'Username', 'login_failed'); + break; + + case 'list': + $object->SetError($mode . 'Server', 'message_listing_failed'); + break; + } + } + } + + } + + /** + * Makes email communication fields required, when form uses email communication + * + * @param kEvent $event + */ + function _setRequired(&$event) + { + $object =& $event->getObject(); + /* @var $object kDBItem */ + + $required = $object->GetDBField('EnableEmailCommunication'); + $fields = Array ( + 'ReplyFromName', 'ReplyFromEmail', 'ReplyServer', 'ReplyPort', 'ReplyUsername', 'ReplyPassword', + ); + + if ($required && $object->GetDBField('BounceEmail')) { + $bounce_fields = Array ('BounceEmail', 'BounceServer', 'BouncePort', 'BounceUsername', 'BouncePassword'); + $fields = array_merge($fields, $bounce_fields); + } + + foreach ($fields as $field) { + $object->setRequired($field, $required); + } + } + + /** + * Don't use security image, when form requires login + * + * @param kEvent $event + */ + function _disableSecurityImage(&$event) + { + $object =& $event->getObject(); + /* @var $object kDBItem */ + + if ($object->GetDBField('RequireLogin')) { + $object->SetDBField('UseSecurityImage', 0); + } + } + + /** + * Queries pop3 server about new incoming mail + * + * @param kEvent $event + */ + function OnProcessReplies(&$event) + { + $this->_processMailbox($event, false); + } + + /** + * Queries pop3 server about new incoming mail + * + * @param kEvent $event + */ + function OnProcessBouncedReplies(&$event) + { + $this->_processMailbox($event, true); + } + + /** + * Queries pop3 server about new incoming mail + * + * @param kEvent $event + */ + function _processMailbox(&$event, $bounce_mode = false) + { + $this->Application->SetVar('client_mode', 1); + + $id_field = $this->Application->getUnitOption($event->Prefix, 'IDField'); + $table_name = $this->Application->getUnitOption($event->Prefix, 'TableName'); + + $sql = 'SELECT * + FROM ' . $table_name . ' + WHERE EnableEmailCommunication = 1'; + $forms = $this->Conn->Query($sql, $id_field); + + $mailbox_helper =& $this->Application->recallObject('MailboxHelper'); + /* @var $mailbox_helper MailboxHelper */ + + $field_prefix = $bounce_mode ? 'Bounce' : 'Reply'; + + foreach ($forms as $form_id => $form_info) { + $recipient_email = $bounce_mode ? $form_info['BounceEmail'] : $form_info['ReplyFromEmail']; + + if (!$recipient_email) { + continue; + } + + $mailbox_helper->process( + Array ( + 'server' => $form_info[$field_prefix . 'Server'], + 'port' => $form_info[$field_prefix . 'Port'], + 'username' => $form_info[$field_prefix . 'Username'], + 'password' => $form_info[$field_prefix . 'Password'] + ), + Array (&$this, 'isValidRecipient'), + Array (&$this, 'processEmail'), + Array ( + 'recipient_email' => $recipient_email, + 'bounce_mode' => $bounce_mode, + ) + ); + } + } + + function isValidRecipient($params) + { + $mailbox_helper =& $this->Application->recallObject('MailboxHelper'); + /* @var $mailbox_helper MailboxHelper */ + + $recipients = $mailbox_helper->getRecipients(); + $recipient_email = $params['recipient_email']; + + $emails_found = preg_match_all('/((' . REGEX_EMAIL_USER . ')(@' . REGEX_EMAIL_DOMAIN . '))/i', $recipients, $all_emails); + + if (is_array($all_emails)) { + for ($i = 0; $i < $emails_found; $i++) { + if ($all_emails[1][$i] == $recipient_email) { + // only read messages, that are addresses to submission reply email + return true; + } + } + } + + // If this is a forwarded message - we drop all the other aliases and deliver only to the x-forward to address; + if (preg_match('/((' . REGEX_EMAIL_USER . ')(@' . REGEX_EMAIL_DOMAIN . '))/i', $mailbox_helper->headers['x-forward-to'], $get_to_email)) { + if ($get_to_email[1] == $recipient_email) { + // only read messages, that are addresses to submission reply email + return true; + } + } + + return false; + } + + function processEmail($params, &$fields_hash) + { + if ($params['bounce_mode']) { + // mark original message as bounced + + $mailbox_helper =& $this->Application->recallObject('MailboxHelper'); + /* @var $mailbox_helper MailboxHelper */ + + if (!array_key_exists('attachments', $mailbox_helper->parsedMessage)) { + // for now only parse bounces based on attachments, skip other bounce types + return false; + } + + for ($i = 0; $i < count($mailbox_helper->parsedMessage['attachments']); $i++) { + $attachment =& $mailbox_helper->parsedMessage['attachments'][$i]; + + switch ($attachment['headers']['content-type']) { + case 'message/delivery-status': + // save as BounceInfo + $mime_decode_helper =& $this->Application->recallObject('MimeDecodeHelper'); + /* @var $mime_decode_helper MimeDecodeHelper */ + + $charset = $mailbox_helper->parsedMessage[ $fields_hash['MessageType'] ][0]['charset']; + $fields_hash['Message'] = $mime_decode_helper->convertEncoding($charset, $attachment['data']); + break; + + case 'message/rfc822': + // undelivered message + $fields_hash['Subject'] = $attachment['filename2'] ? $attachment['filename2'] : $attachment['filename']; + break; + } + } + } + + if (!preg_match('/^(.*) #verify(.*)$/', $fields_hash['Subject'], $regs)) { + // incorrect subject, no verification code + return false; + } + + $sql = 'SELECT ' . $this->Application->getUnitOption('submission-log', 'IDField') . ' + FROM ' . $this->Application->getUnitOption('submission-log', 'TableName') . ' + WHERE MessageId = ' . $this->Conn->qstr($fields_hash['MessageId']); + $found = $this->Conn->GetOne($sql); + + if ($found) { + // don't process same message twice + return false; + } + + $reply_to =& $this->Application->recallObject('submission-log.-reply-to', null, Array ('skip_autoload' => true)); + /* @var $reply_to kDBItem */ + + $reply_to->Load($regs[2], 'VerifyCode'); + if (!$reply_to->isLoaded()) { + // fake verification code OR feedback, containing submission log was deleted + return false; + } + + if ($params['bounce_mode']) { + // mark original message as bounced + $reply_to->SetDBField('BounceInfo', $fields_hash['Message']); + $reply_to->SetDBField('BounceDate_date', TIMENOW); + $reply_to->SetDBField('BounceDate_time', TIMENOW); + $reply_to->SetDBField('SentStatus', SUBMISSION_LOG_BOUNCE); + $reply_to->Update(); + + return true; + } + + $reply =& $this->Application->recallObject('submission-log.-reply', null, Array ('skip_autoload' => true)); + /* @var $reply kDBItem */ + + $reply->SetDBFieldsFromHash($fields_hash); + $reply->SetDBField('ReplyTo', $reply_to->GetID()); + $reply->SetDBField('FormSubmissionId', $reply_to->GetDBField('FormSubmissionId')); + $reply->SetDBField('ToEmail', $params['recipient_email']); + $reply->SetDBField('Subject', $regs[1]); // save subject without verification code + $reply->SetDBField('SentStatus', SUBMISSION_LOG_SENT); + + return $reply->Create(); + } } \ No newline at end of file Index: core/units/helpers/form_submission_helper.php =================================================================== --- core/units/helpers/form_submission_helper.php (revision 0) +++ core/units/helpers/form_submission_helper.php (revision 0) @@ -0,0 +1,105 @@ + EMAIL_COMMUNICATION_ROLE_NAME, + 'email' => EMAIL_COMMUNICATION_ROLE_EMAIL, + 'subject' => EMAIL_COMMUNICATION_ROLE_SUBJECT, + 'body' => EMAIL_COMMUNICATION_ROLE_BODY, + ); + + /** + * Returns submission field based on given role + * + * @param kDBItem $form_submission + * @param string $role + * @return string + */ + function getFieldByRole(&$form_submission, $role, $formatted = false, $format = null) + { + static $cache = Array (); + + $form_id = $form_submission->GetDBField('FormId'); + + if (!array_key_exists($form_id, $cache)) { + $id_field = $this->Application->getUnitOption('formflds', 'IDField'); + $table_name = $this->Application->getUnitOption('formflds', 'TableName'); + + $sql = 'SELECT ' . $id_field . ', EmailCommunicationRole + FROM ' . $table_name . ' + WHERE FormId = ' . $form_id . ' AND EmailCommunicationRole <> 0'; + $cache[$form_id] = $this->Conn->GetCol($sql, 'EmailCommunicationRole'); + } + + // convert string representation of role to numeric + if (!is_numeric($role)) { + $role = strtolower($role); + $role = array_key_exists($role, $this->roleNames) ? $this->roleNames[$role] : false; + } + + // get field by role + $field_id = array_key_exists($role, $cache[$form_id]) ? $cache[$form_id][$role] : false; + + if ($field_id) { + return $formatted ? $form_submission->GetField('fld_' . $field_id, $format) : $form_submission->GetDBField('fld_' . $field_id); + } + + return false; + } + + /** + * Returns submission field based on given name + * + * @param kDBItem $form_submission + * @param string $name + * @return string + */ + function getFieldByName(&$form_submission, $name, $formatted = false, $format = null) + { + static $cache = Array (); + + $form_id = $form_submission->GetDBField('FormId'); + + if (!array_key_exists($form_id, $cache)) { + $id_field = $this->Application->getUnitOption('formflds', 'IDField'); + $table_name = $this->Application->getUnitOption('formflds', 'TableName'); + + $sql = 'SELECT ' . $id_field . ', FieldName + FROM ' . $table_name . ' + WHERE FormId = ' . $form_id; + $cache[$form_id] = $this->Conn->GetCol($sql, 'FieldName'); + } + + if ($field_id) { + return $formatted ? $form_submission->GetField('fld_' . $field_id, $format) : $form_submission->GetDBField('fld_' . $field_id); + } + + return false; + } + + /** + * Returns form object field based on form submission + * + * @param $form_submission kDBItem + * @return kDBItem + */ + function &getForm(&$form_submission) + { + $form_id = $form_submission->GetDBField('FormId'); + + $form =& $this->Application->recallObject('form', null, Array ('skip_autoload' => true)); + /* @var $form kDBItem */ + + if (!$form->isLoaded() || ($form->GetID() != $form_id)) { + $form->Load($form_id); + } + + return $form; + } + } \ No newline at end of file Index: core/units/helpers/helpers_config.php =================================================================== --- core/units/helpers/helpers_config.php (revision 13377) +++ core/units/helpers/helpers_config.php (working copy) @@ -49,15 +49,20 @@ Array ('pseudo' => 'JSONHelper', 'class' => 'JSONHelper', 'file' => 'json_helper.php', 'build_event' => '', 'require_classes' => 'kHelper'), Array ('pseudo' => 'LanguageImportHelper', 'class' => 'LanguageImportHelper', 'file' => 'language_import_helper.php', 'build_event' => '', 'require_classes' => 'kHelper'), Array ('pseudo' => 'SkinHelper', 'class' => 'SkinHelper', 'file' => 'skin_helper.php', 'build_event' => '', 'require_classes' => 'kHelper'), - Array ('class' => 'SiteConfigHelper', 'pseudo' => 'SiteConfigHelper', 'file' => 'site_config_helper.php', 'build_event' => '', 'require_classes' => 'kHelper'), - Array ('class' => 'MenuHelper', 'pseudo' => 'MenuHelper', 'file' => 'menu_helper.php', 'build_event' => '', 'require_classes' => 'kHelper'), + Array ('pseudo' => 'SiteConfigHelper', 'class' => 'SiteConfigHelper', 'file' => 'site_config_helper.php', 'build_event' => '', 'require_classes' => 'kHelper'), + Array ('pseudo' => 'MenuHelper', 'class' => 'MenuHelper', 'file' => 'menu_helper.php', 'build_event' => '', 'require_classes' => 'kHelper'), - Array ('class' => 'InpCustomFieldsHelper', 'pseudo' => 'InpCustomFieldsHelper', 'file' => 'custom_fields_helper.php', 'build_event' => '', 'require_classes' => 'kHelper'), - Array ('class' => 'kCountryStatesHelper', 'pseudo' => 'CountryStatesHelper', 'file' => 'country_states_helper.php', 'build_event' => '', 'require_classes' => 'kHelper'), - Array ('class' => 'kBracketsHelper', 'pseudo' => 'BracketsHelper', 'file' => 'brackets_helper.php', 'build_event' => '', 'require_classes' => 'kHelper'), - Array ('class' => 'kXMLHelper', 'pseudo' => 'kXMLHelper', 'file' => 'xml_helper.php', 'build_event' => '', 'require_classes' => 'kHelper'), - Array ('class' => 'kCatDBItemExportHelper', 'pseudo' => 'CatItemExportHelper', 'file' => 'cat_dbitem_export_helper.php', 'build_event' => '', 'require_classes' => 'kHelper'), - Array ('class' => 'EmailMessageHelper', 'pseudo' => 'EmailMessageHelper', 'file' => 'email_message_helper.php', 'build_event' => '', 'require_classes' => 'kHelper'), - Array ('class' => 'ListHelper', 'pseudo' => 'ListHelper', 'file' => 'list_helper.php', 'build_event' => '', 'require_classes' => 'kHelper'), + Array ('pseudo' => 'InpCustomFieldsHelper', 'class' => 'InpCustomFieldsHelper', 'file' => 'custom_fields_helper.php', 'build_event' => '', 'require_classes' => 'kHelper'), + Array ('pseudo' => 'CountryStatesHelper', 'class' => 'kCountryStatesHelper', 'file' => 'country_states_helper.php', 'build_event' => '', 'require_classes' => 'kHelper'), + Array ('pseudo' => 'BracketsHelper', 'class' => 'kBracketsHelper', 'file' => 'brackets_helper.php', 'build_event' => '', 'require_classes' => 'kHelper'), + Array ('pseudo' => 'kXMLHelper', 'class' => 'kXMLHelper', 'file' => 'xml_helper.php', 'build_event' => '', 'require_classes' => 'kHelper'), + Array ('pseudo' => 'CatItemExportHelper', 'class' => 'kCatDBItemExportHelper', 'file' => 'cat_dbitem_export_helper.php', 'build_event' => '', 'require_classes' => 'kHelper'), + Array ('pseudo' => 'EmailMessageHelper', 'class' => 'EmailMessageHelper', 'file' => 'email_message_helper.php', 'build_event' => '', 'require_classes' => 'kHelper'), + Array ('pseudo' => 'ListHelper', 'class' => 'ListHelper', 'file' => 'list_helper.php', 'build_event' => '', 'require_classes' => 'kHelper'), + + Array ('pseudo' => 'FormSubmissionHelper', 'class' => 'FormSubmissionHelper', 'file' => 'form_submission_helper.php', 'build_event' => '', 'require_classes' => 'kHelper'), + Array ('pseudo' => 'MailboxHelper', 'class' => 'MailboxHelper', 'file' => 'mailbox_helper.php', 'build_event' => '', 'require_classes' => 'kHelper'), + Array ('pseudo' => 'POP3Helper', 'class' => 'POP3Helper', 'file' => 'pop3_helper.php', 'build_event' => '', 'require_classes' => 'kHelper'), + Array ('pseudo' => 'MimeDecodeHelper', 'class' => 'MimeDecodeHelper', 'file' => 'mime_decode_helper.php', 'build_event' => '', 'require_classes' => 'kHelper'), ), ); \ No newline at end of file Index: core/units/helpers/mailbox_helper.php =================================================================== --- core/units/helpers/mailbox_helper.php (revision 0) +++ core/units/helpers/mailbox_helper.php (revision 0) @@ -0,0 +1,480 @@ +Application->makeClass('POP3Helper', $connection_info); + /* @var $pop3_helper POP3Helper */ + + $connection_status = $pop3_helper->initMailbox(); + + if (is_string($connection_status)) { + return $connection_status; + } + + if (defined('DEBUG_MODE') && DEBUG_MODE && $this->Application->isDebugMode()) { + $this->Application->Debugger->appendHTML('Reading MAILBOX: ' . $connection_info['username']); + } + + // Figure out if all messages are huge + $only_big_messages = true; + $max_message_size = $this->maxMegabytes * (1024 * 1024); + + foreach ($pop3_helper->messageSizes as $message_size) { + if (($message_size <= $max_message_size) && ($max_message_size > 0)) { + $only_big_messages = false; + break; + } + } + + $count = $total_size = 0; + + foreach ($pop3_helper->messageSizes as $message_number => $message_size) { + // Too many messages? + if (($count++ > $this->maxMessages) && ($this->maxMessages > 0)) { + break; + } + + // Message too big? + if (!$only_big_messages && ($message_size > $max_message_size) && ($max_message_size > 0)) { + $this->_displayLogMessage('message #' . $message_number . ' too big, skipped'); + continue; + } + + // Processed enough for today? + if (($total_size > $max_message_size) && ($max_message_size > 0)) { + break; + } + + $total_size += $message_size; + $pop3_helper->getEmail($message_number, $message_source); + + $processed = $this->normalize($message_source, $verify_callback, $process_callback, $callback_params, $include_attachment_contents); + + if ($processed) { + // delete message from server immediatly after retrieving & processing + $pop3_helper->deleteEmail($message_number); + $this->_displayLogMessage('message #' . $message_number . ': processed'); + } + else { + $this->_displayLogMessage('message #' . $message_number . ': skipped'); + } + } + + $pop3_helper->close(); + + return 'success'; + } + + /** + * Displays log message + * + * @param string $text + */ + function _displayLogMessage($text) + { + if (defined('DEBUG_MODE') && DEBUG_MODE && $this->Application->isDebugMode()) { + $this->Application->Debugger->appendHTML($text); + } + } + + /** + * Takes an RFC822 formatted date, returns a unix timestamp (allowing for zone) + * + * @param string $rfcdate + * @return int + */ + function rfcToTime($rfcdate) + { + $date = strtotime($rfcdate); + + if ($date == -1) { + return false; + } + + return $date; + } + + /** + * Gets recipients from all possible headers + * + * @return string + */ + function getRecipients() + { + $ret = ''; + + // headers that could contain recipients + $recipient_headers = Array ( + 'to', 'cc', 'envelope-to', 'resent-to', 'delivered-to', + 'apparently-to', 'envelope-to', 'x-envelope-to', 'received', + ); + + foreach ($recipient_headers as $recipient_header) { + if (!array_key_exists($recipient_header, $this->headers)) { + continue; + } + + if (!is_array($this->headers["$recipient_header"])) { + $ret .= ' ' . $this->headers["$recipient_header"]; + } else { + $ret .= ' ' . implode(' ', $this->headers["$recipient_header"]); + } + } + + return $ret; + } + + /** + * "Flattens" the multi-demensinal headers array into a single dimension one + * + * @param Array $input + * @param string $add + * @return Array + */ + function flattenHeadersArray($input, $add = '') + { + $output = Array (); + + foreach ($input as $key => $value) { + if (!empty($add)) { + $newkey = ucfirst( strtolower($add) ); + } elseif (is_numeric($key)) { + $newkey = ''; + } else { + $newkey = ucfirst( strtolower($key) ); + } + + if (is_array($value)) { + $output = array_merge($output, $this->flattenHeadersArray($value, $newkey)); + } else { + $output[] = (!empty($newkey) ? $newkey . ': ' : '') . $value; + } + } + + return $output; + } + + /** + * Processes given message using given callbacks + * + * @param string $message + * @param Array $verify_callback + * @param Array $process_callback + * @param bool $include_attachment_contents + * @return bool + */ + function normalize($message, $verify_callback, $process_callback, $callback_params, $include_attachment_contents = true) + { + // Decode message + $this->decodeMime($message, $include_attachment_contents); + + // Init vars; $good will hold all the correct infomation from now on + $good = Array (); + + // trim() some stuff now instead of later + $this->headers['from'] = trim($this->headers['from']); + $this->headers['to'] = trim($this->headers['to']); + $this->headers['cc'] = array_key_exists('cc', $this->headers) ? trim($this->headers['cc']) : ''; + $this->headers['x-forward-to'] = array_key_exists('x-forward-to', $this->headers) ? $this->headers['x-forward-to'] : ''; + $this->headers['subject'] = trim($this->headers['subject']); + $this->headers['received'] = is_array($this->headers['received']) ? $this->headers['received'] : Array ($this->headers['received']); + + if (array_key_exists('return-path', $this->headers) && is_array($this->headers['return-path'])) { + $this->headers['return-path'] = implode(' ', $this->flattenHeadersArray($this->headers['return-path'])); + } + + // Create our own message-ID if it's missing + $message_id = array_key_exists('message-id', $this->headers) ? trim($this->headers['message-id']) : ''; + $good['emailid'] = $message_id ? $message_id : md5($message) . "@in-portal"; + + // Stops us looping in stupid conversations with other mail software + if (isset($this->headers['x-loop-detect']) && $this->headers['x-loop-detect'] > 2) { + return false; + } + + $esender =& $this->Application->recallObject('EmailSender'); + /* @var $esender kEmailSendingHelper */ + + // Get the return address + $return_path = ''; + + if (array_key_exists('return-path', $this->headers)) { + $return_path = $esender->ExtractRecipientEmail($this->headers['return-path']); + } + + if (!$return_path) { + if (array_key_exists('reply-to', $this->headers)) { + $return_path = $esender->ExtractRecipientEmail( $this->headers['reply-to'] ); + } + else { + $return_path = $esender->ExtractRecipientEmail( $this->headers['from'] ); + } + } + + // Get the sender's name & email + $good['fromemail'] = $esender->ExtractRecipientEmail($this->headers['from']); + $good['fromname'] = $esender->ExtractRecipientName($this->headers['from'], $good['fromemail']); + + // Get the list of recipients + if (!$verify_callback[0]->$verify_callback[1]($callback_params)) { + // error: mail is propably spam + return false; + } + + // Handle the subject + $good['subject'] = $this->headers['subject']; + + // Priorities rock + $good['priority'] = array_key_exists('x-priority', $this->headers) ? (int)$this->headers['x-priority'] : 0; + + switch ($good['priority']) { + case 1: case 5: break; + default: + $good['priority'] = 3; + } + + // If we have attachments it's about time we tell the user about it + if (array_key_exists('attachments', $this->parsedMessage) && is_array($this->parsedMessage['attachments'])) { + $good['attach'] = count( $this->parsedMessage['attachments'] ); + } else { + $good['attach'] = 0; + } + + // prepare message text (for replies, etc) + if (isset($this->parsedMessage['text'][0]) && trim($this->parsedMessage['text'][0]['body']) != '') { + $message_body = trim($this->parsedMessage['text'][0]['body']); + $message_type = 'text'; + } elseif (isset($this->parsedMessage['html']) && trim($this->parsedMessage['html'][0]['body']) != '') { + $message_body = trim($this->parsedMessage['html'][0]['body']); + $message_type = 'html'; + } else { + $message_body = '[no message]'; + $message_type = 'text'; + } + + // remove scripts + $message_body = preg_replace("/]*>[^<]+<\/script[^>]*>/is", '', $message_body); + $message_body = preg_replace("/]*>[^<]*<\/iframe[^>]*>/is", '', $message_body); + + if ($message_type == 'html') { + $message_body = $esender->ConvertToText($message_body); + } + + $mime_decode_helper =& $this->Application->recallObject('MimeDecodeHelper'); + /* @var $mime_decode_helper MimeDecodeHelper */ + + // convert to site encoding + $message_charset = $this->parsedMessage[$message_type][0]['charset']; + + if ($message_charset) { + $good['message'] = $mime_decode_helper->convertEncoding($message_charset, $message_body); + } + + if (array_key_exists('delivery-date', $this->headers)) { + // We found the Delivery-Date header (and it's not too far in the future) + $dateline = $this->rfcToTime($this->headers['delivery-date']); + + if ($dateline > TIMENOW + 86400) { + unset($dateline); + } + } + + // We found the latest date from the received headers + $received_timestamp = $this->headers['received'][0]; + $dateline = $this->rfcToTime(trim( substr($received_timestamp, strrpos($received_timestamp, ';') + 1) )); + + if ($dateline == $this->rfcToTime(0)) { + unset($dateline); + } + + if (!isset($dateline)) { + $dateline = TIMENOW; + } + + // save collected data to database + $fields_hash = Array ( + 'DeliveryDate' => $dateline, // date, when SMTP server received the message + 'ReceivedDate' => TIMENOW, // date, when message was retrieved from POP3 server + 'CreatedOn' => $this->rfcToTime($this->headers['date']), // date, when created on sender's computer + 'ReturnPath' => $return_path, + 'FromEmail' => $good['fromemail'], + 'FromName' => $good['fromname'], + 'To' => $this->headers['to'], + 'Subject' => $good['subject'], + 'Message' => $good['message'], + 'MessageType' => $message_type, + 'AttachmentCount' => $good['attach'], + 'MessageId' => $good['emailid'], + 'Source' => $message, + 'Priority' => $good['priority'], + 'Size' => strlen($message), + ); + + return $process_callback[0]->$process_callback[1]($callback_params, $fields_hash); + } + + /** + * Function that decodes the MIME message and creates the $this->headers and $this->parsedMessage data arrays + * + * @param string $message + * @param bool $include_attachments + * + */ + function decodeMime($message, $include_attachments = true) + { + $message = preg_replace("/\r?\n/", "\r\n", trim($message)); + + $mime_decode_helper =& $this->Application->recallObject('MimeDecodeHelper'); + /* @var $mime_decode_helper MimeDecodeHelper */ + + // 1. separate headers from bodies + $mime_decode_helper->InitHelper($message); + $decoded_message = $mime_decode_helper->decode(true, true, true); + + // 2. extract attachments + $this->parsedMessage = Array (); // ! reset value + $this->parseOutput($decoded_message, $this->parsedMessage, $include_attachments); + + // 3. add "other" attachments (text part, that is not maked as attachment) + if (array_key_exists('text', $this->parsedMessage) && count($this->parsedMessage['text']) > 1) { + for ($attach = 1; $attach < count($this->parsedMessage['text']); $attach++) { + $this->parsedMessage['attachments'][] = Array ( + 'data' => $this->parsedMessage['text']["$attach"]['body'], + ); + } + } + + $this->headers = $this->parsedMessage['headers']; // ! reset value + + if (empty($decoded_message->ctype_parameters['boundary'])) { + // when no boundary, then assume all message is it's text + $this->parsedMessage['text'][0]['body'] = $decoded_message->body; + } + } + + /** + * Returns content-id's from inline attachments in message + * + * @return Array + */ + function getContentIds() + { + $cids = Array(); + + if (array_key_exists('attachments', $this->parsedMessage) && is_array($this->parsedMessage['attachments'])) { + foreach ($this->parsedMessage['attachments'] as $attachnum => $attachment) { + if (!isset($attachment['headers']['content-id'])) { + continue; + } + + $cid = $attachment['headers']['content-id']; + + if (substr($cid, 0, 1) == '<' && substr($cid, -1) == '>') { + $cid = substr($cid, 1, -1); + } + + $cids["$attachnum"] = $cid; + } + } + + return $cids; + } + + /** + * Get more detailed information about attachments + * + * @param stdClass $decoded parsed headers & body as object + * @param Array $parts parsed parts + * @param bool $include_attachments + */ + function parseOutput(&$decoded, &$parts, $include_attachments = true) + { + $ctype = strtolower($decoded->ctype_primary . '/' . $decoded->ctype_secondary); + + // don't parse attached messages recursevely + if (!empty($decoded->parts) && $ctype != 'message/rfc822') { + for ($i = 0; $i < count($decoded->parts); $i++) { + $this->parseOutput($decoded->parts["$i"], $parts, $include_attachments); + } + } else/*if (!empty($decoded->disposition) && $decoded->disposition != 'inline' or 1)*/ { + switch ($ctype) { + case 'text/plain': + case 'text/html': + if (!empty($decoded->disposition) && ($decoded->disposition == 'attachment')) { + $parts['attachments'][] = Array ( + 'data' => $include_attachments ? $decoded->body : '', + 'filename' => array_key_exists('filename', $decoded->d_parameters) ? $decoded->d_parameters['filename'] : '', // from content-disposition + 'filename2' => $decoded->ctype_parameters['name'], // from content-type + 'type' => $decoded->ctype_primary, // "text" + 'encoding' => $decoded->headers['content-transfer-encoding'] + ); + } else { + $body_type = $decoded->ctype_secondary == 'plain' ? 'text' : 'html'; + $parts[$body_type][] = Array ( + 'content-type' => $ctype, + 'charset' => array_key_exists('charset', $decoded->ctype_parameters) ? $decoded->ctype_parameters['charset'] : 'ISO-8859-1', + 'body' => $decoded->body + ); + } + break; + + case 'message/rfc822': + // another e-mail as attachment + $parts['attachments'][] = Array ( + 'data' => $include_attachments ? $decoded->body : '', + 'filename' => array_key_exists('filename', $decoded->d_parameters) ? $decoded->d_parameters['filename'] : '', + 'filename2' => array_key_exists('name', $decoded->ctype_parameters) ? $decoded->ctype_parameters['name'] : $decoded->parts[0]->headers['subject'], + 'type' => $decoded->ctype_primary, // "message" + 'headers' => $decoded->headers // individual copy of headers with each attachment + ); + break; + + default: + if (!stristr($decoded->headers['content-type'], 'signature')) { + $parts['attachments'][] = Array ( + 'data' => $include_attachments ? $decoded->body : '', + 'filename' => array_key_exists('filename', $decoded->d_parameters) ? $decoded->d_parameters['filename'] : '', // from content-disposition + 'filename2' => $decoded->ctype_parameters['name'], // from content-type + 'type' => $decoded->ctype_primary, + 'headers' => $decoded->headers // individual copy of headers with each attachment + ); + } + + } + } + + $parts['headers'] = $decoded->headers; // headers of next parts overwrite previous part headers + } + + } \ No newline at end of file Index: core/units/helpers/mime_decode_helper.php =================================================================== --- core/units/helpers/mime_decode_helper.php (revision 0) +++ core/units/helpers/mime_decode_helper.php (revision 0) @@ -0,0 +1,482 @@ +_splitBodyHeader($message); + + $this->_headerPart = $header; + $this->_bodyPart = $body; + } + + /** + * Decodes email message, that was previously set using InitHelper method + * + * @param bool $decode_headers + * @param bool $include_bodies + * @param bool $decode_bodies + * @return stdClass + */ + function decode($decode_headers = false, $include_bodies = false, $decode_bodies = false) + { + $this->_decodeHeaders = $decode_headers; + $this->_includeBodies = $include_bodies; + $this->_decodeBodies = $decode_bodies; + + $ret = $this->decodePart($this->_headerPart, $this->_bodyPart); + + if ($ret === false) { + $this->raiseError($this->_lastErrorMessage); + + return false; + } + + return $ret; + } + + function decodePart($headers, $body, $default_ctype = 'text/plain', $only_headers = false) + { + $return = new stdClass; + + // process headers + $return->headers = Array (); + $headers = $this->_parseHeaders($headers, $this->_decodeHeaders); + $single_headers = Array ('subject', 'from', 'to', 'cc', 'reply-to', 'date'); + + foreach ($headers as $value) { + $header_name = strtolower($value['name']); + $header_value = $only_headers ? $this->_decodeHeader($value['value']) : $value['value']; + + if (array_key_exists($header_name, $return->headers) && !is_array($return->headers[$header_name]) && !in_array($header_name, $single_headers)) { + // this is not a single header, so convert it to array, when 2nd value is found + $return->headers[$header_name] = Array ( $return->headers[$header_name] ); + $return->headers[$header_name][] = $header_value; + } + elseif (array_key_exists($header_name, $return->headers) && !in_array($header_name, $single_headers)) { + $return->headers[$header_name][] = $header_value; + } + else { + $return->headers[$header_name] = $header_value; + } + } + + if ($only_headers) { + return $return->headers; + } + + foreach ($headers as $value) { + $header_name = strtolower($value['name']); + $header_value = $value['value']; + + switch ($header_name) { + case 'content-type': + $content_type = $this->_parseHeaderValue($header_value); + + if (preg_match('/([0-9a-z+.-]+)\/([0-9a-z+.-]+)/i', $content_type['value'], $regs)) { + // "text/plain", "text/html", etc. + $return->ctype_primary = $regs[1]; + $return->ctype_secondary = $regs[2]; + } + + if (array_key_exists('other', $content_type)) { + // "charset", etc. + foreach ($content_type['other'] as $p_name => $p_value) { + $return->ctype_parameters["$p_name"] = $p_value; + } + } + break; + + case 'content-disposition'; + $content_disposition = $this->_parseHeaderValue($header_value); + $return->disposition = $content_disposition['value']; + + if (array_key_exists('other', $content_disposition)) { + // "filename", etc. + foreach ($content_disposition['other'] as $p_name => $p_value) { + $return->d_parameters["$p_name"] = $p_value; + } + } + break; + + case 'content-transfer-encoding': + $content_transfer_encoding = $this->_parseHeaderValue($header_value); + break; + } + } + + // process message body + if (isset($content_type)) { + switch ( strtolower($content_type['value']) ) { + case 'text/plain': + case 'text/html': + if ($this->_includeBodies) { + $encoding = isset($content_transfer_encoding) ? $content_transfer_encoding['value'] : '7bit'; + $return->body = $this->_decodeBodies ? $this->_decodeBody($body, $encoding) : $body; + } + break; + + case 'multipart/parallel': + case 'multipart/report': // RFC1892 + case 'multipart/signed': // PGP + case 'multipart/digest': + case 'multipart/alternative': + case 'multipart/appledouble': + case 'multipart/related': + case 'multipart/mixed': + if (!isset($content_type['other']['boundary'])) { + $this->_lastErrorMessage = 'No boundary found for ' . $content_type['value'] . ' part'; + return false; + } + + $default_ctype = (strtolower($content_type['value']) === 'multipart/digest') ? 'message/rfc822' : 'text/plain'; + + $parts = $this->_boundarySplit($body, $content_type['other']['boundary']); + + for ($i = 0; $i < count($parts); $i++) { + list ($part_header, $part_body) = $this->_splitBodyHeader($parts[$i]); + $part = $this->decodePart($part_header, $part_body, $default_ctype); + + if ($part === false) { + // part is broken + $this->raiseError($this->_lastErrorMessage); + } + + $return->parts[] = $part; + } + break; + + case 'message/rfc822': + case 'message/disposition-notification': + // create another instance, not to interfear with main parser + $mime_decode_helper =& $this->Application->makeClass('MimeDecodeHelper'); + /* @var $mime_decode_helper MimeDecodeHelper */ + + $mime_decode_helper->InitHelper($body); + + $return->parts[] = $mime_decode_helper->decode(true, $this->_includeBodies, $this->_decodeBodies); + unset($mime_decode_helper); + break; + + default: + if ($this->_includeBodies) { + $encoding = isset($content_transfer_encoding) ? $content_transfer_encoding['value'] : '7bit'; + $return->body = $this->_decodeBodies ? $this->_decodeBody($body, $encoding) : $body; + } + break; + } + + } else { + $ctype = explode('/', $default_ctype); + $return->ctype_primary = $ctype[0]; + $return->ctype_secondary = $ctype[1]; + + if ($this->_includeBodies) { + $return->body = $this->_decodeBodies ? $this->_decodeBody($body) : $body; + } + } + + return $return; + } + + /** + * Divides message into header and body parts + * + * @param string $input + * @return Array + */ + function _splitBodyHeader($input) + { + if (strpos($input, "\r\n\r\n") === false) { + return Array ($input, ''); + } elseif (preg_match("/^(.*?)\r?\n\r?\n(.*)/s", $input, $match)) { + return Array ($match[1], $match[2]); + } else { + $this->_lastErrorMessage = 'Could not split header and body'; + + return false; + } + } + + /** + * Parses headers string into array and optionally decode them + * + * @param string $input + * @param bool $decode + * @return Array + */ + function _parseHeaders($input, $decode = false) + { + if (!$input) { + return Array (); + } + + $ret = Array (); + + // Unfold the input + $input = preg_replace("/\r\n/", "\n", $input); + $input = preg_replace("/\n(\t| )+/", ' ', $input); + $headers = explode("\n", trim($input)); + + foreach ($headers as $value) { + $pos = strpos($value, ':'); + $hdr_name = substr($value, 0, $pos); + $hdr_value = substr($value, $pos + 1); + + if ($hdr_value[0] == ' ') { + $hdr_value = substr($hdr_value, 1); + } + + $ret[] = Array ( + 'name' => $hdr_name, + 'value' => $decode ? $this->_decodeHeader($hdr_value) : $hdr_value + ); + } + + return $ret; + } + + /** + * Parses header value in following format (without quotes): "multipart/alternative; boundary=001636c9274051e332048498d8cc" + * + * @param string $input + * @return Array + */ + function _parseHeaderValue($input) + { + $ret = Array (); + $pos = strpos($input, ';'); + + if ($pos === false) { + $ret['value'] = trim($input); + + return $ret; + } + + // get text until first ";" + $ret['value'] = trim(substr($input, 0, $pos)); + $input = trim(substr($input, $pos + 1)); + + if (strlen($input) > 0) { + // This splits on a semi-colon, if there's no preceeding backslash + // Can't handle if it's in double quotes however. (Of course anyone + // sending that needs a good slap). + $parameters = preg_split('/\s*(?_quotedPrintableDecode($text); + $text = str_replace('_', ' ', $text); + preg_match_all('/=([a-f0-9]{2})/i', $text, $matches); + + foreach($matches[1] as $value) { + $text = str_replace('=' . $value, chr(hexdec($value)), $text); + } + break; + } + + $input = $this->convertEncoding($charset, str_replace($encoded, $text, $input)); + } + + return $input; + } + + /** + * Converts encoding to one, that site uses + * + * @param string $from_engoding + * @param string $text + * @return string + * @author Alex + */ + function convertEncoding($from_engoding, $text) + { + if (!function_exists('mb_convert_encoding')) { + // if mbstring extension not installed + return $text; + } + + static $to_encoding = false; + + if ($to_encoding === false) { + $language =& $this->Application->recallObject('lang.current'); + /* @var $language LanguagesItem */ + $to_encoding = $language->GetDBField('Charset'); + } + + return mb_convert_encoding($text, $to_encoding, $from_engoding); + + } + + /** + * Decodes message body + * + * @param string $input + * @param string $encoding + * @return string + */ + function _decodeBody($input, $encoding = '7bit') + { + switch (strtolower($encoding)) { + case 'quoted-printable': + return $this->_quotedPrintableDecode($input); + break; + + case 'base64': + return base64_decode($input); + break; + } + + // for 7bit, 8bit, anything else + return $input; + } + + /** + * Decodes "quoted-printable" encoding + * + * @param string $string + * @return string + */ + function _quotedPrintableDecode($string) + { + // Remove soft line breaks + $string = preg_replace("/=\r?\n/", '', $string); + + // Replace encoded characters + if (preg_match_all('/=[a-f0-9]{2}/i', $string, $matches)) { + $matches = array_unique($matches[0]); + foreach ($matches as $value) { + $string = str_replace($value, chr(hexdec(substr($value,1))), $string); + } + } + + return $string; + } + } \ No newline at end of file Index: core/units/helpers/pop3_helper.php =================================================================== --- core/units/helpers/pop3_helper.php (revision 0) +++ core/units/helpers/pop3_helper.php (revision 0) @@ -0,0 +1,339 @@ +serverInfo = $server_info; + + if (isset($connection_timeout)) { + $this->connectionTimeout = $connection_timeout; + } + } + + /** + * Check if we can connect and log into the mailbox + * + * @return string + */ + function initMailbox($dry_run = false) + { + if ($this->connect() === false) { + return 'socket'; + } + elseif ($this->auth() === false) { + $this->close(); + return 'login'; + } + elseif ($this->getMessageList() === false) { + $this->close(); + return 'list'; + } + elseif (empty($this->messageSizes)) { + $this->close(); + return 'empty'; // Still 'good' + } + elseif ($dry_run) { + $this->close(); + return 'success'; + } + + return true; + } + + /** + * Logs failed logins to our server + * + * @param int $error error code + * @param Array $error_array params of error + */ + function error($error, $error_array) + { + trigger_error('POP3 Error. Code: ' . $error . '; Params: ' . print_r($error_array, true), E_USER_WARNING); + } + + /** + * Connect to the server + * + * @return bool + */ + function connect() + { + // Apparently fsockopen() doesn't return false in these cases: + if (empty($this->serverInfo['server'])) { + return false; + } + + if ($this->connected == true) { + return true; + } + + $this->fp = @fsockopen($this->serverInfo['server'], intval($this->serverInfo['port']), $error_no, $error_str, $this->connectionTimeout); + + if (!is_resource($this->fp)) { + $this->error(101, $this->serverInfo); + return false; + } + + $this->connected = true; + $buffer = fgets($this->fp, 4096); + + if (substr($buffer, 0, 3) != '+OK') { + $this->error(101, $this->serverInfo); + $this->close(); + return false; + } + else { + return true; + } + } + + /** + * Close connection to the server + * + */ + function close() + { + if ($this->connected == true) { // and is_resource($this->fp)) { + $this->connected = false; + @fputs($this->fp, "QUIT\r\n"); + @fclose($this->fp); + } + } + + /** + * Login to the server + * + * @param bool $dry_run + * @return bool + */ + function auth($dry_run = false) + { + if (!is_resource($this->fp) && ($this->connect() === false)) { + return false; + } + + fputs($this->fp, 'USER ' . $this->serverInfo['username'] . "\r\n"); + $buffer = fgets($this->fp, 4096); + + if (substr($buffer, 0, 3) != '+OK') { + $this->close(); + return false; + } + + fputs($this->fp, 'PASS ' . $this->serverInfo['password'] . "\r\n"); + $buffer = fgets($this->fp, 4096); + + if (substr($buffer, 0, 3) != '+OK') { + $this->error(102, $this->serverInfo); + $this->close(); + + return false; + } + else { + if ($dry_run) { + $this->close(); + } + + return true; + } + } + + /** + * Get the list of messages and their ID's + * + * @return bool + */ + function getMessageList() + { + fputs($this->fp, "LIST\r\n"); + + if (substr(fgets($this->fp, 4096), 0, 3) != '+OK') { + $this->close(); + return false; + } + + // Store the message numbers and sizes + $buffer = fgets($this->fp, 4096); + + while ($buffer != ".\r\n") { + $msginfo = explode(' ', $buffer); + $this->messageSizes[ trim($msginfo[0]) ] = trim($msginfo[1]); + $buffer = fgets($this->fp, 4096); + } + + return true; + } + + /** + * Gets the size of a message + * + * @param int $message_number + * @return int + */ + function getSize($message_number) + { + return $this->messageSizes["$message_number"]; + } + + /** + * Gets email number $message_number from the server + * + * @param int $message_number + * @param string $source + * @return string + */ + function getEmail($message_number, &$source) + { + return $this->getData("RETR $message_number\r\n", $source); + } + + /** + * Gets the top $lines from the message + * + * @param int $message_number + * @param string $source + * @param int $lines + * @param string $stopat + * @param bool $onelineonly + * @return string + */ + function getTop($message_number, &$source, $lines = 0, $stopat = '', $onelineonly = false) + { + return $this->getData("TOP $message_number $lines\r\n", $source, $stopat, $onelineonly); + } + + /** + * Issues $command and returns the output + * + * @param string $command + * @param string $source + * @param string $stopat + * @param bool $onelineonly + * @return string + */ + function getData($command, &$source, $stopat = '', $onelineonly = false) + { + fputs($this->fp, $command); + + if (substr(fgets($this->fp, 4096), 0, 3) != '+OK') { + return false; + } + + $source = ''; + $buffer = fgets($this->fp, 4096); + + while ($buffer != ".\r\n") { + if (!$onelineonly) { + $source .= $buffer; + } + + if (!empty($stopat)) { + if (strtolower(substr(trim($buffer), 0, strlen($stopat))) == strtolower($stopat)) { + if ($onelineonly) { + $source = $buffer; + } + else { + $onelineonly = true; + } + + $stopat = ''; + } + } + + $buffer = fgets($this->fp, 4096); + } + + return true; + } + + /** + * Sends the given command to the server and returns true or false on success + * + * @param string $command + * @return bool + */ + function sendCommand($command) + { + fputs($this->fp, $command . "\r\n"); + $buffer = trim(fgets($this->fp, 4096)); + + if (substr($buffer, 0, 3) != '+OK') { + $this->error(103, Array ('cmd' => $command, 'resp' => $buffer)); + return false; + } + else { + return true; + } + } + + /** + * Delete message number $message_number from the server + * + * @param int $message_number + * @return bool + */ + function deleteEmail($message_number) + { + return $this->sendCommand("DELE $message_number"); + } + + /** + * Create new instance of object + * + * @return kBase + */ + function &makeClass($server_info, $connection_timeout = null) + { + $object = new POP3Helper($server_info, $connection_timeout); + + return $object; + } + } \ No newline at end of file Index: core/units/submission_log/submission_log_config.php =================================================================== --- core/units/submission_log/submission_log_config.php (revision 0) +++ core/units/submission_log/submission_log_config.php (revision 0) @@ -0,0 +1,116 @@ + 'submission-log', + 'ItemClass' => Array ('class' => 'kDBItem', 'file' => '', 'build_event' => 'OnItemBuild'), + 'ListClass' => Array ('class' => 'kDBList', 'file' => '', 'build_event' => 'OnListBuild'), + 'EventHandlerClass' => Array ('class' => 'SubmissionLogEventHandler', 'file' => 'submission_log_eh.php', 'build_event' => 'OnBuild'), + 'TagProcessorClass' => Array ('class' => 'SubmissionLogTagProcessor', 'file' => 'submission_log_tp.php', 'build_event' => 'OnBuild'), + + 'AutoLoad' => true, + + 'QueryString' => Array ( + 1 => 'id', + 2 => 'Page', + 3 => 'event', + 4 => 'form_id' + ), + + 'IDField' => 'SubmissionLogId', + + 'TableName' => TABLE_PREFIX . 'SubmissionLog', + + 'StatusField' => Array ('ReplyStatus'), + + 'ParentPrefix' => 'formsubs', + 'ForeignKey' => 'FormSubmissionId', + 'ParentTableKey' => 'FormSubmissionId', + 'AutoDelete' => true, + 'AutoClone' => true, + + 'TitleField' => 'Subject', + + 'ListSQLs' => Array ( + '' => ' SELECT %1$s.* %2$s FROM %1$s', + ), + + 'ListSortings' => Array ( + '' => Array ( + 'Sorting' => Array ('SubmissionLogId' => 'desc'), + ) + ), + + 'Fields' => Array ( + 'SubmissionLogId' => Array ('type' => 'int', 'not_null' => 1, 'default' => 0), + 'FormSubmissionId' => Array ('type' => 'int', 'not_null' => 1, 'default' => 0), + + 'FromEmail' => Array ( + 'type' => 'string', 'max_len' => 255, + 'regexp' => '/' . REGEX_EMAIL_USER . '@' . REGEX_EMAIL_DOMAIN . '/', + 'not_null' => 1, 'required' => 1, 'default' => '' + ), + + 'ToEmail' => Array ( + 'type' => 'string', 'max_len' => 255, + 'regexp' => '/' . REGEX_EMAIL_USER . '@' . REGEX_EMAIL_DOMAIN . '/', + 'not_null' => 1, 'required' => 1, 'default' => '' + ), + + 'Cc' => Array ('type' => 'string', 'default' => NULL), + 'Bcc' => Array ('type' => 'string', 'default' => NULL), + 'Subject' => Array ('type' => 'string', 'max_len' => 255, 'not_null' => 1, 'required' => 1, 'default' => ''), + 'Message' => Array ('type' => 'string', 'required' => 1, 'using_fck' => 1, 'default' => NULL), + + 'Attachment' => Array ( + 'type' => 'string', + 'formatter' => 'kUploadFormatter', 'upload_dir' => SUBMISSION_LOG_ATTACHMENT_PATH, + 'file_types' => '*.*', 'files_description' => '!la_hint_AnyFiles!', 'multiple' => 100, + 'default' => NULL + ), + + 'ReplyStatus' => Array ( + 'type' => 'int', + 'formatter' => 'kOptionsFormatter', 'options' => Array (1 => 'la_Yes', 0 => 'la_No'), 'use_phrases' => 1, + 'not_null' => 1, 'default' => 0 // create as not replied + ), + + 'SentStatus' => Array ( + 'type' => 'int', + 'formatter' => 'kOptionsFormatter', 'options' => Array (1 => 'la_Yes', 0 => 'la_No', 2 => 'la_opt_Bounce'), 'use_phrases' => 1, + 'not_null' => 1, 'default' => 0 // create as not sent + ), + + 'SentOn' => Array ('type' => 'int', 'formatter' => 'kDateFormatter', 'default' => NULL), + 'RepliedOn' => Array ('type' => 'int', 'formatter' => 'kDateFormatter', 'default' => NULL), + 'VerifyCode' => Array ('type' => 'string', 'max_len' => 32, 'not_null' => 1, 'default' => ''), + 'DraftId' => Array ('type' => 'int', 'not_null' => 1, 'default' => 0), + 'MessageId' => Array ('type' => 'string', 'max_len' => 255, 'not_null' => 1, 'default' => ''), + 'BounceInfo' => Array ('type' => 'string', 'default' => NULL), + 'BounceDate' => Array ('type' => 'int', 'formatter' => 'kDateFormatter', 'default' => NULL), + ), + + 'VirtualFields' => Array ( + 'ReplyTo' => Array ('type' => 'int', 'default' => null), + ), + + 'Grids' => Array ( + 'Default' => Array ( + 'Icons' => Array (0 => 'icon16_not_replied.gif', 1 => 'icon16_replied.gif'), + 'Fields' => Array ( + 'SubmissionLogId' => Array ('title' => 'la_col_Id', 'data_block' => 'grid_checkbox_td', 'filter_block' => 'grid_range_filter', ), + 'Subject' => Array ('title' => 'la_col_Subject', 'data_block' => 'grid_subject_td', 'filter_block' => 'grid_like_filter',), + 'Message' => Array ('title' => 'la_col_Message', 'filter_block' => 'grid_like_filter', 'first_chars' => 100), + 'ReplyStatus' => Array ('title' => 'la_col_ReplyStatus', 'filter_block' => 'grid_options_filter',), + 'SentStatus' => Array ('title' => 'la_col_SentStatus', 'filter_block' => 'grid_options_filter',), + 'FromEmail' => Array ('title' => 'la_col_FromEmail', 'filter_block' => 'grid_like_filter',), + 'ToEmail' => Array ('title' => 'la_col_ToEmail', 'filter_block' => 'grid_like_filter',), + 'SentOn' => Array ('title' => 'la_col_SentOn', 'filter_block' => 'grid_date_range_filter',), + 'RepliedOn' => Array ('title' => 'la_col_RepliedOn', 'filter_block' => 'grid_date_range_filter',), + 'Cc' => Array ('title' => 'la_col_Cc', 'filter_block' => 'grid_like_filter', 'hidden' => 1), + 'Bcc' => Array ('title' => 'la_col_Bcc', 'filter_block' => 'grid_like_filter', 'hidden' => 1), + 'BounceInfo' => Array ('title' => 'la_col_BounceInfo', 'filter_block' => 'grid_like_filter', 'hidden' => 1), + 'BounceDate' => Array ('title' => 'la_col_BounceDate', 'filter_block' => 'grid_date_range_filter', 'hidden' => 1), + ), + ), + ), + ); \ No newline at end of file Index: core/units/submission_log/submission_log_eh.php =================================================================== --- core/units/submission_log/submission_log_eh.php (revision 0) +++ core/units/submission_log/submission_log_eh.php (revision 0) @@ -0,0 +1,677 @@ + Array ('subitem' => 'add|edit'), + 'OnSaveDraft' => Array ('subitem' => 'add|edit'), + 'OnUseDraft' => Array ('subitem' => 'add|edit'), + 'OnDeleteDraft' => Array ('subitem' => 'add|edit'), + + 'OnProcessBounceMail' => Array ('subitem' => true), + ); + + $this->permMapping = array_merge($this->permMapping, $permissions); + } + + /** + * Checks event permissions + * + * @param kEvent $event + * @return bool + */ + function CheckPermission(&$event) + { + $section = $event->getSection(); + $form_id = $this->Application->GetVar('form_id'); + + if ($form_id) { + // copy form_id to env to be passed info upload links + $this->Application->SetVar($event->getPrefixSpecial() . '_form_id', $form_id); + } + else { + $form_id = $this->Application->GetVar($event->getPrefixSpecial() . '_form_id'); + } + + $event->setEventParam('PermSection', $section . ':' . $form_id); + + return parent::CheckPermission($event); + } + + /** + * Prepares new kDBItem object + * + * @param kEvent $event + * @access protected + */ + function OnNew(&$event) + { + parent::OnNew($event); + + $object =& $event->getObject(); + /* @var $object kDBItem */ + + $form_submission =& $this->Application->recallObject('formsubs'); + /* @var $form_submission kDBItem */ + + $form_submission_helper =& $this->Application->recallObject('FormSubmissionHelper'); + /* @var $form_submission_helper FormSubmissionHelper */ + + $form =& $form_submission_helper->getForm($form_submission); + + $from_email = $form->GetDBField('ReplyFromEmail'); + $to_email = $form_submission_helper->getFieldByRole($form_submission, EMAIL_COMMUNICATION_ROLE_EMAIL); + + if ($this->Application->GetVar('client_mode')) { + // debug code for sending email from client + $object->SetDBField('FromEmail', $to_email); + $object->SetDBField('ToEmail', $from_email); + + } + else { + $object->SetDBField('FromEmail', $from_email); + $object->SetDBField('ToEmail', $to_email); + } + + $object->SetDBField('Cc', $form->GetDBField('ReplyCc')); + $object->SetDBField('Bcc', $form->GetDBField('ReplyBcc')); + + $ids = $this->StoreSelectedIDs($event); + if ($ids) { + $org_message =& $this->Application->recallObject($event->Prefix . '.-item', null, Array ('skip_autoload' => true)); + /* @var $org_message kDBItem */ + + $org_message->Load( array_shift($ids) ); + // client could reply from different email, so compare to admin email! + if ($org_message->GetDBField('ToEmail') == $from_email) { + // can reply only to client email, not own :) + + // transform subject + $message_subject = $org_message->GetDBField('Subject'); + + if ($message_subject) { + $object->SetDBField('Subject', $this->_transformSubject($message_subject, 'Re')); + } + + // add signature + $message_body = $form->GetDBField('ReplyMessageSignature'); + + if ($org_message->GetDBField('Message')) { + // add replied marks + $message_body .= '> ' . preg_replace('/([\r]*\n)/', '\\1> ', $org_message->GetDBField('Message')); + } + + $object->SetDBField('ToEmail', $org_message->GetDBField('FromEmail')); // user client's email from reply + $object->SetDBField('Message', $message_body); + $object->SetDBField('ReplyTo', $org_message->GetID()); + } + } + else { + $sql = 'SELECT COUNT(*) + FROM ' . $object->TableName . ' + WHERE FormSubmissionId = ' . $form_submission->GetID(); + $replies_found = $this->Conn->GetOne($sql); + + if (!$replies_found) { + // 1st message from admin -> quote subject & text from feedback + $message_subject = $form_submission_helper->getFieldByRole($form_submission, EMAIL_COMMUNICATION_ROLE_SUBJECT); + + if ($message_subject) { + $object->SetDBField('Subject', $this->_transformSubject($message_subject, 'Re')); + } + + // add signature + $message_body = $form->GetDBField('ReplyMessageSignature'); + + // add replied marks + $original_message_body = $form_submission_helper->getFieldByRole($form_submission, EMAIL_COMMUNICATION_ROLE_BODY); + + if ($original_message_body) { + $message_body .= '> ' . preg_replace('/([\r]*\n)/', '\\1> ', $original_message_body); + } + + $object->SetDBField('Message', $message_body); + } + } + + $this->clearSelectedIDs($event); + } + + /** + * Parses $search string in subject and reformats it + * Used for replying and forwarding + * + * @param string $subject + * @param string $search + * @return string + */ + function _transformSubject($subject, $search = 'Re') + { + $regex = '/'.$search.'(\[([\d]+)\]){0,1}:/i'; + preg_match_all($regex, $subject, $regs); + + if ($regs[2]) { + $reply_count = 0; // reply count without numbers (equals to "re[1]") + $max_reply_number = 0; // maximal reply number + sort($regs[2], SORT_NUMERIC); // sort ascending (non-numeric replies first) + foreach ($regs[2] as $match) { + if (!$match) { + // found "re:" + $reply_count++; + } + elseif ($match > $max_reply) { + // found "re:[number]" + $max_reply_number = $match; + } + } + + return $search.'['.($reply_count + $max_reply_number + 1).']: '.trim(preg_replace($regex, '', $subject)); + } + + return $search.': '.$subject; + } + + /** + * Resends reply, that was not sent last time + * + * @param kEvent $event + */ + function OnResendReply(&$event) + { + $ids = $this->StoreSelectedIDs($event); + + if (!$ids) { + return ; + } + + $object =& $event->getObject( Array('skip_autoload' => true) ); + /* @var $object kDBItem */ + + $sql = 'SELECT f.ReplyFromEmail, sl.' . $object->IDField . ' + FROM ' . $object->TableName . ' sl + JOIN ' . $this->Application->getUnitOption('formsubs', 'TableName') . ' fs ON fs.FormSubmissionId = sl.FormSubmissionId + JOIN ' . $this->Application->getUnitOption('form', 'TableName') . ' f ON f.FormId = fs.FormId + WHERE sl.' . $object->IDField . ' IN (' . implode(',', $ids) . ')'; + $reply_emails = $this->Conn->GetCol($sql, $object->IDField); + + foreach ($ids as $id) { + $object->Load($id); + + // allow to send messages, that were successfully sended before :( + if (($object->GetDBField('ToEmail') != $reply_emails[$id]) && ($object->GetDBField('SentStatus') != SUBMISSION_LOG_SENT)) { + $object->SetOriginalField('SentStatus', 0); // reset sent status to update sent date automatically + + $this->_sendEmail($object); // resend email here + } + } + + $this->clearSelectedIDs($event); + + if (!$this->Application->GetVar('from_list')) { + $event->SetRedirectParam('opener', 'u'); + } + } + + /** + * Updates last operation dates for log record + * + * @param kEvent $event + */ + function OnBeforeItemCreate(&$event) + { + parent::OnBeforeItemCreate($event); + + $this->_validateRecipients($event); + $this->_updateStatusDates($event); + } + + /** + * Updates last operation dates for log record + * + * @param kEvent $event + */ + function OnBeforeItemUpdate(&$event) + { + parent::OnBeforeItemUpdate($event); + + $this->_validateRecipients($event); + $this->_updateStatusDates($event); + } + + /** + * Validates email recipients + * + * @param kEvent $event + */ + function _validateRecipients(&$event) + { + $object =& $event->getObject(); + /* @var $object kDBItem */ + + $esender =& $this->Application->recallObject('EmailSender'); + /* @var $esender kEmailSendingHelper */ + + $cc = $object->GetDBField('Cc'); + + if ($cc && ($esender->GetRecipients($cc) === false)) { + $object->SetError('Cc', 'invalid_format'); + } + + $bcc = $object->GetDBField('Bcc'); + + if ($bcc && ($esender->GetRecipients($bcc) === false)) { + $object->SetError('Bcc', 'invalid_format'); + } + } + + /** + * Generates verification code and sets it inside sent message + * + * @param kDBItem $object + * @return string + */ + function _generateVerificationCode(&$object) + { + $code = Array ( + $object->GetDBField('FromEmail'), + $object->GetDBField('ToEmail'), + $object->GetID(), + getmicrotime() + ); + + $object->SetDBField('VerifyCode', md5( implode('-', $code) )); + } + + /** + * Sends email based on fields from given submission-log record + * + * @param kDBItem $object + */ + function _sendEmail(&$object) + { + if ($this->Application->GetVar('client_mode')) { + return ; + } + + if (!$object->GetDBField('VerifyCode')) { + $this->_generateVerificationCode($object); + } + + $form_submission =& $this->_getFormSubmission($object); + + $form_submission_helper =& $this->Application->recallObject('FormSubmissionHelper'); + /* @var $form_submission_helper FormSubmissionHelper */ + + $form =& $form_submission_helper->getForm($form_submission); + + $send_params = Array ( + 'from_name' => $form->GetDBField('ReplyFromName'), + 'from_email' => $object->GetDBField('FromEmail'), + + 'to_email' => $object->GetDBField('ToEmail'), + + 'subject' => $object->GetDBField('Subject'), + 'message' => $object->GetDBField('Message'), + ); + + $to_name = $form_submission_helper->getFieldByRole($form_submission, EMAIL_COMMUNICATION_ROLE_NAME); + + if ($to_name) { + $send_params['to_name'] = $to_name; + } + + $esender =& $this->Application->recallObject('EmailSender'); + /* @var $esender kEmailSendingHelper */ + + $esender->SetReturnPath( $form->GetDBField('BounceEmail') ); + + if ($object->GetDBField('Cc')) { + $recipients = $esender->GetRecipients( $object->GetDBField('Cc') ); + + foreach ($recipients as $recipient_info) { + $esender->AddCc($recipient_info['Email'], $recipient_info['Name']); + } + } + + if ($object->GetDBField('Bcc')) { + $recipients = $esender->GetRecipients( $object->GetDBField('Bcc') ); + + foreach ($recipients as $recipient_info) { + $esender->AddBcc($recipient_info['Email'], $recipient_info['Name']); + } + } + + if ($object->GetDBField('Attachment')) { + $attachments = explode('|', $object->GetField('Attachment', 'file_paths')); + + foreach ($attachments as $attachment) { + $esender->AddAttachment($attachment); + } + } + + $this->Application->EmailEventAdmin('FORM.SUBMISSION.REPLY.TO.USER', null, $send_params); + + // mark as sent after sending is finished + $object->SetDBField('SentStatus', SUBMISSION_LOG_SENT); + + // reset bounce status before (re-)sending + $object->SetDBField('BounceInfo', NULL); + $object->SetDBField('BounceDate_date', NULL); + $object->SetDBField('BounceDate_time', NULL); + + if ($object->GetDBField('DraftId')) { + $temp_handler =& $this->Application->recallObject('draft_TempHandler', 'kTempTablesHandler'); + /* @var $temp_handler kTempTablesHandler */ + + $temp_handler->DeleteItems('draft', '', Array ($object->GetDBField('DraftId'))); + $object->SetDBField('DraftId', 0); + } + + $object->Update(); + } + + /** + * Sends new email after log record was created + * Updates last update time for submission + * + * @param kEvent $event + */ + function OnAfterItemCreate(&$event) + { + parent::OnAfterItemCreate($event); + + $object =& $event->getObject(); + /* @var $object kDBItem */ + + $this->_sendEmail($object); // send email + + $this->_updateSubmission($event); + + $reply_to = $object->GetDBField('ReplyTo'); + if (!$reply_to) { + $reply_to = $this->_getLastMessageId($event, !$this->Application->GetVar('client_mode')); + } + + if ($reply_to) { + // this is reply to other message -> mark it as replied + $org_message =& $this->Application->recallObject($event->Prefix . '.-item', null, Array ('skip_autoload' => true)); + /* @var $org_message kDBItem */ + + $org_message->Load($reply_to); + $org_message->SetDBField('ReplyStatus', SUBMISSION_LOG_REPLIED); + $org_message->Update(); + } + + if ($this->Application->GetVar('client_mode')) { + // new reply from client received -> send notification about it + $this->Application->EmailEventAdmin('FORM.SUBMISSION.REPLY.FROM.USER'); + } + } + + /** + * Returns last message id (client OR admin) + * + * @param kEvent $event + * @param bool $from_client + * @return int + */ + function _getLastMessageId(&$event, $from_client = false) + { + $object =& $event->getObject(); + /* @var $object kDBItem */ + + $form_submission =& $this->_getFormSubmission($object); + + $form_submission_helper =& $this->Application->recallObject('FormSubmissionHelper'); + /* @var $form_submission_helper FormSubmissionHelper */ + + $form =& $form_submission_helper->getForm($form_submission); + $reply_email = $form->GetDBField('ReplyFromEmail'); + + $sql = 'SELECT MAX(' . $object->IDField . ') + FROM ' . $object->TableName . ' + WHERE (FormSubmissionId = ' . $form_submission->GetID() . ') AND (ToEmail' . ($from_client ? ' = ' : ' <> ') . $this->Conn->qstr($reply_email) . ')'; + return $this->Conn->GetOne($sql); + } + + /** + * Updates last update time for submission + * + * @param kEvent $event + */ + function OnAfterItemUpdate(&$event) + { + parent::OnAfterItemUpdate($event); + + $this->_updateSubmission($event); + + $object =& $event->getObject(); + /* @var $object kDBItem */ + + // send out email event to admin for bouncing + if ( $object->GetOriginalField('SentStatus') != $object->GetDBField('SentStatus') + && $object->GetDBField('SentStatus') == SUBMISSION_LOG_BOUNCE ) { + + $this->Application->EmailEventAdmin('FORM.SUBMISSION.REPLY.FROM.USER.BOUNCED'); + } + } + + /** + * Sets last sent/reply dates based on field changes in log record + * + * @param kEvent $event + */ + function _updateStatusDates(&$event) + { + $object =& $event->getObject(); + /* @var $object kDBItem */ + + $now = adodb_mktime(); + + $sent_status = $object->GetDBField('SentStatus'); + if (($sent_status == SUBMISSION_LOG_SENT) && ($sent_status != $object->GetOriginalField('SentStatus'))) { + // sent status was set + $object->SetDBField('SentOn_date', $now); + $object->SetDBField('SentOn_time', $now); + } + + $reply_status = $object->GetDBField('ReplyStatus'); + if (($reply_status == SUBMISSION_LOG_REPLIED) && ($reply_status != $object->GetOriginalField('ReplyStatus'))) { + // sent status was set + $object->SetDBField('RepliedOn_date', $now); + $object->SetDBField('RepliedOn_time', $now); + } + } + + /** + * Returns form submission by given event of submission log + * + * @param kDBItem $object + * @return kDBItem + */ + function &_getFormSubmission(&$object) + { + $submission_id = $object->GetDBField('FormSubmissionId'); + + $form_submission =& $this->Application->recallObject('formsubs.-item', null, Array ('skip_autoload' => true)); + /* @var $form_submission kDBItem */ + + if ($form_submission->isLoaded() && ($form_submission->GetID() == $submission_id)) { + // already loaded AND has needed id + return $form_submission; + } + + $form_submission->Load($submission_id); + + return $form_submission; + } + + /** + * Sets last updated field for form submission + * + * @param kEvent $event + */ + function _updateSubmission(&$event) + { + $object =& $event->getObject(); + /* @var $object kDBItem */ + + $form_submission =& $this->_getFormSubmission($object); + + // 1. set last updated + $last_updated = max ($object->GetDBField('SentOn'), $object->GetDBField('RepliedOn')); + + if ($form_submission->GetDBField('LastUpdatedOn') < $last_updated) { + // don't set smaller last update, that currenly set + $form_submission->SetDBField('LastUpdatedOn_date', $last_updated); + $form_submission->SetDBField('LastUpdatedOn_time', $last_updated); + } + + // 2. update submission status + $form_submission_helper =& $this->Application->recallObject('FormSubmissionHelper'); + /* @var $form_submission_helper FormSubmissionHelper */ + + $form =& $form_submission_helper->getForm($form_submission); + $client_responce = $form->GetDBField('ReplyFromEmail') == $object->GetDBField('ToEmail'); + $replied = $object->GetDBField('ReplyStatus') == SUBMISSION_LOG_REPLIED; + + if (!$client_responce && !$replied) { + // admin sends new email to client + $form_submission->SetDBField('LogStatus', SUBMISSION_REPLIED); + } + elseif ($client_responce) { + // client email becomes replied OR receiving new unreplied email from client + $form_submission->SetDBField('LogStatus', $replied ? SUBMISSION_REPLIED : SUBMISSION_NEW_EMAIL); + } + + if ($object->GetDBField('SentStatus') == SUBMISSION_LOG_BOUNCE) { + // propagate bounce status from reply + $form_submission->SetDBField('LogStatus', SUBMISSION_BOUNCE); + } + + $form_submission->Update(); + } + + /** + * Saves current unsent message as draft + * + * @param kEvent $event + */ + function OnSaveDraft(&$event) + { + $object =& $event->getObject( Array('skip_autoload' => true) ); + /* @var $object kDBItem */ + + $draft =& $this->Application->recallObject('draft', null, Array('skip_autoload' => true)); + /* @var $draft kDBItem */ + + $items_info = $this->Application->GetVar( $event->getPrefixSpecial(true) ); + if ($items_info) { + foreach ($items_info as $id => $field_values) { + $object->setID($id); + $object->SetFieldsFromHash($field_values); + + $load_keys = Array ( + 'FormSubmissionId' => $object->GetDBField('FormSubmissionId'), + 'CreatedById' => $this->Application->RecallVar('user_id'), + ); + + // get existing draft for given submission and user + $draft->Load($load_keys); + + $draft->SetDBField('Message', $object->GetDBField('Message')); + + if ($draft->isLoaded()) { + $draft->Update(); + } + else { + $draft->SetDBFieldsFromHash($load_keys); + $draft->Create(); + } + } + } + + $this->Application->SetVar($event->getPrefixSpecial() . '_SaveEvent', 'OnCreate'); + $event->SetRedirectParam('opener', 'u'); + } + + /** + * Uses found draft instead of submission reply body + * + * @param kEvent $event + */ + function OnUseDraft(&$event) + { + $object =& $event->getObject( Array('skip_autoload' => true) ); + /* @var $object kDBItem */ + + $draft =& $this->Application->recallObject('draft', null, Array('skip_autoload' => true)); + /* @var $draft kDBItem */ + + $items_info = $this->Application->GetVar( $event->getPrefixSpecial(true) ); + if ($items_info) { + foreach ($items_info as $id => $field_values) { + $object->setID($id); + $object->SetFieldsFromHash($field_values); + + $load_keys = Array ( + 'FormSubmissionId' => $object->GetDBField('FormSubmissionId'), + 'CreatedById' => $this->Application->RecallVar('user_id'), + ); + + // get existing draft for given submission and user + $draft->Load($load_keys); + if ($draft->isLoaded()) { + $object->SetDBField('Message', $draft->GetDBField('Message')); + $object->SetDBField('DraftId', $draft->GetID()); + } + } + } + + $this->Application->SetVar($event->getPrefixSpecial() . '_SaveEvent', 'OnCreate'); + $event->redirect = false; + } + + /** + * Deletes draft, that matches given user and form submission + * + * @param kEvent $event + */ + function OnDeleteDraft(&$event) + { + $object =& $event->getObject( Array('skip_autoload' => true) ); + /* @var $object kDBItem */ + + $draft =& $this->Application->recallObject('draft', null, Array('skip_autoload' => true)); + /* @var $draft kDBItem */ + + $items_info = $this->Application->GetVar( $event->getPrefixSpecial(true) ); + if ($items_info) { + foreach ($items_info as $id => $field_values) { + $object->setID($id); + $object->SetFieldsFromHash($field_values); + $object->SetDBField('DraftId', 0); + + $load_keys = Array ( + 'FormSubmissionId' => $object->GetDBField('FormSubmissionId'), + 'CreatedById' => $this->Application->RecallVar('user_id'), + ); + + // get existing draft for given submission and user + $draft->Load($load_keys); + if ($draft->isLoaded()) { + $temp_handler =& $this->Application->recallObject('draft_TempHandler', 'kTempTablesHandler'); + /* @var $temp_handler kTempTablesHandler */ + + $temp_handler->DeleteItems('draft', '', Array ($draft->GetID())); + } + } + } + + $this->Application->SetVar($event->getPrefixSpecial() . '_SaveEvent', 'OnCreate'); + $event->redirect = false; + } + } \ No newline at end of file Index: core/units/submission_log/submission_log_tp.php =================================================================== --- core/units/submission_log/submission_log_tp.php (revision 0) +++ core/units/submission_log/submission_log_tp.php (revision 0) @@ -0,0 +1,99 @@ +getObject($params); + /* @var $object kDBItem */ + + $user_reply = $this->IsUserReply($params); + + return $user_reply && ($object->GetDBField('ReplyStatus') != SUBMISSION_LOG_REPLIED); + } + + /** + * Checks, that current log record is mail from client to admin + * + * @param Array $params + * @return bool + */ + function IsUserReply($params) + { + $object =& $this->getObject($params); + /* @var $object kDBItem */ + + $reply_email = $this->Application->ConfigValue('SubmissionReplyFromEmail'); + + return $object->GetDBField('ToEmail') == $reply_email; + } + + /** + * Checks if there is draft for given article + * + * @param Array $params + * @return bool + */ + function HasDraft($params) + { + if (!$this->IsNewItem($params)) { + // use drafts only for unsent (new) messages + return false; + } + + $object =& $this->getObject($params); + /* @var $object kDBItem */ + + $draft =& $this->Application->recallObject('draft', null, Array('skip_autoload' => true)); + /* @var $draft kDBItem */ + + $load_keys = Array ( + 'FormSubmissionId' => $object->GetDBField('FormSubmissionId'), + 'CreatedById' => $this->Application->RecallVar('user_id'), + ); + + // get existing draft for given submission and user + $draft->Load($load_keys); + + return $draft->isLoaded(); + } + + /** + * Lists all files, uploadeded to given field + * + * @param Array $params + * @return string + */ + function IterateFiles($params) + { + $object =& $this->getObject($params); + /* @var $object kDBItem */ + + $field = $this->SelectParam($params, 'name,field'); + $value = $object->GetDBField($field); + + if (!$value) { + return ''; + } + + $ret = ''; + $files = explode('|', $value); + $block_params = $this->prepareTagParams($params); + $block_params['name'] = $params['render_as']; + + foreach ($files as $file) { + $object->SetDBField($field, $file); + $ret .= $this->Application->ParseBlock($block_params); + } + + $object->SetDBField($field, $value); + + return $ret; + } + } \ No newline at end of file