Commit e6e057d1 authored by Holger Just's avatar Holger Just

Merge branch 'release-v3.2.0' into stable

parents d61ad013 bd132c56
......@@ -31,3 +31,4 @@ doc/app
/.rvmrc*
/*.iml
/.idea
.rbx
language: ruby
rvm:
- 1.8.7
- 1.9.2
- 1.9.3
- rbx-18mode
env:
- "RAILS_ENV=test DB=mysql BUNDLE_WITHOUT=rmagick:mysql2:postgres:sqlite"
- "RAILS_ENV=test DB=mysql2 BUNDLE_WITHOUT=rmagick:mysql:postgres:sqlite"
- "RAILS_ENV=test DB=postgres BUNDLE_WITHOUT=rmagick:mysql:mysql2:sqlite"
- "RAILS_ENV=test DB=sqlite BUNDLE_WITHOUT=rmagick:mysql:mysql2:postgres"
matrix:
exclude:
- rvm: 1.9.2
env: "RAILS_ENV=test DB=mysql BUNDLE_WITHOUT=rmagick:mysql2:postgres:sqlite"
- rvm: 1.9.3
env: "RAILS_ENV=test DB=mysql BUNDLE_WITHOUT=rmagick:mysql2:postgres:sqlite"
- rvm: rbx-18mode
env: "RAILS_ENV=test DB=mysql BUNDLE_WITHOUT=rmagick:mysql2:postgres:sqlite"
allow_failures:
- rvm: rbx-18mode
before_script:
- "sudo apt-get --no-install-recommends install bzr cvs darcs git mercurial subversion"
- "rake ci:travis:prepare"
branches:
only:
- unstable
- master
- stable
notifications:
email: false
irc: "irc.freenode.org#chiliproject"
......@@ -9,12 +9,15 @@ gem "rubytree", "~> 0.5.2", :require => 'tree'
gem "rdoc", ">= 2.4.2"
gem "liquid", "~> 2.3.0"
gem "acts-as-taggable-on", "= 2.1.0"
gem 'gravatarify', '~> 3.0.0'
# Needed only on RUBY_VERSION = 1.8, ruby 1.9+ compatible interpreters should bring their csv
gem "fastercsv", "~> 1.5.0", :platforms => [:ruby_18, :jruby, :mingw_18]
gem "tzinfo", "~> 0.3.31" # Fixes #903. Not required for Rails >= 3.2
group :test do
gem 'shoulda', '~> 2.10.3'
# Shoulda doesn't work nice on 1.9.3 and seems to need test-unit explicitely…
gem 'test-unit', :platforms => [:mri_19]
gem 'edavis10-object_daddy', :require => 'object_daddy'
gem 'mocha'
gem 'capybara'
......@@ -52,7 +55,7 @@ end
# orders of magnitude compared to their native counterparts. You have been
# warned.
platforms :mri, :mingw do
platforms :mri, :mingw, :rbx do
group :mysql2 do
gem "mysql2", "~> 0.2.7"
end
......@@ -74,7 +77,7 @@ platforms :mri_18, :mingw_18 do
end
end
platforms :mri_19, :mingw_19 do
platforms :mri_19, :mingw_19, :rbx do
group :sqlite do
gem "sqlite3"
end
......
......@@ -77,7 +77,7 @@ class AdminController < ApplicationController
def info
@db_adapter_name = ActiveRecord::Base.connection.adapter_name
@checklist = [
[:text_default_administrator_account_changed, User.find(:first, :conditions => ["login=? and hashed_password=?", 'admin', User.hash_password('admin')]).nil?],
[:text_default_administrator_account_changed, !User.find_by_login("admin").try(:check_password?, "admin")],
[:text_file_repository_writable, File.writable?(Attachment.storage_path)],
[:text_plugin_assets_writable, File.writable?(Engines.public_directory)],
[:text_rmagick_available, Object.const_defined?(:Magick)]
......
......@@ -31,18 +31,6 @@ class ApplicationController < ActionController::Base
cookies.delete(:autologin)
end
# Remove broken cookie after upgrade from 0.8.x (#4292)
# See https://rails.lighthouseapp.com/projects/8994/tickets/3360
# TODO: remove it when Rails is fixed
before_filter :delete_broken_cookies
def delete_broken_cookies
if cookies['_chiliproject_session'] && cookies['_chiliproject_session'] !~ /--/
cookies.delete '_chiliproject_session'
redirect_to home_path
return false
end
end
# FIXME: Remove this when all of Rack and Rails have learned how to
# properly use encodings
before_filter :params_filter
......
......@@ -85,7 +85,7 @@ class DocumentsController < ApplicationController
if attachments.present? && attachments[:files].present? && Setting.notified_events.include?('document_added')
# TODO: refactor
attachments.first.container.recipients.each do |recipient|
@document.recipients.each do |recipient|
Mailer.deliver_attachments_added(attachments[:files], recipient)
end
end
......
......@@ -17,7 +17,7 @@ require 'cgi'
module ApplicationHelper
include Redmine::I18n
include GravatarHelper::PublicMethods
include Gravatarify::Helper
extend Forwardable
def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter
......@@ -954,6 +954,16 @@ module ApplicationHelper
(@has_content && @has_content[name]) || false
end
# Returns the gravatar image tag for the given email
# +email+ is a string with an email address
def gravatar(email, options={})
gravatarify_options = {}
gravatarify_options[:secure] = options.delete :ssl
[:default, :size, :rating, :filetype].each {|key| gravatarify_options[:key] = options.delete :key}
gravatarify_options[:html] = options
gravatar_tag email, gravatarify_options
end
# Returns the avatar image tag for the given +user+ if avatars are enabled
# +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
def avatar(user, options = { })
......
......@@ -712,7 +712,7 @@ class Issue < ActiveRecord::Base
# The default assumption is that journals have the same permissions
# as the journaled object, issue notes have separate permissions though
def journal_editable_by?(journal, user)
return true if journal.author == user && user.allowed_to?(:edit_own_issue_notes, project)
return true if journal.user == user && user.allowed_to?(:edit_own_issue_notes, project)
user.allowed_to? :edit_issue_notes, project
end
......
......@@ -41,7 +41,6 @@ class Message < ActiveRecord::Base
acts_as_watchable
attr_protected :locked, :sticky
validates_presence_of :board, :subject, :content
validates_length_of :subject, :maximum => 255
......@@ -51,7 +50,7 @@ class Message < ActiveRecord::Base
:conditions => Project.allowed_to_condition(args.first || User.current, :view_messages) } }
safe_attributes 'subject', 'content'
safe_attributes 'locked', 'sticky',
safe_attributes 'locked', 'sticky', 'board_id',
:if => lambda {|message, user|
user.allowed_to?(:edit_messages, message.project)
}
......@@ -81,9 +80,15 @@ class Message < ActiveRecord::Base
end
def after_destroy
parent.reset_last_reply_id! if parent
board.reset_counters!
end
def reset_last_reply_id!
clid = children.present? ? children.last.id : nil
self.update_attribute(:last_reply_id, clid)
end
def sticky=(arg)
write_attribute :sticky, (arg == true || arg.to_s == '1' ? 1 : 0)
end
......
......@@ -27,7 +27,7 @@ class Version < ActiveRecord::Base
validates_presence_of :name
validates_uniqueness_of :name, :scope => [:project_id]
validates_length_of :name, :maximum => 60
validates_format_of :effective_date, :with => /^\d{4}-\d{2}-\d{2}$/, :message => :not_a_date, :allow_nil => true
validates_format_of :start_date, :effective_date, :with => /^\d{4}-\d{2}-\d{2}$/, :message => :not_a_date, :allow_nil => true
validates_inclusion_of :status, :in => VERSION_STATUSES
validates_inclusion_of :sharing, :in => VERSION_SHARINGS
......@@ -37,6 +37,7 @@ class Version < ActiveRecord::Base
safe_attributes 'name',
'description',
'start_date',
'effective_date',
'due_date',
'wiki_page_title',
......
<h2><%=l(:label_register)%> <%=link_to l(:label_login_with_open_id_option), signin_url if Setting.openid? %></h2>
<% form_tag({:action => 'register'}, :class => "tabular") do %>
<% form_tag({:action => 'register'}, :class => "tabular", :autocomplete => :off) do %>
<%= error_messages_for 'user' %>
<div class="box">
......
......@@ -3,8 +3,7 @@
<div class="autoscroll">
<table class="list issues">
<thead><tr>
<th class="checkbox hide-when-print"><%= link_to image_tag('toggle_check.png'), {}, :onclick => 'toggleIssuesSelection(Element.up(this, "form")); return false;',
:title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}" %>
<th class="checkbox hide-when-print"><%= link_to image_tag('toggle_check.png'), {}, :title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}" %>
</th>
<%= sort_header_tag('id', :caption => '#', :default_order => 'desc') %>
<% query.columns.each do |column| %>
......
......@@ -3,5 +3,4 @@
<%= call_hook(:view_issues_sidebar_planning_bottom) %>
<%= render_sidebar_queries %>
<%= call_hook(:view_issues_sidebar_queries_bottom) %>
<p><%= l(:mail_body_wiki_content_added, :id => link_to(h(@wiki_content.page.pretty_title), @wiki_content_url),
:author => h(@wiki_content.author)) %><br />
<em><%=h @wiki_content.comments %></em></p>
<em><%=h @wiki_content.last_journal.notes %></em></p>
<%= l(:mail_body_wiki_content_added, :id => h(@wiki_content.page.pretty_title),
:author => h(@wiki_content.author)) %>
<%= @wiki_content.comments %>
<%= @wiki_content.last_journal.notes %>
<%= @wiki_content_url %>
<p><%= l(:mail_body_wiki_content_updated, :id => link_to(h(@wiki_content.page.pretty_title), @wiki_content_url),
:author => h(@wiki_content.author)) %><br />
<em><%=h @wiki_content.comments %></em></p>
<em><%=h @wiki_content.last_journal.notes %></em></p>
<p><%= l(:label_view_diff) %>:<br />
<%= link_to h(@wiki_diff_url), @wiki_diff_url %></p>
<%= l(:mail_body_wiki_content_updated, :id => h(@wiki_content.page.pretty_title),
:author => h(@wiki_content.author)) %>
<%= @wiki_content.comments %>
<%= @wiki_content.last_journal.notes %>
<%= @wiki_content.page.pretty_title %>:
<%= @wiki_content_url %>
......
......@@ -115,4 +115,32 @@ module ActionController
end
end
end
# Backported fix for CVE-2012-2660
# https://groups.google.com/group/rubyonrails-security/browse_thread/thread/f1203e3376acec0f
# TODO: Remove this once we are on Rails >= 3.2.4
require 'action_controller/request'
class Request
protected
# Remove nils from the params hash
def deep_munge(hash)
hash.each_value do |v|
case v
when Array
v.grep(Hash) { |x| deep_munge(x) }
when Hash
deep_munge(v)
end
end
keys = hash.keys.find_all { |k| hash[k] == [nil] }
keys.each { |k| hash[k] = nil }
hash
end
def parse_query(qs)
deep_munge(super)
end
end
end
......@@ -983,22 +983,22 @@ bg:
description_choose_project: Проекти
description_date_from: Въведете начална дата
label_deleted_custom_field: (изтрито потребителско поле)
field_custom_filter: Custom LDAP filter
text_display_subprojects: Display subprojects
text_current_project: Current project
label_toc: Contents
search_input_placeholder: search ...
setting_mail_handler_confirmation_on_success: Send confirmation email on successful incoming email
label_mail_handler_confirmation: "Confirmation of email submission: %{subject}"
label_mail_handler_errors_with_submission: "There were errors with your email submission:"
label_document_watchers: Watchers
setting_mail_handler_confirmation_on_failure: Send confirmation email on failed incoming email
field_custom_filter: Потребителски LDAP филтър
text_display_subprojects: Показване на подпроекти
text_current_project: Текущ проект
label_toc: Съдържание
search_input_placeholder: търсене ...
setting_mail_handler_confirmation_on_success: Изпращане на е-мейл за потвърждение при успешен входен е-мейл
label_mail_handler_confirmation: "Потвърждение на изпратено с е-мейл: %{subject}"
label_mail_handler_errors_with_submission: "Има грешки във вашия е-мейл:"
label_document_watchers: Наблюдатели
setting_mail_handler_confirmation_on_failure: Изпращане на е-мейл за потвърждение при неуспешен входен е-мейл
label_between: between
label_mail_handler_failure: "Failed email submission: %{subject}"
notice_not_authorized_action: You are not authorized to perform this action.
text_mail_handler_confirmation_successful: Your email has been successful added at the following url
field_issue_summary: Issue summary
field_new_saved_query: New saved query
field_issue_view_all_open: View all open issues
label_subtask_add: Add a subtask
label_issue_hierarchy: Issue hierarchy
label_mail_handler_failure: "Пропаднал е-мейл: %{subject}"
notice_not_authorized_action: Вие нямате разрешение да изпълните това действие.
text_mail_handler_confirmation_successful: Вашият е-мейл беше успешно добавен на следващия адрес
field_issue_summary: Заглавие на задачата
field_new_saved_query: Нова записана заявка
field_issue_view_all_open: Разглеждане на всички отворени задачи
label_subtask_add: Добавяне на подзадача
label_issue_hierarchy: Йерархия на задачите
This diff is collapsed.
......@@ -963,28 +963,28 @@ de:
notice_gantt_chart_truncated: Die Grafik ist unvollständig, da das Maximum der anzeigbaren Aufgaben überschritten wurde (%{max})
setting_gantt_items_limit: Maximale Anzahl von Aufgaben die im Gantt-Chart angezeigt werden.
text_powered_by: Powered by %{link}
label_cvs_module: Module
label_filesystem_path: Root directory
label_darcs_path: Root directory
label_bazaar_path: Root directory
label_cvs_module: Modul
label_filesystem_path: Wurzelverzeichnis
label_darcs_path: Wurzelverzeichnis
label_bazaar_path: Wurzelverzeichnis
label_cvs_path: CVSROOT
label_git_path: Path to .git directory
label_mercurial_path: Root directory
label_my_queries: My custom queries
label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee
text_journal_changed_no_detail: "%{label} updated"
button_expand_all: Expand all
button_collapse_all: Collapse all
label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author
field_effective_date: Due date
label_news_comment_added: Comment added to a news
field_warn_on_leaving_unsaved: Warn me when leaving a page with unsaved text
text_warn_on_leaving_unsaved: The current page contains unsaved text that will be lost if you leave this page.
text_default_encoding: "Default: UTF-8"
text_git_repo_example: a bare and local repository (e.g. /gitrepo, c:\gitrepo)
label_notify_member_plural: Email issue updates
label_path_encoding: Path encoding
text_mercurial_repo_example: local repository (e.g. /hgrepo, c:\hgrepo)
label_git_path: Pfad zum .git Verzeichnis
label_mercurial_path: Wurzelverzeichnis
label_my_queries: Meine benutzerdefinierten Abfragen
label_additional_workflow_transitions_for_assignee: Zusätzliche Workflow-Übergänge wenn das Ticket an den Benutzer zugewiesen ist
text_journal_changed_no_detail: "%{label} aktualisiert"
button_expand_all: Alles ausklappen
button_collapse_all: Alles einklappen
label_additional_workflow_transitions_for_author: Zusätzliche Workflow-Übergänge wenn der Benutzer der Autor ist
field_effective_date: Abshlussdatum
label_news_comment_added: "Kommentar erfolgreich hinzugefügt"
field_warn_on_leaving_unsaved: "Warnen wenn eine Seite mit ungespeichertem Text verlassen wird"
text_warn_on_leaving_unsaved: "Die aktuelle Seite enthält ungespeicherten Text er verloren geht wenn Sie diese Seite verlassen."
text_default_encoding: "Standard: UTF-8"
text_git_repo_example: Ein lokales "bare Repository" (z.B. /gitrepo, c:\gitrepo)
label_notify_member_plural: Benachrichtigungen verschicken
label_path_encoding: Pfadkodierung
text_mercurial_repo_example: Lokales Projektarchiv (z.B. /hgrepo, c:\hgrepo)
label_diff: diff
description_filter: Filter
description_search: Suchfeld
......@@ -1003,19 +1003,19 @@ de:
description_date_range_interval: Zeitraum durch Start- und Enddatum festlegen
description_date_from: Startdatum eintragen
description_date_to: Enddatum eintragen
field_custom_filter: Custom LDAP filter
field_custom_filter: Benutzerdefinierter LDAP-Filter
label_toc: "Inhaltsverzeichnis"
text_display_subprojects: Display subprojects
text_current_project: Current project
setting_mail_handler_confirmation_on_success: Send confirmation email on successful incoming email
label_mail_handler_confirmation: "Confirmation of email submission: %{subject}"
label_mail_handler_errors_with_submission: "There were errors with your email submission:"
label_document_watchers: Watchers
setting_mail_handler_confirmation_on_failure: Send confirmation email on failed incoming email
label_between: between
label_mail_handler_failure: "Failed email submission: %{subject}"
notice_not_authorized_action: You are not authorized to perform this action.
text_mail_handler_confirmation_successful: Your email has been successful added at the following url
field_issue_summary: Issue summary
field_new_saved_query: New saved query
field_issue_view_all_open: View all open issues
text_display_subprojects: Unterprojekte anzeigen
text_current_project: Aktuelles Project
setting_mail_handler_confirmation_on_success: Bestätigungs-E-Mail bei erfolgreich eingegangenen E-Mails versenden
label_mail_handler_confirmation: "Bestätigung der E-Mail-Verarbeitung: %{subject}"
label_mail_handler_errors_with_submission: "Es traten Fehler bei der E-Mail verarbeitung auf:"
label_document_watchers: Beobachter
setting_mail_handler_confirmation_on_failure: Bestätigungs-E-Mail bei fehlgeschlagenen eingehenden E-Mails versenden
label_between: zwischen
label_mail_handler_failure: "E-Mail-Versand fehlgeschlagen: %{subject}"
notice_not_authorized_action: Sie sind für diese Aktion nicht autorisiert.
text_mail_handler_confirmation_successful: Ihre E-Mil wurde erfolgreich zu folgender URL hinzugefügt
field_issue_summary: Ticketübersicht
field_new_saved_query: Neue gespeicherte Abfrage
field_issue_view_all_open: Alle offen Tickets
This diff is collapsed.
......@@ -154,7 +154,7 @@ pt-BR:
general_text_Yes: 'Sim'
general_text_no: 'não'
general_text_yes: 'sim'
general_lang_name: 'Português(Brasil)'
general_lang_name: 'Português (Brasil)'
general_csv_separator: ';'
general_csv_decimal_separator: ','
general_csv_encoding: ISO-8859-1
......@@ -245,7 +245,7 @@ pt-BR:
field_role: Cargo
field_homepage: Página do projeto
field_is_public: Público
field_parent: Sub-projeto de
field_parent: Subprojeto de
field_is_in_roadmap: Exibir no planejamento
field_login: Usuário
field_mail_notification: Notificações por e-mail
......@@ -273,7 +273,7 @@ pt-BR:
field_comments: Comentário
field_url: URL
field_start_page: Página inicial
field_subproject: Sub-projeto
field_subproject: Subprojeto
field_hours: Horas
field_activity: Atividade
field_spent_on: Data
......@@ -411,8 +411,8 @@ pt-BR:
label_auth_source: Modo de autenticação
label_auth_source_new: Novo modo de autenticação
label_auth_source_plural: Modos de autenticação
label_subproject_plural: Sub-projetos
label_and_its_subprojects: "%{value} e seus sub-projetos"
label_subproject_plural: Subprojetos
label_and_its_subprojects: "%{value} e seus subprojetos"
label_min_max_length: Tamanho mín-máx
label_list: Lista
label_date: Data
......@@ -879,7 +879,7 @@ pt-BR:
field_sharing: Compartilhamento
label_version_sharing_hierarchy: Com a hierarquia do projeto
label_version_sharing_system: Com todos os projetos
label_version_sharing_descendants: Com sub-projetos
label_version_sharing_descendants: Com subprojetos
label_version_sharing_tree: Com a árvore do projeto
label_version_sharing_none: Sem compartilhamento
error_can_not_archive_project: Este projeto não pode ser arquivado
......@@ -989,7 +989,7 @@ pt-BR:
label_mercurial_path: Diretório raiz
label_diff: diff
description_search: Searchfield
description_search: Campo de pesquisa
description_user_mail_notification: Configuração de notificações por e-mail
description_date_range_list: Escolha um período a partira da lista
description_date_to: Digite a data final
......@@ -1008,21 +1008,21 @@ pt-BR:
description_date_from: Digita a data inicial
label_deleted_custom_field: (campo personalizado excluído)
field_custom_filter: Custom LDAP filter
text_display_subprojects: Display subprojects
text_current_project: Current project
label_toc: Contents
search_input_placeholder: search ...
setting_mail_handler_confirmation_on_success: Send confirmation email on successful incoming email
label_mail_handler_confirmation: "Confirmation of email submission: %{subject}"
label_mail_handler_errors_with_submission: "There were errors with your email submission:"
label_document_watchers: Watchers
setting_mail_handler_confirmation_on_failure: Send confirmation email on failed incoming email
text_display_subprojects: Exibir subprojetos
text_current_project: Projeto atual
label_toc: Conteúdo
search_input_placeholder: pesquisar ...
setting_mail_handler_confirmation_on_success: Enviar confirmação ao receber um e-mail
label_mail_handler_confirmation: "Confirmação de envio de e-mail: %{subject}"
label_mail_handler_errors_with_submission: "Seu envio de email falhou:"
label_document_watchers: Observadores
setting_mail_handler_confirmation_on_failure: Enviar confirmação ao falhar o recebimento de um e-mail
label_between: between
label_mail_handler_failure: "Failed email submission: %{subject}"
notice_not_authorized_action: You are not authorized to perform this action.
text_mail_handler_confirmation_successful: Your email has been successful added at the following url
field_issue_summary: Issue summary
field_new_saved_query: New saved query
field_issue_view_all_open: View all open issues
label_subtask_add: Add a subtask
label_issue_hierarchy: Issue hierarchy
label_mail_handler_failure: "Envio de e-mail falhou: %{subject}"
notice_not_authorized_action: Você não tem permissão para fazer isto.
text_mail_handler_confirmation_successful: Seu e-mail foi adicionado com sucesso a URL a seguir
field_issue_summary: Relatório
field_new_saved_query: Nova consulta
field_issue_view_all_open: Ver tarefas abertas
label_subtask_add: Adicionar
label_issue_hierarchy: Subtarefas
This diff is collapsed.
......@@ -12,6 +12,8 @@
# See doc/COPYRIGHT.rdoc for more details.
#++
require 'wiki_content'
class MergeWikiVersionsWithJournals < ActiveRecord::Migration
# This is provided here for migrating up after the WikiContent::Version class has been removed
class WikiContent < ActiveRecord::Base
......
= ChiliProject Changelog
== 2012-06-09 v3.2.0
* Bug #844: Set autocomplete=off for some fields in Registration form
* Bug #863: missing journals fixture at test/unit/issue_test.rb
* Bug #950: jQuery AJAX requests don't include CSRF token
* Bug #966: "edit own notes" fails since 3.1.0
* Bug #967: Menu - Missing translations (French)
* Bug #968: Forum threads aren't always displaying "Last Message"
* Bug #969: Forum URLs in the menu are missing the project_id
* Bug #970: Long version titles extend outside the group menu when expanding Roadmap
* Bug #974: menu link broken in duplicate issue mode
* Bug #975: Start Date is not saved for Versions
* Bug #984: Cannot unlock a forum thread
* Bug #986: Notification Mail for Wiki-Changes doesn't contain change comment
* Bug #994: select all in issue list isn't working
* Bug #1007: Right clicking on item in roadmap displays menu at incorrect position
* Bug #1008: error 500 when uploading a new file to an existing document
* Bug #1024: Select multiple issues with shift key in issue list
* Bug #1025: Rails: Unsafe Query Generation Risk in Ruby on Rails (CVE-2012-2660)
* Bug #1033: Replace vendored gravatar lib by gem
* Feature #749: Git Integration: Property Main Branch
* Feature #947: Reformat the CSS files to use a standard
* Feature #983: Bulgarian translation of several strings
* Feature #988: Swedish translation of several strings
* Feature #1016: Limit height of project drop down menu
* Task #982: Updated czech localization for chiliproject 3.1
== 2012-04-04 v3.1.0
* Bug #739: Relative textile links not converted to full URLs in emails
......
.icon-example-works { background-image: url(../images/it_works.png); }
.icon-example-works {
background-image: url(../images/it_works.png);
}
......@@ -18,7 +18,7 @@ module ChiliProject
module VERSION #:nodoc:
MAJOR = 3
MINOR = 1
MINOR = 2
PATCH = 0
TINY = PATCH # Redmine compat
......
......@@ -259,7 +259,7 @@ Redmine::MenuManager.map :project_menu do |menu|
:caption => :label_issue_plural,
:children => issue_query_proc
})
menu.push(:new_issue, { :controller => 'issues', :action => 'new' }, {
menu.push(:new_issue, { :controller => 'issues', :action => 'new', :copy_from => nil }, {
:param => :project_id,
:caption => :label_issue_new,
:parent => :issues,
......@@ -345,7 +345,7 @@ Redmine::MenuManager.map :project_menu do |menu|
project.boards.collect do |board|
Redmine::MenuManager::MenuItem.new(
"board-#{board.id}".to_sym,
{ :controller => 'boards', :action => 'show', :id => board },
{ :controller => 'boards', :action => 'show', :project_id => project, :id => board },
{
:caption => board.name # is h() in menu_helper.rb
})
......
......@@ -100,6 +100,13 @@ module Redmine
def default_branch
bras = self.branches
return nil if bras.nil?
head = nil
scm_cmd('symbolic-ref', 'HEAD') do |io|
head = io.readline
end
/^refs\/heads\/(.*)$/.match(head)
bras.include?($1) ? $1 : bras.first
rescue ScmCommandAborted, EOFError
bras.include?('master') ? 'master' : bras.first
end
......
......@@ -271,7 +271,8 @@ module Redmine
end
blame
rescue HgCommandAborted
nil # means not found or cannot be annotated
# means not found or cannot be annotated
Annotate.new
end
class Revision < Redmine::Scm::Adapters::Revision
......
......@@ -24,6 +24,41 @@ end
# Tasks can be hooked into by redefining them in a plugin
namespace :ci do
namespace :travis do
desc "Prepare a TRAVIS run"
task :prepare do
ENV['RAILS_ENV'] = 'test'
RAILS_ENV = 'test'
database_yml = {"database" => "chiliproject_test"}
database_yml.merge! case ENV['DB']
when 'mysql'
{"adapter" => "mysql", "username" => "root"}
when 'mysql2'
{"adapter" => "mysql2", "username" => "root"}
when 'postgres'
{"adapter" => "postgresql", "username" => "postgres"}
when 'sqlite'
{"adapter" => "sqlite3", "database" => "db/test.sqlite3"}
end
File.open("config/database.yml", 'w') do |f|
YAML.dump({"test" => database_yml}, f)
end
Rake::Task["generate_session_store"].invoke
# Create and migrate the database
Rake::Task["db:create"].invoke
Rake::Task["db:migrate"].invoke
Rake::Task["db:migrate:plugins"].invoke
Rake::Task["db:schema:dump"].invoke
# Create test repositories
Rake::Task["test:scm:setup:all"].invoke
end
end
desc "Setup Redmine for a new build."
task :setup do
Rake::Task["ci:dump_environment"].invoke
......
......@@ -467,7 +467,15 @@ jQuery.viewportHeight = function() {
// Automatically use format.js for jQuery Ajax
jQuery.ajaxSetup({
'beforeSend': function(xhr) {xhr.setRequestHeader("Accept", "text/javascript")}
'beforeSend': function(xhr) {
xhr.setRequestHeader("Accept", "text/javascript");
// TODO: Remove once jquery-rails (Rails 3) has been added a dependency
var csrf_meta_tag = jQuery('meta[name="csrf-token"]');
if (csrf_meta_tag) {
xhr.setRequestHeader('X-CSRF-Token', csrf_meta_tag.attr('content'));
}
}
})
/* TODO: integrate with existing code and/or refactor */
......@@ -563,6 +571,13 @@ jQuery(document).ready(function($) {
// Click on the menu header with a dropdown menu
$('#account-nav .drop-down').live('click', function(event) {
var menuItem = $(this);
var menuUl = menuItem.find('> ul');
menuUl.css('height', 'auto');
if(menuUl.height() > $.viewportHeight()) {
var windowHeight = $.viewportHeight() - 150;
menuUl.css({'height': windowHeight});
}
toggleTopMenu(menuItem);
......
......@@ -3,6 +3,7 @@
var element = el;
var opts = options;
var observingContextMenuClick;
var observingToggleAllClick;
var lastSelected = null;
var menu;
var menuId = 'context-menu';
......@@ -68,18 +69,17 @@
if (lastSelected !== null)
{
var toggling = false;
var rows = $(selectorName);
for (i = 0; i < rows.length; i++)
{
if (toggling || rows[i] == tr)
{
methods.addSelection(rows[i]);
var rows = $('.' + selectorName);
rows.each(function() {
var self = $(this);
if(toggling || (self.get(0) == tr.get(0))) {
methods.addSelection(self);
}
if (rows[i] == tr || rows[i] == lastSelected)
{
if(((self.get(0) == tr.get(0)) || (self.get(0) == lastSelected.get(0)))
&& (tr.get(0) !== lastSelected.get(0))) {
toggling = !toggling;
}
}
});
} else {
methods.addSelection(tr);
}
......@@ -153,7 +153,7 @@
}
if(maxHeight > $(window).height()) {
renderY =+ menu.height();
renderY -= menu.height();
menu.addClass(reverseYClass);
} else {
menu.removeClass(reverseYClass);
......@@ -174,6 +174,7 @@
addSelection: function(element) {
element.addClass(contextMenuSelectionClass);
methods.checkSelectionBox(element, true);
methods.clearDocumentSelection();
},
isSelected: function(element) {
return element.hasClass(contextMenuSelectionClass);
......@@ -194,6 +195,27 @@
inputs.each(function() {
inputs.attr('checked', checked ? 'checked' : false);
});
},
toggleIssuesSelection: function(e) {
e.preventDefault();
e.stopPropagation();
var issues = $(this).parents('form').find('tr.issue');
var checked = methods.isSelected(issues.eq(0));
issues.each(function() {
var self = $(this);
if(checked) {
methods.removeSelection(self)
} else {
methods.addSelection(self);
}
});
},
clearDocumentSelection: function() {
if(document.selection) {
document.selection.clear();
} else {
window.getSelection().removeAllRanges();
}
}
};
......@@ -205,6 +227,11 @@
observingContextMenuClick = true;
}
if(!observingToggleAllClick) {
element.find('.issues img[alt="Toggle_check"]').bind('click', methods.toggleIssuesSelection);
observingToggleAllClick = true;
}
methods.unselectAll();
};
......
This diff is collapsed.
/* The main calendar widget. DIV containing a table. */
img.calendar-trigger {
cursor: pointer;
vertical-align: middle;
margin-left: 4px;
}
div.calendar { position: relative; z-index: 30;}
div.calendar {
position: relative;
z-index: 30;
}
div.calendar, div.calendar table {
div.calendar,
div.calendar table {
border: 1px solid #556;
font-size: 11px;
color: #000;
......@@ -18,7 +21,6 @@ div.calendar, div.calendar table {
}
/* Header part -- contains navigation buttons and day names. */
div.calendar .button { /* "<<", "<", ">", ">>" buttons have this class */
text-align: center; /* They are the navigation buttons */
padding: 2px; /* Make the buttons seem like they're pressing */
......@@ -68,17 +70,18 @@ div.calendar thead .active { /* Active (pressed) buttons in header */
}
/* The body part -- contains all the days in month. */
div.calendar tbody .day { /* Cells <TD> containing month days dates */
width: 2em;
color: #456;
text-align: right;
padding: 2px 4px 2px 2px;
}
div.calendar tbody .day.othermonth {
font-size: 80%;
color: #bbb;
}
div.calendar tbody .day.othermonth.oweekend {
color: #fbb;
}
......@@ -125,7 +128,9 @@ div.calendar tbody td.today { /* Cell showing selected date */
color: #f00;
}
div.calendar tbody .disabled { color: #999; }
div.calendar tbody .disabled {
color: #999;
}
div.calendar tbody .emptycell { /* Empty cells (the best is to hide them) */
visibility: hidden;
......@@ -136,7 +141,6 @@ div.calendar tbody .emptyrow { /* Empty row (some months need less than 6 rows)
}
/* The footer part -- status bar and "Close" button */
div.calendar tfoot .footrow { /* The <TR> in footer (only one right now) */
text-align: center;
background: #556;
......@@ -163,7 +167,6 @@ div.calendar tfoot .active { /* Active (pressed) style for buttons in footer */
}
/* Combo boxes (menus that display months/years for direct selection) */
div.calendar .combo {
position: absolute;
display: none;
......
#context-menu { position: absolute; z-index: 40; }
#context-menu {
position: absolute;
z-index: 40;
}
#context-menu ul, #context-menu li, #context-menu a {
display:block;
margin:0;
padding:0;
border:0;
#context-menu ul,
#context-menu li,
#context-menu a {
display: block;
margin: 0;
padding: 0;
border: 0;
}
#context-menu ul {
width:150px;
border-top:1px solid #ddd;
border-left:1px solid #ddd;
border-bottom:1px solid #777;
border-right:1px solid #777;
background:white;
list-style:none;
width: 150px;
border-top: 1px solid #ddd;
border-left: 1px solid #ddd;
border-bottom: 1px solid #777;
border-right: 1px solid #777;
background: white;
list-style: none;
}
#context-menu li {
position:relative;
padding:1px;
z-index:39;
border:1px solid white;
position: relative;
padding: 1px;
z-index: 39;
border: 1px solid white;
}
#context-menu li.folder ul {
position: absolute;
left: 168px; /* ie6 */
top: -2px;
max-height: 300px;
overflow: hidden;
overflow-y: auto;
}
#context-menu li.folder ul { position:absolute; left:168px; /* IE6 */ top:-2px; max-height:300px; overflow:hidden; overflow-y: auto; }
#context-menu li.folder>ul { left:148px; }
#context-menu.reverse-y li.folder>ul { top:auto; bottom:0; }
#context-menu.reverse-x li.folder ul { left:auto; right:168px; /* IE6 */ }
#context-menu.reverse-x li.folder>ul { right:148px; }
#context-menu li.folder > ul {
left: 148px;
}
#context-menu.reverse-y li.folder > ul {
top: auto;
bottom: 0;
}
#context-menu.reverse-x li.folder ul {
left: auto;
right: 168px; /* IE6 */
}
#context-menu.reverse-x li.folder > ul {
right: 148px;
}
#context-menu a {
text-decoration:none !important;
text-decoration: none !important;
background-repeat: no-repeat;
background-position: 1px 50%;
padding: 1px 0px 1px 20px;
width:100%; /* IE */
width: 100%; /* IE */
}
#context-menu li > a {
width: auto;
}
/* others */
#context-menu a.disabled,
#context-menu a.disabled:hover {
color: #ccc;
}
#context-menu li:hover {
border: 1px solid gray;
background-color: #eee;
}
#context-menu a:hover {
color: #2A5685;
}
#context-menu li.folder:hover {
z-index: 40;
}
#context-menu ul ul,
#context-menu li:hover ul ul {
display: none;
}
#context-menu li:hover ul,
#context-menu li:hover li:hover ul {
display: block;
}
#context-menu li>a { width:auto; } /* others */
#context-menu a.disabled, #context-menu a.disabled:hover {color: #ccc;}
#context-menu li:hover { border:1px solid gray; background-color:#eee; }
#context-menu a:hover {color:#2A5685;}
#context-menu li.folder:hover { z-index:40; }
#context-menu ul ul, #context-menu li:hover ul ul { display:none; }
#context-menu li:hover ul, #context-menu li:hover li:hover ul { display:block; }
/* selected element */
.context-menu-selection { background-color:#507AAA !important; color:#000 !important; }
.context-menu-selection:hover { background-color:#507AAA !important; color:#000 !important; }
.context-menu-selection {
background-color: #507AAA !important;
color: #000 !important;
}
.context-menu-selection:hover {
background-color: #507AAA !important;
color: #000 !important;
}
#context-menu .submenu {
border: transparent solid 5px;
......
#context-menu li.folder ul { left:auto; right:168px; }
#context-menu li.folder>ul { left:auto; right:148px; }
#context-menu li a.submenu { background:url("../images/bullet_arrow_left.png") left no-repeat; }
#context-menu li.folder ul {
left: auto;
right: 168px;
}
#context-menu li.folder > ul {
left: auto;
right: 148px;
}
#context-menu li a.submenu {
background: url("../images/bullet_arrow_left.png") left no-repeat;
}
#context-menu a {
background-position: 100% 40%;
......
/* IE6 how i love to hate thee */
#account-nav li a {
width:45px;
width: 45px;
}
#account-nav li li a {
width:150px;
width: 150px;
}
.title-bar {
zoom:1;
zoom: 1;
}
.title-bar-extras label {
float:none;
display:inline;
padding-right:10px;
float: none;
display: inline;
padding-right: 10px;
}
.issue-dropdown li.hover {
background-color:#fff;
background-color: #fff;
}
.issue-dropdown li.hover ul {
display:block;
left:112px;
display: block;
left: 112px;
}
body .file-thumbs a {
width:150px;
width: 150px;
}
#history .journal {
zoom:1;
zoom: 1;
}
body #history .wiki {
overflow:hidden;
zoom:1;
overflow: hidden;
zoom: 1;
}
#main-menu li li {
height:30px;
height: 30px;
}
#main-menu li li li {
height:auto;
height: auto;
}
a.has-thumb.active {
background:none;
background: none;
}
.title-bar-extras ul {
background-image:none;
border-top:1px solid #154E5D;
background-image: none;
border-top: 1px solid #154E5D;
}
/* These will be included for IE6 & IE7 */
.title-bar h2 {
height:21px;
height: 21px;
}
td.dropdown {
z-index:50;
position:relative;
z-index: 50;
position: relative;
}
body .title-bar-extras {
overflow:hidden;
overflow: hidden;
}
#main-menu, .title-bar {
z-index:4;
#main-menu,
.title-bar {
z-index: 4;
}
.title-bar .button-large ul {
z-index:15;
z-index: 15;
}
form.tooltip-active {
z-index:14;
z-index: 14;
}
#main-menu li li a span {
position:absolute;
right:0;
top:0;
display:block;
position: absolute;
right: 0;
top: 0;
display: block;
}
#main-menu li li li a span {
right:10px;
right: 10px;
}
body .file-thumbs a {
max-width:150px;
max-width: 150px;
}
#watchers {
position:relative;
z-index:5;
position: relative;
z-index: 5;
}
div.attachments {
position:relative;
z-index:4;
position: relative;
z-index: 4;
}
fieldset.collapsible.header_collapsible legend {
margin-left:-15px;
margin-left: -15px;
}
.jstEditor {
padding-left: 0px;
}
.jstEditor textarea, .jstEditor iframe {
margin: 0;
}
......@@ -17,80 +18,106 @@
}
.jstElements button {
margin-right : 6px;
width : 24px;
margin-right: 6px;
width: 24px;
height: 24px;
padding: 4px;
border-style: solid;
border-width: 1px;
border-color: #ddd;
background-color : #f7f7f7;
background-position : 50% 50%;
background-color: #f7f7f7;
background-position: 50% 50%;
background-repeat: no-repeat;
}
.jstElements button:hover {
border-color : #000;
border-color: #000;
}
.jstElements button span {
display : none;
display: none;
}
.jstElements span {
display : inline;
display: inline;
}
.jstSpacer {
width : 0px;
width: 0px;
font-size: 1px;
margin-right: 4px;
}
.jstElements .help { float: right; margin-right: 0.5em; padding-top: 8px; font-size: 0.9em; }
.jstElements .help a {padding: 2px 0 2px 20px; background: url(../images/help.png) no-repeat 0 50%;}
.jstElements .help {
float: right;
margin-right: 0.5em;
padding-top: 8px;
font-size: 0.9em;
}
.jstElements .help a {
padding: 2px 0 2px 20px;
background: url(../images/help.png) no-repeat 0 50%;
}
/* Buttons
-------------------------------------------------------- */
.jstb_strong {
background-image: url(../images/jstoolbar/bt_strong.png);
}
.jstb_em {
background-image: url(../images/jstoolbar/bt_em.png);
}
.jstb_ins {
background-image: url(../images/jstoolbar/bt_ins.png);
}
.jstb_del {
background-image: url(../images/jstoolbar/bt_del.png);
}
.jstb_code {
background-image: url(../images/jstoolbar/bt_code.png);
}
.jstb_h1 {
background-image: url(../images/jstoolbar/bt_h1.png);
}
.jstb_h2 {
background-image: url(../images/jstoolbar/bt_h2.png);
}
.jstb_h3 {
background-image: url(../images/jstoolbar/bt_h3.png);
}
.jstb_ul {
background-image: url(../images/jstoolbar/bt_ul.png);
}
.jstb_ol {
background-image: url(../images/jstoolbar/bt_ol.png);
}
.jstb_bq {
background-image: url(../images/jstoolbar/bt_bq.png);
}
.jstb_unbq {
background-image: url(../images/jstoolbar/bt_bq_remove.png);
}
.jstb_pre {
background-image: url(../images/jstoolbar/bt_pre.png);
}
.jstb_link {
background-image: url(../images/jstoolbar/bt_link.png);
}
.jstb_img {
background-image: url(../images/jstoolbar/bt_img.png);
}
/***** Media print specific styles *****/
@media print {
#top-menu, #header, #main-menu, #sidebar, #footer, .contextual, .other-formats { display:none; }
#main { background: #fff; }
#content { width: 99%; margin: 0; padding: 0; border: 0; background: #fff; overflow: visible !important;}
#wiki_add_attachment { display:none; }
.hide-when-print { display: none; }
.autoscroll {overflow-x: visible;}
table.list {margin-top:0.5em;}
table.list th, table.list td {border: 1px solid #aaa;}
#top-menu,
#header,
#main-menu,
#sidebar,
#footer,
.contextual,
.other-formats {
display:none;
}
#main {
background: #fff;
}
#content {
width: 99%;
margin: 0;
padding: 0;
border: 0;
background: #fff;
overflow: visible !important;
}
#wiki_add_attachment {
display:none;
}
.hide-when-print {
display: none;
}
.autoscroll {
overflow-x: visible;
}
table.list {
margin-top:0.5em;
}
table.list th,
table.list td {
border: 1px solid #aaa;
}
}
......@@ -2,8 +2,68 @@
* CSS Reset
* Based on http://meyerweb.com/eric/tools/css/reset/
*/
html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, font, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td {
html,
body,
div,
span,
applet,
object,
iframe,
h1,
h2,
h3,
h4,
h5,
h6,
p,
blockquote,
pre,
a,
abbr,
acronym,
address,
big,
cite,
code,
del,
dfn,
em,
font,
img,
ins,
kbd,
q,
s,
samp,
small,
strike,
strong,
sub,
sup,
tt,
var,
b,
u,
i,
center,
dl,
dt,
dd,
ol,
ul,
li,
fieldset,
form,
label,
legend,
table,
caption,
tbody,
tfoot,
thead,
tr,
th,
td {
margin: 0;
padding: 0;
border: 0;
......@@ -12,16 +72,20 @@ html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockq
vertical-align: baseline;
background: transparent;
}
body {
line-height: 1;
}
ol, ul {
ol,
ul {
list-style: none;
}
ins {
text-decoration: underline;
}
del {
text-decoration: line-through;
}
......@@ -34,5 +98,5 @@ table {
/* Helper classes */
.clearfix {
clear:both;
clear: both;
}
body, #wrapper { direction: rtl;}
#quick-search { float: left; }
#main-menu { margin-left: -500px; left: auto; right: 6px; margin-right: 0px;}
#main-menu li { float: right; }
#top-menu ul { float: right; }
#account { float: left; }
#top-menu #loggedas { float: left; }
#top-menu li { float: right; }
.tabular label.floating
{
body,
#wrapper {
direction: rtl;
}
#quick-search {
float: left;
}
#main-menu {
margin-left: -500px;
left: auto;
right: 6px;
margin-right: 0px;
}
#main-menu li {
float: right;
}
#top-menu ul {
float: right;
}
#account {
float: left;
}
#top-menu #loggedas {
float: left;
}
#top-menu li {
float: right;
}
.tabular label.floating {
margin-right: 0;
margin-left: auto;
text-align: right;
}
.tabular label
{
.tabular label {
float: right;
margin-left: auto;
}
.tabular p
{
.tabular p {
clear: right;
}
.tabular label.block { text-align: right; }
.icon
{
.tabular label.block {
text-align: right;
}
.icon {
background-position: 100% 40%;
padding-right: 20px;
padding-left: 0px;
}
div#activity dt, #search-results dt
{
div#activity dt,
#search-results dt {
background-position: 100% 50%;
padding-right: 20px;
padding-left: 0px;
}
#content .tabs ul li { float: right; }
#content .tabs ul { padding-left: auto; padding-right: 1em; }
table.progress { float: right; }
.contextual { float: left; }
.icon22 { background-position: 100% 40%; padding-right: 26px; padding-left: auto; }
h3, .wiki h2 { padding: 10px 2px 1px 0; }
.tooltip span.tip { text-align: right; }
tr.issue td.subject { text-align: right; }
tr.time-entry td.subject, tr.time-entry td.comments { text-align: right; }
#sidebar { float: left; }
#main.nosidebar #content { border-width: 1px; border-style: solid; border-color: #D7D7D7 #BBBBBB #BBBBBB #D7D7D7;}
.tabular.settings label { margin-left: auto; }
.splitcontentleft { float: right; }
.splitcontentright { float: left; }
p.progress-info { clear: right; }
table.list td.buttons a { padding-right: 20px; }
.filecontent { direction: ltr; }
.entries { direction: ltr; }
.changeset-changes { direction: ltr; padding-left: 2em }
.changesets { direction: ltr; }
div#issue-changesets { float: left; margin-right: 1em; margin-left: 0 }
div#issue-changesets div.wiki { direction: ltr; padding-left: 2em }
#activity dt, .journal { clear: right; }
.journal-link { float: left; }
div.wiki pre { direction: ltr; }
#content .tabs ul li {
float: right;
}
#content .tabs ul {
padding-left: auto;
padding-right: 1em;
}
table.progress {
float: right;
}
.contextual {
float: left;
}
.icon22 {
background-position: 100% 40%;
padding-right: 26px;
padding-left: auto;
}
h3,
.wiki h2 {
padding: 10px 2px 1px 0;
}
.tooltip span.tip {
text-align: right;
}
tr.issue td.subject {
text-align: right;
}
tr.time-entry td.subject,
tr.time-entry td.comments {
text-align: right;
}
#sidebar {
float: left;
}
#main.nosidebar #content {
border-width: 1px;
border-style: solid;
border-color: #D7D7D7 #BBBBBB #BBBBBB #D7D7D7;
}
.tabular.settings label {
margin-left: auto;
}
.splitcontentleft {
float: right;
}
.splitcontentright {
float: left;
}
p.progress-info {
clear: right;
}
table.list td.buttons a {
padding-right: 20px;
}
.filecontent {
direction: ltr;
}
.entries {
direction: ltr;
}
.changeset-changes {
direction: ltr;
padding-left: 2em;
}
.changesets {
direction: ltr;
}
div#issue-changesets {
float: left;
margin-right: 1em;
margin-left: 0;
}
div#issue-changesets div.wiki {
direction: ltr;
padding-left: 2em;
}
#activity dt,
.journal {
clear: right;
}
.journal-link {
float: left;
}
div.wiki pre {
direction: ltr;
}
This diff is collapsed.
......@@ -147,6 +147,28 @@ LOREM
end
end
context "#add_attachment" do
setup do
@request.session[:user_id] = 2
set_tmp_attachments_directory
@document = Document.generate!(:project => Project.find('ecookbook'),
:title => 'Test')
end
should "send a notification mail" do
ActionMailer::Base.deliveries.clear
Setting.notified_events = Setting.notified_events.dup << 'document_added'
post :add_attachment,
:id => @document.id,
:attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}}
@document.reload
assert_not_nil @document
assert_equal 2, ActionMailer::Base.deliveries.size
end
end
def test_destroy
@request.session[:user_id] = 2
post :destroy, :id => 1
......
......@@ -121,6 +121,30 @@ class MessagesControllerTest < ActionController::TestCase
assert_equal 'New body', message.content
end
def test_post_edit_sticky_and_locked
@request.session[:user_id] = 2
post :edit, :board_id => 1, :id => 1,
:message => { :subject => 'New subject',
:content => 'New body',
:locked => '1',
:sticky => '1'}
assert_redirected_to '/boards/1/topics/1'
message = Message.find(1)
assert_equal true, message.sticky?
assert_equal true, message.locked?
end
def test_post_edit_should_allow_to_change_board
@request.session[:user_id] = 2
post :edit, :board_id => 1, :id => 1,
:message => { :subject => 'New subject',
:content => 'New body',
:board_id => 2}
assert_redirected_to '/boards/2/topics/1'
message = Message.find(1)
assert_equal Board.find(2), message.board
end
def test_reply
@request.session[:user_id] = 2
post :reply, :board_id => 1, :id => 1, :reply => { :content => 'This is a test reply', :subject => 'Test reply' }
......
......@@ -131,6 +131,10 @@ class ProjectEnumerationsControllerTest < ActionController::TestCase
end
def test_update_when_creating_new_activities_will_not_convert_existing_data_if_an_exception_is_raised
# SQLite doesn't support nested transactions, thus we can't test transaction-
# based features in a test wrapped in a transaction.
return if ChiliProject::Database.sqlite?
# TODO: Need to cause an exception on create but these tests
# aren't setup for mocking. Just create a record now so the
# second one is a dupicate
......
......@@ -112,13 +112,18 @@ class VersionsControllerTest < ActionController::TestCase
def test_post_update
@request.session[:user_id] = 2
today = Date.today
put :update, :id => 2,
:version => { :name => 'New version name',
:effective_date => Date.today.strftime("%Y-%m-%d")}
:start_date => today.yesterday.strftime("%Y-%m-%d"),
:effective_date => today.strftime("%Y-%m-%d"),
}
assert_redirected_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => 'ecookbook'
version = Version.find(2)
assert_equal 'New version name', version.name
assert_equal Date.today, version.effective_date
assert_equal today.yesterday, version.start_date
assert_equal today, version.effective_date
end
def test_post_update_with_validation_failure
......
......@@ -21,6 +21,7 @@ class IssueTest < ActiveSupport::TestCase
:issue_statuses, :issue_categories, :issue_relations, :workflows,
:enumerations,
:issues,
:journals,
:custom_fields, :custom_fields_projects, :custom_fields_trackers, :custom_values,
:time_entries
......
......@@ -14,7 +14,9 @@
require File.expand_path('../../test_helper', __FILE__)
class JournalTest < ActiveSupport::TestCase
fixtures :issues, :issue_statuses, :journals, :enumerations
fixtures :issues, :issue_statuses, :journals, :enumerations,\
:users, :trackers, :projects, :members, :member_roles, :roles,\
:enabled_modules
def setup
@journal = IssueJournal.find(1)
......@@ -35,23 +37,17 @@ class JournalTest < ActiveSupport::TestCase
def test_create_should_send_email_notification
ActionMailer::Base.deliveries.clear
issue = Issue.find(:first)
if issue.journals.empty?
issue.init_journal(User.current, "This journal represents the creationa of journal version 1")
issue.save
end
user = User.find(:first)
issue = issues :issues_001
assert_equal 0, ActionMailer::Base.deliveries.size
issue.reload
issue.update_attribute(:subject, "New subject to trigger automatic journal entry")
assert_equal 2, ActionMailer::Base.deliveries.size
end
def test_create_should_not_send_email_notification_if_told_not_to
ActionMailer::Base.deliveries.clear
issue = Issue.find(:first)
user = User.find(:first)
issue = issues :issues_001
user = users :users_001
journal = issue.init_journal(user, "A note")
JournalObserver.instance.send_notification = false
......
......@@ -231,6 +231,13 @@ begin
end
end
def test_default_branch
@adapter.send :scm_cmd, 'branch', '-m', 'master', 'non-master-default-branch'
assert_equal 'non-master-default-branch', @adapter.default_branch
ensure
@adapter.send :scm_cmd, 'branch', '-m', 'non-master-default-branch', 'master'
end
private
def test_scm_version_for(scm_command_version, version)
......
......@@ -101,6 +101,18 @@ class MessageTest < ActiveSupport::TestCase
# Watchers removed
end
def test_destroy_last_reply
message = Message.find(4)
last_reply = message.last_reply
penultimate_reply = message.children[-2]
assert last_reply.destroy
message.reload
assert_equal penultimate_reply, message.last_reply
end
def test_destroy_reply
message = Message.find(5)
board = message.board
......
......@@ -17,7 +17,7 @@ class NewsTest < ActiveSupport::TestCase
fixtures :projects, :users, :roles, :members, :member_roles, :enabled_modules, :news
def valid_news
{ :title => 'Test news', :description => 'Lorem ipsum etc', :author => User.find(:first) }
{ :title => 'Test news', :description => 'Lorem ipsum etc', :author => users(:users_001) }
end
......@@ -28,7 +28,7 @@ class NewsTest < ActiveSupport::TestCase
def test_create_should_send_email_notification
ActionMailer::Base.deliveries.clear
Setting.notified_events = Setting.notified_events.dup << 'news_added'
news = Project.find(:first).news.new(valid_news)
news = projects(:projects_001).news.new(valid_news)
assert news.save
assert_equal 2, ActionMailer::Base.deliveries.size
......
......@@ -82,7 +82,7 @@ class ProjectTest < ActiveSupport::TestCase
end
assert_equal Tracker.all, Project.new.trackers
assert_equal Tracker.find(1, 3), Project.new(:tracker_ids => [1, 3]).trackers
assert_equal Tracker.find(1, 3).sort_by(&:id), Project.new(:tracker_ids => [1, 3]).trackers.sort_by(&:id)
end
def test_update
......
Copyright (c) 2007 West Arete Computing, Inc.
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.
\ No newline at end of file
== Gravatar Plugin
This plugin provides a handful of view helpers for displaying gravatars
(globally-recognized avatars).
Gravatars allow users to configure an avatar to go with their email address at
a central location: http://gravatar.com. Gravatar-aware websites (such
as yours) can then look up and display each user's preferred avatar, without
having to handle avatar management. The user gets the benefit of not having to
set up an avatar for each site that they post on.
== Installation
cd ~/myapp
ruby script/plugin install git://github.com/woods/gravatar-plugin.git
or, if you're using piston[http://piston.rubyforge.org] (worth it!):
cd ~/myapp/vendor/plugins
piston import git://github.com/woods/gravatar-plugin.git
== Example
If you represent your users with a model that has an +email+ method (typical
for most rails authentication setups), then you can simply use this method
in your views:
<%= gravatar_for @user %>
This will be replaced with the full HTML +img+ tag necessary for displaying
that user's gravatar.
Other helpers are documented under GravatarHelper::PublicMethods.
== Acknowledgments
Thanks to Magnus Bergmark (http://github.com/Mange), who contributed the SSL
support in this plugin, as well as a few minor fixes.
The following people have also written gravatar-related Ruby libraries:
* Seth Rasmussen created the gravatar gem[http://gravatar.rubyforge.org]
* Matt McCray has also created a gravatar
plugin[http://mattmccray.com/svn/rails/plugins/gravatar_helper]
== Author
Scott A. Woods
West Arete Computing, Inc.
http://westarete.com
scott at westarete dot com
== TODO
* Add specs for ssl support
* Finish rdoc documentation
\ No newline at end of file
require 'spec/rake/spectask'
require 'rake/rdoctask'
desc 'Default: run all specs'
task :default => :spec
desc 'Run all application-specific specs'
Spec::Rake::SpecTask.new(:spec) do |t|
# t.rcov = true
end
desc "Report code statistics (KLOCs, etc) from the application"
task :stats do
RAILS_ROOT = File.dirname(__FILE__)
STATS_DIRECTORIES = [
%w(Libraries lib/),
%w(Specs spec/),
].collect { |name, dir| [ name, "#{RAILS_ROOT}/#{dir}" ] }.select { |name, dir| File.directory?(dir) }
require 'code_statistics'
CodeStatistics.new(*STATS_DIRECTORIES).to_s
end
namespace :doc do
desc 'Generate documentation for the assert_request plugin.'
Rake::RDocTask.new(:plugin) do |rdoc|
rdoc.rdoc_dir = 'rdoc'
rdoc.title = 'Gravatar Rails Plugin'
rdoc.options << '--line-numbers' << '--inline-source' << '--accessor' << 'cattr_accessor=rw'
rdoc.rdoc_files.include('README')
rdoc.rdoc_files.include('lib/**/*.rb')
end
end
author: Scott Woods, West Arete Computing
summary: View helpers for displaying gravatars.
homepage: http://github.com/woods/gravatar-plugin/
plugin: git://github.com/woods/gravatar-plugin.git
license: MIT
version: 0.1
rails_version: 1.0+
#-- encoding: UTF-8
require 'gravatar'
ActionView::Base.send :include, GravatarHelper::PublicMethods
#-- encoding: UTF-8
require 'digest/md5'
require 'cgi'
module GravatarHelper
# These are the options that control the default behavior of the public
# methods. They can be overridden during the actual call to the helper,
# or you can set them in your environment.rb as such:
#
# # Allow racier gravatars
# GravatarHelper::DEFAULT_OPTIONS[:rating] = 'R'
#
DEFAULT_OPTIONS = {
# The URL of a default image to display if the given email address does
# not have a gravatar.
:default => nil,
# The default size in pixels for the gravatar image (they're square).
:size => 50,
# The maximum allowed MPAA rating for gravatars. This allows you to
# exclude gravatars that may be out of character for your site.
:rating => 'PG',
# The alt text to use in the img tag for the gravatar. Since it's a
# decorational picture, the alt text should be empty according to the
# XHTML specs.
:alt => '',
# The title text to use for the img tag for the gravatar.
:title => '',
# The class to assign to the img tag for the gravatar.
:class => 'gravatar',
# Whether or not to display the gravatars using HTTPS instead of HTTP
:ssl => false,
}
# The methods that will be made available to your views.
module PublicMethods
# Return the HTML img tag for the given user's gravatar. Presumes that
# the given user object will respond_to "email", and return the user's
# email address.
def gravatar_for(user, options={})
gravatar(user.email, options)
end
# Return the HTML img tag for the given email address's gravatar.
def gravatar(email, options={})
src = h(gravatar_url(email, options))
options = DEFAULT_OPTIONS.merge(options)
[:class, :alt, :size, :title].each { |opt| options[opt] = h(options[opt]) }
"<img class=\"#{options[:class]}\" alt=\"#{options[:alt]}\" title=\"#{options[:title]}\" width=\"#{options[:size]}\" height=\"#{options[:size]}\" src=\"#{src}\" />"
end
# Returns the base Gravatar URL for the given email hash. If ssl evaluates to true,
# a secure URL will be used instead. This is required when the gravatar is to be
# displayed on a HTTPS site.
def gravatar_api_url(hash, ssl=false)
if ssl
"https://secure.gravatar.com/avatar/#{hash}"
else
"http://www.gravatar.com/avatar/#{hash}"
end
end
# Return the gravatar URL for the given email address.
def gravatar_url(email, options={})
email_hash = Digest::MD5.hexdigest(email)
options = DEFAULT_OPTIONS.merge(options)
options[:default] = CGI::escape(options[:default]) unless options[:default].nil?
gravatar_api_url(email_hash, options.delete(:ssl)).tap do |url|
opts = []
[:rating, :size, :default].each do |opt|
unless options[opt].nil?
value = h(options[opt])
opts << [opt, value].join('=')
end
end
url << "?#{opts.join('&')}" unless opts.empty?
end
end
end
end
#-- encoding: UTF-8
require 'rubygems'
require 'erb' # to get "h"
require 'active_support' # to get "returning"
require File.dirname(__FILE__) + '/../lib/gravatar'
include GravatarHelper, GravatarHelper::PublicMethods, ERB::Util
describe "gravatar_url with a custom default URL" do
before(:each) do
@original_options = DEFAULT_OPTIONS.dup
DEFAULT_OPTIONS[:default] = "no_avatar.png"
@url = gravatar_url("somewhere")
end
it "should include the \"default\" argument in the result" do
@url.should match(/&default=no_avatar.png/)
end
after(:each) do
DEFAULT_OPTIONS.merge!(@original_options)
end
end
describe "gravatar_url with default settings" do
before(:each) do
@url = gravatar_url("somewhere")
end
it "should have a nil default URL" do
DEFAULT_OPTIONS[:default].should be_nil
end
it "should not include the \"default\" argument in the result" do
@url.should_not match(/&default=/)
end
end
describe "gravatar with a custom title option" do
it "should include the title in the result" do
gravatar('example@example.com', :title => "This is a title attribute").should match(/This is a title attribute/)
end
end
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment