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.
This commit is contained in:
2017-01-27 11:57:52 +01:00
parent 2d698f604b
commit 7c398a9f33
45 changed files with 2284 additions and 271 deletions

View File

@@ -8,7 +8,6 @@
<exclude>/EIFGENs$</exclude>
</file_rule>
<option warning="true" void_safety="all">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<setting name="concurrency" value="scoop"/>
<mapping old_name="CMS_LAYOUT" new_name="CMS_ENVIRONMENT"/>
@@ -30,6 +29,7 @@
<library name="text_filter" location="$ISE_LIBRARY\unstable\library\text\text_filter\text_filter-safe.ecf"/>
<library name="time" location="$ISE_LIBRARY\library\time\time-safe.ecf"/>
<library name="uri_template" location="$ISE_LIBRARY\contrib\library\text\parser\uri_template\uri_template-safe.ecf"/>
<library name="uuid" location="$ISE_LIBRARY\library\uuid\uuid-safe.ecf"/>
<library name="wsf" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\wsf-safe.ecf"/>
<library name="wsf_extension" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\wsf_extension-safe.ecf" readonly="false"/>
<library name="wsf_html" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf_html\wsf_html-safe.ecf" readonly="false"/>

View File

@@ -8,8 +8,7 @@
<exclude>/CVS$</exclude>
<exclude>/EIFGENs$</exclude>
</file_rule>
<option warning="true" full_class_checking="false" void_safety="none" syntax="transitional">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
<option warning="true" void_safety="none">
</option>
<mapping old_name="CMS_LAYOUT" new_name="CMS_ENVIRONMENT"/>
<library name="base" location="$ISE_LIBRARY\library\base\base.ecf"/>
@@ -30,6 +29,7 @@
<library name="text_filter" location="$ISE_LIBRARY\unstable\library\text\text_filter\text_filter.ecf"/>
<library name="time" location="$ISE_LIBRARY\library\time\time.ecf"/>
<library name="uri_template" location="$ISE_LIBRARY\contrib\library\text\parser\uri_template\uri_template.ecf"/>
<library name="uuid" location="$ISE_LIBRARY\library\uuid\uuid.ecf"/>
<library name="wsf" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\wsf.ecf"/>
<library name="wsf_extension" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\wsf_extension.ecf" readonly="false"/>
<library name="wsf_html" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf_html\wsf_html.ecf" readonly="false"/>

View File

@@ -1,2 +1,4 @@
site/db/*
site/files/*
site/export/*
site/import/*

View File

@@ -24,6 +24,7 @@
<library name="cms_auth_module" location="..\..\modules\auth\auth-safe.ecf" readonly="false"/>
<library name="cms_basic_auth_module" location="..\..\modules\basic_auth\basic_auth-safe.ecf" readonly="false"/>
<library name="cms_blog_module" location="..\..\modules\blog\cms_blog_module-safe.ecf" readonly="false"/>
<library name="cms_comments_module" location="..\..\modules\comments\comments-safe.ecf" readonly="false"/>
<library name="cms_contact_module" location="..\..\modules\contact\contact-safe.ecf" readonly="false"/>
<library name="cms_custom_block_module" location="..\..\modules\custom_block\custom_block-safe.ecf" readonly="false"/>
<library name="cms_demo_module" location="modules\demo\cms_demo_module-safe.ecf" readonly="false"/>

View File

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

View File

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

View File

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

View File

@@ -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 */
);

View File

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

View File

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

View File

@@ -14,6 +14,7 @@
<library name="base_extension" location="$ISE_LIBRARY\library\base_extension\base_extension-safe.ecf"/>
<library name="cms" location="..\..\cms-safe.ecf" readonly="false"/>
<library name="cms_model" location="..\..\library\model\cms_model-safe.ecf" readonly="false"/>
<library name="cms_comments_module" location="..\..\modules\comments\comments-safe.ecf" readonly="false"/>
<library name="cms_node_module" location="..\..\modules\node\node-safe.ecf" readonly="false"/>
<library name="error" location="$ISE_LIBRARY\contrib\library\utility\general\error\error-safe.ecf"/>

View File

@@ -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
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
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
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_blog.title) as l_blogs and then
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
a_import_ctx.log (l_node_type.name + " %"" + f.path.utf_8_name + "%" skipped (already exists for user #" + l_author.id.out + ")!")
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 + " %"" + 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)
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
end
else
a_import_ctx.log (l_node_type.name + " %"" + f.path.utf_8_name + "%" skipped (Author is unknown!)")
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

View File

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

View File

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

View File

@@ -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)
-- <Precursor>
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
-- <Precursor>
feature -- Access: router
setup_router (a_router: WSF_ROUTER; a_api: CMS_API)
-- <Precursor>
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

View File

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

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-15-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-15-0 http://www.eiffel.com/developers/xml/configuration-1-15-0.xsd" name="comments_module" uuid="C1D39ECD-6993-456B-AD14-0CF94504340F" library_target="comments_module">
<target name="comments_module">
<root all_classes="true"/>
<file_rule>
<exclude>/.git$</exclude>
<exclude>/EIFGENs$</exclude>
<exclude>/.svn$</exclude>
</file_rule>
<option warning="true">
</option>
<setting name="concurrency" value="scoop"/>
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
<library name="base_extension" location="$ISE_LIBRARY\library\base_extension\base_extension-safe.ecf"/>
<library name="cms" location="..\..\cms-safe.ecf" readonly="false"/>
<library name="cms_model" location="..\..\library\model\cms_model-safe.ecf" readonly="false"/>
<library name="error" location="$ISE_LIBRARY\contrib\library\utility\general\error\error-safe.ecf"/>
<library name="http" location="$ISE_LIBRARY\contrib\library\network\protocol\http\http-safe.ecf"/>
<library name="json" location="$ISE_LIBRARY\contrib\library\text\parser\json\library\json-safe.ecf" readonly="false"/>
<library name="time" location="$ISE_LIBRARY\library\time\time-safe.ecf"/>
<library name="wsf" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\wsf-safe.ecf"/>
<library name="wsf_html" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf_html\wsf_html-safe.ecf"/>
<library name="wsf_extension" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\wsf_extension-safe.ecf" readonly="false"/>
<cluster name="src" location=".\" recursive="true"/>
</target>
</system>

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-15-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-15-0 http://www.eiffel.com/developers/xml/configuration-1-15-0.xsd" name="comments_module" uuid="C1D39ECD-6993-456B-AD14-0CF94504340F" library_target="comments_module">
<target name="comments_module">
<root all_classes="true"/>
<file_rule>
<exclude>/.git$</exclude>
<exclude>/EIFGENs$</exclude>
<exclude>/.svn$</exclude>
</file_rule>
<option warning="true" void_safety="none">
</option>
<setting name="concurrency" value="scoop"/>
<library name="base" location="$ISE_LIBRARY\library\base\base.ecf"/>
<library name="base_extension" location="$ISE_LIBRARY\library\base_extension\base_extension.ecf"/>
<library name="cms" location="..\..\cms.ecf" readonly="false"/>
<library name="cms_model" location="..\..\library\model\cms_model.ecf" readonly="false"/>
<library name="error" location="$ISE_LIBRARY\contrib\library\utility\general\error\error.ecf"/>
<library name="http" location="$ISE_LIBRARY\contrib\library\network\protocol\http\http.ecf"/>
<library name="json" location="$ISE_LIBRARY\contrib\library\text\parser\json\library\json.ecf" readonly="false"/>
<library name="time" location="$ISE_LIBRARY\library\time\time.ecf"/>
<library name="wsf" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\wsf.ecf"/>
<library name="wsf_html" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf_html\wsf_html.ecf"/>
<library name="wsf_extension" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\wsf_extension.ecf" readonly="false"/>
<cluster name="src" location=".\" recursive="true"/>
</target>
</system>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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 */
);

View File

@@ -27,10 +27,6 @@ inherit
CMS_TAXONOMY_HOOK
-- CMS_HOOK_EXPORT
-- CMS_EXPORT_NODE_UTILITIES
create
make

View File

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

View File

@@ -351,7 +351,8 @@ feature -- Output
end
a_output.append ("</p>")
end
elseif attached a_node.content as l_content then
else
if attached a_node.content as l_content then
a_output.append ("<p class=%"content%">")
if attached cms_api.format (a_node.format) as f then
append_formatted_content_to (l_content, f, a_output)
@@ -360,8 +361,73 @@ feature -- Output
end
a_output.append ("</p>")
end
append_comments_as_html_to (a_node, a_output, a_response)
end
a_output.append ("</div>")
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 ("<div class=%"comments-box%"><div class=%"title%">Comments</div><ul class=%"comments%">")
across
l_comments as ic
loop
append_comment_as_html_to (ic.item, a_output, a_response)
end
a_output.append ("</ul></div>")
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 ("<li class=%"comment%">")
if attached a_comment.author as l_author then
a_output.append ("<span class=%"author%">")
a_output.append (cms_api.html_encoded (l_author.name))
a_output.append ("</span>")
elseif attached a_comment.author_name as l_author_name then
a_output.append ("<span class=%"author%">")
a_output.append (cms_api.html_encoded (l_author_name))
a_output.append ("</span>")
end
if attached a_comment.creation_date as dt then
a_output.append (" <span class=%"info%">(")
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 (")</span>")
end
if attached a_comment.content as l_content then
a_output.append ("<div class=%"content%">")
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 ("</div>")
end
if attached a_comment.items as lst and then not lst.is_empty then
a_output.append ("<ul class=%"comments%">")
across
lst as ic
loop
append_comment_as_html_to (ic.item, a_output, a_response)
end
a_output.append ("</ul>")
end
a_output.append ("</li>")
end
end

View File

@@ -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,8 +112,21 @@ feature -- Conversion
then
l_node.set_link (lnk)
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_node_api.cms_api.module_api ({CMS_TAXONOMY_MODULE}) as l_taxonomy_api 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
@@ -75,8 +135,13 @@ feature -- Conversion
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)
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

View File

@@ -13,6 +13,7 @@
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
<library name="cms" location="..\..\cms-safe.ecf"/>
<library name="cms_model" location="..\..\library\model\cms_model-safe.ecf" readonly="false"/>
<library name="cms_comments_module" location="..\..\modules\comments\comments-safe.ecf" readonly="false"/>
<library name="cms_recent_changes_module" location="..\..\modules\recent_changes\recent_changes-safe.ecf" readonly="false"/>
<library name="cms_taxonomy_module" location="..\..\modules\taxonomy\taxonomy-safe.ecf" readonly="false"/>
<library name="error" location="$ISE_LIBRARY\contrib\library\utility\general\error\error-safe.ecf"/>

View File

@@ -7,12 +7,12 @@
<exclude>/CVS$</exclude>
<exclude>/.svn$</exclude>
</file_rule>
<option warning="true" full_class_checking="false" void_safety="none" syntax="transitional">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
<option warning="true" void_safety="none">
</option>
<library name="base" location="$ISE_LIBRARY\library\base\base.ecf"/>
<library name="cms" location="..\..\cms.ecf"/>
<library name="cms_model" location="..\..\library\model\cms_model.ecf" readonly="false"/>
<library name="cms_comments_module" location="..\..\modules\comments\comments.ecf" readonly="false"/>
<library name="cms_recent_changes_module" location="..\..\modules\recent_changes\recent_changes.ecf" readonly="false"/>
<library name="cms_taxonomy_module" location="..\..\modules\taxonomy\taxonomy.ecf" readonly="false"/>
<library name="error" location="$ISE_LIBRARY\contrib\library\utility\general\error\error.ecf"/>

View File

@@ -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
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
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
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_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!)")
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_page.author as l_author then
if attached l_entity.author as l_author then
if
attached l_page_api.pages_with_title (l_page.title) as l_pages and then
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
a_import_ctx.log (l_node_type.name + " %"" + f.path.utf_8_name + "%" skipped (already exists for user #" + l_author.id.out + ")!")
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.parent as l_parent and then
attached l_entity.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)
l_parentable_list.extend ([l_entity, l_parent])
l_entity.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)
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_page, l_link.location)
l_new_pages.force (l_entity, l_link.location)
end
end
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 (Author is unknown!)")
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 + " %"" + f.path.utf_8_name + "%" skipped (Status is Not Published!)")
end
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)
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_page.id.out + " : unable to find parent!")
end
a_import_ctx.log (l_node_type.name + " #" + l_entity.id.out + " : unable to find parent!")
end
end
end

View File

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

View File

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

View File

@@ -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,15 +74,48 @@ 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)
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
note
copyright: "2011-2017, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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