From ce8de442e9db90fa4c0cf81436878bc953f0b6a3 Mon Sep 17 00:00:00 2001 From: Jocelyn Fiat Date: Thu, 10 Dec 2015 11:21:20 +0100 Subject: [PATCH] Implemented taxonomy administration pages - create term, vocabulary, add or remove term from vocabularies, ... Fixed content editing related to taxonomy (especially with multiple terms vs tags). Fixed various SQL storage issue related to taxonomy and vocabularies. Added CMS_RESPONSE.wsf_theme as helper. --- .../modules/taxonomy/files/css/taxonomy.css | 5 + .../modules/taxonomy/files/scss/taxonomy.scss | 6 + .../themes/bootstrap/assets/css/style.css | 13 +- .../themes/bootstrap/assets/scss/style.scss | 15 +- .../admin/handler/cms_admin_cache_handler.e | 4 +- .../admin/handler/cms_admin_export_handler.e | 4 +- .../admin/handler/cms_admin_modules_handler.e | 4 +- modules/admin/handler/cms_admin_response.e | 20 - .../handler/role/cms_role_form_response.e | 20 - .../handler/role/cms_role_view_response.e | 21 - .../handler/user/cms_user_form_response.e | 21 - .../handler/user/cms_user_view_response.e | 21 - .../handler/cms_node_type_webform_manager.e | 122 +++--- modules/node/handler/node_form_response.e | 20 - modules/node/handler/node_view_response.e | 20 - .../cms_recent_changes_module.e | 2 +- modules/taxonomy/cms_taxonomy_api.e | 47 +- modules/taxonomy/cms_taxonomy_module.e | 50 ++- modules/taxonomy/cms_term_collection.e | 16 + modules/taxonomy/cms_vocabulary_collection.e | 16 + .../handler/taxonomy_term_admin_handler.e | 269 ++++++++++++ .../taxonomy_vocabulary_admin_handler.e | 414 ++++++++++++++++++ .../handler/taxonomy_vocabulary_handler.e | 129 ++++++ .../persistence/cms_taxonomy_storage_i.e | 34 +- .../persistence/cms_taxonomy_storage_null.e | 28 +- .../persistence/cms_taxonomy_storage_sql.e | 190 +++++++- modules/taxonomy/site/files/css/taxonomy.css | 5 + .../taxonomy/site/files/scss/taxonomy.scss | 6 + src/service/cms_api.e | 6 +- src/service/response/cms_response.e | 21 +- 30 files changed, 1302 insertions(+), 247 deletions(-) create mode 100644 modules/taxonomy/handler/taxonomy_term_admin_handler.e create mode 100644 modules/taxonomy/handler/taxonomy_vocabulary_admin_handler.e create mode 100644 modules/taxonomy/handler/taxonomy_vocabulary_handler.e 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/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/node/handler/cms_node_type_webform_manager.e b/modules/node/handler/cms_node_type_webform_manager.e index 453a272..6e5ae7b 100644 --- a/modules/node/handler/cms_node_type_webform_manager.e +++ b/modules/node/handler/cms_node_type_webform_manager.e @@ -100,6 +100,7 @@ feature -- Forms ... populate_form_with_taxonomy (response: NODE_RESPONSE; f: CMS_FORM; a_node: detachable CMS_NODE) 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 @@ -126,6 +127,9 @@ feature -- Forms ... 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_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) @@ -139,7 +143,7 @@ feature -- Forms ... if voc.is_tags then w_voc_set.set_legend (response.translation (voc.name, Void)) - create ti.make ({STRING_32} "taxonomy_terms[" + voc.name + "]") + create ti.make ({STRING_32} "taxonomy_" + voc.id.out) w_voc_set.extend (ti) if voc.is_term_required then ti.enable_required @@ -186,7 +190,8 @@ feature -- Forms ... 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) + 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) @@ -196,7 +201,7 @@ feature -- Forms ... end end else - create w_select.make ({STRING_32} "taxonomy_terms[" + voc.name + "]") + create w_select.make ({STRING_32} "taxonomy_" + voc.id.out) w_voc_set.extend (w_select) if attached voc.description as l_desc then @@ -248,68 +253,81 @@ feature -- Forms ... l_text: READABLE_STRING_GENERAL l_found: BOOLEAN t: detachable CMS_TERM + vid: INTEGER_64 do if a_node /= Void and then a_node.has_id and then - attached fd.table_item ("taxonomy_terms") as fd_terms + attached fd.table_item ("taxonomy_vocabularies") as fd_vocs 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 + if a_response.has_permissions (<<{STRING_32} "update any taxonomy", {STRING_32} "update " + content_type.name + " 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_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 - 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) + 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 - 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) + if not l_found then + -- Remove term + l_terms_to_remove.force (t_ic.item) end end across - l_new_terms as t_ic + l_terms_to_remove 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) + 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 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/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/taxonomy/cms_taxonomy_api.e b/modules/taxonomy/cms_taxonomy_api.e index 69534d8..677dbdf 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 @@ -125,19 +146,39 @@ 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_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 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..d3a6e4e 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 @@ -407,7 +407,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 +436,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/response/cms_response.e b/src/service/response/cms_response.e index 8746d00..ec7daeb 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 @@ -984,6 +983,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)