Conflicts:
	draft/application/cms/cms.ecf
	draft/application/cms/example/src/web_cms.e
	draft/application/cms/src/cms_configuration.e
	draft/application/cms/src/cms_default_setup.e
	draft/application/cms/src/cms_service.e
	draft/application/cms/src/cms_setup.e
	draft/application/cms/src/handler/cms_file_system_handler.e
	draft/application/cms/src/kernel/content/format/filters/cms_html_filter.e
	draft/application/cms/src/modules/debug/debug_module.e
	draft/application/cms/src/notification/cms_email.e
	draft/application/cms/src/notification/cms_storage_mailer.e
	draft/application/cms/src/storage/cms_sed_storage.e
	draft/application/cms/src/storage/cms_storage.e
	library/runtime/process/notification_email/notification_external_mailer.e
	tools/bin/ecf_updater.exe
This commit is contained in:
jvelilla
2013-06-18 09:56:53 -03:00
70 changed files with 4048 additions and 554 deletions

View File

@@ -23,6 +23,7 @@
<library name="wsf" location="..\..\..\library\server\wsf\wsf-safe.ecf" readonly="false"/>
<library name="wsf_html" location="..\..\..\library\server\wsf_html\wsf_html-safe.ecf" readonly="false"/>
<library name="wsf_session" location="..\..\..\library\server\wsf\wsf_session-safe.ecf" readonly="false"/>
<library name="notification_email" location="..\..\..\library\runtime\process\notification_email\notification_email-safe.ecf"/>
<cluster name="src" location=".\src\" recursive="true"/>
</target>
</system>

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-10-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-10-0 http://www.eiffel.com/developers/xml/configuration-1-10-0.xsd" name="cms" library_target="cms" uuid="0D24AE3C-61DA-4E81-8DCF-90C2E65FB669">
<target name="cms">
<root all_classes="true"/>
<file_rule>
<exclude>/EIFGENs$</exclude>
<exclude>/CVS$</exclude>
<exclude>/.svn$</exclude>
</file_rule>
<option warning="true" syntax="transitional">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<setting name="concurrency" value="thread"/>
<setting name="exception_trace" value="true"/>
<library name="base" location="$ISE_LIBRARY\library\base\base.ecf"/>
<library name="encoder" location="..\..\..\library\text\encoder\encoder.ecf" readonly="false"/>
<library name="wsf_html" location="..\..\..\library\server\wsf_html\wsf_html.ecf" readonly="false"/>
<library name="http" location="..\..\..\library\network\protocol\http\http.ecf" readonly="false"/>
<library name="openid" location="..\..\..\library\security\openid\consumer\openid.ecf" />
<library name="process" location="$ISE_LIBRARY\library\process\process.ecf"/>
<library name="time" location="$ISE_LIBRARY\library\time\time.ecf"/>
<library name="uuid" location="$ISE_LIBRARY\library\uuid\uuid.ecf"/>
<library name="uri_template" location="..\..\..\library\text\parser\uri_template\uri_template.ecf"/>
<library name="wsf" location="..\..\..\library\server\wsf\wsf.ecf" readonly="false"/>
<library name="wsf_session" location="..\..\..\library\server\wsf\wsf_session.ecf" readonly="false"/>
<library name="notification_email" location="..\..\..\library\runtime\process\notification_email\notification_email.ecf"/>
<cluster name="src" location=".\src\" recursive="true">
</cluster>
</target>
</system>

View File

@@ -0,0 +1,135 @@
note
description: "[
This class implements the Demo of WEB CMS service
]"
class
WEB_CMS
inherit
WSF_DEFAULT_SERVICE
redefine
initialize
end
create
make_and_launch
feature {NONE} -- Initialization
initialize
local
args: ARGUMENTS_32
cfg: detachable READABLE_STRING_32
i,n: INTEGER
do
--| Arguments
create args
from
i := 1
n := args.argument_count
until
i > n or cfg /= Void
loop
if attached args.argument (i) as s then
if s.same_string_general ("--config") or s.same_string_general ("-c") then
if i < n then
cfg := args.argument (i + 1)
end
end
end
i := i + 1
end
if cfg = Void then
if file_exists ("cms.ini") then
cfg := {STRING_32} "cms.ini"
end
end
--| EWF settings
service_options := create {WSF_SERVICE_LAUNCHER_OPTIONS_FROM_INI}.make_from_file ("ewf.ini")
Precursor
--| CMS initialization
launch_cms (cms_setup (cfg))
end
cms_setup (a_cfg_fn: detachable READABLE_STRING_GENERAL): CMS_CUSTOM_SETUP
do
if a_cfg_fn /= Void then
create Result.make_from_file (a_cfg_fn)
else
create Result -- Default
end
setup_modules (Result)
setup_storage (Result)
end
launch_cms (a_setup: CMS_SETUP)
local
cms: CMS_SERVICE
do
create cms.make (a_setup)
on_launched (cms)
cms_service := cms
end
feature -- Execution
cms_service: CMS_SERVICE
execute (req: WSF_REQUEST; res: WSF_RESPONSE)
do
cms_service.execute (req, res)
end
feature -- Access
setup_modules (a_setup: CMS_SETUP)
local
m: CMS_MODULE
do
create {DEMO_MODULE} m.make
m.enable
a_setup.add_module (m)
create {SHUTDOWN_MODULE} m.make
m.enable
a_setup.add_module (m)
create {DEBUG_MODULE} m.make
m.enable
a_setup.add_module (m)
create {OPENID_MODULE} m.make
m.enable
a_setup.add_module (m)
end
setup_storage (a_setup: CMS_SETUP)
do
end
feature -- Event
on_launched (cms: CMS_SERVICE)
local
e: CMS_EMAIL
do
create e.make (cms.site_email, cms.site_email, "[" + cms.site_name + "] launched...", "The site [" + cms.site_name + "] was launched at " + (create {DATE_TIME}.make_now_utc).out + " UTC.")
cms.mailer.safe_process_email (e)
end
feature -- Helper
file_exists (fn: READABLE_STRING_GENERAL): BOOLEAN
local
f: RAW_FILE
do
create f.make_with_name (fn)
Result := f.exists and then f.is_readable
end
end

View File

@@ -0,0 +1,311 @@
note
description: "Summary description for {CMS_CONFIGURATION}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
CMS_CONFIGURATION
inherit
ANY
SHARED_EXECUTION_ENVIRONMENT
export
{NONE} all
end
create
make,
make_from_file
feature {NONE} -- Initialization
make
do
create options.make_equal (10)
analyze
end
make_from_file (a_filename: READABLE_STRING_GENERAL)
-- Initialize `Current'.
local
p: PATH
do
make
create p.make_from_string (a_filename)
configuration_location := p
import_from_path (p)
analyze
end
analyze
do
get_root_location
get_var_location
get_themes_location
get_files_location
end
feature -- Access
configuration_location: detachable PATH
option (a_name: READABLE_STRING_GENERAL): detachable ANY
do
Result := options.item (a_name)
end
options: STRING_TABLE [STRING_32]
feature -- Conversion
append_to_string (s: STRING)
local
utf: UTF_CONVERTER
do
s.append ("Options:%N")
across
options as c
loop
s.append (c.key.to_string_8)
s.append_character ('=')
utf.string_32_into_utf_8_string_8 (c.item, s)
s.append_character ('%N')
end
s.append ("Specific:%N")
s.append ("root_location=" + root_location.utf_8_name + "%N")
s.append ("var_location=" + var_location.utf_8_name + "%N")
s.append ("files_location=" + files_location.utf_8_name + "%N")
s.append ("themes_location=" + themes_location.utf_8_name + "%N")
end
feature -- Element change
set_option (a_name: READABLE_STRING_GENERAL; a_value: STRING_32)
do
options.force (a_value, a_name.as_string_8)
end
feature -- Access
var_location: PATH
root_location: PATH
files_location: PATH
themes_location: PATH
theme_name (dft: detachable like theme_name): READABLE_STRING_8
do
if attached options.item ("theme") as s then
Result := s
elseif dft /= Void then
Result := dft
else
Result := "default"
end
end
site_id: READABLE_STRING_8
do
if attached options.item ("site.id") as s then
Result := s
else
Result := "_EWF_CMS_NO_ID_"
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_url (dft: like site_url): READABLE_STRING_8
do
if attached options.item ("site.url") as s then
Result := s
else
Result := dft
end
if Result /= Void then
if Result.is_empty then
-- ok
elseif not Result.ends_with ("/") then
Result := Result + "/"
end
end
end
site_script_url (dft: like site_script_url): detachable READABLE_STRING_8
do
if attached options.item ("site.script_url") as s then
Result := s
else
Result := dft
end
if Result /= Void then
if Result.is_empty then
elseif not Result.ends_with ("/") then
Result := Result + "/"
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 -- Change
get_var_location
local
utf: UTF_CONVERTER
do
if attached options.item ("var-dir") as s then
create var_location.make_from_string (utf.utf_8_string_8_to_escaped_string_32 (s))
else
var_location := execution_environment.current_working_path
end
end
get_root_location
local
utf: UTF_CONVERTER
do
if attached options.item ("root-dir") as s then
create root_location.make_from_string (utf.utf_8_string_8_to_escaped_string_32 (s))
else
root_location := execution_environment.current_working_path
end
end
get_files_location
local
utf: UTF_CONVERTER
do
if attached options.item ("files-dir") as s then
create files_location.make_from_string (utf.utf_8_string_8_to_escaped_string_32 (s))
else
create files_location.make_from_string ("files")
end
end
get_themes_location
local
utf: UTF_CONVERTER
do
if attached options.item ("themes-dir") as s then
create themes_location.make_from_string (utf.utf_8_string_8_to_escaped_string_32 (s))
else
themes_location := root_location.extended ("themes")
end
end
feature {NONE} -- Implementation
import_from_file (fn: READABLE_STRING_GENERAL)
do
import_from_path (create {PATH}.make_from_string (fn))
end
import_from_path (a_filename: PATH)
-- Import ini file content
local
f: PLAIN_TEXT_FILE
l,v: STRING_8
p: INTEGER
do
create f.make_with_path (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 then
if l[1] = '#' then
-- commented line
else
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
if l.is_case_insensitive_equal ("@include") then
import_from_file (resolved_string (v))
else
set_option (l.as_lower, resolved_string (v))
end
end
end
end
f.read_line
end
f.close
end
end
feature {NONE} -- Environment
resolved_string (s: READABLE_STRING_8): STRING_32
-- Resolved `s' using `options' or else environment variables.
local
i,n,b,e: INTEGER
k: detachable READABLE_STRING_8
do
from
i := 1
n := s.count
create Result.make (s.count)
until
i > n
loop
if i + 1 < n and then s[i] = '$' and then s[i+1] = '{' then
b := i + 2
e := s.index_of ('}', b) - 1
if e > 0 then
k := s.substring (b, e)
if attached option (k) as v then
if attached {READABLE_STRING_32} v as s32 then
Result.append (s32)
else
Result.append (v.out)
end
i := e + 1
elseif attached execution_environment.item (k) as v then
Result.append (v)
i := e + 1
else
Result.extend (s[i])
end
else
Result.extend (s[i])
end
else
Result.extend (s[i])
end
i := i + 1
end
end
end

View File

@@ -0,0 +1,132 @@
note
description: "Summary description for {CMS_DEFAULT_SETUP}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
CMS_DEFAULT_SETUP
inherit
CMS_SETUP
redefine
default_create
end
create
default_create,
make,
make_from_file
feature {NONE} -- Initialization
make (a_cfg: CMS_CONFIGURATION)
do
configuration := a_cfg
default_create
end
make_from_file (fn: READABLE_STRING_GENERAL)
local
cfg: CMS_CONFIGURATION
do
create cfg.make_from_file (fn)
make (cfg)
end
default_create
do
Precursor
build_modules
build_storage
build_session_manager
build_auth_engine
build_mailer
end
feature -- Access
modules: ARRAYED_LIST [CMS_MODULE]
storage: CMS_STORAGE
-- CMS persistent layer
session_manager: WSF_SESSION_MANAGER
-- CMS Session manager
auth_engine: CMS_AUTH_ENGINE
-- CMS Authentication engine
mailer: NOTIFICATION_MAILER
feature {NONE} -- Initialization
build_modules
local
m: CMS_MODULE
do
create modules.make (3)
-- Core
create {USER_MODULE} m.make
m.enable
modules.extend (m)
create {ADMIN_MODULE} m.make
m.enable
modules.extend (m)
create {NODE_MODULE} m.make
m.enable
modules.extend (m)
end
build_storage
local
dn: PATH
do
if attached configuration as cfg and then attached cfg.var_location as l_site_var_dir then
dn := l_site_var_dir
else
create dn.make_current
end
create {CMS_SED_STORAGE} storage.make (dn.extended ("_storage_").name)
end
build_session_manager
local
dn: PATH
do
if attached configuration as cfg and then attached cfg.var_location as l_site_var_dir then
dn := l_site_var_dir
else
create dn.make_empty
end
dn := dn.extended ("_storage_").extended ("_sessions_")
create {WSF_FS_SESSION_MANAGER} session_manager.make_with_folder (dn.name)
end
build_auth_engine
do
create {CMS_STORAGE_AUTH_ENGINE} auth_engine.make (storage)
end
build_mailer
local
ch_mailer: NOTIFICATION_CHAIN_MAILER
st_mailer: CMS_STORAGE_MAILER
do
create st_mailer.make (storage)
create ch_mailer.make (st_mailer)
ch_mailer.set_next (create {NOTIFICATION_SENDMAIL_MAILER})
mailer := ch_mailer
end
feature -- Change
add_module (m: CMS_MODULE)
do
modules.force (m)
end
end

View File

@@ -0,0 +1,442 @@
note
description: "[
This class implements the CMS service
It could be used to implement the main EWF service, or
even for a specific handler.
]"
class
CMS_SERVICE
inherit
WSF_SERVICE
create
make
feature {NONE} -- Initialization
make (a_setup: CMS_SETUP)
local
cfg: detachable CMS_CONFIGURATION
do
cfg := a_setup.configuration
if cfg = Void then
create cfg.make
end
configuration := cfg
base_url := a_setup.base_url
site_id := cfg.site_id
site_url := cfg.site_url ("")
site_name := cfg.site_name ("EWF::CMS")
site_email := cfg.site_email ("webmaster")
site_dir := cfg.root_location
site_var_dir := cfg.var_location
files_location := cfg.files_location
themes_location := cfg.themes_location
theme_name := cfg.theme_name ("default")
set_script_url (cfg.site_script_url (Void)) -- Temporary value
compute_theme_resource_location
create content_types.make (3)
modules := a_setup.modules
storage := a_setup.storage
session_manager := a_setup.session_manager
auth_engine := a_setup.auth_engine
mailer := a_setup.mailer
initialize_storage
initialize_auth_engine
initialize_session_manager
initialize_mailer
initialize_router
initialize_modules
end
initialize_session_manager
-- local
-- dn: DIRECTORY_NAME
do
-- create dn.make_from_string (site_var_dir)
-- dn.extend ("_storage_")
-- dn.extend ("_sessions_")
-- create {WSF_FS_SESSION_MANAGER} session_manager.make_with_folder (dn.string)
end
initialize_storage
do
if not storage.has_user then
initialize_users
end
end
initialize_users
require
has_no_user: not storage.has_user
local
u: CMS_USER
ur: CMS_USER_ROLE
do
create u.make_new ("admin")
u.set_password ("istrator")
storage.save_user (u)
create ur.make_with_id (1, "anonymous")
storage.save_user_role (ur)
create ur.make_with_id (2, "authenticated")
ur.add_permission ("create page")
ur.add_permission ("edit page")
storage.save_user_role (ur)
end
initialize_mailer
local
-- ch_mailer: CMS_CHAIN_MAILER
-- st_mailer: CMS_STORAGE_MAILER
do
-- create st_mailer.make (storage)
-- create ch_mailer.make (st_mailer)
-- ch_mailer.set_next (create {CMS_SENDMAIL_MAILER})
-- mailer := ch_mailer
end
initialize_router
local
-- h: CMS_HANDLER
file_hdl: CMS_FILE_SYSTEM_HANDLER
do
create router.make (10)
router.set_base_url (base_url)
router.map (create {WSF_URI_MAPPING}.make ("/", create {CMS_HANDLER}.make (agent handle_home)))
router.map (create {WSF_URI_MAPPING}.make ("/favicon.ico", create {CMS_HANDLER}.make (agent handle_favicon)))
create file_hdl.make_with_path (files_location)
file_hdl.disable_index
file_hdl.set_max_age (8*60*60)
router.map (create {WSF_STARTS_WITH_MAPPING}.make ("/files/", file_hdl))
create file_hdl.make_with_path (theme_resource_location)
file_hdl.set_max_age (8*60*60)
router.map (create {WSF_STARTS_WITH_MAPPING}.make ("/theme/", file_hdl))
end
initialize_modules
do
across
modules as m
loop
if m.item.is_enabled then
m.item.register (Current)
if attached {CMS_HOOK_AUTO_REGISTER} m.item as h_auto then
h_auto.hook_auto_register (Current)
end
end
end
end
initialize_auth_engine
do
-- create {CMS_STORAGE_AUTH_ENGINE} auth_engine.make (storage)
end
feature -- Access
configuration: CMS_CONFIGURATION
auth_engine: CMS_AUTH_ENGINE
modules: LIST [CMS_MODULE]
feature -- Hook: menu_alter
add_menu_alter_hook (h: like menu_alter_hooks.item)
local
lst: like menu_alter_hooks
do
lst := menu_alter_hooks
if lst = Void then
create lst.make (1)
menu_alter_hooks := lst
end
if not lst.has (h) then
lst.force (h)
end
end
menu_alter_hooks: detachable ARRAYED_LIST [CMS_HOOK_MENU_ALTER]
call_menu_alter_hooks (m: CMS_MENU_SYSTEM; a_execution: CMS_EXECUTION)
do
if attached menu_alter_hooks as lst then
across
lst as c
loop
c.item.menu_alter (m, a_execution)
end
end
end
feature -- Hook: form_alter
add_form_alter_hook (h: like form_alter_hooks.item)
local
lst: like form_alter_hooks
do
lst := form_alter_hooks
if lst = Void then
create lst.make (1)
form_alter_hooks := lst
end
if not lst.has (h) then
lst.force (h)
end
end
form_alter_hooks: detachable ARRAYED_LIST [CMS_HOOK_FORM_ALTER]
call_form_alter_hooks (f: CMS_FORM; a_form_data: detachable WSF_FORM_DATA; a_execution: CMS_EXECUTION)
do
if attached form_alter_hooks as lst then
across
lst as c
loop
c.item.form_alter (f, a_form_data, a_execution)
end
end
end
feature -- Hook: block
add_block_hook (h: like block_hooks.item)
local
lst: like block_hooks
do
lst := block_hooks
if lst = Void then
create lst.make (1)
block_hooks := lst
end
if not lst.has (h) then
lst.force (h)
end
end
block_hooks: detachable ARRAYED_LIST [CMS_HOOK_BLOCK]
hook_block_view (a_execution: CMS_EXECUTION)
do
if attached block_hooks as lst then
across
lst as c
loop
across
c.item.block_list as blst
loop
c.item.get_block_view (blst.item, a_execution)
end
end
end
end
feature -- Router
site_id: READABLE_STRING_8
site_name: READABLE_STRING_32
site_email: READABLE_STRING_8
site_url: READABLE_STRING_8
site_dir: PATH
site_var_dir: PATH
files_location: PATH
themes_location: PATH
compute_theme_resource_location
do
theme_resource_location := themes_location.extended (theme_name).extended ("res")
end
theme_resource_location: PATH
theme_name: READABLE_STRING_32
router: WSF_ROUTER
map_uri_template (tpl: STRING; proc: PROCEDURE [ANY, TUPLE [req: WSF_REQUEST; res: WSF_RESPONSE]])
do
router.map (create {WSF_URI_TEMPLATE_MAPPING}.make_from_template (tpl, create {CMS_HANDLER}.make (proc)))
end
map_uri (a_uri: STRING; proc: PROCEDURE [ANY, TUPLE [req: WSF_REQUEST; res: WSF_RESPONSE]])
do
router.map (create {WSF_URI_MAPPING}.make (a_uri, create {CMS_HANDLER}.make (proc)))
end
feature -- URL related
front_path: STRING
do
if attached base_url as l_base_url then
Result := l_base_url + "/"
else
Result := "/"
end
end
urls_set: BOOLEAN
initialize_urls (req: WSF_REQUEST)
local
u: like base_url
do
if not urls_set then
u := base_url
if u = Void then
u := ""
end
urls_set := True
if site_url.is_empty then
site_url := req.absolute_script_url (u)
end
set_script_url (req.script_url (u))
end
end
base_url: detachable READABLE_STRING_8
-- Base url (related to the script path).
script_url: detachable READABLE_STRING_8
set_script_url (a_url: like script_url)
local
s: STRING_8
do
if a_url = Void then
script_url := Void
elseif not a_url.is_empty then
if a_url.ends_with ("/") then
create s.make_from_string (a_url)
else
create s.make (a_url.count + 1)
s.append (a_url)
s.append_character ('/')
end
script_url := s
end
ensure
attached script_url as l_url implies l_url.ends_with ("/")
end
feature -- Report
is_front_page (req: WSF_REQUEST): BOOLEAN
do
Result := req.path_info.same_string (front_path)
end
feature {CMS_EXECUTION, CMS_MODULE} -- Security report
user_has_permission (u: detachable CMS_USER; s: detachable READABLE_STRING_8): BOOLEAN
-- Anonymous or user `u' has permission for `s' ?
--| `s' could be "create page",
do
Result := storage.user_has_permission (u, s)
end
feature -- Storage
session_controller (req: WSF_REQUEST): CMS_SESSION_CONTROLER
-- New session controller for request `req'
do
create Result.make (req, session_manager, site_id)
end
session_manager: WSF_SESSION_MANAGER
-- CMS Session manager
storage: CMS_STORAGE
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
storage.save_log (l_log)
end
feature -- Content type
content_types: ARRAYED_LIST [CMS_CONTENT_TYPE]
-- Available content types
add_content_type (a_type: CMS_CONTENT_TYPE)
do
content_types.force (a_type)
end
content_type (a_name: READABLE_STRING_8): detachable CMS_CONTENT_TYPE
do
across
content_types as t
until
Result /= Void
loop
if t.item.name.same_string (a_name) then
Result := t.item
end
end
end
feature -- Notification
mailer: NOTIFICATION_MAILER
feature -- Core Execution
handle_favicon (req: WSF_REQUEST; res: WSF_RESPONSE)
local
fres: WSF_FILE_RESPONSE
do
create fres.make_with_path (theme_resource_location.extended ("favicon.ico"))
fres.set_expires_in_seconds (7 * 24 * 60 * 60) -- 7 jours
res.send (fres)
end
handle_home (req: WSF_REQUEST; res: WSF_RESPONSE)
do
(create {HOME_CMS_EXECUTION}.make (req, res, Current)).execute
end
execute (req: WSF_REQUEST; res: WSF_RESPONSE)
-- Default request handler if no other are relevant
local
e: CMS_EXECUTION
sess: WSF_ROUTER_SESSION
do
initialize_urls (req)
create sess
router.dispatch (req, res, sess)
if not sess.dispatched then
create {NOT_FOUND_CMS_EXECUTION} e.make (req, res, Current)
e.execute
end
end
end

View File

@@ -0,0 +1,55 @@
note
description: "Summary description for {CMS_SETUP}."
author: ""
date: "$Date$"
revision: "$Revision$"
deferred class
CMS_SETUP
feature -- Access
configuration: detachable CMS_CONFIGURATION
base_url: detachable READABLE_STRING_8
modules: LIST [CMS_MODULE]
deferred
end
storage: CMS_STORAGE
-- CMS persistent layer
deferred
end
session_manager: WSF_SESSION_MANAGER
-- CMS Session manager
deferred
end
auth_engine: CMS_AUTH_ENGINE
-- CMS Authentication engine
deferred
end
mailer: NOTIFICATION_MAILER
-- CMS email engine
deferred
end
feature -- Change
set_base_url (a_base_url: like base_url)
do
if a_base_url /= Void and then not a_base_url.is_empty then
base_url := a_base_url
else
base_url := Void
end
end
add_module (m: CMS_MODULE)
deferred
end
end

View File

@@ -0,0 +1,17 @@
note
description: "Summary description for {CMS_FILE_SYSTEM_HANDLER}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
CMS_FILE_SYSTEM_HANDLER
inherit
WSF_FILE_SYSTEM_HANDLER
create
make,
make_with_path
end

View File

@@ -0,0 +1,128 @@
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 ("&lt;" + c.item + "&gt; ")
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
p1 := p2 + 1
else
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

View File

@@ -0,0 +1,128 @@
note
description: "Summary description for {DEBUG_MODULE}."
date: "$Date$"
revision: "$Revision$"
class
DEBUG_MODULE
inherit
CMS_MODULE
-- CMS_HOOK_BLOCK
CMS_HOOK_AUTO_REGISTER
SHARED_EXECUTION_ENVIRONMENT
export
{NONE} all
end
create
make
feature {NONE} -- Initialization
make
do
name := "debug"
version := "1.0"
description := "Debug"
package := "cms"
end
feature {CMS_SERVICE} -- Registration
service: detachable CMS_SERVICE
register (a_service: CMS_SERVICE)
do
service := a_service
a_service.map_uri_template ("/debug/", agent handle_debug (a_service, ?, ?))
end
feature -- Hooks
-- block_list: ITERABLE [like {CMS_BLOCK}.name]
-- do
-- Result := <<"debug-info">>
-- end
-- get_block_view (a_block_id: detachable READABLE_STRING_8; a_execution: CMS_EXECUTION)
-- local
-- b: CMS_CONTENT_BLOCK
-- do
-- create b.make ("debug-info", "Debug", "... ", a_execution.formats.plain_text)
-- a_execution.add_block (b, Void)
-- end
feature -- Handler
handle_debug (cms: CMS_SERVICE; req: WSF_REQUEST; res: WSF_RESPONSE)
local
e: CMS_EXECUTION
s: STRING
do
if req.is_get_request_method then
create {ANY_CMS_EXECUTION} e.make (req, res, cms)
e.set_title ("DEBUG")
create s.make_empty
append_info_to ("Name", cms.site_name, e, s)
append_info_to ("Url", cms.site_url, e, s)
if attached cms.configuration as cfg and then attached cfg.configuration_location as l_loc then
s.append ("<hr/>")
append_info_to ("Configuration file", l_loc.name, e, s)
end
s.append ("<hr/>")
append_info_to ("Current dir", execution_environment.current_working_path.utf_8_name, e, s)
append_info_to ("Base url", cms.base_url, e, s)
append_info_to ("Script url", cms.script_url, e, s)
s.append ("<hr/>")
append_info_to ("Dir", cms.site_dir.utf_8_name, e, s)
append_info_to ("Var dir", cms.site_var_dir.utf_8_name, e, s)
s.append ("<hr/>")
append_info_to ("Theme", cms.theme_name, e, s)
append_info_to ("Theme location", cms.theme_resource_location.utf_8_name, e, s)
s.append ("<hr/>")
append_info_to ("Files location", cms.files_location.utf_8_name, e, s)
s.append ("<hr/>")
append_info_to ("Url", e.url ("/", Void), e, s)
if attached e.user as u then
append_info_to ("User", u.name, e, s)
append_info_to ("User url", e.user_url (u), e, s)
end
e.set_main_content (s)
else
create {NOT_FOUND_CMS_EXECUTION} e.make (req, res, cms)
end
e.execute
end
append_info_to (n: READABLE_STRING_8; v: detachable READABLE_STRING_GENERAL; e: CMS_EXECUTION; t: STRING)
do
t.append ("<li>")
t.append ("<strong>" + n + "</strong>: ")
if v /= Void then
t.append (e.html_encoded (v))
end
t.append ("</li>")
end
note
copyright: "Copyright (c) 1984-2013, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software
5949 Hollister Ave., Goleta, CA 93117 USA
Telephone 805-685-1006, Fax 805-685-6869
Website http://www.eiffel.com
Customer support http://support.eiffel.com
]"
end

View File

@@ -0,0 +1,18 @@
note
description : "[
Component representing an email
]"
author : "$Author$"
date : "$Date$"
revision : "$Revision$"
class
CMS_EMAIL
inherit
NOTIFICATION_EMAIL
create
make
end

View File

@@ -0,0 +1,38 @@
note
description: "Summary description for {CMS_CHAIN_MAILER}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
CMS_STORAGE_MAILER
inherit
NOTIFICATION_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: NOTIFICATION_EMAIL)
do
storage.save_email (a_email)
end
end

View File

@@ -0,0 +1,615 @@
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: READABLE_STRING_GENERAL)
-- Initialize `Current'.
do
create directory_name.make_from_string (dn)
ensure_directory_exists (directory_name)
create sed
initialize
end
directory_name: PATH
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
fn: PATH
f: RAW_FILE
do
fn := directory_name.extended (a_type)
ensure_directory_exists (fn)
fn := fn.extended (a_id.out)
-- .appended_with_extension ("txt")
create f.make_with_path (fn)
-- 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
fn: PATH
f: RAW_FILE
do
fn := directory_name.extended (a_type)
ensure_directory_exists (fn)
fn := fn.extended (a_id.out)
-- .append_with_extension ("txt")
create f.make_with_path (fn)
if f.exists and f.is_readable then
f.open_read
Result := sed_file_retrieved (f)
f.close
end
end
feature -- Access: user
has_user: BOOLEAN
-- Has any user?
do
Result := users_count > 0
end
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
is_valid_credential (u, p: READABLE_STRING_32): BOOLEAN
do
if attached user_by_name (u) as l_user then
Result := attached l_user.encoded_password as l_pass and then l_pass.same_string (encoded_password (p))
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 -- Access: user_role
user_role_by_id (a_id: INTEGER): detachable CMS_USER_ROLE
do
if attached {like user_role_by_id} object_with_id (a_id, "user_roles") as ur then
Result := ur
end
end
user_roles: LIST [CMS_USER_ROLE]
local
i: INTEGER
n: like last_sequence
do
n := last_sequence ("user_roles")
create {ARRAYED_LIST [CMS_USER_ROLE]} Result.make (n)
if n > 0 then
from
i := 1
until
i > n
loop
if attached user_role_by_id (i) as ur then
Result.force (ur)
end
i := i + 1
end
end
end
feature -- Change: user_role
save_user_role (a_role: CMS_USER_ROLE)
do
if not a_role.has_id then
a_role.set_id (next_sequence ("user_roles"))
end
save_object_with_id (a_role, a_role.id, "user_roles")
end
feature -- Email
save_email (a_email: NOTIFICATION_EMAIL)
local
dn: PATH
fn: PATH
f: RAW_FILE
ts: INTEGER_64
i: INTEGER
do
dn := directory_name.extended ("emails")
ensure_directory_exists (dn)
ts := (create {HTTP_DATE_TIME_UTILITIES}).unix_time_stamp (a_email.date)
from
fn := dn.extended (ts.out).appended_with_extension ("txt")
create f.make_with_path (fn)
until
not f.exists
loop
i := i + 1
fn := dn.extended (ts.out + "-" + i.out).appended_with_extension ("txt")
f.make_with_path (fn)
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: PATH
f: RAW_FILE
do
fn := directory_name.extended (a_type).appended_with_extension ("last_id")
create f.make_with_path (fn)
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: PATH
f: RAW_FILE
do
fn := directory_name.extended (a_type).appended_with_extension ("last_id")
create f.make_with_path (fn)
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: PATH
res: detachable like users_index
retried: INTEGER
do
fn := directory_name.extended ("users.db")
create f.make_with_path (fn)
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: PATH
do
fn := directory_name.extended ("users.db")
create f.make_with_path (fn)
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
feature -- Misc
custom_type (a_type: READABLE_STRING_8): STRING
do
Result := "custom__" + a_type
end
custom_value_id (a_name: READABLE_STRING_8; a_type: READABLE_STRING_8): INTEGER
-- Storage `id' for custom value named `a_name' if any.
-- If no such data exists, return 0
local
i,
l_id, l_last_id: INTEGER
t: STRING
do
t := custom_type (a_type)
l_last_id := last_sequence (t)
from
i := 1
until
i > l_last_id or l_id > 0
loop
if
attached {TUPLE [name: READABLE_STRING_8; value: attached like custom_value]} object_with_id (i, t) as obj and then
obj.name.same_string (a_name)
then
l_id := i
end
i := i + 1
end
end
set_custom_value (a_name: READABLE_STRING_8; a_value: attached like custom_value ; a_type: READABLE_STRING_8)
-- Save data `a_name:a_value' for type `a_type'
local
t: STRING
l_id: INTEGER
do
t := custom_type (a_type)
l_id := custom_value_id (a_name, a_type)
if l_id = 0 then
l_id := next_sequence (t)
end
save_object_with_id ([a_name, a_value], l_id, t)
end
custom_value (a_name: READABLE_STRING_8; a_type: READABLE_STRING_8): detachable TABLE_ITERABLE [READABLE_STRING_8, STRING_8]
-- Data for name `a_name' and type `a_type'.
local
i,
l_id, l_last_id: INTEGER
t: STRING
do
t := custom_type (a_type)
l_last_id := last_sequence (t)
from
i := 1
until
i > l_last_id or l_id > 0
loop
if
attached {TUPLE [name: READABLE_STRING_8; value: attached like custom_value]} object_with_id (i, t) as obj and then
obj.name.same_string (a_name)
then
l_id := i
Result := obj.value
end
i := i + 1
end
end
custom_value_names_where (a_where_key, a_where_value: READABLE_STRING_8; a_type: READABLE_STRING_8): detachable LIST [READABLE_STRING_8]
-- Name where custom value has item `a_where_key' same as `a_where_value' for type `a_type'.
local
i, l_last_id: INTEGER
t: STRING
l_key_found: BOOLEAN
res: ARRAYED_LIST [READABLE_STRING_8]
do
create res.make (0)
t := custom_type (a_type)
l_last_id := last_sequence (t)
from
i := 1
until
i > l_last_id
loop
if
attached {TUPLE [name: READABLE_STRING_8; value: attached like custom_value]} object_with_id (i, t) as d
then
l_key_found := False
across
d.value as c
until
l_key_found or Result /= Void
loop
if c.key.same_string (a_where_key) then
l_key_found := True
if c.item.same_string (a_where_value) then
res.force (d.name)
end
end
end
end
i := i + 1
end
if not res.is_empty then
Result := res
end
end
feature {NONE} -- Implementation
ensure_directory_exists (dn: PATH)
local
d: DIRECTORY
do
d := tmp_dir
d.make_with_path (dn)
if not d.exists then
d.recursive_create_dir
end
end
feature {NONE} -- Implementation
tmp_dir: DIRECTORY
once
create Result.make_with_path (directory_name)
end
invariant
end

View File

@@ -0,0 +1,186 @@
note
description : "[
CMS interface to storage
]"
date : "$Date$"
revision : "$Revision$"
deferred class
CMS_STORAGE
feature {NONE} -- Initialization
initialize
do
end
feature -- Access: user
has_user: BOOLEAN
-- Has any user?
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
is_valid_credential (u, p: READABLE_STRING_32): BOOLEAN
deferred
end
feature -- Change: user
save_user (a_user: CMS_USER)
deferred
ensure
a_user_password_is_encoded: a_user.password = Void
a_user.has_id
end
feature -- Access: roles and permissions
user_has_permission (u: detachable CMS_USER; s: detachable READABLE_STRING_8): BOOLEAN
-- Anonymous or user `u' has permission for `s' ?
--| `s' could be "create page",
do
if s = Void then
Result := True
elseif u = Void then
Result := user_role_has_permission (anonymous_user_role, s)
else
Result := user_role_has_permission (authenticated_user_role, s)
if not Result and attached u.roles as l_roles then
across
l_roles as r
until
Result
loop
if attached user_role_by_id (r.item) as ur then
Result := user_role_has_permission (ur, s)
end
end
end
end
end
anonymous_user_role: CMS_USER_ROLE
do
if attached user_role_by_id (1) as l_anonymous then
Result := l_anonymous
else
create Result.make ("anonymous")
end
end
authenticated_user_role: CMS_USER_ROLE
do
if attached user_role_by_id (2) as l_authenticated then
Result := l_authenticated
else
create Result.make ("authenticated")
end
end
user_role_has_permission (a_role: CMS_USER_ROLE; s: READABLE_STRING_8): BOOLEAN
do
Result := a_role.has_permission (s)
end
user_role_by_id (a_id: like {CMS_USER_ROLE}.id): detachable CMS_USER_ROLE
deferred
end
user_roles: LIST [CMS_USER_ROLE]
deferred
end
feature -- Change: roles and permissions
save_user_role (a_user_role: CMS_USER_ROLE)
deferred
end
feature -- Email
save_email (a_email: NOTIFICATION_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
feature -- Misc
set_custom_value (a_name: READABLE_STRING_8; a_value: attached like custom_value; a_type: READABLE_STRING_8)
-- Save data `a_name:a_value' for type `a_type'
deferred
end
custom_value (a_name: READABLE_STRING_8; a_type: READABLE_STRING_8): detachable TABLE_ITERABLE [READABLE_STRING_8, STRING_8]
-- Data for name `a_name' and type `a_type'.
deferred
end
custom_value_names_where (a_where_key, a_where_value: READABLE_STRING_8; a_type: READABLE_STRING_8): detachable LIST [READABLE_STRING_8]
-- Names where custom value has item `a_where_key' same as `a_where_value' for type `a_type'.
deferred
end
end