diff --git a/examples/demo/site/modules/node/scripts/node.sql b/examples/demo/site/modules/node/scripts/node.sql index 43078f9..bdea9a7 100644 --- a/examples/demo/site/modules/node/scripts/node.sql +++ b/examples/demo/site/modules/node/scripts/node.sql @@ -11,12 +11,27 @@ CREATE TABLE nodes ( `publish` DATETIME, `created` DATETIME NOT NULL, `changed` DATETIME NOT NULL, - `status` INTEGER + `status` INTEGER, + CONSTRAINT Unique_nid_revision UNIQUE (nid,revision) +); + +CREATE TABLE node_revisions ( + `nid` INTEGER NOT NULL, + `revision` INTEGER NOT NULL, + `title` VARCHAR(255) NOT NULL, + `summary` TEXT, + `content` TEXT, + `format` VARCHAR(128), + `author` INTEGER, + `changed` DATETIME NOT NULL, + `status` INTEGER, + CONSTRAINT Unique_nid_revision PRIMARY KEY (nid,revision) ); CREATE TABLE page_nodes( - `nid` INTEGER PRIMARY KEY AUTO_INCREMENT NOT NULL, - `revision` INTEGER, - `parent` INTEGER + `nid` INTEGER NOT NULL, + `revision` INTEGER NOT NULL, + `parent` INTEGER, + CONSTRAINT PK_nid_revision PRIMARY KEY (nid,revision) ); diff --git a/modules/blog/cms_blog_module.e b/modules/blog/cms_blog_module.e index c1bd05a..bca251e 100644 --- a/modules/blog/cms_blog_module.e +++ b/modules/blog/cms_blog_module.e @@ -49,12 +49,12 @@ feature {CMS_API} -- Module Initialization Precursor (api) if attached {CMS_NODE_API} api.module_api ({CMS_NODE_MODULE}) as l_node_api then + create ct create blog_api.make (api, l_node_api) node_api := l_node_api -- Depends on {CMS_NODE_MODULE} - create ct --| For now, add all available formats to content type `ct'. across api.formats as ic @@ -67,7 +67,7 @@ feature {CMS_API} -- Module Initialization -- Add support for CMS_BLOG, which requires a storage extension to store the optional "tags" value -- For now, we only have extension based on SQL statement. if attached {CMS_NODE_STORAGE_SQL} l_node_api.node_storage as l_sql_node_storage then - l_sql_node_storage.register_node_storage_extension (create {CMS_NODE_STORAGE_SQL_BLOG_EXTENSION}.make (l_sql_node_storage)) + l_sql_node_storage.register_node_storage_extension (create {CMS_NODE_STORAGE_SQL_BLOG_EXTENSION}.make (l_node_api, l_sql_node_storage)) end end end @@ -83,9 +83,10 @@ feature {CMS_API} -- Module management if not l_sql_storage.sql_table_exists ("blog_post_nodes") then sql := "[ CREATE TABLE blog_post_nodes( - `nid` INTEGER PRIMARY KEY AUTO_INCREMENT NOT NULL CHECK("nid">=0), - `revision` INTEGER, - `tags` VARCHAR(255) + `nid` INTEGER NOT NULL CHECK("nid">=0), + `revision` INTEGER NOT NULL, + `tags` VARCHAR(255), + CONSTRAINT PK_nid_revision PRIMARY KEY (nid,revision) ); ]" l_sql_storage.sql_execute_script (sql, Void) diff --git a/modules/blog/cms_node_storage_sql_blog_extension.e b/modules/blog/cms_node_storage_sql_blog_extension.e index 4dd2d5c..1b1d5dd 100644 --- a/modules/blog/cms_node_storage_sql_blog_extension.e +++ b/modules/blog/cms_node_storage_sql_blog_extension.e @@ -11,6 +11,7 @@ inherit CMS_PROXY_STORAGE_SQL rename + make as make_proxy, sql_storage as node_storage redefine node_storage @@ -21,6 +22,12 @@ create feature {NONE} -- Initialization + make (a_node_api: CMS_NODE_API; a_sql_storage: CMS_NODE_STORAGE_SQL) + do + set_node_api (a_node_api) + make_proxy (a_sql_storage) + end + node_storage: CMS_NODE_STORAGE_SQL -- @@ -39,22 +46,19 @@ feature -- Persistence l_new_tags: detachable STRING_32 l_previous_tags: detachable STRING_32 l_update: BOOLEAN + l_has_modif: BOOLEAN do - error_handler.reset if attached api as l_api then l_api.logger.put_information (generator + ".store", Void) end - create l_parameters.make (2) - l_parameters.put (a_node.id, "nid") - l_parameters.put (a_node.revision, "revision") - - sql_query (sql_select_blog_data, l_parameters) + error_handler.reset + -- Check existing record, if any. + if attached node_data (a_node) as d then + l_update := d.revision = a_node.revision + l_previous_tags := d.tags + end if not has_error then - if sql_rows_count = 1 then - l_previous_tags := sql_read_string_32 (3) - l_update := True - end if attached a_node.tags as l_tags and then not l_tags.is_empty then create l_new_tags.make (0) across @@ -68,37 +72,60 @@ feature -- Persistence else l_new_tags := Void end - l_parameters.put (l_new_tags, "tags") - if l_update and l_new_tags /~ l_previous_tags then - sql_change (sql_update_blog_data, l_parameters) - elseif l_new_tags /= Void then - sql_change (sql_insert_blog_data, l_parameters) + + l_has_modif := l_has_modif or (l_new_tags /~ l_previous_tags) + + create l_parameters.make (3) + l_parameters.put (a_node.id, "nid") + l_parameters.put (a_node.revision, "revision") + l_parameters.force (l_new_tags, "tags") + + if l_update then + if l_has_modif then + sql_change (sql_update_node_data, l_parameters) + end else - -- no blog data, means everything is empty. + if l_has_modif then + sql_change (sql_insert_node_data, l_parameters) + else + -- no page data, means everything is empty. + -- FOR NOW: always record row +-- sql_change (sql_insert_node_data, l_parameters) + end end end end load (a_node: CMS_BLOG) + local + l_tags: READABLE_STRING_32 + do + if attached node_data (a_node) as d then + l_tags := d.tags + a_node.set_tags_from_string (l_tags) + end + end + + node_data (a_node: CMS_NODE): detachable TUPLE [revision: INTEGER_64; tags: READABLE_STRING_32] local l_parameters: STRING_TABLE [ANY] + l_rev: INTEGER_64 + l_tags: detachable READABLE_STRING_32 n: INTEGER do error_handler.reset create l_parameters.make (2) l_parameters.put (a_node.id, "nid") l_parameters.put (a_node.revision, "revision") - sql_query (sql_select_blog_data, l_parameters) + sql_query (sql_select_node_data, l_parameters) if not has_error then n := sql_rows_count if n = 1 then - -- nid, revision, parent - if - attached sql_read_string_32 (3) as l_tags and then - not l_tags.is_whitespace - then - -- FIXME: find a simple way to access the declared content types. - a_node.set_tags_from_string (l_tags) + -- nid, revision, tags + l_rev := sql_read_integer_64 (2) + l_tags := sql_read_string_32 (3) + if l_tags /= Void then + Result := [l_rev, l_tags] end else check unique_data: n = 0 end @@ -108,8 +135,8 @@ feature -- Persistence feature -- SQL - sql_select_blog_data: STRING = "SELECT nid, revision, tags FROM blog_post_nodes WHERE nid =:nid AND revision=:revision;" - sql_insert_blog_data: STRING = "INSERT INTO blog_post_nodes (nid, revision, tags) VALUES (:nid, :revision, :tags);" - sql_update_blog_data: STRING = "UPDATE blog_post_nodes SET nid=:nid, revision=:revision, tags=:tags WHERE nid=:nid AND revision=:revision;" + sql_select_node_data: STRING = "SELECT nid, revision, tags FROM blog_post_nodes WHERE nid =:nid AND revision<=:revision ORDER BY revision DESC LIMIT 1;" + sql_insert_node_data: STRING = "INSERT INTO blog_post_nodes (nid, revision, tags) VALUES (:nid, :revision, :tags);" + sql_update_node_data: STRING = "UPDATE blog_post_nodes SET nid=:nid, revision=:revision, tags=:tags WHERE nid=:nid AND revision=:revision;" end diff --git a/modules/node/cms_node_api.e b/modules/node/cms_node_api.e index 7b45350..a5c8ad4 100644 --- a/modules/node/cms_node_api.e +++ b/modules/node/cms_node_api.e @@ -25,6 +25,7 @@ feature {NONE} -- Initialization do node_storage := a_node_storage make (a_api) +-- error_handler.add_synchronization (a_node_storage.error_handler) end initialize @@ -220,6 +221,12 @@ feature -- Access: Node Result := node_storage.nodes end + node_revisions (a_node: CMS_NODE): LIST [CMS_NODE] + do + Result := node_storage.node_revisions (a_node) + Result := full_nodes (Result) + end + trashed_nodes (a_user: detachable CMS_USER): LIST [CMS_NODE] -- List of nodes with status in {CMS_NODE_API}.trashed. -- if `a_user' is set, return nodes related to this user. @@ -242,6 +249,11 @@ feature -- Access: Node Result := full_node (node_storage.node_by_id (a_id)) end + revision_node (a_node_id: INTEGER_64; a_revision_id: INTEGER_64): detachable CMS_NODE + do + Result := full_node (node_storage.node_by_id_and_revision (a_node_id, a_revision_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'. @@ -277,10 +289,24 @@ feature -- Access: Node end end + full_nodes (a_nodes: LIST [CMS_NODE]): LIST [CMS_NODE] + -- Convert list of nodes into a list of nodes when possible. + do + create {ARRAYED_LIST [CMS_NODE]} Result.make (a_nodes.count) + + across + a_nodes as ic + loop + if attached full_node (ic.item) as l_full then + Result.force (l_full) + end + end + end + is_author_of_node (u: CMS_USER; a_node: CMS_NODE): BOOLEAN -- Is the user `u' owner of the node `n'. do - if attached node_storage.node_author (a_node.id) as l_author then + if attached node_storage.node_author (a_node) as l_author then Result := u.same_as (l_author) end end @@ -306,7 +332,9 @@ feature -- Change: Node save_node (a_node: CMS_NODE) -- Save `a_node'. do + reset_error node_storage.save_node (a_node) + error_handler.append (node_storage.error_handler) end new_node (a_node: CMS_NODE) @@ -314,39 +342,47 @@ feature -- Change: Node require no_id: not a_node.has_id do + reset_error node_storage.new_node (a_node) + error_handler.append (node_storage.error_handler) end delete_node (a_node: CMS_NODE) -- Delete `a_node'. do + reset_error if a_node.has_id then node_storage.delete_node (a_node) + error_handler.append (node_storage.error_handler) end end update_node (a_node: CMS_NODE) -- Update node `a_node' data. do + reset_error node_storage.update_node (a_node) + error_handler.append (node_storage.error_handler) end trash_node (a_node: CMS_NODE) -- Trash node `a_node'. --! remove the node from the storage. do + reset_error node_storage.trash_node (a_node) + error_handler.append (node_storage.error_handler) end - restore_node (a_node: CMS_NODE) -- Restore node `a_node'. -- From {CMS_NODE_API}.trashed to {CMS_NODE_API}.not_published. do + reset_error node_storage.restore_node (a_node) + error_handler.append (node_storage.error_handler) end - feature -- Node status Not_published: INTEGER = 0 diff --git a/modules/node/cms_node_module.e b/modules/node/cms_node_module.e index c7cedd1..0e0c3b8 100644 --- a/modules/node/cms_node_module.e +++ b/modules/node/cms_node_module.e @@ -71,7 +71,7 @@ feature {CMS_API} -- Module Initialization -- Add support for CMS_PAGE, which requires a storage extension to store the optional "parent" id. -- For now, we only have extension based on SQL statement. if attached {CMS_NODE_STORAGE_SQL} l_node_api.node_storage as l_sql_node_storage then - l_sql_node_storage.register_node_storage_extension (create {CMS_NODE_STORAGE_SQL_PAGE_EXTENSION}.make (l_sql_node_storage)) + l_sql_node_storage.register_node_storage_extension (create {CMS_NODE_STORAGE_SQL_PAGE_EXTENSION}.make (l_node_api, l_sql_node_storage)) -- FIXME: the following code is mostly for test purpose/initialization, remove later if l_sql_node_storage.sql_table_items_count ("page_nodes") = 0 then @@ -157,10 +157,18 @@ feature -- Access Result.force ("view any " + l_type_name) Result.force ("edit any " + l_type_name) Result.force ("delete any " + l_type_name) + Result.force ("trash any " + l_type_name) + Result.force ("restore any " + l_type_name) + + Result.force ("view revisions any " + l_type_name) Result.force ("view own " + l_type_name) Result.force ("edit own " + l_type_name) Result.force ("delete own " + l_type_name) + Result.force ("trash own " + l_type_name) + Result.force ("restore own " + l_type_name) + + Result.force ("view revisions own " + l_type_name) end end end @@ -189,6 +197,7 @@ feature -- Access: router a_router.map (l_uri_mapping, a_router.methods_get_post) a_router.handle ("/node/add/{type}", l_node_handler, a_router.methods_get_post) + a_router.handle ("/node/{id}/revision", l_node_handler, a_router.methods_get) a_router.handle ("/node/{id}/edit", l_node_handler, a_router.methods_get_post) a_router.handle ("/node/{id}/delete", l_node_handler, a_router.methods_get_post) a_router.handle ("/node/{id}/trash", l_node_handler, a_router.methods_get_post) @@ -201,8 +210,7 @@ feature -- Access: router create l_uri_mapping.make_trailing_slash_ignored ("/nodes", l_nodes_handler) a_router.map (l_uri_mapping, a_router.methods_get) - --Trash - + -- Trash create l_trash_handler.make (a_api, a_node_api) create l_uri_mapping.make_trailing_slash_ignored ("/trash", l_trash_handler) a_router.map (l_uri_mapping, a_router.methods_get) diff --git a/modules/node/handler/cms_node_type_webform_manager.e b/modules/node/handler/cms_node_type_webform_manager.e index cfa4e8f..1eadb94 100644 --- a/modules/node/handler/cms_node_type_webform_manager.e +++ b/modules/node/handler/cms_node_type_webform_manager.e @@ -187,7 +187,8 @@ feature -- Forms ... a_node.set_content (b, s, f.name) end - + -- Update author + a_node.set_author (response.user) end new_node (response: NODE_RESPONSE; fd: WSF_FORM_DATA; a_node: detachable CMS_NODE): G @@ -272,20 +273,25 @@ feature -- Output lnk.set_weight (1) a_response.add_to_primary_tabs (lnk) + if a_node.status = {CMS_NODE_API}.trashed then create lnk.make ("Trash", node_api.node_path (a_node) + "/trash") lnk.set_weight (2) a_response.add_to_primary_tabs (lnk) - else + elseif a_node /= Void and then a_node.has_id then -- Node in {{CMS_NODE_API}.published} or {CMS_NODE_API}.not_published} status. create lnk.make ("Edit", node_api.node_path (a_node) + "/edit") lnk.set_weight (2) a_response.add_to_primary_tabs (lnk) + if + node_api.has_permission_for_action_on_node ("view revisions", a_node, l_user) + then + create lnk.make ("Revisions", node_api.node_path (a_node) + "/revision") + lnk.set_weight (3) + a_response.add_to_primary_tabs (lnk) + end if - a_node /= Void and then - a_node.id > 0 and then - attached node_api.node_type_for (a_node) as l_type and then node_api.has_permission_for_action_on_node ("delete", a_node, l_user) then create lnk.make ("Delete", node_api.node_path (a_node) + "/delete") diff --git a/modules/node/handler/node_form_response.e b/modules/node/handler/node_form_response.e index d0dbf91..0de8dad 100644 --- a/modules/node/handler/node_form_response.e +++ b/modules/node/handler/node_form_response.e @@ -236,17 +236,15 @@ feature -- Form end if a_node /= Void then l_node := a_node + apply_form_data_to_node (a_type, fd, a_node) + if l_node.has_id then - change_node (a_type, fd, a_node) s := "modified" else - change_node (a_type, fd, a_node) - l_node.set_author (user) s := "created" end else l_node := new_node (a_type, fd, Void) - l_node.set_author (user) s := "created" end @@ -261,7 +259,11 @@ feature -- Form else api.log ("node", "Anonymous " + s + " node " + a_type.name +" #" + l_node.id.out, 0, node_local_link (l_node, Void)) end - add_success_message ("Node #" + l_node.id.out + " saved.") + if node_api.has_error then + add_error_message ("Node #" + l_node.id.out + " failed to save.") + else + add_success_message ("Node #" + l_node.id.out + " saved.") + end if attached fd.string_item ("path_alias") as f_path_alias and then @@ -398,9 +400,10 @@ feature -- Form else Result := a_content_type.new_node (a_node) end + Result.set_author (user) end - change_node (a_content_type: CMS_NODE_TYPE [CMS_NODE]; a_form_data: WSF_FORM_DATA; a_node: CMS_NODE) + apply_form_data_to_node (a_content_type: CMS_NODE_TYPE [CMS_NODE]; a_form_data: WSF_FORM_DATA; a_node: CMS_NODE) -- Update node `a_node' with form_data `a_form_data' for the given content type `a_content_type'. do if attached node_api.node_type_webform_manager (a_content_type) as wf then diff --git a/modules/node/handler/node_handler.e b/modules/node/handler/node_handler.e index 4843dd9..1805969 100644 --- a/modules/node/handler/node_handler.e +++ b/modules/node/handler/node_handler.e @@ -81,7 +81,7 @@ feature -- HTTP Methods -- local l_node: detachable CMS_NODE - l_nid: INTEGER_64 + l_nid, l_rev: INTEGER_64 edit_response: NODE_FORM_RESPONSE view_response: NODE_VIEW_RESPONSE do @@ -97,16 +97,32 @@ feature -- HTTP Methods check valid_url: req.percent_encoded_path_info.starts_with ("/node/") end create edit_response.make (req, res, api, node_api) edit_response.execute + elseif req.percent_encoded_path_info.ends_with ("/revision") then + do_revisions (req, res) else -- Display existing node l_nid := node_id_path_parameter (req) if l_nid > 0 then + if + attached {WSF_STRING} req.query_parameter ("revision") as p_rev and then + p_rev.value.is_integer_64 + then + l_rev := p_rev.value.to_integer_64 + end l_node := node_api.node (l_nid) if - l_node /= Void and then l_node.is_published + l_node /= Void and then + l_rev > 0 and then + node_api.has_permission_for_action_on_node ("view revisions", l_node, current_user (req)) + then + l_node := node_api.revision_node (l_nid, l_rev) + end + if + l_node /= Void and then (l_rev > 0 or else l_node.is_published) then create view_response.make (req, res, api, node_api) view_response.set_node (l_node) + view_response.set_revision (l_rev) view_response.execute else send_not_found (req, res) @@ -234,7 +250,7 @@ feature {NONE} -- Trash:Restore l_id.is_integer and then attached node_api.node (l_id.integer_value) as l_node then - if node_api.has_permission_for_action_on_node ("trash", l_node, current_user (req)) then + if node_api.has_permission_for_action_on_node ("restore", l_node, current_user (req)) then node_api.restore_node (l_node) res.send (create {CMS_REDIRECTION_RESPONSE_MESSAGE}.make (req.absolute_script_url (""))) else @@ -252,6 +268,58 @@ feature {NONE} -- Trash:Restore end end + do_revisions (req: WSF_REQUEST; res: WSF_RESPONSE) + -- Display revisions of a node. + local + r: GENERIC_VIEW_CMS_RESPONSE + b: STRING + n: CMS_NODE + l_link: CMS_LOCAL_LINK + do + 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 + if node_api.has_permission_for_action_on_node ("view revisions", l_node, current_user (req)) then + create r.make (req, res, api) + create b.make_empty + b.append ("") + r.set_title ("Revisions for " + html_encoded (l_node.title)) + r.set_main_content (b) + r.execute + else + send_access_denied (req, res) + -- send_not_authorized ? + end + else + do_error (req, res, l_id) + end + else + (create {INTERNAL_SERVER_ERROR_CMS_RESPONSE}.make (req, res, api)).execute + end + end + feature -- Error do_error (req: WSF_REQUEST; res: WSF_RESPONSE; a_id: detachable WSF_STRING) diff --git a/modules/node/handler/node_view_response.e b/modules/node/handler/node_view_response.e index aac253f..b3021ef 100644 --- a/modules/node/handler/node_view_response.e +++ b/modules/node/handler/node_view_response.e @@ -36,6 +36,9 @@ feature -- Access node: detachable CMS_NODE + revision: INTEGER_64 + -- If not zero, about history version of the node. + feature -- Element change set_node (a_node: like node) @@ -43,18 +46,22 @@ feature -- Element change node := a_node end + set_revision (a_rev: like revision) + do + revision := a_rev + end + feature -- Execution process -- Computed response message. local - b: STRING_8 + b: detachable STRING_8 nid: INTEGER_64 l_node: like node do l_node := node if l_node = Void then - create b.make_empty nid := node_id_path_parameter (request) if nid > 0 then l_node := node_api.node (nid) @@ -67,8 +74,16 @@ feature -- Execution then l_manager.append_html_output_to (l_node, Current) end + elseif revision > 0 then + set_main_content ("Missing revision node!") else - set_main_content ("Missing node") + set_main_content ("Missing node!") + end + if revision > 0 then + add_warning_message ("The revisions let you track differences between multiple versions of a post.") + end + if l_node /= Void and revision > 0 then + set_title ("Revision #" + revision.out + " of " + html_encoded (l_node.title)) end end diff --git a/modules/node/persistence/cms_node_storage_extension.e b/modules/node/persistence/cms_node_storage_extension.e index 289f95a..1ea4f40 100644 --- a/modules/node/persistence/cms_node_storage_extension.e +++ b/modules/node/persistence/cms_node_storage_extension.e @@ -6,8 +6,17 @@ note deferred class CMS_NODE_STORAGE_EXTENSION [G -> CMS_NODE] +feature -- Change + + set_node_api (a_node_api: CMS_NODE_API) + do + node_api := a_node_api + end + feature -- Access + node_api: CMS_NODE_API + content_type: READABLE_STRING_8 deferred end diff --git a/modules/node/persistence/cms_node_storage_i.e b/modules/node/persistence/cms_node_storage_i.e index 6f103c0..5021203 100644 --- a/modules/node/persistence/cms_node_storage_i.e +++ b/modules/node/persistence/cms_node_storage_i.e @@ -73,6 +73,11 @@ feature -- Access deferred end + node_revisions (a_node: CMS_NODE): LIST [CMS_NODE] + -- Revisions of node `a_node'. + deferred + end + trashed_nodes (a_user: detachable CMS_USER): LIST [CMS_NODE] -- List of nodes by user `a_user' if set, or any. require @@ -92,10 +97,18 @@ feature -- Access deferred end - node_author (a_id: like {CMS_NODE}.id): detachable CMS_USER + node_by_id_and_revision (a_node_id, a_revision: INTEGER_64): detachable CMS_NODE + -- Retrieve node by node id `a_node_id' and revision `a_revision', if any. + require + has_node_id: a_node_id > 0 + has_revision: a_revision > 0 + deferred + end + + node_author (a_node: CMS_NODE): detachable CMS_USER -- Node's author. if any. require - valid_node: a_id > 0 + valid_node: a_node.has_id deferred end diff --git a/modules/node/persistence/cms_node_storage_null.e b/modules/node/persistence/cms_node_storage_null.e index 3a34c77..25b6f51 100644 --- a/modules/node/persistence/cms_node_storage_null.e +++ b/modules/node/persistence/cms_node_storage_null.e @@ -35,12 +35,18 @@ feature -- Access: node do end - nodes: LIST[CMS_NODE] + nodes: LIST [CMS_NODE] -- List of nodes. do create {ARRAYED_LIST [CMS_NODE]} Result.make (0) end + node_revisions (a_node: CMS_NODE): LIST [CMS_NODE] + -- Revisions of node `a_node'. + do + create {ARRAYED_LIST [CMS_NODE]} Result.make (0) + end + trashed_nodes (a_user: detachable CMS_USER): LIST [CMS_NODE] -- . do @@ -58,7 +64,12 @@ feature -- Access: node do end - node_author (a_id: like {CMS_NODE}.id): detachable CMS_USER + node_by_id_and_revision (a_node_id, a_revision: INTEGER_64): detachable CMS_NODE + -- + do + end + + node_author (a_node: CMS_NODE): detachable CMS_USER -- Node's author. if any. do end diff --git a/modules/node/persistence/cms_node_storage_sql.e b/modules/node/persistence/cms_node_storage_sql.e index 6fde513..d9f6a28 100644 --- a/modules/node/persistence/cms_node_storage_sql.e +++ b/modules/node/persistence/cms_node_storage_sql.e @@ -60,6 +60,33 @@ feature -- Access -- end end + node_revisions (a_node: CMS_NODE): LIST [CMS_NODE] + -- Revisions of node `a_node'. + local + l_parameters: STRING_TABLE [detachable ANY] + do + create {ARRAYED_LIST [CMS_NODE]} Result.make (0) + Result.force (a_node) + + error_handler.reset + write_information_log (generator + ".node_revisions") + + from + create l_parameters.make (1) + l_parameters.force (a_node.id, "nid") + l_parameters.force (a_node.revision, "revision") + sql_query (sql_select_node_revisions, l_parameters) + sql_start + until + sql_after + loop + if attached fetch_node as l_node then + Result.force (l_node) + end + sql_forth + end + end + trashed_nodes (a_user: detachable CMS_USER): LIST [CMS_NODE] -- List of nodes. local @@ -130,15 +157,32 @@ feature -- Access end end - node_author (a_id: like {CMS_NODE}.id): detachable CMS_USER + node_by_id_and_revision (a_node_id, a_revision: INTEGER_64): detachable CMS_NODE + -- Retrieve node by id `a_id', if any. + local + l_parameters: STRING_TABLE [ANY] + do + error_handler.reset + write_information_log (generator + ".node") + create l_parameters.make (1) + l_parameters.put (a_node_id, "nid") + l_parameters.put (a_revision, "revision") + sql_query (sql_select_node_by_id_and_revision, l_parameters) + if sql_rows_count = 1 then + Result := fetch_node + end + end + + node_author (a_node: CMS_NODE): detachable CMS_USER -- Node's author for the given node id. local l_parameters: STRING_TABLE [ANY] do error_handler.reset write_information_log (generator + ".node_author") - create l_parameters.make (1) - l_parameters.put (a_id, "nid") + create l_parameters.make (2) + l_parameters.put (a_node.id, "nid") + l_parameters.put (a_node.revision, "revision") sql_query (Select_user_author, l_parameters) if sql_rows_count >= 1 then Result := fetch_author @@ -156,6 +200,33 @@ feature -- Access end end + last_inserted_node_revision (a_node: detachable CMS_NODE): INTEGER_64 + -- Last insert revision for node of id `nid'. + local + l_parameters: STRING_TABLE [ANY] + do + error_handler.reset + write_information_log (generator + ".last_inserted_node_revision") + if a_node /= Void and then a_node.has_id then + create l_parameters.make (1) + l_parameters.force (a_node.id, "nid") + sql_query (Sql_last_insert_node_revision_for_nid, l_parameters) + if sql_rows_count = 1 then + if sql_item (1) /= Void then + Result := sql_read_integer_64 (1) + end + end + end +-- if Result = 0 and not has_error then --| include the case a_node = Void +-- sql_query (Sql_last_insert_node_revision, Void) +-- if sql_rows_count = 1 then +-- if sql_item (1) /= Void then +-- Result := sql_read_integer_64 (1) +-- end +-- end +-- end + end + feature -- Change: Node new_node (a_node: CMS_NODE) @@ -174,20 +245,17 @@ feature -- Change: Node -- Remove node by id `a_id'. local l_parameters: STRING_TABLE [ANY] - l_time: DATE_TIME do - create l_time.make_now_utc write_information_log (generator + ".delete_node {" + a_id.out + "}") error_handler.reset - create l_parameters.make (1) - l_parameters.put (l_time, "changed") + create l_parameters.make (3) + l_parameters.put (create {DATE_TIME}.make_now_utc, "changed") l_parameters.put ({CMS_NODE_API}.trashed, "status") l_parameters.put (a_id, "nid") sql_change (sql_delete_node, l_parameters) end - trash_node_by_id (a_id: INTEGER_64) -- local @@ -225,7 +293,9 @@ feature {NONE} -- Implementation store_node (a_node: CMS_NODE) local + l_copy_parameters: STRING_TABLE [detachable ANY] l_parameters: STRING_TABLE [detachable ANY] + l_rev: like last_inserted_node_revision now: DATE_TIME do create now.make_now_utc @@ -248,27 +318,46 @@ feature {NONE} -- Implementation l_parameters.put (0, "author") end sql_begin_transaction + if a_node.has_id then - -- Update - l_parameters.put (a_node.id, "nid") - sql_change (sql_update_node, l_parameters) - if not error_handler.has_error then - -- FIXED: FOR NOW no revision --- 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) + l_rev := a_node.revision.max (last_inserted_node_revision (a_node)) + 1 --| starts at (nid, 1) + else + l_rev := last_inserted_node_revision (a_node) + 1 --| starts at (nid, 1) + end + if a_node.has_id then + -- Copy existing node data to node_revisions table. + create l_copy_parameters.make (2) + l_copy_parameters.force (a_node.id, "nid") +-- l_copy_parameters.force (l_rev - 1, "revision") + sql_change (sql_copy_node_to_revision, l_copy_parameters) + + if not has_error then + a_node.set_revision (l_rev) + + -- Update + l_parameters.put (a_node.id, "nid") + l_parameters.put (a_node.revision, "revision") + sql_change (sql_update_node, l_parameters) + + if not error_handler.has_error then + a_node.set_modification_date (now) + end end else -- Store new node l_parameters.put (a_node.creation_date, "created") + l_parameters.put (l_rev, "revision") + 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. + a_node.set_revision (l_rev) -- New object. +-- check a_node.revision = last_inserted_node_revision (a_node) end end end if not error_handler.has_error then - extended_store (a_node) + extended_store (a_node) -- Note, `a_node.revision' is updated. end if error_handler.has_error then sql_rollback_transaction @@ -298,6 +387,9 @@ feature {NONE} -- Queries -- SQL Query to retrieve all nodes. --| note: {CMS_NODE_API}.trashed = -1 + sql_select_node_revisions: STRING = "SELECT nodes.nid, node_revisions.revision, nodes.type, node_revisions.title, node_revisions.summary, node_revisions.content, node_revisions.format, node_revisions.author, nodes.publish, nodes.created, node_revisions.changed, node_revisions.status FROM nodes INNER JOIN node_revisions ON nodes.nid = node_revisions.nid WHERE nodes.nid = :nid AND node_revisions.revision < :revision ORDER BY node_revisions.revision DESC;" + -- SQL query to get node revisions (missing the latest one). + sql_select_trash_nodes: STRING = "SELECT nid, revision, type, title, summary, content, format, author, publish, created, changed, status FROM nodes WHERE status = -1 ;" -- SQL Query to retrieve all trahsed nodes. --| note: {CMS_NODE_API}.trashed = -1 @@ -308,15 +400,15 @@ feature {NONE} -- Queries sql_select_node_by_id: STRING = "SELECT nid, revision, type, title, summary, content, format, author, publish, created, changed, status FROM nodes WHERE nid =:nid ORDER BY revision DESC, publish DESC LIMIT 1;" + sql_select_node_by_id_and_revision: STRING = "SELECT nodes.nid, node_revisions.revision, nodes.type, node_revisions.title, node_revisions.summary, node_revisions.content, node_revisions.format, node_revisions.author, nodes.publish, nodes.created, node_revisions.changed, node_revisions.status FROM nodes INNER JOIN node_revisions ON nodes.nid = node_revisions.nid WHERE nodes.nid = :nid AND node_revisions.revision = :revision ORDER BY node_revisions.revision DESC;" + sql_select_recent_nodes: STRING = "SELECT nid, revision, type, title, summary, content, format, author, publish, created, changed, status FROM nodes ORDER BY nid DESC, publish DESC LIMIT :rows OFFSET :offset ;" - sql_insert_node: STRING = "INSERT INTO nodes (revision, type, title, summary, content, format, publish, created, changed, status, author) VALUES (1, :type, :title, :summary, :content, :format, :publish, :created, :changed, :status, :author);" + sql_insert_node: STRING = "INSERT INTO nodes (revision, type, title, summary, content, format, publish, created, changed, status, author) VALUES (:revision, :type, :title, :summary, :content, :format, :publish, :created, :changed, :status, :author);" -- SQL Insert to add a new node. - sql_update_node : STRING = "UPDATE nodes SET revision = revision, type=:type, title=:title, summary=:summary, content=:content, format=:format, publish=:publish, changed=:changed, status=:status, author=:author WHERE nid=:nid;" --- FIXME: for now no revision inc.! --- sql_update_node : STRING = "UPDATE nodes SET revision = revision + 1, type=:type, title=:title, summary=:summary, content=:content, format=:format, publish=:publish, changed=:changed, revision = revision + 1, author=:author WHERE nid=:nid;" - -- SQL node. + sql_update_node : STRING = "UPDATE nodes SET revision=:revision, type=:type, title=:title, summary=:summary, content=:content, format=:format, publish=:publish, changed=:changed, status=:status, author=:author WHERE nid=:nid;" + -- SQL update node. sql_delete_node: STRING = "UPDATE nodes SET changed=:changed, status =:status WHERE nid=:nid" -- Soft deletion with free metadata. @@ -327,24 +419,18 @@ feature {NONE} -- Queries sql_restore_node: STRING = "UPDATE nodes SET changed=:changed, status =:status WHERE nid=:nid" -- Restore node to {CMS_NODE_API}.not_publised. --- 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, revision = revision + 1 WHERE nid=:nid;" --- -- SQL update node title. - --- 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, revision = revision + 1 WHERE nid=:nid;" --- -- SQL node content. - sql_last_insert_node_id: STRING = "SELECT MAX(nid) FROM nodes;" + sql_copy_node_to_revision: STRING = "INSERT INTO node_revisions (nid, revision, title, summary, content, format, author, changed, status) SELECT nid, revision, title, summary, content, format, author, changed, status FROM nodes WHERE nid=:nid;" + + Sql_last_insert_node_revision: STRING = "SELECT MAX(revision) FROM node_revisions;" + Sql_last_insert_node_revision_for_nid: STRING = "SELECT MAX(revision) FROM node_revisions WHERE nid=:nid;" + feature {NONE} -- Sql Queries: USER_ROLES collaborators, author - Select_user_author: STRING = "SELECT uid, name, password, salt, email, users.status, users.created, signed FROM nodes INNER JOIN users ON nodes.author=users.uid AND nodes.nid = :nid;" + Select_user_author: STRING = "SELECT uid, name, password, salt, email, users.status, users.created, signed FROM nodes INNER JOIN users ON nodes.author=users.uid AND nodes.nid = :nid AND nodes.revision = :revision;" - Select_node_author: STRING = "SELECT nid, revision, type, title, summary, content, format, author, publish, created, changed 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, format, author, publish, created, changed FROM users INNER JOIN nodes ON nodes.author=users.uid AND nodes.nid =:nid;" feature {NONE} -- Implementation diff --git a/modules/node/persistence/cms_node_storage_sql_page_extension.e b/modules/node/persistence/cms_node_storage_sql_page_extension.e index d385e64..9d64d50 100644 --- a/modules/node/persistence/cms_node_storage_sql_page_extension.e +++ b/modules/node/persistence/cms_node_storage_sql_page_extension.e @@ -11,6 +11,7 @@ inherit CMS_PROXY_STORAGE_SQL rename + make as make_proxy, sql_storage as node_storage redefine node_storage @@ -21,6 +22,12 @@ create feature {NONE} -- Initialization + make (a_node_api: CMS_NODE_API; a_sql_storage: CMS_NODE_STORAGE_SQL) + do + set_node_api (a_node_api) + make_proxy (a_sql_storage) + end + node_storage: CMS_NODE_STORAGE_SQL -- @@ -38,62 +45,86 @@ feature -- Persistence l_parameters: STRING_TABLE [ANY] l_new_parent_id, l_previous_parent_id: INTEGER_64 l_update: BOOLEAN + l_has_modif: 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") + if attached api as l_api then + l_api.logger.put_information (generator + ".store", Void) + end - sql_query (sql_select_page_data, l_parameters) + error_handler.reset + -- Check existing record, if any. + if attached node_data (a_node) as d then + l_update := True + l_previous_parent_id := d.parent_id + end if not 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) + l_has_modif := l_has_modif or (l_new_parent_id /= l_previous_parent_id) + + create l_parameters.make (3) + l_parameters.put (a_node.id, "nid") + l_parameters.put (a_node.revision, "revision") + l_parameters.force (l_new_parent_id, "parent") + + if l_update then + if l_has_modif then + sql_change (sql_update_node_data, l_parameters) + end else - -- no page data, means everything is empty. + if l_has_modif then + sql_change (sql_insert_node_data, l_parameters) + else + -- no page data, means everything is empty. + -- FOR NOW: always record row + sql_change (sql_insert_node_data, l_parameters) + end end end end load (a_node: CMS_PAGE) + local + ct: CMS_PAGE_NODE_TYPE + l_parent_id: INTEGER_64 + do + if attached node_data (a_node) as d then + l_parent_id := d.parent_id + if + l_parent_id > 0 and then + l_parent_id /= a_node.id and then + attached node_storage.node_by_id (l_parent_id) as l_parent + then + if attached {CMS_PAGE_NODE_TYPE} node_api.content_type (l_parent.content_type) as l_parent_ct then + ct := l_parent_ct + else + create ct + end + a_node.set_parent (ct.new_node (l_parent)) + else + write_debug_log ("Invalid parent node id!") + end + end + end + + node_data (a_node: CMS_NODE): detachable TUPLE [parent_id: INTEGER_64] local l_parameters: STRING_TABLE [ANY] n: INTEGER - ct: CMS_PAGE_NODE_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) + sql_query (sql_select_node_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 - -- FIXME: find a simple way to access the declared content types. - create ct - a_node.set_parent (ct.new_node (l_parent)) - else - write_debug_log ("Invalid parent node id!") - end + Result := [sql_read_integer_64 (3)] else check unique_data: n = 0 end end @@ -102,9 +133,8 @@ feature -- Persistence 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;" - + sql_select_node_data: STRING = "SELECT nid, revision, parent FROM page_nodes WHERE nid =:nid AND revision<=:revision ORDER BY revision DESC LIMIT 1;" + sql_insert_node_data: STRING = "INSERT INTO page_nodes (nid, revision, parent) VALUES (:nid, :revision, :parent);" + sql_update_node_data: STRING = "UPDATE page_nodes SET nid=:nid, revision=:revision, parent=:parent WHERE nid=:nid AND revision=:revision;" end diff --git a/src/service/response/cms_response.e b/src/service/response/cms_response.e index 8773268..4b240e3 100644 --- a/src/service/response/cms_response.e +++ b/src/service/response/cms_response.e @@ -890,6 +890,10 @@ feature -- Generation add_to_primary_menu (lnk) invoke_menu_system_alter (menu_system) + if api.enabled_modules.count = 0 then + add_to_primary_menu (create {CMS_LOCAL_LINK}.make ("Install", "admin/install")) + end + -- Blocks create l_menu_list_prepared.make (0) get_blocks