From 59c03c5f4d97328eb0c909e520ffdbb872194e7d Mon Sep 17 00:00:00 2001 From: Jocelyn Fiat Date: Fri, 22 Jan 2016 21:33:06 +0100 Subject: [PATCH] Added CMS_STRING_EXPANDER. For now with basic implementation. It will be improved later Added SEO related attribute in CMS_RESPONSE. Added improved Contact module. Added basic SEO module. --- examples/demo/demo-safe.ecf | 4 +- examples/demo/install_modules.bat | 2 + examples/demo/site/config/cms.ini | 4 + .../site/modules/contact/config/contact.json | 8 + .../modules/contact/files/css/contact.css | 124 ++++ .../modules/contact/files/scss/contact.scss | 140 +++++ .../contact/templates/block_contact.tpl | 25 + .../contact/templates/block_post_contact.tpl | 15 + .../contact/templates/email_message.tpl | 10 + .../contact/templates/email_notification.tpl | 6 + .../site/modules/node/files/scss/node.scss | 5 - examples/demo/src/demo_cms_execution.e | 10 +- modules/auth/cms_authentication_module.e | 15 +- modules/contact/contact-safe.ecf | 21 + modules/contact/site/config/contact.json | 8 + modules/contact/site/files/css/contact.css | 124 ++++ modules/contact/site/files/scss/contact.scss | 140 +++++ .../contact/site/templates/block_contact.tpl | 25 + .../site/templates/block_post_contact.tpl | 15 + .../contact/site/templates/email_message.tpl | 10 + .../site/templates/email_notification.tpl | 6 + modules/contact/src/cms_contact_module.e | 556 ++++++++++++++++++ modules/contact/src/contact_api.e | 40 ++ .../src/contact_email_service_parameters.e | 63 ++ modules/contact/src/contact_message.e | 43 ++ .../src/persistence/contact_storage_fs.e | 56 ++ .../src/persistence/contact_storage_i.e | 27 + .../src/persistence/contact_storage_null.e | 39 ++ .../src/persistence/contact_storage_sql.e | 49 ++ modules/node/site/files/scss/node.scss | 5 - modules/seo/seo-safe.ecf | 14 + modules/seo/src/cms_seo_module.e | 154 +++++ src/configuration/cms_setup.e | 32 + src/hooks/cms_hook_block_helper.e | 2 +- src/service/misc/cms_string_expander.e | 119 ++++ src/service/response/cms_response.e | 45 +- 36 files changed, 1939 insertions(+), 22 deletions(-) create mode 100644 examples/demo/site/modules/contact/config/contact.json create mode 100644 examples/demo/site/modules/contact/files/css/contact.css create mode 100644 examples/demo/site/modules/contact/files/scss/contact.scss create mode 100644 examples/demo/site/modules/contact/templates/block_contact.tpl create mode 100644 examples/demo/site/modules/contact/templates/block_post_contact.tpl create mode 100644 examples/demo/site/modules/contact/templates/email_message.tpl create mode 100644 examples/demo/site/modules/contact/templates/email_notification.tpl create mode 100644 modules/contact/contact-safe.ecf create mode 100644 modules/contact/site/config/contact.json create mode 100644 modules/contact/site/files/css/contact.css create mode 100644 modules/contact/site/files/scss/contact.scss create mode 100644 modules/contact/site/templates/block_contact.tpl create mode 100644 modules/contact/site/templates/block_post_contact.tpl create mode 100644 modules/contact/site/templates/email_message.tpl create mode 100644 modules/contact/site/templates/email_notification.tpl create mode 100644 modules/contact/src/cms_contact_module.e create mode 100644 modules/contact/src/contact_api.e create mode 100644 modules/contact/src/contact_email_service_parameters.e create mode 100644 modules/contact/src/contact_message.e create mode 100644 modules/contact/src/persistence/contact_storage_fs.e create mode 100644 modules/contact/src/persistence/contact_storage_i.e create mode 100644 modules/contact/src/persistence/contact_storage_null.e create mode 100644 modules/contact/src/persistence/contact_storage_sql.e create mode 100644 modules/seo/seo-safe.ecf create mode 100644 modules/seo/src/cms_seo_module.e create mode 100644 src/service/misc/cms_string_expander.e diff --git a/examples/demo/demo-safe.ecf b/examples/demo/demo-safe.ecf index 3fa344b..2d6c3dc 100644 --- a/examples/demo/demo-safe.ecf +++ b/examples/demo/demo-safe.ecf @@ -24,6 +24,7 @@ + @@ -33,6 +34,7 @@ + @@ -48,7 +50,7 @@ - + diff --git a/examples/demo/install_modules.bat b/examples/demo/install_modules.bat index 98de61d..7b79fd1 100644 --- a/examples/demo/install_modules.bat +++ b/examples/demo/install_modules.bat @@ -6,6 +6,7 @@ set ROC_CMS_DIR=%~dp0 %ROC_CMD% install --module ..\..\modules\auth --dir %ROC_CMS_DIR% %ROC_CMD% install --module ..\..\modules\basic_auth --dir %ROC_CMS_DIR% %ROC_CMD% install --module ..\..\modules\blog --dir %ROC_CMS_DIR% +%ROC_CMD% install --module ..\..\modules\contact --dir %ROC_CMS_DIR% %ROC_CMD% install --module ..\..\modules\node --dir %ROC_CMS_DIR% %ROC_CMD% install --module ..\..\modules\oauth20 --dir %ROC_CMS_DIR% %ROC_CMD% install --module ..\..\modules\openid --dir %ROC_CMS_DIR% @@ -13,3 +14,4 @@ set ROC_CMS_DIR=%~dp0 %ROC_CMD% install --module ..\..\modules\feed_aggregator --dir %ROC_CMS_DIR% %ROC_CMD% install --module ..\..\modules\google_search --dir %ROC_CMS_DIR% %ROC_CMD% install --module ..\..\modules\taxonomy --dir %ROC_CMS_DIR% +%ROC_CMD% install --module ..\..\modules\seo --dir %ROC_CMS_DIR% diff --git a/examples/demo/site/config/cms.ini b/examples/demo/site/config/cms.ini index 32b9c43..6141d83 100644 --- a/examples/demo/site/config/cms.ini +++ b/examples/demo/site/config/cms.ini @@ -7,6 +7,10 @@ root-dir=site/www # Name of the site, for the title, and eventual message. name=Eiffel CMS +property[headline]=Eiffel CMS -- the demo +property[description]=Demo for Eiffel ROC CMS. +property[keywords]=eiffel,cms,demo + # Email used for notification email=noreply@example.com diff --git a/examples/demo/site/modules/contact/config/contact.json b/examples/demo/site/modules/contact/config/contact.json new file mode 100644 index 0000000..c4c4101 --- /dev/null +++ b/examples/demo/site/modules/contact/config/contact.json @@ -0,0 +1,8 @@ +{ + "--email": "webmaster@example.com", + "subjet": "Thank you for contacting us", + "recaptcha": { + "site_key":"", + "secret_key":"" + } +} diff --git a/examples/demo/site/modules/contact/files/css/contact.css b/examples/demo/site/modules/contact/files/css/contact.css new file mode 100644 index 0000000..defc30f --- /dev/null +++ b/examples/demo/site/modules/contact/files/css/contact.css @@ -0,0 +1,124 @@ +.contact-box { + background-color: #F2F7F9; + width: 465px; + padding: 20px; + border: 6px solid #8FB5C1; + -moz-border-radius: 15px; + -webkit-border-radius: 15px; + border-radius: 15px; + position: relative; + /* Remove box shadow firefox, chrome and opera put around required fields. + * It looks rubbish. + */ + /* Normalize placeholder styles */ + /* chrome, safari */ + /* mozilla */ + /* ie (faux placeholder) */ +} +.contact-box h1 { + font-size: 42px; +} +.contact-box h2 { + margin-bottom: 15px; + font-style: italic; + font-weight: normal; +} +.contact-box label { + font-size: 15px; + margin-bottom: 2px; + display: block; +} +.contact-box input, .contact-box select, .contact-box textarea { + width: 100%; + font-size: 15px; + border: 1px solid #CEE1E8; + margin-bottom: 20px; + padding: 4px; +} +.contact-box input:focus, .contact-box select:focus, .contact-box textarea:focus { + border: 1px solid #AFCDD8; + background-color: #EBF2F4; +} +.contact-box textarea { + height: 150px; + resize: none; +} +.contact-box span.required { + font-weight: bold; + color: #F00; +} +.contact-box input[type=submit] { + width: 100px; + background-color: #333; + color: #FFF; + border: none; + display: block; + float: right; + margin-bottom: 0px; + margin-right: 6px; + background-color: #8FB5C1; + -moz-border-radius: 8px; +} +.contact-box input[type=submit]:hover { + background-color: #A6CFDD; +} +.contact-box input[type=submit]:active { + position: relative; + top: 1px; +} +.contact-box .message { + width: 95%; + margin: 25px 0px; + padding: 10px; + display: block; + border: solid 1px #ccc; + border-radius: 8px; + -webkit-border-radius: 8px; + -moz-border-radius: 8px; +} +.contact-box .message.hidden { + display: none; +} +.contact-box .message.error { + border-color: #E58E8E; + background-color: #FFE6E6; +} +.contact-box .message.error li { + padding: 2px; + list-style: none; +} +.contact-box .message.error li:before { + content: ' - '; +} +.contact-box .message.error #info { + font-weight: bold; +} +.contact-box .message.error #info:before { + content: ''; +} +.contact-box .message.success { + border-color: #83D186; + padding-top: 25px; + background-color: #D3EDD3; +} +.contact-box .req-field-desc { + font-style: italic; +} +.contact-box input:required, .contact-box textarea:required { + -moz-box-shadow: none; + -webkit-box-shadow: none; + -o-box-shadow: none; + box-shadow: none; +} +.contact-box ::-webkit-input-placeholder { + color: #CCC; + font-style: italic; +} +.contact-box input:-moz-placeholder, .contact-box textarea:-moz-placeholder { + color: #CCC; + font-style: italic; +} +.contact-box input.placeholder-text, .contact-box textarea.placeholder-text { + color: #CCC; + font-style: italic; +} diff --git a/examples/demo/site/modules/contact/files/scss/contact.scss b/examples/demo/site/modules/contact/files/scss/contact.scss new file mode 100644 index 0000000..4fc2240 --- /dev/null +++ b/examples/demo/site/modules/contact/files/scss/contact.scss @@ -0,0 +1,140 @@ +.contact-box { + background-color:#F2F7F9; + width:465px; + padding:20px; + border: 6px solid #8FB5C1; + -moz-border-radius:15px; + -webkit-border-radius:15px; + border-radius:15px; + position:relative; + + h1 { + font-size:42px; + } + + h2 { + margin-bottom:15px; + font-style:italic; + font-weight:normal; + } + + label { + font-size:15px; + margin-bottom:2px; + display:block; + } + + input, select, textarea { + width:100%; + font-size:15px; + border: 1px solid #CEE1E8; + margin-bottom:20px; + padding:4px; + &:focus { + border: 1px solid #AFCDD8; + background-color: #EBF2F4; + } + } + + textarea { + height:150px; + resize: none; + } + + span.required { + font-weight:bold; + color:#F00; + } + + input[type=submit] { + width: 100px; + background-color:#333; + color:#FFF; + border:none; + display:block; + float:right; + margin-bottom:0px; + margin-right:6px; + background-color:#8FB5C1; + -moz-border-radius:8px; + &:hover { + background-color: #A6CFDD; + } + &:active { + position:relative; + top:1px; + } + } + + .message { + width:95%; + margin:25px 0px; + padding:10px; + display:block; + border:solid 1px #ccc; + border-radius:8px; + -webkit-border-radius:8px; + -moz-border-radius:8px; + + &.hidden { + display: none; + } + + &.error { + border-color: #E58E8E; + background-color:#FFE6E6; + + li { + padding:2px; + list-style:none; + &:before { content: ' - '; } + } + #info { + font-weight:bold; + &:before { content: ''; } + } + } + + &.success { + border-color: #83D186; + padding-top: 25px; + background-color:#D3EDD3; + } + } + + .req-field-desc { + font-style:italic; + } + + /* Remove box shadow firefox, chrome and opera put around required fields. + * It looks rubbish. + */ + input:required, textarea:required { + -moz-box-shadow:none; + -webkit-box-shadow:none; + -o-box-shadow:none; + box-shadow:none; + } + + /* Normalize placeholder styles */ + + /* chrome, safari */ + ::-webkit-input-placeholder { + color:#CCC; + font-style:italic; + } + + /* mozilla */ + input:-moz-placeholder, textarea:-moz-placeholder { + color:#CCC; + font-style:italic; + } + + /* ie (faux placeholder) */ + input.placeholder-text, textarea.placeholder-text { + color:#CCC; + font-style:italic; + } + +} + diff --git a/examples/demo/site/modules/contact/templates/block_contact.tpl b/examples/demo/site/modules/contact/templates/block_contact.tpl new file mode 100644 index 0000000..a8ff0b0 --- /dev/null +++ b/examples/demo/site/modules/contact/templates/block_contact.tpl @@ -0,0 +1,25 @@ +
+

Contact us!

+
+ + + + + + + + + {unless isempty="$recaptcha_site_key"} +
+
+ {/unless} + +

* indicates a required field

+
+ {unless isempty="$error_response"} +
    + {foreach item="item" from="$error_response"}
  • {$item/}
  • {/foreach} +
+
Try again later
+ {/unless} +
diff --git a/examples/demo/site/modules/contact/templates/block_post_contact.tpl b/examples/demo/site/modules/contact/templates/block_post_contact.tpl new file mode 100644 index 0000000..a245283 --- /dev/null +++ b/examples/demo/site/modules/contact/templates/block_post_contact.tpl @@ -0,0 +1,15 @@ +
+{if condition="$has_error"} +
+ Internal Server Error Error 500 +

The page you requested could not be served because the server is down, + either contact the webmaster or try again. + Use your browser's Back button to navigate to the page you came from.

+

Or you could just press this link: Take Me Home

+
+{/if} +{unless condition="$has_error"} +

Thank you for contacting the Eiffel Programming Language community.
+We will get back to you promptly on your contact request.

+{/unless} +
diff --git a/examples/demo/site/modules/contact/templates/email_message.tpl b/examples/demo/site/modules/contact/templates/email_message.tpl new file mode 100644 index 0000000..108e26a --- /dev/null +++ b/examples/demo/site/modules/contact/templates/email_message.tpl @@ -0,0 +1,10 @@ +

+Thank you for contacting {$sitename/}.
+We will get back to you promptly about your contact message. +

+

Your contact information:

+
+ Name: {$name/}
+ Email: {$email/}
+ Message: {$message/}
+
diff --git a/examples/demo/site/modules/contact/templates/email_notification.tpl b/examples/demo/site/modules/contact/templates/email_notification.tpl new file mode 100644 index 0000000..6b411b5 --- /dev/null +++ b/examples/demo/site/modules/contact/templates/email_notification.tpl @@ -0,0 +1,6 @@ +

Contact information:

+
+ Name: {$name/}
+ Email: {$email/}
+ Message: {$message/}
+
diff --git a/examples/demo/site/modules/node/files/scss/node.scss b/examples/demo/site/modules/node/files/scss/node.scss index 5cf9324..34d5e20 100644 --- a/examples/demo/site/modules/node/files/scss/node.scss +++ b/examples/demo/site/modules/node/files/scss/node.scss @@ -3,22 +3,17 @@ ul.cms-nodes { list-style-type: none; padding: 3px 3px 3px 3px; border: solid 1px #ccc; - li{ border-top: dotted 1px #ccc; &:first-child { border-top: none; } } - li.cms_type_page a::before { content: "[page] "; } - li.cms_type_blog a::before { content: "[blog] "; } - - } diff --git a/examples/demo/src/demo_cms_execution.e b/examples/demo/src/demo_cms_execution.e index 7681a07..5c37f2d 100644 --- a/examples/demo/src/demo_cms_execution.e +++ b/examples/demo/src/demo_cms_execution.e @@ -51,11 +51,18 @@ feature -- CMS modules a_setup.register_module (create {CMS_BASIC_AUTH_MODULE}.make) a_setup.register_module (create {CMS_OAUTH_20_MODULE}.make) a_setup.register_module (create {CMS_OPENID_MODULE}.make) + a_setup.register_module (create {CMS_SESSION_AUTH_MODULE}.make) -- Nodes a_setup.register_module (create {CMS_NODE_MODULE}.make (a_setup)) a_setup.register_module (create {CMS_BLOG_MODULE}.make) + -- Contact + a_setup.register_module (create {CMS_CONTACT_MODULE}.make) + + -- Misc + a_setup.register_module (create {CMS_SEO_MODULE}.make) + -- Taxonomy a_setup.register_module (create {CMS_TAXONOMY_MODULE}.make) @@ -66,10 +73,9 @@ feature -- CMS modules a_setup.register_module (create {FEED_AGGREGATOR_MODULE}.make) -- Miscellanious + a_setup.register_module (create {GOOGLE_CUSTOM_SEARCH_MODULE}.make) a_setup.register_module (create {CMS_DEBUG_MODULE}.make) a_setup.register_module (create {CMS_DEMO_MODULE}.make) - a_setup.register_module (create {GOOGLE_CUSTOM_SEARCH_MODULE}.make) - a_setup.register_module (create {CMS_SESSION_AUTH_MODULE}.make) end end diff --git a/modules/auth/cms_authentication_module.e b/modules/auth/cms_authentication_module.e index d721728..3bb64a1 100644 --- a/modules/auth/cms_authentication_module.e +++ b/modules/auth/cms_authentication_module.e @@ -95,7 +95,7 @@ feature -- Router do create m.make_trailing_slash_ignored ("/account", create {WSF_URI_AGENT_HANDLER}.make (agent handle_account(a_api, ?, ?))) a_router.map (m, a_router.methods_head_get) - + a_router.handle ("/account/roc-login", create {WSF_URI_AGENT_HANDLER}.make (agent handle_login(a_api, ?, ?)), a_router.methods_head_get) a_router.handle ("/account/roc-logout", create {WSF_URI_AGENT_HANDLER}.make (agent handle_logout(a_api, ?, ?)), a_router.methods_head_get) a_router.handle ("/account/roc-register", create {WSF_URI_AGENT_HANDLER}.make (agent handle_register(a_api, ?, ?)), a_router.methods_get_post) @@ -672,16 +672,19 @@ feature -- Handler end get_block_view (a_block_id: READABLE_STRING_8; a_response: CMS_RESPONSE) + local + loc: READABLE_STRING_8 do - if a_block_id.is_case_insensitive_equal_general ("register") and then a_response.location.starts_with ("account/roc-register") then + loc := a_response.location + if a_block_id.is_case_insensitive_equal_general ("register") and then loc.starts_with ("account/roc-register") then get_block_view_register (a_block_id, a_response) - elseif a_block_id.is_case_insensitive_equal_general ("reactivate") and then a_response.location.starts_with ("account/reactivate") then + elseif a_block_id.is_case_insensitive_equal_general ("reactivate") and then loc.starts_with ("account/reactivate") then get_block_view_reactivate (a_block_id, a_response) - elseif a_block_id.is_case_insensitive_equal_general ("new_password") and then a_response.location.starts_with ("account/new-password") then + elseif a_block_id.is_case_insensitive_equal_general ("new_password") and then loc.starts_with ("account/new-password") then get_block_view_new_password (a_block_id, a_response) - elseif a_block_id.is_case_insensitive_equal_general ("reset_password") and then a_response.location.starts_with ("account/reset-password") then + elseif a_block_id.is_case_insensitive_equal_general ("reset_password") and then loc.starts_with ("account/reset-password") then get_block_view_reset_password (a_block_id, a_response) - elseif a_block_id.is_case_insensitive_equal_general ("registration") and then a_response.location.starts_with ("admin/pending-registrations") then + elseif a_block_id.is_case_insensitive_equal_general ("registration") and then loc.starts_with ("admin/pending-registrations") then get_block_view_registration (a_block_id, a_response) end end diff --git a/modules/contact/contact-safe.ecf b/modules/contact/contact-safe.ecf new file mode 100644 index 0000000..f8d83ce --- /dev/null +++ b/modules/contact/contact-safe.ecf @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/modules/contact/site/config/contact.json b/modules/contact/site/config/contact.json new file mode 100644 index 0000000..c4c4101 --- /dev/null +++ b/modules/contact/site/config/contact.json @@ -0,0 +1,8 @@ +{ + "--email": "webmaster@example.com", + "subjet": "Thank you for contacting us", + "recaptcha": { + "site_key":"", + "secret_key":"" + } +} diff --git a/modules/contact/site/files/css/contact.css b/modules/contact/site/files/css/contact.css new file mode 100644 index 0000000..defc30f --- /dev/null +++ b/modules/contact/site/files/css/contact.css @@ -0,0 +1,124 @@ +.contact-box { + background-color: #F2F7F9; + width: 465px; + padding: 20px; + border: 6px solid #8FB5C1; + -moz-border-radius: 15px; + -webkit-border-radius: 15px; + border-radius: 15px; + position: relative; + /* Remove box shadow firefox, chrome and opera put around required fields. + * It looks rubbish. + */ + /* Normalize placeholder styles */ + /* chrome, safari */ + /* mozilla */ + /* ie (faux placeholder) */ +} +.contact-box h1 { + font-size: 42px; +} +.contact-box h2 { + margin-bottom: 15px; + font-style: italic; + font-weight: normal; +} +.contact-box label { + font-size: 15px; + margin-bottom: 2px; + display: block; +} +.contact-box input, .contact-box select, .contact-box textarea { + width: 100%; + font-size: 15px; + border: 1px solid #CEE1E8; + margin-bottom: 20px; + padding: 4px; +} +.contact-box input:focus, .contact-box select:focus, .contact-box textarea:focus { + border: 1px solid #AFCDD8; + background-color: #EBF2F4; +} +.contact-box textarea { + height: 150px; + resize: none; +} +.contact-box span.required { + font-weight: bold; + color: #F00; +} +.contact-box input[type=submit] { + width: 100px; + background-color: #333; + color: #FFF; + border: none; + display: block; + float: right; + margin-bottom: 0px; + margin-right: 6px; + background-color: #8FB5C1; + -moz-border-radius: 8px; +} +.contact-box input[type=submit]:hover { + background-color: #A6CFDD; +} +.contact-box input[type=submit]:active { + position: relative; + top: 1px; +} +.contact-box .message { + width: 95%; + margin: 25px 0px; + padding: 10px; + display: block; + border: solid 1px #ccc; + border-radius: 8px; + -webkit-border-radius: 8px; + -moz-border-radius: 8px; +} +.contact-box .message.hidden { + display: none; +} +.contact-box .message.error { + border-color: #E58E8E; + background-color: #FFE6E6; +} +.contact-box .message.error li { + padding: 2px; + list-style: none; +} +.contact-box .message.error li:before { + content: ' - '; +} +.contact-box .message.error #info { + font-weight: bold; +} +.contact-box .message.error #info:before { + content: ''; +} +.contact-box .message.success { + border-color: #83D186; + padding-top: 25px; + background-color: #D3EDD3; +} +.contact-box .req-field-desc { + font-style: italic; +} +.contact-box input:required, .contact-box textarea:required { + -moz-box-shadow: none; + -webkit-box-shadow: none; + -o-box-shadow: none; + box-shadow: none; +} +.contact-box ::-webkit-input-placeholder { + color: #CCC; + font-style: italic; +} +.contact-box input:-moz-placeholder, .contact-box textarea:-moz-placeholder { + color: #CCC; + font-style: italic; +} +.contact-box input.placeholder-text, .contact-box textarea.placeholder-text { + color: #CCC; + font-style: italic; +} diff --git a/modules/contact/site/files/scss/contact.scss b/modules/contact/site/files/scss/contact.scss new file mode 100644 index 0000000..4fc2240 --- /dev/null +++ b/modules/contact/site/files/scss/contact.scss @@ -0,0 +1,140 @@ +.contact-box { + background-color:#F2F7F9; + width:465px; + padding:20px; + border: 6px solid #8FB5C1; + -moz-border-radius:15px; + -webkit-border-radius:15px; + border-radius:15px; + position:relative; + + h1 { + font-size:42px; + } + + h2 { + margin-bottom:15px; + font-style:italic; + font-weight:normal; + } + + label { + font-size:15px; + margin-bottom:2px; + display:block; + } + + input, select, textarea { + width:100%; + font-size:15px; + border: 1px solid #CEE1E8; + margin-bottom:20px; + padding:4px; + &:focus { + border: 1px solid #AFCDD8; + background-color: #EBF2F4; + } + } + + textarea { + height:150px; + resize: none; + } + + span.required { + font-weight:bold; + color:#F00; + } + + input[type=submit] { + width: 100px; + background-color:#333; + color:#FFF; + border:none; + display:block; + float:right; + margin-bottom:0px; + margin-right:6px; + background-color:#8FB5C1; + -moz-border-radius:8px; + &:hover { + background-color: #A6CFDD; + } + &:active { + position:relative; + top:1px; + } + } + + .message { + width:95%; + margin:25px 0px; + padding:10px; + display:block; + border:solid 1px #ccc; + border-radius:8px; + -webkit-border-radius:8px; + -moz-border-radius:8px; + + &.hidden { + display: none; + } + + &.error { + border-color: #E58E8E; + background-color:#FFE6E6; + + li { + padding:2px; + list-style:none; + &:before { content: ' - '; } + } + #info { + font-weight:bold; + &:before { content: ''; } + } + } + + &.success { + border-color: #83D186; + padding-top: 25px; + background-color:#D3EDD3; + } + } + + .req-field-desc { + font-style:italic; + } + + /* Remove box shadow firefox, chrome and opera put around required fields. + * It looks rubbish. + */ + input:required, textarea:required { + -moz-box-shadow:none; + -webkit-box-shadow:none; + -o-box-shadow:none; + box-shadow:none; + } + + /* Normalize placeholder styles */ + + /* chrome, safari */ + ::-webkit-input-placeholder { + color:#CCC; + font-style:italic; + } + + /* mozilla */ + input:-moz-placeholder, textarea:-moz-placeholder { + color:#CCC; + font-style:italic; + } + + /* ie (faux placeholder) */ + input.placeholder-text, textarea.placeholder-text { + color:#CCC; + font-style:italic; + } + +} + diff --git a/modules/contact/site/templates/block_contact.tpl b/modules/contact/site/templates/block_contact.tpl new file mode 100644 index 0000000..a8ff0b0 --- /dev/null +++ b/modules/contact/site/templates/block_contact.tpl @@ -0,0 +1,25 @@ +
+

Contact us!

+
+ + + + + + + + + {unless isempty="$recaptcha_site_key"} +
+
+ {/unless} + +

* indicates a required field

+
+ {unless isempty="$error_response"} +
    + {foreach item="item" from="$error_response"}
  • {$item/}
  • {/foreach} +
+
Try again later
+ {/unless} +
diff --git a/modules/contact/site/templates/block_post_contact.tpl b/modules/contact/site/templates/block_post_contact.tpl new file mode 100644 index 0000000..a245283 --- /dev/null +++ b/modules/contact/site/templates/block_post_contact.tpl @@ -0,0 +1,15 @@ +
+{if condition="$has_error"} +
+ Internal Server Error Error 500 +

The page you requested could not be served because the server is down, + either contact the webmaster or try again. + Use your browser's Back button to navigate to the page you came from.

+

Or you could just press this link: Take Me Home

+
+{/if} +{unless condition="$has_error"} +

Thank you for contacting the Eiffel Programming Language community.
+We will get back to you promptly on your contact request.

+{/unless} +
diff --git a/modules/contact/site/templates/email_message.tpl b/modules/contact/site/templates/email_message.tpl new file mode 100644 index 0000000..108e26a --- /dev/null +++ b/modules/contact/site/templates/email_message.tpl @@ -0,0 +1,10 @@ +

+Thank you for contacting {$sitename/}.
+We will get back to you promptly about your contact message. +

+

Your contact information:

+
+ Name: {$name/}
+ Email: {$email/}
+ Message: {$message/}
+
diff --git a/modules/contact/site/templates/email_notification.tpl b/modules/contact/site/templates/email_notification.tpl new file mode 100644 index 0000000..6b411b5 --- /dev/null +++ b/modules/contact/site/templates/email_notification.tpl @@ -0,0 +1,6 @@ +

Contact information:

+
+ Name: {$name/}
+ Email: {$email/}
+ Message: {$message/}
+
diff --git a/modules/contact/src/cms_contact_module.e b/modules/contact/src/cms_contact_module.e new file mode 100644 index 0000000..09f8968 --- /dev/null +++ b/modules/contact/src/cms_contact_module.e @@ -0,0 +1,556 @@ +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, + is_installed, + install, + initialize, + contact_api + end + + SHARED_HTML_ENCODER + + 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" + -- + +feature {CMS_API} -- Module Initialization + + initialize (api: CMS_API) + -- + 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 + + is_installed (api: CMS_API): BOOLEAN + -- Is Current module installed? + local + ut: FILE_UTILITIES + do + Result := ut.directory_path_exists (file_system_storage_path (api)) + end + + 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 + 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 := "

Contact us!

" + f.to_html (a_response.wsf_theme) + "
" + 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 ("

") + end + + create f_submit.make_with_text ("submit-op", "Send") + f.extend (f_submit) + +-- f.extend_html_text ("[ +--

* indicates a required field

+-- ]") + + 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 (html_encoded (api.setup.site_url), "siteurl") + vars.put (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} -- HTML ENCODING. + + html_encoded (s: detachable READABLE_STRING_GENERAL): STRING_8 + do + if s /= Void then + Result := html_encoder.general_encoded_string (s) + else + create Result.make_empty + 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] + utf: UTF_CONVERTER + 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 ("
  • ") + Result.append (html_encoded (ic.key)) + Result.append (": ") + Result.append (ic.item) -- Already html encoded. + Result.append ("
  • %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 := "[ +

    Thank you for contacting $sitename.
    + We will get back to you promptly on your contact request. +

    + ]" + + contact_notification_message_template + end + + contact_notification_message_template: STRING = "[ +

    Contact information:

    +
    + Name: $name
    + Email: $email
    + Message: $message
    +
    + ]" + +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 diff --git a/modules/contact/src/contact_api.e b/modules/contact/src/contact_api.e new file mode 100644 index 0000000..166bfda --- /dev/null +++ b/modules/contact/src/contact_api.e @@ -0,0 +1,40 @@ +note + description: "API for the contact module." + date: "$Date: 2015-05-22 23:00:09 +0200 (ven., 22 mai 2015) $" + revision: "$Revision: 97349 $" + +class + CONTACT_API + +inherit + CMS_MODULE_API + rename + make as make_api + end + + REFACTORING_HELPER + +create + make + +feature {NONE} -- Initialization + + make (a_api: CMS_API; a_contact_storage: like contact_storage) + -- . + do + make_api (a_api) + contact_storage := a_contact_storage + end + +feature {CMS_MODULE} -- Access nodes storage. + + contact_storage: CONTACT_STORAGE_I + +feature -- Basic operation + + save_contact_message (msg: CONTACT_MESSAGE) + do + contact_storage.save_contact_message (msg) + end + +end diff --git a/modules/contact/src/contact_email_service_parameters.e b/modules/contact/src/contact_email_service_parameters.e new file mode 100644 index 0000000..153c018 --- /dev/null +++ b/modules/contact/src/contact_email_service_parameters.e @@ -0,0 +1,63 @@ +note + description: "Summary description for {CONTACT_EMAIL_SERVICE_PARAMETERS}." + date: "$Date: 2015-07-03 19:04:52 +0200 (ven., 03 juil. 2015) $" + revision: "$Revision: 97646 $" + +class + CONTACT_EMAIL_SERVICE_PARAMETERS + +create + make + +feature {NONE} -- Initialization + + make (a_cms_api: CMS_API; a_contact_module: CMS_CONTACT_MODULE) + local + utf: UTF_CONVERTER + l_site_name: READABLE_STRING_8 + s: detachable READABLE_STRING_32 + l_contact_email, l_contact_subject: detachable READABLE_STRING_8 + do + -- Use global smtp setting if any, otherwise "localhost" + l_site_name := utf.escaped_utf_32_string_to_utf_8_string_8 (a_cms_api.setup.site_name) + admin_email := a_cms_api.setup.site_email + + if not admin_email.has ('<') then + admin_email := l_site_name + " <" + admin_email + ">" + end + + if attached {CONFIG_READER} a_cms_api.module_configuration (a_contact_module, Void) as cfg then + s := cfg.text_item ("email") + if s /= Void then + l_contact_email := utf.utf_32_string_to_utf_8_string_8 (s) + end + s := cfg.text_item ("subject") + if s /= Void then + l_contact_subject := utf.utf_32_string_to_utf_8_string_8 (s) + end + end + if l_contact_email /= Void then + if not l_contact_email.has ('<') then + l_contact_email := l_site_name + " <" + l_contact_email + ">" + end + contact_email := l_contact_email + else + contact_email := admin_email + end + if l_contact_subject /= Void then + contact_subject_text := l_contact_subject + else + contact_subject_text := "Thank you for contacting us" + end + end + +feature -- Access + + admin_email: IMMUTABLE_STRING_8 + + contact_email: IMMUTABLE_STRING_8 + -- Contact email. + + contact_subject_text: IMMUTABLE_STRING_8 + +end diff --git a/modules/contact/src/contact_message.e b/modules/contact/src/contact_message.e new file mode 100644 index 0000000..07232bd --- /dev/null +++ b/modules/contact/src/contact_message.e @@ -0,0 +1,43 @@ +note + description: "Interface {CONTACT_MESSAGE} representing the contact's message." + date: "$Date: 2015-07-03 19:04:52 +0200 (ven., 03 juil. 2015) $" + revision: "$Revision: 97646 $" + +class + CONTACT_MESSAGE + +create + make + +feature {NONE} -- Initialization + + make (a_name: like username; a_message: like message) + do + username := a_name + message := a_message + create date.make_now_utc + end + +feature -- Access + + username: READABLE_STRING_32 + + email: detachable READABLE_STRING_8 + + message: READABLE_STRING_32 + + date: DATE_TIME + +feature -- Change + + set_email (e: like email) + do + email := e + end + + set_date (d: like date) + do + date := d + end + +end diff --git a/modules/contact/src/persistence/contact_storage_fs.e b/modules/contact/src/persistence/contact_storage_fs.e new file mode 100644 index 0000000..48782ca --- /dev/null +++ b/modules/contact/src/persistence/contact_storage_fs.e @@ -0,0 +1,56 @@ +note + description: "[ + Contact message storage based on SQL statements. + ]" + date: "$Date: 2015-07-03 19:04:52 +0200 (ven., 03 juil. 2015) $" + revision: "$Revision: 97646 $" + +class + CONTACT_STORAGE_FS + +inherit + CONTACT_STORAGE_I + + CMS_STORAGE_FS_I + + REFACTORING_HELPER + +create + make + +feature -- Access + +feature -- Change + + save_contact_message (m: CONTACT_MESSAGE) + local + s: STRING + utf: UTF_CONVERTER + now: DATE_TIME + do + error_handler.reset + create now.make_now_utc + + write_information_log (generator + ".save_contact_message") + + create s.make_empty + s.append ("date=") + s.append (m.date.out) + s.append_character ('%N') + s.append ("name=") + s.append (utf.utf_32_string_to_utf_8_string_8 (m.username)) + s.append_character ('%N') + + if attached m.email as l_email then + s.append ("email=") + s.append (l_email) + s.append_character ('%N') + end + s.append ("message=%N") + s.append (utf.utf_32_string_to_utf_8_string_8 (m.message)) + s.append_character ('%N') + + save_to_file (s, date_to_yyyymmdd_hhmmss_string (now)) + end + +end diff --git a/modules/contact/src/persistence/contact_storage_i.e b/modules/contact/src/persistence/contact_storage_i.e new file mode 100644 index 0000000..b98b223 --- /dev/null +++ b/modules/contact/src/persistence/contact_storage_i.e @@ -0,0 +1,27 @@ +note + description: "[ + Persistence interface for CONTACT_MODULE. + ]" + author: "$Author: jfiat $" + date: "$Date: 2015-05-22 23:00:09 +0200 (ven., 22 mai 2015) $" + revision: "$Revision: 97349 $" + +deferred class + CONTACT_STORAGE_I + +feature -- Error Handling + + error_handler: ERROR_HANDLER + -- Error handler. + deferred + end + +feature -- Access + +feature -- Change + + save_contact_message (m: CONTACT_MESSAGE) + deferred + end + +end diff --git a/modules/contact/src/persistence/contact_storage_null.e b/modules/contact/src/persistence/contact_storage_null.e new file mode 100644 index 0000000..f6600b4 --- /dev/null +++ b/modules/contact/src/persistence/contact_storage_null.e @@ -0,0 +1,39 @@ +note + description: "[ + Objects that ... + ]" + author: "$Author: jfiat $" + date: "$Date: 2015-05-22 23:00:09 +0200 (ven., 22 mai 2015) $" + revision: "$Revision: 97349 $" + +class + CONTACT_STORAGE_NULL + +inherit + CONTACT_STORAGE_I + +create + make + +feature {NONE} -- Initialization + + make + -- Initialize `Current'. + do + create error_handler.make + end + +feature -- Error Handling + + error_handler: ERROR_HANDLER + -- Error handler. + +feature -- Access + +feature -- Change + + save_contact_message (m: CONTACT_MESSAGE) + do + end + +end diff --git a/modules/contact/src/persistence/contact_storage_sql.e b/modules/contact/src/persistence/contact_storage_sql.e new file mode 100644 index 0000000..19e1dc6 --- /dev/null +++ b/modules/contact/src/persistence/contact_storage_sql.e @@ -0,0 +1,49 @@ +note + description: "[ + Contact message storage based on SQL statements. + ]" + date: "$Date: 2015-02-13 13:08:13 +0100 (ven., 13 févr. 2015) $" + revision: "$Revision: 96616 $" + +class + CONTACT_STORAGE_SQL + +inherit + CMS_PROXY_STORAGE_SQL + + CONTACT_STORAGE_I + + CMS_STORAGE_SQL_I + + REFACTORING_HELPER + +create + make + +feature -- Access + +feature -- Change + + save_contact_message (m: CONTACT_MESSAGE) + local + l_parameters: STRING_TABLE [detachable ANY] + now: DATE_TIME + do + create now.make_now_utc + error_handler.reset + + write_information_log (generator + ".save_contact_message") + create l_parameters.make (9) + l_parameters.put (m, "message") + l_parameters.put (now, "changed") + sql_begin_transaction + sql_modify (sql_insert_contact_message, l_parameters) + sql_commit_transaction + end + +feature {NONE} -- Queries + + sql_insert_contact_message: STRING = "INSERT INTO contact_messages (name, email, date, message) VALUES (:name, :email, :date, :message);" + -- SQL Insert to add a new contact message. + +end diff --git a/modules/node/site/files/scss/node.scss b/modules/node/site/files/scss/node.scss index 5cf9324..34d5e20 100644 --- a/modules/node/site/files/scss/node.scss +++ b/modules/node/site/files/scss/node.scss @@ -3,22 +3,17 @@ ul.cms-nodes { list-style-type: none; padding: 3px 3px 3px 3px; border: solid 1px #ccc; - li{ border-top: dotted 1px #ccc; &:first-child { border-top: none; } } - li.cms_type_page a::before { content: "[page] "; } - li.cms_type_blog a::before { content: "[blog] "; } - - } diff --git a/modules/seo/seo-safe.ecf b/modules/seo/seo-safe.ecf new file mode 100644 index 0000000..19da9de --- /dev/null +++ b/modules/seo/seo-safe.ecf @@ -0,0 +1,14 @@ + + + + + + + + + + + + + diff --git a/modules/seo/src/cms_seo_module.e b/modules/seo/src/cms_seo_module.e new file mode 100644 index 0000000..d7455ab --- /dev/null +++ b/modules/seo/src/cms_seo_module.e @@ -0,0 +1,154 @@ +note + description: "[ + Module that provides Search Engine Optimization. + ]" + date: "$Date: 2016-01-08 22:43:12 +0100 (ven., 08 janv. 2016) $" + revision: "$Revision: 98369 $" + +class + CMS_SEO_MODULE + +inherit + CMS_MODULE + redefine + setup_hooks + end + + CMS_HOOK_AUTO_REGISTER + + CMS_HOOK_RESPONSE_ALTER + + REFACTORING_HELPER + +create + make + +feature {NONE} -- Initialization + + make + -- Create current module + do + version := "1.0" + description := "Search Engine Optimization" + package := "misc" + end + +feature -- Access + + name: STRING = "seo" + -- + +feature -- Router + + setup_router (a_router: WSF_ROUTER; a_api: CMS_API) + -- Router configuration. + do + 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_response_alter_hook (Current) + end + +feature -- Hook + + response_alter (a_response: CMS_RESPONSE) + local + l_siteurl: STRING + l_title, l_desc, l_keywords: detachable READABLE_STRING_32 + l_now: DATE_TIME + dt: detachable DATE_TIME + l_props: STRING_TABLE [READABLE_STRING_32] + api: CMS_API + setup: CMS_SETUP + do + api := a_response.api + setup := api.setup + l_siteurl := a_response.absolute_url (a_response.request.percent_encoded_path_info, Void) + + create l_props.make_equal_caseless (5) + l_props.put (l_siteurl, "url") + l_props.put (api.setup.site_name, "site_name") + if attached api.setup.site_properties as tb then + across + tb as ic + loop + if ic.key.same_string ("headline") then + l_props.force (ic.item, "site_name") + else + l_props.put (ic.item, ic.key) + end + end + end + l_title := a_response.title + if l_title /= Void then + l_props.put (l_title, "title") + end + + l_desc := a_response.description + if l_desc = Void then + l_desc := api.setup.site_description + else + l_props.put (l_desc, "description") + end + l_keywords := a_response.keywords + if l_keywords = Void then + l_keywords := api.setup.site_keywords + else + l_props.put (l_keywords, "keywords") + end + create l_now.make_now_utc + dt := a_response.publication_date + if dt = Void then + dt := l_now + end + l_props.put (date_to_yyyy_mm_dd_string (dt), "published_time") + dt := a_response.modification_date + if dt = Void then + dt := l_now + end + l_props.put (date_to_yyyy_mm_dd_string (dt), "modified_time") + + a_response.add_additional_head_line ("", False) + if l_desc /= Void then + a_response.add_additional_head_line ("", False) + end + if l_keywords /= Void then + a_response.add_additional_head_line ("", False) + end + + -- Meta properties + a_response.add_additional_head_line ("", False) + across + l_props as ic + loop + a_response.add_additional_head_line ("", False) + end + a_response.add_additional_head_line ("", False) + a_response.add_additional_head_line ("", False) + end + +feature -- Helpers: date + + date_to_yyyy_mm_dd_string (dt: DATE_TIME): STRING + -- Date to YYYY-mm-dd format. + do + create Result.make (10) + Result.append_integer (dt.year) + Result.append_character ('-') + if dt.month < 10 then + Result.append_character ('0') + end + Result.append_integer (dt.month) + Result.append_character ('-') + if dt.day < 10 then + Result.append_character ('0') + end + Result.append_integer (dt.day) + end + +end diff --git a/src/configuration/cms_setup.e b/src/configuration/cms_setup.e index fac2593..c19580d 100644 --- a/src/configuration/cms_setup.e +++ b/src/configuration/cms_setup.e @@ -209,6 +209,38 @@ feature -- Access: Site Result := utf.utf_32_string_to_utf_8_string_8 (site_name) end + site_properties: detachable STRING_TABLE [READABLE_STRING_32] + -- Optional site properties. + do + Result := text_table_item ("site.property") + end + + site_property (a_name: READABLE_STRING_GENERAL): detachable READABLE_STRING_32 + do + Result := text_item ({STRING_32} "site." + a_name.as_string_32) + if Result = Void and then attached site_properties as props then + Result := props.item (a_name) + end + end + + site_description: detachable READABLE_STRING_32 + -- Optional site description. + do + Result := site_property ("description") + end + + site_headline: detachable READABLE_STRING_32 + -- Optional site headline. + do + Result := site_property ("headline") + end + + site_keywords: detachable READABLE_STRING_32 + -- Optional site comma separated keywords. + do + Result := site_property ("keywords") + end + site_email: READABLE_STRING_8 -- Website email address. -- Used as "From:" address when the site is sending emails diff --git a/src/hooks/cms_hook_block_helper.e b/src/hooks/cms_hook_block_helper.e index d0b90ef..2eb3b16 100644 --- a/src/hooks/cms_hook_block_helper.e +++ b/src/hooks/cms_hook_block_helper.e @@ -27,6 +27,6 @@ feature -- Factory end note - copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Eiffel Software and others" + copyright: "2011-2016, Jocelyn Fiat, Javier Velilla, Eiffel Software and others" license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)" end diff --git a/src/service/misc/cms_string_expander.e b/src/service/misc/cms_string_expander.e new file mode 100644 index 0000000..b5a462e --- /dev/null +++ b/src/service/misc/cms_string_expander.e @@ -0,0 +1,119 @@ +note + description: "[ + String expander facility for the CMS. + ]" + date: "$Date$" + revision: "$Revision$" + +class + CMS_STRING_EXPANDER [G -> STRING_GENERAL] + +create + make + +feature {NONE} -- Initialization + + make + do + create values.make (3) + end + +feature -- Access + + values: STRING_TABLE [G] + + has_error: BOOLEAN + do + Result := attached error_handler as h and then h.has_error + end + +feature -- Element change + + force, put (new: G; key: READABLE_STRING_GENERAL) + do + values.force (new, key) + end + +feature -- Conversion + + expand_string (a_text: STRING_GENERAL) + do + if attached {STRING_32} a_text as t32 then + expand_string_32 (t32) + elseif attached {STRING_8} a_text as t8 then + expand_string_8 (t8) + else + check known_string: False end + end + end + + expand_string_8 (a_text: STRING) + local + k, s: STRING_8 + utf: UTF_CONVERTER + do + --| FIXME: implement better string expander. + across + values as ic + loop + if attached {READABLE_STRING_8} ic.item as v then + s := v.to_string_8 + elseif attached ic.item as v then + s := utf.utf_32_string_to_utf_8_string_8 (v) + else + create s.make_empty + end + if attached {READABLE_STRING_8} ic.key as v then + k := v + else + k := utf.utf_32_string_to_utf_8_string_8 (ic.key) + end + a_text.replace_substring_all ({STRING_8} "$" + k, s) + end + end + + expand_string_32 (a_text: STRING_32) + local + k, s: STRING_32 + do + --| FIXME: implement better string expander. + across + values as ic + loop + if attached ic.item as v then + s := v.to_string_32 + else + create s.make_empty + end + k := ic.key.to_string_32 + a_text.replace_substring_all ({STRING_32} "$" + k, s) + end + end + +feature {NONE} -- Implementation + + reset_error + do + if attached error_handler as h then + h.reset + end + end + + error_handler: detachable ERROR_HANDLER + + report_error (m: detachable READABLE_STRING_GENERAL) + local + h: like error_handler + do + h := error_handler + if h = Void then + create h.make + error_handler := h + end + h.add_custom_error (-1, "string expander error", m) + end + +;note + copyright: "2011-2016, Jocelyn Fiat, Javier Velilla, Eiffel Software and others" + license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)" +end diff --git a/src/service/response/cms_response.e b/src/service/response/cms_response.e index 751075c..3eaafa5 100644 --- a/src/service/response/cms_response.e +++ b/src/service/response/cms_response.e @@ -91,12 +91,24 @@ feature -- Access header: WSF_HEADER + main_content: detachable STRING_8 + +feature -- Access: metadata + title: detachable READABLE_STRING_32 page_title: detachable READABLE_STRING_32 -- Page title - main_content: detachable STRING_8 + description: detachable READABLE_STRING_32 + + keywords: detachable READABLE_STRING_32 + + publication_date: detachable DATE_TIME + -- Optional publication date. + + modification_date: detachable DATE_TIME + -- Optional modification date. additional_page_head_lines: detachable LIST [READABLE_STRING_8] -- HTML>head>...extra lines @@ -104,6 +116,8 @@ feature -- Access redirection: detachable READABLE_STRING_8 -- Location for eventual redirection. +feature -- Access: query + location: STRING_8 -- Associated cms local location. do @@ -113,6 +127,7 @@ feature -- Access end end + feature -- API api: CMS_API @@ -286,6 +301,32 @@ feature -- Element change page_title := t end + set_description (d: like description) + do + description := d + end + + set_keywords (s: like keywords) + do + keywords := s + end + + set_publication_date (dt: like publication_date) + do + publication_date := dt + if dt /= Void and modification_date = Void then + modification_date := dt + end + end + + set_modification_date (dt: like modification_date) + do + modification_date := dt + if dt /= Void and publication_date = Void then + publication_date := dt + end + end + set_main_content (s: like main_content) do main_content := s @@ -1358,6 +1399,6 @@ feature {NONE} -- Execution end note - copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Eiffel Software and others" + copyright: "2011-2016, Jocelyn Fiat, Javier Velilla, Eiffel Software and others" license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)" end