577 lines
17 KiB
Plaintext
577 lines
17 KiB
Plaintext
note
|
|
description: "[
|
|
API to handle taxonomy vocabularies and terms.
|
|
]"
|
|
date: "$Date$"
|
|
revision: "$Revision$"
|
|
|
|
class
|
|
CMS_TAXONOMY_API
|
|
|
|
inherit
|
|
CMS_MODULE_API
|
|
redefine
|
|
initialize
|
|
end
|
|
|
|
REFACTORING_HELPER
|
|
|
|
create
|
|
make
|
|
|
|
feature {NONE} -- Initialization
|
|
|
|
initialize
|
|
-- <Precursor>
|
|
do
|
|
Precursor
|
|
|
|
-- Create the node storage for type blog
|
|
if attached storage.as_sql_storage as l_storage_sql then
|
|
create {CMS_TAXONOMY_STORAGE_SQL} taxonomy_storage.make (l_storage_sql)
|
|
else
|
|
create {CMS_TAXONOMY_STORAGE_NULL} taxonomy_storage.make
|
|
end
|
|
end
|
|
|
|
feature {CMS_MODULE} -- Access nodes storage.
|
|
|
|
taxonomy_storage: CMS_TAXONOMY_STORAGE_I
|
|
|
|
feature -- Access node
|
|
|
|
vocabulary_count: INTEGER_64
|
|
-- Number of vocabulary.
|
|
do
|
|
Result := taxonomy_storage.vocabulary_count
|
|
end
|
|
|
|
vocabularies (a_limit: NATURAL_32; a_offset: NATURAL_32): CMS_VOCABULARY_COLLECTION
|
|
-- List of vocabularies ordered by weight and limited by limit and offset.
|
|
do
|
|
Result := taxonomy_storage.vocabularies (a_limit, a_offset)
|
|
end
|
|
|
|
vocabulary (a_id: INTEGER_64): detachable CMS_VOCABULARY
|
|
-- Vocabulary associated with id `a_id'.
|
|
require
|
|
valid_id: a_id > 0
|
|
do
|
|
Result := taxonomy_storage.vocabulary (a_id)
|
|
end
|
|
|
|
vocabularies_for_type (a_type_name: READABLE_STRING_GENERAL): detachable CMS_VOCABULARY_COLLECTION
|
|
-- Vocabularies associated with content type `a_type_name'.
|
|
do
|
|
Result := taxonomy_storage.vocabularies_for_type (a_type_name)
|
|
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 `a_vocab' with associated terms.
|
|
do
|
|
reset_error
|
|
a_vocab.terms.wipe_out
|
|
if attached terms (a_vocab, 0, 0) as lst then
|
|
across
|
|
lst as ic
|
|
loop
|
|
a_vocab.extend (ic.item)
|
|
end
|
|
end
|
|
end
|
|
|
|
term_count_from_vocabulary (a_vocab: CMS_VOCABULARY): INTEGER_64
|
|
-- Number of terms from vocabulary `a_vocab'.
|
|
require
|
|
has_id: a_vocab.has_id
|
|
do
|
|
Result := taxonomy_storage.term_count_from_vocabulary (a_vocab)
|
|
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 related to `(a_type_name,a_entity)', and if `a_vocabulary' is set
|
|
-- constrain to be part of `a_vocabulary'.
|
|
do
|
|
Result := taxonomy_storage.terms_of_entity (a_type_name, a_entity, a_vocabulary)
|
|
end
|
|
|
|
terms (a_vocab: CMS_VOCABULARY; a_limit: NATURAL_32; a_offset: NATURAL_32): CMS_TERM_COLLECTION
|
|
-- List of terms ordered by weight and limited by limit and offset.
|
|
require
|
|
has_id: a_vocab.has_id
|
|
do
|
|
Result := taxonomy_storage.terms (a_vocab, a_limit, a_offset)
|
|
end
|
|
|
|
term_by_id (a_tid: INTEGER_64): detachable CMS_TERM
|
|
do
|
|
Result := taxonomy_storage.term_by_id (a_tid)
|
|
end
|
|
|
|
term_by_text (a_term_text: READABLE_STRING_GENERAL; a_vocabulary: detachable CMS_VOCABULARY): detachable CMS_TERM
|
|
-- Term with text `a_term_text', included in vocabulary `a_vocabulary' if provided.
|
|
do
|
|
Result := taxonomy_storage.term_by_text (a_term_text, a_vocabulary)
|
|
end
|
|
|
|
entities_associated_with_term (a_term: CMS_TERM): detachable LIST [TUPLE [entity: READABLE_STRING_32; typename: detachable READABLE_STRING_32]]
|
|
-- Entities and related typename associated with `a_term'.
|
|
require
|
|
a_term_exists: a_term.has_id
|
|
do
|
|
Result := taxonomy_storage.entities_associated_with_term (a_term)
|
|
error_handler.append (taxonomy_storage.error_handler)
|
|
end
|
|
|
|
feature -- Write
|
|
|
|
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
|
|
reset_error
|
|
taxonomy_storage.save_vocabulary (a_voc, False)
|
|
error_handler.append (taxonomy_storage.error_handler)
|
|
end
|
|
|
|
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
|
|
reset_error
|
|
taxonomy_storage.save_term (a_term, voc)
|
|
error_handler.append (taxonomy_storage.error_handler)
|
|
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 `a_term' with `(a_type_name, a_entity)'.
|
|
do
|
|
reset_error
|
|
taxonomy_storage.associate_term_with_entity (a_term, a_type_name, a_entity)
|
|
error_handler.append (taxonomy_storage.error_handler)
|
|
end
|
|
|
|
unassociate_term_from_entity (a_term: CMS_TERM; a_type_name: READABLE_STRING_GENERAL; a_entity: READABLE_STRING_GENERAL)
|
|
-- Unassociate term `a_term' from `(a_type_name, a_entity)'.
|
|
do
|
|
reset_error
|
|
taxonomy_storage.unassociate_term_from_entity (a_term, a_type_name, a_entity)
|
|
error_handler.append (taxonomy_storage.error_handler)
|
|
end
|
|
|
|
associate_vocabulary_with_type (a_voc: CMS_VOCABULARY; a_type_name: READABLE_STRING_GENERAL)
|
|
-- Associate vocabulary `a_voc' with type `a_type_name'.
|
|
require
|
|
existing_term: a_voc.has_id
|
|
do
|
|
reset_error
|
|
taxonomy_storage.associate_vocabulary_with_type (a_voc, a_type_name)
|
|
error_handler.append (taxonomy_storage.error_handler)
|
|
end
|
|
|
|
unassociate_vocabulary_with_type (a_voc: CMS_VOCABULARY; a_type_name: READABLE_STRING_GENERAL)
|
|
-- Un-associate vocabulary `a_voc' from type `a_type_name'.
|
|
require
|
|
existing_term: a_voc.has_id
|
|
do
|
|
reset_error
|
|
taxonomy_storage.unassociate_vocabulary_with_type (a_voc, a_type_name)
|
|
error_handler.append (taxonomy_storage.error_handler)
|
|
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_div: WSF_FORM_DIV
|
|
w_select: WSF_FORM_SELECT
|
|
w_opt: WSF_FORM_SELECT_OPTION
|
|
w_cb: WSF_FORM_CHECKBOX_INPUT
|
|
w_voc_set: WSF_FORM_DIV
|
|
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_div.make
|
|
w_div.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_div.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_div.extend (w_voc_set)
|
|
|
|
if voc.is_tags then
|
|
w_voc_set.extend_html_text ("<strong><label>" + cms_api.html_encoded (cms_api.translation (voc.name, Void)) + "</label></strong>")
|
|
-- 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.extend_html_text ("<strong><label>" + cms_api.html_encoded (l_desc) + "</label></strong>")
|
|
-- w_voc_set.set_legend (cms_api.html_encoded (l_desc))
|
|
else
|
|
w_voc_set.extend_html_text ("<strong><label>" + cms_api.html_encoded (voc.name) + "</label></strong>")
|
|
-- 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.extend_html_text ("<strong><label>" + cms_api.html_encoded (voc.name) + "</label></strong>")
|
|
-- 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_div, l_title_field)
|
|
else
|
|
a_form.extend (w_div)
|
|
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
|
|
|
|
splitted_string (s: READABLE_STRING_32; sep: CHARACTER): LIST [READABLE_STRING_32]
|
|
-- Splitted string from `s' with separator `sep', and support '"..."' wrapping.
|
|
local
|
|
i,j,n,b: INTEGER
|
|
t: STRING_32
|
|
do
|
|
create {ARRAYED_LIST [READABLE_STRING_32]} Result.make (1)
|
|
Result.compare_objects
|
|
from
|
|
i := 1
|
|
b := 1
|
|
n := s.count
|
|
create t.make_empty
|
|
until
|
|
i > n
|
|
loop
|
|
if s[i].is_space then
|
|
if not t.is_empty then
|
|
t.append_character (s[i])
|
|
end
|
|
elseif s[i] = sep then
|
|
t.left_adjust
|
|
t.right_adjust
|
|
if t.count > 2 and t.starts_with_general ("%"") and t.ends_with_general ("%"") then
|
|
t.remove_head (1)
|
|
t.remove_tail (1)
|
|
end
|
|
Result.force (t)
|
|
create t.make_empty
|
|
elseif s[i] = '"' then
|
|
j := s.index_of ('"', i + 1)
|
|
if j > 0 then
|
|
t.append (s.substring (i, j))
|
|
end
|
|
i := j
|
|
else
|
|
t.append_character (s[i])
|
|
end
|
|
i := i + 1
|
|
end
|
|
if not t.is_empty then
|
|
t.left_adjust
|
|
t.right_adjust
|
|
Result.force (t)
|
|
end
|
|
end
|
|
|
|
end
|