diff --git a/README.txt b/README.txt new file mode 100644 index 00000000..f03e046f --- /dev/null +++ b/README.txt @@ -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: + + +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. diff --git a/app/controllers/dmsf_controller.rb b/app/controllers/dmsf_controller.rb new file mode 100644 index 00000000..2742508e --- /dev/null +++ b/app/controllers/dmsf_controller.rb @@ -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 diff --git a/app/controllers/dmsf_detail_controller.rb b/app/controllers/dmsf_detail_controller.rb new file mode 100644 index 00000000..fab12cf2 --- /dev/null +++ b/app/controllers/dmsf_detail_controller.rb @@ -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 \ No newline at end of file diff --git a/app/controllers/dmsf_state_controller.rb b/app/controllers/dmsf_state_controller.rb new file mode 100644 index 00000000..5cf3be55 --- /dev/null +++ b/app/controllers/dmsf_state_controller.rb @@ -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 \ No newline at end of file diff --git a/app/helpers/dmsf_access_error.rb b/app/helpers/dmsf_access_error.rb new file mode 100644 index 00000000..8a8d7722 --- /dev/null +++ b/app/helpers/dmsf_access_error.rb @@ -0,0 +1,3 @@ +class DmsfAccessError < StandardError + +end diff --git a/app/helpers/dmsf_content_error.rb b/app/helpers/dmsf_content_error.rb new file mode 100644 index 00000000..fbbd2f29 --- /dev/null +++ b/app/helpers/dmsf_content_error.rb @@ -0,0 +1,3 @@ +class DmsfContentError < StandardError + +end diff --git a/app/helpers/dmsf_helper.rb b/app/helpers/dmsf_helper.rb new file mode 100644 index 00000000..7f815d1e --- /dev/null +++ b/app/helpers/dmsf_helper.rb @@ -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 diff --git a/app/helpers/dmsf_upload.rb b/app/helpers/dmsf_upload.rb new file mode 100644 index 00000000..f81fc9ce --- /dev/null +++ b/app/helpers/dmsf_upload.rb @@ -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 \ No newline at end of file diff --git a/app/helpers/dmsf_zip.rb b/app/helpers/dmsf_zip.rb new file mode 100644 index 00000000..4a6d56ee --- /dev/null +++ b/app/helpers/dmsf_zip.rb @@ -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 \ No newline at end of file diff --git a/app/models/dmsf_file.rb b/app/models/dmsf_file.rb new file mode 100644 index 00000000..03151710 --- /dev/null +++ b/app/models/dmsf_file.rb @@ -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 \ No newline at end of file diff --git a/app/models/dmsf_file_lock.rb b/app/models/dmsf_file_lock.rb new file mode 100644 index 00000000..fdf28ce1 --- /dev/null +++ b/app/models/dmsf_file_lock.rb @@ -0,0 +1,5 @@ +class DmsfFileLock < ActiveRecord::Base + unloadable + belongs_to :file, :class_name => "DmsfFile", :foreign_key => "dmsf_file_id" + belongs_to :user +end \ No newline at end of file diff --git a/app/models/dmsf_file_revision.rb b/app/models/dmsf_file_revision.rb new file mode 100644 index 00000000..3e370135 --- /dev/null +++ b/app/models/dmsf_file_revision.rb @@ -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 \ No newline at end of file diff --git a/app/models/dmsf_folder.rb b/app/models/dmsf_folder.rb new file mode 100644 index 00000000..3b1a37df --- /dev/null +++ b/app/models/dmsf_folder.rb @@ -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 + diff --git a/app/models/dmsf_mailer.rb b/app/models/dmsf_mailer.rb new file mode 100644 index 00000000..5a036fa2 --- /dev/null +++ b/app/models/dmsf_mailer.rb @@ -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 diff --git a/app/models/dmsf_user_pref.rb b/app/models/dmsf_user_pref.rb new file mode 100644 index 00000000..4942d515 --- /dev/null +++ b/app/models/dmsf_user_pref.rb @@ -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 + diff --git a/app/views/dmsf/_path.html.erb b/app/views/dmsf/_path.html.erb new file mode 100644 index 00000000..47bed373 --- /dev/null +++ b/app/views/dmsf/_path.html.erb @@ -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 %> diff --git a/app/views/dmsf/_user_pref.html.erb b/app/views/dmsf/_user_pref.html.erb new file mode 100644 index 00000000..4f53dac4 --- /dev/null +++ b/app/views/dmsf/_user_pref.html.erb @@ -0,0 +1,15 @@ +
+ <% form_tag({:controller => "dmsf_state", :action => "user_pref", :id => @project, :current => URI.escape(request.url)}, + :method=>:post) do %> +
+ <%= submit_tag("Save", :title => "Save preferences", :style => "font-size: 0.9em;", :tabindex => 10) %> +
+

Your <%= l(:dmsf) %> preferences for project

+
+ Notifications: + <%= select_tag("email_notify", + options_for_select([["Default", nil], ["Activated", true], ["Deactivated", false]], + :selected => DmsfUserPref.for(@project, User.current).email_notify), :tabindex => 1) %> +
+ <% end %> +
\ No newline at end of file diff --git a/app/views/dmsf/email_entries.html.erb b/app/views/dmsf/email_entries.html.erb new file mode 100644 index 00000000..7973d0d2 --- /dev/null +++ b/app/views/dmsf/email_entries.html.erb @@ -0,0 +1,43 @@ +<% html_title("DMSF") %> + +
+
+ +<% path = @folder.nil? ? [] : @folder.dmsf_path %> +

+<%= render(:partial => 'path', :locals => {:path => path}) %> +

+ +

Send documents by email

+ +<% form_tag({:action => "email_entries_send", :id => @project, :folder_id => @folder}, + { :method=>:post, :class => "tabular"}) do %> +
+

+ <%= label_tag("", "From:") %> + <%= h(User.current.mail) %> +

+

+ <%= label_tag("email[to]", "To:") %> + <%= text_field_tag("email[to]", @email_params["to"], :style => "width: 90%;") %> +

+

+ <%= label_tag("email[cc]", "CC:") %> + <%= text_field_tag("email[cc]", @email_params["cc"], :style => "width: 90%;") %> +

+

+ <%= label_tag("email[subject]", "Subject:") %> + <%= text_field_tag("email[subject]", @email_params["subject"], :style => "width: 90%;") %> +

+

+ <%= label_tag("", "Documents:") %> + Documents.zip + <%= hidden_field_tag("email[zipped_content]", @email_params["zipped_content"]) %> +

+

+ <%= label_tag("email[body]", "Body:") %> + <%= text_area_tag("email[body]", @email_params["body"], :rows=> "20", :style => "width: 90%;") %> +

+

<%= submit_tag("Send") %>

+
+<% end %> \ No newline at end of file diff --git a/app/views/dmsf/index.html.erb b/app/views/dmsf/index.html.erb new file mode 100644 index 00000000..32c290ee --- /dev/null +++ b/app/views/dmsf/index.html.erb @@ -0,0 +1,305 @@ +<% html_title("DMSF") %> + +
+ <% 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 %> +
+ +<% path = @folder.nil? ? [] : @folder.dmsf_path %> +

+ <%= render(:partial => 'path', :locals => {:path => path}) %> +

+ +
+<%= textilizable(@folder.description) unless @folder.nil? %> +
+ +<% +form_tag({:action => "entries_operation", :id => @project, :folder_id => @folder}, :method => :post, + :class => "dmfs_entries", :id => "Entries") do +%> + <%= hidden_field_tag("action") %> + + + + + <%= 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") %> + + + + + <% @subfolders.each do |subfolder| %> + + + + + + + + + + <% end %> + <% @files.each do |file| %> + + + + + + + + + + <% end %> + +
+ +
<%= check_box_tag("subfolders[]", subfolder.id, false, :title => "Check for multi download or email") %> + <%= link_to(h(subfolder.name), + {:action => "index", :folder_id => subfolder}, + :class => "icon icon-folder") %> + ---- +
+ <% 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 %> +

+
<%= check_box_tag("files[]", file.id, false, :title => "Check for zip download or email") %> + <%= 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") %> +
+ (<%= h(file.display_name) %>)

+
<%= number_to_human_size(file.last_revision.size) unless file.last_revision.nil? %> + <%= 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 %> + + <%= 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 %> + <%= h(file.last_revision.user) unless file.last_revision.nil? %> +
+ <%= 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") %> +   + <% 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 %> +   + <% 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 %> +

+
+
+ <%= submit_tag("Download", :title => "Download checked in zip archive", :name => "download_entries") %> + + <%= submit_tag("Email", :title => "Send checked by email", :name => "email_entries") %> +
+
+<% end %> + +
+

File Upload

+<% form_tag({:controller => "dmsf_detail", :action => "upload_files", :id => @project, :folder_id => @folder}, + :id => "uploadform", :method=>:post, :multipart => true) do %> +
+
+ File size: +
+There can be uploaded maximum of 20 files at once. To upload files greater than 2GB you must have 64b browser. +
+
+
+

+ +<%= file_field_tag("uploaded_files[1]", :size => 30, :id => nil) %> + +
+<%= 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) %>) + +

+<%= submit_tag("Upload") %> +
+<% end %> +
+ +<%= 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" %> + +<% end %> \ No newline at end of file diff --git a/app/views/dmsf_detail/_file_locked.html.erb b/app/views/dmsf_detail/_file_locked.html.erb new file mode 100644 index 00000000..9a9d74a4 --- /dev/null +++ b/app/views/dmsf_detail/_file_locked.html.erb @@ -0,0 +1 @@ +

File locked!

\ No newline at end of file diff --git a/app/views/dmsf_detail/_file_new_revision.html.erb b/app/views/dmsf_detail/_file_new_revision.html.erb new file mode 100644 index 00000000..2bc3e816 --- /dev/null +++ b/app/views/dmsf_detail/_file_new_revision.html.erb @@ -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 +%> +
+
+ Create New Revision [-] +
+
+

+ <%= label_tag("file[title]", "Title:") %> + <%= text_field_tag("file[title]", @file.last_revision.title, :size => "32") %> +

+

+ <%= label_tag("file[description]", "Description:") %> + <%= text_area_tag("file[description]", @file.last_revision.description, :rows=> "6") %> +

+ +

+ <%= label_tag("file[file]", "New content:") %> + <%= file_field_tag("file[file]", :size => 30, :id => "fileFileUpload") %> +
+ + (<%= l(:label_max_size) %>: <%= number_to_human_size(Setting.attachment_max_size.to_i.kilobytes) %>) + +

+

+ <%= 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
+ <%= radio_button_tag("file[version]", "minor", false, :id => "fileMinorVersionRadio") %> + <%= @file.last_revision.major_version %>.<%= @file.last_revision.minor_version + 1 %> Minor
+ <%= radio_button_tag("file[version]", "major") %> + <%= @file.last_revision.major_version + 1 %>.0 Major
+

+

+ <%= 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)) %> +

+
+
+

+ <%= label_tag("file[comment]", "Comment:") %> + <%= text_area_tag("file[comment]", "", :rows=> "2") %> +

+
+
+
+ <%= submit_tag("Create") %> +
+
+
+<% end %> diff --git a/app/views/dmsf_detail/_upload_file.html.erb b/app/views/dmsf_detail/_upload_file.html.erb new file mode 100644 index 00000000..35afb078 --- /dev/null +++ b/app/views/dmsf_detail/_upload_file.html.erb @@ -0,0 +1,38 @@ +
+ <%= hidden_field_tag("commited_files[#{i}][disk_filename]", upload.disk_filename) %> +
+ <%= h(upload.name) %> + <%= hidden_field_tag("commited_files[#{i}][name]", upload.name) %> +
+

+ <%= label_tag("commited_files[#{i}][title]", "Title:") %> + <%= text_field_tag("commited_files[#{i}][title]", upload.title, :size => "32") %> +

+

+ <%= label_tag("commited_files[#{i}][description]", "Description:") %> + <%= text_area_tag("commited_files[#{i}][description]", upload.description, :rows=> "6") %> +

+

+ <%= label_tag("commited_files[#{i}][version]_minor", "Version:") %> + <%= radio_button_tag("commited_files[#{i}][version]", "minor", true) %> + <%= upload.major_version %>.<%= upload.minor_version + 1 %>
+ <%= radio_button_tag("commited_files[#{i}][version]", "major") %> + <%= upload.major_version + 1 %>.0
+

+
+
+

+ <%= label_tag("commited_files[#{i}][comment]", "Comment:") %> + <%= text_area_tag("commited_files[#{i}][comment]", upload.comment, :rows=> "2") %> +

+

+ <%= label_tag("", "Mime:") %> + <%= h(upload.mime_type) %> +

+

+ <%= label_tag("", "Size:") %> + <%= number_to_human_size(upload.size) %> +

+
+
+
\ No newline at end of file diff --git a/app/views/dmsf_detail/_upload_file_locked.html.erb b/app/views/dmsf_detail/_upload_file_locked.html.erb new file mode 100644 index 00000000..ff2ccad0 --- /dev/null +++ b/app/views/dmsf_detail/_upload_file_locked.html.erb @@ -0,0 +1,31 @@ +
+ <%= hidden_field_tag("commited_files[#{i}][disk_filename]", upload.disk_filename) %> +
+ <%= h(upload.name) %> +

File locked!

+
+

+ <%= label_tag("", "Title:") %> + <%= h(upload.title) %> +

+

+ <%= label_tag("", "Description:") %> + <%= h(upload.description) %> +

+

+ <%= label_tag("", "Version:") %> + <%= upload.major_version %>.<%= upload.minor_version %> +

+
+
+

+ <%= label_tag("", "Mime:") %> + <%= h(upload.mime_type) %> +

+

+ <%= label_tag("", "Size:") %> + <%= number_to_human_size(upload.size) %> +

+
+
+
diff --git a/app/views/dmsf_detail/file_detail.html.erb b/app/views/dmsf_detail/file_detail.html.erb new file mode 100644 index 00000000..1f436738 --- /dev/null +++ b/app/views/dmsf_detail/file_detail.html.erb @@ -0,0 +1,114 @@ +<% html_title("DMSF") %> + +
+
+ +<% path = @file.folder.nil? ? [] : @file.folder.dmsf_path %> +

+<%= render(:partial => "/dmsf/path", :locals => {:path => path}) %> +/ +<%= h(@file.last_revision.title) %>
<%= h(@file.name) %>
+<% if @file.notification %> + <%= image_tag("notify.png", :plugin => "redmine_dmsf", :alt => "Not. act.", + :title => "Notifications active") %> +<% end %> +

+ +<% 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 %> + +

Revisions

+<% @file.revisions.each do |revision| %> +
+
+ + <%= revision.source_revision.nil? ? "Created:" : "Changed:" %> + <%= revision.updated_at.strftime("%Y-%m-%d %H:%M:%S") %> + by + <%= h(revision.user) %> + +
+

+ <%= label_tag("", "Title:") %> + <%= h(revision.title) %> +

+

+ <%= label_tag("", "Description:") %> + <%= h(revision.description) %> +

+

+ <%= label_tag("", "Version:") %> + <%= revision.major_version %>.<%= revision.minor_version %> +

+

+ <%= label_tag("", "Workflow:") %> + <%= case revision.workflow + when 1 then "Waiting for approval" + when 2 then "Approved" + else "None" + end %> +

+
+
+

+ <%= label_tag("", "Comment:") %> + <%= h(revision.comment) %> +

+

+ <%= label_tag("", "Mime:") %> + <%= h(revision.mime_type) %> +

+

+ <%= label_tag("", "Size:") %> + <%= number_to_human_size(revision.size) %> +

+
+
+
+ <%= 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 %> +
+
+
+<% 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" %> + +<% end %> \ No newline at end of file diff --git a/app/views/dmsf_detail/folder_detail.html.erb b/app/views/dmsf_detail/folder_detail.html.erb new file mode 100644 index 00000000..e879493a --- /dev/null +++ b/app/views/dmsf_detail/folder_detail.html.erb @@ -0,0 +1,29 @@ +<% html_title("DMSF") %> + +
+
+ +

+<%= render(:partial => "/dmsf/path", :locals => {:path => @folder.dmsf_path}) %> +

+ +<% form_tag({:action => "save_folder", :id => @project, :folder_id => @folder}, + :method=>:post, :multipart => true, :class => "tabular") do %> +
+

+ <%= label_tag("title", "Title:") %> + <%= text_field_tag("title", @folder.name, :size => "32") %> +

+

+ <%= label_tag("description", "Description:") %> + <%= text_area_tag("description", @folder.description, :rows => 15, :class => "wiki-edit") %> +

+
+ <%= submit_tag("Save") %> +<% end %> + +<%= wikitoolbar_for "description" %> + +<% content_for :header_tags do %> + <%= stylesheet_link_tag "dmsf", :plugin => "redmine_dmsf" %> +<% end %> diff --git a/app/views/dmsf_detail/upload_file.html.erb b/app/views/dmsf_detail/upload_file.html.erb new file mode 100644 index 00000000..ab384492 --- /dev/null +++ b/app/views/dmsf_detail/upload_file.html.erb @@ -0,0 +1,2 @@ +{"original_filename":"<%= h(@tempfile.original_filename) %>", "content_type":"<%= h(@tempfile.content_type) %>", +"disk_filename":"<%= h(@disk_filename) %>"} \ No newline at end of file diff --git a/app/views/dmsf_detail/upload_files.html.erb b/app/views/dmsf_detail/upload_files.html.erb new file mode 100644 index 00000000..242b86c2 --- /dev/null +++ b/app/views/dmsf_detail/upload_files.html.erb @@ -0,0 +1,41 @@ +<% html_title("DMSF") %> + +
+
+ +<% path = @folder.nil? ? [] : @folder.dmsf_path %> +

+ <%= render(:partial => '/dmsf/path', :locals => {:path => path}) %> +

+ +
+<%= textilizable(@folder.description) unless @folder.nil? %> +
+ +

Uploaded Files

+<% +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" %> + +<% end %> \ No newline at end of file diff --git a/app/views/dmsf_mailer/files_deleted.text.html.rhtml b/app/views/dmsf_mailer/files_deleted.text.html.rhtml new file mode 100644 index 00000000..db55afd1 --- /dev/null +++ b/app/views/dmsf_mailer/files_deleted.text.html.rhtml @@ -0,0 +1,16 @@ + + + + + + + User <%= @user %> deleted following files in project <%= @project.name %>: + <% @files.each do |file| %> +

+ <%= h(file.dmsf_path_str) %> + (<%= number_to_human_size(file.last_revision.size) %>), + version: <%= file.last_revision.major_version %>.<%= file.last_revision.minor_version %> +

+ <% end %> + + \ No newline at end of file diff --git a/app/views/dmsf_mailer/files_deleted.text.plain.rhtml b/app/views/dmsf_mailer/files_deleted.text.plain.rhtml new file mode 100644 index 00000000..764b3a3e --- /dev/null +++ b/app/views/dmsf_mailer/files_deleted.text.plain.rhtml @@ -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 %> \ No newline at end of file diff --git a/app/views/dmsf_mailer/files_updated.text.html.rhtml b/app/views/dmsf_mailer/files_updated.text.html.rhtml new file mode 100644 index 00000000..fc651d36 --- /dev/null +++ b/app/views/dmsf_mailer/files_updated.text.html.rhtml @@ -0,0 +1,21 @@ + + + + + + + User <%= @user %> actualized following files in project <%= @project.name %>: + <% @files.each do |file| %> +

+ <%= 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 }) %> +

+ <% end %> + + \ No newline at end of file diff --git a/app/views/dmsf_mailer/files_updated.text.plain.rhtml b/app/views/dmsf_mailer/files_updated.text.plain.rhtml new file mode 100644 index 00000000..a1f3ed64 --- /dev/null +++ b/app/views/dmsf_mailer/files_updated.text.plain.rhtml @@ -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 %> \ No newline at end of file diff --git a/app/views/settings/_dmsf_settings.erb b/app/views/settings/_dmsf_settings.erb new file mode 100644 index 00000000..5d614afb --- /dev/null +++ b/app/views/settings/_dmsf_settings.erb @@ -0,0 +1,42 @@ +

+ <%=content_tag(:label, "Maximum files download:") %> + <%=text_field_tag "settings[dmsf_max_file_download]", @settings["dmsf_max_file_download"], :size=>10 %>
+ (<%=l(:label_default)%>: 0) +
+ Limits maximum number of files downloaded in zip or sent via email. "0" means unlimited. +

+ +

+ <%=content_tag(:label, "File storage directory:") %> + <%=text_field_tag "settings[dmsf_storage_directory]", @settings["dmsf_storage_directory"], :size=>50 %>
+ (<%=l(:label_default)%>: <%="#{RAILS_ROOT}/files/dmsf"%>) +

+ +

+ <%=content_tag(:label, "Index database:") %> + <%=text_field_tag 'settings[dmsf_index_database]', @settings['dmsf_index_database'], :size=>50 %>
+ (<%=l(:label_default)%>: <%="#{RAILS_ROOT}/files/dmsf_index"%>) +

+ +

+ <%=content_tag(:label, "Stemming Language:") %> + <%=text_field_tag 'settings[dmsf_stemming_lang]', @settings['dmsf_stemming_lang'] %>
+ (<%=l(:label_default)%>: english )
+
+ 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) +

+ +

+ <%=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)
+ <%=radio_button_tag 'settings[dmsf_stemming_strategy]', 'STEM_SOME', @settings['dmsf_stemming_strategy'] == 'STEM_SOME' %> Stem some
+ <%=radio_button_tag 'settings[dmsf_stemming_strategy]', 'STEM_ALL', @settings['dmsf_stemming_strategy'] == 'STEM_ALL' %> Stem all
+
+ This controls how the query parser will apply the stemming algorithm. The default value is STEM_NONE. The possible values are: +
+ * STEM_NONE: Don't perform any stemming.
+ * 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'.
+ * STEM_ALL: Search for stemmed forms of all words (note: no 'Z' prefix is added).
+
+ Note that the stemming algorithm is only applied to words in probabilistic fields - boolean filter terms are never stemmed.
+

diff --git a/assets/images/approve.png b/assets/images/approve.png new file mode 100644 index 00000000..fc25958a Binary files /dev/null and b/assets/images/approve.png differ diff --git a/assets/images/approved.png b/assets/images/approved.png new file mode 100644 index 00000000..da85f26d Binary files /dev/null and b/assets/images/approved.png differ diff --git a/assets/images/askforapproval.png b/assets/images/askforapproval.png new file mode 100644 index 00000000..c8de1657 Binary files /dev/null and b/assets/images/askforapproval.png differ diff --git a/assets/images/delete.png b/assets/images/delete.png new file mode 100644 index 00000000..ec01ecd3 Binary files /dev/null and b/assets/images/delete.png differ diff --git a/assets/images/dmsf.png b/assets/images/dmsf.png new file mode 100644 index 00000000..fc221f94 Binary files /dev/null and b/assets/images/dmsf.png differ diff --git a/assets/images/filedetails.png b/assets/images/filedetails.png new file mode 100644 index 00000000..0ef642fa Binary files /dev/null and b/assets/images/filedetails.png differ diff --git a/assets/images/lock.png b/assets/images/lock.png new file mode 100644 index 00000000..653ce555 Binary files /dev/null and b/assets/images/lock.png differ diff --git a/assets/images/locked.png b/assets/images/locked.png new file mode 100644 index 00000000..d7341e71 Binary files /dev/null and b/assets/images/locked.png differ diff --git a/assets/images/lockedbycurrent.png b/assets/images/lockedbycurrent.png new file mode 100644 index 00000000..3e6adbb4 Binary files /dev/null and b/assets/images/lockedbycurrent.png differ diff --git a/assets/images/notify.png b/assets/images/notify.png new file mode 100644 index 00000000..24067ba7 Binary files /dev/null and b/assets/images/notify.png differ diff --git a/assets/images/notifynot.png b/assets/images/notifynot.png new file mode 100644 index 00000000..dc8eee6c Binary files /dev/null and b/assets/images/notifynot.png differ diff --git a/assets/images/operations.png b/assets/images/operations.png new file mode 100644 index 00000000..8bf76fbb Binary files /dev/null and b/assets/images/operations.png differ diff --git a/assets/images/plupload/backgrounds.gif b/assets/images/plupload/backgrounds.gif new file mode 100644 index 00000000..39e33ebc Binary files /dev/null and b/assets/images/plupload/backgrounds.gif differ diff --git a/assets/images/plupload/buttons-disabled.png b/assets/images/plupload/buttons-disabled.png new file mode 100644 index 00000000..afa11af9 Binary files /dev/null and b/assets/images/plupload/buttons-disabled.png differ diff --git a/assets/images/plupload/buttons.png b/assets/images/plupload/buttons.png new file mode 100644 index 00000000..153e7388 Binary files /dev/null and b/assets/images/plupload/buttons.png differ diff --git a/assets/images/plupload/delete.gif b/assets/images/plupload/delete.gif new file mode 100644 index 00000000..78ca8b3b Binary files /dev/null and b/assets/images/plupload/delete.gif differ diff --git a/assets/images/plupload/done.gif b/assets/images/plupload/done.gif new file mode 100644 index 00000000..29f3ed7c Binary files /dev/null and b/assets/images/plupload/done.gif differ diff --git a/assets/images/plupload/error.gif b/assets/images/plupload/error.gif new file mode 100644 index 00000000..4682b630 Binary files /dev/null and b/assets/images/plupload/error.gif differ diff --git a/assets/images/plupload/plupload-bw.png b/assets/images/plupload/plupload-bw.png new file mode 100644 index 00000000..bb4147e8 Binary files /dev/null and b/assets/images/plupload/plupload-bw.png differ diff --git a/assets/images/plupload/plupload.png b/assets/images/plupload/plupload.png new file mode 100644 index 00000000..74fa3ad3 Binary files /dev/null and b/assets/images/plupload/plupload.png differ diff --git a/assets/images/plupload/throbber.gif b/assets/images/plupload/throbber.gif new file mode 100644 index 00000000..4ae8b16a Binary files /dev/null and b/assets/images/plupload/throbber.gif differ diff --git a/assets/images/plupload/transp50.png b/assets/images/plupload/transp50.png new file mode 100644 index 00000000..eb0efe10 Binary files /dev/null and b/assets/images/plupload/transp50.png differ diff --git a/assets/images/unlock.png b/assets/images/unlock.png new file mode 100644 index 00000000..13132c2b Binary files /dev/null and b/assets/images/unlock.png differ diff --git a/assets/images/unlockdisabled.png b/assets/images/unlockdisabled.png new file mode 100644 index 00000000..faba7693 Binary files /dev/null and b/assets/images/unlockdisabled.png differ diff --git a/assets/images/waitingforapproval.png b/assets/images/waitingforapproval.png new file mode 100644 index 00000000..9236c0e6 Binary files /dev/null and b/assets/images/waitingforapproval.png differ diff --git a/assets/images/workflowdisabled.png b/assets/images/workflowdisabled.png new file mode 100644 index 00000000..90435fcf Binary files /dev/null and b/assets/images/workflowdisabled.png differ diff --git a/assets/javascripts/plupload/gears_init.js b/assets/javascripts/plupload/gears_init.js new file mode 100644 index 00000000..5f44f09b --- /dev/null +++ b/assets/javascripts/plupload/gears_init.js @@ -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}; + } +})(); diff --git a/assets/javascripts/plupload/i18n/cs.js b/assets/javascripts/plupload/i18n/cs.js new file mode 100644 index 00000000..1ee5d5fc --- /dev/null +++ b/assets/javascripts/plupload/i18n/cs.js @@ -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ů' +}); \ No newline at end of file diff --git a/assets/javascripts/plupload/i18n/da.js b/assets/javascripts/plupload/i18n/da.js new file mode 100644 index 00000000..fc958965 --- /dev/null +++ b/assets/javascripts/plupload/i18n/da.js @@ -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.' +}); \ No newline at end of file diff --git a/assets/javascripts/plupload/i18n/de.js b/assets/javascripts/plupload/i18n/de.js new file mode 100644 index 00000000..96429d31 --- /dev/null +++ b/assets/javascripts/plupload/i18n/de.js @@ -0,0 +1,25 @@ +// German +plupload.addI18n({ + 'Select files' : 'Wählen Sie die Dateien:', + 'Add files to the upload queue and click the start button.' : 'Dateien hinzufügen und danach auf \'Starten des Uploads\' klicken und die Datei hochzuladen.', + 'Filename' : 'Dateiname', + 'Status' : 'Status', + 'Size' : 'Größe', + 'Add files' : 'Hinzufü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ügbar', + 'Drag files here.' : 'Ziehen Sie die Dateien hier hin', + 'File extension error.': 'Dateiendungs Fehler.', + 'File size error.': 'Dateigröß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ügen', + 'Start Upload': 'Starten des Uploads.', + '%d files queued': '%d Dateien in der Warteschlange.' +}); \ No newline at end of file diff --git a/assets/javascripts/plupload/i18n/es.js b/assets/javascripts/plupload/i18n/es.js new file mode 100644 index 00000000..2379421f --- /dev/null +++ b/assets/javascripts/plupload/i18n/es.js @@ -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ñ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í', + 'File extension error.': 'Error de extensión de archivo.', + 'File size error.': 'Error de tamaño de archivo.', + 'Init error.': 'Error de inicialización.', + 'HTTP Error.': 'Error de HTTP.', + 'Security error.': 'Error de seguridad.', + 'Generic error.': 'Error gené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.' +}); \ No newline at end of file diff --git a/assets/javascripts/plupload/i18n/fr.js b/assets/javascripts/plupload/i18n/fr.js new file mode 100644 index 00000000..724706a2 --- /dev/null +++ b/assets/javascripts/plupload/i18n/fr.js @@ -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.' +}); \ No newline at end of file diff --git a/assets/javascripts/plupload/i18n/it.js b/assets/javascripts/plupload/i18n/it.js new file mode 100644 index 00000000..7946578a --- /dev/null +++ b/assets/javascripts/plupload/i18n/it.js @@ -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.' +}); \ No newline at end of file diff --git a/assets/javascripts/plupload/i18n/nl.js b/assets/javascripts/plupload/i18n/nl.js new file mode 100644 index 00000000..8372c88b --- /dev/null +++ b/assets/javascripts/plupload/i18n/nl.js @@ -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.' +}); \ No newline at end of file diff --git a/assets/javascripts/plupload/i18n/pt-br.js b/assets/javascripts/plupload/i18n/pt-br.js new file mode 100644 index 00000000..bd696d3f --- /dev/null +++ b/assets/javascripts/plupload/i18n/pt-br.js @@ -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: ' +}); diff --git a/assets/javascripts/plupload/i18n/ru.js b/assets/javascripts/plupload/i18n/ru.js new file mode 100644 index 00000000..a78af8e5 --- /dev/null +++ b/assets/javascripts/plupload/i18n/ru.js @@ -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.': 'Ошибка ввода-вывода.' +}); \ No newline at end of file diff --git a/assets/javascripts/plupload/i18n/sv.js b/assets/javascripts/plupload/i18n/sv.js new file mode 100644 index 00000000..11c75245 --- /dev/null +++ b/assets/javascripts/plupload/i18n/sv.js @@ -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' +}); \ No newline at end of file diff --git a/assets/javascripts/plupload/jquery.plupload.queue.min.js b/assets/javascripts/plupload/jquery.plupload.queue.min.js new file mode 100644 index 00000000..e4379fb8 --- /dev/null +++ b/assets/javascripts/plupload/jquery.plupload.queue.min.js @@ -0,0 +1 @@ +(function(c){var d={};function a(e){return plupload.translate(e)||e}function b(f,e){e.contents().each(function(g,h){h=c(h);if(!h.is(".plupload")){h.remove()}});e.prepend('
'+a("Select files")+'
'+a("Add files to the upload queue and click the start button.")+'
'+a("Filename")+'
 
'+a("Status")+'
'+a("Size")+'
 
    ')}c.fn.pluploadQueue=function(e){if(e){this.each(function(){var j,i,k;i=c(this);k=i.attr("id");if(!k){k=plupload.guid();i.attr("id",k)}j=new plupload.Uploader(c.extend({dragdrop:true,container:k},e));d[k]=j;function h(l){var n;if(l.status==plupload.DONE){n="plupload_done"}if(l.status==plupload.FAILED){n="plupload_failed"}if(l.status==plupload.QUEUED){n="plupload_delete"}if(l.status==plupload.UPLOADING){n="plupload_uploading"}var m=c("#"+l.id).attr("class",n).find("a").css("display","block");if(l.hint){m.attr("title",l.hint)}}function f(){c("span.plupload_total_status",i).html(j.total.percent+"%");c("div.plupload_progress_bar",i).css("width",j.total.percent+"%");c("span.plupload_upload_status",i).text(a("Uploaded %d/%d files").replace(/%d\/%d/,j.total.uploaded+"/"+j.files.length))}function g(){var m=c("ul.plupload_filelist",i).html(""),n=0,l;c.each(j.files,function(p,o){l="";if(o.status==plupload.DONE){if(o.target_name){l+=''}l+='';l+='';n++;c("#"+k+"_count").val(n)}m.append('
  • '+o.name+'
    '+o.percent+'%
    '+plupload.formatSize(o.size)+'
     
    '+l+"
  • ");h(o);c("#"+o.id+".plupload_delete a").click(function(q){c("#"+o.id).remove();j.removeFile(o);q.preventDefault()})});c("span.plupload_total_file_size",i).html(plupload.formatSize(j.total.size));if(j.total.queued===0){c("span.plupload_add_text",i).text(a("Add files."))}else{c("span.plupload_add_text",i).text(j.total.queued+" files queued.")}c("a.plupload_start",i).toggleClass("plupload_disabled",j.files.length==(j.total.uploaded+j.total.failed));m[0].scrollTop=m[0].scrollHeight;f();if(!j.files.length&&j.features.dragdrop&&j.settings.dragdrop){c("#"+k+"_filelist").append('
  • '+a("Drag files here.")+"
  • ")}}j.bind("UploadFile",function(l,m){c("#"+m.id).addClass("plupload_current_file")});j.bind("Init",function(l,m){b(k,i);if(!e.unique_names&&e.rename){c("#"+k+"_filelist div.plupload_file_name span",i).live("click",function(s){var q=c(s.target),o,r,n,p="";o=l.getFile(q.parents("li")[0].id);n=o.name;r=/^(.+)(\.[^.]+)$/.exec(n);if(r){n=r[1];p=r[2]}q.hide().after('');q.next().val(n).focus().blur(function(){q.show().next().remove()}).keydown(function(u){var t=c(this);if(u.keyCode==13){u.preventDefault();o.name=t.val()+p;q.text(o.name);t.blur()}})})}c("a.plupload_add",i).attr("id",k+"_browse");l.settings.browse_button=k+"_browse";if(l.features.dragdrop&&l.settings.dragdrop){l.settings.drop_element=k+"_filelist";c("#"+k+"_filelist").append('
  • '+a("Drag files here.")+"
  • ")}c("#"+k+"_container").attr("title","Using runtime: "+m.runtime);c("a.plupload_start",i).click(function(n){if(!c(this).hasClass("plupload_disabled")){j.start()}n.preventDefault()});c("a.plupload_stop",i).click(function(n){n.preventDefault();j.stop()});c("a.plupload_start",i).addClass("plupload_disabled")});j.init();j.bind("Error",function(l,o){var m=o.file,n;if(m){n=o.message;if(o.details){n+=" ("+o.details+")"}if(o.code==plupload.FILE_SIZE_ERROR){alert(a("Error: File to large: ")+m.name)}if(o.code==plupload.FILE_EXTENSION_ERROR){alert(a("Error: Invalid file extension: ")+m.name)}m.hint=n;c("#"+m.id).attr("class","plupload_failed").find("a").css("display","block").attr("title",n)}});j.bind("StateChanged",function(){if(j.state===plupload.STARTED){c("li.plupload_delete a,div.plupload_buttons",i).hide();c("span.plupload_upload_status,div.plupload_progress,a.plupload_stop",i).css("display","block");c("span.plupload_upload_status",i).text("Uploaded "+j.total.uploaded+"/"+j.files.length+" files");if(e.multiple_queues){c("span.plupload_total_status,span.plupload_total_file_size",i).show()}}else{g();c("a.plupload_stop,div.plupload_progress",i).hide();c("a.plupload_delete",i).css("display","block")}});j.bind("QueueChanged",g);j.bind("FileUploaded",function(l,m){h(m)});j.bind("UploadProgress",function(l,m){c("#"+m.id+" div.plupload_file_status",i).html(m.percent+"%");h(m);f();if(e.multiple_queues&&j.total.uploaded+j.total.failed==j.files.length){c(".plupload_buttons,.plupload_upload_status",i).css("display","inline");c(".plupload_start",i).addClass("plupload_disabled");c("span.plupload_total_status,span.plupload_total_file_size",i).hide()}});if(e.setup){e.setup(j)}});return this}else{return d[c(this[0]).attr("id")]}}})(jQuery); \ No newline at end of file diff --git a/assets/javascripts/plupload/jquery.ui.plupload.js b/assets/javascripts/plupload/jquery.ui.plupload.js new file mode 100644 index 00000000..cc039f5a --- /dev/null +++ b/assets/javascripts/plupload/jquery.ui.plupload.js @@ -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( + '
    ' + + '
    ' + + '
    ' + + + '
    ' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
    ' + _('Filename') + '' + _('Status') + '' + _('Size') + ' 
    ' + + + '
    ' + + '
    ' + + '
    ' + + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
    ' + + '
    ' + + '
    ' + + '
    ' + + '' + + '
    ' + ); +} + + +$.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 = '' + err.message + ''; + details = err.details; + + if (details) { + message += "
    " + err.details + ""; + } 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! %s 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 += "
    " + details + ""; + } + + 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 += ''; + } + fields += ''; + fields += ''; + + count++; + self.counter.val(count); + } + + filelist.append( + '' + + '' + file.name + '' + + '' + file.percent + '%' + + '' + plupload.formatSize(file.size) + '' + + '
    ' + fields + '' + + '' + ); + + 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('' + _("Drag files here.") + ''); + } 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(''); + 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('' + _("Drag files here.") + ''); + + 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 = $( + '
    ' + + '' + + '

    ' + message + '

    ' + + '
    ' + ); + + 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)); diff --git a/assets/javascripts/plupload/plupload.browserplus.min.js b/assets/javascripts/plupload/plupload.browserplus.min.js new file mode 100644 index 00000000..a5b20b3b --- /dev/null +++ b/assets/javascripts/plupload/plupload.browserplus.min.js @@ -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;l0){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';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":"gt","&":"amp",'"':"quot","'":"#39"},l=/[<>&\"\']/g,b,c=window.setTimeout,d={},e;function h(){this.returnValue=false}function j(){this.cancelBubble=true}(function(n){var o=n.split(/,/),p,r,q;for(p=0;p0){g.each(o,function(r,q){n[q]=r})}});return n},cleanName:function(n){var o,p;p=[/[\300-\306]/g,"A",/[\340-\346]/g,"a",/\307/g,"C",/\347/g,"c",/[\310-\313]/g,"E",/[\350-\353]/g,"e",/[\314-\317]/g,"I",/[\354-\357]/g,"i",/\321/g,"N",/\361/g,"n",/[\322-\330]/g,"O",/[\362-\370]/g,"o",/[\331-\334]/g,"U",/[\371-\374]/g,"u"];for(o=0;o0?"&":"?")+p}return o},each:function(q,r){var p,o,n;if(q){p=q.length;if(p===b){for(o in q){if(q.hasOwnProperty(o)){if(r(q[o],o)===false){return}}}}else{for(n=0;n1073741824){return Math.round(n/1073741824,1)+" GB"}if(n>1048576){return Math.round(n/1048576,1)+" MB"}if(n>1024){return Math.round(n/1024,1)+" KB"}return n+" b"},getPos:function(o,s){var t=0,r=0,v,u=document,p,q;o=o;s=s||u.body;function n(B){var z,A,w=0,C=0;if(B){A=B.getBoundingClientRect();z=u.compatMode==="CSS1Compat"?u.documentElement:u.body;w=A.left+z.scrollLeft;C=A.top+z.scrollTop}return{x:w,y:C}}if(o&&o.getBoundingClientRect&&(navigator.userAgent.indexOf("MSIE")>0&&u.documentMode!==8)){p=n(o);q=n(s);return{x:p.x-q.x,y:p.y-q.y}}v=o;while(v&&v!=s&&v.nodeType){t+=v.offsetLeft||0;r+=v.offsetTop||0;v=v.offsetParent}v=o.parentNode;while(v&&v!=s&&v.nodeType){t-=v.scrollLeft||0;r-=v.scrollTop||0;v=v.parentNode}return{x:t,y:r}},getSize:function(n){return{w:n.offsetWidth||n.clientWidth,h:n.offsetHeight||n.clientHeight}},parseSize:function(n){var o;if(typeof(n)=="string"){n=/^([0-9]+)([mgk]+)$/.exec(n.toLowerCase().replace(/[^0-9mkg]/g,""));o=n[2];n=+n[1];if(o=="g"){n*=1073741824}if(o=="m"){n*=1048576}if(o=="k"){n*=1024}}return n},xmlEncode:function(n){return n?(""+n).replace(l,function(o){return a[o]?"&"+a[o]+";":o}):n},toArray:function(p){var o,n=[];for(o=0;o=0;p--){if(r[p].key===q||r[p].orig===u){if(t.detachEvent){t.detachEvent("on"+o,r[p].func)}else{if(t.removeEventListener){t.removeEventListener(o,r[p].func,false)}}r[p].orig=null;r[p].func=null;r.splice(p,1);if(u!==b){break}}}if(!r.length){delete d[t[e]][o]}if(n(d[t[e]])){delete d[t[e]];try{delete t[e]}catch(s){t[e]=b}}},removeAllEvents:function(o){var n=arguments[1];if(o[e]===b||!o[e]){return}g.each(d[o[e]],function(q,p){g.removeEvent(o,p,n)})}};g.Uploader=function(q){var o={},t,s=[],p;t=new g.QueueProgress();q=g.extend({chunk_size:0,multipart:true,multi_selection:true,file_data_name:"file",filters:[]},q);function r(){var v,w=0,u;if(this.state==g.STARTED){for(u=0;u0?Math.ceil(t.uploaded/s.length*100):0}else{t.bytesPerSec=Math.ceil(t.loaded/((+new Date()-p||1)/1000));t.percent=t.size>0?Math.ceil(t.loaded/t.size*100):0}}g.extend(this,{state:g.STOPPED,runtime:"",features:{},files:s,settings:q,total:t,id:g.guid(),init:function(){var z=this,A,w,v,y=0,x;if(typeof(q.preinit)=="function"){q.preinit(z)}else{g.each(q.preinit,function(C,B){z.bind(B,C)})}q.page_url=q.page_url||document.location.pathname.replace(/\/[^\/]+$/g,"/");if(!/^(\w+:\/\/|\/)/.test(q.url)){q.url=q.page_url+q.url}q.chunk_size=g.parseSize(q.chunk_size);q.max_file_size=g.parseSize(q.max_file_size);z.bind("FilesAdded",function(B,E){var D,C,G=0,H,F=q.filters;if(F&&F.length){H=[];g.each(F,function(I){g.each(I.extensions.split(/,/),function(J){H.push("\\."+J.replace(new RegExp("["+("/^$.*+?|()[]{}\\".replace(/./g,"\\$&"))+"]","g"),"\\$&"))})});H=new RegExp(H.join("|")+"$","i")}for(D=0;Dq.max_file_size){B.trigger("Error",{code:g.FILE_SIZE_ERROR,message:g.translate("File size error."),file:C});continue}s.push(C);G++}if(G){c(function(){z.trigger("QueueChanged");z.refresh()},1)}else{return false}});if(q.unique_names){z.bind("UploadFile",function(B,C){var E=C.name.match(/\.([^.]+)$/),D="tmp";if(E){D=E[1]}C.target_name=C.id+"."+D})}z.bind("UploadProgress",function(B,C){C.percent=C.size>0?Math.ceil(C.loaded/C.size*100):100;n()});z.bind("StateChanged",function(B){if(B.state==g.STARTED){p=(+new Date())}});z.bind("QueueChanged",n);z.bind("Error",function(B,C){if(C.file){C.file.status=g.FAILED;n();if(B.state==g.STARTED){c(function(){r.call(z)},1)}}});z.bind("FileUploaded",function(B,C){C.status=g.DONE;C.loaded=C.size;B.trigger("UploadProgress",C);c(function(){r.call(z)},1)});if(q.runtimes){w=[];x=q.runtimes.split(/\s?,\s?/);for(A=0;A=0;u--){if(s[u].id===v){return s[u]}}},removeFile:function(v){var u;for(u=s.length-1;u>=0;u--){if(s[u].id===v.id){return this.splice(u,1)[0]}}},splice:function(w,u){var v;v=s.splice(w===b?0:w,u===b?s.length:u);this.trigger("FilesRemoved",v);this.trigger("QueueChanged");return v},trigger:function(v){var x=o[v.toLowerCase()],w,u;if(x){u=Array.prototype.slice.call(arguments);u[0]=this;for(w=0;w=0;v--){if(x[v].func===w){x.splice(v,1);break}}}else{x=[]}if(!x.length){delete o[u]}}},unbindAll:function(){var u=this;g.each(o,function(w,v){u.unbind(v)})},destroy:function(){this.trigger("Destroy");this.unbindAll()}})};g.File=function(q,o,p){var n=this;n.id=q;n.name=o;n.size=p;n.loaded=0;n.percent=0;n.status=0};g.Runtime=function(){this.getFeatures=function(){};this.init=function(n,o){}};g.QueueProgress=function(){var n=this;n.size=0;n.loaded=0;n.uploaded=0;n.failed=0;n.queued=0;n.percent=0;n.bytesPerSec=0;n.reset=function(){n.size=n.loaded=n.uploaded=n.failed=n.queued=n.percent=n.bytesPerSec=0}};g.runtimes={};window.plupload=g})();(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;l0;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(t3){k.pop()}while(k.length<4){k.push(0)}l=r.split(".");while(l.length>4){l.pop()}do{t=parseInt(l[p],10);m=parseInt(k[p],10);p++}while(p8?"":0.01});o.className="plupload silverlight";if(p.settings.container){k=b.getElementById(p.settings.container);k.style.position="relative"}k.appendChild(o);for(l=0;l';function j(){return b.getElementById(p.id+"_silverlight").content.Upload}p.bind("Silverlight:Init",function(){var i,r={};if(h[p.id]){return}h[p.id]=true;p.bind("Silverlight:StartSelectFiles",function(s){i=[]});p.bind("Silverlight:SelectFile",function(s,v,t,u){var w;w=d.guid();r[w]=v;r[v]=w;i.push(new d.File(w,t,u))});p.bind("Silverlight:SelectSuccessful",function(){if(i.length){p.trigger("FilesAdded",i)}});p.bind("Silverlight:UploadChunkError",function(s,v,t,w,u){p.trigger("Error",{code:d.IO_ERROR,message:"IO Error.",details:u,file:s.getFile(r[v])})});p.bind("Silverlight:UploadFileProgress",function(s,w,t,v){var u=s.getFile(r[w]);if(u.status!=d.FAILED){u.size=v;u.loaded=t;s.trigger("UploadProgress",u)}});p.bind("Refresh",function(s){var t,u,v;t=b.getElementById(s.settings.browse_button);if(t){u=d.getPos(t,b.getElementById(s.settings.container));v=d.getSize(t);d.extend(b.getElementById(s.id+"_silverlight_container").style,{top:u.y+"px",left:u.x+"px",width:v.w+"px",height:v.h+"px"})}});p.bind("Silverlight:UploadChunkSuccessful",function(s,v,t,y,x){var w,u=s.getFile(r[v]);w={chunk:t,chunks:y,response:x};s.trigger("ChunkUploaded",u,w);if(u.status!=d.FAILED){j().UploadNextChunk()}if(t==y-1){u.status=d.DONE;s.trigger("FileUploaded",u,{response:x})}});p.bind("Silverlight:UploadSuccessful",function(s,v,t){var u=s.getFile(r[v]);u.status=d.DONE;s.trigger("FileUploaded",u,{response:t})});p.bind("FilesRemoved",function(s,u){var t;for(t=0;t';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;u0){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;u0&&navigator.vendor.indexOf("Apple")!==-1;return{html5:k,dragdrop:m.mozInnerScreenX!==c||j||d,jpgresize:l,pngresize:l,multipart:l||!!m.FileReader||!!m.FormData,progress:n,chunking:j||l,canOpenDialog:navigator.userAgent.indexOf("WebKit")!==-1}},init:function(m,n){var j={},k;function l(s){var q,p,r=[],t,o={};for(p=0;p";A=g.getElementById(m.id+"_html5");A.onchange=function(){l(this.files);this.value=""};B=g.getElementById(s.settings.browse_button);if(B){var u=s.settings.browse_button_hover,v=s.settings.browse_button_active,t=s.features.canOpenDialog?B:C;if(u){f.addEvent(t,"mouseover",function(){f.addClass(B,u)},s.id);f.addEvent(t,"mouseout",function(){f.removeClass(B,u)},s.id)}if(v){f.addEvent(t,"mousedown",function(){f.addClass(B,v)},s.id);f.addEvent(g.body,"mouseup",function(){f.removeClass(B,v)},s.id)}if(s.features.canOpenDialog){f.addEvent(B,"click",function(y){g.getElementById(s.id+"_html5").click();y.preventDefault()},s.id)}}});m.bind("PostInit",function(){var o=g.getElementById(m.settings.drop_element);if(o){if(d){f.addEvent(o,"dragenter",function(s){var r,p,q;r=g.getElementById(m.id+"_drop");if(!r){r=g.createElement("input");r.setAttribute("type","file");r.setAttribute("id",m.id+"_drop");r.setAttribute("multiple","multiple");f.addEvent(r,"change",function(){l(this.files);f.removeEvent(r,"change",m.id);r.parentNode.removeChild(r)},m.id);o.appendChild(r)}p=f.getPos(o,g.getElementById(m.settings.container));q=f.getSize(o);f.extend(o.style,{position:"relative"});f.extend(r.style,{position:"absolute",display:"block",top:0,left:0,width:q.w+"px",height:q.h+"px",opacity:0})},m.id);return}f.addEvent(o,"dragover",function(p){p.preventDefault()},m.id);f.addEvent(o,"drop",function(q){var p=q.dataTransfer;if(p&&p.files){l(p.files)}q.preventDefault()},m.id)}});m.bind("Refresh",function(o){var p,r,s,t,q;p=g.getElementById(m.settings.browse_button);if(p){r=f.getPos(p,g.getElementById(o.settings.container));s=f.getSize(p);t=g.getElementById(m.id+"_html5_container");f.extend(t.style,{top:r.y+"px",left:r.x+"px",width:s.w+"px",height:s.h+"px"});if(m.features.canOpenDialog){q=parseInt(p.parentNode.style.zIndex,10);if(isNaN(q)){q=0}f.extend(p.style,{position:"relative",zIndex:q});f.extend(t.style,{zIndex:q-1})}}});m.bind("UploadFile",function(o,q){var r=o.settings,t,p;function s(u){var x=0,w=0;function v(){var E=u,L,M,H,I,J=0,A="----pluploadboundary"+f.guid(),D,F,B,C="--",K="\r\n",G="",z,y=o.settings.url;if(q.status==f.DONE||q.status==f.FAILED||o.state==f.STOPPED){return}I={name:q.target_name||q.name};if(r.chunk_size&&k.chunking){D=r.chunk_size;H=Math.ceil(q.size/D);F=Math.min(D,q.size-(x*D));if(typeof(u)=="string"){E=u.substring(x*D,x*D+F)}else{E=u.slice(x*D,F)}I.chunk=x;I.chunks=H}else{F=q.size}L=new XMLHttpRequest();M=L.upload;if(M){M.onprogress=function(N){q.loaded=Math.min(q.size,w+N.loaded-J);o.trigger("UploadProgress",q)}}if(!o.settings.multipart||!k.multipart){y=f.buildUrl(o.settings.url,I)}else{I.name=q.target_name||q.name}L.open("post",y,true);L.onreadystatechange=function(){var N,P;if(L.readyState==4){try{N=L.status}catch(O){N=0}if(N>=400){o.trigger("Error",{code:f.HTTP_ERROR,message:f.translate("HTTP Error."),file:q,status:N})}else{if(H){P={chunk:x,chunks:H,response:L.responseText,status:N};o.trigger("ChunkUploaded",q,P);w+=F;if(P.cancelled){q.status=f.FAILED;return}q.loaded=Math.min(q.size,(x+1)*D)}else{q.loaded=q.size}o.trigger("UploadProgress",q);if(!H||++x>=H){q.status=f.DONE;o.trigger("FileUploaded",q,{response:L.responseText,status:N});t=u=j[q.id]=null}else{v()}}L=E=B=G=null}};f.each(o.settings.headers,function(O,N){L.setRequestHeader(N,O)});if(o.settings.multipart&&k.multipart){if(!L.sendAsBinary){B=new FormData();f.each(f.extend(I,o.settings.multipart_params),function(O,N){B.append(N,O)});B.append(o.settings.file_data_name,E);L.send(B);return}L.setRequestHeader("Content-Type","multipart/form-data; boundary="+A);f.each(f.extend(I,o.settings.multipart_params),function(O,N){G+=C+A+K+'Content-Disposition: form-data; name="'+N+'"'+K+K;G+=unescape(encodeURIComponent(O))+K});z=f.mimeTypes[q.name.replace(/^.+\.([^.]+)/,"$1")]||"application/octet-stream";G+=C+A+K+'Content-Disposition: form-data; name="'+o.settings.file_data_name+'"; filename="'+unescape(encodeURIComponent(q.name))+'"'+K+"Content-Type: "+z+K+K+E+K+C+A+C+K;J=G.length-E.length;E=G}else{L.setRequestHeader("Content-Type","application/octet-stream")}if(L.sendAsBinary){L.sendAsBinary(E)}else{L.send(E)}}v()}t=j[q.id];p=o.settings.resize;if(k.jpgresize){if(p&&/\.(png|jpg|jpeg)$/i.test(q.name)){b(t,p.width,p.height,/\.png$/i.test(q.name)?"image/png":"image/jpeg",function(u){if(u.success){q.size=u.data.length;s(u.data)}else{h(t,s)}})}else{h(t,s)}}else{s(t)}});m.bind("Destroy",function(o){var q,r,p=g.body,s={inputContainer:o.id+"_html5_container",inputFile:o.id+"_html5",browseButton:o.settings.browse_button,dropElm:o.settings.drop_element};for(q in s){r=g.getElementById(s[q]);if(r){f.removeAllEvents(r,o.id)}}f.removeAllEvents(g.body,o.id);if(o.settings.container){p=g.getElementById(o.settings.container)}p.removeChild(g.getElementById(s.inputContainer))});n({success:true})}});a=function(){var l,m,B,v,w,q,u,x,G,s,D,z,n,F,j,E,C,o,k;function r(){var K=false,I;function L(N,P){var M=K?0:-8*(P-1),Q=0,O;for(O=0;O>Math.abs(M+P*8))&255)}H(N,R,true)}return{II:function(M){if(M===j){return K}else{K=M}},init:function(M){I=M},SEGMENT:function(M,O,N){if(!arguments.length){return I}if(typeof O=="number"){return I.substr(parseInt(M,10),O)}H(M,O,N)},BYTE:function(M){return L(M,1)},SHORT:function(M){return L(M,2)},LONG:function(M,N){if(N===j){return L(M,4)}else{J(M,N,4)}},SLONG:function(M){var N=L(M,4);return(N>2147483647?N-4294967296:N)},STRING:function(M,N){var O="";for(N+=M;M4){K=G.LONG(K)+F}for(R=0;R4){K=G.LONG(K)+F}S[T]=G.STRING(K,M-1);continue;case 3:if(M>2){K=G.LONG(K)+F}for(R=0;R1){K=G.LONG(K)+F}for(R=0;R