Revision 1298:4f746d8966dd .svn/pristine/28
| .svn/pristine/28/2804d830457d070d543c18e9bbae7cbb0206aa55.svn-base | ||
|---|---|---|
| 1 |
/* Redmine - project management software |
|
| 2 |
Copyright (C) 2006-2013 Jean-Philippe Lang */ |
|
| 3 |
|
|
| 4 |
function addFile(inputEl, file, eagerUpload) {
|
|
| 5 |
|
|
| 6 |
if ($('#attachments_fields').children().length < 10) {
|
|
| 7 |
|
|
| 8 |
var attachmentId = addFile.nextAttachmentId++; |
|
| 9 |
|
|
| 10 |
var fileSpan = $('<span>', { id: 'attachments_' + attachmentId });
|
|
| 11 |
|
|
| 12 |
fileSpan.append( |
|
| 13 |
$('<input>', { type: 'text', 'class': 'filename readonly', name: 'attachments[' + attachmentId + '][filename]', readonly: 'readonly'} ).val(file.name),
|
|
| 14 |
$('<input>', { type: 'text', 'class': 'description', name: 'attachments[' + attachmentId + '][description]', maxlength: 255, placeholder: $(inputEl).data('description-placeholder') } ).toggle(!eagerUpload),
|
|
| 15 |
$('<a> </a>').attr({ href: "#", 'class': 'remove-upload' }).click(removeFile).toggle(!eagerUpload)
|
|
| 16 |
).appendTo('#attachments_fields');
|
|
| 17 |
|
|
| 18 |
if(eagerUpload) {
|
|
| 19 |
ajaxUpload(file, attachmentId, fileSpan, inputEl); |
|
| 20 |
} |
|
| 21 |
|
|
| 22 |
return attachmentId; |
|
| 23 |
} |
|
| 24 |
return null; |
|
| 25 |
} |
|
| 26 |
|
|
| 27 |
addFile.nextAttachmentId = 1; |
|
| 28 |
|
|
| 29 |
function ajaxUpload(file, attachmentId, fileSpan, inputEl) {
|
|
| 30 |
|
|
| 31 |
function onLoadstart(e) {
|
|
| 32 |
fileSpan.removeClass('ajax-waiting');
|
|
| 33 |
fileSpan.addClass('ajax-loading');
|
|
| 34 |
$('input:submit', $(this).parents('form')).attr('disabled', 'disabled');
|
|
| 35 |
} |
|
| 36 |
|
|
| 37 |
function onProgress(e) {
|
|
| 38 |
if(e.lengthComputable) {
|
|
| 39 |
this.progressbar( 'value', e.loaded * 100 / e.total ); |
|
| 40 |
} |
|
| 41 |
} |
|
| 42 |
|
|
| 43 |
function actualUpload(file, attachmentId, fileSpan, inputEl) {
|
|
| 44 |
|
|
| 45 |
ajaxUpload.uploading++; |
|
| 46 |
|
|
| 47 |
uploadBlob(file, $(inputEl).data('upload-path'), attachmentId, {
|
|
| 48 |
loadstartEventHandler: onLoadstart.bind(progressSpan), |
|
| 49 |
progressEventHandler: onProgress.bind(progressSpan) |
|
| 50 |
}) |
|
| 51 |
.done(function(result) {
|
|
| 52 |
progressSpan.progressbar( 'value', 100 ).remove(); |
|
| 53 |
fileSpan.find('input.description, a').css('display', 'inline-block');
|
|
| 54 |
}) |
|
| 55 |
.fail(function(result) {
|
|
| 56 |
progressSpan.text(result.statusText); |
|
| 57 |
}).always(function() {
|
|
| 58 |
ajaxUpload.uploading--; |
|
| 59 |
fileSpan.removeClass('ajax-loading');
|
|
| 60 |
var form = fileSpan.parents('form');
|
|
| 61 |
if (form.queue('upload').length == 0 && ajaxUpload.uploading == 0) {
|
|
| 62 |
$('input:submit', form).removeAttr('disabled');
|
|
| 63 |
} |
|
| 64 |
form.dequeue('upload');
|
|
| 65 |
}); |
|
| 66 |
} |
|
| 67 |
|
|
| 68 |
var progressSpan = $('<div>').insertAfter(fileSpan.find('input.filename'));
|
|
| 69 |
progressSpan.progressbar(); |
|
| 70 |
fileSpan.addClass('ajax-waiting');
|
|
| 71 |
|
|
| 72 |
var maxSyncUpload = $(inputEl).data('max-concurrent-uploads');
|
|
| 73 |
|
|
| 74 |
if(maxSyncUpload == null || maxSyncUpload <= 0 || ajaxUpload.uploading < maxSyncUpload) |
|
| 75 |
actualUpload(file, attachmentId, fileSpan, inputEl); |
|
| 76 |
else |
|
| 77 |
$(inputEl).parents('form').queue('upload', actualUpload.bind(this, file, attachmentId, fileSpan, inputEl));
|
|
| 78 |
} |
|
| 79 |
|
|
| 80 |
ajaxUpload.uploading = 0; |
|
| 81 |
|
|
| 82 |
function removeFile() {
|
|
| 83 |
$(this).parent('span').remove();
|
|
| 84 |
return false; |
|
| 85 |
} |
|
| 86 |
|
|
| 87 |
function uploadBlob(blob, uploadUrl, attachmentId, options) {
|
|
| 88 |
|
|
| 89 |
var actualOptions = $.extend({
|
|
| 90 |
loadstartEventHandler: $.noop, |
|
| 91 |
progressEventHandler: $.noop |
|
| 92 |
}, options); |
|
| 93 |
|
|
| 94 |
uploadUrl = uploadUrl + '?attachment_id=' + attachmentId; |
|
| 95 |
if (blob instanceof window.File) {
|
|
| 96 |
uploadUrl += '&filename=' + encodeURIComponent(blob.name); |
|
| 97 |
} |
|
| 98 |
|
|
| 99 |
return $.ajax(uploadUrl, {
|
|
| 100 |
type: 'POST', |
|
| 101 |
contentType: 'application/octet-stream', |
|
| 102 |
beforeSend: function(jqXhr) {
|
|
| 103 |
jqXhr.setRequestHeader('Accept', 'application/js');
|
|
| 104 |
}, |
|
| 105 |
xhr: function() {
|
|
| 106 |
var xhr = $.ajaxSettings.xhr(); |
|
| 107 |
xhr.upload.onloadstart = actualOptions.loadstartEventHandler; |
|
| 108 |
xhr.upload.onprogress = actualOptions.progressEventHandler; |
|
| 109 |
return xhr; |
|
| 110 |
}, |
|
| 111 |
data: blob, |
|
| 112 |
cache: false, |
|
| 113 |
processData: false |
|
| 114 |
}); |
|
| 115 |
} |
|
| 116 |
|
|
| 117 |
function addInputFiles(inputEl) {
|
|
| 118 |
var clearedFileInput = $(inputEl).clone().val('');
|
|
| 119 |
|
|
| 120 |
if (inputEl.files) {
|
|
| 121 |
// upload files using ajax |
|
| 122 |
uploadAndAttachFiles(inputEl.files, inputEl); |
|
| 123 |
$(inputEl).remove(); |
|
| 124 |
} else {
|
|
| 125 |
// browser not supporting the file API, upload on form submission |
|
| 126 |
var attachmentId; |
|
| 127 |
var aFilename = inputEl.value.split(/\/|\\/); |
|
| 128 |
attachmentId = addFile(inputEl, { name: aFilename[ aFilename.length - 1 ] }, false);
|
|
| 129 |
if (attachmentId) {
|
|
| 130 |
$(inputEl).attr({ name: 'attachments[' + attachmentId + '][file]', style: 'display:none;' }).appendTo('#attachments_' + attachmentId);
|
|
| 131 |
} |
|
| 132 |
} |
|
| 133 |
|
|
| 134 |
clearedFileInput.insertAfter('#attachments_fields');
|
|
| 135 |
} |
|
| 136 |
|
|
| 137 |
function uploadAndAttachFiles(files, inputEl) {
|
|
| 138 |
|
|
| 139 |
var maxFileSize = $(inputEl).data('max-file-size');
|
|
| 140 |
var maxFileSizeExceeded = $(inputEl).data('max-file-size-message');
|
|
| 141 |
|
|
| 142 |
var sizeExceeded = false; |
|
| 143 |
$.each(files, function() {
|
|
| 144 |
if (this.size && maxFileSize && this.size > parseInt(maxFileSize)) {sizeExceeded=true;}
|
|
| 145 |
}); |
|
| 146 |
if (sizeExceeded) {
|
|
| 147 |
window.alert(maxFileSizeExceeded); |
|
| 148 |
} else {
|
|
| 149 |
$.each(files, function() {addFile(inputEl, this, true);});
|
|
| 150 |
} |
|
| 151 |
} |
|
| 152 |
|
|
| 153 |
function handleFileDropEvent(e) {
|
|
| 154 |
|
|
| 155 |
$(this).removeClass('fileover');
|
|
| 156 |
blockEventPropagation(e); |
|
| 157 |
|
|
| 158 |
if ($.inArray('Files', e.dataTransfer.types) > -1) {
|
|
| 159 |
uploadAndAttachFiles(e.dataTransfer.files, $('input:file.file_selector'));
|
|
| 160 |
} |
|
| 161 |
} |
|
| 162 |
|
|
| 163 |
function dragOverHandler(e) {
|
|
| 164 |
$(this).addClass('fileover');
|
|
| 165 |
blockEventPropagation(e); |
|
| 166 |
} |
|
| 167 |
|
|
| 168 |
function dragOutHandler(e) {
|
|
| 169 |
$(this).removeClass('fileover');
|
|
| 170 |
blockEventPropagation(e); |
|
| 171 |
} |
|
| 172 |
|
|
| 173 |
function setupFileDrop() {
|
|
| 174 |
if (window.File && window.FileList && window.ProgressEvent && window.FormData) {
|
|
| 175 |
|
|
| 176 |
$.event.fixHooks.drop = { props: [ 'dataTransfer' ] };
|
|
| 177 |
|
|
| 178 |
$('form div.box').has('input:file').each(function() {
|
|
| 179 |
$(this).on({
|
|
| 180 |
dragover: dragOverHandler, |
|
| 181 |
dragleave: dragOutHandler, |
|
| 182 |
drop: handleFileDropEvent |
|
| 183 |
}); |
|
| 184 |
}); |
|
| 185 |
} |
|
| 186 |
} |
|
| 187 |
|
|
| 188 |
$(document).ready(setupFileDrop); |
|
| .svn/pristine/28/2806c2e1c64e92f9ca9d69815ab933298ecbeb25.svn-base | ||
|---|---|---|
| 1 |
module OpenIdAuthentication |
|
| 2 |
class Association < ActiveRecord::Base |
|
| 3 |
set_table_name :open_id_authentication_associations |
|
| 4 |
|
|
| 5 |
def from_record |
|
| 6 |
OpenID::Association.new(handle, secret, issued, lifetime, assoc_type) |
|
| 7 |
end |
|
| 8 |
end |
|
| 9 |
end |
|
| .svn/pristine/28/2840fb98174d2a4d326c6f441324c06bb7ec4f82.svn-base | ||
|---|---|---|
| 1 |
# Redmine - project management software |
|
| 2 |
# Copyright (C) 2006-2013 Jean-Philippe Lang |
|
| 3 |
# |
|
| 4 |
# This program is free software; you can redistribute it and/or |
|
| 5 |
# modify it under the terms of the GNU General Public License |
|
| 6 |
# as published by the Free Software Foundation; either version 2 |
|
| 7 |
# of the License, or (at your option) any later version. |
|
| 8 |
# |
|
| 9 |
# This program is distributed in the hope that it will be useful, |
|
| 10 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
| 11 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
| 12 |
# GNU General Public License for more details. |
|
| 13 |
# |
|
| 14 |
# You should have received a copy of the GNU General Public License |
|
| 15 |
# along with this program; if not, write to the Free Software |
|
| 16 |
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
|
| 17 |
|
|
| 18 |
require "digest/md5" |
|
| 19 |
require "fileutils" |
|
| 20 |
|
|
| 21 |
class Attachment < ActiveRecord::Base |
|
| 22 |
belongs_to :container, :polymorphic => true |
|
| 23 |
belongs_to :author, :class_name => "User", :foreign_key => "author_id" |
|
| 24 |
|
|
| 25 |
validates_presence_of :filename, :author |
|
| 26 |
validates_length_of :filename, :maximum => 255 |
|
| 27 |
validates_length_of :disk_filename, :maximum => 255 |
|
| 28 |
validates_length_of :description, :maximum => 255 |
|
| 29 |
validate :validate_max_file_size |
|
| 30 |
|
|
| 31 |
acts_as_event :title => :filename, |
|
| 32 |
:url => Proc.new {|o| {:controller => 'attachments', :action => 'download', :id => o.id, :filename => o.filename}}
|
|
| 33 |
|
|
| 34 |
acts_as_activity_provider :type => 'files', |
|
| 35 |
:permission => :view_files, |
|
| 36 |
:author_key => :author_id, |
|
| 37 |
:find_options => {:select => "#{Attachment.table_name}.*",
|
|
| 38 |
:joins => "LEFT JOIN #{Version.table_name} ON #{Attachment.table_name}.container_type='Version' AND #{Version.table_name}.id = #{Attachment.table_name}.container_id " +
|
|
| 39 |
"LEFT JOIN #{Project.table_name} ON #{Version.table_name}.project_id = #{Project.table_name}.id OR ( #{Attachment.table_name}.container_type='Project' AND #{Attachment.table_name}.container_id = #{Project.table_name}.id )"}
|
|
| 40 |
|
|
| 41 |
acts_as_activity_provider :type => 'documents', |
|
| 42 |
:permission => :view_documents, |
|
| 43 |
:author_key => :author_id, |
|
| 44 |
:find_options => {:select => "#{Attachment.table_name}.*",
|
|
| 45 |
:joins => "LEFT JOIN #{Document.table_name} ON #{Attachment.table_name}.container_type='Document' AND #{Document.table_name}.id = #{Attachment.table_name}.container_id " +
|
|
| 46 |
"LEFT JOIN #{Project.table_name} ON #{Document.table_name}.project_id = #{Project.table_name}.id"}
|
|
| 47 |
|
|
| 48 |
cattr_accessor :storage_path |
|
| 49 |
@@storage_path = Redmine::Configuration['attachments_storage_path'] || File.join(Rails.root, "files") |
|
| 50 |
|
|
| 51 |
cattr_accessor :thumbnails_storage_path |
|
| 52 |
@@thumbnails_storage_path = File.join(Rails.root, "tmp", "thumbnails") |
|
| 53 |
|
|
| 54 |
before_save :files_to_final_location |
|
| 55 |
after_destroy :delete_from_disk |
|
| 56 |
|
|
| 57 |
# Returns an unsaved copy of the attachment |
|
| 58 |
def copy(attributes=nil) |
|
| 59 |
copy = self.class.new |
|
| 60 |
copy.attributes = self.attributes.dup.except("id", "downloads")
|
|
| 61 |
copy.attributes = attributes if attributes |
|
| 62 |
copy |
|
| 63 |
end |
|
| 64 |
|
|
| 65 |
def validate_max_file_size |
|
| 66 |
if @temp_file && self.filesize > Setting.attachment_max_size.to_i.kilobytes |
|
| 67 |
errors.add(:base, l(:error_attachment_too_big, :max_size => Setting.attachment_max_size.to_i.kilobytes)) |
|
| 68 |
end |
|
| 69 |
end |
|
| 70 |
|
|
| 71 |
def file=(incoming_file) |
|
| 72 |
unless incoming_file.nil? |
|
| 73 |
@temp_file = incoming_file |
|
| 74 |
if @temp_file.size > 0 |
|
| 75 |
if @temp_file.respond_to?(:original_filename) |
|
| 76 |
self.filename = @temp_file.original_filename |
|
| 77 |
self.filename.force_encoding("UTF-8") if filename.respond_to?(:force_encoding)
|
|
| 78 |
end |
|
| 79 |
if @temp_file.respond_to?(:content_type) |
|
| 80 |
self.content_type = @temp_file.content_type.to_s.chomp |
|
| 81 |
end |
|
| 82 |
if content_type.blank? && filename.present? |
|
| 83 |
self.content_type = Redmine::MimeType.of(filename) |
|
| 84 |
end |
|
| 85 |
self.filesize = @temp_file.size |
|
| 86 |
end |
|
| 87 |
end |
|
| 88 |
end |
|
| 89 |
|
|
| 90 |
def file |
|
| 91 |
nil |
|
| 92 |
end |
|
| 93 |
|
|
| 94 |
def filename=(arg) |
|
| 95 |
write_attribute :filename, sanitize_filename(arg.to_s) |
|
| 96 |
filename |
|
| 97 |
end |
|
| 98 |
|
|
| 99 |
# Copies the temporary file to its final location |
|
| 100 |
# and computes its MD5 hash |
|
| 101 |
def files_to_final_location |
|
| 102 |
if @temp_file && (@temp_file.size > 0) |
|
| 103 |
self.disk_directory = target_directory |
|
| 104 |
self.disk_filename = Attachment.disk_filename(filename, disk_directory) |
|
| 105 |
logger.info("Saving attachment '#{self.diskfile}' (#{@temp_file.size} bytes)")
|
|
| 106 |
path = File.dirname(diskfile) |
|
| 107 |
unless File.directory?(path) |
|
| 108 |
FileUtils.mkdir_p(path) |
|
| 109 |
end |
|
| 110 |
md5 = Digest::MD5.new |
|
| 111 |
File.open(diskfile, "wb") do |f| |
|
| 112 |
if @temp_file.respond_to?(:read) |
|
| 113 |
buffer = "" |
|
| 114 |
while (buffer = @temp_file.read(8192)) |
|
| 115 |
f.write(buffer) |
|
| 116 |
md5.update(buffer) |
|
| 117 |
end |
|
| 118 |
else |
|
| 119 |
f.write(@temp_file) |
|
| 120 |
md5.update(@temp_file) |
|
| 121 |
end |
|
| 122 |
end |
|
| 123 |
self.digest = md5.hexdigest |
|
| 124 |
end |
|
| 125 |
@temp_file = nil |
|
| 126 |
# Don't save the content type if it's longer than the authorized length |
|
| 127 |
if self.content_type && self.content_type.length > 255 |
|
| 128 |
self.content_type = nil |
|
| 129 |
end |
|
| 130 |
end |
|
| 131 |
|
|
| 132 |
# Deletes the file from the file system if it's not referenced by other attachments |
|
| 133 |
def delete_from_disk |
|
| 134 |
if Attachment.where("disk_filename = ? AND id <> ?", disk_filename, id).empty?
|
|
| 135 |
delete_from_disk! |
|
| 136 |
end |
|
| 137 |
end |
|
| 138 |
|
|
| 139 |
# Returns file's location on disk |
|
| 140 |
def diskfile |
|
| 141 |
File.join(self.class.storage_path, disk_directory.to_s, disk_filename.to_s) |
|
| 142 |
end |
|
| 143 |
|
|
| 144 |
def title |
|
| 145 |
title = filename.to_s |
|
| 146 |
if description.present? |
|
| 147 |
title << " (#{description})"
|
|
| 148 |
end |
|
| 149 |
title |
|
| 150 |
end |
|
| 151 |
|
|
| 152 |
def increment_download |
|
| 153 |
increment!(:downloads) |
|
| 154 |
end |
|
| 155 |
|
|
| 156 |
def project |
|
| 157 |
container.try(:project) |
|
| 158 |
end |
|
| 159 |
|
|
| 160 |
def visible?(user=User.current) |
|
| 161 |
if container_id |
|
| 162 |
container && container.attachments_visible?(user) |
|
| 163 |
else |
|
| 164 |
author == user |
|
| 165 |
end |
|
| 166 |
end |
|
| 167 |
|
|
| 168 |
def deletable?(user=User.current) |
|
| 169 |
if container_id |
|
| 170 |
container && container.attachments_deletable?(user) |
|
| 171 |
else |
|
| 172 |
author == user |
|
| 173 |
end |
|
| 174 |
end |
|
| 175 |
|
|
| 176 |
def image? |
|
| 177 |
!!(self.filename =~ /\.(bmp|gif|jpg|jpe|jpeg|png)$/i) |
|
| 178 |
end |
|
| 179 |
|
|
| 180 |
def thumbnailable? |
|
| 181 |
image? |
|
| 182 |
end |
|
| 183 |
|
|
| 184 |
# Returns the full path the attachment thumbnail, or nil |
|
| 185 |
# if the thumbnail cannot be generated. |
|
| 186 |
def thumbnail(options={})
|
|
| 187 |
if thumbnailable? && readable? |
|
| 188 |
size = options[:size].to_i |
|
| 189 |
if size > 0 |
|
| 190 |
# Limit the number of thumbnails per image |
|
| 191 |
size = (size / 50) * 50 |
|
| 192 |
# Maximum thumbnail size |
|
| 193 |
size = 800 if size > 800 |
|
| 194 |
else |
|
| 195 |
size = Setting.thumbnails_size.to_i |
|
| 196 |
end |
|
| 197 |
size = 100 unless size > 0 |
|
| 198 |
target = File.join(self.class.thumbnails_storage_path, "#{id}_#{digest}_#{size}.thumb")
|
|
| 199 |
|
|
| 200 |
begin |
|
| 201 |
Redmine::Thumbnail.generate(self.diskfile, target, size) |
|
| 202 |
rescue => e |
|
| 203 |
logger.error "An error occured while generating thumbnail for #{disk_filename} to #{target}\nException was: #{e.message}" if logger
|
|
| 204 |
return nil |
|
| 205 |
end |
|
| 206 |
end |
|
| 207 |
end |
|
| 208 |
|
|
| 209 |
# Deletes all thumbnails |
|
| 210 |
def self.clear_thumbnails |
|
| 211 |
Dir.glob(File.join(thumbnails_storage_path, "*.thumb")).each do |file| |
|
| 212 |
File.delete file |
|
| 213 |
end |
|
| 214 |
end |
|
| 215 |
|
|
| 216 |
def is_text? |
|
| 217 |
Redmine::MimeType.is_type?('text', filename)
|
|
| 218 |
end |
|
| 219 |
|
|
| 220 |
def is_diff? |
|
| 221 |
self.filename =~ /\.(patch|diff)$/i |
|
| 222 |
end |
|
| 223 |
|
|
| 224 |
# Returns true if the file is readable |
|
| 225 |
def readable? |
|
| 226 |
File.readable?(diskfile) |
|
| 227 |
end |
|
| 228 |
|
|
| 229 |
# Returns the attachment token |
|
| 230 |
def token |
|
| 231 |
"#{id}.#{digest}"
|
|
| 232 |
end |
|
| 233 |
|
|
| 234 |
# Finds an attachment that matches the given token and that has no container |
|
| 235 |
def self.find_by_token(token) |
|
| 236 |
if token.to_s =~ /^(\d+)\.([0-9a-f]+)$/ |
|
| 237 |
attachment_id, attachment_digest = $1, $2 |
|
| 238 |
attachment = Attachment.where(:id => attachment_id, :digest => attachment_digest).first |
|
| 239 |
if attachment && attachment.container.nil? |
|
| 240 |
attachment |
|
| 241 |
end |
|
| 242 |
end |
|
| 243 |
end |
|
| 244 |
|
|
| 245 |
# Bulk attaches a set of files to an object |
|
| 246 |
# |
|
| 247 |
# Returns a Hash of the results: |
|
| 248 |
# :files => array of the attached files |
|
| 249 |
# :unsaved => array of the files that could not be attached |
|
| 250 |
def self.attach_files(obj, attachments) |
|
| 251 |
result = obj.save_attachments(attachments, User.current) |
|
| 252 |
obj.attach_saved_attachments |
|
| 253 |
result |
|
| 254 |
end |
|
| 255 |
|
|
| 256 |
def self.latest_attach(attachments, filename) |
|
| 257 |
attachments.sort_by(&:created_on).reverse.detect {
|
|
| 258 |
|att| att.filename.downcase == filename.downcase |
|
| 259 |
} |
|
| 260 |
end |
|
| 261 |
|
|
| 262 |
def self.prune(age=1.day) |
|
| 263 |
Attachment.where("created_on < ? AND (container_type IS NULL OR container_type = '')", Time.now - age).destroy_all
|
|
| 264 |
end |
|
| 265 |
|
|
| 266 |
# Moves an existing attachment to its target directory |
|
| 267 |
def move_to_target_directory! |
|
| 268 |
if !new_record? & readable? |
|
| 269 |
src = diskfile |
|
| 270 |
self.disk_directory = target_directory |
|
| 271 |
dest = diskfile |
|
| 272 |
if src != dest && FileUtils.mkdir_p(File.dirname(dest)) && FileUtils.mv(src, dest) |
|
| 273 |
update_column :disk_directory, disk_directory |
|
| 274 |
end |
|
| 275 |
end |
|
| 276 |
end |
|
| 277 |
|
|
| 278 |
# Moves existing attachments that are stored at the root of the files |
|
| 279 |
# directory (ie. created before Redmine 2.3) to their target subdirectories |
|
| 280 |
def self.move_from_root_to_target_directory |
|
| 281 |
Attachment.where("disk_directory IS NULL OR disk_directory = ''").find_each do |attachment|
|
|
| 282 |
attachment.move_to_target_directory! |
|
| 283 |
end |
|
| 284 |
end |
|
| 285 |
|
|
| 286 |
private |
|
| 287 |
|
|
| 288 |
# Physically deletes the file from the file system |
|
| 289 |
def delete_from_disk! |
|
| 290 |
if disk_filename.present? && File.exist?(diskfile) |
|
| 291 |
File.delete(diskfile) |
|
| 292 |
end |
|
| 293 |
end |
|
| 294 |
|
|
| 295 |
def sanitize_filename(value) |
|
| 296 |
# get only the filename, not the whole path |
|
| 297 |
just_filename = value.gsub(/^.*(\\|\/)/, '') |
|
| 298 |
|
|
| 299 |
# Finally, replace invalid characters with underscore |
|
| 300 |
@filename = just_filename.gsub(/[\/\?\%\*\:\|\"\'<>]+/, '_') |
|
| 301 |
end |
|
| 302 |
|
|
| 303 |
# Returns the subdirectory in which the attachment will be saved |
|
| 304 |
def target_directory |
|
| 305 |
time = created_on || DateTime.now |
|
| 306 |
time.strftime("%Y/%m")
|
|
| 307 |
end |
|
| 308 |
|
|
| 309 |
# Returns an ASCII or hashed filename that do not |
|
| 310 |
# exists yet in the given subdirectory |
|
| 311 |
def self.disk_filename(filename, directory=nil) |
|
| 312 |
timestamp = DateTime.now.strftime("%y%m%d%H%M%S")
|
|
| 313 |
ascii = '' |
|
| 314 |
if filename =~ %r{^[a-zA-Z0-9_\.\-]*$}
|
|
| 315 |
ascii = filename |
|
| 316 |
else |
|
| 317 |
ascii = Digest::MD5.hexdigest(filename) |
|
| 318 |
# keep the extension if any |
|
| 319 |
ascii << $1 if filename =~ %r{(\.[a-zA-Z0-9]+)$}
|
|
| 320 |
end |
|
| 321 |
while File.exist?(File.join(storage_path, directory.to_s, "#{timestamp}_#{ascii}"))
|
|
| 322 |
timestamp.succ! |
|
| 323 |
end |
|
| 324 |
"#{timestamp}_#{ascii}"
|
|
| 325 |
end |
|
| 326 |
end |
|
| .svn/pristine/28/28420e6f2a289bd07da6ea940b845a9449d5bc41.svn-base | ||
|---|---|---|
| 1 |
api.array :issues, api_meta(:total_count => @issue_count, :offset => @offset, :limit => @limit) do |
|
| 2 |
@issues.each do |issue| |
|
| 3 |
api.issue do |
|
| 4 |
api.id issue.id |
|
| 5 |
api.project(:id => issue.project_id, :name => issue.project.name) unless issue.project.nil? |
|
| 6 |
api.tracker(:id => issue.tracker_id, :name => issue.tracker.name) unless issue.tracker.nil? |
|
| 7 |
api.status(:id => issue.status_id, :name => issue.status.name) unless issue.status.nil? |
|
| 8 |
api.priority(:id => issue.priority_id, :name => issue.priority.name) unless issue.priority.nil? |
|
| 9 |
api.author(:id => issue.author_id, :name => issue.author.name) unless issue.author.nil? |
|
| 10 |
api.assigned_to(:id => issue.assigned_to_id, :name => issue.assigned_to.name) unless issue.assigned_to.nil? |
|
| 11 |
api.category(:id => issue.category_id, :name => issue.category.name) unless issue.category.nil? |
|
| 12 |
api.fixed_version(:id => issue.fixed_version_id, :name => issue.fixed_version.name) unless issue.fixed_version.nil? |
|
| 13 |
api.parent(:id => issue.parent_id) unless issue.parent.nil? |
|
| 14 |
|
|
| 15 |
api.subject issue.subject |
|
| 16 |
api.description issue.description |
|
| 17 |
api.start_date issue.start_date |
|
| 18 |
api.due_date issue.due_date |
|
| 19 |
api.done_ratio issue.done_ratio |
|
| 20 |
api.estimated_hours issue.estimated_hours |
|
| 21 |
|
|
| 22 |
render_api_custom_values issue.custom_field_values, api |
|
| 23 |
|
|
| 24 |
api.created_on issue.created_on |
|
| 25 |
api.updated_on issue.updated_on |
|
| 26 |
api.closed_on issue.closed_on |
|
| 27 |
|
|
| 28 |
api.array :relations do |
|
| 29 |
issue.relations.each do |relation| |
|
| 30 |
api.relation(:id => relation.id, :issue_id => relation.issue_from_id, :issue_to_id => relation.issue_to_id, :relation_type => relation.relation_type, :delay => relation.delay) |
|
| 31 |
end |
|
| 32 |
end if include_in_api_response?('relations')
|
|
| 33 |
end |
|
| 34 |
end |
|
| 35 |
end |
|
| .svn/pristine/28/2864783005a4ba0460d387fef08dd617a059d205.svn-base | ||
|---|---|---|
| 1 |
# Redmine - project management software |
|
| 2 |
# Copyright (C) 2006-2011 Jean-Philippe Lang |
|
| 3 |
# |
|
| 4 |
# This program is free software; you can redistribute it and/or |
|
| 5 |
# modify it under the terms of the GNU General Public License |
|
| 6 |
# as published by the Free Software Foundation; either version 2 |
|
| 7 |
# of the License, or (at your option) any later version. |
|
| 8 |
# |
|
| 9 |
# This program is distributed in the hope that it will be useful, |
|
| 10 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
| 11 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
| 12 |
# GNU General Public License for more details. |
|
| 13 |
# |
|
| 14 |
# You should have received a copy of the GNU General Public License |
|
| 15 |
# along with this program; if not, write to the Free Software |
|
| 16 |
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
|
| 17 |
|
|
| 18 |
require File.expand_path('../../../../test_helper', __FILE__)
|
|
| 19 |
|
|
| 20 |
class Redmine::ConfigurationTest < ActiveSupport::TestCase |
|
| 21 |
def setup |
|
| 22 |
@conf = Redmine::Configuration |
|
| 23 |
end |
|
| 24 |
|
|
| 25 |
def test_empty |
|
| 26 |
assert_kind_of Hash, load_conf('empty.yml', 'test')
|
|
| 27 |
end |
|
| 28 |
|
|
| 29 |
def test_default |
|
| 30 |
assert_kind_of Hash, load_conf('default.yml', 'test')
|
|
| 31 |
assert_equal 'foo', @conf['somesetting'] |
|
| 32 |
end |
|
| 33 |
|
|
| 34 |
def test_no_default |
|
| 35 |
assert_kind_of Hash, load_conf('no_default.yml', 'test')
|
|
| 36 |
assert_equal 'foo', @conf['somesetting'] |
|
| 37 |
end |
|
| 38 |
|
|
| 39 |
def test_overrides |
|
| 40 |
assert_kind_of Hash, load_conf('overrides.yml', 'test')
|
|
| 41 |
assert_equal 'bar', @conf['somesetting'] |
|
| 42 |
end |
|
| 43 |
|
|
| 44 |
def test_with |
|
| 45 |
load_conf('default.yml', 'test')
|
|
| 46 |
assert_equal 'foo', @conf['somesetting'] |
|
| 47 |
@conf.with 'somesetting' => 'bar' do |
|
| 48 |
assert_equal 'bar', @conf['somesetting'] |
|
| 49 |
end |
|
| 50 |
assert_equal 'foo', @conf['somesetting'] |
|
| 51 |
end |
|
| 52 |
|
|
| 53 |
private |
|
| 54 |
|
|
| 55 |
def load_conf(file, env) |
|
| 56 |
@conf.load( |
|
| 57 |
:file => File.join(Rails.root, 'test', 'fixtures', 'configuration', file), |
|
| 58 |
:env => env |
|
| 59 |
) |
|
| 60 |
end |
|
| 61 |
end |
|
| .svn/pristine/28/28ac0f519eb9b5a961e5bcb8825c18def371a932.svn-base | ||
|---|---|---|
| 1 |
<%= @note %> (from application) |
|
| .svn/pristine/28/28e4e2427d2a8e8bd98db1e2c7317b6c662eb82a.svn-base | ||
|---|---|---|
| 1 |
<h2><%=l(:label_news)%></h2> |
|
| 2 |
|
|
| 3 |
<% labelled_tabular_form_for @news, :html => { :id => 'news-form', :method => :put } do |f| %>
|
|
| 4 |
<%= render :partial => 'form', :locals => { :f => f } %>
|
|
| 5 |
<%= submit_tag l(:button_save) %> |
|
| 6 |
<%= link_to_remote l(:label_preview), |
|
| 7 |
{ :url => preview_news_path(:project_id => @project),
|
|
| 8 |
:method => 'get', |
|
| 9 |
:update => 'preview', |
|
| 10 |
:with => "Form.serialize('news-form')"
|
|
| 11 |
}, :accesskey => accesskey(:preview) %> |
|
| 12 |
<% end %> |
|
| 13 |
<div id="preview" class="wiki"></div> |
|
| 14 |
|
|
| 15 |
<% content_for :header_tags do %> |
|
| 16 |
<%= stylesheet_link_tag 'scm' %> |
|
| 17 |
<% end %> |
|
| .svn/pristine/28/28f61d6f82da16a79bf305217d0869e0a3170794.svn-base | ||
|---|---|---|
| 1 |
<div class="contextual"> |
|
| 2 |
<%= link_to_if_authorized l(:label_document_new), |
|
| 3 |
{:controller => 'documents', :action => 'new', :project_id => @project},
|
|
| 4 |
:class => 'icon icon-add', |
|
| 5 |
:onclick => 'Element.show("add-document"); Form.Element.focus("document_title"); return false;' %>
|
|
| 6 |
</div> |
|
| 7 |
|
|
| 8 |
<div id="add-document" style="display:none;"> |
|
| 9 |
<h2><%=l(:label_document_new)%></h2> |
|
| 10 |
<% form_tag({:controller => 'documents', :action => 'new', :project_id => @project}, :class => "tabular", :multipart => true) do %>
|
|
| 11 |
<%= render :partial => 'documents/form' %> |
|
| 12 |
<div class="box"> |
|
| 13 |
<p><label><%=l(:label_attachment_plural)%></label><%= render :partial => 'attachments/form' %></p> |
|
| 14 |
</div> |
|
| 15 |
<%= submit_tag l(:button_create) %> |
|
| 16 |
<%= link_to l(:button_cancel), "#", :onclick => 'Element.hide("add-document")' %>
|
|
| 17 |
<% end %> |
|
| 18 |
</div> |
|
| 19 |
|
|
| 20 |
<h2><%=l(:label_document_plural)%></h2> |
|
| 21 |
|
|
| 22 |
<% if @grouped.empty? %><p class="nodata"><%= l(:label_no_data) %></p><% end %> |
|
| 23 |
|
|
| 24 |
<% @grouped.keys.sort.each do |group| %> |
|
| 25 |
<h3><%= group %></h3> |
|
| 26 |
<%= render :partial => 'documents/document', :collection => @grouped[group] %> |
|
| 27 |
<% end %> |
|
| 28 |
|
|
| 29 |
<% content_for :sidebar do %> |
|
| 30 |
<h3><%= l(:label_sort_by, '') %></h3> |
|
| 31 |
<% form_tag({}, :method => :get) do %>
|
|
| 32 |
<label><%= radio_button_tag 'sort_by', 'category', (@sort_by == 'category'), :onclick => 'this.form.submit();' %> <%= l(:field_category) %></label><br /> |
|
| 33 |
<label><%= radio_button_tag 'sort_by', 'date', (@sort_by == 'date'), :onclick => 'this.form.submit();' %> <%= l(:label_date) %></label><br /> |
|
| 34 |
<label><%= radio_button_tag 'sort_by', 'title', (@sort_by == 'title'), :onclick => 'this.form.submit();' %> <%= l(:field_title) %></label><br /> |
|
| 35 |
<label><%= radio_button_tag 'sort_by', 'author', (@sort_by == 'author'), :onclick => 'this.form.submit();' %> <%= l(:field_author) %></label> |
|
| 36 |
<% end %> |
|
| 37 |
<% end %> |
|
| 38 |
|
|
| 39 |
<% html_title(l(:label_document_plural)) -%> |
|
Also available in: Unified diff