Commit 1fe29131 authored by Arnold Hendriks's avatar Arnold Hendriks

Merge branch 'feature/add-form-feedback-mail-attachments' into 'master'

Add form feedback mail attachments

See merge request !119
parents 9e775a1f f4d12e34
......@@ -380,6 +380,7 @@
<xs:attribute name="receiverfield" type="xs:string" />
<xs:attribute name="bcc" type="xs:string" />
<xs:attribute name="sendoncanceltoo" type="xs:boolean" /> <!-- LEGACY no longer supported -->
<xs:attribute name="attachmentsid" type="xs:string" />
</xs:complexType>
</xs:element>
......
......@@ -248,6 +248,13 @@
<member name="fixformoptions" type="record" /> <!-- archived original condition in case we ever need it. at some point we can drop this member -->
</contenttype>
<!-- The contenttype used to store mail handler attachments in an instance -->
<contenttype namespace="http://www.webhare.net/xmlns/publisher/mailhandlerattachments">
<member name="attachments" type="array">
<member name="file" type="file" />
</member>
</contenttype>
<!-- form as widget
we can't set the standard form as a widget, because we can't control
......
......@@ -2416,12 +2416,15 @@
<textgroup gid="mailhandler"> <!-- tolliumapps.formedit.handlers.mailhandler -->
<text tid="addresults">Add the results to the email</text>
<text tid="applytemplate">Mail design</text>
<text tid="attachments">Attachments</text>
<text tid="bcc">BCC</text>
<text tid="bcc-desc">Multiple addresses are separated by commas</text>
<text tid="checkduplicates">Check for duplicate email addresses</text>
<text tid="condition">Condition</text>
<text tid="editmailto">Edit mail to</text>
<text tid="emailcontents">Email message</text>
<text tid="file">File</text>
<text tid="files">These files will be sent along the email as attachments</text>
<text tid="fsobject">Choose an email template:</text>
<text tid="mailto">Mail to</text>
<text tid="moduletemplate">Template in module:</text>
......
......@@ -2416,12 +2416,15 @@
<textgroup gid="mailhandler"> <!-- tolliumapps.formedit.handlers.mailhandler -->
<text tid="addresults">Voeg de resultaten bij de e-mail</text>
<text tid="applytemplate">Maildesign</text>
<text tid="attachments">Bijlagen</text>
<text tid="bcc">BCC</text>
<text tid="bcc-desc">Meerdere adressen worden gescheiden door komma's</text>
<text tid="checkduplicates">Controleer op dubbele e-mailadressen</text>
<text tid="condition">Voorwaarde</text>
<text tid="editmailto">Wijzig mail naar</text>
<text tid="emailcontents">E-mailbericht</text>
<text tid="file">Bestand</text>
<text tid="files">Deze bestanden worden als bijlagen bij de e-mail meegezonden</text>
<text tid="fsobject">Kies een sjabloon:</text>
<text tid="mailto">Mail naar</text>
<text tid="moduletemplate">Sjabloon in module:</text>
......
......@@ -8,6 +8,8 @@ LOADLIB "wh::internet/smtp.whlib";
LOADLIB "wh::internet/urls.whlib";
LOADLIB "wh::xml/xsd.whlib";
LOADLIB "mod::tollium/lib/screenbase.whlib";
LOADLIB "mod::system/lib/dialogs.whlib";
LOADLIB "mod::system/lib/mailer.whlib";
LOADLIB "mod::system/lib/services.whlib";
......@@ -178,6 +180,41 @@ STATIC OBJECTTYPE MailHandlerEditor EXTEND FormComponentExtensionBase
this->node->RemoveAttribute(attributename);
}
MACRO ReadAttachments()
{
STRING instanceid := this->node->GetAttribute("attachmentsid");
IF (instanceid != "")
{
RECORD instance := this->contexts->formcomponentapi->GetInstance(instanceid);
IF (RecordExists(instance))
^attachments->^files->value := instance.attachments;
}
}
MACRO WriteAttachments()
{
STRING instanceid := this->node->GetAttribute("attachmentsid");
IF (RecordExists(^attachments->^files->value))
{
RECORD instance :=
[ attachments := ^attachments->^files->value
, whfstype := "http://www.webhare.net/xmlns/publisher/mailhandlerattachments"
];
IF (instanceid = "")
instanceid := this->contexts->formcomponentapi->CreateInstance(instance);
ELSE
this->contexts->formcomponentapi->SetInstance(instanceid, instance);
this->node->SetAttribute("attachmentsid", instanceid);
}
ELSE
{
this->node->RemoveAttribute("attachmentsid");
IF (instanceid != "")
this->contexts->formcomponentapi->SetInstance(instanceid, DEFAULT RECORD);
}
}
UPDATE PUBLIC MACRO PostInitExtension()
{
IF(this->existing)
......@@ -340,6 +377,25 @@ STATIC OBJECTTYPE MailHandlerEditor EXTEND FormComponentExtensionBase
}
>;
PUBLIC STATIC OBJECTTYPE Attachments EXTEND TolliumFragmentBase
<
UPDATE MACRO PostInitComponent()
{
^files->AddAction(GetTid("~download"), PTR this->DoDownloadFile, [ actiontype := "downloadaction" ]);
}
MACRO DoAddFile(RECORD ARRAY files)
{
^files->AddRows(SELECT file := files FROM files);
}
MACRO DoDownloadFile(OBJECT downloadhandler)
{
RECORD todownload := ^files->selection[0].file;
downloadhandler->SendFile(todownload.data, todownload.mimetype, todownload.filename);
}
>;
PUBLIC STATIC OBJECTTYPE MailFeedbackEditor EXTEND MailHandlerEditor
<
MACRO NEW()
......@@ -363,6 +419,7 @@ PUBLIC STATIC OBJECTTYPE MailFeedbackEditor EXTEND MailHandlerEditor
^sendername->required := NOT (^sender->value != "" AND ^sendername->value = "");
this->FillIntraFieldPulldown(^receiverfield, "receiverfield");
this->ReadAttachments();
^bcc->value := this->node->GetAttribute("bcc");
}
......@@ -375,6 +432,7 @@ PUBLIC STATIC OBJECTTYPE MailFeedbackEditor EXTEND MailHandlerEditor
this->node->RemoveAttribute("sendoncanceltoo");
this->SubmitIntraFieldPulldown(^receiverfield, "receiverfield");
this->WriteAttachments();
IF (^bcc->value != "")
this->node->SetAttribute("bcc", ^bcc->value);
......@@ -585,6 +643,10 @@ OBJECT FUNCTION GetTaskMail(RECORD settings, RECORD result, STRING handlertype,
, ...mailmergedata
, addresults := settings.addresults
];
FOREVERY (RECORD attachment FROM settings.attachments)
mail->AddWrappedAttachment(attachment.file);
RETURN mail;
}
......@@ -669,12 +731,17 @@ RECORD FUNCTION ParseMailHandler(RECORD fielddef, OBJECT node, RECORD parseconte
, receiverfield := node->GetAttribute("receiverfield")
, bcc := node->GetAttribute("bcc")
, attachfiles := ParseXSBoolean(node->GetAttribute("attachfiles"))
, attachments := RECORD[]
];
STRING instance := node->GetAttribute("textid");
IF (instance != "" AND parsecontext.getinstance != DEFAULT MACRO PTR)
fielddef.text := parsecontext.getinstance(instance).data;
instance := node->GetAttribute("attachmentsid");
IF (instance != "" AND parsecontext.getinstance != DEFAULT MACRO PTR)
fielddef.attachments := parsecontext.getinstance(instance).attachments;
RETURN fielddef;
}
......
......@@ -65,6 +65,9 @@
<newtab tid=".emailcontents">
<includefragment name="emailcontents" fragment="#emailcontents" />
</newtab>
<newtab tid=".attachments">
<includefragment name="attachments" fragment="#attachments" />
</newtab>
</tabsextension>
<tabsextension
......@@ -115,4 +118,22 @@
</contents>
</fragment>
<fragment name="attachments" gid=".mailhandler" lib="webtools/formhandlers/mailhandler.whlib">
<actions>
<uploadaction name="addfile" title="" onupload="doaddfile" multiple="true" />
</actions>
<contents>
<text title="" valuetid=".files" wordwrap="true" />
<arrayedit
name="files"
buttons="add delete"
addactionoverride="addfile"
orderable="true"
public="true"
height="1pr">
<column name="file" type="blobrecord" />
</arrayedit>
</contents>
</fragment>
</screens>
......@@ -4914,6 +4914,36 @@ To change a <xs:attribute /> for adding descriptions
</xs:attribute>
</xs:attributeGroup>
<xs:attributeGroup name="ArrayListEditActions">
<xs:attribute name="addactionoverride" type="ComponentRef">
<xs:annotation>
<xs:documentation>
<html:p>
The action that will be used to add rows.
</html:p>
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="editactionoverride" type="ComponentRef">
<xs:annotation>
<xs:documentation>
<html:p>
The action that will be used to edit a row.
</html:p>
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="deleteactionoverride" type="ComponentRef">
<xs:annotation>
<xs:documentation>
<html:p>
The action that will be used to delete rows.
</html:p>
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:attributeGroup>
<xs:attributeGroup name="ArrayListEditFlags">
<xs:attribute name="editableflags" type="CheckFlags">
<xs:annotation>
......@@ -4933,6 +4963,7 @@ To change a <xs:attribute /> for adding descriptions
<xs:attributeGroup ref="tc:IsDirtyable" />
<xs:attributeGroup ref="ListContextMenus" /><!-- FIXME: experimental for now -->
<xs:attributeGroup ref="ArrayListEditButtons" />
<xs:attributeGroup ref="ArrayListEditActions" />
<xs:attributeGroup ref="ArrayListEditFlags" />
<xs:attribute name="openaction" type="ComponentRef">
......
......@@ -26,6 +26,10 @@ PUBLIC OBJECTTYPE ArrayListEditBase EXTEND TolliumFragmentBase
INTEGER lastaddedactionid;
RECORD ARRAY addedactions;
OBJECT pvt_addactionoverride;
OBJECT pvt_editactionoverride;
OBJECT pvt_deleteactionoverride;
// ---------------------------------------------------------------------------
//
// Public variables
......@@ -35,6 +39,10 @@ PUBLIC OBJECTTYPE ArrayListEditBase EXTEND TolliumFragmentBase
PUBLIC PROPERTY onrowdelete(pvt_onrowdelete, SetOnRowDelete);
PUBLIC PROPERTY onrowsdelete(pvt_onrowsdelete, SetOnRowsDelete);
PUBLIC PROPERTY addactionoverride(pvt_addactionoverride, SetAddActionOverride);
PUBLIC PROPERTY editactionoverride(pvt_editactionoverride, SetEditActionOverride);
PUBLIC PROPERTY deleteactionoverride(pvt_deleteactionoverride, SetDeleteActionOverride);
PUBLIC PROPERTY oncelledit(this->list->oncelledit, this->list->oncelledit);
PUBLIC PROPERTY onselect(this->list->onselect, this->list->onselect);
PUBLIC PROPERTY onfocusin(this->list->onfocusin, this->list->onfocusin);
......@@ -126,6 +134,10 @@ PUBLIC OBJECTTYPE ArrayListEditBase EXTEND TolliumFragmentBase
this->onrowdelete := description.onrowdelete;
this->onrowsdelete := description.onrowsdelete;
this->addactionoverride := description.addactionoverride;
this->editactionoverride := description.editactionoverride;
this->deleteactionoverride := description.deleteactionoverride;
this->onselect := description.onselect;
this->oncelledit := description.oncelledit;
......@@ -203,6 +215,30 @@ PUBLIC OBJECTTYPE ArrayListEditBase EXTEND TolliumFragmentBase
this->CheckDelete();
}
MACRO SetAddActionOverride(OBJECT addaction)
{
this->pvt_addactionoverride := addaction;
this->additem->action := ObjectExists(this->pvt_addactionoverride) ? this->pvt_addactionoverride : this->add;
this->addbutton->action := ObjectExists(this->pvt_addactionoverride) ? this->pvt_addactionoverride : this->add;
}
MACRO SetEditActionOverride(OBJECT editaction)
{
this->pvt_editactionoverride := editaction;
IF (ObjectExists(this->pvt_editactionoverride))
this->pvt_editactionoverride->enableon := this->edit->enableon;
this->edititem->action := ObjectExists(this->pvt_editactionoverride) ? this->pvt_editactionoverride : this->edit;
this->editbutton->action := ObjectExists(this->pvt_editactionoverride) ? this->pvt_editactionoverride : this->edit;
}
MACRO SetDeleteActionOverride(OBJECT deleteaction)
{
this->pvt_deleteactionoverride := deleteaction;
IF (ObjectExists(this->pvt_deleteactionoverride))
this->pvt_deleteactionoverride->enableon := this->"delete"->enableon;
this->deleteitem->action := ObjectExists(this->pvt_deleteactionoverride) ? this->pvt_deleteactionoverride : this->"delete";
this->deletebutton->action := ObjectExists(this->pvt_deleteactionoverride) ? this->pvt_deleteactionoverride : this->"delete";
this->CheckDelete();
}
STRING ARRAY FUNCTION GetFlags()
{
RETURN SELECT AS STRING ARRAY flag FROM ToRecordArray(this->list->flags, "flag") WHERE flag != "__arrayedit_canmoveup" AND flag != "__arrayedit_canmovedown";
......@@ -274,16 +310,28 @@ PUBLIC OBJECTTYPE ArrayListEditBase EXTEND TolliumFragmentBase
*/
UPDATE PUBLIC INTEGER FUNCTION AddAction(STRING title, MACRO PTR toexecute, RECORD options DEFAULTSTO DEFAULT RECORD)
{
options := ValidateOptions([ requireselection := TRUE ], options);
options := ValidateOptions(
[ requireselection := TRUE
, actiontype := "action"
], options,
[ enums := [ actiontype := [ "action", "downloadaction", "uploadaction", "windowopenaction" ] ]
]);
this->lastaddedactionid := this->lastaddedactionid + 1;
INTEGER newactionid := this->lastaddedactionid;
OBJECT action := this->owner->CreateTolliumComponent("action");
OBJECT action := this->owner->CreateTolliumComponent(options.actiontype);
OBJECT button := this->owner->CreateTolliumComponent("button");
OBJECT menuitem := this->owner->CreateTolliumComponent("menuitem");
action->onexecute := toexecute;
SWITCH (options.actiontype)
{
CASE "action" { action->onexecute := toexecute; }
CASE "downloadaction" { action->ondownload := toexecute; }
CASE "uploadaction" { action->onupload := toexecute; }
CASE "windowopenaction" { action->onwindowopen := toexecute; }
}
button->title := title;
button->action := action;
button->width := "1pr";
......@@ -503,6 +551,8 @@ PUBLIC OBJECTTYPE ArrayListEditBase EXTEND TolliumFragmentBase
MACRO CheckDelete()
{
this->"delete"->enabled := this->pvt_enabled AND (this->onrowdelete != DEFAULT FUNCTION PTR OR this->onrowsdelete != DEFAULT FUNCTION PTR);
IF (ObjectExists(this->deleteactionoverride))
this->deleteactionoverride->enabled := this->pvt_enabled;
}
>;
......@@ -826,7 +876,13 @@ PUBLIC OBJECTTYPE ArrayEdit EXTEND ArrayListEditBase
{
options := ValidateOptions( [ insertat := 0 ], options, [ optional := [ "insertat" ]]);
RECORD ARRAY newdata := this->DoRowEdit(basedata);
this->AddRows(this->DoRowEdit(basedata), options);
}
PUBLIC MACRO AddRows(RECORD ARRAY newdata, RECORD options DEFAULTSTO DEFAULT RECORD)
{
options := ValidateOptions( [ insertat := 0 ], options, [ optional := [ "insertat" ]]);
IF (Length(newdata) = 0)
RETURN;
......@@ -1088,7 +1144,7 @@ PUBLIC OBJECTTYPE ArrayEdit EXTEND ArrayListEditBase
// FIXME: also use for celledit
// FIXME: expose the function (make it PUBLIC)?
/*PUBLIC*/ MACRO UpdateSingleRow(RECORD newdata)
PUBLIC MACRO UpdateSingleRow(RECORD newdata)
{
INTEGER pos := (SELECT AS INTEGER #currows + 1 FROM this->currows WHERE rowkey = this->list->value) - 1;
IF (pos = -1)
......
......@@ -10,17 +10,17 @@
<enableon source="list" min="1" checkflags="__arrayedit_canmovedown" onexecute="movedownvalue" />
</action>
<action name="add" onexecute="addvalue" />
<action name="edit">
<enableon source="list" min="1" max="1" onexecute="editvalue" />
<action name="edit" onexecute="editvalue">
<enableon source="list" min="1" max="1" />
</action>
<action name="delete">
<enableon source="list" min="1" onexecute="deletevalue" />
<action name="delete" onexecute="deletevalue">
<enableon source="list" min="1" />
</action>
</actions>
<menus>
<menu name="selectionmenu">
<item action="edit" tid="~edit" disablemode="hidden" />
<item action="delete" tid="~delete" disablemode="hidden" />
<item name="edititem" action="edit" tid="~edit" disablemode="hidden" />
<item name="deleteitem" action="delete" tid="~delete" disablemode="hidden" />
<divider />
<item action="moveup" name="moveupitem" tid="~up" visible="false"/>
<item action="movedown" name="movedownitem" tid="~down" visible="false" />
......@@ -28,7 +28,7 @@
<!-- added actions appear here -->
</menu>
<menu name="noselectionmenu">
<item action="add" tid="common.actions.add" />
<item name="additem" action="add" tid="common.actions.add" />
</menu>
</menus>
<contents>
......
......@@ -53,6 +53,7 @@ RECORD testformfileoptions := [ addpaymentmethod := FALSE
, addtwolevelfield := FALSE
, addtimefield := FALSE
, setmailresultsfields := FALSE
, addfeedbackattachments := FALSE
, iscustom := FALSE
, iswidget := FALSE
, publish := TRUE
......@@ -585,6 +586,14 @@ MACRO FillTestFormFile(OBJECT formfile, RECORD options)
mailhandler2->SetAttribute("subject", "About Your Submission");
mailhandler2->SetAttribute("mailtemplate", formdefs->CreateLinkRef(mailtemplate->id));
mailhandler2->SetAttribute("bcc", "mailresult+bcc@beta.webhare.net, mailresult+bcc2@beta.webhare.net");
IF (options.addfeedbackattachments)
{
STRING attachments := formdefs->CreateInstance(
[ whfstype := "http://www.webhare.net/xmlns/publisher/mailhandlerattachments"
, attachments := [ [ file := WrapBlob(GetHarescriptResource("mod::webhare_testsuite/data/test/bob.jpg"), "bob.jpg") ] ]
]);
mailhandler2->SetAttribute("attachmentsid", attachments);
}
IF(options.iscustom)
{
......
......@@ -7,6 +7,8 @@
LOADLIB "wh::files.whlib";
LOADLIB "mod::tollium/lib/testframework.whlib";
LOADLIB "mod::system/lib/resources.whlib";
LOADLIB "mod::system/lib/testframework.whlib";
LOADLIB "mod::webhare_testsuite/lib/system/tests.whlib";
......@@ -189,6 +191,10 @@ PUBLIC ASYNC MACRO TestBuildCustomForm()
TT(":Sender name")->value := "testformfile-formeditapp";
TT(":Sender address")->value := "testformfile-formeditapp@beta.webhare.net";
BLOB img := GetHarescriptResource("mod::webhare_testsuite/data/test/bob.jpg");
TT("fragment1!attachments!files!addbutton")->action->ExecuteUpload([WrapBlob(img, "bob.jpg")]);
TestEq(1, Length(TT("fragment1!attachments!files!list")->rows));
AWAIT ExpectScreenChange(-1, PTR TTClick(":OK"));
AWAIT ExpectAndAnswerMessageBox("YES", PTR TTClick("Publish"), [ awaitcall := TRUE ]); //confirm save and publish
......
<?wh
LOADLIB "wh::internet/mime.whlib";
LOADLIB "mod::system/lib/testframework.whlib";
LOADLIB "mod::publisher/lib/testframework.whlib";
......@@ -24,7 +26,7 @@ MACRO AddExtraHandlers(RECORD forminfo)
PUBLIC MACRO BuildForm()
{
BuildWebtoolForm([ customformedits := PTR AddExtraHandlers ]);
BuildWebtoolForm([ customformedits := PTR AddExtraHandlers, addfeedbackattachments := TRUE ]);
}
PUBLIC ASYNC MACRO TestMailOptions()
......@@ -50,6 +52,11 @@ PUBLIC ASYNC MACRO TestMailOptions()
TestEq(TRUE, RecordExists(result1mail));
TestEq(`Mail From <mailfrom@beta.webhare.net>`, (SELECT AS STRING value FROM result1mail.headers WHERE field="From"));
TestEq(`<mailfrom@beta.webhare.net>`, result1mail.envelope_sender);
RECORD feedbackmail := testfw->ExtractAllMailFor("testmails@beta.webhare.net", [ timeout := 60000, count := 1 ]);
TestEq(TRUE, RecordExists(feedbackmail));
RECORD part := GetEmailPrimaryMIMEPart(feedbackmail.toppart, "multipart/mixed");
TestEq(TRUE, RecordExists(SELECT FROM part.subparts WHERE mimetype = 'image/jpeg; name="bob.jpg"'));
}
RunTestframework([ PTR BuildForm
......
......@@ -17,6 +17,21 @@ MACRO DuplicateAction()
topscreen->comp->SetSelectedRows([Length(topscreen->comp->value)-1]);
}
MACRO CustomAddAction()
{
topscreen->comp->AddRows([[checkbox := TRUE, text := "Custom added"]]);
}
MACRO CustomEditAction()
{
topscreen->comp->UpdateSingleRow([checkbox := FALSE, text := "Custom edited"]);
}
MACRO CustomDeleteAction()
{
topscreen->comp->UpdateSingleRow([checkbox := FALSE, text := "Custom 'deleted'"]);
}
OBJECT ASYNC FUNCTION TestArrayEdit()
{
OBJECT browser := GetTestController()->LoadScreen("webhare_testsuite:tests/anycomponent.anycomponent",
......@@ -74,6 +89,32 @@ OBJECT ASYNC FUNCTION TestArrayEdit()
TestEq([1], topscreen->comp->GetSelectedRows()); //verify insert pos
TestEq("Superrow!!!", topscreen->comp->selection.text);
// Test custom add/edit/delete actions
OBJECT addaction := topscreen->CreateTolliumComponent("action");
addaction->onexecute := PTR CustomAddAction;
topscreen->comp->addactionoverride := addaction;
OBJECT editaction := topscreen->CreateTolliumComponent("action");
editaction->onexecute := PTR CustomEditAction;
topscreen->comp->editactionoverride := editaction;
OBJECT deleteaction := topscreen->CreateTolliumComponent("action");
deleteaction->onexecute := PTR CustomDeleteAction;
topscreen->comp->deleteactionoverride := deleteaction;
topscreen->comp->addbutton->tolliumclick();
TestEq([[rowkey := 4, checkbox := TRUE, text := "Custom added"]], topscreen->comp->selection);
topscreen->comp->editbutton->tolliumclick();
TestEq([[rowkey := 4, checkbox := FALSE, text := "Custom edited"]], topscreen->comp->selection);
topscreen->comp->deletebutton->tolliumclick(); // The custom delete action doesn't actually delete, but changes the title
TestEq([[rowkey := 4, checkbox := FALSE, text := "Custom 'deleted'"]], topscreen->comp->selection);
topscreen->comp->addactionoverride := DEFAULT OBJECT;
topscreen->comp->editactionoverride := DEFAULT OBJECT;
topscreen->comp->deleteactionoverride := DEFAULT OBJECT;
TestEq(4, Length(topscreen->comp->rows));
AWAIT ExpectAndAnswerMessageBox("yes", PTR topscreen->comp->deletebutton->TolliumClick());
TestEq(3, Length(topscreen->comp->rows));
AWAIT ExpectScreenChange(-1, PTR topscreen->tolliumexecutecancel());
RETURN TRUE;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment