Added basic webapi system to ROC CMS system.

Added sql_delete routine to replace sql_modify with "DELETE FROM .." sql statement.
Fixed filter setup when a module has more than one filter.
Fixed filter setup for site,admin and webapi modes.
Added CMS_AUTH_FILTER, and check if user is already authenticated, then skip following auth filters.
Added specific webapi handler classes for root, user, access token, ...
Added user profile system to the core module.
Moved /user/{uid} from auth module to core module.
Added possibility to add html before and after a cms form. (useful to add a form before or after, as nested form are forbidden).
Now theme can be installed using roc install command.
This commit is contained in:
Jocelyn Fiat
2017-09-05 15:54:40 +02:00
parent 34f0aa5844
commit ac9d29b971
88 changed files with 3552 additions and 553 deletions

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-15-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-15-0 http://www.eiffel.com/developers/xml/configuration-1-15-0.xsd" name="cms" uuid="8CC0D052-57D1-4CAA-AFF1-448FA290734B" library_target="cms">
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-17-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-17-0 http://www.eiffel.com/developers/xml/configuration-1-17-0.xsd" name="cms" uuid="8CC0D052-57D1-4CAA-AFF1-448FA290734B" library_target="cms">
<target name="cms">
<root all_classes="true"/>
<file_rule>
@@ -7,9 +7,12 @@
<exclude>/CVS$</exclude>
<exclude>/EIFGENs$</exclude>
</file_rule>
<option warning="true" void_safety="all">
<option warning="true">
</option>
<setting name="concurrency" value="scoop"/>
<capability>
<concurrency support="scoop" use="scoop"/>
<void_safety support="all" use="all"/>
</capability>
<mapping old_name="CMS_LAYOUT" new_name="CMS_ENVIRONMENT"/>
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
<library name="base_extension" location="$ISE_LIBRARY\library\base_extension\base_extension-safe.ecf"/>
@@ -20,9 +23,11 @@
<library name="encoder" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\text\encoder\encoder-safe.ecf" readonly="false"/>
<library name="error" location="$ISE_LIBRARY\contrib\library\utility\general\error\error-safe.ecf"/>
<library name="http" location="$ISE_LIBRARY\contrib\library\network\protocol\http\http-safe.ecf"/>
<library name="http_authorization" location="$ISE_LIBRARY\contrib\library\web\authentication\http_authorization\http_authorization-safe.ecf"/>
<library name="i18n" location="$ISE_LIBRARY\library\i18n\i18n-safe.ecf"/>
<library name="json" location="$ISE_LIBRARY\contrib\library\text\parser\json\library\json-safe.ecf" readonly="false"/>
<library name="kmp_matcher" location="$ISE_LIBRARY\library\text\regexp\kmp_matcher\kmp_matcher-safe.ecf"/>
<library name="microdata" location="$ISE_LIBRARY\contrib\library\text\parser\microdata\microdata.ecf"/>
<library name="net" location="$ISE_LIBRARY\library\net\net-safe.ecf"/>
<library name="notification_mailer" location="$ISE_LIBRARY\contrib\library\runtime\process\notification_email\notification_email-safe.ecf"/>
<library name="smarty" location="$ISE_LIBRARY\contrib\library\text\template\smarty\smarty-safe.ecf" readonly="false"/>

View File

@@ -20,6 +20,7 @@
<library name="encoder" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\text\encoder\encoder.ecf" readonly="false"/>
<library name="error" location="$ISE_LIBRARY\contrib\library\utility\general\error\error.ecf"/>
<library name="http" location="$ISE_LIBRARY\contrib\library\network\protocol\http\http.ecf"/>
<library name="http_authorization" location="$ISE_LIBRARY\contrib\library\web\authentication\http_authorization\http_authorization.ecf"/>
<library name="i18n" location="$ISE_LIBRARY\library\i18n\i18n.ecf"/>
<library name="json" location="$ISE_LIBRARY\contrib\library\text\parser\json\library\json.ecf" readonly="false"/>
<library name="kmp_matcher" location="$ISE_LIBRARY\library\text\regexp\kmp_matcher\kmp_matcher.ecf"/>

View File

@@ -3,7 +3,11 @@
"project": "demo-safe.ecf",
"location": ".",
"site_directory": "site",
"themes": {
"admin": { "location": "../../themes/admin", "mode": "link" }
},
"modules": {
"demo": { "location": "modules/demo" },
"core": { "location": "../../modules/core" },
"admin": { "location": "../../modules/admin" },
"auth": { "location": "../../modules/auth" },

View File

@@ -57,13 +57,15 @@ output=site\db\mails
#openid.token=
#oauth.token=
[webapi]
mode=on
[administration]
base_path=/roc-admin
#theme=admin
theme=admin
# CMS Installation, are accessible by "all", "none" or uppon "permission". (default is none)
installation_access=all
[dev]
# masquerade: all, none, permission. Default is none.
masquerade=none
masquerade=all

View File

@@ -98,19 +98,6 @@ feature -- Roles
roles: detachable LIST [CMS_USER_ROLE]
-- If set, list of roles for current user.
feature -- Access: data
item (k: READABLE_STRING_GENERAL): detachable ANY assign put
-- Additional item data associated with key `k'.
do
if attached items as tb then
Result := tb.item (k)
end
end
items: detachable STRING_TABLE [detachable ANY]
-- Additional data.
feature -- Status report
has_id: BOOLEAN
@@ -223,29 +210,6 @@ feature -- Element change: roles
roles := lst
end
feature -- Change element: data
put (d: like item; k: READABLE_STRING_GENERAL)
-- Associate data item `d' with key `k'.
local
tb: like items
do
tb := items
if tb = Void then
create tb.make (1)
items := tb
end
tb.force (d, k)
end
remove (k: READABLE_STRING_GENERAL)
-- Remove data item associated with key `k'.
do
if attached items as tb then
tb.remove (k)
end
end
feature -- Status change
mark_not_active

View File

@@ -0,0 +1,72 @@
note
description: "[
User profile used to extend information associated with a {CMS_USER}.
]"
date: "$Date$"
revision: "$Revision$"
class
CMS_USER_PROFILE
inherit
TABLE_ITERABLE [READABLE_STRING_32, READABLE_STRING_GENERAL]
create
make
feature {NONE} -- Initialization
make
-- Create Current profile.
do
create items.make (0)
end
feature -- Access
item alias "[]" (k: READABLE_STRING_GENERAL): detachable READABLE_STRING_32
-- Profile item associated with key `k`.
do
Result := items.item (k)
end
has_key (k: READABLE_STRING_GENERAL): BOOLEAN
-- Has a profile item associated with key `k`?
do
Result := items.has (k)
end
count: INTEGER
do
Result := items.count
end
is_empty: BOOLEAN
do
Result := items.is_empty
end
feature -- Change
force (v: READABLE_STRING_GENERAL; k: READABLE_STRING_GENERAL)
-- Associated value `v` with key `k`.
do
items.force (v.to_string_32, k)
end
feature -- Access
new_cursor: TABLE_ITERATION_CURSOR [READABLE_STRING_32, READABLE_STRING_GENERAL]
-- Fresh cursor associated with current structure
do
Result := items.new_cursor
end
feature {NONE} -- Implementation
items: STRING_TABLE [READABLE_STRING_32]
;note
copyright: "2011-2014, Javier Velilla, Jocelyn Fiat, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -110,6 +110,12 @@ feature -- Query
sql_post_execution
end
sql_delete (a_sql_statement: STRING; a_params: detachable STRING_TABLE [detachable ANY])
-- <Precursor>
do
sql_modify (a_sql_statement, a_params)
end
sql_rows_count: INTEGER
-- Number of rows for last sql execution.
do

View File

@@ -230,6 +230,12 @@ feature -- Operation
end
end
sql_delete (a_sql_statement: STRING; a_params: detachable STRING_TABLE [detachable ANY])
-- <Precursor>
do
sql_modify (a_sql_statement, a_params)
end
sqlite_arguments (a_params: STRING_TABLE [detachable ANY]): ARRAYED_LIST [SQLITE_BIND_ARG [ANY]]
local
k: READABLE_STRING_GENERAL

View File

@@ -0,0 +1,29 @@
note
description: "Summary description for {CMS_AUTH_FILTER_WITH_LOGOUT}."
author: ""
date: "$Date$"
revision: "$Revision$"
deferred class
CMS_AUTH_STRATEGY_FILTER
inherit
CMS_AUTH_FILTER
redefine
set_current_user
end
feature -- Basic operations
auth_strategy: STRING
deferred
end
set_current_user (u: CMS_USER)
do
Precursor (u)
-- Record auth strategy:
api.set_execution_variable ({CMS_AUTHENTICATION_MODULE}.auth_strategy_execution_variable_name, auth_strategy)
end
end

View File

@@ -124,8 +124,6 @@ feature -- Router
a_router.handle ("/account/new-password", create {WSF_URI_AGENT_HANDLER}.make (agent handle_new_password(a_api, ?, ?)), a_router.methods_get_post)
a_router.handle ("/account/reset-password", create {WSF_URI_AGENT_HANDLER}.make (agent handle_reset_password(a_api, ?, ?)), a_router.methods_get_post)
a_router.handle ("/account/change/{field}", create {WSF_URI_TEMPLATE_AGENT_HANDLER}.make (agent handle_change_field (a_api, ?, ?)), a_router.methods_get_post)
a_router.handle ("/user/{uid}", create {CMS_USER_HANDLER}.make (a_api), a_router.methods_get)
end
feature -- Hooks configuration
@@ -206,6 +204,23 @@ feature -- Hooks configuration
end
end
feature -- Handler / Constants
auth_strategy_execution_variable_name: STRING = "auth_strategy"
-- Exevc
auth_strategy (req: WSF_REQUEST): detachable READABLE_STRING_8
-- Strategy used by current authentication.
-- note: if user is authenticated..
do
if
attached {READABLE_STRING_GENERAL} req.execution_variable (auth_strategy_execution_variable_name) as s and then
s.is_valid_as_string_8
then
Result := s.to_string_8
end
end
feature -- Handler
handle_account (api: CMS_API; req: WSF_REQUEST; res: WSF_RESPONSE)
@@ -214,10 +229,13 @@ feature -- Handler
l_user: detachable CMS_USER
b: STRING
lnk: CMS_LOCAL_LINK
f: CMS_FORM
tf: WSF_FORM_TEXT_INPUT
do
create {GENERIC_VIEW_CMS_RESPONSE} r.make (req, res, api)
create b.make_empty
l_user := r.user
create f.make (r.location, "roccms-user-view")
if attached smarty_template_block (Current, "account_info", api) as l_tpl_block then
l_tpl_block.set_weight (-10)
r.add_block (l_tpl_block, "content")
@@ -225,6 +243,30 @@ feature -- Handler
debug ("cms")
r.add_warning_message ("Error with block [resources_page]")
end
if l_user /= Void then
create tf.make_with_text ("username", l_user.name)
tf.set_label ("Username")
f.extend (tf)
if attached l_user.email as l_email then
create tf.make_with_text ("email", l_email.to_string_32)
tf.set_label ("Email")
f.extend (tf)
end
if attached l_user.profile_name as l_prof_name then
create tf.make_with_text ("profile_name", l_prof_name)
tf.set_label ("Profile name")
f.extend (tf)
end
create tf.make_with_text ("creation", api.formatted_date_time_yyyy_mm_dd (l_user.creation_date))
tf.set_label ("Creation date")
f.extend (tf)
if attached l_user.last_login_date as dt then
create tf.make_with_text ("last_login", api.formatted_date_time_ago (dt))
tf.set_label ("Last login")
f.extend (tf)
end
end
end
if r.is_authenticated then
@@ -237,6 +279,9 @@ feature -- Handler
r.add_to_primary_tabs (lnk)
end
api.hooks.invoke_form_alter (f, Void, r)
f.append_to_html (r.wsf_theme, b)
r.set_main_content (b)
if l_user = Void then
@@ -251,10 +296,12 @@ feature -- Handler
l_user: detachable CMS_USER
b: STRING
lnk: CMS_LOCAL_LINK
l_form: CMS_FORM
do
create {GENERIC_VIEW_CMS_RESPONSE} r.make (req, res, api)
create b.make_empty
l_user := r.user
create l_form.make (r.location, "roccms-user-edit")
if attached smarty_template_block (Current, "account_edit", api) as l_tpl_block then
l_tpl_block.set_weight (-10)
r.add_block (l_tpl_block, "content")
@@ -262,6 +309,7 @@ feature -- Handler
debug ("cms")
r.add_warning_message ("Error with block [resources_page]")
end
-- Build CMS form...
end
create lnk.make ("View", "account/")
lnk.set_weight (1)
@@ -287,6 +335,8 @@ feature -- Handler
f.append_to_html (r.wsf_theme, b)
end
l_form.append_to_html (r.wsf_theme, b)
r.set_main_content (b)
if l_user = Void then
@@ -336,7 +386,7 @@ feature -- Handler
loc: STRING
do
create {GENERIC_VIEW_CMS_RESPONSE} r.make (req, res, api)
if attached {READABLE_STRING_8} api.execution_variable ("auth_strategy") as l_auth_strategy then
if attached auth_strategy (req) as l_auth_strategy then
loc := l_auth_strategy
else
loc := ""

View File

@@ -0,0 +1,47 @@
note
description: "Summary description for {CMS_AUTHENTICATION_MODULE_WEBAPI}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
CMS_AUTHENTICATION_MODULE_WEBAPI
inherit
CMS_MODULE_WEBAPI [CMS_AUTHENTICATION_MODULE]
redefine
permissions
end
create
make
feature -- Security
permissions: LIST [READABLE_STRING_8]
-- List of permission ids, used by this module, and declared.
do
Result := Precursor
Result.force ("admin users")
end
feature {NONE} -- Router/administration
setup_webapi_router (a_router: WSF_ROUTER; a_api: CMS_API)
-- <Precursor>
do
a_router.handle ("/info", create {WSF_URI_AGENT_HANDLER}.make (agent handle_info (?, ?, a_api)), a_router.methods_get)
end
feature -- Request handling
handle_info (req: WSF_REQUEST; res: WSF_RESPONSE; api: CMS_API)
local
m: WSF_HTML_PAGE_RESPONSE
do
create m.make
m.set_body ("{%"info%":%"" + api.setup.site_name + "%"}")
res.send (m)
end
end

View File

@@ -19,6 +19,8 @@ inherit
setup_hooks
end
CMS_WITH_WEBAPI
CMS_HOOK_BLOCK
create
@@ -33,6 +35,13 @@ feature {NONE} -- Initialization
description := "Service to manage basic authentication"
end
feature {CMS_EXECUTION} -- Administration
webapi: CMS_BASIC_AUTH_MODULE_WEBAPI
do
create Result.make (Current)
end
feature -- Access
name: STRING = "basic_auth"

View File

@@ -0,0 +1,36 @@
note
description: "Summary description for {CMS_BASIC_AUTH_MODULE_WEBAPI}."
date: "$Date$"
revision: "$Revision$"
class
CMS_BASIC_AUTH_MODULE_WEBAPI
inherit
CMS_MODULE_WEBAPI [CMS_BASIC_AUTH_MODULE]
redefine
filters
end
create
make
feature {NONE} -- Router/administration
setup_webapi_router (a_router: WSF_ROUTER; a_api: CMS_API)
-- <Precursor>
do
end
feature -- Access: filter
filters (a_api: CMS_API): detachable LIST [WSF_FILTER]
-- Possibly list of Filter's module.
do
create {ARRAYED_LIST [WSF_FILTER]} Result.make (1)
Result.extend (create {CMS_BASIC_AUTH_FILTER}.make (a_api))
end
note
copyright: "2011-2017, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -9,7 +9,7 @@ class
CMS_BASIC_AUTH_FILTER
inherit
CMS_AUTH_FILTER_I
CMS_AUTH_STRATEGY_FILTER
REFACTORING_HELPER

View File

@@ -15,9 +15,6 @@ inherit
new_mapping as new_uri_mapping
end
WSF_FILTER
WSF_RESOURCE_HANDLER_HELPER
redefine
do_get
@@ -34,7 +31,6 @@ feature -- execute
-- Execute request handler.
do
execute_methods (req, res)
execute_next (req, res)
end
uri_execute (req: WSF_REQUEST; res: WSF_RESPONSE)

View File

@@ -0,0 +1,6 @@
CREATE TABLE user_profiles(
`uid` INTEGER NOT NULL CHECK("uid">=0),
`key` VARCHAR(255) NOT NULL,
`value` TEXT,
CONSTRAINT PK_uid_key PRIMARY KEY (uid,key)
);

View File

@@ -57,7 +57,7 @@ feature {CMS_API} -- Module Initialization
create feed_aggregator_api.make (api)
end
feature {CMS_API} -- Access: API
feature {CMS_API, CMS_MODULE} -- Access: API
feed_aggregator_api: detachable FEED_AGGREGATOR_API
-- Eventual module api.

View File

@@ -10,7 +10,7 @@ class
CMS_OAUTH_20_FILTER
inherit
CMS_AUTH_FILTER_I
CMS_AUTH_STRATEGY_FILTER
rename
make as make_filter
end

View File

@@ -9,7 +9,7 @@ class
CMS_OPENID_FILTER
inherit
CMS_AUTH_FILTER_I
CMS_AUTH_STRATEGY_FILTER
rename
make as make_filter
end

View File

@@ -9,7 +9,7 @@ class
CMS_SESSION_AUTH_FILTER
inherit
CMS_AUTH_FILTER_I
CMS_AUTH_STRATEGY_FILTER
rename
make as make_filter
end

View File

@@ -11,8 +11,6 @@ inherit
CMS_PROXY_STORAGE_SQL
CMS_SESSION_AUTH_STORAGE_I
CMS_STORAGE_SQL_I
REFACTORING_HELPER

View File

@@ -0,0 +1,6 @@
CREATE TABLE user_profiles(
`uid` INTEGER NOT NULL CHECK("uid">=0),
`key` VARCHAR(255) NOT NULL.
`value` TEXT.
CONSTRAINT PK_uid_key PRIMARY KEY (uid,key)
);

View File

@@ -103,6 +103,24 @@ feature {NONE} -- Initialization
site_theme_name := text_item_or_default ("site.theme", "default")
set_theme (site_theme_name)
-- Webapi
webapi_enabled := string_8_item_or_default ("webapi.mode", "off").is_case_insensitive_equal_general ("on")
l_url := string_8_item ("webapi.base_path")
if l_url /= Void and then not l_url.is_empty then
if l_url [l_url.count] = '/' then
l_url := l_url.substring (1, l_url.count - 1)
end
if l_url [1] /= '/' then
l_url := "/" + l_url
end
create webapi_base_path.make_from_string (l_url)
else
create webapi_base_path.make_from_string (default_webapi_base_path)
end
-- Administration
l_url := string_8_item ("administration.base_path")
if l_url /= Void and then not l_url.is_empty then
@@ -117,6 +135,7 @@ feature {NONE} -- Initialization
create administration_base_path.make_from_string (default_administration_base_path)
end
administration_theme_name := text_item_or_default ("administration.theme", theme_name) -- TODO: Default to builtin theme?
end
feature -- Access
@@ -314,11 +333,19 @@ feature -- Access: Site
-- Optional path defining the front page.
-- By default "" or "/".
webapi_enabled: BOOLEAN
-- Is WebAPI enabled?
webapi_base_path: IMMUTABLE_STRING_8
-- Web API base url, default=`default_webapi_base_path`.
administration_base_path: IMMUTABLE_STRING_8
-- Administration base url, default=`default_administration_base_path`.
feature {NONE} -- Constants
default_webapi_base_path: STRING = "/api"
default_administration_base_path: STRING = "/admin"
feature -- Settings
@@ -338,6 +365,12 @@ feature -- Settings
end
end
set_webapi_mode
-- Switch to webapi mode.
do
set_site_mode
end
set_administration_mode
-- Switch to administration mode.
--| - Change theme

View File

@@ -36,9 +36,13 @@ feature -- Hook
if attached {CMS_HOOK_RESPONSE_ALTER} Current as h_resp then
a_hooks.subscribe_to_response_alter_hook (h_resp)
end
if attached {CMS_HOOK_WEBAPI_RESPONSE_ALTER} Current as h_resp then
a_hooks.subscribe_to_webapi_response_alter_hook (h_resp)
end
end
note
copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
copyright: "2011-2017, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -60,6 +60,28 @@ feature -- Hook: response
end
end
feature -- Hook: webapi response
subscribe_to_webapi_response_alter_hook (h: CMS_HOOK_WEBAPI_RESPONSE_ALTER)
-- Add `h' as subscriber of response alter hooks CMS_HOOK_WEBAPI_RESPONSE_ALTER.
do
subscribe_to_hook (h, {CMS_HOOK_WEBAPI_RESPONSE_ALTER})
end
invoke_webapi_response_alter (a_response: WEBAPI_RESPONSE)
-- Invoke response alter hook for response `a_response'.
do
if attached subscribers ({CMS_HOOK_WEBAPI_RESPONSE_ALTER}) as lst then
across
lst as ic
loop
if attached {CMS_HOOK_WEBAPI_RESPONSE_ALTER} ic.item as h then
h.webapi_response_alter (a_response)
end
end
end
end
feature -- Hook: menu_system_alter
subscribe_to_menu_system_alter_hook (h: CMS_HOOK_MENU_SYSTEM_ALTER)
@@ -110,7 +132,7 @@ feature -- Hook: form_alter
-- Add `h' as subscriber of form alter hooks CMS_HOOK_FORM_ALTER,
-- and response `a_response'.
do
subscribe_to_hook (h, {CMS_HOOK_MENU_ALTER})
subscribe_to_hook (h, {CMS_HOOK_FORM_ALTER})
end
invoke_form_alter (a_form: CMS_FORM; a_form_data: detachable WSF_FORM_DATA; a_response: CMS_RESPONSE)

View File

@@ -0,0 +1,30 @@
note
description: "[
Hook providing a way to alter a webapi response.
]"
date: "$Date$"
revision: "$Revision$"
deferred class
CMS_HOOK_WEBAPI_RESPONSE_ALTER
inherit
CMS_HOOK
feature -- Hook
webapi_response_alter (a_response: WEBAPI_RESPONSE)
deferred
end
note
copyright: "2011-2017, Jocelyn Fiat, Javier Velilla, 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

@@ -10,11 +10,68 @@ inherit
WSF_FORM
rename
process as process_form
redefine
append_to_html
end
create
make
feature -- Access
before_widgets: detachable ARRAYED_LIST [WSF_WIDGET]
-- Optional widget before the Current form.
after_widgets: detachable ARRAYED_LIST [WSF_WIDGET]
-- Optional widget after the Current form.
feature -- Element change
put_widget_before_form (w: WSF_WIDGET)
local
lst: like before_widgets
do
lst := before_widgets
if lst = Void then
create lst.make (1)
before_widgets := lst
end
lst.extend (w)
end
put_widget_after_form (w: WSF_WIDGET)
local
lst: like after_widgets
do
lst := after_widgets
if lst = Void then
create lst.make (1)
after_widgets := lst
end
lst.extend (w)
end
feature -- Conversion
append_to_html (a_theme: WSF_THEME; a_html: STRING_8)
do
if attached before_widgets as lst then
across
lst as ic
loop
ic.item.append_to_html (a_theme, a_html)
end
end
Precursor (a_theme, a_html)
if attached after_widgets as lst then
across
lst as ic
loop
ic.item.append_to_html (a_theme, a_html)
end
end
end
feature -- Basic operation
prepare (a_response: CMS_RESPONSE)
@@ -40,6 +97,6 @@ feature -- Basic operation
end
note
copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
copyright: "2011-2017, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -10,10 +10,17 @@ inherit
CMS_MODULE
redefine
initialize,
setup_hooks,
install,
permissions
end
CMS_WITH_WEBAPI
CMS_HOOK_AUTO_REGISTER
CMS_HOOK_FORM_ALTER
create
make
@@ -41,10 +48,16 @@ feature {CMS_API} -- Module Initialization
feature {CMS_API} -- Module management
install (a_api: CMS_API)
local
l_parent_loc: PATH
do
-- Schema
if attached a_api.storage.as_sql_storage as l_sql_storage then
l_sql_storage.sql_execute_file_script (a_api.module_resource_location (Current, (create {PATH}.make_from_string ("scripts")).extended (name + ".sql")), Void)
l_parent_loc := a_api.module_resource_location (Current, create {PATH}.make_from_string ("scripts"))
l_sql_storage.sql_execute_file_script (l_parent_loc.extended (name + ".sql"), Void)
if not l_sql_storage.has_error then
l_sql_storage.sql_execute_file_script (l_parent_loc.extended ("user_profile.sql"), Void)
end
if l_sql_storage.has_error then
a_api.logger.put_error ("Could not initialize database for module [" + name + "]", generating_type)
@@ -81,6 +94,7 @@ feature {CMS_API} -- Module management
--! external configuration file?
--! at the moment we only have 1 admin to the whole site.
--! is that ok?
l_anonymous_role.add_permission ("view any page")
a_api.user_api.save_user_role (l_anonymous_role)
@@ -101,6 +115,7 @@ feature -- Router
setup_router (a_router: WSF_ROUTER; a_api: CMS_API)
-- <Precursor>
do
a_router.handle ("/user/{uid}", create {CMS_USER_HANDLER}.make (a_api), a_router.methods_get)
end
feature -- Security
@@ -117,6 +132,54 @@ feature -- Security
Result.force ("edit path_alias")
end
feature {CMS_EXECUTION} -- Administration
webapi: CMS_CORE_MODULE_WEBAPI
do
create Result.make (Current)
end
feature -- Hooks
setup_hooks (a_hooks: CMS_HOOK_CORE_MANAGER)
do
a_hooks.subscribe_to_form_alter_hook (Current)
end
feature -- Hook
form_alter (a_form: CMS_FORM; a_form_data: detachable WSF_FORM_DATA; a_response: CMS_RESPONSE)
-- Hook execution on form `a_form' and its associated data `a_form_data',
-- for related response `a_response'.
local
fset: WSF_FORM_FIELD_SET
tf: WSF_FORM_TEXT_INPUT
do
if
attached a_form.id as fid and then
fid.same_string ("roccms-user-view")
then
if
attached a_response.user as u and then
attached a_response.api.user_api as l_user_profile_api and then
attached l_user_profile_api.user_profile (u) as l_profile and then
not l_profile.is_empty
then
create fset.make
fset.set_legend ("User-Profile")
a_form.extend (fset)
across
l_profile as ic
loop
create tf.make_with_text (ic.key.to_string_32, ic.item)
tf.set_label (ic.key.to_string_32)
a_form.extend (tf)
end
end
end
end
note
copyright: "2011-2017, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"

View File

@@ -8,12 +8,34 @@ class
inherit
CMS_MODULE_API
redefine
initialize
end
CMS_USER_PROFILE_API
redefine
initialize
end
REFACTORING_HELPER
create
make
feature {NONE} -- Initialization
initialize
do
Precursor {CMS_MODULE_API}
Precursor {CMS_USER_PROFILE_API}
user_storage := storage
end
feature -- Storage
user_storage: CMS_USER_STORAGE_I
-- User storage.
feature -- Validation
is_valid_username (a_name: READABLE_STRING_32): BOOLEAN
@@ -87,43 +109,53 @@ feature -- Access: user
user_by_id (a_id: like {CMS_USER}.id): detachable CMS_USER
-- User by id `a_id', if any.
do
Result := storage.user_by_id (a_id)
Result := user_storage.user_by_id (a_id)
end
user_by_name (a_username: READABLE_STRING_GENERAL): detachable CMS_USER
-- User by name `a_user_name', if any.
do
Result := storage.user_by_name (a_username)
Result := user_storage.user_by_name (a_username)
end
user_by_id_or_name (a_uid: READABLE_STRING_GENERAL): detachable CMS_USER
-- User by id or name `a_uid`, if any.
do
if a_uid.is_integer_64 then
Result := user_by_id (a_uid.to_integer_64)
else
Result := user_by_name (a_uid)
end
end
user_by_email (a_email: READABLE_STRING_GENERAL): detachable CMS_USER
-- User by email `a_email', if any.
do
Result := storage.user_by_email (a_email)
Result := user_storage.user_by_email (a_email)
end
user_by_activation_token (a_token: READABLE_STRING_32): detachable CMS_USER
-- User by activation token `a_token'.
do
Result := storage.user_by_activation_token (a_token)
Result := user_storage.user_by_activation_token (a_token)
end
user_by_password_token (a_token: READABLE_STRING_32): detachable CMS_USER
-- User by password token `a_token'.
do
Result := storage.user_by_password_token (a_token)
Result := user_storage.user_by_password_token (a_token)
end
users_count: INTEGER
-- Number of users.
do
Result := storage.users_count
Result := user_storage.users_count
end
recent_users (params: CMS_DATA_QUERY_PARAMETERS): ITERABLE [CMS_USER]
-- List of the `a_rows' most recent users starting from `a_offset'.
do
Result := storage.recent_users (params.offset.to_integer_32, params.size.to_integer_32)
Result := user_storage.recent_users (params.offset.to_integer_32, params.size.to_integer_32)
end
admin_user: detachable CMS_USER
@@ -149,8 +181,8 @@ feature -- Change User
if
attached a_user.email as l_email
then
storage.new_user (a_user)
error_handler.append (storage.error_handler)
user_storage.new_user (a_user)
error_handler.append (user_storage.error_handler)
else
error_handler.add_custom_error (0, "bad new user request", "Missing password or email to create new user!")
end
@@ -164,8 +196,8 @@ feature -- Change User
user_by_name (a_new_username) = Void
do
reset_error
storage.update_username (a_user, a_new_username)
error_handler.append (storage.error_handler)
user_storage.update_username (a_user, a_new_username)
error_handler.append (user_storage.error_handler)
end
update_user (a_user: CMS_USER)
@@ -174,8 +206,8 @@ feature -- Change User
has_id: a_user.has_id
do
reset_error
storage.update_user (a_user)
error_handler.append (storage.error_handler)
user_storage.update_user (a_user)
error_handler.append (user_storage.error_handler)
end
delete_user (a_user: CMS_USER)
@@ -184,8 +216,8 @@ feature -- Change User
has_id: a_user.has_id
do
reset_error
storage.delete_user (a_user)
error_handler.append (storage.error_handler)
user_storage.delete_user (a_user)
error_handler.append (user_storage.error_handler)
end
feature -- Status report
@@ -193,7 +225,7 @@ feature -- Status report
is_valid_credential (a_auth_login, a_auth_password: READABLE_STRING_32): BOOLEAN
-- Is the credentials `a_auth_login' and `a_auth_password' valid?
do
Result := storage.is_valid_credential (a_auth_login, a_auth_password)
Result := user_storage.is_valid_credential (a_auth_login, a_auth_password)
end
user_has_permission (a_user: detachable CMS_USER; a_permission: detachable READABLE_STRING_GENERAL): BOOLEAN
@@ -229,7 +261,7 @@ feature -- Status report
if l_roles = Void then
-- Fill user with its roles.
create {ARRAYED_LIST [CMS_USER_ROLE]} l_roles.make (0)
l_roles := storage.user_roles_for (a_user)
l_roles := user_storage.user_roles_for (a_user)
end
Result := l_roles
end
@@ -262,13 +294,13 @@ feature -- User roles.
user_role_by_id (a_id: like {CMS_USER_ROLE}.id): detachable CMS_USER_ROLE
-- Retrieve a `Role' represented by an id `a_id' if any.
do
Result := storage.user_role_by_id (a_id)
Result := user_storage.user_role_by_id (a_id)
end
user_role_by_name (a_name: READABLE_STRING_GENERAL): detachable CMS_USER_ROLE
-- Retrieve a `Role' represented by a name `a_name' if any.
do
Result := storage.user_role_by_name (a_name)
Result := user_storage.user_role_by_name (a_name)
end
role_permissions: HASH_TABLE [LIST [READABLE_STRING_8], STRING_8]
@@ -278,12 +310,16 @@ feature -- User roles.
do
create Result.make (cms_api.enabled_modules.count + 1)
l_used_permissions := storage.role_permissions
l_used_permissions := user_storage.role_permissions
across
cms_api.enabled_modules as ic
loop
lst := ic.item.permissions
if attached {CMS_ADMINISTRABLE} ic.item as adm then
if
attached {CMS_ADMINISTRABLE} ic.item as adm and then
attached adm.module_administration.permissions as adm_permissions and then
not adm_permissions.is_empty
then
create {ARRAYED_LIST [READABLE_STRING_8]} l_full_lst.make (lst.count)
l_full_lst.compare_objects
-- l_full_lst.append (lst)
@@ -294,14 +330,38 @@ feature -- User roles.
l_full_lst.extend (lst_ic.item)
end
end
-- l_full_lst.append (adm.module_administration.permissions)
lst := adm.module_administration.permissions
-- l_full_lst.append (adm_permissions)
across
adm_permissions as lst_ic
loop
if not l_full_lst.has (lst_ic.item) then
l_full_lst.extend (lst_ic.item)
end
end
lst := l_full_lst
end
if
attached {CMS_WITH_WEBAPI} ic.item as wapi and then
attached wapi.module_webapi.permissions as wapi_permissions and then
not wapi_permissions.is_empty
then
create {ARRAYED_LIST [READABLE_STRING_8]} l_full_lst.make (lst.count)
l_full_lst.compare_objects
-- l_full_lst.append (lst)
across
lst as lst_ic
loop
if not l_full_lst.has (lst_ic.item) then
l_full_lst.extend (lst_ic.item)
end
end
-- l_full_lst.append (wapi_permissions)
across
wapi_permissions as lst_ic
loop
if not l_full_lst.has (lst_ic.item) then
l_full_lst.extend (lst_ic.item)
end
end
lst := l_full_lst
end
@@ -331,7 +391,7 @@ feature -- User roles.
roles: LIST [CMS_USER_ROLE]
-- List of possible roles.
do
Result := storage.user_roles
Result := user_storage.user_roles
end
effective_roles: LIST [CMS_USER_ROLE]
@@ -340,7 +400,7 @@ feature -- User roles.
l_roles: like roles
r: CMS_USER_ROLE
do
l_roles := storage.user_roles
l_roles := user_storage.user_roles
create {ARRAYED_LIST [CMS_USER_ROLE]} Result.make (l_roles.count)
across
l_roles as ic
@@ -357,7 +417,7 @@ feature -- User roles.
roles_count: INTEGER
-- Number of roles
do
Result := storage.user_roles.count
Result := user_storage.user_roles.count
end
feature -- Change User role
@@ -365,31 +425,31 @@ feature -- Change User role
save_user_role (a_user_role: CMS_USER_ROLE)
do
reset_error
storage.save_user_role (a_user_role)
error_handler.append (storage.error_handler)
user_storage.save_user_role (a_user_role)
error_handler.append (user_storage.error_handler)
end
unassign_role_from_user (a_role: CMS_USER_ROLE; a_user: CMS_USER; )
-- Unassign user_role `a_role' to user `a_user'.
do
reset_error
storage.unassign_role_from_user (a_role, a_user)
error_handler.append (storage.error_handler)
user_storage.unassign_role_from_user (a_role, a_user)
error_handler.append (user_storage.error_handler)
end
assign_role_to_user (a_role: CMS_USER_ROLE; a_user: CMS_USER; )
-- Assign user_role `a_role' to user `a_user'.
do
reset_error
storage.assign_role_to_user (a_role, a_user)
error_handler.append (storage.error_handler)
user_storage.assign_role_to_user (a_role, a_user)
error_handler.append (user_storage.error_handler)
end
delete_role (a_role: CMS_USER_ROLE)
do
reset_error
storage.delete_role (a_role)
error_handler.append (storage.error_handler)
user_storage.delete_role (a_role)
error_handler.append (user_storage.error_handler)
end
feature -- User Activation
@@ -397,7 +457,7 @@ feature -- User Activation
new_activation (a_token: READABLE_STRING_32; a_id: INTEGER_64)
-- Save activation token `a_token', for the user with the id `a_id'.
do
storage.save_activation (a_token, a_id)
user_storage.save_activation (a_token, a_id)
end
feature -- User Password Recovery
@@ -405,13 +465,13 @@ feature -- User Password Recovery
new_password (a_token: READABLE_STRING_32; a_id: INTEGER_64)
-- Save password token `a_token', for the user with the id `a_id'.
do
storage.save_password (a_token, a_id)
user_storage.save_password (a_token, a_id)
end
remove_password (a_token: READABLE_STRING_32)
-- Remove password token `a_token', from the storage.
-- Remove password token `a_token', from the user_storage.
do
storage.remove_password (a_token)
user_storage.remove_password (a_token)
end
feature -- User status
@@ -423,7 +483,7 @@ feature -- User status
-- The user is active
Trashed: INTEGER = -1
-- The user is trashed (soft delete), ready to be deleted/destroyed from storage.
-- The user is trashed (soft delete), ready to be deleted/destroyed from user_storage.
feature -- Access - Temp User
@@ -431,36 +491,36 @@ feature -- Access - Temp User
-- Number of pending users.
--! to be accepted or rehected
do
Result := storage.temp_users_count
Result := user_storage.temp_users_count
end
temp_user_by_name (a_username: READABLE_STRING_GENERAL): detachable CMS_USER
-- User by name `a_user_name', if any.
do
Result := storage.temp_user_by_name (a_username.as_string_32)
Result := user_storage.temp_user_by_name (a_username.as_string_32)
end
temp_user_by_email (a_email: READABLE_STRING_8): detachable CMS_USER
-- User by email `a_email', if any.
do
Result := storage.temp_user_by_email (a_email)
Result := user_storage.temp_user_by_email (a_email)
end
temp_user_by_activation_token (a_token: READABLE_STRING_32): detachable CMS_USER
-- User by activation token `a_token'.
do
Result := storage.temp_user_by_activation_token (a_token)
Result := user_storage.temp_user_by_activation_token (a_token)
end
temp_recent_users (params: CMS_DATA_QUERY_PARAMETERS): ITERABLE [CMS_TEMP_USER]
-- List of the `a_rows' most recent users starting from `a_offset'.
do
Result := storage.temp_recent_users (params.offset.to_integer_32, params.size.to_integer_32)
Result := user_storage.temp_recent_users (params.offset.to_integer_32, params.size.to_integer_32)
end
token_by_temp_user_id (a_id: like {CMS_USER}.id): detachable STRING
do
Result := storage.token_by_temp_user_id (a_id)
Result := user_storage.token_by_temp_user_id (a_id)
end
feature -- Change Temp User
@@ -477,8 +537,8 @@ feature -- Change Temp User
attached a_temp_user.salt as l_salt and then
attached a_temp_user.email as l_email
then
storage.new_user_from_temp_user (a_temp_user)
error_handler.append (storage.error_handler)
user_storage.new_user_from_temp_user (a_temp_user)
error_handler.append (user_storage.error_handler)
else
error_handler.add_custom_error (0, "bad new user request", "Missing password or email to create new user!")
end
@@ -495,17 +555,17 @@ feature -- Change Temp User
attached a_temp_user.password as l_password and then
attached a_temp_user.email as l_email
then
storage.new_temp_user (a_temp_user)
error_handler.append (storage.error_handler)
user_storage.new_temp_user (a_temp_user)
error_handler.append (user_storage.error_handler)
else
error_handler.add_custom_error (0, "bad new user request", "Missing password or email to create new user!")
end
end
remove_activation (a_token: READABLE_STRING_32)
-- Remove activation token `a_token', from the storage.
-- Remove activation token `a_token', from the user_storage.
do
storage.remove_activation (a_token)
user_storage.remove_activation (a_token)
end
delete_temp_user (a_temp_user: CMS_TEMP_USER)
@@ -514,10 +574,38 @@ feature -- Change Temp User
has_id: a_temp_user.has_id
do
reset_error
storage.delete_temp_user (a_temp_user)
error_handler.append (storage.error_handler)
user_storage.delete_temp_user (a_temp_user)
error_handler.append (user_storage.error_handler)
end
--feature -- Access: User profile
-- user_profile (a_user: CMS_USER): detachable CMS_USER_PROFILE
-- -- User profile for `a_user'.
-- require
-- valid_user: a_user.has_id
-- do
-- Result := user_profile_storage.user_profile (a_user)
-- end
-- user_profile_item (a_item_name: READABLE_STRING_GENERAL; a_user: CMS_USER): detachable READABLE_STRING_32
-- -- User profile item `a_item_name` for `a_user`.
-- require
-- valid_user: a_user.has_id
-- do
-- Result := user_profile_storage.user_profile_item (a_user, a_item_name)
-- end
--feature -- Change: User profile
-- save_user_profile (a_user: CMS_USER; a_user_profile: CMS_USER_PROFILE)
-- -- Save `a_user' profile `a_user_profile'.
-- require
-- valid_user: a_user.has_id
-- do
-- user_profile_storage.save_user_profile (a_user, a_user_profile)
-- end
note
copyright: "2011-2017, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"

View File

@@ -0,0 +1,87 @@
note
description: "API to handle user profiles."
date: "$Date$"
revision: "$Revision$"
class
CMS_USER_PROFILE_API
inherit
CMS_MODULE_API
redefine
initialize
end
REFACTORING_HELPER
create
make
feature {NONE} -- Initialization
initialize
-- <Precursor>
do
Precursor
-- Storage initialization
if attached cms_api.storage.as_sql_storage as l_storage_sql then
create {CMS_USER_PROFILE_STORAGE_SQL} user_profile_storage.make (l_storage_sql)
else
-- FIXME: in case of NULL storage, should Current be disabled?
create {CMS_USER_PROFILE_STORAGE_NULL} user_profile_storage
end
end
feature {CMS_MODULE} -- Access nodes storage.
user_profile_storage: CMS_USER_PROFILE_STORAGE_I
feature -- Access: profile
user_profile (a_user: CMS_USER): detachable CMS_USER_PROFILE
-- User profile for `a_user'.
require
valid_user: a_user.has_id
do
Result := user_profile_storage.user_profile (a_user)
end
user_profile_item (a_item_name: READABLE_STRING_GENERAL; a_user: CMS_USER): detachable READABLE_STRING_32
-- User profile item `a_item_name` for `a_user`.
require
valid_user: a_user.has_id
do
Result := user_profile_storage.user_profile_item (a_user, a_item_name)
end
users_with_profile_item (a_item_name: READABLE_STRING_GENERAL; a_value: detachable READABLE_STRING_GENERAL): detachable LIST [CMS_USER]
-- Users having a profile item `a_item_name:a_value`.
-- Note: if `a_value` is Void, return users having a profile item named `a_item_name`.
require
not a_item_name.is_whitespace
do
Result := user_profile_storage.users_with_profile_item (a_item_name, a_value)
end
feature -- Change: profile
save_user_profile (a_user: CMS_USER; a_user_profile: CMS_USER_PROFILE)
-- Save `a_user' profile `a_user_profile'.
require
valid_user: a_user.has_id
do
user_profile_storage.save_user_profile (a_user, a_user_profile)
end
save_user_profile_item (a_user: CMS_USER; a_user_profile_name, a_user_profile_value: READABLE_STRING_GENERAL)
-- Save `a_user' profile item `a_user_profile_name=a_user_profile_value`.
require
valid_user: a_user.has_id
do
user_profile_storage.save_user_profile_item (a_user, a_user_profile_name, a_user_profile_value)
end
note
copyright: "2011-2017, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -57,15 +57,21 @@ feature -- execute
feature -- Query
user_id_path_parameter (req: WSF_REQUEST): INTEGER_64
-- User id passed as path parameter for request `req'.
user_path_parameter (req: WSF_REQUEST): detachable CMS_USER
-- User id (uid or username) passed as path parameter for request `req'.
local
s: STRING
l_uid: INTEGER_64
do
if attached {WSF_STRING} req.path_parameter ("uid") as p_nid then
s := p_nid.value
if s.is_integer_64 then
Result := s.to_integer_64
l_uid := s.to_integer_64
if l_uid > 0 then
Result := api.user_api.user_by_id (l_uid)
end
else
Result := api.user_api.user_by_name (s)
end
end
end
@@ -76,28 +82,23 @@ feature -- HTTP Methods
-- <Precursor>
local
l_user: detachable CMS_USER
l_uid: INTEGER_64
view_response: CMS_USER_VIEW_RESPONSE
do
if api.has_permission ("view user") then
-- Display existing node
l_uid := user_id_path_parameter (req)
if l_uid > 0 then
l_user := api.user_api.user_by_id (l_uid)
l_user := user_path_parameter (req)
if
l_user /= Void
then
create view_response.make (req, res, api)
view_response.execute
(create {CMS_USER_VIEW_RESPONSE}.make_with_user (l_user, req, res, api)).execute
else
send_not_found (req, res)
end
else
send_bad_request (req, res)
end
else
send_access_denied (req, res)
end
end
note
copyright: "2011-2017, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -10,7 +10,19 @@ inherit
CMS_RESPONSE
create
make
make_with_user
feature {NONE} -- Initialization
make_with_user (u: CMS_USER; req: WSF_REQUEST; res: WSF_RESPONSE; a_api: like api)
do
make (req, res, a_api)
associated_user := u
end
feature -- Access
associated_user: CMS_USER
feature -- Query
@@ -33,16 +45,11 @@ feature -- Process
-- Computed response message.
local
b: STRING_8
uid: INTEGER_64
user_api: CMS_USER_API
f: CMS_FORM
do
user_api := api.user_api
create b.make_empty
uid := user_id_path_parameter (request)
if
uid > 0 and then
attached user_api.user_by_id (uid) as l_user
attached associated_user as l_user
then
if
api.has_permission ("view user")
@@ -67,7 +74,6 @@ feature -- Process Edit
th: WSF_FORM_HIDDEN_INPUT
do
create Result.make (a_url, a_name)
create th.make ("user-id")
if a_user /= Void then
th.set_text_value (a_user.id.out)
@@ -79,12 +85,14 @@ feature -- Process Edit
populate_form (Result, a_user)
end
populate_form (a_form: WSF_FORM; a_user: detachable CMS_USER)
populate_form (a_form: CMS_FORM; a_user: detachable CMS_USER)
-- Fill the web form `a_form' with data from `a_node' if set,
-- and apply this to content type `a_content_type'.
local
ti: WSF_FORM_TEXT_INPUT
fs: WSF_FORM_FIELD_SET
l_new_access_token_form: WSF_FORM
l_access_token: detachable READABLE_STRING_32
do
if a_user /= Void then
create fs.make
@@ -97,7 +105,33 @@ feature -- Process Edit
ti.set_is_readonly (True)
fs.extend (ti)
a_form.extend (fs)
if api.setup.webapi_enabled then
create fs.make
fs.set_legend ("Web API")
l_access_token := api.user_api.user_profile_item ("access_token", a_user)
if l_access_token /= Void then
create ti.make_with_text ("api_access_token", a_user.name)
ti.set_text_value (l_access_token)
ti.set_label ("Access Token")
ti.set_is_readonly (True)
fs.extend (ti)
end
create l_new_access_token_form.make (api.webapi_path ("access_token"), Void)
l_new_access_token_form.set_method_post
if l_access_token /= Void then
l_new_access_token_form.extend (create {WSF_FORM_SUBMIT_INPUT}.make_with_text ("access_token_op", "Refresh Access Token"))
else
l_new_access_token_form.extend (create {WSF_FORM_SUBMIT_INPUT}.make_with_text ("access_token_op", "Create Access Token"))
end
l_new_access_token_form.extend (create {WSF_FORM_HIDDEN_INPUT}.make_with_text ("destination", request.percent_encoded_path_info))
a_form.put_widget_after_form (l_new_access_token_form)
a_form.extend (fs)
end
end
end
note
copyright: "2011-2017, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -800,7 +800,7 @@ feature -- Change: User activation
sql_begin_transaction
write_information_log (generator + ".save_activation")
create l_utc_date.make_now_utc
create l_parameters.make (2)
create l_parameters.make (3)
l_parameters.put (a_token, "token")
l_parameters.put (a_id, "uid")
l_parameters.put (l_utc_date, "utc_date")

View File

@@ -0,0 +1,66 @@
note
description: "Interface for accessing user profile contents from the database."
date: "$Date$"
revision: "$Revision$"
deferred class
CMS_USER_PROFILE_STORAGE_I
feature -- Error Handling
error_handler: ERROR_HANDLER
-- Error handler.
deferred
end
feature -- Access
user_profile (a_user: CMS_USER): detachable CMS_USER_PROFILE
-- User profile for `a_user'.
require
has_id: a_user.has_id
deferred
end
user_profile_item (a_user: CMS_USER; a_item_name: READABLE_STRING_GENERAL): detachable READABLE_STRING_32
require
valid_user: a_user.has_id
do
if attached user_profile (a_user) as pf then
Result := pf.item (a_item_name)
end
end
users_with_profile_item (a_item_name: READABLE_STRING_GENERAL; a_value: detachable READABLE_STRING_GENERAL): detachable LIST [CMS_USER]
-- Users having a profile item `a_item_name:a_value`.
-- Note: if `a_value` is Void, return users having a profile item named `a_item_name`.
deferred
end
feature -- Change
save_user_profile (a_user: CMS_USER; a_profile: CMS_USER_PROFILE)
-- Save user profile `a_profile' for `a_user'.
require
user_has_id: a_user.has_id
deferred
end
save_user_profile_item (a_user: CMS_USER; a_profile_item_name: READABLE_STRING_GENERAL; a_profile_item_value: READABLE_STRING_GENERAL)
require
user_has_id: a_user.has_id
local
pf: detachable CMS_USER_PROFILE
do
pf := user_profile (a_user)
if pf = Void then
create pf.make
end
pf.force (a_profile_item_value, a_profile_item_name)
save_user_profile (a_user, pf)
end
note
copyright: "2011-2017, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -0,0 +1,42 @@
note
description: "Summary description for {CMS_USER_PROFILE_STORAGE_NULL}."
date: "$Date$"
revision: "$Revision$"
class
CMS_USER_PROFILE_STORAGE_NULL
inherit
CMS_USER_PROFILE_STORAGE_I
feature -- Error handler
error_handler: ERROR_HANDLER
-- Error handler.
do
create Result.make
end
feature -- Access
user_profile (a_user: CMS_USER): detachable CMS_USER_PROFILE
-- <Precursor>
do
end
users_with_profile_item (a_item_name: READABLE_STRING_GENERAL; a_value: detachable READABLE_STRING_GENERAL): detachable LIST [CMS_USER]
-- <Precursor>
do
end
feature -- Change
save_user_profile (a_user: CMS_USER; a_profile: CMS_USER_PROFILE)
-- <Precursor>
do
end
note
copyright: "2011-2017, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -0,0 +1,212 @@
note
description: "Interface for accessing user profile contents from SQL database."
date: "$Date$"
revision: "$Revision$"
class
CMS_USER_PROFILE_STORAGE_SQL
inherit
CMS_USER_PROFILE_STORAGE_I
redefine
user_profile_item,
save_user_profile_item
end
CMS_PROXY_STORAGE_SQL
CMS_STORAGE_SQL_I
create
make
feature -- Access
user_profile_item (a_user: CMS_USER; a_item_name: READABLE_STRING_GENERAL): detachable READABLE_STRING_32
-- User profile for `a_user'.
local
l_parameters: STRING_TABLE [detachable ANY]
do
reset_error
create l_parameters.make (2)
l_parameters.put (a_user.id, "uid")
l_parameters.put (a_item_name, "key")
sql_query (sql_select_user_profile_item, l_parameters)
if not has_error then
Result := sql_read_string_32 (2)
end
sql_finalize
end
user_profile (a_user: CMS_USER): detachable CMS_USER_PROFILE
-- User profile for `a_user'.
local
l_parameters: STRING_TABLE [detachable ANY]
do
reset_error
create l_parameters.make (1)
l_parameters.put (a_user.id, "uid")
sql_query (sql_select_user_profile_items, l_parameters)
if not has_error then
create Result.make
from
sql_start
until
sql_after or has_error
loop
if
attached sql_read_string_32 (1) as l_key and
attached sql_read_string_32 (2) as l_val
then
Result.force (l_val, l_key)
end
sql_forth
end
end
sql_finalize
end
users_with_profile_item (a_item_name: READABLE_STRING_GENERAL; a_value: detachable READABLE_STRING_GENERAL): detachable LIST [CMS_USER]
-- Users having a profile item `a_item_name:a_value`.
-- Note: if `a_value` is Void, return users having a profile item named `a_item_name`.
local
l_parameters: STRING_TABLE [detachable ANY]
l_uids: ARRAYED_LIST [INTEGER_64]
do
reset_error
create l_parameters.make (2)
l_parameters.put (a_item_name, "key")
if a_value = Void then
sql_query (sql_select_users_with_profile_item_named, l_parameters)
else
l_parameters.put (a_value, "value")
sql_query (sql_select_users_with_profile_item, l_parameters)
end
if not has_error then
create l_uids.make (0)
from
sql_start
until
sql_after or has_error
loop
if
attached sql_read_integer_64 (1) as l_uid and then
l_uid > 0
then
l_uids.force (l_uid)
end
sql_forth
end
end
sql_finalize
if
not has_error and
l_uids /= Void and
attached api as l_cms_api
then
create {ARRAYED_LIST [CMS_USER]} Result.make (l_uids.count)
across
l_uids as ic
loop
if attached l_cms_api.user_api.user_by_id (ic.item) as u then
Result.force (u)
else
check known_user: False end
end
end
end
end
feature -- Change
save_user_profile_item (a_user: CMS_USER; a_item_name: READABLE_STRING_GENERAL; a_item_value: READABLE_STRING_GENERAL)
-- Save user profile item `a_item_name:a_item_value` for `a_user'.
local
l_parameters: STRING_TABLE [detachable ANY]
do
create l_parameters.make (3)
l_parameters.put (a_user.id, "uid")
l_parameters.put (a_item_name, "key")
l_parameters.put (a_item_value, "value")
reset_error
if user_profile_item (a_user, a_item_name) = Void then
sql_insert (sql_insert_user_profile_item, l_parameters)
else
sql_modify (sql_update_user_profile_item, l_parameters)
end
sql_finalize
end
save_user_profile (a_user: CMS_USER; a_profile: CMS_USER_PROFILE)
-- Save user profile `a_profile' for `a_user'.
local
l_parameters: STRING_TABLE [detachable ANY]
p: detachable CMS_USER_PROFILE
l_item: like user_profile_item
l_is_new: BOOLEAN
l_has_diff: BOOLEAN
do
p := user_profile (a_user)
create l_parameters.make (3)
reset_error
across
a_profile as ic
until
has_error
loop
l_item := ic.item
-- No previous profile, or no item with same name, or same value
l_has_diff := True
if p = Void then
l_is_new := True
elseif p.has_key (ic.key) then
l_is_new := False
l_has_diff := attached p.item (ic.key) as l_prev_item and then not l_prev_item.same_string (l_item)
else
l_is_new := True
end
if l_has_diff then
l_parameters.put (a_user.id, "uid")
l_parameters.put (ic.key, "key")
l_parameters.put (l_item, "value")
if l_is_new then
sql_insert (sql_insert_user_profile_item, l_parameters)
else
sql_modify (sql_update_user_profile_item, l_parameters)
end
l_parameters.wipe_out
end
end
sql_finalize
end
feature {NONE} -- Queries
sql_select_user_profile_items: STRING = "SELECT key, value FROM user_profiles WHERE uid=:uid;"
-- user profile items for :uid;
sql_select_user_profile_item: STRING = "SELECT key, value FROM user_profiles WHERE uid=:uid AND key=:key"
-- user profile items for :uid;
sql_select_users_with_profile_item: STRING = "SELECT uid FROM user_profiles WHERE key=:key and value=:value"
-- users with profile item named :key and value :value;
sql_select_users_with_profile_item_named: STRING = "SELECT uid FROM user_profiles WHERE key=:key"
-- users with profile item named :key;
sql_insert_user_profile_item: STRING = "INSERT INTO user_profiles (uid, key, value) VALUES (:uid, :key, :value);"
-- new user profile item for :uid;
sql_update_user_profile_item: STRING = "UPDATE user_profiles SET value = :value WHERE uid = :uid AND key = :key;"
-- user profile items for :uid;
note
copyright: "2011-2017, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -0,0 +1,39 @@
note
description: "Summary description for {CMS_CORE_ACCESS_TOKEN_WEBAPI_AUTH_FILTER}."
date: "$Date$"
revision: "$Revision$"
class
CMS_ACCESS_TOKEN_WEBAPI_AUTH_FILTER
inherit
CMS_WEBAPI_AUTH_FILTER
create
make
feature -- Basic operations
execute (req: WSF_REQUEST; res: WSF_RESPONSE)
-- Execute the filter.
local
tok: READABLE_STRING_GENERAL
do
if
attached req.http_authorization as l_auth and then
l_auth.starts_with_general ("Bearer ")
then
tok := l_auth.substring (8, l_auth.count)
if attached api.user_api.users_with_profile_item ("access_token", tok) as lst then
if lst.count = 1 then
api.set_user (lst.first)
end
end
end
execute_next (req, res)
end
note
copyright: "2011-2017, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -0,0 +1,182 @@
note
description: "Summary description for {CMS_ACCESS_TOKEN_WEBAPI_HANDLER}."
date: "$Date$"
revision: "$Revision$"
class
CMS_ACCESS_TOKEN_WEBAPI_HANDLER
inherit
CMS_WEBAPI_HANDLER
WSF_URI_TEMPLATE_HANDLER
create
make
feature -- Execution
execute (req: WSF_REQUEST; res: WSF_RESPONSE)
-- Execute handler for `req' and respond in `res'.
local
l_uid: READABLE_STRING_GENERAL
do
if attached {WSF_STRING} req.path_parameter ("uid") as p_uid then
l_uid := p_uid.value
if req.is_post_request_method then
post_access_token (l_uid, req, res)
elseif req.is_get_request_method then
get_access_token (l_uid, req, res)
else
send_bad_request (Void, req, res)
end
else
send_bad_request ("Missing {uid} parameter", req, res)
end
end
feature -- Helper
user_by_uid (a_uid: READABLE_STRING_GENERAL): detachable CMS_USER
do
Result := api.user_api.user_by_id_or_name (a_uid)
end
feature -- Request execution
get_access_token (a_uid: READABLE_STRING_GENERAL; req: WSF_REQUEST; res: WSF_RESPONSE)
-- Execute handler for `req' and respond in `res'.
local
rep: HM_WEBAPI_RESPONSE
do
if attached user_by_uid (a_uid) as l_user then
if attached api.user as u then
if u.same_as (l_user) or api.user_api.is_admin_user (u) then
rep := new_access_token_webapi_response (l_user, user_access_token (l_user), req, res)
if attached {WSF_STRING} req.item ("destination") as dest then
rep.set_redirection (dest.url_encoded_value)
end
rep.execute
else
-- Only admin, or current user can see its access_token!
send_access_denied (Void, req, res)
end
else
send_access_denied (Void, req, res)
end
else
send_not_found ("User not found", req, res)
end
end
post_access_token (a_uid: READABLE_STRING_GENERAL; req: WSF_REQUEST; res: WSF_RESPONSE)
-- Execute handler for `req' and respond in `res'.
local
l_access_token: detachable READABLE_STRING_32
rep: like new_webapi_response
do
if attached user_by_uid (a_uid) as l_user then
if attached api.user as u then
if u.same_as (l_user) or api.user_api.is_admin_user (u) then
if attached req.path_parameter ("application") then
end
-- l_access_token := user_access_token (l_user)
l_access_token := new_key (40)
-- if l_access_token /= Void then
-- l_access_token := "Updated-" + (create {UUID_GENERATOR}).generate_uuid.out
-- else
-- l_access_token := "New-" + (create {UUID_GENERATOR}).generate_uuid.out
-- end
set_user_access_token (l_user, l_access_token)
rep := new_access_token_webapi_response (l_user, l_access_token, req, res)
if attached {WSF_STRING} req.item ("destination") as dest then
rep.set_redirection (dest.url_encoded_value)
end
rep.execute
else
-- Only admin, or current user can create the user access_token!
send_access_denied (Void, req, res)
end
else
send_access_denied (Void, req, res)
end
else
send_not_found ("User not found", req, res)
end
end
feature {NONE} -- Implementation
user_access_token (a_user: CMS_USER): detachable READABLE_STRING_8
do
if
attached api.user_api.user_profile_item ("access_token", a_user) as l_access_token and then
not l_access_token.is_whitespace and then
l_access_token.is_valid_as_string_8
then
Result := l_access_token.to_string_8
else
-- Result := new_key (40)
end
end
set_user_access_token (a_user: CMS_USER; a_access_token: READABLE_STRING_GENERAL)
do
api.user_api.save_user_profile_item (a_user, "access_token", a_access_token)
end
new_access_token_webapi_response (a_user: CMS_USER; a_access_token: detachable READABLE_STRING_GENERAL; req: WSF_REQUEST; res: WSF_RESPONSE): like new_webapi_response
local
tb: STRING_TABLE [detachable ANY]
do
Result := new_webapi_response (req, res)
if a_access_token /= Void then
Result.add_string_field ("access_token", a_access_token)
else
Result.add_string_field ("access_token", "NONE")
Result.add_link ("new_access_token", "user/" + a_user.id.out + "/access_token", req.percent_encoded_path_info)
end
create tb.make_equal (3)
tb.force (a_user.name, "name")
tb.force (a_user.id, "uid")
Result.add_table_iterator_field ("user", tb)
-- Result.add_string_field ("username", a_user.name)
-- Result.add_integer_64_field ("uid", a_user.id)
Result.add_self (req.percent_encoded_path_info)
add_user_links_to (a_user, Result)
end
new_key (len: INTEGER): STRING_8
local
rand: RANDOM
n: INTEGER
v: NATURAL_32
do
create rand.set_seed ((create {DATE_TIME}.make_now_utc).seconds)
rand.start
create Result.make (len)
from
n := 1
until
n = len
loop
rand.forth
v := (rand.item \\ 16).to_natural_32
check 0 <= v and v <= 15 end
if v < 9 then
Result.append_code (48 + v) -- 48 '0'
else
Result.append_code (97 + v - 9) -- 97 'a'
end
n := n + 1
end
end
note
copyright: "2011-2017, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -0,0 +1,43 @@
note
description: "Summary description for {CMS_BASIC_WEBAPI_AUTH_FILTER}."
date: "$Date$"
revision: "$Revision$"
class
CMS_BASIC_WEBAPI_AUTH_FILTER
inherit
CMS_WEBAPI_AUTH_FILTER
create
make
feature -- Basic operations
execute (req: WSF_REQUEST; res: WSF_RESPONSE)
-- Execute the filter.
local
l_auth: HTTP_AUTHORIZATION
do
create l_auth.make (req.http_authorization)
if
l_auth.is_basic and then
attached l_auth.login as l_auth_login and then
attached l_auth.password as l_auth_password
then
if
api.user_api.is_valid_credential (l_auth_login, l_auth_password) and then
attached api.user_api.user_by_name (l_auth_login) as l_user
then
api.set_user (l_user)
else
-- not authenticated due to bad login or password.
end
end
execute_next (req, res)
end
note
copyright: "2011-2017, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -0,0 +1,57 @@
note
description: "Summary description for {CMS_CORE_MODULE_WEBAPI}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
CMS_CORE_MODULE_WEBAPI
inherit
CMS_MODULE_WEBAPI [CMS_CORE_MODULE]
redefine
permissions,
filters
end
create
make
feature -- Security
permissions: LIST [READABLE_STRING_8]
-- List of permission ids, used by this module, and declared.
do
Result := Precursor
Result.force ("admin users")
Result.force ("view users")
end
feature {NONE} -- Router/administration
setup_webapi_router (a_router: WSF_ROUTER; a_api: CMS_API)
-- <Precursor>
local
l_root: CMS_ROOT_WEBAPI_HANDLER
do
create l_root.make (a_api)
a_router.handle ("", l_root, a_router.methods_get)
a_router.handle ("/", l_root, a_router.methods_get)
a_router.handle ("/user/{uid}/access_token", create {CMS_ACCESS_TOKEN_WEBAPI_HANDLER}.make (a_api), a_router.methods_get_post)
a_router.handle ("/user/{uid}", create {CMS_USER_WEBAPI_HANDLER}.make (a_api), a_router.methods_get)
end
feature -- Access: filter
filters (a_api: CMS_API): detachable LIST [WSF_FILTER]
-- Possibly list of Filter's module.
do
create {ARRAYED_LIST [WSF_FILTER]} Result.make (2)
Result.extend (create {CMS_ACCESS_TOKEN_WEBAPI_AUTH_FILTER}.make (a_api))
Result.extend (create {CMS_BASIC_WEBAPI_AUTH_FILTER}.make (a_api))
end
note
copyright: "2011-2017, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -0,0 +1,36 @@
note
description: "Summary description for {CMS_ROOT_WEBAPI_HANDLER}."
date: "$Date$"
revision: "$Revision$"
class
CMS_ROOT_WEBAPI_HANDLER
inherit
CMS_WEBAPI_HANDLER
WSF_URI_HANDLER
create
make
feature -- Execution
execute (req: WSF_REQUEST; res: WSF_RESPONSE)
-- Execute handler for `req' and respond in `res'.
local
rep: HM_WEBAPI_RESPONSE
do
rep := new_webapi_response (req, res)
rep.add_string_field ("site_name", api.setup.site_name)
if attached api.user as u then
add_user_links_to (u, rep)
end
rep.add_self (req.percent_encoded_path_info)
rep.execute
end
note
copyright: "2011-2017, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -0,0 +1,80 @@
note
description: "Summary description for {CMS_USER_WEBAPI_HANDLER}."
date: "$Date$"
revision: "$Revision$"
class
CMS_USER_WEBAPI_HANDLER
inherit
CMS_WEBAPI_HANDLER
WSF_URI_TEMPLATE_HANDLER
create
make
feature -- Execution
execute (req: WSF_REQUEST; res: WSF_RESPONSE)
-- Execute handler for `req' and respond in `res'.
do
if req.is_get_request_method then
execute_get (req, res)
else
send_bad_request (Void, req, res)
end
end
execute_get (req: WSF_REQUEST; res: WSF_RESPONSE)
-- Execute handler for `req' and respond in `res'.
local
rep: HM_WEBAPI_RESPONSE
l_user: detachable CMS_USER
do
if attached api.user as u then
if attached {WSF_STRING} req.path_parameter ("uid") as p_uid then
if p_uid.is_integer then
l_user := api.user_api.user_by_id (p_uid.integer_value)
else
l_user := api.user_api.user_by_name (p_uid.value)
end
-- if l_user = Void and p_uid.is_case_insensitive_equal ("me") then
-- l_user := u
-- end
if l_user /= Void then
if l_user.same_as (u) or api.has_permissions (<<"admin users", "view users">>) then
rep := new_webapi_response (req, res)
rep.add_string_field ("uid", u.id.out)
rep.add_string_field ("name", u.name)
if attached u.email as l_email then
rep.add_string_field ("email", l_email)
end
if attached u.profile_name as l_profile_name then
rep.add_string_field ("profile_name", l_profile_name)
end
add_user_links_to (u, rep)
else
rep := new_wepapi_error_response ("denied", req, res)
rep.set_status_code ({HTTP_STATUS_CODE}.user_access_denied)
end
else
rep := new_wepapi_error_response ("Not found", req, res)
rep.set_status_code ({HTTP_STATUS_CODE}.not_found)
end
else
rep := new_wepapi_error_response ("Bad request", req, res)
rep.set_status_code ({HTTP_STATUS_CODE}.bad_request)
end
rep.execute
else
-- FIXME: use specific Web API response!
send_access_denied (Void, req, res)
end
end
note
copyright: "2011-2017, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -84,6 +84,11 @@ feature -- Operation
sql_storage.sql_modify (a_sql_statement, a_params)
end
sql_delete (a_sql_statement: STRING; a_params: detachable STRING_TABLE [detachable ANY])
do
sql_storage.sql_delete (a_sql_statement, a_params)
end
feature -- Access
sql_start
@@ -133,6 +138,6 @@ feature -- Conversion
end
note
copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
copyright: "2011-2017, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -138,6 +138,11 @@ feature -- Operation
deferred
end
sql_delete (a_sql_statement: STRING; a_params: detachable STRING_TABLE [detachable ANY])
-- Execute sql delete `a_sql_statement' with optional parameters `a_params'.
deferred
end
feature -- Helper
sql_script_content (a_path: PATH): detachable STRING
@@ -462,6 +467,6 @@ feature {NONE} -- Implementation
end
note
copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
copyright: "2011-2017, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -6,29 +6,8 @@ note
deferred class
CMS_ADMINISTRABLE
feature -- Administration
module_administration: like administration
-- Associated administration module.
do
Result := internal_module_administration
if Result = Void then
Result := administration
internal_module_administration := Result
end
end
feature {NONE} -- Implementation
internal_module_administration: detachable like module_administration
-- Cached version of `module_administration`.
feature {NONE} -- Administration
administration: CMS_MODULE_ADMINISTRATION [CMS_MODULE]
-- Administration module.
deferred
end
inherit
CMS_WITH_MODULE_ADMINISTRATION
note
copyright: "2011-2017, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"

View File

@@ -54,6 +54,10 @@ feature {NONE} -- Initialize
s := setup.administration_base_path
administration_base_path_location := s.shared_substring (2, s.count)
-- Webapi backend
s := setup.webapi_base_path
webapi_base_path_location := s.shared_substring (2, s.count)
-- Initialize contents.
initialize_content_types
@@ -398,6 +402,55 @@ feature -- Access: url
site_url: IMMUTABLE_STRING_8
-- Site url
feature -- Access: WebAPI
is_webapi_request (req: WSF_REQUEST): BOOLEAN
do
Result := setup.webapi_enabled and then req.percent_encoded_path_info.starts_with_general (setup.webapi_base_path)
end
webapi_path (a_relative_path: detachable READABLE_STRING_8): STRING_8
require
is_webapi_enabled: setup.webapi_enabled
do
create Result.make_from_string (setup.webapi_base_path)
if a_relative_path /= Void then
if a_relative_path.is_empty then
Result.append_character ('/')
else
if a_relative_path[1] /= '/' then
Result.append_character ('/')
end
Result.append (a_relative_path)
end
end
end
webapi_path_location (a_relative_location: detachable READABLE_STRING_8): STRING_8
require
is_webapi_enabled: setup.webapi_enabled
no_first_slash: a_relative_location = Void or else not a_relative_location.starts_with_general ("/")
do
create Result.make_from_string (webapi_base_path_location)
if a_relative_location /= Void then
if a_relative_location.is_empty then
Result.append_character ('/')
else
if a_relative_location[1] /= '/' then
Result.append_character ('/')
end
Result.append (a_relative_location)
end
end
end
feature {NONE} -- Implementation/WebAPI.
webapi_base_path_location: IMMUTABLE_STRING_8
-- Webapi path without first slash!
feature -- Access: Administration
is_administration_request (req: WSF_REQUEST): BOOLEAN
do
Result := req.percent_encoded_path_info.starts_with_general (setup.administration_base_path)
@@ -435,13 +488,20 @@ feature -- Access: url
end
end
feature {NONE} -- Url implementation.
feature {NONE} -- Implementation/ Administration
administration_base_path_location: IMMUTABLE_STRING_8
-- Administration path without first slash!
feature -- CMS links
webapi_link (a_title: READABLE_STRING_GENERAL; a_relative_location: detachable READABLE_STRING_8): CMS_LOCAL_LINK
require
no_first_slash: a_relative_location = Void or else not a_relative_location.starts_with_general ("/")
do
Result := local_link (a_title, webapi_path_location (a_relative_location))
end
administration_link (a_title: READABLE_STRING_GENERAL; a_relative_location: detachable READABLE_STRING_8): CMS_LOCAL_LINK
require
no_first_slash: a_relative_location = Void or else not a_relative_location.starts_with_general ("/")
@@ -510,9 +570,17 @@ feature -- Settings
switch_to_site_mode
do
if is_administration_mode then
if not is_site_mode then
setup.set_site_mode
is_administration_mode := False
mode := mode_site
end
end
switch_to_webapi_mode
do
if not is_webapi_mode then
setup.set_webapi_mode
mode := mode_webapi
end
end
@@ -520,12 +588,32 @@ feature -- Settings
do
if not is_administration_mode then
setup.set_administration_mode
is_administration_mode := True
mode := mode_administration
end
end
mode: NATURAL_8
mode_site: NATURAL_8 = 0
mode_administration: NATURAL_8 = 1
mode_webapi: NATURAL_8 = 2
is_site_mode: BOOLEAN
do
Result := mode = mode_site
end
is_administration_mode: BOOLEAN
-- Is administration mode?
do
Result := mode = mode_administration
end
is_webapi_mode: BOOLEAN
-- Is webapi mode?
do
Result := mode = mode_webapi
end
is_debug: BOOLEAN
-- Is debug mode enabled?
@@ -969,6 +1057,12 @@ feature {CMS_EXECUTION} -- Hooks
else
l_module := Void
end
elseif is_webapi_mode then
if attached {CMS_WITH_WEBAPI} l_module as wapi then
l_module := wapi.module_webapi
else
l_module := Void
end
end
if l_module /= Void then
if attached {CMS_HOOK_AUTO_REGISTER} l_module as l_auto then
@@ -1215,19 +1309,41 @@ feature -- Environment/ module
module_configuration_by_name (a_module_name: READABLE_STRING_GENERAL; a_name: detachable READABLE_STRING_GENERAL): detachable CONFIG_READER
-- Configuration reader for `a_module', and if `a_name' is set, using name `a_name'.
local
k: STRING_32
p: detachable PATH
l_cache: like module_configuration_cache
do
-- Search first in site/config/modules/$module_name/($app|$module_name).(json|ini)
-- if none, look as sub configuration if $app /= Void
-- and then in site/modules/$module_name/config/($app|$module_name).(json|ini)
-- and if non in sub config if $app /= Void
create k.make_from_string_general (a_module_name)
if a_name /= Void then
k.append_character (':')
k.append_string_general (a_name)
end
l_cache := module_configuration_cache
if l_cache /= Void then
l_cache.search (k)
else
create l_cache.make_caseless (1)
module_configuration_cache := l_cache
end
if l_cache.found then
Result := l_cache.found_item
else
p := site_location.extended ("config").extended ("modules").extended (a_module_name)
Result := module_configuration_by_name_in_location (a_module_name, p, a_name)
if Result = Void then
p := module_location_by_name (a_module_name).extended ("config")
Result := module_configuration_by_name_in_location (a_module_name, p, a_name)
end
l_cache.force (Result, k)
end
end
module_configuration_cache: detachable STRING_TABLE [detachable CONFIG_READER]
-- Cache for `module_configuration(_by_name)` function.
module_configuration_by_name_in_location (a_module_name: READABLE_STRING_GENERAL; a_dir: PATH; a_name: detachable READABLE_STRING_GENERAL): detachable CONFIG_READER
-- Configuration reader from "$a_dir/($a_module_name|$a_name).(json|ini)" location.

View File

@@ -19,6 +19,7 @@ inherit
execute_default,
filter_execute,
initialize,
initialize_filter,
initialize_router
end
@@ -61,6 +62,13 @@ feature {NONE} -- Initialization
create {WSF_MAINTENANCE_FILTER} filter
end
initialize_filter
-- Initialize `filter`.
do
create_filter
-- setup_filter: delayed to `initialize_execution`.
end
initialize_router
-- Initialize `router`.
do
@@ -147,20 +155,34 @@ feature -- Settings: router
configure_api_file_handler (l_router)
end
setup_router_for_administration
-- <Precursor>
setup_router_and_filter_for_webapi
local
l_api: like api
l_router: like router
l_module: CMS_MODULE
f, l_filter, l_last_filter: WSF_FILTER
do
l_api := api
l_router := router
l_api.logger.put_debug (generator + ".setup_router_for_administration", Void)
l_api.logger.put_debug (generator + ".setup_router_for_webapi", Void)
-- Configure root of api handler.
l_router.set_base_url (l_api.administration_path (Void))
l_router.set_base_url (l_api.webapi_path (Void))
-- Find insertion location for new filter
-- i.e just before the CMS_EXECUTION filter.
from
l_filter := filter
until
l_last_filter /= Void or not attached l_filter.next as l_next_filter
loop
if l_next_filter.next = Void then
l_last_filter := l_next_filter
else
l_filter := l_next_filter
end
end
-- Include routes from modules.
across
@@ -169,10 +191,86 @@ feature -- Settings: router
l_module := ic.item
if
l_module.is_initialized and then
attached {CMS_ADMINISTRABLE} l_module as l_administration and then
attached {CMS_WITH_WEBAPI} l_module as l_webapi and then
attached l_webapi.module_webapi as wapi
then
wapi.setup_router (l_router, l_api)
if attached wapi.filters (l_api) as l_filters then
across
l_filters as f_ic
loop
f := f_ic.item
l_filter.set_next (f)
f.set_next (l_last_filter)
l_filter := f
end
end
end
end
end
setup_router_and_filter_for_administration
-- <Precursor>
local
l_api: like api
l_router: like router
l_module: CMS_MODULE
f, l_filter, l_last_filter: WSF_FILTER
do
l_api := api
l_router := router
l_api.logger.put_debug (generator + ".setup_router_and_filter_for_administration", Void)
-- Configure root of api handler.
l_router.set_base_url (l_api.administration_path (Void))
-- Apply normal filters
setup_filter
-- Find insertion location for new filter
-- i.e just before the CMS_EXECUTION filter.
from
l_filter := filter
until
l_last_filter /= Void or not attached l_filter.next as l_next_filter
loop
if l_next_filter.next = Void then
l_last_filter := l_next_filter
else
l_filter := l_next_filter
end
end
-- Include routes from modules.
across
modules as ic
loop
l_module := ic.item
if
l_module.is_initialized
then
if
attached {CMS_WITH_MODULE_ADMINISTRATION} l_module as l_administration and then
attached l_administration.module_administration as adm
then
adm.setup_router (l_router, l_api)
if attached adm.filters (l_api) as l_filters then
across
l_filters as f_ic
loop
f := f_ic.item
l_filter.set_next (f)
f.set_next (l_last_filter)
l_filter := f
end
end
-- elseif
-- attached {CMS_WITH_WEBAPI} l_module as l_wapi and then
-- attached l_wapi.module_webapi as wapi
-- then
-- wapi.setup_router (l_router, l_api)
end
end
end
map_uri ("/install", create {CMS_ADMIN_INSTALL_HANDLER}.make (api), l_router.methods_head_get)
@@ -234,6 +332,8 @@ feature -- Request execution
request.set_uploaded_file_path (api.temp_location)
if api.is_administration_request (request) then
initialize_administration_execution
elseif api.is_webapi_request (request) then
initialize_webapi_execution
else
initialize_site_execution
end
@@ -244,15 +344,24 @@ feature -- Request execution
do
api.switch_to_site_mode
api.initialize_execution
setup_filter
setup_router
end
initialize_webapi_execution
-- Initialize for site execution.
do
api.switch_to_webapi_mode
api.initialize_execution
setup_router_and_filter_for_webapi
end
initialize_administration_execution
-- Initialize for administration execution.
do
api.switch_to_administration_mode
api.initialize_execution
setup_router_for_administration
setup_router_and_filter_for_administration
end
execute
@@ -277,7 +386,6 @@ feature -- Filters
-- Create `filter'.
local
f, l_filter: detachable WSF_FILTER
l_module: CMS_MODULE
l_api: like api
do
l_api := api
@@ -294,6 +402,32 @@ feature -- Filters
-- f.set_next (l_filter)
-- l_filter := f
filter := l_filter
end
setup_filter
-- Setup `filter'.
local
f, l_filter, l_last_filter: detachable WSF_FILTER
l_module: CMS_MODULE
l_api: like api
do
l_api := api
-- Find insertion location for new filter
-- i.e just before the CMS_EXECUTION filter.
from
l_filter := filter
until
l_last_filter /= Void or not attached l_filter.next as l_next_filter
loop
if l_next_filter.next = Void then
l_last_filter := l_next_filter
else
l_filter := l_next_filter
end
end
-- Include filters from modules
across
modules as ic
@@ -305,18 +439,14 @@ feature -- Filters
then
across l_m_filters as f_ic loop
f := f_ic.item
f.set_next (l_filter)
l_filter.set_next (f)
f.set_next (l_last_filter)
-- f.set_next (l_filter)
l_filter := f
end
end
end
filter := l_filter
end
setup_filter
-- Setup `filter'.
do
-- filter := l_filter
end
feature -- Execution

View File

@@ -80,7 +80,7 @@ feature -- Status
is_initialized: BOOLEAN
-- Is Current module initialized?
feature {CMS_API, CMS_MODULE_ADMINISTRATION} -- Access: API
feature {CMS_API, CMS_MODULE} -- Access: API
module_api: detachable CMS_MODULE_API
-- Eventual module api.

View File

@@ -14,6 +14,8 @@ inherit
rename
is_initialized as module_is_initialized,
is_enabled as module_is_enabled
redefine
module_api
end
feature {NONE} -- Initialization
@@ -53,6 +55,14 @@ feature -- Status
Result := module.is_enabled
end
feature {CMS_API, CMS_MODULE_ADMINISTRATION, CMS_MODULE_WEBAPI} -- Access: API
module_api: detachable CMS_MODULE_API
-- Eventual module api.
do
Result := module.module_api
end
feature -- Router
setup_router (a_router: WSF_ROUTER; a_api: CMS_API)

View File

@@ -0,0 +1,36 @@
note
description: "Interface providing administration module."
date: "$Date$"
revision: "$Revision$"
deferred class
CMS_WITH_MODULE_ADMINISTRATION
feature -- Administration
module_administration: like administration
-- Associated administration module.
do
Result := internal_module_administration
if Result = Void then
Result := administration
internal_module_administration := Result
end
end
feature {NONE} -- Implementation
internal_module_administration: detachable like module_administration
-- Cached version of `module_administration`.
feature {NONE} -- Administration
administration: CMS_MODULE_ADMINISTRATION [CMS_MODULE]
-- Administration module.
deferred
end
note
copyright: "2011-2017, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -6,10 +6,13 @@ note
revision: "$Revision$"
deferred class
CMS_AUTH_FILTER_I
CMS_AUTH_FILTER
inherit
WSF_FILTER
rename
execute as auth_execute
end
feature {NONE} -- Initialization
@@ -25,20 +28,28 @@ feature -- API Service
feature -- Basic operations
execute (req: WSF_REQUEST; res: WSF_RESPONSE)
auth_execute (req: WSF_REQUEST; res: WSF_RESPONSE)
-- <Precursor>
deferred
do
-- If user is already authenticated, do not try current authenticating filter
-- and go to next filter directly.
if api.user_is_authenticated then
execute_next (req, res)
else
execute (req, res)
end
end
auth_strategy: STRING
execute (req: WSF_REQUEST; res: WSF_RESPONSE)
-- <Precursor>
require
no_user_authenticated: api.user = Void
deferred
end
set_current_user (u: CMS_USER)
do
api.set_user (u)
-- Record auth strategy:
api.set_execution_variable ("auth_strategy", auth_strategy)
end
end

View File

@@ -1,6 +1,6 @@
note
description: "[
Generic CMS Response.
Generic CMS Web Response.
It builds the content to get process to render the output.
]"
date: "$Date$"
@@ -10,64 +10,33 @@ deferred class
CMS_RESPONSE
inherit
CMS_URL_UTILITIES
CMS_RESPONSE_I
redefine
make, initialize
end
REFACTORING_HELPER
CMS_URL_UTILITIES
feature {NONE} -- Initialization
make (req: WSF_REQUEST; res: WSF_RESPONSE; a_api: like api)
do
status_code := {HTTP_STATUS_CODE}.ok
api := a_api
request := req
response := res
create header.make
create values.make (3)
site_url := a_api.site_url
if attached a_api.base_url as l_base_url then
base_url := l_base_url
end
base_path := a_api.base_path
initialize
Precursor (req, res, a_api)
end
initialize
local
s: READABLE_STRING_8
do
Precursor
get_theme
create menu_system.make
initialize_block_region_settings
s := request.percent_encoded_path_info
if not s.is_empty and then s[1] = '/' then
create location.make_from_string (s.substring (2, s.count))
else
create location.make_from_string (s)
end
end
feature -- Access
request: WSF_REQUEST
response: WSF_RESPONSE
status_code: INTEGER
header: WSF_HEADER
main_content: detachable STRING_8
feature -- Settings
is_administration_mode: BOOLEAN
-- Is administration mode?
do
Result := api.is_administration_mode
end
feature -- Access: metadata
title: detachable READABLE_STRING_32
@@ -77,96 +46,19 @@ feature -- Access: metadata
description: detachable READABLE_STRING_32
keywords: detachable READABLE_STRING_32
publication_date: detachable DATE_TIME
-- Optional publication date.
modification_date: detachable DATE_TIME
-- Optional modification date.
additional_page_head_lines: detachable LIST [READABLE_STRING_8]
-- HTML>head>...extra lines
redirection: detachable READABLE_STRING_8
-- Location for eventual redirection.
redirection_delay: NATURAL
-- Optional redirection delay in seconds.
feature -- Access: query
location: IMMUTABLE_STRING_8
-- Associated cms local location.
request_url (opts: detachable CMS_API_OPTIONS): STRING_8
-- Current request location as a url.
do
Result := url (location, opts)
end
feature -- API
api: CMS_API
-- Current CMS API.
setup: CMS_SETUP
-- Current setup
do
Result := api.setup
end
formats: CMS_FORMATS
-- Available content formats.
do
Result := api.formats
end
feature -- URL utilities
is_front: BOOLEAN
-- Is current response related to "front" page?
local
l_path_info: READABLE_STRING_8
do
l_path_info := request.percent_encoded_path_info
if attached setup.front_page_path as l_front_page_path then
Result := l_front_page_path.same_string (l_path_info)
else
if base_path.same_string (l_path_info) then
Result := True
else
Result := l_path_info.is_empty or else l_path_info.same_string ("/")
end
end
end
site_url: IMMUTABLE_STRING_8
-- Absolute site url.
-- Always ends with '/'
base_url: detachable IMMUTABLE_STRING_8
-- Base url if any.
--| Usually it is Void, but it could be
--| /project/demo/
base_path: IMMUTABLE_STRING_8
-- Base path, default to "/".
-- Always ends with '/'
-- Could be /project/demo/
feature -- Access: CMS
site_name: STRING_32
do
Result := setup.site_name
end
front_page_url: READABLE_STRING_8
do
Result := absolute_url ("/", Void)
end
values: CMS_VALUE_TABLE
-- Associated values indexed by string name.
@@ -177,72 +69,6 @@ feature -- Specific values
Result := values.item ("optional_content_type")
end
feature -- User access
is_authenticated: BOOLEAN
-- Is user authenticated?
do
Result := user /= Void
end
user: detachable CMS_USER
-- Active user if authenticated.
do
Result := api.user
end
set_user (u: CMS_USER)
-- Set active user to `u'.
require
attached_u: u /= Void
do
api.set_user (u)
end
unset_user
-- Unset active user.
do
api.unset_user
end
feature -- Permission
has_permission_on_link (a_link: CMS_LINK): BOOLEAN
-- Does current user has permission to access link `a_link'?
do
Result := True
if
attached {CMS_LOCAL_LINK} a_link as lnk and then
attached lnk.permission_arguments as l_perms
then
Result := has_permissions (l_perms)
end
end
has_permission (a_permission: READABLE_STRING_GENERAL): BOOLEAN
-- Does current user has permission `a_permission' ?
do
Result := user_has_permission (user, a_permission)
end
has_permissions (a_permission_list: ITERABLE [READABLE_STRING_GENERAL]): BOOLEAN
-- Does current user has any of the permissions `a_permission_list' ?
do
Result := user_has_permissions (user, a_permission_list)
end
user_has_permission (a_user: detachable CMS_USER; a_permission: READABLE_STRING_GENERAL): BOOLEAN
-- Does `a_user' has permission `a_permission' ?
do
Result := api.user_has_permission (a_user, a_permission)
end
user_has_permissions (a_user: detachable CMS_USER; a_permission_list: ITERABLE [READABLE_STRING_GENERAL]): BOOLEAN
-- Does `a_user' has any of the permissions `a_permission_list' ?
do
Result := api.user_has_permissions (a_user, a_permission_list)
end
feature -- Head customization
add_additional_head_line (s: READABLE_STRING_8; a_allow_duplication: BOOLEAN)
@@ -322,27 +148,6 @@ feature -- Element change
description := d
end
set_keywords (s: like keywords)
do
keywords := s
end
set_publication_date (dt: like publication_date)
do
publication_date := dt
if dt /= Void and modification_date = Void then
modification_date := dt
end
end
set_modification_date (dt: like modification_date)
do
modification_date := dt
if dt /= Void and publication_date = Void then
publication_date := dt
end
end
set_main_content (s: like main_content)
do
main_content := s
@@ -365,34 +170,6 @@ feature -- Element change
values.remove (k)
end
set_redirection (a_location: READABLE_STRING_8)
-- Set `redirection' to `a_location'.
do
redirection := a_location
end
set_redirection_delay (nb_secs: NATURAL)
do
redirection_delay := nb_secs
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
debug
to_implement ("Add implementation")
end
-- 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
@@ -904,16 +681,6 @@ feature -- Blocks
Result.set_is_raw (True)
end
feature -- Hooks
hooks: CMS_HOOK_CORE_MANAGER
-- Manager handling hook subscriptions.
obsolete
"Use api.hooks [2017-05-31]"
do
Result := api.hooks
end
feature -- Menu: change
add_to_main_menu (lnk: CMS_LINK)
@@ -938,22 +705,6 @@ feature -- Menu: change
m.extend (lnk)
end
feature -- Internationalization (i18n)
translation (a_text: READABLE_STRING_GENERAL; opts: detachable CMS_API_OPTIONS): STRING_32
-- Translated text `a_text' according to expected context (lang, ...)
-- and adapt according to options eventually set by `opts'.
do
Result := api.translation (a_text, opts)
end
formatted_string (a_text: READABLE_STRING_GENERAL; args: TUPLE): STRING_32
-- Format `a_text' using arguments `args'.
--| ex: formatted_string ("hello $1, see page $title.", ["bob", "contact"] -> "hello bob, see page contact"
do
Result := api.formatted_string (a_text, args)
end
feature -- Message
add_message (a_msg: READABLE_STRING_8; a_category: detachable READABLE_STRING_8)
@@ -1068,19 +819,6 @@ feature {NONE} -- Theme helpers
internal_wsf_theme: detachable WSF_THEME
-- Once per object for `wsf_theme'.
feature -- Element Change
set_status_code (a_status: INTEGER)
-- Set `status_code' with `a_status'.
note
EIS: "src=eiffel:?class=HTTP_STATUS_CODE"
do
to_implement ("Feature to test if a_status is a valid status code!!!.")
status_code := a_status
ensure
status_code_set: status_code = a_status
end
feature -- Cache managment
clear_cache (a_cache_id_list: detachable ITERABLE [READABLE_STRING_GENERAL])
@@ -1366,32 +1104,8 @@ feature -- Generation
a_lnk.set_is_forbidden (not has_permission_on_link (a_lnk))
end
feature -- Helpers: cms link
administration_link (a_title: READABLE_STRING_GENERAL; a_relative_location: detachable READABLE_STRING_8): CMS_LOCAL_LINK
require
no_first_slash: a_relative_location = Void or else not a_relative_location.starts_with_general ("/")
do
Result := api.administration_link (a_title, a_relative_location)
end
local_link (a_title: READABLE_STRING_GENERAL; a_location: READABLE_STRING_8): CMS_LOCAL_LINK
do
Result := api.local_link (a_title, a_location)
end
user_local_link (u: CMS_USER; a_opt_title: detachable READABLE_STRING_GENERAL): CMS_LOCAL_LINK
do
Result := api.user_local_link (u, a_opt_title)
end
feature -- Helpers: html links
user_profile_name, user_display_name (u: CMS_USER): READABLE_STRING_32
do
Result := api.user_display_name (u)
end
user_html_link (u: CMS_USER): STRING
require
u_with_name: not u.name.is_whitespace
@@ -1399,43 +1113,6 @@ feature -- Helpers: html links
Result := api.user_html_link (u)
end
feature -- Helpers: URLs
location_absolute_url (a_location: READABLE_STRING_8; opts: detachable CMS_API_OPTIONS): STRING
-- Absolute URL for `a_location'.
--| Options `opts' could be
--| - absolute: True|False => return absolute url
--| - query: string => append "?query"
--| - fragment: string => append "#fragment"
do
Result := api.location_absolute_url (a_location, opts)
end
location_url (a_location: READABLE_STRING_8; opts: detachable CMS_API_OPTIONS): STRING
-- URL for `a_location'.
--| Options `opts' could be
--| - absolute: True|False => return absolute url
--| - query: string => append "?query"
--| - fragment: string => append "#fragment"
do
Result := api.location_url (a_location, opts)
end
module_resource_url (a_module: CMS_MODULE; a_path: READABLE_STRING_8; opts: detachable CMS_API_OPTIONS): STRING_8
-- Url for resource `a_path` associated with module `a_module`.
require
a_valid_valid: a_path.is_empty or else a_path.starts_with ("/")
do
Result := url ("/module/" + a_module.name + a_path, opts)
end
user_url (u: CMS_USER): like url
require
u_with_id: u.has_id
do
Result := api.user_url (u)
end
feature -- Execution
execute
@@ -1499,7 +1176,6 @@ feature {NONE} -- Execution
on_terminated
do
end
note

View File

@@ -0,0 +1,376 @@
note
description: "[
Generic CMS Response.
It builds the content to get process to render the output.
]"
date: "$Date$"
revision: "$Revision$"
deferred class
CMS_RESPONSE_I
inherit
CMS_URL_UTILITIES
REFACTORING_HELPER
feature {NONE} -- Initialization
make (req: WSF_REQUEST; res: WSF_RESPONSE; a_api: like api)
do
status_code := {HTTP_STATUS_CODE}.ok
api := a_api
request := req
response := res
create header.make
site_url := a_api.site_url
if attached a_api.base_url as l_base_url then
base_url := l_base_url
end
base_path := a_api.base_path
initialize
end
initialize
local
s: READABLE_STRING_8
do
s := request.percent_encoded_path_info
if not s.is_empty and then s[1] = '/' then
create location.make_from_string (s.substring (2, s.count))
else
create location.make_from_string (s)
end
end
feature -- Access
request: WSF_REQUEST
response: WSF_RESPONSE
status_code: INTEGER
header: WSF_HEADER
feature -- Settings
is_site_mode: BOOLEAN
-- Is site mode?
do
Result := api.is_site_mode
end
is_webapi_mode: BOOLEAN
-- Is Web API mode?
do
Result := api.is_webapi_mode
end
is_administration_mode: BOOLEAN
-- Is administration mode?
do
Result := api.is_administration_mode
end
feature -- Access: metadata
keywords: detachable READABLE_STRING_32
publication_date: detachable DATE_TIME
-- Optional publication date.
modification_date: detachable DATE_TIME
-- Optional modification date.
redirection: detachable READABLE_STRING_8
-- Location for eventual redirection.
redirection_delay: NATURAL
-- Optional redirection delay in seconds.
feature -- Access: query
location: IMMUTABLE_STRING_8
-- Associated cms local location.
request_url (opts: detachable CMS_API_OPTIONS): STRING_8
-- Current request location as a url.
do
Result := url (location, opts)
end
feature -- API
api: CMS_API
-- Current CMS API.
setup: CMS_SETUP
-- Current setup
do
Result := api.setup
end
feature -- URL utilities
is_front: BOOLEAN
-- Is current response related to "front" page?
local
l_path_info: READABLE_STRING_8
do
l_path_info := request.percent_encoded_path_info
if attached setup.front_page_path as l_front_page_path then
Result := l_front_page_path.same_string (l_path_info)
else
if base_path.same_string (l_path_info) then
Result := True
else
Result := l_path_info.is_empty or else l_path_info.same_string ("/")
end
end
end
site_url: IMMUTABLE_STRING_8
-- Absolute site url.
-- Always ends with '/'
base_url: detachable IMMUTABLE_STRING_8
-- Base url if any.
--| Usually it is Void, but it could be
--| /project/demo/
base_path: IMMUTABLE_STRING_8
-- Base path, default to "/".
-- Always ends with '/'
-- Could be /project/demo/
feature -- Access: CMS
site_name: STRING_32
do
Result := setup.site_name
end
front_page_url: READABLE_STRING_8
do
Result := absolute_url ("/", Void)
end
feature -- User access
is_authenticated: BOOLEAN
-- Is user authenticated?
do
Result := user /= Void
end
user: detachable CMS_USER
-- Active user if authenticated.
do
Result := api.user
end
set_user (u: CMS_USER)
-- Set active user to `u'.
require
attached_u: u /= Void
do
api.set_user (u)
end
unset_user
-- Unset active user.
do
api.unset_user
end
feature -- Permission
has_permission_on_link (a_link: CMS_LINK): BOOLEAN
-- Does current user has permission to access link `a_link'?
do
Result := True
if
attached {CMS_LOCAL_LINK} a_link as lnk and then
attached lnk.permission_arguments as l_perms
then
Result := has_permissions (l_perms)
end
end
has_permission (a_permission: READABLE_STRING_GENERAL): BOOLEAN
-- Does current user has permission `a_permission' ?
do
Result := user_has_permission (user, a_permission)
end
has_permissions (a_permission_list: ITERABLE [READABLE_STRING_GENERAL]): BOOLEAN
-- Does current user has any of the permissions `a_permission_list' ?
do
Result := user_has_permissions (user, a_permission_list)
end
user_has_permission (a_user: detachable CMS_USER; a_permission: READABLE_STRING_GENERAL): BOOLEAN
-- Does `a_user' has permission `a_permission' ?
do
Result := api.user_has_permission (a_user, a_permission)
end
user_has_permissions (a_user: detachable CMS_USER; a_permission_list: ITERABLE [READABLE_STRING_GENERAL]): BOOLEAN
-- Does `a_user' has any of the permissions `a_permission_list' ?
do
Result := api.user_has_permissions (a_user, a_permission_list)
end
feature -- Element change
set_keywords (s: like keywords)
do
keywords := s
end
set_publication_date (dt: like publication_date)
do
publication_date := dt
if dt /= Void and modification_date = Void then
modification_date := dt
end
end
set_modification_date (dt: like modification_date)
do
modification_date := dt
if dt /= Void and publication_date = Void then
publication_date := dt
end
end
set_redirection (a_location: READABLE_STRING_8)
-- Set `redirection' to `a_location'.
do
redirection := a_location
end
set_redirection_delay (nb_secs: NATURAL)
do
redirection_delay := nb_secs
end
feature -- Hooks
hooks: CMS_HOOK_CORE_MANAGER
-- Manager handling hook subscriptions.
obsolete
"Use api.hooks [2017-05-31]"
do
Result := api.hooks
end
feature -- Internationalization (i18n)
translation (a_text: READABLE_STRING_GENERAL; opts: detachable CMS_API_OPTIONS): STRING_32
-- Translated text `a_text' according to expected context (lang, ...)
-- and adapt according to options eventually set by `opts'.
do
Result := api.translation (a_text, opts)
end
formatted_string (a_text: READABLE_STRING_GENERAL; args: TUPLE): STRING_32
-- Format `a_text' using arguments `args'.
--| ex: formatted_string ("hello $1, see page $title.", ["bob", "contact"] -> "hello bob, see page contact"
do
Result := api.formatted_string (a_text, args)
end
feature -- Element Change
set_status_code (a_status: INTEGER)
-- Set `status_code' with `a_status'.
note
EIS: "src=eiffel:?class=HTTP_STATUS_CODE"
do
status_code := a_status
ensure
status_code_set: status_code = a_status
end
feature -- Helpers: cms link
webapi_link (a_title: READABLE_STRING_GENERAL; a_relative_location: detachable READABLE_STRING_8): CMS_LOCAL_LINK
require
no_first_slash: a_relative_location = Void or else not a_relative_location.starts_with_general ("/")
do
Result := api.webapi_link (a_title, a_relative_location)
end
administration_link (a_title: READABLE_STRING_GENERAL; a_relative_location: detachable READABLE_STRING_8): CMS_LOCAL_LINK
require
no_first_slash: a_relative_location = Void or else not a_relative_location.starts_with_general ("/")
do
Result := api.administration_link (a_title, a_relative_location)
end
local_link (a_title: READABLE_STRING_GENERAL; a_location: READABLE_STRING_8): CMS_LOCAL_LINK
do
Result := api.local_link (a_title, a_location)
end
user_local_link (u: CMS_USER; a_opt_title: detachable READABLE_STRING_GENERAL): CMS_LOCAL_LINK
do
Result := api.user_local_link (u, a_opt_title)
end
feature -- Helpers: html links
user_profile_name, user_display_name (u: CMS_USER): READABLE_STRING_32
do
Result := api.user_display_name (u)
end
feature -- Helpers: URLs
location_absolute_url (a_location: READABLE_STRING_8; opts: detachable CMS_API_OPTIONS): STRING
-- Absolute URL for `a_location'.
--| Options `opts' could be
--| - absolute: True|False => return absolute url
--| - query: string => append "?query"
--| - fragment: string => append "#fragment"
do
Result := api.location_absolute_url (a_location, opts)
end
location_url (a_location: READABLE_STRING_8; opts: detachable CMS_API_OPTIONS): STRING
-- URL for `a_location'.
--| Options `opts' could be
--| - absolute: True|False => return absolute url
--| - query: string => append "?query"
--| - fragment: string => append "#fragment"
do
Result := api.location_url (a_location, opts)
end
module_resource_url (a_module: CMS_MODULE; a_path: READABLE_STRING_8; opts: detachable CMS_API_OPTIONS): STRING_8
-- Url for resource `a_path` associated with module `a_module`.
require
a_valid_valid: a_path.is_empty or else a_path.starts_with ("/")
do
Result := url ("/module/" + a_module.name + a_path, opts)
end
user_url (u: CMS_USER): like url
require
u_with_id: u.has_id
do
Result := api.user_url (u)
end
feature -- Execution
execute
deferred
end
note
copyright: "2011-2017, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -7,7 +7,6 @@ class
BAD_REQUEST_ERROR_CMS_RESPONSE
inherit
CMS_RESPONSE
redefine
custom_prepare
@@ -37,6 +36,6 @@ feature -- Execution
end
note
copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
copyright: "2011-2017, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -7,7 +7,6 @@ class
FORBIDDEN_ERROR_CMS_RESPONSE
inherit
CMS_RESPONSE
redefine
custom_prepare

View File

@@ -7,7 +7,6 @@ class
INTERNAL_SERVER_ERROR_CMS_RESPONSE
inherit
CMS_RESPONSE
redefine
custom_prepare
@@ -36,7 +35,7 @@ feature -- Execution
set_main_content ("<em>Internal Server Error</em>")
end
note
copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
copyright: "2011-2017, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -7,7 +7,6 @@ class
NOT_FOUND_ERROR_CMS_RESPONSE
inherit
CMS_RESPONSE
redefine
custom_prepare
@@ -36,7 +35,7 @@ feature -- Execution
set_main_content ("<em>The requested page %"" + request.request_uri + "%"could not be found.</em>")
end
note
copyright: "2011-2016, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
copyright: "2011-2017, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -7,7 +7,6 @@ class
NOT_IMPLEMENTED_ERROR_CMS_RESPONSE
inherit
CMS_RESPONSE
redefine
custom_prepare
@@ -38,7 +37,7 @@ feature -- Execution
end
end
note
copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
copyright: "2011-2017, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -19,7 +19,7 @@ feature -- Execution
do
end
note
copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
copyright: "2011-2017, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -0,0 +1,51 @@
note
description: "Summary description for Hyper media {HM_WEBAPI_RESPONSE}."
date: "$Date$"
revision: "$Revision$"
deferred class
HM_WEBAPI_RESPONSE
inherit
WEBAPI_RESPONSE
feature -- Element change
add_self (a_href: READABLE_STRING_8)
deferred
end
add_string_field (a_name: READABLE_STRING_GENERAL; a_value: READABLE_STRING_GENERAL)
deferred
end
add_boolean_field (a_name: READABLE_STRING_GENERAL; a_value: BOOLEAN)
deferred
end
add_integer_64_field (a_name: READABLE_STRING_GENERAL; a_value: INTEGER_64)
deferred
end
add_iterator_field (a_name: READABLE_STRING_GENERAL; a_value: ITERABLE [detachable ANY])
deferred
end
add_table_iterator_field (a_name: READABLE_STRING_GENERAL; a_value: TABLE_ITERABLE [detachable ANY, READABLE_STRING_GENERAL])
deferred
end
add_link (rel: READABLE_STRING_8; a_attname: detachable READABLE_STRING_8; a_att_href: READABLE_STRING_8)
deferred
end
add_templated_link (rel: READABLE_STRING_8; a_attname: detachable READABLE_STRING_8; a_att_href: READABLE_STRING_8)
deferred
end
invariant
note
copyright: "2011-2017, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -7,7 +7,6 @@ class
HOME_CMS_RESPONSE
inherit
CMS_RESPONSE
redefine
custom_prepare
@@ -32,5 +31,8 @@ feature -- Execution
set_title (Void)
set_page_title (Void)
end
note
copyright: "2011-2017, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -0,0 +1,201 @@
note
description: "Summary description for JSON {JSON_WEBAPI_RESPONSE}."
date: "$Date$"
revision: "$Revision$"
class
JSON_WEBAPI_RESPONSE
inherit
HM_WEBAPI_RESPONSE
redefine
initialize
end
create
make
feature {NONE} -- Initialization
initialize
do
Precursor
create resource.make_empty
end
feature -- Access
resource: JSON_OBJECT
feature -- Element change
add_self (a_href: READABLE_STRING_8)
do
add_link ("self", Void, a_href)
end
feature -- Fields
-- add_field (a_name: READABLE_STRING_GENERAL; a_value: detachable ANY)
-- do
-- resource.put (new_resource_item (a_value), a_name)
-- end
add_string_field (a_name: READABLE_STRING_GENERAL; a_value: READABLE_STRING_GENERAL)
do
resource.put_string (a_value, a_name)
end
add_boolean_field (a_name: READABLE_STRING_GENERAL; a_value: BOOLEAN)
do
resource.put_boolean (a_value, a_name)
end
add_integer_64_field (a_name: READABLE_STRING_GENERAL; a_value: INTEGER_64)
do
resource.put_integer (a_value, a_name)
end
add_natural_64_field (a_name: READABLE_STRING_GENERAL; a_value: NATURAL_64)
do
resource.put_natural (a_value, a_name)
end
add_real_64_field (a_name: READABLE_STRING_GENERAL; a_value: REAL_64)
do
resource.put_real (a_value, a_name)
end
add_iterator_field (a_name: READABLE_STRING_GENERAL; a_value: ITERABLE [detachable ANY])
do
resource.put (new_resource_item (a_value), a_name)
end
add_table_iterator_field (a_name: READABLE_STRING_GENERAL; a_value: TABLE_ITERABLE [detachable ANY, READABLE_STRING_GENERAL])
do
resource.put (new_resource_item (a_value), a_name)
end
feature -- Links
add_link (rel: READABLE_STRING_8; a_attname: detachable READABLE_STRING_8 ; a_att_href: READABLE_STRING_8)
local
lnks: JSON_OBJECT
lnk: JSON_OBJECT
do
if attached {JSON_OBJECT} resource.item ("_links") as j_links then
lnks := j_links
else
create lnks.make_with_capacity (1)
resource.put (lnks, "_links")
end
create lnk.make_with_capacity (2)
if a_attname /= Void then
lnk.put_string (a_attname, "name")
end
lnk.put_string (a_att_href, "href")
lnks.put (lnk, rel)
end
add_templated_link (rel: READABLE_STRING_8; a_attname: detachable READABLE_STRING_8; a_att_href: READABLE_STRING_8)
local
lnks: JSON_OBJECT
lnk: JSON_OBJECT
do
if attached {JSON_OBJECT} resource.item ("_links") as j_links then
lnks := j_links
else
create lnks.make_with_capacity (1)
resource.put (lnks, "_links")
end
create lnk.make_with_capacity (2)
if a_attname /= Void then
lnk.put_string (a_attname, "name")
end
lnk.put_string (a_att_href, "href")
lnk.put_boolean (True, "templated")
lnks.put (lnk, rel)
end
feature -- Execution
process
local
m: WSF_PAGE_RESPONSE
do
create m.make_with_body (resource.representation)
m.header.put_content_type ("application/json")
response.send (m)
end
feature {NONE} -- Implementation factory
new_resource_item (a_value: detachable ANY): JSON_VALUE
local
l_serializer: JSON_REFLECTOR_SERIALIZER
ctx: JSON_SERIALIZER_CONTEXT
do
create {JSON_NULL} Result
create l_serializer
create ctx
ctx.set_default_serializer (l_serializer)
ctx.set_is_type_name_included (False)
ctx.register_serializer (create {TABLE_ITERABLE_JSON_SERIALIZER [detachable ANY, READABLE_STRING_GENERAL]}, {TABLE_ITERABLE [detachable ANY, READABLE_STRING_GENERAL]})
ctx.register_serializer (create {ITERABLE_JSON_SERIALIZER [detachable ANY]}, {ITERABLE [detachable ANY]})
Result := l_serializer.to_json (a_value, ctx)
-- if a_value = Void then
-- create {JSON_NULL} Result
-- elseif attached {READABLE_STRING_GENERAL} a_value as s then
-- create {JSON_STRING} Result.make_from_string_general (s)
-- elseif attached {BOOLEAN} a_value as b then
-- create {JSON_BOOLEAN} Result.make (b)
-- elseif attached {NUMERIC} a_value as num then
---- if attached {INTEGER_64} num as i64 then
---- add_integer_64_field (a_name, i64)
---- elseif attached {INTEGER_32} num as i32 then
---- add_integer_64_field (a_name, i32.as_integer_64)
---- elseif attached {INTEGER_16} num as i16 then
---- add_integer_64_field (a_name, i16.as_integer_64)
---- elseif attached {INTEGER_8} num as i8 then
---- add_integer_64_field (a_name, i8.as_integer_64)
---- elseif attached {NATURAL_64} num as n64 then
---- add_natural_64_field (a_name, n64)
---- elseif attached {NATURAL_32} num as n32 then
---- add_natural_64_field (a_name, n32.as_natural_64)
---- elseif attached {NATURAL_16} num as n16 then
---- add_natural_64_field (a_name, n16.as_natural_64)
---- elseif attached {NATURAL_8} num as n8 then
---- add_natural_64_field (a_name, n8.as_natural_64)
---- elseif attached {REAL_64} num as r64 then
---- add_real_64_field (a_name, r64)
---- elseif attached {REAL_32} num as r32 then
---- add_real_64_field (a_name, r32.to_double
---- else
---- check is_basic_numeric_type: False end
---- add_string_field (a_name, num.out)
---- end
-- elseif attached {CHARACTER_8} a_value as ch8 then
---- add_string_field (a_name, ch8.out)
-- elseif attached {CHARACTER_32} a_value as ch32 then
---- add_string_field (a_name, ch32.out)
-- elseif attached {POINTER} a_value as ptr then
---- add_string_field (a_name, ptr.out)
-- elseif attached {ITERABLE [detachable ANY]} a_value as arr then
---- add_iterator_field (a_name, arr)
-- elseif attached {TABLE_ITERABLE [detachable ANY, READABLE_STRING_GENERAL]} a_value as tb then
---- add_table_iterator_field (a_name, tb)
-- else
-- check is_supported_type: False end
-- end
end
invariant
note
copyright: "2011-2017, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -0,0 +1,70 @@
note
description: "Summary description for Microdata {MD_WEBAPI_RESPONSE}."
date: "$Date$"
revision: "$Revision$"
class
MD_WEBAPI_RESPONSE
inherit
HM_WEBAPI_RESPONSE
redefine
initialize
end
create
make
feature {NONE} -- Initialization
initialize
do
Precursor
create resource.make
end
feature -- Access
resource: MD_DOCUMENT
feature -- Element change
add_self (a_href: READABLE_STRING_8)
do
add_field ("self", a_href)
end
add_field (a_name: READABLE_STRING_GENERAL; a_value: READABLE_STRING_GENERAL)
local
p: MD_PROPERTY
do
create p.make ("self", a_value, Void)
resource.put (p)
end
add_link (rel: READABLE_STRING_8; a_attname: READABLE_STRING_8 ; a_att_href: READABLE_STRING_8)
local
i: MD_ITEM
do
-- TODO
end
feature -- Execution
execute
local
m: WSF_PAGE_RESPONSE
do
-- TODO
create m.make_with_body ("NOT IMPLEMENTED")
m.set_status_code ({HTTP_STATUS_CODE}.not_implemented)
m.header.put_content_type ("application/xhtml+xml")
response.send (m)
end
invariant
note
copyright: "2011-2017, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -0,0 +1,44 @@
note
description: "[
Generic Web API Response.
]"
date: "$Date$"
revision: "$Revision$"
deferred class
WEBAPI_RESPONSE
inherit
CMS_RESPONSE_I
feature -- Status report
is_root: BOOLEAN
-- Is current response related to root api endpoint?
local
l_path_info: READABLE_STRING_8
do
l_path_info := request.percent_encoded_path_info
if l_path_info.ends_with_general ("/") then
l_path_info := l_path_info.substring (1, l_path_info.count - 1)
end
Result := l_path_info.same_string (api.setup.webapi_base_path)
end
feature -- Execution
execute
do
api.hooks.invoke_webapi_response_alter (Current)
process
end
process
-- Execute Current webapi response.
deferred
end
note
copyright: "2011-2017, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -0,0 +1,90 @@
note
description: "[
Objects that ...
]"
author: "$Author$"
date: "$Date$"
revision: "$Revision$"
deferred class
CMS_MODULE_WEBAPI [G -> CMS_MODULE]
inherit
CMS_MODULE
rename
is_initialized as module_is_initialized,
is_enabled as module_is_enabled
redefine
module_api
end
feature {NONE} -- Initialization
make (a_module: G)
-- Initialize `Current'.
do
module := a_module
version := a_module.version
description := a_module.description
package := a_module.package
module_is_initialized := a_module.is_initialized
module_is_enabled := a_module.is_enabled
end
feature -- Access
module: G
name: STRING
do
Result := module.name
end
feature {CMS_API, CMS_MODULE_ADMINISTRATION, CMS_MODULE_WEBAPI} -- Access: API
module_api: detachable CMS_MODULE_API
-- Eventual module api.
do
Result := module.module_api
end
feature -- Status
is_initialized: BOOLEAN
-- Is Current module initialized?
do
Result := module.is_initialized
end
is_enabled: BOOLEAN
-- Is Current module enabled?
do
Result := module.is_enabled
end
feature -- Router
setup_router (a_router: WSF_ROUTER; a_api: CMS_API)
-- <Precursor>
do
if a_router.base_url /= Void then
setup_webapi_router (a_router, a_api)
end
end
feature {NONE} -- Router/administration
setup_webapi_router (a_router: WSF_ROUTER; a_api: CMS_API)
-- Setup url dispatching for Current module web API.
-- (note: `a_router` is already based with webapi path prefix).
require
is_initialized: is_initialized
router_has_base_url: a_router.base_url /= Void
deferred
end
note
copyright: "2011-2017, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -0,0 +1,15 @@
note
description: "Summary description for {CMS_WEBAPI_AUTH_FILTER}."
date: "$Date$"
revision: "$Revision$"
deferred class
CMS_WEBAPI_AUTH_FILTER
inherit
CMS_AUTH_FILTER
note
copyright: "2011-2017, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -0,0 +1,100 @@
note
description: "[
Objects that ...
]"
author: "$Author$"
date: "$Date$"
revision: "$Revision$"
deferred class
CMS_WEBAPI_HANDLER
inherit
WSF_HANDLER
CMS_API_ACCESS
CMS_ENCODERS
REFACTORING_HELPER
feature {NONE} -- Initialization
make (a_api: CMS_API)
-- Initialize Current handler with `a_api'.
do
api := a_api
end
feature -- API Service
api: CMS_API
feature -- Factory
new_webapi_response (req: WSF_REQUEST; res: WSF_RESPONSE): HM_WEBAPI_RESPONSE
do
-- create {MD_WEBAPI_RESPONSE} Result.make (req, res, api)
create {JSON_WEBAPI_RESPONSE} Result.make (req, res, api)
end
new_wepapi_error_response (msg: detachable READABLE_STRING_GENERAL; req: WSF_REQUEST; res: WSF_RESPONSE): HM_WEBAPI_RESPONSE
do
Result := new_webapi_response (req, res)
if msg /= Void then
Result.add_string_field ("error", msg)
else
Result.add_string_field ("error", "True")
end
end
send_not_found (m: detachable READABLE_STRING_GENERAL; req: WSF_REQUEST; res: WSF_RESPONSE)
local
rep: HM_WEBAPI_RESPONSE
do
if m /= Void then
rep := new_wepapi_error_response (m, req, res)
else
rep := new_wepapi_error_response ("Not found", req, res)
end
rep.set_status_code ({HTTP_STATUS_CODE}.not_found)
rep.execute
end
send_access_denied (m: detachable READABLE_STRING_GENERAL; req: WSF_REQUEST; res: WSF_RESPONSE)
local
rep: HM_WEBAPI_RESPONSE
do
if m /= Void then
rep := new_wepapi_error_response (m, req, res)
else
rep := new_wepapi_error_response ("Access denied", req, res)
end
rep.set_status_code ({HTTP_STATUS_CODE}.user_access_denied)
rep.execute
end
send_bad_request (m: detachable READABLE_STRING_GENERAL; req: WSF_REQUEST; res: WSF_RESPONSE)
local
rep: HM_WEBAPI_RESPONSE
do
if m /= Void then
rep := new_wepapi_error_response (m, req, res)
else
rep := new_wepapi_error_response ("Bad request", req, res)
end
rep.set_status_code ({HTTP_STATUS_CODE}.bad_request)
rep.execute
end
feature {NONE} -- Builder
add_user_links_to (u: CMS_USER; rep: HM_WEBAPI_RESPONSE)
do
rep.add_link ("account", "user/" + u.id.out, rep.api.webapi_path ("/user/" + u.id.out))
end
note
copyright: "2011-2017, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -0,0 +1,36 @@
note
description: "Interface providing webapi module."
date: "$Date$"
revision: "$Revision$"
deferred class
CMS_WITH_WEBAPI
feature -- Administration
module_webapi: like webapi
-- Associated web api module.
do
Result := internal_module_webapi
if Result = Void then
Result := webapi
internal_module_webapi := Result
end
end
feature {NONE} -- Implementation
internal_module_webapi: detachable like module_webapi
-- Cached version of `module_webapi`.
feature {NONE} -- Web API
webapi: CMS_MODULE_WEBAPI [CMS_MODULE]
-- Web API module.
deferred
end
note
copyright: "2011-2017, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -0,0 +1,107 @@
div {
background-color: #ffdddd;
}
ul.horizontal li {
display: inline-block;
}
#header #primary.menu ul li {
color: #555;
background-color: #fff;
padding: 10px;
margin: 0;
}
#header #primary.menu ul li a {
color: #555;
text-decoration: none;
}
#header #primary.menu ul li a:hover {
color: black;
}
#header #primary.menu ul.horizontal {
border-bottom: solid 1px #ddd;
}
#header #primary.menu ul.horizontal li {
border-top: solid 3px #fff;
}
#header #primary.menu ul.horizontal li:hover {
background-color: #ffe;
border-top: solid 3px #999;
}
#header #primary.menu ul.horizontal li.active {
font-weight: bold;
border-top: solid 3px #ddd;
background-color: #ddd;
}
#header #primary.menu ul.horizontal li.active:hover {
border-top: solid 3px blue;
}
#content {
margin-left: 20px;
}
#content #highlighted {
position: relative;
border: solid 1px #ddd;
background-color: #ffc;
width: 70%;
left: 15%;
right: 15%;
padding: 5px;
font-style: italic;
}
#content .preview {
border: solid 1px red;
}
.sidebar {
padding: 5px;
margin: 3px;
/* border: solid 1px #ccc; */
}
.sidebar#sidebar_first {
width: 250px;
position: fixed;
top: 45px;
left: 0;
bottom: 0;
width: 200px;
border-right: solid 1px #ddd;
}
.sidebar#sidebar_second {
width: 250px;
float: right;
}
.sidebar + .main {
margin-left: 200px;
}
#primary-tabs ul.horizontal {
list-style-type: none;
}
#primary-tabs ul.horizontal li {
display: inline;
padding: 2px 5px;
border: solid 1px #ccf;
}
#primary-tabs ul.horizontal li.active {
border-color: #99f #99f #ddd;
border-style: solid solid none;
border-width: 2px 1px 0;
padding: 2px 7px 1px;
}
#message li.error {
background-color: #f99;
border: solid 1px red;
padding: 5px 2px 5px 2px;
}
table.with_border thead td {
font-weight: bold;
}
table.with_border td {
border: solid 1px #ccc;
padding: 2px 5px 2px 5px;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 994 B

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,8 @@
$(document).ready(function() {
$('#gcse_search_form').submit(function() {
window.open('', 'formpopup', 'width=600,height=600,resizeable,scrollbars');
this.target = 'formpopup';
});
});

View File

@@ -0,0 +1,110 @@
ul.horizontal {
li {
display: inline-block;
}
}
#header {
#primary.menu {
ul {
li {
color: #555;
a {
color: #555;
text-decoration: none;
&:hover { color: black; }
}
background-color: #fff;
padding: 10px;
margin: 0;
}
&.horizontal {
border-bottom: solid 1px #ddd;
li {
border-top: solid 3px #fff;
&:hover {
background-color: #ffe;
border-top: solid 3px #999;
}
&.active {
font-weight: bold;
border-top: solid 3px #ddd;
background-color: #ddd;
}
&.active:hover {
border-top: solid 3px blue;
}
}
}
}
}
}
#content {
margin-left: 20px;
#highlighted {
position: relative;
border: solid 1px #ddd;
background-color: #ffc;
width: 70%;
left: 15%;
right: 15%;
padding: 5px;
font-style: italic;
}
.preview {
border: solid 1px red;
}
}
.sidebar {
padding: 5px;
margin: 3px;
/* border: solid 1px #ccc; */
&#sidebar_first {
width: 250px;
position: fixed;
top: 45px;
left: 0;
bottom: 0;
width: 200px;
border-right: solid 1px #ddd;
}
&#sidebar_second {
width: 250px;
float: right;
}
&+.main {
margin-left: 200px;
}
}
#primary-tabs {
ul.horizontal {
list-style-type: none;
li {
display: inline;
padding: 2px 5px;
border: solid 1px #ccf;
}
li.active {
border-color: #99f #99f #ddd;
border-style: solid solid none;
border-width: 2px 1px 0;
padding: 2px 7px 1px;
}
}
}
#message li.error {
background-color: #f99;
border: solid 1px red;
padding: 5px 2px 5px 2px;
}
table.with_border {
thead td {
font-weight: bold;
}
td {
border: solid 1px #ccc;
padding: 2px 5px 2px 5px;
}
}

38
themes/admin/debug.tpl Normal file
View File

@@ -0,0 +1,38 @@
{assign name="debug_enabled" value="True"/}
{if condition="$debug_enabled"}
<!-- start debug -->
{literal}
<style>
div.cms-debug>span {
position: absolute;
bottom: 5px;
right: 5px;
color: #ccc;
padding: 5px;
}
div.cms-debug:hover>span {
color: red;
}
div.cms-debug>span+ul {
display: none;
border: solid 2px red;
background-color: #ccc;
white-space: pre-wrap;
}
div.cms-debug:hover>span+ul {
display: block;
position: relative;
bottom: 5px;
left: 1%; right: 1%;
width: 98%;
}
</style>
{/literal}
<div class="cms-debug"><span>Show debug</span>
<ul>
{assign name="kpage" value="page"/}{assign name="kregions" value="regions"/}{foreach key="k" item="i" from="$page.variables"}{unless condition="$k ~ $kpage"}{unless condition="$k ~ $kregions"}<li><strong>{$k/}</strong>={htmlentities}{$i/}{/htmlentities}</li>{/unless}{/unless}
{/foreach}
</ul>
</div>
<!-- end debug -->
{/if}

101
themes/admin/page.tpl Normal file
View File

@@ -0,0 +1,101 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- EWF CMS -->
<link rel="stylesheet" href="{$theme_path/}css/style.css">
<!-- jQuery dep -->
<script src="{$theme_path/}js/jquery-1.10.2.min.js"></script>
<script src="{$theme_path/}js/popup_search.js"></script>
{if isset="$head"}{$head/}{/if}
{if isset="$styles"}{$styles/}{/if}
{if isset="$scripts"}{$scripts/}{/if}
{if isset="$head_lines"}{$head_lines/}{/if}
<!-- bootstrap framework -->
<!-- Latest compiled and minified CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css">
<!-- Optional theme -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap-theme.min.css">
<title>{$head_title/}</title>
</head>
<body>
<!-- Page Top -->
{if isset="$region_top"}
{$region_top/}
{/if}
<!-- Body -->
<div class='container-fluid'>
<!-- Page Header -->
<div id="header">
{if isset="$page.primary_nav"}
{$page.primary_nav/}
{/if}
</div>
<!-- Page search -->
<div class="row">
<div class="col-md-2 col-md-offset-9">
<form action="{$site_url/}gcse" class="search-form" id="gcse_search_form">
<div class="form-group has-feedback">
<input type="search" class="form-control" name="q" id="gcse_search" placeholder="search" value="{htmlentities}{$cms_search_query/}{/htmlentities}" >
<span class="glyphicon glyphicon-search form-control-feedback"></span>
</div>
</form>
</div>
</div>
<!-- General Page Content -->
<div id='content' class='row-fluid'>
<!-- Left Sidebar sidebar_first -->
{unless isempty="$page.region_sidebar_first"}
<div id="sidebar_first" class="sidebar">{$page.region_sidebar_first/}</div>
{/unless}
<!-- Right Sidebar sidebar_second-->
{unless isempty="$page.region_sidebar_second"}
<div id="sidebar_second" class="sidebar">{$page.region_sidebar_second/}</div>
{/unless}
<!-- Highlighted, Help, Content -->
<div id='main' class='span8 main'>
<!-- Highlighted Section -->
{unless isempty="$page.region_highlighted"}
<div id="highlighted">{$page.region_highlighted/}</div>
{/unless}
<!-- Help Section -->
{unless isempty="$page.region_help"}
<div id="help">{$page.region_help/}</div>
{/unless}
<!-- Main Content Section -->
{unless isempty="$page_title"}<h1 class="page-title">{$page_title/}</h1>{/unless}
{$page.region_content/}
{if condition="$page.is_front"}
{if isset="$page.region_feed_news"}
<div class="column" style="width: 45%; float: left">{$page.region_feed_news/}</div>
{/if}
{if isset="$page.region_feed_forum"}
<div class="column" style="width: 45%; float: left">{$page.region_feed_forum/}</div>
{/if}
{/if}
</div>
</div>
</div>
<!--Page footer -->
{$page.region_footer/}
<!-- Page Bottom -->
{$page.region_bottom/}
<!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
<!-- Latest compiled and minified JavaScript -->
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/js/bootstrap.min.js"></script>
{include file="debug.tpl"/}
</body>
</html>

14
themes/admin/theme.info Normal file
View File

@@ -0,0 +1,14 @@
name=admin
engine=smarty
author=jocelyn fiat
version=0.1
regions[page_top] = Top
regions[header] = Header
regions[content] = Content
regions[highlighted] = Highlighted
regions[help] = Help
regions[footer] = Footer
regions[sidebar_first] = first sidebar
regions[sidebar_second] = second sidebar
regions[page_bottom] = Bottom
navigation=default_nav

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 994 B

View File

66
themes/min/page.tpl Normal file
View File

@@ -0,0 +1,66 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- EWF CMS -->
<link rel="stylesheet" href="{$theme_path/}css/style.css">
{if isset="$head"}{$head/}{/if}
{if isset="$styles"}{$styles/}{/if}
{if isset="$scripts"}{$scripts/}{/if}
{if isset="$head_lines"}{$head_lines/}{/if}
<title>{$head_title/}</title>
</head>
<body>
<!-- Page Top -->
{if isset="$region_top"}
{$region_top/}
{/if}
<!-- Body -->
<div>
<!-- Page Header -->
<div id="header">
{if isset="$page.primary_nav"}
{$page.primary_nav/}
{/if}
</div>
{if isset="$page.regions.search"}
<!-- Page search -->
<div class="search">{$page.regions.search/}</div>
{/if}
<!-- General Page Content -->
<div id='content'>
<!-- Left Sidebar sidebar_first -->
{unless isempty="$page.region_sidebar_first"}
<div id="sidebar_first" class="sidebar">{$page.region_sidebar_first/}</div>
{/unless}
<!-- Right Sidebar sidebar_second-->
{unless isempty="$page.region_sidebar_second"}
<div id="sidebar_second" class="sidebar">{$page.region_sidebar_second/}</div>
{/unless}
<!-- Highlighted, Help, Content -->
<div id='main'>
<!-- Highlighted Section -->
{unless isempty="$page.region_highlighted"}
<div id="highlighted">{$page.region_highlighted/}</div>
{/unless}
<!-- Help Section -->
{unless isempty="$page.region_help"}
<div id="help">{$page.region_help/}</div>
{/unless}
<!-- Main Content Section -->
{unless isempty="$page_title"}<h1 class="page-title">{$page_title/}</h1>{/unless}
{$page.region_content/}
</div>
</div>
</div>
<!--Page footer -->
{$page.region_footer/}
<!-- Page Bottom -->
{$page.region_bottom/}
</body>
</html>

15
themes/min/theme.info Normal file
View File

@@ -0,0 +1,15 @@
name=min
engine=smarty
author=jfiat
version=0.1
regions[page_top] = Top
regions[header] = Header
regions[search] = Search
regions[content] = Content
regions[highlighted] = Highlighted
regions[help] = Help
regions[footer] = Footer
regions[sidebar_first] = first sidebar
regions[sidebar_second] = second sidebar
regions[page_bottom] = Bottom
navigation=default_nav

View File

@@ -16,7 +16,7 @@ feature -- Access
help: STRING_32
once
Result := "[--module|-m <MODULE_PATH>] [(--dir|-d <CMS_PATH>) | <MODULE_NAME>] [--site-dir <CMS_SITE_PATH>]"
Result := "[--module|-m <MODULE_PATH>] [--theme <THEME_PATH>] [(--dir|-d <CMS_PATH>) | <MODULE_NAME>] [--site-dir <CMS_SITE_PATH>]"
end
feature -- Status report
@@ -25,6 +25,7 @@ feature -- Status report
-- Is the submitted install command valid?
local
i, n: INTEGER
optional_theme: BOOLEAN
optional_module: BOOLEAN
optional_config: BOOLEAN
cms_path: BOOLEAN
@@ -41,10 +42,14 @@ feature -- Status report
optional_config := True
elseif args [i].same_string ("--config") then
optional_config := True
elseif args [i].same_string ("-m") then
optional_module := True
elseif args [i].same_string ("--module") then
elseif
args [i].same_string ("-m") or args [i].same_string ("--module")
then
optional_module := True
elseif
args [i].same_string ("--theme")
then
optional_theme := True
elseif args [i].same_string ("-d") then
cms_path := True
elseif args [i].same_string ("--dir") then
@@ -56,14 +61,14 @@ feature -- Status report
Result := True
else
if n <= 5 then
if (cms_path and (optional_module)) then
if cms_path and optional_module then
-- valid command
Result := True
else
print ("Error check the optional argument --module|-m and --dir|-d")
end
elseif n <= 3 then
if (cms_path and not optional_module) then
if cms_path and not optional_module then
Result := True
else
print ("Error missing value for dir")
@@ -96,6 +101,16 @@ feature -- Helpers
end
end
theme_name (a_path: PATH): STRING_32
do
-- FIXME: better implementation needed. Either based on "a" new theme.info file, or parsing the .ecf
if attached a_path.entry as e then
Result := e.name
else
Result := a_path.name
end
end
feature -- Execution
execute (args: ARRAY [READABLE_STRING_32])
@@ -103,13 +118,16 @@ feature -- Execution
-- Pattern: module_src/site/* => cms/site/modules/$module_name/*
local
l_config_path, l_site_path, l_cms_path, p: detachable PATH
l_module_source_locations: ARRAYED_LIST [PATH]
l_module_source_locations, l_theme_source_locations: ARRAYED_LIST [ROC_INSTALL_COPY_PARAMETERS]
l_site_dir: DIRECTORY
l_modules_dir: DIRECTORY
l_themes_dir: DIRECTORY
l_dest_dir: DIRECTORY
l_cp_params: ROC_INSTALL_COPY_PARAMETERS
i,n: INTEGER
do
create l_module_source_locations.make (1)
create l_theme_source_locations.make (1)
from
i := 1
n := args.upper
@@ -148,6 +166,13 @@ feature -- Execution
if i <= n then
l_module_source_locations.force (create {PATH}.make_from_string (args[i]))
end
elseif
arg.same_string ("--theme")
then
i := i + 1
if i <= n then
l_theme_source_locations.force (create {PATH}.make_from_string (args[i]))
end
elseif
arg.same_string ("-v")
or arg.same_string ("--verbose")
@@ -192,7 +217,34 @@ feature -- Execution
if not p.is_absolute then
p := l_cms_path.extended_path (p)
end
l_module_source_locations.extend (p.canonical_path)
create l_cp_params.make_with_location (p.canonical_path)
if attached cfg.resolved_text_item ({STRING_32} "modules." + ic.item + ".mode") as l_mode and then l_mode.is_case_insensitive_equal_general ("link") then
l_cp_params.set_link_mode (True)
end
l_module_source_locations.extend (l_cp_params)
end
end
end
if attached cfg.resolved_text_table_item ("themes") as tb then
across
tb as ic
loop
l_theme_source_locations.extend (create {PATH}.make_from_string (ic.item))
end
elseif attached cfg.table_keys ("themes") as tb_keys then
across
tb_keys as ic
loop
if attached cfg.resolved_text_item ({STRING_32} "themes." + ic.item + ".location") as l_loc then
create p.make_from_string (l_loc)
if not p.is_absolute then
p := l_cms_path.extended_path (p)
end
create l_cp_params.make_with_location (p.canonical_path)
if attached cfg.resolved_text_item ({STRING_32} "modules." + ic.item + ".mode") as l_mode and then l_mode.is_case_insensitive_equal_general ("link") then
l_cp_params.set_link_mode (True)
end
l_theme_source_locations.extend (l_cp_params)
end
end
end
@@ -211,7 +263,7 @@ feature -- Execution
loop
if
attached ic.item as l_module_source_path and then
attached module_name (l_module_source_path) as l_mod_name
attached module_name (l_module_source_path.location) as l_mod_name
then
-- Install configuration files.
create l_site_dir.make_with_path (l_site_path)
@@ -249,6 +301,45 @@ feature -- Execution
print ("Error: could not retrieve module name.%N")
end
end
across
l_theme_source_locations as ic
loop
if
attached ic.item as l_theme_source_path and then
attached theme_name (l_theme_source_path.location) as l_theme_name
then
-- Install configuration files.
create l_site_dir.make_with_path (l_site_path)
if l_site_dir.exists then
create l_themes_dir.make_with_path (l_site_path.extended ("themes"))
if not l_themes_dir.exists then
l_themes_dir.create_dir
end
create l_dest_dir.make_with_path (l_themes_dir.path.extended (l_theme_name))
if not l_dest_dir.exists then
l_dest_dir.create_dir
end
print ("Install theme ")
print (l_theme_name)
print (" in %"")
print (l_dest_dir.path.name)
print ("%":%N")
install_theme_elements (l_theme_source_path, l_dest_dir.path, Void)
print (" - ")
print (directories_count.out + " director" + if directories_count > 1 then "ies" else "y" end + ", ")
print (files_count.out + " file" + if files_count > 1 then "s" else "" end)
if files_changes_count > 0 then
print (" (+" + files_changes_count.out + ")")
end
print (".%N")
else
print ({STRING_32} "The CMS Application located at " + l_cms_path.name + "does not have the site or themes folders.%N")
end
else
print ("Error: could not retrieve theme name.%N")
end
end
end
roc_configuration (a_cfg_location: PATH): detachable CONFIG_READER
@@ -259,7 +350,7 @@ feature -- Execution
end
end
install_module_elements (a_module_source_path: PATH; a_cms_module_target_path: PATH; a_element: detachable READABLE_STRING_GENERAL)
install_module_elements (a_module_source_path: ROC_INSTALL_COPY_PARAMETERS; a_cms_module_target_path: PATH; a_element: detachable READABLE_STRING_GENERAL)
-- Install module site files from `a_module_source_path' to cms application `a_cms_module_target_path' under expected modules folder.
-- If `a_element' is set, take into account only sub folder `a_element'.
local
@@ -267,7 +358,7 @@ feature -- Execution
l_dest_dir: DIRECTORY
l_src_dir: DIRECTORY
do
l_path := a_module_source_path.extended ("site")
l_path := a_module_source_path.location.extended ("site")
if a_element /= Void then
-- Copy all files under "site/$a_element" into "site/modules/$module_name/$a_element" location.
create l_src_dir.make_with_path (l_path.extended (a_element))
@@ -284,7 +375,43 @@ feature -- Execution
directories_count := -1
files_changes_count := 0
if a_module_source_path.is_link_mode then
copy_directory (l_src_dir, l_dest_dir, True)
else
link_directory (l_src_dir, l_dest_dir, True)
end
end
install_theme_elements (a_theme_source_path: ROC_INSTALL_COPY_PARAMETERS; a_cms_theme_target_path: PATH; a_element: detachable READABLE_STRING_GENERAL)
-- Install theme site files from `a_theme_source_path' to cms application `a_cms_theme_target_path' under expected "themes" folder.
-- If `a_element' is set, take into account only sub folder `a_element'.
local
l_path: PATH
l_dest_dir: DIRECTORY
l_src_dir: DIRECTORY
do
l_path := a_theme_source_path.location
if a_element /= Void then
-- Copy all files under "site/$a_element" into "site/themes/$theme_name/$a_element" location.
create l_src_dir.make_with_path (l_path.extended (a_element))
create l_dest_dir.make_with_path (a_cms_theme_target_path.extended (a_element))
else
-- Copy all files under "site" into "site/themes/$theme_name/" location.
create l_src_dir.make_with_path (l_path)
create l_dest_dir.make_with_path (a_cms_theme_target_path)
end
if not l_dest_dir.exists then
l_dest_dir.create_dir
end
files_count := 0
directories_count := -1
files_changes_count := 0
if a_theme_source_path.is_link_mode then
copy_directory (l_src_dir, l_dest_dir, True)
else
link_directory (l_src_dir, l_dest_dir, True)
end
end
is_verbose: BOOLEAN
@@ -333,6 +460,13 @@ feature {NONE} -- System/copy files
end
end
link_directory (a_src: DIRECTORY; a_dest: DIRECTORY; is_recursive: BOOLEAN)
-- Link all elements from `a_src' to `a_dest'.
do
-- TODO: implement symbolic link ...
copy_directory (a_src, a_dest, is_recursive)
end
copy_file_in_directory (a_file: FILE; a_dir: PATH)
-- Copy file `a_file' to dir `a_dir'.
local
@@ -386,6 +520,6 @@ feature {NONE} -- System/copy files
note
copyright: "2011-2016, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
copyright: "2011-2017, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -0,0 +1,39 @@
note
description: "Summary description for {ROC_INSTALL_COPY_PARAMETERS}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
ROC_INSTALL_COPY_PARAMETERS
create
make_with_location
convert
make_with_location ({PATH})
feature {NONE} -- Creation
make_with_location (a_location: PATH)
do
location := a_location
end
feature -- Access
location: PATH
is_link_mode: BOOLEAN
feature -- Element change
set_link_mode (b: BOOLEAN)
do
is_link_mode := b
end
note
copyright: "2011-2017, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end