Mercurial > hg > soundsoftware-site
changeset 1484:51364c0cd58f redmine-2.4-integration
Merge from live branch. Still need to merge manually in files overridden by plugins.
line wrap: on
line diff
--- a/.hgignore Tue Jan 14 14:37:42 2014 +0000 +++ b/.hgignore Wed Jan 15 09:59:14 2014 +0000 @@ -32,7 +32,10 @@ vendor/rails *.rbc .git/ +*~ +public/themes/soundsoftware/stylesheets/fonts/* .bundle Gemfile.lock Gemfile.local +re:^config\.ru$
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.hgtags Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,2 @@ +202986dd17e4e02b3622b65cf38f358b67a393e8 bibliography_plugin_alpha +cf4cc816278aa7cc8f39a465f52304f6e91dd34a last_rails2_version
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/all-wcprops Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 38 +/svn/!svn/ver/6261/branches/1.2-stable +END
--- a/app/controllers/account_controller.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/controllers/account_controller.rb Wed Jan 15 09:59:14 2014 +0000 @@ -112,15 +112,21 @@ # User self-registration def register (redirect_to(home_url); return) unless Setting.self_registration? || session[:auth_source_registration] + if request.get? session[:auth_source_registration] = nil - @user = User.new(:language => current_language.to_s) + @user = User.new(:language => Setting.default_language) + + @ssamr_user_details = SsamrUserDetail.new + else user_params = params[:user] || {} @user = User.new @user.safe_attributes = user_params @user.admin = false + @user.register + if session[:auth_source_registration] @user.activate @user.login = session[:auth_source_registration][:login] @@ -137,6 +143,13 @@ @user.password, @user.password_confirmation = user_params[:password], user_params[:password_confirmation] end + @ssamr_user_details = SsamrUserDetail.new(params[:ssamr_user_details]) + + # associates the 2 objects + @user.ssamr_user_detail = @ssamr_user_details + @selected_institution_id = params[:ssamr_user_details][:institution_id].to_i + + case Setting.self_registration when '1' register_by_email_activation(@user) @@ -318,6 +331,9 @@ # Pass a block for behavior when a user fails to save def register_manually_by_administrator(user, &block) if user.save + + @ssamr_user_details.save! + # Sends an email to the administrators Mailer.account_activation_request(user).deliver account_pending(user)
--- a/app/controllers/activities_controller.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/controllers/activities_controller.rb Wed Jan 15 09:59:14 2014 +0000 @@ -40,7 +40,22 @@ events = @activity.events(@date_from, @date_to) - if events.empty? || stale?(:etag => [@activity.scope, @date_to, @date_from, @with_subprojects, @author, events.first, events.size, User.current, current_language]) + @institution_name = params[:institution] + if !@institution_name.blank? + events = events.select do |e| + e.respond_to?(:event_author) and e.event_author and + e.event_author.respond_to?(:ssamr_user_detail) and + !e.event_author.ssamr_user_detail.nil? and + e.event_author.ssamr_user_detail.institution_name == @institution_name + end + if events.empty? + # We don't want to dump into the output any arbitrary string + # from the URL that has no matching events + @institution_name = "" + end + end + + if events.empty? || stale?(:etag => [@activity.scope, @date_to, @date_from, @with_subprojects, @author, @institution_name, events.first, events.size, User.current, current_language]) respond_to do |format| format.html { @events_by_day = events.group_by {|event| User.current.time_to_date(event.event_datetime)}
--- a/app/controllers/application_controller.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/controllers/application_controller.rb Wed Jan 15 09:59:14 2014 +0000 @@ -274,14 +274,14 @@ def find_project @project = Project.find(params[:id]) rescue ActiveRecord::RecordNotFound - render_404 + User.current.logged? ? render_404 : require_login end # Find project of id params[:project_id] def find_project_by_project_id @project = Project.find(params[:project_id]) rescue ActiveRecord::RecordNotFound - render_404 + User.current.logged? ? render_404 : require_login end # Find a project based on params[:project_id] @@ -380,6 +380,21 @@ uri = URI.parse(back_url) # do not redirect user to another host or to the login or register page if (uri.relative? || (uri.host == request.host)) && !uri.path.match(%r{/(login|account/register)}) + # soundsoftware: if back_url is the home page, + # change it to My Page (#125) + if (uri.path == home_path) + if (uri.path =~ /\/$/) + uri.path = uri.path + "my" + else + uri.path = uri.path + "/my" + end + end + # soundsoftware: if login page is https but back_url http, + # switch back_url to https to ensure cookie validity (#83) + if (uri.scheme == "http") && (URI.parse(request.url).scheme == "https") + uri.scheme = "https" + end + back_url = uri.to_s redirect_to(back_url) return end
--- a/app/controllers/attachments_controller.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/controllers/attachments_controller.rb Wed Jan 15 09:59:14 2014 +0000 @@ -20,6 +20,10 @@ before_filter :file_readable, :read_authorize, :only => [:show, :download, :thumbnail] before_filter :delete_authorize, :only => :destroy before_filter :authorize_global, :only => :upload + before_filter :active_authorize, :only => :toggle_active + + include AttachmentsHelper + helper :attachments accept_api_auth :show, :download, :upload @@ -48,7 +52,9 @@ end def download - if @attachment.container.is_a?(Version) || @attachment.container.is_a?(Project) + # cc: formerly this happened only if "@attachment.container.is_a?(Version)" + # or Project. Not good for us, we want to tally all downloads [by humans] + if not user_is_search_bot? @attachment.increment_download end @@ -116,6 +122,14 @@ end end + def toggle_active + @attachment.active = !@attachment.active? + @attachment.save! + respond_to do |format| + format.js + end + end + private def find_project @attachment = Attachment.find(params[:id]) @@ -144,6 +158,10 @@ @attachment.deletable? ? true : deny_access end + def active_authorize + true + end + def detect_content_type(attachment) content_type = attachment.content_type if content_type.blank?
--- a/app/controllers/files_controller.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/controllers/files_controller.rb Wed Jan 15 09:59:14 2014 +0000 @@ -25,8 +25,9 @@ include SortHelper def index - sort_init 'filename', 'asc' + sort_init 'active', 'desc' sort_update 'filename' => "#{Attachment.table_name}.filename", + 'active' => "#{Attachment.table_name}.active", 'created_on' => "#{Attachment.table_name}.created_on", 'size' => "#{Attachment.table_name}.filesize", 'downloads' => "#{Attachment.table_name}.downloads" @@ -50,4 +51,5 @@ end redirect_to project_files_path(@project) end + end
--- a/app/controllers/issues_controller.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/controllers/issues_controller.rb Wed Jan 15 09:59:14 2014 +0000 @@ -144,7 +144,16 @@ call_hook(:controller_issues_new_before_save, { :params => params, :issue => @issue }) @issue.save_attachments(params[:attachments] || (params[:issue] && params[:issue][:uploads])) if @issue.save + call_hook(:controller_issues_new_after_save, { :params => params, :issue => @issue}) + + # Also adds the assignee to the watcher's list + if params[:issue][:assigned_to_id] && !params[:issue][:assigned_to_id].empty? + unless @issue.watcher_ids.include?(params[:issue][:assigned_to_id]) + @issue.add_watcher(User.find(params[:issue][:assigned_to_id])) + end + end + respond_to do |format| format.html { render_attachment_warning_if_needed(@issue) @@ -387,10 +396,22 @@ return false end end + + # tests if the the user assigned_to_id + # is in this issues watcher's list + # if not, adds it. + + if params[:issue] && params[:issue][:assigned_to_id] && !params[:issue][:assigned_to_id].empty? + unless @issue.watched_by?(User.find(params[:issue][:assigned_to_id])) + @issue.add_watcher(User.find(params[:issue][:assigned_to_id])) + end + end + @issue.safe_attributes = issue_attributes @priorities = IssuePriority.active @allowed_statuses = @issue.new_statuses_allowed_to(User.current) true + end # TODO: Refactor, lots of extra code in here
--- a/app/controllers/members_controller.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/controllers/members_controller.rb Wed Jan 15 09:59:14 2014 +0000 @@ -17,6 +17,7 @@ class MembersController < ApplicationController model_object Member + menu_item :members before_filter :find_model_object, :except => [:index, :create, :autocomplete] before_filter :find_project_from_association, :except => [:index, :create, :autocomplete] before_filter :find_project_by_project_id, :only => [:index, :create, :autocomplete] @@ -54,12 +55,22 @@ attrs = params[:membership].dup user_ids = attrs.delete(:user_ids) user_ids.each do |user_id| - members << Member.new(:role_ids => params[:membership][:role_ids], :user_id => user_id) + @new_member = Member.new(:role_ids => params[:membership][:role_ids], :user_id => user_id) + members << @new_member + + # send notification to member + Mailer.member_added_to_project(@new_member, @project).deliver end else - members << Member.new(:role_ids => params[:membership][:role_ids], :user_id => params[:membership][:user_id]) + @new_member = Member.new(:role_ids => params[:membership][:role_ids], :user_id => params[:membership][:user_id]) + members << @new_member + + # send notification to member + Mailer.member_added_to_project(@new_member, @project).deliver end + @project.members << members + end respond_to do |format| @@ -112,9 +123,9 @@ end def autocomplete - respond_to do |format| - format.js - end + @principals = Principal.active.not_member_of(@project).like(params[:q]).all(:limit => 100) + logger.debug "Query for #{params[:q]} returned #{@principals.size} results" + render :layout => false end private
--- a/app/controllers/my_controller.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/controllers/my_controller.rb Wed Jan 15 09:59:14 2014 +0000 @@ -23,18 +23,24 @@ helper :issues helper :users helper :custom_fields + helper :projects + helper :activities BLOCKS = { 'issuesassignedtome' => :label_assigned_to_me_issues, 'issuesreportedbyme' => :label_reported_issues, 'issueswatched' => :label_watched_issues, + 'activitymyprojects' => :label_activity_my_recent, 'news' => :label_news_latest, + 'tipoftheday' => :label_tipoftheday, 'calendar' => :label_calendar, 'documents' => :label_document_plural, - 'timelog' => :label_spent_time + 'timelog' => :label_spent_time, + 'myprojects' => :label_my_projects, + 'colleagues' => :label_my_colleagues }.merge(Redmine::Views::MyPage::Block.additional_blocks).freeze - DEFAULT_LAYOUT = { 'left' => ['issuesassignedtome'], - 'right' => ['issuesreportedbyme'] + DEFAULT_LAYOUT = { 'left' => ['myprojects', 'activitymyprojects'], + 'right' => ['colleagues', 'tipoftheday', 'issueswatched'] }.freeze def index @@ -52,9 +58,40 @@ def account @user = User.current @pref = @user.pref + @ssamr_user_details = @user.ssamr_user_detail + + + if @user.ssamr_user_detail == nil + @selected_institution_id = nil + else + @selected_institution_id = @ssamr_user_details.institution_id.to_i + end + if request.post? @user.safe_attributes = params[:user] @user.pref.attributes = params[:pref] + + @user.pref[:no_self_notified] = (params[:no_self_notified] == '1') + + if @user.ssamr_user_detail == nil + @ssamr_user_details = SsamrUserDetail.new() + @user.ssamr_user_detail = @ssamr_user_details + else + @ssamr_user_details = @user.ssamr_user_detail + end + + if params[:ssamr_user_details].nil? or params[:ssamr_user_details].empty? + @ssamr_user_details.description = @user.ssamr_user_detail.description + @ssamr_user_details.institution_id = @user.ssamr_user_detail.institution_id + @institution_type = @ssamr_user_details.institution_type + @other_institution = @ssamr_user_details.other_institution + else + @ssamr_user_details.description = params[:ssamr_user_details][:description] + @ssamr_user_details.institution_id = params[:ssamr_user_details][:institution_id] + @ssamr_user_details.institution_type = params[:ssamr_user_details][:institution_type] + @ssamr_user_details.other_institution = params[:ssamr_user_details][:other_institution] + end + if @user.save @user.pref.save set_language_if_valid @user.language
--- a/app/controllers/projects_controller.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/controllers/projects_controller.rb Wed Jan 15 09:59:14 2014 +0000 @@ -20,8 +20,8 @@ menu_item :roadmap, :only => :roadmap menu_item :settings, :only => :settings - before_filter :find_project, :except => [ :index, :list, :new, :create, :copy ] - before_filter :authorize, :except => [ :index, :list, :new, :create, :copy, :archive, :unarchive, :destroy] + before_filter :find_project, :except => [ :index, :list, :explore, :new, :create, :copy ] + before_filter :authorize, :except => [ :index, :list, :explore, :new, :create, :copy, :archive, :unarchive, :destroy] before_filter :authorize_global, :only => [:new, :create] before_filter :require_admin, :only => [ :copy, :archive, :unarchive, :destroy ] accept_rss_auth :index @@ -44,16 +44,29 @@ include RepositoriesHelper include ProjectsHelper helper :members + include ActivitiesHelper + helper :activities - # Lists visible projects + # Lists visible projects. Paginator is for top-level projects only + # (subprojects belong to them) def index respond_to do |format| format.html { - scope = Project - unless params[:closed] - scope = scope.active - end - @projects = scope.visible.order('lft').all + sort_init 'name' + sort_update %w(name lft created_on updated_on) + @limit = per_page_option + @project_count = Project.visible_roots.count + @project_pages = Paginator.new self, @project_count, @limit, params['page'] + @offset ||= @project_pages.current.offset + @projects = Project.visible_roots.all(:offset => @offset, :limit => @limit, :order => sort_clause) + render :template => 'projects/index.html.erb', :layout => !request.xhr? + +## Redmine 2.2: +# scope = Project +# unless params[:closed] +# scope = scope.active +# end +# @projects = scope.visible.order('lft').all } format.api { @offset, @limit = api_offset_and_limit @@ -67,6 +80,16 @@ end end + # A different view of projects using explore boxes + def explore + respond_to do |format| + format.html { + @projects = Project.visible + render :template => 'projects/explore.html.erb', :layout => !request.xhr? + } + end + end + def new @issue_custom_fields = IssueCustomField.sorted.all @trackers = Tracker.sorted.all @@ -80,7 +103,7 @@ @project = Project.new @project.safe_attributes = params[:project] - if validate_parent_id && @project.save + if validate_is_public_key && validate_parent_id && @project.save @project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id') # Add current user as a project member if current user is not admin unless User.current.admin? @@ -171,6 +194,7 @@ @issue_category ||= IssueCategory.new @member ||= @project.members.new @trackers = Tracker.sorted.all + @repository ||= @project.repository @wiki ||= @project.wiki end @@ -199,6 +223,14 @@ end end + def overview + @project.has_welcome_page = params[:has_welcome_page] + if @project.save + flash[:notice] = l(:notice_successful_update) + end + redirect_to :action => 'settings', :id => @project, :tab => 'overview' + end + def modules @project.enabled_module_names = params[:enabled_module_names] flash[:notice] = l(:notice_successful_update) @@ -245,6 +277,19 @@ private + def validate_is_public_key + # Although is_public isn't mandatory in the project model (it gets + # defaulted), it must be present in params -- it can be true or + # false, but it must be there. This permits us to make forms in + # which the user _has_ to select public or private (rather than + # defaulting it) if we want to + if params.nil? || params[:project].nil? || !params[:project].has_key?(:is_public) + @project.errors.add :is_public, :public_or_private + return false + end + true + end + # Validates parent_id param according to user's permissions # TODO: move it to Project model in a validation that depends on User.current def validate_parent_id
--- a/app/controllers/repositories_controller.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/controllers/repositories_controller.rb Wed Jan 15 09:59:14 2014 +0000 @@ -63,6 +63,7 @@ end def update + params[:repository_scm]='Mercurial' attrs = pickup_extra_info @repository.safe_attributes = attrs[:attrs] if attrs[:attrs_extra].keys.any? @@ -170,9 +171,7 @@ @content = @repository.cat(@path, @rev) (show_error_not_found; return) unless @content - if is_raw || - (@content.size && @content.size > Setting.file_max_size_displayed.to_i.kilobyte) || - ! is_entry_text_data?(@content, @path) + if is_raw # Force the download send_opt = { :filename => filename_for_content_disposition(@path.split('/').last) } send_type = Redmine::MimeType.of(@path) @@ -180,6 +179,7 @@ send_opt[:disposition] = (Redmine::MimeType.is_type?('image', @path) && !is_raw ? 'inline' : 'attachment') send_data @content, send_opt else + @display_raw = ((@content.size && @content.size > Setting.file_max_size_displayed.to_i.kilobyte) || !is_entry_text_data?(@content, @path)) # Prevent empty lines when displaying a file with Windows style eol # TODO: UTF-16 # Is this needs? AttachmentsController reads file simply.
--- a/app/controllers/search_controller.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/controllers/search_controller.rb Wed Jan 15 09:59:14 2014 +0000 @@ -66,6 +66,7 @@ # no more than 5 tokens to search for @tokens.slice! 5..-1 if @tokens.size > 5 + @project_matches = [] @results = [] @results_by_type = Hash.new {|h,k| h[k] = 0} @@ -79,6 +80,12 @@ :before => params[:previous].nil?) @results += r @results_by_type[s] += c + if s == 'projects' + r, c = s.singularize.camelcase.constantize.search(@tokens, nil, + :all_words => @all_words, + :titles_only => 1) + @project_matches += r + end end @results = @results.sort {|a,b| b.event_datetime <=> a.event_datetime} if params[:previous].nil?
--- a/app/controllers/sys_controller.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/controllers/sys_controller.rb Wed Jan 15 09:59:14 2014 +0000 @@ -23,7 +23,7 @@ # extra_info attribute from repository breaks activeresource client render :xml => p.to_xml( :only => [:id, :identifier, :name, :is_public, :status], - :include => {:repository => {:only => [:id, :url]}} + :include => {:repository => {:only => [:id, :url, :is_external, :external_url]}} ) end @@ -68,6 +68,55 @@ render :nothing => true, :status => 404 end + def get_external_repo_url + project = Project.find(params[:id]) + if project.repository + repo = project.repository + if repo.is_external? + render :text => repo.external_url, :status => 200 + else + render :nothing => true, :status => 200 + end + end + rescue ActiveRecord::RecordNotFound + render :nothing => true, :status => 404 + end + + def clear_repository_cache + project = Project.find(params[:id]) + if project.repository + project.repository.clear_cache + end + render :nothing => true, :status => 200 + rescue ActiveRecord::RecordNotFound + render :nothing => true, :status => 404 + end + + def set_embedded_active + project = Project.find(params[:id]) + mods = project.enabled_modules + enable = (params[:enable] == "1") + if mods.detect {|m| m.name == "redmine_embedded"} + logger.info "Project #{project.name} currently has Embedded enabled" + if !enable + logger.info "Disabling Embedded" + modnames = mods.all(:select => :name).collect{|m| m.name}.reject{|n| n == "redmine_embedded"} + project.enabled_module_names = modnames + end + else + logger.info "Project #{project.name} currently has Embedded disabled" + if enable + logger.info "Enabling Embedded" + modnames = mods.all(:select => :name).collect{|m| m.name} + modnames << "redmine_embedded" + project.enabled_module_names = modnames + end + end + render :nothing => true, :status => 200 + rescue ActiveRecord::RecordNotFound + render :nothing => true, :status => 404 + end + protected def check_enabled
--- a/app/controllers/users_controller.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/controllers/users_controller.rb Wed Jan 15 09:59:14 2014 +0000 @@ -59,6 +59,12 @@ end def show + + if @user.ssamr_user_detail != nil + @description = @user.ssamr_user_detail.description + @institution_name = @user.ssamr_user_detail.institution_name + end + # show projects based on current user visibility @memberships = @user.memberships.where(Project.visible_condition(User.current)).all @@ -78,23 +84,36 @@ end end - def new + def new @user = User.new(:language => Setting.default_language, :mail_notification => Setting.default_notification_option) @user.safe_attributes = params[:user] @auth_sources = AuthSource.all + @ssamr_user_details = SsamrUserDetail.new end def create @user = User.new(:language => Setting.default_language, :mail_notification => Setting.default_notification_option) @user.safe_attributes = params[:user] + @user = User.new(params[:user]) @user.admin = params[:user][:admin] || false @user.login = params[:user][:login] @user.password, @user.password_confirmation = params[:user][:password], params[:user][:password_confirmation] unless @user.auth_source_id + # TODO: Similar to My#account + @user.pref.attributes = params[:pref] + @user.pref[:no_self_notified] = (params[:no_self_notified] == '1') + + @ssamr_user_details = SsamrUserDetail.new(params[:ssamr_user_details]) + + # associates the 2 objects + @user.ssamr_user_detail = @ssamr_user_details + if @user.save @user.pref.attributes = params[:pref] @user.pref.save + @ssamr_user_details.save! + Mailer.account_information(@user, @user.password).deliver if params[:send_information] respond_to do |format| @@ -123,6 +142,15 @@ def edit @auth_sources = AuthSource.all + + @ssamr_user_details = @user.ssamr_user_detail + + if @user.ssamr_user_detail == nil + @selected_institution_id = nil + else + @selected_institution_id = @user.ssamr_user_detail.institution_id.to_i + end + @membership ||= Member.new end @@ -138,6 +166,26 @@ # TODO: Similar to My#account @user.pref.attributes = params[:pref] + if @user.ssamr_user_detail == nil + @ssamr_user_details = SsamrUserDetail.new() + @user.ssamr_user_detail = @ssamr_user_details + else + @ssamr_user_details = @user.ssamr_user_detail + end + + if params[:ssamr_user_details].nil? or params[:ssamr_user_details].empty? + @ssamr_user_details.description = @user.ssamr_user_detail.description + @ssamr_user_details.institution_id = @user.ssamr_user_detail.institution_id + @ssamr_user_details.other_institution = @user.ssamr_user_detail.other_institution + @ssamr_user_details.institution_type = @user.ssamr_user_detail.institution_type + + else + @ssamr_user_details.description = params[:ssamr_user_details][:description] + @ssamr_user_details.institution_id = params[:ssamr_user_details][:institution_id] + @ssamr_user_details.other_institution = params[:ssamr_user_details][:other_institution] + @ssamr_user_details.institution_type = params[:ssamr_user_details][:institution_type] + end + if @user.save @user.pref.save
--- a/app/controllers/welcome_controller.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/controllers/welcome_controller.rb Wed Jan 15 09:59:14 2014 +0000 @@ -18,9 +18,21 @@ class WelcomeController < ApplicationController caches_action :robots + include ProjectsHelper + helper :projects + def index - @news = News.latest User.current - @projects = Project.latest User.current + @site_project = Project.find_by_identifier "soundsoftware-site" + @site_news = [] + @site_news = News.latest_for(@site_project, 3) if @site_project + + # tests if user is logged in to generate the tips of the day list + if User.current.logged? + @tipsoftheday = Setting.tipoftheday_text + else + @tipsoftheday = '' + end + end def robots
--- a/app/helpers/activities_helper.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/helpers/activities_helper.rb Wed Jan 15 09:59:14 2014 +0000 @@ -18,6 +18,7 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. module ActivitiesHelper + def sort_activity_events(events) events_by_group = events.group_by(&:event_group) sorted_events = [] @@ -30,4 +31,152 @@ end sorted_events end + + def date_of_event(e) + if e.respond_to? :updated_at + e.updated_at + elsif e.respond_to? :updated_on + e.updated_on + elsif e.respond_to? :created_on + e.created_on + elsif e.respond_to? :committed_on + e.committed_on + else + nil + end + end + + def project_activity_on_events(events) + + # Score each project for which there are any events, by giving + # each event a score based on how long ago it was (the more recent + # the better). Return a hash mapping project id to score. + + projhash = Hash.new + + events.each do |e| + if e.respond_to?(:project) + p = e.project + d = date_of_event e + if !d.nil? + dd = Date.parse d.to_s + age = Date.today - dd + score = (age < 14 ? 15-age : 1) + if projhash.key? p + projhash[p] += score + else + projhash[p] = score + end + end + end + end + projhash + end + + def projects_by_activity(user, count) + + # Return up to count of the user's project ids ordered by that user's + # recent activity, omitting any projects for which no activity + # occurred in the recent past and any projects not visible to + # the current user + + activity = Redmine::Activity::Fetcher.new(User.current, :author => user) + + # Limit scope so as to exclude issues (which non-members can add) + activity.scope = [ "changesets", "files", "documents", "news", "wiki_edits", "messages", "time_entries", "publications" ] + + days = Setting.activity_days_default.to_i + events = activity.events(Date.today - days, Date.today + 1) + projhash = project_activity_on_events(events) + projhash.keys.sort_by { |k| -projhash[k] }.first(count) + end + + def render_active_colleagues(colleagues) + + s = "" + + start = Time.now + + my_inst = "" + if ! User.current.ssamr_user_detail.nil? + my_inst = User.current.ssamr_user_detail.institution_name + end + + actives = Hash.new + for c in colleagues + u = User.find_by_id(c) + active_projects = projects_by_activity(u, 3) + if !active_projects.empty? + actives[c] = active_projects + end + end + + if actives.empty? + l(:label_no_active_colleagues) + else + + s << "<dl>" + for c in actives.keys.sample(10) + u = User.find_by_id(c) + s << "<dt>" + s << avatar(u, :size => '24') + s << "<span class='user'>" + s << h(u.name) + s << "</span>" + if !u.ssamr_user_detail.nil? + inst = u.ssamr_user_detail.institution_name + if inst != "" and inst != my_inst + s << " - <span class='institution'>" + s << h(u.ssamr_user_detail.institution_name) + s << "</span>" + end + end + s << "</dt>" + s << "<dd>" + s << "<span class='active'>" + s << (actives[c].map { |p| link_to_project(p) }.join ", ") + s << "</span>" + end + s << "</dl>" + + finish = Time.now + logger.info "render_active_colleagues: took #{finish-start}" + + s.html_safe + end + end + + def busy_projects(events, count) + + # Return a list of count projects randomly selected from amongst + # the busiest projects represented by the given activity events + + projhash = project_activity_on_events(events) + + # pick N highest values and use cutoff value as selection threshold + threshold = projhash.values.sort.last(count).first + + # select projects above threshold and pick N from them randomly + busy = projhash.keys.select { |k| projhash[k] >= threshold }.sample(count) + + # return projects rather than just ids + busy.map { |pid| Project.find(pid) } + end + + def busy_institutions(events, count) + authors = events.map do |e| + e.event_author unless !e.respond_to?(:event_author) + end.compact + institutions = authors.map do |a| + if a.respond_to?(:ssamr_user_detail) and !a.ssamr_user_detail.nil? + a.ssamr_user_detail.institution_name + end + end + insthash = institutions.compact.sort.group_by { |i| i } + insthash = insthash.merge(insthash) { |k,v| v.length } + threshold = insthash.values.sort.last(count).first + insthash.keys.select { |k| insthash[k] >= threshold }.sample(count) + end + +>>>>>>> other end
--- a/app/helpers/application_helper.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/helpers/application_helper.rb Wed Jan 15 09:59:14 2014 +0000 @@ -317,8 +317,15 @@ def principals_check_box_tags(name, principals) s = '' - principals.each do |principal| - s << "<label>#{ check_box_tag name, principal.id, false, :id => nil } #{h principal}</label>\n" + + principals.sort.each do |principal| + + if principal.type == "User" + s << "<label>#{ check_box_tag name, principal.id, false, :id => nil } #{link_to_user principal}</label>\n" + else + s << "<label>#{ check_box_tag name, principal.id, false, :id => nil } #{h principal} (Group)</label>\n" + end + end s.html_safe end @@ -432,21 +439,28 @@ def page_header_title if @project.nil? || @project.new_record? - h(Setting.app_title) + a = [h(Setting.app_title), ''] + else + pname = [] b = [] ancestors = (@project.root? ? [] : @project.ancestors.visible.all) if ancestors.any? root = ancestors.shift b << link_to_project(root, {:jump => current_menu_item}, :class => 'root') if ancestors.size > 2 - b << "\xe2\x80\xa6" + b << '…' ancestors = ancestors[-2, 2] end b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') } + b = b.join(' » ').html_safe + b << (' »'.html_safe) end - b << h(@project) - b.join(" \xc2\xbb ").html_safe + + pname << h(@project) + + a = [pname, b] + end end @@ -1234,6 +1248,17 @@ '<meta name="robots" content="noindex,follow,noarchive" />'.html_safe end + def stylesheet_platform_font_tag + agent = request.env['HTTP_USER_AGENT'] + name = 'fonts-generic' + if agent and agent =~ %r{Windows} + name = 'fonts-ms' + elsif agent and agent =~ %r{Macintosh} + name = 'fonts-mac' + end + stylesheet_link_tag name, :media => 'all' + end + # Returns true if arg is expected in the API response def include_in_api_response?(arg) unless @included_in_api_response
--- a/app/helpers/attachments_helper.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/helpers/attachments_helper.rb Wed Jan 15 09:59:14 2014 +0000 @@ -44,4 +44,11 @@ api.created_on attachment.created_on end end + + # Returns true if user agent appears (approximately) to be a search + # bot or crawler + def user_is_search_bot? + agent = request.env['HTTP_USER_AGENT'] + agent and agent =~ /(bot|slurp|crawler|spider)\b/i + end end
--- a/app/helpers/my_helper.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/helpers/my_helper.rb Wed Jan 15 09:59:14 2014 +0000 @@ -18,6 +18,7 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. module MyHelper + def calendar_items(startdt, enddt) Issue.visible. where(:project_id => User.current.projects.map(&:id)). @@ -26,6 +27,12 @@ all end + def all_colleagues_of(user) + # Return a list of all user ids who have worked with the given user + # (on projects that are visible to the current user) + user.projects.select { |p| p.visible? }.map { |p| p.members.map { |m| m.user_id } }.flatten.sort.uniq.reject { |i| user.id == i } + end + def documents_items Document.visible.order("#{Document.table_name}.created_on DESC").limit(10).all end @@ -68,4 +75,6 @@ order("#{TimeEntry.table_name}.spent_on DESC, #{Project.table_name}.name ASC, #{Tracker.table_name}.position ASC, #{Issue.table_name}.id ASC"). all end +======= +>>>>>>> other end
--- a/app/helpers/projects_helper.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/helpers/projects_helper.rb Wed Jan 15 09:59:14 2014 +0000 @@ -25,8 +25,8 @@ def project_settings_tabs tabs = [{:name => 'info', :action => :edit_project, :partial => 'projects/edit', :label => :label_information_plural}, + {:name => 'overview', :action => :edit_project, :partial => 'projects/settings/overview', :label => :label_welcome_page}, {:name => 'modules', :action => :select_project_modules, :partial => 'projects/settings/modules', :label => :label_module_plural}, - {:name => 'members', :action => :manage_members, :partial => 'projects/settings/members', :label => :label_member_plural}, {:name => 'versions', :action => :manage_versions, :partial => 'projects/settings/versions', :label => :label_version_plural}, {:name => 'categories', :action => :manage_categories, :partial => 'projects/settings/issue_categories', :label => :label_issue_category_plural}, {:name => 'wiki', :action => :manage_wiki, :partial => 'projects/settings/wiki', :label => :label_wiki}, @@ -51,17 +51,200 @@ content_tag('select', options.html_safe, :name => 'project[parent_id]', :id => 'project_parent_id') end - # Renders the projects index + def render_project_short_description(project) + s = '' + if (project.short_description) + s << "<div class='description'>" + s << textilizable(project.short_description, :project => project).gsub(/<[^>]+>/, '') + s << "</div>" + end + s.html_safe + end + + # Renders a tree of projects 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_project_hierarchy(projects) - render_project_nested_lists(projects) do |project| - s = link_to_project(project, {}, :class => "#{project.css_classes} #{User.current.member_of?(project) ? 'my-project' : nil}") - if project.description.present? - s << content_tag('div', textilizable(project.short_description, :project => project), :class => 'wiki description') + s = '' + if projects.any? + ancestors = [] + original_project = @project + projects.each do |project| + # set the project environment to please macros. + @project = project + if (ancestors.empty? || project.is_descendant_of?(ancestors.last)) + s << "<ul class='projects #{ ancestors.empty? ? 'root' : nil}'>\n" + else + ancestors.pop + s << "</li>" + while (ancestors.any? && !project.is_descendant_of?(ancestors.last)) + ancestors.pop + s << "</ul></li>\n" + end + end + classes = (ancestors.empty? ? 'root' : 'child') + s << "<li class='#{classes}'><div class='#{classes}'>" + + link_to_project(project, {}, :class => "project #{User.current.member_of?(project) ? 'my-project' : nil}") + s << render_project_short_description(project) + s << "</div>\n" + ancestors << project end - s end + s.html_safe end + + def render_my_project_in_hierarchy(project) + + s = '' + + if User.current.member_of?(project) + + # set the project environment to please macros. + @project = project + + classes = (project.root? ? 'root' : 'child') + + s << "<li class='#{classes}'><div class='#{classes}'>" + + link_to_project(project, {}, :class => "project my-project") + if project.is_public? + s << " <span class='public'>" << l(:field_is_public) << "</span>" + else + s << " <span class='private'>" << l(:field_is_private) << "</span>" + end + s << render_project_short_description(project) + s << "</div>\n" + + cs = '' + project.children.each do |child| + cs << render_my_project_in_hierarchy(child) + end + + if cs != '' + s << "<ul class='projects'>\n" << cs << "</ul>\n"; + end + + end + + s + + end + + # Renders a tree of projects where the current user belongs + # 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_my_project_hierarchy(projects) + + s = '' + + original_project = @project + + projects.each do |project| + if project.root? || !projects.include?(project.parent) + s << render_my_project_in_hierarchy(project) + end + end + + @project = original_project + + if s != '' + a = '' + a << "<ul class='projects root'>\n" + a << s + a << "</ul>\n" + s = a + end + + s.html_safe + + end + + # Renders a tree of projects. 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) + + s = "" + s << "<div class='autoscroll'>" + s << "<table class='list projects'>" + s << "<thead><tr>" + + s << sort_header_tag('name', :caption => l(:field_name)) + s << "<th class='managers'>" << l(:label_managers) << "</th>" + s << sort_header_tag('created_on', :default_order => 'desc') + s << sort_header_tag('updated_on', :default_order => 'desc') + + s << "</tr></thead><tbody>" + + original_project = @project + + projects.each do |project| + s << render_project_in_table(project, cycle('odd', 'even'), 0) + end + + s << "</table>" + + @project = original_project + + s.html_safe + end + + + def render_project_in_table(project, oddeven, level) + + # set the project environment to please macros. + @project = project + + classes = (level == 0 ? 'root' : 'child') + + s = "" + + s << "<tr class='#{oddeven} #{classes} level#{level}'>" + s << "<td class='firstcol' align=top><div class='name hosted_here" + s << " no_description" if project.description.blank? + s << "'>" << link_to_project(project, {}, :class => "project #{User.current.member_of?(project) ? 'my-project' : nil}"); + s << "</div>" + s << render_project_short_description(project) + + s << "<td class='managers' align=top>" + + 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 << '<nobr>' << mgrs.join(', ') << '</nobr>' + else + s << mgrs.join(', ') + end + end + end + end + + s << "</td>" + s << "<td class='created_on' align=top>" << format_date(project.created_on) << "</td>" + s << "<td class='updated_on' align=top>" << format_date(project.updated_on) << "</td>" + + s << "</tr>" + + project.children.each do |child| + if child.is_public? or User.current.member_of?(child) + s << render_project_in_table(child, oddeven, level + 1) + end + end + + s + end + + # Returns a set of options for a select field, grouped by project. def version_options_for_select(versions, selected=nil) grouped = Hash.new {|h,k| h[k] = []} @@ -81,4 +264,38 @@ sharing = 'none' unless Version::VERSION_SHARINGS.include?(sharing) l("label_version_sharing_#{sharing}") end + + def score_maturity(project) + nr_changes = (project.repository.nil? ? 0 : project.repository.changesets.count) + downloadables = [project.attachments, + project.versions.collect { |v| v.attachments }, + project.documents.collect { |d| d.attachments }].flatten + nr_downloadables = downloadables.count + nr_downloads = downloadables.map do |d| d.downloads end.sum + nr_members = project.members.count + nr_publications = if project.respond_to? :publications + then project.publications.count else 0 end + Math.log(1 + nr_changes) + + Math.log(1 + nr_downloadables) + + Math.log(1 + nr_downloads) + + Math.sqrt(nr_members > 1 ? (nr_members - 1) : 0) + + Math.sqrt(nr_publications) + end + + def all_maturity_scores() + phash = Hash.new + pp = Project.visible(User.anonymous) + pp.each do |p| + phash[p] = score_maturity p + end + phash + end + + def mature_projects(count) + phash = all_maturity_scores + scores = phash.values.sort + threshold = scores[scores.length / 2] + if threshold == 0 then threshold = 1 end + phash.keys.select { |k| phash[k] > threshold }.sample(count) + end end
--- a/app/helpers/repositories_helper.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/helpers/repositories_helper.rb Wed Jan 15 09:59:14 2014 +0000 @@ -294,4 +294,24 @@ end max_space end + + # Generates a link to a downloadable archive for a revision + # Options: + # * :text - Link text (default to the formatted revision) + def link_to_revision_archive(repository, revision, project, options={}) + method = repository.class.name.demodulize.underscore + "_link_to_revision_archive" + if repository.is_a?(Repository) && + respond_to?(method) && method != 'link_to_revision_archive' + send(method, repository, revision, project, options) + end + end + + def mercurial_link_to_revision_archive(repository, revision, project, options={}) + text = options.delete(:text) || format_revision(revision) + rev = revision.respond_to?(:identifier) ? revision.identifier : revision + if rev.blank? then rev = 'tip' end + content_tag('a', h(text), + { :href => "/hg/#{project.identifier}/archive/#{rev}.zip" }.merge(options)); + end + end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/models/institution.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,2 @@ +class Institution < ActiveRecord::Base +end
--- a/app/models/mailer.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/models/mailer.rb Wed Jan 15 09:59:14 2014 +0000 @@ -27,6 +27,30 @@ { :host => Setting.host_name, :protocol => Setting.protocol } end + # Builds a mail for notifying the specified member that they were + # added to a project + def member_added_to_project(member, project) + + principal = Principal.find(member.user_id) + + users = [] + if principal.type == "User" + users = [User.find(member.user_id)] + else + users = Principal.find(member.user_id).users + end + + users.map do |user| + + set_language_if_valid user.language + @project_url = url_for(:controller => 'projects', :action => 'show', :id => project.id) + @project_name = project.name + mail :to => user.mail, + :subject => l(:mail_subject_added_to_project, Setting.app_title) + + end + end + # Builds a mail for notifying to_users and cc_users about a new issue def issue_add(issue, to_users, cc_users) redmine_headers 'Project' => issue.project.identifier, @@ -492,3 +516,17 @@ Rails.logger end end + +# Patch TMail so that message_id is not overwritten + +### NB: Redmine 2.2 no longer uses TMail I think? This function has +### been removed there + +module TMail + class Mail + def add_message_id( fqdn = nil ) + self.message_id ||= ::TMail::new_message_id(fqdn) + end + end +end +
--- a/app/models/news.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/models/news.rb Wed Jan 15 09:59:14 2014 +0000 @@ -70,4 +70,9 @@ Mailer.news_added(self).deliver end end + + # returns latest news for a specific project + def self.latest_for(project, count = 5) + find(:all, :limit => count, :conditions => [ "#{News.table_name}.project_id = #{project.id}", Project.allowed_to_condition(User.current, :view_news) ], :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC") + end end
--- a/app/models/project.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/models/project.rb Wed Jan 15 09:59:14 2014 +0000 @@ -90,6 +90,7 @@ scope :status, lambda {|arg| where(arg.blank? ? nil : {:status => arg.to_i}) } scope :all_public, lambda { where(:is_public => true) } scope :visible, lambda {|*args| where(Project.visible_condition(args.shift || User.current, *args)) } + scope :visible_roots, lambda {|*args| where(Project.root_visible_by(args.shift || User.current, *args)) } scope :allowed_to, lambda {|*args| user = User.current permission = nil @@ -162,6 +163,10 @@ allowed_to_condition(user, :view_project, options) end + def self.root_visible_by(user=nil) + return "#{Project.table_name}.parent_id IS NULL AND " + visible_condition(user) + end + # Returns a SQL conditions string used to find all projects for which +user+ has the given +permission+ # # Valid options: @@ -542,8 +547,22 @@ end # Returns a short description of the projects (first lines) - def short_description(length = 255) - description.gsub(/^(.{#{length}}[^\n\r]*).*$/m, '\1...').strip if description + def short_description(length = 200) + + ## The short description is used in lists, e.g. Latest projects, + ## My projects etc. It should be no more than a line or two with + ## no text formatting. + + ## 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 can leave too much text 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}}[^\.;:,-]*).*$/m, '\1 ...').strip if description end def css_classes @@ -669,7 +688,8 @@ 'custom_field_values', 'custom_fields', 'tracker_ids', - 'issue_custom_field_ids' + 'issue_custom_field_ids', + 'has_welcome_page' safe_attributes 'enabled_module_names', :if => lambda {|project, user| project.new_record? || user.allowed_to?(:select_project_modules, project) }
--- a/app/models/repository.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/models/repository.rb Wed Jan 15 09:59:14 2014 +0000 @@ -51,6 +51,8 @@ 'password', 'path_encoding', 'log_encoding', + 'is_external', + 'external_url', 'is_default' safe_attributes 'url', @@ -357,6 +359,10 @@ nil end + def clear_cache + clear_changesets + end + def self.scm_adapter_class nil end
--- a/app/models/repository/bazaar.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/models/repository/bazaar.rb Wed Jan 15 09:59:14 2014 +0000 @@ -15,7 +15,7 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -require 'redmine/scm/adapters/bazaar_adapter' +require_dependency 'redmine/scm/adapters/bazaar_adapter' class Repository::Bazaar < Repository attr_protected :root_url
--- a/app/models/repository/cvs.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/models/repository/cvs.rb Wed Jan 15 09:59:14 2014 +0000 @@ -15,7 +15,7 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -require 'redmine/scm/adapters/cvs_adapter' +require_dependency 'redmine/scm/adapters/cvs_adapter' require 'digest/sha1' class Repository::Cvs < Repository
--- a/app/models/repository/darcs.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/models/repository/darcs.rb Wed Jan 15 09:59:14 2014 +0000 @@ -15,7 +15,7 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -require 'redmine/scm/adapters/darcs_adapter' +require_dependency 'redmine/scm/adapters/darcs_adapter' class Repository::Darcs < Repository validates_presence_of :url, :log_encoding
--- a/app/models/repository/filesystem.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/models/repository/filesystem.rb Wed Jan 15 09:59:14 2014 +0000 @@ -18,7 +18,7 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -require 'redmine/scm/adapters/filesystem_adapter' +require_dependency 'redmine/scm/adapters/filesystem_adapter' class Repository::Filesystem < Repository attr_protected :root_url
--- a/app/models/repository/git.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/models/repository/git.rb Wed Jan 15 09:59:14 2014 +0000 @@ -16,7 +16,7 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -require 'redmine/scm/adapters/git_adapter' +require_dependency 'redmine/scm/adapters/git_adapter' class Repository::Git < Repository attr_protected :root_url
--- a/app/models/repository/mercurial.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/models/repository/mercurial.rb Wed Jan 15 09:59:14 2014 +0000 @@ -15,7 +15,7 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -require 'redmine/scm/adapters/mercurial_adapter' +require_dependency 'redmine/scm/adapters/mercurial_adapter' class Repository::Mercurial < Repository # sort changesets by revision number @@ -24,7 +24,7 @@ :foreign_key => 'repository_id' attr_protected :root_url - validates_presence_of :url + # validates_presence_of :url # number of changesets to fetch at once FETCH_AT_ONCE = 100
--- a/app/models/repository/subversion.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/models/repository/subversion.rb Wed Jan 15 09:59:14 2014 +0000 @@ -15,7 +15,7 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -require 'redmine/scm/adapters/subversion_adapter' +require_dependency 'redmine/scm/adapters/subversion_adapter' class Repository::Subversion < Repository attr_protected :root_url
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/models/ssamr_user_detail.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,24 @@ +class SsamrUserDetail < ActiveRecord::Base + belongs_to :user + + validates_presence_of :description + + validate :check_institution + + def check_institution() + errors.add(:institution_id, "Please insert an institution") if + institution_id.blank? and other_institution.blank? + end + + def institution_name() + if not self.institution_type.nil? + if self.institution_type + Institution.find(self.institution_id).name + else + self.other_institution + end + else + "" + end + end +end
--- a/app/models/user.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/models/user.rb Wed Jan 15 09:59:14 2014 +0000 @@ -79,6 +79,11 @@ scope :logged, lambda { where("#{User.table_name}.status <> #{STATUS_ANONYMOUS}") } scope :status, lambda {|arg| where(arg.blank? ? nil : {:status => arg.to_i}) } + has_one :ssamr_user_detail, :dependent => :destroy, :class_name => 'SsamrUserDetail' + accepts_nested_attributes_for :ssamr_user_detail + + has_one :author + acts_as_customizable attr_accessor :password, :password_confirmation, :generate_password @@ -92,6 +97,7 @@ validates_presence_of :login, :firstname, :lastname, :mail, :if => Proc.new { |user| !user.is_a?(AnonymousUser) } validates_uniqueness_of :login, :if => Proc.new { |user| user.login_changed? && user.login.present? }, :case_sensitive => false validates_uniqueness_of :mail, :if => Proc.new { |user| user.mail_changed? && user.mail.present? }, :case_sensitive => false + # Login must contain letters, numbers, underscores only validates_format_of :login, :with => /\A[a-z0-9_\-@\.]*\z/i validates_length_of :login, :maximum => LOGIN_LENGTH_LIMIT @@ -107,6 +113,8 @@ before_destroy :remove_references_before_destroy after_save :update_notified_project_ids + validates_acceptance_of :terms_and_conditions, :on => :create, :message => :must_accept_terms_and_conditions + scope :in_group, lambda {|group| group_id = group.is_a?(Group) ? group.id : group.to_i where("#{User.table_name}.id IN (SELECT gu.user_id FROM #{table_name_prefix}groups_users#{table_name_suffix} gu WHERE gu.group_id = ?)", group_id) @@ -144,6 +152,10 @@ write_attribute(:mail, arg.to_s.strip) end + def description=(arg) + write_attribute(:description, arg.to_s.strip) + end + def identity_url=(url) if url.blank? write_attribute(:identity_url, '')
--- a/app/views/account/register.html.erb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/views/account/register.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -1,5 +1,10 @@ +<%= javascript_include_tag "ssamr_institutions" %> +<%= javascript_include_tag "ssamr_registration" %> + <h2><%=l(:label_register)%> <%=link_to l(:label_login_with_open_id_option), signin_url if Setting.openid? %></h2> +<%= l(:text_who_can_register).html_safe %> + <%= labelled_form_for @user, :url => register_path do |f| %> <%= error_messages_for 'user' %> @@ -16,7 +21,30 @@ <p><%= f.text_field :firstname, :required => true %></p> <p><%= f.text_field :lastname, :required => true %></p> <p><%= f.text_field :mail, :required => true %></p> + +<!-- We only support English in this site <p><%= f.select :language, lang_options_for_select %></p> +--> + +<%= labelled_fields_for :ssamr_user_details, @ssamr_user_details do |fields| %> +<h3><%=l(:label_ssamr_details)%></h3> + +<p> + <%= fields.text_area :description, :rows => 3, :cols => 40, :required => true, :class => 'wiki-edit' %> + <em> <%=l(:text_user_ssamr_description_info).html_safe%></em> +</p> + +<p><label for="institution"><%=l("field_ssamr_user_detail.institution")%> <span class="required">*</span></label> + <%= fields.radio_button :institution_type, true, :checked => true %> + <%= fields.collection_select(:institution_id, Institution.find(:all, :order => "institutions.order"), :id, :name, {:selected => @selected_institution_id, :prompt => true}).gsub('&', '&').html_safe %> +</p> + +<p> + <%= fields.radio_button :institution_type, false %> Other: + <%= fields.text_field(:other_institution) %> +</p> +<% end %> + <% if Setting.openid? %> <p><%= f.text_field :identity_url %></p> @@ -27,5 +55,10 @@ <% end %> </div> +<%= check_box :user, :terms_and_conditions %> <%= l(:accept_terms_and_conditions) %> <%= link_to("Terms and Conditions", "https://code.soundsoftware.ac.uk/projects/soundsoftware-site/wiki/TandCs", {:target => "_blank"}) %>. +<br /> +<br /> + <%= submit_tag l(:button_submit) %> <% end %> +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/views/activities/_busy.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,50 @@ +<% events = @events_by_day %> +<% if (events.nil?) + activity = Redmine::Activity::Fetcher.new(User.anonymous) + days = Setting.activity_days_default.to_i + events = activity.events(Date.today - days, Date.today + 1) + end +%> + +<% if events.empty? %> + +<% else %> + + <ul> + + <% + for project in busy_projects(events, 7) + %> + + <li class="busy"> + <span class="title"> + <% if !project.root? %> + <% project.ancestors.each do |p| %> + <%= h(p) %> » + <% end %> + <% end %> + <%= link_to_project project %> + </span> + <% if !project.is_public? %> + <span class="private"><%= l(:field_is_private) %></span> + <% end %> + <span class='managers'> + <% + u = project.users_by_role + if ! u.empty? %> + <%= + mgmt_roles = u.keys.select{ |r| r.allowed_to?(:edit_project) } + managers = mgmt_roles.map{ |r| u[r] }.flatten.sort.uniq + managers.map{ |m| m.name }.join(', ') + %><% + end + %> + </span> + + <%= render_project_short_description project %> + </li> + + <% end %> + </ul> + +<% end %>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/views/activities/_busy_institution.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,28 @@ +<% events = @events_by_day %> +<% if (events.nil?) + activity = Redmine::Activity::Fetcher.new(User.anonymous) + days = Setting.activity_days_default.to_i + events = activity.events(Date.today - days, Date.today + 1) + end +%> + +<% if events.empty? %> + +<% else %> + + <ul> + + <% + for institution in busy_institutions(events, 5) + %> + + <li class="busy"> + <span class="title"> + <%= link_to h(institution), { :controller => 'activities', :institution => institution } %> + </span> + </li> + + <% end %> + </ul> +<% end %> +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/views/activities/_recent.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,65 @@ +<% events = @events_by_day %> +<% max = 5 %> +<% if (events.nil?) + activity = Redmine::Activity::Fetcher.new(User.current, :project => @project) + + if @project + # Don't show news (duplicated with News box) or wiki edits (too + # tedious) in project front page + activity.scope = [ "changesets", "files", "issues", "documents" ] + end + + events = activity.events(Date.today - 28, Date.today + 1) + + if defined? user + events = events.select { |e| user.member_of? e.project } + end + + events = events.first(max) + + end +%> + +<div id="activity"> + +<% if @project.nil? %> + <%= content_tag('h3', l(:label_activity_my_recent)) %> + <div class="activity box"> +<% end %> + +<% if events.empty? %> + + <% if @project.nil? %> + <p><%= l(:label_activity_my_recent_none) %></p> + <% end %> + +<% else %> + + <% if !@project.nil? %> + <div class="activity box"> + <%= content_tag('h3', l(:label_activity_recent)) %> + <% end %> + + <dl> + <% events.sort {|x,y| y.event_datetime <=> x.event_datetime }.each do |e| -%> + <dt class="<%= User.current.logged? && e.respond_to?(:event_author) && User.current == e.event_author ? 'me' : nil %>"> + <%= avatar(e.event_author, :size => "24") if e.respond_to?(:event_author) %> + <span class="time"><%= format_time(e.event_datetime) %></span> + <%= content_tag('span', link_to_project(e.project), :class => 'project') if @project.nil? || @project != e.project %> + <% if e.respond_to?(:event_author) %> + <span class="author"><%= e.event_author %></span> + <% end %> + </dt> + <dd><%= link_to format_activity_title(e.event_title), e.event_url %> + <span class="description"><%= format_activity_description(e.event_description) %></span> + </dd> + <% end -%> + </dl> + + </div> + +<% end %> + +<% if events.empty? and @project.nil? %></div><% end %> + +</div>
--- a/app/views/activities/index.html.erb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/views/activities/index.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -1,4 +1,14 @@ -<h2><%= @author.nil? ? l(:label_activity) : l(:label_user_activity, link_to_user(@author)).html_safe %></h2> +<h2><%= + if @author.nil? + if @institution_name.blank? + l(:label_activity) + else + l(:label_institution_activity, h(@institution_name)) + end + else + l(:label_user_activity, link_to_user(@author)).html_safe + end + %></h2> <p class="subtitle"><%= l(:label_date_from_to, :start => format_date(@date_to - @days), :end => format_date(@date_to-1)) %></p> <div id="activity">
--- a/app/views/attachments/_links.html.erb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/views/attachments/_links.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -7,7 +7,7 @@ :id => attachment, :filename => attachment.filename %> <% end %> <%= h(" - #{attachment.description}") unless attachment.description.blank? %> - <span class="size">(<%= number_to_human_size attachment.filesize %>)</span> + <span class="size_and_count"><%= number_to_human_size attachment.filesize %><%= ", " + l(:label_x_downloads, :count => attachment.downloads) unless attachment.downloads == 0 %></span> <% if options[:deletable] %> <%= link_to image_tag('delete.png'), attachment_path(attachment), :data => {:confirm => l(:text_are_you_sure)},
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/views/attachments/toggle_active.js.erb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,1 @@ +$('#active-<%= @attachment.id %>').html('<%= link_to image_tag(@attachment.active? ? 'fav.png' : 'fav_off.png'), {:controller => 'attachments', :action => 'toggle_active', :project_id => @project.id, :id => @attachment}, :remote => true %>');
--- a/app/views/files/index.html.erb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/views/files/index.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -5,29 +5,51 @@ <h2><%=l(:label_attachment_plural)%></h2> <% delete_allowed = User.current.allowed_to?(:manage_files, @project) %> +<% active_change_allowed = delete_allowed %> <table class="list files"> <thead><tr> + <%= sort_header_tag('active', :caption => l(:field_active)) %> <%= sort_header_tag('filename', :caption => l(:field_filename)) %> <%= sort_header_tag('created_on', :caption => l(:label_date), :default_order => 'desc') %> <%= sort_header_tag('size', :caption => l(:field_filesize), :default_order => 'desc') %> - <%= sort_header_tag('downloads', :caption => l(:label_downloads_abbr), :default_order => 'desc') %> + <%= sort_header_tag('downloads', :caption => l(:field_downloads), :default_order => 'desc') %> <th>MD5</th> <th></th> </tr></thead> <tbody> +<% have_file = false %> <% @containers.each do |container| %> <% next if container.attachments.empty? -%> <% if container.is_a?(Version) -%> <tr> - <th colspan="6"> + <th colspan="7"> <%= link_to(h(container), {:controller => 'versions', :action => 'show', :id => container}, :class => "icon icon-package") %> </th> </tr> <% end -%> <% container.attachments.each do |file| %> - <tr class="file <%= cycle("odd", "even") %>"> - <td class="filename"><%= link_to_attachment file, :download => true, :title => file.description %></td> + <tr class="file <%= cycle("odd", "even") %> <%= "active" if file.active? %>"> + <td class="active"> + <% have_file = true %> + <% if active_change_allowed + active_id = "active-" + file.id.to_s -%> + <div id="<%= active_id %>"> + <%= link_to image_tag(file.active? ? 'fav.png' : 'fav_off.png'), + {:controller => 'attachments', :action => 'toggle_active', :project_id => @project.id, :id => file}, + :remote => true + %> + </div> + <% else -%> + <%= image_tag('fav.png') if file.active? %> + <% end -%> + </td> + <% if file.active? %> + <td class="filename active"><%= link_to_attachment file, :download => true %><br><span class="description"><%= h(file.description) %></span></td> + <% else %> + <td class="filename"><%= link_to_attachment file, :download => true, :title => file.description %> + <% end %> + </td> <td class="created_on"><%= format_time(file.created_on) %></td> <td class="filesize"><%= number_to_human_size(file.filesize) %></td> <td class="downloads"><%= file.downloads %></td> @@ -43,4 +65,6 @@ </tbody> </table> +<%= l(:text_files_active_change).html_safe if active_change_allowed and have_file %> + <% html_title(l(:label_attachment_plural)) -%>
--- a/app/views/layouts/base.html.erb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/views/layouts/base.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -3,11 +3,12 @@ <head> <meta charset="utf-8" /> <title><%=h html_title %></title> -<meta name="description" content="<%= Redmine::Info.app_name %>" /> -<meta name="keywords" content="issue,bug,tracker" /> +<meta name="description" content="A repository for software developed and published by audio and music researchers in the UK." /> +<meta name="keywords" content="audio,music,software,research,UK,sound,repository,code,redmine" /> <%= csrf_meta_tag %> <%= favicon %> <%= stylesheet_link_tag 'jquery/jquery-ui-1.9.2', 'application', :media => 'all' %> +<%= stylesheet_platform_font_tag %> <%= stylesheet_link_tag 'rtl', :media => 'all' if l(:direction) == 'rtl' %> <%= javascript_heads %> <%= heads_for_theme %> @@ -27,21 +28,42 @@ <%= render_menu :top_menu if User.current.logged? || !Setting.login_required? -%> </div> -<div id="header"> +<%= tag('div', {:id => 'header', :class => (display_main_menu?(@project) ? 'header-project' : 'header-general')}, true) %> <% if User.current.logged? || !Setting.login_required? %> - <div id="quick-search"> + <div id="project-search-jump"> + <div id="quick-search"> <%= form_tag({:controller => 'search', :action => 'index', :id => @project}, :method => :get ) do %> <%= hidden_field_tag(controller.default_search_scope, 1, :id => nil) if controller.default_search_scope %> <label for='q'> <%= link_to l(:label_search), {:controller => 'search', :action => 'index', :id => @project}, :accesskey => accesskey(:search) %>: </label> - <%= text_field_tag 'q', @question, :size => 20, :class => 'small', :accesskey => accesskey(:quick_search) %> - <% end %> + <%= text_field_tag 'q', @question, :size => 20, :class => 'small', :accesskey => accesskey(:quick_search) %> + <% end %> + </div> + + <div id="project-jump-box"> <%= render_project_jump_box %> + </div> </div> <% end %> - <h1><%= page_header_title %></h1> + <% unless page_header_title[1].empty? %> + <h3 id="project-ancestors-title"><%= page_header_title[1] %></h3> + <% end %> + + <h1 id="project-title" + <% unless page_header_title[1].empty? %> + style="margin-top: 0px; " + <% end %> + <% if !@project.nil? and @project.name.length > 35 %> + class="long-title" + <% end %> + ><% if display_main_menu?(@project) %> + <%= link_to_project(@project) %> + <% else %> + <%= page_header_title[0] %> + <% end %> + </h1> <% if display_main_menu?(@project) %> <div id="main-menu"> @@ -70,7 +92,7 @@ <div id="footer"> <div class="bgl"><div class="bgr"> - Powered by <%= link_to Redmine::Info.app_name, Redmine::Info.url %> © 2006-2013 Jean-Philippe Lang + <small>Operated by <a href="http://soundsoftware.ac.uk/">SoundSoftware.ac.uk</a> – <a href="mailto:info@soundsoftware.ac.uk">contact us</a><br>Powered by <%= link_to Redmine::Info.app_name, Redmine::Info.url %></small> </div></div> </div> </div>
--- a/app/views/mailer/account_activation_request.html.erb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/views/mailer/account_activation_request.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -1,2 +1,12 @@ <p><%= l(:mail_body_account_activation_request, h(@user.login)) %></p> -<p><%= link_to h(@url), @url %></p> +<p><%= link_to @url, @url %></p> + +<h3><%= l(:label_ssamr_details) %></h3> +<p> + <%=l(:field_name)%>: <%= h @user.name %><br /> + <%=l(:field_mail)%>: <%= h @user.mail %> +</p> + +<h4><%= l(:label_ssamr_description ) %></h4> +<%= textilizable @user.ssamr_user_detail['description'] %> +
--- a/app/views/mailer/account_activation_request.text.erb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/views/mailer/account_activation_request.text.erb Wed Jan 15 09:59:14 2014 +0000 @@ -1,2 +1,11 @@ <%= l(:mail_body_account_activation_request, @user.login) %> <%= @url %> + +*<%= l(:label_ssamr_details) %>* +<%=l(:field_name)%>: <%= h @user.name %> +<%=l(:field_mail)%>: <%= h @user.mail %> + +*<%= l(:label_ssamr_description ) %>* +<%= h @user.ssamr_user_detail['description'] %> + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/views/mailer/member_added_to_project.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,3 @@ +<p><%= l(:notice_added_to_project, :project_name => @project_name) %></p> +<p><%= l(:notice_project_homepage, :project_url => @project_url) %></p> +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/views/mailer/member_added_to_project.text.erb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,2 @@ +<%= l(:notice_added_to_project, :project_name => @project_name) %> +<%= l(:notice_project_homepage, :project_url => @project_url) %>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/views/members/_editlist.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,73 @@ +<%= error_messages_for 'member' %> + +<% roles = Role.find_all_givable + members = @project.member_principals.find(:all, :include => [:roles, :principal]).sort %> + +<% if members.any? %> +<table class="list members"> + <thead><tr> + <th><%= l(:label_user) %></th> + <th><%= l(:label_role_plural) %></th> + <th style="width:15%"></th> + </tr></thead> + <tbody> + <% members.each do |member| %> + <% next if member.new_record? %> + <tr id="member-<%= member.id %>" class="<%= cycle 'odd', 'even' %> member"> + <td class="<%= member.principal.class.name.downcase %>"><%= link_to_user member.principal %></td> + <td class="roles"> + <span id="member-<%= member.id %>-roles"><%=h member.roles.sort.collect(&:to_s).join(', ') %></span> + <%= form_for(member, {:as => :membership, :remote => true, :url => membership_path(member), + :method => :put, + :html => { :id => "member-#{member.id}-roles-form", :class => 'hol' }} + ) do |f| %> + <p><% roles.each do |role| %> + <label><%= check_box_tag 'membership[role_ids][]', role.id, member.roles.include?(role), :disabled => member.member_roles.detect {|mr| mr.role_id == role.id && !mr.inherited_from.nil?} %> <%=h role %></label><br /> + <% end %></p> + <%= hidden_field_tag 'membership[role_ids][]', '' %> + <p><%= submit_tag l(:button_change), :class => "small" %> + <%= link_to_function l(:button_cancel), + "$('#member-#{member.id}-roles').show(); $('#member-#{member.id}-roles-form').hide(); return false;" + %></p> + <% end %> + </td> + <td class="buttons"> + <%= link_to_function l(:button_edit), + "$('#member-#{member.id}-roles').hide(); $('#member-#{member.id}-roles-form').show(); return false;", + :class => 'icon icon-edit' %> + <%= delete_link membership_path(member), + :data => (!User.current.admin? && member.include?(User.current) ? {:confirm => l(:text_own_membership_delete_confirmation)} : {}) if member.deletable? %> + </td> + </tr> +<% end; reset_cycle %> + </tbody> +</table> +<% else %> +<p class="nodata"><%= l(:label_no_data) %></p> +<% end %> + +<% content_for :sidebar do %> +<% principals = Principal.active.find(:all, :limit => 100, :order => 'type, login, lastname ASC') - @project.principals %> + +<% if roles.any? && principals.any? %> + <%= form_for(@member, {:as => :membership, :url => project_memberships_path(@project), :method => :post}) do |f| %> + <h3><%=l(:label_member_new)%></h3> + + <p><%= label_tag "principal_search", l(:label_principal_search) %><%= text_field_tag 'principal_search', nil %></p> + <%= javascript_tag "observeSearchfield('principal_search', 'principals', '#{ escape_javascript autocomplete_project_memberships_path(@project) }')" %> + + <div id="principals"> + <% if params[:q] && params[:q].length > 1 %> + <%= principals_check_box_tags 'membership[user_ids][]', @principals %> + <% end %> + </div> + + <p><%= l(:label_set_role_plural) %>:</p> + <% roles.each do |role| %> + <label><%= check_box_tag 'membership[role_ids][]', role.id %> <%=h role %> </label><div style="margin-left: 2em; margin-bottom: 0.5em"><i><%=l( 'label_' + role.name.downcase + "_description").to_sym %></i></div> + <% end %> + + <p><%= submit_tag l(:button_add), :id => 'member-add-submit' %></p> + <% end %> +<% end %> +<% end %>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/views/members/_list.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,26 @@ + +<% roles = Role.find_all_givable + members = @project.member_principals.find(:all, :include => [:roles, :principal]).sort %> + +<% if members.any? %> + +<div id="members"> +<dl> +<% members.each do |member| %> +<dt id="member-<%= member.id %>" class="member <%= member.principal.class.name.downcase %> <%= User.current.logged? && User.current == member.user ? 'me' : nil %>"> +<%= avatar(member.user, :size => "24") %> +<span class="user"><%= link_to_user member.principal %></span> +<span class="email"><%= member.user.mail if !member.user.pref.hide_mail %></span> +<dd id="member-<%= member.id %>" class="roles <%= member.principal.class.name.downcase %> <%= User.current.logged? && User.current == member.user ? 'me' : nil %>"> +<span class="roles" id="member-<%= member.id %>-roles"><%=h member.roles.sort.collect(&:to_s).join(', ') %></span> +</dd> +<% end %> +</dl> +<% else %> +<p class="nodata"><%= l(:label_no_data) %></p> +<% end %> +</div> + +<% content_for :sidebar do %> +<% end %> +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/views/members/index.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,13 @@ + +<h2><%=l(:label_member_plural)%></h2> + +<% editable = authorize_for('members', 'update') %> + +<div id="memberlist"> +<% if editable %> +<%= render :partial => 'editlist' %> +<% else %> +<%= render :partial => 'list' %> +<% end %> +</div> +
--- a/app/views/my/account.html.erb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/views/my/account.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -1,3 +1,5 @@ +<%= javascript_include_tag "ssamr_institutions" %> + <div class="contextual"> <%= link_to(l(:button_change_password), {:action => 'password'}, :class => 'icon icon-passwd') if @user.change_password_allowed? %> <%= call_hook(:view_my_account_contextual, :user => @user)%> @@ -24,9 +26,33 @@ <% @user.custom_field_values.select(&:editable?).each do |value| %> <p><%= custom_field_tag_with_label :user, value %></p> <% end %> + <%= call_hook(:view_my_account, :user => @user, :form => f) %> </fieldset> +<%= labelled_fields_for :ssamr_user_details, @user.ssamr_user_detail do |fields| %> +<h3><%=l(:label_ssamr_details)%></h3> +<div class="box tabular"> + <p> + <%= fields.text_area :description, :rows => 3, :cols => 25, :required => true, :class => 'wiki-edit' %> + </p> + + <p><label for="institution"><%=l("field_ssamr_user_detail.institution")%> <span class="required">*</span></label> + <nobr> + <%= fields.radio_button :institution_type, true %> + <%= fields.collection_select(:institution_id, Institution.find(:all, :order => "institutions.order"), :id, :name, {:selected => @selected_institution_id, :prompt => true} ).gsub('&', '&').html_safe %> + </nobr> + </p> + + <p> + <nobr> + <%= fields.radio_button :institution_type, false %> Other: + <%= fields.text_field :other_institution, :size => 19 %> + </nobr> + </p> +</div> +<% end %> + <%= submit_tag l(:button_save) %> </div>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/views/my/blocks/_activitymyprojects.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,4 @@ + +<%= render :partial => 'activities/recent', :locals => { :user => User.current } %> + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/views/my/blocks/_colleagues.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,13 @@ + +<% colleagues = all_colleagues_of(@user) %> + +<% if !colleagues.empty? %> +<div id="activity"> + <h3><%=l(:label_my_colleagues)%></h3> + <div class="activity box" id="active-colleagues"> + <%= render_active_colleagues(colleagues).html_safe %> + </div> +</div> +<% end %> + +
--- a/app/views/my/blocks/_issueswatched.html.erb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/views/my/blocks/_issueswatched.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -6,3 +6,4 @@ <% watched_issues = issueswatched_items %> <%= render :partial => 'issues/list_simple', :locals => { :issues => watched_issues } %> +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/views/my/blocks/_myprojects.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,16 @@ +<% @user_projects = User.current.projects.all(:order => :name) %> + +<% if @user_projects.empty? %> +<h3><%=l(:label_my_projects)%></h3> +<div class="box"> + <div class="tip"><%= l(:label_have_no_projects)%> <%= link_to(l(:label_project_new), {:controller => 'projects', :action => 'new'}) if User.current.allowed_to?(:add_project, nil, :global => true) %></div> +</div> +<% else %> +<h3><%=l(:label_my_projects)%> (<%= @user_projects.count %>)</h3> +<div class="box"> +<%= + render :partial => 'projects/my', :locals => { :user => User.current } + %> +</div> +<% end %> +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/views/my/blocks/_tipoftheday.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,4 @@ + <h3><%=l(:label_tipoftheday)%></h3> + <div class="tipoftheday box"> + <div class="tip"><%= textilizable Setting.tipoftheday_text %></div> + </div>
--- a/app/views/my/page.html.erb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/views/my/page.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -1,5 +1,10 @@ +<% content_for :header_tags do %> + <%= stylesheet_link_tag 'redmine_tags', :plugin => 'redmine_tags' %> +<% end %> + <div class="contextual"> <%= link_to l(:label_personalize_page), :action => 'page_layout' %> + <%= ('| ' + link_to(l(:label_project_new), {:controller => 'projects', :action => 'new'}, :class => 'icon icon-add')).html_safe if User.current.allowed_to?(:add_project, nil, :global => true) %> </div> <h2><%=l(:label_my_page)%></h2>
--- a/app/views/news/_news.html.erb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/views/news/_news.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -1,6 +1,13 @@ -<p><%= link_to_project(news.project) + ': ' unless @project %> -<%= link_to h(news.title), news_path(news) %> -<%= "(#{l(:label_x_comments, :count => news.comments_count)})" if news.comments_count > 0 %> -<br /> +<div id="news"> +<dt> +<span class="time"><%= format_time(news.created_on) %></span> +<% project ||= @project %> +<% if !project %> +<span class="project"><%= link_to_project(news.project) %></span> +<% end %> +<span class="headline"><%= link_to h(news.title), news_path(news) %></span> +<span class="comments"><%= "(#{l(:label_x_comments, :count => news.comments_count)})" if news.comments_count > 0 %></span> +</dt><dd> <% unless news.summary.blank? %><span class="summary"><%=h news.summary %></span><br /><% end %> -<span class="author"><%= authoring news.created_on, news.author %></span></p> +</dd> +</div>
--- a/app/views/projects/_form.html.erb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/views/projects/_form.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -2,15 +2,37 @@ <div class="box tabular"> <!--[form:project]--> -<p><%= f.text_field :name, :required => true, :size => 60 %></p> +<p><%= f.text_field :name, :required => true, :size => 60 %> + <br /> + <em class="info"> <%= l(:text_project_name_info).html_safe %></em> +</p> <p><%= f.text_area :description, :rows => 8, :class => 'wiki-edit' %></p> <p><%= f.text_field :identifier, :required => true, :size => 60, :disabled => @project.identifier_frozen?, :maxlength => Project::IDENTIFIER_MAX_LENGTH %> <% unless @project.identifier_frozen? %> + <br /> <em class="info"><%= l(:text_length_between, :min => 1, :max => Project::IDENTIFIER_MAX_LENGTH) %> <%= l(:text_project_identifier_info).html_safe %></em> <% end %></p> -<p><%= f.text_field :homepage, :size => 60 %></p> -<p><%= f.check_box :is_public %></p> + +<p><%= f.text_field :homepage, :size => 60 %> +<br /> + <em class="info"> <%= l(:text_project_homepage_info) %></em> +</p> +<p> +<label for="project_is_public_1"><%= l(:field_public_or_private)%> <span class="required">*</span></label> +<% + # if the project hasn't been created fully yet, then we don't + # want to set either public or private (make the user decide) + initialised = !@project.id.nil? +%> + <%= f.radio_button :is_public, 1, :checked => (initialised && @project.is_public?) %> + <%= l(:text_project_public_info) %> +<br /> + <%= f.radio_button :is_public, 0, :checked => (initialised && !@project.is_public?) %> + <%= l(:text_project_private_info) %> +<br> + <em class="info"><%= l(:text_project_visibility_info) %></em> +</p> <% unless @project.allowed_parents.compact.empty? %> <p><%= label(:project, :parent_id, l(:field_parent)) %><%= parent_project_select_tag(@project) %></p> @@ -40,7 +62,12 @@ </fieldset> <% end %> -<% if @project.new_record? || @project.module_enabled?('issue_tracking') %> +<% if @project.new_record? %> +<% @trackers.each do |tracker| %> + <%= hidden_field_tag 'project[tracker_ids][]', tracker.id %> +<% end %> +<%= hidden_field_tag 'project[tracker_ids][]', '' %> +<% elsif @project.module_enabled?('issue_tracking') %> <% unless @trackers.empty? %> <fieldset class="box tabular" id="project_trackers"><legend><%=l(:label_tracker_plural)%></legend> <% @trackers.each do |tracker| %>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/views/projects/_latest.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,19 @@ + <ul> + <% for project in Project.latest(User.current) %> + <li class="latest"> + <span class="title"> + <% if !project.root? %> + <% project.ancestors.each do |p| %> + <%= h(p) %> » + <% end %> + <% end %> + <%= link_to_project project %> + </span> + <% if !project.is_public? %> + <span class="private"><%= l(:field_is_private) %></span> + <% end %> + <span class="time"><%= format_time(project.created_on)%></span> + <%= render_project_short_description project %> + </li> + <% end %> + </ul>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/views/projects/_mature.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,38 @@ + + <ul> + + <% + for project in mature_projects(5) + %> + + <li class="busy"> + <span class="title"> + <% if !project.root? %> + <% project.ancestors.each do |p| %> + <%= h(p) %> » + <% end %> + <% end %> + <%= link_to_project project %> + </span> + <% if !project.is_public? %> + <span class="private"><%= l(:field_is_private) %></span> + <% end %> + <span class='managers'> + <% + u = project.users_by_role + if ! u.empty? %> + <%= + mgmt_roles = u.keys.select{ |r| r.allowed_to?(:edit_project) } + managers = mgmt_roles.map{ |r| u[r] }.flatten.sort.uniq + managers.map{ |m| m.name }.join(', ') + %><% + end + %> + </span> + + <%= render_project_short_description project %> + </li> + + <% end %> + </ul> +
--- a/app/views/projects/_members_box.html.erb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/views/projects/_members_box.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -1,8 +1,8 @@ <% if @users_by_role.any? %> - <div class="members box"> + <div id="memberbox"><div class="box"> <h3><%=l(:label_member_plural)%></h3> <p><% @users_by_role.keys.sort.each do |role| %> <%=h role %>: <%= @users_by_role[role].sort.collect{|u| link_to_user u}.join(", ").html_safe %><br /> <% end %></p> - </div> + </div></div> <% end %>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/views/projects/_my.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,1 @@ +<%= render_my_project_hierarchy(@user_projects) %>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/views/projects/explore.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,50 @@ +<% content_for :header_tags do %> + <%= stylesheet_link_tag 'redmine_tags', :plugin => 'redmine_tags' %> +<% end %> + +<div class="contextual"> + <%= link_to l(:label_project_all), { :controller => 'projects', :action => 'index' }%> + <%= ('| ' + link_to(l(:label_search_projects), { :controller => 'search', :action => 'index', :projects => 1 })).html_safe %> + <%= ('| ' + link_to(l(:label_project_new), {:controller => 'projects', :action => 'new'}, :class => 'icon icon-add')).html_safe if User.current.allowed_to?(:add_project, nil, :global => true) %> +</div> + +<h2><%= l(:label_explore_projects) %></h2> + +<div class="threecolumnleft"> +<% cache(:action => 'explore', :action_suffix => 'tags') do %> + <div class="tags box"> + <h3><%=l(:label_project_tags_all)%></h3> + <%= render :partial => 'projects/tagcloud' %> + </div> +<% end %> +</div> + +<div class="threecolumnright"> + <div class="projects box"> + <h3><%=l(:label_project_latest)%></h3> + <%= render :partial => 'projects/latest' %> + <%= link_to l(:label_projects_more), { :controller => 'projects' }, :class => 'more' %> + </div> +</div> + +<div class="threecolumnleft"> + <% cache(:action => 'explore', :action_suffix => 'busy_institutions') do %> + <div class="institutions box"> + <h3><%=l(:label_institutions_busy)%></h3> + <%= render :partial => 'activities/busy_institution' %> + <%= link_to l(:label_overall_activity), { :controller => 'activities', :action => 'index' }, :class => 'more' %> + </div> + <% end %> +</div> + +<div class="threecolumnmid"> + <% cache(:action => 'explore', :action_suffix => 'busy_projects') do %> + <div class="projects box"> + <h3><%=l(:label_projects_busy)%></h3> + <%= render :partial => 'activities/busy' %> + <%= link_to l(:label_overall_activity), { :controller => 'activities', :action => 'index' }, :class => 'more' %> + </div> + <% end %> +</div> + +<% html_title(l(:label_explore_projects)) -%>
--- a/app/views/projects/index.html.erb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/views/projects/index.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -3,25 +3,18 @@ <% end %> <div class="contextual"> - <%= link_to(l(:label_project_new), {:controller => 'projects', :action => 'new'}, :class => 'icon icon-add') + ' |' if User.current.allowed_to?(:add_project, nil, :global => true) %> - <%= link_to(l(:label_issue_view_all), issues_path) + ' |' if User.current.allowed_to?(:view_issues, nil, :global => true) %> - <%= link_to(l(:label_overall_spent_time), time_entries_path) + ' |' if User.current.allowed_to?(:view_time_entries, nil, :global => true) %> - <%= link_to l(:label_overall_activity), - { :controller => 'activities', :action => 'index', - :id => nil } %> + <%= link_to l(:label_overall_activity), { :controller => 'activities', :action => 'index' }%> + <%= ('| ' + link_to(l(:label_project_new), {:controller => 'projects', :action => 'new'}, :class => 'icon icon-add')).html_safe if User.current.allowed_to?(:add_project, nil, :global => true) %> </div> -<h2><%= l(:label_project_plural) %></h2> +<h2> +<%= l("label_project_all") %> +</h2> -<div id="projects-index"> -<%= render_project_hierarchy(@projects) %> -</div> +<%= render_project_table(@projects) %> -<% if User.current.logged? %> -<p style="text-align:right;"> -<span class="my-project"><%= l(:label_my_projects) %></span> -</p> -<% end %> +<p class="pagination"><%= pagination_links_full @project_pages, @project_count %></p> + <% other_formats_links do |f| %> <%= f.link_to 'Atom', :url => {:key => User.current.rss_key} %>
--- a/app/views/projects/new.html.erb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/views/projects/new.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -3,6 +3,5 @@ <%= labelled_form_for @project do |f| %> <%= render :partial => 'form', :locals => { :f => f } %> <%= submit_tag l(:button_create) %> -<%= submit_tag l(:button_create_and_continue), :name => 'continue' %> <%= javascript_tag "$('#project_name').focus();" %> <% end %>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/views/projects/settings/_overview.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,30 @@ + +<%= form_for @project, + :url => { :action => 'overview', :id => @project.id }, + :html => {:id => 'overview-form'} do |f| %> + +<div class="box tabular"> + +<p><%= l(:text_has_welcome_page_info, { :overview_link => link_to(l(:label_overview), { :controller => 'projects', :action => 'show' } ) } ).html_safe %></p> + +<% if @project.module_enabled? :wiki %> + +<p><%= link_to(l(:button_welcome_page_edit), {:controller => 'wiki', :action => 'edit', :project_id => @project, :id => Wiki.titleize("Overview")}, :class => 'icon icon-edit') %> + +<% else %> + +<p><%= l(:text_has_welcome_page_wiki_disabled, { :modules_link => link_to(l(:label_module_plural), { :controller => 'projects', :action => 'settings', :tab => 'modules' } ) } ).html_safe %></p> + +<% end %> + +<p><label for="has_welcome_page"><%= l(:label_has_welcome_page) %></label> +<%= check_box_tag 'has_welcome_page', 1, @project.has_welcome_page? -%> +<br/><em><%= l(:setting_has_welcome_page) %></em> + +</p> + +</div> + +<%= submit_tag l(:button_save) %> + +<% end %>
--- a/app/views/projects/settings/_repositories.html.erb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/views/projects/settings/_repositories.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -1,41 +1,61 @@ -<% if @project.repositories.any? %> -<table class="list"> - <thead> - <tr> - <th><%= l(:field_identifier) %></th> - <th><%= l(:field_repository_is_default) %></th> - <th><%= l(:label_scm) %></th> - <th><%= l(:label_repository) %></th> - <th></th> - </tr> - </thead> - <tbody> - <% @project.repositories.sort.each do |repository| %> - <tr class="<%= cycle 'odd', 'even' %>"> - <td class="name"> - <%= link_to repository.identifier, - {:controller => 'repositories', :action => 'show',:id => @project, :repository_id => repository.identifier_param} if repository.identifier.present? %> - </td> - <td><%= checked_image repository.is_default? %></td> - <td><%=h repository.scm_name %></td> - <td><%=h repository.url %></td> - <td class="buttons"> - <% if User.current.allowed_to?(:manage_repository, @project) %> - <%= link_to(l(:label_user_plural), committers_repository_path(repository), + +<%= javascript_include_tag 'repository' %> + +<% if @repository %> + +<%= form_for @repository, + :as => :repository, + :remote => true, + :url => { :controller => 'repositories', :action => 'update', :id => @repository }, + :method => 'put' do |f| %> + +<%= error_messages_for 'repository' %> + +<div class="box tabular"> + +<p> + <%= l(:text_settings_repo_explanation).html_safe %></ br> + <% if @repository.is_external %> + <p><%= l(:text_settings_repo_is_external).html_safe %></ br> + <% else %> + <p><%= l(:text_settings_repo_is_internal).html_safe %></ br> + <% end %> +</p> + + +<p> + <%= label_tag('repository_is_external', l(:label_is_external_repository)) %> + <%= check_box :repository, :is_external, :onclick => "toggle_ext_url()" %> + <br/><em><%= l(:setting_external_repository).html_safe %></em> +</p> + +<p> + <%= label_tag('repository_external_url', l(:label_repository_external_url)) %> + <%= text_field :repository, :external_url, :disabled => !(@repository and @repository.is_external) %> + <br/><em><%= l(:setting_external_repository_url).html_safe %></em> +</p> + +<p><%= l(:text_settings_repo_need_help).html_safe %></p> + +</div> + +<div class="contextual"> +<% if @repository && !@repository.new_record? %> +<%= link_to(l(:label_user_plural), + { + :controller => 'repositories', + :action => 'committers', + :id => @repository + }, :class => 'icon icon-user') %> - <%= link_to(l(:button_edit), edit_repository_path(repository), - :class => 'icon icon-edit') %> - <%= delete_link repository_path(repository) %> - <% end %> - </td> - </tr> - <% end %> - </tbody> -</table> -<% else %> -<p class="nodata"><%= l(:label_no_data) %></p> +<% end %> +</div> + +<%= submit_tag(l(:button_save)) %> + <% end %> -<% if User.current.allowed_to?(:manage_repository, @project) %> - <p><%= link_to l(:label_repository_new), new_project_repository_path(@project), :class => 'icon icon-add' %></p> +<% else %> + <%= l(:text_settings_repo_creation).html_safe %></ br> <% end %> +
--- a/app/views/projects/show.html.erb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/views/projects/show.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -11,6 +11,34 @@ <% end %> </div> +<% if @project.has_welcome_page %> +<% page = @project.wiki.find_page("Overview") %> +<% end %> + +<% if page %> + +<% if @project.module_enabled? :wiki %> +<% if User.current.allowed_to?(:edit_wiki_pages, @project) %> +<div class="contextual"> +<%= link_to(l(:button_welcome_page_edit_this), {:controller => 'wiki', :action => 'edit', :project_id => @project, :id => Wiki.titleize("Overview")}, :class => 'icon icon-edit') %> +</div> +<% end %> +<% end %> + +<div class="contextual" style="clear: right"> +<ul> +<% unless @project.homepage.blank? %><li><%=l(:field_homepage)%>: <%= link_to h(@project.homepage), @project.homepage %></li><% end %> +<% if @subprojects.any? %> + <li><%=l(:label_subproject_plural)%>: + <%= @subprojects.collect{|p| link_to(h(p), :action => 'show', :id => p)}.join(", ").html_safe %></li> +<% end %> +</ul> +</div> + +<%= render(:partial => "wiki/content", :locals => {:content => page.content_for_version()}) %> + +<% else %> + <h2><%=l(:label_overview)%></h2> <% unless @project.active? %> @@ -38,7 +66,8 @@ <% end %> </ul> - <% if User.current.allowed_to?(:view_issues, @project) %> + <% if User.current.allowed_to?(:view_issues, @project) and @open_issues_by_tracker.values.any? %> + <div class="issues box"> <h3><%=l(:label_issue_tracking)%></h3> <ul> @@ -59,7 +88,9 @@ <% end %> </p> </div> + <% end %> + <%= call_hook(:view_projects_show_left, :project => @project) %> </div> @@ -73,10 +104,14 @@ <p><%= link_to l(:label_news_view_all), project_news_index_path(@project) %></p> </div> <% end %> + + <%= render :partial => 'activities/recent' %> + <%= call_hook(:view_projects_show_right, :project => @project) %> </div> <% content_for :sidebar do %> + <%= call_hook(:view_projects_show_sidebar_top, :project => @project) %> <% if @total_hours.present? %> <h3><%= l(:label_spent_time) %></h3> <p><span class="icon icon-time"><%= l_hours(@total_hours) %></span></p> @@ -90,6 +125,8 @@ <%= call_hook(:view_projects_show_sidebar_bottom, :project => @project) %> <% end %> +<% end %> + <% content_for :header_tags do %> <%= auto_discovery_link_tag(:atom, {:controller => 'activities', :action => 'index', :id => @project, :format => 'atom', :key => User.current.rss_key}) %> <% end %>
--- a/app/views/repositories/_dir_list_content.html.erb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/views/repositories/_dir_list_content.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -17,7 +17,7 @@ :parent_id => tr_id)) %>');"> </span> <% end %> <%= link_to h(ent_name), - {:action => (entry.is_dir? ? 'show' : 'changes'), :id => @project, :repository_id => @repository.identifier_param, :path => to_path_param(ent_path), :rev => @rev}, + {:action => (entry.is_dir? ? 'show' : 'entry'), :id => @project, :repository_id => @repository.identifier_param, :path => to_path_param(ent_path), :rev => @rev}, :class => (entry.is_dir? ? 'icon icon-folder' : "icon icon-file #{Redmine::MimeType.css_class_of(ent_name)}")%> </td> <td class="size"><%= (entry.size ? number_to_human_size(entry.size) : "?") unless entry.is_dir? %></td>
--- a/app/views/repositories/_navigation.html.erb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/views/repositories/_navigation.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -6,6 +6,8 @@ {:action => 'stats', :id => @project, :repository_id => @repository.identifier_param}, :class => 'icon icon-stats' if @repository.supports_all_revisions? %> +<%= link_to_revision_archive(@repository, @rev, @project, { :text => l(:label_download_revision), :class => 'icon icon-package' }) %> + <%= form_tag({:action => controller.action_name, :id => @project, :repository_id => @repository.identifier_param, @@ -32,3 +34,5 @@ <%= text_field_tag 'rev', @rev, :size => 8 %> <% end %> <% end -%> + +
--- a/app/views/repositories/entry.html.erb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/views/repositories/entry.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -8,7 +8,15 @@ <%= render :partial => 'link_to_functions' %> -<%= render :partial => 'common/file', :locals => {:filename => @path, :content => @content} %> +<% if @display_raw %> + <% if @content.size && @content.size > Setting.file_max_size_displayed.to_i.kilobyte %> + <em><%= l(:text_data_too_large) %></em> + <% else %> + <em><%= l(:text_binary_data) %></em> + <% end %> +<% else %> + <%= render :partial => 'common/file', :locals => {:filename => @path, :content => @content} %> +<% end %> <% content_for :header_tags do %> <%= stylesheet_link_tag "scm" %>
--- a/app/views/repositories/revision.html.erb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/views/repositories/revision.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -1,11 +1,15 @@ <div class="contextual"> + + <%= link_to_revision_archive(@repository, @changeset, @project, { :text => l(:label_download_revision), :class => 'icon icon-package' }) %> + + « <% unless @changeset.previous.nil? -%> <%= link_to_revision(@changeset.previous, @repository, :text => l(:label_previous)) %> <% else -%> <%= l(:label_previous) %> <% end -%> -| + <% unless @changeset.next.nil? -%> <%= link_to_revision(@changeset.next, @repository, :text => l(:label_next)) %> <% else -%> @@ -22,6 +26,7 @@ <%= text_field_tag 'rev', @rev, :size => 8 %> <%= submit_tag 'OK', :name => nil %> <% end %> + </div> <h2><%= avatar(@changeset.user, :size => "24") %><%= l(:label_revision) %> <%= format_revision(@changeset) %></h2>
--- a/app/views/search/index.html.erb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/views/search/index.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -22,12 +22,22 @@ <% end %> </div> +<% if @project_matches and !@project_matches.empty? and !@pagination_previous_date %> + <h3><%= l(:label_matching_project_plural) %> (<%= @project_matches.count %>)</h3> + <dl id="search-results"> + <% @project_matches.each do |e| %> + <dt class="<%= e.event_type %>"><%= content_tag('span', h(e.project), :class => 'project') unless @project == e.project %> <%= link_to highlight_tokens(truncate(e.event_title, :length => 255), @tokens), e.event_url %></dt> + <dd><span class="description"><%= e.short_description %></span> + <% end %> + </dl> +<% end %> + <% if @results %> + + <h3><%= l(:label_result_plural) %> (<%= @results_by_type.values.sum %>)</h3> <div id="search-results-counts"> <%= render_results_by_type(@results_by_type) unless @scope.size == 1 %> </div> - - <h3><%= l(:label_result_plural) %> (<%= @results_by_type.values.sum %>)</h3> <dl id="search-results"> <% @results.each do |e| %> <dt class="<%= e.event_type %>"><%= content_tag('span', h(e.project), :class => 'project') unless @project == e.project %> <%= link_to highlight_tokens(truncate(e.event_title, :length => 255), @tokens), e.event_url %></dt>
--- a/app/views/settings/_general.html.erb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/views/settings/_general.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -3,9 +3,15 @@ <div class="box tabular settings"> <p><%= setting_text_field :app_title, :size => 30 %></p> +<p><%= setting_text_area :notifications_text, :cols => 60, :rows => 5, :class => 'wiki-edit' %></p> + <%= wikitoolbar_for 'settings_notifications_text' %> + <p><%= setting_text_area :welcome_text, :cols => 60, :rows => 5, :class => 'wiki-edit' %></p> <%= wikitoolbar_for 'settings_welcome_text' %> +<p><%= setting_text_area :tipoftheday_text, :cols => 60, :rows => 5, :class => 'wiki-edit' %></p> +<%= wikitoolbar_for 'settings_tipoftheday_text' %> + <p><%= setting_text_field :attachment_max_size, :size => 6 %> <%= l(:"number.human.storage_units.units.kb") %></p> <p><%= setting_text_field :per_page_options, :size => 20 %>
--- a/app/views/users/_form.html.erb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/views/users/_form.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -1,3 +1,7 @@ +<% content_for :header_tags do %> + <%= javascript_include_tag "ssamr_institutions" %> +<% end %> + <%= error_messages_for 'user' %> <div id="user_form"> @@ -22,6 +26,33 @@ <%= call_hook(:view_users_form, :user => @user, :form => f) %> </fieldset> +<div class="box tabular"> + <h3><%=l(:label_ssamr_details)%></h3> + <%= labelled_fields_for :ssamr_user_detail, @user.ssamr_user_detail do |ssamr_user_detail| %> + <p> + <%= ssamr_user_detail.text_area :description, :rows => 3, :cols => 40, :required => true, :class => 'wiki-edit' %> + </p> + + <p><label for="institution"><%=l("field_ssamr_user_detail.institution")%> <span class="required">*</span></label> + <nobr> + <%= ssamr_user_detail.radio_button :institution_type, true %> + <%= ssamr_user_detail.collection_select(:institution_id, Institution.find(:all, :order => "institutions.order"), :id, :name, {:selected => @selected_institution_id, :prompt => true} ).gsub('&', '&').html_safe %> + </nobr> + </p> + + + + <p> + <nobr> + <%= ssamr_user_detail.radio_button :institution_type, false %> Other: + <%= ssamr_user_detail.text_field :other_institution %> + </nobr> + </p> + <% end %> +</div> + + + <fieldset class="box tabular"> <legend><%=l(:label_authentication)%></legend> <% unless @auth_sources.empty? %>
--- a/app/views/users/show.html.erb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/views/users/show.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -20,6 +20,13 @@ <% end %> </ul> +<h3><%=l(:label_ssamr_description)%></h3> +<%= textilizable @description %> + +<h3><%=l(:label_ssamr_institution)%></h3> +<p><%= h @institution_name %></p> + + <% unless @memberships.empty? %> <h3><%=l(:label_project_plural)%></h3> <ul>
--- a/app/views/welcome/index.html.erb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/views/welcome/index.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -1,39 +1,28 @@ -<h2><%= l(:label_home) %></h2> -<div class="splitcontentleft"> - <%= textilizable Setting.welcome_text %> - <% if @news.any? %> - <div class="news box"> - <h3><%=l(:label_news_latest)%></h3> - <%= render :partial => 'news/news', :collection => @news %> - <%= link_to l(:label_news_view_all), :controller => 'news' %> +<% if not Setting.notifications_text.empty? %> + <div class="notifications flash newsalert"> + <%= textilizable Setting.notifications_text %> </div> +<% end %> + +<div id="welcomepagenews"> + <% if @site_news.any? %> + <div class="news box"> + <h3><%=l(:label_news_site_latest)%></h3> + <%= render :partial => 'news/news', :locals => { :project => @site_project }, :collection => @site_news %> + <%= link_to l(:label_news_more), { :controller => 'news', :project_id => @site_project.identifier, :action => 'index' } %> + </div> <% end %> - <%= call_hook(:view_welcome_index_left, :projects => @projects) %> </div> -<div class="splitcontentright"> - <% if @projects.any? %> - <div class="projects box"> - <h3><%=l(:label_project_latest)%></h3> - <ul> - <% for project in @projects %> - <% @project = project %> - <li> - <%= link_to_project project %> (<%= format_time(project.created_on) %>) - <%= textilizable project.short_description, :project => project %> - </li> - <% end %> - <% @project = nil %> - </ul> - </div> - <% end %> - <%= call_hook(:view_welcome_index_right, :projects => @projects) %> +<div id="welcomepage"> +<%= textilizable Setting.welcome_text %> </div> - + <% content_for :header_tags do %> <%= auto_discovery_link_tag(:atom, {:controller => 'news', :action => 'index', :key => User.current.rss_key, :format => 'atom'}, :title => "#{Setting.app_title}: #{l(:label_news_latest)}") %> <%= auto_discovery_link_tag(:atom, {:controller => 'activities', :action => 'index', :key => User.current.rss_key, :format => 'atom'}, :title => "#{Setting.app_title}: #{l(:label_activity)}") %> <% end %> +
--- a/config/application.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/config/application.rb Wed Jan 15 09:59:14 2014 +0000 @@ -29,6 +29,10 @@ # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. # config.time_zone = 'Central Time (US & Canada)' + # Avoid deprecation warning (may want to change this later if future + # Redmine updates are OK with it) + config.i18n.enforce_available_locales = false + # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] # config.i18n.default_locale = :de
--- a/config/locales/bg.yml Tue Jan 14 14:37:42 2014 +0000 +++ b/config/locales/bg.yml Wed Jan 15 09:59:14 2014 +0000 @@ -545,6 +545,7 @@ label_login_with_open_id_option: или вход чрез OpenID label_password_lost: Забравена парола label_home: Начало + label_home_heading: Начало label_my_page: Лична страница label_my_account: Профил label_my_projects: Проекти, в които участвам
--- a/config/locales/bs.yml Tue Jan 14 14:37:42 2014 +0000 +++ b/config/locales/bs.yml Wed Jan 15 09:59:14 2014 +0000 @@ -440,6 +440,7 @@ label_login_with_open_id_option: ili prijava sa OpenID-om label_password_lost: Izgubljena lozinka label_home: Početna stranica + label_home_heading: Početna stranica label_my_page: Moja stranica label_my_account: Moj korisnički račun label_my_projects: Moji projekti
--- a/config/locales/ca.yml Tue Jan 14 14:37:42 2014 +0000 +++ b/config/locales/ca.yml Wed Jan 15 09:59:14 2014 +0000 @@ -476,6 +476,7 @@ label_login_with_open_id_option: "o entra amb l'OpenID" label_password_lost: Contrasenya perduda label_home: Inici + label_home_heading: Inici label_my_page: La meva pàgina label_my_account: El meu compte label_my_projects: Els meus projectes
--- a/config/locales/cs.yml Tue Jan 14 14:37:42 2014 +0000 +++ b/config/locales/cs.yml Wed Jan 15 09:59:14 2014 +0000 @@ -489,6 +489,7 @@ label_login_with_open_id_option: nebo se přihlašte s OpenID label_password_lost: Zapomenuté heslo label_home: Úvodní + label_home_heading: Úvodní label_my_page: Moje stránka label_my_account: Můj účet label_my_projects: Moje projekty
--- a/config/locales/da.yml Tue Jan 14 14:37:42 2014 +0000 +++ b/config/locales/da.yml Wed Jan 15 09:59:14 2014 +0000 @@ -369,6 +369,7 @@ label_register: Registrér label_password_lost: Glemt kodeord label_home: Forside + label_home_heading: Forside label_my_page: Min side label_my_account: Min konto label_my_projects: Mine projekter
--- a/config/locales/de.yml Tue Jan 14 14:37:42 2014 +0000 +++ b/config/locales/de.yml Wed Jan 15 09:59:14 2014 +0000 @@ -818,6 +818,172 @@ one: 1 Projekt other: "%{count} Projekte" label_year: Jahr + label_project_all: Alle Projekte + label_project_latest: Neueste Projekte + label_issue: Ticket + label_issue_new: Neues Ticket + label_issue_plural: Tickets + label_issue_view_all: Alle Tickets anzeigen + label_issues_by: "Tickets von %{value}" + label_issue_added: Ticket hinzugefügt + label_issue_updated: Ticket aktualisiert + label_document: Dokument + label_document_new: Neues Dokument + label_document_plural: Dokumente + label_document_added: Dokument hinzugefügt + label_role: Rolle + label_role_plural: Rollen + label_role_new: Neue Rolle + label_role_and_permissions: Rollen und Rechte + label_member: Mitglied + label_member_new: Neues Mitglied + label_member_plural: Mitglieder + label_tracker: Tracker + label_tracker_plural: Tracker + label_tracker_new: Neuer Tracker + label_workflow: Workflow + label_issue_status: Ticket-Status + label_issue_status_plural: Ticket-Status + label_issue_status_new: Neuer Status + label_issue_category: Ticket-Kategorie + label_issue_category_plural: Ticket-Kategorien + label_issue_category_new: Neue Kategorie + label_custom_field: Benutzerdefiniertes Feld + label_custom_field_plural: Benutzerdefinierte Felder + label_custom_field_new: Neues Feld + label_enumerations: Aufzählungen + label_enumeration_new: Neuer Wert + label_information: Information + label_information_plural: Informationen + label_please_login: Anmelden + label_register: Registrieren + label_login_with_open_id_option: oder mit OpenID anmelden + label_password_lost: Kennwort vergessen + label_home: Hauptseite + label_home_heading: Hauptseite + label_my_page: Meine Seite + label_my_account: Mein Konto + label_my_projects: Meine Projekte + label_my_page_block: Bereich "Meine Seite" + label_administration: Administration + label_login: Anmelden + label_logout: Abmelden + label_help: Hilfe + label_reported_issues: Gemeldete Tickets + label_assigned_to_me_issues: Mir zugewiesen + label_last_login: Letzte Anmeldung + label_registered_on: Angemeldet am + label_activity: Aktivität + label_overall_activity: Aktivitäten aller Projekte anzeigen + label_user_activity: "Aktivität von %{value}" + label_new: Neu + label_logged_as: Angemeldet als + label_environment: Umgebung + label_authentication: Authentifizierung + label_auth_source: Authentifizierungs-Modus + label_auth_source_new: Neuer Authentifizierungs-Modus + label_auth_source_plural: Authentifizierungs-Arten + label_subproject_plural: Unterprojekte + label_subproject_new: Neues Unterprojekt + label_and_its_subprojects: "%{value} und dessen Unterprojekte" + label_min_max_length: Länge (Min. - Max.) + label_list: Liste + label_date: Datum + label_integer: Zahl + label_float: Fließkommazahl + label_boolean: Boolean + label_string: Text + label_text: Langer Text + label_attribute: Attribut + label_attribute_plural: Attribute + label_download: "%{count} Download" + label_download_plural: "%{count} Downloads" + label_no_data: Nichts anzuzeigen + label_change_status: Statuswechsel + label_history: Historie + label_attachment: Datei + label_attachment_new: Neue Datei + label_attachment_delete: Anhang löschen + label_attachment_plural: Dateien + label_file_added: Datei hinzugefügt + label_report: Bericht + label_report_plural: Berichte + label_news: News + label_news_new: News hinzufügen + label_news_plural: News + label_news_latest: Letzte News + label_news_view_all: Alle News anzeigen + label_news_added: News hinzugefügt + label_settings: Konfiguration + label_overview: Übersicht + label_version: Version + label_version_new: Neue Version + label_version_plural: Versionen + label_close_versions: Vollständige Versionen schließen + label_confirmation: Bestätigung + label_export_to: "Auch abrufbar als:" + label_read: Lesen... + label_public_projects: Öffentliche Projekte + label_open_issues: offen + label_open_issues_plural: offen + label_closed_issues: geschlossen + label_closed_issues_plural: geschlossen + label_x_open_issues_abbr_on_total: + zero: 0 offen / %{total} + one: 1 offen / %{total} + other: "%{count} offen / %{total}" + label_x_open_issues_abbr: + zero: 0 offen + one: 1 offen + other: "%{count} offen" + label_x_closed_issues_abbr: + zero: 0 geschlossen + one: 1 geschlossen + other: "%{count} geschlossen" + label_total: Gesamtzahl + label_permissions: Berechtigungen + label_current_status: Gegenwärtiger Status + label_new_statuses_allowed: Neue Berechtigungen + label_all: alle + label_none: kein + label_nobody: Niemand + label_next: Weiter + label_previous: Zurück + label_used_by: Benutzt von + label_details: Details + label_add_note: Kommentar hinzufügen + label_per_page: Pro Seite + label_calendar: Kalender + label_months_from: Monate ab + label_gantt: Gantt-Diagramm + label_internal: Intern + label_last_changes: "%{count} letzte Änderungen" + label_change_view_all: Alle Änderungen anzeigen + label_personalize_page: Diese Seite anpassen + label_comment: Kommentar + label_comment_plural: Kommentare + label_x_comments: + zero: keine Kommentare + one: 1 Kommentar + other: "%{count} Kommentare" + label_comment_add: Kommentar hinzufügen + label_comment_added: Kommentar hinzugefügt + label_comment_delete: Kommentar löschen + label_query: Benutzerdefinierte Abfrage + label_query_plural: Benutzerdefinierte Berichte + label_query_new: Neuer Bericht + label_filter_add: Filter hinzufügen + label_filter_plural: Filter + label_equals: ist + label_not_equals: ist nicht + label_in_less_than: in weniger als + label_in_more_than: in mehr als + label_greater_or_equal: ">=" + label_less_or_equal: "<=" + label_in: an + label_today: heute + label_all_time: gesamter Zeitraum +>>>>>>> other label_yesterday: gestern mail_body_account_activation_request: "Ein neuer Benutzer (%{value}) hat sich registriert. Sein Konto wartet auf Ihre Genehmigung:"
--- a/config/locales/el.yml Tue Jan 14 14:37:42 2014 +0000 +++ b/config/locales/el.yml Wed Jan 15 09:59:14 2014 +0000 @@ -439,6 +439,7 @@ label_login_with_open_id_option: ή συνδεθείτε με OpenID label_password_lost: Ανάκτηση κωδικού πρόσβασης label_home: Αρχική σελίδα + label_home_heading: Αρχική σελίδα label_my_page: Η σελίδα μου label_my_account: Ο λογαριασμός μου label_my_projects: Τα έργα μου
--- a/config/locales/en-GB.yml Tue Jan 14 14:37:42 2014 +0000 +++ b/config/locales/en-GB.yml Wed Jan 15 09:59:14 2014 +0000 @@ -1,1105 +1,3 @@ en-GB: - direction: ltr - date: - formats: - # Use the strftime parameters for formats. - # When no format has been given, it uses default. - # You can provide other formats here if you like! - default: "%d/%m/%Y" - short: "%d %b" - long: "%d %B, %Y" + dummy_translation_key: Dummy translation value - day_names: [Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday] - abbr_day_names: [Sun, Mon, Tue, Wed, Thu, Fri, Sat] - - # Don't forget the nil at the beginning; there's no such thing as a 0th month - month_names: [~, January, February, March, April, May, June, July, August, September, October, November, December] - abbr_month_names: [~, Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec] - # Used in date_select and datime_select. - order: - - :year - - :month - - :day - - time: - formats: - default: "%d/%m/%Y %I:%M %p" - time: "%I:%M %p" - short: "%d %b %H:%M" - long: "%d %B, %Y %H:%M" - am: "am" - pm: "pm" - - datetime: - distance_in_words: - half_a_minute: "half a minute" - less_than_x_seconds: - one: "less than 1 second" - other: "less than %{count} seconds" - x_seconds: - one: "1 second" - other: "%{count} seconds" - less_than_x_minutes: - one: "less than a minute" - other: "less than %{count} minutes" - x_minutes: - one: "1 minute" - other: "%{count} minutes" - about_x_hours: - one: "about 1 hour" - other: "about %{count} hours" - x_hours: - one: "1 hour" - other: "%{count} hours" - x_days: - one: "1 day" - other: "%{count} days" - about_x_months: - one: "about 1 month" - other: "about %{count} months" - x_months: - one: "1 month" - other: "%{count} months" - about_x_years: - one: "about 1 year" - other: "about %{count} years" - over_x_years: - one: "over 1 year" - other: "over %{count} years" - almost_x_years: - one: "almost 1 year" - other: "almost %{count} years" - - number: - format: - separator: "." - delimiter: " " - precision: 3 - - currency: - format: - format: "%u%n" - unit: "£" - - human: - format: - delimiter: "" - precision: 3 - storage_units: - format: "%n %u" - units: - byte: - one: "Byte" - other: "Bytes" - kb: "KB" - mb: "MB" - gb: "GB" - tb: "TB" - -# Used in array.to_sentence. - support: - array: - sentence_connector: "and" - skip_last_comma: false - - activerecord: - errors: - template: - header: - one: "1 error prohibited this %{model} from being saved" - other: "%{count} errors prohibited this %{model} from being saved" - messages: - inclusion: "is not included in the list" - exclusion: "is reserved" - invalid: "is invalid" - confirmation: "doesn't match confirmation" - accepted: "must be accepted" - empty: "can't be empty" - blank: "can't be blank" - too_long: "is too long (maximum is %{count} characters)" - too_short: "is too short (minimum is %{count} characters)" - wrong_length: "is the wrong length (should be %{count} characters)" - taken: "has already been taken" - not_a_number: "is not a number" - not_a_date: "is not a valid date" - greater_than: "must be greater than %{count}" - greater_than_or_equal_to: "must be greater than or equal to %{count}" - equal_to: "must be equal to %{count}" - less_than: "must be less than %{count}" - less_than_or_equal_to: "must be less than or equal to %{count}" - odd: "must be odd" - even: "must be even" - greater_than_start_date: "must be greater than start date" - not_same_project: "doesn't belong to the same project" - circular_dependency: "This relation would create a circular dependency" - cant_link_an_issue_with_a_descendant: "An issue cannot be linked to one of its subtasks" - earlier_than_minimum_start_date: "cannot be earlier than %{date} because of preceding issues" - - actionview_instancetag_blank_option: Please select - - general_text_No: 'No' - general_text_Yes: 'Yes' - general_text_no: 'no' - general_text_yes: 'yes' - general_lang_name: 'English (British)' - general_csv_separator: ',' - general_csv_decimal_separator: '.' - general_csv_encoding: ISO-8859-1 - general_pdf_encoding: UTF-8 - general_first_day_of_week: '1' - - notice_account_updated: Account was successfully updated. - notice_account_invalid_creditentials: Invalid user or password - notice_account_password_updated: Password was successfully updated. - notice_account_wrong_password: Wrong password - notice_account_register_done: Account was successfully created. An email containing the instructions to activate your account was sent to %{email}. - notice_account_unknown_email: Unknown user. - notice_can_t_change_password: This account uses an external authentication source. Impossible to change the password. - notice_account_lost_email_sent: An email with instructions to choose a new password has been sent to you. - notice_account_activated: Your account has been activated. You can now log in. - notice_successful_create: Successful creation. - notice_successful_update: Successful update. - notice_successful_delete: Successful deletion. - notice_successful_connection: Successful connection. - notice_file_not_found: The page you were trying to access doesn't exist or has been removed. - notice_locking_conflict: Data has been updated by another user. - notice_not_authorized: You are not authorised to access this page. - notice_not_authorized_archived_project: The project you're trying to access has been archived. - notice_email_sent: "An email was sent to %{value}" - notice_email_error: "An error occurred while sending mail (%{value})" - notice_feeds_access_key_reseted: Your Atom access key was reset. - notice_api_access_key_reseted: Your API access key was reset. - notice_failed_to_save_issues: "Failed to save %{count} issue(s) on %{total} selected: %{ids}." - notice_failed_to_save_members: "Failed to save member(s): %{errors}." - notice_no_issue_selected: "No issue is selected! Please, check the issues you want to edit." - notice_account_pending: "Your account was created and is now pending administrator approval." - notice_default_data_loaded: Default configuration successfully loaded. - notice_unable_delete_version: Unable to delete version. - notice_unable_delete_time_entry: Unable to delete time log entry. - notice_issue_done_ratios_updated: Issue done ratios updated. - notice_gantt_chart_truncated: "The chart was truncated because it exceeds the maximum number of items that can be displayed (%{max})" - - error_can_t_load_default_data: "Default configuration could not be loaded: %{value}" - error_scm_not_found: "The entry or revision was not found in the repository." - error_scm_command_failed: "An error occurred when trying to access the repository: %{value}" - error_scm_annotate: "The entry does not exist or cannot be annotated." - error_scm_annotate_big_text_file: "The entry cannot be annotated, as it exceeds the maximum text file size." - error_issue_not_found_in_project: 'The issue was not found or does not belong to this project' - error_no_tracker_in_project: 'No tracker is associated to this project. Please check the Project settings.' - error_no_default_issue_status: 'No default issue status is defined. Please check your configuration (Go to "Administration -> Issue statuses").' - error_can_not_delete_custom_field: Unable to delete custom field - error_can_not_delete_tracker: "This tracker contains issues and cannot be deleted." - error_can_not_remove_role: "This role is in use and cannot be deleted." - error_can_not_reopen_issue_on_closed_version: 'An issue assigned to a closed version cannot be reopened' - error_can_not_archive_project: This project cannot be archived - error_issue_done_ratios_not_updated: "Issue done ratios not updated." - error_workflow_copy_source: 'Please select a source tracker or role' - error_workflow_copy_target: 'Please select target tracker(s) and role(s)' - error_unable_delete_issue_status: 'Unable to delete issue status' - error_unable_to_connect: "Unable to connect (%{value})" - warning_attachments_not_saved: "%{count} file(s) could not be saved." - - mail_subject_lost_password: "Your %{value} password" - mail_body_lost_password: 'To change your password, click on the following link:' - mail_subject_register: "Your %{value} account activation" - mail_body_register: 'To activate your account, click on the following link:' - mail_body_account_information_external: "You can use your %{value} account to log in." - mail_body_account_information: Your account information - mail_subject_account_activation_request: "%{value} account activation request" - mail_body_account_activation_request: "A new user (%{value}) has registered. The account is pending your approval:" - mail_subject_reminder: "%{count} issue(s) due in the next %{days} days" - mail_body_reminder: "%{count} issue(s) that are assigned to you are due in the next %{days} days:" - mail_subject_wiki_content_added: "'%{id}' wiki page has been added" - mail_body_wiki_content_added: "The '%{id}' wiki page has been added by %{author}." - mail_subject_wiki_content_updated: "'%{id}' wiki page has been updated" - mail_body_wiki_content_updated: "The '%{id}' wiki page has been updated by %{author}." - - - field_name: Name - field_description: Description - field_summary: Summary - field_is_required: Required - field_firstname: First name - field_lastname: Last name - field_mail: Email - field_filename: File - field_filesize: Size - field_downloads: Downloads - field_author: Author - field_created_on: Created - field_updated_on: Updated - field_field_format: Format - field_is_for_all: For all projects - field_possible_values: Possible values - field_regexp: Regular expression - field_min_length: Minimum length - field_max_length: Maximum length - field_value: Value - field_category: Category - field_title: Title - field_project: Project - field_issue: Issue - field_status: Status - field_notes: Notes - field_is_closed: Issue closed - field_is_default: Default value - field_tracker: Tracker - field_subject: Subject - field_due_date: Due date - field_assigned_to: Assignee - field_priority: Priority - field_fixed_version: Target version - field_user: User - field_principal: Principal - field_role: Role - field_homepage: Homepage - field_is_public: Public - field_parent: Subproject of - field_is_in_roadmap: Issues displayed in roadmap - field_login: Login - field_mail_notification: Email notifications - field_admin: Administrator - field_last_login_on: Last connection - field_language: Language - field_effective_date: Date - field_password: Password - field_new_password: New password - field_password_confirmation: Confirmation - field_version: Version - field_type: Type - field_host: Host - field_port: Port - field_account: Account - field_base_dn: Base DN - field_attr_login: Login attribute - field_attr_firstname: Firstname attribute - field_attr_lastname: Lastname attribute - field_attr_mail: Email attribute - field_onthefly: On-the-fly user creation - field_start_date: Start date - field_done_ratio: "% Done" - field_auth_source: Authentication mode - field_hide_mail: Hide my email address - field_comments: Comment - field_url: URL - field_start_page: Start page - field_subproject: Subproject - field_hours: Hours - field_activity: Activity - field_spent_on: Date - field_identifier: Identifier - field_is_filter: Used as a filter - field_issue_to: Related issue - field_delay: Delay - field_assignable: Issues can be assigned to this role - field_redirect_existing_links: Redirect existing links - field_estimated_hours: Estimated time - field_column_names: Columns - field_time_entries: Log time - field_time_zone: Time zone - field_searchable: Searchable - field_default_value: Default value - field_comments_sorting: Display comments - field_parent_title: Parent page - field_editable: Editable - field_watcher: Watcher - field_identity_url: OpenID URL - field_content: Content - field_group_by: Group results by - field_sharing: Sharing - field_parent_issue: Parent task - field_member_of_group: "Assignee's group" - field_assigned_to_role: "Assignee's role" - field_text: Text field - field_visible: Visible - field_warn_on_leaving_unsaved: "Warn me when leaving a page with unsaved text" - - setting_app_title: Application title - setting_app_subtitle: Application subtitle - setting_welcome_text: Welcome text - setting_default_language: Default language - setting_login_required: Authentication required - setting_self_registration: Self-registration - setting_attachment_max_size: Attachment max. size - setting_issues_export_limit: Issues export limit - setting_mail_from: Emission email address - setting_bcc_recipients: Blind carbon copy recipients (bcc) - setting_plain_text_mail: Plain text mail (no HTML) - setting_host_name: Host name and path - setting_text_formatting: Text formatting - setting_wiki_compression: Wiki history compression - setting_feeds_limit: Feed content limit - setting_default_projects_public: New projects are public by default - setting_autofetch_changesets: Autofetch commits - setting_sys_api_enabled: Enable WS for repository management - setting_commit_ref_keywords: Referencing keywords - setting_commit_fix_keywords: Fixing keywords - setting_autologin: Autologin - setting_date_format: Date format - setting_time_format: Time format - setting_cross_project_issue_relations: Allow cross-project issue relations - setting_issue_list_default_columns: Default columns displayed on the issue list - setting_emails_header: Email header - setting_emails_footer: Email footer - setting_protocol: Protocol - setting_per_page_options: Objects per page options - setting_user_format: Users display format - setting_activity_days_default: Days displayed on project activity - setting_display_subprojects_issues: Display subprojects issues on main projects by default - setting_enabled_scm: Enabled SCM - setting_mail_handler_body_delimiters: "Truncate emails after one of these lines" - setting_mail_handler_api_enabled: Enable WS for incoming emails - setting_mail_handler_api_key: API key - setting_sequential_project_identifiers: Generate sequential project identifiers - setting_gravatar_enabled: Use Gravatar user icons - setting_gravatar_default: Default Gravatar image - setting_diff_max_lines_displayed: Max number of diff lines displayed - setting_file_max_size_displayed: Max size of text files displayed inline - setting_repository_log_display_limit: Maximum number of revisions displayed on file log - setting_openid: Allow OpenID login and registration - setting_password_min_length: Minimum password length - setting_new_project_user_role_id: Role given to a non-admin user who creates a project - setting_default_projects_modules: Default enabled modules for new projects - setting_issue_done_ratio: Calculate the issue done ratio with - setting_issue_done_ratio_issue_field: Use the issue field - setting_issue_done_ratio_issue_status: Use the issue status - setting_start_of_week: Start calendars on - setting_rest_api_enabled: Enable REST web service - setting_cache_formatted_text: Cache formatted text - setting_default_notification_option: Default notification option - setting_commit_logtime_enabled: Enable time logging - setting_commit_logtime_activity_id: Activity for logged time - setting_gantt_items_limit: Maximum number of items displayed on the gantt chart - setting_issue_group_assignment: Allow issue assignment to groups - setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues - - permission_add_project: Create project - permission_add_subprojects: Create subprojects - permission_edit_project: Edit project - permission_select_project_modules: Select project modules - permission_manage_members: Manage members - permission_manage_project_activities: Manage project activities - permission_manage_versions: Manage versions - permission_manage_categories: Manage issue categories - permission_view_issues: View Issues - permission_add_issues: Add issues - permission_edit_issues: Edit issues - permission_manage_issue_relations: Manage issue relations - permission_add_issue_notes: Add notes - permission_edit_issue_notes: Edit notes - permission_edit_own_issue_notes: Edit own notes - permission_move_issues: Move issues - permission_delete_issues: Delete issues - permission_manage_public_queries: Manage public queries - permission_save_queries: Save queries - permission_view_gantt: View gantt chart - permission_view_calendar: View calendar - permission_view_issue_watchers: View watchers list - permission_add_issue_watchers: Add watchers - permission_delete_issue_watchers: Delete watchers - permission_log_time: Log spent time - permission_view_time_entries: View spent time - permission_edit_time_entries: Edit time logs - permission_edit_own_time_entries: Edit own time logs - permission_manage_news: Manage news - permission_comment_news: Comment news - permission_view_documents: View documents - permission_manage_files: Manage files - permission_view_files: View files - permission_manage_wiki: Manage wiki - permission_rename_wiki_pages: Rename wiki pages - permission_delete_wiki_pages: Delete wiki pages - permission_view_wiki_pages: View wiki - permission_view_wiki_edits: View wiki history - permission_edit_wiki_pages: Edit wiki pages - permission_delete_wiki_pages_attachments: Delete attachments - permission_protect_wiki_pages: Protect wiki pages - permission_manage_repository: Manage repository - permission_browse_repository: Browse repository - permission_view_changesets: View changesets - permission_commit_access: Commit access - permission_manage_boards: Manage forums - permission_view_messages: View messages - permission_add_messages: Post messages - permission_edit_messages: Edit messages - permission_edit_own_messages: Edit own messages - permission_delete_messages: Delete messages - permission_delete_own_messages: Delete own messages - permission_export_wiki_pages: Export wiki pages - permission_manage_subtasks: Manage subtasks - - project_module_issue_tracking: Issue tracking - project_module_time_tracking: Time tracking - project_module_news: News - project_module_documents: Documents - project_module_files: Files - project_module_wiki: Wiki - project_module_repository: Repository - project_module_boards: Forums - project_module_calendar: Calendar - project_module_gantt: Gantt - - label_user: User - label_user_plural: Users - label_user_new: New user - label_user_anonymous: Anonymous - label_project: Project - label_project_new: New project - label_project_plural: Projects - label_x_projects: - zero: no projects - one: 1 project - other: "%{count} projects" - label_project_all: All Projects - label_project_latest: Latest projects - label_issue: Issue - label_issue_new: New issue - label_issue_plural: Issues - label_issue_view_all: View all issues - label_issues_by: "Issues by %{value}" - label_issue_added: Issue added - label_issue_updated: Issue updated - label_document: Document - label_document_new: New document - label_document_plural: Documents - label_document_added: Document added - label_role: Role - label_role_plural: Roles - label_role_new: New role - label_role_and_permissions: Roles and permissions - label_role_anonymous: Anonymous - label_role_non_member: Non member - label_member: Member - label_member_new: New member - label_member_plural: Members - label_tracker: Tracker - label_tracker_plural: Trackers - label_tracker_new: New tracker - label_workflow: Workflow - label_issue_status: Issue status - label_issue_status_plural: Issue statuses - label_issue_status_new: New status - label_issue_category: Issue category - label_issue_category_plural: Issue categories - label_issue_category_new: New category - label_custom_field: Custom field - label_custom_field_plural: Custom fields - label_custom_field_new: New custom field - label_enumerations: Enumerations - label_enumeration_new: New value - label_information: Information - label_information_plural: Information - label_please_login: Please log in - label_register: Register - label_login_with_open_id_option: or login with OpenID - label_password_lost: Lost password - label_home: Home - label_my_page: My page - label_my_account: My account - label_my_projects: My projects - label_my_page_block: My page block - label_administration: Administration - label_login: Sign in - label_logout: Sign out - label_help: Help - label_reported_issues: Reported issues - label_assigned_to_me_issues: Issues assigned to me - label_last_login: Last connection - label_registered_on: Registered on - label_activity: Activity - label_overall_activity: Overall activity - label_user_activity: "%{value}'s activity" - label_new: New - label_logged_as: Logged in as - label_environment: Environment - label_authentication: Authentication - label_auth_source: Authentication mode - label_auth_source_new: New authentication mode - label_auth_source_plural: Authentication modes - label_subproject_plural: Subprojects - label_subproject_new: New subproject - label_and_its_subprojects: "%{value} and its subprojects" - label_min_max_length: Min - Max length - label_list: List - label_date: Date - label_integer: Integer - label_float: Float - label_boolean: Boolean - label_string: Text - label_text: Long text - label_attribute: Attribute - label_attribute_plural: Attributes - label_no_data: No data to display - label_change_status: Change status - label_history: History - label_attachment: File - label_attachment_new: New file - label_attachment_delete: Delete file - label_attachment_plural: Files - label_file_added: File added - label_report: Report - label_report_plural: Reports - label_news: News - label_news_new: Add news - label_news_plural: News - label_news_latest: Latest news - label_news_view_all: View all news - label_news_added: News added - label_news_comment_added: Comment added to a news - label_settings: Settings - label_overview: Overview - label_version: Version - label_version_new: New version - label_version_plural: Versions - label_close_versions: Close completed versions - label_confirmation: Confirmation - label_export_to: 'Also available in:' - label_read: Read... - label_public_projects: Public projects - label_open_issues: open - label_open_issues_plural: open - label_closed_issues: closed - label_closed_issues_plural: closed - label_x_open_issues_abbr_on_total: - zero: 0 open / %{total} - one: 1 open / %{total} - other: "%{count} open / %{total}" - label_x_open_issues_abbr: - zero: 0 open - one: 1 open - other: "%{count} open" - label_x_closed_issues_abbr: - zero: 0 closed - one: 1 closed - other: "%{count} closed" - label_total: Total - label_permissions: Permissions - label_current_status: Current status - label_new_statuses_allowed: New statuses allowed - label_all: all - label_none: none - label_nobody: nobody - label_next: Next - label_previous: Previous - label_used_by: Used by - label_details: Details - label_add_note: Add a note - label_per_page: Per page - label_calendar: Calendar - label_months_from: months from - label_gantt: Gantt - label_internal: Internal - label_last_changes: "last %{count} changes" - label_change_view_all: View all changes - label_personalize_page: Personalise this page - label_comment: Comment - label_comment_plural: Comments - label_x_comments: - zero: no comments - one: 1 comment - other: "%{count} comments" - label_comment_add: Add a comment - label_comment_added: Comment added - label_comment_delete: Delete comments - label_query: Custom query - label_query_plural: Custom queries - label_query_new: New query - label_my_queries: My custom queries - label_filter_add: Add filter - label_filter_plural: Filters - label_equals: is - label_not_equals: is not - label_in_less_than: in less than - label_in_more_than: in more than - label_greater_or_equal: '>=' - label_less_or_equal: '<=' - label_in: in - label_today: today - label_all_time: all time - label_yesterday: yesterday - label_this_week: this week - label_last_week: last week - label_last_n_days: "last %{count} days" - label_this_month: this month - label_last_month: last month - label_this_year: this year - label_date_range: Date range - label_less_than_ago: less than days ago - label_more_than_ago: more than days ago - label_ago: days ago - label_contains: contains - label_not_contains: doesn't contain - label_day_plural: days - label_repository: Repository - label_repository_plural: Repositories - label_browse: Browse - label_branch: Branch - label_tag: Tag - label_revision: Revision - label_revision_plural: Revisions - label_revision_id: "Revision %{value}" - label_associated_revisions: Associated revisions - label_added: added - label_modified: modified - label_copied: copied - label_renamed: renamed - label_deleted: deleted - label_latest_revision: Latest revision - label_latest_revision_plural: Latest revisions - label_view_revisions: View revisions - label_view_all_revisions: View all revisions - label_max_size: Maximum size - label_sort_highest: Move to top - label_sort_higher: Move up - label_sort_lower: Move down - label_sort_lowest: Move to bottom - label_roadmap: Roadmap - label_roadmap_due_in: "Due in %{value}" - label_roadmap_overdue: "%{value} late" - label_roadmap_no_issues: No issues for this version - label_search: Search - label_result_plural: Results - label_all_words: All words - label_wiki: Wiki - label_wiki_edit: Wiki edit - label_wiki_edit_plural: Wiki edits - label_wiki_page: Wiki page - label_wiki_page_plural: Wiki pages - label_index_by_title: Index by title - label_index_by_date: Index by date - label_current_version: Current version - label_preview: Preview - label_feed_plural: Feeds - label_changes_details: Details of all changes - label_issue_tracking: Issue tracking - label_spent_time: Spent time - label_overall_spent_time: Overall spent time - label_f_hour: "%{value} hour" - label_f_hour_plural: "%{value} hours" - label_time_tracking: Time tracking - label_change_plural: Changes - label_statistics: Statistics - label_commits_per_month: Commits per month - label_commits_per_author: Commits per author - label_view_diff: View differences - label_diff_inline: inline - label_diff_side_by_side: side by side - label_options: Options - label_copy_workflow_from: Copy workflow from - label_permissions_report: Permissions report - label_watched_issues: Watched issues - label_related_issues: Related issues - label_applied_status: Applied status - label_loading: Loading... - label_relation_new: New relation - label_relation_delete: Delete relation - label_relates_to: related to - label_duplicates: duplicates - label_duplicated_by: duplicated by - label_blocks: blocks - label_blocked_by: blocked by - label_precedes: precedes - label_follows: follows - label_end_to_start: end to start - label_end_to_end: end to end - label_start_to_start: start to start - label_start_to_end: start to end - label_stay_logged_in: Stay logged in - label_disabled: disabled - label_show_completed_versions: Show completed versions - label_me: me - label_board: Forum - label_board_new: New forum - label_board_plural: Forums - label_board_locked: Locked - label_board_sticky: Sticky - label_topic_plural: Topics - label_message_plural: Messages - label_message_last: Last message - label_message_new: New message - label_message_posted: Message added - label_reply_plural: Replies - label_send_information: Send account information to the user - label_year: Year - label_month: Month - label_week: Week - label_date_from: From - label_date_to: To - label_language_based: Based on user's language - label_sort_by: "Sort by %{value}" - label_send_test_email: Send a test email - label_feeds_access_key: Atom access key - label_missing_feeds_access_key: Missing a Atom access key - label_feeds_access_key_created_on: "Atom access key created %{value} ago" - label_module_plural: Modules - label_added_time_by: "Added by %{author} %{age} ago" - label_updated_time_by: "Updated by %{author} %{age} ago" - label_updated_time: "Updated %{value} ago" - label_jump_to_a_project: Jump to a project... - label_file_plural: Files - label_changeset_plural: Changesets - label_default_columns: Default columns - label_no_change_option: (No change) - label_bulk_edit_selected_issues: Bulk edit selected issues - label_theme: Theme - label_default: Default - label_search_titles_only: Search titles only - label_user_mail_option_all: "For any event on all my projects" - label_user_mail_option_selected: "For any event on the selected projects only..." - label_user_mail_option_none: "No events" - 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" - label_user_mail_option_only_owner: "Only for things I am the owner of" - label_user_mail_no_self_notified: "I don't want to be notified of changes that I make myself" - label_registration_activation_by_email: account activation by email - label_registration_manual_activation: manual account activation - label_registration_automatic_activation: automatic account activation - label_display_per_page: "Per page: %{value}" - label_age: Age - label_change_properties: Change properties - label_general: General - label_more: More - label_scm: SCM - label_plugins: Plugins - label_ldap_authentication: LDAP authentication - label_downloads_abbr: D/L - label_optional_description: Optional description - label_add_another_file: Add another file - label_preferences: Preferences - label_chronological_order: In chronological order - label_reverse_chronological_order: In reverse chronological order - label_planning: Planning - label_incoming_emails: Incoming emails - label_generate_key: Generate a key - label_issue_watchers: Watchers - label_example: Example - label_display: Display - label_sort: Sort - label_ascending: Ascending - label_descending: Descending - label_date_from_to: From %{start} to %{end} - label_wiki_content_added: Wiki page added - label_wiki_content_updated: Wiki page updated - label_group: Group - label_group_plural: Groups - label_group_new: New group - label_time_entry_plural: Spent time - label_version_sharing_none: Not shared - label_version_sharing_descendants: With subprojects - label_version_sharing_hierarchy: With project hierarchy - label_version_sharing_tree: With project tree - label_version_sharing_system: With all projects - label_update_issue_done_ratios: Update issue done ratios - label_copy_source: Source - label_copy_target: Target - label_copy_same_as_target: Same as target - label_display_used_statuses_only: Only display statuses that are used by this tracker - label_api_access_key: API access key - label_missing_api_access_key: Missing an API access key - label_api_access_key_created_on: "API access key created %{value} ago" - 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_user_search: "Search for user:" - - button_login: Login - button_submit: Submit - button_save: Save - button_check_all: Check all - button_uncheck_all: Uncheck all - button_collapse_all: Collapse all - button_expand_all: Expand all - button_delete: Delete - button_create: Create - button_create_and_continue: Create and continue - button_test: Test - button_edit: Edit - button_edit_associated_wikipage: "Edit associated Wiki page: %{page_title}" - button_add: Add - button_change: Change - button_apply: Apply - button_clear: Clear - button_lock: Lock - button_unlock: Unlock - button_download: Download - button_list: List - button_view: View - button_move: Move - button_move_and_follow: Move and follow - button_back: Back - button_cancel: Cancel - button_activate: Activate - button_sort: Sort - button_log_time: Log time - button_rollback: Rollback to this version - button_watch: Watch - button_unwatch: Unwatch - button_reply: Reply - button_archive: Archive - button_unarchive: Unarchive - button_reset: Reset - button_rename: Rename - button_change_password: Change password - button_copy: Copy - button_copy_and_follow: Copy and follow - button_annotate: Annotate - button_update: Update - button_configure: Configure - button_quote: Quote - button_duplicate: Duplicate - button_show: Show - - status_active: active - status_registered: registered - status_locked: locked - - version_status_open: open - version_status_locked: locked - version_status_closed: closed - - field_active: Active - - text_select_mail_notifications: Select actions for which email notifications should be sent. - text_regexp_info: eg. ^[A-Z0-9]+$ - text_min_max_length_info: 0 means no restriction - text_project_destroy_confirmation: Are you sure you want to delete this project and related data? - text_subprojects_destroy_warning: "Its subproject(s): %{value} will be also deleted." - text_workflow_edit: Select a role and a tracker to edit the workflow - text_are_you_sure: Are you sure? - text_journal_changed: "%{label} changed from %{old} to %{new}" - text_journal_changed_no_detail: "%{label} updated" - text_journal_set_to: "%{label} set to %{value}" - text_journal_deleted: "%{label} deleted (%{old})" - text_journal_added: "%{label} %{value} added" - text_tip_issue_begin_day: task beginning this day - 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, dashes and underscores are allowed, must start with a lower case letter.<br />Once saved, the identifier cannot be changed.' - text_caracters_maximum: "%{count} characters maximum." - text_caracters_minimum: "Must be at least %{count} characters long." - text_length_between: "Length between %{min} and %{max} characters." - text_tracker_no_workflow: No workflow defined for this tracker - text_unallowed_characters: Unallowed characters - text_comma_separated: Multiple values allowed (comma separated). - text_line_separated: Multiple values allowed (one line for each value). - text_issues_ref_in_commit_messages: Referencing and fixing issues in commit messages - text_issue_added: "Issue %{id} has been reported by %{author}." - text_issue_updated: "Issue %{id} has been updated by %{author}." - text_wiki_destroy_confirmation: Are you sure you want to delete this wiki and all its content? - text_issue_category_destroy_question: "Some issues (%{count}) are assigned to this category. What do you want to do?" - text_issue_category_destroy_assignments: Remove category assignments - text_issue_category_reassign_to: Reassign issues to this category - text_user_mail_option: "For unselected projects, you will only receive notifications about things you watch or you're involved in (eg. issues you're the author or assignee)." - text_no_configuration_data: "Roles, trackers, issue statuses and workflow have not been configured yet.\nIt is highly recommended to load the default configuration. You will be able to modify it once loaded." - text_load_default_configuration: Load the default configuration - text_status_changed_by_changeset: "Applied in changeset %{value}." - text_time_logged_by_changeset: "Applied in changeset %{value}." - text_issues_destroy_confirmation: 'Are you sure you want to delete the selected issue(s)?' - text_select_project_modules: 'Select modules to enable for this project:' - text_default_administrator_account_changed: Default administrator account changed - text_file_repository_writable: Attachments directory writable - text_plugin_assets_writable: Plugin assets directory writable - text_rmagick_available: RMagick available (optional) - text_destroy_time_entries_question: "%{hours} hours were reported on the issues you are about to delete. What do you want to do?" - text_destroy_time_entries: Delete reported hours - text_assign_time_entries_to_project: Assign reported hours to the project - text_reassign_time_entries: 'Reassign reported hours to this issue:' - text_user_wrote: "%{value} wrote:" - text_enumeration_destroy_question: "%{count} objects are assigned to this value." - text_enumeration_category_reassign_to: 'Reassign them to this value:' - text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/configuration.yml and restart the application to enable them." - text_repository_usernames_mapping: "Select or update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped." - text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.' - text_custom_field_possible_values_info: 'One line for each value' - text_wiki_page_destroy_question: "This page has %{descendants} child page(s) and descendant(s). What do you want to do?" - text_wiki_page_nullify_children: "Keep child pages as root pages" - 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_zoom_in: Zoom in - text_zoom_out: Zoom out - text_warn_on_leaving_unsaved: "The current page contains unsaved text that will be lost if you leave this page." - - default_role_manager: Manager - default_role_developer: Developer - default_role_reporter: Reporter - default_tracker_bug: Bug - default_tracker_feature: Feature - default_tracker_support: Support - default_issue_status_new: New - default_issue_status_in_progress: In Progress - default_issue_status_resolved: Resolved - default_issue_status_feedback: Feedback - default_issue_status_closed: Closed - default_issue_status_rejected: Rejected - default_doc_category_user: User documentation - default_doc_category_tech: Technical documentation - default_priority_low: Low - default_priority_normal: Normal - default_priority_high: High - default_priority_urgent: Urgent - default_priority_immediate: Immediate - default_activity_design: Design - default_activity_development: Development - - enumeration_issue_priorities: Issue priorities - enumeration_doc_categories: Document categories - enumeration_activities: Activities (time tracking) - enumeration_system_activity: System Activity - label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee - label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author - label_bulk_edit_selected_time_entries: Bulk edit selected time entries - text_time_entries_destroy_confirmation: Are you sure you want to delete the selected time entr(y/ies)? - label_issue_note_added: Note added - label_issue_status_updated: Status updated - label_issue_priority_updated: Priority updated - label_issues_visibility_own: Issues created by or assigned to the user - field_issues_visibility: Issues visibility - label_issues_visibility_all: All issues - permission_set_own_issues_private: Set own issues public or private - field_is_private: Private - permission_set_issues_private: Set issues public or private - label_issues_visibility_public: All non private issues - text_issues_destroy_descendants_confirmation: This will also delete %{count} subtask(s). - field_commit_logs_encoding: Commit messages encoding - field_scm_path_encoding: Path encoding - text_scm_path_encoding_note: "Default: UTF-8" - field_path_to_repository: Path to repository - field_root_directory: Root directory - field_cvs_module: Module - field_cvsroot: CVSROOT - text_mercurial_repository_note: Local repository (e.g. /hgrepo, c:\hgrepo) - text_scm_command: Command - text_scm_command_version: Version - label_git_report_last_commit: Report last commit for files and directories - text_scm_config: You can configure your SCM commands in config/configuration.yml. Please restart the application after editing it. - text_scm_command_not_available: SCM command is not available. Please check settings on the administration panel. - notice_issue_successful_create: Issue %{id} created. - label_between: between - label_diff: diff - text_git_repository_note: Repository is bare and local (e.g. /gitrepo, c:\gitrepo) - description_query_sort_criteria_direction: Sort direction - description_project_scope: Search scope - description_filter: Filter - description_user_mail_notification: Mail notification settings - description_date_from: Enter start date - description_message_content: Message content - description_available_columns: Available Columns - description_date_range_interval: Choose range by selecting start and end date - description_issue_category_reassign: Choose issue category - description_search: Searchfield - description_notes: Notes - description_date_range_list: Choose range from list - description_choose_project: Projects - description_date_to: Enter end date - description_query_sort_criteria_attribute: Sort attribute - description_wiki_subpages_reassign: Choose new parent page - description_selected_columns: Selected Columns - label_parent_revision: Parent - label_child_revision: Child - button_edit_section: Edit this section - setting_repositories_encodings: Attachments and repositories encodings - description_all_columns: All Columns - button_export: Export - label_export_options: "%{export_format} export options" - error_attachment_too_big: This file cannot be uploaded because it exceeds the maximum allowed file size (%{max_size}) - notice_failed_to_save_time_entries: "Failed to save %{count} time entrie(s) on %{total} selected: %{ids}." - label_x_issues: - zero: 0 issue - one: 1 issue - other: "%{count} issues" - label_repository_new: New repository - field_repository_is_default: Main repository - label_copy_attachments: Copy attachments - label_item_position: "%{position} of %{count}" - label_completed_versions: Completed versions - field_multiple: Multiple values - setting_commit_cross_project_ref: Allow issues of all the other projects to be referenced and fixed - text_issue_conflict_resolution_add_notes: Add my notes and discard my other changes - text_issue_conflict_resolution_overwrite: Apply my changes anyway (previous notes will be kept but some changes may be overwritten) - notice_issue_update_conflict: The issue has been updated by an other user while you were editing it. - text_issue_conflict_resolution_cancel: Discard all my changes and redisplay %{link} - permission_manage_related_issues: Manage related issues - field_auth_source_ldap_filter: LDAP filter - label_search_for_watchers: Search for watchers to add - notice_account_deleted: Your account has been permanently deleted. - setting_unsubscribe: Allow users to delete their own account - button_delete_my_account: Delete my account - text_account_destroy_confirmation: |- - Are you sure you want to proceed? - Your account will be permanently deleted, with no way to reactivate it. - error_session_expired: Your session has expired. Please login again. - text_session_expiration_settings: "Warning: changing these settings may expire the current sessions including yours." - setting_session_lifetime: Session maximum lifetime - setting_session_timeout: Session inactivity timeout - label_session_expiration: Session expiration - permission_close_project: Close / reopen the project - label_show_closed_projects: View closed projects - button_close: Close - button_reopen: Reopen - project_status_active: active - project_status_closed: closed - project_status_archived: archived - text_project_closed: This project is closed and read-only. - notice_user_successful_create: User %{id} created. - field_core_fields: Standard fields - field_timeout: Timeout (in seconds) - setting_thumbnails_enabled: Display attachment thumbnails - setting_thumbnails_size: Thumbnails size (in pixels) - label_status_transitions: Status transitions - label_fields_permissions: Fields permissions - label_readonly: Read-only - label_required: Required - text_repository_identifier_info: 'Only lower case letters (a-z), numbers, dashes and underscores are allowed.<br />Once saved, the identifier cannot be changed.' - field_board_parent: Parent forum - label_attribute_of_project: Project's %{name} - label_attribute_of_author: Author's %{name} - label_attribute_of_assigned_to: Assignee's %{name} - label_attribute_of_fixed_version: Target version's %{name} - label_copy_subtasks: Copy subtasks - label_copied_to: copied to - label_copied_from: copied from - label_any_issues_in_project: any issues in project - label_any_issues_not_in_project: any issues not in project - field_private_notes: Private notes - permission_view_private_notes: View private notes - permission_set_notes_private: Set notes as private - label_no_issues_in_project: no issues in project - label_any: all - label_last_n_weeks: last %{count} weeks - setting_cross_project_subtasks: Allow cross-project subtasks - label_cross_project_descendants: With subprojects - label_cross_project_tree: With project tree - label_cross_project_hierarchy: With project hierarchy - label_cross_project_system: With all projects - button_hide: Hide - setting_non_working_week_days: Non-working days - label_in_the_next_days: in the next - label_in_the_past_days: in the past - label_attribute_of_user: User's %{name} - text_turning_multiple_off: If you disable multiple values, multiple values will be - removed in order to preserve only one value per item. - label_attribute_of_issue: Issue's %{name} - permission_add_documents: Add documents - permission_edit_documents: Edit documents - permission_delete_documents: Delete documents - label_gantt_progress_line: Progress line - setting_jsonp_enabled: Enable JSONP support - field_inherit_members: Inherit members - field_closed_on: Closed - field_generate_password: Generate password - setting_default_projects_tracker_ids: Default trackers for new projects - label_total_time: Total - notice_account_not_activated_yet: You haven't activated your account yet. If you want - to receive a new activation email, please <a href="%{url}">click this link</a>. - notice_account_locked: Your account is locked. - label_hidden: Hidden - label_visibility_private: to me only - label_visibility_roles: to these roles only - label_visibility_public: to any users - field_must_change_passwd: Must change password at next logon - notice_new_password_must_be_different: The new password must be different from the - current password - setting_mail_handler_excluded_filenames: Exclude attachments by name - text_convert_available: ImageMagick convert available (optional)
--- a/config/locales/en.yml Tue Jan 14 14:37:42 2014 +0000 +++ b/config/locales/en.yml Wed Jan 15 09:59:14 2014 +0000 @@ -1,14 +1,13 @@ en: - # Text direction: Left-to-Right (ltr) or Right-to-Left (rtl) direction: ltr date: formats: # Use the strftime parameters for formats. # When no format has been given, it uses default. # You can provide other formats here if you like! - default: "%m/%d/%Y" - short: "%b %d" - long: "%B %d, %Y" + default: "%d/%m/%Y" + short: "%d %b" + long: "%d %B, %Y" day_names: [Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday] abbr_day_names: [Sun, Mon, Tue, Wed, Thu, Fri, Sat] @@ -24,10 +23,10 @@ time: formats: - default: "%m/%d/%Y %I:%M %p" + default: "%d/%m/%Y %I:%M %p" time: "%I:%M %p" short: "%d %b %H:%M" - long: "%B %d, %Y %H:%M" + long: "%d %B, %Y %H:%M" am: "am" pm: "pm" @@ -74,9 +73,13 @@ number: format: separator: "." - delimiter: "" + delimiter: " " precision: 3 + currency: + format: + format: "%u%n" + unit: "£" human: format: delimiter: "" @@ -130,6 +133,8 @@ circular_dependency: "This relation would create a circular dependency" cant_link_an_issue_with_a_descendant: "An issue cannot be linked to one of its subtasks" earlier_than_minimum_start_date: "cannot be earlier than %{date} because of preceding issues" + must_accept_terms_and_conditions: "You must accept the Terms and Conditions" + public_or_private: "You must select either public or private" actionview_instancetag_blank_option: Please select @@ -142,7 +147,7 @@ general_csv_decimal_separator: '.' general_csv_encoding: ISO-8859-1 general_pdf_encoding: UTF-8 - general_first_day_of_week: '7' + general_first_day_of_week: '1' notice_account_updated: Account was successfully updated. notice_account_invalid_creditentials: Invalid user or password @@ -171,7 +176,7 @@ notice_failed_to_save_time_entries: "Failed to save %{count} time entrie(s) on %{total} selected: %{ids}." notice_failed_to_save_members: "Failed to save member(s): %{errors}." notice_no_issue_selected: "No issue is selected! Please, check the issues you want to edit." - notice_account_pending: "Your account was created and is now pending administrator approval." + notice_account_pending: "Your account is now awaiting administrator approval. You will receive a notification email when your account has been activated." notice_default_data_loaded: Default configuration successfully loaded. notice_unable_delete_version: Unable to delete version. notice_unable_delete_time_entry: Unable to delete time log entry. @@ -220,6 +225,15 @@ mail_subject_wiki_content_updated: "'%{id}' wiki page has been updated" mail_body_wiki_content_updated: "The '%{id}' wiki page has been updated by %{author}." + gui_validation_error: 1 error + gui_validation_error_plural: "%{count} errors" + + field_ssamr_user_detail: + description: User Description + institution: Institution + + field_other_institution: '' + field_name: Name field_description: Description field_summary: Summary @@ -260,6 +274,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 @@ -337,7 +352,13 @@ field_inherit_members: Inherit members field_generate_password: Generate password field_must_change_passwd: Must change password at next logon + field_public_or_private: "Public or Private?" + setting_external_repository: "Select this if the project's main repository is hosted somewhere else" + setting_external_repository_url: "The URL of the existing external repository. Must be publicly accessible without a password" + label_repository_external_url: "External repository URL" + field_terms_and_conditions: 'Terms and Conditions:' + accept_terms_and_conditions: 'I have read, and agree to, the ' setting_app_title: Application title setting_app_subtitle: Application subtitle setting_welcome_text: Welcome text @@ -393,9 +414,12 @@ setting_rest_api_enabled: Enable REST web service setting_cache_formatted_text: Cache formatted text setting_default_notification_option: Default notification option + setting_has_welcome_page: Select this to replace the project overview page with your welcome page setting_commit_logtime_enabled: Enable time logging setting_commit_logtime_activity_id: Activity for logged time setting_gantt_items_limit: Maximum number of items displayed on the gantt chart + setting_tipoftheday_text: Tip of the Day + setting_notifications_text: Notifications setting_issue_group_assignment: Allow issue assignment to groups setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues setting_commit_cross_project_ref: Allow issues of all the other projects to be referenced and fixed @@ -448,8 +472,8 @@ permission_add_documents: Add documents permission_edit_documents: Edit documents permission_delete_documents: Delete documents - permission_manage_files: Manage files - permission_view_files: View files + permission_manage_files: Manage downloads + permission_view_files: View downloads permission_manage_wiki: Manage wiki permission_rename_wiki_pages: Rename wiki pages permission_delete_wiki_pages: Delete wiki pages @@ -473,34 +497,55 @@ permission_manage_subtasks: Manage subtasks permission_manage_related_issues: Manage related issues - project_module_issue_tracking: Issue tracking + project_module_issue_tracking: Issue tracking (bugs and feature requests) project_module_time_tracking: Time tracking - project_module_news: News - project_module_documents: Documents - project_module_files: Files + project_module_news: News updates + project_module_documents: Documents for download + project_module_files: Files for download (such as software builds) project_module_wiki: Wiki - project_module_repository: Repository + project_module_repository: Source code repository project_module_boards: Forums project_module_calendar: Calendar - project_module_gantt: Gantt - + project_module_gantt: Gantt chart + project_module_redmine_embedded: Embedded documentation (Javadoc, Doxygen or MATLAB) + label_tipoftheday: Tip of the day + label_notifications: Important Message + label_ssamr_description: Research description + label_ssamr_details: Other Details + label_ssamr_institution: Institution + label_ssamr_description: Research description label_user: User label_user_plural: Users label_user_new: New user label_user_anonymous: Anonymous label_project: Project - label_project_new: New project + label_project_new: Start a new project label_project_plural: Projects + label_my_project_plural: My Projects + label_other_project_plural: Other Projects + label_have_no_projects: You are not a member of any projects. label_x_projects: zero: no projects one: 1 project other: "%{count} projects" + label_x_downloads: + zero: never downloaded + one: downloaded once + other: "downloaded %{count} times" label_project_all: All Projects label_project_latest: Latest projects + label_projects_more: More projects + label_project_tags_all: Popular tags + label_projects_busy: Busy projects + label_projects_mature: Mature projects + label_search_projects: Search projects + label_institutions_busy: Active institutions + label_managers: Managed by label_issue: Issue label_issue_new: New issue label_issue_plural: Issues label_issue_view_all: View all issues + label_issue_view_all_watched: View all watched issues label_issues_by: "Issues by %{value}" label_issue_added: Issue added label_issue_updated: Issue updated @@ -518,7 +563,7 @@ label_role_anonymous: Anonymous label_role_non_member: Non member label_member: Member - label_member_new: New member + label_member_new: Add new member label_member_plural: Members label_tracker: Tracker label_tracker_plural: Trackers @@ -542,8 +587,9 @@ label_login_with_open_id_option: or login with OpenID label_password_lost: Lost password label_home: Home + label_home_heading: Welcome! label_my_page: My page - label_my_account: My account + label_my_account: Account label_my_projects: My projects label_my_page_block: My page block label_administration: Administration @@ -555,8 +601,12 @@ label_last_login: Last connection label_registered_on: Registered on label_activity: Activity + label_activity_recent: Recent activity + label_activity_my_recent: Recent activity in my projects + label_activity_my_recent_none: No recent activity label_overall_activity: Overall activity label_user_activity: "%{value}'s activity" + label_institution_activity: "Activity from %{value}" label_new: New label_logged_as: Logged in as label_environment: Environment @@ -583,7 +633,7 @@ label_attachment: File label_attachment_new: New file label_attachment_delete: Delete file - label_attachment_plural: Files + label_attachment_plural: Downloads label_file_added: File added label_report: Report label_report_plural: Reports @@ -591,6 +641,8 @@ label_news_new: Add news label_news_plural: News label_news_latest: Latest news + label_news_site_latest: Site news + label_news_more: More news label_news_view_all: View all news label_news_added: News added label_news_comment_added: Comment added to a news @@ -645,7 +697,7 @@ label_internal: Internal label_last_changes: "last %{count} changes" label_change_view_all: View all changes - label_personalize_page: Personalize this page + label_personalize_page: Personalise this page label_comment: Comment label_comment_plural: Comments label_x_comments: @@ -693,8 +745,12 @@ label_day_plural: days label_repository: Repository label_repository_new: New repository + label_is_external_repository: Track an external repository label_repository_plural: Repositories label_browse: Browse + label_explore_projects: Explore projects + label_modification: "%{count} change" + label_modification_plural: "%{count} changes" label_branch: Branch label_tag: Tag label_revision: Revision @@ -710,6 +766,7 @@ label_latest_revision_plural: Latest revisions label_view_revisions: View revisions label_view_all_revisions: View all revisions + label_download_revision: Download as Zip label_max_size: Maximum size label_sort_highest: Move to top label_sort_higher: Move up @@ -720,7 +777,8 @@ label_roadmap_overdue: "%{value} late" label_roadmap_no_issues: No issues for this version label_search: Search - label_result_plural: Results + label_result_plural: Search results + label_matching_project_plural: Matching project names label_all_words: All words label_wiki: Wiki label_wiki_edit: Wiki edit @@ -800,8 +858,9 @@ label_added_time_by: "Added by %{author} %{age} ago" label_updated_time_by: "Updated by %{author} %{age} ago" label_updated_time: "Updated %{value} ago" + label_time_ago: "%{age} ago" label_jump_to_a_project: Jump to a project... - label_file_plural: Files + label_file_plural: Downloads label_changeset_plural: Changesets label_default_columns: Default columns label_no_change_option: (No change) @@ -866,8 +925,10 @@ 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:" + label_welcome_page: "Welcome page" + label_has_welcome_page: "Use your own welcome page" label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee label_issues_visibility_all: All issues @@ -877,6 +938,9 @@ label_parent_revision: Parent label_child_revision: Child label_export_options: "%{export_format} export options" + label_my_colleagues: "Active projects for people I'm working with" + label_working_in: Working in + label_no_active_colleagues: No recent activity label_copy_attachments: Copy attachments label_copy_subtasks: Copy subtasks label_item_position: "%{position} of %{count}" @@ -913,7 +977,7 @@ button_expand_all: Expand all button_delete: Delete button_create: Create - button_create_and_continue: Create and continue + button_create_and_continue: Create and Add Another button_test: Test button_edit: Edit button_edit_associated_wikipage: "Edit associated Wiki page: %{page_title}" @@ -951,6 +1015,8 @@ button_duplicate: Duplicate button_show: Show button_hide: Hide + button_welcome_page_edit: Create or edit welcome page + button_welcome_page_edit_this: Edit this page button_edit_section: Edit this section button_export: Export button_delete_my_account: Delete my account @@ -970,6 +1036,7 @@ version_status_closed: closed field_active: Active + field_current: Current text_select_mail_notifications: Select actions for which email notifications should be sent. text_regexp_info: eg. ^[A-Z0-9]+$ @@ -986,10 +1053,18 @@ text_tip_issue_begin_day: issue beginning this day text_tip_issue_end_day: issue ending this day text_tip_issue_begin_end_day: issue beginning and ending this day - text_project_identifier_info: 'Only lower case letters (a-z), numbers, dashes and underscores are allowed, must start with a lower case letter.<br />Once saved, the identifier cannot be changed.' + text_project_identifier_info: 'The system identifier that will form the unique part of the URL for your project.<br/>Only lower case letters (a-z), numbers, dashes and underscores are allowed. Must start with a letter.<br />Once saved, the identifier cannot be changed.' + text_project_homepage_info: 'Optional link to an external project page.' text_caracters_maximum: "%{count} characters maximum." text_caracters_minimum: "Must be at least %{count} characters long." text_length_between: "Length between %{min} and %{max} characters." + text_project_name_info: "This will be the name of your project as it appears throughout this site.<br /> You can change it at any time, in the project's settings." + text_project_public_info: "Public: visible to anybody browsing the site." + text_project_private_info: "Private: visible only to you, and to users you have added as project members." + text_project_visibility_info: "You can change whether your project is public or private later if you wish." + text_user_ssamr_description_info: 'Please describe your current research or development interests.<br/>This information will be used at registration to determine that you are a real person – so please be descriptive, or your application may be delayed or rejected.<br/>After registration, the description is publicly visible in your profile and you can edit it at any time.' + text_issue_parent_issue_info: 'If this is a subtask, please insert its parent task number or write the main task name.' + text_tracker_no_workflow: No workflow defined for this tracker text_unallowed_characters: Unallowed characters text_comma_separated: Multiple values allowed (comma separated). @@ -1023,7 +1098,7 @@ text_enumeration_destroy_question: "%{count} objects are assigned to this value." text_enumeration_category_reassign_to: 'Reassign them to this value:' text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/configuration.yml and restart the application to enable them." - text_repository_usernames_mapping: "Select or update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped." + text_repository_usernames_mapping: "Select the project member associated with each username found in the repository log.\nUsers whose name or email matches that in the repository are mapped automatically." text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.' text_custom_field_possible_values_info: 'One line for each value' text_wiki_page_destroy_question: "This page has %{descendants} child page(s) and descendant(s). What do you want to do?" @@ -1034,6 +1109,8 @@ text_zoom_in: Zoom in text_zoom_out: Zoom out text_warn_on_leaving_unsaved: "The current page contains unsaved text that will be lost if you leave this page." + text_files_active_change: <br>Click the star to switch active status for a download on or off.<br>Active files will be shown more prominently in the download page. + text_settings_repo_creation: <b>Creating repository...</b></p><p>The source code repository for a project will be set up automatically within a few minutes of the project being created.</p><p>Please reload this page in five minutes, and <a href="/projects/soundsoftware-site/wiki/Help">contact us</a> if there is any problem.</p><p>If you wish to use this project to track a repository that is already hosted somewhere else, please wait until the repository has been created here and then return to this settings page to configure it.</p><p>If you don't want a repository at all, go to the Modules tab and switch it off there. text_scm_path_encoding_note: "Default: UTF-8" text_git_repository_note: Repository is bare and local (e.g. /gitrepo, c:\gitrepo) text_mercurial_repository_note: Local repository (e.g. /hgrepo, c:\hgrepo) @@ -1048,6 +1125,14 @@ text_session_expiration_settings: "Warning: changing these settings may expire the current sessions including yours." text_project_closed: This project is closed and read-only. text_turning_multiple_off: "If you disable multiple values, multiple values will be removed in order to preserve only one value per item." + text_settings_repo_explanation: <b>External repositories</b><p>Normally your project's primary repository will be the Mercurial repository hosted at this site.<p>However, if you already have your project hosted somewhere else, you can specify your existing external repository's URL here – then this site will track that repository in a read-only “mirror” copy. External Mercurial, git and Subversion repositories can be tracked. Note that you cannot switch to an external repository if you have already made any commits to the repository hosted here. + text_settings_repo_is_internal: Currently the repository hosted at this site is the primary repository for this project. + text_settings_repo_is_external: Currently the repository hosted at this site is a read-only copy of an external repository. + text_settings_repo_need_help: Please <a href="/projects/soundsoftware-site/wiki/Help">contact us</a> if you need help deciding how best to set this up.<br>We can also import complete revision history from other systems into a new primary repository for you if you wish. + text_has_welcome_page_info: <b>Welcome page</b><p>You can replace the standard %{overview_link} page for this project with your own welcome page.<br>This page will be editable using the project Wiki. + text_has_welcome_page_wiki_disabled: <b>Note:</b> You must enable the Wiki module in the %{modules_link} tab before you can create or edit this page. + text_binary_data: Binary data + text_data_too_large: Binary data, or file too large to display default_role_manager: Manager default_role_developer: Developer @@ -1075,6 +1160,16 @@ enumeration_doc_categories: Document categories enumeration_activities: Activities (time tracking) enumeration_system_activity: System Activity + + label_manager_description: All powers including adding and removing members and adjusting project settings + label_developer_description: Can commit to repository and carry out most project editing tasks + label_reporter_description: Can submit bug reports; has read access for private projects + + label_set_role_plural: Choose roles for new member + + notice_added_to_project: 'You have been added to the project "%{project_name}".' + notice_project_homepage: "You can visit the project using the following link: %{project_url}" + mail_subject_added_to_project: "You've been added to a project on %{value}" description_filter: Filter description_search: Searchfield description_choose_project: Projects @@ -1094,3 +1189,8 @@ description_date_from: Enter start date description_date_to: Enter end date text_repository_identifier_info: 'Only lower case letters (a-z), numbers, dashes and underscores are allowed.<br />Once saved, the identifier cannot be changed.' + + text_who_can_register: '<h3>Who can register here?</h3><p>You can register if you are a researcher in the audio and music field are you are:<ul><li>working in the UK,</li><li>working with UK researchers who are also using this site, <i>or</i></li><li>working on projects having an audience within the UK research community.</li></ul></p><p>Please ensure you provide enough information in the description below to establish which of these is the case.</p><p>(You do not need to register in order to download or use code from this site — only to host your own code here.)</p>' + + field_no_tags: No tags +
--- a/config/locales/es.yml Tue Jan 14 14:37:42 2014 +0000 +++ b/config/locales/es.yml Wed Jan 15 09:59:14 2014 +0000 @@ -465,6 +465,7 @@ label_help: Ayuda label_history: Histórico label_home: Inicio + label_home_heading: Inicio label_in: en label_in_less_than: en menos que label_in_more_than: en más que
--- a/config/locales/eu.yml Tue Jan 14 14:37:42 2014 +0000 +++ b/config/locales/eu.yml Wed Jan 15 09:59:14 2014 +0000 @@ -460,6 +460,7 @@ label_login_with_open_id_option: edo OpenID-rekin saioa hasi label_password_lost: Pasahitza galduta label_home: Hasiera + label_home_heading: Hasiera label_my_page: Nire orria label_my_account: Nire kontua label_my_projects: Nire proiektuak
--- a/config/locales/fi.yml Tue Jan 14 14:37:42 2014 +0000 +++ b/config/locales/fi.yml Wed Jan 15 09:59:14 2014 +0000 @@ -365,6 +365,7 @@ label_register: Rekisteröidy label_password_lost: Hukattu salasana label_home: Koti + label_home_heading: Koti label_my_page: Omasivu label_my_account: Oma tili label_my_projects: Omat projektit
--- a/config/locales/fr.yml Tue Jan 14 14:37:42 2014 +0000 +++ b/config/locales/fr.yml Wed Jan 15 09:59:14 2014 +0000 @@ -537,6 +537,7 @@ label_login_with_open_id_option: S'authentifier avec OpenID label_password_lost: Mot de passe perdu label_home: Accueil + label_home_heading: Accueil label_my_page: Ma page label_my_account: Mon compte label_my_projects: Mes projets
--- a/config/locales/gl.yml Tue Jan 14 14:37:42 2014 +0000 +++ b/config/locales/gl.yml Wed Jan 15 09:59:14 2014 +0000 @@ -440,6 +440,7 @@ label_help: Axuda label_history: Histórico label_home: Inicio + label_home_heading: Inicio label_in: en label_in_less_than: en menos que label_in_more_than: en mais que
--- a/config/locales/he.yml Tue Jan 14 14:37:42 2014 +0000 +++ b/config/locales/he.yml Wed Jan 15 09:59:14 2014 +0000 @@ -481,6 +481,7 @@ label_login_with_open_id_option: או התחבר באמצעות OpenID label_password_lost: אבדה הסיסמה? label_home: דף הבית + label_home_heading: דף הבית label_my_page: הדף שלי label_my_account: החשבון שלי label_my_projects: הפרויקטים שלי
--- a/config/locales/hr.yml Tue Jan 14 14:37:42 2014 +0000 +++ b/config/locales/hr.yml Wed Jan 15 09:59:14 2014 +0000 @@ -452,6 +452,7 @@ label_login_with_open_id_option: or login with OpenID label_password_lost: Izgubljena zaporka label_home: Početna stranica + label_home_heading: Početna stranica label_my_page: Moja stranica label_my_account: Moj profil label_my_projects: Moji projekti
--- a/config/locales/hu.yml Tue Jan 14 14:37:42 2014 +0000 +++ b/config/locales/hu.yml Wed Jan 15 09:59:14 2014 +0000 @@ -381,6 +381,7 @@ label_register: Regisztráljon label_password_lost: Elfelejtett jelszó label_home: Kezdőlap + label_home_heading: Kezdőlap label_my_page: Saját kezdőlapom label_my_account: Fiókom adatai label_my_projects: Saját projektem
--- a/config/locales/id.yml Tue Jan 14 14:37:42 2014 +0000 +++ b/config/locales/id.yml Wed Jan 15 09:59:14 2014 +0000 @@ -445,6 +445,7 @@ label_login_with_open_id_option: atau login menggunakan OpenID label_password_lost: Lupa password label_home: Halaman depan + label_home_heading: Halaman depan label_my_page: Beranda label_my_account: Akun saya label_my_projects: Proyek saya
--- a/config/locales/it.yml Tue Jan 14 14:37:42 2014 +0000 +++ b/config/locales/it.yml Wed Jan 15 09:59:14 2014 +0000 @@ -325,6 +325,7 @@ label_register: Registrati label_password_lost: Password dimenticata label_home: Home + label_home_heading: Home label_my_page: Pagina personale label_my_account: Il mio utente label_my_projects: I miei progetti
--- a/config/locales/ja.yml Tue Jan 14 14:37:42 2014 +0000 +++ b/config/locales/ja.yml Wed Jan 15 09:59:14 2014 +0000 @@ -524,6 +524,7 @@ label_login_with_open_id_option: またはOpenIDでログインする label_password_lost: パスワードの再発行 label_home: ホーム + label_home_heading: ホーム label_my_page: マイページ label_my_account: 個人設定 label_my_projects: マイプロジェクト
--- a/config/locales/ko.yml Tue Jan 14 14:37:42 2014 +0000 +++ b/config/locales/ko.yml Wed Jan 15 09:59:14 2014 +0000 @@ -483,6 +483,7 @@ label_login_with_open_id_option: 또는 OpenID로 로그인 label_password_lost: 비밀번호 찾기 label_home: 초기화면 + label_home_heading: 초기화면 label_my_page: 내 페이지 label_my_account: 내 계정 label_my_projects: 내 프로젝트
--- a/config/locales/lt.yml Tue Jan 14 14:37:42 2014 +0000 +++ b/config/locales/lt.yml Wed Jan 15 09:59:14 2014 +0000 @@ -565,6 +565,7 @@ label_login_with_open_id_option: arba prisijunkite su OpenID label_password_lost: Prarastas slaptažodis label_home: Pagrindinis + label_home_heading: Pagrindinis label_my_page: Mano puslapis label_my_account: Mano paskyra label_my_projects: Mano projektai
--- a/config/locales/lv.yml Tue Jan 14 14:37:42 2014 +0000 +++ b/config/locales/lv.yml Wed Jan 15 09:59:14 2014 +0000 @@ -455,6 +455,7 @@ label_login_with_open_id_option: vai pieslēgties ar OpenID label_password_lost: Nozaudēta parole label_home: Sākums + label_home_heading: Sākums label_my_page: Mana lapa label_my_account: Mans konts label_my_projects: Mani projekti
--- a/config/locales/mk.yml Tue Jan 14 14:37:42 2014 +0000 +++ b/config/locales/mk.yml Wed Jan 15 09:59:14 2014 +0000 @@ -473,6 +473,7 @@ label_login_with_open_id_option: или најави се со OpenID label_password_lost: Изгубена лозинка label_home: Почетна + label_home_heading: Почетна label_my_page: Мојата страна label_my_account: Мојот профил label_my_projects: Мои проекти
--- a/config/locales/mn.yml Tue Jan 14 14:37:42 2014 +0000 +++ b/config/locales/mn.yml Wed Jan 15 09:59:14 2014 +0000 @@ -461,6 +461,7 @@ label_login_with_open_id_option: or login with OpenID label_password_lost: Нууц үгээ алдсан label_home: Нүүр + label_home_heading: Нүүр label_my_page: Миний хуудас label_my_account: Миний данс label_my_projects: Миний төслүүд
--- a/config/locales/nl.yml Tue Jan 14 14:37:42 2014 +0000 +++ b/config/locales/nl.yml Wed Jan 15 09:59:14 2014 +0000 @@ -411,6 +411,7 @@ label_help: Help label_history: Geschiedenis label_home: Home + label_home_heading: Home label_in: in label_in_less_than: in minder dan label_in_more_than: in meer dan
--- a/config/locales/no.yml Tue Jan 14 14:37:42 2014 +0000 +++ b/config/locales/no.yml Wed Jan 15 09:59:14 2014 +0000 @@ -352,6 +352,7 @@ label_register: Registrer label_password_lost: Mistet passord label_home: Hjem + label_home_heading: Hjem label_my_page: Min side label_my_account: Min konto label_my_projects: Mine prosjekter
--- a/config/locales/pl.yml Tue Jan 14 14:37:42 2014 +0000 +++ b/config/locales/pl.yml Wed Jan 15 09:59:14 2014 +0000 @@ -443,6 +443,7 @@ label_help: Pomoc label_history: Historia label_home: Główna + label_home_heading: Główna label_in: w label_in_less_than: mniejsze niż label_in_more_than: większe niż
--- a/config/locales/pt-BR.yml Tue Jan 14 14:37:42 2014 +0000 +++ b/config/locales/pt-BR.yml Wed Jan 15 09:59:14 2014 +0000 @@ -391,6 +391,7 @@ label_register: Cadastre-se label_password_lost: Perdi minha senha label_home: Página inicial + label_home_heading: Página inicial label_my_page: Minha página label_my_account: Minha conta label_my_projects: Meus projetos
--- a/config/locales/pt.yml Tue Jan 14 14:37:42 2014 +0000 +++ b/config/locales/pt.yml Wed Jan 15 09:59:14 2014 +0000 @@ -375,6 +375,7 @@ label_register: Registar label_password_lost: Perdi a palavra-chave label_home: Página Inicial + label_home_heading: Página Inicial label_my_page: Página Pessoal label_my_account: Minha conta label_my_projects: Meus projectos
--- a/config/locales/ro.yml Tue Jan 14 14:37:42 2014 +0000 +++ b/config/locales/ro.yml Wed Jan 15 09:59:14 2014 +0000 @@ -423,6 +423,7 @@ label_login_with_open_id_option: sau autentificare cu OpenID label_password_lost: Parolă uitată label_home: Acasă + label_home_heading: Acasă label_my_page: Pagina mea label_my_account: Contul meu label_my_projects: Proiectele mele
--- a/config/locales/ru.yml Tue Jan 14 14:37:42 2014 +0000 +++ b/config/locales/ru.yml Wed Jan 15 09:59:14 2014 +0000 @@ -516,6 +516,7 @@ label_help: Помощь label_history: История label_home: Домашняя страница + label_home_heading: Домашняя страница label_incoming_emails: Приём сообщений label_index_by_date: История страниц label_index_by_title: Оглавление
--- a/config/locales/sl.yml Tue Jan 14 14:37:42 2014 +0000 +++ b/config/locales/sl.yml Wed Jan 15 09:59:14 2014 +0000 @@ -417,6 +417,7 @@ label_register: Registracija label_password_lost: Spremeni geslo label_home: Domov + label_home_heading: Domov label_my_page: Moja stran label_my_account: Moj račun label_my_projects: Moji projekti
--- a/config/locales/sr-YU.yml Tue Jan 14 14:37:42 2014 +0000 +++ b/config/locales/sr-YU.yml Wed Jan 15 09:59:14 2014 +0000 @@ -473,6 +473,7 @@ label_login_with_open_id_option: ili prijava sa OpenID label_password_lost: Izgubljena lozinka label_home: Početak + label_home_heading: Početak label_my_page: Moja stranica label_my_account: Moj nalog label_my_projects: Moji projekti
--- a/config/locales/sr.yml Tue Jan 14 14:37:42 2014 +0000 +++ b/config/locales/sr.yml Wed Jan 15 09:59:14 2014 +0000 @@ -471,6 +471,7 @@ label_login_with_open_id_option: или пријава са OpenID label_password_lost: Изгубљена лозинка label_home: Почетак + label_home_heading: Почетак label_my_page: Моја страница label_my_account: Мој налог label_my_projects: Моји пројекти
--- a/config/locales/sv.yml Tue Jan 14 14:37:42 2014 +0000 +++ b/config/locales/sv.yml Wed Jan 15 09:59:14 2014 +0000 @@ -580,6 +580,7 @@ label_login_with_open_id_option: eller logga in med OpenID label_password_lost: Glömt lösenord label_home: Hem + label_home_heading: Hem label_my_page: Min sida label_my_account: Mitt konto label_my_projects: Mina projekt
--- a/config/locales/th.yml Tue Jan 14 14:37:42 2014 +0000 +++ b/config/locales/th.yml Wed Jan 15 09:59:14 2014 +0000 @@ -357,6 +357,7 @@ label_register: ลงทะเบียน label_password_lost: ลืมรหัสผ่าน label_home: หน้าแรก + label_home_heading: หน้าแรก label_my_page: หน้าของฉัน label_my_account: บัญชีของฉัน label_my_projects: โครงการของฉัน
--- a/config/locales/tr.yml Tue Jan 14 14:37:42 2014 +0000 +++ b/config/locales/tr.yml Wed Jan 15 09:59:14 2014 +0000 @@ -382,6 +382,7 @@ label_register: Kayıt label_password_lost: Parolamı unuttum label_home: Anasayfa + label_home_heading: Anasayfa label_my_page: Kişisel Sayfam label_my_account: Hesabım label_my_projects: Projelerim
--- a/config/locales/uk.yml Tue Jan 14 14:37:42 2014 +0000 +++ b/config/locales/uk.yml Wed Jan 15 09:59:14 2014 +0000 @@ -332,6 +332,7 @@ label_register: Зареєструватися label_password_lost: Забули пароль label_home: Домашня сторінка + label_home_heading: Домашня сторінка label_my_page: Моя сторінка label_my_account: Мій обліковий запис label_my_projects: Мої проекти
--- a/config/locales/vi.yml Tue Jan 14 14:37:42 2014 +0000 +++ b/config/locales/vi.yml Wed Jan 15 09:59:14 2014 +0000 @@ -419,6 +419,7 @@ label_register: Đăng ký label_password_lost: Phục hồi mật mã label_home: Trang chính + label_home_heading: Trang chính label_my_page: Trang riêng label_my_account: Cá nhân label_my_projects: Dự án của bạn
--- a/config/locales/zh-TW.yml Tue Jan 14 14:37:42 2014 +0000 +++ b/config/locales/zh-TW.yml Wed Jan 15 09:59:14 2014 +0000 @@ -627,6 +627,7 @@ label_login_with_open_id_option: 或使用 OpenID 登入 label_password_lost: 遺失密碼 label_home: 網站首頁 + label_home_heading: 網站首頁 label_my_page: 帳戶首頁 label_my_account: 我的帳戶 label_my_projects: 我的專案
--- a/config/locales/zh.yml Tue Jan 14 14:37:42 2014 +0000 +++ b/config/locales/zh.yml Wed Jan 15 09:59:14 2014 +0000 @@ -488,6 +488,7 @@ label_login_with_open_id_option: 或使用OpenID登录 label_password_lost: 忘记密码 label_home: 主页 + label_home_heading: 主页 label_my_page: 我的工作台 label_my_account: 我的帐号 label_my_projects: 我的项目
--- a/config/routes.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/config/routes.rb Wed Jan 15 09:59:14 2014 +0000 @@ -89,6 +89,9 @@ post 'issues/:object_id/watchers', :to => 'watchers#create', :object_type => 'issue' delete 'issues/:object_id/watchers/:user_id' => 'watchers#destroy', :object_type => 'issue' + match 'projects/:id/settings/:tab', :to => "projects#settings" + match 'projects/:id/overview', :to => "projects#overview" + resources :projects do member do get 'settings(/:tab)', :action => 'settings', :as => 'settings' @@ -108,6 +111,14 @@ end end + shallow do + resources :members, :controller => 'members', :only => [:index, :show, :new, :create, :update, :destroy] do + collection do + get 'autocomplete' + end + end + end + resource :enumerations, :controller => 'project_enumerations', :only => [:update, :destroy] get 'issues/:copy_from/copy', :to => 'issues#new', :as => 'copy_issue' @@ -189,6 +200,9 @@ end match '/issues', :controller => 'issues', :action => 'destroy', :via => :delete + # changed this route to ensure compatibility with Rails 3 --lf.20130109 + match 'explore' => 'projects#explore' + resources :queries, :except => [:show] resources :news, :only => [:index, :show, :edit, :update, :destroy] @@ -272,6 +286,7 @@ get 'attachments/:id/:filename', :to => 'attachments#show', :id => /\d+/, :filename => /.*/, :as => 'named_attachment' get 'attachments/download/:id/:filename', :to => 'attachments#download', :id => /\d+/, :filename => /.*/, :as => 'download_named_attachment' get 'attachments/download/:id', :to => 'attachments#download', :id => /\d+/ + get 'attachments/toggle_active/:id', :to => 'attachments#toggle_active', :id => /\d+/ get 'attachments/thumbnail/:id(/:size)', :to => 'attachments#thumbnail', :id => /\d+/, :size => /\d+/, :as => 'thumbnail' resources :attachments, :only => [:show, :destroy] @@ -336,6 +351,8 @@ match 'sys/projects', :to => 'sys#projects', :via => :get match 'sys/projects/:id/repository', :to => 'sys#create_project_repository', :via => :post + match 'sys/projects/:id/repository_cache.:format', :controller => 'sys', :action => 'clear_repository_cache', :conditions => { :method => :post } + match 'sys/projects/:id/embedded.:format', :controller => 'sys', :action => 'set_embedded_active', :conditions => { :method => :post } match 'sys/fetch_changesets', :to => 'sys#fetch_changesets', :via => :get match 'uploads', :to => 'attachments#upload', :via => :post
--- a/config/settings.yml Tue Jan 14 14:37:42 2014 +0000 +++ b/config/settings.yml Wed Jan 15 09:59:14 2014 +0000 @@ -25,6 +25,10 @@ default: Project management welcome_text: default: +tipoftheday_text: + default: +notifications_text: + default: login_required: default: 0 self_registration:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/console/db/seeds.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,9 @@ +# This file should contain all the record creation needed to seed the database with its default values. +# The data can then be loaded with the rake db:seed (or created alongside the db with db:setup). +# +# Examples: +# +# cities = City.create([{ :name => 'Chicago' }, { :name => 'Copenhagen' }]) +# Major.create(:name => 'Daley', :city => cities.first) + +puts "this is a test" \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/db/migrate/20101123161346_create_ssamr_user_details.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,14 @@ +class CreateSsamrUserDetails < ActiveRecord::Migration + def self.up + create_table :ssamr_user_details do |t| + t.integer :user_id + t.text :description + t.text :university + end + end + + def self.down + drop_table :ssamr_user_details + end + +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/db/migrate/20101216140621_create_institutions.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,13 @@ +class CreateInstitutions < ActiveRecord::Migration + def self.up + create_table :institutions do |t| + t.string :name + + t.timestamps + end + end + + def self.down + drop_table :institutions + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/db/migrate/20101216145813_fix_university_name_in_ssamr_details_table.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,10 @@ +class FixUniversityNameInSsamrDetailsTable < ActiveRecord::Migration + def self.up + rename_column :ssamr_user_details, :university, :institution_id + end + + def self.down + # there's no need to rollback the name of this column + # because it was not used previously + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/db/migrate/20110126153504_add_other_institution_column_to_ssamr_user_details.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,9 @@ +class AddOtherInstitutionColumnToSsamrUserDetails < ActiveRecord::Migration + def self.up + add_column :ssamr_user_details, :other_institution, :string + end + + def self.down + remove_column :ssamr_user_details, :other_institution + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/db/migrate/20110127161758_add_institution_type_column_to_ssamr_user_details.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,9 @@ +class AddInstitutionTypeColumnToSsamrUserDetails < ActiveRecord::Migration + def self.up + add_column :ssamr_user_details, :institution_type, :boolean + end + + def self.down + remove_column :ssamr_user_details, :institution_type + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/db/migrate/20110202170156_add_order_column_to_institutions.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,9 @@ +class AddOrderColumnToInstitutions < ActiveRecord::Migration + def self.up + add_column :institutions, :order, :integer + end + + def self.down + remove_column :institutions, :order + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/db/migrate/20110207142856_add_ext_rep_to_repositories.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,11 @@ +class AddExtRepToRepositories < ActiveRecord::Migration + def self.up + add_column :repositories, :is_external, :bool + add_column :repositories, :external_url, :string + end + + def self.down + remove_column :repositories, :is_external + remove_column :repositories, :external_url + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/db/migrate/20110303152903_add_active_column_to_attachments.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,9 @@ +class AddActiveColumnToAttachments < ActiveRecord::Migration + def self.up + add_column :attachments, :active, :boolean + end + + def self.down + remove_column :attachments, :active + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/db/migrate/20110331152140_add_has_welcome_page_to_projects.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,9 @@ +class AddHasWelcomePageToProjects < ActiveRecord::Migration + def self.up + add_column :projects, :has_welcome_page, :boolean + end + + def self.down + remove_column :projects, :has_welcome_page + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/db/seed_data/institutions.txt Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,141 @@ +University of Aberdeen|1 +University of Abertay Dundee |2 +Aberystwyth University|3 +Anglia Ruskin University|4 +University of the Arts London|5 +Aston University|6 +Bangor University|7 +University of Bath|8 +Bath Spa University|9 +University of Bedfordshire|10 +Birkbeck, University of London|11 +University of Birmingham|12 +Birmingham City University|13 +Birmingham Conservatoire|14 +University Centre at Blackburn College|15 +University of Bolton|16 +Bournemouth University|17 +University of Bradford|18 +University of Brighton|19 +University of Bristol|20 +Brunel University|21 +University of Buckingham|22 +Buckinghamshire New University|23 +University of Cambridge|24 +Institute of Cancer Research, University of London|25 +Canterbury Christ Church University|26 +Cardiff University|27 +University of Central Lancashire|28 +Central School of Speech and Drama, University of London|29 +University of Chester|30 +University of Chichester|31 +City University London|32 +Courtauld Institute of Art|33 +Coventry University|34 +Cranfield University|35 +University for the Creative Arts|36 +University of Cumbria|37 +De Montfort University|38 +University of Derby|39 +University of Dundee|40 +Durham University|41 +University of East Anglia|42 +University of East London|43 +Edge Hill University|44 +University of Edinburgh|45 +Edinburgh Napier University|46 +Institute of Education, University of London|47 +University of Essex|48 +University of Exeter|49 +University of Glamorgan|50 +University of Glasgow|51 +Glasgow Caledonian University|52 +University of Gloucestershire|53 +Goldsmiths, University of London|54 +University of Greenwich|55 +Glyndŵr University|56 +Heriot-Watt University|57 +University of Hertfordshire|58 +Heythrop College|59 +University of Huddersfield|60 +University of Hull|61 +Hull York Medical School|62 +Imperial College London|63 +Keele University|64 +University of Kent|65 +King's College London|66 +Kingston University|67 +Lancaster University|68 +University of Leeds|69 +Leeds Metropolitan University|70 +University of Leicester|71 +University of Lincoln|72 +University of Liverpool|73 +Liverpool Hope University|74 +Liverpool John Moores University|75 +London Business School|76 +London College of Music|77 +London Metropolitan University|78 +London School of Economics and Political Science|79 +London School of Hygiene and Tropical Medicine|80 +London South Bank University|81 +Loughborough University|82 +University of Manchester|83 +Manchester Metropolitan University|84 +Middlesex University|85 +Newcastle University|86 +University of Northampton|87 +Northumbria University|88 +University of Nottingham|89 +Nottingham Trent University|90 +The Open University|91 +University of Oxford|92 +Oxford Brookes University|93 +Peninsula College of Medicine and Dentistry|94 +University of Plymouth|95 +University of Portsmouth|96 +Queen's University Belfast|97 +Queen Margaret University|98 +Queen Mary, University of London|99 +University of Reading|100 +The Robert Gordon University, Aberdeen|101 +Roehampton University|102 +Royal Academy of Music|103 +Royal College of Art|104 +Royal Holloway, University of London|105 +Royal Veterinary College|106 +University of St Andrews|107 +St George's, University of London|108 +University of Salford|109 +School of Advanced Study, University of London|110 +School of Oriental and African Studies|111 +School of Pharmacy, University of London|112 +University of Sheffield|113 +Sheffield Hallam University|114 +University of Southampton|115 +Southampton Solent University|116 +Staffordshire University|117 +University of Stirling|118 +University of Strathclyde|119 +University of Sunderland|120 +University of Surrey|121 +University of Sussex|122 +Swansea Metropolitan University|123 +Swansea University|124 +University of Teesside|125 +Thames Valley University|126 +University of Ulster|127 +University College London|128 +University of Wales|129 +University of Wales Institute, Cardiff|130 +University of Wales, Newport|131 +University of Wales, Trinity Saint David|132 +University of Warwick|133 +University of Westminster|134 +University of the West of England|135 +University of the West of Scotland|136 +University of Winchester|137 +University of Wolverhampton|138 +University of Worcester|139 +University of York|140 +York St John University|141
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/db/seeds.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,27 @@ +# This file should contain all the record creation needed to seed the database with its default values. +# The data can then be loaded with the rake db:seed (or created alongside the db with db:setup). +# +# Examples: +# +# cities = City.create([{ :name => 'Chicago' }, { :name => 'Copenhagen' }]) +# Major.create(:name => 'Daley', :city => cities.first) + +def truncate_table(table_name) + quoted = connection.quote_table_name(table_name) + connection.execute("TRUNCATE #{quoted}") +end + +def connection + ActiveRecord::Base.connection +end + +truncate_table('institutions') + +open("db/seed_data/institutions.txt") do |institutions| + institutions.read.each_line do |institution| + inst=institution.split('|') + + + Institution.create(:name => inst[0].chomp, :order => inst[1].chomp) + end +end \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/extra/mercurial/redminehelper.py Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,91 @@ +# redminehelper: Redmine helper extension for Mercurial +# it's a draft to show a possible way to explore repository by the Redmine overhaul patch +# see: http://www.redmine.org/issues/4455 +# +# Copyright 2010 Alessio Franceschelli (alefranz.net) +# Copyright 2010 Yuya Nishihara <yuya@tcha.org> +# +# This software may be used and distributed according to the terms of the +# GNU General Public License version 2 or any later version. + +'''command to list revision of each file +''' + +import re, time +from mercurial import cmdutil, commands, node, error + +SPECIAL_TAGS = ('tip',) + +def rhsummary(ui, repo, **opts): + """output the summary of the repository""" + # see mercurial/commands.py:tip + ui.write(':tip: rev node\n') + tipctx = repo[len(repo) - 1] + ui.write('%d %s\n' % (tipctx.rev(), tipctx)) + + # see mercurial/commands.py:root + ui.write(':root: path\n') + ui.write(repo.root + '\n') + + # see mercurial/commands.py:tags + ui.write(':tags: rev node name\n') + for t, n in reversed(repo.tagslist()): + if t in SPECIAL_TAGS: + continue + try: + r = repo.changelog.rev(n) + except error.LookupError: + r = -1 + ui.write('%d %s %s\n' % (r, node.short(n), t)) + + # see mercurial/commands.py:branches + def iterbranches(): + for t, n in repo.branchtags().iteritems(): + yield t, n, repo.changelog.rev(n) + + ui.write(':branches: rev node name\n') + for t, n, r in sorted(iterbranches(), key=lambda e: e[2], reverse=True): + if repo.lookup(r) in repo.branchheads(t, closed=False): + ui.write('%d %s %s\n' % (r, node.short(n), t)) # only open branch + +def rhentries(ui, repo, path='', **opts): + """output the entries of the specified directory""" + rev = opts.get('rev') + pathprefix = (path.rstrip('/') + '/').lstrip('/') + + # TODO: clean up + dirs, files = {}, {} + mf = repo[rev].manifest() + for f in repo[rev]: + if not f.startswith(pathprefix): + continue + + name = re.sub(r'/.*', '', f[len(pathprefix):]) + if '/' in f[len(pathprefix):]: + dirs[name] = (name,) + else: + try: + fctx = repo.filectx(f, fileid=mf[f]) + ctx = fctx.changectx() + tm, tzoffset = ctx.date() + localtime = int(tm) + tzoffset - time.timezone + files[name] = (ctx.rev(), node.short(ctx.node()), localtime, + fctx.size(), name) + except LookupError: # TODO: when this occurs? + pass + + ui.write(':dirs: name\n') + for n, v in sorted(dirs.iteritems(), key=lambda e: e[0]): + ui.write(' '.join(v) + '\n') + + ui.write(':files: rev node time size name\n') + for n, v in sorted(files.iteritems(), key=lambda e: e[0]): + ui.write(' '.join(str(e) for e in v) + '\n') + + +cmdtable = { + 'rhsummary': (rhsummary, [], 'hg rhsummary'), + 'rhentries': (rhentries, + [('r', 'rev', '', 'show the specified revision')], + 'hg rhentries [path]'), +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/extra/soundsoftware/SoundSoftware-unsalted.pm Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,469 @@ +package Apache::Authn::SoundSoftware; + +=head1 Apache::Authn::SoundSoftware + +SoundSoftware - a mod_perl module for Apache authentication against a +Redmine database and optional LDAP implementing the access control +rules required for the SoundSoftware.ac.uk repository site. + +=head1 SYNOPSIS + +This module is closely based on the Redmine.pm authentication module +provided with Redmine. It is intended to be used for authentication +in front of a repository service such as hgwebdir. + +Requirements: + +1. Clone/pull from repo for public project: Any user, no +authentication required + +2. Clone/pull from repo for private project: Project members only + +3. Push to repo for public project: "Permitted" users only (this +probably means project members who are also identified in the hgrc web +section for the repository and so will be approved by hgwebdir?) + +4. Push to repo for private project: "Permitted" users only (as above) + +5. Push to any repo that is tracking an external repo: Refused always + +=head1 INSTALLATION + +Debian/ubuntu: + + apt-get install libapache-dbi-perl libapache2-mod-perl2 \ + libdbd-mysql-perl libauthen-simple-ldap-perl libio-socket-ssl-perl + +Note that LDAP support is hardcoded "on" in this script (it is +optional in the original Redmine.pm). + +=head1 CONFIGURATION + + ## This module has to be in your perl path + ## eg: /usr/local/lib/site_perl/Apache/Authn/SoundSoftware.pm + PerlLoadModule Apache::Authn::SoundSoftware + + # Example when using hgwebdir + ScriptAlias / "/var/hg/hgwebdir.cgi/" + + <Location /> + AuthName "Mercurial" + AuthType Basic + Require valid-user + PerlAccessHandler Apache::Authn::SoundSoftware::access_handler + PerlAuthenHandler Apache::Authn::SoundSoftware::authen_handler + SoundSoftwareDSN "DBI:mysql:database=redmine;host=localhost" + SoundSoftwareDbUser "redmine" + SoundSoftwareDbPass "password" + Options +ExecCGI + AddHandler cgi-script .cgi + ## Optional where clause (fulltext search would be slow and + ## database dependant). + # SoundSoftwareDbWhereClause "and members.role_id IN (1,2)" + ## Optional prefix for local repository URLs + # SoundSoftwareRepoPrefix "/var/hg/" + </Location> + +See the original Redmine.pm for further configuration notes. + +=cut + +use strict; +use warnings FATAL => 'all', NONFATAL => 'redefine'; + +use DBI; +use Digest::SHA1; +use Authen::Simple::LDAP; +use Apache2::Module; +use Apache2::Access; +use Apache2::ServerRec qw(); +use Apache2::RequestRec qw(); +use Apache2::RequestUtil qw(); +use Apache2::Const qw(:common :override :cmd_how); +use APR::Pool (); +use APR::Table (); + +my @directives = ( + { + name => 'SoundSoftwareDSN', + req_override => OR_AUTHCFG, + args_how => TAKE1, + errmsg => 'Dsn in format used by Perl DBI. eg: "DBI:Pg:dbname=databasename;host=my.db.server"', + }, + { + name => 'SoundSoftwareDbUser', + req_override => OR_AUTHCFG, + args_how => TAKE1, + }, + { + name => 'SoundSoftwareDbPass', + req_override => OR_AUTHCFG, + args_how => TAKE1, + }, + { + name => 'SoundSoftwareDbWhereClause', + req_override => OR_AUTHCFG, + args_how => TAKE1, + }, + { + name => 'SoundSoftwareRepoPrefix', + req_override => OR_AUTHCFG, + args_how => TAKE1, + }, +); + +sub SoundSoftwareDSN { + my ($self, $parms, $arg) = @_; + $self->{SoundSoftwareDSN} = $arg; + my $query = "SELECT + hashed_password, auth_source_id, permissions + FROM members, projects, users, roles, member_roles + WHERE + projects.id=members.project_id + AND member_roles.member_id=members.id + AND users.id=members.user_id + AND roles.id=member_roles.role_id + AND users.status=1 + AND login=? + AND identifier=? "; + $self->{SoundSoftwareQuery} = trim($query); +} + +sub SoundSoftwareDbUser { set_val('SoundSoftwareDbUser', @_); } +sub SoundSoftwareDbPass { set_val('SoundSoftwareDbPass', @_); } +sub SoundSoftwareDbWhereClause { + my ($self, $parms, $arg) = @_; + $self->{SoundSoftwareQuery} = trim($self->{SoundSoftwareQuery}.($arg ? $arg : "")." "); +} + +sub SoundSoftwareRepoPrefix { + my ($self, $parms, $arg) = @_; + if ($arg) { + $self->{SoundSoftwareRepoPrefix} = $arg; + } +} + +sub trim { + my $string = shift; + $string =~ s/\s{2,}/ /g; + return $string; +} + +sub set_val { + my ($key, $self, $parms, $arg) = @_; + $self->{$key} = $arg; +} + +Apache2::Module::add(__PACKAGE__, \@directives); + + +my %read_only_methods = map { $_ => 1 } qw/GET PROPFIND REPORT OPTIONS/; + +sub access_handler { + my $r = shift; + + print STDERR "SoundSoftware.pm: In access handler at " . scalar localtime() . "\n"; + + unless ($r->some_auth_required) { + $r->log_reason("No authentication has been configured"); + return FORBIDDEN; + } + + my $method = $r->method; + + print STDERR "SoundSoftware.pm: Method: $method, uri " . $r->uri . ", location " . $r->location . "\n"; + print STDERR "SoundSoftware.pm: Accept: " . $r->headers_in->{Accept} . "\n"; + + my $dbh = connect_database($r); + unless ($dbh) { + print STDERR "SoundSoftware.pm: Database connection failed!: " . $DBI::errstr . "\n"; + return FORBIDDEN; + } + + print STDERR "Connected to db, dbh is " . $dbh . "\n"; + + my $project_id = get_project_identifier($dbh, $r); + + if (!defined $read_only_methods{$method}) { + print STDERR "SoundSoftware.pm: Method is not read-only\n"; + if (project_repo_is_readonly($dbh, $project_id, $r)) { + print STDERR "SoundSoftware.pm: Project repo is read-only, refusing access\n"; + return FORBIDDEN; + } else { + print STDERR "SoundSoftware.pm: Project repo is read-write, authentication handler required\n"; + return OK; + } + } + + my $status = get_project_status($dbh, $project_id, $r); + + $dbh->disconnect(); + undef $dbh; + + if ($status == 0) { # nonexistent + print STDERR "SoundSoftware.pm: Project does not exist, refusing access\n"; + return FORBIDDEN; + } elsif ($status == 1) { # public + print STDERR "SoundSoftware.pm: Project is public, no restriction here\n"; + $r->set_handlers(PerlAuthenHandler => [\&OK]) + } else { # private + print STDERR "SoundSoftware.pm: Project is private, authentication handler required\n"; + } + + return OK +} + +sub authen_handler { + my $r = shift; + + print STDERR "SoundSoftware.pm: In authentication handler at " . scalar localtime() . "\n"; + + my $dbh = connect_database($r); + unless ($dbh) { + print STDERR "SoundSoftware.pm: Database connection failed!: " . $DBI::errstr . "\n"; + return AUTH_REQUIRED; + } + + my $project_id = get_project_identifier($dbh, $r); + my $realm = get_realm($dbh, $project_id, $r); + $r->auth_name($realm); + + my ($res, $redmine_pass) = $r->get_basic_auth_pw(); + unless ($res == OK) { + $dbh->disconnect(); + undef $dbh; + return $res; + } + + print STDERR "SoundSoftware.pm: User is " . $r->user . ", got password\n"; + + my $permitted = is_permitted($dbh, $project_id, $r->user, $redmine_pass, $r); + + $dbh->disconnect(); + undef $dbh; + + if ($permitted) { + return OK; + } else { + print STDERR "SoundSoftware.pm: Not permitted\n"; + $r->note_auth_failure(); + return AUTH_REQUIRED; + } +} + +sub get_project_status { + my $dbh = shift; + my $project_id = shift; + my $r = shift; + + if (!defined $project_id or $project_id eq '') { + return 0; # nonexistent + } + + my $sth = $dbh->prepare( + "SELECT is_public FROM projects WHERE projects.identifier = ?;" + ); + + $sth->execute($project_id); + my $ret = 0; # nonexistent + if (my @row = $sth->fetchrow_array) { + if ($row[0] eq "1" || $row[0] eq "t") { + $ret = 1; # public + } else { + $ret = 2; # private + } + } + $sth->finish(); + undef $sth; + + $ret; +} + +sub project_repo_is_readonly { + my $dbh = shift; + my $project_id = shift; + my $r = shift; + + if (!defined $project_id or $project_id eq '') { + return 0; # nonexistent + } + + my $sth = $dbh->prepare( + "SELECT repositories.is_external FROM repositories, projects WHERE projects.identifier = ? AND repositories.project_id = projects.id;" + ); + + $sth->execute($project_id); + my $ret = 0; # nonexistent + if (my @row = $sth->fetchrow_array) { + if (defined($row[0]) && ($row[0] eq "1" || $row[0] eq "t")) { + $ret = 1; # read-only (i.e. external) + } else { + $ret = 0; # read-write + } + } + $sth->finish(); + undef $sth; + + $ret; +} + +sub is_permitted { + my $dbh = shift; + my $project_id = shift; + my $redmine_user = shift; + my $redmine_pass = shift; + my $r = shift; + + my $pass_digest = Digest::SHA1::sha1_hex($redmine_pass); + + my $cfg = Apache2::Module::get_config + (__PACKAGE__, $r->server, $r->per_dir_config); + + my $query = $cfg->{SoundSoftwareQuery}; + my $sth = $dbh->prepare($query); + $sth->execute($redmine_user, $project_id); + + my $ret; + while (my ($hashed_password, $auth_source_id, $permissions) = $sth->fetchrow_array) { + + # Test permissions for this user before we verify credentials + # -- if the user is not permitted this action anyway, there's + # not much point in e.g. contacting the LDAP + + my $method = $r->method; + + if ((defined $read_only_methods{$method} && $permissions =~ /:browse_repository/) + || $permissions =~ /:commit_access/) { + + # User would be permitted this action, if their + # credentials checked out -- test those now + + print STDERR "SoundSoftware.pm: User $redmine_user has required role, checking credentials\n"; + + unless ($auth_source_id) { + if ($hashed_password eq $pass_digest) { + print STDERR "SoundSoftware.pm: User $redmine_user authenticated via password\n"; + $ret = 1; + last; + } + } else { + my $sthldap = $dbh->prepare( + "SELECT host,port,tls,account,account_password,base_dn,attr_login FROM auth_sources WHERE id = ?;" + ); + $sthldap->execute($auth_source_id); + while (my @rowldap = $sthldap->fetchrow_array) { + my $ldap = Authen::Simple::LDAP->new( + host => ($rowldap[2] eq "1" || $rowldap[2] eq "t") ? "ldaps://$rowldap[0]" : $rowldap[0], + port => $rowldap[1], + basedn => $rowldap[5], + binddn => $rowldap[3] ? $rowldap[3] : "", + bindpw => $rowldap[4] ? $rowldap[4] : "", + filter => "(".$rowldap[6]."=%s)" + ); + if ($ldap->authenticate($redmine_user, $redmine_pass)) { + print STDERR "SoundSoftware.pm: User $redmine_user authenticated via LDAP\n"; + $ret = 1; + } + } + $sthldap->finish(); + undef $sthldap; + } + } else { + print STDERR "SoundSoftware.pm: User $redmine_user lacks required role for this project\n"; + } + } + + $sth->finish(); + undef $sth; + + $ret; +} + +sub get_project_identifier { + my $dbh = shift; + my $r = shift; + + my $location = $r->location; + my ($repo) = $r->uri =~ m{$location/*([^/]+)}; + + return $repo if (!$repo); + + $repo =~ s/[^a-zA-Z0-9\._-]//g; + + # The original Redmine.pm returns the string just calculated as + # the project identifier. That won't do for us -- we may have + # (and in fact already do have, in our test instance) projects + # whose repository names differ from the project identifiers. + + # This is a rather fundamental change because it means that almost + # every request needs more than one database query -- which + # prompts us to start passing around $dbh instead of connecting + # locally within each function as is done in Redmine.pm. + + my $sth = $dbh->prepare( + "SELECT projects.identifier FROM projects, repositories WHERE repositories.project_id = projects.id AND repositories.url LIKE ?;" + ); + + my $cfg = Apache2::Module::get_config + (__PACKAGE__, $r->server, $r->per_dir_config); + + my $prefix = $cfg->{SoundSoftwareRepoPrefix}; + if (!defined $prefix) { $prefix = '%/'; } + + my $identifier = ''; + + $sth->execute($prefix . $repo); + my $ret = 0; + if (my @row = $sth->fetchrow_array) { + $identifier = $row[0]; + } + $sth->finish(); + undef $sth; + + print STDERR "SoundSoftware.pm: Repository '$repo' belongs to project '$identifier'\n"; + + $identifier; +} + +sub get_realm { + my $dbh = shift; + my $project_id = shift; + my $r = shift; + + my $sth = $dbh->prepare( + "SELECT projects.name FROM projects WHERE projects.identifier = ?;" + ); + + my $name = $project_id; + + $sth->execute($project_id); + my $ret = 0; + if (my @row = $sth->fetchrow_array) { + $name = $row[0]; + } + $sth->finish(); + undef $sth; + + # be timid about characters not permitted in auth realm and revert + # to project identifier if any are found + if ($name =~ m/[^\w\d\s\._-]/) { + $name = $project_id; + } + + my $realm = '"Mercurial repository for ' . "'$name'" . '"'; + + $realm; +} + +sub connect_database { + my $r = shift; + + my $cfg = Apache2::Module::get_config + (__PACKAGE__, $r->server, $r->per_dir_config); + + return DBI->connect($cfg->{SoundSoftwareDSN}, + $cfg->{SoundSoftwareDbUser}, + $cfg->{SoundSoftwareDbPass}); +} + +1;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/extra/soundsoftware/SoundSoftware.pm Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,571 @@ +package Apache::Authn::SoundSoftware; + +=head1 Apache::Authn::SoundSoftware + +SoundSoftware - a mod_perl module for Apache authentication against a +Redmine database and optional LDAP implementing the access control +rules required for the SoundSoftware.ac.uk repository site. + +=head1 SYNOPSIS + +This module is closely based on the Redmine.pm authentication module +provided with Redmine. It is intended to be used for authentication +in front of a repository service such as hgwebdir. + +Requirements: + +1. Clone/pull from repo for public project: Any user, no +authentication required + +2. Clone/pull from repo for private project: Project members only + +3. Push to repo for public project: "Permitted" users only (this +probably means project members who are also identified in the hgrc web +section for the repository and so will be approved by hgwebdir?) + +4. Push to repo for private project: "Permitted" users only (as above) + +5. Push to any repo that is tracking an external repo: Refused always + +=head1 INSTALLATION + +Debian/ubuntu: + + apt-get install libapache-dbi-perl libapache2-mod-perl2 \ + libdbd-mysql-perl libauthen-simple-ldap-perl libio-socket-ssl-perl + +Note that LDAP support is hardcoded "on" in this script (it is +optional in the original Redmine.pm). + +=head1 CONFIGURATION + + ## This module has to be in your perl path + ## eg: /usr/local/lib/site_perl/Apache/Authn/SoundSoftware.pm + PerlLoadModule Apache::Authn::SoundSoftware + + # Example when using hgwebdir + ScriptAlias / "/var/hg/hgwebdir.cgi/" + + <Location /> + AuthName "Mercurial" + AuthType Basic + Require valid-user + PerlAccessHandler Apache::Authn::SoundSoftware::access_handler + PerlAuthenHandler Apache::Authn::SoundSoftware::authen_handler + SoundSoftwareDSN "DBI:mysql:database=redmine;host=localhost" + SoundSoftwareDbUser "redmine" + SoundSoftwareDbPass "password" + Options +ExecCGI + AddHandler cgi-script .cgi + ## Optional where clause (fulltext search would be slow and + ## database dependant). + # SoundSoftwareDbWhereClause "and members.role_id IN (1,2)" + ## Optional prefix for local repository URLs + # SoundSoftwareRepoPrefix "/var/hg/" + </Location> + +See the original Redmine.pm for further configuration notes. + +=cut + +use strict; +use warnings FATAL => 'all', NONFATAL => 'redefine'; + +use DBI; +use Digest::SHA; +use Authen::Simple::LDAP; +use Apache2::Module; +use Apache2::Access; +use Apache2::ServerRec qw(); +use Apache2::RequestRec qw(); +use Apache2::RequestUtil qw(); +use Apache2::Const qw(:common :override :cmd_how); +use APR::Pool (); +use APR::Table (); + +my @directives = ( + { + name => 'SoundSoftwareDSN', + req_override => OR_AUTHCFG, + args_how => TAKE1, + errmsg => 'Dsn in format used by Perl DBI. eg: "DBI:Pg:dbname=databasename;host=my.db.server"', + }, + { + name => 'SoundSoftwareDbUser', + req_override => OR_AUTHCFG, + args_how => TAKE1, + }, + { + name => 'SoundSoftwareDbPass', + req_override => OR_AUTHCFG, + args_how => TAKE1, + }, + { + name => 'SoundSoftwareDbWhereClause', + req_override => OR_AUTHCFG, + args_how => TAKE1, + }, + { + name => 'SoundSoftwareRepoPrefix', + req_override => OR_AUTHCFG, + args_how => TAKE1, + }, + { + name => 'SoundSoftwareSslRequired', + req_override => OR_AUTHCFG, + args_how => TAKE1, + }, +); + +sub SoundSoftwareDSN { + my ($self, $parms, $arg) = @_; + $self->{SoundSoftwareDSN} = $arg; + my $query = "SELECT + hashed_password, salt, auth_source_id, permissions + FROM members, projects, users, roles, member_roles + WHERE + projects.id=members.project_id + AND member_roles.member_id=members.id + AND users.id=members.user_id + AND roles.id=member_roles.role_id + AND users.status=1 + AND login=? + AND identifier=? "; + $self->{SoundSoftwareQuery} = trim($query); +} + +sub SoundSoftwareDbUser { set_val('SoundSoftwareDbUser', @_); } +sub SoundSoftwareDbPass { set_val('SoundSoftwareDbPass', @_); } +sub SoundSoftwareDbWhereClause { + my ($self, $parms, $arg) = @_; + $self->{SoundSoftwareQuery} = trim($self->{SoundSoftwareQuery}.($arg ? $arg : "")." "); +} + +sub SoundSoftwareRepoPrefix { + my ($self, $parms, $arg) = @_; + if ($arg) { + $self->{SoundSoftwareRepoPrefix} = $arg; + } +} + +sub SoundSoftwareSslRequired { set_val('SoundSoftwareSslRequired', @_); } + +sub trim { + my $string = shift; + $string =~ s/\s{2,}/ /g; + return $string; +} + +sub set_val { + my ($key, $self, $parms, $arg) = @_; + $self->{$key} = $arg; +} + +Apache2::Module::add(__PACKAGE__, \@directives); + + +my %read_only_methods = map { $_ => 1 } qw/GET PROPFIND REPORT OPTIONS/; + +sub access_handler { + my $r = shift; + + print STDERR "SoundSoftware.pm:$$: In access handler at " . scalar localtime() . "\n"; + + unless ($r->some_auth_required) { + $r->log_reason("No authentication has been configured"); + return FORBIDDEN; + } + + my $method = $r->method; + + print STDERR "SoundSoftware.pm:$$: Method: $method, uri " . $r->uri . ", location " . $r->location . "\n"; + print STDERR "SoundSoftware.pm:$$: Accept: " . $r->headers_in->{Accept} . "\n"; + + my $dbh = connect_database($r); + unless ($dbh) { + print STDERR "SoundSoftware.pm:$$: Database connection failed!: " . $DBI::errstr . "\n"; + return FORBIDDEN; + } + + print STDERR "Connected to db, dbh is " . $dbh . "\n"; + + my $project_id = get_project_identifier($dbh, $r); + + # We want to delegate most of the work to the authentication + # handler (to ensure that user is asked to login even for + # nonexistent projects -- so they can't tell whether a private + # project exists or not without authenticating). So + # + # * if the project is public + # - if the method is read-only + # + set handler to OK, no auth needed + # - if the method is not read-only + # + if the repo is read-only, return forbidden + # + else require auth + # * if the project is not public or does not exist + # + require auth + # + # If we are requiring auth and are not currently https, and + # https is required, then we must return a redirect to https + # instead of an OK. + + my $status = get_project_status($dbh, $project_id, $r); + my $readonly = project_repo_is_readonly($dbh, $project_id, $r); + + $dbh->disconnect(); + undef $dbh; + + my $auth_ssl_reqd = will_require_ssl_auth($r); + + if ($status == 1) { # public + + print STDERR "SoundSoftware.pm:$$: Project is public\n"; + + if (!defined $read_only_methods{$method}) { + + print STDERR "SoundSoftware.pm:$$: Method is not read-only\n"; + + if ($readonly) { + print STDERR "SoundSoftware.pm:$$: Project repo is read-only, refusing access\n"; + return FORBIDDEN; + } else { + print STDERR "SoundSoftware.pm:$$: Project repo is read-write, auth required\n"; + # fall through, this is the normal case + } + + } elsif ($auth_ssl_reqd and $r->unparsed_uri =~ m/cmd=branchmap/) { + + # A hac^H^H^Hspecial case. We want to ensure we switch to + # https (if it will be necessarily for authentication) + # before the first POST request, and this is what I think + # will give us suitable warning for Mercurial. + + print STDERR "SoundSoftware.pm:$$: Switching to HTTPS in preparation\n"; + # fall through, this is the normal case + + } else { + # Public project, read-only method -- this is the only + # case we can decide for certain to accept in this function + print STDERR "SoundSoftware.pm:$$: Method is read-only, no restriction here\n"; + $r->set_handlers(PerlAuthenHandler => [\&OK]); + return OK; + } + + } else { # status != 1, i.e. nonexistent or private -- equivalent here + + print STDERR "SoundSoftware.pm:$$: Project is private or nonexistent, auth required\n"; + # fall through + } + + if ($auth_ssl_reqd) { + my $redir_to = "https://" . $r->hostname() . $r->unparsed_uri(); + print STDERR "SoundSoftware.pm:$$: Need to switch to HTTPS, redirecting to $redir_to\n"; + $r->headers_out->add('Location' => $redir_to); + return REDIRECT; + } else { + return OK; + } +} + +sub authen_handler { + my $r = shift; + + print STDERR "SoundSoftware.pm:$$: In authentication handler at " . scalar localtime() . "\n"; + + my $dbh = connect_database($r); + unless ($dbh) { + print STDERR "SoundSoftware.pm:$$: Database connection failed!: " . $DBI::errstr . "\n"; + return AUTH_REQUIRED; + } + + my $project_id = get_project_identifier($dbh, $r); + my $realm = get_realm($dbh, $project_id, $r); + $r->auth_name($realm); + + my ($res, $redmine_pass) = $r->get_basic_auth_pw(); + unless ($res == OK) { + $dbh->disconnect(); + undef $dbh; + return $res; + } + + print STDERR "SoundSoftware.pm:$$: User is " . $r->user . ", got password\n"; + + my $status = get_project_status($dbh, $project_id, $r); + if ($status == 0) { + # nonexistent, behave like private project you aren't a member of + print STDERR "SoundSoftware.pm:$$: Project doesn't exist, not permitted\n"; + $dbh->disconnect(); + undef $dbh; + $r->note_auth_failure(); + return AUTH_REQUIRED; + } + + my $permitted = is_permitted($dbh, $project_id, $r->user, $redmine_pass, $r); + + $dbh->disconnect(); + undef $dbh; + + if ($permitted) { + return OK; + } else { + print STDERR "SoundSoftware.pm:$$: Not permitted\n"; + $r->note_auth_failure(); + return AUTH_REQUIRED; + } +} + +sub get_project_status { + my $dbh = shift; + my $project_id = shift; + my $r = shift; + + if (!defined $project_id or $project_id eq '') { + return 0; # nonexistent + } + + my $sth = $dbh->prepare( + "SELECT is_public FROM projects WHERE projects.identifier = ?;" + ); + + $sth->execute($project_id); + my $ret = 0; # nonexistent + if (my @row = $sth->fetchrow_array) { + if ($row[0] eq "1" || $row[0] eq "t") { + $ret = 1; # public + } else { + $ret = 2; # private + } + } + $sth->finish(); + undef $sth; + + $ret; +} + +sub will_require_ssl_auth { + my $r = shift; + + my $cfg = Apache2::Module::get_config + (__PACKAGE__, $r->server, $r->per_dir_config); + + if ($cfg->{SoundSoftwareSslRequired} eq "on") { + if ($r->dir_config('HTTPS') eq "on") { + # already have ssl + return 0; + } else { + # require ssl for auth, don't have it yet + return 1; + } + } elsif ($cfg->{SoundSoftwareSslRequired} eq "off") { + # don't require ssl for auth + return 0; + } else { + print STDERR "WARNING: SoundSoftware.pm:$$: SoundSoftwareSslRequired should be either 'on' or 'off'\n"; + # this is safer + return 1; + } +} + +sub project_repo_is_readonly { + my $dbh = shift; + my $project_id = shift; + my $r = shift; + + if (!defined $project_id or $project_id eq '') { + return 0; # nonexistent + } + + my $sth = $dbh->prepare( + "SELECT repositories.is_external FROM repositories, projects WHERE projects.identifier = ? AND repositories.project_id = projects.id;" + ); + + $sth->execute($project_id); + my $ret = 0; # nonexistent + if (my @row = $sth->fetchrow_array) { + if (defined($row[0]) && ($row[0] eq "1" || $row[0] eq "t")) { + $ret = 1; # read-only (i.e. external) + } else { + $ret = 0; # read-write + } + } + $sth->finish(); + undef $sth; + + $ret; +} + +sub is_permitted { + my $dbh = shift; + my $project_id = shift; + my $redmine_user = shift; + my $redmine_pass = shift; + my $r = shift; + + my $pass_digest = Digest::SHA::sha1_hex($redmine_pass); + + my $cfg = Apache2::Module::get_config + (__PACKAGE__, $r->server, $r->per_dir_config); + + my $query = $cfg->{SoundSoftwareQuery}; + my $sth = $dbh->prepare($query); + $sth->execute($redmine_user, $project_id); + + my $ret; + while (my ($hashed_password, $salt, $auth_source_id, $permissions) = $sth->fetchrow_array) { + + # Test permissions for this user before we verify credentials + # -- if the user is not permitted this action anyway, there's + # not much point in e.g. contacting the LDAP + + my $method = $r->method; + + if ((defined $read_only_methods{$method} && $permissions =~ /:browse_repository/) + || $permissions =~ /:commit_access/) { + + # User would be permitted this action, if their + # credentials checked out -- test those now + + print STDERR "SoundSoftware.pm: User $redmine_user has required role, checking credentials\n"; + + unless ($auth_source_id) { + my $salted_password = Digest::SHA::sha1_hex($salt.$pass_digest); + if ($hashed_password eq $salted_password) { + print STDERR "SoundSoftware.pm: User $redmine_user authenticated via password\n"; + $ret = 1; + last; + } + } else { + my $sthldap = $dbh->prepare( + "SELECT host,port,tls,account,account_password,base_dn,attr_login FROM auth_sources WHERE id = ?;" + ); + $sthldap->execute($auth_source_id); + while (my @rowldap = $sthldap->fetchrow_array) { + my $ldap = Authen::Simple::LDAP->new( + host => ($rowldap[2] eq "1" || $rowldap[2] eq "t") ? "ldaps://$rowldap[0]" : $rowldap[0], + port => $rowldap[1], + basedn => $rowldap[5], + binddn => $rowldap[3] ? $rowldap[3] : "", + bindpw => $rowldap[4] ? $rowldap[4] : "", + filter => "(".$rowldap[6]."=%s)" + ); + if ($ldap->authenticate($redmine_user, $redmine_pass)) { + print STDERR "SoundSoftware.pm:$$: User $redmine_user authenticated via LDAP\n"; + $ret = 1; + } + } + $sthldap->finish(); + undef $sthldap; + last if ($ret); + } + } else { + print STDERR "SoundSoftware.pm:$$: User $redmine_user lacks required role for this project\n"; + } + } + + $sth->finish(); + undef $sth; + + $ret; +} + +sub get_project_identifier { + my $dbh = shift; + my $r = shift; + my $location = $r->location; + my ($repo) = $r->uri =~ m{$location/*([^/]*)}; + + return $repo if (!$repo); + + $repo =~ s/[^a-zA-Z0-9\._-]//g; + + # The original Redmine.pm returns the string just calculated as + # the project identifier. That won't do for us -- we may have + # (and in fact already do have, in our test instance) projects + # whose repository names differ from the project identifiers. + + # This is a rather fundamental change because it means that almost + # every request needs more than one database query -- which + # prompts us to start passing around $dbh instead of connecting + # locally within each function as is done in Redmine.pm. + + my $sth = $dbh->prepare( + "SELECT projects.identifier FROM projects, repositories WHERE repositories.project_id = projects.id AND repositories.url LIKE ?;" + ); + + my $cfg = Apache2::Module::get_config + (__PACKAGE__, $r->server, $r->per_dir_config); + + my $prefix = $cfg->{SoundSoftwareRepoPrefix}; + if (!defined $prefix) { $prefix = '%/'; } + my $identifier = ''; + + $sth->execute($prefix . $repo); + my $ret = 0; + if (my @row = $sth->fetchrow_array) { + $identifier = $row[0]; + } + $sth->finish(); + undef $sth; + + print STDERR "SoundSoftware.pm:$$: Repository '$repo' belongs to project '$identifier'\n"; + + $identifier; +} + +sub get_realm { + my $dbh = shift; + my $project_id = shift; + my $r = shift; + + my $sth = $dbh->prepare( + "SELECT projects.name FROM projects WHERE projects.identifier = ?;" + ); + + my $name = $project_id; + + $sth->execute($project_id); + my $ret = 0; + if (my @row = $sth->fetchrow_array) { + $name = $row[0]; + } + $sth->finish(); + undef $sth; + + # be timid about characters not permitted in auth realm and revert + # to project identifier if any are found + if ($name =~ m/[^\w\d\s\._-]/) { + $name = $project_id; + } elsif ($name =~ m/^\s*$/) { + # empty or whitespace + $name = $project_id; + } + + if ($name =~ m/^\s*$/) { + # nothing even in $project_id -- probably a nonexistent project. + # use repo name instead (don't want to admit to user that project + # doesn't exist) + my $location = $r->location; + my ($repo) = $r->uri =~ m{$location/*([^/]*)}; + $name = $repo; + } + +# my $realm = '"Mercurial repository for ' . "'$name'" . '"'; +# see #577: + my $realm = '"Mercurial repository for ' . "$name" . '"'; + + $realm; +} + +sub connect_database { + my $r = shift; + + my $cfg = Apache2::Module::get_config + (__PACKAGE__, $r->server, $r->per_dir_config); + + return DBI->connect($cfg->{SoundSoftwareDSN}, + $cfg->{SoundSoftwareDbUser}, + $cfg->{SoundSoftwareDbPass}); +} + +1;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/extra/soundsoftware/convert-external-repos.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,185 @@ +#!/usr/bin/env ruby + +# == Synopsis +# +# convert-external-repos: Update local Mercurial mirrors of external repos, +# by running an external command for each project requiring an update. +# +# == Usage +# +# convert-external-repos [OPTIONS...] -s [DIR] -r [HOST] +# +# == Arguments (mandatory) +# +# -s, --scm-dir=DIR use DIR as base directory for repositories +# -r, --redmine-host=HOST assume Redmine is hosted on HOST. Examples: +# -r redmine.example.net +# -r http://redmine.example.net +# -r https://example.net/redmine +# -k, --key=KEY use KEY as the Redmine API key +# -c, --command=COMMAND use this command to update each external +# repository: command is called with the name +# of the project, the path to its repo, and +# its external repo url as its three args +# +# == Options +# +# --http-user=USER User for HTTP Basic authentication with Redmine WS +# --http-pass=PASSWORD Password for Basic authentication with Redmine WS +# -t, --test only show what should be done +# -h, --help show help and exit +# -v, --verbose verbose +# -V, --version print version and exit +# -q, --quiet no log + + +require 'getoptlong' +require 'find' +require 'etc' + +Version = "1.0" + +opts = GetoptLong.new( + ['--scm-dir', '-s', GetoptLong::REQUIRED_ARGUMENT], + ['--redmine-host', '-r', GetoptLong::REQUIRED_ARGUMENT], + ['--key', '-k', GetoptLong::REQUIRED_ARGUMENT], + ['--http-user', GetoptLong::REQUIRED_ARGUMENT], + ['--http-pass', GetoptLong::REQUIRED_ARGUMENT], + ['--command' , '-c', GetoptLong::REQUIRED_ARGUMENT], + ['--test', '-t', GetoptLong::NO_ARGUMENT], + ['--verbose', '-v', GetoptLong::NO_ARGUMENT], + ['--version', '-V', GetoptLong::NO_ARGUMENT], + ['--help' , '-h', GetoptLong::NO_ARGUMENT], + ['--quiet' , '-q', GetoptLong::NO_ARGUMENT] + ) + +$verbose = 0 +$quiet = false +$redmine_host = '' +$repos_base = '' +$http_user = '' +$http_pass = '' +$test = false + +$mirrordir = '/var/mirror' + +def log(text, options={}) + level = options[:level] || 0 + puts text unless $quiet or level > $verbose + exit 1 if options[:exit] +end + +def system_or_raise(command) + raise "\"#{command}\" failed" unless system command +end + +begin + opts.each do |opt, arg| + case opt + when '--scm-dir'; $repos_base = arg.dup + when '--redmine-host'; $redmine_host = arg.dup + when '--key'; $api_key = arg.dup + when '--http-user'; $http_user = arg.dup + when '--http-pass'; $http_pass = arg.dup + when '--command'; $command = arg.dup + when '--verbose'; $verbose += 1 + when '--test'; $test = true + when '--version'; puts Version; exit + when '--help'; puts "Read source for documentation"; exit + when '--quiet'; $quiet = true + end + end +rescue + exit 1 +end + +if $test + log("running in test mode") +end + +if ($redmine_host.empty? or $repos_base.empty? or $command.empty?) + puts "Read source for documentation"; exit +end + +unless File.directory?($repos_base) + log("directory '#{$repos_base}' doesn't exist", :exit => true) +end + +begin + require 'active_resource' +rescue LoadError + log("This script requires activeresource.\nRun 'gem install activeresource' to install it.", :exit => true) +end + +class Project < ActiveResource::Base + self.headers["User-agent"] = "SoundSoftware external repository converter/#{Version}" + self.format = :xml +end + +log("querying Redmine for projects...", :level => 1); + +$redmine_host.gsub!(/^/, "http://") unless $redmine_host.match("^https?://") +$redmine_host.gsub!(/\/$/, '') + +Project.site = "#{$redmine_host}/sys"; +Project.user = $http_user; +Project.password = $http_pass; + +begin + # Get all active projects that have the Repository module enabled + projects = Project.find(:all, :params => {:key => $api_key}) +rescue ActiveResource::ForbiddenAccess + log("Request was denied by your Redmine server. Make sure that 'WS for repository management' is enabled in application settings and that you provided the correct API key.") +rescue => e + log("Unable to connect to #{Project.site}: #{e}", :exit => true) +end + +if projects.nil? + log('no project found, perhaps you forgot to "Enable WS for repository management"', :exit => true) +end + +log("retrieved #{projects.size} projects", :level => 1) + +projects.each do |project| + log("treating project #{project.name}", :level => 1) + + if project.identifier.empty? + log("\tno identifier for project #{project.name}") + next + elsif not project.identifier.match(/^[a-z0-9_\-]+$/) + log("\tinvalid identifier for project #{project.name} : #{project.identifier}"); + next + end + + if !project.respond_to?(:repository) or !project.repository.is_external? + log("\tproject #{project.identifier} does not use an external repository"); + next + end + + external_url = project.repository.external_url; + log("\tproject #{project.identifier} has external repository url #{external_url}"); + + if !external_url.match(/^[a-z][a-z+]{0,8}[a-z]:\/\//) + log("\tthis doesn't look like a plausible url to me, skipping") + next + end + + repos_path = File.join($repos_base, project.identifier).gsub(File::SEPARATOR, File::ALT_SEPARATOR || File::SEPARATOR) + + unless File.directory?(repos_path) + log("\tproject repo directory '#{repos_path}' doesn't exist") + next + end + + system($command, project.identifier, repos_path, external_url) + + $cache_clearance_file = File.join($mirrordir, project.identifier, 'url_changed') + if File.file?($cache_clearance_file) + log("\tproject repo url has changed, requesting cache clearance") + if project.post(:repository_cache, :key => $api_key) + File.delete($cache_clearance_file) + end + end + +end +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/extra/soundsoftware/doxysafe.pl Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,220 @@ +#!/usr/bin/perl -w + +# Read a Doxyfile and print it out again to stdout, with only +# whitelisted keys in it and with some keys set to pre-fixed values. +# +# Note that OUTPUT_DIRECTORY is not included; it should be added by +# the caller + +use strict; + +my $txt = join "", <>; +$txt =~ s/^\s*#.*$//gm; +$txt =~ s/\\\n//gs; +$txt =~ s/\r//g; +$txt =~ s/\n\s*\n/\n/gs; + +my %fixed = ( + FULL_PATH_NAMES => "NO", + SYMBOL_CACHE_SIZE => 2, + EXCLUDE_SYMLINKS => "YES", + GENERATE_HTML => "YES", + PERL_PATH => "/usr/bin/perl", + HAVE_DOT => "YES", + HTML_OUTPUT => ".", + HTML_DYNAMIC_SECTIONS => "NO", + SEARCHENGINE => "NO", + DOT_FONTNAME => "FreeMono", + DOT_FONTSIZE => 10, + DOT_FONTPATH => "/usr/share/fonts/truetype/freefont", + DOT_IMAGE_FORMAT => "png", + DOT_PATH => "/usr/bin/dot", + DOT_TRANSPARENT => "YES", +); + +# These are the keys that are safe to take from the output and include +# in the output; they may still need to be checked for safe values (if +# file paths). +my @safe = qw( +INPUT +FILE_PATTERNS +EXAMPLE_PATH +EXAMPLE_PATTERNS +IMAGE_PATH +INCLUDE_PATH +INCLUDE_FILE_PATTERNS +DOXYFILE_ENCODING +PROJECT_NAME +PROJECT_NUMBER +CREATE_SUBDIRS +OUTPUT_LANGUAGE +BRIEF_MEMBER_DESC +REPEAT_BRIEF +ABBREVIATE_BRIEF +ALWAYS_DETAILED_SEC +INLINE_INHERITED_MEMB +STRIP_FROM_PATH +STRIP_FROM_INC_PATH +JAVADOC_AUTOBRIEF +QT_AUTOBRIEF +MULTILINE_CPP_IS_BRIEF +INHERIT_DOCS +SEPARATE_MEMBER_PAGES +TAB_SIZE +ALIASES +OPTIMIZE_OUTPUT_FOR_C +OPTIMIZE_OUTPUT_JAVA +OPTIMIZE_FOR_FORTRAN +OPTIMIZE_OUTPUT_VHDL +EXTENSION_MAPPING +BUILTIN_STL_SUPPORT +CPP_CLI_SUPPORT +SIP_SUPPORT +IDL_PROPERTY_SUPPORT +DISTRIBUTE_GROUP_DOC +SUBGROUPING +TYPEDEF_HIDES_STRUCT +EXTRACT_ALL +EXTRACT_PRIVATE +EXTRACT_STATIC +EXTRACT_LOCAL_CLASSES +EXTRACT_LOCAL_METHODS +EXTRACT_ANON_NSPACES +HIDE_UNDOC_MEMBERS +HIDE_UNDOC_CLASSES +HIDE_FRIEND_COMPOUNDS +HIDE_IN_BODY_DOCS +INTERNAL_DOCS +HIDE_SCOPE_NAMES +SHOW_INCLUDE_FILES +FORCE_LOCAL_INCLUDES +INLINE_INFO +SORT_MEMBER_DOCS +SORT_BRIEF_DOCS +SORT_MEMBERS_CTORS_1ST +SORT_GROUP_NAMES +SORT_BY_SCOPE_NAME +GENERATE_TODOLIST +GENERATE_TESTLIST +GENERATE_BUGLIST +GENERATE_DEPRECATEDLIST +ENABLED_SECTIONS +MAX_INITIALIZER_LINES +SHOW_USED_FILES +SHOW_DIRECTORIES +SHOW_FILES +SHOW_NAMESPACES +QUIET +WARNINGS +WARN_IF_UNDOCUMENTED +WARN_IF_DOC_ERROR +WARN_NO_PARAMDOC +INPUT_ENCODING +RECURSIVE +EXCLUDE +EXCLUDE_SYMLINKS +EXCLUDE_PATTERNS +EXCLUDE_SYMBOLS +EXAMPLE_RECURSIVE +SOURCE_BROWSER +INLINE_SOURCES +STRIP_CODE_COMMENTS +REFERENCED_BY_RELATION +REFERENCES_RELATION +REFERENCES_LINK_SOURCE +VERBATIM_HEADERS +ALPHABETICAL_INDEX +COLS_IN_ALPHA_INDEX +IGNORE_PREFIX +HTML_TIMESTAMP +HTML_ALIGN_MEMBERS +ENABLE_PREPROCESSING +MACRO_EXPANSION +EXPAND_ONLY_PREDEF +SEARCH_INCLUDES +PREDEFINED +EXPAND_AS_DEFINED +SKIP_FUNCTION_MACROS +ALLEXTERNALS +EXTERNAL_GROUPS +CLASS_DIAGRAMS +HIDE_UNDOC_RELATIONS +CLASS_GRAPH +COLLABORATION_GRAPH +GROUP_GRAPHS +UML_LOOK +TEMPLATE_RELATIONS +INCLUDE_GRAPH +INCLUDED_BY_GRAPH +CALL_GRAPH +CALLER_GRAPH +GRAPHICAL_HIERARCHY +DIRECTORY_GRAPH +DOT_GRAPH_MAX_NODES +MAX_DOT_GRAPH_DEPTH +DOT_MULTI_TARGETS +DOT_CLEANUP +); + +my %safehash; +for my $sk (@safe) { $safehash{$sk} = 1; } + +my @lines = split "\n", $txt; + +my %settings; + +sub is_safe { + my $key = shift; + defined $safehash{$key} and $safehash{$key} == 1; +} + +sub has_file_path { + # Returns true if the given key expects a file path as a value. + # We only need to test keys that are safe; unsafe keys have been + # rejected already. + my $key = shift; + $key eq "INPUT" or + $key =~ /^OUTPUT_/ or + $key =~ /_PATH$/ or + $key =~ /_PATTERNS$/; +} + +sub is_safe_file_path { + my $value = shift; + not $value =~ /^\// and not $value =~ /\.\./; +} + +foreach my $line (@lines) { + + chomp $line; + my ($key, $value) = split /\s*=\s*/, $line; + + next if !defined $key; + + if ($key =~ /^GENERATE_/ and not $key =~ /LIST$/) { + print STDERR "NOTE: Setting $key explicitly to NO\n"; + $settings{$key} = "NO"; + next; + } + + if (!is_safe($key)) { + print STDERR "NOTE: Skipping non-whitelisted key $key\n"; + next; + } + + if (has_file_path($key) and !is_safe_file_path($value)) { + print STDERR "ERROR: Unsafe file path \"$value\" for key $key\n"; + exit 1; + } + + $settings{$key} = $value; +} + +foreach my $key (keys %fixed) { + my $value = $fixed{$key}; + print STDERR "NOTE: Setting $key to fixed value $value\n"; + $settings{$key} = $value; +} + +print join "\n", map { "$_ = $settings{$_}" } keys %settings; +print "\n";
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/extra/soundsoftware/extract-docs.sh Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,150 @@ +#!/bin/bash + +# Run this script from anywhere + +# Enumerate Hg repos; make sure they're up to date; extract docs for +# each + +hgdir="/var/hg" +docdir="/var/doc" +logfile="/var/www/test-cannam/log/extract-docs.log" + +redgrp="redmine" + +apikey="" +apischeme="https" +apihost="" +apiuser="" +apipass="" + +progdir=$(dirname $0) +case "$progdir" in + /*) ;; + *) progdir="$(pwd)/$progdir" ;; +esac + +types="doxygen javadoc matlabdocs" # Do Doxygen first (it can be used for Java too) + +for x in $types; do + if [ ! -x "$progdir/extract-$x.sh" ]; then + echo "Helper script not available: $progdir/extract-$x.sh" + exit 1 + fi +done + +enable_embedded() +{ + p="$1" + if [ -n "$apikey" ]; then + if [ -n "$apiuser" ]; then + sudo -u docgen curl -u "$apiuser":"$apipass" "$apischeme://$apihost/sys/projects/$p/embedded.xml?enable=1&key=$apikey" -d "" + else + sudo -u docgen curl "$apischeme://$apihost/sys/projects/$p/embedded.xml?enable=1&key=$apikey" -d "" + fi + else + echo "Can't enable Embedded, API not configured" 1>&2 + fi +} + +# We want to ensure the doc extraction is done by the unprivileged +# user docgen, which is not a member of any interesting group +# +# To this end, we create the tmpdir with user docgen and group +# www-data, and use the www-data user to pull out an archive of the Hg +# repo tip into a location beneath that, before using the docgen user +# to extract docs from that location and write them into the tmpdir + +# Same tmpdir for each project: we delete and recreate to avoid +# cleanup duty from lots of directories being created +# +tmpdir=$(mktemp -d "$docdir/tmp_XXXXXX") + +fail() +{ + message="$1" + echo "$message" 1>&2 + case "$tmpdir" in + */tmp*) rm -rf "$tmpdir";; + *);; + esac + exit 1 +} + +case "$tmpdir" in + /*) ;; + *) fail "Temporary directory creation failed";; +esac + +chown docgen.www-data "$tmpdir" || fail "Temporary directory ownership change failed" +chmod g+rwx "$tmpdir" || fail "Temporary directory permissions change failed" + +for projectdir in "$hgdir"/* ; do + + if [ -d "$projectdir" ] && [ -d "$projectdir/.hg" ]; then + + if ! sudo -u www-data hg -R "$projectdir" -q update; then + echo "Failed to update Hg in $projectdir, skipping" 1>&2 + continue + fi + + project=$(basename "$projectdir") + + tmptargetdir="$tmpdir/doc" + snapshotdir="$tmpdir/hgsnapshot" + + rm -rf "$tmptargetdir" "$snapshotdir" + + mkdir -m 770 "$tmptargetdir" || fail "Temporary target directory creation failed" + chown docgen.www-data "$tmptargetdir" || fail "Temporary target directory ownership change failed" + + mkdir -m 770 "$snapshotdir" || fail "Snapshot directory creation failed" + chown docgen.www-data "$snapshotdir" || fail "Snapshot directory ownership change failed" + + hgparents=$(sudo -u www-data hg -R "$projectdir" parents) + if [ -z "$hgparents" ]; then + echo "Hg repo at $projectdir has no working copy (empty repo?), skipping" + continue + else + echo "Found non-empty Hg repo: $projectdir for project $project" + fi + + if ! sudo -u www-data hg -R "$projectdir" archive -r tip -t files "$snapshotdir"; then + echo "Failed to pick archive from $projectdir, skipping" 1>&2 + continue + fi + + targetdir="$docdir/$project" + + echo "Temporary dir is $tmpdir, temporary doc dir is $tmptargetdir, snapshot dir is $snapshotdir, eventual target is $targetdir" + + for x in $types; do + if sudo -u docgen "$progdir/extract-$x.sh" "$project" "$snapshotdir" "$tmptargetdir" >> "$logfile" 2>&1; then + break + else + echo "Failed to extract via type $x" + fi + done + + if [ -f "$tmptargetdir/index.html" ]; then + echo "Processing resulted in an index.html being created, looks good!" + if [ ! -d "$targetdir" ] || [ ! -f "$targetdir/index.html" ]; then + echo "This project hasn't had doc extracted before: enabling Embedded" + enable_embedded "$project" + fi + + if [ -d "$targetdir" ]; then + mv "$targetdir" "$targetdir"_"$$" && \ + mv "$tmptargetdir" "$targetdir" && \ + rm -rf "$targetdir"_"$$" + chgrp -R "$redgrp" "$targetdir" + else + mv "$tmptargetdir" "$targetdir" + chgrp -R "$redgrp" "$targetdir" + fi + else + echo "Processing did not result in an index.html being created" + fi + fi +done + +rm -rf "$tmpdir"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/extra/soundsoftware/extract-doxygen.sh Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,49 @@ +#!/bin/bash + +docdir="/var/doc" + +progdir=$(dirname $0) +case "$progdir" in + /*) ;; + *) progdir="$(pwd)/$progdir" ;; +esac + +project="$1" +projectdir="$2" +targetdir="$3" + +if [ -z "$project" ] || [ -z "$targetdir" ] || [ -z "$projectdir" ]; then + echo "Usage: $0 <project> <projectdir> <targetdir>" + exit 2 +fi + +if [ ! -d "$projectdir" ]; then + echo "Project directory $projectdir not found" + exit 1 +fi + +if [ ! -d "$targetdir" ]; then + echo "Target dir $targetdir not found" + exit 1 +fi + +if [ -f "$targetdir/index.html" ]; then + echo "Target dir $targetdir already contains index.html" + exit 1 +fi + +doxyfile=$(find "$projectdir" -type f -name Doxyfile -print | head -1) + +if [ -z "$doxyfile" ]; then + echo "No Doxyfile found for project $project" + exit 1 +fi + +echo "Project $project contains a Doxyfile at $doxyfile" + +cd "$projectdir" || exit 1 + +"$progdir/doxysafe.pl" "$doxyfile" | \ + sed -e '$a OUTPUT_DIRECTORY='"$targetdir" | \ + doxygen - +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/extra/soundsoftware/extract-javadoc.sh Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,80 @@ +#!/bin/bash + +docdir="/var/doc" + +project="$1" +projectdir="$2" +targetdir="$3" + +if [ -z "$project" ] || [ -z "$targetdir" ] || [ -z "$projectdir" ]; then + echo "Usage: $0 <project> <projectdir> <targetdir>" + exit 2 +fi + +if [ ! -d "$projectdir" ]; then + echo "Project directory $projectdir not found" + exit 1 +fi + +if [ ! -d "$targetdir" ]; then + echo "Target dir $targetdir not found" + exit 1 +fi + +if [ -f "$targetdir/index.html" ]; then + echo "Target dir $targetdir already contains index.html" + exit 1 +fi + +# Identify Java files whose packages match the trailing parts of their +# paths, and list the resulting packages and the path prefixes with +# the packages removed (so as to find code in subdirs, +# e.g. src/com/example/...) + +# Regexp match is very rough; check what is actually permitted for +# package declarations + +find "$projectdir" -type f -name \*.java \ + -exec egrep '^ *package +[a-zA-Z][a-zA-Z0-9\._-]*;.*$' \{\} /dev/null \; | + sed -e 's/\/[^\/]*: *package */:/' -e 's/;.*$//' | + sort | uniq | ( + current_prefix= + current_packages= + while IFS=: read filepath package; do + echo "Looking at $package in $filepath" + packagepath=${package//./\/} + prefix=${filepath%$packagepath} + prefix=${prefix:=$projectdir} + if [ "$prefix" = "$filepath" ]; then + echo "Package $package does not match suffix of path $filepath, skipping" + continue + fi + if [ "$prefix" != "$current_prefix" ]; then + echo "Package $package matches file path and has new prefix $prefix" + if [ -n "$current_packages" ]; then + echo "Running Javadoc for packages $current_packages from prefix $current_prefix" + echo "Command is: javadoc -sourcepath "$current_prefix" -d "$targetdir" -subpackages $current_packages" + javadoc -sourcepath "$current_prefix" -d "$targetdir" -subpackages $current_packages + fi + current_prefix="$prefix" + current_packages="$package" + else + echo "Package $package matches file path with same prefix as previous file" + current_packages="$current_packages $package" + fi + done + prefix=${prefix:=$projectdir} + if [ -n "$current_packages" ]; then + echo "Running Javadoc for packages $current_packages in prefix $current_prefix" + echo "Command is: javadoc -sourcepath "$current_prefix" -d "$targetdir" -subpackages $current_packages" + javadoc -sourcepath "$current_prefix" -d "$targetdir" -subpackages $current_packages + fi + ) + +if [ -f "$targetdir"/overview-tree.html ]; then + cp "$targetdir"/overview-tree.html "$targetdir"/index.html +fi + +# for exit code: +[ -f "$targetdir/index.html" ] +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/extra/soundsoftware/extract-matlabdocs.sh Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,47 @@ +#!/bin/bash + +docdir="/var/doc" + +progdir=$(dirname $0) +case "$progdir" in + /*) ;; + *) progdir="$(pwd)/$progdir" ;; +esac + +project="$1" +projectdir="$2" +targetdir="$3" + +if [ -z "$project" ] || [ -z "$targetdir" ] || [ -z "$projectdir" ]; then + echo "Usage: $0 <project> <projectdir> <targetdir>" + exit 2 +fi + +if [ ! -d "$projectdir" ]; then + echo "Project directory $projectdir not found" + exit 1 +fi + +if [ ! -d "$targetdir" ]; then + echo "Target dir $targetdir not found" + exit 1 +fi + +if [ -f "$targetdir/index.html" ]; then + echo "Target dir $targetdir already contains index.html" + exit 1 +fi + +mfile=$(find "$projectdir" -type f -name \*.m -print0 | xargs -0 grep -l '^% ' | head -1) + +if [ -z "$mfile" ]; then + echo "No MATLAB files with comments found for project $project" + exit 1 +fi + +echo "Project $project contains at least one MATLAB file with comments" + +cd "$projectdir" || exit 1 + +perl "$progdir/matlab-docs.pl" -c "$progdir/matlab-docs.conf" -d "$targetdir" +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/extra/soundsoftware/get-apache-log-stats.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,179 @@ + +# Read an Apache log file in SoundSoftware site format from stdin and +# produce some per-project stats. +# +# Invoke with e.g. +# +# cat /var/log/apache2/code-access.log | \ +# script/runner -e production extra/soundsoftware/get-apache-log-stats.rb + + +# Use the ApacheLogRegex parser, a neat thing +# See http://www.simonecarletti.com/blog/2009/02/apache-log-regex-a-lightweight-ruby-apache-log-parser/ +require 'apachelogregex' + +# This is the format defined in our httpd.conf +vhost_combined_format = '%v:%p %h %{X-Forwarded-For}i %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"' + +parser = ApacheLogRegex.new(vhost_combined_format) + +# project name -> count of hg clones +clones = Hash.new(0) + +# project name -> count of hg pulls +pulls = Hash.new(0) + +# project name -> count of hg pushes +pushes = Hash.new(0) + +# project name -> count of hg archive requests (i.e. Download as Zip) +zips = Hash.new(0) + +# project name -> count of hits to pages under /projects/projectname +hits = Hash.new(0) + +# project name -> Project object +@projects = Hash.new + +parseable = 0 +unparseable = 0 + +def is_public_project?(project) + if !project + false + elsif project =~ /^\d+$/ + # ignore numerical project ids, they are only used when editing projects + false + elsif @projects.key?(project) + @projects[project].is_public? + else + pobj = Project.find_by_identifier(project) + if pobj + @projects[project] = pobj + pobj.is_public? + else + print "Project not found: ", project, "\n" + false + end + end +end + +def print_stats(h) + h.keys.sort { |a,b| h[b] <=> h[a] }.each do |p| + if h[p] > 0 + print h[p], " ", @projects[p].name, " [", p, "]\n" + end + end +end + +STDIN.each do |line| + + record = parser.parse(line) + + # most annoyingly, the parser can't handle the comma-separated list + # in X-Forwarded-For where it has more than one element. If it has + # failed, remove any IP addresses or the word "unknown" with + # trailing commas and try again + if not record + filtered = line.gsub(/(unknown|([0-9]+\.){3}[0-9]+),\s*/, "") + record = parser.parse(filtered) + end + + # discard, but count, unparseable lines + if not record + print "Line not parseable: ", line, "\n" + unparseable += 1 + next + end + + # discard everything that isn't a 200 OK response + next if record["%>s"] != "200" + + # discard anything apparently requested by a crawler + next if record["%{User-Agent}i"] =~ /(bot|slurp|crawler|spider|Redmine)\b/i + + # pull out request e.g. GET / HTTP/1.0 + request = record["%r"] + + # split into method, path, protocol + if not request =~ /^[^\s]+ ([^\s]+) [^\s]+$/ + print "Line not parseable (bad method, path, protocol): ", line, "\n" + unparseable += 1 + next + end + + # get the path e.g. /projects/weevilmatic and split on / + path = $~[1] + components = path.split("/") + + # should have at least two elements unless path is "/"; first should + # be empty (begins with /) + if path != "/" and (components.size < 2 or components[0] != "") + print "Line not parseable (degenerate path): ", line, "\n" + unparseable += 1 + next + end + + if components[1] == "hg" + + # path is /hg/project?something or /hg/project/something + + project = components[2].split("?")[0] + if not is_public_project?(project) + next + end + + if components[2] =~ /&roots=00*$/ + clones[project] += 1 + elsif components[2] =~ /cmd=capabilities/ + pulls[project] += 1 + elsif components[2] =~ /cmd=unbundle/ + pushes[project] += 1 + elsif components[3] == "archive" + zips[project] += 1 + end + + elsif components[1] == "projects" + + # path is /projects/project or /projects/project/something + + project = components[2] + project = project.split("?")[0] if project + if not is_public_project?(project) + next + end + + hits[project] += 1 + + end + + parseable += 1 +end + +# Each clone is also a pull; deduct it from the pulls hash, because we +# want that to contain only non-clone pulls + +clones.keys.each do |project| + pulls[project] -= 1 +end + +print parseable, " parseable\n" +print unparseable, " unparseable\n" + + +print "\nMercurial clones:\n" +print_stats clones + +print "\nMercurial pulls (excluding clones):\n" +print_stats pulls + +print "\nMercurial pushes:\n" +print_stats pushes + +print "\nMercurial archive (zip file) downloads:\n" +print_stats zips + +print "\nProject page hits (excluding crawlers):\n" +print_stats hits + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/extra/soundsoftware/get-statistics.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,21 @@ + +# Log user and project information +# +# Invoke with e.g. +# +# ./script/rails runner -e production extra/soundsoftware/get-statistics.rb +# + +projectStats = { + :all => Project.active.all.count, + :private => Project.active.find(:all, :conditions => {:is_public => false}).count, + :top_level => Project.active.find(:all, :conditions => {:parent_id => nil}).count, + :top_level_and_private => Project.active.find(:all, :conditions => {:is_public => false, :parent_id => nil}).count + } + +userStats = {:all => User.active.all.count} + +stats = {:date => Date.today, :projects => projectStats, :users => userStats}.to_json + +print "#{stats}\n" +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/extra/soundsoftware/matlab-docs-credit.html Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,1 @@ +<div style="clear: both; float: right"><small><i>Produced by mtree2html by Hartmut Pohlheim</i></small></div>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/extra/soundsoftware/matlab-docs.conf Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,166 @@ +# configuration file for generation of html-docu from m-files +# +# Author: Hartmut Pohlheim +# History: 05.11.2000 file created (parameters for mtree2html2001) +# +# The following options/variables must be changed/adapted: +# dirmfiles +# dirhtml +# csslink +# texttitleframelayout +# texttitlefiles +# +# The following options/variables should be adapted: +# authorfile +# filenametopframe +# codeheadmeta + +#======================================================================== +# Variables (possible keywords: set) +# to use the built-in settings, comment the line using # in first column +#======================================================================== + +#------------------------------------------------------------------------ +# dirmfiles: name of directory containing Matlab m-files +# dirhtml: name of directory to place the html-files into +# exthtml: extension used for the html files (.html or .htm) +# don't forget the point in front of the extension +#------------------------------------------------------------------------ +set dirmfiles = . +set dirhtml = doc-output +set exthtml = .html + +#------------------------------------------------------------------------ +# authorfile: name of file containing info about author (in html) +# if defined, this text is included at the bottom of the +# html files +#------------------------------------------------------------------------ +set authorfile = matlab-docs-credit.html + +#------------------------------------------------------------------------ +# csslink: text for linking to css file (style sheets) +# the text defined here is directly included into the head +# of the html file +#------------------------------------------------------------------------ +#set csslink = <link rel=stylesheet type="text/css" href="CSSFILENAME.css" /> + +#------------------------------------------------------------------------ +# links2filescase: this is a bit difficult +# Matlab is case sensitive on UNIX, but case insensitive +# on Windows. Under UNIX Matlab function calls work +# only, when the case of file name and function call are +# identical, under Windows you can do what you want. +# This scripts help you, to keep an exact case in your +# project. +# exact - internal links are only generated, when case of file +# name and in source code are identical +# all - case doesn't matter +# exactupper - same as exact, additionally links are also vreated to +# all upper case function names in source code (often +# used by Mathworks) +# exactvery - same as exact, additionally info about not matching +# case is written to screen (stdout), this can be very +# helpful in cleaning up the case in a project +#------------------------------------------------------------------------ +set links2filescase = all + +#------------------------------------------------------------------------ +# texttitleframelayout: text of title for frame layout file (whole docu) +#------------------------------------------------------------------------ +set texttitleframelayout = MATLAB Function Documentation + +#------------------------------------------------------------------------ +# texttitle/headerindexalldirs: text of title and header for directory index +#------------------------------------------------------------------------ +set texttitleindexalldirs = Index of Directories +set textheaderindexalldirs = Index of Directories + +#------------------------------------------------------------------------ +# texttitle/headerindex: text of title and header for index file +#------------------------------------------------------------------------ +set texttitleindex = A-Z Index of Functions +set textheaderindex = A-Z Index of Functions + +#------------------------------------------------------------------------ +# texttitle/headerfiles: text of title and header for files +# name of file will be added at the end +#------------------------------------------------------------------------ +set texttitlefiles = Function +set textheaderfiles = Documentation of + +#------------------------------------------------------------------------ +# frames: whether to use frames in layout (yes or no) +#------------------------------------------------------------------------ +set frames = no + +#------------------------------------------------------------------------ +# filenametopframe: name of file including frame layout (highest level file) +# [default: index] +#------------------------------------------------------------------------ +set filenametopframe = index + +#------------------------------------------------------------------------ +# textjumpindexglobal: text displayed for jump to index of all files +# (global) +# textjumpindexlocal: text displayed for jump to index of files in actual +# directory (local) +#------------------------------------------------------------------------ +set textjumpindexglobal = <b>Index of</b> all files: +set textjumpindexlocal = this subdirectory only: + +#------------------------------------------------------------------------ +# includesource: include source of m-files in documentation [YES|no] +#------------------------------------------------------------------------ +set includesource = yes + +#------------------------------------------------------------------------ +# usecontentsm: use contents.m files as well for structured +# (hopefully) index [YES|no] +#------------------------------------------------------------------------ +set usecontentsm = no + +#------------------------------------------------------------------------ +# includesource: write/update contents.m files [yes|NO] +#------------------------------------------------------------------------ +set writecontentsm = no + +#------------------------------------------------------------------------ +# processtree: parse whole directory tree recursively [YES|no] +#------------------------------------------------------------------------ +set processtree = yes + +#------------------------------------------------------------------------ +# producetree: produce tree for html-files in same structure than +# tree of m-files [yes|NO] +# if no, all files are saved in the same directory, often +# easier for outside linking to files +#------------------------------------------------------------------------ +set producetree = yes + +#------------------------------------------------------------------------ +# codebodyindex/files: HTML-code for adding to BODY tag +# can be used for defining colors and +# backgroundimages of the files +# No longer recommended, use the css file +#------------------------------------------------------------------------ +set codebodyindex = +set codebodyfiles = + +#------------------------------------------------------------------------ +# codeheadmeta: HTML-code added in HEAD area, use for supplying META info +#------------------------------------------------------------------------ +set codeheadmeta = + +#------------------------------------------------------------------------ +# codehr: HTML-code used to define a <HR>, do what you want +#------------------------------------------------------------------------ +set codehr = <hr> + +#------------------------------------------------------------------------ +# codeheader: HTML-code added to <H*> tags, use for centering header text +# or changing the colour/size/font of the header text +#------------------------------------------------------------------------ +set codeheader = + + +# End of parameter file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/extra/soundsoftware/matlab-docs.pl Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,1585 @@ +@rem = '--*-Perl-*--'; +@rem = ' +@echo off +perl -w -S %0.bat %1 %2 %3 %4 %5 %6 %7 %8 %9 +goto endofperl +@rem '; +# perl -w -S %0.bat "$@" +#!/usr/bin/perl +# +# mtree2html_2000 - produce html files from Matlab m-files. +# use configuration file for flexibility +# can process tree of directories +# +# Copyright (C) 1996-2000 Hartmut Pohlheim. All rights reserved. +# includes small parts of m2html from Jeffrey C. Kantor 1995 +# +# Author: Hartmut Pohlheim +# History: 06.03.1996 file created +# 07.03.1996 first working version +# 08.03.1996 modularized, help text only once included +# 11.03.1996 clean up, some functions rwritten +# 18.04.1996 silent output with writing in one line only +# version 0.20 fixed +# 14.05.1996 start of adding tree structure, could create tree +# 15.05.1996 creating of index files for every directory +# 17.05.1996 first working version except compact A-Z index +# 20.05.1996 cleanup of actual version, more variables and +# configurable settings +# 21.05.1996 reading, update and creation of contents.m added +# 22.05.1996 creation of short index started +# 28.05.1996 jump letters for short index, +# 3 different directory indexes (short/long/contents) +# 29.05.1996 major cleanup, short and long index created from one function +# links for HTML and Indexes from 1 function, +# version 0.9 +# 30.05.1996 contents.m changed to Contents.m (because unix likes it) +# function definition can be in first line of m file before comments +# version 0.91 fixed +# 03.06.1996 contents file can be written as wanted, the links will be correct +# cross references in help block of m-file will be found and +# converted, even if the name of the function is written upper case +# version 0.92 fixed +# 05.06.1996 construction of dependency matrix changed, is able now to process +# even the whole matlab tree (previous version needed to much memory) +# removed warning for contents files in different directories +# version 0.94 fixed +# 06.06.1996 new link name matrices for ConstructHTMLFile created, +# everything is done in ConstructDependencyMatrix, +# both dependencies (calls and called) and matrix +# with all mentioned names in this m-file, thus, much +# less scanning in html construction +# script is now (nearly) linear scalable, thus, matlab-toolbox +# tree takes less than 1 hour on a Pentium120, with source +# version 0.96 fixed +# 10.06.1996 order of creation changed, first all indexes (includes +# update/creation of contents.m) and then ConstructDepency +# thus, AutoAdd section will be linked as well +# excludenames extended, some more common word function names added +# version 0.97 fixed +# 17.02.1998 writecontentsm as command line parameter added +# error of file not found will even appear when silent +# version 1.02 +# 21.05.2000 mark comments in source code specially (no fully correct, +# can't handle % in strings) +# version 1.11 +# 05.11.2000 link also to upper and mixed case m-files +# searching for .m files now really works (doesn't find grep.com any longer) +# file renamed to mtree2html2001 +# generated html code now all lower case +# inclusion of meta-description and meta-keywords in html files +# HTML4 compliance done (should be strict HTML4.0, quite near XHTML) +# version 1.23 +# +# 29.03.2011 (Chris Cannam) add frames option. + +$VERSION = '1.23'; +($PROGRAM = $0) =~ s@.*/@@; $PROGRAM = "\U$PROGRAM\E"; +$debug = 1; + +#------------------------------------------------------------------------ +# Define platform specific things +#------------------------------------------------------------------------ +# suffix for files to search is defined twice +# the first ($suffix) is for string creation and contains the . as well +# the second ($suffixforsearch) is for regular expression, handling of . is quite special +$suffix = ".m"; +$suffixforsearch = "m"; +# the directory separator +$dirsep = "/"; +# what is the current directory +$diract = "."; + +#------------------------------------------------------------------------ +# Define all variables and their standard settings +# documentation of variables is contained in accompanying rc file +#------------------------------------------------------------------------ +%var = +( + 'authorfile', '', + 'codebodyfiles', '', + 'codebodyindex', '', + 'codeheadmeta', '<meta name="author of conversion perl script" content="Hartmut Pohlheim" />', + 'codehr', '<hr size="3" noshade="noshade" />', + 'codeheader', '', + 'configfile', 'matlab-docs.conf', + 'csslink', '', + 'dirmfiles', $diract, + 'dirhtml', $diract, + 'exthtml', '.html', + 'frames', 'yes', + 'filenametopframe', 'index', + 'filenameindexlongglobal', 'indexlg', + 'filenameindexlonglocal', 'indexll', + 'filenameindexshortglobal', 'indexsg', + 'filenameindexshortlocal', 'indexsl', + 'filenameextensionframe', 'f', + 'filenameextensionindex', 'i', + 'filenameextensionjump', 'j', + 'filenamedirshort', 'dirtops', + 'filenamedirlong', 'dirtopl', + 'filenamedircontents', 'dirtopc', + 'includesource', 'yes', + 'links2filescase', 'all', + 'processtree', 'yes', + 'producetree', 'yes', + 'textjumpindexlocal', 'Local Index', + 'textjumpindexglobal', 'Global Index', + 'texttitleframelayout', 'Documentation of Matlab Files', + 'texttitleindexalldirs', 'Index of Directories', + 'textheaderindexalldirs', 'Index of Directories', + 'texttitleindex', '', + 'textheaderindex', '', + 'texttitlefiles', 'Documentation of ', + 'textheaderfiles', 'Documentation of ', + 'usecontentsm', 'yes', + 'writecontentsm', 'no' +); + + +# define all m-file names, that should be excluded from linking +# however, files will still be converted +@excludenames = ( 'all','ans','any','are', + 'cs', + 'demo','dos', + 'echo','edit','else','elseif','end','exist', + 'flag','for','function', + 'global', + 'help', + 'i','if','inf','info', + 'j', + 'more', + 'null', + 'return', + 'script','strings', + 'what','which','while','who','whos','why', + ); + +# Text for inclusion in created HTML/Frame files: Doctype and Charset +$TextDocTypeHTML = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">'; +$TextDocTypeFrame = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Frameset//EN" "http://www.w3.org/TR/REC-html40/frameset.dtd">'; +$TextMetaCharset = '<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" />'; + +#------------------------------------------------------------------------ +# Read the command line arguments +#------------------------------------------------------------------------ +if (@ARGV == 0) { + &DisplayHelp() if &CheckFileName($var{'configfile'}, 'configuration file'); +} + +# Print provided command line arguments on screen +foreach (@ARGV) { print " $_\n "; } + +# Get the options +use Getopt::Long; +@options = ('help|h', 'todo|t', 'version|v', + 'authorfile|a=s', 'configfile|c=s', 'dirhtml|html|d=s', + 'dirmfiles|mfiles|m=s', 'includesource|i=s', + 'processtree|r=s', 'producetree|p=s', + 'silent|quiet|q', 'writecontentsm|w=s'); +&GetOptions(@options) || die "use -h switch to display help statement\n"; + + +# Display help or todo list, when requested +&DisplayHelp() if $opt_help; +&DisplayTodo() if $opt_todo; +die "$PROGRAM v$VERSION\n" if $opt_version; + +$exit_status = 0; + +#------------------------------------------------------------------------ +# Read the config file +#------------------------------------------------------------------------ +$var{'configfile'} = $opt_configfile if $opt_configfile; +&GetConfigFile($var{'configfile'}); + + +#------------------------------------------------------------------------ +# Process/Check the command line otions +#------------------------------------------------------------------------ +$var{'dirhtml'} = $opt_dirhtml if $opt_dirhtml; +if (!(substr($var{'dirhtml'}, -1, 1) eq $dirsep)) { $var{'dirhtml'} = $var{'dirhtml'}.$dirsep; } +$var{'dirmfiles'} = $opt_dirmfiles if $opt_dirmfiles; +if (!(substr($var{'dirmfiles'}, -1, 1) eq $dirsep)) { $var{'dirmfiles'} = $var{'dirmfiles'}.$dirsep; } + +$var{'authorfile'} = $opt_author if $opt_author; +$var{'includesource'} = $opt_includesource if $opt_includesource; +if ($var{'includesource'} ne 'no') { $var{'includesource'} = 'yes'; } +$var{'processtree'} = $opt_processtree if $opt_processtree; +if ($var{'processtree'} ne 'no') { $var{'processtree'} = 'yes'; } +$var{'producetree'} = $opt_producetree if $opt_producetree; +if ($var{'producetree'} ne 'no') { $var{'producetree'} = 'yes'; } +if ($var{'processtree'} eq 'no') { $var{'producetree'} = 'no'; } +if ($var{'frames'} ne 'no') { $var{'frames'} = 'yes'; } +# if (($var{'processtree'} eq 'yes') && ($var{'producetree'} eq 'no')) { $var{'usecontentsm'} = 'no'; } + +$var{'writecontentsm'} = $opt_writecontentsm if $opt_writecontentsm; + +#------------------------------------------------------------------------ +# Do the real stuff +#------------------------------------------------------------------------ + +# Print variables on screen, when not silent +&ListVariables if !$opt_silent; + +# Check the author file +if ($var{'authorfile'} ne '') { + if (!($var{'authorfile'} =~ m,^/,)) { + # relative path: treat as relative to config file + my $cfd = $var{'configfile'}; + $cfd =~ s,/[^/]*$,/,; + $cfd =~ s,^[^/]*$,.,; + $var{'authorfile'} = "$cfd/" . $var{'authorfile'}; + } + if (&CheckFileName($var{'authorfile'}, 'author file')) { + $var{'authorfile'} = ''; + if (!$opt_silent) { print " Proceeding without author information!\n"; } + } +} + +# Call the function doing all the real work +&ConstructNameMatrix; + +&ConstructDependencyMatrix; + +&ConstructAllIndexFiles; + +&ConstructHTMLFiles; + +exit $exit_status; + +#------------------------------------------------------------------------ +# Construct list of all mfile names and initialize various data arrays. +#------------------------------------------------------------------------ +sub ConstructNameMatrix +{ + local(*MFILE); + local($file, $dirname); + local(@newdirectories); + local(%localnames); + + $RecDeep = 0; + &ParseTreeReadFiles($var{'dirmfiles'}, $RecDeep); + + foreach $dirname (@directories) { + if ($dirnumbermfiles{$dirname} > 0) { + push(@newdirectories, $dirname); + if (! defined($contentsname{$dirname})) { + $contentsname{$dirname} = 'Contents'; + if (($var{'writecontentsm'} eq 'no') && ($var{'usecontentsm'} eq 'yes')) { + print "\r ParseTree - for directory $dirname no contents file found!\n"; + print " create one or enable writing of contents file (writecontentsm = yes)!\n"; + } + } + } + } + @alldirectories = @directories; + @directories = @newdirectories; + + foreach $dirname (@directories) { + if ($debug > 0) { print "Dir: $dirname \t\t $dirnumbermfiles{$dirname} \t$contentsname{$dirname}\n"; } + } + + @names = sort(keys %mfile); + + # check, if name of directory is identical to name of file + @dirsinglenames = values(%dirnamesingle); + grep($localnames{$_}++, @dirsinglenames); + @dirandfilename = grep($localnames{$_}, @names); + if (@dirandfilename) { + print "\r Name clash between directory and file name: @dirandfilename\n"; + print " These files will be excluded from linking!\n"; + push(@excludenames, @dirandfilename); + } + + # construct names matrix for help text linking + # exclude some common words (and at the same time m-functions) from linking in help text + grep($localnames{$_}++, @excludenames); + @linknames = grep(!$localnames{$_}, @names); + + if ($debug > 2) { print "linknames (names of found m-files):\n @linknames\n"; } + +} + +#------------------------------------------------------------------------ +# Parse tree and collect all Files +#------------------------------------------------------------------------ +sub ParseTreeReadFiles +{ + local($dirname, $localRecDeep) = @_; + local($file, $name, $filewosuffix); + local($dirhtmlname, $dirmode); + local($relpath, $relpathtoindex, $replacevardir); + local(*CHECKDIR, *AKTDIR); + local(@ALLEFILES); + + opendir(AKTDIR, $dirname) || die "ParseTree - Can't open directory $dirname: $!"; + if ($debug > 1) { print "\nDirectory: $dirname\n"; } + + # create relative path + $_ = $dirname; $replacevardir = $var{'dirmfiles'}; + s/$replacevardir//; $relpath = $_; + s/[^\/]+/../g; $relpathtoindex = $_; + + # producetree no + if ($var{'producetree'} eq 'no') { $relpath = ''; $relpathtoindex = ''; } + + # names of directories (top-level and below top-level m-file-directory) + push(@directories, $dirname); + $dirnumbermfiles{$dirname} = 0; # set number of m-files for this dir to zero + # relative path from top-level directory, depends on directory name + $dirnamerelpath{$dirname} = $relpath; + # relative path from actual directory to top-level directory, depends on directory name + $dirnamerelpathtoindex{$dirname} = $relpathtoindex; + # recursion level for directory, depends on directory name + $dirnamerecdeep{$dirname} = $localRecDeep; + + # only the name of the directory, without path + $rindexprint = rindex($dirname, $dirsep, length($dirname)-2); + $rindsub = substr($dirname, $rindexprint+1, length($dirname)-$rindexprint-2); + $dirnamesingle{$dirname} = $rindsub; + + # create name of html-directories + $_ = $dirname; + s/$var{'dirmfiles'}/$var{'dirhtml'}/; + $dirhtmlname = $_; + if ($var{'producetree'} eq 'no') { $dirhtmlname = $var{'dirhtml'}; } + # try to open html directory, if error, then create directory, + # use same mode as for corresponding m-file directory + opendir(CHECKDIR,"$dirhtmlname") || do { + $dirmode = (stat($dirname))[2]; # print "$dirmode\n"; + mkdir("$dirhtmlname", $dirmode) || die ("Cannot create directory $dirhtmlname: $! !"); + }; + closedir(CHECKDIR); + + + # read everything from this directory and process them + @ALLEFILES = readdir(AKTDIR); + + foreach $file (@ALLEFILES) { + # exclude . and .. directories + next if $file eq '.'; next if $file eq '..'; + + # test for existense of entry (redundant, used for debugging) + if (-e $dirname.$file) { + # if it's a directory, call this function recursively + if (-d $dirname.$file) { + if ($var{'processtree'} eq 'yes') { + &ParseTreeReadFiles($dirname.$file.$dirsep, $localRecDeep+1); + } + } + # if it's a file - test for m-file, save name and create some arrays + elsif (-f $dirname.$file) { + if ($file =~ /\.$suffixforsearch$/i) { + # Remove the file suffix to establish the matlab identifiers + $filewosuffix = $file; + $filewosuffix =~ s/\.$suffixforsearch$//i; + # $filename = $name; + + # Contents file in unix must start with a capital letter (Contents.m) + # ensure, that m-file name is lower case, except the contents file + if (! ($filewosuffix =~ /^contents$/i)) { + # if ($var{'links2filescase'} eq 'low') { $filewosuffix = "\L$filewosuffix\E"; } + $filewosuffixlow = "\L$filewosuffix\E"; + } + else { $contentsname{$dirname} = $filewosuffix; } + + # internal handle name is always lower case + $name = $filewosuffixlow; + # file name is not lower case + $filename = $filewosuffix; + + # if don't use C|contents.m, then forget all C|contents.m + if ($var{'usecontentsm'} eq 'no') { if ($name =~ /contents/i) { next; } } + + # if m-file with this name already exists, use directory and name for name + # only the first occurence of name will be used for links + if (defined $mfile{$name}) { + if (! ($name =~ /^contents$/i) ) { + print "\r ParseTree - Name conflict: $name in $dirname already exists: $mfile{$name} !\n"; + print " $mfile{$name} will be used for links!\n"; + } + $name = $dirname.$name; + } + # mfile name with path + $mfile{$name} = $dirname.$file; + # mfile name (without path) + $mfilename{$name} = $filename; + # mfile directory + $mfiledir{$name} = $dirname; + + # html file name and full path, special extension of Contents files + if ($name =~ /contents/i) { $extrahtmlfilename = $dirnamesingle{$dirname}; } + else { $extrahtmlfilename = ''; } + $hfile{$name} = $dirhtmlname.$mfilename{$name}.$extrahtmlfilename.$var{'exthtml'}; + + # save relative html path + # if ($var{'producetree'} eq 'yes') { + $hfilerelpath{$name} = $relpath; + # } else { # if no tree to produce, relative path is empty + # $hfilerelpath{$name} = ''; + # } + + # create relative path from html file to directory with global index file + $hfileindexpath{$name} = $relpathtoindex; + + # Function declaration, if one exists, set default to script + $synopsis{$name} = ""; + $mtype{$name} = "script"; + + # First comment line + $apropos{$name} = ""; + + # count number of m-files in directories + $dirnumbermfiles{$dirname}++; + + if ($debug > 1) { + if ($opt_silent) { print "\r"; } + print " ParseTree: $name \t\t $mfile{$name} \t\t $hfile{$name}\t\t"; + if (!$opt_silent) { print "\n"; } + } + } + } + else { + print "Unknown type of file in $dirname: $file\n"; + } + } + else { print "Error: Not existing file in $dirname: $file\n"; } + } + + closedir(AKTDIR) + +} + +#------------------------------------------------------------------------ +# Construct Dependency matrix +# $dep{$x,$y} > 0 if $x includes a reference to $y. +#------------------------------------------------------------------------ +sub ConstructDependencyMatrix +{ + &ConstructDependencyMatrixReadFiles('all'); + &ConstructDependencyMatrixReally; +} + + +#------------------------------------------------------------------------ +# Construct Dependency matrix +# $dep{$x,$y} > 0 if $x includes a reference to $y. +#------------------------------------------------------------------------ +sub ConstructDependencyMatrixReadFiles +{ + local($whatstring) = @_; + local(*MFILE); + local($name, $inames); + local(%symbolsdep, %symbolsall); + + # Initialize as all zeros. + # foreach $name (@names) { grep($dep{$name,$_}=0,@names); if ($debug > 0) { print "\r DepMatrix anlegen: $name\t$#names\t"; } } + + # Compute the dependency matrix + $inames = -1; + foreach $name (@names) { + # Read each file and tabulate the distinct alphanumeric identifiers in + # an array of symbols. Also scan for: + # synopsis: The function declaration line + # apropos: The first line of the help text + + # look for whatstring, if all: process every file, if contents: process only contents files + if ($whatstring eq 'contents') { if (! ($name =~ /contents$/i) ) { next; } } + elsif ($whatstring eq 'all') { } # do nothing + else { print "\r ConstructDependency: Unknown parameter whatstring: $whatstring \n"; } + + undef %symbolsall; undef %symbolsdep; + open(MFILE,"<$mfile{$name}") || die("Can't open $mfile{$name}: $!\n"); + while (<MFILE>) { + chop; + + # Split on nonalphanumerics, then look for all words, used for links later + # this one for all references + @wordsall = grep(/[a-zA-Z]\w*/, split('\W',$_)); + # set all words to lower case for link checking + undef @wordsall2; + # do case conversion not, case checking is done later + foreach (@wordsall) { push(@wordsall2, "\L$_\E"); } + # @wordsall2 = @wordsall; + grep($symbolsall{$_}++, @wordsall2); + + # Store first comment line, skip all others. + if (/^\s*%/) { + if (!$apropos{$name}) { + s/^\s*%\s*//; # remove % and leading white spaces on line + $_ = &SubstituteHTMLEntities($_); + $apropos{$name} = $_; + } + next; + } + + # If it's the function declaration line, then store it and skip + # but only, when first function definition (multiple function lines when private subfunctions in file + if ($synopsis{$name} eq '') { + if (/^\s*function/) { + s/^\s*function\s*//; + $synopsis{$name} = $_; + $mtype{$name} = "function"; + next; + } + } + + # Split off any trailing comments + if ($_ ne '') { + # this one for references in program code only + # when syntax parsing, here is a working place + ($statement) = split('%',$_,1); + @wordsdep = grep(/[a-zA-Z]\w*/,split('\W',$statement)); + # do case conversion not, case checking is done later + undef @wordsdep2; + foreach (@wordsdep) { push(@wordsdep2, "\L$_\E"); } + grep($symbolsdep{$_}++, @wordsdep2); + } + } + close MFILE; + + # compute intersection between %symbolsall and @linknames + delete($symbolsall{$name}); + # foreach $localsumall ($symbolsall) { + # $localsumall = "\L$localsumall\E"; + # } + @{'all'.$name} = grep($symbolsall{$_}, @linknames); + + # compute intersection between %symbolsdep and @linknames + delete($symbolsdep{$name}); + @{'depcalls'.$name} = grep($symbolsdep{$_}, @linknames); + + $inames++; print "\r DepCallsMatrix: $inames/$#names\t $name\t"; + if ($debug > 2) { print "\n depnames: @{'depcalls'.$name}\n all: @{'all'.$name}\n"; } + } +} + + +#------------------------------------------------------------------------ +# Construct Dependency matrix +# $dep{$x,$y} > 0 if $x includes a reference to $y. +#------------------------------------------------------------------------ +sub ConstructDependencyMatrixReally +{ + local($inames, $name); + + $inames = -1; + foreach $name (@names) { undef %{'depint'.$name}; } + foreach $name (@names) { + grep(${'depint'.$_}{$name}++, @{'depcalls'.$name}); + $inames++; print "\r DepCalledMatrix1: $inames/$#names\t $name\t"; + } + $inames = -1; + foreach $name (@names) { + # compute intersection between %depint.name{$_} and @linknames + if (defined (%{'depint'.$name})) { @{'depcalled'.$name} = grep(${'depint'.$name}{$_}, @linknames); } + $inames++; print "\r DepCalledMatrix2: $inames/$#names\t $name\t"; + if ($debug > 2) { print "\n depcalled: @{'depcalled'.$name}\n"; } + } + +} + + +#======================================================================== +# Construct all index files +#======================================================================== +sub ConstructAllIndexFiles +{ + local(@localnames); + local($ActDir); + local($name); + + # define variables and names for frame target + $GlobalNameFrameMainLeft = 'Cont_Main'; + $GlobalNameFrameMainRight = 'Cont_Lower'; + $GlobalNameFrameAZIndexsmall = 'IndexAZindex'; + $GlobalNameFrameAZIndexjump = 'IndexAZjump'; + + $indexcreated = 0; + + &ConstructHighestIndexFile; + $indexcreated++; + + # if ($var{'producetree'} eq 'yes') { + # moved next 2 lines out of if for producetree no + # &ConstructHighestIndexFile; + # $indexcreated++; + + foreach $ActDir (@directories) { + undef @localnames; + foreach $name (@names) { + local($pathsubstr) = substr($mfile{$name}, 0, rindex($mfile{$name}, "/")+1); + if ($ActDir eq $pathsubstr) { + if ($debug > 1) { print "IndexFile: $pathsubstr ActDir: $ActDir Hfilerelpath: $hfilerelpath{$name}\n"; } + push(@localnames, $name); + } + } + if ($debug > 2) { print "localnames: @localnames\n"; } + # create contents file and short|long index of files in local directory + &ConstructContentsmFile($ActDir, @localnames); + &ConstructAZIndexFile($ActDir, 'short', 'local', @localnames); + &ConstructAZIndexFile($ActDir, 'long', 'local', @localnames); + $indexcreated+=2; + } + # } else { + # &ConstructContentsmFile($var{'dirmfiles'}, @names); + # } + + # create short|long index of files in all directory + &ConstructAZIndexFile($var{'dirmfiles'}, 'short', 'global', @names); + &ConstructAZIndexFile($var{'dirmfiles'}, 'long', 'global', @names); + $indexcreated+=2; + + # if contents.m were created or updated, the dependency matrices should + # be updated as well + if ($var{'writecontentsm'} eq 'yes') { &ConstructDependencyMatrixReadFiles('contents');; } +} + + +#======================================================================== +# Construct the highest level index file +#======================================================================== +sub ConstructHighestIndexFile +{ + local(*IFILE); + local($indexfile, $filename); + + # Build the frame layout file, this files includes the layout of the frames + # Build the frame layout file name (highest one) + $indexfile = $var{'dirhtml'}.$var{'filenametopframe'}.$var{'exthtml'}; + + if ($var{'frames'} eq 'yes') { + + open(IFILE,">$indexfile") || die("Cannot open frame layout file $indexfile\n"); + + # Write the header of frame file + print IFILE "$TextDocTypeFrame\n<html>\n<head>\n$var{'codeheadmeta'}\n$TextMetaCharset\n"; + print IFILE " <title>$var{'texttitleframelayout'}</title>\n"; + print IFILE "</head>\n"; + + # definition of 2 frames, left the tree of directories, + # right the index of that directory or the docu of a file + print IFILE "<frameset cols=\"25%,75%\">\n"; + print IFILE " <frame src=\"$var{'filenamedirshort'}$var{'exthtml'}\" name=\"$GlobalNameFrameMainLeft\" />\n"; + print IFILE " <frame src=\"$var{'filenameindexshortglobal'}$var{'filenameextensionframe'}$var{'exthtml'}\" name=\"$GlobalNameFrameMainRight\" />\n"; print IFILE "</frameset>\n"; + + print IFILE "</html>\n"; + + close(IFILE); + + if ($opt_silent) { print "\r"; } + print " Frame layout file created: $indexfile\t"; + if (!$opt_silent) { print "\n"; } + } + + for($irun=0; $irun <= 2; $irun++) { + # Build the top directory index file, these files include the directory tree + # Build the directory tree index file name + + # Create no directory file for contents, when no contents to use + if (($irun == 2) && ($var{'usecontentsm'} eq 'no')) { next; } + + # Assign the correct index file name + if ($irun == 0) { $filename = $var{'filenamedirshort'}; } + elsif ($irun == 1) { $filename = $var{'filenamedirlong'}; } + elsif ($irun == 2) { $filename = $var{'filenamedircontents'}; } + + $indexfile = $var{'dirhtml'}.$filename.$var{'exthtml'}; + + open(IFILE,">$indexfile") || die("Cannot open directory tree index file $indexfile\n"); + # Write header of HTML file + print IFILE "$TextDocTypeHTML\n<html>\n<head>\n$var{'codeheadmeta'}\n$TextMetaCharset\n$var{'csslink'}\n"; + + if ($var{'texttitleindexalldirs'} eq '') { + print IFILE "<title>Index of Directories of $var{'dirmfiles'}</title>\n"; + } else { + print IFILE "<title>$var{'texttitleindexalldirs'}</title>\n"; + } + + if ($var{'frames'} eq 'yes') { + print IFILE "<base target=\"$GlobalNameFrameMainRight\" />\n"; + } + + print IFILE "</head>\n"; + print IFILE "<body $var{'codebodyindex'}>\n"; + print IFILE "<div id=\"matlabdoc\">\n"; + if ($var{'textheaderindexalldirs'} eq '') { + print IFILE "<h1 $var{'codeheader'}>Index of Directories of <em>$var{'dirmfiles'}</em></h1>\n"; + } else { + print IFILE "<h1 $var{'codeheader'}>$var{'textheaderindexalldirs'}</h1>\n"; + } + print IFILE "<p>\n"; + + if ($var{'frames'} eq 'yes') { + if ($irun == 0) { print IFILE "<strong>short</strong>\n"; } + else { print IFILE "<a href=\"$var{'filenamedirshort'}$var{'exthtml'}\" target=\"$GlobalNameFrameMainLeft\">short</a>\n"; } + if ($irun == 1) { print IFILE " | <strong>long</strong>\n"; } + else { print IFILE " | <a href=\"$var{'filenamedirlong'}$var{'exthtml'}\" target=\"$GlobalNameFrameMainLeft\">long</a>\n"; } + if ($var{'usecontentsm'} eq 'yes') { + if ($irun == 2) { print IFILE " | <strong>contents</strong>\n"; } + else { print IFILE " | <a href=\"$var{'filenamedircontents'}$var{'exthtml'}\" target=\"$GlobalNameFrameMainLeft\">contents</a>\n"; } + } + } else { + if ($irun == 0) { print IFILE "<strong>short</strong>\n"; } + else { print IFILE "<a href=\"$var{'filenamedirshort'}$var{'exthtml'}\">short</a>\n"; } + if ($irun == 1) { print IFILE " | <strong>long</strong>\n"; } + else { print IFILE " | <a href=\"$var{'filenamedirlong'}$var{'exthtml'}\">long</a>\n"; } + if ($var{'usecontentsm'} eq 'yes') { + if ($irun == 2) { print IFILE " | <strong>contents</strong>\n"; } + else { print IFILE " | <a href=\"$var{'filenamedircontents'}$var{'exthtml'}\">contents</a>\n"; } + } + } + + print IFILE "</p><br />\n\n"; + print IFILE "<ul>\n"; + + # go through all directories and create a list entry for each one, + # depending on recursion level create sublists + $prevrecdeeplevel = 0; + foreach $name (@alldirectories) { + $actrecdeeplevel = $dirnamerecdeep{$name}; + for( ; $prevrecdeeplevel < $actrecdeeplevel; $prevrecdeeplevel++ ) { print IFILE "<ul>\n"; } + for( ; $prevrecdeeplevel > $actrecdeeplevel; $prevrecdeeplevel-- ) { print IFILE "</ul>\n"; } + if ($irun == 0) { $indexfilenameused = $var{'filenameindexshortlocal'}.$var{'filenameextensionframe'}; } + elsif ($irun == 1) { $indexfilenameused = $var{'filenameindexlonglocal'}.$var{'filenameextensionframe'}; } + elsif ($irun == 2) { $indexfilenameused = $contentsname{$name}; } + else { die "ConstructHighestIndexFile: Unknown value of irun"; } + if ($dirnumbermfiles{$name} > 0) { + # producetree no + # if ($var{'producetree'} eq 'no') { $dirnamehere = ''; } + # else { $dirnamehere = '$dirnamerelpath{$name}'; } + # print IFILE "<LI><A HREF=\"$dirnamehere$indexfilenameused_$dirnamesingle{$name}$var{'exthtml'}\">$dirnamesingle{$name}</A>\n"; + print IFILE "<li><a href=\"$dirnamerelpath{$name}$indexfilenameused$dirnamesingle{$name}$var{'exthtml'}\">$dirnamesingle{$name}</a></li>\n"; + } else { + # print directories with no m-files inside not + # print IFILE "<li>$dirnamesingle{$name}</li>\n"; + } + } + $actrecdeeplevel = 0; + for( ; $prevrecdeeplevel > $actrecdeeplevel; $prevrecdeeplevel-- ) { print IFILE "</ul>\n"; } + print IFILE "</ul>\n<br />$var{'codehr'}\n"; + + # Include info about author from authorfile + &WriteFile2Handle($var{'authorfile'}, IFILE); + + print IFILE "<!--navigate-->\n"; + print IFILE "<!--copyright-->\n"; + print IFILE "</div>\n</body>\n</html>\n"; + + close(IFILE); + + if ($opt_silent) { print "\r"; } + print " Directory - Indexfile created: $indexfile\t"; + if (!$opt_silent) { print "\n"; } + } +} + + +#======================================================================== +# Construct the A-Z index file (global/local and/or short/long) +#======================================================================== +sub ConstructAZIndexFile +{ + local($LocalActDir, $LocalShortLong, $LocalGlobalLocal, @localnames) = @_; + local(*IFILE); + local($name, $indexfilename, $dirpath); + local($firstletter, $firstone); + + if ($debug > 2) { print "localnames in AZ small: @localnames\n"; print " ActDir in A-Z: $LocalActDir\n"; } + + # extract filename of index file from parameters of function + if ($LocalShortLong eq 'short') { + if ($LocalGlobalLocal eq 'global') { $indexfilename = $var{'filenameindexshortglobal'}; } + elsif ($LocalGlobalLocal eq 'local') { $indexfilename = $var{'filenameindexshortlocal'}; } + else { die "wrong parameter for LocalGlobalLocal in ConstructAZIndexFile: $LocalGlobalLocal."; } + } elsif ($LocalShortLong eq 'long') { + if ($LocalGlobalLocal eq 'global') { $indexfilename = $var{'filenameindexlongglobal'}; } + elsif ($LocalGlobalLocal eq 'local') { $indexfilename = $var{'filenameindexlonglocal'}; } + else { die "wrong parameter for LocalGlobalLocal in ConstructAZIndexFile: $LocalGlobalLocal."; } + } else { die "wrong parameter for LocalShortLong in ConstructAZIndexFile: $LocalShortLong."; } + + # producetree no + # if ($var{'producetree'} eq 'no') { $dirnamehere = ''; } + # else { $dirnamehere = '$dirnamerelpath{$LocalActDir}'; } + # Build the index file name + # handle the global index file case separately (no extra directory name in file) + # the local index file name must be extended by the name of the directory + if ($LocalGlobalLocal eq 'global') { $extradirfilename = ''; } + else { $extradirfilename = $dirnamesingle{$LocalActDir}; } + $indexfile = $var{'dirhtml'}.$dirnamerelpath{$LocalActDir}.$indexfilename.$var{'filenameextensionindex'}.$extradirfilename.$var{'exthtml'}; + + if ($LocalShortLong eq 'short' and $extradirfilename eq '' and $var{'frames'} ne 'yes') { + # With no frames and no subdir path, this must go in the + # top-level index file instead + $indexfile = $var{'dirhtml'}.$var{'filenametopframe'}.$var{'exthtml'}; + } + + if ($debug > 2) { print " indexfilename (a-z small): $indexfile\n"; } + + open(IFILE,">$indexfile") || die("Cannot open index file $indexfile: $!\n"); + + # Write the header of HTML file + print IFILE "$TextDocTypeHTML\n<html>\n<head>\n$var{'codeheadmeta'}\n$TextMetaCharset\n$var{'csslink'}\n"; + + my $dirToPrint = $LocalActDir; + $dirToPrint =~ s,^./,,; + + if ($var{'texttitleindex'} eq '') { + print IFILE "<title>Index of Matlab Files in Directory $dirToPrint</title>\n"; + } else { + if ($LocalGlobalLocal eq 'global') { print IFILE "<title>$var{'texttitleindex'}</title>\n"; } + else { print IFILE "<title>$var{'texttitleindex'} in Directory $dirToPrint</title>\n"; } + } + + if ($var{'frames'} eq 'yes') { + print IFILE "<base target=\"$GlobalNameFrameMainRight\" />\n"; + } + print IFILE "</head>\n"; + + print IFILE "<body $var{'codebodyindex'}>\n"; + print IFILE "<div id=\"matlabdoc\">\n"; + if ($var{'textheaderindex'} eq '') { + print IFILE "<h1 $var{'codeheader'}>Index of Matlab Files in Directory $dirToPrint</h1>\n"; + } else { + if ($LocalGlobalLocal eq 'global') { print IFILE "<h1 $var{'codeheader'}>$var{'textheaderindex'}</h1>\n"; } + else { print IFILE "<h1 $var{'codeheader'}>$var{'textheaderindex'} in Directory $dirToPrint</h1>\n"; } + } + + # include links to indexes + &ConstructLinks2Index(IFILE, $dirnamerelpathtoindex{$LocalActDir}, $LocalActDir, $LocalGlobalLocal); + + # Collect the starting letters of m files in this directory or all m-files + for('a'..'z') { undef @{$_}; } + foreach $name (@localnames) { + if (! ($mfilename{$name} =~ /contents/i)) { + $firstletter = substr($mfilename{$name}, 0, 1); + # convert first letter always to lower case + # needed for reference to lower and upper case m-files + $firstletter = "\L$firstletter\E"; + push(@{$firstletter}, $name); + } + } + + if ($LocalShortLong eq 'short') { + # begin create short index + print IFILE "<table width=\"100%\">\n"; + + for('a'..'z') { + # print " $_: @{$_}\n"; + $numberofletter = $#{$_}+1; + $cols = 3; + if ($numberofletter > 0) { + print IFILE "\n<tr><td><br/><strong><a name=\"\U$_\E$_\"></a><span class=\"an\">\U$_\E</span></strong></td></tr>\n"; + for ($count = 0; $count < $numberofletter; $count++) { + if (($count % $cols) == 0) { + if ($count > 0) { + print IFILE "</tr><tr>\n"; + } + print IFILE "<tr><td></td>"; + } + $name = @{$_}[$count]; + if ($LocalGlobalLocal eq 'global') { $dirpath = $hfilerelpath{$name}; } else { $dirpath = ""; } + print IFILE "<td><a href=\"$dirpath$mfilename{$name}$var{'exthtml'}\">$mfilename{$name}</a></td>"; + } + + print IFILE "</tr>\n"; + + # $numberhalf = ($numberofletter + 1 - (($numberofletter+1) % 2))/2; + # if ($debug > 2) { print " $_: @{$_} \t $numberhalf \t $numberofletter\n"; } + # for($count = 0; $count < $numberhalf; $count++) { + # $name = @{$_}[$count]; + # if ($LocalGlobalLocal eq 'global') { $dirpath = $hfilerelpath{$name}; } else { $dirpath = ""; } + # print IFILE "<tr><td width=\"50%\"><a href=\"$dirpath$mfilename{$name}$var{'exthtml'}\">$mfilename{$name}</a></td>"; + # if (($count + $numberhalf) < $numberofletter) { + # $name = @{$_}[$count + $numberhalf]; + # if ($LocalGlobalLocal eq 'global') { $dirpath = $hfilerelpath{$name}; } else { $dirpath = ""; } + # print IFILE "<td width=\"50%\"><a href=\"$dirpath$mfilename{$name}$var{'exthtml'}\">$mfilename{$name}</a></td></tr>\n"; + # } else { + # print IFILE "<td width=\"50%\"></td></tr>\n"; + # } + # } + } + } + print IFILE "</table>\n<br />$var{'codehr'}\n"; + + } elsif ($LocalShortLong eq 'long') { + # begin create long index + print IFILE "<table width=\"100%\">\n"; + print IFILE "<tr><th>Name</th><th>Synopsis</th></tr>\n"; + + for('a'..'z') { + # print " $_: @{$_}\n"; + $numberofletter = $#{$_}+1; + if ($numberofletter > 0) { + $firstone = 1; + foreach $name (@{$_}) { + if ($debug > 1) { print " AZinforeach1: $name \t\t $hfilerelpath{$name} \t\t $dirnamerelpath{$LocalActDir}\n"; } + if ($LocalGlobalLocal eq 'global') { $dirpath = $hfilerelpath{$name}; } else { $dirpath = ""; } + if (! ($mfilename{$name} =~ /contents/i)) { + if ($firstone == 1) { print IFILE "\n<tr><td colspan=\"2\"><br /><strong><a name=\"\U$_\E$_\"></a><span class=\"an\">\U$_\E</span></strong></td></tr>\n"; $firstone = 0; } + print IFILE "<tr><td valign=\"top\"><a href=\"$dirpath$mfilename{$name}$var{'exthtml'}\">$mfilename{$name}</a></td><td>$apropos{$name}</td></tr>\n"; + } + } + } + } + print IFILE "</table>\n<br />$var{'codehr'}\n"; + } else { die "wrong parameter for LocalShortLong in ConstructAZIndexFile: $LocalShortLong."; } + + # Include info about author from authorfile + &WriteFile2Handle($var{'authorfile'}, IFILE); + + print IFILE "<!--navigate-->\n"; + print IFILE "<!--copyright-->\n"; + print IFILE "</div>\n</body>\n</html>\n"; + + close(IFILE); + + if ($opt_silent) { print "\r"; } + print " Indexfile small (A-Z) created: $indexfile\t"; + if (!$opt_silent) { print "\n"; } + + + # Build the A-Z jump index file name + # handle the global index file case separately (no extra directory name in file) + if ($LocalGlobalLocal eq 'global') { $extradirfilename = ''; } + else { $extradirfilename = $dirnamesingle{$LocalActDir}; } + + if ($var{'frames'} eq 'yes') { + + $indexfile = $var{'dirhtml'}.$dirnamerelpath{$LocalActDir}.$indexfilename.$var{'filenameextensionjump'}.$extradirfilename.$var{'exthtml'}; + if ($debug > 2) { print " indexfilename (a-z jump): $indexfile\n"; } + open(IFILE,">$indexfile") || die("Cannot open jump index file $indexfile: $!\n"); + + # Write the header of HTML file + print IFILE "$TextDocTypeHTML\n<html>\n<head>\n$var{'codeheadmeta'}\n$TextMetaCharset\n$var{'csslink'}\n"; + + if ($var{'texttitleindex'} eq '') { + print IFILE "<title>A-Z jump index in directory $dirToPrint</title>\n"; + } else { + if ($LocalGlobalLocal eq 'global') { print IFILE "<title>$var{'texttitleindex'}</title>\n"; } + else { print IFILE "<title>$var{'texttitleindex'} in Directory $dirToPrint</title>\n"; } + } + + if ($var{'frames'} eq 'yes') { + print IFILE "<base target=\"$GlobalNameFrameAZIndexsmall\" />\n"; + } + print IFILE "</head>\n"; + print IFILE "<body $var{'codebodyindex'}>\n"; + print IFILE "<div id=\"matlabdoc\">\n"; + + # Write the A-Z jump line, generate link for letters with files starting with this letter + # and only letters for no files starting with this letter + # use previously generated arrays with names of files sorted by starting letter + for('a'..'z') { + $numberofletter = $#{$_}+1; + if ($numberofletter > 0) { + print IFILE "<strong><a href=\"$indexfilename$var{'filenameextensionindex'}$extradirfilename$var{'exthtml'}#\U$_\E$_\">\U$_\E</a> </strong>\n"; + } else { + print IFILE "\U$_\E \n"; + } + } + + print IFILE "</div></body>\n</html>\n"; + + close(IFILE); + + if ($opt_silent) { print "\r"; } + print " Indexfile small (A-Z jump) created: $indexfile\t"; + if (!$opt_silent) { print "\n"; } + } + + + # Build the frame layout file, this file includes the layout of the frames + # Build the frame layout file name (for small/compact A-Z index) + # handle the global index file case separately (no extra directory name in file) + if ($LocalGlobalLocal eq 'global') { $extradirfilename = ''; } + else { $extradirfilename = $dirnamesingle{$LocalActDir}; } + + if ($var{'frames'} eq 'yes') { + + $indexfile = $var{'dirhtml'}.$dirnamerelpath{$LocalActDir}.$indexfilename.$var{'filenameextensionframe'}.$extradirfilename.$var{'exthtml'}; + if ($debug > 2) { print " indexfilename (a-z frame): $indexfile\n"; } + + open(IFILE,">$indexfile") || die("Cannot open jump index frame file $indexfile: $!\n"); + + # Write the header of Frame file + print IFILE "$TextDocTypeHTML\n<html>\n<head>\n$var{'codeheadmeta'}\n$TextMetaCharset\n$var{'csslink'}\n"; + + if ($var{'texttitleindex'} eq '') { + print IFILE "<title>Index of Matlab Files in Directory $dirToPrint</title>\n"; + } else { + if ($LocalGlobalLocal eq 'global') { print IFILE "<title>$var{'texttitleindex'}</title>\n"; } + else { print IFILE "<title>$var{'texttitleindex'} in Directory $dirToPrint</title>\n"; } + } + print IFILE "</head>\n"; + + # definition of 2 frames, top the A-Z index, below the jump letter line + print IFILE "<frameset rows=\"90%,10%\">\n"; + print IFILE " <frame src=\"$indexfilename$var{'filenameextensionindex'}$extradirfilename$var{'exthtml'}\" name=\"$GlobalNameFrameAZIndexsmall\" />\n"; + print IFILE " <frame src=\"$indexfilename$var{'filenameextensionjump'}$extradirfilename$var{'exthtml'}\" name=\"$GlobalNameFrameAZIndexjump\" />\n"; + print IFILE "</frameset>\n"; + + print IFILE "</html>\n"; + + close(IFILE); + + if ($opt_silent) { print "\r"; } + print " Frame layout file created: $indexfile\t"; + if (!$opt_silent) { print "\n"; } + } +} + + +#======================================================================== +# Construct the links to all indexes +#======================================================================== +sub ConstructLinks2Index +{ + local(*WRITEFILE, $LocalPath2Index, $PathContents, $LocalGlobalLocal) = @_; + + # include links to short/long - local/global index and C|contents.m + print WRITEFILE "\n<p>"; + print WRITEFILE "$var{'textjumpindexglobal'} "; + + if ($var{'frames'} eq 'yes') { + print WRITEFILE "<a href=\"$LocalPath2Index$var{'filenameindexshortglobal'}$var{'filenameextensionframe'}$var{'exthtml'}\">short</a> | "; + print WRITEFILE "<a href=\"$LocalPath2Index$var{'filenameindexlongglobal'}$var{'filenameextensionframe'}$var{'exthtml'}\">long</a>\n"; + } else { + print WRITEFILE "<a href=\"$LocalPath2Index$var{'filenametopframe'}$var{'exthtml'}\">short</a> | "; + print WRITEFILE "<a href=\"$LocalPath2Index$var{'filenameindexlongglobal'}$var{'filenameextensionindex'}$var{'exthtml'}\">long</a>\n"; + } + + if ($LocalGlobalLocal eq 'local') { + if ($var{'usecontentsm'} eq 'yes') { + print WRITEFILE " | <a href=\"$contentsname{$PathContents}$dirnamesingle{$PathContents}$var{'exthtml'}\">Local contents</a>\n"; + } + if ($var{'frames'} eq 'yes') { + print WRITEFILE " | $var{'textjumpindexlocal'} "; + print WRITEFILE "<a href=\"$var{'filenameindexshortlocal'}$var{'filenameextensionframe'}$dirnamesingle{$PathContents}$var{'exthtml'}\">short</a> | "; + print WRITEFILE "<a href=\"$var{'filenameindexlonglocal'}$var{'filenameextensionframe'}$dirnamesingle{$PathContents}$var{'exthtml'}\">long</a>\n"; + } else { + print WRITEFILE " | $var{'textjumpindexlocal'} "; + print WRITEFILE "<a href=\"$var{'filenameindexshortlocal'}$var{'filenameextensionindex'}$dirnamesingle{$PathContents}$var{'exthtml'}\">short</a> | "; + print WRITEFILE "<a href=\"$var{'filenameindexlonglocal'}$var{'filenameextensionindex'}$dirnamesingle{$PathContents}$var{'exthtml'}\">long</a>\n"; + } + } + print WRITEFILE "</p>\n\n"; + print WRITEFILE "$var{'codehr'}\n"; +} + + +#======================================================================== +# Construct the contents.m files or update +#======================================================================== +sub ConstructContentsmFile +{ + local($LocalActDir, @localnames) = @_; + local(*CFILE, $name,$newline); + local($contentsfile, $isincontentsonly); + local(@lines, @autoaddlines, @emptylines); + local($autoadd) = 'AutoAdd'; + local($autoaddsection) = 0; + local($emptylineflag) = 0; + local(%nameincontents); + + # Build the contents file name + $contentsfile = $LocalActDir.$contentsname{$LocalActDir}.$suffix; + + if (-e $contentsfile) { + open(CFILE,"<$contentsfile") || die("Cannot open contents file $contentsfile: $!\n"); + while (<CFILE>) { + # Search for the specified string pattern + @words = split; + if ((@words >= 3) && ($words[2] eq '-')) { + $isincontentsonly = 0; + foreach $name (@localnames) { + if ($name eq $words[1]) { # old + # if ($mfilename{$name} eq $words[1]) { + $isincontentsonly = 1; + $nameincontents{$name} = 1; + $newline = sprintf("%% %-13s - %s\n", $mfilename{$name}, $apropos{$name}); + push(@lines, $newline); + } + } + # issue a warning, if file is in contents, but not as file in the directory + if ($isincontentsonly == 0) { + print "\rConstructContents: Obsolete entry $words[1] in $contentsfile ! Entry not used.\n"; + } + } else { + # look for the AutoAdd section, should be the second word + if ((@words >= 2) && ($words[1] eq $autoadd)) { $autoaddsection = 1; } + # push the red line in an array + push(@lines, $_); + } + } + close(CFILE); + } else { + $newline = "% MATLAB Files in directory $LocalActDir\n%\n"; + push(@lines, $newline); + + } + + # collect the file names, that were not included in original C|contents.m + foreach $name (@localnames) { + if (! defined $nameincontents{$name}) { + if (! ($mfilename{$name} =~ /contents/i)) { + $newline = sprintf("%% %-13s - %s\n", $mfilename{$name}, $apropos{$name}); + push(@autoaddlines, $newline); + } + } + } + + # write/update C|contents.m only if variable is set + if ($var{'writecontentsm'} eq 'yes') { + unlink($contentsfile); + open(CFILE,">$contentsfile") || die("Cannot open contents file $contentsfile: $!\n"); + # write old C|contents.m or header of new file, as long as comment lines + foreach $line (@lines) { + if ($emptylineflag == 0) { + if ($line =~ /^\s*%/) { print CFILE $line; } + else { $emptylineflag = 1; push(@emptylines, $line); } + } else { push(@emptylines, $line); } + } + # add header of AutoAdd section + if (($autoaddsection == 0) && (@autoaddlines > 0)) { print CFILE "%\n% $autoadd\n"; } + # add autoadd section lines (previously undocumented files + foreach $line (@autoaddlines) { print CFILE $line; } + # add tail of original C|contents.m (everything behind first non-comment line) + foreach $line (@emptylines) { print CFILE $line; } + print CFILE "\n"; + close CFILE; + if ($opt_silent) { print "\r"; } + print " Contents file created/updated: $contentsfile\t"; + if (!$opt_silent) { print "\n"; } + } +} + + +#======================================================================== +# Replace found special characters with their HTMl Entities +#======================================================================== +sub SubstituteHTMLEntities { + local($_) = @_; + + # Replace & <-> & < <-> < > <-> > " <-> " + s/&/&/g; s/\</</g; s/\>/>/g; s/\"/"/g; + return $_; +} + +#======================================================================== +# Replace found m-filenamestring with full link. +#======================================================================== +sub SubstituteName2Link { + local($_, $funname) = @_; + local($refstr1, $refstr2, $reffound); + + # Look for something matching in the line + if ( /(\W+)($funname)(\W+)/i ) { + $reffound = $2; + $refstr1 = "<a class=\"mfun\" href=\"$hfileindexpath{$name}$hfilerelpath{$funname}$mfilename{$funname}$var{'exthtml'}\">"; + $refstr2 = "<\/a>"; + # Do links only for exact case match + if ( ($var{'links2filescase'} eq 'exact') || ($var{'links2filescase'} eq 'exactvery') ) { + if ( /(\W+)($funname)(\W+)/g ) { + s/(\W+)($funname)(\W+)/$1$refstr1$funname$refstr2$3/g; + } + else { + # Print info for not matching case in references, good for check up of files + if ( ($var{'links2filescase'} eq 'exactvery') ) { + print "Diff in case found: $funname (case of file name) <-> $reffound (case in source code)\n"; + print " (source line) $_ \n"; + } + } + } + # Do links for exact match and additionally for all upper case (often used in original matlab help text) + elsif ( ($var{'links2filescase'} eq 'exactupper') ) { + s/(\W+)($funname)(\W+)/$1$refstr1$2$refstr2$3/g; + $funname2 = "\U$funname\E"; + s/(\W+)($funname2)(\W+)/$1$refstr1$2$refstr2$3/g; + } + # Do links for all case mixes, this calls for trouble under LINUX/UNIX + else { #elsif ( ($var{'links2filescase'} eq 'all') ) + s/(\W+)($funname)(\W+)/$1$refstr1$2$refstr2$3/ig; + } + } + + return $_; +} + +#======================================================================== +# Construct the html files for each matlab file. +# Need to reread each matlab file to find the help text. +# Note that we can't do this in a single loop because sometimes +# the help text maybe before the function declaration. +#======================================================================== +sub ConstructHTMLFiles +{ + local(*MFILE); + local(*HFILE); + + local($filescreated) = 0; + local($functionline); + + foreach $name (@names) { + # Create cross reference information already here, used for keywords as well + # Construct list of referenced functions + @xref = @{'depcalls'.$name}; # the functions, that this m-file calls + @yref = @{'depcalled'.$name}; # the functions, that this m-file is called from + # print " depcalls: @{'depcalls'.$name}\n depcalled: @{'depcalled'.$name}\n"; + # foreach $cname (@names) { next if $cname eq $name; push(@yref,$cname) if grep(/$name/,@{'depcalls'.$cname}); } + + + # Open m-file and html-file + open(MFILE,"<$mfile{$name}"); + open(HFILE,">$hfile{$name}"); + + # Write the header of HTML file + print HFILE "$TextDocTypeHTML\n<html>\n<head>\n$var{'codeheadmeta'}\n$TextMetaCharset\n$var{'csslink'}\n"; + + # Write meta tags: use apropos (one line function description) for description + # and cross reference function names for keywords (any better ideas?) + print HFILE "<meta name=\"description\" content=\" $apropos{$name} \" />\n"; + print HFILE "<meta name=\"keywords\" content=\" @xref @yref \" />\n"; + + # Write Title and start body of html-file + print HFILE "<title>$var{'texttitlefiles'} $mfilename{$name}</title>\n</head>\n"; + print HFILE "<body $var{'codebodyfiles'}>\n"; + print HFILE "<div id=\"matlabdoc\">\n"; + print HFILE "<h1 $var{'codeheader'}>$var{'textheaderfiles'} $mfilename{$name}</h1>\n"; + +# http://test.soundsoftware.ac.uk/cannam/projects/smallbox/repository/annotate/DL/RLS-DLA/SolveFISTA.m +# print HFILE "<a href=\"" . $hfileindexpath{$name} . "../../projects/smallbox/repository/annotate/" . $mfiledir{$name} . $mfilename{$name} . ".m\">View in repository</a>\n"; + + print HFILE "$var{'codehr'}\n"; + + # include links to short/long - local/global index and C|contents.m + &ConstructLinks2Index(HFILE, $hfileindexpath{$name}, $mfiledir{$name}, 'local'); + + # If this is a function, then write out the first line as a synopsis + if ($mtype{$name} eq "function") { + print HFILE "<h2 $var{'codeheader'}>Function Synopsis</h2>\n"; + print HFILE "<pre>$synopsis{$name}</pre>\n$var{'codehr'}\n"; + } + + # Look for the matlab help text block + $functionline = "\n"; + do { + $_ = <MFILE>; + # remember functionline, if before help text block + if (/^\s*function/) { $functionline = $_; } + } until (/^\s*%/ || eof); + if (! (eof(MFILE))) { + print HFILE "<h2 $var{'codeheader'}>Help text</h2>\n"; + print HFILE "<pre>\n"; + while (/^\s*%/) { + # First remove leading % and white space, then Substitute special characlers + s/^\s*%//; + $_ = &SubstituteHTMLEntities($_); + + # check/create cross references + foreach $funname (@{'all'.$name}) { + if ($funname =~ /simulink/) { print "\n Simulink - Filename: $name; scanname: $funname\n"; } + next if $funname eq $name; + $_ = &SubstituteName2Link($_, $funname); + } + print HFILE $_; + if (! eof) { $_ = <MFILE>; } + } + print HFILE "</pre>\n$var{'codehr'}\n"; + } + + # Write the cross reference information + if (@xref || @yref) { + print HFILE "<h2 $var{'codeheader'}>Cross-Reference Information</H2>\n"; + print HFILE "<table border=\"0\" width=\"100%\">\n<tr align=\"left\">\n<th width=\"50%\">"; + if (@xref) { + print HFILE "This $mtype{$name} calls"; + } + print HFILE "</th>\n<th width=\"50%\">"; + if (@yref) { + print HFILE "This $mtype{$name} is called by"; + } + print HFILE "</th>\n</tr>\n<tr valign=\"top\"><td>"; + if (@xref) { + print HFILE "\n<ul>\n"; + foreach $cname (sort @xref) { + print HFILE "<li><a class=\"mfun\" href=\"$hfileindexpath{$name}$hfilerelpath{$cname}$mfilename{$cname}$var{'exthtml'}\">$mfilename{$cname}</a></li>\n"; + } + print HFILE "</ul>\n"; + } + print HFILE "</td><td>"; + if (@yref) { + print HFILE "\n<ul>\n"; + foreach $cname (sort @yref) { + print HFILE "<li><a class=\"mfun\" href=\"$hfileindexpath{$name}$hfilerelpath{$cname}$mfilename{$cname}$var{'exthtml'}\">$mfilename{$cname}</a></li>\n"; + } + print HFILE "</ul>\n"; + } + print HFILE "</td>\n</tr>\n</table>\n"; + print HFILE "$var{'codehr'}\n"; + } + + # Include source text if requested + if (($var{'includesource'} eq 'yes') && (! ($mfilename{$name} =~ /^contents$/i))) { + print HFILE "<h2 $var{'codeheader'}>Listing of $mtype{$name} $mfilename{$name}</h2>\n"; + seek(MFILE,0,0); + print HFILE "<pre>\n"; + $IsStillHelp = 2; + print HFILE $functionline; # functionline from scanning of help + while (<MFILE>) { + if ($IsStillHelp == 2) { + next if (/^\s*$/); + next if (/^\s*function/); + if (/^\s*%/) { $IsStillHelp = 1; next; } + } elsif ($IsStillHelp == 1) { + next if (/^\s*%/); + $IsStillHelp = 0; + } + + # Substritute special characters + $_ = &SubstituteHTMLEntities($_); + + # check for comment in line and format with css em + s/^(.*)%(.*?)([\s\r\n]+)$/$1<em class=\"mcom\">%$2<\/em>$3/; + + # check/create cross references + foreach $funname (@{'all'.$name}) { + next if $funname eq $name; + $_ = &SubstituteName2Link($_, $funname); + } + print HFILE $_; + } + print HFILE "</pre>\n$var{'codehr'}\n"; + } + + # Include info about author from authorfile + &WriteFile2Handle($var{'authorfile'}, HFILE) ; + + print HFILE "<!--navigate-->\n"; + print HFILE "<!--copyright-->\n"; + print HFILE "</div>\n</body>\n</html>\n"; + close(MFILE); + close(HFILE); + + # Print name of finished file + if ($opt_silent) { print "\r"; } + print " HTML-File created: $hfile{$name}\t"; + if (!$opt_silent) { print "\n"; } + $filescreated++; + } + + print "\n$PROGRAM: $indexcreated index and $filescreated files created.\n"; +} + +#======================================================================== +# Function: CheckFileName +# Purpose: . +#======================================================================== +sub CheckFileName { + local($filename, $description) = @_; + local(*CHECKFILE); + + open(CHECKFILE,"<$filename") || do { + if ($description eq '') {$description = 'file';} + # if (!$opt_silent) { print "Cannot open $description $filename: $!\n"; } + print "Cannot open $description $filename: $!\n"; + return 1; + }; + close(CHECKFILE); + return 0; + +} + +#======================================================================== +# Function: CheckDirName +# Purpose: . +#======================================================================== +sub CheckDirName { + local($dirname, $description) = @_; + local(*CHECKDIR); + + opendir(CHECKDIR,"$dirname") || die ("Cannot open $description directory $dirname: $!\n"); + closedir(CHECKDIR); +} + +#======================================================================== +# Function: WriteFile2Handle +# Purpose: . +#======================================================================== +sub WriteFile2Handle { + local($filename, *WRITEFILE) = @_; + local(*READFILE); + + if ($filename ne '') { + open(READFILE,"<$filename"); + @filecontents = <READFILE>; + close(READFILE); + print WRITEFILE "@filecontents\n"; + # if (!$opt_silent) {print " Contents of $filename added\n"}; + } +} + + +#======================================================================== +# Function: GetConfigFile +# Purpose: Read user's configuration file, if such exists. +#======================================================================== +sub GetConfigFile +{ + local($filename) = @_; + local(*CONFIG); + local($value); + + if (&CheckFileName($filename, 'configuration file')) { + # if (!$opt_silent) { print " Proceeding using built-in defaults for configuration.\n"; } + print " Proceeding using built-in defaults for configuration.\n"; + return 0; + }; + + open(CONFIG,"< $filename"); + while (<CONFIG>) { + s/#.*$//; + next if /^\s*$/o; + + # match keyword: process one or more arguments + # keyword set + if (/^\s*set\s+(\S+)\s*=\s*(.*)/) { + # setting a configuration variable + if (defined $var{$1}) { + $var{$1} = $2; + if ($debug > 3) { print "$1: $var{$1}\n"; } + } + else { + print "$PROGRAM: unknown variable `$1' in configuration file\n" + } + } else { + chop($_); + print "$PROGRAM: unknown keyword in configuration file in line: `$_'\n" + } + } + close CONFIG; + 1; +} + + +#------------------------------------------------------------------------ +# DisplayHelp - display help text using -h or -help command-line switch +#------------------------------------------------------------------------ +sub DisplayHelp +{ + $help=<<EofHelp; + $PROGRAM v$VERSION - generate html documentation from Matlab m-files + + Usage: $PROGRAM [-h] [-c config_file] [-m|dirmfiles matlab_dir] [-d|dirhtml html_dir] + [-i yes|no] [-r yes|no] [-p yes|no] [-quiet|q] [-a authorfile] + + $PROGRAM is a perl script that reads each matlab .m file in a directory + to produce a corresponding .html file of help documentation and cross + reference information. An index file is written with links to all of + the html files produced. The options are: + + -quiet or -q : be silent, no status information during generation + -help or -h : display this help message + -todo or -t : print the todo list for $PROGRAM + -version or -v : display version + + -configfile or -c : name of configuration file (default to $var{'configfile'}). + -dirmfiles or -m : top level directory containing matlab files to generate html for; + default to actual directory. + -dirhtml or -d : top level directory for generated html files; + default to actual directory. + + -includesource or -i : Include matlab source in the html documentation [yes|no] + default to yes. + -processtree or -r : create docu for m-file directory and all subdirectories [yes|no]; + default to yes. + -producetree or -p : create multi-level docu identical to directory structure + of m-files [yes|no]; default to yes. + -writecontentsm or -w: update or write contents.m files into the matlab source + directories [yes|no]; default to no. + + -authorfile or -a : name of file including author information, last element in html; + default to empty. + + The command line setting overwrite all other settings (built-in and configuration file). + The configuration file settings overwrite the built-in settings (and not the command + line settings). + + Typical usages are: + $PROGRAM + (use default parameters from perl script, if configuration + file is found -> generation of docu, else display of help) + + $PROGRAM -dirmfiles matlab -dirhtml html + (generate html documentation for all m-files in directory matlab, + place html files in directory html, use built-in defaults for + all other parameters, this way all m-files in the directory + matlab and below are converted and the generated html-files are + placed in the directory html and below producing the same + directory structure than below matlab) + + $PROGRAM -quiet + (use built-in parameters from perl script, if configuration + file is found use these settings as well, do generation, + no display except critical errors, status of conversion and result) + + $PROGRAM -m toolbox -dirhtml doc/html -r yes -p no + (convert all m-files in directory toolbox and below and place + the generated html files in directory doc/html, read all m-files + recursively, however, the generated html files are placed in one + directory) + + $PROGRAM -m toolbox -dirhtml doc/html -i no -r no + (convert all m-files in directory toolbox and place + the generated html files in directory doc/html, do not read m-files + recursively, do not include source code in documentation) + +EofHelp + + die "$help"; +} + +#------------------------------------------------------------------------ +# DisplayTodo - display ToDo list using -t or -todo command-line switch +#------------------------------------------------------------------------ +sub DisplayTodo +{ + $todo=<<EofToDo; + $PROGRAM v$VERSION - ToDo list + + o use more than one high level directory + + o what should/could be done here??? + +EofToDo + + die "$todo"; +} + + +#------------------------------------------------------------------------ +# ListVariables - list all defined variables and their values +#------------------------------------------------------------------------ +sub ListVariables +{ + local($value); + + if ($debug > 0) { + print "List of all variables and their values\n"; + foreach (sort keys %var) + { + if ($var{$_} eq '') { + $value = "empty"; + } else { + $value = $var{$_}; + } + print " $_\n $value\n"; + } + print "\n\n"; + } +} + + +__END__ +:endofperl
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/extra/soundsoftware/reposman-soundsoftware.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,369 @@ +#!/usr/bin/env ruby + +# == Synopsis +# +# reposman: manages your repositories with Redmine +# +# == Usage +# +# reposman [OPTIONS...] -s [DIR] -r [HOST] +# +# Examples: +# reposman --scm-dir=/var/svn --redmine-host=redmine.example.net --scm subversion +# reposman -s /var/git -r redmine.example.net -u http://svn.example.net --scm git +# +# == Arguments (mandatory) +# +# -s, --scm-dir=DIR use DIR as base directory for repositories +# -r, --redmine-host=HOST assume Redmine is hosted on HOST. Examples: +# -r redmine.example.net +# -r http://redmine.example.net +# -r https://example.net/redmine +# -k, --key=KEY use KEY as the Redmine API key (you can use the +# --key-file option as an alternative) +# +# == Options +# +# -o, --owner=OWNER owner of the repository. using the rails login +# allow user to browse the repository within +# Redmine even for private project. If you want to +# share repositories through Redmine.pm, you need +# to use the apache owner. +# -g, --group=GROUP group of the repository. (default: root) +# --scm=SCM the kind of SCM repository you want to create (and +# register) in Redmine (default: Subversion). +# reposman is able to create Git and Subversion +# repositories. For all other kind, you must specify +# a --command option +# -u, --url=URL the base url Redmine will use to access your +# repositories. This option is used to automatically +# register the repositories in Redmine. The project +# identifier will be appended to this url. Examples: +# -u https://example.net/svn +# -u file:///var/svn/ +# if this option isn't set, reposman will register +# the repositories with local file paths in Redmine +# -c, --command=COMMAND use this command instead of "svnadmin create" to +# create a repository. This option can be used to +# create repositories other than subversion and git +# kind. +# This command override the default creation for git +# and subversion. +# --http-user=USER User for HTTP Basic authentication with Redmine WS +# --http-pass=PASSWORD Password for Basic authentication with Redmine WS +# --key-file=PATH path to a file that contains the Redmine API key +# (use this option instead of --key if you don't +# the key to appear in the command line) +# -t, --test only show what should be done +# -h, --help show help and exit +# -v, --verbose verbose +# -V, --version print version and exit +# -q, --quiet no log +# +# == References +# +# You can find more information on the redmine's wiki : http://www.redmine.org/wiki/redmine/HowTos + + +require 'getoptlong' +#require 'rdoc/usage' +require 'find' +require 'etc' + +Version = "1.3" +SUPPORTED_SCM = %w( Subversion Darcs Mercurial Bazaar Git Filesystem ) + +opts = GetoptLong.new( + ['--scm-dir', '-s', GetoptLong::REQUIRED_ARGUMENT], + ['--redmine-host', '-r', GetoptLong::REQUIRED_ARGUMENT], + ['--key', '-k', GetoptLong::REQUIRED_ARGUMENT], + ['--key-file', GetoptLong::REQUIRED_ARGUMENT], + ['--owner', '-o', GetoptLong::REQUIRED_ARGUMENT], + ['--group', '-g', GetoptLong::REQUIRED_ARGUMENT], + ['--url', '-u', GetoptLong::REQUIRED_ARGUMENT], + ['--command' , '-c', GetoptLong::REQUIRED_ARGUMENT], + ['--scm', GetoptLong::REQUIRED_ARGUMENT], + ['--http-user', GetoptLong::REQUIRED_ARGUMENT], + ['--http-pass', GetoptLong::REQUIRED_ARGUMENT], + ['--test', '-t', GetoptLong::NO_ARGUMENT], + ['--verbose', '-v', GetoptLong::NO_ARGUMENT], + ['--version', '-V', GetoptLong::NO_ARGUMENT], + ['--help' , '-h', GetoptLong::NO_ARGUMENT], + ['--quiet' , '-q', GetoptLong::NO_ARGUMENT] + ) + +$verbose = 0 +$quiet = false +$redmine_host = '' +$repos_base = '' +$http_user = '' +$http_pass = '' +$svn_owner = 'root' +$svn_group = 'root' +$use_groupid = true +$svn_url = false +$test = false +$scm = 'Subversion' + +def log(text, options={}) + level = options[:level] || 0 + puts text unless $quiet or level > $verbose + exit 1 if options[:exit] +end + +def system_or_raise(command) + raise "\"#{command}\" failed" unless system command +end + +def usage + puts "See source code for supported options" + exit +end + +module SCM + + module Subversion + def self.create(path) + system_or_raise "svnadmin create #{path}" + end + end + + module Git + def self.create(path) + Dir.mkdir path + Dir.chdir(path) do + system_or_raise "git --bare init --shared" + system_or_raise "git update-server-info" + end + end + end + +end + +begin + opts.each do |opt, arg| + case opt + when '--scm-dir'; $repos_base = arg.dup + when '--redmine-host'; $redmine_host = arg.dup + when '--key'; $api_key = arg.dup + when '--key-file' + begin + $api_key = File.read(arg).strip + rescue Exception => e + $stderr.puts "Unable to read the key from #{arg}: #{e.message}" + exit 1 + end + when '--owner'; $svn_owner = arg.dup; $use_groupid = false; + when '--group'; $svn_group = arg.dup; $use_groupid = false; + when '--url'; $svn_url = arg.dup + when '--scm'; $scm = arg.dup.capitalize; log("Invalid SCM: #{$scm}", :exit => true) unless SUPPORTED_SCM.include?($scm) + when '--http-user'; $http_user = arg.dup + when '--http-pass'; $http_pass = arg.dup + when '--command'; $command = arg.dup + when '--verbose'; $verbose += 1 + when '--test'; $test = true + when '--version'; puts Version; exit + when '--help'; usage + when '--quiet'; $quiet = true + end + end +rescue + exit 1 +end + +if $test + log("running in test mode") +end + +# Make sure command is overridden if SCM vendor is not handled internally (for the moment Subversion and Git) +if $command.nil? + begin + scm_module = SCM.const_get($scm) + rescue + log("Please use --command option to specify how to create a #{$scm} repository.", :exit => true) + end +end + +$svn_url += "/" if $svn_url and not $svn_url.match(/\/$/) + +if ($redmine_host.empty? or $repos_base.empty?) + usage +end + +unless File.directory?($repos_base) + log("directory '#{$repos_base}' doesn't exist", :exit => true) +end + +begin + require 'active_resource' +rescue LoadError + log("This script requires activeresource.\nRun 'gem install activeresource' to install it.", :exit => true) +end + +class Project < ActiveResource::Base + self.headers["User-agent"] = "SoundSoftware repository manager/#{Version}" + self.format = :xml +end + +log("querying Redmine for projects...", :level => 1); + +$redmine_host.gsub!(/^/, "http://") unless $redmine_host.match("^https?://") +$redmine_host.gsub!(/\/$/, '') + +Project.site = "#{$redmine_host}/sys"; +Project.user = $http_user; +Project.password = $http_pass; + +begin + # Get all active projects that have the Repository module enabled + projects = Project.find(:all, :params => {:key => $api_key}) +rescue ActiveResource::ForbiddenAccess + log("Request was denied by your Redmine server. Make sure that 'WS for repository management' is enabled in application settings and that you provided the correct API key.") +rescue => e + log("Unable to connect to #{Project.site}: #{e}", :exit => true) +end + +if projects.nil? + log('No project found, perhaps you forgot to "Enable WS for repository management"', :exit => true) +end + +log("found #{projects.size} projects at " + Time.now.inspect); + +def set_owner_and_rights(project, repos_path, &block) + if mswin? + yield if block_given? + else + uid, gid = Etc.getpwnam($svn_owner).uid, ($use_groupid ? Etc.getgrnam(project.identifier).gid : Etc.getgrnam($svn_group).gid) + right = project.is_public ? 02775 : 02770 + yield if block_given? + Find.find(repos_path) do |f| + File.chmod right, f + File.chown uid, gid, f + end + end +end + +def other_read_right?(file) + (File.stat(file).mode & 0007).zero? ? false : true +end + +def owner_name(file) + mswin? ? + $svn_owner : + Etc.getpwuid( File.stat(file).uid ).name +end + +def mswin? + (RUBY_PLATFORM =~ /(:?mswin|mingw)/) || (RUBY_PLATFORM == 'java' && (ENV['OS'] || ENV['os']) =~ /windows/i) +end + +projects.each do |project| + log("inspecting project #{project.name}", :level => 1) + + if project.identifier.empty? + log("\tno identifier for project #{project.name}!") + next + elsif not project.identifier.match(/^[a-z0-9_\-]+$/) + log("\tinvalid identifier for project #{project.name} : #{project.identifier}!"); + next; + end + + repos_path = File.join($repos_base, project.identifier).gsub(File::SEPARATOR, File::ALT_SEPARATOR || File::SEPARATOR) + + create_repos = false + # Logic required for SoundSoftware.ac.uk repositories: + # + # * If the project has a repository path declared already, + # - if it's a local path, + # - if it does not exist + # - if it has the right root + # - create it + # - else + # - leave alone (remote repository) + # * else + # - create repository with same name as project + # - set to project + + if project.respond_to?(:repository) + + repos_url = project.repository.url; + log("\texisting url for project #{project.identifier} is #{repos_url}", :level => 2); + + if repos_url.match(/^file:\//) || repos_url.match(/^\//) + + repos_url = repos_url.gsub(/^file:\/*/, "/"); + log("\tthis is a local file path, at #{repos_url}", :level => 2); + + if repos_url.slice(0, $repos_base.length) != $repos_base + # leave repos_path set to our original suggestion + log("\tpreparing to replace incorrect repo location #{repos_url} for #{project.name} with #{repos_path}"); + create_repos = true + else + if !File.directory?(repos_url) + log("\tpreparing to create repo for #{project.name} at #{repos_url}"); + repos_path = repos_url + create_repos = true + else + log("\tit exists and is in the right place", :level => 2); + end + end + else + log("\tthis is a remote path, leaving alone", :level => 2); + end + else + log("\tpreparing to set repo location and create for #{project.name} at #{repos_url}") +# if File.directory?(repos_path) +# log("\trepository path #{repos_path} already exists, not creating") +# else + create_repos = true +# end + end + + if create_repos + + registration_url = repos_path + if $svn_url + registration_url = "#{$svn_url}#{project.identifier}" + end + + if $test + log("\tproposal: create repository #{repos_path}") + log("\tproposal: register repository #{repos_path} in Redmine with vendor #{$scm}, url #{registration_url}") + next + end + +# No -- we need "other" users to be able to read it. Access control +# is not handled through Unix user id anyway +# project.is_public ? File.umask(0002) : File.umask(0007) + File.umask(0002) + + log("\taction: create repository #{repos_path}") + + begin + if !File.directory?(repos_path) + set_owner_and_rights(project, repos_path) do + if scm_module.nil? + log("\trunning command: #{$command} #{repos_path}") + system_or_raise "#{$command} #{repos_path}" + else + scm_module.create(repos_path) + end + end + end + rescue => e + log("\tunable to create #{repos_path} : #{e}\n") + next + end + + begin + log("\taction: register repository #{repos_path} in Redmine with vendor #{$scm}, url #{registration_url}"); + project.post(:repository, :vendor => $scm, :repository => {:url => "#{registration_url}"}, :key => $api_key) + rescue => e + log("\trepository #{repos_path} not registered in Redmine: #{e.message}"); + end + log("\trepository #{repos_path} created"); + end +end + +log("project review completed at " + Time.now.inspect); +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/extra/soundsoftware/update-external-repo.sh Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,132 @@ +#!/bin/sh + +mirrordir="/var/mirror" +hg="/usr/bin/hg" + +project="$1" +local_repo="$2" +remote_repo="$3" + +if [ -z "$project" ] || [ -z "$local_repo" ] || [ -z "$remote_repo" ]; then + echo "Usage: $0 <project> <local-repo-path> <remote-repo-url>" + exit 2 +fi + + # We need to handle different source repository types separately. + # + # The convert extension cannot convert directly from a remote git + # repo; we'd have to mirror to a local repo first. Incremental + # conversions do work though. The hg-git plugin will convert + # directly from remote repositories, but not via all schemes + # (e.g. https is not currently supported). It's probably easier to + # use git itself to clone locally and then convert or hg-git from + # there. + # + # We can of course convert directly from remote Subversion repos, + # but we need to keep track of that -- you can ask to convert into a + # repo that has already been used (for Mercurial) and it'll do so + # happily; we don't want that. + # + # Converting from a remote Hg repo should be fine! + # + # One other thing -- we can't actually tell the difference between + # the various SCM types based on URL alone. We have to try them + # (ideally in an order determined by a guess based on the URL) and + # see what happens. + +project_mirror="$mirrordir/$project" +mkdir -p "$project_mirror" +project_repo_mirror="$project_mirror/repo" + + # Some test URLs: + # + # http://aimc.googlecode.com/svn/trunk/ + # http://aimc.googlecode.com/svn/ + # http://vagar.org/git/flam + # https://github.com/wslihgt/IMMF0salience.git + # http://hg.breakfastquay.com/dssi-vst/ + # git://github.com/schacon/hg-git.git + # http://svn.drobilla.net/lad (externals!) + +# If we are importing from another distributed system, then our aim is +# to create either a Hg repo or a git repo at $project_mirror, which +# we can then pull from directly to the Hg repo at $local_repo (using +# hg-git, in the case of a git repo). + +# Importing from SVN, we should use hg convert directly to the target +# hg repo (or should we?) but keep a record of the last changeset ID +# we brought in, and test each time whether it matches the last +# changeset ID actually in the repo + +success="" + +# If we have a record of the last successfully updated remote repo +# URL, check it against our current remote URL: if it has changed, we +# will need to start again with a new clone rather than pulling +# updates into the existing local mirror + +successfile="$project_mirror/last_successful_url" +if [ -f "$successfile" ]; then + last=$(cat "$successfile") + if [ x"$last" = x"$remote_repo" ]; then + echo "$$: Remote URL is unchanged from last successful update" + else + echo "$$: Remote URL has changed since last successful update:" + echo "$$: Last URL was $last, current is $remote_repo" + suffix="$$.$(date +%s)" + echo "$$: Moving existing repos to $suffix suffix and starting afresh" + mv "$project_repo_mirror" "$project_repo_mirror"."$suffix" + mv "$local_repo" "$local_repo"."$suffix" + mv "$successfile" "$successfile"."$suffix" + touch "$project_mirror/url_changed" + fi +fi + +if [ -d "$project_repo_mirror" ]; then + + # Repo mirror exists: update it + echo "$$: Mirror for project $project exists at $project_repo_mirror, updating" 1>&2 + + if [ -d "$project_repo_mirror/.hg" ]; then + "$hg" --config extensions.convert= convert --datesort "$remote_repo" "$project_repo_mirror" && success=true + if [ -z "$success" ]; then + ( cd "$project_repo_mirror" && "$hg" pull "$remote_repo" ) && success=true + fi + elif [ -d "$project_repo_mirror/.git" ]; then + ( cd "$project_repo_mirror" && git pull "$remote_repo" master ) && success=true + else + echo "$$: ERROR: Repo mirror dir $project_repo_mirror exists but is not an Hg or git repo" 1>&2 + fi + +else + + # Repo mirror does not exist yet + echo "$$: Mirror for project $project does not yet exist at $project_repo_mirror, trying to convert or clone" 1>&2 + + case "$remote_repo" in + *git*) + git clone "$remote_repo" "$project_repo_mirror" || + "$hg" --config extensions.convert= convert --datesort "$remote_repo" "$project_repo_mirror" + ;; + *) + "$hg" --config extensions.convert= convert --datesort "$remote_repo" "$project_repo_mirror" || + git clone "$remote_repo" "$project_repo_mirror" || + "$hg" clone "$remote_repo" "$project_repo_mirror" + ;; + esac && success=true + +fi + +echo "Success=$success" + +if [ -n "$success" ]; then + echo "$$: Update successful, pulling into local repo at $local_repo" + if [ ! -d "$local_repo" ]; then + "$hg" init "$local_repo" + fi + if [ -d "$project_repo_mirror/.git" ]; then + ( cd "$local_repo" && "$hg" --config extensions.hggit= pull "$project_repo_mirror" ) && echo "$remote_repo" > "$successfile" + else + ( cd "$local_repo" && "$hg" pull "$project_repo_mirror" ) && echo "$remote_repo" > "$successfile" + fi +fi
--- a/files/delete.me Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1 +0,0 @@ -default directory for uploaded files \ No newline at end of file
--- a/lib/redmine.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/lib/redmine.rb Wed Jan 15 09:59:14 2014 +0000 @@ -87,10 +87,10 @@ # Permissions Redmine::AccessControl.map do |map| - map.permission :view_project, {:projects => [:show], :activities => [:index]}, :public => true, :read => true + map.permission :view_project, {:projects => [:show], :activities => [:index], :members => [:index]}, :public => true, :read => true map.permission :search_project, {:search => :index}, :public => true, :read => true map.permission :add_project, {:projects => [:new, :create]}, :require => :loggedin - map.permission :edit_project, {:projects => [:settings, :edit, :update]}, :require => :member + map.permission :edit_project, {:projects => [:settings, :edit, :update, :overview]}, :require => :member map.permission :close_project, {:projects => [:close, :reopen]}, :require => :member, :read => true map.permission :select_project_modules, {:projects => :modules}, :require => :member map.permission :manage_members, {:projects => :settings, :members => [:index, :show, :create, :update, :destroy, :autocomplete]}, :require => :member @@ -199,7 +199,7 @@ Redmine::MenuManager.map :top_menu do |menu| menu.push :home, :home_path menu.push :my_page, { :controller => 'my', :action => 'page' }, :if => Proc.new { User.current.logged? } - menu.push :projects, { :controller => 'projects', :action => 'index' }, :caption => :label_project_plural + menu.push :projects, { :controller => 'projects', :action => 'explore' }, :caption => :label_project_plural menu.push :administration, { :controller => 'admin', :action => 'index' }, :if => Proc.new { User.current.admin? }, :last => true menu.push :help, Redmine::Info.help_url, :last => true end @@ -236,6 +236,8 @@ Redmine::MenuManager.map :project_menu do |menu| menu.push :overview, { :controller => 'projects', :action => 'show' } + menu.push :members, { :controller => 'members', :action => 'index' }, :param => :project_id + menu.push :news, { :controller => 'news', :action => 'index' }, :param => :project_id, :caption => :label_news_plural menu.push :activity, { :controller => 'activities', :action => 'index' } menu.push :roadmap, { :controller => 'versions', :action => 'index' }, :param => :project_id, :if => Proc.new { |p| p.shared_versions.any? } @@ -244,7 +246,6 @@ :html => { :accesskey => Redmine::AccessKeys.key_for(:new_issue) } menu.push :gantt, { :controller => 'gantts', :action => 'show' }, :param => :project_id, :caption => :label_gantt menu.push :calendar, { :controller => 'calendars', :action => 'show' }, :param => :project_id, :caption => :label_calendar - menu.push :news, { :controller => 'news', :action => 'index' }, :param => :project_id, :caption => :label_news_plural menu.push :documents, { :controller => 'documents', :action => 'index' }, :param => :project_id, :caption => :label_document_plural menu.push :wiki, { :controller => 'wiki', :action => 'show', :id => nil }, :param => :project_id, :if => Proc.new { |p| p.wiki && !p.wiki.new_record? }
--- a/lib/redmine/info.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/lib/redmine/info.rb Wed Jan 15 09:59:14 2014 +0000 @@ -3,7 +3,7 @@ class << self def app_name; 'Redmine' end def url; 'http://www.redmine.org/' end - def help_url; 'http://www.redmine.org/guide' end + def help_url; '/projects/soundsoftware-site/wiki/Help' end def versioned_name; "#{app_name} #{Redmine::VERSION}" end def environment
--- a/lib/redmine/scm/adapters/abstract_adapter.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/lib/redmine/scm/adapters/abstract_adapter.rb Wed Jan 15 09:59:14 2014 +0000 @@ -287,7 +287,12 @@ def scm_iconv(to, from, str) return nil if str.nil? - return str if to == from + # bug 446: non-utf8 paths in repositories blow up repo viewer and reposman + # -- Remove this short-circuit: we want the conversion to + # happen always, so we can trap the error here if the + # source text happens not to be in the advertised + # encoding (instead of having the database blow up later) +# return str if to == from if str.respond_to?(:force_encoding) str.force_encoding(from) begin
--- a/lib/redmine/scm/adapters/bazaar_adapter.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/lib/redmine/scm/adapters/bazaar_adapter.rb Wed Jan 15 09:59:14 2014 +0000 @@ -15,7 +15,7 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -require 'redmine/scm/adapters/abstract_adapter' +require_dependency 'redmine/scm/adapters/abstract_adapter' module Redmine module Scm
--- a/lib/redmine/scm/adapters/cvs_adapter.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/lib/redmine/scm/adapters/cvs_adapter.rb Wed Jan 15 09:59:14 2014 +0000 @@ -15,7 +15,7 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -require 'redmine/scm/adapters/abstract_adapter' +require_dependency 'redmine/scm/adapters/abstract_adapter' module Redmine module Scm
--- a/lib/redmine/scm/adapters/darcs_adapter.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/lib/redmine/scm/adapters/darcs_adapter.rb Wed Jan 15 09:59:14 2014 +0000 @@ -15,7 +15,7 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -require 'redmine/scm/adapters/abstract_adapter' +require_dependency 'redmine/scm/adapters/abstract_adapter' require 'rexml/document' module Redmine
--- a/lib/redmine/scm/adapters/filesystem_adapter.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/lib/redmine/scm/adapters/filesystem_adapter.rb Wed Jan 15 09:59:14 2014 +0000 @@ -18,7 +18,7 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -require 'redmine/scm/adapters/abstract_adapter' +require_dependency 'redmine/scm/adapters/abstract_adapter' require 'find' module Redmine
--- a/lib/redmine/scm/adapters/git_adapter.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/lib/redmine/scm/adapters/git_adapter.rb Wed Jan 15 09:59:14 2014 +0000 @@ -15,7 +15,7 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -require 'redmine/scm/adapters/abstract_adapter' +require_dependency 'redmine/scm/adapters/abstract_adapter' module Redmine module Scm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/redmine/scm/adapters/mercurial/hg-template-0.9.5.tmpl Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,12 @@ +changeset = 'This template must be used with --debug option\n' +changeset_quiet = 'This template must be used with --debug option\n' +changeset_verbose = 'This template must be used with --debug option\n' +changeset_debug = '<logentry revision="{rev}" node="{node|short}">\n<author>{author|escape}</author>\n<date>{date|isodate}</date>\n<paths>\n{files}{file_adds}{file_dels}{file_copies}</paths>\n<msg>{desc|escape}</msg>\n{tags}</logentry>\n\n' + +file = '<path action="M">{file|urlescape}</path>\n' +file_add = '<path action="A">{file_add|urlescape}</path>\n' +file_del = '<path action="D">{file_del|urlescape}</path>\n' +file_copy = '<path-copied copyfrom-path="{source|urlescape}">{name|urlescape}</path-copied>\n' +tag = '<tag>{tag|escape}</tag>\n' +header='<?xml version="1.0" encoding="UTF-8" ?>\n<log>\n\n' +footer='</log>'
--- a/lib/redmine/scm/adapters/mercurial/hg-template-1.0.tmpl Tue Jan 14 14:37:42 2014 +0000 +++ b/lib/redmine/scm/adapters/mercurial/hg-template-1.0.tmpl Wed Jan 15 09:59:14 2014 +0000 @@ -9,4 +9,4 @@ file_copy = '<path-copied copyfrom-path="{source|urlescape}">{name|urlescape}</path-copied>\n' parent = '<parent>{node|short}</parent>\n' header='<?xml version="1.0" encoding="UTF-8" ?>\n<log>\n\n' -# footer="</log>" +footer='</log>'
--- a/lib/redmine/scm/adapters/mercurial_adapter.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/lib/redmine/scm/adapters/mercurial_adapter.rb Wed Jan 15 09:59:14 2014 +0000 @@ -15,7 +15,7 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -require 'redmine/scm/adapters/abstract_adapter' +require_dependency 'redmine/scm/adapters/abstract_adapter' require 'cgi' module Redmine @@ -198,8 +198,7 @@ output.force_encoding('UTF-8') end begin - # Mercurial < 1.5 does not support footer template for '</log>' - parse_xml("#{output}</log>")['log'] + parse_xml("#{output}")['log'] rescue end end
--- a/lib/redmine/scm/adapters/subversion_adapter.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/lib/redmine/scm/adapters/subversion_adapter.rb Wed Jan 15 09:59:14 2014 +0000 @@ -15,7 +15,7 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -require 'redmine/scm/adapters/abstract_adapter' +require_dependency 'redmine/scm/adapters/abstract_adapter' require 'uri' module Redmine
--- a/log/delete.me Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1 +0,0 @@ -default directory for uploaded files \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/engines/generators/plugin_migration/templates/plugin_migration.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,13 @@ +class <%= class_name %> < ActiveRecord::Migration + def self.up + <%- plugins.each do |plugin| -%> + Engines.plugins["<%= plugin.name %>"].migrate(<%= new_versions[plugin.name] %>) + <%- end -%> + end + + def self.down + <%- plugins.each do |plugin| -%> + Engines.plugins["<%= plugin.name %>"].migrate(<%= current_versions[plugin.name] %>) + <%- end -%> + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/Gemfile Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,3 @@ +gem 'bibtex-ruby' +gem 'nokogiri' +gem 'citeproc-ruby' \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/README.rdoc Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,3 @@ += bibliography + +Description goes here
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/app/controllers/authors_controller.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,13 @@ +class AuthorsController < ApplicationController + helper :publications + include PublicationsHelper + + def index + @authors = Author.find(:all) + end + + def show + @author = Author.find(params[:id]) + end + +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/app/controllers/authorships_controller.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,15 @@ +class AuthorshipsController < ApplicationController + + def sort + @authorships = Authorship.find(params['authorship']) + + @authorships.each do |authorship| + + # note: auth_order is usually called position (default column name in the acts_as_list plugin ) + authorship.auth_order = params['authorship'].index(authorship.id.to_s) + 1 + authorship.save + end + + render :nothing => true, :status => 200 + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/app/controllers/bibtex_entries_controller.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,3 @@ +class BibtexEntriesController < ApplicationController + +end \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/app/controllers/publications_controller.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,280 @@ +# -*- coding: utf-8 -*- +# vendor/plugins/redmine_bibliography/app/controllers/publications_controller.rb + +class PublicationsController < ApplicationController + unloadable + + model_object Publication + before_filter :find_model_object, :only => [ :show, :add_project ] + before_filter :find_project_by_project_id, :authorize, :only => [ :edit, :new, :update, :create ] + + def new + find_project_by_project_id + @publication = Publication.new + + # we'll always want a new publication to have its bibtex entry + @publication.build_bibtex_entry + + end + + def create + @project = Project.find(params[:project_id]) + + @publication = Publication.new(params[:publication]) + @publication.projects << @project unless @project.nil? + + if @publication.save + @publication.notify_authors_publication_added(@project) + + flash[:notice] = "Successfully created publication." + redirect_to :action => :show, :id => @publication, :project_id => @project + else + render :action => 'new', :project_id => @project + end + end + + def index + if !params[:project_id].nil? + find_project_by_project_id + @project = Project.find(params[:project_id]) + @publications = Publication.find :all, :joins => :projects, :conditions => ["project_id = ?", @project.id] + else + @publications = Publication.find :all + end + end + + def new_from_bibfile + @publication.current_step = session[:publication_step] + + # contents of the paste text area + bibtex_entry = params[:bibtex_entry] + + # method for creating "pasted" bibtex entries + if bibtex_entry + parse_bibtex_list bibtex_entry + end + end + + def show_bibtex_fields + @fields = [] + + unless params[:value].empty? + @fields = BibtexEntryType.fields(params[:value]) + end + + respond_to do |format| + format.js { + render :show_bibtex_fields + } + end + end + + def add_author + if (request.xhr?) + render :text => User.find(params[:user_id]).name + else + # No? Then render an action. + #render :action => 'view_attribute', :attr => @name + logger.error { "Error while adding Author to publication." } + end + end + + def edit + find_project_by_project_id unless params[:project_id].nil? + + @publication = Publication.find(params[:id]) + @selected_bibtex_entry_type_id = @publication.bibtex_entry.entry_type + @bibtype_fields = BibtexEntryType.fields(@selected_bibtex_entry_type_id) + end + + def update + @publication = Publication.find(params[:id]) + + if @publication.update_attributes(params[:publication]) + flash[:notice] = "Successfully updated Publication." + + # expires the previosly cached entries + Rails.cache.delete "publication-#{@publication.id}-ieee" + Rails.cache.delete "publication-#{@publication.id}-bibtex" + + if !params[:project_id].nil? + redirect_to :action => :show, :id => @publication, :project_id => params[:project_id] + else + redirect_to :action => :show, :id => @publication + end + else + render :action => 'edit' + end + end + + + def show + find_project_by_project_id unless params[:project_id].nil? + + if @publication.nil? + @publications = Publication.all + render "index", :alert => 'The publication was not found!' + else + @authors = @publication.authors + @bibtext_entry = @publication.bibtex_entry + end + end + + # parse string with bibtex authors + def parse_authors(authors_entry) + # in bibtex the authors are always seperated by "and" + return authors_entry.split(" and ") + end + + # parses a list of bibtex + def parse_bibtex_list(bibtex_list) + bibliography = BibTeX.parse bibtex_list + + no_entries = bibliography.data.length + + # parses the bibtex entries + bibliography.data.map do |d| + + if d.class == BibTeX::Entry + create_bibtex_entry d + end + end + end + + def create_bibtex_entry(d) + @publication = Publication.new + @bentry = BibtexEntry.new + authors = [] + institution = "" + email = "" + + d.fields.keys.map do |field| + case field.to_s + when "author" + authors = parse_authors d[field] + when "title" + @publication.title = d[field] + when "institution" + institution = d[field] + when "email" + email = d[field] + else + @bentry[field] = d[field] + end + end + + @publication.bibtex_entry = @bentry + @publication.save + + # need to save all authors + # and establish the author-publication association + # via the authorships table + authors.each_with_index.map do |authorname, idx| + author = Author.new(:name => authorname) + if author.save! + # todo: catch the errors... + puts "SAVED" + else + puts "NOT SAVED" + end + + author.authorships.create!( + :publication => @publication, + :institution => institution, + :email => email, + :order => idx) + end + end + + def autocomplete_for_project + @publication = Publication.find(params[:id]) + + @projects = Project.active.name_or_homepage_like(params[:q]).find(:all, :limit => 100) - @publication.projects + logger.debug "Query for \"#{params[:q]}\" returned \"#{@projects.size}\" results" + render :layout => false + end + + def autocomplete_for_author + @results = [] + + object_id = params[:object_id] + @object_name = "publications[authorships_attributes][#{object_id}][search_results]" + + # todo: make sure query works with both pgres and mysql ~lf.20131010 + authors_list = Author.joins(:authorships).where("LOWER(authorships.name_on_paper) LIKE LOWER(?)", "%#{params[:term]}%").uniq + + # name_like scope, defined in lib/user_author patch + users_list = User.active.name_like(params[:term]).find(:all, :limit => 100) + + logger.debug "Query for \"#{params[:term]}\" returned \"#{authors_list.size}\" authors and \"#{users_list.size}\" users" + + # will check if any of the members of the users list + # doesn't belong to the authors list + + @results = authors_list + + users_list.each do |user| + @results << user unless authors_list.include?(user.author) + end + + logger.debug { "Autocomplete_for_author results --> #{@results}" } + + render :layout => false + end + + def sort_author_order + params[:authorships].each_with_index do |id, index| + Authorship.update_all(['auth_order=?', index+1], ['id=?', id]) + end + render :nothing => true + end + + def add_project + @projects = Project.find(params[:publication][:project_ids]) + @publication.projects << @projects + @project = Project.find(params[:project_id]) + + # TODO luisf should also respond to HTML??? + respond_to do |format| + format.html { redirect_to :back } + format.js { + render(:update) {|page| + page[:add_project_form].reset + page.replace_html :list_projects, :partial => 'list_projects' + } + } + end + end + + def remove_project + @project = Project.find(params[:project_id]) + proj = Project.find(params[:remove_project_id]) + + if @publication.projects.length > 1 + if @publication.projects.exists? proj + @publication.projects.delete proj if request.post? + end + else + logger.error { "Cannot remove project from publication list" } + end + + logger.error { "CURRENT project name#{proj.name} and wanna delete #{@project.name}" } + + render(:update) {|page| + page.replace_html "list_projects", :partial => 'list_projects', :id => @publication + } + end + + def destroy + find_project_by_project_id unless params[:project_id].nil? + @publication = Publication.find(params[:id]) + + @publication.destroy + + flash[:notice] = "Successfully deleted Publication." + redirect_to :controller => :publications, :action => 'index', :project_id => @project + end + + private + +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/app/helpers/authors_helper.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,28 @@ +module AuthorsHelper + unloadable + + def render_author_publications(author) + s = "" + pubs = [] + + author.publications.each do |pub| + pubs << link_to(pub.title, pub) + end + + if pubs.size < 3 + s << '<nobr>' << pubs.join(', ') << '</nobr>' + else + s << pubs.join(', ') + end + s + end + + + # Generates a link to an author + # todo: test options + def link_to_author(author, options={}, html_options = nil) + url = {:controller => 'authors', :action => 'show', :id => author}.merge(options) + link_to(h(author.name), url, html_options) + end + +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/app/helpers/authorships_helper.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +module AuthorshipsHelper + + # Generates a link to either author or user, depending on which is + # available + def link_to_authorship(authorship) + s = '' + if authorship.author.nil? + # legacy reasons… + s << h(authorship.name_on_paper) + else + if authorship.author.user.nil? + s << link_to(authorship.name_on_paper, :controller => 'authors', :action => 'show', :id => authorship.author) + else + s << link_to(authorship.name_on_paper, :controller => 'users', :action => 'show', :id => authorship.author.user) + end + end + s.html_safe + end + +end + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/app/helpers/bibtex_entries_helper.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,3 @@ +module BibtexEntriesHelper + +end \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/app/helpers/publications_helper.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,146 @@ +# -*- coding: utf-8 -*- +require 'bibtex' + +module PublicationsHelper + include AuthorshipsHelper + + def link_to_publication(publication, options={}, html_options = nil) + url = {:controller => 'publications', :action => 'show', :id => publication}.merge(options) + link_to(h(publication.title), url, html_options) + end + + def projects_check_box_tags(name, projects) + s = '' + projects.sort.each do |project| + if User.current.allowed_to?(:edit_publication, project) + s << "<label>#{ check_box_tag name, project.id, false } #{link_to_project project}</label>\n" + s << '<br />' + end + end + + s.html_safe + end + + def link_to_remove_fields(name, f) + f.hidden_field(:_destroy) + link_to_function(name, "remove_fields(this)", :class => 'icon icon-del') + end + + def link_to_add_author_fields(name, f, association, action) + new_object = f.object.class.reflect_on_association(association).klass.new + fields = f.fields_for(association, new_object, :child_index => "new_#{association}") do |builder| + # renders _authorship_fields.html.erb + render(association.to_s.singularize + "_fields", :f => builder) + end + + link_to_function(name, "add_author_fields(this, '#{association}', '#{escape_javascript(fields)}', '#{action}')", { :class => 'icon icon-add', :id => "add_another_author" }) + end + + def sanitized_object_name(object_name) + object_name.gsub(/\]\[|[^-a-zA-Z0-9:.]/,"_").sub(/_$/,"") + end + + def sanitized_method_name(method_name) + method_name.sub(/\?$/, "") + end + + def form_tag_name(object_name, method_name) + str = "#{object_name.to_s}[#{sanitized_method_name(method_name.to_s)}]" + str.to_sym + end + + def form_tag_id(object_name, method_name) + str = "#{sanitized_object_name(object_name.to_s)}_#{sanitized_method_name(method_name.to_s)}" + str.to_sym + end + + def form_object_id(object_name) + str = object_name.split("\[").last().gsub("\]","") + str.to_sym + end + + def render_authorships_list(publication) + s = '<p>' + + publication.authorships.each do |authorship| + s << link_to_authorship(authorship) + s << "<br /><em>#{authorship.institution}</em></p>" + end + + s.html_safe + end + + def render_projects_list(publication, show_delete_icon) + s= "" + + publication.projects.visible.each do |proj| + s << link_to_project(proj, {}, :class => 'publication_project') + + if show_delete_icon + if User.current.allowed_to?(:edit_publication, @project) + if @project == proj + # todo: move this message to yml file + confirm_msg = 'Are you sure you want to remove the current project from this publication\'s projects list?' + else + confirm_msg = false + end + + s << link_to(l(:button_delete), { :url => { :controller => 'publications', :action => 'remove_project', :id => publication, :remove_project_id => proj, :project_id => @project }, :method => :post, :confirm => confirm_msg }, :class => 'icon icon-del', :remote => :true) + end + end + + s << "<br />" + end + + s.html_safe + end + + def print_ieee_format(publication) + Rails.cache.fetch("publication-#{publication.id}-ieee") do + publication.print_entry(:ieee).html_safe + end + end + + def print_bibtex_format(publication) + Rails.cache.fetch("publication-#{publication.id}-bibtex") do + publication.print_entry(:bibtex) + end + end + + def show_bibtex_fields(bibtex_entry) + s = "" + bibtex_entry.attributes.keys.sort.each do |key| + value = bibtex_entry.attributes[key].to_s + next if key == 'id' or key == 'publication_id' or value == "" + s << "<h4>" + l("field_#{key}") + "</h4>" + s << "<p>" + if key == "entry_type" + s << bibtex_entry.entry_type_label + else + s << value + end + s << "</p>" + end + s + end +end + + +def render_authorship_link(link_class, link_id) + + # Renders a link for an author used when adding authors for a publication + # link_class can be either User or Author + # link_id will be the id of the Author/User we wish to link + + s= "" + + if link_class == "Author" + url = {:controller => 'authors', :action => 'show', :id => link_id} + s << link_to(h(Author.find(link_id).name), url) + else + url = {:controller => 'users', :action => 'show', :id => link_id} + s << link_to(h(User.find(link_id).name), url) + end + + s.html_safe +end +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/app/models/author.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,46 @@ +class Author < ActiveRecord::Base + unloadable + + has_many :authorships, :dependent => :destroy + has_many :publications, :through => :authorships + + belongs_to :user + + def <=>(author) + name.downcase <=> author.name.downcase + end + + # todo: review usage of scope --lf.20130108 + scope :like, lambda {|q| + s = "%#{q.to_s.strip.downcase}%" + {:conditions => ["LOWER(name) LIKE :s", {:s => s}], + :order => 'name' + } + } + + def institution + if self.authorships.first.nil? + "" + else + self.authorships.first.institution + end + end + + def mail + if self.authorships.first.nil? + "" + else + self.authorships.first.mail + end + end + + # todo: need to fix the name getter + def name + if self.authorships.first.nil? + "" + else + self.authorships.first.name + end + end + +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/app/models/authorship.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,138 @@ +class Authorship < ActiveRecord::Base + unloadable + + belongs_to :author + belongs_to :publication + + accepts_nested_attributes_for :author + accepts_nested_attributes_for :publication + + validates_presence_of :name_on_paper + + attr_writer :search_author_id , :search_author_class + attr_writer :search_author_tie + + ### attr_accessor :search_results, :identify_author + ## attr_writer :search_author_class + + before_save :set_author + before_update :delete_publication_cache + + # tod: review scope of ordering + acts_as_list :column => 'auth_order' + + # todo: review usage of scope --lf.20130108 + scope :like_unique, lambda {|q| + s = "%#{q.to_s.strip.downcase}%" + {:conditions => ["LOWER(name_on_paper) LIKE :s OR LOWER(email) LIKE :s", {:s => s}], + :order => 'name_on_paper', + :group => "name_on_paper, institution, email" + } + } + + # todo: review usage of scope --lf.20130108 + scope :like, lambda {|q| + s = "%#{q.to_s.strip.downcase}%" + {:conditions => ["LOWER(name_on_paper) LIKE :s OR LOWER(email) LIKE :s", {:s => s}], + :order => 'name_on_paper' + } + } + + def search_author_class + # Authorship must always have an Author + # unless it hasn't been saved yet + # using default setter (attr_writer) + + if self.author.nil? + aclass = "" + else + aclass = "Author" + end + + @search_author_class || aclass + end + + def search_author_id + if self.author.nil? + authid = "" + else + authid = author_id + end + + @search_author_id || authid + end + + def search_author_tie + if self.author.nil? + auth_tie = false + else + auth_tie = true + end + + @search_author_tie || auth_tie + end + + def name + return self.name_on_paper + end + + def <=>(authorship) + name.downcase <=> authorship.name.downcase + end + + def mail + return self.email + end + + protected + + def delete_publication_cache + publication = Publication.find(self.publication_id) + Rails.cache.delete "publication-#{publication.id}-ieee" + Rails.cache.delete "publication-#{publication.id}-bibtex" + end + + private + + def set_author + # do we want to associate the authorship + # with an existing author/user? + if @search_author_tie + # if an author, simply associates with it + # if an user, checks if it has already an author associated with it + # if so, associates with that author + # otherwise, creates a new author + + case @search_author_class + when "" + author = Author.new + author.save + + when "User" + user = User.find(@search_author_id) + + if user.author.nil? + # User w/o author: + # create new author and update user + author = Author.new + author.save + user.author = author + user.save + else + author = user.author + end + + when "Author" + author = Author.find(@search_author_id) + end + + # if we don't want to associate with an existing author/user + else + # todo: should we delete any previously existing relationship? + author = Author.new + author.save + end + + self.author = author + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/app/models/bibtex_entry.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,16 @@ +class BibtexEntry < ActiveRecord::Base + unloadable + + belongs_to :publication + validates_presence_of :entry_type + + def entry_type_name + entry_type = self.entry_type + BibtexEntryType.find(entry_type).name + end + + def entry_type_label + entry_type = self.entry_type + BibtexEntryType.find(entry_type).label + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/app/models/bibtex_entry_type.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,30 @@ +class BibtexEntryType < ActiveRecord::Base + unloadable + + @@fields = Hash['article', ['journal', 'year', 'volume', 'number', 'pages', 'month', 'note' ], + 'book' , [ 'editor', 'publisher', 'volume', 'series', 'address', 'edition', 'month', 'year', 'note' ], + 'booklet' , [ 'howpublished', 'address', 'year', 'month', 'note', 'key' ], + 'conference', [ 'booktitle', 'year', 'editor', 'pages', 'organization', 'publisher', 'address', 'month', 'note' ], + 'inbook', [ 'editor', 'publisher', 'chapter', 'pages', 'volume', 'series', 'address', 'edition', 'year', 'note' ], + 'incollection', [ 'editor', 'publisher', 'chapter', 'pages', 'volume', 'series', 'address', 'edition', 'year', 'note' ], + 'inproceedings', [ 'booktitle', 'year', 'editor', 'pages', 'organization', 'publisher', 'address', 'month', 'note' ], + 'manual', [ 'organization', 'address', 'edition', 'month', 'year', 'note' ], + 'masterthesis', [ 'school', 'year', 'address', 'month', 'note' ], + 'misc', [ 'howpublished', 'month', 'year', 'note' ], + 'phdthesis', [ 'school', 'year', 'address', 'month', 'note' ], + 'proceedings', [ 'booktitle', 'year', 'editor', 'pages', 'organization', 'publisher', 'address', 'month', 'note' ], + 'techreport', [ 'year', 'type', 'number', 'address', 'month', 'note' ], + 'unpublished', [ 'note', 'month', 'year' ]] + + def redundant? + name == 'conference' # conference is a duplicate of inproceedings + end + + def label + l("field_bibtex_#{name}") + end + + def self.fields (type) + @@fields[ self.find(type).name ] + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/app/models/publication.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,110 @@ +# vendor/plugins/redmine_bibliography/app/models/publication.rb + +class Publication < ActiveRecord::Base + unloadable + + has_many :authorships, :dependent => :destroy, :order => "auth_order ASC" + has_many :authors, :through => :authorships, :uniq => true + + has_one :bibtex_entry, :dependent => :destroy + + validates_presence_of :title + validates_length_of :authorships, :minimum => 1, :message => l("error_no_authors") + validates_associated :bibtex_entry, :authorships + + accepts_nested_attributes_for :authorships + accepts_nested_attributes_for :authors, :allow_destroy => true + accepts_nested_attributes_for :bibtex_entry, :allow_destroy => true + + has_and_belongs_to_many :projects, :uniq => true + + before_save :set_initial_author_order + + scope :visible, lambda {|*args| { :include => :projects, + :conditions => Project.allowed_to_condition(args.shift || User.current, :view_publication, *args) } } + + acts_as_activity_provider :type => 'publication', + :timestamp => "#{Publication.table_name}.created_at", + :find_options => { + :include => :projects, + :conditions => "#{Project.table_name}.id = projects_publications.project_id" + } + + acts_as_event :title => Proc.new {|o| o.title }, + :datetime => :created_at, + :type => 'publications', + :author => nil, + #todo - need too move the cache from the helper to the model + :description => Proc.new {|o| o.print_entry(:ieee)}, + :url => Proc.new {|o| {:controller => 'publications', :action => 'show', :id => o.id }} + + + # Ensure error message uses proper text instead of + # bibtex_entry.entry_type (#268). There has to be a better way to + # do this! + def self.human_attribute_name(k, *args) + if k == 'bibtex_entry.entry_type' + l(:field_entry_type) + else + super + end + end + + def notify_authors_publication_added(project) + self.authors.each do |author| + Rails.logger.debug { "Sending mail to \"#{self.title}\" publication authors." } + Mailer.publication_added(author.user, self, project).deliver unless author.user.nil? + end + end + + def notify_authors_publication_updated(project) + self.authors.each do |author| + Rails.logger.debug { "Sending mail to \"#{self.title}\" publication authors." } + Mailer.publication_updated(author.user, self, project).deliver unless author.user.nil? + end + end + + + def set_initial_author_order + authorships = self.authorships + + logger.debug { "Publication \"#{self.title}\" has #{authorships.size} authors." } + + authorships.each_with_index do |authorship, index| + if authorship.auth_order.nil? + authorship.auth_order = index + end + end + end + + def print_bibtex_author_names + # this authors are correctly sorted because the authorships model + # already outputs the author names ASC by auth_order + self.authorships.map{|a| a.name_on_paper}.join(' and ') + end + + def print_entry(style) + bib = BibTeX::Entry.new + + bib.author = self.print_bibtex_author_names + bib.title = self.title + + self.bibtex_entry.attributes.keys.sort.each do |key| + value = self.bibtex_entry.attributes[key].to_s + next if key == 'id' or key == 'publication_id' or value == "" + + if key == "entry_type" + bib.type = BibtexEntryType.find(self.bibtex_entry.entry_type).name + else + bib[key.to_sym] = value + end + end + + if style == :ieee + CiteProc.process(bib.to_citeproc, :style => :ieee, :format => :html) + else + bibtex = bib.to_s :include => :meta_content + bibtex.strip! + end + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/app/views/activities/_recent.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,92 @@ +<% events = @events_by_day %> +<% max = 5 %> +<% if (events.nil?) + activity = Redmine::Activity::Fetcher.new(User.current, :project => @project) + + if @project + # Don't show news (duplicated with News box) or wiki edits (too + # tedious) in project front page + activity.scope = [ "changesets", "files", "issues", "documents" ] + end + + events = activity.events(Date.today - 28, Date.today + 1) + + if defined? user + events = events.select { |e| + + if e.class != Publication + user.member_of? e.project + else + e.projects.map {|p| user.member_of? p }.any? + end + } + + end + + events = events.first(max) + + end +%> + +<div id="activity"> + +<% if @project.nil? %> + <%= content_tag('h3', l(:label_activity_my_recent)) %> + <div class="activity box"> +<% end %> + +<% if events.empty? %> + + <% if @project.nil? %> + <div class="tip"><%= l(:label_activity_my_recent_none) %></div> + <% end %> + +<% else %> + + <% if !@project.nil? %> + <div class="activity box"> + <%= content_tag('h3', l(:label_activity_recent)) %> + <% end %> + + <dl> + <% events.sort {|x,y| y.event_datetime <=> x.event_datetime }.each do |e| -%> + <%- if e.class != Publication -%> + <dt class="<%= User.current.logged? && e.respond_to?(:event_author) && User.current == e.event_author ? 'me' : nil %>"> + <%= avatar(e.event_author, :size => "24") if e.respond_to?(:event_author) %> + <span class="time"><%= format_time(e.event_datetime) %></span> + <%= content_tag('span', link_to_project(e.project), :class => 'project') if @project.nil? || @project != e.project %> + <% if e.respond_to?(:event_author) %> + <span class="author"><%= e.event_author %></span> + <% end %> + </dt> + <dd><%= link_to format_activity_title(e.event_title), e.event_url %> + <span class="description"><%= format_activity_description(e.event_description) %></span> + </dd> + <% else -%> + <dt class="<%= User.current.logged? && e.respond_to?(:event_author) && User.current == e.event_author ? 'me' : nil %>"> + <span class="time"><%= format_time(e.event_datetime) %></span> + <%= link_to format_activity_title(e.event_title), e.event_url %> + was added to the following + <% if e.projects.count > 1 %> + projects: + <%- else -%> + project: + <%- end -%> + <%= content_tag('span', e.projects.join(', ')) -%> <% if e.respond_to?(:event_author) %> + <span class="author"><%= e.event_author %></span> + <% end %> + </dt> + <dd><%= link_to format_activity_title(e.event_title), e.event_url %> + <span class="description"><%= format_activity_description(e.event_description) %></span> + </dd> + <% end -%> + <% end -%> + </dl> + + </div> + +<% end %> + +<% if events.empty? and @project.nil? %></div><% end %> + +</div>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/app/views/activities/index.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,91 @@ +<h2><%= + if @author.nil? + if @institution_name.blank? + l(:label_activity) + else + l(:label_institution_activity, h(@institution_name)) + end + else + l(:label_user_activity, link_to_user(@author)).html_safe + end + %></h2> +<p class="subtitle"><%= l(:label_date_from_to, :start => format_date(@date_to - @days), :end => format_date(@date_to-1)) %></p> + +<div id="activity"> +<% @events_by_day.keys.sort.reverse.each do |day| %> +<h3><%= format_activity_day(day) %></h3> +<dl> +<% @events_by_day[day].sort {|x,y| y.event_datetime <=> x.event_datetime }.each do |e| -%> + <%- if e.class != Publication -%> + <dt class="<%= e.event_type %> <%= User.current.logged? && e.respond_to?(:event_author) && User.current == e.event_author ? 'me' : nil %>"> + <%= avatar(e.event_author, :size => "24") if e.respond_to?(:event_author) %> + <span class="time"><%= format_time(e.event_datetime, false) %></span> + <%= content_tag('span', h(e.project), :class => 'project') if @project.nil? || @project != e.project %> + <%= link_to format_activity_title(e.event_title), e.event_url %> + </dt> + <dd> + <span class="description"><%= format_activity_description(e.event_description) %></span> + <span class="author"><%= link_to_user(e.event_author) if e.respond_to?(:event_author) %></span> + </dd> + <%- else -%> + <dt class="<%= e.event_type %> <%= User.current.logged? && e.respond_to?(:event_author) && User.current == e.event_author ? 'me' : nil %>"> + <%= avatar(e.event_author, :size => "24") if e.respond_to?(:event_author) %> + <span class="time"><%= format_time(e.event_datetime, false) %></span> + <%= link_to format_activity_title(e.event_title), e.event_url %> + was added to the following + <% if e.projects.count > 1 %> + projects: + <%- else -%> + project: + <%- end -%> + <%= content_tag('span', e.projects.join(', ')) -%> + </dt> + <dd> + <span class="description"><%= e.event_description -%></span> + <span class="author"><%= link_to_user(e.event_author) if e.respond_to?(:event_author) %></span> + </dd> + <% end -%> +<%- end -%> +</dl> +<% end -%> +</div> + +<%= content_tag('p', l(:label_no_data), :class => 'nodata') if @events_by_day.empty? %> + +<div style="float:left;"> +<%= link_to_content_update("\xc2\xab " + l(:label_previous), + params.merge(:from => @date_to - @days - 1), + :title => l(:label_date_from_to, :start => format_date(@date_to - 2*@days), :end => format_date(@date_to - @days - 1))) %> +</div> +<div style="float:right;"> +<%= link_to_content_update(l(:label_next) + " \xc2\xbb", + params.merge(:from => @date_to + @days - 1), + :title => l(:label_date_from_to, :start => format_date(@date_to), :end => format_date(@date_to + @days - 1))) unless @date_to >= Date.today %> +</div> + +<% other_formats_links do |f| %> + <%= f.link_to 'Atom', :url => params.merge(:from => nil, :key => User.current.rss_key) %> +<% end %> + +<% content_for :header_tags do %> +<%= auto_discovery_link_tag(:atom, params.merge(:format => 'atom', :from => nil, :key => User.current.rss_key)) %> +<% end %> + +<% content_for :sidebar do %> +<% form_tag({}, :method => :get) do %> +<h3><%= l(:label_activity) %></h3> +<p><% @activity.event_types.each do |t| %> +<%= check_box_tag "show_#{t}", 1, @activity.scope.include?(t) %> +<label for="show_<%=t%>"><%= link_to(l("label_#{t.singularize}_plural"), {"show_#{t}" => 1, :user_id => params[:user_id]})%></label> +<br /> +<% end %></p> +<% if @project && @project.descendants.active.any? %> + <%= hidden_field_tag 'with_subprojects', 0 %> + <p><label><%= check_box_tag 'with_subprojects', 1, @with_subprojects %> <%=l(:label_subproject_plural)%></label></p> +<% end %> +<%= hidden_field_tag('user_id', params[:user_id]) unless params[:user_id].blank? %> +<p><%= submit_tag l(:button_apply), :class => 'button-small', :name => nil %></p> +<% end %> +<% end %> + +<% html_title(l(:label_activity), @author) -%>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/app/views/authors/index.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,26 @@ +<h2><%=l(:label_authors_index)%></h2> + +<table class="list authors"> + <thead><tr> + <th><%=l(:field_author_name)%></th> + <th><%=l(:field_author_username)%></th> + <th><%=l(:field_author_publications)%></th> + </tr></thead> + <tbody> + + <% @authors.each do |author|%> + <tr id="author-<%= author.id %>" class="<%= cycle('odd', 'even') %>"> + <td class="title"> + <%= link_to_author author %> + </td> + <td class="username "> + <%= link_to author.user unless author.user.nil? %> + </td> + <td class="project"> + <%= render_author_publications(author) %> + </td> + </tr> + <% end %> + </tbody> +</table> +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/app/views/authors/show.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,28 @@ +<h2><%=l(:label_authors_show)%></h2> + +<div class="autoscroll"> + <table class="list authors"> + <thead> + <tr> + <th><%=l(:field_authorship_publication_title)%></th> + <th><%=l(:field_authorship_name)%></th> + <th><%=l(:field_authorship_email)%></th> + <th><%=l(:field_authorship_institution)%></th> + </tr> + </thead> + + <% @author.authorships.each do |authorship| %> + <tr id="authorship-<%= authorship.id %>" class="<%= cycle('odd', 'even') %>"> + <td class="title"><%= link_to_publication(authorship.publication) %></td> + <td class="name"><%= h authorship.name_on_paper %></td> + <td class="email"><%= h authorship.email %></td> + <td class="institution"><%= h authorship.institution %></td> + </tr> + <% end %> + </tbody> + </table> +</div> + +<% content_for :sidebar do %> +<% end %> +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/app/views/authorships/update.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,1 @@ +<h2>Authorships#update</h2>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/app/views/mailer/publication_added.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,1 @@ +<%= l(:mail_body_publication_added, :publication => @publication.title, :project => @project.name) %>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/app/views/mailer/publication_added.text.erb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,2 @@ +<%= l(:mail_body_publication_added, :publication => @publication.title, :project => @project.name) %> +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/app/views/my/blocks/_publications_box.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,30 @@ +<% get_my_publications %> + +<h3><%=l(:label_my_publications_box) %> <%= "(" + @my_publications.count.to_s + ")" %> </h3> + +<table class="list publications"> + <thead><tr> + <th><%=l(:field_publication_title)%></th> + <th><%=l(:field_publication_authors)%></th> + <th><%=l(:field_publication_projects)%></th> + </tr></thead> + <tbody> + + <% @my_publications.each do |publication|%> + <tr id="publication-<%= publication.id %>" class="<%= cycle('odd', 'even') %>"> + <td class="title"> + <%= link_to publication.title, publication %> + </td> + <td class="authors"> + <%= render_publications_authors(publication) %> + </td> + <td class="project"> + <%= render_publications_projects(publication) %> + </td> + </tr> + <% end %> + </tbody> +</table> + + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/app/views/projects/_bibliography_box.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,35 @@ +<% content_for :header_tags do %> + <%= stylesheet_link_tag 'bibliography', :plugin => 'redmine_bibliography' %> + <%= javascript_include_tag 'bibtex', :plugin => 'redmine_bibliography' -%> +<% end %> + +<% if @project.publications.any? %> +<%= stylesheet_link_tag 'bibliography', :plugin => 'redmine_bibliography' %> + <div id="bibliography"> + <div class="box"> + <h3><%=l(:label_related_publication_plural)%></h3> + + <dl> + <% @project.publications.each do |publication| %> + <dt> + <%= print_ieee_format(publication) %> + </dt> + <dd> + <%= link_to(l("more_details_link"), {:controller => :publications, :action => :show, :id => publication.id, :project_id => @project.id}) -%> + + <%= link_to l(:bibtex_link).html_safe, "javascript:void(0)", :class => "bibtex-link"-%> + + <%- unless publication.external_url.blank? -%> + <%= link_to l(:external_url_link), publication.external_url, {:target => "_blank"} -%> + <%- end -%> + + + </dd> + <dd class="bibtex-textarea collapsed" style="display: none;"> + <textarea readonly> <%= print_bibtex_format(publication) -%> </textarea> + </dd> + <% end -%> + </dl> + </div> +</div> +<% end -%>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/app/views/projects/show.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,142 @@ +<div class="contextual"> + <% if User.current.allowed_to?(:add_subprojects, @project) %> + <%= link_to l(:label_subproject_new), {:controller => 'projects', :action => 'new', :parent_id => @project}, :class => 'icon icon-add' %> + <% end %> + + <% if User.current.allowed_to?(:close_project, @project) %> + <% if @project.active? %> + <%= link_to l(:button_close), close_project_path(@project), :data => {:confirm => l(:text_are_you_sure)}, :method => :post, :class => 'icon icon-lock' %> + <% else %> + <%= link_to l(:button_reopen), reopen_project_path(@project), :data => {:confirm => l(:text_are_you_sure)}, :method => :post, :class => 'icon icon-unlock' %> + <% end %> + <% end %> + + <% if @project.module_enabled? :redmine_bibliography %> + <% if User.current.allowed_to?(:add_publication, @project) %> + <%= link_to l(:label_add_publication_to_project), {:controller => 'publications', :action => 'new', :project_id => @project}, :class => 'icon icon-add' %> + <% end %> + <% end %> +</div> + +<% if @project.has_welcome_page %> +<% page = @project.wiki.find_page("Overview") %> +<% end %> + +<% if page %> + +<% if @project.module_enabled? :wiki %> +<% if User.current.allowed_to?(:edit_wiki_pages, @project) %> +<div class="contextual"> +<%= link_to(l(:button_welcome_page_edit_this), {:controller => 'wiki', :action => 'edit', :project_id => @project, :id => Wiki.titleize("Overview")}, :class => 'icon icon-edit') %> +</div> +<% end %> +<% end %> + +<div class="contextual" style="clear: right"> +<ul> +<% unless @project.homepage.blank? %><li><%=l(:field_homepage)%>: <%= link_to h(@project.homepage), @project.homepage %></li><% end %> +<% if @subprojects.any? %> + <li><%=l(:label_subproject_plural)%>: + <%= @subprojects.collect{|p| link_to(h(p), :action => 'show', :id => p)}.join(", ").html_safe %></li> +<% end %> +</ul> +</div> + +<%= render(:partial => "wiki/content", :locals => {:content => page.content_for_version()}) %> + +<% else %> + +<h2><%=l(:label_overview)%></h2> + +<% unless @project.active? %> + <p class="warning"><span class="icon icon-lock"><%= l(:text_project_closed) %></span></p> +<% end %> + +<div class="splitcontentleft"> + <% if @project.description.present? %> + <div class="wiki"> + <%= textilizable @project.description %> + </div> + <% end %> + <div class="overviewfields"> + <% unless @project.homepage.blank? %><h4><%=l(:field_homepage)%>:</h4><ul><li><%= link_to h(@project.homepage), @project.homepage %></li></ul><% end %> + <% if @subprojects.any? %> + <h4><%=l(:label_subproject_plural)%>:</h4> + <ul> + <li><%= @subprojects.collect{|p| link_to(h(p), :action => 'show', :id => p)}.join("</li><li>").html_safe %></li> + </ul> + <% end %> + <% @project.visible_custom_field_values.each do |custom_value| %> + <% if !custom_value.value.blank? %> + <h4><%= custom_value.custom_field.name%>:</h4><ul><li><%=h show_value(custom_value) %></li></ul> + <% end %> + <% end %> + </div> + <% if User.current.allowed_to?(:view_issues, @project) and @open_issues_by_tracker.values.any? %> + <div class="issues box"> + <h3><%=l(:label_issue_tracking)%></h3> + <ul> + <% for tracker in @trackers %> + <li><%= link_to h(tracker.name), :controller => 'issues', :action => 'index', :project_id => @project, + :set_filter => 1, + "tracker_id" => tracker.id %>: + <%= l(:label_x_open_issues_abbr_on_total, :count => @open_issues_by_tracker[tracker].to_i, + :total => @total_issues_by_tracker[tracker].to_i) %> + </li> + <% end %> + </ul> + <p> + <%= link_to l(:label_issue_view_all), :controller => 'issues', :action => 'index', :project_id => @project, :set_filter => 1 %> + <% if User.current.allowed_to?(:view_calendar, @project, :global => true) %> + | <%= link_to(l(:label_calendar), :controller => 'calendars', :action => 'show', :project_id => @project) %> + <% end %> + <% if User.current.allowed_to?(:view_gantt, @project, :global => true) %> + | <%= link_to(l(:label_gantt), :controller => 'gantts', :action => 'show', :project_id => @project) %> + <% end %> + </p> + </div> + <% end %> + <%= call_hook(:view_projects_show_left, :project => @project) %> +</div> + +<div class="splitcontentright"> + + <%= render :partial => 'bibliography_box' %> + + <%= render :partial => 'members_box' %> + + <% if @news.any? && authorize_for('news', 'index') %> + <div class="news box"> + <h3><%=l(:label_news_latest)%></h3> + <%= render :partial => 'news/news', :collection => @news %> + <p><%= link_to l(:label_news_view_all), :controller => 'news', :action => 'index', :project_id => @project %></p> + </div> + <% end %> + + <%= render :partial => 'activities/recent' %> + + <%= call_hook(:view_projects_show_right, :project => @project) %> +</div> + +<% content_for :sidebar do %> + <%= call_hook(:view_projects_show_sidebar_top, :project => @project) %> + <% if @total_hours.present? && User.current.allowed_to?(:view_time_entries, @project) %> + <h3><%= l(:label_spent_time) %></h3> + <p><span class="icon icon-time"><%= l_hours(@total_hours) %></span></p> + <p> + <% if User.current.allowed_to?(:log_time, @project) %> + <%= link_to l(:button_log_time), new_project_time_entry_path(@project) %> | + <% end %> + <%= link_to(l(:label_details), project_time_entries_path(@project)) %> | + <%= link_to(l(:label_report), report_project_time_entries_path(@project)) %></p> + <% end %> + <%= call_hook(:view_projects_show_sidebar_bottom, :project => @project) %> +<% end %> + +<% end %> + +<% content_for :header_tags do %> +<%= auto_discovery_link_tag(:atom, {:controller => 'activities', :action => 'index', :id => @project, :format => 'atom', :key => User.current.rss_key}) %> +<% end %> + +<% html_title(l(:label_overview)) -%>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/app/views/publications/_add_project_form.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,26 @@ +<%= form_for(:publication, + :remote => true, + :url => {:controller => 'publications', :action => 'add_project', :id => @publication, :project_id => @project}, + :method => :post, + :html => { :id => 'add_project_form' }, + :loading => "$('project-add-submit').disable()", + :complete => "$('project-add-submit').enable()") do |f| %> + + <fieldset><legend><%=l(:label_add_project_to_publication)%></legend> + <p> + <%= label_tag "project_search", l(:label_project_search) %><%= text_field_tag 'project_search', nil %> + </p> + + <%= javascript_tag "observeSearchfield('project_search', 'projects', '#{ escape_javascript url_for(:controller => 'publications', + :action => 'autocomplete_for_project', + :id => @publication.id) }')" %> + + <div id="projects"> + <% if params[:q] && params[:q].length > 1 %> + <%= projects_check_box_tags 'project[project_ids][]', @projects %> + <% end %> + </div> + + <p><%= submit_tag l(:button_add), :id => 'project-add-submit' %></p> + </fieldset> + <% end %>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/app/views/publications/_authorship_fields.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,30 @@ +<div id="authors" class="fields"> + <div class="author_edit" id="<%= form_tag_id( f.object_name, :edit_author_info ) %>"> + + <p><%= f.label :name_on_paper %><%= f.text_field :name_on_paper, :class => "author_name_on_paper" -%></p> + <p><%= f.label :institution %><%= f.text_field :institution -%></p> + <p><%= f.label :email %><%= f.text_field :email -%></p> + + <p class="author_associated search_author_tie"> + <%= f.check_box :search_author_tie, :label => '' -%> + <% unless f.object.search_author_tie %> + <span>Not associated with any SoundSoftware site user.</span> + <% else %> + <span>Associated with <%= render_authorship_link(f.object.search_author_class, f.object.search_author_id) -%>.</span> + <% end %> + </p> + + <%= f.hidden_field :search_author_class -%> + <%= f.hidden_field :search_author_id -%> + </div> + + <div> + <p> + <%= button_to_function l(:label_save_author), '', :id => form_tag_id(f.object_name, :edit_save_button), :class => 'author_save_btn' -%> + + <%= button_to_function l(:label_edit_author), '', :id => form_tag_id(f.object_name, :edit_button), :class => 'author_edit_btn', :style => "display:none;" -%> + + <%= link_to_remove_fields l("remove_author"), f %> + </p> + </div> +</div> \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/app/views/publications/_bibtex_fields.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,78 @@ +<p> + <label for="publication_bibtex_entry_attributes_entry_type"><%=l("field_entry_type")%> <span class="required">*</span></label> + <%= f.collection_select :entry_type, + BibtexEntryType.find(:all).reject { |x| x.redundant? }, + :id, + :label, + { :selected => @selected_bibtex_entry_type_id, :prompt => true } %> +</p> + +<p class="bibtex hol"> + <%= f.label :year %><%= f.text_field :year, :size => 5 -%> +</p> +<p class="bibtex hol"> + <%= f.label :month %><%= f.text_field :month, :size => 5 -%> +</p> +<p class="bibtex hol"> + <%= f.label :chapter %><%= f.text_field :chapter, :size => 5 -%> +</p> +<p class="bibtex hol"> + <%= f.label :editor %><%= f.text_field :editor -%> +</p> +<p class="bibtex hol"> + <%= f.label :booktitle %><%= f.text_field :booktitle -%> +</p> +<p class="bibtex hol"> + <%= f.label :publisher %><%= f.text_field :publisher -%> +</p> +<p class="bibtex hol"> + <%= f.label :pages %><%= f.text_field :pages, :size => 5 -%> +</p> +<p class="bibtex hol"> + <%= f.label :address %><%= f.text_field :address -%> +</p> +<p class="bibtex hol"> + <%= f.label :annote %><%= f.text_field :annote -%> +</p> +<p class="bibtex hol"> + <%= f.label :crossref %><%= f.text_field :crossref -%> +</p> +<p class="bibtex hol"> + <%= f.label :edition %><%= f.text_field :edition -%> +</p> +<p class="bibtex hol"> + <%= f.label :eprint %><%= f.text_field :eprint -%> +</p> +<p class="bibtex hol"> + <%= f.label :howpublished %><%= f.text_field :howpublished -%> +</p> +<p class="bibtex hol"> + <%= f.label :journal %><%= f.text_field :journal -%> +</p> +<p class="bibtex hol"> + <%= f.label :key %><%= f.text_field :key -%> +</p> +<p class="bibtex hol"> + <%= f.label :note %><%= f.text_field :note -%> +</p> +<p class="bibtex hol"> + <%= f.label :number %><%= f.text_field :number, :size => 5 -%> +</p> +<p class="bibtex hol"> + <%= f.label :organization %><%= f.text_field :organization %> +</p> +<p class="bibtex hol"> + <%= f.label :school %><%= f.text_field :school %> +</p> +<p class="bibtex hol"> + <%= f.label :series %><%= f.text_field :series %> +</p> +<p class="bibtex hol"> + <%= f.label :type %><%= f.text_field :type %> +</p> +<p class="bibtex hol"> + <%= f.label :url %><%= f.text_field :url %> +</p> +<p class="bibtex hol"> + <%= f.label :volume %><%= f.text_field :volume %> +</p> \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/app/views/publications/_form.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,34 @@ +<h3><%= f.label :title %> <span class="required">*</span> <%= f.text_field :title, :required => true, :size => 70 %></h3> + +<div class="splitcontentleft"> + <h3><%= l(:label_publication_other_details) %></h3> + <div class="box tabular"> + <%= f.fields_for :bibtex_entry do |builder| -%> + <%= render :partial => 'bibtex_fields', :locals => { :f => builder} %> + <%- end -%> + + <p> + <%= f.label :external_url %><%= f.text_field :external_url -%> + <br /> + <em><%= l(:text_external_url) -%></em> + </p> + <p> + <%= f.label :doi %><%= f.text_field :doi -%> + <br /> + <em><%= l(:text_doi) %></em> + </p> + + </div> +</div> + +<div class="splitcontentright"> + <h3><%= l(:authors) %></h3> + <div class="box tabular"> + <%= f.fields_for :authorships do |builder| -%> + <%= render "authorship_fields", :f => builder %> + <%- end -%> + <%= link_to_add_author_fields l(:label_add_an_author), f, :authorships, params[:action] %> + </div> +</div> + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/app/views/publications/_identify_author_form.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,25 @@ +<legend><%= "Identify Authors in the system…" %></legend> + +<%= link_to_remote "It's me!", + { :url => { :controller => 'publications', + :action => 'add_me_as_author', + :project_id => @project }, :method => 'post'}, + { :class => 'icon icon-add', :id => "add_me_as_author" } %> + +<p> + <%= label_tag "author_search", l(:label_project_search) %><%= text_field_tag 'author_search', nil %> +</p> + +<%= observe_field( form_tag_id(f.object_name, :name), + :frequency => 0.5, + :update => :identify_author, + :url => { :controller => 'publications', :action => 'autocomplete_for_author' }, + :with => 'q') +%> + +<div id="identify_author"> + <% if params[:q] && params[:q].length > 1 %> + <%= select_author_links 'author[author_ids][]', @authors %> + <% end %> +</div> +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/app/views/publications/_list_projects.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,1 @@ +<%= render_projects_list(@publication, true) %>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/app/views/publications/_new_bibtex_step.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,9 @@ +<h3>New Bibtex</h3> + +<h4>Paste your Bibtex entries here</h4> +<p> + <%=label_tag :bibtex_entry %> + <%=text_area_tag :bibtex_entry%> +</p> + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/app/views/publications/_review_bibtex_step.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,2 @@ +<h2>Review new entries</h2> +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/app/views/publications/add_project.rjs Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,3 @@ +page.replace_html :list_projects, :partial => 'list_projects' +page[:add_project_form].reset +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/app/views/publications/autocomplete_for_author.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,12 @@ +<%= raw @results.map { |result| + { + 'label' => result.name, + 'value' => result.name, + 'search_author_class' => result.class.name, + 'search_author_id' => result.id, + 'name' => result.name, + 'institution' => result.institution, + 'email' => result.mail, + 'authorship_link' => " Keep associated with #{render_authorship_link(result.class.name, result.id)}" + } +}.to_json %> \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/app/views/publications/autocomplete_for_project.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,3 @@ +<% if params[:q] && params[:q].length > 1 %> + <%= projects_check_box_tags 'publication[project_ids][]', @projects %> +<% end %>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/app/views/publications/create.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,1 @@ +<h2>Publications#create</h2>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/app/views/publications/edit.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,39 @@ +<% content_for :header_tags do %> + <%= stylesheet_link_tag 'bibliography', :plugin => 'redmine_bibliography' -%> + <%= javascript_include_tag 'bibliography', :plugin => 'redmine_bibliography' -%> + <%= javascript_include_tag 'authors', :plugin => 'redmine_bibliography' -%> + + <%= javascript_include_tag 'edit_publication', :plugin => 'redmine_bibliography' -%> + + <%= javascript_tag "$('#publication_bibtex_entry_attributes_entry_type').live('change', function() { + $this = $(this); + $.ajax({ + type: 'get', + url: '#{url_for(:controller => :publications, :action => :show_bibtex_fields)}', + data: { + value: $this.val() + }, + dataType: 'script' + }); return false; });" -%> + + <%= javascript_tag "authorship_autocomplete('#{url_for :controller => :publications, :action => :autocomplete_for_author}');" -%> + +<% end %> + +<%= error_messages_for 'publication' %> + +<h2><%=l(:label_publication_show)%></h2> + +<%= form_for @publication, :url => { :project_id => @project, :action => :update } do |f| -%> + + <%= render :partial => 'form', :locals => { :f => f } %> + + <div style="clear:both"></div> + <%= f.submit %> +<% end %> + +<p> + <%= link_to l(:label_publication_show), { :controller => "publications", :action => "show", :id => @publication, :project_id => @project_id } %> | + <%= link_to l(:label_publication_index), { :controller => "publications", :action => "index", :project_id => @project } %> +</p> +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/app/views/publications/import.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,12 @@ +<h1>New Publication</h1> + +<% form_for @publication, :url => { :action => "create" } do |f| %> + <% f.error_messages %> + + <%= render :partial => "#{@publication.current_step}_bibtex_step", :locals => { :f => f } %> + + <p><%= f.submit "Submit" %></p> + <p><%= f.submit "Back", :name => "back_button" unless @publication.first_step? %></p> + +<% end %> +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/app/views/publications/index.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,40 @@ +<div class="contextual"> + <% if User.current.allowed_to?(:add_publication, @project) %> + <%= link_to l(:label_publication_new), {:controller => 'publications', :action => 'new', :project_id => @project}, :class => 'icon icon-add' %> + <% end %> +</div> + + <% if @project %> + <h3><%= l(:label_all_publications_for_project, :project => @project.name) %></h3> + <% else %> + <h3><%= l(:label_all_publications) %></h3> + <% end %> + + <div class="autoscroll"> + <table class="list publications"> + <thead><tr> + <th><%= l(:title) %></th> + <th><%= l(:authors) %></th> + <th><%= l(:year) %></th> + <th><%= l(:associated_projects) %></th> + </tr></thead> + + <%- @publications.each do |publication| -%> + <%- if publication.projects.visible.length > 0 -%> + <tr class="<%= cycle('odd', 'even') %>"> + <td class="firstcol title" align="top"><%= link_to publication.title, :controller => "publications", :action => "show", :id => publication, :project_id => @project %></td> + <td class="authors" align="top"> + <%= render_authorships_list(publication) %> + <td class="year"><%= publication.bibtex_entry.year %></td> + <td class="projects"> + <%= render_projects_list(publication, false) %> + </td> + </tr> + <%- end -%> + <%- end -%> + </table> + </div> + +<% content_for :sidebar do %> +<% end %> +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/app/views/publications/new.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,35 @@ +<% content_for :header_tags do %> + <%= javascript_include_tag 'authors', :plugin => 'redmine_bibliography' %> + <%= stylesheet_link_tag 'bibliography', :plugin => 'redmine_bibliography' %> + <%= javascript_include_tag 'bibliography', :plugin => 'redmine_bibliography' -%> + <%= javascript_include_tag 'new_publication', :plugin => 'redmine_bibliography' -%> + + <%= javascript_tag " + $('#publication_bibtex_entry_attributes_entry_type').live('change', function() { + $this = $(this); + $.ajax({ + type: 'get', + url: '#{url_for(:controller => :publications, :action => :show_bibtex_fields)}', + data: { + value: $this.val() + }, + dataType: 'script' + }); + return false; + });"-%> + + <%= javascript_tag "authorship_autocomplete('#{url_for :controller => :publications, :action => :autocomplete_for_author}');" -%> + +<% end %> + +<%= error_messages_for 'publication' %> + +<h2><%=l(:label_publication_new)%></h2> + +<%= form_for @publication, :url => { :project_id => @project, :action => :create } do |f| -%> + + <%= render :partial => 'form', :locals => { :f => f } %> + + <div style="clear:both"></div> + <%= f.submit %> +<% end %>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/app/views/publications/show.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,90 @@ +<%= javascript_tag "$(document).ready(function(){ + + $('#authorships').sortable({ + axis: 'y', + dropOnEmpty: false, + handle: '.handle', + cursor: 'crosshair', + items: 'li', + opacity: 0.4, + scroll: true, + update: function(){ + $.ajax({ + type: 'post', + data: $('#authorships').sortable('serialize'), + dataType: 'script', + complete: function(request){ + $('#authorship').effect('highlight'); + }, + url: '#{url_for(:controller => :authorships, :action => :sort)}'}); + } + }); + }); +" -%> + +<h2><%=l(:label_publication_show)%></h2> + +<div class="box"> + <h3>Publication Info</h3> + <p><%= print_ieee_format(@publication)%></p> + + <h3>B<small>IB</small>T<sub>E</sub>X Format</h3> + <pre><%=h print_bibtex_format(@publication) %></pre> +</div> + +<div class="box"> + +<h4><%= l(:authors) %></h4> + +<ul id="authorships"> + <% for authorship in @publication.authorships.find(:all, :order => :auth_order) %> + <%= content_tag_for :li, authorship do -%> + <%- if User.current.allowed_to?(:edit_publication, @project) && @publication.authorships.length > 1 -%> + <span class="handle">[drag to reorder]</span> + <%- end -%> + + <%= link_to_authorship authorship %> <em><%= h(authorship.institution) %></em> + + <br /> + <%- end -%> + <%- end -%> +</ul> + +<%- if @publication.bibtex_entry != nil -%> + <%= show_bibtex_fields(@publication.bibtex_entry).html_safe -%> +<%- end -%> + +<%- unless @publication.external_url.blank? -%> + <p> + <b><%= l(:field_external_url) %>:</b> <%= link_to h(@publication.external_url), @publication.external_url, {:target => "_blank"} -%> + </p> +<%- end -%> + +<% unless @publication.doi.blank? %> + <p> + <b><%= l(:field_doi)-%>:</b> <%= link_to h(@publication.doi), "http://dx.doi.org/#{@publication.doi}", {:target => "_blank"} -%> + </p> +<% end %> + +<br / > + <% if User.current.allowed_to?(:add_publication, @project) %> + <%= link_to l(:label_publication_edit), { :controller => "publications", :action => "edit", :id => @publication, :project_id => @project } %> | + <%= link_to "Delete", {:controller => 'publications', :action => 'destroy', :id => @publication, :project_id => @project }, + :confirm => l(:text_are_you_sure), :method => :delete, :title => l(:button_delete) %> | + <% end %> + <%= link_to l(:view_all_publications), {:controller => 'publications', :action => 'index', :project_id => @project } %> +</div> + +<% projects = Project.active.find(:all, :limit => 100, :order => 'name ASC') - @publication.projects %> + +<% content_for :sidebar do %> + <h3><%=l(:label_publication_project_index)%></h3> + + <p id="list_projects"> + <%= render :partial => 'list_projects' %> + </p> + + <%- if User.current.allowed_to?(:edit_publication, @project) -%> + <%= render :partial => 'add_project_form' %> + <%- end -%> +<% end %>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/app/views/publications/show_bibtex_fields.js.erb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,15 @@ +fields = <%= @fields.to_json.html_safe -%>; + +$.each($(".bibtex"), function( key, value ) { + $this = $(this); + + input_id = $this.children('input').attr('id'); + name = input_id.split('_')[4]; + + if ($.inArray(name, fields) !== -1){ + $this.show(); + } + else{ + $this.hide(); + } +}); \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/app/views/publications/update.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,1 @@ +<h2>Publications#update</h2>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/app/views/settings/_bibliography.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,3 @@ +<p><label>Menu caption</label> +<%= text_field_tag 'settings[menu]', @settings['menu'], :size => 30 %> +<br /><em>Clear this field if you don't want to add a tab to the project menu</em></p>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/app/views/users/show.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,104 @@ +<%= stylesheet_link_tag 'bibliography', :plugin => 'redmine_bibliography' %> + +<div class="contextual"> +<%= link_to(l(:button_edit), edit_user_path(@user), :class => 'icon icon-edit') if User.current.admin? %> +</div> + +<h2><%= avatar @user, :size => "50" %> <%=h @user.name %></h2> + +<div class="splitcontentleft"> +<ul> + <% unless @user.pref.hide_mail %> + <li><%=l(:field_mail)%>: <%= mail_to(h(@user.mail), nil, :encode => 'javascript') %></li> + <% end %> + <% @user.visible_custom_field_values.each do |custom_value| %> + <% if !custom_value.value.blank? %> + <li><%=h custom_value.custom_field.name%>: <%=h show_value(custom_value) %></li> + <% end %> + <% end %> + <li><%=l(:label_registered_on)%>: <%= format_date(@user.created_on) %></li> + <% unless @user.last_login_on.nil? %> + <li><%=l(:field_last_login_on)%>: <%= format_date(@user.last_login_on) %></li> + <% end %> +</ul> + +<h3><%=l(:label_ssamr_description)%></h3> +<%= textilizable @description %> + +<h3><%=l(:label_ssamr_institution)%></h3> +<p><%= h @institution_name %></p> + + +<% unless @memberships.empty? %> +<h3><%=l(:label_project_plural)%></h3> +<ul> +<% for membership in @memberships %> + <li><%= link_to_project(membership.project) %> + (<%=h membership.roles.sort.collect(&:to_s).join(', ') %>, <%= format_date(membership.created_on) %>)</li> +<% end %> +</ul> +<% end %> +<%= call_hook :view_account_left_bottom, :user => @user %> +</div> + +<div class="splitcontentright"> + + <% if @user.author %> + <div id="bibliography"> + <% @publications = Publication.all(:include => :authors, :conditions => "authors.id = #{@user.author.id}") %> + + <h3><%=l(:publications) %> <%= "(" + @publications.count.to_s + ")" %> </h3> + + <% @publications.each do |publication|%> + <dt> + <span class="authors"> + <%= publication.authorships.map { |a| h a.name_on_paper }.join(', ') %><% if !publication.authorships.empty? %>.<% end %> + </span> + <span class="title"><%= link_to publication.title, :controller => 'publications', :action => 'show', :id => publication %></span> + <% if publication.bibtex_entry.year.to_s != "" %> + <span class="year"> + (<%= publication.bibtex_entry.year %>) + </span> + <% end %> + </dt> + <dd> + </dd> + <% end %> + </div> + <% end %> + + +<% unless @events_by_day.empty? %> +<h3><%= link_to l(:label_activity), :controller => 'activities', :action => 'index', :id => nil, :user_id => @user, :from => @events_by_day.keys.first %></h3> + +<p> +<%=l(:label_reported_issues)%>: <%= Issue.count(:conditions => ["author_id=?", @user.id]) %> +</p> + +<div id="activity"> +<% @events_by_day.keys.sort.reverse.each do |day| %> +<h4><%= format_activity_day(day) %></h4> +<dl> +<% @events_by_day[day].sort {|x,y| y.event_datetime <=> x.event_datetime }.each do |e| -%> + <dt class="<%= e.event_type %>"> + <span class="time"><%= format_time(e.event_datetime, false) %></span> + <%= content_tag('span', h(e.project), :class => 'project') %> + <%= link_to format_activity_title(e.event_title), e.event_url %></dt> + <dd><span class="description"><%= format_activity_description(e.event_description) %></span></dd> +<% end -%> +</dl> +<% end -%> +</div> + +<% other_formats_links do |f| %> + <%= f.link_to 'Atom', :url => {:controller => 'activities', :action => 'index', :id => nil, :user_id => @user, :key => User.current.rss_key} %> +<% end %> + +<% content_for :header_tags do %> + <%= auto_discovery_link_tag(:atom, :controller => 'activities', :action => 'index', :user_id => @user, :format => :atom, :key => User.current.rss_key) %> +<% end %> +<% end %> +<%= call_hook :view_account_right_bottom, :user => @user %> +</div> + +<% html_title @user.name %>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/assets/javascripts/authors.js Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,45 @@ +function add_author_fields(link, association, content, action) { + var new_id = new Date().getTime(); + var regexp = new RegExp("new_" + association, "g"); + + $(link).before(content.replace(regexp, new_id)); +} + +function remove_fields(link) { + $(link).prev("input[type=hidden]").val("1"); + $(link).closest(".fields").hide(); +} + +function authorship_autocomplete(url){ + $(".author_name_on_paper").live('keyup.autocomplete', function(){ + $this = $(this); + + $this.autocomplete({ + source: url, + minLength: 2, + focus: function(event, ui) { + $this.val(ui.item.label); + return false; + }, + select: function(event, ui){ + $this.closest('div').find("input[id$='institution']").val(ui.item.institution); + $this.closest('div').find("input[id$='email']").val(ui.item.email); + + $this.closest('div').find("input[id$='search_author_class']").val(ui.item. search_author_class); + $this.closest('div').find("input[id$='search_author_id']").val(ui.item. search_author_id); + + $this.closest('div').find("input[id$='search_author_tie']").attr('checked', ' checked'); + $this.closest('div').find("input[id$='search_author_tie']").next('span').replaceWith("<span>" + ui.item.authorship_link + "</span>"); + + // triggers the save button + $this.closest('div').next('div').find('.author_save_btn').click(); + } + }) + .data( "autocomplete" )._renderItem = function( ul, item ) { + return $( "<li>" ) + .data("item.autocomplete", item ) + .append( "<a>" + item.label + "<br><em>" + item.email + "</em><br>" + item. institution + "</a>" ) + .appendTo(ul); + }; + }); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/assets/javascripts/bibliography.js Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,36 @@ +// bibliography.js + +function disable_fields(){ + $this = $(this); + + $author_info = $this.closest('div').prev(); +// $author_info.children('.description').toggle(); + $author_info.find('p :input').attr("readonly", true); + $author_info.find('p :input').addClass('readonly'); + + // Always hides on save + $this.closest('div').prev().find('p.search_author_tie').hide(); + + $this.siblings('.author_edit_btn').show(); + $this.hide(); + + return false; +} + +function enable_fields(){ + $this = $(this); + + $author_info = $this.closest('div').prev(); +// $author_info.children('.description').toggle(); + $author_info.find('p :input').attr("readonly", false); + $author_info.find('p :input').removeClass('readonly'); + + // Always shows on edit + $this.closest('div').prev().find('p.search_author_tie').show(); + + $this.siblings('.author_save_btn').show(); + $this.hide(); + + return false; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/assets/javascripts/bibtex.js Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,5 @@ + +$('.bibtex-link').live("click", function() { + $this = $(this); + $this.closest('dd').next('dd').toggle(); +}); \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/assets/javascripts/edit_publication.js Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,13 @@ +// edit_publication.js + +$(document).ready(function(){ + // shows the correct bibtex fields + $('#publication_bibtex_entry_attributes_entry_type').trigger('change'); + + // adds the events to the edit/save authorship button + $('.author_save_btn').live('click', disable_fields); + $('.author_edit_btn').live('click', enable_fields); + + // clicks all authorships + $('.author_save_btn').trigger('click'); +}); \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/assets/javascripts/new_publication.js Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,9 @@ +// edit_publication.js + +$(document).ready(function(){ + // adds the events to the edit/save authorship button + $('.author_save_btn').live('click', disable_fields); + $('.author_edit_btn').live('click', enable_fields); + + +}); \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/assets/stylesheets/bibliography.css Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,76 @@ +li .handle { + font-size: 12px; + cursor: move; + color: #777; +} + +.readonly { + border: none; + padding: 0; + margin: 0; + background-color: transparent; +} + +.tabular .author_identify label { + font-weight: normal; +} + +.tabular .author_edit p { + padding-bottom: 0; +} + +.tabular .author_edit .description { + font-style: italic; + font-size: small; +} + +.publication_project { + margin-right: 18px; +} + +div#bibliography dd { margin-bottom: 1em; font-size: 0.9em; } + +div#bibliography dd .authors { font-style: italic; } +div#bibliography dd span.authors { color: #808080; } +div#bibliography dd span.year { padding-left: 0.6em; } + +div#bibliography .box dt { + background: url(../../../images/document.png) no-repeat 0% 4px; + padding-left: 20px; + margin-left: 0; +} +div#bibliography .box dd { + padding-left: 20px; + margin-left: 0; +} + +div#bibliography h3 { + background: url(../../../images/table_multiple.png) no-repeat 0% 50%; + padding-left: 20px; +} + +div#bibliography textarea { + width: 90%; + height: 200px; + font: normal 8px; + padding: 2px 10px; + border: solid 1px #ddd; +} + +input.author_search {width:100%} +input.author_search { + background: url(../../../images/magnifier.png) no-repeat 2px 50%; padding-left:20px; + border:1px solid #9EB1C2; border-radius:3px; height:1.5em; width:95%; +} +input.author_search.ajax-loading { + background-image: url(../../../images/loading.gif); +} + +.search_author_tie { + display: none; + float: left; +} + +.author_edit_btn { + display: none; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/config/locales/en.yml Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,154 @@ +# English strings go here for Rails i18n +en: + project_module_redmine_bibliography: "Publications (references to papers related to the project)" + + title: "Title" + authors: "Authors" + author: "Author" + name: "Name" + year: "Year" + associated_projects: "Associated Projects" + publications_box: "My Publications" + label_my_publications_box: "My Publications" + view_all_publications: "View All Project's Publications" + publications: Publications + + identify_author_question: Is the right person selected above? + identify_author_yes: "Yes" + identify_author_correct: "Yes, but I need to correct some details below" + identify_author_no: "No, the author was not found in the search" + + error_no_authors: "Please add at least one author to this publication." + + label_all_publications: All Publications + label_all_publications_for_project: Publications associated with %{project} + label_authors_show: "Authorships associated with this author" + label_authors_index: "List of authors" + + field_authorship_publication_title: "Publication" + field_authorship_name: "Name on Paper" + field_authorship_email: "Email" + field_authorship_institution: "Institution" + + field_external_url: "External URL" + field_doi: "DOI" + field_publication_title: Title + field_publication_authors: Authors + field_publication_projects: "Associated projects" + field_author_name: Author + field_author_user: User Name + field_author_username: "Associated user" + field_author_publications: "Publications by this Author" + field_identify_author_yes: "Yes" + field_identify_author_correct: "Corrections" + field_identify_author_no: "No" + + label_author_is_me: "(I am this author)" + label_add_me_as_author: "Add me as an author" + label_add_an_author: "Add an author" + label_add_another_author: "Add another author" + field_search_name: "Search by name" + field_search_results: "" + label_save_author: "Save author" + label_edit_author: "Edit author" + label_author_information: "Author Information" + + remove_author: "Remove this author" + + label_publication_plural: "Publications" + label_related_publication_plural: "Related publications" + label_publication_new: "Create New Publication" + label_publication_index: "List of Publication" + label_add_publication_to_project: "Add publication to this project" + label_publication_edit: "Edit Publication" + label_publication_show: "Publication Details" + label_add_project_to_publication: "Add this publication to a project" + label_project_search: "Find project by name: " + label_publication_project_index: "Projects associated with this publication" + label_publication_index: "View all publications" + label_publication_other_details: "Details" + + text_external_url: "Link to the publication or to an external page about it." + text_doi: "DOI (Digital Object Identifier)." + text_author_name_on_paper: "Author's name as it appears on the paper." + text_author_institution: "Author's institution as on the paper." + text_author_email: "Author's email address as on the paper." + + text_author_search: "Search existing authors" + + # authorships model + field_institution: "Institution" + field_name_on_paper: "Name" + field_email: "Email Address" + + # bibtex_entries model + field_entry_type: "Publication Type" + field_id: "id" + field_publication_id: "Publication_id" + field_address: "Address" + field_annote: "Annote" + field_booktitle: "Title of Book or Proceedings" + field_chapter: "Chapter" + field_crossref: "Cross Reference" + field_edition: "Edition" + field_editor: "Editor" + field_eprint: "eprint" + field_howpublished: "How was it Published" + field_journal: "Journal" + field_key: "Key" + field_month: "Month" + field_note: "Note" + field_number: "Number" + field_organization: "Organization" + field_pages: "Pages" + field_publisher: "Publisher" + field_school: "School" + field_series: "Series" + field_type: "Type" + field_url: "URL" + field_volume: "Volume" + field_year: "Year" + + field_bibtex_article: Journal article + field_bibtex_book: Book + field_bibtex_booklet: Booklet + field_bibtex_conference: Article in conference proceedings + field_bibtex_inbook: Book chapter or part + field_bibtex_incollection: Part of a collection + field_bibtex_inproceedings: Article in conference proceedings + field_bibtex_manual: Technical manual + field_bibtex_masterthesis: "Master's thesis" + field_bibtex_misc: Other + field_bibtex_phdthesis: "PhD thesis" + field_bibtex_proceedings: Conference proceedings + field_bibtex_techreport: Technical report + field_bibtex_unpublished: Unpublished + + label_author_1: First author + label_author_2: Second author + label_author_3: Third author + label_author_4: Fourth author + label_author_5: Fifth author + label_author_6: Sixth author + label_author_7: Seventh author + label_author_8: Eighth author + label_author_9: Ninth author + label_author_10: Tenth author + label_author_11: Eleventh author + label_author_12: Twelfth author + label_author_13: Thirteenth author + label_author_14: Fourteenth author + label_author_15: Fifteenth author + label_author_16: Sixteenth author + label_author_17: Seventeenth author + label_author_18: Eighteenth author + label_author_19: Nineteenth author + label_author_20: Twentieth author + + mail_subject_publication_added: "You have been added as an author to a new publication" + mail_body_publication_added: "A new publication (%{publication}) has been added to the project '%{project}.'" + + bibtex_link: "[B<small>IB</small>T<sub>E</sub>X]" + more_details_link: "[More Details]" + external_url_link: "[URL (ext.)]" +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/config/routes.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,19 @@ +RedmineApp::Application.routes.draw do + match "publications/show_bibtex_fields", :to => 'publications#show_bibtex_fields', :via => "get" + + match "publications/autocomplete_for_author", :to => 'publications#autocomplete_for_author', :via => "get" + + match "authors/show/:id", :to => 'authors#show' + + match "publications/add_project/:id", :to => 'publications#add_project' + + match "publications/autocomplete_for_project", :to => 'publications#autocomplete_for_project' + + resources :authorships do + collection do + post 'sort', :action => 'sort' + end + end + + resources :publications +end \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/db/migrate/001_create_authors.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,12 @@ +class CreateAuthors < ActiveRecord::Migration + def self.up + create_table :authors do |t| + t.column :user_id, :integer + t.column :name, :string + end + end + + def self.down + drop_table :authors + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/db/migrate/002_create_publications.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,12 @@ +class CreatePublications < ActiveRecord::Migration + def self.up + create_table :publications do |t| + t.column :title, :string + t.column :reviewed, :boolean, :default => false + end + end + + def self.down + drop_table :publications + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/db/migrate/003_create_authorships.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,16 @@ +class CreateAuthorships < ActiveRecord::Migration + def self.up + create_table :authorships do |t| + t.column :author_id, :integer + t.column :publication_id, :integer + t.column :name_on_paper, :string + t.column :auth_order, :integer + t.column :institution, :string + t.column :email, :string + end + end + + def self.down + drop_table :authorships + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/db/migrate/004_create_bibtex_entries.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,35 @@ +class CreateBibtexEntries < ActiveRecord::Migration + def self.up + create_table :bibtex_entries do |t| + t.column :publication_id, :integer + t.column :entry_type, :integer + t.column :address, :string + t.column :annote, :string + t.column :booktitle, :string + t.column :chapter, :string + t.column :crossref, :string + t.column :edition, :string + t.column :editor, :string + t.column :eprint, :string + t.column :howpublished, :string + t.column :journal, :string + t.column :key, :string + t.column :month, :string + t.column :note, :text + t.column :number, :string + t.column :organization, :string + t.column :pages, :string + t.column :publisher, :string + t.column :school, :string + t.column :series, :string + t.column :type, :string + t.column :url, :string + t.column :volume, :integer + t.column :year, :integer + end + end + + def self.down + drop_table :bibtex_entries + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/db/migrate/005_create_projects_publications_join_table.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,12 @@ +class CreateProjectsPublicationsJoinTable < ActiveRecord::Migration + def self.up + create_table :projects_publications, :id => false do |t| + t.integer :project_id + t.integer :publication_id + end + end + + def self.down + drop_table :projects_publications + end +end \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/db/migrate/006_create_bibtex_entry_types.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,13 @@ +class CreateBibtexEntryTypes < ActiveRecord::Migration + def self.up + create_table :bibtex_entry_types do |t| + t.string :name + + t.timestamps + end + end + + def self.down + drop_table :bibtex_entry_types + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/db/migrate/007_add_external_url_column_to_publications.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,9 @@ +class AddExternalUrlColumnToPublications < ActiveRecord::Migration + def self.up + add_column :publications, :external_url, :string + end + + def self.down + remove_column :publications, :external_url + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/db/migrate/008_add_doi_and_timestamp_columns_to_publications.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,12 @@ +class AddDoiAndTimestampColumnsToPublications < ActiveRecord::Migration + def self.up + add_column :publications, :doi, :string + add_timestamps :publications + + end + + def self.down + remove_column :publications, :doi + remove_timestamps :publications + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/db/migrate/009_add_timestamp_columns_to_authors.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,9 @@ +class AddTimestampColumnsToAuthors < ActiveRecord::Migration + def self.up + add_timestamps :authors + end + + def self.down + remove_timestamps :authors + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/db/seed_data/bibtex_entry_types_list.txt Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,14 @@ +inproceedings +conference +article +masterthesis +phdthesis +book +booklet +inbook +incollection +manual +techreport +proceedings +unpublished +misc
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/init.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,64 @@ +require 'redmine' + +require 'bibtex' +require 'citeproc' + +# Patches to the Redmine core. +ActionDispatch::Callbacks.to_prepare do + require_dependency 'project' + require_dependency 'user' + require_dependency 'mailer' + + unless Project.included_modules.include? Bibliography::ProjectPublicationsPatch + Project.send(:include, Bibliography::ProjectPublicationsPatch) + end + + unless User.included_modules.include? Bibliography::UserAuthorPatch + User.send(:include, Bibliography::UserAuthorPatch) + end + + unless Mailer.included_modules.include? Bibliography::MailerPatch + Mailer.send(:include, Bibliography::MailerPatch) + end + + unless ProjectsHelper.included_modules.include?(Bibliography::ProjectsHelperPatch) + ProjectsHelper.send(:include, Bibliography::ProjectsHelperPatch) + end + + unless MyHelper.included_modules.include?(Bibliography::MyHelperPatch) + MyHelper.send(:include, Bibliography::MyHelperPatch) + end +end + + +# Plugin Info +Redmine::Plugin.register :redmine_bibliography do + name 'Redmine Bibliography plugin' + author 'Chris Cannam, Luis Figueira' + description 'This is a bibliography management plugin for Redmine' + version '0.0.1' + url 'http://example.com/path/to/plugin' + author_url 'http://example.com/about' + + settings :default => { 'menu' => 'Publications' }, :partial => 'settings/bibliography' + + project_module :redmine_bibliography do + permission :view_publication, {:publications => :show}, :public => :true + permission :publications, { :publications => :index }, :public => true + permission :edit_publication, {:publications => [:edit, :update]} + permission :add_publication, {:publications => [:new, :create]} + permission :delete_publication, {:publications => :destroy} + + + end + + # extending the Project Menu + menu :project_menu, :publications, { :controller => 'publications', :action => 'index', :path => nil }, :after => :activity, :param => :project_id, :caption => Proc.new { Setting.plugin_redmine_bibliography['menu'] }, + :if => Proc.new { !Setting.plugin_redmine_bibliography['menu'].blank? } + + activity_provider :publication, :class_name => 'Publication', :default => true + +end + + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/lang/en.yml Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,2 @@ +# English strings go here +my_label: "My label"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/lib/bibliography/mailer_patch.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,30 @@ +require_dependency 'mailer' + +module Bibliography + module MailerPatch + def self.included(base) # :nodoc: + + # Builds a tmail object used to email the specified user that a publication was created and the user is + # an author of that publication + # + # Example: + # publication_added(user) => tmail object + # Mailer.deliver_add_to_project(user) => sends an email to the registered user + def publication_added(user, publication, project) + + @publication = publication + @project = project + + set_language_if_valid user.language + + mail :to => user.mail, + :subject => l(:mail_subject_register, Setting.app_title) + + @publication_url = url_for( :controller => 'publications', :action => 'show', :id => publication.id ) + @publication_title = publication.title + end + + + end + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/lib/bibliography/my_helper_patch.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,52 @@ +module Bibliography + module MyHelperPatch + + def self.included(base) # :nodoc: + base.send(:include, InstanceMethods) + + base.class_eval do + unloadable + end + end + + module InstanceMethods + + def get_my_publications() + if not User.current.author.nil? + @my_publications = Publication.all(:include => :authors, :conditions => "authors.id = #{User.current.author.id}") + else + @my_publications = [] + end + end + + def render_publications_projects(publication) + s = "" + projs = [] + + publication.projects.each do |proj| + projs << link_to(proj.name, proj) + end + + s << projs.join(', ') + + s.html_safe + end + + def render_publications_authors(publication) + s = "" + auths = [] + + publication.authorships.each do |auth| + auths << h(auth.name_on_paper) + end + + s << auths.join(', ') + + s.html_safe + end + + + end + end +end +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/lib/bibliography/project_publications_patch.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,25 @@ +require_dependency 'project' + +module Bibliography + module ProjectPublicationsPatch + def self.included(base) + base.class_eval do + has_and_belongs_to_many :publications, :uniq => true + + scope :name_or_homepage_like, lambda {|q| + s = "%#{q.to_s.strip.downcase}%" + {:conditions => ["LOWER(name) LIKE :s OR LOWER(homepage) LIKE :s", {:s => s}], + :order => 'name' + } + } + end + end #self.included + + module ProjectMethods + + + + + end #ProjectMethods + end #ProjectPublicationsPatch +end #RedmineBibliography \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/lib/bibliography/projects_helper_patch.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,17 @@ +module Bibliography + module ProjectsHelperPatch + + def self.included(base) # :nodoc: + base.send(:include, InstanceMethods) + base.send(:include, PublicationsHelper) + + base.class_eval do + unloadable + end + end + + module InstanceMethods + end + end +end +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/lib/bibliography/user_author_patch.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,47 @@ +require_dependency 'user' + +module Bibliography + module UserAuthorPatch + def self.included(base) + base.send(:include, InstanceMethods) + + base.class_eval do + # adapted from the app/models/principals_model.rb + # to remove the email address from the search + scope :name_like, lambda {|q| + q = q.to_s + if q.blank? + where({}) + else + pattern = "%#{q}%" + sql = %w(login firstname lastname).map {|column| "LOWER(#{ table_name}. #{column}) LIKE LOWER(:p)"}.join(" OR ") + params = {:p => pattern} + if q =~ /^(.+)\s+(.+)$/ + a, b = "#{$1}%", "#{$2}%" + sql << " OR (LOWER(#{table_name}.firstname) LIKE LOWER(:a) AND LOWER (#{table_name}.lastname) LIKE LOWER(:b))" + sql << " OR (LOWER(#{table_name}.firstname) LIKE LOWER(:b) AND LOWER (#{table_name}.lastname) LIKE LOWER(:a))" + params.merge!(:a => a, :b => b) + end + where(sql, params) + end + } + end #base.class_eval + + end #self.included + + module InstanceMethods + + # todo: deprecated? ~lf.20131011 + def institution + unless self.ssamr_user_detail.nil? + institution_name = self.ssamr_user_detail.institution_name + else + institution_name = "No Institution Set" + end + return institution_name + end + + end #InstanceMethods + + end #UserPublicationsPatch +end #RedmineBibliography
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/lib/tasks/seed_bibtex_entry_types.rake Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,20 @@ +namespace :redmine do + namespace :plugins do + namespace :redmine_bibliography do + + task :seed_bibtex_entry_types => :environment do + desc "Seeds the Bibtex Entry Types Table" + + quoted = ActiveRecord::Base.connection.quote_table_name('bibtex_entry_types') + ActiveRecord::Base.connection.execute("TRUNCATE #{quoted}") + + open(File.dirname(__FILE__) + "/../../db/seed_data/bibtex_entry_types_list.txt") do |bibtex_entry_types| + bibtex_entry_types.read.each_line do |bibtex_entry_type| + BibtexEntryType.create(:name => bibtex_entry_type.chomp) + end + end + end + + end + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/test/fixtures/authors.yml Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,18 @@ +# authors.yml +--- +one: + id: 1 + user_id: 1 + name: +two: + id: 2 + user_id: + name: +three: + id: 3 + user_id: + name: +four: + id: 4 + user_id: + name:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/test/fixtures/authorships.yml Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,42 @@ +# authorships.yml +--- +one: + id: 1 + author_id: 1 + publication_id: 1 + name_on_paper: Yih-Farn R. Chen + auth_order: 1 + institution: Imperial College London + email: chen@imperial.ac.uk +two: + id: 2 + author_id: 2 + publication_id: 1 + name_on_paper: Glenn S. Fowler + auth_order: 2 + institution: + email: MyString +three: + id: 3 + author_id: 3 + publication_id: 1 + name_on_paper: Jackie Brown + auth_order: 1 + institution: + email: j.brown@m.com +four: + id: 4 + author_id: 4 + publication_id: 1 + name_on_paper: Captain Boomtime + auth_order: 2 + institution: + email: cpt.boom@time.co.uk +five: + id: 5 + author_id: 1 + publication_id: 2 + name_on_paper: Yih-Farn Chen + auth_order: 1 + institution: "Imperial College, London" + email: yfc@gmail.com
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/test/fixtures/bibtex_entries.yml Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,7 @@ +# bibtex_entries.yml +--- +one: + id: 1 + entry_type: InProceedings + booktitle: International Conference on Software Maintenance + year: 1995
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/test/fixtures/publications.yml Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,13 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html +one: + id: 1 + title: Publication Number 1 + reviewed: true + external_url: + doi: +two: + id: 2 + title: Publication Number 2 + reviewed: false + external_url: + doi:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/test/fixtures/users.yml Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,167 @@ +# users.yml +--- +users_004: + created_on: 2006-07-19 19:34:07 +02:00 + status: 1 + last_login_on: + language: en + # password = foo + salt: 3126f764c3c5ac61cbfc103f25f934cf + hashed_password: 9e4dd7eeb172c12a0691a6d9d3a269f7e9fe671b + updated_on: 2006-07-19 19:34:07 +02:00 + admin: false + mail: rhill@somenet.foo + lastname: Hill + firstname: Robert + id: 4 + auth_source_id: + mail_notification: all + login: rhill + type: User +users_001: + created_on: 2006-07-19 19:12:21 +02:00 + status: 1 + last_login_on: 2006-07-19 22:57:52 +02:00 + language: en + # password = admin + salt: 82090c953c4a0000a7db253b0691a6b4 + hashed_password: b5b6ff9543bf1387374cdfa27a54c96d236a7150 + updated_on: 2006-07-19 22:57:52 +02:00 + admin: true + mail: admin@somenet.foo + lastname: Admin + firstname: redMine + id: 1 + auth_source_id: + mail_notification: all + login: admin + type: User +users_002: + created_on: 2006-07-19 19:32:09 +02:00 + status: 1 + last_login_on: 2006-07-19 22:42:15 +02:00 + language: en + # password = jsmith + salt: 67eb4732624d5a7753dcea7ce0bb7d7d + hashed_password: bfbe06043353a677d0215b26a5800d128d5413bc + updated_on: 2006-07-19 22:42:15 +02:00 + admin: false + mail: jsmith@somenet.foo + lastname: Smith + firstname: John + id: 2 + auth_source_id: + mail_notification: all + login: jsmith + type: User +users_003: + created_on: 2006-07-19 19:33:19 +02:00 + status: 1 + last_login_on: + language: en + # password = foo + salt: 7599f9963ec07b5a3b55b354407120c0 + hashed_password: 8f659c8d7c072f189374edacfa90d6abbc26d8ed + updated_on: 2006-07-19 19:33:19 +02:00 + admin: false + mail: dlopper@somenet.foo + lastname: Lopper + firstname: Dave + id: 3 + auth_source_id: + mail_notification: all + login: dlopper + type: User +users_005: + id: 5 + created_on: 2006-07-19 19:33:19 +02:00 + # Locked + status: 3 + last_login_on: + language: en + hashed_password: 1 + updated_on: 2006-07-19 19:33:19 +02:00 + admin: false + mail: dlopper2@somenet.foo + lastname: Lopper2 + firstname: Dave2 + auth_source_id: + mail_notification: all + login: dlopper2 + type: User +users_006: + id: 6 + created_on: 2006-07-19 19:33:19 +02:00 + status: 0 + last_login_on: + language: '' + hashed_password: 1 + updated_on: 2006-07-19 19:33:19 +02:00 + admin: false + mail: '' + lastname: Anonymous + firstname: '' + auth_source_id: + mail_notification: only_my_events + login: '' + type: AnonymousUser +users_007: + id: 7 + created_on: 2006-07-19 19:33:19 +02:00 + status: 1 + last_login_on: + language: '' + hashed_password: 1 + updated_on: 2006-07-19 19:33:19 +02:00 + admin: false + mail: someone@foo.bar + lastname: One + firstname: Some + auth_source_id: + mail_notification: only_my_events + login: someone + type: User +users_008: + id: 8 + created_on: 2006-07-19 19:33:19 +02:00 + status: 1 + last_login_on: + language: 'it' + # password = foo + salt: 7599f9963ec07b5a3b55b354407120c0 + hashed_password: 8f659c8d7c072f189374edacfa90d6abbc26d8ed + updated_on: 2006-07-19 19:33:19 +02:00 + admin: false + mail: miscuser8@foo.bar + lastname: Misc + firstname: User + auth_source_id: + mail_notification: only_my_events + login: miscuser8 + type: User +users_009: + id: 9 + created_on: 2006-07-19 19:33:19 +02:00 + status: 1 + last_login_on: + language: 'it' + hashed_password: 1 + updated_on: 2006-07-19 19:33:19 +02:00 + admin: false + mail: miscuser9@foo.bar + lastname: Misc + firstname: User + auth_source_id: + mail_notification: only_my_events + login: miscuser9 + type: User +groups_010: + id: 10 + lastname: A Team + type: Group +groups_011: + id: 11 + lastname: B Team + type: Group + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/test/functional/authors_controller_test.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,13 @@ +# authors_controller_test.rb + +require File.expand_path(File.dirname(__FILE__) + '/../test_helper') + +class AuthorsControllerTest < ActionController::TestCase + self.fixture_path = File.dirname(__FILE__) + "/../fixtures/" + fixtures :users, :authors, :authorships + + def test_truth + assert true + end + +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/test/functional/authorships_controller_test.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,8 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class AuthorshipsControllerTest < ActionController::TestCase + # Replace this with your real tests. + def test_truth + assert true + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/test/functional/publications_controller_test.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,8 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class PublicationsControllerTest < ActionController::TestCase + # Replace this with your real tests. + def test_truth + assert true + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/test/test_helper.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,27 @@ +ENV['RAILS_ENV'] ||= 'test' + +# Load the normal Rails helper +require File.expand_path(File.dirname(__FILE__) + '/../../../test/test_helper') +require File.expand_path(File.dirname(__FILE__) + '/../app/controllers/publications_controller') + +class BibliographyControllerTest < ActionController::TestCase + self.fixture_path = File.dirname(__FILE__) + "/fixtures/" + + fixtures :authors + + def setup + + end + + def test_truth + assert true + end + + # def test_routing + # assert_routing( + # {:method => :get, :path => '/requirements'}, + # :controller => 'requirements', :action => 'index' + # ) + # end + +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/test/unit/author_test.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,17 @@ +# author_test.rb + +require File.expand_path(File.dirname(__FILE__) + '/../test_helper') + +class AuthorTest < ActiveSupport::TestCase + self.fixture_path = File.dirname(__FILE__) + "/../fixtures/" + fixtures :users, :authors, :authorships + + def test_relationships + author = Author.find(1) + + assert_equal(author.authorships.first.name_on_paper, "Yih-Farn R. Chen") + assert_equal(author.authorships.count, 2) + + end + +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/test/unit/authorship_test.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,16 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class AuthorshipTest < ActiveSupport::TestCase + fixtures :authorships + + # Replace this with your real tests. + def test_truth + luis = Author.first + + + assert true + end + + + +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/test/unit/bibtex_entry_test.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,10 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class BibtexEntryTest < ActiveSupport::TestCase + fixtures :bibtex_entries + + # Replace this with your real tests. + def test_truth + assert true + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_bibliography/test/unit/publication_test.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,31 @@ +# publication_test + +require File.expand_path(File.dirname(__FILE__) + '/../test_helper') + +class PublicationTest < ActiveSupport::TestCase + self.fixture_path = File.dirname(__FILE__) + "/../fixtures/" + + fixtures :publications, :authorships + + # Replace this with your real tests. + def test_truth + assert true + end + + def test_relationships + # test authorships - publication relationship + publication = Publication.find(1) + + assert 4, publication.authorships.count + end + + def test_new_publication_validations + pub = Publication.create + + assert !pub.valid?, "!pub.valid?" + assert_equal 2, pub.errors.count, "Number of errors" + assert_equal ["can't be blank"], pub.errors[:title] + assert_equal ["Please add at least one author to this publication."], pub.errors[:authorships] + end + +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_checkout/README.rdoc Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,43 @@ += Redmine Checkout plugin + +Author:: Holger Just +URL:: http://dev.holgerjust.de/projects/redmine-checkout + +This plugin to Redmine adds a link to the actual repository to the GUI. + +This plugin includes ZeroClipboard[http://code.google.com/p/zeroclipboard/] +by Joseph Huckaby. This software is licensed under the +{GNU Lesser General Public License}[http://www.gnu.org/licenses/lgpl.html]. + +Copyright (c) 2009, 2010 Holger Just + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + += Installation + +The installation follows the standard installation path from +http://www.redmine.org/projects/redmine/wiki/Plugins + +1. Copy the software to the vendor/plugins directory. Make sure that the name + of the directory is redmine_checkout. +2. Run rake db:migrate_plugins RAILS_ENV=production +3. Restart Redmine
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_checkout/Rakefile Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,10 @@ +#!/usr/bin/env ruby +require 'redmine_plugin_support' + +Dir[File.expand_path(File.dirname(__FILE__)) + "/lib/tasks/**/*.rake"].sort.each { |ext| load ext } + +RedminePluginSupport::Base.setup do |plugin| + plugin.project_name = 'redmine_checkout' + plugin.default_task = [:spec] + plugin.tasks = [:doc, :release, :clean, :spec, :stats] +end \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_checkout/app/views/projects/settings/_repository_checkout.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,54 @@ +<p><%= form.select(:checkout_overwrite, [ + [l(:general_text_Yes), "1"], + [l(:general_text_No), "0"] + ], + {}, + :onchange => <<-EOF + Effect.toggle($('checkout_settings'), 'slide', {duration:0.2}); + EOF + )%></p> + +<div id="checkout_settings" <%= 'style="display:none;"' unless form.object.checkout_overwrite? %>><fieldset> + <legend><%=l :label_checkout %></legend> + + <p><%= form.text_area :checkout_description, :cols => 60, :rows => 5, :class => 'wiki-edit', :label => :field_description %></p> + <%= wikitoolbar_for 'repository_checkout_description' %> + + <% if form.object.scm_name == 'Subversion' %> + <p><%= form.select :checkout_display_login,[ + [l(:label_display_login_none), ''], + [l(:label_display_login_username), 'username'], + [l(:label_display_login_password), 'password'] + ], + :label => :setting_checkout_display_login %></p> + <% end %> + + <p><%= form.check_box :checkout_display_command %></p> + + <% javascript_tag do %> + protocolForm = new Subform( + '<%= escape_javascript(render(:partial => "projects/settings/repository_checkout_protocol", :locals => {:protocol => Checkout::Protocol.new({:protocol => form.object.scm_name, :append_path => form.object.allow_subtree_checkout? ? 1: 0, :repository => form.object})})) %>', + <%= form.object.checkout_protocols.length %>, + 'checkout_protocol_table' + ); + <% end %> + <p><label><%=l :label_protocol_plural %></label><%=l :help_repository_checkout_protocols %></p> + <%= hidden_field_tag 'repository[checkout_protocols][-1][protocol]', 'empty' %> + <table class="list checkout_protocol_table"> + <thead><tr> + <th class="protocol_protocol" ><%= l(:setting_protocol)%></th> + <th class="protocol_command" ><%= l(:setting_checkout_command)%></th> + <th class="protocol_fixed_url" ><%= l(:setting_checkout_fixed_url) %></th> + <th class="protocol_access" ><%= l(:label_permissions) %></th> + <th class="protocol_append_path"><%= l(:label_append_path) %></th> + <th class="protocol_is_default" ><%= l(:label_default) %></th> + <th class="protocol_delete" ></th> + </tr></thead> + <tbody id="checkout_protocol_table"> + <% form.object.checkout_protocols.each_with_index do |protocol, index| %> + <%= render :partial => 'projects/settings/repository_checkout_protocol', :locals => {:protocol => protocol, :index => index, :classes => cycle('odd', 'even')} %> + <% end %> + </tbody> + </table> + <div style="text-align: right"><%= link_to_function l(:button_add_protocol), "protocolForm.add()", {:class => "icon icon-add"} %></div> +</fieldset></div>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_checkout/app/views/projects/settings/_repository_checkout_protocol.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,18 @@ +<% + index ||= "--INDEX--" + classes ||= "" + + protocol = Checkout::Protocol.new(protocol) unless protocol.is_a? Checkout::Protocol +%> +<tr id="<%= "checkout_protocols_#{index}" %>" class="<%= classes %>" <%= 'style="display:none"' if index == '--INDEX--' %>> + <td class="protocol_protocol"><%= text_field_tag "repository[checkout_protocols][#{index}][protocol]", protocol.protocol, :size => 10 %></td> + <td class="protocol_command"><%= text_field_tag "repository[checkout_protocols][#{index}][command]", protocol.command, :size => 15 %></td> + <td class="protocol_fixed_url"><%= text_field_tag "repository[checkout_protocols][#{index}][fixed_url]", protocol.fixed_url, :size => 60 %></td> + <td class="protocol_access"><%= select_tag "repository[checkout_protocols][#{index}][access]", options_for_select([ + [l(:label_access_read_write), 'read+write'], + [l(:label_access_read_only), 'read-only'], + [l(:label_access_permission), 'permission']], protocol.access) %></td> + <td class="protocol_append_path"><%= check_box_tag "repository[checkout_protocols][#{index}][append_path]", 1, protocol.append_path? %></td> + <td class="protocol_is_default"><%= check_box_tag "repository[checkout_protocols][#{index}][is_default]", 1, protocol.default? %></td> + <td class="protocol_delete"><%= image_to_function 'delete.png', "var e=$('checkout_protocols_#{index}');var parent=e.up(\"tbody\");e.remove();recalculate_even_odd(parent);return false" %></td> +</tr>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_checkout/app/views/redmine_checkout_hooks/_view_repositories_show_contextual.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,46 @@ + +<% content_for :header_tags do %> + <%= stylesheet_link_tag 'checkout', :plugin => 'redmine_checkout' %> +<% end %> + +<div class="repository-info"> + <% if repository.checkout_description.present? %> + <div class="wiki<%= ' bottomline' if protocols.present? %>"><%= textilizable repository.checkout_description %></div> + <% end %> + <% if protocols.present? %> + <div id="checkout_box"> + <ul id="checkout_protocols"> + <% protocols.each do |p| -%> + <li> + <a <%= 'class="selected"' if p == default_protocol %> id="checkout_protocol_<%= p.protocol.to_s.underscore %>" data-permission="<%= p.access_rw(User.current) %>" href="<%= URI.escape p.url(checkout_path) %>"><%=h p.protocol %></a> + </li> + <% end -%> + </ul> + <%= text_field_tag :checkout_url, h(default_protocol.full_command(checkout_path)), :readonly => true %> + + <p> + <% if User.current.logged? %> + <% if repository.is_external? %> + <%= (l :label_access_type_all, :type => l(:label_access_read_only)).html_safe %> + <% else %> + <% if default_protocol %><%= (l :label_access_type, :type => l(default_protocol.access_label(User.current))).html_safe %><% end %> + <% end %> + <% else %> + + <% end %> + </p> + + <% javascript_tag do %> + var checkout_access = $H({<%= protocols.inject([]){|r,p| r << "'checkout_protocol_#{p.protocol.to_s.underscore}': '#{l(p.access_label(User.current))}'"}.join(', ') %>}); + var checkout_commands = $H({<%= protocols.inject([]){|r,p| r << "'checkout_protocol_#{p.protocol.to_s.underscore}': '#{escape_javascript(p.full_command(checkout_path))}'"}.join(', ') %>}); + <% end %> + + </div> + <% end%> + <% if repository.is_external? %> + <div style="clear: left"> + </div> + <p class="topline" style="padding-top: 1em"><%= (l(:text_repository_external, :location => repository.external_url)).html_safe %></p> + <% end %> +</div> +<div style="clear: left"></div>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_checkout/app/views/settings/_checkout.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,43 @@ +<% form_tag({:action => 'edit', :tab => 'checkout'}) do %> + +<% javascript_tag do %> +protocolForms = $H(); + +document.observe("dom:loaded", function() { + $('tab-content-checkout').select('fieldset.collapsed').each(function(e){ + e.down('div').hide(); + }); + <% + CheckoutHelper.supported_scm.select{|scm| Setting.enabled_scm.include?(scm)}.each do |scm| + next if Setting.send("checkout_overwrite_description_#{scm}?") + -%> + $('settings_checkout_description_<%= scm %>').up('div').up('div').hide(); + <%- end %> +}); +<% end %> + + +<div class="box tabular settings"> +<p><%= setting_check_box :checkout_display_checkout_info %></p> + +<p><%= setting_text_area :checkout_description_Abstract, :cols => 60, :rows => 5, :class => 'wiki-edit', :label => :field_description %></p> +<%= wikitoolbar_for 'settings_checkout_description_Abstract' %> + +<p><%= setting_check_box :checkout_use_zero_clipboard %></p> + +<% CheckoutHelper.supported_scm.select{|scm| Setting.enabled_scm.include?(scm)}.each do |scm| -%> +<fieldset class="collapsible collapsed"> + <legend onclick="toggleFieldset(this);"><%= "Repository::#{scm}".constantize.scm_name %></legend> + <div><%= render :partial => 'checkout_scm', :locals => {:scm => scm} %></div> +</fieldset> +<%- end %> + +</div> + +<%= submit_tag l(:button_save) %> +<%- end %> + +<% content_for :header_tags do %> + <%= javascript_include_tag 'subform', :plugin => 'redmine_checkout' %> + <%= stylesheet_link_tag 'checkout', :plugin => 'redmine_checkout' %> +<% end %>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_checkout/app/views/settings/_checkout_protocol.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,19 @@ +<% + index ||= "--INDEX--" + classes ||= "" + + protocol = Checkout::Protocol.new(protocol) unless protocol.is_a? Checkout::Protocol +%> +<tr id="<%= "checkout_protocols_#{scm}_#{index}" %>" class="<%= classes %>" <%= 'style="display:none"' if index == '--INDEX--' %>> + <td class="protocol_protocol" ><%= text_field_tag "settings[checkout_protocols_#{scm}][#{index}][protocol]", protocol.protocol, :size => 10 %></td> + <td class="protocol_command" ><%= text_field_tag "settings[checkout_protocols_#{scm}][#{index}][command]", protocol.command, :size => 15 %></td> + <td class="protocol_regex" ><%= text_field_tag "settings[checkout_protocols_#{scm}][#{index}][regex]", protocol.regex, :size => 30 %></td> + <td class="protocol_regex_replacement"><%= text_field_tag "settings[checkout_protocols_#{scm}][#{index}][regex_replacement]", protocol.regex_replacement, :size => 30 %></td> + <td class="protocol_access" ><%= select_tag "settings[checkout_protocols_#{scm}][#{index}][access]", options_for_select([ + [l(:label_access_read_write), 'read+write'], + [l(:label_access_read_only), 'read-only'], + [l(:label_access_permission), 'permission']], protocol.access) %></td> + <td class="protocol_append_path"><%= check_box_tag "settings[checkout_protocols_#{scm}][#{index}][append_path]", 1, protocol.append_path? %></td> + <td class="protocol_is_default"><%= check_box_tag "settings[checkout_protocols_#{scm}][#{index}][is_default]", 1, protocol.default? %></td> + <td class="protocol_delete"><%= image_to_function 'delete.png', "var e=$('checkout_protocols_#{scm}_#{index}');var parent=e.up(\"tbody\");e.remove();recalculate_even_odd(parent);return false" %></td> +</tr>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_checkout/app/views/settings/_checkout_scm.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,46 @@ +<div> + <p><%= setting_check_box "checkout_overwrite_description_#{scm}", :label => :setting_checkout_overwrite_description, :onclick => <<-EOF + Effect.toggle($('settings_checkout_description_#{scm}').up("div").up("div"), 'slide', {duration:0.2}); + EOF + %></p> + + <div> + <p><%= setting_text_area "checkout_description_#{scm}", :cols => 60, :rows => 5, :class => 'wiki-edit', :label => :field_description %></p> + <%= wikitoolbar_for "settings_checkout_description_#{scm}" %> + </div> + + <% if scm == 'Subversion' %> + <p><%= setting_select "checkout_display_login",[ + [l(:label_display_login_username), 'username'], + [l(:label_display_login_password), 'password'] + ], + :blank => :label_display_login_none %></p> + <% end %> + + <p><%= setting_check_box "checkout_display_command_#{scm}", :label => :field_checkout_display_command %></p> + + <% javascript_tag do %> + <% repo = "Repository::#{scm}".constantize %> + var subform = new Subform('<%= escape_javascript(render(:partial => "checkout_protocol", :locals => {:protocol => Checkout::Protocol.new({:protocol => repo.scm_name, :append_path => (repo.allow_subtree_checkout? ? '1' : '0'), :command => repo.checkout_default_command}), :scm => scm})) %>',<%= Setting.send("checkout_protocols_#{scm}").length %>,'settings_checkout_protocols_<%= scm %>'); + protocolForms.set('<%= scm %>', subform); + <% end %> + <p><label><%=l :label_protocol_plural %></label><%=l :help_checkout_protocols %></p> + <table class="list checkout_protocol_table"> + <thead><tr> + <th class="protocol_protocol" ><%= l(:setting_protocol)%></th> + <th class="protocol_command" ><%= l(:setting_checkout_command)%></th> + <th class="protocol_regex" ><%= l(:setting_checkout_url_regex) %></th> + <th class="protocol_regex_replacement"><%= l(:setting_checkout_url_regex_replacement) %></th> + <th class="protocol_access" ><%= l(:label_permissions) %></th> + <th class="protocol_append_path" ><%= l(:label_append_path) %></th> + <th class="protocol_is_default" ><%= l(:label_default) %></th> + <th class="protocol_delete" ></th> + </tr></thead> + <tbody id="settings_checkout_protocols_<%= scm %>"> + <% Setting.send("checkout_protocols_#{scm}").each_with_index do |protocol, index| %> + <%= render :partial => 'checkout_protocol', :locals => {:protocol => Checkout::Protocol.new(protocol), :scm => scm, :index => index, :classes => cycle('odd', 'even')} %> + <% end %> + </tbody> + </table> + <div style="text-align: right"><%= link_to_function l(:button_add_protocol), "protocolForms.get('#{scm}').add()", {:class => "icon icon-add"} %></div> +</div>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_checkout/app/views/settings/_redmine_checkout.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,1 @@ +<%=l(:help_moved_settings, :link => link_to(l(:label_settings_location), {:controller => 'settings', :action => 'index', :tab => 'checkout'})) %>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_checkout/assets/images/ZeroClipboard.as Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,81 @@ +package { + // Simple Set Clipboard System + // Author: Joseph Huckaby + + import flash.display.Stage; + import flash.display.Sprite; + import flash.display.LoaderInfo; + import flash.display.StageScaleMode; + import flash.events.*; + import flash.display.StageAlign; + import flash.display.StageScaleMode; + import flash.external.ExternalInterface; + import flash.system.Security; + import flash.utils.*; + import flash.system.System; + + public class ZeroClipboard extends Sprite { + + private var id:String = ''; + private var button:Sprite; + private var clipText:String = ''; + + public function ZeroClipboard() { + // constructor, setup event listeners and external interfaces + stage.scaleMode = StageScaleMode.EXACT_FIT; + flash.system.Security.allowDomain("*"); + + // import flashvars + var flashvars:Object = LoaderInfo( this.root.loaderInfo ).parameters; + id = flashvars.id; + + // invisible button covers entire stage + button = new Sprite(); + button.buttonMode = true; + button.useHandCursor = true; + button.graphics.beginFill(0xCCFF00); + button.graphics.drawRect(0, 0, Math.floor(flashvars.width), Math.floor(flashvars.height)); + button.alpha = 0.0; + addChild(button); + button.addEventListener(MouseEvent.CLICK, clickHandler); + + button.addEventListener(MouseEvent.MOUSE_OVER, function(event:Event) { + ExternalInterface.call( 'ZeroClipboard.dispatch', id, 'mouseOver', null ); + } ); + button.addEventListener(MouseEvent.MOUSE_OUT, function(event:Event) { + ExternalInterface.call( 'ZeroClipboard.dispatch', id, 'mouseOut', null ); + } ); + button.addEventListener(MouseEvent.MOUSE_DOWN, function(event:Event) { + ExternalInterface.call( 'ZeroClipboard.dispatch', id, 'mouseDown', null ); + } ); + button.addEventListener(MouseEvent.MOUSE_UP, function(event:Event) { + ExternalInterface.call( 'ZeroClipboard.dispatch', id, 'mouseUp', null ); + } ); + + // external functions + ExternalInterface.addCallback("setHandCursor", setHandCursor); + ExternalInterface.addCallback("setText", setText); + + // signal to the browser that we are ready + ExternalInterface.call( 'ZeroClipboard.dispatch', id, 'load', null ); + } + + public function setText(newText) { + // set the maximum number of files allowed + clipText = newText; + } + + public function setHandCursor(enabled:Boolean) { + // control whether the hand cursor is shown on rollover (true) + // or the default arrow cursor (false) + button.useHandCursor = enabled; + } + + private function clickHandler(event:Event):void { + // user click copies text to clipboard + // as of flash player 10, this MUST happen from an in-movie flash click event + System.setClipboard( clipText ); + ExternalInterface.call( 'ZeroClipboard.dispatch', id, 'complete', clipText ); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_checkout/assets/images/button.svg Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,11 @@ +<?xml version="1.0" standalone="no"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> +<svg width="100%" height="100%" version="1.1" xmlns="http://www.w3.org/2000/svg"> + <defs> + <linearGradient id="gradient" x1="100%" y1="100%"> + <stop offset="0%" style="stop-color:#ddd; stop-opacity:1" /> + <stop offset="100%" style="stop-color:#f8f8f8; stop-opacity:1" /> + </linearGradient> + </defs> + <rect width="100%" height="100%" style="fill:url(#gradient)"/> +</svg> \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_checkout/assets/images/button_focus.svg Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,11 @@ +<?xml version="1.0" standalone="no"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> +<svg width="100%" height="100%" version="1.1" xmlns="http://www.w3.org/2000/svg"> + <defs> + <linearGradient id="gradient" x1="100%" y1="100%"> + <stop offset="0%" style="stop-color:#507AAA; stop-opacity:1" /> + <stop offset="100%" style="stop-color:#759fcf; stop-opacity:1" /> + </linearGradient> + </defs> + <rect width="100%" height="100%" style="fill:url(#gradient)"/> +</svg> \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_checkout/assets/images/button_selected.svg Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,11 @@ +<?xml version="1.0" standalone="no"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> +<svg width="100%" height="100%" version="1.1" xmlns="http://www.w3.org/2000/svg"> + <defs> + <linearGradient id="gradient" x1="100%" y1="100%"> + <stop offset="0%" style="stop-color:#aaa; stop-opacity:1" /> + <stop offset="100%" style="stop-color:#ccc; stop-opacity:1" /> + </linearGradient> + </defs> + <rect width="100%" height="100%" style="fill:url(#gradient)"/> +</svg> \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_checkout/assets/javascripts/ZeroClipboard.js Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,311 @@ +// Simple Set Clipboard System +// Author: Joseph Huckaby + +var ZeroClipboard = { + + version: "1.0.7", + clients: {}, // registered upload clients on page, indexed by id + moviePath: 'ZeroClipboard.swf', // URL to movie + nextId: 1, // ID of next movie + + $: function(thingy) { + // simple DOM lookup utility function + if (typeof(thingy) == 'string') thingy = document.getElementById(thingy); + if (!thingy.addClass) { + // extend element with a few useful methods + thingy.hide = function() { this.style.display = 'none'; }; + thingy.show = function() { this.style.display = ''; }; + thingy.addClass = function(name) { this.removeClass(name); this.className += ' ' + name; }; + thingy.removeClass = function(name) { + var classes = this.className.split(/\s+/); + var idx = -1; + for (var k = 0; k < classes.length; k++) { + if (classes[k] == name) { idx = k; k = classes.length; } + } + if (idx > -1) { + classes.splice( idx, 1 ); + this.className = classes.join(' '); + } + return this; + }; + thingy.hasClass = function(name) { + return !!this.className.match( new RegExp("\\s*" + name + "\\s*") ); + }; + } + return thingy; + }, + + setMoviePath: function(path) { + // set path to ZeroClipboard.swf + this.moviePath = path; + }, + + dispatch: function(id, eventName, args) { + // receive event from flash movie, send to client + var client = this.clients[id]; + if (client) { + client.receiveEvent(eventName, args); + } + }, + + register: function(id, client) { + // register new client to receive events + this.clients[id] = client; + }, + + getDOMObjectPosition: function(obj, stopObj) { + // get absolute coordinates for dom element + var info = { + left: 0, + top: 0, + width: obj.width ? obj.width : obj.offsetWidth, + height: obj.height ? obj.height : obj.offsetHeight + }; + + while (obj && (obj != stopObj)) { + info.left += obj.offsetLeft; + info.top += obj.offsetTop; + obj = obj.offsetParent; + } + + return info; + }, + + Client: function(elem) { + // constructor for new simple upload client + this.handlers = {}; + + // unique ID + this.id = ZeroClipboard.nextId++; + this.movieId = 'ZeroClipboardMovie_' + this.id; + + // register client with singleton to receive flash events + ZeroClipboard.register(this.id, this); + + // create movie + if (elem) this.glue(elem); + } +}; + +ZeroClipboard.Client.prototype = { + + id: 0, // unique ID for us + ready: false, // whether movie is ready to receive events or not + movie: null, // reference to movie object + clipText: '', // text to copy to clipboard + handCursorEnabled: true, // whether to show hand cursor, or default pointer cursor + cssEffects: true, // enable CSS mouse effects on dom container + handlers: null, // user event handlers + + glue: function(elem, appendElem, stylesToAdd) { + // glue to DOM element + // elem can be ID or actual DOM element object + this.domElement = ZeroClipboard.$(elem); + + // float just above object, or zIndex 99 if dom element isn't set + var zIndex = 99; + if (this.domElement.style.zIndex) { + zIndex = parseInt(this.domElement.style.zIndex, 10) + 1; + } + + if (typeof(appendElem) == 'string') { + appendElem = ZeroClipboard.$(appendElem); + } + else if (typeof(appendElem) == 'undefined') { + appendElem = document.getElementsByTagName('body')[0]; + } + + // find X/Y position of domElement + var box = ZeroClipboard.getDOMObjectPosition(this.domElement, appendElem); + + // create floating DIV above element + this.div = document.createElement('div'); + var style = this.div.style; + style.position = 'absolute'; + style.left = '' + box.left + 'px'; + style.top = '' + box.top + 'px'; + style.width = '' + box.width + 'px'; + style.height = '' + box.height + 'px'; + style.zIndex = zIndex; + + if (typeof(stylesToAdd) == 'object') { + for (addedStyle in stylesToAdd) { + style[addedStyle] = stylesToAdd[addedStyle]; + } + } + + // style.backgroundColor = '#f00'; // debug + + appendElem.appendChild(this.div); + + this.div.innerHTML = this.getHTML( box.width, box.height ); + }, + + getHTML: function(width, height) { + // return HTML for movie + var html = ''; + var flashvars = 'id=' + this.id + + '&width=' + width + + '&height=' + height; + + if (navigator.userAgent.match(/MSIE/)) { + // IE gets an OBJECT tag + var protocol = location.href.match(/^https/i) ? 'https://' : 'http://'; + html += '<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" codebase="'+protocol+'download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=9,0,0,0" width="'+width+'" height="'+height+'" id="'+this.movieId+'" align="middle"><param name="allowScriptAccess" value="always" /><param name="allowFullScreen" value="false" /><param name="movie" value="'+ZeroClipboard.moviePath+'" /><param name="loop" value="false" /><param name="menu" value="false" /><param name="quality" value="best" /><param name="bgcolor" value="#ffffff" /><param name="flashvars" value="'+flashvars+'"/><param name="wmode" value="transparent"/></object>'; + } + else { + // all other browsers get an EMBED tag + html += '<embed id="'+this.movieId+'" src="'+ZeroClipboard.moviePath+'" loop="false" menu="false" quality="best" bgcolor="#ffffff" width="'+width+'" height="'+height+'" name="'+this.movieId+'" align="middle" allowScriptAccess="always" allowFullScreen="false" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer" flashvars="'+flashvars+'" wmode="transparent" />'; + } + return html; + }, + + hide: function() { + // temporarily hide floater offscreen + if (this.div) { + this.div.style.left = '-2000px'; + } + }, + + show: function() { + // show ourselves after a call to hide() + this.reposition(); + }, + + destroy: function() { + // destroy control and floater + if (this.domElement && this.div) { + this.hide(); + this.div.innerHTML = ''; + + var body = document.getElementsByTagName('body')[0]; + try { body.removeChild( this.div ); } catch(e) {;} + + this.domElement = null; + this.div = null; + } + }, + + reposition: function(elem) { + // reposition our floating div, optionally to new container + // warning: container CANNOT change size, only position + if (elem) { + this.domElement = ZeroClipboard.$(elem); + if (!this.domElement) this.hide(); + } + + if (this.domElement && this.div) { + var box = ZeroClipboard.getDOMObjectPosition(this.domElement); + var style = this.div.style; + style.left = '' + box.left + 'px'; + style.top = '' + box.top + 'px'; + } + }, + + setText: function(newText) { + // set text to be copied to clipboard + this.clipText = newText; + if (this.ready) this.movie.setText(newText); + }, + + addEventListener: function(eventName, func) { + // add user event listener for event + // event types: load, queueStart, fileStart, fileComplete, queueComplete, progress, error, cancel + eventName = eventName.toString().toLowerCase().replace(/^on/, ''); + if (!this.handlers[eventName]) this.handlers[eventName] = []; + this.handlers[eventName].push(func); + }, + + setHandCursor: function(enabled) { + // enable hand cursor (true), or default arrow cursor (false) + this.handCursorEnabled = enabled; + if (this.ready) this.movie.setHandCursor(enabled); + }, + + setCSSEffects: function(enabled) { + // enable or disable CSS effects on DOM container + this.cssEffects = !!enabled; + }, + + receiveEvent: function(eventName, args) { + // receive event from flash + eventName = eventName.toString().toLowerCase().replace(/^on/, ''); + + // special behavior for certain events + switch (eventName) { + case 'load': + // movie claims it is ready, but in IE this isn't always the case... + // bug fix: Cannot extend EMBED DOM elements in Firefox, must use traditional function + this.movie = document.getElementById(this.movieId); + if (!this.movie) { + var self = this; + setTimeout( function() { self.receiveEvent('load', null); }, 1 ); + return; + } + + // firefox on pc needs a "kick" in order to set these in certain cases + if (!this.ready && navigator.userAgent.match(/Firefox/) && navigator.userAgent.match(/Windows/)) { + var self = this; + setTimeout( function() { self.receiveEvent('load', null); }, 100 ); + this.ready = true; + return; + } + + this.ready = true; + this.movie.setText( this.clipText ); + this.movie.setHandCursor( this.handCursorEnabled ); + break; + + case 'mouseover': + if (this.domElement && this.cssEffects) { + this.domElement.addClass('hover'); + if (this.recoverActive) this.domElement.addClass('active'); + } + break; + + case 'mouseout': + if (this.domElement && this.cssEffects) { + this.recoverActive = false; + if (this.domElement.hasClass('active')) { + this.domElement.removeClass('active'); + this.recoverActive = true; + } + this.domElement.removeClass('hover'); + } + break; + + case 'mousedown': + if (this.domElement && this.cssEffects) { + this.domElement.addClass('active'); + } + break; + + case 'mouseup': + if (this.domElement && this.cssEffects) { + this.domElement.removeClass('active'); + this.recoverActive = false; + } + break; + } // switch eventName + + if (this.handlers[eventName]) { + for (var idx = 0, len = this.handlers[eventName].length; idx < len; idx++) { + var func = this.handlers[eventName][idx]; + + if (typeof(func) == 'function') { + // actual function reference + func(this, args); + } + else if ((typeof(func) == 'object') && (func.length == 2)) { + // PHP style object + method, i.e. [myObject, 'myMethod'] + func[0][ func[1] ](this, args); + } + else if (typeof(func) == 'string') { + // name of function + window[func](this, args); + } + } // foreach event handler defined + } // user defined handler for event + } + +};
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_checkout/assets/javascripts/checkout.js Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,33 @@ +document.observe("dom:loaded", function() { + /* update the checkout URL if clicked on a protocol */ + $('checkout_protocols').select('a').each(function(e) { + e.observe('click', function(event) { + $('checkout_url').value = checkout_commands.get(this.id); + $('checkout_protocols').select('a').each(function(e) { + e.removeClassName("selected"); + }); + this.addClassName("selected") + + var value = checkout_access.get(this.id); + $('checkout_access').innerHTML = value; + + event.stop(); + }); + }); + /* select the text field contents if activated */ + Event.observe('checkout_url', 'click', function(event) { + this.activate(); + }); + + if (typeof('ZeroClipboard') != 'undefined') { + $('clipboard_container').show(); + clipboard = new ZeroClipboard.Client(); + clipboard.setHandCursor( true ); + clipboard.glue('clipboard_button', 'clipboard_container'); + + clipboard.addEventListener('mouseOver', function (client) { + clipboard.setText( $('checkout_url').value ); + }); + } +}); +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_checkout/assets/javascripts/subform.js Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,45 @@ +var Subform = Class.create({ + lineIndex: 1, + parentElement: "", + initialize: function(rawHTML, lineIndex, parentElement) { + this.rawHTML = rawHTML; + this.lineIndex = lineIndex; + this.parentElement = parentElement; + }, + + parsedHTML: function() { + return this.rawHTML.replace(/--INDEX--/g, this.lineIndex++); + }, + + add: function() { + var e = $(this.parentElement); + Element.insert(e, { bottom: this.parsedHTML()}); + Effect.toggle(e.childElements().last(), 'slide', {duration:0.2}); + recalculate_even_odd(e); + }, + + add_after: function(e) { + Element.insert(e, { after: this.parsedHTML()}); + Effect.toggle(e.next(), 'slide', {duration:0.2}); + recalculate_even_odd($(this.parentElement)); + }, + + add_on_top: function() { + var e = $(this.parentElement); + Element.insert(e, { top: this.parsedHTML()}); + Effect.toggle(e.childElements().first(), 'slide', {duration:0.2}); + recalculate_even_odd(e); + } +}); + +function recalculate_even_odd(element) { + $A(element.childElements()).inject( + 0, + function(acc, e) + { + e.removeClassName("even"); + e.removeClassName("odd"); + e.addClassName( (acc%2==0) ? "odd" : "even"); return ++acc; + } + ) +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_checkout/assets/stylesheets/checkout.css Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,166 @@ +/* Uncomment the following line for nicer tables if you use the alternate theme (or derived). */ +/* @import url(checkout_alternate.css); */ + +table.checkout_protocol_table td { padding-right: 6px; vertical-align: middle; /* Double the border with of text input boxes */ } +table.checkout_protocol_table td.protocol_access { padding-right: 0; } +table.checkout_protocol_table td input[type=text], .checkout_protocol_table td select { width: 100%; } +table.checkout_protocol_table td.protocol_delete { width: 16px; } +table.checkout_protocol_table td.protocol_append_path, table.checkout_protocol_table td.protocol_is_default { text-align: center; } + +.icon-changeset { background-image: url(../../../images/changeset.png);} + +.repository-info { + background-color: #eee; + border: 1px solid #E4E4E4; + padding: 0 10px; + margin: 4px 0 10px; +} + +.bottomline { + border-bottom: 1px solid #ccc; +} + +.topline { + border-top: 1px solid #ccc; +} + +#checkout_box { + margin: 10px 0; +} + +#checkout_protocols { + height: 23px; + float: left; + margin: 0; + padding: 0; +} + +#checkout_protocols li { + float: left; + list-style-type: none; + margin: 0; + padding: 0; +} + +#checkout_protocols li:first-child a { + border-left-width: 1px; + + /* Standard, Opera 10, IE 9 */ + border-top-left-radius: 3px; + border-bottom-left-radius: 3px; + /* Konquerer */ + -khtml-border-top-left-radius: 3px; + -khtml-border-bottom-left-radius: 3px; + /* Gecko (Firefox, ...) */ + -moz-border-radius: 3px 0 0 3px; + /* Webkit (Chrome, Safari, ...) */ + -webkit-border-top-left-radius: 3px; + -webkit-border-bottom-left-radius: 3px; + /* IE <= 9 not supported */ +} + +#checkout_protocols li a, +#clipboard_button { + background-color: #eee; + background: url(../images/button.svg) 0 0 no-repeat; /* Opera needs an "image" :( - using svg for this so it will scale properly without looking too ugly */ + background: -khtml-gradient(linear, left top, left bottom, from(#f8f8f8), to(#ddd)); /* Konquerer */ + background: -moz-linear-gradient(top, #f8f8f8, #ddd); /* Gecko (Firefox, ...) */ + background: -webkit-gradient(linear, left top, left bottom, from(#f8f8f8), to(#ddd)); /* Webkit (Chrome, Safari, ...) */ + filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#f8f8f8', endColorstr='#dddddd'); /* IE 5.5 - 7 */ + -ms-filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#f8f8f8', endColorstr='#dddddd'); /* IE 8 */ + + border-color: #bbb; + border-style: solid; + border-width: 1px 1px 1px 0; + + color: #333; + display: block; + font-size: 11px; + font-weight: bold; + line-height: 21px; + margin: 0; + padding: 0 10px 0 11px; + text-decoration: none; + text-shadow: 1px 1px 0 #fff; + position: relative; /* to please IE */ +} + +#checkout_protocols li a:hover, +#checkout_protocols li a:focus { + background-color: #507AAA; + background: url(../images/button_focus.svg) 0 0 no-repeat; /* Opera needs an "image" :( - using svg for this so it will scale properly without looking too ugly */ + background: -khtml-gradient(linear, left top, left bottom, from(#759fcf), to(#507AAA)); /* Konquerer */ + background: -moz-linear-gradient(top, #759fcf, #507AAA); /* Gecko (Firefox, ...) */ + background: -webkit-gradient(linear, left top, left bottom, from(#759fcf), to(#507AAA)); /* Webkit (Chrome, Safari, ...) */ + filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#759fcf', endColorstr='#507AAA'); /* IE 5.5 - IE 7 */ + -ms-filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#759fcf', endColorstr='#507AAA'); /* IE 8 */ + + color: #fff; + text-shadow: -1px -1px 0 rgba(0,0,0,0.4); + border-top-color: #759fcf; + border-bottom-color: #507AAA; +} + +#checkout_protocols li a.selected, +#clipboard_button.active { + background-color: #bbb; + background: url(../images/button_selected.svg) 0 0 no-repeat; /* Opera needs an "image" :( - using svg for this so it will scale properly without looking too ugly */ + background: -webkit-gradient(linear, left top, left bottom, from(#ccc), to(#aaa)); /* Konquerer */ + background: -moz-linear-gradient(top, #ccc, #aaa); /* Gecko (Firefox, ...) */ + background: -webkit-gradient(linear, left top, left bottom, from(#ccc), to(#aaa)); /* Webkit (Chrome, Safari, ...) */ + filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#cccccc', endColorstr='#aaaaaa'); /* IE 5.5 - IE 7 */ + -ms-filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#cccccc', endColorstr='#aaaaaa'); /* IE 8 */ + + color: #000; + text-shadow: 1px 1px 0 rgba(255,255,255,0.4); + border-color: #bbb; +} + +#checkout_url { + border: 1px solid #bbb; + border-width: 1px 1px 1px 0; + background-color: #fff; + color: #000; + font-size: 11px; + height: 16px; + padding: 3px 5px 2px; + width: 400px; + font-family: Monaco,"DejaVu Sans Mono","Bitstream Vera Sans Mono","Courier New",monospace; + margin: 0 5px 0 0; + float: left; +} + +#checkout_box p { + color: #666; + line-height: 23px; + font-size: 11px; + margin: 0 0 0 5px; +} + +span#checkout_access { + font-weight: bold; +} + +#clipboard_container { + position: relative; + float: left; + margin-right: 5px; +} + +#clipboard_button { + height: 21px; + width: 23px; + padding: 0; + border-width: 1px; + text-align: center; + + border-radius: 5px; /* Standard, Opera 10, IE 9 */ + -khtml-border-radius: 3px; /* Konquerer */ + -moz-border-radius: 3px ; /* Gecko (Firefox, ...) */ + -webkit-border-radius: 3px; /* Webkit (Chrome, Safari, ...) */ + /* IE <= 9 not supported */ +} + +#clipboard_button img { + padding-top: 2px; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_checkout/assets/stylesheets/checkout_alternate.css Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,2 @@ +table.checkout_protocol_table td { padding-right: 10px !important; /* Double the border with of text input boxes */ } +table.checkout_protocol_table td.protocol_access { padding-right: 2px !important; }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_checkout/config/locales/de.yml Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,39 @@ +de: + label_checkout: "Checkout" + + setting_checkout_display_checkout_info: "Checkout-Informationen anzeigen" + setting_checkout_fixed_url: "Checkout-URL" + setting_checkout_url_regex: "Regulärer Ausdruck" + setting_checkout_url_regex_replacement: "Ersatztext" + setting_checkout_display_login: "Mitgliedsnamen anzeigen" + setting_checkout_command: "Checkout-Befehl" + setting_checkout_use_zero_clipboard: "Zwischenablagen-Helfer anzeigen" + + setting_checkout_overwrite_description: "Standard-Beschreibung überschreiben" + field_checkout_overwrite: "Überschreibe Standardeinstellung für Checkout-Protokolle" + field_checkout_display_command: "Checkout-Befehl anzeigen" + + label_protocol_plural: "Protokolle" + button_add_protocol: "Protokoll hinzufügen" + + label_access_type: 'Diese URL erlaubt Zugriff zum <span id="checkout_access">{{type}}</span>.' + label_access_read_only: 'Nur-Lesen' + label_access_read_write: "Lesen+Schreiben" + label_access_permission: "Abhängig von Benutzer-Rechten" + + label_append_path: "Pfad anhängen" + + label_display_login_none: "Mitgliedsnamen nicht anzeigen" + label_display_login_username: "Mitgliedsnamen anzeigen, aber kein Kennwort" + label_display_login_password: "Mitgliedsnamen und Kennwort anzeigen" + + label_copy_to_clipboard: "In die Zwischenablage kopieren" + + help_checkout_protocols: | + Die URLs in Protokollen werden aus der originalen URL erzeugt, auf die der + reguläre Ausdruck und der Ersatztext angewendet werden. Der Ersatztext + erlaubt Rückwärtsreferenzen zu geklammerten Audrücken mit der \1 Notation. + help_repository_checkout_protocols: | + Lassen Sie das Checkout-URL-Feld leer um die URL des Projektarchivs zu verwenden. + help_moved_settings: "Die Konfigurationsseite wurde nach {{link}} verschoben." + label_settings_location: "Administration -> Konfiguration -> Checkout" \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_checkout/config/locales/en-GB.yml Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,41 @@ +en-GB: + label_checkout: "Checkout" + + setting_checkout_display_checkout_info: "Display checkout information" + setting_checkout_fixed_url: "Checkout URL" + setting_checkout_url_regex: "Regular expression" + setting_checkout_url_regex_replacement: "Replacement text" + setting_checkout_display_login: "Display Login" + setting_checkout_command: "Checkout command" + setting_checkout_use_zero_clipboard: "Display clipboard helper" + + setting_checkout_overwrite_description: "Overwrite default description" + field_checkout_overwrite: "Overwrite default settings for checkout protocols" + field_checkout_display_command: "Display checkout command" + + label_protocol_plural: "Protocols" + button_add_protocol: "Add Protocol" + + label_access_type: 'You have <span id="checkout_access">{{type}}</span> access to this URL.' + label_access_read_only: 'Read Only' + label_access_read_write: "Read and Write" + label_access_permission: "Depending on user's permissions" + + label_append_path: "Append path" + + label_display_login_none: "Do not show login or password" + label_display_login_username: "Show login but no password" + label_display_login_password: "Show login and password" + + label_copy_to_clipboard: "Copy to clipboard" + + help_checkout_protocols: | + The URLs in protocols are generated from applying the regular expression + and the replacement text to the original URL. The replacement text + supports back-references to braced expressions using the \1 notation. + help_repository_checkout_protocols: | + Leave the Checkout URL field empty to use the defined repository URL. + help_moved_settings: "The settings page has been moved to {{link}}." + label_settings_location: "Administration -> Settings -> Checkout" + + text_repository_external: "The primary repository for this project is hosted at <code>{{location}}</code> .<br>This repository is a read-only copy which is updated automatically."
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_checkout/config/locales/en.yml Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,43 @@ +en: + label_checkout: "Checkout" + + setting_checkout_display_checkout_info: "Display checkout information" + setting_checkout_fixed_url: "Checkout URL" + setting_checkout_url_regex: "Regular expression" + setting_checkout_url_regex_replacement: "Replacement text" + setting_checkout_display_login: "Display Login" + setting_checkout_command: "Checkout command" + setting_checkout_use_zero_clipboard: "Display clipboard helper" + + setting_checkout_overwrite_description: "Overwrite default description" + field_checkout_overwrite: "Overwrite default settings for checkout protocols" + field_checkout_display_command: "Display checkout command" + + label_protocol_plural: "Protocols" + button_add_protocol: "Add Protocol" + + label_access_type: 'You have <span id="checkout_access">%{type}</span> access to this URL.' + label_access_type_all: 'All access to this URL is <span id="checkout_access">%{type}</span>.' + label_access_read_only: 'Read Only' + label_access_read_write: "Read and Write" + label_access_permission: "Depending on user's permissions" + + label_append_path: "Append path" + + label_display_login_none: "Do not show login or password" + label_display_login_username: "Show login but no password" + label_display_login_password: "Show login and password" + + label_copy_to_clipboard: "Copy to clipboard" + + help_checkout_protocols: | + The URLs in protocols are generated from applying the regular expression + and the replacement text to the original URL. The replacement text + supports back-references to braced expressions using the \1 notation. + help_repository_checkout_protocols: | + Leave the Checkout URL field empty to use the defined repository URL. + help_moved_settings: "The settings page has been moved to %{link}." + label_settings_location: "Administration -> Settings -> Checkout" + + text_repository_external: "The primary repository for this project is hosted at <code>%{location}</code> .<br>This repository is a read-only copy which is updated automatically every hour." +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_checkout/config/locales/es.yml Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,39 @@ +es: + label_checkout: "Checkout" + + setting_checkout_display_checkout_info: "Display checkout information" + setting_checkout_fixed_url: "URL de checkout" + setting_checkout_url_regex: "Expresion regular" + setting_checkout_url_regex_replacement: "Texto de remplazo" + setting_checkout_display_login: "Mostrar usuario" + setting_checkout_command: "Comando de checkout" + setting_checkout_use_zero_clipboard: "Display clipboard helper" + + setting_checkout_overwrite_description: "Overwrite default description" + field_checkout_overwrite: "Overwrite default settings for checkout protocols" + field_checkout_display_command: "Display checkout command" + + label_protocol_plural: "Protocolos" + button_add_protocol: "Crear Protocolo" + + label_access_type: 'This URL has <span id="checkout_access">{{type}}</span> access.' + label_access_read_only: 'Read-Only' + label_access_read_write: "Read+Write" + label_access_permission: "Depending on user's permissions" + + label_append_path: "Append path" + + label_display_login_none: "No mostrar usuario o contraseña" + label_display_login_username: "Mostrar usuario pero no contraseña" + label_display_login_password: "Mostrar usuario y contraseña" + + label_copy_to_clipboard: "Copy to clipboard" + + help_checkout_protocols: | + The URLs in protocols are generated from applying the regular expression + and the replacement text to the original URL. The replacement text + supports back-references to braced expressions using the \1 notation. + help_repository_checkout_protocols: | + Leave the Checkout URL field empty to use the defined repository URL. + help_moved_settings: "The settings page has been moved to {{link}}." + label_settings_location: "Administration -> Settings -> Checkout"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_checkout/config/locales/fr.yml Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,39 @@ +fr: + label_checkout: "Dépôt" + + setting_checkout_display_checkout_info: "Display checkout information" + setting_checkout_fixed_url: "URL du dépôt" + setting_checkout_url_regex: "Expression Régulière" + setting_checkout_url_regex_replacement: "Texte de substitution" + setting_checkout_display_login: "Affiche le login" + setting_checkout_command: "Checkout command" + setting_checkout_use_zero_clipboard: "Display clipboard helper" + + setting_checkout_overwrite_description: "Overwrite default description" + field_checkout_overwrite: "Overwrite default settings for checkout protocols" + field_checkout_display_command: "Affiche l'URL du dépôt" + + label_protocol_plural: "Protocoles" + button_add_protocol: "Créer un protocole" + + label_access_type: 'This URL has <span id="checkout_access">{{type}}</span> access.' + label_access_read_only: 'Read-Only' + label_access_read_write: "Read+Write" + label_access_permission: "Depending on user's permissions" + + label_append_path: "Append path" + + label_display_login_none: "Ne pas afficher le login ni le mot de passe" + label_display_login_username: "Afficher le login, pas le mot de passe" + label_display_login_password: "Afficher le login et le mot de passe" + + label_copy_to_clipboard: "Copy to clipboard" + + help_checkout_protocols: | + The URLs in protocols are generated from applying the regular expression + and the replacement text to the original URL. The replacement text + supports back-references to braced expressions using the \1 notation. + help_repository_checkout_protocols: | + Leave the Checkout URL field empty to use the defined repository URL. + help_moved_settings: "The settings page has been moved to {{link}}." + label_settings_location: "Administration -> Settings -> Checkout"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_checkout/config/locales/it.yml Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,40 @@ +it: + label_checkout: "Checkout" + + setting_checkout_display_checkout_info: "Visualizza le informazioni sul checkout" + setting_checkout_fixed_url: "URL di checkout" + setting_checkout_url_regex: "Espressione regolare" + setting_checkout_url_regex_replacement: "Testo sostituito" + setting_checkout_display_login: "Login visualizzato" + setting_checkout_command: "Comando per il checkout" + setting_checkout_use_zero_clipboard: "Visualizza l'utility per la copia negli appunti" + + setting_checkout_overwrite_description: "Sovrascrivi la descrizione predefinita" + field_checkout_overwrite: "Sovrascrivi le impostazioni di checkout predefinite" + field_checkout_display_command: "Visualizza il comando per il checkout" + + label_protocol_plural: "Protocolli" + button_add_protocol: "Aggiungi Protocollo" + + label_access_type: 'Questo URL ha accesso <span id="checkout_access">{{type}}</span>.' + label_access_read_only: 'Sola-Lettura' + label_access_read_write: "Lettura+Scrittura" + label_access_permission: "Dipende dai permessi dell'utente" + + label_append_path: "Aggiungi percorso" + + label_display_login_none: "Non mostrare il login e password" + label_display_login_username: "Mostra il login senza password" + label_display_login_password: "Mostra il login e la password" + + label_copy_to_clipboard: "Copia negli appunti" + + help_checkout_protocols: | + Gli URL dei protocolli sono generati applicando le espressioni regolari + ed effettuando la sostituzione dell'URL originale con il testo specificato. + Il testo per la sostituzione può contenere riferimenti a più match usando + la notazione \1 \2... + help_repository_checkout_protocols: | + Lascia il campo URL di checkout bianco per usare l'URL definito nel repository. + help_moved_settings: "La pagina delle impostazioni è stata spostata in {{link}}." + label_settings_location: "Amministrazione -> Impostazioni -> Checkout"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_checkout/config/locales/ja.yml Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,39 @@ +ja: + label_checkout: "Checkout" + + setting_checkout_display_checkout_info: "Display checkout information" + setting_checkout_fixed_url: "チェックアウト URL" + setting_checkout_url_regex: "Regular expression" + setting_checkout_url_regex_replacement: "Replacement text" + setting_checkout_display_login: "ログインの表示" + setting_checkout_command: "Checkout command" + setting_checkout_use_zero_clipboard: "Display clipboard helper" + + setting_checkout_overwrite_description: "Overwrite default description" + field_checkout_overwrite: "Overwrite default settings for checkout protocols" + field_checkout_display_command: "Display checkout command" + + label_protocol_plural: "Protocols" + button_add_protocol: "Add Protocol" + + label_access_type: 'This URL has <span id="checkout_access">{{type}}</span> access.' + label_access_read_only: 'Read-Only' + label_access_read_write: "Read+Write" + label_access_permission: "Depending on user's permissions" + + label_append_path: "Append path" + + label_display_login_none: "ログインとパスワードを非表示" + label_display_login_username: "ログインのみ表示" + label_display_login_password: "ログインとパスワードを表示" + + label_copy_to_clipboard: "Copy to clipboard" + + help_checkout_protocols: | + The URLs in protocols are generated from applying the regular expression + and the replacement text to the original URL. The replacement text + supports back-references to braced expressions using the \1 notation. + help_repository_checkout_protocols: | + Leave the Checkout URL field empty to use the defined repository URL. + help_moved_settings: "The settings page has been moved to {{link}}." + label_settings_location: "Administration -> Settings -> Checkout"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_checkout/config/locales/ko.yml Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,40 @@ +ko: + label_checkout: "Checkout" + + setting_checkout_display_checkout_info: "Display checkout information" + setting_checkout_fixed_url: "체크 아웃 URL" + setting_checkout_url_regex: "정규식" + setting_checkout_url_regex_replacement: "대체 문자열" + setting_checkout_display_login: "로그인 표시" + setting_checkout_command: "Checkout command" + setting_checkout_use_zero_clipboard: "Display clipboard helper" + + setting_checkout_overwrite_description: "Overwrite default description" + field_checkout_overwrite: "Overwrite default settings for checkout protocols" + field_checkout_display_command: "Display checkout command" + + label_protocol_plural: "Protocols" + button_add_protocol: "Add Protocol" + + label_access_type: 'This URL has <span id="checkout_access">{{type}}</span> access.' + label_access_read_only: 'Read-Only' + label_access_read_write: "Read+Write" + label_access_permission: "Depending on user's permissions" + + label_append_path: "Append path" + + label_display_login_none: "로그인과 비밀번호를 보여주지 않습니다." + label_display_login_username: "로그인은 보여주지만 비밀번호는 보여주지 않습니다." + label_display_login_password: "로그인과 비밀번호를 보여줍니다." + + label_copy_to_clipboard: "Copy to clipboard" + + help_checkout_protocols: | + The URLs in protocols are generated from applying the regular expression + and the replacement text to the original URL. The replacement text + supports back-references to braced expressions using the \1 notation. + help_repository_checkout_protocols: | + Leave the Checkout URL field empty to use the defined repository URL. + help_moved_settings: "The settings page has been moved to {{link}}." + label_settings_location: "Administration -> Settings -> Checkout" +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_checkout/config/locales/nl.yml Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,40 @@ +nl: + label_checkout: "Checkout" + + setting_checkout_display_checkout_info: "Checkout-informatie tonen" + setting_checkout_fixed_url: "Checkout-URL" + setting_checkout_url_regex: "Reguliere expressie" + setting_checkout_url_regex_replacement: "Vervangingstekst" + setting_checkout_display_login: "Geef login weer" + setting_checkout_command: "Checkout-opdracht" + setting_checkout_use_zero_clipboard: "Klembord-hulp tonen" + + setting_checkout_overwrite_description: "Standaard omschrijving overschrijven" + field_checkout_overwrite: "Overschrijf standaard instellingen voor checkout-protocollen" + field_checkout_display_command: "Checkout-opdracht tonen" + + label_protocol_plural: "Protocollen" + button_add_protocol: "Protocol toevoegen" + + label_access_type: 'Deze URL heeft <span id="checkout_access">{{type}}</span> toegang.' + label_access_read_only: 'Alleen lezen' + label_access_read_write: "Lezen en schrijven" + label_access_permission: "Afhankelijk van gebruikersrechten" + + label_append_path: "Pad toevoegen" + + label_display_login_none: "Geen logingegevens tonen" + label_display_login_username: "Toon login zonder wachtwoord" + label_display_login_password: "Toon login en wachtwoord" + + label_copy_to_clipboard: "Naar klembord kopiëren" + + help_checkout_protocols: | + De URLs in protocollen worden samengesteld vanuit de originele URL, na + toepassing van de reguliere expressie en vervangingstekst. De vervangingstekst + ondersteunt referenties vanuit tussen haakjes geplaatste expressies + door middel van de \1 notatie. + help_repository_checkout_protocols: | + Laat het veld Checkout-URL leeg om de projectrepository te gebruiken. + help_moved_settings: "De instellingspagina is verplaatst naar {{link}}." + label_settings_location: "Administratie -> Instellingen -> Checkout" \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_checkout/config/locales/pl.yml Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,39 @@ +pl: + label_checkout: "Pobieranie repozytorium" + + setting_checkout_display_checkout_info: "Pokaż informację o możliwości pobrania repozytorium" + setting_checkout_fixed_url: "Adres URL pobierania repozytorium" + setting_checkout_url_regex: "Wyrażenie regularne" + setting_checkout_url_regex_replacement: "Wynikowy adres URL" + setting_checkout_display_login: "Pokaż dane logowania" + setting_checkout_command: "Komenda pobrania repozytorium" + setting_checkout_use_zero_clipboard: "Pokaż pomocnika schowka" + + setting_checkout_overwrite_description: "Nadpisz domyślny opis" + field_checkout_overwrite: "Nadpisz domyślne ustawienia dla protokołów" + field_checkout_display_command: "Pokaż komendę pobrania repozytorium" + + label_protocol_plural: "Protokoły" + button_add_protocol: "Dodaj protokół" + + label_access_type: 'Ten adres URL ma dostęp <span id="checkout_access">{{type}}</span>.' + label_access_read_only: 'Tylko do odczytu' + label_access_read_write: "Odczyt+Zapis" + label_access_permission: "Zależne od uprawnień użytkownika" + + label_append_path: "Dołącz ścieżkę" + + label_display_login_none: "Nie pokazuj loginu ani hasła" + label_display_login_username: "Pokaż tylko login" + label_display_login_password: "Pokaż login i hasło" + + label_copy_to_clipboard: "Kopiuj do schowka" + + help_checkout_protocols: | + Wynikowe adresy URL w protokołach są generowane przez zamianę oryginalnego + adresu URL repozytorium na podstawie wyrażenia regularnego. Wynikowy adres + URL wspiera referencje do grup (tzw. back-references) używając notacji \1. + help_repository_checkout_protocols: | + Pozostaw adres URL pobierania repozytorium pusty aby uzyć adresu zdefiniowanego w ustawieniach projektu. + help_moved_settings: "Ustawienia zostały przeniesione do {{link}}." + label_settings_location: "Administracja -> Ustawienia -> Pobieranie repozytorium"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_checkout/config/locales/ro.yml Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,39 @@ +ro: + label_checkout: "Checkout" + + setting_checkout_display_checkout_info: "Display checkout information" + setting_checkout_fixed_url: "URL-ul pentru checkout" + setting_checkout_url_regex: "Expresie regulata (regexp)" + setting_checkout_url_regex_replacement: "Text inlocuitor (regexp)" + setting_checkout_display_login: "Arata datele pentru autentificare" + setting_checkout_command: "Comanda de checkout" + setting_checkout_use_zero_clipboard: "Display clipboard helper" + + setting_checkout_overwrite_description: "Overwrite default description" + field_checkout_overwrite: "Overwrite default settings for checkout protocols" + field_checkout_display_command: "Display checkout command" + + label_protocol_plural: "Protocols" + button_add_protocol: "Add Protocol" + + label_access_type: 'This URL has <span id="checkout_access">{{type}}</span> access.' + label_access_read_only: 'Read-Only' + label_access_read_write: "Read+Write" + label_access_permission: "Depending on user's permissions" + + label_append_path: "Append path" + + label_display_login_none: "Nu afisa username sau parola" + label_display_login_username: "Afiseaza username-ul, dar nu si parola" + label_display_login_password: "Afiseaza atat username-ul, cat si parola" + + label_copy_to_clipboard: "Copy to clipboard" + + help_checkout_protocols: | + The URLs in protocols are generated from applying the regular expression + and the replacement text to the original URL. The replacement text + supports back-references to braced expressions using the \1 notation. + help_repository_checkout_protocols: | + Leave the Checkout URL field empty to use the defined repository URL. + help_moved_settings: "The settings page has been moved to {{link}}." + label_settings_location: "Administration -> Settings -> Checkout"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_checkout/db/migrate/20091208210439_add_checkout_url_info.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,11 @@ +class AddCheckoutUrlInfo < ActiveRecord::Migration + def self.up + add_column :repositories, :checkout_url_type, :string, :default => 'none', :null => false + add_column :repositories, :checkout_url, :string, :default => '', :null => false + end + + def self.down + remove_column :repository, :checkout_url_type + remove_column :repository, :checkout_url + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_checkout/db/migrate/20091220173312_add_display_login.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,9 @@ +class AddDisplayLogin < ActiveRecord::Migration + def self.up + add_column :repositories, :display_login, :string, :default => 'none', :null => false + end + + def self.down + remove_column :repositories, :display_login + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_checkout/db/migrate/20100118174556_add_render_link.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,9 @@ +class AddRenderLink < ActiveRecord::Migration + def self.up + add_column :repositories, :render_link, :boolean, :null => true + end + + def self.down + remove_column :repositories, :render_link + end +end \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_checkout/db/migrate/20100118235845_remove_defaults.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,14 @@ +class RemoveDefaults < ActiveRecord::Migration + def self.up + change_column :repositories, :checkout_url_type, :string, :default => nil, :null => true + change_column :repositories, :checkout_url, :string, :default => nil, :null => true + change_column :repositories, :display_login, :string, :default => nil, :null => true + end + + def self.down + change_column :repositories, :checkout_url_type, :string, :default => 'none', :null => false + change_column :repositories, :checkout_url, :string, :default => '', :null => false + change_column :repositories, :display_login, :string, :default => 'none', :null => false + end +end +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_checkout/db/migrate/20100118235909_add_overwrite_option.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,15 @@ +class AddOverwriteOption < ActiveRecord::Migration + def self.up + add_column :repositories, :checkout_url_overwrite, :boolean, :default => false, :null => false + + # existing repositories are set to overwrite the default settings + # This is to keep continuity of settings. + Repository.reset_column_information + Repository.update_all({:checkout_url_overwrite, true}) + end + + def self.down + remove_column :repositories, :checkout_url_overwrite + end +end +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_checkout/db/migrate/20100203202320_update_settings.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,36 @@ +class UpdateSettings < ActiveRecord::Migration + def self.up + settings = Setting.plugin_redmine_checkout + if settings['checkout_url_type'] == "overwritten" + settings['checkout_url_type'] = "generated" + end + + if settings.has_key? "checkout_url_regex" + settings['checkout_url_regex_default'] = settings.delete("checkout_url_regex") + end + + if settings.has_key? "checkout_url_regex_replacement" + settings['checkout_url_regex_replacement_default'] = settings.delete("checkout_url_regex_replacement") + end + + Setting.plugin_redmine_checkout = settings + end + + def self.down + settings = Setting.plugin_redmine_checkout + if settings['checkout_url_type'] == "generated" + settings['checkout_url_type'] = "overwritten" + end + + if settings.has_key? "checkout_url_regex_default" + settings['checkout_url_regex'] = settings.delete("checkout_url_regex_default") + end + + if settings.has_key? "checkout_url_regex_replacement_default" + settings['checkout_url_regex_replacement'] = settings.delete("checkout_url_regex_replacement_default") + end + + Setting.plugin_redmine_checkout = settings + end +end +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_checkout/db/migrate/20100426154202_rename_render_link_to_render_type.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,31 @@ +class RenameRenderLinkToRenderType < ActiveRecord::Migration + def self.up + render_link = Setting.plugin_redmine_checkout.delete 'render_link' + unless render_link.nil? + Setting.plugin_redmine_checkout['render_type'] = (render_link == 'true' ? 'link' : 'url') + Setting.plugin_redmine_checkout = Setting.plugin_redmine_checkout + end + + add_column :repositories, :render_type, :string, :default => 'url', :null => false + + Repository.update_all({:render_type => 'link'}, :render_link => true) + Repository.update_all({:render_type => 'url'}, ["render_link != ?", true]) + + remove_column :repositories, :render_link + end + + def self.down + render_type = Setting.plugin_redmine_checkout.delete 'render_type' + unless render_type.nil? + Setting.plugin_redmine_checkout['render_link'] = (render_type == 'link' ? 'true' : 'false') + Setting.plugin_redmine_checkout = Setting.plugin_redmine_checkout + end + + add_column :repositories, :render_link, :boolean, :null => true + + Repository.update_all({:render_link => true}, :render_type => 'link') + Repository.update_all({:render_link => false}, ["render_type != ?", 'link']) + + remove_column :repositories, :render_type + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_checkout/db/migrate/20100512135418_consolidate_repository_options.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,49 @@ +class ConsolidateRepositoryOptions < ActiveRecord::Migration + class Repository < ActiveRecord::Base + def self.inheritance_column + # disable single table inheritance + nil + end + + serialize :checkout_settings, Hash + end + + def self.up + add_column :repositories, :checkout_settings, :text + + Repository.all.each do |r| + r.checkout_settings = { + "checkout_url_type" => r.checkout_url_type, + "checkout_url" => r.checkout_url, + "display_login" => r.display_login, + "render_type" => r.render_type, + "checkout_url_overwrite" => r.checkout_url_overwrite + } + r.save! + end + remove_column :repositories, :checkout_url_type + remove_column :repositories, :checkout_url + remove_column :repositories, :display_login + remove_column :repositories, :render_type + remove_column :repositories, :checkout_url_overwrite + end + + def self.down + add_column :repositories, :checkout_url_type, :string, :default => nil, :null => true + add_column :repositories, :checkout_url, :string, :default => nil, :null => true + add_column :repositories, :display_login, :string, :default => nil, :null => true + add_column :repositories, :render_type, :string, :default => 'url', :null => false + add_column :repositories, :checkout_url_overwrite, :boolean, :default => false, :null => false + + Repository.all.each do |r| + r.checkout_url_type = r.checkout_settings["checkout_url_type"] + r.checkout_url = r.checkout_settings["checkout_url"] + r.display_login = r.checkout_settings["display_login"] + r.render_link = r.checkout_settings["render_link"] + r.checkout_url_overwrite = r.checkout_settings["checkout_url_overwrite"] + r.save! + end + + remove_column :repositories, :checkout_settings + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_checkout/db/migrate/20100609153630_apply_setting_changes.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,113 @@ +class ApplySettingChanges < ActiveRecord::Migration + class Repository < ActiveRecord::Base + def self.inheritance_column + # disable single table inheritance + nil + end + + def scm_name + self.type || 'Abstract' + end + + serialize :checkout_settings, Hash + end + + def self.up + default_commands = { + 'Bazaar' => 'bzr checkout', + 'Cvs' => 'cvs checkout', + 'Darcs' => 'darcs get', + 'Git' => 'git clone', + 'Mercurial' => 'hg clone', + 'Subversion' => 'svn checkout' + } + + ## First migrate the individual repositories + + Repository.all.each do |r| + allow_subtree_checkout = ['Cvs', 'Subversion'].include? r.scm_name + + protocol = case r.checkout_settings['checkout_url_type'] + when 'none', 'generated' + nil + when 'original', 'overwritten' + HashWithIndifferentAccess.new({ "0" => HashWithIndifferentAccess.new({ + :protocol => r.scm_name, + :command => Setting.plugin_redmine_checkout["checkout_cmd_#{r.scm_name}"] || default_commands[r.scm_name], + :regex => "", + :regex_replacement => "", + :fixed_url => (r.checkout_settings['checkout_url_type'] == 'original' ? (r.url || "") : r.checkout_settings["checkout_url"]), + :access => 'permission', + :append_path => (allow_subtree_checkout ? '1' : '0'), + :is_default => '1'}) + }) + end + + r.checkout_settings = Hash.new({ + 'checkout_protocols' => protocol, + 'checkout_description' => "The data contained in this repository can be downloaded to your computer using one of several clients. +Please see the documentation of your version control software client for more information. + +Please select the desired protocol below to get the URL.", + 'checkout_display_login' => (r.checkout_settings['display_login'] == 'none' ? '' : r.checkout_settings['display_login']), + 'checkout_overwrite' => (r.checkout_settings['checkout_url_overwrite'] == 'true') ? '1': '0', + 'checkout_display_command' => (r.checkout_settings["render_type"].to_s == 'cmd') ? '1' : '0' + }) + r.save! + end + + ## Then the global settings + + settings = HashWithIndifferentAccess.new({ + 'display_login' => Setting.plugin_redmine_checkout['display_login'], + 'use_zero_clipboard' => '1', + + 'display_checkout_info' => (Setting.plugin_redmine_checkout['checkout_url_type'] == 'none' ? '0' : '1'), + 'description_Abstract' => <<-EOF +The data contained in this repository can be downloaded to your computer using one of several clients. +Please see the documentation of your version control software client for more information. + +Please select the desired protocol below to get the URL. +EOF + }) + + default_commands.keys.each do |scm| + settings["description_#{scm}"] = '' + settings["overwrite_description_#{scm}"] = '0' + + display_command = (Setting.plugin_redmine_checkout["render_type"].to_s == 'cmd') ? '1' : '0' + settings["display_command_#{scm}"] = display_command + + case Setting.plugin_redmine_checkout['checkout_url_type'] + when 'generated', 'none': + regex = Setting.plugin_redmine_checkout["checkout_url_regex_#{scm}"] + replacement = Setting.plugin_redmine_checkout["checkout_url_regex_replacement_#{scm}"] + when 'original': + regex = '' + replacement = '' + end + + settings["protocols_#{scm}"] = HashWithIndifferentAccess.new({ + # access can be one of + # read+write => this protocol always allows read/write access + # read-only => this protocol always allows read access only + # permission => Access depends on redmine permissions + '0' => HashWithIndifferentAccess.new({ + :protocol => scm, + :command => Setting.plugin_redmine_checkout["checkout_cmd_#{scm}"] || default_commands[scm], + :regex => regex, + :regex_replacement => replacement, + :fixed_url => '', + :access => 'permission', + :append_path => (['Cvs', 'Subversion'].include?(scm) ? '1' : '0'), + :is_default => '1' + }) + }) + end + Setting.plugin_redmine_checkout = settings + end + + def self.down + raise ActiveRecord::IrreversibleMigration.new "Sorry, there is no down migration yet. If you really need one, please create an issue on http://dev.holgerjust.de/projects/redmine-checkout" + end +end \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_checkout/db/migrate/20100808185600_change_protocol_storage_from_hash_to_array.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,58 @@ +class ChangeProtocolStorageFromHashToArray < ActiveRecord::Migration + class Repository < ActiveRecord::Base + def self.inheritance_column + # disable single table inheritance + nil + end + + def scm_name + self.type || 'Abstract' + end + + serialize :checkout_settings, Hash + end + + def self.up + ## First migrate the individual repositories + Repository.all.each do |r| + next unless r.checkout_settings + next unless r.checkout_settings['checkout_protocols'].is_a? Hash + r.checkout_settings['checkout_protocols'] = r.checkout_settings['checkout_protocols'].sort{|(ak,av),(bk,bv)|ak<=>bk}.collect{|id,protocol| protocol} + r.save! + end + + ## Then the global settings + settings = Setting.plugin_redmine_checkout + settings.keys.grep(/^protocols_/).each do |protocols| + next unless settings[protocols].is_a? Hash + settings[protocols] = settings[protocols].sort{|(ak,av),(bk,bv)|ak<=>bk}.collect{|id,protocol| protocol} + end + Setting.plugin_redmine_checkout = settings + end + + def self.down + ## First migrate the individual repositories + Repository.all.each do |r| + next unless r.checkout_settings['checkout_protocols'].is_a? Hash + r.checkout_settings['checkout_protocols'] = r.checkout_settings['checkout_protocols'].inject(HashWithIndifferentAccess.new) do |result, p| + result[result.length.to_s] = p + end + r.save! + end + + ## Then the global settings + settings = Setting.plugin_redmine_checkout + settings.keys.grep(/^protocols_/).each do |protocols| + next unless r.checkout_settings['checkout_protocols'].is_a? Hash + settings[protocols] = settings[protocols].inject(HashWithIndifferentAccess.new) do |result, p| + result[result.length.to_s] = p + end + end + Setting.plugin_redmine_checkout = settings + + + + + raise ActiveRecord::IrreversibleMigration.new "Sorry, there is no down migration yet. If you really need one, please create an issue on http://dev.holgerjust.de/projects/redmine-checkout" + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_checkout/doc/COPYING Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,22 @@ +Copyright (c) 2009 Holger Just + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_checkout/init.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,101 @@ +require 'redmine' + +ActionDispatch::Callbacks.to_prepare do + # Patches + require_dependency 'checkout/settings_controller_patch' + + require_dependency 'checkout/repositories_helper_patch' + require_dependency 'checkout/repository_patch' + + require_dependency 'checkout/settings_helper_patch' + require_dependency 'checkout/setting_patch' +end + +# Hooks +require 'checkout/repository_hooks' + +Redmine::Plugin.register :redmine_checkout do + name 'Redmine Checkout plugin' + url 'http://dev.holgerjust.de/projects/redmine-checkout' + author 'Holger Just' + author_url 'http://meine-er.de' + description 'Add links to the actual repository to the repository view.' + version '0.5' + + requires_redmine :version_or_higher => '0.9' + + settings_defaults = HashWithIndifferentAccess.new({ + 'display_login' => nil, + 'use_zero_clipboard' => '1', + + 'display_checkout_info' => '1', + 'description_Abstract' => <<-EOF +The data contained in this repository can be downloaded to your computer using one of several clients. +Please see the documentation of your version control software client for more information. + +Please select the desired protocol below to get the URL. +EOF + }) + + # this is needed for setting the defaults + require 'checkout/repository_patch' + + CheckoutHelper.supported_scm.each do |scm| + klazz = "Repository::#{scm}".constantize + + settings_defaults["description_#{scm}"] = '' + settings_defaults["overwrite_description_#{scm}"] = '0' + settings_defaults["display_command_#{scm}"] = '0' + + # access can be one of + # read+write => this protocol always allows read/write access + # read-only => this protocol always allows read access only + # permission => Access depends on redmine permissions + settings_defaults["protocols_#{scm}"] = [HashWithIndifferentAccess.new({ + :protocol => scm, + :command => klazz.checkout_default_command, + :regex => '', + :regex_replacement => '', + :fixed_url => '', + :access => 'permission', + :append_path => (klazz.allow_subtree_checkout? ? '1' : '0'), + :is_default => '1' + })] + end + + settings :default => settings_defaults, :partial => 'settings/redmine_checkout' + + Redmine::WikiFormatting::Macros.register do + desc <<-EOF +Creates a checkout link to the actual repository. Example: + + use the default checkout protocol !{{repository}} + or use a specific protocol !{{repository(SVN)}} + or use the checkout protocol of a specific specific project: !{{repository(projectname:SVN)}}" +EOF + + macro :repository do |obj, args| + proto = args.first + if proto.to_s =~ %r{^([^\:]+)\:(.*)$} + project_identifier, proto = $1, $2 + project = Project.find_by_identifier(project_identifier) || Project.find_by_name(project_identifier) + else + project = @project + end + + if project && project.repository + protocols = project.repository.checkout_protocols.select{|p| p.access_rw(User.current)} + + if proto.present? + proto_obj = protocols.find{|p| p.protocol.downcase == proto.downcase} + else + proto_obj = protocols.find(&:default?) || protocols.first + end + end + raise "Checkout protocol #{proto} not found" unless proto_obj + + cmd = (project.repository.checkout_display_command? && proto_obj.command.present?) ? proto_obj.command.strip + " " : "" + cmd + link_to(proto_obj.url, proto_obj.url) + end + end +end \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_checkout/lib/checkout/protocol.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,116 @@ +module Checkout + class <<self + def awesome? + # Yes, this plugin is awesome! + true + end + end + + class Protocol + attr_accessor :protocol, :regex, :regex_replacement, :access, :repository + attr_writer :default, :command, :fixed_url, :append_path + + + def initialize(args={}) + args = args.dup + + @protocol = args.delete :protocol + @command = args.delete :command # optional, if not set the default from the repo is used + + # either a fixed url + @fixed_url = args.delete :fixed_url + + # or a regex + @regex = args.delete :regex + @regex_replacement = args.delete :regex_replacement + + @access = args.delete :access + @append_path = args.delete :append_path + @default = args.delete :is_default + + @repository = args.delete :repository + end + + def full_command(path = "") + cmd = "" + if repository.checkout_display_command? + cmd = self.command.present? ? self.command.strip + " " : "" + end + cmd + URI.escape(self.url(path)) + end + + def default? + @default.to_i > 0 + end + + def command + @command || self.repository && self.repository.checkout_default_command || "" + end + + def append_path? + @append_path.to_i > 0 + end + + def access_rw(user) + # reduces the three available access levels 'read+write', 'read-only' and 'permission' + # to 'read+write' and 'read-only' and nil (not allowed) + + @access_rw ||= {} + return @access_rw[user] if @access_rw.key? user + @access_rw[user] = case access + when 'permission' + case + when user.allowed_to?(:commit_access, repository.project) && user.allowed_to?(:browse_repository, repository.project) + 'read+write' + when user.allowed_to?(:browse_repository, repository.project) + 'read-only' + else + nil + end + else + @access + end + end + + def access_label(user) + case access_rw(user) + when 'read+write' + :label_access_read_write + when 'read-only' + :label_access_read_only + end + end + + def fixed_url + @fixed_url.present? ? @fixed_url : begin + if (regex.blank? || regex_replacement.blank?) + repository.url + else + repository.url.gsub(Regexp.new(regex), regex_replacement) + end + end + rescue RegexpError + repository.url || "" + end + + def url(path = "") + return "" unless repository + + url = fixed_url.sub(/\/+$/, "") + if repository.allow_subtree_checkout? && self.append_path? && path.present? + url = "#{url}/#{path}" + end + + if repository.checkout_display_login? + begin + uri = URI.parse url + (uri.user = repository.login) if repository.login + (uri.password = repository.password) if (repository.checkout_display_login == 'password' && repository.login && repository.password) + url = uri.to_s + rescue URI::InvalidURIError + end + end + url + end + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_checkout/lib/checkout/repositories_helper_patch.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,34 @@ +require_dependency 'repositories_helper' + +module Checkout + module RepositoriesHelperPatch + def self.included(base) # :nodoc: + base.send(:include, InstanceMethods) + + base.class_eval do + alias_method_chain :repository_field_tags, :checkout + alias_method_chain :scm_select_tag, :javascript + end + end + + module InstanceMethods + def repository_field_tags_with_checkout(form, repository) + tags = repository_field_tags_without_checkout(form, repository) || "" + return tags if repository.class.name == "Repository" + + tags + @controller.send(:render_to_string, :partial => 'projects/settings/repository_checkout', :locals => {:form => form, :repository => repository, :scm => repository.scm_name}) + end + + def scm_select_tag_with_javascript(*args) + content_for :header_tags do + javascript_include_tag('subform', :plugin => 'redmine_checkout') + + stylesheet_link_tag('checkout', :plugin => 'redmine_checkout') + end + scm_select_tag_without_javascript(*args) + end + end + end +end + +RepositoriesHelper.send(:include, Checkout::RepositoriesHelperPatch) +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_checkout/lib/checkout/repository_hooks.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,40 @@ +module Checkout + class RepositoryHooks < Redmine::Hook::ViewListener + # Renders the checkout URL + # + # Context: + # * :project => Current project + # * :repository => Current Repository + # + def view_repositories_show_contextual(context={}) + if context[:repository].present? && Setting.checkout_display_checkout_info? + protocols = context[:repository].checkout_protocols.select do |p| + p.access_rw(User.current) + end + + path = context[:controller].instance_variable_get("@path") + if path && context[:controller].instance_variable_get("@entry") + # a single file is showing, so we return only the directory + path = File.dirname(path) + end + + default = protocols.find(&:default?) || protocols.first + + context.merge!({ + :protocols => protocols, + :default_protocol => default, + :checkout_path => path + }) + + options = {:partial => "redmine_checkout_hooks/view_repositories_show_contextual"} + + # cc: cribbed from + # http://www.redmine.org/projects/redmine/repository/revisions/9785/diff/trunk/lib/redmine/hook.rb + # for http://www.redmine.org/issues/11105 (formerly used + # render_to_string on this controller, wasn't working for + # :header_tags) + context[:hook_caller].send(:render, {:locals => context}.merge(options)) + end + end + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_checkout/lib/checkout/repository_patch.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,186 @@ +require_dependency 'repository' +require_dependency 'checkout_helper' + +module Checkout + module RepositoryPatch + def self.included(base) # :nodoc: + base.extend(ClassMethods) + base.send(:include, InstanceMethods) + + base.class_eval do + unloadable + serialize :checkout_settings, Hash + end + end + + module ClassMethods + def allow_subtree_checkout? + # default implementation + false + end + + def checkout_default_command + # default implementation + "" + end + end + + module InstanceMethods + def after_initialize + self.checkout_settings ||= {} + end + + def checkout_overwrite=(value) + checkout_settings['checkout_overwrite'] = value + end + + def checkout_overwrite + (checkout_settings['checkout_overwrite'].to_i > 0) ? '1' : '0' + end + + def checkout_overwrite? + self.scm_name != 'Abstract' && checkout_overwrite.to_i > 0 + end + + def checkout_description=(value) + checkout_settings['checkout_description'] = value + end + + def checkout_description + if checkout_overwrite? + checkout_settings['checkout_description'] + else + if CheckoutHelper.supported_scm.include?(scm_name) && Setting.send("checkout_overwrite_description_#{scm_name}?") + Setting.send("checkout_description_#{scm_name}") + else + Setting.send("checkout_description_Abstract") + end + end + end + + def checkout_protocols + @checkout_protocols ||= begin + if CheckoutHelper.supported_scm.include? scm_name + if checkout_overwrite? + protocols = checkout_settings['checkout_protocols'] || [] + else + protocols = Setting.send("checkout_protocols_#{scm_name}") || [] + end + else + protocols = [] + end + + protocols.collect do |p| + Checkout::Protocol.new p.merge({:repository => self}) + end + end + end + + def checkout_protocols=(value) + # value is an Array or a Hash + if value.is_a? Hash + value = value.dup.delete_if {|id, protocol| id.to_i < 0 } + value = value.sort{|(ak,av),(bk,bv)|ak<=>bk}.collect{|id,protocol| protocol} + end + + checkout_settings['checkout_protocols'] = value + end + + def checkout_display_login + if checkout_overwrite? && self.scm_name == "Subversion" + result = checkout_settings['checkout_display_login'] + else + result = Setting.checkout_display_login + end + (result.to_i > 0) ? '1' : '0' + end + + def checkout_display_login? + checkout_display_login.to_i > 0 + end + + def checkout_display_login=(value) + value = nil unless self.scm_name == "Subversion" + checkout_settings['checkout_display_login'] = value + end + + def checkout_display_command? + checkout_display_command.to_i > 0 + end + + def checkout_display_command=(value) + checkout_settings['checkout_display_command'] = value + end + + def checkout_display_command + if checkout_overwrite? + checkout_settings['checkout_display_command'] + else + Setting.send("checkout_display_command_#{scm_name}") + end + end + + def allow_subtree_checkout? + self.class.allow_subtree_checkout? + end + + def checkout_default_command + self.class.checkout_default_command + end + end + end +end + +Repository.send(:include, Checkout::RepositoryPatch) + +subtree_checkout_repos = ["Subversion", "Cvs"] +commands = { + 'Bazaar' => 'bzr checkout', + 'Cvs' => 'cvs checkout', + 'Darcs' => 'darcs get', + 'Git' => 'git clone', + 'Mercurial' => 'hg clone', + 'Subversion' => 'svn checkout' +} + +CheckoutHelper.supported_scm.each do |scm| + require_dependency "repository/#{scm.underscore}" + cls = Repository.const_get(scm) + + allow_subtree_checkout = "" + if subtree_checkout_repos.include? scm + allow_subtree_checkout = <<-EOS + def allow_subtree_checkout? + true + end + EOS + end + + checkout_command = "" + if commands[scm] + checkout_command = <<-EOS + def checkout_default_command + '#{commands[scm]}' + end + EOS + end + + class_mod = Module.new + class_mod.module_eval(<<-EOF + def self.included(base) + base.extend ChildClassMethods + + base.class_eval do + unloadable + serialize :checkout_settings, Hash + end + end + + module ChildClassMethods + #{allow_subtree_checkout} + #{checkout_command} + end + EOF + ) + cls.send(:include, class_mod) +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_checkout/lib/checkout/setting_patch.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,75 @@ +require_dependency 'setting' + +module Checkout + module SettingPatch + def self.included(base) # :nodoc: + base.extend(ClassMethods) + + base.class_eval do + unloadable + + # Defines getter and setter for each setting + # Then setting values can be read using: Setting.some_setting_name + # or set using Setting.some_setting_name = "some value" + Redmine::Plugin.find(:redmine_checkout).settings[:default].keys.each do |name| + if name.start_with?('protocols_') + default = "[]" + else + default = <<-END_SRC + begin + default = Setting.available_settings['plugin_redmine_checkout']['default']['#{name}'] + # perform a deep copy of the default + Marshal::load(Marshal::dump(default)) + end + END_SRC + end + + src = <<-END_SRC + def self.checkout_#{name} + self.plugin_redmine_checkout[:#{name}] || #{default} + end + + def self.checkout_#{name}? + self.checkout_#{name}.to_i > 0 + end + + def self.checkout_#{name}=(value) + setting = Setting.plugin_redmine_checkout + setting[:#{name}] = value + Setting.plugin_redmine_checkout = setting + end + END_SRC + class_eval src, __FILE__, __LINE__ + end + + class <<self + alias_method :store_without_checkout, :[]= + alias_method :[]=, :store_with_checkout + + alias_method :retrieve_without_checkout, :[] + alias_method :[], :retrieve_with_checkout + end + end + end + + module ClassMethods + def store_with_checkout(name, value) + if name.to_s.starts_with? "checkout_" + self.send("#{name}=", value) + else + store_without_checkout(name, value) + end + end + + def retrieve_with_checkout(name) + if name.to_s.starts_with? "checkout_" + self.send("#{name}") + else + retrieve_without_checkout(name) + end + end + end + end +end + +Setting.send(:include, Checkout::SettingPatch) \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_checkout/lib/checkout/settings_controller_patch.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,44 @@ +require_dependency 'settings_controller' + +module Checkout + module SettingsControllerPatch + def self.included(base) # :nodoc: + base.send(:include, InstanceMethods) + + base.class_eval do + unloadable + + alias_method_chain :edit, :checkout + end + end + + module InstanceMethods + def edit_with_checkout + if request.post? && params['tab'] == 'checkout' + if params[:settings] && params[:settings].is_a?(Hash) + settings = HashWithIndifferentAccess.new + (params[:settings] || {}).each do |name, value| + if name = name.to_s.slice(/checkout_(.+)/, 1) + case value + when Array + # remove blank values in array settings + value.delete_if {|v| v.blank? } + when Hash + # change protocols hash to array. + value = value.sort{|(ak,av),(bk,bv)|ak<=>bk}.collect{|id,protocol| protocol} if name.start_with? "protocols_" + end + settings[name.to_sym] = value + end + end + + Setting.plugin_redmine_checkout = settings + params[:settings] = {} + end + end + edit_without_checkout + end + end + end +end + +SettingsController.send(:include, Checkout::SettingsControllerPatch)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_checkout/lib/checkout/settings_helper_patch.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,23 @@ +require_dependency 'settings_helper' + +module Checkout + module SettingsHelperPatch + def self.included(base) # :nodoc: + base.send(:include, InstanceMethods) + + base.class_eval do + alias_method_chain :administration_settings_tabs, :checkout + end + end + + module InstanceMethods + def administration_settings_tabs_with_checkout + tabs = administration_settings_tabs_without_checkout + tabs << {:name => 'checkout', :partial => 'settings/checkout', :label => :label_checkout} + end + end + end +end + +SettingsHelper.send(:include, Checkout::SettingsHelperPatch) +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_checkout/lib/checkout_helper.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,7 @@ +module CheckoutHelper + class <<self + def supported_scm + Object.const_defined?("REDMINE_SUPPORTED_SCM") ? REDMINE_SUPPORTED_SCM : Redmine::Scm::Base.all + end + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_checkout/lib/tasks/set_default.rake Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,10 @@ +namespace :redmine do + namespace :plugins do + namespace :redmine_checkout do + desc "Sets all repositories to inherit the default setting for the checkout URL." + task :set_default => :environment do + Repository.all.each{|r| r.update_attributes(:checkout_overwrite => "0")} + end + end + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_checkout/lib/tasks/spec.rake Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,14 @@ +begin + require "spec/rake/spectask" + namespace :spec do + namespace :plugins do + desc "Runs the examples for redmine_checkout" + Spec::Rake::SpecTask.new(:redmine_checkout) do |t| + t.spec_opts = ['--options', "\"#{RAILS_ROOT}/spec/spec.opts\""] + t.spec_files = FileList['vendor/plugins/redmine_checkout/spec/**/*_spec.rb'] + end + end + end + task :spec => "spec:plugins:redmine_checkout" +rescue LoadError +end \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_checkout/spec/controllers/repositories_controller_spec.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,48 @@ +require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') + +describe RepositoriesController do + fixtures :settings, :repositories, :projects, :roles, :users, :enabled_modules + integrate_views + + before(:each) do + Setting.default_language = 'en' + User.current = nil + end + + def get_repo + get :show, :id => 1 + end + + it "should display the protocol selector" do + get_repo + response.should be_success + response.should render_template('show') + + response.should have_tag('ul#checkout_protocols') do + with_tag('a[id=?][href=?]', 'checkout_protocol_subversion', "file:///#{RAILS_ROOT.gsub(%r{config\/\.\.}, '')}/tmp/test/subversion_repository") + with_tag('a[id=?][href=?]', 'checkout_protocol_svn+ssh', 'svn+ssh://subversion_repository@svn.foo.bar/svn') + end + end + + it "should display the description" do + get_repo + response.should be_success + response.should render_template('show') + + response.should have_tag('div.repository-info', /Please select the desired protocol below to get the URL/) + end + + it 'should respect the use zero clipboard option' do + Setting.checkout_use_zero_clipboard = '1' + get_repo + response.should be_success + response.should render_template('show') + response.should have_tag('script[src*=?]', 'ZeroClipboard') + + Setting.checkout_use_zero_clipboard = '0' + get_repo + response.should be_success + response.should render_template('show') + response.should_not have_tag('script[src*=]', 'ZeroClipboard') + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_checkout/spec/fixtures/enabled_modules.yml Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,5 @@ +--- +enabled_modules_001: + name: repository + project_id: 1 + id: 1
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_checkout/spec/fixtures/projects.yml Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,13 @@ +--- +projects_001: + created_on: 2006-07-19 19:13:59 +02:00 + name: eCookbook + updated_on: 2006-07-19 22:53:01 +02:00 + id: 1 + description: Recipes management application + homepage: http://ecookbook.somenet.foo/ + is_public: true + identifier: ecookbook + parent_id: + lft: 1 + rgt: 10
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_checkout/spec/fixtures/repositories.yml Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,9 @@ +--- +svn: + project_id: 1 + url: file:///<%= RAILS_ROOT.gsub(%r{config\/\.\.}, '') %>/tmp/test/subversion_repository + id: 1 + root_url: file:///<%= RAILS_ROOT.gsub(%r{config\/\.\.}, '') %>/tmp/test/subversion_repository + password: "" + login: "" + type: Subversion
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_checkout/spec/fixtures/roles.yml Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,187 @@ +--- +roles_001: + name: Manager + id: 1 + builtin: 0 + permissions: | + --- + - :add_project + - :edit_project + - :manage_members + - :manage_versions + - :manage_categories + - :view_issues + - :add_issues + - :edit_issues + - :manage_issue_relations + - :manage_subtasks + - :add_issue_notes + - :move_issues + - :delete_issues + - :view_issue_watchers + - :add_issue_watchers + - :delete_issue_watchers + - :manage_public_queries + - :save_queries + - :view_gantt + - :view_calendar + - :log_time + - :view_time_entries + - :edit_time_entries + - :delete_time_entries + - :manage_news + - :comment_news + - :view_documents + - :manage_documents + - :view_wiki_pages + - :export_wiki_pages + - :view_wiki_edits + - :edit_wiki_pages + - :delete_wiki_pages_attachments + - :protect_wiki_pages + - :delete_wiki_pages + - :rename_wiki_pages + - :add_messages + - :edit_messages + - :delete_messages + - :manage_boards + - :view_files + - :manage_files + - :browse_repository + - :manage_repository + - :view_changesets + - :manage_project_activities + + position: 1 +roles_002: + name: Developer + id: 2 + builtin: 0 + permissions: | + --- + - :edit_project + - :manage_members + - :manage_versions + - :manage_categories + - :view_issues + - :add_issues + - :edit_issues + - :manage_issue_relations + - :manage_subtasks + - :add_issue_notes + - :move_issues + - :delete_issues + - :view_issue_watchers + - :save_queries + - :view_gantt + - :view_calendar + - :log_time + - :view_time_entries + - :edit_own_time_entries + - :manage_news + - :comment_news + - :view_documents + - :manage_documents + - :view_wiki_pages + - :view_wiki_edits + - :edit_wiki_pages + - :protect_wiki_pages + - :delete_wiki_pages + - :add_messages + - :edit_own_messages + - :delete_own_messages + - :manage_boards + - :view_files + - :manage_files + - :browse_repository + - :view_changesets + + position: 2 +roles_003: + name: Reporter + id: 3 + builtin: 0 + permissions: | + --- + - :edit_project + - :manage_members + - :manage_versions + - :manage_categories + - :view_issues + - :add_issues + - :edit_issues + - :manage_issue_relations + - :add_issue_notes + - :move_issues + - :view_issue_watchers + - :save_queries + - :view_gantt + - :view_calendar + - :log_time + - :view_time_entries + - :manage_news + - :comment_news + - :view_documents + - :manage_documents + - :view_wiki_pages + - :view_wiki_edits + - :edit_wiki_pages + - :delete_wiki_pages + - :add_messages + - :manage_boards + - :view_files + - :manage_files + - :browse_repository + - :view_changesets + + position: 3 +roles_004: + name: Non member + id: 4 + builtin: 1 + permissions: | + --- + - :view_issues + - :add_issues + - :edit_issues + - :manage_issue_relations + - :add_issue_notes + - :move_issues + - :save_queries + - :view_gantt + - :view_calendar + - :log_time + - :view_time_entries + - :comment_news + - :view_documents + - :manage_documents + - :view_wiki_pages + - :view_wiki_edits + - :edit_wiki_pages + - :add_messages + - :view_files + - :manage_files + - :browse_repository + - :view_changesets + + position: 4 +roles_005: + name: Anonymous + id: 5 + builtin: 2 + permissions: | + --- + - :view_issues + - :add_issue_notes + - :view_gantt + - :view_calendar + - :view_time_entries + - :view_documents + - :view_wiki_pages + - :view_wiki_edits + - :view_files + - :browse_repository + - :view_changesets + + position: 5 +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_checkout/spec/fixtures/settings.yml Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,115 @@ +--- + settings: + name: plugin_redmine_checkout + value: | + --- !map:HashWithIndifferentAccess + display_checkout_info: "1" + description_Abstract: | + The data contained in this repository can be downloaded to your computer using one of several clients. + Please see the documentation of your version control software client for more information. + + Please select the desired protocol below to get the URL. + display_command_Bazaar: '1' + use_zero_clipboard: "1" + + overwrite_description_Bazaar: "0" + description_Bazaar: "" + display_command_Bazaar: '1' + protocols_Bazaar: + "0": !map:HashWithIndifferentAccess + command: "bzr checkout" + regex: "" + regex_replacement: "" + read_write: readwrite + append_path: "0" + is_default: "1" + protocol: Bazaar + overwrite_description_Cvs: "0" + description_Cvs: "" + display_command_Cvs: '1' + protocols_Cvs: + "0": !map:HashWithIndifferentAccess + command: "cvs checkout" + regex: "" + regex_replacement: "" + read_write: readwrite + append_path: "0" + is_default: "1" + protocol: Cvs + overwrite_description_Darcs: "0" + description_Darcs: "" + display_command_Darcs: '1' + protocols_Darcs: + "0": !map:HashWithIndifferentAccess + command: "darcs get" + regex: "" + regex_replacement: "" + read_write: readwrite + append_path: "0" + is_default: "1" + protocol: Darcs + overwrite_description_Filesystem: "0" + description_Filesystem: "" + display_command_Filesystem: '1' + protocols_Filesystem: !map:HashWithIndifferentAccess + "0": !map:HashWithIndifferentAccess + command: "" + regex: "" + append_path: "0" + is_default: "1" + protocol: Filesystem + access: read+write + regex_replacement: "" + overwrite_description_Git: "0" + description_Git: "" + display_command_Git: '1' + protocols_Git: !map:HashWithIndifferentAccess + "0": !map:HashWithIndifferentAccess + command: "git clone" + regex: "" + append_path: "0" + is_default: "1" + protocol: Git + access: read+write + regex_replacement: "" + overwrite_description_Mercurial: "0" + description_Mercurial: "" + display_command_Mercurial: '1' + protocols_Mercurial: !map:HashWithIndifferentAccess + "0": !map:HashWithIndifferentAccess + command: "hg clone" + regex: "" + append_path: "0" + is_default: "1" + protocol: Mercurial + access: read+write + regex_replacement: "" + display_login: username + overwrite_description_Subversion: "0" + description_Subversion: "" + display_command_Subversion: '1' + protocols_Subversion: !map:HashWithIndifferentAccess + "0": !map:HashWithIndifferentAccess + command: "svn checkout" + regex: foo + append_path: "1" + is_default: "1" + protocol: Subversion + access: permission + regex_replacement: bar + "1": !map:HashWithIndifferentAccess + command: "svn co" + regex: "^.*?([^/]+)/?$" + append_path: "1" + is_default: "0" + protocol: SVN+SSH + access: read-only + regex_replacement: svn+ssh://\1@svn.foo.bar/svn + "2": !map:HashWithIndifferentAccess + command: "svn checkout" + append_path: "0" + is_default: "0" + regex: "" + protocol: Root + access: read+write + regex_replacement: ""
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_checkout/spec/macros/macro_spec.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,55 @@ +require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') + +describe "Macros" do + fixtures :settings, :repositories, :projects, :enabled_modules + + include ERB::Util + include ApplicationHelper + include ActionView::Helpers::TextHelper + include ActionView::Helpers::TagHelper + include ActionView::Helpers::UrlHelper + + before(:each) do + Setting.checkout_display_command_Subversion = '0' + + @project = projects :projects_001 + end + + + it "should display default checkout url" do + text = "{{repository}}" + + url = "file:///#{RAILS_ROOT.gsub(%r{config\/\.\.}, '')}/tmp/test/subversion_repository" + textilizable(text).should eql "<p><a href=\"#{url}\">#{url}</a></p>" + end + + it "should display forced checkout url" do + text = "{{repository(svn+ssh)}}" + + url = 'svn+ssh://subversion_repository@svn.foo.bar/svn' + textilizable(text).should eql "<p><a href=\"#{url}\">#{url}</a></p>" + end + + it "should fail without set project" do + @project = nil + + text = "{{repository(svn+ssh)}}" + textilizable(text).should eql "<p><div class=\"flash error\">Error executing the <strong>repository</strong> macro (Checkout protocol svn+ssh not found)</div></p>" + end + + it "should display checkout url from stated project" do + @project = nil + text = "{{repository(ecookbook:svn+ssh)}}" + + url = 'svn+ssh://subversion_repository@svn.foo.bar/svn' + textilizable(text).should eql "<p><a href=\"#{url}\">#{url}</a></p>" + end + + it "should display command" do + Setting.checkout_display_command_Subversion = '1' + + text = "{{repository(svn+ssh)}}" + url = 'svn+ssh://subversion_repository@svn.foo.bar/svn' + textilizable(text).should eql "<p>svn co <a href=\"#{url}\">#{url}</a></p>" + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_checkout/spec/models/protocol_spec.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,36 @@ +require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') + +describe Checkout::Protocol do + fixtures :settings, :repositories, :projects, :enabled_modules + + before(:each) do + @admin = User.new + @admin.admin = true + @user = User.new + + @repo = repositories :svn + @repo.url = "http://example.com/svn/testrepo" + end + + it "should use regexes for generated URL" do + protocol = @repo.checkout_protocols.find{|r| r.protocol == "SVN+SSH"} + protocol.url.should eql "svn+ssh://testrepo@svn.foo.bar/svn" + end + + it "should resolve access properties" do + protocol = @repo.checkout_protocols.find{|r| r.protocol == "Subversion"} + protocol.access.should eql "permission" + protocol.access_rw(@admin).should eql "read+write" + + User.current = @user + protocol.access_rw(@user).should eql "read-only" + end + + it "should display the checkout command" do + subversion = @repo.checkout_protocols.find{|r| r.protocol == "Subversion"} + svn_ssh = @repo.checkout_protocols.find{|r| r.protocol == "SVN+SSH"} + + subversion.command.should eql "svn checkout" + svn_ssh.command.should eql "svn co" + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_checkout/spec/models/repository_spec.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,49 @@ +require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') + +describe Repository do + fixtures :settings, :repositories + + describe "initialize" do + before(:each) do + @repo = Repository.new() + end + + it "should properly set default values" do + @repo.checkout_overwrite?.should be_false + @repo.checkout_description.should match /Please select the desired protocol below to get the URL/ + @repo.checkout_display_login?.should be_false # no subversion repo + @repo.allow_subtree_checkout?.should be_false + @repo.checkout_protocols.should eql [] + end + end + + describe "subtree checkout" do + before(:each) do + @svn = Repository::Subversion.new + @git = Repository::Git.new + end + it "should be allowed on subversion" do + @svn.allow_subtree_checkout?.should eql true + end + it "should only be possible if checked" do + + end + + it "should be forbidden on git" do + @git.allow_subtree_checkout?.should eql false + end + end + + describe "extensions" do + before(:each) do + @repo = Repository::Subversion.new + end + + it "should provide protocols" do + protocols = @repo.checkout_protocols + protocols[0].protocol.should eql "Subversion" + protocols[1].protocol.should eql "SVN+SSH" + protocols[2].protocol.should eql "Root" + end + end +end \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_checkout/spec/models/setting_spec.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,14 @@ +require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') + +describe Setting do + fixtures :settings + + before(:each) do + Setting.default_language = 'en' + end + + it "should recognize checkout methods" do + Setting.checkout_display_checkout_info.should eql Setting.plugin_redmine_checkout['display_checkout_info'] + Setting.checkout_display_checkout_info.should eql Setting.plugin_redmine_checkout[:display_checkout_info] + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_checkout/spec/sanity_spec.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,11 @@ +require File.dirname(__FILE__) + '/spec_helper' + +describe Class do + it "should be a class of Class" do + Class.class.should eql(Class) + end + + it "should be awesome" do + Checkout.awesome?.should be_true + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_checkout/spec/spec.opts Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,7 @@ +--colour +--format +progress +--loadby +mtime +--reverse +--backtrace \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_checkout/spec/spec_helper.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,27 @@ +ENV['RAILS_ENV'] ||= 'test' + +# prevent case where we are using rubygems and test-unit 2.x is installed +begin + require 'rubygems' + gem "test-unit", "~> 1.2.3" +rescue LoadError +end + +begin + require "config/environment" unless defined? RAILS_ROOT + require RAILS_ROOT + '/spec/spec_helper' +rescue LoadError => error + puts <<-EOS + + You need to install rspec in your Redmine project. + Please execute the following code: + + gem install rspec-rails + script/generate rspec + + EOS + raise error +end + +Fixtures.create_fixtures File.join(File.dirname(__FILE__), "fixtures"), ActiveRecord::Base.connection.tables +require File.join(File.dirname(__FILE__), "..", "init.rb") \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_embedded/COPYING Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + 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. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_embedded/README.md Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,8 @@ +Redmine Embedded +================ + +Plugin for Redmine to embed Doxygen/Javadoc files based on the original by Jean-Philippe Lang but updated for Redmine 2 and Rails 3 + +To use, create a directory for the HTML documentation and set the owner to the same user as the webserver user running your Redmine installation. Default setting is "/var/doc". + +Documentation should be a zip of the root html directory, prefereably compiled without search (CSS needs some work).
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_embedded/app/controllers/redmine_embedded_controller.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,152 @@ +# Redmine - project management software +# Copyright (C) 2008 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 'iconv' + +class DataFile < ActiveRecord::Base + def self.save(directory, zipname, upload) + path = File.join(directory, zipname) + File.open(path, "wb") { |f| f.write(upload['datafile'].read) } + end +end + +class RedmineEmbeddedController < ApplicationController + class RedmineEmbeddedControllerError < StandardError; end + + unloadable + layout 'base' + before_filter :find_project, :authorize + + def index + file = params[:request_path] + path = get_real_path(file) + if File.directory?(path) + file = get_index_file(path) + target = file || [] + #target << file + # Forces redirect to the index file when the requested path is a directory + # so that relative links in embedded html pages work + redirect_to :request_path => target + return + end + + # Check file extension + raise RedmineEmbeddedControllerError.new('This file can not be viewed (invalid extension).') unless Redmine::Plugins::RedmineEmbedded.valid_extension?(path) + + if Redmine::MimeType.is_type?('image', path) + send_file path, :disposition => 'inline', :type => Redmine::MimeType.of(path) + else + embed_file path + end + + rescue Errno::ENOENT => e + @content = "No documentation found" + @title = "" + render :index + rescue Errno::EACCES => e + # Can not read the file + render_error "Unable to read the file: #{e.message}" + rescue RedmineEmbeddedControllerError => e + render_error e.message + end + + def upload + if params[:upload] + file = params[:upload] + zipname = sanitize_filename(params[:upload]['datafile'].original_filename) + if ["zip"].include?(File.extname(zipname).downcase[1..-1]) + dir = get_project_directory.gsub("/html", "") + if File.directory? dir + `rm -rf #{dir}/*` #clean up any exisiting docs + else + Dir.mkdir dir + end + filename = DataFile.save(dir, zipname, params[:upload]) + Dir.chdir(dir) + `unzip #{zipname}` + redirect_to show_embedded_url(@project), :notice => "Documentation uploaded" + else + render :index, :error => "File must be ZIP format" + end + else + render :index, :error => "No file uploaded" + end + end + + private + + def sanitize_filename(file_name) + # get only the filename, not the whole path (from IE) + just_filename = File.basename(file_name) + # replace all none alphanumeric, underscore or perioids + # with underscore + just_filename.sub(/[^\w\.\-]/,'_') + end + + def find_project + @project = Project.find(params[:id]) + rescue ActiveRecord::RecordNotFound + render_404 + end + + # Return the path to the html root directory for the current project + def get_project_directory + @project_directory ||= Setting.plugin_redmine_embedded['path'].to_s.gsub('{PROJECT}', @project.identifier) + end + + # Returns the absolute path of the requested file + # Parameter is an Array + def get_real_path(path) + real = get_project_directory + real = File.join(real, path) unless path.nil? || path.empty? + dir = File.expand_path(get_project_directory) + real = File.expand_path(real) + raise Errno::ENOENT unless real.starts_with?(dir) && File.exist?(real) + real + end + + # Returns the index file in the given directory + # and raises an exception if none is found + def get_index_file(dir) + indexes = Setting.plugin_redmine_embedded['index'].to_s.split + file = indexes.find {|f| File.exist?(File.join(dir, f))} + raise RedmineEmbeddedControllerError.new("No index file found in #{dir} (#{indexes.join(', ')}).") if file.nil? + file + end + + # Renders a given HTML file + def embed_file(path) + @content = File.read(path) + + # Extract html title from embedded page + if @content =~ %r{<title>([^<]*)</title>}mi + @title = $1.strip + end + + # Keep html body only + @content.gsub!(%r{^.*<body[^>]*>(.*)</body>.*$}mi, '\\1') + + # Re-encode content if needed + source_encoding = Setting.plugin_redmine_embedded['encoding'].to_s + unless source_encoding.blank? + begin; @content = Iconv.new('UTF-8', source_encoding).iconv(@content); rescue; end + end + + @doc_template = Redmine::Plugins::RedmineEmbedded.detect_template_from_path(path) + render :action => 'index' + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_embedded/app/helpers/redmine_embedded_helper.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,35 @@ +# Redmine - project management software +# Copyright (C) 2008 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. + +module RedmineEmbeddedHelper + + # Adds include tags for assets of the given template + def asset_include_tags(template) + + Redmine::Plugins::RedmineEmbedded.assets(template).each { |asset| content_for(:header_tags) { asset_include_tag(asset) } } + end + + private + + def asset_include_tag(asset) + if asset =~ %r{\.js$} + javascript_include_tag(asset, :plugin => 'redmine_embedded') + else + stylesheet_link_tag(asset, :plugin => 'redmine_embedded') + end + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_embedded/app/views/redmine_embedded/_upload.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,14 @@ +<div class="row"> + <%= form_tag(upload_embedded_path(@project), :multipart => true) do -%> + <fieldset id="embedded_doc" class="collapsible collapsed"> + <legend onclick="toggleFieldset(this);">Upload documentation</legend> + <div style="display: none;"> + Select File (ZIP format only, root must be html directory): + <%= file_field "upload", "datafile" %> + <%= submit_tag "Upload" %> + </div> + </fieldset> + <% end -%> +</div> +<div class="clearfix"></div> +<hr />
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_embedded/app/views/redmine_embedded/index.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,9 @@ +<%= render :partial => "upload" if User.current.allowed_to?(:edit_embedded_doc, @project) -%> + +<!-- Embedded page --> +<%= @content.html_safe %> +<!-- Embedded page end --> + +<% html_title(@title) if @title %> + +<% asset_include_tags(@doc_template) %>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_embedded/app/views/settings/_redmine_embedded.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,22 @@ +<p><label>HTML directory</label> +<%= text_field_tag 'settings[path]', @settings['path'], :size => 80 %> +<br /><em>Use {PROJECT} to include the project identifier in the path</em></p> + +<p><label>Index files</label> +<%= text_area_tag 'settings[index]', @settings['index'], :cols => 60, :rows => 3 %> +<br /><em>Space separated list of index files by priority</em></p> + +<p><label>Valid extensions</label> +<%= text_area_tag 'settings[extensions]', @settings['extensions'], :cols => 60, :rows => 3 %> +<br /><em>Space separated list of file extensions that can be viewed (case insensitive)</em></p> + +<p><label>Default template</label> +<%= select_tag 'settings[template]', options_for_select([''] + Redmine::Plugins::RedmineEmbedded.available_templates, @settings['template']) %></p> + +<p><label>Files encoding</label> +<%= text_field_tag 'settings[encoding]', @settings['encoding'] %> +<br /><em>Eg. ISO-8859-1<br />Leave this field empty if HTML files are UTF-8 encoded</em></p> + +<p><label>Menu caption</label> +<%= text_field_tag 'settings[menu]', @settings['menu'], :size => 30 %> +<br /><em>Clear this field if you don't want to add a tab to the project menu</em></p>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_embedded/assets/javascripts/rcov.js Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,18 @@ +function toggleCode( id ) { + if ( document.getElementById ) + elem = document.getElementById( id ); + else if ( document.all ) + elem = eval( "document.all." + id ); + else + return false; + + elemStyle = elem.style; + + if ( elemStyle.display != "block" ) { + elemStyle.display = "block" + } else { + elemStyle.display = "none" + } + + return true; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_embedded/assets/stylesheets/doxygen.css Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,224 @@ +#content { + font-family: Geneva, Arial, Helvetica, sans-serif; +} +#content h1, h2, h3 {border: 0;} +#content h1 {margin-bottom: 1em;} +#content CAPTION { font-weight: bold } +#content DIV.qindex { + line-height: 120%; + margin-bottom: 1em; +} +#content A.qindex { + text-decoration: none; + font-weight: bold; + padding: 2px; +} +#content A.qindexHL { + text-decoration: underline; + font-weight: bold; + padding: 2px; +} +#content A.el { text-decoration: none; font-weight: bold } +#content A.elRef { font-weight: bold } +#content A.code { text-decoration: none; font-weight: normal; color: #1A419D} +#content A.codeRef { font-weight: normal; color: #1A419D} +#content DL.el { margin-left: -1cm } +#content PRE.fragment { + border: 1px solid #CCCCCC; + background-color: #f5f5f5; + margin-top: 4px; + margin-bottom: 4px; + margin-left: 2px; + margin-right: 8px; + padding-left: 6px; + padding-right: 6px; + padding-top: 4px; + padding-bottom: 4px; +} +#content DIV.fragment { + border: 1px solid #CCCCCC; + background-color: #f5f5f5; + padding: 6px; +} +#content DIV.ah { background-color: black; font-weight: bold; color: #ffffff; margin-bottom: 3px; margin-top: 3px } +#content TD.md { background-color: #F4F4FB; font-weight: bold; } +#content TD.mdname1 { background-color: #F4F4FB; font-weight: bold; color: #602020; } +#content TD.mdname { background-color: #F4F4FB; font-weight: bold; color: #602020; width: 600px; } +#content DIV.groupHeader { + margin-left: 16px; + margin-top: 12px; + margin-bottom: 6px; + font-weight: bold; + font-family: Geneva, Arial, Helvetica, sans-serif; +} +#content DIV.groupText { margin-left: 16px; font-style: italic; font-size: 14px } +#content TD.indexkey { + background-color: #eeeeff; + font-weight: bold; + padding-right : 10px; + padding-top : 2px; + padding-left : 10px; + padding-bottom : 2px; + margin-left : 0px; + margin-right : 0px; + margin-top : 2px; + margin-bottom : 2px; + border: 1px solid #CCCCCC; +} +#content TD.indexvalue { + background-color: #eeeeff; + font-style: italic; + padding-right : 10px; + padding-top : 2px; + padding-left : 10px; + padding-bottom : 2px; + margin-left : 0px; + margin-right : 0px; + margin-top : 2px; + margin-bottom : 2px; + border: 1px solid #CCCCCC; +} +#content TR.memlist { + background-color: #f0f0f0; +} +#content P.formulaDsp { text-align: center; } +#content IMG.formulaDsp { } +#content IMG.formulaInl { vertical-align: middle; } +#content SPAN.keyword { color: #008000 } +#content SPAN.keywordtype { color: #604020 } +#content SPAN.keywordflow { color: #e08000 } +#content SPAN.comment { color: #800000 } +#content SPAN.preprocessor { color: #806020 } +#content SPAN.stringliteral { color: #002080 } +#content SPAN.charliteral { color: #008080 } +#content .mdTable { + border: 1px solid #868686; + background-color: #F4F4FB; +} +#content .mdRow { + padding: 8px 10px; +} +#content .mdescLeft { + padding: 0px 8px 4px 8px; + font-size: 14px; + font-style: italic; + background-color: #FAFAFA; + border-top: 1px none #E0E0E0; + border-right: 1px none #E0E0E0; + border-bottom: 1px none #E0E0E0; + border-left: 1px none #E0E0E0; + margin: 0px; +} +#content .mdescRight { + padding: 0px 8px 4px 8px; + font-size: 14px; + font-style: italic; + background-color: #FAFAFA; + border-top: 1px none #E0E0E0; + border-right: 1px none #E0E0E0; + border-bottom: 1px none #E0E0E0; + border-left: 1px none #E0E0E0; + margin: 0px; +} +#content .memItemLeft { + padding: 1px 0px 0px 8px; + margin: 4px; + border-top-width: 1px; + border-right-width: 1px; + border-bottom-width: 1px; + border-left-width: 1px; + border-top-style: solid; + border-top-color: #E0E0E0; + border-right-color: #E0E0E0; + border-bottom-color: #E0E0E0; + border-left-color: #E0E0E0; + border-right-style: none; + border-bottom-style: none; + border-left-style: none; + background-color: #FAFAFA; + font-family: Geneva, Arial, Helvetica, sans-serif; + font-size: 12px; +} +#content .memItemRight { + padding: 1px 8px 0px 8px; + margin: 4px; + border-top-width: 1px; + border-right-width: 1px; + border-bottom-width: 1px; + border-left-width: 1px; + border-top-style: solid; + border-top-color: #E0E0E0; + border-right-color: #E0E0E0; + border-bottom-color: #E0E0E0; + border-left-color: #E0E0E0; + border-right-style: none; + border-bottom-style: none; + border-left-style: none; + background-color: #FAFAFA; + font-family: Geneva, Arial, Helvetica, sans-serif; + font-size: 13px; +} +#content .search { color: #003399; font-weight: bold; +} +#content FORM.search { + margin-bottom: 0px; + margin-top: 0px; +} +#content INPUT.search { font-size: 75%; + color: #000080; + font-weight: normal; + background-color: #eeeeff; +} +#content TD.tiny { font-size: 75%; +} +#content .tabs ul li a#MSearchClose { + width: 12px; + display: inline-block; + overflow: hidden; + padding: 2px; + height: 12px; + line-height: 12px; +} + +#content .tabs ul li #MSearchField { + width: 120px; + margin: 0; +} + +#content .tabs ul li #MSearchBox { + margin-bottom: -2px; +} + +#content .memitem { + padding: 0; + margin-bottom: 10px; +} +#content .memname { + white-space: nowrap; + font-weight: bold; +} +#content .memproto, .memdoc { + border: 1px solid #84b0c7; +} +#content .memdoc { + padding: 2px 5px; + background-color: #eef3f5; + border-top-width: 0; + -webkit-border-bottom-left-radius: 8px; + -webkit-border-bottom-right-radius: 8px; + -webkit-box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.15); + -moz-border-radius-bottomleft: 8px; + -moz-border-radius-bottomright: 8px; + -moz-box-shadow: rgba(0, 0, 0, 0.15) 5px 5px 5px; +} +#content .memproto { + padding: 0; + background-color: #d5e1e8; + font-weight: bold; + -webkit-border-top-left-radius: 8px; + -webkit-border-top-right-radius: 8px; + -webkit-box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.15); + -moz-border-radius-topleft: 8px; + -moz-border-radius-topright: 8px; + -moz-box-shadow: rgba(0, 0, 0, 0.15) 5px 5px 5px; +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_embedded/assets/stylesheets/javadoc.css Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,23 @@ +/* Javadoc style sheet */ + +/* Table colors */ +.TableHeadingColor { background: #eef; } /* Dark mauve */ +.TableHeadingColor th b { font-size: 70%; } +.TableSubHeadingColor { background: #EEEEee; } /* Light mauve */ +.TableRowColor { background: #FFFFFF; } /* White */ + +/* Font used in left-hand frame lists */ +.FrameTitleFont { font-size: 100%; font-family: Helvetica, Arial, sans-serif; color:#000000 } +.FrameHeadingFont { font-size: 90%; font-family: Helvetica, Arial, sans-serif; color:#000000 } +.FrameItemFont { font-size: 90%; font-family: Helvetica, Arial, sans-serif; color:#000000 } + +/* Navigation bar fonts and colors */ +.NavBarCell1 { background-color: inherit; } /* Light mauve */ +.NavBarCell1Rev { background-color: inherit; } /* Dark Blue */ +.NavBarFont1 { font-family: Arial, Helvetica, sans-serif;} +.NavBarFont1Rev { font-family: Arial, Helvetica, sans-serif; font-style: underline; } + +.NavBarCell2 { font-family: Arial, Helvetica, sans-serif; background-color:#FFFFFF; color:#000000; display: none;} +.NavBarCell3 { font-family: Arial, Helvetica, sans-serif; background-color:#FFFFFF; color:#000000} + +#content table { border-collapse: collapse; border-color: #ddd; }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_embedded/assets/stylesheets/rcov.css Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,525 @@ +span.cross-ref-title { + font-size: 140%; +} +span.cross-ref a { + text-decoration: none; +} +span.cross-ref { + background-color:#f3f7fa; + border: 1px dashed #333; + margin: 1em; + padding: 0.5em; + overflow: hidden; +} +a.crossref-toggle { + text-decoration: none; +} +span.marked0 { + background-color: rgb(185, 210, 200); + display: block; +} +span.marked1 { + background-color: rgb(190, 215, 205); + display: block; +} +span.inferred0 { + background-color: rgb(175, 200, 200); + display: block; +} +span.inferred1 { + background-color: rgb(180, 205, 205); + display: block; +} +span.uncovered0 { + background-color: rgb(225, 110, 110); + display: block; +} +span.uncovered1 { + background-color: rgb(235, 120, 120); + display: block; +} +span.overview { + border-bottom: 8px solid black; +} +div.overview { + border-bottom: 8px solid black; +} +#content div.footer { + font-size: 68%; + margin-top: 1.5em; +} +#content h1, h2, h3, h4, h5, h6 { + margin-bottom: 0.5em; +} +h5 { + margin-top: 0.5em; +} +.hidden { + display: none; +} +div.separator { + height: 10px; +} +/* Commented out for better readability, esp. on IE */ +/* +table tr td, table tr th { + font-size: 68%; +} +td.value table tr td { + font-size: 11px; +} +*/ +table.percent_graph { + height: 12px; + border: #808080 1px solid; + empty-cells: show; +} +table.percent_graph td.covered { + height: 10px; + background: #00f000; +} +table.percent_graph td.uncovered { + height: 10px; + background: #e00000; +} +table.percent_graph td.NA { + height: 10px; + background: #eaeaea; +} +table.report { + border-collapse: collapse; + width: 100%; +} +table.report td.heading { + background: #dcecff; + border: #d0d0d0 1px solid; + font-weight: bold; + text-align: center; +} +table.report td.heading:hover { + background: #c0ffc0; +} +table.report td.text { + border: #d0d0d0 1px solid; +} +table.report td.value, +table.report td.lines_total, +table.report td.lines_code { + text-align: right; + border: #d0d0d0 1px solid; +} +table.report tr.light { + background-color: #f6f7f8; +} +table.report tr.dark { + background-color: #fff; +} +span.run0 { + background-color: rgb(178, 204, 255); + display: block; +} +span.run1 { + background-color: rgb(178, 206, 255); + display: block; +} +span.run2 { + background-color: rgb(178, 209, 255); + display: block; +} +span.run3 { + background-color: rgb(178, 211, 255); + display: block; +} +span.run4 { + background-color: rgb(178, 214, 255); + display: block; +} +span.run5 { + background-color: rgb(178, 218, 255); + display: block; +} +span.run6 { + background-color: rgb(178, 220, 255); + display: block; +} +span.run7 { + background-color: rgb(178, 223, 255); + display: block; +} +span.run8 { + background-color: rgb(178, 225, 255); + display: block; +} +span.run9 { + background-color: rgb(178, 228, 255); + display: block; +} +span.run10 { + background-color: rgb(178, 232, 255); + display: block; +} +span.run11 { + background-color: rgb(178, 234, 255); + display: block; +} +span.run12 { + background-color: rgb(178, 237, 255); + display: block; +} +span.run13 { + background-color: rgb(178, 239, 255); + display: block; +} +span.run14 { + background-color: rgb(178, 242, 255); + display: block; +} +span.run15 { + background-color: rgb(178, 246, 255); + display: block; +} +span.run16 { + background-color: rgb(178, 248, 255); + display: block; +} +span.run17 { + background-color: rgb(178, 251, 255); + display: block; +} +span.run18 { + background-color: rgb(178, 253, 255); + display: block; +} +span.run19 { + background-color: rgb(178, 255, 253); + display: block; +} +span.run20 { + background-color: rgb(178, 255, 249); + display: block; +} +span.run21 { + background-color: rgb(178, 255, 247); + display: block; +} +span.run22 { + background-color: rgb(178, 255, 244); + display: block; +} +span.run23 { + background-color: rgb(178, 255, 242); + display: block; +} +span.run24 { + background-color: rgb(178, 255, 239); + display: block; +} +span.run25 { + background-color: rgb(178, 255, 235); + display: block; +} +span.run26 { + background-color: rgb(178, 255, 233); + display: block; +} +span.run27 { + background-color: rgb(178, 255, 230); + display: block; +} +span.run28 { + background-color: rgb(178, 255, 228); + display: block; +} +span.run29 { + background-color: rgb(178, 255, 225); + display: block; +} +span.run30 { + background-color: rgb(178, 255, 221); + display: block; +} +span.run31 { + background-color: rgb(178, 255, 219); + display: block; +} +span.run32 { + background-color: rgb(178, 255, 216); + display: block; +} +span.run33 { + background-color: rgb(178, 255, 214); + display: block; +} +span.run34 { + background-color: rgb(178, 255, 211); + display: block; +} +span.run35 { + background-color: rgb(178, 255, 207); + display: block; +} +span.run36 { + background-color: rgb(178, 255, 205); + display: block; +} +span.run37 { + background-color: rgb(178, 255, 202); + display: block; +} +span.run38 { + background-color: rgb(178, 255, 200); + display: block; +} +span.run39 { + background-color: rgb(178, 255, 197); + display: block; +} +span.run40 { + background-color: rgb(178, 255, 193); + display: block; +} +span.run41 { + background-color: rgb(178, 255, 191); + display: block; +} +span.run42 { + background-color: rgb(178, 255, 188); + display: block; +} +span.run43 { + background-color: rgb(178, 255, 186); + display: block; +} +span.run44 { + background-color: rgb(178, 255, 183); + display: block; +} +span.run45 { + background-color: rgb(178, 255, 179); + display: block; +} +span.run46 { + background-color: rgb(179, 255, 178); + display: block; +} +span.run47 { + background-color: rgb(182, 255, 178); + display: block; +} +span.run48 { + background-color: rgb(184, 255, 178); + display: block; +} +span.run49 { + background-color: rgb(187, 255, 178); + display: block; +} +span.run50 { + background-color: rgb(191, 255, 178); + display: block; +} +span.run51 { + background-color: rgb(193, 255, 178); + display: block; +} +span.run52 { + background-color: rgb(196, 255, 178); + display: block; +} +span.run53 { + background-color: rgb(198, 255, 178); + display: block; +} +span.run54 { + background-color: rgb(201, 255, 178); + display: block; +} +span.run55 { + background-color: rgb(205, 255, 178); + display: block; +} +span.run56 { + background-color: rgb(207, 255, 178); + display: block; +} +span.run57 { + background-color: rgb(210, 255, 178); + display: block; +} +span.run58 { + background-color: rgb(212, 255, 178); + display: block; +} +span.run59 { + background-color: rgb(215, 255, 178); + display: block; +} +span.run60 { + background-color: rgb(219, 255, 178); + display: block; +} +span.run61 { + background-color: rgb(221, 255, 178); + display: block; +} +span.run62 { + background-color: rgb(224, 255, 178); + display: block; +} +span.run63 { + background-color: rgb(226, 255, 178); + display: block; +} +span.run64 { + background-color: rgb(229, 255, 178); + display: block; +} +span.run65 { + background-color: rgb(233, 255, 178); + display: block; +} +span.run66 { + background-color: rgb(235, 255, 178); + display: block; +} +span.run67 { + background-color: rgb(238, 255, 178); + display: block; +} +span.run68 { + background-color: rgb(240, 255, 178); + display: block; +} +span.run69 { + background-color: rgb(243, 255, 178); + display: block; +} +span.run70 { + background-color: rgb(247, 255, 178); + display: block; +} +span.run71 { + background-color: rgb(249, 255, 178); + display: block; +} +span.run72 { + background-color: rgb(252, 255, 178); + display: block; +} +span.run73 { + background-color: rgb(255, 255, 178); + display: block; +} +span.run74 { + background-color: rgb(255, 252, 178); + display: block; +} +span.run75 { + background-color: rgb(255, 248, 178); + display: block; +} +span.run76 { + background-color: rgb(255, 246, 178); + display: block; +} +span.run77 { + background-color: rgb(255, 243, 178); + display: block; +} +span.run78 { + background-color: rgb(255, 240, 178); + display: block; +} +span.run79 { + background-color: rgb(255, 238, 178); + display: block; +} +span.run80 { + background-color: rgb(255, 234, 178); + display: block; +} +span.run81 { + background-color: rgb(255, 232, 178); + display: block; +} +span.run82 { + background-color: rgb(255, 229, 178); + display: block; +} +span.run83 { + background-color: rgb(255, 226, 178); + display: block; +} +span.run84 { + background-color: rgb(255, 224, 178); + display: block; +} +span.run85 { + background-color: rgb(255, 220, 178); + display: block; +} +span.run86 { + background-color: rgb(255, 218, 178); + display: block; +} +span.run87 { + background-color: rgb(255, 215, 178); + display: block; +} +span.run88 { + background-color: rgb(255, 212, 178); + display: block; +} +span.run89 { + background-color: rgb(255, 210, 178); + display: block; +} +span.run90 { + background-color: rgb(255, 206, 178); + display: block; +} +span.run91 { + background-color: rgb(255, 204, 178); + display: block; +} +span.run92 { + background-color: rgb(255, 201, 178); + display: block; +} +span.run93 { + background-color: rgb(255, 198, 178); + display: block; +} +span.run94 { + background-color: rgb(255, 196, 178); + display: block; +} +span.run95 { + background-color: rgb(255, 192, 178); + display: block; +} +span.run96 { + background-color: rgb(255, 189, 178); + display: block; +} +span.run97 { + background-color: rgb(255, 187, 178); + display: block; +} +span.run98 { + background-color: rgb(255, 184, 178); + display: block; +} +span.run99 { + background-color: rgb(255, 182, 178); + display: block; +} +span.run100 { + background-color: rgb(255, 178, 178); + display: block; +} +pre { + white-space: pre-wrap; /* CSS2.1 compliant */ + white-space: -moz-pre-wrap; /* Mozilla-based browsers */ + white-space: -o-pre-wrap; /* Opera 7+ */ +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_embedded/config/routes.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,4 @@ +RedmineApp::Application.routes.draw do + post "/projects/:id/embedded/upload", :to => "redmine_embedded#upload", :as => :upload_embedded + get 'projects/:id/embedded(/*request_path(.:format))', :to => "redmine_embedded#index", :as => :show_embedded +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_embedded/init.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,28 @@ +require 'redmine' +require 'redmine_embedded' + +Redmine::Plugin.register :redmine_embedded do + name 'Redmine Embedded Documentation' + author 'Jean-Philippe Lang, Reuben Mallaby' + description 'Embed various documentations in your projects' + version '0.0.2' + settings :partial => 'settings/redmine_embedded', + :default => { 'path' => '/var/doc/{PROJECT}/html', + 'index' => 'main.html overview-summary.html index.html', + 'extensions' => 'html png gif', + 'template' => '', + 'encoding' => '', + 'menu' => 'Embedded' } + project_module :redmine_embedded do + permission :view_embedded_doc, {:redmine_embedded => :index} + permission :edit_embedded_doc, {:redmine_embedded => :upload} + end + menu :project_menu, :redmine_embedded, + { + :controller => "redmine_embedded", + :action => "index" + }, + :caption => Proc.new { Setting.plugin_redmine_embedded['menu'] }, + :if => Proc.new { !Setting.plugin_redmine_embedded['menu'].blank? } +end +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_embedded/lib/redmine_embedded.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,69 @@ +# Redmine - project management software +# Copyright (C) 2008 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. + +module Redmine + module Plugins + module RedmineEmbedded + class << self + + # Returns an Array of available templates + def available_templates + assets_by_template.keys.sort + end + + # Returns the assets for a given template + def assets(template) + assets_by_template.has_key?(template) ? assets_by_template[template] : [] + end + + def detect_template_from_path(path) + t = path.to_s.split(%r{[/\\]}) & available_templates + t.empty? ? Setting.plugin_redmine_embedded['template'].to_s : t.first + end + + def valid_extension?(path) + extensions = Setting.plugin_redmine_embedded['extensions'].to_s.split.each(&:downcase) + extensions.include?(File.extname(path).downcase[1..-1]) + end + + private + + # A Hash of available assets by template + def assets_by_template + @@assets_by_template ||= scan_assets + end + + # Scans assets directory for templates + # and returns a Hash of available assets by template + def scan_assets + a = Hash.new {|h,k| h[k] = [] } + Dir.glob(File.join(File.dirname(__FILE__), '../assets/*/*.{css,js}')).each do |asset| + asset = File.basename(asset) + template = asset.gsub(%r{\.(js|css)$}, '') + a[template] << asset + end + a + end + end + end + end +end + +class << RedmineApp::Application;self;end.class_eval do + define_method :clear!, lambda {} +end +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_tags/COPYING Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,17 @@ +redmine_tags is a redMine plugin, that adds tagging support. + +Copyright (c) 2010 Eric Davis +Copyright (c) 2010 Aleksey V Zapparov AKA ixti + +redmine_tags 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 3 of the License, or +(at your option) any later version. + +redmine_tags 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 redmine_tags. If not, see <http://www.gnu.org/licenses/>.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_tags/Gemfile Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,1 @@ +gem "acts-as-taggable-on", "2.3.3"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_tags/HISTORY.md Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,4 @@ +## 2.0.1 (unreleased) + +- Added Simplified Chinese translation. Thanks to archonwang. +- Added Bulgarian translation. Thanks to Ivan Cenov.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_tags/LICENSE Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,619 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_tags/README.md Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,41 @@ +Redmine Tags +============ + +Allows marking up different models in Redmine with tags. +Inspired by original redmine\_tags of Eric Davis. + + +Supported models +---------------- + +- Issues + + +Requirements +------------ + +- Redmine `>= 2.1.0` +- acts-as-taggable-on `= 2.3.3` + + +Installation +------------ + +- Clone this repository into `redmine/plugins/redmine_tags` +- Install dependencies and migrate database: + + cd redmine/ + bundle install + RAILS_ENV=production rails generate acts_as_taggable_on:migration + RAILS_ENV=production rake db:migrate + RAILS_ENV=production rake redmine:plugins:migrate + +- Restart your Redmine web server (e.g. mongrel, thin, mod\_rails) + + +License +------- + +This plugin is licensed under the terms of GNU/GPL v3+. +See COPYING and LICENSE for details. +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_tags/README.rdoc Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,15 @@ += Redmine Tags + +Allows marking up different models in Redmine with tags. +Inspired by original redmine_tags of Eric Davis. But in +comparison extended with some code that was already wrote +as part of my own redmine taggable_issues branch. + +== Supported models + +* Issues + +== License + +This plugin is licensed under the GNU/GPL v3. +See COPYING and LICENSE for details.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_tags/app/helpers/filters_helper.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,62 @@ +# This file is a part of redmine_tags +# redMine plugin, that adds tagging support. +# +# Copyright (c) 2010 Aleksey V Zapparov AKA ixti +# +# redmine_tags 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 3 of the License, or +# (at your option) any later version. +# +# redmine_tags 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 redmine_tags. If not, see <http://www.gnu.org/licenses/>. + +module FiltersHelper + # returns link to the page with issues filtered by specified filters + # === parameters + # * <i>title</i> = link title text + # * <i>filters</i> = filters to be applied (see <tt>link_to_filter_options</tt> for details) + # * <i>options</i> = (optional) base options of the link + # === example + # link_to_filter 'foobar', [[ :tags, '~', 'foobar' ]] + # link_to_filter 'foobar', [[ :tags, '~', 'foobar' ]], :project_id => project + def link_to_filter(title, filters, options = {}) + options.merge! link_to_filter_options(filters) + link_to title, options + end + + + # returns hash suitable for passing it to the <tt>to_link</tt> + # === parameters + # * <i>filters</i> = array of arrays. each child array is an array of strings: + # name, operator and value + # === example + # link_to 'foobar', link_to_filter_options [[ :tags, '~', 'foobar' ]] + # + # filters = [[ :tags, '~', 'bazbaz' ], [:status_id, 'o']] + # link_to 'bazbaz', link_to_filter_options filters + def link_to_filter_options(filters) + options = { + :controller => 'issues', + :action => 'index', + :set_filter => 1, + :fields => [], + :values => {}, + :operators => {} + } + + filters.each do |f| + name, operator, value = f + options[:fields].push(name) + options[:operators][name] = operator + options[:values][name] = [value] + end + + options + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_tags/app/helpers/issues_helper.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,42 @@ +# This file is a part of redmine_tags +# redMine plugin, that adds tagging support. +# +# Copyright (c) 2010 Aleksey V Zapparov AKA ixti +# +# redmine_tags 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 3 of the License, or +# (at your option) any later version. +# +# redmine_tags 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 redmine_tags. If not, see <http://www.gnu.org/licenses/>. + +module IssuesHelper + include TagsHelper + + def sidebar_tags + unless @sidebar_tags + @sidebar_tags = [] + if :none != RedmineTags.settings[:issues_sidebar].to_sym + @sidebar_tags = Issue.available_tags({ + :project => @project, + :open_only => (RedmineTags.settings[:issues_open_only].to_i == 1) + }) + end + end + @sidebar_tags + end + + def render_sidebar_tags + render_tags_list(sidebar_tags, { + :show_count => (RedmineTags.settings[:issues_show_count].to_i == 1), + :open_only => (RedmineTags.settings[:issues_open_only].to_i == 1), + :style => RedmineTags.settings[:issues_sidebar].to_sym + }) + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_tags/app/helpers/tags_helper.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,113 @@ +# This file is a part of redmine_tags +# redMine plugin, that adds tagging support. +# +# Copyright (c) 2010 Aleksey V Zapparov AKA ixti +# +# redmine_tags 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 3 of the License, or +# (at your option) any later version. +# +# redmine_tags 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 redmine_tags. If not, see <http://www.gnu.org/licenses/>. + +module TagsHelper + include ActsAsTaggableOn::TagsHelper + include FiltersHelper + + + # Returns tag link + # === Parameters + # * <i>tag</i> = Instance of Tag + # * <i>options</i> = (optional) Options (override system settings) + # * show_count - Boolean. Whenever show tag counts + # * open_only - Boolean. Whenever link to the filter with "open" issues + # only limit. + def render_tag_link(tag, options = {}) + filters = [[:tags, '=', tag.name]] + filters << [:status_id, 'o'] if options[:open_only] + + content = link_to_filter tag.name, filters, :project_id => @project + if options[:show_count] + content << content_tag('span', "(#{tag.count})", :class => 'tag-count') + end + + content_tag('span', content, :class => 'tag-label') + end + + def render_project_tag_link(tag, options = {}) + content = link_to tag.name, :controller => :projects, :action => :index, :tag_search => tag.name + + if options[:show_count] + content << content_tag('span', "(#{tag.count})", :class => 'tag-count') + end + content_tag('span', content, :class => 'tag-label') + end + + + # Renders list of tags + # Clouds are rendered as block <tt>div</tt> with internal <tt>span</t> per tag. + # Lists are rendered as unordered lists <tt>ul</tt>. Lists are ordered by + # <tt>tag.count</tt> descending. + # === Parameters + # * <i>tags</i> = Array of Tag instances + # * <i>options</i> = (optional) Options (override system settings) + # * show_count - Boolean. Whenever show tag counts + # * open_only - Boolean. Whenever link to the filter with "open" issues + # only limit. + # * style - list, cloud + def render_tags_list(tags, options = {}) + unless tags.nil? or tags.empty? + content, style = '', options.delete(:style) + + # prevent ActsAsTaggableOn::TagsHelper from calling `all` + # otherwise we will need sort tags after `tag_cloud` + tags = tags.all if tags.respond_to?(:all) + + case sorting = "#{RedmineTags.settings[:issues_sort_by]}:#{RedmineTags.settings[:issues_sort_order]}" + when "name:asc"; tags.sort! { |a,b| a.name <=> b.name } + when "name:desc"; tags.sort! { |a,b| b.name <=> a.name } + when "count:asc"; tags.sort! { |a,b| a.count <=> b.count } + when "count:desc"; tags.sort! { |a,b| b.count <=> a.count } + # Unknown sorting option. Fallback to default one + else + logger.warn "[redmine_tags] Unknown sorting option: <#{sorting}>" + tags.sort! { |a,b| a.name <=> b.name } + end + + if :list == style + list_el, item_el = 'ul', 'li' + elsif :cloud == style + list_el, item_el = 'div', 'span' + tags = cloudify(tags) + else + raise "Unknown list style" + end + + content = content.html_safe + tag_cloud tags, (1..8).to_a do |tag, weight| + content << " ".html_safe + content_tag(item_el, render_project_tag_link(tag, options), :class => "tag-nube-#{weight}") + " ".html_safe + end + + content_tag(list_el, content, :class => 'tags') + end + end + + private + + # make snowball. first tags comes in th middle. + def cloudify(tags) + temp, tags, trigger = tags, [], true + temp.each do |tag| + tags.send((trigger ? 'push' : 'unshift'), tag) + trigger = !trigger + end + tags + end + +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_tags/app/views/auto_completes/_search_tag_list.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,5 @@ +<ul> + <% @tags.each do |tag| -%> + <%= content_tag 'li', h('%s (%d)' % [tag.name, tag.count]), :name => tag.name %> + <% end -%> +</ul>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_tags/app/views/auto_completes/_tag_list.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,4 @@ +<%= raw @tags.collect {|tag| + tag.name + }.to_json +%>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_tags/app/views/auto_completes/_tag_list.json.erb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,4 @@ +<%= raw @tags.collect {|tag| + tag.name + }.to_json +%>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_tags/app/views/issues/_bulk_tags_form.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,1 @@ +<%= render :partial => "issues/tags_form", :locals => {:issue => Issue.new({:project => issues.first.project})} %>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_tags/app/views/issues/_tags.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,6 @@ +<% unless issue.tag_list.empty? %> + <tr> + <td><b><%=l(:tags)%>:</b></td> + <td><%= safe_join(issue.tag_counts.collect{ |t| render_tag_link(t, :show_count => false, :open_only => false) }, ', ') %></td> + </tr> +<% end %>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_tags/app/views/issues/_tags_form.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,29 @@ +<div> + <p id="issue_tags"> + <% issue = Issue.new({:project => issues.first.project}) if defined? issues %> + <% text_field_options = {:label => :tags, :size => 60, :class => 'hol'} %> + <% if defined? form %> + <%= form.text_field :tag_list, text_field_options %> + <% else %> + <%= label_tag :tags, nil, :for => :issue_tag_list %> + <%= text_field_tag 'issue[tag_list]', "", :class => text_field_options[:class], :size => text_field_options[:size], :id => :issue_tag_list %> + <% end %> + </p> + <div id="issue_tag_candidates" class="autocomplete"></div> + <%= stylesheet_link_tag 'jquery.tagit.css', :plugin => 'redmine_tags' %> + <%= stylesheet_link_tag 'redmine_tags', :plugin => 'redmine_tags' %> + <%= javascript_include_tag 'tag-it', :plugin => 'redmine_tags' %> + <%= javascript_tag "$('#issue_tag_list').tagit({ + tagSource: function(search, showChoices) { + var that = this; + $.ajax({ + url: '#{url_for(:controller => 'auto_completes', :action => 'issue_tags', :project_id => issue.project.id)}', + data: {q: search.term}, + success: function(choices) { + showChoices(that._subtractArray(jQuery.parseJSON(choices), that.assignedTags())); + } + }); + }, + }); +" %> +</div>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_tags/app/views/issues/_tags_sidebar.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,6 @@ +<% unless sidebar_tags.empty? -%> + <%= stylesheet_link_tag 'jquery.tagit.css', :plugin => 'redmine_tags' %> + <%= stylesheet_link_tag 'redmine_tags', :plugin => 'redmine_tags' %> + <h3><%= l(:tags) %></h3> + <%= render_sidebar_tags %> +<% end -%>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_tags/app/views/projects/_filter_search_tags.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,34 @@ +<div> + <p id="project_tags"> + <%= label_tag 'tag_search', l(:tags_search).html_safe -%> + <%= text_field_tag 'tag_search', params[:tag_search] -%> + <br /> + </p> + + <div id="project_tag_candidates" class="autocomplete" style="margin-top: 0;"></div> + + <%= stylesheet_link_tag 'jquery.tagit.css', :plugin => 'redmine_tags' %> + <%= stylesheet_link_tag 'redmine_tags', :plugin => 'redmine_tags' %> + <%= javascript_include_tag 'tag-it', :plugin => 'redmine_tags' %> + + <%= javascript_tag "$('#tag_search').tagit({ + tagSource: function(search, showChoices) { + var that = this; + $.ajax({ + url: '#{url_for(:controller => 'auto_completes', :action => 'project_tags')}', + data: {q: search.term}, + success: function(choices) { + showChoices(that._subtractArray(jQuery.parseJSON(choices), that.assignedTags())); + } + }); + }, + }); +" -%> + +</div> + + + + + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_tags/app/views/projects/_filter_tags.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,13 @@ +<p class='tag'> + <%= labelled_fields_for @project do |f| -%> + <div> + <p id="project_tags"> + <%= f.text_field :tag_list, :label => :tags, :size => 60, :class => 'hol' %> + </p> + <div id="project_tag_candidates" class="autocomplete"></div> + <%= javascript_include_tag 'tags_input', :plugin => 'redmine_tags' %> + + <%= javascript_tag "observeProjectTagsField('#{url_for(:controller => 'auto_completes', :action => 'project_tags')}')" %> + </div> + <% end -%> +</p>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_tags/app/views/projects/_filtered_projects.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,7 @@ +<% if @projects.empty? %> +<p><b><%= l(:project_filter_no_results) %></b></p> +<% else %> +<%= render_project_table_with_filtering(@projects, @question) %> +<% end %> + +<p class="pagination"><%= pagination_links_full @project_pages, @project_count %></p>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_tags/app/views/projects/_my.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,1 @@ +<%= render_my_project_hierarchy_with_tags(@user_projects) %>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_tags/app/views/projects/_my_projects.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,17 @@ +<% if !@user_projects.empty? %> +<% if @myproj_status=="true" %> +<fieldset id="my_projects_fieldset" class="collapsible"> + <legend onclick="toggleFieldsetWithState(this);"><h2><%= l(:label_my_project_plural) %></h2></legend> +<% else %> +<fieldset id="my_projects_fieldset" class="collapsible collapsed"> + <legend onclick="toggleFieldsetWithState(this);"><h2><%= l(:label_my_project_plural) %></h2></legend> + <div style="display: none;"> +<% end %> + <div> + <%= render_my_project_hierarchy_with_tags(@user_projects)%> + </div> + <% unless @myproj_status=="true" %> + </div> + <%- end -%> +</fieldset> +<% end %>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_tags/app/views/projects/_tagcloud.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,6 @@ + +<div id="tags"> +<%= render_tags_list(Project.available_tags.select { |t| Project.tagged_with(t).count > 1 }, :style => :cloud) %> +</div> + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_tags/app/views/projects/_tags.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,7 @@ +<% unless @project.tag_list.empty? %> + <%= stylesheet_link_tag 'redmine_tags', :plugin => 'redmine_tags' %> + <dl class="tags"> + <dt class="tags-title"><%=l(:tags)%></dt> + <dd class="tags"><%= raw @project.tag_counts.collect{ |t| render_project_tag_link(t) }.join(', ') %></dd> + </dl> +<% end %>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_tags/app/views/projects/_tags_form.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,26 @@ +<%= labelled_fields_for :project, project do |f| -%> +<div> + <p id="project_tags"><%= f.text_field :tag_list, :label => :tags, :size => 60, :class => 'hol' %> + <em class="info"><%= l(:text_tags_search).html_safe %></em> + </p> + <div id="project_tag_candidates" class="autocomplete" style="margin-top: 0;"></div> + <%= stylesheet_link_tag 'jquery.tagit.css', :plugin => 'redmine_tags' %> + <%= stylesheet_link_tag 'redmine_tags', :plugin => 'redmine_tags' %> + <%= javascript_include_tag 'tag-it', :plugin => 'redmine_tags' %> + + <%= javascript_tag "$('#project_tag_list').tagit({ + tagSource: function(search, showChoices) { + var that = this; + $.ajax({ + url: '#{url_for(:controller => 'auto_completes', :action => 'project_tags')}', + data: {q: search.term}, + success: function(choices) { + showChoices(that._subtractArray(jQuery.parseJSON(choices), that.assignedTags())); + } + }); + }, + }); +" %> + +</div> +<% end -%>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_tags/app/views/projects/index.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,44 @@ +<% content_for :header_tags do %> + <%= auto_discovery_link_tag(:atom, {:action => 'index', :format => 'atom', :key => User.current.rss_key}) %> + <%= stylesheet_link_tag 'redmine_tags', :plugin => 'redmine_tags' %> +<% end %> + +<%= javascript_include_tag 'projects_index', :plugin => 'redmine_tags' %> + +<div class="contextual"> + <%= link_to l(:label_overall_activity), { :controller => 'activities', :action => 'index' }%> + <%= ('| ' + link_to(l(:label_project_new), {:controller => 'projects', :action => 'new'}, :class => 'icon icon-add')).html_safe if User.current.allowed_to?(:add_project, nil, :global => true) %> +</div> + +<div style="clear:both;"></div> +<h2> + <%= l("label_project_all") %> +</h2> + +<div style="clear:both;"></div> + +<%= form_tag :projects, :method => :get do -%> + <div> + <p class='q'> + <%= label_tag 'search', l('project_filtering_q_label') %> + <%= text_field_tag 'search', params[:search] -%> + </p> + + <div id='filter_tags'> + <%= render :partial => 'filter_search_tags' -%> + </div> + + <%= submit_tag :search, :name => "Search" %> + + </div> +<%- end -%> + +<div id="projects"> + <%= render :partial => 'filtered_projects' %> +</div> + +<% other_formats_links do |f| %> + <%= f.link_to 'Atom', :url => {:key => User.current.rss_key} %> +<% end %> + +<% html_title(l(:label_project_plural)) -%>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_tags/app/views/tags/_settings.html.erb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,19 @@ +<fieldset><legend><%= l(:setting_issue_tags) %></legend> + <p> + <label><%= l(:issues_sidebar) %></label> + <%= select_tag 'settings[issues_sidebar]', options_for_select(%w(none list cloud).collect{|v| [l("issue_tags_sidebar_#{v}"), v]}, @settings[:issues_sidebar]) %> + </p> + <p> + <label><%= l(:issues_show_count) %></label> + <%= check_box_tag 'settings[issues_show_count]', 1, 1 == @settings[:issues_show_count].to_i %> + </p> + <p> + <label><%= l(:issues_open_only) %></label> + <%= check_box_tag 'settings[issues_open_only]', 1, 1 == @settings[:issues_open_only].to_i %> + </p> + <p> + <label><%= l(:issues_sort_by) %></label> + <%= select_tag 'settings[issues_sort_by]', options_for_select(%w(name count).collect{|v| [l("issues_sort_by_#{v}"), v]}, @settings[:issues_sort_by]) %> + <%= select_tag 'settings[issues_sort_order]', options_for_select(%w(asc desc).collect{|v| [l("issues_sort_order_#{v}"), v]}, @settings[:issues_sort_order]) %> + </p> +</fieldset>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_tags/assets/javascripts/tag-it.js Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,538 @@ +/* +* jQuery UI Tag-it! +* +* @version v2.0 (06/2011) +* +* Copyright 2011, Levy Carneiro Jr. +* Released under the MIT license. +* http://aehlke.github.com/tag-it/LICENSE +* +* Homepage: +* http://aehlke.github.com/tag-it/ +* +* Authors: +* Levy Carneiro Jr. +* Martin Rehfeld +* Tobias Schmidt +* Skylar Challand +* Alex Ehlke +* +* Maintainer: +* Alex Ehlke - Twitter: @aehlke +* +* Dependencies: +* jQuery v1.4+ +* jQuery UI v1.8+ +*/ +(function($) { + + $.widget('ui.tagit', { + options: { + allowDuplicates : false, + caseSensitive : true, + fieldName : 'tags', + placeholderText : null, // Sets `placeholder` attr on input field. + readOnly : false, // Disables editing. + removeConfirmation: false, // Require confirmation to remove tags. + tagLimit : null, // Max number of tags allowed (null for unlimited). + + // Used for autocomplete, unless you override `autocomplete.source`. + availableTags : [], + + // Use to override or add any options to the autocomplete widget. + // + // By default, autocomplete.source will map to availableTags, + // unless overridden. + autocomplete: {}, + + // Shows autocomplete before the user even types anything. + showAutocompleteOnFocus: false, + + // When enabled, quotes are unneccesary for inputting multi-word tags. + allowSpaces: false, + + // The below options are for using a single field instead of several + // for our form values. + // + // When enabled, will use a single hidden field for the form, + // rather than one per tag. It will delimit tags in the field + // with singleFieldDelimiter. + // + // The easiest way to use singleField is to just instantiate tag-it + // on an INPUT element, in which case singleField is automatically + // set to true, and singleFieldNode is set to that element. This + // way, you don't need to fiddle with these options. + singleField: false, + + // This is just used when preloading data from the field, and for + // populating the field with delimited tags as the user adds them. + singleFieldDelimiter: ',', + + // Set this to an input DOM node to use an existing form field. + // Any text in it will be erased on init. But it will be + // populated with the text of tags as they are created, + // delimited by singleFieldDelimiter. + // + // If this is not set, we create an input node for it, + // with the name given in settings.fieldName. + singleFieldNode: null, + + // Whether to animate tag removals or not. + animate: true, + + // Optionally set a tabindex attribute on the input that gets + // created for tag-it. + tabIndex: null, + + // Event callbacks. + beforeTagAdded : null, + afterTagAdded : null, + + beforeTagRemoved : null, + afterTagRemoved : null, + + onTagClicked : null, + onTagLimitExceeded : null, + + + // DEPRECATED: + // + // /!\ These event callbacks are deprecated and WILL BE REMOVED at some + // point in the future. They're here for backwards-compatibility. + // Use the above before/after event callbacks instead. + onTagAdded : null, + onTagRemoved: null, + // `autocomplete.source` is the replacement for tagSource. + tagSource: null + // Do not use the above deprecated options. + }, + + _create: function() { + // for handling static scoping inside callbacks + var that = this; + + // There are 2 kinds of DOM nodes this widget can be instantiated on: + // 1. UL, OL, or some element containing either of these. + // 2. INPUT, in which case 'singleField' is overridden to true, + // a UL is created and the INPUT is hidden. + if (this.element.is('input')) { + this.tagList = $('<ul></ul>').insertAfter(this.element); + this.options.singleField = true; + this.options.singleFieldNode = this.element; + this.element.css('display', 'none'); + } else { + this.tagList = this.element.find('ul, ol').andSelf().last(); + } + + this.tagInput = $('<input type="text" />').addClass('ui-widget-content'); + + if (this.options.readOnly) this.tagInput.attr('disabled', 'disabled'); + + if (this.options.tabIndex) { + this.tagInput.attr('tabindex', this.options.tabIndex); + } + + if (this.options.placeholderText) { + this.tagInput.attr('placeholder', this.options.placeholderText); + } + + if (!this.options.autocomplete.source) { + this.options.autocomplete.source = function(search, showChoices) { + var filter = search.term.toLowerCase(); + var choices = $.grep(this.options.availableTags, function(element) { + // Only match autocomplete options that begin with the search term. + // (Case insensitive.) + return (element.toLowerCase().indexOf(filter) === 0); + }); + showChoices(this._subtractArray(choices, this.assignedTags())); + }; + } + + if (this.options.showAutocompleteOnFocus) { + this.tagInput.focus(function(event, ui) { + that._showAutocomplete(); + }); + + if (typeof this.options.autocomplete.minLength === 'undefined') { + this.options.autocomplete.minLength = 0; + } + } + + // Bind autocomplete.source callback functions to this context. + if ($.isFunction(this.options.autocomplete.source)) { + this.options.autocomplete.source = $.proxy(this.options.autocomplete.source, this); + } + + // DEPRECATED. + if ($.isFunction(this.options.tagSource)) { + this.options.tagSource = $.proxy(this.options.tagSource, this); + } + + this.tagList + .addClass('tagit') + .addClass('ui-widget ui-widget-content ui-corner-all') + // Create the input field. + .append($('<li class="tagit-new"></li>').append(this.tagInput)) + .click(function(e) { + var target = $(e.target); + if (target.hasClass('tagit-label')) { + var tag = target.closest('.tagit-choice'); + if (!tag.hasClass('removed')) { + that._trigger('onTagClicked', e, {tag: tag, tagLabel: that.tagLabel(tag)}); + } + } else { + // Sets the focus() to the input field, if the user + // clicks anywhere inside the UL. This is needed + // because the input field needs to be of a small size. + that.tagInput.focus(); + } + }); + + // Single field support. + var addedExistingFromSingleFieldNode = false; + if (this.options.singleField) { + if (this.options.singleFieldNode) { + // Add existing tags from the input field. + var node = $(this.options.singleFieldNode); + var tags = node.val().split(this.options.singleFieldDelimiter); + node.val(''); + $.each(tags, function(index, tag) { + that.createTag(tag, null, true); + addedExistingFromSingleFieldNode = true; + }); + } else { + // Create our single field input after our list. + this.options.singleFieldNode = $('<input type="hidden" style="display:none;" value="" name="' + this.options.fieldName + '" />'); + this.tagList.after(this.options.singleFieldNode); + } + } + + // Add existing tags from the list, if any. + if (!addedExistingFromSingleFieldNode) { + this.tagList.children('li').each(function() { + if (!$(this).hasClass('tagit-new')) { + that.createTag($(this).text(), $(this).attr('class'), true); + $(this).remove(); + } + }); + } + + // Events. + this.tagInput + .keydown(function(event) { + // Backspace is not detected within a keypress, so it must use keydown. + if (event.which == $.ui.keyCode.BACKSPACE && that.tagInput.val() === '') { + var tag = that._lastTag(); + if (!that.options.removeConfirmation || tag.hasClass('remove')) { + // When backspace is pressed, the last tag is deleted. + that.removeTag(tag); + } else if (that.options.removeConfirmation) { + tag.addClass('remove ui-state-highlight'); + } + } else if (that.options.removeConfirmation) { + that._lastTag().removeClass('remove ui-state-highlight'); + } + + // Comma/Space/Enter are all valid delimiters for new tags, + // except when there is an open quote or if setting allowSpaces = true. + // Tab will also create a tag, unless the tag input is empty, + // in which case it isn't caught. + if ( + event.which === $.ui.keyCode.COMMA || + event.which === $.ui.keyCode.ENTER || + ( + event.which == $.ui.keyCode.TAB && + that.tagInput.val() !== '' + ) || + ( + event.which == $.ui.keyCode.SPACE && + that.options.allowSpaces !== true && + ( + $.trim(that.tagInput.val()).replace( /^s*/, '' ).charAt(0) != '"' || + ( + $.trim(that.tagInput.val()).charAt(0) == '"' && + $.trim(that.tagInput.val()).charAt($.trim(that.tagInput.val()).length - 1) == '"' && + $.trim(that.tagInput.val()).length - 1 !== 0 + ) + ) + ) + ) { + // Enter submits the form if there's no text in the input. + if (!(event.which === $.ui.keyCode.ENTER && that.tagInput.val() === '')) { + event.preventDefault(); + } + + that.createTag(that._cleanedInput()); + + // The autocomplete doesn't close automatically when TAB is pressed. + // So let's ensure that it closes. + that.tagInput.autocomplete('close'); + } + }).blur(function(e){ + // Create a tag when the element loses focus. + // If autocomplete is enabled and suggestion was clicked, don't add it. + if (!that.tagInput.data('autocomplete-open')) { + that.createTag(that._cleanedInput()); + } + }); + + // Autocomplete. + if (this.options.availableTags || this.options.tagSource || this.options.autocomplete.source) { + var autocompleteOptions = { + select: function(event, ui) { + that.createTag(ui.item.value); + // Preventing the tag input to be updated with the chosen value. + return false; + } + }; + $.extend(autocompleteOptions, this.options.autocomplete); + + // tagSource is deprecated, but takes precedence here since autocomplete.source is set by default, + // while tagSource is left null by default. + autocompleteOptions.source = this.options.tagSource || autocompleteOptions.source; + + this.tagInput.autocomplete(autocompleteOptions).bind('autocompleteopen', function(event, ui) { + that.tagInput.data('autocomplete-open', true); + }).bind('autocompleteclose', function(event, ui) { + that.tagInput.data('autocomplete-open', false) + }); + } + }, + + _cleanedInput: function() { + // Returns the contents of the tag input, cleaned and ready to be passed to createTag + return $.trim(this.tagInput.val().replace(/^"(.*)"$/, '$1')); + }, + + _lastTag: function() { + return this.tagList.find('.tagit-choice:last:not(.removed)'); + }, + + _tags: function() { + return this.tagList.find('.tagit-choice:not(.removed)'); + }, + + assignedTags: function() { + // Returns an array of tag string values + var that = this; + var tags = []; + if (this.options.singleField) { + tags = $(this.options.singleFieldNode).val().split(this.options.singleFieldDelimiter); + if (tags[0] === '') { + tags = []; + } + } else { + this._tags().each(function() { + tags.push(that.tagLabel(this)); + }); + } + return tags; + }, + + _updateSingleTagsField: function(tags) { + // Takes a list of tag string values, updates this.options.singleFieldNode.val to the tags delimited by this.options.singleFieldDelimiter + $(this.options.singleFieldNode).val(tags.join(this.options.singleFieldDelimiter)).trigger('change'); + }, + + _subtractArray: function(a1, a2) { + var result = []; + for (var i = 0; i < a1.length; i++) { + if ($.inArray(a1[i], a2) == -1) { + result.push(a1[i]); + } + } + return result; + }, + + tagLabel: function(tag) { + // Returns the tag's string label. + if (this.options.singleField) { + return $(tag).find('.tagit-label:first').text(); + } else { + return $(tag).find('input:first').val(); + } + }, + + _showAutocomplete: function() { + this.tagInput.autocomplete('search', ''); + }, + + _findTagByLabel: function(name) { + var that = this; + var tag = null; + this._tags().each(function(i) { + if (that._formatStr(name) == that._formatStr(that.tagLabel(this))) { + tag = $(this); + return false; + } + }); + return tag; + }, + + _isNew: function(name) { + return !this._findTagByLabel(name); + }, + + _formatStr: function(str) { + if (this.options.caseSensitive) { + return str; + } + return $.trim(str.toLowerCase()); + }, + + _effectExists: function(name) { + return Boolean($.effects && ($.effects[name] || ($.effects.effect && $.effects.effect[name]))); + }, + + createTag: function(value, additionalClass, duringInitialization) { + var that = this; + + value = $.trim(value); + + if(this.options.preprocessTag) { + value = this.options.preprocessTag(value); + } + + if (value === '') { + return false; + } + + if (!this.options.allowDuplicates && !this._isNew(value)) { + var existingTag = this._findTagByLabel(value); + if (this._trigger('onTagExists', null, { + existingTag: existingTag, + duringInitialization: duringInitialization + }) !== false) { + if (this._effectExists('highlight')) { + existingTag.effect('highlight'); + } + } + return false; + } + + if (this.options.tagLimit && this._tags().length >= this.options.tagLimit) { + this._trigger('onTagLimitExceeded', null, {duringInitialization: duringInitialization}); + return false; + } + + var label = $(this.options.onTagClicked ? '<a class="tagit-label"></a>' : '<span class="tagit-label"></span>').text(value); + + // Create tag. + var tag = $('<li></li>') + .addClass('tagit-choice ui-widget-content ui-state-default ui-corner-all') + .addClass(additionalClass) + .append(label); + + if (this.options.readOnly){ + tag.addClass('tagit-choice-read-only'); + } else { + tag.addClass('tagit-choice-editable'); + // Button for removing the tag. + var removeTagIcon = $('<span></span>') + .addClass('ui-icon ui-icon-close'); + var removeTag = $('<a><span class="text-icon">\xd7</span></a>') // \xd7 is an X + .addClass('tagit-close') + .append(removeTagIcon) + .click(function(e) { + // Removes a tag when the little 'x' is clicked. + that.removeTag(tag); + }); + tag.append(removeTag); + } + + // Unless options.singleField is set, each tag has a hidden input field inline. + if (!this.options.singleField) { + var escapedValue = label.html(); + tag.append('<input type="hidden" style="display:none;" value="' + escapedValue + '" name="' + this.options.fieldName + '" />'); + } + + if (this._trigger('beforeTagAdded', null, { + tag: tag, + tagLabel: this.tagLabel(tag), + duringInitialization: duringInitialization + }) === false) { + return; + } + + if (this.options.singleField) { + var tags = this.assignedTags(); + tags.push(value); + this._updateSingleTagsField(tags); + } + + // DEPRECATED. + this._trigger('onTagAdded', null, tag); + + this.tagInput.val(''); + + // Insert tag. + this.tagInput.parent().before(tag); + + this._trigger('afterTagAdded', null, { + tag: tag, + tagLabel: this.tagLabel(tag), + duringInitialization: duringInitialization + }); + + if (this.options.showAutocompleteOnFocus && !duringInitialization) { + setTimeout(function () { that._showAutocomplete(); }, 0); + } + }, + + removeTag: function(tag, animate) { + animate = typeof animate === 'undefined' ? this.options.animate : animate; + + tag = $(tag); + + // DEPRECATED. + this._trigger('onTagRemoved', null, tag); + + if (this._trigger('beforeTagRemoved', null, {tag: tag, tagLabel: this.tagLabel(tag)}) === false) { + return; + } + + if (this.options.singleField) { + var tags = this.assignedTags(); + var removedTagLabel = this.tagLabel(tag); + tags = $.grep(tags, function(el){ + return el != removedTagLabel; + }); + this._updateSingleTagsField(tags); + } + + if (animate) { + tag.addClass('removed'); // Excludes this tag from _tags. + var hide_args = this._effectExists('blind') ? ['blind', {direction: 'horizontal'}, 'fast'] : ['fast']; + + var thisTag = this; + hide_args.push(function() { + tag.remove(); + thisTag._trigger('afterTagRemoved', null, {tag: tag, tagLabel: thisTag.tagLabel(tag)}); + }); + + tag.fadeOut('fast').hide.apply(tag, hide_args).dequeue(); + } else { + tag.remove(); + this._trigger('afterTagRemoved', null, {tag: tag, tagLabel: this.tagLabel(tag)}); + } + + }, + + removeTagByLabel: function(tagLabel, animate) { + var toRemove = this._findTagByLabel(tagLabel); + if (!toRemove) { + throw "No such tag exists with the name '" + tagLabel + "'"; + } + this.removeTag(toRemove, animate); + }, + + removeAll: function() { + // Removes all tags. + var that = this; + this._tags().each(function(index, tag) { + that.removeTag(tag, false); + }); + } + + }); +})(jQuery); \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_tags/assets/stylesheets/jquery.tagit.css Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,54 @@ +ul.tagit { + padding: 1px 5px; + overflow: auto; + margin-left: inherit; /* usually we don't want the regular ul margins. */ + margin-right: inherit; +} +ul.tagit li { + display: block; + float: left; + margin: 2px 5px 2px 0; +} +ul.tagit li.tagit-choice { + padding: .2em 18px .2em .5em; + position: relative; + line-height: inherit; +} +ul.tagit li.tagit-new { + padding: .25em 4px .25em 0; +} + +ul.tagit li.tagit-choice a.tagit-label { + cursor: pointer; + text-decoration: none; +} +ul.tagit li.tagit-choice .tagit-close { + cursor: pointer; + position: absolute; + right: .1em; + top: 50%; + margin-top: -8px; +} + +/* used for some custom themes that don't need image icons */ +ul.tagit li.tagit-choice .tagit-close .text-icon { + display: none; +} + +ul.tagit li.tagit-choice input { + display: block; + float: left; + margin: 2px 5px 2px 0; +} +ul.tagit input[type="text"] { + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; + + border: none; + margin: 0; + padding: 0; + width: inherit; + background-color: inherit; + outline: none; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_tags/assets/stylesheets/redmine_tags.css Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,62 @@ +/** + * This file is a part of redmine_tags + * redMine plugin, that adds tagging support. + * + * Copyright (c) 2010 Aleksey V Zapparov AKA ixti + * + * redmine_tags 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 3 of the License, or + * (at your option) any later version. + * + * redmine_tags 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 redmine_tags. If not, see <http://www.gnu.org/licenses/>. +*/ + +ul.tags { list-style: none; padding: 0px; } +ul.tags li { margin: .25em 0px; } + +div.tags { text-align: center; } +div.tags h3 { text-align: left; } +div.tags .tag-label { margin: .25em; } +div.tags .tag-nube-1 { font-size: .8em; } +div.tags .tag-nube-2 { font-size: .9em; } +div.tags .tag-nube-3 { font-size: 1em; } +div.tags .tag-nube-4 { font-size: 1.1em; } +div.tags .tag-nube-5 { font-size: 1.2em; } +div.tags .tag-nube-6 { font-size: 1.3em; } +div.tags .tag-nube-7 { font-size: 1.4em; } +div.tags .tag-nube-8 { font-size: 1.5em; } + +.tag-count { font-size: .75em; margin-left: .5em; } + +.tagit.ui-widget { + font-size: 1em; + margin: 0px; +} + +ul.tagit li.tagit-choice { + color: #505050; + font-weight: normal; +} + +ul.tagit li.tagit-choice:hover, ul.tagit li.tagit-choice.remove { + border-color: #6D95E0; +} + +ul.tagit input[type="text"] { + background: transparent; +} + +ul.projects .tags, ul.projects .no-tags { padding-left: 0.5em; color: #3e442c; font-size: 0.95em } +table.projects th.tags { color: #3e442c; } + +dl.tags { margin-top: 1.5em; margin-left: 0; padding-bottom: 1em; overflow: hidden; margin-bottom: 1em; border-bottom: 1px dotted #bbbbbb; } +dt.tags-title { float: left; font-weight: bold; color: #3e442c; } +dd.tags { margin-left: .5em; float: left; color: #3e442c; } +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_tags/config/locales/bg.yml Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,41 @@ +# This file is a part of redmine_tags +# redMine plugin, that adds tagging support. +# +# Bulgarian translation for redmine_tags +# by Ivan Cenov (jwalker_@Skype) i_cenov@botevgrad.com +# +# Copyright (c) 2010 Aleksey V Zapparov AKA ixti +# +# redmine_tags 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 3 of the License, or +# (at your option) any later version. +# +# redmine_tags 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 redmine_tags. If not, see <http://www.gnu.org/licenses/>. + +bg: + tags: Маркери + field_tags: Маркери + field_tag_list: Маркери + setting_issue_tags: Маркери на задачите + issues_sidebar: Показване на страничния панел като + issues_show_count: Показване на броя на задачите + issues_open_only: Само отворените задачи + issues_sort_by: Подреждане на маркерите по + + issue_tags_sidebar_none: Да не се показват + issue_tags_sidebar_list: Списък + issue_tags_sidebar_cloud: Облак + + issues_sort_by_name: Наименование + issues_sort_by_count: Количество задачи + issues_sort_order_asc: Нарастване + issues_sort_order_desc: Намаляване + + auto_complete_new_tag: Добавяне на нов...
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_tags/config/locales/de.yml Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,43 @@ +# This file is a part of redmine_tags +# redMine plugin, that adds tagging support. +# +# German translation for redmine_tags +# by: Terence Miller AKA cforce, <cforce(at)gmx.de>, +# Jörg Jans +# +# Copyright (c) 2010 Terence Miller AKA cforce +# Copyright (c) 2010 Jörg Jans +# +# redmine_tags 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 3 of the License, or +# (at your option) any later version. +# +# redmine_tags 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 redmine_tags. If not, see <http://www.gnu.org/licenses/>. + +de: + tags: Tags + field_tags: Tags + field_tag_list: Tags + setting_issue_tags: Ticket Tags + issues_sidebar: Zeige die Tags auf der Sidebar + issues_show_count: Zeige die Ticketanzahl an + issues_open_only: Zeige nur noch offene Tickets + issues_sort_by: Sortiere Tags nach + + issue_tags_sidebar_none: Keine + issue_tags_sidebar_list: Liste + issue_tags_sidebar_cloud: Cloud + + issues_sort_by_name: Name + issues_sort_by_count: Anzahl tickets + issues_sort_order_asc: Aufsteigend + issues_sort_order_desc: Absteigend + + auto_complete_new_tag: Hinzufügen...
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_tags/config/locales/en.yml Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,50 @@ +# This file is a part of redmine_tags +# redMine plugin, that adds tagging support. +# +# English translation for redmine_tags +# by Aleksey V Zapparov AKA ixti +# +# Copyright (c) 2010 Aleksey V Zapparov AKA ixti +# +# redmine_tags 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 3 of the License, or +# (at your option) any later version. +# +# redmine_tags 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 redmine_tags. If not, see <http://www.gnu.org/licenses/>. + +en: + tags: Tags + field_tags: Tags + field_tag_list: Tags + setting_issue_tags: Issues Tags + issues_sidebar: Display tags on sidebar as + issues_show_count: Display amount of issues + issues_open_only: Display open issues only + issues_sort_by: Sort tags by + + issue_tags_sidebar_none: None + issue_tags_sidebar_list: List + issue_tags_sidebar_cloud: Cloud + + issues_sort_by_name: Name + issues_sort_by_count: Issues amount + issues_sort_order_asc: Ascending + issues_sort_order_desc: Descending + + auto_complete_new_tag: Add new... + + project_filtering_q_label: "Filter by name:" + project_filter_no_results: "No matching projects found" + button_filter: "Filter" + + tags_search: "Filter by tag" + + text_tags_search: "A tag can be any text you like, but they're most useful if you choose tags that are already being used for the same thing by other projects (where possible). <br />Some tag examples are: library, plugin, paper, c++, mir, alpha, stable, bsd, android, ...<br />Tags help others find your work: please don't forget to tag your projects!" +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_tags/config/locales/fr.yml Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,41 @@ +# This file is a part of redmine_tags +# redMine plugin, that adds tagging support. +# +# French translation for redmine_tags +# by Stephane HANNEQUIN, <stephane.hannequin(at)aster-ingenierie.com> +# +# Copyright (c) 2010 Stephane HANNEQUIN +# +# redmine_tags 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 3 of the License, or +# (at your option) any later version. +# +# redmine_tags 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 redmine_tags. If not, see <http://www.gnu.org/licenses/>. + +fr: + tags: Tags + field_tags: Tags + field_tag_list: Tags + setting_issue_tags: Tags des demandes + issues_sidebar: Afficher les Tags comme + issues_show_count: Afficher le nombre de demande + issues_open_only: N'afficher que les demandes ouvertes + issues_sort_by: (en) Sort tags by + + issue_tags_sidebar_none: Ne pas afficher + issue_tags_sidebar_list: Liste + issue_tags_sidebar_cloud: Nuage + + issues_sort_by_name: (en) Name + issues_sort_by_count: (en) Issues amount + issues_sort_order_asc: (en) Ascending + issues_sort_order_desc: (en) Descending + + auto_complete_new_tag: Nouveau...
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_tags/config/locales/ru.yml Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,41 @@ +# This file is a part of redmine_tags +# redMine plugin, that adds tagging support. +# +# Russian translation for redmine_tags +# by Aleksey V Zapparov AKA ixti +# +# Copyright (c) 2010 Aleksey V Zapparov AKA ixti +# +# redmine_tags 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 3 of the License, or +# (at your option) any later version. +# +# redmine_tags 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 redmine_tags. If not, see <http://www.gnu.org/licenses/>. + +ru: + tags: Метки + field_tags: Метки + field_tag_list: Метки + setting_issue_tags: Метки задач + issues_sidebar: Боковую панель как + issues_show_count: Показать кол-во задач + issues_open_only: Только открытые задачи + issues_sort_by: Упорядочить метки по + + issue_tags_sidebar_none: Не показывать + issue_tags_sidebar_list: Список + issue_tags_sidebar_cloud: Облако + + issues_sort_by_name: Названию + issues_sort_by_count: Кол-ву задач + issues_sort_order_asc: по возрастанию + issues_sort_order_desc: по убыванию + + auto_complete_new_tag: Добавить...
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_tags/config/locales/zh.yml Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,41 @@ +# This file is a part of redmine_tags +# redMine plugin, that adds tagging support. +# +# Simplified Chinese translation for redmine_tags +# by archonwang (https://github.com/archonwang) +# +# Copyright (c) 2010 Aleksey V Zapparov AKA ixti +# +# redmine_tags 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 3 of the License, or +# (at your option) any later version. +# +# redmine_tags 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 redmine_tags. If not, see <http://www.gnu.org/licenses/>. + +zh: + tags: 标签 + field_tags: 标签 + field_tag_list: 标签 + setting_issue_tags: 问题标签 + issues_sidebar: 在侧边栏显示标签 + issues_show_count: 显示问题计数 + issues_open_only: 仅显示打开的问题 + issues_sort_by: 标签按何种方式排序 + + issue_tags_sidebar_none: 无 + issue_tags_sidebar_list: 列表显示 + issue_tags_sidebar_cloud: 云显示 + + issues_sort_by_name: 名称 + issues_sort_by_count: 问题计数 + issues_sort_order_asc: 顺序 + issues_sort_order_desc: 倒序 + + auto_complete_new_tag: 添加新标签 ... ...
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_tags/config/routes.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,6 @@ +RedmineApp::Application.routes.draw do + match '/issue_tags/auto_complete/:project_id', :to => 'auto_completes#issue_tags', :via => :get, :as => 'auto_complete_issue_tags' + match '/auto_completes/project_search_tags', :to => 'auto_completes#project_search_tags' + match '/auto_completes/project_tags', :to => 'auto_completes#project_tags' + match 'projects/set_fieldset_status' => 'projects#set_fieldset_status', :constraints => {:method => :post} +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_tags/db/migrate/001_acts_as_taggable_on_migration.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,29 @@ +class ActsAsTaggableOnMigration < ActiveRecord::Migration + def self.up + create_table :tags do |t| + t.column :name, :string + end + + create_table :taggings do |t| + t.column :tag_id, :integer + t.column :taggable_id, :integer + t.column :tagger_id, :integer + t.column :tagger_type, :string + + # You should make sure that the column created is + # long enough to store the required class names. + t.column :taggable_type, :string + t.column :context, :string + + t.column :created_at, :datetime + end + + add_index :taggings, :tag_id + add_index :taggings, [:taggable_id, :taggable_type, :context] + end + + def self.down + drop_table :taggings + drop_table :tags + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_tags/init.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,83 @@ +# This file is a part of redmine_tags +# redMine plugin, that adds tagging support. +# +# Copyright (c) 2010 Aleksey V Zapparov AKA ixti +# +# redmine_tags 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 3 of the License, or +# (at your option) any later version. +# +# redmine_tags 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 redmine_tags. If not, see <http://www.gnu.org/licenses/>. + +require 'redmine' +require 'redmine_tags' + + +Redmine::Plugin.register :redmine_tags do + name 'redmine_tags' + author 'Aleksey V Zapparov AKA "ixti"' + description 'redMine tagging support' + version '2.0.1-dev' + url 'https://github.com/ixti/redmine_tags/' + author_url 'http://www.ixti.net/' + + requires_redmine :version_or_higher => '1.2.0' + + settings :default => { + :issues_sidebar => 'none', + :issues_show_count => 0, + :issues_open_only => 0, + :issues_sort_by => 'name', + :issues_sort_order => 'asc' + }, :partial => 'tags/settings' +end + + +ActionDispatch::Callbacks.to_prepare do + require_dependency 'redmine_project_filtering' + + unless Project.included_modules.include?(RedmineTags::Patches::ProjectPatch) + Project.send(:include, RedmineTags::Patches::ProjectPatch) + end + + unless ProjectsHelper.included_modules.include?(RedmineTags::Patches::ProjectsHelperPatch) + ProjectsHelper.send(:include, RedmineTags::Patches::ProjectsHelperPatch) + end + + unless Issue.included_modules.include?(RedmineTags::Patches::IssuePatch) + Issue.send(:include, RedmineTags::Patches::IssuePatch) + end + + unless IssuesHelper.included_modules.include?(RedmineTags::Patches::IssuesHelperPatch) + IssuesHelper.send(:include, RedmineTags::Patches::IssuesHelperPatch) + end + + unless ProjectsController.included_modules.include?(RedmineTags::Patches::ProjectsControllerPatch) + ProjectsController.send(:include, RedmineTags::Patches::ProjectsControllerPatch) + end + + unless AutoCompletesController.included_modules.include?(RedmineTags::Patches::AutoCompletesControllerPatch) + AutoCompletesController.send(:include, RedmineTags::Patches::AutoCompletesControllerPatch) + end + + unless Query.included_modules.include?(RedmineTags::Patches::QueryPatch) + Query.send(:include, RedmineTags::Patches::QueryPatch) + end + + unless QueriesHelper.included_modules.include?(RedmineTags::Patches::QueriesHelperPatch) + QueriesHelper.send(:include, RedmineTags::Patches::QueriesHelperPatch) + end +end + + +require 'redmine_tags/hooks/model_issue_hook' +require 'redmine_tags/hooks/views_issues_hook' +require 'redmine_tags/hooks/views_projects_hook' +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_tags/lib/redmine_project_filtering.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,15 @@ +module RedmineProjectFiltering + + # transforms a question and a list of custom fields into something that Project.search can process + def self.calculate_tokens(question, custom_fields=nil) + list = [] + list << question if question.present? + + tokens = list.join(' ').scan(%r{((\s|^)"[\s\w]+"(\s|$)|\S+)}) + tokens = tokens.collect{ |m| m.first.gsub(%r{(^\s*"\s*|\s*"\s*$)}, '') } + + # tokens must be at least 2 characters long + tokens.select {|w| w.length > 1 } + end + +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_tags/lib/redmine_tags.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,21 @@ +# This file is a part of redmine_tags +# redMine plugin, that adds tagging support. +# +# Copyright (c) 2010 Aleksey V Zapparov AKA ixti +# +# redmine_tags 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 3 of the License, or +# (at your option) any later version. +# +# redmine_tags 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 redmine_tags. If not, see <http://www.gnu.org/licenses/>. + +module RedmineTags + def self.settings() Setting[:plugin_redmine_tags] end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_tags/lib/redmine_tags/hooks/model_issue_hook.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,58 @@ +# This file is a part of redmine_tags +# redMine plugin, that adds tagging support. +# +# Copyright (c) 2010 Eric Davis +# Copyright (c) 2010 Aleksey V Zapparov AKA ixti +# +# redmine_tags 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 3 of the License, or +# (at your option) any later version. +# +# redmine_tags 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 redmine_tags. If not, see <http://www.gnu.org/licenses/>. + +module RedmineTags + module Hooks + class ModelIssueHook < Redmine::Hook::ViewListener + def controller_issues_edit_before_save(context={}) + save_tags_to_issue(context, true) + end + + def controller_issues_bulk_edit_before_save(context={}) + save_tags_to_issue(context, true) + end + + # Issue has an after_save method that calls reload (update_nested_set_attributes) + # This makes it impossible for a new record to get a tag_list, it's + # cleared on reload. So instead, hook in after the Issue#save to update + # this issue's tag_list and call #save ourselves. + def controller_issues_new_after_save(context={}) + save_tags_to_issue(context, false) + context[:issue].save + end + + def save_tags_to_issue(context, create_journal) + params = context[:params] + + if params && params[:issue] && !params[:issue][:tag_list].nil? + old_tags = context[:issue].tag_list.to_s + context[:issue].tag_list = params[:issue][:tag_list] + new_tags = context[:issue].tag_list.to_s + + if create_journal and not (old_tags == new_tags || context[:issue].current_journal.blank?) + context[:issue].current_journal.details << JournalDetail.new(:property => 'attr', + :prop_key => 'tag_list', + :old_value => old_tags, + :value => new_tags) + end + end + end + end + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_tags/lib/redmine_tags/hooks/views_issues_hook.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,37 @@ +# This file is a part of redmine_tags +# redMine plugin, that adds tagging support. +# +# Copyright (c) 2010 Aleksey V Zapparov AKA ixti +# +# redmine_tags 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 3 of the License, or +# (at your option) any later version. +# +# redmine_tags 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 redmine_tags. If not, see <http://www.gnu.org/licenses/>. + +module RedmineTags + module Hooks + class ViewsIssuesHook < Redmine::Hook::ViewListener + + # TODO: temporary hack to disable tags on issues + ## BEGIN ~lf 20130712 + + # render_on :view_issues_show_details_bottom, :partial => '# issues/tags' + # render_on :view_issues_form_details_bottom, :partial => '# issues/tags_form' + # render_on :view_issues_sidebar_planning_bottom, :partial => '# issues/tags_sidebar' + # render_on :view_issues_bulk_edit_details_bottom, :partial => 'issues/tags_form' + + ## END ~lf 20130712 + + + end + end +end +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_tags/lib/redmine_tags/hooks/views_projects_hook.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,10 @@ +module RedmineTags + module Hooks + class ViewsProjectsHook < Redmine::Hook::ViewListener + render_on :view_projects_form, :partial => 'projects/tags_form' + render_on :view_projects_show_sidebar_top, :partial => 'projects/tags' +# render_on :view_issues_sidebar_planning_bottom, :partial => 'issues/tags_sidebar' + end + end +end +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_tags/lib/redmine_tags/patches/auto_completes_controller_patch.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,57 @@ +# This file is a part of redmine_tags +# redMine plugin, that adds tagging support. +# +# Copyright (c) 2010 Aleksey V Zapparov AKA ixti +# +# redmine_tags 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 3 of the License, or +# (at your option) any later version. +# +# redmine_tags 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 redmine_tags. If not, see <http://www.gnu.org/licenses/>. + +require_dependency 'auto_completes_controller' + +module RedmineTags + module Patches + module AutoCompletesControllerPatch + def self.included(base) + base.send(:include, InstanceMethods) + + base.class_eval do + unloadable + end + end + + + module InstanceMethods + def issue_tags + @name = params[:q].to_s + @tags = Issue.available_tags({ + :project_id => @project, + :name_like => @name + }) + render :layout => false, :partial => 'tag_list' + end + + def project_tags + @name = params[:q].to_s + @tags = Project.available_tags :name_like => @name + render :layout => false, :partial => 'tag_list' + end + + def project_search_tags + @name = params[:q].to_s + @tags = Project.available_tags :name_like => @name + render :layout => false, :partial => 'search_tag_list' + end + end + end + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_tags/lib/redmine_tags/patches/issue_patch.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,76 @@ +# This file is a part of redmine_tags +# redMine plugin, that adds tagging support. +# +# Copyright (c) 2010 Aleksey V Zapparov AKA ixti +# +# redmine_tags 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 3 of the License, or +# (at your option) any later version. +# +# redmine_tags 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 redmine_tags. If not, see <http://www.gnu.org/licenses/>. + +require_dependency 'issue' + +module RedmineTags + module Patches + module IssuePatch + def self.included(base) + base.extend(ClassMethods) + + base.class_eval do + unloadable + acts_as_taggable + + scope :on_project, lambda { |project| + project = project.id if project.is_a? Project + { :conditions => ["#{Project.table_name}.id=?", project] } + } + + Issue.safe_attributes 'tag_list' + end + end + + module ClassMethods + TAGGING_IDS_LIMIT_SQL = <<-SQL + tag_id IN ( + SELECT #{ActsAsTaggableOn::Tagging.table_name}.tag_id + FROM #{ActsAsTaggableOn::Tagging.table_name} + WHERE #{ActsAsTaggableOn::Tagging.table_name}.taggable_id IN (?) + ) + SQL + + # Returns available issue tags + # === Parameters + # * <i>options</i> = (optional) Options hash of + # * project - Project to search in. + # * open_only - Boolean. Whenever search within open issues only. + # * name_like - String. Substring to filter found tags. + def available_tags(options = {}) + ids_scope = Issue.visible + ids_scope = ids_scope.on_project(options[:project]) if options[:project] + ids_scope = ids_scope.open if options[:open_only] + + conditions = [""] + + # limit to the tags matching given %name_like% + if options[:name_like] + conditions[0] << "#{ActsAsTaggableOn::Tag.table_name}.name LIKE ? AND " + conditions << "%#{options[:name_like].downcase}%" + end + + conditions[0] << TAGGING_IDS_LIMIT_SQL + conditions << ids_scope.map{ |issue| issue.id }.push(-1) + + self.all_tag_counts(:conditions => conditions) + end + end + end + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_tags/lib/redmine_tags/patches/issues_helper_patch.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,37 @@ + +require_dependency 'issues_helper' + +module RedmineTags + module Patches + module IssuesHelperPatch + def self.included(base) + base.send(:include, InstanceMethods) + end + + module InstanceMethods + include TagsHelper + + def sidebar_tags + unless @sidebar_tags + @sidebar_tags = [] + if :none != RedmineTags.settings[:issues_sidebar].to_sym + @sidebar_tags = Issue.available_tags({ + :project => @project, + :open_only => (RedmineTags.settings[:issues_open_only].to_i == 1) + }) + end + end + @sidebar_tags + end + + def render_sidebar_tags + render_tags_list(sidebar_tags, { + :show_count => (RedmineTags.settings[:issues_show_count].to_i == 1), + :open_only => (RedmineTags.settings[:issues_open_only].to_i == 1), + :style => RedmineTags.settings[:issues_sidebar].to_sym + }) + end + end + end + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_tags/lib/redmine_tags/patches/project_patch.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,72 @@ +# C4DM + +require_dependency 'project' + +module RedmineTags + module Patches + module ProjectPatch + def self.included(base) # :nodoc: + base.extend(ClassMethods) + base.send(:include, InstanceMethods) + + base.class_eval do + unloadable + acts_as_taggable + + Project.safe_attributes 'tag_list' + + # TODO: review need for this callback (uneeded on update) ~lf.03042013 + after_create :save_tags + end + end + + module InstanceMethods + def save_tags + self.tags = Tag.transaction do + @tag_list.each(&:save) + end + end + end + + module ClassMethods + TAGGING_IDS_LIMIT_SQL = <<-SQL + tag_id IN ( + SELECT #{ActsAsTaggableOn::Tagging.table_name}.tag_id + FROM #{ActsAsTaggableOn::Tagging.table_name} + WHERE #{ActsAsTaggableOn::Tagging.table_name}.taggable_id IN (?) + ) + SQL + + def search_by_question(question) + if question.length > 1 + search(RedmineProjectFiltering.calculate_tokens(question), nil, :all_words => true).first.sort_by(&:lft) + else + all(:order => 'lft') + end + end + + # Returns available project tags + # Does not return tags from private projects + # === Parameters + # * <i>options</i> = (optional) Options hash of + # * name_like - String. Substring to filter found tags. + def available_tags( options = {} ) + ids_scope = Project.visible + + conditions = [""] + + # limit to the tags matching given %name_like% + if options[:name_like] + conditions[0] << "#{ActsAsTaggableOn::Tag.table_name}.name LIKE ? AND " + conditions << "%#{options[:name_like].downcase}%" + end + + conditions[0] << TAGGING_IDS_LIMIT_SQL + conditions << ids_scope.map{ |issue| issue.id }.push(-1) + + self.all_tag_counts(:conditions => conditions) + end + end + end + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_tags/lib/redmine_tags/patches/projects_controller_patch.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,131 @@ +# -*- coding: utf-8 -*- +require_dependency 'projects_controller' + +module RedmineTags + module Patches + module ProjectsControllerPatch + def self.included(base) + base.send(:include, InstanceMethods) + base.class_eval do + unloadable + # skip_before_filter :authorize, :only => [:set_fieldset_status] + # skip_before_filter :find_project, :only => [:set_fieldset_status] + + alias :index filtered_index + end + end + + module InstanceMethods + def paginate_projects + sort_init 'name' + sort_update %w(name lft created_on updated_on) + @limit = per_page_option + + # Only top-level visible projects are counted --lf.10Jan2013 + top_level_visible_projects = @projects.visible_roots + @project_count = top_level_visible_projects.count + + # Project.visible_roots.find(@projects).count + + @project_pages = ActionController::Pagination::Paginator.new self, @project_count, @limit, params['page'] + @offset ||= @project_pages.current.offset + end + + # def set_fieldset_status +# + # # luisf. test for missing parameters……… + # field = params[:field_id] + # status = params[:status] +# + # session[(field + "_status").to_sym] = status + # render :nothing => true + # end + + # gets the status of the collabsible fieldsets + # def get_fieldset_statuses + # if session[:my_projects_fieldset_status].nil? + # @myproj_status = "true" + # else + # @myproj_status = session[:my_projects_fieldset_status] + # end +# + # if session[:filters_fieldset_status].nil? + # @filter_status = "false" + # else + # @filter_status = session[:filters_fieldset_status] + # end +# + # if params && params[:project] && !params[:project][:tag_list].# nil? + # @filter_status = "true" + # end +# + # end + + # Lists visible projects. Paginator is for top-level projects only + # (subprojects belong to them) + def filtered_index + @project = Project.new + filter_projects + # get_fieldset_statuses + + sort_clause = "name" + + respond_to do |format| + format.html { + paginate_projects + + # todo: check ordering ~luisf.14/Jan/2013 + @projects = @projects[@offset, @limit] + + render :template => 'projects/index.html.erb', :layout => !request.xhr? + } + format.api { + @offset, @limit = api_offset_and_limit + @project_count = Project.visible.count + @projects = Project.visible.find(@projects, :offset => @offset, :limit => @limit, :order => 'lft') + } + format.atom { + projects = Project.visible.find(:all, :order => 'created_on DESC', :limit => Setting.feeds_limit.to_i) + render_feed(projects, :title => "#{Setting.app_title}: #{l(:label_project_latest)}") + } + format.js { + paginate_projects + @projects = Project.visible_roots.find(@projects, :offset => @offset, :limit => @limit, :order => sort_clause) + render :update do |page| + page.replace_html 'projects', :partial => 'filtered_projects' + end + } + end + end + + private + + def filter_projects + # find projects like question + @question = (params[:search] || "").strip + + if @question.empty? + projects = Project.visible + else + projects = Project.visible.like(@question) + end + + # search for tags + if params.has_key?(:tag_search) + tag_list = (params[:tag_search] || "").strip.split(",") + else + tag_list = "" + end + + unless tag_list.empty? + projects = projects.tagged_with(tag_list) + end + + ## TODO: luisf-10Apr2013 should I only return the visible_roots? + @projects = projects + + end + end + end + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_tags/lib/redmine_tags/patches/projects_helper_patch.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,268 @@ +module RedmineTags + module Patches + module ProjectsHelperPatch + + def self.included(base) # :nodoc: + base.send(:include, InstanceMethods) + base.send(:include, TagsHelper) + + base.class_eval do + unloadable + end + end + + module InstanceMethods + # 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_with_filtering(projects, question) + custom_fields = "" + s = "" + if projects.any? + tokens = RedmineProjectFiltering.calculate_tokens(question, custom_fields) + + s << "<div class='autoscroll'>" + s << "<table class='list projects'>" + s << "<thead><tr>" + + s << sort_header_tag('name', :caption => l("field_name")) + s << "<th class='tags'>" << l("tags") << "</th>" + s << "<th class='managers'>" << l("label_managers") << "</th>" + s << sort_header_tag('created_on', :default_order => 'desc') + s << sort_header_tag('updated_on', :default_order => 'desc') + + s << "</tr></thead><tbody>" + + original_project = @project + + projects.each do |project| + s << render_project_in_table_with_filtering(project, cycle('odd', 'even'), 0, tokens) + end + + s << "</table>" + else + s << "\n" + end + @project = original_project + + s.html_safe + end + + def render_project_in_table_with_filtering(project, oddeven, level, tokens) + # set the project environment to please macros. + @project = project + + classes = (level == 0 ? 'root' : 'child') + + s = "" + + s << "<tr class='#{oddeven} #{classes} level#{level}'>" + s << "<td class='firstcol' align=top><div class='name hosted_here" + s << " no_description" if project.description.blank? + s << "'>" << link_to( highlight_tokens(project.name, tokens), {:controller => 'projects', :action => 'show', :id => project}, :class => "project #{User.current.member_of?(project) ? 'my-project' : nil}") + s << "</div>" + s << highlight_tokens(render_project_short_description(project), tokens) + s << "</td>" + + # taglist + s << "<td class='tags' align=top>" << project.tag_counts.collect{ |t| render_project_tag_link(t) }.join(', ') << "</td>" + + s << "<td class='managers' align=top>" + + 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 << '<nobr>' << mgrs.join(', ') << '</nobr>' + else + s << mgrs.join(', ') + end + end + end + end + + s << "</td>" + + s << "<td class='created_on' align=top>" << format_date(project.created_on) << "</td>" + s << "<td class='updated_on' align=top>" << format_date(project.updated_on) << "</td>" + + s << "</tr>" + + project.children.each do |child| + if child.is_public? or User.current.member_of?(child) + s << render_project_in_table_with_filtering(child, oddeven, level + 1, tokens) + end + end + + s.html_safe + end + + + + # Renders a tree of projects 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_project_hierarchy_with_filtering(projects,custom_fields,question) + s = [] + if projects.any? + tokens = RedmineProjectFiltering.calculate_tokens(question, custom_fields) + + ancestors = [] + original_project = @project + projects.each do |project| + # set the project environment to please macros. + @project = project + if (ancestors.empty? || project.is_descendant_of?(ancestors.last)) + s << "<ul class='projects #{ ancestors.empty? ? 'root' : nil}'>" + else + ancestors.pop + s << "</li>" + while (ancestors.any? && !project.is_descendant_of?(ancestors.last)) + ancestors.pop + s << "</ul></li>" + end + end + classes = (ancestors.empty? ? 'root' : 'child') + s << "<li class='#{classes}'><div class='#{classes}'>" + + link_to( highlight_tokens(project.name, tokens), + {:controller => 'projects', :action => 'show', :id => project}, + :class => "project #{User.current.member_of?(project) ? 'my-project' : nil}" + ) + s << "<ul class='filter_fields'>" + + # CustomField.usable_for_project_filtering.each do |field| + # value_model = project.custom_value_for(field.id) + # value = value_model.present? ? value_model.value : nil + # s << "<li><b>#{field.name.humanize}:</b> #{highlight_tokens(value, tokens)}</li>" if value.present? + # end + + s << "</ul>" + s << "<div class='clear'></div>" + unless project.description.blank? + s << "<div class='wiki description'>" + s << "<b>#{ t(:field_description) }:</b>" + s << highlight_tokens(textilizable(project.short_description, :project => project), tokens) + s << "\n</div>" + end + s << "</div>" + ancestors << project + end + ancestors.size.times{ s << "</li></ul>" } + @project = original_project + end + (s.join "\n").html_safe + end + + # Renders a tree of projects where the current user belongs + # 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_my_project_hierarchy_with_tags(projects) + + s = '' + + original_project = @project + + projects.each do |project| + if project.root? || !projects.include?(project.parent) + s << render_my_project_in_hierarchy_with_tags(project) + end + end + + @project = original_project + + if s != '' + a = '' + a << "<ul class='projects root'>\n" + a << s + a << "</ul>\n" + s = a + end + + s.html_safe + + end + + + + + def render_my_project_in_hierarchy_with_tags(project) + + s = '' + + if User.current.member_of?(project) + + # set the project environment to please macros. + @project = project + + classes = (project.root? ? 'root' : 'child') + + s << "<li class='#{classes}'><div class='#{classes}'>" + + link_to_project(project, {}, :class => "project my-project") + if project.is_public? + s << " <span class='public'>" << l(:field_is_public) << "</span>" + else + s << " <span class='private'>" << l(:field_is_private) << "</span>" + end + + tc = project.tag_counts + if tc.empty? + s << " <span class='no-tags'>" << l(:field_no_tags) << "</span>" + else + s << " <span class='tags'>" << tc.collect{ |t| render_project_tag_link(t) }.join(', ') << "</span>" + end + + s << render_project_short_description(project) + + s << "</div>\n" + + cs = '' + project.children.each do |child| + cs << render_my_project_in_hierarchy_with_tags(child) + end + + if cs != '' + s << "<ul class='projects'>\n" << cs << "</ul>\n"; + end + + end + + s.html_safe + + end + + private + + # copied from search_helper. This one doesn't escape html or limit the text length + def highlight_tokens(text, tokens) + return text unless text && tokens && !tokens.empty? + re_tokens = tokens.collect {|t| Regexp.escape(t)} + regexp = Regexp.new "(#{re_tokens.join('|')})", Regexp::IGNORECASE + result = '' + text.split(regexp).each_with_index do |words, i| + words = words.mb_chars + if i.even? + result << words + else + t = (tokens.index(words.downcase) || 0) % 4 + result << content_tag('span', words, :class => "highlight token-#{t}") + end + end + result.html_safe + end + + end + end + end +end +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_tags/lib/redmine_tags/patches/queries_helper_patch.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,49 @@ +# This file is a part of redmine_tags +# redMine plugin, that adds tagging support. +# +# Copyright (c) 2010 Aleksey V Zapparov AKA ixti +# +# redmine_tags 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 3 of the License, or +# (at your option) any later version. +# +# redmine_tags 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 redmine_tags. If not, see <http://www.gnu.org/licenses/>. + +require_dependency 'queries_helper' + +module RedmineTags + module Patches + module QueriesHelperPatch + def self.included(base) + base.send(:include, InstanceMethods) + + base.class_eval do + unloadable + alias_method :column_content_original, :column_content + alias_method :column_content, :column_content_extended + end + end + + + module InstanceMethods + include TagsHelper + + + def column_content_extended(column, issue) + if column.name.eql? :tags + column.value(issue).collect{ |t| render_tag_link(t) }.join(', ') + else + column_content_original(column, issue) + end + end + end + end + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/redmine_tags/lib/redmine_tags/patches/query_patch.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,85 @@ +# This file is a part of redmine_tags +# redMine plugin, that adds tagging support. +# +# Copyright (c) 2010 Aleksey V Zapparov AKA ixti +# +# redmine_tags 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 3 of the License, or +# (at your option) any later version. +# +# redmine_tags 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 redmine_tags. If not, see <http://www.gnu.org/licenses/>. + +require_dependency 'query' + +module RedmineTags + module Patches + module QueryPatch + def self.included(base) + base.send(:include, InstanceMethods) + + base.class_eval do + unloadable + + alias_method :statement_original, :statement + alias_method :statement, :statement_extended + + alias_method :available_filters_original, :available_filters + alias_method :available_filters, :available_filters_extended + + base.add_available_column(QueryColumn.new(:tags)) + end + end + + + module InstanceMethods + def statement_extended + filter = filters.delete 'tags' + clauses = statement_original || "" + + if filter + filters.merge!( 'tags' => filter ) + + op = operator_for('tags') + case op + when '=', '!' + issues = Issue.tagged_with(values_for('tags').clone) + when '!*' + issues = Issue.tagged_with(ActsAsTaggableOn::Tag.all.map(&:to_s), :exclude => true) + else + issues = Issue.tagged_with(ActsAsTaggableOn::Tag.all.map(&:to_s), :any => true) + end + + compare = op.eql?('!') ? 'NOT IN' : 'IN' + ids_list = issues.collect{ |issue| issue.id }.push(0).join(',') + + clauses << " AND " unless clauses.empty? + clauses << "( #{Issue.table_name}.id #{compare} (#{ids_list}) ) " + end + + clauses + end + + + def available_filters_extended + unless @available_filters + available_filters_original.merge!({ 'tags' => { + :name => l(:tags), + :type => :list_optional, + :order => 6, + :values => Issue.available_tags(:project => project).collect{ |t| [t.name, t.name] } + }}) + end + @available_filters + end + end + end + end +end +
--- a/public/javascripts/application.js Tue Jan 14 14:37:42 2014 +0000 +++ b/public/javascripts/application.js Wed Jan 15 09:59:14 2014 +0000 @@ -591,3 +591,4 @@ $(document).ready(setupAjaxIndicator); $(document).ready(hideOnLoad); $(document).ready(addFormObserversForDoubleSubmit); +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/javascripts/repository.js Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,8 @@ +function toggle_ext_url() { + if (document.getElementById('repository_is_external').checked) { + document.getElementById('repository_external_url').disabled = false; + } else { + document.getElementById('repository_external_url').disabled = true; + } +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/javascripts/ssamr_institutions.js Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,31 @@ + + +/* SSAMR specific functions */ + +/* institution related functions */ +$(document).ready(function(){ + $('#ssamr_user_details_institution_type_true').click(function() { + $('#ssamr_user_details_other_institution').attr('disabled', 'disabled'); + $('#ssamr_user_details_institution_id').removeAttr('disabled'); + }); + + $('#ssamr_user_details_institution_type_false').click(function() { + $('#ssamr_user_details_other_institution').removeAttr('disabled'); + $('#ssamr_user_details_institution_id').attr('disabled', 'disabled'); + }); + + if($('#ssamr_user_details_institution_type_true').checked) + $('#ssamr_user_details_other_institution').attr('disabled', 'disabled'); + else if($('ssamr_user_details_institution_type_false').checked) + $('#ssamr_user_details_institution_id').attr('disabled', 'disabled'); + else { + $('#ssamr_user_details_other_institution').attr('disabled', 'disabled'); + $('#ssamr_user_details_institution_id').removeAttr('disabled'); + $('#ssamr_user_details_institution_type_true').checked = true; + $('#ssamr_user_details_institution_type_false').checked = false; + } +} +); + + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/javascripts/ssamr_registration.js Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,6 @@ +/* SSAMR specific functions */ + +/* initialisation: disabling the other institution text box */ +$(document).ready(function(){ + $('#ssamr_user_details_other_institution').attr('disabled', 'disabled'); + }); \ No newline at end of file
--- a/public/stylesheets/application.css Tue Jan 14 14:37:42 2014 +0000 +++ b/public/stylesheets/application.css Wed Jan 15 09:59:14 2014 +0000 @@ -5,7 +5,12 @@ #content h1, h2, h3, h4 {color: #555;} h2, .wiki h1 {font-size: 20px;} h3, .wiki h2 {font-size: 16px;} -h4, .wiki h3 {font-size: 13px;} +h4, .wiki h4, .wiki h5, .wiki h6 {font-size: 14px;} +.wiki h3 {font-size: 15px;} +.wiki h5 {font-weight: normal; font-style: italic; } +.wiki h6 {font-weight: normal; font-style: normal; } + + h4 {border-bottom: 1px dotted #bbb;} /***** Layout *****/ @@ -25,12 +30,14 @@ #account {float:right;} -#header {min-height:5.3em;margin:0;background-color:#628DB6;color:#f8f8f8; padding: 4px 8px 20px 6px; position:relative;} +#header {height:68px;margin:0;background-color:#628DB6;color:#f8f8f8; padding: 4px 8px 0px 6px; position:relative;} #header a {color:#f8f8f8;} #header h1 a.ancestor { font-size: 80%; } -#quick-search {float:right;} -#main-menu {position: absolute; bottom: 0px; left:6px; margin-right: -500px;} +#project-search-jump {float:right; } + + +#main-menu {position: absolute; bottom: 0px; left:8px; margin-right: -500px;} #main-menu ul {margin: 0; padding: 0;} #main-menu li { float:left; @@ -190,7 +197,8 @@ tr.changeset td.committed_on { text-align: center; width: 15%; white-space:nowrap;} table.files tbody th {text-align:left;} -table.files tr.file td.filename { text-align: left; padding-left: 24px; } +table.files tr.file td { text-align: center; } +table.files tr.file td.filename { text-align: left; } table.files tr.file td.digest { font-size: 80%; } table.members td.roles, table.memberships td.roles { width: 45%; } @@ -269,9 +277,12 @@ h3.version { background: url(../images/package.png) no-repeat 0% 50%; padding-left: 20px; } div.issues h3 { background: url(../images/ticket.png) no-repeat 0% 50%; padding-left: 20px; } +div.activity h3 { background: url(../images/ticket.png) no-repeat 0% 50%; padding-left: 20px; } div.members h3 { background: url(../images/group.png) no-repeat 0% 50%; padding-left: 20px; } div.news h3 { background: url(../images/news.png) no-repeat 0% 50%; padding-left: 20px; } div.projects h3 { background: url(../images/projects.png) no-repeat 0% 50%; padding-left: 20px; } +div.tags h3 { background: url(../images/ticket_note.png) no-repeat 0% 50%; padding-left: 20px; } +div.institutions h3 { background: url(../images/group.png) no-repeat 0% 50%; padding-left: 20px; } #watchers select {width: 95%; display: block;} #watchers a.delete {opacity: 0.4; vertical-align: middle;} @@ -312,6 +323,11 @@ .splitcontent {overflow:auto;} .splitcontentleft{float:left; width:49%;} .splitcontentright{float:right; width:49%;} + +.threecolumnleft{float:left; clear:left; width: 32%;} +.threecolumnright{float:right; clear:right; width: 32%;} +.threecolumnmid{margin-left: 33%; margin-right: 33%;} + form {display: inline;} input, select {vertical-align: middle; margin-top: 1px; margin-bottom: 1px;} fieldset {border: 1px solid #e4e4e4; margin:0;} @@ -368,22 +384,52 @@ div.journal {overflow:auto;} div.journal.private-notes {border-left:2px solid #d22; padding-left:4px; margin-left:-6px;} -div#activity dl, #search-results { margin-left: 2em; } -div#activity dd, #search-results dd { margin-bottom: 1em; padding-left: 18px; font-size: 0.9em; } -div#activity dt, #search-results dt { margin-bottom: 0px; padding-left: 20px; line-height: 18px; background-position: 0 50%; background-repeat: no-repeat; } -div#activity dt.me .time { border-bottom: 1px solid #999; } -div#activity dt .time { color: #777; font-size: 80%; } +div#activity dl, div#news dl, #search-results { margin-left: 2em; } +div#activity .box dl { margin-left: 0; } +div#activity dd, div#news dd, #search-results dd { margin-bottom: 1em; padding-left: 18px; font-size: 0.9em; } +div#activity dt, div#news dt, #search-results dt { margin-bottom: 0px; padding-left: 20px; line-height: 18px; background-position: 0 50%; background-repeat: no-repeat; } +div#activity dt.me .time, div#news dt.me .time { border-bottom: 1px solid #999; } +div#activity dt .time, div#news dt .time, .projects .latest .time, .projects .busy .managers { color: #777; font-size: 80%; } div#activity dd .description, #search-results dd .description { font-style: italic; } -div#activity span.project:after, #search-results span.project:after { content: " -"; } +div#activity span.project:after, div#news span.project:after, #search-results span.project:after { content: " -"; } div#activity dd span.description, #search-results dd span.description { display:block; color: #808080; } div#activity dt.grouped {margin-left:5em;} div#activity dd.grouped {margin-left:9em;} +div#members dl { margin-left: 2em; } +div#members dd { margin-bottom: 1em; padding-left: 18px; font-size: 0.9em; } +div#members dt { margin-bottom: 0px; padding-left: 20px; line-height: 18px; background-position: 0 50%; background-repeat: no-repeat; } +div#members dt.me .email { border-bottom: 1px solid #999; } +div#members dt .email { color: #777; font-size: 80%; } +div#members dd .roles { font-style: italic; } + +div#memberbox h3 { + background: url(../images/group.png) no-repeat 0% 50%; + padding-left: 20px; +} +div#memberbox p { + padding-left: 20px; + margin-left: 0; +} + +div.issues ul { + padding-left: 20px; + margin-left: 0; + list-style-type: none; +} +div.issues p { + padding-left: 20px; + margin-left: 0; +} + +.projects .latest .title, .projects .busy .title { margin-right: 0.5em; } +.tipoftheday .tip { margin-left: 2em; margin-top: 0.5em; } + #search-results dd { margin-bottom: 1em; padding-left: 20px; margin-left:0px; } -div#search-results-counts {float:right;} -div#search-results-counts ul { margin-top: 0.5em; } -div#search-results-counts li { list-style-type:none; float: left; margin-left: 1em; } +div#search-results-counts { display: block; padding-left: 0; margin-left: 0; } +div#search-results-counts ul { margin-top: 0.5em; padding-left: 0; margin-left: 0; } +div#search-results-counts li { display: inline; list-style-type: none; margin-right: 1em; } dt.issue { background-image: url(../images/ticket.png); } dt.issue-edit { background-image: url(../images/ticket_edit.png); } @@ -426,16 +472,20 @@ input#issue_subject { width: 99%; } select#issue_done_ratio { width: 95px; } -ul.projects {margin:0; padding-left:1em;} -ul.projects ul {padding-left:1.6em;} -ul.projects.root {margin:0; padding:0;} -ul.projects li {list-style-type:none;} +ul.projects { margin: 0; padding-left: 1em; } +ul.projects.root { margin: 0; padding: 0; } +/*ul.projects ul.projects { border-left: 3px solid #e0e0e0; } */ +ul.projects li.root { list-style-type:none; margin-bottom: 1em; } +ul.projects li.child { list-style-type:none; } +ul.projects div.root a.project { font-weight: bold; } +li.latest, li.busy { margin-bottom: 0.5em; } #projects-index ul.projects ul.projects { border-left: 3px solid #e0e0e0; padding-left:1em;} #projects-index ul.projects li.root {margin-bottom: 1em;} #projects-index ul.projects li.child {margin-top: 1em;} -#projects-index ul.projects div.root a.project { font-family: "Trebuchet MS", Verdana, sans-serif; font-weight: bold; font-size: 16px; margin: 0 0 10px 0; } -.my-project { padding-left: 18px; background: url(../images/fav.png) no-repeat 0 50%; } +#projects-index ul.projects div.root a.project { font-weight: bold; font-size: 16px; margin: 0 0 10px 0; } +/* .my-project { padding-left: 18px; background: url(../images/fav.png) no-repeat 0 50%; } */ + #notified-projects>ul, #tracker_project_ids>ul, #custom_field_project_ids>ul {max-height:250px; overflow-y:auto;} @@ -472,6 +522,9 @@ min-height: 1.8em; clear:left; } +.tabular ul{ +margin-top: -4px; +} html>body .tabular p {overflow:hidden;} @@ -485,6 +538,15 @@ width: 175px; } +.tabular .splitcontentleft .box p, .tabular .splitcontentright .box p, .splitcontentleft .tabular p, .splitcontentright .tabular p, .tabular .splitcontentleft .box ul, .tabular .splitcontentright .box ul, .splitcontentleft .tabular ul, .splitcontentright .tabular ul { +padding-left: 120px; +} + +.tabular .splitcontentleft .box label, .tabular .splitcontentright .box label, .splitcontentleft .tabular label, .splitcontentright .tabular label { +margin-left: -120px; +width: 115px; +} + .tabular label.floating{ font-weight: normal; margin-left: 0px; @@ -538,6 +600,9 @@ #attachments_fields .ajax-waiting input.filename {background:url(../images/hourglass.png) no-repeat 0px 50%;} #attachments_fields .ajax-loading input.filename {background:url(../images/loading.gif) no-repeat 0px 50%;} #attachments_fields div.ui-progressbar { width: 100px; height:14px; margin: 2px 0 -5px 8px; display: inline-block; } +#attachments_fields input[type=text] {margin-left: 8px; } +#attachments_fields img {vertical-align: middle;} + a.remove-upload {background: url(../images/delete.png) no-repeat 1px 50%; width:1px; display:inline-block; padding-left:16px;} a.remove-upload:hover {text-decoration:none !important;} @@ -546,7 +611,8 @@ div.attachments { margin-top: 12px; } div.attachments p { margin:4px 0 2px 0; } div.attachments img { vertical-align: middle; } -div.attachments span.author { font-size: 0.9em; color: #888; } +div.attachments span.author { font-size: 0.9em; color: #888; font-style: italic; padding-left: 4px } +div.attachments span.size_and_count { font-size: 0.9em; color: #888; padding-left: 4px; } div.thumbnails {margin-top:0.6em;} div.thumbnails div {background:#fff;border:2px solid #ddd;display:inline-block;margin-right:2px;} @@ -716,9 +782,9 @@ #roadmap table.progress td { height: 1.2em; } /***** Tabs *****/ -#content .tabs {height: 2.6em; margin-bottom:1.2em; position:relative; overflow:hidden;} -#content .tabs ul {margin:0; position:absolute; bottom:0; padding-left:0.5em; width: 2000px; border-bottom: 1px solid #bbbbbb;} -#content .tabs ul li { +#content .tabs, #content .tabs2 {height: 2.6em; margin-bottom:1.2em; position:relative; overflow:hidden;} +#content .tabs ul, #content .tabs2 ul {margin:0; position:absolute; bottom:0; padding-left:0.5em; width: 2000px; border-bottom: 1px solid #bbbbbb;} +#content .tabs ul li, #content .tabs2 ul li { float:left; list-style-type:none; white-space:nowrap; @@ -727,7 +793,7 @@ position:relative; margin-bottom:-1px; } -#content .tabs ul li a{ +#content .tabs ul li a, #content .tabs2 ul li a { display:block; font-size: 0.9em; text-decoration:none; @@ -742,19 +808,19 @@ border-top-right-radius:3px; } -#content .tabs ul li a:hover { +#content .tabs ul li a:hover, #content .tabs2 ul li a:hover { background-color: #ffffdd; text-decoration:none; } -#content .tabs ul li a.selected { +#content .tabs ul li a.selected, #content .tabs2 ul li a.selected { background-color: #fff; border: 1px solid #bbbbbb; border-bottom: 1px solid #fff; color:#444; } -#content .tabs ul li a.selected:hover {background-color: #fff;} +#content .tabs ul li a.selected:hover, #content .tabs2 ul li a.selected:hover {background-color: #fff;} div.tabs-buttons { position:absolute; right: 0; width: 48px; height: 24px; background: white; bottom: 0; border-bottom: 1px solid #bbbbbb; } @@ -1037,7 +1103,31 @@ #activity dt, .journal {clear: left;} -.journal-link {float: right;} +td.username img.gravatar { + margin: 0 0.5em 0 0; + vertical-align: top; +} + +#activity dt img.gravatar, #members dt img.gravatar, #active-colleagues img.gravatar { + float: left; + clear: left; + margin: 0.2em 1em 0.4em 0; +} + +/* Used on 12px Gravatar img tags without the icon background */ +.icon-gravatar { + float: left; + margin-right: 4px; +} + +#activity dt, +.journal { + clear: left; +} + +.journal-link { + float: right; +} h2 img { vertical-align:middle; }
--- a/public/stylesheets/scm.css Tue Jan 14 14:37:42 2014 +0000 +++ b/public/stylesheets/scm.css Wed Jan 15 09:59:14 2014 +0000 @@ -35,7 +35,7 @@ #changes-legend li { float: left; background-position: 5px 0; } table.filecontent { border: 1px solid #e2e2e2; border-collapse: collapse; width:98%; background-color: #fafafa; } -table.filecontent tbody {font-family:"Liberation Mono", Courier, monospace; font-size:12px;} +table.filecontent tbody {font-family:"SourceCodePro-Regular","Liberation Mono", Courier, monospace; font-size:12px;} table.filecontent th { border: 1px solid #e2e2e2; background-color: #eee; } table.filecontent th.filename { background-color: #e4e4d4; text-align: left; padding:5px;} table.filecontent tr.spacing th { text-align:center; } @@ -61,7 +61,7 @@ table.filecontent td.line-code pre { margin: 0px; white-space: pre-wrap; - font-family:"Liberation Mono", Courier, monospace; font-size:12px; + font-family:"SourceCodePro-Regular","Liberation Mono", Courier, monospace; font-size:12px; } table.filecontent tr:target th.line-num { background-color:#E0E0E0; color: #777; }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/themes/soundsoftware/stylesheets/application.css Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,306 @@ +@import url(../../../stylesheets/application.css); + +/* Colours: + + #be5700 link (orange) + + #3e442c text (dark green) + (OK for headings, but small text is easier to read in black) + + #ffa801 logo (yellow) + + #525a38 logotype (olive) + + #fdfaf0 background (cream) + + #fdfbf5 alternate background (lighter cream) + + #ffe69b highlight (light yellow) + + #a9b680 detail (light green) +*/ + +body { + background: #fdfbf5; + color: #000; + margin: 0; + margin-bottom: 40px; + min-width: 620px; +} + +h1 { + color: #3e442c; +} + +h2,h3,h4,.wiki h1 { + color: #3e442c; +} + +.wiki h2,.wiki h3,.wiki h4 { + color: #000; +} + +h2,.wiki h1 { + font-size: 1.8em; +} + +.wiki h2 { + margin-top: 1em; +} + +.splitcontentleft p:first-child { + margin-top: 0; +} + +div.attachments { + margin-top: 2em; +} +#wiki_add_attachment { + margin-top: 1.5em; +} + +/* Hide these (the paragraph markers that show anchors) -- they confuse more than they help */ +a.wiki-anchor:hover { display: none; } +h1:hover a.wiki-anchor, h2:hover a.wiki-anchor, h3:hover a.wiki-anchor { display: none; } + +.box { + padding: 6px; + margin-bottom: 10px; + background-color: #fdfaf0; + color: #3e442c; + line-height: 1.5em; + border: 1px solid #a9b680; + border-left: 4px solid #a9b680; +} + +.box h4 { + margin-top: 0; + padding-top: 0; +} + +.odd {background-color:#fdf7e4;} +.even {background-color: #fdfaf0;} + +.box .more { margin-left: 40px; } + +.box .institution { font-size: 0.95em; } + +#content .tabs { margin-bottom: 0; } + +table.list th { background-color: #fdfaf0; border-bottom: 1px solid #a9b680; } +table.list { border: 1px solid #a9b680; /* border-left: 4px solid #a9b680; */ } +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; padding-top: 0.3em; } +table.projects .description { padding-bottom: 0.5em; } +table.projects .no_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.9em } + +table.files tr.active td { padding-top: 0.5em; padding-bottom: 0.5em; } +table.files .file .active { font-weight: bold; } +table.files .file .description { font-weight: normal; color: #3e442c; } + +#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; } +#header { position: absolute; z-index: 0; top: 0; width: 100%; background: #fdfbf5; border-bottom: 2px solid #a9b680; /* height:80px; */ padding: 20px 0 0.5em 0; margin-bottom: 0; } +#header a { color: #be5700; } +#header h1 { color: #525a38; margin-top: 25px; font-size: 3em; font-weight: normal; margin-left: 10px; } +#header h1#project-title.long-title { font-size: 2em; line-height: 0.95em } +#header #project-title a, #header #project-title a:hover { color: #525a38; text-decoration: none; } +.header-general h1 { + background: url('soundsoftware-logo-code-title-only-transparent.png') no-repeat 0 0; + text-indent: -9999px; + width: 537px; + height: 34px; + } + +#quick-search { margin-right: 6px; margin-top: 1em; color: #000; } +#project-jump-box { float: right; margin-right: 6px; margin-top: 5px; color: #000; } +#project-ancestors-title { + margin-bottom: -6px; + margin-left: 12px; + margin-top: 6px; +} + +#main-menu { position: absolute; top: 100px; /* background-color: #be5700; */ left: 2px; border-top: 0; width: 100%;/* height: 1.82em; */ padding: 0; margin: 0; border: 0; } +#main-menu li { margin: 0; padding: 0; } +#main-menu li a { background-color: #fdfbf5; color: #be5700; border-right: 1px solid #a9b680; font-size: 97%; padding: 0em 8px 0em 10px; font-weight: normal; } +#main-menu li:last-child a { border-right: 0; } +#main-menu li a:hover { background-color: #fdfbf5; color: #be5700; text-decoration: underline; } +#main-menu li a.selected, #main-menu li a.selected:hover { background-color: #fdfbf5; color: #3e442c; } + +#footer { background-color: #fdfbf5; border: 0; border-top: 2px solid #a9b680; color: #3e442c; text-align: right; } +#footer a { color: #be5700; font-weight: bold; } + +p.statistics { text-align: right; font-size:0.9em; color: #666; } + +#main { margin-top: 135px; font:95%; background: #fdfaf0; } +#main a { font-weight: medium; color: #be5700;} +#main a:hover { color: #be5700; text-decoration: underline; } +#content { background: #fdfbf5; } +/*#content .tabs ul { bottom:-1px; } +*/ +h2, h3, h4, .wiki h1, .wiki h2, .wiki h3 { border-bottom: 0px; } +/*h2, .wiki h1 { letter-spacing:-1px; } +*/ +/* h4 { border-bottom: dotted 1px #c0c0c0; } */ + +.wiki p, .wiki li { margin-left: 30px; margin-right: 3em; } + +.overviewfields h4 { margin-left: 30px; margin-bottom: 0; } +.overviewfields ul { margin-top: 0; } + +.repository-info .wiki p { margin-left: 0 } + +div.issue { background: #fdfaf0; border: 1px solid #a9b680; border-left: 4px solid #a9b680; } + +#top-menu a.home, #top-menu a.my-page, #top-menu a.projects, #top-menu a.administration, #top-menu a.help { + background-position: 0% 40%; + background-repeat: no-repeat; + padding-left: 20px; + padding-top: 2px; + padding-bottom: 3px; +} + +#top-menu a.home { background-image: url(../images/home.png); } +#top-menu a.my-page { background-image: url(../../../images/user.png); } +#top-menu a.projects { background-image: url(../../../images/projects.png); } +#top-menu a.administration { background-image: url(../images/wrench.png); } +#top-menu a.help { background-image: url(../../../images/help.png); } + +#welcomepage { + width: 80%; + margin-left: 71px; + margin-top: 10px; +} +#welcomepage blockquote { + border: 0; + margin-top: 2em; + padding-left: 0; + margin-left: 0; +} +#welcomepage h2 { + font-size: 2em; + margin-bottom: 0.8em; +} +#welcomepage p, #welcomepage li { + font-size: 1.25em; + color: #3e442c; +} +#welcomepage p { + margin-left: 2em; + margin-top: 1em; + margin-bottom: 0.7em; +} +#welcomepage a { + white-space: nowrap; +} +#welcomepagenews { + float: right; + width: 27%; + margin-right: 3%; + margin-top: 1em; + margin-left: 2em; + margin-bottom: 2em; +} + +div.flash.newsalert { + float: right; + width: 25%; + padding: 0px 10px 2px 24px; + margin: 1.2em 1em 1em 2em; + border-color: #dd0000; +} + +/* for Javadoc in Embedded context: */ + +.TableHeadingColor { background-color: #fdf7e4; color: #3e442c; border: 0px solid #fff; } +.TableHeadingColor th { background-color: #fdf7e4; color: #3e442c; border: 1px solid #a9b680; } +.TableHeadingColor th font { font-size: 1.4em; color: #3e442c; } +.TableSubHeadingColor { background-color: #fdfaf0; color: #3e442c; border: 0px solid #fff; } +.TableSubHeadingColor th { background-color: #fdfaf0; color: #3e442c; border: 1px solid #a9b680; } +.TableRowColor { background-color: #fdfbf5; color: #000000; border: 0; } +.TableRowColor td { background-color: #fdfbf5; color: #000000; border: 0; } +.NavBarCell1 { background-color: #ffe69b; color:#000000 } + +.controller-redmine_embedded table { border: 0px solid #fff; } +.controller-redmine_embedded h3 { margin-top: 0.5em; } +.controller-redmine_embedded hr { color: #a9b680; background-color: #a9b680 } +.controller-redmine_embedded center { text-align: left; } /* haha */ +.controller-redmine_embedded th { font-weight: normal; text-align: left; } +.controller-redmine_embedded caption { text-align: left; font-weight: bold; font-size: 14px; margin-top: 1em; } +.controller-redmine_embedded li.blockList { list-style-type: none; } + +.controller-redmine_embedded .summary { font-style: normal; margin-bottom: 1.5em; } + +/* Special hack to hide the FRAMES | NO FRAMES links -- they don't + work in this context -- and right-align the All Classes and Detail links */ +.controller-redmine_embedded .NavBarCell2 a[target=_top] { width: 0px; visibility: hidden; } +.controller-redmine_embedded .NavBarCell2 + .NavBarCell2 { text-align: right; } +.controller-redmine_embedded .NavBarCell3 + .NavBarCell3 { text-align: right; } + +.topNav, .bottomNav { position: relative; overflow: hidden; } +.topNav ul li, .bottomNav ul li { float: left; list-style-type: none; margin-right: 14px; position: relative; font-weight: bold; } +.controller-redmine_embedded .subNav .navList { display: none; } + +/* For Doxygen in Embedded context (though note some of the Javadoc + rules will also apply to Doxygen): */ + +.memItemLeft, +.memItemRight, +.memTemplParams, +.memTemplItemLeft, +.memTemplItemRight, +.indexkey, +.memproto, +.memproto td, +.memdoc a { font-family: 'SourceCodePro-Regular', monospace; font-size: 0.9em; font-weight: normal; } + +.controller-redmine_embedded .memTemplParams { font-style: italic; } + +.controller-redmine_embedded #projectname, +.controller-redmine_embedded .title { font-weight: bold; font-size: 14px; } + +.controller-redmine_embedded h2 { font-size: 16px; } + +.controller-redmine_embedded .memitem { border-bottom: 1px solid #a9b680; padding-top: 0.5em; } +.controller-redmine_embedded .memitem:last-child { border-bottom: 0px; } + +.controller-redmine_embedded .contents { margin-top: 0.5em; } +.controller-redmine_embedded .contents td { padding: 0px; } + +.controller-redmine_embedded .contents h1, +.controller-redmine_embedded .contents h2, +.controller-redmine_embedded .navigation h1, +.controller-redmine_embedded .navigation h2 { padding-top: 1em; padding-bottom: 0; } + +.controller-redmine_embedded .contents .center { text-align: center; } /* undo javadoc hack above */ + +/* For MATLAB documentation */ + +.controller-redmine_embedded #matlabdoc th { text-align: left; } + +/* autocomplete positioning fix */ +div.autocomplete { + margin-top:136px; +} + +#my_projects_fieldset.collapsible { + font-size: 1.0em; + font-color: green; +} + + + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/themes/soundsoftware/stylesheets/fonts-generic.css Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,31 @@ +@import url(fonts.css?3); + +h1, #project-ancestors-title, #top-menu a { + font-family: Insider, 'Gill Sans', Tahoma, sans-serif; + font-weight: bold; +} + +#top-menu div, #top-menu li { + font-size: 12px; +} + +body,p,li,table { + font-family: Insider, DroidSans, 'Liberation Sans', tahoma, verdana, sans-serif; + font-size: 14px; + line-height: 1.34; + font-weight: normal; +} + +h2,h3,h4,.wiki h1,.embedded h1 { + font-family: Insider, DroidSans, 'Liberation Sans', tahoma, verdana, sans-serif; + font-weight: bold; + line-height: 1.34; +} + +code,pre,.code,.line-code,.embedded a.el { + font-family: 'SourceCodePro-Regular', monospace; + font-weight: normal; + font-size: 0.9em; +} + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/themes/soundsoftware/stylesheets/fonts-mac.css Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,30 @@ +@import url(fonts.css?3); + +h1, #project-ancestors-title, #top-menu a { + font-family: Insider, "Lucida Grande", sans-serif; + font-weight: bold; +} + +#top-menu div, #top-menu li { + font-size: 12px; +} + +body,p,li,table { + font-family: Insider, "Lucida Grande", sans-serif; + font-size: 14px; + line-height: 1.34; + font-weight: normal; +} + +h2,h3,h4,.wiki h1,.embedded h1 { + font-family: Insider, "Lucida Grande", sans-serif; + font-weight: bold; + line-height: 1.34; +} + +code,pre,.code,.line-code,.embedded a.el { + font-family: 'SourceCodePro-Regular', monospace; + font-weight: normal; + font-size: 0.9em; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/themes/soundsoftware/stylesheets/fonts-ms.css Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,33 @@ +@import url(fonts.css?3); + +/* IE likes us to separate out normal & bold into different fonts + rather than use the selectors on the same font */ + +h1, #project-ancestors-title, #top-menu a { + font-family: Insider-Medium, Tahoma, sans-serif; + font-weight: normal; +} + +#top-menu div, #top-menu li { + font-size: 12px; +} + +body,p,li,table { + font-family: Insider-Regular, tahoma, verdana, sans-serif; + font-size: 14px; + line-height: 1.34; + font-weight: normal; +} + +h2,h3,h4,.wiki h1,.embedded h1 { + font-family: Insider-Medium, tahoma, verdana, sans-serif; + font-weight: normal; + line-height: 1.34; +} + +code,pre,.code,.line-code,.embedded a.el { + font-family: 'SourceCodePro-Regular', monospace; + font-weight: normal; + font-size: 0.9em; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/themes/soundsoftware/stylesheets/fonts.css Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,48 @@ + +@font-face { + font-family: 'Insider'; + font-weight: bold; + font-style: normal; + src: url('fonts/24BC0E_0_0.eot'); + src: url('fonts/24BC0E_0_0.eot?#iefix') format('embedded-opentype'), url('fonts/24BC0E_0_0.woff') format('woff'), url('fonts/24BC0E_0_0.ttf') format('truetype'); +} + +@font-face { + font-family: 'Insider'; + font-weight: normal; + font-style: italic; + src: url('fonts/276F6F_0_0.eot'); + src: url('fonts/276F6F_0_0.eot?#iefix') format('embedded-opentype'), url('fonts/276F6F_0_0.woff') format('woff'), url('fonts/276F6F_0_0.ttf') format('truetype'); +} + +@font-face { + font-family: 'Insider'; + font-weight: normal; + font-style: normal; + src: url('fonts/24BC35_0_0.eot'); + src: url('fonts/24BC35_0_0.eot?#iefix') format('embedded-opentype'), url('fonts/24BC35_0_0.woff') format('woff'), url('fonts/24BC35_0_0.ttf') format('truetype'); +} + +@font-face { + font-family: 'Insider-Medium'; + font-weight: normal; + font-style: normal; + src: url('fonts/24BC0E_0_0.eot'); + src: url('fonts/24BC0E_0_0.eot?#iefix') format('embedded-opentype'), url('fonts/24BC0E_0_0.woff') format('woff'), url('fonts/24BC0E_0_0.ttf') format('truetype'); +} + +@font-face { + font-family: 'Insider-Regular'; + font-weight: normal; + font-style: normal; + src: url('fonts/24BC35_0_0.eot'); + src: url('fonts/24BC35_0_0.eot?#iefix') format('embedded-opentype'), url('fonts/24BC35_0_0.woff') format('woff'), url('fonts/24BC35_0_0.ttf') format('truetype'); +} + +@font-face { + font-family: 'SourceCodePro-Regular'; + font-weight: normal; + font-style: normal; + src: url('fonts/SourceCodePro-Regular-webfont.eot'); + src: url('fonts/SourceCodePro-Regular-webfont.eot?#iefix') format('embedded-opentype'), url('fonts/SourceCodePro-Regular-webfont.woff') format('woff'), url('fonts/SourceCodePro-Regular-webfont.ttf') format('truetype'); +}
Binary file public/themes/soundsoftware/stylesheets/soundsoftware-logo-code-title-only-transparent.png has changed
Binary file public/themes/soundsoftware/stylesheets/soundsoftware-logo-code-title-only-transparent.xcf has changed
Binary file public/themes/soundsoftware/stylesheets/soundsoftware-logo-title-only-transparent-beta.png has changed
Binary file public/themes/soundsoftware/stylesheets/soundsoftware-logo-title-only-transparent-beta.xcf has changed
Binary file public/themes/soundsoftware/stylesheets/soundsoftware-logo-title-only-transparent.png has changed
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/themes/ssamr/stylesheets/application.css Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,97 @@ +@import url(../../../stylesheets/application.css); + +@font-face +{ + /* A font by Jos Buivenga (exljbris) -> www.exljbris.com */ + font-family: Tallys; + src: url('Tallys_15.otf'); +} + +@font-face +{ + font-family: Droid; + src: url('DroidSans.ttf'); +} + +@font-face +{ + font-family: Droid; + font-weight: bold; + src: url('DroidSans-Bold.ttf'); +} + +@font-face +{ + font-family: Gillius; + src: url('GilliusADFNo2-Regular.otf'); +} + +@font-face +{ + font-family: Gillius; + font-weight: bold; + src: url('GilliusADFNo2-Bold.otf'); +} + +body { + background: #ffffff; + color: #404040; + margin: 0; + margin-bottom: 40px; +/* font-size: 95%; */ +} + +body,p,h2,h3,h4,li,table { + font-family: Droid, tahoma, verdana, sans-serif; +} + +h1 { + font-family: Tallys, Cambria, Georgia, "Times New Roman", Times, serif; + font-weight: normal; +} + +h2,h3,h4 { + color: #000000; +} + +h2 { font-size: 2em; } + +#top-menu { position: absolute; top: 0; z-index: 1; left: 0px; width: 100%; font-size: 80%; height: 2em; padding-top: 0.5em; background-color: #000000; } +#top-menu a { font-weight: bold; } +#header { position: absolute; z-index: 0; top: 0; width: 100%; background: #ffffff; border-bottom: 2px solid #000; height:80px; padding: 10px 0 0 0; margin-bottom: 0; } +#header a { color: #000; } +#header h1 { color: #000; text-shadow: #ccc 1px 1px; margin-top: 30px; font-size: 3em; font-weight: normal; margin-left: 0.3em; } +#quick-search { margin-right: 6px; margin-top: 2em; color: #000; } +#main-menu { position: absolute; top: 90px; background-color: #000000; left: 0; border-top: 0; width: 100%; height: 2em; } +#main-menu li { margin: 0; padding: 0; padding-top: 1px; } +#main-menu li a { background-color: #000; border-right: 1px solid #fff; font-size: 90%; padding: 4px 8px 4px 8px; font-weight: bold; } +#main-menu li a:hover { background-color: #80b0da; color: #ffffff; } +#main-menu li a.selected, #main-menu li a.selected:hover { background-color: #80b0da; color: #ffffff; } + +#footer { background-color: #000; border: 0; color: #fff;} +#footer a { color: #fff; font-weight: bold; } + +#main { margin-top: 135px; font:95%; background: #e8eaec; } +#main a { font-weight: medium; color: #800;} +#main a:hover { color: #800; text-decoration: underline; } +#content { background: #fff; } +/*#content .tabs ul { bottom:-1px; } +*/ +h2, h3, h4, .wiki h1, .wiki h2, .wiki h3 { border-bottom: 0px; } +/*h2, .wiki h1 { letter-spacing:-1px; } +*/ +h4 { border-bottom: dotted 1px #c0c0c0; } + +#top-menu a.home, #top-menu a.my-page, #top-menu a.projects, #top-menu a.administration, #top-menu a.help { + background-position: 0% 40%; + background-repeat: no-repeat; + padding-left: 20px; + padding-top: 2px; + padding-bottom: 3px; +} + +#top-menu a.home { background-image: url(../images/home.png); } +#top-menu a.my-page { background-image: url(../../../images/user.png); } +#top-menu a.projects { background-image: url(../../../images/projects.png); } +#top-menu a.administration { background-image: url(../images/wrench.png); } +#top-menu a.help { background-image: url(../../../images/help.png); }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/fixtures/institutions.yml Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,7 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html + +one: + name: MyString + +two: + name: MyString
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/fixtures/ssamr_user_details.yml Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,7 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html + +# one: +# column: value +# +# two: +# column: value
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/unit/institution_test.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,8 @@ +require 'test_helper' + +class InstitutionTest < ActiveSupport::TestCase + # Replace this with your real tests. + test "the truth" do + assert true + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/unit/ssamr_user_details_test.rb Wed Jan 15 09:59:14 2014 +0000 @@ -0,0 +1,8 @@ +require 'test_helper' + +class SsamrUserDetailsTest < ActiveSupport::TestCase + # Replace this with your real tests. + test "User Id should be correct" do + assert_equal user_id, user.id + end +end