Added trash feature: Remove or revert a node.

Added Handler to show the current trash nodes for a given user.
An admin can see all the trash nodes.
Updated storage to handle trash and revert nodes.
This commit is contained in:
jvelilla
2015-05-14 11:07:15 -03:00
parent 57bf5ad0dc
commit 9fbadac7ac
10 changed files with 401 additions and 13 deletions

View File

@@ -212,6 +212,14 @@ feature -- Access: Node
Result := node_storage.nodes Result := node_storage.nodes
end end
trash_nodes (a_user: CMS_USER): LIST [CMS_NODE]
-- List of nodes with status in {CMS_NODE_API}.trashed.
-- if the current user is admin, it will retrieve all the trashed nodes
do
Result := node_storage.trash_nodes (a_user.id)
end
recent_nodes (a_offset, a_rows: INTEGER): LIST [CMS_NODE] recent_nodes (a_offset, a_rows: INTEGER): LIST [CMS_NODE]
-- List of the `a_rows' most recent nodes starting from `a_offset'. -- List of the `a_rows' most recent nodes starting from `a_offset'.
do do
@@ -316,6 +324,21 @@ feature -- Change: Node
node_storage.update_node (a_node) node_storage.update_node (a_node)
end end
trash_node (a_node: CMS_NODE)
-- Trash node `a_node'.
--! remove the node from the storage.
do
node_storage.trash_node (a_node)
end
revert_node (a_node: CMS_NODE)
-- Revert node `a_node'.
-- From {CMS_NODE_API}.trashed to {CMS_NODE_API}.not_published.
do
node_storage.revert_node (a_node)
end
feature -- Node status feature -- Node status

View File

@@ -182,21 +182,28 @@ feature -- Output
a_response.add_variable (a_node, "node") a_response.add_variable (a_node, "node")
create lnk.make (a_response.translation ("View", Void), a_response.node_local_link (a_node).location) create lnk.make (a_response.translation ("View", Void), a_response.node_local_link (a_node).location)
lnk.set_weight (1) lnk.set_weight (1)
a_response.add_to_primary_tabs (lnk)
create lnk.make ("Edit", node_api.node_path (a_node) + "/edit")
lnk.set_weight (2)
a_response.add_to_primary_tabs (lnk) a_response.add_to_primary_tabs (lnk)
if if a_node.status = {CMS_NODE_API}.trashed then
a_node /= Void and then create lnk.make ("Trash", node_api.node_path (a_node) + "/trash")
a_node.id > 0 and then lnk.set_weight (2)
attached node_api.node_type_for (a_node) as l_type and then
node_api.has_permission_for_action_on_node ("delete", a_node, a_response.current_user (a_response.request))
then
create lnk.make ("Delete", node_api.node_path (a_node) + "/delete")
lnk.set_weight (3)
a_response.add_to_primary_tabs (lnk) a_response.add_to_primary_tabs (lnk)
else
-- 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
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, a_response.current_user (a_response.request))
then
create lnk.make ("Delete", node_api.node_path (a_node) + "/delete")
lnk.set_weight (3)
a_response.add_to_primary_tabs (lnk)
end
end end
create s.make_empty create s.make_empty

View File

@@ -99,6 +99,29 @@ feature -- Execution
set_title (formatted_string (translation ("Delete $1 #$2", Void), [l_type.title, l_node.id])) set_title (formatted_string (translation ("Delete $1 #$2", Void), [l_type.title, l_node.id]))
f.append_to_html (wsf_theme, b) f.append_to_html (wsf_theme, b)
end end
elseif
request.path_info.ends_with_general ("/trash") and then
node_api.has_permission_for_action_on_node ("trash", l_node, current_user (request))
then
f := new_trash_form (l_node, url (request.path_info, Void), "trash-" + l_type.name, l_type)
invoke_form_alter (f, fd)
if request.is_post_request_method then
f.process (Current)
fd := f.last_data
end
if l_node.has_id then
add_to_menu (create {CMS_LOCAL_LINK}.make (translation ("View", Void), node_url (l_node)), primary_tabs)
add_to_menu (create {CMS_LOCAL_LINK}.make ("Trash", "/node/" + l_node.id.out + "/trash"), primary_tabs)
end
if attached redirection as l_location then
-- FIXME: Hack for now
set_title (l_node.title)
b.append (html_encoded (l_type.title) + " trasged")
else
set_title (formatted_string (translation ("Trash $1 #$2", Void), [l_type.title, l_node.id]))
f.append_to_html (wsf_theme, b)
end
else else
b.append ("<h1>") b.append ("<h1>")
b.append (translation ("Access denied", Void)) b.append (translation ("Access denied", Void))
@@ -305,6 +328,47 @@ feature -- Form
Result := f Result := f
end end
new_trash_form (a_node: detachable CMS_NODE; a_url: READABLE_STRING_8; a_name: STRING; a_node_type: CMS_NODE_TYPE [CMS_NODE]): CMS_FORM
-- Create a web form named `a_name' for node `a_node' (if set), using form action url `a_url', and for type of node `a_node_type'.
local
f: CMS_FORM
ts: WSF_FORM_SUBMIT_INPUT
do
create f.make (a_url, a_name)
f.extend_html_text ("<br/>")
f.extend_html_text ("<legend>Are you sure you want to trash the current node?</legend>")
if
a_node /= Void and then
a_node.id > 0
then
create ts.make ("op")
ts.set_default_value ("Trash")
fixme ("[
ts.set_default_value (translation ("Trash"))
]")
f.extend (ts)
end
f.extend_html_text ("<br/>")
f.extend_html_text ("<legend>Do you want to revert the current node?</legend>")
if
a_node /= Void and then
a_node.id > 0
then
create ts.make ("op")
ts.set_default_value ("Revert")
fixme ("[
ts.set_default_value (translation ("Revert"))
]")
f.extend (ts)
end
Result := f
end
populate_form (a_content_type: CMS_NODE_TYPE [CMS_NODE]; a_form: WSF_FORM; a_node: detachable CMS_NODE) populate_form (a_content_type: CMS_NODE_TYPE [CMS_NODE]; a_form: WSF_FORM; a_node: detachable CMS_NODE)
-- Fill the web form `a_form' with data from `a_node' if set, -- Fill the web form `a_form' with data from `a_node' if set,
-- and apply this to content type `a_content_type'. -- and apply this to content type `a_content_type'.

View File

@@ -93,6 +93,10 @@ feature -- HTTP Methods
check valid_url: req.path_info.starts_with_general ("/node/") end check valid_url: req.path_info.starts_with_general ("/node/") end
create edit_response.make (req, res, api, node_api) create edit_response.make (req, res, api, node_api)
edit_response.execute edit_response.execute
elseif req.path_info.ends_with_general ("/trash") then
check valid_url: req.path_info.starts_with_general ("/node/") end
create edit_response.make (req, res, api, node_api)
edit_response.execute
else else
-- Display existing node -- Display existing node
l_nid := node_id_path_parameter (req) l_nid := node_id_path_parameter (req)
@@ -129,6 +133,18 @@ feature -- HTTP Methods
then then
do_delete (req, res) do_delete (req, res)
end end
elseif req.path_info.ends_with_general ("/trash") then
if
attached {WSF_STRING} req.form_parameter ("op") as l_op and then
l_op.value.same_string ("Trash")
then
do_trash (req, res)
elseif
attached {WSF_STRING} req.form_parameter ("op") as l_op and then
l_op.value.same_string ("Revert")
then
do_revert (req, res)
end
elseif req.path_info.starts_with_general ("/node/add/") then elseif req.path_info.starts_with_general ("/node/add/") then
create edit_response.make (req, res, api, node_api) create edit_response.make (req, res, api, node_api)
edit_response.execute edit_response.execute
@@ -178,6 +194,62 @@ feature -- HTTP Methods
send_not_implemented ("REST API not yet implemented", req, res) send_not_implemented ("REST API not yet implemented", req, res)
end end
feature {NONE} -- Trash:Revert
do_trash (req: WSF_REQUEST; res: WSF_RESPONSE)
-- Trash a node from the database.
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.integer_value) as l_node
then
if node_api.has_permission_for_action_on_node ("trash", l_node, current_user (req)) then
node_api.trash_node (l_node)
res.send (create {CMS_REDIRECTION_RESPONSE_MESSAGE}.make (req.absolute_script_url ("")))
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
else
send_access_denied (req, res)
end
end
do_revert (req: WSF_REQUEST; res: WSF_RESPONSE)
-- Revert a node: From {CMS_NODE_API}.trashed to {CMS_NODE_API}.not_published.
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.integer_value) as l_node
then
if node_api.has_permission_for_action_on_node ("trash", l_node, current_user (req)) then
node_api.revert_node (l_node)
res.send (create {CMS_REDIRECTION_RESPONSE_MESSAGE}.make (req.absolute_script_url ("")))
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
else
send_access_denied (req, res)
end
end
feature -- Error feature -- Error
do_error (req: WSF_REQUEST; res: WSF_RESPONSE; a_id: detachable WSF_STRING) do_error (req: WSF_REQUEST; res: WSF_RESPONSE; a_id: detachable WSF_STRING)

View File

@@ -0,0 +1,78 @@
note
description: "Request handler related to /trash "
date: "$Date$"
revision: "$Revision$"
class
TRASH_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)
-- <Precursor>
local
l_page: CMS_RESPONSE
s: STRING
n: CMS_NODE
lnk: CMS_LOCAL_LINK
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)
if attached current_user (req) as l_user then
l_page.add_variable (node_api.trash_nodes (l_user), "nodes")
-- NOTE: for development purposes we have the following hardcode output.
create s.make_from_string ("<p>Nodes:</p>")
if attached node_api.trash_nodes (l_user) as lst then
s.append ("<ul class=%"cms-nodes%">%N")
across
lst as ic
loop
n := ic.item
lnk := node_api.node_link (n)
s.append ("<li class=%"cms_type_"+ n.content_type +"%">")
s.append (l_page.link (lnk.title, lnk.location, Void))
s.append ("</li>%N")
end
s.append ("</ul>%N")
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<br/>", Void), "highlighted")
l_page.execute
end
end
end

View File

@@ -127,6 +127,7 @@ feature -- Access: router
l_node_handler: NODE_HANDLER l_node_handler: NODE_HANDLER
l_nodes_handler: NODES_HANDLER l_nodes_handler: NODES_HANDLER
l_uri_mapping: WSF_URI_MAPPING l_uri_mapping: WSF_URI_MAPPING
l_trash_handler: TRASH_HANDLER
do do
-- TODO: for now, focused only on web interface, add REST api later. [2015-April-29] -- TODO: for now, focused only on web interface, add REST api later. [2015-April-29]
create l_node_handler.make (a_api, a_node_api) create l_node_handler.make (a_api, a_node_api)
@@ -136,6 +137,7 @@ feature -- Access: router
a_router.handle_with_request_methods ("/node/add/{type}", l_node_handler, a_router.methods_get_post) a_router.handle_with_request_methods ("/node/add/{type}", l_node_handler, a_router.methods_get_post)
a_router.handle_with_request_methods ("/node/{id}/edit", l_node_handler, a_router.methods_get_post) a_router.handle_with_request_methods ("/node/{id}/edit", l_node_handler, a_router.methods_get_post)
a_router.handle_with_request_methods ("/node/{id}/delete", l_node_handler, a_router.methods_get_post) a_router.handle_with_request_methods ("/node/{id}/delete", l_node_handler, a_router.methods_get_post)
a_router.handle_with_request_methods ("/node/{id}/trash", l_node_handler, a_router.methods_get_post)
a_router.handle_with_request_methods ("/node/{id}", l_node_handler, a_router.methods_get) a_router.handle_with_request_methods ("/node/{id}", l_node_handler, a_router.methods_get)
-- For now: no REST API handling... a_router.methods_get_put_delete + a_router.methods_get_post) -- For now: no REST API handling... a_router.methods_get_put_delete + a_router.methods_get_post)
@@ -144,6 +146,13 @@ feature -- Access: router
create l_nodes_handler.make (a_api, a_node_api) create l_nodes_handler.make (a_api, a_node_api)
create l_uri_mapping.make_trailing_slash_ignored ("/nodes", l_nodes_handler) create l_uri_mapping.make_trailing_slash_ignored ("/nodes", l_nodes_handler)
a_router.map_with_request_methods (l_uri_mapping, a_router.methods_get) a_router.map_with_request_methods (l_uri_mapping, a_router.methods_get)
--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_with_request_methods (l_uri_mapping, a_router.methods_get)
end end
feature -- Hooks feature -- Hooks

View File

@@ -70,6 +70,11 @@ feature -- Access
deferred deferred
end end
trash_nodes (a_user_id: INTEGER_64): LIST [CMS_NODE]
-- List of nodes by user `a_user_id'.
deferred
end
recent_nodes (a_lower: INTEGER; a_count: INTEGER): LIST [CMS_NODE] recent_nodes (a_lower: INTEGER; a_count: INTEGER): LIST [CMS_NODE]
-- List of recent `a_count' nodes with an offset of `lower'. -- List of recent `a_count' nodes with an offset of `lower'.
deferred deferred
@@ -135,6 +140,36 @@ feature -- Change: Node
deferred deferred
end end
trash_node (a_node: CMS_NODE)
-- Trash `a_node'.
do
if a_node.has_id then
trash_node_by_id (a_node.id)
end
end
revert_node (a_node: CMS_NODE)
-- Revert `a_node'.
do
if a_node.has_id then
revert_node_by_id (a_node.id)
end
end
trash_node_by_id (a_id: INTEGER_64)
-- Trash node by id `a_id'.
require
valid_node_id: a_id > 0
deferred
end
revert_node_by_id (a_id: INTEGER_64)
-- Revert node by id `a_id'.
require
valid_node_id: a_id > 0
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 (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'. -- -- Update node title to `a_title', node identified by id `a_node_id'.
-- -- The user `a_user_id' is an existing or new collaborator. -- -- The user `a_user_id' is an existing or new collaborator.

View File

@@ -41,6 +41,12 @@ feature -- Access: node
create {ARRAYED_LIST [CMS_NODE]} Result.make (0) create {ARRAYED_LIST [CMS_NODE]} Result.make (0)
end end
trash_nodes (a_user_id: INTEGER_64): LIST [CMS_NODE]
-- List of nodes by user `a_user_id'.
do
create {ARRAYED_LIST [CMS_NODE]} Result.make (0)
end
recent_nodes (a_lower: INTEGER; a_count: INTEGER): LIST [CMS_NODE] recent_nodes (a_lower: INTEGER; a_count: INTEGER): LIST [CMS_NODE]
-- List of the `a_count' most recent nodes, starting from `a_lower'. -- List of the `a_count' most recent nodes, starting from `a_lower'.
do do
@@ -80,6 +86,16 @@ feature -- Node
do do
end end
trash_node_by_id (a_id: INTEGER_64)
-- <Precursor>
do
end
revert_node_by_id (a_id: INTEGER_64)
-- <Precursor>
do
end
-- update_node_title (a_user_id: like {CMS_NODE}.id; a_node_id: like {CMS_NODE}.id; a_title: READABLE_STRING_32) -- update_node_title (a_user_id: like {CMS_NODE}.id; a_node_id: like {CMS_NODE}.id; a_title: READABLE_STRING_32)
-- -- <Precursor> -- -- <Precursor>
-- do -- do

View File

@@ -59,6 +59,36 @@ feature -- Access
-- end -- end
end end
trash_nodes (a_user_id: INTEGER_64): LIST [CMS_NODE]
-- List of nodes.
local
l_parameters: STRING_TABLE [detachable ANY]
do
create {ARRAYED_LIST [CMS_NODE]} Result.make (0)
error_handler.reset
write_information_log (generator + ".trash_nodes")
from
create l_parameters.make (1)
if a_user_id > 1 then
-- Not admin user
l_parameters.put (a_user_id, "author")
sql_query (sql_select_trash_nodes_by_author, l_parameters)
else
sql_query (sql_select_trash_nodes, Void)
end
sql_start
until
sql_after
loop
if attached fetch_node as l_node then
Result.force (l_node)
end
sql_forth
end
end
recent_nodes (a_lower: INTEGER; a_count: INTEGER): LIST [CMS_NODE] recent_nodes (a_lower: INTEGER; a_count: INTEGER): LIST [CMS_NODE]
-- List of recent `a_count' nodes with an offset of `lower'. -- List of recent `a_count' nodes with an offset of `lower'.
local local
@@ -147,7 +177,7 @@ feature -- Change: Node
l_time: DATE_TIME l_time: DATE_TIME
do do
create l_time.make_now_utc create l_time.make_now_utc
write_information_log (generator + ".delete_node") write_information_log (generator + ".delete_node {" + a_id.out + "}")
error_handler.reset error_handler.reset
create l_parameters.make (1) create l_parameters.make (1)
@@ -157,6 +187,40 @@ feature -- Change: Node
sql_change (sql_delete_node, l_parameters) sql_change (sql_delete_node, l_parameters)
end end
trash_node_by_id (a_id: INTEGER_64)
-- <Precursor>
local
l_parameters: STRING_TABLE [ANY]
l_time: DATE_TIME
do
create l_time.make_now_utc
write_information_log (generator + ".trash_node {" + a_id.out + "}")
error_handler.reset
create l_parameters.make (1)
l_parameters.put (a_id, "nid")
sql_change (sql_trash_node, l_parameters)
end
revert_node_by_id (a_id: INTEGER_64)
-- <Precursor>
local
l_parameters: STRING_TABLE [ANY]
l_time: DATE_TIME
do
create l_time.make_now_utc
write_information_log (generator + ".revert_node {" + a_id.out + "}")
error_handler.reset
create l_parameters.make (1)
l_parameters.put (l_time, "changed")
l_parameters.put ({CMS_NODE_API}.not_published, "status")
l_parameters.put (a_id, "nid")
sql_change (sql_revert_node, l_parameters)
end
feature {NONE} -- Implementation feature {NONE} -- Implementation
store_node (a_node: CMS_NODE) store_node (a_node: CMS_NODE)
@@ -228,6 +292,14 @@ feature {NONE} -- Queries
-- SQL Query to retrieve all nodes. -- SQL Query to retrieve all nodes.
--| note: {CMS_NODE_API}.trashed = -1 --| note: {CMS_NODE_API}.trashed = -1
sql_select_trash_nodes: STRING = "SELECT * FROM Nodes WHERE status = -1 ;"
-- SQL Query to retrieve all trahsed nodes.
--| note: {CMS_NODE_API}.trashed = -1
sql_select_trash_nodes_by_author: STRING = "SELECT * FROM Nodes WHERE status = -1 and author = :author ;"
-- SQL Query to retrieve all nodes by a given author.
--| note: {CMS_NODE_API}.trashed = -1
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: 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_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_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 ;"
@@ -243,6 +315,12 @@ feature {NONE} -- Queries
sql_delete_node: STRING = "UPDATE nodes SET changed=:changed, status =:status WHERE nid=:nid" sql_delete_node: STRING = "UPDATE nodes SET changed=:changed, status =:status WHERE nid=:nid"
-- Soft deletion with free metadata. -- Soft deletion with free metadata.
sql_trash_node: STRING = "DELETE FROM NODES WHERE nid=:nid"
-- Physical deletion with free metadata.
sql_revert_node: STRING = "UPDATE nodes SET changed=:changed, status =:status WHERE nid=:nid"
-- Revert node to {CMS_NODE_API}.not_publised.
-- 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, revision = revision + 1 WHERE nid=:nid;" -- sql_update_node_title: STRING ="UPDATE nodes SET title=:title, changed=:changed, revision = revision + 1 WHERE nid=:nid;"

View File

@@ -40,6 +40,10 @@ feature -- Initialization
--| Node --| Node
-- FIXME: move that initialization to node module -- FIXME: move that initialization to node module
-- TODO: should we move the initialization to an
--! external configuration file?
--! at the moment we only have 1 admin to the whole site.
--! is that ok?
l_anonymous_role.add_permission ("view any page") l_anonymous_role.add_permission ("view any page")
a_storage.save_user_role (l_anonymous_role) a_storage.save_user_role (l_anonymous_role)
@@ -47,9 +51,11 @@ feature -- Initialization
l_authenticated_role.add_permission ("view any page") l_authenticated_role.add_permission ("view any page")
l_authenticated_role.add_permission ("edit any page") l_authenticated_role.add_permission ("edit any page")
l_authenticated_role.add_permission ("delete page") l_authenticated_role.add_permission ("delete page")
l_authenticated_role.add_permission ("trash page")
l_authenticated_role.add_permission ("view own page") l_authenticated_role.add_permission ("view own page")
l_authenticated_role.add_permission ("edit own page") l_authenticated_role.add_permission ("edit own page")
l_authenticated_role.add_permission ("delete own page") l_authenticated_role.add_permission ("delete own page")
l_authenticated_role.add_permission ("trash own page")
a_storage.save_user_role (l_authenticated_role) a_storage.save_user_role (l_authenticated_role)