diff --git a/examples/demo/demo-safe.ecf b/examples/demo/demo-safe.ecf index d1cf5a9..b8c67db 100644 --- a/examples/demo/demo-safe.ecf +++ b/examples/demo/demo-safe.ecf @@ -29,16 +29,16 @@ + - - + diff --git a/examples/demo/install_modules.bat b/examples/demo/install_modules.bat index c3def68..98de61d 100644 --- a/examples/demo/install_modules.bat +++ b/examples/demo/install_modules.bat @@ -12,3 +12,4 @@ set ROC_CMS_DIR=%~dp0 %ROC_CMD% install --module ..\..\modules\recent_changes --dir %ROC_CMS_DIR% %ROC_CMD% install --module ..\..\modules\feed_aggregator --dir %ROC_CMS_DIR% %ROC_CMD% install --module ..\..\modules\google_search --dir %ROC_CMS_DIR% +%ROC_CMD% install --module ..\..\modules\taxonomy --dir %ROC_CMS_DIR% diff --git a/examples/demo/site/modules/taxonomy/files/css/taxonomy.css b/examples/demo/site/modules/taxonomy/files/css/taxonomy.css new file mode 100644 index 0000000..41c87fa --- /dev/null +++ b/examples/demo/site/modules/taxonomy/files/css/taxonomy.css @@ -0,0 +1,21 @@ +ul.taxonomy { + font-size: 80%; + list-style-type: none; + font-style: italic; + margin: 0; +} +ul.taxonomy li { + padding: 2px; + margin-right: 3px; + display: inline-block; + border: none; +} +ul.taxonomy li a:hover { + text-decoration: none; +} +ul.taxonomy li:hover { + padding: 1px; + border-top: solid 1px #66f; + border-bottom: solid 1px #66f; + background-color: #ddf; +} diff --git a/examples/demo/site/modules/taxonomy/files/scss/taxonomy.scss b/examples/demo/site/modules/taxonomy/files/scss/taxonomy.scss new file mode 100644 index 0000000..f409395 --- /dev/null +++ b/examples/demo/site/modules/taxonomy/files/scss/taxonomy.scss @@ -0,0 +1,21 @@ +ul.taxonomy { + font-size: 80%; + list-style-type: none; + font-style: italic; + margin: 0; + li { + a:hover { + text-decoration: none; + } + padding: 2px; + margin-right: 3px; + display: inline-block; + border: none; + &:hover { + padding: 1px; + border-top: solid 1px #66f; + border-bottom: solid 1px #66f; + background-color: #ddf; + } + } +} diff --git a/examples/demo/site/modules/taxonomy/scripts/install.sql b/examples/demo/site/modules/taxonomy/scripts/install.sql new file mode 100644 index 0000000..2eaf9a2 --- /dev/null +++ b/examples/demo/site/modules/taxonomy/scripts/install.sql @@ -0,0 +1,24 @@ + +CREATE TABLE taxonomy_term ( + `tid` INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT UNIQUE, + `text` VARCHAR(255) NOT NULL, + `weight` INTEGER, + `description` TEXT, + `langcode` VARCHAR(12) +); + +CREATE TABLE taxonomy_hierarchy ( + `tid` INTEGER NOT NULL, + `parent` INTEGER, + CONSTRAINT PK_tid_parent PRIMARY KEY (tid,parent) +); + +/* Associate tid with unique (type,entity) + * for instance: "page" + "$nid" -> "tid" + */ +CREATE TABLE taxonomy_index ( + `tid` INTEGER NOT NULL, + `entity` VARCHAR(255), + `type` VARCHAR(255) NOT NULL, + CONSTRAINT PK_tid_entity_type PRIMARY KEY (tid,entity,type) +); diff --git a/examples/demo/site/modules/taxonomy/scripts/uninstall.sql b/examples/demo/site/modules/taxonomy/scripts/uninstall.sql new file mode 100644 index 0000000..0efae92 --- /dev/null +++ b/examples/demo/site/modules/taxonomy/scripts/uninstall.sql @@ -0,0 +1,3 @@ +DROP TABLE IF EXISTS taxonomy_term; +DROP TABLE IF EXISTS taxonomy_hierarchy; +DROP TABLE IF EXISTS taxonomy_index; diff --git a/examples/demo/src/demo_cms_execution.e b/examples/demo/src/demo_cms_execution.e index bf3e1ae..a3286d9 100644 --- a/examples/demo/src/demo_cms_execution.e +++ b/examples/demo/src/demo_cms_execution.e @@ -66,6 +66,10 @@ feature -- CMS modules a_setup.register_module (m) create {CMS_BLOG_MODULE} m.make + a_setup.register_module (m) + + -- Taxonomy + create {CMS_TAXONOMY_MODULE} m.make a_setup.register_module (m) -- Recent changes diff --git a/modules/admin/cms_admin_module.e b/modules/admin/cms_admin_module.e index 8d83cc9..263e0eb 100644 --- a/modules/admin/cms_admin_module.e +++ b/modules/admin/cms_admin_module.e @@ -47,6 +47,8 @@ feature -- Access: router configure_web (a_api: CMS_API; a_router: WSF_ROUTER) local l_admin_handler: CMS_ADMIN_HANDLER + + l_modules_handler: CMS_ADMIN_MODULES_HANDLER l_users_handler: CMS_ADMIN_USERS_HANDLER l_roles_handler: CMS_ADMIN_ROLES_HANDLER @@ -62,6 +64,10 @@ feature -- Access: router create l_uri_mapping.make_trailing_slash_ignored ("/admin", l_admin_handler) a_router.map (l_uri_mapping, a_router.methods_get_post) + create l_modules_handler.make (a_api) + create l_uri_mapping.make_trailing_slash_ignored ("/admin/modules", l_modules_handler) + a_router.map (l_uri_mapping, a_router.methods_get_post) + create l_users_handler.make (a_api) create l_uri_mapping.make_trailing_slash_ignored ("/admin/users", l_users_handler) a_router.map (l_uri_mapping, a_router.methods_get_post) @@ -134,7 +140,13 @@ feature -- Hooks create lnk.make ("Admin", "admin") lnk.set_permission_arguments (<<"manage " + {CMS_ADMIN_MODULE}.name>>) a_menu_system.management_menu.extend (lnk) + end + + create lnk.make ("Module", "admin/modules") + lnk.set_permission_arguments (<<"manage module">>) + a_menu_system.management_menu.extend (lnk) + -- Per module cache permission! create lnk.make ("Cache", "admin/cache") a_menu_system.management_menu.extend (lnk) diff --git a/modules/admin/handler/cms_admin_modules_handler.e b/modules/admin/handler/cms_admin_modules_handler.e new file mode 100644 index 0000000..53ffb20 --- /dev/null +++ b/modules/admin/handler/cms_admin_modules_handler.e @@ -0,0 +1,320 @@ +note + description: "[ + Administrate modules. + ]" + date: "$Date$" + revision: "$Revision$" + +class + CMS_ADMIN_MODULES_HANDLER + +inherit + CMS_HANDLER + + WSF_URI_HANDLER + rename + new_mapping as new_uri_mapping + end + + WSF_RESOURCE_HANDLER_HELPER + redefine + do_get, do_post + end + + REFACTORING_HELPER + + CMS_SETUP_ACCESS + + CMS_ACCESS + +create + make + +feature -- Execution + + execute (req: WSF_REQUEST; res: WSF_RESPONSE) + -- Execute request handler + do + execute_methods (req, res) + end + + do_get (req: WSF_REQUEST; res: WSF_RESPONSE) + local + r: CMS_RESPONSE + s: STRING + f: CMS_FORM + l_denied: BOOLEAN + do + if + attached {WSF_STRING} req.query_parameter ("op") as l_op and then l_op.same_string ("uninstall") and then + attached {WSF_TABLE} req.query_parameter ("module_uninstallation") as tb + then + create {GENERIC_VIEW_CMS_RESPONSE} r.make (req, res, api) + if attached api.setup.string_8_item ("admin.installation_access") as l_access then + if l_access.is_case_insensitive_equal ("none") then + l_denied := True + elseif l_access.is_case_insensitive_equal ("permission") then + l_denied := not r.has_permission ("install modules") + end + else + l_denied := True + end + if l_denied then + create {FORBIDDEN_ERROR_CMS_RESPONSE} r.make (req, res, api) + r.set_main_content ("You do not have permission to access CMS module uninstallation procedure!") + else + create s.make_empty + across + tb as ic + loop + if attached api.setup.modules.item_by_name (ic.item.string_representation) as l_module then + if api.is_module_installed (l_module) then + api.uninstall_module (l_module) + if api.is_module_installed (l_module) then + s.append ("

ERROR: Module " + l_module.name + " failed to be uninstalled!

") + else + s.append ("

Module " + l_module.name + " was successfully uninstalled.

") + end + else + s.append ("

Module " + l_module.name + " is not installed.

") + end + end + end + s.append (r.link ("Back to modules management", r.location, Void)) + r.set_main_content (s) + end + r.execute + else + 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) + r.set_page_title ("Modules") + r.set_main_content (s) + r.execute + end + end + + do_post (req: WSF_REQUEST; res: WSF_RESPONSE) + local + r: CMS_RESPONSE + s: STRING + f: CMS_FORM + l_denied: BOOLEAN + do + if attached {WSF_STRING} req.item ("op") as l_op then + if l_op.same_string ("Install modules") then + create {GENERIC_VIEW_CMS_RESPONSE} r.make (req, res, api) + + if attached api.setup.string_8_item ("admin.installation_access") as l_access then + if l_access.is_case_insensitive_equal ("none") then + l_denied := True + elseif l_access.is_case_insensitive_equal ("permission") then + l_denied := not r.has_permission ("install modules") + end + else + l_denied := True + end + if l_denied then + create {FORBIDDEN_ERROR_CMS_RESPONSE} r.make (req, res, api) + r.set_main_content ("You do not have permission to access CMS module installation procedure!") + else + f := modules_collection_web_form (r) + if l_op.same_string ("Install modules") then + f.submit_actions.extend (agent on_installation_submit) + f.process (r) + elseif l_op.same_string ("uninstall") then + f.submit_actions.extend (agent on_uninstallation_submit) + f.process (r) + end + if + not attached f.last_data as l_data or else + not l_data.is_valid + 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) + r.set_page_title ("Modules") + r.set_main_content (s) + else + r.add_notice_message ("Operation on module(s) succeeded.") + r.set_redirection (r.location) + end + end + r.execute + else + create {BAD_REQUEST_ERROR_CMS_RESPONSE} r.make (req, res, api) + r.execute + end + else + do_get (req, res) + end + end + + modules_collection_web_form (a_response: CMS_RESPONSE): CMS_FORM + local + mod: CMS_MODULE + f_cb: WSF_FORM_CHECKBOX_INPUT + w_tb: WSF_WIDGET_TABLE + w_row: WSF_WIDGET_TABLE_ROW + w_item: WSF_WIDGET_TABLE_ITEM + w_submit: WSF_FORM_SUBMIT_INPUT + w_set: WSF_FORM_FIELD_SET + + l_mods_to_install: ARRAYED_LIST [CMS_MODULE] + do + create Result.make (a_response.url (a_response.location, Void), "modules_collection") + create w_tb.make + w_tb.add_css_class ("modules_table") + create w_row.make (5) + create w_item.make_with_text ("Enabled ") + w_row.add_item (w_item) + create w_item.make_with_text ("Module") + w_row.add_item (w_item) + create w_item.make_with_text ("Version") + w_row.add_item (w_item) + create w_item.make_with_text ("Description") + w_row.add_item (w_item) + w_tb.add_head_row (w_row) + + create l_mods_to_install.make (0) + across + a_response.api.setup.modules as ic + loop + mod := ic.item + if not a_response.api.is_module_installed (mod) then + l_mods_to_install.extend (mod) + else + create w_row.make (5) + create f_cb.make ("module_" + mod.name) + f_cb.set_text_value (mod.name) + f_cb.set_checked (mod.is_enabled) + f_cb.set_is_readonly (True) + create w_item.make_with_content (f_cb) + w_row.add_item (w_item) + + create w_item.make_with_text (mod.name) + w_row.add_item (w_item) + + create w_item.make_with_text (mod.version) + w_row.add_item (w_item) + + if attached mod.description as l_desc then + create w_item.make_with_text (l_desc) + w_row.add_item (w_item) + else + create w_item.make_with_text ("") + w_row.add_item (w_item) + end + create w_item.make_with_text (a_response.link ("Uninstall", a_response.location + "?op=uninstall&module_uninstallation[]=" + mod.name, Void)) + w_row.add_item (w_item) + + w_tb.add_row (w_row) + end + end + create w_set.make + w_set.set_legend ("Installed modules") + w_set.extend (w_tb) +-- create w_submit.make ("op") +-- w_submit.set_text_value ("Save") +-- w_set.extend (w_submit) + Result.extend (w_set) + + Result.extend_html_text ("
") + + if not l_mods_to_install.is_empty then + create w_tb.make + w_tb.add_css_class ("modules_table") + create w_row.make (3) + create w_item.make_with_text ("Install ") + w_row.add_item (w_item) + create w_item.make_with_text ("Module") + w_row.add_item (w_item) + create w_item.make_with_text ("Description") + w_row.add_item (w_item) + w_tb.add_head_row (w_row) + across + l_mods_to_install as ic + loop + mod := ic.item + create w_row.make (3) + create f_cb.make ("module_installation[" + mod.name + "]") + f_cb.set_text_value (mod.name) + create w_item.make_with_content (f_cb) + w_row.add_item (w_item) + + create w_item.make_with_text (mod.name) + w_row.add_item (w_item) + + if attached mod.description as l_desc then + create w_item.make_with_text (l_desc) + w_row.add_item (w_item) + else + create w_item.make_with_text ("") + w_row.add_item (w_item) + end + w_tb.add_row (w_row) + end + create w_set.make + w_set.set_legend ("Available modules for installation") + w_set.extend (w_tb) + create w_submit.make ("op") + w_submit.set_text_value ("Install modules") + w_set.extend (w_submit) + Result.extend (w_set) + end + end + + on_installation_submit (fd: WSF_FORM_DATA) + local + l_mods: CMS_MODULE_COLLECTION + do + if attached {WSF_TABLE} fd.table_item ("module_installation") as tb and then not tb.is_empty then + l_mods := api.setup.modules + across + tb as ic + loop + if + attached {WSF_STRING} ic.item as l_mod_name and then + attached l_mods.item_by_name (l_mod_name.value) as m + then + api.install_module (m) + if not api.is_module_installed (m) then + fd.report_error ("Installation failed for module " + m.name) + end + else + fd.report_error ("Can not find associated module" + ic.item.as_string.url_encoded_value) + end + end + else + fd.report_error ("No module to install!") + end + end + + on_uninstallation_submit (fd: WSF_FORM_DATA) + local + l_mods: CMS_MODULE_COLLECTION + do + if attached {WSF_TABLE} fd.table_item ("module_uninstallation") as tb and then not tb.is_empty then + l_mods := api.setup.modules + across + tb as ic + loop + if + attached {WSF_STRING} ic.item as l_mod_name and then + attached l_mods.item_by_name (l_mod_name.value) as m + then + api.uninstall_module (m) + if api.is_module_installed (m) then + fd.report_error ("Un-Installation failed for module " + m.name) + end + else + fd.report_error ("Can not find associated module" + ic.item.as_string.url_encoded_value) + end + end + else + fd.report_error ("No module to uninstall!") + end + end + +end diff --git a/modules/blog/cms_blog_api.e b/modules/blog/cms_blog_api.e index 8c63dcd..695289d 100644 --- a/modules/blog/cms_blog_api.e +++ b/modules/blog/cms_blog_api.e @@ -36,7 +36,7 @@ feature {NONE} -- Initialization Precursor -- Create the node storage for type blog - if attached {CMS_STORAGE_SQL_I} storage as l_storage_sql then + if attached storage.as_sql_storage as l_storage_sql then create {CMS_BLOG_STORAGE_SQL} blog_storage.make (l_storage_sql) else create {CMS_BLOG_STORAGE_NULL} blog_storage.make diff --git a/modules/blog/cms_blog_module.e b/modules/blog/cms_blog_module.e index 0daf7cc..ec38c1e 100644 --- a/modules/blog/cms_blog_module.e +++ b/modules/blog/cms_blog_module.e @@ -83,7 +83,7 @@ feature {CMS_API} -- Module management sql: STRING do -- Schema - if attached {CMS_STORAGE_SQL_I} api.storage as l_sql_storage then + if attached api.storage.as_sql_storage as l_sql_storage then if not l_sql_storage.sql_table_exists ("blog_post_nodes") then sql := "[ CREATE TABLE blog_post_nodes( diff --git a/modules/node/cms_node_module.e b/modules/node/cms_node_module.e index b6fd250..0ea6f2d 100644 --- a/modules/node/cms_node_module.e +++ b/modules/node/cms_node_module.e @@ -41,6 +41,9 @@ feature {NONE} -- Initialization description := "Service to manage content based on 'node'" package := "core" config := a_setup + -- Optional dependencies, mainly for information. + put_dependency ({CMS_RECENT_CHANGES_MODULE}, False) + put_dependency ({CMS_TAXONOMY_MODULE}, False) end config: CMS_SETUP @@ -63,7 +66,7 @@ feature {CMS_API} -- Module Initialization Precursor (a_api) -- Storage initialization - if attached {CMS_STORAGE_SQL_I} a_api.storage as l_storage_sql then + if attached a_api.storage.as_sql_storage as l_storage_sql then create {CMS_NODE_STORAGE_SQL} l_node_storage.make (l_storage_sql) else -- FIXME: in case of NULL storage, should Current be disabled? @@ -111,7 +114,7 @@ feature {CMS_API} -- Module management -- Is Current module installed? do Result := Precursor (a_api) - if Result and attached {CMS_STORAGE_SQL_I} a_api.storage as l_sql_storage then + if Result and attached a_api.storage.as_sql_storage as l_sql_storage then Result := l_sql_storage.sql_table_exists ("nodes") and l_sql_storage.sql_table_exists ("page_nodes") end @@ -120,7 +123,7 @@ feature {CMS_API} -- Module management install (a_api: CMS_API) do -- Schema - if attached {CMS_STORAGE_SQL_I} a_api.storage as l_sql_storage then + if attached a_api.storage.as_sql_storage as l_sql_storage then l_sql_storage.sql_execute_file_script (a_api.module_resource_location (Current, (create {PATH}.make_from_string ("scripts")).extended (name).appended_with_extension ("sql")), Void) end Precursor {CMS_MODULE}(a_api) diff --git a/modules/node/handler/cms_node_type_webform_manager.e b/modules/node/handler/cms_node_type_webform_manager.e index 4876844..05ea3dd 100644 --- a/modules/node/handler/cms_node_type_webform_manager.e +++ b/modules/node/handler/cms_node_type_webform_manager.e @@ -48,8 +48,8 @@ feature -- Forms ... if a_node /= Void then ta.set_text_value (a_node.content) end - ta.set_label ("Content") - ta.set_description ("This is the main content") + ta.set_label (response.translation ("Content", Void)) + ta.set_description (response.translation ("This is the main content", Void)) ta.set_is_required (False) -- Summary @@ -61,8 +61,8 @@ feature -- Forms ... if a_node /= Void then sum.set_text_value (a_node.summary) end - sum.set_label ("Summary") - sum.set_description ("Text displayed in short view.") + sum.set_label (response.translation ("Summary", Void)) + sum.set_description (response.translation ("Text displayed in short view.", Void)) sum.set_is_required (False) create fset.make @@ -93,9 +93,230 @@ feature -- Forms ... f.extend (fset) -- 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 + 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 + end + end + populate_form_with_path_alias (response: NODE_RESPONSE; f: CMS_FORM; a_node: detachable CMS_NODE) local ti: WSF_FORM_TEXT_INPUT @@ -316,6 +537,33 @@ feature -- Output s.append ("") + if + attached {CMS_TAXONOMY_API} a_response.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 + 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 + s.append ("
    ") + s.append (a_response.html_encoded (ic.item.name)) + s.append (": ") + across + l_terms as t_ic + loop + s.append ("
  • ") + a_response.append_link_to_html (t_ic.item.text, "taxonomy/term/" + t_ic.item.id.out, Void, s) + s.append ("
  • ") + end + s.append ("
%N") + end + end + 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. -- The usage of the summary is to give a short overview in the list of nodes or for the meta tag "description" diff --git a/modules/node/handler/node_form_response.e b/modules/node/handler/node_form_response.e index 0ce6d63..96d222a 100644 --- a/modules/node/handler/node_form_response.e +++ b/modules/node/handler/node_form_response.e @@ -117,7 +117,7 @@ feature {NONE} -- Create a new node hooks.invoke_form_alter (f, fd, Current) if request.is_post_request_method then f.validation_actions.extend (agent edit_form_validate (?, b)) - f.submit_actions.extend (agent edit_form_submit (?, l_node, a_type, b)) + f.submit_actions.put_front (agent edit_form_submit (?, l_node, a_type, b)) f.process (Current) fd := f.last_data end @@ -147,7 +147,7 @@ feature {NONE} -- Create a new node hooks.invoke_form_alter (f, fd, Current) if request.is_post_request_method then f.validation_actions.extend (agent edit_form_validate (?, b)) - f.submit_actions.extend (agent edit_form_submit (?, a_node, a_type, b)) + f.submit_actions.put_front (agent edit_form_submit (?, a_node, a_type, b)) f.process (Current) fd := f.last_data end diff --git a/modules/node/node-safe.ecf b/modules/node/node-safe.ecf index f024e33..00ea6fc 100644 --- a/modules/node/node-safe.ecf +++ b/modules/node/node-safe.ecf @@ -14,6 +14,7 @@ + diff --git a/modules/node/node.ecf b/modules/node/node.ecf index 3fe2d71..bfbfead 100644 --- a/modules/node/node.ecf +++ b/modules/node/node.ecf @@ -14,6 +14,7 @@ + diff --git a/modules/oauth20/cms_oauth_20_module.e b/modules/oauth20/cms_oauth_20_module.e index 321f451..0474636 100644 --- a/modules/oauth20/cms_oauth_20_module.e +++ b/modules/oauth20/cms_oauth_20_module.e @@ -72,7 +72,7 @@ feature {CMS_API} -- Module Initialization Precursor (a_api) -- Storage initialization - if attached {CMS_STORAGE_SQL_I} a_api.storage as l_storage_sql then + if attached a_api.storage.as_sql_storage as l_storage_sql then create {CMS_OAUTH_20_STORAGE_SQL} l_user_auth_storage.make (l_storage_sql) else -- FIXME: in case of NULL storage, should Current be disabled? @@ -93,7 +93,7 @@ feature {CMS_API} -- Module management l_consumers: LIST [STRING] do -- Schema - if attached {CMS_STORAGE_SQL_I} api.storage as l_sql_storage then + if attached api.storage.as_sql_storage as l_sql_storage then if not l_sql_storage.sql_table_exists ("oauth2_consumers") then --| Schema l_sql_storage.sql_execute_file_script (api.module_resource_location (Current, (create {PATH}.make_from_string ("scripts")).extended ("oauth2_consumers.sql")), Void) @@ -112,7 +112,7 @@ feature {CMS_API} -- Module management else from l_sql_storage.sql_start - create {ARRAYED_LIST[STRING]} l_consumers.make (2) + create {ARRAYED_LIST [STRING]} l_consumers.make (2) until l_sql_storage.sql_after loop diff --git a/modules/openid/cms_openid_module.e b/modules/openid/cms_openid_module.e index 988085b..f098c50 100644 --- a/modules/openid/cms_openid_module.e +++ b/modules/openid/cms_openid_module.e @@ -74,7 +74,7 @@ feature {CMS_API} -- Module Initialization Precursor (a_api) -- Storage initialization - if attached {CMS_STORAGE_SQL_I} a_api.storage as l_storage_sql then + if attached a_api.storage.as_sql_storage as l_storage_sql then create {CMS_OPENID_STORAGE_SQL} l_openid_storage.make (l_storage_sql) else -- FIXME: in case of NULL storage, should Current be disabled? @@ -93,7 +93,7 @@ feature {CMS_API} -- Module management install (api: CMS_API) do -- Schema - if attached {CMS_STORAGE_SQL_I} api.storage as l_sql_storage then + if attached api.storage.as_sql_storage as l_sql_storage then if not l_sql_storage.sql_table_exists ("openid_consumers") then --| Schema l_sql_storage.sql_execute_file_script (api.module_resource_location (Current, (create {PATH}.make_from_string ("scripts")).extended ("openid_consumers.sql")), Void) diff --git a/modules/taxonomy/cms_taxonomy_api.e b/modules/taxonomy/cms_taxonomy_api.e new file mode 100644 index 0000000..f0b3ea7 --- /dev/null +++ b/modules/taxonomy/cms_taxonomy_api.e @@ -0,0 +1,218 @@ +note + description: "[ + API to handle taxonomy vocabularies and terms. + ]" + date: "$Date$" + revision: "$Revision$" + +class + CMS_TAXONOMY_API + +inherit + CMS_MODULE_API + redefine + initialize + end + + REFACTORING_HELPER + +create + make + +feature {NONE} -- Initialization + + initialize + -- + do + Precursor + + -- Create the node storage for type blog + if attached storage.as_sql_storage as l_storage_sql then + create {CMS_TAXONOMY_STORAGE_SQL} taxonomy_storage.make (l_storage_sql) + else + create {CMS_TAXONOMY_STORAGE_NULL} taxonomy_storage.make + end + end + +feature {CMS_MODULE} -- Access nodes storage. + + taxonomy_storage: CMS_TAXONOMY_STORAGE_I + +feature -- Access node + + vocabulary_count: INTEGER_64 + -- Number of vocabulary. + do + Result := taxonomy_storage.vocabulary_count + end + + vocabularies (a_limit: NATURAL_32; a_offset: NATURAL_32): CMS_VOCABULARY_COLLECTION + -- List of vocabularies ordered by weight and limited by limit and offset. + do + Result := taxonomy_storage.vocabularies (a_limit, a_offset) + end + + vocabulary (a_id: INTEGER): detachable CMS_VOCABULARY + -- Vocabulary associated with id `a_id'. + require + valid_id: a_id > 0 + do + Result := taxonomy_storage.vocabulary (a_id) + end + + vocabularies_for_type (a_type_name: READABLE_STRING_GENERAL): detachable CMS_VOCABULARY_COLLECTION + -- Vocabularies associated with content type `a_type_name'. + do + Result := taxonomy_storage.vocabularies_for_type (a_type_name) + end + + fill_vocabularies_with_terms (a_vocab: CMS_VOCABULARY) + -- Fill `a_vocab' with associated terms. + do + reset_error + a_vocab.terms.wipe_out + if attached terms (a_vocab, 0, 0) as lst then + across + lst as ic + loop + a_vocab.extend (ic.item) + end + end + end + + term_count_from_vocabulary (a_vocab: CMS_VOCABULARY): INTEGER_64 + -- Number of terms from vocabulary `a_vocab'. + require + has_id: a_vocab.has_id + do + Result := taxonomy_storage.term_count_from_vocabulary (a_vocab) + 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'. + do + Result := taxonomy_storage.terms_of_entity (a_type_name, a_entity, a_vocabulary) + end + + terms (a_vocab: CMS_VOCABULARY; a_limit: NATURAL_32; a_offset: NATURAL_32): CMS_TERM_COLLECTION + -- List of terms ordered by weight and limited by limit and offset. + require + has_id: a_vocab.has_id + do + Result := taxonomy_storage.terms (a_vocab, a_limit, a_offset) + end + + term_by_id (a_tid: INTEGER): detachable CMS_TERM + do + Result := taxonomy_storage.term_by_id (a_tid) + end + + term_by_text (a_term_text: READABLE_STRING_GENERAL; a_vocabulary: detachable CMS_VOCABULARY): detachable CMS_TERM + -- Term with text `a_term_text', included in vocabulary `a_vocabulary' if provided. + do + Result := taxonomy_storage.term_by_text (a_term_text, a_vocabulary) + end + +feature -- Write + + save_vocabulary (a_voc: CMS_VOCABULARY) + do + reset_error + taxonomy_storage.save_vocabulary (a_voc) + error_handler.append (taxonomy_storage.error_handler) + end + + save_term (a_term: CMS_TERM; voc: CMS_VOCABULARY) + do + reset_error + taxonomy_storage.save_term (a_term, 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 + reset_error + taxonomy_storage.associate_term_with_entity (a_term, a_type_name, a_entity) + error_handler.append (taxonomy_storage.error_handler) + end + + unassociate_term_from_entity (a_term: CMS_TERM; a_type_name: READABLE_STRING_GENERAL; a_entity: READABLE_STRING_GENERAL) + -- Unassociate term `a_term' from `(a_type_name, a_entity)'. + do + reset_error + taxonomy_storage.unassociate_term_from_entity (a_term, a_type_name, a_entity) + error_handler.append (taxonomy_storage.error_handler) + end + + associate_vocabulary_with_type (a_voc: CMS_VOCABULARY; a_type_name: READABLE_STRING_GENERAL) + -- Associate vocabulary `a_voc' with type `a_type_name'. + require + existing_term: a_voc.has_id + do + reset_error + taxonomy_storage.associate_vocabulary_with_type (a_voc, a_type_name) + error_handler.append (taxonomy_storage.error_handler) + end + + unassociate_vocabulary_with_type (a_voc: CMS_VOCABULARY; a_type_name: READABLE_STRING_GENERAL) + -- Un-associate vocabulary `a_voc' from type `a_type_name'. + require + existing_term: a_voc.has_id + do + reset_error + taxonomy_storage.unassociate_vocabulary_with_type (a_voc, a_type_name) + error_handler.append (taxonomy_storage.error_handler) + end + +feature -- Helpers + + splitted_string (s: READABLE_STRING_32; sep: CHARACTER): LIST [READABLE_STRING_32] + -- Splitted string from `s' with separator `sep', and support '"..."' wrapping. + local + i,j,n,b: INTEGER + t: STRING_32 + do + create {ARRAYED_LIST [READABLE_STRING_32]} Result.make (1) + Result.compare_objects + from + i := 1 + b := 1 + n := s.count + create t.make_empty + until + i > n + loop + if s[i].is_space then + if not t.is_empty then + t.append_character (s[i]) + end + elseif s[i] = sep then + t.left_adjust + t.right_adjust + if t.count > 2 and t.starts_with_general ("%"") and t.ends_with_general ("%"") then + t.remove_head (1) + t.remove_tail (1) + end + Result.force (t) + create t.make_empty + elseif s[i] = '"' then + j := s.index_of ('"', i + 1) + if j > 0 then + t.append (s.substring (i, j)) + end + i := j + else + t.append_character (s[i]) + end + i := i + 1 + end + if not t.is_empty then + t.left_adjust + t.right_adjust + Result.force (t) + end + end + +end diff --git a/modules/taxonomy/cms_taxonomy_module.e b/modules/taxonomy/cms_taxonomy_module.e new file mode 100644 index 0000000..77a1249 --- /dev/null +++ b/modules/taxonomy/cms_taxonomy_module.e @@ -0,0 +1,158 @@ +note + description: "[ + Taxonomy module managing vocabularies and terms. + ]" + date: "$Date: 2015-05-22 15:13:00 +0100 (lun., 18 mai 2015) $" + revision: "$Revision 96616$" + +class + CMS_TAXONOMY_MODULE + +inherit + CMS_MODULE + rename + module_api as taxonomy_api + redefine + register_hooks, + initialize, + install, + uninstall, + taxonomy_api, + permissions + end + + CMS_HOOK_MENU_SYSTEM_ALTER + + CMS_HOOK_RESPONSE_ALTER + +create + make + +feature {NONE} -- Initialization + + make + do + version := "1.0" + description := "Taxonomy solution" + package := "core" +-- add_dependency ({CMS_NODE_MODULE}) + end + +feature -- Access + + name: STRING = "taxonomy" + + permissions: LIST [READABLE_STRING_8] + -- List of permission ids, used by this module, and declared. + do + Result := Precursor + Result.force ("admin taxonomy") + Result.force ("update any taxonomy") + Result.force ("update page taxonomy") -- related to node module + Result.force ("update blog taxonomy") -- related to blog module + end + +feature {CMS_API} -- Module Initialization + + initialize (api: CMS_API) + -- + do + Precursor (api) + create taxonomy_api.make (api) + end + +feature {CMS_API} -- Module management + + install (api: CMS_API) + local + voc: CMS_VOCABULARY + l_taxonomy_api: like taxonomy_api + do + -- Schema + if attached {CMS_STORAGE_SQL_I} api.storage as l_sql_storage then + l_sql_storage.sql_execute_file_script (api.module_resource_location (Current, (create {PATH}.make_from_string ("scripts")).extended ("install").appended_with_extension ("sql")), Void) + if l_sql_storage.has_error then + api.logger.put_error ("Could not install database for taxonomy module", generating_type) + end + Precursor (api) + + create l_taxonomy_api.make (api) + create voc.make ("Tags") + voc.set_description ("Enter comma separated tags.") + l_taxonomy_api.save_vocabulary (voc) + voc.set_is_tags (True) + l_taxonomy_api.associate_vocabulary_with_type (voc, "page") + end + end + + uninstall (api: CMS_API) + -- (export status {CMS_API}) + do + if attached {CMS_STORAGE_SQL_I} api.storage as l_sql_storage then + l_sql_storage.sql_execute_file_script (api.module_resource_location (Current, (create {PATH}.make_from_string ("scripts")).extended ("uninstall").appended_with_extension ("sql")), Void) + if l_sql_storage.has_error then + api.logger.put_error ("Could not remove database for taxonomy module", generating_type) + end + end + Precursor (api) + end + +feature {CMS_API} -- Access: API + + taxonomy_api: detachable CMS_TAXONOMY_API + -- + +feature -- Access: router + + setup_router (a_router: WSF_ROUTER; a_api: CMS_API) + -- + do + if attached taxonomy_api as l_taxonomy_api then + configure_web (a_api, l_taxonomy_api, a_router) + else + -- Issue with api/dependencies, + -- thus Current module should not be used! + -- thus no url mapping + end + end + + configure_web (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_HANDLER + l_uri_mapping: WSF_URI_MAPPING + do + create l_taxonomy_handler.make (a_api, a_taxonomy_api) + -- Let the class BLOG_HANDLER handle the requests on "/taxonomys" + create l_uri_mapping.make_trailing_slash_ignored ("/taxonomy", l_taxonomy_handler) + a_router.map (l_uri_mapping, a_router.methods_get) + + -- We can add a page number after /taxonomys/ to get older posts + a_router.handle ("/taxonomy/{vocabulary}", l_taxonomy_handler, a_router.methods_get) + + -- If a user id is given route with taxonomy user handler + --| FIXME: maybe /user/{user}/taxonomys/ would be better. + a_router.handle ("/taxonomy/{vocabulary}/{termid}", l_taxonomy_handler, a_router.methods_get) + end + +feature -- Hooks + + register_hooks (a_response: CMS_RESPONSE) + do + a_response.hooks.subscribe_to_menu_system_alter_hook (Current) + a_response.hooks.subscribe_to_response_alter_hook (Current) + end + + response_alter (a_response: CMS_RESPONSE) + do + a_response.add_style (a_response.url ("/module/" + name + "/files/css/taxonomy.css", Void), Void) + end + + menu_system_alter (a_menu_system: CMS_MENU_SYSTEM; a_response: CMS_RESPONSE) + do + -- Add the link to the taxonomy to the main menu +-- create lnk.make ("Taxonomy", "taxonomy/") +-- a_menu_system.primary_menu.extend (lnk) + end + +end diff --git a/modules/taxonomy/cms_term.e b/modules/taxonomy/cms_term.e new file mode 100644 index 0000000..aadb153 --- /dev/null +++ b/modules/taxonomy/cms_term.e @@ -0,0 +1,110 @@ +note + description: "[ + Taxonomy vocabulary term. + ]" + date: "$Date$" + revision: "$Revision$" + +class + CMS_TERM + +inherit + COMPARABLE + + DEBUG_OUTPUT + undefine + is_equal + end + +create + make, + make_with_id + +feature {NONE} -- Initialization + + make_with_id (a_id: INTEGER_64; a_text: READABLE_STRING_GENERAL) + do + id := a_id + make (a_text) + end + + make (a_text: READABLE_STRING_GENERAL) + do + set_text (a_text) + end + +feature -- Access + + id: INTEGER_64 + -- Associated term id. + + text: IMMUTABLE_STRING_32 + -- Text for the term. + + description: detachable IMMUTABLE_STRING_32 + -- Optional description. + + weight: INTEGER + -- Associated weight for ordering. + +feature -- Status report + + has_id: BOOLEAN + -- Has valid id? + do + Result := id > 0 + end + + debug_output: STRING_32 + -- String that should be displayed in debugger to represent `Current'. + do + create Result.make_empty + Result.append_character ('#') + Result.append (id.out) + Result.append_character (' ') + Result.append (text) + Result.append_character (' ') + Result.append ("weight=") + Result.append_integer (weight) + end + +feature -- Comparison + + is_less alias "<" (other: like Current): BOOLEAN + -- Is current object less than `other'? + do + if weight = other.weight then + if text.same_string (other.text) then + Result := id < other.id + else + Result := text < other.text + end + else + Result := weight < other.weight + end + end + +feature -- Element change + + set_id (a_id: INTEGER_64) + do + id := a_id + end + + set_text (a_text: READABLE_STRING_GENERAL) + do + create text.make_from_string_general (a_text) + end + + set_weight (w: like weight) + do + weight := w + end + + set_description (a_description: READABLE_STRING_GENERAL) + do + create description.make_from_string_general (a_description) + end + +end + diff --git a/modules/taxonomy/cms_term_collection.e b/modules/taxonomy/cms_term_collection.e new file mode 100644 index 0000000..5e882e7 --- /dev/null +++ b/modules/taxonomy/cms_term_collection.e @@ -0,0 +1,92 @@ +note + description: "[ + Collection of CMS terms (see Taxonomy). + ]" + date: "$Date$" + revision: "$Revision$" + +class + CMS_TERM_COLLECTION + +inherit + ITERABLE [CMS_TERM] + +create + make + +feature {NONE} -- Initialization + + make (nb: INTEGER) + do + create items.make (nb) + end + +feature -- Access + + new_cursor: INDEXABLE_ITERATION_CURSOR [CMS_TERM] + -- + do + Result := items.new_cursor + end + + count: INTEGER + -- Number of terms. + do + Result := items.count + end + +feature -- Status report + + is_empty: BOOLEAN + do + Result := count = 0 + end + + has (a_term: CMS_TERM): BOOLEAN + -- Has `a_term'? + do + Result := items.has (a_term) + end + +feature -- Element change + + wipe_out + -- Remove all items. + do + items.wipe_out + ensure + empty: count = 0 + end + + force, extend (a_term: CMS_TERM) + -- Add term `a_term'; + do + if not has (a_term) then + items.force (a_term) + end + end + + remove (a_term: CMS_TERM) + -- Remove term `a_term'. + do + items.prune_all (a_term) + end + + sort + -- Sort `items' + local + l_sorter: QUICK_SORTER [CMS_TERM] + do + create l_sorter.make (create {COMPARABLE_COMPARATOR [CMS_TERM]}) + l_sorter.sort (items) + end + +feature {NONE} -- Implementation + + items: ARRAYED_LIST [CMS_TERM] + -- List of terms. + +;note + copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Eiffel Software and others" + license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)" +end diff --git a/modules/taxonomy/cms_vocabulary.e b/modules/taxonomy/cms_vocabulary.e new file mode 100644 index 0000000..4ab45a9 --- /dev/null +++ b/modules/taxonomy/cms_vocabulary.e @@ -0,0 +1,128 @@ +note + description: "[ + Taxonomy vocabulary. + ]" + date: "$Date$" + revision: "$Revision$" + +class + CMS_VOCABULARY + +inherit + CMS_TERM + rename + text as name, + set_text as set_name + redefine + make + end + + ITERABLE [CMS_TERM] + undefine + is_equal + end + +create + make, + make_with_id, + make_from_term, + make_none + +feature {NONE} -- Initialization + + make_none + do + make ("") + end + + make (a_name: READABLE_STRING_GENERAL) + do + Precursor (a_name) + create terms.make (0) + end + + make_from_term (a_term: CMS_TERM) + do + make_with_id (a_term.id, a_term.text) + description := a_term.description + set_weight (a_term.weight) + end + +feature -- Access + + terms: CMS_TERM_COLLECTION + -- Collection of terms. + + new_cursor: INDEXABLE_ITERATION_CURSOR [CMS_TERM] + -- + do + Result := terms.new_cursor + end + +feature -- Status report + + associated_content_type: detachable READABLE_STRING_GENERAL + -- Associated content type, if any. + + is_tags: BOOLEAN + -- New terms accepted (as tags), in the context of `associated_content_type'? + + multiple_terms_allowed: BOOLEAN + -- Accepts multiple terms, in the context of `associated_content_type'? + + is_term_required: BOOLEAN + -- At least one term is required, in the context of `associated_content_type'? + +feature -- Element change + + set_is_tags (b: BOOLEAN) + -- Set `is_tags' to `b'. + do + is_tags := b + end + + allow_multiple_term (b: BOOLEAN) + -- Set `multiple_terms_allowed' to `b'. + do + multiple_terms_allowed := b + end + + set_is_term_required (b: BOOLEAN) + -- Set `is_term_required' to `b'. + do + is_term_required := b + end + + set_associated_content_type (a_type: detachable READABLE_STRING_GENERAL; a_is_tags, a_multiple, a_is_required: BOOLEAN) + -- If `a_type' is set, define `associated_content_type' and related options, + -- otherwise reset `associated_content_type'. + do + if a_type = Void then + associated_content_type := Void + set_is_tags (False) + allow_multiple_term (False) + set_is_term_required (False) + else + associated_content_type := a_type + set_is_tags (a_is_tags) + allow_multiple_term (a_multiple) + set_is_term_required (a_is_required) + end + end + +feature -- Element change + + force, extend (a_term: CMS_TERM) + -- Add `a_term' to the vocabulary terms `terms'. + do + terms.force (a_term) + end + + sort + -- Sort `items' + do + terms.sort + end + +end + diff --git a/modules/taxonomy/cms_vocabulary_collection.e b/modules/taxonomy/cms_vocabulary_collection.e new file mode 100644 index 0000000..b4c3028 --- /dev/null +++ b/modules/taxonomy/cms_vocabulary_collection.e @@ -0,0 +1,98 @@ +note + description: "[ + Collection of CMS vocabularies (see Taxonomy). + ]" + date: "$Date$" + revision: "$Revision$" + +class + CMS_VOCABULARY_COLLECTION + +inherit + ITERABLE [CMS_VOCABULARY] + +create + make + +feature {NONE} -- Initialization + + make (nb: INTEGER) + do + create items.make (nb) + end + +feature -- Access + + item_by_name (a_voc_name: READABLE_STRING_GENERAL): detachable CMS_VOCABULARY + -- Vocabulary from current collection associated with name `a_voc_name', if any. + do + across + items as ic + until + Result /= Void + loop + if ic.item.name.is_case_insensitive_equal_general (a_voc_name) then + Result := ic.item + end + end + end + + new_cursor: INDEXABLE_ITERATION_CURSOR [CMS_VOCABULARY] + -- + do + Result := items.new_cursor + end + + count: INTEGER + -- Number of vocabularies. + do + Result := items.count + end + +feature -- Status report + + is_empty: BOOLEAN + do + Result := count = 0 + end + + has (a_vocabulary: CMS_VOCABULARY): BOOLEAN + -- Has `a_vocabulary'? + do + Result := items.has (a_vocabulary) + end + +feature -- Element change + + force, extend (a_vocabulary: CMS_VOCABULARY) + -- Add vocabulary `a_vocabulary'; + do + if not has (a_vocabulary) then + items.force (a_vocabulary) + end + end + + remove (a_vocabulary: CMS_VOCABULARY) + -- Remove vocabulary `a_vocabulary'. + do + items.prune_all (a_vocabulary) + end + + sort + -- Sort `items' + local + l_sorter: QUICK_SORTER [CMS_VOCABULARY] + do + create l_sorter.make (create {COMPARABLE_COMPARATOR [CMS_VOCABULARY]}) + l_sorter.sort (items) + end + +feature {NONE} -- Implementation + + items: ARRAYED_LIST [CMS_VOCABULARY] + -- List of vocabularies. + +;note + copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Eiffel Software and others" + license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)" +end diff --git a/modules/taxonomy/handler/taxonomy_handler.e b/modules/taxonomy/handler/taxonomy_handler.e new file mode 100644 index 0000000..bef7d11 --- /dev/null +++ b/modules/taxonomy/handler/taxonomy_handler.e @@ -0,0 +1,81 @@ +note + description: "[ + Request handler related to + /taxonomy + /taxonomy/{vocabulary} + /taxonomy/{vocabulary}/{term} + ]" + date: "$Date$" + revision: "$revision$" + +class + TAXONOMY_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 + do + -- Read page number from path parameter. + + -- Responding with `main_content_html (l_page)'. + create {GENERIC_VIEW_CMS_RESPONSE} l_page.make (req, res, api) + l_page.set_main_content ("Not Yet Implemented -- In Progress") + l_page.execute + end + +end diff --git a/modules/taxonomy/persistence/cms_taxonomy_storage_i.e b/modules/taxonomy/persistence/cms_taxonomy_storage_i.e new file mode 100644 index 0000000..559d0d7 --- /dev/null +++ b/modules/taxonomy/persistence/cms_taxonomy_storage_i.e @@ -0,0 +1,127 @@ +note + description: "[ + Interface for accessing taxonomy data from storage. + ]" + date: "$Date$" + revision: "$Revision$" + +deferred class + CMS_TAXONOMY_STORAGE_I + +feature -- Error Handling + + error_handler: ERROR_HANDLER + -- Error handler. + deferred + end + +feature -- Access + + vocabulary_count: INTEGER_64 + -- Count of vocabularies. + deferred + end + + vocabularies (a_limit: NATURAL_32; a_offset: NATURAL_32): CMS_VOCABULARY_COLLECTION + -- List of vocabularies ordered by weight from `a_offset' to `a_offset + a_limit'. + deferred + end + + vocabulary (a_id: INTEGER_64): detachable CMS_VOCABULARY + -- Vocabulary by id `a_id'. + require + valid_id: a_id > 0 + deferred + end + + vocabularies_for_type (a_type_name: READABLE_STRING_GENERAL): detachable CMS_VOCABULARY_COLLECTION + -- Vocabularies associated with content type `a_type_name'. + require + valid_type_name: not a_type_name.is_whitespace + deferred + end + + terms_count: INTEGER_64 + -- Number of terms. + deferred + end + + term_by_id (tid: INTEGER_64): detachable CMS_TERM + -- Term associated with id `tid'. + deferred + ensure + Result /= Void implies Result.id = tid + end + + term_by_text (a_term_text: READABLE_STRING_GENERAL; a_vocabulary: detachable CMS_VOCABULARY): detachable CMS_TERM + -- Term with text `a_term_text', included in vocabulary `a_vocabulary' if provided. + deferred + ensure + Result /= Void implies a_term_text.same_string (Result.text) + end + + term_count_from_vocabulary (a_vocab: CMS_VOCABULARY): INTEGER_64 + -- Number of terms from vocabulary `a_vocab'. + require + has_id: a_vocab.has_id + deferred + end + + terms (a_vocab: CMS_VOCABULARY; a_limit: NATURAL_32; a_offset: NATURAL_32): CMS_TERM_COLLECTION + -- List of terms from vocabulary `a_vocab' ordered by weight from `a_offset' to `a_offset + a_limit'. + require + has_id: a_vocab.has_id + deferred + 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'. + deferred + end + +feature -- Store + + save_vocabulary (a_voc: CMS_VOCABULARY) + -- Insert or update vocabulary `a_voc'. + 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'. + deferred + ensure + not error_handler.has_error implies t.has_id and then term_by_id (t.id) /= Void + 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 + existing_term: a_term.has_id + deferred + end + + unassociate_term_from_entity (a_term: CMS_TERM; a_type_name: READABLE_STRING_GENERAL; a_entity: READABLE_STRING_GENERAL) + -- Unassociate term `a_term' from `(a_type_name, a_entity)'. + require + existing_term: a_term.has_id + deferred + end + + associate_vocabulary_with_type (a_voc: CMS_VOCABULARY; a_type_name: READABLE_STRING_GENERAL) + -- Associate vocabulary `a_voc' with type `a_type_name'. + require + existing_term: a_voc.has_id + deferred + end + + unassociate_vocabulary_with_type (a_voc: CMS_VOCABULARY; a_type_name: READABLE_STRING_GENERAL) + -- Un-associate vocabulary `a_voc' from type `a_type_name'. + require + existing_term: a_voc.has_id + deferred + end + +end diff --git a/modules/taxonomy/persistence/cms_taxonomy_storage_null.e b/modules/taxonomy/persistence/cms_taxonomy_storage_null.e new file mode 100644 index 0000000..bd8f05d --- /dev/null +++ b/modules/taxonomy/persistence/cms_taxonomy_storage_null.e @@ -0,0 +1,117 @@ +note + description: "Summary description for {CMS_TAXONOMY_STORAGE_NULL}." + date: "$Date$" + revision: "$Revision$" + +class + CMS_TAXONOMY_STORAGE_NULL + +inherit + CMS_TAXONOMY_STORAGE_I + +create + make + +feature {NONE} -- Initialization + + make + -- Initialize `Current'. + do + create error_handler.make + end + +feature -- Error Handling + + error_handler: ERROR_HANDLER + -- Error handler. + +feature -- Access + + vocabulary_count: INTEGER_64 + -- Count of vocabularies. + do + end + + term_count_from_vocabulary (a_vocab: CMS_VOCABULARY): INTEGER_64 + -- Number of terms from vocabulary `a_vocab'. + do + end + + vocabularies (a_limit: NATURAL_32; a_offset: NATURAL_32): CMS_VOCABULARY_COLLECTION + -- List of vocabularies ordered by weight from `a_offset' to `a_offset + a_limit'. + do + create Result.make (0) + end + + vocabulary (a_id: INTEGER_64): detachable CMS_VOCABULARY + -- Vocabulary by id `a_id'. + do + end + + vocabularies_for_type (a_type_name: READABLE_STRING_GENERAL): detachable CMS_VOCABULARY_COLLECTION + -- + do + end + + terms_count: INTEGER_64 + -- Number of terms. + do + end + + term_by_id (tid: INTEGER_64): detachable CMS_TERM + -- Term associated with id `tid'. + do + end + + term_by_text (a_term_text: READABLE_STRING_GENERAL; a_vocabulary: detachable CMS_VOCABULARY): detachable CMS_TERM + do + end + + terms (a_vocab: CMS_VOCABULARY; a_limit: NATURAL_32; a_offset: NATURAL_32): CMS_TERM_COLLECTION + -- List of terms from vocabulary `a_vocab' ordered by weight from `a_offset' to `a_offset + a_limit'. + do + create Result.make (0) + 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)'. + do + end + +feature -- Store + + save_vocabulary (a_voc: CMS_VOCABULARY) + -- Insert or update vocabulary `a_voc'. + do + error_handler.add_custom_error (-1, "not implemented", "save_vocabulary") + end + + save_term (t: CMS_TERM; voc: CMS_VOCABULARY) + -- + do + error_handler.add_custom_error (-1, "not implemented", "save_term") + 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") + end + + unassociate_term_from_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", "unassociate_term_from_entity") + end + + associate_vocabulary_with_type (a_voc: CMS_VOCABULARY; a_type_name: READABLE_STRING_GENERAL) + -- Associate vocabulary `a_voc' with type `a_type_name'. + do + error_handler.add_custom_error (-1, "not implemented", "associate_vocabulary_with_type") + end + + unassociate_vocabulary_with_type (a_voc: CMS_VOCABULARY; a_type_name: READABLE_STRING_GENERAL) + -- Un-associate vocabulary `a_voc' from type `a_type_name'. + do + error_handler.add_custom_error (-1, "not implemented", "unassociate_vocabulary_with_type") + end + +end diff --git a/modules/taxonomy/persistence/cms_taxonomy_storage_sql.e b/modules/taxonomy/persistence/cms_taxonomy_storage_sql.e new file mode 100644 index 0000000..5ed17dc --- /dev/null +++ b/modules/taxonomy/persistence/cms_taxonomy_storage_sql.e @@ -0,0 +1,508 @@ +note + description: "[ + Implementation of taxonomy storage using a SQL database. + ]" + date: "$Date$" + revision: "$Revision$" + +class + CMS_TAXONOMY_STORAGE_SQL + +inherit + CMS_TAXONOMY_STORAGE_I + + CMS_PROXY_STORAGE_SQL + +create + make + +feature -- Access + + vocabulary_count: INTEGER_64 + -- Count of vocabularies. + do + error_handler.reset + sql_query (sql_select_vocabularies_count, Void) + if not has_error and not sql_after then + Result := sql_read_integer_64 (1) + end + sql_finalize + end + + vocabularies (a_limit: NATURAL_32; a_offset: NATURAL_32): CMS_VOCABULARY_COLLECTION + -- List of vocabularies ordered by weight from `a_offset' to `a_offset + a_limit'. + local + l_parameters: STRING_TABLE [detachable ANY] + do + create Result.make (0) + error_handler.reset + + create l_parameters.make (3) + l_parameters.put (0, "parent_tid") + from + sql_query (sql_select_terms, l_parameters) + sql_start + until + sql_after + loop + if attached fetch_term as l_term then + Result.force (create {CMS_VOCABULARY}.make_from_term (l_term)) + end + sql_forth + end + sql_finalize + end + + vocabulary (a_tid: INTEGER_64): detachable CMS_VOCABULARY + -- Vocabulary by id `a_tid'. + do + if attached term_by_id (a_tid) as t then + create Result.make_from_term (t) + end + end + + term_count_from_vocabulary (a_vocab: CMS_VOCABULARY): INTEGER_64 + -- Number of terms from vocabulary `a_vocab'. + local + l_parameters: STRING_TABLE [detachable ANY] + do + error_handler.reset + create l_parameters.make (1) + l_parameters.put (a_vocab.id, "parent_tid") + sql_query (sql_select_vocabulary_terms_count, Void) + if not has_error and not sql_after then + Result := sql_read_integer_64 (1) + end + sql_finalize + end + + terms (a_vocab: CMS_VOCABULARY; a_limit: NATURAL_32; a_offset: NATURAL_32): CMS_TERM_COLLECTION + -- List of terms from vocabulary `a_vocab' ordered by weight from `a_offset' to `a_offset + a_limit'. + local + l_parameters: STRING_TABLE [detachable ANY] + do + create Result.make (0) + error_handler.reset + + create l_parameters.make (3) + l_parameters.put (a_vocab.id, "parent_tid") +-- l_parameters.put (a_limit, "limit") +-- l_parameters.put (a_offset, "offset") + from + sql_query (sql_select_terms, l_parameters) +-- sql_query (sql_select_terms_with_range, l_parameters) + sql_start + until + sql_after + loop + if attached fetch_term as l_term then + Result.force (l_term) + end + sql_forth + end + sql_finalize + end + + terms_count: INTEGER_64 + -- Number of terms. + do + error_handler.reset + sql_query (sql_select_terms_count, Void) + if not has_error and not sql_after then + Result := sql_read_integer_64 (1) + end + sql_finalize + end + + term_by_id (a_tid: INTEGER_64): detachable CMS_TERM + local + l_parameters: STRING_TABLE [detachable ANY] + do + error_handler.reset + + create l_parameters.make (1) + l_parameters.put (a_tid, "tid") + sql_query (sql_select_term, l_parameters) + sql_start + if not has_error and not sql_after then + Result := fetch_term + end + sql_finalize + end + + term_by_text (a_term_text: READABLE_STRING_GENERAL; a_vocabulary: detachable CMS_VOCABULARY): detachable CMS_TERM + local + l_parameters: STRING_TABLE [detachable ANY] + do + error_handler.reset + + create l_parameters.make (1) + l_parameters.put (a_term_text, "text") + if a_vocabulary /= Void then + l_parameters.put (a_vocabulary.id, "parent_tid") + sql_query (sql_select_vocabulary_term_by_text, l_parameters) + else + sql_query (sql_select_term_by_text, l_parameters) + end + sql_start + if not has_error and not sql_after then + Result := fetch_term + end + sql_finalize + end + + terms_of_entity (a_type_name: READABLE_STRING_GENERAL; a_entity: READABLE_STRING_GENERAL; a_vocabulary: detachable CMS_VOCABULARY): detachable CMS_TERM_COLLECTION + -- + local + l_parameters: STRING_TABLE [detachable ANY] + l_tids: ARRAYED_LIST [INTEGER_64] + tid: INTEGER_64 + do + error_handler.reset + + create l_parameters.make (3) + l_parameters.put (a_type_name, "type") + l_parameters.put (a_entity , "entity") + if a_vocabulary /= Void then + l_parameters.put (a_vocabulary.id , "parent_tid") + sql_query (sql_select_vocabulary_terms_of_entity, l_parameters) + else + sql_query (sql_select_terms_of_entity, l_parameters) + end + + create l_tids.make (0) + from + sql_start + until + sql_after or has_error + loop + tid := sql_read_integer_64 (1) + if tid > 0 then + l_tids.force (tid) + end + sql_forth + end + sql_finalize + if not l_tids.is_empty then + create Result.make (l_tids.count) + across + l_tids as ic + loop + if + ic.item > 0 and then + attached term_by_id (ic.item) as t + then + Result.force (t) + end + end + end + end + +feature -- Store + + save_vocabulary (voc: CMS_VOCABULARY) + do + save_term (voc, create {CMS_VOCABULARY}.make_none) + across + voc.terms as ic + until + has_error + loop + save_term (ic.item, voc) + end + end + + save_term (t: CMS_TERM; voc: CMS_VOCABULARY) + local + l_parameters: STRING_TABLE [detachable ANY] + do + error_handler.reset + + create l_parameters.make (5) + l_parameters.put (t.text, "text") + l_parameters.put (t.description, "description") + l_parameters.put (t.weight, "weight") + + sql_begin_transaction + if t.has_id then + l_parameters.put (t.id, "tid") + sql_modify (sql_update_term, l_parameters) + else + sql_insert (sql_insert_term, l_parameters) + t.set_id (last_inserted_term_id) + end + if not has_error then + l_parameters.put (t.id, "tid") + l_parameters.put (voc.id, "parent_tid") + sql_insert (sql_insert_term_in_vocabulary, l_parameters) + end + if has_error then + sql_rollback_transaction + else + sql_commit_transaction + end + 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 + l_parameters: STRING_TABLE [detachable ANY] + do + error_handler.reset + + create l_parameters.make (3) + l_parameters.put (a_term.id, "tid") + l_parameters.put (a_entity, "entity") + l_parameters.put (a_type_name, "type") + + sql_insert (sql_insert_term_index, l_parameters) + sql_finalize + end + + unassociate_term_from_entity (a_term: CMS_TERM; a_type_name: READABLE_STRING_GENERAL; a_entity: READABLE_STRING_GENERAL) + -- Unassociate term `a_term' from `(a_type_name, a_entity)'. + local + l_parameters: STRING_TABLE [detachable ANY] + do + error_handler.reset + + create l_parameters.make (3) + l_parameters.put (a_term.id, "tid") + l_parameters.put (a_entity, "entity") + l_parameters.put (a_type_name, "type") + + sql_modify (sql_delete_term_index, l_parameters) + sql_finalize + end + +feature -- Vocabulary and types + + mask_is_tags: INTEGER = 0b0001 -- 1 + mask_multiple_terms: INTEGER = 0b0010 -- 2 + mask_is_required: INTEGER = 0b0100 -- 4 + + vocabularies_for_type (a_type_name: READABLE_STRING_GENERAL): detachable CMS_VOCABULARY_COLLECTION + -- + -- note: vocabularies are not filled with associated terms. + local + voc: detachable CMS_VOCABULARY + l_parameters: STRING_TABLE [detachable ANY] + l_data: ARRAYED_LIST [TUPLE [tid: INTEGER_64; entity: INTEGER_64]] + tid, ent: INTEGER_64 + do + error_handler.reset + + create l_parameters.make (3) + l_parameters.put (a_type_name, "type") + sql_query (sql_select_vocabularies_for_type, l_parameters) + + create l_data.make (0) + from + sql_start + until + sql_after or has_error + loop + tid := sql_read_integer_64 (1) + if attached sql_read_string_32 (2) as s and then s.is_integer_64 then + ent := s.to_integer_64 + else + ent := 0 + end + if ent > 0 then + -- Vocabulary index should have 0 or negative value for `entity'! + check zero_or_negative_entity_value: False end + else + ent := - ent + if tid > 0 then + l_data.force ([tid, ent]) + end + end + sql_forth + end + sql_finalize + if not l_data.is_empty then + create Result.make (l_data.count) + across + l_data as ic + loop + tid := ic.item.tid + ent := ic.item.entity + check ic.item.tid > 0 end + + if + attached term_by_id (tid) as t + then + create voc.make_from_term (t) + --| 1: mask 0001: New terms allowed (i.e tags) + --| 2: mask 0010: Allow multiple tags + --| 4: mask 0100: At least one tag is required + voc.set_associated_content_type (a_type_name, ent & mask_is_tags = mask_is_tags, ent & mask_multiple_terms = mask_multiple_terms, ent & mask_is_required = mask_is_required) + Result.force (voc) + end + end + end + end + + associate_vocabulary_with_type (a_voc: CMS_VOCABULARY; a_type_name: READABLE_STRING_GENERAL) + -- + local + l_parameters: STRING_TABLE [detachable ANY] + i: INTEGER + do + error_handler.reset + + create l_parameters.make (3) + l_parameters.put (a_voc.id, "tid") + if a_voc.is_tags then + i := i | mask_is_tags + end + if a_voc.is_term_required then + i := i | mask_multiple_terms + end + if a_voc.multiple_terms_allowed then + 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) + sql_finalize + end + + unassociate_vocabulary_with_type (a_voc: CMS_VOCABULARY; a_type_name: READABLE_STRING_GENERAL) + -- + local + l_parameters: STRING_TABLE [detachable ANY] + do + error_handler.reset + + create l_parameters.make (2) + l_parameters.put (a_voc.id, "tid") + l_parameters.put (a_type_name, "type") + + sql_insert (sql_delete_vocabulary_index, l_parameters) + sql_finalize + end + +feature {NONE} -- Queries + + last_inserted_term_id: INTEGER_64 + -- Last insert term id. + do + error_handler.reset + sql_query (Sql_last_inserted_term_id, Void) + if not has_error and not sql_after then + Result := sql_read_integer_64 (1) + end + sql_finalize + end + + fetch_term: detachable CMS_TERM + local + tid: INTEGER_64 + l_text: detachable READABLE_STRING_32 + do + tid := sql_read_integer_64 (1) + l_text := sql_read_string_32 (2) + if tid > 0 and l_text /= Void then + create Result.make_with_id (tid, l_text) + Result.set_weight (sql_read_integer_32 (3)) + if attached sql_read_string_32 (4) as l_desc then + Result.set_description (l_desc) + end + end + end + + sql_select_terms_count: STRING = "SELECT count(*) FROM taxonomy_term ;" + -- Number of terms. + + sql_select_vocabularies_count: STRING = "SELECT count(*) FROM taxonomy_term INNER JOIN taxonomy_hierarchy ON taxonomy_term.tid = taxonomy_hierarchy.tid WHERE taxonomy_hierarchy.parent = 0;" + -- Number of terms without parent. + + sql_select_vocabulary_terms_count: STRING = "SELECT count(*) FROM taxonomy_term INNER JOIN taxonomy_hierarchy ON taxonomy_term.tid = taxonomy_hierarchy.tid WHERE taxonomy_hierarchy.parent = :parent_tid;" + -- Number of terms under :parent_tid. + + sql_select_terms: 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 + WHERE taxonomy_hierarchy.parent = :parent_tid + ORDER BY taxonomy_term.weight ASC ; + ]" + -- Terms under :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 + WHERE taxonomy_hierarchy.parent = :parent_tid + ORDER BY taxonomy_term.weight ASC LIMIT :limit OFFSET :offset + ; + ]" + -- Terms under :parent_tid, and :limit, :offset + + sql_select_term: STRING = "SELECT tid, text, weight, description FROM taxonomy_term WHERE tid=:tid;" + -- Term with tid :tid . + + sql_select_term_by_text: STRING = "SELECT tid, text, weight, description FROM taxonomy_term WHERE text=:text;" + -- Term with text :text . + + sql_select_vocabulary_term_by_text: 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 + WHERE taxonomy_hierarchy.parent=:parent_tid AND taxonomy_term.text=:text + ; + ]" + -- Term with text :text and with parent :parent_tid + + Sql_last_inserted_term_id: STRING = "SELECT MAX(tid) FROM taxonomy_term;" + + sql_insert_term: STRING = "[ + INSERT INTO taxonomy_term (text, weight, description, langcode) + VALUES (:text, :weight, :description, null); + ]" + + sql_update_term: STRING = "[ + UPDATE taxonomy_term + SET tid=:tid, text=:text, weight=:weight, description=:description, langcode=null + WHERE tid=:tid; + ]" + + sql_insert_term_in_vocabulary: STRING = "[ + INSERT INTO taxonomy_hierarchy (tid, parent) + VALUES (:tid, :parent_tid); + ]" + + sql_select_terms_of_entity: STRING = "[ + SELECT tid FROM taxonomy_index WHERE type=:type AND entity=:entity; + ]" + + sql_select_vocabulary_terms_of_entity: STRING = "[ + SELECT taxonomy_index.tid + FROM taxonomy_index INNER JOIN taxonomy_hierarchy ON taxonomy_index.tid=taxonomy_hierarchy.tid + WHERE taxonomy_hierarchy.parent=:parent_tid AND taxonomy_index.type=:type AND taxonomy_index.entity=:entity; + ]" + + sql_select_vocabularies_for_type: STRING = "[ + SELECT tid, entity + FROM taxonomy_index + WHERE type=:type AND entity <= 0; + ]" + + sql_insert_term_index: STRING = "[ + INSERT INTO taxonomy_index (tid, entity, type) + VALUES (:tid, :entity, :type); + ]" + + sql_delete_term_index: STRING = "[ + DELETE FROM taxonomy_index WHERE tid=:tid AND entity=:entity AND type=:type + ; + ]" + + sql_delete_vocabulary_index: STRING = "[ + DELETE FROM taxonomy_index WHERE tid=:tid AND type=:type + ; + ]" + + +end diff --git a/modules/taxonomy/site/files/css/taxonomy.css b/modules/taxonomy/site/files/css/taxonomy.css new file mode 100644 index 0000000..41c87fa --- /dev/null +++ b/modules/taxonomy/site/files/css/taxonomy.css @@ -0,0 +1,21 @@ +ul.taxonomy { + font-size: 80%; + list-style-type: none; + font-style: italic; + margin: 0; +} +ul.taxonomy li { + padding: 2px; + margin-right: 3px; + display: inline-block; + border: none; +} +ul.taxonomy li a:hover { + text-decoration: none; +} +ul.taxonomy li:hover { + padding: 1px; + border-top: solid 1px #66f; + border-bottom: solid 1px #66f; + background-color: #ddf; +} diff --git a/modules/taxonomy/site/files/scss/taxonomy.scss b/modules/taxonomy/site/files/scss/taxonomy.scss new file mode 100644 index 0000000..f409395 --- /dev/null +++ b/modules/taxonomy/site/files/scss/taxonomy.scss @@ -0,0 +1,21 @@ +ul.taxonomy { + font-size: 80%; + list-style-type: none; + font-style: italic; + margin: 0; + li { + a:hover { + text-decoration: none; + } + padding: 2px; + margin-right: 3px; + display: inline-block; + border: none; + &:hover { + padding: 1px; + border-top: solid 1px #66f; + border-bottom: solid 1px #66f; + background-color: #ddf; + } + } +} diff --git a/modules/taxonomy/site/scripts/install.sql b/modules/taxonomy/site/scripts/install.sql new file mode 100644 index 0000000..2eaf9a2 --- /dev/null +++ b/modules/taxonomy/site/scripts/install.sql @@ -0,0 +1,24 @@ + +CREATE TABLE taxonomy_term ( + `tid` INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT UNIQUE, + `text` VARCHAR(255) NOT NULL, + `weight` INTEGER, + `description` TEXT, + `langcode` VARCHAR(12) +); + +CREATE TABLE taxonomy_hierarchy ( + `tid` INTEGER NOT NULL, + `parent` INTEGER, + CONSTRAINT PK_tid_parent PRIMARY KEY (tid,parent) +); + +/* Associate tid with unique (type,entity) + * for instance: "page" + "$nid" -> "tid" + */ +CREATE TABLE taxonomy_index ( + `tid` INTEGER NOT NULL, + `entity` VARCHAR(255), + `type` VARCHAR(255) NOT NULL, + CONSTRAINT PK_tid_entity_type PRIMARY KEY (tid,entity,type) +); diff --git a/modules/taxonomy/site/scripts/uninstall.sql b/modules/taxonomy/site/scripts/uninstall.sql new file mode 100644 index 0000000..0efae92 --- /dev/null +++ b/modules/taxonomy/site/scripts/uninstall.sql @@ -0,0 +1,3 @@ +DROP TABLE IF EXISTS taxonomy_term; +DROP TABLE IF EXISTS taxonomy_hierarchy; +DROP TABLE IF EXISTS taxonomy_index; diff --git a/modules/taxonomy/taxonomy-safe.ecf b/modules/taxonomy/taxonomy-safe.ecf new file mode 100644 index 0000000..e4813e9 --- /dev/null +++ b/modules/taxonomy/taxonomy-safe.ecf @@ -0,0 +1,26 @@ + + + + + + /.git$ + /EIFGENs$ + /.svn$ + + + + + + + + + + + + + + + + + diff --git a/modules/taxonomy/taxonomy.ecf b/modules/taxonomy/taxonomy.ecf new file mode 100644 index 0000000..6fa259f --- /dev/null +++ b/modules/taxonomy/taxonomy.ecf @@ -0,0 +1,26 @@ + + + + + + /.git$ + /EIFGENs$ + /.svn$ + + + + + + + + + + + + + + + + + diff --git a/src/configuration/cms_setup.e b/src/configuration/cms_setup.e index 979646e..93e3602 100644 --- a/src/configuration/cms_setup.e +++ b/src/configuration/cms_setup.e @@ -132,14 +132,16 @@ feature {NONE} -- Implementation: update until not a_module.is_enabled loop - if - attached a_collection.item (ic.item) as mod and then - mod.is_enabled - then - update_module_status_within (mod, a_collection) - else - --| dependency not found or disabled - a_module.disable + if ic.item.is_required then + if + attached a_collection.item (ic.item.module_type) as mod and then + mod.is_enabled + then + update_module_status_within (mod, a_collection) + else + --| dependency not found or disabled + a_module.disable + end end end end diff --git a/src/persistence/cms_storage.e b/src/persistence/cms_storage.e index c45d9c1..f49ccbd 100644 --- a/src/persistence/cms_storage.e +++ b/src/persistence/cms_storage.e @@ -27,6 +27,16 @@ feature -- Access api: detachable CMS_API assign set_api -- Associated CMS API. +feature -- Conversion + + as_sql_storage: detachable CMS_STORAGE_SQL_I + -- SQL based variant of `Current' if possible. + do + if attached {CMS_STORAGE_SQL_I} Current as st then + Result := st + end + end + feature -- Status report is_available: BOOLEAN @@ -59,4 +69,7 @@ feature -- Element change api := a_api end +note + copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Eiffel Software and others" + license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)" end diff --git a/src/persistence/sql/cms_storage_sql_i.e b/src/persistence/sql/cms_storage_sql_i.e index 4ddb2f3..16f5444 100644 --- a/src/persistence/sql/cms_storage_sql_i.e +++ b/src/persistence/sql/cms_storage_sql_i.e @@ -236,16 +236,22 @@ feature -- Access sql_start -- Set the cursor on first element. + require + no_error: not has_error deferred end sql_after: BOOLEAN -- Are there no more items to iterate over? + require + no_error: not has_error deferred end sql_forth -- Fetch next row from last sql execution, if any. + require + no_error: not has_error deferred end @@ -255,6 +261,7 @@ feature -- Access sql_item (a_index: INTEGER): detachable ANY require + no_error: not has_error valid_index: sql_valid_item_index (a_index) deferred end @@ -446,7 +453,7 @@ feature {NONE} -- Implementation across l_removals as ic loop - Result.remove_substring (ic.item.start_index - j, ic.item.end_index - j) + Result.remove_substring (ic.item.start_index - j - a_start_index + 1, ic.item.end_index - j - a_start_index + 1) j := j + ic.item.end_index - ic.item.start_index + 1 end -- a_offset.replace (a_offset.item j) diff --git a/src/service/cms_api.e b/src/service/cms_api.e index 18e73ce..8d1b450 100644 --- a/src/service/cms_api.e +++ b/src/service/cms_api.e @@ -107,7 +107,7 @@ feature {NONE} -- Initialize feature {CMS_ACCESS} -- Installation - install + install_all_modules -- Install CMS or uninstalled module which are enabled. local l_module: CMS_MODULE @@ -121,14 +121,30 @@ feature {CMS_ACCESS} -- Installation -- and leave the responsability to the module to know -- if this is installed or not... if not l_module.is_installed (Current) then - l_module.install (Current) - if l_module.is_enabled then - l_module.initialize (Current) - end + install_module (l_module) end end end + install_module (m: CMS_MODULE) + -- Install module `m'. + require + module_not_installed: not is_module_installed (m) + do + m.install (Current) + if m.is_enabled then + m.initialize (Current) + end + end + + uninstall_module (m: CMS_MODULE) + -- Uninstall module `m'. + require + module_installed: is_module_installed (m) + do + m.uninstall (Current) + end + feature -- Access setup: CMS_SETUP diff --git a/src/service/cms_module.e b/src/service/cms_module.e index 8c87807..8d65e6d 100644 --- a/src/service/cms_module.e +++ b/src/service/cms_module.e @@ -29,8 +29,8 @@ feature -- Access version: STRING -- Version of the module? - dependencies: detachable LIST [TYPE [CMS_MODULE]] - -- Optional dependencies. + dependencies: detachable LIST [TUPLE [module_type: TYPE [CMS_MODULE]; is_required: BOOLEAN]] + -- Optional declaration for dependencies. permissions: LIST [READABLE_STRING_8] -- List of permission ids, used by this module, and declared. @@ -55,16 +55,22 @@ feature {CMS_API} -- Module Initialization end add_dependency (a_type: TYPE [CMS_MODULE]) - -- Add dependency using type of module `a_type'. - local - deps: like dependencies + -- Add required dependency using type of module `a_type'. do - deps := dependencies - if deps = Void then - create {ARRAYED_LIST [TYPE [CMS_MODULE]]} deps.make (1) - dependencies := deps + put_dependency (a_type, True) + end + + put_dependency (a_type: TYPE [CMS_MODULE]; is_required: BOOLEAN) + -- Add required or optional dependency using type of module `a_type', based on `is_required' value. + local + lst: like dependencies + do + lst := dependencies + if lst = Void then + create {ARRAYED_LIST [TUPLE [module_type: TYPE [CMS_MODULE]; is_required: BOOLEAN]]} lst.make (1) + dependencies := lst end - deps.force (a_type) + lst.force ([a_type, is_required]) end feature -- Status diff --git a/src/service/handler/cms_admin_install_handler.e b/src/service/handler/cms_admin_install_handler.e index 9b61fa2..f5f4b45 100644 --- a/src/service/handler/cms_admin_install_handler.e +++ b/src/service/handler/cms_admin_install_handler.e @@ -45,6 +45,7 @@ feature -- HTTP Methods lst: ARRAYED_LIST [CMS_MODULE] l_denied: BOOLEAN do + --| FIXME: improve the installer. create {GENERIC_VIEW_CMS_RESPONSE} r.make (req, res, api) if attached api.setup.string_8_item ("admin.installation_access") as l_access then if l_access.is_case_insensitive_equal ("none") then @@ -77,7 +78,7 @@ feature -- HTTP Methods lst.force (l_module) end end - api.install + api.install_all_modules across lst as ic loop