Compare commits
482 Commits
Author | SHA1 | Date |
---|---|---|
Felix Schäfer | dfb77852cf | |
Holger Just | 93de0ba668 | |
Holger Just | fdc9c245ac | |
Holger Just | d7b23acea0 | |
Holger Just | f9babbbbc3 | |
Toshi MARUYAMA | 90a05668a0 | |
Holger Just | e4b814e167 | |
Felix Schäfer | 304155122d | |
Felix Schäfer | 1e2a029099 | |
Holger Just | 68e6d171b1 | |
Gregor Schmidt | d6e5c9e1fa | |
Holger Just | 63976e6522 | |
Holger Just | 31ad2ef6c3 | |
Holger Just | 595e60fb9c | |
Holger Just | 8b357a118d | |
Holger Just | 6d3dc6e619 | |
Holger Just | 0b567641bc | |
Holger Just | fd306095c6 | |
Holger Just | 430b6bb442 | |
Felix Schäfer | 3f9007b909 | |
Felix Schäfer | 6bef9c26ab | |
Felix Schäfer | 6744e23ed2 | |
Felix Schäfer | 065542c7d1 | |
Felix Schäfer | dc66e8f831 | |
Felix Schäfer | 6e30b3d3fc | |
Holger Just | 2854524ba9 | |
Holger Just | eb41df17d2 | |
Holger Just | 7f7a06706f | |
Holger Just | db3087b318 | |
Holger Just | d8536ced55 | |
Holger Just | 908391d54e | |
Holger Just | 93b2a1daf9 | |
Holger Just | c69a005353 | |
Holger Just | bf4ed9b37b | |
Holger Just | 90a94d75c3 | |
Holger Just | 2243c8dfd0 | |
Holger Just | cee98d306b | |
Holger Just | 0dac4ecb53 | |
Holger Just | c65779bbc1 | |
Holger Just | 0261b16b3e | |
Holger Just | c80591fe57 | |
Felix Schäfer | cdad6f752a | |
Felix Schäfer | 335da86b55 | |
Holger Just | 8e417fd5c4 | |
Holger Just | 07e54eda9e | |
Holger Just | a93a3af895 | |
Holger Just | 5156fbbfc4 | |
Felix Schäfer | 4d4efa482e | |
Holger Just | c734c6506d | |
Toshi MARUYAMA | e7ef06a926 | |
Felix Schäfer | 0dfa793abc | |
C-Moreira | ce5524ba4d | |
Holger Just | 003fc93b15 | |
Holger Just | da4641442f | |
Web Siduction | 0e1a622a6a | |
Holger Just | d63d2d2e81 | |
Holger Just | 7a4b664577 | |
Felix Schäfer | d24d4ce6b6 | |
jplang | a2f8557f23 | |
Holger Just | 080c5b63ca | |
Holger Just | 22204d588d | |
Holger Just | 19dd488298 | |
Holger Just | 12cedb1e91 | |
Holger Just | 27f24a78db | |
Andrew Smith | 4838c4ede8 | |
Holger Just | f2e4c71b27 | |
Andrew Smith | ff94716791 | |
Felix Schäfer | cb9ac281d9 | |
Felix Schäfer | a642a1fdc4 | |
Holger Just | f89c003847 | |
Andrew Smith | d6eb87508c | |
Romano Licker | 1e96ac9e06 | |
Holger Just | 9fde11f950 | |
Felix Schäfer | d41c7f1e8c | |
Andrew Smith | e3dc444b9c | |
Felix Schäfer | 37385642fa | |
Felix Schäfer | 12b2d3c182 | |
Andrew Smith | 37c762b997 | |
Holger Just | 17793c87e3 | |
Holger Just | eea550e639 | |
Holger Just | 760df0ae35 | |
Andrew Smith | a4fbb15f6c | |
Andrew Smith | 6fa46e5136 | |
Andrew Smith | cf83e274d7 | |
Jan Vlnas | 29af3ec964 | |
Holger Just | 6932070752 | |
Holger Just | cd4efd2e0d | |
Felix Schäfer | 2f21522458 | |
Holger Just | d3d6a93a45 | |
Holger Just | e4386f61da | |
Holger Just | 59e2a2fdde | |
Holger Just | 5c7a3a53c2 | |
Holger Just | 4d9060964f | |
Holger Just | 16e266e7e5 | |
Holger Just | d629209364 | |
Holger Just | 221a2e73ce | |
Holger Just | c9d141061d | |
Holger Just | 8e41daf92a | |
Felix Schäfer | caceb58947 | |
Felix Schäfer | 5ef63ec4bb | |
Holger Just | e6e057d10d | |
Felix Schäfer | f8ec89b999 | |
Holger Just | bd132c5607 | |
Holger Just | bcb02a4634 | |
Holger Just | d11e074748 | |
Felix Schäfer | 1f91054244 | |
Felix Schäfer | 32e1cc2dee | |
Felix Schäfer | 3997220a45 | |
Felix Schäfer | 69656422a1 | |
Felix Schäfer | d6ad07ee97 | |
Justin Geibel | 1722e96bb0 | |
Andrew Smith | 4956e9ca93 | |
Andrew Smith | 7c1b17509f | |
Andrew Smith | c8be011a93 | |
Holger Just | 8ab42473d9 | |
Felix Schäfer | a43c06ff77 | |
Andrew Smith | f983b451ad | |
Andrew Smith | 61c00779d0 | |
Dereckson | 375045a82b | |
Felix Schäfer | 2e18840f12 | |
Felix Schäfer | 20448c7efb | |
Andrew Smith | 9cd14ca152 | |
Felix Schäfer | 87f68e58ad | |
Andrew Smith | 43723385c6 | |
Felix Schäfer | 93abdcf487 | |
Felix Schäfer | 1d3cdf1fa9 | |
Felix Schäfer | 3e84c4817b | |
Felix Schäfer | 7930e8fa66 | |
Felix Schäfer | d4f0542e74 | |
tmaruyama | 6a26543887 | |
Felix Schäfer | 03dfae0529 | |
Eric Davis | 75bb38df08 | |
Jean-Philippe Lang | 16947dc016 | |
unknown | e7d96825b2 | |
Holger Just | 2bcd8787d3 | |
jwalkerbg | 6a4559e6af | |
Gabriel Mazetto | 859a9cb9d2 | |
Holger Just | 33260d885d | |
Holger Just | 277815ec96 | |
Holger Just | 01c386b3c6 | |
Holger Just | a6071c75ec | |
Spenser Jones | e6c79ddef4 | |
Holger Just | c3fc106355 | |
Holger Just | b169f02377 | |
Holger Just | 1f4baaa27b | |
Felix Schäfer | 8f06b77ccb | |
Holger Just | b8a29c288b | |
Holger Just | aca166271b | |
Holger Just | d61ad01308 | |
Holger Just | f681ed8c13 | |
Holger Just | eb62f4f7ec | |
Holger Just | 4aed677908 | |
Holger Just | 6f055664f1 | |
Holger Just | bb4340eb6d | |
Jean-Philippe Lang | 3183aa55ed | |
Jean-Philippe Lang | 275163ead2 | |
Jean-Philippe Lang | fc5dfd5813 | |
Jean-Philippe Lang | a3f6b30e99 | |
Jean-Philippe Lang | 5de377c5ee | |
Jean-Philippe Lang | 305df19ab7 | |
Jean-Philippe Lang | c3ca5813d5 | |
Jean-Philippe Lang | 384890c5ad | |
Jean-Philippe Lang | e77cb6133d | |
Jean-Philippe Lang | 7505cb2ff0 | |
Jean-Philippe Lang | 2eeb4b13a6 | |
Felix Schäfer | 0a7c6e6774 | |
Felix Schäfer | 6f064d3856 | |
Robert Mitwicki | c6af5c7982 | |
Jean-Philippe Lang | 20a79124f7 | |
Felix Schäfer | e6ec8ab30c | |
Felix Schäfer | dafe09ee32 | |
Andrew Smith | 7b3280e5ad | |
Andrew Smith | fd250726ce | |
Andrew Smith | 1cfd20e7e7 | |
Andrew Smith | b15e6d8305 | |
Andrew Smith | 2a70e2704f | |
Andrew Smith | 23a857bf75 | |
Andrew Smith | a510f0a85f | |
Andrew Smith | 2b640f76ec | |
Andrew Smith | 1b7ddb3cd7 | |
Andrew Smith | 27160b76f5 | |
Gregor Schmidt | c9a403a4b5 | |
Felix Schäfer | 565aeabc79 | |
Holger Just | 68efc3de32 | |
Felix Schäfer | efac256a6b | |
Felix Schäfer | f01194856f | |
Felix Schäfer | df66e9b915 | |
Felix Schäfer | 20eed68421 | |
Felix Schäfer | 26c847a4ca | |
Felix Schäfer | 1bc182405e | |
Michaël Rigart | 23864d99b5 | |
Felix Schäfer | ef5dddf4db | |
Felix Schäfer | 2c76240544 | |
Holger Just | 724bd48494 | |
Felix Schäfer | 900eda7a23 | |
Felix Schäfer | 4b5271f487 | |
Felix Schäfer | 4a3f10317f | |
Eric Davis | b9b2c8c0d7 | |
Felix Schäfer | fbc71a80f8 | |
Felix Schäfer | f327298f88 | |
Michaël Rigart | e678612d75 | |
Michaël Rigart | cbcce70400 | |
Felix Schäfer | fda1dfa96d | |
Dominique Feyer | f561c54356 | |
Holger Just | 11e93ff36a | |
Holger Just | 3ecace901b | |
Holger Just | 2e7050ef79 | |
Holger Just | e0eb21b48b | |
Holger Just | 1eefd4b40d | |
Holger Just | 0fd499afca | |
Holger Just | aa3ab990d0 | |
Holger Just | 3f99ee63ff | |
Holger Just | be4d679d54 | |
Holger Just | 3f126bdeaf | |
Holger Just | 8e85cbdc11 | |
Holger Just | 21f70fc86c | |
Holger Just | f4c87b92e2 | |
Holger Just | b692d4a671 | |
Holger Just | 185edcd283 | |
Holger Just | 19f2ccd496 | |
Holger Just | 91f6e79f4d | |
Holger Just | e85947c7d4 | |
Holger Just | a0a2776f95 | |
Holger Just | 3c9e9764b0 | |
Holger Just | 3f325243ce | |
Holger Just | c313ed2d25 | |
Holger Just | e1ecae83b3 | |
Holger Just | 323b5bebc8 | |
Holger Just | 91c04f335d | |
Holger Just | 31620d0c0a | |
Holger Just | 9967b5cdf2 | |
Holger Just | e91a1e010f | |
Holger Just | 637ca24aed | |
Holger Just | a938d582b1 | |
Holger Just | 33a8baf347 | |
Holger Just | d7ebffb7ad | |
Holger Just | 61a65d4624 | |
Holger Just | d9a0ac37eb | |
Holger Just | dbbc0b4919 | |
Eric Davis | 0407abbd56 | |
Eric Davis | 4ce3b88473 | |
Holger Just | b080ad14ef | |
Holger Just | f1324e6af4 | |
Holger Just | 446f943968 | |
Holger Just | 3c3eb2f7e7 | |
Holger Just | dc5fd8bc10 | |
Holger Just | 0c87f611e9 | |
Eric Davis | b8a7f2923a | |
Eric Davis | e4554a6d7b | |
Eric Davis | 00df832126 | |
Eric Davis | 66fe8287ce | |
Eric Davis | a3b1127ef8 | |
Eric Davis | c51d339512 | |
Eric Davis | af00598e5e | |
Eric Davis | f835420383 | |
Eric Davis | 38d0d530b0 | |
Eric Davis | 4acee9e989 | |
Eric Davis | aafec2c50f | |
Eric Davis | 08454ab7fa | |
Eric Davis | 5ad97a4ea3 | |
Eric Davis | e045306a5c | |
Eric Davis | 8100ce0ce5 | |
Eric Davis | 84cc8ab215 | |
Eric Davis | ef00061568 | |
Eric Davis | 15428fc092 | |
Eric Davis | a8d28e4593 | |
Eric Davis | ce0c32ea02 | |
Eric Davis | 97fe88f8d9 | |
Eric Davis | 3df729e47d | |
Eric Davis | 7bce7f7b07 | |
Eric Davis | 04db42f537 | |
Eric Davis | 16943d04f7 | |
Eric Davis | 8e3d4da376 | |
Eric Davis | 65e7995682 | |
Eric Davis | e6e6a06fff | |
Eric Davis | 10054cfd8f | |
Eric Davis | 8b3207a893 | |
Eric Davis | 037d915d7b | |
Eric Davis | c3555b1728 | |
Eric Davis | 8160cd02bd | |
Eric Davis | 5deae7ebe3 | |
Eric Davis | a110f1041d | |
Eric Davis | 111c7f47f7 | |
Eric Davis | df5b2198e9 | |
Eric Davis | 3af5544dbc | |
Eric Davis | 61a21f4990 | |
Eric Davis | 72eadcc6ea | |
Eric Davis | dc541597ec | |
Eric Davis | 1dd07471ca | |
Eric Davis | 4ff670f5fa | |
Eric Davis | 9f4683d71b | |
Eric Davis | 331ecb4c4f | |
Eric Davis | 7c7aca4f0c | |
Eric Davis | d53c4e9aae | |
Eric Davis | 9d8fc86b8e | |
Eric Davis | 260e8b84f8 | |
Eric Davis | 21685caf5f | |
Gregor Schmidt | 69ccbafaf4 | |
Gregor Schmidt | 10e16e9a56 | |
Gregor Schmidt | 6273891ae7 | |
Gregor Schmidt | 92b6d43150 | |
Holger Just | 6f17ec6fd0 | |
Holger Just | d5f4bec112 | |
Holger Just | 745f889e92 | |
Holger Just | ceac1bb419 | |
Holger Just | e1ac469cfb | |
Holger Just | d24e8186b4 | |
Holger Just | 81f6635878 | |
Holger Just | ba6fcfeca9 | |
Holger Just | 2733e5173f | |
Holger Just | 6b725c32e6 | |
Holger Just | 0049f82182 | |
Holger Just | 58a7182e2d | |
Eric Davis | 2f0d13149d | |
Eric Davis | d583c94b5f | |
Eric Davis | 3a16f3f5b1 | |
Holger Just | 04eb115da7 | |
Holger Just | 34fef9d8fc | |
Holger Just | e2c57fd12d | |
Holger Just | 7458bca34b | |
Holger Just | 60deeb5306 | |
Holger Just | 4656cf1c57 | |
Eric Davis | d2ccdc88fa | |
Eric Davis | d32480753b | |
Eric Davis | 9f0bd25523 | |
Eric Davis | 100faf94f7 | |
Eric Davis | f6d0932bff | |
Eric Davis | 2a74699196 | |
Eric Davis | ee48a8da4f | |
Eric Davis | 52ab42b5b0 | |
Eric Davis | e8b8841717 | |
Eric Davis | f9a2e30b9f | |
Eric Davis | f76c922dd8 | |
Eric Davis | 8e6ccceb28 | |
Eric Davis | a73c23ae9b | |
Eric Davis | 0385979d5e | |
Eric Davis | 612f2f98e4 | |
Eric Davis | b312bf387a | |
Eric Davis | 971277b507 | |
Eric Davis | c33227ffe3 | |
Eric Davis | 32fa8cb522 | |
Romano Licker | 83f8f63640 | |
jwollert | a1500a49b5 | |
Romano Licker | d22afea284 | |
Romano Licker | e371bcad24 | |
Romano Licker | 0eeeb04c31 | |
Romano Licker | 729e801c9f | |
jwollert | 7e287d6e5a | |
Eric Davis | 7d1c0374e4 | |
Felix Schäfer | fbde6859d1 | |
Felix Schäfer | 1c84fc0f4d | |
Felix Schäfer | 8671006b70 | |
Felix Schäfer | aa88fe8e39 | |
Eric Davis | b32fc97314 | |
Eric Davis | a2feb14c83 | |
Eric Davis | 4b5d4307d2 | |
Eric Davis | f179fea7bf | |
Felix Schäfer | 1d21cab2e4 | |
Holger Just | 5bf27de92a | |
Holger Just | 0b952a1edd | |
Holger Just | 9f4d12ffdf | |
Holger Just | 28ce07b1dd | |
Eric Davis | 01e43f2e6b | |
Gregor Schmidt | 3577550cfa | |
Eric Davis | 8fc2b72740 | |
Gregor Schmidt | 13da5e0445 | |
Eric Davis | e6fe1fc776 | |
Eric Davis | b1d9667335 | |
Holger Just | a31a4a8852 | |
Eric Davis | dedf696666 | |
Holger Just | 464dafc1e7 | |
Holger Just | f6805303fb | |
Holger Just | 994132a51a | |
Holger Just | fe6a79ac17 | |
Holger Just | cc0526cb27 | |
Holger Just | 862c9e0fde | |
Eric Davis | 71ecdb57ed | |
Eric Davis | 1e7dfe545e | |
Eric Davis | 7778ff2f83 | |
Holger Just | 29fe856916 | |
Eric Davis | 81ca15c52c | |
Holger Just | 82432f3f99 | |
Eric Davis | 72fa3ff920 | |
Eric Davis | 981143f78f | |
Eric Davis | 7906afe6b9 | |
Eric Davis | 6f8d8c4105 | |
Eric Davis | 91914cb877 | |
Holger Just | 7261622196 | |
Eric Davis | 4289559b5f | |
Eric Davis | 1e41ec912b | |
Eric Davis | af9572d985 | |
Eric Davis | 5f3c6b87e4 | |
Felix Schäfer | b1671e46f0 | |
jwollert | edce16c89a | |
Romano Licker | 977f74e11a | |
Romano Licker | c16cfd8ff1 | |
Romano Licker | 734da91b4b | |
jwollert | fecfbaf908 | |
Eric Davis | 1897459b76 | |
Eric Davis | 3e87af38d4 | |
Eric Davis | 3409f5b620 | |
Eric Davis | ab2856b53f | |
Eric Davis | 7bc11f8de0 | |
Eric Davis | f10412ef71 | |
Eric Davis | a911d83889 | |
Eric Davis | 5620488727 | |
Eric Davis | b160217299 | |
Holger Just | 9c8380667c | |
Kornelius Kalnbach | aa8237c546 | |
Kornelius Kalnbach | 260a6bb1da | |
Kornelius Kalnbach | 7b1a263d1d | |
Kornelius Kalnbach | 65af1e4436 | |
Eric Davis | a2514ecc54 | |
Eric Davis | f31f44f4af | |
Eric Davis | cce496b011 | |
Eric Davis | e51f067f12 | |
Eric Davis | 8d10daa68f | |
Eric Davis | b968d260fd | |
Eric Davis | 34ffbc8e46 | |
Eric Davis | cbb99f81a1 | |
Eric Davis | 923cdf3ff3 | |
Eric Davis | a592f024cb | |
Eric Davis | dc682eaddc | |
Eric Davis | aed2b4f661 | |
Eric Davis | d99bc8a066 | |
Eric Davis | 5e2d633ebb | |
Eric Davis | ca3eeedfff | |
Eric Davis | 155e1ba2a7 | |
Eric Davis | 2b7a221dee | |
Eric Davis | 061beb4967 | |
Gregor Schmidt | c1ecadbff7 | |
Holger Just | c87738daf2 | |
Holger Just | 2a3e12927f | |
Eric Davis | 27fd103132 | |
Eric Davis | a9d6518a5d | |
Eric Davis | d8023e6949 | |
Eric Davis | 667f85f9a5 | |
Eric Davis | 7f06e7394c | |
Eric Davis | d94f7f7f9b | |
Eric Davis | 949d229629 | |
Eric Davis | ccad72ba85 | |
Eric Davis | 4b8f4c3b83 | |
Eric Davis | d63cb35e82 | |
Eric Davis | cfed74579d | |
Eric Davis | 9bb8f80d7e | |
Eric Davis | b4804f4ce4 | |
Eric Davis | adffd044d7 | |
Eric Davis | ffa2d5f319 | |
Eric Davis | 156cdf316e | |
Eric Davis | aed0a6d5c3 | |
Eric Davis | 635923f90d | |
Eric Davis | 8f45e780ce | |
Eric Davis | 124c98d4ba | |
Eric Davis | 8c183dda2e | |
Eric Davis | e7e4557009 | |
Eric Davis | 5cedd5cdcb | |
Eric Davis | cf40447356 | |
Eric Davis | 61847d831a | |
Eric Davis | 6c9c85134a | |
Eric Davis | 6435905132 | |
Eric Davis | ce973429fe | |
Eric Davis | cff91d8cf0 | |
Eric Davis | 13f57032c1 | |
Eric Davis | 7485858da2 | |
Eric Davis | 2dc0e100c3 | |
Eric Davis | 5949f8de91 | |
Eric Davis | ec21b4c075 | |
Eric Davis | 84ef979c47 | |
Eric Davis | 6d522a9acd | |
Eric Davis | 48675d63a5 | |
Eric Davis | bfa57e5e6c | |
Eric Davis | ac48b91864 | |
Eric Davis | d3392c6d0d | |
Eric Davis | a0c777ee79 | |
Eric Davis | c23f73e9fe | |
Eric Davis | 484ed29fa2 | |
Eric Davis | 1f57eb01b1 | |
Eric Davis | 74cfd7b5ca | |
Eric Davis | ddeb1a2a0f | |
Eric Davis | 0f35c7d1c2 | |
Eric Davis | 3a0a7d93b1 | |
Eric Davis | f0987c8238 |
|
@ -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
|
||||||
|
|
|
@ -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"
|
|
@ -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
27
Gemfile
|
@ -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
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
|
2
Rakefile
2
Rakefile
|
@ -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 }
|
||||||
|
|
|
@ -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?
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
|
@ -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 }
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -45,19 +45,32 @@ 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?
|
||||||
attachments = Attachment.attach_files(@document, params[:attachments])
|
if User.current.allowed_to?(:add_document_watchers, @project) && params[:document]['watcher_user_ids'].present?
|
||||||
render_attachment_warning_if_needed(@document)
|
@document.watcher_user_ids = params[:document]['watcher_user_ids']
|
||||||
flash[:notice] = l(:notice_successful_create)
|
end
|
||||||
redirect_to :action => 'index', :project_id => @project
|
|
||||||
|
if @document.save
|
||||||
|
attachments = Attachment.attach_files(@document, params[:attachments])
|
||||||
|
render_attachment_warning_if_needed(@document)
|
||||||
|
flash[:notice] = l(:notice_successful_create)
|
||||||
|
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])
|
|
||||||
flash[:notice] = l(:notice_successful_update)
|
if request.post?
|
||||||
redirect_to :action => 'show', :id => @document
|
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)
|
||||||
|
redirect_to :action => 'show', :id => @document
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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])
|
||||||
@membership = Member.edit_membership(params[:membership_id], params[:membership], @group)
|
|
||||||
@membership.save if request.post?
|
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.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' }
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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');"
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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},
|
||||||
|
|
|
@ -197,8 +197,16 @@ class UsersController < ApplicationController
|
||||||
|
|
||||||
|
|
||||||
def edit_membership
|
def edit_membership
|
||||||
@membership = Member.edit_membership(params[:membership_id], params[:membership], @user)
|
if params[:project_ids] # Multiple memberships, one per project
|
||||||
@membership.save if request.post?
|
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.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' }
|
||||||
|
|
|
@ -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.watchable = @watched
|
@watcher = Watcher.new((params[:watcher] || {}).merge({:user_id => user_id}))
|
||||||
@watcher.save if request.post?
|
@watcher.watchable = @watched
|
||||||
|
@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
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
select_tag "", option_tags, html_options.merge({ :onchange => "if (this.value != \'\') { window.location = this.value; }" })
|
||||||
s << '</select>'
|
|
||||||
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
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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 = []
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
content_tag("div", journal_content, :id => "change-#{journal.id}", :class => journal_classes)
|
||||||
end
|
end
|
||||||
|
|
||||||
# This renders a journal entry wiht a header and details
|
# This renders a journal entry with a header and details
|
||||||
def render_journal_details(journal, header_label = :label_updated_time_by)
|
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={})
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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])
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,11 +540,15 @@ class Query < ActiveRecord::Base
|
||||||
sql = ''
|
sql = ''
|
||||||
case operator
|
case operator
|
||||||
when "="
|
when "="
|
||||||
if value.present?
|
if [:date, :date_past].include?(type_for(field))
|
||||||
sql = "#{db_table}.#{db_field} IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")"
|
sql = date_clause(db_table, db_field, (Date.parse(value.first) rescue nil), (Date.parse(value.first) rescue nil))
|
||||||
else
|
else
|
||||||
# empty set of allowed values produces no result
|
if value.any?
|
||||||
sql = "0=1"
|
sql = "#{db_table}.#{db_field} IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")"
|
||||||
|
else
|
||||||
|
# IN an empty set
|
||||||
|
sql = "0=1"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
when "!"
|
when "!"
|
||||||
if value.present?
|
if value.present?
|
||||||
|
@ -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
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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>
|
|
@ -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>
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -1,5 +1 @@
|
||||||
<div id="admin-menu">
|
<%= render_menu :admin_menu %>
|
||||||
<ul>
|
|
||||||
<%= render_menu :admin_menu %>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
|
@ -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' %>
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
<%= projects_check_box_tags 'project_ids[]', @projects %>
|
|
@ -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 %>
|
||||||
|
|
|
@ -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>
|
||||||
</li>
|
<div class="submenu"></div>
|
||||||
|
</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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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 %>
|
||||||
|
|
|
@ -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('« ' + l(:label_previous), params.merge(@gantt.params_previous)) %></td>
|
<td align="left"><%= link_to_content_update('« ' + l(:label_previous), params.merge(@gantt.params_previous)) %></td>
|
||||||
<td align="right"><%= link_to_content_update(l(:label_next) + ' »', params.merge(@gantt.params_next)) %></td>
|
<td align="right"><%= link_to_content_update(l(:label_next) + ' »', params.merge(@gantt.params_next)) %></td>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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')
|
||||||
%>
|
%>
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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| %>
|
||||||
|
|
|
@ -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%">
|
||||||
|
|
|
@ -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) %>
|
||||||
|
|
|
@ -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 %>
|
|
@ -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>
|
||||||
|
|
|
@ -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? %>
|
||||||
|
|
|
@ -1,77 +1,84 @@
|
||||||
<%= 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>
|
||||||
<p class="author">
|
<hr />
|
||||||
<%= authoring @issue.created_on, @issue.author %>.
|
|
||||||
<% if @issue.created_on != @issue.updated_on %>
|
<p class="author">
|
||||||
<%= l(:label_updated_time, time_tag(@issue.updated_on)) %>.
|
<%= avatar(@issue.author, :size => "14") %>
|
||||||
|
<%= authoring @issue.created_on, @issue.author %>.
|
||||||
|
<% if @issue.created_on != @issue.updated_on %>
|
||||||
|
<%= l(:label_updated_time, time_tag(@issue.updated_on)) %>.
|
||||||
|
<% end %>
|
||||||
|
</p>
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<div class="meta">
|
||||||
|
<table class="attributes">
|
||||||
|
<tr>
|
||||||
|
<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>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<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>
|
||||||
|
</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="progress"><%=l(:field_done_ratio)%>:</th><td class="progress"><%= progress_bar @issue.done_ratio, :width => '80px', :legend => "#{@issue.done_ratio}%" %></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<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) %>
|
||||||
|
<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>
|
||||||
|
<% else %>
|
||||||
|
<th></th><td></td>
|
||||||
<% end %>
|
<% end %>
|
||||||
</p>
|
</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>
|
||||||
|
<% if @issue.estimated_hours %>
|
||||||
|
<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 %>
|
||||||
|
</tr>
|
||||||
|
<%= render_custom_fields_rows(@issue) %>
|
||||||
|
<%= call_hook(:view_issues_show_details_bottom, :issue => @issue) %>
|
||||||
|
</table>
|
||||||
|
</div><!-- .meta -->
|
||||||
|
|
||||||
<table class="attributes">
|
|
||||||
<tr>
|
|
||||||
<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>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<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>
|
|
||||||
</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="progress"><%=l(:field_done_ratio)%>:</th><td class="progress"><%= progress_bar @issue.done_ratio, :width => '80px', :legend => "#{@issue.done_ratio}%" %></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<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) %>
|
|
||||||
<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>
|
|
||||||
<% end %>
|
|
||||||
</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>
|
|
||||||
<% if @issue.estimated_hours %>
|
|
||||||
<th class="estimated-hours"><%=l(:field_estimated_hours)%>:</th><td class="estimated-hours"><%= l_hours(@issue.estimated_hours) %></td>
|
|
||||||
<% end %>
|
|
||||||
</tr>
|
|
||||||
<%= render_custom_fields_rows(@issue) %>
|
|
||||||
<%= call_hook(:view_issues_show_details_bottom, :issue => @issue) %>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<% if @issue.description? || @issue.attachments.any? -%>
|
|
||||||
<hr />
|
|
||||||
<% if @issue.description? %>
|
<% if @issue.description? %>
|
||||||
<div class="contextual">
|
<hr />
|
||||||
<%= link_to_remote_if_authorized(l(:button_quote), { :url => {:controller => 'journals', :action => 'new', :id => @issue} }, :class => 'icon icon-comment') %>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p><strong><%=l(:field_description)%></strong></p>
|
<div class="description">
|
||||||
<div class="wiki">
|
<div class="contextual">
|
||||||
<%= textilizable @issue, :description, :attachments => @issue.attachments %>
|
<%= link_to_remote_if_authorized(l(:button_quote), { :url => {:controller => 'journals', :action => 'new', :id => @issue} }, :class => 'icon icon-comment') %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<p><strong><%=l(:field_description)%></strong></p>
|
||||||
|
<div class="wiki">
|
||||||
|
<%= textilizable @issue, :description, :attachments => @issue.attachments %>
|
||||||
|
</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}')" %>
|
|
||||||
|
|
|
@ -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
Loading…
Reference in New Issue