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 ("