diff --git a/modules/node/cms_node_api.e b/modules/node/cms_node_api.e index 8f0a8d9..9ae9d85 100644 --- a/modules/node/cms_node_api.e +++ b/modules/node/cms_node_api.e @@ -212,6 +212,14 @@ feature -- Access: Node Result := node_storage.nodes end + + trashed_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.trashed_nodes (a_user.id) + end + recent_nodes (a_offset, a_rows: INTEGER): LIST [CMS_NODE] -- List of the `a_rows' most recent nodes starting from `a_offset'. do @@ -316,6 +324,21 @@ feature -- Change: Node node_storage.update_node (a_node) 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 + + + restore_node (a_node: CMS_NODE) + -- Restore node `a_node'. + -- From {CMS_NODE_API}.trashed to {CMS_NODE_API}.not_published. + do + node_storage.restore_node (a_node) + end + feature -- Node status diff --git a/modules/node/handler/cms_node_type_webform_manager.e b/modules/node/handler/cms_node_type_webform_manager.e index f571c26..fd90c12 100644 --- a/modules/node/handler/cms_node_type_webform_manager.e +++ b/modules/node/handler/cms_node_type_webform_manager.e @@ -182,11 +182,29 @@ feature -- Output a_response.add_variable (a_node, "node") create lnk.make (a_response.translation ("View", Void), a_response.node_local_link (a_node).location) lnk.set_weight (1) + a_response.add_to_primary_tabs (lnk) - 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) + 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 + -- 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 create s.make_empty s.append ("
") diff --git a/modules/node/handler/node_form_response.e b/modules/node/handler/node_form_response.e index c01330a..cb20a57 100644 --- a/modules/node/handler/node_form_response.e +++ b/modules/node/handler/node_form_response.e @@ -48,7 +48,11 @@ feature -- Execution attached node_api.node (nid) as l_node then if attached node_api.node_type_for (l_node) as l_type then - if node_api.has_permission_for_action_on_node ("edit", l_node, current_user (request)) then + fixme ("refactor: process_edit, process_create porcess edit") + if + request.path_info.ends_with_general ("/edit") and then + node_api.has_permission_for_action_on_node ("edit", l_node, current_user (request)) + then f := new_edit_form (l_node, url (request.path_info, Void), "edit-" + l_type.name, l_type) invoke_form_alter (f, fd) if request.is_post_request_method then @@ -60,6 +64,7 @@ feature -- Execution 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 (translation ("Edit", Void), "/node/" + l_node.id.out + "/edit"), primary_tabs) + add_to_menu (create {CMS_LOCAL_LINK}.make ("Delete", "/node/" + l_node.id.out + "/delete"), primary_tabs) end if attached redirection as l_location then @@ -70,6 +75,53 @@ feature -- Execution set_title (formatted_string (translation ("Edit $1 #$2", Void), [l_type.title, l_node.id])) f.append_to_html (wsf_theme, b) end + elseif + request.path_info.ends_with_general ("/delete") and then + node_api.has_permission_for_action_on_node ("delete", l_node, current_user (request)) + then + f := new_delete_form (l_node, url (request.path_info, Void), "delete-" + 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 (translation ("Edit", Void), "/node/" + l_node.id.out + "/edit"), primary_tabs) + add_to_menu (create {CMS_LOCAL_LINK}.make ("Delete", "/node/" + l_node.id.out + "/delete"), 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) + " deleted") + else + set_title (formatted_string (translation ("Delete $1 #$2", Void), [l_type.title, l_node.id])) + f.append_to_html (wsf_theme, b) + 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) + " trashed") + 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 b.append ("

") b.append (translation ("Access denied", Void)) @@ -243,10 +295,25 @@ feature -- Form ts.set_default_value ("Preview") f.extend (ts) + Result := f + end + + + new_delete_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 ("
") + f.extend_html_text ("Are you sure you want to delete?") + + -- TODO check if we need to check for has_permissions!! if a_node /= Void and then - a_node.id > 0 and then - has_permission ("delete " + a_name) + a_node.id > 0 then create ts.make ("op") ts.set_default_value ("Delete") @@ -254,11 +321,54 @@ feature -- Form ts.set_default_value (translation ("Delete")) ]") f.extend (ts) + fixme ("wsf_html: add support for HTML5 input attributes!!! ") + f.extend_html_text("" ) end Result := f 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 ("
") + f.extend_html_text ("Are you sure you want to trash the current node?") + 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 ("
") + f.extend_html_text ("Do you want to restore the current node?") + if + a_node /= Void and then + a_node.id > 0 + then + create ts.make ("op") + ts.set_default_value ("Restore") + fixme ("[ + ts.set_default_value (translation ("Restore")) + ]") + 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) -- Fill the web form `a_form' with data from `a_node' if set, -- and apply this to content type `a_content_type'. diff --git a/modules/node/handler/node_handler.e b/modules/node/handler/node_handler.e index de50f39..88fb687 100644 --- a/modules/node/handler/node_handler.e +++ b/modules/node/handler/node_handler.e @@ -89,6 +89,14 @@ feature -- HTTP Methods check valid_url: req.path_info.starts_with_general ("/node/") end create edit_response.make (req, res, api, node_api) edit_response.execute + elseif req.path_info.ends_with_general ("/delete") then + check valid_url: req.path_info.starts_with_general ("/node/") end + create edit_response.make (req, res, api, node_api) + 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 -- Display existing node l_nid := node_id_path_parameter (req) @@ -116,14 +124,26 @@ feature -- HTTP Methods do fixme ("Refactor code: extract methods: edit_node and add_node") if req.path_info.ends_with_general ("/edit") then + create edit_response.make (req, res, api, node_api) + edit_response.execute + elseif req.path_info.ends_with_general ("/delete") then if attached {WSF_STRING} req.form_parameter ("op") as l_op and then l_op.value.same_string ("Delete") then do_delete (req, res) - else - create edit_response.make (req, res, api, node_api) - edit_response.execute + 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 ("Restore") + then + do_restore (req, res) end elseif req.path_info.starts_with_general ("/node/add/") then create edit_response.make (req, res, api, node_api) @@ -174,6 +194,62 @@ feature -- HTTP Methods send_not_implemented ("REST API not yet implemented", req, res) end +feature {NONE} -- Trash:Restore + + 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_restore (req: WSF_REQUEST; res: WSF_RESPONSE) + -- Restore 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.restore_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 do_error (req: WSF_REQUEST; res: WSF_RESPONSE; a_id: detachable WSF_STRING) diff --git a/modules/node/handler/trash_handler.e b/modules/node/handler/trash_handler.e new file mode 100644 index 0000000..5f12770 --- /dev/null +++ b/modules/node/handler/trash_handler.e @@ -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) + -- + 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.trashed_nodes (l_user), "nodes") + + -- NOTE: for development purposes we have the following hardcode output. + create s.make_from_string ("

Nodes:

") + if attached node_api.trashed_nodes (l_user) as lst then + s.append ("
    %N") + across + lst as ic + loop + n := ic.item + lnk := node_api.node_link (n) + s.append ("
  • ") + s.append (l_page.link (lnk.title, lnk.location, Void)) + s.append ("
  • %N") + end + s.append ("
%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
", Void), "highlighted") + l_page.execute + end + end + +end diff --git a/modules/node/node_module.e b/modules/node/node_module.e index 8df4eab..4431205 100644 --- a/modules/node/node_module.e +++ b/modules/node/node_module.e @@ -127,6 +127,7 @@ feature -- Access: router l_node_handler: NODE_HANDLER l_nodes_handler: NODES_HANDLER l_uri_mapping: WSF_URI_MAPPING + l_trash_handler: TRASH_HANDLER do -- 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) @@ -135,6 +136,8 @@ 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/{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}/trash", l_node_handler, a_router.methods_get_post) 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) @@ -143,6 +146,13 @@ feature -- Access: router create l_nodes_handler.make (a_api, a_node_api) 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) + + --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 feature -- Hooks @@ -176,6 +186,9 @@ feature -- Hooks create lnk.make ("List of nodes", a_response.url ("/nodes", Void)) a_menu_system.primary_menu.extend (lnk) + create lnk.make ("Trash nodes", a_response.url ("/trash", Void)) + a_menu_system.primary_menu.extend (lnk) + create lnk.make ("Create ..", a_response.url ("/node/", Void)) a_menu_system.primary_menu.extend (lnk) end diff --git a/modules/node/persistence/cms_node_storage_i.e b/modules/node/persistence/cms_node_storage_i.e index 6090d95..5bbf8ca 100644 --- a/modules/node/persistence/cms_node_storage_i.e +++ b/modules/node/persistence/cms_node_storage_i.e @@ -70,6 +70,11 @@ feature -- Access deferred end + trashed_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] -- List of recent `a_count' nodes with an offset of `lower'. deferred @@ -135,6 +140,36 @@ feature -- Change: Node deferred 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 + + restore_node (a_node: CMS_NODE) + -- Restore `a_node'. + do + if a_node.has_id then + restore_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 + + restore_node_by_id (a_id: INTEGER_64) + -- Restore 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 to `a_title', node identified by id `a_node_id'. -- -- The user `a_user_id' is an existing or new collaborator. diff --git a/modules/node/persistence/cms_node_storage_null.e b/modules/node/persistence/cms_node_storage_null.e index 87559af..b5a9406 100644 --- a/modules/node/persistence/cms_node_storage_null.e +++ b/modules/node/persistence/cms_node_storage_null.e @@ -41,6 +41,12 @@ feature -- Access: node create {ARRAYED_LIST [CMS_NODE]} Result.make (0) end + trashed_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] -- List of the `a_count' most recent nodes, starting from `a_lower'. do @@ -80,6 +86,16 @@ feature -- Node do end + trash_node_by_id (a_id: INTEGER_64) + -- + do + end + + restore_node_by_id (a_id: INTEGER_64) + -- + do + end + -- update_node_title (a_user_id: like {CMS_NODE}.id; a_node_id: like {CMS_NODE}.id; a_title: READABLE_STRING_32) -- -- -- do diff --git a/modules/node/persistence/cms_node_storage_sql.e b/modules/node/persistence/cms_node_storage_sql.e index 06ecadc..b409851 100644 --- a/modules/node/persistence/cms_node_storage_sql.e +++ b/modules/node/persistence/cms_node_storage_sql.e @@ -59,6 +59,36 @@ feature -- Access -- end end + trashed_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] -- List of recent `a_count' nodes with an offset of `lower'. local @@ -147,7 +177,7 @@ feature -- Change: Node l_time: DATE_TIME do create l_time.make_now_utc - write_information_log (generator + ".delete_node") + write_information_log (generator + ".delete_node {" + a_id.out + "}") error_handler.reset create l_parameters.make (1) @@ -157,6 +187,40 @@ feature -- Change: Node sql_change (sql_delete_node, l_parameters) end + + trash_node_by_id (a_id: INTEGER_64) + -- + 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 + + restore_node_by_id (a_id: INTEGER_64) + -- + local + l_parameters: STRING_TABLE [ANY] + l_time: DATE_TIME + do + create l_time.make_now_utc + write_information_log (generator + ".restore_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_restore_node, l_parameters) + end + + feature {NONE} -- Implementation store_node (a_node: CMS_NODE) @@ -228,6 +292,14 @@ feature {NONE} -- Queries -- SQL Query to retrieve all nodes. --| 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_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" -- Soft deletion with free metadata. + sql_trash_node: STRING = "DELETE FROM nodes WHERE nid=:nid" + -- Physical deletion with free metadata. + + 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;" diff --git a/src/persistence/sql/cms_storage_sql_builder.e b/src/persistence/sql/cms_storage_sql_builder.e index 6000207..a1bec3b 100644 --- a/src/persistence/sql/cms_storage_sql_builder.e +++ b/src/persistence/sql/cms_storage_sql_builder.e @@ -40,6 +40,10 @@ feature -- Initialization --| Node -- 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") 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 ("edit any 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 ("edit 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)