From a341bd98ebc583f10cf3897581044d916953489c Mon Sep 17 00:00:00 2001 From: Jocelyn Fiat Date: Tue, 28 Feb 2017 11:24:48 +0100 Subject: [PATCH] Improved node management. - List node by node types - fixed the trash/restore/delete workflow Added messaging module to send message to cms users (by email for now). Added early protection for cache, export and import functionalities. --- examples/demo/demo.ecf | 1 + examples/demo/roc.cfg | 1 + .../site/modules/admin/files/css/admin.css | 4 +- .../site/modules/admin/files/scss/admin.scss | 4 +- .../modules/messaging/files/css/messaging.css | 4 + .../messaging/files/scss/messaging.scss | 6 + examples/demo/src/demo_cms_execution.e | 1 + modules/admin/cms_admin_module.e | 4 + .../admin/handler/cms_admin_cache_handler.e | 48 ++- .../admin/handler/cms_admin_export_handler.e | 72 ++-- .../admin/handler/cms_admin_import_handler.e | 77 ++-- .../handler/user/cms_admin_users_handler.e | 13 +- modules/admin/site/files/css/admin.css | 4 +- modules/admin/site/files/scss/admin.scss | 4 +- modules/messaging/messaging-safe.ecf | 20 + .../messaging/site/files/css/messaging.css | 4 + .../messaging/site/files/scss/messaging.scss | 6 + modules/messaging/src/cms_messaging_api.e | 17 + modules/messaging/src/cms_messaging_module.e | 381 ++++++++++++++++++ modules/node/cms_node_api.e | 11 + modules/node/cms_node_module.e | 3 +- modules/node/content/cms_node.e | 10 +- .../handler/cms_node_type_webform_manager.e | 17 +- modules/node/handler/node_form_response.e | 51 ++- modules/node/handler/node_handler.e | 40 +- modules/node/handler/nodes_handler.e | 89 +++- modules/node/persistence/cms_node_storage_i.e | 15 +- .../node/persistence/cms_node_storage_null.e | 6 + .../node/persistence/cms_node_storage_sql.e | 50 ++- src/kernel/content/cms_smarty_template_text.e | 98 +++++ src/service/cms_api.e | 82 +++- src/service/cms_api_import_imp.e | 24 +- src/service/cms_email.e | 14 +- src/service/response/cms_response.e | 72 ++-- src/service/user/cms_user_api.e | 14 + 35 files changed, 1055 insertions(+), 212 deletions(-) create mode 100644 examples/demo/site/modules/messaging/files/css/messaging.css create mode 100644 examples/demo/site/modules/messaging/files/scss/messaging.scss create mode 100644 modules/messaging/messaging-safe.ecf create mode 100644 modules/messaging/site/files/css/messaging.css create mode 100644 modules/messaging/site/files/scss/messaging.scss create mode 100644 modules/messaging/src/cms_messaging_api.e create mode 100644 modules/messaging/src/cms_messaging_module.e create mode 100644 src/kernel/content/cms_smarty_template_text.e diff --git a/examples/demo/demo.ecf b/examples/demo/demo.ecf index d6466eb..8491d47 100644 --- a/examples/demo/demo.ecf +++ b/examples/demo/demo.ecf @@ -32,6 +32,7 @@ + diff --git a/examples/demo/roc.cfg b/examples/demo/roc.cfg index 88eb9c9..c7160be 100644 --- a/examples/demo/roc.cfg +++ b/examples/demo/roc.cfg @@ -23,6 +23,7 @@ "files": { "location": "../../modules/files" }, "custom_block": { "location": "../../modules/custom_block" }, "wikitext": { "location": "../../modules/wikitext" }, + "messaging": { "location": "../../modules/messaging" }, "comments": { "location": "../../modules/comments" } } } diff --git a/examples/demo/site/modules/admin/files/css/admin.css b/examples/demo/site/modules/admin/files/css/admin.css index 34127cd..c6386ae 100644 --- a/examples/demo/site/modules/admin/files/css/admin.css +++ b/examples/demo/site/modules/admin/files/css/admin.css @@ -10,7 +10,7 @@ ul.cms-users li:first-child { border-top: none; } ul.cms-users li.cms_user a::before { - content: "[users] "; + content: "[user] "; } ul.cms-roles { @@ -25,7 +25,7 @@ ul.cms-roles li:first-child { border-top: none; } ul.cms-roles li.cms_role a::before { - content: "[roles] "; + content: "[role] "; } ul.cms-permissions { diff --git a/examples/demo/site/modules/admin/files/scss/admin.scss b/examples/demo/site/modules/admin/files/scss/admin.scss index 8cf64a1..b075b88 100644 --- a/examples/demo/site/modules/admin/files/scss/admin.scss +++ b/examples/demo/site/modules/admin/files/scss/admin.scss @@ -12,7 +12,7 @@ ul.cms-users { } li.cms_user a::before { - content: "[users] "; + content: "[user] "; } } @@ -31,7 +31,7 @@ ul.cms-roles { } li.cms_role a::before { - content: "[roles] "; + content: "[role] "; } } diff --git a/examples/demo/site/modules/messaging/files/css/messaging.css b/examples/demo/site/modules/messaging/files/css/messaging.css new file mode 100644 index 0000000..dfed698 --- /dev/null +++ b/examples/demo/site/modules/messaging/files/css/messaging.css @@ -0,0 +1,4 @@ +.messaging-box fieldset { + overflow: scroll; + height: 250px; +} diff --git a/examples/demo/site/modules/messaging/files/scss/messaging.scss b/examples/demo/site/modules/messaging/files/scss/messaging.scss new file mode 100644 index 0000000..655859d --- /dev/null +++ b/examples/demo/site/modules/messaging/files/scss/messaging.scss @@ -0,0 +1,6 @@ +.messaging-box { + fieldset { + overflow:scroll; + height:250px; + } +} diff --git a/examples/demo/src/demo_cms_execution.e b/examples/demo/src/demo_cms_execution.e index 1d66e23..9d5bf49 100644 --- a/examples/demo/src/demo_cms_execution.e +++ b/examples/demo/src/demo_cms_execution.e @@ -84,6 +84,7 @@ feature -- CMS modules a_setup.register_module (create {FEED_AGGREGATOR_MODULE}.make) -- Miscellanious + a_setup.register_module (create {CMS_MESSAGING_MODULE}.make) a_setup.register_module (create {GOOGLE_CUSTOM_SEARCH_MODULE}.make) a_setup.register_module (create {CMS_CUSTOM_BLOCK_MODULE}.make) a_setup.register_module (create {CMS_DEBUG_MODULE}.make) diff --git a/modules/admin/cms_admin_module.e b/modules/admin/cms_admin_module.e index fc0c808..b59d893 100644 --- a/modules/admin/cms_admin_module.e +++ b/modules/admin/cms_admin_module.e @@ -124,6 +124,7 @@ feature -- Security Result.force ("admin users") Result.force ("admin roles") Result.force ("admin modules") + Result.force ("admin cache") Result.force ("admin core caches") Result.force ("clear blocks cache") Result.force ("admin export") @@ -159,13 +160,16 @@ feature -- Hooks -- Per module cache permission! create lnk.make ("Cache", "admin/cache") + lnk.set_permission_arguments (<<"admin cache">>) admin_lnk.extend (lnk) -- Per module export permission! create lnk.make ("Export", "admin/export") + lnk.set_permission_arguments (<<"admin export">>) admin_lnk.extend (lnk) -- Per module import permission! create lnk.make ("Import", "admin/import") + lnk.set_permission_arguments (<<"admin import">>) admin_lnk.extend (lnk) end end diff --git a/modules/admin/handler/cms_admin_cache_handler.e b/modules/admin/handler/cms_admin_cache_handler.e index 5f53515..afe5415 100644 --- a/modules/admin/handler/cms_admin_cache_handler.e +++ b/modules/admin/handler/cms_admin_cache_handler.e @@ -41,11 +41,15 @@ feature -- Execution s: STRING f: CMS_FORM do - create {GENERIC_VIEW_CMS_RESPONSE} l_response.make (req, res, api) - f := clear_cache_web_form (l_response) - create s.make_empty - f.append_to_html (l_response.wsf_theme, s) - l_response.set_main_content (s) + if api.has_permission ("admin cache") then + create {GENERIC_VIEW_CMS_RESPONSE} l_response.make (req, res, api) + f := clear_cache_web_form (l_response) + create s.make_empty + f.append_to_html (l_response.wsf_theme, s) + l_response.set_main_content (s) + else + create {FORBIDDEN_ERROR_CMS_RESPONSE} l_response.make (req, res, api) + end l_response.execute end @@ -55,23 +59,27 @@ feature -- Execution s: STRING f: CMS_FORM do - create {GENERIC_VIEW_CMS_RESPONSE} l_response.make (req, res, api) - f := clear_cache_web_form (l_response) - f.process (l_response) - if - attached f.last_data as fd and then - fd.is_valid - then - if attached fd.string_item ("op") as l_op and then l_op.same_string (text_clear_all_caches) then - api.hooks.invoke_clear_cache (Void, l_response) - l_response.add_notice_message ("Caches cleared (if allowed)!") - else - fd.report_error ("Invalid form data!") + if api.has_permission ("admin cache") then + create {GENERIC_VIEW_CMS_RESPONSE} l_response.make (req, res, api) + f := clear_cache_web_form (l_response) + f.process (l_response) + if + attached f.last_data as fd and then + fd.is_valid + then + if attached fd.string_item ("op") as l_op and then l_op.same_string (text_clear_all_caches) then + api.hooks.invoke_clear_cache (Void, l_response) + l_response.add_notice_message ("Caches cleared (if allowed)!") + else + fd.report_error ("Invalid form data!") + end end + create s.make_empty + f.append_to_html (l_response.wsf_theme, s) + l_response.set_main_content (s) + else + create {FORBIDDEN_ERROR_CMS_RESPONSE} l_response.make (req, res, api) end - create s.make_empty - f.append_to_html (l_response.wsf_theme, s) - l_response.set_main_content (s) l_response.execute end diff --git a/modules/admin/handler/cms_admin_export_handler.e b/modules/admin/handler/cms_admin_export_handler.e index 23a48ed..a7eb3eb 100644 --- a/modules/admin/handler/cms_admin_export_handler.e +++ b/modules/admin/handler/cms_admin_export_handler.e @@ -41,11 +41,15 @@ feature -- Execution s: STRING f: CMS_FORM do - create {GENERIC_VIEW_CMS_RESPONSE} l_response.make (req, res, api) - f := exportation_web_form (l_response) - create s.make_empty - f.append_to_html (l_response.wsf_theme, s) - l_response.set_main_content (s) + if api.has_permission ("admin export") then + create {GENERIC_VIEW_CMS_RESPONSE} l_response.make (req, res, api) + f := exportation_web_form (l_response) + create s.make_empty + f.append_to_html (l_response.wsf_theme, s) + l_response.set_main_content (s) + else + create {FORBIDDEN_ERROR_CMS_RESPONSE} l_response.make (req, res, api) + end l_response.execute end @@ -56,37 +60,41 @@ feature -- Execution f: CMS_FORM l_exportation: CMS_EXPORT_CONTEXT do - create {GENERIC_VIEW_CMS_RESPONSE} l_response.make (req, res, api) - f := exportation_web_form (l_response) - f.process (l_response) - if - attached f.last_data as fd and then - fd.is_valid - then - if attached fd.string_item ("op") as l_op and then l_op.same_string (text_export_all_data) then - if attached fd.string_item ("folder") as l_folder then - create l_exportation.make (api.site_location.extended ("export").extended (l_folder)) + if api.has_permission ("admin export") then + create {GENERIC_VIEW_CMS_RESPONSE} l_response.make (req, res, api) + f := exportation_web_form (l_response) + f.process (l_response) + if + attached f.last_data as fd and then + fd.is_valid + then + if attached fd.string_item ("op") as l_op and then l_op.same_string (text_export_all_data) then + if attached fd.string_item ("folder") as l_folder then + create l_exportation.make (api.site_location.extended ("export").extended (l_folder)) + else + create l_exportation.make (api.site_location.extended ("export").extended ((create {DATE_TIME}.make_now_utc).formatted_out ("yyyy-[0]mm-[0]dd---hh24-[0]mi-[0]ss"))) + end + api.hooks.invoke_export_to (Void, l_exportation, l_response) + l_response.add_notice_message ("All data exported (if allowed)!") + create s.make_empty + across + l_exportation.logs as ic + loop + s.append (ic.item) + s.append ("
") + s.append_character ('%N') + end + l_response.add_notice_message (s) else - create l_exportation.make (api.site_location.extended ("export").extended ((create {DATE_TIME}.make_now_utc).formatted_out ("yyyy-[0]mm-[0]dd---hh24-[0]mi-[0]ss"))) + fd.report_error ("Invalid form data!") end - api.hooks.invoke_export_to (Void, l_exportation, l_response) - l_response.add_notice_message ("All data exported (if allowed)!") - create s.make_empty - across - l_exportation.logs as ic - loop - s.append (ic.item) - s.append ("
") - s.append_character ('%N') - end - l_response.add_notice_message (s) - else - fd.report_error ("Invalid form data!") end + create s.make_empty + f.append_to_html (l_response.wsf_theme, s) + l_response.set_main_content (s) + else + create {FORBIDDEN_ERROR_CMS_RESPONSE} l_response.make (req, res, api) end - create s.make_empty - f.append_to_html (l_response.wsf_theme, s) - l_response.set_main_content (s) l_response.execute end diff --git a/modules/admin/handler/cms_admin_import_handler.e b/modules/admin/handler/cms_admin_import_handler.e index 05d31ce..845d1bf 100644 --- a/modules/admin/handler/cms_admin_import_handler.e +++ b/modules/admin/handler/cms_admin_import_handler.e @@ -41,11 +41,15 @@ feature -- Execution s: STRING f: CMS_FORM do - create {GENERIC_VIEW_CMS_RESPONSE} l_response.make (req, res, api) - f := importation_web_form (l_response) - create s.make_empty - f.append_to_html (l_response.wsf_theme, s) - l_response.set_main_content (s) + if api.has_permission ("admin import") then + create {GENERIC_VIEW_CMS_RESPONSE} l_response.make (req, res, api) + f := importation_web_form (l_response) + create s.make_empty + f.append_to_html (l_response.wsf_theme, s) + l_response.set_main_content (s) + else + create {FORBIDDEN_ERROR_CMS_RESPONSE} l_response.make (req, res, api) + end l_response.execute end @@ -57,43 +61,48 @@ feature -- Execution l_importation: CMS_IMPORT_CONTEXT p: PATH do - create {GENERIC_VIEW_CMS_RESPONSE} l_response.make (req, res, api) - f := importation_web_form (l_response) - f.process (l_response) - if - attached f.last_data as fd and then - fd.is_valid - then - if attached fd.string_item ("op") as l_op and then l_op.same_string (text_import_all_data) then - if attached fd.string_item ("folder") as l_folder then - create p.make_from_string (l_folder) - create l_importation.make (api.site_location.extended (import_folder_name).extended (l_folder)) - if l_importation.location_exists then - l_response.add_notice_message ("Import all data (if permitted)!") - api.hooks.invoke_import_from (Void, l_importation, l_response) - create s.make_empty - across - l_importation.logs as ic - loop - s.append (ic.item) - s.append ("
") - s.append_character ('%N') + if api.has_permission ("admin import") then + create {GENERIC_VIEW_CMS_RESPONSE} l_response.make (req, res, api) + f := importation_web_form (l_response) + f.process (l_response) + if + attached f.last_data as fd and then + fd.is_valid + then + if attached fd.string_item ("op") as l_op and then l_op.same_string (text_import_all_data) then + if attached fd.string_item ("folder") as l_folder then + create p.make_from_string (l_folder) + create l_importation.make (api.site_location.extended (import_folder_name).extended (l_folder)) + if l_importation.location_exists then + l_response.add_notice_message ("Import all data (if permitted)!") + api.hooks.invoke_import_from (Void, l_importation, l_response) + create s.make_empty + across + l_importation.logs as ic + loop + s.append (ic.item) + s.append ("
") + s.append_character ('%N') + end + l_response.add_notice_message (s) + else + l_response.add_error_message ("Specified import folder is not found!") + fd.report_invalid_field ("folder", "Folder not found!") end - l_response.add_notice_message (s) else - l_response.add_error_message ("Specified import folder is not found!") - fd.report_invalid_field ("folder", "Folder not found!") + fd.report_error ("Invalid form data!") end else fd.report_error ("Invalid form data!") end - else - fd.report_error ("Invalid form data!") end + create s.make_empty + f.append_to_html (l_response.wsf_theme, s) + l_response.set_main_content (s) + else + create {FORBIDDEN_ERROR_CMS_RESPONSE} l_response.make (req, res, api) end - create s.make_empty - f.append_to_html (l_response.wsf_theme, s) - l_response.set_main_content (s) + l_response.execute end diff --git a/modules/admin/handler/user/cms_admin_users_handler.e b/modules/admin/handler/user/cms_admin_users_handler.e index 16c7b2e..0aad636 100644 --- a/modules/admin/handler/user/cms_admin_users_handler.e +++ b/modules/admin/handler/user/cms_admin_users_handler.e @@ -66,6 +66,7 @@ feature -- HTTP Methods s_pager: STRING l_count: INTEGER user_api: CMS_USER_API + l_display_name: READABLE_STRING_32 do -- At the moment the template are hardcoded, but we can -- get them from the configuration file and load them into @@ -81,9 +82,9 @@ feature -- HTTP Methods create s.make_empty if l_count > 1 then - l_response.set_title ("Listing " + l_count.out + " Users") + l_response.set_title ("Listing " + l_count.out + " users") else - l_response.set_title ("Listing " + l_count.out + " User") + l_response.set_title ("A single user") end create s_pager.make_empty @@ -106,7 +107,13 @@ feature -- HTTP Methods s.append ("") - s.append (html_encoded (u.name)) + l_display_name := user_api.user_display_name (u) + s.append (html_encoded (l_display_name)) + if not l_display_name.same_string (u.name) then + s.append (" [") + s.append (html_encoded (u.name)) + s.append ("]") + end s.append ("") if attached user_api.user_roles (u) as l_roles and then not l_roles.is_empty then s.append (" (") diff --git a/modules/admin/site/files/css/admin.css b/modules/admin/site/files/css/admin.css index 34127cd..c6386ae 100644 --- a/modules/admin/site/files/css/admin.css +++ b/modules/admin/site/files/css/admin.css @@ -10,7 +10,7 @@ ul.cms-users li:first-child { border-top: none; } ul.cms-users li.cms_user a::before { - content: "[users] "; + content: "[user] "; } ul.cms-roles { @@ -25,7 +25,7 @@ ul.cms-roles li:first-child { border-top: none; } ul.cms-roles li.cms_role a::before { - content: "[roles] "; + content: "[role] "; } ul.cms-permissions { diff --git a/modules/admin/site/files/scss/admin.scss b/modules/admin/site/files/scss/admin.scss index 8cf64a1..b075b88 100644 --- a/modules/admin/site/files/scss/admin.scss +++ b/modules/admin/site/files/scss/admin.scss @@ -12,7 +12,7 @@ ul.cms-users { } li.cms_user a::before { - content: "[users] "; + content: "[user] "; } } @@ -31,7 +31,7 @@ ul.cms-roles { } li.cms_role a::before { - content: "[roles] "; + content: "[role] "; } } diff --git a/modules/messaging/messaging-safe.ecf b/modules/messaging/messaging-safe.ecf new file mode 100644 index 0000000..5b9da41 --- /dev/null +++ b/modules/messaging/messaging-safe.ecf @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + diff --git a/modules/messaging/site/files/css/messaging.css b/modules/messaging/site/files/css/messaging.css new file mode 100644 index 0000000..dfed698 --- /dev/null +++ b/modules/messaging/site/files/css/messaging.css @@ -0,0 +1,4 @@ +.messaging-box fieldset { + overflow: scroll; + height: 250px; +} diff --git a/modules/messaging/site/files/scss/messaging.scss b/modules/messaging/site/files/scss/messaging.scss new file mode 100644 index 0000000..655859d --- /dev/null +++ b/modules/messaging/site/files/scss/messaging.scss @@ -0,0 +1,6 @@ +.messaging-box { + fieldset { + overflow:scroll; + height:250px; + } +} diff --git a/modules/messaging/src/cms_messaging_api.e b/modules/messaging/src/cms_messaging_api.e new file mode 100644 index 0000000..00e0732 --- /dev/null +++ b/modules/messaging/src/cms_messaging_api.e @@ -0,0 +1,17 @@ +note + description: "API for the contact module." + date: "$Date$" + revision: "$Revision$" + +class + CMS_MESSAGING_API + +inherit + CMS_MODULE_API + + REFACTORING_HELPER + +create + make + +end diff --git a/modules/messaging/src/cms_messaging_module.e b/modules/messaging/src/cms_messaging_module.e new file mode 100644 index 0000000..1fc8351 --- /dev/null +++ b/modules/messaging/src/cms_messaging_module.e @@ -0,0 +1,381 @@ +note + description: "[ + Module that provides messenger functionality. + ]" + author: "$Author$" + date: "$Date$" + revision: "$Revision$" + +class + CMS_MESSAGING_MODULE + +inherit + CMS_MODULE + rename + module_api as messaging_api + redefine + setup_hooks, +-- install, + initialize, + permissions, + messaging_api + end + + CMS_HOOK_AUTO_REGISTER + + CMS_HOOK_MENU_SYSTEM_ALTER + + REFACTORING_HELPER + + SHARED_LOGGER + +create + make + +feature {NONE} -- Initialization + + make + -- Create current module + do + version := "1.0" + description := "Messaging module" + package := "messaging" + end + +feature -- Access + + name: STRING = "messaging" + -- + +feature {CMS_API} -- Module Initialization + + initialize (api: CMS_API) + -- + local + l_messaging_api: like messaging_api + do + Precursor (api) + create l_messaging_api.make (api) + messaging_api := l_messaging_api + end + +feature {CMS_API} -- Access: API + + messaging_api: detachable CMS_MESSAGING_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 ("/messaging", create {WSF_URI_AGENT_HANDLER}.make (agent handle_get_messaging (a_api, ?, ?))) + a_router.map (m, a_router.methods_head_get) + a_router.handle ("/messaging", create {WSF_URI_AGENT_HANDLER}.make (agent handle_post_messaging (a_api, ?, ?)), a_router.methods_put_post) + end + +feature -- Security + + permissions: LIST [READABLE_STRING_8] + -- List of permission ids, used by this module, and declared. + do + Result := Precursor + Result.force ("admin messaging") + Result.force ("message any user") + Result.force ("use messaging") + end + +feature -- Hooks configuration + + setup_hooks (a_hooks: CMS_HOOK_CORE_MANAGER) + -- Module hooks configuration. + do + auto_subscribe_to_hooks (a_hooks) + 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 messaging to menu") + end + end + + new_html_messaging_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/messaging.css", Void), Void) +-- TODO: use template to overwrite/customize +-- if attached smarty_template_block (Current, "messaging", api) as l_tpl_block then +-- 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_messaging_form (a_response, api) + api.hooks.invoke_form_alter (f, f.last_data, a_response) + + Result := "

Send message to ...

" + f.to_html (a_response.wsf_theme) + "
" +-- end + end + + new_messaging_form (a_response: CMS_RESPONSE; api: CMS_API): CMS_FORM + local + f: CMS_FORM + f_name: WSF_FORM_TEXT_INPUT + f_msg: WSF_FORM_TEXTAREA + f_submit: WSF_FORM_SUBMIT_INPUT + f_user: WSF_FORM_CHECKBOX_INPUT + f_set: WSF_FORM_FIELD_SET + l_params: CMS_DATA_QUERY_PARAMETERS + nb: INTEGER + i: INTEGER + do + create f.make (a_response.url ("messaging", Void), "messaging-form") + if attached api.user as l_current_user then + nb := api.user_api.users_count + from + create f_set.make + f_set.set_legend ("Select users") + f.extend (f_set) + i := 0 + until + i > nb + loop + create l_params.make (i.to_natural_64, 25) + if attached api.user_api.recent_users (l_params) as l_users then + across + l_users as ic + loop + if l_current_user.id = ic.item.id then + else + create f_user.make_with_value ("users[]", ic.item.id.out) + f_user.set_title (api.user_api.user_display_name (ic.item)) + f_set.extend (f_user) + end + end + end + i := i + 25 + end + + create f_name.make ("title") + f_name.set_size (80) + f_name.set_label ("Title") + f_name.set_is_required (True) + f.extend (f_name) + + create f_msg.make ("message") + f_msg.set_cols (80) + f_msg.set_rows (75) + f_msg.set_label ("Message") + f_msg.set_rows (5) + f_msg.set_is_required (True) + f.extend (f_msg) + + create f_submit.make_with_text ("submit-op", "Send") + f.extend (f_submit) + end + Result := f + end + + handle_get_messaging (api: CMS_API; req: WSF_REQUEST; res: WSF_RESPONSE) + local + r: CMS_RESPONSE + do + if api.has_permission ("use messaging") or api.has_permission ("message any user") then + create {GENERIC_VIEW_CMS_RESPONSE} r.make (req, res, api) + r.values.force ("messaging", "messaging") + r.set_main_content (new_html_messaging_form (r, api)) + else + create {FORBIDDEN_ERROR_CMS_RESPONSE} r.make (req, res, api) + end + r.execute + end + + handle_post_messaging (api: CMS_API; req: WSF_REQUEST; res: WSF_RESPONSE) + local + r: CMS_RESPONSE + e: CMS_EMAIL + l_emails: ARRAYED_LIST [CMS_EMAIL] + vars: STRING_TABLE [READABLE_STRING_8] + l_messaging_email_address: READABLE_STRING_8 + s: STRING + l_uid: READABLE_STRING_32 + f: like new_messaging_form + l_user: detachable CMS_USER + l_email_title: READABLE_STRING_8 + l_email_messg: READABLE_STRING_8 + do + if api.has_permission ("message any user") then + create {GENERIC_VIEW_CMS_RESPONSE} r.make (req, res, api) + r.add_style (r.url ("/module/" + name + "/files/css/messaging.css", Void), Void) + + create s.make_empty + + f := new_messaging_form (r, api) + f.process (r) + if attached f.last_data as fd then + if + not fd.has_error and then + attached fd.string_item ("title") as l_title and then + attached fd.string_item ("message") as l_message and then + attached fd.table_item ("users") as l_users + then + create l_emails.make (l_users.count) + + s.append ("Send message %"") + s.append (r.html_encoded (l_title)) + s.append ("%"") + s.append (" to users:
    ") + across + l_users as ic + loop + if attached {WSF_STRING} ic.item as p_uid then + l_uid := p_uid.value + if l_uid.is_integer_64 then + l_user := api.user_api.user_by_id (l_uid.to_integer_64) + else + l_user := api.user_api.user_by_name (l_uid) + end + s.append ("
  • ") + if l_user /= Void and then attached l_user.email as l_user_email then + s.append (r.html_encoded (api.user_api.user_display_name (l_user))) + s.append (" <") + s.append (r.html_encoded (l_user_email)) + s.append (">") + + l_email_title := resolved_template_text (api, l_title, l_user) + l_email_messg := resolved_template_text (api, l_message, l_user) + + + e := api.new_email (l_user_email, l_email_title, l_email_messg) + + s.append ("
    ")
    +									s.append (e.message)
    +									s.append ("
    ") + l_emails.force (e) + api.process_email (e) + if e.is_sent then + s.append (" successfully sent.") + else + s.append (" failure, not sent!") + end + else + s.append (r.html_encoded (p_uid.value)) + s.append (" skipped!") + end + s.append ("
  • %N") + end + end + else + f.append_to_html (r.wsf_theme, s) + end + end + r.set_main_content (s) + else + create {FORBIDDEN_ERROR_CMS_RESPONSE} r.make (req, res, api) + end + r.execute + 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 + + resolved_template_text (api: CMS_API; a_text: READABLE_STRING_GENERAL; a_target_user: detachable CMS_USER): STRING_8 + local + smt: CMS_SMARTY_TEMPLATE_TEXT + utf: UTF_CONVERTER + do + create smt.make (utf.utf_32_string_to_utf_8_string_8 (a_text)) + across + api.builtin_variables as vars_ic + loop + smt.set_value (vars_ic.item, vars_ic.key) + end + if a_target_user /= Void then + smt.set_value (a_target_user.name, "target_user_name") + smt.set_value (api.user_api.user_display_name (a_target_user), "target_user_profile_name") + smt.set_value (a_target_user.id.out, "target_user_id") + if attached a_target_user.email as l_email then + smt.set_value (l_email, "target_user_email") + end + end + Result := smt.string + 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 (messaging_message_template) +-- elseif a_message_id.is_case_insensitive_equal_general ("notification") then +-- create Result.make_from_string (messaging_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 + +end diff --git a/modules/node/cms_node_api.e b/modules/node/cms_node_api.e index cad6ea2..54c4834 100644 --- a/modules/node/cms_node_api.e +++ b/modules/node/cms_node_api.e @@ -153,6 +153,11 @@ feature -- Access: Node Result := node_storage.nodes_count end + nodes_of_type_count (a_content_type: CMS_CONTENT_TYPE): NATURAL_64 + do + Result := node_storage.nodes_of_type_count (a_content_type) + end + nodes: LIST [CMS_NODE] -- List of nodes. do @@ -179,6 +184,12 @@ feature -- Access: Node Result := node_storage.recent_nodes (params.offset.to_integer_32, params.size.to_integer_32) end + recent_nodes_of_type (a_content_type: CMS_CONTENT_TYPE; params: CMS_DATA_QUERY_PARAMETERS): ITERABLE [CMS_NODE] + -- Most recent `a_content_type` nodes according to `params.offset' and `params.size'. + do + Result := node_storage.recent_nodes_of_type (a_content_type, params.offset.to_integer_32, params.size.to_integer_32) + end + recent_node_changes_before (params: CMS_DATA_QUERY_PARAMETERS; a_date: DATE_TIME): ITERABLE [CMS_NODE] -- List of recent changes, before `a_date', according to `params' settings. do diff --git a/modules/node/cms_node_module.e b/modules/node/cms_node_module.e index 048309c..17a3f46 100644 --- a/modules/node/cms_node_module.e +++ b/modules/node/cms_node_module.e @@ -150,13 +150,13 @@ feature -- Access Result.force ("restore own " + l_type_name) Result.force ("view unpublished " + l_type_name) - Result.force ("view revisions own " + l_type_name) Result.force ("export " + l_type_name) end end Result.force ("view trash") + Result.force ("view own trash") end end @@ -195,6 +195,7 @@ feature -- Access: router create l_nodes_handler.make (a_api, a_node_api) create l_uri_mapping.make_trailing_slash_ignored ("/nodes", l_nodes_handler) a_router.map (l_uri_mapping, a_router.methods_get) + a_router.handle ("/nodes/{type}", l_nodes_handler, a_router.methods_get) -- Trash create l_trash_handler.make (a_api, a_node_api) diff --git a/modules/node/content/cms_node.e b/modules/node/content/cms_node.e index fcb2ca3..4f6754c 100644 --- a/modules/node/content/cms_node.e +++ b/modules/node/content/cms_node.e @@ -92,7 +92,15 @@ feature -- Status reports do Result := status = {CMS_NODE_API}.published ensure - Result implies not is_trashed + Result implies not is_trashed and not is_not_published + end + + is_not_published: BOOLEAN + -- Is Current not published? + do + Result := status = {CMS_NODE_API}.not_published + ensure + Result implies not is_published end is_trashed: BOOLEAN diff --git a/modules/node/handler/cms_node_type_webform_manager.e b/modules/node/handler/cms_node_type_webform_manager.e index fbcd551..fa2d80a 100644 --- a/modules/node/handler/cms_node_type_webform_manager.e +++ b/modules/node/handler/cms_node_type_webform_manager.e @@ -291,9 +291,12 @@ feature -- Output a_response.add_to_primary_tabs (lnk) if a_node.status = {CMS_NODE_API}.trashed then - create lnk.make ("Delete", l_node_api.node_path (a_node) + "/delete") + create lnk.make ("Restore", l_node_api.node_path (a_node) + "/trash") lnk.set_weight (2) a_response.add_to_primary_tabs (lnk) + create lnk.make ("Delete", l_node_api.node_path (a_node) + "/delete") + lnk.set_weight (3) + a_response.add_to_primary_tabs (lnk) elseif a_node.has_id then -- Node in {{CMS_NODE_API}.published} or {CMS_NODE_API}.not_published} status. create lnk.make ("Edit", l_node_api.node_path (a_node) + "/edit") @@ -320,7 +323,17 @@ feature -- Output if is_teaser then a_output.append (" cms-teaser") end - a_output.append ("cms-node node-" + a_node.content_type + "%">") + a_output.append ("cms-node node-" + a_node.content_type) + if a_node.is_published then + a_output.append (" cms-status-published") + elseif a_node.is_trashed then + a_output.append (" cms-status-trashed") + elseif a_node.is_not_published then + a_output.append (" cms-status-unpublished") + else + a_output.append (" cms-status-" + a_node.status.out) + end + a_output.append ("%">") a_output.append ("
    ") if attached a_node.author as l_author then diff --git a/modules/node/handler/node_form_response.e b/modules/node/handler/node_form_response.e index e1c750c..d6e3e1a 100644 --- a/modules/node/handler/node_form_response.e +++ b/modules/node/handler/node_form_response.e @@ -159,7 +159,6 @@ feature {NONE} -- Create a new node end end - delete_node (a_node: CMS_NODE; a_type: CMS_NODE_TYPE [CMS_NODE]; b: STRING_8) local f: like new_edit_form @@ -187,7 +186,7 @@ feature {NONE} -- Create a new node f.append_to_html (wsf_theme, b) end else - -- + b.append ("ERROR: node is not in the trash!") end end @@ -381,7 +380,7 @@ feature -- Form create f.make (a_url, a_name) f.extend_html_text ("
    ") - f.extend_html_text ("Are you sure you want to delete?") + f.extend_html_text ("Are you sure you want to delete? (impossible to undo)") -- TODO check if we need to check for has_permissions!! if @@ -400,46 +399,42 @@ feature -- Form ts.set_formmethod ("GET") f.extend (ts) end - f.extend_html_text ("
    ") - f.extend_html_text ("Do you want to restore the current node?") - if - a_node /= Void and then - a_node.id > 0 - then - create ts.make ("op") - ts.set_default_value ("Restore") - ts.set_formaction ("/node/"+a_node.id.out+"/delete") - ts.set_formmethod ("POST") - fixme ("[ - ts.set_default_value (translation ("Restore")) - ]") - f.extend (ts) - end Result := f end - - new_trash_form (a_node: detachable CMS_NODE; a_url: READABLE_STRING_8; a_name: STRING; a_node_type: CMS_NODE_TYPE [CMS_NODE]): CMS_FORM - -- Create a web form named `a_name' for node `a_node' (if set), using form action url `a_url', and for type of node `a_node_type'. + new_trash_form (a_node: CMS_NODE; a_url: READABLE_STRING_8; a_name: STRING; a_node_type: CMS_NODE_TYPE [CMS_NODE]): CMS_FORM + -- Create a web form named `a_name' for node `a_node', using form action url `a_url', and for type of node `a_node_type'. local f: CMS_FORM ts: WSF_FORM_SUBMIT_INPUT do create f.make (a_url, a_name) + f.set_method_post f.extend_html_text ("
    ") - f.extend_html_text ("Are you sure you want to trash the current node?") - if - a_node /= Void and then - a_node.id > 0 - then + if a_node.is_trashed then + f.extend_html_text ("Are you sure you want to restore the current node?") create ts.make ("op") - ts.set_default_value ("Trash") + ts.set_default_value ("Restore") + ts.set_formaction ("/node/" + a_node.id.out + "/trash") + ts.set_formmethod ("POST") fixme ("[ ts.set_default_value (translation ("Trash")) ]") - f.extend (ts) + else + f.extend_html_text ("Are you sure you want to trash the current node?") + create ts.make ("op") + ts.set_default_value ("Trash") + ts.set_formaction ("/node/" + a_node.id.out + "/trash") + ts.set_formmethod ("POST") + + fixme ("[ + ts.set_default_value (translation ("Trash")) + ]") + end + + f.extend (ts) Result := f end diff --git a/modules/node/handler/node_handler.e b/modules/node/handler/node_handler.e index 52d52eb..1518e80 100644 --- a/modules/node/handler/node_handler.e +++ b/modules/node/handler/node_handler.e @@ -174,18 +174,20 @@ feature -- HTTP Methods l_op.value.same_string ("Delete") then do_delete (req, res) - elseif - attached {WSF_STRING} req.form_parameter ("op") as l_op and then - l_op.value.same_string ("Restore") - then - do_restore (req, res) + else + send_bad_request (req, res) end elseif req.percent_encoded_path_info.ends_with ("/trash") then - if - attached {WSF_STRING} req.form_parameter ("op") as l_op and then - l_op.value.same_string ("Trash") - then - do_trash (req, res) + if attached {WSF_STRING} req.form_parameter ("op") as l_op then + if l_op.is_case_insensitive_equal ("Trash") then + do_trash (req, res) + elseif l_op.is_case_insensitive_equal ("Restore") then + do_restore (req, res) + else + send_bad_request (req, res) + end + else + send_bad_request (req, res) end elseif req.percent_encoded_path_info.starts_with ("/node/add/") then create edit_response.make (req, res, api, node_api) @@ -206,6 +208,14 @@ feature -- HTTP Methods send_not_implemented ("REST API not yet implemented", req, res) end + process_node_creation (req: WSF_REQUEST; res: WSF_RESPONSE; a_user: CMS_USER) + do + to_implement ("REST API") + send_not_implemented ("REST API not yet implemented", req, res) + end + +feature {NONE} -- Trash:Restore + do_trash (req: WSF_REQUEST; res: WSF_RESPONSE) -- Trash a node, soft delete. do @@ -233,14 +243,6 @@ feature -- HTTP Methods end end - process_node_creation (req: WSF_REQUEST; res: WSF_RESPONSE; a_user: CMS_USER) - do - to_implement ("REST API") - send_not_implemented ("REST API not yet implemented", req, res) - end - -feature {NONE} -- Trash:Restore - do_delete (req: WSF_REQUEST; res: WSF_RESPONSE) -- Delete a node from the database. local @@ -283,7 +285,7 @@ feature {NONE} -- Trash:Restore then if node_api.has_permission_for_action_on_node ("restore", l_node, l_user) then node_api.restore_node (l_node) - res.send (create {CMS_REDIRECTION_RESPONSE_MESSAGE}.make (req.absolute_script_url (""))) + res.send (create {CMS_REDIRECTION_RESPONSE_MESSAGE}.make (req.absolute_script_url ("/" + node_api.node_path (l_node)))) else send_access_denied (req, res) -- send_not_authorized ? diff --git a/modules/node/handler/nodes_handler.e b/modules/node/handler/nodes_handler.e index 7d87d1f..f9a5131 100644 --- a/modules/node/handler/nodes_handler.e +++ b/modules/node/handler/nodes_handler.e @@ -11,7 +11,16 @@ inherit WSF_URI_HANDLER rename - new_mapping as new_uri_mapping + new_mapping as new_uri_mapping, + execute as execute_uri + end + + WSF_URI_TEMPLATE_HANDLER + rename + new_mapping as new_uri_template_mapping, + execute as execute_uri_template + select + new_uri_template_mapping end WSF_RESOURCE_HANDLER_HELPER @@ -26,6 +35,16 @@ create feature -- execute + execute_uri (req: WSF_REQUEST; res: WSF_RESPONSE) + do + execute (req, res) + end + + execute_uri_template (req: WSF_REQUEST; res: WSF_RESPONSE) + do + execute (req, res) + end + execute (req: WSF_REQUEST; res: WSF_RESPONSE) -- Execute request handler do @@ -39,30 +58,57 @@ feature -- HTTP Methods local l_response: CMS_RESPONSE s: STRING + l_content_type: detachable CMS_CONTENT_TYPE n: CMS_NODE lnk: CMS_LOCAL_LINK l_page_helper: CMS_PAGINATION_GENERATOR s_pager: STRING l_count: NATURAL_64 + inc: BOOLEAN l_include_trashed: BOOLEAN + lst: detachable ITERABLE [CMS_NODE] do -- At the moment the template are hardcoded, but we can -- get them from the configuration file and load them into -- the setup class. - - l_count := node_api.nodes_count + if attached {WSF_STRING} req.path_parameter ("type") as p_node_type and then attached api.content_type (p_node_type.value) as ct then + l_content_type := api.content_type (p_node_type.value) + end create {GENERIC_VIEW_CMS_RESPONSE} l_response.make (req, res, api) create s.make_empty - if l_count > 1 then - l_response.set_title ("Listing " + l_count.out + " nodes") - else - l_response.set_title ("Listing " + l_count.out + " node") + across + api.content_types as ic + loop + if attached {CMS_NODE_TYPE [CMS_NODE]} ic.item as l_note_type then + create lnk.make (l_note_type.name, "nodes/" + l_note_type.name) + if l_note_type = l_content_type then + lnk.set_is_active (True) + end + l_response.add_to_primary_tabs (lnk) + end end create s_pager.make_empty - create l_page_helper.make ("nodes/?page={page}&size={size}", node_api.nodes_count, 25) -- FIXME: Make this default page size a global CMS settings + if l_content_type /= Void then + l_count := node_api.nodes_of_type_count (l_content_type) + if l_count > 1 then + l_response.set_title ("Listing " + l_count.out + " " + l_content_type.name + " nodes") + else + l_response.set_title ("Listing " + l_count.out + " " + l_content_type.name + " node") + end + create l_page_helper.make ("nodes/" + l_content_type.name + "/?page={page}&size={size}", l_count, 25) -- FIXME: Make this default page size a global CMS settings + else + l_count := node_api.nodes_count + if l_count > 1 then + l_response.set_title ("Listing " + l_count.out + " nodes") + else + l_response.set_title ("Listing " + l_count.out + " node") + end + create l_page_helper.make ("nodes/?page={page}&size={size}", l_count, 25) -- FIXME: Make this default page size a global CMS settings + end + l_page_helper.get_setting_from_request (req) if l_page_helper.has_upper_limit and then l_page_helper.pages_count > 1 then l_page_helper.append_to_html (l_response, s_pager) @@ -71,7 +117,12 @@ feature -- HTTP Methods end end - if attached node_api.recent_nodes (create {CMS_DATA_QUERY_PARAMETERS}.make (l_page_helper.current_page_offset, l_page_helper.page_size)) as lst then + if l_content_type /= Void then + lst := node_api.recent_nodes_of_type (l_content_type, create {CMS_DATA_QUERY_PARAMETERS}.make (l_page_helper.current_page_offset, l_page_helper.page_size)) + else + lst := node_api.recent_nodes (create {CMS_DATA_QUERY_PARAMETERS}.make (l_page_helper.current_page_offset, l_page_helper.page_size)) + end + if lst /= Void then if attached {WSF_STRING} req.query_parameter ("include_trash") as v and then v.is_case_insensitive_equal ("yes") then l_include_trashed := l_response.has_permissions (<<"view trash", "view any trash">>) end @@ -80,7 +131,14 @@ feature -- HTTP Methods lst as ic loop n := ic.item - if not n.is_trashed or else l_include_trashed then + inc := True + if not n.is_published then + inc := view_unpublished_node_permitted (n) + end + if inc and n.is_trashed then + inc := l_include_trashed + end + if inc then lnk := node_api.node_link (n) s.append ("
  • + local + tpl: detachable TEMPLATE_TEXT + l_table_inspector: detachable STRING_TABLE_OF_STRING_INSPECTOR + do + template_context.disable_verbose + debug ("cms") + template_context.enable_verbose + end + + create tpl.make_from_text (source) + + across + values as ic + loop + tpl.add_value (ic.item, ic.key) + end + + create l_table_inspector.register (({detachable STRING_TABLE [STRING_8]}).name) + create l_table_inspector.register (({detachable STRING_TABLE [STRING_32]}).name) + create l_table_inspector.register (({detachable STRING_TABLE [READABLE_STRING_8]}).name) + create l_table_inspector.register (({detachable STRING_TABLE [READABLE_STRING_32]}).name) + tpl.get_structure + tpl.get_output + l_table_inspector.unregister +-- l_table32_inspector.unregister + + if attached tpl.output as l_output then + Result := l_output + else + Result := source + end + end + +note + copyright: "2011-2017, 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/cms_api.e b/src/service/cms_api.e index fb4fefa..3b135d1 100644 --- a/src/service/cms_api.e +++ b/src/service/cms_api.e @@ -44,6 +44,9 @@ feature {NONE} -- Initialize l_enabled_modules: CMS_MODULE_COLLECTION l_uninstalled_mods: detachable ARRAYED_LIST [CMS_MODULE] do + -- Initialize site_url + initialize_site_url + -- Initialize formats. initialize_formats -- Initialize contents. @@ -101,6 +104,35 @@ feature {NONE} -- Initialize setup_hooks end + initialize_site_url + -- Initialize site and base url. + local + l_url: detachable STRING_8 + i,j: INTEGER + do + --| WARNING: do not use `absolute_url' and `url', since it relies on site_url and base_url. + if attached setup.site_url as l_site_url and then not l_site_url.is_empty then + create l_url.make_from_string (l_site_url) + else + l_url := request.absolute_script_url ("/") + end + check is_not_empty: not l_url.is_empty end + if l_url [l_url.count] /= '/' then + l_url.append_character ('/') + end + site_url := l_url + i := l_url.substring_index ("://", 1) + if i > 0 then + j := l_url.index_of ('/', i + 3) + if j > 0 then + base_url := l_url.substring (j, l_url.count) + end + end + ensure + site_url_set: site_url /= Void + site_url_ends_with_slash: site_url.ends_with_general ("/") + end + initialize_content_types -- Initialize content types. do @@ -199,6 +231,16 @@ feature -- Access storage: CMS_STORAGE -- Default persistence storage. +feature -- Access: url + + site_url: IMMUTABLE_STRING_8 + -- Site url + + base_url: detachable IMMUTABLE_STRING_8 + -- Base url if any. + --| Usually it is Void, but it could be + --| /project/demo/ + feature -- Settings is_debug: BOOLEAN @@ -400,6 +442,8 @@ feature -- Emails setup.mailer.safe_process_email (e) if setup.mailer.has_error then error_handler.add_custom_error (0, "Mailer error", "Error occurred while processing email.") + else + e.set_is_sent (True) end end @@ -442,7 +486,13 @@ feature -- Permissions system -- Anonymous or user `user' has permission for `a_permission'? --| `a_permission' could be for instance "create page". do - Result := user_api.user_has_permission (user, a_permission) + Result := user_has_permission (user, a_permission) + end + + has_permissions (a_permission_list: ITERABLE [READABLE_STRING_GENERAL]): BOOLEAN + -- Anonymous or user `user' has any of the permissions `a_permission_list`? + do + Result := user_has_permissions (user, a_permission_list) end user_has_permission (a_user: detachable CMS_USER; a_permission: detachable READABLE_STRING_GENERAL): BOOLEAN @@ -452,6 +502,18 @@ feature -- Permissions system Result := user_api.user_has_permission (a_user, a_permission) end + user_has_permissions (a_user: detachable CMS_USER; a_permission_list: ITERABLE [READABLE_STRING_GENERAL]): BOOLEAN + -- Does `a_user' has any of the permissions `a_permission_list' ? + do + across + a_permission_list as ic + until + Result + loop + Result := user_has_permission (a_user, ic.item) + end + end + feature -- Query: module is_module_installed (a_module: CMS_MODULE): BOOLEAN @@ -905,6 +967,24 @@ feature -- Access: active user user_api.update_user (a_user) end +feature -- Site builtin variables + + builtin_variables: STRING_TABLE [detachable ANY] + -- Builtin variables , value indexed by name. + do + create Result.make (7) + + Result["site_url"] := site_url + Result["site_email"] := setup.site_email + Result["site_name"] := setup.site_name + if attached user as l_user then + Result["active_user"] := l_user + Result["user"] := l_user.name + Result["user_id"] := l_user.id + Result["user_profile_name"] := user_api.user_display_name (l_user) + end + end + feature -- Request utilities execution_variable (a_name: READABLE_STRING_GENERAL): detachable ANY diff --git a/src/service/cms_api_import_imp.e b/src/service/cms_api_import_imp.e index f3fec86..b27a8ef 100644 --- a/src/service/cms_api_import_imp.e +++ b/src/service/cms_api_import_imp.e @@ -160,7 +160,7 @@ feature -- Import import_json_user (j_user: JSON_OBJECT; a_import_ctx: CMS_IMPORT_CONTEXT) local - l_user_by_name, l_user_by_email: detachable CMS_USER + l_user_by_name, l_user_by_email, l_user: detachable CMS_USER do if attached json_to_user (j_user) as u then l_user_by_name := user_api.user_by_name (u.name) @@ -170,6 +170,28 @@ feature -- Import if l_user_by_name /= Void or l_user_by_email /= Void then a_import_ctx.log ("Skip user %"" + u.name + "%": already exists!") -- Already exists! + if l_user_by_email /= Void then + l_user := l_user_by_email + if + l_user_by_name /= Void and then + l_user_by_name.same_as (l_user) + then + -- Two different accounts exists! + a_import_ctx.log ("Two different accounts already exists for username %"" + u.name + "%" !") + end + else + l_user := l_user_by_name + end + -- Check if new information are now available. + if + l_user /= Void and then + l_user.profile_name = Void and then + attached u.profile_name as l_new_prof_name and then + not l_new_prof_name.is_whitespace + then + l_user.set_profile_name (l_new_prof_name) + user_api.update_user (l_user) + end else user_api.new_user (u) a_import_ctx.log ("New user %"" + u.name + "%" -> " + u.id.out + " .") diff --git a/src/service/cms_email.e b/src/service/cms_email.e index a61d9d8..fb33b49 100644 --- a/src/service/cms_email.e +++ b/src/service/cms_email.e @@ -12,7 +12,19 @@ inherit create make +feature -- Status report + + is_sent: BOOLEAN + -- Current Email is sent. + +feature -- Element change + + set_is_sent (b: BOOLEAN) + do + is_sent := b + end + note - copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Eiffel Software and others" + copyright: "2011-2017, 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 130aa24..f2fc91b 100644 --- a/src/service/response/cms_response.e +++ b/src/service/response/cms_response.e @@ -24,46 +24,18 @@ feature {NONE} -- Initialization response := res create header.make create values.make (3) + site_url := a_api.site_url + base_url := a_api.base_url initialize end initialize do - initialize_site_url get_theme create menu_system.make initialize_block_region_settings end - initialize_site_url - -- Initialize site and base url. - local - l_url: detachable STRING_8 - i,j: INTEGER - do - --| WARNING: do not use `absolute_url' and `url', since it relies on site_url and base_url. - if attached setup.site_url as l_site_url and then not l_site_url.is_empty then - create l_url.make_from_string (l_site_url) - else - l_url := request.absolute_script_url ("/") - end - check is_not_empty: not l_url.is_empty end - if l_url [l_url.count] /= '/' then - l_url.append_character ('/') - end - site_url := l_url - i := l_url.substring_index ("://", 1) - if i > 0 then - j := l_url.index_of ('/', i + 3) - if j > 0 then - base_url := l_url.substring (j, l_url.count) - end - end - ensure - site_url_set: site_url /= Void - site_url_ends_with_slash: site_url.ends_with_general ("/") - end - feature -- Access request: WSF_REQUEST @@ -236,13 +208,7 @@ feature -- Permission user_has_permissions (a_user: detachable CMS_USER; a_permission_list: ITERABLE [READABLE_STRING_GENERAL]): BOOLEAN -- Does `a_user' has any of the permissions `a_permission_list' ? do - across - a_permission_list as ic - until - Result - loop - Result := user_has_permission (a_user, ic.item) - end + Result := api.user_has_permissions (a_user, a_permission_list) end feature -- Head customization @@ -1088,6 +1054,17 @@ feature -- Cache managment end end +feature -- Response builtin variables + + builtin_variables: STRING_TABLE [detachable ANY] + -- builtin variables value indexed by name. + do + Result := api.builtin_variables + Result ["site_url"] := site_url + Result ["host"] := site_url -- FIXME: check and remove if unused. + Result ["is_https"] := request.is_https + end + feature -- Generation prepare (page: CMS_HTML_PAGE) @@ -1233,14 +1210,17 @@ feature -- Generation end end + -- Fill with CMS builtin variables. + across + builtin_variables as ic + loop + page.register_variable (ic.item, ic.key) + end + -- Variables page.register_variable (absolute_url ("", Void), "site_url") page.register_variable (absolute_url ("", Void), "host") -- Same as `site_url'. page.register_variable (request.is_https, "is_https") - if attached user as l_user then - page.register_variable (l_user.name, "user") - page.register_variable (user_profile_name (l_user), "user_profile_name") - end if attached title as l_title then page.register_variable (l_title, "site_title") else @@ -1358,15 +1338,9 @@ feature -- Helpers: cms link feature -- Helpers: html links - user_profile_name (u: CMS_USER): READABLE_STRING_32 + user_profile_name, user_display_name (u: CMS_USER): READABLE_STRING_32 do - if attached u.profile_name as pn and then not pn.is_whitespace then - Result := pn - elseif not u.name.is_whitespace then - Result := u.name - else - Result := {STRING_32} "user #" + u.id.out - end + Result := api.user_api.user_display_name (u) end user_html_link (u: CMS_USER): STRING diff --git a/src/service/user/cms_user_api.e b/src/service/user/cms_user_api.e index f402a8f..082b4a5 100644 --- a/src/service/user/cms_user_api.e +++ b/src/service/user/cms_user_api.e @@ -68,6 +68,20 @@ feature -- Validation end end +feature -- Query + + user_display_name (u: CMS_USER): READABLE_STRING_32 + -- Display name for user `u`. + do + if attached u.profile_name as pn and then not pn.is_whitespace then + Result := pn + elseif not u.name.is_whitespace then + Result := u.name + else + Result := {STRING_32} "user #" + u.id.out + end + end + feature -- Access: user user_by_id (a_id: like {CMS_USER}.id): detachable CMS_USER