Compare commits

...

15 Commits

Author SHA1 Message Date
ed24eb7c94 Updated theme example, to have only 2 feeds. 2015-10-09 19:47:49 +02:00
43d6b4a197 Committed module files installed for demo example. 2015-10-09 19:45:21 +02:00
dffd06e331 Implemented a basic block caching system.
- for block {block_id}, to have a cache with 3600 seconds of expiration,
    declare in the cms.ini
     [blocks]
     {block_id}.expiration=3600

Added support for size in feed aggregation with new field "size"
2015-10-09 19:38:57 +02:00
463105f29f Added feed aggregation module.
Redesigned the CMS_BLOCK system,
   - added condition attribute. It can be set via configuration file
     with
     [blocks]
      {blockid}.region={region_name}
      {blockid}.conditions[]=is_front
      {blockid}.conditions[]=path:location-path/foo/bar
   - For backward compatibility, the CMS will check only conditions for block name prefixed by "?".
Improved the configuration library to support list and table properties.
Updated theme for now, to include the feed examples.
Added "cache" classes, to ease caching of html output for instance. (TODO: improve by providing a cache manager).
2015-10-08 13:56:31 +02:00
abebd00a4f Added feed aggregation module.
Improved the CMS Block system to support condition.
2015-10-05 16:04:24 +02:00
ec53a2682b Updated notification mailer, to always store output messages.
Fixed CMS_RESPONSE, and specific error response, to return expected status code.
2015-09-28 10:47:57 +02:00
jvelilla
7b2e6ab7b4 Merge branch 'jvelilla-roc_jv_issues' 2015-09-15 14:35:35 -03:00
jvelilla
87f4de1264 Merge branch 'roc_jv_issues' of https://github.com/jvelilla/ROC into roc_jv_issues 2015-09-15 14:30:02 -03:00
jvelilla
ed614a662c Updated CMS node and blog to remove extension data.
Comments: minor update.

Updated CMS_NODE_STORAGE_I API.
Delete a node using a node as formal parameter instead of node id.
Clean code and update log information.

Added precondition to delete node to accept nodes with a valid a id.

Added missing assertions tag names.
2015-09-15 14:28:33 -03:00
jvelilla
d54ad59e5f Added missing assertions tag names. 2015-09-15 14:10:51 -03:00
jvelilla
9173ef2ded Added precondition to delete node to accept nodes with a valid a id. 2015-09-15 11:40:13 -03:00
jvelilla
ad9e908dc2 Updated CMS_NODE_STORAGE_I API.
Delete a node using a node as formal parameter instead of node id.
Clean code and update log information.
2015-09-15 10:42:30 -03:00
jvelilla
4584917877 Comments: minor update. 2015-09-15 09:02:38 -03:00
jvelilla
f7d68d09e4 Updated CMS node and blog to remove extension data. 2015-09-15 08:54:43 -03:00
f9ecd4956f Keep the until date in the form data, so that new filter will remember the until date. 2015-09-09 23:12:52 +02:00
53 changed files with 2151 additions and 115 deletions

View File

@@ -19,6 +19,7 @@
<library name="cms_blog_module" location="..\..\modules\blog\cms_blog_module-safe.ecf" readonly="false"/>
<library name="cms_demo_module" location="modules\demo\cms_demo_module-safe.ecf" readonly="false"/>
<library name="cms_email_service" location="..\..\library\email\email-safe.ecf" readonly="false"/>
<library name="cms_feed_aggregator_module" location="..\..\modules\feed_aggregator\feed_aggregator-safe.ecf" readonly="false"/>
<library name="cms_model" location="..\..\library\model\cms_model-safe.ecf" readonly="false"/>
<library name="cms_node_module" location="..\..\modules\node\node-safe.ecf" readonly="false"/>
<library name="cms_oauth_20_module" location="..\..\modules\oauth20\oauth20-safe.ecf" readonly="false"/>

View File

@@ -10,3 +10,4 @@ set ROC_CMS_DIR=%~dp0
%ROC_CMD% install --module ..\..\modules\oauth20 --dir %ROC_CMS_DIR%
%ROC_CMD% install --module ..\..\modules\openid --dir %ROC_CMS_DIR%
%ROC_CMD% install --module ..\..\modules\recent_changes --dir %ROC_CMS_DIR%
%ROC_CMD% install --module ..\..\modules\feed_aggregator --dir %ROC_CMS_DIR%

View File

@@ -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)

View File

@@ -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,6 +33,17 @@ openid=on
[blocks]
#navigation.region=sidebar_first
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)

View File

@@ -0,0 +1,28 @@
{
"ids": ["eiffel", "forum"],
"feeds": {
"eiffel": {
"title": "Eiffel related posts.",
"expiration": "21600",
"size": 10,
"locations": [
"https://bertrandmeyer.com/feed/",
"https://room.eiffel.com/blog/feed",
"https://room.eiffel.com/article/feed",
"https://room.eiffel.com/library/feed"
]
, "categories": ["Eiffel"]
,"option_description": "enabled"
},
"forum": {
"title": "Eiffel Forum",
"expiration": "21600",
"size": 10,
"locations": [
"https://groups.google.com/forum/feed/eiffel-users/msgs/atom.xml?num=15".
"http://stackoverflow.com/feeds/tag?tagnames=eiffel&sort=newest"
]
,"option_description": "enabled"
}
}
}

View File

@@ -0,0 +1,53 @@
div.feed ul {
list-style: none;
position: relative;
padding: 0;
margin: 0;
width: 99%;
}
div.feed li {
/* border-top: solid 1px #ddd; */
padding: 0;
margin: 0 0 5px 0;
}
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 {
padding: 5px;
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: "...";
}

View File

@@ -0,0 +1,54 @@
div.feed {
ul {
list-style: none;
position: relative;
padding: 0;
margin: 0;
width: 99%;
}
li {
/* border-top: solid 1px #ddd; */
padding: 0;
margin: 0 0 5px 0;
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 {
padding: 5px;
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: "..."; }
}
}

View File

@@ -62,9 +62,15 @@
<!-- Main Content Section -->
{unless isempty="$page_title"}<h1 class="page-title">{$page_title/}</h1>{/unless}
{$page.region_content/}
{if condition="$page.is_front"}
{if isset="$page.region_feed_eiffel"}
<div class="column" style="width: 45%; float: left">{$page.region_feed_eiffel/}</div>
{/if}
{if isset="$page.region_feed_forum"}
<div class="column" style="width: 45%; float: left">{$page.region_feed_forum/}</div>
{/if}
{/if}
</div>
</div>
</div>

View File

@@ -82,6 +82,9 @@ feature -- CMS setup
create {CMS_RECENT_CHANGES_MODULE} m.make
a_setup.register_module (m)
-- Recent changes
create {FEED_AGGREGATOR_MODULE} m.make
a_setup.register_module (m)
-- Miscellanious
create {CMS_DEBUG_MODULE} m.make

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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"))

View File

@@ -28,7 +28,6 @@ feature {NONE} -- Initialization
-- Initialize service.
do
admin_email := parameters.admin_email
create {NOTIFICATION_SMTP_MAILER} mailer.make (parameters.smtp_server)
set_successful
end

View File

@@ -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
-- end
-- 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
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

View File

@@ -108,6 +108,18 @@ feature -- Persistence
end
end
delete_node (a_node: CMS_BLOG)
-- <Precursor>
local
l_parameters: STRING_TABLE [ANY]
do
if a_node.has_id then
create l_parameters.make (1)
l_parameters.put (a_node.id, "nid")
sql_change (sql_delete_node_data, l_parameters)
end
end
feature {NONE} -- Implementation
node_data (a_node: CMS_NODE): detachable TUPLE [revision: INTEGER_64; tags: READABLE_STRING_32]
@@ -145,5 +157,6 @@ feature -- SQL
sql_select_node_data: STRING = "SELECT nid, revision, tags FROM blog_post_nodes WHERE nid=:nid AND revision<=:revision ORDER BY revision DESC LIMIT 1;"
sql_insert_node_data: STRING = "INSERT INTO blog_post_nodes (nid, revision, tags) VALUES (:nid, :revision, :tags);"
sql_update_node_data: STRING = "UPDATE blog_post_nodes SET nid=:nid, revision=:revision, tags=:tags WHERE nid=:nid AND revision=:revision;"
sql_delete_node_data: STRING = "DELETE FROM blog_post_nodes WHERE nid=:nid;"
end

View File

@@ -0,0 +1,115 @@
note
description: "Feed aggregation parameters."
date: "$Date$"
revision: "$Revision$"
class
FEED_AGGREGATION
create
make
feature {NONE} -- Initialization
make (a_name: READABLE_STRING_GENERAL)
do
create name.make_from_string_general (a_name)
create {ARRAYED_LIST [READABLE_STRING_8]} locations.make (0)
expiration := 60*60
description_enabled := True
size := 10
end
feature -- Access
name: IMMUTABLE_STRING_32
-- Associated name.
expiration: INTEGER
-- Suggested expiration time in seconds (default: 1 hour).
-- If negative then never expires.
size: INTEGER
-- Number of entries to display per page.
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)
do
if a_desc = Void then
description := Void
else
create description.make_from_string_general (a_desc)
end
end
set_expiration (nb_seconds: INTEGER)
-- Set `expiration' to `nb_seconds'.
do
expiration := nb_seconds
end
set_size (nb: INTEGER)
-- Set `size' to `nb'.
do
size := nb
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

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-14-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-14-0 http://www.eiffel.com/developers/xml/configuration-1-14-0.xsd" name="feed_aggregator" uuid="6A78AB37-9B07-4C42-9E24-0CA7D3C61E12" library_target="feed_aggregator">
<target name="feed_aggregator">
<root all_classes="true"/>
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
<library name="base_extension" location="$ISE_LIBRARY\library\base_extension\base_extension-safe.ecf"/>
<library name="time" location="$ISE_LIBRARY\library\time\time-safe.ecf"/>
<library name="cms" location="..\..\cms-safe.ecf"/>
<library name="cms_model" location="..\..\library\model\cms_model-safe.ecf" readonly="false"/>
<library name="error" location="$ISE_LIBRARY\contrib\library\utility\general\error\error-safe.ecf"/>
<library name="feed" location="$ISE_LIBRARY\contrib\library\text\parser\feed\feed-safe.ecf"/>
<library name="http" location="$ISE_LIBRARY\contrib\library\network\protocol\http\http-safe.ecf"/>
<library name="http_client" location="$ISE_LIBRARY\contrib\library\network\http_client\http_client-safe.ecf"/>
<library name="wsf" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\wsf-safe.ecf"/>
<library name="wsf_extension" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\wsf_extension-safe.ecf" readonly="false"/>
<library name="wsf_html" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf_html\wsf_html-safe.ecf" readonly="false"/>
<cluster name="src" location="." recursive="true"/>
</target>
</system>

View File

@@ -0,0 +1,146 @@
note
description: "API for Feed aggregator module."
date: "$Date$"
revision: "$Revision$"
class
FEED_AGGREGATOR_API
inherit
CMS_MODULE_API
create
make
feature -- Access
aggregations: HASH_TABLE [FEED_AGGREGATION, STRING]
-- 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
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 + ".size") as l_size then
if l_size.is_integer then
agg.set_size (l_size.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
do
if attached a_name.is_valid_as_string_8 then
Result := aggregations.item (a_name.as_string_8)
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

View File

@@ -0,0 +1,308 @@
note
description: "CMS module bringing support for feed aggregation."
date: "$Date: 2015-02-13 13:08:13 +0100 (ven., 13 févr. 2015) $"
revision: "$Revision: 96616 $"
class
FEED_AGGREGATOR_MODULE
inherit
CMS_MODULE
rename
module_api as feed_aggregator_api
redefine
initialize,
register_hooks,
permissions,
feed_aggregator_api
end
CMS_HOOK_BLOCK
CMS_HOOK_RESPONSE_ALTER
CMS_HOOK_MENU_SYSTEM_ALTER
create
make
feature {NONE} -- Initialization
make
-- Create Current module, disabled by default.
do
version := "1.0"
description := "Feed aggregation"
package := "feed"
end
feature -- Access
name: STRING = "feed_aggregator"
permissions: LIST [READABLE_STRING_8]
-- List of permission ids, used by this module, and declared.
do
Result := Precursor
Result.force ("manage feed aggregator")
end
feature {CMS_API} -- Module Initialization
initialize (api: CMS_API)
-- <Precursor>
do
Precursor (api)
create feed_aggregator_api.make (api)
end
feature {CMS_API} -- Access: API
feed_aggregator_api: detachable FEED_AGGREGATOR_API
-- Eventual module api.
feature -- Access: router
setup_router (a_router: WSF_ROUTER; a_api: CMS_API)
-- <Precursor>
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)
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 ("<h1>")
s.append (r.html_encoded (l_agg.name))
s.append ("</h1>")
if attached l_agg.included_categories as l_categories then
s.append ("<span class=%"category%">")
across
l_categories as cats_ic
loop
s.append (" [")
s.append (r.html_encoded (cats_ic.item))
s.append ("]")
end
s.append ("</span>")
end
if attached l_agg.description as l_desc and then l_desc.is_valid_as_string_8 then
s.append ("<div class=%"description%">")
s.append (l_desc.as_string_8)
s.append ("</div>")
end
s.append ("<ul>")
across
l_agg.locations as ic
loop
s.append ("<li><a href=%"")
s.append (ic.item)
s.append ("%">")
s.append (ic.item)
s.append ("</a></li>")
end
s.append ("</ul>")
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 ("<li>")
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 ("<span class=%"category%">")
across
l_categories as cats_ic
loop
s.append (" [")
s.append (r.html_encoded (cats_ic.item))
s.append ("]")
end
s.append ("</span>")
end
if attached ic.item.description as l_desc then
if l_desc.is_valid_as_string_8 then
s.append ("<div class=%"description%">")
s.append (l_desc.as_string_8)
s.append ("</div>")
end
end
s.append ("</li>")
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
register_hooks (a_response: CMS_RESPONSE)
-- Module 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
block_list: ITERABLE [like {CMS_BLOCK}.name]
-- 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
if attached feed_aggregator_api as l_feed_api then
l_aggs := l_feed_api.aggregations
create res.make (l_aggs.count)
across
l_aggs as ic
loop
res.force ("?feed." + ic.key)
end
else
create res.make (0)
end
Result := res
end
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
s: READABLE_STRING_8
b: CMS_CONTENT_BLOCK
pref: STRING
do
if attached feed_aggregator_api as l_feed_api then
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 feed_to_html (s, 0, 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
vis: FEED_TO_XHTML_VISITOR
s: STRING
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 Result.make (1024)
Result.append ("<!-- ")
Result.append ("Updated: " + l_cache.cache_date_time.out)
Result.append (" -->")
create vis.make (Result)
if a_count = 0 then
nb := l_agg.size
else
nb := a_count
end
vis.set_limit (nb)
vis.set_description_enabled (l_agg.description_enabled)
if with_feed_info then
create s.make_empty
if attached l_agg.description as l_desc then
s.append ("<div class=%"description%">")
s.append_string_general (l_desc)
s.append ("</div>")
end
vis.set_header (s)
end
create s.make_empty
s.append_string ("<liv class=%"nav%">")
s.append_string (a_response.link ("See more ...", "feed_aggregation/" + a_response.url_encoded (a_feed_id), Void))
s.append_string ("</li>")
vis.set_footer (s)
if attached l_feed_api.aggregation_feed (l_agg) as l_feed then
l_feed.accept (vis)
end
l_cache.put (Result)
end
end
end
end
feature -- Hook
response_alter (a_response: CMS_RESPONSE)
do
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

View File

@@ -0,0 +1,53 @@
div.feed ul {
list-style: none;
position: relative;
padding: 0;
margin: 0;
width: 99%;
}
div.feed li {
/* border-top: solid 1px #ddd; */
padding: 0;
margin: 0 0 5px 0;
}
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 {
padding: 5px;
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: "...";
}

View File

@@ -0,0 +1,54 @@
div.feed {
ul {
list-style: none;
position: relative;
padding: 0;
margin: 0;
width: 99%;
}
li {
/* border-top: solid 1px #ddd; */
padding: 0;
margin: 0 0 5px 0;
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 {
padding: 5px;
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: "..."; }
}
}

View File

@@ -244,7 +244,7 @@ feature -- Hooks
block_list: ITERABLE [like {CMS_BLOCK}.name]
-- <Precursor>
do
Result := <<"node-info">>
Result := <<"?node-info">>
end
get_block_view (a_block_id: READABLE_STRING_8; a_response: CMS_RESPONSE)

View File

@@ -30,7 +30,7 @@ feature -- Forms ...
local
ti: WSF_FORM_NUMBER_INPUT
fs: WSF_FORM_FIELD_SET
l_parent_id, nid: INTEGER_64
l_parent_id: INTEGER_64
do
Precursor (response, f, a_node)
@@ -102,7 +102,6 @@ feature -- Forms ...
parent_validation (a_response: NODE_RESPONSE; fd: WSF_FORM_DATA)
local
l_selected: BOOLEAN
node_api: CMS_NODE_API
l_parent_id: INTEGER_64
nid: INTEGER_64

View File

@@ -49,6 +49,13 @@ feature -- Persistence
end
end
delete_node (a_node: CMS_NODE)
-- remove node extensions.
require
a_node_accepted: is_accepted (a_node)
deferred
end
feature {NONE} -- Persistence implementation
store (a_node: G)

View File

@@ -44,7 +44,7 @@ feature {NONE} -- Implementation
extended_store (a_node: CMS_NODE)
-- Store extended data from `a_node'.
require
not error_handler.has_error
not_has_error: not error_handler.has_error
do
if attached node_storage_extension (a_node) as ext then
ext.store_node (a_node)
@@ -54,13 +54,23 @@ feature {NONE} -- Implementation
extended_load (a_node: CMS_NODE)
-- Load extended data into `a_node'.
require
not error_handler.has_error
not_has_error: not error_handler.has_error
do
if attached node_storage_extension (a_node) as ext then
ext.load_node (a_node)
end
end
extended_delete (a_node: CMS_NODE)
-- Delete extended data related to node `a_node'.
require
not_has_error: not error_handler.has_error
do
if attached node_storage_extension (a_node) as ext then
ext.delete_node (a_node)
end
end
feature -- Access
nodes_count: NATURAL_64
@@ -165,16 +175,19 @@ feature -- Change: Node
delete_node (a_node: CMS_NODE)
-- Delete `a_node'.
require
valid_node_id: a_node.has_id
do
if a_node.has_id then
delete_node_by_id (a_node.id)
end
-- TODO
-- Check if we need to use a transaction
-- we delete a node
-- node_revisions
-- and extensions (PAGE, BLOG, etc).
delete_node_base (a_node)
end
delete_node_by_id (a_id: INTEGER_64)
-- Remove node by id `a_id'.
require
valid_node_id: a_id > 0
delete_node_base (a_node: CMS_NODE)
-- Remove node `a_node'.
deferred
end

View File

@@ -106,7 +106,7 @@ feature -- Node
do
end
delete_node_by_id (a_id: INTEGER_64)
delete_node_base (a_node: CMS_NODE)
-- <Precursor>
do
end

View File

@@ -53,11 +53,6 @@ feature -- Access
end
sql_forth
end
-- across
-- Result as ic
-- loop
-- fill_node (ic.item)
-- end
end
node_revisions (a_node: CMS_NODE): LIST [CMS_NODE]
@@ -95,7 +90,7 @@ feature -- Access
create {ARRAYED_LIST [CMS_NODE]} Result.make (0)
error_handler.reset
write_information_log (generator + ".trash_nodes")
write_information_log (generator + ".trashed_nodes")
from
create l_parameters.make (1)
@@ -124,7 +119,7 @@ feature -- Access
create {ARRAYED_LIST [CMS_NODE]} Result.make (0)
error_handler.reset
write_information_log (generator + ".nodes")
write_information_log (generator + ".recent_nodes")
from
create l_parameters.make (2)
@@ -150,7 +145,7 @@ feature -- Access
create {ARRAYED_LIST [CMS_NODE]} Result.make (0)
error_handler.reset
write_information_log (generator + ".nodes")
write_information_log (generator + ".recent_node_changes_before")
from
create l_parameters.make (3)
@@ -176,7 +171,7 @@ feature -- Access
l_parameters: STRING_TABLE [ANY]
do
error_handler.reset
write_information_log (generator + ".node")
write_information_log (generator + ".node_by_id")
create l_parameters.make (1)
l_parameters.put (a_id, "nid")
sql_query (sql_select_node_by_id, l_parameters)
@@ -191,7 +186,7 @@ feature -- Access
l_parameters: STRING_TABLE [ANY]
do
error_handler.reset
write_information_log (generator + ".node")
write_information_log (generator + ".node_by_id_and_revision")
create l_parameters.make (1)
l_parameters.put (a_node_id, "nid")
l_parameters.put (a_revision, "revision")
@@ -326,7 +321,7 @@ feature -- Change: Node
local
l_parameters: STRING_TABLE [ANY]
do
write_information_log (generator + ".delete_node {" + a_id.out + "}")
write_information_log (generator + ".trash_node_by_id {" + a_id.out + "}")
error_handler.reset
create l_parameters.make (3)
@@ -336,19 +331,27 @@ feature -- Change: Node
sql_change (sql_trash_node, l_parameters)
end
delete_node_by_id (a_id: INTEGER_64)
delete_node_base (a_node: CMS_NODE)
-- <Precursor>
local
l_parameters: STRING_TABLE [ANY]
l_time: DATE_TIME
do
create l_time.make_now_utc
write_information_log (generator + ".trash_node {" + a_id.out + "}")
write_information_log (generator + ".delete_node_base {" + a_node.id.out + "}")
error_handler.reset
create l_parameters.make (1)
l_parameters.put (a_id, "nid")
l_parameters.put (a_node.id, "nid")
sql_change (sql_delete_node, l_parameters)
-- we remove node_revisions and pages.
-- Check: maybe we need a transaction.
sql_change (sql_delete_node_revisions, l_parameters)
if not error_handler.has_error then
extended_delete (a_node)
end
end
restore_node_by_id (a_id: INTEGER_64)
@@ -358,7 +361,7 @@ feature -- Change: Node
l_time: DATE_TIME
do
create l_time.make_now_utc
write_information_log (generator + ".restore_node {" + a_id.out + "}")
write_information_log (generator + ".restore_node_by_id {" + a_id.out + "}")
error_handler.reset
create l_parameters.make (1)
@@ -520,6 +523,8 @@ feature {NONE} -- Queries
WHERE pn.parent = :nid AND node.status != -1 GROUP BY node.nid, node.revision;
]"
sql_delete_node_revisions: STRING = "DELETE FROM node_revisions WHERE nid=:nid;"
feature {NONE} -- Sql Queries: USER_ROLES collaborators, author
Select_user_author: STRING = "SELECT uid, name, password, salt, email, users.status, users.created, signed FROM nodes INNER JOIN users ON nodes.author=users.uid AND nodes.nid = :nid AND nodes.revision = :revision;"

View File

@@ -112,6 +112,19 @@ feature -- Persistence
end
end
delete_node (a_node: CMS_PAGE)
-- <Precursor>
local
l_parameters: STRING_TABLE [ANY]
do
if a_node.has_id then
create l_parameters.make (1)
l_parameters.put (a_node.id, "nid")
sql_change (sql_delete_node_data, l_parameters)
end
end
feature {NONE} -- Implementation
node_data (a_node: CMS_NODE): detachable TUPLE [revision: INTEGER_64; parent_id: INTEGER_64]
@@ -143,5 +156,6 @@ feature -- SQL
sql_select_node_data: STRING = "SELECT nid, revision, parent FROM page_nodes WHERE nid=:nid AND revision<=:revision ORDER BY revision DESC LIMIT 1;"
sql_insert_node_data: STRING = "INSERT INTO page_nodes (nid, revision, parent) VALUES (:nid, :revision, :parent);"
sql_update_node_data: STRING = "UPDATE page_nodes SET nid=:nid, revision=:revision, parent=:parent WHERE nid=:nid AND revision=:revision;"
sql_delete_node_data: STRING = "DELETE FROM page_nodes WHERE nid=:nid;"
end

View File

@@ -1,5 +1,5 @@
note
description: "CMS module that bring support for recent changes."
description: "CMS module that brings support for recent changes."
date: "$Date: 2015-02-13 13:08:13 +0100 (ven., 13 févr. 2015) $"
revision: "$Revision: 96616 $"
@@ -19,6 +19,8 @@ inherit
CMS_HOOK_RESPONSE_ALTER
CMS_HOOK_BLOCK
create
make
@@ -49,10 +51,132 @@ feature -- Access: router
-- <Precursor>
do
a_router.handle ("/recent_changes/", create {WSF_URI_AGENT_HANDLER}.make (agent handle_recent_changes (a_api, ?, ?)), a_router.methods_head_get)
a_router.handle ("/recent_changes/feed", create {WSF_URI_AGENT_HANDLER}.make (agent handle_recent_changes_feed (a_api, ?, ?)), a_router.methods_head_get)
end
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.
do
Result := <<"?recent_changes">>
end
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
b: CMS_CONTENT_BLOCK
s, l_content: STRING
gen: FEED_TO_XHTML_VISITOR
do
if a_block_id.same_string_general ("recent_changes") then
create l_content.make (1024)
create gen.make (l_content)
create s.make_empty
s.append_string ("<liv class=%"nav%">")
s.append_string (a_response.link ("See more ...", "recent_changes/", Void))
s.append_string ("</li>")
gen.set_footer (s)
recent_changes_feed (a_response, 10, Void).accept (gen)
create b.make (a_block_id, Void, l_content, Void)
a_response.put_block (b, Void, False)
end
end
recent_changes_feed (a_response: CMS_RESPONSE; a_size: NATURAL_32; a_source: detachable READABLE_STRING_8): FEED
local
l_changes: CMS_RECENT_CHANGE_CONTAINER
ch: CMS_RECENT_CHANGE_ITEM
l_user: detachable CMS_USER
l_feed: FEED
l_feed_item: FEED_ITEM
lnk: FEED_LINK
nb: NATURAL_32
do
l_user := Void -- Public access for the feed!
create l_changes.make (a_size, create {DATE_TIME}.make_now_utc, a_source)
if attached a_response.hooks.subscribers ({CMS_RECENT_CHANGES_HOOK}) as lst then
across
lst as ic
loop
if attached {CMS_RECENT_CHANGES_HOOK} ic.item as h then
if attached h.recent_changes_sources as h_sources then
if
a_source = Void
or else across h_sources as h_ic some h_ic.item.is_case_insensitive_equal (a_source) end
then
h.populate_recent_changes (l_changes, l_user)
end
end
end
end
end
create l_feed.make ("CMS Recent changes")
l_feed.set_date (create {DATE_TIME}.make_now_utc)
nb := a_size
across
l_changes as ic
until
nb = 0
loop
ch := ic.item
create l_feed_item.make (ch.link.title)
l_feed_item.set_date (ch.date)
l_feed_item.set_description (ch.information)
l_feed_item.set_category (ch.source)
create lnk.make (a_response.absolute_url (ch.link.location, Void))
l_feed_item.links.force (lnk, "")
l_feed.extend (l_feed_item)
nb := nb - 1
end
l_feed.sort
Result := l_feed
end
feature -- Handler
handle_recent_changes_feed (api: CMS_API; req: WSF_REQUEST; res: WSF_RESPONSE)
local
r: CMS_RESPONSE
htdate: HTTP_DATE
l_content: STRING
l_until_date: detachable DATE_TIME
l_until_date_timestamp: INTEGER_64
l_filter_source: detachable READABLE_STRING_8
l_size: NATURAL_32
mesg: CMS_CUSTOM_RESPONSE_MESSAGE
do
if attached {WSF_STRING} req.query_parameter ("date") as p_until_date then
l_until_date_timestamp := p_until_date.value.to_integer_64
create htdate.make_from_timestamp (l_until_date_timestamp)
l_until_date := htdate.date_time
end
if attached {WSF_STRING} req.query_parameter ("source") as p_filter then
l_filter_source := p_filter.url_encoded_value
if l_filter_source.is_empty then
l_filter_source := Void
end
end
if attached {WSF_STRING} req.query_parameter ("size") as p_size then
l_size := p_size.integer_value.to_natural_32
end
if l_size = 0 then
l_size := 25
end
create {GENERIC_VIEW_CMS_RESPONSE} r.make (req, res, api)
create l_content.make (1024)
recent_changes_feed (r, l_size, l_filter_source).accept (create {ATOM_FEED_GENERATOR}.make (l_content))
create mesg.make ({HTTP_STATUS_CODE}.ok)
mesg.set_payload (l_content)
res.send (mesg)
end
handle_recent_changes (api: CMS_API; req: WSF_REQUEST; res: WSF_RESPONSE)
local
r: CMS_RESPONSE
@@ -66,8 +190,10 @@ feature -- Handler
l_form: CMS_FORM
l_select: WSF_FORM_SELECT
l_size_field: WSF_FORM_NUMBER_INPUT
l_date_field: WSF_FORM_HIDDEN_INPUT
l_submit: WSF_FORM_SUBMIT_INPUT
l_until_date: detachable DATE_TIME
l_until_date_timestamp: INTEGER_64
l_filter_source: detachable READABLE_STRING_8
l_size: NATURAL_32
l_query: STRING
@@ -76,7 +202,8 @@ feature -- Handler
i: INTEGER
do
if attached {WSF_STRING} req.query_parameter ("date") as p_until_date then
create htdate.make_from_timestamp (p_until_date.value.to_integer_64)
l_until_date_timestamp := p_until_date.value.to_integer_64
create htdate.make_from_timestamp (l_until_date_timestamp)
l_until_date := htdate.date_time
-- l_until_date.second_add (-1)
end
@@ -138,6 +265,12 @@ feature -- Handler
l_size_field.set_size (25)
l_size_field.set_label ("Items per page")
l_form.extend (l_size_field)
if l_until_date /= Void then
create l_date_field.make_with_text ("date", l_until_date_timestamp.out)
l_form.extend (l_date_field)
end
create l_submit.make_with_text ("op", "Filter")
l_form.extend (l_submit)
l_form.extend_html_text ("<br/>")
@@ -234,7 +367,7 @@ feature -- Handler
end
l_content.append ("<a href=%"")
l_content.append (r.url (r.location, create {CMS_API_OPTIONS}.make_from_manifest (<<["query", l_query]>>)))
l_content.append ("%">More ...</a>")
l_content.append ("%">See more ...</a>")
end
end
@@ -259,6 +392,7 @@ feature -- Hooks configuration
do
a_response.hooks.subscribe_to_menu_system_alter_hook (Current)
a_response.hooks.subscribe_to_response_alter_hook (Current)
a_response.hooks.subscribe_to_block_hook (Current)
end
feature -- Hook

View File

@@ -16,6 +16,7 @@
<library name="cms" location="..\..\cms-safe.ecf"/>
<library name="cms_model" location="..\..\library\model\cms_model-safe.ecf" readonly="false"/>
<library name="error" location="$ISE_LIBRARY\contrib\library\utility\general\error\error-safe.ecf"/>
<library name="feed" location="$ISE_LIBRARY\contrib\library\text\parser\feed\feed-safe.ecf"/>
<library name="http" location="$ISE_LIBRARY\contrib\library\network\protocol\http\http-safe.ecf"/>
<library name="wsf" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\wsf-safe.ecf"/>
<library name="wsf_extension" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\wsf_extension-safe.ecf" readonly="false"/>

View File

@@ -1,4 +1,4 @@
package ROC_cms
package ROC
project
cms = "cms-safe.ecf"
@@ -19,6 +19,21 @@ project
basic_auth = "modules/basic_auth/basic_auth.ecf"
node = "modules/node/node-safe.ecf"
node = "modules/node/node.ecf"
demo = "examples/demo/demo-safe.ecf"
cms_demo_module = "examples/demo/modules/demo/cms_demo_module-safe.ecf"
config_tests = "library/configuration/tests/config_tests-safe.ecf"
email_service = "library/email/email-safe.ecf"
persistence_sqlite3 = "library/persistence/sqlite3/persistence_sqlite3-safe.ecf"
store_mysql = "library/persistence/store_mysql/store_mysql-safe.ecf"
persistence_store_odbc = "library/persistence/store_odbc/store_odbc-safe.ecf"
tests_store_odbc = "library/persistence/store_odbc/tests/tests-safe.ecf"
admin = "modules/admin/admin-safe.ecf"
auth_module = "modules/auth/auth-safe.ecf"
cms_blog_module = "modules/blog/cms_blog_module-safe.ecf"
feed_aggregator = "modules/feed_aggregator/feed_aggregator-safe.ecf"
oauth_module = "modules/oauth20/oauth20-safe.ecf"
openid_module = "modules/openid/openid-safe.ecf"
recent_changes = "modules/recent_changes/recent_changes-safe.ecf"
note
title: ROC CMS
@@ -27,5 +42,6 @@ note
license: Eiffel Forum v2
copyright: Jocelyn Fiat, Javier Velilla, Eiffel Software
link[source]: "Github" https://github.com/EiffelWebFramework/ROC.git
-- link[doc]: "Documentation" http://
end

94
src/cache/cms_cache.e vendored Normal file
View File

@@ -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

150
src/cache/cms_file_cache.e vendored Normal file
View File

@@ -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
-- <Precursor>
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
-- <Precursor>
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
-- <Precursor>
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)
-- <Precursor>
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

49
src/cache/cms_file_object_cache.e vendored Normal file
View File

@@ -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

69
src/cache/cms_file_string_8_cache.e vendored Normal file
View File

@@ -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

61
src/cache/cms_memory_cache.e vendored Normal file
View File

@@ -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
-- <Precursor>
do
create Result.make_now_utc
end
item: detachable G
feature -- Element change
delete
-- <Precursor>
local
l_default: detachable G
do
item := l_default
cache_date_time := current_date_time
end
put (g: G)
-- <Precursor>
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

View File

@@ -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
@@ -86,13 +98,19 @@ feature -- Access
local
retried: BOOLEAN
f: FILE
l_chain: NOTIFICATION_CHAIN_MAILER
l_storage_mailer: NOTIFICATION_STORAGE_MAILER
l_mailer: detachable NOTIFICATION_MAILER
do
if not retried then
if attached text_item ("mailer.smtp") as l_smtp then
create {NOTIFICATION_SMTP_MAILER} mailer.make (l_smtp)
create {NOTIFICATION_SMTP_MAILER} l_mailer.make (l_smtp)
elseif attached text_item ("mailer.sendmail") as l_sendmail then
create {NOTIFICATION_SENDMAIL_MAILER} mailer.make_with_location (l_sendmail)
elseif attached text_item ("mailer.output") as l_output then
create {NOTIFICATION_SENDMAIL_MAILER} l_mailer.make_with_location (l_sendmail)
end
-- If a mailer.ouput is set, set a notification chain with potential previous mailer
-- and file storage.
if attached text_item ("mailer.output") as l_output then
if l_output.is_case_insensitive_equal ("@stderr") then
f := io.error
elseif l_output.is_case_insensitive_equal ("@stdout") then
@@ -104,10 +122,18 @@ feature -- Access
f.close
end
end
create {NOTIFICATION_STORAGE_MAILER} mailer.make (create {NOTIFICATION_EMAIL_FILE_STORAGE}.make (f))
create {NOTIFICATION_STORAGE_MAILER} l_storage_mailer.make (create {NOTIFICATION_EMAIL_FILE_STORAGE}.make (f))
if l_mailer /= Void then
create l_chain.make (l_mailer)
l_chain.set_next (l_storage_mailer)
l_mailer := l_chain
else
create {NOTIFICATION_STORAGE_MAILER} mailer.make (create {NOTIFICATION_EMAIL_FILE_STORAGE}.make (io.error))
l_mailer := l_storage_mailer
end
elseif l_mailer = Void then
create {NOTIFICATION_STORAGE_MAILER} l_mailer.make (create {NOTIFICATION_EMAIL_FILE_STORAGE}.make (io.error))
end
mailer := l_mailer
else
check valid_mailer: False end
-- FIXME: should we report persistent error message? If yes, see how.

View File

@@ -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

View File

@@ -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

View File

@@ -138,6 +138,11 @@ 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
l_ok: BOOLEAN
l_block_cache: detachable TUPLE [block: CMS_CACHE_BLOCK; region: READABLE_STRING_8; expired: BOOLEAN]
do
if attached subscribers ({CMS_HOOK_BLOCK}) as lst then
across
@@ -147,7 +152,25 @@ feature -- Hook: block
across
h.block_list as blst
loop
h.get_block_view (blst.item, a_response)
l_ok := False
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
l_ok := True
end
else
l_ok := True
end
if l_ok then
l_block_cache := a_response.block_cache (bl)
if l_block_cache /= Void and then not l_block_cache.expired then
a_response.add_block (l_block_cache.block, l_block_cache.region)
else
h.get_block_view (bl, a_response)
end
end
end
end
end

View File

@@ -0,0 +1,24 @@
note
description: "Condition for block to be displayed."
date: "$Date$"
revision: "$Revision$"
deferred class
CMS_BLOCK_CONDITION
feature -- Access
description: READABLE_STRING_32
deferred
end
feature -- Evaluation
satisfied_for_response (res: CMS_RESPONSE): BOOLEAN
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

View File

@@ -0,0 +1,60 @@
note
description: "Condition for block to be displayed."
date: "$Date$"
revision: "$Revision$"
class
CMS_BLOCK_EXPRESSION_CONDITION
inherit
CMS_BLOCK_CONDITION
create
make,
make_none
feature {NONE} -- Initialization
make (a_exp: READABLE_STRING_8)
do
expression := a_exp
end
make_none
do
make ("<none>")
end
feature -- Access
description: STRING_32
do
create Result.make_from_string_general ("Expression: %"")
Result.append_string_general (expression)
Result.append_character ('%"')
end
expression: STRING
feature -- Evaluation
satisfied_for_response (res: CMS_RESPONSE): BOOLEAN
local
exp: like expression
do
exp := expression
if exp.same_string ("is_front") then
Result := res.is_front
elseif exp.same_string ("*") then
Result := True
elseif exp.same_string ("<none>") then
Result := False
elseif exp.starts_with ("path:") then
Result := res.location.same_string (exp.substring (6, exp.count))
end
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

View File

@@ -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

View File

@@ -39,6 +39,9 @@ feature -- Status report
deferred
end
conditions: detachable LIST [CMS_BLOCK_CONDITION]
-- Optional block condition to be enabled.
feature -- Element change
add_css_class (a_class: READABLE_STRING_8)
@@ -67,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

View File

@@ -0,0 +1,99 @@
note
description: "[
CMS_BLOCK implemented with a `cache'
as caching solution.
]"
date: "$Date: 2014-11-18 10:13:13 +0100 (mar., 18 nov. 2014) $"
revision: "$Revision: 96110 $"
class
CMS_CACHE_BLOCK
inherit
CMS_BLOCK
create
make
feature {NONE} -- Initialization
make (a_name: like name; a_cache: like cache)
require
a_name_not_blank: not a_name.is_whitespace
do
is_enabled := True
name := a_name
cache := a_cache
set_is_raw (True)
end
feature -- Access
name: READABLE_STRING_8
-- <Precursor>
title: detachable READABLE_STRING_32
-- <Precursor>
cache: CMS_CACHE [READABLE_STRING_8]
-- Cache content.
feature -- Status report
is_empty: BOOLEAN
-- Is current block empty?
do
Result := is_raw and not cache.exists
end
is_raw: BOOLEAN assign set_is_raw
-- Is raw?
-- If True, do not get wrapped it with block specific div
feature -- Element change
set_is_raw (b: BOOLEAN)
do
is_raw := b
end
set_name (n: like name)
-- Set `name' to `n'.
require
not n.is_whitespace
do
name := n
end
set_title (a_title: like title)
-- Set `title' to `a_title'.
do
title := a_title
end
feature -- Conversion
to_html (a_theme: CMS_THEME): STRING_8
do
debug
print ("REUSE CACHE for [" + name + "]!!!%N")
end
-- Why in this particular case theme is not used to generate the content?
if attached cache.item as l_content then
Result := l_content
else
Result := ""
check exists: False end
end
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)"
source: "[
Eiffel Software
5949 Hollister Ave., Goleta, CA 93117 USA
Telephone 805-685-1006, Fax 805-685-6869
Website http://www.eiffel.com
Customer support http://support.eiffel.com
]"
end

View File

@@ -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,15 +67,14 @@ 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)
b.add_condition (create {CMS_BLOCK_EXPRESSION_CONDITION}.make_none)
a_response.add_block (b, "footer")
end
end
feature -- Handler

View File

@@ -423,6 +423,59 @@ 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
block_cache (a_block_id: READABLE_STRING_8): detachable TUPLE [block: CMS_CACHE_BLOCK; region: READABLE_STRING_8; expired: BOOLEAN]
-- Cached version of block `a_block_id'.
local
l_cache: CMS_FILE_STRING_8_CACHE
do
if
attached setup.text_item ("blocks." + a_block_id + ".expiration") as nb_secs and then
nb_secs.is_integer
then
if attached block_region_preference (a_block_id, "none") as l_region and then not l_region.same_string_general ("none") then
create l_cache.make (api.files_location.extended (".cache").extended ("blocks").extended (a_block_id).appended_with_extension ("html"))
if
l_cache.exists and then
not l_cache.expired (Void, nb_secs.to_integer)
then
Result := [create {CMS_CACHE_BLOCK} .make (a_block_id, l_cache), l_region, False]
else
Result := [create {CMS_CACHE_BLOCK} .make (a_block_id, l_cache), l_region, True]
end
end
end
end
feature -- Blocks regions
regions: STRING_TABLE [CMS_BLOCK_REGION]
@@ -459,8 +512,23 @@ feature -- Blocks regions
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 +541,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 <a href=%"http://www.eiffel.com/%">EWF</a>", Void), "footer")
put_block (create {CMS_CONTENT_BLOCK}.make ("made_with", Void, "Made with <a href=%"https://www.eiffel.org/%">EWF</a>", Void), "footer", True)
end
end
@@ -741,6 +809,7 @@ feature -- Generation
l_region: CMS_BLOCK_REGION
l_menu_list_prepared: ARRAYED_LIST [CMS_LINK_COMPOSITE]
l_empty_blocks: detachable ARRAYED_LIST [CMS_BLOCK]
l_block_html: STRING
do
-- Menu
create {CMS_LOCAL_LINK} lnk.make ("Home", "")
@@ -839,7 +908,13 @@ feature -- Generation
end
end
end
page.add_to_region (theme.block_html (ic.item), reg_ic.item.name)
l_block_html := theme.block_html (ic.item)
if attached {CMS_CACHE_BLOCK} ic.item then
elseif attached block_cache (ic.item.name) as l_cache_block then
l_cache_block.block.cache.put (l_block_html)
end
page.add_to_region (l_block_html, reg_ic.item.name)
end
end
@@ -995,6 +1070,7 @@ feature {NONE} -- Execution
page: CMS_HTML_PAGE_RESPONSE
utf: UTF_CONVERTER
h: HTTP_HEADER
l_new_location: READABLE_STRING_8
do
if attached {READABLE_STRING_GENERAL} values.item ("optional_content_type") as l_type then
create cms_page.make_typed (utf.utf_32_string_to_utf_8_string_8 (l_type))
@@ -1010,12 +1086,12 @@ feature {NONE} -- Execution
if attached redirection as l_location then
-- FIXME: find out if this is safe or not.
if l_location.has_substring ("://") then
-- h.put_location (l_location)
response.redirect_now (l_location)
l_new_location := l_location
else
-- h.put_location (request.absolute_url (l_location, Void))
response.redirect_now (absolute_url (l_location, Void))
l_new_location := absolute_url (l_location, Void)
end
-- h.put_location (l_new_location)
response.redirect_now (l_new_location)
else
h.put_header_object (header)

View File

@@ -20,9 +20,10 @@ feature -- Generation
custom_prepare (page: CMS_HTML_PAGE)
do
set_status_code ({HTTP_STATUS_CODE}.bad_request)
page.register_variable (absolute_url (request.percent_encoded_path_info, Void), "request")
page.set_status_code ({HTTP_STATUS_CODE}.bad_request)
page.register_variable (page.status_code.out, "code")
page.set_status_code (status_code)
page.register_variable (status_code.out, "code")
end
feature -- Execution

View File

@@ -20,9 +20,10 @@ feature -- Generation
custom_prepare (page: CMS_HTML_PAGE)
do
set_status_code ({HTTP_STATUS_CODE}.forbidden)
page.register_variable (absolute_url (request.percent_encoded_path_info, Void), "request")
page.set_status_code ({HTTP_STATUS_CODE}.forbidden)
page.register_variable (page.status_code.out, "code")
page.set_status_code (status_code)
page.register_variable (status_code.out, "code")
end
feature -- Execution

View File

@@ -20,9 +20,10 @@ feature -- Generation
custom_prepare (page: CMS_HTML_PAGE)
do
set_status_code ({HTTP_STATUS_CODE}.internal_server_error)
page.register_variable (absolute_url (location, Void), "request")
page.set_status_code ({HTTP_STATUS_CODE}.internal_server_error)
page.register_variable (page.status_code.out, "code")
page.set_status_code (status_code)
page.register_variable (status_code.out, "code")
end
feature -- Execution

View File

@@ -20,9 +20,10 @@ feature -- Generation
custom_prepare (page: CMS_HTML_PAGE)
do
set_status_code ({HTTP_STATUS_CODE}.not_found)
page.register_variable (absolute_url (request.percent_encoded_path_info, Void), "request")
page.set_status_code ({HTTP_STATUS_CODE}.not_found)
page.register_variable (page.status_code.out, "code")
page.set_status_code (status_code)
page.register_variable (status_code.out, "code")
end
feature -- Execution

View File

@@ -20,9 +20,10 @@ feature -- Generation
custom_prepare (page: CMS_HTML_PAGE)
do
set_status_code ({HTTP_STATUS_CODE}.not_implemented)
page.register_variable (absolute_url (request.percent_encoded_path_info, Void), "request")
page.set_status_code ({HTTP_STATUS_CODE}.not_implemented)
page.register_variable (page.status_code.out, "code")
page.set_status_code (status_code)
page.register_variable (status_code.out, "code")
end
feature -- Execution