Compare commits

..

482 Commits

Author SHA1 Message Date
Felix Schäfer dfb77852cf Split up Travis tests to avoid hitting the hard Travis timeout 2013-03-19 23:40:33 +01:00
Holger Just 93de0ba668 Bump version to 3.8.0 2013-03-19 22:36:48 +01:00
Holger Just fdc9c245ac Update changelog for 3.8.0 2013-03-19 22:36:06 +01:00
Holger Just d7b23acea0 Don't test Darcs on Travis. It breaks there :( 2013-03-19 22:24:22 +01:00
Holger Just f9babbbbc3 Use RubyGems 1.8.25 on Travis as 2.0.x fails with Rails 2.3 2013-03-19 22:23:56 +01:00
Toshi MARUYAMA 90a05668a0 fix db migrate #1164 2013-03-19 21:30:48 +01:00
Holger Just e4b814e167 Bump Rails to 2.3.18 #1252 2013-03-19 21:30:48 +01:00
Felix Schäfer 304155122d Use SSL communications to rubygems.org 2013-03-19 21:24:55 +01:00
Felix Schäfer 1e2a029099 Explicitely set js calendar until complete switch to jQuery #1121 2013-03-19 21:19:33 +01:00
Holger Just 68e6d171b1 Manually apply basic before_filters in case of routing error #1248 2013-03-15 15:15:10 +01:00
Gregor Schmidt d6e5c9e1fa Fixing HTML in groups index view 2013-03-12 16:57:59 +01:00
Holger Just 63976e6522 Bump the version to v3.7.0 2013-02-13 16:03:00 +01:00
Holger Just 31ad2ef6c3 Update changelog for 3.7.0 release 2013-02-13 16:02:54 +01:00
Holger Just 595e60fb9c Fix stupidity in last filter of liquid #1235 2013-02-13 15:46:08 +01:00
Holger Just 8b357a118d Remove monkey patch which is already included in Rails 2.3.17 #1233 2013-02-13 15:18:17 +01:00
Holger Just 6d3dc6e619 Bump json gem to a safe version for CVE-2013-0269, CVE-2013-0333 2013-02-13 15:18:17 +01:00
Holger Just 0b567641bc Don't set YAML on serialized fields #1233 2013-02-13 15:18:17 +01:00
Holger Just fd306095c6 Bump Rails to 2.3.17 #1233 2013-02-13 15:18:17 +01:00
Holger Just 430b6bb442 Improve on broken MySQL handling of login tokens #1234 2013-02-13 15:18:09 +01:00
Felix Schäfer 3f9007b909 Merge branch 'release-v3.6.0' into stable 2013-01-29 22:37:23 +01:00
Felix Schäfer 6bef9c26ab Bump version to v3.6.0 2013-01-29 22:33:42 +01:00
Felix Schäfer 6744e23ed2 Update Changelog for v3.6.0 2013-01-29 21:46:27 +01:00
Felix Schäfer 065542c7d1 Remove Rails patches which are already included in Rails 2.3.16 #1219 2013-01-29 21:44:15 +01:00
Felix Schäfer dc66e8f831 Bump Rails version to 2.3.16 #1219 2013-01-29 21:24:25 +01:00
Felix Schäfer 6e30b3d3fc Get notified about other watched things than issues #1216
When I select "only for things I watch or am involved in" as my Email
notifications setting, I want to get notified about everyhing I watch,
not just issues. The previous setting would only notify me about issues
I'm involved in, now I will additionally be notified about everything I watch.
2013-01-25 15:30:32 +01:00
Holger Just 2854524ba9 Merge branch 'release-v3.5.1' into stable 2013-01-16 23:26:41 +01:00
Holger Just eb41df17d2 Bump version to v3.5.1 2013-01-16 23:26:13 +01:00
Holger Just 7f7a06706f Update Changelog for v3.5.1 2013-01-16 23:25:47 +01:00
Holger Just db3087b318 Fix for CVE-2013-0155 in Rails 2013-01-16 23:24:15 +01:00
Holger Just d8536ced55 Merge branch 'release-v3.5.0' into stable 2013-01-09 14:06:00 +01:00
Holger Just 908391d54e Bump version to v3.5.0 2013-01-09 14:05:02 +01:00
Holger Just 93b2a1daf9 Update Changelog for v3.5.0 2013-01-09 14:04:56 +01:00
Holger Just c69a005353 Remove Rails patches which are already included in Rails 2.3.15 #1200 2013-01-09 13:56:52 +01:00
Holger Just bf4ed9b37b Bump Rails version to 2.3.15 #1200 2013-01-09 13:56:43 +01:00
Holger Just 90a94d75c3 Merge branch 'release-v3.4.0' into stable 2013-01-06 23:47:42 +01:00
Holger Just 2243c8dfd0 Bump version to v3.4.0 2013-01-06 23:43:44 +01:00
Holger Just cee98d306b Update Changelog for v3.4.0 2013-01-06 23:43:04 +01:00
Holger Just 0dac4ecb53 Ensure UTF-8 source encoding 2013-01-06 23:42:41 +01:00
Holger Just c65779bbc1 Update Copyright for 2013
We programmers have a nice new years tradition: We revisit all of
our projects and add 1 to a small number near a "(c)".

-- Volker Dusch
https://twitter.com/__edorian/status/153801913442373633
2013-01-06 23:42:27 +01:00
Holger Just 0261b16b3e Update locales 2013-01-06 23:20:08 +01:00
Holger Just c80591fe57 Adapt tests for escaping of ' introduced in 8e417fd 2013-01-06 21:17:19 +01:00
Felix Schäfer cdad6f752a Links to new/not existing wikipages are italic #1197 2013-01-06 21:13:34 +01:00
Felix Schäfer 335da86b55 Correctly save the subprojects setting when editing queries #1188 2013-01-06 20:32:53 +01:00
Holger Just 8e417fd5c4 Fix XSS vulnerabilities in Rails (CVE-2012-3464, CVE-2012-3465) #1113 #1114 2013-01-06 20:32:53 +01:00
Holger Just 07e54eda9e SQL Injection Vulnerability in Ruby on Rails (CVE-2012-5664) #1195 2013-01-06 20:32:53 +01:00
Holger Just a93a3af895 Consider HEAD a readonly method in Redmine.pm #1134 2013-01-06 20:32:53 +01:00
Holger Just 5156fbbfc4 Make the WikiContentJournal class available during migration #1194 2013-01-06 20:32:53 +01:00
Felix Schäfer 4d4efa482e Use capybara < 2 as capybara 2 drops ruby 1.8 support 2013-01-02 16:36:49 +01:00
Holger Just c734c6506d Install Darcs 2.3 on Travis CI #1142
Our tests break on Darcs >=2.5 as Darcs changed the repository
format and the command API.
2013-01-02 16:16:39 +01:00
Toshi MARUYAMA e7ef06a926 add travis build status to README.rdoc 2012-12-09 18:24:13 +01:00
Felix Schäfer 0dfa793abc Add a CONTRIBUTING guide. #1192 2012-12-09 18:10:11 +01:00
C-Moreira ce5524ba4d Add localized translation for Time entry menu. #1118 2012-11-22 11:54:11 +01:00
Holger Just 003fc93b15 Make the commented configuration.yml.example more approachable #1144 2012-09-14 22:27:49 +02:00
Holger Just da4641442f Update package list before installing packages on Travis 2012-09-12 21:39:09 +02:00
Web Siduction 0e1a622a6a wiki-text monospaced 11px 2012-08-20 20:18:17 +02:00
Holger Just d63d2d2e81 Display sidebar queries outside of a project #1090 2012-08-20 18:48:28 +02:00
Holger Just 7a4b664577 Set default category_id instead of the object #1087
Rails 2.3 still has issues with synchronizing the association_id
and association attributes of an object. That means, if you set the
association with an object first and then just set the id afterwards, the object wins and the setting of the id gets lost.

This is not an issue in Rails >= 3.1 anymore.
2012-08-20 18:26:32 +02:00
Felix Schäfer d24d4ce6b6 Stick with a working version of mocha
mocha 0.12.2 is known not to work with test/unit:
https://github.com/freerange/mocha/issues/94

Pinning the version of mocha until this is resolved
2012-08-06 10:34:41 +02:00
jplang a2f8557f23 Correctly copy advanced workflow settings #904
Original commit message:
Fixed: Workflow copy does not copy advanced workflow settings

git-svn-id: svn://rubyforge.org/var/svn/redmine/trunk@6148 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-08-06 08:20:58 +02:00
Holger Just 080c5b63ca Merge branch 'release-v3.3.0' into stable 2012-07-15 20:26:01 +02:00
Holger Just 22204d588d Merge branch 'master' into stable 2012-07-15 18:46:48 +02:00
Holger Just 19dd488298 Bump version to 3.3.0 2012-07-15 18:45:49 +02:00
Holger Just 12cedb1e91 Update Changelog for v3.3.0 2012-07-15 18:44:41 +02:00
Holger Just 27f24a78db Add copyright statement 2012-07-15 18:43:00 +02:00
Andrew Smith 4838c4ede8 Replace the progress bars with a copied jQuery version. Fixes #1056 2012-07-15 18:28:23 +02:00
Holger Just f2e4c71b27 Add gravatar no-op library.
This helps to not break on plugins having require "gravatar"
in their init.rb. This statement is not needed anymore in any
supported version of ChiliProject (or Redmine for that matter) and
should thus be removed from all plugins.
2012-07-15 17:48:43 +02:00
Andrew Smith ff94716791 Replace the JS calendar with a jQueryUI version. 2012-07-15 15:25:13 +02:00
Felix Schäfer cb9ac281d9 Add date_field and date_field_tag form helpers #817 2012-07-15 15:19:02 +02:00
Felix Schäfer a642a1fdc4 Bump net-ldap #1078 2012-07-14 21:16:16 +02:00
Holger Just f89c003847 Make list style for ul and ol elements more specific. #1075 2012-07-13 23:27:53 +02:00
Andrew Smith d6eb87508c Update jQuery/UI to 1.7.2/1.8.21. Fixes #1076 2012-07-11 14:41:56 +02:00
Romano Licker 1e96ac9e06 [#935] Setting improperly set up for default values
having a fixed call order resolves a problem where
'value=' was called before 'name=' resulting in no
serialization
2012-07-11 12:56:58 +02:00
Holger Just 9fde11f950 Load rake tasks from plugins in chiliproject_plugins #1074 2012-07-10 14:39:32 +02:00
Felix Schäfer d41c7f1e8c Fix migrating from Redmine 1.4 #1067
Contributed by Steffen Schüssler
2012-07-09 20:41:17 +02:00
Andrew Smith e3dc444b9c Losslessly recompress all the image files. Fixes #1070 2012-07-09 18:28:14 +02:00
Felix Schäfer 37385642fa Don't show the search field when it's not needed #979
Contributed by Harald Klimach
2012-07-09 18:11:04 +02:00
Felix Schäfer 12b2d3c182 Show the register link everywhere it's needed #979
Contributed by Harald Klimach
2012-07-09 18:06:32 +02:00
Andrew Smith 37c762b997 Force the hover colour so that even rows highlight. Fixes #1063 2012-07-06 20:06:03 +02:00
Holger Just 17793c87e3 Format date in user's prefered format by default 2012-07-02 21:10:55 +02:00
Holger Just eea550e639 Fix the Strainer patch to enforce a filter array
Up until now, the patch used to be a no-op. While the filters class
attribute was set correctly, the methods using it were not actually
overridden as they are only included above the existing methods in
the module chain.

This resulted in an arbitrary load order of filters on Ruby 1.8. As
such, our overridden standard filters might not have actually
overridden anything.

Still, the patch can be completely removed once we either require
Ruby 1.9 (as we have ordered ahshes by default then) or once
https://github.com/Shopify/liquid/pull/87 was merged and released
upstream.
2012-07-02 21:10:55 +02:00
Holger Just 760df0ae35 Define today variable for liquid #1055 2012-07-02 21:10:55 +02:00
Andrew Smith a4fbb15f6c Include a minimal Modernizr JS build. Fixes #1054 2012-07-02 20:28:00 +02:00
Andrew Smith 6fa46e5136 Set the users language on the HTML tag. Fixes #1051 2012-06-28 10:03:08 +02:00
Andrew Smith cf83e274d7 Change to an HTML5 doctype #1018 2012-06-26 22:19:37 +02:00
Jan Vlnas 29af3ec964 Correct engine tests to work on Ruby 1.9 #952 #944 2012-06-23 21:23:50 +02:00
Holger Just 6932070752 Require bundler 1.0.14 to use the rbx platform in Gemfile 2012-06-22 17:34:28 +02:00
Holger Just cd4efd2e0d Set $KCODE to UTF-8 on Ruby 1.8 to mimic Rails 3 behavior 2012-06-22 17:31:51 +02:00
Felix Schäfer 2f21522458 Enable project-specific css #1017 2012-06-21 10:28:44 +02:00
Holger Just d3d6a93a45 Fix failing tests for Ruby 1.9 #1046 2012-06-20 18:00:34 +02:00
Holger Just e4386f61da Add "me" to user custom fields filters #1046
Adapted from
28f9605fe2
by Jean-Philippe Lang
2012-06-20 16:07:09 +02:00
Holger Just 59e2a2fdde Merge branch 'release-v3.2.2' into stable 2012-06-13 11:04:48 +02:00
Holger Just 5c7a3a53c2 Bump version to 3.2.2 2012-06-13 10:26:48 +02:00
Holger Just 4d9060964f Update changelog for v3.2.2 2012-06-13 10:25:18 +02:00
Holger Just 16e266e7e5 Fix SQL injection via nested hashes in conditions. CVE-2012-2695 #1037 2012-06-13 10:12:10 +02:00
Holger Just d629209364 Fix SQL injection via nested hashes in conditions (CVE-2012-2694) #1036 2012-06-13 10:10:03 +02:00
Holger Just 221a2e73ce Merge branch 'release-v3.2.1' into stable 2012-06-10 20:38:18 +02:00
Holger Just c9d141061d Bump version to v3.2.1 2012-06-10 20:36:34 +02:00
Holger Just 8e41daf92a Update changelog for v3.2.1 2012-06-10 20:36:34 +02:00
Felix Schäfer caceb58947 Restore the default class for gravatars #1034 2012-06-10 20:34:23 +02:00
Felix Schäfer 5ef63ec4bb Fix option parsing for gravatars #1034
In addition to that:
* the default size was 50px in the old lib, this has been restored
* some tests to test the default and option parsing
2012-06-10 19:54:46 +02:00
Holger Just e6e057d10d Merge branch 'release-v3.2.0' into stable 2012-06-09 19:07:06 +02:00
Felix Schäfer f8ec89b999 Test stable- and release- branches on Travis too 2012-06-09 19:02:18 +02:00
Holger Just bd132c5607 Bump version to 3.2.0 2012-06-09 17:59:06 +02:00
Holger Just bcb02a4634 Update Changelog for v3.2.0 2012-06-09 17:56:21 +02:00
Holger Just d11e074748 Fix trailing whitespace 2012-06-09 17:52:37 +02:00
Felix Schäfer 1f91054244 Switch from vendored gravatar lib to gravatarify gem #1033 2012-06-09 17:39:22 +02:00
Felix Schäfer 32e1cc2dee Don't test transaction-based features on SQLite
SQLite doesn't support nested transactions, thus we can't test
transaction-based features inside a test wrapped in a transaction.
2012-06-09 17:35:49 +02:00
Felix Schäfer 3997220a45 Add IRC notifications to the Travis config 2012-06-09 12:02:54 +02:00
Felix Schäfer 69656422a1 Test ChiliProject on rubinius on Travis
rubinius 1.9-mode won't load liquid currently:
https://github.com/Shopify/liquid/pull/107
2012-06-07 14:30:45 +02:00
Felix Schäfer d6ad07ee97 Refine some apt-get commands in Travis
* Remove the update, the Travis VMs should be up-to-date enough for us
* Remove the quiets so we can see errors if needed
2012-06-05 16:00:09 +02:00
Justin Geibel 1722e96bb0 Change default branch behaviour for git repos #749
If git repository HEAD points to a branch, use that as the default branch.
Otherwise fall back to previous method.
2012-06-05 15:33:04 +02:00
Andrew Smith 4956e9ca93 Limit the size of the projects dropdown menu. #1016 2012-06-05 15:15:48 +02:00
Andrew Smith 7c1b17509f Clear the document selection when selecting multiple rows. 2012-06-05 15:05:35 +02:00
Andrew Smith c8be011a93 Correct major logic issues when selecting multiple rows.
The rows were being incorrectly selected (because the class name had a
missing '.').

jQuery items can't be directly compared to each other so we need to get
the HTML element to test if they are the same.
2012-06-05 15:05:35 +02:00
Holger Just 8ab42473d9 [#1025] Fix Rails vulnerability (CVE-2012-2660) 2012-06-01 20:56:09 +02:00
Felix Schäfer a43c06ff77 Don't depend on DB ordering in journal_test
And add all fixtures needed to run the test on its own.
2012-05-25 13:37:11 +02:00
Andrew Smith f983b451ad Reformat CSS to use the coding standard. #947
Formatting was done mostly by hand using the following as a guide
http://gnuvince.wordpress.com/2007/02/26/reformatting-a-css-file-with-vim/

" Replace all sequences of white spaces with one space
:%s/[ \t\n]\+/ /g

" Go to the end of the command, then forward one character and insert
" a newline
]/lr^M

" Make sure there is a semi-colon before each closing bracket
:%s/\([^; ]\) *}/\1;}/g

" Add a newline after every semi-colon
:%s/;/;^M/g

" Add a newline after every opening brace and make put one space
" between it and the preceeding text
:%s/\([^ ]*\) *{/\1 {^M/g

" Add two newlines after every closing brace
:%s/}/}^M^M/g

" Remove 'trailing' spaces in front of the semi-colons
:%s/ *;/;/g

" Make sure there is only one space after a colon
:%s/: */: /g

" Make the text before the colon lowercase
:%s/\(.\{-}\):/\L\1:/g

" Remove all trailing spaces at the beginning of lines
:%s/^ \+/g

" Indent the whole file
gg=G

" Split each rule onto its own line (This also matched some
" property/value combos so needed confirming
:%s/\([a-z0-9]\+\), \+/\1,^M/gc
2012-05-21 13:45:39 +02:00
Andrew Smith 61c00779d0 Set the X-CSRF-Token header for AJAX requests with jQuery. #950 2012-05-20 20:41:02 +02:00
Dereckson 375045a82b Improving French translation #967 2012-05-20 20:35:33 +02:00
Felix Schäfer 2e18840f12 Disable autocomplete on registragion form #844 2012-05-20 10:30:34 +02:00
Felix Schäfer 20448c7efb Load all required fixtures on issue unit test #863
Contributed by Toshi MARUYAMA
2012-05-20 10:18:00 +02:00
Andrew Smith 9cd14ca152 Fix a typo when calculating the menu X position. #1007 2012-05-19 11:52:51 +02:00
Felix Schäfer 87f68e58ad Recalculate last reply of thread on message deletion #968 2012-05-19 11:38:40 +02:00
Andrew Smith 43723385c6 Handle the '(Un)Check all' link. #994 2012-05-19 10:37:17 +02:00
Felix Schäfer 93abdcf487 Fix comments missing in wiki add/update mails #986 2012-05-19 00:00:16 +02:00
Felix Schäfer 1d3cdf1fa9 Correct email sending on attachment to document #1008 2012-05-18 22:45:52 +02:00
Felix Schäfer 3e84c4817b Updated czech localization by Radek Tříška 2012-05-18 22:02:40 +02:00
Felix Schäfer 7930e8fa66 Also test stable and unstable branches on TravisCI 2012-05-14 14:18:52 +02:00
Felix Schäfer d4f0542e74 Don't rely on ordering in news_test
The tests would fail because not all storage backends have a default
ordering
2012-05-14 12:39:17 +02:00
tmaruyama 6a26543887 scm: mercurial: fix unit adapter annotate test fails on Windows Mercurial 1.8.4+29-e597ef52a7c2.
git-svn-id: svn://rubyforge.org/var/svn/redmine/trunk@6042 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-05-14 10:54:17 +02:00
Felix Schäfer 03dfae0529 ChiliProject on Travis <3 <3 <3 2012-05-14 10:45:50 +02:00
Eric Davis 75bb38df08 Require WikiContent directly so WikiContentJournal is loaded before it's patched.
Error: uninitialized constant MergeWikiVersionsWithJournals::WikiContentJournal
2012-04-16 09:25:35 -07:00
Jean-Philippe Lang 16947dc016 [984] Allow to change locked and sticky status and the board of messages 2012-04-14 15:02:07 +02:00
unknown e7d96825b2 [#988] Improving Swedish translation 2012-04-13 16:07:42 +02:00
Holger Just 2bcd8787d3 Remove cookie cleaning method as it is not required anymore 2012-04-13 13:44:16 +02:00
jwalkerbg 6a4559e6af [#983] Translated several keys to Bulgarian language. 2012-04-13 09:45:08 +02:00
Gabriel Mazetto 859a9cb9d2 Some translation fixes and more missing translations for pt-BR 2012-04-12 08:20:02 +02:00
Holger Just 33260d885d Ensure array order for repeatable success of tests 2012-04-11 23:15:33 +02:00
Holger Just 277815ec96 Don't render cusom queries in the sidebar anymore
The queries are now part of the issues menu and are thus redundant in the
sidebar.
2012-04-11 20:50:42 +02:00
Holger Just 01c386b3c6 [#970] Cap too long menu entries with an ellipsis 2012-04-11 20:48:30 +02:00
Holger Just a6071c75ec [#975] Add test for editing version start date 2012-04-11 00:07:42 +02:00
Spenser Jones e6c79ddef4 [#975] Validate and enable editing of Version start date 2012-04-11 00:07:32 +02:00
Holger Just c3fc106355 [#974] Force the issue new link in the sidebar to the new action 2012-04-10 23:35:12 +02:00
Holger Just b169f02377 REMEMBER: ALWAYS RUN THE TESTS! 2012-04-09 15:46:06 +02:00
Holger Just 1f4baaa27b Take the password salt into account when checking for admin account change 2012-04-09 15:43:51 +02:00
Felix Schäfer 8f06b77ccb correct 'edit own issue notes' permission #966 2012-04-07 15:59:51 +02:00
Holger Just b8a29c288b Add the project_id to board URLs in the menu to generate valid URLs 2012-04-05 20:49:47 +02:00
Holger Just aca166271b Update German translation 2012-04-05 16:18:32 +02:00
Holger Just d61ad01308 Merge branch 'release-v3.1.0' into stable 2012-04-04 14:26:46 +02:00
Holger Just f681ed8c13 Bump version to 3.1.0 2012-04-04 14:21:43 +02:00
Holger Just eb62f4f7ec Update changelog for v3.1.0 2012-04-04 14:21:42 +02:00
Holger Just 4aed677908 Fix trailing whitespace 2012-04-04 14:21:42 +02:00
Holger Just 6f055664f1 Add copyright statement 2012-04-04 14:21:42 +02:00
Holger Just bb4340eb6d Update locales with new keys 2012-04-04 14:21:42 +02:00
Jean-Philippe Lang 3183aa55ed Set user_id as a protected attribute (#922). 2012-04-04 14:21:41 +02:00
Jean-Philippe Lang 275163ead2 Prevent mass-assignment vulnerability when adding/updating a wiki (#922). 2012-04-04 14:21:41 +02:00
Jean-Philippe Lang fc5dfd5813 Prevent mass-assignment vulnerability when adding/updating a version (#922). 2012-04-04 14:21:41 +02:00
Jean-Philippe Lang a3f6b30e99 Prevent mass-assignment vulnerability when adding/updating a time entry (#922). 2012-04-04 14:21:41 +02:00
Jean-Philippe Lang 5de377c5ee Use safe_attributes= just like in #create. (#922) 2012-04-04 14:21:41 +02:00
Jean-Philippe Lang 305df19ab7 Prevent mass-assignment vulnerability when adding/updating a news (#922). 2012-04-04 14:21:40 +02:00
Jean-Philippe Lang c3ca5813d5 Prevent mass-assignment vulnerability when adding/updating a forum message (#922). 2012-04-04 14:21:40 +02:00
Jean-Philippe Lang 384890c5ad Prevent mass-assignment vulnerability when adding a project member (#922). 2012-04-04 14:21:40 +02:00
Jean-Philippe Lang e77cb6133d Prevent mass-assignment vulnerability when adding/updating an issue category (#922). 2012-04-04 14:21:39 +02:00
Jean-Philippe Lang 7505cb2ff0 Prevent mass-assignment vulnerability when adding/updating a document (#922). 2012-04-04 14:21:39 +02:00
Jean-Philippe Lang 2eeb4b13a6 Prevent mass-assignment vulnerability when adding a news comment (#922). 2012-04-04 14:21:39 +02:00
Felix Schäfer 0a7c6e6774 Correct handling of @Rational#to_s@ on ruby 1.9 #887
Contributed by Martin S
2012-04-04 09:36:58 +02:00
Felix Schäfer 6f064d3856 Activity: use default filter only on first request #861 2012-03-28 20:31:20 +02:00
Robert Mitwicki c6af5c7982 Correct error message on group name #873 2012-03-28 08:56:06 +02:00
Jean-Philippe Lang 20a79124f7 Set format for activeresource client in reposman.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@7955 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-03-27 12:51:43 +02:00
Felix Schäfer e6ec8ab30c Remove length limits on some user fields #928 2012-03-25 09:40:56 +02:00
Felix Schäfer dafe09ee32 Adapt test to new jQuery context menu
jQuery context menu introduced in 1b7ddb3cd7
2012-03-23 12:34:53 +01:00
Andrew Smith 7b3280e5ad Replace tables that were using the 'width' attribute with inline styles (should be replaced with an ID/class). 2012-03-23 11:22:58 +01:00
Andrew Smith fd250726ce Remove some inline styling and some depreciated table cell attributes and replace them with proper class names. 2012-03-23 11:22:57 +01:00
Andrew Smith 1cfd20e7e7 Fix missing cells and incorrect close order 2012-03-23 11:22:57 +01:00
Andrew Smith b15e6d8305 Fix duplicated 'back_url' IDs
Various hidden input fields are used to hold a URL to send the user back
to the correct page after submitting a form, however, they all use the
same ID which isn't allowed in HTML. Passing in 'id' as nil stops the
'hidden_field_tag' from adding an ID attribute to the tag.
2012-03-23 11:22:57 +01:00
Andrew Smith 2a70e2704f HTML corrections 2012-03-23 11:22:57 +01:00
Andrew Smith 23a857bf75 Remove hard-coded div on issue page 2012-03-21 21:07:21 +01:00
Andrew Smith a510f0a85f Updates the plugin so that it can support multiple instances.
Multiple instances need to be applied to different elements in the DOM
otherwise multiple menus will be called for each click/right click.

e.g.

jQuery(document).ContextMenu(url);

would work for any form on a page. Using this would mean that multiple
instances couldn't be used though

jQuery('#content form').eq(0).ContextMenu(url);
jQuery('#content form').eq(3).ContextMenu(url);

Using the above 2 menus will be created for the first and 4th forms on
the page. Any of forms won't response to menu clicks.

jQuery(docuemnt).ContextMenu(url);
jQuery('#content form').eq(0).ContextMenu(url);

With the above any form on the page will respond to menu clicks but the
first form will send 2 requests for the context menu.
2012-03-21 21:07:21 +01:00
Andrew Smith 2b640f76ec Simple wrapper for backwards compatibility 2012-03-21 21:06:58 +01:00
Andrew Smith 1b7ddb3cd7 jQuery version of the issue context menu 2012-03-21 21:06:58 +01:00
Andrew Smith 27160b76f5 The jQuery ajaxStart/Stop functions are being called at the wrong time.
The ajaxStart/ajaxStop functions were being called before the document
was ready and the 'ajax-indicator' element existed. This meant that they
would never be called when an ajax event happened.
2012-03-20 22:54:20 +01:00
Gregor Schmidt c9a403a4b5 [#939] Update documentation for GMail setup
It now matches the current guide in ChiliProject Wiki at https://www.chiliproject.org/projects/chiliproject/wiki/Email_Delivery#SMTP-server-using-TLS-GMail
2012-03-20 22:49:37 +01:00
Felix Schäfer 565aeabc79 Issue hierarchy on issue show view #906 2012-03-20 09:25:54 +01:00
Holger Just 68efc3de32 [#903] Require updated tzinfo with fix for Ruby 1.9.3 2012-03-18 18:15:08 +01:00
Felix Schäfer efac256a6b Design fix
The lower would only show if you have the permission to see/add related issues
2012-03-17 15:31:26 +01:00
Felix Schäfer f01194856f Use the news description as event description #933 2012-03-17 13:55:36 +01:00
Felix Schäfer df66e9b915 aaj: default to don't force notes as event descriptions #933 2012-03-17 13:53:29 +01:00
Felix Schäfer 20eed68421 [#739] Convert relative links to full links in textile
Textile doesn't "understand" :only_path => false and thus doesn't convert links of the form

<pre>"foo":/bar</pre>

to full URLs, this is done in a subsequent method akin to the wiki_lins and so on
2012-03-16 00:47:18 +01:00
Felix Schäfer 26c847a4ca More specific CSS #911 2012-03-16 00:30:20 +01:00
Felix Schäfer 1bc182405e Correct truncated styles on journal entries #914 2012-03-09 18:44:04 +01:00
Michaël Rigart 23864d99b5 Fix progress bar css #868 2012-03-09 18:36:32 +01:00
Felix Schäfer ef5dddf4db Switch to GitAdapter for quoting #891
Fixes problems with paths containing quotes (sic)
2012-03-05 18:21:28 +01:00
Felix Schäfer 2c76240544 Fix edit issue notes permission #888 2012-03-05 18:13:59 +01:00
Holger Just 724bd48494 Remove unused code
Since Rails 2.3.11, protect_from_forgery exclusively calls
handle_unverified_request which defaults to resetting the session. The old
code to handle an invalid CSRF token is not used anymore and is thus
removed to un-confuse people.
2012-03-05 12:50:44 +01:00
Felix Schäfer 900eda7a23 Don't require a specific pg gem version #915
pg was locked to ~> 0.9.0 as it was the last version to support ruby 1.8.6, we don't support it officially anymore, thus we can remove the version requirement.

Contributed by Dies Koper.
2012-03-01 12:56:31 +01:00
Felix Schäfer 4b5271f487 Correct quotation #891
The Windows shell doesn't seem to support single quotes…

Contributed by Dies Koper, Luke Carrier
2012-03-01 00:40:23 +01:00
Felix Schäfer 4a3f10317f Correct (wrongly) assumed ordering in test #892
Contributed by Luke Carrier.
2012-02-29 22:28:47 +01:00
Eric Davis b9b2c8c0d7 [#559] Group menus and add some dynamic menus 2012-02-26 22:37:34 -08:00
Felix Schäfer fbc71a80f8 Fix issue option list orientation #869
Contributed by Alf Gaida.
2012-02-24 16:36:04 +01:00
Felix Schäfer f327298f88 Fix missing submenu hint on issue context menu #882
Contributed by Andrew Smith
2012-02-24 15:56:29 +01:00
Michaël Rigart e678612d75 gitignore intellij project files 2012-02-24 15:45:22 +01:00
Michaël Rigart cbcce70400 Fix requiring SCM classes in dev mode #828
Change require to require_dependency so SCM classes get loaded on each request in dev mode
2012-02-24 15:44:02 +01:00
Felix Schäfer fda1dfa96d Merge pull request #153 from dfeyer/master
Fixes for Activity list column alignment
2012-02-22 13:53:13 -08:00
Dominique Feyer f561c54356 Fixes activity list column alignment 2012-02-12 23:08:37 +01:00
Holger Just 11e93ff36a Merge branch 'release-v3.0.0' into stable 2012-02-07 00:09:59 +01:00
Holger Just 3ecace901b Bump version to 3.0.0 2012-02-06 23:58:16 +01:00
Holger Just 2e7050ef79 Update Changelog for 3.0.0 release 2012-02-06 23:58:16 +01:00
Holger Just e0eb21b48b [#826] Position the .button-large as any other title-bar element 2012-02-06 23:58:16 +01:00
Holger Just 1eefd4b40d Merge branch 'master' into unstable
Conflicts:
	app/models/journal_observer.rb
	lib/chili_project/version.rb
	test/unit/principal_drop_test.rb
2012-02-06 23:57:10 +01:00
Holger Just 0fd499afca Merge branch 'master' into unstable 2012-02-06 22:47:35 +01:00
Holger Just aa3ab990d0 [#593] Generate wiki content notifications in the JournalObserver
This will prevent the usage of the wrong wiki_content status.

The code is not overly pretty and deserves a thorough refactoring, but at
least it solves the problem at hand.
2012-02-06 22:44:44 +01:00
Holger Just 3f99ee63ff Always display our custom 404 page 2012-01-30 21:28:57 +01:00
Holger Just be4d679d54 [#849 #789] Remove config.ru as Rails 2.3 doesn work well as a rack-only app 2012-01-29 21:47:05 +01:00
Holger Just 3f126bdeaf [#843] Add config/setup_load_paths.rb to .gitignore 2012-01-29 19:55:32 +01:00
Holger Just 8e85cbdc11 [#839] Remove ruby-debug dependency.
It completely breaks on Ruby 1.9.3-p0. This is a stopper for people installing all groups. For the rest it is rarely used at all. Developers and testers requiring it can include it into their Gemfile.local
2012-01-29 19:55:32 +01:00
Holger Just 21f70fc86c Bump version to 3.0.0beta2 2012-01-18 19:48:06 +01:00
Holger Just f4c87b92e2 Update changelog for 3.0.0beta1 2012-01-18 19:31:11 +01:00
Holger Just b692d4a671 Fix source encoding 2012-01-18 19:26:56 +01:00
Holger Just 185edcd283 Fix trailing whitespace 2012-01-18 19:26:03 +01:00
Holger Just 19f2ccd496 Update copyright for 2012 2012-01-18 19:25:13 +01:00
Holger Just 91f6e79f4d Update locales with new strings 2012-01-18 19:23:56 +01:00
Holger Just e85947c7d4 Merge branch 'master' into unstable 2012-01-18 19:18:27 +01:00
Holger Just a0a2776f95 Make the strainer monkey patch more conservative and compatible 2012-01-18 10:19:04 +01:00
Holger Just 3c9e9764b0 [#807] Re-add details class to maintain backwarts-compatible lasses 2012-01-17 23:43:28 +01:00
Holger Just 3f325243ce Merge branch 'pulls/783/remove-new-issue-link-if-unauthorized' of https://github.com/finnlabs/chiliproject into unstable 2012-01-17 20:14:25 +01:00
Holger Just c313ed2d25 Remove unloadable from the monkey patches for Liquid.
It turns out in this case or core patches, unloadable actually breaks in
development mode. Did I already mention that I hate the Rails reloader?
2012-01-17 19:38:28 +01:00
Holger Just e1ecae83b3 [#774 #815 #807] Fix styles used for gravatars in issues
Gravatars now generally have a border and are displayed equally.
The Issue history formatting is cleaned up with much of the positioning
magic removed in favor or a simple float.
2012-01-17 19:34:33 +01:00
Holger Just 323b5bebc8 [#774] Move gravatar of issue author into the author tag 2012-01-17 19:31:02 +01:00
Holger Just 91c04f335d [#807] Move gravatar into the journal div 2012-01-17 19:25:53 +01:00
Holger Just 31620d0c0a Override first and last filters to allow simple array slicing 2012-01-17 13:47:13 +01:00
Holger Just 9967b5cdf2 Patch Liquid to include filters in a predictable order
This brings us a reliable filter override until
https://github.com/Shopify/liquid/pull/87 is accepted and
released upstream.
2012-01-16 20:49:39 +01:00
Holger Just e91a1e010f [#778] Properly cache Liquid markup
This commit moves the markup caching into Liquid rendering. As
Liquid allows to return different results depending on the environment
(variables, logged user, ...) we only cache the page if it contains
no active content.

Unfortunetely, active content currently also includes the TOC
in wiki pages.
2012-01-16 17:08:52 +01:00
Holger Just 637ca24aed Completely disable the Rails cache for tests
Settings extension to handle cache behaviour by Gregor Schmidt.
2012-01-16 15:54:05 +01:00
Holger Just a938d582b1 [#780] Clarify deprecation of Setting.clear_cache 2012-01-16 14:53:51 +01:00
Holger Just 33a8baf347 Merge branch 'pulls/780/setting-cache' of https://github.com/finnlabs/chiliproject into unstable 2012-01-16 14:50:34 +01:00
Holger Just d7ebffb7ad [#795] Generate error flash on Liquid syntax error 2012-01-16 14:10:45 +01:00
Holger Just 61a65d4624 [#827] Add status group in time entries report.
Patch provided by Jérôme BATAILLE.
Test from Jean-Philippe Lang.
2012-01-16 13:48:06 +01:00
Holger Just d9a0ac37eb Move Redmine::Info to ChiliProject::Info 2012-01-16 13:28:43 +01:00
Holger Just dbbc0b4919 [#558] Reduce version info in help link 2012-01-16 13:08:25 +01:00
Eric Davis 0407abbd56 Hide the More menu if there are no items to show 2012-01-12 15:00:23 -08:00
Eric Davis 4ce3b88473 Add a menu to view all projects since the main click event is blocked 2012-01-12 14:58:15 -08:00
Holger Just b080ad14ef [#790] Test the lazy variables 2012-01-05 23:36:30 +01:00
Holger Just f1324e6af4 [#790] Replace TagList and VariableList tags with variables 2012-01-05 23:36:30 +01:00
Holger Just 446f943968 [#790] Add filter to output an array into an unordered list 2012-01-05 23:36:30 +01:00
Holger Just 3c3eb2f7e7 [#790] Register variables with an API 2012-01-05 23:36:29 +01:00
Holger Just dc5fd8bc10 Actually focus username in login dropdown 2012-01-04 18:31:05 +01:00
Holger Just 0c87f611e9 Merge branch 'master' into unstable
Conflicts:
	config/locales/bg.yml
	config/locales/bs.yml
	config/locales/ca.yml
	config/locales/cs.yml
	config/locales/da.yml
	config/locales/de.yml
	config/locales/el.yml
	config/locales/en-GB.yml
	config/locales/es.yml
	config/locales/eu.yml
	config/locales/fa.yml
	config/locales/fi.yml
	config/locales/fr.yml
	config/locales/gl.yml
	config/locales/he.yml
	config/locales/hr.yml
	config/locales/hu.yml
	config/locales/id.yml
	config/locales/it.yml
	config/locales/ja.yml
	config/locales/ko.yml
	config/locales/lt.yml
	config/locales/lv.yml
	config/locales/mk.yml
	config/locales/mn.yml
	config/locales/nl.yml
	config/locales/no.yml
	config/locales/pl.yml
	config/locales/pt-BR.yml
	config/locales/pt.yml
	config/locales/ro.yml
	config/locales/ru.yml
	config/locales/sk.yml
	config/locales/sl.yml
	config/locales/sr-YU.yml
	config/locales/sr.yml
	config/locales/sv.yml
	config/locales/th.yml
	config/locales/tr.yml
	config/locales/uk.yml
	config/locales/vi.yml
	config/locales/zh-TW.yml
	config/locales/zh.yml
	db/migrate/20100217010520_add_custom_filter_to_auth_sources.rb
	lib/chili_project/version.rb
2012-01-04 16:41:10 +01:00
Eric Davis b8a7f2923a Remove arbitrary limit on the Time Entry Report columns
Appears it was put in place to prevent wide reports but was actually
limiting the data, especially when putting a day in each column.
2011-12-29 15:24:03 -08:00
Eric Davis e4554a6d7b [#792] Fix tests from merge 2011-12-29 11:50:48 -08:00
Eric Davis 00df832126 Merge branch 'ticket/unstable/792-confirmation-emails' into unstable 2011-12-29 10:28:47 -08:00
Eric Davis 66fe8287ce [#809] Fixed a nil object error in params. 2011-12-29 10:08:03 -08:00
Eric Davis a3b1127ef8 Merge branch 'ticket/unstable/809-add-multiple-projects' into unstable 2011-12-29 09:58:34 -08:00
Eric Davis c51d339512 [#809] Multiple projects can now be assigned to a group or user.
* Added a Javascript autocomplete for searching Projects
* Updated the Users and Groups controllers' #edit_membership method to
  create/update multiple Member records
2011-12-29 09:30:32 -08:00
Eric Davis af00598e5e [#809] Refactored a duplicate form to a partial. 2011-12-29 09:30:32 -08:00
Eric Davis f835420383 Merge branch 'ticket/unstable/808-issue-description-diff' into unstable 2011-12-29 08:36:46 -08:00
Eric Davis 38d0d530b0 [#808] Show issue description diffs in the lightbox popup 2011-12-29 08:35:51 -08:00
Eric Davis 4acee9e989 Add a reusable dialog-window for lightbox style popups 2011-12-29 08:35:51 -08:00
Eric Davis aafec2c50f Add some nice defaults for jQuery ajax 2011-12-29 08:35:51 -08:00
Eric Davis 08454ab7fa [#808] Truncate and show a link to the full journal diff in the issue history 2011-12-29 08:35:51 -08:00
Eric Davis 5ad97a4ea3 [#808] Add JournalsController#diff to diff a single field 2011-12-29 08:35:45 -08:00
Eric Davis e045306a5c [#808] Add routing for journal diffs 2011-12-29 08:35:37 -08:00
Eric Davis 8100ce0ce5 Optimize: Remove per-language assertions for Mailer tests
I can't find any real reason they are needed at all and were taking
a lot of time to run. Results for the test class

Before: 56 seconds
After: 23 seconds
2011-12-28 12:20:45 -08:00
Eric Davis 84cc8ab215 Guard against nil author or author emails in the Mailer. 2011-12-28 11:31:05 -08:00
Eric Davis ef00061568 [#798] Fixed the sidebar design by moving #sidebar out of #main-menu
The #main-menu was used as the entire left column but was having styles
applied to it for the menu which were leaking onto the #sidebar. By
wrapping the column in a unique div the menu styles were isolated from
the sidebar styles.
2011-12-28 11:14:38 -08:00
Eric Davis 15428fc092 [#806] Add Board and Message watcher management 2011-12-28 11:03:24 -08:00
Eric Davis a8d28e4593 Merge branch 'ticket/unstable/805-wiki-watching' into unstable 2011-12-28 10:23:26 -08:00
Eric Davis ce0c32ea02 Refactor: create the permission name dynamically to support other classes 2011-12-28 10:22:06 -08:00
Eric Davis 97fe88f8d9 [#805] Add the Watchers sidebar to Wiki Pages 2011-12-28 10:22:06 -08:00
Eric Davis 3df729e47d Replace puts and !!! messages with pending tests when the test SCMs are missing 2011-12-27 18:23:14 -08:00
Eric Davis 7bce7f7b07 Merge branch 'ticket/unstable/802-group-watchers' into unstable 2011-12-27 18:09:25 -08:00
Eric Davis 04db42f537 Fix another broken test that can't run by itself due to missing fixtures 2011-12-27 17:38:38 -08:00
Eric Davis 16943d04f7 [#802] Use the group.png icon as a Group avatar 2011-12-27 17:38:34 -08:00
Eric Davis 8e3d4da376 [#802] Show groups in the watchers partial and users autocomplete 2011-12-27 17:38:30 -08:00
Eric Davis 65e7995682 [#802] Change WatchersController to allow groups 2011-12-27 17:38:25 -08:00
Eric Davis e6e6a06fff [#802] Allow Groups to be added as a Watcher 2011-12-27 17:38:20 -08:00
Eric Davis 10054cfd8f [#802] Move methods from User so other Principals can use them 2011-12-27 17:38:14 -08:00
Eric Davis 8b3207a893 Fix test that doesn't run alone due to missing fixtures 2011-12-27 17:16:16 -08:00
Eric Davis 037d915d7b Merge branch 'ticket/unstable/800-non-member-watch' into unstable 2011-12-27 17:14:57 -08:00
Eric Davis c3555b1728 [#800 #801] Exclude users already watching the issue 2011-12-27 17:09:29 -08:00
Eric Davis 8160cd02bd [#800 #801] Add Javascript search to bulk add watchers to issues. 2011-12-27 17:09:24 -08:00
Eric Davis 5deae7ebe3 [#800 #801] Refactor AutoCompletesController#users to not be coupled to Groups 2011-12-27 17:09:19 -08:00
Eric Davis a110f1041d [#800 #801] Refactor the user auto complete method to the AutoCompletesController 2011-12-27 17:09:18 -08:00
Eric Davis 111c7f47f7 [#800 #801] Test that non-member watchers aren't removed if they still have access 2011-12-27 17:08:48 -08:00
Eric Davis df5b2198e9 Merge branch 'ticket/unstable/799-document-watching' into unstable 2011-12-27 13:50:47 -08:00
Eric Davis 3af5544dbc [LSS#4190] Add watching to documents 2011-12-27 13:16:52 -08:00
Eric Davis 61a21f4990 [#797] Fix wiki page hierarchy 2011-12-27 13:05:21 -08:00
Eric Davis 72eadcc6ea [#796] Adds date range filter.
Based on r6226 from Redmine by Jean-Philippe Lang
2011-12-27 13:02:46 -08:00
Eric Davis dc541597ec [#792] Send email from mail_handler for emails which are missing required information 2011-12-26 17:39:29 -08:00
Eric Davis 1dd07471ca [#792] Send email from mail_handler for emails missing a project 2011-12-26 17:38:26 -08:00
Eric Davis 4ff670f5fa [#792] Send email from mail_handler for unauthorized actions 2011-12-26 17:38:25 -08:00
Eric Davis 9f4683d71b [#792] Send email when mail_handler gets an email from an unknown user 2011-12-26 17:38:25 -08:00
Eric Davis 331ecb4c4f [#792] Add confirmation emails for successful forum replies 2011-12-26 17:38:25 -08:00
Eric Davis 7c7aca4f0c [#792] Add confirmation emails for successful issue replies 2011-12-26 17:38:25 -08:00
Eric Davis d53c4e9aae [#792] Add confirmation emails for successful issue creation 2011-12-26 17:38:24 -08:00
Eric Davis 9d8fc86b8e [#792] Add settings for confirmation emails are sent for incoming mail 2011-12-26 17:38:19 -08:00
Eric Davis 260e8b84f8 [#674] Convert outbound mail to be sent per-recipient
Instead of a single email that is sent out with all the recipients as CC/BCC,
each recipient will be delivered their own email. This will let emails to be
customized per user based on their permissions, without exposing private data.
2011-12-26 16:58:43 -08:00
Eric Davis 21685caf5f [#791] Add support for pop3s (SSL) to redmine📧receive_pop3 2011-12-26 12:45:30 -08:00
Gregor Schmidt 69ccbafaf4 [#783] Removing link to new issue when missing permissions 2011-12-20 10:13:01 +01:00
Gregor Schmidt 10e16e9a56 [#780] Clearing (Settings) cache to make the tests pass again 2011-12-19 17:30:19 +01:00
Gregor Schmidt 6273891ae7 [#780] Remove non-functional call to Setting.check_cache 2011-12-19 17:30:13 +01:00
Gregor Schmidt 92b6d43150 [#780] using a changing cache key to avoid the need for explicit cache invalidation
This should work on all cache stores, since only the basic cache operations fetch and delete are used. The methods clear_cache and check_cache can no longer be supported. To sweep the whole cache Rails.cache.clear may be used. To invalidate the Settings only, you may use Setting.first.try(:touch).
2011-12-19 17:30:08 +01:00
Holger Just 6f17ec6fd0 Bump version to 3.0.0beta1 2011-12-18 21:51:43 +01:00
Holger Just d5f4bec112 Update changelog for 3.0.0-beta1 2011-12-18 21:40:21 +01:00
Holger Just 745f889e92 Enforce UTF-8 source encoding 2011-12-18 21:40:14 +01:00
Holger Just ceac1bb419 Update missing keys in i18n locales 2011-12-18 21:40:14 +01:00
Holger Just e1ac469cfb Fix trailing whitespace 2011-12-18 21:40:01 +01:00
Holger Just d24e8186b4 Merge remote-tracking branch 'chiliproject/master' into unstable 2011-12-18 21:33:05 +01:00
Holger Just 81f6635878 Remove debug code. Sorry for that... 2011-12-18 15:58:42 +01:00
Holger Just ba6fcfeca9 Override some filters of Liquid core.
These filters change the implemntation of sub and gsub to use the block method.
This prevents the evaluation of backreferences in the replacement text. See
https://gist.github.com/1491437 for examples.

Also, it allows split to be called without arguments to split strings on
whitespace.
2011-12-18 15:54:09 +01:00
Holger Just 2733e5173f Fix trailing whitespace 2011-12-18 15:49:18 +01:00
Holger Just 6b725c32e6 Add wiki formatting for h4 2011-12-18 15:49:00 +01:00
Holger Just 0049f82182 Redirect back to where I came from when loggin in via the menu form 2011-12-18 12:28:32 +01:00
Holger Just 58a7182e2d Use the correct subject id for quoting 2011-12-18 12:04:13 +01:00
Eric Davis 2f0d13149d Copyright header update 2011-12-17 15:50:52 -08:00
Eric Davis d583c94b5f Update locales 2011-12-17 15:49:28 -08:00
Eric Davis 3a16f3f5b1 Merge remote-tracking branch 'upstream/master' into unstable 2011-12-17 15:20:58 -08:00
Holger Just 04eb115da7 Add identity tag which just returns whatever was entered as input 2011-12-17 23:49:25 +01:00
Holger Just 34fef9d8fc Fix display of floatable TOCs in wikis 2011-12-17 22:14:19 +01:00
Holger Just e2c57fd12d Make floated TOCs in wikis compatible with the Liquid engine
The floated tags (>toc and <toc) are invalid syntax because of the < and >
characters. We transform them to toc_left and toc_right instead.
2011-12-17 22:14:19 +01:00
Holger Just 7458bca34b Don't create arguments for legacy tags if there aren't any 2011-12-17 22:14:19 +01:00
Holger Just 60deeb5306 Move definiton of liquid legacy tags into the Legacy module.
This is required to retaiun the definition in development mode as the module
is stragely reloaded.
2011-12-17 22:01:20 +01:00
Holger Just 4656cf1c57 Escape raw input if there is a Liquid syntax error 2011-12-17 21:57:01 +01:00
Eric Davis d2ccdc88fa [#760] Extract quick search to a partial.
Contributed by Peer Allan
2011-12-10 12:28:53 -08:00
Eric Davis d32480753b [#692] Update the style of the issue show page to be cleaner 2011-12-10 12:22:38 -08:00
Eric Davis 9f0bd25523 [#692] i18n English string in view 2011-12-10 12:21:34 -08:00
Eric Davis 100faf94f7 [#692] Add some style to issue action menus 2011-12-10 12:16:54 -08:00
Eric Davis f6d0932bff [#692] Use i18n label for the More menu 2011-12-10 11:35:12 -08:00
Eric Davis 2a74699196 [#692] Make breadcrumbs larger and not completely underlined 2011-12-10 11:30:38 -08:00
Eric Davis ee48a8da4f Merge branch 'ticket/unstable/692-final-design' into unstable 2011-12-10 10:40:09 -08:00
Eric Davis 52ab42b5b0 [#692] Add missing closing tag 2011-12-09 19:26:21 -08:00
Eric Davis e8b8841717 [#692] Fix image names 2011-12-09 19:04:20 -08:00
Eric Davis f9a2e30b9f [#692] Fix issue history styles 2011-12-09 19:04:20 -08:00
Eric Davis f76c922dd8 [#692] Fix the context menu styles
It was conflicting with S&P theme styles due to the CSS load order,
reset them back to the pre-S&P theme style.
2011-12-09 19:04:20 -08:00
Eric Davis 8e6ccceb28 [#692] Remove selected gradient from context menu tables 2011-12-09 19:04:19 -08:00
Eric Davis a73c23ae9b [#692] Use text based logo 2011-12-09 19:04:19 -08:00
Eric Davis 0385979d5e [#692] Add register link back into the top menu 2011-12-09 19:04:19 -08:00
Eric Davis 612f2f98e4 [#692] Fix and simplify the top menu open/closing
* Remove bunch of extra code from choosen
* Fix awkward indention
* Add admin menu items to the Modules menu
* Turn off bold on menu item hover, was causing jumping in width
* Fix color on opened top menus
* Fix a bunch of edge cases in the menu clicks.
* Remove gross 'html' binding in favor of a one time only one
2011-12-09 19:04:08 -08:00
Eric Davis b312bf387a [#692] Cleanup layout structure from rebase 2011-12-09 18:06:19 -08:00
Eric Davis 971277b507 [#692] Fix failing test due to design changes 2011-12-09 18:06:19 -08:00
Eric Davis c33227ffe3 [#692] Update CSS by reviewing pre and post-merge diff 2011-12-09 18:06:18 -08:00
Eric Davis 32fa8cb522 [#692] Fix syntax errors and undefined methods in layout from merge 2011-12-09 18:06:18 -08:00
Romano Licker 83f8f63640 [#692] implemented widget design for mypage
- background gradient image missing
- made icon grey in order to keep it visible
- raised icon margin to position it directly inside the
  widget when in personalize mode
- added a background image to display the gradient
2011-12-09 18:06:18 -08:00
jwollert a1500a49b5 [#692] remove duplicate header from the bottom of the ticket show view 2011-12-09 18:06:18 -08:00
Romano Licker d22afea284 [#692] header-menu subentries closer together
2 columns instead of 3 for issue detail
2011-12-09 18:06:17 -08:00
Romano Licker e371bcad24 [#692] fixes admin-menu
new design on ticket view
filter / options / attachments fieldset redesign
2011-12-09 18:06:12 -08:00
Romano Licker 0eeeb04c31 [#692] removes div#admin-menu - now content of main menu 2011-12-09 14:04:02 -08:00
Romano Licker 729e801c9f [#692] renamed div#account to div#header cleanup 2011-12-09 14:03:59 -08:00
jwollert 7e287d6e5a [#692] fixes IE7 overflow bug in project search results 2011-12-09 13:57:24 -08:00
Eric Davis 7d1c0374e4 Merge branch 'ticket/unstable/747-capybara' into unstable 2011-12-09 13:28:33 -08:00
Felix Schäfer fbde6859d1 Merge branch 'rubychan-coderay-1.0' into unstable 2011-12-09 16:58:24 +01:00
Felix Schäfer 1c84fc0f4d Text corrections. 2011-12-09 16:57:48 +01:00
Felix Schäfer 8671006b70 [#649] Prevent the extra wiki styles from interfering with code highlighting. 2011-12-09 16:57:16 +01:00
Felix Schäfer aa88fe8e39 Merge branch 'coderay-1.0' of https://github.com/rubychan/chiliproject into rubychan-coderay-1.0 2011-12-09 16:50:20 +01:00
Eric Davis b32fc97314 [#747] Add some capybara helper methods 2011-12-01 18:13:27 -08:00
Eric Davis a2feb14c83 [#747] Include capybara for integration tests 2011-12-01 16:46:50 -08:00
Eric Davis 4b5d4307d2 Merge branch 'ticket/unstable/720-tagging' into unstable 2011-12-01 15:58:29 -08:00
Eric Davis f179fea7bf [#720] Use = to show the explicit dependency 2011-12-01 15:58:05 -08:00
Felix Schäfer 1d21cab2e4 Merge branch 'master' into unstable
Conflicts:
	app/views/projects/index.rhtml
	lib/chili_project/compatibility.rb
2011-12-01 17:46:05 +01:00
Holger Just 5bf27de92a [604] Add compatibility check for Liquid 2011-11-26 22:24:51 +01:00
Holger Just 0b952a1edd [#263] Fix test error on Ruby 1.9
TreeNode#to_s (from the rubytree gem) is broken. It is implicitly called by
Shoulda on 1.9 only. As we don't actually need it, we just remove the method.

The bug is reported at http://rubyforge.org/tracker/index.php?func=detail&aid=29435&group_id=1215&atid=4793
2011-11-26 20:36:56 +01:00
Holger Just 9f4d12ffdf [#734] Don't modify text inline.
This is not only a wee bit faster (yay!) but also allows use to deal with
frozen strings and doesn't change content which might inadvertently be saved
later on. And it fixes some broken tests on Ruby 1.9 :)
2011-11-26 16:10:24 +01:00
Holger Just 28ce07b1dd [#734] Force instance_variables to string for Ruby 1.9 compatibility 2011-11-26 16:09:08 +01:00
Eric Davis 01e43f2e6b Merge pull request #134 from schmidt/pulls/725/unstable
[#725] Compatibility check for jQuery availability (unstable)
2011-11-25 12:50:25 -08:00
Gregor Schmidt 3577550cfa [#725] Chili-core now loads jQuery by default
This was added to unstable with ab2856b53 and will be released with Chili 3.0
2011-11-25 20:24:06 +01:00
Eric Davis 8fc2b72740 [#720] Add acts-as-taggable-on gem and migration for tag support
As per the gem docs, 2.1.0 is the last Rails 2.x compatable version
2011-11-25 11:23:34 -08:00
Gregor Schmidt 13da5e0445 [#725] Add compatibility check for jQuery availability
In the current master a.k.a. ChiliProject 2.x, there is
no jQuery loaded by core.
2011-11-25 20:19:58 +01:00
Eric Davis e6fe1fc776 Merge remote-tracking branch 'meineerde/issues/unstable/604-liquid-rebased' into unstable 2011-11-25 01:44:19 -08:00
Eric Davis b1d9667335 Fix test so it runs in isolation 2011-11-20 02:49:22 +01:00
Holger Just a31a4a8852 [#604] Adapt fixtures for new liquid syntax 2011-11-20 02:49:22 +01:00
Eric Davis dedf696666 [#604] Add missing test for Liquid 2011-11-20 02:49:22 +01:00
Holger Just 464dafc1e7 [#604] We don't support the old escape style anymore 2011-11-20 02:49:22 +01:00
Holger Just f6805303fb [#604] Remove the leading newline from Liquid blocks for easier formatting 2011-11-20 02:49:21 +01:00
Holger Just 994132a51a [#604] Add nicer error formatting, similar to the old style 2011-11-20 02:49:21 +01:00
Holger Just fe6a79ac17 [#604] Add some handy filters 2011-11-20 02:49:21 +01:00
Holger Just cc0526cb27 [#604] Introduce compatibility layer for third party macros.
This be removed with complete macro removal.
2011-11-20 02:49:21 +01:00
Holger Just 862c9e0fde [#604] Adapt the upstream include mechanism to work with Wiki pages 2011-11-20 02:49:20 +01:00
Eric Davis 71ecdb57ed [#604] Port the child_pages to Liquid tag 2011-11-20 02:49:20 +01:00
Eric Davis 1e7dfe545e [#604] Port the macro_list to Liquid: variable_list and tag_list 2011-11-20 02:49:20 +01:00
Eric Davis 7778ff2f83 [#604] Port hello_world macro to liquid 2011-11-20 02:49:20 +01:00
Holger Just 29fe856916 [#604] Add base tag 2011-11-20 02:49:19 +01:00
Eric Davis 81ca15c52c [#604] Add ability to support legacy macros 2011-11-20 02:49:19 +01:00
Holger Just 82432f3f99 [#604] Evaluate Liquid before Textile-to-HTML transformation.
This changes how the liquid integration works. It now integrates the Textile
conversion step. This was necessary because if you first convert the snippets
inside of loops and conditionals from Textile to HTML, you loose some
important context information which is required to e.g. build proper lists in
textile.

We expect the standard case that Liquid tags return Textile markup instead
of HTML. Thus, we can convert the final textile markup to HTML as a very last
step.

To allow existing and new macros (or tags) to return HTML for advanced usage,
we save their respective output into the context and put a placeholder string
into the generated markup. After the transformation to HTML, we insert the
previously generated HTML into the string using search+replace in
lib/chili_project/liquid/template.rb. Tags have to be registered using
:html => true for this special treatment.
2011-11-20 02:49:19 +01:00
Eric Davis 72fa3ff920 [#604] Run the output of the WikiFormatted text into Liquid
The current view's instance variables are sent to liquid dynamically
2011-11-20 02:49:19 +01:00
Eric Davis 981143f78f [#604] Disable classic ChiliProject wiki macros 2011-11-20 02:49:18 +01:00
Eric Davis 7906afe6b9 [#604] Add an IssueDrop with custom field support 2011-11-20 02:49:12 +01:00
Eric Davis 6f8d8c4105 [#604] Add basic WikiPageDrop 2011-11-19 23:34:52 +01:00
Eric Davis 91914cb877 [#604] Add simple Liquid drops for Projects and Principals 2011-11-19 23:33:36 +01:00
Holger Just 7261622196 [#604] Add base drop 2011-11-19 23:31:58 +01:00
Eric Davis 4289559b5f [#604] Require the liquid gem 2011-11-19 23:31:05 +01:00
Eric Davis 1e41ec912b Guard against a LocalJumpError in Project#project_tree 2011-11-14 19:23:49 -08:00
Eric Davis af9572d985 Confirm before deleting issue categories 2011-11-14 19:10:13 -08:00
Eric Davis 5f3c6b87e4 Show email notification options in a project tree 2011-11-14 18:55:39 -08:00
Felix Schäfer b1671e46f0 Merge pull request #111 from edavis10/ticket/unstable/672-query-subprojects
#672 Allow queries to include subproject issues
2011-11-13 12:11:52 -08:00
jwollert edce16c89a [#692] enable jump to project box to take options (projects and html) 2011-11-12 16:22:31 -08:00
Romano Licker 977f74e11a [#692] Main design changes for new theme 2011-11-12 16:22:25 -08:00
Romano Licker c16cfd8ff1 [#692] images for new design 2011-11-12 16:22:24 -08:00
Romano Licker 734da91b4b [#692] login slidedown implemented with tabindex and focus fix 2011-11-12 16:22:24 -08:00
jwollert fecfbaf908 [#692] change project navigation arrow on hover
- fix project navigation arrows
2011-11-12 16:22:24 -08:00
Eric Davis 1897459b76 [#263] Refactor: move top menu logic to ApplicationHelper 2011-11-06 19:49:23 -08:00
Eric Davis 3e87af38d4 [#263] Make issue description the full width 2011-11-06 19:38:44 -08:00
Eric Davis 3409f5b620 [#658] Include jQuery v1.7, jquery ui 1.8.16, and smoothness theme 2011-11-06 19:21:56 -08:00
Eric Davis ab2856b53f [#263] Add new layout and theme
Merge branch 'ticket/unstable/263-new-layout-ready' into unstable
2011-11-06 18:18:35 -08:00
Eric Davis 7bc11f8de0 [#263] Tests require one theme being present 2011-11-06 17:46:55 -08:00
Eric Davis f10412ef71 [#263] Update tests for views in the new layout 2011-11-06 17:46:31 -08:00
Eric Davis a911d83889 [#263] Fix theme tests that changed singleton data 2011-11-06 16:39:28 -08:00
Eric Davis 5620488727 [#263] Removed and tweaks the design based on feedback 2011-11-06 16:39:15 -08:00
Eric Davis b160217299 Use full paths to test_helper.rb for metric_fu/rcov to pick up 2011-11-03 19:43:20 -07:00
Holger Just 9c8380667c Merge branch 'master' into unstable 2011-10-31 18:18:44 +01:00
Kornelius Kalnbach aa8237c546 fix application_helper_test 2011-10-29 13:44:47 +02:00
Kornelius Kalnbach 260a6bb1da fixed wiki syntaxhl issue 2011-10-29 12:20:11 +02:00
Kornelius Kalnbach 7b1a263d1d trying to fix wiki syntaxhl issue 2011-10-29 12:10:18 +02:00
Kornelius Kalnbach 65af1e4436 #649 update to CodeRay 1.0 according to Etienne's r7618 in Redmine 2011-10-29 11:35:16 +02:00
Eric Davis a2514ecc54 [#263] Remove Shane and Peter theme 2011-10-28 15:22:20 -07:00
Eric Davis f31f44f4af [#263] Remove dead context menu code 2011-10-28 15:21:37 -07:00
Eric Davis cce496b011 [#263] Remove placeholder thumbnail images 2011-10-28 15:20:09 -07:00
Eric Davis e51f067f12 Merge branch 'ticket/unstable/263-new-layout' into unstable 2011-10-28 15:12:59 -07:00
Eric Davis 8d10daa68f [#263] Add additional wiki styles 2011-10-28 14:58:50 -07:00
Eric Davis b968d260fd [#263] Remove DOM hacks to reposition elements 2011-10-28 14:54:09 -07:00
Eric Davis 34ffbc8e46 [#263] Remove dead code, was unbound in later JS 2011-10-28 14:53:49 -07:00
Eric Davis cbb99f81a1 Merge multiple jquery ready blocks 2011-10-28 14:48:38 -07:00
Eric Davis 923cdf3ff3 Refactor duplicated and abbreviated animationRate variable 2011-10-28 14:45:30 -07:00
Eric Davis a592f024cb [#263] Remove new issue lightbox (dead code) 2011-10-28 14:43:47 -07:00
Eric Davis dc682eaddc [#263] Remove issue tooltip javascript 2011-10-28 14:41:20 -07:00
Eric Davis aed2b4f661 [#263] Update jquery load order and embed noConflict in the HTML head directly 2011-10-28 14:36:10 -07:00
Eric Davis d99bc8a066 [#263] Replace image background with a theme-able css3 background 2011-10-28 14:35:36 -07:00
Eric Davis 5e2d633ebb [#263] Merge common.js into application.js 2011-10-28 14:26:14 -07:00
Eric Davis ca3eeedfff [#263] Remove profile popups, never finished 2011-10-28 14:21:41 -07:00
Eric Davis 155e1ba2a7 [#263] Remove thickbox 2011-10-28 14:18:53 -07:00
Eric Davis 2b7a221dee [#672] Allow each Query to control if subprojects are included or not 2011-10-25 18:13:32 -07:00
Eric Davis 061beb4967 Merge remote-tracking branch 'finnlabs/pulls/653/move-query-models-into-separate-files' into unstable 2011-10-14 16:40:08 -07:00
Gregor Schmidt c1ecadbff7 Moving query related models into separate files
This should enable easier overwriting/reloading in plugins, since now the autoloader is able to find the models.
2011-10-12 11:35:26 +02:00
Holger Just c87738daf2 Merge branch 'stable' into unstable 2011-10-04 17:37:14 +02:00
Holger Just 2a3e12927f Merge branch 'stable' into unstable 2011-08-27 18:56:51 +02:00
Eric Davis 27fd103132 [#263] Override some defaults in the sap theme 2011-08-05 12:12:39 -07:00
Eric Davis a9d6518a5d [#263] Set up default styles 2011-08-05 12:09:16 -07:00
Eric Davis d8023e6949 [#263] Remove duplicated favicon 2011-08-05 11:36:55 -07:00
Eric Davis 667f85f9a5 [#263] Extract custom S&P images 2011-08-05 11:35:05 -07:00
Eric Davis 7f06e7394c [#263] Update SAP theme 2011-08-05 11:32:21 -07:00
Eric Davis d94f7f7f9b [#263] Remove old themes 2011-08-05 11:29:13 -07:00
Eric Davis 949d229629 [#263] Extract custom colors to color.css 2011-08-05 11:21:29 -07:00
Eric Davis ccad72ba85 [#263] Move the fugue images into the main images/ directory 2011-08-05 10:50:08 -07:00
Eric Davis 4b8f4c3b83 [#263] Allow setting the page header title 2011-08-05 10:45:48 -07:00
Eric Davis d63cb35e82 [#263] Add icons to the main menu 2011-08-05 10:41:29 -07:00
Eric Davis cfed74579d [#263] Cleaning up admin menus 2011-08-05 10:22:54 -07:00
Eric Davis 9bb8f80d7e [#263] Merging css
* Append main.css to the bottom of application.css
* Remove main.css
* Move print styles out to print.css
* Move the CSS reset to be loaded first
* Port some colors to color.css
2011-08-05 09:56:13 -07:00
Eric Davis b4804f4ce4 [#263] Merged redmine-reset.css into application.css 2011-08-05 09:28:52 -07:00
Eric Davis adffd044d7 [#263] Fix the missing line on the top menu 2011-08-05 09:05:52 -07:00
Eric Davis ffa2d5f319 [#263] Remove the tree of parent issues from the issue title 2011-08-05 09:01:03 -07:00
Eric Davis 156cdf316e [#263] Remove duplicated updated label 2011-08-05 08:56:28 -07:00
Eric Davis aed0a6d5c3 Merge branch 'stable' into unstable 2011-08-01 16:57:22 -07:00
Eric Davis 635923f90d [#263] Fix admin menu sidebar styling 2011-07-29 15:33:59 -07:00
Eric Davis 8f45e780ce [#263] Fix issue subject style 2011-07-29 15:33:43 -07:00
Eric Davis 124c98d4ba [#263] Fix for changes to the admin_menu 2011-07-29 15:06:09 -07:00
Eric Davis 8c183dda2e [#263] Fix XSS regression from merge 2011-07-29 15:04:22 -07:00
Eric Davis e7e4557009 [#263] Fix journal formatting 2011-07-29 15:04:03 -07:00
Eric Davis 5cedd5cdcb [#263] Don't output heads_for_wiki_formatter 2011-07-29 14:46:39 -07:00
Eric Davis cf40447356 Fix the thickbox loading image path 2011-07-29 14:43:33 -07:00
Eric Davis 61847d831a CSS cleanup for the sidebar 2011-07-29 14:43:33 -07:00
Eric Davis 6c9c85134a Fix the issue action bar CSS 2011-07-29 14:43:33 -07:00
Eric Davis 6435905132 Port issue show view 2011-07-29 14:43:33 -07:00
Eric Davis ce973429fe Move the Add link next to the Related issues label 2011-07-29 14:37:24 -07:00
Eric Davis cff91d8cf0 Port id selectors to the main issues list 2011-07-29 14:37:24 -07:00
Eric Davis 13f57032c1 Add the new issue button partial 2011-07-29 14:37:24 -07:00
Eric Davis 7485858da2 Cleanup styles in the issue filter section 2011-07-29 14:37:24 -07:00
Eric Davis 2dc0e100c3 Port the issue filter section on the issues list 2011-07-29 14:37:24 -07:00
Eric Davis 5949f8de91 Port issue history changes 2011-07-29 14:37:24 -07:00
Eric Davis ec21b4c075 Merge issue context menu changes 2011-07-29 14:32:53 -07:00
Eric Davis 84ef979c47 Add welcome text to the project list 2011-07-29 14:31:27 -07:00
Eric Davis 6d522a9acd Remove the list of versions from sidebar, will be moved to menu 2011-07-29 14:31:26 -07:00
Eric Davis 48675d63a5 Clean up footer 2011-07-29 14:31:26 -07:00
Eric Davis bfa57e5e6c Fix hardcoded image path 2011-07-29 14:31:26 -07:00
Eric Davis ac48b91864 Add images 2011-07-29 14:31:26 -07:00
Eric Davis d3392c6d0d Add JavaScript 2011-07-29 14:31:26 -07:00
Eric Davis a0c777ee79 Add the CSS, including resets 2011-07-29 14:31:26 -07:00
Eric Davis c23f73e9fe Port ApplicationHelper patches 2011-07-29 14:31:26 -07:00
Eric Davis 484ed29fa2 Port main layout 2011-07-29 14:29:23 -07:00
Eric Davis 1f57eb01b1 Merge HTML head section 2011-07-29 14:25:35 -07:00
Eric Davis 74cfd7b5ca Merge branch 'stable' into unstable 2011-07-29 13:54:27 -07:00
Eric Davis ddeb1a2a0f Merge branch 'stable' into unstable 2011-07-01 22:16:58 -07:00
Eric Davis 0f35c7d1c2 Fix test on 1.9.2 postgres.
Multiple records were found and postgres was returning them in a different
order than what the test assumed.
2011-06-03 14:30:48 -07:00
Eric Davis 3a0a7d93b1 Merge branch 'release-v2.0.0' into unstable
Merging during the release to make sure everything is passing in CI,
which isn't set up to test release branches.
2011-06-03 11:09:06 -07:00
Eric Davis f0987c8238 [#437] Move the encoding to the top of pdf.rb for Ruby 1.9 2011-05-30 15:11:08 -07:00
867 changed files with 15016 additions and 12444 deletions

4
.gitignore vendored
View File

@ -4,6 +4,7 @@
/config/configuration.yml /config/configuration.yml
/config/database.yml /config/database.yml
/config/email.yml /config/email.yml
/config/setup_load_paths.rb
/config/initializers/session_store.rb /config/initializers/session_store.rb
/coverage /coverage
/db/*.db /db/*.db
@ -28,3 +29,6 @@ doc/app
/Gemfile.lock /Gemfile.lock
/Gemfile.local /Gemfile.local
/.rvmrc* /.rvmrc*
/*.iml
/.idea
.rbx

59
.travis.yml Normal file
View File

@ -0,0 +1,59 @@
language: ruby
rvm:
- 1.8.7
- 1.9.2
- 1.9.3
- rbx-18mode
env:
- "TEST_SUITE=units RAILS_ENV=test DB=mysql BUNDLE_WITHOUT=rmagick:mysql2:postgres:sqlite"
- "TEST_SUITE=units RAILS_ENV=test DB=mysql2 BUNDLE_WITHOUT=rmagick:mysql:postgres:sqlite"
- "TEST_SUITE=units RAILS_ENV=test DB=postgres BUNDLE_WITHOUT=rmagick:mysql:mysql2:sqlite"
- "TEST_SUITE=units RAILS_ENV=test DB=sqlite BUNDLE_WITHOUT=rmagick:mysql:mysql2:postgres"
- "TEST_SUITE=functionals RAILS_ENV=test DB=mysql BUNDLE_WITHOUT=rmagick:mysql2:postgres:sqlite"
- "TEST_SUITE=functionals RAILS_ENV=test DB=mysql2 BUNDLE_WITHOUT=rmagick:mysql:postgres:sqlite"
- "TEST_SUITE=functionals RAILS_ENV=test DB=postgres BUNDLE_WITHOUT=rmagick:mysql:mysql2:sqlite"
- "TEST_SUITE=functionals RAILS_ENV=test DB=sqlite BUNDLE_WITHOUT=rmagick:mysql:mysql2:postgres"
- "TEST_SUITE=integration RAILS_ENV=test DB=mysql BUNDLE_WITHOUT=rmagick:mysql2:postgres:sqlite"
- "TEST_SUITE=integration RAILS_ENV=test DB=mysql2 BUNDLE_WITHOUT=rmagick:mysql:postgres:sqlite"
- "TEST_SUITE=integration RAILS_ENV=test DB=postgres BUNDLE_WITHOUT=rmagick:mysql:mysql2:sqlite"
- "TEST_SUITE=integration RAILS_ENV=test DB=sqlite BUNDLE_WITHOUT=rmagick:mysql:mysql2:postgres"
matrix:
exclude:
- rvm: 1.9.2
env: "TEST_SUITE=units RAILS_ENV=test DB=mysql BUNDLE_WITHOUT=rmagick:mysql2:postgres:sqlite"
- rvm: 1.9.2
env: "TEST_SUITE=functionals RAILS_ENV=test DB=mysql BUNDLE_WITHOUT=rmagick:mysql2:postgres:sqlite"
- rvm: 1.9.2
env: "TEST_SUITE=integration RAILS_ENV=test DB=mysql BUNDLE_WITHOUT=rmagick:mysql2:postgres:sqlite"
- rvm: 1.9.3
env: "TEST_SUITE=units RAILS_ENV=test DB=mysql BUNDLE_WITHOUT=rmagick:mysql2:postgres:sqlite"
- rvm: 1.9.3
env: "TEST_SUITE=functionals RAILS_ENV=test DB=mysql BUNDLE_WITHOUT=rmagick:mysql2:postgres:sqlite"
- rvm: 1.9.3
env: "TEST_SUITE=integration RAILS_ENV=test DB=mysql BUNDLE_WITHOUT=rmagick:mysql2:postgres:sqlite"
- rvm: rbx-18mode
env: "TEST_SUITE=units RAILS_ENV=test DB=mysql BUNDLE_WITHOUT=rmagick:mysql2:postgres:sqlite"
- rvm: rbx-18mode
env: "TEST_SUITE=functionals RAILS_ENV=test DB=mysql BUNDLE_WITHOUT=rmagick:mysql2:postgres:sqlite"
- rvm: rbx-18mode
env: "TEST_SUITE=integration RAILS_ENV=test DB=mysql BUNDLE_WITHOUT=rmagick:mysql2:postgres:sqlite"
allow_failures:
- rvm: rbx-18mode
before_install:
- "sudo apt-get update -qq"
- "sudo apt-get --no-install-recommends install bzr cvs git mercurial subversion"
before_script:
- "rvm rubygems 1.8.25" # Rubygems 2.0.x fails with Rails 2.3
- "rake ci:travis:prepare"
- "rm -rf tmp/test/darcs_repository" # Don't test Darcs on Travis. It breaks there :(
script: "bundle exec rake test:$TEST_SUITE"
branches:
only:
- unstable
- master
- stable
- /^stable-.*$/
- /^release-.*$/
notifications:
email: false
irc: "irc.freenode.org#chiliproject"

33
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,33 @@
For the impatient: [report][cpo_new-issue], confirm, claim,
[fork][gh_chiliproject], [branch][cpo_contribute-code-branch],
[write][cpo_code-standards], [test][cpo_code-review], push.
The short version:
* Make sure the issue you are working on is reported and confirmed, add a note
to the issue to claim your intention to work on it.
* Fork [ChiliProject on GitHub][gh_chiliproject]
* Create a new branch from `master` with a descriptive name prefixed by the
issue ID (Example: `123-change_background_from_black_to_blue`).
* Make changes according to our [Code Standards][cpo_code-standards].
1. Be sure to include tests as necessary.
1. Make sure to not break existing tests.
1. Please try to make sure your code is going to pass a [Code
Review][cpo_code-review] prior to submitting the patch. If in doubt, just ask.
* Either upload your branch to GitHub and send a pull request to the
ChiliProject repository, or attach a patch to the issue on ChiliProject. If
you send a pull request on GitHub, remember to link to the pull request in the
issue you create on ChiliProject and to link to the issue on ChiliProject in
the pull request on GitHub.
* Make sure you watch the corresponding issue in case any discussion arises or
improvements are needed.
The long version is on the [Contribute Code][cpo_contribute-code] page.
[cpo_new-issue]: https://www.chiliproject.org/projects/chiliproject/issues/new
[cpo_contribute-code-branch]: https://www.chiliproject.org/projects/chiliproject/wiki/Contribute_Code#Branch
[cpo_contribute-code]: https://www.chiliproject.org/projects/chiliproject/wiki/Contribute_Code
[cpo_code-standards]: https://www.chiliproject.org/projects/chiliproject/wiki/Code_Standards
[cpo_code-review]: https://www.chiliproject.org/projects/chiliproject/wiki/Code_Review
[gh_chiliproject]: https://github.com/chiliproject/chiliproject

27
Gemfile
View File

@ -1,23 +1,32 @@
source :rubygems # -*- coding: utf-8 -*-
source "https://rubygems.org"
gem "rails", "2.3.16" gem "rails", "2.3.18"
gem "coderay", "~> 0.9.7" gem "json", "~> 1.7.7"
gem "coderay", "~> 1.0.0"
gem "i18n", "~> 0.4.2" gem "i18n", "~> 0.4.2"
gem "rubytree", "~> 0.5.2", :require => 'tree' gem "rubytree", "~> 0.5.2", :require => 'tree'
gem "rdoc", ">= 2.4.2" 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 # 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 "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 group :test do
gem 'shoulda', '~> 2.10.3' 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 'edavis10-object_daddy', :require => 'object_daddy'
gem 'mocha' gem 'mocha', '0.12.1'
gem 'capybara' # capybara 2 drops ruby 1.8.7 compatibility
gem 'capybara', '< 2.0.0'
end end
group :ldap do group :ldap do
gem "net-ldap", '~> 0.2.2' gem "net-ldap", '~> 0.3.1'
end end
group :openid do group :openid do
@ -48,13 +57,13 @@ end
# orders of magnitude compared to their native counterparts. You have been # orders of magnitude compared to their native counterparts. You have been
# warned. # warned.
platforms :mri, :mingw do platforms :mri, :mingw, :rbx do
group :mysql2 do group :mysql2 do
gem "mysql2", "~> 0.2.7" gem "mysql2", "~> 0.2.7"
end end
group :postgres do group :postgres do
gem "pg", "~> 0.9.0" gem "pg"
# gem "postgres-pr" # gem "postgres-pr"
end end
end end
@ -70,7 +79,7 @@ platforms :mri_18, :mingw_18 do
end end
end end
platforms :mri_19, :mingw_19 do platforms :mri_19, :mingw_19, :rbx do
group :sqlite do group :sqlite do
gem "sqlite3" gem "sqlite3"
end end

View File

@ -1,4 +1,4 @@
= ChiliProject = ChiliProject {<img src="https://travis-ci.org/chiliproject/chiliproject.png?branch=master" />}[http://travis-ci.org/chiliproject/chiliproject]
ChiliProject is a web based project management system. It supports your team throughout the complete project life cycle, from setting up and discussing a project plan, over tracking issues and reporting work progress to collaboratively sharing knowledge. ChiliProject is a web based project management system. It supports your team throughout the complete project life cycle, from setting up and discussing a project plan, over tracking issues and reporting work progress to collaboratively sharing knowledge.

View File

@ -8,3 +8,5 @@ require 'rake/testtask'
require 'rdoc/task' require 'rdoc/task'
require 'tasks/rails' require 'tasks/rails'
# Load rake tasks from plugins in chiliproject_plugins
Dir["#{RAILS_ROOT}/vendor/chiliproject_plugins/*/lib/tasks/**/*.rake"].sort.each { |ext| load ext }

View File

@ -37,7 +37,7 @@ class AccountController < ApplicationController
def lost_password def lost_password
redirect_to(home_url) && return unless Setting.lost_password? redirect_to(home_url) && return unless Setting.lost_password?
if params[:token] if params[:token]
@token = Token.find_by_action_and_value("recovery", params[:token]) @token = Token.find_by_action_and_value("recovery", params[:token].to_s)
redirect_to(home_url) && return unless @token and !@token.expired? redirect_to(home_url) && return unless @token and !@token.expired?
@user = @token.user @user = @token.user
if request.post? if request.post?
@ -53,7 +53,7 @@ class AccountController < ApplicationController
return return
else else
if request.post? if request.post?
user = User.find_by_mail(params[:mail]) user = User.find_by_mail(params[:mail].to_s)
# user not found in db # user not found in db
(flash.now[:error] = l(:notice_account_unknown_email); return) unless user (flash.now[:error] = l(:notice_account_unknown_email); return) unless user
# user uses an external authentification # user uses an external authentification
@ -109,7 +109,7 @@ class AccountController < ApplicationController
# Token based account activation # Token based account activation
def activate def activate
redirect_to(home_url) && return unless Setting.self_registration? && params[:token] redirect_to(home_url) && return unless Setting.self_registration? && params[:token]
token = Token.find_by_action_and_value('register', params[:token]) token = Token.find_by_action_and_value('register', params[:token].to_s)
redirect_to(home_url) && return unless token and !token.expired? redirect_to(home_url) && return unless token and !token.expired?
user = token.user user = token.user
redirect_to(home_url) && return unless user.registered? redirect_to(home_url) && return unless user.registered?

View File

@ -33,7 +33,7 @@ class ActivitiesController < ApplicationController
:with_subprojects => @with_subprojects, :with_subprojects => @with_subprojects,
:author => @author) :author => @author)
@activity.scope_select {|t| !params["show_#{t}"].nil?} @activity.scope_select {|t| !params["show_#{t}"].nil?}
@activity.scope = (@author.nil? ? :default : :all) if @activity.scope.empty? @activity.scope = (@author.nil? ? :default : :all) if @activity.scope.empty? unless params[:set_filter]
events = @activity.events(@date_from, @date_to) events = @activity.events(@date_from, @date_to)

View File

@ -19,6 +19,10 @@ class AdminController < ApplicationController
include SortHelper include SortHelper
menu_item :projects, :only => [:projects]
menu_item :plugins, :only => [:plugins]
menu_item :info, :only => [:info]
def index def index
@no_configuration_data = Redmine::DefaultData::Loader::no_data? @no_configuration_data = Redmine::DefaultData::Loader::no_data?
end end
@ -73,7 +77,7 @@ class AdminController < ApplicationController
def info def info
@db_adapter_name = ActiveRecord::Base.connection.adapter_name @db_adapter_name = ActiveRecord::Base.connection.adapter_name
@checklist = [ @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_file_repository_writable, File.writable?(Attachment.storage_path)],
[:text_plugin_assets_writable, File.writable?(Engines.public_directory)], [:text_plugin_assets_writable, File.writable?(Engines.public_directory)],
[:text_rmagick_available, Object.const_defined?(:Magick)] [:text_rmagick_available, Object.const_defined?(:Magick)]

View File

@ -31,18 +31,6 @@ class ApplicationController < ActionController::Base
cookies.delete(:autologin) cookies.delete(:autologin)
end 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 # FIXME: Remove this when all of Rack and Rails have learned how to
# properly use encodings # properly use encodings
before_filter :params_filter before_filter :params_filter
@ -64,7 +52,16 @@ class ApplicationController < ActionController::Base
before_filter :user_setup, :check_if_login_required, :set_localization before_filter :user_setup, :check_if_login_required, :set_localization
filter_parameter_logging :password filter_parameter_logging :password
rescue_from ActionController::InvalidAuthenticityToken, :with => :invalid_authenticity_token # FIXME: This doesn't work with Rails >= 3.0 anymore
# Possible workaround: https://github.com/rails/rails/issues/671#issuecomment-1780159
rescue_from ActionController::RoutingError, :with => proc{
# manually apply basic before_filters which aren't applied by default here
user_setup
check_if_login_required
set_localization
render_404
}
include Redmine::Search::Controller include Redmine::Search::Controller
include Redmine::MenuManager::MenuController include Redmine::MenuManager::MenuController
@ -75,8 +72,6 @@ class ApplicationController < ActionController::Base
end end
def user_setup def user_setup
# Check the settings cache for each request
Setting.check_cache
# Find the current user # Find the current user
User.current = find_current_user User.current = find_current_user
end end
@ -94,11 +89,11 @@ class ApplicationController < ActionController::Base
user user
elsif params[:format] == 'atom' && params[:key] && accept_key_auth_actions.include?(params[:action]) elsif params[:format] == 'atom' && params[:key] && accept_key_auth_actions.include?(params[:action])
# RSS key authentication does not start a session # RSS key authentication does not start a session
User.find_by_rss_key(params[:key]) User.find_by_rss_key(params[:key].to_s)
elsif Setting.rest_api_enabled? && api_request? elsif Setting.rest_api_enabled? && api_request?
if (key = api_key_from_request) && accept_key_auth_actions.include?(params[:action]) if (key = api_key_from_request) && accept_key_auth_actions.include?(params[:action])
# Use API key # Use API key
User.find_by_api_key(key) User.find_by_api_key(key.to_s)
else else
# HTTP Basic, either username/password or API key/random # HTTP Basic, either username/password or API key/random
authenticate_with_http_basic do |username, password| authenticate_with_http_basic do |username, password|
@ -335,13 +330,6 @@ class ApplicationController < ActionController::Base
request.xhr? ? false : 'base' request.xhr? ? false : 'base'
end end
def invalid_authenticity_token
if api_request?
logger.error "Form authenticity token is missing or is invalid. API calls must include a proper Content-type header (text/xml or text/json)."
end
render_error "Invalid form authenticity token."
end
def render_feed(items, options={}) def render_feed(items, options={})
@items = items || [] @items = items || []
@items.sort! {|x,y| y.event_datetime <=> x.event_datetime } @items.sort! {|x,y| y.event_datetime <=> x.event_datetime }

View File

@ -13,7 +13,8 @@
#++ #++
class AutoCompletesController < ApplicationController class AutoCompletesController < ApplicationController
before_filter :find_project before_filter :find_project, :only => :issues
before_filter :require_admin, :only => :projects
def issues def issues
@issues = [] @issues = []
@ -33,6 +34,38 @@ class AutoCompletesController < ApplicationController
render :layout => false render :layout => false
end end
def users
if params[:remove_group_members].present?
@group = Group.find(params[:remove_group_members])
@removed_users = @group.users
end
if params[:remove_watchers].present? && params[:klass].present?
watcher_class = params[:klass].constantize
if watcher_class.included_modules.include?(Redmine::Acts::Watchable) # check class is a watching class
@object = watcher_class.find(params[:remove_watchers])
@removed_users = @object.watcher_users
end
end
@removed_users ||= []
if params[:include_groups]
user_finder = Principal
else
user_finder = User
end
@users = user_finder.active.like(params[:q]).find(:all, :limit => 100) - @removed_users
render :layout => false
end
def projects
@principal = Principal.find(params[:id])
@projects = Project.active.like(params[:q]).find(:all, :limit => 100) - @principal.projects
render :layout => false
end
private private
def find_project def find_project

View File

@ -21,7 +21,10 @@ class CommentsController < ApplicationController
verify :method => :post, :only => :create, :render => {:nothing => true, :status => :method_not_allowed } verify :method => :post, :only => :create, :render => {:nothing => true, :status => :method_not_allowed }
def create def create
@comment = Comment.new(params[:comment]) raise Unauthorized unless @news.commentable?
@comment = Comment.new
@comment.safe_attributes = params[:comment]
@comment.author = User.current @comment.author = User.current
if @news.comments << @comment if @news.comments << @comment
flash[:notice] = l(:label_comment_added) flash[:notice] = l(:label_comment_added)

View File

@ -45,21 +45,34 @@ class DocumentsController < ApplicationController
def new def new
@document = @project.documents.build @document = @project.documents.build
@document.safe_attributes = params[:document] @document.safe_attributes = params[:document]
if request.post? && @document.save if request.post?
if User.current.allowed_to?(:add_document_watchers, @project) && params[:document]['watcher_user_ids'].present?
@document.watcher_user_ids = params[:document]['watcher_user_ids']
end
if @document.save
attachments = Attachment.attach_files(@document, params[:attachments]) attachments = Attachment.attach_files(@document, params[:attachments])
render_attachment_warning_if_needed(@document) render_attachment_warning_if_needed(@document)
flash[:notice] = l(:notice_successful_create) flash[:notice] = l(:notice_successful_create)
redirect_to :action => 'index', :project_id => @project redirect_to :action => 'index', :project_id => @project
end end
end end
end
def edit def edit
@categories = DocumentCategory.all @categories = DocumentCategory.all
if request.post? and @document.update_attributes(params[:document])
if request.post?
if User.current.allowed_to?(:add_document_watchers, @project) && params[:document]['watcher_user_ids'].present?
@document.watcher_user_ids = params[:document]['watcher_user_ids']
end
if @document.update_attributes(params[:document])
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_to :action => 'show', :id => @document redirect_to :action => 'show', :id => @document
end end
end end
end
def destroy def destroy
@document.destroy @document.destroy
@ -70,7 +83,12 @@ class DocumentsController < ApplicationController
attachments = Attachment.attach_files(@document, params[:attachments]) attachments = Attachment.attach_files(@document, params[:attachments])
render_attachment_warning_if_needed(@document) render_attachment_warning_if_needed(@document)
Mailer.deliver_attachments_added(attachments[:files]) if attachments.present? && attachments[:files].present? && Setting.notified_events.include?('document_added') if attachments.present? && attachments[:files].present? && Setting.notified_events.include?('document_added')
# TODO: refactor
@document.recipients.each do |recipient|
Mailer.deliver_attachments_added(attachments[:files], recipient)
end
end
redirect_to :action => 'show', :id => @document redirect_to :action => 'show', :id => @document
end end

View File

@ -42,7 +42,11 @@ class FilesController < ApplicationController
render_attachment_warning_if_needed(container) render_attachment_warning_if_needed(container)
if !attachments.empty? && !attachments[:files].blank? && Setting.notified_events.include?('file_added') if !attachments.empty? && !attachments[:files].blank? && Setting.notified_events.include?('file_added')
Mailer.deliver_attachments_added(attachments[:files]) # TODO: refactor
recipients = attachments[:files].first.container.project.notified_users.select {|user| user.allowed_to?(:view_files, container.project)}.collect {|u| u.mail}
recipients.each do |recipient|
Mailer.deliver_attachments_added(attachments[:files], recipient)
end
end end
redirect_to project_files_path(@project) redirect_to project_files_path(@project)
end end

View File

@ -126,16 +126,19 @@ class GroupsController < ApplicationController
end end
end end
def autocomplete_for_user
@group = Group.find(params[:id])
@users = User.active.not_in_group(@group).like(params[:q]).all(:limit => 100)
render :layout => false
end
def edit_membership def edit_membership
@group = Group.find(params[:id]) @group = Group.find(params[:id])
if params[:project_ids] # Multiple memberships, one per project
params[:project_ids].each do |project_id|
@membership = Member.edit_membership(params[:membership_id], (params[:membership]|| {}).merge(:project_id => project_id), @group)
@membership.save if request.post?
end
else # Single membership
@membership = Member.edit_membership(params[:membership_id], params[:membership], @group) @membership = Member.edit_membership(params[:membership_id], params[:membership], @group)
@membership.save if request.post? @membership.save if request.post?
end
respond_to do |format| respond_to do |format|
if @membership.valid? if @membership.valid?
format.html { redirect_to :controller => 'groups', :action => 'edit', :id => @group, :tab => 'memberships' } format.html { redirect_to :controller => 'groups', :action => 'edit', :id => @group, :tab => 'memberships' }

View File

@ -12,6 +12,8 @@
# See doc/COPYRIGHT.rdoc for more details. # See doc/COPYRIGHT.rdoc for more details.
#++ #++
require 'diff'
class JournalsController < ApplicationController class JournalsController < ApplicationController
before_filter :find_journal, :only => [:edit, :diff] before_filter :find_journal, :only => [:edit, :diff]
before_filter :find_issue, :only => [:new] before_filter :find_issue, :only => [:new]
@ -84,6 +86,22 @@ class JournalsController < ApplicationController
end end
end end
def diff
if valid_field?(params[:field])
from = @journal.changes[params[:field]][0]
to = @journal.changes[params[:field]][1]
@diff = Redmine::Helpers::Diff.new(to, from)
@issue = @journal.journaled
respond_to do |format|
format.html { }
format.js { render :layout => false }
end
else
render_404
end
end
private private
def find_journal def find_journal
@ -100,4 +118,9 @@ class JournalsController < ApplicationController
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound
render_404 render_404
end end
# Is this a valid field for diff'ing?
def valid_field?(field)
field.to_s.strip == "description"
end
end end

View File

@ -14,6 +14,7 @@
class LdapAuthSourcesController < AuthSourcesController class LdapAuthSourcesController < AuthSourcesController
menu_item :ldap_authentication, :only => [:index]
protected protected
def auth_source_class def auth_source_class

View File

@ -107,7 +107,7 @@ class MessagesController < ApplicationController
content = "#{ll(Setting.default_language, :text_user_wrote, user)}\\n> " content = "#{ll(Setting.default_language, :text_user_wrote, user)}\\n> "
content << text.to_s.strip.gsub(%r{<pre>((.|\s)*?)</pre>}m, '[...]').gsub('"', '\"').gsub(/(\r?\n|\r\n?)/, "\\n> ") + "\\n\\n" content << text.to_s.strip.gsub(%r{<pre>((.|\s)*?)</pre>}m, '[...]').gsub('"', '\"').gsub(/(\r?\n|\r\n?)/, "\\n> ") + "\\n\\n"
render(:update) { |page| render(:update) { |page|
page << "$('reply_subject').value = \"#{subject}\";" page << "$('message_subject').value = \"#{subject}\";"
page.<< "$('message_content').value = \"#{content}\";" page.<< "$('message_content').value = \"#{content}\";"
page.show 'reply' page.show 'reply'
page << "Form.Element.focus('message_content');" page << "Form.Element.focus('message_content');"

View File

@ -20,6 +20,7 @@ class QueriesController < ApplicationController
def new def new
@query = Query.new(params[:query]) @query = Query.new(params[:query])
@query.project = params[:query_is_for_all] ? nil : @project @query.project = params[:query_is_for_all] ? nil : @project
@query.display_subprojects = params[:display_subprojects] if params[:display_subprojects].present?
@query.user = User.current @query.user = User.current
@query.is_public = false unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin? @query.is_public = false unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin?
@ -42,6 +43,7 @@ class QueriesController < ApplicationController
@query.add_filters(params[:fields] || params[:f], params[:operators] || params[:op], params[:values] || params[:v]) if params[:fields] || params[:f] @query.add_filters(params[:fields] || params[:f], params[:operators] || params[:op], params[:values] || params[:v]) if params[:fields] || params[:f]
@query.attributes = params[:query] @query.attributes = params[:query]
@query.project = nil if params[:query_is_for_all] @query.project = nil if params[:query_is_for_all]
@query.display_subprojects = params[:display_subprojects] if params[:display_subprojects].present?
@query.is_public = false unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin? @query.is_public = false unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin?
@query.group_by ||= params[:group_by] @query.group_by ||= params[:group_by]
@query.column_names = params[:c] if params[:c] @query.column_names = params[:c] if params[:c]

View File

@ -72,8 +72,7 @@ class TimeEntryReportsController < ApplicationController
@periods = [] @periods = []
# Date#at_beginning_of_ not supported in Rails 1.2.x # Date#at_beginning_of_ not supported in Rails 1.2.x
date_from = @from.to_time date_from = @from.to_time
# 100 columns max while date_from <= @to.to_time
while date_from <= @to.to_time && @periods.length < 100
case @columns case @columns
when 'year' when 'year'
@periods << "#{date_from.year}" @periods << "#{date_from.year}"
@ -161,6 +160,9 @@ class TimeEntryReportsController < ApplicationController
@available_criterias = { 'project' => {:sql => "#{TimeEntry.table_name}.project_id", @available_criterias = { 'project' => {:sql => "#{TimeEntry.table_name}.project_id",
:klass => Project, :klass => Project,
:label => :label_project}, :label => :label_project},
'status' => {:sql => "#{Issue.table_name}.status_id",
:klass => IssueStatus,
:label => :field_status},
'version' => {:sql => "#{Issue.table_name}.fixed_version_id", 'version' => {:sql => "#{Issue.table_name}.fixed_version_id",
:klass => Version, :klass => Version,
:label => :label_version}, :label => :label_version},

View File

@ -197,8 +197,16 @@ class UsersController < ApplicationController
def edit_membership def edit_membership
if params[:project_ids] # Multiple memberships, one per project
params[:project_ids].each do |project_id|
@membership = Member.edit_membership(params[:membership_id], (params[:membership] || {}).merge(:project_id => project_id), @user)
@membership.save if request.post?
end
else # Single membership
@membership = Member.edit_membership(params[:membership_id], params[:membership], @user) @membership = Member.edit_membership(params[:membership_id], params[:membership], @user)
@membership.save if request.post? @membership.save if request.post?
end
respond_to do |format| respond_to do |format|
if @membership.valid? if @membership.valid?
format.html { redirect_to :controller => 'users', :action => 'edit', :id => @user, :tab => 'memberships' } format.html { redirect_to :controller => 'users', :action => 'edit', :id => @user, :tab => 'memberships' }

View File

@ -16,6 +16,7 @@ class WatchersController < ApplicationController
before_filter :find_project before_filter :find_project
before_filter :require_login, :check_project_privacy, :only => [:watch, :unwatch] before_filter :require_login, :check_project_privacy, :only => [:watch, :unwatch]
before_filter :authorize, :only => [:new, :destroy] before_filter :authorize, :only => [:new, :destroy]
before_filter :authorize_access_to_object, :only => [:new, :destroy]
verify :method => :post, verify :method => :post,
:only => [ :watch, :unwatch ], :only => [ :watch, :unwatch ],
@ -34,9 +35,12 @@ class WatchersController < ApplicationController
end end
def new def new
@watcher = Watcher.new(params[:watcher]) params[:user_ids].each do |user_id|
@watcher = Watcher.new((params[:watcher] || {}).merge({:user_id => user_id}))
@watcher.watchable = @watched @watcher.watchable = @watched
@watcher.save if request.post? @watcher.save if request.post?
end if params[:user_ids].present?
respond_to do |format| respond_to do |format|
format.html { redirect_to :back } format.html { redirect_to :back }
format.js do format.js do
@ -50,7 +54,7 @@ class WatchersController < ApplicationController
end end
def destroy def destroy
@watched.set_watcher(User.find(params[:user_id]), false) if request.post? @watched.set_watcher(Principal.find(params[:user_id]), false) if request.post?
respond_to do |format| respond_to do |format|
format.html { redirect_to :back } format.html { redirect_to :back }
format.js do format.js do
@ -94,4 +98,24 @@ private
rescue ::ActionController::RedirectBackError rescue ::ActionController::RedirectBackError
render :text => (watching ? 'Watcher added.' : 'Watcher removed.'), :layout => true render :text => (watching ? 'Watcher added.' : 'Watcher removed.'), :layout => true
end end
def authorize_access_to_object
permission = ''
case params[:action]
when 'new'
permission << 'add_'
when 'destroy'
permission << 'delete_'
end
# Ends up like: :delete_wiki_page_watchers
permission << "#{@watched.class.name.underscore}_watchers"
if User.current.allowed_to?(permission.to_sym, @project)
return true
else
deny_access
end
end
end end

37
app/drops/base_drop.rb Normal file
View File

@ -0,0 +1,37 @@
#-- encoding: UTF-8
#-- copyright
# ChiliProject is a project management system.
#
# Copyright (C) 2010-2013 the ChiliProject Team
#
# 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.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
class BaseDrop < Liquid::Drop
def initialize(object)
@object = object unless object.respond_to?(:visible?) && !object.visible?
end
# Defines a Liquid method on the drop that is allowed to call the
# Ruby method directly. Best used for attributes.
#
# Based on Module#liquid_methods
def self.allowed_methods(*allowed_methods)
class_eval do
allowed_methods.each do |sym|
define_method sym do
if @object.respond_to?(:public_send)
@object.public_send(sym) rescue nil
else
@object.send(sym) rescue nil
end
end
end
end
end
end

79
app/drops/issue_drop.rb Normal file
View File

@ -0,0 +1,79 @@
#-- encoding: UTF-8
#-- copyright
# ChiliProject is a project management system.
#
# Copyright (C) 2010-2013 the ChiliProject Team
#
# 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.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
class IssueDrop < BaseDrop
allowed_methods :id
allowed_methods :subject
allowed_methods :description
allowed_methods :project
allowed_methods :tracker
allowed_methods :status
allowed_methods :due_date
allowed_methods :category
allowed_methods :assigned_to
allowed_methods :priority
allowed_methods :fixed_version
allowed_methods :author
allowed_methods :created_on
allowed_methods :updated_on
allowed_methods :start_date
allowed_methods :done_ratio
allowed_methods :estimated_hours
allowed_methods :parent
def custom_field(name)
return '' unless name.present?
custom_field = IssueCustomField.find_by_name(name.strip)
return '' unless custom_field.present?
custom_value = @object.custom_value_for(custom_field)
if custom_value.present?
return custom_value.value
else
return ''
end
end
# TODO: both required, method_missing for Ruby and before_method for Liquid
# Allows accessing custom fields by their name:
#
# - issue.the_name_of_player => CustomField(:name => "The name Of Player")
#
def before_method(method_sym)
if custom_field_with_matching_name = has_custom_field_with_matching_name?(method_sym)
custom_field(custom_field_with_matching_name.name)
else
super
end
end
# Allows accessing custom fields by their name:
#
# - issue.the_name_of_player => CustomField(:name => "The name Of Player")
#
def method_missing(method_sym, *arguments, &block)
if custom_field_with_matching_name = has_custom_field_with_matching_name?(method_sym)
custom_field(custom_field_with_matching_name.name)
else
super
end
end
private
def has_custom_field_with_matching_name?(method_sym)
custom_field_with_matching_name = @object.available_custom_fields.detect {|custom_field|
custom_field.name.downcase.underscore.gsub(' ','_') == method_sym.to_s
}
end
end

View File

@ -0,0 +1,17 @@
#-- encoding: UTF-8
#-- copyright
# ChiliProject is a project management system.
#
# Copyright (C) 2010-2013 the ChiliProject Team
#
# 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.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
class IssueStatusDrop < BaseDrop
allowed_methods :name
end

View File

@ -0,0 +1,17 @@
#-- encoding: UTF-8
#-- copyright
# ChiliProject is a project management system.
#
# Copyright (C) 2010-2013 the ChiliProject Team
#
# 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.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
class PrincipalDrop < BaseDrop
allowed_methods :name
end

17
app/drops/project_drop.rb Normal file
View File

@ -0,0 +1,17 @@
#-- encoding: UTF-8
#-- copyright
# ChiliProject is a project management system.
#
# Copyright (C) 2010-2013 the ChiliProject Team
#
# 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.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
class ProjectDrop < BaseDrop
allowed_methods :name, :identifier
end

17
app/drops/tracker_drop.rb Normal file
View File

@ -0,0 +1,17 @@
#-- encoding: UTF-8
#-- copyright
# ChiliProject is a project management system.
#
# Copyright (C) 2010-2013 the ChiliProject Team
#
# 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.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
class TrackerDrop < BaseDrop
allowed_methods :name
end

View File

@ -0,0 +1,17 @@
#-- encoding: UTF-8
#-- copyright
# ChiliProject is a project management system.
#
# Copyright (C) 2010-2013 the ChiliProject Team
#
# 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.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
class WikiPageDrop < BaseDrop
allowed_methods :title
end

View File

@ -16,9 +16,8 @@ require 'forwardable'
require 'cgi' require 'cgi'
module ApplicationHelper module ApplicationHelper
include Redmine::WikiFormatting::Macros::Definitions
include Redmine::I18n include Redmine::I18n
include GravatarHelper::PublicMethods include Gravatarify::Helper
extend Forwardable extend Forwardable
def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter
@ -224,17 +223,15 @@ module ApplicationHelper
end end
# Renders the project quick-jump box # Renders the project quick-jump box
def render_project_jump_box def render_project_jump_box(projects = [], html_options = {})
projects = User.current.memberships.collect(&:project).compact.uniq projects ||= User.current.memberships.collect(&:project).compact.uniq
if projects.any? if projects.any?
s = '<select onchange="if (this.value != \'\') { window.location = this.value; }">' + # option_tags = content_tag :option, l(:label_jump_to_a_project), :value => ""
"<option value=''>#{ l(:label_jump_to_a_project) }</option>" + option_tags = (content_tag :option, "", :value => "" )
'<option value="" disabled="disabled">---</option>' option_tags << project_tree_options_for_select(projects, :selected => @project) do |p|
s << project_tree_options_for_select(projects, :selected => @project) do |p|
{ :value => url_for(:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item) } { :value => url_for(:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item) }
end end
s << '</select>' select_tag "", option_tags, html_options.merge({ :onchange => "if (this.value != \'\') { window.location = this.value; }" })
s
end end
end end
@ -288,7 +285,15 @@ module ApplicationHelper
def principals_check_box_tags(name, principals) def principals_check_box_tags(name, principals)
s = '' s = ''
principals.sort.each do |principal| principals.sort.each do |principal|
s << "<label>#{ check_box_tag name, principal.id, false } #{h principal}</label>\n" s << "<label style='display:block;'>#{ check_box_tag name, principal.id, false } #{h principal}</label>\n"
end
s
end
def projects_check_box_tags(name, projects)
s = ''
projects.each do |project|
s << "<label>#{ check_box_tag name, project.id, false } #{h project}</label>\n"
end end
s s
end end
@ -389,7 +394,9 @@ module ApplicationHelper
end end
def page_header_title def page_header_title
if @project.nil? || @project.new_record? if @page_header_title.present?
h(@page_header_title)
elsif @project.nil? || @project.new_record?
h(Setting.app_title) h(Setting.app_title)
else else
b = [] b = []
@ -429,8 +436,9 @@ module ApplicationHelper
css << 'theme-' + theme.name css << 'theme-' + theme.name
end end
css << 'controller-' + params[:controller] css << 'project-' + @project.id.to_s if @project.present?
css << 'action-' + params[:action] css << 'controller-' + params[:controller] if params[:controller]
css << 'action-' + params[:action] if params[:action]
css.join(' ') css.join(' ')
end end
@ -447,23 +455,55 @@ module ApplicationHelper
case args.size case args.size
when 1 when 1
obj = options[:object] obj = options[:object]
text = args.shift input_text = args.shift
when 2 when 2
obj = args.shift obj = args.shift
attr = args.shift attr = args.shift
text = obj.send(attr).to_s input_text = obj.send(attr).to_s
else else
raise ArgumentError, 'invalid arguments to textilizable' raise ArgumentError, 'invalid arguments to textilizable'
end end
return '' if text.blank? return '' if input_text.blank?
project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil) project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
only_path = options.delete(:only_path) == false ? false : true only_path = options.delete(:only_path) == false ? false : true
text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr) { |macro, args| exec_macro(macro, obj, args) } begin
text = ChiliProject::Liquid::Legacy.run_macros(input_text)
liquid_template = ChiliProject::Liquid::Template.parse(text)
liquid_variables = get_view_instance_variables_for_liquid
liquid_variables.merge!({'current_user' => User.current})
liquid_variables.merge!({'toc' => '{{toc}}'}) # Pass toc through to replace later
liquid_variables.merge!(ChiliProject::Liquid::Variables.all)
# Pass :view in a register so this view (with helpers) can be used inside of a tag
text = liquid_template.render(liquid_variables, :registers => {:view => self, :object => obj, :attribute => attr})
# Add Liquid errors to the log
if Rails.logger && Rails.logger.debug?
msg = ""
liquid_template.errors.each do |exception|
msg << "[Liquid Error] #{exception.message}\n:\n#{exception.backtrace.join("\n")}"
msg << "\n\n"
end
Rails.logger.debug msg
end
rescue Liquid::SyntaxError => exception
msg = "[Liquid Syntax Error] #{exception.message}"
if Rails.logger && Rails.logger.debug?
log_msg = "#{msg}\n"
log_msg << exception.backtrace.collect{ |str| " #{str}" }.join("\n")
log_msg << "\n\n"
Rails.logger.debug log_msg
end
# Skip Liquid if there is a syntax error
text = content_tag(:div, msg, :class => "flash error")
text << h(input_text)
end
@parsed_headings = [] @parsed_headings = []
text = parse_non_pre_blocks(text) do |text| text = parse_non_pre_blocks(text) do |text|
[:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links, :parse_headings].each do |method_name| [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links, :parse_headings, :parse_relative_urls].each do |method_name|
send method_name, text, project, obj, attr, only_path, options send method_name, text, project, obj, attr, only_path, options
end end
end end
@ -504,6 +544,41 @@ module ApplicationHelper
parsed parsed
end end
RELATIVE_LINK_RE = %r{
<a
(?:
(\shref=
(?: # the href and link
(?:'(\/[^>]+?)')|
(?:"(\/[^>]+?)")
)
)|
[^>]
)*
>
[^<]*?<\/a> # content and closing link tag.
}x unless const_defined?(:RELATIVE_LINK_RE)
def parse_relative_urls(text, project, obj, attr, only_path, options)
return if only_path
text.gsub!(RELATIVE_LINK_RE) do |m|
href, relative_url = $1, $2 || $3
next m unless href.present?
if defined?(request) && request.present?
# we have a request!
protocol, host_with_port = request.protocol, request.host_with_port
elsif @controller
# use the same methods as url_for in the Mailer
url_opts = @controller.class.default_url_options
next m unless url_opts && url_opts[:protocol] && url_opts[:host]
protocol, host_with_port = "#{url_opts[:protocol]}://", url_opts[:host]
else
next m
end
m.sub href, " href=\"#{protocol}#{host_with_port}#{relative_url}\""
end
end
def parse_inline_attachments(text, project, obj, attr, only_path, options) def parse_inline_attachments(text, project, obj, attr, only_path, options)
# when using an image link, try to use an attachment, if possible # when using an image link, try to use an attachment, if possible
if options[:attachments] || (obj && obj.respond_to?(:attachments)) if options[:attachments] || (obj && obj.respond_to?(:attachments))
@ -712,7 +787,7 @@ module ApplicationHelper
end end
end end
TOC_RE = /<p>\{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE) TOC_RE = /<p>\{%\s*toc(_right|_left)?\s*%\}<\/p>/i unless const_defined?(:TOC_RE)
# Renders the TOC with given headings # Renders the TOC with given headings
def replace_toc(text, headings) def replace_toc(text, headings)
@ -720,10 +795,14 @@ module ApplicationHelper
if headings.empty? if headings.empty?
'' ''
else else
div_class = 'toc' toc_class = 'toc'
div_class << ' right' if $1 == '>' toc_class << ' right' if $1 == '_right'
div_class << ' left' if $1 == '<' toc_class << ' left' if $1 == '_left'
out = "<ul class=\"#{div_class}\"><li>"
out = "<fieldset class=\"header_collapsible collapsible #{toc_class}\">"
out << "<legend onclick=\"toggleFieldset(this);\"><span>#{l(:label_toc)}</span></legend>"
out << "<div>"
out << "<ul class=\"toc\"><li>"
root = headings.map(&:first).min root = headings.map(&:first).min
current = root current = root
started = false started = false
@ -741,6 +820,7 @@ module ApplicationHelper
end end
out << '</li></ul>' * (current - root) out << '</li></ul>' * (current - root)
out << '</li></ul>' out << '</li></ul>'
out << '</div></fieldset>'
end end
end end
end end
@ -772,7 +852,7 @@ module ApplicationHelper
def back_url_hidden_field_tag def back_url_hidden_field_tag
back_url = params[:back_url] || request.env['HTTP_REFERER'] back_url = params[:back_url] || request.env['HTTP_REFERER']
back_url = CGI.unescape(back_url.to_s) back_url = CGI.unescape(back_url.to_s)
hidden_field_tag('back_url', CGI.escape(back_url)) unless back_url.blank? hidden_field_tag('back_url', CGI.escape(back_url), :id => nil) unless back_url.blank?
end end
def check_all_links(form_name) def check_all_links(form_name)
@ -788,12 +868,10 @@ module ApplicationHelper
pcts << (100 - pcts[1] - pcts[0]) pcts << (100 - pcts[1] - pcts[0])
width = options[:width] || '100px;' width = options[:width] || '100px;'
legend = options[:legend] || '' legend = options[:legend] || ''
content_tag('table', content_tag('div',
content_tag('tr', content_tag('div', '', :style => "width: #{pcts[0]}%;", :class => 'closed ui-progressbar-value ui-widget-header ui-corner-left') +
(pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : '') + content_tag('div', '', :style => "width: #{pcts[1]}%;", :class => 'done ui-progressbar-value ui-widget-header'),
(pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : '') + :class => 'progress ui-progressbar ui-widget ui-widget-content ui-corner-all', :style => "width: #{width};") +
(pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : '')
), :class => 'progress', :style => "width: #{width};") +
content_tag('p', legend, :class => 'pourcent') content_tag('p', legend, :class => 'pourcent')
end end
@ -806,7 +884,7 @@ module ApplicationHelper
def context_menu(url) def context_menu(url)
unless @context_menu_included unless @context_menu_included
content_for :header_tags do content_for :header_tags do
javascript_include_tag('context_menu') + javascript_include_tag('context_menu.jquery') +
stylesheet_link_tag('context_menu') stylesheet_link_tag('context_menu')
end end
if l(:direction) == 'rtl' if l(:direction) == 'rtl'
@ -816,7 +894,7 @@ module ApplicationHelper
end end
@context_menu_included = true @context_menu_included = true
end end
javascript_tag "new ContextMenu('#{ url_for(url) }')" javascript_tag "jQuery(document).ContextMenu('#{ url_for(url) }')"
end end
def context_menu_link(name, url, options={}) def context_menu_link(name, url, options={})
@ -836,33 +914,25 @@ module ApplicationHelper
end end
def calendar_for(field_id) def calendar_for(field_id)
include_calendar_headers_tags javascript_tag("jQuery('##{field_id}').datepicker(datepickerSettings)")
image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
end end
def include_calendar_headers_tags def jquery_datepicker_settings
unless @calendar_headers_tags_included start_of_week = Setting.start_of_week.to_s
@calendar_headers_tags_included = true start_of_week_string = start_of_week.present? ? "firstDay: '#{start_of_week}', " : ''
content_for :header_tags do script = javascript_tag("var datepickerSettings = {" +
start_of_week = case Setting.start_of_week.to_i start_of_week_string +
when 1 "showOn: 'both', " +
'Calendar._FD = 1;' # Monday "buttonImage: '" + path_to_image('/images/calendar.png') + "', " +
when 7 "buttonImageOnly: true, " +
'Calendar._FD = 0;' # Sunday "showButtonPanel: true, " +
when 6 "dateFormat: 'yy-mm-dd' " +
'Calendar._FD = 6;' # Saturday "}")
else unless current_language == :en
'' # use language jquery_locale = l("jquery.ui", :default => current_language.to_s)
end script << javascript_include_tag("libs/ui/i18n/jquery.ui.datepicker-#{jquery_locale}.js")
javascript_include_tag('calendar/calendar') +
javascript_include_tag("calendar/lang/calendar-#{current_language.to_s.downcase}.js") +
javascript_tag(start_of_week) +
javascript_include_tag('calendar/calendar-setup') +
stylesheet_link_tag('calendar')
end
end end
script
end end
def content_for(name, content = nil, &block) def content_for(name, content = nil, &block)
@ -875,10 +945,29 @@ module ApplicationHelper
(@has_content && @has_content[name]) || false (@has_content && @has_content[name]) || false
end 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}
# Default size is 50x50 px
gravatarify_options[:size] ||= 50
options[:class] ||= 'gravatar'
gravatarify_options[:html] = options
gravatar_tag email, gravatarify_options
end
# Returns the avatar image tag for the given +user+ if avatars are enabled # 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>') # +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 = { }) def avatar(user, options = { })
if Setting.gravatar_enabled? if Setting.gravatar_enabled?
if user.is_a?(Group)
size = options[:size] || 50
size = "#{size}x#{size}" # image_tag uses WxH
options[:class] ||= 'gravatar'
return image_tag("group.png", options.merge(:size => size))
end
options.merge!({:ssl => (defined?(request) && request.ssl?), :default => Setting.gravatar_default}) options.merge!({:ssl => (defined?(request) && request.ssl?), :default => Setting.gravatar_default})
email = nil email = nil
if user.respond_to?(:mail) if user.respond_to?(:mail)
@ -898,6 +987,7 @@ module ApplicationHelper
unless User.current.pref.warn_on_leaving_unsaved == '0' unless User.current.pref.warn_on_leaving_unsaved == '0'
tags << "\n" + javascript_tag("Event.observe(window, 'load', function(){ new WarnLeavingUnsaved('#{escape_javascript( l(:text_warn_on_leaving_unsaved) )}'); });") tags << "\n" + javascript_tag("Event.observe(window, 'load', function(){ new WarnLeavingUnsaved('#{escape_javascript( l(:text_warn_on_leaving_unsaved) )}'); });")
end end
tags << jquery_datepicker_settings
tags tags
end end
@ -935,6 +1025,72 @@ module ApplicationHelper
end end
end end
# Expands the current menu item using JavaScript based on the params
def expand_current_menu
current_menu_class =
case
when params[:controller] == "timelog"
"reports"
when params[:controller] == 'projects' && params[:action] == 'changelog'
"reports"
when params[:controller] == 'issues' && ['calendar','gantt'].include?(params[:action])
"reports"
when params[:controller] == 'projects' && params[:action] == 'roadmap'
'roadmap'
when params[:controller] == 'versions' && params[:action] == 'show'
'roadmap'
when params[:controller] == 'projects' && params[:action] == 'settings'
'settings'
when params[:controller] == 'contracts' || params[:controller] == 'deliverables'
'contracts'
else
params[:controller]
end
javascript_tag("jQuery.menu_expand({ menuItem: '.#{current_menu_class}' });")
end
# Menu items for the main top menu
def main_top_menu_items
split_top_menu_into_main_or_more_menus[:main]
end
# Menu items for the more top menu
def more_top_menu_items
split_top_menu_into_main_or_more_menus[:more]
end
def help_menu_item
split_top_menu_into_main_or_more_menus[:help]
end
# Split the :top_menu into separate :main and :more items
def split_top_menu_into_main_or_more_menus
unless @top_menu_split
items_for_main_level = []
items_for_more_level = []
help_menu = nil
menu_items_for(:top_menu) do |item|
if item.name == :home || item.name == :my_page
items_for_main_level << item
elsif item.name == :help
help_menu = item
elsif item.name == :projects
# Remove, present in layout
else
items_for_more_level << item
end
end
@top_menu_split = {
:main => items_for_main_level,
:more => items_for_more_level,
:help => help_menu
}
end
@top_menu_split
end
private private
def wiki_helper def wiki_helper
@ -946,4 +1102,20 @@ module ApplicationHelper
def link_to_content_update(text, url_params = {}, html_options = {}) def link_to_content_update(text, url_params = {}, html_options = {})
link_to(text, url_params, html_options) link_to(text, url_params, html_options)
end end
def get_view_instance_variables_for_liquid
internal_variables = %w{
@output_buffer @cookies @helpers @real_format @assigns_added @assigns
@view_paths @controller
}
self.instance_variables.collect(&:to_s).reject do |ivar|
ivar.match(/^@_/) || # Rails "internal" variables: @_foo
ivar.match(/^@template/) ||
internal_variables.include?(ivar)
end.inject({}) do |acc,ivar|
acc[ivar.sub('@','')] = instance_variable_get(ivar)
acc
end
end
end end

View File

@ -36,8 +36,7 @@ module CustomFieldsHelper
field_format = Redmine::CustomFieldFormat.find_by_name(custom_field.field_format) field_format = Redmine::CustomFieldFormat.find_by_name(custom_field.field_format)
case field_format.try(:edit_as) case field_format.try(:edit_as)
when "date" when "date"
text_field_tag(field_name, custom_value.value, :id => field_id, :size => 10) + date_field_tag(field_name, custom_value.value, :id => field_id, :size => 10)
calendar_for(field_id)
when "text" when "text"
text_area_tag(field_name, custom_value.value, :id => field_id, :rows => 3, :style => 'width:90%') text_area_tag(field_name, custom_value.value, :id => field_id, :rows => 3, :style => 'width:90%')
when "bool" when "bool"
@ -71,8 +70,7 @@ module CustomFieldsHelper
field_format = Redmine::CustomFieldFormat.find_by_name(custom_field.field_format) field_format = Redmine::CustomFieldFormat.find_by_name(custom_field.field_format)
case field_format.try(:edit_as) case field_format.try(:edit_as)
when "date" when "date"
text_field_tag(field_name, '', :id => field_id, :size => 10) + date_field_tag(field_name, '', :id => field_id, :size => 10)
calendar_for(field_id)
when "text" when "text"
text_area_tag(field_name, '', :id => field_id, :rows => 3, :style => 'width:90%') text_area_tag(field_name, '', :id => field_id, :rows => 3, :style => 'width:90%')
when "bool" when "bool"

View File

@ -52,13 +52,14 @@ module IssuesHelper
"<strong>#{@cached_label_priority}</strong>: #{h(issue.priority.name)}" "<strong>#{@cached_label_priority}</strong>: #{h(issue.priority.name)}"
end end
# TODO: deprecate and/or remove
def render_issue_subject_with_tree(issue) def render_issue_subject_with_tree(issue)
s = '' s = ''
ancestors = issue.root? ? [] : issue.ancestors.all ancestors = issue.root? ? [] : issue.ancestors.all
ancestors.each do |ancestor| ancestors.each do |ancestor|
s << '<div>' + content_tag('p', link_to_issue(ancestor)) s << '<div>' + content_tag('h2', link_to_issue(ancestor))
end end
s << '<div>' + content_tag('h3', h(issue.subject)) s << '<div class="subject">' + content_tag('h2', h(issue.subject))
s << '</div>' * (ancestors.size + 1) s << '</div>' * (ancestors.size + 1)
s s
end end
@ -78,6 +79,22 @@ module IssuesHelper
s s
end end
def render_parents_and_subtree(issue)
return if issue.leaf? && !issue.parent
s = '<form><table id="issue_tree" class="list">'
issue_list(issue.self_and_ancestors.sort_by(&:lft) + issue.descendants.sort_by(&:lft)) do |el, level|
s << content_tag('tr',
content_tag('td', check_box_tag("ids[]", el.id, false, :id => nil), :class => 'checkbox') +
content_tag('td', link_to_issue(el, :truncate => 60), :class => 'subject') +
content_tag('td', h(el.status)) +
content_tag('td', link_to_user(el.assigned_to)) +
content_tag('td', progress_bar(el.done_ratio, :width => '80px')),
:class => "issue issue-#{el.id} #{"self" if el == issue} hascontextmenu #{level > 0 ? "idnt idnt-#{level}" : nil}")
end
s << '</table></form>'
s
end
def render_custom_fields_rows(issue) def render_custom_fields_rows(issue)
return if issue.custom_field_values.empty? return if issue.custom_field_values.empty?
ordered_values = [] ordered_values = []

View File

@ -27,24 +27,34 @@ module JournalsHelper
def render_journal(model, journal, options = {}) def render_journal(model, journal, options = {})
return "" if journal.initial? return "" if journal.initial?
journal_content = render_journal_details(journal, :label_updated_time_by)
journal_content += render_notes(model, journal, options) unless journal.notes.blank? journal_classes = journal.css_classes
content_tag "div", journal_content, { :id => "change-#{journal.id}", :class => journal.css_classes } journal_content = render_journal_details(journal, :label_updated_time_by, model, options)
avatar = avatar(journal.user, :size => "40")
unless avatar.blank?
profile_wrap = content_tag("div", avatar, {:class => "profile-wrap"}, false)
journal_content = profile_wrap + journal_content
journal_classes << " has-avatar"
end end
# This renders a journal entry wiht a header and details content_tag("div", journal_content, :id => "change-#{journal.id}", :class => journal_classes)
def render_journal_details(journal, header_label = :label_updated_time_by) end
# This renders a journal entry with a header and details
def render_journal_details(journal, header_label = :label_updated_time_by, model=nil, options={})
header = <<-HTML header = <<-HTML
<h4> <h4>
<div style="float:right;">#{link_to "##{journal.anchor}", :anchor => "note-#{journal.anchor}"}</div> <div class="journal-link" style="float:right;">#{link_to "##{journal.anchor}", :anchor => "note-#{journal.anchor}"}</div>
#{avatar(journal.user, :size => "24")}
#{content_tag('a', '', :name => "note-#{journal.anchor}")}
#{authoring journal.created_at, journal.user, :label => header_label} #{authoring journal.created_at, journal.user, :label => header_label}
#{content_tag('a', '', :name => "note-#{journal.anchor}")}
</h4> </h4>
HTML HTML
header << render_notes(model, journal, options) unless journal.notes.blank?
if journal.details.any? if journal.details.any?
details = content_tag "ul", :class => "details" do details = content_tag "ul", :class => "journal-attributes details" do
journal.details.collect do |detail| journal.details.collect do |detail|
if d = journal.render_detail(detail) if d = journal.render_detail(detail)
content_tag("li", d) content_tag("li", d)
@ -53,7 +63,7 @@ module JournalsHelper
end end
end end
content_tag("div", "#{header}#{details}", :id => "change-#{journal.id}", :class => "journal") content_tag "div", "#{header}#{details}", :class => "journal-details"
end end
def render_notes(model, journal, options={}) def render_notes(model, journal, options={})

View File

@ -84,11 +84,12 @@ module QueriesHelper
end end
end end
@query.group_by = params[:group_by] @query.group_by = params[:group_by]
@query.display_subprojects = params[:display_subprojects] if params[:display_subprojects]
@query.column_names = params[:c] || (params[:query] && params[:query][:column_names]) @query.column_names = params[:c] || (params[:query] && params[:query][:column_names])
session[:query] = {:project_id => @query.project_id, :filters => @query.filters, :group_by => @query.group_by, :column_names => @query.column_names} session[:query] = {:project_id => @query.project_id, :filters => @query.filters, :group_by => @query.group_by, :column_names => @query.column_names, :display_subprojects => @query.display_subprojects}
else else
@query = Query.find_by_id(session[:query][:id]) if session[:query][:id] @query = Query.find_by_id(session[:query][:id]) if session[:query][:id]
@query ||= Query.new(:name => "_", :project => @project, :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names]) @query ||= Query.new(:name => "_", :project => @project, :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names], :display_subprojects => session[:query][:display_subprojects])
@query.project = @project @query.project = @project
end end
end end

View File

@ -89,12 +89,12 @@ class Changeset < ActiveRecord::Base
# Attribute reader for committer that encodes the committer string to # Attribute reader for committer that encodes the committer string to
# the repository log encoding (e.g. UTF-8) # the repository log encoding (e.g. UTF-8)
def committer def committer
self.class.to_utf8(read_attribute(:committer), repository.repo_log_encoding) self.class.to_utf8(read_attribute(:committer), repository_encoding)
end end
def before_create def before_create
self.committer = self.class.to_utf8(self.committer, repository.repo_log_encoding) self.committer = self.class.to_utf8(self.committer, repository_encoding)
self.comments = self.class.normalize_comments(self.comments, repository.repo_log_encoding) self.comments = self.class.normalize_comments(self.comments, repository_encoding)
self.user = repository.find_committer_user(self.committer) self.user = repository.find_committer_user(self.committer)
end end

View File

@ -13,8 +13,11 @@
#++ #++
class Comment < ActiveRecord::Base class Comment < ActiveRecord::Base
include Redmine::SafeAttributes
belongs_to :commented, :polymorphic => true, :counter_cache => true belongs_to :commented, :polymorphic => true, :counter_cache => true
belongs_to :author, :class_name => 'User', :foreign_key => 'author_id' belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
validates_presence_of :commented, :author, :comments validates_presence_of :commented, :author, :comments
safe_attributes 'comments'
end end

View File

@ -25,6 +25,7 @@ class Document < ActiveRecord::Base
end) end)
acts_as_searchable :columns => ['title', "#{table_name}.description"], :include => :project acts_as_searchable :columns => ['title', "#{table_name}.description"], :include => :project
acts_as_watchable
validates_presence_of :project, :title, :category validates_presence_of :project, :title, :category
validates_length_of :title, :maximum => 60 validates_length_of :title, :maximum => 60
@ -40,7 +41,9 @@ class Document < ActiveRecord::Base
def after_initialize def after_initialize
if new_record? if new_record?
self.category ||= DocumentCategory.default # FIXME: on Rails 3 use this instead
# self.category ||= DocumentCategory.default
self.category_id = DocumentCategory.default.id if self.category_id == 0
end end
end end
@ -51,4 +54,10 @@ class Document < ActiveRecord::Base
end end
@updated_on @updated_on
end end
def recipients
mails = super # from acts_as_event
mails += watcher_recipients
mails.uniq
end
end end

View File

@ -14,6 +14,10 @@
class DocumentObserver < ActiveRecord::Observer class DocumentObserver < ActiveRecord::Observer
def after_create(document) def after_create(document)
Mailer.deliver_document_added(document) if Setting.notified_events.include?('document_added') if Setting.notified_events.include?('document_added')
document.recipients.each do |recipient|
Mailer.deliver_document_added(document, recipient)
end
end
end end
end end

View File

@ -22,6 +22,11 @@ class Group < Principal
validates_uniqueness_of :lastname, :case_sensitive => false validates_uniqueness_of :lastname, :case_sensitive => false
validates_length_of :lastname, :maximum => 30 validates_length_of :lastname, :maximum => 30
# Returns an array of all of the email addresses of the group's users
def mails
users.collect(&:mail)
end
def to_s def to_s
lastname.to_s lastname.to_s
end end
@ -43,4 +48,9 @@ class Group < Principal
:conditions => ["#{Member.table_name}.user_id = ? AND #{MemberRole.table_name}.inherited_from IN (?)", user.id, member.member_role_ids]).each(&:destroy) :conditions => ["#{Member.table_name}.user_id = ? AND #{MemberRole.table_name}.inherited_from IN (?)", user.id, member.member_role_ids]).each(&:destroy)
end end
end end
def self.human_attribute_name(attribute_name)
attribute_name = "name" if attribute_name == "lastname"
super(attribute_name)
end
end end

View File

@ -103,6 +103,10 @@ class Issue < ActiveRecord::Base
(usr || User.current).allowed_to?(:view_issues, self.project) (usr || User.current).allowed_to?(:view_issues, self.project)
end end
def to_liquid
IssueDrop.new(self)
end
def after_initialize def after_initialize
if new_record? if new_record?
# set default values for new records only # set default values for new records only
@ -364,7 +368,7 @@ class Issue < ActiveRecord::Base
def attachment_removed(obj) def attachment_removed(obj)
init_journal(User.current) init_journal(User.current)
create_journal create_journal
last_journal.update_attribute(:changes, {"attachments_" + obj.id.to_s => [obj.filename, nil]}.to_yaml) last_journal.update_attribute(:changes, {"attachments_" + obj.id.to_s => [obj.filename, nil]})
end end
# Return true if the issue is closed, otherwise false # Return true if the issue is closed, otherwise false
@ -703,6 +707,15 @@ class Issue < ActiveRecord::Base
projects projects
end end
# Overrides Redmine::Acts::Journalized::Permissions
#
# 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.user == user && user.allowed_to?(:edit_own_issue_notes, project)
user.allowed_to? :edit_issue_notes, project
end
private private
def update_nested_set_attributes def update_nested_set_attributes

View File

@ -17,7 +17,9 @@ class IssueObserver < ActiveRecord::Observer
def after_create(issue) def after_create(issue)
if self.send_notification if self.send_notification
Mailer.deliver_issue_add(issue) if Setting.notified_events.include?('issue_added') (issue.recipients + issue.watcher_recipients).uniq.each do |recipient|
Mailer.deliver_issue_add(issue, recipient)
end
end end
clear_notification clear_notification
end end

View File

@ -28,6 +28,10 @@ class IssueStatus < ActiveRecord::Base
IssueStatus.update_all("is_default=#{connection.quoted_false}", ['id <> ?', id]) if self.is_default? IssueStatus.update_all("is_default=#{connection.quoted_false}", ['id <> ?', id]) if self.is_default?
end end
def to_liquid
IssueStatusDrop.new(self)
end
# Returns the default status for new issues # Returns the default status for new issues
def self.default def self.default
find(:first, :conditions =>["is_default=?", true]) find(:first, :conditions =>["is_default=?", true])

View File

@ -76,7 +76,7 @@ class Journal < ActiveRecord::Base
end end
def editable_by?(user) def editable_by?(user)
journaled.journal_editable_by?(user) journaled.journal_editable_by?(self, user)
end end
def details def details

View File

@ -27,11 +27,15 @@ class JournalObserver < ActiveRecord::Observer
if journal.initial? if journal.initial?
if Setting.notified_events.include?('wiki_content_added') if Setting.notified_events.include?('wiki_content_added')
Mailer.deliver_wiki_content_added(wiki_content) (wiki_content.recipients + wiki_page.wiki.watcher_recipients).uniq.each do |recipient|
Mailer.deliver_wiki_content_added(wiki_content, recipient)
end
end end
else else
if Setting.notified_events.include?('wiki_content_updated') if Setting.notified_events.include?('wiki_content_updated')
Mailer.deliver_wiki_content_updated(wiki_content) (wiki_content.recipients + wiki_page.wiki.watcher_recipients + wiki_page.watcher_recipients).uniq.each do |recipient|
Mailer.deliver_wiki_content_updated(wiki_content, recipient)
end
end end
end end
end end
@ -43,7 +47,10 @@ class JournalObserver < ActiveRecord::Observer
(Setting.notified_events.include?('issue_note_added') && journal.notes.present?) || (Setting.notified_events.include?('issue_note_added') && journal.notes.present?) ||
(Setting.notified_events.include?('issue_status_updated') && journal.new_status.present?) || (Setting.notified_events.include?('issue_status_updated') && journal.new_status.present?) ||
(Setting.notified_events.include?('issue_priority_updated') && journal.new_value_for('priority_id').present?) (Setting.notified_events.include?('issue_priority_updated') && journal.new_value_for('priority_id').present?)
Mailer.deliver_issue_edit(journal) issue = journal.issue
(issue.recipients + issue.watcher_recipients).uniq.each do |recipient|
Mailer.deliver_issue_edit(journal, recipient)
end
end end
end end

View File

@ -69,6 +69,7 @@ class MailHandler < ActionMailer::Base
else else
# Default behaviour, emails from unknown users are ignored # Default behaviour, emails from unknown users are ignored
logger.info "MailHandler: ignoring email from unknown user [#{sender_email}]" if logger && logger.info logger.info "MailHandler: ignoring email from unknown user [#{sender_email}]" if logger && logger.info
Mailer.deliver_mail_handler_unauthorized_action(user, email.subject.to_s, :to => sender_email) if Setting.mail_handler_confirmation_on_failure
return false return false
end end
end end
@ -102,12 +103,15 @@ class MailHandler < ActionMailer::Base
rescue ActiveRecord::RecordInvalid => e rescue ActiveRecord::RecordInvalid => e
# TODO: send a email to the user # TODO: send a email to the user
logger.error e.message if logger logger.error e.message if logger
Mailer.deliver_mail_handler_missing_information(user, email.subject.to_s, e.message) if Setting.mail_handler_confirmation_on_failure
false false
rescue MissingInformation => e rescue MissingInformation => e
logger.error "MailHandler: missing information from #{user}: #{e.message}" if logger logger.error "MailHandler: missing information from #{user}: #{e.message}" if logger
Mailer.deliver_mail_handler_missing_information(user, email.subject.to_s, e.message) if Setting.mail_handler_confirmation_on_failure
false false
rescue UnauthorizedAction => e rescue UnauthorizedAction => e
logger.error "MailHandler: unauthorized attempt from #{user}" if logger logger.error "MailHandler: unauthorized attempt from #{user}" if logger
Mailer.deliver_mail_handler_unauthorized_action(user, email.subject.to_s) if Setting.mail_handler_confirmation_on_failure
false false
end end
@ -141,6 +145,7 @@ class MailHandler < ActionMailer::Base
issue.save! issue.save!
add_attachments(issue) add_attachments(issue)
logger.info "MailHandler: issue ##{issue.id} created by #{user}" if logger && logger.info logger.info "MailHandler: issue ##{issue.id} created by #{user}" if logger && logger.info
Mailer.deliver_mail_handler_confirmation(issue, user, issue.subject) if Setting.mail_handler_confirmation_on_success
issue issue
end end
@ -162,6 +167,7 @@ class MailHandler < ActionMailer::Base
add_attachments(issue) add_attachments(issue)
issue.save! issue.save!
logger.info "MailHandler: issue ##{issue.id} updated by #{user}" if logger && logger.info logger.info "MailHandler: issue ##{issue.id} updated by #{user}" if logger && logger.info
Mailer.deliver_mail_handler_confirmation(issue.last_journal, user, email.subject) if Setting.mail_handler_confirmation_on_success
issue.last_journal issue.last_journal
end end
@ -190,6 +196,7 @@ class MailHandler < ActionMailer::Base
reply.board = message.board reply.board = message.board
message.children << reply message.children << reply
add_attachments(reply) add_attachments(reply)
Mailer.deliver_mail_handler_confirmation(message, user, reply.subject) if Setting.mail_handler_confirmation_on_success
reply reply
else else
logger.info "MailHandler: ignoring reply from [#{sender_email}] to a locked topic" if logger && logger.info logger.info "MailHandler: ignoring reply from [#{sender_email}] to a locked topic" if logger && logger.info

View File

@ -30,20 +30,19 @@ class Mailer < ActionMailer::Base
{ :host => h, :protocol => Setting.protocol } { :host => h, :protocol => Setting.protocol }
end end
# Builds a tmail object used to email recipients of the added issue. # Builds a tmail object used to email a recipient of the added issue.
# #
# Example: # Example:
# issue_add(issue) => tmail object # issue_add(issue, 'user@example.com') => tmail object
# Mailer.deliver_issue_add(issue) => sends an email to issue recipients # Mailer.deliver_issue_add(issue, 'user@example.com') => sends an email to 'user@example.com'
def issue_add(issue) def issue_add(issue, recipient)
redmine_headers 'Project' => issue.project.identifier, redmine_headers 'Project' => issue.project.identifier,
'Issue-Id' => issue.id, 'Issue-Id' => issue.id,
'Issue-Author' => issue.author.login, 'Issue-Author' => issue.author.login,
'Type' => "Issue" 'Type' => "Issue"
redmine_headers 'Issue-Assignee' => issue.assigned_to.login if issue.assigned_to redmine_headers 'Issue-Assignee' => issue.assigned_to.login if issue.assigned_to
message_id issue message_id issue
recipients issue.recipients recipients [recipient]
cc(issue.watcher_recipients - @recipients)
subject "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] (#{issue.status.name}) #{issue.subject}" subject "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] (#{issue.status.name}) #{issue.subject}"
body :issue => issue, body :issue => issue,
:issue_url => url_for(:controller => 'issues', :action => 'show', :id => issue) :issue_url => url_for(:controller => 'issues', :action => 'show', :id => issue)
@ -53,9 +52,9 @@ class Mailer < ActionMailer::Base
# Builds a tmail object used to email recipients of the edited issue. # Builds a tmail object used to email recipients of the edited issue.
# #
# Example: # Example:
# issue_edit(journal) => tmail object # issue_edit(journal, 'user@example.com') => tmail object
# Mailer.deliver_issue_edit(journal) => sends an email to issue recipients # Mailer.deliver_issue_edit(journal, 'user@example.com') => sends an email to issue recipients
def issue_edit(journal) def issue_edit(journal, recipient)
issue = journal.journaled.reload issue = journal.journaled.reload
redmine_headers 'Project' => issue.project.identifier, redmine_headers 'Project' => issue.project.identifier,
'Issue-Id' => issue.id, 'Issue-Id' => issue.id,
@ -65,9 +64,7 @@ class Mailer < ActionMailer::Base
message_id journal message_id journal
references issue references issue
@author = journal.user @author = journal.user
recipients issue.recipients recipients [recipient]
# Watchers in cc
cc(issue.watcher_recipients - @recipients)
s = "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] " s = "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] "
s << "(#{issue.status.name}) " if journal.details['status_id'] s << "(#{issue.status.name}) " if journal.details['status_id']
s << issue.subject s << issue.subject
@ -93,12 +90,12 @@ class Mailer < ActionMailer::Base
# Builds a tmail object used to email users belonging to the added document's project. # Builds a tmail object used to email users belonging to the added document's project.
# #
# Example: # Example:
# document_added(document) => tmail object # document_added(document, 'test@example.com') => tmail object
# Mailer.deliver_document_added(document) => sends an email to the document's project recipients # Mailer.deliver_document_added(document, 'test@example.com') => sends an email to the document's project recipients
def document_added(document) def document_added(document, recipient)
redmine_headers 'Project' => document.project.identifier, redmine_headers 'Project' => document.project.identifier,
'Type' => "Document" 'Type' => "Document"
recipients document.recipients recipients [recipient]
subject "[#{document.project.name}] #{l(:label_document_new)}: #{document.title}" subject "[#{document.project.name}] #{l(:label_document_new)}: #{document.title}"
body :document => document, body :document => document,
:document_url => url_for(:controller => 'documents', :action => 'show', :id => document) :document_url => url_for(:controller => 'documents', :action => 'show', :id => document)
@ -110,7 +107,7 @@ class Mailer < ActionMailer::Base
# Example: # Example:
# attachments_added(attachments) => tmail object # attachments_added(attachments) => tmail object
# Mailer.deliver_attachments_added(attachments) => sends an email to the project's recipients # Mailer.deliver_attachments_added(attachments) => sends an email to the project's recipients
def attachments_added(attachments) def attachments_added(attachments, recipient)
container = attachments.first.container container = attachments.first.container
added_to = '' added_to = ''
added_to_url = '' added_to_url = ''
@ -118,16 +115,14 @@ class Mailer < ActionMailer::Base
when 'Project' when 'Project'
added_to_url = url_for(:controller => 'files', :action => 'index', :project_id => container) added_to_url = url_for(:controller => 'files', :action => 'index', :project_id => container)
added_to = "#{l(:label_project)}: #{container}" added_to = "#{l(:label_project)}: #{container}"
recipients container.project.notified_users.select {|user| user.allowed_to?(:view_files, container.project)}.collect {|u| u.mail}
when 'Version' when 'Version'
added_to_url = url_for(:controller => 'files', :action => 'index', :project_id => container.project) added_to_url = url_for(:controller => 'files', :action => 'index', :project_id => container.project)
added_to = "#{l(:label_version)}: #{container.name}" added_to = "#{l(:label_version)}: #{container.name}"
recipients container.project.notified_users.select {|user| user.allowed_to?(:view_files, container.project)}.collect {|u| u.mail}
when 'Document' when 'Document'
added_to_url = url_for(:controller => 'documents', :action => 'show', :id => container.id) added_to_url = url_for(:controller => 'documents', :action => 'show', :id => container.id)
added_to = "#{l(:label_document)}: #{container.title}" added_to = "#{l(:label_document)}: #{container.title}"
recipients container.recipients
end end
recipients [recipient]
redmine_headers 'Project' => container.project.identifier, redmine_headers 'Project' => container.project.identifier,
'Type' => "Attachment" 'Type' => "Attachment"
subject "[#{container.project.name}] #{l(:label_attachment_new)}" subject "[#{container.project.name}] #{l(:label_attachment_new)}"
@ -142,11 +137,11 @@ class Mailer < ActionMailer::Base
# Example: # Example:
# news_added(news) => tmail object # news_added(news) => tmail object
# Mailer.deliver_news_added(news) => sends an email to the news' project recipients # Mailer.deliver_news_added(news) => sends an email to the news' project recipients
def news_added(news) def news_added(news, recipient)
redmine_headers 'Project' => news.project.identifier, redmine_headers 'Project' => news.project.identifier,
'Type' => "News" 'Type' => "News"
message_id news message_id news
recipients news.recipients recipients [recipient]
subject "[#{news.project.name}] #{l(:label_news)}: #{news.title}" subject "[#{news.project.name}] #{l(:label_news)}: #{news.title}"
body :news => news, body :news => news,
:news_url => url_for(:controller => 'news', :action => 'show', :id => news) :news_url => url_for(:controller => 'news', :action => 'show', :id => news)
@ -176,14 +171,13 @@ class Mailer < ActionMailer::Base
# Example: # Example:
# message_posted(message) => tmail object # message_posted(message) => tmail object
# Mailer.deliver_message_posted(message) => sends an email to the recipients # Mailer.deliver_message_posted(message) => sends an email to the recipients
def message_posted(message) def message_posted(message, recipient)
redmine_headers 'Project' => message.project.identifier, redmine_headers 'Project' => message.project.identifier,
'Topic-Id' => (message.parent_id || message.id), 'Topic-Id' => (message.parent_id || message.id),
'Type' => "Forum" 'Type' => "Forum"
message_id message message_id message
references message.parent unless message.parent.nil? references message.parent unless message.parent.nil?
recipients(message.recipients) recipients [recipient]
cc((message.root.watcher_recipients + message.board.watcher_recipients).uniq - @recipients)
subject "[#{message.board.project.name} - #{message.board.name} - msg#{message.root.id}] #{message.subject}" subject "[#{message.board.project.name} - #{message.board.name} - msg#{message.root.id}] #{message.subject}"
body :message => message, body :message => message,
:message_url => url_for({ :controller => 'messages', :action => 'show', :board_id => message.board, :id => message.root, :r => message, :anchor => "message-#{message.id}" }) :message_url => url_for({ :controller => 'messages', :action => 'show', :board_id => message.board, :id => message.root, :r => message, :anchor => "message-#{message.id}" })
@ -195,13 +189,12 @@ class Mailer < ActionMailer::Base
# Example: # Example:
# wiki_content_added(wiki_content) => tmail object # wiki_content_added(wiki_content) => tmail object
# Mailer.deliver_wiki_content_added(wiki_content) => sends an email to the project's recipients # Mailer.deliver_wiki_content_added(wiki_content) => sends an email to the project's recipients
def wiki_content_added(wiki_content) def wiki_content_added(wiki_content, recipient)
redmine_headers 'Project' => wiki_content.project.identifier, redmine_headers 'Project' => wiki_content.project.identifier,
'Wiki-Page-Id' => wiki_content.page.id, 'Wiki-Page-Id' => wiki_content.page.id,
'Type' => "Wiki" 'Type' => "Wiki"
message_id wiki_content message_id wiki_content
recipients wiki_content.recipients recipients [recipient]
cc(wiki_content.page.wiki.watcher_recipients - recipients)
subject "[#{wiki_content.project.name}] #{l(:mail_subject_wiki_content_added, :id => wiki_content.page.pretty_title)}" subject "[#{wiki_content.project.name}] #{l(:mail_subject_wiki_content_added, :id => wiki_content.page.pretty_title)}"
body :wiki_content => wiki_content, body :wiki_content => wiki_content,
:wiki_content_url => url_for(:controller => 'wiki', :action => 'show', :project_id => wiki_content.project, :id => wiki_content.page.title) :wiki_content_url => url_for(:controller => 'wiki', :action => 'show', :project_id => wiki_content.project, :id => wiki_content.page.title)
@ -213,13 +206,12 @@ class Mailer < ActionMailer::Base
# Example: # Example:
# wiki_content_updated(wiki_content) => tmail object # wiki_content_updated(wiki_content) => tmail object
# Mailer.deliver_wiki_content_updated(wiki_content) => sends an email to the project's recipients # Mailer.deliver_wiki_content_updated(wiki_content) => sends an email to the project's recipients
def wiki_content_updated(wiki_content) def wiki_content_updated(wiki_content, recipient)
redmine_headers 'Project' => wiki_content.project.identifier, redmine_headers 'Project' => wiki_content.project.identifier,
'Wiki-Page-Id' => wiki_content.page.id, 'Wiki-Page-Id' => wiki_content.page.id,
'Type' => "Wiki" 'Type' => "Wiki"
message_id wiki_content message_id wiki_content
recipients wiki_content.recipients recipients [recipient]
cc(wiki_content.page.wiki.watcher_recipients + wiki_content.page.watcher_recipients - recipients)
subject "[#{wiki_content.project.name}] #{l(:mail_subject_wiki_content_updated, :id => wiki_content.page.pretty_title)}" subject "[#{wiki_content.project.name}] #{l(:mail_subject_wiki_content_updated, :id => wiki_content.page.pretty_title)}"
body :wiki_content => wiki_content, body :wiki_content => wiki_content,
:wiki_content_url => url_for(:controller => 'wiki', :action => 'show', :project_id => wiki_content.project, :id => wiki_content.page.title), :wiki_content_url => url_for(:controller => 'wiki', :action => 'show', :project_id => wiki_content.project, :id => wiki_content.page.title),
@ -293,6 +285,44 @@ class Mailer < ActionMailer::Base
render_multipart('register', body) render_multipart('register', body)
end end
def mail_handler_confirmation(object, user, email_subject)
recipients user.mail
case
when object.is_a?(Issue)
project = object.project.name
url = url_for(:controller => 'issues', :action => 'show', :id => object.id)
when object.is_a?(Journal)
project = object.project.name
url = url_for(:controller => 'issues', :action => 'show', :id => object.issue.id)
when object.class == Message
project = object.project.name
url = url_for(object.event_url)
else
project = ''
url = ''
end
subject "[#{project}] #{l(:label_mail_handler_confirmation, :subject => email_subject)}"
body(:object => object,
:url => url)
render_multipart('mail_handler_confirmation', body)
end
def mail_handler_unauthorized_action(user, email_subject, options={})
recipients options[:to] || user.mail
subject l(:label_mail_handler_failure, :subject => email_subject)
body({})
render_multipart('mail_handler_unauthorized_action', body)
end
def mail_handler_missing_information(user, email_subject, error_message)
recipients user.mail
subject l(:label_mail_handler_failure, :subject => email_subject)
body({:errors => error_message.to_s})
render_multipart('mail_handler_missing_information', body)
end
def test(user) def test(user)
redmine_headers 'Type' => "Test" redmine_headers 'Type' => "Test"
set_language_if_valid(user.language) set_language_if_valid(user.language)
@ -395,7 +425,7 @@ class Mailer < ActionMailer::Base
# Removes the current user from the recipients and cc # Removes the current user from the recipients and cc
# if he doesn't want to receive notifications about what he does # if he doesn't want to receive notifications about what he does
@author ||= User.current @author ||= User.current
if @author.pref[:no_self_notified] if @author && @author.mail && @author.pref[:no_self_notified]
recipients((recipients.is_a?(Array) ? recipients : [recipients]) - [@author.mail]) if recipients.present? recipients((recipients.is_a?(Array) ? recipients : [recipients]) - [@author.mail]) if recipients.present?
cc((cc.is_a?(Array) ? cc : [cc]) - [@author.mail]) if cc.present? cc((cc.is_a?(Array) ? cc : [cc]) - [@author.mail]) if cc.present?
end end
@ -403,13 +433,6 @@ class Mailer < ActionMailer::Base
notified_users = [recipients, cc].flatten.compact.uniq notified_users = [recipients, cc].flatten.compact.uniq
# Rails would log recipients only, not cc and bcc # Rails would log recipients only, not cc and bcc
mylogger.info "Sending email notification to: #{notified_users.join(', ')}" if mylogger mylogger.info "Sending email notification to: #{notified_users.join(', ')}" if mylogger
# Blind carbon copy recipients
if Setting.bcc_recipients?
bcc(notified_users)
recipients []
cc []
end
super super
end end

View File

@ -41,7 +41,6 @@ class Message < ActiveRecord::Base
acts_as_watchable acts_as_watchable
attr_protected :locked, :sticky
validates_presence_of :board, :subject, :content validates_presence_of :board, :subject, :content
validates_length_of :subject, :maximum => 255 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) } } :conditions => Project.allowed_to_condition(args.first || User.current, :view_messages) } }
safe_attributes 'subject', 'content' safe_attributes 'subject', 'content'
safe_attributes 'locked', 'sticky', safe_attributes 'locked', 'sticky', 'board_id',
:if => lambda {|message, user| :if => lambda {|message, user|
user.allowed_to?(:edit_messages, message.project) user.allowed_to?(:edit_messages, message.project)
} }
@ -81,9 +80,15 @@ class Message < ActiveRecord::Base
end end
def after_destroy def after_destroy
parent.reset_last_reply_id! if parent
board.reset_counters! board.reset_counters!
end end
def reset_last_reply_id!
clid = children.present? ? children.last.id : nil
self.update_attribute(:last_reply_id, clid)
end
def sticky=(arg) def sticky=(arg)
write_attribute :sticky, (arg == true || arg.to_s == '1' ? 1 : 0) write_attribute :sticky, (arg == true || arg.to_s == '1' ? 1 : 0)
end end

View File

@ -14,6 +14,13 @@
class MessageObserver < ActiveRecord::Observer class MessageObserver < ActiveRecord::Observer
def after_create(message) def after_create(message)
Mailer.deliver_message_posted(message) if Setting.notified_events.include?('message_posted') if Setting.notified_events.include?('message_posted')
recipients = message.recipients
recipients += message.root.watcher_recipients
recipients += message.board.watcher_recipients
recipients.uniq.each do |recipient|
Mailer.deliver_message_posted(message, recipient)
end
end
end end
end end

View File

@ -22,7 +22,8 @@ class News < ActiveRecord::Base
validates_length_of :title, :maximum => 60 validates_length_of :title, :maximum => 60
validates_length_of :summary, :maximum => 255 validates_length_of :summary, :maximum => 255
acts_as_journalized :event_url => Proc.new {|o| {:controller => 'news', :action => 'show', :id => o.journaled_id} } acts_as_journalized :event_url => Proc.new {|o| {:controller => 'news', :action => 'show', :id => o.journaled_id} },
:event_description => :description
acts_as_searchable :columns => ["#{table_name}.title", "#{table_name}.summary", "#{table_name}.description"], :include => :project acts_as_searchable :columns => ["#{table_name}.title", "#{table_name}.summary", "#{table_name}.description"], :include => :project
acts_as_watchable acts_as_watchable
@ -39,6 +40,11 @@ class News < ActiveRecord::Base
!user.nil? && user.allowed_to?(:view_news, project) !user.nil? && user.allowed_to?(:view_news, project)
end end
# Returns true if the news can be commented by user
def commentable?(user=User.current)
user.allowed_to?(:comment_news, project)
end
# returns latest news for projects visible by user # returns latest news for projects visible by user
def self.latest(user = User.current, count = 5) def self.latest(user = User.current, count = 5)
find(:all, :limit => count, :conditions => Project.allowed_to_condition(user, :view_news), :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC") find(:all, :limit => count, :conditions => Project.allowed_to_condition(user, :view_news), :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC")

View File

@ -14,6 +14,10 @@
class NewsObserver < ActiveRecord::Observer class NewsObserver < ActiveRecord::Observer
def after_create(news) def after_create(news)
Mailer.deliver_news_added(news) if Setting.notified_events.include?('news_added') if Setting.notified_events.include?('news_added')
news.recipients.each do |recipient|
Mailer.deliver_news_added(news, recipient)
end
end
end end
end end

View File

@ -31,6 +31,10 @@ class Principal < ActiveRecord::Base
before_create :set_default_empty_values before_create :set_default_empty_values
def to_liquid
PrincipalDrop.new(self)
end
def name(formatter = nil) def name(formatter = nil)
to_s to_s
end end
@ -44,6 +48,82 @@ class Principal < ActiveRecord::Base
end end
end end
def active?
true
end
def logged?
true # TODO: should all principals default to logged or not?
end
# Return true if the user is allowed to do the specified action on a specific context
# Action can be:
# * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
# * a permission Symbol (eg. :edit_project)
# Context can be:
# * a project : returns true if user is allowed to do the specified action on this project
# * a group of projects : returns true if user is allowed on every project
# * nil with options[:global] set : check if user has at least one role allowed for this action,
# or falls back to Non Member / Anonymous permissions depending if the user is logged
def allowed_to?(action, context, options={})
if context && context.is_a?(Project)
# No action allowed on archived projects
return false unless context.active?
# No action allowed on disabled modules
return false unless context.allows_to?(action)
# Admin users are authorized for anything else
return true if admin?
roles = roles_for_project(context)
return false unless roles
roles.detect {|role| (context.is_public? || role.member?) && role.allowed_to?(action)}
elsif context && context.is_a?(Array)
# Authorize if user is authorized on every element of the array
context.map do |project|
allowed_to?(action,project,options)
end.inject do |memo,allowed|
memo && allowed
end
elsif options[:global]
# Admin users are always authorized
return true if admin?
# authorize if user has at least one role that has this permission
roles = memberships.collect {|m| m.roles}.flatten.uniq
roles.detect {|r| r.allowed_to?(action)} || (self.logged? ? Role.non_member.allowed_to?(action) : Role.anonymous.allowed_to?(action))
else
false
end
end
# Is the user allowed to do the specified action on any project?
# See allowed_to? for the actions and valid options.
def allowed_to_globally?(action, options)
allowed_to?(action, nil, options.reverse_merge(:global => true))
end
# Return user's roles for project
def roles_for_project(project)
roles = []
# No role on archived projects
return roles unless project && project.active?
if logged?
# Find project membership
membership = memberships.detect {|m| m.project_id == project.id}
if membership
roles = membership.roles
else
@role_non_member ||= Role.non_member
roles << @role_non_member
end
else
@role_anonymous ||= Role.anonymous
roles << @role_anonymous
end
roles
end
protected protected
# Make sure we don't try to insert NULL values (see #4632) # Make sure we don't try to insert NULL values (see #4632)

View File

@ -82,6 +82,16 @@ class Project < ActiveRecord::Base
named_scope :active, { :conditions => "#{Project.table_name}.status = #{STATUS_ACTIVE}"} named_scope :active, { :conditions => "#{Project.table_name}.status = #{STATUS_ACTIVE}"}
named_scope :all_public, { :conditions => { :is_public => true } } named_scope :all_public, { :conditions => { :is_public => true } }
named_scope :visible, lambda { { :conditions => Project.visible_by(User.current) } } named_scope :visible, lambda { { :conditions => Project.visible_by(User.current) } }
named_scope :like, lambda {|q|
s = "%#{q.to_s.strip.downcase}%"
{
:conditions => ["LOWER(name) LIKE ?", s]
}
}
def to_liquid
ProjectDrop.new(self)
end
def initialize(attributes = nil) def initialize(attributes = nil)
super super
@ -131,6 +141,11 @@ class Project < ActiveRecord::Base
end end
end end
# Is the project visible to the current user
def visible?
User.current.allowed_to?(:view_project, self)
end
def self.allowed_to_condition(user, permission, options={}) def self.allowed_to_condition(user, permission, options={})
base_statement = "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}" base_statement = "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"
if perm = Redmine::AccessControl.permission(permission) if perm = Redmine::AccessControl.permission(permission)
@ -616,7 +631,7 @@ class Project < ActiveRecord::Base
while (ancestors.any? && !project.is_descendant_of?(ancestors.last)) while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
ancestors.pop ancestors.pop
end end
yield project, ancestors.size yield project, ancestors.size if block_given?
ancestors << project ancestors << project
end end
end end

View File

@ -12,64 +12,7 @@
# See doc/COPYRIGHT.rdoc for more details. # See doc/COPYRIGHT.rdoc for more details.
#++ #++
class QueryColumn
attr_accessor :name, :sortable, :groupable, :default_order
include Redmine::I18n
def initialize(name, options={})
self.name = name
self.sortable = options[:sortable]
self.groupable = options[:groupable] || false
if groupable == true
self.groupable = name.to_s
end
self.default_order = options[:default_order]
@caption_key = options[:caption] || "field_#{name}"
end
def caption
l(@caption_key)
end
# Returns true if the column is sortable, otherwise false
def sortable?
!sortable.nil?
end
def value(issue)
issue.send name
end
end
class QueryCustomFieldColumn < QueryColumn
def initialize(custom_field)
self.name = "cf_#{custom_field.id}".to_sym
self.sortable = custom_field.order_statement || false
if %w(list date bool int).include?(custom_field.field_format)
self.groupable = custom_field.order_statement
end
self.groupable ||= false
@cf = custom_field
end
def caption
@cf.name
end
def custom_field
@cf
end
def value(issue)
cv = issue.custom_values.detect {|v| v.custom_field_id == @cf.id}
cv && @cf.cast_value(cv.value)
end
end
class Query < ActiveRecord::Base class Query < ActiveRecord::Base
class StatementInvalid < ::ActiveRecord::StatementInvalid
end
belongs_to :project belongs_to :project
belongs_to :user belongs_to :user
@ -90,6 +33,7 @@ class Query < ActiveRecord::Base
"*" => :label_all, "*" => :label_all,
">=" => :label_greater_or_equal, ">=" => :label_greater_or_equal,
"<=" => :label_less_or_equal, "<=" => :label_less_or_equal,
"><" => :label_between,
"<t+" => :label_in_less_than, "<t+" => :label_in_less_than,
">t+" => :label_in_more_than, ">t+" => :label_in_more_than,
"t+" => :label_in, "t+" => :label_in,
@ -107,8 +51,8 @@ class Query < ActiveRecord::Base
:list_status => [ "o", "=", "!", "c", "*" ], :list_status => [ "o", "=", "!", "c", "*" ],
:list_optional => [ "=", "!", "!*", "*" ], :list_optional => [ "=", "!", "!*", "*" ],
:list_subprojects => [ "*", "!*", "=" ], :list_subprojects => [ "*", "!*", "=" ],
:date => [ "<t+", ">t+", "t+", "t", "w", ">t-", "<t-", "t-" ], :date => [ "=", ">=", "<=", "><", "<t+", ">t+", "t+", "t", "w", ">t-", "<t-", "t-" ],
:date_past => [ ">t-", "<t-", "t-", "t", "w" ], :date_past => [ "=", ">=", "<=", "><", ">t-", "<t-", "t-", "t", "w" ],
:string => [ "=", "~", "!", "!~" ], :string => [ "=", "~", "!", "!~" ],
:text => [ "~", "!~" ], :text => [ "~", "!~" ],
:integer => [ "=", ">=", "<=", "!*", "*" ] } :integer => [ "=", ">=", "<=", "!*", "*" ] }
@ -139,6 +83,7 @@ class Query < ActiveRecord::Base
def initialize(attributes = nil) def initialize(attributes = nil)
super attributes super attributes
self.filters ||= { 'status_id' => {:operator => "o", :values => [""]} } self.filters ||= { 'status_id' => {:operator => "o", :values => [""]} }
self.display_subprojects ||= Setting.display_subprojects_issues?
end end
def after_initialize def after_initialize
@ -245,7 +190,7 @@ class Query < ActiveRecord::Base
def add_filter(field, operator, values) def add_filter(field, operator, values)
# values must be an array # values must be an array
return unless values and values.is_a? Array # and !values.first.empty? return unless values.nil? || values.is_a?(Array)
# check if field is defined as an available filter # check if field is defined as an available filter
if available_filters.has_key? field if available_filters.has_key? field
filter_options = available_filters[field] filter_options = available_filters[field]
@ -254,7 +199,7 @@ class Query < ActiveRecord::Base
# allowed_values = values & ([""] + (filter_options[:values] || []).collect {|val| val[1]}) # allowed_values = values & ([""] + (filter_options[:values] || []).collect {|val| val[1]})
# filters[field] = {:operator => operator, :values => allowed_values } if (allowed_values.first and !allowed_values.first.empty?) or ["o", "c", "!*", "*", "t"].include? operator # filters[field] = {:operator => operator, :values => allowed_values } if (allowed_values.first and !allowed_values.first.empty?) or ["o", "c", "!*", "*", "t"].include? operator
#end #end
filters[field] = {:operator => operator, :values => values } filters[field] = {:operator => operator, :values => (values || ['']) }
end end
end end
@ -266,9 +211,9 @@ class Query < ActiveRecord::Base
# Add multiple filters using +add_filter+ # Add multiple filters using +add_filter+
def add_filters(fields, operators, values) def add_filters(fields, operators, values)
if fields.is_a?(Array) && operators.is_a?(Hash) && values.is_a?(Hash) if fields.is_a?(Array) && operators.is_a?(Hash) && (values.nil? || values.is_a?(Hash))
fields.each do |field| fields.each do |field|
add_filter(field, operators[field], values[field]) add_filter(field, operators[field], values && values[field])
end end
end end
end end
@ -277,6 +222,10 @@ class Query < ActiveRecord::Base
filters and filters[field] filters and filters[field]
end end
def type_for(field)
available_filters[field][:type] if available_filters.has_key?(field)
end
def operator_for(field) def operator_for(field)
has_filter?(field) ? filters[field][:operator] : nil has_filter?(field) ? filters[field][:operator] : nil
end end
@ -285,6 +234,10 @@ class Query < ActiveRecord::Base
has_filter?(field) ? filters[field][:values] : nil has_filter?(field) ? filters[field][:values] : nil
end end
def value_for(field, index=0)
(values_for(field) || [])[index]
end
def label_for(field) def label_for(field)
label = available_filters[field][:name] if available_filters.has_key?(field) label = available_filters[field][:name] if available_filters.has_key?(field)
label ||= field.gsub(/\_id$/, "") label ||= field.gsub(/\_id$/, "")
@ -410,7 +363,7 @@ class Query < ActiveRecord::Base
# all subprojects # all subprojects
ids += project.descendants.collect(&:id) ids += project.descendants.collect(&:id)
end end
elsif Setting.display_subprojects_issues? elsif display_subprojects?
ids += project.descendants.collect(&:id) ids += project.descendants.collect(&:id)
end end
project_clauses << "#{Project.table_name}.id IN (%s)" % ids.join(',') project_clauses << "#{Project.table_name}.id IN (%s)" % ids.join(',')
@ -430,8 +383,10 @@ class Query < ActiveRecord::Base
next unless v and !v.empty? next unless v and !v.empty?
operator = operator_for(field) operator = operator_for(field)
# "me" value subsitution # "me" value substitution
if %w(assigned_to_id author_id watcher_id).include?(field) if %w(assigned_to_id author_id watcher_id).include?(field) ||
# user custom fields
available_filters.has_key?(field) && available_filters[field][:format] == 'user'
v.push(User.current.logged? ? User.current.id.to_s : "0") if v.delete("me") v.push(User.current.logged? ? User.current.id.to_s : "0") if v.delete("me")
end end
@ -519,7 +474,7 @@ class Query < ActiveRecord::Base
def issue_count def issue_count
Issue.count(:include => [:status, :project], :conditions => statement) Issue.count(:include => [:status, :project], :conditions => statement)
rescue ::ActiveRecord::StatementInvalid => e rescue ::ActiveRecord::StatementInvalid => e
raise StatementInvalid.new(e.message) raise Query::StatementInvalid.new(e.message)
end end
# Returns the issue count by group or nil if query is not grouped # Returns the issue count by group or nil if query is not grouped
@ -539,7 +494,7 @@ class Query < ActiveRecord::Base
end end
r r
rescue ::ActiveRecord::StatementInvalid => e rescue ::ActiveRecord::StatementInvalid => e
raise StatementInvalid.new(e.message) raise Query::StatementInvalid.new(e.message)
end end
# Returns the issues # Returns the issues
@ -554,7 +509,7 @@ class Query < ActiveRecord::Base
:limit => options[:limit], :limit => options[:limit],
:offset => options[:offset] :offset => options[:offset]
rescue ::ActiveRecord::StatementInvalid => e rescue ::ActiveRecord::StatementInvalid => e
raise StatementInvalid.new(e.message) raise Query::StatementInvalid.new(e.message)
end end
# Returns the journals # Returns the journals
@ -566,7 +521,7 @@ class Query < ActiveRecord::Base
:limit => options[:limit], :limit => options[:limit],
:offset => options[:offset] :offset => options[:offset]
rescue ::ActiveRecord::StatementInvalid => e rescue ::ActiveRecord::StatementInvalid => e
raise StatementInvalid.new(e.message) raise Query::StatementInvalid.new(e.message)
end end
# Returns the versions # Returns the versions
@ -575,7 +530,7 @@ class Query < ActiveRecord::Base
Version.find :all, :include => :project, Version.find :all, :include => :project,
:conditions => Query.merge_conditions(project_statement, options[:conditions]) :conditions => Query.merge_conditions(project_statement, options[:conditions])
rescue ::ActiveRecord::StatementInvalid => e rescue ::ActiveRecord::StatementInvalid => e
raise StatementInvalid.new(e.message) raise Query::StatementInvalid.new(e.message)
end end
private private
@ -585,12 +540,16 @@ class Query < ActiveRecord::Base
sql = '' sql = ''
case operator case operator
when "=" when "="
if value.present? if [:date, :date_past].include?(type_for(field))
sql = date_clause(db_table, db_field, (Date.parse(value.first) rescue nil), (Date.parse(value.first) rescue nil))
else
if value.any?
sql = "#{db_table}.#{db_field} IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")" sql = "#{db_table}.#{db_field} IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")"
else else
# empty set of allowed values produces no result # IN an empty set
sql = "0=1" sql = "0=1"
end end
end
when "!" when "!"
if value.present? if value.present?
sql = "(#{db_table}.#{db_field} IS NULL OR #{db_table}.#{db_field} NOT IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + "))" sql = "(#{db_table}.#{db_field} IS NULL OR #{db_table}.#{db_field} NOT IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + "))"
@ -605,42 +564,58 @@ class Query < ActiveRecord::Base
sql = "#{db_table}.#{db_field} IS NOT NULL" sql = "#{db_table}.#{db_field} IS NOT NULL"
sql << " AND #{db_table}.#{db_field} <> ''" if is_custom_filter sql << " AND #{db_table}.#{db_field} <> ''" if is_custom_filter
when ">=" when ">="
if is_custom_filter if [:date, :date_past].include?(type_for(field))
sql = "#{db_table}.#{db_field} != '' AND CAST(#{db_table}.#{db_field} AS decimal(60,4)) >= #{value.first.to_f}" sql = date_clause(db_table, db_field, (Date.parse(value.first) rescue nil), nil)
else else
sql = "#{db_table}.#{db_field} >= #{value.first.to_f}" if is_custom_filter
sql = "CAST(#{db_table}.#{db_field} AS decimal(60,3)) >= #{value.first.to_i}"
else
sql = "#{db_table}.#{db_field} >= #{value.first.to_i}"
end
end end
when "<=" when "<="
if is_custom_filter if [:date, :date_past].include?(type_for(field))
sql = "#{db_table}.#{db_field} != '' AND CAST(#{db_table}.#{db_field} AS decimal(60,4)) <= #{value.first.to_f}" sql = date_clause(db_table, db_field, nil, (Date.parse(value.first) rescue nil))
else else
sql = "#{db_table}.#{db_field} <= #{value.first.to_f}" if is_custom_filter
sql = "CAST(#{db_table}.#{db_field} AS decimal(60,3)) <= #{value.first.to_i}"
else
sql = "#{db_table}.#{db_field} <= #{value.first.to_i}"
end
end
when "><"
if [:date, :date_past].include?(type_for(field))
sql = date_clause(db_table, db_field, (Date.parse(value[0]) rescue nil), (Date.parse(value[1]) rescue nil))
else
if is_custom_filter
sql = "CAST(#{db_table}.#{db_field} AS decimal(60,3)) BETWEEN #{value[0].to_i} AND #{value[1].to_i}"
else
sql = "#{db_table}.#{db_field} BETWEEN #{value[0].to_i} AND #{value[1].to_i}"
end
end end
when "o" when "o"
sql = "#{IssueStatus.table_name}.is_closed=#{connection.quoted_false}" if field == "status_id" sql = "#{IssueStatus.table_name}.is_closed=#{connection.quoted_false}" if field == "status_id"
when "c" when "c"
sql = "#{IssueStatus.table_name}.is_closed=#{connection.quoted_true}" if field == "status_id" sql = "#{IssueStatus.table_name}.is_closed=#{connection.quoted_true}" if field == "status_id"
when ">t-" when ">t-"
sql = date_range_clause(db_table, db_field, - value.first.to_i, 0) sql = relative_date_clause(db_table, db_field, - value.first.to_i, 0)
when "<t-" when "<t-"
sql = date_range_clause(db_table, db_field, nil, - value.first.to_i) sql = relative_date_clause(db_table, db_field, nil, - value.first.to_i)
when "t-" when "t-"
sql = date_range_clause(db_table, db_field, - value.first.to_i, - value.first.to_i) sql = relative_date_clause(db_table, db_field, - value.first.to_i, - value.first.to_i)
when ">t+" when ">t+"
sql = date_range_clause(db_table, db_field, value.first.to_i, nil) sql = relative_date_clause(db_table, db_field, value.first.to_i, nil)
when "<t+" when "<t+"
sql = date_range_clause(db_table, db_field, 0, value.first.to_i) sql = relative_date_clause(db_table, db_field, 0, value.first.to_i)
when "t+" when "t+"
sql = date_range_clause(db_table, db_field, value.first.to_i, value.first.to_i) sql = relative_date_clause(db_table, db_field, value.first.to_i, value.first.to_i)
when "t" when "t"
sql = date_range_clause(db_table, db_field, 0, 0) sql = relative_date_clause(db_table, db_field, 0, 0)
when "w" when "w"
from = l(:general_first_day_of_week) == '7' ? first_day_of_week = l(:general_first_day_of_week).to_i
# week starts on sunday day_of_week = Date.today.cwday
((Date.today.cwday == 7) ? Time.now.at_beginning_of_day : Time.now.at_beginning_of_week - 1.day) : days_ago = (day_of_week >= first_day_of_week ? day_of_week - first_day_of_week : day_of_week + 7 - first_day_of_week)
# week starts on monday (Rails default) sql = relative_date_clause(db_table, db_field, - days_ago, - days_ago + 6)
Time.now.at_beginning_of_week
sql = "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date(from), connection.quoted_date(from + 7.days)]
when "~" when "~"
sql = "LOWER(#{db_table}.#{db_field}) LIKE '%#{connection.quote_string(value.first.to_s.downcase)}%'" sql = "LOWER(#{db_table}.#{db_field}) LIKE '%#{connection.quote_string(value.first.to_s.downcase)}%'"
when "!~" when "!~"
@ -667,23 +642,32 @@ class Query < ActiveRecord::Base
options = { :type => :list, :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]], :order => 20 } options = { :type => :list, :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]], :order => 20 }
when "user", "version" when "user", "version"
next unless project next unless project
options = { :type => :list_optional, :values => field.possible_values_options(project), :order => 20} values = field.possible_values_options(project)
if User.current.logged? && field.field_format == 'user'
values.unshift ["<< #{l(:label_me)} >>", "me"]
end
options = { :type => :list_optional, :values => values, :order => 20}
else else
options = { :type => :string, :order => 20 } options = { :type => :string, :order => 20 }
end end
@available_filters["cf_#{field.id}"] = options.merge({ :name => field.name }) @available_filters["cf_#{field.id}"] = options.merge({ :name => field.name, :format => field.field_format })
end end
end end
# Returns a SQL clause for a date or datetime field. # Returns a SQL clause for a date or datetime field.
def date_range_clause(table, field, from, to) def date_clause(table, field, from, to)
s = [] s = []
if from if from
s << ("#{table}.#{field} > '%s'" % [connection.quoted_date((Date.yesterday + from).to_time.end_of_day)]) s << ("#{table}.#{field} > '%s'" % [connection.quoted_date((from - 1).to_time.end_of_day)])
end end
if to if to
s << ("#{table}.#{field} <= '%s'" % [connection.quoted_date((Date.today + to).to_time.end_of_day)]) s << ("#{table}.#{field} <= '%s'" % [connection.quoted_date(to.to_time.end_of_day)])
end end
s.join(' AND ') s.join(' AND ')
end end
# Returns a SQL clause for a date or datetime field using relative dates.
def relative_date_clause(table, field, days_from, days_to)
date_clause(table, field, (days_from ? Date.today + days_from : nil), (days_to ? Date.today + days_to : nil))
end
end end

View File

@ -0,0 +1,16 @@
#-- encoding: UTF-8
#-- copyright
# ChiliProject is a project management system.
#
# Copyright (C) 2010-2013 the ChiliProject Team
#
# 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.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
class Query::StatementInvalid < ActiveRecord::StatementInvalid
end

View File

@ -0,0 +1,42 @@
#-- encoding: UTF-8
#-- copyright
# ChiliProject is a project management system.
#
# Copyright (C) 2010-2013 the ChiliProject Team
#
# 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.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
class QueryColumn
attr_accessor :name, :sortable, :groupable, :default_order
include Redmine::I18n
def initialize(name, options={})
self.name = name
self.sortable = options[:sortable]
self.groupable = options[:groupable] || false
if groupable == true
self.groupable = name.to_s
end
self.default_order = options[:default_order]
@caption_key = options[:caption] || "field_#{name}"
end
def caption
l(@caption_key)
end
# Returns true if the column is sortable, otherwise false
def sortable?
!sortable.nil?
end
def value(issue)
issue.send name
end
end

View File

@ -0,0 +1,40 @@
#-- encoding: UTF-8
#-- copyright
# ChiliProject is a project management system.
#
# Copyright (C) 2010-2013 the ChiliProject Team
#
# 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.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
class QueryCustomFieldColumn < QueryColumn
def initialize(custom_field)
self.name = "cf_#{custom_field.id}".to_sym
self.sortable = custom_field.order_statement || false
if %w(list date bool int).include?(custom_field.field_format)
self.groupable = custom_field.order_statement
end
self.groupable ||= false
@cf = custom_field
end
def caption
@cf.name
end
def custom_field
@cf
end
def value(issue)
cv = issue.custom_values.detect {|v| v.custom_field_id == @cf.id}
cv && @cf.cast_value(cv.value)
end
end

View File

@ -12,7 +12,7 @@
# See doc/COPYRIGHT.rdoc for more details. # See doc/COPYRIGHT.rdoc for more details.
#++ #++
require 'redmine/scm/adapters/bazaar_adapter' require_dependency 'redmine/scm/adapters/bazaar_adapter'
class Repository::Bazaar < Repository class Repository::Bazaar < Repository
attr_protected :root_url attr_protected :root_url

View File

@ -12,7 +12,7 @@
# See doc/COPYRIGHT.rdoc for more details. # See doc/COPYRIGHT.rdoc for more details.
#++ #++
require 'redmine/scm/adapters/cvs_adapter' require_dependency 'redmine/scm/adapters/cvs_adapter'
require 'digest/sha1' require 'digest/sha1'
class Repository::Cvs < Repository class Repository::Cvs < Repository

View File

@ -12,7 +12,7 @@
# See doc/COPYRIGHT.rdoc for more details. # See doc/COPYRIGHT.rdoc for more details.
#++ #++
require 'redmine/scm/adapters/darcs_adapter' require_dependency 'redmine/scm/adapters/darcs_adapter'
class Repository::Darcs < Repository class Repository::Darcs < Repository
validates_presence_of :url, :log_encoding validates_presence_of :url, :log_encoding

View File

@ -12,7 +12,7 @@
# See doc/COPYRIGHT.rdoc for more details. # See doc/COPYRIGHT.rdoc for more details.
#++ #++
require 'redmine/scm/adapters/filesystem_adapter' require_dependency 'redmine/scm/adapters/filesystem_adapter'
class Repository::Filesystem < Repository class Repository::Filesystem < Repository
attr_protected :root_url attr_protected :root_url

View File

@ -12,7 +12,7 @@
# See doc/COPYRIGHT.rdoc for more details. # See doc/COPYRIGHT.rdoc for more details.
#++ #++
require 'redmine/scm/adapters/git_adapter' require_dependency 'redmine/scm/adapters/git_adapter'
class Repository::Git < Repository class Repository::Git < Repository
attr_protected :root_url attr_protected :root_url

View File

@ -12,7 +12,7 @@
# See doc/COPYRIGHT.rdoc for more details. # See doc/COPYRIGHT.rdoc for more details.
#++ #++
require 'redmine/scm/adapters/mercurial_adapter' require_dependency 'redmine/scm/adapters/mercurial_adapter'
class Repository::Mercurial < Repository class Repository::Mercurial < Repository
# sort changesets by revision number # sort changesets by revision number

View File

@ -12,7 +12,7 @@
# See doc/COPYRIGHT.rdoc for more details. # See doc/COPYRIGHT.rdoc for more details.
#++ #++
require 'redmine/scm/adapters/subversion_adapter' require_dependency 'redmine/scm/adapters/subversion_adapter'
class Repository::Subversion < Repository class Repository::Subversion < Repository
attr_protected :root_url attr_protected :root_url

View File

@ -97,13 +97,17 @@ class Setting < ActiveRecord::Base
# Returns the value of the setting named name # Returns the value of the setting named name
def self.[](name) def self.[](name)
Marshal.load(Rails.cache.fetch("chiliproject/setting/#{name}") {Marshal.dump(find_or_default(name).value)}) if use_caching?
Marshal.load(Rails.cache.fetch(self.cache_key(name)) {Marshal.dump(find_or_default(name).value)})
else
find_or_default(name).value
end
end end
def self.[]=(name, v) def self.[]=(name, v)
setting = find_or_default(name) setting = find_or_default(name)
setting.value = (v ? v : "") setting.value = (v ? v : "")
Rails.cache.delete "chiliproject/setting/#{name}" Rails.cache.delete self.cache_key(name)
setting.save setting.save
setting.value setting.value
end end
@ -137,23 +141,42 @@ class Setting < ActiveRecord::Base
Object.const_defined?(:OpenID) && self[:openid].to_i > 0 Object.const_defined?(:OpenID) && self[:openid].to_i > 0
end end
# Checks if settings have changed since the values were read # Deprecation Warning: This method is no longer available. There is no
# and clears the cache hash if it's the case # replacement.
# Called once per request
def self.check_cache def self.check_cache
settings_updated_on = Setting.maximum(:updated_on) # DEPRECATED SINCE 3.0.0beta2
cache_cleared_on = Rails.cache.read('chiliproject/setting-cleared_on') ActiveSupport::Deprecation.warn "The Setting.check_cache method is " +
cache_cleared_on = cache_cleared_on ? Marshal.load(cache_cleared_on) : Time.now "deprecated and will be removed in the future. There should be no " +
if settings_updated_on && cache_cleared_on <= settings_updated_on "replacement for this functionality needed."
clear_cache
end
end end
# Clears all of the Setting caches # Clears all of the Setting caches
def self.clear_cache def self.clear_cache
Rails.cache.delete_matched( /^chiliproject\/setting\/.+$/ ) # DEPRECATED SINCE 3.0.0beta2
Rails.cache.write('chiliproject/setting-cleared_on', Marshal.dump(Time.now)) ActiveSupport::Deprecation.warn "The Setting.clear_cache method is " +
logger.info 'Settings cache cleared.' if logger "deprecated and will be removed in the future. There should be no " +
"replacement for this functionality needed. To sweep the whole " +
"cache Rails.cache.clear may be used. To invalidate the Settings " +
"only, you may use Setting.first.try(:touch)"
end
# Temporarily deactivate settings caching in the block scope
def self.uncached
cache_setting = self.use_caching
self.use_caching = false
yield
ensure
self.use_caching = cache_setting
end
# Check if Setting caching should be performed
def self.use_caching?
!Thread.current['chiliproject/settings/do_not_use_caching']
end
# Dis-/En-able Setting caching. This is mainly intended to be used in tests
def self.use_caching=(new_value)
Thread.current['chiliproject/settings/do_not_use_caching'] = !new_value
end end
private private
@ -162,7 +185,13 @@ private
def self.find_or_default(name) def self.find_or_default(name)
name = name.to_s name = name.to_s
raise "There's no setting named #{name}" unless @@available_settings.has_key?(name) raise "There's no setting named #{name}" unless @@available_settings.has_key?(name)
setting = find_by_name(name) find_by_name(name) or new do |s|
setting ||= new(:name => name, :value => @@available_settings[name]['default']) if @@available_settings.has_key? name s.name = name
s.value = @@available_settings[name]['default']
end
end
def self.cache_key(name)
"chiliproject/setting/#{Setting.maximum(:updated_on).to_i}/#{name}"
end end
end end

View File

@ -35,6 +35,10 @@ class Tracker < ActiveRecord::Base
name <=> tracker.name name <=> tracker.name
end end
def to_liquid
TrackerDrop.new(self)
end
def self.all def self.all
find(:all, :order => 'position') find(:all, :order => 'position')
end end

View File

@ -64,10 +64,9 @@ class User < Principal
validates_uniqueness_of :mail, :if => Proc.new { |user| !user.mail.blank? }, :case_sensitive => false validates_uniqueness_of :mail, :if => Proc.new { |user| !user.mail.blank? }, :case_sensitive => false
# Login must contain lettres, numbers, underscores only # Login must contain lettres, numbers, underscores only
validates_format_of :login, :with => /^[a-z0-9_\-@\.]*$/i validates_format_of :login, :with => /^[a-z0-9_\-@\.]*$/i
validates_length_of :login, :maximum => 30 validates_length_of :login, :firstname, :lastname, :maximum => 255
validates_length_of :firstname, :lastname, :maximum => 30
validates_format_of :mail, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i, :allow_nil => true validates_format_of :mail, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i, :allow_nil => true
validates_length_of :mail, :maximum => 60, :allow_nil => true validates_length_of :mail, :maximum => 255, :allow_nil => true
validates_confirmation_of :password, :allow_nil => true validates_confirmation_of :password, :allow_nil => true
validates_inclusion_of :mail_notification, :in => MAIL_NOTIFICATION_OPTIONS.collect(&:first), :allow_blank => true validates_inclusion_of :mail_notification, :in => MAIL_NOTIFICATION_OPTIONS.collect(&:first), :allow_blank => true
validates_inclusion_of :status, :in => [STATUS_ANONYMOUS, STATUS_ACTIVE, STATUS_REGISTERED, STATUS_LOCKED] validates_inclusion_of :status, :in => [STATUS_ANONYMOUS, STATUS_ACTIVE, STATUS_REGISTERED, STATUS_LOCKED]
@ -345,27 +344,6 @@ class User < Principal
!logged? !logged?
end end
# Return user's roles for project
def roles_for_project(project)
roles = []
# No role on archived projects
return roles unless project && project.active?
if logged?
# Find project membership
membership = memberships.detect {|m| m.project_id == project.id}
if membership
roles = membership.roles
else
@role_non_member ||= Role.non_member
roles << @role_non_member
end
else
@role_anonymous ||= Role.anonymous
roles << @role_anonymous
end
roles
end
# Return true if the user is a member of project # Return true if the user is a member of project
def member_of?(project) def member_of?(project)
!roles_for_project(project).detect {|role| role.member?}.nil? !roles_for_project(project).detect {|role| role.member?}.nil?
@ -388,53 +366,6 @@ class User < Principal
@projects_by_role @projects_by_role
end end
# Return true if the user is allowed to do the specified action on a specific context
# Action can be:
# * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
# * a permission Symbol (eg. :edit_project)
# Context can be:
# * a project : returns true if user is allowed to do the specified action on this project
# * a group of projects : returns true if user is allowed on every project
# * nil with options[:global] set : check if user has at least one role allowed for this action,
# or falls back to Non Member / Anonymous permissions depending if the user is logged
def allowed_to?(action, context, options={})
if context && context.is_a?(Project)
# No action allowed on archived projects
return false unless context.active?
# No action allowed on disabled modules
return false unless context.allows_to?(action)
# Admin users are authorized for anything else
return true if admin?
roles = roles_for_project(context)
return false unless roles
roles.detect {|role| (context.is_public? || role.member?) && role.allowed_to?(action)}
elsif context && context.is_a?(Array)
# Authorize if user is authorized on every element of the array
context.map do |project|
allowed_to?(action,project,options)
end.inject do |memo,allowed|
memo && allowed
end
elsif options[:global]
# Admin users are always authorized
return true if admin?
# authorize if user has at least one role that has this permission
roles = memberships.collect {|m| m.roles}.flatten.uniq
roles.detect {|r| r.allowed_to?(action)} || (self.logged? ? Role.non_member.allowed_to?(action) : Role.anonymous.allowed_to?(action))
else
false
end
end
# Is the user allowed to do the specified action on any project?
# See allowed_to? for the actions and valid options.
def allowed_to_globally?(action, options)
allowed_to?(action, nil, options.reverse_merge(:global => true))
end
safe_attributes 'login', safe_attributes 'login',
'firstname', 'firstname',
'lastname', 'lastname',
@ -472,6 +403,8 @@ class User < Principal
when 'only_my_events' when 'only_my_events'
if object.is_a?(Issue) && (object.author == self || object.assigned_to == self) if object.is_a?(Issue) && (object.author == self || object.assigned_to == self)
true true
elsif object.respond_to?(:watched_by?) && object.watched_by?(self) # Make it clear that we always want to be notified about things we watch in this case
true
else else
false false
end end

View File

@ -27,7 +27,7 @@ class Version < ActiveRecord::Base
validates_presence_of :name validates_presence_of :name
validates_uniqueness_of :name, :scope => [:project_id] validates_uniqueness_of :name, :scope => [:project_id]
validates_length_of :name, :maximum => 60 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 :status, :in => VERSION_STATUSES
validates_inclusion_of :sharing, :in => VERSION_SHARINGS validates_inclusion_of :sharing, :in => VERSION_SHARINGS
@ -37,6 +37,7 @@ class Version < ActiveRecord::Base
safe_attributes 'name', safe_attributes 'name',
'description', 'description',
'start_date',
'effective_date', 'effective_date',
'due_date', 'due_date',
'wiki_page_title', 'wiki_page_title',

View File

@ -14,7 +14,7 @@
class Watcher < ActiveRecord::Base class Watcher < ActiveRecord::Base
belongs_to :watchable, :polymorphic => true belongs_to :watchable, :polymorphic => true
belongs_to :user belongs_to :user, :class_name => 'Principal', :foreign_key => 'user_id'
validates_presence_of :user validates_presence_of :user
validates_uniqueness_of :user_id, :scope => [:watchable_type, :watchable_id] validates_uniqueness_of :user_id, :scope => [:watchable_type, :watchable_id]

View File

@ -98,7 +98,7 @@ class WikiContent < ActiveRecord::Base
changes.delete("text") changes.delete("text")
changes["data"] = hash[:text] changes["data"] = hash[:text]
changes["compression"] = hash[:compression] changes["compression"] = hash[:compression]
update_attribute(:changes, changes.to_yaml) update_attribute(:changes, changes)
end end
def text def text

View File

@ -53,6 +53,10 @@ class WikiPage < ActiveRecord::Base
end end
end end
def to_liquid
WikiPageDrop.new(self)
end
def visible?(user=User.current) def visible?(user=User.current)
!user.nil? && user.allowed_to?(:view_wiki_pages, project) !user.nil? && user.allowed_to?(:view_wiki_pages, project)
end end

View File

@ -86,8 +86,8 @@ class Workflow < ActiveRecord::Base
else else
transaction do transaction do
delete_all :tracker_id => target_tracker.id, :role_id => target_role.id delete_all :tracker_id => target_tracker.id, :role_id => target_role.id
connection.insert "INSERT INTO #{Workflow.table_name} (tracker_id, role_id, old_status_id, new_status_id)" + connection.insert "INSERT INTO #{Workflow.table_name} (tracker_id, role_id, old_status_id, new_status_id, author, assignee)" +
" SELECT #{target_tracker.id}, #{target_role.id}, old_status_id, new_status_id" + " SELECT #{target_tracker.id}, #{target_role.id}, old_status_id, new_status_id, author, assignee" +
" FROM #{Workflow.table_name}" + " FROM #{Workflow.table_name}" +
" WHERE tracker_id = #{source_tracker.id} AND role_id = #{source_role.id}" " WHERE tracker_id = #{source_tracker.id} AND role_id = #{source_role.id}"
end end

View File

@ -0,0 +1,35 @@
<div id="nav-login-content">
<% form_tag({:controller => "account", :action=> "login"}) do %>
<%= hidden_field_tag 'back_url', CGI.escape(request.url), :id => nil %>
<table>
<tr>
<td><label for="username-pulldown"><%= l(:field_login) %></label></td>
<td><label for="password-pulldown"><%= l(:field_password) %></label></td>
<td></td>
</tr>
<tr>
<td><%= text_field_tag 'username', nil, :tabindex => '1', :id => 'username-pulldown' %></td>
<td><%= password_field_tag 'password', nil, :tabindex => '1', :id => 'password-pulldown' %></td>
<td><input type="submit" name="login" value="<%=l(:button_login)%>" tabindex="1"/></td>
</tr>
</table>
<div id = "optional_login_fields" style = "top = 10px; white-space:nowrap">
<% if Setting.openid? %>
<%= text_field_tag "openid_url", nil, :placeholder => l(:field_identity_url), :tabindex => '1' %>
<% end %>
<% if Setting.autologin? %>
<label for="autologin"><%= check_box_tag 'autologin', 1, false, :tabindex => 1 %> <%= l(:label_stay_logged_in) %></label>
<% end %>
<% if Setting.lost_password? %>
<%= link_to l(:label_password_lost), {:controller => 'account', :action => 'lost_password'}, :tabindex => 1 %>
<% end %>
<% if !User.current.logged? && Setting.self_registration? %>
<%= "|" if Setting.lost_password? %>
<%= link_to l(:label_register), { :controller => 'account', :action => 'register' }, :tabindex => 1 %>
<% end %>
</div>
<% end %>
</div>

View File

@ -5,7 +5,7 @@
<table> <table>
<tr> <tr>
<td align="right"><label for="username"><%=l(:field_login)%>:</label></td> <td align="right"><label for="username"><%=l(:field_login)%>:</label></td>
<td align="left"><%= text_field_tag 'username', nil, :tabindex => '1' %></td> <td align="left"><%= text_field_tag 'username', nil, :tabindex => '2' %></td>
</tr> </tr>
<tr> <tr>
<td align="right"><label for="password"><%=l(:field_password)%>:</label></td> <td align="right"><label for="password"><%=l(:field_password)%>:</label></td>

View File

@ -1,6 +1,6 @@
<h2><%=l(:label_register)%> <%=link_to l(:label_login_with_open_id_option), signin_url if Setting.openid? %></h2> <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' %> <%= error_messages_for 'user' %>
<div class="box"> <div class="box">

View File

@ -42,6 +42,7 @@
<% content_for :sidebar do %> <% content_for :sidebar do %>
<% form_tag({}, :method => :get) do %> <% form_tag({}, :method => :get) do %>
<h3><%= l(:label_activity) %></h3> <h3><%= l(:label_activity) %></h3>
<%= hidden_field_tag "set_filter", 1, :id => nil %>
<p><% @activity.event_types.each do |t| %> <p><% @activity.event_types.each do |t| %>
<%= check_box_tag "show_#{t}", 1, @activity.scope.include?(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> <label for="show_<%=t%>"><%= link_to(l("label_#{t.singularize}_plural"), {"show_#{t}" => 1, :user_id => params[:user_id]})%></label>

View File

@ -1,5 +1 @@
<div id="admin-menu"> <%= render_menu :admin_menu %>
<ul>
<%= render_menu :admin_menu %>
</ul>
</div>

View File

@ -1,5 +1,3 @@
<h2><%=l(:label_administration)%></h2>
<div id="admin-index"> <div id="admin-index">
<%= render :partial => 'no_data' if @no_configuration_data %> <%= render :partial => 'no_data' if @no_configuration_data %>
<%= render :partial => 'menu' %> <%= render :partial => 'menu' %>

View File

@ -0,0 +1 @@
<%= projects_check_box_tags 'project_ids[]', @projects %>

View File

@ -71,3 +71,13 @@
<%= auto_discovery_link_tag(:atom, {:format => 'atom', :key => User.current.rss_key}, :title => "#{@project}: #{@board}") %> <%= auto_discovery_link_tag(:atom, {:format => 'atom', :key => User.current.rss_key}, :title => "#{@project}: #{@board}") %>
<%= stylesheet_link_tag 'scm' %> <%= stylesheet_link_tag 'scm' %>
<% end %> <% end %>
<% content_for :sidebar do %>
<% if User.current.allowed_to?(:add_board_watchers, @project) ||
(@board.watchers.present? && User.current.allowed_to?(:view_board_watchers, @project)) %>
<div id="watchers">
<%= render :partial => 'watchers/watchers', :locals => {:watched => @board} %>
</div>
<% end %>
<% end %>

View File

@ -1,52 +1,55 @@
<ul> <ul class="menu">
<%= call_hook(:view_issues_context_menu_start, {:issues => @issues, :can => @can, :back => @back }) %> <%= call_hook(:view_issues_context_menu_start, {:issues => @issues, :can => @can, :back => @back }) %>
<% if !@issue.nil? -%> <% if !@issue.nil? -%>
<li><%= context_menu_link l(:button_edit), {:controller => 'issues', :action => 'edit', :id => @issue}, <li class="edit"><%= context_menu_link l(:button_edit), {:controller => 'issues', :action => 'edit', :id => @issue},
:class => 'icon-edit', :disabled => !@can[:edit] %></li> :class => 'icon-edit', :disabled => !@can[:edit] %></li>
<% else %> <% else %>
<li><%= context_menu_link l(:button_edit), {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id)}, <li class="edit"><%= context_menu_link l(:button_edit), {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id)},
:class => 'icon-edit', :disabled => !@can[:edit] %></li> :class => 'icon-edit', :disabled => !@can[:edit] %></li>
<% end %> <% end %>
<% if @allowed_statuses.present? %> <% if @allowed_statuses.present? %>
<li class="folder"> <li class="folder status">
<a href="#" class="submenu" onclick="return false;"><%= l(:field_status) %></a> <a href="#" class="context_item" onclick="return false;"><%= l(:field_status) %></a>
<ul> <ul>
<% @statuses.each do |s| -%> <% @statuses.each do |s| -%>
<li><%= context_menu_link s.name, {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), :issue => {:status_id => s}, :back_url => @back}, :method => :post, <li><%= context_menu_link s.name, {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), :issue => {:status_id => s}, :back_url => @back}, :method => :post,
:selected => (@issue && s == @issue.status), :disabled => !(@can[:update] && @allowed_statuses.include?(s)) %></li> :selected => (@issue && s == @issue.status), :disabled => !(@can[:update] && @allowed_statuses.include?(s)) %></li>
<% end -%> <% end -%>
</ul> </ul>
<div class="submenu"></div>
</li> </li>
<% end %> <% end %>
<% unless @trackers.nil? %> <% unless @trackers.nil? %>
<li class="folder"> <li class="folder tracker">
<a href="#" class="submenu"><%= l(:field_tracker) %></a> <a href="#" class="context_item"><%= l(:field_tracker) %></a>
<ul> <ul>
<% @trackers.each do |t| -%> <% @trackers.each do |t| -%>
<li><%= context_menu_link t.name, {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), :issue => {'tracker_id' => t}, :back_url => @back}, :method => :post, <li><%= context_menu_link t.name, {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), :issue => {'tracker_id' => t}, :back_url => @back}, :method => :post,
:selected => (@issue && t == @issue.tracker), :disabled => !@can[:edit] %></li> :selected => (@issue && t == @issue.tracker), :disabled => !@can[:edit] %></li>
<% end -%> <% end -%>
</ul> </ul>
<div class="submenu"></div>
</li> </li>
<% end %> <% end %>
<li class="folder"> <li class="folder priority">
<a href="#" class="submenu"><%= l(:field_priority) %></a> <a href="#" class="context_item"><%= l(:field_priority) %></a>
<ul> <ul>
<% @priorities.each do |p| -%> <% @priorities.each do |p| -%>
<li><%= context_menu_link p.name, {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), :issue => {'priority_id' => p}, :back_url => @back}, :method => :post, <li><%= context_menu_link p.name, {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), :issue => {'priority_id' => p}, :back_url => @back}, :method => :post,
:selected => (@issue && p == @issue.priority), :disabled => (!@can[:edit] || @issues.detect {|i| !i.leaf?}) %></li> :selected => (@issue && p == @issue.priority), :disabled => (!@can[:edit] || @issues.detect {|i| !i.leaf?}) %></li>
<% end -%> <% end -%>
</ul> </ul>
<div class="submenu"></div>
</li> </li>
<% #TODO: allow editing versions when multiple projects %> <% #TODO: allow editing versions when multiple projects %>
<% unless @project.nil? || @project.shared_versions.open.empty? -%> <% unless @project.nil? || @project.shared_versions.open.empty? -%>
<li class="folder"> <li class="folder fixed_version">
<a href="#" class="submenu"><%= l(:field_fixed_version) %></a> <a href="#" class="context_item"><%= l(:field_fixed_version) %></a>
<ul> <ul>
<% @project.shared_versions.open.sort.each do |v| -%> <% @project.shared_versions.open.sort.each do |v| -%>
<li><%= context_menu_link format_version_name(v), {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), :issue => {'fixed_version_id' => v}, :back_url => @back}, :method => :post, <li><%= context_menu_link format_version_name(v), {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), :issue => {'fixed_version_id' => v}, :back_url => @back}, :method => :post,
@ -55,11 +58,13 @@
<li><%= context_menu_link l(:label_none), {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), :issue => {'fixed_version_id' => 'none'}, :back_url => @back}, :method => :post, <li><%= context_menu_link l(:label_none), {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), :issue => {'fixed_version_id' => 'none'}, :back_url => @back}, :method => :post,
:selected => (@issue && @issue.fixed_version.nil?), :disabled => !@can[:update] %></li> :selected => (@issue && @issue.fixed_version.nil?), :disabled => !@can[:update] %></li>
</ul> </ul>
<div class="submenu"></div>
</li> </li>
<% end %> <% end %>
<% if @assignables.present? -%> <% if @assignables.present? -%>
<li class="folder"> <li class="folder assigned">
<a href="#" class="submenu"><%= l(:field_assigned_to) %></a> <a href="#" class="context_item"><%= l(:field_assigned_to) %></a>
<ul> <ul>
<% @assignables.each do |u| -%> <% @assignables.each do |u| -%>
<li><%= context_menu_link u.name, {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), :issue => {'assigned_to_id' => u}, :back_url => @back}, :method => :post, <li><%= context_menu_link u.name, {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), :issue => {'assigned_to_id' => u}, :back_url => @back}, :method => :post,
@ -68,11 +73,13 @@
<li><%= context_menu_link l(:label_nobody), {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), :issue => {'assigned_to_id' => 'none'}, :back_url => @back}, :method => :post, <li><%= context_menu_link l(:label_nobody), {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), :issue => {'assigned_to_id' => 'none'}, :back_url => @back}, :method => :post,
:selected => (@issue && @issue.assigned_to.nil?), :disabled => !@can[:update] %></li> :selected => (@issue && @issue.assigned_to.nil?), :disabled => !@can[:update] %></li>
</ul> </ul>
<div class="submenu"></div>
</li> </li>
<% end %> <% end %>
<% unless @project.nil? || @project.issue_categories.empty? -%> <% unless @project.nil? || @project.issue_categories.empty? -%>
<li class="folder"> <li class="folder">
<a href="#" class="submenu"><%= l(:field_category) %></a> <a href="#" class="context_item"><%= l(:field_category) %></a>
<ul> <ul>
<% @project.issue_categories.each do |u| -%> <% @project.issue_categories.each do |u| -%>
<li><%= context_menu_link u.name, {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), :issue => {'category_id' => u}, :back_url => @back}, :method => :post, <li><%= context_menu_link u.name, {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), :issue => {'category_id' => u}, :back_url => @back}, :method => :post,
@ -81,28 +88,30 @@
<li><%= context_menu_link l(:label_none), {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), :issue => {'category_id' => 'none'}, :back_url => @back}, :method => :post, <li><%= context_menu_link l(:label_none), {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), :issue => {'category_id' => 'none'}, :back_url => @back}, :method => :post,
:selected => (@issue && @issue.category.nil?), :disabled => !@can[:update] %></li> :selected => (@issue && @issue.category.nil?), :disabled => !@can[:update] %></li>
</ul> </ul>
<div class="submenu"></div>
</li> </li>
<% end -%> <% end -%>
<% if Issue.use_field_for_done_ratio? %> <% if Issue.use_field_for_done_ratio? %>
<li class="folder"> <li class="folder done_ratio">
<a href="#" class="submenu"><%= l(:field_done_ratio) %></a> <a href="#" class="context_item"><%= l(:field_done_ratio) %></a>
<ul> <ul>
<% (0..10).map{|x|x*10}.each do |p| -%> <% (0..10).map{|x|x*10}.each do |p| -%>
<li><%= context_menu_link "#{p}%", {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), :issue => {'done_ratio' => p}, :back_url => @back}, :method => :post, <li><%= context_menu_link "#{p}%", {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), :issue => {'done_ratio' => p}, :back_url => @back}, :method => :post,
:selected => (@issue && p == @issue.done_ratio), :disabled => (!@can[:edit] || @issues.detect {|i| !i.leaf?}) %></li> :selected => (@issue && p == @issue.done_ratio), :disabled => (!@can[:edit] || @issues.detect {|i| !i.leaf?}) %></li>
<% end -%> <% end -%>
</ul> </ul>
<div class="submenu"></div>
</li> </li>
<% end %> <% end %>
<% if !@issue.nil? %> <% if !@issue.nil? %>
<% if @can[:log_time] -%> <% if @can[:log_time] -%>
<li><%= context_menu_link l(:button_log_time), {:controller => 'timelog', :action => 'new', :issue_id => @issue}, <li class="log_time"><%= context_menu_link l(:button_log_time), {:controller => 'timelog', :action => 'new', :issue_id => @issue},
:class => 'icon-time-add' %></li> :class => 'context_item' %></li>
<% end %> <% end %>
<% if User.current.logged? %> <% if User.current.logged? %>
<li><%= watcher_link(@issue, User.current) %></li> <li class="watch"><%= watcher_link(@issue, User.current) %></li>
<% end %> <% end %>
<% end %> <% end %>
@ -110,12 +119,13 @@
<li><%= context_menu_link l(:button_duplicate), {:controller => 'issues', :action => 'new', :project_id => @project, :copy_from => @issue}, <li><%= context_menu_link l(:button_duplicate), {:controller => 'issues', :action => 'new', :project_id => @project, :copy_from => @issue},
:class => 'icon-duplicate', :disabled => !@can[:copy] %></li> :class => 'icon-duplicate', :disabled => !@can[:copy] %></li>
<% end %> <% end %>
<li><%= context_menu_link l(:button_copy), new_issue_move_path(:ids => @issues.collect(&:id), :copy_options => {:copy => 't'}),
:class => 'icon-copy', :disabled => !@can[:move] %></li> <li class="move"><%= context_menu_link l(:button_move), new_issue_move_path(:ids => @issues.collect(&:id)),
<li><%= context_menu_link l(:button_move), new_issue_move_path(:ids => @issues.collect(&:id)), :class => 'context_item', :disabled => !@can[:move] %></li>
:class => 'icon-move', :disabled => !@can[:move] %></li> <li class="copy"><%= context_menu_link l(:button_copy), new_issue_move_path(:ids => @issues.collect(&:id), :copy_options => {:copy => 't'}),
<li><%= context_menu_link l(:button_delete), {:controller => 'issues', :action => 'destroy', :ids => @issues.collect(&:id), :back_url => @back}, :class => 'context_item' %></li>
:method => :post, :confirm => l(:text_issues_destroy_confirmation), :class => 'icon-del', :disabled => !@can[:delete] %></li> <li class="delete"><%= context_menu_link l(:button_delete), {:controller => 'issues', :action => 'destroy', :ids => @issues.collect(&:id)},
:method => :post, :confirm => l(:text_issues_destroy_confirmation), :class => 'context_item', :disabled => !@can[:delete] %></li>
<%= call_hook(:view_issues_context_menu_end, {:issues => @issues, :can => @can, :back => @back }) %> <%= call_hook(:view_issues_context_menu_end, {:issues => @issues, :can => @can, :back => @back }) %>
</ul> </ul>

View File

@ -9,6 +9,15 @@
<p><label for="document_description"><%=l(:field_description)%></label> <p><label for="document_description"><%=l(:field_description)%></label>
<%= text_area 'document', 'description', :cols => 60, :rows => 15, :class => 'wiki-edit' %></p> <%= text_area 'document', 'description', :cols => 60, :rows => 15, :class => 'wiki-edit' %></p>
<% if User.current.allowed_to?(:add_document_watchers, @project) -%>
<p id="watchers_form"><label><%= l(:label_document_watchers) %></label>
<% @document.project.users.sort.each do |user| -%>
<label class="floating"><%= check_box_tag 'document[watcher_user_ids][]', user.id, @document.watched_by?(user) %> <%=h user %></label>
<% end -%>
</p>
<% end %>
<!--[eoform:document]--> <!--[eoform:document]-->
</div> </div>

View File

@ -27,6 +27,16 @@
<% html_title h(@document.title) -%> <% html_title h(@document.title) -%>
<% content_for :sidebar do %>
<% if User.current.allowed_to?(:add_document_watchers, @project) ||
(@document.watchers.present? && User.current.allowed_to?(:view_document_watchers, @project)) %>
<div id="watchers">
<%= render :partial => 'watchers/watchers', :locals => {:watched => @document} %>
</div>
<% end %>
<% end %>
<% content_for :header_tags do %> <% content_for :header_tags do %>
<%= stylesheet_link_tag 'scm' %> <%= stylesheet_link_tag 'scm' %>
<% end %> <% end %>

View File

@ -72,7 +72,7 @@ t_height = g_height + headers_height
<p class="warning"><%= l(:notice_gantt_chart_truncated, :max => @gantt.max_rows) %></p> <p class="warning"><%= l(:notice_gantt_chart_truncated, :max => @gantt.max_rows) %></p>
<% end %> <% end %>
<table width="100%" style="border:0; border-collapse: collapse;"> <table style="width:100%; border:0; border-collapse: collapse;">
<tr> <tr>
<td style="width:<%= subject_width %>px; padding:0px;"> <td style="width:<%= subject_width %>px; padding:0px;">
@ -98,7 +98,7 @@ month_f = @gantt.date_from
left = 0 left = 0
height = (show_weeks ? header_heigth : header_heigth + g_height) height = (show_weeks ? header_heigth : header_heigth + g_height)
@gantt.months.times do @gantt.months.times do
width = ((month_f >> 1) - month_f) * zoom - 1 width = (((month_f >> 1) - month_f) * zoom - 1).to_i
%> %>
<div style="left:<%= left %>px;width:<%= width %>px;height:<%= height %>px;" class="gantt_hdr"> <div style="left:<%= left %>px;width:<%= width %>px;height:<%= height %>px;" class="gantt_hdr">
<%= link_to h("#{month_f.year}-#{month_f.month}"), @gantt.params.merge(:year => month_f.year, :month => month_f.month), :title => "#{month_name(month_f.month)} #{month_f.year}"%> <%= link_to h("#{month_f.year}-#{month_f.month}"), @gantt.params.merge(:year => month_f.year, :month => month_f.month), :title => "#{month_name(month_f.month)} #{month_f.year}"%>
@ -176,7 +176,7 @@ if Date.today >= @gantt.date_from and Date.today <= @gantt.date_to %>
</tr> </tr>
</table> </table>
<table width="100%"> <table style="width:100%">
<tr> <tr>
<td align="left"><%= link_to_content_update('&#171; ' + l(:label_previous), params.merge(@gantt.params_previous)) %></td> <td align="left"><%= link_to_content_update('&#171; ' + l(:label_previous), params.merge(@gantt.params_previous)) %></td>
<td align="right"><%= link_to_content_update(l(:label_next) + ' &#187;', params.merge(@gantt.params_next)) %></td> <td align="right"><%= link_to_content_update(l(:label_next) + ' &#187;', params.merge(@gantt.params_next)) %></td>

View File

@ -41,17 +41,5 @@
</div> </div>
<div class="splitcontentright"> <div class="splitcontentright">
<% if projects.any? %> <%= render :partial => 'members/membership_assignment', :locals => {:principal => @group, :projects => projects - @group.projects, :roles => roles } %>
<fieldset><legend><%=l(:label_project_new)%></legend>
<% remote_form_for(:membership, :url => { :action => 'edit_membership', :id => @group }) do %>
<%= label_tag "membership_project_id", l(:description_choose_project), :class => "hidden-for-sighted" %>
<%= select_tag 'membership[project_id]', options_for_membership_project_select(@group, projects) %>
<p><%= l(:label_role_plural) %>:
<% roles.each do |role| %>
<label><%= check_box_tag 'membership[role_ids][]', role.id %> <%=h role %></label>
<% end %></p>
<p><%= submit_tag l(:button_add) %></p>
<% end %>
</fieldset>
<% end %>
</div> </div>

View File

@ -33,7 +33,7 @@
<%= observe_field(:user_search, <%= observe_field(:user_search,
:frequency => 0.5, :frequency => 0.5,
:update => :users, :update => :users,
:url => { :controller => 'groups', :action => 'autocomplete_for_user', :id => @group }, :url => auto_complete_users_path(:remove_group_members => @group),
:with => 'q') :with => 'q')
%> %>

View File

@ -19,6 +19,7 @@
<td class="buttons"><%= link_to l(:button_delete), group, :confirm => l(:text_are_you_sure), :method => :delete, :class => 'icon icon-del' %></td> <td class="buttons"><%= link_to l(:button_delete), group, :confirm => l(:text_are_you_sure), :method => :delete, :class => 'icon icon-del' %></td>
</tr> </tr>
<% end %> <% end %>
</tbody>
</table> </table>
<% else %> <% else %>
<p class="nodata"><%= l(:label_no_data) %></p> <p class="nodata"><%= l(:label_no_data) %></p>

View File

@ -12,7 +12,7 @@
<% html_title "Wiki Syntax Quick Reference" %> <% html_title "Wiki Syntax Quick Reference" %>
<h1>Wiki Syntax Quick Reference</h1> <h1>Wiki Syntax Quick Reference</h1>
<table width="100%"> <table style="width:100%">
<tr><th colspan="3">Font Styles</th></tr> <tr><th colspan="3">Font Styles</th></tr>
<tr><th><img src="../images/jstoolbar/bt_strong.png" style="border: 1px solid #bbb;" alt="Strong" /></th><td width="50%">*Strong*</td><td width="50%"><strong>Strong</strong></td></tr> <tr><th><img src="../images/jstoolbar/bt_strong.png" style="border: 1px solid #bbb;" alt="Strong" /></th><td width="50%">*Strong*</td><td width="50%"><strong>Strong</strong></td></tr>
<tr><th><img src="../images/jstoolbar/bt_em.png" style="border: 1px solid #bbb;" alt="Italic" /></th><td>_Italic_</td><td><em>Italic</em></td></tr> <tr><th><img src="../images/jstoolbar/bt_em.png" style="border: 1px solid #bbb;" alt="Italic" /></th><td>_Italic_</td><td><em>Italic</em></td></tr>

View File

@ -13,20 +13,22 @@
} }
a.new { color: #b73535; } a.new { color: #b73535; }
.CodeRay .c { color:#666; } .syntaxhl .line-numbers { padding: 2px 4px 2px 4px; background-color: #eee; margin:0 }
.syntaxhl .comment { color:#666; }
.CodeRay .cl { color:#B06; font-weight:bold } .syntaxhl .class { color:#B06; font-weight:bold }
.CodeRay .dl { color:black } .syntaxhl .delimiter { color:black }
.CodeRay .fu { color:#06B; font-weight:bold } .syntaxhl .function { color:#06B; font-weight:bold }
.CodeRay .il { background: #eee } .syntaxhl .inline { background: #eee }
.CodeRay .il .idl { font-weight: bold; color: #888 } .syntaxhl .inline .inline-delimiter { font-weight: bold; color: #888 }
.CodeRay .iv { color:#33B } .syntaxhl .instance-variable { color:#33B }
.CodeRay .r { color:#080; font-weight:bold } .syntaxhl .reserved { color:#080; font-weight:bold }
.syntaxhl .string { background-color:#fff0f0; color: #D20; }
.syntaxhl .string .delimiter { color:#710 }
.CodeRay .s { background-color:#fff0f0 }
.CodeRay .s .dl { color:#710 }
<% end %> <% end %>
<% html_title "Wiki Formatting" %> <% html_title "Wiki Formatting" %>
@ -236,7 +238,7 @@ To go live, all you need to add is a database and a web server.
<h2><a name="13" class="wiki-page"></a>Code highlighting</h2> <h2><a name="13" class="wiki-page"></a>Code highlighting</h2>
<p>Code highlightment relies on <a href="http://coderay.rubychan.de/" class="external">CodeRay</a>, a fast syntax highlighting library written completely in Ruby. It currently supports c, cpp, css, delphi, groovy, html, java, javascript, json, php, python, rhtml, ruby, scheme, sql, xml and yaml languages.</p> <p>The default code highlighting relies on <a href="http://coderay.rubychan.de/" class="external">CodeRay</a>, a fast syntax highlighting library written completely in Ruby. It currently supports c, cpp, css, delphi, groovy, html, java, javascript, json, php, python, rhtml, ruby, scheme, sql, xml and yaml languages.</p>
<p>You can highlight code in your wiki page using this syntax:</p> <p>You can highlight code in your wiki page using this syntax:</p>
@ -248,15 +250,14 @@ To go live, all you need to add is a database and a web server.
<p>Example:</p> <p>Example:</p>
<pre><code class="ruby CodeRay"><span class="no"> 1</span> <span class="c"># The Greeter class</span> <pre><code class="ruby syntaxhl"><span class="line-numbers"> 1</span> <span class="comment"># The Greeter class</span>
<span class="no"> 2</span> <span class="r">class</span> <span class="cl">Greeter</span> <span class="line-numbers"> 2</span> <span class="reserved">class</span> <span class="class">Greeter</span>
<span class="no"> 3</span> <span class="r">def</span> <span class="fu">initialize</span>(name) <span class="line-numbers"> 3</span> <span class="reserved">def</span> <span class="function">initialize</span>(name)
<span class="no"> 4</span> <span class="iv">@name</span> = name.capitalize <span class="line-numbers"> 4</span> <span class="instance-variable">@name</span> = name.capitalize
<span class="no"> 5</span> <span class="r">end</span> <span class="line-numbers"> 5</span> <span class="reserved">end</span>
<span class="no"> 6</span> <span class="line-numbers"> 6</span>
<span class="no"> 7</span> <span class="r">def</span> <span class="fu">salute</span> <span class="line-numbers"> 7</span> <span class="reserved">def</span> <span class="function">salute</span>
<span class="no"> 8</span> puts <span class="s"><span class="dl">"</span><span class="k">Hello </span><span class="il"><span class="idl">#{</span><span class="iv">@name</span><span class="idl">}</span></span><span class="k">!</span><span class="dl">"</span></span> <span class="line-numbers"> 8</span> puts <span class="string"><span class="delimiter">"</span><span class="content">Hello </span><span class="inline"><span class="inline-delimiter">#{</span><span class="instance-variable">@name</span><span class="inline-delimiter">}</span></span><span class="content">!</span><span class="delimiter">"</span></span>
<span class="no"> 9</span> <span class="r">end</span> <span class="line-numbers"> 9</span> <span class="reserved">end</span>
<span class="no"><strong>10</strong></span> <span class="r">end</span> <span class="line-numbers"><strong>10</strong></span> <span class="reserved">end</span></code>
</code>
</pre> </pre>

View File

@ -46,12 +46,12 @@
<div class="splitcontentright"> <div class="splitcontentright">
<p> <p>
<label for='start_date'><%= l(:field_start_date) %></label> <label for='start_date'><%= l(:field_start_date) %></label>
<%= text_field_tag 'start_date', '', :size => 10 %><%= calendar_for('start_date') %> <%= date_field_tag 'start_date', '', :size => 10 %>
</p> </p>
<p> <p>
<label for='due_date'><%= l(:field_due_date) %></label> <label for='due_date'><%= l(:field_due_date) %></label>
<%= text_field_tag 'due_date', '', :size => 10 %><%= calendar_for('due_date') %> <%= date_field_tag 'due_date', '', :size => 10 %>
</p> </p>
</div> </div>

View File

@ -31,8 +31,8 @@
</div> </div>
<div class="splitcontentright"> <div class="splitcontentright">
<p><%= f.text_field :start_date, :size => 10, :disabled => !@issue.leaf? %><%= calendar_for('issue_start_date') if @issue.leaf? %></p> <p><%= f.date_field :start_date, :size => 10, :disabled => !@issue.leaf? %><%= calendar_for('issue_start_date') if @issue.leaf? %></p>
<p><%= f.text_field :due_date, :size => 10, :disabled => !@issue.leaf? %><%= calendar_for('issue_due_date') if @issue.leaf? %></p> <p><%= f.date_field :due_date, :size => 10, :disabled => !@issue.leaf? %><%= calendar_for('issue_due_date') if @issue.leaf? %></p>
<p><%= f.text_field :estimated_hours, :size => 3, :disabled => !@issue.leaf? %> <%= l(:field_hours) %></p> <p><%= f.text_field :estimated_hours, :size => 3, :disabled => !@issue.leaf? %> <%= l(:field_hours) %></p>
<% if @issue.leaf? && Issue.use_field_for_done_ratio? %> <% if @issue.leaf? && Issue.use_field_for_done_ratio? %>
<p><%= f.select :done_ratio, ((0..10).to_a.collect {|r| ["#{r*10} %", r*10] }) %></p> <p><%= f.select :done_ratio, ((0..10).to_a.collect {|r| ["#{r*10} %", r*10] }) %></p>

View File

@ -38,7 +38,7 @@
<%= call_hook(:view_issues_edit_notes_bottom, { :issue => @issue, :notes => @notes, :form => f }) %> <%= call_hook(:view_issues_edit_notes_bottom, { :issue => @issue, :notes => @notes, :form => f }) %>
</fieldset> </fieldset>
<fieldset id="attachments" class="collapsible collapsed borders"><legend onclick="toggleFieldset(this);"><%=l(:label_attachment_plural)%></legend> <fieldset id="attachments" class="header_collapsible collapsible collapsed"><legend onclick="toggleFieldset(this);"><%=l(:label_attachment_plural)%></legend>
<div style="display: none;"> <div style="display: none;">
<%= render :partial => 'attachments/form' %> <%= render :partial => 'attachments/form' %>
</div> </div>

View File

@ -1,10 +1,9 @@
<% form_tag({}) do -%> <% form_tag({}) do -%>
<%= hidden_field_tag 'back_url', url_for(params) %> <%= hidden_field_tag 'back_url', url_for(params), :id => nil %>
<div class="autoscroll"> <div class="autoscroll">
<table class="list issues"> <table class="list issues">
<thead><tr> <thead><tr>
<th class="checkbox hide-when-print"><%= link_to image_tag('toggle_check.png'), {}, :onclick => 'toggleIssuesSelection(Element.up(this, "form")); return false;', <th class="checkbox hide-when-print"><%= link_to image_tag('toggle_check.png'), {}, :title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}" %>
:title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}" %>
</th> </th>
<%= sort_header_tag('id', :caption => '#', :default_order => 'desc') %> <%= sort_header_tag('id', :caption => '#', :default_order => 'desc') %>
<% query.columns.each do |column| %> <% query.columns.each do |column| %>

View File

@ -1,10 +1,9 @@
<div class="contextual"> <p>
<% if authorize_for('issue_relations', 'new') %> <strong><%=l(:label_related_issues)%></strong>
<%= toggle_link l(:button_add), 'new-relation-form', {:focus => 'relation_issue_to_id'} %> <% if authorize_for('issue_relations', 'new') %>
<% end %> (<%= toggle_link l(:button_add), 'new-relation-form', {:focus => 'relation_issue_to_id'} %>)
</div> <% end %>
</p>
<p><strong><%=l(:label_related_issues)%></strong></p>
<% if @relations.present? %> <% if @relations.present? %>
<table style="width:100%"> <table style="width:100%">

View File

@ -1,17 +1,7 @@
<h3><%= l(:label_issue_plural) %></h3> <h3><%= l(:label_issue_plural) %></h3>
<%= link_to l(:label_issue_view_all), { :controller => 'issues', :action => 'index', :project_id => @project, :set_filter => 1 } %><br />
<% if @project %>
<%= link_to l(:field_summary), :controller => 'reports', :action => 'issue_report', :id => @project %><br />
<% end %>
<%= call_hook(:view_issues_sidebar_issues_bottom) %> <%= call_hook(:view_issues_sidebar_issues_bottom) %>
<% if User.current.allowed_to?(:view_calendar, @project, :global => true) %>
<%= link_to(l(:label_calendar), :controller => 'calendars', :action => 'show', :project_id => @project) %><br />
<% end %>
<% if User.current.allowed_to?(:view_gantt, @project, :global => true) %>
<%= link_to(l(:label_gantt), :controller => 'gantts', :action => 'show', :project_id => @project) %><br />
<% end %>
<%= call_hook(:view_issues_sidebar_planning_bottom) %> <%= call_hook(:view_issues_sidebar_planning_bottom) %>
<%= render_sidebar_queries %> <%= render_sidebar_queries unless @project %>
<%= call_hook(:view_issues_sidebar_queries_bottom) %> <%= call_hook(:view_issues_sidebar_queries_bottom) %>

View File

@ -0,0 +1,11 @@
<% if !@issue.leaf? || @issue.parent || User.current.allowed_to?(:manage_subtasks, @project) %>
<hr />
<p>
<strong><%= l(:label_issue_hierarchy) %></strong>
<% if User.current.allowed_to?(:manage_subtasks, @project) %>
(<%= link_to(l(:label_subtask_add), {:controller => 'issues', :action => 'new', :project_id => @project, :issue => {:parent_issue_id => @issue}}) %>)
<% end %>
</p>
<% end %>
<%= render_parents_and_subtree @issue %>

View File

@ -65,11 +65,11 @@
<% end %> <% end %>
<p> <p>
<label for='issue_start_date'><%= l(:field_start_date) %></label> <label for='issue_start_date'><%= l(:field_start_date) %></label>
<%= text_field_tag 'issue[start_date]', '', :size => 10 %><%= calendar_for('issue_start_date') %> <%= date_field_tag 'issue[start_date]', '', :size => 10 %>
</p> </p>
<p> <p>
<label for='issue_due_date'><%= l(:field_due_date) %></label> <label for='issue_due_date'><%= l(:field_due_date) %></label>
<%= text_field_tag 'issue[due_date]', '', :size => 10 %><%= calendar_for('issue_due_date') %> <%= date_field_tag 'issue[due_date]', '', :size => 10 %>
</p> </p>
<% if Issue.use_field_for_done_ratio? %> <% if Issue.use_field_for_done_ratio? %>
<p> <p>

View File

@ -1,23 +1,29 @@
<div class="title-bar">
<h2><%= @query.new_record? ? l(:label_issue_plural) : h(@query.name) %></h2>
<div class="title-bar-extras">
<div class="contextual"> <div class="contextual">
<% if !@query.new_record? && @query.editable_by?(User.current) %> <% if !@query.new_record? && @query.editable_by?(User.current) %>
<%= link_to l(:button_edit), {:controller => 'queries', :action => 'edit', :id => @query}, :class => 'icon icon-edit' %> <%= link_to l(:button_edit), {:controller => 'queries', :action => 'edit', :id => @query}, :class => 'icon icon-edit' %>
<%= link_to l(:button_delete), {:controller => 'queries', :action => 'destroy', :id => @query}, :confirm => l(:text_are_you_sure), :method => :post, :class => 'icon icon-del' %> <%= link_to l(:button_delete), {:controller => 'queries', :action => 'destroy', :id => @query}, :confirm => l(:text_are_you_sure), :method => :post, :class => 'icon icon-del' %>
<% end %> <% end %>
<%= render :partial => 'queries/new_issue_button' %>
</div> </div>
<h2><%= @query.new_record? ? l(:label_issue_plural) : h(@query.name) %></h2>
<% html_title(@query.new_record? ? l(:label_issue_plural) : h(@query.name)) %> <% html_title(@query.new_record? ? l(:label_issue_plural) : h(@query.name)) %>
<% form_tag({ :controller => 'queries', :action => 'new' }, :id => 'query_form') do %> <% form_tag({ :controller => 'queries', :action => 'new' }, :id => 'query_form') do %>
<%= hidden_field_tag('project_id', @project.to_param) if @project %> <%= hidden_field_tag('project_id', @project.to_param) if @project %>
<div id="query_form_content" class="hide-when-print"> <div id="query_form_content" class="hide-when-print">
<fieldset id="filters" class="collapsible <%= @query.new_record? ? "" : "collapsed" %>"> <fieldset id="filters" class="header_collapsible collapsible <%= @query.new_record? ? "" : "collapsed" %>">
<legend onclick="toggleFieldset(this);"><%= l(:label_filter_plural) %></legend> <legend onclick="toggleFieldset(this);"><%= l(:label_filter_plural) %></legend>
<div style="<%= @query.new_record? ? "" : "display: none;" %>"> <div class="filter-fields" style="<%= @query.new_record? ? "" : "display: none;" %>">
<%= render :partial => 'queries/filters', :locals => {:query => @query} %> <%= render :partial => 'queries/filters', :locals => {:query => @query} %>
</div> </div>
</fieldset> </fieldset>
<fieldset class="collapsible collapsed"> <fieldset id="column_options" class="header_collapsible collapsible collapsed">
<legend onclick="toggleFieldset(this);"><%= l(:label_options) %></legend> <legend onclick="toggleFieldset(this);"><%= l(:label_options) %></legend>
<div style="display: none;"> <div style="display: none;">
<table> <table>
@ -29,6 +35,20 @@
<td><label for='group_by'><%= l(:field_group_by) %></label></td> <td><label for='group_by'><%= l(:field_group_by) %></label></td>
<td><%= select_tag('group_by', options_for_select([[]] + @query.groupable_columns.collect {|c| [c.caption, c.name.to_s]}, @query.group_by)) %></td> <td><%= select_tag('group_by', options_for_select([[]] + @query.groupable_columns.collect {|c| [c.caption, c.name.to_s]}, @query.group_by)) %></td>
</tr> </tr>
<tr>
<td><%= l(:label_project_plural) %></td>
<td>
<label>
<%= radio_button_tag('display_subprojects', '0', !@query.display_subprojects?) %>
<%= l(:text_current_project) %>
</label>
<br />
<label>
<%= radio_button_tag('display_subprojects', '1', @query.display_subprojects?) %>
<%= l(:label_and_its_subprojects, :value => l(:text_current_project)) %>
</label>
</td>
</tr>
</table> </table>
</div> </div>
</fieldset> </fieldset>
@ -55,6 +75,9 @@
</p> </p>
<% end %> <% end %>
</div><!-- .title-bar-extras -->
</div><!-- .title-bar -->
<%= error_messages_for 'query' %> <%= error_messages_for 'query' %>
<% if @query.valid? %> <% if @query.valid? %>
<% if @issues.empty? %> <% if @issues.empty? %>

View File

@ -1,53 +1,65 @@
<%= render :partial => 'action_menu' %> <div class="title-bar" id="upper-title-bar">
<div class="title-bar-actions">
<h2><%= h(@issue.tracker.name) %> #<%= h(@issue.id) %><%= call_hook(:view_issues_show_identifier, :issue => @issue) %></h2> <%= render :partial => 'action_menu' %>
</div>
</div>
<div class="<%= @issue.css_classes %> details"> <div class="<%= @issue.css_classes %> details">
<%= avatar(@issue.author, :size => "50") %>
<div class="subject"> <h1 class="subject">
<%= render_issue_subject_with_tree(@issue) %> <%=h(@issue.subject) %> (<%= h(@issue.tracker.name) + ' #' +@issue.id.to_s %>)
</div> </h1>
<hr />
<p class="author"> <p class="author">
<%= avatar(@issue.author, :size => "14") %>
<%= authoring @issue.created_on, @issue.author %>. <%= authoring @issue.created_on, @issue.author %>.
<% if @issue.created_on != @issue.updated_on %> <% if @issue.created_on != @issue.updated_on %>
<%= l(:label_updated_time, time_tag(@issue.updated_on)) %>. <%= l(:label_updated_time, time_tag(@issue.updated_on)) %>.
<% end %> <% end %>
</p> </p>
<hr />
<table class="attributes"> <div class="meta">
<tr> <table class="attributes">
<tr>
<th class="status"><%=l(:field_status)%>:</th><td class="status"><%= h(@issue.status.name) %></td> <th class="status"><%=l(:field_status)%>:</th><td class="status"><%= h(@issue.status.name) %></td>
<th class="start-date"><%=l(:field_start_date)%>:</th><td class="start-date"><%= format_date(@issue.start_date) %></td> <th class="start-date"><%=l(:field_start_date)%>:</th><td class="start-date"><%= format_date(@issue.start_date) %></td>
</tr> </tr>
<tr> <tr>
<th class="priority"><%=l(:field_priority)%>:</th><td class="priority"><%= h(@issue.priority.name) %></td> <th class="priority"><%=l(:field_priority)%>:</th><td class="priority"><%= h(@issue.priority.name) %></td>
<th class="due-date"><%=l(:field_due_date)%>:</th><td class="due-date"><%= format_date(@issue.due_date) %></td> <th class="due-date"><%=l(:field_due_date)%>:</th><td class="due-date"><%= format_date(@issue.due_date) %></td>
</tr> </tr>
<tr> <tr>
<th class="assigned-to"><%=l(:field_assigned_to)%>:</th><td class="assigned-to"><%= avatar(@issue.assigned_to, :size => "14") %><%= @issue.assigned_to ? link_to_user(@issue.assigned_to) : "-" %></td> <th class="assigned-to"><%=l(:field_assigned_to)%>:</th><td class="assigned-to"><%= avatar(@issue.assigned_to, :size => "14") %><%= @issue.assigned_to ? link_to_user(@issue.assigned_to) : "-" %></td>
<th class="progress"><%=l(:field_done_ratio)%>:</th><td class="progress"><%= progress_bar @issue.done_ratio, :width => '80px', :legend => "#{@issue.done_ratio}%" %></td> <th class="progress"><%=l(:field_done_ratio)%>:</th><td class="progress"><%= progress_bar @issue.done_ratio, :width => '80px', :legend => "#{@issue.done_ratio}%" %></td>
</tr> </tr>
<tr> <tr>
<th class="category"><%=l(:field_category)%>:</th><td class="category"><%=h(@issue.category ? @issue.category.name : "-") %></td> <th class="category"><%=l(:field_category)%>:</th><td class="category"><%=h(@issue.category ? @issue.category.name : "-") %></td>
<% if User.current.allowed_to?(:view_time_entries, @project) %> <% if User.current.allowed_to?(:view_time_entries, @project) %>
<th class="spent-time"><%=l(:label_spent_time)%>:</th> <th class="spent-time"><%=l(:label_spent_time)%>:</th>
<td class="spent-time"><%= @issue.spent_hours > 0 ? (link_to l_hours(@issue.spent_hours), {:controller => 'timelog', :action => 'index', :project_id => @project, :issue_id => @issue}) : "-" %></td> <td class="spent-time"><%= @issue.spent_hours > 0 ? (link_to l_hours(@issue.spent_hours), {:controller => 'timelog', :action => 'index', :project_id => @project, :issue_id => @issue}) : "-" %></td>
<% else %>
<th></th><td></td>
<% end %> <% end %>
</tr> </tr>
<tr> <tr>
<th class="fixed-version"><%=l(:field_fixed_version)%>:</th><td class="fixed-version"><%= @issue.fixed_version ? link_to_version(@issue.fixed_version) : "-" %></td> <th class="fixed-version"><%=l(:field_fixed_version)%>:</th><td class="fixed-version"><%= @issue.fixed_version ? link_to_version(@issue.fixed_version) : "-" %></td>
<% if @issue.estimated_hours %> <% if @issue.estimated_hours %>
<th class="estimated-hours"><%=l(:field_estimated_hours)%>:</th><td class="estimated-hours"><%= l_hours(@issue.estimated_hours) %></td> <th class="estimated-hours"><%=l(:field_estimated_hours)%>:</th><td class="estimated-hours"><%= l_hours(@issue.estimated_hours) %></td>
<% else %>
<th></th><td></td>
<% end %> <% end %>
</tr> </tr>
<%= render_custom_fields_rows(@issue) %> <%= render_custom_fields_rows(@issue) %>
<%= call_hook(:view_issues_show_details_bottom, :issue => @issue) %> <%= call_hook(:view_issues_show_details_bottom, :issue => @issue) %>
</table> </table>
</div><!-- .meta -->
<% if @issue.description? || @issue.attachments.any? -%>
<hr />
<% if @issue.description? %> <% if @issue.description? %>
<hr />
<div class="description">
<div class="contextual"> <div class="contextual">
<%= link_to_remote_if_authorized(l(:button_quote), { :url => {:controller => 'journals', :action => 'new', :id => @issue} }, :class => 'icon icon-comment') %> <%= link_to_remote_if_authorized(l(:button_quote), { :url => {:controller => 'journals', :action => 'new', :id => @issue} }, :class => 'icon icon-comment') %>
</div> </div>
@ -56,22 +68,17 @@
<div class="wiki"> <div class="wiki">
<%= textilizable @issue, :description, :attachments => @issue.attachments %> <%= textilizable @issue, :description, :attachments => @issue.attachments %>
</div> </div>
</div>
<% end %> <% end %>
<%= link_to_attachments @issue %>
<% if @issue.attachments.any? -%>
<hr />
<%= link_to_attachments @issue %>
<% end -%> <% end -%>
<%= call_hook(:view_issues_show_description_bottom, :issue => @issue) %> <%= call_hook(:view_issues_show_description_bottom, :issue => @issue) %>
<% if !@issue.leaf? || User.current.allowed_to?(:manage_subtasks, @project) %> <%= render :partial => 'tree_simple' %>
<hr />
<div id="issue_tree">
<div class="contextual">
<%= link_to(l(:button_add), {:controller => 'issues', :action => 'new', :project_id => @project, :issue => {:parent_issue_id => @issue}}) if User.current.allowed_to?(:manage_subtasks, @project) %>
</div>
<p><strong><%=l(:label_subtask_plural)%></strong></p>
<%= render_descendants_tree(@issue) unless @issue.leaf? %>
</div>
<% end %>
<% if authorize_for('issue_relations', 'new') || @issue.relations.present? %> <% if authorize_for('issue_relations', 'new') || @issue.relations.present? %>
<hr /> <hr />
@ -80,6 +87,7 @@
</div> </div>
<% end %> <% end %>
<hr />
</div> </div>
<% if @changesets.present? %> <% if @changesets.present? %>
@ -91,14 +99,19 @@
<% if @journals.present? %> <% if @journals.present? %>
<div id="history"> <div id="history">
<h3><%=l(:label_history)%></h3> <h3 class="rounded-background"><%=l(:label_history)%></h3>
<%= render :partial => 'history', :locals => { :issue => @issue, :journals => @journals } %> <%= render :partial => 'history', :locals => { :issue => @issue, :journals => @journals } %>
</div> </div>
<% end %> <% end %>
<div style="clear: both;"></div> <div style="clear: both;"></div>
<%= render :partial => 'action_menu' %>
<div class="title-bar" id="lower-title-bar">
<div class="title-bar-actions">
<%= render :partial => 'action_menu', :locals => {:replace_watcher => 'watcher2' } %>
</div>
</div>
<div style="clear: both;"></div> <div style="clear: both;"></div>
<% if authorize_for('issues', 'edit') %> <% if authorize_for('issues', 'edit') %>
@ -129,9 +142,8 @@
<% content_for :header_tags do %> <% content_for :header_tags do %>
<%= auto_discovery_link_tag(:atom, {:format => 'atom', :key => User.current.rss_key}, :title => "#{@issue.project} - #{@issue.tracker} ##{@issue.id}: #{@issue.subject}") %> <%= auto_discovery_link_tag(:atom, {:format => 'atom', :key => User.current.rss_key}, :title => "#{@issue.project} - #{@issue.tracker} ##{@issue.id}: #{@issue.subject}") %>
<%= stylesheet_link_tag 'scm' %> <%= stylesheet_link_tag 'scm' %>
<%= javascript_include_tag 'context_menu' %> <%= javascript_include_tag 'context_menu.jquery' %>
<%= stylesheet_link_tag 'context_menu' %> <%= stylesheet_link_tag 'context_menu' %>
<%= stylesheet_link_tag 'context_menu_rtl' if l(:direction) == 'rtl' %> <%= stylesheet_link_tag 'context_menu_rtl' if l(:direction) == 'rtl' %>
<% end %> <% end %>
<div id="context-menu" style="display: none;"></div> <%= javascript_tag "jQuery(document).ContextMenu('#{issues_context_menu_path}')" %>
<%= javascript_tag "new ContextMenu('#{issues_context_menu_path}')" %>

View File

@ -0,0 +1,9 @@
<p><%= authoring @journal.created_at, @journal.user, :label => :label_updated_time_by %></p>
<div class="text-diff">
<%= simple_format_without_paragraph @diff.to_html %>
</div>
<p><%= link_to(l(:button_back), issue_path(@issue)) %></p>
<% html_title "#{@issue.tracker.name} ##{@issue.id}: #{@issue.subject}" %>

Some files were not shown because too many files have changed in this diff Show More