From 133c243126ff0c38e4fe70a985a62aef1b9302a0 Mon Sep 17 00:00:00 2001 From: Jocelyn Fiat Date: Tue, 14 Apr 2015 11:25:02 +0200 Subject: [PATCH] Implemented CMS storage for user and nodes. Implemented role and permission storage. Introduced the CMS_PARTIAL_NODE and CMS_PARTIAL_USER. Added support for node storage extension - storage of data specific to each node content type, - in addition to the core CMS_NODE) - For now, only implemented for SQL storage. Note: in current version, CMS_PAGE support is hard coded in the core, (as opposed to be only supported by the node module.) Commented/removed for now, the Web API code to update node summary, title, via REST request. --- .../site/scripts/{sqlite.sql => core.sql} | 29 +- examples/demo/site/scripts/node.sql | 22 ++ .../site/themes/bootstrap/assets/css/node.css | 15 + .../themes/bootstrap/assets/scss/node.scss | 14 + examples/demo/site/themes/bootstrap/debug.tpl | 40 ++- examples/demo/site/themes/bootstrap/page.tpl | 3 +- library/model/src/content/cms_content_type.e | 23 +- library/model/src/content/cms_node.e | 135 ++++---- library/model/src/content/cms_partial_node.e | 84 +++++ library/model/src/user/cms_partial_user.e | 19 ++ library/model/src/user/cms_user.e | 15 +- library/model/src/user/cms_user_role.e | 22 +- .../store/cms_storage_store_sql.e | 5 + .../mysql/src/cms_node_storage_mysql.e | 22 +- .../sqlite/src/cms_node_storage_sqlite.e | 18 +- .../sqlite/src/cms_storage_sqlite_builder.e | 50 +-- src/kernel/content/format/cms_formats.e | 19 +- .../basic_auth/filter/basic_auth_filter.e | 2 - src/modules/cms_module.e | 34 +- src/modules/node/cms_node_api.e | 128 ++++++-- src/modules/node/content_type/cms_page.e | 95 ++++++ .../node/content_type/cms_page_content_type.e | 32 ++ .../node/content_type/cms_partial_page.e | 17 + .../{ => experimental}/node_content_handler.e | 0 .../experimental/node_resource_handler.e | 240 ++++++++++++++ .../experimental/node_resources_handler.e | 75 +++++ .../{ => experimental}/node_summary_handler.e | 0 .../{ => experimental}/node_title_handler.e | 0 src/modules/node/handler/node_handler.e | 73 ++++- src/modules/node/handler/nodes_handler.e | 14 +- src/modules/node/node_module.e | 249 +++++++++++---- src/persistence/cms_proxy_storage_sql.e | 112 +++++++ src/persistence/cms_storage.e | 13 + src/persistence/cms_storage_null.e | 17 + src/persistence/cms_storage_sql.e | 69 +++- src/persistence/node/cms_node_storage.e | 119 +++++-- .../node/cms_node_storage_extension.e | 53 ++++ src/persistence/node/cms_node_storage_sql.e | 277 ++++++++-------- .../cms_node_storage_sql_page_extension.e | 111 +++++++ src/persistence/user/cms_user_storage.e | 52 +-- src/persistence/user/cms_user_storage_sql.e | 295 +++++++++++++++++- src/service/cms_api.e | 31 +- src/service/cms_module_api.e | 12 +- src/service/handler/cms_module_handler.e | 3 +- src/service/response/cms_response.e | 7 - src/service/user/cms_user_api.e | 65 ++++ 46 files changed, 2262 insertions(+), 468 deletions(-) rename examples/demo/site/scripts/{sqlite.sql => core.sql} (53%) create mode 100644 examples/demo/site/scripts/node.sql create mode 100644 examples/demo/site/themes/bootstrap/assets/css/node.css create mode 100644 examples/demo/site/themes/bootstrap/assets/scss/node.scss create mode 100644 library/model/src/content/cms_partial_node.e create mode 100644 library/model/src/user/cms_partial_user.e create mode 100644 src/modules/node/content_type/cms_page.e create mode 100644 src/modules/node/content_type/cms_page_content_type.e create mode 100644 src/modules/node/content_type/cms_partial_page.e rename src/modules/node/handler/{ => experimental}/node_content_handler.e (100%) create mode 100644 src/modules/node/handler/experimental/node_resource_handler.e create mode 100644 src/modules/node/handler/experimental/node_resources_handler.e rename src/modules/node/handler/{ => experimental}/node_summary_handler.e (100%) rename src/modules/node/handler/{ => experimental}/node_title_handler.e (100%) create mode 100644 src/persistence/cms_proxy_storage_sql.e create mode 100644 src/persistence/node/cms_node_storage_extension.e create mode 100644 src/persistence/node/cms_node_storage_sql_page_extension.e diff --git a/examples/demo/site/scripts/sqlite.sql b/examples/demo/site/scripts/core.sql similarity index 53% rename from examples/demo/site/scripts/sqlite.sql rename to examples/demo/site/scripts/core.sql index ab6db34..f1b4ba0 100644 --- a/examples/demo/site/scripts/sqlite.sql +++ b/examples/demo/site/scripts/core.sql @@ -12,23 +12,22 @@ CREATE TABLE "users"( UNIQUE("name") ); -CREATE TABLE "users_roles"( +CREATE TABLE "roles"( "rid" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL CHECK("rid">=0), - "role" VARCHAR(100) NOT NULL, - CONSTRAINT "role" - UNIQUE("role") + "name" VARCHAR(100) NOT NULL, + CONSTRAINT "name" + UNIQUE("name") ); -CREATE TABLE "nodes"( - "nid" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL CHECK("nid">=0), - "version" INTEGER, - "type" INTEGER, - "title" VARCHAR(255) NOT NULL, - "summary" TEXT NOT NULL, - "content" MEDIUMTEXT NOT NULL, - "author" INTEGER, - "publish" DATETIME, - "created" DATETIME NOT NULL, - "changed" DATETIME NOT NULL +CREATE TABLE "users_roles"( + "uid" INTEGER NOT NULL CHECK("uid">=0), + "rid" INTEGER NOT NULL CHECK("rid">=0) ); + +CREATE TABLE "role_permissions"( + "rid" INTEGER NOT NULL CHECK("rid">=0), + "permission" VARCHAR(255) NOT NULL, + "module" VARCHAR(255) +); + COMMIT; diff --git a/examples/demo/site/scripts/node.sql b/examples/demo/site/scripts/node.sql new file mode 100644 index 0000000..6dc2792 --- /dev/null +++ b/examples/demo/site/scripts/node.sql @@ -0,0 +1,22 @@ +BEGIN; + +CREATE TABLE "nodes"( + "nid" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL CHECK("nid">=0), + "revision" INTEGER, + "type" TEXT NOT NULL, + "title" VARCHAR(255) NOT NULL, + "summary" TEXT NOT NULL, + "content" MEDIUMTEXT NOT NULL, + "author" INTEGER, + "publish" DATETIME, + "created" DATETIME NOT NULL, + "changed" DATETIME NOT NULL +); + +CREATE TABLE page_nodes( + "nid" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL CHECK("nid">=0), + "revision" INTEGER, + "parent" INTEGER +); + +COMMIT; diff --git a/examples/demo/site/themes/bootstrap/assets/css/node.css b/examples/demo/site/themes/bootstrap/assets/css/node.css new file mode 100644 index 0000000..7888687 --- /dev/null +++ b/examples/demo/site/themes/bootstrap/assets/css/node.css @@ -0,0 +1,15 @@ +ul.cms-nodes { + list-style-type: none; + padding: 3px 3px 3px 3px; + border: solid 1px #ccc; +} + +li.cms_type_page { + border-top: dotted 1px #ccc; +} +li.cms_type_page a::before { + content: "[page] "; +} +li.cms_type_page:first-child { + border-top: none; +} diff --git a/examples/demo/site/themes/bootstrap/assets/scss/node.scss b/examples/demo/site/themes/bootstrap/assets/scss/node.scss new file mode 100644 index 0000000..cd7408f --- /dev/null +++ b/examples/demo/site/themes/bootstrap/assets/scss/node.scss @@ -0,0 +1,14 @@ +ul.cms-nodes { + list-style-type: none; + padding: 3px 3px 3px 3px; + border: solid 1px #ccc; +} +li.cms_type_page { + a::before { + content: "[page] "; + } + border-top: dotted 1px #ccc; + &:first-child { + border-top: none; + } +} diff --git a/examples/demo/site/themes/bootstrap/debug.tpl b/examples/demo/site/themes/bootstrap/debug.tpl index 6031876..b689091 100644 --- a/examples/demo/site/themes/bootstrap/debug.tpl +++ b/examples/demo/site/themes/bootstrap/debug.tpl @@ -1,14 +1,38 @@ {assign name="debug_enabled" value="True"/} {if condition="$debug_enabled"} + +{literal} + +{/literal} +
Show debug +
+ {/if} diff --git a/examples/demo/site/themes/bootstrap/page.tpl b/examples/demo/site/themes/bootstrap/page.tpl index 577af62..03037f7 100644 --- a/examples/demo/site/themes/bootstrap/page.tpl +++ b/examples/demo/site/themes/bootstrap/page.tpl @@ -5,6 +5,7 @@ + @@ -70,8 +71,6 @@ - diff --git a/library/model/src/content/cms_content_type.e b/library/model/src/content/cms_content_type.e index 2b58002..695f3f3 100644 --- a/library/model/src/content/cms_content_type.e +++ b/library/model/src/content/cms_content_type.e @@ -6,10 +6,29 @@ note date: "$Date$" revision: "$Revision$" -class +deferred class CMS_CONTENT_TYPE +feature -- Access + + name: READABLE_STRING_8 + -- Internal name. + deferred + end + + title: READABLE_STRING_32 + -- Human readable name. + deferred + end + +feature -- Factory + + new_node (a_partial_node: detachable CMS_NODE): CMS_NODE + -- New node based on partial `a_partial_node', or from none. + deferred + end + note - copyright: "2011-2014, Javier Velilla, Jocelyn Fiat, Eiffel Software and others" + copyright: "2011-2015, Javier Velilla, Jocelyn Fiat, Eiffel Software and others" license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)" end diff --git a/library/model/src/content/cms_node.e b/library/model/src/content/cms_node.e index 6956776..5828c60 100644 --- a/library/model/src/content/cms_node.e +++ b/library/model/src/content/cms_node.e @@ -6,33 +6,31 @@ note date: "$Date: 2015-01-27 19:15:02 +0100 (mar., 27 janv. 2015) $" revision: "$Revision: 96542 $" -class +deferred class CMS_NODE inherit REFACTORING_HELPER -create - make, - make_empty +--create +-- make, +-- make_empty feature{NONE} -- Initialization make_empty -- Create empty node. do - make ({STRING_32} "", {STRING_32} "", {STRING_32} "") + make ({STRING_32} "") end - make (a_content: READABLE_STRING_32; a_summary: READABLE_STRING_32; a_title: READABLE_STRING_32) - -- Create current node with `a_content', `a_summary' and `a_title'. + make (a_title: READABLE_STRING_32) + -- Create current node with `a_title'. local l_time: DATE_TIME do create l_time.make_now_utc - set_content (a_content) - set_summary (a_summary) set_title (a_title) set_creation_date (l_time) set_modification_date (l_time) @@ -41,32 +39,68 @@ feature{NONE} -- Initialization debug ("refactor_fixme") fixme ("Remove default harcoded format") end - set_format ("HTML") - - debug ("refactor_fixme") - fixme ("Remove default harcoded content type") - end - set_content_type ("Page") ensure - content_set: content = a_content - summary_set: summary = a_summary title_set: title = a_title end +feature {CMS_CONTENT_TYPE} -- Conversion + + import_node (a_node: CMS_NODE) + -- Import `a_node' into current node. + do + set_id (a_node.id) + set_revision (a_node.revision) + set_title (a_node.title) + set_creation_date (a_node.creation_date) + set_modification_date (a_node.modification_date) + set_publication_date (a_node.publication_date) + set_author (a_node.author) + set_content ( + a_node.content, + a_node.summary, + a_node.format + ) + end + feature -- Access id: INTEGER_64 assign set_id -- Unique id. --| Should we use NATURAL_64 instead? - content: READABLE_STRING_32 - -- Content of the node. + revision: INTEGER_64 assign set_revision + -- Revision value. + --| Note: for now version is not supported. - summary: READABLE_STRING_32 - -- A short summary of the node. + content_type: READABLE_STRING_8 + -- Associated content type name. + -- Page, Article, Blog, News, etc. + deferred + end + +feature -- Access title: READABLE_STRING_32 -- Full title of the node. + -- Required! + + summary: detachable READABLE_STRING_8 + -- A short summary of the node. + deferred + end + + content: detachable READABLE_STRING_8 + -- Content of the node. + deferred + end + + format: detachable READABLE_STRING_8 + -- Format associated with `content' and `summary'. + -- For example: text, mediawiki, html, etc + deferred + end + +feature -- Access: date modification_date: DATE_TIME -- When the node was updated. @@ -80,13 +114,7 @@ feature -- Access publication_date_output: READABLE_STRING_32 -- Formatted output. - format: READABLE_STRING_32 - -- Format associated with `body'. - -- For example: text, mediawiki, html, etc - - content_type: READABLE_STRING_32 - -- Associated content type name. - -- Page, Article, Blog, News, etc. +feature -- Access: author author: detachable CMS_USER -- Author of current node. @@ -99,22 +127,31 @@ feature -- status report Result := id > 0 end -feature -- Element change - - set_content (a_content: like content) - -- Assign `content' with `a_content'. + same_node (a_node: CMS_NODE): BOOLEAN + -- Is `a_node' same node as Current? do - content := a_content + -- FIXME: if we introduce notion of revision, update this part! + Result := Current = a_node or id = a_node.id ensure - content_assigned: content = a_content + valid_result: Result implies a_node.id = id end - set_summary (a_summary: like summary) - -- Assign `summary' with `a_summary'. + is_typed_as (a_content_type: READABLE_STRING_GENERAL): BOOLEAN + -- Is current node of type `a_content_type' ? do - summary := a_summary + Result := a_content_type.is_case_insensitive_equal (content_type) + end + +feature -- Element change + + set_content (a_content: like content; a_summary: like summary; a_format: like format) + -- Set `content', `summary' and `format' to `a_content', `a_summary' and `a_format'. + -- The `format' is associated with both `content' and `summary' + deferred ensure + content_assigned: content = a_content summary_assigned: summary = a_summary + format_assigned: format = a_format end set_title (a_title: like title) @@ -150,28 +187,20 @@ feature -- Element change publication_date_assigned: publication_date = a_publication_date end - set_content_type (a_content_type: like content_type) - -- Assign `content_type' with `a_content_type'. + set_id (a_id: like id) + -- Assign `id' with `a_id'. do - content_type := a_content_type + id := a_id ensure - content_type_assigned: content_type = a_content_type + id_assigned: id = a_id end - set_format (a_format: like format) - -- Assign `format' with `a_format'. + set_revision (a_revision: like revision) + -- Assign `revision' with `a_revision'. do - format := a_format + revision := a_revision ensure - format_assigned: format = a_format - end - - set_id (an_id: like id) - -- Assign `id' with `an_id'. - do - id := an_id - ensure - id_assigned: id = an_id + revision_assigned: revision = a_revision end set_author (u: like author) diff --git a/library/model/src/content/cms_partial_node.e b/library/model/src/content/cms_partial_node.e new file mode 100644 index 0000000..60ac7b1 --- /dev/null +++ b/library/model/src/content/cms_partial_node.e @@ -0,0 +1,84 @@ +note + description: "Node object representing the CMS_NODE data in database." + date: "$Date$" + revision: "$Revision$" + +class + CMS_PARTIAL_NODE + +inherit + CMS_NODE + rename + make_empty as make_empty_node, + set_content as set_all_content + end + +create + make_empty + +feature {NONE} -- Initialization + + make_empty (a_content_type: READABLE_STRING_8) + require + type_not_blank: not a_content_type.is_whitespace + do + content_type := a_content_type + make_empty_node + end + +feature -- Access: code + + content_type: READABLE_STRING_8 + -- + +feature -- Access: content + + summary: detachable READABLE_STRING_8 + -- A short summary of the node. + + content: detachable READABLE_STRING_8 + -- Content of the node. + + format: detachable READABLE_STRING_8 + -- Format associated with `content' and `summary'. + -- For example: text, mediawiki, html, etc + +feature -- Element change + + set_all_content (a_content: like content; a_summary: like summary; a_format: like format) + do + set_content (a_content) + set_summary (a_summary) + set_format (a_format) + end + + set_content (a_content: like content) + -- Assign `content' with `a_content', and set the associated `format'. + do + content := a_content + ensure + content_assigned: content = a_content + end + + set_summary (a_summary: like summary) + -- Assign `summary' with `a_summary'. + do + summary := a_summary + ensure + summary_assigned: summary = a_summary + end + + set_format (a_format: like format) + -- Assign `format' with `a_format'. + do + format := a_format + ensure + format_assigned: format = a_format + end + +invariant + +note + copyright: "2011-2015, Javier Velilla, Jocelyn Fiat, Eiffel Software and others" + license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)" +end diff --git a/library/model/src/user/cms_partial_user.e b/library/model/src/user/cms_partial_user.e new file mode 100644 index 0000000..c2a7e32 --- /dev/null +++ b/library/model/src/user/cms_partial_user.e @@ -0,0 +1,19 @@ +note + description: "Partial CMS USER." + date: "$Date$" + revision: "$Revision$" + +class + CMS_PARTIAL_USER + +inherit + CMS_USER + +create + make, + make_with_id -- MAYBE: export to CMS_STORAGE + +note + copyright: "2011-2015, Javier Velilla, Jocelyn Fiat, Eiffel Software and others" + license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)" +end diff --git a/library/model/src/user/cms_user.e b/library/model/src/user/cms_user.e index 6491893..5abb436 100644 --- a/library/model/src/user/cms_user.e +++ b/library/model/src/user/cms_user.e @@ -71,6 +71,11 @@ feature -- Access last_login_date: detachable DATE_TIME -- User last login. +feature -- Roles + + roles: detachable LIST [CMS_USER_ROLE] + -- If set, list of roles for current user. + feature -- Access: data data: detachable STRING_TABLE [detachable ANY] @@ -189,6 +194,14 @@ feature -- Change element set_last_login_date (create {DATE_TIME}.make_now_utc) end +feature -- Element change: roles + + set_roles (lst: like roles) + -- Set `roles' to `lst'. + do + roles := lst + end + feature -- Change element: data set_data_item (k: READABLE_STRING_GENERAL; d: like data_item) @@ -217,6 +230,6 @@ invariant id_or_name_set: id > 0 or else not name.is_whitespace note - copyright: "2011-2014, Javier Velilla, Jocelyn Fiat, Eiffel Software and others" + copyright: "2011-2015, Javier Velilla, Jocelyn Fiat, Eiffel Software and others" license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)" end diff --git a/library/model/src/user/cms_user_role.e b/library/model/src/user/cms_user_role.e index 0b7b663..e44c08f 100644 --- a/library/model/src/user/cms_user_role.e +++ b/library/model/src/user/cms_user_role.e @@ -22,15 +22,25 @@ create feature {NONE} -- Initialization - make_with_id (a_id: like id; a_name: like name) + make_with_id (a_id: like id) + -- Create current role with role id `a_id'. do id := a_id - make (a_name) + name := {STRING_32} "" + initialize end make (a_name: like name) + -- Create current role with name `a_name'. + require + a_name_valid: not a_name.is_whitespace do name := a_name + initialize + end + + initialize + do create {ARRAYED_LIST [READABLE_STRING_8]} permissions.make (0) end @@ -42,10 +52,10 @@ feature -- Status report Result := id > 0 end - has_permission (p: READABLE_STRING_8): BOOLEAN + has_permission (p: READABLE_STRING_GENERAL): BOOLEAN -- Has permission `p'? do - Result := across permissions as c some c.item.is_case_insensitive_equal (p) end + Result := across permissions as c some p.is_case_insensitive_equal (c.item) end end feature -- Access @@ -53,7 +63,7 @@ feature -- Access id: INTEGER -- Unique id associated with Current role. - name: READABLE_STRING_8 + name: READABLE_STRING_32 -- Name of Current role. permissions: LIST [READABLE_STRING_8] @@ -115,6 +125,6 @@ feature -- Permission change end note - copyright: "2011-2014, Javier Velilla, Jocelyn Fiat, Eiffel Software and others" + copyright: "2011-2015, Javier Velilla, Jocelyn Fiat, Eiffel Software and others" license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)" end diff --git a/library/persistence/implementation/store/cms_storage_store_sql.e b/library/persistence/implementation/store/cms_storage_store_sql.e index d5511bd..edfbac2 100644 --- a/library/persistence/implementation/store/cms_storage_store_sql.e +++ b/library/persistence/implementation/store/cms_storage_store_sql.e @@ -109,6 +109,11 @@ feature -- Query db_handler.forth end + sql_valid_item_index (a_index: INTEGER): BOOLEAN + do + Result := attached {DB_TUPLE} db_handler.item as l_item and then l_item.valid_index (a_index) + end + sql_item (a_index: INTEGER): detachable ANY do if attached {DB_TUPLE} db_handler.item as l_item and then l_item.count >= a_index then diff --git a/library/persistence/mysql/src/cms_node_storage_mysql.e b/library/persistence/mysql/src/cms_node_storage_mysql.e index b125f0d..ee0302a 100644 --- a/library/persistence/mysql/src/cms_node_storage_mysql.e +++ b/library/persistence/mysql/src/cms_node_storage_mysql.e @@ -26,7 +26,9 @@ feature -- Access do create {ARRAYED_LIST [CMS_NODE]} Result.make (0) across nodes_iterator as ic loop - Result.force (ic.item) + if attached ic.item as l_node then + Result.force (l_node) + end end end @@ -34,27 +36,29 @@ feature -- Access -- List of recent `a_count' nodes with an offset of `lower'. do create {ARRAYED_LIST [CMS_NODE]} Result.make (a_count) - across recent_nodes_iterator (a_lower, a_count) as c loop - Result.force (c.item) + across recent_nodes_iterator (a_lower, a_count) as ic loop + if attached ic.item as l_node then + Result.force (l_node) + end end end feature -- Access: iterator - nodes_iterator: DATABASE_ITERATION_CURSOR [CMS_NODE] + nodes_iterator: DATABASE_ITERATION_CURSOR [detachable CMS_NODE] -- List of nodes. local l_parameters: STRING_TABLE [ANY] do error_handler.reset - log.write_information (generator + ".nodes_iterator") + write_information_log (generator + ".nodes_iterator") create l_parameters.make (0) - sql_query (select_nodes, l_parameters) + sql_query (sql_select_nodes, l_parameters) create Result.make (db_handler, agent fetch_node) sql_post_execution end - recent_nodes_iterator (a_lower, a_rows: INTEGER): DATABASE_ITERATION_CURSOR [CMS_NODE] + recent_nodes_iterator (a_lower, a_rows: INTEGER): DATABASE_ITERATION_CURSOR [detachable CMS_NODE] -- The most recent `a_rows'. local l_parameters: STRING_TABLE [ANY] @@ -62,11 +66,11 @@ feature -- Access: iterator do -- FIXME: check implementation... error_handler.reset - log.write_information (generator + ".recent_nodes_iterator") + write_information_log (generator + ".recent_nodes_iterator") create l_parameters.make (2) l_parameters.put (a_rows, "rows") l_parameters.put (a_lower, "offset") - create l_query.make_from_string (select_recent_nodes) + create l_query.make_from_string (sql_select_recent_nodes) sql_query (l_query, l_parameters) create Result.make (db_handler, agent fetch_node) sql_post_execution diff --git a/library/persistence/sqlite/src/cms_node_storage_sqlite.e b/library/persistence/sqlite/src/cms_node_storage_sqlite.e index 5d9b150..60d6d54 100644 --- a/library/persistence/sqlite/src/cms_node_storage_sqlite.e +++ b/library/persistence/sqlite/src/cms_node_storage_sqlite.e @@ -26,7 +26,9 @@ feature -- Access do create {ARRAYED_LIST [CMS_NODE]} Result.make (0) across nodes_iterator as ic loop - Result.force (ic.item) + if attached ic.item as l_node then + Result.force (l_node) + end end end @@ -34,14 +36,16 @@ feature -- Access -- List of recent `a_count' nodes with an offset of `lower'. do create {ARRAYED_LIST [CMS_NODE]} Result.make (a_count) - across recent_nodes_iterator (a_lower, a_count) as c loop - Result.force (c.item) + across recent_nodes_iterator (a_lower, a_count) as ic loop + if attached ic.item as l_node then + Result.force (l_node) + end end end feature -- Access: iterator - nodes_iterator: DATABASE_ITERATION_CURSOR [CMS_NODE] + nodes_iterator: DATABASE_ITERATION_CURSOR [detachable CMS_NODE] -- List of nodes. local l_parameters: STRING_TABLE [ANY] @@ -49,11 +53,11 @@ feature -- Access: iterator error_handler.reset write_information_log (generator + ".nodes_iterator") create l_parameters.make (0) - sql_query (select_nodes, l_parameters) + sql_query (sql_select_nodes, l_parameters) create Result.make (db_handler, agent fetch_node) end - recent_nodes_iterator (a_lower, a_rows: INTEGER): DATABASE_ITERATION_CURSOR [CMS_NODE] + recent_nodes_iterator (a_lower, a_rows: INTEGER): DATABASE_ITERATION_CURSOR [detachable CMS_NODE] -- The most recent `a_rows'. local l_parameters: STRING_TABLE [ANY] @@ -65,7 +69,7 @@ feature -- Access: iterator create l_parameters.make (2) l_parameters.put (a_rows, "rows") l_parameters.put (a_lower, "offset") - create l_query.make_from_string (select_recent_nodes) + create l_query.make_from_string (sql_select_recent_nodes) sql_query (l_query, l_parameters) create Result.make (db_handler, agent fetch_node) end diff --git a/library/persistence/sqlite/src/cms_storage_sqlite_builder.e b/library/persistence/sqlite/src/cms_storage_sqlite_builder.e index 3feae38..efdad97 100644 --- a/library/persistence/sqlite/src/cms_storage_sqlite_builder.e +++ b/library/persistence/sqlite/src/cms_storage_sqlite_builder.e @@ -45,47 +45,27 @@ feature -- Factory end initialize (a_setup: CMS_SETUP; a_storage: CMS_STORAGE_STORE_SQL) - do - initialize_schema (a_setup, a_storage) - initialize_data (a_setup, a_storage) - end - - initialize_schema (a_setup: CMS_SETUP; a_storage: CMS_STORAGE_STORE_SQL) - local - p: PATH - f: PLAIN_TEXT_FILE - sql: STRING - do - p := a_setup.layout.path.extended ("scripts").extended ("sqlite.sql") - create f.make_with_path (p) - if f.exists and then f.is_access_readable then - create sql.make (f.count) - f.open_read - from - f.start - until - f.exhausted or f.end_of_file - loop - f.read_stream_thread_aware (1_024) - sql.append (f.last_string) - end - f.close - a_storage.error_handler.reset --- a_storage.sql_begin_transaction - a_storage.sql_change (sql, Void) --- a_storage.sql_commit_transaction - end - end - - initialize_data (a_setup: CMS_SETUP; a_storage: CMS_STORAGE_STORE_SQL) local u: CMS_USER + r: CMS_USER_ROLE do + -- Schema + a_storage.sql_execute_file_script (a_setup.layout.path.extended ("scripts").extended ("core.sql")) + + -- Data + -- Users create u.make ("admin") - u.set_password ("#admin#") + u.set_password ("istrator#") u.set_email (a_setup.site_email) a_storage.new_user (u) + + -- Roles + create r.make ("anonymous") + a_storage.save_user_role (r) + create r.make ("authenticated") + r.add_permission ("create page") + r.add_permission ("edit page") + a_storage.save_user_role (r) end - end diff --git a/src/kernel/content/format/cms_formats.e b/src/kernel/content/format/cms_formats.e index 3a5d52d..b4711c7 100644 --- a/src/kernel/content/format/cms_formats.e +++ b/src/kernel/content/format/cms_formats.e @@ -9,15 +9,18 @@ class feature -- Access - format (a_name: like {CONTENT_FORMAT}.name): detachable CONTENT_FORMAT + item (a_name: detachable READABLE_STRING_GENERAL): CONTENT_FORMAT do - across - all_formats as c - until - Result /= Void - loop - if c.item.name.same_string (a_name) then - Result := c.item + Result := default_format + if a_name /= Void then + across + all_formats as c + until + Result /= Void + loop + if a_name.same_string (c.item.name) then + Result := c.item + end end end end diff --git a/src/modules/basic_auth/filter/basic_auth_filter.e b/src/modules/basic_auth/filter/basic_auth_filter.e index 9c47c03..158d4be 100644 --- a/src/modules/basic_auth/filter/basic_auth_filter.e +++ b/src/modules/basic_auth/filter/basic_auth_filter.e @@ -21,8 +21,6 @@ feature -- Basic operations local l_auth: HTTP_AUTHORIZATION do - -- We can inherit from SHARED_LOGGER and write - -- write_debug_log ... api.logger.put_debug (generator + ".execute ", Void) create l_auth.make (req.http_authorization) if attached req.raw_header_data as l_raw_data then diff --git a/src/modules/cms_module.e b/src/modules/cms_module.e index ed05acb..2964057 100644 --- a/src/modules/cms_module.e +++ b/src/modules/cms_module.e @@ -7,7 +7,6 @@ deferred class CMS_MODULE inherit - REFACTORING_HELPER feature -- Access @@ -27,6 +26,39 @@ feature -- Access version: STRING -- Version od the module? +feature {CMS_API} -- Module Initialization + + initialize (api: CMS_API) + -- Initialize Current module with `api'. + require + is_enabled: is_enabled + do + -- Redefine to process specific module initialization. + end + +feature {CMS_API} -- Module management + + is_installed (api: CMS_API): BOOLEAN + -- Is Current module installed? + do + Result := is_enabled + -- FIXME: implement proper installation status. + end + + install (api: CMS_API) + require + is_not_installed: not is_installed (api) + do + -- Not Yet Supported + end + + uninstall (api: CMS_API) + require + is_installed: is_installed (api) + do + -- Not Yet Supported + end + feature -- Router router (a_api: CMS_API): WSF_ROUTER diff --git a/src/modules/node/cms_node_api.e b/src/modules/node/cms_node_api.e index 6b13dc3..e94851d 100644 --- a/src/modules/node/cms_node_api.e +++ b/src/modules/node/cms_node_api.e @@ -1,6 +1,7 @@ note - description: "Summary description for {CMS_NODE_API}." - author: "" + description: "[ + API to manage CMS Nodes + ]" date: "$Date: 2015-02-13 13:08:13 +0100 (ven., 13 févr. 2015) $" revision: "$Revision: 96616 $" @@ -9,12 +10,55 @@ class inherit CMS_MODULE_API + redefine + initialize + end REFACTORING_HELPER create make +feature {NONE} -- Implementation + + initialize + -- + do + Precursor + initialize_content_types + end + + initialize_content_types + -- Initialize content type system. + do + create content_types.make (1) + add_content_type (create {CMS_PAGE_CONTENT_TYPE}) + end + +feature -- Content type + + content_types: ARRAYED_LIST [CMS_CONTENT_TYPE] + -- Available content types + + add_content_type (a_type: CMS_CONTENT_TYPE) + do + content_types.force (a_type) + end + + content_type (a_name: READABLE_STRING_GENERAL): detachable CMS_CONTENT_TYPE + do + across + content_types as ic + until + Result /= Void + loop + Result := ic.item + if not a_name.is_case_insensitive_equal (Result.name) then + Result := Void + end + end + end + feature -- Access: Node nodes_count: INTEGER_64 @@ -40,7 +84,37 @@ feature -- Access: Node debug ("refactor_fixme") fixme ("Check preconditions") end - Result := storage.node_by_id (a_id) + Result := full_node (storage.node_by_id (a_id)) + end + + full_node (a_node: detachable CMS_NODE): detachable CMS_NODE + -- If `a_node' is partial, return the full node from `a_node', + -- otherwise return directly `a_node'. + do + if attached {CMS_PARTIAL_NODE} a_node as l_partial_node then + if attached content_type (l_partial_node.content_type) as ct then + Result := ct.new_node (l_partial_node) + storage.fill_node (Result) + else + Result := l_partial_node + end + else + Result := a_node + end + + -- Update partial user if needed. + if + Result /= Void and then + attached {CMS_PARTIAL_USER} Result.author as l_partial_author + then + if attached cms_api.user_api.user_by_id (l_partial_author.id) as l_author then + Result.set_author (l_author) + else + check + valid_author_id: False + end + end + end end feature -- Change: Node @@ -67,32 +141,32 @@ feature -- Change: Node storage.update_node (a_node) end - update_node_title (a_user_id: like {CMS_USER}.id; a_node_id: like {CMS_NODE}.id; a_title: READABLE_STRING_32) - -- Update node title, with user identified by `a_id', with node id `a_node_id' and a new title `a_title'. - do - debug ("refactor_fixme") - fixme ("Check preconditions") - end - storage.update_node_title (a_user_id, a_node_id, a_title) - end +-- update_node_title (a_user_id: like {CMS_USER}.id; a_node_id: like {CMS_NODE}.id; a_title: READABLE_STRING_32) +-- -- Update node title, with user identified by `a_id', with node id `a_node_id' and a new title `a_title'. +-- do +-- debug ("refactor_fixme") +-- fixme ("Check preconditions") +-- end +-- storage.update_node_title (a_user_id, a_node_id, a_title) +-- end - update_node_summary (a_user_id: like {CMS_USER}.id; a_node_id: like {CMS_NODE}.id; a_summary: READABLE_STRING_32) - -- Update node summary, with user identified by `a_user_id', with node id `a_node_id' and a new summary `a_summary'. - do - debug ("refactor_fixme") - fixme ("Check preconditions") - end - storage.update_node_summary (a_user_id, a_node_id, a_summary) - end +-- update_node_summary (a_user_id: like {CMS_USER}.id; a_node_id: like {CMS_NODE}.id; a_summary: READABLE_STRING_32) +-- -- Update node summary, with user identified by `a_user_id', with node id `a_node_id' and a new summary `a_summary'. +-- do +-- debug ("refactor_fixme") +-- fixme ("Check preconditions") +-- end +-- storage.update_node_summary (a_user_id, a_node_id, a_summary) +-- end - update_node_content (a_user_id: like {CMS_USER}.id; a_node_id: like {CMS_NODE}.id; a_content: READABLE_STRING_32) - -- Update node content, with user identified by `a_user_id', with node id `a_node_id' and a new content `a_content'. - do - debug ("refactor_fixme") - fixme ("Check preconditions") - end - storage.update_node_content (a_user_id, a_node_id, a_content) - end +-- update_node_content (a_user_id: like {CMS_USER}.id; a_node_id: like {CMS_NODE}.id; a_content: READABLE_STRING_32) +-- -- Update node content, with user identified by `a_user_id', with node id `a_node_id' and a new content `a_content'. +-- do +-- debug ("refactor_fixme") +-- fixme ("Check preconditions") +-- end +-- storage.update_node_content (a_user_id, a_node_id, a_content) +-- end end diff --git a/src/modules/node/content_type/cms_page.e b/src/modules/node/content_type/cms_page.e new file mode 100644 index 0000000..995830a --- /dev/null +++ b/src/modules/node/content_type/cms_page.e @@ -0,0 +1,95 @@ +note + description: "A page node." + date: "$Date$" + revision: "$Revision$" + +class + CMS_PAGE + +inherit + CMS_NODE + redefine + make_empty, + import_node + end + +create + make_empty, + make + +feature {NONE} -- Initialization + + make_empty + do + Precursor + end + +feature {CMS_CONTENT_TYPE} -- Conversion + + import_node (a_node: CMS_NODE) + -- + do + Precursor (a_node) + if attached {CMS_PAGE} a_node as l_page then + set_parent (l_page) + end + end + +feature -- Access + + content_type: READABLE_STRING_8 + once + Result := {CMS_PAGE_CONTENT_TYPE}.name + end + +feature -- Access: content + + summary: detachable READABLE_STRING_8 + -- A short summary of the node. + + content: detachable READABLE_STRING_8 + -- Content of the node. + + format: detachable READABLE_STRING_8 + -- Format associated with `content' and `summary'. + -- For example: text, mediawiki, html, etc + + parent: detachable CMS_PAGE + -- Eventual parent page. + --| Used to describe a book structure. + +feature -- Element change + + set_content (a_content: like content; a_summary: like summary; a_format: like format) + do + content := a_content + summary := a_summary + format := a_format + end + + set_parent (a_page: detachable CMS_PAGE) + -- Set `parent' to `a_page' + require + Current_is_not_parent_of_a_page: not is_parent_of (a_page) + do + parent := a_page + end + +feature -- Status report + + is_parent_of (a_page: detachable CMS_PAGE): BOOLEAN + -- Is Current page, a parent of `a_page' ? + do + if + a_page /= Void and then + attached a_page.parent as l_parent + then + if l_parent.same_node (a_page) then + Result := True + else + Result := is_parent_of (l_parent) + end + end + end + +end diff --git a/src/modules/node/content_type/cms_page_content_type.e b/src/modules/node/content_type/cms_page_content_type.e new file mode 100644 index 0000000..bb2383f --- /dev/null +++ b/src/modules/node/content_type/cms_page_content_type.e @@ -0,0 +1,32 @@ +note + description: "Summary description for {CMS_PAGE_CONTENT_TYPE}." + author: "" + date: "$Date$" + revision: "$Revision$" + +class + CMS_PAGE_CONTENT_TYPE + +inherit + CMS_CONTENT_TYPE + +feature -- Access + + name: STRING = "page" + -- Internal name. + + title: STRING_32 = "Page" + -- Human readable name. + +feature -- Factory + + new_node (a_partial_node: detachable CMS_NODE): CMS_PAGE + -- New node based on partial `a_partial_node', or from none. + do + create Result.make_empty + if a_partial_node /= Void then + Result.import_node (a_partial_node) + end + end + +end diff --git a/src/modules/node/content_type/cms_partial_page.e b/src/modules/node/content_type/cms_partial_page.e new file mode 100644 index 0000000..fc2dd7b --- /dev/null +++ b/src/modules/node/content_type/cms_partial_page.e @@ -0,0 +1,17 @@ +note + description: "Summary description for {CMS_PARTIAL_PAGE}." + author: "" + date: "$Date$" + revision: "$Revision$" + +class + CMS_PARTIAL_PAGE + +inherit + CMS_PAGE + +create + make_empty, + make + +end diff --git a/src/modules/node/handler/node_content_handler.e b/src/modules/node/handler/experimental/node_content_handler.e similarity index 100% rename from src/modules/node/handler/node_content_handler.e rename to src/modules/node/handler/experimental/node_content_handler.e diff --git a/src/modules/node/handler/experimental/node_resource_handler.e b/src/modules/node/handler/experimental/node_resource_handler.e new file mode 100644 index 0000000..f0cba1f --- /dev/null +++ b/src/modules/node/handler/experimental/node_resource_handler.e @@ -0,0 +1,240 @@ +note + description: "Node handler for the API." + date: "$Date: 2015-02-13 13:08:13 +0100 (ven., 13 févr. 2015) $" + revision: "$Revision: 96616 $" + +class + NODE_RESOURCE_HANDLER + +inherit + CMS_NODE_HANDLER + + 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, + do_put, + do_delete + end + + REFACTORING_HELPER + +create + make + +feature -- execute + + execute (req: WSF_REQUEST; res: WSF_RESPONSE) + -- Execute request handler + do + execute_methods (req, res) + end + + uri_execute (req: WSF_REQUEST; res: WSF_RESPONSE) + -- Execute request handler + do + execute (req, res) + end + + uri_template_execute (req: WSF_REQUEST; res: WSF_RESPONSE) + -- Execute request handler + do + execute (req, res) + end + +feature -- HTTP Methods + + do_get (req: WSF_REQUEST; res: WSF_RESPONSE) + -- + local + l_page: CMS_RESPONSE + do + -- Existing node + if attached {WSF_STRING} req.path_parameter ("id") as l_id then + if + l_id.is_integer and then + attached node_api.node (l_id.value.to_integer_64) as l_node + then + -- FIXME: there is a mix between CMS interface and API interface here. + create {GENERIC_VIEW_CMS_RESPONSE} l_page.make (req, res, api) + l_page.add_variable (l_node, "node") + l_page.execute + else + do_error (req, res, l_id) + end + else + -- Factory + new_node (req, res) + end + end + + do_post (req: WSF_REQUEST; res: WSF_RESPONSE) + -- + local + u_node: CMS_NODE + do + to_implement ("Check user permissions!!!") + if attached current_user (req) as l_user then + if attached {WSF_STRING} req.path_parameter ("id") as l_id then + if + l_id.is_integer and then + attached node_api.node (l_id.value.to_integer_64) as l_node + then + if attached {WSF_STRING} req.form_parameter ("method") as l_method then + if l_method.is_case_insensitive_equal ("DELETE") then + do_delete (req, res) + elseif l_method.is_case_insensitive_equal ("PUT") then + do_put (req, res) + else + (create {INTERNAL_SERVER_ERROR_CMS_RESPONSE}.make (req, res, api)).execute + end + end + else + do_error (req, res, l_id) + end + else + -- New node + -- FIXME !!! + if attached {WSF_STRING} req.form_parameter ("type") as p_type then + if attached node_api.content_type (p_type.value) as ct then + end + create {CMS_PARTIAL_NODE} u_node.make_empty (p_type.url_encoded_value) + else + create {CMS_PARTIAL_NODE} u_node.make_empty ("") + end + + update_node_from_data_form (req, u_node) + u_node.set_author (l_user) + node_api.new_node (u_node) + redirect_to (req.absolute_script_url (""), res) + end + else + send_access_denied (res) + end + end + + do_put (req: WSF_REQUEST; res: WSF_RESPONSE) + -- + do + + if attached current_user (req) as l_user then + if attached {WSF_STRING} req.path_parameter ("id") as l_id then + if + l_id.is_integer and then + attached node_api.node (l_id.value.to_integer_64) as l_node + then + update_node_from_data_form (req, l_node) + l_node.set_author (l_user) + node_api.update_node (l_node) + redirect_to (req.absolute_script_url (""), res) + else + do_error (req, res, l_id) + end + else + (create {INTERNAL_SERVER_ERROR_CMS_RESPONSE}.make (req, res, api)).execute + end + else + send_access_denied (res) + end + end + + do_delete (req: WSF_REQUEST; res: WSF_RESPONSE) + -- + do + if attached current_user_name (req) then + if attached {WSF_STRING} req.path_parameter ("id") as l_id then + if + l_id.is_integer and then + attached node_api.node (l_id.integer_value) as l_node + then + node_api.delete_node (l_node) + res.send (create {CMS_REDIRECTION_RESPONSE_MESSAGE}.make (req.absolute_script_url (""))) + else + do_error (req, res, l_id) + end + else + (create {INTERNAL_SERVER_ERROR_CMS_RESPONSE}.make (req, res, api)).execute + end + else + send_access_denied (res) + end + end + +feature -- Error + + do_error (req: WSF_REQUEST; res: WSF_RESPONSE; a_id: WSF_STRING) + -- Handling error. + local + l_page: CMS_RESPONSE + do + create {GENERIC_VIEW_CMS_RESPONSE} l_page.make (req, res, api) + l_page.add_variable (req.absolute_script_url (req.path_info), "request") + if a_id.is_integer then + -- resource not found + l_page.add_variable ("404", "code") + l_page.set_status_code (404) + else + -- bad request + l_page.add_variable ("400", "code") + l_page.set_status_code (400) + end + l_page.execute + end + +feature {NONE} -- Node + + new_node (req: WSF_REQUEST; res: WSF_RESPONSE) + local + l_page: CMS_RESPONSE + do + if api.user_has_permission (current_user (req), "create node") then + create {GENERIC_VIEW_CMS_RESPONSE} l_page.make (req, res, api) + l_page.execute + else + send_access_denied (res) + end + end + +feature -- {NONE} Form data + + update_node_from_data_form (req: WSF_REQUEST; a_node: CMS_NODE) + -- Extract request form data and build a object + -- Node + local + l_title: detachable READABLE_STRING_32 + l_summary, l_content, l_format: detachable READABLE_STRING_8 + do + if attached {WSF_STRING} req.form_parameter ("title") as p_title then + l_title := p_title.value + a_node.set_title (l_title) + end + if attached {WSF_STRING} req.form_parameter ("summary") as p_summary then + l_summary := html_encoded (p_summary.value) + end + if attached {WSF_STRING} req.form_parameter ("content") as p_content then + l_content := html_encoded (p_content.value) + end + if attached {WSF_STRING} req.form_parameter ("format") as p_format then + l_format := p_format.url_encoded_value + end + if l_format = Void then + l_format := a_node.format + end + a_node.set_content (l_content, l_summary, l_format) + end + +end diff --git a/src/modules/node/handler/experimental/node_resources_handler.e b/src/modules/node/handler/experimental/node_resources_handler.e new file mode 100644 index 0000000..e9cafb7 --- /dev/null +++ b/src/modules/node/handler/experimental/node_resources_handler.e @@ -0,0 +1,75 @@ +note + description: "Request handler related to /nodes." + date: "$Date: 2015-02-13 13:08:13 +0100 (ven., 13 févr. 2015) $" + revision: "$Revision: 96616 $" + +class + NODE_RESOURCES_HANDLER + +inherit + CMS_NODE_HANDLER + + WSF_URI_HANDLER + rename + new_mapping as new_uri_mapping + end + + WSF_RESOURCE_HANDLER_HELPER + redefine + do_get + end + + REFACTORING_HELPER + +create + make + +feature -- execute + + execute (req: WSF_REQUEST; res: WSF_RESPONSE) + -- Execute request handler + do + execute_methods (req, res) + end + +feature -- HTTP Methods + + do_get (req: WSF_REQUEST; res: WSF_RESPONSE) + -- + local + l_page: CMS_RESPONSE + s: STRING + do + -- At the moment the template is hardcoded, but we can + -- get them from the configuration file and load them into + -- the setup class. + + create {GENERIC_VIEW_CMS_RESPONSE} l_page.make (req, res, api) + l_page.add_variable (node_api.nodes, "nodes") + + + -- NOTE: for development purposes we have the following hardcode output. + create s.make_from_string ("

Nodes:

") + if attached node_api.nodes as lst then + across + lst as ic + loop + s.append ("
  • ") + s.append ("") + s.append (api.html_encoded (ic.item.title)) + s.append (" (") + s.append (ic.item.id.out) + s.append (")") + s.append ("") + s.append ("
  • %N") + end + end + + l_page.set_main_content (s) + l_page.add_block (create {CMS_CONTENT_BLOCK}.make ("nodes_warning", Void, "/nodes/ is not yet fully implemented
    ", Void), "highlighted") + l_page.execute + end + +end diff --git a/src/modules/node/handler/node_summary_handler.e b/src/modules/node/handler/experimental/node_summary_handler.e similarity index 100% rename from src/modules/node/handler/node_summary_handler.e rename to src/modules/node/handler/experimental/node_summary_handler.e diff --git a/src/modules/node/handler/node_title_handler.e b/src/modules/node/handler/experimental/node_title_handler.e similarity index 100% rename from src/modules/node/handler/node_title_handler.e rename to src/modules/node/handler/experimental/node_title_handler.e diff --git a/src/modules/node/handler/node_handler.e b/src/modules/node/handler/node_handler.e index 4257563..25ed87c 100644 --- a/src/modules/node/handler/node_handler.e +++ b/src/modules/node/handler/node_handler.e @@ -62,15 +62,51 @@ feature -- HTTP Methods -- local l_page: CMS_RESPONSE + s: STRING + hdate: HTTP_DATE do -- Existing node if attached {WSF_STRING} req.path_parameter ("id") as l_id then if l_id.is_integer and then - attached node_api.node (l_id.value.to_integer_64) as l_node + attached {CMS_NODE} node_api.node (l_id.value.to_integer_64) as l_node then + -- FIXME: there is a mix between CMS interface and API interface here. create {GENERIC_VIEW_CMS_RESPONSE} l_page.make (req, res, api) l_page.add_variable (l_node, "node") + + create s.make_empty + s.append ("

    ") + s.append (html_encoded (l_node.title)) + s.append ("

    ") + s.append ("
    ") + if attached l_node.author as l_author then + s.append (" by ") + s.append (l_author.name) + end + if attached l_node.modification_date as l_modified then + s.append (" (modified: ") + create hdate.make_from_date_time (l_modified) + s.append (hdate.yyyy_mmm_dd_string) + s.append (")") + end + s.append ("
    ") + if attached l_node.content as l_content then + s.append ("

    ") + s.append (api.formats.item (l_node.format).formatted_output (l_content)) + s.append ("

    ") + end + if attached {CMS_PAGE} l_node as l_node_page then + if attached l_node_page.parent as l_parent_node then + s.append ("") + end + end + l_page.set_main_content (s) l_page.execute else do_error (req, res, l_id) @@ -107,7 +143,15 @@ feature -- HTTP Methods end else -- New node - create u_node.make ("", "", "") + -- FIXME !!! + if attached {WSF_STRING} req.form_parameter ("type") as p_type then + if attached node_api.content_type (p_type.value) as ct then + end + create {CMS_PARTIAL_NODE} u_node.make_empty (p_type.url_encoded_value) + else + create {CMS_PARTIAL_NODE} u_node.make_empty ("") + end + update_node_from_data_form (req, u_node) u_node.set_author (l_user) node_api.new_node (u_node) @@ -192,7 +236,7 @@ feature {NONE} -- Node local l_page: CMS_RESPONSE do - if attached current_user_name (req) then + if api.user_has_permission (current_user (req), "create node") then create {GENERIC_VIEW_CMS_RESPONSE} l_page.make (req, res, api) l_page.execute else @@ -205,16 +249,27 @@ feature -- {NONE} Form data update_node_from_data_form (req: WSF_REQUEST; a_node: CMS_NODE) -- Extract request form data and build a object -- Node + local + l_title: detachable READABLE_STRING_32 + l_summary, l_content, l_format: detachable READABLE_STRING_8 do - if attached {WSF_STRING} req.form_parameter ("title") as l_title then - a_node.set_title (l_title.value) + if attached {WSF_STRING} req.form_parameter ("title") as p_title then + l_title := p_title.value + a_node.set_title (l_title) end - if attached {WSF_STRING} req.form_parameter ("summary") as l_summary then - a_node.set_summary (l_summary.value) + if attached {WSF_STRING} req.form_parameter ("summary") as p_summary then + l_summary := html_encoded (p_summary.value) end - if attached {WSF_STRING} req.form_parameter ("content") as l_content then - a_node.set_content (l_content.value) + if attached {WSF_STRING} req.form_parameter ("content") as p_content then + l_content := html_encoded (p_content.value) end + if attached {WSF_STRING} req.form_parameter ("format") as p_format then + l_format := p_format.url_encoded_value + end + if l_format = Void then + l_format := a_node.format + end + a_node.set_content (l_content, l_summary, l_format) end end diff --git a/src/modules/node/handler/nodes_handler.e b/src/modules/node/handler/nodes_handler.e index a4b790b..2adb58a 100644 --- a/src/modules/node/handler/nodes_handler.e +++ b/src/modules/node/handler/nodes_handler.e @@ -39,8 +39,7 @@ feature -- HTTP Methods local l_page: CMS_RESPONSE s: STRING - l_user: CMS_USER - l_node: CMS_NODE + n: CMS_NODE do -- At the moment the template is hardcoded, but we can -- get them from the configuration file and load them into @@ -53,20 +52,23 @@ feature -- HTTP Methods -- NOTE: for development purposes we have the following hardcode output. create s.make_from_string ("

    Nodes:

    ") if attached node_api.nodes as lst then + s.append ("%N") end l_page.set_main_content (s) diff --git a/src/modules/node/node_module.e b/src/modules/node/node_module.e index 9ff4304..7c690cb 100644 --- a/src/modules/node/node_module.e +++ b/src/modules/node/node_module.e @@ -10,14 +10,16 @@ inherit CMS_MODULE redefine - register_hooks + register_hooks, + initialize, + is_installed, + install end CMS_HOOK_MENU_SYSTEM_ALTER CMS_HOOK_BLOCK - create make @@ -36,6 +38,59 @@ feature {NONE} -- Initialization config: CMS_SETUP -- Node configuration. +feature {CMS_API} -- Module Initialization + + initialize (api: CMS_API) + -- + do + Precursor (api) + if attached {CMS_NODE_STORAGE_SQL} api.storage as l_sql_storage then + l_sql_storage.register_node_storage_extension (create {CMS_NODE_STORAGE_SQL_PAGE_EXTENSION}.make (l_sql_storage)) + else + -- FIXME: maybe provide a default solution based on file system, when no SQL storage is available. + end + end + +feature {CMS_API} -- Module management + + is_installed (api: CMS_API): BOOLEAN + -- Is Current module installed? + do + if attached {CMS_STORAGE_SQL} api.storage as l_sql_storage then + Result := l_sql_storage.sql_table_exists ("nodes") and + l_sql_storage.sql_table_exists ("page_nodes") + end + end + + install (api: CMS_API) + local + p1,p2: CMS_PAGE + ct: CMS_PAGE_CONTENT_TYPE + do + -- Schema + if attached {CMS_STORAGE_SQL} api.storage as l_sql_storage then + l_sql_storage.sql_execute_file_script (api.setup.layout.path.extended ("scripts").extended (name).appended_with_extension ("sql")) + end + + -- Data + -- FIXME: for test purpose, remove later + if attached api.user_api.user_by_id (1) as u then + create ct + p1 := ct.new_node (Void) + p1.set_title ("Welcome") + p1.set_content ("Welcome, you are using the ROC Eiffel CMS", "Welcome Eiffel ROC user", Void) -- Use default format + p1.set_author (u) + api.storage.save_node (p1) + + p2 := ct.new_node (Void) + p2.set_title ("A new page example") + p2.set_content ("This is the content of a page", "This is a new page", Void) -- Use default format + p2.set_author (u) + p2.set_parent (p1) + api.storage.save_node (p2) + end + end + feature -- Access: router router (a_api: CMS_API): WSF_ROUTER @@ -44,87 +99,119 @@ feature -- Access: router l_node_api: CMS_NODE_API do create l_node_api.make (a_api) - create Result.make (5) - configure_api_node (a_api, l_node_api, Result) - configure_api_nodes (a_api, l_node_api, Result) - configure_api_node_title (a_api, l_node_api, Result) - configure_api_node_summary (a_api, l_node_api, Result) - configure_api_node_content (a_api, l_node_api, Result) + create Result.make (2) + configure_cms (a_api, l_node_api, Result) +-- configure_api (a_api, l_node_api, Result) end -feature {NONE} -- Implementation: routes - - configure_api_node (a_api: CMS_API; a_node_api: CMS_NODE_API; a_router: WSF_ROUTER) + configure_cms (a_api: CMS_API; a_node_api: CMS_NODE_API; a_router: WSF_ROUTER) local l_node_handler: NODE_HANDLER - l_methods: WSF_REQUEST_METHODS - do - create l_node_handler.make (a_api, a_node_api) - create l_methods - l_methods.enable_get - l_methods.enable_post - l_methods.enable_put - a_router.handle_with_request_methods ("/node", l_node_handler, l_methods) - - create l_node_handler.make (a_api, a_node_api) - create l_methods - l_methods.enable_get - l_methods.enable_post - l_methods.enable_put - l_methods.enable_delete - a_router.handle_with_request_methods ("/node/{id}", l_node_handler, l_methods) --- a_router.handle_with_request_methods ("/nodes/", create {WSF_URI_AGENT_HANDLER}.make (agent do_get_nodes (?,?, a_api, a_node_api)), a_router.methods_get) - end - - configure_api_nodes (a_api: CMS_API; a_node_api: CMS_NODE_API; a_router: WSF_ROUTER) - local + l_edit_node_handler: NODE_HANDLER + l_new_node_handler: NODE_HANDLER l_nodes_handler: NODES_HANDLER l_methods: WSF_REQUEST_METHODS do + create l_node_handler.make (a_api, a_node_api) + create l_methods + l_methods.enable_get + l_methods.enable_post + l_methods.lock + a_router.handle_with_request_methods ("/node", l_node_handler, l_methods) + + create l_new_node_handler.make (a_api, a_node_api) + a_router.handle_with_request_methods ("/node/new/{type}", create {WSF_URI_AGENT_HANDLER}.make (agent do_get_node_creation_by_type (?,?, "type", a_node_api)), a_router.methods_get) + a_router.handle_with_request_methods ("/node/new", create {WSF_URI_AGENT_HANDLER}.make (agent do_get_node_creation_selection (?,?, a_api)), a_router.methods_get) + + create l_edit_node_handler.make (a_api, a_node_api) + a_router.handle_with_request_methods ("/node/{id}/edit", l_edit_node_handler, a_router.methods_get_post) + + create l_node_handler.make (a_api, a_node_api) + a_router.handle_with_request_methods ("/node/{id}", l_node_handler, a_router.methods_get_put_delete + a_router.methods_get_post) + + -- Nodes create l_nodes_handler.make (a_api, a_node_api) create l_methods l_methods.enable_get a_router.handle_with_request_methods ("/nodes", l_nodes_handler, l_methods) end - configure_api_node_summary (a_api: CMS_API; a_node_api: CMS_NODE_API; a_router: WSF_ROUTER) - local - l_report_handler: NODE_SUMMARY_HANDLER - l_methods: WSF_REQUEST_METHODS - do - create l_report_handler.make (a_api, a_node_api) - create l_methods - l_methods.enable_get - l_methods.enable_post - l_methods.enable_put - a_router.handle_with_request_methods ("/node/{id}/field/summary", l_report_handler, l_methods) - end +-- configure_api (a_api: CMS_API; a_node_api: CMS_NODE_API; a_router: WSF_ROUTER) +-- do +-- configure_api_node (a_api, a_node_api, a_router) +-- configure_api_nodes (a_api, a_node_api, a_router) +-- configure_api_node_title (a_api, a_node_api, a_router) +-- configure_api_node_summary (a_api, a_node_api, a_router) +-- configure_api_node_content (a_api, a_node_api, a_router) +-- end - configure_api_node_title (a_api: CMS_API; a_node_api: CMS_NODE_API; a_router: WSF_ROUTER) - local - l_report_handler: NODE_TITLE_HANDLER - l_methods: WSF_REQUEST_METHODS - do - create l_report_handler.make (a_api, a_node_api) - create l_methods - l_methods.enable_get - l_methods.enable_post - l_methods.enable_put - a_router.handle_with_request_methods ("/node/{id}/field/title", l_report_handler, l_methods) - end +feature {NONE} -- Implementation: routes - configure_api_node_content (a_api: CMS_API; a_node_api: CMS_NODE_API; a_router: WSF_ROUTER) - local - l_report_handler: NODE_CONTENT_HANDLER - l_methods: WSF_REQUEST_METHODS - do - create l_report_handler.make (a_api, a_node_api) - create l_methods - l_methods.enable_get - l_methods.enable_post - l_methods.enable_put - a_router.handle_with_request_methods ("/node/{id}/field/content", l_report_handler, l_methods) - end +-- configure_api_node (a_api: CMS_API; a_node_api: CMS_NODE_API; a_router: WSF_ROUTER) +-- local +-- l_node_handler: NODE_RESOURCE_HANDLER +-- l_methods: WSF_REQUEST_METHODS +-- do +-- create l_node_handler.make (a_api, a_node_api) +-- create l_methods +-- l_methods.enable_get +-- l_methods.enable_post +-- l_methods.lock +-- a_router.handle_with_request_methods ("/api/node", l_node_handler, l_methods) + +-- create l_node_handler.make (a_api, a_node_api) +-- a_router.handle_with_request_methods ("/api/node/{id}", l_node_handler, a_router.methods_get_put_delete + a_router.methods_get_post) +-- end + +-- configure_api_nodes (a_api: CMS_API; a_node_api: CMS_NODE_API; a_router: WSF_ROUTER) +-- local +-- l_nodes_handler: NODE_RESOURCES_HANDLER +-- l_methods: WSF_REQUEST_METHODS +-- do +-- create l_nodes_handler.make (a_api, a_node_api) +-- create l_methods +-- l_methods.enable_get +-- a_router.handle_with_request_methods ("/api/nodes", l_nodes_handler, l_methods) +-- end + +-- configure_api_node_summary (a_api: CMS_API; a_node_api: CMS_NODE_API; a_router: WSF_ROUTER) +-- local +-- l_report_handler: NODE_SUMMARY_HANDLER +-- l_methods: WSF_REQUEST_METHODS +-- do +-- create l_report_handler.make (a_api, a_node_api) +-- create l_methods +-- l_methods.enable_get +-- l_methods.enable_post +-- l_methods.enable_put +-- a_router.handle_with_request_methods ("/node/{id}/field/summary", l_report_handler, l_methods) +-- end + +-- configure_api_node_title (a_api: CMS_API; a_node_api: CMS_NODE_API; a_router: WSF_ROUTER) +-- local +-- l_report_handler: NODE_TITLE_HANDLER +-- l_methods: WSF_REQUEST_METHODS +-- do +-- create l_report_handler.make (a_api, a_node_api) +-- create l_methods +-- l_methods.enable_get +-- l_methods.enable_post +-- l_methods.enable_put +-- a_router.handle_with_request_methods ("/node/{id}/field/title", l_report_handler, l_methods) +-- end + +-- configure_api_node_content (a_api: CMS_API; a_node_api: CMS_NODE_API; a_router: WSF_ROUTER) +-- local +-- l_report_handler: NODE_CONTENT_HANDLER +-- l_methods: WSF_REQUEST_METHODS +-- do +-- create l_report_handler.make (a_api, a_node_api) +-- create l_methods +-- l_methods.enable_get +-- l_methods.enable_post +-- l_methods.enable_put +-- a_router.handle_with_request_methods ("/node/{id}/field/content", l_report_handler, l_methods) +-- end feature -- Hooks @@ -156,4 +243,30 @@ feature -- Hooks a_menu_system.primary_menu.extend (lnk) end +feature -- Handler + + do_get_node_creation_selection (req: WSF_REQUEST; res: WSF_RESPONSE; a_api: CMS_API) + local + l_page: NOT_IMPLEMENTED_ERROR_CMS_RESPONSE + do + create l_page.make (req, res, a_api) + l_page.execute + end + + do_get_node_creation_by_type (req: WSF_REQUEST; res: WSF_RESPONSE; a_type_varname: READABLE_STRING_8; a_node_api: CMS_NODE_API) + local + l_page: NOT_IMPLEMENTED_ERROR_CMS_RESPONSE + l_node: detachable CMS_NODE + do + create l_page.make (req, res, a_node_api.cms_api) + if + attached {WSF_STRING} req.path_parameter (a_type_varname) as p_type and then + attached a_node_api.content_type (p_type.value) as ct + then + l_node := ct.new_node (Void) + l_page.set_main_content (l_node.out) + end + l_page.execute + end + end diff --git a/src/persistence/cms_proxy_storage_sql.e b/src/persistence/cms_proxy_storage_sql.e new file mode 100644 index 0000000..6f87aea --- /dev/null +++ b/src/persistence/cms_proxy_storage_sql.e @@ -0,0 +1,112 @@ +note + description: "Proxy on a {CMS_STORAGE_SQL} interface." + date: "$Date: 2015-02-13 13:08:13 +0100 (ven., 13 févr. 2015) $" + revision: "$Revision: 96616 $" + +class + CMS_PROXY_STORAGE_SQL + +inherit + CMS_STORAGE_SQL + +create + make + +feature {NONE} -- Initialization + + make (a_sql_storage: like sql_storage) + do + sql_storage := a_sql_storage + end + + sql_storage: CMS_STORAGE_SQL + +feature -- Access + + api: detachable CMS_API + -- Associated CMS api. + do + Result := sql_storage.api + end + +feature -- Error handler + + error_handler: ERROR_HANDLER + -- Error handler. + do + Result := sql_storage.error_handler + end + +feature -- Execution + + sql_begin_transaction + do + sql_storage.sql_begin_transaction + end + + sql_rollback_transaction + do + sql_storage.sql_rollback_transaction + end + + sql_commit_transaction + do + sql_storage.sql_commit_transaction + end + + sql_post_execution + -- Post database execution. + -- note: execute after each `sql_query' and `sql_change'. + do + sql_storage.sql_post_execution + end + +feature -- Operation + + sql_query (a_sql_statement: STRING; a_params: detachable STRING_TABLE [detachable ANY]) + do + sql_storage.sql_query (a_sql_statement, a_params) + end + + sql_change (a_sql_statement: STRING; a_params: detachable STRING_TABLE [detachable ANY]) + do + sql_storage.sql_change (a_sql_statement, a_params) + end + +feature -- Access + + sql_rows_count: INTEGER + -- Number of rows for last sql execution. + do + Result := sql_storage.sql_rows_count + end + + sql_start + -- Set the cursor on first element. + do + sql_storage.sql_start + end + + sql_after: BOOLEAN + -- Are there no more items to iterate over? + do + Result := sql_storage.sql_after + end + + sql_forth + -- Fetch next row from last sql execution, if any. + do + sql_storage.sql_forth + end + + sql_valid_item_index (a_index: INTEGER): BOOLEAN + do + Result := sql_storage.sql_valid_item_index (a_index) + end + + sql_item (a_index: INTEGER): detachable ANY + do + Result:= sql_storage.sql_item (a_index) + end + +end diff --git a/src/persistence/cms_storage.e b/src/persistence/cms_storage.e index 1a11b9a..8463272 100644 --- a/src/persistence/cms_storage.e +++ b/src/persistence/cms_storage.e @@ -22,6 +22,11 @@ feature {NONE} -- Initialization do end +feature -- Access + + api: detachable CMS_API assign set_api + -- Associated CMS API. + feature -- Status report is_available: BOOLEAN @@ -39,6 +44,14 @@ feature -- Error Handling error_handler: ERROR_HANDLER -- Error handler. +feature -- Element change + + set_api (a_api: like api) + -- Set `api' to `a_api'. + do + api := a_api + end + feature -- Misc -- set_custom_value (a_name: READABLE_STRING_8; a_value: attached like custom_value; a_type: READABLE_STRING_8) diff --git a/src/persistence/cms_storage_null.e b/src/persistence/cms_storage_null.e index 7e28103..0c1da17 100644 --- a/src/persistence/cms_storage_null.e +++ b/src/persistence/cms_storage_null.e @@ -95,12 +95,20 @@ feature -- Change: user do end + feature -- Access: roles and permissions user_role_by_id (a_id: like {CMS_USER_ROLE}.id): detachable CMS_USER_ROLE do end + user_roles_for (a_user: CMS_USER): LIST [CMS_USER_ROLE] + -- User roles for user `a_user'. + -- Note: anonymous and authenticated roles are not included. + do + create {ARRAYED_LIST [CMS_USER_ROLE]} Result.make (0) + end + user_roles: LIST [CMS_USER_ROLE] do create {ARRAYED_LIST [CMS_USER_ROLE]} Result.make (0) @@ -179,4 +187,13 @@ feature -- Node do end + +feature -- Helpers + + fill_node (a_node: CMS_NODE) + -- Fill `a_node' with extra information from database. + -- i.e: specific to each content type data. + do + end + end diff --git a/src/persistence/cms_storage_sql.e b/src/persistence/cms_storage_sql.e index 5e1281e..3d37eb3 100644 --- a/src/persistence/cms_storage_sql.e +++ b/src/persistence/cms_storage_sql.e @@ -7,6 +7,13 @@ note deferred class CMS_STORAGE_SQL +feature -- Access + + api: detachable CMS_API + -- Associated CMS api. + deferred + end + feature -- Error handler error_handler: ERROR_HANDLER @@ -108,6 +115,54 @@ feature -- Operation deferred end +feature -- Helper + + sql_execute_file_script (a_path: PATH) + -- Execute SQL script from `a_path'. + local + f: PLAIN_TEXT_FILE + sql: STRING + do + create f.make_with_path (a_path) + if f.exists and then f.is_access_readable then + create sql.make (f.count) + f.open_read + from + f.start + until + f.exhausted or f.end_of_file + loop + f.read_stream_thread_aware (1_024) + sql.append (f.last_string) + end + f.close + sql_execute_script (sql) + end + end + + sql_execute_script (a_sql_script: STRING) + -- Execute SQL script. + -- i.e: multiple SQL statements. + do + reset_error +-- sql_begin_transaction + sql_change (a_sql_script, Void) +-- sql_commit_transaction + end + + sql_table_exists (a_table_name: READABLE_STRING_8): BOOLEAN + -- Does table `a_table_name' exists? + local + l_params: STRING_TABLE [detachable ANY] + do + reset_error + create l_params.make (1) + l_params.force (a_table_name, "tbname") + sql_query ("SELECT count(*) FROM :tbname ;", l_params) + Result := not has_error + -- FIXME: find better solution + end + feature -- Access sql_rows_count: INTEGER @@ -130,7 +185,13 @@ feature -- Access deferred end + sql_valid_item_index (a_index: INTEGER): BOOLEAN + deferred + end + sql_item (a_index: INTEGER): detachable ANY + require + valid_index: sql_valid_item_index (a_index) deferred end @@ -160,7 +221,7 @@ feature -- Access elseif attached {INTEGER_32_REF} l_item as l_value then Result := l_value.item else --- check is_integer_32: False end + check is_integer_32: False end end end @@ -177,7 +238,7 @@ feature -- Access elseif attached {BOOLEAN_REF} l_item as l_boolean_ref then Result := l_boolean_ref.item.out else --- check is_string: False end + check is_string: False end end end @@ -192,7 +253,9 @@ feature -- Access Result := l_string else if attached sql_read_string (a_index) as s8 then - Result := s8.to_string_32 -- FIXME + Result := s8.to_string_32 -- FIXME: any escape? + else + check is_string_32: False end end end end diff --git a/src/persistence/node/cms_node_storage.e b/src/persistence/node/cms_node_storage.e index 2a29751..3b10581 100644 --- a/src/persistence/node/cms_node_storage.e +++ b/src/persistence/node/cms_node_storage.e @@ -17,6 +17,51 @@ feature -- Error Handling deferred end +feature -- Storage extension + + register_node_storage_extension (a_extension: CMS_NODE_STORAGE_EXTENSION [CMS_NODE]) + -- Register `a_extension' as extension to the node storage system. + local + tb: like node_storage_extensions + do + tb := node_storage_extensions + if tb = Void then + create tb.make_caseless (1) + node_storage_extensions := tb + end + tb.force (a_extension, a_extension.content_type) + end + + node_storage_extension (a_node: CMS_NODE): detachable CMS_NODE_STORAGE_EXTENSION [CMS_NODE] + -- Extension to the node storage system for node `a_node'. + do + if attached node_storage_extensions as tb then + Result := tb.item (a_node.content_type) + end + end + +feature {NONE} -- Implementation + + node_storage_extensions: detachable STRING_TABLE [CMS_NODE_STORAGE_EXTENSION [CMS_NODE]] + -- Table of node storage extensions. + + extended_store (a_node: CMS_NODE) + -- Store extended data from `a_node'. + do + if attached node_storage_extension (a_node) as ext then + ext.store_node (a_node) + end + end + + extended_load (a_node: CMS_NODE) + -- Load extended data into `a_node'. + do + if attached node_storage_extension (a_node) as ext then + ext.load_node (a_node) + end + end + + feature -- Access nodes_count: INTEGER_64 @@ -44,7 +89,7 @@ feature -- Access node_author (a_id: like {CMS_NODE}.id): detachable CMS_USER -- Node's author. if any. require - valid_node: a_id >0 + valid_node: a_id > 0 deferred end @@ -68,6 +113,15 @@ feature -- Change: Node deferred end + update_node (a_node: CMS_NODE) + -- Update node content `a_node'. + -- The user `a_id' is an existing or new collaborator. + require + has_id: a_node.has_id + has_author: attached a_node.author as l_author and then l_author.has_id + deferred + end + delete_node (a_node: CMS_NODE) -- Delete `a_node'. do @@ -83,39 +137,40 @@ feature -- Change: Node deferred end - update_node (a_node: CMS_NODE) - -- Update node content `a_node'. - -- The user `a_id' is an existing or new collaborator. +-- update_node_title (a_user_id: like {CMS_USER}.id; a_node_id: like {CMS_NODE}.id; a_title: READABLE_STRING_32) +-- -- Update node title to `a_title', node identified by id `a_node_id'. +-- -- The user `a_user_id' is an existing or new collaborator. +-- require +-- valid_node_id: a_node_id > 0 +-- valid_user_id: a_user_id > 0 +-- deferred +-- end + +-- update_node_summary (a_user_id: like {CMS_USER}.id; a_node_id: like {CMS_NODE}.id; a_summary: READABLE_STRING_32) +-- -- Update node summary to `a_summary', node identified by id `a_node_id'. +-- -- The user `a_user_id' is an existing or new collaborator. +-- require +-- valid_id: a_node_id > 0 +-- valid_user_id: a_user_id > 0 +-- deferred +-- end + +-- update_node_content (a_user_id: like {CMS_USER}.id; a_node_id: like {CMS_NODE}.id; a_content: READABLE_STRING_32) +-- -- Update node content to `a_content', node identified by id `a_node_id'. +-- -- The user `a_user_id' is an existing or new collaborator. +-- require +-- valid_id: a_node_id > 0 +-- valid_user_id: a_user_id > 0 +-- deferred +-- end + +feature -- Helpers + + fill_node (a_node: CMS_NODE) + -- Fill `a_node' with extra information from database. + -- i.e: specific to each content type data. require has_id: a_node.has_id - has_author: attached a_node.author as l_author and then l_author.has_id - deferred - end - - update_node_title (a_user_id: like {CMS_USER}.id; a_node_id: like {CMS_NODE}.id; a_title: READABLE_STRING_32) - -- Update node title to `a_title', node identified by id `a_node_id'. - -- The user `a_user_id' is an existing or new collaborator. - require - valid_node_id: a_node_id > 0 - valid_user_id: a_user_id > 0 - deferred - end - - update_node_summary (a_user_id: like {CMS_USER}.id; a_node_id: like {CMS_NODE}.id; a_summary: READABLE_STRING_32) - -- Update node summary to `a_summary', node identified by id `a_node_id'. - -- The user `a_user_id' is an existing or new collaborator. - require - valid_id: a_node_id > 0 - valid_user_id: a_user_id > 0 - deferred - end - - update_node_content (a_user_id: like {CMS_USER}.id; a_node_id: like {CMS_NODE}.id; a_content: READABLE_STRING_32) - -- Update node content to `a_content', node identified by id `a_node_id'. - -- The user `a_user_id' is an existing or new collaborator. - require - valid_id: a_node_id > 0 - valid_user_id: a_user_id > 0 deferred end diff --git a/src/persistence/node/cms_node_storage_extension.e b/src/persistence/node/cms_node_storage_extension.e new file mode 100644 index 0000000..289f95a --- /dev/null +++ b/src/persistence/node/cms_node_storage_extension.e @@ -0,0 +1,53 @@ +note + description: "Node storage extension for specific node descendant." + date: "$Date$" + revision: "$Revision$" + +deferred class + CMS_NODE_STORAGE_EXTENSION [G -> CMS_NODE] + +feature -- Access + + content_type: READABLE_STRING_8 + deferred + end + +feature -- Status report + + is_accepted (a_node: CMS_NODE): BOOLEAN + -- Is `a_node' accepted by current storage extension? + do + Result := attached {G} a_node + end + +feature -- Persistence + + store_node (a_node: CMS_NODE) + require + a_node_accepted: is_accepted (a_node) + do + if attached {G} a_node as obj then + store (obj) + end + end + + load_node (a_node: CMS_NODE) + require + a_node_accepted: is_accepted (a_node) + do + if attached {G} a_node as obj then + load (obj) + end + end + +feature {NONE} -- Persistence implementation + + store (a_node: G) + deferred + end + + load (a_node: G) + deferred + end + +end diff --git a/src/persistence/node/cms_node_storage_sql.e b/src/persistence/node/cms_node_storage_sql.e index 42f7c5f..cead57d 100644 --- a/src/persistence/node/cms_node_storage_sql.e +++ b/src/persistence/node/cms_node_storage_sql.e @@ -1,6 +1,7 @@ note - description: "Summary description for {CMS_NODE_STORAGE_SQL}." - author: "" + description: "[ + CMS NODE Storage based on SQL statements. + ]" date: "$Date: 2015-02-13 13:08:13 +0100 (ven., 13 févr. 2015) $" revision: "$Revision: 96616 $" @@ -23,7 +24,7 @@ feature -- Access do error_handler.reset write_information_log (generator + ".nodes_count") - sql_query (select_nodes_count, Void) + sql_query (sql_select_nodes_count, Void) if sql_rows_count = 1 then Result := sql_read_integer_64 (1) end @@ -38,7 +39,7 @@ feature -- Access write_information_log (generator + ".nodes") from - sql_query (select_nodes, Void) + sql_query (sql_select_nodes, Void) sql_start until sql_after @@ -48,6 +49,11 @@ feature -- Access end sql_forth end +-- across +-- Result as ic +-- loop +-- fill_node (ic.item) +-- end end recent_nodes (a_lower: INTEGER; a_count: INTEGER): LIST [CMS_NODE] @@ -64,7 +70,7 @@ feature -- Access create l_parameters.make (2) l_parameters.put (a_count, "rows") l_parameters.put (a_lower, "offset") - sql_query (select_recent_nodes, l_parameters) + sql_query (sql_select_recent_nodes, l_parameters) sql_start until sql_after @@ -84,8 +90,8 @@ feature -- Access error_handler.reset write_information_log (generator + ".node") create l_parameters.make (1) - l_parameters.put (a_id,"id") - sql_query (select_node_by_id, l_parameters) + l_parameters.put (a_id, "nid") + sql_query (sql_select_node_by_id, l_parameters) if sql_rows_count = 1 then Result := fetch_node end @@ -121,31 +127,14 @@ feature -- Change: Node new_node (a_node: CMS_NODE) -- Save node `a_node'. - local - l_parameters: STRING_TABLE [detachable ANY] do - -- New node - error_handler.reset - write_information_log (generator + ".new_node") - create l_parameters.make (7) - l_parameters.put (a_node.title, "title") - l_parameters.put (a_node.summary, "summary") - l_parameters.put (a_node.content, "content") - l_parameters.put (a_node.publication_date, "publish") - l_parameters.put (a_node.creation_date, "created") - l_parameters.put (a_node.modification_date, "changed") - if - attached a_node.author as l_author and then - l_author.id > 0 - then - l_parameters.put (l_author.id, "author") - else - l_parameters.put (0, "author") - end - sql_change (sql_insert_node, l_parameters) - if not error_handler.has_error then - a_node.set_id (last_inserted_node_id) - end + store_node (a_node) + end + + update_node (a_node: CMS_NODE) + -- Update node content `a_node'. + do + store_node (a_node) end delete_node_by_id (a_id: INTEGER_64) @@ -161,144 +150,184 @@ feature -- Change: Node sql_change (sql_delete_node, l_parameters) end - update_node (a_node: CMS_NODE) - -- Update node content `a_node'. +-- update_node_title (a_user_id: like {CMS_USER}.id; a_node_id: like {CMS_NODE}.id; a_title: READABLE_STRING_32) +-- -- +-- local +-- l_parameters: STRING_TABLE [detachable ANY] +-- do +-- -- FIXME: unused a_user_id ! +-- error_handler.reset +-- write_information_log (generator + ".update_node_title") +-- create l_parameters.make (3) +-- l_parameters.put (a_title, "title") +-- l_parameters.put (create {DATE_TIME}.make_now_utc, "changed") +-- l_parameters.put (a_node_id, "nid") +-- sql_change (sql_update_node_title, l_parameters) +-- end + +-- update_node_summary (a_user_id: Like {CMS_USER}.id; a_node_id: like {CMS_NODE}.id; a_summary: READABLE_STRING_32) +-- -- +-- local +-- l_parameters: STRING_TABLE [detachable ANY] +-- do +-- -- FIXME: unused a_user_id ! +-- error_handler.reset +-- write_information_log (generator + ".update_node_summary") +-- create l_parameters.make (3) +-- l_parameters.put (a_summary, "summary") +-- l_parameters.put (create {DATE_TIME}.make_now_utc, "changed") +-- l_parameters.put (a_node_id, "nid") +-- sql_change (sql_update_node_summary, l_parameters) +-- end + +-- update_node_content (a_user_id: Like {CMS_USER}.id;a_node_id: like {CMS_NODE}.id; a_content: READABLE_STRING_32) +-- -- +-- local +-- l_parameters: STRING_TABLE [detachable ANY] +-- do +-- -- FIXME: unused a_user_id ! +-- error_handler.reset +-- write_information_log (generator + ".update_node_content") +-- create l_parameters.make (3) +-- l_parameters.put (a_content, "content") +-- l_parameters.put (create {DATE_TIME}.make_now_utc, "changed") +-- l_parameters.put (a_node_id, "nid") +-- sql_change (sql_update_node_content, l_parameters) +-- end + +feature {NONE} -- Implementation + + store_node (a_node: CMS_NODE) local l_parameters: STRING_TABLE [detachable ANY] now: DATE_TIME + is_new: BOOLEAN do create now.make_now_utc error_handler.reset - write_information_log (generator + ".update_node") - create l_parameters.make (7) + + write_information_log (generator + ".store_node") + create l_parameters.make (8) + l_parameters.put (a_node.content_type, "type") l_parameters.put (a_node.title, "title") l_parameters.put (a_node.summary, "summary") l_parameters.put (a_node.content, "content") l_parameters.put (a_node.publication_date, "publish") l_parameters.put (now, "changed") - l_parameters.put (a_node.id, "id") if attached a_node.author as l_author then + check valid_author: l_author.has_id end l_parameters.put (l_author.id, "author") else l_parameters.put (0, "author") end - sql_change (sql_update_node, l_parameters) - if not error_handler.has_error then - a_node.set_modification_date (now) + sql_begin_transaction + if a_node.has_id then + is_new := True + -- Update + l_parameters.put (a_node.id, "id") + sql_change (sql_update_node, l_parameters) + if not error_handler.has_error then + a_node.set_revision (a_node.revision + 1) -- FIXME: Should be incremented by one, in same transaction...but check! + a_node.set_modification_date (now) + end + else + is_new := False + -- Store new node + l_parameters.put (a_node.creation_date, "created") + sql_change (sql_insert_node, l_parameters) + if not error_handler.has_error then + a_node.set_modification_date (now) + a_node.set_id (last_inserted_node_id) + a_node.set_revision (1) -- New object. + end end + extended_store (a_node) + sql_commit_transaction end - update_node_title (a_user_id: like {CMS_USER}.id; a_node_id: like {CMS_NODE}.id; a_title: READABLE_STRING_32) - -- - local - l_parameters: STRING_TABLE [detachable ANY] - do - -- FIXME: unused a_user_id ! - error_handler.reset - write_information_log (generator + ".update_node_title") - create l_parameters.make (3) - l_parameters.put (a_title, "title") - l_parameters.put (create {DATE_TIME}.make_now_utc, "changed") - l_parameters.put (a_node_id, "nid") - sql_change (sql_update_node_title, l_parameters) - end +feature -- Helpers - update_node_summary (a_user_id: Like {CMS_USER}.id; a_node_id: like {CMS_NODE}.id; a_summary: READABLE_STRING_32) - -- - local - l_parameters: STRING_TABLE [detachable ANY] + fill_node (a_node: CMS_NODE) + -- Fill `a_node' with extra information from database. + -- i.e: specific to each content type data. do - -- FIXME: unused a_user_id ! error_handler.reset - write_information_log (generator + ".update_node_summary") - create l_parameters.make (3) - l_parameters.put (a_summary, "summary") - l_parameters.put (create {DATE_TIME}.make_now_utc, "changed") - l_parameters.put (a_node_id, "nid") - sql_change (sql_update_node_summary, l_parameters) - end - - update_node_content (a_user_id: Like {CMS_USER}.id;a_node_id: like {CMS_NODE}.id; a_content: READABLE_STRING_32) - -- - local - l_parameters: STRING_TABLE [detachable ANY] - do - -- FIXME: unused a_user_id ! - error_handler.reset - write_information_log (generator + ".update_node_content") - create l_parameters.make (3) - l_parameters.put (a_content, "content") - l_parameters.put (create {DATE_TIME}.make_now_utc, "changed") - l_parameters.put (a_node_id, "nid") - sql_change (sql_update_node_content, l_parameters) + write_information_log (generator + ".fill_node") + extended_load (a_node) end feature {NONE} -- Queries - Select_nodes_count: STRING = "select count(*) from Nodes;" + sql_select_nodes_count: STRING = "SELECT count(*) from Nodes;" - Select_nodes: STRING = "select * from Nodes;" - -- SQL Query to retrieve all nodes. + sql_select_nodes: STRING = "SELECT * from Nodes;" + -- SQL Query to retrieve all nodes. - Select_node_by_id: STRING = "select * from Nodes where nid =:nid order by nid desc, publish desc;" + sql_select_node_by_id: STRING = "SELECT nid, revision, type, title, summary, content, author, publish, created, changed FROM Nodes WHERE nid =:nid ORDER BY revision desc, publish desc LIMIT 1;" - Select_recent_nodes: STRING = "select * from Nodes order by nid desc, publish desc LIMIT :rows OFFSET :offset ;" + sql_select_recent_nodes: STRING = "SELECT nid, revision, type, title, summary, content, author, publish, created, changed FROM Nodes ORDER BY nid desc, publish desc LIMIT :rows OFFSET :offset ;" - SQL_Insert_node: STRING = "insert into nodes (title, summary, content, publish, created, changed, author) values (:title, :summary, :content, :publish, :created, :changed, :author);" - -- SQL Insert to add a new node. + sql_insert_node: STRING = "INSERT INTO nodes (revision, type, title, summary, content, publish, created, changed, author) VALUES (1, :type, :title, :summary, :content, :publish, :created, :changed, :author);" + -- SQL Insert to add a new node. - SQL_Update_node : STRING = "update nodes SET title=:title, summary=:summary, content=:content, publish=:publish, changed=:changed, version = version + 1, author=:author where nid=:nid;" - -- SQL node. + sql_update_node : STRING = "UPDATE nodes SET revision = revision + 1, type=:type, title=:title, summary=:summary, content=:content, publish=:publish, changed=:changed, revision = revision + 1, author=:author WHERE nid=:nid;" + -- SQL node. - SQL_Delete_node: STRING = "delete from nodes where nid=:nid;" + sql_delete_node: STRING = "DELETE FROM nodes WHERE nid=:nid;" - Sql_update_node_author: STRING = "update nodes SET author=:author where nid=:nid;" +-- sql_update_node_author: STRING = "UPDATE nodes SET author=:author WHERE nid=:nid;" - SQL_Update_node_title: STRING ="update nodes SET title=:title, changed=:changed, version = version + 1 where nid=:nid;" - -- SQL update node title. +-- sql_update_node_title: STRING ="UPDATE nodes SET title=:title, changed=:changed, revision = revision + 1 WHERE nid=:nid;" +-- -- SQL update node title. - SQL_Update_node_summary: STRING ="update nodes SET summary=:summary, changed=:changed, version = version + 1 where nid=:nid;" - -- SQL update node summary. +-- sql_update_node_summary: STRING ="UPDATE nodes SET summary=:summary, changed=:changed, revision = revision + 1 WHERE nid=:nid;" +-- -- SQL update node summary. - SQL_Update_node_content: STRING ="update nodes SET content=:content, changed=:changed, version = version + 1 where nid=:nid;" - -- SQL node content. +-- sql_update_node_content: STRING ="UPDATE nodes SET content=:content, changed=:changed, revision = revision + 1 WHERE nid=:nid;" +-- -- SQL node content. - Sql_last_insert_node_id: STRING = "SELECT MAX(nid) from nodes;" + sql_last_insert_node_id: STRING = "SELECT MAX(nid) FROM nodes;" feature {NONE} -- Sql Queries: USER_ROLES collaborators, author - Select_user_author: STRING = "SELECT * FROM Nodes INNER JOIN users ON nodes.author=users.uid and users.uid = :uid;" + Select_user_author: STRING = "SELECT uid, name, password, salt, email, status, created, signed FROM Nodes INNER JOIN users ON nodes.author=users.uid AND users.uid = :uid;" - Select_node_author: STRING = "SELECT * FROM Users INNER JOIN nodes ON nodes.author=users.uid and nodes.nid =:nid;" + Select_node_author: STRING = "SELECT nid, revision, type, title, summary, content, author, publish, created, changed FROM users INNER JOIN nodes ON nodes.author=users.uid AND nodes.nid =:nid;" feature {NONE} -- Implementation - fetch_node: CMS_NODE + fetch_node: detachable CMS_PARTIAL_NODE do - create Result.make ("", "", "") - if attached sql_read_integer_64 (1) as l_id then - Result.set_id (l_id) - end - if attached sql_read_string_32 (4) as l_title then - Result.set_title (l_title) - end - if attached sql_read_string_32 (5) as l_summary then - Result.set_summary (l_summary) - end - if attached sql_read_string (6) as l_content then - Result.set_content (l_content) - end - if attached sql_read_date_time (8) as l_publication_date then - Result.set_publication_date (l_publication_date) - end - if attached sql_read_date_time (9) as l_creation_date then - Result.set_creation_date (l_creation_date) - end - if attached sql_read_date_time (10) as l_modif_date then - Result.set_modification_date (l_modif_date) - end - if attached sql_read_integer_64 (7) as l_author_id then - -- access to API ... + if attached sql_read_string (3) as l_type then + create Result.make_empty (l_type) + + if attached sql_read_integer_64 (1) as l_id then + Result.set_id (l_id) + end + if attached sql_read_integer_64 (2) as l_revision then + Result.set_revision (l_revision) + end + if attached sql_read_string_32 (4) as l_title then + Result.set_title (l_title) + end + if attached sql_read_string_32 (5) as l_summary then + Result.set_summary (l_summary) + end + if attached sql_read_string (6) as l_content then + Result.set_content (l_content) + end + if attached sql_read_date_time (8) as l_publication_date then + Result.set_publication_date (l_publication_date) + end + if attached sql_read_date_time (9) as l_creation_date then + Result.set_creation_date (l_creation_date) + end + if attached sql_read_date_time (10) as l_modif_date then + Result.set_modification_date (l_modif_date) + end + if attached sql_read_integer_64 (7) as l_author_id then + Result.set_author (create {CMS_PARTIAL_USER}.make_with_id (l_author_id)) + end end end diff --git a/src/persistence/node/cms_node_storage_sql_page_extension.e b/src/persistence/node/cms_node_storage_sql_page_extension.e new file mode 100644 index 0000000..31f049d --- /dev/null +++ b/src/persistence/node/cms_node_storage_sql_page_extension.e @@ -0,0 +1,111 @@ +note + description: "Storage extension for Page nodes." + date: "$Date$" + revision: "$Revision$" + +class + CMS_NODE_STORAGE_SQL_PAGE_EXTENSION + +inherit + CMS_NODE_STORAGE_EXTENSION [CMS_PAGE] + + CMS_PROXY_STORAGE_SQL + rename + sql_storage as node_storage + redefine + node_storage + end + + SHARED_LOGGER + +create + make + +feature {NONE} -- Initialization + + node_storage: CMS_NODE_STORAGE_SQL + -- + +feature -- Access + + content_type: STRING + once + Result := {CMS_PAGE_CONTENT_TYPE}.name + end + +feature -- Persistence + + store (a_node: CMS_PAGE) + local + l_parameters: STRING_TABLE [ANY] + l_new_parent_id, l_previous_parent_id: INTEGER_64 + l_update: BOOLEAN + do + error_handler.reset + write_information_log (generator + ".store_page_data") + create l_parameters.make (2) + l_parameters.put (a_node.id, "nid") + l_parameters.put (a_node.revision, "revision") + + sql_query (sql_select_page_data, l_parameters) + if has_error then + if sql_rows_count = 1 then + l_previous_parent_id := sql_read_integer_64 (3) + l_update := True + end + if attached a_node.parent as l_parent then + l_new_parent_id := l_parent.id + else + l_new_parent_id := 0 + end + l_parameters.put (l_new_parent_id, "parent") + if l_update and l_new_parent_id /= l_previous_parent_id then + sql_change (sql_update_page_data, l_parameters) + elseif l_new_parent_id > 0 then + sql_change (sql_insert_page_data, l_parameters) + else + -- no page data, means everything is empty. + end + end + end + + load (a_node: CMS_PAGE) + local + l_parameters: STRING_TABLE [ANY] + n: INTEGER + ct: CMS_PAGE_CONTENT_TYPE + do + error_handler.reset + write_information_log (generator + ".fill_page") + create l_parameters.make (2) + l_parameters.put (a_node.id, "nid") + l_parameters.put (a_node.revision, "revision") + sql_query (sql_select_page_data, l_parameters) + if not has_error then + n := sql_rows_count + if n = 1 then + -- nid, revision, parent + if + attached sql_read_integer_64 (3) as l_parent_id and then + l_parent_id /= a_node.id and then + attached node_storage.node_by_id (l_parent_id) as l_parent + then + create ct + a_node.set_parent (ct.new_node (l_parent)) + else + write_debug_log ("Invalid parent node id!") + end + else + check unique_data: n = 0 end + end + end + end + +feature -- SQL + + sql_select_page_data: STRING = "SELECT nid, revision, parent FROM page_nodes WHERE nid =:nid AND revision=:revision;" + sql_insert_page_data: STRING = "INSERT INTO page_nodes (nid, revision, parent) VALUES (:nid, :revision, :parent);" + sql_update_page_data: STRING = "UPDATE page_nodes SET nid=:nid, revision=:revision, parent=:parent WHERE nid=:nid AND revision=:revision;" + + +end diff --git a/src/persistence/user/cms_user_storage.e b/src/persistence/user/cms_user_storage.e index abbc2f0..a399a62 100644 --- a/src/persistence/user/cms_user_storage.e +++ b/src/persistence/user/cms_user_storage.e @@ -90,29 +90,29 @@ feature -- Change: user feature -- Access: roles and permissions - user_has_permission (u: detachable CMS_USER; s: detachable READABLE_STRING_8): BOOLEAN - -- Anonymous or user `u' has permission for `s' ? - --| `s' could be "create page", - do --- if s = Void then --- Result := True --- elseif u = Void then ----- Result := user_role_has_permission (anonymous_user_role, s) --- else --- Result := user_role_has_permission (authenticated_user_role, s) --- if not Result and attached u.roles as l_roles then --- across --- l_roles as r --- until --- Result --- loop --- if attached user_role_by_id (r.item) as ur then --- Result := user_role_has_permission (ur, s) --- end --- end --- end --- end - end +-- user_has_permission (u: detachable CMS_USER; s: detachable READABLE_STRING_8): BOOLEAN +-- -- Anonymous or user `u' has permission for `s' ? +-- --| `s' could be "create page", +-- do +---- if s = Void then +---- Result := True +---- elseif u = Void then +------ Result := user_role_has_permission (anonymous_user_role, s) +---- else +---- Result := user_role_has_permission (authenticated_user_role, s) +---- if not Result and attached u.roles as l_roles then +---- across +---- l_roles as r +---- until +---- Result +---- loop +---- if attached user_role_by_id (r.item) as ur then +---- Result := user_role_has_permission (ur, s) +---- end +---- end +---- end +---- end +-- end user_role_has_permission (a_role: CMS_USER_ROLE; s: READABLE_STRING_8): BOOLEAN do @@ -124,6 +124,12 @@ feature -- Access: roles and permissions deferred end + user_roles_for (a_user: CMS_USER): LIST [CMS_USER_ROLE] + -- User roles for user `a_user'. + -- Note: anonymous and authenticated roles are not included. + deferred + end + user_roles: LIST [CMS_USER_ROLE] -- Possible list of user roles. deferred diff --git a/src/persistence/user/cms_user_storage_sql.e b/src/persistence/user/cms_user_storage_sql.e index b98ef69..cf87ae9 100644 --- a/src/persistence/user/cms_user_storage_sql.e +++ b/src/persistence/user/cms_user_storage_sql.e @@ -208,24 +208,240 @@ feature -- Change: user feature -- Access: roles and permissions user_role_by_id (a_id: like {CMS_USER_ROLE}.id): detachable CMS_USER_ROLE + local + l_parameters: STRING_TABLE [ANY] do - to_implement (generator + ".user_role_by_id") + error_handler.reset + write_information_log (generator + ".user_role_by_id") + create l_parameters.make (1) + l_parameters.put (a_id, "rid") + sql_query (select_user_role_by_id, l_parameters) + if sql_rows_count = 1 then + Result := fetch_user_role + if Result /= Void and not has_error then + fill_user_role (Result) + end + else + check no_more_than_one: sql_rows_count = 0 end + end + end + + user_roles_for (a_user: CMS_USER): LIST [CMS_USER_ROLE] + local + l_parameters: STRING_TABLE [ANY] + do + error_handler.reset + write_information_log (generator + ".user_roles_for") + + create {ARRAYED_LIST [CMS_USER_ROLE]} Result.make (0) + from + create l_parameters.make (1) + l_parameters.put (a_user.id, "uid") + sql_query (select_user_roles_by_user_id, l_parameters) + sql_start + until + sql_after + loop + if attached fetch_user_role as l_role then + Result.force (l_role) + end + sql_forth + end + if not has_error then + across Result as ic loop + fill_user_role (ic.item) + end + end end user_roles: LIST [CMS_USER_ROLE] + local + l_role: detachable CMS_USER_ROLE do - to_implement (generator + ".user_roles") - create {ARRAYED_LIST[CMS_USER_ROLE]} Result.make (0) + error_handler.reset + write_information_log (generator + ".user_roles") + + create {ARRAYED_LIST [CMS_USER_ROLE]} Result.make (0) + from + sql_query (select_user_roles, Void) + sql_start + until + sql_after + loop + l_role := fetch_user_role + if l_role /= Void then + Result.force (l_role) + end + sql_forth + end + if not has_error then + across Result as ic loop + fill_user_role (ic.item) + end + end + end + + fill_user_role (a_role: CMS_USER_ROLE) + require + a_role_has_id: a_role.has_id + do + if attached role_permissions_by_id (a_role.id) as l_permissions then + across + l_permissions as p_ic + loop + a_role.add_permission (p_ic.item) + end + end + end + + role_permissions_by_id (a_role_id: INTEGER_32): LIST [READABLE_STRING_8] + local + l_parameters: STRING_TABLE [ANY] + do + error_handler.reset + write_information_log (generator + ".role_permissions_by_id") + + create {ARRAYED_LIST [READABLE_STRING_8]} Result.make (0) + from + create l_parameters.make (1) + l_parameters.put (a_role_id, "rid") + sql_query (select_role_permissions_by_role_id, l_parameters) + sql_start + until + sql_after + loop + if attached sql_read_string (1) as l_permission then + Result.force (l_permission) + end +-- if attached sql_read_string_32 (2) as l_module then +-- end + sql_forth + end end feature -- Change: roles and permissions save_user_role (a_user_role: CMS_USER_ROLE) + local + l_parameters: STRING_TABLE [detachable ANY] + l_existing_role: detachable CMS_USER_ROLE + l_permissions: detachable LIST [READABLE_STRING_8] + p: READABLE_STRING_8 + l_found: BOOLEAN do - to_implement (generator + ".save_user_role") + error_handler.reset + write_information_log (generator + ".save_user_role") + + + if a_user_role.has_id then + l_existing_role := user_role_by_id (a_user_role.id) + check l_existing_role /= Void and then l_existing_role.id = a_user_role.id end + if + l_existing_role /= Void and then + l_existing_role.name.same_string (a_user_role.name) + then + -- No update needed + else + create l_parameters.make (2) + l_parameters.put (a_user_role.id, "rid") + l_parameters.put (a_user_role.name, "name") + sql_change (sql_update_user_role, l_parameters) + end + if not a_user_role.permissions.is_empty then + -- FIXME: check if this is non set permissions,or none ... + if l_existing_role /= Void then + l_permissions := l_existing_role.permissions + fill_user_role (l_existing_role) + end + if l_permissions = Void or else l_permissions.is_empty then + l_permissions := role_permissions_by_id (a_user_role.id) + end + + across + a_user_role.permissions as ic + loop + p := ic.item + from + l_found := False + l_permissions.start + until + l_found or l_permissions.after + loop + if p.is_case_insensitive_equal (l_permissions.item) then + l_found := True + else + l_permissions.forth + end + end + if l_found then + -- Already there, skip + else + -- Add permission + set_permission_for_role_id (p, a_user_role.id) + end + end + -- Remove other + across + l_permissions as ic + loop + unset_permission_for_role_id (ic.item, a_user_role.id) + end + end + else + create l_parameters.make (1) + l_parameters.put (a_user_role.name, "name") + sql_change (sql_insert_user_role, l_parameters) + if not error_handler.has_error then + a_user_role.set_id (last_inserted_user_role_id) + across + a_user_role.permissions as ic + loop + set_permission_for_role_id (ic.item, a_user_role.id) + end + end + end end -feature {NONE} -- Implementation + set_permission_for_role_id (a_permission: READABLE_STRING_8; a_role_id: INTEGER) + require + a_role_id > 0 + not a_permission.is_whitespace + local + l_parameters: STRING_TABLE [detachable ANY] + do + create l_parameters.make (3) + l_parameters.put (a_role_id, "rid") + l_parameters.put (a_permission, "permission") + l_parameters.put (Void, "module") -- FIXME: unsupported for now! + sql_change (sql_insert_user_role_permission, l_parameters) + end + + unset_permission_for_role_id (a_permission: READABLE_STRING_8; a_role_id: INTEGER) + require + a_role_id > 0 + not a_permission.is_whitespace + + local + l_parameters: STRING_TABLE [detachable ANY] + do + create l_parameters.make (2) + l_parameters.put (a_role_id, "rid") + l_parameters.put (a_permission, "permission") + sql_change (sql_delete_user_role_permission, l_parameters) + end + + last_inserted_user_role_id: INTEGER_32 + -- Last inserted user role id. + do + error_handler.reset + write_information_log (generator + ".last_inserted_user_role_id") + sql_query (Sql_last_insert_user_role_id, Void) + if sql_rows_count = 1 then + Result := sql_read_integer_32 (1) + end + end + +feature {NONE} -- Implementation: User user_salt (a_username: READABLE_STRING_32): detachable READABLE_STRING_8 -- User salt for the given user `a_username', if any. @@ -289,32 +505,83 @@ feature {NONE} -- Implementation end end +feature {NONE} -- Implementation: User role + + fetch_user_role: detachable CMS_USER_ROLE + local + l_id: INTEGER_32 + l_name: detachable READABLE_STRING_32 + do + if attached sql_read_integer_32 (1) as rid then + l_id := rid + end + if attached sql_read_string_32 (2) as s and then not s.is_whitespace then + l_name := s + end + + if l_name /= Void then + create Result.make (l_name) + if l_id > 0 then + Result.set_id (l_id) + end + elseif l_id > 0 then + create Result.make_with_id (l_id) + end + end + feature {NONE} -- Sql Queries: USER - Select_users_count: STRING = "select count(*) from Users;" + Select_users_count: STRING = "SELECT count(*) FROM Users;" -- Number of users. - Sql_last_insert_user_id: STRING = "SELECT MAX(uid) from Users;" + Sql_last_insert_user_id: STRING = "SELECT MAX(uid) FROM Users;" - Select_users: STRING = "select * from Users;" + Select_users: STRING = "SELECT * FROM Users;" -- List of users. - Select_user_by_id: STRING = "select * from Users where uid =:uid;" + Select_user_by_id: STRING = "SELECT * FROM Users WHERE uid =:uid;" -- Retrieve user by id if exists. - Select_user_by_name: STRING = "select * from Users where name =:name;" + Select_user_by_name: STRING = "SELECT * FROM Users WHERE name =:name;" -- Retrieve user by name if exists. - Select_user_by_email: STRING = "select * from Users where email =:email;" + Select_user_by_email: STRING = "SELECT * FROM Users WHERE email =:email;" -- Retrieve user by email if exists. - Select_salt_by_username: STRING = "select salt from Users where name =:name;" + Select_salt_by_username: STRING = "SELECT salt FROM Users WHERE name =:name;" -- Retrieve salt by username if exists. - Sql_Insert_user: STRING = "insert into users (name, password, salt, email, created) values (:name, :password, :salt, :email, :created);" + sql_insert_user: STRING = "INSERT INTO users (name, password, salt, email, created) VALUES (:name, :password, :salt, :email, :created);" -- SQL Insert to add a new node. - sql_update_user: STRING = "update users SET name=:name, password=:password, salt=:salt, email=:email WHERE uid=:uid;" + sql_update_user: STRING = "UPDATE users SET name=:name, password=:password, salt=:salt, email=:email WHERE uid=:uid;" +feature {NONE} -- Sql Queries: USER ROLE + + sql_last_insert_user_role_id: STRING = "SELECT MAX(rid) FROM roles;" + + select_user_roles: STRING = "SELECT rid, name FROM roles;" + -- List of user roles. + + sql_insert_user_role: STRING = "INSERT INTO roles (name) VALUES (:name);" + -- SQL Insert to add a new user role with name :name. + + sql_update_user_role : STRING = "UPDATE roles SET name=:name WHERE rid=:rid;" + -- Update user role with id :rid. + + select_user_roles_by_user_id: STRING = "SELECT rid, name FROM roles INNER JOIN users_roles ON users_roles.rid=roles.rid WHERE users_roles.uid=:uid;" + -- List of user roles for user id :uid. + + select_user_role_by_id: STRING = "SELECT rid, name FROM roles WHERE rid=:rid;" + -- User role for role id :rid; + + sql_insert_user_role_permission: STRING = "INSERT INTO role_permissions (rid, permission, module) VALUES (:rid, :permission, :module);" + -- SQL Insert a new permission :permission for user role :rid. + + sql_delete_user_role_permission: STRING = "DELETE FROM role_permissions WHERE rid=:rid AND permission=:permission;" + -- SQL Delete permission :permission from user role :rid. + + select_role_permissions_by_role_id: STRING = "SELECT permission, module FROM role_permissions WHERE rid=:rid;" + -- User role permissions for role id :rid; end diff --git a/src/service/cms_api.e b/src/service/cms_api.e index 8eb8e78..e036ab7 100644 --- a/src/service/cms_api.e +++ b/src/service/cms_api.e @@ -32,6 +32,8 @@ feature {NONE} -- Initialize initialize -- Initialize the persitent layer. + local + l_module: CMS_MODULE do to_implement ("Refactor database setup") if attached setup.storage (error_handler) as l_storage then @@ -39,6 +41,17 @@ feature {NONE} -- Initialize else create {CMS_STORAGE_NULL} storage end + storage.set_api (Current) + + across + setup.enabled_modules as ic + loop + l_module := ic.item + if not l_module.is_installed (Current) then + l_module.install (Current) + end + l_module.initialize (Current) + end end feature -- Access @@ -50,7 +63,14 @@ feature -- Access -- Logger storage: CMS_STORAGE - -- Default persistence storage. + -- Default persistence storage. + +feature -- Formats + + formats: CMS_FORMATS + once + create Result + end feature -- Status Report @@ -66,6 +86,15 @@ feature -- Status Report Result := error_handler.as_string_representation end +feature -- Permissions system + + user_has_permission (a_user: detachable CMS_USER; a_permission: detachable READABLE_STRING_GENERAL): BOOLEAN + -- Anonymous or user `a_user' has permission for `a_permission'? + --| `a_permission' could be for instance "create page". + do + Result := user_api.user_has_permission (a_user, a_permission) + end + feature -- Query: module module (a_type: TYPE [CMS_MODULE]): detachable CMS_MODULE diff --git a/src/service/cms_module_api.e b/src/service/cms_module_api.e index a5c085c..1568758 100644 --- a/src/service/cms_module_api.e +++ b/src/service/cms_module_api.e @@ -11,16 +11,22 @@ feature {NONE} -- Implementation make (a_api: CMS_API) do - api := a_api + cms_api := a_api + initialize + end + + initialize + -- Initialize Current api. + do end feature {CMS_MODULE, CMS_API} -- Restricted access - api: CMS_API + cms_api: CMS_API storage: CMS_STORAGE do - Result := api.storage + Result := cms_api.storage end end diff --git a/src/service/handler/cms_module_handler.e b/src/service/handler/cms_module_handler.e index 4a86272..3cff4bb 100644 --- a/src/service/handler/cms_module_handler.e +++ b/src/service/handler/cms_module_handler.e @@ -1,6 +1,5 @@ note - description: "Summary description for {CMS_MODULE_HANDLER}." - author: "" + description: "CMS handler specific for a module api" date: "$Date: 2015-02-13 13:08:13 +0100 (ven., 13 févr. 2015) $" revision: "$Revision: 96616 $" diff --git a/src/service/response/cms_response.e b/src/service/response/cms_response.e index bd679b1..3043d68 100644 --- a/src/service/response/cms_response.e +++ b/src/service/response/cms_response.e @@ -268,13 +268,6 @@ feature -- Logging -- service.storage.save_log (l_log) end -feature -- Formats - - formats: CMS_FORMATS - once - create Result - end - feature -- Menu menu_system: CMS_MENU_SYSTEM diff --git a/src/service/user/cms_user_api.e b/src/service/user/cms_user_api.e index 5d89cea..c17536a 100644 --- a/src/service/user/cms_user_api.e +++ b/src/service/user/cms_user_api.e @@ -17,6 +17,12 @@ create feature -- Access + user_by_id (a_id: like {CMS_USER}.id): detachable CMS_USER + -- User by id `a_id', if any. + do + Result := storage.user_by_id (a_id) + end + user_by_name (a_username: READABLE_STRING_32): detachable CMS_USER -- User by name `a_user_name', if any. do @@ -31,6 +37,65 @@ feature -- Status report Result := storage.is_valid_credential (a_auth_login, a_auth_password) end + user_has_permission (a_user: detachable CMS_USER; a_permission: detachable READABLE_STRING_GENERAL): BOOLEAN + -- Anonymous or user `a_user' has permission for `a_permission'? + --| `a_permission' could be for instance "create page". + do + if a_permission = Void then + Result := True + elseif a_user = Void then + Result := user_role_has_permission (anonymous_user_role, a_permission) + else + Result := user_role_has_permission (authenticated_user_role, a_permission) + if not Result then + Result := across user_roles (a_user) as ic some user_role_has_permission (ic.item, a_permission) end + end + end + end + + user_roles (a_user: CMS_USER): LIST [CMS_USER_ROLE] + local + l_roles: detachable LIST [CMS_USER_ROLE] + do + l_roles := a_user.roles + if l_roles = Void then + -- Fill user with its roles. + create {ARRAYED_LIST [CMS_USER_ROLE]} l_roles.make (0) + l_roles := storage.user_roles_for (a_user) + end + Result := l_roles + end + +feature -- User roles. + + anonymous_user_role: CMS_USER_ROLE + do + if attached user_role_by_id (1) as l_anonymous then + Result := l_anonymous + else + create Result.make ("anonymous") + end + end + + authenticated_user_role: CMS_USER_ROLE + do + if attached user_role_by_id (2) as l_authenticated then + Result := l_authenticated + else + create Result.make ("authenticated") + end + end + + user_role_has_permission (a_role: CMS_USER_ROLE; a_permission: READABLE_STRING_GENERAL): BOOLEAN + do + Result := a_role.has_permission (a_permission) + end + + user_role_by_id (a_id: like {CMS_USER_ROLE}.id): detachable CMS_USER_ROLE + do + Result := storage.user_role_by_id (a_id) + end + feature -- Change User new_user (a_user: CMS_USER)