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