") then
a_block.set_title (Void)
else
a_block.set_title (l_title)
end
end
end
block_conditions (a_block_id: READABLE_STRING_8): detachable ARRAYED_LIST [CMS_BLOCK_EXPRESSION_CONDITION]
-- Condition associated with `a_block_id' in configuration, if any.
do
if attached setup.text_item ("blocks." + a_block_id + ".condition") as s then
create Result.make (1)
Result.force (create {CMS_BLOCK_EXPRESSION_CONDITION}.make (s))
end
if attached setup.text_list_item ("blocks." + a_block_id + ".conditions") as lst then
if Result = Void then
create Result.make (lst.count)
end
across
lst as ic
loop
Result.force (create {CMS_BLOCK_EXPRESSION_CONDITION}.make (ic.item))
end
end
end
block_options (a_block_id: READABLE_STRING_8): detachable STRING_TABLE [READABLE_STRING_32]
-- Options associated with `a_block_id' in configuration, if any.
do
if attached setup.text_table_item ("blocks." + a_block_id + ".options") as tb then
Result := tb
end
end
is_block_included (a_block_id: READABLE_STRING_8; dft: BOOLEAN): BOOLEAN
-- Is block `a_block_id' included in current response?
-- If no preference, return `dft'.
do
if attached block_conditions (a_block_id) as l_conditions then
Result := across l_conditions as ic some ic.item.satisfied_for_response (Current) end
else
Result := dft
end
end
block_cache (a_block_id: READABLE_STRING_8): detachable TUPLE [block: CMS_CACHE_BLOCK; region: READABLE_STRING_8; expired: BOOLEAN]
-- Cached version of block `a_block_id'.
local
l_cache: CMS_FILE_STRING_8_CACHE
do
if
attached setup.text_item ("blocks." + a_block_id + ".expiration") as nb_secs and then
nb_secs.is_integer
then
if attached block_region_preference (a_block_id, "none") as l_region and then not l_region.same_string_general ("none") then
create l_cache.make (api.files_location.extended (".cache").extended ("blocks").extended (a_block_id).appended_with_extension ("html"))
if
l_cache.exists and then
not l_cache.expired (Void, nb_secs.to_integer)
then
Result := [create {CMS_CACHE_BLOCK} .make (a_block_id, l_cache), l_region, False]
else
Result := [create {CMS_CACHE_BLOCK} .make (a_block_id, l_cache), l_region, True]
end
end
end
end
clear_block_caches (a_block_id_list: detachable ITERABLE [READABLE_STRING_GENERAL])
-- Clear cache for block `a_block_id_list' if set,
-- otherwise clear all block caches if `a_block_id_list' is Void.
local
p,pb: PATH
dir: DIRECTORY
l_cache: CMS_FILE_STRING_8_CACHE
do
p := api.files_location.extended (".cache").extended ("blocks")
if a_block_id_list /= Void then
across
a_block_id_list as ic
loop
-- FIXME: find a smarter way to avoid conflict between block id, and other cache id.
-- however, this is only about "Cache" so not that critical if deleted by mistake.
pb := p.extended (ic.item).appended_with_extension ("html")
create l_cache.make (pb)
if l_cache.exists then
l_cache.delete
end
end
else
-- Clear all block caches.
create dir.make_with_path (p)
dir.recursive_delete
end
end
feature {CMS_HOOK_CORE_MANAGER} -- Block management: internal
internal_block_alias_table: like block_alias_table
-- Internal memory cache for `block_alias_table'.
block_alias_table: detachable STRING_TABLE [LIST [READABLE_STRING_8]]
-- Table of included block aliases, if any.
-- note: { block_id => [ alias-names ..] }
local
k,v: READABLE_STRING_GENERAL
l_block_id, l_alias_id: READABLE_STRING_8
lst: detachable LIST [READABLE_STRING_8]
do
Result := internal_block_alias_table
if
Result = Void and then
attached setup.text_table_item ("blocks.&aliases") as tb
then
create Result.make (tb.count)
across
tb as ic
loop
k := ic.key
v := ic.item
if v.is_valid_as_string_8 then
l_block_id := v.to_string_8
if k.is_valid_as_string_8 then
l_alias_id := k.to_string_8
if is_block_included (l_alias_id, False) then
lst := Result.item (l_block_id)
if lst = Void then
create {ARRAYED_LIST [READABLE_STRING_8]} lst.make (1)
end
lst.force (l_alias_id)
Result.force (lst, l_block_id)
end
else
check valid_alias_id: False end
end
else
check valid_block_id: False end
end
end
end
end
feature -- Blocks regions
regions: STRING_TABLE [CMS_BLOCK_REGION]
-- Layout regions, that contains blocks.
blocks: STRING_TABLE [CMS_BLOCK]
-- Blocks indexed by their block id.
block_region_settings: STRING_TABLE [STRING]
block_region (b: CMS_BLOCK; a_default_region: detachable READABLE_STRING_8): CMS_BLOCK_REGION
-- Region associated with block `b', or else `a_default_region' if provided.
local
l_region_name: detachable READABLE_STRING_8
do
l_region_name := block_region_settings.item (b.name)
if l_region_name = Void then
if attached setup.text_item ("blocks." + b.name + ".region") as l_setup_name then
l_region_name := l_setup_name.as_string_8 -- FIXME: potential truncated string 32.
-- Remember for later.
block_region_settings.force (l_region_name, b.name)
elseif a_default_region /= Void then
l_region_name := a_default_region
else
-- Default .. put it in same named region
-- Maybe a bad idea
l_region_name := b.name.as_lower
end
end
if attached regions.item (l_region_name) as res then
Result := res
else
create Result.make (l_region_name)
regions.force (Result, l_region_name)
end
end
feature {NONE} -- Blocks
put_core_block (b: CMS_BLOCK; a_default_region: detachable READABLE_STRING_8; is_block_included_by_default: BOOLEAN; a_alias_table: like block_alias_table)
-- Add block `b' to associated region or `a_default_region' if provided
-- and check optional associated condition.
-- If no condition then use `is_block_included_by_default' to
-- decide if block is included or not.
local
l_region: detachable like block_region
do
if is_block_included (b.name, is_block_included_by_default) then
l_region := block_region (b, a_default_region)
l_region.extend (b)
blocks.force (b, b.name)
end
-- Included alias block ids.
if
a_alias_table /= Void and then
attached a_alias_table.item (b.name) as l_aliases
then
across
l_aliases as ic
loop
add_block (create {CMS_ALIAS_BLOCK}.make_with_block (ic.item, b), a_default_region)
end
end
end
feature -- Blocks
put_block (b: CMS_BLOCK; a_default_region: detachable READABLE_STRING_8; is_block_included_by_default: BOOLEAN)
-- Add block `b' to associated region or `a_default_region' if provided
-- and check optional associated condition.
-- If no condition then use `is_block_included_by_default' to
-- decide if block is included or not.
do
if is_block_included (b.name, is_block_included_by_default) then
add_block (b, a_default_region)
end
end
add_block (b: CMS_BLOCK; a_default_region: detachable READABLE_STRING_8)
-- Add block `b' to associated region or `a_default_region' if provided.
-- WARNING: ignore any block condition! USE WITH CARE!
local
l_region: detachable like block_region
do
l_region := block_region (b, a_default_region)
l_region.extend (b)
blocks.force (b, b.name)
end
remove_block (b: CMS_BLOCK)
-- Remove block `b' from associated region.
local
l_region: detachable like block_region
l_found: BOOLEAN
do
across
regions as reg_ic
until
l_found
loop
l_region := reg_ic.item
l_found := l_region.blocks.has (b)
if l_found then
l_region.remove (b)
end
end
blocks.remove (b.name)
end
get_blocks
-- Get block from CMS core, and from modules.
local
l_region: CMS_BLOCK_REGION
b: CMS_BLOCK
do
get_core_blocks
get_module_blocks
across
regions as reg_ic
loop
l_region := reg_ic.item
across
l_region.blocks as ic
loop
update_block (ic.item)
end
l_region.sort
end
debug ("cms")
create {CMS_CONTENT_BLOCK} b.make ("made_with", Void, "Made with EWF", Void)
b.set_weight (99)
put_block (b, "footer", True)
end
end
get_core_blocks
-- Get blocks provided by the CMS core.
local
l_alias_table: like block_alias_table
do
-- Get included aliased blocks.
l_alias_table := block_alias_table
put_core_block (top_header_block, "top", True, l_alias_table)
put_core_block (header_block, "header", True, l_alias_table)
if attached message_block as m then
put_core_block (m, "content", True, l_alias_table)
end
if attached primary_tabs_block as m then
put_core_block (m, "content", True, l_alias_table)
end
add_block (content_block, "content") -- Can not be disabled!
if attached management_menu_block as l_block then
put_core_block (l_block, "sidebar_first", True, l_alias_table)
end
if attached navigation_menu_block as l_block then
put_core_block (l_block, "sidebar_first", True, l_alias_table)
end
if attached user_menu_block as l_block then
put_core_block (l_block, "sidebar_second", True, l_alias_table)
end
end
get_module_blocks
-- Get blocks provided by modules.
do
-- Get block from modules, and related alias.
api.hooks.invoke_block (Current)
end
primary_menu_block: detachable CMS_MENU_BLOCK
do
if attached primary_menu as m and then not m.is_empty then
create Result.make (m)
end
end
management_menu_block: detachable CMS_MENU_BLOCK
do
if attached management_menu as m and then not m.is_empty then
create Result.make (m)
end
end
navigation_menu_block: detachable CMS_MENU_BLOCK
do
if attached navigation_menu as m and then not m.is_empty then
create Result.make (m)
end
end
user_menu_block: detachable CMS_MENU_BLOCK
do
if attached user_menu as m and then not m.is_empty then
create Result.make (m)
end
end
top_header_block: CMS_CONTENT_BLOCK
local
s: STRING
do
create s.make_empty
create Result.make ("page_top", Void, s, Void)
Result.set_weight (-5)
Result.set_is_raw (True)
end
header_block: CMS_CONTENT_BLOCK
local
s: STRING
l_hb: STRING
do
create s.make_from_string (theme.menu_html (primary_menu, True, Void))
create l_hb.make_empty
create Result.make ("header", Void, l_hb, Void)
Result.set_weight (-4)
Result.set_is_raw (True)
end
horizontal_primary_menu_html: STRING
do
create Result.make_empty
Result.append ("")
end
horizontal_primary_tabs_html: STRING
do
create Result.make_empty
Result.append ("")
Result.append (theme.menu_html (primary_tabs, True, Void))
Result.append ("
")
end
message_html: detachable STRING
do
if attached message as m and then not m.is_empty then
Result := "" + m + "
"
end
end
message_block: detachable CMS_CONTENT_BLOCK
do
if attached message as m and then not m.is_empty then
create Result.make ("message", Void, "" + m + "
", Void)
Result.set_is_raw (True)
Result.set_weight (-3)
end
end
primary_tabs_block: detachable CMS_MENU_BLOCK
do
if attached primary_tabs as m and then not m.is_empty then
create Result.make (m)
Result.is_horizontal := True
Result.set_is_raw (True)
Result.set_weight (-2)
Result.add_css_class ("tabs")
end
end
content_block: CMS_CONTENT_BLOCK
local
s: STRING
do
if attached main_content as l_content then
s := l_content
else
s := ""
debug
s := "No Content"
end
end
create Result.make ("content", Void, s, Void)
Result.set_weight (-1)
Result.set_is_raw (True)
end
feature -- Hooks
hooks: CMS_HOOK_CORE_MANAGER
-- Manager handling hook subscriptions.
obsolete
"Use api.hooks [dec/2015]"
do
Result := api.hooks
end
feature -- Menu: change
add_to_main_menu (lnk: CMS_LINK)
obsolete
"use add_to_primary_menu [Nov/2014]"
do
add_to_primary_menu (lnk)
end
add_to_primary_menu (lnk: CMS_LINK)
do
add_to_menu (lnk, primary_menu)
end
add_to_primary_tabs (lnk: CMS_LINK)
do
add_to_menu (lnk, primary_tabs)
end
add_to_menu (lnk: CMS_LINK; m: CMS_MENU)
do
m.extend (lnk)
end
feature -- Internationalization (i18n)
translation (a_text: READABLE_STRING_GENERAL; opts: detachable CMS_API_OPTIONS): STRING_32
-- Translated text `a_text' according to expected context (lang, ...)
-- and adapt according to options eventually set by `opts'.
do
Result := api.translation (a_text, opts)
end
formatted_string (a_text: READABLE_STRING_GENERAL; args: TUPLE): STRING_32
-- Format `a_text' using arguments `args'.
--| ex: formatted_string ("hello $1, see page $title.", ["bob", "contact"] -> "hello bob, see page contact"
do
Result := api.formatted_string (a_text, args)
end
feature -- Message
add_message (a_msg: READABLE_STRING_8; a_category: detachable READABLE_STRING_8)
local
m: like message
do
m := message
if m = Void then
create m.make (a_msg.count + 9)
message := m
end
if a_category /= Void then
m.append ("")
else
m.append ("")
end
m.append (a_msg + "")
end
add_notice_message (a_msg: READABLE_STRING_8)
do
add_message (a_msg, "notice")
end
add_warning_message (a_msg: READABLE_STRING_8)
do
add_message (a_msg, "warning")
end
add_error_message (a_msg: READABLE_STRING_8)
do
add_message (a_msg, "error")
end
add_success_message (a_msg: READABLE_STRING_8)
do
add_message (a_msg, "success")
end
report_form_errors (fd: WSF_FORM_DATA)
require
has_error: not fd.is_valid
do
if attached fd.errors as errs then
across
errs as err
loop
if attached err.item as e then
if attached e.field as l_field then
if attached e.message as e_msg then
add_error_message (e_msg) --"Field [" + l_field.name + "] is invalid. " + e_msg)
else
add_error_message ("Field [" + l_field.name + "] is invalid.")
end
elseif attached e.message as e_msg then
add_error_message (e_msg)
end
end
end
end
end
message: detachable STRING_8
feature -- Theme
theme: CMS_THEME
-- Current theme
get_theme
local
l_info: CMS_THEME_INFORMATION
do
if attached setup.theme_information_location as fn then
create l_info.make (fn)
else
create l_info.make_default
end
if l_info.engine.is_case_insensitive_equal_general ("smarty") then
create {SMARTY_CMS_THEME} theme.make (setup, l_info, site_url)
else
create {MISSING_CMS_THEME} theme.make (setup, l_info, site_url)
status_code := {HTTP_STATUS_CODE}.service_unavailable
to_implement ("Check how to add the Retry-after, http://tools.ietf.org/html/rfc7231#section-6.6.4 and http://tools.ietf.org/html/rfc7231#section-7.1.3")
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
set_status_code (a_status: INTEGER)
-- Set `status_code' with `a_status'.
note
EIS: "src=eiffel:?class=HTTP_STATUS_CODE"
do
to_implement ("Feature to test if a_status is a valid status code!!!.")
status_code := a_status
ensure
status_code_set: status_code = a_status
end
feature -- Cache managment
clear_cache (a_cache_id_list: detachable ITERABLE [READABLE_STRING_GENERAL])
-- Clear caches identified by `a_cache_id_list',
-- or clear all caches if `a_cache_id_list' is Void.
do
if has_permissions (<<"clear blocks cache", "admin core caches">>) then
clear_block_caches (a_cache_id_list)
end
end
feature -- Generation
prepare (page: CMS_HTML_PAGE)
local
lnk: CMS_LINK
l_region: CMS_BLOCK_REGION
l_menu_list_prepared: ARRAYED_LIST [CMS_LINK_COMPOSITE]
l_empty_blocks: detachable ARRAYED_LIST [CMS_BLOCK]
l_block_html: STRING
do
-- Menu
create {CMS_LOCAL_LINK} lnk.make ("Home", "")
lnk.set_weight (-10)
add_to_primary_menu (lnk)
api.hooks.invoke_menu_system_alter (menu_system, Current)
if api.enabled_modules.count = 0 then
add_to_primary_menu (create {CMS_LOCAL_LINK}.make ("Install", "admin/install"))
end
-- Blocks
create l_menu_list_prepared.make (0)
get_blocks
across
regions as reg_ic
loop
l_region := reg_ic.item
across
l_region.blocks as ic
loop
if attached {CMS_MENU_BLOCK} ic.item as l_menu_block then
l_menu_list_prepared.force (l_menu_block.menu)
prepare_links (l_menu_block.menu)
if l_menu_block.menu.is_empty then
if l_empty_blocks = Void then
create l_empty_blocks.make (1)
end
l_empty_blocks.force (l_menu_block)
end
end
end
if l_empty_blocks /= Void then
across
l_empty_blocks as ic
loop
l_region.remove (ic.item)
end
l_empty_blocks := Void
end
end
-- Prepare menu not in a block.
across
menu_system as ic
loop
if not l_menu_list_prepared.has (ic.item) then
l_menu_list_prepared.force (ic.item)
prepare_links (ic.item)
end
end
l_menu_list_prepared.wipe_out -- Clear for memory purpose.
-- Sort items
across menu_system as ic loop
ic.item.sort
end
-- Values
common_prepare (page)
custom_prepare (page)
-- Cms response
api.hooks.invoke_response_alter (Current)
-- Cms values
api.hooks.invoke_value_table_alter (values, Current)
-- Predefined values
page.register_variable (page, "page") -- DO NOT REMOVE
-- Values Associated with current Execution object.
across
values as ic
loop
page.register_variable (ic.item, ic.key)
end
-- Block rendering
across
regions as reg_ic
loop
l_region := reg_ic.item
-- region blocks Already sorted.
across
l_region.blocks as ic
loop
if attached {CMS_SMARTY_TEMPLATE_BLOCK} ic.item as l_tpl_block then
-- Apply page variables to smarty block.
-- FIXME: maybe add notion of values at the CMS_BLOCK level
-- or consider a CMS_BLOCK_WITH_VALUES ...
across
page.variables as var_ic
loop
if not l_tpl_block.values.has (var_ic.key) then
-- Do not overwrite if has key.
l_tpl_block.set_value (var_ic.item, var_ic.key)
end
end
end
l_block_html := theme.block_html (ic.item)
if attached {CMS_CACHE_BLOCK} ic.item then
elseif attached block_cache (ic.item.name) as l_cache_block then
l_cache_block.block.cache.put (l_block_html)
end
page.add_to_region (l_block_html, reg_ic.item.name)
end
end
-- Additional lines in
if attached additional_page_head_lines as l_head_lines then
across
l_head_lines as hl
loop
page.head_lines.force (hl.item)
end
end
end
common_prepare (page: CMS_HTML_PAGE)
-- Common preparation for page `page'.
do
debug ("refactor_fixme")
fixme ("Fix generation common")
end
-- Information
page.set_title (title)
debug ("cms")
if title = Void then
page.set_title ({STRING_32} "CMS::" + request.path_info) --| FIXME: probably, should be removed and handled by theme.
end
end
-- Variables
page.register_variable (absolute_url ("", Void), "site_url")
page.register_variable (absolute_url ("", Void), "host") -- Same as `site_url'.
page.register_variable (request.is_https, "is_https")
if attached current_user_name (request) as l_user then
page.register_variable (l_user, "user")
end
page.register_variable (title, "site_title")
page.set_is_front (is_front)
page.set_is_https (request.is_https)
-- Variables/Misc
-- FIXME: logo .. could be a settings of theme, managed by admin front-end/database.
-- if attached logo_location as l_logo then
-- page.register_variable (l_logo, "logo")
-- end
-- Menu...
page.register_variable (horizontal_primary_menu_html, "primary_nav")
page.register_variable (horizontal_primary_tabs_html, "primary_tabs")
-- Page related
if attached page_title as l_page_title then
page.register_variable (l_page_title, "page_title")
end
end
custom_prepare (page: CMS_HTML_PAGE)
-- Common preparation for page `page' that can be redefined by descendants.
do
end
prepare_links (a_comp: CMS_LINK_COMPOSITE)
-- Update the active status recursively on `a_comp'.
local
to_remove: ARRAYED_LIST [CMS_LINK]
ln: CMS_LINK
l_comp_link: detachable CMS_LOCAL_LINK
do
if attached {CMS_LOCAL_LINK} a_comp as lnk then
l_comp_link := lnk
get_local_link_active_status (lnk)
end
if attached a_comp.items as l_items then
create to_remove.make (0)
across
l_items as ic
loop
ln := ic.item
if attached {CMS_LOCAL_LINK} ln as l_local then
get_local_link_active_status (l_local)
end
if ln.is_forbidden then
to_remove.force (ln)
else
if
(ln.is_expanded or ln.is_collapsed) and then
attached {CMS_LINK_COMPOSITE} ln as l_comp
then
prepare_links (l_comp)
end
if l_comp_link /= Void then
if ln.is_expanded or (not ln.is_expandable and ln.is_active) then
l_comp_link.set_expanded (True)
end
end
end
end
across
to_remove as ic
loop
a_comp.remove (ic.item)
end
end
if l_comp_link /= Void and then l_comp_link.is_active then
l_comp_link.set_expanded (True)
end
end
get_local_link_active_status (a_lnk: CMS_LOCAL_LINK)
-- Get `a_lnk.is_active' value according to `request' data.
local
qs: STRING
l_is_active: BOOLEAN
do
create qs.make_from_string (request.percent_encoded_path_info)
if qs.starts_with ("/") then
qs.remove_head (1)
end
l_is_active := qs.same_string (a_lnk.location)
if not l_is_active then
if attached request.query_string as l_query_string and then not l_query_string.is_empty then
qs.append_character ('?')
qs.append (l_query_string)
end
l_is_active := qs.same_string (a_lnk.location)
end
a_lnk.set_is_active (l_is_active)
a_lnk.set_is_forbidden (not has_permission_on_link (a_lnk))
end
feature -- Helpers: cms link
local_link (a_title: READABLE_STRING_GENERAL; a_location: READABLE_STRING_8): CMS_LOCAL_LINK
do
create Result.make (a_title, a_location)
end
user_local_link (u: CMS_USER; a_opt_title: detachable READABLE_STRING_GENERAL): CMS_LOCAL_LINK
do
if a_opt_title /= Void then
create Result.make (a_opt_title, user_url (u))
else
create Result.make (u.name, user_url (u))
end
end
user_html_link (u: CMS_USER): like link
do
Result := link (u.name, "user/" + u.id.out, Void)
end
user_url (u: CMS_USER): like url
require
u_with_id: u.has_id
do
Result := url ("user/" + u.id.out, Void)
end
feature -- Execution
execute
do
begin
process
terminate
end
feature {NONE} -- Execution
begin
do
end
process
deferred
end
frozen terminate
local
cms_page: CMS_HTML_PAGE
page: CMS_HTML_PAGE_RESPONSE
utf: UTF_CONVERTER
h: HTTP_HEADER
l_new_location: READABLE_STRING_8
do
if attached {READABLE_STRING_GENERAL} values.item ("optional_content_type") as l_type then
create cms_page.make_typed (utf.utf_32_string_to_utf_8_string_8 (l_type))
else
create cms_page.make
end
prepare (cms_page)
create page.make (theme.page_html (cms_page))
page.set_status_code (status_code)
h := page.header
h.put_content_length (page.html.count)
h.put_current_date
if attached redirection as l_location then
-- FIXME: find out if this is safe or not.
if l_location.has_substring ("://") then
l_new_location := l_location
else
l_new_location := absolute_url (l_location, Void)
end
-- h.put_location (l_new_location)
response.redirect_now (l_new_location)
else
h.put_header_object (header)
response.send (page)
end
on_terminated
end
on_terminated
do
end
note
copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end