Extracted page support from cms_node_module, and add a proper CMS_PAGE_MODULE.

- now, the CMS_PAGE_MODULE has to be declared in the related CMS_SETUP via CMS_EXECUTION.
   (See demo for example)

Improved the export facilities.
  Implemented blog and page export.
Added import facilities.
  Implemented blog and page import.

Improved node revision web interface (allow to edit a past revision, in order to restore it as latest revisionm i.e current).
Removed specific tag from blog module, and reuse the taxonomy module for that purpose.

Added WIKITEXT module that provide a WIKITEXT_FILTER, so now we can have wikitext content.
   - for now, no support for wiki links such as [[Foobar]].
This commit is contained in:
2017-01-20 16:05:40 +01:00
parent 3bcfb0a44a
commit 2d698f604b
59 changed files with 1761 additions and 679 deletions

View File

@@ -39,6 +39,7 @@
<library name="cms_seo_module" location="..\..\modules\seo\seo-safe.ecf" readonly="false"/>
<library name="cms_session_auth_module" location="..\..\modules\session_auth\cms_session_auth-safe.ecf" readonly="false"/>
<library name="cms_taxnomy_module" location="..\..\modules\taxonomy\taxonomy-safe.ecf" readonly="false"/>
<library name="cms_wikitext_module" location="..\..\modules\wikitext\wikitext-safe.ecf" readonly="false"/>
<library name="persistence_sqlite3" location="..\..\library\persistence\sqlite3\sqlite3-safe.ecf" readonly="false"/>
<!--
By default, commented, since it depends on specific environment settings.

View File

@@ -19,6 +19,7 @@
"session_auth": { "location": "../../modules/session_auth" },
"taxonomy": { "location": "../../modules/taxonomy" },
"files": { "location": "../../modules/files" },
"custom_block": { "location": "../../modules/custom_block" }
"custom_block": { "location": "../../modules/custom_block" },
"wikitext": { "location": "../../modules/wikitext" }
}
}

View File

@@ -28,10 +28,3 @@ CREATE TABLE node_revisions (
CONSTRAINT Unique_nid_revision PRIMARY KEY (nid,revision)
);
CREATE TABLE page_nodes(
`nid` INTEGER NOT NULL,
`revision` INTEGER NOT NULL,
`parent` INTEGER,
CONSTRAINT PK_nid_revision PRIMARY KEY (nid,revision)
);

View File

@@ -0,0 +1,8 @@
CREATE TABLE page_nodes(
`nid` INTEGER NOT NULL,
`revision` INTEGER NOT NULL,
`parent` INTEGER,
CONSTRAINT PK_nid_revision PRIMARY KEY (nid,revision)
);

View File

@@ -0,0 +1,15 @@
#main code, #main e, #main eiffel {
display: block;
font-family: monospace;
white-space: pre-wrap;
border: solid 1px #ccc;
background-color: #fff;
line-height: 1.3;
padding: 10px;
margin: 2px 0 2px 0;
}
#main code.inline, #main e.inline, #main eiffel.inline {
display: inline-block;
padding: 0 2px 0 2px;
margin: 0;
}

View File

@@ -0,0 +1,18 @@
#main {
code, e, eiffel {
display: block;
font-family: monospace;
white-space: pre-wrap;
border: solid 1px #ccc;
background-color: #fff;
line-height: 1.3;
padding: 10px;
margin: 2px 0 2px 0;
&.inline {
display: inline-block;
padding: 0 2px 0 2px;
margin: 0;
}
}
}

View File

@@ -53,8 +53,12 @@ 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)
a_setup.register_module (create {CMS_BLOG_MODULE}.make)
-- Files
@@ -69,6 +73,9 @@ feature -- CMS modules
-- Taxonomy
a_setup.register_module (create {CMS_TAXONOMY_MODULE}.make)
-- Wiki
a_setup.register_module (create {WIKITEXT_MODULE}.make (a_setup))
-- Recent changes
a_setup.register_module (create {CMS_RECENT_CHANGES_MODULE}.make)

View File

@@ -58,6 +58,7 @@ feature -- Access: router
l_admin_cache_handler: CMS_ADMIN_CACHE_HANDLER
l_admin_export_handler: CMS_ADMIN_EXPORT_HANDLER
l_admin_import_handler: CMS_ADMIN_IMPORT_HANDLER
l_uri_mapping: WSF_URI_MAPPING
do
@@ -89,6 +90,10 @@ feature -- Access: router
create l_admin_export_handler.make (a_api)
create l_uri_mapping.make_trailing_slash_ignored ("/admin/export", l_admin_export_handler)
a_router.map (l_uri_mapping, a_router.methods_get_post)
create l_admin_import_handler.make (a_api)
create l_uri_mapping.make_trailing_slash_ignored ("/admin/import", l_admin_import_handler)
a_router.map (l_uri_mapping, a_router.methods_get_post)
create l_user_handler.make (a_api)
a_router.handle ("/admin/add/user", l_user_handler, a_router.methods_get_post)
@@ -118,7 +123,9 @@ feature -- Security
Result.force ("admin core caches")
Result.force ("clear blocks cache")
Result.force ("admin export")
Result.force ("admin import")
Result.force ("export core")
Result.force ("import core")
end
feature -- Hooks
@@ -154,6 +161,9 @@ feature -- Hooks
-- Per module export permission!
create lnk.make ("Export", "admin/export")
admin_lnk.extend (lnk)
-- Per module import permission!
create lnk.make ("Import", "admin/import")
admin_lnk.extend (lnk)
end
end

View File

@@ -54,7 +54,7 @@ feature -- Execution
l_response: CMS_RESPONSE
s: STRING
f: CMS_FORM
l_exportation_parameters: CMS_EXPORT_PARAMETERS
l_exportation: CMS_EXPORT_CONTEXT
do
create {GENERIC_VIEW_CMS_RESPONSE} l_response.make (req, res, api)
f := exportation_web_form (l_response)
@@ -65,15 +65,15 @@ feature -- Execution
then
if attached fd.string_item ("op") as l_op and then l_op.same_string (text_export_all_data) then
if attached fd.string_item ("folder") as l_folder then
create l_exportation_parameters.make (api.site_location.extended ("export").extended (l_folder))
create l_exportation.make (api.site_location.extended ("export").extended (l_folder))
else
create l_exportation_parameters.make (api.site_location.extended ("export").extended ((create {DATE_TIME}.make_now_utc).formatted_out ("yyyy-[0]mm-[0]dd---hh24-[0]mi-[0]ss")))
create l_exportation.make (api.site_location.extended ("export").extended ((create {DATE_TIME}.make_now_utc).formatted_out ("yyyy-[0]mm-[0]dd---hh24-[0]mi-[0]ss")))
end
api.hooks.invoke_export_to (Void, l_exportation_parameters, l_response)
api.hooks.invoke_export_to (Void, l_exportation, l_response)
l_response.add_notice_message ("All data exported (if allowed)!")
create s.make_empty
across
l_exportation_parameters.logs as ic
l_exportation.logs as ic
loop
s.append (ic.item)
s.append ("<br/>")

View File

@@ -0,0 +1,121 @@
note
description: "[
Administrate import functionality.
]"
date: "$Date$"
revision: "$Revision$"
class
CMS_ADMIN_IMPORT_HANDLER
inherit
CMS_HANDLER
WSF_URI_HANDLER
rename
new_mapping as new_uri_mapping
end
WSF_RESOURCE_HANDLER_HELPER
redefine
do_get,
do_post
end
REFACTORING_HELPER
create
make
feature -- Execution
execute (req: WSF_REQUEST; res: WSF_RESPONSE)
-- Execute request handler
do
execute_methods (req, res)
end
do_get (req: WSF_REQUEST; res: WSF_RESPONSE)
local
l_response: CMS_RESPONSE
s: STRING
f: CMS_FORM
do
create {GENERIC_VIEW_CMS_RESPONSE} l_response.make (req, res, api)
f := importation_web_form (l_response)
create s.make_empty
f.append_to_html (l_response.wsf_theme, s)
l_response.set_main_content (s)
l_response.execute
end
do_post (req: WSF_REQUEST; res: WSF_RESPONSE)
local
l_response: CMS_RESPONSE
s: STRING
f: CMS_FORM
l_importation: CMS_IMPORT_CONTEXT
p: PATH
do
create {GENERIC_VIEW_CMS_RESPONSE} l_response.make (req, res, api)
f := importation_web_form (l_response)
f.process (l_response)
if
attached f.last_data as fd and then
fd.is_valid
then
if attached fd.string_item ("op") as l_op and then l_op.same_string (text_import_all_data) then
if attached fd.string_item ("folder") as l_folder then
create p.make_from_string (l_folder)
if p.is_absolute then
create l_importation.make (p)
else
create l_importation.make (api.site_location.extended ("import").extended (l_folder))
end
else
create l_importation.make (api.site_location.extended ("import").extended ("default"))
end
api.hooks.invoke_import_from (Void, l_importation, l_response)
l_response.add_notice_message ("All data imported (if allowed)!")
create s.make_empty
across
l_importation.logs as ic
loop
s.append (ic.item)
s.append ("<br/>")
s.append_character ('%N')
end
l_response.add_notice_message (s)
else
fd.report_error ("Invalid form data!")
end
end
create s.make_empty
f.append_to_html (l_response.wsf_theme, s)
l_response.set_main_content (s)
l_response.execute
end
feature -- Widget
importation_web_form (a_response: CMS_RESPONSE): CMS_FORM
local
f_name: WSF_FORM_TEXT_INPUT
but: WSF_FORM_SUBMIT_INPUT
do
create Result.make (a_response.url (a_response.location, Void), "import_all_data")
Result.extend_raw_text ("Import CMS data from ")
create f_name.make_with_text ("folder", "default")
f_name.set_label ("Import folder name")
f_name.set_description ("Folder name under 'imports' folder.")
f_name.set_is_required (True)
Result.extend (f_name)
create but.make_with_text ("op", text_import_all_data)
Result.extend (but)
end
feature -- Interface text.
text_import_all_data: STRING_32 = "Import all data"
end

View File

@@ -283,7 +283,7 @@ feature -- Execution
fd.report_error ("Installation failed for module " + m.name)
end
else
fd.report_error ("Can not find associated module" + ic.item.as_string.url_encoded_value)
fd.report_error ("Can not find associated module " + ic.item.as_string.url_encoded_value)
end
end
else

View File

@@ -599,7 +599,7 @@ feature -- Handler
end
elseif attached {WSF_STRING} req.form_parameter ("username") as l_username then
if
attached {CMS_USER} l_user_api.user_by_name (l_username) as l_user and then
attached {CMS_USER} l_user_api.user_by_name (l_username.value) as l_user and then
attached l_user.email as l_user_email
then
-- User exist create a new token and send a new email.

View File

@@ -9,8 +9,7 @@ class
inherit
CMS_NODE
redefine
make_empty,
import_node
make_empty
end
create
@@ -24,23 +23,6 @@ feature {NONE} -- Initialization
Precursor
end
feature -- Conversion
import_node (a_node: CMS_NODE)
-- <Precursor>
do
Precursor (a_node)
if attached {CMS_BLOG} a_node as l_blog then
if attached l_blog.tags as l_tags then
across
l_tags as ic
loop
add_tag (ic.item)
end
end
end
end
feature -- Access
content_type: READABLE_STRING_8
@@ -60,11 +42,6 @@ feature -- Access: node
-- Format associated with `content' and `summary'.
-- For example: text, mediawiki, html, etc
feature -- Access: blog
tags: detachable ARRAYED_LIST [READABLE_STRING_32]
-- Optional tags
feature -- Element change: node
set_content (a_content: like content; a_summary: like summary; a_format: like format)
@@ -74,39 +51,5 @@ feature -- Element change: node
format := a_format
end
feature -- Element change: blog
add_tag (a_tag: READABLE_STRING_32)
-- Set `parent' to `a_page'
require
not a_tag.is_whitespace
local
l_tags: like tags
do
l_tags := tags
if l_tags = Void then
create l_tags.make (1)
tags := l_tags
end
l_tags.force (a_tag)
end
set_tags_from_string (a_tags: READABLE_STRING_32)
local
t: STRING_32
do
tags := Void
across
a_tags.split (',') as ic
loop
t := ic.item
t.left_adjust
t.right_adjust
if not t.is_whitespace then
add_tag (t)
end
end
end
end

View File

@@ -1,6 +1,5 @@
note
description: "API to handle nodes of type blog. Extends the node API."
author: "Dario B<>sch <daboesch@student.ethz.ch"
date: "$Date: 2015-05-21 14:46:00 +0100$"
revision: "$Revision: 96616 $"
@@ -41,6 +40,11 @@ feature {NONE} -- Initialization
else
create {CMS_BLOG_STORAGE_NULL} blog_storage.make
end
--| 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
-- initialize_node_types
end
@@ -54,10 +58,8 @@ feature {CMS_MODULE} -- Access nodes storage.
feature -- Configuration of blog handlers
entries_per_page : NATURAL_32 = 2
entries_per_page : NATURAL_32
-- The numbers of posts that are shown on one page. If there are more post a pagination is generated
--| For test reasons this is 2, so we don't have to create a lot of blog entries.
--| TODO: Set to bigger constant.
feature -- Access node
@@ -88,7 +90,7 @@ feature -- Access node
Result := nodes_to_blogs (blog_storage.blogs_limited (a_limit, a_offset))
end
blogs_from_user_order_created_desc_limited (a_user: CMS_USER; a_limit: NATURAL_32; a_offset: NATURAL_32) : LIST [CMS_BLOG]
blogs_from_user_order_created_desc_limited (a_user: CMS_USER; a_limit: NATURAL_32; a_offset: NATURAL_32): LIST [CMS_BLOG]
-- List of nodes ordered by creation date and limited by limit and offset
require
has_id: a_user.has_id
@@ -97,6 +99,15 @@ feature -- Access node
Result := nodes_to_blogs (blog_storage.blogs_from_user_limited (a_user, a_limit, a_offset))
end
blogs_by_title (a_user: CMS_USER; a_title: READABLE_STRING_GENERAL): LIST [CMS_BLOG]
-- List of blogs with title `a_title` for user `a_user` and ordered by creation date .
require
has_id: a_user.has_id
do
-- load all posts and add the authors to each post
Result := nodes_to_blogs (blog_storage.blogs_from_user_with_title (a_user, a_title))
end
feature -- Conversion
full_blog_node (a_blog: CMS_BLOG): CMS_BLOG
@@ -112,6 +123,13 @@ feature -- Conversion
end
end
feature -- Commit
save_blog (a_blog: CMS_BLOG)
do
node_api.save_node (a_blog)
end
feature {NONE} -- Helpers
nodes_to_blogs (a_nodes: LIST [CMS_NODE]): ARRAYED_LIST [CMS_BLOG]

View File

@@ -1,6 +1,5 @@
note
description: "Displays all posts (pages with type blog). It's possible to list posts by user."
author: "Dario B<>sch <daboesch@student.ethz.ch>"
date: "$Date: 2015-05-22 15:13:00 +0100 (lun., 18 mai 2015) $"
revision: "$Revision 96616$"
@@ -24,8 +23,12 @@ inherit
CMS_HOOK_EXPORT
CMS_HOOK_IMPORT
CMS_EXPORT_NODE_UTILITIES
CMS_IMPORT_NODE_UTILITIES
create
make
@@ -34,8 +37,8 @@ feature {NONE} -- Initialization
make
do
version := "1.0"
description := "Service to demonstrate new node for blog"
package := "demo"
description := "Blogging service"
package := "content"
add_dependency ({CMS_NODE_MODULE})
end
@@ -51,14 +54,12 @@ feature {CMS_API} -- Module Initialization
ct: CMS_BLOG_NODE_TYPE
do
Precursor (api)
if attached {CMS_NODE_API} api.module_api ({CMS_NODE_MODULE}) as l_node_api then
create ct
create blog_api.make (api, l_node_api)
node_api := l_node_api
-- Depends on {CMS_NODE_MODULE}
--| For now, add all available formats to content type `ct'.
across
api.formats as ic
@@ -67,12 +68,6 @@ feature {CMS_API} -- Module Initialization
end
l_node_api.add_node_type (ct)
l_node_api.add_node_type_webform_manager (create {CMS_BLOG_NODE_TYPE_WEBFORM_MANAGER}.make (ct, l_node_api))
-- Add support for CMS_BLOG, which requires a storage extension to store the optional "tags" value
-- For now, we only have extension based on SQL statement.
if attached {CMS_NODE_STORAGE_SQL} l_node_api.node_storage as l_sql_node_storage then
l_sql_node_storage.register_node_storage_extension (create {CMS_NODE_STORAGE_SQL_BLOG_EXTENSION}.make (l_node_api, l_sql_node_storage))
end
end
end
@@ -148,6 +143,7 @@ feature -- Hooks
a_hooks.subscribe_to_menu_system_alter_hook (Current)
a_hooks.subscribe_to_response_alter_hook (Current)
a_hooks.subscribe_to_export_hook (Current)
a_hooks.subscribe_to_import_hook (Current)
end
response_alter (a_response: CMS_RESPONSE)
@@ -164,7 +160,7 @@ feature -- Hooks
a_menu_system.primary_menu.extend (lnk)
end
export_to (a_export_id_list: detachable ITERABLE [READABLE_STRING_GENERAL]; a_export_parameters: CMS_EXPORT_PARAMETERS; a_response: CMS_RESPONSE)
export_to (a_export_id_list: detachable ITERABLE [READABLE_STRING_GENERAL]; a_export_ctx: CMS_EXPORT_CONTEXT; a_response: CMS_RESPONSE)
-- Export data identified by `a_export_id_list',
-- or export all data if `a_export_id_list' is Void.
local
@@ -175,21 +171,28 @@ feature -- Hooks
lst: LIST [CMS_BLOG]
do
if
a_export_id_list = Void
or else across a_export_id_list as ic some ic.item.same_string ("blog") end
attached node_api as l_node_api and then
attached l_node_api.node_type ("blog") as l_node_type and then
( a_export_id_list = Void
or else across a_export_id_list as ic some ic.item.same_string (l_node_type.name) end
)
then
if
a_response.has_permissions (<<"export any node", "export blog">>) and then
a_response.has_permissions (<<"export any node", "export " + l_node_type.name>>) and then
attached blog_api as l_blog_api
then
lst := l_blog_api.blogs_order_created_desc
a_export_parameters.log ("Exporting " + lst.count.out + " blogs")
a_export_ctx.log ("Exporting " + lst.count.out + " [" + l_node_type.name + "] items...")
create d.make_with_path (a_export_ctx.location.extended ("nodes").extended (l_node_type.name))
if d.exists then
d.recursive_delete
end
across
lst as ic
loop
n := l_blog_api.full_blog_node (ic.item)
a_export_parameters.log (n.content_type + " #" + n.id.out)
p := a_export_parameters.location.extended ("nodes").extended (n.content_type).extended (n.id.out).appended_with_extension ("json")
a_export_ctx.log (n.content_type + " #" + n.id.out)
p := a_export_ctx.location.extended ("nodes").extended (n.content_type).extended (n.id.out).appended_with_extension ("json")
create d.make_with_path (p.parent)
if not d.exists then
d.recursive_create_dir
@@ -197,16 +200,16 @@ feature -- Hooks
create f.make_with_path (p)
if not f.exists or else f.is_access_writable then
f.open_write
f.put_string (json_to_string (blog_node_to_json (n)))
f.put_string (json_to_string (blog_node_to_json (n, l_blog_api)))
f.close
end
-- Revisions.
if
attached node_api as l_node_api and then
attached l_node_api.node_revisions (n) as l_revisions and then l_revisions.count > 1
attached l_node_api.node_revisions (n) as l_revisions and then
l_revisions.count > 1
then
a_export_parameters.log (n.content_type + " " + l_revisions.count.out + " revisions.")
p := a_export_parameters.location.extended ("nodes").extended (n.content_type).extended (n.id.out)
a_export_ctx.log (n.content_type + " " + l_revisions.count.out + " revisions.")
p := a_export_ctx.location.extended ("nodes").extended (n.content_type).extended (n.id.out)
create d.make_with_path (p)
if not d.exists then
d.recursive_create_dir
@@ -218,7 +221,7 @@ feature -- Hooks
create f.make_with_path (p.extended ("rev-" + n.revision.out).appended_with_extension ("json"))
if not f.exists or else f.is_access_writable then
f.open_write
f.put_string (json_to_string (blog_node_to_json (l_blog)))
f.put_string (json_to_string (blog_node_to_json (l_blog, l_blog_api)))
end
f.close
end
@@ -229,8 +232,102 @@ feature -- Hooks
end
end
blog_node_to_json (a_blog: CMS_BLOG): JSON_OBJECT
blog_node_to_json (a_blog: CMS_BLOG; a_blog_api: CMS_BLOG_API): JSON_OBJECT
do
Result := node_to_json (a_blog)
Result := node_to_json (a_blog, a_blog_api.node_api)
end
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
f: PLAIN_TEXT_FILE
s: STRING
jp: JSON_PARSER
loc: STRING
do
if
attached node_api as l_node_api and then
attached {CMS_BLOG_NODE_TYPE} l_node_api.node_type ("blog") as l_node_type and then
( a_import_id_list = Void
or else across a_import_id_list as ic some ic.item.same_string ({CMS_BLOG_NODE_TYPE}.name) end
)
then
if
a_response.has_permissions (<<"import any node", "import " + l_node_type.name>>) and then
attached blog_api as l_blog_api
then
p := a_import_ctx.location.extended ("nodes").extended (l_node_type.name)
create d.make_with_path (p)
if d.exists and then d.is_readable then
a_import_ctx.log ("Importing [" + l_node_type.name + "] items ..")
across
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
end
end
else
a_import_ctx.log (l_node_type.name + " %"" + f.path.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
end
end
end
end
end
end
end
end
end
end
json_to_node_blog (a_node_type: CMS_BLOG_NODE_TYPE; j: JSON_OBJECT; a_node_api: CMS_NODE_API): detachable CMS_BLOG
do
if attached json_to_node (a_node_type, j, a_node_api) as l_node then
Result := a_node_type.new_node (l_node)
end
end
end

View File

@@ -9,11 +9,7 @@ class
inherit
CMS_NODE_TYPE_WEBFORM_MANAGER [CMS_BLOG]
redefine
content_type,
populate_form,
update_node,
new_node,
append_content_as_html_to
content_type
end
create
@@ -24,76 +20,4 @@ feature -- Access
content_type: CMS_BLOG_NODE_TYPE
-- Associated content type.
feature -- form
populate_form (response: NODE_RESPONSE; f: CMS_FORM; a_node: detachable CMS_NODE)
local
ti: WSF_FORM_TEXT_INPUT
s: STRING_32
do
Precursor (response, f, a_node)
create ti.make ("tags")
ti.set_label ("Tags")
ti.set_size (70)
if
a_node /= Void and then
attached {CMS_BLOG} a_node as a_blog and then
attached a_blog.tags as l_tags
then
create s.make_empty
across
l_tags as ic
loop
if not s.is_empty then
s.append_character (',')
end
s.append (ic.item)
end
ti.set_text_value (s)
end
ti.set_is_required (False)
f.extend (ti)
end
update_node (response: NODE_RESPONSE; fd: WSF_FORM_DATA; a_node: CMS_NODE)
do
Precursor (response, fd, a_node)
if attached fd.string_item ("tags") as l_tags then
if attached {CMS_BLOG} a_node as l_blog then
l_blog.set_tags_from_string (l_tags)
end
end
end
new_node (response: NODE_RESPONSE; fd: WSF_FORM_DATA; a_node: detachable like new_node): like content_type.new_node
-- <Precursor>
do
Result := Precursor (response, fd, a_node)
if attached fd.string_item ("tags") as l_tags then
Result.set_tags_from_string (l_tags)
end
end
feature -- Output
append_content_as_html_to (a_node: CMS_BLOG; is_teaser: BOOLEAN; a_output: STRING; a_response: detachable CMS_RESPONSE)
-- <Precursor>
do
Precursor (a_node, is_teaser, a_output, a_response)
if attached {CMS_BLOG} a_node as l_blog_post then
if attached l_blog_post.tags as l_tags then
a_output.append ("<div><strong>Tags:</strong> ")
across
l_tags as ic
loop
a_output.append ("<span class=%"tag%">")
a_output.append (cms_api.html_encoded (ic.item))
a_output.append ("</span> ")
end
a_output.append ("</div>")
end
end
end
end

View File

@@ -1,167 +0,0 @@
note
description: "Storage extension for Blog nodes."
date: "$Date$"
revision: "$Revision$"
class
CMS_NODE_STORAGE_SQL_BLOG_EXTENSION
inherit
CMS_NODE_STORAGE_EXTENSION [CMS_BLOG]
CMS_PROXY_STORAGE_SQL
rename
make as make_proxy,
sql_storage as node_storage
redefine
node_storage
end
create
make
feature {NONE} -- Initialization
make (a_node_api: CMS_NODE_API; a_sql_storage: CMS_NODE_STORAGE_SQL)
do
set_node_api (a_node_api)
make_proxy (a_sql_storage)
end
node_storage: CMS_NODE_STORAGE_SQL
-- <Precursor>
feature -- Access
content_type: STRING
once
Result := {CMS_BLOG_NODE_TYPE}.name
end
feature -- Persistence
store (a_node: CMS_BLOG)
-- <Precursor>.
local
l_parameters: STRING_TABLE [detachable ANY]
l_new_tags: detachable STRING_32
l_previous_tags: detachable STRING_32
l_update: BOOLEAN
l_has_modif: BOOLEAN
do
if attached api as l_api then
l_api.logger.put_information (generator + ".store", Void)
end
error_handler.reset
-- Check existing record, if any.
if attached node_data (a_node) as d then
l_update := d.revision = a_node.revision
l_previous_tags := d.tags
end
if not has_error then
if attached a_node.tags as l_tags and then not l_tags.is_empty then
create l_new_tags.make (0)
across
l_tags as ic
loop
if not l_new_tags.is_empty then
l_new_tags.append_character (',')
end
l_new_tags.append (ic.item)
end
else
l_new_tags := Void
end
l_has_modif := l_has_modif or (l_new_tags /~ l_previous_tags)
create l_parameters.make (3)
l_parameters.put (a_node.id, "nid")
l_parameters.put (a_node.revision, "revision")
l_parameters.force (l_new_tags, "tags")
if l_update then
if l_has_modif then
sql_modify (sql_update_node_data, l_parameters)
end
else
if l_has_modif then
sql_insert (sql_insert_node_data, l_parameters)
else
-- no page data, means everything is empty.
-- FOR NOW: always record row
-- sql_change (sql_insert_node_data, l_parameters)
end
end
sql_finalize
end
end
load (a_node: CMS_BLOG)
-- <Precursor>.
local
l_tags: READABLE_STRING_32
do
if attached node_data (a_node) as d then
l_tags := d.tags
a_node.set_tags_from_string (l_tags)
end
end
delete_node (a_node: CMS_BLOG)
-- <Precursor>
local
l_parameters: STRING_TABLE [ANY]
do
if a_node.has_id then
create l_parameters.make (1)
l_parameters.put (a_node.id, "nid")
sql_modify (sql_delete_node_data, l_parameters)
sql_finalize
end
end
feature {NONE} -- Implementation
node_data (a_node: CMS_NODE): detachable TUPLE [revision: INTEGER_64; tags: READABLE_STRING_32]
-- Node extension data for node `a_node' as tuple.
local
l_parameters: STRING_TABLE [ANY]
l_rev: INTEGER_64
l_tags: detachable READABLE_STRING_32
n: INTEGER
do
error_handler.reset
create l_parameters.make (2)
l_parameters.put (a_node.id, "nid")
l_parameters.put (a_node.revision, "revision")
sql_query (sql_select_node_data, l_parameters)
if not has_error then
if not sql_after then
-- nid, revision, tags
l_rev := sql_read_integer_64 (2)
l_tags := sql_read_string_32 (3)
if l_tags /= Void then
Result := [l_rev, l_tags]
end
sql_forth
if not sql_after then
check unique_data: n = 0 end
Result := Void
end
end
end
sql_finalize
ensure
accepted_revision: Result /= Void implies Result.revision <= a_node.revision
end
feature -- SQL
sql_select_node_data: STRING = "SELECT nid, revision, tags FROM blog_post_nodes WHERE nid=:nid AND revision<=:revision ORDER BY revision DESC LIMIT 1;"
sql_insert_node_data: STRING = "INSERT INTO blog_post_nodes (nid, revision, tags) VALUES (:nid, :revision, :tags);"
sql_update_node_data: STRING = "UPDATE blog_post_nodes SET nid=:nid, revision=:revision, tags=:tags WHERE nid=:nid AND revision=:revision;"
sql_delete_node_data: STRING = "DELETE FROM blog_post_nodes WHERE nid=:nid;"
end

View File

@@ -44,4 +44,11 @@ feature -- Access
deferred
end
blogs_from_user_with_title (a_user: CMS_USER; a_title: READABLE_STRING_GENERAL): LIST [CMS_NODE]
-- List of blogs from `a_user' with title `a_title` and ordered by creation date.
require
has_id: a_user.has_id
deferred
end
end

View File

@@ -44,4 +44,10 @@ feature -- Access
do
create {ARRAYED_LIST [CMS_NODE]} Result.make (0)
end
blogs_from_user_with_title (a_user: CMS_USER; a_title: READABLE_STRING_GENERAL): LIST [CMS_NODE]
-- List of blogs from `a_user' with title `a_title` and ordered by creation date.
do
create {ARRAYED_LIST [CMS_NODE]} Result.make (0)
end
end

View File

@@ -105,7 +105,7 @@ feature -- Access
write_information_log (generator + ".blogs_from_user_limited")
from
create l_parameters.make (2)
create l_parameters.make (3)
l_parameters.put (a_limit, "limit")
l_parameters.put (a_offset, "offset")
l_parameters.put (a_user.id, "user")
@@ -122,6 +122,33 @@ feature -- Access
sql_finalize
end
blogs_from_user_with_title (a_user: CMS_USER; a_title: READABLE_STRING_GENERAL): LIST [CMS_NODE]
-- <Precursor>
local
l_parameters: STRING_TABLE [detachable ANY]
do
create {ARRAYED_LIST [CMS_NODE]} Result.make (0)
error_handler.reset
write_information_log (generator + ".blogs_from_user_with_title")
from
create l_parameters.make (2)
l_parameters.put (a_user.id, "user")
l_parameters.put (a_title, "title")
sql_query (sql_blogs_from_user_with_title, l_parameters)
sql_start
until
sql_after
loop
if attached fetch_node as l_node then
Result.force (l_node)
end
sql_forth
end
sql_finalize
end
feature {NONE} -- Queries
sql_select_blog_count: STRING = "SELECT count(*) FROM nodes WHERE status != -1 AND type = %"blog%";"
@@ -136,10 +163,12 @@ feature {NONE} -- Queries
-- SQL Query to retrieve all nodes that are from the type "blog" ordered by descending creation date.
sql_blogs_limited: STRING = "SELECT * FROM nodes WHERE status != -1 AND type = %"blog%" ORDER BY created DESC LIMIT :limit OFFSET :offset ;"
--- SQL Query to retrieve all node of type "blog" limited by limit and starting at offset
--- SQL Query to retrieve all nodes of type "blog" limited by limit and starting at offset
sql_blogs_from_user_limited: STRING = "SELECT * FROM nodes WHERE status != -1 AND type = %"blog%" AND author = :user ORDER BY created DESC LIMIT :limit OFFSET :offset ;"
--- SQL Query to retrieve all node of type "blog" from author with id limited by limit + offset
--- SQL Query to retrieve all nodes of type "blog" from author with id limited by limit + offset
sql_blogs_from_user_with_title: STRING = "SELECT * FROM nodes WHERE status != -1 AND type = %"blog%" AND author = :user AND title = :title ORDER BY created DESC;"
--- SQL Query to retrieve all nodes of type "blog" from author with title .
end

View File

@@ -1,6 +0,0 @@
CREATE TABLE blog_post_nodes(
`nid` INTEGER NOT NULL CHECK("nid">=0),
`revision` INTEGER NOT NULL,
`tags` VARCHAR(255),
CONSTRAINT PK_nid_revision PRIMARY KEY (nid,revision)
);

View File

@@ -10,9 +10,6 @@ class
inherit
CMS_MODULE_API
redefine
initialize
end
REFACTORING_HELPER
@@ -28,30 +25,6 @@ feature {NONE} -- Initialization
-- error_handler.add_synchronization (a_node_storage.error_handler)
end
initialize
-- <Precursor>
do
Precursor
initialize_node_types
end
initialize_node_types
-- Initialize content type system.
local
ct: CMS_PAGE_NODE_TYPE
do
-- Initialize node content types.
create ct
--| For now, add all available formats to content type `ct'.
across
cms_api.formats as ic
loop
ct.extend_format (ic.item)
end
add_node_type (ct)
add_node_type_webform_manager (create {CMS_PAGE_NODE_TYPE_WEBFORM_MANAGER}.make (ct, Current))
end
feature {CMS_MODULE} -- Access nodes storage.
node_storage: CMS_NODE_STORAGE_I
@@ -301,45 +274,9 @@ feature -- Access: Node
expected_type: across Result as ic all ic.item.content_type.same_string (a_node_type.name) end
end
feature -- Access: page/book outline
children (a_node: CMS_NODE): detachable LIST [CMS_NODE]
-- Children of node `a_node'.
-- note: this is the partial version of the nodes.
nodes_of_type_with_title (a_node_type: CMS_CONTENT_TYPE; a_title: READABLE_STRING_GENERAL): LIST [CMS_NODE]
do
Result := node_storage.children (a_node)
end
available_parents_for_node (a_node: CMS_NODE): LIST [CMS_NODE]
-- Potential parent nodes for node `a_node'.
-- Ensure no cycle exists.
do
create {ARRAYED_LIST [CMS_NODE]} Result.make (0)
across node_storage.available_parents_for_node (a_node) as ic loop
check distinct: not a_node.same_node (ic.item) end
if not is_node_a_parent_of (a_node, ic.item) then
Result.force (ic.item)
end
end
ensure
no_cycle: across Result as c all not is_node_a_parent_of (a_node, c.item) end
end
is_node_a_parent_of (a_node: CMS_NODE; a_child: CMS_NODE): BOOLEAN
-- Is `a_node' a direct or indirect parent of node `a_child'?
require
distinct_nodes: not a_node.same_node (a_child)
do
if
attached {CMS_PAGE} full_node (a_child) as l_child_page and then
attached l_child_page.parent as l_parent
then
if l_parent.same_node (a_node) then
Result := True
else
Result := is_node_a_parent_of (a_node, l_parent)
end
end
Result := node_storage.nodes_of_type_with_title (a_node_type, a_title)
end
feature -- Permission Scope: Node

View File

@@ -27,9 +27,9 @@ inherit
CMS_TAXONOMY_HOOK
CMS_HOOK_EXPORT
-- CMS_HOOK_EXPORT
CMS_EXPORT_NODE_UTILITIES
-- CMS_EXPORT_NODE_UTILITIES
create
make
@@ -60,8 +60,6 @@ feature {CMS_API} -- Module Initialization
initialize (a_api: CMS_API)
-- <Precursor>
local
p1,p2: CMS_PAGE
ct: CMS_PAGE_NODE_TYPE
l_node_api: like node_api
l_node_storage: CMS_NODE_STORAGE_I
do
@@ -78,34 +76,6 @@ feature {CMS_API} -- Module Initialization
-- Node API initialization
create l_node_api.make_with_storage (a_api, l_node_storage)
node_api := l_node_api
-- Add support for CMS_PAGE, which requires a storage extension to store the optional "parent" id.
-- For now, we only have extension based on SQL statement.
if attached {CMS_NODE_STORAGE_SQL} l_node_api.node_storage as l_sql_node_storage then
l_sql_node_storage.register_node_storage_extension (create {CMS_NODE_STORAGE_SQL_PAGE_EXTENSION}.make (l_node_api, l_sql_node_storage))
-- FIXME: the following code is mostly for test purpose/initialization, remove later
if l_sql_node_storage.sql_table_items_count ("page_nodes") = 0 then
if attached a_api.user_api.user_by_id (1) as u then
create ct
p1 := ct.new_node (Void)
p1.set_title ("Welcome")
p1.set_content ("Welcome, you are using the ROC Eiffel CMS", Void, Void) -- Use default format
p1.set_author (u)
l_sql_node_storage.save_node (p1)
p2 := ct.new_node (Void)
p2.set_title ("A new page example")
p2.set_content ("This is the content of a page", Void, Void) -- Use default format
p2.set_author (u)
p2.set_parent (p1)
l_sql_node_storage.save_node (p2)
end
end
else
-- FIXME: maybe provide a default solution based on file system, when no SQL storage is available.
-- IDEA: we could also have generic extension to node system, that handle generic addition field.
end
ensure then
node_api_set: node_api /= Void
end
@@ -117,8 +87,7 @@ feature {CMS_API} -- Module management
do
Result := Precursor (a_api)
if Result and attached a_api.storage.as_sql_storage as l_sql_storage then
Result := l_sql_storage.sql_table_exists ("nodes") and
l_sql_storage.sql_table_exists ("page_nodes")
Result := l_sql_storage.sql_table_exists ("nodes")
end
end
@@ -247,7 +216,7 @@ feature -- Hooks
a_hooks.subscribe_to_menu_system_alter_hook (Current)
a_hooks.subscribe_to_block_hook (Current)
a_hooks.subscribe_to_response_alter_hook (Current)
a_hooks.subscribe_to_export_hook (Current)
-- a_hooks.subscribe_to_export_hook (Current)
-- Module specific hook, if available.
a_hooks.subscribe_to_hook (Current, {CMS_RECENT_CHANGES_HOOK})
@@ -427,94 +396,4 @@ feature -- Hooks
end
end
export_to (a_export_id_list: detachable ITERABLE [READABLE_STRING_GENERAL]; a_export_parameters: CMS_EXPORT_PARAMETERS; a_response: CMS_RESPONSE)
-- Export data identified by `a_export_id_list',
-- or export all data if `a_export_id_list' is Void.
local
l_node_type: CMS_CONTENT_TYPE
n: CMS_NODE
p: PATH
d: DIRECTORY
f: PLAIN_TEXT_FILE
lst: LIST [CMS_NODE]
do
if attached node_api as l_node_api then
across
l_node_api.node_types as types_ic
loop
l_node_type := types_ic.item
if
a_response.has_permissions (<<"export any node", "export " + l_node_type.name>>) and then
l_node_type.name.same_string_general ("page") and then
( a_export_id_list = Void
or else across a_export_id_list as ic some ic.item.same_string (l_node_type.name) end
)
then
-- For now, handle only page from this node module.
lst := l_node_api.nodes_of_type (l_node_type)
a_export_parameters.log ("Exporting " + lst.count.out + " nodes of type " + l_node_type.name)
p := a_export_parameters.location.extended ("nodes").extended (l_node_type.name)
create d.make_with_path (p)
if not d.exists then
d.recursive_create_dir
end
across
lst as ic
loop
n := l_node_api.full_node (ic.item)
a_export_parameters.log (l_node_type.name + " #" + n.id.out + " rev=" + n.revision.out)
create f.make_with_path (p.extended (n.id.out).appended_with_extension ("json"))
if not f.exists or else f.is_access_writable then
f.open_write
if attached {CMS_PAGE} n as l_page then
f.put_string (json_to_string (page_node_to_json (l_page)))
else
f.put_string (json_to_string (node_to_json (n)))
end
f.close
end
-- Revisions.
if attached l_node_api.node_revisions (n) as l_revisions and then l_revisions.count > 1 then
a_export_parameters.log (l_node_type.name + " " + l_revisions.count.out + " revisions.")
p := a_export_parameters.location.extended ("nodes").extended (l_node_type.name).extended (n.id.out)
create d.make_with_path (p)
if not d.exists then
d.recursive_create_dir
end
across
l_revisions as revs_ic
loop
n := revs_ic.item
create f.make_with_path (p.extended ("rev-" + n.revision.out).appended_with_extension ("json"))
if not f.exists or else f.is_access_writable then
f.open_write
if attached {CMS_PAGE} n as l_page then
f.put_string (json_to_string (page_node_to_json (l_page)))
else
f.put_string (json_to_string (node_to_json (n)))
end
f.close
end
end
end
end
end
end
end
end
page_node_to_json (a_page: CMS_PAGE): JSON_OBJECT
local
j: JSON_OBJECT
do
Result := node_to_json (a_page)
if attached a_page.parent as l_parent_page then
create j.make_empty
j.put_string (l_parent_page.content_type, "type")
j.put_integer (l_parent_page.id, "nid")
Result.put (j, "parent")
end
end
end

View File

@@ -11,11 +11,15 @@ class
inherit
CMS_EXPORT_JSON_UTILITIES
CMS_API_ACCESS
feature -- Access
node_to_json (n: CMS_NODE): JSON_OBJECT
node_to_json (n: CMS_NODE; a_node_api: CMS_NODE_API): JSON_OBJECT
local
jo,j_author: JSON_OBJECT
jo: JSON_OBJECT
ja: JSON_ARRAY
j, jterm: JSON_OBJECT
do
create Result.make_empty
Result.put_string (n.content_type, "type")
@@ -27,10 +31,7 @@ feature -- Access
put_date_into_json (n.publication_date, "publication_date", Result)
Result.put_integer (n.status, "status")
if attached n.author as u then
create j_author.make
j_author.put_integer (u.id, "uid")
j_author.put_string (u.name, "name")
Result.put (j_author, "author")
Result.put (user_to_json (u), "author")
end
create jo.make_empty
if attached n.format as l_format then
@@ -44,12 +45,41 @@ feature -- Access
end
Result.put (jo, "data")
if attached n.link as lnk then
create jo.make_empty
jo.put_string (lnk.title, "title")
jo.put_string (lnk.location, "location")
jo.put_integer (lnk.weight, "weight")
Result.put (jo, "link")
Result.put (link_to_json (lnk), "link")
end
if attached {CMS_TAXONOMY_API} a_node_api.cms_api.module_api ({CMS_TAXONOMY_MODULE}) as l_taxonomy_api then
if
attached l_taxonomy_api.terms_of_content (n, Void) as l_tags and then
not l_tags.is_empty
then
create ja.make (l_tags.count)
across
l_tags as ic
loop
create jterm.make_with_capacity (2)
jterm.put_integer (ic.item.id, "id")
jterm.put_string (ic.item.text, "text")
ja.extend (jterm)
end
Result.put (ja, "tags")
end
end
end
user_to_json (u: CMS_USER): JSON_OBJECT
do
create Result.make
Result.put_integer (u.id, "uid")
Result.put_string (u.name, "name")
end
link_to_json (lnk: CMS_LOCAL_LINK): JSON_OBJECT
do
create Result.make_empty
Result.put_string (lnk.title, "title")
Result.put_string (lnk.location, "location")
Result.put_integer (lnk.weight, "weight")
end
end

View File

@@ -8,6 +8,8 @@ class
inherit
CMS_NODE_TYPE_WEBFORM_MANAGER [CMS_PAGE]
rename
make as make_for_node
redefine
content_type,
append_content_as_html_to,
@@ -19,11 +21,22 @@ inherit
create
make
feature {NONE} -- Initialization
make (a_type: like content_type; a_page_api: CMS_PAGE_API)
do
page_api := a_page_api
make_for_node (a_type, a_page_api.node_api)
end
feature -- Access
content_type: CMS_PAGE_NODE_TYPE
-- Associated content type.
page_api: CMS_PAGE_API
-- Associated page API.
feature -- Forms ...
populate_form (response: NODE_RESPONSE; f: CMS_FORM; a_node: detachable CMS_NODE)
@@ -122,7 +135,7 @@ feature -- Forms ...
if
nid > 0 and then
attached l_node_api.node (nid) as l_node and then
l_node_api.is_node_a_parent_of (l_node, l_parent_node)
page_api.is_node_a_parent_of (l_node, l_parent_node)
then
fd.report_invalid_field ("select_parent_node", "Invalid parent due to cycle (node #" + nid.out + " is already a parent of node #" + l_parent_id.out)
end
@@ -144,7 +157,7 @@ feature -- Output
lnk: CMS_LOCAL_LINK
do
Precursor (a_node, is_teaser, a_output, a_response)
if not is_teaser then
l_node_api := node_api
if
@@ -170,7 +183,7 @@ feature -- Output
a_output.append (a_response.link (l_parent_node.title, l_node_api.node_path (l_parent_node), Void))
a_output.append ("</li>")
end
if attached l_node_api.children (a_node) as l_children then
if attached page_api.children (a_node) as l_children then
across
l_children as ic
loop

View File

@@ -30,7 +30,15 @@ feature -- Execution
location.ends_with_general ("/edit") and then
node_api.has_permission_for_action_on_node ("edit", l_node, user)
then
edit_node (l_node, l_type, b)
if
attached {WSF_STRING} request.query_parameter ("revision") as p_rev and then
p_rev.value.is_integer_64 and then
attached node_api.revision_node (l_node.id, p_rev.value.to_integer_64) as l_rev_node
then
edit_node (l_rev_node, l_type, l_rev_node.revision < l_node.revision, b)
else
edit_node (l_node, l_type, False, b)
end
elseif
location.ends_with_general ("/delete") and then
node_api.has_permission_for_action_on_node ("delete", l_node, user)
@@ -118,12 +126,15 @@ feature {NONE} -- Create a new node
end
edit_node (a_node: CMS_NODE; a_type: CMS_NODE_TYPE [CMS_NODE]; b: STRING_8)
edit_node (a_node: CMS_NODE; a_type: CMS_NODE_TYPE [CMS_NODE]; is_old_revision: BOOLEAN; b: STRING_8)
local
f: like new_edit_form
fd: detachable WSF_FORM_DATA
do
f := new_edit_form (A_node, url (location, Void), "edit-" + a_type.name, a_type)
if is_old_revision then
add_warning_message ("You are editing old revision #" + a_node.revision.out + " !")
end
api.hooks.invoke_form_alter (f, fd, Current)
if request.is_post_request_method then
f.validation_actions.extend (agent edit_form_validate (?, b))
@@ -135,6 +146,7 @@ feature {NONE} -- Create a new node
add_to_menu (node_local_link (a_node, translation ("View", Void)), primary_tabs)
add_to_menu (create {CMS_LOCAL_LINK}.make (translation ("Edit", Void), node_api.node_path (a_node) + "/edit"), primary_tabs)
add_to_menu (create {CMS_LOCAL_LINK}.make ("Delete", node_api.node_path (a_node) + "/delete"), primary_tabs)
add_to_menu (create {CMS_LOCAL_LINK}.make ("Revisions", node_api.node_path (a_node) + "/revision"), primary_tabs)
end
if attached redirection as l_location then

View File

@@ -122,6 +122,7 @@ feature -- HTTP Methods
if
l_node /= Void and then
l_rev > 0 and then
l_rev < l_node.revision and then
node_api.has_permission_for_action_on_node ("view revisions", l_node, api.user)
then
l_node := node_api.revision_node (l_nid, l_rev)
@@ -322,7 +323,12 @@ feature {NONE} -- Trash:Restore
b.append ("<a href=%"")
b.append (r.url (node_api.node_path (n) + "?revision=" + n.revision.out, Void))
b.append ("%">")
if n.revision = l_node.revision then
b.append ("Current ")
else
b.append ("Revision")
end
b.append (" #")
b.append (n.revision.out)
b.append (" : ")
b.append (n.modification_date.out)
@@ -331,6 +337,11 @@ feature {NONE} -- Trash:Restore
b.append (" by ")
b.append (r.link (l_author.name, "user/" + l_author.id.out, Void))
end
if node_api.has_permission_for_action_on_node ("edit revisions", l_node, api.user) then
b.append (" (<a href=%"")
b.append (r.url (node_api.node_path (n) + "/edit?revision=" + n.revision.out, Void))
b.append ("%">edit</a>)")
end
b.append ("</li>")
end
b.append ("</ul>")

View File

@@ -38,6 +38,7 @@ feature -- Execution
local
nid: INTEGER_64
l_node: like node
l_title: STRING_32
do
l_node := node
if l_node = Void then
@@ -62,8 +63,11 @@ feature -- Execution
if revision > 0 then
add_warning_message ("The revisions let you track differences between multiple versions of a post.")
end
if l_node /= Void and revision > 0 then
set_title ("Revision #" + revision.out + " of " + html_encoded (l_node.title))
if l_node /= Void and then revision > 0 and then revision < l_node.revision then
create l_title.make_from_string_general ("Revision #" + revision.out + " of %"")
l_title.append (l_node.title)
l_title.append_character ('"')
set_title (l_title)
end
end

View File

@@ -0,0 +1,106 @@
note
description: "[
Routines usefull during node exportation (see {CMS_HOOK_IMPORT}).
]"
date: "$Date$"
revision: "$Revision$"
deferred class
CMS_IMPORT_NODE_UTILITIES
inherit
CMS_IMPORT_JSON_UTILITIES
CMS_API_ACCESS
feature -- Access
user_by_name (a_name: READABLE_STRING_GENERAL; a_node_api: CMS_NODE_API): detachable CMS_USER
-- CMS user named `a_name`.
do
Result := a_node_api.cms_api.user_api.user_by_name (a_name)
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
local
l_node: CMS_PARTIAL_NODE
do
if
attached json_string_item (j, "title") as l_title
then
create l_node.make_empty (a_node_type.name)
l_node.set_title (l_title)
Result := l_node
if attached json_date_item (j, "creation_date") as dt then
l_node.set_creation_date (dt)
end
if attached json_date_item (j, "modification_date") as dt then
l_node.set_modification_date (dt)
end
if attached json_date_item (j, "publication_date") as dt then
l_node.set_publication_date (dt)
end
if attached json_integer_item (j, "status") as l_status then
inspect l_status
when {CMS_NODE_API}.published then
l_node.mark_published
when {CMS_NODE_API}.not_published then
l_node.mark_not_published
when {CMS_NODE_API}.trashed then
l_node.mark_trashed
else
end
end
if attached {JSON_OBJECT} j.item ("author") as j_author then
l_node.set_author (json_to_user (j_author, a_node_api))
end
if attached {JSON_OBJECT} j.item ("data") as j_data then
l_node.set_all_content (json_string_item (j_data, "content"), json_string_item (j_data, "summary"), json_string_8_item (j_data, "format"))
end
if
attached {JSON_OBJECT} j.item ("link") as j_link and then
attached json_to_local_link (j_link) as lnk
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
end
end
end
end
json_to_user (j_user: JSON_OBJECT; a_node_api: CMS_NODE_API): detachable CMS_USER
do
if
attached json_string_item (j_user, "name") as l_author_name
then
Result := user_by_name (l_author_name, a_node_api)
end
end
json_to_local_link (j_link: JSON_OBJECT): detachable CMS_LOCAL_LINK
do
if
attached json_string_8_item (j_link, "location") as loc
then
create Result.make (json_string_item (j_link, "title"), loc)
Result.set_weight (json_integer_item (j_link, "weight").to_integer_32)
end
end
end

View File

@@ -140,19 +140,22 @@ feature -- Access
expected_type: across Result as ic all ic.item.content_type.same_string (a_node_type.name) end
end
feature -- Access: outline
children (a_node: CMS_NODE): detachable LIST [CMS_NODE]
-- Children of node `a_node'.
-- note: this is the partial version of the nodes.
deferred
end
available_parents_for_node (a_node: CMS_NODE): LIST [CMS_NODE]
-- Given the node `a_node', return the list of possible parent nodes id
deferred
ensure
a_node_excluded: across Result as ic all not a_node.same_node (ic.item) end
nodes_of_type_with_title (a_node_type: CMS_CONTENT_TYPE; a_title: READABLE_STRING_GENERAL): LIST [CMS_NODE]
-- List of nodes of type `a_node_type' with title `a_title`.
--| Redefine to optimize!
do
Result := nodes_of_type (a_node_type)
from
Result.start
until
Result.after
loop
if a_title.same_string (Result.item.title) then
Result.forth
else
Result.remove
end
end
end
feature -- Change: Node

View File

@@ -75,19 +75,6 @@ feature -- Access: node
do
end
feature -- Access: outline
children (a_node: CMS_NODE): detachable LIST [CMS_NODE]
-- <Precursor>
do
end
available_parents_for_node (a_node: CMS_NODE): LIST [CMS_NODE]
-- <Precursor>
do
create {ARRAYED_LIST [CMS_NODE]} Result.make (0)
end
feature -- Node
new_node (a_node: CMS_NODE)

View File

@@ -13,7 +13,8 @@ inherit
CMS_NODE_STORAGE_I
redefine
nodes_of_type
nodes_of_type,
nodes_of_type_with_title
end
CMS_STORAGE_SQL_I
@@ -277,53 +278,27 @@ feature -- Access
sql_finalize
end
feature -- Access: outline
children (a_node: CMS_NODE): detachable LIST [CMS_NODE]
-- <Precursor>
nodes_of_type_with_title (a_node_type: CMS_CONTENT_TYPE; a_title: READABLE_STRING_GENERAL): LIST [CMS_NODE]
-- List of nodes of type `a_node_type' with title `a_title`.
local
l_parameters: STRING_TABLE [detachable ANY]
do
create {ARRAYED_LIST [CMS_NODE]} Result.make (0)
error_handler.reset
write_information_log (generator + ".children")
write_information_log (generator + ".nodes_of_type_with_title")
create l_parameters.make (2)
l_parameters.put (a_node_type.name, "node_type")
l_parameters.put (a_title, "title")
from
create l_parameters.make (1)
l_parameters.put (a_node.id, "nid")
sql_query (sql_select_children_of_node, l_parameters)
sql_start
until
sql_after
loop
if attached fetch_node as l_node then
Result.force (l_node)
end
sql_forth
end
sql_finalize
end
available_parents_for_node (a_node: CMS_NODE): LIST [CMS_NODE]
-- <Precursor>
local
l_parameters: STRING_TABLE [detachable ANY]
do
create {ARRAYED_LIST [CMS_NODE]} Result.make (0)
error_handler.reset
write_information_log (generator + ".available_parents_for_node")
from
create l_parameters.make (1)
l_parameters.put (a_node.id, "nid")
sql_query (sql_select_available_parents_for_node, l_parameters)
sql_query (sql_select_nodes_of_type_with_title, l_parameters)
sql_start
until
sql_after
loop
if attached fetch_node as l_node then
check expected_node_type: l_node.content_type.same_string (a_node_type.name) end
Result.force (l_node)
end
sql_forth
@@ -377,7 +352,7 @@ feature -- Change: Node
sql_modify (sql_delete_node, l_parameters)
sql_finalize
-- we remove node_revisions and pages.
-- we remove node_revisions and potential extended nodes.
-- Check: maybe we need a transaction.
sql_modify (sql_delete_node_revisions, l_parameters)
sql_finalize
@@ -516,6 +491,10 @@ feature {NONE} -- Queries
-- SQL Query to retrieve all nodes of type :node_type.
--| note: {CMS_NODE_API}.trashed = -1
sql_select_nodes_of_type_with_title: STRING = "SELECT nid, revision, type, title, summary, content, format, author, publish, created, changed, status FROM nodes WHERE status != -1 AND type=:node_type AND title=:title;"
-- SQL Query to retrieve all nodes of type :node_type with title :title.
--| note: {CMS_NODE_API}.trashed = -1
sql_select_node_revisions: STRING = "SELECT nodes.nid, node_revisions.revision, nodes.type, node_revisions.title, node_revisions.summary, node_revisions.content, node_revisions.format, node_revisions.author, nodes.publish, nodes.created, node_revisions.changed, node_revisions.status FROM nodes INNER JOIN node_revisions ON nodes.nid = node_revisions.nid WHERE nodes.nid = :nid AND node_revisions.revision < :revision ORDER BY node_revisions.revision DESC;"
-- SQL query to get node revisions (missing the latest one).
@@ -557,22 +536,8 @@ feature {NONE} -- Queries
Sql_last_insert_node_revision: STRING = "SELECT MAX(revision) FROM node_revisions;"
Sql_last_insert_node_revision_for_nid: STRING = "SELECT MAX(revision) FROM node_revisions WHERE nid=:nid;"
sql_select_available_parents_for_node : STRING = "[
SELECT node.nid, node.revision, node.type, title, summary, content, format, author, publish, created, changed, status
FROM nodes node LEFT JOIN page_nodes pn ON node.nid = pn.nid AND node.nid != :nid
WHERE node.nid != :nid AND pn.parent != :nid AND node.status != -1 GROUP BY node.nid, node.revision;
]"
sql_select_children_of_node: STRING = "[
SELECT node.nid, node.revision, node.type, title, summary, content, format, author, publish, created, changed, status
FROM nodes node LEFT JOIN page_nodes pn ON node.nid = pn.nid
WHERE pn.parent = :nid AND node.status != -1 GROUP BY node.nid, node.revision;
]"
sql_delete_node_revisions: STRING = "DELETE FROM node_revisions WHERE nid=:nid;"
feature {NONE} -- Implementation
fetch_node: detachable CMS_PARTIAL_NODE

View File

@@ -28,10 +28,3 @@ CREATE TABLE node_revisions (
CONSTRAINT Unique_nid_revision PRIMARY KEY (nid,revision)
);
CREATE TABLE page_nodes(
`nid` INTEGER NOT NULL,
`revision` INTEGER NOT NULL,
`parent` INTEGER,
CONSTRAINT PK_nid_revision PRIMARY KEY (nid,revision)
);

View File

@@ -0,0 +1,8 @@
CREATE TABLE page_nodes(
`nid` INTEGER NOT NULL,
`revision` INTEGER NOT NULL,
`parent` INTEGER,
CONSTRAINT PK_nid_revision PRIMARY KEY (nid,revision)
);

View File

@@ -0,0 +1,172 @@
note
description: "API to handle nodes of type page. Extends the node API."
date: "$Date$"
revision: "$Revision$"
class
CMS_PAGE_API
inherit
CMS_MODULE_API
rename
make as make_with_cms_api
redefine
initialize
end
REFACTORING_HELPER
create
make
feature {NONE} -- Initialization
make (a_api: CMS_API; a_node_api: CMS_NODE_API)
-- (from CMS_MODULE_API)
-- (export status {NONE})
do
node_api := a_node_api
make_with_cms_api (a_api)
end
initialize
-- <Precursor>
do
Precursor
---- l_sql_node_storage.register_node_storage_extension (create {CMS_NODE_STORAGE_SQL_PAGE_EXTENSION}.make (l_node_api, l_sql_node_storage))
-- Create the node storage for type blog
if attached storage.as_sql_storage as l_storage_sql then
create {CMS_PAGE_STORAGE_SQL} page_storage.make (l_storage_sql)
else
create {CMS_PAGE_STORAGE_NULL} page_storage.make
end
initialize_node_types
end
initialize_node_types
-- Initialize content type system.
local
ct: CMS_PAGE_NODE_TYPE
do
-- Initialize node content types.
create ct
page_content_type := ct
--| For now, add all available formats to content type `ct'.
across
cms_api.formats as ic
loop
ct.extend_format (ic.item)
end
node_api.add_node_type (ct)
node_api.add_node_type_webform_manager (create {CMS_PAGE_NODE_TYPE_WEBFORM_MANAGER}.make (ct, Current))
end
feature {CMS_API_ACCESS, CMS_MODULE, CMS_API} -- Restricted access
node_api: CMS_NODE_API
page_content_type: CMS_PAGE_NODE_TYPE
feature -- Access
pages: LIST [CMS_PAGE]
-- All pages.
do
Result := nodes_to_pages (node_api.nodes_of_type (page_content_type))
end
pages_with_title (a_title: READABLE_STRING_GENERAL): LIST [CMS_PAGE]
-- List of pages with title `a_title`.
do
Result := nodes_to_pages (node_api.nodes_of_type_with_title (page_content_type, a_title))
end
feature -- Access: page/book outline
children (a_node: CMS_NODE): detachable LIST [CMS_NODE]
-- Children of node `a_node'.
-- note: this is the partial version of the nodes.
do
Result := page_storage.children (a_node)
end
available_parents_for_node (a_node: CMS_NODE): LIST [CMS_NODE]
-- Potential parent nodes for node `a_node'.
-- Ensure no cycle exists.
do
create {ARRAYED_LIST [CMS_NODE]} Result.make (0)
across page_storage.available_parents_for_node (a_node) as ic loop
check distinct: not a_node.same_node (ic.item) end
if not is_node_a_parent_of (a_node, ic.item) then
Result.force (ic.item)
end
end
ensure
no_cycle: across Result as c all not is_node_a_parent_of (a_node, c.item) end
end
is_node_a_parent_of (a_node: CMS_NODE; a_child: CMS_NODE): BOOLEAN
-- Is `a_node' a direct or indirect parent of node `a_child'?
require
distinct_nodes: not a_node.same_node (a_child)
do
if
attached {CMS_PAGE} node_api.full_node (a_child) as l_child_page and then
attached l_child_page.parent as l_parent
then
if l_parent.same_node (a_node) then
Result := True
else
Result := is_node_a_parent_of (a_node, l_parent)
end
end
end
feature -- Conversion
full_page_node (a_page: CMS_PAGE): CMS_PAGE
-- If `a_page' is partial, return the full page node from `a_page',
-- otherwise return directly `a_page'.
require
a_page_set: a_page /= Void
do
if attached {CMS_PAGE} node_api.full_node (a_page) as l_full_page then
Result := l_full_page
else
Result := a_page
end
end
feature -- Commit
save_page (a_page: CMS_PAGE)
do
node_api.save_node (a_page)
end
feature {CMS_MODULE} -- Access nodes storage.
page_storage: CMS_PAGE_STORAGE_I
feature {NONE} -- Helpers
nodes_to_pages (a_nodes: LIST [CMS_NODE]): ARRAYED_LIST [CMS_PAGE]
-- Convert list of nodes into a list of page when possible.
do
create {ARRAYED_LIST [CMS_PAGE]} Result.make (a_nodes.count)
if attached node_api as l_node_api then
across
a_nodes as ic
loop
if attached {CMS_PAGE} l_node_api.full_node (ic.item) as l_page then
Result.force (l_page)
end
end
end
end
end

View File

@@ -0,0 +1,411 @@
note
description: "Node page implementation."
date: "$Date$"
revision: "$Revision$"
class
CMS_PAGE_MODULE
inherit
CMS_MODULE
rename
module_api as page_api
redefine
setup_hooks,
initialize,
install, is_installed,
page_api
end
CMS_HOOK_EXPORT
CMS_HOOK_IMPORT
CMS_EXPORT_NODE_UTILITIES
CMS_IMPORT_NODE_UTILITIES
create
make
feature {NONE} -- Initialization
make
do
version := "1.0"
description := "Page service"
package := "content"
add_dependency ({CMS_NODE_MODULE})
end
feature -- Access
name: STRING = "page"
feature {CMS_API} -- Module Initialization
initialize (a_api: CMS_API)
-- <Precursor>
local
p1,p2: CMS_PAGE
ct: CMS_PAGE_NODE_TYPE
do
Precursor (a_api)
if attached {CMS_NODE_API} a_api.module_api ({CMS_NODE_MODULE}) as l_node_api then
node_api := l_node_api
create page_api.make (a_api, l_node_api)
-- Add support for CMS_PAGE, which requires a storage extension to store the optional "parent" id.
-- For now, we only have extension based on SQL statement.
if attached {CMS_NODE_STORAGE_SQL} l_node_api.node_storage as l_sql_node_storage then
l_sql_node_storage.register_node_storage_extension (create {CMS_NODE_STORAGE_SQL_PAGE_EXTENSION}.make (l_node_api, l_sql_node_storage))
-- FIXME: the following code is mostly for test purpose/initialization, remove later
if l_sql_node_storage.sql_table_items_count ("page_nodes") = 0 then
if attached a_api.user_api.user_by_id (1) as u then
create ct
p1 := ct.new_node (Void)
p1.set_title ("Welcome")
p1.set_content ("Welcome, you are using the ROC Eiffel CMS", Void, Void) -- Use default format
p1.set_author (u)
l_sql_node_storage.save_node (p1)
p2 := ct.new_node (Void)
p2.set_title ("A new page example")
p2.set_content ("This is the content of a page", Void, Void) -- Use default format
p2.set_author (u)
p2.set_parent (p1)
l_sql_node_storage.save_node (p2)
end
end
else
-- FIXME: maybe provide a default solution based on file system, when no SQL storage is available.
-- IDEA: we could also have generic extension to node system, that handle generic addition field.
end
end
end
feature {CMS_API} -- Module management
is_installed (a_api: CMS_API): BOOLEAN
-- Is Current module installed?
do
Result := Precursor (a_api)
if Result and attached a_api.storage.as_sql_storage as l_sql_storage then
Result := l_sql_storage.sql_table_exists ("page_nodes")
end
end
install (a_api: CMS_API)
do
-- Schema
if attached a_api.storage.as_sql_storage as l_sql_storage then
if attached a_api.module ({CMS_NODE_MODULE}) as l_node_module then
l_sql_storage.sql_execute_file_script (a_api.module_resource_location (l_node_module, (create {PATH}.make_from_string ("scripts")).extended (name).appended_with_extension ("sql")), Void)
end
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
page_api: detachable CMS_PAGE_API
-- <Precursor>
node_api: detachable CMS_NODE_API
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_export_hook (Current)
a_hooks.subscribe_to_import_hook (Current)
end
export_to (a_export_id_list: detachable ITERABLE [READABLE_STRING_GENERAL]; a_export_ctx: CMS_EXPORT_CONTEXT; a_response: CMS_RESPONSE)
-- Export data identified by `a_export_id_list',
-- or export all data if `a_export_id_list' is Void.
local
n: CMS_PAGE
p: PATH
d: DIRECTORY
f: PLAIN_TEXT_FILE
lst: LIST [CMS_NODE]
do
if
attached node_api as l_node_api and then
attached l_node_api.node_type ("page") as l_node_type and then
( a_export_id_list = Void
or else across a_export_id_list as ic some ic.item.same_string (l_node_type.name) end
)
then
if
a_response.has_permissions (<<"export any node", "export " + l_node_type.name>>) and then
attached page_api as l_page_api
then
lst := l_page_api.pages
a_export_ctx.log ("Exporting " + lst.count.out + " notes of type `" + l_node_type.name + "`.")
create d.make_with_path (a_export_ctx.location.extended ("nodes").extended (l_node_type.name))
if d.exists then
d.recursive_delete
end
across
lst as ic
loop
if attached {CMS_PAGE} ic.item as l_page_node then
n := l_page_api.full_page_node (l_page_node)
a_export_ctx.log (n.content_type + " #" + n.id.out)
p := a_export_ctx.location.extended ("nodes").extended (n.content_type).extended (n.id.out).appended_with_extension ("json")
create d.make_with_path (p.parent)
if not d.exists then
d.recursive_create_dir
end
create f.make_with_path (p)
if not f.exists or else f.is_access_writable then
f.open_write
f.put_string (json_to_string (page_node_to_json (n, l_page_api)))
f.close
end
-- Revisions.
if
attached l_node_api.node_revisions (n) as l_revisions and then
l_revisions.count > 1
then
a_export_ctx.log (n.content_type + " " + l_revisions.count.out + " revisions.")
p := a_export_ctx.location.extended ("nodes").extended (n.content_type).extended (n.id.out)
create d.make_with_path (p)
if not d.exists then
d.recursive_create_dir
end
across
l_revisions as revs_ic
loop
if attached {CMS_PAGE} revs_ic.item as l_rev_page then
create f.make_with_path (p.extended ("rev-" + n.revision.out).appended_with_extension ("json"))
if not f.exists or else f.is_access_writable then
f.open_write
f.put_string (json_to_string (page_node_to_json (l_rev_page, l_page_api)))
end
f.close
end
end
end
end
end
end
end
end
page_node_to_json (a_page: CMS_PAGE; a_page_api: CMS_PAGE_API): JSON_OBJECT
local
j: JSON_OBJECT
p: CMS_PAGE
do
Result := node_to_json (a_page, a_page_api.node_api)
if attached a_page.parent as l_parent_page then
p := a_page_api.full_page_node (l_parent_page)
create j.make_empty
j.put_string (p.content_type, "type")
j.put_string (p.title, "title")
j.put_integer (p.id, "nid")
if attached p.link as lnk then
j.put (link_to_json (lnk), "link")
end
Result.put (j, "parent")
end
end
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
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.
do
if
attached node_api as l_node_api and then
attached {CMS_PAGE_NODE_TYPE} l_node_api.node_type ({CMS_PAGE_NODE_TYPE}.name) as l_node_type and then
attached page_api as l_page_api and then
( a_import_id_list = Void
or else across a_import_id_list as ic some ic.item.same_string (l_node_type.name) end
)
then
if
a_response.has_permissions (<<"import any node", "import " + l_node_type.name>>)
then
p := a_import_ctx.location.extended ("nodes").extended (l_node_type.name)
create d.make_with_path (p)
if d.exists and then d.is_readable then
create l_parentable_list.make (0)
create l_new_pages.make (0)
a_import_ctx.log ("Importing [" + l_node_type.name + "] items ..")
across
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
if
attached l_page_api.pages_with_title (l_page.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 + ")!")
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
end
end
else
a_import_ctx.log (l_node_type.name + " %"" + f.path.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
end
end
end
end
end
end
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
end
end
end
end
end
end
json_to_node_page (a_node_type: CMS_PAGE_NODE_TYPE; j: JSON_OBJECT; a_node_api: CMS_NODE_API): detachable CMS_PAGE
local
p: detachable CMS_PAGE
do
if attached json_to_node (a_node_type, j, a_node_api) as l_node then
Result := a_node_type.new_node (l_node)
if
attached {JSON_OBJECT} j.item ("parent") as j_parent and then
attached json_string_item (j_parent, "title") as l_title
then
p := a_node_type.new_node_with_title (l_title, Void)
if
attached {JSON_OBJECT} j_parent.item ("link") as j_link and then
attached json_to_local_link (j_link) as l_parent_lnk and then
not l_parent_lnk.location.starts_with ("node/")
then
p.set_link (l_parent_lnk)
else
-- No link ..
end
if not Result.is_parent_of (p) then
Result.set_parent (p)
end
end
end
end
update_page_parent (a_page: CMS_PAGE; a_parent: CMS_PAGE; a_new_pages: STRING_TABLE [CMS_PAGE])
require
no_parent_set: a_page.parent = Void
local
lst: detachable LIST [CMS_PAGE]
p: detachable CMS_PAGE
do
p := a_parent
if attached page_api as l_page_api then
if attached a_parent.link as l_parent_lnk then
lst := l_page_api.pages_with_title (a_parent.title)
across
lst as ic
until
p.has_id
loop
if
attached ic.item.link as lnk and then
lnk.location.same_string_general (l_parent_lnk.location)
then
p := ic.item
end
end
else
lst := l_page_api.pages_with_title (a_parent.title)
if lst.count = 1 then
p := lst.first
end
end
end
if p.has_id then
a_page.set_parent (p)
end
end
end

View File

@@ -0,0 +1,31 @@
note
description: "Summary description for {CMS_PAGE_STORAGE_I}."
date: "$Date$"
revision: "$Revision$"
deferred class
CMS_PAGE_STORAGE_I
feature -- Error Handling
error_handler: ERROR_HANDLER
-- Error handler.
deferred
end
feature -- Access
children (a_node: CMS_NODE): detachable LIST [CMS_NODE]
-- Children of node `a_node'.
-- note: this is the partial version of the nodes.
deferred
end
available_parents_for_node (a_node: CMS_NODE): LIST [CMS_NODE]
-- Given the node `a_node', return the list of possible parent nodes id
deferred
ensure
a_node_excluded: across Result as ic all not a_node.same_node (ic.item) end
end
end

View File

@@ -0,0 +1,31 @@
note
description: "Summary description for {CMS_PAGE_STORAGE_NULL}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
CMS_PAGE_STORAGE_NULL
inherit
CMS_NODE_STORAGE_NULL
CMS_PAGE_STORAGE_I
create
make
feature -- Access
children (a_node: CMS_NODE): detachable LIST [CMS_NODE]
-- <Precursor>
do
end
available_parents_for_node (a_node: CMS_NODE): LIST [CMS_NODE]
-- <Precursor>
do
create {ARRAYED_LIST [CMS_NODE]} Result.make (0)
end
end

View File

@@ -0,0 +1,86 @@
note
description: "Access to the sql database for the page module"
date: "$Date$"
revision: "$Revision$"
class
CMS_PAGE_STORAGE_SQL
inherit
CMS_NODE_STORAGE_SQL
CMS_PAGE_STORAGE_I
create
make
feature -- Access
children (a_node: CMS_NODE): detachable LIST [CMS_NODE]
-- <Precursor>
local
l_parameters: STRING_TABLE [detachable ANY]
do
create {ARRAYED_LIST [CMS_NODE]} Result.make (0)
error_handler.reset
write_information_log (generator + ".children")
from
create l_parameters.make (1)
l_parameters.put (a_node.id, "nid")
sql_query (sql_select_children_of_node, l_parameters)
sql_start
until
sql_after
loop
if attached fetch_node as l_node then
Result.force (l_node)
end
sql_forth
end
sql_finalize
end
available_parents_for_node (a_node: CMS_NODE): LIST [CMS_NODE]
-- <Precursor>
local
l_parameters: STRING_TABLE [detachable ANY]
do
create {ARRAYED_LIST [CMS_NODE]} Result.make (0)
error_handler.reset
write_information_log (generator + ".available_parents_for_node")
from
create l_parameters.make (1)
l_parameters.put (a_node.id, "nid")
sql_query (sql_select_available_parents_for_node, l_parameters)
sql_start
until
sql_after
loop
if attached fetch_node as l_node then
Result.force (l_node)
end
sql_forth
end
sql_finalize
end
feature {NONE} -- Queries
sql_select_available_parents_for_node : STRING = "[
SELECT node.nid, node.revision, node.type, title, summary, content, format, author, publish, created, changed, status
FROM nodes node LEFT JOIN page_nodes pn ON node.nid = pn.nid AND node.nid != :nid
WHERE node.nid != :nid AND pn.parent != :nid AND node.status != -1 GROUP BY node.nid, node.revision;
]"
sql_select_children_of_node: STRING = "[
SELECT node.nid, node.revision, node.type, title, summary, content, format, author, publish, created, changed, status
FROM nodes node LEFT JOIN page_nodes pn ON node.nid = pn.nid
WHERE pn.parent = :nid AND node.status != -1 GROUP BY node.nid, node.revision;
]"
end

View File

@@ -0,0 +1,15 @@
#main code, #main e, #main eiffel {
display: block;
font-family: monospace;
white-space: pre-wrap;
border: solid 1px #ccc;
background-color: #fff;
line-height: 1.3;
padding: 10px;
margin: 2px 0 2px 0;
}
#main code.inline, #main e.inline, #main eiffel.inline {
display: inline-block;
padding: 0 2px 0 2px;
margin: 0;
}

View File

@@ -0,0 +1,18 @@
#main {
code, e, eiffel {
display: block;
font-family: monospace;
white-space: pre-wrap;
border: solid 1px #ccc;
background-color: #fff;
line-height: 1.3;
padding: 10px;
margin: 2px 0 2px 0;
&.inline {
display: inline-block;
padding: 0 2px 0 2px;
margin: 0;
}
}
}

View File

@@ -0,0 +1,26 @@
<?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="wikitext" uuid="7A757B53-6145-4171-833D-27292F65E34C" library_target="wikitext">
<target name="wikitext">
<root all_classes="true"/>
<file_rule>
<exclude>/EIFGENs$</exclude>
<exclude>/CVS$</exclude>
<exclude>/.svn$</exclude>
</file_rule>
<option warning="true" void_safety="all">
</option>
<setting name="concurrency" value="scoop"/>
<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="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="http_authorization" location="$ISE_LIBRARY\contrib\library\network\authentication\http_authorization\http_authorization-safe.ecf" readonly="false"/>
<library name="text_filter" location="$ISE_LIBRARY\unstable\library\text\text_filter\text_filter-safe.ecf"/>
<library name="wikitext" location="$ISE_LIBRARY\contrib\library\text\parser\wikitext\wikitext-safe.ecf"/>
<library name="wsf" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\wsf-safe.ecf"/>
<library name="wsf_extension" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\wsf_extension-safe.ecf" readonly="false"/>
<library name="wsf_html" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf_html\wsf_html-safe.ecf" readonly="false"/>
<cluster name="src" location=".\" recursive="true"/>
</target>
</system>

View File

@@ -0,0 +1,65 @@
note
description: "Summary description for {WIKITEXT_FILTER}."
date: "$Date$"
revision: "$Revision$"
class
WIKITEXT_FILTER
inherit
CONTENT_FILTER
redefine
help
end
STRING_HANDLER
feature -- Access
name: STRING_8 = "wikitext_renderer"
title: STRING_8 = "Wikitext renderer"
help: STRING = "Wikitext rendered as HTML"
description: STRING_8 = "Render Wikitext as HTML."
feature -- Conversion
filter (a_text: STRING_GENERAL)
local
wk: WIKI_CONTENT_TEXT
utf: UTF_CONVERTER
l_wikitext: STRING_8
vis: WIKI_XHTML_GENERATOR
html: STRING
do
if attached {STRING_8} a_text as s8 then
l_wikitext := s8
elseif attached {STRING_32} a_text as s32 then
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)
end
else
l_wikitext := utf.utf_32_string_to_utf_8_string_8 (a_text)
end
create wk.make_from_string (l_wikitext)
if attached wk.structure as st then
create html.make (l_wikitext.count)
create vis.make (html)
vis.code_aliases.extend ("eiffel")
vis.code_aliases.extend ("e")
st.process (vis)
a_text.set_count (0)
-- if attached {STRING_8} a_text as s8 then
-- s8.wipe_out
-- elseif attached {STRING_32} a_text as s32 then
-- s32.wipe_out
-- end
a_text.append (html)
end
end
end

View File

@@ -0,0 +1,32 @@
note
description: "Summary description for {WIKITEXT_FORMAT}."
date: "$Date$"
revision: "$Revision$"
class
WIKITEXT_FORMAT
inherit
CONTENT_FORMAT
redefine
default_create
end
feature {NONE} -- Initialization
default_create
do
Precursor
create filters.make (1)
filters.force (create {WIKITEXT_FILTER})
end
feature -- Access
name: STRING = "wikitext"
title: STRING_8 = "Wikitext"
filters: ARRAYED_LIST [CONTENT_FILTER]
end

View File

@@ -0,0 +1,80 @@
note
description: "CMS module that bring support for path aliases."
date: "$Date: 2015-02-13 13:08:13 +0100 (ven., 13 févr. 2015) $"
revision: "$Revision: 96616 $"
class
WIKITEXT_MODULE
inherit
CMS_MODULE
rename
module_api as wikitext_api
redefine
initialize,
setup_hooks
end
CMS_HOOK_RESPONSE_ALTER
create
make
feature {NONE} -- Initialization
make (a_setup: CMS_SETUP)
-- Create Current module, disabled by default.
do
version := "1.0"
description := "Wikitext module"
package := "filter"
config := a_setup
end
config: CMS_SETUP
-- Node configuration.
feature -- Access
name: STRING = "wikitext"
feature {CMS_API} -- Module Initialization
initialize (api: CMS_API)
-- <Precursor>
local
f: CMS_FORMAT
do
Precursor (api)
create f.make_from_format (create {WIKITEXT_FORMAT})
api.formats.extend (f)
-- FIXME!!!
across
api.content_types as ic
loop
ic.item.extend_format (f)
end
end
feature -- Router
setup_router (a_router: WSF_ROUTER; a_api: CMS_API)
-- Setup url dispatching for Current module.
do
end
feature -- Hooks
setup_hooks (a_hooks: CMS_HOOK_CORE_MANAGER)
do
a_hooks.subscribe_to_response_alter_hook (Current)
end
response_alter (a_response: CMS_RESPONSE)
do
a_response.add_style (a_response.url ("/module/" + name + "/files/css/wikitext.css", Void), Void)
end
end

View File

@@ -234,7 +234,7 @@ feature -- Hook: export
subscribe_to_hook (h, {CMS_HOOK_EXPORT})
end
invoke_export_to (a_export_id_list: detachable ITERABLE [READABLE_STRING_GENERAL]; a_export_parameters: CMS_EXPORT_PARAMETERS; a_response: CMS_RESPONSE)
invoke_export_to (a_export_id_list: detachable ITERABLE [READABLE_STRING_GENERAL]; a_export_parameters: CMS_EXPORT_CONTEXT; a_response: CMS_RESPONSE)
-- Invoke response alter hook for response `a_response'.
local
d: DIRECTORY
@@ -254,7 +254,36 @@ feature -- Hook: export
end
end
feature -- Hook: import
subscribe_to_import_hook (h: CMS_HOOK_IMPORT)
-- Add `h' as subscriber of import hooks CMS_HOOK_IMPORT.
do
subscribe_to_hook (h, {CMS_HOOK_IMPORT})
end
invoke_import_from (a_import_id_list: detachable ITERABLE [READABLE_STRING_GENERAL]; a_import_ctx: CMS_IMPORT_CONTEXT; a_response: CMS_RESPONSE)
-- Invoke response alter hook for response `a_response'.
local
d: DIRECTORY
do
if attached subscribers ({CMS_HOOK_IMPORT}) as lst then
create d.make_with_path (a_import_ctx.location)
if not d.exists then
d.recursive_create_dir
end
across
lst as ic
loop
if attached {CMS_HOOK_IMPORT} ic.item as h then
h.import_from (a_import_id_list, a_import_ctx, a_response)
end
end
end
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

@@ -6,7 +6,7 @@ note
revision: "$Revision$"
class
CMS_EXPORT_PARAMETERS
CMS_EXPORT_CONTEXT
create
make

View File

@@ -13,19 +13,19 @@ inherit
feature -- Hook
export_to (a_export_id_list: detachable ITERABLE [READABLE_STRING_GENERAL]; a_export_parameters: CMS_EXPORT_PARAMETERS; a_response: CMS_RESPONSE)
export_to (a_export_id_list: detachable ITERABLE [READABLE_STRING_GENERAL]; a_export_ctx: CMS_EXPORT_CONTEXT; a_response: CMS_RESPONSE)
-- Export data identified by `a_export_id_list',
-- or export all data if `a_export_id_list' is Void.
deferred
end
-- export_identifiers: detachable ITERABLE [READABLE_STRING_32]
-- export_identifiers: detachable ITERABLE [READABLE_STRING_GENERAL]
-- -- Optional list of exportation ids, if any.
-- do
-- -- To redefine if needed.
-- end
note
copyright: "2011-2015, 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

@@ -0,0 +1,23 @@
note
description: "Summary description for {CMS_HOOK_IMPORT}."
date: "$Date$"
revision: "$Revision$"
deferred class
CMS_HOOK_IMPORT
inherit
CMS_HOOK
feature -- Hook
import_from (a_impot_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
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,43 @@
note
description: "[
Parameters used by CMS_HOOK_IMPORT subscribers.
]"
date: "$Date$"
revision: "$Revision$"
class
CMS_IMPORT_CONTEXT
create
make
feature {NONE} -- Initialization
make (a_location: PATH)
do
location := a_location
create logs.make (10)
end
feature -- Access
location: PATH
-- Location of import folder.
feature -- Logs
logs: ARRAYED_LIST [READABLE_STRING_8]
-- Associated importation logs.
log (m: READABLE_STRING_8)
-- Add message `m' into `logs'.
do
logs.force (m)
end
invariant
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,57 @@
note
description: "[
Usefull routines to import to JSON.
]"
date: "$Date$"
revision: "$Revision$"
class
CMS_IMPORT_JSON_UTILITIES
feature -- Access
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
Result := s.unescaped_string_32
end
end
json_string_8_item (j: JSON_OBJECT; a_name: READABLE_STRING_GENERAL): detachable STRING_8
do
if attached {JSON_STRING} j.item (a_name) as s then
Result := s.unescaped_string_8
end
end
json_integer_item (j: JSON_OBJECT; a_name: READABLE_STRING_GENERAL): INTEGER_64
local
s: READABLE_STRING_GENERAL
do
if attached {JSON_NUMBER} j.item (a_name) as i then
Result := i.integer_64_item
elseif attached {JSON_STRING} j.item (a_name) as js then
s := js.unescaped_string_32
if s.is_integer_64 then
Result := s.to_integer_64
end
end
end
json_date_item (j: JSON_OBJECT; a_name: READABLE_STRING_GENERAL): detachable DATE_TIME
local
hd: HTTP_DATE
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
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

@@ -38,7 +38,7 @@ feature -- Access
password: Result /= Void implies (Result.hashed_password /= Void and Result.password = Void)
end
user_by_name (a_name: like {CMS_USER}.name): detachable CMS_USER
user_by_name (a_name: READABLE_STRING_GENERAL): detachable CMS_USER
-- User with name `a_name', if any.
require
a_name /= Void and then not a_name.is_empty
@@ -284,6 +284,6 @@ feature -- New Temp User
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

@@ -77,7 +77,7 @@ feature -- Access: user
sql_finalize
end
user_by_name (a_name: like {CMS_USER}.name): detachable CMS_USER
user_by_name (a_name: READABLE_STRING_GENERAL): detachable CMS_USER
-- User for the given name `a_name', if any.
local
l_parameters: STRING_TABLE [detachable ANY]
@@ -1347,6 +1347,6 @@ feature {NONE} -- SQL select
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

@@ -804,7 +804,7 @@ feature -- Environment/ modules and theme
feature -- Hook
export_to (a_export_id_list: detachable ITERABLE [READABLE_STRING_GENERAL]; a_export_parameters: CMS_EXPORT_PARAMETERS; a_response: CMS_RESPONSE)
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
@@ -816,14 +816,14 @@ feature -- Hook
do
if attached a_response.has_permissions (<<"admin export", "export core">>) then
if a_export_id_list = Void then -- Include everything
p := a_export_parameters.location.extended ("core")
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_parameters.log ("Exporting path_aliases")
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)
@@ -835,7 +835,7 @@ feature -- Hook
-- custom_values export.
if attached storage.custom_values as lst then
a_export_parameters.log ("Exporting custom_values")
a_export_ctx.log ("Exporting custom_values")
create ja.make_empty
across
lst as ic
@@ -857,7 +857,7 @@ feature -- Hook
end
-- users export.
a_export_parameters.log ("Exporting users")
a_export_ctx.log ("Exporting users")
create jo.make_empty
create jobj.make_empty
@@ -1016,7 +1016,7 @@ feature {NONE} -- Implementation: current user
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

@@ -264,19 +264,9 @@ feature -- Filters
setup_filter
-- Setup `filter'.
local
f: WSF_FILTER
do
api.logger.put_debug (generator + ".setup_filter", Void)
from
f := filter
until
not attached f.next as l_next
loop
f := l_next
end
f.set_next (Current)
append_filter (Current)
end
feature -- Execution
@@ -331,7 +321,7 @@ feature -- 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)"
source: "[
Eiffel Software

View File

@@ -22,7 +22,7 @@ feature -- Access: user
Result := storage.user_by_id (a_id)
end
user_by_name (a_username: READABLE_STRING_32): detachable CMS_USER
user_by_name (a_username: READABLE_STRING_GENERAL): detachable CMS_USER
-- User by name `a_user_name', if any.
do
Result := storage.user_by_name (a_username)
@@ -407,6 +407,6 @@ feature -- Change Temp User
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