- Removed CMS_REQUEST_UTIL - centralize a few request related code into CMS_API Added CMS_API.user, CMS_API.set_user (CMS_USER), ... and user related routines. Refactored Auth related code - added various abstractions to factorize implementation and harmonize solutions. - revisited the logout strategy. - updated the account info page, and remove info user should not care about. - simplified the process, and encourage auth module to follow same design. Added CMS_LINK helper routines to modify the related query string. Removed CMS_USER.profile (and related routines) - It was not used so far. - it will probably a specific module later, if needed. Update various module to avoid fetching user from sql directly, and let this task to CMS_USER_API. Removed CMS_NODE_API.node_author (a_node: CMS_NODE): detachable CMS_USER, - as the info is already in CMS_NODE.author Added CMS_RESPONSE.redirection_delay, if ever one code want to redirect after a few seconds. Added the request uri info to the not found cms response.
534 lines
16 KiB
Plaintext
534 lines
16 KiB
Plaintext
note
|
|
description: "[
|
|
Module that provide contact us web form functionality.
|
|
]"
|
|
author: "$Author: jfiat $"
|
|
date: "$Date: 2016-01-08 22:43:12 +0100 (ven., 08 janv. 2016) $"
|
|
revision: "$Revision: 98369 $"
|
|
|
|
class
|
|
CMS_CONTACT_MODULE
|
|
|
|
inherit
|
|
CMS_MODULE
|
|
rename
|
|
module_api as contact_api
|
|
redefine
|
|
setup_hooks,
|
|
install,
|
|
initialize,
|
|
contact_api
|
|
end
|
|
|
|
CMS_HOOK_BLOCK
|
|
|
|
CMS_HOOK_BLOCK_HELPER
|
|
|
|
CMS_HOOK_AUTO_REGISTER
|
|
|
|
CMS_HOOK_MENU_SYSTEM_ALTER
|
|
|
|
SHARED_EXECUTION_ENVIRONMENT
|
|
export
|
|
{NONE} all
|
|
end
|
|
|
|
REFACTORING_HELPER
|
|
|
|
SHARED_LOGGER
|
|
|
|
create
|
|
make
|
|
|
|
feature {NONE} -- Initialization
|
|
|
|
make
|
|
-- Create current module
|
|
do
|
|
version := "1.0"
|
|
description := "Contact form module"
|
|
package := "messaging"
|
|
end
|
|
|
|
feature -- Access
|
|
|
|
name: STRING = "contact"
|
|
-- <Precursor>
|
|
|
|
feature {CMS_API} -- Module Initialization
|
|
|
|
initialize (api: CMS_API)
|
|
-- <Precursor>
|
|
local
|
|
l_contact_api: like contact_api
|
|
ut: FILE_UTILITIES
|
|
p: PATH
|
|
contact_storage: CONTACT_STORAGE_I
|
|
do
|
|
Precursor (api)
|
|
-- if attached api.storage.as_sql_storage as l_storage_sql then
|
|
-- create {CONTACT_STORAGE_SQL} contact_storage.make (l_storage_sql)
|
|
-- else
|
|
p := file_system_storage_path (api)
|
|
if ut.directory_path_exists (p) then
|
|
create {CONTACT_STORAGE_FS} contact_storage.make (p, api)
|
|
else
|
|
create {CONTACT_STORAGE_NULL} contact_storage.make
|
|
end
|
|
|
|
create l_contact_api.make (api, contact_storage)
|
|
contact_api := l_contact_api
|
|
end
|
|
|
|
feature {CMS_API} -- Module management
|
|
|
|
install (api: CMS_API)
|
|
local
|
|
retried: BOOLEAN
|
|
d: DIRECTORY
|
|
do
|
|
if not retried then
|
|
create d.make_with_path (file_system_storage_path (api))
|
|
d.recursive_create_dir
|
|
Precursor {CMS_MODULE}(api) -- Marked installed
|
|
end
|
|
rescue
|
|
retried := True
|
|
retry
|
|
end
|
|
|
|
file_system_storage_path (api: CMS_API): PATH
|
|
-- Location of eventual file system based storage for contact messages.
|
|
do
|
|
Result := api.site_location.extended ("db").extended (name).extended ("messages")
|
|
end
|
|
|
|
feature {CMS_API} -- Access: API
|
|
|
|
contact_api: detachable CONTACT_API
|
|
|
|
feature -- Router
|
|
|
|
setup_router (a_router: WSF_ROUTER; a_api: CMS_API)
|
|
-- Router configuration.
|
|
local
|
|
m: WSF_URI_MAPPING
|
|
do
|
|
create m.make_trailing_slash_ignored ("/contact", create {WSF_URI_AGENT_HANDLER}.make (agent handle_contact (a_api, ?, ?)))
|
|
a_router.map (m, a_router.methods_head_get)
|
|
a_router.handle ("/contact", create {WSF_URI_AGENT_HANDLER}.make (agent handle_post_contact (a_api, ?, ?)), a_router.methods_put_post)
|
|
end
|
|
|
|
feature -- Recaptcha
|
|
|
|
recaptcha_secret_key (api: CMS_API): detachable READABLE_STRING_8
|
|
-- Get recaptcha security key.
|
|
local
|
|
utf: UTF_CONVERTER
|
|
do
|
|
if attached api.module_configuration (Current, Void) as cfg then
|
|
if
|
|
attached cfg.text_item ("recaptcha.secret_key") as l_recaptcha_key and then
|
|
not l_recaptcha_key.is_empty
|
|
then
|
|
Result := utf.utf_32_string_to_utf_8_string_8 (l_recaptcha_key)
|
|
end
|
|
end
|
|
end
|
|
|
|
recaptcha_site_key (api: CMS_API): detachable READABLE_STRING_8
|
|
-- Get recaptcha security key.
|
|
local
|
|
utf: UTF_CONVERTER
|
|
do
|
|
if attached api.module_configuration (Current, Void) as cfg then
|
|
if
|
|
attached cfg.text_item ("recaptcha.site_key") as l_recaptcha_key and then
|
|
not l_recaptcha_key.is_empty
|
|
then
|
|
Result := utf.utf_32_string_to_utf_8_string_8 (l_recaptcha_key)
|
|
end
|
|
end
|
|
end
|
|
|
|
feature -- Hooks configuration
|
|
|
|
setup_hooks (a_hooks: CMS_HOOK_CORE_MANAGER)
|
|
-- Module hooks configuration.
|
|
do
|
|
auto_subscribe_to_hooks (a_hooks)
|
|
a_hooks.subscribe_to_block_hook (Current)
|
|
end
|
|
|
|
feature -- Hooks
|
|
|
|
menu_system_alter (a_menu_system: CMS_MENU_SYSTEM; a_response: CMS_RESPONSE)
|
|
-- Hook execution on collection of menu contained by `a_menu_system'
|
|
-- for related response `a_response'.
|
|
do
|
|
debug ("refactor_fixme")
|
|
fixme ("add contact to menu")
|
|
end
|
|
end
|
|
|
|
block_list: ITERABLE [like {CMS_BLOCK}.name]
|
|
do
|
|
Result := <<"?contact">>
|
|
end
|
|
|
|
get_block_view (a_block_id: READABLE_STRING_8; a_response: CMS_RESPONSE)
|
|
do
|
|
if a_block_id.is_case_insensitive_equal_general ("contact") then
|
|
-- "contact", "post_contact"
|
|
if a_response.request.is_get_request_method then
|
|
if attached template_block (Current, a_block_id, a_response) as l_tpl_block then
|
|
if attached recaptcha_site_key (a_response.api) as l_recaptcha_site_key then
|
|
l_tpl_block.set_value (l_recaptcha_site_key, "recaptcha_site_key")
|
|
end
|
|
a_response.add_block (l_tpl_block, "content")
|
|
a_response.add_style (a_response.url ("/module/" + name + "/files/css/contact.css", Void), Void)
|
|
else
|
|
debug ("cms")
|
|
a_response.add_warning_message ("Error with block [" + a_block_id + "]")
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
new_html_contact_form (a_response: CMS_RESPONSE; api: CMS_API): STRING
|
|
local
|
|
f: CMS_FORM
|
|
do
|
|
a_response.add_style (a_response.url ("/module/" + name + "/files/css/contact.css", Void), Void)
|
|
if attached template_block (Current, "contact", a_response) as l_tpl_block then
|
|
if attached recaptcha_site_key (api) as l_recaptcha_site_key then
|
|
l_tpl_block.set_value (l_recaptcha_site_key, "recaptcha_site_key")
|
|
end
|
|
across
|
|
a_response.values as tb
|
|
loop
|
|
l_tpl_block.set_value (tb.item, tb.key)
|
|
end
|
|
Result := l_tpl_block.to_html (a_response.theme)
|
|
else
|
|
f := new_contact_form (a_response, api)
|
|
api.hooks.invoke_form_alter (f, f.last_data, a_response)
|
|
|
|
Result := "<div class=%"contact-box%"><h1>Contact us!</h1>" + f.to_html (a_response.wsf_theme) + "<br/></div>"
|
|
end
|
|
end
|
|
|
|
new_contact_form (a_response: CMS_RESPONSE; api: CMS_API): CMS_FORM
|
|
local
|
|
f: CMS_FORM
|
|
f_name: WSF_FORM_TEXT_INPUT
|
|
f_email: WSF_FORM_EMAIL_INPUT
|
|
f_msg: WSF_FORM_TEXTAREA
|
|
f_submit: WSF_FORM_SUBMIT_INPUT
|
|
do
|
|
create f.make (a_response.url ("contact", Void), "contact-form")
|
|
create f_name.make ("name")
|
|
f_name.set_label ("Name")
|
|
f_name.set_is_required (True)
|
|
f.extend (f_name)
|
|
|
|
create f_email.make ("email")
|
|
f_email.set_label ("Email Address")
|
|
f_email.set_is_required (True)
|
|
f.extend (f_email)
|
|
|
|
create f_msg.make ("message")
|
|
f_msg.set_label ("Message")
|
|
f_msg.set_rows (5)
|
|
f_msg.set_is_required (True)
|
|
f.extend (f_msg)
|
|
|
|
if attached recaptcha_site_key (api) as l_recaptcha_site_key then
|
|
f.extend_html_text ("<div class=%"g-recaptcha%" data-sitekey=%"" + l_recaptcha_site_key + "%"></div><br/>")
|
|
end
|
|
|
|
create f_submit.make_with_text ("submit-op", "Send")
|
|
f.extend (f_submit)
|
|
|
|
-- f.extend_html_text ("[
|
|
-- <p class="req-field-desc"><span class="required">*</span> indicates a required field</p>
|
|
-- ]")
|
|
|
|
Result := f
|
|
end
|
|
|
|
handle_contact (api: CMS_API; req: WSF_REQUEST; res: WSF_RESPONSE)
|
|
local
|
|
r: CMS_RESPONSE
|
|
do
|
|
-- FIXME: we should use WSF_FORM, and integrate the recaptcha using the form alter hook.
|
|
write_debug_log (generator + ".handle_contact")
|
|
create {GENERIC_VIEW_CMS_RESPONSE} r.make (req, res, api)
|
|
r.values.force ("contact", "contact")
|
|
r.set_main_content (new_html_contact_form (r, api))
|
|
r.execute
|
|
end
|
|
|
|
handle_post_contact (api: CMS_API; req: WSF_REQUEST; res: WSF_RESPONSE)
|
|
local
|
|
r: CMS_RESPONSE
|
|
msg: CONTACT_MESSAGE
|
|
l_params: CONTACT_EMAIL_SERVICE_PARAMETERS
|
|
e: CMS_EMAIL
|
|
vars: STRING_TABLE [READABLE_STRING_8]
|
|
l_contact_email_address: READABLE_STRING_8
|
|
do
|
|
write_information_log (generator + ".handle_post_contact")
|
|
create {GENERIC_VIEW_CMS_RESPONSE} r.make (req, res, api)
|
|
r.add_style (r.url ("/module/" + name + "/files/css/contact.css", Void), Void)
|
|
r.values.force (False, "has_error")
|
|
|
|
create vars.make_caseless (5)
|
|
vars.put (safe_html_encoded (api.setup.site_url), "siteurl")
|
|
vars.put (safe_html_encoded (api.setup.site_name), "sitename")
|
|
|
|
write_debug_log (generator + ".handle_post_contact {Form Parameters:" + form_parameters_as_string (req) + "}")
|
|
if
|
|
attached {WSF_STRING} req.form_parameter ("name") as l_name and then
|
|
attached {WSF_STRING} req.form_parameter ("email") as l_email and then
|
|
attached {WSF_STRING} req.form_parameter ("message") as l_message
|
|
then
|
|
if
|
|
is_form_captcha_verified (req, "g-recaptcha-response", api) and then
|
|
l_email.value.is_valid_as_string_8
|
|
then
|
|
l_contact_email_address := l_email.value.to_string_8
|
|
|
|
if attached contact_api as l_contact_api then
|
|
create msg.make (l_name.value, l_message.value)
|
|
msg.set_email (l_contact_email_address)
|
|
|
|
l_contact_api.save_contact_message (msg)
|
|
end
|
|
|
|
create l_params.make (api, Current)
|
|
|
|
-- Send internal email to admin.
|
|
vars.put (html_encoded (l_name.value), "name")
|
|
vars.put (html_encoded (l_contact_email_address), "email")
|
|
vars.put (html_encoded (l_message.value), "message")
|
|
|
|
write_debug_log (generator + ".handle_post_contact: send notification email")
|
|
|
|
e := api.new_email (l_params.admin_email, "Notification Contact", email_html_message ("notification", r, vars))
|
|
e.set_from_address (l_params.admin_email)
|
|
e.add_header_line ("MIME-Version:1.0")
|
|
e.add_header_line ("Content-Type: text/html; charset=utf-8")
|
|
api.process_email (e)
|
|
|
|
if not api.has_error then
|
|
-- Send Contact email to the user
|
|
write_information_log (generator + ".handle_post_contact: preparing the message.")
|
|
e := api.new_email (l_contact_email_address, l_params.contact_subject_text, email_html_message ("message", r, vars))
|
|
e.set_from_address (l_params.admin_email)
|
|
e.add_header_line ("MIME-Version:1.0")
|
|
e.add_header_line ("Content-Type: text/html; charset=utf-8")
|
|
write_debug_log (generator + ".handle_post_contact: send_contact_email")
|
|
api.process_email (e)
|
|
end
|
|
|
|
if api.has_error then
|
|
write_error_log (generator + ".handle_post_contact: error message:["+ api.string_representation_of_errors +"]")
|
|
r.set_status_code ({HTTP_CONSTANTS}.internal_server_error)
|
|
r.values.force (True, "has_error")
|
|
vars.put ("True", "has_error")
|
|
end
|
|
if attached template_block_with_values (Current, "post_contact", r, vars) as l_tpl_block then
|
|
across
|
|
r.values as tb
|
|
loop
|
|
l_tpl_block.set_value (tb.item, tb.key)
|
|
end
|
|
r.set_main_content (l_tpl_block.to_html (r.theme))
|
|
else
|
|
r.set_main_content ("Thank you for your message.")
|
|
end
|
|
r.execute
|
|
else
|
|
-- send a bad request status code and redisplay the form with the previous data loaded.
|
|
r.set_value (False, "error")
|
|
r.set_status_code ({HTTP_STATUS_CODE}.bad_request)
|
|
if attached template_block_with_values (Current, "contact", r, vars) as l_tpl_block then
|
|
across
|
|
r.values as tb
|
|
loop
|
|
l_tpl_block.set_value (tb.item, tb.key)
|
|
end
|
|
if attached recaptcha_site_key (api) as l_recaptcha_site_key then
|
|
l_tpl_block.set_value (l_recaptcha_site_key, "recaptcha_site_key")
|
|
l_tpl_block.set_value (<<"Missing Captcha", "Internal Server Error">>, "error_response")
|
|
end
|
|
r.set_main_content (l_tpl_block.to_html (r.theme))
|
|
else
|
|
debug ("cms")
|
|
r.add_warning_message ("Error with block [contact]")
|
|
end
|
|
end
|
|
r.execute
|
|
end
|
|
else
|
|
-- Internal server error
|
|
write_error_log (generator + ".handle_post_contact: Internal Server error")
|
|
r.values.force (True, "has_error")
|
|
r.set_status_code ({HTTP_CONSTANTS}.internal_server_error)
|
|
if attached template_block_with_values (Current, "post_contact", r, vars) as l_tpl_block then
|
|
across
|
|
r.values as tb
|
|
loop
|
|
l_tpl_block.set_value (tb.item, tb.key)
|
|
end
|
|
r.set_main_content (l_tpl_block.to_html (r.theme))
|
|
end
|
|
r.execute
|
|
end
|
|
end
|
|
|
|
is_form_captcha_verified (req: WSF_REQUEST; a_form_field_id: READABLE_STRING_GENERAL; api: CMS_API): BOOLEAN
|
|
do
|
|
if attached recaptcha_secret_key (api) as l_recaptcha_key then
|
|
if
|
|
attached {WSF_STRING} req.form_parameter (a_form_field_id) as l_recaptcha_response and then
|
|
is_captcha_verified (l_recaptcha_key, l_recaptcha_response.value)
|
|
then
|
|
Result := True
|
|
else
|
|
--| Bad or missing captcha
|
|
Result := False
|
|
end
|
|
else
|
|
--| reCaptcha is not setup, so no verification
|
|
Result := True
|
|
end
|
|
end
|
|
|
|
feature {NONE} -- Helpers
|
|
|
|
form_parameters_as_string (req: WSF_REQUEST): STRING
|
|
do
|
|
create Result.make_empty
|
|
across req.form_parameters as ic loop
|
|
Result.append (ic.item.key)
|
|
Result.append_character ('=')
|
|
Result.append_string (ic.item.string_representation)
|
|
Result.append_character ('%N')
|
|
end
|
|
end
|
|
|
|
feature {NONE} -- Contact Message
|
|
|
|
template_block_with_values (a_module: CMS_MODULE; a_block_id: READABLE_STRING_8; a_response: CMS_RESPONSE; a_values: STRING_TABLE [ANY]): like template_block
|
|
do
|
|
Result := template_block (a_module, a_block_id, a_response)
|
|
if Result /= Void then
|
|
across
|
|
a_values as ic
|
|
loop
|
|
Result.set_value (ic.item, ic.key)
|
|
end
|
|
end
|
|
end
|
|
|
|
email_html_message (a_message_id: READABLE_STRING_8; a_response: CMS_RESPONSE; a_html_encoded_values: STRING_TABLE [READABLE_STRING_8]): STRING
|
|
-- html message related to `a_message_id'.
|
|
local
|
|
res: PATH
|
|
p: detachable PATH
|
|
tpl: CMS_SMARTY_TEMPLATE_BLOCK
|
|
exp: CMS_STRING_EXPANDER [STRING_8]
|
|
do
|
|
write_debug_log (generator + ".email_html_message for [" + a_message_id + " ]")
|
|
|
|
create res.make_from_string ("templates")
|
|
res := res.extended ("email_").appended (a_message_id).appended_with_extension ("tpl")
|
|
p := a_response.api.module_theme_resource_location (Current, res)
|
|
if p /= Void then
|
|
if attached p.entry as e then
|
|
create tpl.make (a_message_id, Void, p.parent, e)
|
|
write_debug_log (generator + ".email_html_message from smarty template:" + tpl.out)
|
|
else
|
|
create tpl.make (a_message_id, Void, p.parent, p)
|
|
write_debug_log (generator + ".email_html_message from smarty template:" + tpl.out)
|
|
end
|
|
across
|
|
a_html_encoded_values as ic
|
|
loop
|
|
tpl.set_value (ic.item, ic.key)
|
|
end
|
|
Result := tpl.to_html (a_response.theme)
|
|
else
|
|
if a_message_id.is_case_insensitive_equal_general ("message") then
|
|
create Result.make_from_string (contact_message_template)
|
|
elseif a_message_id.is_case_insensitive_equal_general ("notification") then
|
|
create Result.make_from_string (contact_notification_message_template)
|
|
else
|
|
create Result.make_from_string (a_message_id)
|
|
across
|
|
a_html_encoded_values as ic
|
|
loop
|
|
Result.append ("<li>")
|
|
Result.append (html_encoded (ic.key))
|
|
Result.append (": ")
|
|
Result.append (ic.item) -- Already html encoded.
|
|
Result.append ("</li>%N")
|
|
end
|
|
end
|
|
|
|
create exp.make
|
|
across
|
|
a_html_encoded_values as ic
|
|
loop
|
|
exp.put (ic.item, ic.key)
|
|
end
|
|
exp.expand_string (Result)
|
|
write_debug_log (generator + ".email_html_message using built-in message:" + Result)
|
|
end
|
|
end
|
|
|
|
contact_message_template: STRING
|
|
do
|
|
Result := "[
|
|
<p>Thank you for contacting $sitename.<br/>
|
|
We will get back to you promptly on your contact request.
|
|
</p>
|
|
]"
|
|
+ contact_notification_message_template
|
|
end
|
|
|
|
contact_notification_message_template: STRING = "[
|
|
<h2>Contact information:</h2>
|
|
<div>
|
|
<strong>Name<strong>: $name <br/>
|
|
<strong>Email<strong>: $email <br/>
|
|
<strong>Message<strong>: $message <br/>
|
|
</div>
|
|
]"
|
|
|
|
feature {NONE} -- Google recaptcha uri template
|
|
|
|
is_captcha_verified (a_secret, a_response: READABLE_STRING_8): BOOLEAN
|
|
local
|
|
api: RECAPTCHA_API
|
|
l_errors: STRING
|
|
do
|
|
write_debug_log (generator + ".is_captcha_verified with response: [" + a_response + "]")
|
|
create api.make (a_secret, a_response)
|
|
Result := api.verify
|
|
if not Result and then attached api.errors as l_api_errors then
|
|
create l_errors.make_empty
|
|
l_errors.append_character ('%N')
|
|
across l_api_errors as ic loop
|
|
l_errors.append ( ic.item )
|
|
l_errors.append_character ('%N')
|
|
end
|
|
write_error_log (generator + ".is_captcha_verified api_errors [" + l_errors + "]")
|
|
end
|
|
end
|
|
|
|
end
|