From 7c398a9f330a50cf69ed1fcb7c29fb7c0133c641 Mon Sep 17 00:00:00 2001
From: Jocelyn Fiat
Date: Fri, 27 Jan 2017 11:57:52 +0100
Subject: [PATCH] Added support for user, user_roles, page, blog export and
import. Added basic support for comments, for now mainly viewing comments
from database (no submission forms yet). Added first simple wikitext filter
(render wikitext content as xhtml). Ensure response content type is text/html
with utf-8 charset.
---
cms-safe.ecf | 2 +-
cms.ecf | 4 +-
examples/demo/.gitignore | 2 +
examples/demo/{demo-safe.ecf => demo.ecf} | 1 +
examples/demo/roc.cfg | 3 +-
.../modules/comments/files/css/comments.css | 18 +
.../modules/comments/files/scss/comments.scss | 17 +
.../site/modules/comments/scripts/install.sql | 13 +
examples/demo/src/demo_cms_execution.e | 4 +-
modules/blog/cms_blog_api.e | 2 +-
modules/blog/cms_blog_module-safe.ecf | 1 +
modules/blog/cms_blog_module.e | 87 ++--
modules/comments/cms_comment.e | 178 ++++++++
modules/comments/cms_comments_api.e | 207 +++++++++
modules/comments/cms_comments_module.e | 96 +++++
modules/comments/cms_partial_comment.e | 25 ++
modules/comments/comments-safe.ecf | 28 ++
modules/comments/comments.ecf | 28 ++
.../persistence/cms_comments_storage_i.e | 38 ++
.../persistence/cms_comments_storage_null.e | 46 ++
.../persistence/cms_comments_storage_sql.e | 187 ++++++++
modules/comments/site/files/css/comments.css | 18 +
.../comments/site/files/scss/comments.scss | 17 +
modules/comments/site/scripts/install.sql | 13 +
modules/node/cms_node_module.e | 4 -
.../node/export/cms_export_node_utilities.e | 2 +-
.../handler/cms_node_type_webform_manager.e | 80 +++-
.../node/import/cms_import_node_utilities.e | 177 +++++++-
modules/node/node-safe.ecf | 1 +
modules/node/node.ecf | 4 +-
.../node/submodules/page/cms_page_module.e | 121 +++---
modules/wikitext/wikitext_filter.e | 17 +-
src/hooks/import/cms_hook_import.e | 2 +-
src/hooks/import/cms_import_json_utilities.e | 72 +++-
src/persistence/user/cms_user_storage_i.e | 4 +-
src/persistence/user/cms_user_storage_null.e | 4 +-
src/persistence/user/cms_user_storage_sql_i.e | 2 +-
src/service/cms_api.e | 128 +-----
src/service/cms_api_export_imp.e | 142 +++++++
src/service/cms_api_import_imp.e | 225 ++++++++++
src/service/cms_file_system_utilities.e | 124 ++++++
src/service/response/cms_response.e | 4 +-
src/service/user/cms_user_api.e | 2 +-
src/support/date_time_ago_converter.e | 401 ++++++++++++++++++
src/theme/cms_html_page_response.e | 4 +-
45 files changed, 2284 insertions(+), 271 deletions(-)
rename examples/demo/{demo-safe.ecf => demo.ecf} (98%)
create mode 100644 examples/demo/site/modules/comments/files/css/comments.css
create mode 100644 examples/demo/site/modules/comments/files/scss/comments.scss
create mode 100644 examples/demo/site/modules/comments/scripts/install.sql
create mode 100644 modules/comments/cms_comment.e
create mode 100644 modules/comments/cms_comments_api.e
create mode 100644 modules/comments/cms_comments_module.e
create mode 100644 modules/comments/cms_partial_comment.e
create mode 100644 modules/comments/comments-safe.ecf
create mode 100644 modules/comments/comments.ecf
create mode 100644 modules/comments/persistence/cms_comments_storage_i.e
create mode 100644 modules/comments/persistence/cms_comments_storage_null.e
create mode 100644 modules/comments/persistence/cms_comments_storage_sql.e
create mode 100644 modules/comments/site/files/css/comments.css
create mode 100644 modules/comments/site/files/scss/comments.scss
create mode 100644 modules/comments/site/scripts/install.sql
create mode 100644 src/service/cms_api_export_imp.e
create mode 100644 src/service/cms_api_import_imp.e
create mode 100644 src/service/cms_file_system_utilities.e
create mode 100644 src/support/date_time_ago_converter.e
diff --git a/cms-safe.ecf b/cms-safe.ecf
index 7167f3e..c91a8c7 100644
--- a/cms-safe.ecf
+++ b/cms-safe.ecf
@@ -8,7 +8,6 @@
/EIFGENs$
@@ -30,6 +29,7 @@
+
diff --git a/cms.ecf b/cms.ecf
index 9796655..446990a 100644
--- a/cms.ecf
+++ b/cms.ecf
@@ -8,8 +8,7 @@
/CVS$/EIFGENs$
-
@@ -30,6 +29,7 @@
+
diff --git a/examples/demo/.gitignore b/examples/demo/.gitignore
index f55f045..3b9e676 100644
--- a/examples/demo/.gitignore
+++ b/examples/demo/.gitignore
@@ -1,2 +1,4 @@
site/db/*
site/files/*
+site/export/*
+site/import/*
diff --git a/examples/demo/demo-safe.ecf b/examples/demo/demo.ecf
similarity index 98%
rename from examples/demo/demo-safe.ecf
rename to examples/demo/demo.ecf
index ffe7c87..861dbe7 100644
--- a/examples/demo/demo-safe.ecf
+++ b/examples/demo/demo.ecf
@@ -24,6 +24,7 @@
+
diff --git a/examples/demo/roc.cfg b/examples/demo/roc.cfg
index 60fc388..379462a 100644
--- a/examples/demo/roc.cfg
+++ b/examples/demo/roc.cfg
@@ -20,6 +20,7 @@
"taxonomy": { "location": "../../modules/taxonomy" },
"files": { "location": "../../modules/files" },
"custom_block": { "location": "../../modules/custom_block" },
- "wikitext": { "location": "../../modules/wikitext" }
+ "wikitext": { "location": "../../modules/wikitext" },
+ "comments": { "location": "../../modules/comments" }
}
}
diff --git a/examples/demo/site/modules/comments/files/css/comments.css b/examples/demo/site/modules/comments/files/css/comments.css
new file mode 100644
index 0000000..487805b
--- /dev/null
+++ b/examples/demo/site/modules/comments/files/css/comments.css
@@ -0,0 +1,18 @@
+div.comments-box div.title {
+ font-size: x-large;
+}
+div.comments-box ul.comments {
+ border-top: solid 1px #eee;
+ padding: 0 0 0 20px;
+ border-left: solid 5px #eee;
+ list-style-type: none;
+}
+div.comments-box ul.comments li.comment {
+ border-bottom: solid 1px #eee;
+}
+div.comments-box ul.comments li.comment > span.author {
+ font-weight: bold;
+}
+div.comments-box ul.comments li.comment > span.info {
+ font-style: italic;
+}
diff --git a/examples/demo/site/modules/comments/files/scss/comments.scss b/examples/demo/site/modules/comments/files/scss/comments.scss
new file mode 100644
index 0000000..b8b52b0
--- /dev/null
+++ b/examples/demo/site/modules/comments/files/scss/comments.scss
@@ -0,0 +1,17 @@
+div.comments-box {
+ div.title {
+ font-size: x-large;
+ }
+ ul.comments {
+ border-top: solid 1px #eee;
+ padding: 0 0 0 20px;
+ border-left: solid 5px #eee;
+ list-style-type: none;
+ li.comment {
+ border-bottom: solid 1px #eee;
+ &>span.author { font-weight: bold; }
+ &>span.info { font-style: italic; }
+ }
+ }
+
+}
diff --git a/examples/demo/site/modules/comments/scripts/install.sql b/examples/demo/site/modules/comments/scripts/install.sql
new file mode 100644
index 0000000..593b628
--- /dev/null
+++ b/examples/demo/site/modules/comments/scripts/install.sql
@@ -0,0 +1,13 @@
+CREATE TABLE comments(
+ `cid` INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT UNIQUE,
+ `content` TEXT,
+ `format` VARCHAR(128),
+ `author` INTEGER,
+ `author_name` VARCHAR(255),
+ `created` DATETIME NOT NULL,
+ `changed` DATETIME NOT NULL,
+ `status` INTEGER,
+ `parent` INTEGER,
+ `entity` VARCHAR(255), /* Associated entity */
+ `entity_type` VARCHAR(255) /* Type of associated entity */
+);
diff --git a/examples/demo/src/demo_cms_execution.e b/examples/demo/src/demo_cms_execution.e
index e6ae9ac..8ba03e4 100644
--- a/examples/demo/src/demo_cms_execution.e
+++ b/examples/demo/src/demo_cms_execution.e
@@ -53,9 +53,6 @@ feature -- CMS modules
a_setup.register_module (create {CMS_OPENID_MODULE}.make)
a_setup.register_module (create {CMS_SESSION_AUTH_MODULE}.make)
- -- User
- a_setup.register_module (create {CMS_USER_PROFILE_MODULE}.make)
-
-- Nodes
a_setup.register_module (create {CMS_NODE_MODULE}.make (a_setup))
a_setup.register_module (create {CMS_PAGE_MODULE}.make)
@@ -69,6 +66,7 @@ feature -- CMS modules
-- Misc
a_setup.register_module (create {CMS_SEO_MODULE}.make)
+ a_setup.register_module (create {CMS_COMMENTS_MODULE}.make)
-- Taxonomy
a_setup.register_module (create {CMS_TAXONOMY_MODULE}.make)
diff --git a/modules/blog/cms_blog_api.e b/modules/blog/cms_blog_api.e
index b13b951..ba9cde0 100644
--- a/modules/blog/cms_blog_api.e
+++ b/modules/blog/cms_blog_api.e
@@ -43,7 +43,7 @@ feature {NONE} -- Initialization
--| For test reasons this is 2, so we don't have to create a lot of blog entries.
--| TODO: Set to bigger constant.
- entries_per_page := 2
+ entries_per_page := 6
-- initialize_node_types
end
diff --git a/modules/blog/cms_blog_module-safe.ecf b/modules/blog/cms_blog_module-safe.ecf
index 52a0e0d..214bbbd 100644
--- a/modules/blog/cms_blog_module-safe.ecf
+++ b/modules/blog/cms_blog_module-safe.ecf
@@ -14,6 +14,7 @@
+
diff --git a/modules/blog/cms_blog_module.e b/modules/blog/cms_blog_module.e
index 3313b3c..20ae020 100644
--- a/modules/blog/cms_blog_module.e
+++ b/modules/blog/cms_blog_module.e
@@ -241,12 +241,11 @@ feature -- Hooks
-- Import data identified by `a_import_id_list',
-- or import all data if `a_import_id_list' is Void.
local
- p: PATH
+ p,fp: PATH
d: DIRECTORY
- f: PLAIN_TEXT_FILE
- s: STRING
- jp: JSON_PARSER
loc: STRING
+ l_id: STRING_32
+ l_entity: detachable CMS_BLOG
do
if
attached node_api as l_node_api and then
@@ -267,51 +266,51 @@ feature -- Hooks
d.entries as ic
loop
if attached ic.item.extension as ext and then ext.same_string_general ("json") then
- create f.make_with_path (p.extended_path (ic.item))
- if f.exists and then f.is_access_readable then
- f.open_read
- from
- create s.make (0)
- until
- f.exhausted or f.end_of_file
- loop
- f.read_stream (1_024)
- s.append (f.last_string)
- end
- f.close
- create jp.make_with_string (s)
- jp.parse_content
- if jp.is_valid and then attached jp.parsed_json_object as j then
- if
- attached json_string_item (j, "type") as l_type and then
- l_type.same_string_general (l_node_type.name)
- then
- if attached json_to_node_blog (l_node_type, j, l_node_api) as l_blog then
- if l_blog.is_published then
- if attached l_blog.author as l_author then
- if
- attached l_blog_api.blogs_by_title (l_author, l_blog.title) as l_blogs and then
- not l_blogs.is_empty
- then
- -- Blog Already exists!
- -- FIXME/TODO
- a_import_ctx.log (l_node_type.name + " %"" + f.path.utf_8_name + "%" skipped (already exists for user #" + l_author.id.out + ")!")
- else
- a_import_ctx.log (l_node_type.name + " %"" + f.path.utf_8_name + "%" imported for user #" + l_author.id.out + ".")
- l_blog_api.save_blog (l_blog)
- if attached {CMS_LOCAL_LINK} l_blog.link as l_link then
- loc := l_node_api.node_path (l_blog)
- if not l_link.location.starts_with_general ("node/") then
- l_blog_api.cms_api.set_path_alias (loc, l_link.location, False)
- end
+ l_id := ic.item.name
+ l_id.remove_tail (ext.count + 1)
+ fp := p.extended_path (ic.item)
+ if attached json_object_from_location (fp) as j then
+ if
+ attached json_string_item (j, "type") as l_type and then
+ l_type.same_string_general (l_node_type.name)
+ then
+ l_entity := json_to_node_blog (l_node_type, j, l_node_api)
+ if l_entity /= Void then
+ if l_entity.is_published then
+ if l_entity.author = Void then
+ -- FIXME!!!
+ l_entity.set_author (l_blog_api.cms_api.user)
+ a_import_ctx.log (l_node_type.name + " %"" + fp.utf_8_name + "%" WARNING (Author is unknown!)")
+ end
+ if attached l_entity.author as l_author then
+ if
+ attached l_blog_api.blogs_by_title (l_author, l_entity.title) as l_blogs and then
+ not l_blogs.is_empty
+ then
+ -- Blog Already exists!
+ -- FIXME/TODO
+ l_entity := l_blogs.first
+ a_import_ctx.log (l_node_type.name + " %"" + fp.utf_8_name + "%" skipped (already exists for user #" + l_author.id.out + ")!")
+ else
+ a_import_ctx.log (l_node_type.name + " %"" + fp.utf_8_name + "%" imported for user #" + l_author.id.out + ".")
+ l_blog_api.save_blog (l_entity)
+ apply_taxonomy_to_node (j, l_entity, l_blog_api.cms_api)
+ if attached {CMS_LOCAL_LINK} l_entity.link as l_link then
+ loc := l_node_api.node_path (l_entity)
+ if not l_link.location.starts_with_general ("node/") then
+ l_blog_api.cms_api.set_path_alias (loc, l_link.location, False)
end
end
- else
- a_import_ctx.log (l_node_type.name + " %"" + f.path.utf_8_name + "%" skipped (Author is unknown!)")
+ end
+ if l_entity /= Void and then l_entity.has_id then
+ -- Support for comments
+ import_comments_file_for_entity (p.extended (l_id).extended ("comments.json"), l_entity, l_node_api.cms_api, a_import_ctx)
end
else
- a_import_ctx.log (l_node_type.name + " %"" + f.path.utf_8_name + "%" skipped (Status is Not Published!)")
+ a_import_ctx.log (l_node_type.name + " %"" + fp.utf_8_name + "%" skipped (Author is unknown!)")
end
+ else
+ a_import_ctx.log (l_node_type.name + " %"" + fp.utf_8_name + "%" skipped (Status is Not Published!)")
end
end
end
diff --git a/modules/comments/cms_comment.e b/modules/comments/cms_comment.e
new file mode 100644
index 0000000..852f703
--- /dev/null
+++ b/modules/comments/cms_comment.e
@@ -0,0 +1,178 @@
+note
+ description: "[
+ Comment object.
+ ]"
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ CMS_COMMENT
+
+inherit
+ ITERABLE [CMS_COMMENT]
+ undefine
+ is_equal
+ end
+
+ COMPARABLE
+
+ DEBUG_OUTPUT
+ undefine
+ is_equal
+ end
+
+create
+ make
+
+feature {NONE} -- Initialization
+
+ make
+ -- Create Current profile.
+ do
+ create {ARRAYED_LIST [CMS_COMMENT]} items.make (0)
+ create modification_date.make_now_utc
+ creation_date := modification_date
+ end
+
+feature -- Access
+
+ id: INTEGER_64 assign set_id
+ -- Unique id.
+ --| Should we use NATURAL_64 instead?
+
+ content: detachable READABLE_STRING_32
+
+ format: detachable READABLE_STRING_8
+
+ author: detachable CMS_USER
+
+ author_name: detachable READABLE_STRING_32
+ -- Author name if no CMS user is associated.
+
+ creation_date: DATE_TIME
+ -- When the comment was created.
+
+ modification_date: DATE_TIME
+ -- When the comment was updated.
+
+ status: INTEGER
+ -- Status of Current comment.
+
+ parent: detachable CMS_COMMENT
+
+ entity: detachable CMS_CONTENT
+ -- Associated content.
+
+feature -- Access
+
+ has_id: BOOLEAN
+ do
+ Result := id > 0
+ end
+
+ new_cursor: ITERATION_CURSOR [CMS_COMMENT]
+ -- Fresh cursor associated with current structure
+ do
+ Result := items.new_cursor
+ end
+
+feature -- Optional
+
+ items: LIST [CMS_COMMENT]
+
+ extend (c: CMS_COMMENT)
+ do
+ items.extend (c)
+ end
+
+ count: INTEGER
+ do
+ Result := items.count
+ end
+
+feature -- Comparison
+
+ is_less alias "<" (other: like Current): BOOLEAN
+ -- Is current object less than `other'?
+ do
+ Result := creation_date < other.creation_date
+ end
+
+feature -- Status report
+
+ debug_output: STRING_32
+ --
+ do
+ create Result.make_empty
+ Result.append_character ('#')
+ Result.append_integer_64 (id)
+ if attached content as l_content then
+ Result.append_character (' ')
+ Result.append_character ('%"')
+ Result.append (l_content.head (25))
+ if l_content.count > 25 then
+ Result.append ("...")
+ end
+ Result.append_character ('%"')
+ end
+ end
+
+feature -- Change
+
+ set_id (a_id: like id)
+ require
+ a_id_positive: a_id > 0
+ do
+ id := a_id
+ end
+
+ set_content (s: like content)
+ do
+ content := s
+ end
+
+ set_format (f: like format)
+ do
+ format := f
+ end
+
+ set_author (u: detachable CMS_USER)
+ do
+ author := u
+ end
+
+ set_author_name (n: like author_name)
+ do
+ author_name := n
+ end
+
+ set_creation_date (dt: DATE_TIME)
+ do
+ creation_date := dt
+ end
+
+ set_modification_date (dt: DATE_TIME)
+ do
+ modification_date := dt
+ end
+
+ set_status (st: like status)
+ do
+ status := st
+ end
+
+ set_parent (p: detachable CMS_COMMENT)
+ do
+ parent := p
+ end
+
+ set_entity (e: like entity)
+ do
+ entity := e
+ end
+
+
+;note
+ copyright: "2011-2014, Javier Velilla, Jocelyn Fiat, Eiffel Software and others"
+ license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
+end
diff --git a/modules/comments/cms_comments_api.e b/modules/comments/cms_comments_api.e
new file mode 100644
index 0000000..a5298c4
--- /dev/null
+++ b/modules/comments/cms_comments_api.e
@@ -0,0 +1,207 @@
+note
+ description: "API to handle user profiles."
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ CMS_COMMENTS_API
+
+inherit
+ CMS_MODULE_API
+ redefine
+ initialize
+ end
+
+ REFACTORING_HELPER
+
+create {CMS_COMMENTS_MODULE}
+ make
+
+feature {NONE} -- Initialization
+
+ initialize
+ -- Create user profile api object with api `a_api' and storage `a_storage'.
+ do
+ Precursor
+
+ if attached storage.as_sql_storage as l_storage_sql then
+ create {CMS_COMMENTS_STORAGE_SQL} comments_storage.make (l_storage_sql)
+ else
+ create {CMS_COMMENTS_STORAGE_NULL} comments_storage.make
+ end
+ end
+
+feature {CMS_MODULE} -- Access nodes storage.
+
+ comments_storage: CMS_COMMENTS_STORAGE_I
+
+feature -- Access
+
+ comment (a_cid: INTEGER_64): detachable CMS_COMMENT
+ require
+ a_cid > 0
+ do
+ Result := comments_storage.comment (a_cid)
+ if Result /= Void then
+ Result := recursive_full_comment (Result)
+ end
+ end
+
+ comments_for (a_content: CMS_CONTENT): detachable LIST [CMS_COMMENT]
+ -- Comments for `a_content`.
+ require
+ valid_content: a_content.has_identifier
+ local
+ l_comment, l_parent: detachable CMS_COMMENT
+
+ tb: HASH_TABLE [CMS_COMMENT, INTEGER_64] -- indexed by "comment id".
+ do
+ if
+ attached comments_storage.comments_for (a_content) as l_flat_lst and then not l_flat_lst.is_empty and then
+ attached full_comments (l_flat_lst) as lst
+ then
+ create {ARRAYED_LIST [CMS_COMMENT]} Result.make (lst.count)
+ create tb.make (lst.count)
+ across
+ lst as ic
+ loop
+ l_comment := ic.item
+ tb.put (l_comment, l_comment.id)
+ if l_comment.parent = Void then
+ Result.extend (l_comment)
+ end
+ end
+ across
+ lst as ic
+ loop
+ l_comment := ic.item
+ l_parent := l_comment.parent
+ if attached {CMS_PARTIAL_COMMENT} l_parent as l_partial then
+ l_parent := tb.item (l_partial.id)
+ l_comment.set_parent (l_parent)
+ end
+ if l_parent /= Void then
+ l_parent.extend (l_comment)
+ end
+ end
+ sort_comments (Result)
+ end
+ end
+
+ full_comment (a_comment: CMS_COMMENT): CMS_COMMENT
+ -- If `a_comment' is partial, return the full object from `a_comment',
+ -- otherwise return directly `a_comment'.
+ require
+ a_comment_set: a_comment /= Void
+ local
+ l_comment: detachable CMS_COMMENT
+ do
+ if attached {CMS_PARTIAL_COMMENT} a_comment as l_partial_comment then
+ l_comment := comment (l_partial_comment.id)
+ if l_comment /= Void then
+ Result := l_comment
+ else
+ Result := l_partial_comment
+ end
+ else
+ Result := a_comment
+ end
+
+ -- Update partial user if needed.
+ if Result /= Void then
+ if attached {CMS_PARTIAL_USER} Result.author as l_partial_author then
+ if attached cms_api.user_api.user_by_id (l_partial_author.id) as l_author then
+ Result.set_author (l_author)
+ else
+ check
+ valid_author_id: False
+ end
+ end
+ end
+ end
+ end
+
+ full_comments (a_comments: LIST [CMS_COMMENT]): LIST [CMS_COMMENT]
+ -- Convert list of potentially partial comments into a list of comments when possible.
+ do
+ create {ARRAYED_LIST [CMS_COMMENT]} Result.make (a_comments.count)
+ across
+ a_comments as ic
+ loop
+ Result.force (full_comment (ic.item))
+ end
+ end
+
+ recursive_full_comment (a_comment: CMS_COMMENT): CMS_COMMENT
+ local
+ lst: LIST [CMS_COMMENT]
+ do
+ Result := full_comment (a_comment)
+ from
+ lst := a_comment.items
+ lst.start
+ until
+ lst.after
+ loop
+ lst.replace (recursive_full_comment (lst.item))
+ lst.forth
+ end
+ end
+
+feature -- Change: profile
+
+ save_comment (a_comment: CMS_COMMENT)
+ -- Save `a_comment`.
+ do
+ comments_storage.save_comment (a_comment)
+ end
+
+ save_recursively_comment (a_comment: CMS_COMMENT)
+ do
+ save_comment (a_comment)
+ across
+ a_comment as ic
+ loop
+ ic.item.set_parent (a_comment)
+ save_recursively_comment (ic.item)
+ end
+ end
+
+feature -- Helper
+
+ has_cycle_in_comments (a_comments: ITERABLE [CMS_COMMENT]; a_known_comments: detachable LIST [CMS_COMMENT]): BOOLEAN
+ local
+ lst: detachable LIST [CMS_COMMENT]
+ do
+ lst := a_known_comments
+ if lst = Void then
+ create {ARRAYED_LIST [CMS_COMMENT]} lst.make (0)
+ elseif across a_comments as ic some lst.has (ic.item) end then
+ Result := True
+ end
+ if not Result then
+ across
+ a_comments as ic
+ loop
+ lst.extend (ic.item)
+ Result := has_cycle_in_comments (ic.item, lst)
+ end
+ end
+ end
+
+ sort_comments (a_items: LIST [CMS_COMMENT])
+ require
+ no_cycle: not has_cycle_in_comments (a_items, Void)
+ local
+ l_sorter: QUICK_SORTER [CMS_COMMENT]
+ do
+ create l_sorter.make (create {COMPARABLE_COMPARATOR [CMS_COMMENT]})
+ l_sorter.sort (a_items)
+ across
+ a_items as ic
+ loop
+ sort_comments (ic.item.items)
+ end
+ end
+
+end
diff --git a/modules/comments/cms_comments_module.e b/modules/comments/cms_comments_module.e
new file mode 100644
index 0000000..5e734ba
--- /dev/null
+++ b/modules/comments/cms_comments_module.e
@@ -0,0 +1,96 @@
+note
+ description: "[
+ Comments module.
+ ]"
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ CMS_COMMENTS_MODULE
+
+inherit
+ CMS_MODULE
+ rename
+ module_api as comments_api
+ redefine
+ setup_hooks,
+ initialize,
+ install,
+ comments_api
+ end
+
+ CMS_HOOK_RESPONSE_ALTER
+
+create
+ make
+
+feature {NONE} -- Initialization
+
+ make
+ do
+ version := "1.0"
+ description := "Comments"
+ package := "content"
+ end
+
+feature -- Access
+
+ name: STRING = "comments"
+
+feature {CMS_API} -- Module Initialization
+
+ initialize (api: CMS_API)
+ --
+ do
+ Precursor (api)
+ create comments_api.make (api)
+ end
+
+feature {CMS_API} -- Module management
+
+ install (a_api: CMS_API)
+ do
+ -- Schema
+ if attached a_api.storage.as_sql_storage as l_sql_storage then
+ l_sql_storage.sql_execute_file_script (a_api.module_resource_location (Current, (create {PATH}.make_from_string ("scripts")).extended ("install.sql")), Void)
+
+ if l_sql_storage.has_error then
+ a_api.logger.put_error ("Could not initialize database for module [" + name + "]", generating_type)
+ else
+ Precursor {CMS_MODULE} (a_api)
+ end
+ end
+ end
+
+feature {CMS_API} -- Access: API
+
+ comments_api: detachable CMS_COMMENTS_API
+ --
+
+feature -- Access: router
+
+ setup_router (a_router: WSF_ROUTER; a_api: CMS_API)
+ --
+ do
+ end
+
+feature -- Hooks
+
+ setup_hooks (a_hooks: CMS_HOOK_CORE_MANAGER)
+ do
+ a_hooks.subscribe_to_response_alter_hook (Current)
+-- a_hooks.subscribe_to_form_alter_hook (Current)
+ end
+
+-- form_alter (a_form: CMS_FORM; a_form_data: detachable WSF_FORM_DATA; a_response: CMS_RESPONSE)
+-- -- Hook execution on form `a_form' and its associated data `a_form_data',
+-- -- for related response `a_response'.
+-- do
+-- end
+
+ response_alter (a_response: CMS_RESPONSE)
+ do
+ a_response.add_style (a_response.url ("/module/" + name + "/files/css/comments.css", Void), Void)
+ end
+
+end
diff --git a/modules/comments/cms_partial_comment.e b/modules/comments/cms_partial_comment.e
new file mode 100644
index 0000000..fd6853e
--- /dev/null
+++ b/modules/comments/cms_partial_comment.e
@@ -0,0 +1,25 @@
+note
+ description: "Summary description for {CMS_PARTIAL_COMMENT}."
+ author: ""
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ CMS_PARTIAL_COMMENT
+
+inherit
+ CMS_COMMENT
+
+create
+ make_with_id
+
+feature {NONE} -- Initialization
+
+ make_with_id (a_cid: like id)
+ do
+ make
+ id := a_cid
+ end
+
+
+end
diff --git a/modules/comments/comments-safe.ecf b/modules/comments/comments-safe.ecf
new file mode 100644
index 0000000..738d1d8
--- /dev/null
+++ b/modules/comments/comments-safe.ecf
@@ -0,0 +1,28 @@
+
+
+
+
+
+ /.git$
+ /EIFGENs$
+ /.svn$
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/modules/comments/comments.ecf b/modules/comments/comments.ecf
new file mode 100644
index 0000000..887913d
--- /dev/null
+++ b/modules/comments/comments.ecf
@@ -0,0 +1,28 @@
+
+
+
+
+
+ /.git$
+ /EIFGENs$
+ /.svn$
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/modules/comments/persistence/cms_comments_storage_i.e b/modules/comments/persistence/cms_comments_storage_i.e
new file mode 100644
index 0000000..705f0d4
--- /dev/null
+++ b/modules/comments/persistence/cms_comments_storage_i.e
@@ -0,0 +1,38 @@
+note
+ description: "Interface for accessing comments from database."
+ date: "$Date$"
+ revision: "$Revision$"
+
+deferred class
+ CMS_COMMENTS_STORAGE_I
+
+feature -- Error Handling
+
+ error_handler: ERROR_HANDLER
+ -- Error handler.
+ deferred
+ end
+
+feature -- Access
+
+ comment (a_cid: INTEGER_64): detachable CMS_COMMENT
+ require
+ a_cid > 0
+ deferred
+ end
+
+ comments_for (a_content: CMS_CONTENT): detachable LIST [CMS_COMMENT]
+ -- Comments for `a_content`.
+ require
+ has_id: a_content.has_identifier
+ deferred
+ end
+
+feature -- Change
+
+ save_comment (a_comment: CMS_COMMENT)
+ -- Save `a_comment`.
+ deferred
+ end
+
+end
diff --git a/modules/comments/persistence/cms_comments_storage_null.e b/modules/comments/persistence/cms_comments_storage_null.e
new file mode 100644
index 0000000..6eae1ca
--- /dev/null
+++ b/modules/comments/persistence/cms_comments_storage_null.e
@@ -0,0 +1,46 @@
+note
+ description: "Summary description for {CMS_COMMENTS_STORAGE_NULL}."
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ CMS_COMMENTS_STORAGE_NULL
+
+inherit
+ CMS_COMMENTS_STORAGE_I
+
+create
+ make
+
+feature {NONE} -- Initialization
+
+ make
+ -- Initialize `Current'.
+ do
+ create error_handler.make
+ end
+
+feature -- Error Handling
+
+ error_handler: ERROR_HANDLER
+ -- Error handler.
+
+feature -- Access
+
+ comment (a_cid: INTEGER_64): detachable CMS_COMMENT
+ do
+ end
+
+ comments_for (a_content: CMS_CONTENT): detachable LIST [CMS_COMMENT]
+ -- Comments for `a_content`.
+ do
+ end
+
+feature -- Change
+
+ save_comment (a_comment: CMS_COMMENT)
+ -- Save `a_comment`.
+ do
+ end
+
+end
diff --git a/modules/comments/persistence/cms_comments_storage_sql.e b/modules/comments/persistence/cms_comments_storage_sql.e
new file mode 100644
index 0000000..6ce4639
--- /dev/null
+++ b/modules/comments/persistence/cms_comments_storage_sql.e
@@ -0,0 +1,187 @@
+note
+ description: "Interface for accessing user profile contents from SQL database."
+ date: "$Date: 2015-05-21 14:46:00 +0100$"
+ revision: "$Revision: 96616 $"
+
+class
+ CMS_COMMENTS_STORAGE_SQL
+
+inherit
+ CMS_COMMENTS_STORAGE_I
+
+ CMS_PROXY_STORAGE_SQL
+
+create
+ make
+
+feature -- Access
+
+ comment (a_cid: INTEGER_64): detachable CMS_COMMENT
+ -- Comment with id `a_cid`.
+ local
+ l_parameters: STRING_TABLE [detachable ANY]
+ do
+ error_handler.reset
+
+ create l_parameters.make (1)
+ l_parameters.put (a_cid, "cid")
+ sql_query (sql_select_comment_by_id, l_parameters)
+ sql_start
+ if not has_error and not sql_after then
+ Result := fetch_comment
+ end
+ sql_finalize
+ end
+
+ comments_for (a_content: CMS_CONTENT): detachable LIST [CMS_COMMENT]
+ -- Comments for content `a_content`.
+ local
+ l_parameters: STRING_TABLE [detachable ANY]
+ do
+ create {ARRAYED_LIST [CMS_COMMENT]} Result.make (0)
+ error_handler.reset
+
+ create l_parameters.make (3)
+ l_parameters.put (a_content.identifier, "content_id")
+ from
+ sql_query (sql_select_comments_for_content, l_parameters)
+ sql_start
+ until
+ sql_after
+ loop
+ if attached fetch_comment as obj then
+ Result.force (obj)
+ end
+ sql_forth
+ end
+ sql_finalize
+ end
+
+feature -- Change
+
+ save_comment (a_comment: CMS_COMMENT)
+ --
+ local
+ l_parameters: STRING_TABLE [detachable ANY]
+ do
+ error_handler.reset
+
+ create l_parameters.make (11)
+
+ l_parameters.put (a_comment.content, "content")
+ l_parameters.put (a_comment.format, "format")
+ if attached a_comment.author as u then
+ l_parameters.put (u.id, "author")
+ l_parameters.put (u.name, "author_name")
+ else
+ l_parameters.put (0, "author")
+ if attached a_comment.author_name as l_author_name then
+ l_parameters.put (l_author_name, "author_name")
+ else
+ l_parameters.put ("", "author_name")
+ end
+ end
+
+ l_parameters.put (a_comment.creation_date, "created")
+ l_parameters.put (a_comment.modification_date, "changed")
+ l_parameters.put (a_comment.status, "status")
+
+ if attached a_comment.parent as p then
+ l_parameters.put (p.id, "parent")
+ end
+
+ if attached a_comment.entity as l_entity then
+ l_parameters.put (l_entity.identifier, "entity")
+ l_parameters.put (l_entity.content_type, "entity_type")
+ else
+ l_parameters.put ("", "entity")
+ l_parameters.put ("", "entity_type")
+ end
+
+ sql_begin_transaction
+ if a_comment.has_id then
+ l_parameters.put (a_comment.id, "cid")
+ sql_modify (sql_update_comment, l_parameters)
+ else
+ sql_insert (sql_insert_comment, l_parameters)
+ if not has_error then
+ a_comment.set_id (last_inserted_comment_id)
+ end
+ end
+ if has_error then
+ sql_rollback_transaction
+ else
+ sql_commit_transaction
+ end
+ sql_finalize
+ end
+
+feature {NONE} -- Implementation
+
+ fetch_comment: detachable CMS_COMMENT
+ local
+ cid, uid, pid, l_entity_id: INTEGER_64
+ l_entity: detachable CMS_PARTIAL_CONTENT
+ do
+ cid := sql_read_integer_64 (1)
+ if cid > 0 then
+ create Result.make
+ Result.set_id (cid)
+ Result.set_content (sql_read_string_32 (2))
+ Result.set_format (sql_read_string (3))
+ uid := sql_read_integer_64 (4)
+ if uid > 0 then
+ Result.set_author (create {CMS_PARTIAL_USER}.make_with_id (uid))
+ end
+ Result.set_author_name (sql_read_string_32 (5))
+ if attached sql_read_date_time (6) as dt then
+ Result.set_creation_date (dt)
+ end
+ if attached sql_read_date_time (7) as dt then
+ Result.set_modification_date (dt)
+ end
+
+ Result.set_status (sql_read_integer_32 (8))
+ pid := sql_read_integer_64 (9)
+ if pid > 0 then
+ Result.set_parent (create {CMS_PARTIAL_COMMENT}.make_with_id (pid))
+ end
+ l_entity_id := sql_read_integer_64 (10)
+ if
+ attached sql_read_string (11) as l_entity_type and
+ l_entity_id > 0
+ then
+ create l_entity.make_empty (l_entity_type)
+ l_entity.set_identifier (l_entity_id.out)
+ Result.set_entity (l_entity)
+ end
+-- l_entity_type := sql_read_string_32 (11)
+-- if l_entity_type /= Void then
+-- Result.set_en
+-- end
+ end
+ end
+
+ last_inserted_comment_id: INTEGER_64
+ -- Last insert comment id.
+ do
+ error_handler.reset
+ sql_query (Sql_last_inserted_comment_id, Void)
+ if not has_error and not sql_after then
+ Result := sql_read_integer_64 (1)
+ end
+ sql_finalize
+ end
+
+feature {NONE} -- Queries
+
+ Sql_last_inserted_comment_id: STRING = "SELECT MAX(cid) FROM comments;"
+
+ sql_select_comment_by_id: STRING = "SELECT cid,content,format,author,author_name,created,changed,status,parent,entity,entity_type FROM comments WHERE cid=:cid;"
+
+ sql_select_comments_for_content: STRING = "SELECT cid,content,format,author,author_name,created,changed,status,parent,entity,entity_type FROM comments WHERE entity=:content_id;"
+
+ sql_insert_comment: STRING = "INSERT INTO comments (content,format,author,author_name,created,changed,status,parent,entity,entity_type) VALUES (:content,:format,:author,:author_name,:created,:changed,:status,:parent,:entity,:entity_type);"
+ sql_update_comment: STRING = "UPDATE comments SET content=:content,format=:format,author=:author,author_name=:author_name,created=:created,changed=:changed,status=:status,parent=:parent,entity=:entity,entity_type=:entity_type WHERE cid=:cid ;"
+
+end
diff --git a/modules/comments/site/files/css/comments.css b/modules/comments/site/files/css/comments.css
new file mode 100644
index 0000000..487805b
--- /dev/null
+++ b/modules/comments/site/files/css/comments.css
@@ -0,0 +1,18 @@
+div.comments-box div.title {
+ font-size: x-large;
+}
+div.comments-box ul.comments {
+ border-top: solid 1px #eee;
+ padding: 0 0 0 20px;
+ border-left: solid 5px #eee;
+ list-style-type: none;
+}
+div.comments-box ul.comments li.comment {
+ border-bottom: solid 1px #eee;
+}
+div.comments-box ul.comments li.comment > span.author {
+ font-weight: bold;
+}
+div.comments-box ul.comments li.comment > span.info {
+ font-style: italic;
+}
diff --git a/modules/comments/site/files/scss/comments.scss b/modules/comments/site/files/scss/comments.scss
new file mode 100644
index 0000000..b8b52b0
--- /dev/null
+++ b/modules/comments/site/files/scss/comments.scss
@@ -0,0 +1,17 @@
+div.comments-box {
+ div.title {
+ font-size: x-large;
+ }
+ ul.comments {
+ border-top: solid 1px #eee;
+ padding: 0 0 0 20px;
+ border-left: solid 5px #eee;
+ list-style-type: none;
+ li.comment {
+ border-bottom: solid 1px #eee;
+ &>span.author { font-weight: bold; }
+ &>span.info { font-style: italic; }
+ }
+ }
+
+}
diff --git a/modules/comments/site/scripts/install.sql b/modules/comments/site/scripts/install.sql
new file mode 100644
index 0000000..593b628
--- /dev/null
+++ b/modules/comments/site/scripts/install.sql
@@ -0,0 +1,13 @@
+CREATE TABLE comments(
+ `cid` INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT UNIQUE,
+ `content` TEXT,
+ `format` VARCHAR(128),
+ `author` INTEGER,
+ `author_name` VARCHAR(255),
+ `created` DATETIME NOT NULL,
+ `changed` DATETIME NOT NULL,
+ `status` INTEGER,
+ `parent` INTEGER,
+ `entity` VARCHAR(255), /* Associated entity */
+ `entity_type` VARCHAR(255) /* Type of associated entity */
+);
diff --git a/modules/node/cms_node_module.e b/modules/node/cms_node_module.e
index 097230f..23022f1 100644
--- a/modules/node/cms_node_module.e
+++ b/modules/node/cms_node_module.e
@@ -27,10 +27,6 @@ inherit
CMS_TAXONOMY_HOOK
--- CMS_HOOK_EXPORT
-
--- CMS_EXPORT_NODE_UTILITIES
-
create
make
diff --git a/modules/node/export/cms_export_node_utilities.e b/modules/node/export/cms_export_node_utilities.e
index f85e428..c50ab18 100644
--- a/modules/node/export/cms_export_node_utilities.e
+++ b/modules/node/export/cms_export_node_utilities.e
@@ -19,7 +19,7 @@ feature -- Access
local
jo: JSON_OBJECT
ja: JSON_ARRAY
- j, jterm: JSON_OBJECT
+ jterm: JSON_OBJECT
do
create Result.make_empty
Result.put_string (n.content_type, "type")
diff --git a/modules/node/handler/cms_node_type_webform_manager.e b/modules/node/handler/cms_node_type_webform_manager.e
index 10e866d..0ec2c44 100644
--- a/modules/node/handler/cms_node_type_webform_manager.e
+++ b/modules/node/handler/cms_node_type_webform_manager.e
@@ -351,17 +351,83 @@ feature -- Output
end
a_output.append ("
")
end
- elseif attached a_node.content as l_content then
- a_output.append ("
")
- if attached cms_api.format (a_node.format) as f then
- append_formatted_content_to (l_content, f, a_output)
- else
- append_formatted_content_to (l_content, cms_api.formats.default_format, a_output)
+ else
+ if attached a_node.content as l_content then
+ a_output.append ("
")
+ if attached cms_api.format (a_node.format) as f then
+ append_formatted_content_to (l_content, f, a_output)
+ else
+ append_formatted_content_to (l_content, cms_api.formats.default_format, a_output)
+ end
+ a_output.append ("
")
end
- a_output.append ("")
+
+ append_comments_as_html_to (a_node, a_output, a_response)
+
end
a_output.append ("")
end
+ append_comments_as_html_to (a_node: G; a_output: STRING; a_response: detachable CMS_RESPONSE)
+ do
+ if attached {CMS_COMMENTS_API} cms_api.module_api ({CMS_COMMENTS_MODULE}) as l_comments_api then
+ if attached l_comments_api.comments_for (a_node) as l_comments and then not l_comments.is_empty then
+ a_output.append ("
Comments
")
+ across
+ l_comments as ic
+ loop
+ append_comment_as_html_to (ic.item, a_output, a_response)
+ end
+ a_output.append ("
")
+ end
+ end
+ end
+
+ append_comment_as_html_to (a_comment: CMS_COMMENT; a_output: STRING; a_response: detachable CMS_RESPONSE)
+ local
+ l_ago: DATE_TIME_AGO_CONVERTER
+ do
+ a_output.append ("
")
+ if attached a_comment.author as l_author then
+ a_output.append ("")
+ a_output.append (cms_api.html_encoded (l_author.name))
+ a_output.append ("")
+ elseif attached a_comment.author_name as l_author_name then
+ a_output.append ("")
+ a_output.append (cms_api.html_encoded (l_author_name))
+ a_output.append ("")
+ end
+ if attached a_comment.creation_date as dt then
+ a_output.append (" (")
+ create l_ago.make
+ a_output.append (l_ago.smart_date_duration (dt))
+ a_output.append (" ")
+ a_output.append (l_ago.short_date (dt))
+ a_output.append (")")
+ end
+ if attached a_comment.content as l_content then
+ a_output.append ("
")
+ if
+ attached a_comment.format as l_format and then
+ attached cms_api.format (l_format) as f
+ then
+ append_formatted_content_to (l_content, f, a_output)
+ else
+ append_formatted_content_to (l_content, cms_api.formats.default_format, a_output)
+ end
+ a_output.append ("
")
+ end
+ if attached a_comment.items as lst and then not lst.is_empty then
+ a_output.append ("
")
+ across
+ lst as ic
+ loop
+ append_comment_as_html_to (ic.item, a_output, a_response)
+ end
+ a_output.append ("
")
+ end
+ a_output.append ("
")
+ end
+
end
diff --git a/modules/node/import/cms_import_node_utilities.e b/modules/node/import/cms_import_node_utilities.e
index 2a8de6c..a01d1b8 100644
--- a/modules/node/import/cms_import_node_utilities.e
+++ b/modules/node/import/cms_import_node_utilities.e
@@ -21,6 +21,53 @@ feature -- Access
Result := a_node_api.cms_api.user_api.user_by_name (a_name)
end
+feature -- Comments helpers
+
+ import_comments_file_for_entity (fn: PATH; a_entity: CMS_CONTENT; api: CMS_API; a_import_ctx: CMS_IMPORT_CONTEXT)
+ local
+ s: STRING
+ jp: JSON_PARSER
+ f: RAW_FILE
+ l_comment: CMS_COMMENT
+ l_log: STRING_8
+ do
+ if attached {CMS_COMMENTS_API} api.module_api ({CMS_COMMENTS_MODULE}) as l_comments_api then
+ create f.make_with_path (fn)
+ if f.exists and then f.is_access_readable then
+ f.open_read
+ from
+ create s.make (0)
+ until
+ f.exhausted or f.end_of_file
+ loop
+ f.read_stream (1_024)
+ s.append (f.last_string)
+ end
+ f.close
+ create jp.make_with_string (s)
+ jp.parse_content
+ if jp.is_valid and then attached jp.parsed_json_value as j_comments then
+ if attached json_to_comments (j_comments, a_entity, l_comments_api) as l_comments then
+ across
+ l_comments as ic
+ loop
+ l_comment := ic.item
+ l_comments_api.save_recursively_comment (l_comment)
+ l_log := "comment #" + l_comment.id.out + " (count="+ l_comment.count.out +") %"" + f.path.utf_8_name + "%" imported"
+ l_log.append (" into " + a_entity.content_type)
+ if attached a_entity.identifier as l_id then
+ l_log.append (" #" + l_id)
+ end
+ l_log.append (" .")
+ a_import_ctx.log (l_log)
+ end
+ end
+ end
+ end
+ end
+ end
+
+
feature -- Conversion
json_to_node (a_node_type: CMS_NODE_TYPE [CMS_NODE]; j: JSON_OBJECT; a_node_api: CMS_NODE_API): detachable CMS_NODE
@@ -65,18 +112,36 @@ feature -- Conversion
then
l_node.set_link (lnk)
end
- if attached {JSON_ARRAY} j.item ("tags") as j_tags and then j_tags.count > 0 then
- if attached {CMS_TAXONOMY_API} a_node_api.cms_api.module_api ({CMS_TAXONOMY_MODULE}) as l_taxonomy_api then
- across
- j_tags as ic
- loop
- if
- attached {JSON_OBJECT} ic.item as j_tag and then
- attached json_string_item (j_tag, "text") as l_tag_text
- then
- if attached l_taxonomy_api.term_by_text (l_tag_text, Void) as t then
- l_taxonomy_api.associate_term_with_content (t, l_node)
- end
+ end
+ end
+
+ apply_taxonomy_to_node (j: JSON_OBJECT; a_node: CMS_NODE; a_cms_api: CMS_API)
+ require
+ a_node.has_id
+ local
+ l_term: CMS_TERM
+ do
+ if attached {JSON_ARRAY} j.item ("tags") as j_tags and then j_tags.count > 0 then
+ if
+ attached {CMS_TAXONOMY_API} a_cms_api.module_api ({CMS_TAXONOMY_MODULE}) as l_taxonomy_api and then
+ attached l_taxonomy_api.vocabularies_for_type (a_node.content_type) as l_voc_coll and then
+ attached l_voc_coll.item_by_name ("Tags") as l_voc
+ then
+ across
+ j_tags as ic
+ loop
+ if
+ attached {JSON_OBJECT} ic.item as j_tag and then
+ attached json_string_item (j_tag, "text") as l_tag_text
+ then
+ if attached l_taxonomy_api.term_by_text (l_tag_text, Void) as t then
+ l_term := t
+ else
+ create l_term.make (l_tag_text)
+ l_taxonomy_api.save_term (l_term, l_voc)
+ end
+ if l_term.has_id then
+ l_taxonomy_api.associate_term_with_content (l_term, a_node)
end
end
end
@@ -103,4 +168,92 @@ feature -- Conversion
end
end
+ json_to_comments (j: JSON_VALUE; a_entity: CMS_CONTENT; a_comments_api: CMS_COMMENTS_API): detachable LIST [CMS_COMMENT]
+ do
+ if attached {JSON_ARRAY} j as j_array then
+ create {ARRAYED_LIST [CMS_COMMENT]} Result.make (j_array.count)
+ across
+ j_array as ic
+ loop
+ if
+ attached {JSON_OBJECT} ic.item as jo and then
+ attached json_to_comment (jo, a_entity, a_comments_api) as c
+ then
+ Result.extend (c)
+ end
+ end
+ end
+ end
+
+ json_to_comment (j: JSON_OBJECT; a_entity: CMS_CONTENT; a_comments_api: CMS_COMMENTS_API): detachable CMS_COMMENT
+ local
+ l_title, l_content: detachable READABLE_STRING_32
+ do
+ create Result.make
+ Result.set_entity (a_entity)
+ if attached {JSON_STRING} j.item ("title") as j_title then
+ l_title := j_title.unescaped_string_32
+ if l_title.is_whitespace then
+ l_title := Void
+ end
+ end
+
+ if attached {JSON_STRING} j.item ("content") as j_content then
+ l_content := j_content.unescaped_string_32
+ if l_content.is_whitespace then
+ l_content := Void
+ end
+ elseif attached {JSON_STRING} j.item ("body") as j_body then
+ l_content := j_body.unescaped_string_32
+ if l_content.is_whitespace then
+ l_content := Void
+ end
+ end
+
+ if l_content = Void then
+ if l_title /= Void then
+ Result.set_content (l_title)
+ end
+ elseif l_title = Void then
+ Result.set_content (l_content)
+ Result.set_format ("wikitext")
+ else
+ if l_content.starts_with (l_title) then
+ Result.set_content (l_content)
+ else
+ Result.set_content (l_title + {STRING_32} "%N%N" + l_content)
+ end
+ Result.set_format ("wikitext")
+ end
+
+ if attached {JSON_STRING} j.item ("format") as j_format then
+ Result.set_format (j_format.unescaped_string_8)
+ end
+ if attached {JSON_OBJECT} j.item ("author") as j_author then
+ if attached {JSON_STRING} j_author.item ("name") as j_author_name then
+ if attached a_comments_api.cms_api.user_api.user_by_name (j_author_name.unescaped_string_32) as u then
+ Result.set_author (u)
+ Result.set_author_name (u.name)
+ else
+ -- User unknown!
+ Result.set_author_name (j_author_name.unescaped_string_32)
+ end
+ end
+ end
+ if attached json_date_item (j, "date") as dt then
+ Result.set_modification_date (dt)
+ Result.set_creation_date (dt)
+ end
+ if
+ attached j.item ("comments") as j_comments and then
+ attached json_to_comments (j_comments, a_entity, a_comments_api) as lst
+ then
+ across
+ lst as ic
+ loop
+ Result.extend (ic.item)
+ end
+ end
+ end
+
end
diff --git a/modules/node/node-safe.ecf b/modules/node/node-safe.ecf
index ad5bafc..aafe6b3 100644
--- a/modules/node/node-safe.ecf
+++ b/modules/node/node-safe.ecf
@@ -13,6 +13,7 @@
+
diff --git a/modules/node/node.ecf b/modules/node/node.ecf
index d69ebca..046c787 100644
--- a/modules/node/node.ecf
+++ b/modules/node/node.ecf
@@ -7,12 +7,12 @@
/CVS$/.svn$
-
+
diff --git a/modules/node/submodules/page/cms_page_module.e b/modules/node/submodules/page/cms_page_module.e
index 4310c24..e563aa8 100644
--- a/modules/node/submodules/page/cms_page_module.e
+++ b/modules/node/submodules/page/cms_page_module.e
@@ -233,14 +233,13 @@ feature -- Hooks
-- Import data identified by `a_import_id_list',
-- or import all data if `a_import_id_list' is Void.
local
- p: PATH
+ l_id: STRING_32
+ p, fp: PATH
d: DIRECTORY
- f: PLAIN_TEXT_FILE
- s: STRING
- jp: JSON_PARSER
loc: READABLE_STRING_8
l_parentable_list: ARRAYED_LIST [TUPLE [page: CMS_PAGE; parent: CMS_PAGE]]
l_new_pages: STRING_TABLE [CMS_PAGE] -- indexed by link location, if any.
+ l_entity: detachable CMS_PAGE
do
if
attached node_api as l_node_api and then
@@ -263,65 +262,60 @@ feature -- Hooks
d.entries as ic
loop
if attached ic.item.extension as ext and then ext.same_string_general ("json") then
- create f.make_with_path (p.extended_path (ic.item))
- if f.exists and then f.is_access_readable then
- f.open_read
- from
- create s.make (0)
- until
- f.exhausted or f.end_of_file
- loop
- f.read_stream (1_024)
- s.append (f.last_string)
- end
- f.close
- create jp.make_with_string (s)
- jp.parse_content
- if jp.is_valid and then attached jp.parsed_json_object as j then
- if
- attached json_string_item (j, "type") as l_type and then
- l_type.same_string_general (l_node_type.name)
- then
- if attached json_to_node_page (l_node_type, j, l_node_api) as l_page then
- if l_page.is_published then
- if l_page.author = Void then
- -- FIXME!!!
- l_page.set_author (l_page_api.cms_api.user)
- a_import_ctx.log (l_node_type.name + " %"" + f.path.utf_8_name + "%" WARNING (Author is unknown!)")
- end
- if attached l_page.author as l_author then
+ l_id := ic.item.name
+ l_id.remove_tail (ext.count + 1)
+ fp := p.extended_path (ic.item)
+ if attached json_object_from_location (fp) as j then
+ if
+ attached json_string_item (j, "type") as l_type and then
+ l_type.same_string_general (l_node_type.name)
+ then
+ l_entity := json_to_node_page (l_node_type, j, l_node_api)
+ if l_entity /= Void then
+ if l_entity.is_published then
+ if l_entity.author = Void then
+ -- FIXME!!!
+ l_entity.set_author (l_page_api.cms_api.user)
+ a_import_ctx.log (l_node_type.name + " %"" + fp.utf_8_name + "%" WARNING (Author is unknown!)")
+ end
+ if attached l_entity.author as l_author then
+ if
+ attached l_page_api.pages_with_title (l_entity.title) as l_pages and then
+ not l_pages.is_empty
+ then
+ -- Page Already exists!
+ -- FIXME/TODO
+ l_entity := l_pages.first
+ a_import_ctx.log (l_node_type.name + " %"" + fp.utf_8_name + "%" skipped (already exists for user #" + l_author.id.out + ")!")
+ else
if
- attached l_page_api.pages_with_title (l_page.title) as l_pages and then
- not l_pages.is_empty
+ attached l_entity.parent as l_parent and then
+ not l_parent.has_id
then
- -- Page Already exists!
- -- FIXME/TODO
- a_import_ctx.log (l_node_type.name + " %"" + f.path.utf_8_name + "%" skipped (already exists for user #" + l_author.id.out + ")!")
- else
- if
- attached l_page.parent as l_parent and then
- not l_parent.has_id
- then
- l_parentable_list.extend ([l_page, l_parent])
- l_page.set_parent (Void)
- end
- l_page_api.save_page (l_page)
- l_new_pages.force (l_page, l_node_api.node_path (l_page))
- a_import_ctx.log (l_node_type.name + " %"" + f.path.utf_8_name + "%" imported as "+ l_page.id.out +" for user #" + l_author.id.out + ".")
- if attached {CMS_LOCAL_LINK} l_page.link as l_link then
- loc := l_node_api.node_path (l_page)
- if not l_link.location.starts_with_general ("node/") then
- l_page_api.cms_api.set_path_alias (loc, l_link.location, False)
- l_new_pages.force (l_page, l_link.location)
- end
+ l_parentable_list.extend ([l_entity, l_parent])
+ l_entity.set_parent (Void)
+ end
+ l_page_api.save_page (l_entity)
+ apply_taxonomy_to_node (j, l_entity, l_page_api.cms_api)
+ l_new_pages.force (l_entity, l_node_api.node_path (l_entity))
+ a_import_ctx.log (l_node_type.name + " %"" + fp.utf_8_name + "%" imported as "+ l_entity.id.out +" for user #" + l_author.id.out + ".")
+ if attached {CMS_LOCAL_LINK} l_entity.link as l_link then
+ loc := l_node_api.node_path (l_entity)
+ if not l_link.location.starts_with_general ("node/") then
+ l_page_api.cms_api.set_path_alias (loc, l_link.location, False)
+ l_new_pages.force (l_entity, l_link.location)
end
end
- else
- a_import_ctx.log (l_node_type.name + " %"" + f.path.utf_8_name + "%" skipped (Author is unknown!)")
+ end
+ if l_entity /= Void and then l_entity.has_id then
+ -- Support for comments
+ import_comments_file_for_entity (p.extended (l_id).extended ("comments.json"), l_entity, l_node_api.cms_api, a_import_ctx)
end
else
- a_import_ctx.log (l_node_type.name + " %"" + f.path.utf_8_name + "%" skipped (Status is Not Published!)")
+ a_import_ctx.log (l_node_type.name + " %"" + fp.utf_8_name + "%" skipped (Author is unknown!)")
end
+ else
+ a_import_ctx.log (l_node_type.name + " %"" + fp.utf_8_name + "%" skipped (Status is Not Published!)")
end
end
end
@@ -331,14 +325,13 @@ feature -- Hooks
across
l_parentable_list as ic
loop
- if attached ic.item.page as l_page then
- update_page_parent (l_page, ic.item.parent, l_new_pages)
- if attached l_page.parent as l_parent then
- a_import_ctx.log (l_node_type.name + " #" + l_page.id.out + " assigned to parent #" + l_parent.id.out)
- l_page_api.save_page (l_page)
- else
- a_import_ctx.log (l_node_type.name + " #" + l_page.id.out + " : unable to find parent!")
- end
+ l_entity := ic.item.page
+ update_page_parent (l_entity, ic.item.parent, l_new_pages)
+ if attached l_entity.parent as l_parent then
+ a_import_ctx.log (l_node_type.name + " #" + l_entity.id.out + " assigned to parent #" + l_parent.id.out)
+ l_page_api.save_page (l_entity)
+ else
+ a_import_ctx.log (l_node_type.name + " #" + l_entity.id.out + " : unable to find parent!")
end
end
end
diff --git a/modules/wikitext/wikitext_filter.e b/modules/wikitext/wikitext_filter.e
index d2a3001..30f1f78 100644
--- a/modules/wikitext/wikitext_filter.e
+++ b/modules/wikitext/wikitext_filter.e
@@ -40,10 +40,10 @@ feature -- Conversion
if s32.is_valid_as_string_8 then
l_wikitext := s32.as_string_8
else
- l_wikitext := utf.utf_32_string_to_utf_8_string_8 (s32)
+ l_wikitext := adapted_text (s32)
end
else
- l_wikitext := utf.utf_32_string_to_utf_8_string_8 (a_text)
+ l_wikitext := adapted_text (a_text)
end
create wk.make_from_string (l_wikitext)
if attached wk.structure as st then
@@ -58,8 +58,19 @@ feature -- Conversion
-- elseif attached {STRING_32} a_text as s32 then
-- s32.wipe_out
-- end
- a_text.append (html)
+ if attached {STRING_32} a_text as a_unicode_text then
+ a_text.append (utf.utf_8_string_8_to_string_32 (html))
+ else
+ a_text.append (html)
+ end
end
end
+ adapted_text (s: READABLE_STRING_32): STRING_8
+ local
+ utf: UTF_CONVERTER
+ do
+ Result := utf.utf_32_string_to_utf_8_string_8 (s)
+ end
+
end
diff --git a/src/hooks/import/cms_hook_import.e b/src/hooks/import/cms_hook_import.e
index 6376d88..8920000 100644
--- a/src/hooks/import/cms_hook_import.e
+++ b/src/hooks/import/cms_hook_import.e
@@ -11,7 +11,7 @@ inherit
feature -- Hook
- import_from (a_impot_id_list: detachable ITERABLE [READABLE_STRING_GENERAL]; a_import_ctx: CMS_IMPORT_CONTEXT; a_response: CMS_RESPONSE)
+ import_from (a_import_id_list: detachable ITERABLE [READABLE_STRING_GENERAL]; a_import_ctx: CMS_IMPORT_CONTEXT; a_response: CMS_RESPONSE)
-- Import data identified by `a_import_id_list',
-- or import all data if `a_import_id_list' is Void.
deferred
diff --git a/src/hooks/import/cms_import_json_utilities.e b/src/hooks/import/cms_import_json_utilities.e
index b3dc3cc..b8b1a06 100644
--- a/src/hooks/import/cms_import_json_utilities.e
+++ b/src/hooks/import/cms_import_json_utilities.e
@@ -10,6 +10,39 @@ class
feature -- Access
+ json_value_from_location (a_location: PATH): detachable JSON_VALUE
+ local
+ f: RAW_FILE
+ s: STRING
+ jp: JSON_PARSER
+ do
+ create f.make_with_path (a_location)
+ if f.exists and then f.is_access_readable then
+ f.open_read
+ from
+ create s.make (0)
+ until
+ f.exhausted or f.end_of_file
+ loop
+ f.read_stream (1_024)
+ s.append (f.last_string)
+ end
+ f.close
+ create jp.make_with_string (s)
+ jp.parse_content
+ if jp.is_valid then
+ Result := jp.parsed_json_value
+ end
+ end
+ end
+
+ json_object_from_location (a_location: PATH): detachable JSON_OBJECT
+ do
+ if attached {JSON_OBJECT} json_value_from_location (a_location) as jo then
+ Result := jo
+ end
+ end
+
json_string_item (j: JSON_OBJECT; a_name: READABLE_STRING_GENERAL): detachable STRING_32
do
if attached {JSON_STRING} j.item (a_name) as s then
@@ -41,13 +74,46 @@ feature -- Access
json_date_item (j: JSON_OBJECT; a_name: READABLE_STRING_GENERAL): detachable DATE_TIME
local
hd: HTTP_DATE
+ i,y,m,d,h,min,sec: INTEGER
+ s: STRING_8
do
if attached {JSON_NUMBER} j.item (a_name) as num then
create hd.make_from_timestamp (num.integer_64_item)
Result := hd.date_time
- elseif attached {JSON_STRING} j.item (a_name) as s then
- create hd.make_from_string (s.unescaped_string_32)
- Result := hd.date_time
+ elseif attached {JSON_STRING} j.item (a_name) as js then
+ s := js.unescaped_string_8
+
+ -- Parse yyyy-mm-dd hh:mm:ss
+ -- 1234567890123456789
+ i := s.index_of ('-', 1)
+ if i = 5 then
+ y := s.substring (1, i - 1).to_integer
+ i := s.index_of ('-', i + 1)
+ if i = 8 then
+ m := s.substring (6, i - 1).to_integer
+ i := s.index_of (' ', i + 1)
+ if i = 11 then
+ d := s.substring (9, i - 1).to_integer
+ i := s.index_of (':', i + 1)
+ if i = 14 then
+ h := s.substring (12, i - 1).to_integer
+ i := s.index_of (':', i + 1)
+ if i = 17 then
+ min := s.substring (15, i - 1).to_integer
+ sec := s.substring (i + 1, s.count).to_integer
+ create Result.make (y,m,d,h,min,sec)
+ end
+ end
+ end
+ end
+ end
+ if Result = Void then
+ create hd.make_from_string (s)
+ if hd.has_error then
+ else
+ Result := hd.date_time
+ end
+ end
end
end
diff --git a/src/persistence/user/cms_user_storage_i.e b/src/persistence/user/cms_user_storage_i.e
index 4a205bf..f96b037 100644
--- a/src/persistence/user/cms_user_storage_i.e
+++ b/src/persistence/user/cms_user_storage_i.e
@@ -48,11 +48,11 @@ feature -- Access
password: Result /= Void implies (Result.hashed_password /= Void and Result.password = Void)
end
- user_by_email (a_email: like {CMS_USER}.email): detachable CMS_USER
+ user_by_email (a_email: READABLE_STRING_GENERAL): detachable CMS_USER
-- User with name `a_email', if any.
deferred
ensure
- same_email: Result /= Void implies a_email ~ Result.email
+ same_email: Result /= Void implies (attached Result.email as r_email and then a_email.same_string (r_email))
password: Result /= Void implies (Result.hashed_password /= Void and Result.password = Void)
end
diff --git a/src/persistence/user/cms_user_storage_null.e b/src/persistence/user/cms_user_storage_null.e
index 0415325..8aaf444 100644
--- a/src/persistence/user/cms_user_storage_null.e
+++ b/src/persistence/user/cms_user_storage_null.e
@@ -30,7 +30,7 @@ feature -- Access: user
do
end
- user_by_email (a_email: like {CMS_USER}.email): detachable CMS_USER
+ user_by_email (a_email: READABLE_STRING_GENERAL): detachable CMS_USER
do
end
@@ -204,6 +204,6 @@ feature -- Temp Users
do
end
note
- copyright: "2011-2016, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
+ copyright: "2011-2017, 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/persistence/user/cms_user_storage_sql_i.e b/src/persistence/user/cms_user_storage_sql_i.e
index 7f09446..cd44891 100644
--- a/src/persistence/user/cms_user_storage_sql_i.e
+++ b/src/persistence/user/cms_user_storage_sql_i.e
@@ -95,7 +95,7 @@ feature -- Access: user
sql_finalize
end
- user_by_email (a_email: like {CMS_USER}.email): detachable CMS_USER
+ user_by_email (a_email: READABLE_STRING_GENERAL): detachable CMS_USER
-- User for the given email `a_email', if any.
local
l_parameters: STRING_TABLE [detachable ANY]
diff --git a/src/service/cms_api.e b/src/service/cms_api.e
index 37acd6f..ff5460f 100644
--- a/src/service/cms_api.e
+++ b/src/service/cms_api.e
@@ -7,14 +7,14 @@ class
CMS_API
inherit
- ANY
+ CMS_HOOK_EXPORT
+ CMS_API_EXPORT_IMP
+
+ CMS_HOOK_IMPORT
+ CMS_API_IMPORT_IMP
CMS_ENCODERS
- CMS_HOOK_EXPORT
-
- CMS_EXPORT_JSON_UTILITIES
-
REFACTORING_HELPER
create
@@ -503,7 +503,14 @@ feature {NONE} -- Hooks
end
end
-feature -- Query: API
+feature {NONE} -- Access: API
+
+ cms_api: CMS_API
+ do
+ Result := Current
+ end
+
+feature -- Access: API
user_api: CMS_USER_API
-- API to access user related data.
@@ -524,6 +531,7 @@ feature -- Hooks
-- Register hooks associated with the cms core.
do
a_hooks.subscribe_to_export_hook (Current)
+ a_hooks.subscribe_to_import_hook (Current)
end
feature -- Path aliases
@@ -618,7 +626,7 @@ feature {NONE}-- Implementation
-- Error handler.
internal_user_api: detachable like user_api
- -- Cached value for `user_api'.
+ -- Cached value for `user_api`.
feature -- Environment/ theme
@@ -802,112 +810,6 @@ feature -- Environment/ modules and theme
Result := module_configuration_by_name (a_module.name, a_name)
end
-feature -- Hook
-
- export_to (a_export_id_list: detachable ITERABLE [READABLE_STRING_GENERAL]; a_export_ctx: CMS_EXPORT_CONTEXT; a_response: CMS_RESPONSE)
- -- .
- local
- p: PATH
- d: DIRECTORY
- ja: JSON_ARRAY
- jobj,jo,j: JSON_OBJECT
- f: PLAIN_TEXT_FILE
- u: CMS_USER
- do
- if attached a_response.has_permissions (<<"admin export", "export core">>) then
- if a_export_id_list = Void then -- Include everything
- p := a_export_ctx.location.extended ("core")
- create d.make_with_path (p)
- if not d.exists then
- d.recursive_create_dir
- end
-
- -- path_aliases export.
- a_export_ctx.log ("Exporting path_aliases")
- create jo.make_empty
- across storage.path_aliases as ic loop
- jo.put_string (ic.item, ic.key)
- end
- create f.make_with_path (p.extended ("path_aliases.json"))
- f.create_read_write
- f.put_string (json_to_string (jo))
- f.close
-
- -- custom_values export.
- if attached storage.custom_values as lst then
- a_export_ctx.log ("Exporting custom_values")
- create ja.make_empty
- across
- lst as ic
- loop
- create j.make_empty
- if attached ic.item.type as l_type then
- j.put_string (l_type, "type")
- end
- j.put_string (ic.item.name, "name")
- if attached ic.item.type as l_value then
- j.put_string (l_value, "value")
- end
- ja.extend (j)
- end
- create f.make_with_path (p.extended ("custom_values.json"))
- f.create_read_write
- f.put_string (json_to_string (ja))
- f.close
- end
-
- -- users export.
- a_export_ctx.log ("Exporting users")
- create jo.make_empty
-
- create jobj.make_empty
- across user_api.recent_users (create {CMS_DATA_QUERY_PARAMETERS}.make (0, user_api.users_count.as_natural_32)) as ic loop
- u := ic.item
- create j.make_empty
- j.put_string (u.name, "name")
- j.put_integer (u.status, "status")
- put_string_into_json (u.email, "email", j)
- put_string_into_json (u.password, "password", j)
- put_string_into_json (u.hashed_password, "hashed_password", j)
- put_date_into_json (u.creation_date, "creation_date", j)
- put_date_into_json (u.last_login_date, "last_login_date", j)
- if attached u.roles as l_roles then
- create ja.make (l_roles.count)
- across
- l_roles as roles_ic
- loop
- ja.extend (create {JSON_STRING}.make_from_string_32 ({STRING_32} " %"" + roles_ic.item.name + {STRING_32} "%" #" + roles_ic.item.id.out))
- end
- j.put (ja, "roles")
- end
- jobj.put (j, u.id.out)
- end
- jo.put (jobj, "users")
-
- create jobj.make_empty
- across user_api.roles as ic loop
- create j.make_empty
- j.put_string (ic.item.name, "name")
- if attached ic.item.permissions as l_perms then
- create ja.make (l_perms.count)
- across
- l_perms as perms_ic
- loop
- ja.extend (create {JSON_STRING}.make_from_string (perms_ic.item))
- end
- j.put (ja, "permissions")
- end
- jobj.put (j, ic.item.id.out)
- end
- jo.put (jobj, "roles")
- create f.make_with_path (p.extended ("users.json"))
- f.create_read_write
- f.put_string (json_to_string (jo))
- f.close
- end
- end
- end
-
feature -- Access: active user
user_is_authenticated: BOOLEAN
diff --git a/src/service/cms_api_export_imp.e b/src/service/cms_api_export_imp.e
new file mode 100644
index 0000000..471ff70
--- /dev/null
+++ b/src/service/cms_api_export_imp.e
@@ -0,0 +1,142 @@
+note
+ description: "Summary description for {CMS_API_EXPORT_IMP}."
+ date: "$Date$"
+ revision: "$Revision$"
+
+deferred class
+ CMS_API_EXPORT_IMP
+
+inherit
+ CMS_ENCODERS
+
+ CMS_HOOK_EXPORT
+
+ CMS_EXPORT_JSON_UTILITIES
+
+ CMS_FILE_SYSTEM_UTILITIES
+
+feature {NONE} -- Query: API
+
+ user_api: CMS_USER_API
+ -- API to access user related data.
+ deferred
+ end
+
+ cms_api: CMS_API
+ deferred
+ end
+
+feature -- Export
+
+ export_to (a_export_id_list: detachable ITERABLE [READABLE_STRING_GENERAL]; a_export_ctx: CMS_EXPORT_CONTEXT; a_response: CMS_RESPONSE)
+ -- .
+ local
+ p: PATH
+ d: DIRECTORY
+ ja: JSON_ARRAY
+ jobj,jo,j: JSON_OBJECT
+ f: PLAIN_TEXT_FILE
+ u: CMS_USER
+ do
+ if attached a_response.has_permissions (<<"admin export", "export core">>) then
+ if a_export_id_list = Void then -- Include everything
+ p := a_export_ctx.location.extended ("core")
+ create d.make_with_path (p)
+ if not d.exists then
+ d.recursive_create_dir
+ end
+
+ -- path_aliases export.
+ a_export_ctx.log ("Exporting path_aliases")
+ create jo.make_empty
+ across cms_api.storage.path_aliases as ic loop
+ jo.put_string (ic.item, ic.key)
+ end
+ create f.make_with_path (p.extended ("path_aliases.json"))
+ f.create_read_write
+ f.put_string (json_to_string (jo))
+ f.close
+
+ -- custom_values export.
+ if attached cms_api.storage.custom_values as lst then
+ a_export_ctx.log ("Exporting custom_values")
+ create ja.make_empty
+ across
+ lst as ic
+ loop
+ create j.make_empty
+ if attached ic.item.type as l_type then
+ j.put_string (l_type, "type")
+ end
+ j.put_string (ic.item.name, "name")
+ if attached ic.item.type as l_value then
+ j.put_string (l_value, "value")
+ end
+ ja.extend (j)
+ end
+ create f.make_with_path (p.extended ("custom_values.json"))
+ f.create_read_write
+ f.put_string (json_to_string (ja))
+ f.close
+ end
+
+ -- user roles export.
+ a_export_ctx.log ("Exporting user roles")
+
+ create jobj.make_empty
+ across user_api.roles as ic loop
+ create j.make_empty
+ j.put_string (ic.item.name, "name")
+ if attached ic.item.permissions as l_perms then
+ create ja.make (l_perms.count)
+ across
+ l_perms as perms_ic
+ loop
+ ja.extend (create {JSON_STRING}.make_from_string (perms_ic.item))
+ end
+ j.put (ja, "permissions")
+ end
+ jobj.put (j, ic.item.id.out)
+ end
+ create f.make_with_path (p.extended ("user_roles.json"))
+ f.create_read_write
+ f.put_string (json_to_string (jo))
+ f.close
+
+ -- users export.
+ a_export_ctx.log ("Exporting users")
+
+ create jobj.make_empty
+ across user_api.recent_users (create {CMS_DATA_QUERY_PARAMETERS}.make (0, user_api.users_count.as_natural_32)) as ic loop
+ u := ic.item
+ create j.make_empty
+ j.put_string (u.name, "name")
+ j.put_integer (u.status, "status")
+ put_string_into_json (u.email, "email", j)
+ put_string_into_json (u.password, "password", j)
+ put_string_into_json (u.hashed_password, "hashed_password", j)
+ put_date_into_json (u.creation_date, "creation_date", j)
+ put_date_into_json (u.last_login_date, "last_login_date", j)
+ if attached u.roles as l_roles then
+ create ja.make (l_roles.count)
+ across
+ l_roles as roles_ic
+ loop
+ ja.extend (create {JSON_STRING}.make_from_string_32 ({STRING_32} " %"" + roles_ic.item.name + {STRING_32} "%" #" + roles_ic.item.id.out))
+ end
+ j.put (ja, "roles")
+ end
+ jobj.put (j, u.id.out)
+ end
+ create f.make_with_path (p.extended ("users.json"))
+ f.create_read_write
+ f.put_string (json_to_string (jobj))
+ f.close
+ end
+ end
+ end
+
+note
+ copyright: "2011-2017, 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/service/cms_api_import_imp.e b/src/service/cms_api_import_imp.e
new file mode 100644
index 0000000..fb49164
--- /dev/null
+++ b/src/service/cms_api_import_imp.e
@@ -0,0 +1,225 @@
+note
+ description: "Summary description for {CMS_API_IMPORT_IMP}."
+ author: ""
+ date: "$Date$"
+ revision: "$Revision$"
+
+deferred class
+ CMS_API_IMPORT_IMP
+
+inherit
+ CMS_ENCODERS
+
+ CMS_HOOK_IMPORT
+
+ CMS_IMPORT_JSON_UTILITIES
+
+ CMS_FILE_SYSTEM_UTILITIES
+
+feature {NONE} -- Query: API
+
+ user_api: CMS_USER_API
+ -- API to access user related data.
+ deferred
+ end
+
+ cms_api: CMS_API
+ deferred
+ end
+
+feature -- Import
+
+ import_from (a_import_id_list: detachable ITERABLE [READABLE_STRING_GENERAL]; a_import_ctx: CMS_IMPORT_CONTEXT; a_response: CMS_RESPONSE)
+ -- Import data identified by `a_import_id_list',
+ -- or import all data if `a_import_id_list' is Void.
+ local
+ p: PATH
+ d: DIRECTORY
+ l_id: STRING_32
+ do
+ -- User roles
+ if
+ a_import_id_list = Void
+ or else across a_import_id_list as ic some ic.item.same_string ("user_roles") end
+ then
+ if
+ a_response.has_permissions (<<"import core">>)
+ then
+ a_import_ctx.log ("Importing user roles...")
+ -- From "core" location
+ if attached json_object_from_location (a_import_ctx.location.extended ("core").extended ("user_roles.json")) as j_user_roles then
+ across
+ j_user_roles as j_ic
+ loop
+ if attached {JSON_OBJECT} j_ic.item as j_user_role then
+ import_json_user_role (j_user_role, a_import_ctx)
+ end
+ end
+ end
+ end
+ end
+
+ -- Users
+ if
+ a_import_id_list = Void
+ or else across a_import_id_list as ic some ic.item.same_string ("users") end
+ then
+ if
+ a_response.has_permissions (<<"import core">>)
+ then
+ a_import_ctx.log ("Importing users...")
+
+ -- From "core" location
+ if attached json_object_from_location (a_import_ctx.location.extended ("core").extended ("users.json")) as j_users then
+ across
+ j_users as j_ic
+ loop
+ if attached {JSON_OBJECT} j_ic.item as j_user then
+ import_json_user (j_user, a_import_ctx)
+ end
+ end
+ end
+ end
+ if
+ a_response.has_permissions (<<"import users">>)
+ then
+ -- From "users" location
+ p := a_import_ctx.location.extended ("users")
+ create d.make_with_path (p)
+ if d.exists and then d.is_readable then
+ a_import_ctx.log ("Importing users ..")
+ across
+ d.entries as ic
+ loop
+ if attached ic.item.extension as ext and then ext.same_string_general ("json") then
+ l_id := ic.item.name
+ l_id.remove_tail (ext.count + 1)
+ if attached json_object_from_location (p.extended_path (ic.item)) as j_user then
+ import_json_user (j_user, a_import_ctx)
+ end
+ end
+ end
+ end
+ end
+ end
+
+ -- User roles
+ if
+ a_import_id_list = Void
+ or else across a_import_id_list as ic some ic.item.same_string ("files") end
+ then
+ if
+ a_response.has_permissions (<<"import files">>)
+ then
+ a_import_ctx.log ("Importing files roles...")
+ -- From "core" location
+ p := a_import_ctx.location.extended ("files")
+ if attached files_from_location (p, True) as l_files then
+ across
+ l_files as ic
+ loop
+ import_file (ic.item, p, a_import_ctx)
+ end
+ end
+ end
+ end
+ end
+
+ import_file (a_file_path: PATH; a_root_path: PATH; a_import_ctx: CMS_IMPORT_CONTEXT)
+ local
+ b: BOOLEAN
+ dst: PATH
+ do
+ if attached relative_path_inside (a_file_path, a_root_path) as rel_path then
+ dst := cms_api.files_location.extended_path (rel_path)
+ b := safe_copy_file (a_file_path, dst)
+ if b then
+ a_import_ctx.log ("Imported file %"" + a_file_path.utf_8_name + "%" to %"" + dst.utf_8_name + "%".")
+ else
+ a_import_ctx.log ("ERROR: unable to import file %"" + a_file_path.utf_8_name + "%" !!!")
+ end
+ else
+ check a_file_path_in_root_directory: False end
+ a_import_ctx.log ("ERROR: unable to import file %"" + a_file_path.utf_8_name + "%" (not in root directory) !")
+ end
+ end
+
+ import_json_user_role (j_user_role: JSON_OBJECT; a_import_ctx: CMS_IMPORT_CONTEXT)
+ local
+ do
+ if attached json_to_user_role (j_user_role) as ur then
+ if user_api.user_role_by_name (ur.name) = Void then
+ a_import_ctx.log ("new user role %"" + ur.name + "%".")
+ user_api.save_user_role (ur)
+ else
+ a_import_ctx.log ("Skip user role %"" + ur.name + "%" : already exists!")
+ -- Already exists!
+ end
+ end
+ end
+
+ import_json_user (j_user: JSON_OBJECT; a_import_ctx: CMS_IMPORT_CONTEXT)
+ local
+ l_user_by_name, l_user_by_email: detachable CMS_USER
+ do
+ if attached json_to_user (j_user) as u then
+ l_user_by_name := user_api.user_by_name (u.name)
+ if attached u.email as l_email then
+ l_user_by_email := user_api.user_by_email (l_email)
+ end
+ if l_user_by_name /= Void or l_user_by_email /= Void then
+ a_import_ctx.log ("Skip user %"" + u.name + "%": already exists!")
+ -- Already exists!
+ else
+ user_api.new_user (u)
+ a_import_ctx.log ("New user %"" + u.name + "%" -> " + u.id.out + " .")
+ end
+ end
+ end
+
+ json_to_user_role (j: JSON_OBJECT): detachable CMS_USER_ROLE
+ do
+ if attached json_string_item (j, "name") as l_name and then not l_name.is_whitespace then
+ create Result.make (l_name)
+ if attached {JSON_ARRAY} j.item ("permissions") as j_permissions then
+ across
+ j_permissions as ic
+ loop
+ if attached {JSON_STRING} ic.item as j_permission then
+ Result.add_permission (j_permission.unescaped_string_8)
+ end
+ end
+ end
+ end
+ end
+
+ json_to_user (j: JSON_OBJECT): detachable CMS_USER
+ local
+ l_roles: ARRAYED_LIST [CMS_USER_ROLE]
+ do
+ if attached json_string_item (j, "name") as l_name and then not l_name.is_whitespace then
+ create Result.make (l_name)
+ Result.set_password ("")
+ if attached json_string_8_item (j, "email") as l_email then
+ Result.set_email (l_email)
+ end
+ if attached {JSON_ARRAY} j.item ("roles") as j_roles then
+ create l_roles.make (j_roles.count)
+ across
+ j_roles as ic
+ loop
+ if attached {JSON_STRING} ic.item as j_role then
+ if attached user_api.user_role_by_name (j_role.unescaped_string_32) as ur then
+ l_roles.extend (ur)
+ end
+ end
+ end
+ Result.set_roles (l_roles)
+ end
+ end
+ end
+
+note
+ copyright: "2011-2017, 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/service/cms_file_system_utilities.e b/src/service/cms_file_system_utilities.e
new file mode 100644
index 0000000..5ed222d
--- /dev/null
+++ b/src/service/cms_file_system_utilities.e
@@ -0,0 +1,124 @@
+note
+ description: "Routines to manipulate files, directories, ..."
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ CMS_FILE_SYSTEM_UTILITIES
+
+feature -- Files
+
+ relative_path_inside (a_path: PATH; a_root_path: PATH): detachable PATH
+ -- Relative path from `a_root_path` to `a_path`, or Void if `a_path` is not inside `a_root_path`.
+ local
+ lst,root_lst: LIST [PATH]
+ err: BOOLEAN
+ do
+ lst := a_path.components
+ root_lst := a_root_path.components
+ if lst.count >= root_lst.count then
+ from
+ lst.start
+ root_lst.start
+ until
+ root_lst.after or err
+ loop
+ if lst.item.same_as (root_lst.item) then
+ lst.forth
+ root_lst.forth
+ else
+ err := True
+ end
+ end
+ if not err then
+ from
+ create Result.make_empty
+ until
+ lst.after
+ loop
+ Result := Result.extended_path (lst.item)
+ lst.forth
+ end
+ end
+ end
+ end
+
+ files_from_location (a_loc: PATH; is_recursive: BOOLEAN): detachable LIST [PATH]
+ local
+ d: DIRECTORY
+ f: RAW_FILE
+ p: PATH
+ retried: BOOLEAN
+ do
+ if not retried then
+ create {ARRAYED_LIST [PATH]} Result.make (0)
+ create d.make_with_path (a_loc)
+ if d.exists then
+ across
+ d.entries as ic
+ loop
+ if ic.item.is_current_symbol or ic.item.is_parent_symbol then
+ -- Ignore
+ else
+ p := a_loc.extended_path (ic.item)
+ create f.make_with_path (p)
+ if f.is_directory then
+ if is_recursive and then attached files_from_location (p, is_recursive) as lst then
+ across
+ lst as lst_ic
+ loop
+ Result.force (lst_ic.item)
+ end
+ end
+ elseif f.exists then
+ Result.force (p)
+ end
+ end
+ end
+ end
+ end
+ rescue
+ retried := True
+ retry
+ end
+
+ safe_copy_file (src,dst: PATH): BOOLEAN
+ -- Copy file from `src` to `dst'
+ -- and return True on success, False on failure.
+ local
+ retried: BOOLEAN
+ f_src, f_dst: RAW_FILE
+ d: DIRECTORY
+ do
+ Result := False
+ if retried then
+ Result := False
+ else
+ create f_src.make_with_path (src)
+ if f_src.exists and then f_src.is_access_readable then
+ if attached dst.parent as l_parent then
+ create d.make_with_path (l_parent)
+ if not d.exists then
+ d.recursive_create_dir
+ end
+ end
+ create f_dst.make_with_path (dst)
+ if not f_dst.exists or else f_dst.is_access_writable then
+ f_src.open_read
+ f_dst.open_write
+ f_src.copy_to (f_dst)
+ f_dst.close
+ f_src.close
+ Result := True -- Succeed!
+ end
+ end
+ end
+ rescue
+ retried := True
+ retry
+ end
+
+note
+ copyright: "2011-2017, 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/service/response/cms_response.e b/src/service/response/cms_response.e
index 7e11b8a..b498d28 100644
--- a/src/service/response/cms_response.e
+++ b/src/service/response/cms_response.e
@@ -1380,6 +1380,8 @@ feature -- Helpers: cms link
feature -- Helpers: html links
user_html_link (u: CMS_USER): STRING
+ require
+ u_with_name: not u.name.is_whitespace
do
Result := link (u.name, "user/" + u.id.out, Void)
end
@@ -1481,6 +1483,6 @@ feature {NONE} -- Execution
end
note
- copyright: "2011-2016, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
+ copyright: "2011-2017, 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/service/user/cms_user_api.e b/src/service/user/cms_user_api.e
index b5c5d22..ba7b512 100644
--- a/src/service/user/cms_user_api.e
+++ b/src/service/user/cms_user_api.e
@@ -28,7 +28,7 @@ feature -- Access: user
Result := storage.user_by_name (a_username)
end
- user_by_email (a_email: READABLE_STRING_32): detachable CMS_USER
+ user_by_email (a_email: READABLE_STRING_GENERAL): detachable CMS_USER
-- User by email `a_email', if any.
do
Result := storage.user_by_email (a_email)
diff --git a/src/support/date_time_ago_converter.e b/src/support/date_time_ago_converter.e
new file mode 100644
index 0000000..d9be8a5
--- /dev/null
+++ b/src/support/date_time_ago_converter.e
@@ -0,0 +1,401 @@
+note
+ description: "Summary description for {DATE_TIME_AGO_CONVERTER}."
+ author: ""
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ DATE_TIME_AGO_CONVERTER
+
+create
+ make
+
+feature {NONE} -- Initialization
+
+ make
+ local
+ dt_now, dt_now_utc: DATE_TIME
+ l_duration: DATE_TIME_DURATION --like {DATE_TIME}.relative_duration
+ do
+ create dt_now.make_now
+ create dt_now_utc.make_now_utc
+
+ l_duration := dt_now_utc.relative_duration (dt_now)
+ utc_offset := l_duration.hour
+
+ smart_date_kind := smart_date_duration_kind
+ end
+
+feature -- Access
+
+ append_date_to (a_text: READABLE_STRING_GENERAL; a_utc_date_time: detachable DATE_TIME; a_output: STRING_GENERAL)
+ require
+ valid_smart_date_kind: smart_date_kind > 0 implies valid_smart_date_kind (smart_date_kind)
+ do
+ if a_utc_date_time /= Void then
+ inspect smart_date_kind
+ when Smart_date_duration_kind then
+ a_output.append (smart_date_duration (a_utc_date_time))
+ when Smart_date_short_kind then
+ a_output.append (short_date (a_utc_date_time))
+ else
+ if attached date_time_format as l_format then
+ a_output.append (formatted_date_time (timezoned_date_time (a_utc_date_time), l_format))
+ else
+ a_output.append (a_text)
+ end
+ end
+ else
+ a_output.append (a_text)
+ end
+ end
+
+ date_time_format: detachable STRING
+
+ utc_offset: INTEGER
+ utc_offset_minute: INTEGER
+
+ smart_date_kind: INTEGER
+
+feature -- Constants
+
+ smart_date_none_kind: INTEGER = 0
+
+ smart_date_duration_kind: INTEGER = 1
+
+ smart_date_short_kind: INTEGER = 2
+
+-- smart_date_none_kind_string: STRING = "none"
+
+-- smart_date_duration_kind_string: STRING = "duration"
+
+-- smart_date_short_kind_string: STRING = "short date"
+
+ valid_smart_date_kind (k: INTEGER): BOOLEAN
+ do
+ inspect
+ k
+ when
+ smart_date_none_kind,
+ smart_date_duration_kind,
+ smart_date_short_kind
+ then
+ Result := True
+ else
+ end
+ end
+
+feature -- Output
+
+ formatted_date_time (a_date_time: DATE_TIME; a_date_time_format: STRING): STRING_8
+ local
+ y,m,d,h,mn,sec: INTEGER
+ s32: STRING_32
+ s: STRING
+ c: CHARACTER_32
+ i: INTEGER
+ do
+ create s32.make (a_date_time_format.count)
+ from
+ i := 1
+ m := a_date_time.month
+ y := a_date_time.year
+ d := a_date_time.day
+ h := a_date_time.hour
+ mn := a_date_time.minute
+ sec := a_date_time.second
+ until
+ i > a_date_time_format.count
+ loop
+ c := a_date_time_format[i]
+ inspect c
+ when 'Y' then s32.append_integer (y)
+ when 'y' then
+ s := y.out
+ s.keep_tail (2)
+ s32.append_string (s)
+ when 'm' then
+ if m < 10 then
+ s32.append_integer (0)
+ end
+ s32.append_integer (m)
+ when 'n' then s32.append_integer (m)
+ when 'M' then
+ s := a_date_time.months_text [m].string
+ s.to_lower; s.put (s.item (1).as_upper, 1); s32.append_string (s)
+ when 'F' then
+ s := a_date_time.long_months_text [m].string
+ s.to_lower; s.put (s.item (1).as_upper, 1); s32.append_string (s)
+ when 'D' then
+ s := a_date_time.days_text [a_date_time.date.day_of_the_week].string
+ s.to_lower; s.put (s.item (1).as_upper, 1); s32.append_string (s)
+ when 'l' then
+ s := a_date_time.long_days_text [a_date_time.date.day_of_the_week].string
+ s.to_lower; s.put (s.item (1).as_upper, 1); s32.append_string (s)
+
+ when 'd' then
+ if d < 10 then
+ s32.append_integer (0)
+ end
+ s32.append_integer (d)
+ when 'j' then
+ s32.append_integer (d)
+-- when 'z' then s32.append_integer (a_date_time.date.*year)
+ when 'a' then
+ if h >= 12 then
+ s32.append_character ('p'); s32.append_character ('m')
+ else
+ s32.append_character ('a'); s32.append_character ('m')
+ end
+ when 'A' then
+ if h >= 12 then
+ s32.append_character ('P'); s32.append_character ('M')
+ else
+ s32.append_character ('A'); s32.append_character ('M')
+ end
+ when 'g','h' then
+ if h >= 12 then
+ if c = 'h' and h - 12 < 10 then
+ s32.append_integer (0)
+ end
+ s32.append_integer (h - 12)
+ else
+ if c = 'h' and h < 10 then
+ s32.append_integer (0)
+ end
+ s32.append_integer (h)
+ end
+ when 'G', 'H' then
+ if c = 'H' and h < 10 then
+ s32.append_integer (0)
+ end
+ s32.append_integer (h)
+ when 'i' then
+ if mn < 10 then
+ s32.append_integer (0)
+ end
+ s32.append_integer (mn)
+ when 's' then
+ if sec < 10 then
+ s32.append_integer (0)
+ end
+ s32.append_integer (sec)
+ when 'u' then
+ s32.append_double (a_date_time.fine_second) -- CHECK result ...
+ when 'w' then s32.append_integer (a_date_time.date.day_of_the_week - 1)
+ when 'W' then s32.append_integer (a_date_time.date.week_of_year)
+ when 'L' then
+ if a_date_time.is_leap_year (y) then
+ s32.append_integer (1)
+ else
+ s32.append_integer (0)
+ end
+ when '\' then
+ if i < a_date_time_format.count then
+ i := i + 1
+ s32.append_character (a_date_time_format[i])
+ else
+ s32.append_character ('\')
+ end
+ else
+ s32.append_character (c)
+ end
+ i := i + 1
+ end
+ Result := s32
+ end
+
+ timezoned_date_time (a_utc_date_time: DATE_TIME): DATE_TIME
+ do
+ if utc_offset /= 0 or utc_offset_minute /= 0 then
+ Result := a_utc_date_time.deep_twin
+ Result.hour_add (utc_offset)
+ Result.minute_add (utc_offset_minute)
+ else
+ Result := a_utc_date_time
+ end
+ end
+
+ short_date (a_utc_date_time: DATE_TIME): STRING_8
+ local
+ l_date_time: DATE_TIME
+ l_now: DATE
+ cy,cm,cd,y,m,d,h,i: INTEGER
+ s: STRING
+ l_duration: DATE_TIME_DURATION --like {DATE_TIME}.relative_duration
+ do
+ create l_date_time.make_now_utc
+ l_duration := l_date_time.relative_duration (a_utc_date_time)
+
+ create l_now.make_now
+ cy := l_now.year
+ cm := l_now.month
+ cd := l_now.day
+
+ l_date_time := timezoned_date_time (a_utc_date_time)
+ y := l_date_time.date.year
+ m := l_date_time.date.month
+ d := l_date_time.date.day
+
+ if cy /= y then
+ if attached date_time_format as l_format then
+ Result := formatted_date_time (l_date_time, l_format)
+ else
+ Result := d.out + "/" + m.out + "/" + y.out
+ end
+ elseif cm /= m then
+ s := l_date_time.months_text [m].string
+ s.to_lower; s.put (s.item (1).as_upper, 1)
+ Result := s
+ Result.append (" ")
+ Result.append (d.out)
+ elseif cd /= d then
+ s := l_date_time.months_text [m].string
+ s.to_lower; s.put (s.item (1).as_upper, 1)
+ Result := s
+ Result.append (" ")
+ Result.append (d.out)
+ if l_duration.day < 7 then
+ s := l_date_time.days_text [l_date_time.date.day_of_the_week].string
+ s.to_lower; s.put (s.item (1).as_upper, 1)
+ Result.append (" - " + s)
+ end
+ else
+ check cd = d and cy = y and cm = m end
+ h := l_date_time.time.hour
+ i := l_date_time.time.minute
+ if h < 10 then
+ Result := "0" + h.out
+ else
+ Result := h.out
+ end
+ Result.append (":")
+ if i < 10 then
+ Result.append ("0")
+ end
+ Result.append (i.out)
+ end
+ end
+
+
+ smart_date_duration (a_utc_date_time: DATE_TIME): STRING_8
+ local
+ l_date_time: DATE_TIME
+ l_now: DATE_TIME
+ l_duration: DATE_TIME_DURATION --like {DATE_TIME}.relative_duration
+ l_duration_time: TIME_DURATION --like {DATE_TIME_DURATION}.time
+ y,m,w,d,h,i: INTEGER
+ w_now,w_utc: INTEGER
+ l_s_code: NATURAL_32
+ l_space_ago_string: STRING
+ do
+ l_s_code := ('s').natural_32_code
+ l_space_ago_string := " ago"
+ create l_now.make_now_utc
+ l_duration := l_now.relative_duration (a_utc_date_time)
+ y := l_duration.date.year
+ m := l_duration.date.month
+ d := l_duration.date.day
+ w_now := l_now.date.week_of_year
+ w_utc := a_utc_date_time.date.week_of_year
+ if y > 0 then
+ if y = 1 then
+ Result := "last year"
+ else
+ Result := y.out + " years"
+-- if m > 0 then
+-- Result.append (" and " + m.out + " month")
+-- if m > 1 then
+-- Result.append_code (l_s_code)
+-- end
+-- end
+ Result.append (l_space_ago_string)
+ end
+ elseif m > 0 then
+ if m = 1 then
+ Result := "last month"
+ else
+ Result := m.out + " months"
+-- if d > 0 then
+-- Result.append (" and " + d.out + " day")
+-- if d > 1 then
+-- Result.append_code (l_s_code)
+-- end
+-- end
+ Result.append (l_space_ago_string)
+ end
+ elseif d >= 7 then
+ w := d // 7
+ if w = 1 and then w_now = w_utc + 1 then
+ Result := "last week"
+ else
+
+ Result := (w+1).out + " weeks"
+-- if d > 7 then
+-- Result.append (" and " + (d - 7).out + " day")
+-- if d - 7 > 1 then
+-- Result.append_code (l_s_code)
+-- end
+-- end
+ Result.append (l_space_ago_string)
+ end
+ elseif d > 0 then
+ if w_now /= w_utc then
+ Result := "last week"
+ else
+ l_duration_time := l_duration.time
+ if d = 1 then
+ Result := "yesturday"
+ else
+ Result := d.out + " days"
+ -- if d = 1 then
+ -- h := l_duration_time.hour
+ -- if h > 0 then
+ -- Result.append (" and " + h.out + " hour")
+ -- if h > 1 then
+ -- Result.append_code (l_s_code)
+ -- end
+ -- end
+ -- end
+ Result.append (l_space_ago_string)
+ end
+ end
+ elseif d = 0 then
+ l_duration_time := l_duration.time
+ h := l_duration_time.hour
+ if h > 0 then
+ if h = 1 then
+ Result := "last hour"
+ else
+ Result := h.out + " hours"
+ Result.append (l_space_ago_string)
+ end
+ else
+ i := l_duration_time.minute
+ if i = 0 then
+ Result := l_duration_time.second.out + " second"
+ if l_duration_time.second > 1 then
+ Result.append_code (l_s_code)
+ end
+ else
+ Result := i.out + " minute"
+ if i > 1 then
+ Result.append_code (l_s_code)
+ end
+ end
+ Result.append (l_space_ago_string)
+ end
+ else
+ l_date_time := timezoned_date_time (a_utc_date_time)
+ if attached date_time_format as l_format then
+ Result := formatted_date_time (l_date_time, l_format)
+ else
+ Result := l_date_time.out
+ end
+ end
+ end
+
+note
+ copyright: "2011-2017, 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/theme/cms_html_page_response.e b/src/theme/cms_html_page_response.e
index 3d8c871..2591bec 100644
--- a/src/theme/cms_html_page_response.e
+++ b/src/theme/cms_html_page_response.e
@@ -57,14 +57,14 @@ feature {WSF_RESPONSE} -- Output
h.put_content_length (s.count)
end
if not h.has_content_type then
- h.put_content_type_text_html
+ h.put_content_type_with_charset ({HTTP_MIME_TYPES}.text_html, "utf-8")
end
res.put_header_text (h.string)
res.put_string (s)
end
note
- copyright: "2011-2012, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
+ copyright: "2011-2017, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software
") + across + l_comments as ic + loop + append_comment_as_html_to (ic.item, a_output, a_response) + end + a_output.append ("