diff --git a/examples/demo/demo-safe.ecf b/examples/demo/demo-safe.ecf index b8c67db..b00a415 100644 --- a/examples/demo/demo-safe.ecf +++ b/examples/demo/demo-safe.ecf @@ -31,6 +31,7 @@ + diff --git a/examples/demo/site/modules/session_auth/scripts/session_auth_table.sql b/examples/demo/site/modules/session_auth/scripts/session_auth_table.sql new file mode 100644 index 0000000..ada32db --- /dev/null +++ b/examples/demo/site/modules/session_auth/scripts/session_auth_table.sql @@ -0,0 +1,11 @@ + +CREATE TABLE session_auth ( + `uid` INTEGER PRIMARY KEY NOT NULL CHECK(`uid`>=0), + `access_token` TEXT NOT NULL, + `created` DATETIME NOT NULL, + CONSTRAINT `uid` + UNIQUE(`uid`), + CONSTRAINT `access_token` + UNIQUE(`access_token`) + ); + diff --git a/examples/demo/site/modules/session_auth/templates/block_login.tpl b/examples/demo/site/modules/session_auth/templates/block_login.tpl new file mode 100644 index 0000000..a344568 --- /dev/null +++ b/examples/demo/site/modules/session_auth/templates/block_login.tpl @@ -0,0 +1,37 @@ +
+ {unless isset="$user"} +

Login or Register

+
+
+
+
+ + +
+ +
+ + +
+ +
+
+
+
+ +
+ {/unless} + {if isset=$error} +
+
+

+ {$error/} +

+
+
+ {/if} +
diff --git a/examples/demo/site/modules/taxonomy/files/css/taxonomy.css b/examples/demo/site/modules/taxonomy/files/css/taxonomy.css index 41c87fa..dd216d0 100644 --- a/examples/demo/site/modules/taxonomy/files/css/taxonomy.css +++ b/examples/demo/site/modules/taxonomy/files/css/taxonomy.css @@ -19,3 +19,8 @@ ul.taxonomy li:hover { border-bottom: solid 1px #66f; background-color: #ddf; } + +table.taxonomy td { + border: solid 1px #ccc; + padding: 2px; +} diff --git a/examples/demo/site/modules/taxonomy/files/scss/taxonomy.scss b/examples/demo/site/modules/taxonomy/files/scss/taxonomy.scss index f409395..2ceff02 100644 --- a/examples/demo/site/modules/taxonomy/files/scss/taxonomy.scss +++ b/examples/demo/site/modules/taxonomy/files/scss/taxonomy.scss @@ -19,3 +19,9 @@ ul.taxonomy { } } } +table.taxonomy { + td { + border: solid 1px #ccc; + padding: 2px; + } +} diff --git a/examples/demo/site/themes/bootstrap/assets/css/style.css b/examples/demo/site/themes/bootstrap/assets/css/style.css index b16026b..5accf73 100644 --- a/examples/demo/site/themes/bootstrap/assets/css/style.css +++ b/examples/demo/site/themes/bootstrap/assets/css/style.css @@ -91,13 +91,10 @@ ul.horizontal li { padding: 5px 2px 5px 2px; } -ul.taxonomy-entities { - list-style-type: none; - padding: 0; +table.with_border thead td { + font-weight: bold; } -ul.taxonomy-entities li { - padding: 0; - margin-top: 5px; - margin-bottom: 10px; - border-top: dotted 1px #ccc; +table.with_border td { + border: solid 1px #ccc; + padding: 2px 5px 2px 5px; } diff --git a/examples/demo/site/themes/bootstrap/assets/scss/style.scss b/examples/demo/site/themes/bootstrap/assets/scss/style.scss index 1192673..90d2bb9 100644 --- a/examples/demo/site/themes/bootstrap/assets/scss/style.scss +++ b/examples/demo/site/themes/bootstrap/assets/scss/style.scss @@ -96,13 +96,12 @@ ul.horizontal { padding: 5px 2px 5px 2px; } -ul.taxonomy-entities { - list-style-type: none; - padding: 0; - li { - padding: 0; - margin-top: 5px; - margin-bottom: 10px; - border-top: dotted 1px #ccc; +table.with_border { + thead td { + font-weight: bold; + } + td { + border: solid 1px #ccc; + padding: 2px 5px 2px 5px; } } diff --git a/examples/demo/src/demo_cms_execution.e b/examples/demo/src/demo_cms_execution.e index a3286d9..c73948f 100644 --- a/examples/demo/src/demo_cms_execution.e +++ b/examples/demo/src/demo_cms_execution.e @@ -89,6 +89,9 @@ feature -- CMS modules create {GOOGLE_CUSTOM_SEARCH_MODULE} m.make a_setup.register_module (m) + + create {CMS_SESSION_AUTH_MODULE} m.make + a_setup.register_module (m) end end diff --git a/library/persistence/implementation/store/cms_storage_store_sql.e b/library/persistence/implementation/store/cms_storage_store_sql.e index 0684964..aec4408 100644 --- a/library/persistence/implementation/store/cms_storage_store_sql.e +++ b/library/persistence/implementation/store/cms_storage_store_sql.e @@ -152,6 +152,7 @@ feature -- Query -- Retrieved value at `a_index' position in `item'. local l_item: like sql_item + i64: INTEGER_64 do l_item := sql_item (a_index) if attached {INTEGER_32} l_item as i then @@ -159,7 +160,18 @@ feature -- Query elseif attached {INTEGER_32_REF} l_item as l_value then Result := l_value.item else - check is_integer_32: False end + if attached {INTEGER_64} l_item as i then + i64 := i + elseif attached {INTEGER_64_REF} l_item as l_value then + i64 := l_value.item + else + check is_integer_32: False end + end + if i64 <= {INTEGER_32}.max_value then + Result := i64.to_integer_32 + else + check is_integer_32: False end + end end end diff --git a/modules/admin/handler/cms_admin_cache_handler.e b/modules/admin/handler/cms_admin_cache_handler.e index 13dd922..5f53515 100644 --- a/modules/admin/handler/cms_admin_cache_handler.e +++ b/modules/admin/handler/cms_admin_cache_handler.e @@ -44,7 +44,7 @@ feature -- Execution 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 (create {CMS_TO_WSF_THEME}.make (l_response, l_response.theme), s) + f.append_to_html (l_response.wsf_theme, s) l_response.set_main_content (s) l_response.execute end @@ -70,7 +70,7 @@ feature -- Execution end end create s.make_empty - f.append_to_html (create {CMS_TO_WSF_THEME}.make (l_response, l_response.theme), s) + 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 8f21b30..8fd510e 100644 --- a/modules/admin/handler/cms_admin_export_handler.e +++ b/modules/admin/handler/cms_admin_export_handler.e @@ -44,7 +44,7 @@ feature -- Execution 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 (create {CMS_TO_WSF_THEME}.make (l_response, l_response.theme), s) + f.append_to_html (l_response.wsf_theme, s) l_response.set_main_content (s) l_response.execute end @@ -85,7 +85,7 @@ feature -- Execution end end create s.make_empty - f.append_to_html (create {CMS_TO_WSF_THEME}.make (l_response, l_response.theme), s) + 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_modules_handler.e b/modules/admin/handler/cms_admin_modules_handler.e index 53ffb20..07e9d38 100644 --- a/modules/admin/handler/cms_admin_modules_handler.e +++ b/modules/admin/handler/cms_admin_modules_handler.e @@ -88,7 +88,7 @@ feature -- Execution create {GENERIC_VIEW_CMS_RESPONSE} r.make (req, res, api) f := modules_collection_web_form (r) create s.make_empty - f.append_to_html (create {CMS_TO_WSF_THEME}.make (r, r.theme), s) + f.append_to_html (r.wsf_theme, s) r.set_page_title ("Modules") r.set_main_content (s) r.execute @@ -133,7 +133,7 @@ feature -- Execution then r.add_error_message ("Error occurred.") create s.make_empty - f.append_to_html (create {CMS_TO_WSF_THEME}.make (r, r.theme), s) + f.append_to_html (r.wsf_theme, s) r.set_page_title ("Modules") r.set_main_content (s) else diff --git a/modules/admin/handler/cms_admin_response.e b/modules/admin/handler/cms_admin_response.e index 3274fb5..fb7dea4 100644 --- a/modules/admin/handler/cms_admin_response.e +++ b/modules/admin/handler/cms_admin_response.e @@ -8,30 +8,10 @@ class inherit CMS_RESPONSE - redefine - make, - initialize - end create make -feature {NONE} -- Initialization - - make (req: WSF_REQUEST; res: WSF_RESPONSE; a_api: like api) - do - create {WSF_NULL_THEME} wsf_theme.make - Precursor (req, res, a_api) - end - - initialize - do - Precursor - create {CMS_TO_WSF_THEME} wsf_theme.make (Current, theme) - end - - wsf_theme: WSF_THEME - feature -- Process process diff --git a/modules/admin/handler/role/cms_role_form_response.e b/modules/admin/handler/role/cms_role_form_response.e index b681549..7cdec58 100644 --- a/modules/admin/handler/role/cms_role_form_response.e +++ b/modules/admin/handler/role/cms_role_form_response.e @@ -8,32 +8,12 @@ class inherit CMS_RESPONSE - redefine - make, - initialize - end CMS_SHARED_SORTING_UTILITIES create make -feature {NONE} -- Initialization - - make (req: WSF_REQUEST; res: WSF_RESPONSE; a_api: like api) - do - create {WSF_NULL_THEME} wsf_theme.make - Precursor (req, res, a_api) - end - - initialize - do - Precursor - create {CMS_TO_WSF_THEME} wsf_theme.make (Current, theme) - end - - wsf_theme: WSF_THEME - feature -- Query role_id_path_parameter (req: WSF_REQUEST): INTEGER_64 diff --git a/modules/admin/handler/role/cms_role_view_response.e b/modules/admin/handler/role/cms_role_view_response.e index 53b9038..a6887ae 100644 --- a/modules/admin/handler/role/cms_role_view_response.e +++ b/modules/admin/handler/role/cms_role_view_response.e @@ -8,31 +8,10 @@ class inherit CMS_RESPONSE - redefine - make, - initialize - end create make - -feature {NONE} -- Initialization - - make (req: WSF_REQUEST; res: WSF_RESPONSE; a_api: like api;) - do - create {WSF_NULL_THEME} wsf_theme.make - Precursor (req, res, a_api) - end - - initialize - do - Precursor - create {CMS_TO_WSF_THEME} wsf_theme.make (Current, theme) - end - - wsf_theme: WSF_THEME - feature -- Query role_id_path_parameter (req: WSF_REQUEST): INTEGER_64 diff --git a/modules/admin/handler/user/cms_user_form_response.e b/modules/admin/handler/user/cms_user_form_response.e index 5209706..40847f0 100644 --- a/modules/admin/handler/user/cms_user_form_response.e +++ b/modules/admin/handler/user/cms_user_form_response.e @@ -7,32 +7,11 @@ class CMS_USER_FORM_RESPONSE inherit - CMS_RESPONSE - redefine - make, - initialize - end create make -feature {NONE} -- Initialization - - make (req: WSF_REQUEST; res: WSF_RESPONSE; a_api: like api) - do - create {WSF_NULL_THEME} wsf_theme.make - Precursor (req, res, a_api) - end - - initialize - do - Precursor - create {CMS_TO_WSF_THEME} wsf_theme.make (Current, theme) - end - - wsf_theme: WSF_THEME - feature -- Query user_id_path_parameter (req: WSF_REQUEST): INTEGER_64 diff --git a/modules/admin/handler/user/cms_user_view_response.e b/modules/admin/handler/user/cms_user_view_response.e index 0568fcc..4cf3716 100644 --- a/modules/admin/handler/user/cms_user_view_response.e +++ b/modules/admin/handler/user/cms_user_view_response.e @@ -8,31 +8,10 @@ class inherit CMS_RESPONSE - redefine - make, - initialize - end create make - -feature {NONE} -- Initialization - - make (req: WSF_REQUEST; res: WSF_RESPONSE; a_api: like api;) - do - create {WSF_NULL_THEME} wsf_theme.make - Precursor (req, res, a_api) - end - - initialize - do - Precursor - create {CMS_TO_WSF_THEME} wsf_theme.make (Current, theme) - end - - wsf_theme: WSF_THEME - feature -- Query user_id_path_parameter (req: WSF_REQUEST): INTEGER_64 diff --git a/modules/auth/cms_authentication_module.e b/modules/auth/cms_authentication_module.e index e0a7c55..3787b75 100644 --- a/modules/auth/cms_authentication_module.e +++ b/modules/auth/cms_authentication_module.e @@ -9,10 +9,10 @@ class inherit CMS_MODULE redefine - setup_hooks + setup_hooks, + permissions end - CMS_HOOK_AUTO_REGISTER CMS_HOOK_VALUE_TABLE_ALTER @@ -52,6 +52,13 @@ feature -- Access name: STRING = "auth" + permissions: LIST [READABLE_STRING_8] + -- List of permission ids, used by this module, and declared. + do + Result := Precursor + Result.force ("account register") + end + feature -- Access: docs root_dir: PATH @@ -228,6 +235,7 @@ feature -- Handler end else create {FORBIDDEN_ERROR_CMS_RESPONSE} r.make (req, res, api) + r.set_main_content ("You can also contact the webmaster to ask for an account.") end r.execute diff --git a/modules/node/content/cms_node.e b/modules/node/content/cms_node.e index f7a755c..0368c0e 100644 --- a/modules/node/content/cms_node.e +++ b/modules/node/content/cms_node.e @@ -11,8 +11,10 @@ deferred class inherit CMS_CONTENT + rename + has_identifier as has_id redefine - debug_output + debug_output, has_id end REFACTORING_HELPER @@ -63,6 +65,12 @@ feature -- Conversion feature -- Access + identifier: detachable IMMUTABLE_STRING_32 + -- Optional identifier. + do + create Result.make_from_string_general (id.out) + end + id: INTEGER_64 assign set_id -- Unique id. --| Should we use NATURAL_64 instead? diff --git a/modules/node/handler/cms_node_type_webform_manager.e b/modules/node/handler/cms_node_type_webform_manager.e index 453a272..10e866d 100644 --- a/modules/node/handler/cms_node_type_webform_manager.e +++ b/modules/node/handler/cms_node_type_webform_manager.e @@ -92,228 +92,15 @@ feature -- Forms ... f.extend (fset) - -- Path alias + -- Path alias populate_form_with_taxonomy (response, f, a_node) populate_form_with_path_alias (response, f, a_node) end - populate_form_with_taxonomy (response: NODE_RESPONSE; f: CMS_FORM; a_node: detachable CMS_NODE) - local - ti: detachable WSF_FORM_TEXT_INPUT - w_set: WSF_FORM_FIELD_SET - w_select: WSF_FORM_SELECT - w_opt: WSF_FORM_SELECT_OPTION - w_cb: WSF_FORM_CHECKBOX_INPUT - w_voc_set: WSF_FORM_FIELD_SET - s: STRING_32 - voc: CMS_VOCABULARY - t: detachable CMS_TERM - l_terms: detachable CMS_TERM_COLLECTION - l_has_edit_permission: BOOLEAN + populate_form_with_taxonomy (response: CMS_RESPONSE; f: CMS_FORM; a_content: detachable CMS_CONTENT) do - if - attached {CMS_TAXONOMY_API} response.api.module_api ({CMS_TAXONOMY_MODULE}) as l_taxonomy_api and then - attached l_taxonomy_api.vocabularies_for_type (content_type.name) as l_vocs and then not l_vocs.is_empty - then - - l_has_edit_permission := response.has_permissions (<<"update any taxonomy", "update " + content_type.name + " taxonomy">>) - - -- Handle Taxonomy fields, if any associated with `content_type'. - create w_set.make - w_set.add_css_class ("taxonomy") - l_vocs.sort - across - l_vocs as vocs_ic - loop - voc := vocs_ic.item - l_terms := Void - if a_node /= Void and then a_node.has_id then - l_terms := l_taxonomy_api.terms_of_entity (a_node.content_type, a_node.id.out, voc) - if l_terms /= Void then - l_terms.sort - end - end - create w_voc_set.make - w_set.extend (w_voc_set) - - if voc.is_tags then - w_voc_set.set_legend (response.translation (voc.name, Void)) - - create ti.make ({STRING_32} "taxonomy_terms[" + voc.name + "]") - w_voc_set.extend (ti) - if voc.is_term_required then - ti.enable_required - end - if attached voc.description as l_desc then - ti.set_description (response.html_encoded (response.translation (l_desc, Void))) - else - ti.set_description (response.html_encoded (response.translation (voc.name, Void))) - end - ti.set_size (70) - if l_terms /= Void then - create s.make_empty - across - l_terms as ic - loop - t := ic.item - if not s.is_empty then - s.append_character (',') - s.append_character (' ') - end - if ic.item.text.has (' ') then - s.append_character ('"') - s.append (t.text) - s.append_character ('"') - else - s.append (t.text) - end - end - ti.set_text_value (s) - end - if not l_has_edit_permission then - ti.set_is_readonly (True) - end - else - l_taxonomy_api.fill_vocabularies_with_terms (voc) - if not voc.terms.is_empty then - if voc.multiple_terms_allowed then - if attached voc.description as l_desc then - w_voc_set.set_legend (response.html_encoded (l_desc)) - else - w_voc_set.set_legend (response.html_encoded (voc.name)) - end - across - voc as voc_terms_ic - loop - t := voc_terms_ic.item - create w_cb.make_with_value ({STRING_32} "taxonomy_terms[" + voc.name + "]", t.text) - w_voc_set.extend (w_cb) - if l_terms /= Void and then across l_terms as ic some ic.item.text.same_string (t.text) end then - w_cb.set_checked (True) - end - if not l_has_edit_permission then - w_cb.set_is_readonly (True) - end - end - else - create w_select.make ({STRING_32} "taxonomy_terms[" + voc.name + "]") - w_voc_set.extend (w_select) - - if attached voc.description as l_desc then - w_select.set_description (response.html_encoded (l_desc)) - else - w_select.set_description (response.html_encoded (voc.name)) - end - w_voc_set.set_legend (response.html_encoded (voc.name)) - - across - voc as voc_terms_ic - loop - t := voc_terms_ic.item - create w_opt.make (response.html_encoded (t.text), response.html_encoded (t.text)) - w_select.add_option (w_opt) - - if l_terms /= Void and then across l_terms as ic some ic.item.text.same_string (t.text) end then - w_opt.set_is_selected (True) - end - end - if not l_has_edit_permission then - w_select.set_is_readonly (True) - end - end - end - end - end - - f.submit_actions.extend (agent taxonomy_submit_action (response, l_taxonomy_api, l_vocs, a_node, ?)) - - if - attached f.fields_by_name ("title") as l_title_fields and then - attached l_title_fields.first as l_title_field - then - f.insert_after (w_set, l_title_field) - else - f.extend (w_set) - end - end - end - - taxonomy_submit_action (a_response: CMS_RESPONSE; a_taxonomy_api: CMS_TAXONOMY_API; a_vocs: CMS_VOCABULARY_COLLECTION; a_node: detachable CMS_NODE fd: WSF_FORM_DATA) - require - vocs_not_empty: not a_vocs.is_empty - local - l_voc_name: READABLE_STRING_32 - l_terms_to_remove: ARRAYED_LIST [CMS_TERM] - l_new_terms: LIST [READABLE_STRING_32] - l_text: READABLE_STRING_GENERAL - l_found: BOOLEAN - t: detachable CMS_TERM - do - if - a_node /= Void and then a_node.has_id and then - attached fd.table_item ("taxonomy_terms") as fd_terms - then - across - fd_terms.values as ic - loop - if attached {WSF_STRING} ic.item as l_string then - l_voc_name := ic.key - l_new_terms := a_taxonomy_api.splitted_string (l_string.value, ',') - if attached a_vocs.item_by_name (l_voc_name) as voc then - if a_response.has_permissions (<<{STRING_32} "update any taxonomy", {STRING_32} "update " + content_type.name + " taxonomy">>) then - create l_terms_to_remove.make (0) - if attached a_taxonomy_api.terms_of_entity (content_type.name, a_node.id.out, voc) as l_existing_terms then - across - l_existing_terms as t_ic - loop - l_text := t_ic.item.text - from - l_found := False - l_new_terms.start - until - l_new_terms.after - loop - if l_new_terms.item.same_string_general (l_text) then - -- Already associated with term `t_ic.text'. - l_found := True - l_new_terms.remove - else - l_new_terms.forth - end - end - if not l_found then - -- Remove term - l_terms_to_remove.force (t_ic.item) - end - end - across - l_terms_to_remove as t_ic - loop - a_taxonomy_api.unassociate_term_from_entity (t_ic.item, content_type.name, a_node.id.out) - end - end - across - l_new_terms as t_ic - loop - t := a_taxonomy_api.term_by_text (t_ic.item, voc) - if - t = Void and voc.is_tags - then - -- Create new term! - create t.make (t_ic.item) - a_taxonomy_api.save_term (t, voc) - if a_taxonomy_api.has_error then - t := Void - end - end - if t /= Void then - a_taxonomy_api.associate_term_with_entity (t, content_type.name, a_node.id.out) - end - end - end - end - end - end + if attached {CMS_TAXONOMY_API} response.api.module_api ({CMS_TAXONOMY_MODULE}) as l_taxonomy_api then + l_taxonomy_api.populate_edit_form (response, f, content_type.name, a_content) end end @@ -547,30 +334,9 @@ feature -- Output if a_response /= Void and then - attached {CMS_TAXONOMY_API} cms_api.module_api ({CMS_TAXONOMY_MODULE}) as l_taxonomy_api and then - attached l_taxonomy_api.vocabularies_for_type (content_type.name) as vocs and then not vocs.is_empty + attached {CMS_TAXONOMY_API} cms_api.module_api ({CMS_TAXONOMY_MODULE}) as l_taxonomy_api then - vocs.sort - across - vocs as ic - loop - if - attached l_taxonomy_api.terms_of_entity (content_type.name, a_node.id.out, ic.item) as l_terms and then - not l_terms.is_empty - then - a_output.append ("
    ") - a_output.append (l_node_api.html_encoded (ic.item.name)) - a_output.append (": ") - across - l_terms as t_ic - loop - a_output.append ("
  • ") - a_response.append_link_to_html (t_ic.item.text, "taxonomy/term/" + t_ic.item.id.out, Void, a_output) - a_output.append ("
  • ") - end - a_output.append ("
%N") - end - end + l_taxonomy_api.append_taxonomy_to_xhtml (a_node, a_response, a_output) end -- We don't show the summary on the detail page, since its just a short view of the full content. Otherwise we would write the same thing twice. diff --git a/modules/node/handler/node_form_response.e b/modules/node/handler/node_form_response.e index f8da987..604bcb5 100644 --- a/modules/node/handler/node_form_response.e +++ b/modules/node/handler/node_form_response.e @@ -7,30 +7,10 @@ class inherit NODE_RESPONSE - redefine - make, - initialize - end create make -feature {NONE} -- Initialization - - make (req: WSF_REQUEST; res: WSF_RESPONSE; a_api: like api; a_node_api: like node_api) - do - create {WSF_NULL_THEME} wsf_theme.make - Precursor (req, res, a_api, a_node_api) - end - - initialize - do - Precursor - create {CMS_TO_WSF_THEME} wsf_theme.make (Current, theme) - end - - wsf_theme: WSF_THEME - feature -- Execution process diff --git a/modules/node/handler/node_view_response.e b/modules/node/handler/node_view_response.e index 3a39f96..64b800b 100644 --- a/modules/node/handler/node_view_response.e +++ b/modules/node/handler/node_view_response.e @@ -8,30 +8,10 @@ class inherit NODE_RESPONSE - redefine - make, - initialize - end create make -feature {NONE} -- Initialization - - make (req: WSF_REQUEST; res: WSF_RESPONSE; a_api: like api; a_node_api: like node_api) - do - create {WSF_NULL_THEME} wsf_theme.make - Precursor (req, res, a_api, a_node_api) - end - - initialize - do - Precursor - create {CMS_TO_WSF_THEME} wsf_theme.make (Current, theme) - end - - wsf_theme: WSF_THEME - feature -- Access node: detachable CMS_NODE diff --git a/modules/oauth20/cms_oauth_20_module.e b/modules/oauth20/cms_oauth_20_module.e index 1e96534..7cf6bab 100644 --- a/modules/oauth20/cms_oauth_20_module.e +++ b/modules/oauth20/cms_oauth_20_module.e @@ -99,7 +99,7 @@ feature {CMS_API} -- Module management l_sql_storage.sql_execute_file_script (api.module_resource_location (Current, (create {PATH}.make_from_string ("scripts")).extended ("oauth2_consumers.sql")), Void) if l_sql_storage.has_error then - api.logger.put_error ("Could not initialize database for blog module", generating_type) + api.logger.put_error ("Could not initialize database for oauth_20 module", generating_type) end -- TODO workaround. l_sql_storage.sql_execute_file_script (api.module_resource_location (Current, (create {PATH}.make_from_string ("scripts")).extended ("oauth2_consumers_initialize.sql")), Void) @@ -108,7 +108,7 @@ feature {CMS_API} -- Module management -- TODO workaround, until we have an admin module l_sql_storage.sql_query ("SELECT name FROM oauth2_consumers;", Void) if l_sql_storage.has_error then - api.logger.put_error ("Could not initialize database for differnent consumerns", generating_type) + api.logger.put_error ("Could not initialize database for differnent consumers", generating_type) else from l_sql_storage.sql_start @@ -227,7 +227,10 @@ feature -- Hooks until lnk2 /= Void loop - if ic.item.location.same_string ("account/roc-logout") then + if + ic.item.location.same_string ("account/roc-logout") or else + ic.item.location.same_string ("basic_auth_logoff") + then lnk2 := ic.item end end diff --git a/modules/openid/cms_openid_module.e b/modules/openid/cms_openid_module.e index e52184e..cc84735 100644 --- a/modules/openid/cms_openid_module.e +++ b/modules/openid/cms_openid_module.e @@ -203,7 +203,10 @@ feature -- Hooks until lnk2 /= Void loop - if ic.item.location.same_string ("account/roc-logout") then + if + ic.item.location.same_string ("account/roc-logout") or else + ic.item.location.same_string ("basic_auth_logoff") + then lnk2 := ic.item end end diff --git a/modules/openid/persitence/cms_openid_storage_sql.e b/modules/openid/persitence/cms_openid_storage_sql.e index f44c975..bb2f0d7 100644 --- a/modules/openid/persitence/cms_openid_storage_sql.e +++ b/modules/openid/persitence/cms_openid_storage_sql.e @@ -197,7 +197,7 @@ feature {NONE} -- User OpenID Sql_insert_openid: STRING = "INSERT INTO openid_items (uid, identity, created) VALUES (:uid, :identity, :utc_date);" - Sql_openid_consumers: STRING = "SELECT name FROM openid_consumers"; + Sql_openid_consumers: STRING = "SELECT name FROM openid_consumers;" feature {NONE} -- Consumer diff --git a/modules/recent_changes/cms_recent_changes_module.e b/modules/recent_changes/cms_recent_changes_module.e index d685d4b..9715dff 100644 --- a/modules/recent_changes/cms_recent_changes_module.e +++ b/modules/recent_changes/cms_recent_changes_module.e @@ -284,7 +284,7 @@ feature -- Handler create l_submit.make_with_text ("op", "Filter") l_form.extend (l_submit) l_form.extend_html_text ("
") - l_form.append_to_html (create {CMS_TO_WSF_THEME}.make (r, r.theme), l_content) + l_form.append_to_html (r.wsf_theme, l_content) end l_changes.reverse_sort diff --git a/modules/session_auth/cms_session_api.e b/modules/session_auth/cms_session_api.e new file mode 100644 index 0000000..fd62dd2 --- /dev/null +++ b/modules/session_auth/cms_session_api.e @@ -0,0 +1,63 @@ +note + description: "API to manage CMS User session authentication" + date: "$Date$" + revision: "$Revision$" + +class + CMS_SESSION_API + + +inherit + CMS_MODULE_API + + REFACTORING_HELPER + +create {CMS_SESSION_AUTH_MODULE} + make_with_storage + +feature {NONE} -- Initialization + + make_with_storage (a_api: CMS_API; a_session_auth_storage: CMS_SESSION_AUTH_STORAGE_I) + -- Create an object with api `a_api' and storage `a_session_auth_storage'. + do + session_auth_storage := a_session_auth_storage + make (a_api) + ensure + session_auth_storage_set: session_auth_storage = a_session_auth_storage + end + +feature {CMS_MODULE} -- Access: User session storage. + + session_auth_storage: CMS_SESSION_AUTH_STORAGE_I + -- storage interface. + +feature -- Access + + user_by_session_token (a_token: READABLE_STRING_32): detachable CMS_USER + -- Retrieve user by token `a_token', if any. + do + Result := session_auth_storage.user_by_session_token (a_token) + end + + has_user_token (a_user: CMS_USER): BOOLEAN + -- Has the user `a_user' and associated session token? + do + Result := session_auth_storage.has_user_token (a_user) + end + +feature -- Change User session + + new_user_session_auth (a_token: READABLE_STRING_GENERAL; a_user: CMS_USER;) + -- New user session for user `a_user' with token `a_token'. + do + session_auth_storage.new_user_session_auth (a_token, a_user) + end + + + update_user_session_auth (a_token: READABLE_STRING_GENERAL; a_user: CMS_USER ) + -- Update user session for user `a_user' with token `a_token'. + do + session_auth_storage.update_user_session_auth (a_token, a_user) + end + +end diff --git a/modules/session_auth/cms_session_auth-safe.ecf b/modules/session_auth/cms_session_auth-safe.ecf new file mode 100644 index 0000000..95f44a5 --- /dev/null +++ b/modules/session_auth/cms_session_auth-safe.ecf @@ -0,0 +1,28 @@ + + + + + + /EIFGENs$ + /CVS$ + /.svn$ + + + + + + + + + + + + + + + + + + diff --git a/modules/session_auth/cms_session_auth_module.e b/modules/session_auth/cms_session_auth_module.e new file mode 100644 index 0000000..cf097d4 --- /dev/null +++ b/modules/session_auth/cms_session_auth_module.e @@ -0,0 +1,349 @@ +note + description: "[ + This module allows the use Session Based Authentication using Cookies to restrict access + by looking up users in the given providers. + ]" + date: "$Date$" + revision: "$Revision$" + +class + CMS_SESSION_AUTH_MODULE + +inherit + CMS_MODULE + rename + module_api as user_session_api + redefine + filters, + setup_hooks, + initialize, + install, + user_session_api + end + + + CMS_HOOK_AUTO_REGISTER + + CMS_HOOK_BLOCK + + CMS_HOOK_MENU_SYSTEM_ALTER + + CMS_HOOK_VALUE_TABLE_ALTER + + SHARED_LOGGER + + CMS_REQUEST_UTIL + +create + make + +feature {NONE} -- Initialization + + make + do + version := "1.0" + description := "Service to manage cookie based authentication" + package := "authentication" + add_dependency ({CMS_AUTHENTICATION_MODULE}) + end + +feature -- Access + + name: STRING = "session_auth" + + + +feature {CMS_API} -- Module Initialization + + initialize (a_api: CMS_API) + -- + local + l_session_auth_api: like user_session_api + l_user_auth_storage: CMS_SESSION_AUTH_STORAGE_I + do + Precursor (a_api) + + -- Storage initialization + if attached a_api.storage.as_sql_storage as l_storage_sql then + create {CMS_SESSION_AUTH_STORAGE_SQL} l_user_auth_storage.make (l_storage_sql) + else + -- FIXME: in case of NULL storage, should Current be disabled? + create {CMS_SESSION_AUTH_STORAGE_NULL} l_user_auth_storage + end + + -- API initialization + create l_session_auth_api.make_with_storage (a_api, l_user_auth_storage) + user_session_api := l_session_auth_api + ensure then + session_auth_api_set: user_session_api /= Void + end + +feature {CMS_API} -- Module management + + install (api: CMS_API) + do + -- Schema + if attached api.storage.as_sql_storage as l_sql_storage then + if not l_sql_storage.sql_table_exists ("session_auth") then + --| Schema + l_sql_storage.sql_execute_file_script (api.module_resource_location (Current, (create {PATH}.make_from_string ("scripts")).extended ("session_auth_table.sql")), Void) + + if l_sql_storage.has_error then + api.logger.put_error ("Could not initialize database for session auth module", generating_type) + end + end + l_sql_storage.sql_finalize + Precursor {CMS_MODULE}(api) + end + end + +feature {CMS_API} -- Access: API + + user_session_api: detachable CMS_SESSION_API + -- + +feature -- Access: router + + setup_router (a_router: WSF_ROUTER; a_api: CMS_API) + -- + do + a_router.handle ("/account/roc-session-login", create {WSF_URI_AGENT_HANDLER}.make (agent handle_login(a_api, ?, ?)), a_router.methods_head_get) + a_router.handle ("/account/roc-session-logout", create {WSF_URI_AGENT_HANDLER}.make (agent handle_logout (a_api, ?, ?)), a_router.methods_get_post) + a_router.handle ("/account/login-with-session", create {WSF_URI_TEMPLATE_AGENT_HANDLER}.make (agent handle_login_with_session (a_api,user_session_api, ?, ?)), a_router.methods_get_post) + end + +feature -- Access: filter + + filters (a_api: CMS_API): detachable LIST [WSF_FILTER] + -- Possibly list of Filter's module. + do + create {ARRAYED_LIST [WSF_FILTER]} Result.make (1) + if attached user_session_api as l_session_api then + Result.extend (create {CMS_SESSION_AUTH_FILTER}.make (a_api, l_session_api)) + end + end + +feature {NONE} -- Implementation: routes + + handle_login (api: CMS_API; req: WSF_REQUEST; res: WSF_RESPONSE) + local + r: CMS_RESPONSE + do + create {GENERIC_VIEW_CMS_RESPONSE} r.make (req, res, api) + r.execute + end + + handle_logout (api: CMS_API; req: WSF_REQUEST; res: WSF_RESPONSE) + local + r: CMS_RESPONSE + l_cookie: WSF_COOKIE + do + if + attached {WSF_STRING} req.cookie ({CMS_SESSION_CONSTANTS}.session_auth_token) as l_cookie_token and then + attached {CMS_USER} current_user (req) as l_user + then + -- Logout Session + create l_cookie.make ({CMS_SESSION_CONSTANTS}.session_auth_token, l_cookie_token.value) + l_cookie.set_path ("/") + l_cookie.set_max_age (-1) + res.add_cookie (l_cookie) + unset_current_user (req) + + create {GENERIC_VIEW_CMS_RESPONSE} r.make (req, res, api) + r.set_status_code ({HTTP_CONSTANTS}.found) + r.set_redirection (req.absolute_script_url ("")) + r.execute + else + fixme (generator + ": missing else implementation in handle_logout!") + end + end + + handle_login_with_session (api: CMS_API; a_session_api: detachable CMS_SESSION_API; req: WSF_REQUEST; res: WSF_RESPONSE) + local + r: CMS_RESPONSE + l_token: STRING + l_cookie: WSF_COOKIE + do + if + attached a_session_api as l_session_api and then + attached {WSF_STRING} req.form_parameter ("username") as l_username and then + attached {WSF_STRING} req.form_parameter ("password") as l_password and then + api.user_api.is_valid_credential (l_username.value, l_password.value) and then + attached api.user_api.user_by_name (l_username.value) as l_user + then + l_token := generate_token + if + a_session_api.has_user_token (l_user) + then + l_session_api.update_user_session_auth (l_token, l_user) + else + l_session_api.new_user_session_auth (l_token, l_user) + end + create l_cookie.make ({CMS_SESSION_CONSTANTS}.session_auth_token, l_token) + l_cookie.set_max_age ({CMS_SESSION_CONSTANTS}.session_max_age) + l_cookie.set_path ("/") + res.add_cookie (l_cookie) + set_current_user (req, l_user) + create {GENERIC_VIEW_CMS_RESPONSE} r.make (req, res, api) + r.set_redirection (req.absolute_script_url ("")) + r.execute + else + create {BAD_REQUEST_ERROR_CMS_RESPONSE} r.make (req, res, api) + if attached template_block ("login", r) as l_tpl_block then + if attached {WSF_STRING} req.form_parameter ("username") as l_username then + l_tpl_block.set_value (l_username.value, "username") + end + l_tpl_block.set_value ("Wrong: Username or password ", "error") + r.add_block (l_tpl_block, "content") + end + r.execute + 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) + a_hooks.subscribe_to_value_table_alter_hook (Current) + end + +feature -- Hooks + + value_table_alter (a_value: CMS_VALUE_TABLE; a_response: CMS_RESPONSE) + -- + do + if + attached a_response.user as u and then + attached {WSF_STRING} a_response.request.cookie ({CMS_SESSION_CONSTANTS}.session_auth_token) + then + a_value.force ("account/roc-session-logout", "auth_login_strategy") + end + + end + + 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'. + local + lnk: CMS_LOCAL_LINK + lnk2: detachable CMS_LINK + do + if + attached a_response.user as u and then + attached {WSF_STRING} a_response.request.cookie ({CMS_SESSION_CONSTANTS}.session_auth_token) + then + across + a_menu_system.primary_menu.items as ic + until + lnk2 /= Void + loop + if ic.item.location.same_string ("account/roc-logout") or else ic.item.location.same_string ("basic_auth_logoff") then + lnk2 := ic.item + end + end + if lnk2 /= Void then + a_menu_system.primary_menu.remove (lnk2) + end + create lnk.make ("Logout", "account/roc-session-logout" ) + a_menu_system.primary_menu.extend (lnk) + else + if a_response.location.starts_with ("account/") then + create lnk.make ("Session", "account/roc-session-login") + a_response.add_to_primary_tabs (lnk) + end + end + end + + block_list: ITERABLE [like {CMS_BLOCK}.name] + local + l_string: STRING + do + Result := <<"login">> + debug ("roc") + create l_string.make_empty + across + Result as ic + loop + l_string.append (ic.item) + l_string.append_character (' ') + end + write_debug_log (generator + ".block_list:" + l_string ) + end + end + + get_block_view (a_block_id: READABLE_STRING_8; a_response: CMS_RESPONSE) + do + if + a_block_id.is_case_insensitive_equal_general ("login") and then + a_response.location.starts_with ("account/roc-session-login") + then + get_block_view_login (a_block_id, a_response) + end + end + +feature {NONE} -- Helpers + + template_block (a_block_id: READABLE_STRING_8; a_response: CMS_RESPONSE): detachable CMS_SMARTY_TEMPLATE_BLOCK + -- Smarty content block for `a_block_id' + local + p: detachable PATH + do + create p.make_from_string ("templates") + p := p.extended ("block_").appended (a_block_id).appended_with_extension ("tpl") + + p := a_response.api.module_theme_resource_location (Current, p) + if p /= Void then + if attached p.entry as e then + create Result.make (a_block_id, Void, p.parent, e) + else + create Result.make (a_block_id, Void, p.parent, p) + end + end + end + +feature {NONE} -- Block views + + get_block_view_login (a_block_id: READABLE_STRING_8; a_response: CMS_RESPONSE) + local + vals: CMS_VALUE_TABLE + do + if attached template_block (a_block_id, a_response) as l_tpl_block then + create vals.make (1) + -- add the variable to the block + value_table_alter (vals, a_response) + across + vals as ic + loop + l_tpl_block.set_value (ic.item, ic.key) + end + a_response.add_block (l_tpl_block, "content") + else + debug ("cms") + a_response.add_warning_message ("Error with block [" + a_block_id + "]") + end + end + end + + + generate_token: STRING + -- Generate token to use in a Session. + local + l_token: STRING + l_security: CMS_TOKEN_GENERATOR + l_encode: URL_ENCODER + do + create l_security + l_token := l_security.token + create l_encode + from until l_token.same_string (l_encode.encoded_string (l_token)) loop + -- Loop ensure that we have a security token that does not contain characters that need encoding. + -- We cannot simply to an encode-decode because the email sent to the user will contain an encoded token + -- but the user will need to use an unencoded token if activation has to be done manually. + l_token := l_security.token + end + Result := l_token + end +end diff --git a/modules/session_auth/cms_session_constants.e b/modules/session_auth/cms_session_constants.e new file mode 100644 index 0000000..a50a980 --- /dev/null +++ b/modules/session_auth/cms_session_constants.e @@ -0,0 +1,19 @@ +note + description: "Summary description for {CMS_SESSION_CONSTANTS}." + date: "$Date$" + revision: "$Revision$" + +class + CMS_SESSION_CONSTANTS + + +feature + session_auth_token: STRING = "EWF_ROC_SESSION_AUTH_TOKEN_" + -- Name of Cookie used to keep the session info. + -- TODO add a config file to be able to customize this value via coniguration file. + + session_max_age: INTEGER = 86400 + -- Value of the Max-Age, before the cookie expires. + -- TODO add a config file to be able to customize this value via coniguration file. + +end diff --git a/modules/session_auth/filter/cms_session_auth_filter.e b/modules/session_auth/filter/cms_session_auth_filter.e new file mode 100644 index 0000000..1acaa53 --- /dev/null +++ b/modules/session_auth/filter/cms_session_auth_filter.e @@ -0,0 +1,55 @@ +note + description: "[ + Processes a HTTP request's checking Session cookies, putting the result into the execution variable user. + ]" + date: "$Date: 2015-02-13 13:08:13 +0100 (ven., 13 févr. 2015) $" + revision: "$Revision: 96616 $" + +class + CMS_SESSION_AUTH_FILTER + +inherit + WSF_URI_TEMPLATE_HANDLER + + CMS_HANDLER + rename + make as make_handler + end + + WSF_FILTER + +create + make + +feature {NONE} -- Initialization + + make (a_api: CMS_API; a_session_oauth_api: CMS_SESSION_API) + do + make_handler (a_api) + session_oauth_api := a_session_oauth_api + end + + session_oauth_api: CMS_SESSION_API + +feature -- Basic operations + + execute (req: WSF_REQUEST; res: WSF_RESPONSE) + -- Execute the filter. + do + api.logger.put_debug (generator + ".execute ", Void) + -- A valid user + if + attached {WSF_STRING} req.cookie ({CMS_SESSION_CONSTANTS}.session_auth_token) as l_roc_auth_session_token + then + if attached session_oauth_api.user_by_session_token (l_roc_auth_session_token.value) as l_user then + set_current_user (req, l_user) + else + api.logger.put_error (generator + ".execute login_valid failed for: " + l_roc_auth_session_token.value , Void) + end + else + api.logger.put_debug (generator + ".execute without authentication", Void) + end + execute_next (req, res) + end + +end diff --git a/modules/session_auth/persistence/cms_session_auth_storage_i.e b/modules/session_auth/persistence/cms_session_auth_storage_i.e new file mode 100644 index 0000000..f40b5bf --- /dev/null +++ b/modules/session_auth/persistence/cms_session_auth_storage_i.e @@ -0,0 +1,46 @@ +note + description: "[ + API to handle OAUTH storage + ]" + date: "$Date$" + revision: "$Revision$" + +deferred class + CMS_SESSION_AUTH_STORAGE_I + +inherit + SHARED_LOGGER + +feature -- Error Handling + + error_handler: ERROR_HANDLER + -- Error handler. + deferred + end + +feature -- Access: Users + + user_by_session_token (a_token: READABLE_STRING_32): detachable CMS_USER + -- Retrieve user by token `a_token', if any. + deferred + end + + has_user_token (a_user: CMS_USER): BOOLEAN + -- Has the user `a_user' and associated session token? + deferred + end + + +feature -- Change User session + + new_user_session_auth (a_token: READABLE_STRING_GENERAL; a_user: CMS_USER;) + -- New user session for user `a_user' with token `a_token'. + deferred + end + + + update_user_session_auth (a_token: READABLE_STRING_GENERAL; a_user: CMS_USER ) + -- Update user session for user `a_user' with token `a_token'. + deferred + end +end diff --git a/modules/session_auth/persistence/cms_session_auth_storage_null.e b/modules/session_auth/persistence/cms_session_auth_storage_null.e new file mode 100644 index 0000000..ff2da97 --- /dev/null +++ b/modules/session_auth/persistence/cms_session_auth_storage_null.e @@ -0,0 +1,47 @@ +note + description: "Summary description for {CMS_SESSION_AUTH_STORAGE_NULL}." + date: "$Date$" + revision: "$Revision$" + +class + CMS_SESSION_AUTH_STORAGE_NULL + +inherit + + CMS_SESSION_AUTH_STORAGE_I + + +feature -- Error handler + + error_handler: ERROR_HANDLER + -- Error handler. + do + create Result.make + end + +feature -- Access + + user_by_session_token (a_token: READABLE_STRING_32): detachable CMS_USER + -- Retrieve user by token `a_token', if any. + do + end + + has_user_token (a_user: CMS_USER): BOOLEAN + -- Has the user `a_user' and associated session token? + do + end + +feature -- Change User session + + new_user_session_auth (a_token: READABLE_STRING_GENERAL; a_user: CMS_USER;) + -- New user session for user `a_user' with token `a_token'. + do + end + + + update_user_session_auth (a_token: READABLE_STRING_GENERAL; a_user: CMS_USER ) + -- Update user session for user `a_user' with token `a_token'. + do + end + +end diff --git a/modules/session_auth/persistence/cms_session_auth_storage_sql.e b/modules/session_auth/persistence/cms_session_auth_storage_sql.e new file mode 100644 index 0000000..d472def --- /dev/null +++ b/modules/session_auth/persistence/cms_session_auth_storage_sql.e @@ -0,0 +1,155 @@ +note + description: "Summary description for {CMS_SESSION_AUTH_STORAGE_SQL}." + date: "$Date$" + revision: "$Revision$" + +class + CMS_SESSION_AUTH_STORAGE_SQL + +inherit + + CMS_SESSION_AUTH_STORAGE_I + + CMS_PROXY_STORAGE_SQL + + CMS_SESSION_AUTH_STORAGE_I + + CMS_STORAGE_SQL_I + + REFACTORING_HELPER + +create + make + +feature -- Access User + + user_by_session_token (a_token: READABLE_STRING_32): detachable CMS_USER + -- Retrieve user by token `a_token', if any. + local + l_parameters: STRING_TABLE [detachable ANY] + do + error_handler.reset + write_information_log (generator + ".user_by_session_token") + create l_parameters.make (1) + l_parameters.put (a_token, "token") + sql_query (Select_user_by_token, l_parameters) + if not has_error and not sql_after then + Result := fetch_user + sql_forth + if not sql_after then + check + no_more_than_one: False + end + Result := Void + end + end + sql_finalize + end + + has_user_token (a_user: CMS_USER): BOOLEAN + -- Has the user `a_user' and associated session token? + local + l_parameters: STRING_TABLE [detachable ANY] + do + error_handler.reset + write_information_log (generator + ".has_user_token") + create l_parameters.make (1) + l_parameters.put (a_user.id, "uid") + sql_query (Select_user_token, l_parameters) + if not has_error and not sql_after then + if sql_read_integer_64 (1) = 1 then + Result := True + else + Result := False + end + end + sql_finalize + end + +feature -- Change User token + + new_user_session_auth (a_token: READABLE_STRING_GENERAL; a_user: CMS_USER;) + -- . + local + l_parameters: STRING_TABLE [detachable ANY] + do + error_handler.reset + write_information_log (generator + ".new_user_session") + create l_parameters.make (3) + l_parameters.put (a_user.id, "uid") + l_parameters.put (a_token, "token") + l_parameters.put (create {DATE_TIME}.make_now_utc, "utc_date") + sql_begin_transaction + sql_insert (sql_insert_session_auth, l_parameters) + sql_commit_transaction + sql_finalize + end + + update_user_session_auth (a_token: READABLE_STRING_GENERAL; a_user: CMS_USER) + -- + local + l_parameters: STRING_TABLE [detachable ANY] + do + error_handler.reset + write_information_log (generator + ".update_user_session_auth") + create l_parameters.make (3) + l_parameters.put (a_user.id, "uid") + l_parameters.put (a_token, "token") + l_parameters.put (create {DATE_TIME}.make_now_utc, "utc_date") + sql_begin_transaction + sql_modify (sql_update_session_auth, l_parameters) + sql_commit_transaction + sql_finalize + end + +feature {NONE} -- Implementation + + fetch_user: detachable CMS_USER + local + l_id: INTEGER_64 + l_name: detachable READABLE_STRING_32 + do + if attached sql_read_integer_64 (1) as i then + l_id := i + end + if attached sql_read_string_32 (2) as s and then not s.is_whitespace then + l_name := s + end + if l_name /= Void then + create Result.make (l_name) + if l_id > 0 then + Result.set_id (l_id) + end + elseif l_id > 0 then + create Result.make_with_id (l_id) + end + if Result /= Void then + if attached sql_read_string (3) as l_password then + -- FIXME: should we return the password here ??? + Result.set_hashed_password (l_password) + end + if attached sql_read_string (5) as l_email then + Result.set_email (l_email) + end + if attached sql_read_integer_32 (6) as l_status then + Result.set_status (l_status) + end + else + check + expected_valid_user: False + end + end + end + +feature {NONE} -- SQL statements + + Select_user_by_token: STRING = "SELECT u.* FROM users as u JOIN session_auth as og ON og.uid = u.uid and og.access_token = :token;" + --| FIXME: replace the u.* by a list of field names, to avoid breaking `featch_user' if two fieds are swiped. + + Sql_insert_session_auth: STRING = "INSERT INTO session_auth (uid, access_token, created) VALUES (:uid, :token, :utc_date);" + + Sql_update_session_auth: STRING = "UPDATE session_auth SET access_token = :token, created = :utc_date WHERE uid =:uid;" + + Select_user_token: STRING = "SELECT COUNT(*) FROM session_auth where uid = :uid;" + +end diff --git a/modules/session_auth/site/cms_token_generator.e b/modules/session_auth/site/cms_token_generator.e new file mode 100644 index 0000000..d18bd90 --- /dev/null +++ b/modules/session_auth/site/cms_token_generator.e @@ -0,0 +1,153 @@ +note + description: "Provides security routine helpers" + date: "$Date$" + revision: "$Revision$" + +class + CMS_TOKEN_GENERATOR + +inherit + + REFACTORING_HELPER + +feature -- Access + + token: STRING + -- Cryptographic random base 64 string. + do + Result := salt_with_size (16) + -- Remove trailing equal sign + Result.keep_head (Result.count - 2) + end + + salt: STRING + -- Cryptographic random number of 16 bytes. + do + Result := salt_with_size (16) + end + + password: STRING + -- Cryptographic random password of 10 bytes. + do + Result := salt_with_size (10) + -- Remove trailing equal signs + Result.keep_head (Result.count - 2) + end + + password_hash (a_password, a_salt: STRING): STRING + -- Password hash based on password `a_password' and salt value `a_salt'. + do + Result := sha1_string (a_password + a_salt ) + end + +feature {NONE} -- Implementation + + salt_with_size (a_val: INTEGER): STRING + -- Return a salt with size `a_val'. + local + l_salt: SALT_XOR_SHIFT_64_GENERATOR + l_array: ARRAY [INTEGER_8] + i: INTEGER + do + create l_salt.make (a_val) + create l_array.make_empty + i := 1 + across + l_salt.new_sequence as c + loop + l_array.force (c.item.as_integer_8, i) + i := i + 1 + end + Result := base_64 (l_array) + end + + sha1_string (a_str: STRING): STRING + -- SHA1 diggest of `a_str'. + do + sha1.update_from_string (a_str) + Result := sha1.digest_as_string + sha1.reset + end + + sha1: SHA1 + -- Create a SHA1 object. + do + create Result.make + end + +feature -- Encoding + + + base_64 (bytes: SPECIAL [INTEGER_8]): STRING_8 + -- Encodes a byte array into a STRING doing base64 encoding. + local + l_output: SPECIAL [INTEGER_8] + l_remaining: INTEGER + i, ptr: INTEGER + char: CHARACTER + do + to_implement ("Check existing code to do that!!!.") + create l_output.make_filled (0, ((bytes.count + 2) // 3) * 4) + l_remaining := bytes.count + from + i := 0 + ptr := 0 + until + l_remaining <= 3 + loop + l_output [ptr] := encode_value (bytes [i] |>> 2) + ptr := ptr + 1 + l_output [ptr] := encode_value (((bytes [i] & 0x3) |<< 4) | ((bytes [i + 1] |>> 4) & 0xF)) + ptr := ptr + 1 + l_output [ptr] := encode_value (((bytes [i + 1] & 0xF) |<< 2) | ((bytes [i + 2] |>> 6) & 0x3)) + ptr := ptr + 1 + l_output [ptr] := encode_value (bytes [i + 2] & 0x3F) + ptr := ptr + 1 + l_remaining := l_remaining - 3 + i := i + 3 + end + -- encode when exactly 1 element (left) to encode + char := '=' + if l_remaining = 1 then + l_output [ptr] := encode_value (bytes [i] |>> 2) + ptr := ptr + 1 + l_output [ptr] := encode_value (((bytes [i]) & 0x3) |<< 4) + ptr := ptr + 1 + l_output [ptr] := char.code.as_integer_8 + ptr := ptr + 1 + l_output [ptr] := char.code.as_integer_8 + ptr := ptr + 1 + end + + -- encode when exactly 2 elements (left) to encode + if l_remaining = 2 then + l_output [ptr] := encode_value (bytes [i] |>> 2) + ptr := ptr + 1 + l_output [ptr] := encode_value (((bytes [i] & 0x3) |<< 4) | ((bytes [i + 1] |>> 4) & 0xF)); + ptr := ptr + 1 + l_output [ptr] := encode_value ((bytes [i + 1] & 0xF) |<< 2); + ptr := ptr + 1 + l_output [ptr] := char.code.as_integer_8 + ptr := ptr + 1 + end + create Result.make_empty + across + l_output as elem + loop + Result.append_character (elem.item.to_character_8) + end + end + + base64_map: SPECIAL [CHARACTER_8] + -- Table for Base64 encoding. + once + Result := ("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/").area + end + + encode_value (i: INTEGER_8): INTEGER_8 + -- Encode `i'. + do + Result := base64_map [i & 0x3F].code.as_integer_8 + end + +end diff --git a/modules/session_auth/site/scripts/session_auth_table.sql.tpl b/modules/session_auth/site/scripts/session_auth_table.sql.tpl new file mode 100644 index 0000000..ada32db --- /dev/null +++ b/modules/session_auth/site/scripts/session_auth_table.sql.tpl @@ -0,0 +1,11 @@ + +CREATE TABLE session_auth ( + `uid` INTEGER PRIMARY KEY NOT NULL CHECK(`uid`>=0), + `access_token` TEXT NOT NULL, + `created` DATETIME NOT NULL, + CONSTRAINT `uid` + UNIQUE(`uid`), + CONSTRAINT `access_token` + UNIQUE(`access_token`) + ); + diff --git a/modules/session_auth/site/templates/block_login.tpl b/modules/session_auth/site/templates/block_login.tpl new file mode 100644 index 0000000..a344568 --- /dev/null +++ b/modules/session_auth/site/templates/block_login.tpl @@ -0,0 +1,37 @@ +
+ {unless isset="$user"} +

Login or Register

+
+
+
+
+ + +
+ +
+ + +
+ +
+
+
+
+ +
+ {/unless} + {if isset=$error} +
+
+

+ {$error/} +

+
+
+ {/if} +
diff --git a/modules/taxonomy/cms_taxonomy_api.e b/modules/taxonomy/cms_taxonomy_api.e index 69534d8..7fec959 100644 --- a/modules/taxonomy/cms_taxonomy_api.e +++ b/modules/taxonomy/cms_taxonomy_api.e @@ -52,7 +52,7 @@ feature -- Access node Result := taxonomy_storage.vocabularies (a_limit, a_offset) end - vocabulary (a_id: INTEGER): detachable CMS_VOCABULARY + vocabulary (a_id: INTEGER_64): detachable CMS_VOCABULARY -- Vocabulary associated with id `a_id'. require valid_id: a_id > 0 @@ -66,6 +66,27 @@ feature -- Access node Result := taxonomy_storage.vocabularies_for_type (a_type_name) end + types_associated_with_vocabulary (a_vocab: CMS_VOCABULARY): detachable LIST [READABLE_STRING_32] + -- Type names associated with `a_vocab'. + do + Result := taxonomy_storage.types_associated_with_vocabulary (a_vocab) + end + + vocabularies_for_term (a_term: CMS_TERM): detachable CMS_VOCABULARY_COLLECTION + -- Vocabularies including `a_term'. + do + Result := taxonomy_storage.vocabularies_for_term (a_term) + end + + is_term_inside_vocabulary (a_term: CMS_TERM; a_vocab: CMS_VOCABULARY): BOOLEAN + -- Is `a_term' inside `a_vocab' ? + require + valid_term: a_term.has_id + valid_vocabulary: a_vocab.has_id + do + Result := taxonomy_storage.is_term_inside_vocabulary (a_term, a_vocab) + end + fill_vocabularies_with_terms (a_vocab: CMS_VOCABULARY) -- Fill `a_vocab' with associated terms. do @@ -88,6 +109,17 @@ feature -- Access node Result := taxonomy_storage.term_count_from_vocabulary (a_vocab) end + terms_of_content (a_content: CMS_CONTENT; a_vocabulary: detachable CMS_VOCABULARY): detachable CMS_TERM_COLLECTION + -- Terms related to `a_content', and if `a_vocabulary' is set + -- constrain to be part of `a_vocabulary'. + require + content_with_identifier: a_content.has_identifier + do + if attached a_content.identifier as l_id then + Result := terms_of_entity (a_content.content_type, l_id, a_vocabulary) + end + end + terms_of_entity (a_type_name: READABLE_STRING_GENERAL; a_entity: READABLE_STRING_GENERAL; a_vocabulary: detachable CMS_VOCABULARY): detachable CMS_TERM_COLLECTION -- Terms related to `(a_type_name,a_entity)', and if `a_vocabulary' is set -- constrain to be part of `a_vocabulary'. @@ -125,19 +157,63 @@ feature -- Access node feature -- Write save_vocabulary (a_voc: CMS_VOCABULARY) + -- Insert or update vocabulary `a_voc' + -- and also save {CMS_VOCABULARY}.terms if `a_with_terms' is True. do reset_error - taxonomy_storage.save_vocabulary (a_voc) + taxonomy_storage.save_vocabulary (a_voc, False) error_handler.append (taxonomy_storage.error_handler) end - save_term (a_term: CMS_TERM; voc: CMS_VOCABULARY) + save_vocabulary_and_terms (a_voc: CMS_VOCABULARY) + -- Insert or update vocabulary `a_voc' + -- and also save {CMS_VOCABULARY}.terms. + do + reset_error + taxonomy_storage.save_vocabulary (a_voc, True) + error_handler.append (taxonomy_storage.error_handler) + end + + save_term (a_term: CMS_TERM; voc: detachable CMS_VOCABULARY) + -- Save `a_term' inside `voc' if set. do reset_error taxonomy_storage.save_term (a_term, voc) error_handler.append (taxonomy_storage.error_handler) end + remove_term_from_vocabulary (t: CMS_TERM; voc: CMS_VOCABULARY) + -- Remove term `t' from vocabulary `voc'. + do + reset_error + taxonomy_storage.remove_term_from_vocabulary (t, voc) + error_handler.append (taxonomy_storage.error_handler) + end + + associate_term_with_content (a_term: CMS_TERM; a_content: CMS_CONTENT) + -- Associate term `a_term' with `a_content'. + require + content_with_identifier: a_content.has_identifier + do + reset_error + if attached a_content.identifier as l_id then + taxonomy_storage.associate_term_with_entity (a_term, a_content.content_type, l_id) + error_handler.append (taxonomy_storage.error_handler) + end + end + + unassociate_term_from_content (a_term: CMS_TERM; a_content: CMS_CONTENT) + -- Unassociate term `a_term' from `a_content'. + require + content_with_identifier: a_content.has_identifier + do + reset_error + if attached a_content.identifier as l_id then + taxonomy_storage.unassociate_term_from_entity (a_term, a_content.content_type, l_id) + error_handler.append (taxonomy_storage.error_handler) + end + end + associate_term_with_entity (a_term: CMS_TERM; a_type_name: READABLE_STRING_GENERAL; a_entity: READABLE_STRING_GENERAL) -- Associate term `a_term' with `(a_type_name, a_entity)'. do @@ -174,6 +250,275 @@ feature -- Write error_handler.append (taxonomy_storage.error_handler) end +feature -- Web forms + + populate_edit_form (a_response: CMS_RESPONSE; a_form: CMS_FORM; a_content_type_name: READABLE_STRING_8; a_content: detachable CMS_CONTENT) + local + ti: detachable WSF_FORM_TEXT_INPUT + th: WSF_FORM_HIDDEN_INPUT + w_set: WSF_FORM_FIELD_SET + w_select: WSF_FORM_SELECT + w_opt: WSF_FORM_SELECT_OPTION + w_cb: WSF_FORM_CHECKBOX_INPUT + w_voc_set: WSF_FORM_FIELD_SET + s: STRING_32 + voc: CMS_VOCABULARY + t: detachable CMS_TERM + l_terms: detachable CMS_TERM_COLLECTION + l_has_edit_permission: BOOLEAN + do + if + attached vocabularies_for_type (a_content_type_name) as l_vocs and then not l_vocs.is_empty + then + l_has_edit_permission := a_response.has_permissions (<<"update any taxonomy", "update " + a_content_type_name + " taxonomy">>) + + -- Handle Taxonomy fields, if any associated with `content_type'. + create w_set.make + w_set.add_css_class ("taxonomy") + l_vocs.sort + across + l_vocs as vocs_ic + loop + voc := vocs_ic.item + create th.make_with_text ({STRING_32} "taxonomy_vocabularies[" + voc.id.out + "]", voc.name) + w_set.extend (th) + + l_terms := Void + if a_content /= Void then + l_terms := terms_of_content (a_content, voc) + if l_terms /= Void then + l_terms.sort + end + end + create w_voc_set.make + w_set.extend (w_voc_set) + + if voc.is_tags then + w_voc_set.set_legend (cms_api.translation (voc.name, Void)) + + create ti.make ({STRING_32} "taxonomy_" + voc.id.out) + w_voc_set.extend (ti) + if voc.is_term_required then + ti.enable_required + end + if attached voc.description as l_desc then + ti.set_description (cms_api.html_encoded (cms_api.translation (l_desc, Void))) + else + ti.set_description (a_response.html_encoded (cms_api.translation (voc.name, Void))) + end + ti.set_size (70) + if l_terms /= Void then + create s.make_empty + across + l_terms as ic + loop + t := ic.item + if not s.is_empty then + s.append_character (',') + s.append_character (' ') + end + if ic.item.text.has (' ') then + s.append_character ('"') + s.append (t.text) + s.append_character ('"') + else + s.append (t.text) + end + end + ti.set_text_value (s) + end + if not l_has_edit_permission then + ti.set_is_readonly (True) + end + else + fill_vocabularies_with_terms (voc) + if not voc.terms.is_empty then + if voc.multiple_terms_allowed then + if attached voc.description as l_desc then + w_voc_set.set_legend (cms_api.html_encoded (l_desc)) + else + w_voc_set.set_legend (cms_api.html_encoded (voc.name)) + end + across + voc as voc_terms_ic + loop + t := voc_terms_ic.item + create w_cb.make_with_value ({STRING_32} "taxonomy_" + voc.id.out + "[]", t.text) + w_cb.set_title (t.text) + w_voc_set.extend (w_cb) + if l_terms /= Void and then across l_terms as ic some ic.item.text.same_string (t.text) end then + w_cb.set_checked (True) + end + if not l_has_edit_permission then + w_cb.set_is_readonly (True) + end + end + else + create w_select.make ({STRING_32} "taxonomy_" + voc.id.out) + w_voc_set.extend (w_select) + + if attached voc.description as l_desc then + w_select.set_description (cms_api.html_encoded (l_desc)) + else + w_select.set_description (cms_api.html_encoded (voc.name)) + end + w_voc_set.set_legend (cms_api.html_encoded (voc.name)) + + across + voc as voc_terms_ic + loop + t := voc_terms_ic.item + create w_opt.make (cms_api.html_encoded (t.text), cms_api.html_encoded (t.text)) + w_select.add_option (w_opt) + + if l_terms /= Void and then across l_terms as ic some ic.item.text.same_string (t.text) end then + w_opt.set_is_selected (True) + end + end + if not l_has_edit_permission then + w_select.set_is_readonly (True) + end + end + end + end + end + + a_form.submit_actions.extend (agent taxonomy_submit_action (a_response, Current, l_vocs, a_content, ?)) + + if + attached a_form.fields_by_name ("title") as l_title_fields and then + attached l_title_fields.first as l_title_field + then + a_form.insert_after (w_set, l_title_field) + else + a_form.extend (w_set) + end + end + end + + taxonomy_submit_action (a_response: CMS_RESPONSE; a_taxonomy_api: CMS_TAXONOMY_API; a_vocs: CMS_VOCABULARY_COLLECTION; a_content: detachable CMS_CONTENT fd: WSF_FORM_DATA) + require + vocs_not_empty: not a_vocs.is_empty + local + l_voc_name: READABLE_STRING_32 + l_terms_to_remove: ARRAYED_LIST [CMS_TERM] + l_new_terms: LIST [READABLE_STRING_32] + l_text: READABLE_STRING_GENERAL + l_found: BOOLEAN + t: detachable CMS_TERM + vid: INTEGER_64 + do + if + a_content /= Void and then a_content.has_identifier and then + attached fd.table_item ("taxonomy_vocabularies") as fd_vocs + then + if a_response.has_permissions (<<{STRING_32} "update any taxonomy", {STRING_32} "update " + a_content.content_type + " taxonomy">>) then + across + fd_vocs.values as ic + loop + vid := ic.key.to_integer_64 + l_voc_name := ic.item.string_representation + + if attached a_vocs.item_by_id (vid) as voc then + if attached fd.string_item ("taxonomy_" + vid.out) as l_string then + l_new_terms := a_taxonomy_api.splitted_string (l_string, ',') + elseif attached fd.table_item ("taxonomy_" + vid.out) as fd_terms then + create {ARRAYED_LIST [READABLE_STRING_32]} l_new_terms.make (fd_terms.count) + across + fd_terms as t_ic + loop + l_new_terms.force (t_ic.item.string_representation) + end + else + create {ARRAYED_LIST [READABLE_STRING_32]} l_new_terms.make (0) + end + + create l_terms_to_remove.make (0) + if attached a_taxonomy_api.terms_of_content (a_content, voc) as l_existing_terms then + across + l_existing_terms as t_ic + loop + l_text := t_ic.item.text + from + l_found := False + l_new_terms.start + until + l_new_terms.after + loop + if l_new_terms.item.same_string_general (l_text) then + -- Already associated with term `t_ic.text'. + l_found := True + l_new_terms.remove + else + l_new_terms.forth + end + end + if not l_found then + -- Remove term + l_terms_to_remove.force (t_ic.item) + end + end + across + l_terms_to_remove as t_ic + loop + a_taxonomy_api.unassociate_term_from_content (t_ic.item, a_content) + end + end + across + l_new_terms as t_ic + loop + t := a_taxonomy_api.term_by_text (t_ic.item, voc) + if + t = Void and voc.is_tags + then + -- Create new term! + create t.make (t_ic.item) + a_taxonomy_api.save_term (t, voc) + if a_taxonomy_api.has_error then + t := Void + end + end + if t /= Void then + a_taxonomy_api.associate_term_with_content (t, a_content) + end + end + end + end + end + end + end + + append_taxonomy_to_xhtml (a_content: CMS_CONTENT; a_response: CMS_RESPONSE; a_output: STRING) + -- Append taxonomy related to `a_content' to xhtml string `a_output', + -- using `a_response' helper routines. + do + if + attached vocabularies_for_type (a_content.content_type) as vocs and then not vocs.is_empty + then + vocs.sort + across + vocs as ic + loop + if + attached terms_of_content (a_content, ic.item) as l_terms and then + not l_terms.is_empty + then + a_output.append ("
    ") + a_output.append (cms_api.html_encoded (ic.item.name)) + a_output.append (": ") + across + l_terms as t_ic + loop + a_output.append ("
  • ") + a_response.append_link_to_html (t_ic.item.text, "taxonomy/term/" + t_ic.item.id.out, Void, a_output) + a_output.append ("
  • ") + end + a_output.append ("
%N") + end + end + end + end + feature -- Helpers splitted_string (s: READABLE_STRING_32; sep: CHARACTER): LIST [READABLE_STRING_32] diff --git a/modules/taxonomy/cms_taxonomy_module.e b/modules/taxonomy/cms_taxonomy_module.e index bfbe5ea..0d3ddb5 100644 --- a/modules/taxonomy/cms_taxonomy_module.e +++ b/modules/taxonomy/cms_taxonomy_module.e @@ -109,6 +109,7 @@ feature -- Access: router do if attached taxonomy_api as l_taxonomy_api then configure_web (a_api, l_taxonomy_api, a_router) + configure_web_amin (a_api, l_taxonomy_api, a_router) else -- Issue with api/dependencies, -- thus Current module should not be used! @@ -120,9 +121,48 @@ feature -- Access: router -- Configure router mapping for web interface. local l_taxonomy_handler: TAXONOMY_HANDLER + l_voc_handler: TAXONOMY_VOCABULARY_HANDLER do create l_taxonomy_handler.make (a_api, a_taxonomy_api) a_router.handle ("/taxonomy/term/{termid}", l_taxonomy_handler, a_router.methods_get) + + create l_voc_handler.make (a_api, a_taxonomy_api) + a_router.handle ("/taxonomy/vocabulary/", l_voc_handler, a_router.methods_get) + a_router.handle ("/taxonomy/vocabulary/{vocid}", l_voc_handler, a_router.methods_get) + end + + configure_web_amin (a_api: CMS_API; a_taxonomy_api: CMS_TAXONOMY_API; a_router: WSF_ROUTER) + -- Configure router mapping for web interface. + local + l_taxonomy_handler: TAXONOMY_TERM_ADMIN_HANDLER + l_voc_handler: TAXONOMY_VOCABULARY_ADMIN_HANDLER + do + a_router.handle ("/admin/taxonomy/", create {WSF_URI_AGENT_HANDLER}.make (agent handle_admin_taxonomy (?, ?, a_api)), a_router.methods_get) + + create l_taxonomy_handler.make (a_api, a_taxonomy_api) + a_router.handle ("/admin/taxonomy/term/", l_taxonomy_handler, a_router.methods_get_post) + a_router.handle ("/admin/taxonomy/term/{termid}", l_taxonomy_handler, a_router.methods_get_post) + + create l_voc_handler.make (a_api, a_taxonomy_api) + a_router.handle ("/admin/taxonomy/vocabulary/", l_voc_handler, a_router.methods_get_post) + a_router.handle ("/admin/taxonomy/vocabulary/{vocid}", l_voc_handler, a_router.methods_get_post) + end + +feature -- Handler + + handle_admin_taxonomy (req: WSF_REQUEST; res: WSF_RESPONSE; api: CMS_API) + local + l_page: CMS_RESPONSE + lnk: CMS_LOCAL_LINK + do + create {GENERIC_VIEW_CMS_RESPONSE} l_page.make (req, res, api) + create lnk.make ("Admin Vocabularies", "admin/taxonomy/vocabulary/") + l_page.add_to_primary_tabs (lnk) + + create lnk.make ("Create terms", "admin/taxonomy/term/") + l_page.add_to_primary_tabs (lnk) + + l_page.execute end feature -- Hooks @@ -139,10 +179,14 @@ feature -- Hooks end menu_system_alter (a_menu_system: CMS_MENU_SYSTEM; a_response: CMS_RESPONSE) + local + lnk: CMS_LOCAL_LINK do - -- Add the link to the taxonomy to the main menu --- create lnk.make ("Taxonomy", "taxonomy/") --- a_menu_system.primary_menu.extend (lnk) + -- Add the link to the taxonomy to the main menu + if a_response.has_permission ("admin taxonomy") then + create lnk.make ("Taxonomy", "admin/taxonomy/") + a_menu_system.management_menu.extend (lnk) + end end end diff --git a/modules/taxonomy/cms_term_collection.e b/modules/taxonomy/cms_term_collection.e index 5e882e7..dc96960 100644 --- a/modules/taxonomy/cms_term_collection.e +++ b/modules/taxonomy/cms_term_collection.e @@ -19,6 +19,7 @@ feature {NONE} -- Initialization make (nb: INTEGER) do create items.make (nb) + items.compare_objects end feature -- Access @@ -48,6 +49,21 @@ feature -- Status report Result := items.has (a_term) end + term_by_id (tid: INTEGER_64): detachable CMS_TERM + -- Term of id `tid' contained in Current, if any. + do + across + items as ic + until + Result /= Void + loop + Result := ic.item + if Result.id /= tid then + Result := Void + end + end + end + feature -- Element change wipe_out diff --git a/modules/taxonomy/cms_vocabulary_collection.e b/modules/taxonomy/cms_vocabulary_collection.e index b4c3028..b8ccf05 100644 --- a/modules/taxonomy/cms_vocabulary_collection.e +++ b/modules/taxonomy/cms_vocabulary_collection.e @@ -19,6 +19,7 @@ feature {NONE} -- Initialization make (nb: INTEGER) do create items.make (nb) + items.compare_objects end feature -- Access @@ -37,6 +38,21 @@ feature -- Access end end + item_by_id (a_id: INTEGER_64): detachable CMS_VOCABULARY + -- Vocabulary of id `a_id' contained in Current, if any. + do + across + items as ic + until + Result /= Void + loop + Result := ic.item + if Result.id /= a_id then + Result := Void + end + end + end + new_cursor: INDEXABLE_ITERATION_CURSOR [CMS_VOCABULARY] -- do diff --git a/modules/taxonomy/handler/taxonomy_term_admin_handler.e b/modules/taxonomy/handler/taxonomy_term_admin_handler.e new file mode 100644 index 0000000..9e97d36 --- /dev/null +++ b/modules/taxonomy/handler/taxonomy_term_admin_handler.e @@ -0,0 +1,269 @@ +note + description: "[ + Request handler related to + /admin/taxonomy/term/{termid} + ]" + date: "$Date$" + revision: "$revision$" + +class + TAXONOMY_TERM_ADMIN_HANDLER + +inherit + CMS_MODULE_HANDLER [CMS_TAXONOMY_API] + rename + module_api as taxonomy_api + end + + WSF_URI_HANDLER + rename + execute as uri_execute, + new_mapping as new_uri_mapping + end + + WSF_URI_TEMPLATE_HANDLER + rename + execute as uri_template_execute, + new_mapping as new_uri_template_mapping + select + new_uri_template_mapping + end + + WSF_RESOURCE_HANDLER_HELPER + redefine + do_get, + do_post + end + + REFACTORING_HELPER + + CMS_API_ACCESS + +create + make + +feature -- execute + + execute (req: WSF_REQUEST; res: WSF_RESPONSE) + -- Execute request handler for any kind of mapping. + do + execute_methods (req, res) + end + + uri_execute (req: WSF_REQUEST; res: WSF_RESPONSE) + -- Execute request handler for URI mapping. + do + execute (req, res) + end + + uri_template_execute (req: WSF_REQUEST; res: WSF_RESPONSE) + -- Execute request handler for URI-template mapping. + do + execute (req, res) + end + +feature -- HTTP Methods + + + do_post (req: WSF_REQUEST; res: WSF_RESPONSE) + local + l_page: CMS_RESPONSE + tid: INTEGER_64 + s: STRING + f: CMS_FORM + t: detachable CMS_TERM + l_parents: detachable CMS_VOCABULARY_COLLECTION + do + if + attached {WSF_STRING} req.path_parameter ("termid") as p_termid and then + p_termid.is_integer + then + tid := p_termid.value.to_integer_64 + if tid > 0 then + t := taxonomy_api.term_by_id (tid) + end + end + + -- Responding with `main_content_html (l_page)'. + create {GENERIC_VIEW_CMS_RESPONSE} l_page.make (req, res, api) + if l_page.has_permission ("admin taxonomy") then + + if t = Void then + l_page.set_title ("New term ...") + create t.make ("") + else + l_page.set_title (t.text) + end + create s.make_empty + f := edit_form (t, l_page, req) + f.process (l_page) + 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 ("Save changes") then + if attached fd.string_item ("text") as l_text then + t.set_text (l_text) + l_page.set_title (t.text) + end + if attached fd.string_item ("description") as l_description then + t.set_description (l_description) + end + if attached fd.string_item ("weight") as l_weight and then l_weight.is_integer then + t.set_weight (l_weight.to_integer) + end + taxonomy_api.save_term (t, Void) + if taxonomy_api.has_error then + fd.report_error ("Term creation failed!") + else + l_page.add_success_message ("Term creation succeed.") + s.append ("
View term: ") + s.append (l_page.link (t.text, "admin/taxonomy/term/" + t.id.out, Void)) + s.append ("
") + + if + attached fd.table_item ("vocabularies") as voc_tb and then + attached taxonomy_api.vocabularies (0, 0) as l_vocabularies + then + l_parents := taxonomy_api.vocabularies_for_term (t) + across + voc_tb as vid_ic + until + taxonomy_api.has_error + loop + if attached l_vocabularies.item_by_id (vid_ic.item.string_representation.to_integer_64) as v then + if l_parents /= Void and then attached l_parents.item_by_id (v.id) as l_v then + -- Already as parent! + l_parents.remove (l_v) + else + taxonomy_api.save_term (t, v) + l_vocabularies.remove (v) + end + end + end + if l_parents /= Void then + across + l_parents as v_ic + until + taxonomy_api.has_error + loop + taxonomy_api.remove_term_from_vocabulary (t, v_ic.item) + end + end + end + -- l_page.set_redirection (l_page.location) + end + else + fd.report_error ("Invalid form data!") + end + end + f.append_to_html (l_page.wsf_theme, s) + l_page.set_main_content (s) + l_page.execute + else + send_access_denied (req, res) + end + end + + do_get (req: WSF_REQUEST; res: WSF_RESPONSE) + -- + local + l_page: CMS_RESPONSE + tid: INTEGER_64 + s: STRING + f: CMS_FORM + t: detachable CMS_TERM + do + if + attached {WSF_STRING} req.path_parameter ("termid") as p_termid and then + p_termid.is_integer + then + tid := p_termid.value.to_integer_64 + if tid > 0 then + t := taxonomy_api.term_by_id (tid) + end + end + -- Responding with `main_content_html (l_page)'. + create {GENERIC_VIEW_CMS_RESPONSE} l_page.make (req, res, api) + if l_page.has_permission ("admin taxonomy") then + if t = Void then + l_page.set_title ("Create term ...") + create t.make ("") + else + l_page.set_title (t.text) + end + create s.make_empty + f := edit_form (t, l_page, req) + f.append_to_html (l_page.wsf_theme, s) + l_page.set_main_content (s) + l_page.execute + else + send_access_denied (req, res) + end + end + + edit_form (t: CMS_TERM; a_page: CMS_RESPONSE; req: WSF_REQUEST): CMS_FORM + local + f: CMS_FORM + voc: detachable CMS_VOCABULARY + w_tf: WSF_FORM_TEXT_INPUT + w_txt: WSF_FORM_TEXTAREA + w_set: WSF_FORM_FIELD_SET + w_cb: WSF_FORM_CHECKBOX_INPUT + l_parents: detachable CMS_VOCABULARY_COLLECTION + do + create f.make (req.percent_encoded_path_info, "taxonomy") + if t.has_id then + f.extend_html_text (a_page.link ("View associated entities", "taxonomy/term/" + t.id.out, Void)) + end + create w_tf.make_with_text ("text", t.text) + w_tf.set_is_required (True) + w_tf.set_label ("Text") + f.extend (w_tf) + + create w_txt.make ("description") + w_txt.set_label ("Description") + w_txt.set_rows (3) + w_txt.set_cols (60) + if attached t.description as l_desc then + w_txt.set_text_value (api.html_encoded (l_desc)) + end + w_txt.set_description ("Description of the terms; can be used by modules or administration.") + f.extend (w_txt) + + create w_tf.make_with_text ("weight", t.weight.out) + w_tf.set_label ("Weight") + w_tf.set_description ("Terms are sorted in ascending order by weight.") + f.extend (w_tf) + + if attached taxonomy_api.vocabularies (0, 0) as vocs then + if t.has_id then + l_parents := taxonomy_api.vocabularies_for_term (t) + end + create w_set.make + w_set.set_legend ("Associated vocabularies") + across + vocs as ic + loop + voc := ic.item + create w_cb.make_with_value ("vocabularies[]", ic.item.id.out) + w_cb.set_title (voc.name) + if + l_parents /= Void and then + across l_parents as p_ic some p_ic.item.id = ic.item.id end + then + w_cb.set_checked (True) + end + w_set.extend (w_cb) + end + if w_set.count > 0 then + f.extend (w_set) + end + end + + f.extend (create {WSF_FORM_SUBMIT_INPUT}.make_with_text ("op", "Save changes")) + Result := f + end + + +end diff --git a/modules/taxonomy/handler/taxonomy_vocabulary_admin_handler.e b/modules/taxonomy/handler/taxonomy_vocabulary_admin_handler.e new file mode 100644 index 0000000..c3ba3ac --- /dev/null +++ b/modules/taxonomy/handler/taxonomy_vocabulary_admin_handler.e @@ -0,0 +1,414 @@ +note + description: "[ + Request handler related to + /admin/taxonomy/vocabulary/ + /admin/taxonomy/vocabulary/{vocid} + ]" + date: "$Date$" + revision: "$revision$" + +class + TAXONOMY_VOCABULARY_ADMIN_HANDLER + +inherit + CMS_MODULE_HANDLER [CMS_TAXONOMY_API] + rename + module_api as taxonomy_api + end + + WSF_URI_HANDLER + rename + execute as uri_execute, + new_mapping as new_uri_mapping + end + + WSF_URI_TEMPLATE_HANDLER + rename + execute as uri_template_execute, + new_mapping as new_uri_template_mapping + select + new_uri_template_mapping + end + + WSF_RESOURCE_HANDLER_HELPER + redefine + do_get, + do_post + end + + REFACTORING_HELPER + + CMS_API_ACCESS + +create + make + +feature -- execute + + execute (req: WSF_REQUEST; res: WSF_RESPONSE) + -- Execute request handler for any kind of mapping. + do + execute_methods (req, res) + end + + uri_execute (req: WSF_REQUEST; res: WSF_RESPONSE) + -- Execute request handler for URI mapping. + do + execute (req, res) + end + + uri_template_execute (req: WSF_REQUEST; res: WSF_RESPONSE) + -- Execute request handler for URI-template mapping. + do + execute (req, res) + end + +feature -- HTTP Methods + + do_post (req: WSF_REQUEST; res: WSF_RESPONSE) + local + l_page: CMS_RESPONSE + voc: CMS_VOCABULARY + l_typename: READABLE_STRING_GENERAL + s: STRING + do + if not api.user_has_permission (current_user (req), "admin taxonomy") then + send_access_denied (req, res) + else + if attached {WSF_STRING} req.form_parameter ("op") as p_op then + if p_op.same_string ("New Vocabulary") then + if + attached {WSF_STRING} req.form_parameter ("vocabulary_name") as p_name and then + not p_name.is_empty + then + create voc.make (p_name.value) + taxonomy_api.save_vocabulary (voc) + create {GENERIC_VIEW_CMS_RESPONSE} l_page.make (req, res, api) + if taxonomy_api.has_error then + l_page.add_error_message ("Vocabulary creation failed!") + else + l_page.add_success_message ("Vocabulary creation succeed!") + l_page.set_redirection ("admin/taxonomy/vocabulary/" + voc.id.out) + end + else + create {BAD_REQUEST_ERROR_CMS_RESPONSE} l_page.make (req, res, api) + end + elseif + p_op.same_string ("Save changes") and then + attached {WSF_STRING} req.path_parameter ("vocid") as p_vocid and then p_vocid.is_integer and then + attached taxonomy_api.vocabulary (p_vocid.value.to_integer_64) as l_vocabulary + then + create {GENERIC_VIEW_CMS_RESPONSE} l_page.make (req, res, api) + create s.make_empty + l_page.add_notice_message ("Vocabulary " + l_vocabulary.id.out) + if attached {WSF_STRING} req.form_parameter ("name") as p_name then + l_vocabulary.set_name (p_name.value) + end + if attached {WSF_STRING} req.form_parameter ("description") as p_desc then + l_vocabulary.set_description (p_desc.value) + end + if attached {WSF_STRING} req.form_parameter ("weight") as p_weight and then p_weight.is_integer then + l_vocabulary.set_weight (p_weight.integer_value) + end + taxonomy_api.save_vocabulary (l_vocabulary) + if taxonomy_api.has_error then + l_page.add_error_message ("Could not save vocabulary") + elseif + attached {WSF_TABLE} req.form_parameter ("typenames") as typenames_table + then + across + typenames_table as ic + loop + l_typename := ic.item.string_representation + create voc.make_from_term (l_vocabulary) + voc.set_associated_content_type (l_typename, False, False, False) + l_page.add_notice_message ("Content type :" + api.html_encoded (l_typename)) + if attached {WSF_TABLE} req.form_parameter ({STRING_32} "vocabulary_" + l_typename.as_string_32) as opts then + across + opts as o_ic + loop + if o_ic.item.same_string ("tags") then + voc.set_is_tags (True) + elseif o_ic.item.same_string ("multiple") then + voc.allow_multiple_term (True) + elseif o_ic.item.same_string ("required") then + voc.set_is_term_required (True) + end + end + end + taxonomy_api.associate_vocabulary_with_type (voc, l_typename) + if taxonomy_api.has_error then + l_page.add_error_message ("Could not save vocabulary content type associations.") + end + end + end + if not taxonomy_api.has_error then + l_page.add_notice_message (l_page.link ({STRING_32} "Back to vocabulary %"" + l_vocabulary.name + "%"", "admin/taxonomy/vocabulary/" + l_vocabulary.id.out, Void)) + end + l_page.set_main_content (s) + else + create {NOT_IMPLEMENTED_ERROR_CMS_RESPONSE} l_page.make (req, res, api) + end + else + create {BAD_REQUEST_ERROR_CMS_RESPONSE} l_page.make (req, res, api) + end + l_page.execute + end + end + + do_get (req: WSF_REQUEST; res: WSF_RESPONSE) + -- + local + tid: INTEGER_64 + do + if not api.user_has_permission (current_user (req), "admin taxonomy") then + send_access_denied (req, res) + else + if attached {WSF_STRING} req.path_parameter ("vocid") as p_vocid then + if p_vocid.is_integer then + tid := p_vocid.value.to_integer_64 + end + end + if tid > 0 then + do_get_vocabulary (tid, req, res) + else + do_get_vocabularies (req, res) + end + end + end + + do_get_vocabulary (tid: INTEGER_64; req: WSF_REQUEST; res: WSF_RESPONSE) + -- + require + valid_tid: tid > 0 + local + l_page: CMS_RESPONSE + s: STRING + l_typename: detachable READABLE_STRING_8 + v: detachable CMS_VOCABULARY + l_typenames: detachable LIST [READABLE_STRING_32] + f: CMS_FORM + wtb: WSF_WIDGET_TABLE + wtb_row: WSF_WIDGET_TABLE_ROW + wtb_item: WSF_WIDGET_TABLE_ITEM + voc: detachable CMS_VOCABULARY + l_term: detachable CMS_TERM + tf_input: WSF_FORM_TEXT_INPUT + tf_text: WSF_FORM_TEXTAREA + tf_num: WSF_FORM_NUMBER_INPUT + w_set: WSF_FORM_FIELD_SET + w_cb: WSF_FORM_CHECKBOX_INPUT + sub: WSF_FORM_SUBMIT_INPUT + do + voc := taxonomy_api.vocabulary (tid) + if voc /= Void then + -- Responding with `main_content_html (l_page)'. + create {GENERIC_VIEW_CMS_RESPONSE} l_page.make (req, res, api) + l_page.set_title (voc.name) + taxonomy_api.fill_vocabularies_with_terms (voc) + + create f.make (req.percent_encoded_path_info, "taxonomy") + + create tf_input.make_with_text ("name", voc.name) + f.extend (tf_input) + + create tf_text.make ("description") + tf_text.set_text_value (voc.description) + tf_text.set_description ("Description of the vocabulary; also used as intructions to present to the user when selecting terms.") + tf_text.set_rows (3) + f.extend (tf_text) + + create tf_num.make_with_text ("weight", voc.weight.out) + tf_num.set_label ("weight") + tf_num.set_description ("Items are displayed in ascending order by weight.") + f.extend (tf_num) + + create wtb.make + wtb.add_css_class ("with_border") + create wtb_row.make (2) + create wtb_item.make_with_text ("Text") + wtb_row.set_item (wtb_item, 1) + create wtb_item.make_with_text ("Description") + wtb_row.set_item (wtb_item, 2) + wtb.add_head_row (wtb_row) + across + voc as ic + loop + l_term := ic.item + + create wtb_row.make (3) + wtb.add_row (wtb_row) + + create wtb_item.make_with_text (l_page.link (ic.item.text, "admin/taxonomy/term/" + l_term.id.out, Void)) + wtb_row.set_item (wtb_item, 1) + if attached ic.item.description as l_desc then + create wtb_item.make_with_text (api.html_encoded (l_desc)) + else + create wtb_item.make_with_text ("") + end + wtb_row.set_item (wtb_item, 2) + end + if wtb.body_row_count > 0 then + f.extend (wtb) + else + f.extend_raw_text ("No terms.") + end + + create w_set.make + w_set.set_legend ("Content types") + f.extend (w_set) + + + l_typenames := taxonomy_api.types_associated_with_vocabulary (voc) + create wtb.make + wtb.add_css_class ("with_border") + create wtb_row.make (5) + wtb_row.set_item (create {WSF_WIDGET_TABLE_ITEM}.make_with_text ("Type"), 1) + create wtb_item.make_with_text ("Settings ...") + wtb_item.add_html_attribute ("colspan", "3") + wtb_row.set_item (wtb_item, 2) + wtb.add_head_row (wtb_row) + + across + api.content_types as ic + loop + create wtb_row.make (4) + wtb.add_row (wtb_row) + + l_typename := ic.item.name + create w_cb.make_with_value ("typenames[]", api.html_encoded (l_typename)) + w_cb.set_title (ic.item.name) + wtb_row.set_item (create {WSF_WIDGET_TABLE_ITEM}.make_with_content (w_cb), 1) + + v := Void + if + l_typenames /= Void and then + across l_typenames as tn_ic some l_typename.is_case_insensitive_equal (tn_ic.item) end + then + w_cb.set_checked (True) + if attached taxonomy_api.vocabularies_for_type (l_typename) as v_list then + across v_list as v_ic until v /= Void loop + if v_ic.item.id = voc.id then + v := v_ic.item + end + end + end + end + create w_cb.make_with_value ("vocabulary_" + l_typename +"[]", "tags") + w_cb.set_title ("Tags") + w_cb.set_checked (v /= Void and then v.is_tags) + wtb_row.set_item (create {WSF_WIDGET_TABLE_ITEM}.make_with_content (w_cb), 2) + + create w_cb.make_with_value ("vocabulary_" + l_typename +"[]", "multiple") + w_cb.set_title ("Multiple Select") + w_cb.set_checked (v /= Void and then v.multiple_terms_allowed) + wtb_row.set_item (create {WSF_WIDGET_TABLE_ITEM}.make_with_content (w_cb), 3) + + create w_cb.make_with_value ("vocabulary_" + l_typename +"[]", "required") + w_cb.set_title ("Required") + w_cb.set_checked (v /= Void and then v.is_term_required) + wtb_row.set_item (create {WSF_WIDGET_TABLE_ITEM}.make_with_content (w_cb), 4) + end + if wtb.body_row_count > 0 then + w_set.extend (wtb) + end + + create sub.make_with_text ("op", "Save changes") + f.extend (sub) + + create s.make_empty + f.append_to_html (l_page.wsf_theme, s) + l_page.set_main_content (s) + else + -- Responding with `main_content_html (l_page)'. + create {NOT_FOUND_ERROR_CMS_RESPONSE} l_page.make (req, res, api) + end + l_page.execute + end + + do_get_vocabularies (req: WSF_REQUEST; res: WSF_RESPONSE) + -- + local + l_page: CMS_RESPONSE + s: STRING + l_typenames: detachable LIST [READABLE_STRING_32] + f: CMS_FORM + wtb: WSF_WIDGET_TABLE + wtb_row: WSF_WIDGET_TABLE_ROW + wtb_item: WSF_WIDGET_TABLE_ITEM + voc: detachable CMS_VOCABULARY + tf_input: WSF_FORM_TEXT_INPUT + w_set: WSF_FORM_FIELD_SET + sub: WSF_FORM_SUBMIT_INPUT + do + -- Responding with `main_content_html (l_page)'. + create {GENERIC_VIEW_CMS_RESPONSE} l_page.make (req, res, api) + create wtb.make + wtb.add_css_class ("with_border") + create wtb_row.make (3) + create wtb_item.make_with_text ("Name") + wtb_row.set_item (wtb_item, 1) + create wtb_item.make_with_text ("Type") + wtb_row.set_item (wtb_item, 2) + create wtb_item.make_with_text ("Operations") + wtb_row.set_item (wtb_item, 3) + wtb.add_head_row (wtb_row) + + if attached taxonomy_api.vocabularies (0, 0) as lst then + across + lst as ic + loop + voc := ic.item + create wtb_row.make (3) + wtb.add_row (wtb_row) + + create wtb_item.make_with_text (l_page.link (ic.item.name, "admin/taxonomy/vocabulary/" + ic.item.id.out, Void)) +-- if attached ic.item.description as l_desc then +-- s.append (" : ") +-- s.append (api.html_encoded (l_desc)) +-- s.append ("") +-- end + wtb_row.set_item (wtb_item, 1) + l_typenames := taxonomy_api.types_associated_with_vocabulary (voc) + if l_typenames /= Void then + create s.make_empty + across + l_typenames as types_ic + loop + if not s.is_empty then + s.append_character (',') + s.append_character (' ') + end + s.append (api.html_encoded (types_ic.item)) + end + create wtb_item.make_with_text (s) + wtb_row.set_item (wtb_item, 2) + end + + s := l_page.link ("edit", "admin/taxonomy/vocabulary/" + voc.id.out, Void) + create wtb_item.make_with_text (s) + wtb_row.set_item (wtb_item, 3) + end + end + create f.make (req.percent_encoded_path_info, "taxonomy") + f.set_method_post + + f.extend (wtb) + + create w_set.make + w_set.set_legend ("Create a new vocabulary") + create tf_input.make_with_text ("vocabulary_name", "") + tf_input.set_label ("Vocabulary name") + w_set.extend (tf_input) + create sub.make_with_text ("op", "New Vocabulary") + w_set.extend (sub) + f.extend (w_set) + + create s.make_empty + f.append_to_html (l_page.wsf_theme, s) + l_page.set_title ("Vocabularies") + l_page.set_main_content (s) + l_page.execute + end + +end diff --git a/modules/taxonomy/handler/taxonomy_vocabulary_handler.e b/modules/taxonomy/handler/taxonomy_vocabulary_handler.e new file mode 100644 index 0000000..6db90f4 --- /dev/null +++ b/modules/taxonomy/handler/taxonomy_vocabulary_handler.e @@ -0,0 +1,129 @@ +note + description: "[ + Request handler related to + /taxonomy/vocabulary/ + /taxonomy/vocabulary/{vocid} + ]" + date: "$Date$" + revision: "$revision$" + +class + TAXONOMY_VOCABULARY_HANDLER + +inherit + CMS_MODULE_HANDLER [CMS_TAXONOMY_API] + rename + module_api as taxonomy_api + end + + WSF_URI_HANDLER + rename + execute as uri_execute, + new_mapping as new_uri_mapping + end + + WSF_URI_TEMPLATE_HANDLER + rename + execute as uri_template_execute, + new_mapping as new_uri_template_mapping + select + new_uri_template_mapping + end + + WSF_RESOURCE_HANDLER_HELPER + redefine + do_get + end + + REFACTORING_HELPER + + CMS_API_ACCESS + +create + make + +feature -- execute + + execute (req: WSF_REQUEST; res: WSF_RESPONSE) + -- Execute request handler for any kind of mapping. + do + execute_methods (req, res) + end + + uri_execute (req: WSF_REQUEST; res: WSF_RESPONSE) + -- Execute request handler for URI mapping. + do + execute (req, res) + end + + uri_template_execute (req: WSF_REQUEST; res: WSF_RESPONSE) + -- Execute request handler for URI-template mapping. + do + execute (req, res) + end + +feature -- HTTP Methods + + do_get (req: WSF_REQUEST; res: WSF_RESPONSE) + -- + local + l_page: CMS_RESPONSE + tid: INTEGER_64 + s: STRING + do + if attached {WSF_STRING} req.path_parameter ("vocid") as p_vocid then + if p_vocid.is_integer then + tid := p_vocid.value.to_integer_64 + end + end + if tid > 0 then + if attached taxonomy_api.vocabulary (tid) as voc then + -- Responding with `main_content_html (l_page)'. + create {GENERIC_VIEW_CMS_RESPONSE} l_page.make (req, res, api) + l_page.set_title (voc.name) + taxonomy_api.fill_vocabularies_with_terms (voc) + + create s.make_empty + s.append ("
    ") + across + voc as ic + loop + s.append ("
  • ") + s.append (l_page.link (ic.item.text, "taxonomy/term/" + ic.item.id.out, Void)) + if attached ic.item.description as l_desc then + s.append (" : ") + s.append (api.html_encoded (l_desc)) + s.append ("") + end + s.append ("
  • ") + end + s.append ("
") + l_page.set_main_content (s) + else + -- Responding with `main_content_html (l_page)'. + create {NOT_FOUND_ERROR_CMS_RESPONSE} l_page.make (req, res, api) + end + l_page.execute + else + -- Responding with `main_content_html (l_page)'. + create {GENERIC_VIEW_CMS_RESPONSE} l_page.make (req, res, api) + create s.make_empty + if attached taxonomy_api.vocabularies (0, 0) as lst then + s.append ("
    ") + across + lst as ic + loop + s.append ("
  • ") + s.append (l_page.link (ic.item.name, "taxonomy/vocabulary/" + ic.item.id.out, Void)) + s.append ("
  • ") + end + s.append ("
") + else + s.append ("No vocabulary!") + end + l_page.set_main_content (s) + l_page.execute + end + end + +end diff --git a/modules/taxonomy/persistence/cms_taxonomy_storage_i.e b/modules/taxonomy/persistence/cms_taxonomy_storage_i.e index a915ebf..cfd7132 100644 --- a/modules/taxonomy/persistence/cms_taxonomy_storage_i.e +++ b/modules/taxonomy/persistence/cms_taxonomy_storage_i.e @@ -41,6 +41,24 @@ feature -- Access deferred end + vocabularies_for_term (a_term: CMS_TERM): detachable CMS_VOCABULARY_COLLECTION + -- Vocabularies including `a_term'. + deferred + end + + is_term_inside_vocabulary (a_term: CMS_TERM; a_vocab: CMS_VOCABULARY): BOOLEAN + -- Is `a_term' inside `a_vocab' ? + require + valid_term: a_term.has_id + valid_vocabulary: a_vocab.has_id + deferred + end + + types_associated_with_vocabulary (a_vocab: CMS_VOCABULARY): detachable LIST [READABLE_STRING_32] + -- Type names associated with `a_vocab'. + deferred + end + terms_count: INTEGER_64 -- Number of terms. deferred @@ -89,20 +107,28 @@ feature -- Access feature -- Store - save_vocabulary (a_voc: CMS_VOCABULARY) - -- Insert or update vocabulary `a_voc'. + save_vocabulary (a_voc: CMS_VOCABULARY; a_with_terms: BOOLEAN) + -- Insert or update vocabulary `a_voc' + -- and also save {CMS_VOCABULARY}.terms if `a_with_terms' is True. deferred ensure not error_handler.has_error implies a_voc.has_id and then vocabulary (a_voc.id) /= Void end - save_term (t: CMS_TERM; voc: CMS_VOCABULARY) - -- Insert or update term `t' as part of vocabulary `voc'. + save_term (t: CMS_TERM; voc: detachable CMS_VOCABULARY) + -- Insert or update term `t' as part of vocabulary `voc' if set. deferred ensure not error_handler.has_error implies t.has_id and then term_by_id (t.id) /= Void end + remove_term_from_vocabulary (t: CMS_TERM; voc: CMS_VOCABULARY) + -- Remove term `t' from vocabulary `voc'. + require + t_has_id: t.has_id + deferred + end + associate_term_with_entity (a_term: CMS_TERM; a_type_name: READABLE_STRING_GENERAL; a_entity: READABLE_STRING_GENERAL) -- Associate term `a_term' with `(a_type_name, a_entity)'. require diff --git a/modules/taxonomy/persistence/cms_taxonomy_storage_null.e b/modules/taxonomy/persistence/cms_taxonomy_storage_null.e index 2e663cd..f8336b6 100644 --- a/modules/taxonomy/persistence/cms_taxonomy_storage_null.e +++ b/modules/taxonomy/persistence/cms_taxonomy_storage_null.e @@ -53,6 +53,21 @@ feature -- Access do end + vocabularies_for_term (a_term: CMS_TERM): detachable CMS_VOCABULARY_COLLECTION + -- + do + end + + is_term_inside_vocabulary (a_term: CMS_TERM; a_vocab: CMS_VOCABULARY): BOOLEAN + -- + do + end + + types_associated_with_vocabulary (a_vocab: CMS_VOCABULARY): detachable LIST [READABLE_STRING_32] + -- + do + end + terms_count: INTEGER_64 -- Number of terms. do @@ -85,18 +100,25 @@ feature -- Access feature -- Store - save_vocabulary (a_voc: CMS_VOCABULARY) - -- Insert or update vocabulary `a_voc'. + save_vocabulary (a_voc: CMS_VOCABULARY; a_with_terms: BOOLEAN) + -- Insert or update vocabulary `a_voc' + -- and also save {CMS_VOCABULARY}.terms if `a_with_terms' is True. do error_handler.add_custom_error (-1, "not implemented", "save_vocabulary") end - save_term (t: CMS_TERM; voc: CMS_VOCABULARY) + save_term (t: CMS_TERM; voc: detachable CMS_VOCABULARY) -- do error_handler.add_custom_error (-1, "not implemented", "save_term") end + remove_term_from_vocabulary (t: CMS_TERM; voc: CMS_VOCABULARY) + -- Remove term `t' from vocabulary `voc'. + do + error_handler.add_custom_error (-1, "not implemented", "remove_term_from_vocabulary") + end + associate_term_with_entity (a_term: CMS_TERM; a_type_name: READABLE_STRING_GENERAL; a_entity: READABLE_STRING_GENERAL) do error_handler.add_custom_error (-1, "not implemented", "associate_term_with_entity") diff --git a/modules/taxonomy/persistence/cms_taxonomy_storage_sql.e b/modules/taxonomy/persistence/cms_taxonomy_storage_sql.e index 7bb850b..15cad57 100644 --- a/modules/taxonomy/persistence/cms_taxonomy_storage_sql.e +++ b/modules/taxonomy/persistence/cms_taxonomy_storage_sql.e @@ -232,21 +232,41 @@ feature -- Access feature -- Store - save_vocabulary (voc: CMS_VOCABULARY) + save_vocabulary (voc: CMS_VOCABULARY; a_with_terms: BOOLEAN) + local + l_terms: CMS_TERM_COLLECTION do save_term (voc, create {CMS_VOCABULARY}.make_none) - across - voc.terms as ic - until - has_error - loop - save_term (ic.item, voc) + + if a_with_terms then + l_terms := terms (voc, 0, 0) + across + voc.terms as ic + until + has_error + loop + if attached l_terms.term_by_id (ic.item.id) as t then + -- Already contained. + -- Remove from `l_terms' to leave only terms to remove. + l_terms.remove (t) + else + save_term (ic.item, voc) + end + end + across + l_terms as ic + until + has_error + loop + remove_term_from_vocabulary (ic.item, voc) + end end end - save_term (t: CMS_TERM; voc: CMS_VOCABULARY) + save_term (t: CMS_TERM; voc: detachable CMS_VOCABULARY) local l_parameters: STRING_TABLE [detachable ANY] + l_insert_voc: BOOLEAN do error_handler.reset @@ -255,6 +275,8 @@ feature -- Store l_parameters.put (t.description, "description") l_parameters.put (t.weight, "weight") + l_insert_voc := voc /= Void and then is_term_inside_vocabulary (t, voc) + sql_begin_transaction if t.has_id then l_parameters.put (t.id, "tid") @@ -263,9 +285,18 @@ feature -- Store sql_insert (sql_insert_term, l_parameters) t.set_id (last_inserted_term_id) end - if not has_error then + if + not has_error and + voc /= Void and then + not l_insert_voc + then + l_parameters.wipe_out l_parameters.put (t.id, "tid") - l_parameters.put (voc.id, "parent_tid") + if voc.has_id then + l_parameters.put (voc.id, "parent_tid") + else + l_parameters.put (0, "parent_tid") + end sql_insert (sql_insert_term_in_vocabulary, l_parameters) end if has_error then @@ -276,6 +307,19 @@ feature -- Store sql_finalize end + remove_term_from_vocabulary (t: CMS_TERM; voc: CMS_VOCABULARY) + local + l_parameters: STRING_TABLE [detachable ANY] + do + error_handler.reset + + create l_parameters.make (2) + l_parameters.put (t.id, "tid") + l_parameters.put (voc.id, "parent_tid") + sql_modify (sql_remove_term_from_vocabulary, l_parameters) + sql_finalize + end + associate_term_with_entity (a_term: CMS_TERM; a_type_name: READABLE_STRING_GENERAL; a_entity: READABLE_STRING_GENERAL) -- Associate term `a_term' with `(a_type_name, a_entity)'. local @@ -376,6 +420,90 @@ feature -- Vocabulary and types end end + is_term_inside_vocabulary (a_term: CMS_TERM; a_voc: CMS_VOCABULARY): BOOLEAN + local + l_parameters: STRING_TABLE [detachable ANY] + do + error_handler.reset + + create l_parameters.make (2) + l_parameters.put (a_term.id, "tid") + l_parameters.put (a_voc.id, "parent_tid") + sql_query (sql_select_term_in_vocabulary, l_parameters) + sql_start + if not has_error or sql_after then + Result := sql_read_integer_64 (1) > 0 + end + sql_finalize + end + + vocabularies_for_term (a_term: CMS_TERM): detachable CMS_VOCABULARY_COLLECTION + -- + local + voc: detachable CMS_VOCABULARY + l_parameters: STRING_TABLE [detachable ANY] + l_parent_id: INTEGER_64 + l_ids: ARRAYED_LIST [INTEGER_64] + do + error_handler.reset + + create l_parameters.make (3) + l_parameters.put (a_term.id, "tid") + sql_query (sql_select_vocabularies_for_term, l_parameters) + + create l_ids.make (1) + from + sql_start + until + sql_after or has_error + loop + l_parent_id := sql_read_integer_64 (1) + l_ids.force (l_parent_id) + sql_forth + end + sql_finalize + + if l_ids.count > 0 then + create Result.make (1) + across + l_ids as ic + loop + voc := vocabulary (ic.item) + if voc /= Void then + Result.force (voc) + end + end + if Result.count = 0 then + Result := Void + end + end + end + + types_associated_with_vocabulary (a_vocab: CMS_VOCABULARY): detachable LIST [READABLE_STRING_32] + -- Type names associated with `a_vocab'. + local + l_parameters: STRING_TABLE [detachable ANY] + do + error_handler.reset + + create l_parameters.make (1) + l_parameters.put (a_vocab.id, "tid") + sql_query (sql_select_type_associated_with_vocabulary, l_parameters) + + create {ARRAYED_LIST [READABLE_STRING_32]} Result.make (3) + from + sql_start + until + sql_after or has_error + loop + if attached sql_read_string_32 (1) as l_typename then + Result.force (l_typename) + end + sql_forth + end + sql_finalize + end + associate_vocabulary_with_type (a_voc: CMS_VOCABULARY; a_type_name: READABLE_STRING_GENERAL) -- local @@ -396,10 +524,17 @@ feature -- Vocabulary and types i := i | mask_is_required end l_parameters.put ((- i).out, "entity") - l_parameters.put (a_type_name, "type") - sql_insert (sql_insert_term_index, l_parameters) + if + attached vocabularies_for_type (a_type_name) as lst and then + across lst as ic some a_voc.id = ic.item.id end + then + sql_modify (sql_update_term_index, l_parameters) + else + sql_insert (sql_insert_term_index, l_parameters) + end + sql_finalize end @@ -464,6 +599,20 @@ feature {NONE} -- Queries ]" -- Terms under :parent_tid. + sql_select_vocabularies_for_term: STRING = "[ + SELECT parent + FROM taxonomy_hierarchy + WHERE tid = :tid + ; + ]" + + sql_select_term_in_vocabulary: STRING = "[ + SELECT count(*) + FROM taxonomy_hierarchy + WHERE tid = :tid and parent = :parent_tid + ; + ]" + sql_select_terms_with_range: STRING = "[ SELECT taxonomy_term.tid, taxonomy_term.text, taxonomy_term.weight, taxonomy_term.description FROM taxonomy_term INNER JOIN taxonomy_hierarchy ON taxonomy_term.tid = taxonomy_hierarchy.tid @@ -505,6 +654,10 @@ feature {NONE} -- Queries VALUES (:tid, :parent_tid); ]" + sql_remove_term_from_vocabulary: STRING = "[ + DELETE FROM taxonomy_hierarchy WHERE tid=:tid AND parent=:parent_tid; + ]" + sql_select_terms_of_entity: STRING = "[ SELECT tid FROM taxonomy_index WHERE type=:type AND entity=:entity; ]" @@ -527,6 +680,19 @@ feature {NONE} -- Queries WHERE type=:type AND entity <= 0; ]" + sql_select_type_associated_with_vocabulary: STRING = "[ + SELECT type + FROM taxonomy_index + WHERE tid=:tid AND entity <= 0; + ]" + + sql_update_term_index: STRING = "[ + UPDATE taxonomy_index + SET entity=:entity + WHERE tid=:tid and type=:type + ; + ]" + sql_insert_term_index: STRING = "[ INSERT INTO taxonomy_index (tid, entity, type) VALUES (:tid, :entity, :type); diff --git a/modules/taxonomy/site/files/css/taxonomy.css b/modules/taxonomy/site/files/css/taxonomy.css index 41c87fa..dd216d0 100644 --- a/modules/taxonomy/site/files/css/taxonomy.css +++ b/modules/taxonomy/site/files/css/taxonomy.css @@ -19,3 +19,8 @@ ul.taxonomy li:hover { border-bottom: solid 1px #66f; background-color: #ddf; } + +table.taxonomy td { + border: solid 1px #ccc; + padding: 2px; +} diff --git a/modules/taxonomy/site/files/scss/taxonomy.scss b/modules/taxonomy/site/files/scss/taxonomy.scss index f409395..2ceff02 100644 --- a/modules/taxonomy/site/files/scss/taxonomy.scss +++ b/modules/taxonomy/site/files/scss/taxonomy.scss @@ -19,3 +19,9 @@ ul.taxonomy { } } } +table.taxonomy { + td { + border: solid 1px #ccc; + padding: 2px; + } +} diff --git a/src/service/cms_api.e b/src/service/cms_api.e index 78a6252..ce43c95 100644 --- a/src/service/cms_api.e +++ b/src/service/cms_api.e @@ -15,7 +15,7 @@ inherit REFACTORING_HELPER - CMS_ENCODERS + CMS_REQUEST_UTIL create make @@ -292,6 +292,26 @@ feature -- Logging end end +feature -- Internationalization (i18n) + + translation (a_text: READABLE_STRING_GENERAL; opts: detachable CMS_API_OPTIONS): STRING_32 + -- Translated text `a_text' according to expected context (lang, ...) + -- and adapt according to options eventually set by `opts'. + do + to_implement ("Implement i18n support [2015-may]") + Result := a_text.as_string_32 + end + + formatted_string (a_text: READABLE_STRING_GENERAL; args: TUPLE): STRING_32 + -- Format `a_text' using arguments `args'. + --| ex: formatted_string ("hello $1, see page $title.", ["bob", "contact"] -> "hello bob, see page contact" + local + l_formatter: CMS_STRING_FORMATTER + do + create l_formatter + Result := l_formatter.formatted_string (a_text, args) + end + feature -- Emails new_email (a_to_address: READABLE_STRING_8; a_subject: READABLE_STRING_8; a_content: READABLE_STRING_8): CMS_EMAIL @@ -407,7 +427,7 @@ feature {NONE} -- Hooks l_hooks: like hooks do l_hooks := hooks - register_hooks (l_hooks) + setup_core_hooks (l_hooks) across enabled_modules as ic loop @@ -436,7 +456,7 @@ feature -- Query: API feature -- Hooks - register_hooks (a_hooks: CMS_HOOK_CORE_MANAGER) + setup_core_hooks (a_hooks: CMS_HOOK_CORE_MANAGER) -- Register hooks associated with the cms core. do a_hooks.subscribe_to_export_hook (Current) diff --git a/src/service/content/cms_content.e b/src/service/content/cms_content.e index e6c9444..62acafb 100644 --- a/src/service/content/cms_content.e +++ b/src/service/content/cms_content.e @@ -13,6 +13,11 @@ inherit feature -- Access + identifier: detachable READABLE_STRING_32 + -- Optional identifier. + deferred + end + title: detachable READABLE_STRING_32 -- Title associated with Current content. deferred @@ -37,6 +42,14 @@ feature -- Access feature -- Status report + has_identifier: BOOLEAN + -- Current content has identifier? + do + Result := identifier /= Void + ensure + Result implies identifier /= Void + end + is_typed_as (a_content_type: READABLE_STRING_GENERAL): BOOLEAN -- Is current node of type `a_content_type' ? do diff --git a/src/service/content/cms_partial_content.e b/src/service/content/cms_partial_content.e index 8b114b0..4c37ea1 100644 --- a/src/service/content/cms_partial_content.e +++ b/src/service/content/cms_partial_content.e @@ -26,6 +26,9 @@ feature {NONE} -- Initialization feature -- Access + identifier: detachable READABLE_STRING_32 + -- + title: detachable READABLE_STRING_32 -- Title associated with Current content. @@ -42,6 +45,15 @@ feature -- Access feature -- Element change + set_identifier (a_identifier: detachable READABLE_STRING_GENERAL) + do + if a_identifier = Void then + identifier := Void + else + create {IMMUTABLE_STRING_32} identifier.make_from_string_general (a_identifier) + end + end + set_title (a_title: detachable READABLE_STRING_GENERAL) do if a_title = Void then diff --git a/src/service/response/cms_response.e b/src/service/response/cms_response.e index 8746d00..751075c 100644 --- a/src/service/response/cms_response.e +++ b/src/service/response/cms_response.e @@ -72,7 +72,6 @@ feature {NONE} -- Initialization l_module: CMS_MODULE l_enabled_modules: CMS_MODULE_COLLECTION do - api.register_hooks (hooks) l_enabled_modules := api.enabled_modules across l_enabled_modules as ic @@ -114,26 +113,6 @@ feature -- Access end end -feature -- Internationalization (i18n) - - translation (a_text: READABLE_STRING_GENERAL; opts: detachable CMS_API_OPTIONS): STRING_32 - -- Translated text `a_text' according to expected context (lang, ...) - -- and adapt according to options eventually set by `opts'. - do - to_implement ("Implement i18n support [2015-may]") - Result := a_text.as_string_32 - end - - formatted_string (a_text: READABLE_STRING_GENERAL; args: TUPLE): STRING_32 - -- Format `a_text' using arguments `args'. - --| ex: formatted_string ("hello $1, see page $title.", ["bob", "contact"] -> "hello bob, see page contact" - local - l_formatter: CMS_STRING_FORMATTER - do - create l_formatter - Result := l_formatter.formatted_string (a_text, args) - end - feature -- API api: CMS_API @@ -897,6 +876,22 @@ feature -- Menu: change m.extend (lnk) end +feature -- Internationalization (i18n) + + translation (a_text: READABLE_STRING_GENERAL; opts: detachable CMS_API_OPTIONS): STRING_32 + -- Translated text `a_text' according to expected context (lang, ...) + -- and adapt according to options eventually set by `opts'. + do + Result := api.translation (a_text, opts) + end + + formatted_string (a_text: READABLE_STRING_GENERAL; args: TUPLE): STRING_32 + -- Format `a_text' using arguments `args'. + --| ex: formatted_string ("hello $1, see page $title.", ["bob", "contact"] -> "hello bob, see page contact" + do + Result := api.formatted_string (a_text, args) + end + feature -- Message add_message (a_msg: READABLE_STRING_8; a_category: detachable READABLE_STRING_8) @@ -984,6 +979,26 @@ feature -- Theme end end +feature -- Theme helpers + + wsf_theme: WSF_THEME + -- WSF Theme from CMS `theme' for Current response. + local + t: like internal_wsf_theme + do + t := internal_wsf_theme + if t = Void then + create {CMS_TO_WSF_THEME} t.make (Current, theme) + internal_wsf_theme := t + end + Result := t + end + +feature {NONE} -- Theme helpers + + internal_wsf_theme: detachable WSF_THEME + -- Once per object for `wsf_theme'. + feature -- Element Change set_status_code (a_status: INTEGER)