note
	description: "[
			API to manage CMS Nodes
		]"
	date: "$Date: 2015-02-13 13:08:13 +0100 (ven., 13 févr. 2015) $"
	revision: "$Revision: 96616 $"

class
	CMS_NODE_API

inherit
	CMS_MODULE_API
		redefine
			initialize
		end

	REFACTORING_HELPER

create {CMS_NODE_MODULE}
	make_with_storage

feature {NONE} -- Initialization

	make_with_storage (a_api: CMS_API; a_node_storage: CMS_NODE_STORAGE_I)
		do
			node_storage := a_node_storage
			make (a_api)
--			error_handler.add_synchronization (a_node_storage.error_handler)
		end

	initialize
			-- <Precursor>
		do
			Precursor
			initialize_node_types
		end

	initialize_node_types
			-- Initialize content type system.
		local
			ct: CMS_PAGE_NODE_TYPE
		do
				-- Initialize node content types.			
			create ct
				--| For now, add all available formats to content type `ct'.
			across
				cms_api.formats as ic
			loop
				ct.extend_format (ic.item)
			end
			add_node_type (ct)
			add_node_type_webform_manager (create {CMS_PAGE_NODE_TYPE_WEBFORM_MANAGER}.make (ct, Current))
		end

feature {CMS_MODULE} -- Access nodes storage.

	node_storage: CMS_NODE_STORAGE_I

feature -- Content type

	add_node_type (a_type: CMS_NODE_TYPE [CMS_NODE])
			-- Register node content type `a_type'.
		do
			cms_api.add_content_type (a_type)
		end

	node_types: ARRAYED_LIST [attached like node_type]
			-- Node content types.
		do
			create Result.make (cms_api.content_types.count)
			across
				cms_api.content_types as ic
			loop
				if attached {like node_type} ic.item as l_node_type then
					Result.extend (l_node_type)
				end
			end
		end

	node_type (a_name: READABLE_STRING_GENERAL): detachable CMS_NODE_TYPE [CMS_NODE]
			-- Content type named `a_named' if any.
		do
			across
				cms_api.content_types as ic
			until
				Result /= Void
			loop
				if
					attached {like node_type} ic.item as l_node_type and then
					a_name.is_case_insensitive_equal (l_node_type.name)
				then
					Result := l_node_type
				end
			end
		end

	node_type_for (a_node: CMS_NODE): like node_type
			-- Content type for node `a_node' if any.
		local
			l_type_name: READABLE_STRING_8
		do
			l_type_name := a_node.content_type
			Result := node_type (l_type_name)
		end

feature -- Content type webform

	content_type_webform_managers: ARRAYED_LIST [CMS_CONTENT_TYPE_WEBFORM_MANAGER [CMS_CONTENT]]
			-- Available content types
		do
			Result := cms_api.content_type_webform_managers
		end

	add_node_type_webform_manager (a_manager: CMS_NODE_TYPE_WEBFORM_MANAGER [CMS_NODE])
			-- Register webform manager `a_manager'.
		do
			cms_api.add_content_type_webform_manager (a_manager)
		end

	node_type_webform_manager (a_node_type: CMS_CONTENT_TYPE): detachable CMS_NODE_TYPE_WEBFORM_MANAGER_I [CMS_NODE]
			-- Web form manager for node type `a_node_type' if any.
		local
			l_type_name: READABLE_STRING_GENERAL
		do
			l_type_name := a_node_type.name
			across
				content_type_webform_managers as ic
			until
				Result /= Void
			loop
				if
					attached {like node_type_webform_manager} ic.item as l_manager and then
					l_type_name.is_case_insensitive_equal (l_manager.name)
				then
					Result := l_manager
				end
			end
		end

feature -- URL

	new_content_path (ct: detachable CMS_CONTENT_TYPE): STRING
			-- URI path for new content of type `ct'
			-- or URI of path for selection of new content possibilities if ct is Void.
		do
			if ct /= Void then
				Result := "node/add/" + ct.name
			else
				Result := "node/"
			end
		end

	node_link (a_node: CMS_NODE): CMS_LOCAL_LINK
			-- CMS link for node `a_node'.
		require
			a_node.has_id
		do
			create Result.make (a_node.title, cms_api.location_alias (node_path (a_node)))
		end

	node_path (a_node: CMS_NODE): STRING
			-- URI path for node `a_node'.
			-- using the /node/{nid} url.
		require
			a_node.has_id
		do
			Result := "node/" + a_node.id.out
		end

	nodes_path: STRING
			-- URI path for list of nodes.
		do
			Result := "nodes"
		end

feature -- Access: Node

	nodes_count: NATURAL_64
		do
			Result := node_storage.nodes_count
		end

	nodes: LIST [CMS_NODE]
			-- List of nodes.
		do
			Result := node_storage.nodes
		end

	node_revisions (a_node: CMS_NODE): LIST [CMS_NODE]
			-- List of revisions for node `a_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.
		do
			Result := node_storage.trashed_nodes (a_user)
		end

	recent_nodes (params: CMS_DATA_QUERY_PARAMETERS): ITERABLE [CMS_NODE]
			-- List of most recent nodes according to `params.offset' and `params.size'.
		do
			Result := node_storage.recent_nodes (params.offset.to_integer_32, params.size.to_integer_32)
		end

	recent_node_changes_before (params: CMS_DATA_QUERY_PARAMETERS; a_date: DATE_TIME): ITERABLE [CMS_NODE]
			-- List of recent changes, before `a_date', according to `params' settings.
		do
			Result := node_storage.recent_node_changes_before (params.offset.to_integer_32, params.size.to_integer_32, a_date)
		end

	node (a_id: INTEGER_64): detachable CMS_NODE
			-- Node by ID.
		do
			debug ("refactor_fixme")
				fixme ("Check preconditions")
			end
			Result := safe_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 := safe_full_node (node_storage.node_by_id_and_revision (a_node_id, a_revision_id))
		end

	safe_full_node (a_node: detachable CMS_NODE): detachable CMS_NODE
		do
			if a_node /= Void then
				Result := full_node (a_node)
			end
		end

	full_node (a_node: CMS_NODE): CMS_NODE
			-- If `a_node' is partial, return the full node from `a_node',
			-- otherwise return directly `a_node'.
		require
			a_node_set: a_node /= Void
		do
			if attached {CMS_PARTIAL_NODE} a_node as l_partial_node then
				if attached node_type_for (l_partial_node) as ct then
					Result := ct.new_node (l_partial_node)
					node_storage.fill_node (Result)
				else
					Result := l_partial_node
				end
					-- Update link with aliasing.
				if Result /= Void and then Result.has_id then
					Result.set_link (node_link (Result))
				end
			else
				Result := a_node
				if Result.has_id and Result.link = Void then
					Result.set_link (node_link (Result))
				end
			end
			check has_link: Result.has_id implies attached Result.link as lnk and then lnk.location.same_string (node_link (Result).location) 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

	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) as l_author then
				Result := u.same_as (l_author)
			end
		end

	nodes_of_type (a_node_type: CMS_CONTENT_TYPE): LIST [CMS_NODE]
			-- List of nodes of type `a_node_type'.
		do
			Result := node_storage.nodes_of_type (a_node_type)
		ensure
			expected_type: across Result as ic all ic.item.content_type.same_string (a_node_type.name) end
		end

feature -- Access: page/book outline

	children (a_node: CMS_NODE): detachable LIST [CMS_NODE]
			-- Children of node `a_node'.
			-- note: this is the partial version of the nodes.
		do
			Result := node_storage.children (a_node)
		end

	available_parents_for_node (a_node: CMS_NODE): LIST [CMS_NODE]
			-- Potential parent nodes for node `a_node'.
			-- Ensure no cycle exists.
		do
			create {ARRAYED_LIST [CMS_NODE]} Result.make (0)
			across node_storage.available_parents_for_node (a_node) as ic loop
				check distinct: not a_node.same_node (ic.item) end
				if not is_node_a_parent_of (a_node, ic.item) then
					Result.force (ic.item)
				end
			end
		ensure
			no_cycle: across Result as c all not is_node_a_parent_of (a_node, c.item) end
		end

	is_node_a_parent_of (a_node: CMS_NODE; a_child: CMS_NODE): BOOLEAN
			-- Is `a_node' a direct or indirect parent of node `a_child'?
		require
			distinct_nodes: not a_node.same_node (a_child)
		do
			if
				attached {CMS_PAGE} full_node (a_child) as l_child_page and then
				attached l_child_page.parent as l_parent
			then
				if l_parent.same_node (a_node) then
					Result := True
				else
					Result := is_node_a_parent_of (a_node, l_parent)
				end
			end
		end

feature -- Permission Scope: Node

	has_permission_for_action_on_node (a_action: READABLE_STRING_8; a_node: CMS_NODE; a_user: detachable CMS_USER; ): BOOLEAN
			-- Has permission to execute action `a_action' on node `a_node', by eventual user `a_user'?
		local
			l_type_name: READABLE_STRING_8
		do
			l_type_name := a_node.content_type
			Result := cms_api.user_has_permission (a_user, a_action + " any " + l_type_name)
			if not Result and a_user /= Void then
				if is_author_of_node (a_user, a_node) then
					Result := cms_api.user_has_permission (a_user, a_action + " own " + l_type_name)
				end
			end
		end

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)
			-- Add a new node `a_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'.
			--! remove the node from the storage.
		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'.
			-- Soft delete
		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
			-- The node is not published.

	Published: INTEGER = 1
			-- The node is published.

	Trashed: INTEGER = -1
			-- The node is trashed (soft delete), ready to be deleted/destroyed from storage.

end
