diff --git a/examples/demo/modules/demo/cms_demo_module.e b/examples/demo/modules/demo/cms_demo_module.e
index 3fb7fa6..d85bf34 100644
--- a/examples/demo/modules/demo/cms_demo_module.e
+++ b/examples/demo/modules/demo/cms_demo_module.e
@@ -93,7 +93,7 @@ feature -- Hooks
block_list: ITERABLE [like {CMS_BLOCK}.name]
do
- Result := <<"demo-info">>
+ Result := <<"?demo-info">>
end
get_block_view (a_block_id: READABLE_STRING_8; a_response: CMS_RESPONSE)
@@ -103,8 +103,8 @@ feature -- Hooks
m: CMS_MENU
lnk: CMS_LOCAL_LINK
do
- if a_block_id.is_case_insensitive_equal_general ("demo-info") then
- if a_response.request.request_uri.starts_with ("/demo/") then
+ if a_block_id.same_string ("demo-info") then
+ if a_response.location.starts_with_general ("demo/") then
create m.make_with_title (a_block_id, "Demo", 2)
create lnk.make ("demo: abc", "demo/abc")
m.extend (lnk)
diff --git a/examples/demo/site/config/cms.ini b/examples/demo/site/config/cms.ini
index 9a90715..ebe4208 100644
--- a/examples/demo/site/config/cms.ini
+++ b/examples/demo/site/config/cms.ini
@@ -20,7 +20,7 @@ smtp=localhost:25
# Default is "on"
# for each module, this can be overwritten with
# module_name= on or off
-*=off
+*=all
admin=on
auth=on
basic_auth=on
@@ -33,9 +33,17 @@ openid=on
[blocks]
#navigation.region=sidebar_first
-feed__bertrandmeyer.region=content
-feed__eiffelroom.region=content
-feed__bertrandmeyer.condition=is_front
+feed.eiffel.region=feed_eiffel
+feed.eiffel.condition=is_front
+
+feed.forum.region=feed_forum
+feed.forum.condition=is_front
+
+feed.stackoverflow.region=feed_stackoverflow
+feed.stackoverflow.condition=is_front
+
+#management.condition=is_front
+#navigation.condition=is_front
[admin]
# CMS Installation, are accessible by "all", "none" or uppon "permission". (default is none)
diff --git a/examples/demo/site/config/modules/feed_aggregator/feeds.json b/examples/demo/site/config/modules/feed_aggregator/feeds.json
new file mode 100644
index 0000000..31d1230
--- /dev/null
+++ b/examples/demo/site/config/modules/feed_aggregator/feeds.json
@@ -0,0 +1,25 @@
+{
+ "ids": ["eiffel", "forum", "stackoverflow"],
+ "feeds": {
+ "eiffel": {
+ "title": "Eiffel related posts.",
+ "expiration": "21600",
+ "locations": [
+ "https://bertrandmeyer.com/feed/",
+ "https://room.eiffel.com/blog/feed"
+ ]
+ , "categories": ["Eiffel"]
+ ,"option_description": "disabled"
+ },
+ "forum": {
+ "title": "Eiffel Users Group",
+ "expiration": "43200",
+ "location": "https://groups.google.com/forum/feed/eiffel-users/msgs/atom.xml?num=15"
+ },
+ "stackoverflow": {
+ "title": "Test",
+ "expiration": "3600",
+ "location": "http://stackoverflow.com/feeds/tag?tagnames=eiffel&sort=newest"
+ }
+ }
+}
diff --git a/examples/demo/site/modules/feed_aggregator/files/css/feed_aggregator.css b/examples/demo/site/modules/feed_aggregator/files/css/feed_aggregator.css
new file mode 100644
index 0000000..abccac2
--- /dev/null
+++ b/examples/demo/site/modules/feed_aggregator/files/css/feed_aggregator.css
@@ -0,0 +1,49 @@
+div.feed ul {
+ list-style: none;
+ position: relative;
+ width: 99%;
+}
+div.feed li {
+ /* border-top: solid 1px #ddd; */
+ margin-bottom: 5px;
+}
+div.feed li a {
+ font-weight: bold;
+}
+div.feed li .date {
+ font-weight: bold;
+ font-size: small;
+}
+div.feed li .category {
+ margin-left: 20px;
+ font-size: 8px;
+ height: 9px;
+ overflow: hidden;
+ color: #999;
+}
+div.feed li .description {
+ margin-left: 20px;
+ font-size: small;
+ height: 18px;
+ overflow: hidden;
+ color: #999;
+}
+div.feed li:hover {
+ margin-bottom: 23px;
+}
+div.feed li:hover .description {
+ position: absolute;
+ height: auto;
+ overflow-y: scroll;
+ overflow-x: scroll;
+ color: #000;
+ background-color: #fff;
+ border: solid 1px #000;
+ z-index: 10;
+}
+div.feed li:hover:last-child {
+ margin-bottom: 28px;
+}
+div.feed li .description::after {
+ content: "...";
+}
diff --git a/examples/demo/site/modules/feed_aggregator/files/scss/feed_aggregator.scss b/examples/demo/site/modules/feed_aggregator/files/scss/feed_aggregator.scss
new file mode 100644
index 0000000..90b855f
--- /dev/null
+++ b/examples/demo/site/modules/feed_aggregator/files/scss/feed_aggregator.scss
@@ -0,0 +1,50 @@
+div.feed {
+ ul {
+ list-style: none;
+ position: relative;
+ width: 99%;
+ }
+ li {
+ /* border-top: solid 1px #ddd; */
+ margin-bottom: 5px;
+
+ a {
+ font-weight: bold;
+ }
+ .date {
+ font-weight: bold;
+ font-size: small;
+ }
+ .category {
+ margin-left: 20px;
+ font-size: 8px;
+ height: 9px;
+ overflow: hidden;
+ color: #999;
+ }
+ .description {
+ margin-left: 20px;
+ font-size: small;
+ height: 18px;
+ overflow: hidden;
+ color: #999;
+ }
+ &:hover {
+ margin-bottom: 23px;
+ .description {
+ position: absolute;
+ height: auto;
+ overflow-y: scroll;
+ overflow-x: scroll;
+ color: #000;
+ background-color: #fff;
+ border: solid 1px #000;
+ z-index: 10;
+ }
+ &:last-child {
+ margin-bottom: 28px;
+ }
+ }
+ .description::after { content: "..."; }
+ }
+}
diff --git a/examples/demo/site/themes/bootstrap/page.tpl b/examples/demo/site/themes/bootstrap/page.tpl
index fc0b79b..8370b05 100644
--- a/examples/demo/site/themes/bootstrap/page.tpl
+++ b/examples/demo/site/themes/bootstrap/page.tpl
@@ -62,9 +62,18 @@
{unless isempty="$page_title"}
{$page_title/}
{/unless}
{$page.region_content/}
+ {if condition="$page.is_front"}
+ {if isset="$page.region_feed_eiffel"}
+ {$page.region_feed_eiffel_users/}
+ {/if}
+ {if isset="$page.region_feed_forum"}
+ {$page.region_feed_forum/}
+ {/if}
+ {if isset="$page.region_feed_stackoverflow"}
+ {$page.region_feed_stackoverflow/}
+ {/if}
+ {/if}
-
-
diff --git a/library/configuration/src/config_reader.e b/library/configuration/src/config_reader.e
index 8d4563f..2698b35 100644
--- a/library/configuration/src/config_reader.e
+++ b/library/configuration/src/config_reader.e
@@ -33,11 +33,53 @@ feature -- Query
end
end
+ resolved_text_list_item (k: READABLE_STRING_GENERAL): detachable LIST [READABLE_STRING_32]
+ -- List of String item associated with key `k',
+ -- and expanded values to resolved variables ${varname}.
+ do
+ if attached text_list_item (k) as lst then
+ from
+ lst.start
+ until
+ lst.after
+ loop
+ lst.replace (resolved_expression (lst.item))
+ lst.forth
+ end
+ end
+ end
+
+ resolved_text_table_item (k: READABLE_STRING_GENERAL): detachable STRING_TABLE [READABLE_STRING_32]
+ -- Table of String item associated with key `k',
+ -- and expanded values to resolved variables ${varname}.
+ do
+ if attached text_table_item (k) as tb then
+ from
+ tb.start
+ until
+ tb.after
+ loop
+ tb.replace (resolved_expression (tb.item_for_iteration), tb.key_for_iteration)
+ tb.forth
+ end
+ end
+ end
+
text_item (k: READABLE_STRING_GENERAL): detachable READABLE_STRING_32
-- String item associated with key `k'.
deferred
end
+ text_list_item (k: READABLE_STRING_GENERAL): detachable LIST [READABLE_STRING_32]
+ -- List of String item associated with key `k'.
+ deferred
+ end
+
+ text_table_item (k: READABLE_STRING_GENERAL): detachable STRING_TABLE [READABLE_STRING_32]
+ -- Table of String item associated with key `k'.
+ deferred
+ end
+
integer_item (k: READABLE_STRING_GENERAL): INTEGER
-- Integer item associated with key `k'.
deferred
@@ -109,7 +151,7 @@ feature -- Duplication
end
note
- copyright: "2011-2014, Jocelyn Fiat, Eiffel Software and others"
+ copyright: "2011-2015, Jocelyn Fiat, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software
diff --git a/library/configuration/src/ini_config.e b/library/configuration/src/ini_config.e
index 37aa75b..56abea5 100644
--- a/library/configuration/src/ini_config.e
+++ b/library/configuration/src/ini_config.e
@@ -119,14 +119,47 @@ feature -- Access: Config Reader
text_item (k: READABLE_STRING_GENERAL): detachable READABLE_STRING_32
-- String item associated with key `k'.
- local
- obj: like item
do
- obj := item (k)
- if attached {READABLE_STRING_32} obj as s32 then
- Result := s32
- elseif attached {READABLE_STRING_8} obj as s then
- Result := utf.utf_8_string_8_to_escaped_string_32 (s)
+ Result := value_to_string_32 (item (k))
+ end
+
+ text_list_item (k: READABLE_STRING_GENERAL): detachable LIST [READABLE_STRING_32]
+ -- List of String item associated with key `k'.
+ do
+ if attached {LIST [READABLE_STRING_8]} item (k) as l_list then
+ create {ARRAYED_LIST [READABLE_STRING_32]} Result.make (l_list.count)
+ Result.compare_objects
+ across
+ l_list as ic
+ until
+ Result = Void
+ loop
+ if attached value_to_string_32 (ic.item) as s32 then
+ Result.force (s32)
+ else
+ Result := Void
+ end
+ end
+ end
+ end
+
+ text_table_item (k: READABLE_STRING_GENERAL): detachable STRING_TABLE [READABLE_STRING_32]
+ -- Table of String item associated with key `k'.
+ do
+ if attached {STRING_TABLE [READABLE_STRING_8]} item (k) as l_list then
+ create {STRING_TABLE [READABLE_STRING_32]} Result.make (l_list.count)
+ Result.compare_objects
+ across
+ l_list as ic
+ until
+ Result = Void
+ loop
+ if attached value_to_string_32 (ic.item) as s32 then
+ Result.force (s32, ic.key)
+ else
+ Result := Void
+ end
+ end
end
end
@@ -226,6 +259,15 @@ feature -- Access
feature {NONE} -- Implementation
+ value_to_string_32 (obj: detachable ANY): detachable STRING_32
+ do
+ if attached {READABLE_STRING_32} obj as s32 then
+ Result := s32
+ elseif attached {READABLE_STRING_8} obj as s then
+ Result := utf.utf_8_string_8_to_escaped_string_32 (s)
+ end
+ end
+
item_from_values (a_values: STRING_TABLE [ANY]; k: READABLE_STRING_GENERAL): detachable ANY
local
i,j: INTEGER
@@ -460,7 +502,7 @@ feature {NONE} -- Implementation
invariant
note
- copyright: "2011-2014, Jocelyn Fiat, Eiffel Software and others"
+ copyright: "2011-2015, Jocelyn Fiat, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software
diff --git a/library/configuration/src/json_config.e b/library/configuration/src/json_config.e
index 4333d26..54fe3d3 100644
--- a/library/configuration/src/json_config.e
+++ b/library/configuration/src/json_config.e
@@ -63,10 +63,46 @@ feature -- Access: Config Reader
text_item (k: READABLE_STRING_GENERAL): detachable READABLE_STRING_32
-- String item associated with query `k'.
do
- if attached {JSON_STRING} item (k) as l_string then
- Result := l_string.unescaped_string_32
- elseif attached {JSON_NUMBER} item (k) as l_number then
- Result := l_number.item
+ Result := value_to_string_32 (item (k))
+ end
+
+ text_list_item (k: READABLE_STRING_GENERAL): detachable LIST [READABLE_STRING_32]
+ -- List of String item associated with key `k'.
+ do
+ if attached {JSON_ARRAY} item (k) as l_array then
+ create {ARRAYED_LIST [READABLE_STRING_32]} Result.make (l_array.count)
+ Result.compare_objects
+ across
+ l_array as ic
+ until
+ Result = Void
+ loop
+ if attached value_to_string_32 (ic.item) as s32 then
+ Result.force (s32)
+ else
+ Result := Void
+ end
+ end
+ end
+ end
+
+ text_table_item (k: READABLE_STRING_GENERAL): detachable STRING_TABLE [READABLE_STRING_32]
+ -- Table of String item associated with key `k'.
+ do
+ if attached {JSON_OBJECT} item (k) as obj then
+ create {STRING_TABLE [READABLE_STRING_32]} Result.make (obj.count)
+ Result.compare_objects
+ across
+ obj as ic
+ until
+ Result = Void
+ loop
+ if attached value_to_string_32 (ic.item) as s32 then
+ Result.force (s32, ic.key.item)
+ else
+ Result := Void
+ end
+ end
end
end
@@ -105,6 +141,15 @@ feature -- Access
feature {NONE} -- Implementation
+ value_to_string_32 (v: detachable ANY): detachable STRING_32
+ do
+ if attached {JSON_STRING} v as l_string then
+ Result := l_string.unescaped_string_32
+ elseif attached {JSON_NUMBER} v as l_number then
+ Result := l_number.item
+ end
+ end
+
object_json_value (a_object: JSON_OBJECT; a_query: READABLE_STRING_32): detachable JSON_VALUE
-- Item associated with query `a_query' from object `a_object' if any.
local
@@ -163,7 +208,7 @@ feature {NONE} -- JSON
end
note
- copyright: "2011-2014, Jocelyn Fiat, Eiffel Software and others"
+ copyright: "2011-2015, Jocelyn Fiat, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software
diff --git a/library/configuration/tests/test_config_reader_set.e b/library/configuration/tests/test_config_reader_set.e
index b975fe5..2f90530 100644
--- a/library/configuration/tests/test_config_reader_set.e
+++ b/library/configuration/tests/test_config_reader_set.e
@@ -21,6 +21,18 @@ feature -- Test
create {INI_CONFIG} cfg.make_from_string ("[
foo = bar
+ collection[] = a
+ collection[] = b
+ collection[] = c
+ collection[] = 1
+ collection[] = 2
+ collection[] = 3
+
+ table[a] = 1
+ table[b] = 2
+ table[c] = 3
+ table[d] = test
+
[first]
abc = 1
def = and so on
@@ -58,6 +70,21 @@ feature -- Test
assert ("has_item (second.is)", cfg.has_item ("second.is"))
assert ("item (second.is)", attached cfg.text_item ("second.is") as v and then v.same_string_general ("2"))
+ assert ("has_item (collection)", cfg.has_item ("collection"))
+ assert ("item (collection)", attached cfg.text_list_item ("collection") as lst and then (
+ lst.has ("a") and lst.has ("b") and lst.has ("c") and lst.has ("1") and lst.has ("2") and lst.has ("3")
+ )
+ )
+
+ assert ("has_item (table)", cfg.has_item ("table"))
+ assert ("item (table)", attached cfg.text_table_item ("table") as tb and then (
+ tb.item ("a") ~ {STRING_32} "1" and
+ tb.item ("b") ~ {STRING_32} "2" and
+ tb.item ("c") ~ {STRING_32} "3" and
+ tb.item ("d") ~ {STRING_32} "test"
+ )
+ )
+
if attached cfg.sub_config ("second") as cfg_second then
assert ("has_item (is)", cfg_second.has_item ("is"))
assert ("item (is)", attached cfg_second.text_item ("is") as v and then v.same_string_general ("2"))
@@ -141,7 +168,9 @@ feature -- Test
"is": 2,
"the": 3,
"end": 4
- }
+ },
+ "collection": ["a", "b", "c", 1, 2, 3],
+ "table": { "a": 1, "b": 2, "c": 3, "d" : "test" }
}
]")
@@ -164,6 +193,21 @@ feature -- Test
assert ("item (second.the)", attached cfg.text_item ("second.the") as v and then v.same_string_general ("3"))
assert ("item (second.end)", attached cfg.text_item ("second.end") as v and then v.same_string_general ("4"))
+ assert ("has_item (collection)", cfg.has_item ("collection"))
+ assert ("item (collection)", attached cfg.text_list_item ("collection") as lst and then (
+ lst.has ("a") and lst.has ("b") and lst.has ("c") and lst.has ("1") and lst.has ("2") and lst.has ("3")
+ )
+ )
+
+ assert ("has_item (table)", cfg.has_item ("table"))
+ assert ("item (table)", attached cfg.text_table_item ("table") as tb and then (
+ tb.item ("a") ~ {STRING_32} "1" and
+ tb.item ("b") ~ {STRING_32} "2" and
+ tb.item ("c") ~ {STRING_32} "3" and
+ tb.item ("d") ~ {STRING_32} "test"
+ )
+ )
+
if attached cfg.sub_config ("second") as cfg_second then
assert ("has_item (is)", cfg_second.has_item ("is"))
assert ("item (is)", attached cfg_second.text_item ("is") as v and then v.same_string_general ("2"))
diff --git a/modules/auth/cms_authentication_module.e b/modules/auth/cms_authentication_module.e
index 2a32c2d..21e59b1 100644
--- a/modules/auth/cms_authentication_module.e
+++ b/modules/auth/cms_authentication_module.e
@@ -521,26 +521,26 @@ feature {NONE} -- Helpers
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)
+-- 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.put_required_block (l_tpl_block, "content")
+-- else
+-- debug ("cms")
+-- a_response.add_warning_message ("Error with block [" + a_block_id + "]")
-- 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
+-- end
+-- end
get_block_view_register (a_block_id: READABLE_STRING_8; a_response: CMS_RESPONSE)
do
@@ -579,7 +579,6 @@ feature {NONE} -- Block views
end
end
-
get_block_view_reactivate (a_block_id: READABLE_STRING_8; a_response: CMS_RESPONSE)
do
if a_response.request.is_get_request_method then
diff --git a/modules/feed_aggregator/feed_aggregation.e b/modules/feed_aggregator/feed_aggregation.e
index 1688089..debbad5 100644
--- a/modules/feed_aggregator/feed_aggregation.e
+++ b/modules/feed_aggregator/feed_aggregation.e
@@ -15,6 +15,8 @@ feature {NONE} -- Initialization
do
create name.make_from_string_general (a_name)
create {ARRAYED_LIST [READABLE_STRING_8]} locations.make (0)
+ expiration := 60*60
+ description_enabled := True
end
feature -- Access
@@ -22,12 +24,23 @@ feature -- Access
name: IMMUTABLE_STRING_32
-- Associated name.
+ expiration: INTEGER
+ -- Suggested expiration time in seconds (default: 1 hour).
+ -- If negative then never expires.
+
description: detachable IMMUTABLE_STRING_32
-- Optional description.
locations: LIST [READABLE_STRING_8]
-- List of feed location aggregated into current.
+ included_categories: detachable LIST [READABLE_STRING_32]
+ -- Optional categories to filter.
+ -- If Void, include any.
+
+ description_enabled: BOOLEAN
+ -- Display description?
+
feature -- Element change
set_description (a_desc: detachable READABLE_STRING_GENERAL)
@@ -39,4 +52,54 @@ feature -- Element change
end
end
+ set_expiration (nb_seconds: INTEGER)
+ -- Set `expiration' to `nb_seconds'.
+ do
+ expiration := nb_seconds
+ end
+
+ set_description_enabled (b: BOOLEAN)
+ -- Set `description_enabled' to `b'.
+ do
+ description_enabled := b
+ end
+
+ reset_categories
+ do
+ included_categories := Void
+ end
+
+ include_category (a_cat: READABLE_STRING_GENERAL)
+ local
+ lst: like included_categories
+ s32: STRING_32
+ do
+ lst := included_categories
+ if lst = Void then
+ create {ARRAYED_LIST [READABLE_STRING_32]} lst.make (1)
+ included_categories := lst
+ lst.compare_objects
+ end
+ s32 := a_cat.to_string_32
+ if not lst.has (s32) then
+ lst.force (s32)
+ end
+ end
+
+feature -- Status report
+
+ is_included (e: FEED_ITEM): BOOLEAN
+ do
+ Result := True
+ if attached e.categories as e_cats then
+ if attached included_categories as lst then
+ Result := across lst as ic some
+ across e_cats as e_ic some
+ e_ic.item.same_string (ic.item)
+ end
+ end
+ end
+ end
+ end
+
end
diff --git a/modules/feed_aggregator/feed_aggregator-safe.ecf b/modules/feed_aggregator/feed_aggregator-safe.ecf
index 6baa236..c43b3e8 100644
--- a/modules/feed_aggregator/feed_aggregator-safe.ecf
+++ b/modules/feed_aggregator/feed_aggregator-safe.ecf
@@ -8,7 +8,9 @@
+
+
diff --git a/modules/feed_aggregator/feed_aggregator_api.e b/modules/feed_aggregator/feed_aggregator_api.e
index 3928a77..a3a97d9 100644
--- a/modules/feed_aggregator/feed_aggregator_api.e
+++ b/modules/feed_aggregator/feed_aggregator_api.e
@@ -18,15 +18,65 @@ feature -- Access
-- List of feed aggregations.
local
agg: FEED_AGGREGATION
+ l_feed_id: READABLE_STRING_32
+ l_title: detachable READABLE_STRING_GENERAL
+ l_location_list: detachable LIST [READABLE_STRING_32]
+ utf: UTF_CONVERTER
+ l_table: like internal_aggregations
do
- create Result.make (2)
- create agg.make ("Blog from Bertrand Meyer")
- agg.locations.force ("https://bertrandmeyer.com/category/computer-science/feed/")
- Result.force (agg, "bertrandmeyer")
-
- create agg.make ("Eiffel Room")
- agg.locations.force ("https://room.eiffel.com/recent_changes/feed")
- Result.force (agg, "eiffelroom")
+ l_table := internal_aggregations
+ if l_table /= Void then
+ Result := l_table
+ else
+ create Result.make (0)
+ internal_aggregations := Result
+ if attached cms_api.module_configuration_by_name ({FEED_AGGREGATOR_MODULE}.name, "feeds") as cfg then
+ if attached cfg.text_list_item ("ids") as l_ids then
+ across
+ l_ids as ic
+ loop
+ l_feed_id := ic.item
+ l_location_list := cfg.text_list_item ({STRING_32} "feeds." + l_feed_id + ".locations")
+ if
+ attached cfg.text_item ({STRING_32} "feeds." + l_feed_id + ".location") as l_location
+ then
+ if l_location_list = Void then
+ create {ARRAYED_LIST [READABLE_STRING_32]} l_location_list.make (1)
+ end
+ l_location_list.force (l_location)
+ end
+ if l_location_list /= Void and then not l_location_list.is_empty then
+ l_title := cfg.text_item ({STRING_32} "feeds." + l_feed_id + ".title")
+ if l_title = Void then
+ l_title := l_feed_id
+ end
+ create agg.make (l_title)
+ if attached cfg.text_item ({STRING_32} "feeds." + l_feed_id + ".expiration") as l_expiration then
+ if l_expiration.is_integer then
+ agg.set_expiration (l_expiration.to_integer)
+ end
+ end
+ if attached cfg.text_item ({STRING_32} "feeds." + l_feed_id + ".option_description") as l_description_opt then
+ agg.set_description_enabled (not l_description_opt.is_case_insensitive_equal_general ("disabled"))
+ end
+ across
+ l_location_list as loc_ic
+ loop
+ agg.locations.force (utf.utf_32_string_to_utf_8_string_8 (loc_ic.item))
+ end
+ Result.force (agg, l_feed_id)
+ if attached cfg.text_list_item ({STRING_32} "feeds." + l_feed_id + ".categories") as l_cats then
+ across
+ l_cats as cats_ic
+ loop
+ agg.include_category (cats_ic.item)
+ end
+ end
+ end
+ end
+ end
+ end
+ end
end
aggregation (a_name: READABLE_STRING_GENERAL): detachable FEED_AGGREGATION
@@ -36,4 +86,56 @@ feature -- Access
end
end
+feature {NONE} -- Access: implementation
+
+ internal_aggregations: detachable like aggregations
+ -- Cache value for `aggregations'.
+
+feature -- Operation
+
+ feed (a_location: READABLE_STRING_8): detachable FEED
+ local
+ fac: FEED_DEFAULT_PARSERS
+ ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT
+ do
+ create fac
+ if attached new_http_client_session (a_location).get ("", ctx) as res then
+ if attached res.body as l_content then
+ Result := fac.feed_from_string (l_content)
+ end
+ end
+ end
+
+ aggregation_feed (agg: FEED_AGGREGATION): detachable FEED
+ -- Feed from aggregation `agg'.
+ local
+ fac: FEED_DEFAULT_PARSERS
+ f: detachable FEED
+ do
+ create fac
+ across
+ agg.locations as ic
+ loop
+ if attached new_http_client_session (ic.item).get ("", Void).body as res then
+ f := fac.feed_from_string (res)
+ if Result /= Void then
+ if f /= Void then
+ Result := Result + f
+ end
+ else
+ Result := f
+ end
+ end
+ end
+ end
+
+ new_http_client_session (a_url: READABLE_STRING_8): HTTP_CLIENT_SESSION
+ local
+ cl: LIBCURL_HTTP_CLIENT
+ do
+ create cl.make
+ Result := cl.new_session (a_url)
+ Result.set_is_insecure (True)
+ end
+
end
diff --git a/modules/feed_aggregator/feed_aggregator_module.e b/modules/feed_aggregator/feed_aggregator_module.e
index c510cb8..2bcdce4 100644
--- a/modules/feed_aggregator/feed_aggregator_module.e
+++ b/modules/feed_aggregator/feed_aggregator_module.e
@@ -21,6 +21,8 @@ inherit
CMS_HOOK_RESPONSE_ALTER
+ CMS_HOOK_MENU_SYSTEM_ALTER
+
create
make
@@ -63,8 +65,112 @@ feature -- Access: router
setup_router (a_router: WSF_ROUTER; a_api: CMS_API)
--
+ local
+ h: WSF_URI_TEMPLATE_HANDLER
do
--- a_router.handle ("/admin/feed_aggregator/", create {WSF_URI_AGENT_HANDLER}.make (agent handle_feed_aggregator_admin (a_api, ?, ?)), a_router.methods_head_get_post)
+ a_router.handle ("/admin/feed_aggregator/", create {WSF_URI_AGENT_HANDLER}.make (agent handle_feed_aggregator_admin (a_api, ?, ?)), a_router.methods_head_get_post)
+ create {WSF_URI_TEMPLATE_AGENT_HANDLER} h.make (agent handle_feed_aggregation (a_api, ?, ?))
+ a_router.handle ("/feed_aggregation/", h, a_router.methods_head_get)
+ a_router.handle ("/feed_aggregation/{feed_id}", h, a_router.methods_head_get)
+ end
+
+feature -- Handle
+
+ handle_feed_aggregator_admin (a_api: CMS_API; req: WSF_REQUEST; res: WSF_RESPONSE)
+ local
+ nyi: NOT_IMPLEMENTED_ERROR_CMS_RESPONSE
+ do
+ create nyi.make (req, res, a_api)
+ nyi.execute
+ end
+
+ handle_feed_aggregation (a_api: CMS_API; req: WSF_REQUEST; res: WSF_RESPONSE)
+ local
+ r: CMS_RESPONSE
+ s: STRING
+ nb: INTEGER
+ do
+ if attached {WSF_STRING} req.query_parameter ("size") as p_size and then p_size.is_integer then
+ nb := p_size.integer_value
+ else
+ nb := -1
+ end
+ create {GENERIC_VIEW_CMS_RESPONSE} r.make (req, res, a_api)
+ if attached {WSF_STRING} req.path_parameter ("feed_id") as p_feed_id then
+ if attached feed_aggregation (p_feed_id.value) as l_agg then
+ create s.make_empty
+ s.append ("")
+ s.append (r.html_encoded (l_agg.name))
+ s.append ("
")
+ if attached l_agg.included_categories as l_categories then
+ s.append ("")
+ across
+ l_categories as cats_ic
+ loop
+ s.append (" [")
+ s.append (r.html_encoded (cats_ic.item))
+ s.append ("]")
+ end
+ s.append ("")
+ end
+ if attached l_agg.description as l_desc and then l_desc.is_valid_as_string_8 then
+ s.append ("")
+ s.append (l_desc.as_string_8)
+ s.append ("
")
+ end
+ s.append ("")
+
+ if attached feed_to_html (p_feed_id.value, nb, True, r) as l_html then
+ s.append (l_html)
+ end
+ r.set_main_content (s)
+ else
+ create {NOT_FOUND_ERROR_CMS_RESPONSE} r.make (req, res, a_api)
+ end
+ else
+ if attached feed_aggregator_api as l_feed_agg_api then
+ create s.make_empty
+ across
+ l_feed_agg_api.aggregations as ic
+ loop
+ s.append ("")
+ s.append (r.link (ic.key, "feed_aggregation/" + r.url_encoded (ic.key), Void))
+ if attached ic.item.included_categories as l_categories then
+ s.append ("")
+ across
+ l_categories as cats_ic
+ loop
+ s.append (" [")
+ s.append (r.html_encoded (cats_ic.item))
+ s.append ("]")
+ end
+ s.append ("")
+ end
+ if attached ic.item.description as l_desc then
+ if l_desc.is_valid_as_string_8 then
+ s.append ("")
+ s.append (l_desc.as_string_8)
+ s.append ("
")
+ end
+ end
+ s.append ("")
+ end
+ r.set_main_content (s)
+ else
+ create {BAD_REQUEST_ERROR_CMS_RESPONSE} r.make (req, res, a_api)
+ end
+ end
+ r.execute
end
feature -- Hooks configuration
@@ -74,6 +180,7 @@ feature -- Hooks configuration
do
a_response.hooks.subscribe_to_block_hook (Current)
a_response.hooks.subscribe_to_response_alter_hook (Current)
+ a_response.hooks.subscribe_to_menu_system_alter_hook (Current)
end
feature -- Hook
@@ -82,14 +189,18 @@ feature -- Hook
-- List of block names, managed by current object.
local
res: ARRAYED_LIST [like {CMS_BLOCK}.name]
+ l_aggs: HASH_TABLE [FEED_AGGREGATION, STRING_8]
do
- create res.make (5)
if attached feed_aggregator_api as l_feed_api then
+ l_aggs := l_feed_api.aggregations
+ create res.make (l_aggs.count)
across
- l_feed_api.aggregations as ic
+ l_aggs as ic
loop
- res.force ("feed__" + ic.key)
+ res.force ("?feed." + ic.key)
end
+ else
+ create res.make (0)
end
Result := res
end
@@ -97,50 +208,178 @@ feature -- Hook
get_block_view (a_block_id: READABLE_STRING_8; a_response: CMS_RESPONSE)
-- Get block object identified by `a_block_id' and associate with `a_response'.
local
- i: INTEGER
s: READABLE_STRING_8
b: CMS_CONTENT_BLOCK
- l_content: STRING
pref: STRING
do
if attached feed_aggregator_api as l_feed_api then
- pref := "feed__"
+ pref := "feed."
if a_block_id.starts_with (pref) then
s := a_block_id.substring (pref.count + 1, a_block_id.count)
else
s := a_block_id
end
- if attached l_feed_api.aggregation (s) as l_agg then
- create l_content.make_empty
- if attached l_agg.description as l_desc then
- l_content.append_string_general (l_desc)
- l_content.append_character ('%N')
- l_content.append_character ('%N')
- end
- across
- l_agg.locations as ic
- loop
- l_content.append ("%T-" + ic.item)
- l_content.append_character ('%N')
- end
- create b.make (a_block_id, l_agg.name, l_content, Void)
- a_response.add_block (b, Void)
+ if attached feed_to_html (s, 5, True, a_response) as l_content then
+ create b.make (a_block_id, Void, l_content, Void)
+ b.set_is_raw (True)
+ a_response.add_block (b, "feed_" + s)
end
end
end
+ feed_aggregation (a_feed_id: READABLE_STRING_GENERAL): detachable FEED_AGGREGATION
+ do
+ if attached feed_aggregator_api as l_feed_api then
+ Result := l_feed_api.aggregation (a_feed_id)
+ end
+ end
+
+ feed_to_html (a_feed_id: READABLE_STRING_GENERAL; a_count: INTEGER; with_feed_info: BOOLEAN; a_response: CMS_RESPONSE): detachable STRING
+ local
+ nb: INTEGER
+ i: INTEGER
+ e: FEED_ITEM
+ l_cache: CMS_FILE_STRING_8_CACHE
+ lnk: detachable FEED_LINK
+ l_today: DATE
+ do
+ if attached feed_aggregator_api as l_feed_api then
+ if attached l_feed_api.aggregation (a_feed_id) as l_agg then
+ create l_cache.make (a_response.api.files_location.extended (".cache").extended (name).extended ("feed__" + a_feed_id + "__" + a_count.out + "_" + with_feed_info.out))
+ Result := l_cache.item
+ if Result = Void or l_cache.expired (Void, l_agg.expiration) then
+ create l_today.make_now_utc
+ create Result.make_from_string ("")
+ Result.append ("")
+ if with_feed_info then
+ if attached l_agg.description as l_desc then
+ Result.append ("
")
+ Result.append_string_general (l_desc)
+ Result.append ("
")
+ end
+ end
+ Result.append ("
")
+ if attached l_feed_api.aggregation_feed (l_agg) as l_feed then
+ nb := a_count
+ across
+ l_feed as f_ic
+ until
+ nb = 0 -- If `a_count' < 0 , no limit.
+ loop
+ e := f_ic.item
+ if l_agg.is_included (e) then
+ nb := nb - 1
+ Result.append ("- ")
+ lnk := e.link
+ if attached e.date as l_date then
+ Result.append ("
")
+ append_date_time_to (l_date, l_today, Result)
+ Result.append ("
")
+ end
+ if lnk /= Void then
+ Result.append ("")
+ else
+ check has_link: False end
+ Result.append ("")
+ end
+ Result.append (a_response.html_encoded (e.title))
+ Result.append ("")
+ debug
+ if attached e.categories as l_categories and then not l_categories.is_empty then
+ Result.append ("")
+ across
+ l_categories as cats_ic
+ loop
+ Result.append (a_response.html_encoded (cats_ic.item))
+ Result.append (" ")
+ end
+ Result.append ("
")
+ end
+ end
+ if
+ l_agg.description_enabled and then
+ attached e.description as l_entry_desc
+ then
+ if l_entry_desc.is_valid_as_string_8 then
+ Result.append ("")
+ Result.append (l_entry_desc.as_string_8)
+ Result.append ("
")
+ else
+ check is_html: False end
+ end
+ end
+ Result.append (" ")
+ end
+ end
+ end
+ Result.append_string ("")
+ Result.append_string (a_response.link ("more ...", "feed_aggregation/" + a_response.url_encoded (a_feed_id), Void))
+ Result.append_string ("")
+
+ Result.append ("
")
+
+
+ Result.append ("
%N")
+ l_cache.put (Result)
+ end
+ end
+ end
+ end
+
+ append_date_time_to (dt: DATE_TIME; a_today: DATE; a_output: STRING_GENERAL)
+ do
+ if dt.year /= a_today.year then
+ a_output.append (dt.year.out)
+ a_output.append (",")
+ end
+ a_output.append (" ")
+ append_month_mmm_to (dt.month, a_output)
+ a_output.append (" ")
+ if dt.day < 10 then
+ a_output.append ("0")
+ end
+ a_output.append (dt.day.out)
+ end
+
+ append_month_mmm_to (m: INTEGER; s: STRING_GENERAL)
+ require
+ 1 <= m and m <= 12
+ do
+ inspect m
+ when 1 then s.append ("Jan")
+ when 2 then s.append ("Feb")
+ when 3 then s.append ("Mar")
+ when 4 then s.append ("Apr")
+ when 5 then s.append ("May")
+ when 6 then s.append ("Jun")
+ when 7 then s.append ("Jul")
+ when 8 then s.append ("Aug")
+ when 9 then s.append ("Sep")
+ when 10 then s.append ("Oct")
+ when 11 then s.append ("Nov")
+ when 12 then s.append ("Dec")
+ else
+ -- Error
+ end
+ end
+
feature -- Hook
response_alter (a_response: CMS_RESPONSE)
do
--- a_response.add_additional_head_line ("[
---
--- ]", True)
+ a_response.add_style (a_response.url ("/module/" + name + "/files/css/feed_aggregator.css", Void), Void)
+ 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'.
+ do
+ a_menu_system.navigation_menu.extend (create {CMS_LOCAL_LINK}.make ("Feeds", "feed_aggregation/"))
+ if a_response.has_permission ("manage feed aggregator") then
+ a_menu_system.management_menu.extend (create {CMS_LOCAL_LINK}.make ("Feeds (admin)", "admin/feed_aggregator/"))
+ end
end
end
diff --git a/modules/feed_aggregator/site/files/css/feed_aggregator.css b/modules/feed_aggregator/site/files/css/feed_aggregator.css
new file mode 100644
index 0000000..abccac2
--- /dev/null
+++ b/modules/feed_aggregator/site/files/css/feed_aggregator.css
@@ -0,0 +1,49 @@
+div.feed ul {
+ list-style: none;
+ position: relative;
+ width: 99%;
+}
+div.feed li {
+ /* border-top: solid 1px #ddd; */
+ margin-bottom: 5px;
+}
+div.feed li a {
+ font-weight: bold;
+}
+div.feed li .date {
+ font-weight: bold;
+ font-size: small;
+}
+div.feed li .category {
+ margin-left: 20px;
+ font-size: 8px;
+ height: 9px;
+ overflow: hidden;
+ color: #999;
+}
+div.feed li .description {
+ margin-left: 20px;
+ font-size: small;
+ height: 18px;
+ overflow: hidden;
+ color: #999;
+}
+div.feed li:hover {
+ margin-bottom: 23px;
+}
+div.feed li:hover .description {
+ position: absolute;
+ height: auto;
+ overflow-y: scroll;
+ overflow-x: scroll;
+ color: #000;
+ background-color: #fff;
+ border: solid 1px #000;
+ z-index: 10;
+}
+div.feed li:hover:last-child {
+ margin-bottom: 28px;
+}
+div.feed li .description::after {
+ content: "...";
+}
diff --git a/modules/feed_aggregator/site/files/scss/feed_aggregator.scss b/modules/feed_aggregator/site/files/scss/feed_aggregator.scss
new file mode 100644
index 0000000..90b855f
--- /dev/null
+++ b/modules/feed_aggregator/site/files/scss/feed_aggregator.scss
@@ -0,0 +1,50 @@
+div.feed {
+ ul {
+ list-style: none;
+ position: relative;
+ width: 99%;
+ }
+ li {
+ /* border-top: solid 1px #ddd; */
+ margin-bottom: 5px;
+
+ a {
+ font-weight: bold;
+ }
+ .date {
+ font-weight: bold;
+ font-size: small;
+ }
+ .category {
+ margin-left: 20px;
+ font-size: 8px;
+ height: 9px;
+ overflow: hidden;
+ color: #999;
+ }
+ .description {
+ margin-left: 20px;
+ font-size: small;
+ height: 18px;
+ overflow: hidden;
+ color: #999;
+ }
+ &:hover {
+ margin-bottom: 23px;
+ .description {
+ position: absolute;
+ height: auto;
+ overflow-y: scroll;
+ overflow-x: scroll;
+ color: #000;
+ background-color: #fff;
+ border: solid 1px #000;
+ z-index: 10;
+ }
+ &:last-child {
+ margin-bottom: 28px;
+ }
+ }
+ .description::after { content: "..."; }
+ }
+}
diff --git a/modules/node/cms_node_module.e b/modules/node/cms_node_module.e
index f4f6e79..1e23a28 100644
--- a/modules/node/cms_node_module.e
+++ b/modules/node/cms_node_module.e
@@ -244,7 +244,7 @@ feature -- Hooks
block_list: ITERABLE [like {CMS_BLOCK}.name]
--
do
- Result := <<"node-info">>
+ Result := <<"?node-info">>
end
get_block_view (a_block_id: READABLE_STRING_8; a_response: CMS_RESPONSE)
diff --git a/src/cache/cms_cache.e b/src/cache/cms_cache.e
new file mode 100644
index 0000000..b01f6af
--- /dev/null
+++ b/src/cache/cms_cache.e
@@ -0,0 +1,94 @@
+note
+ description: "Abstract interface for cache of value conforming to formal {G}."
+ date: "$Date: 2014-12-03 16:12:08 +0100 (mer., 03 déc. 2014) $"
+ revision: "$Revision: 96232 $"
+
+deferred class
+ CMS_CACHE [G -> ANY]
+
+feature -- Status report
+
+ exists: BOOLEAN
+ -- Do associated cache file exists?
+ deferred
+ end
+
+ expired (a_reference_date: detachable DATE_TIME; a_duration_in_seconds: INTEGER): BOOLEAN
+ -- Is associated cached item expired?
+ -- If `a_reference_date' is attached, cache is expired if `a_reference' is more recent than cached item.
+ local
+ d1, d2: DATE_TIME
+ do
+ if exists then
+ if
+ a_reference_date /= Void and then
+ a_reference_date > cache_date_time
+ then
+ Result := True
+ else
+ if a_duration_in_seconds = -1 then
+ Result := False -- Never expires
+ elseif a_duration_in_seconds = 0 then
+ Result := True -- Always expires
+ elseif a_duration_in_seconds > 0 then
+ d1 := cache_date_time
+ d2 := current_date_time
+ d2.second_add (- a_duration_in_seconds) --| do not modify `cache_date_time'
+ Result := d2 > d1 -- cached date + duration is older than current date
+ else
+ -- Invalid expiration value
+ -- thus always expired.
+ Result := True
+ end
+ end
+ else
+ Result := True
+ end
+ end
+
+feature -- Access
+
+ item: detachable G
+ -- Value from the cache.
+ deferred
+ end
+
+ cache_date_time: DATE_TIME
+ -- Date time for current cache if exists.
+ -- Note: it may be UTC or not , depending on cache type.
+ deferred
+ end
+
+ cache_duration_in_seconds: INTEGER_64
+ -- Number of seconds since cache was set.
+ require
+ exists: exists
+ local
+ d1, d2: DATE_TIME
+ do
+ d1 := cache_date_time
+ d2 := current_date_time
+ Result := d2.relative_duration (d1).seconds_count
+ end
+
+ current_date_time: DATE_TIME
+ -- Current date time for relative duration with cache_date_time.
+ deferred
+ end
+
+feature -- Element change
+
+ delete
+ -- Remove cache.
+ deferred
+ end
+
+ put (g: G)
+ -- Put `g' into cache.
+ deferred
+ 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
diff --git a/src/cache/cms_file_cache.e b/src/cache/cms_file_cache.e
new file mode 100644
index 0000000..0ed4697
--- /dev/null
+++ b/src/cache/cms_file_cache.e
@@ -0,0 +1,150 @@
+note
+ description: "Cache using a local file."
+ date: "$Date: 2015-09-24 18:24:06 +0200 (jeu., 24 sept. 2015) $"
+ revision: "$Revision: 97926 $"
+
+deferred class
+ CMS_FILE_CACHE [G -> ANY]
+
+inherit
+ CMS_CACHE [G]
+
+feature {NONE} -- Initialization
+
+ make (a_cache_filename: PATH)
+ do
+ path := a_cache_filename
+ end
+
+ path: PATH
+
+feature -- Status report
+
+ exists: BOOLEAN
+ -- Do associated cache file exists?
+ local
+ ut: FILE_UTILITIES
+ do
+ Result := ut.file_path_exists (path)
+ end
+
+feature -- Access
+
+ cache_date_time: DATE_TIME
+ --
+ local
+ f: RAW_FILE
+ do
+ create f.make_with_path (path)
+ if f.exists then
+ Result := utc_file_date_time (f)
+ else
+ create Result.make_now_utc
+ end
+ end
+
+ current_date_time: DATE_TIME
+ --
+ do
+ -- UTC, since `cache_date_time' is UTC!
+ create Result.make_now_utc
+ end
+
+ file_size: INTEGER
+ -- Associated file size.
+ require
+ exists: exists
+ local
+ f: RAW_FILE
+ do
+ create f.make_with_path (path)
+ if f.exists and then f.is_access_readable then
+ Result := f.count
+ end
+ end
+
+ item: detachable G
+ local
+ f: RAW_FILE
+ retried: BOOLEAN
+ do
+ if not retried then
+ create f.make_with_path (path)
+ if f.exists and then f.is_access_readable then
+ f.open_read
+ Result := file_to_item (f)
+ f.close
+ end
+ end
+ rescue
+ retried := True
+ retry
+ end
+
+feature -- Element change
+
+ delete
+ --
+ local
+ f: RAW_FILE
+ retried: BOOLEAN
+ do
+ if not retried then
+ create f.make_with_path (path)
+ -- Create recursively parent directory if it does not exists.
+ if f.exists and then f.is_access_writable then
+ f.delete
+ end
+ end
+ rescue
+ retried := True
+ retry
+ end
+
+ put (g: G)
+ --
+ local
+ f: RAW_FILE
+ d: DIRECTORY
+ do
+ create f.make_with_path (path)
+ -- Create recursively parent directory if it does not exists.
+ create d.make_with_path (path.parent)
+ if not d.exists then
+ d.recursive_create_dir
+ end
+ if not f.exists or else f.is_access_writable then
+ f.open_write
+ item_to_file (g, f)
+ f.close
+ end
+ end
+
+feature -- Helpers
+
+ utc_file_date_time (f: FILE): DATE_TIME
+ -- Last change date for file `f'.
+ require
+ f.exists
+ do
+ create Result.make_from_epoch (f.date.as_integer_32)
+ end
+
+feature {NONE} -- Implementation
+
+ file_to_item (f: FILE): detachable G
+ require
+ is_open_write: f.is_open_read
+ deferred
+ end
+
+ item_to_file (g: G; f: FILE)
+ require
+ is_open_write: f.is_open_write
+ deferred
+ 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
diff --git a/src/cache/cms_file_object_cache.e b/src/cache/cms_file_object_cache.e
new file mode 100644
index 0000000..e32883a
--- /dev/null
+++ b/src/cache/cms_file_object_cache.e
@@ -0,0 +1,49 @@
+note
+ description: "Cache for value conforming to formal {G}, and implemented using local file."
+ date: "$Date: 2014-10-30 12:13:25 +0100 (jeu., 30 oct. 2014) $"
+ revision: "$Revision: 96016 $"
+
+class
+ CMS_FILE_OBJECT_CACHE [G -> ANY]
+
+inherit
+ CMS_FILE_CACHE [G]
+
+ SED_STORABLE_FACILITIES
+
+create
+ make
+
+feature {NONE} -- Implementation
+
+ file_to_item (f: FILE): detachable G
+ local
+ retried: BOOLEAN
+ l_reader: SED_MEDIUM_READER_WRITER
+ l_void: detachable G
+ do
+ if retried then
+ Result := l_void
+ else
+ create l_reader.make_for_reading (f)
+ if attached {G} retrieved (l_reader, True) as l_data then
+ Result := l_data
+ end
+ end
+ rescue
+ retried := True
+ retry
+ end
+
+ item_to_file (a_data: G; f: FILE)
+ local
+ l_writer: SED_MEDIUM_READER_WRITER
+ do
+ create l_writer.make_for_writing (f)
+ basic_store (a_data, l_writer, True)
+ 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
diff --git a/src/cache/cms_file_string_8_cache.e b/src/cache/cms_file_string_8_cache.e
new file mode 100644
index 0000000..73ee030
--- /dev/null
+++ b/src/cache/cms_file_string_8_cache.e
@@ -0,0 +1,69 @@
+note
+ description: "Cache system for STRING_8 value."
+ date: "$Date: 2014-10-30 12:13:25 +0100 (jeu., 30 oct. 2014) $"
+ revision: "$Revision: 96016 $"
+
+class
+ CMS_FILE_STRING_8_CACHE
+
+inherit
+ CMS_FILE_CACHE [STRING]
+
+create
+ make
+
+feature -- Access
+
+ append_to (a_output: STRING)
+ -- Append `item' to `a_output'.
+ local
+ f: RAW_FILE
+ retried: BOOLEAN
+ do
+ if not retried then
+ create f.make_with_path (path)
+ if f.exists and then f.is_access_readable then
+ f.open_read
+ if attached file_to_item (f) as s then
+ a_output.append (s)
+ end
+ f.close
+ end
+ end
+ rescue
+ retried := True
+ retry
+ end
+
+feature {NONE} -- Implementation
+
+ file_to_item (f: FILE): detachable STRING
+ local
+ retried: BOOLEAN
+ do
+ if retried then
+ Result := Void
+ else
+ from
+ create Result.make_empty
+ until
+ f.exhausted or f.end_of_file
+ loop
+ f.read_stream_thread_aware (1_024)
+ Result.append (f.last_string)
+ end
+ end
+ rescue
+ retried := True
+ retry
+ end
+
+ item_to_file (a_data: STRING; f: FILE)
+ do
+ f.put_string (a_data)
+ 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
diff --git a/src/cache/cms_memory_cache.e b/src/cache/cms_memory_cache.e
new file mode 100644
index 0000000..188f3c8
--- /dev/null
+++ b/src/cache/cms_memory_cache.e
@@ -0,0 +1,61 @@
+note
+ description: "Cache relying on memory."
+ date: "$Date: 2014-12-03 16:57:00 +0100 (mer., 03 déc. 2014) $"
+ revision: "$Revision: 96234 $"
+
+deferred class
+ CMS_MEMORY_CACHE [G -> ANY]
+
+inherit
+ CMS_CACHE [G]
+
+feature {NONE} -- Initialization
+
+ make
+ do
+ cache_date_time := current_date_time
+ end
+
+
+feature -- Status report
+
+ exists: BOOLEAN
+ -- Do associated cache memory exists?
+ do
+ Result := item /= Void
+ end
+
+feature -- Access
+
+ cache_date_time: DATE_TIME
+
+ current_date_time: DATE_TIME
+ --
+ do
+ create Result.make_now_utc
+ end
+
+ item: detachable G
+
+feature -- Element change
+
+ delete
+ --
+ local
+ l_default: detachable G
+ do
+ item := l_default
+ cache_date_time := current_date_time
+ end
+
+ put (g: G)
+ --
+ do
+ item := g
+ cache_date_time := current_date_time
+ 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
diff --git a/src/configuration/cms_default_setup.e b/src/configuration/cms_default_setup.e
index bdcfc4a..e15b9b8 100644
--- a/src/configuration/cms_default_setup.e
+++ b/src/configuration/cms_default_setup.e
@@ -68,6 +68,18 @@ feature -- Access
Result := configuration.resolved_text_item (a_name)
end
+ text_list_item (a_name: READABLE_STRING_GENERAL): detachable LIST [READABLE_STRING_32]
+ -- Configuration values associated with `a_name', if any.
+ do
+ Result := configuration.text_list_item (a_name)
+ end
+
+ text_table_item (a_name: READABLE_STRING_GENERAL): detachable STRING_TABLE [READABLE_STRING_32]
+ -- Configuration indexed values associated with `a_name', if any.
+ do
+ Result := configuration.text_table_item (a_name)
+ end
+
string_8_item (a_name: READABLE_STRING_GENERAL): detachable READABLE_STRING_8
-- String 8 configuration value associated with `a_name', if any.
local
diff --git a/src/configuration/cms_setup.e b/src/configuration/cms_setup.e
index dd7b5b6..979646e 100644
--- a/src/configuration/cms_setup.e
+++ b/src/configuration/cms_setup.e
@@ -196,6 +196,16 @@ feature -- Query
deferred
end
+ text_list_item (a_name: READABLE_STRING_GENERAL): detachable LIST [READABLE_STRING_32]
+ -- Configuration values associated with `a_name', if any.
+ deferred
+ end
+
+ text_table_item (a_name: READABLE_STRING_GENERAL): detachable STRING_TABLE [READABLE_STRING_32]
+ -- Configuration indexed values associated with `a_name', if any.
+ deferred
+ end
+
text_item_or_default (a_name: READABLE_STRING_GENERAL; a_default_value: READABLE_STRING_GENERAL): READABLE_STRING_32
-- `text_item' associated with `a_name' or if none, `a_default_value'.
do
diff --git a/src/hooks/cms_hook_block.e b/src/hooks/cms_hook_block.e
index 349f47c..cdccb6b 100644
--- a/src/hooks/cms_hook_block.e
+++ b/src/hooks/cms_hook_block.e
@@ -15,6 +15,8 @@ feature -- Hook
block_list: ITERABLE [like {CMS_BLOCK}.name]
-- List of block names, managed by current object.
+ -- If prefixed by "?", condition will be check
+ -- to determine if it should be displayed (and computed) or not.
deferred
end
@@ -23,4 +25,7 @@ feature -- Hook
deferred
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
diff --git a/src/hooks/cms_hook_core_manager.e b/src/hooks/cms_hook_core_manager.e
index 285d782..2772a25 100644
--- a/src/hooks/cms_hook_core_manager.e
+++ b/src/hooks/cms_hook_core_manager.e
@@ -138,6 +138,9 @@ feature -- Hook: block
invoke_block (a_response: CMS_RESPONSE)
-- Invoke block hook for response `a_response' in order to get block from modules.
+ local
+ bl: READABLE_STRING_8
+ bl_optional: BOOLEAN
do
if attached subscribers ({CMS_HOOK_BLOCK}) as lst then
across
@@ -147,7 +150,16 @@ feature -- Hook: block
across
h.block_list as blst
loop
- h.get_block_view (blst.item, a_response)
+ bl := blst.item
+ bl_optional := bl.count > 0 and bl[1] = '?'
+ if bl_optional then
+ bl := bl.substring (2, bl.count)
+ if a_response.is_block_included (bl, False) then
+ h.get_block_view (bl, a_response)
+ end
+ else
+ h.get_block_view (bl, a_response)
+ end
end
end
end
diff --git a/src/kernel/content/block/condition/cms_block_expression_condition.e b/src/kernel/content/block/condition/cms_block_expression_condition.e
index 72e5be5..531fef1 100644
--- a/src/kernel/content/block/condition/cms_block_expression_condition.e
+++ b/src/kernel/content/block/condition/cms_block_expression_condition.e
@@ -6,11 +6,12 @@ note
class
CMS_BLOCK_EXPRESSION_CONDITION
-inherit
+inherit
CMS_BLOCK_CONDITION
create
- make
+ make,
+ make_none
feature {NONE} -- Initialization
@@ -19,9 +20,14 @@ feature {NONE} -- Initialization
expression := a_exp
end
+ make_none
+ do
+ make ("")
+ end
+
feature -- Access
- description: READABLE_STRING_32
+ description: STRING_32
do
create Result.make_from_string_general ("Expression: %"")
Result.append_string_general (expression)
@@ -33,10 +39,18 @@ feature -- Access
feature -- Evaluation
satisfied_for_response (res: CMS_RESPONSE): BOOLEAN
+ local
+ exp: like expression
do
- if expression.same_string ("is_front") then
+ exp := expression
+ if exp.same_string ("is_front") then
Result := res.is_front
- elseif expression.starts_with ("path=") then
+ elseif exp.same_string ("*") then
+ Result := True
+ elseif exp.same_string ("") then
+ Result := False
+ elseif exp.starts_with ("path:") then
+ Result := res.location.same_string (exp.substring (6, exp.count))
end
end
diff --git a/src/kernel/content/block/condition/cms_block_location_condition.e b/src/kernel/content/block/condition/cms_block_location_condition.e
new file mode 100644
index 0000000..076392c
--- /dev/null
+++ b/src/kernel/content/block/condition/cms_block_location_condition.e
@@ -0,0 +1,45 @@
+note
+ description: "Condition for block to be displayed based on location."
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ CMS_BLOCK_LOCATION_CONDITION
+
+inherit
+ CMS_BLOCK_CONDITION
+
+create
+ make_with_location
+
+feature {NONE} -- Initialization
+
+ make_with_location (a_location: READABLE_STRING_8)
+ do
+ location := a_location
+ end
+
+feature -- Access
+
+ description: STRING_32
+ do
+ create Result.make_from_string_general ("Location: %"")
+ Result.append_string_general (location)
+ Result.append_character ('%"')
+ end
+
+ location: STRING
+
+feature -- Evaluation
+
+ satisfied_for_response (res: CMS_RESPONSE): BOOLEAN
+ local
+ loc: like location
+ do
+ Result := res.location.same_string (loc)
+ 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
diff --git a/src/kernel/content/cms_block.e b/src/kernel/content/cms_block.e
index 0bbf1aa..e629b93 100644
--- a/src/kernel/content/cms_block.e
+++ b/src/kernel/content/cms_block.e
@@ -70,6 +70,19 @@ feature -- Element change
opts.remove_css_class (a_class)
end
+ add_condition (a_condition: CMS_BLOCK_CONDITION)
+ -- Add condition `a_condition'.
+ local
+ l_conditions: like conditions
+ do
+ l_conditions := conditions
+ if l_conditions = Void then
+ create {ARRAYED_LIST [CMS_BLOCK_CONDITION]} l_conditions.make (1)
+ conditions := l_conditions
+ end
+ l_conditions.force (a_condition)
+ end
+
feature -- Conversion
to_html (a_theme: CMS_THEME): STRING_8
diff --git a/src/modules/cms_debug_module.e b/src/modules/cms_debug_module.e
index 04c4efe..72b1cca 100644
--- a/src/modules/cms_debug_module.e
+++ b/src/modules/cms_debug_module.e
@@ -58,7 +58,7 @@ feature -- Hooks
block_list: ITERABLE [like {CMS_BLOCK}.name]
do
- Result := <<"debug-info">>
+ Result := <<"?debug-info">>
end
get_block_view (a_block_id: READABLE_STRING_8; a_response: CMS_RESPONSE)
@@ -67,14 +67,13 @@ feature -- Hooks
dbg: WSF_DEBUG_INFORMATION
s: STRING
do
- if a_response.theme.has_region ("debug") then
- create dbg.make
- create s.make_empty
- dbg.append_information_to (a_response.request, a_response.response, s)
- append_info_to ("Storage", a_response.api.storage.generator, a_response, s)
- create b.make ("debug-info", "Debug", s, a_response.formats.plain_text)
- a_response.add_block (b, "footer")
- end
+ create dbg.make
+ create s.make_empty
+ dbg.append_information_to (a_response.request, a_response.response, s)
+ append_info_to ("Storage", a_response.api.storage.generator, a_response, s)
+ create b.make ("debug-info", "Debug", s, a_response.formats.plain_text)
+ b.add_condition (create {CMS_BLOCK_EXPRESSION_CONDITION}.make_none)
+ a_response.add_block (b, "footer")
end
feature -- Handler
diff --git a/src/service/response/cms_response.e b/src/service/response/cms_response.e
index 72559ce..3d026c5 100644
--- a/src/service/response/cms_response.e
+++ b/src/service/response/cms_response.e
@@ -423,6 +423,36 @@ feature -- Blocks initialization
Result := setup.text_item_or_default ("blocks." + a_block_id + ".region", a_default_region)
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)
+ across
+ lst as ic
+ loop
+ Result.force (create {CMS_BLOCK_EXPRESSION_CONDITION}.make (ic.item))
+ end
+ end
+ 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
+
feature -- Blocks regions
regions: STRING_TABLE [CMS_BLOCK_REGION]
@@ -457,10 +487,25 @@ feature -- Blocks regions
end
end
-feature -- Blocks
+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.
+ 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)
+ 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
@@ -473,29 +518,29 @@ feature -- Blocks
debug ("refactor_fixme")
fixme ("find a way to have this in configuration or database, and allow different order")
end
- add_block (top_header_block, "top")
- add_block (header_block, "header")
+ put_block (top_header_block, "top", True)
+ put_block (header_block, "header", True)
if attached message_block as m then
- add_block (m, "content")
+ put_block (m, "content", True)
end
if attached primary_tabs_block as m then
- add_block (m, "content")
+ put_block (m, "content", True)
end
- add_block (content_block, "content")
+ add_block (content_block, "content") -- Can not be disabled!
if attached management_menu_block as l_block then
- add_block (l_block, "sidebar_first")
+ put_block (l_block, "sidebar_first", True)
end
if attached navigation_menu_block as l_block then
- add_block (l_block, "sidebar_first")
+ put_block (l_block, "sidebar_first", True)
end
if attached user_menu_block as l_block then
- add_block (l_block, "sidebar_second")
+ put_block (l_block, "sidebar_second", True)
end
hooks.invoke_block (Current)
debug ("cms")
- add_block (create {CMS_CONTENT_BLOCK}.make ("made_with", Void, "Made with EWF", Void), "footer")
+ put_block (create {CMS_CONTENT_BLOCK}.make ("made_with", Void, "Made with EWF", Void), "footer", True)
end
end