diff --git a/draft/application/cms/.gitignore b/draft/application/cms/.gitignore
new file mode 100644
index 00000000..f998b949
--- /dev/null
+++ b/draft/application/cms/.gitignore
@@ -0,0 +1 @@
+_storage_
diff --git a/draft/application/cms/README.md b/draft/application/cms/README.md
new file mode 100644
index 00000000..79620c23
--- /dev/null
+++ b/draft/application/cms/README.md
@@ -0,0 +1,2 @@
+Experimental a simple CMS using EWF.
+Inspired by Drupal
diff --git a/draft/application/cms/cms-safe.ecf b/draft/application/cms/cms-safe.ecf
new file mode 100644
index 00000000..596289b0
--- /dev/null
+++ b/draft/application/cms/cms-safe.ecf
@@ -0,0 +1,30 @@
+
+
+
+
+
+ /EIFGENs$
+ /CVS$
+ /.svn$
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/draft/application/cms/cms.ini b/draft/application/cms/cms.ini
new file mode 100644
index 00000000..f1803e45
--- /dev/null
+++ b/draft/application/cms/cms.ini
@@ -0,0 +1,3 @@
+site.name=EWF Web CMS
+#site.base_url=/demo
+site.email=your@email.com
diff --git a/draft/application/cms/ewf.ini b/draft/application/cms/ewf.ini
new file mode 100644
index 00000000..6a794d69
--- /dev/null
+++ b/draft/application/cms/ewf.ini
@@ -0,0 +1,4 @@
+# For nino connector, use port 9999
+port=9090
+
+#verbose=true
diff --git a/draft/application/cms/favicon.ico b/draft/application/cms/favicon.ico
new file mode 100644
index 00000000..343067f9
Binary files /dev/null and b/draft/application/cms/favicon.ico differ
diff --git a/draft/application/cms/files/favicon.ico b/draft/application/cms/files/favicon.ico
new file mode 100644
index 00000000..343067f9
Binary files /dev/null and b/draft/application/cms/files/favicon.ico differ
diff --git a/draft/application/cms/src/core/cms_configuration.e b/draft/application/cms/src/core/cms_configuration.e
new file mode 100644
index 00000000..e0f56325
--- /dev/null
+++ b/draft/application/cms/src/core/cms_configuration.e
@@ -0,0 +1,146 @@
+note
+ description: "Summary description for {CMS_CONFIGURATION}."
+ author: ""
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ CMS_CONFIGURATION
+
+create
+ make,
+ make_from_file
+
+feature {NONE} -- Initialization
+
+ make
+ do
+ create options.make (10)
+ end
+
+ make_from_file (a_filename: READABLE_STRING_32)
+ -- Initialize `Current'.
+ do
+ make
+ import (a_filename)
+ end
+
+feature -- Access
+
+ option (a_name: READABLE_STRING_GENERAL): detachable ANY
+ do
+ Result := options.item (a_name.as_string_8)
+ end
+
+ options: HASH_TABLE [STRING, STRING]
+
+feature -- Element change
+
+ set_option (a_name: READABLE_STRING_GENERAL; a_value: STRING)
+ do
+ options.force (a_value, a_name.as_string_8)
+ end
+
+feature -- Access
+
+ root_location: STRING
+ do
+ if attached options.item ("root.location") as s then
+ Result := s
+ else
+ Result := execution_environment.current_working_directory
+ end
+ if not Result.ends_with ("/") then
+ Result.append_character ('/')
+ end
+ end
+
+ themes_location: STRING
+ do
+ if attached options.item ("themes.location") as s then
+ Result := s
+ else
+ Result := root_location + "theme/default/"
+ end
+ end
+
+ site_name (dft: like site_name): READABLE_STRING_8
+ do
+ if attached options.item ("site.name") as s then
+ Result := s
+ else
+ Result := dft
+ end
+ end
+
+ site_base_url (dft: like site_base_url): detachable READABLE_STRING_8
+ do
+ if attached options.item ("site.base_url") as s then
+ Result := s
+ else
+ Result := dft
+ end
+ if Result /= Void then
+ if Result.is_empty then
+ Result := Void
+ elseif not Result.starts_with ("/") then
+ Result := "/" + Result
+ if Result.ends_with ("/") then
+ Result := Result.substring (1, Result.count - 1)
+ end
+ end
+ end
+ end
+
+ site_email (dft: like site_email): READABLE_STRING_8
+ do
+ if attached options.item ("site.email") as s then
+ Result := s
+ else
+ Result := dft
+ end
+ end
+
+feature {NONE} -- Implementation
+
+ execution_environment: EXECUTION_ENVIRONMENT
+ once
+ create Result
+ end
+
+ import (a_filename: READABLE_STRING_32)
+ -- Import ini file content
+ local
+ f: PLAIN_TEXT_FILE
+ l,v: STRING_8
+ p: INTEGER
+ do
+ --FIXME: handle unicode filename here.
+ create f.make (a_filename)
+ if f.exists and f.is_readable then
+ f.open_read
+ from
+ f.read_line
+ until
+ f.exhausted
+ loop
+ l := f.last_string
+ l.left_adjust
+ if not l.is_empty and then l[1] /= '#' then
+ p := l.index_of ('=', 1)
+ if p > 1 then
+ v := l.substring (p + 1, l.count)
+ l.keep_head (p - 1)
+ v.left_adjust
+ v.right_adjust
+ l.right_adjust
+ set_option (l.as_lower, v)
+ end
+ end
+ f.read_line
+ end
+ f.close
+ end
+ end
+
+end
diff --git a/draft/application/cms/src/core/cms_execution.e b/draft/application/cms/src/core/cms_execution.e
new file mode 100644
index 00000000..94d420d5
--- /dev/null
+++ b/draft/application/cms/src/core/cms_execution.e
@@ -0,0 +1,648 @@
+note
+ description: "[
+ This is the execution of the cms handler request
+ It builds the content to get process to render the output
+ ]"
+
+deferred class
+ CMS_EXECUTION
+
+inherit
+ CMS_COMMON_API
+
+feature {NONE} -- Initialization
+
+ make (req: WSF_REQUEST; res: WSF_RESPONSE; a_service: like service)
+ do
+ status_code := {HTTP_STATUS_CODE}.ok
+ service := a_service
+ request := req
+ response := res
+ create header.make
+ initialize
+ end
+
+ initialize
+ do
+ is_front := request.path_info.same_string (service.front_path)
+ has_js := True -- by default it is true, check cookie to see if this is not supported.
+ if attached request.cookie ("has_js") as c_has_js then
+ has_js := c_has_js.same_string ("0")
+ end
+ get_theme
+ controller := service.session_controller (request)
+ create menu_system.make
+ create blocks.make (3)
+
+ if attached {like message} session_item (pending_messages_session_item_name) as m then
+ message := m
+ end
+ remove_session_item (pending_messages_session_item_name)
+ end
+
+feature -- Access
+
+ service: CMS_SERVICE
+ request: WSF_REQUEST
+
+feature {CMS_SESSION_CONTROLER} -- Access: restricted
+
+ response: WSF_RESPONSE
+
+ controller: CMS_SESSION_CONTROLER
+
+ base_url: detachable READABLE_STRING_8
+ do
+ Result := service.base_url
+ end
+
+ pending_messages_session_item_name: STRING = "cms.pending_messages"
+ -- Session item name to get the pending messages.
+
+feature -- Access: CMS
+
+ site_name: STRING_32
+ do
+ Result := service.site_name
+ end
+
+ front_page_url: READABLE_STRING_8
+ do
+ Result := url ("/", Void)
+ end
+
+feature -- Permission
+
+ has_permissions (lst: detachable ITERABLE [READABLE_STRING_8]): BOOLEAN
+ do
+ if lst = Void then
+ Result := True
+ else
+ Result := across lst as c all has_permission (c.item) end
+ end
+ end
+
+ has_permission (s: detachable READABLE_STRING_8): BOOLEAN
+ -- Anonymous or Current `user' has permission for `s'
+ --| `s' could be "create page",
+ do
+ if s = Void then
+ Result := True
+ else
+ if s.same_string ("authenticated") then
+ Result := authenticated
+ else
+ if s.has_substring ("admin") or s.has_substring ("users") then
+ Result := attached user as u and then u.is_admin
+ else
+ Result := True
+ end
+ end
+ end
+ end
+
+feature -- Status
+
+ is_front: BOOLEAN
+
+ has_js: BOOLEAN
+ -- Client has javascript enabled?
+ -- FIXME: not yet implemented
+
+ is_mobile: BOOLEAN
+ -- Is Client on mobile device?
+ -- FIXME: not yet implemented
+
+feature -- Theme
+
+ theme: CMS_THEME
+
+ get_theme
+ do
+ create {DEFAULT_CMS_THEME} theme.make (service)
+ end
+
+feature -- Access: User
+
+ authenticated: BOOLEAN
+ do
+ Result := user /= Void
+ end
+
+ user: detachable CMS_USER
+ do
+ if attached {CMS_USER} session_item ("user") as u then
+ Result := u
+ end
+ end
+
+ last_user_access_date: detachable DATE_TIME
+ do
+ if attached {DATE_TIME} session_item ("last_access") as dt then
+ Result := dt
+ end
+ end
+
+feature -- Element change: user
+
+ login (u: attached like user; req: WSF_REQUEST)
+ do
+ controller.start_session (req)
+ u.set_last_login_date_now
+ storage.save_user (u)
+ set_user (u)
+ init_last_user_access_date
+ log ("user", "user %"" + u.name + "%" signed in.", 0, user_local_link (u))
+ end
+
+ logout (req: WSF_REQUEST)
+ require
+ authenticated
+ do
+ if attached user as u then
+ log ("user", "user %"" + u.name + "%" signed out.", 0, user_local_link (u))
+ end
+ set_user (Void)
+ controller.start_session (req)
+ end
+
+feature -- Logging
+
+ log (a_category: READABLE_STRING_8; a_message: READABLE_STRING_8; a_level: INTEGER; a_link: detachable CMS_LINK)
+ local
+ l_log: CMS_LOG
+ do
+ create l_log.make (a_category, a_message, a_level, Void)
+ if a_link /= Void then
+ l_log.set_link (a_link)
+ end
+ l_log.set_info (request.http_user_agent)
+ service.storage.save_log (l_log)
+ end
+
+feature -- Menu
+
+ menu_system: CMS_MENU_SYSTEM
+
+ main_menu: CMS_MENU
+ do
+ Result := menu_system.main_menu
+ end
+
+ management_menu: CMS_MENU
+ do
+ Result := menu_system.management_menu
+ end
+
+ navigation_menu: CMS_MENU
+ do
+ Result := menu_system.navigation_menu
+ end
+
+ user_menu: CMS_MENU
+ do
+ Result := menu_system.user_menu
+ end
+
+ primary_tabs: CMS_MENU
+ do
+ Result := menu_system.primary_tabs
+ end
+
+feature -- Menu: change
+
+ add_to_main_menu (lnk: CMS_LINK)
+ do
+ if attached {CMS_LOCAL_LINK} lnk as l_local then
+ l_local.get_is_active (request)
+ end
+ main_menu.extend (lnk)
+ end
+
+ add_to_menu (lnk: CMS_LINK; m: CMS_MENU)
+ do
+ if attached {CMS_LOCAL_LINK} lnk as l_local then
+ l_local.get_is_active (request)
+ end
+ m.extend (lnk)
+ end
+
+feature -- Message
+
+ add_message (a_msg: READABLE_STRING_8; a_category: detachable READABLE_STRING_8)
+ local
+ m: like message
+ do
+ m := message
+ if m = Void then
+ create m.make (a_msg.count + 9)
+ message := m
+ end
+ if a_category /= Void then
+ m.append ("
")
+ else
+ m.append ("
")
+ end
+ m.append (a_msg + "
")
+ end
+
+ add_notice_message (a_msg: READABLE_STRING_8)
+ do
+ add_message (a_msg, "notice")
+ end
+
+ add_warning_message (a_msg: READABLE_STRING_8)
+ do
+ add_message (a_msg, "warning")
+ end
+
+ add_error_message (a_msg: READABLE_STRING_8)
+ do
+ add_message (a_msg, "error")
+ end
+
+ add_success_message (a_msg: READABLE_STRING_8)
+ do
+ add_message (a_msg, "success")
+ end
+
+ report_form_errors (fd: CMS_FORM_DATA)
+ require
+ has_error: not fd.is_valid
+ do
+ if attached fd.errors as errs then
+ across
+ errs as err
+ loop
+ if attached err.item as e then
+ if attached e.field as l_field then
+ if attached e.message as e_msg then
+ add_error_message (e_msg) --"Field [" + l_field.name + "] is invalid. " + e_msg)
+ else
+ add_error_message ("Field [" + l_field.name + "] is invalid.")
+ end
+ elseif attached e.message as e_msg then
+ add_error_message (e_msg)
+ end
+ end
+ end
+ end
+ end
+
+ message: detachable STRING_8
+
+feature -- Blocks
+
+ formats: CMS_FORMATS
+ once
+ create Result
+ end
+
+ blocks: ARRAYED_LIST [TUPLE [block: CMS_BLOCK; name: READABLE_STRING_8; region: READABLE_STRING_8]]
+
+ add_block (b: CMS_BLOCK; a_region: detachable READABLE_STRING_8)
+ do
+ if a_region /= Void then
+ blocks.extend ([b, b.name, a_region])
+ elseif attached block_region (b) as l_region then
+ blocks.extend ([b, b.name, l_region])
+ end
+ end
+
+ block_region (b: CMS_BLOCK): detachable READABLE_STRING_8
+ local
+ l_name: READABLE_STRING_8
+ do
+ l_name := b.name
+ if l_name.starts_with ("footer") then
+ Result := "footer"
+ elseif l_name.starts_with ("management") then
+ Result := "first_sidebar"
+ elseif l_name.starts_with ("navigation") then
+ Result := "first_sidebar"
+ elseif l_name.starts_with ("user") then
+ Result := "first_sidebar"
+ else
+ Result := "first_sidebar"
+ end
+ -- FIXME: let the user choose ...
+ end
+
+ get_blocks
+ local
+ b: CMS_CONTENT_BLOCK
+ s: STRING_8
+ m: CMS_MENU
+ do
+ m := management_menu
+ if not m.is_empty then
+ add_block (create {CMS_MENU_BLOCK}.make (m), Void)
+ end
+
+ m := navigation_menu
+ if not m.is_empty then
+ add_block (create {CMS_MENU_BLOCK}.make (m), Void)
+ end
+
+ m := user_menu
+ if not m.is_empty then
+ add_block (create {CMS_MENU_BLOCK}.make (m), Void)
+ end
+
+ create s.make_empty
+ s.append ("This site demonstrates a first implementation of CMS using EWF.%N")
+ create b.make ("about", "About", s, formats.plain_text)
+ add_block (b, "second_sidebar")
+
+ create s.make_empty
+ s.append ("Made with EWF")
+ create b.make ("made_with", Void, s, formats.full_html)
+ add_block (b, "footer")
+
+ service.hook_block_view (Current)
+ end
+
+feature -- Access
+
+ status_code: INTEGER
+
+ header: WSF_HEADER
+
+ title: detachable READABLE_STRING_32
+ -- HTML>head>title value
+
+ page_title: detachable READABLE_STRING_32
+ -- Page title
+
+ main_content: detachable STRING_8
+
+ redirection: detachable READABLE_STRING_8
+
+feature -- Generation
+
+ prepare_menu_system (a_menu_system: CMS_MENU_SYSTEM)
+ do
+ across
+ a_menu_system as c
+ loop
+ prepare_menu (c.item)
+ end
+ end
+
+ prepare_menu (a_menu: CMS_MENU)
+ local
+ to_remove: ARRAYED_LIST [CMS_LINK]
+ do
+ create to_remove.make (0)
+ across
+ a_menu as c
+ loop
+ if attached {CMS_LOCAL_LINK} c.item as lm then
+ if not has_permissions (lm.permission_arguments) then
+ to_remove.force (lm)
+ else
+ lm.get_is_active (request)
+ end
+ end
+ end
+ across
+ to_remove as c
+ loop
+ a_menu.remove (c.item)
+ end
+ end
+
+ prepare (page: CMS_HTML_PAGE)
+ local
+ s: STRING_8
+ do
+ add_to_main_menu (create {CMS_LOCAL_LINK}.make ("Home", "/"))
+
+ service.call_menu_alter_hooks (menu_system, Current)
+ prepare_menu_system (menu_system)
+
+ get_blocks
+
+ if attached title as l_title then
+ page.set_title (l_title)
+ else
+ page.set_title ("CMS::" + request.path_info)
+ end
+
+ page.add_to_header_region (top_header_region)
+ page.add_to_header_region (header_region)
+ if attached message as m and then not m.is_empty then
+ page.add_to_content_region ("
" + m + "
")
+ end
+ page.add_to_content_region ("%N")
+ if attached page_title as l_page_title then
+ page.add_to_content_region ("
"+ l_page_title +"
%N")
+ end
+ if attached primary_tabs as tabs_menu and then not tabs_menu.is_empty then
+ page.add_to_content_region (theme.menu_html (tabs_menu, True))
+ end
+ page.add_to_content_region (content_region)
+
+ -- blocks
+ across
+ blocks as c
+ loop
+ if attached c.item as b_info then
+ create s.make_from_string ("
")
+ if attached b_info.block.title as l_title then
+ s.append ("
")
+ page.add_to_region (s, b_info.region)
+ end
+ end
+ end
+
+ logo_location: STRING
+ do
+ Result := url ("/theme/logo.png", Void)
+ end
+
+ top_header_region: STRING_8
+ do
+ Result := "
%N")
+
+ debug ("cms")
+ if attached controller.session as sess then
+ b.append ("
Session#" + sess.uuid + "
%N")
+ end
+ end
+
+ set_main_content (b)
+ end
+
+end
diff --git a/draft/application/cms/src/core/handler/not_found_cms_execution.e b/draft/application/cms/src/core/handler/not_found_cms_execution.e
new file mode 100644
index 00000000..8756855c
--- /dev/null
+++ b/draft/application/cms/src/core/handler/not_found_cms_execution.e
@@ -0,0 +1,34 @@
+note
+ description: "[
+ This class implements the web service
+
+ It inherits from WSF_DEFAULT_SERVICE to get default EWF connector ready
+ And from WSF_URI_TEMPLATE_ROUTED_SERVICE to use the router service
+
+ `initialize' can be redefine to provide custom options if needed.
+ ]"
+
+class
+ NOT_FOUND_CMS_EXECUTION
+
+inherit
+ CMS_EXECUTION
+
+create
+ make
+
+feature -- Execution
+
+ process
+ -- Computed response message.
+ local
+ b: STRING_8
+ do
+ status_code := {HTTP_STATUS_CODE}.not_found
+ create b.make_empty
+ set_title ("Page Not Found")
+ b.append ("The requested page could not be found.%N")
+ set_main_content (b)
+ end
+
+end
diff --git a/draft/application/cms/src/core/handler/theme_cms_execution.e b/draft/application/cms/src/core/handler/theme_cms_execution.e
new file mode 100644
index 00000000..32999ade
--- /dev/null
+++ b/draft/application/cms/src/core/handler/theme_cms_execution.e
@@ -0,0 +1,47 @@
+note
+ description: "[
+ ]"
+
+class
+ THEME_CMS_EXECUTION
+
+inherit
+ CMS_EXECUTION
+
+create
+ make
+
+feature -- Execution
+
+ process
+ -- Computed response message.
+ local
+-- l_url: READABLE_STRING_8
+ b: STRING_8
+ do
+ set_title ("Home")
+ create b.make_empty
+
+ b.append ("
Home
%N")
+ if attached service.storage.recent_nodes (1, 10) as l_nodes then
+ across
+ l_nodes as c
+ loop
+ b.append ("
%N")
+
+ set_main_content (b)
+ end
+
+end
diff --git a/draft/application/cms/src/core/hooks/cms_hook.e b/draft/application/cms/src/core/hooks/cms_hook.e
new file mode 100644
index 00000000..9e6ae0e7
--- /dev/null
+++ b/draft/application/cms/src/core/hooks/cms_hook.e
@@ -0,0 +1,10 @@
+note
+ description: "Summary description for {CMS_HOOK}."
+ author: ""
+ date: "$Date$"
+ revision: "$Revision$"
+
+deferred class
+ CMS_HOOK
+
+end
diff --git a/draft/application/cms/src/core/hooks/cms_hook_block.e b/draft/application/cms/src/core/hooks/cms_hook_block.e
new file mode 100644
index 00000000..4f55b8b6
--- /dev/null
+++ b/draft/application/cms/src/core/hooks/cms_hook_block.e
@@ -0,0 +1,23 @@
+note
+ description: "Summary description for {CMS_HOOK_BLOCK}."
+ author: ""
+ date: "$Date$"
+ revision: "$Revision$"
+
+deferred class
+ CMS_HOOK_BLOCK
+
+inherit
+ CMS_HOOK
+
+feature -- Hook
+
+ block_list: ITERABLE [like {CMS_BLOCK}.name]
+ deferred
+ end
+
+ get_block_view (a_block_id: detachable READABLE_STRING_8; a_execution: CMS_EXECUTION)
+ deferred
+ end
+
+end
diff --git a/draft/application/cms/src/core/hooks/cms_hook_form_alter.e b/draft/application/cms/src/core/hooks/cms_hook_form_alter.e
new file mode 100644
index 00000000..141430f5
--- /dev/null
+++ b/draft/application/cms/src/core/hooks/cms_hook_form_alter.e
@@ -0,0 +1,19 @@
+note
+ description: "Summary description for {CMS_HOOK_FORM_ALTER}."
+ author: ""
+ date: "$Date$"
+ revision: "$Revision$"
+
+deferred class
+ CMS_HOOK_FORM_ALTER
+
+inherit
+ CMS_HOOK
+
+feature -- Hook
+
+ form_alter (a_form: CMS_FORM; a_execution: CMS_EXECUTION)
+ deferred
+ end
+
+end
diff --git a/draft/application/cms/src/core/hooks/cms_hook_menu_alter.e b/draft/application/cms/src/core/hooks/cms_hook_menu_alter.e
new file mode 100644
index 00000000..b3737ab7
--- /dev/null
+++ b/draft/application/cms/src/core/hooks/cms_hook_menu_alter.e
@@ -0,0 +1,19 @@
+note
+ description: "Summary description for {CMS_HOOK_MENU_ALTER}."
+ author: ""
+ date: "$Date$"
+ revision: "$Revision$"
+
+deferred class
+ CMS_HOOK_MENU_ALTER
+
+inherit
+ CMS_HOOK
+
+feature -- Hook
+
+ menu_alter (a_menu_system: CMS_MENU_SYSTEM; a_execution: CMS_EXECUTION)
+ deferred
+ end
+
+end
diff --git a/draft/application/cms/src/core/kernel/api/cms_api_options.e b/draft/application/cms/src/core/kernel/api/cms_api_options.e
new file mode 100644
index 00000000..e33c5164
--- /dev/null
+++ b/draft/application/cms/src/core/kernel/api/cms_api_options.e
@@ -0,0 +1,75 @@
+note
+ description: "Summary description for {CMS_API_OPTIONS}."
+ author: ""
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ CMS_API_OPTIONS
+
+create
+ make,
+ make_from_manifest
+
+convert
+ make_from_manifest ({ ARRAY [TUPLE [key: STRING; value: detachable ANY]],
+ ARRAY [TUPLE [STRING_8, ARRAY [TUPLE [STRING_8, READABLE_STRING_32]]]]
+ })
+
+feature {NONE} -- Initialization
+
+ make (n: INTEGER)
+ do
+ create table.make (n)
+ end
+
+ make_from_manifest (lst: ARRAY [TUPLE [key: STRING; value: detachable ANY]])
+ do
+ make (lst.count)
+ across
+ lst as c
+ loop
+ force (c.item.value, c.item.key)
+ end
+ end
+
+feature -- Access
+
+ item (k: STRING): detachable ANY
+ do
+ Result := table.item (k)
+ end
+
+ force (v: detachable ANY; k: STRING)
+ do
+ table.force (v, k)
+ end
+
+ boolean_item (k: STRING; dft: BOOLEAN): BOOLEAN
+ do
+ if attached {BOOLEAN} item (k) as b then
+ Result := b
+ else
+ Result := dft
+ end
+ end
+
+ string_general_item (k: STRING): detachable READABLE_STRING_GENERAL
+ do
+ if attached {READABLE_STRING_GENERAL} item (k) as s then
+ Result := s
+ end
+ end
+
+ string_item, string_8_item (k: STRING): detachable READABLE_STRING_8
+ do
+ if attached {READABLE_STRING_8} item (k) as s then
+ Result := s
+ end
+ end
+
+ table: HASH_TABLE [detachable ANY, STRING]
+
+invariant
+
+end
diff --git a/draft/application/cms/src/core/kernel/api/cms_common_api.e b/draft/application/cms/src/core/kernel/api/cms_common_api.e
new file mode 100644
index 00000000..03855975
--- /dev/null
+++ b/draft/application/cms/src/core/kernel/api/cms_common_api.e
@@ -0,0 +1,204 @@
+note
+ description: "Summary description for {WSF_CMS_COMMON_API}."
+ author: ""
+ date: "$Date$"
+ revision: "$Revision$"
+
+deferred class
+ CMS_COMMON_API
+
+feature {NONE} -- Access
+
+ service: CMS_SERVICE
+ deferred
+ end
+
+ base_url: detachable READABLE_STRING_8
+ -- Base url if any.
+ deferred
+ end
+
+ based_path (p: STRING): STRING
+ -- Path `p' in the context of the `base_url'
+ do
+ if attached base_url as l_base_url then
+ create Result.make_from_string (l_base_url)
+ Result.append (p)
+ else
+ Result := p
+ end
+ end
+
+feature -- Access
+
+ url_encoded (s: detachable READABLE_STRING_GENERAL): STRING_8
+ local
+ enc: URL_ENCODER
+ do
+ create enc
+ if s /= Void then
+ Result := enc.general_encoded_string (s)
+ else
+ create Result.make_empty
+ end
+ end
+
+ html_encoded (s: detachable READABLE_STRING_GENERAL): STRING_8
+ local
+ enc: HTML_ENCODER
+ do
+ create enc
+ if s /= Void then
+ Result := enc.general_encoded_string (s)
+ else
+ create Result.make_empty
+ end
+ end
+
+ link (a_text: READABLE_STRING_GENERAL; a_path: STRING; opts: detachable CMS_API_OPTIONS): STRING
+ local
+ l_html: BOOLEAN
+ do
+ l_html := True
+ if opts /= Void then
+ l_html := opts.boolean_item ("html", l_html)
+ end
+ Result := ""
+ if l_html then
+ if attached {READABLE_STRING_8} a_text as t then
+ Result.append (t)
+ else
+ Result.append (a_text.to_string_8)
+ end
+ else
+ Result.append (checked_plain (a_text))
+ end
+ Result.append ("")
+ end
+
+ user_link (u: CMS_USER): like link
+ do
+ Result := link (u.name, "/user/" + u.id.out, Void)
+ end
+
+ node_link (n: CMS_NODE): like link
+ do
+ Result := link (html_encoded (n.title), "/node/" + n.id.out, Void)
+ end
+
+ user_url (u: CMS_USER): like url
+ do
+ Result := url ("/user/" + u.id.out, Void)
+ end
+
+ node_url (n: CMS_NODE): like url
+ do
+ Result := url ("/node/" + n.id.out, Void)
+ end
+
+ url (a_path: STRING; opts: detachable CMS_API_OPTIONS): STRING
+ local
+ q,f: detachable STRING_8
+ l_abs: BOOLEAN
+ do
+ l_abs := False
+
+ Result := based_path (a_path)
+ if opts /= Void then
+ l_abs := opts.boolean_item ("absolute", l_abs)
+ if attached opts.item ("query") as l_query then
+ if attached {READABLE_STRING_8} l_query as s_value then
+ q := s_value
+ elseif attached {ITERABLE [TUPLE [key, value: READABLE_STRING_GENERAL]]} l_query as lst then
+ create q.make_empty
+ across
+ lst as c
+ loop
+ if q.is_empty then
+ else
+ q.append_character ('&')
+ end
+ q.append (url_encoded (c.item.key))
+ q.append_character ('=')
+ q.append (url_encoded (c.item.value))
+ end
+ end
+ end
+ if attached opts.string_item ("fragment") as s_frag then
+ f := s_frag
+ end
+ end
+ if q /= Void then
+ Result.append ("?" + q)
+ end
+ if f /= Void then
+ Result.append ("#" + f)
+ end
+ if l_abs then
+ if Result.substring_index ("://", 1) = 0 then
+ Result.prepend (service.site_url)
+ end
+ end
+ end
+
+ checked_url (a_url: STRING): STRING
+ do
+ Result := a_url
+ end
+
+ checked_plain (a_text: READABLE_STRING_GENERAL): STRING_8
+ do
+ Result := html_encoder.general_encoded_string (a_text)
+ end
+
+feature -- Helper
+
+ is_empty (s: detachable READABLE_STRING_GENERAL): BOOLEAN
+ -- Is `s' is Void or empty ?
+ do
+ Result := s = Void or else s.is_empty
+ end
+
+ unix_timestamp (dt: DATE_TIME): INTEGER_64
+ do
+ Result := (create {HTTP_DATE_TIME_UTILITIES}).unix_time_stamp (dt)
+ end
+
+ unix_timestamp_to_date_time (t: INTEGER_64): DATE_TIME
+ do
+ Result := (create {HTTP_DATE_TIME_UTILITIES}).unix_time_stamp_to_date_time (t)
+ end
+
+ string_unix_timestamp_to_date_time (s: READABLE_STRING_8): DATE_TIME
+ do
+ if s.is_integer_64 then
+ Result := (create {HTTP_DATE_TIME_UTILITIES}).unix_time_stamp_to_date_time (s.to_integer_64)
+ else
+ Result := (create {HTTP_DATE_TIME_UTILITIES}).unix_time_stamp_to_date_time (0)
+ end
+ end
+
+feature {NONE} -- Implementation
+
+ options_boolean (opts: HASH_TABLE [detachable ANY, STRING]; k: STRING; dft: BOOLEAN): BOOLEAN
+ do
+ if attached {BOOLEAN} opts.item (k) as h then
+ Result := h
+ else
+ Result := dft
+ end
+ end
+
+ options_string (opts: HASH_TABLE [detachable ANY, STRING]; k: STRING): detachable STRING
+ do
+ if attached {STRING} opts.item (k) as s then
+ Result := s
+ end
+ end
+
+ html_encoder: HTML_ENCODER
+ once ("thread")
+ create Result
+ end
+
+end
diff --git a/draft/application/cms/src/core/kernel/api/cms_url_api_options.e b/draft/application/cms/src/core/kernel/api/cms_url_api_options.e
new file mode 100644
index 00000000..5133d15a
--- /dev/null
+++ b/draft/application/cms/src/core/kernel/api/cms_url_api_options.e
@@ -0,0 +1,25 @@
+note
+ description: "Summary description for {CMS_URL_API_OPTIONS}."
+ author: ""
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ CMS_URL_API_OPTIONS
+
+inherit
+ CMS_API_OPTIONS
+
+create
+ make,
+ make_absolute
+
+feature {NONE} -- Initialization
+
+ make_absolute
+ do
+ make (1)
+ force (True, "absolute")
+ end
+
+end
diff --git a/draft/application/cms/src/core/kernel/auth/cms_auth_engine.e b/draft/application/cms/src/core/kernel/auth/cms_auth_engine.e
new file mode 100644
index 00000000..47561f30
--- /dev/null
+++ b/draft/application/cms/src/core/kernel/auth/cms_auth_engine.e
@@ -0,0 +1,16 @@
+note
+ description: "Summary description for {CMS_AUTH_ENGINE}."
+ author: ""
+ date: "$Date$"
+ revision: "$Revision$"
+
+deferred class
+ CMS_AUTH_ENGINE
+
+feature -- Status
+
+ valid_credential (u,p: READABLE_STRING_32): BOOLEAN
+ deferred
+ end
+
+end
diff --git a/draft/application/cms/src/core/kernel/auth/cms_storage_auth_engine.e b/draft/application/cms/src/core/kernel/auth/cms_storage_auth_engine.e
new file mode 100644
index 00000000..1dc7b0ba
--- /dev/null
+++ b/draft/application/cms/src/core/kernel/auth/cms_storage_auth_engine.e
@@ -0,0 +1,34 @@
+note
+ description: "Summary description for {CMS_STORAGE_AUTH_ENGINE}."
+ author: ""
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ CMS_STORAGE_AUTH_ENGINE
+
+inherit
+ CMS_AUTH_ENGINE
+
+create
+ make
+
+feature {NONE} -- Initialization
+
+ make (a_storage: like storage)
+ do
+ storage := a_storage
+ end
+
+ storage: CMS_STORAGE
+
+feature -- Status
+
+ valid_credential (u,p: READABLE_STRING_32): BOOLEAN
+ do
+ if attached storage.user_by_name (u) as l_user then
+ Result := attached l_user.encoded_password as l_pass and then l_pass.same_string (storage.encoded_password (p))
+ end
+ end
+
+end
diff --git a/draft/application/cms/src/core/kernel/cms_html_page.e b/draft/application/cms/src/core/kernel/cms_html_page.e
new file mode 100644
index 00000000..2ccc7405
--- /dev/null
+++ b/draft/application/cms/src/core/kernel/cms_html_page.e
@@ -0,0 +1,225 @@
+note
+ description: "Summary description for {CMS_HTML_PAGE}."
+ author: ""
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ CMS_HTML_PAGE
+
+create
+ make
+
+feature {NONE} -- Initialization
+
+ make
+ do
+ create regions.make (5)
+ language := "en"
+
+ status_code := {HTTP_STATUS_CODE}.ok
+ create header.make
+ create {ARRAYED_LIST [STRING]} head_lines.make (5)
+ header.put_content_type_text_html
+ end
+
+feature -- Status
+
+ status_code: INTEGER
+
+feature -- Header
+
+ header: HTTP_HEADER
+
+feature -- Region
+
+ regions: HASH_TABLE [STRING_8, STRING_8]
+ -- header
+ -- content
+ -- footer
+ -- could have sidebar first, sidebar second, ...
+
+ region (n: STRING_8): STRING_8
+ do
+ if attached regions.item (n) as r then
+ Result := r
+ else
+ Result := ""
+ debug
+ Result := "{{" + n + "}}"
+ end
+ end
+ end
+
+ html_head: STRING_8
+ local
+ t: like title
+ lines: like head_lines
+ do
+ t := title
+ lines := head_lines
+ if t /= Void or else lines.count > 0 then
+ create Result.make (50)
+ if t /= Void then
+ Result.append ("" + t + "%N")
+ end
+ Result.append_character ('%N')
+ across
+ lines as l
+ loop
+ Result.append (l.item)
+ Result.append_character ('%N')
+ end
+ else
+ create Result.make_empty
+ end
+ end
+
+ header_region: STRING_8
+ do
+ Result := region ("header")
+ end
+
+ content_region: STRING_8
+ do
+ Result := region ("content")
+ end
+
+ footer_region: STRING_8
+ do
+ Result := region ("content")
+ end
+
+feature -- Element change
+
+ add_to_region (s: STRING; k: STRING)
+ local
+ r: detachable STRING
+ do
+ r := regions.item (k)
+ if r = Void then
+ create r.make_from_string (s)
+ set_region (r, k)
+ else
+ r.append (s)
+ end
+ end
+
+ add_to_header_region (s: STRING)
+ do
+ add_to_region (s, "header")
+ end
+
+ add_to_content_region (s: STRING)
+ do
+ add_to_region (s, "content")
+ end
+
+ add_to_footer_region (s: STRING)
+ do
+ add_to_region (s, "footer")
+ end
+
+ set_region (s: STRING; k: STRING)
+ do
+ regions.force (s, k)
+ end
+
+-- set_header_region (s: STRING)
+-- do
+-- set_region (s, "header")
+-- end
+
+-- set_content_region (s: STRING)
+-- do
+-- set_region (s, "content")
+-- end
+
+-- set_footer_region (s: STRING)
+-- do
+-- set_region (s, "footer")
+-- end
+
+feature -- Access
+
+ title: detachable STRING
+
+ language: STRING
+
+ head_lines: LIST [STRING]
+
+ head_lines_to_string: STRING
+ do
+ create Result.make_empty
+ across
+ head_lines as h
+ loop
+ Result.append (h.item)
+ Result.append_character ('%N')
+ end
+ end
+
+-- variables: HASH_TABLE [detachable ANY, STRING_8]
+
+feature -- Element change
+
+ set_status_code (c: like status_code)
+ do
+ status_code := c
+ end
+
+ set_language (s: like language)
+ do
+ language := s
+ end
+
+ set_title (s: like title)
+ do
+ title := s
+ end
+
+ add_meta_name_content (a_name: STRING; a_content: STRING)
+ local
+ s: STRING_8
+ do
+ s := ""
+ head_lines.extend (s)
+ end
+
+ add_meta_http_equiv (a_http_equiv: STRING; a_content: STRING)
+ local
+ s: STRING_8
+ do
+ s := ""
+ head_lines.extend (s)
+ end
+
+ add_style (a_href: STRING; a_media: detachable STRING)
+ local
+ s: STRING_8
+ do
+ s := "")
+ head_lines.extend (s)
+ end
+
+ add_javascript_url (a_src: STRING)
+ local
+ s: STRING_8
+ do
+ s := ""
+ head_lines.extend (s)
+ end
+
+ add_javascript_content (a_script: STRING)
+ local
+ s: STRING_8
+ do
+ s := ""
+ head_lines.extend (s)
+ end
+
+end
diff --git a/draft/application/cms/src/core/kernel/cms_session_controler.e b/draft/application/cms/src/core/kernel/cms_session_controler.e
new file mode 100644
index 00000000..9a7eac2c
--- /dev/null
+++ b/draft/application/cms/src/core/kernel/cms_session_controler.e
@@ -0,0 +1,134 @@
+note
+ description: "[
+ Summary description for CMS_SESSION_CONTROLER.
+ ]"
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ CMS_SESSION_CONTROLER
+
+inherit
+ ANY
+
+ WSF_SESSION_FACTORY [WSF_SESSION]
+
+create
+ make
+
+feature -- Initialization
+
+ make (req: WSF_REQUEST; a_mngr: like session_manager)
+ do
+ session_manager := a_mngr
+ initialize
+ create discarded_sessions.make
+ get_session (req)
+ end
+
+ initialize
+ do
+ end
+
+feature -- Session access
+
+ session: WSF_SESSION
+
+ has_pending_session: BOOLEAN
+
+ discarded_sessions: LINKED_LIST [like session]
+
+feature -- Session operation
+
+ session_commit (page: CMS_HTML_PAGE_RESPONSE; e: CMS_EXECUTION)
+ do
+ if has_pending_session then
+ session.apply_to (page.header, e.request, e.request.script_url ("/"))
+ end
+ session.commit
+ end
+
+ apply_sessions_to (h: HTTP_HEADER; req: WSF_REQUEST; a_path: detachable READABLE_STRING_8)
+ do
+ session.apply_to (h, req, a_path)
+ across
+ discarded_sessions as c
+ loop
+ c.item.apply_to (h, req, a_path)
+ end
+ end
+
+ start_session (req: WSF_REQUEST)
+ -- Start a new session
+ local
+ s: like session
+ do
+ close_session (req)
+ s := new_session (req, False, session_manager)
+ req.set_execution_variable (session_request_variable_name, s)
+ session := s
+ if s.is_pending then
+ has_pending_session := True
+ end
+ ensure
+ session_attached: session /= Void
+ end
+
+ get_session (req: WSF_REQUEST)
+ -- Get existing session, or start a new one
+ local
+ s: like session
+ do
+ if attached {like session} req.execution_variable (session_request_variable_name) as r_session then
+ session := r_session
+ else
+ s := new_session (req, True, session_manager)
+-- create {CMS_SESSION} s.make (req, "_EWF_CMS_SESSID")
+ if s.is_pending then
+ has_pending_session := True
+ end
+ session := s
+ req.set_execution_variable (session_request_variable_name, s)
+ end
+ if session.expired then
+ start_session (req)
+ end
+ end
+
+ close_session (req: WSF_REQUEST)
+ -- Close `session' if any
+ do
+ if session.is_pending then
+ has_pending_session := has_pending_session or not discarded_sessions.is_empty
+ else
+ has_pending_session := True
+ discarded_sessions.extend (session)
+ end
+ session.destroy
+ end
+
+feature -- Session internal
+
+ session_manager: WSF_SESSION_MANAGER
+
+ new_session (req: WSF_REQUEST; a_reuse: BOOLEAN; m: WSF_SESSION_MANAGER): like session
+ local
+ s: CMS_SESSION
+ dt: DATE_TIME
+ do
+ if a_reuse then
+ create s.make (req, session_id_name, m)
+ else
+ create s.make_new (session_id_name, m)
+ create dt.make_now_utc
+ dt.day_add (31)
+ s.set_expiration (dt)
+ end
+ Result := s
+ end
+
+ session_request_variable_name: STRING = "_EWF_CMS_SESSION_"
+
+ session_id_name: STRING = "_EWF_CMS_SESSID"
+
+end
diff --git a/draft/application/cms/src/core/kernel/cms_user.e b/draft/application/cms/src/core/kernel/cms_user.e
new file mode 100644
index 00000000..182447f8
--- /dev/null
+++ b/draft/application/cms/src/core/kernel/cms_user.e
@@ -0,0 +1,158 @@
+note
+ description: "Summary description for {CMS_USER}."
+ author: ""
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ CMS_USER
+
+inherit
+ DEBUG_OUTPUT
+
+create
+ make_new,
+ make
+
+feature {NONE} -- Initialization
+
+ make (a_id: like id; n: like name; dt: like creation_date)
+ require
+ a_id > 0
+ do
+ id := a_id
+ creation_date := dt
+ name := n
+ ensure
+ valid_password: password = Void and encoded_password /= Void
+ end
+
+ make_new (n: like name)
+ do
+ name := n
+ create creation_date.make_now_utc
+ end
+
+feature -- Access
+
+ is_admin: BOOLEAN
+ do
+ Result := id = 1
+ end
+
+ id: INTEGER
+
+ name: STRING_8
+
+ password: detachable READABLE_STRING_32
+
+ encoded_password: detachable READABLE_STRING_8
+
+ email: detachable READABLE_STRING_8
+
+ profile: detachable CMS_USER_PROFILE
+
+ creation_date: DATE_TIME
+
+ last_login_date: detachable DATE_TIME
+
+ data: detachable HASH_TABLE [detachable ANY, STRING]
+
+ data_item (k: STRING): detachable ANY
+ do
+ if attached data as l_data then
+ Result := l_data.item (k)
+ end
+ end
+
+feature -- Status report
+
+ has_id: BOOLEAN
+ do
+ Result := id > 0
+ end
+
+ has_email: BOOLEAN
+ do
+ Result := attached email as e and then not e.is_empty
+ end
+
+ debug_output: STRING
+ do
+ Result := name
+ end
+
+ same_as (u: detachable CMS_USER): BOOLEAN
+ do
+ Result := u /= Void and then id = u.id
+ end
+
+feature -- Element change
+
+ set_id (a_id: like id)
+ do
+ id := a_id
+ end
+
+ set_password (p: like password)
+ do
+ password := p
+ end
+
+ set_encoded_password (p: like encoded_password)
+ do
+ encoded_password := p
+ end
+
+ set_email (m: like email)
+ do
+ email := m
+ end
+
+ set_profile (prof: like profile)
+ do
+ profile := prof
+ end
+
+ set_data_item (k: READABLE_STRING_8; d: like data_item)
+ local
+ l_data: like data
+ do
+ l_data := data
+ if l_data = Void then
+ create l_data.make (1)
+ data := l_data
+ end
+ l_data.force (d, k)
+ end
+
+ remove_data_item (k: READABLE_STRING_8)
+ do
+ if attached data as l_data then
+ l_data.remove (k)
+ end
+ end
+
+ set_profile_item (k: READABLE_STRING_8; v: READABLE_STRING_8)
+ local
+ prof: like profile
+ do
+ prof := profile
+ if prof = Void then
+ create prof.make
+ profile := prof
+ end
+ prof.force (v, k)
+ end
+
+ set_last_login_date (dt: like last_login_date)
+ do
+ last_login_date := dt
+ end
+
+ set_last_login_date_now
+ do
+ set_last_login_date (create {DATE_TIME}.make_now_utc)
+ end
+
+end
diff --git a/draft/application/cms/src/core/kernel/cms_user_profile.e b/draft/application/cms/src/core/kernel/cms_user_profile.e
new file mode 100644
index 00000000..80d4b773
--- /dev/null
+++ b/draft/application/cms/src/core/kernel/cms_user_profile.e
@@ -0,0 +1,51 @@
+note
+ description: "Summary description for {CMS_USER_PROFILE}."
+ author: ""
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ CMS_USER_PROFILE
+
+inherit
+ TABLE_ITERABLE [READABLE_STRING_8, READABLE_STRING_8]
+
+create
+ make
+
+feature {NONE} -- Initialization
+
+ make
+ do
+ create items.make (0)
+ end
+
+feature -- Access
+
+ item (k: READABLE_STRING_8): detachable READABLE_STRING_8
+ do
+ Result := items.item (k.as_string_8)
+ end
+
+feature -- Change
+
+ force (v: READABLE_STRING_8; k: READABLE_STRING_8)
+ do
+ items.force (v, k.as_string_8)
+ end
+
+feature -- Access
+
+ new_cursor: TABLE_ITERATION_CURSOR [READABLE_STRING_8, READABLE_STRING_8]
+ -- Fresh cursor associated with current structure
+ do
+ Result := items.new_cursor
+ end
+
+feature {NONE} -- Implementation
+
+ items: HASH_TABLE [READABLE_STRING_8, STRING_8]
+
+invariant
+
+end
diff --git a/draft/application/cms/src/core/kernel/content/cms_block.e b/draft/application/cms/src/core/kernel/content/cms_block.e
new file mode 100644
index 00000000..a96c3bc9
--- /dev/null
+++ b/draft/application/cms/src/core/kernel/content/cms_block.e
@@ -0,0 +1,32 @@
+note
+ description: "Summary description for {CMS_BLOCK}."
+ author: ""
+ date: "$Date$"
+ revision: "$Revision$"
+
+deferred class
+ CMS_BLOCK
+
+feature -- Access
+
+ name: READABLE_STRING_8
+ deferred
+ end
+
+ title: detachable READABLE_STRING_32
+ deferred
+ end
+
+feature -- status report
+
+ is_enabled: BOOLEAN
+
+feature -- Conversion
+
+ to_html (a_theme: CMS_THEME): STRING_8
+ deferred
+ end
+
+invariant
+
+end
diff --git a/draft/application/cms/src/core/kernel/content/cms_content_block.e b/draft/application/cms/src/core/kernel/content/cms_content_block.e
new file mode 100644
index 00000000..f3f65c28
--- /dev/null
+++ b/draft/application/cms/src/core/kernel/content/cms_content_block.e
@@ -0,0 +1,46 @@
+note
+ description: "Summary description for {CMS_CONTENT_BLOCK}."
+ author: ""
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ CMS_CONTENT_BLOCK
+
+inherit
+ CMS_BLOCK
+
+create
+ make
+
+feature {NONE} -- Initialization
+
+ make (a_name: like name; a_title: like title; a_body: like body; a_format: like format)
+ do
+ is_enabled := True
+ name := a_name
+ title := a_title
+ body := a_body
+ format := a_format
+ end
+
+feature -- Access
+
+ name: READABLE_STRING_8
+
+ title: detachable READABLE_STRING_32
+
+ body: READABLE_STRING_8
+
+ format: CMS_FORMAT
+
+feature -- Conversion
+
+ to_html (a_theme: CMS_THEME): STRING_8
+ do
+ Result := format.to_html (body)
+ end
+
+invariant
+
+end
diff --git a/draft/application/cms/src/core/kernel/content/cms_content_type.e b/draft/application/cms/src/core/kernel/content/cms_content_type.e
new file mode 100644
index 00000000..64438267
--- /dev/null
+++ b/draft/application/cms/src/core/kernel/content/cms_content_type.e
@@ -0,0 +1,60 @@
+note
+ description: "Summary description for {CMS_CONTENT_TYPE}."
+ author: ""
+ date: "$Date$"
+ revision: "$Revision$"
+
+deferred class
+ CMS_CONTENT_TYPE
+
+feature -- Access
+
+ name: READABLE_STRING_8
+ -- Internal name
+ deferred
+ end
+
+ title: READABLE_STRING_8
+ deferred
+ end
+
+ description: detachable READABLE_STRING_8
+ -- Optional description
+ deferred
+ end
+
+ available_formats: LIST [CMS_FORMAT]
+ deferred
+ end
+
+feature -- Factory
+
+ fill_edit_form (f: CMS_FORM; a_node: detachable CMS_NODE)
+ -- Fill the edit form `f'
+ deferred
+ end
+
+ change_node (a_execution: CMS_EXECUTION; a_form_data: CMS_FORM_DATA; a_node: like new_node)
+ -- Apply data from `a_form_data' to a_node
+ require
+ a_node.has_id
+ deferred
+ end
+
+ new_node (a_execution: CMS_EXECUTION; a_form_data: CMS_FORM_DATA; a_node: detachable like new_node): CMS_NODE
+ -- New content created with `a_form_data'
+ deferred
+ ensure
+ a_node /= Void implies a_node = Result
+ end
+
+feature {NONE} -- Implementation: helper
+
+ formats: CMS_FORMATS
+ once
+ create Result
+ end
+
+invariant
+
+end
diff --git a/draft/application/cms/src/core/kernel/content/cms_menu_block.e b/draft/application/cms/src/core/kernel/content/cms_menu_block.e
new file mode 100644
index 00000000..9d825726
--- /dev/null
+++ b/draft/application/cms/src/core/kernel/content/cms_menu_block.e
@@ -0,0 +1,45 @@
+note
+ description: "Summary description for {CMS_MENU_BLOCK}."
+ author: ""
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ CMS_MENU_BLOCK
+
+inherit
+ CMS_BLOCK
+
+create
+ make
+
+feature {NONE} -- Initialization
+
+ make (a_menu: like menu)
+ do
+ is_enabled := True
+ menu := a_menu
+ name := a_menu.name
+ title := a_menu.title
+ end
+
+feature -- Access
+
+ menu: CMS_MENU
+
+ name: READABLE_STRING_8
+
+ title: detachable READABLE_STRING_32
+
+ is_horizontal: BOOLEAN
+
+feature -- Conversion
+
+ to_html (a_theme: CMS_THEME): STRING_8
+ do
+ Result := a_theme.menu_html (menu, is_horizontal)
+ end
+
+invariant
+
+end
diff --git a/draft/application/cms/src/core/kernel/content/cms_node.e b/draft/application/cms/src/core/kernel/content/cms_node.e
new file mode 100644
index 00000000..545020b8
--- /dev/null
+++ b/draft/application/cms/src/core/kernel/content/cms_node.e
@@ -0,0 +1,101 @@
+note
+ description: "Summary description for {WSF_CMS_NODE}."
+ author: ""
+ date: "$Date$"
+ revision: "$Revision$"
+
+deferred class
+ CMS_NODE
+
+feature -- Access
+
+ id: INTEGER
+ -- Unique identifier of Current.
+
+ title: detachable READABLE_STRING_32
+ -- Associated title (optional).
+ deferred
+ end
+
+ body: detachable READABLE_STRING_8
+ -- Body of Current.
+ deferred
+ end
+
+ format: CMS_FORMAT
+ -- Format associated with `body'
+ deferred
+ end
+
+ content_type_name: STRING
+ -- Associated content type name
+ deferred
+ end
+
+feature -- status report
+
+ has_id: BOOLEAN
+ do
+ Result := id > 0
+ end
+
+feature -- Access: status
+
+ author: detachable CMS_USER
+
+ creation_date: DATE_TIME
+
+ modification_date: DATE_TIME
+
+feature -- Change
+
+ set_id (a_id: like id)
+ require
+ not has_id
+ do
+ id := a_id
+ end
+
+ set_author (u: like author)
+ do
+ author := u
+ end
+
+feature -- Conversion
+
+ to_html (a_theme: CMS_THEME): STRING_8
+ local
+ d: STRING
+ do
+ Result := "
"
+ if attached title as l_title then
+ Result.append ("
" + a_theme.node_link (Current) + "
")
+ end
+ create d.make_empty
+ if attached author as u then
+ d.append ("by " + a_theme.user_link (u) + " ")
+ end
+ if attached modification_date as dt then
+ d.append ("last modified: " + dt.year.out + "/" + dt.month.out + "/" + dt.day.out + "")
+ end
+ if not d.is_empty then
+ Result.append ("
")
+ Result.append (d)
+ Result.append ("
")
+ end
+ if attached body as b then
+ Result.append ("
")
+ end
+
+feature {NONE} -- Implementation: helper
+
+ formats: CMS_FORMATS
+ once
+ create Result
+ end
+
+end
diff --git a/draft/application/cms/src/core/kernel/content/format/cms_filtered_html_format.e b/draft/application/cms/src/core/kernel/content/format/cms_filtered_html_format.e
new file mode 100644
index 00000000..43febb3e
--- /dev/null
+++ b/draft/application/cms/src/core/kernel/content/format/cms_filtered_html_format.e
@@ -0,0 +1,44 @@
+note
+ description : "[
+ Filtered html format
+ ]"
+ date : "$Date$"
+ revision : "$Revision$"
+
+class
+ CMS_FILTERED_HTML_FORMAT
+
+inherit
+ CMS_FORMAT
+ redefine
+ default_create
+ end
+
+feature {NONE} -- Initialization
+
+ default_create
+ do
+ Precursor
+ create filters.make (3)
+ filters.force (create {CMS_URL_FILTER})
+ filters.force (create {CMS_HTML_FILTER})
+ filters.force (create {CMS_LINE_BREAK_CONVERTER_FILTER})
+
+-- help := "
Web page addresses and e-mail addresses turn into links automatically.
Allowed HTML tags: "
+-- across
+-- allowed_html_tags as c
+-- loop
+-- help.append ("<" + c.item + "> ")
+-- end
+-- help.append ("
Lines and paragraphs break automatically.
")
+ end
+
+feature -- Access
+
+ name: STRING = "filtered_html"
+
+ title: STRING_8 = "Filtered HTML"
+
+ filters: ARRAYED_LIST [CMS_FILTER]
+
+end
diff --git a/draft/application/cms/src/core/kernel/content/format/cms_format.e b/draft/application/cms/src/core/kernel/content/format/cms_format.e
new file mode 100644
index 00000000..fe9d8dd3
--- /dev/null
+++ b/draft/application/cms/src/core/kernel/content/format/cms_format.e
@@ -0,0 +1,46 @@
+note
+ description: "Summary description for {WSF_CMS_FORMAT}."
+ author: ""
+ date: "$Date$"
+ revision: "$Revision$"
+
+deferred class
+ CMS_FORMAT
+
+feature -- Access
+
+ name: STRING
+ deferred
+ end
+
+ title: READABLE_STRING_8
+ deferred
+ end
+
+ help: STRING
+ do
+ create Result.make (0)
+ across
+ filters as c
+ loop
+ if attached c.item.help as h and then not h.is_empty then
+ Result.append ("
" + h + "
")
+ end
+ end
+ end
+
+ filters: LIST [CMS_FILTER]
+ deferred
+ end
+
+ to_html (a_text: READABLE_STRING_8): STRING_8
+ do
+ create Result.make_from_string (a_text)
+ across
+ filters as c
+ loop
+ c.item.filter (Result)
+ end
+ end
+
+end
diff --git a/draft/application/cms/src/core/kernel/content/format/cms_formats.e b/draft/application/cms/src/core/kernel/content/format/cms_formats.e
new file mode 100644
index 00000000..5c82e097
--- /dev/null
+++ b/draft/application/cms/src/core/kernel/content/format/cms_formats.e
@@ -0,0 +1,54 @@
+note
+ description: "Summary description for {CMS_FORMATS}."
+ author: ""
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ CMS_FORMATS
+
+feature -- Access
+
+ format (a_name: like {CMS_FORMAT}.name): detachable CMS_FORMAT
+ do
+ across
+ all_formats as c
+ until
+ Result /= Void
+ loop
+ if c.item.name.same_string (a_name) then
+ Result := c.item
+ end
+ end
+ end
+
+ all_formats: LIST [CMS_FORMAT]
+ once
+ create {ARRAYED_LIST [CMS_FORMAT]} Result.make (3)
+ Result.force (plain_text)
+ Result.force (full_html)
+ Result.force (filtered_html)
+ end
+
+ default_format: CMS_FORMAT
+ do
+ Result := plain_text --FIXME
+ end
+
+ plain_text: CMS_PLAIN_TEXT_FORMAT
+ once
+ create Result
+ end
+
+ full_html: CMS_FULL_HTML_FORMAT
+ once
+ create Result
+ end
+
+ filtered_html: CMS_FILTERED_HTML_FORMAT
+ once
+ create Result
+ end
+
+
+end
diff --git a/draft/application/cms/src/core/kernel/content/format/cms_full_html_format.e b/draft/application/cms/src/core/kernel/content/format/cms_full_html_format.e
new file mode 100644
index 00000000..c486e975
--- /dev/null
+++ b/draft/application/cms/src/core/kernel/content/format/cms_full_html_format.e
@@ -0,0 +1,35 @@
+note
+ description : "[
+ Full html format
+ ]"
+ date : "$Date$"
+ revision : "$Revision$"
+
+class
+ CMS_FULL_HTML_FORMAT
+
+inherit
+ CMS_FORMAT
+ redefine
+ default_create
+ end
+
+feature {NONE} -- Initialization
+
+ default_create
+ do
+ Precursor
+ create filters.make (2)
+ filters.force (create {CMS_URL_FILTER})
+ filters.force (create {CMS_LINE_BREAK_CONVERTER_FILTER})
+ end
+
+feature -- Access
+
+ name: STRING = "full_html"
+
+ title: STRING_8 = "Full HTML"
+
+ filters: ARRAYED_LIST [CMS_FILTER]
+
+end
diff --git a/draft/application/cms/src/core/kernel/content/format/cms_plain_text_format.e b/draft/application/cms/src/core/kernel/content/format/cms_plain_text_format.e
new file mode 100644
index 00000000..76b05d4e
--- /dev/null
+++ b/draft/application/cms/src/core/kernel/content/format/cms_plain_text_format.e
@@ -0,0 +1,49 @@
+note
+ description : "[
+ Plain Text format
+ ]"
+ date : "$Date$"
+ revision : "$Revision$"
+
+class
+ CMS_PLAIN_TEXT_FORMAT
+
+inherit
+ CMS_FORMAT
+ redefine
+ default_create,
+ help
+ end
+
+feature {NONE} -- Initialization
+
+ default_create
+ do
+ Precursor
+ create filters.make (2)
+ filters.force (create {CMS_HTML_TO_TEXT_FILTER})
+ filters.force (create {CMS_LINE_BREAK_CONVERTER_FILTER})
+ end
+
+
+feature -- Access
+
+ name: STRING = "plain_text"
+
+ title: STRING_8 = "Plain text"
+
+ help: STRING
+ do
+ Result := "
No HTML tags allowed.
"
+ Result.append (Precursor)
+ end
+--
+--
No HTML tags allowed.
+--
Web page addresses and e-mail addresses turn into links automatically.
+--
Lines and paragraphs break automatically.
+--
+-- ]"
+
+ filters: ARRAYED_LIST [CMS_FILTER]
+
+end
diff --git a/draft/application/cms/src/core/kernel/content/format/filters/cms_filter.e b/draft/application/cms/src/core/kernel/content/format/filters/cms_filter.e
new file mode 100644
index 00000000..1b49552a
--- /dev/null
+++ b/draft/application/cms/src/core/kernel/content/format/filters/cms_filter.e
@@ -0,0 +1,35 @@
+note
+ description: "Summary description for {CMS_FILTER}."
+ author: ""
+ date: "$Date$"
+ revision: "$Revision$"
+
+deferred class
+ CMS_FILTER
+
+feature -- Access
+
+ name: READABLE_STRING_8
+ deferred
+ end
+
+ title: READABLE_STRING_8
+ deferred
+ end
+
+ description: READABLE_STRING_8
+ deferred
+ end
+
+ help: READABLE_STRING_8
+ do
+ Result := description
+ end
+
+feature -- Conversion
+
+ filter (s: STRING_8)
+ deferred
+ end
+
+end
diff --git a/draft/application/cms/src/core/kernel/content/format/filters/cms_html_filter.e b/draft/application/cms/src/core/kernel/content/format/filters/cms_html_filter.e
new file mode 100644
index 00000000..6c8b2b9f
--- /dev/null
+++ b/draft/application/cms/src/core/kernel/content/format/filters/cms_html_filter.e
@@ -0,0 +1,126 @@
+note
+ description: "Summary description for {CMS_HTML_FILTER}."
+ author: ""
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ CMS_HTML_FILTER
+
+inherit
+ CMS_FILTER
+ redefine
+ default_create
+ end
+
+feature {NONE} -- Initialization
+
+ default_create
+ do
+ Precursor
+ allowed_html_tags := <<"a", "em", "strong", "cite", "blockquote", "code", "ul", "ol", "li", "dl">>
+ description := "Allowed HTML tags: "
+ across
+ allowed_html_tags as c
+ loop
+ description.append ("<" + c.item + "> ")
+ end
+ end
+
+feature -- Access
+
+ name: STRING_8 = "html_filter"
+
+ title: STRING_8 = "HTML filter"
+
+ description: STRING_8
+
+ allowed_html_tags: ITERABLE [READABLE_STRING_8]
+
+feature -- Conversion
+
+ filter (a_text: STRING_8)
+ local
+ l_new: STRING_8
+ i: INTEGER
+ n: INTEGER
+ in_tag: BOOLEAN
+ p1, p2: INTEGER
+ do
+ create l_new.make (a_text.count)
+ from
+ p1 := 1
+ i := a_text.index_of ('<', 1)
+ if i > 0 then
+ l_new.append (a_text.substring (1, i - 1))
+ end
+ n := a_text.count
+ until
+ i = 0 or i > n
+ loop
+ if a_text[i] = '<' then
+ in_tag := True
+ p1 := i
+ p2 := a_text.index_of ('>', i + 1)
+ if p2 = 0 then
+ -- next '<'
+ i := a_text.index_of ('<', i + 1)
+ if i > 0 then
+ l_new.append (a_text.substring (p1, i - 1))
+ end
+ else
+ if is_authorized (a_text.substring (p1, p2)) then
+ l_new.append (a_text.substring (p1, p2))
+ i := a_text.index_of ('<', p2 + 1)
+ else
+ i := a_text.index_of ('<', p2 + 1)
+ end
+ if i > 0 then
+ l_new.append (a_text.substring (p2 + 1, i - 1))
+ end
+ end
+ else
+ i := i + 1
+ end
+ end
+ l_new.append (a_text.substring (p1, n))
+ a_text.wipe_out
+ a_text.append (l_new)
+ end
+
+ is_authorized (s: READABLE_STRING_8): BOOLEAN
+ -- Is `s' authorized?
+ --| `s' has either "<....>" or "<..../>" or ""
+ local
+ l_tagname: detachable STRING
+ i,n,p1: INTEGER
+ do
+-- create l_tagname.make_empty
+ from
+ i := 2 -- skip first '<'
+ n := s.count
+ until
+ i > n or l_tagname /= Void
+ loop
+ if p1 > 0 then
+ if s[i].is_space or s[i] = '/' or s[i] = '>' then
+ l_tagname := s.substring (p1, i - 1)
+ end
+ else
+ if s[i].is_space or s[i] = '/' then
+ else
+ p1 := i
+ end
+ end
+ i := i + 1
+ end
+ if l_tagname /= Void then
+ l_tagname.to_lower
+ Result := across allowed_html_tags as c some c.item.same_string (l_tagname) end
+ else
+ Result := True
+ end
+ end
+
+
+end
diff --git a/draft/application/cms/src/core/kernel/content/format/filters/cms_html_to_text_filter.e b/draft/application/cms/src/core/kernel/content/format/filters/cms_html_to_text_filter.e
new file mode 100644
index 00000000..ebc81bcd
--- /dev/null
+++ b/draft/application/cms/src/core/kernel/content/format/filters/cms_html_to_text_filter.e
@@ -0,0 +1,34 @@
+note
+ description: "Summary description for {CMS_HTML_TO_TEXT_FILTER}."
+ author: ""
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ CMS_HTML_TO_TEXT_FILTER
+
+inherit
+ CMS_FILTER
+
+feature -- Access
+
+ name: STRING_8 = "html_to_text"
+
+ title: STRING_8 = "HTML to text"
+
+ description: STRING_8 = "Replaces HTML tags and entities with plain text formatting, moving links at the end. This filter is just for text messages and it isn't safe for rendering content on a web page."
+
+feature -- Conversion
+
+ filter (a_text: STRING_8)
+ local
+ enc: HTML_ENCODER
+ s: STRING_8
+ do
+ create enc
+ s := enc.encoded_string (a_text)
+ a_text.wipe_out
+ a_text.append (s)
+ end
+
+end
diff --git a/draft/application/cms/src/core/kernel/content/format/filters/cms_line_break_converter_filter.e b/draft/application/cms/src/core/kernel/content/format/filters/cms_line_break_converter_filter.e
new file mode 100644
index 00000000..995cd267
--- /dev/null
+++ b/draft/application/cms/src/core/kernel/content/format/filters/cms_line_break_converter_filter.e
@@ -0,0 +1,34 @@
+note
+ description: "Summary description for {CMS_HTML_FILTER}."
+ author: ""
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ CMS_LINE_BREAK_CONVERTER_FILTER
+
+inherit
+ CMS_FILTER
+ redefine
+ help
+ end
+
+feature -- Access
+
+ name: STRING_8 = "line_break_converter"
+
+ title: STRING_8 = "Line break converter"
+
+ help: STRING = "Lines and paragraphs break automatically"
+
+ description: STRING_8 = "Converts line breaks into HTML (i.e. <br> and <p> tags)."
+
+feature -- Conversion
+
+ filter (a_text: STRING_8)
+ do
+ a_text.replace_substring_all ("%N", " %N")
+ -- FIXME jfiat [2012/09/12] :also use
...
+ end
+
+end
diff --git a/draft/application/cms/src/core/kernel/content/format/filters/cms_no_html_filter.e b/draft/application/cms/src/core/kernel/content/format/filters/cms_no_html_filter.e
new file mode 100644
index 00000000..8092bce2
--- /dev/null
+++ b/draft/application/cms/src/core/kernel/content/format/filters/cms_no_html_filter.e
@@ -0,0 +1,77 @@
+note
+ description: "Summary description for {CMS_NO_HTML_FILTER}."
+ author: ""
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ CMS_NO_HTML_FILTER
+
+inherit
+ CMS_FILTER
+ redefine
+ default_create
+ end
+
+feature {NONE} -- Initialization
+
+ default_create
+ do
+ Precursor
+ end
+
+feature -- Access
+
+ name: STRING_8 = "no_html_filter"
+
+ title: STRING_8 = "No HTML filter"
+
+ description: STRING_8 = "HTML tags removed! "
+
+feature -- Conversion
+
+ filter (a_text: STRING_8)
+ local
+ l_new: STRING_8
+ i: INTEGER
+ n: INTEGER
+ in_tag: BOOLEAN
+ p1, p2: INTEGER
+ do
+ create l_new.make (a_text.count)
+ from
+ p1 := 1
+ i := a_text.index_of ('<', 1)
+ if i > 0 then
+ l_new.append (a_text.substring (1, i - 1))
+ end
+ n := a_text.count
+ until
+ i = 0 or i > n
+ loop
+ if a_text[i] = '<' then
+ in_tag := True
+ p1 := i
+ p2 := a_text.index_of ('>', i + 1)
+ if p2 = 0 then
+ -- next '<'
+ i := a_text.index_of ('<', i + 1)
+ if i > 0 then
+ l_new.append (a_text.substring (p1, i - 1))
+ end
+ else
+ i := a_text.index_of ('<', p2 + 1)
+ if i > 0 then
+ l_new.append (a_text.substring (p2 + 1, i - 1))
+ end
+ end
+ else
+ i := i + 1
+ end
+ end
+ l_new.append (a_text.substring (p1, n))
+ a_text.wipe_out
+ a_text.append (l_new)
+ end
+
+end
diff --git a/draft/application/cms/src/core/kernel/content/format/filters/cms_url_filter.e b/draft/application/cms/src/core/kernel/content/format/filters/cms_url_filter.e
new file mode 100644
index 00000000..a53be807
--- /dev/null
+++ b/draft/application/cms/src/core/kernel/content/format/filters/cms_url_filter.e
@@ -0,0 +1,33 @@
+note
+ description: "Summary description for {CMS_URL_FILTER}."
+ author: ""
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ CMS_URL_FILTER
+
+inherit
+ CMS_FILTER
+ redefine
+ help
+ end
+
+feature -- Access
+
+ name: STRING_8 = "url"
+
+ title: STRING_8 = "URL filter"
+
+ description: STRING_8 = "Turns web and e-mail addresses into clickable links."
+
+ help: STRING = "Web page addresses and e-mail addresses turn into links automatically."
+
+feature -- Conversion
+
+ filter (a_text: STRING_8)
+ do
+ --| FIXME jfiat [2012/09/12] : todo
+ end
+
+end
diff --git a/draft/application/cms/src/core/kernel/form/cms_form.e b/draft/application/cms/src/core/kernel/form/cms_form.e
new file mode 100644
index 00000000..4065d98e
--- /dev/null
+++ b/draft/application/cms/src/core/kernel/form/cms_form.e
@@ -0,0 +1,166 @@
+note
+ description: "Summary description for {CMS_FORM}."
+ author: ""
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ CMS_FORM
+
+inherit
+ ITERABLE [CMS_FORM_ITEM]
+
+create
+ make
+
+feature {NONE} -- Initialization
+
+ make (a_action: READABLE_STRING_8; a_id: READABLE_STRING_8)
+ do
+ action := a_action
+ id := a_id
+
+ create html_classes.make (2)
+ create items.make (10)
+ end
+
+feature -- Access
+
+ action: READABLE_STRING_8
+ -- URL for the web form
+
+ id: READABLE_STRING_8
+ -- Id of the form
+
+ count: INTEGER
+ do
+ Result := items.count
+ end
+
+feature -- Validation
+
+ validation_action: detachable PROCEDURE [ANY, TUPLE [CMS_FORM_DATA]]
+ -- Procedure to validate the data
+ -- report error if not valid
+
+-- submit_callbacks_actions: HASH_TABLE [PROCEDURE [ANY, TUPLE [CMS_FORM_DATA]], STRING]
+-- -- Submit callbacks indexed by submit names
+
+feature -- Element change
+
+ set_validation_action (act: like validation_action)
+ do
+ validation_action := act
+ end
+
+feature -- Access
+
+ new_cursor: ITERATION_CURSOR [CMS_FORM_ITEM]
+ -- Fresh cursor associated with current structure
+ do
+ Result := items.new_cursor
+ end
+
+feature -- Optional
+
+ html_classes: ARRAYED_LIST [STRING_8]
+
+feature -- Items
+
+ has_field (a_name: READABLE_STRING_GENERAL): BOOLEAN
+ do
+ Result := across items as i some attached {CMS_FORM_FIELD} i.item as l_field and then l_field.name.same_string_general (a_name) end
+ end
+
+-- items_by_name (a_name: READABLE_STRING_GENERAL): detachable LIST [CMS_FORM_ITEM]
+-- local
+-- res: detachable ARRAYED_LIST [CMS_FORM_ITEM]
+-- do
+-- across
+-- items as c
+-- loop
+-- if c.item.name.same_string_general (a_name) then
+-- if res = Void then
+-- create res.make (1)
+-- end
+-- res.force (c.item)
+-- end
+-- end
+-- Result := res
+-- end
+
+ fields_by_name (a_name: READABLE_STRING_GENERAL): detachable LIST [CMS_FORM_FIELD]
+ local
+ res: detachable ARRAYED_LIST [CMS_FORM_FIELD]
+ do
+ across
+ items as c
+ loop
+ if
+ attached {CMS_FORM_FIELD} c.item as l_field and then
+ l_field.name.same_string_general (a_name)
+ then
+ if res = Void then
+ create res.make (1)
+ end
+ res.force (l_field)
+ end
+ end
+ Result := res
+ end
+
+ extend (i: CMS_FORM_ITEM)
+ local
+ n: READABLE_STRING_8
+ do
+ if attached {CMS_FORM_FIELD} i as l_field then
+ n := l_field.name
+ if n.is_empty then
+ n := (items.count + 1).out
+ l_field.update_name (n)
+ end
+ end
+ items.force (i)
+ end
+
+ extend_text (t: READABLE_STRING_8)
+ do
+ extend (create {CMS_FORM_RAW_TEXT}.make (t))
+ end
+
+feature -- Conversion
+
+ to_html (a_theme: CMS_THEME): STRING_8
+ local
+ s: STRING
+ do
+ Result := "
%N")
+ end
+
+feature {NONE} -- Implementation
+
+ items: ARRAYED_LIST [CMS_FORM_ITEM]
+ -- name => item
+
+invariant
+
+end
diff --git a/draft/application/cms/src/core/kernel/form/cms_form_button_input.e b/draft/application/cms/src/core/kernel/form/cms_form_button_input.e
new file mode 100644
index 00000000..e7937122
--- /dev/null
+++ b/draft/application/cms/src/core/kernel/form/cms_form_button_input.e
@@ -0,0 +1,20 @@
+note
+ description: "Summary description for {CMS_FORM_BUTTON_INPUT}."
+ author: ""
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ CMS_FORM_BUTTON_INPUT
+
+inherit
+ CMS_FORM_INPUT
+
+create
+ make
+
+feature -- Access
+
+ input_type: STRING = "button"
+
+end
diff --git a/draft/application/cms/src/core/kernel/form/cms_form_checkbox_input.e b/draft/application/cms/src/core/kernel/form/cms_form_checkbox_input.e
new file mode 100644
index 00000000..42b35165
--- /dev/null
+++ b/draft/application/cms/src/core/kernel/form/cms_form_checkbox_input.e
@@ -0,0 +1,46 @@
+note
+ description: "Summary description for {CMS_FORM_CHECKBOX_INPUT}."
+ author: ""
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ CMS_FORM_CHECKBOX_INPUT
+
+inherit
+ CMS_FORM_INPUT
+ redefine
+ specific_input_attributes_string
+ end
+
+create
+ make
+
+feature -- Access
+
+ checked: BOOLEAN
+ -- Current element should be preselected when the page loads
+
+ input_type: STRING = "checkbox"
+
+feature -- Change
+
+ set_checked (b: like checked)
+ do
+ checked := b
+ end
+
+feature {NONE} -- Implementation
+
+ specific_input_attributes_string: detachable STRING_8
+ -- Specific input attributes if any.
+ -- To redefine if needed
+ do
+ if checked then
+ Result := "checked=%"checked%""
+ end
+ end
+
+invariant
+
+end
diff --git a/draft/application/cms/src/core/kernel/form/cms_form_data.e b/draft/application/cms/src/core/kernel/form/cms_form_data.e
new file mode 100644
index 00000000..cf6ebdf2
--- /dev/null
+++ b/draft/application/cms/src/core/kernel/form/cms_form_data.e
@@ -0,0 +1,195 @@
+note
+ description : "Objects that ..."
+ author : "$Author$"
+ date : "$Date$"
+ revision : "$Revision$"
+
+class
+ CMS_FORM_DATA
+
+inherit
+ TABLE_ITERABLE [detachable WSF_VALUE, READABLE_STRING_8]
+
+create
+ make
+
+feature {NONE} -- Initialization
+
+ make (req: WSF_REQUEST; a_form: CMS_FORM)
+ -- Initialize `Current'.
+ do
+ form := a_form
+ create items.make (a_form.count)
+ get_items (req)
+ validate
+ end
+
+feature -- Access
+
+ form: CMS_FORM
+
+feature -- Status
+
+ is_valid: BOOLEAN
+ do
+ Result := errors = Void
+ end
+
+feature -- Access
+
+ item (a_name: READABLE_STRING_GENERAL): detachable WSF_VALUE
+ do
+ Result := items.item (a_name.as_string_8)
+ end
+
+ string_item (a_name: READABLE_STRING_GENERAL): detachable READABLE_STRING_32
+ do
+ if attached {WSF_STRING} item (a_name) as s then
+ Result := s.value
+ end
+ end
+
+ integer_item (a_name: READABLE_STRING_GENERAL): INTEGER
+ do
+ if attached {WSF_STRING} item (a_name) as s and then s.is_integer then
+ Result := s.integer_value
+ end
+ end
+
+ new_cursor: TABLE_ITERATION_CURSOR [detachable WSF_VALUE, READABLE_STRING_8]
+ -- Fresh cursor associated with current structure
+ do
+ Result := items.new_cursor
+ end
+
+feature -- Basic operation
+
+ validate
+ do
+ across
+ form as f
+ loop
+ if attached {CMS_FORM_FIELD} f.item as l_field then
+ l_field.validate (Current)
+ end
+ end
+ if attached form.validation_action as act then
+ act.call ([Current])
+ end
+ end
+
+ set_fields_invalid (b: BOOLEAN; a_name: READABLE_STRING_GENERAL)
+ do
+ if attached form.fields_by_name (a_name) as lst then
+ across
+ lst as i
+ loop
+ i.item.set_is_invalid (b)
+ end
+ end
+ end
+
+ apply_to_associated_form
+ do
+ if attached errors as errs then
+ across
+ errs as e
+ loop
+ if attached e.item as err then
+ if attached err.field as e_field then
+ set_fields_invalid (True, e_field.name)
+ end
+ end
+ end
+ end
+ across
+ items as c
+ loop
+ across
+ form as i
+ loop
+ if attached {CMS_FORM_FIELD} i.item as l_field then
+ if not attached {CMS_FORM_SUBMIT_INPUT} l_field then
+ if l_field.name.same_string (c.key) then
+ l_field.set_value (c.item)
+ end
+ end
+ end
+ end
+ end
+ end
+
+feature -- Change
+
+ report_error (a_msg: READABLE_STRING_8)
+ do
+ add_error (Void, a_msg)
+ ensure
+ is_invalid: not is_valid
+ end
+
+ report_invalid_field (a_field_name: READABLE_STRING_8; a_msg: READABLE_STRING_8)
+ require
+ has_field: form.has_field (a_field_name)
+ do
+ if attached form.fields_by_name (a_field_name) as lst then
+ across
+ lst as c
+ loop
+ add_error (c.item, a_msg)
+ end
+ end
+ ensure
+ is_invalid: not is_valid
+ end
+
+feature {NONE} -- Implementation
+
+ get_items (req: WSF_REQUEST)
+ do
+ get_form_items (req, form)
+ end
+
+ get_form_items (req: WSF_REQUEST; lst: ITERABLE [CMS_FORM_ITEM])
+ local
+ n: READABLE_STRING_8
+ v: detachable WSF_VALUE
+ do
+ across
+ lst as c
+ loop
+ if attached {CMS_FORM_FIELD} c.item as l_field then
+ n := l_field.name
+ v := req.form_parameter (n)
+ if l_field.is_required and (v = Void or else v.is_empty) then
+ add_error (l_field, "Field %"" + l_field.name + "%" is required")
+ else
+ items.force (v, n)
+ end
+ elseif attached {CMS_FORM_FIELD_SET} c.item as l_fieldset then
+ get_form_items (req, l_fieldset)
+ end
+ end
+ end
+
+ add_error (a_field: detachable CMS_FORM_FIELD; a_msg: detachable READABLE_STRING_8)
+ local
+ err: like errors
+ do
+ err := errors
+ if err = Void then
+ create err.make (1)
+ errors := err
+ end
+ err.force ([a_field, a_msg])
+ end
+
+ items: HASH_TABLE [detachable WSF_VALUE, READABLE_STRING_8]
+
+feature -- Reports
+
+ errors: detachable ARRAYED_LIST [TUPLE [field: detachable CMS_FORM_FIELD; message: detachable READABLE_STRING_8]]
+
+invariant
+
+end
diff --git a/draft/application/cms/src/core/kernel/form/cms_form_field.e b/draft/application/cms/src/core/kernel/form/cms_form_field.e
new file mode 100644
index 00000000..17ebf9da
--- /dev/null
+++ b/draft/application/cms/src/core/kernel/form/cms_form_field.e
@@ -0,0 +1,133 @@
+note
+ description: "Summary description for {CMS_FORM_ITEM}."
+ author: ""
+ date: "$Date$"
+ revision: "$Revision$"
+
+deferred class
+ CMS_FORM_FIELD
+
+inherit
+ CMS_FORM_ITEM
+
+ DEBUG_OUTPUT
+
+feature -- Access
+
+ name: READABLE_STRING_8
+
+ label: detachable READABLE_STRING_8
+
+ description: detachable READABLE_STRING_8
+
+ is_required: BOOLEAN
+
+ is_invalid: BOOLEAN
+
+ is_readonly: BOOLEAN
+
+
+feature -- Status report
+
+ debug_output: STRING
+ -- String that should be displayed in debugger to represent `Current'.
+ do
+ Result := name + " {" + generator + "}"
+ end
+
+feature -- Validation
+
+ validation_action: detachable PROCEDURE [ANY, TUPLE [CMS_FORM_DATA]]
+ -- Function returning True if valid, otherwise False
+
+ validate (fd: CMS_FORM_DATA)
+ do
+ if attached validation_action as act then
+ act.call ([fd])
+ end
+ end
+
+feature -- Element change
+
+ update_name (a_name: like name)
+ require
+ name.is_empty
+ do
+ name := a_name
+ end
+
+ set_is_required (b: BOOLEAN)
+ do
+ is_required := b
+ end
+
+ set_is_readonly (b: BOOLEAN)
+ do
+ is_readonly := b
+ end
+
+ set_label (lab: like label)
+ do
+ label := lab
+ end
+
+ set_description (t: like description)
+ do
+ description := t
+ end
+
+ set_validation_action (act: like validation_action)
+ do
+ validation_action := act
+ end
+
+ set_is_invalid (b: BOOLEAN)
+ do
+ is_invalid := b
+ end
+
+ set_value (v: detachable WSF_VALUE)
+ -- Set value `v' if applicable to Current
+ deferred
+ end
+
+feature -- Conversion
+
+ to_html (a_theme: CMS_THEME): STRING_8
+ local
+ cl: STRING
+ do
+ create cl.make_empty
+ if is_required then
+ if not cl.is_empty then
+ cl.append_character (' ')
+ end
+ cl.append ("required")
+ end
+ if is_invalid then
+ if not cl.is_empty then
+ cl.append_character (' ')
+ end
+ cl.append ("error")
+ end
+ create Result.make_from_string ("
")
+ if attached label as lab then
+ Result.append ("")
+ if is_required then
+ Result.append (" (required)")
+ end
+ Result.append (" %N")
+ end
+ Result.append (item_to_html (a_theme))
+ if attached description as desc then
+ Result.append ("
" + desc + "
")
+ end
+ Result.append ("
")
+ end
+
+ item_to_html (a_theme: CMS_THEME): STRING_8
+ deferred
+ end
+
+
+end
diff --git a/draft/application/cms/src/core/kernel/form/cms_form_field_set.e b/draft/application/cms/src/core/kernel/form/cms_form_field_set.e
new file mode 100644
index 00000000..9b3588b7
--- /dev/null
+++ b/draft/application/cms/src/core/kernel/form/cms_form_field_set.e
@@ -0,0 +1,70 @@
+note
+ description : "Objects that ..."
+ author : "$Author$"
+ date : "$Date$"
+ revision : "$Revision$"
+
+class
+ CMS_FORM_FIELD_SET
+
+inherit
+ CMS_FORM_ITEM
+
+ ITERABLE [CMS_FORM_ITEM]
+
+create
+ make
+
+feature {NONE} -- Initialization
+
+ make
+ -- Initialize `Current'.
+ do
+ create items.make (0)
+ end
+
+feature -- Access
+
+ legend: detachable READABLE_STRING_8
+
+feature -- Access
+
+ new_cursor: ITERATION_CURSOR [CMS_FORM_ITEM]
+ -- Fresh cursor associated with current structure
+ do
+ Result := items.new_cursor
+ end
+
+feature -- Change
+
+ set_legend (v: like legend)
+ do
+ legend := v
+ end
+
+ extend (i: CMS_FORM_ITEM)
+ do
+ items.force (i)
+ end
+
+feature -- Conversion
+
+ to_html (a_theme: CMS_THEME): STRING_8
+ do
+ Result := "%N")
+ end
+
+feature {NONE} -- Implementation
+
+ items: ARRAYED_LIST [CMS_FORM_ITEM]
+
+end
diff --git a/draft/application/cms/src/core/kernel/form/cms_form_file_input.e b/draft/application/cms/src/core/kernel/form/cms_form_file_input.e
new file mode 100644
index 00000000..df257456
--- /dev/null
+++ b/draft/application/cms/src/core/kernel/form/cms_form_file_input.e
@@ -0,0 +1,46 @@
+note
+ description: "Summary description for {CMS_FORM_FILE_INPUT}."
+ author: ""
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ CMS_FORM_FILE_INPUT
+
+inherit
+ CMS_FORM_INPUT
+ redefine
+ specific_input_attributes_string
+ end
+
+create
+ make
+
+feature -- Access
+
+ input_type: STRING = "file"
+
+ accepted_types: detachable READABLE_STRING_8
+ -- Types of files that the server accepts
+
+feature -- Change
+
+ set_accepted_types (v: like accepted_types)
+ do
+ accepted_types := v
+ end
+
+feature {NONE} -- Implementation
+
+ specific_input_attributes_string: detachable STRING_8
+ -- Specific input attributes if any.
+ -- To redefine if needed
+ do
+ if attached accepted_types as l_accepted_types then
+ Result := " accept=%"" + l_accepted_types + "%""
+ end
+ end
+
+invariant
+
+end
diff --git a/draft/application/cms/src/core/kernel/form/cms_form_hidden_input.e b/draft/application/cms/src/core/kernel/form/cms_form_hidden_input.e
new file mode 100644
index 00000000..20272d4f
--- /dev/null
+++ b/draft/application/cms/src/core/kernel/form/cms_form_hidden_input.e
@@ -0,0 +1,36 @@
+note
+ description: "Summary description for {CMS_FORM_INPUT}."
+ author: ""
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ CMS_FORM_HIDDEN_INPUT
+
+inherit
+ CMS_FORM_INPUT
+ redefine
+ input_type,
+ item_to_html
+ end
+
+create
+ make
+
+feature -- Access
+
+ input_type: STRING
+ once
+ Result := "hidden"
+ end
+
+feature -- Conversion
+
+ item_to_html (a_theme: CMS_THEME): STRING_8
+ do
+ Result := "
")
+ end
+
+end
diff --git a/draft/application/cms/src/core/kernel/form/cms_form_image_input.e b/draft/application/cms/src/core/kernel/form/cms_form_image_input.e
new file mode 100644
index 00000000..f3d1860d
--- /dev/null
+++ b/draft/application/cms/src/core/kernel/form/cms_form_image_input.e
@@ -0,0 +1,58 @@
+note
+ description: "Summary description for {CMS_FORM_IMAGE_INPUT}."
+ author: ""
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ CMS_FORM_IMAGE_INPUT
+
+inherit
+ CMS_FORM_INPUT
+ redefine
+ specific_input_attributes_string
+ end
+
+create
+ make
+
+feature -- Access
+
+ input_type: STRING = "image"
+
+ src: detachable READABLE_STRING_8
+ -- Specifies the URL of the image to use as a submit button
+
+ alt: detachable READABLE_STRING_8
+ -- Alternate text for an image.
+
+feature -- Change
+
+ set_src (v: like src)
+ do
+ src := v
+ end
+
+ set_alt (v: like alt)
+ do
+ alt := v
+ end
+
+feature {NONE} -- Implementation
+
+ specific_input_attributes_string: detachable STRING_8
+ -- Specific input attributes if any.
+ -- To redefine if needed
+ do
+ create Result.make_empty
+ if attached src as l_src then
+ Result.append (" src=%"" + l_src + "%"")
+ end
+ if attached alt as l_alt then
+ Result.append (" alt=%"" + l_alt + "%"")
+ end
+ end
+
+invariant
+
+end
diff --git a/draft/application/cms/src/core/kernel/form/cms_form_input.e b/draft/application/cms/src/core/kernel/form/cms_form_input.e
new file mode 100644
index 00000000..fae1353d
--- /dev/null
+++ b/draft/application/cms/src/core/kernel/form/cms_form_input.e
@@ -0,0 +1,109 @@
+note
+ description: "Summary description for {CMS_FORM_INPUT}."
+ author: ""
+ date: "$Date$"
+ revision: "$Revision$"
+
+deferred class
+ CMS_FORM_INPUT
+
+inherit
+ CMS_FORM_FIELD
+
+feature {NONE} -- Initialization
+
+ make (a_name: like name)
+ do
+ name := a_name
+ end
+
+feature -- Access
+
+ default_value: detachable READABLE_STRING_32
+
+ size: INTEGER
+ -- Width, in characters, of an element.
+
+ maxlength: INTEGER
+ -- Maximum number of characters allowed in an element.
+
+ disabled: BOOLEAN
+ -- Current element should be disabled?
+
+ input_type: STRING
+ deferred
+ end
+
+feature -- Element change
+
+ set_text_value (s: detachable READABLE_STRING_32)
+ do
+ set_default_value (s)
+ end
+
+ set_size (i: like size)
+ do
+ size := i
+ end
+
+ set_maxlength (i: like maxlength)
+ do
+ maxlength := i
+ end
+
+ set_disabled (b: like disabled)
+ do
+ disabled := b
+ end
+
+ set_value (v: detachable WSF_VALUE)
+ do
+ if attached {WSF_STRING} v as s then
+ set_text_value (s.value)
+ else
+ set_text_value (Void)
+ end
+ end
+
+ set_default_value (v: like default_value)
+ do
+ default_value := v
+ end
+
+feature -- Conversion
+
+ item_to_html (a_theme: CMS_THEME): STRING_8
+ do
+ Result := " 0 then
+ Result.append (" size=%"" + size.out + "%"")
+ end
+ if maxlength > 0 then
+ Result.append (" maxlength=%"" + maxlength.out + "%"")
+ end
+
+ if attached specific_input_attributes_string as s then
+ Result.append_character (' ')
+ Result.append (s)
+ end
+ Result.append ("/>")
+ end
+
+feature {NONE} -- Implementation
+
+ specific_input_attributes_string: detachable STRING_8
+ -- Specific input attributes if any.
+ --| To redefine if needed
+ do
+ end
+
+end
diff --git a/draft/application/cms/src/core/kernel/form/cms_form_item.e b/draft/application/cms/src/core/kernel/form/cms_form_item.e
new file mode 100644
index 00000000..0dafeb60
--- /dev/null
+++ b/draft/application/cms/src/core/kernel/form/cms_form_item.e
@@ -0,0 +1,16 @@
+note
+ description: "Summary description for {CMS_FORM_ITEM}."
+ author: ""
+ date: "$Date$"
+ revision: "$Revision$"
+
+deferred class
+ CMS_FORM_ITEM
+
+feature -- Conversion
+
+ to_html (a_theme: CMS_THEME): STRING_8
+ deferred
+ end
+
+end
diff --git a/draft/application/cms/src/core/kernel/form/cms_form_password_input.e b/draft/application/cms/src/core/kernel/form/cms_form_password_input.e
new file mode 100644
index 00000000..2949ca44
--- /dev/null
+++ b/draft/application/cms/src/core/kernel/form/cms_form_password_input.e
@@ -0,0 +1,26 @@
+note
+ description: "Summary description for {CMS_FORM_PASSWORD_INPUT}."
+ author: ""
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ CMS_FORM_PASSWORD_INPUT
+
+inherit
+ CMS_FORM_INPUT
+ redefine
+ input_type
+ end
+
+create
+ make
+
+feature -- Access
+
+ input_type: STRING
+ once
+ Result := "password"
+ end
+
+end
diff --git a/draft/application/cms/src/core/kernel/form/cms_form_radio_input.e b/draft/application/cms/src/core/kernel/form/cms_form_radio_input.e
new file mode 100644
index 00000000..f5c10a52
--- /dev/null
+++ b/draft/application/cms/src/core/kernel/form/cms_form_radio_input.e
@@ -0,0 +1,46 @@
+note
+ description: "Summary description for {CMS_FORM_RADIO_INPUT}."
+ author: ""
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ CMS_FORM_RADIO_INPUT
+
+inherit
+ CMS_FORM_INPUT
+ redefine
+ specific_input_attributes_string
+ end
+
+create
+ make
+
+feature -- Access
+
+ checked: BOOLEAN
+ -- Current element should be preselected when the page loads
+
+ input_type: STRING = "radio"
+
+feature -- Change
+
+ set_checked (b: like checked)
+ do
+ checked := b
+ end
+
+feature {NONE} -- Implementation
+
+ specific_input_attributes_string: detachable STRING_8
+ -- Specific input attributes if any.
+ -- To redefine if needed
+ do
+ if checked then
+ Result := "checked=%"checked%""
+ end
+ end
+
+invariant
+
+end
diff --git a/draft/application/cms/src/core/kernel/form/cms_form_raw_text.e b/draft/application/cms/src/core/kernel/form/cms_form_raw_text.e
new file mode 100644
index 00000000..bd5df16f
--- /dev/null
+++ b/draft/application/cms/src/core/kernel/form/cms_form_raw_text.e
@@ -0,0 +1,49 @@
+note
+ description: "Summary description for {CMS_FORM_RAW_TEXT}."
+ author: ""
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ CMS_FORM_RAW_TEXT
+
+inherit
+ CMS_FORM_ITEM
+ redefine
+ to_html
+ end
+
+create
+ make
+
+feature {NONE} -- Initialization
+
+ make (a_text: like text)
+ do
+ text := a_text
+ end
+
+feature -- Access
+
+ text: READABLE_STRING_8
+
+feature -- Element change
+
+ set_value (v: detachable WSF_VALUE)
+ do
+ -- Not applicable
+ end
+
+feature -- Conversion
+
+ to_html (a_theme: CMS_THEME): STRING_8
+ do
+ Result := item_to_html (a_theme)
+ end
+
+ item_to_html (a_theme: CMS_THEME): STRING_8
+ do
+ Result := text
+ end
+
+end
diff --git a/draft/application/cms/src/core/kernel/form/cms_form_reset_input.e b/draft/application/cms/src/core/kernel/form/cms_form_reset_input.e
new file mode 100644
index 00000000..d652d66b
--- /dev/null
+++ b/draft/application/cms/src/core/kernel/form/cms_form_reset_input.e
@@ -0,0 +1,20 @@
+note
+ description: "Summary description for {CMS_FORM_RESET_INPUT}."
+ author: ""
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ CMS_FORM_RESET_INPUT
+
+inherit
+ CMS_FORM_INPUT
+
+create
+ make
+
+feature -- Access
+
+ input_type: STRING = "reset"
+
+end
diff --git a/draft/application/cms/src/core/kernel/form/cms_form_select.e b/draft/application/cms/src/core/kernel/form/cms_form_select.e
new file mode 100644
index 00000000..e8b5375b
--- /dev/null
+++ b/draft/application/cms/src/core/kernel/form/cms_form_select.e
@@ -0,0 +1,116 @@
+note
+ description: "Summary description for {CMS_FORM_SELECT}."
+ author: ""
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ CMS_FORM_SELECT
+
+inherit
+ CMS_FORM_FIELD
+
+create
+ make
+
+feature {NONE} -- Initialization
+
+ make (a_name: like name)
+ do
+ name := a_name
+ create options.make (0)
+ end
+
+feature -- Access
+
+ options: ARRAYED_LIST [CMS_FORM_SELECT_OPTION]
+
+feature -- Element change
+
+ set_text_value (v: detachable like {CMS_FORM_SELECT_OPTION}.value)
+ local
+ opt: CMS_FORM_SELECT_OPTION
+ l_found: BOOLEAN
+ do
+ if v /= Void then
+ across
+ options as o
+ loop
+ -- FIXME: unicode ...
+ if o.item.value.same_string (v) then
+ l_found := True
+ o.item.set_is_selected (True)
+ else
+ o.item.set_is_selected (False)
+ end
+ end
+ if not l_found then
+ create opt.make (v, Void)
+ opt.set_is_selected (True)
+ add_option (opt)
+ end
+ else
+ across
+ options as o
+ loop
+ o.item.set_is_selected (False)
+ end
+ end
+ end
+
+ set_value (v: detachable WSF_VALUE)
+ do
+ if attached {WSF_STRING} v as s then
+ set_text_value (s.value)
+ else
+ set_text_value (Void)
+ end
+ end
+
+ add_option (opt: CMS_FORM_SELECT_OPTION)
+ do
+ options.force (opt)
+ end
+
+feature -- Conversion
+
+ item_to_html (a_theme: CMS_THEME): STRING_8
+ local
+ l_is_already_selected: BOOLEAN
+ h: detachable STRING_8
+ do
+ Result := "")
+ else
+ Result.append ("/>")
+ end
+
+ across
+ options as o
+ loop
+ Result.append ("%N")
+ if attached o.item.description as d then
+ if h = Void then
+ create h.make_empty
+ end
+ h.append ("
"+ o.item.text +":"+ d + "
")
+ end
+ end
+ Result.append ("%N")
+ if h /= Void then
+ Result.append ("
" + h + "
%N")
+ end
+ end
+
+end
diff --git a/draft/application/cms/src/core/kernel/form/cms_form_select_option.e b/draft/application/cms/src/core/kernel/form/cms_form_select_option.e
new file mode 100644
index 00000000..441142a0
--- /dev/null
+++ b/draft/application/cms/src/core/kernel/form/cms_form_select_option.e
@@ -0,0 +1,50 @@
+note
+ description : "Objects that ..."
+ author : "$Author$"
+ date : "$Date$"
+ revision : "$Revision$"
+
+class
+ CMS_FORM_SELECT_OPTION
+
+create
+ make
+
+feature {NONE} -- Initialization
+
+ make (a_value: like value; a_text: detachable like text)
+ -- Initialize `Current'.
+ do
+ value := a_value
+ if a_text = Void then
+ text := a_value
+ else
+ text := a_text
+ end
+ end
+
+feature -- Status
+
+ is_selected: BOOLEAN
+
+feature -- Access
+
+ value: READABLE_STRING_8
+
+ text: READABLE_STRING_8
+
+ description: detachable READABLE_STRING_8
+
+feature -- Change
+
+ set_is_selected (b: like is_selected)
+ do
+ is_selected := b
+ end
+
+ set_description (d: like description)
+ do
+ description := d
+ end
+
+end
diff --git a/draft/application/cms/src/core/kernel/form/cms_form_submit_input.e b/draft/application/cms/src/core/kernel/form/cms_form_submit_input.e
new file mode 100644
index 00000000..1eac86ec
--- /dev/null
+++ b/draft/application/cms/src/core/kernel/form/cms_form_submit_input.e
@@ -0,0 +1,20 @@
+note
+ description: "Summary description for {CMS_FORM_INPUT}."
+ author: ""
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ CMS_FORM_SUBMIT_INPUT
+
+inherit
+ CMS_FORM_INPUT
+
+create
+ make
+
+feature -- Access
+
+ input_type: STRING = "submit"
+
+end
diff --git a/draft/application/cms/src/core/kernel/form/cms_form_text_input.e b/draft/application/cms/src/core/kernel/form/cms_form_text_input.e
new file mode 100644
index 00000000..26221cae
--- /dev/null
+++ b/draft/application/cms/src/core/kernel/form/cms_form_text_input.e
@@ -0,0 +1,20 @@
+note
+ description: "Summary description for {CMS_FORM_TEXT_INPUT}."
+ author: ""
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ CMS_FORM_TEXT_INPUT
+
+inherit
+ CMS_FORM_INPUT
+
+create
+ make
+
+feature -- Access
+
+ input_type: STRING = "text"
+
+end
diff --git a/draft/application/cms/src/core/kernel/form/cms_form_textarea.e b/draft/application/cms/src/core/kernel/form/cms_form_textarea.e
new file mode 100644
index 00000000..f64bb4dd
--- /dev/null
+++ b/draft/application/cms/src/core/kernel/form/cms_form_textarea.e
@@ -0,0 +1,85 @@
+note
+ description: "Summary description for {CMS_FORM_INPUT}."
+ author: ""
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ CMS_FORM_TEXTAREA
+
+inherit
+ CMS_FORM_FIELD
+
+create
+ make
+
+feature {NONE} -- Initialization
+
+ make (a_name: like name)
+ do
+ name := a_name
+ end
+
+feature -- Access
+
+ default_value: detachable READABLE_STRING_GENERAL
+
+ rows: INTEGER
+
+ cols: INTEGER
+
+feature -- Element change
+
+ set_rows (i: like rows)
+ do
+ rows := i
+ end
+
+ set_cols (i: like cols)
+ do
+ cols := i
+ end
+
+ set_text_value (s: like default_value)
+ do
+ set_default_value (s)
+ end
+
+ set_value (v: detachable WSF_VALUE)
+ do
+ if attached {WSF_STRING} v as s then
+ set_text_value (s.value)
+ else
+ set_text_value (Void)
+ end
+ end
+
+ set_default_value (v: like default_value)
+ do
+ default_value := v
+ end
+
+feature -- Conversion
+
+ item_to_html (a_theme: CMS_THEME): STRING_8
+ do
+ Result := "")
+ end
+
+end
diff --git a/draft/application/cms/src/core/kernel/link/cms_external_link.e b/draft/application/cms/src/core/kernel/link/cms_external_link.e
new file mode 100644
index 00000000..a73d9638
--- /dev/null
+++ b/draft/application/cms/src/core/kernel/link/cms_external_link.e
@@ -0,0 +1,36 @@
+note
+ description: "Summary description for {CMS_EXTERNAL_MENU}."
+ author: ""
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ CMS_EXTERNAL_LINK
+
+inherit
+ CMS_LINK
+
+create
+ make
+
+feature {NONE} -- Initialization
+
+ make (a_title: like title; a_location: like location)
+ do
+ title := a_title
+ location := a_location
+ end
+
+feature -- Status report
+
+ is_active: BOOLEAN = False
+
+ is_expanded: BOOLEAN = False
+
+ is_expandable: BOOLEAN = False
+
+ children: detachable LIST [CMS_LINK]
+ do
+ end
+
+end
diff --git a/draft/application/cms/src/core/kernel/link/cms_link.e b/draft/application/cms/src/core/kernel/link/cms_link.e
new file mode 100644
index 00000000..c9b37941
--- /dev/null
+++ b/draft/application/cms/src/core/kernel/link/cms_link.e
@@ -0,0 +1,43 @@
+note
+ description: "Summary description for {CMS_MENU}."
+ author: ""
+ date: "$Date$"
+ revision: "$Revision$"
+
+deferred class
+ CMS_LINK
+
+inherit
+ REFACTORING_HELPER
+
+feature -- Access
+
+ title: READABLE_STRING_32
+
+ location: READABLE_STRING_8
+
+ options: detachable CMS_API_OPTIONS
+
+feature -- status report
+
+ is_active: BOOLEAN
+ deferred
+ end
+
+ is_expanded: BOOLEAN
+ deferred
+ end
+
+ is_expandable: BOOLEAN
+ deferred
+ end
+
+feature -- Query
+
+ parent: detachable CMS_LINK
+
+ children: detachable LIST [CMS_LINK]
+ deferred
+ end
+
+end
diff --git a/draft/application/cms/src/core/kernel/link/cms_local_link.e b/draft/application/cms/src/core/kernel/link/cms_local_link.e
new file mode 100644
index 00000000..152c89ab
--- /dev/null
+++ b/draft/application/cms/src/core/kernel/link/cms_local_link.e
@@ -0,0 +1,72 @@
+note
+ description: "Summary description for {CMS_LOCAL_MENU}."
+ author: ""
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ CMS_LOCAL_LINK
+
+inherit
+ CMS_LINK
+
+create
+ make
+
+feature {NONE} -- Initialization
+
+ make (a_title: detachable like title; a_location: like location)
+ do
+ if a_title /= Void then
+ title := a_title
+ else
+ title := a_location
+ end
+ location := a_location
+ end
+
+feature -- Status report
+
+ is_active: BOOLEAN
+
+ is_expanded: BOOLEAN
+
+ is_expandable: BOOLEAN
+
+ permission_arguments: detachable ITERABLE [STRING]
+
+ children: detachable LIST [CMS_LINK]
+
+feature -- Element change
+
+ set_children (lst: like children)
+ do
+ children := lst
+ end
+
+ set_expanded (b: like is_expanded)
+ do
+ is_expanded := b
+ end
+
+ set_expandable (b: like is_expandable)
+ do
+ is_expandable := b
+ end
+
+ get_is_active (req: WSF_REQUEST)
+ do
+ is_active := req.path_info.same_string (location)
+ end
+
+ set_permission_arguments (args: ITERABLE [STRING])
+ do
+ permission_arguments := args
+ end
+
+ set_options (opts: like options)
+ do
+ options := opts
+ end
+
+end
diff --git a/draft/application/cms/src/core/kernel/link/cms_menu.e b/draft/application/cms/src/core/kernel/link/cms_menu.e
new file mode 100644
index 00000000..67b6bbb2
--- /dev/null
+++ b/draft/application/cms/src/core/kernel/link/cms_menu.e
@@ -0,0 +1,73 @@
+note
+ description: "Summary description for {CMS_MENU}."
+ author: ""
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ CMS_MENU
+
+inherit
+ ITERABLE [CMS_LINK]
+
+create
+ make,
+ make_with_title
+
+feature {NONE} -- Initialization
+
+ make (a_name: like name; n: INTEGER)
+ do
+ name := a_name
+ create items.make (n)
+ end
+
+ make_with_title (a_name: like name; a_title: READABLE_STRING_32; n: INTEGER)
+ do
+ make (a_name, n)
+ set_title (a_title)
+ end
+
+feature -- Access
+
+ name: READABLE_STRING_8
+
+ title: detachable READABLE_STRING_32
+
+ items: ARRAYED_LIST [CMS_LINK]
+
+ extend (lnk: CMS_LINK)
+ do
+ items.extend (lnk)
+ end
+
+ remove (lnk: CMS_LINK)
+ do
+ items.prune_all (lnk)
+ end
+
+feature -- status report
+
+ is_empty: BOOLEAN
+ do
+ Result := items.is_empty
+ end
+
+feature -- Element change
+
+ set_title (t: like title)
+ do
+ title := t
+ end
+
+feature -- Access
+
+ new_cursor: ITERATION_CURSOR [CMS_LINK]
+ -- Fresh cursor associated with current structure
+ do
+ Result := items.new_cursor
+ end
+
+invariant
+
+end
diff --git a/draft/application/cms/src/core/kernel/link/cms_menu_system.e b/draft/application/cms/src/core/kernel/link/cms_menu_system.e
new file mode 100644
index 00000000..095c70a6
--- /dev/null
+++ b/draft/application/cms/src/core/kernel/link/cms_menu_system.e
@@ -0,0 +1,88 @@
+note
+ description: "Summary description for {CMS_MENU_SYSTEM}."
+ author: ""
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ CMS_MENU_SYSTEM
+
+inherit
+ ITERABLE [CMS_MENU]
+
+create
+ make
+
+feature {NONE} -- Initialization
+
+ make
+ do
+ create items.make (5)
+ force (create {CMS_MENU}.make ("main-menu", 3))
+ force (create {CMS_MENU}.make_with_title ("management", "Management", 3))
+ force (create {CMS_MENU}.make_with_title ("navigation", "Navigation", 3))
+ force (create {CMS_MENU}.make_with_title ("user", "User", 3))
+ end
+
+feature -- Access
+
+ item (n: like {CMS_MENU}.name): CMS_MENU
+ local
+ m: detachable CMS_MENU
+ do
+ m := items.item (n)
+ if m = Void then
+ create m.make (n, 3)
+ force (m)
+ end
+ Result := m
+ end
+
+ main_menu: CMS_MENU
+ do
+ Result := item ("main-menu")
+ end
+
+ management_menu: CMS_MENU
+ do
+ Result := item ("management")
+ end
+
+ navigation_menu: CMS_MENU
+ do
+ Result := item ("navigation")
+ end
+
+ user_menu: CMS_MENU
+ do
+ Result := item ("user")
+ end
+
+ primary_tabs: CMS_MENU
+ do
+ Result := item ("primary-tabs")
+ end
+
+feature -- Change
+
+ force (m: CMS_MENU)
+ do
+ items.force (m, m.name)
+ end
+
+feature -- Access
+
+ new_cursor: ITERATION_CURSOR [CMS_MENU]
+ -- Fresh cursor associated with current structure
+ do
+ Result := items.new_cursor
+ end
+
+feature {NONE} -- Implementation
+
+ items: HASH_TABLE [CMS_MENU, like {CMS_MENU}.name]
+-- items: ARRAYED_LIST [CMS_MENU]
+
+invariant
+
+end
diff --git a/draft/application/cms/src/core/kernel/log/cms_log.e b/draft/application/cms/src/core/kernel/log/cms_log.e
new file mode 100644
index 00000000..7f71460c
--- /dev/null
+++ b/draft/application/cms/src/core/kernel/log/cms_log.e
@@ -0,0 +1,152 @@
+note
+ description: "Summary description for {CMS_LOG}."
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ CMS_LOG
+
+create
+ make
+
+feature {NONE} -- Initialization
+
+ make (a_category: like category; a_message: like message; a_level: like level; a_date: detachable like date)
+ do
+ category := a_category
+ message := a_message
+ set_level (a_level)
+ if a_date = Void then
+ create date.make_now_utc
+ else
+ date := a_date
+ end
+ end
+
+ make_with_id (a_id: like id; a_category: like category; a_message: like message; a_level: like level; a_date: detachable like date)
+ do
+ id := a_id
+ make (a_category, a_message, a_level, a_date)
+ end
+
+feature -- Access
+
+ id: INTEGER
+ -- Unique identifier of Current.
+
+ category: READABLE_STRING_8
+ -- Associated title (optional).
+
+ message: READABLE_STRING_8
+ -- Log message
+
+ level: INTEGER
+ -- Severity level
+
+ level_name: STRING
+ do
+ Result := level_to_string (level)
+ end
+
+ info: detachable READABLE_STRING_8
+
+ link: detachable CMS_LINK
+
+ date: DATE_TIME
+
+feature -- status report
+
+ has_id: BOOLEAN
+ do
+ Result := id > 0
+ end
+
+feature -- Change
+
+ set_id (a_id: like id)
+ require
+ not has_id
+ do
+ id := a_id
+ end
+
+ set_level (a_level: like level)
+ do
+ if a_level = 0 then
+ level := level_notice
+ else
+ level := a_level
+ end
+ end
+
+ set_link (lnk: like link)
+ do
+ link := lnk
+ end
+
+ set_info (inf: like info)
+ do
+ info := inf
+ end
+
+feature -- Conversion
+
+ to_html (a_theme: CMS_THEME): STRING_8
+ do
+ Result := "
"
+ Result.append ("
")
+ Result.append (message)
+ if attached info as l_info then
+ Result.append (" Information: ")
+ Result.append (l_info)
+ end
+ if attached link as lnk then
+ Result.append (" Associated link: ")
+ Result.append (a_theme.link (lnk.title, lnk.location, lnk.options))
+ end
+ Result.append ("
")
+ end
+
+ set_main_content (b)
+ end
+
+end
diff --git a/draft/application/cms/src/core/modules/admin/admin_module.e b/draft/application/cms/src/core/modules/admin/admin_module.e
new file mode 100644
index 00000000..a669ab28
--- /dev/null
+++ b/draft/application/cms/src/core/modules/admin/admin_module.e
@@ -0,0 +1,100 @@
+note
+ description: "Summary description for {ADMIN_MODULE}."
+ author: ""
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ ADMIN_MODULE
+
+inherit
+ CMS_MODULE
+
+ CMS_HOOK_MENU_ALTER
+
+create
+ make
+
+feature {NONE} -- Initialization
+
+ make (a_service: like service)
+ do
+ service := a_service
+ name := "admin"
+ version := "1.0"
+ description := "Set of service to administrate the site"
+ package := "core"
+
+ enable
+ end
+
+feature {CMS_SERVICE} -- Registration
+
+ service: CMS_SERVICE
+
+ register (a_service: CMS_SERVICE)
+ do
+ a_service.map_uri ("/admin/", agent handle_admin)
+ a_service.map_uri ("/admin/users/", agent handle_admin_users)
+ a_service.map_uri ("/admin/blocks/", agent handle_admin_blocks)
+ a_service.map_uri ("/admin/modules/", agent handle_admin_modules)
+ a_service.map_uri ("/admin/logs/", agent handle_admin_logs)
+ a_service.map_uri_template ("/admin/log/{log-id}", agent handle_admin_log_view)
+
+ a_service.add_menu_alter_hook (Current)
+ end
+
+feature -- Hooks
+
+ menu_alter (a_menu_system: CMS_MENU_SYSTEM; a_execution: CMS_EXECUTION)
+ local
+ lnk: CMS_LOCAL_LINK
+ do
+ create lnk.make ("Administer", "/admin/")
+ lnk.set_permission_arguments (<<"administer">>)
+ a_menu_system.management_menu.extend (lnk)
+ end
+
+ links: HASH_TABLE [CMS_MODULE_LINK, STRING]
+ -- Link indexed by path
+ local
+-- lnk: CMS_MODULE_LINK
+ do
+ create Result.make (3)
+-- create lnk.make ("Date/time demo")
+-- lnk.set_callback (agent process_date_time_demo, <<"arg">>)
+-- Result["/demo/date/{arg}"] := lnk
+ end
+
+ handle_admin (req: WSF_REQUEST; res: WSF_RESPONSE)
+ do
+ (create {ADMIN_CMS_EXECUTION}.make (req, res, service)).execute
+ end
+
+ handle_admin_users (req: WSF_REQUEST; res: WSF_RESPONSE)
+ do
+ (create {ADMIN_USERS_CMS_EXECUTION}.make (req, res, service)).execute
+ end
+
+ handle_admin_blocks (req: WSF_REQUEST; res: WSF_RESPONSE)
+ do
+ (create {ADMIN_BLOCKS_CMS_EXECUTION}.make (req, res, service)).execute
+ end
+
+ handle_admin_modules (req: WSF_REQUEST; res: WSF_RESPONSE)
+ do
+ (create {ADMIN_MODULES_CMS_EXECUTION}.make (req, res, service)).execute
+ end
+
+ handle_admin_logs (req: WSF_REQUEST; res: WSF_RESPONSE)
+ do
+ (create {ADMIN_LOGS_CMS_EXECUTION}.make (req, res, service)).execute
+ end
+
+ handle_admin_log_view (req: WSF_REQUEST; res: WSF_RESPONSE)
+ do
+ (create {LOG_VIEW_CMS_EXECUTION}.make (req, res, service)).execute
+ end
+
+
+end
diff --git a/draft/application/cms/src/core/modules/admin/admin_modules_cms_execution.e b/draft/application/cms/src/core/modules/admin/admin_modules_cms_execution.e
new file mode 100644
index 00000000..c674b553
--- /dev/null
+++ b/draft/application/cms/src/core/modules/admin/admin_modules_cms_execution.e
@@ -0,0 +1,57 @@
+note
+ description: "Summary description for {ADMIN_MODULES_CMS_EXECUTION}."
+ author: ""
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ ADMIN_MODULES_CMS_EXECUTION
+
+inherit
+ CMS_EXECUTION
+
+create
+ make
+
+feature -- Execution
+
+ process
+ -- Computed response message.
+ local
+ b: STRING_8
+ do
+ set_title ("Modules")
+ -- check Permission !!!
+ create b.make_empty
+ if has_permission ("administrate modules") then
+ b.append ("
")
+ across
+ service.modules as m
+ loop
+ if attached m.item as mod then
+ if mod.is_enabled then
+ b.append ("
")
+ else
+ b.append ("
")
+ end
+ b.append ("" + mod.name + " (version:" + mod.version + ")")
+ b.append (" package=" + mod.package)
+ if mod.is_enabled then
+ b.append (" [disable]")
+ else
+ b.append (" [enable]")
+ end
+ b.append ("
" + mod.description + "
")
+
+ b.append ("
")
+ end
+ end
+ b.append ("
")
+ else
+ b.append ("
Access denied
")
+ end
+
+ set_main_content (b)
+ end
+
+end
diff --git a/draft/application/cms/src/core/modules/admin/admin_users_cms_execution.e b/draft/application/cms/src/core/modules/admin/admin_users_cms_execution.e
new file mode 100644
index 00000000..e785bdbd
--- /dev/null
+++ b/draft/application/cms/src/core/modules/admin/admin_users_cms_execution.e
@@ -0,0 +1,56 @@
+note
+ description: "Summary description for {ADMIN_USERS_CMS_EXECUTION}."
+ author: ""
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ ADMIN_USERS_CMS_EXECUTION
+
+inherit
+ CMS_EXECUTION
+
+create
+ make
+
+feature -- Execution
+
+ process
+ -- Computed response message.
+ local
+ b: STRING_8
+ do
+ set_title ("Users")
+ -- check Permission !!!
+ create b.make_empty
+ if has_permission ("administrate users") then
+
+ b.append ("
")
+ across
+ service.storage.all_users as c
+ loop
+ if attached c.item as u then
+ b.append ("
")
+ b.append ("" + user_link (u) + " (id=" + u.id.out + ")")
+ if attached u.email as l_email then
+ b.append (" ["+ l_email +"]")
+ end
+ if attached u.creation_date as dt then
+ b.append (" - created: " + dt.out)
+ end
+ if attached u.last_login_date as dt then
+ b.append (" - last signed: " + dt.out)
+ end
+
+ b.append ("
")
+ end
+ end
+ b.append ("
")
+ else
+ b.append ("
Access denied
")
+ end
+
+ set_main_content (b)
+ end
+
+end
diff --git a/draft/application/cms/src/core/modules/admin/log_view_cms_execution.e b/draft/application/cms/src/core/modules/admin/log_view_cms_execution.e
new file mode 100644
index 00000000..a9fe8718
--- /dev/null
+++ b/draft/application/cms/src/core/modules/admin/log_view_cms_execution.e
@@ -0,0 +1,39 @@
+note
+ description: "Summary description for {LOG_VIEW_CMS_EXECUTION}."
+ author: ""
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ LOG_VIEW_CMS_EXECUTION
+
+inherit
+ CMS_EXECUTION
+
+create
+ make
+
+feature -- Execution
+
+ process
+ -- Computed response message.
+ local
+ b: STRING_8
+ do
+ if attached {WSF_STRING} request.path_parameter ("log-id") as p_id and then p_id.is_integer then
+ create b.make_empty
+
+ if attached storage.log (p_id.integer_value) as l_log then
+ set_title ("Log #" + l_log.id.out)
+ b.append (l_log.to_html (theme))
+ else
+ set_title ("Log [" + p_id.value + "] does not exists!")
+ end
+ set_main_content (b)
+ else
+ set_redirection ("/admin/logs")
+ set_main_content ("not found")
+ end
+ end
+
+end
diff --git a/draft/application/cms/src/core/modules/cms_module.e b/draft/application/cms/src/core/modules/cms_module.e
new file mode 100644
index 00000000..025e6c8a
--- /dev/null
+++ b/draft/application/cms/src/core/modules/cms_module.e
@@ -0,0 +1,57 @@
+note
+ description: "Summary description for {WSF_CMS_MODULE}."
+ author: ""
+ date: "$Date$"
+ revision: "$Revision$"
+
+deferred class
+ CMS_MODULE
+
+feature -- Access
+
+ is_enabled: BOOLEAN
+
+ name: STRING
+
+ description: STRING
+
+ package: STRING
+
+ version: STRING
+
+feature {CMS_SERVICE} -- Registration
+
+ register (a_service: CMS_SERVICE)
+ deferred
+ end
+
+feature -- Settings
+
+ enable
+ do
+ is_enabled := True
+ end
+
+ disable
+ do
+ is_enabled := False
+ end
+
+feature -- Hooks
+
+ help_text (a_path: STRING): STRING
+ do
+ Result := ""
+ end
+
+ permissions: LIST [TUPLE [title: detachable STRING; description: detachable STRING]]
+ do
+ create {ARRAYED_LIST [like permissions.item]} Result.make (0)
+ end
+
+ links: HASH_TABLE [CMS_MODULE_LINK, STRING]
+ -- Link indexed by path
+ deferred
+ end
+
+end
diff --git a/draft/application/cms/src/core/modules/cms_module_link.e b/draft/application/cms/src/core/modules/cms_module_link.e
new file mode 100644
index 00000000..490db5a7
--- /dev/null
+++ b/draft/application/cms/src/core/modules/cms_module_link.e
@@ -0,0 +1,50 @@
+note
+ description: "Summary description for {CMS_MODULE_LINK}."
+ author: ""
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ CMS_MODULE_LINK
+
+create
+ make
+
+feature {NONE} -- Initialization
+
+ make (a_title: like title)
+ do
+ title := a_title
+ end
+
+feature -- Access
+
+ title: STRING_32
+
+ description: detachable STRING_32
+
+ callback: detachable PROCEDURE [ANY, TUPLE [cms: detachable CMS_EXECUTION; args: detachable ITERABLE [STRING]]]
+ callback_arguments: detachable ITERABLE [STRING]
+
+ permission: detachable LIST [STRING]
+
+ parent: detachable CMS_MODULE_LINK
+
+feature -- Element change
+
+ set_callback (cb: like callback; args: like callback_arguments)
+ do
+ callback := cb
+ callback_arguments := args
+ end
+
+feature -- Execution
+
+ execute
+ do
+ if attached callback as cb then
+ cb.call ([Void, callback_arguments])
+ end
+ end
+
+end
diff --git a/draft/application/cms/src/core/modules/node/cms_page.e b/draft/application/cms/src/core/modules/node/cms_page.e
new file mode 100644
index 00000000..79cb4033
--- /dev/null
+++ b/draft/application/cms/src/core/modules/node/cms_page.e
@@ -0,0 +1,75 @@
+note
+ description: "Summary description for {CMS_PAGE}."
+ author: ""
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ CMS_PAGE
+
+inherit
+ CMS_NODE
+
+create
+ make_new,
+ make
+
+feature {NONE} -- Initialization
+
+ make (a_id: like id; a_title: like title; dt: like creation_date)
+ require
+ a_id > 0
+ do
+ set_id (a_id)
+ creation_date := dt
+ modification_date := dt
+ title := a_title
+ initialize
+ end
+
+ make_new (a_title: like title)
+ do
+ title := a_title
+ create creation_date.make_now_utc
+ modification_date := creation_date
+ initialize
+ end
+
+ initialize
+ do
+ format := formats.default_format
+ end
+
+feature -- Access
+
+ title: detachable READABLE_STRING_32
+
+ body: detachable READABLE_STRING_8
+
+ format: CMS_FORMAT
+
+ content_type_name: STRING = "page"
+
+feature -- Change
+
+ set_title (a_title: like title)
+ -- Set `title' to `a_title'
+ do
+ title := a_title
+ end
+
+ set_body (a_body: like body; a_format: like format)
+ -- Set `body' and associated `format'
+ do
+ body := a_body
+ format := a_format
+ end
+
+feature -- Conversion
+
+-- to_html (a_theme: CMS_THEME): STRING_8
+-- do
+-- Result := Precursor (a_theme)
+-- end
+
+end
diff --git a/draft/application/cms/src/core/modules/node/cms_page_content_type.e b/draft/application/cms/src/core/modules/node/cms_page_content_type.e
new file mode 100644
index 00000000..a6fc6ebb
--- /dev/null
+++ b/draft/application/cms/src/core/modules/node/cms_page_content_type.e
@@ -0,0 +1,177 @@
+note
+ description: "Summary description for {CMS_PAGE_CONTENT_TYPE}."
+ author: ""
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ CMS_PAGE_CONTENT_TYPE
+
+inherit
+ CMS_CONTENT_TYPE
+
+create
+ make
+
+feature {NONE} -- Initialization
+
+ make
+ do
+ create {ARRAYED_LIST [like available_formats.item]} available_formats.make (1)
+ available_formats.extend (formats.plain_text)
+ available_formats.extend (formats.filtered_html)
+ available_formats.extend (formats.full_html)
+ end
+
+feature -- Access
+
+ name: STRING = "page"
+
+ title: STRING = "basic page"
+
+ description: detachable READABLE_STRING_8
+ -- Optional description
+ do
+ Result := "Use basic pages for your static content, such as an 'About us' page."
+ end
+
+ available_formats: LIST [CMS_FORMAT]
+
+feature -- Factory
+
+ fill_edit_form (f: CMS_FORM; a_node: detachable CMS_NODE)
+ local
+ ti: CMS_FORM_TEXT_INPUT
+ fset: CMS_FORM_FIELD_SET
+ ta: CMS_FORM_TEXTAREA
+ tselect: CMS_FORM_SELECT
+ opt: CMS_FORM_SELECT_OPTION
+ do
+ create ti.make ("title")
+ ti.set_label ("Title")
+ ti.set_size (70)
+ if a_node /= Void then
+ ti.set_text_value (a_node.title)
+ end
+ ti.set_is_required (True)
+ f.extend (ti)
+
+ f.extend (create {CMS_FORM_RAW_TEXT}.make (" "))
+
+ create ta.make ("body")
+ ta.set_rows (10)
+ ta.set_cols (70)
+ if a_node /= Void then
+ ta.set_text_value (a_node.body)
+ end
+-- ta.set_label ("Body")
+ ta.set_description ("This is the main content")
+ ta.set_is_required (False)
+
+ create fset.make
+ fset.set_legend ("Body")
+ fset.extend (ta)
+
+
+ fset.extend (create {CMS_FORM_RAW_TEXT}.make (" "))
+
+ create tselect.make ("format")
+ tselect.set_label ("Body's format")
+ tselect.set_is_required (True)
+ across
+ available_formats as c
+ loop
+ create opt.make (c.item.name, c.item.title)
+ if attached c.item.help as f_help then
+ opt.set_description ("
" + f_help + "
")
+ end
+ tselect.add_option (opt)
+ end
+ if a_node /= Void then
+ tselect.set_text_value (a_node.format.name)
+ end
+
+ fset.extend (tselect)
+
+ f.extend (fset)
+
+ end
+
+ change_node (a_execution: CMS_EXECUTION; fd: CMS_FORM_DATA; a_node: like new_node)
+ local
+ b: detachable READABLE_STRING_8
+ f: detachable CMS_FORMAT
+ do
+ if attached fd.integer_item ("id") as l_id and then l_id > 0 then
+ check a_node.id = l_id end
+ end
+ if attached fd.string_item ("title") as l_title then
+ a_node.set_title (l_title)
+ end
+
+ if attached fd.string_item ("body") as l_body then
+ b := l_body
+ end
+ if attached fd.string_item ("format") as s_format and then attached formats.format (s_format) as f_format then
+ f := f_format
+ elseif a_node /= Void then
+ f := a_node.format
+ else
+ f := formats.default_format
+ end
+ if b /= Void then
+ a_node.set_body (b, f)
+ end
+ end
+
+ new_node (a_execution: CMS_EXECUTION; fd: CMS_FORM_DATA; a_node: detachable like new_node): CMS_PAGE
+ --
+ local
+ b: detachable READABLE_STRING_8
+ f: detachable CMS_FORMAT
+ l_node: detachable like new_node
+ do
+ l_node := a_node
+ if attached fd.integer_item ("id") as l_id and then l_id > 0 then
+ if l_node /= Void then
+ check l_node.id = l_id end
+ else
+ if attached {like new_node} a_execution.service.storage.node (l_id) as n then
+ l_node := n
+ else
+ -- FIXME: Error
+ end
+ end
+ end
+ if attached fd.string_item ("title") as l_title then
+ if l_node = Void then
+ create l_node.make_new (l_title)
+ else
+ l_node.set_title (l_title)
+ end
+ else
+ if l_node = Void then
+ create l_node.make_new ("...")
+ end
+ end
+ l_node.set_author (a_execution.user)
+
+ if attached fd.string_item ("body") as l_body then
+ b := l_body
+ end
+ if attached fd.string_item ("format") as s_format and then attached formats.format (s_format) as f_format then
+ f := f_format
+ elseif a_node /= Void then
+ f := a_node.format
+ else
+ f := formats.default_format
+ end
+ if b /= Void then
+ l_node.set_body (b, f)
+ end
+ Result := l_node
+ end
+
+invariant
+
+end
diff --git a/draft/application/cms/src/core/modules/node/node_add_cms_execution.e b/draft/application/cms/src/core/modules/node/node_add_cms_execution.e
new file mode 100644
index 00000000..fafd5eee
--- /dev/null
+++ b/draft/application/cms/src/core/modules/node/node_add_cms_execution.e
@@ -0,0 +1,143 @@
+note
+ description: "[
+ ]"
+
+class
+ NODE_ADD_CMS_EXECUTION
+
+inherit
+ CMS_EXECUTION
+
+create
+ make
+
+feature -- Execution
+
+ process
+ -- Computed response message.
+ local
+ b: STRING_8
+ f: like edit_form
+ fd: detachable CMS_FORM_DATA
+ l_preview: BOOLEAN
+ l_format: detachable CMS_FORMAT
+ do
+ create b.make_empty
+ if attached non_empty_string_path_parameter ("type") as s_type then
+ if attached service.content_type (s_type) as l_type then
+ f := edit_form (Void, request.path_info, "add-" + l_type.name, l_type)
+ if request.is_post_request_method then
+ create fd.make (request, f)
+ l_preview := attached {WSF_STRING} fd.item ("op") as l_op and then l_op.same_string ("Preview")
+ end
+
+ set_title ("Create " + l_type.title)
+ if has_permission ("create " + l_type.name) then
+
+ if fd /= Void and l_preview then
+ b.append ("Preview
")
+ if attached fd.string_item ("format") as s_format and then attached formats.format (s_format) as f_format then
+ l_format := f_format
+ end
+ if attached fd.string_item ("title") as l_title then
+ b.append ("Title:
" + html_encoded (l_title) + "
")
+ end
+ if attached fd.string_item ("body") as l_body then
+ b.append ("Body:
")
+ if l_format /= Void then
+ b.append (l_format.to_html (l_body))
+ else
+ b.append (html_encoded (l_body))
+ end
+ b.append ("
")
+ end
+ b.append ("
")
+ end
+
+ if fd /= Void and then fd.is_valid and not l_preview then
+ across
+ fd as c
+ loop
+ b.append ("
" + html_encoded (c.key) + "=")
+ if attached c.item as v then
+ b.append (html_encoded (v.string_representation))
+ end
+ b.append ("
")
+ end
+ if attached l_type.new_node (Current, fd, Void) as l_node then
+ service.storage.save_node (l_node)
+ if attached user as u then
+ service.log ("node", "User %"" + user_link (u) + "%" created node " + link (l_type.name +" #" + l_node.id.out, "/node/" + l_node.id.out , Void), 0, node_local_link (l_node))
+ else
+ service.log ("node", "Anonymous created node "+ l_type.name +" #" + l_node.id.out, 0, node_local_link (l_node))
+ end
+ add_success_message ("Node #" + l_node.id.out + " saved.")
+ set_redirection (node_url (l_node))
+ end
+ -- Creation ...
+ else
+ if fd /= Void then
+ if not fd.is_valid then
+ report_form_errors (fd)
+ end
+ fd.apply_to_associated_form
+ end
+ b.append (f.to_html (theme))
+ end
+ else
+ set_title ("Access denied")
+ end
+ else
+ set_title ("Unknown content type [" + s_type + "]")
+ end
+ else
+ set_title ("Create new content ...")
+ b.append ("
")
+ across
+ service.content_types as c
+ loop
+ if has_permission ("create " + c.item.name) then
+ b.append ("
" + link (c.item.name, "/node/add/" + c.item.name, Void))
+ if attached c.item.description as d then
+ b.append ("
" + d + "
")
+ end
+ b.append ("
")
+ end
+ end
+ b.append ("
")
+ end
+ set_main_content (b)
+ end
+
+ edit_form (a_node: detachable CMS_NODE; a_url: READABLE_STRING_8; a_name: STRING; a_type: CMS_CONTENT_TYPE): CMS_FORM
+ local
+ f: CMS_FORM
+ ts: CMS_FORM_SUBMIT_INPUT
+ th: CMS_FORM_HIDDEN_INPUT
+ do
+ create f.make (a_url, a_name)
+
+ create th.make ("node-id")
+ if a_node /= Void then
+ th.set_text_value (a_node.id.out)
+ else
+ th.set_text_value ("0")
+ end
+ f.extend (th)
+
+ a_type.fill_edit_form (f, a_node)
+
+ f.extend (create {CMS_FORM_RAW_TEXT}.make (" "))
+
+ create ts.make ("op")
+ ts.set_default_value ("Save")
+ f.extend (ts)
+
+ create ts.make ("op")
+ ts.set_default_value ("Preview")
+ f.extend (ts)
+
+ Result := f
+ end
+
+end
diff --git a/draft/application/cms/src/core/modules/node/node_edit_cms_execution.e b/draft/application/cms/src/core/modules/node/node_edit_cms_execution.e
new file mode 100644
index 00000000..7e699e13
--- /dev/null
+++ b/draft/application/cms/src/core/modules/node/node_edit_cms_execution.e
@@ -0,0 +1,148 @@
+note
+ description: "[
+ ]"
+
+class
+ NODE_EDIT_CMS_EXECUTION
+
+inherit
+ CMS_EXECUTION
+
+create
+ make
+
+feature -- Execution
+
+ process
+ -- Computed response message.
+ local
+ b: STRING_8
+ f: like edit_form
+ fd: detachable CMS_FORM_DATA
+ l_preview: BOOLEAN
+ l_format: detachable CMS_FORMAT
+ do
+ create b.make_empty
+ if
+ attached {WSF_STRING} request.path_parameter ("nid") as p_nid and then
+ p_nid.is_integer and then
+ attached service.storage.node (p_nid.integer_value) as l_node
+ then
+ if attached service.content_type (l_node.content_type_name) as l_type then
+ if has_permission ("edit " + l_type.name) then
+ f := edit_form (l_node, request.path_info, "edit-" + l_type.name, l_type)
+ if request.is_post_request_method then
+ create fd.make (request, f)
+ l_preview := attached {WSF_STRING} fd.item ("op") as l_op and then l_op.same_string ("Preview")
+ end
+
+ set_title ("Edit #" + l_node.id.out)
+
+ add_to_menu (create {CMS_LOCAL_LINK}.make ("View", node_url (l_node)), primary_tabs)
+ add_to_menu (create {CMS_LOCAL_LINK}.make ("Edit", "/node/" + l_node.id.out + "/edit"), primary_tabs)
+
+ if fd /= Void and l_preview then
+ b.append ("Preview
")
+ if attached fd.string_item ("format") as s_format and then attached formats.format (s_format) as f_format then
+ l_format := f_format
+ end
+ if attached fd.string_item ("title") as l_title then
+ b.append ("Title:
" + html_encoded (l_title) + "
")
+ end
+ if attached fd.string_item ("body") as l_body then
+ b.append ("Body:
")
+ if l_format /= Void then
+ b.append (l_format.to_html (l_body))
+ else
+ b.append (html_encoded (l_body))
+ end
+ b.append ("
")
+ end
+ b.append ("
")
+ end
+
+ if fd /= Void and then fd.is_valid and not l_preview then
+ across
+ fd as c
+ loop
+ b.append ("
" + html_encoded (c.key) + "=")
+ if attached c.item as v then
+ b.append (html_encoded (v.string_representation))
+ end
+ b.append ("
")
+ end
+ l_type.change_node (Current, fd, l_node)
+ service.storage.save_node (l_node)
+ if attached user as u then
+ service.log ("node", "User %"" + user_link (u) + "%" modified node " + link (l_type.name +" #" + l_node.id.out, "/node/" + l_node.id.out , Void), 0, node_local_link (l_node))
+ else
+ service.log ("node", "Anonymous modified node "+ l_type.name +" #" + l_node.id.out, 0, node_local_link (l_node))
+ end
+ add_success_message ("Node #" + l_node.id.out + " saved.")
+ set_redirection (node_url (l_node))
+ else
+ if fd /= Void then
+ if not fd.is_valid then
+ report_form_errors (fd)
+ end
+ fd.apply_to_associated_form
+ end
+ b.append (f.to_html (theme))
+ end
+ else
+ b.append ("
Access denied
")
+ end
+ else
+ set_title ("Unknown node")
+ end
+ else
+ set_title ("Create new content ...")
+ b.append ("
")
+ across
+ service.content_types as c
+ loop
+ if has_permission ("create " + c.item.name) then
+ b.append ("
" + link (c.item.name, "/node/add/" + c.item.name, Void))
+ if attached c.item.description as d then
+ b.append ("
" + d + "
")
+ end
+ b.append ("
")
+ end
+ end
+ b.append ("
")
+ end
+ set_main_content (b)
+ end
+
+ edit_form (a_node: detachable CMS_NODE; a_url: READABLE_STRING_8; a_name: STRING; a_type: CMS_CONTENT_TYPE): CMS_FORM
+ local
+ f: CMS_FORM
+ ts: CMS_FORM_SUBMIT_INPUT
+ th: CMS_FORM_HIDDEN_INPUT
+ do
+ create f.make (a_url, a_name)
+
+ create th.make ("node-id")
+ if a_node /= Void then
+ th.set_text_value (a_node.id.out)
+ else
+ th.set_text_value ("0")
+ end
+ f.extend (th)
+
+ a_type.fill_edit_form (f, a_node)
+
+ f.extend (create {CMS_FORM_RAW_TEXT}.make (" "))
+
+ create ts.make ("op")
+ ts.set_default_value ("Save")
+ f.extend (ts)
+
+ create ts.make ("op")
+ ts.set_default_value ("Preview")
+ f.extend (ts)
+
+ Result := f
+ end
+
+end
diff --git a/draft/application/cms/src/core/modules/node/node_module.e b/draft/application/cms/src/core/modules/node/node_module.e
new file mode 100644
index 00000000..72724c44
--- /dev/null
+++ b/draft/application/cms/src/core/modules/node/node_module.e
@@ -0,0 +1,113 @@
+note
+ description: "Summary description for {NODE_MODULE}."
+ author: ""
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ NODE_MODULE
+
+inherit
+ CMS_MODULE
+
+ CMS_HOOK_MENU_ALTER
+
+ CMS_HOOK_BLOCK
+
+create
+ make
+
+feature {NONE} -- Initialization
+
+ make (a_service: like service)
+ do
+ service := a_service
+ name := "node"
+ version := "1.0"
+ description := "Service to manage content based on 'node'"
+ package := "core"
+
+ enable
+ end
+
+feature {CMS_SERVICE} -- Registration
+
+ service: CMS_SERVICE
+
+ register (a_service: CMS_SERVICE)
+ local
+ h: CMS_HANDLER
+ do
+ a_service.map_uri ("/node/add", agent handle_node_add)
+ a_service.map_uri_template ("/node/add/{type}", agent handle_node_add)
+
+ create {CMS_HANDLER} h.make (agent handle_node_view)
+ a_service.router.map (create {WSF_URI_TEMPLATE_MAPPING}.make ("/node/{nid}", h))
+ a_service.router.map (create {WSF_URI_TEMPLATE_MAPPING}.make ("/node/{nid}/view", h))
+
+ a_service.map_uri_template ("/node/{nid}/edit", agent handle_node_edit)
+
+ a_service.add_content_type (create {CMS_PAGE_CONTENT_TYPE}.make)
+
+ a_service.add_menu_alter_hook (Current)
+ a_service.add_block_hook (Current)
+ end
+
+feature -- Hooks
+
+ block_list: ITERABLE [like {CMS_BLOCK}.name]
+ do
+ Result := <<"node-info">>
+ end
+
+ get_block_view (a_block_id: detachable READABLE_STRING_8; a_execution: CMS_EXECUTION)
+-- local
+-- b: CMS_CONTENT_BLOCK
+ do
+-- if
+-- a_execution.is_front and then
+-- attached a_execution.user as u
+-- then
+-- create b.make ("node-info", "Node", "Node ...", a_execution.formats.plain_text)
+-- a_execution.add_block (b, Void)
+-- end
+ end
+
+ menu_alter (a_menu_system: CMS_MENU_SYSTEM; a_execution: CMS_EXECUTION)
+ local
+ lnk: CMS_LOCAL_LINK
+ do
+ if a_execution.authenticated then
+ create lnk.make ("Add content", "/node/add/")
+ lnk.set_permission_arguments (<<"authenticated">>)
+ a_menu_system.navigation_menu.extend (lnk)
+ end
+ end
+
+ links: HASH_TABLE [CMS_MODULE_LINK, STRING]
+ -- Link indexed by path
+ local
+-- lnk: CMS_MODULE_LINK
+ do
+ create Result.make (3)
+-- create lnk.make ("Date/time demo")
+-- lnk.set_callback (agent process_date_time_demo, <<"arg">>)
+-- Result["/demo/date/{arg}"] := lnk
+ end
+
+ handle_node_view (req: WSF_REQUEST; res: WSF_RESPONSE)
+ do
+ (create {NODE_VIEW_CMS_EXECUTION}.make (req, res, service)).execute
+ end
+
+ handle_node_edit (req: WSF_REQUEST; res: WSF_RESPONSE)
+ do
+ (create {NODE_EDIT_CMS_EXECUTION}.make (req, res, service)).execute
+ end
+
+ handle_node_add (req: WSF_REQUEST; res: WSF_RESPONSE)
+ do
+ (create {NODE_ADD_CMS_EXECUTION}.make (req, res, service)).execute
+ end
+
+end
diff --git a/draft/application/cms/src/core/modules/node/node_view_cms_execution.e b/draft/application/cms/src/core/modules/node/node_view_cms_execution.e
new file mode 100644
index 00000000..4c67eaa7
--- /dev/null
+++ b/draft/application/cms/src/core/modules/node/node_view_cms_execution.e
@@ -0,0 +1,41 @@
+note
+ description: "[
+ ]"
+
+class
+ NODE_VIEW_CMS_EXECUTION
+
+inherit
+ CMS_EXECUTION
+
+create
+ make
+
+feature -- Execution
+
+ process
+ -- Computed response message.
+ local
+ b: STRING_8
+ do
+ if attached {WSF_STRING} request.path_parameter ("nid") as p_nid and then p_nid.is_integer then
+ create b.make_empty
+
+ if attached storage.node (p_nid.integer_value) as l_node then
+ set_title ("Node [" + l_node.id.out + "]")
+ add_to_menu (create {CMS_LOCAL_LINK}.make ("View", node_url (l_node)), primary_tabs)
+ add_to_menu (create {CMS_LOCAL_LINK}.make ("Edit", "/node/" + l_node.id.out + "/edit"), primary_tabs)
+
+ b.append (l_node.to_html (theme))
+ else
+ set_title ("Node [" + p_nid.value + "] does not exists!")
+ end
+ set_main_content (b)
+ else
+ set_title ("Node ...")
+ create b.make_empty
+ set_main_content (b)
+ end
+ end
+
+end
diff --git a/draft/application/cms/src/core/modules/user/user_account_cms_execution.e b/draft/application/cms/src/core/modules/user/user_account_cms_execution.e
new file mode 100644
index 00000000..3dc2fadf
--- /dev/null
+++ b/draft/application/cms/src/core/modules/user/user_account_cms_execution.e
@@ -0,0 +1,59 @@
+note
+ description: "[
+ ]"
+
+class
+ USER_ACCOUNT_CMS_EXECUTION
+
+inherit
+ CMS_EXECUTION
+
+create
+ make
+
+feature -- Execution
+
+ process
+ -- Computed response message.
+ local
+ b: STRING_8
+ vars: detachable ARRAY [READABLE_STRING_32]
+ n: INTEGER
+-- vars: detachable WSF_TABLE
+ do
+ if attached {WSF_TABLE} request.path_parameter ("vars") as tb then
+ vars := tb.as_array_of_string
+ end
+ if vars = Void or else vars.is_empty then
+ set_title ("Account")
+ create b.make_empty
+ b.append ("Account")
+ set_main_content (b)
+ else
+ n := vars.count
+ create b.make_empty
+ if n >= 1 then
+ if vars[1].same_string ("password") then
+ set_title ("Password")
+ if n >= 2 then
+ if vars[2].same_string ("reset") then
+ b.append ("Reset password")
+ else
+ b.append ("password ???")
+ end
+ end
+ elseif vars[1].same_string ("register") then
+ set_title ("Registration")
+ b.append ("Register new account")
+ else
+ b.append ("???")
+ end
+ else
+ set_title ("Account/")
+ b.append ("...")
+ end
+ set_main_content (b)
+ end
+ end
+
+end
diff --git a/draft/application/cms/src/core/modules/user/user_cms_execution.e b/draft/application/cms/src/core/modules/user/user_cms_execution.e
new file mode 100644
index 00000000..e02447e3
--- /dev/null
+++ b/draft/application/cms/src/core/modules/user/user_cms_execution.e
@@ -0,0 +1,182 @@
+note
+ description: "[
+ ]"
+
+class
+ USER_CMS_EXECUTION
+
+inherit
+ CMS_EXECUTION
+
+ USER_MODULE_LIB
+
+create
+ make
+
+feature -- Execution
+
+ process
+ -- Computed response message.
+ local
+ b: STRING_8
+ u: detachable CMS_USER
+ do
+ if attached {WSF_STRING} request.path_parameter ("uid") as p_uid then
+ if p_uid.is_integer then
+ u := service.storage.user_by_id (p_uid.integer_value)
+ else
+ u := service.storage.user_by_name (p_uid.value)
+ end
+ else
+ u := user
+ end
+ initialize_primary_tabs (u)
+
+ if u /= Void then
+ if not u.same_as (user) and then not has_permission ("admin view users") then
+ set_main_content ("Access denied")
+ else
+ service.storage.fill_user_profile (u)
+ create b.make_empty
+ set_title ("User [" + u.name + "]")
+ b.append ("
%N")
+ if attached u.email as l_email then
+ b.append ("
%N")
+ if attached u.last_login_date as dt then
+ b.append ("
Last signed: "+ dt.out +"
%N")
+ else
+ b.append ("
Never signed yet
%N")
+ end
+ if u = user and then attached last_user_access_date as dt then
+ b.append ("
Session date: "+ dt.out +"
%N")
+ end
+
+ service.storage.fill_user_profile (u)
+ if attached u.profile as prof then
+ across
+ prof as p
+ loop
+ b.append ("
" + p.key + "=" + p.item +"
%N")
+ end
+ end
+ b.append ("
")
+ set_main_content (b)
+ end
+ else
+ process_login
+ end
+ end
+
+ process_login
+ local
+ l_url: detachable READABLE_STRING_8
+ b: STRING_8
+ f: CMS_FORM
+ fd: detachable CMS_FORM_DATA
+ do
+ if
+ attached {WSF_STRING} request.item ("destination") as s_dest
+ then
+ l_url := request.script_url (s_dest.value)
+ end
+ if l_url = Void then
+ l_url := request.script_url ("/user")
+ end
+ f := login_form (request.path_info, "login-form", l_url)
+ service.call_form_alter_hooks (f, Current)
+
+ if request.is_request_method ("post") then
+ create fd.make (request, f)
+ if fd.is_valid then
+ on_form_submitted (fd)
+ if attached {WSF_STRING} fd.integer_item ("form-destination") as s_dest then
+ l_url := request.script_url (s_dest.value)
+ end
+ end
+ end
+
+ if authenticated then
+ set_redirection (l_url)
+ set_title ("Login")
+ create b.make_empty
+ set_main_content (b)
+ set_redirection (url ("/user", Void))
+ else
+ set_title ("Login")
+ create b.make_empty
+ if fd /= Void then
+ if not fd.is_valid then
+ report_form_errors (fd)
+ end
+ fd.apply_to_associated_form
+ end
+ b.append (f.to_html (theme))
+ set_main_content (b)
+ end
+ end
+
+ on_form_submitted (fd: CMS_FORM_DATA)
+ local
+ u: detachable CMS_USER
+ do
+ if attached {WSF_STRING} fd.item (form_username_or_email_name) as s_name and then not s_name.is_empty then
+ u := service.storage.user_by_name (s_name.value)
+ if u = Void then
+ u := service.storage.user_by_email (s_name.value)
+ end
+ end
+ if u = Void then
+ fd.report_error ("Sorry, unrecognized username/email or password. " + link ("Have you forgotten your password?", "/user/password", Void))
+ else
+ if attached {WSF_STRING} fd.item (form_password_name) as s_passwd and then not s_passwd.is_empty then
+ if service.auth_engine.valid_credential (u.name, s_passwd.value) then
+ login (u, request)
+ else
+ fd.report_error ("Sorry, unrecognized username/email or password. " + link ("Have you forgotten your password?", "/user/password", Void))
+ end
+ end
+ end
+ end
+
+ login_form (a_action: READABLE_STRING_8; a_form_name: READABLE_STRING_8; a_destination: READABLE_STRING_8): CMS_FORM
+ local
+ th: CMS_FORM_HIDDEN_INPUT
+ ti: CMS_FORM_TEXT_INPUT
+ tp: CMS_FORM_PASSWORD_INPUT
+ ts: CMS_FORM_SUBMIT_INPUT
+ do
+ create Result.make (a_action, a_form_name)
+
+ create th.make ("form-destination")
+ th.set_default_value (a_destination)
+ Result.extend (th)
+
+ create ti.make (form_username_or_email_name)
+ ti.set_label ("Username or email")
+ ti.set_is_required (True)
+ Result.extend (ti)
+
+ create tp.make (form_password_name)
+ tp.set_label ("Password")
+ tp.set_is_required (True)
+ tp.set_description (link ("Reset password", "/user/password", Void))
+ Result.extend (tp)
+
+ Result.extend_text ("[
+
+ ]")
+
+ create ts.make ("op")
+ ts.set_default_value ("Log in")
+ Result.extend (ts)
+
+ Result.extend_text ("
Need an account? " + link ("Sign up now!", "/user/register", Void) + "
")
+ end
+
+ form_username_or_email_name: STRING = "name"
+ form_password_name: STRING = "password"
+
+end
diff --git a/draft/application/cms/src/core/modules/user/user_edit_cms_execution.e b/draft/application/cms/src/core/modules/user/user_edit_cms_execution.e
new file mode 100644
index 00000000..57fbe5ec
--- /dev/null
+++ b/draft/application/cms/src/core/modules/user/user_edit_cms_execution.e
@@ -0,0 +1,168 @@
+note
+ description: "[
+ ]"
+
+class
+ USER_EDIT_CMS_EXECUTION
+
+inherit
+ CMS_EXECUTION
+
+ USER_MODULE_LIB
+
+create
+ make
+
+feature -- Execution
+
+ process
+ -- Computed response message.
+ local
+ b: STRING_8
+ f: CMS_FORM
+ fd: detachable CMS_FORM_DATA
+ u, fu: detachable CMS_USER
+ up: detachable CMS_USER_PROFILE
+ l_is_editing_current_user: BOOLEAN
+ do
+ if attached {WSF_STRING} request.path_parameter ("uid") as p_uid and then p_uid.is_integer then
+ u := service.storage.user_by_id (p_uid.integer_value)
+ if has_permission ("view users") then
+ else
+ if u /= Void and then u.same_as (user) then
+ else
+ u := Void
+ end
+ end
+ else
+ u := user
+ end
+ if attached user as l_active_user then
+ l_is_editing_current_user := l_active_user.same_as (u)
+ end
+ create b.make_empty
+ initialize_primary_tabs (u)
+ if u = Void then
+ b.append ("Access denied")
+ set_redirection (url ("/user/register", Void))
+ else
+ service.storage.fill_user_profile (u)
+ f := edit_form (u, request.path_info, "user-edit")
+
+ if request.is_post_request_method then
+ create fd.make (request, f)
+ if attached {WSF_STRING} fd.item ("username") as s_username then
+ fu := service.storage.user_by_name (s_username.value)
+ if fu = Void then
+ fd.report_invalid_field ("username", "User does not exist!")
+ end
+ end
+ if attached {WSF_STRING} fd.item ("email") as s_email then
+ fu := service.storage.user_by_email (s_email.value)
+ if fu /= Void and then fu.id /= u.id then
+ fd.report_invalid_field ("email", "Email is already used by another user!")
+ end
+ end
+ fu := Void
+ end
+ if fd /= Void and then fd.is_valid then
+ across
+ fd as c
+ loop
+ b.append ("
" + html_encoded (c.key) + "=")
+ if attached c.item as v then
+ b.append (html_encoded (v.string_representation))
+ end
+ b.append ("
")
+ end
+
+ if attached {WSF_STRING} fd.item ("password") as s_password then
+ u.set_password (s_password.value)
+ end
+ if attached {WSF_STRING} fd.item ("email") as s_email then
+ u.set_email (s_email.value)
+ end
+
+ if attached {WSF_STRING} fd.item ("note") as s_note then
+ up := u.profile
+ if up = Void then
+ create up.make
+ end
+ up.force (s_note.value, "note")
+ u.set_profile (up)
+ end
+
+ service.storage.save_user (u)
+ if l_is_editing_current_user and u /= user then
+ set_user (u)
+ end
+ set_redirection (url ("/user", Void))
+ set_main_content (b)
+ else
+ if fd /= Void then
+ if not fd.is_valid then
+ report_form_errors (fd)
+ end
+ fd.apply_to_associated_form
+ end
+ b.append (f.to_html (theme))
+ end
+ end
+ set_main_content (b)
+ end
+
+ edit_form (u: CMS_USER; a_url: READABLE_STRING_8; a_name: STRING): CMS_FORM
+ local
+ f: CMS_FORM
+ ti: CMS_FORM_TEXT_INPUT
+ tp: CMS_FORM_PASSWORD_INPUT
+ ta: CMS_FORM_TEXTAREA
+ ts: CMS_FORM_SUBMIT_INPUT
+ do
+ create f.make (a_url, a_name)
+
+ create ti.make ("username")
+ ti.set_label ("Username")
+ ti.set_default_value (u.name)
+ ti.set_is_required (False)
+ ti.set_is_readonly (True)
+ f.extend (ti)
+
+ f.extend (create {CMS_FORM_RAW_TEXT}.make (" "))
+
+ create tp.make ("password")
+ tp.set_label ("Password")
+ tp.set_is_required (False)
+ f.extend (tp)
+
+ f.extend (create {CMS_FORM_RAW_TEXT}.make (" "))
+
+ create ti.make ("email")
+ ti.set_label ("Valid email address")
+ if attached u.email as l_email then
+ ti.set_default_value (l_email)
+ end
+ ti.set_is_required (True)
+ f.extend (ti)
+
+ f.extend (create {CMS_FORM_RAW_TEXT}.make (" "))
+
+ create ta.make ("note")
+ ta.set_label ("Additional note about you")
+ ta.set_description ("You can use this input to tell us more about you")
+ if attached u.profile as p and then attached p.item ("note") as l_note then
+ ta.set_default_value (l_note)
+ end
+ ta.set_is_required (False)
+ f.extend (ta)
+
+ f.extend (create {CMS_FORM_RAW_TEXT}.make (" "))
+
+ create ts.make ("op")
+ ts.set_default_value ("Save")
+ f.extend (ts)
+
+ Result := f
+ end
+
+end
diff --git a/draft/application/cms/src/core/modules/user/user_login_cms_execution.e b/draft/application/cms/src/core/modules/user/user_login_cms_execution.e
new file mode 100644
index 00000000..56f1a4f0
--- /dev/null
+++ b/draft/application/cms/src/core/modules/user/user_login_cms_execution.e
@@ -0,0 +1,114 @@
+note
+ description: "[
+ ]"
+
+class
+ USER_LOGIN_CMS_EXECUTION
+
+inherit
+ CMS_EXECUTION
+
+ CMS_AUTH_ENGINE
+
+create
+ make
+
+feature -- Status
+
+ valid_credential (u,p: READABLE_STRING_32): BOOLEAN
+ do
+ if attached service.storage.user_by_name (u) as l_user then
+ Result := attached l_user.encoded_password as l_pass and then l_pass.same_string (service.storage.encoded_password (p))
+ end
+ end
+
+feature -- Execution
+
+ process
+ -- Computed response message.
+ local
+ auth_engine: CMS_AUTH_ENGINE
+ l_url: detachable READABLE_STRING_8
+ err: detachable STRING_8
+ b: STRING_8
+ u: detachable STRING_32
+ do
+ if request.is_request_method ("post") then
+ if
+ attached {WSF_STRING} request.form_parameter (form_login_name) as s_login and then not s_login.is_empty and
+ attached {WSF_STRING} request.form_parameter (form_password_name) as s_passwd and then not s_passwd.is_empty
+ then
+ auth_engine := Current
+ u := s_login.value
+ if attached service.storage.user_by_name (u) as l_user and auth_engine.valid_credential (u, s_passwd.value) then
+ login (l_user, request)
+ else
+ err := "Authentication failed for [" + html_encoded (u) + "]"
+ end
+ if attached {WSF_STRING} request.form_parameter ("form-destination") as s_dest then
+ l_url := request.script_url (s_dest.value)
+ end
+ end
+ else
+ if
+ attached {WSF_STRING} request.item ("destination") as s_dest
+ then
+ l_url := request.script_url (s_dest.value)
+ end
+ end
+
+ if l_url = Void then
+ l_url := request.script_url ("/user")
+ end
+
+ if authenticated then
+ set_redirection (l_url)
+ set_title ("Login")
+ create b.make_empty
+ b.append ("
")
+ end
+
+ b.append ("%N")
+
+ set_main_content (b)
+ end
+ end
+
+ form_login_name: STRING = "login"
+ form_password_name: STRING = "password"
+
+end
diff --git a/draft/application/cms/src/core/modules/user/user_logout_cms_execution.e b/draft/application/cms/src/core/modules/user/user_logout_cms_execution.e
new file mode 100644
index 00000000..8fa1221d
--- /dev/null
+++ b/draft/application/cms/src/core/modules/user/user_logout_cms_execution.e
@@ -0,0 +1,39 @@
+note
+ description: "[
+ ]"
+
+class
+ USER_LOGOUT_CMS_EXECUTION
+
+inherit
+ CMS_EXECUTION
+
+create
+ make
+
+feature -- Execution
+
+ process
+ -- Computed response message.
+ local
+-- l_url: READABLE_STRING_8
+ b: STRING_8
+ do
+ logout (request)
+
+ if
+ attached {WSF_STRING} request.item ("destination") as s_dest
+ then
+ set_redirection (request.script_url (s_dest.value))
+ else
+ set_redirection (request.script_url ("/"))
+ end
+
+ set_title ("Logout")
+ create b.make_empty
+ set_main_content (b)
+-- l_url := request.script_url ("/info/")
+-- res.redirect_now_with_content (l_url, "Redirection to " + l_url, "text/html")
+ end
+
+end
diff --git a/draft/application/cms/src/core/modules/user/user_module.e b/draft/application/cms/src/core/modules/user/user_module.e
new file mode 100644
index 00000000..44996556
--- /dev/null
+++ b/draft/application/cms/src/core/modules/user/user_module.e
@@ -0,0 +1,157 @@
+note
+ description: "Summary description for {USER_MODULE}."
+ author: ""
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ USER_MODULE
+
+inherit
+ CMS_MODULE
+
+ CMS_HOOK_MENU_ALTER
+
+ CMS_HOOK_BLOCK
+
+create
+ make
+
+feature {NONE} -- Initialization
+
+ make (a_service: like service)
+ do
+ service := a_service
+ name := "user"
+ version := "1.0"
+ description := "Users management"
+ package := "core"
+
+ enable
+ end
+
+feature {CMS_SERVICE} -- Registration
+
+ service: CMS_SERVICE
+
+ register (a_service: CMS_SERVICE)
+ local
+ h: CMS_HANDLER
+ do
+-- a_service.map_uri ("/user", agent handle_login)
+ a_service.map_uri ("/user/logout", agent handle_logout)
+ a_service.map_uri ("/user/register", agent handle_register)
+ a_service.map_uri ("/user/password", agent handle_request_new_password)
+
+ create {CMS_HANDLER} h.make (agent handle_user)
+ a_service.router.map (create {WSF_URI_TEMPLATE_MAPPING}.make ("/user/{uid}", h))
+ a_service.router.map (create {WSF_URI_MAPPING}.make_trailing_slash_ignored ("/user", h))
+ a_service.map_uri_template ("/user/{uid}/edit", agent handle_edit)
+ a_service.map_uri_template ("/user/reset/{uid}/{last-signed}/{extra}", agent handle_reset_password)
+
+ a_service.add_menu_alter_hook (Current)
+ a_service.add_block_hook (Current)
+ end
+
+feature -- Hooks
+
+ block_list: ITERABLE [like {CMS_BLOCK}.name]
+ do
+ Result := <<"user-info">>
+ end
+
+ get_block_view (a_block_id: detachable READABLE_STRING_8; a_execution: CMS_EXECUTION)
+ local
+ b: CMS_CONTENT_BLOCK
+ do
+ if
+ a_execution.is_front and then
+ attached a_execution.user as u
+ then
+ create b.make ("user-info", "User", "Welcome " + a_execution.html_encoded (u.name), a_execution.formats.plain_text)
+ a_execution.add_block (b, Void)
+ end
+ end
+
+ menu_alter (a_menu_system: CMS_MENU_SYSTEM; a_execution: CMS_EXECUTION)
+ local
+ lnk: CMS_LOCAL_LINK
+ opts: CMS_API_OPTIONS
+ do
+ if attached a_execution.user as u then
+ create lnk.make ("Logout", "/user/logout")
+ a_execution.add_to_main_menu (lnk)
+ else
+ create lnk.make ("Login", "/user")
+ create opts.make_from_manifest (<<["query", <<["destination", a_execution.request.path_info]>> ]>>)
+ lnk.set_options (opts)
+ a_execution.add_to_main_menu (lnk)
+
+ create lnk.make ("Sign up", "/user/register")
+ lnk.set_options (opts)
+ a_execution.add_to_main_menu (lnk)
+ end
+ if a_execution.authenticated then
+ create lnk.make ("My Account", "/user")
+ a_menu_system.user_menu.extend (lnk)
+ create lnk.make ("Logout", "/user/logout")
+ a_menu_system.user_menu.extend (lnk)
+ else
+ create lnk.make ("Login", "/user")
+ a_menu_system.user_menu.extend (lnk)
+ end
+ end
+
+ links: HASH_TABLE [CMS_MODULE_LINK, STRING]
+ -- Link indexed by path
+ local
+-- lnk: CMS_MODULE_LINK
+ do
+ create Result.make (3)
+-- create lnk.make ("Date/time demo")
+-- lnk.set_callback (agent process_date_time_demo, <<"arg">>)
+-- Result["/demo/date/{arg}"] := lnk
+ end
+
+-- handle_login (req: WSF_REQUEST; res: WSF_RESPONSE)
+-- do
+-- (create {USER_LOGIN_CMS_EXECUTION}.make (req, res, service)).execute
+-- end
+
+ handle_logout (req: WSF_REQUEST; res: WSF_RESPONSE)
+ do
+ (create {USER_LOGOUT_CMS_EXECUTION}.make (req, res, service)).execute
+ end
+
+ handle_user (req: WSF_REQUEST; res: WSF_RESPONSE)
+ do
+ (create {USER_CMS_EXECUTION}.make (req, res, service)).execute
+ end
+
+ handle_edit (req: WSF_REQUEST; res: WSF_RESPONSE)
+ do
+ (create {USER_EDIT_CMS_EXECUTION}.make (req, res, service)).execute
+ end
+
+-- handle_account (req: WSF_REQUEST; res: WSF_RESPONSE)
+-- do
+-- (create {USER_ACCOUNT_CMS_EXECUTION}.make (req, res, service)).execute
+-- end
+
+ handle_register (req: WSF_REQUEST; res: WSF_RESPONSE)
+ do
+ (create {USER_REGISTER_CMS_EXECUTION}.make (req, res, service)).execute
+ end
+
+ handle_request_new_password (req: WSF_REQUEST; res: WSF_RESPONSE)
+ do
+ (create {USER_NEW_PASSWORD_CMS_EXECUTION}.make (req, res, service)).execute
+ end
+
+ handle_reset_password (req: WSF_REQUEST; res: WSF_RESPONSE)
+ do
+ (create {USER_RESET_PASSWORD_CMS_EXECUTION}.make (req, res, service)).execute
+ end
+
+
+end
diff --git a/draft/application/cms/src/core/modules/user/user_module_lib.e b/draft/application/cms/src/core/modules/user/user_module_lib.e
new file mode 100644
index 00000000..e1da1d1d
--- /dev/null
+++ b/draft/application/cms/src/core/modules/user/user_module_lib.e
@@ -0,0 +1,29 @@
+note
+ description: "Summary description for {USER_MODULE_LIB}."
+ author: ""
+ date: "$Date$"
+ revision: "$Revision$"
+
+deferred class
+ USER_MODULE_LIB
+
+inherit
+ CMS_COMMON_API
+
+ CMS_EXECUTION
+
+feature -- Initialization
+
+ initialize_primary_tabs (u: detachable CMS_USER)
+ do
+ if u /= Void then
+ primary_tabs.extend (create {CMS_LOCAL_LINK}.make ("View", "/user/" + u.id.out))
+ primary_tabs.extend (create {CMS_LOCAL_LINK}.make ("Edit", "/user/" + u.id.out + "/edit"))
+ else
+ primary_tabs.extend (create {CMS_LOCAL_LINK}.make ("Create new account", "/user/register"))
+ primary_tabs.extend (create {CMS_LOCAL_LINK}.make ("Log in", "/user"))
+ primary_tabs.extend (create {CMS_LOCAL_LINK}.make ("Request new password", "/user/password"))
+ end
+ end
+
+end
diff --git a/draft/application/cms/src/core/modules/user/user_new_password_cms_execution.e b/draft/application/cms/src/core/modules/user/user_new_password_cms_execution.e
new file mode 100644
index 00000000..a4d36faf
--- /dev/null
+++ b/draft/application/cms/src/core/modules/user/user_new_password_cms_execution.e
@@ -0,0 +1,154 @@
+note
+ description: "[
+ ]"
+
+class
+ USER_NEW_PASSWORD_CMS_EXECUTION
+
+inherit
+ CMS_EXECUTION
+
+ USER_MODULE_LIB
+
+create
+ make
+
+feature -- Execution
+
+ process
+ -- Computed response message.
+ local
+ b: STRING_8
+ f: CMS_FORM
+ u: detachable CMS_USER
+ fd: detachable CMS_FORM_DATA
+ e: detachable CMS_EMAIL
+ l_uuid: UUID
+ do
+ set_title ("Request new password")
+ create b.make_empty
+ if not request.is_post_request_method and authenticated then
+ u := user
+ initialize_primary_tabs (u)
+ if u /= Void then
+ if attached u.email as l_email then
+ f := new_password_form (request.path_info, "new-password")
+ b.append ("Password reset instructions will be mailed to " + l_email + ". You must " + link ("log out", "/user/logout", Void) + " to use the password reset link in the e-mail.")
+ b.append (f.to_html (theme))
+ else
+ b.append ("Your account does not have any email address set!")
+ set_redirection (url ("/user/"+ u.id.out +"/edit", Void))
+ end
+ else
+ b.append ("Unexpected issue")
+ end
+ else
+ f := new_password_form (request.path_info, "new-password")
+ if request.is_post_request_method then
+ create fd.make (request, f)
+ if attached {WSF_STRING} fd.item ("name") as s_name then
+ u := service.storage.user_by_name (s_name.value)
+ if u = Void then
+ u := service.storage.user_by_email (s_name.value)
+ if u = Void then
+ fd.report_invalid_field ("name", "Sorry, " + html_encoded (s_name.value)+ " is not recognized as a user name or an e-mail address.")
+ end
+ end
+ end
+ end
+ initialize_primary_tabs (u)
+ if fd /= Void and then fd.is_valid then
+ across
+ fd as c
+ loop
+ b.append ("
" + html_encoded (c.key) + "=")
+ if attached c.item as v then
+ b.append (html_encoded (v.string_representation))
+ end
+ b.append ("
")
+ end
+ if u /= Void and then attached u.email as l_mail_address then
+ l_uuid := (create {UUID_GENERATOR}).generate_uuid
+ e := new_password_email (u, l_mail_address, l_uuid.out)
+ u.set_data_item ("new_password_extra", l_uuid.out)
+ service.storage.save_user (u)
+ service.mailer.safe_process_email (e)
+ add_success_message ("Further instructions have been sent to your e-mail address.")
+ set_redirection (url ("/user", Void))
+ end
+ set_main_content (b)
+ else
+ if fd /= Void then
+ if not fd.is_valid then
+ report_form_errors (fd)
+ end
+ fd.apply_to_associated_form
+ end
+ b.append (f.to_html (theme))
+ end
+ end
+ set_main_content (b)
+ end
+
+ new_password_form (a_url: READABLE_STRING_8; a_name: STRING): CMS_FORM
+ require
+ attached user as l_auth_user implies l_auth_user.has_email
+ local
+ u: like user
+ f: CMS_FORM
+ ti: CMS_FORM_TEXT_INPUT
+ th: CMS_FORM_HIDDEN_INPUT
+ ts: CMS_FORM_SUBMIT_INPUT
+ err: BOOLEAN
+ do
+ create f.make (a_url, a_name)
+ u := user
+ if u = Void then
+ create ti.make ("name")
+ ti.set_label ("Username or e-mail address")
+ ti.set_is_required (True)
+ f.extend (ti)
+ elseif attached u.email as l_mail then
+ create th.make ("name")
+ th.set_default_value (l_mail)
+ th.set_is_required (True)
+ f.extend (th)
+ else
+ f.extend (create {CMS_FORM_RAW_TEXT}.make ("The associated account has no e-mail address."))
+ err := True
+ end
+
+ if not err then
+ create ts.make ("op")
+ ts.set_default_value ("E-mail new password")
+ f.extend (ts)
+ end
+
+ Result := f
+ end
+
+ new_password_email (u: CMS_USER; a_mail_address: STRING; a_extra: READABLE_STRING_8): CMS_EMAIL
+ local
+ b: STRING
+ opts: CMS_URL_API_OPTIONS
+ dt: detachable DATE_TIME
+ do
+ create b.make_empty
+ create opts.make_absolute
+
+ b.append ("A request to reset the password for your account has been made at " + service.site_name + ".%N")
+ b.append ("You may now log in by clicking this link or copying and pasting it to your browser:%N%N")
+ dt := u.last_login_date
+ if dt = Void then
+ dt := u.creation_date
+ end
+ b.append (url ("/user/reset/" + u.id.out + "/" + unix_timestamp (dt).out + "/" + a_extra, opts))
+ b.append ("%N")
+ b.append ("%N")
+ b.append ("This link can only be used once to log in and will lead you to a page where you can set your password. It expires after one day and nothing will happen if it's not used.%N")
+ b.append ("%N%N-- The %"" + service.site_name + "%" team")
+
+ create Result.make (service.site_email, a_mail_address, "Account details for " + u.name + " at " + service.site_name, b)
+ end
+
+end
diff --git a/draft/application/cms/src/core/modules/user/user_register_cms_execution.e b/draft/application/cms/src/core/modules/user/user_register_cms_execution.e
new file mode 100644
index 00000000..49602809
--- /dev/null
+++ b/draft/application/cms/src/core/modules/user/user_register_cms_execution.e
@@ -0,0 +1,205 @@
+note
+ description: "[
+ ]"
+
+class
+ USER_REGISTER_CMS_EXECUTION
+
+inherit
+ CMS_EXECUTION
+
+ USER_MODULE_LIB
+
+create
+ make
+
+feature -- Execution
+
+ process
+ -- Computed response message.
+ local
+ b: STRING_8
+ f: CMS_FORM
+ fd: detachable CMS_FORM_DATA
+ u: detachable CMS_USER
+ up: detachable CMS_USER_PROFILE
+ e: detachable CMS_EMAIL
+ l_pass: detachable READABLE_STRING_32
+ l_uuid: UUID
+ do
+ set_title ("Create new account")
+ create b.make_empty
+ if authenticated then
+ initialize_primary_tabs (user)
+ b.append ("You are already " + link ("signed in", "/user", Void) + ", please " + link ("signout", "/user/logout", Void) + " before trying to " + link ("register a new account", "/account/register", Void) + ".")
+ set_redirection (url ("/user", Void))
+ else
+ f := registration_form (request.path_info, "reg")
+
+ if request.is_post_request_method then
+ create fd.make (request, f)
+ if attached {WSF_STRING} fd.item ("username") as s_username then
+ u := service.storage.user_by_name (s_username.value)
+ if u /= Void then
+ fd.report_invalid_field ("username", "User already exists!")
+ end
+ end
+ if attached {WSF_STRING} fd.item ("email") as s_email then
+ u := service.storage.user_by_email (s_email.value)
+ if u /= Void then
+ fd.report_invalid_field ("email", "Email is already used!")
+ end
+ end
+ u := Void
+ end
+ if fd /= Void and then fd.is_valid then
+ across
+ fd as c
+ loop
+ b.append ("
" + html_encoded (c.key) + "=")
+ if attached c.item as v then
+ b.append (html_encoded (v.string_representation))
+ end
+ b.append ("
")
+ end
+ if attached {WSF_STRING} fd.item ("username") as s_username then
+ u := service.storage.user_by_name (s_username.value)
+
+ create u.make_new (s_username.value)
+ if attached {WSF_STRING} fd.item ("password") as s_password then
+ u.set_password (s_password.value)
+ l_pass := u.password
+ end
+ if attached {WSF_STRING} fd.item ("email") as s_email then
+ u.set_email (s_email.value)
+ end
+
+ if attached {WSF_STRING} fd.item ("note") as s_note then
+ create up.make
+ up.force (s_note.value, "note")
+ u.set_profile (up)
+ end
+
+ l_uuid := (create {UUID_GENERATOR}).generate_uuid
+ u.set_data_item ("new_password_extra", l_uuid.out)
+
+ service.storage.save_user (u)
+ if attached u.email as l_mail_address then
+ e := new_registration_email (l_mail_address, u, l_pass, l_uuid.out)
+ service.mailer.safe_process_email (e)
+ end
+ e := new_user_account_email (service.site_email, u)
+ service.mailer.safe_process_email (e)
+
+ login (u, request)
+ set_redirection (url ("/user", Void))
+ end
+ set_main_content (b)
+ else
+ initialize_primary_tabs (user)
+ if fd /= Void then
+ if not fd.is_valid then
+ report_form_errors (fd)
+ end
+ fd.apply_to_associated_form
+ end
+ b.append (f.to_html (theme))
+ end
+ end
+ set_main_content (b)
+ end
+
+ registration_form (a_url: READABLE_STRING_8; a_name: STRING): CMS_FORM
+ local
+ f: CMS_FORM
+ ti: CMS_FORM_TEXT_INPUT
+ tp: CMS_FORM_PASSWORD_INPUT
+ ta: CMS_FORM_TEXTAREA
+ ts: CMS_FORM_SUBMIT_INPUT
+ do
+ create f.make (a_url, a_name)
+
+ create ti.make ("username")
+ ti.set_label ("Username")
+ ti.set_is_required (True)
+ ti.set_validation_action (agent (fd: CMS_FORM_DATA)
+ do
+ if attached {WSF_STRING} fd.item ("username") as f_username and then f_username.value.count >= 5 then
+ else
+ fd.report_invalid_field ("username", "Username should contain at least 5 characters!")
+ end
+ end)
+ f.extend (ti)
+
+ f.extend (create {CMS_FORM_RAW_TEXT}.make (" "))
+
+ create tp.make ("password")
+ tp.set_label ("Password")
+ tp.set_is_required (True)
+ f.extend (tp)
+
+ f.extend (create {CMS_FORM_RAW_TEXT}.make (" "))
+
+ create ti.make ("email")
+ ti.set_label ("Valid email address")
+ ti.set_is_required (True)
+ f.extend (ti)
+
+ f.extend (create {CMS_FORM_RAW_TEXT}.make (" "))
+
+ create ta.make ("note")
+ ta.set_label ("Additional note about you")
+ ta.set_description ("You can use this input to tell us more about you")
+ ta.set_is_required (False)
+ f.extend (ta)
+
+ f.extend (create {CMS_FORM_RAW_TEXT}.make (" "))
+
+ create ts.make ("Register")
+ ts.set_default_value ("Register")
+ f.extend (ts)
+
+ Result := f
+ end
+
+ new_registration_email (a_mail_address: STRING; u: CMS_USER; a_password: detachable like {CMS_USER}.password; a_extra: READABLE_STRING_8): CMS_EMAIL
+ require
+ has_clear_password: u.password /= Void or else a_password /= Void
+ local
+ p: detachable like {CMS_USER}.password
+ b: STRING
+ opts: CMS_URL_API_OPTIONS
+ do
+ p := a_password
+ if p = Void then
+ p := u.password
+ end
+
+ create b.make_from_string (u.name + "%N" + "Thank you for registering at " + service.site_name + ". ")
+ create opts.make_absolute
+-- if p /= Void then
+ b.append ("You may now log in to " + url ("/user", opts) + " using your username %""+ u.name +"%" and password%N")
+-- b.append ("%Nusername: " + u.name + "%Npassword: " + p + "%N%N")
+-- end
+ b.append ("You may also log in by clicking on this link or copying and pasting it in your browser:%N%N")
+ b.append (url ("/user/reset/" + u.id.out + "/" + unix_timestamp (u.creation_date).out + "/" + a_extra, opts))
+-- b.append (url ("/user/reset/" + u.id.out + "/" + unix_timestamp (u.creation_date).out + "/", opts))
+ b.append ("%N%NThis is a one-time login, so it can be used only once.%N%NAfter logging in, you will be redirected to " + url ("/user/" + u.id.out + "/edit", opts) + " so you can change your password.%N")
+ b.append ("%N%N-- The %"" + service.site_name + "%" team")
+
+ create Result.make (service.site_email, a_mail_address, "Account details for " + u.name + " at " + service.site_name, b)
+ end
+
+ new_user_account_email (a_mail_address: STRING; u: CMS_USER): CMS_EMAIL
+ local
+ b: STRING
+ opts: CMS_URL_API_OPTIONS
+ do
+ create b.make_from_string ("New user account %"" + u.name + "%" at " + service.site_name + ". ")
+ create opts.make_absolute
+ b.append ("See user account: " + user_url (u) + "%N")
+ b.append ("%N%N-- The %"" + service.site_name + "%" team")
+ create Result.make (service.site_email, a_mail_address, "New User Account %"" + u.name + "%" at " + service.site_name, b)
+ end
+
+end
diff --git a/draft/application/cms/src/core/modules/user/user_reset_password_cms_execution.e b/draft/application/cms/src/core/modules/user/user_reset_password_cms_execution.e
new file mode 100644
index 00000000..dcf7042d
--- /dev/null
+++ b/draft/application/cms/src/core/modules/user/user_reset_password_cms_execution.e
@@ -0,0 +1,86 @@
+note
+ description: "[
+ ]"
+
+class
+ USER_RESET_PASSWORD_CMS_EXECUTION
+
+inherit
+ CMS_EXECUTION
+
+create
+ make
+
+feature -- Execution
+
+ process
+ -- Computed response message.
+ local
+ b: STRING_8
+ u: detachable CMS_USER
+ err: BOOLEAN
+ t: INTEGER_64
+ l_extra: detachable READABLE_STRING_8
+ do
+ create b.make_empty
+ u := user
+ if u /= Void then
+ add_success_message ("You are logged in as " + u.name + ". " + link ("Change your password", "/user/" + u.id.out + "/edit", Void))
+ set_redirection (front_page_url)
+ else
+ if attached {WSF_STRING} request.path_parameter ("uid") as p_uid and then p_uid.is_integer then
+ u := service.storage.user_by_id (p_uid.integer_value)
+ end
+ if u /= Void then
+ if attached non_empty_string_path_parameter ("last-signed") as p_last_signed then
+ if p_last_signed.is_integer_64 then
+ t := p_last_signed.to_integer_64
+ else
+ err := True
+ end
+ if t > 0 then
+ if attached u.last_login_date as l_last then
+ if t /= unix_timestamp (l_last) then
+ err := True
+ end
+ else
+ if t /= unix_timestamp (u.creation_date) then
+ err := True
+ end
+ end
+ end
+ else
+ err := True
+ end
+ if attached non_empty_string_path_parameter ("extra") as s_extra then
+ l_extra := s_extra
+ if l_extra /= Void then
+ if attached {READABLE_STRING_8} u.data_item ("new_password_extra") as u_extra and then u_extra.same_string (l_extra) then
+ else
+ err := True
+ end
+ else
+ err := True
+ end
+ else
+ err := True
+ end
+ if not err then
+ login (u, request)
+ u.remove_data_item ("new_password_extra")
+ service.storage.save_user (u)
+ set_redirection (url ("/user/" + u.id.out + "/edit", Void))
+ set_main_content (b)
+ end
+ else
+ err := True
+ end
+ if err then
+ add_warning_message ("The one-time login link you clicked is invalid.")
+ set_redirection (front_page_url)
+ end
+ end
+ set_main_content (b)
+ end
+
+end
diff --git a/draft/application/cms/src/core/notification/cms_chain_mailer.e b/draft/application/cms/src/core/notification/cms_chain_mailer.e
new file mode 100644
index 00000000..f9f50cad
--- /dev/null
+++ b/draft/application/cms/src/core/notification/cms_chain_mailer.e
@@ -0,0 +1,58 @@
+note
+ description: "Summary description for {CMS_CHAIN_MAILER}."
+ author: ""
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ CMS_CHAIN_MAILER
+
+inherit
+ CMS_MAILER
+
+create
+ make
+
+feature {NONE} -- Initialization
+
+ make (a_mailer: like active)
+ do
+ active := a_mailer
+ end
+
+feature -- Access
+
+ active: CMS_MAILER
+
+ next: detachable CMS_MAILER
+
+feature -- Status
+
+ is_available: BOOLEAN
+ do
+ Result := active.is_available
+ if not Result and attached next as l_next then
+ Result := l_next.is_available
+ end
+ end
+
+feature -- Change
+
+ set_next (m: like next)
+ do
+ next := m
+ end
+
+feature -- Basic operation
+
+ process_email (a_email: CMS_EMAIL)
+ do
+ if active.is_available then
+ active.process_email (a_email)
+ end
+ if attached next as l_next and then l_next.is_available then
+ l_next.process_email (a_email)
+ end
+ end
+
+end
diff --git a/draft/application/cms/src/core/notification/cms_email.e b/draft/application/cms/src/core/notification/cms_email.e
new file mode 100644
index 00000000..90733493
--- /dev/null
+++ b/draft/application/cms/src/core/notification/cms_email.e
@@ -0,0 +1,94 @@
+note
+ description : "[
+ Component representing an email
+ ]"
+ author : "$Author$"
+ date : "$Date$"
+ revision : "$Revision$"
+
+class
+ CMS_EMAIL
+
+create
+ make
+
+feature {NONE} -- Initialization
+
+ make (a_from: like from_address; a_to_address: READABLE_STRING_8; a_subject: like subject; a_body: like body)
+ -- Initialize `Current'.
+ do
+ initialize
+ from_address := a_from
+ subject := a_subject
+ body := a_body
+ to_addresses.extend (a_to_address)
+
+ end
+
+ initialize
+ do
+ create date.make_now_utc
+ create to_addresses.make (1)
+ end
+
+feature -- Access
+
+ date: DATE_TIME
+
+ from_address: READABLE_STRING_8
+
+ to_addresses: ARRAYED_LIST [READABLE_STRING_8]
+
+ subject: READABLE_STRING_8
+
+ body: READABLE_STRING_8
+
+feature -- Change
+
+ set_date (d: like date)
+ do
+ date := d
+ end
+
+feature -- Conversion
+
+ message: STRING_8
+ do
+ Result := header
+ Result.append ("%N")
+ Result.append (body)
+ Result.append ("%N")
+ Result.append ("%N")
+ end
+
+ header: STRING_8
+ do
+ create Result.make (20)
+ Result.append ("From: " + from_address + "%N")
+ Result.append ("Date: " + date_to_rfc1123_http_date_format (date) + " GMT%N")
+ Result.append ("To: ")
+ across
+ to_addresses as c
+ loop
+ Result.append (c.item)
+ Result.append_character (';')
+ end
+ Result.append_character ('%N')
+ Result.append ("Subject: " + subject + "%N")
+ ensure
+ Result.ends_with ("%N")
+ end
+
+
+feature {NONE} -- Implementation
+
+ date_to_rfc1123_http_date_format (dt: DATE_TIME): STRING_8
+ -- String representation of `dt' using the RFC 1123
+ do
+ Result := dt.formatted_out ("ddd,[0]dd mmm yyyy [0]hh:[0]mi:[0]ss.ff2") + " GMT"
+ end
+
+invariant
+-- invariant_clause: True
+
+end
diff --git a/draft/application/cms/src/core/notification/cms_external_mailer.e b/draft/application/cms/src/core/notification/cms_external_mailer.e
new file mode 100644
index 00000000..0f81db7d
--- /dev/null
+++ b/draft/application/cms/src/core/notification/cms_external_mailer.e
@@ -0,0 +1,190 @@
+note
+ description : "Objects that ..."
+ author : "$Author$"
+ date : "$Date$"
+ revision : "$Revision$"
+
+class
+ CMS_EXTERNAL_MAILER
+
+inherit
+ CMS_MAILER
+
+ EXECUTION_ENVIRONMENT
+
+create
+ make
+
+feature {NONE} -- Initialization
+
+ make (a_exe: like executable_path; args: detachable ITERABLE [READABLE_STRING_8])
+ -- Initialize `Current'.
+ do
+ set_parameters (a_exe, args)
+ end
+
+ executable_path: READABLE_STRING_8
+
+ arguments: detachable ARRAYED_LIST [READABLE_STRING_8]
+
+ stdin_mode_set: BOOLEAN
+ -- Use `stdin' to pass email message, rather than using local file?
+
+ stdin_termination_sequence: detachable STRING
+ -- Termination sequence for the stdin mode
+ --| If any, this tells the executable all the data has been provided
+ --| For instance, using sendmail, you should have "%N.%N%N"
+
+feature -- Status
+
+ is_available: BOOLEAN
+ local
+ f: RAW_FILE
+ do
+ create f.make (executable_path)
+ Result := f.exists
+ end
+
+feature -- Change
+
+ set_parameters (cmd: like executable_path; args: detachable ITERABLE [READABLE_STRING_8])
+ -- Set parameters `executable_path' and associated `arguments'
+ local
+ l_args: like arguments
+ do
+ executable_path := cmd
+ if args = Void then
+ arguments := Void
+ else
+ create l_args.make (5)
+ across
+ args as c
+ loop
+ l_args.force (c.item)
+ end
+ arguments := l_args
+ end
+ end
+
+ set_stdin_mode (b: BOOLEAN; v: like stdin_termination_sequence)
+ -- Set the `stdin_mode_set' value
+ -- and provide optional termination sequence when stdin mode is selected.
+ do
+ stdin_mode_set := b
+ stdin_termination_sequence := v
+ end
+
+feature -- Basic operation
+
+ process_email (a_email: CMS_EMAIL)
+ local
+ l_factory: PROCESS_FACTORY
+ args: like arguments
+ p: detachable PROCESS
+ retried: INTEGER
+ do
+ if retried = 0 then
+ create l_factory
+ if stdin_mode_set then
+ p := l_factory.process_launcher (executable_path, arguments, Void)
+ p.set_hidden (True)
+ p.set_separate_console (False)
+
+ p.redirect_input_to_stream
+ p.launch
+ if p.launched then
+ p.put_string (a_email.message)
+ if attached stdin_termination_sequence as v then
+ p.put_string (v)
+ end
+ end
+ else
+ if attached arguments as l_args then
+ args := l_args.twin
+ else
+ if attached {RAW_FILE} new_temporary_file (generator) as f then
+ f.create_read_write
+ f.put_string (a_email.message)
+ f.close
+ create args.make (1)
+ args.force (f.name)
+ end
+ end
+ p := l_factory.process_launcher (executable_path, args, Void)
+ p.set_hidden (True)
+ p.set_separate_console (False)
+
+ p.launch
+ end
+ if p.launched and not p.has_exited then
+ p.wait_for_exit_with_timeout (1_000_000)
+ if not p.has_exited then
+ p.terminate
+ if not p.has_exited then
+ p.wait_for_exit_with_timeout (1_000_000)
+ end
+ end
+ end
+ elseif retried = 1 then
+ if p /= Void and then p.launched and then not p.has_exited then
+ p.terminate
+ if not p.has_exited then
+ p.wait_for_exit_with_timeout (1_000_000)
+ end
+ end
+ end
+ rescue
+ retried := retried + 1
+ retry
+ end
+
+feature {NONE} -- Implementation
+
+ new_temporary_file (a_extension: detachable STRING_8): RAW_FILE
+ -- Create file with temporary name.
+ -- With concurrent execution, noting ensures that {FILE_NAME}.make_temporary_name is unique
+ -- So using `a_extension' may help
+ local
+ fn: FILE_NAME
+ s: like {FILE_NAME}.string
+ f: detachable like new_temporary_file
+ i: INTEGER
+ do
+ -- With concurrent execution, nothing ensures that {FILE_NAME}.make_temporary_name is unique
+ -- So let's try to find
+ from
+ until
+ f /= Void or i > 1000
+ loop
+ create fn.make_temporary_name
+ s := fn.string
+ if i > 0 then
+ s.append_character ('-')
+ s.append_integer (i)
+ create fn.make_from_string (s)
+ end
+ if a_extension /= Void then
+ fn.add_extension (a_extension)
+ end
+ s := fn.string
+ create f.make (fn.string)
+ if f.exists then
+ i := i + 1
+ f := Void
+ end
+ end
+ if f = Void then
+ Result := new_temporary_file (Void)
+ else
+ Result := f
+ check not_temporary_file_exists: not Result.exists end
+ check temporary_creatable: Result.is_creatable end
+ end
+ ensure
+ not_result_exists: not Result.exists
+ result_creatable: Result.is_creatable
+ end
+
+invariant
+
+end
diff --git a/draft/application/cms/src/core/notification/cms_mailer.e b/draft/application/cms/src/core/notification/cms_mailer.e
new file mode 100644
index 00000000..fe98a701
--- /dev/null
+++ b/draft/application/cms/src/core/notification/cms_mailer.e
@@ -0,0 +1,48 @@
+note
+ description : "[
+ Component responsible to send email
+ ]"
+ author : "$Author$"
+ date : "$Date$"
+ revision : "$Revision$"
+
+deferred class
+ CMS_MAILER
+
+feature -- Status
+
+ is_available: BOOLEAN
+ -- Is mailer available to use?
+ deferred
+ end
+
+feature -- Basic operation
+
+ process_emails (lst: ITERABLE [CMS_EMAIL])
+ -- Process set of emails `lst'
+ require
+ is_available
+ do
+ across
+ lst as c
+ loop
+ process_email (c.item)
+ end
+ end
+
+ safe_process_email (a_email: CMS_EMAIL)
+ -- Same as `process_email', but include the check of `is_available'
+ do
+ if is_available then
+ process_email (a_email)
+ end
+ end
+
+ process_email (a_email: CMS_EMAIL)
+ -- Process the sending of `a_email'
+ require
+ is_available
+ deferred
+ end
+
+end
diff --git a/draft/application/cms/src/core/notification/cms_sendmail_mailer.e b/draft/application/cms/src/core/notification/cms_sendmail_mailer.e
new file mode 100644
index 00000000..264b7e1c
--- /dev/null
+++ b/draft/application/cms/src/core/notification/cms_sendmail_mailer.e
@@ -0,0 +1,34 @@
+note
+ description : "[
+ CMS_MAILER using sendmail as mailtool
+ ]"
+ author : "$Author$"
+ date : "$Date$"
+ revision : "$Revision$"
+
+class
+ CMS_SENDMAIL_MAILER
+
+inherit
+ CMS_EXTERNAL_MAILER
+ redefine
+ default_create
+ end
+
+create
+ default_create
+
+feature {NONE} -- Initialization
+
+ default_create
+ do
+ Precursor
+ make ("/usr/sbin/sendmail", <<"-t">>)
+ if not is_available then
+ make ("/usr/bin/sendmail", <<"-t">>)
+ end
+ set_stdin_mode (True, "%N.%N%N")
+ end
+
+
+end
diff --git a/draft/application/cms/src/core/notification/cms_storage_mailer.e b/draft/application/cms/src/core/notification/cms_storage_mailer.e
new file mode 100644
index 00000000..c57e8b58
--- /dev/null
+++ b/draft/application/cms/src/core/notification/cms_storage_mailer.e
@@ -0,0 +1,38 @@
+note
+ description: "Summary description for {CMS_CHAIN_MAILER}."
+ author: ""
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ CMS_STORAGE_MAILER
+
+inherit
+ CMS_MAILER
+
+create
+ make
+
+feature {NONE} -- Initialization
+
+ make (a_storage: like storage)
+ do
+ storage := a_storage
+ end
+
+feature -- Access
+
+ storage: CMS_STORAGE
+
+feature -- Status
+
+ is_available: BOOLEAN = True
+
+feature -- Basic operation
+
+ process_email (a_email: CMS_EMAIL)
+ do
+ storage.save_email (a_email)
+ end
+
+end
diff --git a/draft/application/cms/src/core/storage/cms_sed_storage.e b/draft/application/cms/src/core/storage/cms_sed_storage.e
new file mode 100644
index 00000000..1ca3f8bb
--- /dev/null
+++ b/draft/application/cms/src/core/storage/cms_sed_storage.e
@@ -0,0 +1,518 @@
+note
+ description : "[
+ CMS Storage implemented using SED
+ ]"
+ date : "$Date$"
+ revision : "$Revision$"
+
+class
+ CMS_SED_STORAGE
+
+inherit
+ CMS_STORAGE
+
+create
+ make
+
+feature {NONE} -- Initialization
+
+ make (dn: STRING)
+ -- Initialize `Current'.
+ do
+ directory_name := dn
+ ensure_directory_exists (dn)
+ create sed
+ initialize
+ end
+
+ directory_name: STRING
+
+ sed: SED_STORABLE_FACILITIES
+
+ sed_file_retrieved (f: FILE): detachable ANY
+ local
+ r: SED_MEDIUM_READER_WRITER
+ do
+ create r.make (f)
+ r.set_for_reading
+ Result := sed.retrieved (r, True)
+ end
+
+ sed_file_store (obj: ANY; f: FILE)
+ local
+ w: SED_MEDIUM_READER_WRITER
+ do
+ create w.make (f)
+ w.set_for_writing
+ sed.store (obj, w)
+ end
+
+ save_object_with_id (obj: ANY; a_id: INTEGER; a_type: STRING)
+ local
+ dn: STRING
+ fn: FILE_NAME
+ f: RAW_FILE
+ do
+ create fn.make_from_string (directory_name)
+ fn.extend (a_type)
+ dn := fn.string
+ ensure_directory_exists (dn)
+ create fn.make_from_string (dn)
+ fn.set_file_name (a_id.out)
+-- fn.add_extension ("txt")
+ create f.make (fn.string)
+-- check not f.exists end
+ f.create_read_write
+ sed_file_store (obj, f)
+ f.close
+ end
+
+ object_with_id (a_id: INTEGER; a_type: STRING): detachable ANY
+ local
+ dn: STRING
+ fn: FILE_NAME
+ f: RAW_FILE
+ do
+ create fn.make_from_string (directory_name)
+ fn.extend (a_type)
+ dn := fn.string
+ ensure_directory_exists (dn)
+ create fn.make_from_string (dn)
+ fn.set_file_name (a_id.out)
+-- fn.add_extension ("txt")
+ create f.make (fn.string)
+ if f.exists and f.is_readable then
+ f.open_read
+ Result := sed_file_retrieved (f)
+ f.close
+ end
+ end
+
+feature -- Access: user
+
+ users_count: INTEGER
+ do
+ Result := last_sequence ("user")
+ end
+
+ fill_user_profile (a_user: CMS_USER)
+ do
+ if a_user.profile = Void then
+ if attached user_profile (a_user) as p then
+ a_user.set_profile (p)
+ end
+ end
+ end
+
+ all_users: LIST [CMS_USER]
+ local
+ res: ARRAYED_LIST [like all_users.item]
+ i, n: like last_sequence
+ do
+ n := last_sequence ("user")
+ create res.make (n)
+ from
+ i := 1
+ until
+ i > n
+ loop
+ if attached user_by_id (i) as u then
+ res.force (u)
+ end
+ i := i + 1
+ end
+ Result := res
+ end
+
+ user_by_id (a_id: like {CMS_USER}.id): detachable CMS_USER
+ do
+ if attached {like user_by_id} object_with_id (a_id, "user") as u then
+ Result := u
+ end
+ end
+
+ user_by_name (a_name: like {CMS_USER}.name): detachable CMS_USER
+ local
+ uid: INTEGER
+ do
+ if attached users_index as t then
+ uid := t.by_name.item (a_name)
+ if uid > 0 then
+ Result := user_by_id (uid)
+ end
+ end
+ end
+
+ user_by_email (a_email: like {CMS_USER}.email): detachable CMS_USER
+ local
+ uid: INTEGER
+ do
+ if attached users_index as t then
+ uid := t.by_email.item (a_email)
+ if uid > 0 then
+ Result := user_by_id (uid)
+ end
+ end
+ end
+
+ encoded_password (a_raw_password: STRING_32): attached like {CMS_USER}.encoded_password
+ do
+ Result := a_raw_password.as_string_8 + "!123!"
+ end
+
+feature -- Change: user
+
+ save_user (a_user: CMS_USER)
+ local
+ uid: INTEGER
+ prof: like {CMS_USER}.profile
+ l_has_new_name: BOOLEAN
+ l_has_new_email: BOOLEAN
+ l_stored_user: like user_by_id
+ do
+ if a_user.has_id then
+ uid := a_user.id
+ l_stored_user := user_by_id (uid)
+ if l_stored_user /= Void then
+ l_has_new_name := not l_stored_user.name.same_string (a_user.name)
+ l_has_new_email := not (l_stored_user.email ~ a_user.email)
+ end
+ else
+ l_has_new_name := True
+ l_has_new_email := True
+ uid := next_sequence ("user")
+ a_user.set_id (uid)
+ end
+ if attached a_user.password as p then
+ a_user.set_encoded_password (encoded_password (p))
+ a_user.set_password (Void)
+ end
+
+ prof := a_user.profile
+ a_user.set_profile (Void)
+ if prof /= Void then
+ save_user_profile (a_user, prof)
+ end
+ save_object_with_id (a_user, uid, "user")
+ if l_has_new_name or l_has_new_email then
+ if attached users_index as l_index then
+ l_index.by_name.force (uid, a_user.name)
+ l_index.by_email.force (uid, a_user.email)
+ store_users_index (l_index)
+ end
+ end
+ a_user.set_profile (prof)
+ end
+
+feature -- Email
+
+ save_email (a_email: CMS_EMAIL)
+ local
+ dn: STRING
+ fn: FILE_NAME
+ f: RAW_FILE
+ ts: INTEGER_64
+ i: INTEGER
+ do
+ create fn.make_from_string (directory_name)
+ fn.extend ("emails")
+ dn := fn.string
+ ensure_directory_exists (dn)
+ ts := (create {HTTP_DATE_TIME_UTILITIES}).unix_time_stamp (a_email.date)
+ from
+ create fn.make_from_string (dn)
+ fn.set_file_name (ts.out)
+ fn.add_extension ("txt")
+ create f.make (fn.string)
+ until
+ not f.exists
+ loop
+ i := i + 1
+ create fn.make_from_string (dn)
+ fn.set_file_name (ts.out + "-" + i.out)
+ fn.add_extension ("txt")
+ f.make (fn.string)
+ end
+ f.create_read_write
+ f.put_string (a_email.message)
+ f.close
+ end
+
+feature -- Log
+
+ log (a_id: like {CMS_LOG}.id): detachable CMS_LOG
+ do
+ if attached {CMS_LOG} object_with_id (a_id, "log") as l then
+ Result := l
+ end
+ end
+
+ recent_logs (a_lower: INTEGER; a_count: INTEGER): LIST [CMS_LOG]
+ local
+ n: Like last_sequence
+ i, p1, nb: INTEGER
+ do
+ n := last_sequence ("log")
+ p1 := n - a_lower + 1
+
+ if p1 > 0 then
+ create {ARRAYED_LIST [CMS_LOG]} Result.make (a_count)
+ from
+ i := p1
+ until
+ i < 1 or nb = a_count
+ loop
+ if attached log (i) as obj then
+ Result.force (obj)
+ nb := nb + 1
+ end
+ i := i - 1
+ end
+ else
+ create {ARRAYED_LIST [CMS_LOG]} Result.make (0)
+ end
+ end
+
+
+ save_log (a_log: CMS_LOG)
+ do
+ if not a_log.has_id then
+ a_log.set_id (next_sequence ("log"))
+ end
+ save_object_with_id (a_log, a_log.id, "log")
+ end
+
+feature -- Node
+
+ recent_nodes (a_lower: INTEGER; a_count: INTEGER): LIST [CMS_NODE]
+ local
+ n: Like last_sequence
+ i, p1, nb: INTEGER
+ do
+ n := last_sequence ("node")
+ p1 := n - a_lower + 1
+
+ if p1 > 0 then
+ create {ARRAYED_LIST [CMS_NODE]} Result.make (a_count)
+ from
+ i := p1
+ until
+ i < 1 or nb = a_count
+ loop
+ if attached node (i) as l_node then
+ Result.force (l_node)
+ nb := nb + 1
+ end
+ i := i - 1
+ end
+ else
+ create {ARRAYED_LIST [CMS_NODE]} Result.make (0)
+ end
+ end
+
+ node (a_id: INTEGER): detachable CMS_NODE
+ do
+ if attached {like node} object_with_id (a_id, "node") as obj then
+ Result := obj
+ end
+ end
+
+ save_node (a_node: CMS_NODE)
+ local
+ nid: INTEGER
+ do
+ if a_node.has_id then
+ nid := a_node.id
+ else
+ nid := next_sequence ("node")
+ a_node.set_id (nid)
+ end
+
+ save_object_with_id (a_node, nid, "node")
+ end
+
+feature {NONE} -- Implementation
+
+ last_sequence (a_type: STRING): INTEGER
+ local
+ fn: FILE_NAME
+ f: RAW_FILE
+ do
+ create fn.make_from_string (directory_name)
+ fn.set_file_name (a_type)
+ fn.add_extension ("last_id")
+ create f.make (fn.string)
+ if f.exists and then f.is_readable then
+ f.open_read
+ f.read_line
+ if f.last_string.is_integer then
+ Result := f.last_string.to_integer
+ else
+ check is_integer: False end
+ end
+ f.close
+ end
+ end
+
+ next_sequence (a_type: STRING): INTEGER
+ local
+ fn: FILE_NAME
+ f: RAW_FILE
+ do
+ create fn.make_from_string (directory_name)
+ fn.set_file_name (a_type)
+ fn.add_extension ("last_id")
+ create f.make (fn.string)
+ if f.exists and then f.is_readable then
+ f.open_read
+ f.read_line
+ if f.last_string.is_integer then
+ Result := f.last_string.to_integer
+ else
+ check is_integer: False end
+ end
+ f.close
+ end
+ Result := Result + 1
+ f.open_write
+ f.put_string (Result.out)
+ f.put_new_line
+ f.close
+ end
+
+ users_index: TUPLE [
+ by_name: HASH_TABLE [like {CMS_USER}.id, like {CMS_USER}.name];
+ by_email: HASH_TABLE [like {CMS_USER}.id, like {CMS_USER}.email]
+ ]
+ local
+ f: RAW_FILE
+ fn: FILE_NAME
+ res: detachable like users_index
+ retried: INTEGER
+ do
+ create fn.make_from_string (directory_name)
+ fn.set_file_name ("users.db")
+ create f.make (fn.string)
+ if retried = 0 then
+ if f.exists and then f.is_readable then
+ f.open_read
+ if attached {like users_index} sed_file_retrieved (f) as r then
+ res := r
+ end
+ f.close
+ else
+ end
+ end
+ if res = Void then
+ res := [ create {HASH_TABLE [like {CMS_USER}.id, like {CMS_USER}.name]}.make (1),
+ create {HASH_TABLE [like {CMS_USER}.id, like {CMS_USER}.email]}.make (1) ]
+ end
+ Result := res
+ rescue
+ retried := retried + 1
+ retry
+ end
+
+ store_users_index (a_users_index: like users_index)
+ local
+ f: RAW_FILE
+ fn: FILE_NAME
+ do
+ create fn.make_from_string (directory_name)
+ fn.set_file_name ("users.db")
+ create f.make (fn.string)
+ if not f.exists or else f.is_writable then
+ f.open_write
+ sed_file_store (a_users_index, f)
+ f.close
+ end
+ end
+
+ user_profile (a_user: CMS_USER): detachable CMS_USER_PROFILE
+ do
+ if attached {like user_profile} object_with_id (a_user.id, "user_profile") as obj then
+ Result := obj
+ end
+ end
+
+ save_user_profile (a_user: CMS_USER; a_prof: CMS_USER_PROFILE)
+ local
+ l_id: INTEGER
+ do
+ if a_user.has_id then
+ l_id := a_user.id
+ end
+
+ save_object_with_id (a_prof, l_id, "user_profile")
+ end
+
+-- user_profiles: TUPLE [by_username: HASH_TABLE [CMS_USER_PROFILE, like {CMS_USER}.name]]
+-- local
+-- f: RAW_FILE
+-- fn: FILE_NAME
+-- res: detachable like user_profiles
+-- retried: INTEGER
+-- do
+-- if retried = 0 then
+-- create fn.make_from_string (directory_name)
+-- fn.set_file_name ("user_profiles.db")
+-- create f.make (fn.string)
+-- if f.exists and then f.is_readable then
+-- f.open_read
+-- if attached {like user_profiles} sed_file_retrieved (f) as r then
+-- res := r
+-- end
+-- f.close
+-- else
+-- end
+-- end
+-- if res = Void then
+-- res := [create {HASH_TABLE [CMS_USER_PROFILE, like {CMS_USER}.name]}.make (1)]
+-- if retried <= 1 then
+-- store_user_profiles (res)
+-- end
+-- end
+-- Result := res
+-- rescue
+-- retried := retried + 1
+-- retry
+-- end
+
+-- store_user_profiles (a_user_profiles: like user_profiles)
+-- local
+-- f: RAW_FILE
+-- fn: FILE_NAME
+-- do
+-- create fn.make_from_string (directory_name)
+-- fn.set_file_name ("user_profiles.db")
+-- create f.make (fn.string)
+-- if not f.exists or else f.is_writable then
+-- f.open_write
+-- sed_file_store (a_user_profiles, f)
+-- f.close
+-- end
+-- end
+
+feature {NONE} -- Implementation
+
+ ensure_directory_exists (dn: STRING)
+ local
+ d: DIRECTORY
+ do
+ d := tmp_dir
+ d.make (dn)
+ if not d.exists then
+ d.recursive_create_dir
+ end
+ end
+
+feature {NONE} -- Implementation
+
+ tmp_dir: DIRECTORY
+ once
+ create Result.make (directory_name)
+ end
+
+invariant
+
+end
diff --git a/draft/application/cms/src/core/storage/cms_storage.e b/draft/application/cms/src/core/storage/cms_storage.e
new file mode 100644
index 00000000..cfd9aec5
--- /dev/null
+++ b/draft/application/cms/src/core/storage/cms_storage.e
@@ -0,0 +1,105 @@
+note
+ description : "[
+ CMS interface to storage
+ ]"
+ date : "$Date$"
+ revision : "$Revision$"
+
+deferred class
+ CMS_STORAGE
+
+feature {NONE} -- Initialization
+
+ initialize
+ do
+ end
+
+feature -- Access: user
+
+ users_count: INTEGER
+ deferred
+ end
+
+ fill_user_profile (a_user: CMS_USER)
+ deferred
+ end
+
+ all_users: LIST [CMS_USER]
+ deferred
+ end
+
+ user_by_id (a_id: like {CMS_USER}.id): detachable CMS_USER
+ require
+ a_id > 0
+ deferred
+ ensure
+ same_id: Result /= Void implies Result.id = a_id
+ no_password: Result /= Void implies Result.password = Void
+ end
+
+ user_by_name (a_name: like {CMS_USER}.name): detachable CMS_USER
+ require
+ a_name /= Void and then not a_name.is_empty
+ deferred
+ ensure
+ no_password: Result /= Void implies Result.password = Void
+ end
+
+ user_by_email (a_email: like {CMS_USER}.email): detachable CMS_USER
+ deferred
+ ensure
+ no_password: Result /= Void implies Result.password = Void
+ end
+
+ encoded_password (a_raw_password: STRING_32): attached like {CMS_USER}.encoded_password
+ deferred
+ end
+
+feature -- Change: user
+
+ save_user (a_user: CMS_USER)
+ deferred
+ ensure
+ a_user_password_is_encoded: a_user.password = Void and a_user.encoded_password /= Void
+ a_user.has_id
+ end
+
+feature -- Email
+
+ save_email (a_email: CMS_EMAIL)
+ deferred
+ end
+
+feature -- Log
+
+ recent_logs (a_lower: INTEGER; a_count: INTEGER): LIST [CMS_LOG]
+ deferred
+ end
+
+ log (a_id: like {CMS_LOG}.id): detachable CMS_LOG
+ require
+ a_id > 0
+ deferred
+ end
+
+ save_log (a_log: CMS_LOG)
+ deferred
+ end
+
+feature -- Node
+
+ recent_nodes (a_lower: INTEGER; a_count: INTEGER): LIST [CMS_NODE]
+ deferred
+ end
+
+ node (a_id: INTEGER): detachable CMS_NODE
+ require
+ a_id > 0
+ deferred
+ end
+
+ save_node (a_node: CMS_NODE)
+ deferred
+ end
+
+end
diff --git a/draft/application/cms/src/core/theme/cms_html_template.e b/draft/application/cms/src/core/theme/cms_html_template.e
new file mode 100644
index 00000000..86a93f50
--- /dev/null
+++ b/draft/application/cms/src/core/theme/cms_html_template.e
@@ -0,0 +1,13 @@
+note
+ description: "Summary description for {WSF_CMS_HTML_TEMPLATE}."
+ author: ""
+ date: "$Date$"
+ revision: "$Revision$"
+
+deferred class
+ CMS_HTML_TEMPLATE
+
+inherit
+ CMS_TEMPLATE
+
+end
diff --git a/draft/application/cms/src/core/theme/cms_page_template.e b/draft/application/cms/src/core/theme/cms_page_template.e
new file mode 100644
index 00000000..74c36646
--- /dev/null
+++ b/draft/application/cms/src/core/theme/cms_page_template.e
@@ -0,0 +1,12 @@
+note
+ description: "Summary description for {CMS_PAGE_TEMPLATE}."
+ author: ""
+ date: "$Date$"
+ revision: "$Revision$"
+
+deferred class
+ CMS_PAGE_TEMPLATE
+
+inherit
+ CMS_TEMPLATE
+end
diff --git a/draft/application/cms/src/core/theme/cms_template.e b/draft/application/cms/src/core/theme/cms_template.e
new file mode 100644
index 00000000..791844c9
--- /dev/null
+++ b/draft/application/cms/src/core/theme/cms_template.e
@@ -0,0 +1,81 @@
+note
+ description: "Summary description for {WSF_CMS_PAGE_TEMPLATE}."
+ author: ""
+ date: "$Date$"
+ revision: "$Revision$"
+
+deferred class
+ CMS_TEMPLATE
+
+feature -- Access
+
+ theme: CMS_THEME
+ deferred
+ end
+
+ variables: HASH_TABLE [detachable ANY, STRING]
+ deferred
+ end
+
+ prepare (page: CMS_HTML_PAGE)
+ deferred
+ end
+
+ to_html (page: CMS_HTML_PAGE): STRING
+ deferred
+ end
+
+feature {NONE} -- Implementation
+
+ apply_template_engine (s: STRING_8)
+ local
+ p,n: INTEGER
+ k: STRING
+ sv: detachable STRING
+ do
+ from
+ n := s.count
+ p := 1
+ until
+ p = 0
+ loop
+ p := s.index_of ('$', p)
+ if p > 0 then
+ k := next_identifier (s, p + 1)
+ s.remove_substring (p, p + k.count)
+ sv := Void
+ if attached variables.item (k) as l_value then
+
+ if attached {STRING_8} l_value as s8 then
+ sv := s8
+ elseif attached {STRING_32} l_value as s32 then
+ sv := s32.as_string_8 -- FIXME: use html encoder
+ else
+ sv := l_value.out
+ end
+ s.insert_string (sv, p)
+ p := p + sv.count
+ else
+ debug
+ s.insert_string ("$" + k, p)
+ end
+ end
+ end
+ end
+ end
+
+ next_identifier (s: STRING; a_index: INTEGER): STRING
+ local
+ i: INTEGER
+ do
+ from
+ i := a_index
+ until
+ not (s[i].is_alpha_numeric or s[i] = '_' or s[i] = '.')
+ loop
+ i := i + 1
+ end
+ Result := s.substring (a_index, i - 1)
+ end
+
+end
diff --git a/draft/application/cms/src/core/theme/cms_theme.e b/draft/application/cms/src/core/theme/cms_theme.e
new file mode 100644
index 00000000..bc3630a1
--- /dev/null
+++ b/draft/application/cms/src/core/theme/cms_theme.e
@@ -0,0 +1,69 @@
+note
+ description: "Summary description for {WSF_CMS_THEME}."
+ author: ""
+ date: "$Date$"
+ revision: "$Revision$"
+
+deferred class
+ CMS_THEME
+
+inherit
+ CMS_COMMON_API
+
+
+feature {NONE} -- Access
+
+ service: CMS_SERVICE
+ deferred
+ end
+
+ base_url: detachable READABLE_STRING_8
+ -- Base url if any.
+ do
+ Result := service.base_url
+ end
+
+feature -- Access
+
+ name: STRING
+ deferred
+ end
+
+ regions: ARRAY [STRING]
+ deferred
+-- Result := <<"header", "content", "footer">>
+ end
+
+ page_template: CMS_TEMPLATE
+ deferred
+ end
+
+feature -- Conversion
+
+ menu_html (a_menu: CMS_MENU; is_horizontal: BOOLEAN): STRING_8
+ do
+ create Result.make_from_string ("
")
+ if is_horizontal then
+ Result.append ("
%N")
+ else
+ Result.append ("
%N")
+ end
+ across
+ a_menu as c
+ loop
+ if c.item.is_active then
+ Result.append ("