505 lines
15 KiB
Plaintext
505 lines
15 KiB
Plaintext
note
|
|
description: "files module."
|
|
date: "$Date$"
|
|
revision: "$Revision$"
|
|
|
|
class
|
|
CMS_FILES_MODULE
|
|
|
|
inherit
|
|
CMS_MODULE
|
|
rename
|
|
module_api as files_api
|
|
redefine
|
|
install,
|
|
initialize,
|
|
setup_hooks,
|
|
permissions,
|
|
files_api
|
|
end
|
|
|
|
CMS_HOOK_MENU_SYSTEM_ALTER
|
|
|
|
SHARED_EXECUTION_ENVIRONMENT
|
|
|
|
create
|
|
make
|
|
|
|
feature {NONE} -- Initialization
|
|
|
|
make
|
|
do
|
|
name := "files"
|
|
version := "1.0"
|
|
description := "Service to upload files, and manage them."
|
|
package := "file"
|
|
end
|
|
|
|
feature -- Access
|
|
|
|
name: STRING
|
|
|
|
permissions: LIST [READABLE_STRING_8]
|
|
-- List of permission ids, used by this module, and declared.
|
|
do
|
|
Result := Precursor
|
|
Result.force (admin_files_permission)
|
|
Result.force (upload_files_permission)
|
|
Result.force (browse_files_permission)
|
|
end
|
|
|
|
admin_files_permission: STRING = "admin files"
|
|
upload_files_permission: STRING = "upload files"
|
|
browse_files_permission: STRING = "browse files"
|
|
|
|
feature {CMS_API} -- Module Initialization
|
|
|
|
initialize (api: CMS_API)
|
|
-- <Precursor>
|
|
do
|
|
Precursor (api)
|
|
if files_api = Void then
|
|
create files_api.make (api)
|
|
end
|
|
end
|
|
|
|
feature {CMS_API}-- Module management
|
|
|
|
install (api: CMS_API)
|
|
-- install the module
|
|
local
|
|
l_files_api: like files_api
|
|
d: DIRECTORY
|
|
do
|
|
create l_files_api.make (api)
|
|
create d.make_with_path (l_files_api.uploads_directory)
|
|
if not d.exists then
|
|
d.recursive_create_dir
|
|
end
|
|
create d.make_with_path (l_files_api.metadata_directory)
|
|
if not d.exists then
|
|
d.recursive_create_dir
|
|
end
|
|
|
|
files_api := l_files_api
|
|
Precursor (api)
|
|
end
|
|
|
|
feature {CMS_API} -- Access: API
|
|
|
|
files_api: detachable CMS_FILES_API
|
|
-- <Precursor>
|
|
|
|
feature -- Access: router
|
|
|
|
setup_router (a_router: WSF_ROUTER; a_api: CMS_API)
|
|
-- <Precursor>
|
|
do
|
|
map_uri_template_agent (a_router, "/" + uploads_location, agent execute_upload (?, ?, a_api), Void) -- Accepts any method GET, HEAD, POST, PUT, DELETE, ...
|
|
map_uri_template_agent (a_router, "/" + uploads_location + "{filename}", agent display_uploaded_file_info (?, ?, a_api), a_router.methods_get)
|
|
map_uri_template_agent (a_router, "/" + uploads_location + "remove/{filename}", agent remove_file (?, ?, a_api), a_router.methods_get)
|
|
end
|
|
|
|
uploads_location: STRING = "upload/"
|
|
|
|
feature -- Hooks
|
|
|
|
setup_hooks (a_hooks: CMS_HOOK_CORE_MANAGER)
|
|
do
|
|
a_hooks.subscribe_to_menu_system_alter_hook (Current)
|
|
end
|
|
|
|
menu_system_alter (a_menu_system: CMS_MENU_SYSTEM; a_response: CMS_RESPONSE)
|
|
local
|
|
link: CMS_LOCAL_LINK
|
|
do
|
|
-- login in demo did somehow not work
|
|
if a_response.has_permission (upload_files_permission) then
|
|
create link.make ("Upload files", uploads_location)
|
|
a_menu_system.navigation_menu.extend (link)
|
|
end
|
|
end
|
|
|
|
feature -- Handler
|
|
|
|
execute_not_found_handler (uri: READABLE_STRING_8; req: WSF_REQUEST; res: WSF_RESPONSE)
|
|
-- `uri' is not found, redirect to default page
|
|
do
|
|
res.redirect_now_with_content (req.script_url ("/"), uri + ": not found. %N Redirection to" + req.script_url ("/"), "text/html")
|
|
end
|
|
|
|
display_uploaded_file_info (req: WSF_REQUEST; res: WSF_RESPONSE; api: CMS_API)
|
|
-- Display information related to a cms uploaded file.
|
|
local
|
|
body: STRING_8
|
|
r: CMS_RESPONSE
|
|
f: CMS_FILE
|
|
md: detachable CMS_FILE_METADATA
|
|
fn: READABLE_STRING_32
|
|
do
|
|
check req.is_get_request_method end
|
|
if not api.has_permission (browse_files_permission) then
|
|
create {FORBIDDEN_ERROR_CMS_RESPONSE} r.make (req, res, api)
|
|
r.add_error_message ("You are not allowed to browse CMS files!")
|
|
elseif attached {WSF_STRING} req.path_parameter ("filename") as p_filename then
|
|
create {GENERIC_VIEW_CMS_RESPONSE} r.make (req, res, api)
|
|
|
|
-- add style
|
|
r.add_style (r.url ("/module/" + name + "/files/css/files.css", Void), Void)
|
|
|
|
create body.make_empty
|
|
|
|
fn := p_filename.value
|
|
r.set_page_title ({STRING_32} "File %"" + fn + {STRING_32} "%"")
|
|
body.append ("<div class=%"uploaded-files%">%N") -- To ease css customization.
|
|
if attached files_api as l_files_api then
|
|
f := l_files_api.new_uploads_file (create {PATH}.make_from_string (fn))
|
|
|
|
body.append ("<div class=%"metadata%">")
|
|
|
|
md := l_files_api.metadata (f)
|
|
|
|
--| Uploader user
|
|
body.append ("<strong>User: </strong>")
|
|
if md /= Void and then attached md.user as meta_user then
|
|
body.append (api.html_encoded (meta_user.name))
|
|
else
|
|
body.append ("unknown user")
|
|
end
|
|
body.append ("<br/>%N")
|
|
|
|
--| Uploaded date
|
|
body.append ("<strong>Upload Time: </strong>")
|
|
if md /= Void and then attached md.date as meta_time then
|
|
body.append (meta_time.out)
|
|
else
|
|
body.append ("NA")
|
|
end
|
|
body.append ("<br/>%N")
|
|
|
|
--| File size
|
|
body.append ("<strong> File Size: </strong>")
|
|
if md /= Void and then md.size > 0 then
|
|
body.append (file_size_human_string (md.size))
|
|
else
|
|
body.append ("NA")
|
|
end
|
|
body.append ("<br/>%N")
|
|
|
|
--| File type
|
|
body.append ("<strong>File Type: </strong>")
|
|
if md /= Void and then attached md.file_type as meta_type then
|
|
body.append (meta_type)
|
|
else
|
|
body.append ("NA")
|
|
end
|
|
body.append ("<br/><br/>%N")
|
|
|
|
body.append ("<a class=%"button%" href=%"" + req.script_url ("/" + l_files_api.file_link (f).location) + "%">Download</a>%N")
|
|
body.append ("<a class=%"button%" href=%"" + req.script_url ("/" + uploads_location + "remove/" + f.filename) + "%">Remove</a>%N")
|
|
body.append ("</div>%N") -- metadata
|
|
|
|
body.append ("<div class=%"overview%">")
|
|
if
|
|
attached f.relative_path.extension as ext and then
|
|
(
|
|
ext.is_case_insensitive_equal_general ("png")
|
|
or ext.is_case_insensitive_equal_general ("jpg")
|
|
or ext.is_case_insensitive_equal_general ("gif")
|
|
)
|
|
then
|
|
body.append ("<img src=%"" + req.script_url ("/" + l_files_api.file_link (f).location) + "%" />")
|
|
else
|
|
-- add default thumbnail
|
|
body.append ("<img src=%"" + req.script_url ("/module/" + name + "/files/img/file-logo.png") + "%" />")
|
|
end
|
|
body.append ("</div>%N") -- Overview
|
|
end
|
|
body.append ("</div>%N")
|
|
|
|
r.add_to_primary_tabs (create {CMS_LOCAL_LINK}.make ("Uploaded files", uploads_location))
|
|
r.set_main_content (body)
|
|
else
|
|
create {BAD_REQUEST_ERROR_CMS_RESPONSE} r.make (req, res, api)
|
|
r.set_main_content ("Missing 'filename' field value!")
|
|
end
|
|
r.execute
|
|
end
|
|
|
|
execute_upload (req: WSF_REQUEST; res: WSF_RESPONSE; api: CMS_API)
|
|
local
|
|
body: STRING_8
|
|
r: CMS_RESPONSE
|
|
do
|
|
if req.is_get_head_request_method or req.is_post_request_method then
|
|
create body.make_empty
|
|
body.append ("<h1> Upload files </h1>%N")
|
|
|
|
create {GENERIC_VIEW_CMS_RESPONSE} r.make (req, res, api)
|
|
|
|
-- set style
|
|
r.add_style (r.url ("/module/" + name + "/files/css/files.css", void), void)
|
|
|
|
-- add JS for dropzone
|
|
r.add_javascript_url (r.url ("/module/" + name + "/files/js/dropzone.js", void))
|
|
r.add_style (r.url ("/module/" + name + "/files/js/dropzone.css", void), void)
|
|
|
|
if api.has_permission (upload_files_permission) then
|
|
-- create body
|
|
body.append ("<p>Please choose some file(s) to upload.</p>")
|
|
|
|
-- create form to choose files and upload them
|
|
body.append ("<div class=%"upload-files%">")
|
|
body.append ("<form action=%"" + r.url (uploads_location, Void) + "%" class=%"dropzone%">")
|
|
body.append ("</form>%N")
|
|
body.append ("<div class=%"center%"><a class=%"button%" href=%""+ r.url (uploads_location, Void) +"%">Upload Files</a></div>")
|
|
body.append ("</div>")
|
|
if req.is_post_request_method then
|
|
process_uploaded_files (req, api, body)
|
|
end
|
|
else
|
|
create {FORBIDDEN_ERROR_CMS_RESPONSE} r.make (req, res, api)
|
|
end
|
|
|
|
-- Build the response.
|
|
if r.has_permission (browse_files_permission) then
|
|
append_uploaded_file_album_to (req, api, body)
|
|
else
|
|
r.add_warning_message ("You are not allowed to browse files!")
|
|
end
|
|
|
|
r.set_main_content (body)
|
|
else
|
|
create {BAD_REQUEST_ERROR_CMS_RESPONSE} r.make (req, res, api)
|
|
end
|
|
r.execute
|
|
end
|
|
|
|
process_uploaded_files (req: WSF_REQUEST; api: CMS_API; a_output: STRING)
|
|
-- Process http request uploaded files.
|
|
require
|
|
has_permission: api.has_permission (upload_files_permission)
|
|
local
|
|
l_uploaded_file: CMS_UPLOADED_FILE
|
|
uf: WSF_UPLOADED_FILE
|
|
do
|
|
if attached files_api as l_files_api then
|
|
-- if has uploaded files, then store them
|
|
if req.has_uploaded_file then
|
|
a_output.append ("<strong>Newly uploaded file(s): </strong>%N")
|
|
a_output.append ("<ul class=%"uploaded-files%">")
|
|
across
|
|
req.uploaded_files as ic
|
|
loop
|
|
uf := ic.item
|
|
create l_uploaded_file.make_with_uploaded_file (l_files_api.uploads_directory, uf)
|
|
a_output.append ("<li>")
|
|
a_output.append (api.html_encoded (l_uploaded_file.filename))
|
|
|
|
-- create medadata file
|
|
l_uploaded_file.set_size (uf.size)
|
|
l_uploaded_file.set_type (uf.content_type)
|
|
|
|
-- store the just uploaded file
|
|
l_files_api.save_uploaded_file (l_uploaded_file)
|
|
|
|
if l_files_api.has_error then
|
|
a_output.append (" <span class=%"error%">: upload failed!</span>")
|
|
end
|
|
a_output.append ("</li>")
|
|
end
|
|
a_output.append ("</ul>%N")
|
|
end
|
|
end
|
|
end
|
|
|
|
append_uploaded_file_album_to (req: WSF_REQUEST; api: CMS_API; a_output: STRING)
|
|
local
|
|
d: DIRECTORY
|
|
f: CMS_FILE
|
|
p: PATH
|
|
rel: PATH
|
|
md: detachable CMS_FILE_METADATA
|
|
do
|
|
if attached files_api as l_files_api then
|
|
rel := l_files_api.uploads_relative_path
|
|
p := l_files_api.uploads_directory
|
|
|
|
a_output.append ("<div class=%"uploaded-files%">%N")
|
|
a_output.append ("<strong>Uploaded files:</strong>%N")
|
|
a_output.append ("<table>%N")
|
|
a_output.append ("<tr><th>Filename</th><th>Uploading Time</th><th>User</th><th>Size</th><th></th><th></th></tr>%N")
|
|
|
|
create d.make_with_path (p)
|
|
if d.exists then
|
|
across
|
|
d.entries as ic
|
|
loop
|
|
|
|
if ic.item.is_current_symbol then
|
|
-- Ignore
|
|
elseif ic.item.is_parent_symbol then
|
|
-- Ignore for now.
|
|
else
|
|
f := l_files_api.new_file (rel.extended_path (ic.item))
|
|
|
|
-- check if f is directory -> yes, then do not show
|
|
if not f.is_directory then
|
|
a_output.append ("<tr>")
|
|
|
|
-- add filename
|
|
a_output.append ("<td class=%"filename%">")
|
|
a_output.append ("<a href=%"" + api.percent_encoded (f.filename) + "%">")
|
|
a_output.append (api.html_encoded (f.filename))
|
|
a_output.append ("</a>")
|
|
a_output.append ("</td>%N")
|
|
|
|
md := l_files_api.metadata (f)
|
|
if md = Void then
|
|
a_output.append ("<td class=%"date%"></td>%N")
|
|
a_output.append ("<td class=%"user%"></td>%N")
|
|
a_output.append ("<td class=%"size%"></td>%N")
|
|
else
|
|
|
|
-- add uploading time
|
|
a_output.append ("<td class=%"date%">")
|
|
if attached md.date as meta_time then
|
|
a_output.append (meta_time.out)
|
|
end
|
|
a_output.append ("</td>%N")
|
|
|
|
-- add user
|
|
a_output.append ("<td class=%"user%">")
|
|
if attached md.user as u then
|
|
a_output.append (api.html_encoded (u.name))
|
|
end
|
|
a_output.append ("</td>%N")
|
|
|
|
-- add size
|
|
a_output.append ("<td class=%"size%">")
|
|
if md.size > 0 then
|
|
a_output.append (file_size_human_string (md.size))
|
|
else
|
|
a_output.append ("NA")
|
|
end
|
|
a_output.append ("</td>%N")
|
|
end
|
|
|
|
-- add download link
|
|
a_output.append ("<td>")
|
|
a_output.append ("<a class=%"button%" href=%"" + req.script_url ("/" + l_files_api.file_link (f).location) + "%" download>Download</a>%N")
|
|
a_output.append ("</td>%N")
|
|
|
|
-- add remove button
|
|
a_output.append ("<td>")
|
|
a_output.append ("<a class=%"button%" href=%"" + req.script_url ("/" + uploads_location + "remove/" + f.filename) + "%">Remove</a>%N")
|
|
a_output.append ("</td>%N")
|
|
|
|
a_output.append ("</tr>%N")
|
|
else
|
|
if f.relative_path.is_current_symbol or f.relative_path.is_parent_symbol then
|
|
-- Ignore "." and ".."
|
|
else
|
|
|
|
-- folder support not yet supported
|
|
|
|
-- -- add directory identifier
|
|
-- a_output.append ("<td>[dir]</td>%N")
|
|
|
|
-- a_output.append ("<td>")
|
|
-- a_output.append ("<a href=%"" + api.percent_encoded (f.filename) + "%">")
|
|
-- a_output.append (api.html_encoded (f.filename))
|
|
-- a_output.append ("</a>")
|
|
-- a_output.append ("</td>%N")
|
|
|
|
-- a_output.append ("<td></td><td></td><td></td><td></td><td></td>")
|
|
end
|
|
|
|
end
|
|
end
|
|
end
|
|
end
|
|
a_output.append ("</table>%N")
|
|
a_output.append ("</div>%N")
|
|
end
|
|
end
|
|
|
|
remove_file (req: WSF_REQUEST; res: WSF_RESPONSE; api: CMS_API)
|
|
local
|
|
body: STRING
|
|
r: CMS_RESPONSE
|
|
err: BOOLEAN
|
|
do
|
|
if attached files_api as l_files_api then
|
|
if not api.has_permission (admin_files_permission) then
|
|
create {FORBIDDEN_ERROR_CMS_RESPONSE} r.make (req, res, api)
|
|
r.add_error_message ("You are not allowed to remove file!")
|
|
elseif attached {WSF_STRING} req.path_parameter ("filename") as p_filename then
|
|
create {GENERIC_VIEW_CMS_RESPONSE} r.make (req, res, api)
|
|
|
|
l_files_api.delete_file (p_filename.value)
|
|
err := l_files_api.has_error
|
|
l_files_api.reset_error
|
|
create body.make_empty
|
|
|
|
if err then
|
|
body.append ("<h3>The file has been removed successfully!</h3>")
|
|
else
|
|
body.append ("<h3>The file removal failed!</h3>")
|
|
end
|
|
|
|
r.add_to_primary_tabs (create {CMS_LOCAL_LINK}.make ("Uploaded files", uploads_location))
|
|
r.set_main_content (body)
|
|
else
|
|
create {BAD_REQUEST_ERROR_CMS_RESPONSE} r.make (req, res, api)
|
|
r.add_error_message ("Missing 'filename' parameter!")
|
|
end
|
|
else
|
|
create {INTERNAL_SERVER_ERROR_CMS_RESPONSE} r.make (req, res, api)
|
|
r.set_main_content ("Removal of file failed due to internal server error!")
|
|
end
|
|
r.execute
|
|
end
|
|
|
|
feature -- Helpers
|
|
|
|
file_size_human_string (a_size: INTEGER): STRING
|
|
local
|
|
size: INTEGER
|
|
do
|
|
size := a_size
|
|
if size >= 1000000 then
|
|
size := size // 1000000
|
|
Result := size.out + " MB"
|
|
else
|
|
if size >= 1000 then
|
|
size := size // 1000
|
|
Result := size.out + " kB"
|
|
else
|
|
Result := size.out + " bytes"
|
|
end
|
|
end
|
|
end
|
|
|
|
feature -- Mapping helper: uri template agent (analogue to the demo-module)
|
|
|
|
map_uri_template (a_router: WSF_ROUTER; a_tpl: STRING; h: WSF_URI_TEMPLATE_HANDLER; rqst_methods: detachable WSF_REQUEST_METHODS)
|
|
-- Map `h' as handler for `a_tpl', according to `rqst_methods'.
|
|
require
|
|
a_tpl_attached: a_tpl /= Void
|
|
h_attached: h /= Void
|
|
do
|
|
a_router.map (create {WSF_URI_TEMPLATE_MAPPING}.make (a_tpl, h), rqst_methods)
|
|
end
|
|
|
|
map_uri_template_agent (a_router: WSF_ROUTER; a_tpl: READABLE_STRING_8; proc: PROCEDURE [TUPLE [req: WSF_REQUEST; res: WSF_RESPONSE]]; rqst_methods: detachable WSF_REQUEST_METHODS)
|
|
-- Map `proc' as handler for `a_tpl', according to `rqst_methods'.
|
|
require
|
|
a_tpl_attached: a_tpl /= Void
|
|
proc_attached: proc /= Void
|
|
do
|
|
map_uri_template (a_router, a_tpl, create {WSF_URI_TEMPLATE_AGENT_HANDLER}.make (proc), rqst_methods)
|
|
end
|
|
|
|
end
|