diff --git a/.gitignore b/.gitignore index 3d99381..932c7cf 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,6 @@ EIFGENs *.swp *.log* *.rc -*.bak \ No newline at end of file +*.bak +*.sqlite +Thumbs.db diff --git a/examples/demo/modules/blog/cms_blog.e b/examples/demo/modules/blog/cms_blog.e index f697a1f..2d2a6d3 100644 --- a/examples/demo/modules/blog/cms_blog.e +++ b/examples/demo/modules/blog/cms_blog.e @@ -31,7 +31,13 @@ feature -- Conversion do Precursor (a_node) if attached {CMS_BLOG} a_node as l_blog then --- l_blog + if attached l_blog.tags as l_tags then + across + l_tags as ic + loop + add_tag (ic.item) + end + end end end @@ -42,7 +48,7 @@ feature -- Access Result := {CMS_BLOG_NODE_TYPE}.name end -feature -- Access: content +feature -- Access: node summary: detachable READABLE_STRING_8 -- A short summary of the node. @@ -54,10 +60,12 @@ feature -- Access: content -- 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 +feature -- Element change: node set_content (a_content: like content; a_summary: like summary; a_format: like format) do @@ -66,6 +74,8 @@ feature -- Element change format := a_format end +feature -- Element change: blog + add_tag (a_tag: READABLE_STRING_32) -- Set `parent' to `a_page' require diff --git a/examples/demo/modules/blog/cms_blog_api.e b/examples/demo/modules/blog/cms_blog_api.e new file mode 100644 index 0000000..c9128a9 --- /dev/null +++ b/examples/demo/modules/blog/cms_blog_api.e @@ -0,0 +1,118 @@ +note + description: "API to handle nodes of type blog. Extends the node API." + author: "Dario Bösch + do + Precursor + + -- Create the node storage for type blog + if attached {CMS_STORAGE_SQL_I} storage as l_storage_sql then + create {CMS_BLOG_STORAGE_SQL} blog_storage.make (l_storage_sql) + else + create {CMS_BLOG_STORAGE_NULL} blog_storage.make + end +-- initialize_node_types + end + +feature {CMS_API_ACCESS, CMS_MODULE, CMS_API} -- Restricted access + + node_api: CMS_NODE_API + +feature {CMS_MODULE} -- Access nodes storage. + + blog_storage: CMS_BLOG_STORAGE_I + +feature -- Configuration of blog handlers + + entries_per_page : NATURAL_32 = 2 + -- 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 + + blogs_count: INTEGER_64 + -- Number of nodes of type blog. + do + Result := blog_storage.blogs_count + end + + blogs_count_from_user (a_user: CMS_USER): INTEGER_64 + -- Number of nodes of type blog from user with `a_user_id'. + require + has_id: a_user.has_id + do + Result := blog_storage.blogs_count_from_user (a_user) + end + + blogs_order_created_desc: LIST [CMS_BLOG] + -- List of nodes ordered by creation date (descending) + do + Result := nodes_to_blogs (blog_storage.blogs) + end + + blogs_order_created_desc_limited (a_limit: NATURAL_32; a_offset: NATURAL_32): LIST [CMS_BLOG] + -- List of nodes ordered by creation date and limited by limit and offset + do + -- load all posts and add the authors to each post + 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] + -- List of nodes ordered by creation date and limited by limit and offset + 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_limited (a_user, a_limit, a_offset)) + end + +feature {NONE} -- Helpers + + nodes_to_blogs (a_nodes: LIST [CMS_NODE]): ARRAYED_LIST [CMS_BLOG] + -- Convert list of nodes into a list of blog when possible. + do + create {ARRAYED_LIST [CMS_BLOG]} Result.make (a_nodes.count) + + if attached node_api as l_node_api then + across + a_nodes as ic + loop + if attached {CMS_BLOG} l_node_api.full_node (ic.item) as l_blog then + Result.force (l_blog) + end + end + end + end + +end diff --git a/examples/demo/modules/blog/cms_blog_module-safe.ecf b/examples/demo/modules/blog/cms_blog_module-safe.ecf index bf6db6e..373775e 100644 --- a/examples/demo/modules/blog/cms_blog_module-safe.ecf +++ b/examples/demo/modules/blog/cms_blog_module-safe.ecf @@ -20,6 +20,7 @@ + diff --git a/examples/demo/modules/blog/cms_blog_module.e b/examples/demo/modules/blog/cms_blog_module.e index 37148db..27b9b3b 100644 --- a/examples/demo/modules/blog/cms_blog_module.e +++ b/examples/demo/modules/blog/cms_blog_module.e @@ -1,17 +1,21 @@ note - description: "Summary description for {CMS_BLOG_MODULE}." - date: "$Date: 2015-02-13 13:08:13 +0100 (ven., 13 févr. 2015) $" - revision: "$Revision: 96616 $" + description: "Displays all posts (pages with type blog). It's possible to list posts by user." + author: "Dario Bösch " + date: "$Date: 2015-05-22 15:13:00 +0100 (lun., 18 mai 2015) $" + revision: "$Revision 96616$" class CMS_BLOG_MODULE inherit CMS_MODULE + rename + module_api as blog_api redefine register_hooks, initialize, - install + install, + blog_api end CMS_HOOK_MENU_SYSTEM_ALTER @@ -39,6 +43,10 @@ feature {CMS_API} -- Module Initialization Precursor (api) if attached {CMS_NODE_API} api.module_api ({NODE_MODULE}) as l_node_api then + create blog_api.make (api, l_node_api) + + node_api := l_node_api + -- Depends on {NODE_MODULE} create ct l_node_api.add_content_type (ct) l_node_api.add_content_type_webform_manager (create {CMS_BLOG_NODE_TYPE_WEBFORM_MANAGER}.make (ct)) @@ -75,12 +83,53 @@ CREATE TABLE "blog_post_nodes"( end end +feature {CMS_API} -- Access: API + + blog_api: detachable CMS_BLOG_API + -- + + node_api: detachable CMS_NODE_API + feature -- Access: router setup_router (a_router: WSF_ROUTER; a_api: CMS_API) -- do - a_router.handle_with_request_methods ("/blogs/", create {WSF_URI_AGENT_HANDLER}.make (agent handle_blogs (?,?, a_api)), a_router.methods_get) + if attached blog_api as l_blog_api then + configure_web (a_api, l_blog_api, a_router) + else + -- Issue with api/dependencies, + -- thus Current module should not be used! + -- thus no url mapping + end + end + + configure_web (a_api: CMS_API; a_blog_api: CMS_BLOG_API; a_router: WSF_ROUTER) + -- Configure router mapping for web interface. + local + l_blog_handler: BLOG_HANDLER + l_blog_user_handler: BLOG_USER_HANDLER + l_uri_mapping: WSF_URI_MAPPING + do + -- TODO: for now, focused only on web interface, add REST api later. [2015-May-18] + create l_blog_handler.make (a_api, a_blog_api) + create l_blog_user_handler.make (a_api, a_blog_api) + + -- Let the class BLOG_HANDLER handle the requests on "/blogs" + create l_uri_mapping.make_trailing_slash_ignored ("/blogs", l_blog_handler) + a_router.map_with_request_methods (l_uri_mapping, a_router.methods_get) + + -- We can add a page number after /blogs/ to get older posts + a_router.handle_with_request_methods ("/blogs/page/{page}", l_blog_handler, a_router.methods_get) + + -- If a user id is given route with blog user handler + --| FIXME: maybe /user/{user}/blogs/ would be better. + a_router.handle_with_request_methods ("/blogs/user/{user}", l_blog_user_handler, a_router.methods_get) + + -- If a user id is given we also want to allow different pages + --| FIXME: what about /user/{user}/blogs/?page={page} ? + a_router.handle_with_request_methods ("/blogs/user/{user}/page/{page}", l_blog_user_handler, a_router.methods_get) + end feature -- Hooks @@ -94,19 +143,8 @@ feature -- Hooks local lnk: CMS_LOCAL_LINK do + -- Add the link to the blog to the main menu create lnk.make ("Blogs", "blogs/") a_menu_system.primary_menu.extend (lnk) end - -feature -- Handler - - handle_blogs (req: WSF_REQUEST; res: WSF_RESPONSE; a_api: CMS_API) - local - r: NOT_IMPLEMENTED_ERROR_CMS_RESPONSE - do - create r.make (req, res, a_api) - r.set_main_content ("Blog module is in development ...") - r.execute - end - end diff --git a/examples/demo/modules/blog/cms_blog_node_type.e b/examples/demo/modules/blog/cms_blog_node_type.e index a393c41..92fd7f5 100644 --- a/examples/demo/modules/blog/cms_blog_node_type.e +++ b/examples/demo/modules/blog/cms_blog_node_type.e @@ -17,10 +17,11 @@ feature {NONE} -- Initialization default_create do Precursor - create {ARRAYED_LIST [like available_formats.item]} available_formats.make (3) + create {ARRAYED_LIST [like available_formats.item]} available_formats.make (4) available_formats.extend (create {PLAIN_TEXT_CONTENT_FORMAT}) available_formats.extend (create {FILTERED_HTML_CONTENT_FORMAT}) available_formats.extend (create {FULL_HTML_CONTENT_FORMAT}) + available_formats.extend (create {CMS_EDITOR_CONTENT_FORMAT}) end feature -- Access diff --git a/examples/demo/modules/blog/handler/blog_handler.e b/examples/demo/modules/blog/handler/blog_handler.e new file mode 100644 index 0000000..de30e17 --- /dev/null +++ b/examples/demo/modules/blog/handler/blog_handler.e @@ -0,0 +1,279 @@ +note + description: "Request handler related to /blogs and /blogs/{page}. Displays all posts in the blog." + author: "Dario Bösch " + date: "$Date: 2015-05-18 13:49:00 +0100 (lun., 18 mai 2015) $" + revision: "$9661667$" + +class + BLOG_HANDLER + +inherit + CMS_BLOG_HANDLER + + WSF_URI_HANDLER + rename + execute as uri_execute, + new_mapping as new_uri_mapping + end + + WSF_URI_TEMPLATE_HANDLER + rename + execute as uri_template_execute, + new_mapping as new_uri_template_mapping + select + new_uri_template_mapping + end + + WSF_RESOURCE_HANDLER_HELPER + redefine + do_get + end + + REFACTORING_HELPER + + CMS_API_ACCESS + +create + make + +feature -- execute + + execute (req: WSF_REQUEST; res: WSF_RESPONSE) + -- Execute request handler for any kind of mapping. + do + execute_methods (req, res) + end + + uri_execute (req: WSF_REQUEST; res: WSF_RESPONSE) + -- Execute request handler for URI mapping. + do + execute (req, res) + end + + uri_template_execute (req: WSF_REQUEST; res: WSF_RESPONSE) + -- Execute request handler for URI-template mapping. + do + execute (req, res) + end + +feature -- Global Variables + + page_number: NATURAL_32 + -- Current page number. + +feature -- HTTP Methods + + do_get (req: WSF_REQUEST; res: WSF_RESPONSE) + -- + local + l_page: CMS_RESPONSE + do + -- Read page number from path parameter. + page_number := page_number_path_parameter (req) + + -- Responding with `main_content_html (l_page)'. + create {GENERIC_VIEW_CMS_RESPONSE} l_page.make (req, res, api) + l_page.set_main_content (main_content_html (l_page)) + l_page.execute + end + +feature -- Query + + posts: LIST [CMS_NODE] + -- Blog posts to display on given page ordered by date (descending). + do + Result := blog_api.blogs_order_created_desc_limited ( + entries_per_page, + entries_per_page * (page_number - 1) + ) + end + + multiple_pages_needed : BOOLEAN + -- Return if more that one page is needed to display posts. + do + Result := entries_per_page < total_entries + end + + pages_count: NATURAL_32 + -- Number of pages needed to display all posts. + require + entries_per_page > 0 + local + tmp: REAL_32 + do + tmp := total_entries.to_real_32 / entries_per_page.to_real_32; + Result := tmp.ceiling.to_natural_32 + end + + page_number_path_parameter (req: WSF_REQUEST): NATURAL_32 + -- Page number from path /blogs/{page}. + -- Unsigned integer since negative pages are not allowed. + local + s: STRING + do + Result := 1 -- default if not get variable is set + if attached {WSF_STRING} req.path_parameter ("page") as p_page then + s := p_page.value + if s.is_natural_32 then + if s.to_natural_32 > 1 then + Result := s.to_natural_32 + end + end + end + end + + total_entries: NATURAL_32 + -- Total number of entries/posts. + do + Result := blog_api.blogs_count.to_natural_32 + end + +feature -- HTML Output + + frozen main_content_html (page: CMS_RESPONSE): STRING + -- Content of the page as a html string. + do + create Result.make_empty + append_main_content_html_to (page, Result) + end + + append_main_content_html_to (page: CMS_RESPONSE; a_output: STRING) + -- Append to `a_output, the content of the page as a html string. + local + n: CMS_NODE + lnk: CMS_LOCAL_LINK + do + -- Output the title. If more than one page, also output the current page number + append_page_title_html_to (a_output) + + -- Get the posts from the current page (given by page number and entries per page) + -- Start list of posts + a_output.append ("
    %N") + across + posts as ic + loop + n := ic.item + lnk := blog_api.node_api.node_link (n) + a_output.append ("
  • ") + + -- Output the creation date + append_creation_date_html_to (n, a_output) + + -- Output the author of the post + append_author_html_to (n, a_output) + + -- Output the title of the post as a link (to the detail page) + append_title_html_to (n, page, a_output) + + -- Output the summary of the post and a more link to the detail page + append_summary_html_to (n, page, a_output) + + a_output.append ("
  • %N") + end + + -- End of post list + a_output.append ("
%N") + + -- Pagination (older and newer links) + append_pagination_html_to (a_output) + end + + append_page_title_html_to (a_output: STRING) + -- Append the title of the page as a html string to `a_output'. + -- It shows the current page number. + do + a_output.append ("

Blog") + if multiple_pages_needed then + a_output.append (" (Page " + page_number.out + " of " + pages_count.out + ")") + end + a_output.append ("

") + end + + append_creation_date_html_to (n: CMS_NODE; a_output: STRING) + -- Append the creation date as a html string to `a_output'. + local + hdate: HTTP_DATE + do + if attached n.creation_date as l_modified then + create hdate.make_from_date_time (l_modified) + hdate.append_to_yyyy_mmm_dd_string (a_output) + a_output.append (" ") + end + end + + append_author_html_to (n: CMS_NODE; a_output: STRING) + -- Append to `a_output', the author of node `n' as html link to author's posts. + do + if attached n.author as l_author then + a_output.append ("by ") + a_output.append ("" + l_author.name + "") + end + end + + append_title_html_to (n: CMS_NODE; page: CMS_RESPONSE; a_output: STRING) + -- Append to `a_output', the title of node `n' as html link to detail page. + local + lnk: CMS_LOCAL_LINK + do + lnk := blog_api.node_api.node_link (n) + a_output.append ("") + a_output.append (page.link (lnk.title, lnk.location, Void)) + a_output.append ("") + end + + append_summary_html_to (n: CMS_NODE; page: CMS_RESPONSE; a_output: STRING) + -- returns a html string with the summary of the node and a link to the detail page + local + lnk: CMS_LOCAL_LINK + do + if attached n.summary as l_summary then + lnk := blog_api.node_api.node_link (n) + a_output.append ("

") + if attached api.format (n.format) as f then + a_output.append (f.formatted_output (l_summary)) + else + a_output.append (page.formats.default_format.formatted_output (l_summary)) + end + a_output.append ("
") + a_output.append (page.link ("See more...", lnk.location, Void)) + a_output.append ("

") + end + end + + append_pagination_html_to (a_output: STRING) + -- Append to `a_output' with the pagination links (if necessary). + local + tmp: NATURAL_32 + do + if multiple_pages_needed then + a_output.append ("
") + + -- If exist older posts show link to next page + if page_number < pages_count then + tmp := page_number + 1 + a_output.append (" << Older Posts ") + end + + -- Delimiter + if page_number < pages_count AND page_number > 1 then + a_output.append (" | ") + end + + -- If exist newer posts show link to previous page + if page_number > 1 then + tmp := page_number -1 + a_output.append (" Newer Posts >> ") + end + + a_output.append ("
") + end + + end + + base_path : STRING + -- the path to the page that lists all blogs + do + Result := "/blogs" + end + +end diff --git a/examples/demo/modules/blog/handler/blog_user_handler.e b/examples/demo/modules/blog/handler/blog_user_handler.e new file mode 100644 index 0000000..961fe35 --- /dev/null +++ b/examples/demo/modules/blog/handler/blog_user_handler.e @@ -0,0 +1,150 @@ +note + description: "[ + Request handler related to + /blogs/user/{id}/ + or /blogs/user/{id}/page/{page}. + + Displays all posts of the given user + ]" + author: "Dario Bösch " + date: "$Date: 2015-05-22 15:13:00 +0100 (lun., 18 mai 2015) $" + revision: "$Revision 96616$" + +class + BLOG_USER_HANDLER + +inherit + BLOG_HANDLER + redefine + do_get, + posts, + total_entries, + append_page_title_html_to, + base_path + end + +create + make + +feature -- Global Variables + + user : detachable CMS_USER + +feature -- HTTP Methods + + do_get (req: WSF_REQUEST; res: WSF_RESPONSE) + -- + local + l_error: NOT_FOUND_ERROR_CMS_RESPONSE + do + user := Void + if attached user_from_request (req) as l_user then + user := l_user + -- Output the results, similar as in the blog hanlder (but with other queries) + Precursor (req, res) + else + -- Throw a bad request error because the user is not valid + create l_error.make (req, res, api) + l_error.set_main_content ("

Error

User with id " + user_id_path_parameter (req).out + " doesn't exist!") + l_error.execute + end + end + +feature -- Query + + user_valid (req: WSF_REQUEST) : BOOLEAN + -- Returns true if a valid user id is given and a user with this id exists, + -- otherwise returns false. + local + user_id: INTEGER_32 + do + user_id := user_id_path_parameter (req) + + if user_id <= 0 then + -- Given user id is not valid + Result := False + else + --Check if user with user_id exists + Result := api.user_api.user_by_id (user_id) /= Void + end + end + + user_from_request (req: WSF_REQUEST): detachable CMS_USER + -- Eventual user with given id in the path of request `req'. + local + uid: like user_id_path_parameter + do + uid := user_id_path_parameter (req) + if uid > 0 then + Result := api.user_api.user_by_id (uid) + else + -- Missing or invalid user id. + end + end + + user_id_path_parameter (req: WSF_REQUEST): INTEGER_32 + -- User id from path /blogs/{user}. + -- Unsigned integer since negative ids are not allowed. + -- If no valid id can be read it returns -1 + local + s: STRING + do + Result := -1 + if attached {WSF_STRING} req.path_parameter ("user") as l_user_id then + if l_user_id.is_integer then + Result := l_user_id.integer_value + end + end + end + + posts: LIST [CMS_BLOG] + -- Blog posts to display on given page. + -- Filters out the posts of the current user. + do + if attached user as l_user then + Result := blog_api.blogs_from_user_order_created_desc_limited (l_user, entries_per_page, entries_per_page * (page_number - 1)) + else + create {ARRAYED_LIST [CMS_BLOG]} Result.make (0) + end + end + + total_entries : NATURAL_32 + -- Returns the number of total entries/posts of the current user + do + if attached user as l_user then + Result := blog_api.blogs_count_from_user (l_user).to_natural_32 + else + Result := Precursor + end + end + +feature -- HTML Output + + append_page_title_html_to (a_output: STRING) + -- Returns the title of the page as a html string. It shows the current page number and the name of the current user + do + a_output.append ("

Posts from ") + if attached user as l_user then + a_output.append (l_user.name) + else + a_output.append ("unknown user") + end + if multiple_pages_needed then + a_output.append (" (Page " + page_number.out + " of " + pages_count.out + ")") + -- Get the posts from the current page (limited by entries per page) + end + a_output.append ("

") + end + + base_path : STRING + -- Path to page listing all blogs. + -- If user is logged in, include user id + do + if attached user as l_user then + Result := "/blogs/user/" + l_user.id.out + else + Result := precursor + end + end + +end diff --git a/examples/demo/modules/blog/handler/cms_blog_handler.e b/examples/demo/modules/blog/handler/cms_blog_handler.e new file mode 100644 index 0000000..450096f --- /dev/null +++ b/examples/demo/modules/blog/handler/cms_blog_handler.e @@ -0,0 +1,23 @@ +note + description: "Deferred request handler related to /blogs/... Has an own blog api." + author: "Dario Bösch " + date: "$Date: 2015-05-18 13:49:00 +0100 (lun., 18 mai 2015) $" + revision: "$9661667$" + +deferred class + CMS_BLOG_HANDLER + +inherit + CMS_MODULE_HANDLER [CMS_BLOG_API] + rename + module_api as blog_api + end + +feature -- Access + + entries_per_page: NATURAL_32 + do + Result := blog_api.entries_per_page + end + +end diff --git a/examples/demo/modules/blog/persistence/cms_blog_storage_i.e b/examples/demo/modules/blog/persistence/cms_blog_storage_i.e new file mode 100644 index 0000000..08c2447 --- /dev/null +++ b/examples/demo/modules/blog/persistence/cms_blog_storage_i.e @@ -0,0 +1,43 @@ +note + description: "Interface for accessing blog contents from the database." + date: "$Date: 2015-01-27 19:15:02 +0100 (mar., 27 janv. 2015) $" + revision: "$Revision: 96542 $" + +deferred class + CMS_BLOG_STORAGE_I + +inherit + CMS_NODE_STORAGE_I + +feature -- Access + + blogs_count: INTEGER_64 + -- Count of blog nodes + deferred + end + + blogs_count_from_user (a_user: CMS_USER) : INTEGER_64 + -- Number of nodes of type blog from `a_user'. + require + has_id: a_user.has_id + deferred + end + + blogs: LIST [CMS_NODE] + -- List of nodes ordered by creation date (descending). + deferred + end + + blogs_limited (limit: NATURAL_32; offset: NATURAL_32): LIST [CMS_NODE] + -- List of posts ordered by creation date from offset to offset + limit. + deferred + end + + blogs_from_user_limited (a_user: CMS_USER; limit: NATURAL_32; offset: NATURAL_32): LIST [CMS_NODE] + -- List of posts from `a_user' ordered by creation date from offset to offset + limit. + require + has_id: a_user.has_id + deferred + end + +end diff --git a/examples/demo/modules/blog/persistence/cms_blog_storage_null.e b/examples/demo/modules/blog/persistence/cms_blog_storage_null.e new file mode 100644 index 0000000..40a860f --- /dev/null +++ b/examples/demo/modules/blog/persistence/cms_blog_storage_null.e @@ -0,0 +1,47 @@ +note + description: "Summary description for {CMS_BLOG_STORAGE_NULL}." + author: "" + date: "$Date$" + revision: "$Revision$" + +class + CMS_BLOG_STORAGE_NULL + +inherit + CMS_NODE_STORAGE_NULL + + CMS_BLOG_STORAGE_I + +create + make + +feature -- Access + + blogs_count: INTEGER_64 + -- Count of nodes. + do + end + + blogs_count_from_user (a_user: CMS_USER) : INTEGER_64 + -- + do + end + + blogs: LIST [CMS_NODE] + -- + do + create {ARRAYED_LIST [CMS_NODE]} Result.make (0) + end + + blogs_limited (limit: NATURAL_32; offset: NATURAL_32) : LIST [CMS_NODE] + -- + do + create {ARRAYED_LIST [CMS_NODE]} Result.make (0) + end + + blogs_from_user_limited (a_user: CMS_USER; limit: NATURAL_32; offset: NATURAL_32): LIST [CMS_NODE] + -- + do + create {ARRAYED_LIST [CMS_NODE]} Result.make (0) + end +end diff --git a/examples/demo/modules/blog/persistence/cms_blog_storage_sql.e b/examples/demo/modules/blog/persistence/cms_blog_storage_sql.e new file mode 100644 index 0000000..ad07f62 --- /dev/null +++ b/examples/demo/modules/blog/persistence/cms_blog_storage_sql.e @@ -0,0 +1,140 @@ +note + description: "Access to the sql database for the blog module" + author: "Dario Bösch " + date: "$Date: 2015-05-21 14:46:00 +0100$" + revision: "$Revision: 96616 $" + +class + CMS_BLOG_STORAGE_SQL + +inherit + CMS_NODE_STORAGE_SQL + + CMS_BLOG_STORAGE_I + +create + make + +feature -- Access + + blogs_count: INTEGER_64 + -- + do + error_handler.reset + write_information_log (generator + ".blogs_count") + sql_query (sql_select_blog_count, Void) + if sql_rows_count = 1 then + Result := sql_read_integer_64 (1) + end + end + + blogs_count_from_user (a_user: CMS_USER) : INTEGER_64 + -- + local + l_parameters: STRING_TABLE [detachable ANY] + do + error_handler.reset + write_information_log (generator + ".blogs_count_from_user") + create l_parameters.make (2) + l_parameters.put (a_user.id, "user") + sql_query (sql_select_blog_count_from_user, l_parameters) + if sql_rows_count = 1 then + Result := sql_read_integer_64 (1) + end + end + + blogs: LIST [CMS_NODE] + -- + do + create {ARRAYED_LIST [CMS_NODE]} Result.make (0) + + error_handler.reset + write_information_log (generator + ".blogs") + + from + sql_query (sql_select_blogs_order_created_desc, Void) + sql_start + until + sql_after + loop + if attached fetch_node as l_node then + Result.force (l_node) + end + sql_forth + end + end + + blogs_limited (a_limit: NATURAL_32; a_offset: NATURAL_32): LIST [CMS_NODE] + -- + local + l_parameters: STRING_TABLE [detachable ANY] + do + create {ARRAYED_LIST [CMS_NODE]} Result.make (0) + + error_handler.reset + write_information_log (generator + ".blogs_limited") + + from + create l_parameters.make (2) + l_parameters.put (a_limit, "limit") + l_parameters.put (a_offset, "offset") + sql_query (sql_blogs_limited, l_parameters) + sql_start + until + sql_after + loop + if attached fetch_node as l_node then + Result.force (l_node) + end + sql_forth + end + end + + blogs_from_user_limited (a_user: CMS_USER; a_limit: NATURAL_32; a_offset: NATURAL_32): LIST [CMS_NODE] + -- + 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_limited") + + from + create l_parameters.make (2) + l_parameters.put (a_limit, "limit") + l_parameters.put (a_offset, "offset") + l_parameters.put (a_user.id, "user") + sql_query (sql_blogs_from_user_limited, l_parameters) + sql_start + until + sql_after + loop + if attached fetch_node as l_node then + Result.force (l_node) + end + sql_forth + end + end + +feature {NONE} -- Queries + + sql_select_blog_count: STRING = "SELECT count(*) FROM Nodes WHERE status != -1 AND type = %"blog%";" + -- Nodes count (Published and not Published) + --| note: {CMS_NODE_API}.trashed = -1 + + sql_select_blog_count_from_user: STRING = "SELECT count(*) FROM Nodes WHERE status != -1 AND type = %"blog%" AND author = :user ;" + -- Nodes count (Published and not Published) + --| note: {CMS_NODE_API}.trashed = -1 + + sql_select_blogs_order_created_desc: STRING = "SELECT * FROM Nodes WHERE status != -1 AND type = %"blog%" ORDER BY created DESC;" + -- 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_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 + + +end diff --git a/examples/demo/site/themes/bootstrap/assets/css/blog.css b/examples/demo/site/themes/bootstrap/assets/css/blog.css new file mode 100644 index 0000000..736608d --- /dev/null +++ b/examples/demo/site/themes/bootstrap/assets/css/blog.css @@ -0,0 +1,25 @@ +ul.cms_blog_nodes { + padding: 0; + margin: 0; +} +ul.cms_blog_nodes li.cms_type_blog { + list-style: none; + display: block; + margin-top: 20px; + padding-bottom: 20px; + border-bottom: 1px dotted black; +} +ul.cms_blog_nodes li.cms_type_blog .blog_title a { + color: black; + font-size: 18px; + text-decoration: none; + display: block; + margin: 6px 0; +} +ul.cms_blog_nodes li.cms_type_blog .blog_title a:hover { + color: #999; +} +ul.cms_blog_nodes li.cms_type_blog .blog_list_summary a { + margin-top: 20px; + display: block; +} diff --git a/examples/demo/site/themes/bootstrap/assets/css/node.css b/examples/demo/site/themes/bootstrap/assets/css/node.css index 7888687..f4277ee 100644 --- a/examples/demo/site/themes/bootstrap/assets/css/node.css +++ b/examples/demo/site/themes/bootstrap/assets/css/node.css @@ -3,13 +3,15 @@ ul.cms-nodes { padding: 3px 3px 3px 3px; border: solid 1px #ccc; } - -li.cms_type_page { +ul.cms-nodes li { border-top: dotted 1px #ccc; } -li.cms_type_page a::before { - content: "[page] "; -} -li.cms_type_page:first-child { +ul.cms-nodes li:first-child { border-top: none; } +ul.cms-nodes li.cms_type_page a::before { + content: "[page] "; +} +ul.cms-nodes li.cms_type_blog a::before { + content: "[blog] "; +} diff --git a/examples/demo/site/themes/bootstrap/assets/scss/blog.scss b/examples/demo/site/themes/bootstrap/assets/scss/blog.scss new file mode 100644 index 0000000..be90622 --- /dev/null +++ b/examples/demo/site/themes/bootstrap/assets/scss/blog.scss @@ -0,0 +1,30 @@ +ul.cms_blog_nodes{ + + padding:0; + margin:0; + + li.cms_type_blog{ + list-style: none; + display: block; + margin-top:20px; + padding-bottom:20px; + border-bottom:1px dotted black; + + .blog_title a{ + color:black; + font-size:18px; + text-decoration: none; + display:block; + margin:6px 0; + + &:hover{ + color:#999; + } + } + + .blog_list_summary a{ + margin-top:20px; + display:block; + } + } +} \ No newline at end of file diff --git a/examples/demo/site/themes/bootstrap/assets/scss/node.scss b/examples/demo/site/themes/bootstrap/assets/scss/node.scss index cd7408f..5cf9324 100644 --- a/examples/demo/site/themes/bootstrap/assets/scss/node.scss +++ b/examples/demo/site/themes/bootstrap/assets/scss/node.scss @@ -1,14 +1,24 @@ ul.cms-nodes { + list-style-type: none; padding: 3px 3px 3px 3px; border: solid 1px #ccc; -} -li.cms_type_page { - a::before { + + li{ + border-top: dotted 1px #ccc; + &:first-child { + border-top: none; + } + } + + li.cms_type_page a::before { content: "[page] "; } - border-top: dotted 1px #ccc; - &:first-child { - border-top: none; + + li.cms_type_blog a::before { + content: "[blog] "; } + + } + diff --git a/examples/demo/site/themes/bootstrap/page.tpl b/examples/demo/site/themes/bootstrap/page.tpl index f56846b..9361fde 100644 --- a/examples/demo/site/themes/bootstrap/page.tpl +++ b/examples/demo/site/themes/bootstrap/page.tpl @@ -6,8 +6,12 @@ - - + + + + + + diff --git a/modules/node/content_type/cms_page_node_type.e b/modules/node/content_type/cms_page_node_type.e index 2053e0d..3fe15b3 100644 --- a/modules/node/content_type/cms_page_node_type.e +++ b/modules/node/content_type/cms_page_node_type.e @@ -17,10 +17,11 @@ feature {NONE} -- Initialization default_create do Precursor - create {ARRAYED_LIST [like available_formats.item]} available_formats.make (3) + create {ARRAYED_LIST [like available_formats.item]} available_formats.make (4) available_formats.extend (create {PLAIN_TEXT_CONTENT_FORMAT}) available_formats.extend (create {FILTERED_HTML_CONTENT_FORMAT}) available_formats.extend (create {FULL_HTML_CONTENT_FORMAT}) + available_formats.extend (create {CMS_EDITOR_CONTENT_FORMAT}) end feature -- Access diff --git a/modules/node/handler/cms_node_type_webform_manager.e b/modules/node/handler/cms_node_type_webform_manager.e index eb14f2e..5f1e4b5 100644 --- a/modules/node/handler/cms_node_type_webform_manager.e +++ b/modules/node/handler/cms_node_type_webform_manager.e @@ -17,11 +17,14 @@ feature -- Forms ... local ti: WSF_FORM_TEXT_INPUT fset: WSF_FORM_FIELD_SET - ta: WSF_FORM_TEXTAREA + ta, sum: CMS_FORM_TEXTAREA tselect: WSF_FORM_SELECT opt: WSF_FORM_SELECT_OPTION + cms_format: CMS_EDITOR_CONTENT_FORMAT l_uri: detachable READABLE_STRING_8 do + create cms_format + create ti.make ("title") ti.set_label ("Title") ti.set_size (70) @@ -33,25 +36,47 @@ feature -- Forms ... f.extend_html_text ("
") - create ta.make ("body") - ta.set_rows (10) - ta.set_cols (70) - if a_node /= Void then - ta.set_text_value (a_node.content) - end --- ta.set_label ("Body") - ta.set_description ("This is the main content") - ta.set_is_required (False) - - create fset.make - fset.set_legend ("Body") - fset.extend (ta) - - fset.extend_html_text ("
") - + -- Select field has to be initialized before textareas are replaced, because they depend on the selection of the field create tselect.make ("format") tselect.set_label ("Body's format") tselect.set_is_required (True) + + -- Main Content + create ta.make ("body") + ta.set_rows (10) + ta.set_cols (70) + ta.show_as_editor_if_selected (tselect, cms_format.name) + if a_node /= Void then + ta.set_text_value (a_node.content) + end + ta.set_label ("Content") + ta.set_description ("This is the main content") + ta.set_is_required (False) + + -- Summary + create sum.make ("summary") + sum.set_rows (3) + sum.set_cols (70) + -- if cms_html is selected + sum.show_as_editor_if_selected (tselect, cms_format.name) + if a_node /= Void then + sum.set_text_value (a_node.summary) + end + sum.set_label ("Summary") + sum.set_description ("This is the summary") + sum.set_is_required (False) + + create fset.make + fset.set_legend ("Body") + + -- Add summary + fset.extend (sum) + fset.extend_html_text("
") + + -- Add content (body) + fset.extend (ta) + fset.extend_html_text ("
") + across content_type.available_formats as c loop @@ -69,7 +94,7 @@ feature -- Forms ... f.extend (fset) - -- Path alias + -- Path alias create ti.make ("path_alias") ti.set_label ("Path") ti.set_size (70) @@ -113,7 +138,7 @@ feature -- Forms ... update_node (response: NODE_RESPONSE; fd: WSF_FORM_DATA; a_node: CMS_NODE) local - b: detachable READABLE_STRING_8 + b,s: detachable READABLE_STRING_8 f: detachable CONTENT_FORMAT do if attached fd.integer_item ("id") as l_id and then l_id > 0 then @@ -126,6 +151,12 @@ feature -- Forms ... if attached fd.string_item ("body") as l_body then b := l_body end + + -- Read out the summary field from the form data + if attached fd.string_item ("summary") as l_summary then + s := l_summary + end + if attached fd.string_item ("format") as s_format and then attached response.api.format (s_format) as f_format then f := f_format elseif a_node /= Void and then attached a_node.format as s_format and then attached response.api.format (s_format) as f_format then @@ -133,15 +164,19 @@ feature -- Forms ... else f := response.formats.default_format end + + -- Update node with summary and body content if b /= Void then - a_node.set_content (b, Void, f.name) -- FIXME: summary + a_node.set_content (b, s, f.name) end + + end new_node (response: NODE_RESPONSE; fd: WSF_FORM_DATA; a_node: detachable CMS_NODE): G -- local - b: detachable READABLE_STRING_8 + b,s: detachable READABLE_STRING_8 f: detachable CONTENT_FORMAT l_node: detachable like new_node do @@ -175,9 +210,16 @@ feature -- Forms ... end l_node.set_author (response.user) + --Summary + if attached fd.string_item ("summary") as l_summary then + s := l_summary + end + + --Content if attached fd.string_item ("body") as l_body then b := l_body end + if attached fd.string_item ("format") as s_format and then attached response.api.format (s_format) as f_format then f := f_format elseif a_node /= Void and then attached a_node.format as s_format and then attached response.api.format (s_format) as f_format then @@ -185,8 +227,10 @@ feature -- Forms ... else f := response.formats.default_format end + + -- Update node with summary and content if b /= Void then - l_node.set_content (b, Void, f.name) + l_node.set_content (b, s, f.name) end Result := l_node end @@ -243,6 +287,23 @@ feature -- Output s.append (")") end s.append ("") + + + -- We don't show the summary on the detail page, since its just a short view of the full content. Otherwise we would write the same thing twice. + -- The usage of the summary is to give a short overview in the list of nodes or for the meta tag "description" + +-- if attached a_node.summary as l_summary then +-- s.append ("

") +-- if attached node_api.cms_api.format (a_node.format) as f then +-- s.append (f.formatted_output (l_summary)) +-- else +-- s.append (a_response.formats.default_format.formatted_output (l_summary)) +-- end + +-- s.append ("

") + +-- end + if attached a_node.content as l_content then s.append ("

") if attached node_api.cms_api.format (a_node.format) as f then diff --git a/modules/node/persistence/cms_node_storage_sql.e b/modules/node/persistence/cms_node_storage_sql.e index 188c56e..d14efeb 100644 --- a/modules/node/persistence/cms_node_storage_sql.e +++ b/modules/node/persistence/cms_node_storage_sql.e @@ -33,6 +33,7 @@ feature -- Access end end + nodes: LIST [CMS_NODE] -- List of nodes. do diff --git a/src/kernel/content/format/cms_editor_content_format.e b/src/kernel/content/format/cms_editor_content_format.e new file mode 100644 index 0000000..69e256e --- /dev/null +++ b/src/kernel/content/format/cms_editor_content_format.e @@ -0,0 +1,31 @@ +note + description: "HTML Content format editable with WYSIWYG editor." + date: "$Date$" + revision: "$Revision$" + +class + CMS_EDITOR_CONTENT_FORMAT + +inherit + CONTENT_FORMAT + redefine + default_create + end + +feature {NONE} -- Initialization + + default_create + do + Precursor + create filters.make (0) + end + +feature -- Access + + name: STRING = "cms_editor" + + title: STRING_8 = "CMS HTML content" + + filters: ARRAYED_LIST [CONTENT_FILTER] + +end diff --git a/src/kernel/content/format/cms_formats.e b/src/kernel/content/format/cms_formats.e index 12132d0..199062d 100644 --- a/src/kernel/content/format/cms_formats.e +++ b/src/kernel/content/format/cms_formats.e @@ -27,10 +27,11 @@ feature -- Access once -- Can we provide an external file to read the -- supported formats? - create {ARRAYED_LIST [CONTENT_FORMAT]} Result.make (3) + create {ARRAYED_LIST [CONTENT_FORMAT]} Result.make (4) Result.force (plain_text) Result.force (full_html) Result.force (filtered_html) + Result.force (cms_html) end default_format: CONTENT_FORMAT @@ -43,6 +44,11 @@ feature -- Access create Result end + cms_html: CMS_EDITOR_CONTENT_FORMAT + once + create Result + end + full_html: FULL_HTML_CONTENT_FORMAT once create Result @@ -53,7 +59,6 @@ feature -- Access create Result end - note copyright: "2011-2014, Jocelyn Fiat, Eiffel Software and others" license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)" diff --git a/src/kernel/form/cms_editor.e b/src/kernel/form/cms_editor.e new file mode 100644 index 0000000..5913956 --- /dev/null +++ b/src/kernel/form/cms_editor.e @@ -0,0 +1,106 @@ +note + description: "Class to import a WYSIWIG editor using javascript code" + author: "Dario Bösch + do + Result := "" + end + +feature -- Javascript + + javascript_replace_textarea (a_textarea : WSF_FORM_TEXTAREA) : STRING + -- + do + -- Replaces the textarea with an editor instance. Save the instance in a variable + Result := editor_variable(a_textarea) + " = CKEDITOR.replace( '" + a_textarea.name + "' );" + end + + javascript_restore_textarea (a_textarea : WSF_FORM_TEXTAREA) : STRING + -- + do + -- Replaces the textarea with an editor instance. Save the instance in a variable + Result := "if (" + editor_variable(a_textarea) + " != undefined) " + editor_variable(a_textarea) + ".destroy();" + end + +end diff --git a/src/kernel/form/cms_form_textarea.e b/src/kernel/form/cms_form_textarea.e new file mode 100644 index 0000000..60df239 --- /dev/null +++ b/src/kernel/form/cms_form_textarea.e @@ -0,0 +1,77 @@ +note + description: "Extends the WSF form textarea with features to add a WYSIWIG editor." + author: "Dario Bösch + do + precursor(a_name) + + -- By default we don't replace the textarea by an editor + editor := False; + end + +feature -- Access + + editor : BOOLEAN + -- True if the textarea should be replaced by the editor. Default is false. + + format_field : detachable WSF_FORM_SELECT + -- Selection field for the format on that it depends, if the editor is shown or not. + + condition_value : detachable STRING + +feature -- Editor + + show_as_editor + -- The textarea will be replaced by a wysiwyg editor + do + editor := True + end + + show_as_editor_if_selected (a_select_field : WSF_FORM_SELECT; a_value : STRING) + -- Replaces the textarea only if a_select_field has a_value (or the value gets selected) + do + editor := True + format_field := a_select_field + condition_value := a_value + end + +feature -- Conversion + + append_item_to_html (a_theme: WSF_THEME; a_html: STRING_8) + do + -- Add javascript to replace textarea with editor + precursor(a_theme, a_html) + if editor then + a_html.append (load_assets) + a_html.append ("") + end + end + +end diff --git a/src/service/cms_api.e b/src/service/cms_api.e index 47e3d0a..97387fd 100644 --- a/src/service/cms_api.e +++ b/src/service/cms_api.e @@ -287,7 +287,7 @@ feature -- Path aliases do Result := a_source if attached storage.path_alias (Result) as l_path then - Result := l_path + Result := "/" + l_path end end diff --git a/src/service/cms_module.e b/src/service/cms_module.e index 7502704..1988de0 100644 --- a/src/service/cms_module.e +++ b/src/service/cms_module.e @@ -43,7 +43,7 @@ feature {CMS_API} -- Module Initialization feature -- Status is_initialized: BOOLEAN - -- Is Current module initialized? + -- Is Current module initialized? feature {CMS_API} -- Access: API