Compare commits

...

17 Commits

Author SHA1 Message Date
0813abe0bb Fixed ROC CMS library compilation. 2015-12-18 15:29:43 +01:00
1094acb3ec Removed unused local variable 2015-12-16 21:05:57 +01:00
e7c9a54f3f Removed unused local. 2015-12-16 21:03:15 +01:00
bbbdac12c8 Moved taxonomy html generation to CMS_TAXONOMY_API. 2015-12-16 21:03:03 +01:00
jvelilla
22528315cb Removed unneeded file. 2015-12-16 16:01:02 +01:00
jvelilla
090a48eb85 Updated class CMS_TOKEN_GENERATOR.
Remove once in sha1 feature.
Updated encoded_base_64 to base_64
2015-12-16 16:01:01 +01:00
jvelilla
e05c4dca3a Fixed typos
Renamed class CMS_SESSION_CONSTANT to CMS_SESSION_CONSTANTS
Removed unneeded classes and files.
Update SQL implementation.
2015-12-16 16:00:59 +01:00
jvelilla
2255fcc0f6 Added Module Session Authentication with Cookies.
Updated Demo example with the Module Session (Authentication with Cookies)
Fixed little issue with SQL query in OpenID module.
2015-12-16 16:00:58 +01:00
e50fb6959e Moved taxonomy integration for web form inside CMS_TAXONOMY_API.
Moved a few helpers routine from CMS_RESPONSE to CMS_API.
Added CMS_CONTENT.identifier: detachable READABLE_STRING_32 .
2015-12-16 15:59:22 +01:00
jvelilla
3b88c746a1 Removed unneeded file. 2015-12-16 10:43:21 -03:00
jvelilla
fa8ef44a4a Merge branch 'jvelilla-roc_login_session' 2015-12-16 10:11:55 -03:00
jvelilla
068943734f Updated class CMS_TOKEN_GENERATOR.
Remove once in sha1 feature.
Updated encoded_base_64 to base_64
2015-12-16 10:03:35 -03:00
jvelilla
089179e60e Fixed typos
Renamed class CMS_SESSION_CONSTANT to CMS_SESSION_CONSTANTS
Removed unneeded classes and files.
Update SQL implementation.
2015-12-15 15:32:31 -03:00
jvelilla
c25590c9cd Added Module Session Authentication with Cookies.
Updated Demo example with the Module Session (Authentication with Cookies)
Fixed little issue with SQL query in OpenID module.
2015-12-13 18:19:25 -03:00
23d266497b Made the SQL storage more flexible with INTEGER_32, by allowing to retrieve INTEGER_64 and convert to INTEGER_32 if value can be converted to integer 32. 2015-12-10 11:26:28 +01:00
ce8de442e9 Implemented taxonomy administration pages
- create term, vocabulary, add or remove term from vocabularies, ...
Fixed content editing related to taxonomy  (especially with multiple terms vs tags).
Fixed various SQL storage issue related to taxonomy and vocabularies.
Added CMS_RESPONSE.wsf_theme as helper.
2015-12-10 11:21:20 +01:00
e3ae564746 Removed an obsolete call to CMS_RESPONSE.hooks . 2015-12-07 22:08:37 +01:00
53 changed files with 2662 additions and 465 deletions

View File

@@ -31,6 +31,7 @@
<library name="cms_node_module" location="..\..\modules\node\node-safe.ecf" readonly="false"/> <library name="cms_node_module" location="..\..\modules\node\node-safe.ecf" readonly="false"/>
<library name="cms_taxnomy_module" location="..\..\modules\taxonomy\taxonomy-safe.ecf" readonly="false"/> <library name="cms_taxnomy_module" location="..\..\modules\taxonomy\taxonomy-safe.ecf" readonly="false"/>
<library name="cms_oauth_20_module" location="..\..\modules\oauth20\oauth20-safe.ecf" readonly="false"/> <library name="cms_oauth_20_module" location="..\..\modules\oauth20\oauth20-safe.ecf" readonly="false"/>
<library name="cms_session_auth_module" location="..\..\modules\session_auth\cms_session_auth-safe.ecf" readonly="false"/>
<library name="cms_openid_module" location="..\..\modules\openid\openid-safe.ecf" readonly="false"/> <library name="cms_openid_module" location="..\..\modules\openid\openid-safe.ecf" readonly="false"/>
<library name="cms_recent_changes_module" location="..\..\modules\recent_changes\recent_changes-safe.ecf" readonly="false"/> <library name="cms_recent_changes_module" location="..\..\modules\recent_changes\recent_changes-safe.ecf" readonly="false"/>
<library name="persistence_sqlite3" location="..\..\library\persistence\sqlite3\sqlite3-safe.ecf" readonly="false"> <library name="persistence_sqlite3" location="..\..\library\persistence\sqlite3\sqlite3-safe.ecf" readonly="false">

View File

@@ -0,0 +1,11 @@
CREATE TABLE session_auth (
`uid` INTEGER PRIMARY KEY NOT NULL CHECK(`uid`>=0),
`access_token` TEXT NOT NULL,
`created` DATETIME NOT NULL,
CONSTRAINT `uid`
UNIQUE(`uid`),
CONSTRAINT `access_token`
UNIQUE(`access_token`)
);

View File

@@ -0,0 +1,37 @@
<div class="primary-tabs">
{unless isset="$user"}
<h3>Login or <a href="{$site_url/}account/roc-register">Register</a></h3>
<div>
<div>
<form name="cms_session_auth" action="{$site_url/}account/login-with-session" method="POST">
<div>
<input type="text" name="username" id="username" required value="{$username/}">
<label>Username</label>
</div>
<div>
<input type="password" name="password" id="password" required >
<label>Password</label>
</div>
<button type="submit">Login</button>
</form>
</div>
</div>
<div>
<div>
<p>
<a href="{$site_url/}account/new-password">Forgot password?</a>
</p>
</div>
</div>
{/unless}
{if isset=$error}
<div>
<div>
<p>
<strong>{$error/}
</p>
</div>
</div>
{/if}
</div>

View File

@@ -19,3 +19,8 @@ ul.taxonomy li:hover {
border-bottom: solid 1px #66f; border-bottom: solid 1px #66f;
background-color: #ddf; background-color: #ddf;
} }
table.taxonomy td {
border: solid 1px #ccc;
padding: 2px;
}

View File

@@ -19,3 +19,9 @@ ul.taxonomy {
} }
} }
} }
table.taxonomy {
td {
border: solid 1px #ccc;
padding: 2px;
}
}

View File

@@ -91,13 +91,10 @@ ul.horizontal li {
padding: 5px 2px 5px 2px; padding: 5px 2px 5px 2px;
} }
ul.taxonomy-entities { table.with_border thead td {
list-style-type: none; font-weight: bold;
padding: 0;
} }
ul.taxonomy-entities li { table.with_border td {
padding: 0; border: solid 1px #ccc;
margin-top: 5px; padding: 2px 5px 2px 5px;
margin-bottom: 10px;
border-top: dotted 1px #ccc;
} }

View File

@@ -96,13 +96,12 @@ ul.horizontal {
padding: 5px 2px 5px 2px; padding: 5px 2px 5px 2px;
} }
ul.taxonomy-entities { table.with_border {
list-style-type: none; thead td {
padding: 0; font-weight: bold;
li { }
padding: 0; td {
margin-top: 5px; border: solid 1px #ccc;
margin-bottom: 10px; padding: 2px 5px 2px 5px;
border-top: dotted 1px #ccc;
} }
} }

View File

@@ -89,6 +89,9 @@ feature -- CMS modules
create {GOOGLE_CUSTOM_SEARCH_MODULE} m.make create {GOOGLE_CUSTOM_SEARCH_MODULE} m.make
a_setup.register_module (m) a_setup.register_module (m)
create {CMS_SESSION_AUTH_MODULE} m.make
a_setup.register_module (m)
end end
end end

View File

@@ -152,6 +152,7 @@ feature -- Query
-- Retrieved value at `a_index' position in `item'. -- Retrieved value at `a_index' position in `item'.
local local
l_item: like sql_item l_item: like sql_item
i64: INTEGER_64
do do
l_item := sql_item (a_index) l_item := sql_item (a_index)
if attached {INTEGER_32} l_item as i then if attached {INTEGER_32} l_item as i then
@@ -159,7 +160,18 @@ feature -- Query
elseif attached {INTEGER_32_REF} l_item as l_value then elseif attached {INTEGER_32_REF} l_item as l_value then
Result := l_value.item Result := l_value.item
else else
check is_integer_32: False end if attached {INTEGER_64} l_item as i then
i64 := i
elseif attached {INTEGER_64_REF} l_item as l_value then
i64 := l_value.item
else
check is_integer_32: False end
end
if i64 <= {INTEGER_32}.max_value then
Result := i64.to_integer_32
else
check is_integer_32: False end
end
end end
end end

View File

@@ -44,7 +44,7 @@ feature -- Execution
create {GENERIC_VIEW_CMS_RESPONSE} l_response.make (req, res, api) create {GENERIC_VIEW_CMS_RESPONSE} l_response.make (req, res, api)
f := clear_cache_web_form (l_response) f := clear_cache_web_form (l_response)
create s.make_empty create s.make_empty
f.append_to_html (create {CMS_TO_WSF_THEME}.make (l_response, l_response.theme), s) f.append_to_html (l_response.wsf_theme, s)
l_response.set_main_content (s) l_response.set_main_content (s)
l_response.execute l_response.execute
end end
@@ -63,14 +63,14 @@ feature -- Execution
fd.is_valid fd.is_valid
then then
if attached fd.string_item ("op") as l_op and then l_op.same_string (text_clear_all_caches) then if attached fd.string_item ("op") as l_op and then l_op.same_string (text_clear_all_caches) then
l_response.hooks.invoke_clear_cache (Void, l_response) api.hooks.invoke_clear_cache (Void, l_response)
l_response.add_notice_message ("Caches cleared (if allowed)!") l_response.add_notice_message ("Caches cleared (if allowed)!")
else else
fd.report_error ("Invalid form data!") fd.report_error ("Invalid form data!")
end end
end end
create s.make_empty create s.make_empty
f.append_to_html (create {CMS_TO_WSF_THEME}.make (l_response, l_response.theme), s) f.append_to_html (l_response.wsf_theme, s)
l_response.set_main_content (s) l_response.set_main_content (s)
l_response.execute l_response.execute
end end

View File

@@ -44,7 +44,7 @@ feature -- Execution
create {GENERIC_VIEW_CMS_RESPONSE} l_response.make (req, res, api) create {GENERIC_VIEW_CMS_RESPONSE} l_response.make (req, res, api)
f := exportation_web_form (l_response) f := exportation_web_form (l_response)
create s.make_empty create s.make_empty
f.append_to_html (create {CMS_TO_WSF_THEME}.make (l_response, l_response.theme), s) f.append_to_html (l_response.wsf_theme, s)
l_response.set_main_content (s) l_response.set_main_content (s)
l_response.execute l_response.execute
end end
@@ -85,7 +85,7 @@ feature -- Execution
end end
end end
create s.make_empty create s.make_empty
f.append_to_html (create {CMS_TO_WSF_THEME}.make (l_response, l_response.theme), s) f.append_to_html (l_response.wsf_theme, s)
l_response.set_main_content (s) l_response.set_main_content (s)
l_response.execute l_response.execute
end end

View File

@@ -88,7 +88,7 @@ feature -- Execution
create {GENERIC_VIEW_CMS_RESPONSE} r.make (req, res, api) create {GENERIC_VIEW_CMS_RESPONSE} r.make (req, res, api)
f := modules_collection_web_form (r) f := modules_collection_web_form (r)
create s.make_empty create s.make_empty
f.append_to_html (create {CMS_TO_WSF_THEME}.make (r, r.theme), s) f.append_to_html (r.wsf_theme, s)
r.set_page_title ("Modules") r.set_page_title ("Modules")
r.set_main_content (s) r.set_main_content (s)
r.execute r.execute
@@ -133,7 +133,7 @@ feature -- Execution
then then
r.add_error_message ("Error occurred.") r.add_error_message ("Error occurred.")
create s.make_empty create s.make_empty
f.append_to_html (create {CMS_TO_WSF_THEME}.make (r, r.theme), s) f.append_to_html (r.wsf_theme, s)
r.set_page_title ("Modules") r.set_page_title ("Modules")
r.set_main_content (s) r.set_main_content (s)
else else

View File

@@ -8,30 +8,10 @@ class
inherit inherit
CMS_RESPONSE CMS_RESPONSE
redefine
make,
initialize
end
create create
make make
feature {NONE} -- Initialization
make (req: WSF_REQUEST; res: WSF_RESPONSE; a_api: like api)
do
create {WSF_NULL_THEME} wsf_theme.make
Precursor (req, res, a_api)
end
initialize
do
Precursor
create {CMS_TO_WSF_THEME} wsf_theme.make (Current, theme)
end
wsf_theme: WSF_THEME
feature -- Process feature -- Process
process process

View File

@@ -8,32 +8,12 @@ class
inherit inherit
CMS_RESPONSE CMS_RESPONSE
redefine
make,
initialize
end
CMS_SHARED_SORTING_UTILITIES CMS_SHARED_SORTING_UTILITIES
create create
make make
feature {NONE} -- Initialization
make (req: WSF_REQUEST; res: WSF_RESPONSE; a_api: like api)
do
create {WSF_NULL_THEME} wsf_theme.make
Precursor (req, res, a_api)
end
initialize
do
Precursor
create {CMS_TO_WSF_THEME} wsf_theme.make (Current, theme)
end
wsf_theme: WSF_THEME
feature -- Query feature -- Query
role_id_path_parameter (req: WSF_REQUEST): INTEGER_64 role_id_path_parameter (req: WSF_REQUEST): INTEGER_64

View File

@@ -8,31 +8,10 @@ class
inherit inherit
CMS_RESPONSE CMS_RESPONSE
redefine
make,
initialize
end
create create
make make
feature {NONE} -- Initialization
make (req: WSF_REQUEST; res: WSF_RESPONSE; a_api: like api;)
do
create {WSF_NULL_THEME} wsf_theme.make
Precursor (req, res, a_api)
end
initialize
do
Precursor
create {CMS_TO_WSF_THEME} wsf_theme.make (Current, theme)
end
wsf_theme: WSF_THEME
feature -- Query feature -- Query
role_id_path_parameter (req: WSF_REQUEST): INTEGER_64 role_id_path_parameter (req: WSF_REQUEST): INTEGER_64

View File

@@ -7,32 +7,11 @@ class
CMS_USER_FORM_RESPONSE CMS_USER_FORM_RESPONSE
inherit inherit
CMS_RESPONSE CMS_RESPONSE
redefine
make,
initialize
end
create create
make make
feature {NONE} -- Initialization
make (req: WSF_REQUEST; res: WSF_RESPONSE; a_api: like api)
do
create {WSF_NULL_THEME} wsf_theme.make
Precursor (req, res, a_api)
end
initialize
do
Precursor
create {CMS_TO_WSF_THEME} wsf_theme.make (Current, theme)
end
wsf_theme: WSF_THEME
feature -- Query feature -- Query
user_id_path_parameter (req: WSF_REQUEST): INTEGER_64 user_id_path_parameter (req: WSF_REQUEST): INTEGER_64

View File

@@ -8,31 +8,10 @@ class
inherit inherit
CMS_RESPONSE CMS_RESPONSE
redefine
make,
initialize
end
create create
make make
feature {NONE} -- Initialization
make (req: WSF_REQUEST; res: WSF_RESPONSE; a_api: like api;)
do
create {WSF_NULL_THEME} wsf_theme.make
Precursor (req, res, a_api)
end
initialize
do
Precursor
create {CMS_TO_WSF_THEME} wsf_theme.make (Current, theme)
end
wsf_theme: WSF_THEME
feature -- Query feature -- Query
user_id_path_parameter (req: WSF_REQUEST): INTEGER_64 user_id_path_parameter (req: WSF_REQUEST): INTEGER_64

View File

@@ -9,10 +9,10 @@ class
inherit inherit
CMS_MODULE CMS_MODULE
redefine redefine
setup_hooks setup_hooks,
permissions
end end
CMS_HOOK_AUTO_REGISTER CMS_HOOK_AUTO_REGISTER
CMS_HOOK_VALUE_TABLE_ALTER CMS_HOOK_VALUE_TABLE_ALTER
@@ -52,6 +52,13 @@ feature -- Access
name: STRING = "auth" name: STRING = "auth"
permissions: LIST [READABLE_STRING_8]
-- List of permission ids, used by this module, and declared.
do
Result := Precursor
Result.force ("account register")
end
feature -- Access: docs feature -- Access: docs
root_dir: PATH root_dir: PATH
@@ -228,6 +235,7 @@ feature -- Handler
end end
else else
create {FORBIDDEN_ERROR_CMS_RESPONSE} r.make (req, res, api) create {FORBIDDEN_ERROR_CMS_RESPONSE} r.make (req, res, api)
r.set_main_content ("You can also contact the webmaster to ask for an account.")
end end
r.execute r.execute

View File

@@ -11,8 +11,10 @@ deferred class
inherit inherit
CMS_CONTENT CMS_CONTENT
rename
has_identifier as has_id
redefine redefine
debug_output debug_output, has_id
end end
REFACTORING_HELPER REFACTORING_HELPER
@@ -63,6 +65,12 @@ feature -- Conversion
feature -- Access feature -- Access
identifier: detachable IMMUTABLE_STRING_32
-- Optional identifier.
do
create Result.make_from_string_general (id.out)
end
id: INTEGER_64 assign set_id id: INTEGER_64 assign set_id
-- Unique id. -- Unique id.
--| Should we use NATURAL_64 instead? --| Should we use NATURAL_64 instead?

View File

@@ -92,228 +92,15 @@ feature -- Forms ...
f.extend (fset) f.extend (fset)
-- Path alias -- Path alias
populate_form_with_taxonomy (response, f, a_node) populate_form_with_taxonomy (response, f, a_node)
populate_form_with_path_alias (response, f, a_node) populate_form_with_path_alias (response, f, a_node)
end end
populate_form_with_taxonomy (response: NODE_RESPONSE; f: CMS_FORM; a_node: detachable CMS_NODE) populate_form_with_taxonomy (response: CMS_RESPONSE; f: CMS_FORM; a_content: detachable CMS_CONTENT)
local
ti: detachable WSF_FORM_TEXT_INPUT
w_set: WSF_FORM_FIELD_SET
w_select: WSF_FORM_SELECT
w_opt: WSF_FORM_SELECT_OPTION
w_cb: WSF_FORM_CHECKBOX_INPUT
w_voc_set: WSF_FORM_FIELD_SET
s: STRING_32
voc: CMS_VOCABULARY
t: detachable CMS_TERM
l_terms: detachable CMS_TERM_COLLECTION
l_has_edit_permission: BOOLEAN
do do
if if attached {CMS_TAXONOMY_API} response.api.module_api ({CMS_TAXONOMY_MODULE}) as l_taxonomy_api then
attached {CMS_TAXONOMY_API} response.api.module_api ({CMS_TAXONOMY_MODULE}) as l_taxonomy_api and then l_taxonomy_api.populate_edit_form (response, f, content_type.name, a_content)
attached l_taxonomy_api.vocabularies_for_type (content_type.name) as l_vocs and then not l_vocs.is_empty
then
l_has_edit_permission := response.has_permissions (<<"update any taxonomy", "update " + content_type.name + " taxonomy">>)
-- Handle Taxonomy fields, if any associated with `content_type'.
create w_set.make
w_set.add_css_class ("taxonomy")
l_vocs.sort
across
l_vocs as vocs_ic
loop
voc := vocs_ic.item
l_terms := Void
if a_node /= Void and then a_node.has_id then
l_terms := l_taxonomy_api.terms_of_entity (a_node.content_type, a_node.id.out, voc)
if l_terms /= Void then
l_terms.sort
end
end
create w_voc_set.make
w_set.extend (w_voc_set)
if voc.is_tags then
w_voc_set.set_legend (response.translation (voc.name, Void))
create ti.make ({STRING_32} "taxonomy_terms[" + voc.name + "]")
w_voc_set.extend (ti)
if voc.is_term_required then
ti.enable_required
end
if attached voc.description as l_desc then
ti.set_description (response.html_encoded (response.translation (l_desc, Void)))
else
ti.set_description (response.html_encoded (response.translation (voc.name, Void)))
end
ti.set_size (70)
if l_terms /= Void then
create s.make_empty
across
l_terms as ic
loop
t := ic.item
if not s.is_empty then
s.append_character (',')
s.append_character (' ')
end
if ic.item.text.has (' ') then
s.append_character ('"')
s.append (t.text)
s.append_character ('"')
else
s.append (t.text)
end
end
ti.set_text_value (s)
end
if not l_has_edit_permission then
ti.set_is_readonly (True)
end
else
l_taxonomy_api.fill_vocabularies_with_terms (voc)
if not voc.terms.is_empty then
if voc.multiple_terms_allowed then
if attached voc.description as l_desc then
w_voc_set.set_legend (response.html_encoded (l_desc))
else
w_voc_set.set_legend (response.html_encoded (voc.name))
end
across
voc as voc_terms_ic
loop
t := voc_terms_ic.item
create w_cb.make_with_value ({STRING_32} "taxonomy_terms[" + voc.name + "]", t.text)
w_voc_set.extend (w_cb)
if l_terms /= Void and then across l_terms as ic some ic.item.text.same_string (t.text) end then
w_cb.set_checked (True)
end
if not l_has_edit_permission then
w_cb.set_is_readonly (True)
end
end
else
create w_select.make ({STRING_32} "taxonomy_terms[" + voc.name + "]")
w_voc_set.extend (w_select)
if attached voc.description as l_desc then
w_select.set_description (response.html_encoded (l_desc))
else
w_select.set_description (response.html_encoded (voc.name))
end
w_voc_set.set_legend (response.html_encoded (voc.name))
across
voc as voc_terms_ic
loop
t := voc_terms_ic.item
create w_opt.make (response.html_encoded (t.text), response.html_encoded (t.text))
w_select.add_option (w_opt)
if l_terms /= Void and then across l_terms as ic some ic.item.text.same_string (t.text) end then
w_opt.set_is_selected (True)
end
end
if not l_has_edit_permission then
w_select.set_is_readonly (True)
end
end
end
end
end
f.submit_actions.extend (agent taxonomy_submit_action (response, l_taxonomy_api, l_vocs, a_node, ?))
if
attached f.fields_by_name ("title") as l_title_fields and then
attached l_title_fields.first as l_title_field
then
f.insert_after (w_set, l_title_field)
else
f.extend (w_set)
end
end
end
taxonomy_submit_action (a_response: CMS_RESPONSE; a_taxonomy_api: CMS_TAXONOMY_API; a_vocs: CMS_VOCABULARY_COLLECTION; a_node: detachable CMS_NODE fd: WSF_FORM_DATA)
require
vocs_not_empty: not a_vocs.is_empty
local
l_voc_name: READABLE_STRING_32
l_terms_to_remove: ARRAYED_LIST [CMS_TERM]
l_new_terms: LIST [READABLE_STRING_32]
l_text: READABLE_STRING_GENERAL
l_found: BOOLEAN
t: detachable CMS_TERM
do
if
a_node /= Void and then a_node.has_id and then
attached fd.table_item ("taxonomy_terms") as fd_terms
then
across
fd_terms.values as ic
loop
if attached {WSF_STRING} ic.item as l_string then
l_voc_name := ic.key
l_new_terms := a_taxonomy_api.splitted_string (l_string.value, ',')
if attached a_vocs.item_by_name (l_voc_name) as voc then
if a_response.has_permissions (<<{STRING_32} "update any taxonomy", {STRING_32} "update " + content_type.name + " taxonomy">>) then
create l_terms_to_remove.make (0)
if attached a_taxonomy_api.terms_of_entity (content_type.name, a_node.id.out, voc) as l_existing_terms then
across
l_existing_terms as t_ic
loop
l_text := t_ic.item.text
from
l_found := False
l_new_terms.start
until
l_new_terms.after
loop
if l_new_terms.item.same_string_general (l_text) then
-- Already associated with term `t_ic.text'.
l_found := True
l_new_terms.remove
else
l_new_terms.forth
end
end
if not l_found then
-- Remove term
l_terms_to_remove.force (t_ic.item)
end
end
across
l_terms_to_remove as t_ic
loop
a_taxonomy_api.unassociate_term_from_entity (t_ic.item, content_type.name, a_node.id.out)
end
end
across
l_new_terms as t_ic
loop
t := a_taxonomy_api.term_by_text (t_ic.item, voc)
if
t = Void and voc.is_tags
then
-- Create new term!
create t.make (t_ic.item)
a_taxonomy_api.save_term (t, voc)
if a_taxonomy_api.has_error then
t := Void
end
end
if t /= Void then
a_taxonomy_api.associate_term_with_entity (t, content_type.name, a_node.id.out)
end
end
end
end
end
end
end end
end end
@@ -547,30 +334,9 @@ feature -- Output
if if
a_response /= Void and then a_response /= Void and then
attached {CMS_TAXONOMY_API} cms_api.module_api ({CMS_TAXONOMY_MODULE}) as l_taxonomy_api and then attached {CMS_TAXONOMY_API} cms_api.module_api ({CMS_TAXONOMY_MODULE}) as l_taxonomy_api
attached l_taxonomy_api.vocabularies_for_type (content_type.name) as vocs and then not vocs.is_empty
then then
vocs.sort l_taxonomy_api.append_taxonomy_to_xhtml (a_node, a_response, a_output)
across
vocs as ic
loop
if
attached l_taxonomy_api.terms_of_entity (content_type.name, a_node.id.out, ic.item) as l_terms and then
not l_terms.is_empty
then
a_output.append ("<ul class=%"taxonomy term-" + ic.item.id.out + "%">")
a_output.append (l_node_api.html_encoded (ic.item.name))
a_output.append (": ")
across
l_terms as t_ic
loop
a_output.append ("<li>")
a_response.append_link_to_html (t_ic.item.text, "taxonomy/term/" + t_ic.item.id.out, Void, a_output)
a_output.append ("</li>")
end
a_output.append ("</ul>%N")
end
end
end end
-- We don't show the summary on the detail page, since its just a short view of the full content. Otherwise we would write the same thing twice. -- We don't show the summary on the detail page, since its just a short view of the full content. Otherwise we would write the same thing twice.

View File

@@ -7,30 +7,10 @@ class
inherit inherit
NODE_RESPONSE NODE_RESPONSE
redefine
make,
initialize
end
create create
make make
feature {NONE} -- Initialization
make (req: WSF_REQUEST; res: WSF_RESPONSE; a_api: like api; a_node_api: like node_api)
do
create {WSF_NULL_THEME} wsf_theme.make
Precursor (req, res, a_api, a_node_api)
end
initialize
do
Precursor
create {CMS_TO_WSF_THEME} wsf_theme.make (Current, theme)
end
wsf_theme: WSF_THEME
feature -- Execution feature -- Execution
process process

View File

@@ -8,30 +8,10 @@ class
inherit inherit
NODE_RESPONSE NODE_RESPONSE
redefine
make,
initialize
end
create create
make make
feature {NONE} -- Initialization
make (req: WSF_REQUEST; res: WSF_RESPONSE; a_api: like api; a_node_api: like node_api)
do
create {WSF_NULL_THEME} wsf_theme.make
Precursor (req, res, a_api, a_node_api)
end
initialize
do
Precursor
create {CMS_TO_WSF_THEME} wsf_theme.make (Current, theme)
end
wsf_theme: WSF_THEME
feature -- Access feature -- Access
node: detachable CMS_NODE node: detachable CMS_NODE

View File

@@ -99,7 +99,7 @@ feature {CMS_API} -- Module management
l_sql_storage.sql_execute_file_script (api.module_resource_location (Current, (create {PATH}.make_from_string ("scripts")).extended ("oauth2_consumers.sql")), Void) l_sql_storage.sql_execute_file_script (api.module_resource_location (Current, (create {PATH}.make_from_string ("scripts")).extended ("oauth2_consumers.sql")), Void)
if l_sql_storage.has_error then if l_sql_storage.has_error then
api.logger.put_error ("Could not initialize database for blog module", generating_type) api.logger.put_error ("Could not initialize database for oauth_20 module", generating_type)
end end
-- TODO workaround. -- TODO workaround.
l_sql_storage.sql_execute_file_script (api.module_resource_location (Current, (create {PATH}.make_from_string ("scripts")).extended ("oauth2_consumers_initialize.sql")), Void) l_sql_storage.sql_execute_file_script (api.module_resource_location (Current, (create {PATH}.make_from_string ("scripts")).extended ("oauth2_consumers_initialize.sql")), Void)
@@ -108,7 +108,7 @@ feature {CMS_API} -- Module management
-- TODO workaround, until we have an admin module -- TODO workaround, until we have an admin module
l_sql_storage.sql_query ("SELECT name FROM oauth2_consumers;", Void) l_sql_storage.sql_query ("SELECT name FROM oauth2_consumers;", Void)
if l_sql_storage.has_error then if l_sql_storage.has_error then
api.logger.put_error ("Could not initialize database for differnent consumerns", generating_type) api.logger.put_error ("Could not initialize database for differnent consumers", generating_type)
else else
from from
l_sql_storage.sql_start l_sql_storage.sql_start
@@ -227,7 +227,10 @@ feature -- Hooks
until until
lnk2 /= Void lnk2 /= Void
loop loop
if ic.item.location.same_string ("account/roc-logout") then if
ic.item.location.same_string ("account/roc-logout") or else
ic.item.location.same_string ("basic_auth_logoff")
then
lnk2 := ic.item lnk2 := ic.item
end end
end end

View File

@@ -203,7 +203,10 @@ feature -- Hooks
until until
lnk2 /= Void lnk2 /= Void
loop loop
if ic.item.location.same_string ("account/roc-logout") then if
ic.item.location.same_string ("account/roc-logout") or else
ic.item.location.same_string ("basic_auth_logoff")
then
lnk2 := ic.item lnk2 := ic.item
end end
end end

View File

@@ -197,7 +197,7 @@ feature {NONE} -- User OpenID
Sql_insert_openid: STRING = "INSERT INTO openid_items (uid, identity, created) VALUES (:uid, :identity, :utc_date);" Sql_insert_openid: STRING = "INSERT INTO openid_items (uid, identity, created) VALUES (:uid, :identity, :utc_date);"
Sql_openid_consumers: STRING = "SELECT name FROM openid_consumers"; Sql_openid_consumers: STRING = "SELECT name FROM openid_consumers;"
feature {NONE} -- Consumer feature {NONE} -- Consumer

View File

@@ -284,7 +284,7 @@ feature -- Handler
create l_submit.make_with_text ("op", "Filter") create l_submit.make_with_text ("op", "Filter")
l_form.extend (l_submit) l_form.extend (l_submit)
l_form.extend_html_text ("<br/>") l_form.extend_html_text ("<br/>")
l_form.append_to_html (create {CMS_TO_WSF_THEME}.make (r, r.theme), l_content) l_form.append_to_html (r.wsf_theme, l_content)
end end
l_changes.reverse_sort l_changes.reverse_sort

View File

@@ -0,0 +1,63 @@
note
description: "API to manage CMS User session authentication"
date: "$Date$"
revision: "$Revision$"
class
CMS_SESSION_API
inherit
CMS_MODULE_API
REFACTORING_HELPER
create {CMS_SESSION_AUTH_MODULE}
make_with_storage
feature {NONE} -- Initialization
make_with_storage (a_api: CMS_API; a_session_auth_storage: CMS_SESSION_AUTH_STORAGE_I)
-- Create an object with api `a_api' and storage `a_session_auth_storage'.
do
session_auth_storage := a_session_auth_storage
make (a_api)
ensure
session_auth_storage_set: session_auth_storage = a_session_auth_storage
end
feature {CMS_MODULE} -- Access: User session storage.
session_auth_storage: CMS_SESSION_AUTH_STORAGE_I
-- storage interface.
feature -- Access
user_by_session_token (a_token: READABLE_STRING_32): detachable CMS_USER
-- Retrieve user by token `a_token', if any.
do
Result := session_auth_storage.user_by_session_token (a_token)
end
has_user_token (a_user: CMS_USER): BOOLEAN
-- Has the user `a_user' and associated session token?
do
Result := session_auth_storage.has_user_token (a_user)
end
feature -- Change User session
new_user_session_auth (a_token: READABLE_STRING_GENERAL; a_user: CMS_USER;)
-- New user session for user `a_user' with token `a_token'.
do
session_auth_storage.new_user_session_auth (a_token, a_user)
end
update_user_session_auth (a_token: READABLE_STRING_GENERAL; a_user: CMS_USER )
-- Update user session for user `a_user' with token `a_token'.
do
session_auth_storage.update_user_session_auth (a_token, a_user)
end
end

View File

@@ -0,0 +1,28 @@
<?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="session_auth" uuid="8A43B6DD-6B39-472C-9A96-978414CBF1E3" library_target="session_auth">
<target name="session_auth">
<root all_classes="true"/>
<file_rule>
<exclude>/EIFGENs$</exclude>
<exclude>/CVS$</exclude>
<exclude>/.svn$</exclude>
</file_rule>
<option warning="true" full_class_checking="false" is_attached_by_default="true" is_obsolete_routine_type="true" void_safety="all" syntax="transitional">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
<library name="cms" location="..\..\cms-safe.ecf"/>
<library name="cms_app_env" location="..\..\library\app_env\app_env-safe.ecf" readonly="false"/>
<library name="cms_auth_module" location="..\..\modules\auth\auth-safe.ecf" readonly="false"/>
<library name="cms_model" location="..\..\library\model\cms_model-safe.ecf" readonly="false"/>
<library name="crypto" location="$ISE_LIBRARY\unstable\library\text\encryption\crypto\crypto-safe.ecf"/>
<library name="encoder" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\text\encoder\encoder-safe.ecf"/>
<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" readonly="false"/>
<library name="time" location="$ISE_LIBRARY\library\time\time-safe.ecf"/>
<library name="wsf" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\wsf-safe.ecf"/>
<library name="wsf_extension" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\wsf_extension-safe.ecf" readonly="false"/>
<cluster name="src" location=".\" recursive="true"/>
</target>
</system>

View File

@@ -0,0 +1,349 @@
note
description: "[
This module allows the use Session Based Authentication using Cookies to restrict access
by looking up users in the given providers.
]"
date: "$Date$"
revision: "$Revision$"
class
CMS_SESSION_AUTH_MODULE
inherit
CMS_MODULE
rename
module_api as user_session_api
redefine
filters,
setup_hooks,
initialize,
install,
user_session_api
end
CMS_HOOK_AUTO_REGISTER
CMS_HOOK_BLOCK
CMS_HOOK_MENU_SYSTEM_ALTER
CMS_HOOK_VALUE_TABLE_ALTER
SHARED_LOGGER
CMS_REQUEST_UTIL
create
make
feature {NONE} -- Initialization
make
do
version := "1.0"
description := "Service to manage cookie based authentication"
package := "authentication"
add_dependency ({CMS_AUTHENTICATION_MODULE})
end
feature -- Access
name: STRING = "session_auth"
feature {CMS_API} -- Module Initialization
initialize (a_api: CMS_API)
-- <Precursor>
local
l_session_auth_api: like user_session_api
l_user_auth_storage: CMS_SESSION_AUTH_STORAGE_I
do
Precursor (a_api)
-- Storage initialization
if attached a_api.storage.as_sql_storage as l_storage_sql then
create {CMS_SESSION_AUTH_STORAGE_SQL} l_user_auth_storage.make (l_storage_sql)
else
-- FIXME: in case of NULL storage, should Current be disabled?
create {CMS_SESSION_AUTH_STORAGE_NULL} l_user_auth_storage
end
-- API initialization
create l_session_auth_api.make_with_storage (a_api, l_user_auth_storage)
user_session_api := l_session_auth_api
ensure then
session_auth_api_set: user_session_api /= Void
end
feature {CMS_API} -- Module management
install (api: CMS_API)
do
-- Schema
if attached api.storage.as_sql_storage as l_sql_storage then
if not l_sql_storage.sql_table_exists ("session_auth") then
--| Schema
l_sql_storage.sql_execute_file_script (api.module_resource_location (Current, (create {PATH}.make_from_string ("scripts")).extended ("session_auth_table.sql")), Void)
if l_sql_storage.has_error then
api.logger.put_error ("Could not initialize database for session auth module", generating_type)
end
end
l_sql_storage.sql_finalize
Precursor {CMS_MODULE}(api)
end
end
feature {CMS_API} -- Access: API
user_session_api: detachable CMS_SESSION_API
-- <Precursor>
feature -- Access: router
setup_router (a_router: WSF_ROUTER; a_api: CMS_API)
-- <Precursor>
do
a_router.handle ("/account/roc-session-login", create {WSF_URI_AGENT_HANDLER}.make (agent handle_login(a_api, ?, ?)), a_router.methods_head_get)
a_router.handle ("/account/roc-session-logout", create {WSF_URI_AGENT_HANDLER}.make (agent handle_logout (a_api, ?, ?)), a_router.methods_get_post)
a_router.handle ("/account/login-with-session", create {WSF_URI_TEMPLATE_AGENT_HANDLER}.make (agent handle_login_with_session (a_api,user_session_api, ?, ?)), a_router.methods_get_post)
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)
if attached user_session_api as l_session_api then
Result.extend (create {CMS_SESSION_AUTH_FILTER}.make (a_api, l_session_api))
end
end
feature {NONE} -- Implementation: routes
handle_login (api: CMS_API; req: WSF_REQUEST; res: WSF_RESPONSE)
local
r: CMS_RESPONSE
do
create {GENERIC_VIEW_CMS_RESPONSE} r.make (req, res, api)
r.execute
end
handle_logout (api: CMS_API; req: WSF_REQUEST; res: WSF_RESPONSE)
local
r: CMS_RESPONSE
l_cookie: WSF_COOKIE
do
if
attached {WSF_STRING} req.cookie ({CMS_SESSION_CONSTANTS}.session_auth_token) as l_cookie_token and then
attached {CMS_USER} current_user (req) as l_user
then
-- Logout Session
create l_cookie.make ({CMS_SESSION_CONSTANTS}.session_auth_token, l_cookie_token.value)
l_cookie.set_path ("/")
l_cookie.set_max_age (-1)
res.add_cookie (l_cookie)
unset_current_user (req)
create {GENERIC_VIEW_CMS_RESPONSE} r.make (req, res, api)
r.set_status_code ({HTTP_CONSTANTS}.found)
r.set_redirection (req.absolute_script_url (""))
r.execute
else
fixme (generator + ": missing else implementation in handle_logout!")
end
end
handle_login_with_session (api: CMS_API; a_session_api: detachable CMS_SESSION_API; req: WSF_REQUEST; res: WSF_RESPONSE)
local
r: CMS_RESPONSE
l_token: STRING
l_cookie: WSF_COOKIE
do
if
attached a_session_api as l_session_api and then
attached {WSF_STRING} req.form_parameter ("username") as l_username and then
attached {WSF_STRING} req.form_parameter ("password") as l_password and then
api.user_api.is_valid_credential (l_username.value, l_password.value) and then
attached api.user_api.user_by_name (l_username.value) as l_user
then
l_token := generate_token
if
a_session_api.has_user_token (l_user)
then
l_session_api.update_user_session_auth (l_token, l_user)
else
l_session_api.new_user_session_auth (l_token, l_user)
end
create l_cookie.make ({CMS_SESSION_CONSTANTS}.session_auth_token, l_token)
l_cookie.set_max_age ({CMS_SESSION_CONSTANTS}.session_max_age)
l_cookie.set_path ("/")
res.add_cookie (l_cookie)
set_current_user (req, l_user)
create {GENERIC_VIEW_CMS_RESPONSE} r.make (req, res, api)
r.set_redirection (req.absolute_script_url (""))
r.execute
else
create {BAD_REQUEST_ERROR_CMS_RESPONSE} r.make (req, res, api)
if attached template_block ("login", r) as l_tpl_block then
if attached {WSF_STRING} req.form_parameter ("username") as l_username then
l_tpl_block.set_value (l_username.value, "username")
end
l_tpl_block.set_value ("Wrong: Username or password ", "error")
r.add_block (l_tpl_block, "content")
end
r.execute
end
end
feature -- Hooks configuration
setup_hooks (a_hooks: CMS_HOOK_CORE_MANAGER)
-- Module hooks configuration.
do
auto_subscribe_to_hooks (a_hooks)
a_hooks.subscribe_to_block_hook (Current)
a_hooks.subscribe_to_value_table_alter_hook (Current)
end
feature -- Hooks
value_table_alter (a_value: CMS_VALUE_TABLE; a_response: CMS_RESPONSE)
-- <Precursor>
do
if
attached a_response.user as u and then
attached {WSF_STRING} a_response.request.cookie ({CMS_SESSION_CONSTANTS}.session_auth_token)
then
a_value.force ("account/roc-session-logout", "auth_login_strategy")
end
end
menu_system_alter (a_menu_system: CMS_MENU_SYSTEM; a_response: CMS_RESPONSE)
-- Hook execution on collection of menu contained by `a_menu_system'
-- for related response `a_response'.
local
lnk: CMS_LOCAL_LINK
lnk2: detachable CMS_LINK
do
if
attached a_response.user as u and then
attached {WSF_STRING} a_response.request.cookie ({CMS_SESSION_CONSTANTS}.session_auth_token)
then
across
a_menu_system.primary_menu.items as ic
until
lnk2 /= Void
loop
if ic.item.location.same_string ("account/roc-logout") or else ic.item.location.same_string ("basic_auth_logoff") then
lnk2 := ic.item
end
end
if lnk2 /= Void then
a_menu_system.primary_menu.remove (lnk2)
end
create lnk.make ("Logout", "account/roc-session-logout" )
a_menu_system.primary_menu.extend (lnk)
else
if a_response.location.starts_with ("account/") then
create lnk.make ("Session", "account/roc-session-login")
a_response.add_to_primary_tabs (lnk)
end
end
end
block_list: ITERABLE [like {CMS_BLOCK}.name]
local
l_string: STRING
do
Result := <<"login">>
debug ("roc")
create l_string.make_empty
across
Result as ic
loop
l_string.append (ic.item)
l_string.append_character (' ')
end
write_debug_log (generator + ".block_list:" + l_string )
end
end
get_block_view (a_block_id: READABLE_STRING_8; a_response: CMS_RESPONSE)
do
if
a_block_id.is_case_insensitive_equal_general ("login") and then
a_response.location.starts_with ("account/roc-session-login")
then
get_block_view_login (a_block_id, a_response)
end
end
feature {NONE} -- Helpers
template_block (a_block_id: READABLE_STRING_8; a_response: CMS_RESPONSE): detachable CMS_SMARTY_TEMPLATE_BLOCK
-- Smarty content block for `a_block_id'
local
p: detachable PATH
do
create p.make_from_string ("templates")
p := p.extended ("block_").appended (a_block_id).appended_with_extension ("tpl")
p := a_response.api.module_theme_resource_location (Current, p)
if p /= Void then
if attached p.entry as e then
create Result.make (a_block_id, Void, p.parent, e)
else
create Result.make (a_block_id, Void, p.parent, p)
end
end
end
feature {NONE} -- Block views
get_block_view_login (a_block_id: READABLE_STRING_8; a_response: CMS_RESPONSE)
local
vals: CMS_VALUE_TABLE
do
if attached template_block (a_block_id, a_response) as l_tpl_block then
create vals.make (1)
-- add the variable to the block
value_table_alter (vals, a_response)
across
vals as ic
loop
l_tpl_block.set_value (ic.item, ic.key)
end
a_response.add_block (l_tpl_block, "content")
else
debug ("cms")
a_response.add_warning_message ("Error with block [" + a_block_id + "]")
end
end
end
generate_token: STRING
-- Generate token to use in a Session.
local
l_token: STRING
l_security: CMS_TOKEN_GENERATOR
l_encode: URL_ENCODER
do
create l_security
l_token := l_security.token
create l_encode
from until l_token.same_string (l_encode.encoded_string (l_token)) loop
-- Loop ensure that we have a security token that does not contain characters that need encoding.
-- We cannot simply to an encode-decode because the email sent to the user will contain an encoded token
-- but the user will need to use an unencoded token if activation has to be done manually.
l_token := l_security.token
end
Result := l_token
end
end

View File

@@ -0,0 +1,19 @@
note
description: "Summary description for {CMS_SESSION_CONSTANTS}."
date: "$Date$"
revision: "$Revision$"
class
CMS_SESSION_CONSTANTS
feature
session_auth_token: STRING = "EWF_ROC_SESSION_AUTH_TOKEN_"
-- Name of Cookie used to keep the session info.
-- TODO add a config file to be able to customize this value via coniguration file.
session_max_age: INTEGER = 86400
-- Value of the Max-Age, before the cookie expires.
-- TODO add a config file to be able to customize this value via coniguration file.
end

View File

@@ -0,0 +1,55 @@
note
description: "[
Processes a HTTP request's checking Session cookies, putting the result into the execution variable user.
]"
date: "$Date: 2015-02-13 13:08:13 +0100 (ven., 13 févr. 2015) $"
revision: "$Revision: 96616 $"
class
CMS_SESSION_AUTH_FILTER
inherit
WSF_URI_TEMPLATE_HANDLER
CMS_HANDLER
rename
make as make_handler
end
WSF_FILTER
create
make
feature {NONE} -- Initialization
make (a_api: CMS_API; a_session_oauth_api: CMS_SESSION_API)
do
make_handler (a_api)
session_oauth_api := a_session_oauth_api
end
session_oauth_api: CMS_SESSION_API
feature -- Basic operations
execute (req: WSF_REQUEST; res: WSF_RESPONSE)
-- Execute the filter.
do
api.logger.put_debug (generator + ".execute ", Void)
-- A valid user
if
attached {WSF_STRING} req.cookie ({CMS_SESSION_CONSTANTS}.session_auth_token) as l_roc_auth_session_token
then
if attached session_oauth_api.user_by_session_token (l_roc_auth_session_token.value) as l_user then
set_current_user (req, l_user)
else
api.logger.put_error (generator + ".execute login_valid failed for: " + l_roc_auth_session_token.value , Void)
end
else
api.logger.put_debug (generator + ".execute without authentication", Void)
end
execute_next (req, res)
end
end

View File

@@ -0,0 +1,46 @@
note
description: "[
API to handle OAUTH storage
]"
date: "$Date$"
revision: "$Revision$"
deferred class
CMS_SESSION_AUTH_STORAGE_I
inherit
SHARED_LOGGER
feature -- Error Handling
error_handler: ERROR_HANDLER
-- Error handler.
deferred
end
feature -- Access: Users
user_by_session_token (a_token: READABLE_STRING_32): detachable CMS_USER
-- Retrieve user by token `a_token', if any.
deferred
end
has_user_token (a_user: CMS_USER): BOOLEAN
-- Has the user `a_user' and associated session token?
deferred
end
feature -- Change User session
new_user_session_auth (a_token: READABLE_STRING_GENERAL; a_user: CMS_USER;)
-- New user session for user `a_user' with token `a_token'.
deferred
end
update_user_session_auth (a_token: READABLE_STRING_GENERAL; a_user: CMS_USER )
-- Update user session for user `a_user' with token `a_token'.
deferred
end
end

View File

@@ -0,0 +1,47 @@
note
description: "Summary description for {CMS_SESSION_AUTH_STORAGE_NULL}."
date: "$Date$"
revision: "$Revision$"
class
CMS_SESSION_AUTH_STORAGE_NULL
inherit
CMS_SESSION_AUTH_STORAGE_I
feature -- Error handler
error_handler: ERROR_HANDLER
-- Error handler.
do
create Result.make
end
feature -- Access
user_by_session_token (a_token: READABLE_STRING_32): detachable CMS_USER
-- Retrieve user by token `a_token', if any.
do
end
has_user_token (a_user: CMS_USER): BOOLEAN
-- Has the user `a_user' and associated session token?
do
end
feature -- Change User session
new_user_session_auth (a_token: READABLE_STRING_GENERAL; a_user: CMS_USER;)
-- New user session for user `a_user' with token `a_token'.
do
end
update_user_session_auth (a_token: READABLE_STRING_GENERAL; a_user: CMS_USER )
-- Update user session for user `a_user' with token `a_token'.
do
end
end

View File

@@ -0,0 +1,155 @@
note
description: "Summary description for {CMS_SESSION_AUTH_STORAGE_SQL}."
date: "$Date$"
revision: "$Revision$"
class
CMS_SESSION_AUTH_STORAGE_SQL
inherit
CMS_SESSION_AUTH_STORAGE_I
CMS_PROXY_STORAGE_SQL
CMS_SESSION_AUTH_STORAGE_I
CMS_STORAGE_SQL_I
REFACTORING_HELPER
create
make
feature -- Access User
user_by_session_token (a_token: READABLE_STRING_32): detachable CMS_USER
-- Retrieve user by token `a_token', if any.
local
l_parameters: STRING_TABLE [detachable ANY]
do
error_handler.reset
write_information_log (generator + ".user_by_session_token")
create l_parameters.make (1)
l_parameters.put (a_token, "token")
sql_query (Select_user_by_token, l_parameters)
if not has_error and not sql_after then
Result := fetch_user
sql_forth
if not sql_after then
check
no_more_than_one: False
end
Result := Void
end
end
sql_finalize
end
has_user_token (a_user: CMS_USER): BOOLEAN
-- Has the user `a_user' and associated session token?
local
l_parameters: STRING_TABLE [detachable ANY]
do
error_handler.reset
write_information_log (generator + ".has_user_token")
create l_parameters.make (1)
l_parameters.put (a_user.id, "uid")
sql_query (Select_user_token, l_parameters)
if not has_error and not sql_after then
if sql_read_integer_64 (1) = 1 then
Result := True
else
Result := False
end
end
sql_finalize
end
feature -- Change User token
new_user_session_auth (a_token: READABLE_STRING_GENERAL; a_user: CMS_USER;)
-- <Precursor>.
local
l_parameters: STRING_TABLE [detachable ANY]
do
error_handler.reset
write_information_log (generator + ".new_user_session")
create l_parameters.make (3)
l_parameters.put (a_user.id, "uid")
l_parameters.put (a_token, "token")
l_parameters.put (create {DATE_TIME}.make_now_utc, "utc_date")
sql_begin_transaction
sql_insert (sql_insert_session_auth, l_parameters)
sql_commit_transaction
sql_finalize
end
update_user_session_auth (a_token: READABLE_STRING_GENERAL; a_user: CMS_USER)
-- <Precursor>
local
l_parameters: STRING_TABLE [detachable ANY]
do
error_handler.reset
write_information_log (generator + ".update_user_session_auth")
create l_parameters.make (3)
l_parameters.put (a_user.id, "uid")
l_parameters.put (a_token, "token")
l_parameters.put (create {DATE_TIME}.make_now_utc, "utc_date")
sql_begin_transaction
sql_modify (sql_update_session_auth, l_parameters)
sql_commit_transaction
sql_finalize
end
feature {NONE} -- Implementation
fetch_user: detachable CMS_USER
local
l_id: INTEGER_64
l_name: detachable READABLE_STRING_32
do
if attached sql_read_integer_64 (1) as i then
l_id := i
end
if attached sql_read_string_32 (2) as s and then not s.is_whitespace then
l_name := s
end
if l_name /= Void then
create Result.make (l_name)
if l_id > 0 then
Result.set_id (l_id)
end
elseif l_id > 0 then
create Result.make_with_id (l_id)
end
if Result /= Void then
if attached sql_read_string (3) as l_password then
-- FIXME: should we return the password here ???
Result.set_hashed_password (l_password)
end
if attached sql_read_string (5) as l_email then
Result.set_email (l_email)
end
if attached sql_read_integer_32 (6) as l_status then
Result.set_status (l_status)
end
else
check
expected_valid_user: False
end
end
end
feature {NONE} -- SQL statements
Select_user_by_token: STRING = "SELECT u.* FROM users as u JOIN session_auth as og ON og.uid = u.uid and og.access_token = :token;"
--| FIXME: replace the u.* by a list of field names, to avoid breaking `featch_user' if two fieds are swiped.
Sql_insert_session_auth: STRING = "INSERT INTO session_auth (uid, access_token, created) VALUES (:uid, :token, :utc_date);"
Sql_update_session_auth: STRING = "UPDATE session_auth SET access_token = :token, created = :utc_date WHERE uid =:uid;"
Select_user_token: STRING = "SELECT COUNT(*) FROM session_auth where uid = :uid;"
end

View File

@@ -0,0 +1,153 @@
note
description: "Provides security routine helpers"
date: "$Date$"
revision: "$Revision$"
class
CMS_TOKEN_GENERATOR
inherit
REFACTORING_HELPER
feature -- Access
token: STRING
-- Cryptographic random base 64 string.
do
Result := salt_with_size (16)
-- Remove trailing equal sign
Result.keep_head (Result.count - 2)
end
salt: STRING
-- Cryptographic random number of 16 bytes.
do
Result := salt_with_size (16)
end
password: STRING
-- Cryptographic random password of 10 bytes.
do
Result := salt_with_size (10)
-- Remove trailing equal signs
Result.keep_head (Result.count - 2)
end
password_hash (a_password, a_salt: STRING): STRING
-- Password hash based on password `a_password' and salt value `a_salt'.
do
Result := sha1_string (a_password + a_salt )
end
feature {NONE} -- Implementation
salt_with_size (a_val: INTEGER): STRING
-- Return a salt with size `a_val'.
local
l_salt: SALT_XOR_SHIFT_64_GENERATOR
l_array: ARRAY [INTEGER_8]
i: INTEGER
do
create l_salt.make (a_val)
create l_array.make_empty
i := 1
across
l_salt.new_sequence as c
loop
l_array.force (c.item.as_integer_8, i)
i := i + 1
end
Result := base_64 (l_array)
end
sha1_string (a_str: STRING): STRING
-- SHA1 diggest of `a_str'.
do
sha1.update_from_string (a_str)
Result := sha1.digest_as_string
sha1.reset
end
sha1: SHA1
-- Create a SHA1 object.
do
create Result.make
end
feature -- Encoding
base_64 (bytes: SPECIAL [INTEGER_8]): STRING_8
-- Encodes a byte array into a STRING doing base64 encoding.
local
l_output: SPECIAL [INTEGER_8]
l_remaining: INTEGER
i, ptr: INTEGER
char: CHARACTER
do
to_implement ("Check existing code to do that!!!.")
create l_output.make_filled (0, ((bytes.count + 2) // 3) * 4)
l_remaining := bytes.count
from
i := 0
ptr := 0
until
l_remaining <= 3
loop
l_output [ptr] := encode_value (bytes [i] |>> 2)
ptr := ptr + 1
l_output [ptr] := encode_value (((bytes [i] & 0x3) |<< 4) | ((bytes [i + 1] |>> 4) & 0xF))
ptr := ptr + 1
l_output [ptr] := encode_value (((bytes [i + 1] & 0xF) |<< 2) | ((bytes [i + 2] |>> 6) & 0x3))
ptr := ptr + 1
l_output [ptr] := encode_value (bytes [i + 2] & 0x3F)
ptr := ptr + 1
l_remaining := l_remaining - 3
i := i + 3
end
-- encode when exactly 1 element (left) to encode
char := '='
if l_remaining = 1 then
l_output [ptr] := encode_value (bytes [i] |>> 2)
ptr := ptr + 1
l_output [ptr] := encode_value (((bytes [i]) & 0x3) |<< 4)
ptr := ptr + 1
l_output [ptr] := char.code.as_integer_8
ptr := ptr + 1
l_output [ptr] := char.code.as_integer_8
ptr := ptr + 1
end
-- encode when exactly 2 elements (left) to encode
if l_remaining = 2 then
l_output [ptr] := encode_value (bytes [i] |>> 2)
ptr := ptr + 1
l_output [ptr] := encode_value (((bytes [i] & 0x3) |<< 4) | ((bytes [i + 1] |>> 4) & 0xF));
ptr := ptr + 1
l_output [ptr] := encode_value ((bytes [i + 1] & 0xF) |<< 2);
ptr := ptr + 1
l_output [ptr] := char.code.as_integer_8
ptr := ptr + 1
end
create Result.make_empty
across
l_output as elem
loop
Result.append_character (elem.item.to_character_8)
end
end
base64_map: SPECIAL [CHARACTER_8]
-- Table for Base64 encoding.
once
Result := ("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/").area
end
encode_value (i: INTEGER_8): INTEGER_8
-- Encode `i'.
do
Result := base64_map [i & 0x3F].code.as_integer_8
end
end

View File

@@ -0,0 +1,11 @@
CREATE TABLE session_auth (
`uid` INTEGER PRIMARY KEY NOT NULL CHECK(`uid`>=0),
`access_token` TEXT NOT NULL,
`created` DATETIME NOT NULL,
CONSTRAINT `uid`
UNIQUE(`uid`),
CONSTRAINT `access_token`
UNIQUE(`access_token`)
);

View File

@@ -0,0 +1,37 @@
<div class="primary-tabs">
{unless isset="$user"}
<h3>Login or <a href="{$site_url/}account/roc-register">Register</a></h3>
<div>
<div>
<form name="cms_session_auth" action="{$site_url/}account/login-with-session" method="POST">
<div>
<input type="text" name="username" id="username" required value="{$username/}">
<label>Username</label>
</div>
<div>
<input type="password" name="password" id="password" required >
<label>Password</label>
</div>
<button type="submit">Login</button>
</form>
</div>
</div>
<div>
<div>
<p>
<a href="{$site_url/}account/new-password">Forgot password?</a>
</p>
</div>
</div>
{/unless}
{if isset=$error}
<div>
<div>
<p>
<strong>{$error/}
</p>
</div>
</div>
{/if}
</div>

View File

@@ -52,7 +52,7 @@ feature -- Access node
Result := taxonomy_storage.vocabularies (a_limit, a_offset) Result := taxonomy_storage.vocabularies (a_limit, a_offset)
end end
vocabulary (a_id: INTEGER): detachable CMS_VOCABULARY vocabulary (a_id: INTEGER_64): detachable CMS_VOCABULARY
-- Vocabulary associated with id `a_id'. -- Vocabulary associated with id `a_id'.
require require
valid_id: a_id > 0 valid_id: a_id > 0
@@ -66,6 +66,27 @@ feature -- Access node
Result := taxonomy_storage.vocabularies_for_type (a_type_name) Result := taxonomy_storage.vocabularies_for_type (a_type_name)
end end
types_associated_with_vocabulary (a_vocab: CMS_VOCABULARY): detachable LIST [READABLE_STRING_32]
-- Type names associated with `a_vocab'.
do
Result := taxonomy_storage.types_associated_with_vocabulary (a_vocab)
end
vocabularies_for_term (a_term: CMS_TERM): detachable CMS_VOCABULARY_COLLECTION
-- Vocabularies including `a_term'.
do
Result := taxonomy_storage.vocabularies_for_term (a_term)
end
is_term_inside_vocabulary (a_term: CMS_TERM; a_vocab: CMS_VOCABULARY): BOOLEAN
-- Is `a_term' inside `a_vocab' ?
require
valid_term: a_term.has_id
valid_vocabulary: a_vocab.has_id
do
Result := taxonomy_storage.is_term_inside_vocabulary (a_term, a_vocab)
end
fill_vocabularies_with_terms (a_vocab: CMS_VOCABULARY) fill_vocabularies_with_terms (a_vocab: CMS_VOCABULARY)
-- Fill `a_vocab' with associated terms. -- Fill `a_vocab' with associated terms.
do do
@@ -88,6 +109,17 @@ feature -- Access node
Result := taxonomy_storage.term_count_from_vocabulary (a_vocab) Result := taxonomy_storage.term_count_from_vocabulary (a_vocab)
end end
terms_of_content (a_content: CMS_CONTENT; a_vocabulary: detachable CMS_VOCABULARY): detachable CMS_TERM_COLLECTION
-- Terms related to `a_content', and if `a_vocabulary' is set
-- constrain to be part of `a_vocabulary'.
require
content_with_identifier: a_content.has_identifier
do
if attached a_content.identifier as l_id then
Result := terms_of_entity (a_content.content_type, l_id, a_vocabulary)
end
end
terms_of_entity (a_type_name: READABLE_STRING_GENERAL; a_entity: READABLE_STRING_GENERAL; a_vocabulary: detachable CMS_VOCABULARY): detachable CMS_TERM_COLLECTION terms_of_entity (a_type_name: READABLE_STRING_GENERAL; a_entity: READABLE_STRING_GENERAL; a_vocabulary: detachable CMS_VOCABULARY): detachable CMS_TERM_COLLECTION
-- Terms related to `(a_type_name,a_entity)', and if `a_vocabulary' is set -- Terms related to `(a_type_name,a_entity)', and if `a_vocabulary' is set
-- constrain to be part of `a_vocabulary'. -- constrain to be part of `a_vocabulary'.
@@ -125,19 +157,63 @@ feature -- Access node
feature -- Write feature -- Write
save_vocabulary (a_voc: CMS_VOCABULARY) save_vocabulary (a_voc: CMS_VOCABULARY)
-- Insert or update vocabulary `a_voc'
-- and also save {CMS_VOCABULARY}.terms if `a_with_terms' is True.
do do
reset_error reset_error
taxonomy_storage.save_vocabulary (a_voc) taxonomy_storage.save_vocabulary (a_voc, False)
error_handler.append (taxonomy_storage.error_handler) error_handler.append (taxonomy_storage.error_handler)
end end
save_term (a_term: CMS_TERM; voc: CMS_VOCABULARY) save_vocabulary_and_terms (a_voc: CMS_VOCABULARY)
-- Insert or update vocabulary `a_voc'
-- and also save {CMS_VOCABULARY}.terms.
do
reset_error
taxonomy_storage.save_vocabulary (a_voc, True)
error_handler.append (taxonomy_storage.error_handler)
end
save_term (a_term: CMS_TERM; voc: detachable CMS_VOCABULARY)
-- Save `a_term' inside `voc' if set.
do do
reset_error reset_error
taxonomy_storage.save_term (a_term, voc) taxonomy_storage.save_term (a_term, voc)
error_handler.append (taxonomy_storage.error_handler) error_handler.append (taxonomy_storage.error_handler)
end end
remove_term_from_vocabulary (t: CMS_TERM; voc: CMS_VOCABULARY)
-- Remove term `t' from vocabulary `voc'.
do
reset_error
taxonomy_storage.remove_term_from_vocabulary (t, voc)
error_handler.append (taxonomy_storage.error_handler)
end
associate_term_with_content (a_term: CMS_TERM; a_content: CMS_CONTENT)
-- Associate term `a_term' with `a_content'.
require
content_with_identifier: a_content.has_identifier
do
reset_error
if attached a_content.identifier as l_id then
taxonomy_storage.associate_term_with_entity (a_term, a_content.content_type, l_id)
error_handler.append (taxonomy_storage.error_handler)
end
end
unassociate_term_from_content (a_term: CMS_TERM; a_content: CMS_CONTENT)
-- Unassociate term `a_term' from `a_content'.
require
content_with_identifier: a_content.has_identifier
do
reset_error
if attached a_content.identifier as l_id then
taxonomy_storage.unassociate_term_from_entity (a_term, a_content.content_type, l_id)
error_handler.append (taxonomy_storage.error_handler)
end
end
associate_term_with_entity (a_term: CMS_TERM; a_type_name: READABLE_STRING_GENERAL; a_entity: READABLE_STRING_GENERAL) associate_term_with_entity (a_term: CMS_TERM; a_type_name: READABLE_STRING_GENERAL; a_entity: READABLE_STRING_GENERAL)
-- Associate term `a_term' with `(a_type_name, a_entity)'. -- Associate term `a_term' with `(a_type_name, a_entity)'.
do do
@@ -174,6 +250,275 @@ feature -- Write
error_handler.append (taxonomy_storage.error_handler) error_handler.append (taxonomy_storage.error_handler)
end end
feature -- Web forms
populate_edit_form (a_response: CMS_RESPONSE; a_form: CMS_FORM; a_content_type_name: READABLE_STRING_8; a_content: detachable CMS_CONTENT)
local
ti: detachable WSF_FORM_TEXT_INPUT
th: WSF_FORM_HIDDEN_INPUT
w_set: WSF_FORM_FIELD_SET
w_select: WSF_FORM_SELECT
w_opt: WSF_FORM_SELECT_OPTION
w_cb: WSF_FORM_CHECKBOX_INPUT
w_voc_set: WSF_FORM_FIELD_SET
s: STRING_32
voc: CMS_VOCABULARY
t: detachable CMS_TERM
l_terms: detachable CMS_TERM_COLLECTION
l_has_edit_permission: BOOLEAN
do
if
attached vocabularies_for_type (a_content_type_name) as l_vocs and then not l_vocs.is_empty
then
l_has_edit_permission := a_response.has_permissions (<<"update any taxonomy", "update " + a_content_type_name + " taxonomy">>)
-- Handle Taxonomy fields, if any associated with `content_type'.
create w_set.make
w_set.add_css_class ("taxonomy")
l_vocs.sort
across
l_vocs as vocs_ic
loop
voc := vocs_ic.item
create th.make_with_text ({STRING_32} "taxonomy_vocabularies[" + voc.id.out + "]", voc.name)
w_set.extend (th)
l_terms := Void
if a_content /= Void then
l_terms := terms_of_content (a_content, voc)
if l_terms /= Void then
l_terms.sort
end
end
create w_voc_set.make
w_set.extend (w_voc_set)
if voc.is_tags then
w_voc_set.set_legend (cms_api.translation (voc.name, Void))
create ti.make ({STRING_32} "taxonomy_" + voc.id.out)
w_voc_set.extend (ti)
if voc.is_term_required then
ti.enable_required
end
if attached voc.description as l_desc then
ti.set_description (cms_api.html_encoded (cms_api.translation (l_desc, Void)))
else
ti.set_description (a_response.html_encoded (cms_api.translation (voc.name, Void)))
end
ti.set_size (70)
if l_terms /= Void then
create s.make_empty
across
l_terms as ic
loop
t := ic.item
if not s.is_empty then
s.append_character (',')
s.append_character (' ')
end
if ic.item.text.has (' ') then
s.append_character ('"')
s.append (t.text)
s.append_character ('"')
else
s.append (t.text)
end
end
ti.set_text_value (s)
end
if not l_has_edit_permission then
ti.set_is_readonly (True)
end
else
fill_vocabularies_with_terms (voc)
if not voc.terms.is_empty then
if voc.multiple_terms_allowed then
if attached voc.description as l_desc then
w_voc_set.set_legend (cms_api.html_encoded (l_desc))
else
w_voc_set.set_legend (cms_api.html_encoded (voc.name))
end
across
voc as voc_terms_ic
loop
t := voc_terms_ic.item
create w_cb.make_with_value ({STRING_32} "taxonomy_" + voc.id.out + "[]", t.text)
w_cb.set_title (t.text)
w_voc_set.extend (w_cb)
if l_terms /= Void and then across l_terms as ic some ic.item.text.same_string (t.text) end then
w_cb.set_checked (True)
end
if not l_has_edit_permission then
w_cb.set_is_readonly (True)
end
end
else
create w_select.make ({STRING_32} "taxonomy_" + voc.id.out)
w_voc_set.extend (w_select)
if attached voc.description as l_desc then
w_select.set_description (cms_api.html_encoded (l_desc))
else
w_select.set_description (cms_api.html_encoded (voc.name))
end
w_voc_set.set_legend (cms_api.html_encoded (voc.name))
across
voc as voc_terms_ic
loop
t := voc_terms_ic.item
create w_opt.make (cms_api.html_encoded (t.text), cms_api.html_encoded (t.text))
w_select.add_option (w_opt)
if l_terms /= Void and then across l_terms as ic some ic.item.text.same_string (t.text) end then
w_opt.set_is_selected (True)
end
end
if not l_has_edit_permission then
w_select.set_is_readonly (True)
end
end
end
end
end
a_form.submit_actions.extend (agent taxonomy_submit_action (a_response, Current, l_vocs, a_content, ?))
if
attached a_form.fields_by_name ("title") as l_title_fields and then
attached l_title_fields.first as l_title_field
then
a_form.insert_after (w_set, l_title_field)
else
a_form.extend (w_set)
end
end
end
taxonomy_submit_action (a_response: CMS_RESPONSE; a_taxonomy_api: CMS_TAXONOMY_API; a_vocs: CMS_VOCABULARY_COLLECTION; a_content: detachable CMS_CONTENT fd: WSF_FORM_DATA)
require
vocs_not_empty: not a_vocs.is_empty
local
l_voc_name: READABLE_STRING_32
l_terms_to_remove: ARRAYED_LIST [CMS_TERM]
l_new_terms: LIST [READABLE_STRING_32]
l_text: READABLE_STRING_GENERAL
l_found: BOOLEAN
t: detachable CMS_TERM
vid: INTEGER_64
do
if
a_content /= Void and then a_content.has_identifier and then
attached fd.table_item ("taxonomy_vocabularies") as fd_vocs
then
if a_response.has_permissions (<<{STRING_32} "update any taxonomy", {STRING_32} "update " + a_content.content_type + " taxonomy">>) then
across
fd_vocs.values as ic
loop
vid := ic.key.to_integer_64
l_voc_name := ic.item.string_representation
if attached a_vocs.item_by_id (vid) as voc then
if attached fd.string_item ("taxonomy_" + vid.out) as l_string then
l_new_terms := a_taxonomy_api.splitted_string (l_string, ',')
elseif attached fd.table_item ("taxonomy_" + vid.out) as fd_terms then
create {ARRAYED_LIST [READABLE_STRING_32]} l_new_terms.make (fd_terms.count)
across
fd_terms as t_ic
loop
l_new_terms.force (t_ic.item.string_representation)
end
else
create {ARRAYED_LIST [READABLE_STRING_32]} l_new_terms.make (0)
end
create l_terms_to_remove.make (0)
if attached a_taxonomy_api.terms_of_content (a_content, voc) as l_existing_terms then
across
l_existing_terms as t_ic
loop
l_text := t_ic.item.text
from
l_found := False
l_new_terms.start
until
l_new_terms.after
loop
if l_new_terms.item.same_string_general (l_text) then
-- Already associated with term `t_ic.text'.
l_found := True
l_new_terms.remove
else
l_new_terms.forth
end
end
if not l_found then
-- Remove term
l_terms_to_remove.force (t_ic.item)
end
end
across
l_terms_to_remove as t_ic
loop
a_taxonomy_api.unassociate_term_from_content (t_ic.item, a_content)
end
end
across
l_new_terms as t_ic
loop
t := a_taxonomy_api.term_by_text (t_ic.item, voc)
if
t = Void and voc.is_tags
then
-- Create new term!
create t.make (t_ic.item)
a_taxonomy_api.save_term (t, voc)
if a_taxonomy_api.has_error then
t := Void
end
end
if t /= Void then
a_taxonomy_api.associate_term_with_content (t, a_content)
end
end
end
end
end
end
end
append_taxonomy_to_xhtml (a_content: CMS_CONTENT; a_response: CMS_RESPONSE; a_output: STRING)
-- Append taxonomy related to `a_content' to xhtml string `a_output',
-- using `a_response' helper routines.
do
if
attached vocabularies_for_type (a_content.content_type) as vocs and then not vocs.is_empty
then
vocs.sort
across
vocs as ic
loop
if
attached terms_of_content (a_content, ic.item) as l_terms and then
not l_terms.is_empty
then
a_output.append ("<ul class=%"taxonomy term-" + ic.item.id.out + "%">")
a_output.append (cms_api.html_encoded (ic.item.name))
a_output.append (": ")
across
l_terms as t_ic
loop
a_output.append ("<li>")
a_response.append_link_to_html (t_ic.item.text, "taxonomy/term/" + t_ic.item.id.out, Void, a_output)
a_output.append ("</li>")
end
a_output.append ("</ul>%N")
end
end
end
end
feature -- Helpers feature -- Helpers
splitted_string (s: READABLE_STRING_32; sep: CHARACTER): LIST [READABLE_STRING_32] splitted_string (s: READABLE_STRING_32; sep: CHARACTER): LIST [READABLE_STRING_32]

View File

@@ -109,6 +109,7 @@ feature -- Access: router
do do
if attached taxonomy_api as l_taxonomy_api then if attached taxonomy_api as l_taxonomy_api then
configure_web (a_api, l_taxonomy_api, a_router) configure_web (a_api, l_taxonomy_api, a_router)
configure_web_amin (a_api, l_taxonomy_api, a_router)
else else
-- Issue with api/dependencies, -- Issue with api/dependencies,
-- thus Current module should not be used! -- thus Current module should not be used!
@@ -120,9 +121,48 @@ feature -- Access: router
-- Configure router mapping for web interface. -- Configure router mapping for web interface.
local local
l_taxonomy_handler: TAXONOMY_HANDLER l_taxonomy_handler: TAXONOMY_HANDLER
l_voc_handler: TAXONOMY_VOCABULARY_HANDLER
do do
create l_taxonomy_handler.make (a_api, a_taxonomy_api) create l_taxonomy_handler.make (a_api, a_taxonomy_api)
a_router.handle ("/taxonomy/term/{termid}", l_taxonomy_handler, a_router.methods_get) a_router.handle ("/taxonomy/term/{termid}", l_taxonomy_handler, a_router.methods_get)
create l_voc_handler.make (a_api, a_taxonomy_api)
a_router.handle ("/taxonomy/vocabulary/", l_voc_handler, a_router.methods_get)
a_router.handle ("/taxonomy/vocabulary/{vocid}", l_voc_handler, a_router.methods_get)
end
configure_web_amin (a_api: CMS_API; a_taxonomy_api: CMS_TAXONOMY_API; a_router: WSF_ROUTER)
-- Configure router mapping for web interface.
local
l_taxonomy_handler: TAXONOMY_TERM_ADMIN_HANDLER
l_voc_handler: TAXONOMY_VOCABULARY_ADMIN_HANDLER
do
a_router.handle ("/admin/taxonomy/", create {WSF_URI_AGENT_HANDLER}.make (agent handle_admin_taxonomy (?, ?, a_api)), a_router.methods_get)
create l_taxonomy_handler.make (a_api, a_taxonomy_api)
a_router.handle ("/admin/taxonomy/term/", l_taxonomy_handler, a_router.methods_get_post)
a_router.handle ("/admin/taxonomy/term/{termid}", l_taxonomy_handler, a_router.methods_get_post)
create l_voc_handler.make (a_api, a_taxonomy_api)
a_router.handle ("/admin/taxonomy/vocabulary/", l_voc_handler, a_router.methods_get_post)
a_router.handle ("/admin/taxonomy/vocabulary/{vocid}", l_voc_handler, a_router.methods_get_post)
end
feature -- Handler
handle_admin_taxonomy (req: WSF_REQUEST; res: WSF_RESPONSE; api: CMS_API)
local
l_page: CMS_RESPONSE
lnk: CMS_LOCAL_LINK
do
create {GENERIC_VIEW_CMS_RESPONSE} l_page.make (req, res, api)
create lnk.make ("Admin Vocabularies", "admin/taxonomy/vocabulary/")
l_page.add_to_primary_tabs (lnk)
create lnk.make ("Create terms", "admin/taxonomy/term/")
l_page.add_to_primary_tabs (lnk)
l_page.execute
end end
feature -- Hooks feature -- Hooks
@@ -139,10 +179,14 @@ feature -- Hooks
end end
menu_system_alter (a_menu_system: CMS_MENU_SYSTEM; a_response: CMS_RESPONSE) menu_system_alter (a_menu_system: CMS_MENU_SYSTEM; a_response: CMS_RESPONSE)
local
lnk: CMS_LOCAL_LINK
do do
-- Add the link to the taxonomy to the main menu -- Add the link to the taxonomy to the main menu
-- create lnk.make ("Taxonomy", "taxonomy/") if a_response.has_permission ("admin taxonomy") then
-- a_menu_system.primary_menu.extend (lnk) create lnk.make ("Taxonomy", "admin/taxonomy/")
a_menu_system.management_menu.extend (lnk)
end
end end
end end

View File

@@ -19,6 +19,7 @@ feature {NONE} -- Initialization
make (nb: INTEGER) make (nb: INTEGER)
do do
create items.make (nb) create items.make (nb)
items.compare_objects
end end
feature -- Access feature -- Access
@@ -48,6 +49,21 @@ feature -- Status report
Result := items.has (a_term) Result := items.has (a_term)
end end
term_by_id (tid: INTEGER_64): detachable CMS_TERM
-- Term of id `tid' contained in Current, if any.
do
across
items as ic
until
Result /= Void
loop
Result := ic.item
if Result.id /= tid then
Result := Void
end
end
end
feature -- Element change feature -- Element change
wipe_out wipe_out

View File

@@ -19,6 +19,7 @@ feature {NONE} -- Initialization
make (nb: INTEGER) make (nb: INTEGER)
do do
create items.make (nb) create items.make (nb)
items.compare_objects
end end
feature -- Access feature -- Access
@@ -37,6 +38,21 @@ feature -- Access
end end
end end
item_by_id (a_id: INTEGER_64): detachable CMS_VOCABULARY
-- Vocabulary of id `a_id' contained in Current, if any.
do
across
items as ic
until
Result /= Void
loop
Result := ic.item
if Result.id /= a_id then
Result := Void
end
end
end
new_cursor: INDEXABLE_ITERATION_CURSOR [CMS_VOCABULARY] new_cursor: INDEXABLE_ITERATION_CURSOR [CMS_VOCABULARY]
-- <Precursor> -- <Precursor>
do do

View File

@@ -0,0 +1,269 @@
note
description: "[
Request handler related to
/admin/taxonomy/term/{termid}
]"
date: "$Date$"
revision: "$revision$"
class
TAXONOMY_TERM_ADMIN_HANDLER
inherit
CMS_MODULE_HANDLER [CMS_TAXONOMY_API]
rename
module_api as taxonomy_api
end
WSF_URI_HANDLER
rename
execute as uri_execute,
new_mapping as new_uri_mapping
end
WSF_URI_TEMPLATE_HANDLER
rename
execute as uri_template_execute,
new_mapping as new_uri_template_mapping
select
new_uri_template_mapping
end
WSF_RESOURCE_HANDLER_HELPER
redefine
do_get,
do_post
end
REFACTORING_HELPER
CMS_API_ACCESS
create
make
feature -- execute
execute (req: WSF_REQUEST; res: WSF_RESPONSE)
-- Execute request handler for any kind of mapping.
do
execute_methods (req, res)
end
uri_execute (req: WSF_REQUEST; res: WSF_RESPONSE)
-- Execute request handler for URI mapping.
do
execute (req, res)
end
uri_template_execute (req: WSF_REQUEST; res: WSF_RESPONSE)
-- Execute request handler for URI-template mapping.
do
execute (req, res)
end
feature -- HTTP Methods
do_post (req: WSF_REQUEST; res: WSF_RESPONSE)
local
l_page: CMS_RESPONSE
tid: INTEGER_64
s: STRING
f: CMS_FORM
t: detachable CMS_TERM
l_parents: detachable CMS_VOCABULARY_COLLECTION
do
if
attached {WSF_STRING} req.path_parameter ("termid") as p_termid and then
p_termid.is_integer
then
tid := p_termid.value.to_integer_64
if tid > 0 then
t := taxonomy_api.term_by_id (tid)
end
end
-- Responding with `main_content_html (l_page)'.
create {GENERIC_VIEW_CMS_RESPONSE} l_page.make (req, res, api)
if l_page.has_permission ("admin taxonomy") then
if t = Void then
l_page.set_title ("New term ...")
create t.make ("")
else
l_page.set_title (t.text)
end
create s.make_empty
f := edit_form (t, l_page, req)
f.process (l_page)
if
attached f.last_data as fd and then
fd.is_valid
then
if attached fd.string_item ("op") as l_op and then l_op.same_string ("Save changes") then
if attached fd.string_item ("text") as l_text then
t.set_text (l_text)
l_page.set_title (t.text)
end
if attached fd.string_item ("description") as l_description then
t.set_description (l_description)
end
if attached fd.string_item ("weight") as l_weight and then l_weight.is_integer then
t.set_weight (l_weight.to_integer)
end
taxonomy_api.save_term (t, Void)
if taxonomy_api.has_error then
fd.report_error ("Term creation failed!")
else
l_page.add_success_message ("Term creation succeed.")
s.append ("<div>View term: ")
s.append (l_page.link (t.text, "admin/taxonomy/term/" + t.id.out, Void))
s.append ("</div>")
if
attached fd.table_item ("vocabularies") as voc_tb and then
attached taxonomy_api.vocabularies (0, 0) as l_vocabularies
then
l_parents := taxonomy_api.vocabularies_for_term (t)
across
voc_tb as vid_ic
until
taxonomy_api.has_error
loop
if attached l_vocabularies.item_by_id (vid_ic.item.string_representation.to_integer_64) as v then
if l_parents /= Void and then attached l_parents.item_by_id (v.id) as l_v then
-- Already as parent!
l_parents.remove (l_v)
else
taxonomy_api.save_term (t, v)
l_vocabularies.remove (v)
end
end
end
if l_parents /= Void then
across
l_parents as v_ic
until
taxonomy_api.has_error
loop
taxonomy_api.remove_term_from_vocabulary (t, v_ic.item)
end
end
end
-- l_page.set_redirection (l_page.location)
end
else
fd.report_error ("Invalid form data!")
end
end
f.append_to_html (l_page.wsf_theme, s)
l_page.set_main_content (s)
l_page.execute
else
send_access_denied (req, res)
end
end
do_get (req: WSF_REQUEST; res: WSF_RESPONSE)
-- <Precursor>
local
l_page: CMS_RESPONSE
tid: INTEGER_64
s: STRING
f: CMS_FORM
t: detachable CMS_TERM
do
if
attached {WSF_STRING} req.path_parameter ("termid") as p_termid and then
p_termid.is_integer
then
tid := p_termid.value.to_integer_64
if tid > 0 then
t := taxonomy_api.term_by_id (tid)
end
end
-- Responding with `main_content_html (l_page)'.
create {GENERIC_VIEW_CMS_RESPONSE} l_page.make (req, res, api)
if l_page.has_permission ("admin taxonomy") then
if t = Void then
l_page.set_title ("Create term ...")
create t.make ("")
else
l_page.set_title (t.text)
end
create s.make_empty
f := edit_form (t, l_page, req)
f.append_to_html (l_page.wsf_theme, s)
l_page.set_main_content (s)
l_page.execute
else
send_access_denied (req, res)
end
end
edit_form (t: CMS_TERM; a_page: CMS_RESPONSE; req: WSF_REQUEST): CMS_FORM
local
f: CMS_FORM
voc: detachable CMS_VOCABULARY
w_tf: WSF_FORM_TEXT_INPUT
w_txt: WSF_FORM_TEXTAREA
w_set: WSF_FORM_FIELD_SET
w_cb: WSF_FORM_CHECKBOX_INPUT
l_parents: detachable CMS_VOCABULARY_COLLECTION
do
create f.make (req.percent_encoded_path_info, "taxonomy")
if t.has_id then
f.extend_html_text (a_page.link ("View associated entities", "taxonomy/term/" + t.id.out, Void))
end
create w_tf.make_with_text ("text", t.text)
w_tf.set_is_required (True)
w_tf.set_label ("Text")
f.extend (w_tf)
create w_txt.make ("description")
w_txt.set_label ("Description")
w_txt.set_rows (3)
w_txt.set_cols (60)
if attached t.description as l_desc then
w_txt.set_text_value (api.html_encoded (l_desc))
end
w_txt.set_description ("Description of the terms; can be used by modules or administration.")
f.extend (w_txt)
create w_tf.make_with_text ("weight", t.weight.out)
w_tf.set_label ("Weight")
w_tf.set_description ("Terms are sorted in ascending order by weight.")
f.extend (w_tf)
if attached taxonomy_api.vocabularies (0, 0) as vocs then
if t.has_id then
l_parents := taxonomy_api.vocabularies_for_term (t)
end
create w_set.make
w_set.set_legend ("Associated vocabularies")
across
vocs as ic
loop
voc := ic.item
create w_cb.make_with_value ("vocabularies[]", ic.item.id.out)
w_cb.set_title (voc.name)
if
l_parents /= Void and then
across l_parents as p_ic some p_ic.item.id = ic.item.id end
then
w_cb.set_checked (True)
end
w_set.extend (w_cb)
end
if w_set.count > 0 then
f.extend (w_set)
end
end
f.extend (create {WSF_FORM_SUBMIT_INPUT}.make_with_text ("op", "Save changes"))
Result := f
end
end

View File

@@ -0,0 +1,414 @@
note
description: "[
Request handler related to
/admin/taxonomy/vocabulary/
/admin/taxonomy/vocabulary/{vocid}
]"
date: "$Date$"
revision: "$revision$"
class
TAXONOMY_VOCABULARY_ADMIN_HANDLER
inherit
CMS_MODULE_HANDLER [CMS_TAXONOMY_API]
rename
module_api as taxonomy_api
end
WSF_URI_HANDLER
rename
execute as uri_execute,
new_mapping as new_uri_mapping
end
WSF_URI_TEMPLATE_HANDLER
rename
execute as uri_template_execute,
new_mapping as new_uri_template_mapping
select
new_uri_template_mapping
end
WSF_RESOURCE_HANDLER_HELPER
redefine
do_get,
do_post
end
REFACTORING_HELPER
CMS_API_ACCESS
create
make
feature -- execute
execute (req: WSF_REQUEST; res: WSF_RESPONSE)
-- Execute request handler for any kind of mapping.
do
execute_methods (req, res)
end
uri_execute (req: WSF_REQUEST; res: WSF_RESPONSE)
-- Execute request handler for URI mapping.
do
execute (req, res)
end
uri_template_execute (req: WSF_REQUEST; res: WSF_RESPONSE)
-- Execute request handler for URI-template mapping.
do
execute (req, res)
end
feature -- HTTP Methods
do_post (req: WSF_REQUEST; res: WSF_RESPONSE)
local
l_page: CMS_RESPONSE
voc: CMS_VOCABULARY
l_typename: READABLE_STRING_GENERAL
s: STRING
do
if not api.user_has_permission (current_user (req), "admin taxonomy") then
send_access_denied (req, res)
else
if attached {WSF_STRING} req.form_parameter ("op") as p_op then
if p_op.same_string ("New Vocabulary") then
if
attached {WSF_STRING} req.form_parameter ("vocabulary_name") as p_name and then
not p_name.is_empty
then
create voc.make (p_name.value)
taxonomy_api.save_vocabulary (voc)
create {GENERIC_VIEW_CMS_RESPONSE} l_page.make (req, res, api)
if taxonomy_api.has_error then
l_page.add_error_message ("Vocabulary creation failed!")
else
l_page.add_success_message ("Vocabulary creation succeed!")
l_page.set_redirection ("admin/taxonomy/vocabulary/" + voc.id.out)
end
else
create {BAD_REQUEST_ERROR_CMS_RESPONSE} l_page.make (req, res, api)
end
elseif
p_op.same_string ("Save changes") and then
attached {WSF_STRING} req.path_parameter ("vocid") as p_vocid and then p_vocid.is_integer and then
attached taxonomy_api.vocabulary (p_vocid.value.to_integer_64) as l_vocabulary
then
create {GENERIC_VIEW_CMS_RESPONSE} l_page.make (req, res, api)
create s.make_empty
l_page.add_notice_message ("Vocabulary " + l_vocabulary.id.out)
if attached {WSF_STRING} req.form_parameter ("name") as p_name then
l_vocabulary.set_name (p_name.value)
end
if attached {WSF_STRING} req.form_parameter ("description") as p_desc then
l_vocabulary.set_description (p_desc.value)
end
if attached {WSF_STRING} req.form_parameter ("weight") as p_weight and then p_weight.is_integer then
l_vocabulary.set_weight (p_weight.integer_value)
end
taxonomy_api.save_vocabulary (l_vocabulary)
if taxonomy_api.has_error then
l_page.add_error_message ("Could not save vocabulary")
elseif
attached {WSF_TABLE} req.form_parameter ("typenames") as typenames_table
then
across
typenames_table as ic
loop
l_typename := ic.item.string_representation
create voc.make_from_term (l_vocabulary)
voc.set_associated_content_type (l_typename, False, False, False)
l_page.add_notice_message ("Content type :" + api.html_encoded (l_typename))
if attached {WSF_TABLE} req.form_parameter ({STRING_32} "vocabulary_" + l_typename.as_string_32) as opts then
across
opts as o_ic
loop
if o_ic.item.same_string ("tags") then
voc.set_is_tags (True)
elseif o_ic.item.same_string ("multiple") then
voc.allow_multiple_term (True)
elseif o_ic.item.same_string ("required") then
voc.set_is_term_required (True)
end
end
end
taxonomy_api.associate_vocabulary_with_type (voc, l_typename)
if taxonomy_api.has_error then
l_page.add_error_message ("Could not save vocabulary content type associations.")
end
end
end
if not taxonomy_api.has_error then
l_page.add_notice_message (l_page.link ({STRING_32} "Back to vocabulary %"" + l_vocabulary.name + "%"", "admin/taxonomy/vocabulary/" + l_vocabulary.id.out, Void))
end
l_page.set_main_content (s)
else
create {NOT_IMPLEMENTED_ERROR_CMS_RESPONSE} l_page.make (req, res, api)
end
else
create {BAD_REQUEST_ERROR_CMS_RESPONSE} l_page.make (req, res, api)
end
l_page.execute
end
end
do_get (req: WSF_REQUEST; res: WSF_RESPONSE)
-- <Precursor>
local
tid: INTEGER_64
do
if not api.user_has_permission (current_user (req), "admin taxonomy") then
send_access_denied (req, res)
else
if attached {WSF_STRING} req.path_parameter ("vocid") as p_vocid then
if p_vocid.is_integer then
tid := p_vocid.value.to_integer_64
end
end
if tid > 0 then
do_get_vocabulary (tid, req, res)
else
do_get_vocabularies (req, res)
end
end
end
do_get_vocabulary (tid: INTEGER_64; req: WSF_REQUEST; res: WSF_RESPONSE)
-- <Precursor>
require
valid_tid: tid > 0
local
l_page: CMS_RESPONSE
s: STRING
l_typename: detachable READABLE_STRING_8
v: detachable CMS_VOCABULARY
l_typenames: detachable LIST [READABLE_STRING_32]
f: CMS_FORM
wtb: WSF_WIDGET_TABLE
wtb_row: WSF_WIDGET_TABLE_ROW
wtb_item: WSF_WIDGET_TABLE_ITEM
voc: detachable CMS_VOCABULARY
l_term: detachable CMS_TERM
tf_input: WSF_FORM_TEXT_INPUT
tf_text: WSF_FORM_TEXTAREA
tf_num: WSF_FORM_NUMBER_INPUT
w_set: WSF_FORM_FIELD_SET
w_cb: WSF_FORM_CHECKBOX_INPUT
sub: WSF_FORM_SUBMIT_INPUT
do
voc := taxonomy_api.vocabulary (tid)
if voc /= Void then
-- Responding with `main_content_html (l_page)'.
create {GENERIC_VIEW_CMS_RESPONSE} l_page.make (req, res, api)
l_page.set_title (voc.name)
taxonomy_api.fill_vocabularies_with_terms (voc)
create f.make (req.percent_encoded_path_info, "taxonomy")
create tf_input.make_with_text ("name", voc.name)
f.extend (tf_input)
create tf_text.make ("description")
tf_text.set_text_value (voc.description)
tf_text.set_description ("Description of the vocabulary; also used as intructions to present to the user when selecting terms.")
tf_text.set_rows (3)
f.extend (tf_text)
create tf_num.make_with_text ("weight", voc.weight.out)
tf_num.set_label ("weight")
tf_num.set_description ("Items are displayed in ascending order by weight.")
f.extend (tf_num)
create wtb.make
wtb.add_css_class ("with_border")
create wtb_row.make (2)
create wtb_item.make_with_text ("Text")
wtb_row.set_item (wtb_item, 1)
create wtb_item.make_with_text ("Description")
wtb_row.set_item (wtb_item, 2)
wtb.add_head_row (wtb_row)
across
voc as ic
loop
l_term := ic.item
create wtb_row.make (3)
wtb.add_row (wtb_row)
create wtb_item.make_with_text (l_page.link (ic.item.text, "admin/taxonomy/term/" + l_term.id.out, Void))
wtb_row.set_item (wtb_item, 1)
if attached ic.item.description as l_desc then
create wtb_item.make_with_text (api.html_encoded (l_desc))
else
create wtb_item.make_with_text ("")
end
wtb_row.set_item (wtb_item, 2)
end
if wtb.body_row_count > 0 then
f.extend (wtb)
else
f.extend_raw_text ("No terms.")
end
create w_set.make
w_set.set_legend ("Content types")
f.extend (w_set)
l_typenames := taxonomy_api.types_associated_with_vocabulary (voc)
create wtb.make
wtb.add_css_class ("with_border")
create wtb_row.make (5)
wtb_row.set_item (create {WSF_WIDGET_TABLE_ITEM}.make_with_text ("Type"), 1)
create wtb_item.make_with_text ("Settings ...")
wtb_item.add_html_attribute ("colspan", "3")
wtb_row.set_item (wtb_item, 2)
wtb.add_head_row (wtb_row)
across
api.content_types as ic
loop
create wtb_row.make (4)
wtb.add_row (wtb_row)
l_typename := ic.item.name
create w_cb.make_with_value ("typenames[]", api.html_encoded (l_typename))
w_cb.set_title (ic.item.name)
wtb_row.set_item (create {WSF_WIDGET_TABLE_ITEM}.make_with_content (w_cb), 1)
v := Void
if
l_typenames /= Void and then
across l_typenames as tn_ic some l_typename.is_case_insensitive_equal (tn_ic.item) end
then
w_cb.set_checked (True)
if attached taxonomy_api.vocabularies_for_type (l_typename) as v_list then
across v_list as v_ic until v /= Void loop
if v_ic.item.id = voc.id then
v := v_ic.item
end
end
end
end
create w_cb.make_with_value ("vocabulary_" + l_typename +"[]", "tags")
w_cb.set_title ("Tags")
w_cb.set_checked (v /= Void and then v.is_tags)
wtb_row.set_item (create {WSF_WIDGET_TABLE_ITEM}.make_with_content (w_cb), 2)
create w_cb.make_with_value ("vocabulary_" + l_typename +"[]", "multiple")
w_cb.set_title ("Multiple Select")
w_cb.set_checked (v /= Void and then v.multiple_terms_allowed)
wtb_row.set_item (create {WSF_WIDGET_TABLE_ITEM}.make_with_content (w_cb), 3)
create w_cb.make_with_value ("vocabulary_" + l_typename +"[]", "required")
w_cb.set_title ("Required")
w_cb.set_checked (v /= Void and then v.is_term_required)
wtb_row.set_item (create {WSF_WIDGET_TABLE_ITEM}.make_with_content (w_cb), 4)
end
if wtb.body_row_count > 0 then
w_set.extend (wtb)
end
create sub.make_with_text ("op", "Save changes")
f.extend (sub)
create s.make_empty
f.append_to_html (l_page.wsf_theme, s)
l_page.set_main_content (s)
else
-- Responding with `main_content_html (l_page)'.
create {NOT_FOUND_ERROR_CMS_RESPONSE} l_page.make (req, res, api)
end
l_page.execute
end
do_get_vocabularies (req: WSF_REQUEST; res: WSF_RESPONSE)
-- <Precursor>
local
l_page: CMS_RESPONSE
s: STRING
l_typenames: detachable LIST [READABLE_STRING_32]
f: CMS_FORM
wtb: WSF_WIDGET_TABLE
wtb_row: WSF_WIDGET_TABLE_ROW
wtb_item: WSF_WIDGET_TABLE_ITEM
voc: detachable CMS_VOCABULARY
tf_input: WSF_FORM_TEXT_INPUT
w_set: WSF_FORM_FIELD_SET
sub: WSF_FORM_SUBMIT_INPUT
do
-- Responding with `main_content_html (l_page)'.
create {GENERIC_VIEW_CMS_RESPONSE} l_page.make (req, res, api)
create wtb.make
wtb.add_css_class ("with_border")
create wtb_row.make (3)
create wtb_item.make_with_text ("Name")
wtb_row.set_item (wtb_item, 1)
create wtb_item.make_with_text ("Type")
wtb_row.set_item (wtb_item, 2)
create wtb_item.make_with_text ("Operations")
wtb_row.set_item (wtb_item, 3)
wtb.add_head_row (wtb_row)
if attached taxonomy_api.vocabularies (0, 0) as lst then
across
lst as ic
loop
voc := ic.item
create wtb_row.make (3)
wtb.add_row (wtb_row)
create wtb_item.make_with_text (l_page.link (ic.item.name, "admin/taxonomy/vocabulary/" + ic.item.id.out, Void))
-- if attached ic.item.description as l_desc then
-- s.append (" : <em>")
-- s.append (api.html_encoded (l_desc))
-- s.append ("</em>")
-- end
wtb_row.set_item (wtb_item, 1)
l_typenames := taxonomy_api.types_associated_with_vocabulary (voc)
if l_typenames /= Void then
create s.make_empty
across
l_typenames as types_ic
loop
if not s.is_empty then
s.append_character (',')
s.append_character (' ')
end
s.append (api.html_encoded (types_ic.item))
end
create wtb_item.make_with_text (s)
wtb_row.set_item (wtb_item, 2)
end
s := l_page.link ("edit", "admin/taxonomy/vocabulary/" + voc.id.out, Void)
create wtb_item.make_with_text (s)
wtb_row.set_item (wtb_item, 3)
end
end
create f.make (req.percent_encoded_path_info, "taxonomy")
f.set_method_post
f.extend (wtb)
create w_set.make
w_set.set_legend ("Create a new vocabulary")
create tf_input.make_with_text ("vocabulary_name", "")
tf_input.set_label ("Vocabulary name")
w_set.extend (tf_input)
create sub.make_with_text ("op", "New Vocabulary")
w_set.extend (sub)
f.extend (w_set)
create s.make_empty
f.append_to_html (l_page.wsf_theme, s)
l_page.set_title ("Vocabularies")
l_page.set_main_content (s)
l_page.execute
end
end

View File

@@ -0,0 +1,129 @@
note
description: "[
Request handler related to
/taxonomy/vocabulary/
/taxonomy/vocabulary/{vocid}
]"
date: "$Date$"
revision: "$revision$"
class
TAXONOMY_VOCABULARY_HANDLER
inherit
CMS_MODULE_HANDLER [CMS_TAXONOMY_API]
rename
module_api as taxonomy_api
end
WSF_URI_HANDLER
rename
execute as uri_execute,
new_mapping as new_uri_mapping
end
WSF_URI_TEMPLATE_HANDLER
rename
execute as uri_template_execute,
new_mapping as new_uri_template_mapping
select
new_uri_template_mapping
end
WSF_RESOURCE_HANDLER_HELPER
redefine
do_get
end
REFACTORING_HELPER
CMS_API_ACCESS
create
make
feature -- execute
execute (req: WSF_REQUEST; res: WSF_RESPONSE)
-- Execute request handler for any kind of mapping.
do
execute_methods (req, res)
end
uri_execute (req: WSF_REQUEST; res: WSF_RESPONSE)
-- Execute request handler for URI mapping.
do
execute (req, res)
end
uri_template_execute (req: WSF_REQUEST; res: WSF_RESPONSE)
-- Execute request handler for URI-template mapping.
do
execute (req, res)
end
feature -- HTTP Methods
do_get (req: WSF_REQUEST; res: WSF_RESPONSE)
-- <Precursor>
local
l_page: CMS_RESPONSE
tid: INTEGER_64
s: STRING
do
if attached {WSF_STRING} req.path_parameter ("vocid") as p_vocid then
if p_vocid.is_integer then
tid := p_vocid.value.to_integer_64
end
end
if tid > 0 then
if attached taxonomy_api.vocabulary (tid) as voc then
-- Responding with `main_content_html (l_page)'.
create {GENERIC_VIEW_CMS_RESPONSE} l_page.make (req, res, api)
l_page.set_title (voc.name)
taxonomy_api.fill_vocabularies_with_terms (voc)
create s.make_empty
s.append ("<ul class=%"taxonomy-terms%">")
across
voc as ic
loop
s.append ("<li>")
s.append (l_page.link (ic.item.text, "taxonomy/term/" + ic.item.id.out, Void))
if attached ic.item.description as l_desc then
s.append (" : <em>")
s.append (api.html_encoded (l_desc))
s.append ("</em>")
end
s.append ("</li>")
end
s.append ("</ul>")
l_page.set_main_content (s)
else
-- Responding with `main_content_html (l_page)'.
create {NOT_FOUND_ERROR_CMS_RESPONSE} l_page.make (req, res, api)
end
l_page.execute
else
-- Responding with `main_content_html (l_page)'.
create {GENERIC_VIEW_CMS_RESPONSE} l_page.make (req, res, api)
create s.make_empty
if attached taxonomy_api.vocabularies (0, 0) as lst then
s.append ("<ul class=%"taxonomy-vocabularies%">")
across
lst as ic
loop
s.append ("<li>")
s.append (l_page.link (ic.item.name, "taxonomy/vocabulary/" + ic.item.id.out, Void))
s.append ("</li>")
end
s.append ("</ul>")
else
s.append ("No vocabulary!")
end
l_page.set_main_content (s)
l_page.execute
end
end
end

View File

@@ -41,6 +41,24 @@ feature -- Access
deferred deferred
end end
vocabularies_for_term (a_term: CMS_TERM): detachable CMS_VOCABULARY_COLLECTION
-- Vocabularies including `a_term'.
deferred
end
is_term_inside_vocabulary (a_term: CMS_TERM; a_vocab: CMS_VOCABULARY): BOOLEAN
-- Is `a_term' inside `a_vocab' ?
require
valid_term: a_term.has_id
valid_vocabulary: a_vocab.has_id
deferred
end
types_associated_with_vocabulary (a_vocab: CMS_VOCABULARY): detachable LIST [READABLE_STRING_32]
-- Type names associated with `a_vocab'.
deferred
end
terms_count: INTEGER_64 terms_count: INTEGER_64
-- Number of terms. -- Number of terms.
deferred deferred
@@ -89,20 +107,28 @@ feature -- Access
feature -- Store feature -- Store
save_vocabulary (a_voc: CMS_VOCABULARY) save_vocabulary (a_voc: CMS_VOCABULARY; a_with_terms: BOOLEAN)
-- Insert or update vocabulary `a_voc'. -- Insert or update vocabulary `a_voc'
-- and also save {CMS_VOCABULARY}.terms if `a_with_terms' is True.
deferred deferred
ensure ensure
not error_handler.has_error implies a_voc.has_id and then vocabulary (a_voc.id) /= Void not error_handler.has_error implies a_voc.has_id and then vocabulary (a_voc.id) /= Void
end end
save_term (t: CMS_TERM; voc: CMS_VOCABULARY) save_term (t: CMS_TERM; voc: detachable CMS_VOCABULARY)
-- Insert or update term `t' as part of vocabulary `voc'. -- Insert or update term `t' as part of vocabulary `voc' if set.
deferred deferred
ensure ensure
not error_handler.has_error implies t.has_id and then term_by_id (t.id) /= Void not error_handler.has_error implies t.has_id and then term_by_id (t.id) /= Void
end end
remove_term_from_vocabulary (t: CMS_TERM; voc: CMS_VOCABULARY)
-- Remove term `t' from vocabulary `voc'.
require
t_has_id: t.has_id
deferred
end
associate_term_with_entity (a_term: CMS_TERM; a_type_name: READABLE_STRING_GENERAL; a_entity: READABLE_STRING_GENERAL) associate_term_with_entity (a_term: CMS_TERM; a_type_name: READABLE_STRING_GENERAL; a_entity: READABLE_STRING_GENERAL)
-- Associate term `a_term' with `(a_type_name, a_entity)'. -- Associate term `a_term' with `(a_type_name, a_entity)'.
require require

View File

@@ -53,6 +53,21 @@ feature -- Access
do do
end end
vocabularies_for_term (a_term: CMS_TERM): detachable CMS_VOCABULARY_COLLECTION
-- <Precursor>
do
end
is_term_inside_vocabulary (a_term: CMS_TERM; a_vocab: CMS_VOCABULARY): BOOLEAN
-- <Precursor>
do
end
types_associated_with_vocabulary (a_vocab: CMS_VOCABULARY): detachable LIST [READABLE_STRING_32]
-- <Precursor>
do
end
terms_count: INTEGER_64 terms_count: INTEGER_64
-- Number of terms. -- Number of terms.
do do
@@ -85,18 +100,25 @@ feature -- Access
feature -- Store feature -- Store
save_vocabulary (a_voc: CMS_VOCABULARY) save_vocabulary (a_voc: CMS_VOCABULARY; a_with_terms: BOOLEAN)
-- Insert or update vocabulary `a_voc'. -- Insert or update vocabulary `a_voc'
-- and also save {CMS_VOCABULARY}.terms if `a_with_terms' is True.
do do
error_handler.add_custom_error (-1, "not implemented", "save_vocabulary") error_handler.add_custom_error (-1, "not implemented", "save_vocabulary")
end end
save_term (t: CMS_TERM; voc: CMS_VOCABULARY) save_term (t: CMS_TERM; voc: detachable CMS_VOCABULARY)
-- <Precursor> -- <Precursor>
do do
error_handler.add_custom_error (-1, "not implemented", "save_term") error_handler.add_custom_error (-1, "not implemented", "save_term")
end end
remove_term_from_vocabulary (t: CMS_TERM; voc: CMS_VOCABULARY)
-- Remove term `t' from vocabulary `voc'.
do
error_handler.add_custom_error (-1, "not implemented", "remove_term_from_vocabulary")
end
associate_term_with_entity (a_term: CMS_TERM; a_type_name: READABLE_STRING_GENERAL; a_entity: READABLE_STRING_GENERAL) associate_term_with_entity (a_term: CMS_TERM; a_type_name: READABLE_STRING_GENERAL; a_entity: READABLE_STRING_GENERAL)
do do
error_handler.add_custom_error (-1, "not implemented", "associate_term_with_entity") error_handler.add_custom_error (-1, "not implemented", "associate_term_with_entity")

View File

@@ -232,21 +232,41 @@ feature -- Access
feature -- Store feature -- Store
save_vocabulary (voc: CMS_VOCABULARY) save_vocabulary (voc: CMS_VOCABULARY; a_with_terms: BOOLEAN)
local
l_terms: CMS_TERM_COLLECTION
do do
save_term (voc, create {CMS_VOCABULARY}.make_none) save_term (voc, create {CMS_VOCABULARY}.make_none)
across
voc.terms as ic if a_with_terms then
until l_terms := terms (voc, 0, 0)
has_error across
loop voc.terms as ic
save_term (ic.item, voc) until
has_error
loop
if attached l_terms.term_by_id (ic.item.id) as t then
-- Already contained.
-- Remove from `l_terms' to leave only terms to remove.
l_terms.remove (t)
else
save_term (ic.item, voc)
end
end
across
l_terms as ic
until
has_error
loop
remove_term_from_vocabulary (ic.item, voc)
end
end end
end end
save_term (t: CMS_TERM; voc: CMS_VOCABULARY) save_term (t: CMS_TERM; voc: detachable CMS_VOCABULARY)
local local
l_parameters: STRING_TABLE [detachable ANY] l_parameters: STRING_TABLE [detachable ANY]
l_insert_voc: BOOLEAN
do do
error_handler.reset error_handler.reset
@@ -255,6 +275,8 @@ feature -- Store
l_parameters.put (t.description, "description") l_parameters.put (t.description, "description")
l_parameters.put (t.weight, "weight") l_parameters.put (t.weight, "weight")
l_insert_voc := voc /= Void and then is_term_inside_vocabulary (t, voc)
sql_begin_transaction sql_begin_transaction
if t.has_id then if t.has_id then
l_parameters.put (t.id, "tid") l_parameters.put (t.id, "tid")
@@ -263,9 +285,18 @@ feature -- Store
sql_insert (sql_insert_term, l_parameters) sql_insert (sql_insert_term, l_parameters)
t.set_id (last_inserted_term_id) t.set_id (last_inserted_term_id)
end end
if not has_error then if
not has_error and
voc /= Void and then
not l_insert_voc
then
l_parameters.wipe_out
l_parameters.put (t.id, "tid") l_parameters.put (t.id, "tid")
l_parameters.put (voc.id, "parent_tid") if voc.has_id then
l_parameters.put (voc.id, "parent_tid")
else
l_parameters.put (0, "parent_tid")
end
sql_insert (sql_insert_term_in_vocabulary, l_parameters) sql_insert (sql_insert_term_in_vocabulary, l_parameters)
end end
if has_error then if has_error then
@@ -276,6 +307,19 @@ feature -- Store
sql_finalize sql_finalize
end end
remove_term_from_vocabulary (t: CMS_TERM; voc: CMS_VOCABULARY)
local
l_parameters: STRING_TABLE [detachable ANY]
do
error_handler.reset
create l_parameters.make (2)
l_parameters.put (t.id, "tid")
l_parameters.put (voc.id, "parent_tid")
sql_modify (sql_remove_term_from_vocabulary, l_parameters)
sql_finalize
end
associate_term_with_entity (a_term: CMS_TERM; a_type_name: READABLE_STRING_GENERAL; a_entity: READABLE_STRING_GENERAL) associate_term_with_entity (a_term: CMS_TERM; a_type_name: READABLE_STRING_GENERAL; a_entity: READABLE_STRING_GENERAL)
-- Associate term `a_term' with `(a_type_name, a_entity)'. -- Associate term `a_term' with `(a_type_name, a_entity)'.
local local
@@ -376,6 +420,90 @@ feature -- Vocabulary and types
end end
end end
is_term_inside_vocabulary (a_term: CMS_TERM; a_voc: CMS_VOCABULARY): BOOLEAN
local
l_parameters: STRING_TABLE [detachable ANY]
do
error_handler.reset
create l_parameters.make (2)
l_parameters.put (a_term.id, "tid")
l_parameters.put (a_voc.id, "parent_tid")
sql_query (sql_select_term_in_vocabulary, l_parameters)
sql_start
if not has_error or sql_after then
Result := sql_read_integer_64 (1) > 0
end
sql_finalize
end
vocabularies_for_term (a_term: CMS_TERM): detachable CMS_VOCABULARY_COLLECTION
-- <Precursor>
local
voc: detachable CMS_VOCABULARY
l_parameters: STRING_TABLE [detachable ANY]
l_parent_id: INTEGER_64
l_ids: ARRAYED_LIST [INTEGER_64]
do
error_handler.reset
create l_parameters.make (3)
l_parameters.put (a_term.id, "tid")
sql_query (sql_select_vocabularies_for_term, l_parameters)
create l_ids.make (1)
from
sql_start
until
sql_after or has_error
loop
l_parent_id := sql_read_integer_64 (1)
l_ids.force (l_parent_id)
sql_forth
end
sql_finalize
if l_ids.count > 0 then
create Result.make (1)
across
l_ids as ic
loop
voc := vocabulary (ic.item)
if voc /= Void then
Result.force (voc)
end
end
if Result.count = 0 then
Result := Void
end
end
end
types_associated_with_vocabulary (a_vocab: CMS_VOCABULARY): detachable LIST [READABLE_STRING_32]
-- Type names associated with `a_vocab'.
local
l_parameters: STRING_TABLE [detachable ANY]
do
error_handler.reset
create l_parameters.make (1)
l_parameters.put (a_vocab.id, "tid")
sql_query (sql_select_type_associated_with_vocabulary, l_parameters)
create {ARRAYED_LIST [READABLE_STRING_32]} Result.make (3)
from
sql_start
until
sql_after or has_error
loop
if attached sql_read_string_32 (1) as l_typename then
Result.force (l_typename)
end
sql_forth
end
sql_finalize
end
associate_vocabulary_with_type (a_voc: CMS_VOCABULARY; a_type_name: READABLE_STRING_GENERAL) associate_vocabulary_with_type (a_voc: CMS_VOCABULARY; a_type_name: READABLE_STRING_GENERAL)
-- <Precursor> -- <Precursor>
local local
@@ -396,10 +524,17 @@ feature -- Vocabulary and types
i := i | mask_is_required i := i | mask_is_required
end end
l_parameters.put ((- i).out, "entity") l_parameters.put ((- i).out, "entity")
l_parameters.put (a_type_name, "type") l_parameters.put (a_type_name, "type")
sql_insert (sql_insert_term_index, l_parameters) if
attached vocabularies_for_type (a_type_name) as lst and then
across lst as ic some a_voc.id = ic.item.id end
then
sql_modify (sql_update_term_index, l_parameters)
else
sql_insert (sql_insert_term_index, l_parameters)
end
sql_finalize sql_finalize
end end
@@ -464,6 +599,20 @@ feature {NONE} -- Queries
]" ]"
-- Terms under :parent_tid. -- Terms under :parent_tid.
sql_select_vocabularies_for_term: STRING = "[
SELECT parent
FROM taxonomy_hierarchy
WHERE tid = :tid
;
]"
sql_select_term_in_vocabulary: STRING = "[
SELECT count(*)
FROM taxonomy_hierarchy
WHERE tid = :tid and parent = :parent_tid
;
]"
sql_select_terms_with_range: STRING = "[ sql_select_terms_with_range: STRING = "[
SELECT taxonomy_term.tid, taxonomy_term.text, taxonomy_term.weight, taxonomy_term.description SELECT taxonomy_term.tid, taxonomy_term.text, taxonomy_term.weight, taxonomy_term.description
FROM taxonomy_term INNER JOIN taxonomy_hierarchy ON taxonomy_term.tid = taxonomy_hierarchy.tid FROM taxonomy_term INNER JOIN taxonomy_hierarchy ON taxonomy_term.tid = taxonomy_hierarchy.tid
@@ -505,6 +654,10 @@ feature {NONE} -- Queries
VALUES (:tid, :parent_tid); VALUES (:tid, :parent_tid);
]" ]"
sql_remove_term_from_vocabulary: STRING = "[
DELETE FROM taxonomy_hierarchy WHERE tid=:tid AND parent=:parent_tid;
]"
sql_select_terms_of_entity: STRING = "[ sql_select_terms_of_entity: STRING = "[
SELECT tid FROM taxonomy_index WHERE type=:type AND entity=:entity; SELECT tid FROM taxonomy_index WHERE type=:type AND entity=:entity;
]" ]"
@@ -527,6 +680,19 @@ feature {NONE} -- Queries
WHERE type=:type AND entity <= 0; WHERE type=:type AND entity <= 0;
]" ]"
sql_select_type_associated_with_vocabulary: STRING = "[
SELECT type
FROM taxonomy_index
WHERE tid=:tid AND entity <= 0;
]"
sql_update_term_index: STRING = "[
UPDATE taxonomy_index
SET entity=:entity
WHERE tid=:tid and type=:type
;
]"
sql_insert_term_index: STRING = "[ sql_insert_term_index: STRING = "[
INSERT INTO taxonomy_index (tid, entity, type) INSERT INTO taxonomy_index (tid, entity, type)
VALUES (:tid, :entity, :type); VALUES (:tid, :entity, :type);

View File

@@ -19,3 +19,8 @@ ul.taxonomy li:hover {
border-bottom: solid 1px #66f; border-bottom: solid 1px #66f;
background-color: #ddf; background-color: #ddf;
} }
table.taxonomy td {
border: solid 1px #ccc;
padding: 2px;
}

View File

@@ -19,3 +19,9 @@ ul.taxonomy {
} }
} }
} }
table.taxonomy {
td {
border: solid 1px #ccc;
padding: 2px;
}
}

View File

@@ -15,7 +15,7 @@ inherit
REFACTORING_HELPER REFACTORING_HELPER
CMS_ENCODERS CMS_REQUEST_UTIL
create create
make make
@@ -292,6 +292,26 @@ feature -- Logging
end end
end 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
to_implement ("Implement i18n support [2015-may]")
Result := a_text.as_string_32
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"
local
l_formatter: CMS_STRING_FORMATTER
do
create l_formatter
Result := l_formatter.formatted_string (a_text, args)
end
feature -- Emails feature -- Emails
new_email (a_to_address: READABLE_STRING_8; a_subject: READABLE_STRING_8; a_content: READABLE_STRING_8): CMS_EMAIL new_email (a_to_address: READABLE_STRING_8; a_subject: READABLE_STRING_8; a_content: READABLE_STRING_8): CMS_EMAIL
@@ -407,7 +427,7 @@ feature {NONE} -- Hooks
l_hooks: like hooks l_hooks: like hooks
do do
l_hooks := hooks l_hooks := hooks
register_hooks (l_hooks) setup_core_hooks (l_hooks)
across across
enabled_modules as ic enabled_modules as ic
loop loop
@@ -436,7 +456,7 @@ feature -- Query: API
feature -- Hooks feature -- Hooks
register_hooks (a_hooks: CMS_HOOK_CORE_MANAGER) setup_core_hooks (a_hooks: CMS_HOOK_CORE_MANAGER)
-- Register hooks associated with the cms core. -- Register hooks associated with the cms core.
do do
a_hooks.subscribe_to_export_hook (Current) a_hooks.subscribe_to_export_hook (Current)

View File

@@ -13,6 +13,11 @@ inherit
feature -- Access feature -- Access
identifier: detachable READABLE_STRING_32
-- Optional identifier.
deferred
end
title: detachable READABLE_STRING_32 title: detachable READABLE_STRING_32
-- Title associated with Current content. -- Title associated with Current content.
deferred deferred
@@ -37,6 +42,14 @@ feature -- Access
feature -- Status report feature -- Status report
has_identifier: BOOLEAN
-- Current content has identifier?
do
Result := identifier /= Void
ensure
Result implies identifier /= Void
end
is_typed_as (a_content_type: READABLE_STRING_GENERAL): BOOLEAN is_typed_as (a_content_type: READABLE_STRING_GENERAL): BOOLEAN
-- Is current node of type `a_content_type' ? -- Is current node of type `a_content_type' ?
do do

View File

@@ -26,6 +26,9 @@ feature {NONE} -- Initialization
feature -- Access feature -- Access
identifier: detachable READABLE_STRING_32
-- <Precursor>
title: detachable READABLE_STRING_32 title: detachable READABLE_STRING_32
-- Title associated with Current content. -- Title associated with Current content.
@@ -42,6 +45,15 @@ feature -- Access
feature -- Element change feature -- Element change
set_identifier (a_identifier: detachable READABLE_STRING_GENERAL)
do
if a_identifier = Void then
identifier := Void
else
create {IMMUTABLE_STRING_32} identifier.make_from_string_general (a_identifier)
end
end
set_title (a_title: detachable READABLE_STRING_GENERAL) set_title (a_title: detachable READABLE_STRING_GENERAL)
do do
if a_title = Void then if a_title = Void then

View File

@@ -72,7 +72,6 @@ feature {NONE} -- Initialization
l_module: CMS_MODULE l_module: CMS_MODULE
l_enabled_modules: CMS_MODULE_COLLECTION l_enabled_modules: CMS_MODULE_COLLECTION
do do
api.register_hooks (hooks)
l_enabled_modules := api.enabled_modules l_enabled_modules := api.enabled_modules
across across
l_enabled_modules as ic l_enabled_modules as ic
@@ -114,26 +113,6 @@ feature -- Access
end end
end 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
to_implement ("Implement i18n support [2015-may]")
Result := a_text.as_string_32
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"
local
l_formatter: CMS_STRING_FORMATTER
do
create l_formatter
Result := l_formatter.formatted_string (a_text, args)
end
feature -- API feature -- API
api: CMS_API api: CMS_API
@@ -897,6 +876,22 @@ feature -- Menu: change
m.extend (lnk) m.extend (lnk)
end 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 feature -- Message
add_message (a_msg: READABLE_STRING_8; a_category: detachable READABLE_STRING_8) add_message (a_msg: READABLE_STRING_8; a_category: detachable READABLE_STRING_8)
@@ -984,6 +979,26 @@ feature -- Theme
end end
end end
feature -- Theme helpers
wsf_theme: WSF_THEME
-- WSF Theme from CMS `theme' for Current response.
local
t: like internal_wsf_theme
do
t := internal_wsf_theme
if t = Void then
create {CMS_TO_WSF_THEME} t.make (Current, theme)
internal_wsf_theme := t
end
Result := t
end
feature {NONE} -- Theme helpers
internal_wsf_theme: detachable WSF_THEME
-- Once per object for `wsf_theme'.
feature -- Element Change feature -- Element Change
set_status_code (a_status: INTEGER) set_status_code (a_status: INTEGER)