* finishing import

git-svn-id: http://redmine-dmsf.googlecode.com/svn/trunk/redmine_dmsf@4 5e329b0b-a2ee-ea63-e329-299493fc886d
This commit is contained in:
vit.jonas@gmail.com 2011-05-05 19:29:34 +00:00
parent acd3c8ca99
commit 2c23d4e1aa
98 changed files with 4050 additions and 0 deletions

80
README.txt Normal file
View File

@ -0,0 +1,80 @@
1 Installation and Setup
1.1. Required packages
For zipped content download you must have rubyzip gem installed.
To use file/document search capabilities you must install xapian (http://xapian.org) search engine.
That means libxapian-ruby1.8 and xapian-omega packages. To index some files with omega you may have to install some other
packages like xpdf, antiword, ...
From Omega documentation:
* PDF (.pdf) if pdftotext is available (comes with xpdf)
* PostScript (.ps, .eps, .ai) if ps2pdf (from ghostscript) and pdftotext (comes with xpdf) are available
* OpenOffice/StarOffice documents (.sxc, .stc, .sxd, .std, .sxi, .sti, .sxm, .sxw, .sxg, .stw) if unzip is available
* OpenDocument format documents (.odt, .ods, .odp, .odg, .odc, .odf, .odb, .odi, .odm, .ott, .ots, .otp, .otg, .otc, .otf, .oti, .oth) if unzip is available
* MS Word documents (.doc, .dot) if antiword is available
* MS Excel documents (.xls, .xlb, .xlt) if xls2csv is available (comes with catdoc)
* MS Powerpoint documents (.ppt, .pps) if catppt is available (comes with catdoc)
* MS Office 2007 documents (.docx, .dotx, .xlsx, .xlst, .pptx, .potx, .ppsx) if unzip is available
* Wordperfect documents (.wpd) if wpd2text is available (comes with libwpd)
* MS Works documents (.wps, .wpt) if wps2text is available (comes with libwps)
* AbiWord documents (.abw)
* Compressed AbiWord documents (.zabw) if gzip is available
* Rich Text Format documents (.rtf) if unrtf is available
* Perl POD documentation (.pl, .pm, .pod) if pod2text is available
* TeX DVI files (.dvi) if catdvi is available
* DjVu files (.djv, .djvu) if djvutxt is available
* XPS files (.xps) if unzip is available
On Debinan (Squeeze) use:
apt-get install xapian-ruby1.8 xapian-omega libxapian-dev xpdf antiword unzip antiword\
catdoc libwpd8c2a libwps-0.1-1 gzip unrtf catdvi djview djview3 libzip-ruby1.8
In case of package shortage it is possible to use:
gem install xapian-full rubyzip
1.2. Plugin installation
Install redmine_dmsf into vendor/plugins directory with:
* Put redmine_dmsf plugin content into vendor/plugins
* Initialize database:
rake db:migrate:plugins RAILS_ENV="production"
* The access rights must be set for web server
Example:
chown -R www-data:www-data /opt/redmine/vendor/plugins/redmine_dmsf
* Restart web server
Then you must configure plugin in Administration -> Plugins -> DMSF -> Configure
It is also neccessary to assign DMSF permissions to appropriate roles.
DMSF act as project module so you must check DMSF in project settings.
Search options will now contain "Dmsf files" check, that allows you to search DMSF content.
To include Wiki DMSF link help:
* In file public/help/wiki_syntax_detailed.html include after document link description:
<ul>
<li>
DMSF:
<ul>
<li><strong>{{dmsf(17)}}</strong> (link to file with id 17)</li>
</ul>
DMSF file id can be found in link for file download
</li>
</ul>
1.3. Setup
It is necessary to index DMSF files with omega before searching attemts to recieve some output:
omindex -s english -l 1 --db {path to index database from plugin configuration} {path to storage from plugin configuration}
This command must be run on regular basis (e.g. from cron)
Example of cron job (once per hour at 8th minute):
* 8 * * * root /usr/bin/omindex -s english -l 1 --db /opt/redmine/files/dmsf_index /opt/redmine/files/dmsf
Use omindex -h for help.

View File

@ -0,0 +1,200 @@
class DmsfController < ApplicationController
unloadable
before_filter :find_project
before_filter :authorize
before_filter :find_folder, :only => [:index, :entries_operation, :email_entries_send]
before_filter :find_file, :only => [:download_file]
helper :sort
include SortHelper
def index
sort_init ["title", "asc"]
sort_update ["title", "size", "modified", "version", "author"]
if @folder.nil?
@subfolders = DmsfFolder.project_root_folders(@project)
@files = DmsfFile.project_root_files(@project)
else
@subfolders = @folder.subfolders
@files = @folder.files
end
@files.sort! do |a,b|
case @sort_criteria.first_key
when "size" then a.last_revision.size <=> b.last_revision.size
when "modified" then a.last_revision.updated_at <=> b.last_revision.updated_at
when "version" then
result = a.last_revision.major_version <=> b.last_revision.major_version
result == 0 ? a.last_revision.minor_version <=> b.last_revision.minor_version : result
when "author" then a.last_revision.user <=> b.last_revision.user
else a.last_revision.title <=> b.last_revision.title
end
end
if !@sort_criteria.first_asc?
@subfolders.reverse!
@files.reverse!
end
respond_to do |format|
format.html { render :layout => !request.xhr? }
format.api
end
end
def download_file
@revision = @file.last_revision
Rails.logger.info "#{Time.now} from #{request.remote_ip}/#{request.env["HTTP_X_FORWARDED_FOR"]}: #{User.current.login} downloaded #{@project.identifier}://#{@file.dmsf_path_str} revision #{@revision.id}"
send_revision
end
def download_revision
@revision = DmsfFileRevision.find(params[:revision_id])
if @revision.deleted
render_404
else
Rails.logger.info "#{Time.now} from #{request.remote_ip}/#{request.env["HTTP_X_FORWARDED_FOR"]}: #{User.current.login} downloaded #{@project.identifier}://#{@revision.file.dmsf_path_str} revision #{@revision.id}"
check_project(@revision.file)
send_revision
end
rescue DmsfAccessError
render_403
end
def entries_operation
selected_folders = params[:subfolders]
selected_files = params[:files]
if selected_folders.nil? && selected_files.nil?
flash[:warning] = l(:warning_no_entries_selected)
redirect_to :action => "index", :id => @project, :folder_id => @folder
return
end
if !params[:email_entries].blank?
email_entries(selected_folders, selected_files)
else
download_entries(selected_folders, selected_files)
end
rescue ZipMaxFilesError
flash[:error] = l(:error_max_files_exceeded) + Setting.plugin_redmine_dmsf["dmsf_max_file_download"].to_i.to_s
redirect_to({:controller => "dmsf", :action => "index", :id => @project, :folder_id => @folder})
rescue DmsfAccessError
render_403
end
def email_entries_send
@email_params = params[:email]
if @email_params["to"].strip.blank?
flash[:error] = l(:error_email_to_must_be_entered)
render :action => "email_entries"
return
end
DmsfMailer.deliver_send_documents(User.current, @email_params["to"], @email_params["cc"],
@email_params["subject"], @email_params["zipped_content"], @email_params["body"])
File.delete(@email_params["zipped_content"])
flash[:notice] = l(:notice_email_sent)
redirect_to({:controller => "dmsf", :action => "index", :id => @project, :folder_id => @folder})
end
class ZipMaxFilesError < StandardError
end
private
def email_entries(selected_folders, selected_files)
zip = DmsfZip.new
zip_entries(zip, selected_folders, selected_files)
ziped_content = "#{DmsfHelper.temp_dir}/#{DmsfHelper.temp_filename("dmsf_email_sent_documents.zip")}";
File.open(ziped_content, "wb") do |f|
zip_file = File.open(zip.finish, "rb")
while (buffer = zip_file.read(8192))
f.write(buffer)
end
end
Rails.logger.info "#{Time.now} from #{request.remote_ip}/#{request.env["HTTP_X_FORWARDED_FOR"]}: #{User.current.login} emailed from project #{@project.identifier}:"
selected_folders.each {|folder| Rails.logger.info "\tFolder #{folder}"} unless selected_folders.nil?
selected_files.each {|file| Rails.logger.info "\tFile #{file}"} unless selected_files.nil?
@email_params = {"zipped_content" => ziped_content}
render :action => "email_entries"
ensure
zip.close unless zip.nil?
end
def download_entries(selected_folders, selected_files)
zip = DmsfZip.new
zip_entries(zip, selected_folders, selected_files)
Rails.logger.info "#{Time.now} from #{request.remote_ip}/#{request.env["HTTP_X_FORWARDED_FOR"]}: #{User.current.login} downloaded from project #{@project.identifier}:"
selected_folders.each {|folder| Rails.logger.info "\tFolder #{folder}"} unless selected_folders.nil?
selected_files.each {|file| Rails.logger.info "\tFile #{file}"} unless selected_files.nil?
send_file(zip.finish,
:filename => filename_for_content_disposition(@project.name + "-" + DateTime.now.strftime("%y%m%d%H%M%S") + ".zip"),
:type => "application/zip",
:disposition => "attachment")
ensure
zip.close unless zip.nil?
end
def zip_entries(zip, selected_folders, selected_files)
if selected_folders && selected_folders.is_a?(Array)
selected_folders.each do |selected_folder_id|
check_project(folder = DmsfFolder.find(selected_folder_id))
zip.add_folder(folder) unless folder.nil?
end
end
if selected_files && selected_files.is_a?(Array)
selected_files.each do |selected_file_id|
check_project(file = DmsfFile.find(selected_file_id))
zip.add_file(file) unless file.nil?
end
end
max_files = 0
max_files = Setting.plugin_redmine_dmsf["dmsf_max_file_download"].to_i
if max_files > 0 && zip.file_count > max_files
raise ZipMaxFilesError, zip.file_count
end
zip
end
def send_revision
send_file(@revision.disk_file,
:filename => filename_for_content_disposition(@revision.file.name),
:type => @revision.detect_content_type,
:disposition => "attachment")
end
def find_project
@project = Project.find(params[:id])
end
def find_folder
@folder = DmsfFolder.find(params[:folder_id]) if params.keys.include?("folder_id")
check_project(@folder)
rescue DmsfAccessError
render_403
end
def find_file
check_project(@file = DmsfFile.find(params[:file_id]))
rescue DmsfAccessError
render_403
end
def check_project(entry)
if !entry.nil? && entry.project != @project
raise DmsfAccessError, "Entry project doesn't match current project"
end
end
end

View File

@ -0,0 +1,215 @@
class DmsfDetailController < ApplicationController
unloadable
before_filter :find_project
before_filter :authorize
before_filter :find_folder, :only => [:create_folder, :delete_folder, :save_folder,
:upload_files, :commit_files, :folder_detail]
before_filter :find_file, :only => [:save_file, :delete_file, :file_detail]
def create_folder
@new_folder = DmsfFolder.create_from_params(@project, @folder, params[:dmsf_folder])
if @new_folder.valid?
flash[:notice] = "Folder created"
Rails.logger.info "#{Time.now} from #{request.remote_ip}/#{request.env["HTTP_X_FORWARDED_FOR"]}: #{User.current.login} created folder #{@project.identifier}://#{@new_folder.dmsf_path_str}"
else
flash[:error] = "Folder creation failed: Title must be entered"
end
redirect_to({:controller => "dmsf", :action => "index", :id => @project, :folder_id => @folder})
end
def delete_folder
check_project(@delete_folder = DmsfFolder.find(params[:delete_folder_id]))
if !@delete_folder.nil?
if @delete_folder.subfolders.empty? && @delete_folder.files.empty?
@delete_folder.destroy
flash[:notice] = "Folder deleted"
Rails.logger.info "#{Time.now} from #{request.remote_ip}/#{request.env["HTTP_X_FORWARDED_FOR"]}: #{User.current.login} deleted folder #{@project.identifier}://#{@delete_folder.dmsf_path_str}"
else
flash[:error] = "Folder is not empty"
end
end
redirect_to :controller => "dmsf", :action => "index", :id => @project, :folder_id => @folder
rescue DmsfAccessError
render_403
end
def folder_detail
end
def save_folder
@folder.description = params[:description]
if params[:title].blank?
flash.now[:error] = "Title must be entered"
render "folder_detail"
return
end
@folder.name = params[:title]
if !@folder.valid?
flash.now[:error] = "Title is already used"
render "folder_detail"
return
end
@folder.save!
Rails.logger.info "#{Time.now} from #{request.remote_ip}/#{request.env["HTTP_X_FORWARDED_FOR"]}: #{User.current.login} updated folder #{@project.identifier}://#{@folder.dmsf_path_str}"
flash[:notice] = "Folder details were saved"
redirect_to :controller => "dmsf", :action => "index", :id => @project, :folder_id => @folder
end
#TODO: show lock/unlock history
def file_detail
end
def delete_file
if !@file.nil?
if @file.locked_for_user?
flash[:error] = "File is locked"
else
@file.deleted = true
@file.deleted_by_user = User.current
@file.save
flash[:notice] = "File deleted"
Rails.logger.info "#{Time.now} from #{request.remote_ip}/#{request.env["HTTP_X_FORWARDED_FOR"]}: #{User.current.login} deleted file #{@project.identifier}://#{@file.dmsf_path_str}"
DmsfMailer.deliver_files_deleted(User.current, [@file])
end
end
redirect_to :controller => "dmsf", :action => "index", :id => @project, :folder_id => @file.folder
end
def delete_revision
@revision = DmsfFileRevision.find(params[:revision_id])
check_project(@revision.file)
if @revision.file.locked_for_user?
flash[:error] = "File is locked"
else
if !@revision.nil? && !@revision.deleted
if @revision.file.revisions.size <= 1
flash[:error] = "At least one revision must be present"
else
@revision.deleted = true
@revision.deleted_by_user = User.current
@revision.save
flash[:notice] = "Revision deleted"
Rails.logger.info "#{Time.now} from #{request.remote_ip}/#{request.env["HTTP_X_FORWARDED_FOR"]}: #{User.current.login} deleted revision #{@project.identifier}://#{@revision.file.dmsf_path_str}/#{@revision.id}"
end
end
end
redirect_to :action => "file_detail", :id => @project, :file_id => @revision.file
end
def upload_files
uploaded_files = params[:uploaded_files]
@uploads = []
if uploaded_files && uploaded_files.is_a?(Hash)
# standard file input uploads
uploaded_files.each_value do |uploaded_file|
@uploads.push(DmsfUpload.create_from_uploaded_file(@project, @folder, uploaded_file))
end
else
# plupload multi upload completed
uploaded = params[:uploaded]
if uploaded && uploaded.is_a?(Hash)
uploaded.each_value do |uploaded_file|
@uploads.push(DmsfUpload.new(@project, @folder, uploaded_file))
end
end
end
end
# plupload single file multi upload handling
def upload_file
@tempfile = params[:file]
@disk_filename = DmsfHelper.temp_filename(@tempfile.original_filename)
File.open("#{DmsfHelper.temp_dir}/#{@disk_filename}", "wb") do |f|
while (buffer = @tempfile.read(8192))
f.write(buffer)
end
end
render :layout => false
end
#TODO: flash notice when files saved and unlocked
#TODO: separate control for approval
def commit_files
commited_files = params[:commited_files]
if commited_files && commited_files.is_a?(Hash)
files = []
commited_files.each_value do |commited_file|
file = DmsfFile.from_commited_file(@project, @folder, commited_file)
if file.locked_for_user?
flash[:error] = "One of files locked"
else
revision = DmsfFileRevision.from_commited_file(file, commited_file)
file.unlock if file.locked?
file.reload
files.push(file)
end
end
Rails.logger.info "#{Time.now} from #{request.remote_ip}/#{request.env["HTTP_X_FORWARDED_FOR"]}: #{User.current.login} uploaded for project #{@project.identifier}:"
files.each {|file| Rails.logger.info "\t#{file.dmsf_path_str}:"}
begin
DmsfMailer.deliver_files_updated(User.current, files)
rescue ActionView::MissingTemplate => e
Rails.logger.error "Could not send email notifications: " + e
end
end
redirect_to :controller => "dmsf", :action => "index", :id => @project, :folder_id => @folder
end
#TODO: separate control for approval
#TODO: don't create revision if nothing change
def save_file
if @file.locked_for_user?
flash[:error] = "File locked"
else
saved_file = params[:file]
DmsfFileRevision.from_saved_file(@file, saved_file)
if @file.locked?
@file.unlock
flash[:notice] = "File unlocked, "
end
@file.reload
flash[:notice] = (flash[:notice].nil? ? "" : flash[:notice]) + "File revision created"
Rails.logger.info "#{Time.now} from #{request.remote_ip}/#{request.env["HTTP_X_FORWARDED_FOR"]}: #{User.current.login} created new revision of file #{@project.identifier}://#{@file.dmsf_path_str}"
begin
DmsfMailer.deliver_files_updated(User.current, [@file])
rescue ActionView::MissingTemplate => e
Rails.logger.error "Could not send email notifications: " + e
end
end
redirect_to :action => "file_detail", :id => @project, :file_id => @file
end
private
def find_project
@project = Project.find(params[:id])
end
def find_folder
@folder = DmsfFolder.find(params[:folder_id]) if params.keys.include?("folder_id")
check_project(@folder)
rescue DmsfAccessError
render_403
end
def find_file
check_project(@file = DmsfFile.find(params[:file_id]))
rescue DmsfAccessError
render_403
end
def check_project(entry)
if !entry.nil? && entry.project != @project
raise DmsfAccessError, "Entry project doesn't match current project"
end
end
end

View File

@ -0,0 +1,106 @@
class DmsfStateController < ApplicationController
unloadable
before_filter :find_project
before_filter :authorize
before_filter :find_folder, :only => [:folder_notify_activate, :folder_notify_deactivate]
before_filter :find_file, :only => [:lock_file, :unlock_file, :file_notify_activate, :file_notify_deactivate]
def lock_file
if @file.locked?
flash[:warning] = l(:warning_file_already_locked)
else
@file.lock
flash[:notice] = l(:notice_file_locked)
end
redirect_to :controller => "dmsf", :action => "index", :id => @project, :folder_id => @file.folder
end
def unlock_file
if !@file.locked?
flash[:warning] = l(:warning_file_not_locked)
else
if @file.locks[0].user == User.current || User.current.allowed_to?(:force_file_unlock, @file.project)
@file.unlock
flash[:notice] = l(:notice_file_unlocked)
else
flash[:error] = l(:error_only_user_that_locked_file_can_unlock_it)
end
end
redirect_to :controller => "dmsf", :action => "index", :id => @project, :folder_id => @file.folder
end
def user_pref
@user_pref = DmsfUserPref.for(@project, User.current)
@user_pref.email_notify = params[:email_notify];
@user_pref.save
flash[:notice] = "Your preferences was saved"
redirect_to URI.unescape(params[:current])
end
def folder_notify_activate
if @folder.notification
flash[:warning] = "Folder notifications already activated"
else
@folder.notify_activate
flash[:notice] = "Folder notifications activated"
end
redirect_to :controller => "dmsf", :action => "index", :id => @project, :folder_id => @folder.folder
end
def folder_notify_deactivate
if !@folder.notification
flash[:warning] = "Folder notifications already deactivated"
else
@folder.notify_deactivate
flash[:notice] = "Folder notifications deactivated"
end
redirect_to :controller => "dmsf", :action => "index", :id => @project, :folder_id => @folder.folder
end
def file_notify_activate
if @file.notification
flash[:warning] = "File notifications already activated"
else
@file.notify_activate
flash[:notice] = "File notifications activated"
end
redirect_to :controller => "dmsf", :action => "index", :id => @project, :folder_id => @file.folder
end
def file_notify_deactivate
if !@file.notification
flash[:warning] = "File notifications already deactivated"
else
@file.notify_deactivate
flash[:notice] = "File notifications deactivated"
end
redirect_to :controller => "dmsf", :action => "index", :id => @project, :folder_id => @file.folder
end
private
def find_project
@project = Project.find(params[:id])
end
def find_folder
@folder = DmsfFolder.find(params[:folder_id]) if params.keys.include?("folder_id")
check_project(@folder)
rescue DmsfAccessError
render_403
end
def find_file
check_project(@file = DmsfFile.find(params[:file_id]))
rescue DmsfAccessError
render_403
end
def check_project(entry)
if !entry.nil? && entry.project != @project
raise DmsfAccessError, "Entry project doesn't match current project"
end
end
end

View File

@ -0,0 +1,3 @@
class DmsfAccessError < StandardError
end

View File

@ -0,0 +1,3 @@
class DmsfContentError < StandardError
end

View File

@ -0,0 +1,26 @@
require "tmpdir"
module DmsfHelper
def self.temp_dir
Dir.tmpdir
end
def self.temp_filename(filename)
filename = sanitize_filename(filename)
timestamp = DateTime.now.strftime("%y%m%d%H%M%S")
while File.exist?(File.join(temp_dir, "#{timestamp}_#{filename}"))
timestamp.succ!
end
"#{timestamp}_#{filename}"
end
def self.sanitize_filename(filename)
# get only the filename, not the whole path
just_filename = File.basename(filename.gsub('\\\\', '/'))
# Finally, replace all non alphanumeric, hyphens or periods with underscore
just_filename.gsub(/[^\w\.\-]/,'_')
end
end

View File

@ -0,0 +1,59 @@
class DmsfUpload
attr_accessor :name
attr_accessor :disk_filename
attr_reader :size
attr_accessor :mime_type
attr_accessor :title
attr_accessor :description
attr_accessor :comment
attr_accessor :major_version
attr_accessor :minor_version
attr_accessor :locked
def disk_file
"#{DmsfHelper.temp_dir}/#{self.disk_filename}"
end
def self.create_from_uploaded_file(project, folder, uploaded_file)
uploaded = {
"disk_filename" => DmsfHelper.temp_filename(uploaded_file.original_filename),
"content_type" => uploaded_file.content_type.to_s,
"original_filename" => uploaded_file.original_filename,
}
File.open("#{DmsfHelper.temp_dir}/#{uploaded["disk_filename"]}", "wb") do |f|
while (buffer = uploaded_file.read(8192))
f.write(buffer)
end
end
DmsfUpload.new(project, folder, uploaded)
end
def initialize(project, folder, uploaded)
@name = uploaded["original_filename"]
dmsf_file = DmsfFile.find_file_by_name(project, folder, @name)
@disk_filename = uploaded["disk_filename"]
@mime_type = uploaded["content_type"]
@size = File.size(disk_file)
if dmsf_file.nil? || dmsf_file.last_revision.nil?
@title = DmsfFileRevision.filename_to_title(@name)
@description = nil
@major_version = 0
@minor_version = 0
else
last_revision = dmsf_file.last_revision
@title = last_revision.title
@description = last_revision.description
@major_version = last_revision.major_version
@minor_version = last_revision.minor_version
end
@locked = !dmsf_file.nil? && dmsf_file.locked_for_user?
end
end

41
app/helpers/dmsf_zip.rb Normal file
View File

@ -0,0 +1,41 @@
require 'zip/zip'
require 'zip/zipfilesystem'
class DmsfZip
attr_reader :file_count
def initialize()
@zip = Tempfile.new(["dmsf_zip",".zip"])
@zip_file = Zip::ZipOutputStream.new(@zip.path)
@file_count = 0
end
def finish
@zip_file.close unless @zip_file.nil?
@zip.path unless @zip.nil?
end
def close
@zip_file.close unless @zip_file.nil?
@zip.close unless @zip.nil?
end
def add_file(file)
@zip_file.put_next_entry(file.dmsf_path_str)
File.open(file.last_revision.disk_file, "rb") do |f|
buffer = ""
while (buffer = f.read(8192))
@zip_file.write(buffer)
end
end
@file_count += 1
end
def add_folder(folder)
@zip_file.put_next_entry(folder.dmsf_path_str + "/")
folder.subfolders.each { |subfolder| self.add_folder(subfolder) }
folder.files.each { |file| self.add_file(file) }
end
end

257
app/models/dmsf_file.rb Normal file
View File

@ -0,0 +1,257 @@
begin
require 'xapian'
$xapian_bindings_available = true
rescue LoadError
Rails.logger.info "REDMAIN_XAPIAN ERROR: No Ruby bindings for Xapian installed !!. PLEASE install Xapian search engine interface for Ruby."
$xapian_bindings_available = false
end
class DmsfFile < ActiveRecord::Base
unloadable
belongs_to :project
belongs_to :folder, :class_name => "DmsfFolder", :foreign_key => "dmsf_folder_id"
has_many :revisions, :class_name => "DmsfFileRevision", :foreign_key => "dmsf_file_id",
:order => "major_version DESC, minor_version DESC, updated_at DESC",
:conditions => { :deleted => false }
has_many :locks, :class_name => "DmsfFileLock", :foreign_key => "dmsf_file_id",
:order => "updated_at DESC"
belongs_to :deleted_by_user, :class_name => "User", :foreign_key => "deleted_by_user_id"
validates_presence_of :name
validate_on_create :validates_name_uniqueness
def validates_name_uniqueness
errors.add(:name, "has already been taken") if
!DmsfFile.find_file_by_name(self.project, self.folder, self.name).nil?
end
acts_as_event :title => Proc.new {|o| "#{o.name}"},
:description => Proc.new {|o| "#{o.last_revision.title} - #{o.last_revision.description}" },
:url => Proc.new {|o| {:controller => "dmsf", :action => "download_file", :id => o.project, :file_id => o}},
:datetime => Proc.new {|o| o.updated_at },
:author => Proc.new {|o| o.last_revision.user }
def self.storage_path
storage_dir = Setting.plugin_redmine_dmsf["dmsf_storage_directory"].strip
if !File.exists?(storage_dir)
Dir.mkdir(storage_dir)
end
storage_dir
end
def self.search(tokens, projects=nil, options={})
tokens = [] << tokens unless tokens.is_a?(Array)
projects = [] << projects unless projects.nil? || projects.is_a?(Array)
find_options = {:include => [:project,:revisions]}
find_options[:order] = "dmsf_files.updated_at " + (options[:before] ? 'DESC' : 'ASC')
limit_options = {}
limit_options[:limit] = options[:limit] if options[:limit]
if options[:offset]
limit_options[:conditions] = "(dmsf_files.updated_at " + (options[:before] ? '<' : '>') + "'#{connection.quoted_date(options[:offset])}')"
end
columns = ["dmsf_files.name","dmsf_file_revisions.title", "dmsf_file_revisions.description"]
columns = ["dmsf_file_revisions.title"] if options[:titles_only]
token_clauses = columns.collect {|column| "(LOWER(#{column}) LIKE ?)"}
sql = (['(' + token_clauses.join(' OR ') + ')'] * tokens.size).join(options[:all_words] ? ' AND ' : ' OR ')
find_options[:conditions] = [sql, * (tokens.collect {|w| "%#{w.downcase}%"} * token_clauses.size).sort]
project_conditions = []
project_conditions << (Project.allowed_to_condition(User.current, :view_dmsf_files))
project_conditions << "project_id IN (#{projects.collect(&:id).join(',')})" unless projects.nil?
results = []
results_count = 0
with_scope(:find => {:conditions => [project_conditions.join(' AND ') + " AND #{DmsfFile.table_name}.deleted = :false", {:false => false}]}) do
with_scope(:find => find_options) do
results_count = count(:all)
results = find(:all, limit_options)
end
end
if !options[:titles_only] && $xapian_bindings_available
database = Xapian::Database.new(Setting.plugin_redmine_dmsf["dmsf_index_database"].strip)
enquire = Xapian::Enquire.new(database)
queryString = tokens.join(' ')
qp = Xapian::QueryParser.new()
stemmer = Xapian::Stem.new(Setting.plugin_redmine_dmsf['dmsf_stemming_lang'].strip)
qp.stemmer = stemmer
qp.database = database
case Setting.plugin_redmine_dmsf['dmsf_stemming_strategy'].strip
when "STEM_NONE" then qp.stemming_strategy = Xapian::QueryParser::STEM_NONE
when "STEM_SOME" then qp.stemming_strategy = Xapian::QueryParser::STEM_SOME
when "STEM_ALL" then qp.stemming_strategy = Xapian::QueryParser::STEM_ALL
end
if options[:all_words]
qp.default_op = Xapian::Query::OP_AND
else
qp.default_op = Xapian::Query::OP_OR
end
query = qp.parse_query(queryString)
enquire.query = query
matchset = enquire.mset(0, 1000)
if !matchset.nil?
matchset.matches.each {|m|
docdata = m.document.data{url}
dochash = Hash[*docdata.scan(/(url|sample|modtime|type|size)=\/?([^\n\]]+)/).flatten]
filename = dochash["url"]
if !filename.nil?
dmsf_attrs = filename.split("_")
next unless results.select{|f| f.id.to_s == dmsf_attrs[1]}.empty?
find_conditions = DmsfFile.merge_conditions(limit_options[:conditions], :id => dmsf_attrs[1] )
dmsf_file = DmsfFile.find(:first, :conditions => find_conditions )
if !dmsf_file.nil?
if options[:offset]
if options[:before]
next if dmsf_file.updated_at < options[:offset]
else
next if dmsf_file.updated_at > options[:offset]
end
end
allowed = User.current.allowed_to?(:view_dmsf_files, dmsf_file.project)
project_included = false
project_included = true if projects.nil?
if !project_included
projects.each {|x|
project_included = true if x[:id] == dmsf_file.project.id
}
end
if (allowed && project_included)
results.push(dmsf_file)
results_count += 1
end
end
end
}
end
end
[results, results_count]
end
def self.project_root_files(project)
find(:all, :conditions =>
["dmsf_folder_id is NULL and project_id = :project_id and deleted = :deleted",
{:project_id => project.id, :deleted => false}], :order => "name ASC")
end
def self.find_file_by_name(project, folder, name)
if folder.nil?
find(:first, :conditions =>
["dmsf_folder_id is NULL and project_id = :project_id and name = :name and deleted = :deleted",
{:project_id => project.id, :name => name, :deleted => false}])
else
find(:first, :conditions =>
["dmsf_folder_id = :folder_id and project_id = :project_id and name = :name and deleted = :deleted",
{:project_id => project.id, :folder_id => folder.id, :name => name, :deleted => false}])
end
end
def self.from_commited_file(project, folder, commited_file)
file = find_file_by_name(project, folder, commited_file["name"])
if file.nil?
file = DmsfFile.new
file.project = project
file.folder = folder
file.name = commited_file["name"]
file.save
end
return file
end
def new_storage_filename
filename = DmsfHelper.sanitize_filename(self.name)
timestamp = DateTime.now.strftime("%y%m%d%H%M%S")
while File.exist?(File.join(DmsfFile.storage_path, "#{timestamp}_#{self.id}_#{filename}"))
timestamp.succ!
end
"#{timestamp}_#{id}_#{filename}"
end
def locked?
self.locks.empty? ? false : self.locks[0].locked
end
def locked_for_user?
self.locked? && self.locks[0].user != User.current
end
def lock
lock = DmsfFileLock.new
lock.file = self
lock.user = User.current
lock.locked = true
lock.save
self.reload
return lock
end
def unlock
lock = DmsfFileLock.new
lock.file = self
lock.user = User.current
lock.locked = false
lock.save
self.reload
return lock
end
def last_revision
self.revisions.empty? ? nil : self.revisions[0]
end
def dmsf_path
path = self.folder.nil? ? [] : self.folder.dmsf_path
path.push(self)
path
end
def dmsf_path_str
path = self.dmsf_path
string_path = path.map { |element| element.name }
string_path.join("/")
end
def notify?
return true if self.notification
return true if folder && folder.notify?
return false
end
def notify_deactivate
self.notification = false
self.save!
end
def notify_activate
self.notification = true
self.save!
end
def display_name
#if self.name.length > 33
# extension = File.extname(self.name)
# return self.name[0, self.name.length - extension.length][0, 25] + "..." + extension
#else
return self.name
#end
end
end

View File

@ -0,0 +1,5 @@
class DmsfFileLock < ActiveRecord::Base
unloadable
belongs_to :file, :class_name => "DmsfFile", :foreign_key => "dmsf_file_id"
belongs_to :user
end

View File

@ -0,0 +1,213 @@
class DmsfFileRevision < ActiveRecord::Base
unloadable
belongs_to :file, :class_name => "DmsfFile", :foreign_key => "dmsf_file_id"
belongs_to :source_revision, :class_name => "DmsfFileRevision", :foreign_key => "source_dmsf_file_revision_id"
belongs_to :user
belongs_to :folder, :class_name => "DmsfFolder", :foreign_key => "dmsf_folder_id"
belongs_to :deleted_by_user, :class_name => "User", :foreign_key => "deleted_by_user_id"
acts_as_event :title => Proc.new {|o| "DMSF updated: #{o.file.dmsf_path_str}"},
:url => Proc.new {|o| {:controller => 'dmsf_detail', :action => 'file_detail', :id => o.file.project, :file_id => o.file}},
:datetime => Proc.new {|o| o.updated_at },
:description => Proc.new {|o| o.comment },
:author => Proc.new {|o| o.user }
acts_as_activity_provider :type => "dmsf_files",
:timestamp => "#{DmsfFileRevision.table_name}.updated_at",
:author_key => "#{DmsfFileRevision.table_name}.user_id",
:permission => :view_dmsf_files,
:find_options => {:select => "#{DmsfFileRevision.table_name}.*",
:joins =>
"INNER JOIN #{DmsfFile.table_name} ON #{DmsfFileRevision.table_name}.dmsf_file_id = #{DmsfFile.table_name}.id " +
"INNER JOIN #{Project.table_name} ON #{DmsfFile.table_name}.project_id = #{Project.table_name}.id",
:conditions => ["#{DmsfFile.table_name}.deleted = :false", {:false => false}]
}
def self.remove_extension(filename)
filename[0, (filename.length - File.extname(filename).length)]
end
def self.filename_to_title(filename)
remove_extension(filename).gsub(/_+/, " ");
end
def self.from_commited_file(dmsf_file, commited_file)
revision = DmsfFileRevision.new
commited_disk_filename = commited_file["disk_filename"]
commited_disk_filepath = "#{DmsfHelper.temp_dir}/#{commited_disk_filename}"
commited_file["file"] = File.new(commited_disk_filepath, "rb")
commited_file["mime_type"] = Redmine::MimeType.of(commited_disk_filepath)
begin
revision.from_form_post(dmsf_file, commited_file)
ensure
commited_file["file"].close
end
revision.save
File.delete(commited_disk_filepath)
revision
end
def self.from_saved_file(dmsf_file, saved_file)
revision = DmsfFileRevision.new
if !saved_file["file"].nil?
#TODO: complete this for file renaming
#dmsf_file.name = saved_file["file"].original_filename
#dmsf_file.save
saved_file["mime_type"] = Redmine::MimeType.of(saved_file["file"].original_filename)
end
revision.from_form_post(dmsf_file, saved_file)
revision.save!
end
def version
"#{self.major_version}.#{self.minor_version}"
end
def project
self.file.project
end
def disk_file
"#{DmsfFile.storage_path}/#{self.disk_filename}"
end
def detect_content_type
content_type = self.mime_type
if content_type.blank?
content_type = Redmine::MimeType.of(self.disk_filename)
end
content_type.to_s
end
def from_form_post(file, posted, source_revision = nil)
source_revision = file.last_revision if source_revision.nil?
self.file = file
self.source_revision = source_revision
if source_revision.nil?
from_form_post_create(posted)
else
from_form_post_existing(posted, source_revision)
end
self.user = User.current
self.title = posted["title"]
self.description = posted["description"]
self.comment = posted["comment"]
self
end
def clone
new_revision = DmsfFileRevision.new
new_revision.file = self.file
new_revision.disk_filename = self.disk_filename
new_revision.size = self.size
new_revision.mime_type = self.mime_type
new_revision.title = self.title
new_revision.description = self.description
new_revision.workflow = self.workflow
new_revision.major_version = self.major_version
new_revision.minor_version = self.minor_version
new_revision.source_revision = self
new_revision.user = User.current
return new_revision
end
def set_workflow(workflow)
if User.current.allowed_to?(:file_approval, self.file.project)
self.workflow = workflow
else
if self.source_revision.nil?
self.workflow = workflow == 2 ? 1 : workflow
else
if workflow == 2 || self.source_revision.workflow == 1 || self.source_revision.workflow == 2
self.workflow = 1
else
self.workflow = workflow
end
end
end
end
def display_title
#if self.title.length > 35
# return self.title[0, 30] + "..."
#else
return self.title
#end
end
private
def from_form_post_create(posted)
if posted["file"].nil?
raise DmsfContentError, "First revision require uploaded file"
else
copy_file_content(posted)
self.major_version = case posted["version"]
when "major" then 1
else 0
end
self.minor_version = case posted["version"]
when "major" then 0
else 1
end
end
set_workflow(posted["workflow"])
end
def from_form_post_existing(posted, source_revision)
self.major_version = source_revision.major_version
self.minor_version = source_revision.minor_version
if posted["file"].nil?
self.disk_filename = source_revision.disk_filename
self.minor_version = case posted["version"]
when "same" then self.minor_version
when "major" then 0
else self.minor_version + 1
end
self.mime_type = source_revision.mime_type
self.size = source_revision.size
else
copy_file_content(posted)
self.minor_version = case posted["version"]
when "major" then 0
else self.minor_version + 1
end
end
self.major_version = case posted["version"]
when "major" then self.major_version + 1
else self.major_version
end
set_workflow(posted["workflow"])
end
def copy_file_content(posted)
self.disk_filename = self.file.new_storage_filename
File.open(self.disk_file, "wb") do |f|
buffer = ""
while (buffer = posted["file"].read(8192))
f.write(buffer)
end
end
self.mime_type = posted["mime_type"]
self.size = File.size(self.disk_file)
#TODO: move this to better place
self.name = self.file.name
self.folder = self.file.folder
end
end

60
app/models/dmsf_folder.rb Normal file
View File

@ -0,0 +1,60 @@
class DmsfFolder < ActiveRecord::Base
unloadable
belongs_to :project
belongs_to :folder, :class_name => "DmsfFolder", :foreign_key => "dmsf_folder_id"
has_many :subfolders, :class_name => "DmsfFolder", :foreign_key => "dmsf_folder_id", :order => "name ASC"
has_many :files, :class_name => "DmsfFile", :foreign_key => "dmsf_folder_id", :order => "name ASC",
:conditions => { :deleted => false }
belongs_to :user
validates_presence_of :name
validates_uniqueness_of :name, :scope => [:dmsf_folder_id, :project_id]
def self.project_root_folders(project)
find(:all, :conditions =>
["dmsf_folder_id is NULL and project_id = :project_id", {:project_id => project.id}], :order => "name ASC")
end
def self.create_from_params(project, parent_folder, params)
new_folder = DmsfFolder.new(params)
new_folder.project = project
new_folder.folder = parent_folder
new_folder.user = User.current
new_folder.save
new_folder
end
def dmsf_path
folder = self
path = []
while !folder.nil?
path.unshift(folder)
folder = folder.folder
end
path
end
def dmsf_path_str
path = self.dmsf_path
string_path = path.map { |element| element.name }
string_path.join("/")
end
def notify?
return true if self.notification
return true if folder && folder.notify?
return false
end
def notify_deactivate
self.notification = false
self.save!
end
def notify_activate
self.notification = true
self.save!
end
end

86
app/models/dmsf_mailer.rb Normal file
View File

@ -0,0 +1,86 @@
require "mailer"
class DmsfMailer < Mailer
def files_updated(user, files)
project = files[0].project
files = files.select { |file| file.notify? }
redmine_headers "Project" => project.identifier
recipients get_notify_user_emails(user, files)
subject project.name + ": Dmsf files updated"
body :user => user, :files => files, :project => project
# TODO: correct way should be render_multipart("files_updated", body), but other plugin broke it
render_multipart(File.expand_path(File.dirname(__FILE__) + "/../views/dmsf_mailer/" + "files_updated"), body)
end
def files_deleted(user, files)
project = files[0].project
files = files.select { |file| file.notify? }
redmine_headers "Project" => project.identifier
recipients get_notify_user_emails(user, files)
subject project.name + ": Dmsf files deleted"
body :user => user, :files => files, :project => project
# TODO: correct way should be render_multipart("files_updated", body), but other plugin broke it
render_multipart(File.expand_path(File.dirname(__FILE__) + "/../views/dmsf_mailer/" + "files_deleted"), body)
end
def send_documents(user, email_to, email_cc, email_subject, zipped_content, email_plain_body)
recipients email_to
if !email_cc.strip.blank?
cc email_cc
end
subject email_subject
from user.mail
content_type "multipart/mixed"
part "text/plain" do |p|
p.body = email_plain_body
end
zipped_content_data = open(zipped_content, "rb") {|io| io.read }
attachment :content_type => "application/zip",
:filename => "Documents.zip",
:body => zipped_content_data
end
private
def get_notify_user_emails(user, files)
if files.empty?
return []
end
project = files[0].project
notify_members = project.members
notify_members = notify_members.select do |notify_member|
notify_user = notify_member.user
if notify_user.pref[:no_self_notified] && notify_user == user
false
else
dmsf_user_prefs = DmsfUserPref.for(project, notify_user)
if dmsf_user_prefs.email_notify.nil?
case notify_user.mail_notification
when 'all'
true
when 'selected'
notify_member.mail_notification?
when 'only_my_events'
notify_user.allowed_to?(:file_approval, project) ? true : false
when 'only_owner'
notify_user.allowed_to?(:file_approval, project) ? true : false
else
false
end
else dmsf_user_prefs.email_notify
end
end
end
notify_members.collect {|m| m.user.mail }
end
end

View File

@ -0,0 +1,19 @@
class DmsfUserPref < ActiveRecord::Base
unloadable
belongs_to :project
belongs_to :user
validates_presence_of :project, :user
validates_uniqueness_of :user_id, :scope => [:project_id]
def self.for(project, user)
user_pref = find(:first, :conditions =>
["project_id = :project_id and user_id = :user_id",
{:project_id => project.id, :user_id => user.id}])
user_pref = DmsfUserPref.new({:project_id => project.id, :user_id => user.id,
:email_notify => nil}) if user_pref.nil?
return user_pref
end
end

View File

@ -0,0 +1,9 @@
<%= link_to("Documents", {:controller => "dmsf", :action => "index", :id=> @project }) %>
<% path.each do |path_element| %>
/
<%= link_to(h(path_element.name), {:controller => "dmsf", :action => "index", :id=> @project, :folder_id => path_element}) %>
<% if path_element.notification %>
<%= image_tag("notify.png", :plugin => "redmine_dmsf", :alt => "Not. act.",
:title => "Notifications active") %>
<% end %>
<% end %>

View File

@ -0,0 +1,15 @@
<div class="box">
<% form_tag({:controller => "dmsf_state", :action => "user_pref", :id => @project, :current => URI.escape(request.url)},
:method=>:post) do %>
<div style="padding-top: 0px; float: right;">
<%= submit_tag("Save", :title => "Save preferences", :style => "font-size: 0.9em;", :tabindex => 10) %>
</div>
<h3>Your <%= l(:dmsf) %> preferences for project</h3>
<div>
Notifications:
<%= select_tag("email_notify",
options_for_select([["Default", nil], ["Activated", true], ["Deactivated", false]],
:selected => DmsfUserPref.for(@project, User.current).email_notify), :tabindex => 1) %>
</div>
<% end %>
</div>

View File

@ -0,0 +1,43 @@
<% html_title("DMSF") %>
<div class="contextual">
</div>
<% path = @folder.nil? ? [] : @folder.dmsf_path %>
<h2>
<%= render(:partial => 'path', :locals => {:path => path}) %>
</h2>
<h3>Send documents by email</h3>
<% form_tag({:action => "email_entries_send", :id => @project, :folder_id => @folder},
{ :method=>:post, :class => "tabular"}) do %>
<div class="box">
<p>
<%= label_tag("", "From:") %>
<%= h(User.current.mail) %>
</p>
<p>
<%= label_tag("email[to]", "To:") %>
<%= text_field_tag("email[to]", @email_params["to"], :style => "width: 90%;") %>
</p>
<p>
<%= label_tag("email[cc]", "CC:") %>
<%= text_field_tag("email[cc]", @email_params["cc"], :style => "width: 90%;") %>
</p>
<p>
<%= label_tag("email[subject]", "Subject:") %>
<%= text_field_tag("email[subject]", @email_params["subject"], :style => "width: 90%;") %>
</p>
<p>
<%= label_tag("", "Documents:") %>
<strong>Documents.zip</strong>
<%= hidden_field_tag("email[zipped_content]", @email_params["zipped_content"]) %>
</p>
<p>
<%= label_tag("email[body]", "Body:") %>
<%= text_area_tag("email[body]", @email_params["body"], :rows=> "20", :style => "width: 90%;") %>
</p>
<p><%= submit_tag("Send") %></p>
</div>
<% end %>

View File

@ -0,0 +1,305 @@
<% html_title("DMSF") %>
<div class="contextual">
<% if User.current.allowed_to?(:folder_manipulation, @project) %>
<% unless @folder.nil? %>
<%= link_to("Details",
{:controller => "dmsf_detail", :action => "folder_detail", :id => @project, :folder_id => @folder}) %> |
<% end %>
<% form_for(DmsfFolder.new, :url => {:controller => "dmsf_detail", :action => "create_folder", :id => @project, :folder_id => @folder}, :html => {:method=>:post}) do |f| %>
Title: <%= f.text_field(:name) %><%= f.submit("Create folder") %>
<% end %>
<% end %>
</div>
<% path = @folder.nil? ? [] : @folder.dmsf_path %>
<h2>
<%= render(:partial => 'path', :locals => {:path => path}) %>
</h2>
<div class="wiki">
<%= textilizable(@folder.description) unless @folder.nil? %>
</div>
<%
form_tag({:action => "entries_operation", :id => @project, :folder_id => @folder}, :method => :post,
:class => "dmfs_entries", :id => "Entries") do
%>
<%= hidden_field_tag("action") %>
<table class="list entries" id="browser">
<thead>
<tr id="root">
<th style="width: 17px; padding: 2px; text-align: left;">
<input id="check_all_entries" title="Check/Uncheck all for zip download or email" type="checkbox" />
</th>
<%= sort_header_tag("title", :caption => "Title") %>
<%= sort_header_tag("size", :caption => "Size") %>
<%= sort_header_tag("modified", :caption => "Modified") %>
<%= sort_header_tag("version", :caption => "Ver.") %>
<%= sort_header_tag("author", :caption => "Author") %>
<th></th>
</tr>
</thead>
<tbody>
<% @subfolders.each do |subfolder| %>
<tr class="dir">
<td class="size"><%= check_box_tag("subfolders[]", subfolder.id, false, :title => "Check for multi download or email") %></td>
<td class="filename">
<%= link_to(h(subfolder.name),
{:action => "index", :folder_id => subfolder},
:class => "icon icon-folder") %>
</td>
<td class="size">-</td>
<td class="modified">-</td>
<td class="version">-</td>
<td class="author">-</td>
<td class="actions">
<div class="right_icon_box">
<% if User.current.allowed_to?(:folder_manipulation, @project) %>
<%= link_to(image_tag("delete.png", :plugin => "redmine_dmsf"),
{:controller => "dmsf_detail", :action => "delete_folder", :id => @project,
:folder_id => @folder, :delete_folder_id => subfolder}, :class => "delete-link",
:title => "Delete") %>
<% end %>
<% if User.current.allowed_to?(:file_approval, @project) %>
<% if subfolder.notification %>
<%= link_to(image_tag("notify.png", :plugin => "redmine_dmsf"),
{:controller => "dmsf_state", :action => "folder_notify_deactivate", :id => @project,
:folder_id => subfolder}, :title => "Notifications active: Deactivate") %>
<% else %>
<%= link_to(image_tag("notifynot.png", :plugin => "redmine_dmsf"),
{:controller => "dmsf_state", :action => "folder_notify_activate", :id => @project,
:folder_id => subfolder}, :title => "Notifications not active: Activate") %>
<% end %>
<% end %>
</div><br class="clear" />
</td>
</tr>
<% end %>
<% @files.each do |file| %>
<tr class="file">
<td class="size"><%= check_box_tag("files[]", file.id, false, :title => "Check for zip download or email") %></td>
<td class="filename">
<%= link_to(h(file.last_revision.display_title),
{:action => "download_file", :id => @project, :file_id => file},
:class => "icon icon-file #{Redmine::MimeType.css_class_of(file.name)}",
:title => "#{h(file.last_revision.title)} version #{file.last_revision.version} download") %>
<div class="filename">
(<%= h(file.display_name) %>)</div><br class="clear" />
</td>
<td class="size"><%= number_to_human_size(file.last_revision.size) unless file.last_revision.nil? %></td>
<td class="modified">
<%= file.last_revision.updated_at.strftime("%Y-%m-%d %H:%M") unless file.last_revision.nil? %>
<% if file.locked_for_user? %>
<%= link_to(image_tag("locked.png", :plugin => "redmine_dmsf"),
{:controller => "users", :action => "show", :id => file.locks[0].user },
:title => "Locked by " + file.locks[0].user.to_s) %>
<% else
if file.locked? %>
<%= image_tag("lockedbycurrent.png", :title => "Locked by you",
:plugin => "redmine_dmsf") %>
<% end %>
<% end %>
</td>
<td class="version">
<%= file.last_revision.version unless file.last_revision.nil? %>
<% case file.last_revision.workflow
when 1 then %><%= image_tag("waitingforapproval.png", :title => "Waiting for Approval",
:plugin => "redmine_dmsf") %>
<% when 2 then %><%= image_tag("approved.png", :title => "Approved",
:plugin => "redmine_dmsf") %>
<% end %>
</td>
<td class="author"><%= h(file.last_revision.user) unless file.last_revision.nil? %></td>
<td class="actions">
<div class="right_icon_box">
<%= link_to(image_tag("filedetails.png", :plugin => "redmine_dmsf"),
{:controller => "dmsf_detail", :action => "file_detail", :id => @project, :file_id => file },
:title => "#{h(file.last_revision.title)} details") %>
&nbsp;
<% unless file.locked_for_user? && !User.current.allowed_to?(:force_file_unlock, @project)%>
<% if file.locked? %>
<%= link_to(image_tag("unlock.png", :plugin => "redmine_dmsf"),
{:controller => "dmsf_state", :action => "unlock_file", :id => @project, :file_id => file },
:title => "Unlock to allow changes for other members") %>
<% else %>
<%= link_to(image_tag("lock.png", :plugin => "redmine_dmsf"),
{:controller => "dmsf_state", :action => "lock_file", :id => @project, :file_id => file },
:title => "Lock to prevent changes for other members") %>
<% end %>
&nbsp;
<% end %>
<% if User.current.allowed_to?(:file_manipulation, @project) %>
<%= link_to(image_tag("delete.png", :plugin => "redmine_dmsf"),
{:controller => "dmsf_detail", :action => "delete_file", :id => @project,
:file_id => file}, :class => "delete-link", :title => "Delete") %>
<% end %>
<% if User.current.allowed_to?(:file_approval, @project) %>
<% if file.notification %>
<%= link_to(image_tag("notify.png", :plugin => "redmine_dmsf"),
{:controller => "dmsf_state", :action => "file_notify_deactivate", :id => @project,
:file_id => file}, :title => "Notifications active: Deactivate") %>
<% else %>
<%= link_to(image_tag("notifynot.png", :plugin => "redmine_dmsf"),
{:controller => "dmsf_state", :action => "file_notify_activate", :id => @project,
:file_id => file}, :title => "Notifications not active: Activate") %>
<% end %>
<% end %>
</div><br class="clear" />
</td>
</tr>
<% end %>
</tbody>
</table>
<div class="controls">
<%= submit_tag("Download", :title => "Download checked in zip archive", :name => "download_entries") %>
<!-- <button type="button" id="EntriesDeleteButton" title="Delete checked">Delete</button> -->
<%= submit_tag("Email", :title => "Send checked by email", :name => "email_entries") %>
</div>
<br style="clear: both"/>
<% end %>
<div class="box">
<h3>File Upload</h3>
<% form_tag({:controller => "dmsf_detail", :action => "upload_files", :id => @project, :folder_id => @folder},
:id => "uploadform", :method=>:post, :multipart => true) do %>
<div style="padding-bottom: 6px; line-height: 1.4em; font-size: 0.9em;">
<div class="upload_select">
File size: <select id="uploader_select"><option value="1">&lt; 100 MB</option><option value="2">> 100 MB</option></select>
</div>
<span>There can be uploaded maximum of 20 files at once. To upload files greater than 2GB you must have 64b browser.</span>
<br style="clear: both" />
</div>
<div id="uploader">
<p>
<span id="uploaded_files_fields">
<%= file_field_tag("uploaded_files[1]", :size => 30, :id => nil) %>
</span>
<br />
<small><%= link_to(l(:label_add_another_file), "#", :onclick => "dmsfAddFileField(); return false;" ) %>
(<%= l(:label_max_size) %>: <%= number_to_human_size(Setting.attachment_max_size.to_i.kilobytes) %>)
</small>
</p>
<%= submit_tag("Upload") %>
</div>
<% end %>
</div>
<%= render(:partial => 'user_pref') %>
<% content_for :header_tags do %>
<%= stylesheet_link_tag "dmsf", :plugin => "redmine_dmsf" %>
<%= stylesheet_link_tag "https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.10/themes/base/jquery-ui.css" %>
<%= stylesheet_link_tag "plupload/jquery.ui.plupload.css", :plugin => "redmine_dmsf" %>
<%= javascript_include_tag "https://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js" %>
<%= javascript_include_tag "https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.10/jquery-ui.min.js" %>
<%= javascript_include_tag "plupload/gears_init.js", :plugin => "redmine_dmsf" %>
<%= javascript_include_tag "plupload/plupload.full.min.js", :plugin => "redmine_dmsf" %>
<%= javascript_include_tag "plupload/jquery.ui.plupload.js", :plugin => "redmine_dmsf" %>
<script type="text/javascript">
jQuery.noConflict();
var originalUploaderContent;
jQuery(document).ready(function() {
jQuery("a.delete-link").click(function(event) {
if(!window.confirm("<%= l(:question_do_you_really_want_to_delete_this_entry) %>")) event.preventDefault();
})
jQuery("#check_all_entries").click(function(event) {
var $this = jQuery(this);
if($this.attr("checked")) {
jQuery("input[type=checkbox]", jQuery("#browser > tbody")).attr("checked", true);
} else {
jQuery("input[type=checkbox]", jQuery("#browser > tbody")).attr("checked", false);
}
})
jQuery("#EntriesDeleteButton").click(function(event) {
if(!window.confirm("Really?")) {
event.preventDefault();
return;
}
var operation = jQuery("input[name=EntriesOperation]");
operation.val("delete");
var entries = jQuery("#Entries");
entries.submit();
})
var uploader = jQuery('#uploader');
originalUploaderContent = uploader.html();
jQuery('#uploader_select').change(function() {
if(jQuery(this).val() == 2) {
uploader.html(originalUploaderContent);
} else {
initPlUploader(uploader);
}
});
initPlUploader(uploader);
});
function initPlUploader(uploader) {
uploader.html("<div></div>");
uploader = jQuery("div",uploader);
uploader.plupload({
runtimes : 'gears,html5,flash,html4',
url : '<%= url_for({:controller => "dmsf_detail", :action => "upload_file", :id => @project, :folder_id => @folder}) %>',
max_file_size : '100mb',
max_file_count: 20, // user can add no more then 20 files at a time
multipart: true,
multipart_params : {authenticity_token : jQuery("input[name=authenticity_token]").val()},
// Rename files by clicking on their titles
rename: true,
// Flash settings
flash_swf_url : '<%= Engines::RailsExtensions::AssetHelpers.plugin_asset_path("redmine_dmsf", "javascripts", "plupload/plupload.flash.swf") %>'
});
jQuery(".plupload_scroll",uploader).resizable({
handles: "s"
});
var pluploader = uploader.plupload('getUploader');
pluploader.bind('FileUploaded', function(pluploader, file, response) {
var responseObject = jQuery.parseJSON(response.response);
var disk_filename_input = jQuery("<input/>").attr("type","hidden")
.attr("name","uploaded[" + dmsfFileFieldCount + "][disk_filename]")
.val(responseObject.disk_filename);
uploader.append(disk_filename_input);
var content_type_input = jQuery("<input/>").attr("type","hidden")
.attr("name","uploaded[" + dmsfFileFieldCount + "][content_type]")
.val(responseObject.content_type);
uploader.append(content_type_input);
var original_filename_input = jQuery("<input/>").attr("type","hidden")
.attr("name","uploaded[" + dmsfFileFieldCount + "][original_filename]")
.val(responseObject.original_filename);
uploader.append(original_filename_input);
if(pluploader.total.uploaded == pluploader.files.length) jQuery('#uploadform').submit();
else dmsfFileFieldCount++;
});
}
var dmsfFileFieldCount = 1;
function dmsfAddFileField() {
if (dmsfFileFieldCount >= 10) return false
dmsfFileFieldCount++;
var f = document.createElement("input");
f.type = "file";
f.name = "uploaded_files[" + dmsfFileFieldCount + "]";
f.size = 30;
var p = document.getElementById("uploaded_files_fields");
p.appendChild(document.createElement("br"));
p.appendChild(f);
}
</script>
<% end %>

View File

@ -0,0 +1 @@
<p class="warning">File locked!</p>

View File

@ -0,0 +1,68 @@
<%
disabled_workflow = []
selected_workflow = nil
if !User.current.allowed_to?(:file_approval, @project)
disabled_workflow << 2
current_workflow = @file.last_revision.workflow
if current_workflow == 1 || current_workflow == 2
disabled_workflow << nil
selected_workflow = 1
end
else
selected_workflow = @file.last_revision.workflow
end
form_tag({:action => "save_file", :id => @project, :file_id => @file},
:method=>:post, :multipart => true, :class => "tabular") do
%>
<div class="box dmsf_detail">
<fieldset class="tabular">
<legend>Create New Revision <a href="#" id="newRevisionFormContentToggle">[-]</a></legend>
<div id="newRevisionFormContent">
<div class="splitcontentleft">
<p>
<%= label_tag("file[title]", "Title:") %>
<%= text_field_tag("file[title]", @file.last_revision.title, :size => "32") %>
</p>
<p>
<%= label_tag("file[description]", "Description:") %>
<%= text_area_tag("file[description]", @file.last_revision.description, :rows=> "6") %>
</p>
<p>
<%= label_tag("file[file]", "New content:") %>
<%= file_field_tag("file[file]", :size => 30, :id => "fileFileUpload") %>
<br />
<small>
(<%= l(:label_max_size) %>: <%= number_to_human_size(Setting.attachment_max_size.to_i.kilobytes) %>)
</small>
</p>
<p>
<%= label_tag("file[version]_minor", "Version:") %>
<%= radio_button_tag("file[version]", "same", true, :id => "fileSameVersionRadio") %>
<%= @file.last_revision.major_version %>.<%= @file.last_revision.minor_version %> Same<br />
<%= radio_button_tag("file[version]", "minor", false, :id => "fileMinorVersionRadio") %>
<%= @file.last_revision.major_version %>.<%= @file.last_revision.minor_version + 1 %> Minor<br />
<%= radio_button_tag("file[version]", "major") %>
<%= @file.last_revision.major_version + 1 %>.0 Major<br />
</p>
<p>
<%= label_tag("file[workflow]", "Workflow:") %>
<%= select_tag("file[workflow]",
options_for_select([["None", nil], ["Waiting for approval", 1], ["Approved", 2]],
:selected => selected_workflow, :disabled => disabled_workflow)) %>
</p>
</div>
<div class="splitcontentright">
<p>
<%= label_tag("file[comment]", "Comment:") %>
<%= text_area_tag("file[comment]", "", :rows=> "2") %>
</p>
</div>
<br style="clear: both;"/>
<br />
<%= submit_tag("Create") %>
</div>
</fieldset>
</div>
<% end %>

View File

@ -0,0 +1,38 @@
<div class="box dmsf_upload">
<%= hidden_field_tag("commited_files[#{i}][disk_filename]", upload.disk_filename) %>
<fieldset class="tabular">
<legend><%= h(upload.name) %></legend>
<%= hidden_field_tag("commited_files[#{i}][name]", upload.name) %>
<div class="splitcontentleft">
<p>
<%= label_tag("commited_files[#{i}][title]", "Title:") %>
<%= text_field_tag("commited_files[#{i}][title]", upload.title, :size => "32") %>
</p>
<p>
<%= label_tag("commited_files[#{i}][description]", "Description:") %>
<%= text_area_tag("commited_files[#{i}][description]", upload.description, :rows=> "6") %>
</p>
<p>
<%= label_tag("commited_files[#{i}][version]_minor", "Version:") %>
<%= radio_button_tag("commited_files[#{i}][version]", "minor", true) %>
<%= upload.major_version %>.<%= upload.minor_version + 1 %><br />
<%= radio_button_tag("commited_files[#{i}][version]", "major") %>
<%= upload.major_version + 1 %>.0<br />
</p>
</div>
<div class="splitcontentright">
<p>
<%= label_tag("commited_files[#{i}][comment]", "Comment:") %>
<%= text_area_tag("commited_files[#{i}][comment]", upload.comment, :rows=> "2") %>
</p>
<p>
<%= label_tag("", "Mime:") %>
<%= h(upload.mime_type) %>
</p>
<p>
<%= label_tag("", "Size:") %>
<%= number_to_human_size(upload.size) %>
</p>
</div>
</fieldset>
</div>

View File

@ -0,0 +1,31 @@
<div class="box dmsf_upload">
<%= hidden_field_tag("commited_files[#{i}][disk_filename]", upload.disk_filename) %>
<fieldset class="tabular">
<legend><%= h(upload.name) %></legend>
<p class="warning">File locked!</p>
<div class="splitcontentleft">
<p>
<%= label_tag("", "Title:") %>
<%= h(upload.title) %>
</p>
<p>
<%= label_tag("", "Description:") %>
<%= h(upload.description) %>
</p>
<p>
<%= label_tag("", "Version:") %>
<%= upload.major_version %>.<%= upload.minor_version %>
</p>
</div>
<div class="splitcontentright">
<p>
<%= label_tag("", "Mime:") %>
<%= h(upload.mime_type) %>
</p>
<p>
<%= label_tag("", "Size:") %>
<%= number_to_human_size(upload.size) %>
</p>
</div>
</fieldset>
</div>

View File

@ -0,0 +1,114 @@
<% html_title("DMSF") %>
<div class="contextual">
</div>
<% path = @file.folder.nil? ? [] : @file.folder.dmsf_path %>
<h2>
<%= render(:partial => "/dmsf/path", :locals => {:path => path}) %>
/
<%= h(@file.last_revision.title) %> <div style="float: right"><%= h(@file.name) %></div>
<% if @file.notification %>
<%= image_tag("notify.png", :plugin => "redmine_dmsf", :alt => "Not. act.",
:title => "Notifications active") %>
<% end %>
</h2>
<% if User.current.allowed_to?(:file_manipulation, @file.project) %>
<% if @file.locked_for_user? %>
<%= render(:partial => 'file_locked') %>
<% else %>
<%= render(:partial => 'file_new_revision') %>
<% end %>
<% end %>
<h3>Revisions</h3>
<% @file.revisions.each do |revision| %>
<div class="box dmsf_detail">
<fieldset class="tabular">
<legend>
<%= revision.source_revision.nil? ? "Created:" : "Changed:" %>
<%= revision.updated_at.strftime("%Y-%m-%d %H:%M:%S") %>
by
<%= h(revision.user) %>
</legend>
<div class="splitcontentleft">
<p>
<%= label_tag("", "Title:") %>
<%= h(revision.title) %>
</p>
<p>
<%= label_tag("", "Description:") %>
<%= h(revision.description) %>
</p>
<p>
<%= label_tag("", "Version:") %>
<%= revision.major_version %>.<%= revision.minor_version %>
</p>
<p>
<%= label_tag("", "Workflow:") %>
<%= case revision.workflow
when 1 then "Waiting for approval"
when 2 then "Approved"
else "None"
end %>
</p>
</div>
<div class="splitcontentright">
<p>
<%= label_tag("", "Comment:") %>
<%= h(revision.comment) %>
</p>
<p>
<%= label_tag("", "Mime:") %>
<%= h(revision.mime_type) %>
</p>
<p>
<%= label_tag("", "Size:") %>
<%= number_to_human_size(revision.size) %>
</p>
</div>
<br style="clear: both;" />
<div>
<%= link_to("Download",
{:controller=>"dmsf", :action => "download_revision", :id => @project, :revision_id => revision}) %>
<% if User.current.allowed_to?(:file_approval, @project) %>
| <%= link_to("Delete",
{:action => "delete_revision", :id => @project, :revision_id => revision},
:class => "delete-link", :title => "Delete revision") %>
<% end %>
</div>
</fieldset>
</div>
<% end %>
<% content_for :header_tags do %>
<%= stylesheet_link_tag "dmsf", :plugin => "redmine_dmsf" %>
<%= javascript_include_tag "https://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js" %>
<script type="text/javascript">
jQuery.noConflict();
jQuery(document).ready(function() {
jQuery("a.delete-link").click(function(event) {
if(!window.confirm("<%= l(:question_do_you_really_want_to_delete_this_revision) %>")) event.preventDefault();
})
jQuery("#fileFileUpload").change(function() {
if(jQuery("input[name=\"file[version]\"]:checked").val() == "same") {
jQuery("#fileMinorVersionRadio").attr("checked", true);
}
jQuery("#fileSameVersionRadio").attr("disabled", "disabled");
});
jQuery("#newRevisionFormContentToggle").toggle(function() {
jQuery("#newRevisionFormContentToggle").text("[-]");
jQuery("#newRevisionFormContent").show();
}, function() {
jQuery("#newRevisionFormContentToggle").text("[+]");
jQuery("#newRevisionFormContent").hide();
});
jQuery("#newRevisionFormContentToggle").text("[+]");
jQuery("#newRevisionFormContent").hide();
});
</script>
<% end %>

View File

@ -0,0 +1,29 @@
<% html_title("DMSF") %>
<div class="contextual">
</div>
<h2>
<%= render(:partial => "/dmsf/path", :locals => {:path => @folder.dmsf_path}) %>
</h2>
<% form_tag({:action => "save_folder", :id => @project, :folder_id => @folder},
:method=>:post, :multipart => true, :class => "tabular") do %>
<div class="box">
<p>
<%= label_tag("title", "Title:") %>
<%= text_field_tag("title", @folder.name, :size => "32") %>
</p>
<p>
<%= label_tag("description", "Description:") %>
<%= text_area_tag("description", @folder.description, :rows => 15, :class => "wiki-edit") %>
</p>
</div>
<%= submit_tag("Save") %>
<% end %>
<%= wikitoolbar_for "description" %>
<% content_for :header_tags do %>
<%= stylesheet_link_tag "dmsf", :plugin => "redmine_dmsf" %>
<% end %>

View File

@ -0,0 +1,2 @@
{"original_filename":"<%= h(@tempfile.original_filename) %>", "content_type":"<%= h(@tempfile.content_type) %>",
"disk_filename":"<%= h(@disk_filename) %>"}

View File

@ -0,0 +1,41 @@
<% html_title("DMSF") %>
<div class="contextual">
</div>
<% path = @folder.nil? ? [] : @folder.dmsf_path %>
<h2>
<%= render(:partial => '/dmsf/path', :locals => {:path => path}) %>
</h2>
<div class="wiki">
<%= textilizable(@folder.description) unless @folder.nil? %>
</div>
<h3>Uploaded Files</h3>
<%
i = 1
form_tag({:action => "commit_files", :id => @project, :folder_id => @folder},
:method=>:post, :class => "tabular") do
%>
<% @uploads.each do |upload| %>
<% if upload.locked %>
<%= render(:partial => 'upload_file_locked', :locals => {:upload => upload, :i => i}) %>
<% else %>
<%= render(:partial => 'upload_file', :locals => {:upload => upload, :i => i}) %>
<% end %>
<% i += 1 %>
<% end %>
<%= submit_tag("Commit") %>
<% end %>
<% content_for :header_tags do %>
<%= stylesheet_link_tag "dmsf", :plugin => "redmine_dmsf" %>
<%= javascript_include_tag "https://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js" %>
<script type="text/javascript">
jQuery.noConflict();
jQuery(document).ready(function() {
});
</script>
<% end %>

View File

@ -0,0 +1,16 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
</head>
<body>
User <%= @user %> deleted following files in project <%= @project.name %>:
<% @files.each do |file| %>
<p>
<%= h(file.dmsf_path_str) %>
(<%= number_to_human_size(file.last_revision.size) %>),
version: <%= file.last_revision.major_version %>.<%= file.last_revision.minor_version %>
</p>
<% end %>
</body>
</html>

View File

@ -0,0 +1,5 @@
User <%= @user %> deleted following files in project <%= @project.name %>:
<% @files.each do |file| %>
<%= file.dmsf_path_str %> (<%= number_to_human_size(file.last_revision.size) %>), version: <%= file.last_revision.major_version %>.<%= file.last_revision.minor_version %>
<% end %>

View File

@ -0,0 +1,21 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
</head>
<body>
User <%= @user %> actualized following files in project <%= @project.name %>:
<% @files.each do |file| %>
<p>
<%= link_to(h(file.dmsf_path_str),
{:only_path => false, :controller => "dmsf", :action => "download_file", :id => file.project,
:file_id => file}) %>
(<%= number_to_human_size(file.last_revision.size) %>),
version: <%= file.last_revision.major_version %>.<%= file.last_revision.minor_version %>,
<%= link_to("Details",
{:only_path => false, :controller => "dmsf_detail", :action => "file_detail", :id => file.project,
:file_id => file }) %>
</p>
<% end %>
</body>
</html>

View File

@ -0,0 +1,7 @@
User <%= @user %> actualized following files in project <%= @project.name %>:
<% @files.each do |file| %>
<%= file.dmsf_path_str %> (<%= number_to_human_size(file.last_revision.size) %>), version: <%= file.last_revision.major_version %>.<%= file.last_revision.minor_version %>
<%= url_for({:only_path => false, :controller => "dmsf_detail", :action => "file_detail", :id => file.project,
:file_id => file }) %>
<% end %>

View File

@ -0,0 +1,42 @@
<p>
<%=content_tag(:label, "Maximum files download:") %>
<%=text_field_tag "settings[dmsf_max_file_download]", @settings["dmsf_max_file_download"], :size=>10 %><br/>
(<%=l(:label_default)%>: 0)
<br/>
Limits maximum number of files downloaded in zip or sent via email. "0" means unlimited.
</p>
<p>
<%=content_tag(:label, "File storage directory:") %>
<%=text_field_tag "settings[dmsf_storage_directory]", @settings["dmsf_storage_directory"], :size=>50 %><br/>
(<%=l(:label_default)%>: <%="#{RAILS_ROOT}/files/dmsf"%>)
</p>
<p>
<%=content_tag(:label, "Index database:") %>
<%=text_field_tag 'settings[dmsf_index_database]', @settings['dmsf_index_database'], :size=>50 %><br/>
(<%=l(:label_default)%>: <%="#{RAILS_ROOT}/files/dmsf_index"%>)
</p>
<p>
<%=content_tag(:label, "Stemming Language:") %>
<%=text_field_tag 'settings[dmsf_stemming_lang]', @settings['dmsf_stemming_lang'] %><br/>
(<%=l(:label_default)%>: english )<br/>
<br/>
Possible values: danish dutch english finnish french german german2 hungarian italian kraaij_pohlmann lovins norwegian porter portuguese romanian russian spanish swedish turkish (pass 'none' to disable stemming)
</p>
<p>
<%=content_tag(:label, "Stem strategy:")%>
<%=radio_button_tag 'settings[dmsf_stemming_strategy]', 'STEM_NONE', @settings['dmsf_stemming_strategy'] == 'STEM_NONE', :checked=>true %> Stem none (default)<br>
<%=radio_button_tag 'settings[dmsf_stemming_strategy]', 'STEM_SOME', @settings['dmsf_stemming_strategy'] == 'STEM_SOME' %> Stem some<br>
<%=radio_button_tag 'settings[dmsf_stemming_strategy]', 'STEM_ALL', @settings['dmsf_stemming_strategy'] == 'STEM_ALL' %> Stem all <br>
<br/>
This controls how the query parser will apply the stemming algorithm. The default value is STEM_NONE. The possible values are:
<br>
* STEM_NONE: Don't perform any stemming.<br>
* STEM_SOME: Search for stemmed forms of terms except for those which start with a capital letter, or are followed by certain characters (currently: (/@\<\>=*[{\" ), or are used with operators which need positional information. Stemmed terms are prefixed with 'Z'.<br>
* STEM_ALL: Search for stemmed forms of all words (note: no 'Z' prefix is added).<br>
<br/>
Note that the stemming algorithm is only applied to words in probabilistic fields - boolean filter terms are never stemmed.<br>
</p>

BIN
assets/images/approve.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 535 B

BIN
assets/images/approved.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 641 B

BIN
assets/images/delete.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 459 B

BIN
assets/images/dmsf.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 803 B

BIN
assets/images/lock.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 797 B

BIN
assets/images/locked.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 530 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
assets/images/notify.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 569 B

BIN
assets/images/notifynot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 578 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 459 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 180 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 994 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 399 B

BIN
assets/images/unlock.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 748 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 662 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 412 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,86 @@
// Copyright 2007, Google Inc.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// 3. Neither the name of Google Inc. nor the names of its contributors may be
// used to endorse or promote products derived from this software without
// specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// Sets up google.gears.*, which is *the only* supported way to access Gears.
//
// Circumvent this file at your own risk!
//
// In the future, Gears may automatically define google.gears.* without this
// file. Gears may use these objects to transparently fix bugs and compatibility
// issues. Applications that use the code below will continue to work seamlessly
// when that happens.
(function() {
// We are already defined. Hooray!
if (window.google && google.gears) {
return;
}
var factory = null;
// Firefox
if (typeof GearsFactory != 'undefined') {
factory = new GearsFactory();
} else {
// IE
try {
factory = new ActiveXObject('Gears.Factory');
// privateSetGlobalObject is only required and supported on WinCE.
if (factory.getBuildInfo().indexOf('ie_mobile') != -1) {
factory.privateSetGlobalObject(this);
}
} catch (e) {
// Safari
if ((typeof navigator.mimeTypes != 'undefined')
&& navigator.mimeTypes["application/x-googlegears"]) {
factory = document.createElement("object");
factory.style.display = "none";
factory.width = 0;
factory.height = 0;
factory.type = "application/x-googlegears";
document.documentElement.appendChild(factory);
}
}
}
// *Do not* define any objects if Gears is not installed. This mimics the
// behavior of Gears defining the objects in the future.
if (!factory) {
return;
}
// Now set up the objects, being careful not to overwrite anything.
//
// Note: In Internet Explorer for Windows Mobile, you can't add properties to
// the window object. However, global objects are automatically added as
// properties of the window object in all browsers.
if (!window.google) {
google = {};
}
if (!google.gears) {
google.gears = {factory: factory};
}
})();

View File

@ -0,0 +1,14 @@
// .po file like language pack
plupload.addI18n({
'Select files' : 'Vyberte soubory',
'Add files to the upload queue and click the start button.' : 'Přidejte soubory do fronty a pak spusťte nahrávání.',
'Filename' : 'Název souboru',
'Status' : 'Status',
'Size' : 'Velikost',
'Add Files' : 'Přidat soubory',
'Stop current upload' : 'Zastavit nahrávání',
'Start uploading queue' : 'Spustit frontu nahrávání',
'Drag files here.' : 'Sem přetáhněte soubory.',
'Start Upload': 'Spustit nahrávání',
'Uploaded %d/%d files': 'Nahráno %d/%d souborů'
});

View File

@ -0,0 +1,12 @@
// .po file like language pack
plupload.addI18n({
'Select files' : 'Vælg filer',
'Add files to the upload queue and click the start button.' : 'Tilføj filer til køen, og tryk på start.',
'Filename' : 'Filnavn',
'Status' : 'Status',
'Size' : 'Størrelse',
'Add files' : 'Tilføj filer',
'Stop current upload' : 'Stop upload',
'Start uploading queue' : 'Start upload',
'Drag files here.' : 'Træk filer her.'
});

View File

@ -0,0 +1,25 @@
// German
plupload.addI18n({
'Select files' : 'W&auml;hlen Sie die Dateien:',
'Add files to the upload queue and click the start button.' : 'Dateien hinzuf&uuml;gen und danach auf \'Starten des Uploads\' klicken und die Datei hochzuladen.',
'Filename' : 'Dateiname',
'Status' : 'Status',
'Size' : 'Gr&ouml;&szlig;e',
'Add files' : 'Hinzuf&uuml;gen von Dateien',
'Stop current upload' : 'Stop aktuellen Upload',
'Start uploading queue' : 'Starte Upload',
'Uploaded %d/%d files': '%d/%d Dateien sind Hochgeladen',
'N/A' : 'Nicht verf&uuml;gbar',
'Drag files here.' : 'Ziehen Sie die Dateien hier hin',
'File extension error.': 'Dateiendungs Fehler.',
'File size error.': 'Dateigr&ouml;ßen Fehler.',
'Init error.': 'Initialisierungs Fehler.',
'HTTP Error.': 'HTTP Fehler.',
'Security error.': 'Sicherheits Fehler.',
'Generic error.': 'Generic Fehler.',
'IO error.': 'Ein/Ausgabe Fehler.',
'Stop Upload': 'Stoppen des Uploads.',
'Add Files': 'Dateien hinzuf&uuml;gen',
'Start Upload': 'Starten des Uploads.',
'%d files queued': '%d Dateien in der Warteschlange.'
});

View File

@ -0,0 +1,25 @@
// Spanish
plupload.addI18n({
'Select files' : 'Elija archivos:',
'Add files to the upload queue and click the start button.' : 'Agregue archivos a la cola de subida y haga click en el boton de iniciar.',
'Filename' : 'Nombre de archivo',
'Status' : 'Estado',
'Size' : 'Tama&ntilde;o',
'Add files' : 'Agregue archivos',
'Stop current upload' : 'Detener subida actual',
'Start uploading queue' : 'Iniciar subida de cola',
'Uploaded %d/%d files': 'Subidos %d/%d archivos',
'N/A' : 'No disponible',
'Drag files here.' : 'Arrastre archivos aqu&iacute;',
'File extension error.': 'Error de extensi&oacute;n de archivo.',
'File size error.': 'Error de tama&ntilde;o de archivo.',
'Init error.': 'Error de inicializaci&oacute;n.',
'HTTP Error.': 'Error de HTTP.',
'Security error.': 'Error de seguridad.',
'Generic error.': 'Error gen&eacute;rico.',
'IO error.': 'Error de entrada/salida.',
'Stop Upload': 'Detener Subida.',
'Add Files': 'Agregar Archivos',
'Start Upload': 'Comenzar Subida.',
'%d files queued': '%d archivos en cola.'
});

View File

@ -0,0 +1,12 @@
// .po file like language pack
plupload.addI18n({
'Select files' : 'Sélectionner les fichiers',
'Add files to the upload queue and click the start button.' : 'Ajouter des fichiers à la file et appuyer sur le bouton démarrer.',
'Filename' : 'Nom de fichier',
'Status' : 'Status',
'Size' : 'Taille',
'Add Files' : 'Ajouter fichiers',
'Stop current upload' : 'Arrêter téléversement',
'Start Upload' : 'Démarrer téléversement',
'Drag files here.' : 'Déposer les fichiers ici.'
});

View File

@ -0,0 +1,21 @@
// .po file like language pack
plupload.addI18n({
'Select files' : 'Seleziona i files',
'Add files to the upload queue and click the start button.' : 'Aggiungi i file alla coda di caricamento e clicca il pulsante di avvio.',
'Filename' : 'Nome file',
'Status' : 'Stato',
'Size' : 'Dimensione',
'Add files' : 'Aggiungi file',
'Stop current upload' : 'Interrompi il caricamento',
'Start uploading queue' : 'Avvia il caricamento',
'Uploaded %d/%d files': 'Caricati %d/%d file',
'N/A' : 'N/D',
'Drag files here.' : 'Trascina i file qui.',
'File extension error.': 'Errore estensione file.',
'File size error.': 'Errore dimensione file.',
'Init error.': 'Errore inizializzazione.',
'HTTP Error.': 'Errore HTTP.',
'Security error.': 'Errore sicurezza.',
'Generic error.': 'Errore generico.',
'IO error.': 'Errore IO.'
});

View File

@ -0,0 +1,21 @@
// Dutch
plupload.addI18n({
'Select files' : 'Selecteer bestand(en):',
'Add files to the upload queue and click the start button.' : 'Voeg bestanden toe aan de wachtrij en druk op \'Start\'.',
'Filename' : 'Bestandsnaam',
'Status' : 'Status',
'Size' : 'Grootte',
'Add files' : 'Voeg bestanden toe',
'Stop current upload' : 'Stop upload',
'Start uploading queue' : 'Start upload',
'Uploaded %d/%d files': '%d/%d bestanden ge-upload',
'N/A' : 'Niet beschikbaar',
'Drag files here.' : 'Sleep bestanden hierheen.',
'File extension error.': 'Ongeldig bestandstype.',
'File size error.': 'Bestandsgrootte Error.',
'Init error.': 'Initialisatie error.',
'HTTP Error.': 'HTTP Error.',
'Security error.': 'Beveiliging error.',
'Generic error.': 'Onbekende error.',
'IO error.': 'IO error.'
});

View File

@ -0,0 +1,35 @@
// Brazilian Portuguese
plupload.addI18n({
'Select files' : 'Escolha os arquivos',
'Add files to the upload queue and click the start button.' : 'Adicione os arquivos abaixo e clique no botão "Iniciar o envio".',
'Filename' : 'Nome do arquivo',
'Status' : 'Status',
'Size' : 'Tamanho',
'Add Files' : 'Adicionar arquivo(s)',
'Stop Upload' : 'Parar o envio',
'Start Upload' : 'Iniciar o envio',
'Add files' : 'Adicionar arquivo(s)',
'Add files.' : 'Adicionar arquivo(s)',
'Stop upload' : 'Parar o envio',
'Start upload' : 'Iniciar o envio',
'Uploaded %d/%d files': 'Enviado(s) %d/%d arquivo(s)',
'N/A' : 'N/D',
'Drag files here.' : 'Arraste os arquivos pra cá',
'File extension error.': 'Tipo de arquivo não permitido.',
'File size error.': 'Tamanho de arquivo não permitido.',
'File count error.': 'Erro na contagem dos arquivos',
'Init error.': 'Erro inicializando.',
'HTTP Error.': 'Erro HTTP.',
'Security error.': 'Erro de segurança.',
'Generic error.': 'Erro genérico.',
'IO error.': 'Erro de E/S.',
'File: %s': 'Arquivo: %s',
'Close': 'Fechar',
'%d files queued': '%d arquivo(s)',
'Using runtime: ': 'Usando: ',
'File: %f, size: %s, max file size: %m': 'Arquivo: %f, tamanho: %s, máximo: %m',
'Upload element accepts only %d file(s) at a time. Extra files were stripped.': 'Só são aceitos %d arquivos por vez. O que passou disso foi descartado.',
'Upload URL might be wrong or doesn\'t exist': 'URL de envio está errada ou não existe',
'Error: File to large: ': 'Erro: Arquivo muito grande: ',
'Error: Invalid file extension: ': 'Erro: Tipo de arquivo não permitido: '
});

View File

@ -0,0 +1,21 @@
// Russian
plupload.addI18n({
'Select files' : 'Выберите файлы',
'Add files to the upload queue and click the start button.' : 'Добавьте файлы в очередь и нажмите кнопку "Загрузить файлы".',
'Filename' : 'Имя файла',
'Status' : 'Статус',
'Size' : 'Размер',
'Add files' : 'Добавить файлы',
'Stop current upload' : 'Остановить загрузку',
'Start uploading queue' : 'Загрузить файлы',
'Uploaded %d/%d files': 'Загружено %d из %d файлов',
'N/A' : 'N/D',
'Drag files here.' : 'Перетащите файлы сюда.',
'File extension error.': 'Неправильное расширение файла.',
'File size error.': 'Неправильный размер файла.',
'Init error.': 'Ошибка инициализации.',
'HTTP Error.': 'Ошибка HTTP.',
'Security error.': 'Ошибка безопасности.',
'Generic error.': 'Общая ошибка.',
'IO error.': 'Ошибка ввода-вывода.'
});

View File

@ -0,0 +1,12 @@
// .po file like language pack
plupload.addI18n({
'Select files' : 'Välj filer',
'Add files to the upload queue and click the start button.' : 'Lägg till filer till kön och tryck på start.',
'Filename' : 'Filnamn',
'Status' : 'Status',
'Size' : 'Storlek',
'Add files' : 'Lägg till filer',
'Stop current upload' : 'Stoppa uppladdningen',
'Start uploading queue' : 'Starta uppladdningen',
'Drag files here.' : 'Dra filer hit'
});

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,737 @@
/**
* jquery.ui.plupload.js
*
* Copyright 2009, Moxiecode Systems AB
* Released under GPL License.
*
* License: http://www.plupload.com/license
* Contributing: http://www.plupload.com/contributing
*
* Depends:
* jquery.ui.core.js
* jquery.ui.widget.js
*
* Optionally:
* jquery.ui.button.js
* jquery.ui.progressbar.js
* jquery.ui.sortable.js
*/
// JSLint defined globals
/*global window:false, document:false, plupload:false, jQuery:false */
(function(window, document, plupload, $, undef) {
var uploaders = {};
function _(str) {
return plupload.translate(str) || str;
}
function renderUI(obj) {
obj.html(
'<div class="plupload_wrapper">' +
'<div class="ui-widget-content plupload_container">' +
'<div class="plupload">' +
'<div class="plupload_content">' +
'<table class="plupload_filelist">' +
'<tr class="ui-widget-header plupload_filelist_header">' +
'<td class="plupload_cell plupload_file_name">' + _('Filename') + '</td>' +
'<td class="plupload_cell plupload_file_status">' + _('Status') + '</td>' +
'<td class="plupload_cell plupload_file_size">' + _('Size') + '</td>' +
'<td class="plupload_cell plupload_file_action">&nbsp;</td>' +
'</tr>' +
'</table>' +
'<div class="plupload_scroll">' +
'<table class="plupload_filelist_content"></table>' +
'</div>' +
'<table class="plupload_filelist">' +
'<tr class="ui-widget-header ui-widget-content plupload_filelist_footer">' +
'<td class="plupload_cell plupload_file_name">' +
'<div class="plupload_buttons"><!-- Visible -->' +
'<a class="plupload_button plupload_add">' + _('Add Files') + '</a>&nbsp;' +
'<a class="plupload_button plupload_start">' + _('Start Upload') + '</a>&nbsp;' +
'<a class="plupload_button plupload_stop plupload_hidden">'+_('Stop Upload') + '</a>&nbsp;' +
'</div>' +
'<div class="plupload_started plupload_hidden"><!-- Hidden -->' +
'<div class="plupload_progress plupload_right">' +
'<div class="plupload_progress_container"></div>' +
'</div>' +
'<div class="plupload_cell plupload_upload_status"></div>' +
'<div class="plupload_clearer">&nbsp;</div>' +
'</div>' +
'</td>' +
'<td class="plupload_file_status"><span class="plupload_total_status">0%</span></td>' +
'<td class="plupload_file_size"><span class="plupload_total_file_size">0 kb</span></td>' +
'<td class="plupload_file_action"></td>' +
'</tr>' +
'</table>' +
'</div>' +
'</div>' +
'</div>' +
'<input class="plupload_count" value="0" type="hidden">' +
'</div>'
);
}
$.widget("ui.plupload", {
contents_bak: '',
runtime: null,
options: {
browse_button_hover: 'ui-state-hover',
browse_button_active: 'ui-state-active',
// widget specific
dragdrop : true,
multiple_queues: true, // re-use widget by default
buttons: {
browse: true,
start: true,
stop: true
},
autostart: false,
sortable: false,
rename: false,
max_file_count: 0 // unlimited
},
FILE_COUNT_ERROR: -9001,
_create: function() {
var self = this, id, uploader;
id = this.element.attr('id');
if (!id) {
id = plupload.guid();
this.element.attr('id', id);
}
this.id = id;
// backup the elements initial state
this.contents_bak = this.element.html();
renderUI(this.element);
// container, just in case
this.container = $('.plupload_container', this.element).attr('id', id + '_container');
// list of files, may become sortable
this.filelist = $('.plupload_filelist_content', this.container).attr('id', id + '_filelist');
// buttons
this.browse_button = $('.plupload_add', this.container).attr('id', id + '_browse');
this.start_button = $('.plupload_start', this.container).attr('id', id + '_start');
this.stop_button = $('.plupload_stop', this.container).attr('id', id + '_stop');
if ($.ui.button) {
this.browse_button.button({
icons: { primary: 'ui-icon-circle-plus' }
});
this.start_button.button({
icons: { primary: 'ui-icon-circle-arrow-e' },
disabled: true
});
this.stop_button.button({
icons: { primary: 'ui-icon-circle-close' }
});
}
// all buttons are optional, so they can be disabled and hidden
if (!this.options.buttons.browse) {
this.browse_button.button('disable').hide();
$('#' + id + self.runtime + '_container').hide();
}
if (!this.options.buttons.start) {
this.start_button.button('disable').hide();
}
if (!this.options.buttons.stop) {
this.stop_button.button('disable').hide();
}
// progressbar
this.progressbar = $('.plupload_progress_container', this.container);
if ($.ui.progressbar) {
this.progressbar.progressbar();
}
// counter
this.counter = $('.plupload_count', this.element)
.attr({
id: id + '_count',
name: id + '_count'
});
// initialize uploader instance
uploader = this.uploader = uploaders[id] = new plupload.Uploader($.extend({
container: id ,
browse_button: id + '_browse'
}, this.options));
uploader.bind('Init', function(up, res) {
if (!self.options.unique_names && self.options.rename) {
self._enableRenaming();
}
if (uploader.features.dragdrop && self.options.dragdrop) {
self._enableDragAndDrop();
}
self.container.attr('title', _('Using runtime: ') + (self.runtime = res.runtime));
self.start_button.click(function(e) {
if (!$(this).button('option', 'disabled')) {
self.start();
}
e.preventDefault();
});
self.stop_button.click(function(e) {
uploader.stop();
e.preventDefault();
});
});
// check if file count doesn't exceed the limit
if (self.options.max_file_count) {
uploader.bind('FilesAdded', function(up, files) {
var length = files.length, removed = [];
if (length > self.options.max_file_count) {
removed = files.splice(self.options.max_file_count);
up.trigger('Error', {
code : self.FILE_COUNT_ERROR,
message : _('File count error.'),
file : removed
});
}
});
}
// uploader internal events must run first
uploader.init();
uploader.bind('FilesAdded', function(up, files) {
self._trigger('selected', null, { up: up, files: files } );
if (self.options.autostart) {
self.start();
}
});
uploader.bind('FilesRemoved', function(up, files) {
self._trigger('removed', null, { up: up, files: files } );
});
uploader.bind('QueueChanged', function() {
self._updateFileList();
});
uploader.bind('StateChanged', function() {
self._handleState();
});
uploader.bind('UploadFile', function(up, file) {
self._handleFileStatus(file);
});
uploader.bind('FileUploaded', function(up, file) {
self._handleFileStatus(file);
self._trigger('uploaded', null, { up: up, file: file } );
});
uploader.bind('UploadProgress', function(up, file) {
// Set file specific progress
$('#' + file.id + ' .plupload_file_status', self.element).html(file.percent + '%');
self._handleFileStatus(file);
self._updateTotalProgress();
self._trigger('progress', null, { up: up, file: file } );
});
uploader.bind('UploadComplete', function(up, files) {
self._trigger('complete', null, { up: up, files: files } );
});
uploader.bind('Error', function(up, err) {
var file = err.file, message, details;
if (file) {
message = '<strong>' + err.message + '</strong>';
details = err.details;
if (details) {
message += " <br /><i>" + err.details + "</i>";
} else {
switch (err.code) {
case plupload.FILE_EXTENSION_ERROR:
details = _("File: %s").replace('%s', file.name);
break;
case plupload.FILE_SIZE_ERROR:
details = _("File: %f, size: %s, max file size: %m").replace(/%([fsm])/g, function($0, $1) {
switch ($1) {
case 'f': return file.name;
case 's': return file.size;
case 'm': return plupload.parseSize(self.options.max_file_size);
}
});
break;
case self.FILE_COUNT_ERROR:
details = _("Upload element accepts only %d file(s) at a time. Extra files were stripped.")
.replace('%d', self.options.max_file_count);
break;
case plupload.IMAGE_FORMAT_ERROR :
details = plupload.translate('Image format either wrong or not supported.');
break;
case plupload.IMAGE_MEMORY_ERROR :
details = plupload.translate('Runtime ran out of available memory.');
break;
case plupload.IMAGE_DIMENSIONS_ERROR :
details = plupload.translate('Resoultion out of boundaries! <b>%s</b> runtime supports images only up to %wx%hpx.').replace(/%([swh])/g, function($0, $1) {
switch ($1) {
case 's': return up.runtime;
case 'w': return up.features.maxWidth;
case 'h': return up.features.maxHeight;
}
});
break;
case plupload.HTTP_ERROR:
details = _("Upload URL might be wrong or doesn't exist");
break;
}
message += " <br /><i>" + details + "</i>";
}
self._notify('error', message);
}
});
},
_setOption: function(key, value) {
if (key == 'buttons' && typeof(value) == 'object') {
value = $.extend(this.options.buttons, value);
if (!value.browse) {
this.browse_button.button('disable').hide();
$('#' + this.id + self.runtime + '_container').hide();
} else {
this.browse_button.button('enable').show();
$('#' + this.id + self.runtime + '_container').show();
}
if (!value.start) {
this.start_button.button('disable').hide();
} else {
this.start_button.button('enable').show();
}
if (!value.stop) {
this.stop_button.button('disable').hide();
} else {
this.start_button.button('enable').show();
}
}
this.uploader.settings[key] = value;
},
start: function() {
this.uploader.start();
this._trigger('start', null);
},
stop: function() {
this.uploader.stop();
this._trigger('stop', null);
},
getFile: function(id) {
var file;
if (typeof id === 'number') {
file = this.uploader.files[id];
} else {
file = this.uploader.getFile(id);
}
return file;
},
removeFile: function(id) {
var file = this.getFile(id);
if (file) {
this.uploader.removeFile(file);
}
},
clearQueue: function() {
this.uploader.splice();
},
getUploader: function() {
return this.uploader;
},
refresh: function() {
this.uploader.refresh();
},
_handleState: function() {
var self = this, uploader = this.uploader;
if (uploader.state === plupload.STARTED) {
$(self.start_button).button('disable');
$([])
.add(self.stop_button)
.add('.plupload_started')
.removeClass('plupload_hidden');
$('.plupload_upload_status', self.element).text(
_('Uploaded %d/%d files').replace('%d/%d', uploader.total.uploaded+'/'+uploader.files.length)
);
$('.plupload_header_content', self.element).addClass('plupload_header_content_bw');
} else {
$([])
.add(self.stop_button)
.add('.plupload_started')
.addClass('plupload_hidden');
if (self.options.multiple_queues) {
$(self.start_button).button('enable');
$('.plupload_header_content', self.element).removeClass('plupload_header_content_bw');
}
self._updateFileList();
}
},
_handleFileStatus: function(file) {
var actionClass, iconClass;
switch (file.status) {
case plupload.DONE:
actionClass = 'plupload_done';
iconClass = 'ui-icon ui-icon-circle-check';
break;
case plupload.FAILED:
actionClass = 'ui-state-error plupload_failed';
iconClass = 'ui-icon ui-icon-alert';
break;
case plupload.QUEUED:
actionClass = 'plupload_delete';
iconClass = 'ui-icon ui-icon-circle-minus';
break;
case plupload.UPLOADING:
actionClass = 'ui-state-highlight plupload_uploading';
iconClass = 'ui-icon ui-icon-circle-arrow-w';
// scroll uploading file into the view if its bottom boundary is out of it
var scroller = $('.plupload_scroll', this.container),
scrollTop = scroller.scrollTop(),
scrollerHeight = scroller.height(),
rowOffset = $('#' + file.id).position().top + $('#' + file.id).height();
if (scrollerHeight < rowOffset) {
scroller.scrollTop(scrollTop + rowOffset - scrollerHeight);
}
break;
}
actionClass += ' ui-state-default plupload_file';
$('#' + file.id)
.attr('class', actionClass)
.find('.ui-icon')
.attr('class', iconClass);
},
_updateTotalProgress: function() {
var uploader = this.uploader;
this.progressbar.progressbar('value', uploader.total.percent);
$('.plupload_total_status', this.element).html(uploader.total.percent + '%');
$('.plupload_upload_status', this.element).text(
_('Uploaded %d/%d files').replace('%d/%d', uploader.total.uploaded+'/'+uploader.files.length)
);
// All files are uploaded
if (uploader.total.uploaded === uploader.files.length) {
uploader.stop();
}
},
_updateFileList: function() {
var self = this, uploader = this.uploader, filelist = this.filelist,
count = 0,
id, prefix = this.id + '_',
fields;
// destroy sortable if enabled
if ($.ui.sortable && this.options.sortable) {
$('tbody', filelist).sortable('destroy');
}
filelist.empty();
$.each(uploader.files, function(i, file) {
fields = '';
id = prefix + count;
if (file.status === plupload.DONE) {
if (file.target_name) {
fields += '<input type="hidden" name="' + id + '_tmpname" value="'+plupload.xmlEncode(file.target_name)+'" />';
}
fields += '<input type="hidden" name="' + id + '_name" value="'+plupload.xmlEncode(file.name)+'" />';
fields += '<input type="hidden" name="' + id + '_status" value="' + (file.status === plupload.DONE ? 'done' : 'failed') + '" />';
count++;
self.counter.val(count);
}
filelist.append(
'<tr class="ui-state-default plupload_file" id="' + file.id + '">' +
'<td class="plupload_cell plupload_file_name"><span>' + file.name + '</span></td>' +
'<td class="plupload_cell plupload_file_status">' + file.percent + '%</td>' +
'<td class="plupload_cell plupload_file_size">' + plupload.formatSize(file.size) + '</td>' +
'<td class="plupload_cell plupload_file_action"><div class="ui-icon"></div>' + fields + '</td>' +
'</tr>'
);
self._handleFileStatus(file);
$('#' + file.id + '.plupload_delete .ui-icon, #' + file.id + '.plupload_done .ui-icon')
.click(function(e) {
$('#' + file.id).remove();
uploader.removeFile(file);
e.preventDefault();
});
self._trigger('updatelist', null, filelist);
});
$('.plupload_total_file_size', self.element).html(plupload.formatSize(uploader.total.size));
if (uploader.total.queued === 0) {
$('.ui-button-text', self.browse_button).text(_('Add Files'));
} else {
$('.ui-button-text', self.browse_button).text(_('%d files queued').replace('%d', uploader.total.queued));
}
if (uploader.files.length === (uploader.total.uploaded + uploader.total.failed)) {
self.start_button.button('disable');
} else {
self.start_button.button('enable');
}
// Scroll to end of file list
filelist[0].scrollTop = filelist[0].scrollHeight;
self._updateTotalProgress();
if (!uploader.files.length && uploader.features.dragdrop && uploader.settings.dragdrop) {
// Re-add drag message if there are no files
$('#' + id + '_filelist').append('<tr><td class="plupload_droptext">' + _("Drag files here.") + '</td></tr>');
} else {
// Otherwise re-initialize sortable
if (self.options.sortable && $.ui.sortable) {
self._enableSortingList();
}
}
},
_enableRenaming: function() {
var self = this;
$('.plupload_file_name span', this.filelist).live('click', function(e) {
var targetSpan = $(e.target), file, parts, name, ext = "";
// Get file name and split out name and extension
file = self.uploader.getFile(targetSpan.parents('tr')[0].id);
name = file.name;
parts = /^(.+)(\.[^.]+)$/.exec(name);
if (parts) {
name = parts[1];
ext = parts[2];
}
// Display input element
targetSpan.hide().after('<input class="plupload_file_rename" type="text" />');
targetSpan.next().val(name).focus().blur(function() {
targetSpan.show().next().remove();
}).keydown(function(e) {
var targetInput = $(this);
if ($.inArray(e.keyCode, [13, 27]) !== -1) {
e.preventDefault();
// Rename file and glue extension back on
if (e.keyCode === 13) {
file.name = targetInput.val() + ext;
targetSpan.text(file.name);
}
targetInput.blur();
}
});
});
},
_enableDragAndDrop: function() {
this.filelist.append('<tr><td class="plupload_droptext">' + _("Drag files here.") + '</td></tr>');
this.filelist.parent().attr('id', this.id + '_dropbox');
this.uploader.settings.drop_element = this.options.drop_element = this.id + '_dropbox';
},
_enableSortingList: function() {
var idxStart, self = this;
if ($('tbody tr', this.filelist).length < 2) {
return;
}
$('tbody', this.filelist).sortable({
containment: 'parent',
items: '.plupload_delete',
helper: function(e, el) {
return el.clone(true).find('td:not(.plupload_file_name)').remove().end().css('width', '100%');
},
start: function(e, ui) {
idxStart = $('tr', this).index(ui.item);
},
stop: function(e, ui) {
var i, length, idx, files = [], idxStop = $('tr', this).index(ui.item);
for (i = 0, length = self.uploader.files.length; i < length; i++) {
if (i === idxStop) {
idx = idxStart;
} else if (i === idxStart) {
idx = idxStop;
} else {
idx = i;
}
files[files.length] = self.uploader.files[idx];
}
files.unshift(files.length);
files.unshift(0);
// re-populate files array
Array.prototype.splice.apply(self.uploader.files, files);
}
});
},
_notify: function(type, message) {
var popup = $(
'<div class="plupload_message">' +
'<span class="plupload_message_close ui-icon ui-icon-circle-close" title="'+_('Close')+'"></span>' +
'<p><span class="ui-icon"></span>' + message + '</p>' +
'</div>'
);
popup
.addClass('ui-state-' + (type === 'error' ? 'error' : 'highlight'))
.find('p .ui-icon')
.addClass('ui-icon-' + (type === 'error' ? 'alert' : 'info'))
.end()
.find('.plupload_message_close')
.click(function() {
popup.remove();
})
.end()
.appendTo('.plupload_header_content', this.container);
},
destroy: function() {
// unbind all button events
$('.plupload_button', this.element).unbind();
// destroy buttons
if ($.ui.button) {
$('.plupload_add, .plupload_start, .plupload_stop', this.container)
.button('destroy');
}
// destroy progressbar
if ($.ui.progressbar) {
this.progressbar.progressbar('destroy');
}
// destroy sortable behavior
if ($.ui.sortable && this.options.sortable) {
$('tbody', this.filelist).sortable('destroy');
}
// destroy uploader instance
this.uploader.destroy();
// restore the elements initial state
this.element
.empty()
.html(this.contents_bak);
this.contents_bak = '';
$.Widget.prototype.destroy.apply(this);
}
});
} (window, document, plupload, jQuery));

View File

@ -0,0 +1 @@
(function(a){a.runtimes.BrowserPlus=a.addRuntime("browserplus",{getFeatures:function(){return{dragdrop:true,jpgresize:true,pngresize:true,chunks:true,progress:true,multipart:true}},init:function(g,i){var e=window.BrowserPlus,h={},d=g.settings,c=d.resize;function f(n){var m,l,j=[],k,o;for(l=0;l<n.length;l++){k=n[l];o=a.guid();h[o]=k;j.push(new a.File(o,k.name,k.size))}if(l){g.trigger("FilesAdded",j)}}function b(){g.bind("PostInit",function(){var m,k=d.drop_element,o=g.id+"_droptarget",j=document.getElementById(k),l;function p(r,q){e.DragAndDrop.AddDropTarget({id:r},function(s){e.DragAndDrop.AttachCallbacks({id:r,hover:function(t){if(!t&&q){q()}},drop:function(t){if(q){q()}f(t)}},function(){})})}function n(){document.getElementById(o).style.top="-1000px"}if(j){if(document.attachEvent&&(/MSIE/gi).test(navigator.userAgent)){m=document.createElement("div");m.setAttribute("id",o);a.extend(m.style,{position:"absolute",top:"-1000px",background:"red",filter:"alpha(opacity=0)",opacity:0});document.body.appendChild(m);a.addEvent(j,"dragenter",function(r){var q,s;q=document.getElementById(k);s=a.getPos(q);a.extend(document.getElementById(o).style,{top:s.y+"px",left:s.x+"px",width:q.offsetWidth+"px",height:q.offsetHeight+"px"})});p(o,n)}else{p(k)}}a.addEvent(document.getElementById(d.browse_button),"click",function(v){var t=[],r,q,u=d.filters,s;v.preventDefault();for(r=0;r<u.length;r++){s=u[r].extensions.split(",");for(q=0;q<s.length;q++){t.push(a.mimeTypes[s[q]])}}e.FileBrowse.OpenBrowseDialog({mimeTypes:t},function(w){if(w.success){f(w.value)}})});j=m=null});g.bind("UploadFile",function(m,j){var l=h[j.id],r={},k=m.settings.chunk_size,n,o=[];function q(s,u){var t;if(j.status==a.FAILED){return}r.name=j.target_name||j.name;if(k){r.chunk=""+s;r.chunks=""+u}t=o.shift();e.Uploader.upload({url:m.settings.url,files:{file:t},cookies:document.cookies,postvars:a.extend(r,m.settings.multipart_params),progressCallback:function(x){var w,v=0;n[s]=parseInt(x.filePercent*t.size/100,10);for(w=0;w<n.length;w++){v+=n[w]}j.loaded=v;m.trigger("UploadProgress",j)}},function(w){var v,x;if(w.success){v=w.value.statusCode;if(k){m.trigger("ChunkUploaded",j,{chunk:s,chunks:u,response:w.value.body,status:v})}if(o.length>0){q(++s,u)}else{j.status=a.DONE;m.trigger("FileUploaded",j,{response:w.value.body,status:v});if(v>=400){m.trigger("Error",{code:a.HTTP_ERROR,message:a.translate("HTTP Error."),file:j,status:v})}}}else{m.trigger("Error",{code:a.GENERIC_ERROR,message:a.translate("Generic Error."),file:j,details:w.error})}})}function p(s){j.size=s.size;if(k){e.FileAccess.chunk({file:s,chunkSize:k},function(v){if(v.success){var w=v.value,t=w.length;n=Array(t);for(var u=0;u<t;u++){n[u]=0;o.push(w[u])}q(0,t)}})}else{n=Array(1);o.push(s);q(0,1)}}if(c&&/\.(png|jpg|jpeg)$/i.test(j.name)){BrowserPlus.ImageAlter.transform({file:l,quality:c.quality||90,actions:[{scale:{maxwidth:c.width,maxheight:c.height}}]},function(s){if(s.success){p(s.value.file)}})}else{p(l)}});i({success:true})}if(e){e.init(function(k){var j=[{service:"Uploader",version:"3"},{service:"DragAndDrop",version:"1"},{service:"FileBrowse",version:"1"},{service:"FileAccess",version:"2"}];if(c){j.push({service:"ImageAlter",version:"4"})}if(k.success){e.require({services:j},function(l){if(l.success){b()}else{i()}})}else{i()}})}else{i()}}})})(plupload);

View File

@ -0,0 +1 @@
(function(f,b,d,e){var a={},g={};function c(){var h;try{h=navigator.plugins["Shockwave Flash"];h=h.description}catch(j){try{h=new ActiveXObject("ShockwaveFlash.ShockwaveFlash").GetVariable("$version")}catch(i){h="0.0"}}h=h.match(/\d+/g);return parseFloat(h[0]+"."+h[1])}d.flash={trigger:function(j,h,i){setTimeout(function(){var m=a[j],l,k;if(m){m.trigger("Flash:"+h,i)}},0)}};d.runtimes.Flash=d.addRuntime("flash",{getFeatures:function(){return{jpgresize:true,pngresize:true,maxWidth:8091,maxHeight:8091,chunks:true,progress:true,multipart:true}},init:function(j,o){var n,i,k,p=0,h=b.body;if(c()<10){o({success:false});return}g[j.id]=false;a[j.id]=j;n=b.getElementById(j.settings.browse_button);i=b.createElement("div");i.id=j.id+"_flash_container";d.extend(i.style,{position:"absolute",top:"0px",background:j.settings.shim_bgcolor||"transparent",zIndex:99999,width:"100%",height:"100%"});i.className="plupload flash";if(j.settings.container){h=b.getElementById(j.settings.container);h.style.position="relative"}h.appendChild(i);k="id="+escape(j.id);i.innerHTML='<object id="'+j.id+'_flash" width="100%" height="100%" style="outline:0" type="application/x-shockwave-flash" data="'+j.settings.flash_swf_url+'"><param name="movie" value="'+j.settings.flash_swf_url+'" /><param name="flashvars" value="'+k+'" /><param name="wmode" value="transparent" /><param name="allowscriptaccess" value="always" /></object>';function m(){return b.getElementById(j.id+"_flash")}function l(){if(p++>5000){o({success:false});return}if(!g[j.id]){setTimeout(l,1)}}l();n=i=null;j.bind("Flash:Init",function(){var s={},r,q=j.settings.resize||{};m().setFileFilters(j.settings.filters,j.settings.multi_selection);if(g[j.id]){return}g[j.id]=true;j.bind("UploadFile",function(t,u){var v=t.settings;m().uploadFile(s[u.id],v.url,{name:u.target_name||u.name,mime:d.mimeTypes[u.name.replace(/^.+\.([^.]+)/,"$1")]||"application/octet-stream",chunk_size:v.chunk_size,width:q.width,height:q.height,quality:q.quality||90,multipart:v.multipart,multipart_params:v.multipart_params||{},file_data_name:v.file_data_name,format:/\.(jpg|jpeg)$/i.test(u.name)?"jpg":"png",headers:v.headers,urlstream_upload:v.urlstream_upload})});j.bind("Flash:UploadProcess",function(u,t){var v=u.getFile(s[t.id]);if(v.status!=d.FAILED){v.loaded=t.loaded;v.size=t.size;u.trigger("UploadProgress",v)}});j.bind("Flash:UploadChunkComplete",function(t,v){var w,u=t.getFile(s[v.id]);w={chunk:v.chunk,chunks:v.chunks,response:v.text};t.trigger("ChunkUploaded",u,w);if(u.status!=d.FAILED){m().uploadNextChunk()}if(v.chunk==v.chunks-1){u.status=d.DONE;t.trigger("FileUploaded",u,{response:v.text})}});j.bind("Flash:SelectFiles",function(t,w){var v,u,x=[],y;for(u=0;u<w.length;u++){v=w[u];y=d.guid();s[y]=v.id;s[v.id]=y;x.push(new d.File(y,v.name,v.size))}if(x.length){j.trigger("FilesAdded",x)}});j.bind("Flash:SecurityError",function(t,u){j.trigger("Error",{code:d.SECURITY_ERROR,message:d.translate("Security error."),details:u.message,file:j.getFile(s[u.id])})});j.bind("Flash:GenericError",function(t,u){j.trigger("Error",{code:d.GENERIC_ERROR,message:d.translate("Generic error."),details:u.message,file:j.getFile(s[u.id])})});j.bind("Flash:IOError",function(t,u){j.trigger("Error",{code:d.IO_ERROR,message:d.translate("IO error."),details:u.message,file:j.getFile(s[u.id])})});j.bind("Flash:ImageError",function(t,u){j.trigger("Error",{code:parseInt(u.code),message:d.translate("Image error."),file:j.getFile(s[u.id])})});j.bind("Flash:StageEvent:rollOver",function(t){var u,v;u=b.getElementById(j.settings.browse_button);v=t.settings.browse_button_hover;if(u&&v){d.addClass(u,v)}});j.bind("Flash:StageEvent:rollOut",function(t){var u,v;u=b.getElementById(j.settings.browse_button);v=t.settings.browse_button_hover;if(u&&v){d.removeClass(u,v)}});j.bind("Flash:StageEvent:mouseDown",function(t){var u,v;u=b.getElementById(j.settings.browse_button);v=t.settings.browse_button_active;if(u&&v){d.addClass(u,v);d.addEvent(b.body,"mouseup",function(){d.removeClass(u,v)},t.id)}});j.bind("Flash:StageEvent:mouseUp",function(t){var u,v;u=b.getElementById(j.settings.browse_button);v=t.settings.browse_button_active;if(u&&v){d.removeClass(u,v)}});j.bind("QueueChanged",function(t){j.refresh()});j.bind("FilesRemoved",function(t,v){var u;for(u=0;u<v.length;u++){m().removeFile(s[v[u].id])}});j.bind("StateChanged",function(t){j.refresh()});j.bind("Refresh",function(t){var u,v,w;m().setFileFilters(j.settings.filters,j.settings.multi_selection);u=b.getElementById(t.settings.browse_button);if(u){v=d.getPos(u,b.getElementById(t.settings.container));w=d.getSize(u);d.extend(b.getElementById(t.id+"_flash_container").style,{top:v.y+"px",left:v.x+"px",width:w.w+"px",height:w.h+"px"})}});j.bind("Destroy",function(t){var u;d.removeAllEvents(b.body,t.id);delete g[t.id];delete a[t.id];u=b.getElementById(t.id+"_flash_container");if(u){h.removeChild(u)}});o({success:true})})}})})(window,document,plupload);

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
(function(e,b,c,d){var f={};function a(l,h,o,n,g){var p,j,i,k;j=google.gears.factory.create("beta.canvas");try{j.decode(l);k=Math.min(h/j.width,o/j.height);if(k<1){j.resize(Math.round(j.width*k),Math.round(j.height*k));return j.encode(g,{quality:n/100})}}catch(m){}return l}c.runtimes.Gears=c.addRuntime("gears",{getFeatures:function(){return{dragdrop:true,jpgresize:true,pngresize:true,chunks:true,progress:true,multipart:true}},init:function(i,k){var j;if(!e.google||!google.gears){return k({success:false})}try{j=google.gears.factory.create("beta.desktop")}catch(h){return k({success:false})}function g(n){var m,l,o=[],p;for(l=0;l<n.length;l++){m=n[l];p=c.guid();f[p]=m.blob;o.push(new c.File(p,m.name,m.blob.length))}i.trigger("FilesAdded",o)}i.bind("PostInit",function(){var m=i.settings,l=b.getElementById(m.drop_element);if(l){c.addEvent(l,"dragover",function(n){j.setDropEffect(n,"copy");n.preventDefault()},i.id);c.addEvent(l,"drop",function(o){var n=j.getDragData(o,"application/x-gears-files");if(n){g(n.files)}o.preventDefault()},i.id);l=0}c.addEvent(b.getElementById(m.browse_button),"click",function(r){var q=[],o,n,p;r.preventDefault();for(o=0;o<m.filters.length;o++){p=m.filters[o].extensions.split(",");for(n=0;n<p.length;n++){q.push("."+p[n])}}j.openFiles(g,{singleFile:!m.multi_selection,filter:q})},i.id)});i.bind("UploadFile",function(r,o){var t=0,s,p,q=0,n=r.settings.resize,l;if(n&&/\.(png|jpg|jpeg)$/i.test(o.name)){f[o.id]=a(f[o.id],n.width,n.height,n.quality||90,/\.png$/i.test(o.name)?"image/png":"image/jpeg")}o.size=f[o.id].length;p=r.settings.chunk_size;l=p>0;s=Math.ceil(o.size/p);if(!l){p=o.size;s=1}function m(){var y,A,v=r.settings.multipart,u=0,z={name:o.target_name||o.name},w=r.settings.url;function x(C){var B,H="----pluploadboundary"+c.guid(),E="--",G="\r\n",D,F;if(v){y.setRequestHeader("Content-Type","multipart/form-data; boundary="+H);B=google.gears.factory.create("beta.blobbuilder");c.each(c.extend(z,r.settings.multipart_params),function(J,I){B.append(E+H+G+'Content-Disposition: form-data; name="'+I+'"'+G+G);B.append(J+G)});F=c.mimeTypes[o.name.replace(/^.+\.([^.]+)/,"$1")]||"application/octet-stream";B.append(E+H+G+'Content-Disposition: form-data; name="'+r.settings.file_data_name+'"; filename="'+o.name+'"'+G+"Content-Type: "+F+G+G);B.append(C);B.append(G+E+H+E+G);D=B.getAsBlob();u=D.length-C.length;C=D}y.send(C)}if(o.status==c.DONE||o.status==c.FAILED||r.state==c.STOPPED){return}if(l){z.chunk=t;z.chunks=s}A=Math.min(p,o.size-(t*p));if(!v){w=c.buildUrl(r.settings.url,z)}y=google.gears.factory.create("beta.httprequest");y.open("POST",w);if(!v){y.setRequestHeader("Content-Disposition",'attachment; filename="'+o.name+'"');y.setRequestHeader("Content-Type","application/octet-stream")}c.each(r.settings.headers,function(C,B){y.setRequestHeader(B,C)});y.upload.onprogress=function(B){o.loaded=q+B.loaded-u;r.trigger("UploadProgress",o)};y.onreadystatechange=function(){var B;if(y.readyState==4){if(y.status==200){B={chunk:t,chunks:s,response:y.responseText,status:y.status};r.trigger("ChunkUploaded",o,B);if(B.cancelled){o.status=c.FAILED;return}q+=A;if(++t>=s){o.status=c.DONE;r.trigger("FileUploaded",o,{response:y.responseText,status:y.status})}else{m()}}else{r.trigger("Error",{code:c.HTTP_ERROR,message:c.translate("HTTP Error."),file:o,chunk:t,chunks:s,status:y.status})}}};if(t<s){x(f[o.id].slice(t*p,A))}}m()});i.bind("Destroy",function(l){var m,n,o={browseButton:l.settings.browse_button,dropElm:l.settings.drop_element};for(m in o){n=b.getElementById(o[m]);if(n){c.removeAllEvents(n,l.id)}}});k({success:true})}})})(window,document,plupload);

View File

@ -0,0 +1 @@
(function(d,a,b,c){function e(f){return a.getElementById(f)}b.runtimes.Html4=b.addRuntime("html4",{getFeatures:function(){return{multipart:true,canOpenDialog:navigator.userAgent.indexOf("WebKit")!==-1}},init:function(f,g){f.bind("Init",function(p){var j=a.body,n,h="javascript",k,x,q,z=[],r=/MSIE/.test(navigator.userAgent),t=[],m=p.settings.filters,o,l,s,w;for(o=0;o<m.length;o++){l=m[o].extensions.split(/,/);for(w=0;w<l.length;w++){s=b.mimeTypes[l[w]];if(s){t.push(s)}}}t=t.join(",");function v(){var B,y,i,A;q=b.guid();z.push(q);B=a.createElement("form");B.setAttribute("id","form_"+q);B.setAttribute("method","post");B.setAttribute("enctype","multipart/form-data");B.setAttribute("encoding","multipart/form-data");B.setAttribute("target",p.id+"_iframe");B.style.position="absolute";y=a.createElement("input");y.setAttribute("id","input_"+q);y.setAttribute("type","file");y.setAttribute("accept",t);y.setAttribute("size",1);A=e(p.settings.browse_button);if(p.features.canOpenDialog&&A){b.addEvent(e(p.settings.browse_button),"click",function(C){y.click();C.preventDefault()},p.id)}b.extend(y.style,{width:"100%",height:"100%",opacity:0,fontSize:"2em"});b.extend(B.style,{overflow:"hidden"});i=p.settings.shim_bgcolor;if(i){B.style.background=i}if(r){b.extend(y.style,{filter:"alpha(opacity=0)"})}b.addEvent(y,"change",function(F){var D=F.target,C,E=[],G;if(D.value){e("form_"+q).style.top=-1048575+"px";C=D.value.replace(/\\/g,"/");C=C.substring(C.length,C.lastIndexOf("/")+1);E.push(new b.File(q,C));if(!p.features.canOpenDialog){b.removeAllEvents(B,p.id)}else{b.removeEvent(A,"click",p.id)}b.removeEvent(y,"change",p.id);v();if(E.length){f.trigger("FilesAdded",E)}}},p.id);B.appendChild(y);j.appendChild(B);p.refresh()}function u(){var i=a.createElement("div");i.innerHTML='<iframe id="'+p.id+'_iframe" name="'+p.id+'_iframe" src="'+h+':&quot;&quot;" style="display:none"></iframe>';n=i.firstChild;j.appendChild(n);b.addEvent(n,"load",function(C){var D=C.target,B,y;if(!k){return}try{B=D.contentWindow.document||D.contentDocument||d.frames[D.id].document}catch(A){p.trigger("Error",{code:b.SECURITY_ERROR,message:b.translate("Security error."),file:k});return}y=B.documentElement.innerText||B.documentElement.textContent;if(y){k.status=b.DONE;k.loaded=1025;k.percent=100;p.trigger("UploadProgress",k);p.trigger("FileUploaded",k,{response:y})}},p.id)}if(p.settings.container){j=e(p.settings.container);j.style.position="relative"}p.bind("UploadFile",function(i,A){var B,y;if(A.status==b.DONE||A.status==b.FAILED||i.state==b.STOPPED){return}B=e("form_"+A.id);y=e("input_"+A.id);y.setAttribute("name",i.settings.file_data_name);B.setAttribute("action",i.settings.url);b.each(b.extend({name:A.target_name||A.name},i.settings.multipart_params),function(E,C){var D=a.createElement("input");b.extend(D,{type:"hidden",name:C,value:E});B.insertBefore(D,B.firstChild)});k=A;e("form_"+q).style.top=-1048575+"px";B.submit();B.parentNode.removeChild(B)});p.bind("FileUploaded",function(i){i.refresh()});p.bind("StateChanged",function(i){if(i.state==b.STARTED){u()}if(i.state==b.STOPPED){d.setTimeout(function(){b.removeEvent(n,"load",i.id);n.parentNode.removeChild(n)},0)}});p.bind("Refresh",function(A){var F,B,C,D,i,G,H,E,y;F=e(A.settings.browse_button);if(F){i=b.getPos(F,e(A.settings.container));G=b.getSize(F);H=e("form_"+q);E=e("input_"+q);b.extend(H.style,{top:i.y+"px",left:i.x+"px",width:G.w+"px",height:G.h+"px"});if(A.features.canOpenDialog){y=parseInt(F.parentNode.style.zIndex,10);if(isNaN(y)){y=0}b.extend(F.style,{position:"relative",zIndex:y});b.extend(H.style,{zIndex:y-1})}C=A.settings.browse_button_hover;D=A.settings.browse_button_active;B=A.features.canOpenDialog?F:H;if(C){b.addEvent(B,"mouseover",function(){b.addClass(F,C)},A.id);b.addEvent(B,"mouseout",function(){b.removeClass(F,C)},A.id)}if(D){b.addEvent(B,"mousedown",function(){b.addClass(F,D)},A.id);b.addEvent(a.body,"mouseup",function(){b.removeClass(F,D)},A.id)}}});f.bind("FilesRemoved",function(y,B){var A,C;for(A=0;A<B.length;A++){C=e("form_"+B[A].id);if(C){C.parentNode.removeChild(C)}}});f.bind("Destroy",function(i){var y,A,B,C={inputContainer:"form_"+q,inputFile:"input_"+q,browseButton:i.settings.browse_button};for(y in C){A=e(C[y]);if(A){b.removeAllEvents(A,i.id)}}b.removeAllEvents(a.body,i.id);b.each(z,function(E,D){B=e("form_"+E);if(B){j.removeChild(B)}})});v()});g({success:true})}})})(window,document,plupload);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

119
assets/stylesheets/dmsf.css Normal file
View File

@ -0,0 +1,119 @@
table.list tbody td, table.list tbody tr:hover td {
border: solid 1px #D7D7D7;
}
#sidebar h3 {
border: none;
}
button {
vertical-align: middle;
margin-top: 1px;
margin-bottom: 1px;
}
form.dmfs_entries {
margin-bottom: 10px;
display: block;
}
form.dmfs_entries div.controls {
float: right;
font-size: 0.9em;
}
form.dmfs_entries div.controls input, form.dmfs_entries div.controls button {
font-size: 0.9em;
}
div.filename {
padding: 0 10px 0 10px; float: right; font-size: 0.8em;
white-space: nowrap;
}
td.size {
font-size: 0.8em;
white-space: nowrap;
}
td.modified {
font-size: 0.8em;
}
td.modified img {
vertical-align:text-top;
}
td.author {
font-size: 0.8em;
}
td.version {
font-size: 0.8em;
white-space: nowrap;
}
td.version img {
vertical-align:text-top;
}
div.right_icon_box {
float: right;
white-space: nowrap;
padding: 0 5px 0 5px;
}
.dmsf_upload textarea {
width: 90%;
}
.dmsf_upload legend {
font-weight: bold;
}
.dmsf_upload .warning {
padding: 4px 4px 4px 30px;
}
.dmsf_detail textarea {
width: 90%;
}
.dmsf_detail legend {
font-weight: bold;
}
.dmsf_detail .warning {
padding: 4px 4px 4px 30px;
}
fieldset {
padding-top: 4px;
padding-right: 8px;
padding-bottom: 8px;
padding-left: 8px;
}
#uploader div.flash {
border: none;
}
.hidden {
display: none;
}
div.upload_select {
float: right;
white-space: nowrap;
line-height: 1.4em;
padding-left: 10px;
font-size: 0.9em;
}
div.upload_select input, div.upload_select select {
font-size: 0.9em;
}
input[type="checkbox"] {
margin: 1px;
}

View File

@ -0,0 +1,138 @@
/*
Plupload
------------------------------------------------------------------- */
.plupload_button {cursor: pointer;}
.plupload_wrapper {
font: normal 11px Verdana,sans-serif;
width: 100%;
}
.plupload .plupload_container input {width: 98%;}
.plupload .plupload_filelist_footer {border-width: 1px 0 0 0}
.plupload .plupload_filelist_header {border-width: 0 0 1px 0}
div.plupload .plupload_file {border-width: 0 0 1px 0}
div.plupload div.plupload_header {border-width: 0 0 1px 0; position: relative;}
.plupload_file .ui-icon {
cursor:pointer;
}
.plupload_header_content {
background-image: url('../../images/plupload/plupload-bw.png');
background-repeat: no-repeat;
background-position: 8px center;
min-height: 56px;
padding-left: 60px;
position:relative;
}
.plupload_header_content_bw {background-image: url('../../images/plupload/plupload-bw.png');}
.plupload_header_title {
font: normal 18px sans-serif;
padding: 6px 0 3px;
}
.plupload_header_text {font: normal 12px sans-serif;}
.plupload_filelist,
.plupload_filelist_content {
border-collapse: collapse;
margin: 0;
padding: 0;
width: 100%;
}
.plupload_cell {padding: 8px 6px;}
.plupload_file {
border-left: none;
border-right: none;
}
.plupload_scroll {
min-height: 168px;
_height: 168px;
overflow-y: auto;
}
.plupload_file_size, .plupload_file_status {text-align: right;}
.plupload_file_size, .plupload_file_status {width: 52px;}
.plupload_file_action {width: 16px;}
.plupload_file_name {
overflow: hidden;
padding-left: 10px;
}
.plupload_file_rename {
width:95%;
}
.plupload_progress {width: 60px;}
.plupload_progress_container {padding: 1px;}
/* Floats */
.plupload_right {float: right;}
.plupload_left {float: left;}
.plupload_clear,.plupload_clearer {clear: both;}
.plupload_clearer, .plupload_progress_bar {
display: block;
font-size: 0;
line-height: 0;
}
.plupload_clearer {height: 0;}
/* Misc */
.plupload_hidden {display: none;}
.plupload_droptext {
background: transparent;
text-align: center;
vertical-align: middle;
border: 0;
line-height: 165px;
}
.plupload_buttons, .plupload_upload_status {float: left}
.plupload_message {
position: absolute;
top: 0px;
left: 0px;
height: 100%;
width: 100%;
}
.plupload_message p {
padding:0.7em;
margin:0;
}
.plupload_message strong {
font-weight: bold;
}
plupload_message i {
font-style: italic;
}
.plupload_message p span.ui-icon {
float: left;
margin-right: 0.3em;
}
.plupload_header_content .ui-state-error,
.plupload_header_content .ui-state-highlight {
border:none;
}
.plupload_message_close {
position:absolute;
top:5px;
right:5px;
cursor:pointer;
}
.plupload .ui-sortable-placeholder {
height:35px;
}

View File

@ -0,0 +1,177 @@
/*
Plupload
------------------------------------------------------------------- */
.plupload_button {
display: -moz-inline-box; /* FF < 3*/
display: inline-block;
font: normal 12px sans-serif;
text-decoration: none;
color: #42454a;
border: 1px solid #bababa;
padding: 2px 8px 3px 20px;
margin-right: 4px;
background: #f3f3f3 url('../img/buttons.png') no-repeat 0 center;
outline: 0;
/* Optional rounded corners for browsers that support it */
-moz-border-radius: 3px;
-khtml-border-radius: 3px;
-webkit-border-radius: 3px;
border-radius: 3px;
}
.plupload_button:hover {
color: #000;
text-decoration: none;
}
.plupload_disabled, a.plupload_disabled:hover {
color: #737373;
border-color: #c5c5c5;
background: #ededed url('../img/buttons-disabled.png') no-repeat 0 center;
cursor: default;
}
.plupload_add {
background-position: -181px center;
}
.plupload_wrapper {
font: normal 11px Verdana,sans-serif;
width: 100%;
}
.plupload_container {
padding: 8px;
background: url('../img/transp50.png');
/*-moz-border-radius: 5px;*/
}
.plupload_container input {
border: 1px solid #DDD;
font: normal 11px Verdana,sans-serif;
width: 98%;
}
.plupload_header {background: #2A2C2E url('../img/backgrounds.gif') repeat-x;}
.plupload_header_content {
background: url('../img/backgrounds.gif') no-repeat 0 -317px;
min-height: 56px;
padding-left: 60px;
color: #FFF;
}
.plupload_header_title {
font: normal 18px sans-serif;
padding: 6px 0 3px;
}
.plupload_header_text {
font: normal 12px sans-serif;
}
.plupload_filelist {
margin: 0;
padding: 0;
list-style: none;
}
.plupload_scroll .plupload_filelist {
height: 185px;
background: #F5F5F5;
overflow-y: scroll;
}
.plupload_filelist li {
padding: 10px 8px;
background: #F5F5F5 url('../img/backgrounds.gif') repeat-x 0 -156px;
border-bottom: 1px solid #DDD;
}
.plupload_filelist_header, .plupload_filelist_footer {
background: #DFDFDF;
padding: 8px 8px;
color: #42454A;
}
.plupload_filelist_header {
border-top: 1px solid #EEE;
border-bottom: 1px solid #CDCDCD;
}
.plupload_filelist_footer {border-top: 1px solid #FFF; height: 22px; line-height: 20px; vertical-align: middle;}
.plupload_file_name {float: left; overflow: hidden}
.plupload_file_status {color: #777;}
.plupload_file_status span {color: #42454A;}
.plupload_file_size, .plupload_file_status, .plupload_progress {
float: right;
width: 80px;
}
.plupload_file_size, .plupload_file_status, .plupload_file_action {text-align: right;}
.plupload_filelist .plupload_file_name {width: 205px}
.plupload_file_action {
float: right;
width: 16px;
height: 16px;
margin-left: 15px;
}
.plupload_file_action * {
display: none;
width: 16px;
height: 16px;
}
li.plupload_uploading {background: #ECF3DC url('../img/backgrounds.gif') repeat-x 0 -238px;}
li.plupload_done {color:#AAA}
li.plupload_delete a {
background: url('../img/delete.gif');
}
li.plupload_failed a {
background: url('../img/error.gif');
cursor: default;
}
li.plupload_done a {
background: url('../img/done.gif');
cursor: default;
}
.plupload_progress, .plupload_upload_status {
display: none;
}
.plupload_progress_container {
margin-top: 3px;
border: 1px solid #CCC;
background: #FFF;
padding: 1px;
}
.plupload_progress_bar {
width: 0px;
height: 7px;
background: #CDEB8B;
}
.plupload_scroll .plupload_filelist_header .plupload_file_action, .plupload_scroll .plupload_filelist_footer .plupload_file_action {
margin-right: 17px;
}
/* Floats */
.plupload_clear,.plupload_clearer {clear: both;}
.plupload_clearer, .plupload_progress_bar {
display: block;
font-size: 0;
line-height: 0;
}
li.plupload_droptext {
background: transparent;
text-align: center;
vertical-align: middle;
border: 0;
line-height: 165px;
}

15
config/locales/cs.yml Normal file
View File

@ -0,0 +1,15 @@
cs:
:dmsf: "DMSF"
:label_dmsf_file_plural: "Dmsf soubory"
:warning_no_entries_selected: "No entries selected"
:error_email_to_must_be_entered: "Email To must be entered"
:notice_email_sent: "Email sent"
:warning_file_already_locked: "File already locked"
:notice_file_locked: "File locked"
:warning_file_not_locked: "File not locked"
:notice_file_unlocked: "File unlocked"
:error_only_user_that_locked_file_can_unlock_it: "Only user that locked file can unlock it"
:question_do_you_really_want_to_delete_this_entry: "Do you really want to delete this entry?"
:error_max_files_exceeded: "Limit for number of simultaneously downloadable files exceeded: "
:question_do_you_really_want_to_delete_this_revision: "Do you really want to delete this revision?"

16
config/locales/de.yml Normal file
View File

@ -0,0 +1,16 @@
# English strings go here for Rails i18n
en:
:dmsf: "DMSF"
:label_dmsf_file_plural: "Dmsf files"
:warning_no_entries_selected: "No entries selected"
:error_email_to_must_be_entered: "Email To must be entered"
:notice_email_sent: "Email sent"
:warning_file_already_locked: "File already locked"
:notice_file_locked: "File locked"
:warning_file_not_locked: "File not locked"
:notice_file_unlocked: "File unlocked"
:error_only_user_that_locked_file_can_unlock_it: "Only user that locked file can unlock it"
:question_do_you_really_want_to_delete_this_entry: "Do you really want to delete this entry?"
:error_max_files_exceeded: "Limit for number of simultaneously downloadable files exceeded: "
:question_do_you_really_want_to_delete_this_revision: "Do you really want to delete this revision?"

16
config/locales/en-GB.yml Normal file
View File

@ -0,0 +1,16 @@
# English strings go here for Rails i18n
en:
:dmsf: "DMSF"
:label_dmsf_file_plural: "Dmsf files"
:warning_no_entries_selected: "No entries selected"
:error_email_to_must_be_entered: "Email To must be entered"
:notice_email_sent: "Email sent"
:warning_file_already_locked: "File already locked"
:notice_file_locked: "File locked"
:warning_file_not_locked: "File not locked"
:notice_file_unlocked: "File unlocked"
:error_only_user_that_locked_file_can_unlock_it: "Only user that locked file can unlock it"
:question_do_you_really_want_to_delete_this_entry: "Do you really want to delete this entry?"
:error_max_files_exceeded: "Limit for number of simultaneously downloadable files exceeded: "
:question_do_you_really_want_to_delete_this_revision: "Do you really want to delete this revision?"

16
config/locales/en.yml Normal file
View File

@ -0,0 +1,16 @@
# English strings go here for Rails i18n
en:
:dmsf: "DMSF"
:label_dmsf_file_plural: "Dmsf files"
:warning_no_entries_selected: "No entries selected"
:error_email_to_must_be_entered: "Email To must be entered"
:notice_email_sent: "Email sent"
:warning_file_already_locked: "File already locked"
:notice_file_locked: "File locked"
:warning_file_not_locked: "File not locked"
:notice_file_unlocked: "File unlocked"
:error_only_user_that_locked_file_can_unlock_it: "Only user that locked file can unlock it"
:question_do_you_really_want_to_delete_this_entry: "Do you really want to delete this entry?"
:error_max_files_exceeded: "Limit for number of simultaneously downloadable files exceeded: "
:question_do_you_really_want_to_delete_this_revision: "Do you really want to delete this revision?"

16
config/locales/es.yml Normal file
View File

@ -0,0 +1,16 @@
# English strings go here for Rails i18n
en:
:dmsf: "DMSF"
:label_dmsf_file_plural: "Dmsf files"
:warning_no_entries_selected: "No entries selected"
:error_email_to_must_be_entered: "Email To must be entered"
:notice_email_sent: "Email sent"
:warning_file_already_locked: "File already locked"
:notice_file_locked: "File locked"
:warning_file_not_locked: "File not locked"
:notice_file_unlocked: "File unlocked"
:error_only_user_that_locked_file_can_unlock_it: "Only user that locked file can unlock it"
:question_do_you_really_want_to_delete_this_entry: "Do you really want to delete this entry?"
:error_max_files_exceeded: "Limit for number of simultaneously downloadable files exceeded: "
:question_do_you_really_want_to_delete_this_revision: "Do you really want to delete this revision?"

16
config/locales/fr.yml Normal file
View File

@ -0,0 +1,16 @@
# English strings go here for Rails i18n
en:
:dmsf: "DMSF"
:label_dmsf_file_plural: "Dmsf files"
:warning_no_entries_selected: "No entries selected"
:error_email_to_must_be_entered: "Email To must be entered"
:notice_email_sent: "Email sent"
:warning_file_already_locked: "File already locked"
:notice_file_locked: "File locked"
:warning_file_not_locked: "File not locked"
:notice_file_unlocked: "File unlocked"
:error_only_user_that_locked_file_can_unlock_it: "Only user that locked file can unlock it"
:question_do_you_really_want_to_delete_this_entry: "Do you really want to delete this entry?"
:error_max_files_exceeded: "Limit for number of simultaneously downloadable files exceeded: "
:question_do_you_really_want_to_delete_this_revision: "Do you really want to delete this revision?"

16
config/locales/ru.yml Normal file
View File

@ -0,0 +1,16 @@
# English strings go here for Rails i18n
en:
:dmsf: "DMSF"
:label_dmsf_file_plural: "Dmsf files"
:warning_no_entries_selected: "No entries selected"
:error_email_to_must_be_entered: "Email To must be entered"
:notice_email_sent: "Email sent"
:warning_file_already_locked: "File already locked"
:notice_file_locked: "File locked"
:warning_file_not_locked: "File not locked"
:notice_file_unlocked: "File unlocked"
:error_only_user_that_locked_file_can_unlock_it: "Only user that locked file can unlock it"
:question_do_you_really_want_to_delete_this_entry: "Do you really want to delete this entry?"
:error_max_files_exceeded: "Limit for number of simultaneously downloadable files exceeded: "
:question_do_you_really_want_to_delete_this_revision: "Do you really want to delete this revision?"

View File

@ -0,0 +1,78 @@
class CreateHierarchy < ActiveRecord::Migration
def self.up
create_table :dmsf_folders do |t|
t.string :name, :null => false
t.text :description
t.references :project, :null => false
t.references :dmsf_folder
t.boolean :notification, :default => false, :null => false
t.references :user, :null => false
t.timestamps
end
create_table :dmsf_files do |t|
t.string :name, :null => false
t.references :project, :null => false
t.references :dmsf_folder
t.boolean :notification, :default => false, :null => false
t.boolean :deleted, :default => false, :null => false
t.integer :deleted_by_user_id
t.timestamps
end
create_table :dmsf_file_revisions do |t|
t.references :dmsf_file, :null => false
t.string :disk_filename, :null => false
t.string :name, :null => false
t.references :dmsf_folder
t.integer :size
t.string :mime_type
t.string :title
t.text :description
t.references :user, :null => false
t.integer :workflow
t.text :comment
t.integer :major_version, :null => false
t.integer :minor_version, :null => false
t.integer :source_dmsf_file_revision_id
t.boolean :deleted, :default => false, :null => false
t.integer :deleted_by_user_id
t.timestamps
end
create_table :dmsf_file_locks do |t|
t.references :dmsf_file, :null => false
t.boolean :locked, :default => false, :null => false
t.references :user, :null => false
t.timestamps
end
create_table :dmsf_user_prefs do |t|
t.references :project, :null => false
t.references :user, :null => false
t.boolean :email_notify
t.timestamps
end
end
def self.down
drop_table :dmsf_file_revisions
drop_table :dmsf_files
drop_table :dmsf_folders
end
end

60
init.rb Normal file
View File

@ -0,0 +1,60 @@
require 'redmine'
Redmine::Plugin.register :redmine_dmsf do
name "DMSF"
author "Vít Jonáš"
description "Document Management System Features for Redmine"
version "0.5.0"
url "https://code.google.com/p/redmine-dmsf/"
author_url "mailto:vit.jonas@kontron-czech.com"
settings :partial => 'settings/dmsf_settings',
:default => {
"dmsf_max_file_download" => "0",
"dmsf_storage_directory" => "#{RAILS_ROOT}/files/dmsf",
"dmsf_index_database" => "#{RAILS_ROOT}/files/dmsf_index",
"dmsf_stemming_lang" => "english",
"dmsf_stemming_strategy" => "STEM_NONE"
}
menu :project_menu, :dmsf, { :controller => 'dmsf', :action => 'index' }, :caption => 'DMSF', :after => :activity, :param => :id
#delete_menu_item :project_menu, :documents
activity_provider :dmsf_files, :class_name => "DmsfFileRevision", :default => true
project_module :dmsf do
permission :browse_documents, {:dmsf => [:index]}
permission :user_preferences, {:dmsf_state => [:user_pref]}
permission :view_dmsf_files, {:dmsf => [:download_file, :download_revision, :entries_operation, :email_entries_send],
:dmsf_detail => [:file_detail]}
permission :folder_manipulation, {:dmsf_detail => [:create_folder, :delete_folder, :folder_detail, :save_folder]}
permission :file_manipulation, {:dmsf_detail => [:upload_files, :upload_file, :commit_files, :save_file, :delete_file],
:dmsf_state => [:lock_file, :unlock_file]}
permission :file_approval, {:dmsf_detail => [:approve_file, :delete_revision],
:dmsf_state => [:file_notify_activate, :file_notify_deactivate, :folder_notify_activate, :folder_notify_deactivate]}
permission :force_file_unlock, {:dmsf_state => [:force_file_unlock]}
end
Redmine::WikiFormatting::Macros.register do
desc "Wiki link to DMSF content:\n\n" +
" File: !{{dmsf(file_id)}}\n\n" +
"_file_id_ can be found in link for file download"
macro :dmsf do |obj, args|
return nil if args.length < 1 # require file id
return nil if @project == nil
entry_id = args[0].strip
file = DmsfFile.find(entry_id)
unless file.nil? || file.deleted
return nil if file.project != @project
return link_to "#{file.last_revision.title}", :controller => "dmsf", :action => "download_file", :id => @project, :file_id => file
end
nil
end
end
end
Redmine::Search.map do |search|
search.register :dmsf_files
end

View File

@ -0,0 +1,127 @@
desc <<-END_DESC
Convert project documents to DMSF folder/file structure.
Converted project must have documents and may not have DMSF active!
Available options:
* project => id or identifier of project (defaults to all projects)
Example:
rake redmine:dmsf_convert_documents project=test RAILS_ENV="production"
END_DESC
require File.expand_path(File.dirname(__FILE__) + "/../../../../../config/environment")
class DmsfConvertDocuments
def self.convert(options={})
projects = options[:project] ? [Project.find(options[:project])] : Project.find(:all)
projects.reject! {|project| !project.enabled_modules.index{|mod| mod.name == "dmsf"}.nil? }
projects.reject! {|project| project.enabled_modules.index{|mod| mod.name == "documents"}.nil? }
unless projects.nil? || projects.empty?
projects.each do |project|
puts "Processing project: " + project.identifier
project.enabled_module_names = (project.enabled_module_names << "dmsf").uniq
project.save!
folders = []
project.documents.each do |document|
folder = DmsfFolder.new
folder.project = project
folder.user = (a = document.attachments.find(:first, :order => "created_on ASC")) ? a.author : User.find(:first)
folder.name = document.title
i = 1
suffix = ""
while folders.index{|f| f.name == (folder.name + suffix)}
i+=1
suffix = "_#{i}"
end
folder.name = folder.name + suffix
folder.description = document.description
folder.save!
folders << folder;
puts "Created folder: " + folder.name
files = []
document.attachments.each do |attachment|
begin
file = DmsfFile.new
file.project = project
file.folder = folder
file.name = attachment.filename
i = 1
suffix = ""
while files.index{|f| f.name == (DmsfFileRevision.remove_extension(file.name) + suffix + File.extname(file.name))}
i+=1
suffix = "_#{i}"
end
file.name = DmsfFileRevision.remove_extension(file.name) + suffix + File.extname(file.name)
revision = DmsfFileRevision.new
revision.file = file
revision.name = file.name
revision.folder = file.folder
revision.title = DmsfFileRevision.filename_to_title(attachment.filename)
revision.description = attachment.description
revision.user = attachment.author
revision.created_at = attachment.created_on
revision.updated_at = attachment.created_on
revision.major_version = 0
revision.minor_version = 1
revision.comment = "Converted from documents"
revision.mime_type = attachment.content_type
revision.disk_filename = file.new_storage_filename
attachment_file = File.open(attachment.diskfile, "rb")
File.open(revision.disk_file, "wb") do |f|
while (buffer = attachment_file.read(8192))
f.write(buffer)
end
end
attachment_file.close
revision.size = File.size(revision.disk_file)
file.save!
revision.save!
files << file
attachment.destroy
puts "Created file: " + file.name
rescue Exception => e
puts "Creating file: " + attachment.filename + " failed"
end
end
document.destroy
end
project.enabled_module_names = project.enabled_module_names.reject {|mod| mod == "documents"}
project.save!
end
end
end
end
namespace :redmine do
task :dmsf_convert_documents => :environment do
options = {}
options[:project] = ENV['project'] if ENV['project']
DmsfConvertDocuments.convert(options)
end
end

24
test/fixtures/dmsf_folders.yml vendored Normal file
View File

@ -0,0 +1,24 @@
# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
# It is possible to use ERb template markup
one:
id: 1
name: folder1
project_id: 1
dmsf_folder_id:
user_id: 1
two:
id: 2
name: folder2
project_id: 1
dmsf_folder_id: 1
user_id: 1

View File

@ -0,0 +1,8 @@
require File.dirname(__FILE__) + '/../test_helper'
class DmsfControllerTest < ActionController::TestCase
# Replace this with your real tests.
def test_truth
assert true
end
end

5
test/test_helper.rb Normal file
View File

@ -0,0 +1,5 @@
# Load the normal Rails helper
require File.expand_path(File.dirname(__FILE__) + '/../../../../test/test_helper')
# Ensure that we are using the temporary fixture path
Engines::Testing.set_fixture_path

View File

@ -0,0 +1,11 @@
require File.dirname(__FILE__) + '/../test_helper'
class DmsfFolderTest < ActiveSupport::TestCase
fixtures :projects, :users, :dmsf_folders, :dmsf_files, :dmsf_file_revisions,
:roles, :members, :member_roles, :enabled_modules, :enumerations
def test_folder_creating
assert_not_nil(dmsf_folders(:one))
end
end