# HG changeset patch # User luisf # Date 1297083177 0 # Node ID 90cc857e968a883b2795a18d9ae9ea90c6f45db3 # Parent e5171a80c541eeced2058d3cda0d149313f1d1be# Parent d1713ab108133933412d2422aefc9fa3d25c03f6 Merge from branch "feature_64" diff -r d1713ab10813 -r 90cc857e968a app/controllers/members_controller.rb --- a/app/controllers/members_controller.rb Mon Feb 07 12:30:26 2011 +0000 +++ b/app/controllers/members_controller.rb Mon Feb 07 12:52:57 2011 +0000 @@ -94,6 +94,7 @@ def autocomplete_for_member @principals = Principal.active.like(params[:q]).find(:all, :limit => 100) - @project.principals + logger.debug "Query for #{params[:q]} returned #{@principals.size} results" render :layout => false end diff -r d1713ab10813 -r 90cc857e968a app/controllers/projects_controller.rb --- a/app/controllers/projects_controller.rb Mon Feb 07 12:30:26 2011 +0000 +++ b/app/controllers/projects_controller.rb Mon Feb 07 12:52:57 2011 +0000 @@ -45,12 +45,22 @@ helper :repositories include RepositoriesHelper include ProjectsHelper - + # Lists visible projects def index respond_to do |format| format.html { - @projects = Project.visible.find(:all, :order => 'lft') + sort_init 'lft' + sort_update %w(lft title created_on updated_on) + @limit = per_page_option + @project_count = Project.visible.count + @project_pages = Paginator.new self, @project_count, @limit, params['page'] + @offset ||= @project_pages.current.offset + @projects = Project.visible.all(:offset => @offset, :limit => @limit, :order => sort_clause) + if User.current.logged? + @user_projects = User.current.projects.sort_by(&:lft) + end + render :template => 'projects/index.rhtml', :layout => !request.xhr? } format.xml { @projects = Project.visible.find(:all, :order => 'lft') diff -r d1713ab10813 -r 90cc857e968a app/helpers/application_helper.rb --- a/app/helpers/application_helper.rb Mon Feb 07 12:30:26 2011 +0000 +++ b/app/helpers/application_helper.rb Mon Feb 07 12:52:57 2011 +0000 @@ -52,7 +52,7 @@ if user.is_a?(User) name = h(user.name(options[:format])) if user.active? - link_to name, :controller => 'users', :action => 'show', :id => user + link_to(name, :controller => 'users', :action => 'show', :id => user) else name end @@ -273,7 +273,7 @@ def principals_check_box_tags(name, principals) s = '' principals.sort.each do |principal| - s << "\n" + s << "\n" end s end diff -r d1713ab10813 -r 90cc857e968a app/helpers/application_helper.rb.orig --- a/app/helpers/application_helper.rb.orig Mon Feb 07 12:30:26 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,801 +0,0 @@ -# redMine - project management software -# Copyright (C) 2006-2007 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -require 'forwardable' -require 'cgi' - -module ApplicationHelper - include Redmine::WikiFormatting::Macros::Definitions - include Redmine::I18n - include GravatarHelper::PublicMethods - - extend Forwardable - def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter - - # Return true if user is authorized for controller/action, otherwise false - def authorize_for(controller, action) - User.current.allowed_to?({:controller => controller, :action => action}, @project) - end - - # Display a link if user is authorized - def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference) - link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action]) - end - - # Display a link to remote if user is authorized - def link_to_remote_if_authorized(name, options = {}, html_options = nil) - url = options[:url] || {} - link_to_remote(name, options, html_options) if authorize_for(url[:controller] || params[:controller], url[:action]) - end - - # Displays a link to user's account page if active - def link_to_user(user, options={}) - if user.is_a?(User) - name = h(user.name(options[:format])) - if user.active? - link_to name, :controller => 'users', :action => 'show', :id => user - else - name - end - else - h(user.to_s) - end - end - - # Displays a link to +issue+ with its subject. - # Examples: - # - # link_to_issue(issue) # => Defect #6: This is the subject - # link_to_issue(issue, :truncate => 6) # => Defect #6: This i... - # link_to_issue(issue, :subject => false) # => Defect #6 - # link_to_issue(issue, :project => true) # => Foo - Defect #6 - # - def link_to_issue(issue, options={}) - title = nil - subject = nil - if options[:subject] == false - title = truncate(issue.subject, :length => 60) - else - subject = issue.subject - if options[:truncate] - subject = truncate(subject, :length => options[:truncate]) - end - end - s = link_to "#{issue.tracker} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue}, - :class => issue.css_classes, - :title => title - s << ": #{h subject}" if subject - s = "#{h issue.project} - " + s if options[:project] - s - end - - # Generates a link to an attachment. - # Options: - # * :text - Link text (default to attachment filename) - # * :download - Force download (default: false) - def link_to_attachment(attachment, options={}) - text = options.delete(:text) || attachment.filename - action = options.delete(:download) ? 'download' : 'show' - - link_to(h(text), {:controller => 'attachments', :action => action, :id => attachment, :filename => attachment.filename }, options) - end - - # Generates a link to a SCM revision - # Options: - # * :text - Link text (default to the formatted revision) - def link_to_revision(revision, project, options={}) - text = options.delete(:text) || format_revision(revision) - - link_to(text, {:controller => 'repositories', :action => 'revision', :id => project, :rev => revision}, :title => l(:label_revision_id, revision)) - end - - def toggle_link(name, id, options={}) - onclick = "Element.toggle('#{id}'); " - onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ") - onclick << "return false;" - link_to(name, "#", :onclick => onclick) - end - - def image_to_function(name, function, html_options = {}) - html_options.symbolize_keys! - tag(:input, html_options.merge({ - :type => "image", :src => image_path(name), - :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};" - })) - end - - def prompt_to_remote(name, text, param, url, html_options = {}) - html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;" - link_to name, {}, html_options - end - - def format_activity_title(text) - h(truncate_single_line(text, :length => 100)) - end - - def format_activity_day(date) - date == Date.today ? l(:label_today).titleize : format_date(date) - end - - def format_activity_description(text) - h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')).gsub(/[\r\n]+/, "
") - end - - def format_version_name(version) - if version.project == @project - h(version) - else - h("#{version.project} - #{version}") - end - end - - def due_date_distance_in_words(date) - if date - l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date)) - end - end - - def render_page_hierarchy(pages, node=nil) - content = '' - if pages[node] - content << "\n" - end - content - end - - # Renders flash messages - def render_flash_messages - s = '' - flash.each do |k,v| - s << content_tag('div', v, :class => "flash #{k}") - end - s - end - - # Renders tabs and their content - def render_tabs(tabs) - if tabs.any? - render :partial => 'common/tabs', :locals => {:tabs => tabs} - else - content_tag 'p', l(:label_no_data), :class => "nodata" - end - end - - # Renders the project quick-jump box - def render_project_jump_box - # Retrieve them now to avoid a COUNT query - projects = User.current.projects.all - if projects.any? - s = '' - s - end - end - - def project_tree_options_for_select(projects, options = {}) - s = '' - project_tree(projects) do |project, level| - name_prefix = (level > 0 ? (' ' * 2 * level + '» ') : '') - tag_options = {:value => project.id} - if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project)) - tag_options[:selected] = 'selected' - else - tag_options[:selected] = nil - end - tag_options.merge!(yield(project)) if block_given? - s << content_tag('option', name_prefix + h(project), tag_options) - end - s - end - - # Yields the given block for each project with its level in the tree - def project_tree(projects, &block) - ancestors = [] - projects.sort_by(&:lft).each do |project| - while (ancestors.any? && !project.is_descendant_of?(ancestors.last)) - ancestors.pop - end - yield project, ancestors.size - ancestors << project - end - end - - def project_nested_ul(projects, &block) - s = '' - if projects.any? - ancestors = [] - projects.sort_by(&:lft).each do |project| - if (ancestors.empty? || project.is_descendant_of?(ancestors.last)) - s << "\n" - end - end - s << "
  • " - s << yield(project).to_s - ancestors << project - end - s << ("
  • \n" * ancestors.size) - end - s - end - - def principals_check_box_tags(name, principals) - s = '' - principals.sort.each do |principal| - s << "\n" - end - s - end - - # Truncates and returns the string as a single line - def truncate_single_line(string, *args) - truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ') - end - - # Truncates at line break after 250 characters or options[:length] - def truncate_lines(string, options={}) - length = options[:length] || 250 - if string.to_s =~ /\A(.{#{length}}.*?)$/m - "#{$1}..." - else - string - end - end - - def html_hours(text) - text.gsub(%r{(\d+)\.(\d+)}, '\1.\2') - end - - def authoring(created, author, options={}) - l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created)) - end - - def time_tag(time) - text = distance_of_time_in_words(Time.now, time) - if @project - link_to(text, {:controller => 'projects', :action => 'activity', :id => @project, :from => time.to_date}, :title => format_time(time)) - else - content_tag('acronym', text, :title => format_time(time)) - end - end - - def syntax_highlight(name, content) - Redmine::SyntaxHighlighting.highlight_by_filename(content, name) - end - - def to_path_param(path) - path.to_s.split(%r{[/\\]}).select {|p| !p.blank?} - end - - def pagination_links_full(paginator, count=nil, options={}) - page_param = options.delete(:page_param) || :page - per_page_links = options.delete(:per_page_links) - url_param = params.dup - # don't reuse query params if filters are present - url_param.merge!(:fields => nil, :values => nil, :operators => nil) if url_param.delete(:set_filter) - - html = '' - if paginator.current.previous - html << link_to_remote_content_update('« ' + l(:label_previous), url_param.merge(page_param => paginator.current.previous)) + ' ' - end - - html << (pagination_links_each(paginator, options) do |n| - link_to_remote_content_update(n.to_s, url_param.merge(page_param => n)) - end || '') - - if paginator.current.next - html << ' ' + link_to_remote_content_update((l(:label_next) + ' »'), url_param.merge(page_param => paginator.current.next)) - end - - unless count.nil? - html << " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})" - if per_page_links != false && links = per_page_links(paginator.items_per_page) - html << " | #{links}" - end - end - - html - end - - def per_page_links(selected=nil) - url_param = params.dup - url_param.clear if url_param.has_key?(:set_filter) - - links = Setting.per_page_options_array.collect do |n| - n == selected ? n : link_to_remote(n, {:update => "content", - :url => params.dup.merge(:per_page => n), - :method => :get}, - {:href => url_for(url_param.merge(:per_page => n))}) - end - links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil - end - - def reorder_links(name, url) - link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)), url.merge({"#{name}[move_to]" => 'highest'}), :method => :post, :title => l(:label_sort_highest)) + - link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)), url.merge({"#{name}[move_to]" => 'higher'}), :method => :post, :title => l(:label_sort_higher)) + - link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)), url.merge({"#{name}[move_to]" => 'lower'}), :method => :post, :title => l(:label_sort_lower)) + - link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)), url.merge({"#{name}[move_to]" => 'lowest'}), :method => :post, :title => l(:label_sort_lowest)) - end - - def breadcrumb(*args) - elements = args.flatten - elements.any? ? content_tag('p', args.join(' » ') + ' » ', :class => 'breadcrumb') : nil - end - - def other_formats_links(&block) - concat('

    ' + l(:label_export_to)) - yield Redmine::Views::OtherFormatsBuilder.new(self) - concat('

    ') - end - - def page_header_title - if @project.nil? || @project.new_record? - h(Setting.app_title) - else - b = [] - ancestors = (@project.root? ? [] : @project.ancestors.visible) - if ancestors.any? - root = ancestors.shift - b << link_to(h(root), {:controller => 'projects', :action => 'show', :id => root, :jump => current_menu_item}, :class => 'root') - if ancestors.size > 2 - b << '…' - ancestors = ancestors[-2, 2] - end - b += ancestors.collect {|p| link_to(h(p), {:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item}, :class => 'ancestor') } - end - b << h(@project) - b.join(' » ') - end - end - - def html_title(*args) - if args.empty? - title = [] - title << @project.name if @project - title += @html_title if @html_title - title << Setting.app_title - title.select {|t| !t.blank? }.join(' - ') - else - @html_title ||= [] - @html_title += args - end - end - - def accesskey(s) - Redmine::AccessKeys.key_for s - end - - # Formats text according to system settings. - # 2 ways to call this method: - # * with a String: textilizable(text, options) - # * with an object and one of its attribute: textilizable(issue, :description, options) - def textilizable(*args) - options = args.last.is_a?(Hash) ? args.pop : {} - case args.size - when 1 - obj = options[:object] - text = args.shift - when 2 - obj = args.shift - attr = args.shift - text = obj.send(attr).to_s - else - raise ArgumentError, 'invalid arguments to textilizable' - end - return '' if text.blank? - project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil) - only_path = options.delete(:only_path) == false ? false : true - - text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr) { |macro, args| exec_macro(macro, obj, args) } - - parse_non_pre_blocks(text) do |text| - [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links].each do |method_name| - send method_name, text, project, obj, attr, only_path, options - end - end - end - - def parse_non_pre_blocks(text) - s = StringScanner.new(text) - tags = [] - parsed = '' - while !s.eos? - s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im) - text, full_tag, closing, tag = s[1], s[2], s[3], s[4] - if tags.empty? - yield text - end - parsed << text - if tag - if closing - if tags.last == tag.downcase - tags.pop - end - else - tags << tag.downcase - end - parsed << full_tag - end - end - # Close any non closing tags - while tag = tags.pop - parsed << "" - end - parsed - end - - def parse_inline_attachments(text, project, obj, attr, only_path, options) - # when using an image link, try to use an attachment, if possible - if options[:attachments] || (obj && obj.respond_to?(:attachments)) - attachments = nil - text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m| - filename, ext, alt, alttext = $1.downcase, $2, $3, $4 - attachments ||= (options[:attachments] || obj.attachments).sort_by(&:created_on).reverse - # search for the picture in attachments - if found = attachments.detect { |att| att.filename.downcase == filename } - image_url = url_for :only_path => only_path, :controller => 'attachments', :action => 'download', :id => found - desc = found.description.to_s.gsub('"', '') - if !desc.blank? && alttext.blank? - alt = " title=\"#{desc}\" alt=\"#{desc}\"" - end - "src=\"#{image_url}\"#{alt}" - else - m - end - end - end - end - - # Wiki links - # - # Examples: - # [[mypage]] - # [[mypage|mytext]] - # wiki links can refer other project wikis, using project name or identifier: - # [[project:]] -> wiki starting page - # [[project:|mytext]] - # [[project:mypage]] - # [[project:mypage|mytext]] - def parse_wiki_links(text, project, obj, attr, only_path, options) - text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m| - link_project = project - esc, all, page, title = $1, $2, $3, $5 - if esc.nil? - if page =~ /^([^\:]+)\:(.*)$/ - link_project = Project.find_by_name($1) || Project.find_by_identifier($1) - page = $2 - title ||= $1 if page.blank? - end - - if link_project && link_project.wiki - # extract anchor - anchor = nil - if page =~ /^(.+?)\#(.+)$/ - page, anchor = $1, $2 - end - # check if page exists - wiki_page = link_project.wiki.find_page(page) - url = case options[:wiki_links] - when :local; "#{title}.html" - when :anchor; "##{title}" # used for single-file wiki export - else - url_for(:only_path => only_path, :controller => 'wiki', :action => 'index', :id => link_project, :page => Wiki.titleize(page), :anchor => anchor) - end - link_to((title || page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new'))) - else - # project or wiki doesn't exist - all - end - else - all - end - end - end - - # Redmine links - # - # Examples: - # Issues: - # #52 -> Link to issue #52 - # Changesets: - # r52 -> Link to revision 52 - # commit:a85130f -> Link to scmid starting with a85130f - # Documents: - # document#17 -> Link to document with id 17 - # document:Greetings -> Link to the document with title "Greetings" - # document:"Some document" -> Link to the document with title "Some document" - # Versions: - # version#3 -> Link to version with id 3 - # version:1.0.0 -> Link to version named "1.0.0" - # version:"1.0 beta 2" -> Link to version named "1.0 beta 2" - # Attachments: - # attachment:file.zip -> Link to the attachment of the current object named file.zip - # Source files: - # source:some/file -> Link to the file located at /some/file in the project's repository - # source:some/file@52 -> Link to the file's revision 52 - # source:some/file#L120 -> Link to line 120 of the file - # source:some/file@52#L120 -> Link to line 120 of the file's revision 52 - # export:some/file -> Force the download of the file - # Forum messages: - # message#1218 -> Link to message with id 1218 - def parse_redmine_links(text, project, obj, attr, only_path, options) - text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(attachment|document|version|commit|source|export|message|project)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|,|\s|\]|<|$)}) do |m| - leading, esc, prefix, sep, identifier = $1, $2, $3, $5 || $7, $6 || $8 - link = nil - if esc.nil? - if prefix.nil? && sep == 'r' - if project && (changeset = project.changesets.find_by_revision(identifier)) - link = link_to("r#{identifier}", {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision}, - :class => 'changeset', - :title => truncate_single_line(changeset.comments, :length => 100)) - end - elsif sep == '#' - oid = identifier.to_i - case prefix - when nil - if issue = Issue.visible.find_by_id(oid, :include => :status) - link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid}, - :class => issue.css_classes, - :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})") - end - when 'document' - if document = Document.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current)) - link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document}, - :class => 'document' - end - when 'version' - if version = Version.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current)) - link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version}, - :class => 'version' - end - when 'message' - if message = Message.find_by_id(oid, :include => [:parent, {:board => :project}], :conditions => Project.visible_by(User.current)) - link = link_to h(truncate(message.subject, :length => 60)), {:only_path => only_path, - :controller => 'messages', - :action => 'show', - :board_id => message.board, - :id => message.root, - :anchor => (message.parent ? "message-#{message.id}" : nil)}, - :class => 'message' - end - when 'project' - if p = Project.visible.find_by_id(oid) - link = link_to h(p.name), {:only_path => only_path, :controller => 'projects', :action => 'show', :id => p}, - :class => 'project' - end - end - elsif sep == ':' - # removes the double quotes if any - name = identifier.gsub(%r{^"(.*)"$}, "\\1") - case prefix - when 'document' - if project && document = project.documents.find_by_title(name) - link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document}, - :class => 'document' - end - when 'version' - if project && version = project.versions.find_by_name(name) - link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version}, - :class => 'version' - end - when 'commit' - if project && (changeset = project.changesets.find(:first, :conditions => ["scmid LIKE ?", "#{name}%"])) - link = link_to h("#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision}, - :class => 'changeset', - :title => truncate_single_line(changeset.comments, :length => 100) - end - when 'source', 'export' - if project && project.repository - name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$} - path, rev, anchor = $1, $3, $5 - link = link_to h("#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project, - :path => to_path_param(path), - :rev => rev, - :anchor => anchor, - :format => (prefix == 'export' ? 'raw' : nil)}, - :class => (prefix == 'export' ? 'source download' : 'source') - end - when 'attachment' - attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil) - if attachments && attachment = attachments.detect {|a| a.filename == name } - link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment}, - :class => 'attachment' - end - when 'project' - if p = Project.visible.find(:first, :conditions => ["identifier = :s OR LOWER(name) = :s", {:s => name.downcase}]) - link = link_to h(p.name), {:only_path => only_path, :controller => 'projects', :action => 'show', :id => p}, - :class => 'project' - end - end - end - end - leading + (link || "#{prefix}#{sep}#{identifier}") - end - end - - # Same as Rails' simple_format helper without using paragraphs - def simple_format_without_paragraph(text) - text.to_s. - gsub(/\r\n?/, "\n"). # \r\n and \r -> \n - gsub(/\n\n+/, "

    "). # 2+ newline -> 2 br - gsub(/([^\n]\n)(?=[^\n])/, '\1
    ') # 1 newline -> br - end - - def lang_options_for_select(blank=true) - (blank ? [["(auto)", ""]] : []) + - valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last } - end - - def label_tag_for(name, option_tags = nil, options = {}) - label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "") - content_tag("label", label_text) - end - - def labelled_tabular_form_for(name, object, options, &proc) - options[:html] ||= {} - options[:html][:class] = 'tabular' unless options[:html].has_key?(:class) - form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc) - end - - def back_url_hidden_field_tag - back_url = params[:back_url] || request.env['HTTP_REFERER'] - back_url = CGI.unescape(back_url.to_s) - hidden_field_tag('back_url', CGI.escape(back_url)) unless back_url.blank? - end - - def check_all_links(form_name) - link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") + - " | " + - link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)") - end - - def progress_bar(pcts, options={}) - pcts = [pcts, pcts] unless pcts.is_a?(Array) - pcts = pcts.collect(&:round) - pcts[1] = pcts[1] - pcts[0] - pcts << (100 - pcts[1] - pcts[0]) - width = options[:width] || '100px;' - legend = options[:legend] || '' - content_tag('table', - content_tag('tr', - (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : '') + - (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : '') + - (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : '') - ), :class => 'progress', :style => "width: #{width};") + - content_tag('p', legend, :class => 'pourcent') - end - - def checked_image(checked=true) - if checked - image_tag 'toggle_check.png' - end - end - - def context_menu(url) - unless @context_menu_included - content_for :header_tags do - javascript_include_tag('context_menu') + - stylesheet_link_tag('context_menu') - end - @context_menu_included = true - end - javascript_tag "new ContextMenu('#{ url_for(url) }')" - end - - def context_menu_link(name, url, options={}) - options[:class] ||= '' - if options.delete(:selected) - options[:class] << ' icon-checked disabled' - options[:disabled] = true - end - if options.delete(:disabled) - options.delete(:method) - options.delete(:confirm) - options.delete(:onclick) - options[:class] << ' disabled' - url = '#' - end - link_to name, url, options - end - - def calendar_for(field_id) - include_calendar_headers_tags - image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) + - javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });") - end - - def include_calendar_headers_tags - unless @calendar_headers_tags_included - @calendar_headers_tags_included = true - content_for :header_tags do - start_of_week = case Setting.start_of_week.to_i - when 1 - 'Calendar._FD = 1;' # Monday - when 7 - 'Calendar._FD = 0;' # Sunday - else - '' # use language - end - - javascript_include_tag('calendar/calendar') + - javascript_include_tag("calendar/lang/calendar-#{current_language.to_s.downcase}.js") + - javascript_tag(start_of_week) + - javascript_include_tag('calendar/calendar-setup') + - stylesheet_link_tag('calendar') - end - end - end - - def content_for(name, content = nil, &block) - @has_content ||= {} - @has_content[name] = true - super(name, content, &block) - end - - def has_content?(name) - (@has_content && @has_content[name]) || false - end - - # Returns the avatar image tag for the given +user+ if avatars are enabled - # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe ') - def avatar(user, options = { }) - if Setting.gravatar_enabled? - options.merge!({:ssl => Setting.protocol == 'https', :default => Setting.gravatar_default}) - email = nil - if user.respond_to?(:mail) - email = user.mail - elsif user.to_s =~ %r{<(.+?)>} - email = $1 - end - return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil - end - end - - private - - def wiki_helper - helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting) - extend helper - return self - end - - def link_to_remote_content_update(text, url_params) - link_to_remote(text, - {:url => url_params, :method => :get, :update => 'content', :complete => 'window.scrollTo(0,0)'}, - {:href => url_for(:params => url_params)} - ) - end - -end diff -r d1713ab10813 -r 90cc857e968a app/helpers/projects_helper.rb --- a/app/helpers/projects_helper.rb Mon Feb 07 12:30:26 2011 +0000 +++ b/app/helpers/projects_helper.rb Mon Feb 07 12:52:57 2011 +0000 @@ -121,7 +121,12 @@ classes = (ancestors.empty? ? 'root' : 'child') s << "
  • " + - link_to_project(project, {}, :class => "project #{User.current.member_of?(project) ? 'my-project' : nil}") + link_to_project(project, {}, :class => "project my-project") + if project.is_public? + s << " " << l("field_is_public") << "" + else + s << " " << l("field_is_private") << "" + end s << "
    #{textilizable(project.short_description, :project => project)}
    " unless project.description.blank? s << "
    \n" ancestors << project @@ -143,66 +148,92 @@ a end - # Renders a tree of projects where the current DOES NOT belong - # as a nested set of unordered lists - # The given collection may be a subset of the whole project tree - # (eg. some intermediate nodes are private and can not be seen) - def render_other_project_hierarchy(projects) - a = '' - s = '' + # Renders a tree of projects that the current user does not belong + # to, or of all projects if the current user is not logged in. The + # given collection may be a subset of the whole project tree + # (eg. some intermediate nodes are private and can not be seen). We + # are potentially interested in various things: the project name, + # description, manager(s), creation date, last activity date, + # general activity level, whether there is anything actually hosted + # here for the project, etc. + def render_project_table(projects) - # True if user has any projects (affects the heading used) - t = FALSE + s = "" + s << "
    " + s << "" + s << "" + + s << sort_header_tag('lft', :caption => l("field_name"), :default_order => 'desc') + s << "" + s << sort_header_tag('created_on', :default_order => 'desc') + s << sort_header_tag('updated_on', :default_order => 'desc') - if projects.any? - ancestors = [] - original_project = @project - projects.each do |project| - # set the project environment to please macros. + s << "" - @project = project + ancestors = [] + original_project = @project + oddeven = 'even' + level = 0 - if not User.current.member_of?(project): + projects.each do |project| - if (ancestors.empty? || project.is_descendant_of?(ancestors.last)) - s << "
      \n" - else - ancestors.pop - s << "" - while (ancestors.any? && !project.is_descendant_of?(ancestors.last)) - ancestors.pop - s << "
    \n" + # set the project environment to please macros. + + @project = project + + if (ancestors.empty? || project.is_descendant_of?(ancestors.last)) + level = level + 1 + else + level = 0 + oddeven = cycle('odd','even') + ancestors.pop + while (ancestors.any? && !project.is_descendant_of?(ancestors.last)) + ancestors.pop + end + end + + classes = (ancestors.empty? ? 'root' : 'child') + + s << "" + s << "" + s << "" + s << "" + s << "" - s << ("\n" * ancestors.size) - @project = original_project + s << "" + s << "" + s << "" + s << "" + + ancestors << project end - if t == TRUE - a << "

    " - a << l("label_other_project_plural") - a << "

    " - a << s - else - a << "

    " - a << l("label_project_all") - a << "

    " - a << s - end + s << "
    " << l("label_managers") << "
    " << link_to_project(project, {}, :class => "project #{User.current.member_of?(project) ? 'my-project' : nil}") << "" + + u = project.users_by_role + if u + u.keys.each do |r| + if r.allowed_to?(:edit_project) + mgrs = [] + u[r].sort.each do |m| + mgrs << link_to_user(m) + end + if mgrs.size < 3 + s << '' << mgrs.join(', ') << '' + else + s << mgrs.join(', ') end end + end + end - classes = (ancestors.empty? ? 'root' : 'child') - s << "
  • " + - link_to_project(project, {}, :class => "project #{User.current.member_of?(project) ? 'my-project' : nil}") - s << "
    #{textilizable(project.short_description, :project => project)}
    " unless project.description.blank? - s << "
    \n" - ancestors << project - else - t = TRUE - end - end + s << "
  • " << format_date(project.created_on) << "" << format_date(project.updated_on) << "
    " + s << textilizable(project.short_description, :project => project) unless project.description.blank? + s << "
    " - a + @project = original_project + + s end diff -r d1713ab10813 -r 90cc857e968a app/models/issue.rb --- a/app/models/issue.rb Mon Feb 07 12:30:26 2011 +0000 +++ b/app/models/issue.rb Mon Feb 07 12:52:57 2011 +0000 @@ -527,7 +527,8 @@ # Returns a string of css classes that apply to the issue def css_classes - s = "issue status-#{status.position} priority-#{priority.position}" + s = "issue status-#{status.position} " + s << "priority-#{priority.position}" s << ' closed' if closed? s << ' overdue' if overdue? s << ' created-by-me' if User.current.logged? && author_id == User.current.id diff -r d1713ab10813 -r 90cc857e968a app/models/project.rb --- a/app/models/project.rb Mon Feb 07 12:30:26 2011 +0000 +++ b/app/models/project.rb Mon Feb 07 12:52:57 2011 +0000 @@ -418,7 +418,14 @@ # Returns a short description of the projects (first lines) def short_description(length = 255) - description.gsub(/^(.{#{length}}[^\n\r]*).*$/m, '\1...').strip if description + ## Original Redmine code: this truncates to the CR that is more + ## than "length" characters from the start. + # description.gsub(/^(.{#{length}}[^\n\r]*).*$/m, '\1...').strip if description + ## That's too much for us, and also we want to omit images and the + ## like. Truncate instead to the first CR that follows _any_ + ## non-blank text, and to the next word break beyond "length" + ## characters if the result is still longer than that. + description.gsub(/![^\s]+!/, '').gsub(/^(\s*[^\n\r]*).*$/m, '\1').gsub(/^(.{#{length}}\b).*$/m, '\1 ...').strip if description end def css_classes diff -r d1713ab10813 -r 90cc857e968a app/views/account/terms.rhtml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/views/account/terms.rhtml Mon Feb 07 12:52:57 2011 +0000 @@ -0,0 +1,17 @@ +

    <%=l(:label_terms_and_conditions)%> <%=link_to l(:label_login_with_open_id_option), signin_url if Setting.openid? %>

    + +<% form_tag({:action => 'register'}, :class => "tabular") do %> +<%= error_messages_for 'user' %> + + + + +
    + + + <%= label_tag(:label_accept_terms_and_conditions, l(:label_accept_terms_and_conditions)) %> + <%= check_box_tag(:pet_dog) %> + + <% end %> \ No newline at end of file diff -r d1713ab10813 -r 90cc857e968a app/views/members/autocomplete_for_member.rhtml --- a/app/views/members/autocomplete_for_member.rhtml Mon Feb 07 12:30:26 2011 +0000 +++ b/app/views/members/autocomplete_for_member.rhtml Mon Feb 07 12:52:57 2011 +0000 @@ -1,1 +1,3 @@ -<%= principals_check_box_tags 'member[user_ids][]', @principals %> \ No newline at end of file +<% if params[:q] && params[:q].length > 1 %> +<%= principals_check_box_tags 'member[user_ids][]', @principals %> +<% end %> diff -r d1713ab10813 -r 90cc857e968a app/views/projects/index.rhtml --- a/app/views/projects/index.rhtml Mon Feb 07 12:30:26 2011 +0000 +++ b/app/views/projects/index.rhtml Mon Feb 07 12:52:57 2011 +0000 @@ -8,18 +8,19 @@ <%= '| ' + link_to(l(:label_project_new), {:controller => 'projects', :action => 'new'}, :class => 'icon icon-add') if User.current.allowed_to?(:add_project, nil, :global => true) %>
    -<% if User.current.logged? %> +<% if @user_projects %> - <%= render_my_project_hierarchy(@projects)%> + <%= render_my_project_hierarchy(@user_projects)%> - <%= render_other_project_hierarchy(@projects)%> - -<% else %> - -

    <%=l(:label_project_plural)%>

    - <%= render_project_hierarchy(@projects)%> <% end %> +

    +<%= l("label_project_all") %> +

    + +<%= render_project_table(@projects) %> + +

    <%= pagination_links_full @project_pages, @project_count %>

    <% other_formats_links do |f| %> diff -r d1713ab10813 -r 90cc857e968a app/views/projects/settings/_members.rhtml --- a/app/views/projects/settings/_members.rhtml Mon Feb 07 12:30:26 2011 +0000 +++ b/app/views/projects/settings/_members.rhtml Mon Feb 07 12:52:57 2011 +0000 @@ -50,7 +50,7 @@ -<% principals = Principal.active.find(:all, :limit => 10, :order => 'type, login, lastname ASC') - @project.principals %> +<% principals = Principal.active.find(:all, :limit => 100, :order => 'type, login, lastname ASC') - @project.principals %>
    <% if roles.any? && principals.any? %> @@ -66,9 +66,11 @@ :url => { :controller => 'members', :action => 'autocomplete_for_member', :id => @project }, :with => 'q') %> - +
    - <%= principals_check_box_tags 'member[user_ids][]', principals %> + <% if params[:q] && params[:q].length > 1 %> + <%= principals_check_box_tags 'member[user_ids][]', @principals %> + <% end %>

    <%= l(:label_role_plural) %>: diff -r d1713ab10813 -r 90cc857e968a app/views/projects/settings/_repository.rhtml --- a/app/views/projects/settings/_repository.rhtml Mon Feb 07 12:30:26 2011 +0000 +++ b/app/views/projects/settings/_repository.rhtml Mon Feb 07 12:52:57 2011 +0000 @@ -7,7 +7,7 @@

    <% if !@repository || !@repository.url %> -
    • The repository for a project will normally be set up automatically within a few minutes of the project being created.
    +
    • <%= l(:text_settings_repo_creation) %>
    <% end %>

    <%= label_tag('repository_scm', l(:label_scm)) %><%= scm_select_tag(@repository) %>

    <%= repository_field_tags(f, @repository) if @repository %> diff -r d1713ab10813 -r 90cc857e968a app/views/welcome/index.rhtml --- a/app/views/welcome/index.rhtml Mon Feb 07 12:30:26 2011 +0000 +++ b/app/views/welcome/index.rhtml Mon Feb 07 12:52:57 2011 +0000 @@ -9,14 +9,6 @@
    <%= textilizable Setting.welcome_text %> - <% if not @tipsoftheday.empty? %> -
    -

    <%=l(:label_tipoftheday)%>

    - <%= textilizable @tipsoftheday %> -
    - <% end %> - - <% if @news.any? %>

    <%=l(:label_news_latest)%>

    @@ -28,6 +20,13 @@
    + <% if not @tipsoftheday.empty? %> +
    +

    <%=l(:label_tipoftheday)%>

    + <%= textilizable @tipsoftheday %> +
    + <% end %> + <% if @projects.any? %>

    <%=l(:label_project_latest)%>

    diff -r d1713ab10813 -r 90cc857e968a config/locales/en-GB.yml --- a/config/locales/en-GB.yml Mon Feb 07 12:30:26 2011 +0000 +++ b/config/locales/en-GB.yml Mon Feb 07 12:52:57 2011 +0000 @@ -242,6 +242,7 @@ field_role: Role field_homepage: Homepage field_is_public: Public + field_is_private: Private field_parent: Subproject of field_is_in_roadmap: Issues displayed in roadmap field_login: Login @@ -435,6 +436,7 @@ other: "{{count}} projects" label_project_all: All Projects label_project_latest: Latest projects + label_managers: Managed by label_issue: Issue label_issue_new: New issue label_issue_plural: Issues @@ -472,6 +474,7 @@ label_information_plural: Information label_please_login: Please log in label_register: Register + label_terms_and_conditions: Terms & Conditions for use of code.soundsoftware.ac.uk label_login_with_open_id_option: or login with OpenID label_password_lost: Lost password label_home: Home @@ -846,6 +849,7 @@ text_tip_issue_end_day: task ending this day text_tip_issue_begin_end_day: task beginning and ending this day text_project_identifier_info: 'Only lower case letters (a-z), numbers and dashes are allowed.
    This will be used in all project-related URLs, and as the repository name. Once saved, the identifier can not be changed.' + text_project_homepage_info: 'Link to an external project page.' text_project_name_info: "This will be the name of your project throughout this site.
    You can change your project's name at any time, in the project's settings." text_project_visibility_info: "If your project is not public, it will only be visible to users that you have added as project members." text_user_ssamr_description_info: 'Please describe your current research or development interests, within the fields of audio and music.
    This information is publicly visible in your profile and you can edit it at any time.' @@ -890,6 +894,7 @@ text_wiki_page_destroy_children: "Delete child pages and all their descendants" text_wiki_page_reassign_children: "Reassign child pages to this parent page" text_own_membership_delete_confirmation: "You are about to remove some or all of your permissions and may no longer be able to edit this project after that.\nAre you sure you want to continue?" + text_settings_repo_creation: The repository for a project should be set up automatically within a few minutes of the project being created.
    You should not have to adjust any settings here; please check again in ten minutes. default_role_manager: Manager default_role_developer: Developer @@ -948,7 +953,7 @@ label_user_mail_option_only_my_events: Only for things I watch or I'm involved in label_user_mail_option_only_assigned: Only for things I am assigned to notice_not_authorized_archived_project: The project you're trying to access has been archived. - label_principal_search: "Search for user or group:" + label_principal_search: "Search by name:" label_user_search: "Search for user:" field_visible: Visible setting_emails_header: Emails header diff -r d1713ab10813 -r 90cc857e968a config/locales/en.yml --- a/config/locales/en.yml Mon Feb 07 12:30:26 2011 +0000 +++ b/config/locales/en.yml Mon Feb 07 12:52:57 2011 +0000 @@ -246,6 +246,7 @@ field_role: Role field_homepage: Homepage field_is_public: Public + field_is_private: Private field_parent: Subproject of field_is_in_roadmap: Issues displayed in roadmap field_login: Login @@ -450,6 +451,7 @@ other: "{{count}} projects" label_project_all: All Projects label_project_latest: Latest projects + label_managers: Managed by label_issue: Issue label_issue_new: New issue label_issue_plural: Issues @@ -487,6 +489,8 @@ label_information_plural: Information label_please_login: Please log in label_register: Register + label_terms_and_conditions: Terms & Conditions for use of code.soundsoftware.ac.uk + label_accept_terms_and_conditions: I have read the terms and conditions and fully accept them label_login_with_open_id_option: or login with OpenID label_password_lost: Lost password label_home: Home @@ -799,7 +803,7 @@ label_profile: Profile label_subtask_plural: Subtasks label_project_copy_notifications: Send email notifications during the project copy - label_principal_search: "Search for user or group:" + label_principal_search: "Search by name:" label_user_search: "Search for user:" button_login: Login @@ -920,6 +924,7 @@ text_own_membership_delete_confirmation: "You are about to remove some or all of your permissions and may no longer be able to edit this project after that.\nAre you sure you want to continue?" text_zoom_in: Zoom in text_zoom_out: Zoom out + text_settings_repo_creation: The repository for a project should be set up automatically within a few minutes of the project being created.
    You should not have to adjust any settings here.
    Please check again in ten minutes, and contact us if there is any problem. default_role_manager: Manager default_role_developer: Developer diff -r d1713ab10813 -r 90cc857e968a db/seed_data/terms_and_conditions.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/db/seed_data/terms_and_conditions.txt Mon Feb 07 12:52:57 2011 +0000 @@ -0,0 +1,20 @@ +As a registered user, + + you are expected to be an active researcher in audio and music in the UK, or to be working with active researchers in audio and music in the UK who are registered users of the site + you undertake to ensure that copyright law is respected in, and attributions are properly maintained for, work you store on or publish through this site + you take responsibility for ensuring that your own institution's terms and conditions are followed with regard to rights in the work you store on this site + you undertake not to use this site for any illegal purpose or for the encouragement of illegal activity + you undertake not to harass or cause a nuisance for any other users, or for anyone else through your use of this site + you undertake not to use the site to promote any commercial activity outside of your normal research work + you accept that any account details you enter will be publicly visible, except for your email address if you choose to to hide it + you accept that any activity associated with your account may be publicly visible + you accept that any account details you enter will be visible to SoundSoftware.ac.uk administrators and may be used to determine whether they have a legitimate reason to use the site or not + The SoundSoftware.ac.uk administrators + + claim no rights in material stored here by users (all copyright holders retain their copyright in work stored here) + accept no liability for ensuring that material stored or published here by users is in accordance with copyright law + aim to keep the site available and to ensure that data is secure and backed up, but offer no guarantee to that effect + reserve the right to carry out administrative tasks as necessary, including administrative work that relates to private projects set up by users + declare that we will not permit users' private projects to be seen or used by employees or students of Queen Mary University of London, except in the case of administrative officials for reasons concerned solely with the daily running of the site + undertake that all content will be made available to the users who own it even if for any reason this site can no longer be maintained and run + SoundSoftware.ac.uk and Queen Mary, University of London accept no liability for any loss or damage incurred through the use of this site. \ No newline at end of file diff -r d1713ab10813 -r 90cc857e968a public/themes/soundsoftware/images/home.png Binary file public/themes/soundsoftware/images/home.png has changed diff -r d1713ab10813 -r 90cc857e968a public/themes/soundsoftware/stylesheets/application.css --- a/public/themes/soundsoftware/stylesheets/application.css Mon Feb 07 12:30:26 2011 +0000 +++ b/public/themes/soundsoftware/stylesheets/application.css Mon Feb 07 12:52:57 2011 +0000 @@ -67,6 +67,19 @@ tr.entry { border-left: 1px solid #a9b680; border-right: 1px solid #a9b680; } tr.entry:last-child { border-bottom: 1px solid #a9b680; } +table.projects th { text-align: left; } +table.projects th.managers { color: #3e442c; } +table.projects .root .name { font-size: 1.2em; } +table.projects .root .description { padding-bottom: 0.5em; } +table.projects .hosted_here { font-weight: bold; } +table.projects .child .name { font-weight: normal; } +table.projects .child .description { font-size: 0.95em; } +table.projects .child .firstcol { padding-left: 1em } +table.projects .level2 .firstcol { padding-left: 2em; } +table.projects .level3 .firstcol { padding-left: 3em; } + +ul.projects .public, ul.projects .private { padding-left: 0.5em; color: #3e442c; font-size: 0.95em } + #top-menu { position: absolute; top: 0; z-index: 1; left: 0px; width: 100%; font-size: 90%; /* height: 2em; */ margin: 0; padding: 0; padding-top: 0.5em; background-color: #3e442c; } #top-menu ul { margin-left: 10px; } #top-menu a { font-weight: bold; } @@ -74,9 +87,9 @@ #header a { color: #be5700; } #header h1 { color: #525a38; margin-top: 25px; font-size: 3em; font-weight: normal; margin-left: 10px; } .header-general h1 { - background: url('soundsoftware-logo-title-only-transparent.png') no-repeat 0 0; + background: url('soundsoftware-logo-title-only-transparent-beta.png') no-repeat 0 0; text-indent: -9999px; - width: 446px; + width: 500px; height: 34px; } diff -r d1713ab10813 -r 90cc857e968a public/themes/soundsoftware/stylesheets/soundsoftware-logo-title-only-transparent-beta.png Binary file public/themes/soundsoftware/stylesheets/soundsoftware-logo-title-only-transparent-beta.png has changed diff -r d1713ab10813 -r 90cc857e968a vendor/plugins/redmine_checkout/config/locales/en-GB.yml --- a/vendor/plugins/redmine_checkout/config/locales/en-GB.yml Mon Feb 07 12:30:26 2011 +0000 +++ b/vendor/plugins/redmine_checkout/config/locales/en-GB.yml Mon Feb 07 12:52:57 2011 +0000 @@ -1,4 +1,4 @@ -en: +en-GB: label_checkout: "Checkout" setting_checkout_display_checkout_info: "Display checkout information"