Compare commits
362 Commits
feature-is
...
develop
Author | SHA1 | Date | |
---|---|---|---|
|
3cc14ef6a0 | ||
|
02613203c9 | ||
|
6f4af6b68d | ||
|
ebb8d46420 | ||
|
6c5406dd99 | ||
|
4aa4100532 | ||
|
d90c1a2c23 | ||
|
22c1f92b2b | ||
|
d17098e703 | ||
|
64c6674762 | ||
|
01febe1411 | ||
|
839444cc8a | ||
|
831439120c | ||
|
6bb886b92a | ||
|
dd3474c06c | ||
|
bcd515eed5 | ||
|
231dfaa8a6 | ||
|
0826dab575 | ||
|
1c037f81a0 | ||
|
71c41fe940 | ||
|
8ad0707509 | ||
|
e0dec2278c | ||
|
7221135849 | ||
|
a2d5b14a2c | ||
|
16de6a0d77 | ||
|
03404adf64 | ||
|
dd2fa6f902 | ||
|
b6acf4c0e2 | ||
|
cf1a7e8852 | ||
|
c0035061b0 | ||
|
6c130cd3ca | ||
|
8db3c45763 | ||
|
696f2d06d6 | ||
|
158f9288d3 | ||
|
e883f22790 | ||
|
2b5bf490a5 | ||
|
014d8ca6af | ||
|
02a99e2f9c | ||
|
23253fc40e | ||
|
d348c45c56 | ||
|
53f6133899 | ||
|
c3bbddb1ff | ||
|
a608d7ea0a | ||
|
fde3a0a949 | ||
|
2bd2b3a463 | ||
|
7207cdcd46 | ||
|
85f247909e | ||
|
e8882292b5 | ||
|
bca3eb332e | ||
|
307c35ff42 | ||
|
786e9f81c0 | ||
|
8cd19c0f53 | ||
|
d5394265ba | ||
|
0919cb83d8 | ||
|
b356625268 | ||
|
4e8ca58a2e | ||
|
6996e1185f | ||
|
b7ad4bf47a | ||
|
75f62663a9 | ||
|
169fbe6216 | ||
|
84d80af1ce | ||
|
d1542c9c00 | ||
|
4da01b2732 | ||
|
b44ad48fd5 | ||
|
3ae92627bc | ||
|
d46df5c129 | ||
|
316c57f85b | ||
|
f11a7f7618 | ||
|
bb7544021f | ||
|
11b3811e17 | ||
|
3e78376be3 | ||
|
cf2f9d419e | ||
|
a9630cea0d | ||
|
4323edcf0c | ||
|
120101c4df | ||
|
b9c3ee1a09 | ||
|
60cdd2224f | ||
|
e7643e97e4 | ||
|
10e5f350f7 | ||
|
1690fa8416 | ||
|
9168a26a72 | ||
|
21a0a3ffb5 | ||
|
27869698e6 | ||
|
2da7d4db42 | ||
|
bc64c877de | ||
|
da7b76e00b | ||
|
3bf4977460 | ||
|
315e06da1b | ||
|
74d06b0c31 | ||
|
d924c4e277 | ||
|
3a71d71ef8 | ||
|
1d15d53eaa | ||
|
1b363e4b6d | ||
|
c158131f88 | ||
|
9f578829ea | ||
|
f7d75f1b94 | ||
|
fa95cbd934 | ||
|
f542a7fef8 | ||
|
a07d2be837 | ||
|
b529c05d11 | ||
|
f8a830802e | ||
|
47da6adebb | ||
|
69949b2941 | ||
|
d676727c1e | ||
|
e0da21cbf8 | ||
|
214d035d12 | ||
|
f005bc1ddd | ||
|
9beffaae7e | ||
|
be2f0b3d62 | ||
|
b72e0f71e5 | ||
|
9137cab212 | ||
|
f4a6cd1cdb | ||
|
0e52a2e673 | ||
|
c2d61083a7 | ||
|
f8a1d26b28 | ||
|
f697d97b6a | ||
|
9bd5912884 | ||
|
562e6803c5 | ||
|
850be91695 | ||
|
516640b0cd | ||
|
126f94016f | ||
|
6bbabaebdd | ||
|
3bec47778a | ||
|
215294fcf3 | ||
|
d32e17df91 | ||
|
51ca50ec36 | ||
|
9e45b7f7f5 | ||
|
5bcd4e1855 | ||
|
fb88c08e85 | ||
|
eac353f921 | ||
|
91dcc78796 | ||
|
5c04c87ff6 | ||
|
6a3f1aed99 | ||
|
f4058ddd69 | ||
|
17c6ba97d6 | ||
|
65aa830c87 | ||
|
ff77046783 | ||
|
6e305eb541 | ||
|
608e7a40e4 | ||
|
c0506c460d | ||
|
da6df31ee9 | ||
|
6875e62942 | ||
|
c07aee6287 | ||
|
16573daee0 | ||
|
5ffe66cbfb | ||
|
0f4c952cbd | ||
|
52d638e027 | ||
|
6a20b36700 | ||
|
a83634c166 | ||
|
4dd8994270 | ||
|
4d7d99c0bb | ||
|
95faf0468a | ||
|
1a67712447 | ||
|
14be872724 | ||
|
63bc47e7b7 | ||
|
0d410605f9 | ||
|
22dfab253b | ||
|
a1c8a49430 | ||
|
3f0c7c23d2 | ||
|
8fde1e4762 | ||
|
3897d7facb | ||
|
959ea74291 | ||
|
ac4d974abb | ||
|
dd8833665d | ||
|
d54d195b86 | ||
|
b752d1fadb | ||
|
57f314badb | ||
|
6a44af34e1 | ||
|
7342566c6e | ||
|
d6eb7532fd | ||
|
7267fada64 | ||
|
bfc568967b | ||
|
154597c09c | ||
|
8f886155b0 | ||
|
df1130b4c9 | ||
|
58ccb93f2d | ||
|
ff2b19d587 | ||
|
ba365af020 | ||
|
b2e25fc501 | ||
|
f29348c604 | ||
|
b39fe8595c | ||
|
5fefc26543 | ||
|
a37b222878 | ||
|
c9f2575469 | ||
|
5101ae5f35 | ||
|
b09a7d2fd2 | ||
|
aa09862059 | ||
|
f9629f3f7b | ||
|
884f50155c | ||
|
e6e20e4f93 | ||
|
82a2d6a39c | ||
|
2b5efb7fee | ||
|
099e4888e8 | ||
|
3eca572866 | ||
|
c39f5c2174 | ||
|
cf684c1514 | ||
|
6c9b6b5309 | ||
|
a7f283256a | ||
|
4506e1d2b5 | ||
|
70f67e6bc6 | ||
|
c71ed2cecb | ||
|
05816cb75a | ||
|
91b189b75f | ||
|
623c562054 | ||
|
2a55024640 | ||
|
8a9f8c66e8 | ||
|
81f433085a | ||
|
6e59a0a526 | ||
|
8397d86313 | ||
|
b0ac05b608 | ||
|
48c3989ae3 | ||
|
b949b7e83a | ||
|
ad7fad9fbe | ||
|
34fbf6ec5f | ||
|
d2db3b16d2 | ||
|
ae11b1de4a | ||
|
6f620e3f54 | ||
|
810b753edf | ||
|
958fb1b9ad | ||
|
48992adefa | ||
|
d95e1e13e5 | ||
|
381dc5b8f7 | ||
|
464c1a8ef5 | ||
|
e5b10a8494 | ||
|
ffc49b9ea6 | ||
|
0efc14dd6f | ||
|
4fb15ccb7d | ||
|
234b70845c | ||
|
6abd0b6faa | ||
|
fef2bd15bf | ||
|
74d07d8fb8 | ||
|
473e9153ed | ||
|
efa10c9afd | ||
|
7438a2bf19 | ||
|
2e0995abac | ||
|
c84afd0f78 | ||
|
52383edfd0 | ||
|
b1276dff6c | ||
|
b413b7ee89 | ||
|
f19f07ec59 | ||
|
83761c66c5 | ||
|
d0e2977746 | ||
|
1be91e5a2a | ||
|
15d4d1aa7d | ||
|
708b90fccd | ||
|
ac7a4c4aa5 | ||
|
b90246a239 | ||
|
a9d327d54f | ||
|
eb8fd0aa55 | ||
|
813184f06c | ||
|
ef2d3a9af9 | ||
|
9a8bd464a3 | ||
|
9e2ea7404b | ||
|
160d11b89b | ||
|
d860f299fd | ||
|
33882d4fa7 | ||
|
85978a4d18 | ||
|
e1e7696d53 | ||
|
695428075b | ||
|
e7c2e721b4 | ||
|
13fad756ab | ||
|
699925f797 | ||
|
920432025f | ||
|
7ff298af79 | ||
|
b29acd71cb | ||
|
dc50e9b316 | ||
|
7f610fd2f3 | ||
|
4ae0019e0f | ||
|
c72ce218f7 | ||
|
1f0791df0e | ||
|
34c9d04a35 | ||
|
aa2868eb17 | ||
|
a2c832a130 | ||
|
b17de014ec | ||
|
b1b72190e1 | ||
|
2ff2f888bc | ||
|
57c2389aae | ||
|
d54c86f813 | ||
|
05a9697007 | ||
|
945429abf0 | ||
|
a016bcb51b | ||
|
f2b1ce795c | ||
|
3a8c56acc4 | ||
|
7b2552f940 | ||
|
324b202215 | ||
|
2c2da6082a | ||
|
dd3fbbd7e4 | ||
|
9bbcd571ec | ||
|
bbc185bf3b | ||
|
d1bcdcda20 | ||
|
6d55602ef3 | ||
|
6e7c9f7c4b | ||
|
5427aab456 | ||
|
4879d64989 | ||
|
dab8ea63fc | ||
|
b03d7a04a0 | ||
|
ef5b93e3f7 | ||
|
69ae1c08ef | ||
|
8e4f828cc6 | ||
|
c4f92f4569 | ||
|
c4d2b99656 | ||
|
d4fe88adab | ||
|
69d0e8313a | ||
|
11a234e135 | ||
|
2f30e4e2f6 | ||
|
118ca9f11f | ||
|
24fc41ee0d | ||
|
c00dbac5e0 | ||
|
d7857c5126 | ||
|
ac6be0d3c0 | ||
|
7ff6f09f67 | ||
|
00b576c5a3 | ||
|
2a33510c96 | ||
|
d1f79d906d | ||
|
aa043f14ac | ||
|
638b28399e | ||
|
1d86f036a3 | ||
|
2f6e0f0a22 | ||
|
1b1b00a10c | ||
|
8693418d39 | ||
|
8ff15368ce | ||
|
32cde534bd | ||
|
c0e26133bd | ||
|
592c2ff9ff | ||
|
30efd0a2db | ||
|
20c3f14cc8 | ||
|
80313c88c8 | ||
|
cab1c09ffc | ||
|
c421919092 | ||
|
d350876bc1 | ||
|
de09c8af56 | ||
|
998f4576fe | ||
|
06c57f7da6 | ||
|
4d5418a601 | ||
|
95cc7f627f | ||
|
2aab4eea3b | ||
|
5bbff9f5a6 | ||
|
b5fcf1636a | ||
|
13012be5d7 | ||
|
ca2ef814fb | ||
|
b9407f6aee | ||
|
9bcb5f9456 | ||
|
f412099f69 | ||
|
0aa5999bb3 | ||
|
16dda0743c | ||
|
d079838818 | ||
|
81ce4688df | ||
|
ee33cc1832 | ||
|
82bb18fe10 | ||
|
8987ca7db6 | ||
|
8066fd8982 | ||
|
a96d8c05a7 | ||
|
4238a5dd50 | ||
|
94da55d15e | ||
|
09979b8551 | ||
|
5b82efa0be | ||
|
8502a36481 | ||
|
bcba64b2a1 | ||
|
e40d922eef | ||
|
7e226b43d3 | ||
|
bbf1a1882a | ||
|
5322cdf609 |
20
.gitignore
vendored
20
.gitignore
vendored
@@ -1,14 +1,20 @@
|
||||
*~
|
||||
tmp
|
||||
.buildpath
|
||||
.externalToolBuilders
|
||||
.project
|
||||
.settings
|
||||
.tx/config
|
||||
attachments
|
||||
backups
|
||||
indefero-*.zip
|
||||
src/IDF/conf/idf.php
|
||||
src/IDF/conf/idf.test.php
|
||||
www/test.php
|
||||
www/media/upload
|
||||
src/IDF/gettexttemplates
|
||||
indefero-*.zip
|
||||
src/IDF/conf/path.php
|
||||
.tx/config
|
||||
src/IDF/gettexttemplates
|
||||
src/IDF/locale/idf.pot.bak
|
||||
test/config.php
|
||||
test/test.db
|
||||
test/tmp
|
||||
test/config.php
|
||||
tmp
|
||||
www/media/upload
|
||||
www/test.php
|
||||
|
10
AUTHORS
10
AUTHORS
@@ -7,32 +7,42 @@ Much appreciated contributors (in alphabetical order):
|
||||
Andrew Nguyen <andrew-git-indefero@na-consulting.net>
|
||||
Baptiste Durand-Bret <bathizte@ozazar.org>
|
||||
Baptiste Michaud <bactisme@gmail.com> - Subversion sync
|
||||
Benjamin Danon <benjamin@sphax3d.org> - French translation
|
||||
Benjamin Jorand <benjamin.jorand@gmail.com> - Mercurial support
|
||||
Brenda Wallace <shiny@cpan.org>
|
||||
Brian Armstrong <brianar>
|
||||
Charles Melbye <charlie@yourwiki.net>
|
||||
Ciaran Gultnieks <ciaran@ciarang.com>
|
||||
Daniel Steudler <steudlerdaniel@gmail.com>
|
||||
David Feeney <davidf>
|
||||
Denis Kot <denis.kot@gmail.com> - Russian translation
|
||||
Dmitry Dulepov <dmitryd>
|
||||
Fernando Sayago Gil <mikados.mikados@gmail.com> - Spanish translation
|
||||
Gert van Valkenhoef <gertvv>
|
||||
Jakub Viták <mainiak@gmail.com> - Czech translation
|
||||
Janez Troha <http://www.dz0ny.info> - Slovenian translation
|
||||
Jean-Philippe Fleury <jpfleury>
|
||||
Jerry <lxb429@gmail.com> - Chinese translation
|
||||
Jonathan Aillet <jonathan.aillet@gmail.com> - French translation
|
||||
Julien Issler <julien@issler.net>
|
||||
Litew <litew9@gmail.com> - Russian translation
|
||||
Ludovic Bellière <xrogaan>
|
||||
Manuel Eidenberger <eidenberger@gmail.com>
|
||||
Matthew Dawson <mjd>
|
||||
Matías Halles <matias@halles.cl>
|
||||
Mehdi Kabab <http://pioupioum.fr/>
|
||||
Nicolas Lassalle <nicolas@beroot.org> - Subversion support
|
||||
Ozan <uobasar@gmail.com> - Turkish translation
|
||||
Patrick Georgi <patrick.georgi@coresystems.de>
|
||||
Pedro Kiefer <pedro@kiefer.com.br> - Brazilian Portuguese translation
|
||||
Raphaël Emourgeon <raphael>
|
||||
Samuel Suther <info@suther.de> - German translation
|
||||
Simon Holywell <treffynnon@php.net>
|
||||
Sindre R. Myren <sindrero@stud.ntnu.no>
|
||||
Stewart Platt <stew@futurete.ch>
|
||||
Stéphane Baron <sbaron>
|
||||
Thomas Keller <me@thomaskeller.biz> - Monotone support
|
||||
Victor Quinault <victor.quinault@gmail.com> - French translation
|
||||
Vladimir Solomatin <slash>
|
||||
William Martin <william.martin@lcpc.fr>
|
||||
Xavier Brochard <xavier@alternatif.org>
|
||||
|
@@ -6,9 +6,15 @@ the installation of InDefero by itself.
|
||||
|
||||
## PHP modules for indefero
|
||||
|
||||
Indefero need the GD module for PHP. It's named "php5-gd" in debian.
|
||||
Indefero needs additional PHP modules to function correctly, namely
|
||||
|
||||
$ apt-get install php5-gd
|
||||
- gd (for graphic operations)
|
||||
- zip (for upload archive processing)
|
||||
|
||||
The package names of these modules might vary between distributions,
|
||||
for Debian they are
|
||||
|
||||
$ apt-get install php5-gd php5-zip
|
||||
|
||||
## Recommended Layout of the Files
|
||||
|
||||
|
35
Makefile
35
Makefile
@@ -37,6 +37,11 @@ help:
|
||||
@printf "\tpo-push - Send the all PO files to the transifex server.\n"
|
||||
@printf "\tpo-pull - Get all PO files from the transifex server.\n"
|
||||
@printf "\tpo-stats - Show translation statistics of all PO files.\n"
|
||||
@printf "\nRules for managing the database :\n";
|
||||
@printf "\tdb-install - Install the database schema.\n"
|
||||
@printf "\tdb-update - Update the database schema.\n"
|
||||
@printf "\tdb-backup-foo - Create a named backup 'foo' with data from the current database.\n"
|
||||
@printf "\tdb-restore-foo - Restore schema and the data from a named backup 'foo' to an empty database.\n"
|
||||
|
||||
#
|
||||
# Internationalization rule, POT & PO file manipulation
|
||||
@@ -58,11 +63,11 @@ pot-update: pluf_path
|
||||
fi
|
||||
touch src/IDF/locale/idf.pot;
|
||||
# Extract string
|
||||
@cd src; php $(PLUF_PATH)/extracttemplates.php IDF/conf/idf.php IDF/gettexttemplates
|
||||
@cd src; php "$(PLUF_PATH)/extracttemplates.php" IDF/conf/idf.php IDF/gettexttemplates
|
||||
@cd src; for phpfile in `find . -iname "*.php"`; do \
|
||||
printf "Parsing file : "$$phpfile"\n"; \
|
||||
xgettext -o idf.pot -p ./IDF/locale/ --from-code=UTF-8 -j \
|
||||
--keyword --keyword=__ --keyword=_n:1,2 -L PHP $$phpfile ; \
|
||||
--keyword --keyword=__ --keyword=_n:1,2 -L PHP "$$phpfile" ; \
|
||||
done
|
||||
# Remove tmp folder
|
||||
rm -Rf src/IDF/gettexttemplates
|
||||
@@ -72,7 +77,7 @@ pot-update: pluf_path
|
||||
po-update: pluf_path
|
||||
@for pofile in `ls src/IDF/locale/*/idf.po`; do \
|
||||
printf "Updating file : "$$pofile"\n"; \
|
||||
msgmerge -v -U $$pofile src/IDF/locale/idf.pot; \
|
||||
msgmerge -v -U "$$pofile" src/IDF/locale/idf.pot; \
|
||||
printf "\n"; \
|
||||
done
|
||||
|
||||
@@ -135,7 +140,27 @@ po-stats:
|
||||
# make develop_zipfile
|
||||
#
|
||||
%-zipfile:
|
||||
@git archive --format=zip --prefix="indefero/" $(@:-zipfile=) \
|
||||
> indefero-$(@:-zipfile=)-`git log $(@:-zipfile=) -n 1 \
|
||||
@git archive --format=zip --prefix="indefero/" $* \
|
||||
> indefero-$*-`git log $* -n 1 \
|
||||
--pretty=format:%h`.zip
|
||||
|
||||
%-tarfile:
|
||||
@git archive --format=tar --prefix="indefero/" $* \
|
||||
> indefero-$*-`git log $* -n 1 \
|
||||
--pretty=format:%h`.tar
|
||||
|
||||
db-install:
|
||||
@cd src && php "$(PLUF_PATH)/migrate.php" --conf=IDF/conf/idf.php -a -d -i
|
||||
|
||||
db-update:
|
||||
@cd src && php "$(PLUF_PATH)/migrate.php" --conf=IDF/conf/idf.php -a -d
|
||||
|
||||
db-backup-%:
|
||||
@[ -e backups ] || mkdir backups
|
||||
@cd src && php "$(PLUF_PATH)/migrate.php" --conf=IDF/conf/idf.php -a -b ../backups $*
|
||||
@echo Files for named backup $* have been saved into backups/ directory.
|
||||
|
||||
db-restore-%:
|
||||
@cd src && php "$(PLUF_PATH)/migrate.php" --conf=IDF/conf/idf.php -a -r ../backups $*
|
||||
@echo Files for named backup $* have been restored from the backups/ directory.
|
||||
|
||||
|
229
NEWS.mdtext
229
NEWS.mdtext
@@ -1,17 +1,240 @@
|
||||
# InDefero 1.2 - xxx xxx xx xx:xx 2011 UTC
|
||||
# InDefero 1.4 - xxx xxx xx xx:xx 2012 UTC
|
||||
|
||||
## New Features
|
||||
|
||||
- Indefero can now record the due date of an issue and lets
|
||||
you filter and search by overdue issues (fixes issue 584,
|
||||
thanks to Simon Holywell!)
|
||||
|
||||
# InDefero 1.3.3 - Sun Nov 18 22:54:00 UTC 2012
|
||||
|
||||
**ATTENTION**: InDefero needs Pluf [a45dc195](http://projects.ceondo.com/p/pluf/source/commit/a45dc195)
|
||||
or newer to run properly!
|
||||
|
||||
## Bugfixes
|
||||
|
||||
- A long standing bug in the Mercurial plugin that lead to corruption
|
||||
of boolean configuration values in the `hgrc` of the served repository
|
||||
has been fixed (issue 523).
|
||||
|
||||
- If an anonymous or authenticated user had no access to any project in a
|
||||
forge, all projects were listed for him (but still no one was actually
|
||||
accessible). This has been fixed.
|
||||
|
||||
- Fixed a problem where the SyncGit plugin doesn't properly updates the
|
||||
authorized_keys file when the public key data contained slashes (thanks
|
||||
to Simon Gareste for the fix!)
|
||||
|
||||
- Under PostgreSQL indefero could not be set up because of a circular
|
||||
foreign key dependency. This has been fixed (issue 800).
|
||||
|
||||
- Under PostgreSQL new projects could not be created due to a failing
|
||||
foreign key relation. Adding project tags was not possible for a similar
|
||||
reason. This has been fixed (issue 800, continued).
|
||||
|
||||
- If a project was created based on the settings of an existing project,
|
||||
not all settings, like for example the new notification settings, have
|
||||
been copied. This has been changed insofar that now all properties except
|
||||
the project's URL, logo and individual SCM settings are copied by default.
|
||||
|
||||
- Wiki pages couldn't be properly saved with E_NOTICE enabled because
|
||||
of a syntax error (fixes issue 808).
|
||||
|
||||
- Indefero now shows detected copies in git changesets.
|
||||
|
||||
- A user is no longer redirected to the Help page if he enters 'H' in the
|
||||
password text field on the login page (fixes issue 821).
|
||||
|
||||
# InDefero 1.3.2 - Wed May 09 20:05 2012 UTC
|
||||
|
||||
## Bugfixes
|
||||
|
||||
- Fix two bugs in the project list view if no project
|
||||
is visible to the user or available in the forge (issue 805)
|
||||
|
||||
# InDefero 1.3.1 - Sun May 06 20:32 2012 UTC
|
||||
|
||||
## Bugfixes
|
||||
|
||||
- Compatiblity fixes for PostgreSQL (issue 800)
|
||||
- Fix TOC generation in wiki (issue 803)
|
||||
- Make the display of large files and diffs faster (issue 804)
|
||||
- Make the project list faster and its tag list more accurate by not
|
||||
counting projects that are invisible to the current user
|
||||
|
||||
## Language and Translations
|
||||
|
||||
- French translation updated (thanks to Jonathan Aillet!)
|
||||
|
||||
# InDefero 1.3 - Sun Apr 15 21:10 2012 UTC
|
||||
|
||||
This new major release of Indefero is possible thanks to the sponsor of
|
||||
[Scilab Enterprises](http://www.scilab-enterprises.com/).
|
||||
Scilab Enterprises is proud to share with the Indefero community the new
|
||||
changes which greatly improves the overall quality of the Indefero
|
||||
forge.
|
||||
|
||||
**ATTENTION**: InDefero now requires a PHP 5.3 compatible setup! You also
|
||||
need Pluf [4121ca4](http://projects.ceondo.com/p/pluf/source/commit/4121ca4)
|
||||
or newer to properly run this version of Indefero!
|
||||
|
||||
## Changes
|
||||
|
||||
- Indefero's post-commit web hook now by default issues HTTP PUT instead of
|
||||
HTTP POST requests and carries the authentication digest in the new
|
||||
`Web-Hook-Hmac` header. The old behaviour can be re-enabled by setting the
|
||||
`$cfg['webhook_processing']` flag to "compat", we urge you to change the
|
||||
implementations of this web hook as this setting is likely to be removed
|
||||
in future versions of Indefero.
|
||||
- Indefero now needs PHP's zip module which is usually not enabled by default.
|
||||
- Previously configured email notifications now have to be explicitely
|
||||
activated in the project's administrative area.
|
||||
|
||||
## New Features
|
||||
|
||||
- It is now possible to upload and embed resources like images or text
|
||||
files into wiki pages. If no preview for a resource's mime type is
|
||||
available, than a download link is provided for it instead.
|
||||
- The notification system has been overhauled; it is now possible to configure
|
||||
what kind of user group, project administrators, members and / or additional
|
||||
mail addresses are notified about updates in a certain section, such as
|
||||
issues, downloads, reviews, and so on. We now also ensure that notification
|
||||
emails for one object are uniquely identifyable to support a grouped view
|
||||
in email clients that support that. (fixes issues 334, 452, 480, and 791)
|
||||
- Indefero can now be configured to record activity metrics for all projects
|
||||
in a forge. This needs a special cron job named `activitycron.php`
|
||||
(under `scripts`) that is run on a regular basis. The metrics can be
|
||||
fine-tuned via `activity_section_weights` and `activity_lookback` in
|
||||
`idf.php` and the result is visible as green bar in the project list view.
|
||||
- The forge's project list has been overhauled - its now possible to attach
|
||||
labels on projects and to filter and order the project list by various
|
||||
criteria. Additionally, projects can now get an external project URL
|
||||
configured that is displayed as linkable icon right beside the project name
|
||||
(if available)
|
||||
- Forge administrators can furthermore configure an alternative entry page
|
||||
for the forge that is displayed instead of the plain project list. This
|
||||
page accepts standard Markdown syntax and has support for the new
|
||||
`projectlist` macro that allows the (partial) inline rendering of the
|
||||
known global project list.
|
||||
- It is now also possible to configure a web hook that informs an external
|
||||
URL about new and updated downloads for a specific project, similar to the
|
||||
available post-commit web hook.
|
||||
- One can now upload multiple files at once by using a special archive format
|
||||
which Indefero processes in the background and for which individual upload
|
||||
records are created.
|
||||
|
||||
## Bugfixes
|
||||
|
||||
- Indefero no longer confuses a non-owner of an issue with a notification that
|
||||
a particular ticket has been opened and assigned to him (fixes issue 562)
|
||||
- The diff view now renders properly in Firefox when a minimum font size
|
||||
is configured or the user zooms the web page (fixes issue 773)
|
||||
- Ensure that IDF does not break UTF-8 encoded strings when
|
||||
shortening them for view rendering (issue 785)
|
||||
- Two XSS holes in the issue and review details views are closed (fixes issue 793)
|
||||
|
||||
## Language and Translations
|
||||
|
||||
- Multiple fixes to English source strings (fixes issues 763, 766, and 772,
|
||||
thanks to JP Fleury!)
|
||||
- Updates to the French translation (thanks to Victor Quinault)
|
||||
|
||||
# InDefero 1.2 - Sun Nov 6 23:04:00 UTC 2011
|
||||
|
||||
ATTENTION: You need Pluf [46b7f251](http://projects.ceondo.com/p/pluf/source/commit/46b7f251)
|
||||
or newer to properly run this version of Indefero!
|
||||
|
||||
## New Features
|
||||
|
||||
- Indefero's issue tracker can now bi-directionally link issues with variable,
|
||||
configurable terms, such as "is related to", "is blocked by" or
|
||||
"is duplicated by" (issue 638)
|
||||
- When you search for issues, the results can further be refined by issue state
|
||||
(open or closed) and label (partially implements issue 548)
|
||||
- Source and diff views now make characters like line endings, tabs and other
|
||||
"invisible" control characters visible on hover and cope with long lines much
|
||||
better (issue 636)
|
||||
- Mercurial source views now show parent revisions (if any) and detailed change
|
||||
information
|
||||
- Subversion source views now show detailed change information (issue 622)
|
||||
- File download URLs now contain the file name rather than the upload id;
|
||||
old links still work though (issues 559 and 686)
|
||||
- monotone file and directory attributes are displayed in the tree and file view
|
||||
(needs a monotone with an interface version of 13.1 or newer)
|
||||
- The context area is now kept in view when a page scrolls down several pages
|
||||
- A summary section has been added to the issue tracker with statistics about
|
||||
open / closed issues, unresolved issues grouped by tags and owners
|
||||
- The project list and title has gathered a customizable icon for each project
|
||||
- The download section now provides MD5 checksums for uploaded files
|
||||
- Wiki pages now come with a designated stylesheet for printer output (issue 713)
|
||||
|
||||
## Bugfixes
|
||||
|
||||
- Git's cron job doesn't erase manually added keys anymore (issue 247)
|
||||
- The SVN interface acts more robust if an underlying repository has been
|
||||
restructured (issues 364 and 721)
|
||||
- monotone zip archive entries now all carry the revision date as mtime (issue 645)
|
||||
- Timeline only displays filter options for items a user has actually access to (issue 655)
|
||||
- Fix the self-link of the RSS feed (issue 666)
|
||||
- The timeline only now only displays filter options for items a user has
|
||||
actually access to (issue 655)
|
||||
- The log, tags and branches parsers for Mercurial are more robust now (issue 663)
|
||||
- Several SSH public key parsing issues have been fixed and the check for existing,
|
||||
uploaded keys has been improved (issue 679)
|
||||
- Diff views now show empty context lines for git and hg again (issue 688)
|
||||
- The SVN command line client no longer accidential tries to store the login
|
||||
credentials we give him as arguments for the user executing the SVN command
|
||||
- The usher section in the forge administration no longer displays a bogus
|
||||
server enty in case no monotone server is configured in the connected
|
||||
usher instance
|
||||
- A timeout that popped up when Usher is restarted has been fixed (issue 695)
|
||||
- The SyncMonotone plugin now cleans up partial artifacts it created during the
|
||||
addition of a new project or monotone key, in case an error popped up in the
|
||||
middle (issue 697)
|
||||
- Indefero now sends the MD5 checksum as HTTP header when downloading a file from the
|
||||
download area; additionally, a unneeded redirect has been removed (issue 716)
|
||||
- Source links without a specific revision did not work due to a wrong regex
|
||||
(issue 730)
|
||||
- Avatar URL generation use correctly the configuration (issue 732)
|
||||
- The SyncGit plugin no longer fails to remove a non-existing post-update hook
|
||||
on repository creation (issue 752)
|
||||
- When uploading a project logo, an existing uploaded file with the same name
|
||||
no longer leads to an error, but is simple overwritten (fixes issue 740)
|
||||
- The error detection and reporting in the SyncMonotone plugin has been improved
|
||||
- The branch links users of the Subversion frontend get when they enter a wrong
|
||||
revision are fixed; this list is now also only displayed (for any SCM) if
|
||||
there are actually branches available in the repository
|
||||
- If git's author name is not encoded in an UTF-8 compatible encoding, skip the
|
||||
author lookup, as we have no information what the author string is actually
|
||||
encoded in
|
||||
- Indefero no longer displays an empty parents paragraph in the commit view for
|
||||
root revisions of a git repository
|
||||
- Indefero now only shows the tags of the closed and not the open issues in the
|
||||
closed issues list
|
||||
|
||||
## Documentation
|
||||
|
||||
- The documentation on the setup of the monotone plugin has been improved.
|
||||
|
||||
## Translations
|
||||
|
||||
- The Russian translation has been enabled by default (thanks for all the great
|
||||
work, Denis Kot and Litew!)
|
||||
- Brazilian Portuguese translation started (thanks to Pedro Kiefer!)
|
||||
- Turkish translation started (thanks to Ozan!)
|
||||
|
||||
# InDefero 1.1.2 - Thu May 26 07:42:25 2011 UTC
|
||||
|
||||
## Bugfixes
|
||||
|
||||
- Fix tags extraction from git repository (issue 675)
|
||||
- Fix SSH validation method (issue 671)
|
||||
- Fix malformed URL in the RSS (issue 666)
|
||||
- Fix validateRevision call for Mercurial Scm (issue 657)
|
||||
|
||||
## Translations
|
||||
|
||||
- Missing word in French translation (issue 672)
|
||||
- Update Spanish translation
|
||||
|
||||
# InDefero 1.1.1 - Mon Mar 28 15:52 2011 UTC
|
||||
|
||||
## Bugfixes
|
||||
|
@@ -25,7 +25,7 @@ If you install monotone from source (<http://monotone.ca/downloads.php>),
|
||||
please follow the `INSTALL` document which comes with the software.
|
||||
It contains detailed instructions, including all needed dependencies.
|
||||
|
||||
## Choose your indefero setup
|
||||
## Choose your indefero (IDF) setup
|
||||
|
||||
The monotone plugin can be used in several different ways:
|
||||
|
||||
@@ -112,14 +112,40 @@ The monotone plugin can be used in several different ways:
|
||||
^D
|
||||
$ chmod 600 usher.conf
|
||||
|
||||
**ATTENTION:** Do _not_ configure a default monotone server key in `usher.conf`,
|
||||
otherwise the individual server key that IDF creates for each project is
|
||||
not used and this could cause problems.
|
||||
|
||||
Your indefero www user needs later write access to `usher.conf` and
|
||||
`projects/`. There are two ways of setting this up:
|
||||
|
||||
* Make the usher user the web user, for example via Apache's `suexec`
|
||||
* Use acls, like this:
|
||||
* Make the usher user the web user, for example via Apache's `suexec`.
|
||||
This is however a bit clumsy.
|
||||
* Preferred: Use Access Control Lists (ACLs), like this:
|
||||
|
||||
#
|
||||
# Linux
|
||||
#
|
||||
$ setfacl -m u:www:rw usher.conf
|
||||
$ setfacl -m d:u:www:rwx projects/
|
||||
$ setfacl -m d:u:usher:rwx projects/
|
||||
#
|
||||
# FreeBSD
|
||||
#
|
||||
$ setfacl -m user:www:rw::allow usher.conf
|
||||
$ setfacl -m user:www:rwxp:fd:allow projects/
|
||||
$ setfacl -m user:usher:rwxp:fd:allow projects/
|
||||
#
|
||||
# Mac OS X
|
||||
#
|
||||
chmod +a '_www allow read,write' usher.conf
|
||||
chmod +a '_www allow read,write,delete,add_file,add_subdirectory,file_inherit,directory_inherit' projects/
|
||||
chmod +a 'usher allow read,write,delete,add_file,add_subdirectory,file_inherit,directory_inherit' projects/
|
||||
|
||||
In each example's last line, `usher` is the user which is executing
|
||||
the usher instance. **It is very important to add this line, otherwise
|
||||
usher won't be able to read and write into the initial file system
|
||||
setup IDF creates!**
|
||||
|
||||
5. Wrap a daemonizer around usher, for example supervise from daemontools
|
||||
(<http://cr.yp.to/damontools.html>):
|
||||
|
284
logo/external_link.svg
Normal file
284
logo/external_link.svg
Normal file
@@ -0,0 +1,284 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="190.51302"
|
||||
height="182.16527"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.47 r22583"
|
||||
sodipodi:docname="external_link.svg"
|
||||
inkscape:export-filename="/Users/tommyd/Entwicklung/Open Source/indefero/www/media/idf/img/external_link.png"
|
||||
inkscape:export-xdpi="7.4108529"
|
||||
inkscape:export-ydpi="7.4108529"
|
||||
enable-background="new">
|
||||
<defs
|
||||
id="defs4">
|
||||
<inkscape:path-effect
|
||||
effect="skeletal"
|
||||
id="path-effect4079"
|
||||
is_visible="true"
|
||||
pattern="M 0,5 10,10 10,0 z"
|
||||
copytype="single_stretched"
|
||||
prop_scale="1"
|
||||
scale_y_rel="false"
|
||||
spacing="0"
|
||||
normal_offset="0"
|
||||
tang_offset="0"
|
||||
prop_units="false"
|
||||
vertical_pattern="false"
|
||||
fuse_tolerance="0" />
|
||||
<inkscape:path-effect
|
||||
effect="skeletal"
|
||||
id="path-effect4071"
|
||||
is_visible="true"
|
||||
pattern="M 0,5 10,10 10,0 z"
|
||||
copytype="single_stretched"
|
||||
prop_scale="1"
|
||||
scale_y_rel="false"
|
||||
spacing="0"
|
||||
normal_offset="0"
|
||||
tang_offset="0"
|
||||
prop_units="false"
|
||||
vertical_pattern="false"
|
||||
fuse_tolerance="0" />
|
||||
<marker
|
||||
inkscape:stockid="Arrow1Lend"
|
||||
orient="auto"
|
||||
refY="0"
|
||||
refX="0"
|
||||
id="Arrow1Lend"
|
||||
style="overflow:visible">
|
||||
<path
|
||||
id="path3627"
|
||||
d="M 0,0 5,-5 -12.5,0 5,5 0,0 z"
|
||||
style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;marker-start:none"
|
||||
transform="matrix(-0.8,0,0,-0.8,-10,0)" />
|
||||
</marker>
|
||||
<inkscape:path-effect
|
||||
effect="skeletal"
|
||||
id="path-effect3619"
|
||||
is_visible="true"
|
||||
pattern="M 0,5 10,10 10,0 z"
|
||||
copytype="single_stretched"
|
||||
prop_scale="1"
|
||||
scale_y_rel="false"
|
||||
spacing="0"
|
||||
normal_offset="0"
|
||||
tang_offset="0"
|
||||
prop_units="false"
|
||||
vertical_pattern="false"
|
||||
fuse_tolerance="0" />
|
||||
<inkscape:path-effect
|
||||
effect="skeletal"
|
||||
id="path-effect3615"
|
||||
is_visible="true"
|
||||
pattern="M 0,5 10,10 10,0 z"
|
||||
copytype="single_stretched"
|
||||
prop_scale="1"
|
||||
scale_y_rel="false"
|
||||
spacing="0"
|
||||
normal_offset="0"
|
||||
tang_offset="0"
|
||||
prop_units="false"
|
||||
vertical_pattern="false"
|
||||
fuse_tolerance="0" />
|
||||
<inkscape:path-effect
|
||||
effect="skeletal"
|
||||
id="path-effect3611"
|
||||
is_visible="true"
|
||||
pattern="M 0,5 10,10 10,0 z"
|
||||
copytype="single_stretched"
|
||||
prop_scale="1"
|
||||
scale_y_rel="false"
|
||||
spacing="0"
|
||||
normal_offset="0"
|
||||
tang_offset="0"
|
||||
prop_units="false"
|
||||
vertical_pattern="false"
|
||||
fuse_tolerance="0" />
|
||||
<inkscape:path-effect
|
||||
effect="skeletal"
|
||||
id="path-effect3607"
|
||||
is_visible="true"
|
||||
pattern="M 0,5 10,10 10,0 z"
|
||||
copytype="single_stretched"
|
||||
prop_scale="1"
|
||||
scale_y_rel="false"
|
||||
spacing="0"
|
||||
normal_offset="0"
|
||||
tang_offset="0"
|
||||
prop_units="false"
|
||||
vertical_pattern="false"
|
||||
fuse_tolerance="0" />
|
||||
<inkscape:path-effect
|
||||
effect="skeletal"
|
||||
id="path-effect3603"
|
||||
is_visible="true"
|
||||
pattern="M 0,5 10,10 10,0 z"
|
||||
copytype="single_stretched"
|
||||
prop_scale="1"
|
||||
scale_y_rel="false"
|
||||
spacing="0"
|
||||
normal_offset="0"
|
||||
tang_offset="0"
|
||||
prop_units="false"
|
||||
vertical_pattern="false"
|
||||
fuse_tolerance="0" />
|
||||
<inkscape:path-effect
|
||||
effect="skeletal"
|
||||
id="path-effect3599"
|
||||
is_visible="true"
|
||||
pattern="M 0,5 10,10 10,0 z"
|
||||
copytype="single_stretched"
|
||||
prop_scale="1"
|
||||
scale_y_rel="false"
|
||||
spacing="0"
|
||||
normal_offset="0"
|
||||
tang_offset="0"
|
||||
prop_units="false"
|
||||
vertical_pattern="false"
|
||||
fuse_tolerance="0" />
|
||||
<inkscape:path-effect
|
||||
effect="skeletal"
|
||||
id="path-effect3595"
|
||||
is_visible="true"
|
||||
pattern="M 0,5 10,10 10,0 z"
|
||||
copytype="single_stretched"
|
||||
prop_scale="1"
|
||||
scale_y_rel="false"
|
||||
spacing="0"
|
||||
normal_offset="0"
|
||||
tang_offset="0"
|
||||
prop_units="false"
|
||||
vertical_pattern="false"
|
||||
fuse_tolerance="0" />
|
||||
<inkscape:perspective
|
||||
sodipodi:type="inkscape:persp3d"
|
||||
inkscape:vp_x="0 : 526.18109 : 1"
|
||||
inkscape:vp_y="0 : 1000 : 0"
|
||||
inkscape:vp_z="744.09448 : 526.18109 : 1"
|
||||
inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
|
||||
id="perspective10" />
|
||||
<inkscape:perspective
|
||||
id="perspective4900"
|
||||
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
|
||||
inkscape:vp_z="1 : 0.5 : 1"
|
||||
inkscape:vp_y="0 : 1000 : 0"
|
||||
inkscape:vp_x="0 : 0.5 : 1"
|
||||
sodipodi:type="inkscape:persp3d" />
|
||||
<filter
|
||||
inkscape:collect="always"
|
||||
id="filter4921"
|
||||
x="-0.12982728"
|
||||
width="1.2596545"
|
||||
y="-0.092135489"
|
||||
height="1.184271"
|
||||
color-interpolation-filters="sRGB">
|
||||
<feGaussianBlur
|
||||
inkscape:collect="always"
|
||||
stdDeviation="6.8004762"
|
||||
id="feGaussianBlur4923" />
|
||||
</filter>
|
||||
<inkscape:perspective
|
||||
id="perspective4933"
|
||||
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
|
||||
inkscape:vp_z="1 : 0.5 : 1"
|
||||
inkscape:vp_y="0 : 1000 : 0"
|
||||
inkscape:vp_x="0 : 0.5 : 1"
|
||||
sodipodi:type="inkscape:persp3d" />
|
||||
<filter
|
||||
color-interpolation-filters="sRGB"
|
||||
inkscape:collect="always"
|
||||
id="filter4921-3"
|
||||
x="-0.12982728"
|
||||
width="1.2596545"
|
||||
y="-0.092135489"
|
||||
height="1.184271">
|
||||
<feGaussianBlur
|
||||
inkscape:collect="always"
|
||||
stdDeviation="6.8004762"
|
||||
id="feGaussianBlur4923-8" />
|
||||
</filter>
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="1"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="1.78"
|
||||
inkscape:cx="138.94497"
|
||||
inkscape:cy="111.09667"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:window-width="1389"
|
||||
inkscape:window-height="803"
|
||||
inkscape:window-x="47"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="0" />
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer2"
|
||||
inkscape:label="Ebene"
|
||||
transform="translate(16.297732,13.906157)"
|
||||
style="display:inline"
|
||||
sodipodi:insensitive="true">
|
||||
<g
|
||||
id="g2818-7"
|
||||
style="fill:#e6e6e6;fill-opacity:1;stroke:#a0a0a0;stroke-width:2.26057386;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;filter:url(#filter4921-3)"
|
||||
transform="matrix(0.97722285,0,0,-0.85847241,-166.837,487.59193)">
|
||||
<rect
|
||||
ry="9.2957697"
|
||||
rx="8.07693"
|
||||
y="389.50504"
|
||||
x="171.42857"
|
||||
height="177.14285"
|
||||
width="125.71429"
|
||||
id="rect2816-9"
|
||||
style="opacity:0.79710143;fill:#000000;fill-opacity:1;stroke:#a0a0a0;stroke-width:2.26057386;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
inkscape:label="Ebene 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-155.26623,-395.13431)"
|
||||
style="display:inline;">
|
||||
<rect
|
||||
style="fill:#e6e6e6;fill-opacity:1;fill-rule:nonzero;stroke:#a0a0a0;stroke-width:2.41343927;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
|
||||
id="rect4985"
|
||||
width="129.92365"
|
||||
height="152.95734"
|
||||
x="168.88869"
|
||||
y="407.28156"
|
||||
rx="9.9356585"
|
||||
ry="11.003931" />
|
||||
<path
|
||||
style="fill:#00000f;fill-opacity:0.94117647;stroke:none;stroke-width:2.17028474999999998;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
|
||||
d="m 205.32036,460.1042 67.83031,0 0,-32.45771 71.54343,60.58773 -72.50161,61.39918 0,-33.26916 -66.75258,0 0,-56.26004"
|
||||
id="path4081"
|
||||
sodipodi:nodetypes="cccccccc" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 8.6 KiB |
@@ -2,7 +2,7 @@
|
||||
<?php
|
||||
|
||||
$xmlfile = dirname(__FILE__) .'/test/report.xml';
|
||||
passthru('phpunit --coverage-clover='.$xmlfile);
|
||||
passthru('phpunit --coverage-clover='.escapeshellarg($xmlfile));
|
||||
$xml = simplexml_load_string(file_get_contents($xmlfile));
|
||||
unlink($xmlfile);
|
||||
printf(
|
||||
|
48
scripts/activitycron.php
Normal file
48
scripts/activitycron.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/*
|
||||
# ***** BEGIN LICENSE BLOCK *****
|
||||
# This file is part of InDefero, an open source project management application.
|
||||
# Copyright (C) 2008-2011 Céondo Ltd and contributors.
|
||||
#
|
||||
# InDefero 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.
|
||||
#
|
||||
# InDefero is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
#
|
||||
# ***** END LICENSE BLOCK ***** */
|
||||
|
||||
/**
|
||||
* This script recalculates the "project activity" for all of the
|
||||
* forge's projects for the given date.
|
||||
* If no date is given, yesterday's date is used.
|
||||
*
|
||||
* This script should run once a day. You can configure its behaviour
|
||||
* with $cfg['activity_section_weights'] and $cfg['activity_lookback'].
|
||||
*
|
||||
* If the script runs more than once with the same date argument,
|
||||
* previously recorded project activity values are replaced with the
|
||||
* newly created ones.
|
||||
*/
|
||||
|
||||
require dirname(__FILE__).'/../src/IDF/conf/path.php';
|
||||
require 'Pluf.php';
|
||||
Pluf::start(dirname(__FILE__).'/../src/IDF/conf/idf.php');
|
||||
|
||||
$date = new DateTime('yesterday');
|
||||
if (count($_SERVER['argv']) > 1) {
|
||||
$date = new DateTime($_SERVER['argv'][1]);
|
||||
}
|
||||
|
||||
echo 'recalculating project activity for '.$date->format('Y-m-d')."\n";
|
||||
IDF_ActivityTaxonomy::recalculateTaxnomies($date);
|
||||
|
165
src/IDF/ActivityTaxonomy.php
Normal file
165
src/IDF/ActivityTaxonomy.php
Normal file
@@ -0,0 +1,165 @@
|
||||
<?php
|
||||
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/*
|
||||
# ***** BEGIN LICENSE BLOCK *****
|
||||
# This file is part of InDefero, an open source project management application.
|
||||
# Copyright (C) 2008-2011 Céondo Ltd and contributors.
|
||||
#
|
||||
# InDefero 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.
|
||||
#
|
||||
# InDefero is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
#
|
||||
# ***** END LICENSE BLOCK ***** */
|
||||
|
||||
/**
|
||||
* Class that calculates the activity value for all projects on a
|
||||
* specific date and time.
|
||||
*
|
||||
* We do this by counting adds or updates of database objects in
|
||||
* the particular section (according to the timeline) and relate this
|
||||
* value to the overall activity of a section in the forge.
|
||||
*
|
||||
* To illustrate the behaviour, a simple example could be a forge with
|
||||
* only two projects that both have only issue tracking enabled.
|
||||
* The first project created or updated 10 tickets during the past period,
|
||||
* the other 20. The activity index for the first should therefor be
|
||||
* calculated as 0.33 and the second as 0.66.
|
||||
* Note that this simple example doesn't take activity in other
|
||||
* sections into account, so the the total activity of all projects
|
||||
* for a certain time period might add up to more than 1.0.
|
||||
*
|
||||
* @author tommyd
|
||||
*/
|
||||
class IDF_ActivityTaxonomy
|
||||
{
|
||||
public static function recalculateTaxnomies(DateTime $date)
|
||||
{
|
||||
$sectionWeights = Pluf::f('activity_section_weights', null);
|
||||
$lookback = Pluf::f('activity_lookback', null);
|
||||
if ($sectionWeights === null || $lookback === null) {
|
||||
throw new LogicException('activity configuration is missing in idf.php');
|
||||
}
|
||||
|
||||
//
|
||||
// query and normalize the section weights
|
||||
//
|
||||
$allWeights = array_sum($sectionWeights);
|
||||
if ($allWeights == 0) {
|
||||
throw new LogicException('the sum of all "activity_section_weights" must not be 0');
|
||||
}
|
||||
foreach ($sectionWeights as $section => $weight) {
|
||||
$sectionWeights[$section] = $weight / (float) $allWeights;
|
||||
}
|
||||
|
||||
//
|
||||
// determine the date boundaries
|
||||
//
|
||||
if ($lookback < 1) {
|
||||
throw new LogicException('lookback must be greater or equal to 1');
|
||||
}
|
||||
$dateCopy = new DateTime();
|
||||
$dateCopy->setTimestamp($date->getTimestamp());
|
||||
$dateBoundaries = array(
|
||||
$dateCopy->format('Y-m-d 23:59:59'),
|
||||
$dateCopy->sub(new DateInterval('P'.$lookback.'D'))->format('Y-m-d 00:00:00')
|
||||
);
|
||||
|
||||
//
|
||||
// now recalculate the values for all projects
|
||||
//
|
||||
$projects = Pluf::factory('IDF_Project')->getList();
|
||||
foreach ($projects as $project) {
|
||||
self::recalculateTaxonomy($date, $project, $dateBoundaries, $sectionWeights);
|
||||
}
|
||||
}
|
||||
|
||||
private static function recalculateTaxonomy(DateTime $date, IDF_Project $project, array $dateBoundaries, array $sectionWeights)
|
||||
{
|
||||
$conf = new IDF_Conf();
|
||||
$conf->setProject($project);
|
||||
|
||||
$sectionClasses = array(
|
||||
'source' => array('IDF_Commit'),
|
||||
'issues' => array('IDF_Issue'),
|
||||
'wiki' => array('IDF_Wiki_Page', 'IDF_Wiki_Resource'),
|
||||
'review' => array('IDF_Review'),
|
||||
'downloads' => array('IDF_Upload')
|
||||
);
|
||||
|
||||
$value = 0;
|
||||
foreach ($sectionWeights as $section => $weight) {
|
||||
// skip closed / non-existant sections
|
||||
if ($conf->getVal($section.'_access_rights') === 'none')
|
||||
continue;
|
||||
|
||||
if (!array_key_exists($section, $sectionClasses))
|
||||
continue;
|
||||
|
||||
$sectionValue = self::calculateActivityValue(
|
||||
$dateBoundaries, $sectionClasses[$section], $project->id);
|
||||
$value = ((1 - $weight) * $value) + ($weight * $sectionValue);
|
||||
}
|
||||
|
||||
echo "project {$project->name} has an activity value of $value\n";
|
||||
|
||||
$sql = new Pluf_SQL('project=%s AND date=%s', array($project->id, $date->format('Y-m-d')));
|
||||
$activity = Pluf::factory('IDF_ProjectActivity')->getOne(array('filter' => $sql->gen()));
|
||||
|
||||
if ($activity == null) {
|
||||
$activity = new IDF_ProjectActivity();
|
||||
$activity->project = $project;
|
||||
$activity->date = $date->format('Y-m-d');
|
||||
$activity->value = $value;
|
||||
$activity->create();
|
||||
} else {
|
||||
$activity->value = $value;
|
||||
$activity->update();
|
||||
}
|
||||
}
|
||||
|
||||
private static function calculateActivityValue(array $dateBoundaries, array $classes, $projectId)
|
||||
{
|
||||
$allCount = self::countActivityFor($dateBoundaries, $classes);
|
||||
if ($allCount == 0) return 0;
|
||||
$prjCount = self::countActivityFor($dateBoundaries, $classes, $projectId);
|
||||
return $prjCount / (float) $allCount;
|
||||
}
|
||||
|
||||
private static function countActivityFor(array $dateBoundaries, array $classes, $projectId = null)
|
||||
{
|
||||
static $cache = array();
|
||||
$argIdent = md5(serialize(func_get_args()));
|
||||
if (array_key_exists($argIdent, $cache)) {
|
||||
return $cache[$argIdent];
|
||||
}
|
||||
|
||||
$cache[$argIdent] = 0;
|
||||
list($higher, $lower) = $dateBoundaries;
|
||||
$db = Pluf::db();
|
||||
$classes_esc = array();
|
||||
foreach ($classes as $class) {
|
||||
$classes_esc[] = $db->esc($class);
|
||||
}
|
||||
$sql = new Pluf_SQL('model_class IN ('.implode(',', $classes_esc).') '.
|
||||
'AND creation_dtime >= %s AND creation_dtime <= %s',
|
||||
array($lower, $higher));
|
||||
|
||||
if ($projectId !== null) {
|
||||
$sql->SAnd(new Pluf_SQL('project=%s', array($projectId)));
|
||||
}
|
||||
|
||||
$cache[$argIdent] = Pluf::factory('IDF_Timeline')->getCount(array('filter' => $sql->gen()));
|
||||
|
||||
return $cache[$argIdent];
|
||||
}
|
||||
}
|
@@ -235,7 +235,7 @@ class IDF_Commit extends Pluf_Model
|
||||
</tr>
|
||||
<tr class="extra">
|
||||
<td colspan="2">
|
||||
<div class="helptext right">'.sprintf(__('Commit %s, by %s'), '<a href="'.$url.'" class="mono">'.$this->scm_id.'</a>', $user).'</div></td></tr>';
|
||||
<div class="helptext right">'.sprintf(__('Commit %1$s, by %2$s'), '<a href="'.$url.'" class="mono">'.$this->scm_id.'</a>', $user).'</div></td></tr>';
|
||||
return Pluf_Template::markSafe($out);
|
||||
}
|
||||
|
||||
@@ -287,6 +287,12 @@ class IDF_Commit extends Pluf_Model
|
||||
$url = str_replace(array('%p', '%r'),
|
||||
array($project->shortname, $this->scm_id),
|
||||
$conf->getVal('webhook_url', ''));
|
||||
|
||||
// trigger a POST instead of the standard PUT if we're asked for
|
||||
$method = 'PUT';
|
||||
if (Pluf::f('webhook_processing', '') === 'compat') {
|
||||
$method = 'POST';
|
||||
}
|
||||
$payload = array('to_send' => array(
|
||||
'project' => $project->shortname,
|
||||
'rev' => $this->scm_id,
|
||||
@@ -297,41 +303,51 @@ class IDF_Commit extends Pluf_Model
|
||||
'creation_date' => $this->creation_dtime,
|
||||
),
|
||||
'project_id' => $project->id,
|
||||
'authkey' => $project->getPostCommitHookKey(),
|
||||
'authkey' => $project->getWebHookKey(),
|
||||
'url' => $url,
|
||||
'method' => $method,
|
||||
);
|
||||
$item = new IDF_Queue();
|
||||
$item->type = 'new_commit';
|
||||
$item->payload = $payload;
|
||||
$item->create();
|
||||
|
||||
if ('' == $conf->getVal('source_notification_email', '')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$current_locale = Pluf_Translation::getLocale();
|
||||
$langs = Pluf::f('languages', array('en'));
|
||||
Pluf_Translation::loadSetLocale($langs[0]);
|
||||
|
||||
$context = new Pluf_Template_Context(
|
||||
array(
|
||||
'c' => $this,
|
||||
'project' => $this->get_project(),
|
||||
'url_base' => Pluf::f('url_base'),
|
||||
)
|
||||
);
|
||||
$tmpl = new Pluf_Template('idf/source/commit-created-email.txt');
|
||||
$text_email = $tmpl->render($context);
|
||||
$addresses = explode(',', $conf->getVal('source_notification_email'));
|
||||
foreach ($addresses as $address) {
|
||||
$email = new Pluf_Mail(Pluf::f('from_email'),
|
||||
$from_email = Pluf::f('from_email');
|
||||
$recipients = $project->getNotificationRecipientsForTab('source');
|
||||
|
||||
foreach ($recipients as $address => $language) {
|
||||
|
||||
if (!empty($this->author) && $this->author->email === $address) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Pluf_Translation::loadSetLocale($language);
|
||||
|
||||
$context = new Pluf_Template_Context(array(
|
||||
'commit' => $this,
|
||||
'project' => $project,
|
||||
'url_base' => Pluf::f('url_base'),
|
||||
));
|
||||
|
||||
// commits are usually not updated, therefor we do not
|
||||
// distinguish between create and update here
|
||||
$tplfile = 'idf/source/commit-created-email.txt';
|
||||
$subject = __('New Commit %1$s - %2$s (%3$s)');
|
||||
|
||||
$tmpl = new Pluf_Template($tplfile);
|
||||
$text_email = $tmpl->render($context);
|
||||
|
||||
$email = new Pluf_Mail($from_email,
|
||||
$address,
|
||||
sprintf(__('New Commit %s - %s (%s)'),
|
||||
sprintf($subject,
|
||||
$this->scm_id, $this->summary,
|
||||
$this->get_project()->shortname));
|
||||
$project->shortname));
|
||||
$email->addTextMessage($text_email);
|
||||
$email->sendMail();
|
||||
}
|
||||
|
||||
Pluf_Translation::loadSetLocale($current_locale);
|
||||
}
|
||||
}
|
||||
|
@@ -84,7 +84,7 @@ class IDF_Conf extends Pluf_Model
|
||||
|
||||
function initCache()
|
||||
{
|
||||
$this->datacache = new ArrayObject();
|
||||
$this->datacache = array();
|
||||
$sql = new Pluf_SQL('project=%s', $this->_project->id);
|
||||
foreach ($this->getList(array('filter' => $sql->gen())) as $val) {
|
||||
$this->datacache[$val->vkey] = $val->vdesc;
|
||||
@@ -129,4 +129,12 @@ class IDF_Conf extends Pluf_Model
|
||||
$this->initCache();
|
||||
}
|
||||
}
|
||||
|
||||
function getKeys()
|
||||
{
|
||||
if ($this->datacache === null) {
|
||||
$this->initCache();
|
||||
}
|
||||
return array_keys($this->datacache);
|
||||
}
|
||||
}
|
||||
|
231
src/IDF/Diff.php
231
src/IDF/Diff.php
@@ -35,9 +35,7 @@ class IDF_Diff
|
||||
public function __construct($diff, $path_strip_level = 0)
|
||||
{
|
||||
$this->path_strip_level = $path_strip_level;
|
||||
// this works because in unified diff format even empty lines are
|
||||
// either prefixed with a '+', '-' or ' '
|
||||
$this->lines = preg_split("/\015\012|\015|\012/", $diff, -1, PREG_SPLIT_NO_EMPTY);
|
||||
$this->lines = IDF_FileUtil::splitIntoLines($diff, true);
|
||||
}
|
||||
|
||||
public function parse()
|
||||
@@ -66,12 +64,12 @@ class IDF_Diff
|
||||
}
|
||||
|
||||
// use new file name by default
|
||||
preg_match("/^\+\+\+ ([^\t]+)/", $newfileline, $m);
|
||||
preg_match("/^\+\+\+ ([^\t\n\r]+)/", $newfileline, $m);
|
||||
$current_file = $m[1];
|
||||
if ($current_file === '/dev/null') {
|
||||
// except if it's /dev/null, use the old one instead
|
||||
// eg. mtn 0.48 and newer
|
||||
preg_match("/^--- ([^\t]+)/", $oldfileline, $m);
|
||||
preg_match("/^--- ([^\t\r\n]+)/", $oldfileline, $m);
|
||||
$current_file = $m[1];
|
||||
}
|
||||
if ($this->path_strip_level > 0) {
|
||||
@@ -101,11 +99,12 @@ class IDF_Diff
|
||||
$files[$current_file]['chunks'][] = array();
|
||||
|
||||
while ($i < $diffsize && ($addlines >= 0 || $dellines >= 0)) {
|
||||
$linetype = $this->lines[$i] != '' ? $this->lines[$i][0] : ' ';
|
||||
$linetype = $this->lines[$i] != '' ? $this->lines[$i][0] : false;
|
||||
$content = substr($this->lines[$i], 1);
|
||||
switch ($linetype) {
|
||||
case ' ':
|
||||
$files[$current_file]['chunks'][$current_chunk][] =
|
||||
array($delstart, $addstart, substr($this->lines[$i++], 1));
|
||||
array($delstart, $addstart, $content);
|
||||
$dellines--;
|
||||
$addlines--;
|
||||
$delstart++;
|
||||
@@ -113,23 +112,26 @@ class IDF_Diff
|
||||
break;
|
||||
case '+':
|
||||
$files[$current_file]['chunks'][$current_chunk][] =
|
||||
array('', $addstart, substr($this->lines[$i++], 1));
|
||||
array('', $addstart, $content);
|
||||
$addlines--;
|
||||
$addstart++;
|
||||
break;
|
||||
case '-':
|
||||
$files[$current_file]['chunks'][$current_chunk][] =
|
||||
array($delstart, '', substr($this->lines[$i++], 1));
|
||||
array($delstart, '', $content);
|
||||
$dellines--;
|
||||
$delstart++;
|
||||
break;
|
||||
case '\\':
|
||||
// ignore newline handling for now, see issue 636
|
||||
$i++;
|
||||
// no new line at the end of this file; remove pseudo new line from last line
|
||||
$cur = count($files[$current_file]['chunks'][$current_chunk]) - 1;
|
||||
$files[$current_file]['chunks'][$current_chunk][$cur][2] =
|
||||
rtrim($files[$current_file]['chunks'][$current_chunk][$cur][2], "\r\n");
|
||||
continue;
|
||||
default:
|
||||
break 2;
|
||||
}
|
||||
$i++;
|
||||
}
|
||||
$current_chunk++;
|
||||
}
|
||||
@@ -144,46 +146,92 @@ class IDF_Diff
|
||||
public function as_html()
|
||||
{
|
||||
$out = '';
|
||||
foreach ($this->files as $filename=>$file) {
|
||||
foreach ($this->files as $filename => $file) {
|
||||
$pretty = '';
|
||||
$fileinfo = IDF_FileUtil::getMimeType($filename);
|
||||
if (IDF_FileUtil::isSupportedExtension($fileinfo[2])) {
|
||||
$pretty = ' prettyprint';
|
||||
}
|
||||
$out .= "\n".'<table class="diff" summary="">'."\n";
|
||||
$out .= '<tr id="diff-'.md5($filename).'"><th colspan="3">'.Pluf_esc($filename).'</th></tr>'."\n";
|
||||
|
||||
$cc = 1;
|
||||
$offsets = array();
|
||||
$contents = array();
|
||||
|
||||
foreach ($file['chunks'] as $chunk) {
|
||||
foreach ($chunk as $line) {
|
||||
if ($line[0] and $line[1]) {
|
||||
$class = 'diff-c';
|
||||
} elseif ($line[0]) {
|
||||
$class = 'diff-r';
|
||||
list($left, $right, $content) = $line;
|
||||
if ($left and $right) {
|
||||
$class = 'context';
|
||||
} elseif ($left) {
|
||||
$class = 'removed';
|
||||
} else {
|
||||
$class = 'diff-a';
|
||||
$class = 'added';
|
||||
}
|
||||
$line_content = self::padLine(Pluf_esc($line[2]));
|
||||
$out .= sprintf('<tr class="diff-line"><td class="diff-lc">%s</td><td class="diff-lc">%s</td><td class="%s%s mono">%s</td></tr>'."\n", $line[0], $line[1], $class, $pretty, $line_content);
|
||||
|
||||
$offsets[] = sprintf('<td>%s</td><td>%s</td>', $left, $right);
|
||||
$content = IDF_FileUtil::emphasizeControlCharacters(Pluf_esc($content));
|
||||
$contents[] = sprintf('<td class="%s%s mono">%s</td>', $class, $pretty, $content);
|
||||
}
|
||||
if (count($file['chunks']) > $cc) {
|
||||
$offsets[] = '<td class="next">...</td><td class="next">...</td>';
|
||||
$contents[] = '<td class="next"></td>';
|
||||
}
|
||||
if (count($file['chunks']) > $cc)
|
||||
$out .= '<tr class="diff-next"><td>...</td><td>...</td><td> </td></tr>'."\n";
|
||||
$cc++;
|
||||
}
|
||||
$out .= '</table>';
|
||||
}
|
||||
return Pluf_Template::markSafe($out);
|
||||
}
|
||||
|
||||
public static function padLine($line)
|
||||
{
|
||||
$line = str_replace("\t", ' ', $line);
|
||||
$n = strlen($line);
|
||||
for ($i=0;$i<$n;$i++) {
|
||||
if (substr($line, $i, 1) != ' ') {
|
||||
break;
|
||||
list($added, $removed) = end($file['chunks_def']);
|
||||
|
||||
$added = $added[0] + $added[1];
|
||||
$leftwidth = 0;
|
||||
if ($added > 0)
|
||||
$leftwidth = ((ceil(log10($added)) + 1) * 8) + 17;
|
||||
|
||||
$removed = $removed[0] + $removed[1];
|
||||
$rightwidth = 0;
|
||||
if ($removed > 0)
|
||||
$rightwidth = ((ceil(log10($removed)) + 1) * 8) + 17;
|
||||
|
||||
// we need to correct the width of a single column a little
|
||||
// to take less space and to hide the empty one
|
||||
$class = '';
|
||||
if ($leftwidth == 0) {
|
||||
$class = 'left-hidden';
|
||||
$rightwidth -= floor(log10($removed));
|
||||
}
|
||||
else if ($rightwidth == 0) {
|
||||
$class = 'right-hidden';
|
||||
$leftwidth -= floor(log10($added));
|
||||
}
|
||||
|
||||
$inner_linecounts =
|
||||
'<table class="diff-linecounts '.$class.'">' ."\n".
|
||||
'<colgroup><col width="'.$leftwidth.'" /><col width="'. $rightwidth.'" /></colgroup>' ."\n".
|
||||
'<tr class="line">' .
|
||||
implode('</tr>'."\n".'<tr class="line">', $offsets).
|
||||
'</tr>' ."\n".
|
||||
'</table>' ."\n";
|
||||
|
||||
|
||||
$inner_contents =
|
||||
'<table class="diff-contents">' ."\n".
|
||||
'<tr class="line">' .
|
||||
implode('</tr>'."\n".'<tr class="line">', $contents) .
|
||||
'</tr>' ."\n".
|
||||
'</table>' ."\n";
|
||||
|
||||
$out .= '<table class="diff unified">' ."\n".
|
||||
'<colgroup><col width="'.($leftwidth + $rightwidth + 1).'" /><col width="*" /></colgroup>' ."\n".
|
||||
'<tr id="diff-'.md5($filename).'">'.
|
||||
'<th colspan="2">'.Pluf_esc($filename).'</th>'.
|
||||
'</tr>' ."\n".
|
||||
'<tr>' .
|
||||
'<td>'. $inner_linecounts .'</td>'. "\n".
|
||||
'<td><div class="scroll">'. $inner_contents .'</div></td>'.
|
||||
'</tr>' ."\n".
|
||||
'</table>' ."\n";
|
||||
}
|
||||
return str_repeat(' ', $i).substr($line, $i);
|
||||
|
||||
return Pluf_Template::markSafe($out);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -208,12 +256,12 @@ class IDF_Diff
|
||||
*/
|
||||
public function fileCompare($orig, $chunks, $filename, $context=10)
|
||||
{
|
||||
$orig_lines = preg_split("/\015\012|\015|\012/", $orig);
|
||||
$orig_lines = IDF_FileUtil::splitIntoLines($orig);
|
||||
$new_chunks = $this->mergeChunks($orig_lines, $chunks, $context);
|
||||
return $this->renderCompared($new_chunks, $filename);
|
||||
}
|
||||
|
||||
public function mergeChunks($orig_lines, $chunks, $context=10)
|
||||
private function mergeChunks($orig_lines, $chunks, $context=10)
|
||||
{
|
||||
$spans = array();
|
||||
$new_chunks = array();
|
||||
@@ -310,38 +358,115 @@ class IDF_Diff
|
||||
return $nnew_chunks;
|
||||
}
|
||||
|
||||
public function renderCompared($chunks, $filename)
|
||||
private function renderCompared($chunks, $filename)
|
||||
{
|
||||
$fileinfo = IDF_FileUtil::getMimeType($filename);
|
||||
$pretty = '';
|
||||
if (IDF_FileUtil::isSupportedExtension($fileinfo[2])) {
|
||||
$pretty = ' prettyprint';
|
||||
}
|
||||
$out = '';
|
||||
|
||||
$cc = 1;
|
||||
$i = 0;
|
||||
$left_offsets = array();
|
||||
$left_contents = array();
|
||||
$right_offsets = array();
|
||||
$right_contents = array();
|
||||
|
||||
$max_lineno_left = $max_lineno_right = 0;
|
||||
|
||||
foreach ($chunks as $chunk) {
|
||||
foreach ($chunk as $line) {
|
||||
$line1 = ' ';
|
||||
$line2 = ' ';
|
||||
$line[2] = (strlen($line[2])) ? self::padLine(Pluf_esc($line[2])) : ' ';
|
||||
$left = '';
|
||||
$right = '';
|
||||
$content = IDF_FileUtil::emphasizeControlCharacters(Pluf_esc($line[2]));
|
||||
|
||||
if ($line[0] and $line[1]) {
|
||||
$class = 'diff-c';
|
||||
$line1 = $line2 = $line[2];
|
||||
$class = 'context';
|
||||
$left = $right = $content;
|
||||
} elseif ($line[0]) {
|
||||
$class = 'diff-r';
|
||||
$line1 = $line[2];
|
||||
$class = 'removed';
|
||||
$left = $content;
|
||||
} else {
|
||||
$class = 'diff-a';
|
||||
$line2 = $line[2];
|
||||
$class = 'added';
|
||||
$right = $content;
|
||||
}
|
||||
$out .= sprintf('<tr class="diff-line"><td class="diff-lc">%s</td><td class="%s mono%s"><code>%s</code></td><td class="diff-lc">%s</td><td class="%s mono%s"><code>%s</code></td></tr>'."\n", $line[0], $class, $pretty, $line1, $line[1], $class, $pretty, $line2);
|
||||
|
||||
$left_offsets[] = sprintf('<td>%s</td>', $line[0]);
|
||||
$right_offsets[] = sprintf('<td>%s</td>', $line[1]);
|
||||
$left_contents[] = sprintf('<td class="%s%s mono">%s</td>', $class, $pretty, $left);
|
||||
$right_contents[] = sprintf('<td class="%s%s mono">%s</td>', $class, $pretty, $right);
|
||||
|
||||
$max_lineno_left = max($max_lineno_left, $line[0]);
|
||||
$max_lineno_right = max($max_lineno_right, $line[1]);
|
||||
}
|
||||
|
||||
if (count($chunks) > $cc) {
|
||||
$left_offsets[] = '<td class="next">...</td>';
|
||||
$right_offsets[] = '<td class="next">...</td>';
|
||||
$left_contents[] = '<td></td>';
|
||||
$right_contents[] = '<td></td>';
|
||||
}
|
||||
if (count($chunks) > $cc)
|
||||
$out .= '<tr class="diff-next"><td>...</td><td> </td><td>...</td><td> </td></tr>'."\n";
|
||||
$cc++;
|
||||
$i++;
|
||||
}
|
||||
|
||||
$leftwidth = 1;
|
||||
if ($max_lineno_left > 0)
|
||||
$leftwidth = ((ceil(log10($max_lineno_left)) + 1) * 8) + 17;
|
||||
|
||||
$rightwidth = 1;
|
||||
if ($max_lineno_right > 0)
|
||||
$rightwidth = ((ceil(log10($max_lineno_right)) + 1) * 8) + 17;
|
||||
|
||||
$inner_linecounts_left =
|
||||
'<table class="diff-linecounts">' ."\n".
|
||||
'<colgroup><col width="'.$leftwidth.'" /></colgroup>' ."\n".
|
||||
'<tr class="line">' .
|
||||
implode('</tr>'."\n".'<tr class="line">', $left_offsets).
|
||||
'</tr>' ."\n".
|
||||
'</table>' ."\n";
|
||||
|
||||
$inner_linecounts_right =
|
||||
'<table class="diff-linecounts">' ."\n".
|
||||
'<colgroup><col width="'.$rightwidth.'" /></colgroup>' ."\n".
|
||||
'<tr class="line">' .
|
||||
implode('</tr>'."\n".'<tr class="line">', $right_offsets).
|
||||
'</tr>' ."\n".
|
||||
'</table>' ."\n";
|
||||
|
||||
$inner_contents_left =
|
||||
'<table class="diff-contents">' ."\n".
|
||||
'<tr class="line">' .
|
||||
implode('</tr>'."\n".'<tr class="line">', $left_contents) .
|
||||
'</tr>' ."\n".
|
||||
'</table>' ."\n";
|
||||
|
||||
$inner_contents_right =
|
||||
'<table class="diff-contents">' ."\n".
|
||||
'<tr class="line">' .
|
||||
implode('</tr>'."\n".'<tr class="line">', $right_contents) .
|
||||
'</tr>' ."\n".
|
||||
'</table>' ."\n";
|
||||
|
||||
$out =
|
||||
'<table class="diff context">' ."\n".
|
||||
'<colgroup>' .
|
||||
'<col width="'.($leftwidth + 1).'" /><col width="*" />' .
|
||||
'<col width="'.($rightwidth + 1).'" /><col width="*" />' .
|
||||
'</colgroup>' ."\n".
|
||||
'<tr id="diff-'.md5($filename).'">'.
|
||||
'<th colspan="4">'.Pluf_esc($filename).'</th>'.
|
||||
'</tr>' ."\n".
|
||||
'<tr>' .
|
||||
'<th colspan="2">'.__('Old').'</th><th colspan="2">'.__('New').'</th>' .
|
||||
'</tr>'.
|
||||
'<tr>' .
|
||||
'<td>'. $inner_linecounts_left .'</td>'. "\n".
|
||||
'<td><div class="scroll">'. $inner_contents_left .'</div></td>'. "\n".
|
||||
'<td>'. $inner_linecounts_right .'</td>'. "\n".
|
||||
'<td><div class="scroll">'. $inner_contents_right .'</div></td>'. "\n".
|
||||
'</tr>' ."\n".
|
||||
'</table>' ."\n";
|
||||
|
||||
return Pluf_Template::markSafe($out);
|
||||
}
|
||||
}
|
||||
|
@@ -65,9 +65,9 @@ class IDF_FileUtil
|
||||
}
|
||||
$table = array();
|
||||
$i = 1;
|
||||
foreach (preg_split("/\015\012|\015|\012/", $content) as $line) {
|
||||
foreach (self::splitIntoLines($content) as $line) {
|
||||
$table[] = '<tr class="c-line"><td class="code-lc" id="L'.$i.'"><a href="#L'.$i.'">'.$i.'</a></td>'
|
||||
.'<td class="code mono'.$pretty.'">'.IDF_Diff::padLine(Pluf_esc($line)).'</td></tr>';
|
||||
.'<td class="code mono'.$pretty.'">'.self::emphasizeControlCharacters(Pluf_esc($line)).'</td></tr>';
|
||||
$i++;
|
||||
}
|
||||
return Pluf_Template::markSafe(implode("\n", $table));
|
||||
@@ -143,6 +143,53 @@ class IDF_FileUtil
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits a string into separate lines while retaining the individual
|
||||
* line ending character for every line.
|
||||
*
|
||||
* OS 9 line endings are not supported.
|
||||
*
|
||||
* @param string content
|
||||
* @param boolean if true, skip completely empty lines
|
||||
* @return string
|
||||
*/
|
||||
public static function splitIntoLines($content, $skipEmpty = false)
|
||||
{
|
||||
$last_off = 0;
|
||||
$lines = array();
|
||||
while (preg_match("/\r\n|\n/", $content, $m, PREG_OFFSET_CAPTURE, $last_off)) {
|
||||
$next_off = strlen($m[0][0]) + $m[0][1];
|
||||
$line = substr($content, $last_off, $next_off - $last_off);
|
||||
$last_off = $next_off;
|
||||
if ($line !== $m[0][0] || !$skipEmpty) $lines[] = $line;
|
||||
}
|
||||
$line = substr($content, $last_off);
|
||||
if ($line !== false && strlen($line) > 0) $lines[] = $line;
|
||||
return $lines;
|
||||
}
|
||||
|
||||
/**
|
||||
* This translates most of the C0 ASCII control characters into
|
||||
* their visual counterparts in the 0x24## unicode plane
|
||||
* (http://en.wikipedia.org/wiki/C0_and_C1_control_codes).
|
||||
*
|
||||
* We could add DEL (0x7F) to this set, but unfortunately this
|
||||
* is not nicely mapped to 0x247F in the control plane, but 0x2421
|
||||
* and adding an if expression below just for this is a little bit
|
||||
* of a hassle. And of course, the more esoteric ones from C1 are
|
||||
* missing as well...
|
||||
*
|
||||
* @param string $content
|
||||
* @return string
|
||||
*/
|
||||
public static function emphasizeControlCharacters($content)
|
||||
{
|
||||
return preg_replace(
|
||||
'/([\x00-\x1F])/ue',
|
||||
'"<span class=\"ctrl-char\" title=\"0x".bin2hex("\\1")."\">$".bin2hex("\\1")."</span>"',
|
||||
$content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find if a given mime type is a text file.
|
||||
* This uses the output of the self::getMimeType function.
|
||||
|
70
src/IDF/Forge.php
Normal file
70
src/IDF/Forge.php
Normal file
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/*
|
||||
# ***** BEGIN LICENSE BLOCK *****
|
||||
# This file is part of InDefero, an open source project management application.
|
||||
# Copyright (C) 2008-2011 Céondo Ltd and contributors.
|
||||
#
|
||||
# InDefero 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.
|
||||
#
|
||||
# InDefero is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
#
|
||||
# ***** END LICENSE BLOCK ***** */
|
||||
|
||||
/**
|
||||
* A lightweight model for the singleton forge entity
|
||||
*/
|
||||
class IDF_Forge
|
||||
{
|
||||
public $_model = __CLASS__;
|
||||
|
||||
public $id = 1;
|
||||
|
||||
/**
|
||||
* @var IDF_Gconf
|
||||
*/
|
||||
private $conf;
|
||||
|
||||
private function __construct() {
|
||||
$this->conf = new IDF_Gconf();
|
||||
$this->conf->setModel($this);
|
||||
}
|
||||
|
||||
public static function instance() {
|
||||
return new IDF_Forge();
|
||||
}
|
||||
|
||||
public function getProjectLabels($default = '') {
|
||||
return $this->conf->getVal('project_labels', $default);
|
||||
}
|
||||
|
||||
public function setProjectLabels($labels) {
|
||||
$this->conf->setVal('project_labels', $labels);
|
||||
}
|
||||
|
||||
public function setCustomForgePageEnabled($enabled) {
|
||||
$this->conf->setVal('custom_forge_page_enabled', $enabled);
|
||||
}
|
||||
|
||||
public function isCustomForgePageEnabled($default = false) {
|
||||
return $this->conf->getVal('custom_forge_page_enabled', $default);
|
||||
}
|
||||
|
||||
public function getCustomForgePageContent($default = '') {
|
||||
return $this->conf->getVal('custom_forge_page_content', $default);
|
||||
}
|
||||
|
||||
public function setCustomForgePageContent($content) {
|
||||
$this->conf->setVal('custom_forge_page_content', $content);
|
||||
}
|
||||
}
|
47
src/IDF/Form/Admin/ForgeConf.php
Normal file
47
src/IDF/Form/Admin/ForgeConf.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/*
|
||||
# ***** BEGIN LICENSE BLOCK *****
|
||||
# This file is part of InDefero, an open source project management application.
|
||||
# Copyright(C) 2008-2011 Céondo Ltd and contributors.
|
||||
#
|
||||
# InDefero 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.
|
||||
#
|
||||
# InDefero is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
#
|
||||
# ***** END LICENSE BLOCK ***** */
|
||||
|
||||
|
||||
/**
|
||||
* Configuration of the forge's start page.
|
||||
*/
|
||||
class IDF_Form_Admin_ForgeConf extends Pluf_Form
|
||||
{
|
||||
public function initFields($extra=array())
|
||||
{
|
||||
$this->fields['enabled'] = new Pluf_Form_Field_Boolean(
|
||||
array('required' => false,
|
||||
'label' => __('Custom forge page enabled'),
|
||||
'widget' => 'Pluf_Form_Widget_CheckboxInput',
|
||||
));
|
||||
$this->fields['content'] = new Pluf_Form_Field_Varchar(
|
||||
array('required' => true,
|
||||
'label' => __('Content'),
|
||||
'widget' => 'Pluf_Form_Widget_TextareaInput',
|
||||
'widget_attrs' => array(
|
||||
'cols' => 68,
|
||||
'rows' => 26,
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
62
src/IDF/Form/Admin/LabelConf.php
Normal file
62
src/IDF/Form/Admin/LabelConf.php
Normal file
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/*
|
||||
# ***** BEGIN LICENSE BLOCK *****
|
||||
# This file is part of InDefero, an open source project management application.
|
||||
# Copyright(C) 2008-2011 Céondo Ltd and contributors.
|
||||
#
|
||||
# InDefero 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.
|
||||
#
|
||||
# InDefero is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
#
|
||||
# ***** END LICENSE BLOCK ***** */
|
||||
|
||||
|
||||
/**
|
||||
* Configuration of forge labels.
|
||||
*/
|
||||
class IDF_Form_Admin_LabelConf extends Pluf_Form
|
||||
{
|
||||
const init_project_labels = 'UI:GUI = Applications with graphical user interfaces
|
||||
UI:CLI = Applications with no graphical user interfaces
|
||||
License:BSD = Applications with BSD license
|
||||
License:GPL = Applications with GPL license
|
||||
';
|
||||
|
||||
public function initFields($extra=array())
|
||||
{
|
||||
$this->fields['project_labels'] = new Pluf_Form_Field_Varchar(
|
||||
array('required' => true,
|
||||
'label' => __('Predefined project labels'),
|
||||
'initial' => self::init_project_labels,
|
||||
'widget_attrs' => array('rows' => 13,
|
||||
'cols' => 75),
|
||||
'widget' => 'Pluf_Form_Widget_TextareaInput',
|
||||
));
|
||||
}
|
||||
|
||||
public function clean_project_labels()
|
||||
{
|
||||
$labels = preg_split("/\s*\n\s*/", $this->cleaned_data['project_labels'], -1, PREG_SPLIT_NO_EMPTY);
|
||||
for ($i=0; $i<count($labels); ++$i) {
|
||||
$labels[$i] = trim($labels[$i]);
|
||||
if (!preg_match('/^[\w-]+(:[\w-]+)?(\s*=\s*[^=]+)?$/', $labels[$i])) {
|
||||
throw new Pluf_Form_Invalid(sprintf(
|
||||
__('The label "%s" is invalid: A label must only consist of alphanumeric '.
|
||||
'characters and dashes, and can optionally contain a ":" with a group prefix.'),
|
||||
$labels[$i]));
|
||||
}
|
||||
}
|
||||
return implode("\n", $labels);
|
||||
}
|
||||
}
|
@@ -72,6 +72,13 @@ class IDF_Form_Admin_ProjectCreate extends Pluf_Form
|
||||
'widget_attrs' => array('size' => '35'),
|
||||
));
|
||||
|
||||
$this->fields['external_project_url'] = new Pluf_Form_Field_Varchar(
|
||||
array('required' => false,
|
||||
'label' => __('External URL'),
|
||||
'widget_attrs' => array('size' => '35'),
|
||||
'initial' => '',
|
||||
));
|
||||
|
||||
$this->fields['scm'] = new Pluf_Form_Field_Varchar(
|
||||
array('required' => true,
|
||||
'label' => __('Repository type'),
|
||||
@@ -127,6 +134,18 @@ class IDF_Form_Admin_ProjectCreate extends Pluf_Form
|
||||
'widget' => 'Pluf_Form_Widget_TextareaInput',
|
||||
));
|
||||
|
||||
for ($i=1;$i<7;$i++) {
|
||||
$this->fields['label'.$i] = new Pluf_Form_Field_Varchar(
|
||||
array('required' => false,
|
||||
'label' => __('Labels'),
|
||||
'initial' => '',
|
||||
'widget_attrs' => array(
|
||||
'maxlength' => 50,
|
||||
'size' => 20,
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
$projects = array('--' => '--');
|
||||
foreach (Pluf::factory('IDF_Project')->getList(array('order' => 'name ASC')) as $proj) {
|
||||
$projects[$proj->name] = $proj->shortname;
|
||||
@@ -199,7 +218,7 @@ class IDF_Form_Admin_ProjectCreate extends Pluf_Form
|
||||
$mtn_master_branch)) {
|
||||
throw new Pluf_Form_Invalid(__(
|
||||
'The master branch is empty or contains illegal characters, '.
|
||||
'please use only letters, digits, dashs and dots as separators.'
|
||||
'please use only letters, digits, dashes and dots as separators.'
|
||||
));
|
||||
}
|
||||
|
||||
@@ -235,6 +254,11 @@ class IDF_Form_Admin_ProjectCreate extends Pluf_Form
|
||||
return $shortname;
|
||||
}
|
||||
|
||||
public function clean_external_project_url()
|
||||
{
|
||||
return IDF_Form_ProjectConf::checkWebURL($this->cleaned_data['external_project_url']);
|
||||
}
|
||||
|
||||
public function clean()
|
||||
{
|
||||
if ($this->cleaned_data['scm'] != 'svn') {
|
||||
@@ -278,11 +302,29 @@ class IDF_Form_Admin_ProjectCreate extends Pluf_Form
|
||||
if (!$this->isValid()) {
|
||||
throw new Exception(__('Cannot save the model from an invalid form.'));
|
||||
}
|
||||
|
||||
// Add a tag for each label
|
||||
$tagids = array();
|
||||
for ($i=1;$i<7;$i++) {
|
||||
if (strlen($this->cleaned_data['label'.$i]) > 0) {
|
||||
if (strpos($this->cleaned_data['label'.$i], ':') !== false) {
|
||||
list($class, $name) = explode(':', $this->cleaned_data['label'.$i], 2);
|
||||
list($class, $name) = array(trim($class), trim($name));
|
||||
} else {
|
||||
$class = 'Other';
|
||||
$name = trim($this->cleaned_data['label'.$i]);
|
||||
}
|
||||
$tag = IDF_Tag::addGlobal($name, $class);
|
||||
$tagids[] = $tag->id;
|
||||
}
|
||||
}
|
||||
|
||||
$project = new IDF_Project();
|
||||
$project->name = $this->cleaned_data['name'];
|
||||
$project->shortname = $this->cleaned_data['shortname'];
|
||||
$project->shortdesc = $this->cleaned_data['shortdesc'];
|
||||
|
||||
$tagids = array();
|
||||
if ($this->cleaned_data['template'] != '--') {
|
||||
// Find the template project
|
||||
$sql = new Pluf_SQL('shortname=%s',
|
||||
@@ -290,52 +332,63 @@ class IDF_Form_Admin_ProjectCreate extends Pluf_Form
|
||||
$tmpl = Pluf::factory('IDF_Project')->getOne(array('filter' => $sql->gen()));
|
||||
$project->private = $tmpl->private;
|
||||
$project->description = $tmpl->description;
|
||||
|
||||
foreach ($tmpl->get_tags_list() as $tag) {
|
||||
$tagids[] = $tag->id;
|
||||
}
|
||||
} else {
|
||||
$project->private = $this->cleaned_data['private_project'];
|
||||
$project->description = __('Click on the Project Management tab to set the description of your project.');
|
||||
|
||||
// Add a tag for each label
|
||||
for ($i=1;$i<7;$i++) {
|
||||
if (strlen($this->cleaned_data['label'.$i]) > 0) {
|
||||
if (strpos($this->cleaned_data['label'.$i], ':') !== false) {
|
||||
list($class, $name) = explode(':', $this->cleaned_data['label'.$i], 2);
|
||||
list($class, $name) = array(trim($class), trim($name));
|
||||
} else {
|
||||
$class = 'Other';
|
||||
$name = trim($this->cleaned_data['label'.$i]);
|
||||
}
|
||||
$tag = IDF_Tag::addGlobal($name, $class);
|
||||
$tagids[] = $tag->id;
|
||||
}
|
||||
}
|
||||
}
|
||||
$project->create();
|
||||
$project->batchAssoc('IDF_Tag', $tagids);
|
||||
|
||||
$conf = new IDF_Conf();
|
||||
$conf->setProject($project);
|
||||
$keys = array('scm', 'svn_remote_url', 'svn_username',
|
||||
'svn_password', 'mtn_master_branch');
|
||||
foreach ($keys as $key) {
|
||||
$this->cleaned_data[$key] = (!empty($this->cleaned_data[$key])) ?
|
||||
$this->cleaned_data[$key] : '';
|
||||
$conf->setVal($key, $this->cleaned_data[$key]);
|
||||
}
|
||||
|
||||
if ($this->cleaned_data['template'] != '--') {
|
||||
$tmplconf = new IDF_Conf();
|
||||
$tmplconf->setProject($tmpl);
|
||||
// We need to get all the configuration variables we want from
|
||||
// the old project and put them into the new project.
|
||||
$props = array(
|
||||
'labels_download_predefined' => IDF_Form_UploadConf::init_predefined,
|
||||
'labels_download_one_max' => IDF_Form_UploadConf::init_one_max,
|
||||
'labels_wiki_predefined' => IDF_Form_WikiConf::init_predefined,
|
||||
'labels_wiki_one_max' => IDF_Form_WikiConf::init_one_max,
|
||||
'labels_issue_template' => IDF_Form_IssueTrackingConf::init_template,
|
||||
'labels_issue_open' => IDF_Form_IssueTrackingConf::init_open,
|
||||
'labels_issue_closed' => IDF_Form_IssueTrackingConf::init_closed,
|
||||
'labels_issue_predefined' => IDF_Form_IssueTrackingConf::init_predefined,
|
||||
'labels_issue_one_max' => IDF_Form_IssueTrackingConf::init_one_max,
|
||||
'issue_relations' => IDF_Form_IssueTrackingConf::init_relations,
|
||||
'webhook_url' => '',
|
||||
'downloads_access_rights' => 'all',
|
||||
'review_access_rights' => 'all',
|
||||
'wiki_access_rights' => 'all',
|
||||
'source_access_rights' => 'all',
|
||||
'issues_access_rights' => 'all',
|
||||
'downloads_notification_email' => '',
|
||||
'review_notification_email' => '',
|
||||
'wiki_notification_email' => '',
|
||||
'source_notification_email' => '',
|
||||
'issues_notification_email' => '',
|
||||
);
|
||||
foreach ($props as $prop => $def) {
|
||||
$conf->setVal($prop, $tmplconf->getVal($prop, $def));
|
||||
|
||||
$allKeys = $tmplconf->getKeys();
|
||||
$scm = $this->cleaned_data['scm'];
|
||||
$ignoreKeys = array('scm', 'external_project_url', 'logo');
|
||||
|
||||
// copy over all existing variables, except scm-related data and
|
||||
// the project url / logo
|
||||
foreach ($allKeys as $key) {
|
||||
if (in_array($key, $ignoreKeys) || strpos($key, $scm.'_') === 0) {
|
||||
continue;
|
||||
}
|
||||
$conf->setVal($key, $tmplconf->getVal($key));
|
||||
}
|
||||
}
|
||||
|
||||
$keys = array(
|
||||
'scm', 'svn_remote_url', 'svn_username',
|
||||
'svn_password', 'mtn_master_branch',
|
||||
'external_project_url'
|
||||
);
|
||||
foreach ($keys as $key) {
|
||||
$this->cleaned_data[$key] = !empty($this->cleaned_data[$key]) ?
|
||||
$this->cleaned_data[$key] : '';
|
||||
$conf->setVal($key, $this->cleaned_data[$key]);
|
||||
}
|
||||
$project->created();
|
||||
|
||||
if ($this->cleaned_data['template'] == '--') {
|
||||
|
@@ -53,6 +53,13 @@ class IDF_Form_Admin_ProjectUpdate extends Pluf_Form
|
||||
'widget_attrs' => array('size' => '35'),
|
||||
));
|
||||
|
||||
$this->fields['external_project_url'] = new Pluf_Form_Field_Varchar(
|
||||
array('required' => false,
|
||||
'label' => __('External URL'),
|
||||
'widget_attrs' => array('size' => '35'),
|
||||
'initial' => $conf->getVal('external_project_url'),
|
||||
));
|
||||
|
||||
if ($this->project->getConf()->getVal('scm') == 'mtn') {
|
||||
$this->fields['mtn_master_branch'] = new Pluf_Form_Field_Varchar(
|
||||
array('required' => false,
|
||||
@@ -63,6 +70,26 @@ class IDF_Form_Admin_ProjectUpdate extends Pluf_Form
|
||||
));
|
||||
}
|
||||
|
||||
$tags = $this->project->get_tags_list();
|
||||
for ($i=1;$i<7;$i++) {
|
||||
$initial = '';
|
||||
if (isset($tags[$i-1])) {
|
||||
if ($tags[$i-1]->class != 'Other') {
|
||||
$initial = (string) $tags[$i-1];
|
||||
} else {
|
||||
$initial = $tags[$i-1]->name;
|
||||
}
|
||||
}
|
||||
$this->fields['label'.$i] = new Pluf_Form_Field_Varchar(
|
||||
array('required' => false,
|
||||
'label' => __('Labels'),
|
||||
'initial' => $initial,
|
||||
'widget_attrs' => array(
|
||||
'maxlength' => 50,
|
||||
'size' => 20,
|
||||
),
|
||||
));
|
||||
}
|
||||
$this->fields['owners'] = new Pluf_Form_Field_Varchar(
|
||||
array('required' => false,
|
||||
'label' => __('Project owners'),
|
||||
@@ -115,22 +142,52 @@ class IDF_Form_Admin_ProjectUpdate extends Pluf_Form
|
||||
return IDF_Form_MembersConf::checkBadLogins($this->cleaned_data['members']);
|
||||
}
|
||||
|
||||
public function clean_external_project_url()
|
||||
{
|
||||
return IDF_Form_ProjectConf::checkWebURL($this->cleaned_data['external_project_url']);
|
||||
}
|
||||
|
||||
public function save($commit=true)
|
||||
{
|
||||
if (!$this->isValid()) {
|
||||
throw new Exception(__('Cannot save the model from an invalid form.'));
|
||||
}
|
||||
|
||||
// Add a tag for each label
|
||||
$tagids = array();
|
||||
for ($i=1;$i<7;$i++) {
|
||||
if (strlen($this->cleaned_data['label'.$i]) > 0) {
|
||||
if (strpos($this->cleaned_data['label'.$i], ':') !== false) {
|
||||
list($class, $name) = explode(':', $this->cleaned_data['label'.$i], 2);
|
||||
list($class, $name) = array(trim($class), trim($name));
|
||||
} else {
|
||||
$class = 'Other';
|
||||
$name = trim($this->cleaned_data['label'.$i]);
|
||||
}
|
||||
$tag = IDF_Tag::addGlobal($name, $class);
|
||||
$tagids[] = $tag->id;
|
||||
}
|
||||
}
|
||||
$this->project->batchAssoc('IDF_Tag', $tagids);
|
||||
|
||||
IDF_Form_MembersConf::updateMemberships($this->project,
|
||||
$this->cleaned_data);
|
||||
$this->project->membershipsUpdated();
|
||||
|
||||
$this->project->name = $this->cleaned_data['name'];
|
||||
$this->project->shortdesc = $this->cleaned_data['shortdesc'];
|
||||
$this->project->update();
|
||||
|
||||
$keys = array('mtn_master_branch');
|
||||
$conf = $this->project->getConf();
|
||||
$keys = array('mtn_master_branch', 'external_project_url');
|
||||
foreach ($keys as $key) {
|
||||
if (!empty($this->cleaned_data[$key])) {
|
||||
$this->project->getConf()->setVal($key, $this->cleaned_data[$key]);
|
||||
if (array_key_exists($key, $this->cleaned_data)) {
|
||||
if (!empty($this->cleaned_data[$key])) {
|
||||
$conf->setVal($key, $this->cleaned_data[$key]);
|
||||
}
|
||||
else {
|
||||
$conf->delVal($key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -161,7 +161,7 @@ class IDF_Form_Admin_UserUpdate extends Pluf_Form
|
||||
'label' => __('Staff'),
|
||||
'initial' => $this->user->staff,
|
||||
'widget' => 'Pluf_Form_Widget_CheckboxInput',
|
||||
'help_text' => __('If you give staff rights to a user, you really need to trust them.'),
|
||||
'help_text' => __('If you give staff rights to a user, you really need to trust him.'),
|
||||
));
|
||||
}
|
||||
|
||||
|
@@ -36,6 +36,7 @@ class IDF_Form_IssueCreate extends Pluf_Form
|
||||
public $user = null;
|
||||
public $project = null;
|
||||
public $show_full = false;
|
||||
public $relation_types = null;
|
||||
|
||||
public function initFields($extra=array())
|
||||
{
|
||||
@@ -45,9 +46,12 @@ class IDF_Form_IssueCreate extends Pluf_Form
|
||||
or $this->user->hasPerm('IDF.project-member', $this->project)) {
|
||||
$this->show_full = true;
|
||||
}
|
||||
$this->relation_types = $this->project->getRelationsFromConfig();
|
||||
|
||||
$contentTemplate = $this->project->getConf()->getVal(
|
||||
'labels_issue_template', IDF_Form_IssueTrackingConf::init_template
|
||||
);
|
||||
|
||||
$this->fields['summary'] = new Pluf_Form_Field_Varchar(
|
||||
array('required' => true,
|
||||
'label' => __('Summary'),
|
||||
@@ -108,16 +112,21 @@ class IDF_Form_IssueCreate extends Pluf_Form
|
||||
'size' => 15,
|
||||
),
|
||||
));
|
||||
$this->fields['due_dtime'] = new Pluf_Form_Field_Date(
|
||||
array('required' => false,
|
||||
'label' => __('Due date'),
|
||||
'initial' => '',
|
||||
'widget_attrs' => array('size' => 15,),
|
||||
));
|
||||
|
||||
$relation_types = $extra['project']->getRelationsFromConfig();
|
||||
$this->fields['relation_type'] = new Pluf_Form_Field_Varchar(
|
||||
$this->fields['relation_type0'] = new Pluf_Form_Field_Varchar(
|
||||
array('required' => false,
|
||||
'label' => __('This issue'),
|
||||
'initial' => $relation_types[0],
|
||||
'initial' => current($this->relation_types),
|
||||
'widget_attrs' => array('size' => 15),
|
||||
));
|
||||
|
||||
$this->fields['relation_issue'] = new Pluf_Form_Field_Varchar(
|
||||
$this->fields['relation_issue0'] = new Pluf_Form_Field_Varchar(
|
||||
array('required' => false,
|
||||
'label' => null,
|
||||
'initial' => '',
|
||||
@@ -211,7 +220,7 @@ class IDF_Form_IssueCreate extends Pluf_Form
|
||||
else $count[$class] += 1;
|
||||
if (in_array($class, $onemax) and $count[$class] > 1) {
|
||||
if (!isset($this->errors['label'.$i])) $this->errors['label'.$i] = array();
|
||||
$this->errors['label'.$i][] = sprintf(__('You cannot provide more than label from the %s class to an issue.'), $class);
|
||||
$this->errors['label'.$i][] = sprintf(__('You cannot provide more than one label from the %s class to an issue.'), $class);
|
||||
throw new Pluf_Form_Invalid(__('You provided an invalid label.'));
|
||||
}
|
||||
}
|
||||
@@ -250,6 +259,63 @@ class IDF_Form_IssueCreate extends Pluf_Form
|
||||
return $this->cleaned_data['status'];
|
||||
}
|
||||
|
||||
// this method is not called from Pluf_Form directly, but shared for
|
||||
// among all similar fields
|
||||
function clean_relation_type($value)
|
||||
{
|
||||
$relation_type = trim($value);
|
||||
if (empty($relation_type))
|
||||
return '';
|
||||
|
||||
$found = false;
|
||||
foreach ($this->relation_types as $type) {
|
||||
if ($type == $relation_type) {
|
||||
$found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!$found) {
|
||||
throw new Pluf_Form_Invalid(__('You provided an invalid relation type.'));
|
||||
}
|
||||
return $relation_type;
|
||||
}
|
||||
|
||||
function clean_relation_type0()
|
||||
{
|
||||
return $this->clean_relation_type($this->cleaned_data['relation_type0']);
|
||||
}
|
||||
|
||||
// this method is not called from Pluf_Form directly, but shared for
|
||||
// among all similar fields
|
||||
function clean_relation_issue($value)
|
||||
{
|
||||
$issues = trim($value);
|
||||
if (empty($issues))
|
||||
return '';
|
||||
|
||||
$issue_ids = preg_split('/\s*,\s*/', $issues, -1, PREG_SPLIT_NO_EMPTY);
|
||||
foreach ($issue_ids as $issue_id) {
|
||||
if (!ctype_digit($issue_id) || (int)$issue_id < 1) {
|
||||
throw new Pluf_Form_Invalid(sprintf(
|
||||
__('The value "%s" is not a valid issue id.'), $issue_id
|
||||
));
|
||||
}
|
||||
$issue = new IDF_Issue($issue_id);
|
||||
if ($issue->id != $issue_id || $issue->project != $this->project->id) {
|
||||
throw new Pluf_Form_Invalid(sprintf(
|
||||
__('The issue "%s" does not exist.'), $issue_id
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
return implode(', ', $issue_ids);
|
||||
}
|
||||
|
||||
function clean_relation_issue0()
|
||||
{
|
||||
return $this->clean_relation_issue($this->cleaned_data['relation_issue0']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean the attachments post failure.
|
||||
*/
|
||||
@@ -308,11 +374,36 @@ class IDF_Form_IssueCreate extends Pluf_Form
|
||||
$issue->status = new IDF_Tag($_t[0]); // first one is the default
|
||||
$issue->owner = null;
|
||||
}
|
||||
$issue->due_dtime = $this->cleaned_data['due_dtime'];
|
||||
$issue->summary = trim($this->cleaned_data['summary']);
|
||||
$issue->create();
|
||||
foreach ($tags as $tag) {
|
||||
$issue->setAssoc($tag);
|
||||
}
|
||||
// add relations (if any)
|
||||
if (!empty($this->cleaned_data['relation_type0'])) {
|
||||
$verb = $this->cleaned_data['relation_type0'];
|
||||
$other_verb = $this->relation_types[$verb];
|
||||
$related_issues = preg_split('/\s*,\s*/', $this->cleaned_data['relation_issue0'], -1, PREG_SPLIT_NO_EMPTY);
|
||||
|
||||
foreach ($related_issues as $related_issue_id) {
|
||||
$related_issue = new IDF_Issue($related_issue_id);
|
||||
$rel = new IDF_IssueRelation();
|
||||
$rel->issue = $issue;
|
||||
$rel->verb = $verb;
|
||||
$rel->other_issue = $related_issue;
|
||||
$rel->submitter = $this->user;
|
||||
$rel->create();
|
||||
|
||||
$other_rel = new IDF_IssueRelation();
|
||||
$other_rel->issue = $related_issue;
|
||||
$other_rel->verb = $other_verb;
|
||||
$other_rel->other_issue = $issue;
|
||||
$other_rel->submitter = $this->user;
|
||||
$other_rel->create();
|
||||
}
|
||||
}
|
||||
|
||||
// add the first comment
|
||||
$comment = new IDF_IssueComment();
|
||||
$comment->issue = $issue;
|
||||
|
@@ -72,15 +72,29 @@ Performance = Performance issue
|
||||
Usability = Affects program usability
|
||||
Maintainability = Hinders future changes';
|
||||
const init_one_max = 'Type, Priority, Milestone';
|
||||
// ATTENTION: if you change something here, change the values below as well!
|
||||
const init_relations = 'is related to
|
||||
blocks, is blocked by
|
||||
duplicates, is duplicated by';
|
||||
|
||||
// These are actually all noop's, but we have no other chance to
|
||||
// tell IDF's translation mechanism to mark the strings as translatable
|
||||
// FIXME: IDF should get a internal translation system for strings like
|
||||
// that, that can also be easily expanded by users
|
||||
private function noop()
|
||||
{
|
||||
__('is related to');
|
||||
__('blocks');
|
||||
__('is blocked by');
|
||||
__('duplicates');
|
||||
__('is duplicated by');
|
||||
}
|
||||
|
||||
public function initFields($extra=array())
|
||||
{
|
||||
$this->fields['labels_issue_template'] = new Pluf_Form_Field_Varchar(
|
||||
array('required' => false,
|
||||
'label' => __('Define an issue template to hint to the reporter to provide certain information'),
|
||||
'label' => __('Define an issue template to hint the reporter to provide certain information'),
|
||||
'initial' => self::init_template,
|
||||
'widget_attrs' => array('rows' => 7,
|
||||
'cols' => 75),
|
||||
@@ -116,7 +130,7 @@ duplicates, is duplicated by';
|
||||
|
||||
$this->fields['labels_issue_one_max'] = new Pluf_Form_Field_Varchar(
|
||||
array('required' => false,
|
||||
'label' => __('Each issue may have at most one label with each of these classes'),
|
||||
'label' => __('Each issue may have at most one label with each of these classes.'),
|
||||
'initial' => self::init_one_max,
|
||||
'widget_attrs' => array('size' => 60),
|
||||
));
|
||||
@@ -125,7 +139,7 @@ duplicates, is duplicated by';
|
||||
array('required' => true,
|
||||
'label' => __('Issue relations'),
|
||||
'initial' => self::init_relations,
|
||||
'help_text' => __('You can define bidirectional relations like "is related to" or "blocks, is blocked by".'),
|
||||
'help_text' => __('You can define bidirectional relations like "is related to" or "blocks, is blocked by". For standard relations pre-configured translations exist, new relations should however be defined in a language that is understood by all project members.'),
|
||||
'widget_attrs' => array('rows' => 7,
|
||||
'cols' => 75),
|
||||
'widget' => 'Pluf_Form_Widget_TextareaInput',
|
||||
|
@@ -39,6 +39,7 @@ class IDF_Form_IssueUpdate extends IDF_Form_IssueCreate
|
||||
or $this->user->hasPerm('IDF.project-member', $this->project)) {
|
||||
$this->show_full = true;
|
||||
}
|
||||
$this->relation_types = $this->project->getRelationsFromConfig();
|
||||
if ($this->show_full) {
|
||||
$this->fields['summary'] = new Pluf_Form_Field_Varchar(
|
||||
array('required' => true,
|
||||
@@ -102,6 +103,59 @@ class IDF_Form_IssueUpdate extends IDF_Form_IssueCreate
|
||||
'size' => 15,
|
||||
),
|
||||
));
|
||||
$due_dtime = substr($this->issue->due_dtime, 0, 10);
|
||||
$this->fields['due_dtime'] = new Pluf_Form_Field_Date(
|
||||
array('required' => false,
|
||||
'label' => __('Due date'),
|
||||
'initial' => $due_dtime,
|
||||
'widget_attrs' => array('size' => 15,),
|
||||
));
|
||||
|
||||
$idx = 0;
|
||||
// note: clean_relation_type0 and clean_relation_issue0 already
|
||||
// exist in the base class
|
||||
$this->fields['relation_type'.$idx] = new Pluf_Form_Field_Varchar(
|
||||
array('required' => false,
|
||||
'label' => __('This issue'),
|
||||
'initial' => current($this->relation_types),
|
||||
'widget_attrs' => array('size' => 15),
|
||||
));
|
||||
|
||||
$this->fields['relation_issue'.$idx] = new Pluf_Form_Field_Varchar(
|
||||
array('required' => false,
|
||||
'label' => null,
|
||||
'initial' => '',
|
||||
'widget_attrs' => array('size' => 10),
|
||||
));
|
||||
|
||||
++$idx;
|
||||
$relatedIssues = $this->issue->getGroupedRelatedIssues(array(), true);
|
||||
foreach ($relatedIssues as $verb => $ids) {
|
||||
$this->fields['relation_type'.$idx] = new Pluf_Form_Field_Varchar(
|
||||
array('required' => false,
|
||||
'label' => __('This issue'),
|
||||
'initial' => $verb,
|
||||
'widget_attrs' => array('size' => 15),
|
||||
));
|
||||
$m = 'clean_relation_type'.$idx;
|
||||
$this->$m = create_function('$form', '
|
||||
return $form->clean_relation_type($form->cleaned_data["relation_type'.$idx.'"]);
|
||||
');
|
||||
|
||||
$this->fields['relation_issue'.$idx] = new Pluf_Form_Field_Varchar(
|
||||
array('required' => false,
|
||||
'label' => null,
|
||||
'initial' => implode(', ', $ids),
|
||||
'widget_attrs' => array('size' => 10),
|
||||
));
|
||||
$m = 'clean_relation_issue'.$idx;
|
||||
$this->$m = create_function('$form', '
|
||||
return $form->clean_relation_issue($form->cleaned_data["relation_issue'.$idx.'"]);
|
||||
');
|
||||
|
||||
++$idx;
|
||||
}
|
||||
|
||||
$tags = $this->issue->get_tags_list();
|
||||
for ($i=1;$i<7;$i++) {
|
||||
$initial = '';
|
||||
@@ -155,6 +209,51 @@ class IDF_Form_IssueUpdate extends IDF_Form_IssueCreate
|
||||
public function clean()
|
||||
{
|
||||
$this->cleaned_data = parent::clean();
|
||||
|
||||
// normalize the user's input by removing dublettes and by combining
|
||||
// ids from identical verbs in different input fields into one array
|
||||
$normRelatedIssues = array();
|
||||
for ($idx = 0; isset($this->cleaned_data['relation_type'.$idx]); ++$idx) {
|
||||
$verb = $this->cleaned_data['relation_type'.$idx];
|
||||
if (empty($verb))
|
||||
continue;
|
||||
|
||||
$ids = preg_split('/\s*,\s*/', $this->cleaned_data['relation_issue'.$idx],
|
||||
-1, PREG_SPLIT_NO_EMPTY);
|
||||
if (count($ids) == 0)
|
||||
continue;
|
||||
|
||||
if (!array_key_exists($verb, $normRelatedIssues))
|
||||
$normRelatedIssues[$verb] = array();
|
||||
foreach ($ids as $id) {
|
||||
if (!in_array($id, $normRelatedIssues[$verb]))
|
||||
$normRelatedIssues[$verb][] = $id;
|
||||
}
|
||||
}
|
||||
|
||||
// now look at any added / removed ids
|
||||
$added = $removed = array();
|
||||
$relatedIssues = $this->issue->getGroupedRelatedIssues(array(), true);
|
||||
$added = array_diff_key($normRelatedIssues, $relatedIssues);
|
||||
$removed = array_diff_key($relatedIssues, $normRelatedIssues);
|
||||
|
||||
$keysToLookAt = array_keys(
|
||||
array_intersect_key($relatedIssues, $normRelatedIssues)
|
||||
);
|
||||
foreach ($keysToLookAt as $key) {
|
||||
$a = array_diff($normRelatedIssues[$key], $relatedIssues[$key]);
|
||||
if (count($a) > 0)
|
||||
$added[$key] = $a;
|
||||
$r = array_diff($relatedIssues[$key], $normRelatedIssues[$key]);
|
||||
if (count($r) > 0)
|
||||
$removed[$key] = $r;
|
||||
}
|
||||
|
||||
// cache the added / removed data, so we do not have to
|
||||
// calculate that again
|
||||
$this->cleaned_data['_added_issue_relations'] = $added;
|
||||
$this->cleaned_data['_removed_issue_relations'] = $removed;
|
||||
|
||||
// As soon as we know that at least one change was done, we
|
||||
// return the cleaned data and do not go further.
|
||||
if (strlen(trim($this->cleaned_data['content']))) {
|
||||
@@ -174,6 +273,9 @@ class IDF_Form_IssueUpdate extends IDF_Form_IssueCreate
|
||||
or ((!is_null($owner) and !is_null($this->issue->get_owner())) and $owner->id != $this->issue->get_owner()->id)) {
|
||||
return $this->cleaned_data;
|
||||
}
|
||||
if (trim($this->issue->due_dtime) != trim($this->cleaned_data['due_dtime'])) {
|
||||
return $this->cleaned_data;
|
||||
}
|
||||
$tags = array();
|
||||
for ($i=1;$i<7;$i++) {
|
||||
if (strlen($this->cleaned_data['label'.$i]) > 0) {
|
||||
@@ -214,6 +316,11 @@ class IDF_Form_IssueUpdate extends IDF_Form_IssueCreate
|
||||
return $this->cleaned_data;
|
||||
}
|
||||
}
|
||||
|
||||
if (count($this->cleaned_data['_added_issue_relations']) != 0 ||
|
||||
count($this->cleaned_data['_removed_issue_relations']) != 0) {
|
||||
return $this->cleaned_data;
|
||||
}
|
||||
}
|
||||
// no changes!
|
||||
throw new Pluf_Form_Invalid(__('No changes were entered.'));
|
||||
@@ -255,20 +362,22 @@ class IDF_Form_IssueUpdate extends IDF_Form_IssueCreate
|
||||
foreach ($tags as $tag) {
|
||||
if (!Pluf_Model_InArray($tag, $oldtags)) {
|
||||
if (!isset($changes['lb'])) $changes['lb'] = array();
|
||||
if (!isset($changes['lb']['add'])) $changes['lb']['add'] = array();
|
||||
if ($tag->class != 'Other') {
|
||||
$changes['lb'][] = (string) $tag; //new tag
|
||||
$changes['lb']['add'][] = (string) $tag; //new tag
|
||||
} else {
|
||||
$changes['lb'][] = (string) $tag->name;
|
||||
$changes['lb']['add'][] = (string) $tag->name;
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach ($oldtags as $tag) {
|
||||
if (!Pluf_Model_InArray($tag, $tags)) {
|
||||
if (!isset($changes['lb'])) $changes['lb'] = array();
|
||||
if (!isset($changes['lb']['rem'])) $changes['lb']['rem'] = array();
|
||||
if ($tag->class != 'Other') {
|
||||
$changes['lb'][] = '-'.(string) $tag; //new tag
|
||||
$changes['lb']['rem'][] = (string) $tag; //new tag
|
||||
} else {
|
||||
$changes['lb'][] = '-'.(string) $tag->name;
|
||||
$changes['lb']['rem'][] = (string) $tag->name;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -286,11 +395,56 @@ class IDF_Form_IssueUpdate extends IDF_Form_IssueCreate
|
||||
or ((!is_null($owner) and !is_null($this->issue->get_owner())) and $owner->id != $this->issue->get_owner()->id)) {
|
||||
$changes['ow'] = (is_null($owner)) ? '---' : $owner->login;
|
||||
}
|
||||
if (trim($this->issue->due_dtime) != trim($this->cleaned_data['due_dtime'])) {
|
||||
$changes['du'] = Pluf_Template_dateFormat($this->cleaned_data['due_dtime']);
|
||||
}
|
||||
// Issue relations - additions
|
||||
foreach ($this->cleaned_data['_added_issue_relations'] as $verb => $ids) {
|
||||
$other_verb = $this->relation_types[$verb];
|
||||
foreach ($ids as $id) {
|
||||
$related_issue = new IDF_Issue($id);
|
||||
$rel = new IDF_IssueRelation();
|
||||
$rel->issue = $this->issue;
|
||||
$rel->verb = $verb;
|
||||
$rel->other_issue = $related_issue;
|
||||
$rel->submitter = $this->user;
|
||||
$rel->create();
|
||||
|
||||
$other_rel = new IDF_IssueRelation();
|
||||
$other_rel->issue = $related_issue;
|
||||
$other_rel->verb = $other_verb;
|
||||
$other_rel->other_issue = $this->issue;
|
||||
$other_rel->submitter = $this->user;
|
||||
$other_rel->create();
|
||||
}
|
||||
if (!isset($changes['rel'])) $changes['rel'] = array();
|
||||
if (!isset($changes['rel']['add'])) $changes['rel']['add'] = array();
|
||||
$changes['rel']['add'][] = $verb.' '.implode(', ', $ids);
|
||||
}
|
||||
// Issue relations - removals
|
||||
foreach ($this->cleaned_data['_removed_issue_relations'] as $verb => $ids) {
|
||||
foreach ($ids as $id) {
|
||||
$db = &Pluf::db();
|
||||
$table = Pluf::factory('IDF_IssueRelation')->getSqlTable();
|
||||
$sql = new Pluf_SQL('verb=%s AND (
|
||||
(issue=%s AND other_issue=%s) OR
|
||||
(other_issue=%s AND issue=%s))',
|
||||
array($verb,
|
||||
$this->issue->id, $id,
|
||||
$this->issue->id, $id));
|
||||
$db->execute('DELETE FROM '.$table.' WHERE '.$sql->gen());
|
||||
}
|
||||
|
||||
if (!isset($changes['rel'])) $changes['rel'] = array();
|
||||
if (!isset($changes['rel']['rem'])) $changes['rel']['rem'] = array();
|
||||
$changes['rel']['rem'][] = $verb.' '.implode(', ', $ids);
|
||||
}
|
||||
// Update the issue
|
||||
$this->issue->batchAssoc('IDF_Tag', $tagids);
|
||||
$this->issue->summary = trim($this->cleaned_data['summary']);
|
||||
$this->issue->status = $status;
|
||||
$this->issue->owner = $owner;
|
||||
$this->issue->due_dtime = $this->cleaned_data['due_dtime'];
|
||||
}
|
||||
// Create the comment
|
||||
$comment = new IDF_IssueComment();
|
||||
|
@@ -32,6 +32,7 @@ class IDF_Form_ProjectConf extends Pluf_Form
|
||||
public function initFields($extra=array())
|
||||
{
|
||||
$this->project = $extra['project'];
|
||||
$conf = $this->project->getConf();
|
||||
|
||||
// Basic part
|
||||
$this->fields['name'] = new Pluf_Form_Field_Varchar(array('required' => true,
|
||||
@@ -51,6 +52,32 @@ class IDF_Form_ProjectConf extends Pluf_Form
|
||||
),
|
||||
'widget' => 'Pluf_Form_Widget_TextareaInput',
|
||||
));
|
||||
$this->fields['external_project_url'] = new Pluf_Form_Field_Varchar(array('required' => false,
|
||||
'label' => __('External URL'),
|
||||
'widget_attrs' => array('size' => '68'),
|
||||
'initial' => $conf->getVal('external_project_url'),
|
||||
));
|
||||
|
||||
$tags = $this->project->get_tags_list();
|
||||
for ($i=1;$i<7;$i++) {
|
||||
$initial = '';
|
||||
if (isset($tags[$i-1])) {
|
||||
if ($tags[$i-1]->class != 'Other') {
|
||||
$initial = (string) $tags[$i-1];
|
||||
} else {
|
||||
$initial = $tags[$i-1]->name;
|
||||
}
|
||||
}
|
||||
$this->fields['label'.$i] = new Pluf_Form_Field_Varchar(
|
||||
array('required' => false,
|
||||
'label' => __('Labels'),
|
||||
'initial' => $initial,
|
||||
'widget_attrs' => array(
|
||||
'maxlength' => 50,
|
||||
'size' => 20,
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
// Logo part
|
||||
$upload_path = Pluf::f('upload_path', false);
|
||||
@@ -67,6 +94,7 @@ class IDF_Form_ProjectConf extends Pluf_Form
|
||||
'move_function_params' =>
|
||||
array('upload_path' => $upload_path,
|
||||
'upload_path_create' => true,
|
||||
'upload_overwrite' => true,
|
||||
'file_name' => $filename,
|
||||
)
|
||||
));
|
||||
@@ -118,20 +146,63 @@ class IDF_Form_ProjectConf extends Pluf_Form
|
||||
return $this->cleaned_data['logo'];
|
||||
}
|
||||
|
||||
public function clean_external_project_url()
|
||||
{
|
||||
return self::checkWebURL($this->cleaned_data['external_project_url']);
|
||||
}
|
||||
|
||||
public static function checkWebURL($url)
|
||||
{
|
||||
$url = trim($url);
|
||||
if (empty($url)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$parsed = parse_url($url);
|
||||
if ($parsed === false || !array_key_exists('scheme', $parsed) ||
|
||||
($parsed['scheme'] != 'http' && $parsed['scheme'] != 'https')) {
|
||||
throw new Pluf_Form_Invalid(__('The entered URL is invalid. Only http and https URLs are allowed.'));
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
public function save($commit=true)
|
||||
{
|
||||
$conf = $this->project->getConf();
|
||||
// Add a tag for each label
|
||||
$tagids = array();
|
||||
for ($i=1;$i<7;$i++) {
|
||||
if (strlen($this->cleaned_data['label'.$i]) > 0) {
|
||||
if (strpos($this->cleaned_data['label'.$i], ':') !== false) {
|
||||
list($class, $name) = explode(':', $this->cleaned_data['label'.$i], 2);
|
||||
list($class, $name) = array(trim($class), trim($name));
|
||||
} else {
|
||||
$class = 'Other';
|
||||
$name = trim($this->cleaned_data['label'.$i]);
|
||||
}
|
||||
$tag = IDF_Tag::addGlobal($name, $class);
|
||||
$tagids[] = $tag->id;
|
||||
}
|
||||
}
|
||||
|
||||
// Basic part
|
||||
$this->project->name = $this->cleaned_data['name'];
|
||||
$this->project->shortdesc = $this->cleaned_data['shortdesc'];
|
||||
$this->project->description = $this->cleaned_data['description'];
|
||||
$this->project->batchAssoc('IDF_Tag', $tagids);
|
||||
$this->project->update();
|
||||
|
||||
// Logo part
|
||||
if ($this->cleaned_data['logo'] !== "") {
|
||||
$conf = $this->project->getConf();
|
||||
if (!empty($this->cleaned_data['logo'])) {
|
||||
$conf->setVal('logo', $this->cleaned_data['logo']);
|
||||
}
|
||||
if (!empty($this->cleaned_data['external_project_url'])) {
|
||||
$conf->setVal('external_project_url', $this->cleaned_data['external_project_url']);
|
||||
}
|
||||
else {
|
||||
$conf->delVal('external_project_url');
|
||||
}
|
||||
|
||||
if ($this->cleaned_data['logo_remove'] === true) {
|
||||
@unlink(Pluf::f('upload_path') . '/' . $this->project->shortname . $conf->getVal('logo'));
|
||||
$conf->delVal('logo');
|
||||
|
@@ -94,7 +94,7 @@ class IDF_Form_Register extends Pluf_Form
|
||||
{
|
||||
$this->cleaned_data['email'] = mb_strtolower(trim($this->cleaned_data['email']));
|
||||
if (Pluf::factory('IDF_EmailAddress')->get_user_for_email_address($this->cleaned_data['email']) != null) {
|
||||
throw new Pluf_Form_Invalid(sprintf(__('The email "%s" is already used. If you need to, click on the help link to recover your password.'), $this->cleaned_data['email']));
|
||||
throw new Pluf_Form_Invalid(sprintf(__('The email "%1$s" is already used. If you need to, you can <a href="%2$s">recover your password</a>.'), $this->cleaned_data['email'], Pluf_HTTP_URL_urlForView('IDF_Views::passwordRecoveryAsk')));
|
||||
}
|
||||
return $this->cleaned_data['email'];
|
||||
}
|
||||
|
@@ -107,7 +107,7 @@ class IDF_Form_RegisterConfirmation extends Pluf_Form
|
||||
throw new Pluf_Form_Invalid($error);
|
||||
}
|
||||
if ($users[0]->active) {
|
||||
throw new Pluf_Form_Invalid(__('This account has already been confirmed. Maybe should you try to recover your password using the help link.'));
|
||||
throw new Pluf_Form_Invalid(sprintf(__('This account has already been confirmed. Maybe should you try to <a href="%s">recover your password</a>.'), Pluf_HTTP_URL_urlForView('IDF_Views::passwordRecoveryAsk')));
|
||||
}
|
||||
$this->_user_id = $email_id[1];
|
||||
return $this->cleaned_data['key'];
|
||||
|
@@ -49,13 +49,10 @@ class IDF_Form_SourceConf extends Pluf_Form
|
||||
'widget' => 'Pluf_Form_Widget_PasswordInput',
|
||||
));
|
||||
}
|
||||
Pluf::loadFunction('Pluf_HTTP_URL_urlForView');
|
||||
$url = Pluf_HTTP_URL_urlForView('idf_faq').'#webhooks';
|
||||
$this->fields['webhook_url'] = new Pluf_Form_Field_Url(
|
||||
array('required' => false,
|
||||
'label' => __('Webhook URL'),
|
||||
'initial' => $this->conf->getVal('webhook_url', ''),
|
||||
'help_text' => sprintf(__('Learn more about the <a href="%s">post-commit web hooks</a>.'), $url),
|
||||
'widget_attrs' => array('size' => 35),
|
||||
));
|
||||
|
||||
|
@@ -57,20 +57,44 @@ class IDF_Form_TabsConf extends Pluf_Form
|
||||
'widget' => 'Pluf_Form_Widget_SelectInput',
|
||||
));
|
||||
}
|
||||
$ak = array('downloads_notification_email',
|
||||
'review_notification_email',
|
||||
'wiki_notification_email',
|
||||
'source_notification_email',
|
||||
'issues_notification_email',);
|
||||
foreach ($ak as $key) {
|
||||
$this->fields[$key] = new IDF_Form_Field_EmailList(
|
||||
array('required' => false,
|
||||
'label' => $key,
|
||||
'initial' => $this->conf->getVal($key, ''),
|
||||
'widget_attrs' => array('size' => 40),
|
||||
));
|
||||
}
|
||||
|
||||
$sections = array(
|
||||
'downloads_notification',
|
||||
'review_notification',
|
||||
'wiki_notification',
|
||||
'source_notification',
|
||||
'issues_notification',
|
||||
);
|
||||
|
||||
foreach ($sections as $section) {
|
||||
$this->fields[$section.'_owners_enabled'] = new Pluf_Form_Field_Boolean(
|
||||
array('required' => false,
|
||||
'label' => __('Project owners'),
|
||||
'initial' => $this->conf->getVal($section.'_owners_enabled', false),
|
||||
'widget' => 'Pluf_Form_Widget_CheckboxInput',
|
||||
));
|
||||
$this->fields[$section.'_members_enabled'] = new Pluf_Form_Field_Boolean(
|
||||
array('required' => false,
|
||||
'label' => __('Project members'),
|
||||
'initial' => $this->conf->getVal($section.'_members_enabled', false),
|
||||
'widget' => 'Pluf_Form_Widget_CheckboxInput',
|
||||
));
|
||||
$this->fields[$section.'_email_enabled'] = new Pluf_Form_Field_Boolean(
|
||||
array('required' => false,
|
||||
'label' => __('Others'),
|
||||
'initial' => $this->conf->getVal($section.'_email_enabled', false),
|
||||
'widget' => 'Pluf_Form_Widget_CheckboxInput',
|
||||
));
|
||||
if ($this->conf->getVal($section.'_email_enabled', false)) {
|
||||
$attrs['readonly'] = 'readonly';
|
||||
}
|
||||
$this->fields[$section.'_email'] = new IDF_Form_Field_EmailList(
|
||||
array('required' => false,
|
||||
'label' => null,
|
||||
'initial' => $this->conf->getVal($section.'_email', ''),
|
||||
'widget_attrs' => array('size' => 20),
|
||||
));
|
||||
}
|
||||
|
||||
$this->fields['private_project'] = new Pluf_Form_Field_Boolean(
|
||||
array('required' => false,
|
||||
|
@@ -106,7 +106,7 @@ class IDF_Form_UpdateUpload extends Pluf_Form
|
||||
else $count[$class] += 1;
|
||||
if (in_array($class, $onemax) and $count[$class] > 1) {
|
||||
if (!isset($this->errors['label'.$i])) $this->errors['label'.$i] = array();
|
||||
$this->errors['label'.$i][] = sprintf(__('You cannot provide more than label from the %s class to an issue.'), $class);
|
||||
$this->errors['label'.$i][] = sprintf(__('You cannot provide more than one label from the %s class to an issue.'), $class);
|
||||
throw new Pluf_Form_Invalid(__('You provided an invalid label.'));
|
||||
}
|
||||
}
|
||||
@@ -146,6 +146,9 @@ class IDF_Form_UpdateUpload extends Pluf_Form
|
||||
$this->upload->modif_dtime = gmdate('Y-m-d H:i:s');
|
||||
$this->upload->update();
|
||||
$this->upload->batchAssoc('IDF_Tag', $tags);
|
||||
|
||||
// Send the notification
|
||||
$this->upload->notify($this->project->getConf(), false);
|
||||
/**
|
||||
* [signal]
|
||||
*
|
||||
|
@@ -79,6 +79,7 @@ class IDF_Form_Upload extends Pluf_Form
|
||||
|
||||
public function clean_file()
|
||||
{
|
||||
// FIXME: we do the same in IDF_Form_WikiResourceCreate and a couple of other places as well
|
||||
$extra = strtolower(implode('|', explode(' ', Pluf::f('idf_extra_upload_ext'))));
|
||||
if (strlen($extra)) $extra .= '|';
|
||||
if (!preg_match('/\.('.$extra.'png|jpg|jpeg|gif|bmp|psd|tif|aiff|asf|avi|bz2|css|doc|eps|gz|jar|mdtext|mid|mov|mp3|mpg|ogg|pdf|ppt|ps|qt|ra|ram|rm|rtf|sdd|sdw|sit|sxi|sxw|swf|tgz|txt|wav|xls|xml|war|wmv|zip)$/i', $this->cleaned_data['file'])) {
|
||||
@@ -116,7 +117,7 @@ class IDF_Form_Upload extends Pluf_Form
|
||||
else $count[$class] += 1;
|
||||
if (in_array($class, $onemax) and $count[$class] > 1) {
|
||||
if (!isset($this->errors['label'.$i])) $this->errors['label'.$i] = array();
|
||||
$this->errors['label'.$i][] = sprintf(__('You cannot provide more than label from the %s class to an issue.'), $class);
|
||||
$this->errors['label'.$i][] = sprintf(__('You cannot provide more than one label from the %s class to a download.'), $class);
|
||||
throw new Pluf_Form_Invalid(__('You provided an invalid label.'));
|
||||
}
|
||||
}
|
||||
|
227
src/IDF/Form/UploadArchive.php
Normal file
227
src/IDF/Form/UploadArchive.php
Normal file
@@ -0,0 +1,227 @@
|
||||
<?php
|
||||
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/*
|
||||
# ***** BEGIN LICENSE BLOCK *****
|
||||
# This file is part of InDefero, an open source project management application.
|
||||
# Copyright (C) 2008-2011 Céondo Ltd and contributors.
|
||||
#
|
||||
# InDefero 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.
|
||||
#
|
||||
# InDefero is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
#
|
||||
# ***** END LICENSE BLOCK ***** */
|
||||
|
||||
/**
|
||||
* Upload and process an archive file.
|
||||
*
|
||||
*/
|
||||
class IDF_Form_UploadArchive extends Pluf_Form
|
||||
{
|
||||
public $user = null;
|
||||
public $project = null;
|
||||
private $archiveHelper = null;
|
||||
|
||||
public function initFields($extra=array())
|
||||
{
|
||||
$this->user = $extra['user'];
|
||||
$this->project = $extra['project'];
|
||||
|
||||
$this->fields['archive'] = new Pluf_Form_Field_File(
|
||||
array('required' => true,
|
||||
'label' => __('Archive file'),
|
||||
'initial' => '',
|
||||
'max_size' => Pluf::f('max_upload_archive_size', 20971520),
|
||||
'move_function_params' => array(
|
||||
'upload_path' => Pluf::f('upload_path').'/'.$this->project->shortname.'/archives',
|
||||
'upload_path_create' => true,
|
||||
'upload_overwrite' => true,
|
||||
)));
|
||||
}
|
||||
|
||||
|
||||
public function clean_archive()
|
||||
{
|
||||
$this->archiveHelper = new IDF_Form_UploadArchiveHelper(
|
||||
Pluf::f('upload_path').'/'.$this->project->shortname.'/archives/'.$this->cleaned_data['archive']);
|
||||
|
||||
// basic archive validation
|
||||
$this->archiveHelper->validate();
|
||||
|
||||
// extension validation
|
||||
$fileNames = $this->archiveHelper->getEntryNames();
|
||||
foreach ($fileNames as $fileName) {
|
||||
$extra = strtolower(implode('|', explode(' ', Pluf::f('idf_extra_upload_ext'))));
|
||||
if (strlen($extra)) $extra .= '|';
|
||||
if (!preg_match('/\.('.$extra.'png|jpg|jpeg|gif|bmp|psd|tif|aiff|asf|avi|bz2|css|doc|eps|gz|jar|mdtext|mid|mov|mp3|mpg|ogg|pdf|ppt|ps|qt|ra|ram|rm|rtf|sdd|sdw|sit|sxi|sxw|swf|tgz|txt|wav|xls|xml|war|wmv|zip)$/i', $fileName)) {
|
||||
@unlink(Pluf::f('upload_path').'/'.$this->project->shortname.'/files/'.$this->cleaned_data['archive']);
|
||||
throw new Pluf_Form_Invalid(sprintf(__('For security reasons, you cannot upload a file (%s) with this extension.'), $fileName));
|
||||
}
|
||||
}
|
||||
|
||||
// label and file name validation
|
||||
$conf = new IDF_Conf();
|
||||
$conf->setProject($this->project);
|
||||
$onemax = array();
|
||||
foreach (explode(',', $conf->getVal('labels_download_one_max', IDF_Form_UploadConf::init_one_max)) as $class) {
|
||||
if (trim($class) != '') {
|
||||
$onemax[] = mb_strtolower(trim($class));
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($fileNames as $fileName) {
|
||||
$meta = $this->archiveHelper->getMetaData($fileName);
|
||||
$count = array();
|
||||
foreach ($meta['labels'] as $label) {
|
||||
$label = trim($label);
|
||||
if (strpos($label, ':') !== false) {
|
||||
list($class, $name) = explode(':', $label, 2);
|
||||
list($class, $name) = array(mb_strtolower(trim($class)),
|
||||
trim($name));
|
||||
} else {
|
||||
$class = 'other';
|
||||
$name = $label;
|
||||
}
|
||||
if (!isset($count[$class])) $count[$class] = 1;
|
||||
else $count[$class] += 1;
|
||||
if (in_array($class, $onemax) and $count[$class] > 1) {
|
||||
throw new Pluf_Form_Invalid(
|
||||
sprintf(__('You cannot provide more than label from the %1$s class to a download (%2$s).'), $class, $name)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$sql = new Pluf_SQL('file=%s AND project=%s', array($fileName, $this->project->id));
|
||||
$upload = Pluf::factory('IDF_Upload')->getOne(array('filter' => $sql->gen()));
|
||||
|
||||
$meta = $this->archiveHelper->getMetaData($fileName);
|
||||
if ($upload != null && $meta['replaces'] !== $fileName) {
|
||||
throw new Pluf_Form_Invalid(
|
||||
sprintf(__('A file with the name "%s" has already been uploaded and is not marked to be replaced.'), $fileName));
|
||||
}
|
||||
}
|
||||
|
||||
return $this->cleaned_data['archive'];
|
||||
}
|
||||
|
||||
/**
|
||||
* If we have uploaded a file, but the form failed remove it.
|
||||
*
|
||||
*/
|
||||
function failed()
|
||||
{
|
||||
if (!empty($this->cleaned_data['archive'])
|
||||
and file_exists(Pluf::f('upload_path').'/'.$this->project->shortname.'/archives/'.$this->cleaned_data['archive'])) {
|
||||
@unlink(Pluf::f('upload_path').'/'.$this->project->shortname.'/archives/'.$this->cleaned_data['archive']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the model in the database.
|
||||
*
|
||||
* @param bool Commit in the database or not. If not, the object
|
||||
* is returned but not saved in the database.
|
||||
*/
|
||||
function save($commit=true)
|
||||
{
|
||||
if (!$this->isValid()) {
|
||||
throw new Exception(__('Cannot save the model from an invalid form.'));
|
||||
}
|
||||
|
||||
$uploadDir = Pluf::f('upload_path').'/'.$this->project->shortname.'/files/';
|
||||
$fileNames = $this->archiveHelper->getEntryNames();
|
||||
|
||||
foreach ($fileNames as $fileName) {
|
||||
$meta = $this->archiveHelper->getMetaData($fileName);
|
||||
|
||||
// add a tag for each label
|
||||
$tags = array();
|
||||
foreach ($meta['labels'] as $label) {
|
||||
$label = trim($label);
|
||||
if (strlen($label) > 0) {
|
||||
if (strpos($label, ':') !== false) {
|
||||
list($class, $name) = explode(':', $label, 2);
|
||||
list($class, $name) = array(trim($class), trim($name));
|
||||
} else {
|
||||
$class = 'Other';
|
||||
$name = $label;
|
||||
}
|
||||
$tags[] = IDF_Tag::add($name, $this->project, $class);
|
||||
}
|
||||
}
|
||||
|
||||
// process a possible replacement
|
||||
if (!empty($meta['replaces'])) {
|
||||
$sql = new Pluf_SQL('file=%s AND project=%s', array($meta['replaces'], $this->project->id));
|
||||
$oldUpload = Pluf::factory('IDF_Upload')->getOne(array('filter' => $sql->gen()));
|
||||
|
||||
if ($oldUpload) {
|
||||
if ($meta['replaces'] === $fileName) {
|
||||
$oldUpload->delete();
|
||||
} else {
|
||||
$tags = $this->project->getTagsFromConfig('labels_download_predefined',
|
||||
IDF_Form_UploadConf::init_predefined);
|
||||
// the deprecate tag is - by definition - always the last one
|
||||
$deprecatedTag = array_pop($tags);
|
||||
$oldUpload->setAssoc($deprecatedTag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// extract the file
|
||||
$this->archiveHelper->extract($fileName, $uploadDir);
|
||||
|
||||
// create the upload
|
||||
$upload = new IDF_Upload();
|
||||
$upload->project = $this->project;
|
||||
$upload->submitter = $this->user;
|
||||
$upload->summary = trim($meta['summary']);
|
||||
$upload->changelog = trim($meta['description']);
|
||||
$upload->file = $fileName;
|
||||
$upload->filesize = filesize($uploadDir.$fileName);
|
||||
$upload->downloads = 0;
|
||||
$upload->create();
|
||||
foreach ($tags as $tag) {
|
||||
$upload->setAssoc($tag);
|
||||
}
|
||||
|
||||
// send the notification
|
||||
$upload->notify($this->project->getConf());
|
||||
/**
|
||||
* [signal]
|
||||
*
|
||||
* IDF_Upload::create
|
||||
*
|
||||
* [sender]
|
||||
*
|
||||
* IDF_Form_Upload
|
||||
*
|
||||
* [description]
|
||||
*
|
||||
* This signal allows an application to perform a set of tasks
|
||||
* just after the upload of a file and after the notification run.
|
||||
*
|
||||
* [parameters]
|
||||
*
|
||||
* array('upload' => $upload);
|
||||
*
|
||||
*/
|
||||
$params = array('upload' => $upload);
|
||||
Pluf_Signal::send('IDF_Upload::create', 'IDF_Form_Upload',
|
||||
$params);
|
||||
}
|
||||
|
||||
// finally unlink the uploaded archive
|
||||
@unlink(Pluf::f('upload_path').'/'.$this->project->shortname.'/archives/'.$this->cleaned_data['archive']);
|
||||
}
|
||||
}
|
||||
|
158
src/IDF/Form/UploadArchiveHelper.php
Normal file
158
src/IDF/Form/UploadArchiveHelper.php
Normal file
@@ -0,0 +1,158 @@
|
||||
<?php
|
||||
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/*
|
||||
# ***** BEGIN LICENSE BLOCK *****
|
||||
# This file is part of InDefero, an open source project management application.
|
||||
# Copyright (C) 2008-2011 Céondo Ltd and contributors.
|
||||
#
|
||||
# InDefero 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.
|
||||
#
|
||||
# InDefero is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
#
|
||||
# ***** END LICENSE BLOCK ***** */
|
||||
|
||||
class IDF_Form_UploadArchiveHelper
|
||||
{
|
||||
private $file = null;
|
||||
private $entries = array();
|
||||
|
||||
public function __construct($file)
|
||||
{
|
||||
$this->file = $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the archive; throws a invalid form exception in case the
|
||||
* archive contains invalid data or cannot be read.
|
||||
*/
|
||||
public function validate()
|
||||
{
|
||||
if (!file_exists($this->file)) {
|
||||
throw new Pluf_Form_Invalid(__('The archive does not exist.'));
|
||||
}
|
||||
|
||||
$za = new ZipArchive();
|
||||
$res = $za->open($this->file);
|
||||
if ($res !== true) {
|
||||
throw new Pluf_Form_Invalid(
|
||||
sprintf(__('The archive could not be read (code %d).'), $res));
|
||||
}
|
||||
|
||||
$manifest = $za->getFromName('manifest.xml');
|
||||
if ($manifest === false) {
|
||||
throw new Pluf_Form_Invalid(__('The archive does not contain a manifest.xml.'));
|
||||
}
|
||||
|
||||
libxml_use_internal_errors(true);
|
||||
$xml = @simplexml_load_string($manifest);
|
||||
if ($xml === false) {
|
||||
$error = libxml_get_last_error();
|
||||
throw new Pluf_Form_Invalid(
|
||||
sprintf(__('The archive\'s manifest is invalid: %s'), $error->message));
|
||||
}
|
||||
|
||||
foreach (@$xml->file as $idx => $file)
|
||||
{
|
||||
$entry = array(
|
||||
'name' => (string)@$file->name,
|
||||
'summary' => (string)@$file->summary,
|
||||
'description' => (string)@$file->description,
|
||||
'replaces' => (string)@$file->replaces,
|
||||
'labels' => array(),
|
||||
'stream' => null
|
||||
);
|
||||
|
||||
if (empty($entry['name'])) {
|
||||
throw new Pluf_Form_Invalid(
|
||||
sprintf(__('The entry %d in the manifest is missing a file name.'), $idx));
|
||||
}
|
||||
|
||||
if (empty($entry['summary'])) {
|
||||
throw new Pluf_Form_Invalid(
|
||||
sprintf(__('The entry %d in the manifest is missing a summary.'), $idx));
|
||||
}
|
||||
|
||||
if ($entry['name'] === 'manifest.xml') {
|
||||
throw new Pluf_Form_Invalid(__('The manifest must not reference itself.'));
|
||||
}
|
||||
|
||||
if ($za->locateName($entry['name']) === false) {
|
||||
throw new Pluf_Form_Invalid(
|
||||
sprintf(__('The entry %s in the manifest does not exist in the archive.'), $entry['name']));
|
||||
}
|
||||
|
||||
if (in_array($entry['name'], $this->entries)) {
|
||||
throw new Pluf_Form_Invalid(
|
||||
sprintf(__('The entry %s in the manifest is referenced more than once.'), $entry['name']));
|
||||
}
|
||||
|
||||
if ($file->labels) {
|
||||
foreach (@$file->labels->label as $label) {
|
||||
$entry['labels'][] = (string)$label;
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: remove this once we allow more than six labels everywhere
|
||||
if (count($entry['labels']) > 6) {
|
||||
throw new Pluf_Form_Invalid(
|
||||
sprintf(__('The entry %s in the manifest has more than the six allowed labels set.'), $entry['name']));
|
||||
}
|
||||
|
||||
$this->entries[$entry['name']] = $entry;
|
||||
}
|
||||
|
||||
$za->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all entry names
|
||||
*
|
||||
* @return array of string
|
||||
*/
|
||||
public function getEntryNames()
|
||||
{
|
||||
return array_keys($this->entries);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns meta data for the given entry
|
||||
*
|
||||
* @param string $name
|
||||
* @throws Exception
|
||||
*/
|
||||
public function getMetaData($name)
|
||||
{
|
||||
if (!array_key_exists($name, $this->entries)) {
|
||||
throw new Exception('unknown file ' . $name);
|
||||
}
|
||||
return $this->entries[$name];
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the file entry $name at $path
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $path
|
||||
* @throws Exception
|
||||
*/
|
||||
public function extract($name, $path)
|
||||
{
|
||||
if (!array_key_exists($name, $this->entries)) {
|
||||
throw new Exception('unknown file ' . $name);
|
||||
}
|
||||
$za = new ZipArchive();
|
||||
$za->open($this->file);
|
||||
$za->extractTo($path, $name);
|
||||
$za->close();
|
||||
}
|
||||
}
|
@@ -64,6 +64,14 @@ Deprecated = Most users should NOT download this';
|
||||
'widget_attrs' => array('size' => 60),
|
||||
));
|
||||
|
||||
$this->conf = $extra['conf'];
|
||||
$this->fields['upload_webhook_url'] = new Pluf_Form_Field_Url(
|
||||
array('required' => false,
|
||||
'label' => __('Webhook URL'),
|
||||
'initial' => $this->conf->getVal('upload_webhook_url', ''),
|
||||
'widget_attrs' => array('size' => 60),
|
||||
));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -56,7 +56,7 @@ class IDF_Form_UserAccount extends Pluf_Form
|
||||
|
||||
$this->fields['email'] = new Pluf_Form_Field_Email(
|
||||
array('required' => true,
|
||||
'label' => __('Your mail'),
|
||||
'label' => __('Your email'),
|
||||
'initial' => $this->user->email,
|
||||
'help_text' => __('If you change your email address, an email will be sent to the new address to confirm it.'),
|
||||
));
|
||||
@@ -168,9 +168,9 @@ class IDF_Form_UserAccount extends Pluf_Form
|
||||
|
||||
$this->fields['secondary_mail'] = new Pluf_Form_Field_Email(
|
||||
array('required' => false,
|
||||
'label' => __('Add a secondary mail address'),
|
||||
'label' => __('Add a secondary email address'),
|
||||
'initial' => '',
|
||||
'help_text' => __('You will get a mail to confirm that you own the address you specify.'),
|
||||
'help_text' => __('You will get an email to confirm that you own the address you specify.'),
|
||||
));
|
||||
}
|
||||
|
||||
@@ -317,8 +317,15 @@ class IDF_Form_UserAccount extends Pluf_Form
|
||||
return '';
|
||||
}
|
||||
|
||||
if (preg_match('#^ssh\-[a-z]{3}\s\S+(\s\S+)?$#', $key)) {
|
||||
$key = str_replace(array("\n", "\r"), '', $key);
|
||||
$keysearch = '';
|
||||
if (preg_match('#^(ssh\-(?:dss|rsa)\s+\S+)(.*)#', $key, $m)) {
|
||||
$basekey = preg_replace('/\s+/', ' ', $m[1]);
|
||||
$comment = trim(preg_replace('/[\r\n]/', ' ', $m[2]));
|
||||
|
||||
$keysearch = $basekey.'%';
|
||||
$key = $basekey;
|
||||
if (!empty($comment))
|
||||
$key .= ' '.$comment;
|
||||
|
||||
if (Pluf::f('idf_strong_key_check', false)) {
|
||||
|
||||
@@ -337,7 +344,9 @@ class IDF_Form_UserAccount extends Pluf_Form
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (preg_match('#^\[pubkey [^\]]+\]\s*\S+\s*\[end\]$#', $key)) {
|
||||
else if (preg_match('#^\[pubkey [^\]]+\]\s*(\S+)\s*\[end\]$#', $key, $m)) {
|
||||
$keysearch = '%'.$m[1].'%';
|
||||
|
||||
if (Pluf::f('idf_strong_key_check', false)) {
|
||||
|
||||
// if monotone can read it, it should be valid
|
||||
@@ -367,7 +376,7 @@ class IDF_Form_UserAccount extends Pluf_Form
|
||||
if ($user) {
|
||||
$ruser = Pluf::factory('Pluf_User', $user);
|
||||
if ($ruser->id > 0) {
|
||||
$sql = new Pluf_SQL('content=%s', array($key));
|
||||
$sql = new Pluf_SQL('content LIKE %s AND user=%s', array($keysearch, $ruser->id));
|
||||
$keys = Pluf::factory('IDF_Key')->getList(array('filter' => $sql->gen()));
|
||||
if (count($keys) > 0) {
|
||||
throw new Pluf_Form_Invalid(
|
||||
|
@@ -27,7 +27,7 @@
|
||||
* This create a new page and the corresponding revision.
|
||||
*
|
||||
*/
|
||||
class IDF_Form_WikiCreate extends Pluf_Form
|
||||
class IDF_Form_WikiPageCreate extends Pluf_Form
|
||||
{
|
||||
public $user = null;
|
||||
public $project = null;
|
||||
@@ -109,7 +109,7 @@ Add your content here. Format your content with:
|
||||
}
|
||||
$sql = new Pluf_SQL('project=%s AND title=%s',
|
||||
array($this->project->id, $title));
|
||||
$pages = Pluf::factory('IDF_WikiPage')->getList(array('filter'=>$sql->gen()));
|
||||
$pages = Pluf::factory('IDF_Wiki_Page')->getList(array('filter'=>$sql->gen()));
|
||||
if ($pages->count() > 0) {
|
||||
throw new Pluf_Form_Invalid(__('A page with this title already exists.'));
|
||||
}
|
||||
@@ -147,7 +147,7 @@ Add your content here. Format your content with:
|
||||
else $count[$class] += 1;
|
||||
if (in_array($class, $onemax) and $count[$class] > 1) {
|
||||
if (!isset($this->errors['label'.$i])) $this->errors['label'.$i] = array();
|
||||
$this->errors['label'.$i][] = sprintf(__('You cannot provide more than label from the %s class to a page.'), $class);
|
||||
$this->errors['label'.$i][] = sprintf(__('You cannot provide more than one label from the %s class to a page.'), $class);
|
||||
throw new Pluf_Form_Invalid(__('You provided an invalid label.'));
|
||||
}
|
||||
}
|
||||
@@ -183,7 +183,7 @@ Add your content here. Format your content with:
|
||||
}
|
||||
}
|
||||
// Create the page
|
||||
$page = new IDF_WikiPage();
|
||||
$page = new IDF_Wiki_Page();
|
||||
$page->project = $this->project;
|
||||
$page->submitter = $this->user;
|
||||
$page->summary = trim($this->cleaned_data['summary']);
|
||||
@@ -193,7 +193,7 @@ Add your content here. Format your content with:
|
||||
$page->setAssoc($tag);
|
||||
}
|
||||
// add the first revision
|
||||
$rev = new IDF_WikiRevision();
|
||||
$rev = new IDF_Wiki_PageRevision();
|
||||
$rev->wikipage = $page;
|
||||
$rev->content = $this->cleaned_data['content'];
|
||||
$rev->submitter = $this->user;
|
@@ -27,7 +27,7 @@
|
||||
* This is a hard delete of the page and the revisions.
|
||||
*
|
||||
*/
|
||||
class IDF_Form_WikiDelete extends Pluf_Form
|
||||
class IDF_Form_WikiPageDelete extends Pluf_Form
|
||||
{
|
||||
protected $page = null;
|
||||
|
@@ -27,7 +27,7 @@
|
||||
* This add a corresponding revision.
|
||||
*
|
||||
*/
|
||||
class IDF_Form_WikiUpdate extends Pluf_Form
|
||||
class IDF_Form_WikiPageUpdate extends Pluf_Form
|
||||
{
|
||||
public $user = null;
|
||||
public $project = null;
|
||||
@@ -120,7 +120,7 @@ class IDF_Form_WikiUpdate extends Pluf_Form
|
||||
}
|
||||
$sql = new Pluf_SQL('project=%s AND title=%s',
|
||||
array($this->project->id, $title));
|
||||
$pages = Pluf::factory('IDF_WikiPage')->getList(array('filter'=>$sql->gen()));
|
||||
$pages = Pluf::factory('IDF_Wiki_Page')->getList(array('filter'=>$sql->gen()));
|
||||
if ($pages->count() > 0 and $pages[0]->id != $this->page->id) {
|
||||
throw new Pluf_Form_Invalid(__('A page with this title already exists.'));
|
||||
}
|
||||
@@ -158,7 +158,7 @@ class IDF_Form_WikiUpdate extends Pluf_Form
|
||||
else $count[$class] += 1;
|
||||
if (in_array($class, $onemax) and $count[$class] > 1) {
|
||||
if (!isset($this->errors['label'.$i])) $this->errors['label'.$i] = array();
|
||||
$this->errors['label'.$i][] = sprintf(__('You cannot provide more than label from the %s class to a page.'), $class);
|
||||
$this->errors['label'.$i][] = sprintf(__('You cannot provide more than one label from the %s class to a page.'), $class);
|
||||
throw new Pluf_Form_Invalid(__('You provided an invalid label.'));
|
||||
}
|
||||
}
|
||||
@@ -229,7 +229,7 @@ class IDF_Form_WikiUpdate extends Pluf_Form
|
||||
}
|
||||
$this->page->update();
|
||||
// add the new revision
|
||||
$rev = new IDF_WikiRevision();
|
||||
$rev = new IDF_Wiki_PageRevision();
|
||||
$rev->wikipage = $this->page;
|
||||
$rev->content = $this->cleaned_data['content'];
|
||||
$rev->submitter = $this->user;
|
169
src/IDF/Form/WikiResourceCreate.php
Normal file
169
src/IDF/Form/WikiResourceCreate.php
Normal file
@@ -0,0 +1,169 @@
|
||||
<?php
|
||||
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/*
|
||||
# ***** BEGIN LICENSE BLOCK *****
|
||||
# This file is part of InDefero, an open source project management application.
|
||||
# Copyright (C) 2008-2011 Céondo Ltd and contributors.
|
||||
#
|
||||
# InDefero 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.
|
||||
#
|
||||
# InDefero is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
#
|
||||
# ***** END LICENSE BLOCK ***** */
|
||||
|
||||
/**
|
||||
* Create a new resource.
|
||||
*
|
||||
* This create a new resource and the corresponding revision.
|
||||
*
|
||||
*/
|
||||
class IDF_Form_WikiResourceCreate extends Pluf_Form
|
||||
{
|
||||
public $user = null;
|
||||
public $project = null;
|
||||
public $show_full = false;
|
||||
|
||||
public function initFields($extra=array())
|
||||
{
|
||||
$this->project = $extra['project'];
|
||||
$this->user = $extra['user'];
|
||||
$initname = (!empty($extra['name'])) ? $extra['name'] : __('ResourceName');
|
||||
|
||||
$this->fields['title'] = new Pluf_Form_Field_Varchar(
|
||||
array('required' => true,
|
||||
'label' => __('Resource title'),
|
||||
'initial' => $initname,
|
||||
'widget_attrs' => array(
|
||||
'maxlength' => 200,
|
||||
'size' => 67,
|
||||
),
|
||||
'help_text' => __('The resource name must contains only letters, digits and the dash (-) character.'),
|
||||
));
|
||||
$this->fields['summary'] = new Pluf_Form_Field_Varchar(
|
||||
array('required' => true,
|
||||
'label' => __('Description'),
|
||||
'help_text' => __('This one line description is displayed in the list of resources.'),
|
||||
'initial' => '',
|
||||
'widget_attrs' => array(
|
||||
'maxlength' => 200,
|
||||
'size' => 67,
|
||||
),
|
||||
));
|
||||
|
||||
$this->fields['file'] = new Pluf_Form_Field_File(
|
||||
array('required' => true,
|
||||
'label' => __('File'),
|
||||
'initial' => '',
|
||||
'max_size' => Pluf::f('max_upload_size', 2097152),
|
||||
'move_function_params' => array('upload_path' => $this->getTempUploadPath(),
|
||||
'upload_path_create' => true,
|
||||
'upload_overwrite' => true),
|
||||
));
|
||||
}
|
||||
|
||||
public function clean_title()
|
||||
{
|
||||
$title = $this->cleaned_data['title'];
|
||||
if (preg_match('/[^a-zA-Z0-9\-]/', $title)) {
|
||||
throw new Pluf_Form_Invalid(__('The title contains invalid characters.'));
|
||||
}
|
||||
$sql = new Pluf_SQL('project=%s AND title=%s',
|
||||
array($this->project->id, $title));
|
||||
$resources = Pluf::factory('IDF_Wiki_Resource')->getList(array('filter'=>$sql->gen()));
|
||||
if ($resources->count() > 0) {
|
||||
throw new Pluf_Form_Invalid(__('A resource with this title already exists.'));
|
||||
}
|
||||
return $title;
|
||||
}
|
||||
|
||||
public function clean_file()
|
||||
{
|
||||
// FIXME: we do the same in IDF_Form_Upload and a couple of other places as well
|
||||
$extra = strtolower(implode('|', explode(' ', Pluf::f('idf_extra_upload_ext'))));
|
||||
if (strlen($extra)) $extra .= '|';
|
||||
if (!preg_match('/\.('.$extra.'png|jpg|jpeg|gif|bmp|psd|tif|aiff|asf|avi|bz2|css|doc|eps|gz|jar|mdtext|mid|mov|mp3|mpg|ogg|pdf|ppt|ps|qt|ra|ram|rm|rtf|sdd|sdw|sit|sxi|sxw|swf|tgz|txt|wav|xls|xml|war|wmv|zip)$/i', $this->cleaned_data['file'])) {
|
||||
@unlink($this->getTempUploadPath().$this->cleaned_data['file']);
|
||||
throw new Pluf_Form_Invalid(__('For security reasons, you cannot upload a file with this extension.'));
|
||||
}
|
||||
return $this->cleaned_data['file'];
|
||||
}
|
||||
|
||||
/**
|
||||
* If we have uploaded a file, but the form failed remove it.
|
||||
*
|
||||
*/
|
||||
function failed()
|
||||
{
|
||||
if (!empty($this->cleaned_data['file'])
|
||||
and file_exists($this->getTempUploadPath().$this->cleaned_data['file'])) {
|
||||
@unlink($this->getTempUploadPath().$this->cleaned_data['file']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the model in the database.
|
||||
*
|
||||
* @param bool Commit in the database or not. If not, the object
|
||||
* is returned but not saved in the database.
|
||||
* @return Object Model with data set from the form.
|
||||
*/
|
||||
function save($commit=true)
|
||||
{
|
||||
if (!$this->isValid()) {
|
||||
throw new Exception(__('Cannot save the model from an invalid form.'));
|
||||
}
|
||||
|
||||
$tempFile = $this->getTempUploadPath().$this->cleaned_data['file'];
|
||||
list($mimeType, , $extension) = IDF_FileUtil::getMimeType($tempFile);
|
||||
|
||||
// create the resource
|
||||
$resource = new IDF_Wiki_Resource();
|
||||
$resource->project = $this->project;
|
||||
$resource->submitter = $this->user;
|
||||
$resource->summary = trim($this->cleaned_data['summary']);
|
||||
$resource->title = trim($this->cleaned_data['title']);
|
||||
$resource->mime_type = $mimeType;
|
||||
$resource->create();
|
||||
|
||||
// add the first revision
|
||||
$rev = new IDF_Wiki_ResourceRevision();
|
||||
$rev->wikiresource = $resource;
|
||||
$rev->submitter = $this->user;
|
||||
$rev->summary = __('Initial resource creation');
|
||||
$rev->filesize = filesize($tempFile);
|
||||
$rev->fileext = $extension;
|
||||
$rev->create();
|
||||
|
||||
$finalFile = $rev->getFilePath();
|
||||
if (!@mkdir(dirname($finalFile), 0755, true)) {
|
||||
@unlink($tempFile);
|
||||
$rev->delete();
|
||||
$resource->delete();
|
||||
throw new Exception('could not create final resource path');
|
||||
}
|
||||
|
||||
if (!@rename($tempFile, $finalFile)) {
|
||||
@unlink($tempFile);
|
||||
$rev->delete();
|
||||
$resource->delete();
|
||||
throw new Exception('could not move resource to final location');
|
||||
}
|
||||
|
||||
return $resource;
|
||||
}
|
||||
|
||||
private function getTempUploadPath()
|
||||
{
|
||||
return Pluf::f('upload_path').'/'.$this->project->shortname.'/wiki/temp/';
|
||||
}
|
||||
}
|
64
src/IDF/Form/WikiResourceDelete.php
Normal file
64
src/IDF/Form/WikiResourceDelete.php
Normal file
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/*
|
||||
# ***** BEGIN LICENSE BLOCK *****
|
||||
# This file is part of InDefero, an open source project management application.
|
||||
# Copyright (C) 2008-2011 Céondo Ltd and contributors.
|
||||
#
|
||||
# InDefero 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.
|
||||
#
|
||||
# InDefero is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
#
|
||||
# ***** END LICENSE BLOCK ***** */
|
||||
|
||||
/**
|
||||
* Delete a documentation page.
|
||||
*
|
||||
* This is a hard delete of the page and the revisions.
|
||||
*
|
||||
*/
|
||||
class IDF_Form_WikiResourceDelete extends Pluf_Form
|
||||
{
|
||||
protected $resource = null;
|
||||
|
||||
public function initFields($extra=array())
|
||||
{
|
||||
$this->resource = $extra['resource'];
|
||||
$this->fields['confirm'] = new Pluf_Form_Field_Boolean(
|
||||
array('required' => true,
|
||||
'label' => __('Yes, I understand that the resource and all its revisions will be deleted.'),
|
||||
'initial' => '',
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the confirmation.
|
||||
*/
|
||||
public function clean_confirm()
|
||||
{
|
||||
if (!$this->cleaned_data['confirm']) {
|
||||
throw new Pluf_Form_Invalid(__('You need to confirm the deletion.'));
|
||||
}
|
||||
return $this->cleaned_data['confirm'];
|
||||
}
|
||||
|
||||
|
||||
function save($commit=true)
|
||||
{
|
||||
if (!$this->isValid()) {
|
||||
throw new Exception(__('Cannot save the model from an invalid form.'));
|
||||
}
|
||||
$this->resource->delete();
|
||||
return true;
|
||||
}
|
||||
}
|
161
src/IDF/Form/WikiResourceUpdate.php
Normal file
161
src/IDF/Form/WikiResourceUpdate.php
Normal file
@@ -0,0 +1,161 @@
|
||||
<?php
|
||||
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/*
|
||||
# ***** BEGIN LICENSE BLOCK *****
|
||||
# This file is part of InDefero, an open source project management application.
|
||||
# Copyright (C) 2008-2011 Céondo Ltd and contributors.
|
||||
#
|
||||
# InDefero 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.
|
||||
#
|
||||
# InDefero is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
#
|
||||
# ***** END LICENSE BLOCK ***** */
|
||||
|
||||
/**
|
||||
* Update a documentation page.
|
||||
*
|
||||
* This add a corresponding revision.
|
||||
*
|
||||
*/
|
||||
class IDF_Form_WikiResourceUpdate extends Pluf_Form
|
||||
{
|
||||
public $user = null;
|
||||
public $project = null;
|
||||
public $page = null;
|
||||
public $show_full = false;
|
||||
|
||||
|
||||
public function initFields($extra=array())
|
||||
{
|
||||
$this->resource = $extra['resource'];
|
||||
$this->user = $extra['user'];
|
||||
$this->project = $extra['project'];
|
||||
|
||||
$this->fields['summary'] = new Pluf_Form_Field_Varchar(
|
||||
array('required' => true,
|
||||
'label' => __('Description'),
|
||||
'help_text' => __('This one line description is displayed in the list of resources.'),
|
||||
'initial' => $this->resource->summary,
|
||||
'widget_attrs' => array(
|
||||
'maxlength' => 200,
|
||||
'size' => 67,
|
||||
),
|
||||
));
|
||||
$this->fields['file'] = new Pluf_Form_Field_File(
|
||||
array('required' => true,
|
||||
'label' => __('File'),
|
||||
'initial' => '',
|
||||
'max_size' => Pluf::f('max_upload_size', 2097152),
|
||||
'move_function_params' => array('upload_path' => $this->getTempUploadPath(),
|
||||
'upload_path_create' => true,
|
||||
'upload_overwrite' => true),
|
||||
));
|
||||
|
||||
$this->fields['comment'] = new Pluf_Form_Field_Varchar(
|
||||
array('required' => true,
|
||||
'label' => __('Comment'),
|
||||
'help_text' => __('One line to describe the changes you made.'),
|
||||
'initial' => '',
|
||||
'widget_attrs' => array(
|
||||
'maxlength' => 200,
|
||||
'size' => 67,
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
public function clean_file()
|
||||
{
|
||||
// FIXME: we do the same in IDF_Form_Upload and a couple of other places as well
|
||||
$extra = strtolower(implode('|', explode(' ', Pluf::f('idf_extra_upload_ext'))));
|
||||
if (strlen($extra)) $extra .= '|';
|
||||
if (!preg_match('/\.('.$extra.'png|jpg|jpeg|gif|bmp|psd|tif|aiff|asf|avi|bz2|css|doc|eps|gz|jar|mdtext|mid|mov|mp3|mpg|ogg|pdf|ppt|ps|qt|ra|ram|rm|rtf|sdd|sdw|sit|sxi|sxw|swf|tgz|txt|wav|xls|xml|war|wmv|zip)$/i', $this->cleaned_data['file'])) {
|
||||
@unlink($this->getTempUploadPath().$this->cleaned_data['file']);
|
||||
throw new Pluf_Form_Invalid(__('For security reasons, you cannot upload a file with this extension.'));
|
||||
}
|
||||
|
||||
list($mimeType, , $extension) = IDF_FileUtil::getMimeType($this->getTempUploadPath().$this->cleaned_data['file']);
|
||||
if ($this->resource->mime_type != $mimeType) {
|
||||
throw new Pluf_Form_Invalid(sprintf(
|
||||
__('The mime type of the uploaded file "%1$s" does not match the mime type of this resource "%2$s"'),
|
||||
$mimeType, $this->resource->mime_type
|
||||
));
|
||||
}
|
||||
$this->cleaned_data['fileext'] = $extension;
|
||||
|
||||
if (md5_file($this->getTempUploadPath().$this->cleaned_data['file']) ===
|
||||
md5_file($this->resource->get_current_revision()->getFilePath())) {
|
||||
throw new Pluf_Form_Invalid(__('The current version of the resource and the uploaded file are equal.'));
|
||||
}
|
||||
return $this->cleaned_data['file'];
|
||||
}
|
||||
|
||||
/**
|
||||
* If we have uploaded a file, but the form failed remove it.
|
||||
*
|
||||
*/
|
||||
function failed()
|
||||
{
|
||||
if (!empty($this->cleaned_data['file'])
|
||||
and file_exists($this->getTempUploadPath().$this->cleaned_data['file'])) {
|
||||
@unlink($this->getTempUploadPath().$this->cleaned_data['file']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the model in the database.
|
||||
*
|
||||
* @param bool Commit in the database or not. If not, the object
|
||||
* is returned but not saved in the database.
|
||||
* @return Object Model with data set from the form.
|
||||
*/
|
||||
function save($commit=true)
|
||||
{
|
||||
if (!$this->isValid()) {
|
||||
throw new Exception(__('Cannot save the model from an invalid form.'));
|
||||
}
|
||||
|
||||
$tempFile = $this->getTempUploadPath().$this->cleaned_data['file'];
|
||||
|
||||
$this->resource->summary = trim($this->cleaned_data['summary']);
|
||||
$this->resource->update();
|
||||
|
||||
// add the new revision
|
||||
$rev = new IDF_Wiki_ResourceRevision();
|
||||
$rev->wikiresource = $this->resource;
|
||||
$rev->submitter = $this->user;
|
||||
$rev->summary = $this->cleaned_data['comment'];
|
||||
$rev->filesize = filesize($tempFile);
|
||||
$rev->fileext = $this->cleaned_data['fileext'];
|
||||
$rev->create();
|
||||
|
||||
$finalFile = $rev->getFilePath();
|
||||
if (!is_dir(dirname($finalFile))) {
|
||||
@unlink($tempFile);
|
||||
$rev->delete();
|
||||
throw new Exception('resource path does not exist');
|
||||
}
|
||||
|
||||
if (!@rename($tempFile, $finalFile)) {
|
||||
@unlink($tempFile);
|
||||
$rev->delete();
|
||||
throw new Exception('could not move resource to final location');
|
||||
}
|
||||
|
||||
return $this->resource;
|
||||
}
|
||||
|
||||
private function getTempUploadPath()
|
||||
{
|
||||
return Pluf::f('upload_path').'/'.$this->project->shortname.'/wiki/temp/';
|
||||
}
|
||||
}
|
@@ -91,6 +91,13 @@ class IDF_Issue extends Pluf_Model
|
||||
'model' => 'IDF_Tag',
|
||||
'verbose' => __('labels'),
|
||||
),
|
||||
'due_dtime' =>
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Datetime',
|
||||
'blank' => true,
|
||||
'is_null' => true,
|
||||
'verbose' => __('due date'),
|
||||
),
|
||||
'status' =>
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Foreignkey',
|
||||
@@ -149,11 +156,28 @@ class IDF_Issue extends Pluf_Model
|
||||
IDF_Search::remove($this);
|
||||
}
|
||||
|
||||
function restore()
|
||||
{
|
||||
// Note: If a due date has not been set then it should
|
||||
// be set to null and not a useless/confusing date so...
|
||||
if ('0000-00-00 00:00:00' === $this->due_dtime) {
|
||||
$this->due_dtime = null;
|
||||
}
|
||||
}
|
||||
|
||||
function preSave($create=false)
|
||||
{
|
||||
if ($this->id == '') {
|
||||
$this->creation_dtime = gmdate('Y-m-d H:i:s');
|
||||
}
|
||||
// Note: If a due date is supplied then it must have
|
||||
// a time appended to it so that calculations of when
|
||||
// an issue falls overdue are accurate. In this case
|
||||
// 23:59:59 is added to signify the end of the day.
|
||||
if ($this->due_dtime and !empty($this->due_dtime)) {
|
||||
$this->due_dtime = date('Y-m-d 23:59:59',
|
||||
strtotime($this->due_dtime));
|
||||
}
|
||||
$this->modif_dtime = gmdate('Y-m-d H:i:s');
|
||||
}
|
||||
|
||||
@@ -169,6 +193,24 @@ class IDF_Issue extends Pluf_Model
|
||||
}
|
||||
}
|
||||
|
||||
function getGroupedRelatedIssues($opts = array(), $idsOnly = false)
|
||||
{
|
||||
$rels = $this->get_related_issues_list(array_merge($opts, array(
|
||||
'view' => 'with_other_issue',
|
||||
)));
|
||||
|
||||
$res = array();
|
||||
foreach ($rels as $rel) {
|
||||
$verb = $rel->verb;
|
||||
if (!array_key_exists($verb, $res)) {
|
||||
$res[$verb] = array();
|
||||
}
|
||||
$res[$verb][] = $idsOnly ? $rel->other_issue : $rel;
|
||||
}
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an HTML fragment used to display this issue in the
|
||||
* timeline.
|
||||
@@ -193,7 +235,7 @@ class IDF_Issue extends Pluf_Model
|
||||
$ic = (in_array($this->status, $request->project->getTagIdsByStatus('closed'))) ? 'issue-c' : 'issue-o';
|
||||
$out .= sprintf(__('<a href="%1$s" class="%2$s" title="View issue">Issue %3$d</a>, %4$s'), $url, $ic, $this->id, Pluf_esc($this->summary)).'</td>';
|
||||
$out .= "\n".'<tr class="extra"><td colspan="2">
|
||||
<div class="helptext right">'.sprintf(__('Creation of <a href="%s" class="%s">issue %d</a>, by %s'), $url, $ic, $this->id, $user).'</div></td></tr>';
|
||||
<div class="helptext right">'.sprintf(__('Creation of <a href="%1$s" class="%2$s">issue %3$d</a>, by %4$s'), $url, $ic, $this->id, $user).'</div></td></tr>';
|
||||
return Pluf_Template::markSafe($out);
|
||||
}
|
||||
|
||||
@@ -203,7 +245,7 @@ class IDF_Issue extends Pluf_Model
|
||||
.Pluf_HTTP_URL_urlForView('IDF_Views_Issue::view',
|
||||
array($request->project->shortname,
|
||||
$this->id));
|
||||
$title = sprintf(__('%s: Issue %d created - %s'),
|
||||
$title = sprintf(__('%1$s: Issue %2$d created - %3$s'),
|
||||
$request->project->name,
|
||||
$this->id, $this->summary);
|
||||
$cts = $this->get_comments_list(array('order' => 'id ASC',
|
||||
@@ -238,91 +280,72 @@ class IDF_Issue extends Pluf_Model
|
||||
*/
|
||||
public function notify($conf, $create=true)
|
||||
{
|
||||
$prj = $this->get_project();
|
||||
$to_email = array();
|
||||
if ('' != $conf->getVal('issues_notification_email', '')) {
|
||||
$langs = Pluf::f('languages', array('en'));
|
||||
$addresses = explode(',', $conf->getVal('issues_notification_email'));
|
||||
foreach ($addresses as $address) {
|
||||
$to_email[] = array($address, $langs[0]);
|
||||
}
|
||||
}
|
||||
$project = $this->get_project();
|
||||
$current_locale = Pluf_Translation::getLocale();
|
||||
$id = '<'.md5($this->id.md5(Pluf::f('secret_key'))).'@'.Pluf::f('mail_host', 'localhost').'>';
|
||||
if ($create) {
|
||||
if (null != $this->get_owner() and $this->owner != $this->submitter) {
|
||||
$email_lang = array($this->get_owner()->email,
|
||||
$this->get_owner()->language);
|
||||
if (!in_array($email_lang, $to_email)) {
|
||||
$to_email[] = $email_lang;
|
||||
}
|
||||
}
|
||||
$comments = $this->get_comments_list(array('order' => 'id ASC'));
|
||||
$context = new Pluf_Template_Context(
|
||||
array(
|
||||
'issue' => $this,
|
||||
'comment' => $comments[0],
|
||||
'project' => $prj,
|
||||
'url_base' => Pluf::f('url_base'),
|
||||
)
|
||||
);
|
||||
foreach ($to_email as $email_lang) {
|
||||
Pluf_Translation::loadSetLocale($email_lang[1]);
|
||||
$email = new Pluf_Mail(Pluf::f('from_email'), $email_lang[0],
|
||||
sprintf(__('Issue %s - %s (%s)'),
|
||||
$this->id, $this->summary, $prj->shortname));
|
||||
$tmpl = new Pluf_Template('idf/issues/issue-created-email.txt');
|
||||
$email->addTextMessage($tmpl->render($context));
|
||||
$email->addHeaders(array('Message-ID'=>$id));
|
||||
$email->sendMail();
|
||||
}
|
||||
} else {
|
||||
$comments = $this->get_comments_list(array('order' => 'id DESC'));
|
||||
$email_sender = '';
|
||||
if (isset($comments[0])) {
|
||||
$email_sender = $comments[0]->get_submitter()->email;
|
||||
}
|
||||
foreach ($this->get_interested_list() as $interested) {
|
||||
$email_lang = array($interested->email,
|
||||
$interested->language);
|
||||
if (!in_array($email_lang, $to_email)) {
|
||||
$to_email[] = $email_lang;
|
||||
}
|
||||
}
|
||||
$email_lang = array($this->get_submitter()->email,
|
||||
$this->get_submitter()->language);
|
||||
if (!in_array($email_lang, $to_email)) {
|
||||
$to_email[] = $email_lang;
|
||||
}
|
||||
if (null != $this->get_owner()) {
|
||||
$email_lang = array($this->get_owner()->email,
|
||||
$this->get_owner()->language);
|
||||
if (!in_array($email_lang, $to_email)) {
|
||||
$to_email[] = $email_lang;
|
||||
}
|
||||
}
|
||||
$context = new Pluf_Template_Context(
|
||||
array(
|
||||
'issue' => $this,
|
||||
'comments' => $comments,
|
||||
'project' => $prj,
|
||||
'url_base' => Pluf::f('url_base'),
|
||||
));
|
||||
foreach ($to_email as $email_lang) {
|
||||
if ($email_lang[0] == $email_sender) {
|
||||
continue; // Do not notify the one having created
|
||||
// the comment
|
||||
}
|
||||
Pluf_Translation::loadSetLocale($email_lang[1]);
|
||||
$email = new Pluf_Mail(Pluf::f('from_email'), $email_lang[0],
|
||||
sprintf(__('Updated Issue %s - %s (%s)'),
|
||||
$this->id, $this->summary, $prj->shortname));
|
||||
$tmpl = new Pluf_Template('idf/issues/issue-updated-email.txt');
|
||||
$email->addTextMessage($tmpl->render($context));
|
||||
$email->addHeaders(array('References'=>$id));
|
||||
$email->sendMail();
|
||||
}
|
||||
|
||||
$from_email = Pluf::f('from_email');
|
||||
$comments = $this->get_comments_list(array('order' => 'id DESC'));
|
||||
$messageId = '<'.md5('issue'.$this->id.md5(Pluf::f('secret_key'))).'@'.Pluf::f('mail_host', 'localhost').'>';
|
||||
$recipients = $project->getNotificationRecipientsForTab('issues');
|
||||
|
||||
// the submitter (might be skipped later on if he is the one who also
|
||||
// submitted the last comment)
|
||||
if (!array_key_exists($this->get_submitter()->email, $recipients)) {
|
||||
$recipients[$this->get_submitter()->email] = $this->get_submitter()->language;
|
||||
}
|
||||
|
||||
// the owner of the issue, if we have one
|
||||
$owner = $this->get_owner();
|
||||
if (null != $owner && !array_key_exists($owner->email, $recipients)) {
|
||||
$recipients[$owner->email] = $owner->language;
|
||||
}
|
||||
|
||||
// additional users who starred the issue
|
||||
foreach ($this->get_interested_list() as $interested) {
|
||||
if (array_key_exists($interested->email, $recipients))
|
||||
continue;
|
||||
$recipients[$interested->email] = $interested->language;
|
||||
}
|
||||
|
||||
foreach ($recipients as $address => $language) {
|
||||
|
||||
// do not notify the creator of the last comment,
|
||||
// i.e. the user who triggered this notification
|
||||
if ($comments[0]->get_submitter()->email === $address) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Pluf_Translation::loadSetLocale($language);
|
||||
|
||||
$context = new Pluf_Template_Context(array(
|
||||
'issue' => $this,
|
||||
'owns_issue' => $owner !== null && $owner->email === $address,
|
||||
// the initial comment for create, the last for update
|
||||
'comment' => $comments[0],
|
||||
'comments' => $comments,
|
||||
'project' => $project,
|
||||
'url_base' => Pluf::f('url_base'),
|
||||
));
|
||||
|
||||
$tplfile = 'idf/issues/issue-created-email.txt';
|
||||
$subject = __('Issue %1$s - %2$s (%3$s)');
|
||||
$headers = array('Message-ID' => $messageId);
|
||||
if (!$create) {
|
||||
$tplfile = 'idf/issues/issue-updated-email.txt';
|
||||
$subject = __('Updated Issue %1$s - %2$s (%3$s)');
|
||||
$headers = array('References' => $messageId);
|
||||
}
|
||||
|
||||
$tmpl = new Pluf_Template($tplfile);
|
||||
$text_email = $tmpl->render($context);
|
||||
|
||||
$email = new Pluf_Mail($from_email, $address,
|
||||
sprintf($subject, $this->id, $this->summary, $project->shortname));
|
||||
$email->addTextMessage($text_email);
|
||||
$email->addHeaders($headers);
|
||||
$email->sendMail();
|
||||
}
|
||||
|
||||
Pluf_Translation::loadSetLocale($current_locale);
|
||||
}
|
||||
}
|
@@ -153,12 +153,23 @@ class IDF_IssueComment extends Pluf_Model
|
||||
$out .= __('Status:'); break;
|
||||
case 'ow':
|
||||
$out .= __('Owner:'); break;
|
||||
case 'du':
|
||||
$out .= __('Due date:'); break;
|
||||
case 'lb':
|
||||
$out .= __('Labels:'); break;
|
||||
case 'rel':
|
||||
$out .= __('Relations:'); break;
|
||||
}
|
||||
$out .= '</strong> ';
|
||||
if ($w == 'lb') {
|
||||
$out .= Pluf_esc(implode(', ', $v));
|
||||
if ($w == 'lb' || $w == 'rel') {
|
||||
foreach ($v as $t => $ls) {
|
||||
foreach ($ls as $l) {
|
||||
if ($t == 'rem') $out .= '<s>';
|
||||
$out .= Pluf_esc($l);
|
||||
if ($t == 'rem') $out .= '</s>';
|
||||
$out .= ' ';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$out .= Pluf_esc($v);
|
||||
}
|
||||
@@ -168,7 +179,7 @@ class IDF_IssueComment extends Pluf_Model
|
||||
}
|
||||
$out .= '</td></tr>';
|
||||
$out .= "\n".'<tr class="extra"><td colspan="2">
|
||||
<div class="helptext right">'.sprintf(__('Comment on <a href="%s" class="%s">issue %d</a>, by %s'), $url, $ic, $issue->id, $user).'</div></td></tr>';
|
||||
<div class="helptext right">'.sprintf(__('Comment on <a href="%1$s" class="%2$s">issue %3$d</a>, by %4$s'), $url, $ic, $issue->id, $user).'</div></td></tr>';
|
||||
return Pluf_Template::markSafe($out);
|
||||
}
|
||||
|
||||
@@ -179,7 +190,7 @@ class IDF_IssueComment extends Pluf_Model
|
||||
.Pluf_HTTP_URL_urlForView('IDF_Views_Issue::view',
|
||||
array($request->project->shortname,
|
||||
$issue->id));
|
||||
$title = sprintf(__('%s: Comment on issue %d - %s'),
|
||||
$title = sprintf(__('%1$s: Comment on issue %2$d - %3$s'),
|
||||
Pluf_esc($request->project->name),
|
||||
$issue->id, Pluf_esc($issue->summary));
|
||||
$url .= '#ic'.$this->id;
|
||||
|
100
src/IDF/IssueRelation.php
Normal file
100
src/IDF/IssueRelation.php
Normal file
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/*
|
||||
# ***** BEGIN LICENSE BLOCK *****
|
||||
# This file is part of InDefero, an open source project management application.
|
||||
# Copyright (C) 2008-2011 Céondo Ltd and contributors.
|
||||
#
|
||||
# InDefero 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.
|
||||
#
|
||||
# InDefero is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
#
|
||||
# ***** END LICENSE BLOCK ***** */
|
||||
|
||||
/**
|
||||
* A relation of one issue to another
|
||||
*/
|
||||
class IDF_IssueRelation extends Pluf_Model
|
||||
{
|
||||
public $_model = __CLASS__;
|
||||
|
||||
function init()
|
||||
{
|
||||
$this->_a['table'] = 'idf_issuerelations';
|
||||
$this->_a['model'] = __CLASS__;
|
||||
$this->_a['cols'] = array(
|
||||
// It is mandatory to have an "id" column.
|
||||
'id' =>
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Sequence',
|
||||
'blank' => true,
|
||||
),
|
||||
'issue' =>
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Foreignkey',
|
||||
'model' => 'IDF_Issue',
|
||||
'blank' => false,
|
||||
'verbose' => __('issue'),
|
||||
'relate_name' => 'related_issues',
|
||||
),
|
||||
'verb' =>
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Text',
|
||||
'blank' => false,
|
||||
'verbose' => __('verb'),
|
||||
),
|
||||
'other_issue' =>
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Foreignkey',
|
||||
'model' => 'IDF_Issue',
|
||||
'blank' => false,
|
||||
'verbose' => __('other issue'),
|
||||
'relate_name' => 'related_other_issues',
|
||||
),
|
||||
'submitter' =>
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Foreignkey',
|
||||
'model' => 'Pluf_User',
|
||||
'blank' => false,
|
||||
'verbose' => __('submitter'),
|
||||
),
|
||||
'creation_dtime' =>
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Datetime',
|
||||
'blank' => true,
|
||||
'verbose' => __('creation date'),
|
||||
),
|
||||
);
|
||||
$this->_a['idx'] = array(
|
||||
'creation_dtime_idx' =>
|
||||
array(
|
||||
'col' => 'creation_dtime',
|
||||
'type' => 'normal',
|
||||
),
|
||||
);
|
||||
$issuetbl = $this->_con->pfx.'idf_issues';
|
||||
$this->_a['views'] = array(
|
||||
'with_other_issue' => array(
|
||||
'join' => 'INNER JOIN '.$issuetbl.' ON other_issue='.$issuetbl.'.id',
|
||||
'select' => $this->getSelect().', summary',
|
||||
'props' => array('summary' => 'other_summary'),
|
||||
));
|
||||
}
|
||||
|
||||
function preSave($create=false)
|
||||
{
|
||||
if ($this->id == '') {
|
||||
$this->creation_dtime = gmdate('Y-m-d H:i:s');
|
||||
}
|
||||
}
|
||||
}
|
@@ -80,7 +80,7 @@ class IDF_Key extends Pluf_Model
|
||||
if (preg_match('#^\[pubkey ([^\]]+)\]\s*(\S+)\s*\[end\]$#', $this->content, $m)) {
|
||||
return array('mtn', $m[1], $m[2]);
|
||||
}
|
||||
else if (preg_match('#^ssh\-[a-z]{3}\s(\S+)(?:\s(\S*))?$#', $this->content, $m)) {
|
||||
else if (preg_match('#^ssh\-(?:dss|rsa)\s(\S+)(?:\s(.*))?$#', $this->content, $m)) {
|
||||
if (!isset($m[2])) {
|
||||
$m[2] = "";
|
||||
}
|
||||
|
@@ -85,9 +85,11 @@ class IDF_Middleware
|
||||
'issuetext' => 'IDF_Template_IssueComment',
|
||||
'timeline' => 'IDF_Template_TimelineFragment',
|
||||
'markdown' => 'IDF_Template_Markdown',
|
||||
'markdown_forge' => 'IDF_Template_MarkdownForge',
|
||||
'showuser' => 'IDF_Template_ShowUser',
|
||||
'ashowuser' => 'IDF_Template_AssignShowUser',
|
||||
'appversion' => 'IDF_Template_AppVersion',
|
||||
'upload' => 'IDF_Template_Tag_UploadUrl',
|
||||
));
|
||||
$params['modifiers'] = array_merge($params['modifiers'],
|
||||
array(
|
||||
@@ -101,6 +103,7 @@ class IDF_Middleware
|
||||
|
||||
function IDF_Middleware_ContextPreProcessor($request)
|
||||
{
|
||||
$forge = IDF_Forge::instance();
|
||||
$c = array();
|
||||
$c['request'] = $request;
|
||||
$c['isAdmin'] = ($request->user->administrator or $request->user->staff);
|
||||
@@ -114,6 +117,7 @@ function IDF_Middleware_ContextPreProcessor($request)
|
||||
}
|
||||
$c['usherConfigured'] = Pluf::f("mtn_usher_conf", null) !== null;
|
||||
$c['allProjects'] = IDF_Views::getProjects($request->user);
|
||||
$c['customForgePageEnabled'] = $forge->isCustomForgePageEnabled();
|
||||
return $c;
|
||||
}
|
||||
|
||||
|
90
src/IDF/Migrations/17AddIssueRelations.php
Normal file
90
src/IDF/Migrations/17AddIssueRelations.php
Normal file
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/*
|
||||
# ***** BEGIN LICENSE BLOCK *****
|
||||
# This file is part of InDefero, an open source project management application.
|
||||
# Copyright (C) 2008-2011 Céondo Ltd and contributors.
|
||||
#
|
||||
# InDefero 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.
|
||||
#
|
||||
# InDefero is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
#
|
||||
# ***** END LICENSE BLOCK ***** */
|
||||
|
||||
/**
|
||||
* Add the new IDF_IssueRelation model.
|
||||
*
|
||||
*/
|
||||
|
||||
function IDF_Migrations_17AddIssueRelations_up($params=null)
|
||||
{
|
||||
$db = Pluf::db();
|
||||
$schema = new Pluf_DB_Schema($db);
|
||||
$schema->model = new IDF_IssueRelation();
|
||||
$schema->createTables();
|
||||
|
||||
// change the serialization format for added / removed labels in IDF_IssueComment
|
||||
$comments = Pluf::factory('IDF_IssueComment')->getList();
|
||||
foreach ($comments as $comment) {
|
||||
if (!isset($comment->changes['lb'])) continue;
|
||||
$changes = $comment->changes;
|
||||
$adds = $removals = array();
|
||||
foreach ($comment->changes['lb'] as $lb) {
|
||||
if (substr($lb, 0, 1) == '-')
|
||||
$removals[] = substr($lb, 1);
|
||||
else
|
||||
$adds[] = $lb;
|
||||
}
|
||||
$changes['lb'] = array();
|
||||
if (count($adds) > 0)
|
||||
$changes['lb']['add'] = $adds;
|
||||
if (count($removals) > 0)
|
||||
$changes['lb']['rem'] = $removals;
|
||||
$comment->changes = $changes;
|
||||
$comment->update();
|
||||
}
|
||||
}
|
||||
|
||||
function IDF_Migrations_17AddIssueRelations_down($params=null)
|
||||
{
|
||||
$db = Pluf::db();
|
||||
$schema = new Pluf_DB_Schema($db);
|
||||
$schema->model = new IDF_IssueRelation();
|
||||
$schema->dropTables();
|
||||
|
||||
// change the serialization format for added / removed labels in IDF_IssueComment
|
||||
$comments = Pluf::factory('IDF_IssueComment')->getList();
|
||||
foreach ($comments as $comment) {
|
||||
$changes = $comment->changes;
|
||||
if (empty($changes))
|
||||
continue;
|
||||
if (isset($changes['lb'])) {
|
||||
$labels = array();
|
||||
foreach ($changes['lb'] as $type => $lbs) {
|
||||
if (!is_array($lbs)) {
|
||||
$labels[] = $lbs;
|
||||
continue;
|
||||
}
|
||||
foreach ($lbs as $lb) {
|
||||
$labels[] = ($type == 'rem' ? '-' : '') . $lb;
|
||||
}
|
||||
}
|
||||
$changes['lb'] = $labels;
|
||||
}
|
||||
// while we're at it, remove any 'rel' changes
|
||||
unset($changes['rel']);
|
||||
$comment->changes = $changes;
|
||||
$comment->update();
|
||||
}
|
||||
}
|
||||
|
63
src/IDF/Migrations/18DownloadMD5.php
Normal file
63
src/IDF/Migrations/18DownloadMD5.php
Normal file
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/*
|
||||
# ***** BEGIN LICENSE BLOCK *****
|
||||
# This file is part of InDefero, an open source project management application.
|
||||
# Copyright (C) 2008-2011 Céondo Ltd and contributors.
|
||||
#
|
||||
# InDefero 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.
|
||||
#
|
||||
# InDefero is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
#
|
||||
# ***** END LICENSE BLOCK ***** */
|
||||
|
||||
/**
|
||||
* Add the md5 column for the download model.
|
||||
*/
|
||||
|
||||
function IDF_Migrations_18DownloadMD5_up($params=null)
|
||||
{
|
||||
// Add the row
|
||||
$table = Pluf::factory('IDF_Upload')->getSqlTable();
|
||||
$sql = array();
|
||||
$sql['PostgreSQL'] = 'ALTER TABLE '.$table.' ADD COLUMN "md5" VARCHAR(32) DEFAULT \'\'';
|
||||
$sql['MySQL'] = 'ALTER TABLE '.$table.' ADD COLUMN `md5` VARCHAR(32) DEFAULT \'\'';
|
||||
$db = Pluf::db();
|
||||
$engine = Pluf::f('db_engine');
|
||||
if (!isset($sql[$engine])) {
|
||||
throw new Exception('SQLite complex migration not supported.');
|
||||
}
|
||||
$db->execute($sql[$engine]);
|
||||
|
||||
// Process md5 of already uploaded file
|
||||
$files = Pluf::factory('IDF_Upload')->getList();
|
||||
foreach ($files as $f) {
|
||||
$f->md5 = md5_file (Pluf::f('upload_path') . '/' . $f->get_project()->shortname . '/files/' . $f->file);
|
||||
$f->update();
|
||||
}
|
||||
}
|
||||
|
||||
function IDF_Migrations_18DownloadMD5_down($params=null)
|
||||
{
|
||||
// Remove the row
|
||||
$table = Pluf::factory('IDF_Upload')->getSqlTable();
|
||||
$sql = array();
|
||||
$sql['PostgreSQL'] = 'ALTER TABLE '.$table.' DROP COLUMN "md5"';
|
||||
$sql['MySQL'] = 'ALTER TABLE '.$table.' DROP COLUMN `md5`';
|
||||
$db = Pluf::db();
|
||||
$engine = Pluf::f('db_engine');
|
||||
if (!isset($sql[$engine])) {
|
||||
throw new Exception('SQLite complex migration not supported.');
|
||||
}
|
||||
$db->execute($sql[$engine]);
|
||||
}
|
74
src/IDF/Migrations/19WikiPageAssocs.php
Normal file
74
src/IDF/Migrations/19WikiPageAssocs.php
Normal file
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/*
|
||||
# ***** BEGIN LICENSE BLOCK *****
|
||||
# This file is part of InDefero, an open source project management application.
|
||||
# Copyright (C) 2008-2011 Céondo Ltd and contributors.
|
||||
#
|
||||
# InDefero 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.
|
||||
#
|
||||
# InDefero is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
#
|
||||
# ***** END LICENSE BLOCK ***** */
|
||||
|
||||
function IDF_Migrations_19WikiPageAssocs_up($params=null)
|
||||
{
|
||||
$engine = Pluf::f('db_engine');
|
||||
if (!in_array($engine, array('MySQL', 'PostgreSQL'))) {
|
||||
throw new Exception('unsupported engine '.$engine);
|
||||
}
|
||||
|
||||
$db = Pluf::db();
|
||||
$intro = new Pluf_DB_Introspect($db);
|
||||
if (in_array($db->pfx.'idf_tag_idf_wiki_page_assoc', $intro->listTables())) {
|
||||
echo '19 skipping up migration - relation table has correct name already'."\n";
|
||||
return;
|
||||
}
|
||||
|
||||
$db->execute('ALTER TABLE '.$db->pfx.'idf_tag_idf_wikipage_assoc RENAME TO '.$db->pfx.'idf_tag_idf_wiki_page_assoc');
|
||||
$db->execute('ALTER TABLE '.$db->pfx.'idf_wikipage_pluf_user_assoc RENAME TO '.$db->pfx.'idf_wiki_page_pluf_user_assoc');
|
||||
|
||||
if ($engine === 'PostgreSQL') {
|
||||
$db->execute('ALTER TABLE '.$db->pfx.'idf_tag_idf_wiki_page_assoc RENAME COLUMN idf_wikipage_id TO idf_wiki_page_id');
|
||||
$db->execute('ALTER TABLE '.$db->pfx.'idf_wiki_page_pluf_user_assoc RENAME COLUMN idf_wikipage_id TO idf_wiki_page_id');
|
||||
} else if ($engine === 'MySQL') {
|
||||
$db->execute('ALTER TABLE '.$db->pfx.'idf_tag_idf_wiki_page_assoc CHANGE idf_wikipage_id idf_wiki_page_id MEDIUMINT NOT NULL');
|
||||
$db->execute('ALTER TABLE '.$db->pfx.'idf_wiki_page_pluf_user_assoc CHANGE idf_wikipage_id idf_wiki_page_id MEDIUMINT NOT NULL');
|
||||
}
|
||||
}
|
||||
|
||||
function IDF_Migrations_19WikiPageAssocs_down($params=null)
|
||||
{
|
||||
$engine = Pluf::f('db_engine');
|
||||
if (!in_array($engine, array('MySQL', 'PostgreSQL'))) {
|
||||
throw new Exception('unsupported engine '.$engine);
|
||||
}
|
||||
|
||||
$db = Pluf::db();
|
||||
$intro = new Pluf_DB_Introspect($db);
|
||||
if (in_array($db->pfx.'idf_tag_idf_wikipage_assoc', $intro->listTables())) {
|
||||
echo '19 skipping down migration - relation table has correct name already'."\n";
|
||||
return;
|
||||
}
|
||||
|
||||
$db->execute('ALTER TABLE '.$db->pfx.'idf_tag_idf_wiki_page_assoc RENAME TO '.$db->pfx.'idf_tag_idf_wikipage_assoc');
|
||||
$db->execute('ALTER TABLE '.$db->pfx.'idf_wiki_page_pluf_user_assoc RENAME TO '.$db->pfx.'idf_wikipage_pluf_user_assoc');
|
||||
|
||||
if ($engine === 'PostgreSQL') {
|
||||
$db->execute('ALTER TABLE '.$db->pfx.'idf_tag_idf_wikipage_assoc RENAME COLUMN idf_wiki_page_id TO idf_wikipage_id');
|
||||
$db->execute('ALTER TABLE '.$db->pfx.'idf_wikipage_pluf_user_assoc RENAME COLUMN idf_wiki_page_id TO idf_wikipage_id');
|
||||
} else if ($engine === 'MySQL') {
|
||||
$db->execute('ALTER TABLE '.$db->pfx.'idf_tag_idf_wikipage_assoc CHANGE idf_wiki_page_id idf_wikipage_id MEDIUMINT NOT NULL');
|
||||
$db->execute('ALTER TABLE '.$db->pfx.'idf_wikipage_pluf_user_assoc CHANGE idf_wiki_page_id idf_wikipage_id MEDIUMINT NOT NULL');
|
||||
}
|
||||
}
|
51
src/IDF/Migrations/20AddWikiResources.php
Normal file
51
src/IDF/Migrations/20AddWikiResources.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/*
|
||||
# ***** BEGIN LICENSE BLOCK *****
|
||||
# This file is part of InDefero, an open source project management application.
|
||||
# Copyright (C) 2008-2011 Céondo Ltd and contributors.
|
||||
#
|
||||
# InDefero 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.
|
||||
#
|
||||
# InDefero is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
#
|
||||
# ***** END LICENSE BLOCK ***** */
|
||||
|
||||
/**
|
||||
* Add the new IDF_Wiki_Resource and IDF_Wiki_ResourceRevision models.
|
||||
*
|
||||
*/
|
||||
|
||||
function IDF_Migrations_20AddWikiResources_up($params=null)
|
||||
{
|
||||
$db = Pluf::db();
|
||||
$schema = new Pluf_DB_Schema($db);
|
||||
|
||||
$schema->model = new IDF_Wiki_Resource();
|
||||
$schema->createTables();
|
||||
|
||||
$schema->model = new IDF_Wiki_ResourceRevision();
|
||||
$schema->createTables();
|
||||
}
|
||||
|
||||
function IDF_Migrations_20AddWikiResources_down($params=null)
|
||||
{
|
||||
$db = Pluf::db();
|
||||
$schema = new Pluf_DB_Schema($db);
|
||||
|
||||
$schema->model = new IDF_Wiki_ResourceRevision();
|
||||
$schema->dropTables();
|
||||
|
||||
$schema->model = new IDF_Wiki_Resource();
|
||||
$schema->dropTables();
|
||||
}
|
68
src/IDF/Migrations/21WikiPageRevisionName.php
Normal file
68
src/IDF/Migrations/21WikiPageRevisionName.php
Normal file
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/*
|
||||
# ***** BEGIN LICENSE BLOCK *****
|
||||
# This file is part of InDefero, an open source project management application.
|
||||
# Copyright (C) 2008-2011 Céondo Ltd and contributors.
|
||||
#
|
||||
# InDefero 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.
|
||||
#
|
||||
# InDefero is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
#
|
||||
# ***** END LICENSE BLOCK ***** */
|
||||
|
||||
function IDF_Migrations_21WikiPageRevisionName_up($params=null)
|
||||
{
|
||||
$engine = Pluf::f('db_engine');
|
||||
if (!in_array($engine, array('MySQL', 'PostgreSQL'))) {
|
||||
throw new Exception('unsupported engine '.$engine);
|
||||
}
|
||||
|
||||
$db = Pluf::db();
|
||||
$intro = new Pluf_DB_Introspect($db);
|
||||
if (in_array($db->pfx.'idf_wikipagerevs', $intro->listTables())) {
|
||||
echo '21 skipping up migration - table has correct name already'."\n";
|
||||
return;
|
||||
}
|
||||
|
||||
$db->execute('ALTER TABLE '.$db->pfx.'idf_wikirevisions RENAME TO '.$db->pfx.'idf_wikipagerevs');
|
||||
$db->execute("UPDATE ".$db->pfx."idf_timeline SET model_class='IDF_Wiki_Page' WHERE model_class LIKE 'IDF_WikiPage'");
|
||||
$db->execute("UPDATE ".$db->pfx."idf_timeline SET model_class='IDF_Wiki_PageRevision' WHERE model_class LIKE 'IDF_WikiRevision'");
|
||||
$db->execute("UPDATE ".$db->pfx."idf_search_occs SET model_class='IDF_Wiki_Page' WHERE model_class LIKE 'IDF_WikiPage'");
|
||||
$db->execute("UPDATE ".$db->pfx."idf_search_occs SET model_class='IDF_Wiki_PageRevision' WHERE model_class LIKE 'IDF_WikiRevision'");
|
||||
$db->execute("UPDATE ".$db->pfx."pluf_search_stats SET model_class='IDF_Wiki_Page' WHERE model_class LIKE 'IDF_WikiPage'");
|
||||
$db->execute("UPDATE ".$db->pfx."pluf_search_stats SET model_class='IDF_Wiki_PageRevision' WHERE model_class LIKE 'IDF_WikiRevision'");
|
||||
}
|
||||
|
||||
function IDF_Migrations_21WikiPageRevisionName_down($params=null)
|
||||
{
|
||||
$engine = Pluf::f('db_engine');
|
||||
if (!in_array($engine, array('MySQL', 'PostgreSQL'))) {
|
||||
throw new Exception('unsupported engine '.$engine);
|
||||
}
|
||||
|
||||
$db = Pluf::db();
|
||||
$intro = new Pluf_DB_Introspect($db);
|
||||
if (in_array($db->pfx.'idf_wikirevisions', $intro->listTables())) {
|
||||
echo '21 skipping down migration - table has correct name already'."\n";
|
||||
return;
|
||||
}
|
||||
|
||||
$db->execute('ALTER TABLE '.$db->pfx.'idf_wikipagerevs RENAME TO '.$db->pfx.'idf_wikirevisions');
|
||||
$db->execute("UPDATE ".$db->pfx."idf_timeline SET model_class='IDF_WikiPage' WHERE model_class LIKE 'IDF_Wiki_Page'");
|
||||
$db->execute("UPDATE ".$db->pfx."idf_timeline SET model_class='IDF_WikiRevision' WHERE model_class LIKE 'IDF_Wiki_PageRevision'");
|
||||
$db->execute("UPDATE ".$db->pfx."idf_search_occs SET model_class='IDF_WikiPage' WHERE model_class LIKE 'IDF_Wiki_Page'");
|
||||
$db->execute("UPDATE ".$db->pfx."idf_search_occs SET model_class='IDF_WikiRevision' WHERE model_class LIKE 'IDF_Wiki_PageRevision'");
|
||||
$db->execute("UPDATE ".$db->pfx."pluf_search_stats SET model_class='IDF_WikiPage' WHERE model_class LIKE 'IDF_Wiki_Page'");
|
||||
$db->execute("UPDATE ".$db->pfx."pluf_search_stats SET model_class='IDF_WikiRevision' WHERE model_class LIKE 'IDF_Wiki_PageRevision'");
|
||||
}
|
60
src/IDF/Migrations/22ProjectTagRelationTable.php
Normal file
60
src/IDF/Migrations/22ProjectTagRelationTable.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/*
|
||||
# ***** BEGIN LICENSE BLOCK *****
|
||||
# This file is part of InDefero, an open source project management application.
|
||||
# Copyright (C) 2008-2011 Céondo Ltd and contributors.
|
||||
#
|
||||
# InDefero 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.
|
||||
#
|
||||
# InDefero is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
#
|
||||
# ***** END LICENSE BLOCK ***** */
|
||||
|
||||
function IDF_Migrations_22ProjectTagRelationTable_up($params=null)
|
||||
{
|
||||
$db = Pluf::db();
|
||||
$table = $db->pfx.'idf_project_idf_tag_assoc';
|
||||
if (!in_array($db->engine, array('MySQL', 'PostgreSQL'))) {
|
||||
throw new Exception('unsupported engine '.$engine);
|
||||
}
|
||||
|
||||
$intro = new Pluf_DB_Introspect($db);
|
||||
if (in_array($table, $intro->listTables())) {
|
||||
echo '21 skipping up migration - table already exists'."\n";
|
||||
return;
|
||||
}
|
||||
|
||||
$schema = Pluf::factory('Pluf_DB_Schema_'.$db->engine, $db);
|
||||
$sql = $schema->getSqlCreate(new IDF_Project());
|
||||
$db->execute($sql[$table]);
|
||||
}
|
||||
|
||||
function IDF_Migrations_22ProjectTagRelationTable_down($params=null)
|
||||
{
|
||||
$db = Pluf::db();
|
||||
$table = $db->pfx.'idf_project_idf_tag_assoc';
|
||||
if (!in_array($db->engine, array('MySQL', 'PostgreSQL'))) {
|
||||
throw new Exception('unsupported engine '.$engine);
|
||||
}
|
||||
|
||||
$intro = new Pluf_DB_Introspect($db);
|
||||
if (!in_array($table, $intro->listTables())) {
|
||||
echo '22 skipping down migration - table does not exist'."\n";
|
||||
return;
|
||||
}
|
||||
|
||||
$schema = Pluf::factory('Pluf_DB_Schema_'.$db->engine, $db);
|
||||
$sql = $schema->getSqlDelete(new IDF_Project());
|
||||
$db->execute($sql[$table]);
|
||||
}
|
42
src/IDF/Migrations/23ProjectActivity.php
Normal file
42
src/IDF/Migrations/23ProjectActivity.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/*
|
||||
# ***** BEGIN LICENSE BLOCK *****
|
||||
# This file is part of InDefero, an open source project management application.
|
||||
# Copyright (C) 2008-2011 Céondo Ltd and contributors.
|
||||
#
|
||||
# InDefero 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.
|
||||
#
|
||||
# InDefero is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
#
|
||||
# ***** END LICENSE BLOCK ***** */
|
||||
|
||||
/**
|
||||
* Add the new IDF_ProjectActivity model.
|
||||
*
|
||||
*/
|
||||
function IDF_Migrations_23ProjectActivity_up($params=null)
|
||||
{
|
||||
$db = Pluf::db();
|
||||
$schema = new Pluf_DB_Schema($db);
|
||||
$schema->model = new IDF_ProjectActivity();
|
||||
$schema->createTables();
|
||||
}
|
||||
|
||||
function IDF_Migrations_23ProjectActivity_down($params=null)
|
||||
{
|
||||
$db = Pluf::db();
|
||||
$schema = new Pluf_DB_Schema($db);
|
||||
$schema->model = new IDF_ProjectActivity();
|
||||
$schema->dropTables();
|
||||
}
|
40
src/IDF/Migrations/24CurrentProjectActivity.php
Normal file
40
src/IDF/Migrations/24CurrentProjectActivity.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/*
|
||||
# ***** BEGIN LICENSE BLOCK *****
|
||||
# This file is part of InDefero, an open source project management application.
|
||||
# Copyright (C) 2008-2011 Céondo Ltd and contributors.
|
||||
#
|
||||
# InDefero 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.
|
||||
#
|
||||
# InDefero is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
#
|
||||
# ***** END LICENSE BLOCK ***** */
|
||||
|
||||
function IDF_Migrations_24CurrentProjectActivity_up($params=null)
|
||||
{
|
||||
$engine = Pluf::f('db_engine');
|
||||
$db = Pluf::db();
|
||||
|
||||
if ($engine === 'PostgreSQL') {
|
||||
$db->execute('ALTER TABLE '.$db->pfx.'idf_projects ADD COLUMN current_activity INTEGER');
|
||||
} else if ($engine === 'MySQL') {
|
||||
$db->execute('ALTER TABLE '.$db->pfx.'idf_projects ADD COLUMN current_activity MEDIUMINT');
|
||||
}
|
||||
}
|
||||
|
||||
function IDF_Migrations_24CurrentProjectActivity_down($params=null)
|
||||
{
|
||||
$db = Pluf::db();
|
||||
$db->execute('ALTER TABLE '.$db->pfx.'idf_projects DROP COLUMN current_activity');
|
||||
}
|
47
src/IDF/Migrations/25NullableProjectInTag.php
Normal file
47
src/IDF/Migrations/25NullableProjectInTag.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/*
|
||||
# ***** BEGIN LICENSE BLOCK *****
|
||||
# This file is part of InDefero, an open source project management application.
|
||||
# Copyright (C) 2008-2011 Céondo Ltd and contributors.
|
||||
#
|
||||
# InDefero 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.
|
||||
#
|
||||
# InDefero is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
#
|
||||
# ***** END LICENSE BLOCK ***** */
|
||||
|
||||
function IDF_Migrations_25NullableProjectInTag_up($params=null)
|
||||
{
|
||||
$engine = Pluf::f('db_engine');
|
||||
$db = Pluf::db();
|
||||
|
||||
if ($engine === 'PostgreSQL') {
|
||||
$db->execute('ALTER TABLE '.$db->pfx.'idf_tags ALTER COLUMN project DROP NOT NULL');
|
||||
} else if ($engine === 'MySQL') {
|
||||
$db->execute('ALTER TABLE '.$db->pfx.'idf_tags MODIFY project MEDIUMINT NULL');
|
||||
// this is only needed for non-transactional setups where MySQL set 0 as default value
|
||||
$db->execute('UPDATE '.$db->pfx.'idf_tags SET project=NULL WHERE project=0');
|
||||
}
|
||||
}
|
||||
|
||||
function IDF_Migrations_25NullableProjectInTag_down($params=null)
|
||||
{
|
||||
$engine = Pluf::f('db_engine');
|
||||
$db = Pluf::db();
|
||||
if ($engine === 'PostgreSQL') {
|
||||
$db->execute('ALTER TABLE '.$db->pfx.'idf_tags ALTER COLUMN project SET NOT NULL');
|
||||
} else if ($engine === 'MySQL') {
|
||||
$db->execute('ALTER TABLE '.$db->pfx.'idf_tags MODIFY project MEDIUMINT NOT NULL');
|
||||
}
|
||||
}
|
46
src/IDF/Migrations/26NullableActivityInProject.php
Normal file
46
src/IDF/Migrations/26NullableActivityInProject.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/*
|
||||
# ***** BEGIN LICENSE BLOCK *****
|
||||
# This file is part of InDefero, an open source project management application.
|
||||
# Copyright (C) 2008-2012 Céondo Ltd and contributors.
|
||||
#
|
||||
# InDefero 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.
|
||||
#
|
||||
# InDefero is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
#
|
||||
# ***** END LICENSE BLOCK ***** */
|
||||
|
||||
function IDF_Migrations_26NullableActivityInProject_up($params=null)
|
||||
{
|
||||
$engine = Pluf::f('db_engine');
|
||||
$db = Pluf::db();
|
||||
if ($engine === 'PostgreSQL') {
|
||||
$db->execute('ALTER TABLE '.$db->pfx.'idf_projects ALTER COLUMN current_activity DROP NOT NULL');
|
||||
} else if ($engine === 'MySQL') {
|
||||
$db->execute('ALTER TABLE '.$db->pfx.'idf_projects MODIFY current_activity MEDIUMINT NULL');
|
||||
// this is only needed for non-transactional setups where MySQL set 0 as default value
|
||||
$db->execute('UPDATE '.$db->pfx.'idf_projects SET current_activity=NULL WHERE current_activity=0');
|
||||
}
|
||||
}
|
||||
|
||||
function IDF_Migrations_26NullableActivityInProject_down($params=null)
|
||||
{
|
||||
$engine = Pluf::f('db_engine');
|
||||
$db = Pluf::db();
|
||||
if ($engine === 'PostgreSQL') {
|
||||
$db->execute('ALTER TABLE '.$db->pfx.'idf_projects ALTER COLUMN current_activity SET NOT NULL');
|
||||
} else if ($engine === 'MySQL') {
|
||||
$db->execute('ALTER TABLE '.$db->pfx.'idf_projects MODIFY current_activity MEDIUMINT NOT NULL');
|
||||
}
|
||||
}
|
55
src/IDF/Migrations/27IssueDueDate.php
Normal file
55
src/IDF/Migrations/27IssueDueDate.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/*
|
||||
# ***** BEGIN LICENSE BLOCK *****
|
||||
# This file is part of InDefero, an open source project management application.
|
||||
# Copyright (C) 2008-2011 Céondo Ltd and contributors.
|
||||
#
|
||||
# InDefero 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.
|
||||
#
|
||||
# InDefero is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
#
|
||||
# ***** END LICENSE BLOCK ***** */
|
||||
|
||||
/**
|
||||
* Add the private column for the project.
|
||||
*/
|
||||
|
||||
function IDF_Migrations_27IssueDueDate_up($params=null)
|
||||
{
|
||||
$table = Pluf::factory('IDF_Issue')->getSqlTable();
|
||||
$sql = array();
|
||||
$sql['PostgreSQL'] = 'ALTER TABLE '.$table.' ADD COLUMN "due_dtime" TIMESTAMP';
|
||||
$sql['MySQL'] = 'ALTER TABLE '.$table.' ADD COLUMN `due_dtime` DATETIME';
|
||||
$db = Pluf::db();
|
||||
$engine = Pluf::f('db_engine');
|
||||
if (!isset($sql[$engine])) {
|
||||
throw new Exception('SQLite complex migration not supported.');
|
||||
}
|
||||
$db->execute($sql[$engine]);
|
||||
}
|
||||
|
||||
function IDF_Migrations_27IssueDueDate_down($params=null)
|
||||
{
|
||||
$table = Pluf::factory('IDF_Issue')->getSqlTable();
|
||||
$sql = array();
|
||||
$sql['PostgreSQL'] = 'ALTER TABLE '.$table.' DROP COLUMN "due_dtime"';
|
||||
$sql['MySQL'] = 'ALTER TABLE '.$table.' DROP COLUMN `due_dtime`';
|
||||
$db = Pluf::db();
|
||||
$engine = Pluf::f('db_engine');
|
||||
if (!isset($sql[$engine])) {
|
||||
throw new Exception('SQLite complex migration not supported.');
|
||||
}
|
||||
$db->execute($sql[$engine]);
|
||||
|
||||
}
|
@@ -22,14 +22,14 @@
|
||||
# ***** END LICENSE BLOCK ***** */
|
||||
|
||||
/**
|
||||
* Add the download of files.
|
||||
* Add wiki functionality.
|
||||
*/
|
||||
|
||||
function IDF_Migrations_7Wiki_up($params=null)
|
||||
{
|
||||
$models = array(
|
||||
'IDF_WikiPage',
|
||||
'IDF_WikiRevision',
|
||||
'IDF_Wiki_Page',
|
||||
'IDF_Wiki_PageRevision',
|
||||
);
|
||||
$db = Pluf::db();
|
||||
$schema = new Pluf_DB_Schema($db);
|
||||
@@ -42,8 +42,8 @@ function IDF_Migrations_7Wiki_up($params=null)
|
||||
function IDF_Migrations_7Wiki_down($params=null)
|
||||
{
|
||||
$models = array(
|
||||
'IDF_WikiRevision',
|
||||
'IDF_WikiPage',
|
||||
'IDF_Wiki_PageRevision',
|
||||
'IDF_Wiki_Page',
|
||||
);
|
||||
$db = Pluf::db();
|
||||
$schema = new Pluf_DB_Schema($db);
|
||||
|
@@ -34,6 +34,7 @@ function IDF_Migrations_Backup_run($folder, $name=null)
|
||||
{
|
||||
$models = array(
|
||||
'IDF_Project',
|
||||
'IDF_ProjectActivity',
|
||||
'IDF_Tag',
|
||||
'IDF_Issue',
|
||||
'IDF_IssueComment',
|
||||
@@ -43,8 +44,10 @@ function IDF_Migrations_Backup_run($folder, $name=null)
|
||||
'IDF_IssueFile',
|
||||
'IDF_Commit',
|
||||
'IDF_Timeline',
|
||||
'IDF_WikiPage',
|
||||
'IDF_WikiRevision',
|
||||
'IDF_Wiki_Page',
|
||||
'IDF_Wiki_PageRevision',
|
||||
'IDF_Wiki_Resource',
|
||||
'IDF_Wiki_ResourceRevision',
|
||||
'IDF_Review',
|
||||
'IDF_Review_Patch',
|
||||
'IDF_Review_Comment',
|
||||
@@ -54,6 +57,7 @@ function IDF_Migrations_Backup_run($folder, $name=null)
|
||||
'IDF_Queue',
|
||||
'IDF_Gconf',
|
||||
'IDF_EmailAddress',
|
||||
'IDF_IssueRelation',
|
||||
);
|
||||
$db = Pluf::db();
|
||||
// Now, for each table, we dump the content in json, this is a
|
||||
@@ -80,6 +84,7 @@ function IDF_Migrations_Backup_restore($folder, $name)
|
||||
{
|
||||
$models = array(
|
||||
'IDF_Project',
|
||||
'IDF_ProjectActivity',
|
||||
'IDF_Tag',
|
||||
'IDF_Issue',
|
||||
'IDF_IssueComment',
|
||||
@@ -89,8 +94,10 @@ function IDF_Migrations_Backup_restore($folder, $name)
|
||||
'IDF_IssueFile',
|
||||
'IDF_Commit',
|
||||
'IDF_Timeline',
|
||||
'IDF_WikiPage',
|
||||
'IDF_WikiRevision',
|
||||
'IDF_Wiki_Resource',
|
||||
'IDF_Wiki_ResourceRevision',
|
||||
'IDF_Wiki_Page',
|
||||
'IDF_Wiki_PageRevision',
|
||||
'IDF_Review',
|
||||
'IDF_Review_Patch',
|
||||
'IDF_Review_Comment',
|
||||
@@ -100,6 +107,7 @@ function IDF_Migrations_Backup_restore($folder, $name)
|
||||
'IDF_Queue',
|
||||
'IDF_Gconf',
|
||||
'IDF_EmailAddress',
|
||||
'IDF_IssueRelation',
|
||||
);
|
||||
$db = Pluf::db();
|
||||
$schema = new Pluf_DB_Schema($db);
|
||||
@@ -111,5 +119,9 @@ function IDF_Migrations_Backup_restore($folder, $name)
|
||||
foreach ($full_data as $model => $data) {
|
||||
Pluf_Test_Fixture::load($data, false);
|
||||
}
|
||||
foreach ($models as $model) {
|
||||
$schema->model = new $model();
|
||||
$schema->createConstraints();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@@ -31,6 +31,7 @@ function IDF_Migrations_Install_setup($params=null)
|
||||
{
|
||||
$models = array(
|
||||
'IDF_Project',
|
||||
'IDF_ProjectActivity',
|
||||
'IDF_Tag',
|
||||
'IDF_Issue',
|
||||
'IDF_IssueComment',
|
||||
@@ -40,8 +41,10 @@ function IDF_Migrations_Install_setup($params=null)
|
||||
'IDF_IssueFile',
|
||||
'IDF_Commit',
|
||||
'IDF_Timeline',
|
||||
'IDF_WikiPage',
|
||||
'IDF_WikiRevision',
|
||||
'IDF_Wiki_Resource',
|
||||
'IDF_Wiki_ResourceRevision',
|
||||
'IDF_Wiki_Page',
|
||||
'IDF_Wiki_PageRevision',
|
||||
'IDF_Review',
|
||||
'IDF_Review_Patch',
|
||||
'IDF_Review_Comment',
|
||||
@@ -51,6 +54,7 @@ function IDF_Migrations_Install_setup($params=null)
|
||||
'IDF_Queue',
|
||||
'IDF_Gconf',
|
||||
'IDF_EmailAddress',
|
||||
'IDF_IssueRelation',
|
||||
);
|
||||
$db = Pluf::db();
|
||||
$schema = new Pluf_DB_Schema($db);
|
||||
@@ -58,6 +62,10 @@ function IDF_Migrations_Install_setup($params=null)
|
||||
$schema->model = new $model();
|
||||
$schema->createTables();
|
||||
}
|
||||
foreach ($models as $model) {
|
||||
$schema->model = new $model();
|
||||
$schema->createConstraints();
|
||||
}
|
||||
// Install the permissions
|
||||
$perm = new Pluf_Permission();
|
||||
$perm->name = 'Project membership';
|
||||
@@ -96,8 +104,10 @@ function IDF_Migrations_Install_teardown($params=null)
|
||||
'IDF_Review_Comment',
|
||||
'IDF_Review_Patch',
|
||||
'IDF_Review',
|
||||
'IDF_WikiRevision',
|
||||
'IDF_WikiPage',
|
||||
'IDF_Wiki_PageRevision',
|
||||
'IDF_Wiki_Page',
|
||||
'IDF_Wiki_ResourceRevision',
|
||||
'IDF_Wiki_Resource',
|
||||
'IDF_Timeline',
|
||||
'IDF_IssueFile',
|
||||
'IDF_Search_Occ',
|
||||
@@ -107,11 +117,17 @@ function IDF_Migrations_Install_teardown($params=null)
|
||||
'IDF_Issue',
|
||||
'IDF_Tag',
|
||||
'IDF_Commit',
|
||||
'IDF_ProjectActivity',
|
||||
'IDF_Project',
|
||||
'IDF_EmailAddress',
|
||||
'IDF_IssueRelation',
|
||||
);
|
||||
$db = Pluf::db();
|
||||
$schema = new Pluf_DB_Schema($db);
|
||||
foreach ($models as $model) {
|
||||
$schema->model = new $model();
|
||||
$schema->dropConstraints();
|
||||
}
|
||||
foreach ($models as $model) {
|
||||
$schema->model = new $model();
|
||||
$schema->dropTables();
|
||||
|
@@ -59,6 +59,16 @@ class IDF_Plugin_SyncGit_Cron
|
||||
$out .= sprintf($template, $cmd, $key->login, $content)."\n";
|
||||
}
|
||||
}
|
||||
$out = "# indefero start" . PHP_EOL . $out . "# indefero end" . PHP_EOL;
|
||||
|
||||
// We update only the part of the file between IDF_START / IDF_END comment
|
||||
$original_keys = file_get_contents($authorized_keys);
|
||||
if (strstr($original_keys, "# indefero start") && strstr($original_keys, "# indefero end")) {
|
||||
$out = preg_replace('%(#\sindefero\sstart).+(#\sindefero\send\s\s?)%isU',
|
||||
$out, $original_keys);
|
||||
} else {
|
||||
$out .= $original_keys;
|
||||
}
|
||||
file_put_contents($authorized_keys, $out, LOCK_EX);
|
||||
}
|
||||
|
||||
|
@@ -210,22 +210,23 @@ class IDF_Plugin_SyncGit_Serve
|
||||
// Indefero's one.
|
||||
$p = realpath(dirname(__FILE__).'/../../../../scripts/git-post-update');
|
||||
$p = Pluf::f('idf_plugin_syncgit_post_update', $p);
|
||||
if (!@unlink($fullpath.'/hooks/post-update')) {
|
||||
$post_update_hook = $fullpath.'/hooks/post-update';
|
||||
if (file_exists($post_update_hook) && !@unlink($post_update_hook)) {
|
||||
Pluf_Log::warn(array('IDF_Plugin_Git_Serve::initRepository',
|
||||
'post-update hook removal error.',
|
||||
$fullpath.'/hooks/post-update'));
|
||||
$post_update_hook));
|
||||
return;
|
||||
}
|
||||
$out = array();
|
||||
$res = 0;
|
||||
exec(sprintf(Pluf::f('idf_exec_cmd_prefix', '').'ln -s %s %s',
|
||||
escapeshellarg($p),
|
||||
escapeshellarg($fullpath.'/hooks/post-update')),
|
||||
escapeshellarg($post_update_hook)),
|
||||
$out, $res);
|
||||
if ($res != 0) {
|
||||
Pluf_Log::warn(array('IDF_Plugin_Git_Serve::initRepository',
|
||||
'post-update hook creation error.',
|
||||
$fullpath.'/hooks/post-update'));
|
||||
$post_update_hook));
|
||||
return;
|
||||
}
|
||||
Pluf_Log::debug(array('IDF_Plugin_Git_Serve::initRepository',
|
||||
|
@@ -178,7 +178,10 @@ class IDF_Plugin_SyncMercurial
|
||||
|
||||
// Generate hgrc content
|
||||
if (is_file($hgrc_file)) {
|
||||
$tmp_content = parse_ini_file($hgrc_file, true);
|
||||
$tmp_content = @parse_ini_file($hgrc_file, true, INI_SCANNER_RAW);
|
||||
if ($tmp_content === false) {
|
||||
throw new Exception('could not parse "'.$hgrc_file.'" because of syntax problems');
|
||||
}
|
||||
$tmp_content['web']['allow_push'] = $allow_push;
|
||||
}
|
||||
else {
|
||||
|
@@ -27,6 +27,18 @@
|
||||
*/
|
||||
class IDF_Plugin_SyncMonotone
|
||||
{
|
||||
private $old_err_rep = 0;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->old_err_rep = error_reporting(0);
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
error_reporting($this->old_err_rep);
|
||||
}
|
||||
|
||||
/**
|
||||
* Entry point of the plugin.
|
||||
*/
|
||||
@@ -80,24 +92,33 @@ class IDF_Plugin_SyncMonotone
|
||||
return;
|
||||
}
|
||||
|
||||
// This guard cleans up on any kind of error, and here is how it works:
|
||||
// As long as the guard is not committed, it keeps a reference to
|
||||
// the given project. When the guard is destroyed and the reference
|
||||
// is still present, it deletes the object. The deletion indirectly
|
||||
// also calls into this plugin again, as the project delete hook
|
||||
// will be called, that removes any changes we've made during the
|
||||
// process.
|
||||
$projectGuard = new IDF_Plugin_SyncMonotone_ModelGuard($project);
|
||||
|
||||
$projecttempl = Pluf::f('mtn_repositories', false);
|
||||
if ($projecttempl === false) {
|
||||
throw new IDF_Scm_Exception(
|
||||
__('"mtn_repositories" must be defined in your configuration file.')
|
||||
$this->_diagnoseProblem(
|
||||
__('"mtn_repositories" must be defined in your configuration file')
|
||||
);
|
||||
}
|
||||
|
||||
$usher_config = Pluf::f('mtn_usher_conf', false);
|
||||
if (!$usher_config || !is_writable($usher_config)) {
|
||||
throw new IDF_Scm_Exception(
|
||||
__('"mtn_usher_conf" does not exist or is not writable.')
|
||||
$this->_diagnoseProblem(
|
||||
__('"mtn_usher_conf" does not exist or is not writable')
|
||||
);
|
||||
}
|
||||
|
||||
$mtnpostpush = realpath(dirname(__FILE__) . '/../../../scripts/mtn-post-push');
|
||||
if (!file_exists($mtnpostpush)) {
|
||||
throw new IDF_Scm_Exception(sprintf(
|
||||
__('Could not find mtn-post-push script "%s".'), $mtnpostpush
|
||||
$this->_diagnoseProblem(sprintf(
|
||||
__('Could not find mtn-post-push script "%s"'), $mtnpostpush
|
||||
));
|
||||
}
|
||||
|
||||
@@ -110,13 +131,12 @@ class IDF_Plugin_SyncMonotone
|
||||
'monotonerc.in',
|
||||
'remote-automate-permissions.in',
|
||||
'hooks.d/',
|
||||
// this is linked and not copied to be able to update
|
||||
// the list of read-only commands on upgrades
|
||||
'hooks.d/indefero_authorize_remote_automate.conf',
|
||||
'hooks.d/indefero_authorize_remote_automate.lua',
|
||||
'hooks.d/indefero_post_push.conf.in',
|
||||
'hooks.d/indefero_post_push.lua',
|
||||
);
|
||||
// enable remote command execution of read-only commands
|
||||
// only for public projects
|
||||
if (!$project->private) {
|
||||
// this is linked and not copied to be able to update
|
||||
// the list of read-only commands on upgrades
|
||||
@@ -131,8 +151,8 @@ class IDF_Plugin_SyncMonotone
|
||||
}
|
||||
foreach ($confdir_contents as $content) {
|
||||
if (!file_exists($confdir.$content)) {
|
||||
throw new IDF_Scm_Exception(sprintf(
|
||||
__('The configuration file %s is missing.'), $content
|
||||
$this->_diagnoseProblem(sprintf(
|
||||
__('The configuration file "%s" is missing'), $content
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -140,14 +160,15 @@ class IDF_Plugin_SyncMonotone
|
||||
$shortname = $project->shortname;
|
||||
$projectpath = sprintf($projecttempl, $shortname);
|
||||
if (file_exists($projectpath)) {
|
||||
throw new IDF_Scm_Exception(sprintf(
|
||||
__('The project path %s already exists.'), $projectpath
|
||||
$this->_diagnoseProblem(sprintf(
|
||||
__('The project path "%s" already exists'), $projectpath
|
||||
));
|
||||
}
|
||||
|
||||
if (!mkdir($projectpath)) {
|
||||
throw new IDF_Scm_Exception(sprintf(
|
||||
__('The project path %s could not be created.'), $projectpath
|
||||
if (!@mkdir($projectpath)) {
|
||||
$this->_diagnoseProblem(sprintf(
|
||||
__('The project path "%s" could not be created'),
|
||||
$projectpath
|
||||
));
|
||||
}
|
||||
|
||||
@@ -156,7 +177,7 @@ class IDF_Plugin_SyncMonotone
|
||||
//
|
||||
$dbfile = $projectpath.'/database.mtn';
|
||||
$cmd = sprintf('db init -d %s', escapeshellarg($dbfile));
|
||||
self::_mtn_exec($cmd);
|
||||
$this->_mtn_exec($cmd);
|
||||
|
||||
//
|
||||
// step 2) create a server key
|
||||
@@ -175,16 +196,17 @@ class IDF_Plugin_SyncMonotone
|
||||
escapeshellarg($projectpath),
|
||||
escapeshellarg($serverkey)
|
||||
);
|
||||
self::_mtn_exec($cmd);
|
||||
$this->_mtn_exec($cmd);
|
||||
|
||||
//
|
||||
// step 3) create a client key, and save it in IDF
|
||||
//
|
||||
$keydir = Pluf::f('tmp_folder').'/mtn-client-keys';
|
||||
if (!file_exists($keydir)) {
|
||||
if (!mkdir($keydir)) {
|
||||
throw new IDF_Scm_Exception(sprintf(
|
||||
__('The key directory %s could not be created.'), $keydir
|
||||
if (!@mkdir($keydir)) {
|
||||
$this->_diagnoseProblem(sprintf(
|
||||
__('The key directory "%s" could not be created'),
|
||||
$keydir
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -194,14 +216,14 @@ class IDF_Plugin_SyncMonotone
|
||||
escapeshellarg($keydir),
|
||||
escapeshellarg($clientkey_name)
|
||||
);
|
||||
$keyinfo = self::_mtn_exec($cmd);
|
||||
$keyinfo = $this->_mtn_exec($cmd);
|
||||
|
||||
$parsed_keyinfo = array();
|
||||
try {
|
||||
$parsed_keyinfo = IDF_Scm_Monotone_BasicIO::parse($keyinfo);
|
||||
}
|
||||
catch (Exception $e) {
|
||||
throw new IDF_Scm_Exception(sprintf(
|
||||
$this->_diagnoseProblem(sprintf(
|
||||
__('Could not parse key information: %s'), $e->getMessage()
|
||||
));
|
||||
}
|
||||
@@ -219,13 +241,13 @@ class IDF_Plugin_SyncMonotone
|
||||
escapeshellarg($keydir),
|
||||
escapeshellarg($clientkey_hash)
|
||||
);
|
||||
$clientkey_pubdata = self::_mtn_exec($cmd);
|
||||
$clientkey_pubdata = $this->_mtn_exec($cmd);
|
||||
|
||||
$cmd = sprintf('au put_public_key --db=%s %s',
|
||||
escapeshellarg($dbfile),
|
||||
escapeshellarg($clientkey_pubdata)
|
||||
);
|
||||
self::_mtn_exec($cmd);
|
||||
$this->_mtn_exec($cmd);
|
||||
|
||||
//
|
||||
// step 4) setup the configuration
|
||||
@@ -238,18 +260,20 @@ class IDF_Plugin_SyncMonotone
|
||||
foreach ($confdir_contents as $content) {
|
||||
$filepath = $projectpath.'/'.$content;
|
||||
if (substr($content, -1) == '/') {
|
||||
if (!mkdir($filepath)) {
|
||||
throw new IDF_Scm_Exception(sprintf(
|
||||
__('Could not create configuration directory "%s"'), $filepath
|
||||
if (!@mkdir($filepath)) {
|
||||
$this->_diagnoseProblem(sprintf(
|
||||
__('Could not create configuration directory "%s"'),
|
||||
$filepath
|
||||
));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (substr($content, -3) != '.in') {
|
||||
if (!symlink($confdir.$content, $filepath)) {
|
||||
IDF_Scm_Exception(sprintf(
|
||||
__('Could not create symlink "%s"'), $filepath
|
||||
if (!@symlink($confdir.$content, $filepath)) {
|
||||
$this->_diagnoseProblem(sprintf(
|
||||
__('Could not create symlink for configuration file "%s"'),
|
||||
$filepath
|
||||
));
|
||||
}
|
||||
continue;
|
||||
@@ -264,9 +288,10 @@ class IDF_Plugin_SyncMonotone
|
||||
|
||||
// remove the .in
|
||||
$filepath = substr($filepath, 0, -3);
|
||||
if (file_put_contents($filepath, $filecontents, LOCK_EX) === false) {
|
||||
throw new IDF_Scm_Exception(sprintf(
|
||||
__('Could not write configuration file "%s"'), $filepath
|
||||
if (@file_put_contents($filepath, $filecontents, LOCK_EX) === false) {
|
||||
$this->_diagnoseProblem(sprintf(
|
||||
__('Could not write configuration file "%s"'),
|
||||
$filepath
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -280,8 +305,8 @@ class IDF_Plugin_SyncMonotone
|
||||
$parsed_config = IDF_Scm_Monotone_BasicIO::parse($usher_rc);
|
||||
}
|
||||
catch (Exception $e) {
|
||||
throw new IDF_Scm_Exception(sprintf(
|
||||
__('Could not parse usher configuration in "%s": %s'),
|
||||
$this->_diagnoseProblem(sprintf(
|
||||
__('Could not parse usher configuration in "%1$s": %2$s'),
|
||||
$usher_config, $e->getMessage()
|
||||
));
|
||||
}
|
||||
@@ -291,7 +316,7 @@ class IDF_Plugin_SyncMonotone
|
||||
foreach ($stanzas as $stanza_line) {
|
||||
if ($stanza_line['key'] == 'server' &&
|
||||
$stanza_line['values'][0] == $shortname) {
|
||||
throw new IDF_Scm_Exception(sprintf(
|
||||
$this->_diagnoseProblem(sprintf(
|
||||
__('usher configuration already contains a server '.
|
||||
'entry named "%s"'),
|
||||
$shortname
|
||||
@@ -315,9 +340,10 @@ class IDF_Plugin_SyncMonotone
|
||||
|
||||
// FIXME: more sanity - what happens on failing writes? we do not
|
||||
// have a backup copy of usher.conf around...
|
||||
if (file_put_contents($usher_config, $usher_rc, LOCK_EX) === false) {
|
||||
throw new IDF_Scm_Exception(sprintf(
|
||||
__('Could not write usher configuration file "%s"'), $usher_config
|
||||
if (@file_put_contents($usher_config, $usher_rc, LOCK_EX) === false) {
|
||||
$this->_diagnoseProblem(sprintf(
|
||||
__('Could not write usher configuration file "%s"'),
|
||||
$usher_config
|
||||
));
|
||||
}
|
||||
|
||||
@@ -325,6 +351,9 @@ class IDF_Plugin_SyncMonotone
|
||||
// step 6) reload usher to pick up the new configuration
|
||||
//
|
||||
IDF_Scm_Monotone_Usher::reload();
|
||||
|
||||
// commit the guard, so the newly created project is not deleted
|
||||
$projectGuard->commit();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -345,8 +374,8 @@ class IDF_Plugin_SyncMonotone
|
||||
$mtn = IDF_Scm_Monotone::factory($project);
|
||||
$stdio = $mtn->getStdio();
|
||||
|
||||
$projectpath = self::_get_project_path($project);
|
||||
$auth_ids = self::_get_authorized_user_ids($project);
|
||||
$projectpath = $this->_get_project_path($project);
|
||||
$auth_ids = $this->_get_authorized_user_ids($project);
|
||||
$key_ids = array();
|
||||
foreach ($auth_ids as $auth_id) {
|
||||
$sql = new Pluf_SQL('user=%s', array($auth_id));
|
||||
@@ -361,9 +390,10 @@ class IDF_Plugin_SyncMonotone
|
||||
|
||||
$write_permissions = implode("\n", $key_ids);
|
||||
$rcfile = $projectpath.'/write-permissions';
|
||||
if (file_put_contents($rcfile, $write_permissions, LOCK_EX) === false) {
|
||||
throw new IDF_Scm_Exception(sprintf(
|
||||
__('Could not write write-permissions file "%s"'), $rcfile
|
||||
if (@file_put_contents($rcfile, $write_permissions, LOCK_EX) === false) {
|
||||
$this->_diagnoseProblem(sprintf(
|
||||
__('Could not write write-permissions file "%s"'),
|
||||
$rcfile
|
||||
));
|
||||
}
|
||||
|
||||
@@ -382,11 +412,13 @@ class IDF_Plugin_SyncMonotone
|
||||
array('key' => 'allow', 'values' => array('*')),
|
||||
);
|
||||
}
|
||||
|
||||
$read_permissions = IDF_Scm_Monotone_BasicIO::compile(array($stanza));
|
||||
$rcfile = $projectpath.'/read-permissions';
|
||||
if (file_put_contents($rcfile, $read_permissions, LOCK_EX) === false) {
|
||||
throw new IDF_Scm_Exception(sprintf(
|
||||
__('Could not write read-permissions file "%s"'), $rcfile
|
||||
if (@file_put_contents($rcfile, $read_permissions, LOCK_EX) === false) {
|
||||
$this->_diagnoseProblem(sprintf(
|
||||
__('Could not write read-permissions file "%s"'),
|
||||
$rcfile
|
||||
));
|
||||
}
|
||||
|
||||
@@ -401,16 +433,16 @@ class IDF_Plugin_SyncMonotone
|
||||
|
||||
$serverRestartRequired = false;
|
||||
if ($project->private && file_exists($projectfile) && is_link($projectfile)) {
|
||||
if (!unlink($projectfile)) {
|
||||
IDF_Scm_Exception(sprintf(
|
||||
if (!@unlink($projectfile)) {
|
||||
$this->_diagnoseProblem(sprintf(
|
||||
__('Could not remove symlink "%s"'), $projectfile
|
||||
));
|
||||
}
|
||||
$serverRestartRequired = true;
|
||||
} else
|
||||
if (!$project->private && !file_exists($projectfile)) {
|
||||
if (!symlink($templatefile, $projectfile)) {
|
||||
throw new IDF_Scm_Exception(sprintf(
|
||||
if (!@symlink($templatefile, $projectfile)) {
|
||||
$this->_diagnoseProblem(sprintf(
|
||||
__('Could not create symlink "%s"'), $projectfile
|
||||
));
|
||||
}
|
||||
@@ -422,6 +454,9 @@ class IDF_Plugin_SyncMonotone
|
||||
// seems to be ignored when the server should be started
|
||||
// again immediately afterwards
|
||||
IDF_Scm_Monotone_Usher::killServer($project->shortname);
|
||||
// give usher some time to cool down, otherwise it might hang
|
||||
// (see https://code.monotone.ca/p/contrib/issues/175/)
|
||||
sleep(2);
|
||||
IDF_Scm_Monotone_Usher::startServer($project->shortname);
|
||||
}
|
||||
}
|
||||
@@ -443,8 +478,8 @@ class IDF_Plugin_SyncMonotone
|
||||
|
||||
$usher_config = Pluf::f('mtn_usher_conf', false);
|
||||
if (!$usher_config || !is_writable($usher_config)) {
|
||||
throw new IDF_Scm_Exception(
|
||||
__('"mtn_usher_conf" does not exist or is not writable.')
|
||||
$this->_diagnoseProblem(
|
||||
__('"mtn_usher_conf" does not exist or is not writable')
|
||||
);
|
||||
}
|
||||
|
||||
@@ -453,16 +488,16 @@ class IDF_Plugin_SyncMonotone
|
||||
|
||||
$projecttempl = Pluf::f('mtn_repositories', false);
|
||||
if ($projecttempl === false) {
|
||||
throw new IDF_Scm_Exception(
|
||||
__('"mtn_repositories" must be defined in your configuration file.')
|
||||
$this->_diagnoseProblem(
|
||||
__('"mtn_repositories" must be defined in your configuration file')
|
||||
);
|
||||
}
|
||||
|
||||
$projectpath = sprintf($projecttempl, $shortname);
|
||||
if (file_exists($projectpath)) {
|
||||
if (!self::_delete_recursive($projectpath)) {
|
||||
throw new IDF_Scm_Exception(sprintf(
|
||||
__('One or more paths underknees %s could not be deleted.'), $projectpath
|
||||
if (!$this->_delete_recursive($projectpath)) {
|
||||
$this->_diagnoseProblem(sprintf(
|
||||
__('One or more paths underneath %s could not be deleted'), $projectpath
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -473,8 +508,9 @@ class IDF_Plugin_SyncMonotone
|
||||
if ($keyname && $keyhash &&
|
||||
file_exists($keydir .'/'. $keyname . '.' . $keyhash)) {
|
||||
if (!@unlink($keydir .'/'. $keyname . '.' . $keyhash)) {
|
||||
throw new IDF_Scm_Exception(sprintf(
|
||||
__('Could not delete client private key %s'), $keyname
|
||||
$this->_diagnoseProblem(sprintf(
|
||||
__('Could not delete client private key "%s"'),
|
||||
$keyname
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -485,8 +521,8 @@ class IDF_Plugin_SyncMonotone
|
||||
$parsed_config = IDF_Scm_Monotone_BasicIO::parse($usher_rc);
|
||||
}
|
||||
catch (Exception $e) {
|
||||
throw new IDF_Scm_Exception(sprintf(
|
||||
__('Could not parse usher configuration in "%s": %s'),
|
||||
$this->_diagnoseProblem(sprintf(
|
||||
__('Could not parse usher configuration in "%1$s": %2$s'),
|
||||
$usher_config, $e->getMessage()
|
||||
));
|
||||
}
|
||||
@@ -505,9 +541,10 @@ class IDF_Plugin_SyncMonotone
|
||||
|
||||
// FIXME: more sanity - what happens on failing writes? we do not
|
||||
// have a backup copy of usher.conf around...
|
||||
if (file_put_contents($usher_config, $usher_rc, LOCK_EX) === false) {
|
||||
throw new IDF_Scm_Exception(sprintf(
|
||||
__('Could not write usher configuration file "%s"'), $usher_config
|
||||
if (@file_put_contents($usher_config, $usher_rc, LOCK_EX) === false) {
|
||||
$this->_diagnoseProblem(sprintf(
|
||||
__('Could not write usher configuration file "%s"'),
|
||||
$usher_config
|
||||
));
|
||||
}
|
||||
|
||||
@@ -528,6 +565,8 @@ class IDF_Plugin_SyncMonotone
|
||||
return;
|
||||
}
|
||||
|
||||
$keyGuard = new IDF_Plugin_SyncMonotone_ModelGuard($key);
|
||||
|
||||
foreach (Pluf::factory('IDF_Project')->getList() as $project) {
|
||||
$conf = new IDF_Conf();
|
||||
$conf->setProject($project);
|
||||
@@ -535,8 +574,8 @@ class IDF_Plugin_SyncMonotone
|
||||
if ($scm != 'mtn')
|
||||
continue;
|
||||
|
||||
$projectpath = self::_get_project_path($project);
|
||||
$auth_ids = self::_get_authorized_user_ids($project);
|
||||
$projectpath = $this->_get_project_path($project);
|
||||
$auth_ids = $this->_get_authorized_user_ids($project);
|
||||
if (!in_array($key->user, $auth_ids))
|
||||
continue;
|
||||
|
||||
@@ -556,8 +595,8 @@ class IDF_Plugin_SyncMonotone
|
||||
$parsed_read_perms = IDF_Scm_Monotone_BasicIO::parse($read_perms);
|
||||
}
|
||||
catch (Exception $e) {
|
||||
throw new IDF_Scm_Exception(sprintf(
|
||||
__('Could not parse read-permissions for project "%s": %s'),
|
||||
$this->_diagnoseProblem(sprintf(
|
||||
__('Could not parse read-permissions for project "%1$s": %2$s'),
|
||||
$shortname, $e->getMessage()
|
||||
));
|
||||
}
|
||||
@@ -598,10 +637,11 @@ class IDF_Plugin_SyncMonotone
|
||||
|
||||
$read_perms = IDF_Scm_Monotone_BasicIO::compile($parsed_read_perms);
|
||||
|
||||
if (file_put_contents($projectpath.'/read-permissions',
|
||||
if (@file_put_contents($projectpath.'/read-permissions',
|
||||
$read_perms, LOCK_EX) === false) {
|
||||
throw new IDF_Scm_Exception(sprintf(
|
||||
__('Could not write read-permissions for project "%s"'), $shortname
|
||||
$this->_diagnoseProblem(sprintf(
|
||||
__('Could not write read-permissions for project "%s"'),
|
||||
$shortname
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -611,9 +651,9 @@ class IDF_Plugin_SyncMonotone
|
||||
if (!in_array('*', $lines) && !in_array($mtn_key_id, $lines)) {
|
||||
$lines[] = $mtn_key_id;
|
||||
}
|
||||
if (file_put_contents($projectpath.'/write-permissions',
|
||||
if (@file_put_contents($projectpath.'/write-permissions',
|
||||
implode("\n", $lines) . "\n", LOCK_EX) === false) {
|
||||
throw new IDF_Scm_Exception(sprintf(
|
||||
$this->_diagnoseProblem(sprintf(
|
||||
__('Could not write write-permissions file for project "%s"'),
|
||||
$shortname
|
||||
));
|
||||
@@ -623,6 +663,8 @@ class IDF_Plugin_SyncMonotone
|
||||
$stdio = $mtn->getStdio();
|
||||
$stdio->exec(array('put_public_key', $key->content));
|
||||
}
|
||||
|
||||
$keyGuard->commit();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -651,8 +693,8 @@ class IDF_Plugin_SyncMonotone
|
||||
if ($scm != 'mtn')
|
||||
continue;
|
||||
|
||||
$projectpath = self::_get_project_path($project);
|
||||
$auth_ids = self::_get_authorized_user_ids($project);
|
||||
$projectpath = $this->_get_project_path($project);
|
||||
$auth_ids = $this->_get_authorized_user_ids($project);
|
||||
if (!in_array($key->user, $auth_ids))
|
||||
continue;
|
||||
|
||||
@@ -672,8 +714,8 @@ class IDF_Plugin_SyncMonotone
|
||||
$parsed_read_perms = IDF_Scm_Monotone_BasicIO::parse($read_perms);
|
||||
}
|
||||
catch (Exception $e) {
|
||||
throw new IDF_Scm_Exception(sprintf(
|
||||
__('Could not parse read-permissions for project "%s": %s'),
|
||||
$this->_diagnoseProblem(sprintf(
|
||||
__('Could not parse read-permissions for project "%1$s": %2$s'),
|
||||
$shortname, $e->getMessage()
|
||||
));
|
||||
}
|
||||
@@ -693,10 +735,11 @@ class IDF_Plugin_SyncMonotone
|
||||
|
||||
$read_perms = IDF_Scm_Monotone_BasicIO::compile($parsed_read_perms);
|
||||
|
||||
if (file_put_contents($projectpath.'/read-permissions',
|
||||
if (@file_put_contents($projectpath.'/read-permissions',
|
||||
$read_perms, LOCK_EX) === false) {
|
||||
throw new IDF_Scm_Exception(sprintf(
|
||||
__('Could not write read-permissions for project "%s"'), $shortname
|
||||
$this->_diagnoseProblem(sprintf(
|
||||
__('Could not write read-permissions for project "%s"'),
|
||||
$shortname
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -711,9 +754,9 @@ class IDF_Plugin_SyncMonotone
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (file_put_contents($projectpath.'/write-permissions',
|
||||
implode("\n", $lines) . "\n", LOCK_EX) === false) {
|
||||
throw new IDF_Scm_Exception(sprintf(
|
||||
if (@file_put_contents($projectpath.'/write-permissions',
|
||||
implode("\n", $lines) . "\n", LOCK_EX) === false) {
|
||||
$this->_diagnoseProblem(sprintf(
|
||||
__('Could not write write-permissions file for project "%s"'),
|
||||
$shortname
|
||||
));
|
||||
@@ -762,7 +805,43 @@ class IDF_Plugin_SyncMonotone
|
||||
));
|
||||
}
|
||||
|
||||
private static function _get_authorized_user_ids($project)
|
||||
private function _get_project_path($project)
|
||||
{
|
||||
$projecttempl = Pluf::f('mtn_repositories', false);
|
||||
if ($projecttempl === false) {
|
||||
$this->_diagnoseProblem(
|
||||
__('"mtn_repositories" must be defined in your configuration file.')
|
||||
);
|
||||
}
|
||||
|
||||
$projectpath = sprintf($projecttempl, $project->shortname);
|
||||
if (!file_exists($projectpath)) {
|
||||
$this->_diagnoseProblem(sprintf(
|
||||
__('The project path %s does not exists.'), $projectpath
|
||||
));
|
||||
}
|
||||
return $projectpath;
|
||||
}
|
||||
|
||||
private function _mtn_exec($cmd)
|
||||
{
|
||||
$fullcmd = sprintf('%s %s %s',
|
||||
Pluf::f('idf_exec_cmd_prefix', ''),
|
||||
Pluf::f('mtn_path', 'mtn'),
|
||||
$cmd
|
||||
);
|
||||
|
||||
$output = $return = null;
|
||||
exec($fullcmd, $output, $return);
|
||||
if ($return != 0) {
|
||||
$this->_diagnoseProblem(sprintf(
|
||||
__('The command "%s" could not be executed.'), $cmd
|
||||
));
|
||||
}
|
||||
return implode("\n", $output);
|
||||
}
|
||||
|
||||
private function _get_authorized_user_ids($project)
|
||||
{
|
||||
$mem = $project->getMembershipData();
|
||||
$members = array_merge((array)$mem['members'],
|
||||
@@ -775,43 +854,7 @@ class IDF_Plugin_SyncMonotone
|
||||
return $userids;
|
||||
}
|
||||
|
||||
private static function _get_project_path($project)
|
||||
{
|
||||
$projecttempl = Pluf::f('mtn_repositories', false);
|
||||
if ($projecttempl === false) {
|
||||
throw new IDF_Scm_Exception(
|
||||
__('"mtn_repositories" must be defined in your configuration file.')
|
||||
);
|
||||
}
|
||||
|
||||
$projectpath = sprintf($projecttempl, $project->shortname);
|
||||
if (!file_exists($projectpath)) {
|
||||
throw new IDF_Scm_Exception(sprintf(
|
||||
__('The project path %s does not exists.'), $projectpath
|
||||
));
|
||||
}
|
||||
return $projectpath;
|
||||
}
|
||||
|
||||
private static function _mtn_exec($cmd)
|
||||
{
|
||||
$fullcmd = sprintf('%s %s %s',
|
||||
Pluf::f('idf_exec_cmd_prefix', ''),
|
||||
Pluf::f('mtn_path', 'mtn'),
|
||||
$cmd
|
||||
);
|
||||
|
||||
$output = $return = null;
|
||||
exec($fullcmd, $output, $return);
|
||||
if ($return != 0) {
|
||||
throw new IDF_Scm_Exception(sprintf(
|
||||
__('The command "%s" could not be executed.'), $cmd
|
||||
));
|
||||
}
|
||||
return implode("\n", $output);
|
||||
}
|
||||
|
||||
private static function _delete_recursive($path)
|
||||
private function _delete_recursive($path)
|
||||
{
|
||||
if (is_file($path) || is_link($path)) {
|
||||
return @unlink($path);
|
||||
@@ -821,10 +864,48 @@ class IDF_Plugin_SyncMonotone
|
||||
$scan = glob(rtrim($path, '/') . '/*');
|
||||
$status = 0;
|
||||
foreach ($scan as $subpath) {
|
||||
$status |= self::_delete_recursive($subpath);
|
||||
$status |= $this->_delete_recursive($subpath);
|
||||
}
|
||||
$status |= rmdir($path);
|
||||
$status |= @rmdir($path);
|
||||
return $status;
|
||||
}
|
||||
}
|
||||
|
||||
private function _diagnoseProblem($msg)
|
||||
{
|
||||
$system_err = error_get_last();
|
||||
if (!empty($system_err)) {
|
||||
$msg .= ': '.$system_err['message'];
|
||||
}
|
||||
|
||||
error_reporting($this->old_err_rep);
|
||||
throw new IDF_Scm_Exception($msg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple helper class that deletes the model instance if
|
||||
* it is not committed
|
||||
*/
|
||||
class IDF_Plugin_SyncMonotone_ModelGuard
|
||||
{
|
||||
private $model;
|
||||
|
||||
public function __construct(Pluf_Model $m)
|
||||
{
|
||||
$this->model = $m;
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
if ($this->model == null)
|
||||
return;
|
||||
$this->model->delete();
|
||||
}
|
||||
|
||||
public function commit()
|
||||
{
|
||||
$this->model = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -67,7 +67,7 @@ class IDF_Project extends Pluf_Model
|
||||
'blank' => false,
|
||||
'size' => 50,
|
||||
'verbose' => __('short name'),
|
||||
'help_text' => __('Used in the url to access the project, must be short with only letters and numbers.'),
|
||||
'help_text' => __('Used in the URL to access the project, must be short with only letters and numbers.'),
|
||||
'unique' => true,
|
||||
),
|
||||
'shortdesc' =>
|
||||
@@ -84,7 +84,14 @@ class IDF_Project extends Pluf_Model
|
||||
'blank' => false,
|
||||
'size' => 250,
|
||||
'verbose' => __('description'),
|
||||
'help_text' => __('The description can be extended using the markdown syntax.'),
|
||||
'help_text' => __('The description can be extended using the Markdown syntax.'),
|
||||
),
|
||||
'tags' =>
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Manytomany',
|
||||
'blank' => true,
|
||||
'model' => 'IDF_Tag',
|
||||
'verbose' => __('labels'),
|
||||
),
|
||||
'private' =>
|
||||
array(
|
||||
@@ -93,7 +100,30 @@ class IDF_Project extends Pluf_Model
|
||||
'verbose' => __('private'),
|
||||
'default' => 0,
|
||||
),
|
||||
);
|
||||
'current_activity' =>
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Foreignkey',
|
||||
'model' => 'IDF_ProjectActivity',
|
||||
'blank' => true,
|
||||
'is_null' => true,
|
||||
'default' => null,
|
||||
'verbose' => __('current project activity'),
|
||||
),
|
||||
);
|
||||
$activityTable = $this->_con->pfx.'idf_projectactivities';
|
||||
$tagTable = $this->_con->pfx.'idf_project_idf_tag_assoc';
|
||||
$this->_a['views'] = array(
|
||||
'join_activities_and_tags' =>
|
||||
array(
|
||||
'join' => 'LEFT JOIN '.$activityTable.' ON current_activity='.$activityTable.'.id '
|
||||
.'LEFT JOIN '.$tagTable.' ON idf_project_id='.$this->getSqlTable().'.id',
|
||||
'select' => 'DISTINCT '.$this->getSelect().', date, value',
|
||||
'props' => array(
|
||||
'date' => 'current_activity_date',
|
||||
'value' => 'current_activity_value'
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -140,7 +170,81 @@ class IDF_Project extends Pluf_Model
|
||||
* @param IDF_Tag Subfilter with a label (null)
|
||||
* @return int Count
|
||||
*/
|
||||
public function getIssueCountByStatus($status='open', $label=null)
|
||||
public function getIssueCountByOwner($status='open')
|
||||
{
|
||||
switch ($status) {
|
||||
case 'open':
|
||||
$tags = implode(',', $this->getTagIdsByStatus('open'));
|
||||
break;
|
||||
case 'closed':
|
||||
default:
|
||||
$tags = implode(',', $this->getTagIdsByStatus('closed'));
|
||||
break;
|
||||
}
|
||||
$sqlIssueTable = Pluf::factory('IDF_Issue')->getSqlTable();
|
||||
$query = "SELECT uid AS id,COUNT(uid) AS nb
|
||||
FROM (
|
||||
SELECT COALESCE(owner, -1) AS uid
|
||||
FROM $sqlIssueTable
|
||||
WHERE status IN ($tags)
|
||||
) AS ff
|
||||
GROUP BY uid";
|
||||
|
||||
$db = Pluf::db();
|
||||
$dbData = $db->select($query);
|
||||
$ownerStatistics = array();
|
||||
foreach ($dbData as $k => $v) {
|
||||
$key = ($v['id'] === '-1') ? null : $v['id'];
|
||||
$ownerStatistics[$key] = (int)$v['nb'];
|
||||
}
|
||||
|
||||
arsort($ownerStatistics);
|
||||
|
||||
return $ownerStatistics;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of overdue issues.
|
||||
*
|
||||
* @return int Count
|
||||
*/
|
||||
public function getIssueCountByDueDate($due='overdue', $label=null, $ids=array())
|
||||
{
|
||||
$tags = array();
|
||||
foreach ($this->getTagsFromConfig('labels_issue_open', IDF_Form_IssueTrackingConf::init_open, 'Status') as $tag) {
|
||||
$tags[] = (int)$tag->id;
|
||||
}
|
||||
if (count($tags) == 0) return array();
|
||||
$sql = new Pluf_SQL(sprintf('project=%%s AND status IN (%s)', implode(', ', $tags)), array($this->id));
|
||||
if (!is_null($label)) {
|
||||
$sql2 = new Pluf_SQL('idf_tag_id=%s', array($label->id));
|
||||
$sql->SAnd($sql2);
|
||||
}
|
||||
if (count($ids) > 0) {
|
||||
$sql2 = new Pluf_SQL(sprintf('id IN (%s)', implode(', ', $ids)));
|
||||
$sql->SAnd($sql2);
|
||||
}
|
||||
if('overdue' === $due) {
|
||||
$sql3 = new Pluf_SQL('due_dtime < NOW()');
|
||||
} else {
|
||||
$sql3 = new Pluf_SQL('due_dtime >= NOW()');
|
||||
}
|
||||
$sql->SAnd($sql3);
|
||||
$params = array('filter' => $sql->gen());
|
||||
if (!is_null($label)) { $params['view'] = 'join_tags'; }
|
||||
$gissue = new IDF_Issue();
|
||||
return $gissue->getCount($params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of open/closed issues.
|
||||
*
|
||||
* @param string Status ('open'), 'closed'
|
||||
* @param IDF_Tag Subfilter with a label (null)
|
||||
* @param array Restrict further to a list of ids
|
||||
* @return int Count
|
||||
*/
|
||||
public function getIssueCountByStatus($status='open', $label=null, $ids=array())
|
||||
{
|
||||
switch ($status) {
|
||||
case 'open':
|
||||
@@ -163,12 +267,48 @@ class IDF_Project extends Pluf_Model
|
||||
$sql2 = new Pluf_SQL('idf_tag_id=%s', array($label->id));
|
||||
$sql->SAnd($sql2);
|
||||
}
|
||||
if (count($ids) > 0) {
|
||||
$sql2 = new Pluf_SQL(sprintf('id IN (%s)', implode(', ', $ids)));
|
||||
$sql->SAnd($sql2);
|
||||
}
|
||||
$params = array('filter' => $sql->gen());
|
||||
if (!is_null($label)) { $params['view'] = 'join_tags'; }
|
||||
$gissue = new IDF_Issue();
|
||||
return $gissue->getCount($params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the tags for a specific list of issues.
|
||||
*
|
||||
* @param string Status ('open') or 'closed'
|
||||
* @param array A list of issue ids
|
||||
* @return array An array of tag objects
|
||||
*/
|
||||
public function getTagsByIssues($issue_ids=array())
|
||||
{
|
||||
// make the below query always a valid one
|
||||
if (count($issue_ids) == 0) $issue_ids[] = 0;
|
||||
|
||||
$assocTable = $this->_con->pfx.'idf_issue_idf_tag_assoc';
|
||||
$query = sprintf(
|
||||
'SELECT DISTINCT idf_tag_id FROM %s '.
|
||||
'WHERE idf_issue_id IN (%s) '.
|
||||
'GROUP BY idf_tag_id',
|
||||
$assocTable, implode(',', $issue_ids)
|
||||
);
|
||||
|
||||
$db = Pluf::db();
|
||||
$dbData = $db->select($query);
|
||||
$ids = array(0);
|
||||
foreach ($dbData as $data) {
|
||||
$ids[] = $data['idf_tag_id'];
|
||||
}
|
||||
|
||||
$sql = new Pluf_SQL(sprintf('id IN (%s)', implode(', ', $ids)));
|
||||
$model = new IDF_Tag();
|
||||
return $model->getList(array('filter' => $sql->gen()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the open/closed tag ids as they are often used when doing
|
||||
* listings.
|
||||
@@ -234,7 +374,10 @@ class IDF_Project extends Pluf_Model
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of relations which are available in this project
|
||||
* Returns a list of relations which are available in this project as
|
||||
* associative array. Each key-value pair marks a set of orthogonal
|
||||
* relations. To ease processing, each of these pairs is included twice
|
||||
* in the array, once as key1 => key2 and once as key2 => key1.
|
||||
*
|
||||
* @return array List of relation names
|
||||
*/
|
||||
@@ -244,7 +387,11 @@ class IDF_Project extends Pluf_Model
|
||||
$rel = $conf->getVal('issue_relations', IDF_Form_IssueTrackingConf::init_relations);
|
||||
$relations = array();
|
||||
foreach (preg_split("/\015\012|\015|\012/", $rel, -1, PREG_SPLIT_NO_EMPTY) as $s) {
|
||||
$relations = array_merge($relations, preg_split("/\s*,\s*/", $s, 2));
|
||||
$verbs = preg_split("/\s*,\s*/", $s, 2);
|
||||
if (count($verbs) == 1)
|
||||
$relations += array($verbs[0] => $verbs[0]);
|
||||
else
|
||||
$relations += array($verbs[0] => $verbs[1], $verbs[1] => $verbs[0]);
|
||||
}
|
||||
return $relations;
|
||||
}
|
||||
@@ -343,13 +490,13 @@ class IDF_Project extends Pluf_Model
|
||||
$dep_ids = IDF_Views_Wiki::getDeprecatedPagesIds($this);
|
||||
$extra = '';
|
||||
if (count($dep_ids)) {
|
||||
$extra = ' AND idf_wikipage_id NOT IN ('.implode(', ', $dep_ids).') ';
|
||||
$extra = ' AND idf_wiki_page_id NOT IN ('.implode(', ', $dep_ids).') ';
|
||||
}
|
||||
$what_t = Pluf::factory('IDF_WikiPage')->getSqlTable();
|
||||
$asso_t = $this->_con->pfx.'idf_tag_idf_wikipage_assoc';
|
||||
$what_t = Pluf::factory('IDF_Wiki_Page')->getSqlTable();
|
||||
$asso_t = $this->_con->pfx.'idf_tag_idf_wiki_page_assoc';
|
||||
$sql = 'SELECT '.$tag_t.'.id AS id, COUNT(*) AS nb_use FROM '.$tag_t.' '."\n".
|
||||
'LEFT JOIN '.$asso_t.' ON idf_tag_id='.$tag_t.'.id '."\n".
|
||||
'LEFT JOIN '.$what_t.' ON idf_wikipage_id='.$what_t.'.id '."\n".
|
||||
'LEFT JOIN '.$what_t.' ON idf_wiki_page_id='.$what_t.'.id '."\n".
|
||||
'WHERE idf_tag_id IS NOT NULL '.$extra.' AND '.$what_t.'.project='.$this->id.' GROUP BY '.$tag_t.'.id, '.$tag_t.'.class, '.$tag_t.'.name ORDER BY '.$tag_t.'.class ASC, '.$tag_t.'.name ASC';
|
||||
} elseif ($what == 'downloads') {
|
||||
$dep_ids = IDF_Views_Download::getDeprecatedFilesIds($this);
|
||||
@@ -368,7 +515,11 @@ class IDF_Project extends Pluf_Model
|
||||
foreach ($this->_con->select($sql) as $idc) {
|
||||
$tag = new IDF_Tag($idc['id']);
|
||||
$tag->nb_use = $idc['nb_use'];
|
||||
$tags[] = $tag;
|
||||
// group by class
|
||||
if (!array_key_exists($tag->class, $tags)) {
|
||||
$tags[$tag->class] = array();
|
||||
}
|
||||
$tags[$tag->class][] = $tag;
|
||||
}
|
||||
return new Pluf_Template_ContextVars($tags);
|
||||
}
|
||||
@@ -447,12 +598,12 @@ class IDF_Project extends Pluf_Model
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the post commit hook key.
|
||||
* Get the web hook key.
|
||||
*
|
||||
* The goal is to get something predictable but from which one
|
||||
* cannot reverse find the secret key.
|
||||
*/
|
||||
public function getPostCommitHookKey()
|
||||
public function getWebHookKey()
|
||||
{
|
||||
return md5($this->id.sha1(Pluf::f('secret_key')).$this->shortname);
|
||||
}
|
||||
@@ -505,6 +656,22 @@ class IDF_Project extends Pluf_Model
|
||||
return $this->_pconf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic overload that falls back to the values of the internal configuration
|
||||
* if no getter / caller matched
|
||||
*
|
||||
* @param string $key
|
||||
*/
|
||||
public function __get($key)
|
||||
{
|
||||
try {
|
||||
return parent::__get($key);
|
||||
}
|
||||
catch (Exception $e) {
|
||||
return $this->getConf()->getVal($key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get simple statistics about the project.
|
||||
*
|
||||
@@ -518,10 +685,10 @@ class IDF_Project extends Pluf_Model
|
||||
$stats = array();
|
||||
$stats['total'] = 0;
|
||||
$what = array('downloads' => 'IDF_Upload',
|
||||
'reviews' => 'IDF_Review',
|
||||
'issues' => 'IDF_Issue',
|
||||
'docpages' => 'IDF_WikiPage',
|
||||
'commits' => 'IDF_Commit',
|
||||
'reviews' => 'IDF_Review',
|
||||
'issues' => 'IDF_Issue',
|
||||
'docpages' => 'IDF_Wiki_Page',
|
||||
'commits' => 'IDF_Commit',
|
||||
);
|
||||
foreach ($what as $key=>$m) {
|
||||
$i = Pluf::factory($m)->getCount(array('filter' => 'project='.(int)$this->id));
|
||||
@@ -651,7 +818,8 @@ class IDF_Project extends Pluf_Model
|
||||
Pluf_Signal::send('IDF_Project::preDelete',
|
||||
'IDF_Project', $params);
|
||||
$what = array('IDF_Upload', 'IDF_Review', 'IDF_Issue',
|
||||
'IDF_WikiPage', 'IDF_Commit', 'IDF_Tag',
|
||||
'IDF_Wiki_Page', 'IDF_Wiki_Resource',
|
||||
'IDF_Commit', 'IDF_Tag',
|
||||
);
|
||||
foreach ($what as $m) {
|
||||
foreach (Pluf::factory($m)->getList(array('filter' => 'project='.(int)$this->id)) as $item) {
|
||||
@@ -692,4 +860,52 @@ class IDF_Project extends Pluf_Model
|
||||
$this->_isRestricted = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an associative array of email addresses to notify about changes
|
||||
* in a certain tab like 'issues', 'source', and so on.
|
||||
*
|
||||
* @param string $tab
|
||||
* @return array Key is the email address, value is the preferred language setting
|
||||
*/
|
||||
public function getNotificationRecipientsForTab($tab)
|
||||
{
|
||||
if (!in_array($tab, array('source', 'issues', 'downloads', 'wiki', 'review'))) {
|
||||
throw new Exception(sprintf('unknown tab %s', $tab));
|
||||
}
|
||||
|
||||
$conf = $this->getConf();
|
||||
$recipients = array();
|
||||
$membership_data = $this->getMembershipData();
|
||||
|
||||
if ($conf->getVal($tab.'_notification_owners_enabled', false)) {
|
||||
foreach ($membership_data['owners'] as $owner) {
|
||||
$recipients[$owner->email] = $owner->language;
|
||||
}
|
||||
}
|
||||
|
||||
if ($conf->getVal($tab.'_notification_members_enabled', false)) {
|
||||
foreach ($membership_data['members'] as $member) {
|
||||
$recipients[$member->email] = $member->language;
|
||||
}
|
||||
}
|
||||
|
||||
if ($conf->getVal($tab.'_notification_email_enabled', false)) {
|
||||
$addresses = preg_split('/\s*,\s*/',
|
||||
$conf->getVal($tab.'_notification_email', ''),
|
||||
-1, PREG_SPLIT_NO_EMPTY);
|
||||
|
||||
// we use a default language setting for this plain list of
|
||||
// addresses, but we ensure that we do not overwrite an existing
|
||||
// address which might come with a proper setting already
|
||||
$languages = Pluf::f('languages', array('en'));
|
||||
foreach ($addresses as $address) {
|
||||
if (array_key_exists($address, $recipients))
|
||||
continue;
|
||||
$recipients[$address] = $languages[0];
|
||||
}
|
||||
}
|
||||
|
||||
return $recipients;
|
||||
}
|
||||
}
|
||||
|
78
src/IDF/ProjectActivity.php
Normal file
78
src/IDF/ProjectActivity.php
Normal file
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/*
|
||||
# ***** BEGIN LICENSE BLOCK *****
|
||||
# This file is part of InDefero, an open source project management application.
|
||||
# Copyright (C) 2008-2011 Céondo Ltd and contributors.
|
||||
#
|
||||
# InDefero 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.
|
||||
#
|
||||
# InDefero is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
#
|
||||
# ***** END LICENSE BLOCK ***** */
|
||||
|
||||
/**
|
||||
* Models the activity value for a project and a given date
|
||||
*
|
||||
* @author tommyd
|
||||
*/
|
||||
class IDF_ProjectActivity extends Pluf_Model
|
||||
{
|
||||
public $_model = __CLASS__;
|
||||
|
||||
function init()
|
||||
{
|
||||
$this->_a['table'] = 'idf_projectactivities';
|
||||
$this->_a['model'] = __CLASS__;
|
||||
$this->_a['cols'] = array(
|
||||
// It is mandatory to have an "id" column.
|
||||
'id' =>
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Sequence',
|
||||
'blank' => true,
|
||||
),
|
||||
'project' =>
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Foreignkey',
|
||||
'model' => 'IDF_Project',
|
||||
'blank' => false,
|
||||
'verbose' => __('project'),
|
||||
'relate_name' => 'activities',
|
||||
),
|
||||
'date' =>
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Datetime',
|
||||
'blank' => false,
|
||||
'verbose' => __('date'),
|
||||
),
|
||||
'value' =>
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Float',
|
||||
'blank' => false,
|
||||
'verbose' => __('value'),
|
||||
'default' => 0,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
function postSave($create=false)
|
||||
{
|
||||
$prj = $this->get_project();
|
||||
$sql = new Pluf_SQL('project=%s', array($prj->id));
|
||||
$list = Pluf::factory('IDF_ProjectActivity')->getList(array('filter' => $sql->gen(), 'order' => 'date desc'));
|
||||
if (count($list) > 0 && $prj->current_activity != $list[0]->id) {
|
||||
$prj->current_activity = $list[0];
|
||||
$prj->update();
|
||||
}
|
||||
}
|
||||
}
|
@@ -138,7 +138,7 @@ class IDF_Review_Comment extends Pluf_Model
|
||||
$ic = (in_array($review->status, $request->project->getTagIdsByStatus('closed'))) ? 'issue-c' : 'issue-o';
|
||||
$out .= sprintf(__('<a href="%1$s" class="%2$s" title="View review">Review %3$d</a>, %4$s'), $url, $ic, $review->id, Pluf_esc($review->summary)).'</td>';
|
||||
$out .= "\n".'<tr class="extra"><td colspan="2">
|
||||
<div class="helptext right">'.sprintf(__('Update of <a href="%s" class="%s">review %d</a>, by %s'), $url, $ic, $review->id, $user).'</div></td></tr>';
|
||||
<div class="helptext right">'.sprintf(__('Update of <a href="%1$s" class="%2$s">review %3$d</a>, by %4$s'), $url, $ic, $review->id, $user).'</div></td></tr>';
|
||||
return Pluf_Template::markSafe($out);
|
||||
}
|
||||
|
||||
@@ -148,7 +148,7 @@ class IDF_Review_Comment extends Pluf_Model
|
||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Review::view',
|
||||
array($request->project->shortname,
|
||||
$review->id));
|
||||
$title = sprintf(__('%s: Updated review %d - %s'),
|
||||
$title = sprintf(__('%1$s: Updated review %2$d - %3$s'),
|
||||
Pluf_esc($request->project->name),
|
||||
$review->id, Pluf_esc($review->summary));
|
||||
$url .= '#ic'.$this->id;
|
||||
@@ -175,50 +175,63 @@ class IDF_Review_Comment extends Pluf_Model
|
||||
*/
|
||||
public function notify($conf, $create=true)
|
||||
{
|
||||
$patch = $this->get_patch();
|
||||
$review = $patch->get_review();
|
||||
$prj = $review->get_project();
|
||||
$to_email = array();
|
||||
if ('' != $conf->getVal('review_notification_email', '')) {
|
||||
$langs = Pluf::f('languages', array('en'));
|
||||
$to_email[] = array($conf->getVal('issues_notification_email'),
|
||||
$langs[0]);
|
||||
}
|
||||
$current_locale = Pluf_Translation::getLocale();
|
||||
$reviewers = $review->getReviewers();
|
||||
$patch = $this->get_patch();
|
||||
$review = $patch->get_review();
|
||||
$prj = $review->get_project();
|
||||
$reviewers = $review->getReviewers();
|
||||
|
||||
if (!Pluf_Model_InArray($review->get_submitter(), $reviewers)) {
|
||||
$reviewers[] = $review->get_submitter();
|
||||
}
|
||||
|
||||
$comments = $patch->getFileComments(array('order' => 'id DESC'));
|
||||
$gcomments = $patch->get_comments_list(array('order' => 'id DESC'));
|
||||
$context = new Pluf_Template_Context(
|
||||
array(
|
||||
'review' => $review,
|
||||
'patch' => $patch,
|
||||
'comments' => $comments,
|
||||
'gcomments' => $gcomments,
|
||||
'project' => $prj,
|
||||
'url_base' => Pluf::f('url_base'),
|
||||
)
|
||||
);
|
||||
// build the list of emails and lang
|
||||
foreach ($reviewers as $user) {
|
||||
$email_lang = array($user->email,
|
||||
$user->language);
|
||||
if (!in_array($email_lang, $to_email)) {
|
||||
$to_email[] = $email_lang;
|
||||
}
|
||||
}
|
||||
$tmpl = new Pluf_Template('idf/review/review-updated-email.txt');
|
||||
foreach ($to_email as $email_lang) {
|
||||
Pluf_Translation::loadSetLocale($email_lang[1]);
|
||||
$email = new Pluf_Mail(Pluf::f('from_email'), $email_lang[0],
|
||||
sprintf(__('Updated Code Review %s - %s (%s)'),
|
||||
$review->id, $review->summary, $prj->shortname));
|
||||
|
||||
$email->addTextMessage($tmpl->render($context));
|
||||
$recipients = $prj->getNotificationRecipientsForTab('review');
|
||||
|
||||
foreach ($reviewers as $user) {
|
||||
if (array_key_exists($user->email, $recipients))
|
||||
continue;
|
||||
$recipients[$user->email] = $user->language;
|
||||
}
|
||||
|
||||
$current_locale = Pluf_Translation::getLocale();
|
||||
|
||||
$from_email = Pluf::f('from_email');
|
||||
$messageId = '<'.md5('review'.$review->id.md5(Pluf::f('secret_key'))).'@'.Pluf::f('mail_host', 'localhost').'>';
|
||||
|
||||
foreach ($recipients as $address => $language) {
|
||||
|
||||
if ($this->get_submitter()->email === $address) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Pluf_Translation::loadSetLocale($language);
|
||||
|
||||
$context = new Pluf_Template_Context(array(
|
||||
'review' => $review,
|
||||
'patch' => $patch,
|
||||
'comments' => $comments,
|
||||
'gcomments' => $gcomments,
|
||||
'project' => $prj,
|
||||
'url_base' => Pluf::f('url_base'),
|
||||
));
|
||||
|
||||
// reviews only updated through comments, see IDF_Review_Patch::notify()
|
||||
$tplfile = 'idf/review/review-updated-email.txt';
|
||||
$subject = __('Updated Code Review %1$s - %2$s (%3$s)');
|
||||
$headers = array('References' => $messageId);
|
||||
|
||||
$tmpl = new Pluf_Template($tplfile);
|
||||
$text_email = $tmpl->render($context);
|
||||
|
||||
$email = new Pluf_Mail($from_email, $address,
|
||||
sprintf($subject, $review->id, $review->summary, $prj->shortname));
|
||||
$email->addTextMessage($text_email);
|
||||
$email->addHeaders($headers);
|
||||
$email->sendMail();
|
||||
}
|
||||
|
||||
Pluf_Translation::loadSetLocale($current_locale);
|
||||
}
|
||||
|
||||
|
@@ -150,7 +150,7 @@ class IDF_Review_Patch extends Pluf_Model
|
||||
$ic = (in_array($review->status, $request->project->getTagIdsByStatus('closed'))) ? 'issue-c' : 'issue-o';
|
||||
$out .= sprintf(__('<a href="%1$s" class="%2$s" title="View review">Review %3$d</a>, %4$s'), $url, $ic, $review->id, Pluf_esc($review->summary)).'</td>';
|
||||
$out .= "\n".'<tr class="extra"><td colspan="2">
|
||||
<div class="helptext right">'.sprintf(__('Creation of <a href="%s" class="%s">review %d</a>, by %s'), $url, $ic, $review->id, $user).'</div></td></tr>';
|
||||
<div class="helptext right">'.sprintf(__('Creation of <a href="%1$s" class="%2$s">review %3$d</a>, by %4$s'), $url, $ic, $review->id, $user).'</div></td></tr>';
|
||||
return Pluf_Template::markSafe($out);
|
||||
}
|
||||
|
||||
@@ -160,7 +160,7 @@ class IDF_Review_Patch extends Pluf_Model
|
||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Review::view',
|
||||
array($request->project->shortname,
|
||||
$review->id));
|
||||
$title = sprintf(__('%s: Creation of Review %d - %s'),
|
||||
$title = sprintf(__('%1$s: Creation of Review %2$d - %3$s'),
|
||||
Pluf_esc($request->project->name),
|
||||
$review->id, Pluf_esc($review->summary));
|
||||
$date = Pluf_Date::gmDateToGmString($this->creation_dtime);
|
||||
@@ -179,35 +179,49 @@ class IDF_Review_Patch extends Pluf_Model
|
||||
|
||||
public function notify($conf, $create=true)
|
||||
{
|
||||
if ('' == $conf->getVal('review_notification_email', '')) {
|
||||
return;
|
||||
}
|
||||
$review = $this->get_review();
|
||||
$project = $review->get_project();
|
||||
$current_locale = Pluf_Translation::getLocale();
|
||||
$langs = Pluf::f('languages', array('en'));
|
||||
Pluf_Translation::loadSetLocale($langs[0]);
|
||||
|
||||
$context = new Pluf_Template_Context(
|
||||
array(
|
||||
'review' => $this->get_review(),
|
||||
'patch' => $this,
|
||||
'comments' => array(),
|
||||
'project' => $this->get_review()->get_project(),
|
||||
'url_base' => Pluf::f('url_base'),
|
||||
)
|
||||
);
|
||||
$tmpl = new Pluf_Template('idf/review/review-created-email.txt');
|
||||
$text_email = $tmpl->render($context);
|
||||
$addresses = explode(';',$conf->getVal('review_notification_email'));
|
||||
foreach ($addresses as $address) {
|
||||
$email = new Pluf_Mail(Pluf::f('from_email'),
|
||||
$from_email = Pluf::f('from_email');
|
||||
$messageId = '<'.md5('review'.$review->id.md5(Pluf::f('secret_key'))).'@'.Pluf::f('mail_host', 'localhost').'>';
|
||||
$recipients = $project->getNotificationRecipientsForTab('review');
|
||||
|
||||
foreach ($recipients as $address => $language) {
|
||||
|
||||
if ($review->get_submitter()->email === $address) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Pluf_Translation::loadSetLocale($language);
|
||||
|
||||
$context = new Pluf_Template_Context(array(
|
||||
'review' => $review,
|
||||
'patch' => $this,
|
||||
'comments' => array(),
|
||||
'project' => $project,
|
||||
'url_base' => Pluf::f('url_base'),
|
||||
));
|
||||
|
||||
// reviews are updated through comments, see IDF_Review_Comment::notify()
|
||||
$tplfile = 'idf/review/review-created-email.txt';
|
||||
$subject = __('New Code Review %1$s - %2$s (%3$s)');
|
||||
$headers = array('Message-ID' => $messageId);
|
||||
|
||||
$tmpl = new Pluf_Template($tplfile);
|
||||
$text_email = $tmpl->render($context);
|
||||
|
||||
$email = new Pluf_Mail($from_email,
|
||||
$address,
|
||||
sprintf(__('New Code Review %s - %s (%s)'),
|
||||
$this->get_review()->id,
|
||||
$this->get_review()->summary,
|
||||
$this->get_review()->get_project()->shortname));
|
||||
sprintf($subject,
|
||||
$review->id,
|
||||
$review->summary,
|
||||
$project->shortname));
|
||||
$email->addTextMessage($text_email);
|
||||
$email->addHeaders($headers);
|
||||
$email->sendMail();
|
||||
}
|
||||
|
||||
Pluf_Translation::loadSetLocale($current_locale);
|
||||
}
|
||||
}
|
||||
|
@@ -88,22 +88,36 @@ class IDF_Scm
|
||||
}
|
||||
|
||||
/**
|
||||
* Run exec and log some information.
|
||||
* Runs the given command and log some information.
|
||||
*
|
||||
* A previous version used plain exec(), but this should not be used
|
||||
* for various reasons, one being that this command does not preserve
|
||||
* trailing whitespace, which is essential for proper diff parsing.
|
||||
*
|
||||
* @param $caller Calling method
|
||||
* @param $cmd Command to run
|
||||
* @param &$out Array of output
|
||||
* @param &$return Return value
|
||||
* @return string Last line of the command
|
||||
*/
|
||||
public static function exec($caller, $cmd, &$out=null, &$return=null)
|
||||
{
|
||||
$return = -1;
|
||||
Pluf_Log::stime('timer');
|
||||
$ret = exec($cmd, $out, $return);
|
||||
$fp = popen($cmd, 'r');
|
||||
$buf = '';
|
||||
if ($fp !== false) {
|
||||
while (!feof($fp)) {
|
||||
$buf .= fread($fp, 1024);
|
||||
}
|
||||
$return = pclose($fp);
|
||||
}
|
||||
$out = preg_split('/\r\n|\r|\n/', $buf);
|
||||
$elem = count($out);
|
||||
if ($elem > 0 && $out[$elem-1] === '')
|
||||
unset($out[$elem-1]);
|
||||
Pluf_Log::perf(array($caller, $cmd, Pluf_Log::etime('timer', 'total_exec')));
|
||||
Pluf_Log::debug(array($caller, $cmd, $out));
|
||||
Pluf_Log::inc('exec_calls');
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -325,7 +339,8 @@ class IDF_Scm
|
||||
* stdClass object {
|
||||
* 'additions' => array('path/to/file', 'path/to/directory', ...),
|
||||
* 'deletions' => array('path/to/file', 'path/to/directory', ...),
|
||||
* 'renames' => array('old/path/to/file' => 'new/path/to/file', ...)
|
||||
* 'renames' => array('old/path/to/file' => 'new/path/to/file', ...),
|
||||
* 'copies' => array('path/to/source' => 'path/to/target', ...),
|
||||
* 'patches' => array('path/to/file', ...),
|
||||
* 'properties' => array('path/to/file' => array(
|
||||
* 'propname' => 'propvalue', 'deletedprop' => null, ...)
|
||||
@@ -482,5 +497,10 @@ class IDF_Scm
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function repository($request, $match)
|
||||
{
|
||||
throw new Exception('This repository does not support web based repository access');
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -52,11 +52,11 @@ class IDF_Scm_Git extends IDF_Scm
|
||||
* A doc/Guide utilisateur/images/ftp-nautilus.png
|
||||
* M doc/Guide utilisateur/textes/log_boot_PEGASE.txt
|
||||
*
|
||||
* Status letters mean : Added (A), Deleted (D), Modified (M), Renamed (R)
|
||||
* Status letters mean : Added (A), Copied (C), Deleted (D), Modified (M), Renamed (R)
|
||||
*/
|
||||
public function getChanges($commit)
|
||||
{
|
||||
$cmd = sprintf('GIT_DIR=%s '.Pluf::f('git_path', 'git').' show %s --name-status --pretty="format:" --diff-filter="[A|D|M|R]" -M',
|
||||
$cmd = sprintf('GIT_DIR=%s '.Pluf::f('git_path', 'git').' show %s --name-status --pretty="format:" --diff-filter="[A|C|D|M|R]" -C -C',
|
||||
escapeshellarg($this->repo),
|
||||
escapeshellarg($commit));
|
||||
$out = array();
|
||||
@@ -67,6 +67,7 @@ class IDF_Scm_Git extends IDF_Scm
|
||||
'additions' => array(),
|
||||
'deletions' => array(),
|
||||
'renames' => array(),
|
||||
'copies' => array(),
|
||||
'patches' => array(),
|
||||
'properties' => array(),
|
||||
);
|
||||
@@ -86,8 +87,11 @@ class IDF_Scm_Git extends IDF_Scm
|
||||
$filename = trim(substr($line, 1));
|
||||
$return->patches[] = $filename;
|
||||
} else if ($action == 'R') {
|
||||
$matches = split ("\t", $line);
|
||||
$matches = preg_split("/\t/", $line);
|
||||
$return->renames[$matches[1]] = $matches[2];
|
||||
} else if ($action == 'C') {
|
||||
$matches = preg_split("/\t/", $line);
|
||||
$return->copies[$matches[1]] = $matches[2];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -209,7 +213,7 @@ class IDF_Scm_Git extends IDF_Scm
|
||||
return $this->cache['tags'];
|
||||
}
|
||||
$cmd = Pluf::f('idf_exec_cmd_prefix', '')
|
||||
.sprintf('GIT_DIR=%s %s for-each-ref --format="%%(taggerdate:iso)%%(committerdate:iso) %%(objectname) %%(refname)" refs/tags',
|
||||
.sprintf('GIT_DIR=%s %s for-each-ref --format="%%(objectname) %%(refname)" refs/tags',
|
||||
escapeshellarg($this->repo),
|
||||
Pluf::f('git_path', 'git'));
|
||||
self::exec('IDF_Scm_Git::getTags', $cmd, $out, $return);
|
||||
@@ -218,18 +222,15 @@ class IDF_Scm_Git extends IDF_Scm
|
||||
$cmd, $return,
|
||||
implode("\n", $out)));
|
||||
}
|
||||
rsort($out);
|
||||
$res = array();
|
||||
foreach ($out as $b) {
|
||||
$elts = explode(' ', $b, 5);
|
||||
$tag = substr(trim($elts[4]), 10);
|
||||
if (false !== strpos($tag, '/')) {
|
||||
$res[$elts[3]] = $b;
|
||||
} else {
|
||||
$res[$tag] = '';
|
||||
}
|
||||
$elts = explode(' ', $b, 2);
|
||||
$tag = substr(trim($elts[1]), 10); // Remove refs/tags/ prefix
|
||||
$res[$tag] = '';
|
||||
}
|
||||
krsort($res);
|
||||
$this->cache['tags'] = $res;
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
@@ -348,6 +349,14 @@ class IDF_Scm_Git extends IDF_Scm
|
||||
if (!preg_match('/<(.*)>/', $author, $match)) {
|
||||
return null;
|
||||
}
|
||||
// FIXME: newer git versions know a i18n.commitencoding setting which
|
||||
// leads to another header, "encoding", with which we _could_ try to
|
||||
// decode the string into utf8. Unfortunately this does not always
|
||||
// work, especially not in older repos, so we would then still have
|
||||
// to supply some fallback.
|
||||
if (!mb_check_encoding($match[1], 'UTF-8')) {
|
||||
return null;
|
||||
}
|
||||
$sql = new Pluf_SQL('login=%s', array($match[1]));
|
||||
$users = Pluf::factory('Pluf_User')->getList(array('filter'=>$sql->gen()));
|
||||
if ($users->count() > 0) {
|
||||
@@ -501,33 +510,27 @@ class IDF_Scm_Git extends IDF_Scm
|
||||
"'".$this->mediumtree_fmt."'",
|
||||
escapeshellarg($commit));
|
||||
}
|
||||
$out = array();
|
||||
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
|
||||
self::exec('IDF_Scm_Git::getCommit', $cmd, $out, $ret);
|
||||
if ($ret != 0 or count($out) == 0) {
|
||||
$out = self::shell_exec('IDF_Scm_Git::getCommit', $cmd);
|
||||
if (strlen($out) == 0) {
|
||||
return false;
|
||||
}
|
||||
if ($getdiff) {
|
||||
$log = array();
|
||||
$change = array();
|
||||
$inchange = false;
|
||||
foreach ($out as $line) {
|
||||
if (!$inchange and 0 === strpos($line, 'diff --git a')) {
|
||||
$inchange = true;
|
||||
}
|
||||
if ($inchange) {
|
||||
$change[] = $line;
|
||||
} else {
|
||||
$log[] = $line;
|
||||
}
|
||||
}
|
||||
$out = self::parseLog($log);
|
||||
$out[0]->diff = implode("\n", $change);
|
||||
} else {
|
||||
$out = self::parseLog($out);
|
||||
$out[0]->diff = '';
|
||||
|
||||
$diffStart = false;
|
||||
if (preg_match('/^diff (?:--git a|--cc)/m', $out, $m, PREG_OFFSET_CAPTURE)) {
|
||||
$diffStart = $m[0][1];
|
||||
}
|
||||
|
||||
$diff = '';
|
||||
if ($diffStart !== false) {
|
||||
$log = substr($out, 0, $diffStart);
|
||||
$diff = substr($out, $diffStart);
|
||||
} else {
|
||||
$log = $out;
|
||||
}
|
||||
|
||||
$out = self::parseLog(preg_split('/\r\n|\n/', $log));
|
||||
$out[0]->diff = $diff;
|
||||
$out[0]->branch = implode(', ', $this->inBranches($out[0]->commit, null));
|
||||
return $out[0];
|
||||
}
|
||||
@@ -640,7 +643,11 @@ class IDF_Scm_Git extends IDF_Scm
|
||||
$c['full_message'] = IDF_Commit::toUTF8($c['full_message']);
|
||||
$c['title'] = IDF_Commit::toUTF8($c['title']);
|
||||
if (isset($c['parents'])) {
|
||||
$c['parents'] = explode(' ', trim($c['parents']));
|
||||
$c['parents'] = preg_split('/ /', trim($c['parents']), -1, PREG_SPLIT_NO_EMPTY);
|
||||
} else {
|
||||
// this is actually an error state because we should _always_
|
||||
// be able to parse the parents line with every git version
|
||||
$c['parents'] = null;
|
||||
}
|
||||
$res[] = (object) $c;
|
||||
return $res;
|
||||
@@ -914,4 +921,82 @@ class IDF_Scm_Git extends IDF_Scm
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function repository($request, $match)
|
||||
{
|
||||
// authenticate: authenticate connection through "extra" password
|
||||
if (isset($_SERVER['HTTP_AUTHORIZATION']) && $_SERVER['HTTP_AUTHORIZATION'] != '')
|
||||
list($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW']) = explode(':' , base64_decode(substr($_SERVER['HTTP_AUTHORIZATION'], 6)));
|
||||
|
||||
if (isset($_SERVER['PHP_AUTH_USER'])) {
|
||||
$sql = new Pluf_SQL('login=%s', array($_SERVER['PHP_AUTH_USER']));
|
||||
$users = Pluf::factory('Pluf_User')->getList(array('filter'=>$sql->gen()));
|
||||
if ((count($users) == 1) && ($users[0]->active)) {
|
||||
$user = $users[0];
|
||||
$realkey = substr(sha1($user->password.Pluf::f('secret_key')), 0, 8);
|
||||
if ($_SERVER['PHP_AUTH_PW'] == $realkey) {
|
||||
$request->user = $user;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (IDF_Precondition::accessSource($request) !== true) {
|
||||
$response = new Pluf_HTTP_Response("");
|
||||
$response->status_code = 401;
|
||||
$response->headers['WWW-Authenticate']='Basic realm="git for '.$this->project.'"';
|
||||
return $response;
|
||||
}
|
||||
|
||||
$path = $match[2];
|
||||
|
||||
// update files before delivering them
|
||||
if (($path == 'objects/info/pack') || ($path == 'info/refs')) {
|
||||
$cmd = sprintf(Pluf::f('idf_exec_cmd_prefix', '').
|
||||
'GIT_DIR=%s '.Pluf::f('git_path', 'git').' update-server-info -f',
|
||||
escapeshellarg($this->repo));
|
||||
self::shell_exec('IDF_Scm_Git::repository', $cmd);
|
||||
}
|
||||
|
||||
// smart HTTP discovery
|
||||
if (($path == 'info/refs') &&
|
||||
(array_key_exists('service', $request->GET))){
|
||||
$service = $request->GET["service"];
|
||||
switch ($service) {
|
||||
case 'git-upload-pack':
|
||||
case 'git-receive-pack':
|
||||
$content = sprintf('%04x',strlen($service)+15).
|
||||
'# service='.$service."\n0000";
|
||||
$content .= self::shell_exec('IDF_Scm_Git::repository',
|
||||
$service.' --stateless-rpc --advertise-refs '.
|
||||
$this->repo);
|
||||
$response = new Pluf_HTTP_Response($content,
|
||||
'application/x-'.$service.'-advertisement');
|
||||
return $response;
|
||||
default:
|
||||
throw new Exception('unknown service: '.$service);
|
||||
}
|
||||
}
|
||||
|
||||
switch($path) {
|
||||
// smart HTTP RPC
|
||||
case 'git-upload-pack':
|
||||
case 'git-receive-pack':
|
||||
$response = new Pluf_HTTP_Response_CommandPassThru($path.
|
||||
' --stateless-rpc '.$this->repo,
|
||||
'application/x-'.$path.'-result');
|
||||
$response->addStdin('php://input');
|
||||
return $response;
|
||||
|
||||
// regular file
|
||||
default:
|
||||
// make sure we're inside the repo hierarchy (ie. no break-out)
|
||||
if (is_file($this->repo.'/'.$path) &&
|
||||
strpos(realpath($this->repo.'/'.$path), $this->repo.'/') == 0) {
|
||||
return new Pluf_HTTP_Response_File($this->repo.'/'.$path,
|
||||
'application/octet-stream');
|
||||
} else {
|
||||
return new Pluf_HTTP_Response_NotFound($request);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -22,19 +22,77 @@
|
||||
# ***** END LICENSE BLOCK ***** */
|
||||
|
||||
/**
|
||||
* Mercurial utils.
|
||||
* A simple RAII helper that manages style files to format hg's log output
|
||||
*/
|
||||
class IDF_Scm_Mercurial_LogStyle
|
||||
{
|
||||
const FULL_LOG = 1;
|
||||
const CHANGES = 2;
|
||||
|
||||
public function __construct($type)
|
||||
{
|
||||
$this->file = tempnam(Pluf::f('tmp_folder'), 'hg-log-style-');
|
||||
|
||||
if ($type == self::FULL_LOG) {
|
||||
$style = 'changeset = "'
|
||||
. 'changeset: {node|short}\n'
|
||||
. 'branch: {branch}\n'
|
||||
. 'author: {author}\n'
|
||||
. 'date: {date|isodate}\n'
|
||||
. 'parents: {parents}\n\n'
|
||||
. '{desc}\n'
|
||||
. '\0\n"'
|
||||
. "\n"
|
||||
. 'parent = "{node|short} "'
|
||||
. "\n";
|
||||
} elseif ($type == self::CHANGES) {
|
||||
$style = 'changeset = "'
|
||||
. 'file_mods: {file_mods}\n'
|
||||
. 'file_adds: {file_adds}\n'
|
||||
. 'file_dels: {file_dels}\n'
|
||||
. 'file_copies: {file_copies}\n\n'
|
||||
. '\0\n"'
|
||||
. "\n"
|
||||
. 'file_mod = "{file_mod}\0"'
|
||||
. "\n"
|
||||
. 'file_add = "{file_add}\0"'
|
||||
. "\n"
|
||||
. 'file_del = "{file_del}\0"'
|
||||
. "\n"
|
||||
. 'file_copy = "{source}\0{name}\0"'
|
||||
. "\n";
|
||||
} else {
|
||||
throw new IDF_Scm_Exception('invalid type ' . $type);
|
||||
}
|
||||
|
||||
file_put_contents($this->file, $style);
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
@unlink($this->file);
|
||||
}
|
||||
|
||||
public function get()
|
||||
{
|
||||
return $this->file;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Main SCM class for Mercurial
|
||||
*
|
||||
* Note: Some commands take a --debug option, this is not lousy coding, but
|
||||
* totally wanted, as hg returns additional / different data in this
|
||||
* mode on which this largely depends.
|
||||
*/
|
||||
class IDF_Scm_Mercurial extends IDF_Scm
|
||||
{
|
||||
protected $hg_log_template;
|
||||
|
||||
public function __construct($repo, $project=null)
|
||||
{
|
||||
$this->repo = $repo;
|
||||
$this->project = $project;
|
||||
$this->hg_log_template = "'".'changeset: {rev}:{node|short}\nauthor: {author}\ndate: {date|isodate}\nfiles: {files}\n{desc}\n'."'";
|
||||
}
|
||||
}
|
||||
|
||||
public function getRepositorySize()
|
||||
{
|
||||
@@ -158,7 +216,8 @@ class IDF_Scm_Mercurial extends IDF_Scm
|
||||
throw new Exception(sprintf(__('Not a valid tree: %s.'), $tree));
|
||||
}
|
||||
$cmd_tmpl = Pluf::f('hg_path', 'hg').' manifest -R %s --debug -r %s';
|
||||
$cmd = sprintf($cmd_tmpl, escapeshellarg($this->repo), $tree, ($recurse) ? '' : '');
|
||||
$cmd = sprintf($cmd_tmpl, escapeshellarg($this->repo),
|
||||
escapeshellarg($tree));
|
||||
$out = array();
|
||||
$res = array();
|
||||
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
|
||||
@@ -208,7 +267,8 @@ class IDF_Scm_Mercurial extends IDF_Scm
|
||||
public function getPathInfo($totest, $commit='tip')
|
||||
{
|
||||
$cmd_tmpl = Pluf::f('hg_path', 'hg').' manifest -R %s --debug -r %s';
|
||||
$cmd = sprintf($cmd_tmpl, escapeshellarg($this->repo), $commit);
|
||||
$cmd = sprintf($cmd_tmpl, escapeshellarg($this->repo),
|
||||
escapeshellarg($commit));
|
||||
$out = array();
|
||||
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
|
||||
self::exec('IDF_Scm_Mercurial::getPathInfo', $cmd, $out);
|
||||
@@ -284,7 +344,7 @@ class IDF_Scm_Mercurial extends IDF_Scm
|
||||
self::exec('IDF_Scm_Mercurial::getBranches', $cmd, $out);
|
||||
$res = array();
|
||||
foreach ($out as $b) {
|
||||
preg_match('/(\S+).*\S+:(\S+)/', $b, $match);
|
||||
preg_match('/(.+?)\s+\S+:(\S+)/', $b, $match);
|
||||
$res[$match[1]] = '';
|
||||
}
|
||||
$this->cache['branches'] = $res;
|
||||
@@ -308,7 +368,7 @@ class IDF_Scm_Mercurial extends IDF_Scm
|
||||
self::exec('IDF_Scm_Mercurial::getTags', $cmd, $out);
|
||||
$res = array();
|
||||
foreach ($out as $b) {
|
||||
preg_match('/(\S+).*\S+:(\S+)/', $b, $match);
|
||||
preg_match('/(.+?)\s+\S+:(\S+)/', $b, $match);
|
||||
$res[$match[1]] = '';
|
||||
}
|
||||
$this->cache['tags'] = $res;
|
||||
@@ -339,33 +399,92 @@ class IDF_Scm_Mercurial extends IDF_Scm
|
||||
if ($this->validateRevision($commit) != IDF_Scm::REVISION_VALID) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$logStyle = new IDF_Scm_Mercurial_LogStyle(IDF_Scm_Mercurial_LogStyle::FULL_LOG);
|
||||
$tmpl = ($getdiff)
|
||||
? Pluf::f('hg_path', 'hg').' log -p -r %s -R %s --template %s'
|
||||
: Pluf::f('hg_path', 'hg').' log -r %s -R %s --template %s';
|
||||
? Pluf::f('hg_path', 'hg').' log --debug -p -r %s -R %s --style %s'
|
||||
: Pluf::f('hg_path', 'hg').' log --debug -r %s -R %s --style %s';
|
||||
$cmd = sprintf($tmpl,
|
||||
escapeshellarg($commit),
|
||||
escapeshellarg($this->repo),
|
||||
$this->hg_log_template);
|
||||
escapeshellarg($logStyle->get()));
|
||||
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
|
||||
$out = self::shell_exec('IDF_Scm_Mercurial::getCommit', $cmd);
|
||||
if (strlen($out) == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$diffStart = strpos($out, 'diff -r');
|
||||
$diff = '';
|
||||
if ($diffStart !== false) {
|
||||
$log = substr($out, 0, $diffStart);
|
||||
$diff = substr($out, $diffStart);
|
||||
} else {
|
||||
$log = $out;
|
||||
}
|
||||
|
||||
$out = self::parseLog(preg_split('/\r\n|\n/', $log));
|
||||
$out[0]->diff = $diff;
|
||||
return $out[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* @see IDF_Scm::getChanges()
|
||||
*/
|
||||
public function getChanges($commit)
|
||||
{
|
||||
if ($this->validateRevision($commit) != IDF_Scm::REVISION_VALID) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$logStyle = new IDF_Scm_Mercurial_LogStyle(IDF_Scm_Mercurial_LogStyle::CHANGES);
|
||||
$tmpl = Pluf::f('hg_path', 'hg').' log --debug -r %s -R %s --style %s';
|
||||
$cmd = sprintf($tmpl,
|
||||
escapeshellarg($commit),
|
||||
escapeshellarg($this->repo),
|
||||
escapeshellarg($logStyle->get()));
|
||||
$out = array();
|
||||
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
|
||||
self::exec('IDF_Scm_Mercurial::getCommit', $cmd, $out);
|
||||
$log = array();
|
||||
$change = array();
|
||||
$inchange = false;
|
||||
foreach ($out as $line) {
|
||||
if (!$inchange and 0 === strpos($line, 'diff -r')) {
|
||||
$inchange = true;
|
||||
self::exec('IDF_Scm_Mercurial::getChanges', $cmd, $out);
|
||||
$log = self::parseLog($out);
|
||||
// we expect only one log entry that contains all the needed information
|
||||
$log = $log[0];
|
||||
|
||||
$return = (object) array(
|
||||
'additions' => preg_split('/\0/', $log->file_adds, -1, PREG_SPLIT_NO_EMPTY),
|
||||
'deletions' => preg_split('/\0/', $log->file_dels, -1, PREG_SPLIT_NO_EMPTY),
|
||||
'patches' => preg_split('/\0/', $log->file_mods, -1, PREG_SPLIT_NO_EMPTY),
|
||||
// hg has no support for built-in attributes, so this keeps empty
|
||||
'properties' => array(),
|
||||
// these two are filled below
|
||||
'copies' => array(),
|
||||
'renames' => array(),
|
||||
);
|
||||
|
||||
$file_copies = preg_split('/\0/', $log->file_copies, -1, PREG_SPLIT_NO_EMPTY);
|
||||
|
||||
// copies are treated as renames if they have an add _and_ a drop;
|
||||
// only if they only have an add, but no drop, they're treated as copies
|
||||
for ($i=0; $i<count($file_copies); $i+=2) {
|
||||
$src = $file_copies[$i];
|
||||
$trg = $file_copies[$i+1];
|
||||
$srcidx = array_search($src, $return->deletions);
|
||||
$trgidx = array_search($trg, $return->additions);
|
||||
if ($srcidx !== false && $trgidx !== false) {
|
||||
$return->renames[$src] = $trg;
|
||||
unset($return->deletions[$srcidx]);
|
||||
unset($return->additions[$trgidx]);
|
||||
continue;
|
||||
}
|
||||
if ($inchange) {
|
||||
$change[] = $line;
|
||||
} else {
|
||||
$log[] = $line;
|
||||
if ($srcidx === false && $trgidx !== false) {
|
||||
$return->copies[$src] = $trg;
|
||||
unset($return->additions[$trgidx]);
|
||||
continue;
|
||||
}
|
||||
// file sutures (counter-operation to copy) not supported
|
||||
}
|
||||
$out = self::parseLog($log, 4);
|
||||
$out[0]->diff = implode("\n", $change);
|
||||
return $out[0];
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -388,54 +507,65 @@ class IDF_Scm_Mercurial extends IDF_Scm
|
||||
*/
|
||||
public function getChangeLog($commit='tip', $n=10)
|
||||
{
|
||||
$cmd = sprintf(Pluf::f('hg_path', 'hg').' log -R %s -l%s --template %s', escapeshellarg($this->repo), $n, $this->hg_log_template, $commit);
|
||||
$logStyle = new IDF_Scm_Mercurial_LogStyle(IDF_Scm_Mercurial_LogStyle::FULL_LOG);
|
||||
|
||||
// hg accepts revision IDs as arguments to --branch / -b as well and
|
||||
// uses the branch of the revision in question to filter the other
|
||||
// revisions
|
||||
$cmd = sprintf(Pluf::f('hg_path', 'hg').' log --debug -R %s -l%s --style %s -b %s',
|
||||
escapeshellarg($this->repo),
|
||||
$n,
|
||||
escapeshellarg($logStyle->get()),
|
||||
escapeshellarg($commit));
|
||||
$out = array();
|
||||
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
|
||||
self::exec('IDF_Scm_Mercurial::getChangeLog', $cmd, $out);
|
||||
return self::parseLog($out, 4);
|
||||
return self::parseLog($out);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the log lines of a --pretty=medium log output.
|
||||
* Parse the log lines of our custom style format.
|
||||
*
|
||||
* @param array Lines.
|
||||
* @param int Number of lines in the headers (3)
|
||||
* @return array Change log.
|
||||
*/
|
||||
|
||||
public static function parseLog($lines, $hdrs=3)
|
||||
public static function parseLog($lines)
|
||||
{
|
||||
$res = array();
|
||||
$c = array();
|
||||
$i = 0;
|
||||
$hdrs += 1;
|
||||
$headers_processed = false;
|
||||
foreach ($lines as $line) {
|
||||
$i++;
|
||||
if (0 === strpos($line, 'changeset:')) {
|
||||
if ($line == "\0") {
|
||||
$headers_processed = false;
|
||||
if (count($c) > 0) {
|
||||
$c['full_message'] = trim($c['full_message']);
|
||||
if (array_key_exists('full_message', $c))
|
||||
$c['full_message'] = trim($c['full_message']);
|
||||
$res[] = (object) $c;
|
||||
}
|
||||
$c = array();
|
||||
$c['commit'] = substr(strrchr($line, ':'), 1);
|
||||
$c['full_message'] = '';
|
||||
$i=1;
|
||||
continue;
|
||||
|
||||
}
|
||||
if ($i == $hdrs) {
|
||||
$c['title'] = trim($line);
|
||||
continue;
|
||||
}
|
||||
$match = array();
|
||||
if (preg_match('/^(\S+):\s*(.*)/', $line, $match)) {
|
||||
if (!$headers_processed && empty($line)) {
|
||||
$headers_processed = true;
|
||||
continue;
|
||||
}
|
||||
if (!$headers_processed && preg_match('/^(\S+):\s*(.*)/', $line, $match)) {
|
||||
$match[1] = strtolower($match[1]);
|
||||
if ($match[1] == 'user') {
|
||||
if ($match[1] == 'changeset') {
|
||||
$c = array();
|
||||
$c['commit'] = $match[2];
|
||||
$c['tree'] = $c['commit'];
|
||||
$c['full_message'] = '';
|
||||
} elseif ($match[1] == 'author') {
|
||||
$c['author'] = $match[2];
|
||||
} elseif ($match[1] == 'summary') {
|
||||
$c['title'] = $match[2];
|
||||
} elseif ($match[1] == 'branch') {
|
||||
$c['branch'] = $match[2];
|
||||
$c['branch'] = empty($match[2]) ? 'default' : $match[2];
|
||||
} elseif ($match[1] == 'parents') {
|
||||
$parents = preg_split('/\s+/', $match[2], -1, PREG_SPLIT_NO_EMPTY);
|
||||
for ($i=0, $j=count($parents); $i<$j; ++$i) {
|
||||
if ($parents[$i] == '000000000000')
|
||||
unset($parents[$i]);
|
||||
}
|
||||
$c['parents'] = $parents;
|
||||
} else {
|
||||
$c[$match[1]] = trim($match[2]);
|
||||
}
|
||||
@@ -444,15 +574,14 @@ class IDF_Scm_Mercurial extends IDF_Scm
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if ($i > ($hdrs+1)) {
|
||||
$c['full_message'] .= trim($line)."\n";
|
||||
if ($headers_processed) {
|
||||
if (empty($c['title']))
|
||||
$c['title'] = trim($line);
|
||||
else
|
||||
$c['full_message'] .= trim($line)."\n";
|
||||
continue;
|
||||
}
|
||||
}
|
||||
$c['tree'] = !empty($c['commit']) ? trim($c['commit']) : '';
|
||||
$c['branch'] = empty($c['branch']) ? 'default' : $c['branch'];
|
||||
$c['full_message'] = !empty($c['full_message']) ? trim($c['full_message']) : '';
|
||||
$res[] = (object) $c;
|
||||
return $res;
|
||||
}
|
||||
|
||||
|
@@ -31,10 +31,10 @@ class IDF_Scm_Monotone extends IDF_Scm
|
||||
/** the minimum supported interface version */
|
||||
public static $MIN_INTERFACE_VERSION = 13.0;
|
||||
|
||||
private $stdio;
|
||||
|
||||
private static $instances = array();
|
||||
|
||||
private $stdio;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
@@ -609,6 +609,7 @@ class IDF_Scm_Monotone extends IDF_Scm
|
||||
'additions' => array(),
|
||||
'deletions' => array(),
|
||||
'renames' => array(),
|
||||
'copies' => array(),
|
||||
'patches' => array(),
|
||||
'properties' => array(),
|
||||
);
|
||||
@@ -698,6 +699,29 @@ class IDF_Scm_Monotone extends IDF_Scm
|
||||
return (object) $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see IDF_Scm::getProperties()
|
||||
*/
|
||||
public function getProperties($rev, $path='')
|
||||
{
|
||||
$out = $this->stdio->exec(array('interface_version'));
|
||||
// support for querying file attributes of committed revisions
|
||||
// was added for mtn 1.1 (interface version 13.1)
|
||||
if (floatval($out) < 13.1)
|
||||
return array();
|
||||
|
||||
$out = $this->stdio->exec(array('get_attributes', $path), array('r' => $rev));
|
||||
$stanzas = IDF_Scm_Monotone_BasicIO::parse($out);
|
||||
$res = array();
|
||||
|
||||
foreach ($stanzas as $stanza) {
|
||||
$line = $stanza[0];
|
||||
$res[$line['values'][0]] = $line['values'][1];
|
||||
}
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see IDF_Scm::getExtraProperties
|
||||
*/
|
||||
|
@@ -119,7 +119,7 @@ class IDF_Scm_Monotone_Stdio implements IDF_Scm_Monotone_IStdio
|
||||
$remote_db_access = Pluf::f('mtn_db_access', 'remote') == 'remote';
|
||||
|
||||
$cmd = Pluf::f('idf_exec_cmd_prefix', '') .
|
||||
Pluf::f('mtn_path', 'mtn') . ' ';
|
||||
escapeshellarg(Pluf::f('mtn_path', 'mtn')) . ' ';
|
||||
|
||||
$opts = Pluf::f('mtn_opts', array());
|
||||
foreach ($opts as $opt) {
|
||||
|
@@ -48,7 +48,7 @@ class IDF_Scm_Monotone_Usher
|
||||
if ($conn == 'none')
|
||||
return array();
|
||||
|
||||
return preg_split('/[ ]/', $conn);
|
||||
return preg_split('/[ ]/', $conn, -1, PREG_SPLIT_NO_EMPTY);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -73,10 +73,10 @@ class IDF_Scm_Monotone_Usher
|
||||
if ($conn == 'none')
|
||||
return array();
|
||||
|
||||
$single_conns = preg_split('/[ ]/', $conn);
|
||||
$single_conns = preg_split('/[ ]/', $conn, -1, PREG_SPLIT_NO_EMPTY);
|
||||
$ret = array();
|
||||
foreach ($single_conns as $conn) {
|
||||
preg_match('/\((\w+)\)([^:]+):(\d+)/', $conn, $matches);
|
||||
preg_match('/\(([^)]+)\)([^:]+):(\d+)/', $conn, $matches);
|
||||
$ret[$matches[1]][] = (object)array(
|
||||
'server' => $matches[1],
|
||||
'address' => $matches[2],
|
||||
|
@@ -33,7 +33,6 @@
|
||||
*/
|
||||
class IDF_Scm_Svn extends IDF_Scm
|
||||
{
|
||||
|
||||
public $username = '';
|
||||
public $password = '';
|
||||
private $assoc = array('dir' => 'tree',
|
||||
@@ -48,11 +47,7 @@ class IDF_Scm_Svn extends IDF_Scm
|
||||
|
||||
public function isAvailable()
|
||||
{
|
||||
$cmd = sprintf(Pluf::f('svn_path', 'svn').' info --xml --username=%s --password=%s %s',
|
||||
escapeshellarg($this->username),
|
||||
escapeshellarg($this->password),
|
||||
escapeshellarg($this->repo));
|
||||
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
|
||||
$cmd = $this->svnCmd(array('info', '--xml'), $this->repo);
|
||||
$xmlInfo = self::shell_exec('IDF_Scm_Svn::isAvailable', $cmd);
|
||||
|
||||
try {
|
||||
@@ -163,12 +158,7 @@ class IDF_Scm_Svn extends IDF_Scm
|
||||
return IDF_Scm::REVISION_VALID;
|
||||
}
|
||||
|
||||
$cmd = sprintf(Pluf::f('svn_path', 'svn').' info --username=%s --password=%s %s@%s',
|
||||
escapeshellarg($this->username),
|
||||
escapeshellarg($this->password),
|
||||
escapeshellarg($this->repo),
|
||||
escapeshellarg($rev));
|
||||
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
|
||||
$cmd = $this->svnCmd(array('info'), $this->repo, $rev);
|
||||
self::exec('IDF_Scm_Svn::validateRevision', $cmd, $out, $ret);
|
||||
|
||||
if ($ret == 0)
|
||||
@@ -176,7 +166,6 @@ class IDF_Scm_Svn extends IDF_Scm
|
||||
return IDF_Scm::REVISION_INVALID;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test a given object hash.
|
||||
*
|
||||
@@ -191,12 +180,9 @@ class IDF_Scm_Svn extends IDF_Scm
|
||||
}
|
||||
|
||||
// Else, test the path on revision
|
||||
$cmd = sprintf(Pluf::f('svn_path', 'svn').' info --xml --username=%s --password=%s %s@%s',
|
||||
escapeshellarg($this->username),
|
||||
escapeshellarg($this->password),
|
||||
escapeshellarg($this->repo.'/'.self::smartEncode($path)),
|
||||
escapeshellarg($rev));
|
||||
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
|
||||
$cmd = $this->svnCmd(array('info', '--xml'),
|
||||
$this->repo.'/'.self::smartEncode($path),
|
||||
$rev);
|
||||
$xmlInfo = self::shell_exec('IDF_Scm_Svn::testHash', $cmd);
|
||||
|
||||
// If exception is thrown, return false
|
||||
@@ -218,12 +204,9 @@ class IDF_Scm_Svn extends IDF_Scm
|
||||
|
||||
public function getTree($commit, $folder='/', $branch=null)
|
||||
{
|
||||
$cmd = sprintf(Pluf::f('svn_path', 'svn').' ls --xml --username=%s --password=%s %s@%s',
|
||||
escapeshellarg($this->username),
|
||||
escapeshellarg($this->password),
|
||||
escapeshellarg($this->repo.'/'.self::smartEncode($folder)),
|
||||
escapeshellarg($commit));
|
||||
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
|
||||
$cmd = $this->svnCmd(array('ls', '--xml'),
|
||||
$this->repo.'/'.self::smartEncode($folder),
|
||||
$commit);
|
||||
$xml = simplexml_load_string(self::shell_exec('IDF_Scm_Svn::getTree', $cmd));
|
||||
$res = array();
|
||||
$folder = (strlen($folder) and ($folder != '/')) ? $folder.'/' : '';
|
||||
@@ -248,7 +231,6 @@ class IDF_Scm_Svn extends IDF_Scm
|
||||
return $res;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the commit message of a revision revision.
|
||||
*
|
||||
@@ -260,12 +242,7 @@ class IDF_Scm_Svn extends IDF_Scm
|
||||
if (isset($this->cache['commitmess'][$rev])) {
|
||||
return $this->cache['commitmess'][$rev];
|
||||
}
|
||||
$cmd = sprintf(Pluf::f('svn_path', 'svn').' log --xml --limit 1 --username=%s --password=%s %s@%s',
|
||||
escapeshellarg($this->username),
|
||||
escapeshellarg($this->password),
|
||||
escapeshellarg($this->repo),
|
||||
escapeshellarg($rev));
|
||||
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
|
||||
$cmd = $this->svnCmd(array('log', '--xml', '--limit', '1'), $this->repo, $rev);
|
||||
try {
|
||||
$xml = simplexml_load_string(self::shell_exec('IDF_Scm_Svn::getCommitMessage', $cmd));
|
||||
$this->cache['commitmess'][$rev] = (string) $xml->logentry->msg;
|
||||
@@ -281,12 +258,8 @@ class IDF_Scm_Svn extends IDF_Scm
|
||||
if ($rev == null) {
|
||||
$rev = 'HEAD';
|
||||
}
|
||||
$cmd = sprintf(Pluf::f('svn_path', 'svn').' info --xml --username=%s --password=%s %s@%s',
|
||||
escapeshellarg($this->username),
|
||||
escapeshellarg($this->password),
|
||||
escapeshellarg($this->repo.'/'.self::smartEncode($filename)),
|
||||
escapeshellarg($rev));
|
||||
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
|
||||
$cmd = $this->svnCmd(array('info', '--xml'),
|
||||
$this->repo.'/'.self::smartEncode($filename), $rev);
|
||||
$xml = simplexml_load_string(self::shell_exec('IDF_Scm_Svn::getPathInfo', $cmd));
|
||||
if (!isset($xml->entry)) {
|
||||
return false;
|
||||
@@ -308,12 +281,9 @@ class IDF_Scm_Svn extends IDF_Scm
|
||||
|
||||
public function getFile($def, $cmd_only=false)
|
||||
{
|
||||
$cmd = sprintf(Pluf::f('svn_path', 'svn').' cat --username=%s --password=%s %s@%s',
|
||||
escapeshellarg($this->username),
|
||||
escapeshellarg($this->password),
|
||||
escapeshellarg($this->repo.'/'.self::smartEncode($def->fullpath)),
|
||||
escapeshellarg($def->rev));
|
||||
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
|
||||
$cmd = $this->svnCmd(array('cat'),
|
||||
$this->repo.'/'.self::smartEncode($def->fullpath),
|
||||
$def->rev);
|
||||
return ($cmd_only) ?
|
||||
$cmd : self::shell_exec('IDF_Scm_Svn::getFile', $cmd);
|
||||
}
|
||||
@@ -329,11 +299,7 @@ class IDF_Scm_Svn extends IDF_Scm
|
||||
return $this->cache['branches'];
|
||||
}
|
||||
$res = array();
|
||||
$cmd = sprintf(Pluf::f('svn_path', 'svn').' ls --username=%s --password=%s %s@HEAD',
|
||||
escapeshellarg($this->username),
|
||||
escapeshellarg($this->password),
|
||||
escapeshellarg($this->repo.'/branches'));
|
||||
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
|
||||
$cmd = $this->svnCmd(array('ls'), $this->repo.'/branches', 'HEAD');
|
||||
self::exec('IDF_Scm_Svn::getBranches', $cmd, $out, $ret);
|
||||
if ($ret == 0) {
|
||||
foreach ($out as $entry) {
|
||||
@@ -344,11 +310,8 @@ class IDF_Scm_Svn extends IDF_Scm
|
||||
}
|
||||
}
|
||||
ksort($res);
|
||||
$cmd = sprintf(Pluf::f('svn_path', 'svn').' info --username=%s --password=%s %s@HEAD',
|
||||
escapeshellarg($this->username),
|
||||
escapeshellarg($this->password),
|
||||
escapeshellarg($this->repo.'/trunk'));
|
||||
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
|
||||
|
||||
$cmd = $this->svnCmd(array('info'), $this->repo.'/trunk', 'HEAD');
|
||||
self::exec('IDF_Scm_Svn::getBranches', $cmd, $out, $ret);
|
||||
if ($ret == 0) {
|
||||
$res = array('trunk' => 'trunk') + $res;
|
||||
@@ -368,11 +331,7 @@ class IDF_Scm_Svn extends IDF_Scm
|
||||
return $this->cache['tags'];
|
||||
}
|
||||
$res = array();
|
||||
$cmd = sprintf(Pluf::f('svn_path', 'svn').' ls --username=%s --password=%s %s@HEAD',
|
||||
escapeshellarg($this->username),
|
||||
escapeshellarg($this->password),
|
||||
escapeshellarg($this->repo.'/tags'));
|
||||
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
|
||||
$cmd = $this->svnCmd(array('ls'), $this->repo.'/tags', 'HEAD');
|
||||
self::exec('IDF_Scm_Svn::getTags', $cmd, $out, $ret);
|
||||
if ($ret == 0) {
|
||||
foreach ($out as $entry) {
|
||||
@@ -412,7 +371,6 @@ class IDF_Scm_Svn extends IDF_Scm
|
||||
return array();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get commit details.
|
||||
*
|
||||
@@ -426,12 +384,8 @@ class IDF_Scm_Svn extends IDF_Scm
|
||||
return false;
|
||||
}
|
||||
$res = array();
|
||||
$cmd = sprintf(Pluf::f('svn_path', 'svn').' log --xml --limit 1 -v --username=%s --password=%s %s@%s',
|
||||
escapeshellarg($this->username),
|
||||
escapeshellarg($this->password),
|
||||
escapeshellarg($this->repo),
|
||||
escapeshellarg($commit));
|
||||
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
|
||||
$cmd = $this->svnCmd(array('log', '--xml', '--limit', '1', '-v'),
|
||||
$this->repo, $commit);
|
||||
$xmlRes = self::shell_exec('IDF_Scm_Svn::getCommit', $cmd);
|
||||
$xml = simplexml_load_string($xmlRes);
|
||||
$res['author'] = (string) $xml->logentry->author;
|
||||
@@ -473,15 +427,87 @@ class IDF_Scm_Svn extends IDF_Scm
|
||||
private function getDiff($rev='HEAD')
|
||||
{
|
||||
$res = array();
|
||||
$cmd = sprintf(Pluf::f('svn_path', 'svn').' diff -c %s --username=%s --password=%s %s',
|
||||
escapeshellarg($rev),
|
||||
escapeshellarg($this->username),
|
||||
escapeshellarg($this->password),
|
||||
escapeshellarg($this->repo));
|
||||
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
|
||||
$cmd = $this->svnCmd(array('diff', '-c', $rev), $this->repo);
|
||||
return self::shell_exec('IDF_Scm_Svn::getDiff', $cmd);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see IDF_Scm::getChanges()
|
||||
*/
|
||||
public function getChanges($commit)
|
||||
{
|
||||
if ($this->validateRevision($commit) != IDF_Scm::REVISION_VALID) {
|
||||
return null;
|
||||
}
|
||||
$cmd = $this->svnCmd(array('log', '--xml', '-v'), $this->repo, $commit);
|
||||
$out = array();
|
||||
$out = self::shell_exec('IDF_Scm_Svn::getChanges', $cmd);
|
||||
$xml = simplexml_load_string($out);
|
||||
if (count($xml) == 0) {
|
||||
return null;
|
||||
}
|
||||
$entry = current($xml);
|
||||
|
||||
$return = (object) array(
|
||||
'additions' => array(),
|
||||
'deletions' => array(),
|
||||
'patches' => array(),
|
||||
// while SVN has support for attributes, we cannot see their changes
|
||||
// in the log's XML unfortunately
|
||||
'properties' => array(),
|
||||
'copies' => array(),
|
||||
'renames' => array(),
|
||||
);
|
||||
|
||||
foreach ($entry->paths->path as $p) {
|
||||
$path = (string) $p;
|
||||
foreach ($p->attributes() as $k => $v) {
|
||||
$key = (string) $k;
|
||||
$val = (string) $v;
|
||||
if ($key != 'action')
|
||||
continue;
|
||||
if ($val == 'M')
|
||||
$return->patches[] = $path;
|
||||
else if ($val == 'A')
|
||||
$return->additions[] = $path;
|
||||
else if ($val == 'D')
|
||||
$return->deletions[] = $path;
|
||||
}
|
||||
}
|
||||
|
||||
// copies are treated as renames if they have an add _and_ a drop;
|
||||
// only if they only have an add, but no drop, they're treated as copies
|
||||
foreach ($entry->paths->path as $p) {
|
||||
$trg = (string) $p;
|
||||
$src = null;
|
||||
foreach ($p->attributes() as $k => $v) {
|
||||
if ((string) $k == 'copyfrom-path') {
|
||||
$src = (string) $v;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($src == null)
|
||||
continue;
|
||||
|
||||
$srcidx = array_search($src, $return->deletions);
|
||||
$trgidx = array_search($trg, $return->additions);
|
||||
if ($srcidx !== false && $trgidx !== false) {
|
||||
$return->renames[$src] = $trg;
|
||||
unset($return->deletions[$srcidx]);
|
||||
unset($return->additions[$trgidx]);
|
||||
continue;
|
||||
}
|
||||
if ($srcidx === false && $trgidx !== false) {
|
||||
$return->copies[$src] = $trg;
|
||||
unset($return->additions[$trgidx]);
|
||||
continue;
|
||||
}
|
||||
// file sutures (counter-operation to copy) not supported
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get latest changes.
|
||||
@@ -491,20 +517,15 @@ class IDF_Scm_Svn extends IDF_Scm
|
||||
*
|
||||
* @return array Changes.
|
||||
*/
|
||||
public function getChangeLog($branch=null, $n=10)
|
||||
public function getChangeLog($rev=null, $n=10)
|
||||
{
|
||||
if ($branch != 'HEAD' and !preg_match('/^\d+$/', $branch)) {
|
||||
if ($rev != 'HEAD' and !preg_match('/^\d+$/', $rev)) {
|
||||
// we accept only revisions or HEAD
|
||||
$branch = 'HEAD';
|
||||
$rev = 'HEAD';
|
||||
}
|
||||
$res = array();
|
||||
$cmd = sprintf(Pluf::f('svn_path', 'svn').' log --xml -v --limit %s --username=%s --password=%s %s@%s',
|
||||
escapeshellarg($n),
|
||||
escapeshellarg($this->username),
|
||||
escapeshellarg($this->password),
|
||||
escapeshellarg($this->repo),
|
||||
escapeshellarg($branch));
|
||||
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
|
||||
$cmd = $this->svnCmd(array('log', '--xml', '-v', '--limit', $n),
|
||||
$this->repo.'@'.$rev);
|
||||
$xmlRes = self::shell_exec('IDF_Scm_Svn::getChangeLog', $cmd);
|
||||
$xml = simplexml_load_string($xmlRes);
|
||||
foreach ($xml->logentry as $entry) {
|
||||
@@ -520,7 +541,6 @@ class IDF_Scm_Svn extends IDF_Scm
|
||||
return $res;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get additionnals properties on path and revision
|
||||
*
|
||||
@@ -531,12 +551,8 @@ class IDF_Scm_Svn extends IDF_Scm
|
||||
public function getProperties($rev, $path='')
|
||||
{
|
||||
$res = array();
|
||||
$cmd = sprintf(Pluf::f('svn_path', 'svn').' proplist --xml --username=%s --password=%s %s@%s',
|
||||
escapeshellarg($this->username),
|
||||
escapeshellarg($this->password),
|
||||
escapeshellarg($this->repo.'/'.self::smartEncode($path)),
|
||||
escapeshellarg($rev));
|
||||
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
|
||||
$cmd = $this->svnCmd(array('proplist', '--xml'),
|
||||
$this->repo.'/'.self::smartEncode($path), $rev);
|
||||
$xmlProps = self::shell_exec('IDF_Scm_Svn::getProperties', $cmd);
|
||||
$props = simplexml_load_string($xmlProps);
|
||||
|
||||
@@ -554,7 +570,6 @@ class IDF_Scm_Svn extends IDF_Scm
|
||||
return $res;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get a specific additionnal property on path and revision
|
||||
*
|
||||
@@ -566,20 +581,14 @@ class IDF_Scm_Svn extends IDF_Scm
|
||||
private function getProperty($property, $rev, $path='')
|
||||
{
|
||||
$res = array();
|
||||
$cmd = sprintf(Pluf::f('svn_path', 'svn').' propget --xml %s --username=%s --password=%s %s@%s',
|
||||
escapeshellarg($property),
|
||||
escapeshellarg($this->username),
|
||||
escapeshellarg($this->password),
|
||||
escapeshellarg($this->repo.'/'.self::smartEncode($path)),
|
||||
escapeshellarg($rev));
|
||||
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
|
||||
$cmd = $this->svnCmd(array('propget', $property, '--xml'),
|
||||
$this->repo.'/'.self::smartEncode($path), $rev);
|
||||
$xmlProp = self::shell_exec('IDF_Scm_Svn::getProperty', $cmd);
|
||||
$prop = simplexml_load_string($xmlProp);
|
||||
|
||||
return (string) $prop->target->property;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the number of the last commit in the repository.
|
||||
*
|
||||
@@ -590,16 +599,38 @@ class IDF_Scm_Svn extends IDF_Scm
|
||||
public function getLastCommit($rev='HEAD')
|
||||
{
|
||||
$xmlInfo = '';
|
||||
$cmd = sprintf(Pluf::f('svn_path', 'svn').' info --xml --username=%s --password=%s %s@%s',
|
||||
escapeshellarg($this->username),
|
||||
escapeshellarg($this->password),
|
||||
escapeshellarg($this->repo),
|
||||
escapeshellarg($rev));
|
||||
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
|
||||
$cmd = $this->svnCmd(array('info', '--xml'), $this->repo, $rev);
|
||||
$xmlInfo = self::shell_exec('IDF_Scm_Svn::getLastCommit', $cmd);
|
||||
|
||||
$xml = simplexml_load_string($xmlInfo);
|
||||
return (string) $xml->entry->commit['revision'];
|
||||
}
|
||||
|
||||
private function svnCmd($args = array(), $repoarg = null, $revarg = null)
|
||||
{
|
||||
$cmdline = array();
|
||||
$cmdline[] = Pluf::f('idf_exec_cmd_prefix', '');
|
||||
$cmdline[] = Pluf::f('svn_path', 'svn');
|
||||
$cmdline[] = '--no-auth-cache';
|
||||
$cmdline[] = '--username='.escapeshellarg($this->username);
|
||||
$cmdline[] = '--password='.escapeshellarg($this->password);
|
||||
|
||||
foreach ($args as $arg) {
|
||||
$cmdline[] = escapeshellarg($arg);
|
||||
}
|
||||
|
||||
if ($repoarg != null) {
|
||||
if ($revarg != null) {
|
||||
$repoarg .= '@'.$revarg;
|
||||
}
|
||||
$cmdline[] = escapeshellarg($repoarg);
|
||||
}
|
||||
|
||||
if ($revarg != null) {
|
||||
$cmdline[] = '--revision='.escapeshellarg($revarg);
|
||||
}
|
||||
|
||||
return implode(' ', $cmdline);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -22,7 +22,7 @@
|
||||
# ***** END LICENSE BLOCK ***** */
|
||||
|
||||
/**
|
||||
* Storage of the occurence of the words.
|
||||
* Storage of the occurrence of the words.
|
||||
*/
|
||||
class IDF_Search_Occ extends Pluf_Model
|
||||
{
|
||||
@@ -30,7 +30,7 @@ class IDF_Search_Occ extends Pluf_Model
|
||||
|
||||
function init()
|
||||
{
|
||||
$this->_a['verbose'] = __('occurence');
|
||||
$this->_a['verbose'] = __('occurrence');
|
||||
$this->_a['table'] = 'idf_search_occs';
|
||||
$this->_a['model'] = 'IDF_Search_Occ';
|
||||
$this->_a['cols'] = array(
|
||||
@@ -72,13 +72,13 @@ class IDF_Search_Occ extends Pluf_Model
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Integer',
|
||||
'blank' => false,
|
||||
'verbose' => __('occurences'),
|
||||
'verbose' => __('occurrences'),
|
||||
),
|
||||
'pondocc' =>
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Float',
|
||||
'blank' => false,
|
||||
'verbose' => __('ponderated occurence'),
|
||||
'verbose' => __('ponderated occurrence'),
|
||||
),
|
||||
);
|
||||
$this->_a['idx'] = array(
|
||||
|
@@ -48,7 +48,9 @@ class IDF_Tag extends Pluf_Model
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Foreignkey',
|
||||
'model' => 'IDF_Project',
|
||||
'blank' => false,
|
||||
'blank' => true,
|
||||
'is_null' => true,
|
||||
'default' => null,
|
||||
'verbose' => __('project'),
|
||||
),
|
||||
'class' =>
|
||||
@@ -75,6 +77,19 @@ class IDF_Tag extends Pluf_Model
|
||||
),
|
||||
);
|
||||
|
||||
$table = $this->_con->pfx.'idf_project_idf_tag_assoc';
|
||||
$cols = implode(', ', array_keys($this->_a['cols']));
|
||||
$this->_a['views'] = array(
|
||||
'join_projects' =>
|
||||
array(
|
||||
'join' => 'LEFT JOIN '.$table
|
||||
.' ON idf_tag_id=id',
|
||||
'select' => $this->getSelect().',COUNT(idf_project_id) as project_count',
|
||||
'group' => $cols,
|
||||
'props' => array('project_count' => 'project_count'),
|
||||
),
|
||||
);
|
||||
|
||||
$this->_a['idx'] = array(
|
||||
'lcname_idx' =>
|
||||
array(
|
||||
@@ -95,7 +110,7 @@ class IDF_Tag extends Pluf_Model
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a tag if not already existing.
|
||||
* Add a project-specific tag if not already existing.
|
||||
*
|
||||
* @param string Name of the tag.
|
||||
* @param IDF_Project Project of the tag.
|
||||
@@ -122,6 +137,33 @@ class IDF_Tag extends Pluf_Model
|
||||
return $tags[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a global tag if not already existing
|
||||
*
|
||||
* @param string Name of the tag.
|
||||
* @param string Class of the tag (IDF_TAG_DEFAULT_CLASS)
|
||||
* @return IDF_Tag The tag.
|
||||
*/
|
||||
public static function addGlobal($name, $class=IDF_TAG_DEFAULT_CLASS)
|
||||
{
|
||||
$class = trim($class);
|
||||
$name = trim($name);
|
||||
$gtag = new IDF_Tag();
|
||||
$sql = new Pluf_SQL('class=%s AND lcname=%s AND project IS NULL',
|
||||
array($class, mb_strtolower($name)));
|
||||
$tags = $gtag->getList(array('filter' => $sql->gen()));
|
||||
if ($tags->count() < 1) {
|
||||
// create a new tag
|
||||
$tag = new IDF_Tag();
|
||||
$tag->name = $name;
|
||||
$tag->class = $class;
|
||||
$tag->project = null;
|
||||
$tag->create();
|
||||
return $tag;
|
||||
}
|
||||
return $tags[0];
|
||||
}
|
||||
|
||||
function __toString()
|
||||
{
|
||||
if ($this->class != IDF_TAG_DEFAULT_CLASS) {
|
||||
|
@@ -31,6 +31,6 @@ class IDF_Template_HotKey extends Pluf_Template_Tag
|
||||
function start($key, $view, $params=array(), $get_params=array())
|
||||
{
|
||||
$url = addslashes(Pluf_HTTP_URL_urlForView($view, $params, $get_params));
|
||||
echo "jQuery.hotkeys.add('$key',{disableInInput: true},function (){window.location.href='$url';});";
|
||||
echo "$(document).bind('keydown','$key',function (){window.location.href='$url';});";
|
||||
}
|
||||
}
|
||||
|
@@ -58,7 +58,7 @@ class IDF_Template_IssueComment extends Pluf_Template_Tag
|
||||
implode('|', $nouns);
|
||||
$text = IDF_Template_safePregReplace('#((?:'.$prefix.')(?:\s+r?))([0-9a-f]{1,40}((?:\s+and|\s+or|,)\s+r?[0-9a-f]{1,40})*)\b#i',
|
||||
array($this, 'callbackCommits'), $text);
|
||||
$text = IDF_Template_safePregReplace('=(src:)([^\s@#,\(\)\\\\]+(?:(\\\\)[\s@#][^\s@#,\(\)\\\\]+){0,})+(?:\@([^\s#,]+))(?:#(\d+))?=im',
|
||||
$text = IDF_Template_safePregReplace('=(src:)([^\s@#,\(\)\\\\]+(?:(\\\\)[\s@#][^\s@#,\(\)\\\\]+){0,})+(?:\@([^\s#,]+))?(?:#(\d+))?=im',
|
||||
array($this, 'callbackSource'), $text);
|
||||
}
|
||||
if ($wordwrap) $text = Pluf_Text::wrapHtml($text, 69, "\n");
|
||||
|
@@ -55,8 +55,17 @@ class IDF_Template_Markdown extends Pluf_Template_Tag
|
||||
$text = IDF_Template_safePregReplace('#\[\[([A-Za-z0-9\-]+)\]\]#im',
|
||||
array($this, 'callbackWikiPageNoName'),
|
||||
$text);
|
||||
|
||||
$filter = new IDF_Template_MarkdownPrefilter();
|
||||
echo $filter->go(Pluf_Text_MarkDown_parse($text));
|
||||
$text = $filter->go(Pluf_Text_MarkDown_parse($text));
|
||||
|
||||
// Replace [[!ResourceName]] with corresponding HTML for the resource;
|
||||
// we need to do that after the HTML filtering as we'd otherwise be unable to use
|
||||
// certain HTML elements, such as iframes, that are used to display text content
|
||||
// FIXME: no support for escaping yet in place
|
||||
echo IDF_Template_safePregReplace('#\[\[!([A-Za-z0-9\-]+)(?:,\s*([^\]]+))?\]\]#im',
|
||||
array($this, 'callbackWikiResource'),
|
||||
$text);
|
||||
}
|
||||
|
||||
function callbackWikiPageNoName($m)
|
||||
@@ -69,15 +78,99 @@ class IDF_Template_Markdown extends Pluf_Template_Tag
|
||||
{
|
||||
$sql = new Pluf_SQL('project=%s AND title=%s',
|
||||
array($this->project->id, $m[2]));
|
||||
$pages = Pluf::factory('IDF_WikiPage')->getList(array('filter'=>$sql->gen()));
|
||||
$pages = Pluf::factory('IDF_Wiki_Page')->getList(array('filter'=>$sql->gen()));
|
||||
if ($pages->count() != 1 and $this->request->rights['hasWikiAccess']
|
||||
and !$this->request->user->isAnonymous()) {
|
||||
return '<img style="vertical-align: text-bottom;" alt=" " src="'.Pluf::f('url_media').'/idf/img/add.png" /><a href="'.Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::create', array($this->project->shortname), array('name'=>$m[2])).'" title="'.__('Create this documentation page').'">'.$m[1].'</a>';
|
||||
return '<img style="vertical-align: text-bottom;" alt=" " src="'.Pluf::f('url_media').'/idf/img/add.png" /><a href="'.Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::createPage', array($this->project->shortname), array('name'=>$m[2])).'" title="'.__('Create this documentation page').'">'.$m[1].'</a>';
|
||||
}
|
||||
if (!$this->request->rights['hasWikiAccess'] or $pages->count() == 0) {
|
||||
return $m[1];
|
||||
}
|
||||
return '<a href="'.Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::view', array($this->project->shortname, $pages[0]->title)).'" title="'.Pluf_esc($pages[0]->summary).'">'.$m[1].'</a>';
|
||||
return '<a href="'.Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::viewPage', array($this->project->shortname, $pages[0]->title)).'" title="'.Pluf_esc($pages[0]->summary).'">'.$m[1].'</a>';
|
||||
}
|
||||
|
||||
function callbackWikiResource($m)
|
||||
{
|
||||
@list($match, $resourceName, $opts) = $m;
|
||||
|
||||
if (!$this->request->rights['hasWikiAccess']) {
|
||||
return '<span title="'.__('You are not allowed to access the wiki.').'">'.$match.'</span>';
|
||||
}
|
||||
|
||||
$sql = new Pluf_SQL('project=%s AND title=%s',
|
||||
array($this->project->id, $resourceName));
|
||||
$resources = Pluf::factory('IDF_Wiki_Resource')->getList(array('filter'=>$sql->gen()));
|
||||
|
||||
if ($resources->count() == 0) {
|
||||
if ($this->request->user->isAnonymous()) {
|
||||
return '<span title="'.__('The wiki resource has not been found.').'">'.$match.'</span>';
|
||||
}
|
||||
|
||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::createResource',
|
||||
array($this->project->shortname),
|
||||
array('name' => $resourceName));
|
||||
return '<img style="vertical-align: text-bottom;" alt=" " src="'.Pluf::f('url_media').'/idf/img/add.png" />'.
|
||||
'<a href="'.$url.'" title="'.__('The wiki resource has not been found. Create it!').'">'.$match.'</a>';
|
||||
}
|
||||
|
||||
// by default, render the most recent revision
|
||||
$resourceRevision = $resources[0]->get_current_revision();
|
||||
|
||||
list($urlConf, $urlMatches) = $this->request->view;
|
||||
|
||||
// if we currently look at an existing wiki page, look up its name and find the proper resource (if any)
|
||||
if ($urlConf['model'] == 'IDF_Views_Wiki' && $urlConf['method'] == 'viewPage') {
|
||||
$sql = new Pluf_SQL('project=%s AND title=%s',
|
||||
array($this->project->id, $urlMatches[2]));
|
||||
$pages = Pluf::factory('IDF_Wiki_Page')->getList(array('filter'=>$sql->gen()));
|
||||
if ($pages->count() == 0) throw new Exception('page not found');
|
||||
$pageRevision = $pages[0]->get_current_revision();
|
||||
|
||||
// if we look at an old version of the page, figure out the resource version back then
|
||||
if (isset($this->request->GET['rev']) and preg_match('/^[0-9]+$/', $this->request->GET['rev'])) {
|
||||
$pageRevision = Pluf_Shortcuts_GetObjectOr404('IDF_Wiki_PageRevision',
|
||||
$this->request->GET['rev']);
|
||||
// this is actually an invariant since we came so far looking at
|
||||
// and rendering the old revision already
|
||||
if ($pageRevision == null) {
|
||||
throw new Exception('page revision with id '.$this->request->GET['rev'].' not found');
|
||||
}
|
||||
}
|
||||
|
||||
$sql = new Pluf_SQL('wikiresource=%s AND idf_wiki_pagerevision_id=%s',
|
||||
array($resources[0]->id, $pageRevision->id));
|
||||
$resourceRevision = Pluf::factory('IDF_Wiki_ResourceRevision')->getOne(
|
||||
array('filter' => $sql->gen(), 'view' => 'join_pagerevision'));
|
||||
|
||||
if ($resourceRevision == null) {
|
||||
return '<span title="'.__('This revision of the resource is no longer available.').'">'.$match.'</span>';
|
||||
}
|
||||
}
|
||||
|
||||
$validOpts = array(
|
||||
'align' => '/^(left|right|center)$/',
|
||||
'width' => '/^\d+(%|px|em)?$/',
|
||||
'height' => '/^\d+(%|px|em)?$/',
|
||||
'preview' => '/^yes|no$/',
|
||||
'title' => '/.+/',
|
||||
);
|
||||
|
||||
$parsedOpts = array();
|
||||
// FIXME: no support for escaping yet in place
|
||||
$opts = preg_split('/\s*,\s*/', $opts, -1, PREG_SPLIT_NO_EMPTY);
|
||||
foreach ((array)@$opts as $opt)
|
||||
{
|
||||
list($key, $value) = preg_split('/\s*=\s*/', $opt, 2);
|
||||
if (!array_key_exists($key, $validOpts)) {
|
||||
continue;
|
||||
}
|
||||
if (!preg_match($validOpts[$key], $value)) {
|
||||
continue;
|
||||
}
|
||||
$parsedOpts[$key] = $value;
|
||||
}
|
||||
|
||||
return $resourceRevision->render($parsedOpts);
|
||||
}
|
||||
|
||||
function callbackEmbeddedDoc($m)
|
||||
|
118
src/IDF/Template/MarkdownForge.php
Normal file
118
src/IDF/Template/MarkdownForge.php
Normal file
@@ -0,0 +1,118 @@
|
||||
<?php
|
||||
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/*
|
||||
# ***** BEGIN LICENSE BLOCK *****
|
||||
# This file is part of InDefero, an open source project management application.
|
||||
# Copyright (C) 2008-2011 Céondo Ltd and contributors.
|
||||
#
|
||||
# InDefero 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.
|
||||
#
|
||||
# InDefero is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
#
|
||||
# ***** END LICENSE BLOCK ***** */
|
||||
|
||||
Pluf::loadFunction('Pluf_Text_MarkDown_parse');
|
||||
|
||||
class IDF_Template_MarkdownForge extends Pluf_Template_Tag
|
||||
{
|
||||
private $request;
|
||||
|
||||
public function start($text, $request)
|
||||
{
|
||||
$this->request = $request;
|
||||
$filter = new IDF_Template_MarkdownPrefilter();
|
||||
$text = $filter->go(Pluf_Text_MarkDown_parse($text));
|
||||
|
||||
// replace {}-macros with the corresponding template rendering
|
||||
echo IDF_Template_safePregReplace('#\{(\w+)(?:,\s*([^\}]+))?\}#im',
|
||||
array($this, 'callbackMacros'),
|
||||
$text);
|
||||
}
|
||||
|
||||
public function callbackMacros($matches)
|
||||
{
|
||||
@list(, $macro, $opts) = $matches;
|
||||
$known_macros = array('projectlist');
|
||||
if (!in_array($macro, $known_macros)) {
|
||||
return $matches[0];
|
||||
}
|
||||
$callbackName = 'callback'.ucfirst(strtolower($macro)).'Macro';
|
||||
return $this->callbackProjectlistMacro($opts);
|
||||
}
|
||||
|
||||
public function callbackProjectlistMacro($opts)
|
||||
{
|
||||
$validOpts = array(
|
||||
'label' => '/^\d+|(\w+:)?\w+$/',
|
||||
'order' => '/^name|activity$/',
|
||||
'limit' => '/^\d+$/',
|
||||
);
|
||||
|
||||
$parsedOpts = array();
|
||||
// FIXME: no support for escaping yet in place
|
||||
$opts = preg_split('/\s*,\s*/', $opts, -1, PREG_SPLIT_NO_EMPTY);
|
||||
foreach ((array)@$opts as $opt)
|
||||
{
|
||||
list($key, $value) = preg_split('/\s*=\s*/', $opt, 2);
|
||||
if (!array_key_exists($key, $validOpts)) {
|
||||
continue;
|
||||
}
|
||||
if (!preg_match($validOpts[$key], $value)) {
|
||||
continue;
|
||||
}
|
||||
$parsedOpts[$key] = $value;
|
||||
}
|
||||
|
||||
$tag = false;
|
||||
if (!empty($parsedOpts['label'])) {
|
||||
if (is_numeric($parsedOpts['label'])) {
|
||||
$tag = Pluf::factory('IDF_Tag')->get($parsedOpts['label']);
|
||||
} else {
|
||||
@list($class, $name) = preg_split('/:/', $parsedOpts['label'], 2);
|
||||
if (empty($name)) {
|
||||
$name = $class;
|
||||
$class = IDF_TAG_DEFAULT_CLASS;
|
||||
}
|
||||
$sql = new Pluf_SQL('class=%s AND lcname=%s AND project IS NULL',
|
||||
array(strtolower($class), mb_strtolower($name)));
|
||||
$tag = Pluf::factory('IDF_Tag')->getOne(array('filter' => $sql->gen()));
|
||||
}
|
||||
// ignore non-global tags
|
||||
if ($tag !== false && $tag->project > 0) {
|
||||
$tag = false;
|
||||
}
|
||||
}
|
||||
|
||||
$order = 'name';
|
||||
if (!empty($parsedOpts['order'])) {
|
||||
$order = $parsedOpts['order'];
|
||||
}
|
||||
|
||||
$projects = IDF_Views::getProjects($this->request->user, $tag, $order);
|
||||
if (!empty($parsedOpts['limit']) && $parsedOpts['limit'] < count($projects)) {
|
||||
// there is no array_slice on ArrayObject, do'h!
|
||||
$projectsCopy = array();
|
||||
for ($i=0; $i<$parsedOpts['limit']; ++$i)
|
||||
$projectsCopy[] = $projects[$i];
|
||||
$projects = $projectsCopy;
|
||||
}
|
||||
|
||||
$tmpl = new Pluf_Template('idf/project-list.html');
|
||||
$context = new Pluf_Template_Context(array(
|
||||
'projects' => $projects,
|
||||
'order' => 'name',
|
||||
));
|
||||
return $tmpl->render($context);
|
||||
}
|
||||
}
|
||||
|
@@ -90,40 +90,69 @@ class IDF_Template_MarkdownPrefilter extends Pluf_Text_HTML_Filter
|
||||
);
|
||||
|
||||
public $allowed = array(
|
||||
'a' => array('href', 'title', 'rel'),
|
||||
'abbr' => array('title'),
|
||||
'address' => array(),
|
||||
'b' => array(),
|
||||
'blockquote' => array(),
|
||||
'br' => array(),
|
||||
'caption' => array(),
|
||||
'code' => array(),
|
||||
'dd' => array(),
|
||||
'del' => array('cite', 'class', 'datetime', 'dir', 'id', 'title'),
|
||||
'div' => array('align', 'class'),
|
||||
'dl' => array(),
|
||||
'dt' => array(),
|
||||
'em' => array(),
|
||||
'h1' => array('id'),
|
||||
'h2' => array('id'),
|
||||
'h3' => array('id'),
|
||||
'h4' => array('id'),
|
||||
'h5' => array('id'),
|
||||
'h6' => array('id'),
|
||||
'hr' => array(),
|
||||
'i' => array(),
|
||||
'img' => array('src', 'class', 'alt', 'height', 'width', 'style'),
|
||||
'ins' => array('cite', 'class', 'datetime', 'dir', 'id', 'title'),
|
||||
'li' => array(),
|
||||
'ol' => array(),
|
||||
'p' => array('align', 'class'),
|
||||
'pre' => array(),
|
||||
'strong' => array(),
|
||||
'table' => array('summary'),
|
||||
'td' => array('style'),
|
||||
'th' => array(),
|
||||
'tr' => array(),
|
||||
'ul' => array(),
|
||||
'a' => array('class', 'dir', 'id', 'style', 'title',
|
||||
'href', 'hreflang', 'rel'),
|
||||
'abbr' => array('class', 'dir', 'id', 'style', 'title'),
|
||||
'address' => array('class', 'dir', 'id', 'style', 'title'),
|
||||
'b' => array('class', 'dir', 'id', 'style', 'title'),
|
||||
'blockquote' => array('class', 'dir', 'id', 'style', 'title',
|
||||
'cite'),
|
||||
'br' => array('class', 'id', 'style', 'title'),
|
||||
'caption' => array('class', 'dir', 'id', 'style', 'title',
|
||||
'align'), // deprecated attribute),
|
||||
'code' => array('class', 'dir', 'id', 'style', 'title'),
|
||||
'dd' => array('class', 'dir', 'id', 'style', 'title'),
|
||||
'del' => array('class', 'dir', 'id', 'style', 'title',
|
||||
'cite', 'datetime'),
|
||||
'div' => array('class', 'dir', 'id', 'style', 'title',
|
||||
'align'), // deprecated attribute
|
||||
'dl' => array('class', 'dir', 'id', 'style', 'title'),
|
||||
'dt' => array('class', 'dir', 'id', 'style', 'title'),
|
||||
'em' => array('class', 'dir', 'id', 'style', 'title'),
|
||||
'font' => array('class', 'dir', 'id', 'style', 'title', // deprecated element
|
||||
'color', 'face', 'size'), // deprecated attribute
|
||||
'h1' => array('class', 'dir', 'id', 'style', 'title',
|
||||
'align'), // deprecated attribute
|
||||
'h2' => array('class', 'dir', 'id', 'style', 'title',
|
||||
'align'), // deprecated attribute
|
||||
'h3' => array('class', 'dir', 'id', 'style', 'title',
|
||||
'align'), // deprecated attribute
|
||||
'h4' => array('class', 'dir', 'id', 'style', 'title',
|
||||
'align'), // deprecated attribute
|
||||
'h5' => array('class', 'dir', 'id', 'style', 'title',
|
||||
'align'), // deprecated attribute
|
||||
'h6' => array('class', 'dir', 'id', 'style', 'title',
|
||||
'align'), // deprecated attribute
|
||||
'hr' => array('class', 'dir', 'id', 'style', 'title',
|
||||
'align', 'noshade', 'size', 'width'), // deprecated attribute
|
||||
'i' => array('class', 'dir', 'id', 'style', 'title'),
|
||||
'img' => array('class', 'dir', 'id', 'style', 'title',
|
||||
'src', 'alt', 'height', 'width'),
|
||||
'ins' => array('class', 'dir', 'id', 'style', 'title',
|
||||
'cite', 'datetime'),
|
||||
'li' => array('class', 'dir', 'id', 'style', 'title',
|
||||
'type'), // deprecated attribute
|
||||
'ol' => array('class', 'dir', 'id', 'style', 'title',
|
||||
'type'), // deprecated attribute
|
||||
'p' => array('class', 'dir', 'id', 'style', 'title',
|
||||
'align'), // deprecated attribute
|
||||
'pre' => array('class', 'dir', 'id', 'style', 'title',
|
||||
'width'), // deprecated attribute
|
||||
'strong' => array('class', 'dir', 'id', 'style', 'title'),
|
||||
'table' => array('class', 'dir', 'id', 'style', 'title',
|
||||
'border', 'cellpadding', 'cellspacing', 'frame', 'rules', 'summary', 'width',
|
||||
'align', 'bgcolor'), // deprecated attribute
|
||||
'td' => array('class', 'dir', 'id', 'style', 'title',
|
||||
'align', 'colspan', 'headers', 'rowspan', 'scope', 'valign',
|
||||
'bgcolor', 'height', 'nowrap', 'width'), // deprecated attribute
|
||||
'th' => array('class', 'dir', 'id', 'style', 'title',
|
||||
'align', 'colspan', 'rowspan', 'scope', 'valign',
|
||||
'bgcolor', 'height', 'nowrap', 'width'), // deprecated attribute
|
||||
'tr' => array('class', 'dir', 'id', 'style', 'title',
|
||||
'align', 'valign',
|
||||
'bgcolor'), // deprecated attribute
|
||||
'ul' => array('class', 'dir', 'id', 'style', 'title',
|
||||
'type'), // deprecated attribute
|
||||
);
|
||||
// tags which should always be self-closing (e.g. "<img />")
|
||||
public $no_close = array(
|
||||
|
35
src/IDF/Template/Tag/UploadUrl.php
Normal file
35
src/IDF/Template/Tag/UploadUrl.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/*
|
||||
# ***** BEGIN LICENSE BLOCK *****
|
||||
# This file is part of InDefero, an open source project management application.
|
||||
# Copyright (C) 2011 Céondo Ltd and contributors.
|
||||
#
|
||||
# InDefero 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.
|
||||
#
|
||||
# InDefero is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
#
|
||||
# ***** END LICENSE BLOCK ***** */
|
||||
|
||||
class IDF_Template_Tag_UploadUrl extends Pluf_Template_Tag
|
||||
{
|
||||
function start($file='')
|
||||
{
|
||||
echo IDF_Template_Tag_UploadUrl::url($file);
|
||||
}
|
||||
|
||||
public static function url($file='')
|
||||
{
|
||||
return Pluf::f('url_upload', Pluf_Template_Tag_MediaUrl::url() . '/upload') . $file;
|
||||
}
|
||||
}
|
@@ -32,21 +32,24 @@ class IDF_Tests_TestDiff extends UnitTestCase
|
||||
parent::__construct('Test the diff parser.');
|
||||
}
|
||||
|
||||
public function testBinaryDiff()
|
||||
{
|
||||
$diff_content = file_get_contents(dirname(__FILE__).'/test-diff.diff');
|
||||
$orig = file_get_contents(dirname(__FILE__).'/test-diff-view.html');
|
||||
$diff = new IDF_Diff($diff_content);
|
||||
$diff->parse();
|
||||
$def = $diff->files['src/IDF/templates/idf/issues/view.html'];
|
||||
|
||||
$orig_lines = preg_split("/\015\012|\015|\012/", $orig);
|
||||
$merged = $diff->mergeChunks($orig_lines, $def, 10);
|
||||
$lchunk = end($merged);
|
||||
$lline = end($lchunk);
|
||||
$this->assertEqual(array('', '166', '{/if}{/block}'),
|
||||
$lline);
|
||||
}
|
||||
//
|
||||
// IDF_Diff::mergeChunks() is now private, so this test needs to be rewritten
|
||||
//
|
||||
//public function testBinaryDiff()
|
||||
//{
|
||||
// $diff_content = file_get_contents(dirname(__FILE__).'/test-diff.diff');
|
||||
// $orig = file_get_contents(dirname(__FILE__).'/test-diff-view.html');
|
||||
// $diff = new IDF_Diff($diff_content);
|
||||
// $diff->parse();
|
||||
// $def = $diff->files['src/IDF/templates/idf/issues/view.html'];
|
||||
//
|
||||
// $orig_lines = preg_split("/\015\012|\015|\012/", $orig);
|
||||
// $merged = $diff->mergeChunks($orig_lines, $def, 10);
|
||||
// $lchunk = end($merged);
|
||||
// $lline = end($lchunk);
|
||||
// $this->assertEqual(array('', '166', '{/if}{/block}'),
|
||||
// $lline);
|
||||
//}
|
||||
|
||||
public function testDiffWithHeaders()
|
||||
{
|
||||
|
@@ -77,6 +77,12 @@ class IDF_Upload extends Pluf_Model
|
||||
'default' => 0,
|
||||
'verbose' => __('file size in bytes'),
|
||||
),
|
||||
'md5' =>
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Text',
|
||||
'blank' => true,
|
||||
'verbose' => __('MD5'),
|
||||
),
|
||||
'submitter' =>
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Foreignkey',
|
||||
@@ -144,6 +150,7 @@ class IDF_Upload extends Pluf_Model
|
||||
if ($this->id == '') {
|
||||
$this->creation_dtime = gmdate('Y-m-d H:i:s');
|
||||
$this->modif_dtime = gmdate('Y-m-d H:i:s');
|
||||
$this->md5 = md5_file ($this->getFullPath());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,6 +167,11 @@ class IDF_Upload extends Pluf_Model
|
||||
return Pluf::f('url_upload').'/'.$project->shortname.'/files/'.$this->file;
|
||||
}
|
||||
|
||||
function getFullPath()
|
||||
{
|
||||
return(Pluf::f('upload_path').'/'.$this->get_project()->shortname.'/files/'.$this->file);
|
||||
}
|
||||
|
||||
/**
|
||||
* We drop the information from the timeline.
|
||||
*/
|
||||
@@ -189,7 +201,7 @@ class IDF_Upload extends Pluf_Model
|
||||
$out .= sprintf(__('<a href="%1$s" title="View download">Download %2$d</a>, %3$s'), $url, $this->id, Pluf_esc($this->summary)).'</td>';
|
||||
$out .= '</tr>';
|
||||
$out .= "\n".'<tr class="extra"><td colspan="2">
|
||||
<div class="helptext right">'.sprintf(__('Addition of <a href="%s">download %d</a>, by %s'), $url, $this->id, $user).'</div></td></tr>';
|
||||
<div class="helptext right">'.sprintf(__('Addition of <a href="%1$s">download %2$d</a>, by %3$s'), $url, $this->id, $user).'</div></td></tr>';
|
||||
return Pluf_Template::markSafe($out);
|
||||
}
|
||||
|
||||
@@ -199,7 +211,7 @@ class IDF_Upload extends Pluf_Model
|
||||
.Pluf_HTTP_URL_urlForView('IDF_Views_Download::view',
|
||||
array($request->project->shortname,
|
||||
$this->id));
|
||||
$title = sprintf(__('%s: Download %d added - %s'),
|
||||
$title = sprintf(__('%1$s: Download %2$d added - %3$s'),
|
||||
$request->project->name,
|
||||
$this->id, $this->summary);
|
||||
$date = Pluf_Date::gmDateToGmString($this->creation_dtime);
|
||||
@@ -222,31 +234,91 @@ class IDF_Upload extends Pluf_Model
|
||||
*/
|
||||
public function notify($conf, $create=true)
|
||||
{
|
||||
if ('' == $conf->getVal('downloads_notification_email', '')) {
|
||||
return;
|
||||
}
|
||||
$current_locale = Pluf_Translation::getLocale();
|
||||
$langs = Pluf::f('languages', array('en'));
|
||||
Pluf_Translation::loadSetLocale($langs[0]);
|
||||
$project = $this->get_project();
|
||||
$url = str_replace(array('%p', '%d'),
|
||||
array($project->shortname, $this->id),
|
||||
$conf->getVal('upload_webhook_url', ''));
|
||||
|
||||
$context = new Pluf_Template_Context(
|
||||
array('file' => $this,
|
||||
'urlfile' => $this->getAbsoluteUrl($this->get_project()),
|
||||
'project' => $this->get_project(),
|
||||
'tags' => $this->get_tags_list(),
|
||||
));
|
||||
$tmpl = new Pluf_Template('idf/downloads/download-created-email.txt');
|
||||
$text_email = $tmpl->render($context);
|
||||
$addresses = explode(',', $conf->getVal('downloads_notification_email'));
|
||||
foreach ($addresses as $address) {
|
||||
$email = new Pluf_Mail(Pluf::f('from_email'),
|
||||
$tags = array();
|
||||
foreach ($this->get_tags_list() as $tag) {
|
||||
$tags[] = $tag->class.':'.$tag->name;
|
||||
}
|
||||
|
||||
$submitter = $this->get_submitter();
|
||||
$payload = array(
|
||||
'to_send' => array(
|
||||
'project' => $project->shortname,
|
||||
'id' => $this->id,
|
||||
'summary' => $this->summary,
|
||||
'changelog' => $this->changelog,
|
||||
'filename' => $this->file,
|
||||
'filesize' => $this->filesize,
|
||||
'md5sum' => $this->md5,
|
||||
'submitter_login' => $submitter->login,
|
||||
'submitter_email' => $submitter->email,
|
||||
'tags' => $tags,
|
||||
),
|
||||
'project_id' => $project->id,
|
||||
'authkey' => $project->getWebHookKey(),
|
||||
'url' => $url,
|
||||
);
|
||||
|
||||
if ($create === true) {
|
||||
$payload['method'] = 'PUT';
|
||||
$payload['to_send']['creation_date'] = $this->creation_dtime;
|
||||
} else {
|
||||
$payload['method'] = 'POST';
|
||||
$payload['to_send']['update_date'] = $this->modif_dtime;
|
||||
}
|
||||
|
||||
$item = new IDF_Queue();
|
||||
$item->type = 'upload';
|
||||
$item->payload = $payload;
|
||||
$item->create();
|
||||
|
||||
$current_locale = Pluf_Translation::getLocale();
|
||||
|
||||
$from_email = Pluf::f('from_email');
|
||||
$messageId = '<'.md5('upload'.$this->id.md5(Pluf::f('secret_key'))).'@'.Pluf::f('mail_host', 'localhost').'>';
|
||||
$recipients = $project->getNotificationRecipientsForTab('downloads');
|
||||
|
||||
foreach ($recipients as $address => $language) {
|
||||
|
||||
if ($this->get_submitter()->email === $address) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Pluf_Translation::loadSetLocale($language);
|
||||
|
||||
$context = new Pluf_Template_Context(array(
|
||||
'file' => $this,
|
||||
'urlfile' => $this->getAbsoluteUrl($project),
|
||||
'project' => $project,
|
||||
'tags' => $this->get_tags_list(),
|
||||
));
|
||||
|
||||
$tplfile = 'idf/downloads/download-created-email.txt';
|
||||
$subject = __('New download - %1$s (%2$s)');
|
||||
$headers = array('Message-ID' => $messageId);
|
||||
if (!$create) {
|
||||
$tplfile = 'idf/downloads/download-updated-email.txt';
|
||||
$subject = __('Updated download - %1$s (%2$s)');
|
||||
$headers = array('References' => $messageId);
|
||||
}
|
||||
|
||||
$tmpl = new Pluf_Template($tplfile);
|
||||
$text_email = $tmpl->render($context);
|
||||
|
||||
$email = new Pluf_Mail($from_email,
|
||||
$address,
|
||||
sprintf(__('New download - %s (%s)'),
|
||||
sprintf($subject,
|
||||
$this->summary,
|
||||
$this->get_project()->shortname));
|
||||
$project->shortname));
|
||||
$email->addTextMessage($text_email);
|
||||
$email->addHeaders($headers);
|
||||
$email->sendMail();
|
||||
}
|
||||
|
||||
Pluf_Translation::loadSetLocale($current_locale);
|
||||
}
|
||||
}
|
@@ -32,20 +32,67 @@ Pluf::loadFunction('Pluf_Shortcuts_GetFormForModel');
|
||||
class IDF_Views
|
||||
{
|
||||
/**
|
||||
* List all the projects managed by InDefero.
|
||||
*
|
||||
* Only the public projects are listed or the private with correct
|
||||
* rights.
|
||||
* The index view.
|
||||
*/
|
||||
public function index($request, $match, $api=false)
|
||||
public function index($request, $match)
|
||||
{
|
||||
$projects = self::getProjects($request->user);
|
||||
$stats = self::getProjectsStatistics ($projects);
|
||||
$forge = IDF_Forge::instance();
|
||||
if (!$forge->isCustomForgePageEnabled()) {
|
||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views::listProjects');
|
||||
return new Pluf_HTTP_Response_Redirect($url);
|
||||
}
|
||||
|
||||
if ($api == true) return $projects;
|
||||
return Pluf_Shortcuts_RenderToResponse('idf/index.html',
|
||||
array('page_title' => __('Welcome'),
|
||||
'content' => $forge->getCustomForgePageContent(),
|
||||
),
|
||||
$request);
|
||||
}
|
||||
|
||||
/**
|
||||
* List all projects unfiltered
|
||||
*
|
||||
* @param unknown_type $request
|
||||
* @param unknown_type $match
|
||||
* @return Pluf_HTTP_Response
|
||||
*/
|
||||
public function listProjects($request, $match)
|
||||
{
|
||||
$match = array('', 'all', 'name');
|
||||
return $this->listProjectsByLabel($request, $match);
|
||||
}
|
||||
|
||||
/**
|
||||
* List projects, optionally filtered by label
|
||||
*
|
||||
* @param unknown_type $request
|
||||
* @param unknown_type $match
|
||||
* @return Pluf_HTTP_Response
|
||||
*/
|
||||
public function listProjectsByLabel($request, $match)
|
||||
{
|
||||
list(, $tagId, $order) = $match;
|
||||
|
||||
$tag = false;
|
||||
if ($tagId !== 'all') {
|
||||
$tag = Pluf::factory('IDF_Tag')->get($match[1]);
|
||||
// ignore non-global tags
|
||||
if ($tag !== false && $tag->project > 0) {
|
||||
$tag = false;
|
||||
}
|
||||
}
|
||||
$order = in_array($order, array('name', 'activity')) ? $order : 'name';
|
||||
|
||||
$projects = self::getProjects($request->user, $tag, $order);
|
||||
$stats = self::getProjectsStatistics($projects);
|
||||
$projectLabels = self::getProjectLabelsWithCounts($request->user);
|
||||
|
||||
return Pluf_Shortcuts_RenderToResponse('idf/listProjects.html',
|
||||
array('page_title' => __('Projects'),
|
||||
'projects' => $projects,
|
||||
'projectLabels' => $projectLabels,
|
||||
'tag' => $tag,
|
||||
'order' => $order,
|
||||
'stats' => new Pluf_Template_ContextVars($stats)),
|
||||
$request);
|
||||
}
|
||||
@@ -292,6 +339,22 @@ class IDF_Views
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Download archive FAQ.
|
||||
*/
|
||||
public function faqArchiveFormat($request, $match)
|
||||
{
|
||||
$title = __('InDefero Upload Archive Format');
|
||||
$projects = self::getProjects($request->user);
|
||||
return Pluf_Shortcuts_RenderToResponse('idf/faq-archive-format.html',
|
||||
array(
|
||||
'page_title' => $title,
|
||||
'projects' => $projects,
|
||||
),
|
||||
$request);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* API FAQ.
|
||||
*/
|
||||
@@ -309,45 +372,136 @@ class IDF_Views
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of projects accessible for the user.
|
||||
* Returns a list of ids of projects that are visible for the given user
|
||||
*
|
||||
* @param Pluf_User
|
||||
* @return ArrayObject IDF_Project
|
||||
* @param Pluf_User $user
|
||||
*/
|
||||
public static function getProjects($user)
|
||||
private static function getUserVisibleProjectIds($user)
|
||||
{
|
||||
$db =& Pluf::db();
|
||||
$false = Pluf_DB_BooleanToDb(false, $db);
|
||||
if ($user->isAnonymous()) {
|
||||
$sql = sprintf('%s=%s', $db->qn('private'), $false);
|
||||
return Pluf::factory('IDF_Project')->getList(array('filter'=> $sql,
|
||||
'order' => 'name ASC'));
|
||||
}
|
||||
// the administrator can see all projects
|
||||
if ($user->administrator) {
|
||||
return Pluf::factory('IDF_Project')->getList(array('order' => 'name ASC'));
|
||||
}
|
||||
// grab the list of projects where the user is admin, member
|
||||
// or authorized
|
||||
$perms = array(
|
||||
Pluf_Permission::getFromString('IDF.project-member'),
|
||||
Pluf_Permission::getFromString('IDF.project-owner'),
|
||||
Pluf_Permission::getFromString('IDF.project-authorized-user')
|
||||
);
|
||||
$sql = new Pluf_SQL("model_class='IDF_Project' AND owner_class='Pluf_User' AND owner_id=%s AND negative=".$false, $user->id);
|
||||
$rows = Pluf::factory('Pluf_RowPermission')->getList(array('filter' => $sql->gen()));
|
||||
|
||||
$sql = sprintf('%s=%s', $db->qn('private'), $false);
|
||||
if ($rows->count() > 0) {
|
||||
$ids = array();
|
||||
foreach ($rows as $row) {
|
||||
$ids[] = $row->model_id;
|
||||
$sql_results = $db->select(
|
||||
'SELECT id FROM '.Pluf::f('db_table_prefix', '').'idf_projects'
|
||||
);
|
||||
foreach ($sql_results as $id) {
|
||||
$ids[] = $id['id'];
|
||||
}
|
||||
$sql .= sprintf(' OR id IN (%s)', implode(', ', $ids));
|
||||
return $ids;
|
||||
}
|
||||
return Pluf::factory('IDF_Project')->getList(array('filter' => $sql,
|
||||
'order' => 'name ASC'));
|
||||
|
||||
// anonymous users can only see non-private projects
|
||||
$false = Pluf_DB_BooleanToDb(false, $db);
|
||||
$sql_results = $db->select(
|
||||
'SELECT id FROM '.$db->pfx.'idf_projects '.
|
||||
'WHERE '.$db->qn('private').'='.$false
|
||||
);
|
||||
|
||||
$ids = array();
|
||||
foreach ($sql_results as $id) {
|
||||
$ids[] = $id['id'];
|
||||
}
|
||||
|
||||
// registered users may additionally see private projects with which
|
||||
// they're somehow affiliated
|
||||
if (!$user->isAnonymous()) {
|
||||
$perms = array(
|
||||
Pluf_Permission::getFromString('IDF.project-member'),
|
||||
Pluf_Permission::getFromString('IDF.project-owner'),
|
||||
Pluf_Permission::getFromString('IDF.project-authorized-user')
|
||||
);
|
||||
$permSql = new Pluf_SQL(
|
||||
"model_class='IDF_Project' AND owner_class='Pluf_User' ".
|
||||
"AND owner_id=%s AND negative=".$false, $user->id
|
||||
);
|
||||
$rows = Pluf::factory('Pluf_RowPermission')->getList(array('filter' => $permSql->gen()));
|
||||
if ($rows->count() > 0) {
|
||||
foreach ($rows as $row) {
|
||||
if (in_array($row->model_id, $ids))
|
||||
continue;
|
||||
$ids[] = $row->model_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of projects accessible for the user and optionally filtered by tag.
|
||||
*
|
||||
* @param Pluf_User
|
||||
* @param IDF_Tag
|
||||
* @return ArrayObject IDF_Project
|
||||
*/
|
||||
public static function getProjects($user, $tag = false, $order = 'name')
|
||||
{
|
||||
$db =& Pluf::db();
|
||||
$sql = new Pluf_SQL('1=1');
|
||||
if ($tag !== false) {
|
||||
$sql->SAnd(new Pluf_SQL('idf_tag_id=%s', $tag->id));
|
||||
}
|
||||
|
||||
$projectIds = self::getUserVisibleProjectIds($user);
|
||||
if (count($projectIds) == 0) {
|
||||
return new ArrayObject();
|
||||
}
|
||||
|
||||
$sql->SAnd(new Pluf_SQL(sprintf($db->pfx.'idf_projects.id IN (%s)', implode(', ', $projectIds))));
|
||||
|
||||
$orderTypes = array(
|
||||
'name' => 'name ASC',
|
||||
'activity' => 'value DESC, name ASC',
|
||||
);
|
||||
return Pluf::factory('IDF_Project')->getList(array(
|
||||
'filter'=> $sql->gen(),
|
||||
'view' => 'join_activities_and_tags',
|
||||
'order' => $orderTypes[$order],
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of global tags each carrying the number of projects that have the
|
||||
* particular tag set
|
||||
*
|
||||
* @param Pluf_User $user
|
||||
* @return array
|
||||
*/
|
||||
public static function getProjectLabelsWithCounts($user) {
|
||||
|
||||
$sql = new Pluf_SQL('project IS NULL');
|
||||
|
||||
$projectIds = self::getUserVisibleProjectIds($user);
|
||||
if (count($projectIds) == 0) {
|
||||
return new ArrayObject();
|
||||
}
|
||||
|
||||
$sql->SAnd(new Pluf_SQL(sprintf('idf_project_id IN (%s)', implode(', ', $projectIds))));
|
||||
|
||||
$tagList = Pluf::factory('IDF_Tag')->getList(array(
|
||||
'filter' => $sql->gen(),
|
||||
'view' => 'join_projects',
|
||||
'order' => 'class ASC, lcname ASC'
|
||||
));
|
||||
|
||||
$maxProjectCount = 0;
|
||||
foreach ($tagList as $tag) {
|
||||
$maxProjectCount = max($maxProjectCount, $tag->project_count);
|
||||
}
|
||||
|
||||
$tags = array();
|
||||
foreach ($tagList as $tag) {
|
||||
// group by class
|
||||
if (!array_key_exists($tag->class, $tags)) {
|
||||
$tags[$tag->class] = array();
|
||||
}
|
||||
$tag->rel_project_count = $tag->project_count / (double) $maxProjectCount;
|
||||
$tags[$tag->class][] = $tag;
|
||||
}
|
||||
return $tags;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns statistics on a list of projects.
|
||||
*
|
||||
@@ -356,30 +510,30 @@ class IDF_Views
|
||||
*/
|
||||
public static function getProjectsStatistics($projects)
|
||||
{
|
||||
// Init the return var
|
||||
$forgestats = array('downloads' => 0,
|
||||
'reviews' => 0,
|
||||
'issues' => 0,
|
||||
'docpages' => 0,
|
||||
'commits' => 0);
|
||||
|
||||
// Count for each projects
|
||||
foreach ($projects as $p) {
|
||||
$pstats = $p->getStats ();
|
||||
$forgestats['downloads'] += $pstats['downloads'];
|
||||
$forgestats['reviews'] += $pstats['reviews'];
|
||||
$forgestats['issues'] += $pstats['issues'];
|
||||
$forgestats['docpages'] += $pstats['docpages'];
|
||||
$forgestats['commits'] += $pstats['commits'];
|
||||
$projectIds = array(0);
|
||||
foreach ($projects as $project) {
|
||||
$projectIds[] = $project->id;
|
||||
}
|
||||
|
||||
// Count projects
|
||||
$forgestats['projects'] = count($projects);
|
||||
$forgestats = array();
|
||||
|
||||
// Count members
|
||||
$sql = new Pluf_SQL('first_name != %s', array('---'));
|
||||
$forgestats['members'] = Pluf::factory('Pluf_User')
|
||||
->getCount(array('filter' => $sql->gen()));
|
||||
// count overall project stats
|
||||
$forgestats['total'] = 0;
|
||||
$what = array(
|
||||
'downloads' => 'IDF_Upload',
|
||||
'reviews' => 'IDF_Review',
|
||||
'issues' => 'IDF_Issue',
|
||||
'docpages' => 'IDF_Wiki_Page',
|
||||
'commits' => 'IDF_Commit',
|
||||
);
|
||||
|
||||
foreach ($what as $key => $model) {
|
||||
$count = Pluf::factory($model)->getCount(array(
|
||||
'filter' => sprintf('project IN (%s)', implode(', ', $projectIds))
|
||||
));
|
||||
$forgestats[$key] = $count;
|
||||
$forgestats['total'] += $count;
|
||||
}
|
||||
|
||||
return $forgestats;
|
||||
}
|
||||
|
@@ -32,20 +32,40 @@ Pluf::loadFunction('Pluf_Shortcuts_GetFormForModel');
|
||||
class IDF_Views_Admin
|
||||
{
|
||||
/**
|
||||
* Home page of the administration.
|
||||
*
|
||||
* It should provide an overview of the forge status.
|
||||
* Start page of the administration.
|
||||
*/
|
||||
public $home_precond = array('Pluf_Precondition::staffRequired');
|
||||
public function home($request, $match)
|
||||
public $forge_precond = array('Pluf_Precondition::staffRequired');
|
||||
public function forge($request, $match)
|
||||
{
|
||||
$title = __('Forge Management');
|
||||
return Pluf_Shortcuts_RenderToResponse('idf/gadmin/home.html',
|
||||
$forge = IDF_Forge::instance();
|
||||
if ($request->method == 'POST') {
|
||||
$form = new IDF_Form_Admin_ForgeConf($request->POST);
|
||||
if ($form->isValid()) {
|
||||
$forge->setCustomForgePageEnabled($form->cleaned_data['enabled']);
|
||||
$forge->setCustomForgePageContent($form->cleaned_data['content']);
|
||||
$request->user->setMessage(__('The forge configuration has been saved.'));
|
||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Admin::forge');
|
||||
return new Pluf_HTTP_Response_Redirect($url);
|
||||
}
|
||||
} else {
|
||||
$params = array();
|
||||
$params['enabled'] = $forge->isCustomForgePageEnabled();
|
||||
if (($content = $forge->getCustomForgePageContent(false)) !== false) {
|
||||
$params['content'] = $content;
|
||||
}
|
||||
if (count($params) == 0) {
|
||||
$params = null; //Nothing in the db, so new form.
|
||||
}
|
||||
$form = new IDF_Form_Admin_ForgeConf($params);
|
||||
}
|
||||
return Pluf_Shortcuts_RenderToResponse('idf/gadmin/forge/index.html',
|
||||
array(
|
||||
'page_title' => $title,
|
||||
),
|
||||
'page_title' => $title,
|
||||
'form' => $form,
|
||||
),
|
||||
$request);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Projects overview.
|
||||
@@ -81,6 +101,40 @@ class IDF_Views_Admin
|
||||
$request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Administrate the labels of a project.
|
||||
*/
|
||||
public $projectLabels_precond = array('Pluf_Precondition::staffRequired');
|
||||
public function projectLabels($request, $match)
|
||||
{
|
||||
$title = __('Project Labels');
|
||||
$forge = IDF_Forge::instance();
|
||||
if ($request->method == 'POST') {
|
||||
$form = new IDF_Form_Admin_LabelConf($request->POST);
|
||||
if ($form->isValid()) {
|
||||
$forge->setProjectLabels($form->cleaned_data['project_labels']);
|
||||
$request->user->setMessage(__('The label configuration has been saved.'));
|
||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Admin::projectLabels');
|
||||
return new Pluf_HTTP_Response_Redirect($url);
|
||||
}
|
||||
} else {
|
||||
$params = array();
|
||||
if (($labels = $forge->getProjectLabels(false)) !== false) {
|
||||
$params['project_labels'] = $labels;
|
||||
}
|
||||
if (count($params) == 0) {
|
||||
$params = null; //Nothing in the db, so new form.
|
||||
}
|
||||
$form = new IDF_Form_Admin_LabelConf($params);
|
||||
}
|
||||
return Pluf_Shortcuts_RenderToResponse('idf/gadmin/projects/labels.html',
|
||||
array(
|
||||
'page_title' => $title,
|
||||
'form' => $form,
|
||||
),
|
||||
$request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Edition of a project.
|
||||
*
|
||||
@@ -106,12 +160,16 @@ class IDF_Views_Admin
|
||||
} else {
|
||||
$form = new IDF_Form_Admin_ProjectUpdate(null, $params);
|
||||
}
|
||||
$arrays = IDF_Views_Project::autoCompleteArrays();
|
||||
return Pluf_Shortcuts_RenderToResponse('idf/gadmin/projects/update.html',
|
||||
array(
|
||||
'page_title' => $title,
|
||||
'project' => $project,
|
||||
'form' => $form,
|
||||
),
|
||||
array_merge(
|
||||
array(
|
||||
'page_title' => $title,
|
||||
'project' => $project,
|
||||
'form' => $form,
|
||||
),
|
||||
$arrays
|
||||
),
|
||||
$request);
|
||||
}
|
||||
|
||||
@@ -139,12 +197,17 @@ class IDF_Views_Admin
|
||||
$form = new IDF_Form_Admin_ProjectCreate(null, $extra);
|
||||
}
|
||||
$base = Pluf::f('url_base').Pluf::f('idf_base').'/p/';
|
||||
|
||||
$arrays = IDF_Views_Project::autoCompleteArrays();
|
||||
return Pluf_Shortcuts_RenderToResponse('idf/gadmin/projects/create.html',
|
||||
array(
|
||||
'page_title' => $title,
|
||||
'form' => $form,
|
||||
'base_url' => $base,
|
||||
),
|
||||
array_merge(
|
||||
array(
|
||||
'page_title' => $title,
|
||||
'form' => $form,
|
||||
'base_url' => $base,
|
||||
),
|
||||
$arrays
|
||||
),
|
||||
$request);
|
||||
}
|
||||
|
||||
|
@@ -93,8 +93,7 @@ class IDF_Views_Api
|
||||
|
||||
public function projectIndex($request, $match)
|
||||
{
|
||||
$view = new IDF_Views();
|
||||
$projects = $view->index($request, $match, true);
|
||||
$projects = IDF_Views::getProjects($request->user);
|
||||
|
||||
$data = array();
|
||||
foreach ($projects as $p) {
|
||||
|
@@ -190,25 +190,46 @@ class IDF_Views_Download
|
||||
}
|
||||
|
||||
/**
|
||||
* Download a file.
|
||||
* Download the file with the given name.
|
||||
*/
|
||||
public $download_precond = array('IDF_Precondition::accessDownloads');
|
||||
public function download($request, $match)
|
||||
{
|
||||
$prj = $request->project;
|
||||
$upload = Pluf_Shortcuts_GetObjectOr404('IDF_Upload', $match[2]);
|
||||
$sql = new Pluf_SQL('file=%s', array($match[2]));
|
||||
$upload = Pluf::factory('IDF_Upload')->getOne(array('filter' => $sql->gen()));
|
||||
if (!$upload) throw new Pluf_HTTP_Error404();
|
||||
$prj->inOr404($upload);
|
||||
$upload->downloads += 1;
|
||||
$upload->update();
|
||||
return new Pluf_HTTP_Response_Redirect($upload->getAbsoluteUrl($prj));
|
||||
$path = $upload->getFullPath();
|
||||
$mime = IDF_FileUtil::getMimeType($path);
|
||||
$render = new Pluf_HTTP_Response_File($path, $mime[0]);
|
||||
$render->headers['Content-MD5'] = $upload->md5;
|
||||
$render->headers['Content-Disposition'] = 'attachment; filename="'.$upload->file.'"';
|
||||
return $render;
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit a new file for download.
|
||||
* Download the file with the given ID (for legacy links).
|
||||
*/
|
||||
public $submit_precond = array('IDF_Precondition::accessDownloads',
|
||||
public $downloadById_precond = array('IDF_Precondition::accessDownloads');
|
||||
public function downloadById($request, $match)
|
||||
{
|
||||
$upload = Pluf_Shortcuts_GetObjectOr404('IDF_Upload', $match[2]);
|
||||
return new Pluf_HTTP_Response_Redirect(
|
||||
Pluf_HTTP_URL_urlForView('IDF_Views_Download::download', array(
|
||||
$match[1], $upload->file
|
||||
)), 301
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new file for download.
|
||||
*/
|
||||
public $create_precond = array('IDF_Precondition::accessDownloads',
|
||||
'IDF_Precondition::projectMemberOrOwner');
|
||||
public function submit($request, $match)
|
||||
public function create($request, $match)
|
||||
{
|
||||
$prj = $request->project;
|
||||
$title = __('New Download');
|
||||
@@ -230,7 +251,7 @@ class IDF_Views_Download
|
||||
array('project' => $prj,
|
||||
'user' => $request->user));
|
||||
}
|
||||
return Pluf_Shortcuts_RenderToResponse('idf/downloads/submit.html',
|
||||
return Pluf_Shortcuts_RenderToResponse('idf/downloads/create.html',
|
||||
array(
|
||||
'auto_labels' => self::autoCompleteArrays($prj),
|
||||
'page_title' => $title,
|
||||
@@ -239,6 +260,39 @@ class IDF_Views_Download
|
||||
$request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new downloads from an uploaded archive.
|
||||
*/
|
||||
public $createFromArchive_precond = array('IDF_Precondition::accessDownloads',
|
||||
'IDF_Precondition::projectMemberOrOwner');
|
||||
public function createFromArchive($request, $match)
|
||||
{
|
||||
$prj = $request->project;
|
||||
$title = __('New Downloads from Archive');
|
||||
if ($request->method == 'POST') {
|
||||
$form = new IDF_Form_UploadArchive(array_merge($request->POST, $request->FILES),
|
||||
array('project' => $prj,
|
||||
'user' => $request->user));
|
||||
if ($form->isValid()) {
|
||||
$upload = $form->save();
|
||||
$request->user->setMessage(__('The archive has been uploaded and processed.'));
|
||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Download::index',
|
||||
array($prj->shortname));
|
||||
return new Pluf_HTTP_Response_Redirect($url);
|
||||
}
|
||||
} else {
|
||||
$form = new IDF_Form_UploadArchive(null,
|
||||
array('project' => $prj,
|
||||
'user' => $request->user));
|
||||
}
|
||||
return Pluf_Shortcuts_RenderToResponse('idf/downloads/createFromArchive.html',
|
||||
array(
|
||||
'page_title' => $title,
|
||||
'form' => $form,
|
||||
),
|
||||
$request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the autocomplete arrays for the little AJAX stuff.
|
||||
*/
|
||||
@@ -299,13 +353,11 @@ class IDF_Views_Download
|
||||
$pag->no_results_text = __('No downloads were found.');
|
||||
$pag->sort_order = array('creation_dtime', 'DESC');
|
||||
$pag->setFromRequest($request);
|
||||
$tags = $prj->getTagCloud('downloads');
|
||||
return Pluf_Shortcuts_RenderToResponse('idf/downloads/index.html',
|
||||
array(
|
||||
'page_title' => $title,
|
||||
'label' => $tag,
|
||||
'downloads' => $pag,
|
||||
'tags' => $tags,
|
||||
'dlabel' => $dtag,
|
||||
),
|
||||
$request);
|
||||
|
@@ -42,6 +42,7 @@ class IDF_Views_Issue
|
||||
// Get stats about the issues
|
||||
$open = $prj->getIssueCountByStatus('open');
|
||||
$closed = $prj->getIssueCountByStatus('closed');
|
||||
$overdue = $prj->getIssueCountByDueDate();
|
||||
// Paginator to paginate the issues
|
||||
$pag = new Pluf_Paginator(new IDF_Issue());
|
||||
$pag->class = 'recent-issues';
|
||||
@@ -61,9 +62,10 @@ class IDF_Views_Issue
|
||||
'id' => __('Id'),
|
||||
array('summary', 'IDF_Views_Issue_SummaryAndLabels', __('Summary')),
|
||||
array('status', 'IDF_Views_Issue_ShowStatus', __('Status')),
|
||||
array('due_dtime', 'IDF_Views_Issue_DueDate', __('Due Date')),
|
||||
array('modif_dtime', 'Pluf_Paginator_DateAgo', __('Last Updated')),
|
||||
);
|
||||
$pag->configure($list_display, array(), array('id', 'status', 'modif_dtime'));
|
||||
$pag->configure($list_display, array(), array('id', 'status', 'due_dtime', 'modif_dtime'));
|
||||
$pag->items_per_page = 10;
|
||||
$pag->no_results_text = __('No issues were found.');
|
||||
$pag->setFromRequest($request);
|
||||
@@ -71,12 +73,105 @@ class IDF_Views_Issue
|
||||
'page_title' => $title,
|
||||
'open' => $open,
|
||||
'closed' => $closed,
|
||||
'issues' => $pag);
|
||||
'overdue' => $overdue,
|
||||
'issues' => $pag,
|
||||
'cloud' => 'issues',
|
||||
);
|
||||
if ($api) return $params;
|
||||
return Pluf_Shortcuts_RenderToResponse('idf/issues/index.html',
|
||||
$params, $request);
|
||||
}
|
||||
|
||||
/**
|
||||
* View the issue summary.
|
||||
* TODO Add thoses data in cache, and process it only after an issue update
|
||||
*/
|
||||
public $summary_precond = array('IDF_Precondition::accessIssues');
|
||||
public function summary($request, $match)
|
||||
{
|
||||
$tagStatistics = array();
|
||||
$ownerStatistics = array();
|
||||
$duedateStatistics = array();
|
||||
$status = array();
|
||||
$isTrackerEmpty = false;
|
||||
|
||||
$prj = $request->project;
|
||||
$opened = $prj->getIssueCountByStatus('open');
|
||||
$closed = $prj->getIssueCountByStatus('closed');
|
||||
|
||||
// Check if the tracker is empty
|
||||
if ($opened === 0 && $closed === 0) {
|
||||
$isTrackerEmpty = true;
|
||||
} else {
|
||||
if ($opened > 0 || $closed > 0) {
|
||||
// Issue status statistics
|
||||
$status['Open'] = array($opened, (int)(100 * $opened / ($opened + $closed)));
|
||||
$status['Closed'] = array($closed, (int)(100 * $closed / ($opened + $closed)));
|
||||
}
|
||||
|
||||
if ($opened > 0) {
|
||||
// Issue owner statistics
|
||||
$owners = $prj->getIssueCountByOwner('open');
|
||||
foreach ($owners as $user => $nb) {
|
||||
if ($user === '') {
|
||||
$key = __('Not assigned');
|
||||
$login = null;
|
||||
} else {
|
||||
$obj = Pluf::factory('Pluf_User')->getOne(array('filter'=>'id='.$user));
|
||||
$key = $obj->first_name . ' ' . $obj->last_name;
|
||||
$login = $obj->login;
|
||||
}
|
||||
$ownerStatistics[$key] = array($nb, (int)(100 * $nb / $opened), $login);
|
||||
}
|
||||
arsort($ownerStatistics);
|
||||
|
||||
// Issue due date statistics
|
||||
$overdue = $prj->getIssueCountByDueDate();
|
||||
$combined_opened = $overdue + $opened;
|
||||
$duedateStatistics = array();
|
||||
if ($combined_opened > 0) {
|
||||
$duedateStatistics = array($overdue, (int)(100 * $overdue / $combined_opened));
|
||||
}
|
||||
|
||||
// Issue class tag statistics
|
||||
$grouped_tags = $prj->getTagCloud();
|
||||
foreach ($grouped_tags as $class => $tags) {
|
||||
foreach ($tags as $tag) {
|
||||
$tagStatistics[$class][$tag->name] = array($tag->nb_use, $tag->id);
|
||||
}
|
||||
uasort($tagStatistics[$class], function ($a, $b) {
|
||||
if ($a[0] === $b[0])
|
||||
return 0;
|
||||
|
||||
return ($a[0] > $b[0]) ? -1 : 1;
|
||||
});
|
||||
}
|
||||
foreach($tagStatistics as $k => $v) {
|
||||
$nbIssueInClass = 0;
|
||||
foreach ($v as $val) {
|
||||
$nbIssueInClass += $val[0];
|
||||
}
|
||||
foreach ($v as $kk => $vv) {
|
||||
$tagStatistics[$k][$kk] = array($vv[0], (int)(100 * $vv[0] / $nbIssueInClass), $vv[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$title = sprintf(__('Summary of tracked issues in %s.'), (string) $prj);
|
||||
|
||||
return Pluf_Shortcuts_RenderToResponse('idf/issues/summary.html',
|
||||
array('page_title' => $title,
|
||||
'trackerEmpty' => $isTrackerEmpty,
|
||||
'project' => $prj,
|
||||
'tagStatistics' => $tagStatistics,
|
||||
'ownerStatistics' => $ownerStatistics,
|
||||
'duedateStatistics' => $duedateStatistics,
|
||||
'status' => $status,
|
||||
),
|
||||
$request);
|
||||
}
|
||||
|
||||
/**
|
||||
* View the issues watch list of a given user.
|
||||
* Limited to a specified project
|
||||
@@ -105,9 +200,16 @@ class IDF_Views_Issue
|
||||
$nb_open = Pluf::factory('IDF_Issue')->getCount(array('filter'=>$sql->gen()));
|
||||
$sql = new Pluf_SQL('project=%s AND id IN ('.$issue_ids.') AND status IN ('.implode(', ', $ctags).')', array($prj->id));
|
||||
$nb_closed = Pluf::factory('IDF_Issue')->getCount(array('filter'=>$sql->gen()));
|
||||
$sql = new Pluf_SQL('project=%s AND id IN ('.$issue_ids.') AND status IN ('.implode(', ', $otags).') AND due_dtime < NOW()', array($prj->id));
|
||||
$nb_overdue = Pluf::factory('IDF_Issue')->getCount(array('filter'=>$sql->gen()));
|
||||
|
||||
// Generate a filter for the paginator
|
||||
switch ($match[2]) {
|
||||
case 'overdue':
|
||||
$title = sprintf(__('Watch List: Overdue Issues for %s'), (string) $prj);
|
||||
$summary = __('This table shows the overdue issues in your watch list for %s project.', (string) $prj);
|
||||
$f_sql = new Pluf_SQL('project=%s AND id IN ('.$issue_ids.') AND status IN ('.implode(', ', $otags).') AND due_dtime < NOW()', array($prj->id));
|
||||
break;
|
||||
case 'closed':
|
||||
$title = sprintf(__('Watch List: Closed Issues for %s'), (string) $prj);
|
||||
$summary = __('This table shows the closed issues in your watch list for %s project.', (string) $prj);
|
||||
@@ -138,9 +240,10 @@ class IDF_Views_Issue
|
||||
'id' => __('Id'),
|
||||
array('summary', 'IDF_Views_Issue_SummaryAndLabels', __('Summary')),
|
||||
array('status', 'IDF_Views_Issue_ShowStatus', __('Status')),
|
||||
array('due_dtime', 'IDF_Views_Issue_DueDate', __('Due Date')),
|
||||
array('modif_dtime', 'Pluf_Paginator_DateAgo', __('Last Updated')),
|
||||
);
|
||||
$pag->configure($list_display, array(), array('id', 'status', 'modif_dtime'));
|
||||
$pag->configure($list_display, array(), array('id', 'status', 'due_dtime', 'modif_dtime'));
|
||||
$pag->items_per_page = 10;
|
||||
$pag->no_results_text = __('No issues were found.');
|
||||
$pag->setFromRequest($request);
|
||||
@@ -149,6 +252,7 @@ class IDF_Views_Issue
|
||||
'page_title' => $title,
|
||||
'open' => $nb_open,
|
||||
'closed' => $nb_closed,
|
||||
'overdue' => $nb_overdue,
|
||||
'issues' => $pag,
|
||||
),
|
||||
$request);
|
||||
@@ -188,9 +292,16 @@ class IDF_Views_Issue
|
||||
$nb_open = Pluf::factory('IDF_Issue')->getCount(array('filter'=>$sql->gen()));
|
||||
$sql = new Pluf_SQL('id IN ('.$issue_ids.') AND status IN ('.implode(', ', $ctags).')', array());
|
||||
$nb_closed = Pluf::factory('IDF_Issue')->getCount(array('filter'=>$sql->gen()));
|
||||
$sql = new Pluf_SQL('id IN ('.$issue_ids.') AND status IN ('.implode(', ', $otags).') AND due_dtime < NOW()', array());
|
||||
$nb_overdue = Pluf::factory('IDF_Issue')->getCount(array('filter'=>$sql->gen()));
|
||||
|
||||
// Generate a filter for the paginator
|
||||
switch ($match[1]) {
|
||||
case 'overdue':
|
||||
$title = sprintf(__('Watch List: Overdue Issues'));
|
||||
$summary = __('This table shows the overdue issues in your watch list.');
|
||||
$f_sql = new Pluf_SQL('id IN ('.$issue_ids.') AND status IN ('.implode(', ', $otags).') AND due_dtime < NOW()', array());
|
||||
break;
|
||||
case 'closed':
|
||||
$title = sprintf(__('Watch List: Closed Issues'));
|
||||
$summary = __('This table shows the closed issues in your watch list.');
|
||||
@@ -220,9 +331,10 @@ class IDF_Views_Issue
|
||||
array('summary', 'IDF_Views_Issue_SummaryAndLabelsUnknownProject', __('Summary')),
|
||||
array('project', 'Pluf_Paginator_FkToString', __('Project')),
|
||||
array('status', 'IDF_Views_Issue_ShowStatus', __('Status')),
|
||||
array('due_dtime', 'IDF_Views_Issue_DueDate', __('Due Date')),
|
||||
array('modif_dtime', 'Pluf_Paginator_DateAgo', __('Last Updated')),
|
||||
);
|
||||
$pag->configure($list_display, array(), array('id', 'project', 'status', 'modif_dtime'));
|
||||
$pag->configure($list_display, array(), array('id', 'project', 'status', 'due_dtime', 'modif_dtime'));
|
||||
$pag->items_per_page = 10;
|
||||
$pag->no_results_text = __('No issues were found.');
|
||||
$pag->setFromRequest($request);
|
||||
@@ -230,6 +342,7 @@ class IDF_Views_Issue
|
||||
array('page_title' => $title,
|
||||
'open' => $nb_open,
|
||||
'closed' => $nb_closed,
|
||||
'overdue' => $nb_overdue,
|
||||
'issues' => $pag,
|
||||
),
|
||||
$request);
|
||||
@@ -240,42 +353,55 @@ class IDF_Views_Issue
|
||||
*
|
||||
* Only open issues are shown.
|
||||
*/
|
||||
public $myIssues_precond = array('IDF_Precondition::accessIssues',
|
||||
'Pluf_Precondition::loginRequired');
|
||||
public function myIssues($request, $match)
|
||||
public $userIssues_precond = array('IDF_Precondition::accessIssues');
|
||||
public function userIssues($request, $match)
|
||||
{
|
||||
$prj = $request->project;
|
||||
|
||||
$sql = new Pluf_SQL('login=%s', array($match[2]));
|
||||
$user = Pluf::factory('Pluf_User')->getOne(array('filter' => $sql->gen()));
|
||||
if ($user === null) {
|
||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Issue::index',
|
||||
array($prj->shortname));
|
||||
return new Pluf_HTTP_Response_Redirect($url);
|
||||
}
|
||||
|
||||
$otags = $prj->getTagIdsByStatus('open');
|
||||
$ctags = $prj->getTagIdsByStatus('closed');
|
||||
if (count($otags) == 0) $otags[] = 0;
|
||||
if (count($ctags) == 0) $ctags[] = 0;
|
||||
switch ($match[2]) {
|
||||
switch ($match[3]) {
|
||||
case 'submit':
|
||||
$title = sprintf(__('My Submitted %s Issues'), (string) $prj);
|
||||
$f_sql = new Pluf_SQL('project=%s AND submitter=%s AND status IN ('.implode(', ', $otags).')', array($prj->id, $request->user->id));
|
||||
$titleFormat = __('%1$s %2$s Submitted %3$s Issues');
|
||||
$f_sql = new Pluf_SQL('project=%s AND submitter=%s AND status IN ('.implode(', ', $otags).')', array($prj->id, $user->id));
|
||||
break;
|
||||
case 'submitclosed':
|
||||
$title = sprintf(__('My Closed Submitted %s Issues'), (string) $prj);
|
||||
$f_sql = new Pluf_SQL('project=%s AND submitter=%s AND status IN ('.implode(', ', $ctags).')', array($prj->id, $request->user->id));
|
||||
$titleFormat = __('%1$s %2$s Closed Submitted %3$s Issues');
|
||||
$f_sql = new Pluf_SQL('project=%s AND submitter=%s AND status IN ('.implode(', ', $ctags).')', array($prj->id, $user->id));
|
||||
break;
|
||||
case 'ownerclosed':
|
||||
$title = sprintf(__('My Closed Working %s Issues'), (string) $prj);
|
||||
$f_sql = new Pluf_SQL('project=%s AND owner=%s AND status IN ('.implode(', ', $ctags).')', array($prj->id, $request->user->id));
|
||||
$titleFormat = __('%1$s %2$s Closed Working %3$s Issues');
|
||||
$f_sql = new Pluf_SQL('project=%s AND owner=%s AND status IN ('.implode(', ', $ctags).')', array($prj->id, $user->id));
|
||||
break;
|
||||
default:
|
||||
$title = sprintf(__('My Working %s Issues'), (string) $prj);
|
||||
$f_sql = new Pluf_SQL('project=%s AND owner=%s AND status IN ('.implode(', ', $otags).')', array($prj->id, $request->user->id));
|
||||
$titleFormat = __('%1$s %2$s Working %3$s Issues');
|
||||
$f_sql = new Pluf_SQL('project=%s AND owner=%s AND status IN ('.implode(', ', $otags).')', array($prj->id, $user->id));
|
||||
break;
|
||||
}
|
||||
$title = sprintf($titleFormat,
|
||||
$user->first_name,
|
||||
$user->last_name,
|
||||
(string) $prj);
|
||||
|
||||
// Get stats about the issues
|
||||
$sql = new Pluf_SQL('project=%s AND submitter=%s AND status IN ('.implode(', ', $otags).')', array($prj->id, $request->user->id));
|
||||
$sql = new Pluf_SQL('project=%s AND submitter=%s AND status IN ('.implode(', ', $otags).')', array($prj->id, $user->id));
|
||||
$nb_submit = Pluf::factory('IDF_Issue')->getCount(array('filter'=>$sql->gen()));
|
||||
$sql = new Pluf_SQL('project=%s AND owner=%s AND status IN ('.implode(', ', $otags).')', array($prj->id, $request->user->id));
|
||||
$sql = new Pluf_SQL('project=%s AND owner=%s AND status IN ('.implode(', ', $otags).')', array($prj->id, $user->id));
|
||||
$nb_owner = Pluf::factory('IDF_Issue')->getCount(array('filter'=>$sql->gen()));
|
||||
// Closed issues
|
||||
$sql = new Pluf_SQL('project=%s AND submitter=%s AND status IN ('.implode(', ', $ctags).')', array($prj->id, $request->user->id));
|
||||
$sql = new Pluf_SQL('project=%s AND submitter=%s AND status IN ('.implode(', ', $ctags).')', array($prj->id, $user->id));
|
||||
$nb_submit_closed = Pluf::factory('IDF_Issue')->getCount(array('filter'=>$sql->gen()));
|
||||
$sql = new Pluf_SQL('project=%s AND owner=%s AND status IN ('.implode(', ', $ctags).')', array($prj->id, $request->user->id));
|
||||
$sql = new Pluf_SQL('project=%s AND owner=%s AND status IN ('.implode(', ', $ctags).')', array($prj->id, $user->id));
|
||||
$nb_owner_closed = Pluf::factory('IDF_Issue')->getCount(array('filter'=>$sql->gen()));
|
||||
|
||||
// Paginator to paginate the issues
|
||||
@@ -286,7 +412,7 @@ class IDF_Views_Issue
|
||||
'current_user' => $request->user);
|
||||
$pag->summary = __('This table shows the open issues.');
|
||||
$pag->forced_where = $f_sql;
|
||||
$pag->action = array('IDF_Views_Issue::myIssues', array($prj->shortname, $match[2]));
|
||||
$pag->action = array('IDF_Views_Issue::userIssues', array($prj->shortname, $match[2]));
|
||||
$pag->sort_order = array('modif_dtime', 'ASC'); // will be reverted
|
||||
$pag->sort_reverse_order = array('modif_dtime');
|
||||
$pag->sort_link_title = true;
|
||||
@@ -295,15 +421,17 @@ class IDF_Views_Issue
|
||||
'id' => __('Id'),
|
||||
array('summary', 'IDF_Views_Issue_SummaryAndLabels', __('Summary')),
|
||||
array('status', 'IDF_Views_Issue_ShowStatus', __('Status')),
|
||||
array('due_dtime', 'IDF_Views_Issue_DueDate', __('Due Date')),
|
||||
array('modif_dtime', 'Pluf_Paginator_DateAgo', __('Last Updated')),
|
||||
);
|
||||
$pag->configure($list_display, array(), array('id', 'status', 'modif_dtime'));
|
||||
$pag->configure($list_display, array(), array('id', 'status', 'due_dtime', 'modif_dtime'));
|
||||
$pag->items_per_page = 10;
|
||||
$pag->no_results_text = __('No issues were found.');
|
||||
$pag->setFromRequest($request);
|
||||
return Pluf_Shortcuts_RenderToResponse('idf/issues/my-issues.html',
|
||||
return Pluf_Shortcuts_RenderToResponse('idf/issues/userIssues.html',
|
||||
array('project' => $prj,
|
||||
'page_title' => $title,
|
||||
'login' => $user->login,
|
||||
'nb_submit' => $nb_submit,
|
||||
'nb_owner' => $nb_owner,
|
||||
'nb_submit_closed' => $nb_submit_closed,
|
||||
@@ -334,7 +462,7 @@ class IDF_Views_Issue
|
||||
array($prj->shortname, $issue->id));
|
||||
$issue->notify($request->conf);
|
||||
if ($api) return $issue;
|
||||
$request->user->setMessage(sprintf(__('<a href="%s">Issue %d</a> has been created.'), $url, $issue->id));
|
||||
$request->user->setMessage(sprintf(__('<a href="%1$s">Issue %2$d</a> has been created.'), $url, $issue->id));
|
||||
return new Pluf_HTTP_Response_Redirect($url);
|
||||
}
|
||||
} else {
|
||||
@@ -345,7 +473,9 @@ class IDF_Views_Issue
|
||||
'form' => $form,
|
||||
'page_title' => $title,
|
||||
'preview' => $preview,
|
||||
'issue' => new IDF_Issue(),
|
||||
),
|
||||
self::getDateShortcuts(),
|
||||
self::autoCompleteArrays($prj)
|
||||
);
|
||||
if ($api == true) return $params;
|
||||
@@ -355,45 +485,153 @@ class IDF_Views_Issue
|
||||
|
||||
public $search_precond = array('IDF_Precondition::accessIssues');
|
||||
public function search($request, $match)
|
||||
{
|
||||
$query = !isset($request->REQUEST['q']) ? '' : $request->REQUEST['q'];
|
||||
return $this->doSearch($request, $query, 'open');
|
||||
}
|
||||
|
||||
public $searchStatus_precond = array('IDF_Precondition::accessIssues');
|
||||
public function searchStatus($request, $match)
|
||||
{
|
||||
$query = !isset($request->REQUEST['q']) ? '' : $request->REQUEST['q'];
|
||||
$status = in_array($match[2], array('open', 'closed', 'overdue')) ? $match[2] : 'open';
|
||||
return $this->doSearch($request, $query, $status);
|
||||
}
|
||||
|
||||
public $searchLabel_precond = array('IDF_Precondition::accessIssues');
|
||||
public function searchLabel($request, $match)
|
||||
{
|
||||
$query = !isset($request->REQUEST['q']) ? '' : $request->REQUEST['q'];
|
||||
$tag_id = intval($match[2]);
|
||||
$status = in_array($match[3], array('open', 'closed', 'overdue')) ? $match[3] : 'open';
|
||||
return $this->doSearch($request, $query, $status, $tag_id);
|
||||
}
|
||||
|
||||
private function doSearch($request, $query, $status, $tag_id=null)
|
||||
{
|
||||
$prj = $request->project;
|
||||
if (!isset($request->REQUEST['q']) or trim($request->REQUEST['q']) == '') {
|
||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Issue::index',
|
||||
array($prj->shortname));
|
||||
if (trim($query) == '') {
|
||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Issue::index', array($prj->shortname));
|
||||
return new Pluf_HTTP_Response_Redirect($url);
|
||||
}
|
||||
$q = $request->REQUEST['q'];
|
||||
$title = sprintf(__('Search Issues - %s'), $q);
|
||||
$issues = new Pluf_Search_ResultSet(IDF_Search::mySearch($q, $prj, 'IDF_Issue'));
|
||||
if (count($issues) > 100) {
|
||||
// no more than 100 results as we do not care
|
||||
$issues->results = array_slice($issues->results, 0, 100);
|
||||
|
||||
$tag = null;
|
||||
if ($tag_id !== null) {
|
||||
$tag = Pluf_Shortcuts_GetObjectOr404('IDF_Tag', $tag_id);
|
||||
}
|
||||
|
||||
$title = sprintf(__('Search issues - %s'), $query);
|
||||
if ($status === 'closed') {
|
||||
$title = sprintf(__('Search closed issues - %s'), $query);
|
||||
} elseif ($status === 'overdue') {
|
||||
$title = sprintf(__('Search overdue issues - %s'), $query);
|
||||
$status = 'open';
|
||||
$overdue_status = true;
|
||||
}
|
||||
|
||||
// using Plufs ResultSet implementation here is inefficient, because
|
||||
// it makes a SELECT for each item and does not allow for further
|
||||
// filtering neither, so we just return the ids and filter by them
|
||||
// and other things in the next round
|
||||
$results = IDF_Search::mySearch($query, $prj, 'IDF_Issue');
|
||||
|
||||
$issue_ids = array(0);
|
||||
foreach ($results as $result) {
|
||||
$issue_ids[] = $result['model_id'];
|
||||
}
|
||||
|
||||
$otags = $prj->getTagIdsByStatus($status);
|
||||
if (count($otags) == 0) $otags[] = 0;
|
||||
$sql = new Pluf_SQL(
|
||||
'id IN ('.implode(',', $issue_ids).') '.
|
||||
'AND status IN ('.implode(', ', $otags).') '.
|
||||
($tag_id !== null ? 'AND idf_tag_id='.$tag_id.' ' : '')
|
||||
);
|
||||
if (isset($overdue_status)) {
|
||||
$sql2 = new Pluf_SQL('due_dtime < NOW()');
|
||||
$sql->SAnd($sql2);
|
||||
}
|
||||
$model = new IDF_Issue();
|
||||
$issues = $model->getList(array('filter' => $sql->gen(), 'view' => 'join_tags'));
|
||||
|
||||
// we unfortunately loose the original sort order,
|
||||
// so we manually have to apply it here again
|
||||
$sorted_issues = new ArrayObject();
|
||||
$filtered_issue_ids = array(0);
|
||||
foreach ($issue_ids as $issue_id) {
|
||||
foreach ($issues as $issue) {
|
||||
if ($issue->id != $issue_id)
|
||||
continue;
|
||||
if (array_key_exists($issue_id, $sorted_issues))
|
||||
continue;
|
||||
$sorted_issues[$issue_id] = $issue;
|
||||
$filtered_issue_ids[] = $issue_id;
|
||||
}
|
||||
}
|
||||
|
||||
$pag = new Pluf_Paginator();
|
||||
$pag->items = $issues;
|
||||
$pag->class = 'recent-issues';
|
||||
$pag->item_extra_props = array('project_m' => $prj,
|
||||
'shortname' => $prj->shortname,
|
||||
'current_user' => $request->user);
|
||||
$pag->items = $sorted_issues;
|
||||
$pag->item_extra_props = array(
|
||||
'project_m' => $prj,
|
||||
'shortname' => $prj->shortname,
|
||||
'current_user' => $request->user
|
||||
);
|
||||
$pag->summary = __('This table shows the found issues.');
|
||||
$pag->action = array('IDF_Views_Issue::search', array($prj->shortname), array('q'=> $q));
|
||||
$pag->extra_classes = array('a-c', '', 'a-c', '');
|
||||
$list_display = array(
|
||||
'id' => __('Id'),
|
||||
array('summary', 'IDF_Views_Issue_SummaryAndLabels', __('Summary')),
|
||||
array('status', 'IDF_Views_Issue_ShowStatus', __('Status')),
|
||||
array('modif_dtime', 'Pluf_Paginator_DateAgo', __('Last Updated')),
|
||||
);
|
||||
$pag->configure($list_display);
|
||||
$pag->items_per_page = 100;
|
||||
$pag->configure(array(
|
||||
'id' => __('Id'),
|
||||
array('summary', 'IDF_Views_Issue_SummaryAndLabels', __('Summary')),
|
||||
array('status', 'IDF_Views_Issue_ShowStatus', __('Status')),
|
||||
array('due_dtime', 'IDF_Views_Issue_DueDate', __('Due Date')),
|
||||
array('modif_dtime', 'Pluf_Paginator_DateAgo', __('Last Updated')),
|
||||
));
|
||||
// disable paginating
|
||||
$pag->items_per_page = PHP_INT_MAX;
|
||||
$pag->no_results_text = __('No issues were found.');
|
||||
$pag->setFromRequest($request);
|
||||
$params = array('page_title' => $title,
|
||||
'issues' => $pag,
|
||||
'q' => $q,
|
||||
);
|
||||
return Pluf_Shortcuts_RenderToResponse('idf/issues/search.html', $params, $request);
|
||||
|
||||
if ($tag_id === null) {
|
||||
$pag->action = array('IDF_Views_Issue::searchStatus',
|
||||
array($prj->shortname, $status),
|
||||
array('q'=> $query),
|
||||
);
|
||||
} else {
|
||||
$pag->action = array('IDF_Views_Issue::searchLabel',
|
||||
array($prj->shortname, $tag_id, $status),
|
||||
array('q'=> $query),
|
||||
);
|
||||
}
|
||||
|
||||
// get stats about the issues
|
||||
$overdue = $prj->getIssueCountByDueDate('overdue', $tag, $issue_ids);
|
||||
$open = $prj->getIssueCountByStatus('open', $tag, $issue_ids);
|
||||
$closed = $prj->getIssueCountByStatus('closed', $tag, $issue_ids);
|
||||
|
||||
// query the available tags for this search result
|
||||
$all_tags = $prj->getTagsByIssues($filtered_issue_ids);
|
||||
$grouped_tags = array();
|
||||
foreach ($all_tags as $atag) {
|
||||
// group by class
|
||||
if (!array_key_exists($atag->class, $grouped_tags)) {
|
||||
$grouped_tags[$atag->class] = array();
|
||||
}
|
||||
$grouped_tags[$atag->class][] = $atag;
|
||||
}
|
||||
|
||||
$params = array(
|
||||
'page_title' => $title,
|
||||
'issues' => $pag,
|
||||
'query' => $query,
|
||||
'status' => $status,
|
||||
'open' => $open,
|
||||
'closed' => $closed,
|
||||
'overdue' => $overdue,
|
||||
'tag' => $tag,
|
||||
'all_tags' => $grouped_tags,
|
||||
);
|
||||
|
||||
return Pluf_Shortcuts_RenderToResponse('idf/issues/search.html', $params, $request);
|
||||
}
|
||||
|
||||
public $view_precond = array('IDF_Precondition::accessIssues');
|
||||
@@ -403,9 +641,11 @@ class IDF_Views_Issue
|
||||
$issue = Pluf_Shortcuts_GetObjectOr404('IDF_Issue', $match[2]);
|
||||
$prj->inOr404($issue);
|
||||
$comments = $issue->get_comments_list(array('order' => 'id ASC'));
|
||||
$related_issues = $issue->getGroupedRelatedIssues(array('order' => 'other_issue ASC'));
|
||||
|
||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Issue::view',
|
||||
array($prj->shortname, $issue->id));
|
||||
$title = Pluf_Template::markSafe(sprintf(__('Issue <a href="%s">%d</a>: %s'), $url, $issue->id, $issue->summary));
|
||||
$title = Pluf_Template::markSafe(sprintf(__('Issue <a href="%1$s">%2$d</a>: %3$s'), $url, $issue->id, Pluf_esc($issue->summary)));
|
||||
$form = false; // The form is available only if logged in.
|
||||
$starred = false;
|
||||
$closed = in_array($issue->status, $prj->getTagIdsByStatus('closed'));
|
||||
@@ -429,7 +669,7 @@ class IDF_Views_Issue
|
||||
$issue->notify($request->conf, false);
|
||||
$comments = $issue->get_comments_list(array('order' => 'id DESC'));
|
||||
$url .= '#ic' . $comments[0]->id;
|
||||
$request->user->setMessage(sprintf(__('<a href="%s">Issue %d</a> has been updated.'), $url, $issue->id));
|
||||
$request->user->setMessage(sprintf(__('<a href="%1$s">Issue %2$d</a> has been updated.'), $url, $issue->id));
|
||||
return new Pluf_HTTP_Response_Redirect($url);
|
||||
}
|
||||
} else {
|
||||
@@ -470,8 +710,10 @@ class IDF_Views_Issue
|
||||
'preview' => $preview,
|
||||
'interested' => $interested->count(),
|
||||
'previous_issue_id' => $previous_issue_id,
|
||||
'next_issue_id' => $next_issue_id
|
||||
'next_issue_id' => $next_issue_id,
|
||||
'related_issues' => $related_issues,
|
||||
),
|
||||
self::getDateShortcuts(),
|
||||
$arrays),
|
||||
$request);
|
||||
}
|
||||
@@ -538,10 +780,18 @@ class IDF_Views_Issue
|
||||
{
|
||||
$prj = $request->project;
|
||||
$status = $match[2];
|
||||
|
||||
if (mb_strtolower($status) == 'open') {
|
||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Issue::index',
|
||||
array($prj->shortname));
|
||||
return new Pluf_HTTP_Response_Redirect($url);
|
||||
}
|
||||
|
||||
$title = sprintf(__('%s Closed Issues'), (string) $prj);
|
||||
// Get stats about the issues
|
||||
$open = $prj->getIssueCountByStatus('open');
|
||||
$closed = $prj->getIssueCountByStatus('closed');
|
||||
$overdue = $prj->getIssueCountByDueDate();
|
||||
// Paginator to paginate the issues
|
||||
$pag = new Pluf_Paginator(new IDF_Issue());
|
||||
$pag->class = 'recent-issues';
|
||||
@@ -561,9 +811,10 @@ class IDF_Views_Issue
|
||||
'id' => __('Id'),
|
||||
array('summary', 'IDF_Views_Issue_SummaryAndLabels', __('Summary')),
|
||||
array('status', 'IDF_Views_Issue_ShowStatus', __('Status')),
|
||||
array('due_dtime', 'IDF_Views_Issue_DueDate', __('Due Date')),
|
||||
array('modif_dtime', 'Pluf_Paginator_DateAgo', __('Last Updated')),
|
||||
);
|
||||
$pag->configure($list_display, array(), array('id', 'status', 'modif_dtime'));
|
||||
$pag->configure($list_display, array(), array('id', 'status', 'due_dtime', 'modif_dtime'));
|
||||
$pag->items_per_page = 10;
|
||||
$pag->no_results_text = __('No issues were found.');
|
||||
$pag->setFromRequest($request);
|
||||
@@ -572,6 +823,59 @@ class IDF_Views_Issue
|
||||
'page_title' => $title,
|
||||
'open' => $open,
|
||||
'closed' => $closed,
|
||||
'overdue' => $overdue,
|
||||
'issues' => $pag,
|
||||
'cloud' => 'closed_issues',
|
||||
),
|
||||
$request);
|
||||
}
|
||||
|
||||
/**
|
||||
* View list of issues for a given project with a given status.
|
||||
*/
|
||||
public $listOverdue_precond = array('IDF_Precondition::accessIssues');
|
||||
public function listOverdue($request, $match)
|
||||
{
|
||||
$prj = $request->project;
|
||||
$status = __('Overdue');
|
||||
|
||||
$title = sprintf(__('%s %s Issues'), (string) $prj, (string) $status);
|
||||
// Get stats about the issues
|
||||
$open = $prj->getIssueCountByStatus('open');
|
||||
$closed = $prj->getIssueCountByStatus('closed');
|
||||
$overdue = $prj->getIssueCountByDueDate();
|
||||
// Paginator to paginate the issues
|
||||
$pag = new Pluf_Paginator(new IDF_Issue());
|
||||
$pag->class = 'recent-issues';
|
||||
$pag->item_extra_props = array('project_m' => $prj,
|
||||
'shortname' => $prj->shortname,
|
||||
'current_user' => $request->user);
|
||||
$pag->summary = __('This table shows the overdue issues.');
|
||||
$otags = $prj->getTagIdsByStatus('open');
|
||||
if (count($otags) == 0) $otags[] = 0;
|
||||
$pag->forced_where = new Pluf_SQL('project=%s AND due_dtime < NOW() AND status IN ('.implode(', ', $otags).')', array($prj->id));
|
||||
$pag->action = array('IDF_Views_Issue::listOverdue', array($prj->shortname, $status));
|
||||
$pag->sort_order = array('due_dtime', 'DESC'); // will be reverted
|
||||
$pag->sort_reverse_order = array('due_dtime');
|
||||
$pag->sort_link_title = true;
|
||||
$pag->extra_classes = array('a-c', '', 'a-c', '');
|
||||
$list_display = array(
|
||||
'id' => __('Id'),
|
||||
array('summary', 'IDF_Views_Issue_SummaryAndLabels', __('Summary')),
|
||||
array('status', 'IDF_Views_Issue_ShowStatus', __('Status')),
|
||||
array('due_dtime', 'IDF_Views_Issue_DueDate', __('Due Date')),
|
||||
array('modif_dtime', 'Pluf_Paginator_DateAgo', __('Last Updated')),
|
||||
);
|
||||
$pag->configure($list_display, array(), array('id', 'status', 'due_dtime', 'modif_dtime'));
|
||||
$pag->items_per_page = 10;
|
||||
$pag->no_results_text = __('No issues were found.');
|
||||
$pag->setFromRequest($request);
|
||||
return Pluf_Shortcuts_RenderToResponse('idf/issues/index.html',
|
||||
array('project' => $prj,
|
||||
'page_title' => $title,
|
||||
'open' => $open,
|
||||
'closed' => $closed,
|
||||
'overdue' => $overdue,
|
||||
'issues' => $pag,
|
||||
'cloud' => 'closed_issues',
|
||||
),
|
||||
@@ -587,12 +891,17 @@ class IDF_Views_Issue
|
||||
$prj = $request->project;
|
||||
$tag = Pluf_Shortcuts_GetObjectOr404('IDF_Tag', $match[2]);
|
||||
$status = $match[3];
|
||||
if ($tag->project != $prj->id or !in_array($status, array('open', 'closed'))) {
|
||||
if ($tag->project != $prj->id or !in_array($status, array('open', 'closed', 'overdue'))) {
|
||||
throw new Pluf_HTTP_Error404();
|
||||
}
|
||||
if ($status == 'open') {
|
||||
$title = sprintf(__('%1$s Issues with Label %2$s'), (string) $prj,
|
||||
(string) $tag);
|
||||
} elseif ($status == 'overdue') {
|
||||
$title = sprintf(__('%1$s Overdue Issues with Label %2$s'), (string) $prj,
|
||||
(string) $tag);
|
||||
$overdue_status = true;
|
||||
$status = 'open';
|
||||
} else {
|
||||
$title = sprintf(__('%1$s Closed Issues with Label %2$s'),
|
||||
(string) $prj, (string) $tag);
|
||||
@@ -600,6 +909,7 @@ class IDF_Views_Issue
|
||||
// Get stats about the open/closed issues having this tag.
|
||||
$open = $prj->getIssueCountByStatus('open', $tag);
|
||||
$closed = $prj->getIssueCountByStatus('closed', $tag);
|
||||
$overdue = $prj->getIssueCountByDueDate('overdue', $tag);
|
||||
// Paginator to paginate the issues
|
||||
$pag = new Pluf_Paginator(new IDF_Issue());
|
||||
$pag->model_view = 'join_tags';
|
||||
@@ -611,6 +921,9 @@ class IDF_Views_Issue
|
||||
$otags = $prj->getTagIdsByStatus($status);
|
||||
if (count($otags) == 0) $otags[] = 0;
|
||||
$pag->forced_where = new Pluf_SQL('project=%s AND idf_tag_id=%s AND status IN ('.implode(', ', $otags).')', array($prj->id, $tag->id));
|
||||
if (isset($overdue_status)) {
|
||||
$pag->forced_where->SAnd(new Pluf_SQL('due_dtime < NOW()'));
|
||||
}
|
||||
$pag->action = array('IDF_Views_Issue::listLabel', array($prj->shortname, $tag->id, $status));
|
||||
$pag->sort_order = array('modif_dtime', 'ASC'); // will be reverted
|
||||
$pag->sort_reverse_order = array('modif_dtime');
|
||||
@@ -620,9 +933,10 @@ class IDF_Views_Issue
|
||||
'id' => __('Id'),
|
||||
array('summary', 'IDF_Views_Issue_SummaryAndLabels', __('Summary')),
|
||||
array('status', 'IDF_Views_Issue_ShowStatus', __('Status')),
|
||||
array('due_dtime', 'IDF_Views_Issue_DueDate', __('Due Date')),
|
||||
array('modif_dtime', 'Pluf_Paginator_DateAgo', __('Last Updated')),
|
||||
);
|
||||
$pag->configure($list_display, array(), array('id', 'status', 'modif_dtime'));
|
||||
$pag->configure($list_display, array(), array('id', 'status', 'due_dtime', 'modif_dtime'));
|
||||
$pag->items_per_page = 10;
|
||||
$pag->no_results_text = __('No issues were found.');
|
||||
$pag->setFromRequest($request);
|
||||
@@ -638,6 +952,7 @@ class IDF_Views_Issue
|
||||
'open' => $open,
|
||||
'label' => $tag,
|
||||
'closed' => $closed,
|
||||
'overdue' => $overdue,
|
||||
'issues' => $pag,
|
||||
),
|
||||
$request);
|
||||
@@ -651,22 +966,66 @@ class IDF_Views_Issue
|
||||
public function autoCompleteIssueList($request, $match)
|
||||
{
|
||||
$prj = $request->project;
|
||||
$issue_id = !empty($match[2]) ? intval($match[2]) : 0;
|
||||
$query = trim($request->REQUEST['q']);
|
||||
$limit = !empty($request->REQUEST['limit']) ? intval($request->REQUEST['limit']) : 0;
|
||||
$limit = max(10, $limit);
|
||||
|
||||
$issues = array();
|
||||
|
||||
// empty search, return the most recently updated issues
|
||||
if (empty($query)) {
|
||||
$sql = new Pluf_SQL('project=%s', array($prj->id));
|
||||
$tmp = Pluf::factory('IDF_Issue')->getList(array(
|
||||
'filter' => $sql->gen(),
|
||||
'order' => 'modif_dtime DESC'
|
||||
));
|
||||
$issues += $tmp->getArrayCopy();
|
||||
}
|
||||
else {
|
||||
// ID-based search
|
||||
if (is_numeric($query)) {
|
||||
$sql = 'project=%s AND CAST(id AS VARCHAR) LIKE %s';
|
||||
// MySQL can't cast to VARCHAR and a CAST to CHAR converts
|
||||
// the whole number, not just the first digit
|
||||
if (strtolower(Pluf::f('db_engine')) == 'mysql') {
|
||||
$sql = 'project=%s AND CAST(id AS CHAR) LIKE %s';
|
||||
}
|
||||
$sql = new Pluf_SQL($sql, array($prj->id, $query.'%'));
|
||||
$tmp = Pluf::factory('IDF_Issue')->getList(array(
|
||||
'filter' => $sql->gen(),
|
||||
'order' => 'id ASC'
|
||||
));
|
||||
$issues += $tmp->getArrayCopy();
|
||||
}
|
||||
|
||||
// text-based search
|
||||
$res = new Pluf_Search_ResultSet(
|
||||
IDF_Search::mySearch($query, $prj, 'IDF_Issue')
|
||||
);
|
||||
foreach ($res as $issue)
|
||||
$issues[] = $issue;
|
||||
}
|
||||
|
||||
// Autocomplete from jQuery UI works with JSON, this old one still
|
||||
// expects a parsable string; since we'd need to bump jQuery beyond
|
||||
// 1.2.6 for this to use as well, we're trying to cope with the old format.
|
||||
// see http://www.learningjquery.com/2010/06/autocomplete-migration-guide
|
||||
|
||||
$arr = array(
|
||||
'Fo|o' => 110,
|
||||
'Bar' => 111,
|
||||
'Baz' => 112,
|
||||
);
|
||||
|
||||
$out = '';
|
||||
foreach ($arr as $key => $val)
|
||||
$ids = array();
|
||||
foreach ($issues as $issue)
|
||||
{
|
||||
$out .= str_replace('|', '|', $key).'|'.$val."\n";
|
||||
if ($issue->id == $issue_id)
|
||||
continue;
|
||||
|
||||
if (in_array($issue->id, $ids))
|
||||
continue;
|
||||
|
||||
if (--$limit < 0)
|
||||
break;
|
||||
|
||||
$out .= str_replace('|', '|', $issue->summary) .'|'.$issue->id."\n";
|
||||
$ids[] = $issue->id;
|
||||
}
|
||||
|
||||
return new Pluf_HTTP_Response($out);
|
||||
@@ -748,13 +1107,34 @@ class IDF_Views_Issue
|
||||
$r = $project->getRelationsFromConfig();
|
||||
$auto['auto_relation_types'] = '';
|
||||
foreach ($r as $rt) {
|
||||
$esc = Pluf_esc($rt);
|
||||
$auto['auto_relation_types'] .= sprintf('{ name: "%s", to: "%s" }, ',
|
||||
$esc, $esc);
|
||||
Pluf_esc(__($rt)), Pluf_esc($rt));
|
||||
}
|
||||
$auto['auto_relation_types'] = substr($auto['auto_relation_types'], 0, -2);
|
||||
return $auto;
|
||||
}
|
||||
|
||||
public static function getDateShortcuts()
|
||||
{
|
||||
return array(
|
||||
'date_shortcuts' => array(
|
||||
__('Soon') => array(
|
||||
__('Today') => date('Y-m-d'),
|
||||
__('Tomorrow') => date('Y-m-d', strtotime('tomorrow')),
|
||||
__('2 Days') => date('Y-m-d', strtotime('+2 days')),
|
||||
),
|
||||
__('Midterm') => array(
|
||||
__('Friday') => date('Y-m-d', strtotime('next friday')),
|
||||
__('1 Week') => date('Y-m-d', strtotime('+1 week')),
|
||||
__('A Fortnight') => date('Y-m-d', strtotime('+2 week')),
|
||||
),
|
||||
__('Longterm') => array(
|
||||
__('End of the Month') => date('Y-m-d', strtotime('last day of this month')),
|
||||
__('1 Month') => date('Y-m-d', strtotime('+1 month')),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -768,6 +1148,16 @@ function IDF_Views_Issue_SummaryAndLabelsUnknownProject($field, $issue, $extra='
|
||||
return IDF_Views_Issue_SummaryAndLabels ($field, $issue, $extra);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the date value for the Due Date table column
|
||||
*/
|
||||
function IDF_Views_Issue_DueDate($field, $issue, $extra='')
|
||||
{
|
||||
if($issue->$field) {
|
||||
return Pluf_Template_dateFormat($issue->$field);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the summary of an issue, then on a new line, display the
|
||||
* list of labels with a link to a view "by label only".
|
||||
@@ -790,8 +1180,12 @@ function IDF_Views_Issue_SummaryAndLabels($field, $issue, $extra='')
|
||||
$s = '<img style="vertical-align: text-bottom;" src="'.Pluf_Template_Tag_MediaUrl::url('/idf/img/star.png').'" alt="'.__('On your watch list.').'" /> ';
|
||||
}
|
||||
$out = '';
|
||||
if ('' != $issue->due_dtime and (time() >= strtotime($issue->due_dtime)) and
|
||||
in_array($issue->status, $issue->get_project()->getTagIdsByStatus('open'))) {
|
||||
$out = ' <span class="overdue">' . __('overdue') . '</span>';
|
||||
}
|
||||
if (count($tags)) {
|
||||
$out = '<br /><span class="note">'.implode(', ', $tags).'</span>';
|
||||
$out .= '<br /><span class="note">'.implode(', ', $tags).'</span>';
|
||||
}
|
||||
return $s.sprintf('<a href="%s">%s</a>', $edit, Pluf_esc($issue->summary)).$out;
|
||||
}
|
||||
|
@@ -68,7 +68,7 @@ class IDF_Views_Project
|
||||
$pages = array();
|
||||
if ($request->rights['hasWikiAccess']) {
|
||||
$tags = IDF_Views_Wiki::getWikiTags($prj);
|
||||
$pages = $tags[0]->get_idf_wikipage_list();
|
||||
$pages = $tags[0]->get_idf_wiki_page_list();
|
||||
}
|
||||
return Pluf_Shortcuts_RenderToResponse('idf/project/home.html',
|
||||
array(
|
||||
@@ -131,8 +131,10 @@ class IDF_Views_Project
|
||||
}
|
||||
if (true === IDF_Precondition::accessWiki($request) &&
|
||||
($model_filter == 'all' || $model_filter == 'documents')) {
|
||||
$classes[] = '\'IDF_WikiPage\'';
|
||||
$classes[] = '\'IDF_WikiRevision\'';
|
||||
$classes[] = '\'IDF_Wiki_Page\'';
|
||||
$classes[] = '\'IDF_Wiki_PageRevision\'';
|
||||
$classes[] = '\'IDF_Wiki_Resource\'';
|
||||
$classes[] = '\'IDF_Wiki_ResourceRevision\'';
|
||||
}
|
||||
if (true === IDF_Precondition::accessReview($request) &&
|
||||
($model_filter == 'all' || $model_filter == 'reviews')) {
|
||||
@@ -305,17 +307,21 @@ class IDF_Views_Project
|
||||
return new Pluf_HTTP_Response_Redirect($url);
|
||||
}
|
||||
} else {
|
||||
$form = new IDF_Form_ProjectConf($prj->getData(), $extra);
|
||||
$form = new IDF_Form_ProjectConf(null, $extra);
|
||||
}
|
||||
|
||||
$logo = $prj->getConf()->getVal('logo');
|
||||
$arrays = self::autoCompleteArrays();
|
||||
return Pluf_Shortcuts_RenderToResponse('idf/admin/summary.html',
|
||||
array(
|
||||
'page_title' => $title,
|
||||
'form' => $form,
|
||||
'project' => $prj,
|
||||
'logo' => $logo,
|
||||
),
|
||||
array_merge(
|
||||
array(
|
||||
'page_title' => $title,
|
||||
'form' => $form,
|
||||
'project' => $prj,
|
||||
'logo' => $logo,
|
||||
),
|
||||
$arrays
|
||||
),
|
||||
$request);
|
||||
}
|
||||
|
||||
@@ -375,8 +381,11 @@ class IDF_Views_Project
|
||||
$title = sprintf(__('%s Downloads Configuration'), (string) $prj);
|
||||
$conf = new IDF_Conf();
|
||||
$conf->setProject($prj);
|
||||
$extra = array(
|
||||
'conf' => $conf,
|
||||
);
|
||||
if ($request->method == 'POST') {
|
||||
$form = new IDF_Form_UploadConf($request->POST);
|
||||
$form = new IDF_Form_UploadConf($request->POST, $extra);
|
||||
if ($form->isValid()) {
|
||||
foreach ($form->cleaned_data as $key=>$val) {
|
||||
$conf->setVal($key, $val);
|
||||
@@ -388,7 +397,7 @@ class IDF_Views_Project
|
||||
}
|
||||
} else {
|
||||
$params = array();
|
||||
$keys = array('labels_download_predefined', 'labels_download_one_max');
|
||||
$keys = array('labels_download_predefined', 'labels_download_one_max', 'upload_webhook_url');
|
||||
foreach ($keys as $key) {
|
||||
$_val = $conf->getVal($key, false);
|
||||
if ($_val !== false) {
|
||||
@@ -398,12 +407,13 @@ class IDF_Views_Project
|
||||
if (count($params) == 0) {
|
||||
$params = null; //Nothing in the db, so new form.
|
||||
}
|
||||
$form = new IDF_Form_UploadConf($params);
|
||||
$form = new IDF_Form_UploadConf($params, $extra);
|
||||
}
|
||||
return Pluf_Shortcuts_RenderToResponse('idf/admin/downloads.html',
|
||||
array(
|
||||
'page_title' => $title,
|
||||
'form' => $form,
|
||||
'hookkey' => $prj->getWebHookKey(),
|
||||
),
|
||||
$request);
|
||||
}
|
||||
@@ -504,21 +514,24 @@ class IDF_Views_Project
|
||||
}
|
||||
}
|
||||
$form->save(); // Save the authorized users.
|
||||
$request->user->setMessage(__('The project tabs access rights have been saved.'));
|
||||
$request->user->setMessage(__('The project tabs access rights and notification settings have been saved.'));
|
||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Project::adminTabs',
|
||||
array($prj->shortname));
|
||||
return new Pluf_HTTP_Response_Redirect($url);
|
||||
}
|
||||
} else {
|
||||
$params = array();
|
||||
$keys = array('downloads_access_rights', 'source_access_rights',
|
||||
'issues_access_rights', 'review_access_rights',
|
||||
'wiki_access_rights',
|
||||
'downloads_notification_email',
|
||||
'review_notification_email',
|
||||
'wiki_notification_email',
|
||||
'source_notification_email',
|
||||
'issues_notification_email');
|
||||
$sections = array('downloads', 'wiki', 'source', 'issues', 'review');
|
||||
$keys = array();
|
||||
|
||||
foreach ($sections as $section) {
|
||||
$keys[] = $section.'_access_rights';
|
||||
$keys[] = $section.'_notification_owners_enabled';
|
||||
$keys[] = $section.'_notification_members_enabled';
|
||||
$keys[] = $section.'_notification_email_enabled';
|
||||
$keys[] = $section.'_notification_email';
|
||||
}
|
||||
|
||||
foreach ($keys as $key) {
|
||||
$_val = $request->conf->getVal($key, false);
|
||||
if ($_val !== false) {
|
||||
@@ -590,6 +603,10 @@ class IDF_Views_Project
|
||||
'mtn' => __('monotone'),
|
||||
);
|
||||
$repository_type = $options[$scm];
|
||||
$hook_request_method = 'PUT';
|
||||
if (Pluf::f('webhook_processing','') === 'compat') {
|
||||
$hook_request_method = 'POST';
|
||||
}
|
||||
return Pluf_Shortcuts_RenderToResponse('idf/admin/source.html',
|
||||
array(
|
||||
'remote_svn' => $remote_svn,
|
||||
@@ -598,8 +615,41 @@ class IDF_Views_Project
|
||||
'repository_size' => $prj->getRepositorySize(),
|
||||
'page_title' => $title,
|
||||
'form' => $form,
|
||||
'hookkey' => $prj->getPostCommitHookKey(),
|
||||
'hookkey' => $prj->getWebHookKey(),
|
||||
'hook_request_method' => $hook_request_method,
|
||||
),
|
||||
$request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the autocomplete arrays for the little AJAX stuff.
|
||||
*/
|
||||
public static function autoCompleteArrays()
|
||||
{
|
||||
$forge = IDF_Forge::instance();
|
||||
$labels = $forge->getProjectLabels(IDF_Form_Admin_LabelConf::init_project_labels);
|
||||
|
||||
$auto = array('auto_labels' => '');
|
||||
$auto_raw = array('auto_labels' => $labels);
|
||||
foreach ($auto_raw as $key => $st) {
|
||||
$st = preg_split("/\015\012|\015|\012/", $st, -1, PREG_SPLIT_NO_EMPTY);
|
||||
foreach ($st as $s) {
|
||||
$v = '';
|
||||
$d = '';
|
||||
$_s = explode('=', $s, 2);
|
||||
if (count($_s) > 1) {
|
||||
$v = trim($_s[0]);
|
||||
$d = trim($_s[1]);
|
||||
} else {
|
||||
$v = trim($_s[0]);
|
||||
}
|
||||
$auto[$key] .= sprintf('{ name: "%s", to: "%s" }, ',
|
||||
Pluf_esc($d),
|
||||
Pluf_esc($v));
|
||||
}
|
||||
$auto[$key] = substr($auto[$key], 0, -2);
|
||||
}
|
||||
|
||||
return $auto;
|
||||
}
|
||||
}
|
||||
|
@@ -91,7 +91,7 @@ class IDF_Views_Review
|
||||
$review = $form->save();
|
||||
$urlr = Pluf_HTTP_URL_urlForView('IDF_Views_Review::view',
|
||||
array($prj->shortname, $review->id));
|
||||
$request->user->setMessage(sprintf(__('The <a href="%s">code review %d</a> has been created.'), $urlr, $review->id));
|
||||
$request->user->setMessage(sprintf(__('The <a href="%1$s">code review %2$d</a> has been created.'), $urlr, $review->id));
|
||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Review::index',
|
||||
array($prj->shortname));
|
||||
return new Pluf_HTTP_Response_Redirect($url);
|
||||
@@ -137,7 +137,7 @@ class IDF_Views_Review
|
||||
$prj->inOr404($review);
|
||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Review::view',
|
||||
array($prj->shortname, $review->id));
|
||||
$title = Pluf_Template::markSafe(sprintf(__('Review <a href="%s">%d</a>: %s'), $url, $review->id, $review->summary));
|
||||
$title = Pluf_Template::markSafe(sprintf(__('Review <a href="%1$s">%2$d</a>: %3$s'), $url, $review->id, Pluf_esc($review->summary)));
|
||||
|
||||
$patches = $review->get_patches_list();
|
||||
$patch = $patches[0];
|
||||
@@ -157,7 +157,7 @@ class IDF_Views_Review
|
||||
$review = $patch->get_review();
|
||||
$urlr = Pluf_HTTP_URL_urlForView('IDF_Views_Review::view',
|
||||
array($prj->shortname, $review->id));
|
||||
$request->user->setMessage(sprintf(__('Your <a href="%s">code review %d</a> has been published.'), $urlr, $review->id));
|
||||
$request->user->setMessage(sprintf(__('Your <a href="%1$s">code review %2$d</a> has been published.'), $urlr, $review->id));
|
||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Review::index',
|
||||
array($prj->shortname));
|
||||
$review_comment->notify($request->conf);
|
||||
|
@@ -66,7 +66,8 @@ class IDF_Views_Source
|
||||
'commit' => $commit,
|
||||
'branches' => $branches,
|
||||
);
|
||||
return Pluf_Shortcuts_RenderToResponse('idf/source/invalid_revision.html',
|
||||
$scmConf = $request->conf->getVal('scm', 'git');
|
||||
return Pluf_Shortcuts_RenderToResponse('idf/source/'.$scmConf.'/invalid_revision.html',
|
||||
$params, $request);
|
||||
}
|
||||
|
||||
@@ -131,6 +132,12 @@ class IDF_Views_Source
|
||||
$request);
|
||||
}
|
||||
|
||||
public function repository($request, $match)
|
||||
{
|
||||
$scm = IDF_Scm::get($request->project);
|
||||
return $scm->repository($request, $match);
|
||||
}
|
||||
|
||||
public $treeBase_precond = array('IDF_Precondition::accessSource',
|
||||
'IDF_Views_Source_Precondition::scmAvailable',
|
||||
'IDF_Views_Source_Precondition::revisionValid');
|
||||
@@ -301,18 +308,13 @@ class IDF_Views_Source
|
||||
throw new Exception('could not retrieve commit object for '. $commit);
|
||||
}
|
||||
$title = sprintf(__('%s Commit Details'), (string) $request->project);
|
||||
$page_title = sprintf(__('%s Commit Details - %s'), (string) $request->project, $commit);
|
||||
$page_title = sprintf(__('%1$s Commit Details - %2$s'), (string) $request->project, $commit);
|
||||
$rcommit = IDF_Commit::getOrAdd($cobject, $request->project);
|
||||
$diff = new IDF_Diff($cobject->diff, $scm->getDiffPathStripLevel());
|
||||
$cobject->diff = null;
|
||||
$diff->parse();
|
||||
$scmConf = $request->conf->getVal('scm', 'git');
|
||||
try {
|
||||
$changes = $scm->getChanges($commit);
|
||||
} catch (Exception $e) {
|
||||
// getChanges is not yes supported by this backend.
|
||||
$changes = array();
|
||||
}
|
||||
$changes = $scm->getChanges($commit);
|
||||
$branches = $scm->getBranches();
|
||||
$in_branches = $scm->inBranches($cobject->commit, '');
|
||||
$tags = $scm->getTags();
|
||||
@@ -510,12 +512,12 @@ function IDF_Views_Source_PrettySizeSimple($size)
|
||||
function IDF_Views_Source_ShortenString($string, $length)
|
||||
{
|
||||
$ellipse = "...";
|
||||
$length = max(strlen($ellipse) + 2, $length);
|
||||
$length = max(mb_strlen($ellipse) + 2, $length);
|
||||
$preflen = ceil($length / 10);
|
||||
|
||||
if (mb_strlen($string) < $length)
|
||||
return $string;
|
||||
|
||||
return substr($string, 0, $preflen).$ellipse.
|
||||
substr($string, -($length - $preflen - mb_strlen($ellipse)));
|
||||
return mb_substr($string, 0, $preflen).$ellipse.
|
||||
mb_substr($string, -($length - $preflen - mb_strlen($ellipse)));
|
||||
}
|
||||
|
@@ -32,22 +32,22 @@ Pluf::loadFunction('Pluf_Shortcuts_GetFormForModel');
|
||||
class IDF_Views_Wiki
|
||||
{
|
||||
/**
|
||||
* View list of issues for a given project.
|
||||
* View list of pages for a given project.
|
||||
*/
|
||||
public $index_precond = array('IDF_Precondition::accessWiki');
|
||||
public function index($request, $match, $api=false)
|
||||
public $listPages_precond = array('IDF_Precondition::accessWiki');
|
||||
public function listPages($request, $match, $api=false)
|
||||
{
|
||||
$prj = $request->project;
|
||||
$title = sprintf(__('%s Documentation'), (string) $prj);
|
||||
// Paginator to paginate the pages
|
||||
$pag = new Pluf_Paginator(new IDF_WikiPage());
|
||||
$pag = new Pluf_Paginator(new IDF_Wiki_Page());
|
||||
$pag->class = 'recent-issues';
|
||||
$pag->item_extra_props = array('project_m' => $prj,
|
||||
'shortname' => $prj->shortname,
|
||||
'current_user' => $request->user);
|
||||
$pag->summary = __('This table shows the documentation pages.');
|
||||
$pag->action = array('IDF_Views_Wiki::index', array($prj->shortname));
|
||||
$pag->edit_action = array('IDF_Views_Wiki::view', 'shortname', 'title');
|
||||
$pag->action = array('IDF_Views_Wiki::listPages', array($prj->shortname));
|
||||
$pag->edit_action = array('IDF_Views_Wiki::viewPage', 'shortname', 'title');
|
||||
$sql = 'project=%s';
|
||||
$ptags = self::getWikiTags($prj);
|
||||
$dtag = array_pop($ptags); // The last tag is the deprecated tag.
|
||||
@@ -67,7 +67,7 @@ class IDF_Views_Wiki
|
||||
$pag->no_results_text = __('No documentation pages were found.');
|
||||
$pag->sort_order = array('title', 'ASC');
|
||||
$pag->setFromRequest($request);
|
||||
return Pluf_Shortcuts_RenderToResponse('idf/wiki/index.html',
|
||||
return Pluf_Shortcuts_RenderToResponse('idf/wiki/listPages.html',
|
||||
array(
|
||||
'page_title' => $title,
|
||||
'pages' => $pag,
|
||||
@@ -77,18 +77,56 @@ class IDF_Views_Wiki
|
||||
$request);
|
||||
}
|
||||
|
||||
/**
|
||||
* View list of resources for a given project.
|
||||
*/
|
||||
public $listResources_precond = array('IDF_Precondition::accessWiki',
|
||||
'Pluf_Precondition::loginRequired');
|
||||
public function listResources($request, $match)
|
||||
{
|
||||
$prj = $request->project;
|
||||
$title = sprintf(__('%s Documentation Resources'), (string) $prj);
|
||||
$pag = new Pluf_Paginator(new IDF_Wiki_Resource());
|
||||
$pag->class = 'recent-issues';
|
||||
$pag->item_extra_props = array('project_m' => $prj,
|
||||
'shortname' => $prj->shortname,
|
||||
'current_user' => $request->user);
|
||||
$pag->summary = __('This table shows the resources that can be used on documentation pages.');
|
||||
$pag->action = array('IDF_Views_Wiki::listResources', array($prj->shortname));
|
||||
$pag->edit_action = array('IDF_Views_Wiki::viewResource', 'shortname', 'title');
|
||||
$pag->forced_where = new Pluf_SQL('project=%s', array($prj->id));
|
||||
$pag->extra_classes = array('right', 'a-c', 'left', 'a-c');
|
||||
$list_display = array(
|
||||
'title' => __('Resource Title'),
|
||||
'mime_type' => __('MIME type'),
|
||||
'summary' => __('Description'),
|
||||
array('modif_dtime', 'Pluf_Paginator_DateYMD', __('Updated')),
|
||||
);
|
||||
$pag->configure($list_display, array(), array('title', 'modif_dtime'));
|
||||
$pag->items_per_page = 25;
|
||||
$pag->no_results_text = __('No resources were found.');
|
||||
$pag->sort_order = array('title', 'ASC');
|
||||
$pag->setFromRequest($request);
|
||||
return Pluf_Shortcuts_RenderToResponse('idf/wiki/listResources.html',
|
||||
array(
|
||||
'page_title' => $title,
|
||||
'resources' => $pag,
|
||||
),
|
||||
$request);
|
||||
}
|
||||
|
||||
public $search_precond = array('IDF_Precondition::accessWiki',);
|
||||
public function search($request, $match)
|
||||
{
|
||||
$prj = $request->project;
|
||||
if (!isset($request->REQUEST['q']) or trim($request->REQUEST['q']) == '') {
|
||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::index',
|
||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::listPages',
|
||||
array($prj->shortname));
|
||||
return new Pluf_HTTP_Response_Redirect($url);
|
||||
}
|
||||
$q = $request->REQUEST['q'];
|
||||
$title = sprintf(__('Documentation Search - %s'), $q);
|
||||
$pages = new Pluf_Search_ResultSet(IDF_Search::mySearch($q, $prj, 'IDF_WikiPage'));
|
||||
$pages = new Pluf_Search_ResultSet(IDF_Search::mySearch($q, $prj, 'IDF_Wiki_Page'));
|
||||
if (count($pages) > 100) {
|
||||
$pages->results = array_slice($pages->results, 0, 100);
|
||||
}
|
||||
@@ -100,7 +138,7 @@ class IDF_Views_Wiki
|
||||
'current_user' => $request->user);
|
||||
$pag->summary = __('This table shows the pages found.');
|
||||
$pag->action = array('IDF_Views_Wiki::search', array($prj->shortname), array('q'=> $q));
|
||||
$pag->edit_action = array('IDF_Views_Wiki::view', 'shortname', 'title');
|
||||
$pag->edit_action = array('IDF_Views_Wiki::viewPage', 'shortname', 'title');
|
||||
$pag->extra_classes = array('right', '', 'a-c');
|
||||
$list_display = array(
|
||||
'title' => __('Page Title'),
|
||||
@@ -122,8 +160,8 @@ class IDF_Views_Wiki
|
||||
/**
|
||||
* View list of pages with a given label.
|
||||
*/
|
||||
public $listLabel_precond = array('IDF_Precondition::accessWiki');
|
||||
public function listLabel($request, $match)
|
||||
public $listPagesWithLabel_precond = array('IDF_Precondition::accessWiki');
|
||||
public function listPagesWithLabel($request, $match)
|
||||
{
|
||||
$prj = $request->project;
|
||||
$tag = Pluf_Shortcuts_GetObjectOr404('IDF_Tag', $match[2]);
|
||||
@@ -133,15 +171,15 @@ class IDF_Views_Wiki
|
||||
// Paginator to paginate the pages
|
||||
$ptags = self::getWikiTags($prj);
|
||||
$dtag = array_pop($ptags); // The last tag is the deprecated tag.
|
||||
$pag = new Pluf_Paginator(new IDF_WikiPage());
|
||||
$pag = new Pluf_Paginator(new IDF_Wiki_Page());
|
||||
$pag->model_view = 'join_tags';
|
||||
$pag->class = 'recent-issues';
|
||||
$pag->item_extra_props = array('project_m' => $prj,
|
||||
'shortname' => $prj->shortname);
|
||||
$pag->summary = sprintf(__('This table shows the documentation pages with label %s.'), (string) $tag);
|
||||
$pag->forced_where = new Pluf_SQL('project=%s AND idf_tag_id=%s', array($prj->id, $tag->id));
|
||||
$pag->action = array('IDF_Views_Wiki::listLabel', array($prj->shortname, $tag->id));
|
||||
$pag->edit_action = array('IDF_Views_Wiki::view', 'shortname', 'title');
|
||||
$pag->action = array('IDF_Views_Wiki::listPagesWithLabel', array($prj->shortname, $tag->id));
|
||||
$pag->edit_action = array('IDF_Views_Wiki::viewPage', 'shortname', 'title');
|
||||
$pag->extra_classes = array('right', '', 'a-c');
|
||||
$list_display = array(
|
||||
'title' => __('Page Title'),
|
||||
@@ -152,13 +190,11 @@ class IDF_Views_Wiki
|
||||
$pag->items_per_page = 25;
|
||||
$pag->no_results_text = __('No documentation pages were found.');
|
||||
$pag->setFromRequest($request);
|
||||
$tags = $prj->getTagCloud('wiki');
|
||||
return Pluf_Shortcuts_RenderToResponse('idf/wiki/index.html',
|
||||
return Pluf_Shortcuts_RenderToResponse('idf/wiki/listPages.html',
|
||||
array(
|
||||
'page_title' => $title,
|
||||
'label' => $tag,
|
||||
'pages' => $pag,
|
||||
'tags' => $tags,
|
||||
'dlabel' => $dtag,
|
||||
),
|
||||
$request);
|
||||
@@ -167,24 +203,25 @@ class IDF_Views_Wiki
|
||||
/**
|
||||
* Create a new documentation page.
|
||||
*/
|
||||
public $create_precond = array('IDF_Precondition::accessWiki',
|
||||
'Pluf_Precondition::loginRequired');
|
||||
public function create($request, $match)
|
||||
public $createPage_precond = array('IDF_Precondition::accessWiki',
|
||||
'Pluf_Precondition::loginRequired');
|
||||
public function createPage($request, $match)
|
||||
{
|
||||
$prj = $request->project;
|
||||
$title = __('New Page');
|
||||
$preview = false;
|
||||
if ($request->method == 'POST') {
|
||||
$form = new IDF_Form_WikiCreate($request->POST,
|
||||
$form = new IDF_Form_WikiPageCreate($request->POST,
|
||||
array('project' => $prj,
|
||||
'user' => $request->user
|
||||
));
|
||||
if ($form->isValid() and !isset($request->POST['preview'])) {
|
||||
$page = $form->save();
|
||||
$urlpage = Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::view',
|
||||
$urlpage = Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::viewPage',
|
||||
array($prj->shortname, $page->title));
|
||||
$request->user->setMessage(sprintf(__('The page <a href="%s">%s</a> has been created.'), $urlpage, Pluf_esc($page->title)));
|
||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::index',
|
||||
$request->user->setMessage(sprintf(__('The page <a href="%1$s">%2$s</a> has been created.'),
|
||||
$urlpage, Pluf_esc($page->title)));
|
||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::listPages',
|
||||
array($prj->shortname));
|
||||
return new Pluf_HTTP_Response_Redirect($url);
|
||||
} elseif (isset($request->POST['preview'])) {
|
||||
@@ -193,12 +230,12 @@ class IDF_Views_Wiki
|
||||
} else {
|
||||
$pagename = (isset($request->GET['name'])) ?
|
||||
$request->GET['name'] : '';
|
||||
$form = new IDF_Form_WikiCreate(null,
|
||||
$form = new IDF_Form_WikiPageCreate(null,
|
||||
array('name' => $pagename,
|
||||
'project' => $prj,
|
||||
'user' => $request->user));
|
||||
}
|
||||
return Pluf_Shortcuts_RenderToResponse('idf/wiki/create.html',
|
||||
return Pluf_Shortcuts_RenderToResponse('idf/wiki/createPage.html',
|
||||
array(
|
||||
'auto_labels' => self::autoCompleteArrays($prj),
|
||||
'page_title' => $title,
|
||||
@@ -208,27 +245,65 @@ class IDF_Views_Wiki
|
||||
$request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new resource.
|
||||
*/
|
||||
public $createResource_precond = array('IDF_Precondition::accessWiki',
|
||||
'Pluf_Precondition::loginRequired');
|
||||
public function createResource($request, $match)
|
||||
{
|
||||
$prj = $request->project;
|
||||
$title = __('New Resource');
|
||||
$preview = false;
|
||||
if ($request->method == 'POST') {
|
||||
$form = new IDF_Form_WikiResourceCreate(array_merge($request->POST, $request->FILES),
|
||||
array('project' => $prj, 'user' => $request->user));
|
||||
if ($form->isValid()) {
|
||||
$resource = $form->save();
|
||||
$urlresource = Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::viewResource',
|
||||
array($prj->shortname, $resource->title));
|
||||
$request->user->setMessage(sprintf(__('The resource <a href="%1$s">%2$s</a> has been created.'), $urlresource, Pluf_esc($resource->title)));
|
||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::listResources',
|
||||
array($prj->shortname));
|
||||
return new Pluf_HTTP_Response_Redirect($url);
|
||||
}
|
||||
} else {
|
||||
$resourcename = (isset($request->GET['name'])) ?
|
||||
$request->GET['name'] : '';
|
||||
$form = new IDF_Form_WikiResourceCreate(null,
|
||||
array('name' => $resourcename,
|
||||
'project' => $prj, 'user' => $request->user));
|
||||
}
|
||||
return Pluf_Shortcuts_RenderToResponse('idf/wiki/createResource.html',
|
||||
array(
|
||||
'page_title' => $title,
|
||||
'form' => $form,
|
||||
),
|
||||
$request);
|
||||
}
|
||||
|
||||
/**
|
||||
* View a documentation page.
|
||||
*/
|
||||
public $view_precond = array('IDF_Precondition::accessWiki');
|
||||
public function view($request, $match)
|
||||
public $viewPage_precond = array('IDF_Precondition::accessWiki');
|
||||
public function viewPage($request, $match)
|
||||
{
|
||||
$prj = $request->project;
|
||||
// Find the page
|
||||
$sql = new Pluf_SQL('project=%s AND title=%s',
|
||||
array($prj->id, $match[2]));
|
||||
$pages = Pluf::factory('IDF_WikiPage')->getList(array('filter'=>$sql->gen()));
|
||||
$pages = Pluf::factory('IDF_Wiki_Page')->getList(array('filter'=>$sql->gen()));
|
||||
if ($pages->count() != 1) {
|
||||
return new Pluf_HTTP_Response_NotFound($request);
|
||||
}
|
||||
$page = $pages[0];
|
||||
$oldrev = false;
|
||||
$revision = $page->get_current_revision();
|
||||
|
||||
// We grab the old revision if requested.
|
||||
if (isset($request->GET['rev']) and preg_match('/^[0-9]+$/', $request->GET['rev'])) {
|
||||
$oldrev = Pluf_Shortcuts_GetObjectOr404('IDF_WikiRevision',
|
||||
$revision = Pluf_Shortcuts_GetObjectOr404('IDF_Wiki_PageRevision',
|
||||
$request->GET['rev']);
|
||||
if ($oldrev->wikipage != $page->id or $oldrev->is_head == true) {
|
||||
if ($revision->wikipage != $page->id) {
|
||||
return new Pluf_HTTP_Response_NotFound($request);
|
||||
}
|
||||
}
|
||||
@@ -237,15 +312,13 @@ class IDF_Views_Wiki
|
||||
$tags = $page->get_tags_list();
|
||||
$dep = Pluf_Model_InArray($dtag, $tags);
|
||||
$title = $page->title;
|
||||
$revision = $page->get_current_revision();
|
||||
$false = Pluf_DB_BooleanToDb(false, $page->getDbConnection());
|
||||
$revs = $page->get_revisions_list(array('order' => 'creation_dtime DESC',
|
||||
'filter' => 'is_head='.$false));
|
||||
return Pluf_Shortcuts_RenderToResponse('idf/wiki/view.html',
|
||||
return Pluf_Shortcuts_RenderToResponse('idf/wiki/viewPage.html',
|
||||
array(
|
||||
'page_title' => $title,
|
||||
'page' => $page,
|
||||
'oldrev' => $oldrev,
|
||||
'rev' => $revision,
|
||||
'revs' => $revs,
|
||||
'tags' => $tags,
|
||||
@@ -255,14 +328,77 @@ class IDF_Views_Wiki
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a revision of a page.
|
||||
* View a documentation resource.
|
||||
*/
|
||||
public $deleteRev_precond = array('IDF_Precondition::accessWiki',
|
||||
'IDF_Precondition::projectMemberOrOwner');
|
||||
public function deleteRev($request, $match)
|
||||
public $viewResource_precond = array('IDF_Precondition::accessWiki');
|
||||
public function viewResource($request, $match)
|
||||
{
|
||||
$prj = $request->project;
|
||||
$oldrev = Pluf_Shortcuts_GetObjectOr404('IDF_WikiRevision', $match[2]);
|
||||
$sql = new Pluf_SQL('project=%s AND title=%s',
|
||||
array($prj->id, $match[2]));
|
||||
$resources = Pluf::factory('IDF_Wiki_Resource')->getList(array('filter'=>$sql->gen()));
|
||||
if ($resources->count() != 1) {
|
||||
return new Pluf_HTTP_Response_NotFound($request);
|
||||
}
|
||||
$resource = $resources[0];
|
||||
$revision = $resource->get_current_revision();
|
||||
|
||||
// grab the old revision if requested.
|
||||
if (isset($request->GET['rev']) and preg_match('/^[0-9]+$/', $request->GET['rev'])) {
|
||||
$revision = Pluf_Shortcuts_GetObjectOr404('IDF_Wiki_ResourceRevision',
|
||||
$request->GET['rev']);
|
||||
if ($revision->wikiresource != $resource->id) {
|
||||
return new Pluf_HTTP_Response_NotFound($request);
|
||||
}
|
||||
}
|
||||
$pagerevs = $revision->getPageRevisions();
|
||||
$title = $resource->title;
|
||||
$false = Pluf_DB_BooleanToDb(false, $resource->getDbConnection());
|
||||
$revs = $resource->get_revisions_list(array('order' => 'creation_dtime DESC',
|
||||
'filter' => 'is_head='.$false));
|
||||
return Pluf_Shortcuts_RenderToResponse('idf/wiki/viewResource.html',
|
||||
array(
|
||||
'page_title' => $title,
|
||||
'resource' => $resource,
|
||||
'rev' => $revision,
|
||||
'revs' => $revs,
|
||||
'pagerevs' => $pagerevs,
|
||||
),
|
||||
$request);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a bytestream to the given raw resource revision
|
||||
*/
|
||||
public $rawResource_precond = array('IDF_Precondition::accessWiki');
|
||||
public function rawResource($request, $match)
|
||||
{
|
||||
$prj = $request->project;
|
||||
$rev = Pluf_Shortcuts_GetObjectOr404('IDF_Wiki_ResourceRevision',
|
||||
$match[2]);
|
||||
$res = $rev->get_wikiresource();
|
||||
if ($res->get_project()->id != $prj->id) {
|
||||
return new Pluf_HTTP_Response_NotFound($request);
|
||||
}
|
||||
|
||||
$response = new Pluf_HTTP_Response_File($rev->getFilePath(), $res->mime_type);
|
||||
if (isset($request->GET['attachment']) && $request->GET['attachment']) {
|
||||
$response->headers['Content-Disposition'] =
|
||||
'attachment; filename="'.$res->title.'.'.$rev->fileext.'"';
|
||||
}
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a revision of a page.
|
||||
*/
|
||||
public $deletePageRev_precond = array('IDF_Precondition::accessWiki',
|
||||
'IDF_Precondition::projectMemberOrOwner');
|
||||
public function deletePageRev($request, $match)
|
||||
{
|
||||
$prj = $request->project;
|
||||
$oldrev = Pluf_Shortcuts_GetObjectOr404('IDF_Wiki_PageRevision', $match[2]);
|
||||
$page = $oldrev->get_wikipage();
|
||||
$prj->inOr404($page);
|
||||
if ($oldrev->is_head == true) {
|
||||
@@ -271,7 +407,7 @@ class IDF_Views_Wiki
|
||||
if ($request->method == 'POST') {
|
||||
$oldrev->delete();
|
||||
$request->user->setMessage(__('The old revision has been deleted.'));
|
||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::view',
|
||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::viewPage',
|
||||
array($prj->shortname, $page->title));
|
||||
return new Pluf_HTTP_Response_Redirect($url);
|
||||
}
|
||||
@@ -281,7 +417,7 @@ class IDF_Views_Wiki
|
||||
$false = Pluf_DB_BooleanToDb(false, $page->getDbConnection());
|
||||
$revs = $page->get_revisions_list(array('order' => 'creation_dtime DESC',
|
||||
'filter' => 'is_head='.$false));
|
||||
return Pluf_Shortcuts_RenderToResponse('idf/wiki/delete.html',
|
||||
return Pluf_Shortcuts_RenderToResponse('idf/wiki/deletePageRev.html',
|
||||
array(
|
||||
'page_title' => $title,
|
||||
'page' => $page,
|
||||
@@ -294,17 +430,53 @@ class IDF_Views_Wiki
|
||||
}
|
||||
|
||||
/**
|
||||
* View a documentation page.
|
||||
* Remove a revision of a resource.
|
||||
*/
|
||||
public $update_precond = array('IDF_Precondition::accessWiki',
|
||||
'Pluf_Precondition::loginRequired');
|
||||
public function update($request, $match)
|
||||
public $deleteResourceRev_precond = array('IDF_Precondition::accessWiki',
|
||||
'IDF_Precondition::projectMemberOrOwner');
|
||||
public function deleteResourceRev($request, $match)
|
||||
{
|
||||
$prj = $request->project;
|
||||
$oldrev = Pluf_Shortcuts_GetObjectOr404('IDF_Wiki_ResourceRevision', $match[2]);
|
||||
$resource = $oldrev->get_wikiresource();
|
||||
$prj->inOr404($resource);
|
||||
if ($oldrev->is_head == true) {
|
||||
return new Pluf_HTTP_Response_NotFound($request);
|
||||
}
|
||||
if ($request->method == 'POST') {
|
||||
$oldrev->delete();
|
||||
$request->user->setMessage(__('The old revision has been deleted.'));
|
||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::viewResource',
|
||||
array($prj->shortname, $resource->title));
|
||||
return new Pluf_HTTP_Response_Redirect($url);
|
||||
}
|
||||
|
||||
$title = sprintf(__('Delete Old Revision of %s'), $resource->title);
|
||||
$false = Pluf_DB_BooleanToDb(false, $resource->getDbConnection());
|
||||
$revs = $resource->get_revisions_list(array('order' => 'creation_dtime DESC',
|
||||
'filter' => 'is_head='.$false));
|
||||
return Pluf_Shortcuts_RenderToResponse('idf/wiki/deleteResourceRev.html',
|
||||
array(
|
||||
'page_title' => $title,
|
||||
'resource' => $resource,
|
||||
'oldrev' => $oldrev,
|
||||
'revs' => $revs,
|
||||
),
|
||||
$request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a documentation page.
|
||||
*/
|
||||
public $updatePage_precond = array('IDF_Precondition::accessWiki',
|
||||
'Pluf_Precondition::loginRequired');
|
||||
public function updatePage($request, $match)
|
||||
{
|
||||
$prj = $request->project;
|
||||
// Find the page
|
||||
$sql = new Pluf_SQL('project=%s AND title=%s',
|
||||
array($prj->id, $match[2]));
|
||||
$pages = Pluf::factory('IDF_WikiPage')->getList(array('filter'=>$sql->gen()));
|
||||
$pages = Pluf::factory('IDF_Wiki_Page')->getList(array('filter'=>$sql->gen()));
|
||||
if ($pages->count() != 1) {
|
||||
return new Pluf_HTTP_Response_NotFound($request);
|
||||
}
|
||||
@@ -316,13 +488,14 @@ class IDF_Views_Wiki
|
||||
'user' => $request->user,
|
||||
'page' => $page);
|
||||
if ($request->method == 'POST') {
|
||||
$form = new IDF_Form_WikiUpdate($request->POST, $params);
|
||||
$form = new IDF_Form_WikiPageUpdate($request->POST, $params);
|
||||
if ($form->isValid() and !isset($request->POST['preview'])) {
|
||||
$page = $form->save();
|
||||
$urlpage = Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::view',
|
||||
$urlpage = Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::viewPage',
|
||||
array($prj->shortname, $page->title));
|
||||
$request->user->setMessage(sprintf(__('The page <a href="%s">%s</a> has been updated.'), $urlpage, Pluf_esc($page->title)));
|
||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::index',
|
||||
$request->user->setMessage(sprintf(__('The page <a href="%1$s">%2$s</a> has been updated.'),
|
||||
$urlpage, Pluf_esc($page->title)));
|
||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::listPages',
|
||||
array($prj->shortname));
|
||||
return new Pluf_HTTP_Response_Redirect($url);
|
||||
} elseif (isset($request->POST['preview'])) {
|
||||
@@ -330,9 +503,9 @@ class IDF_Views_Wiki
|
||||
}
|
||||
} else {
|
||||
|
||||
$form = new IDF_Form_WikiUpdate(null, $params);
|
||||
$form = new IDF_Form_WikiPageUpdate(null, $params);
|
||||
}
|
||||
return Pluf_Shortcuts_RenderToResponse('idf/wiki/update.html',
|
||||
return Pluf_Shortcuts_RenderToResponse('idf/wiki/updatePage.html',
|
||||
array(
|
||||
'auto_labels' => self::autoCompleteArrays($prj),
|
||||
'page_title' => $title,
|
||||
@@ -345,34 +518,82 @@ class IDF_Views_Wiki
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a Wiki page.
|
||||
* Update a documentation resource.
|
||||
*/
|
||||
public $delete_precond = array('IDF_Precondition::accessWiki',
|
||||
'IDF_Precondition::projectMemberOrOwner');
|
||||
public function delete($request, $match)
|
||||
public $updateResource_precond = array('IDF_Precondition::accessWiki',
|
||||
'Pluf_Precondition::loginRequired');
|
||||
public function updateResource($request, $match)
|
||||
{
|
||||
$prj = $request->project;
|
||||
$page = Pluf_Shortcuts_GetObjectOr404('IDF_WikiPage', $match[2]);
|
||||
$prj->inOr404($page);
|
||||
$params = array('page' => $page);
|
||||
// Find the page
|
||||
$sql = new Pluf_SQL('project=%s AND title=%s',
|
||||
array($prj->id, $match[2]));
|
||||
$resources = Pluf::factory('IDF_Wiki_Resource')->getList(array('filter'=>$sql->gen()));
|
||||
if ($resources->count() != 1) {
|
||||
return new Pluf_HTTP_Response_NotFound($request);
|
||||
}
|
||||
$resource = $resources[0];
|
||||
$title = sprintf(__('Update %s'), $resource->title);
|
||||
$revision = $resource->get_current_revision();
|
||||
$params = array('project' => $prj,
|
||||
'user' => $request->user,
|
||||
'resource' => $resource);
|
||||
if ($request->method == 'POST') {
|
||||
$form = new IDF_Form_WikiDelete($request->POST, $params);
|
||||
$form = new IDF_Form_WikiResourceUpdate(array_merge($request->POST, $request->FILES),
|
||||
$params);
|
||||
if ($form->isValid()) {
|
||||
$form->save();
|
||||
$request->user->setMessage(__('The documentation page has been deleted.'));
|
||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::index',
|
||||
$page = $form->save();
|
||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::viewResource',
|
||||
array($prj->shortname, $resource->title));
|
||||
$request->user->setMessage(sprintf(__('The resource <a href="%1$s">%2$s</a> has been updated.'),
|
||||
$url, Pluf_esc($resource->title)));
|
||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::listResources',
|
||||
array($prj->shortname));
|
||||
return new Pluf_HTTP_Response_Redirect($url);
|
||||
}
|
||||
} else {
|
||||
$form = new IDF_Form_WikiDelete(null, $params);
|
||||
$form = new IDF_Form_WikiResourceUpdate(null, $params);
|
||||
}
|
||||
|
||||
return Pluf_Shortcuts_RenderToResponse('idf/wiki/updateResource.html',
|
||||
array(
|
||||
'page_title' => $title,
|
||||
'resource' => $resource,
|
||||
'rev' => $revision,
|
||||
'form' => $form,
|
||||
),
|
||||
$request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a Wiki page.
|
||||
*/
|
||||
public $deletePage_precond = array('IDF_Precondition::accessWiki',
|
||||
'IDF_Precondition::projectMemberOrOwner');
|
||||
public function deletePage($request, $match)
|
||||
{
|
||||
$prj = $request->project;
|
||||
$page = Pluf_Shortcuts_GetObjectOr404('IDF_Wiki_Page', $match[2]);
|
||||
$prj->inOr404($page);
|
||||
$params = array('page' => $page);
|
||||
if ($request->method == 'POST') {
|
||||
$form = new IDF_Form_WikiPageDelete($request->POST, $params);
|
||||
if ($form->isValid()) {
|
||||
$form->save();
|
||||
$request->user->setMessage(__('The documentation page has been deleted.'));
|
||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::listPages',
|
||||
array($prj->shortname));
|
||||
return new Pluf_HTTP_Response_Redirect($url);
|
||||
}
|
||||
} else {
|
||||
$form = new IDF_Form_WikiPageDelete(null, $params);
|
||||
}
|
||||
$title = sprintf(__('Delete Page %s'), $page->title);
|
||||
$revision = $page->get_current_revision();
|
||||
$false = Pluf_DB_BooleanToDb(false, $page->getDbConnection());
|
||||
$revs = $page->get_revisions_list(array('order' => 'creation_dtime DESC',
|
||||
'filter' => 'is_head='.$false));
|
||||
return Pluf_Shortcuts_RenderToResponse('idf/wiki/deletepage.html',
|
||||
return Pluf_Shortcuts_RenderToResponse('idf/wiki/deletePage.html',
|
||||
array(
|
||||
'page_title' => $title,
|
||||
'page' => $page,
|
||||
@@ -384,6 +605,45 @@ class IDF_Views_Wiki
|
||||
$request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a Wiki resource.
|
||||
*/
|
||||
public $deleteResource_precond = array('IDF_Precondition::accessWiki',
|
||||
'IDF_Precondition::projectMemberOrOwner');
|
||||
public function deleteResource($request, $match)
|
||||
{
|
||||
$prj = $request->project;
|
||||
$resource = Pluf_Shortcuts_GetObjectOr404('IDF_Wiki_Resource', $match[2]);
|
||||
$prj->inOr404($resource);
|
||||
$params = array('resource' => $resource);
|
||||
if ($request->method == 'POST') {
|
||||
$form = new IDF_Form_WikiResourceDelete($request->POST, $params);
|
||||
if ($form->isValid()) {
|
||||
$form->save();
|
||||
$request->user->setMessage(__('The documentation resource has been deleted.'));
|
||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::listResources',
|
||||
array($prj->shortname));
|
||||
return new Pluf_HTTP_Response_Redirect($url);
|
||||
}
|
||||
} else {
|
||||
$form = new IDF_Form_WikiResourceDelete(null, $params);
|
||||
}
|
||||
$title = sprintf(__('Delete Resource %s'), $resource->title);
|
||||
$revision = $resource->get_current_revision();
|
||||
$false = Pluf_DB_BooleanToDb(false, $resource->getDbConnection());
|
||||
$revs = $resource->get_revisions_list(array('order' => 'creation_dtime DESC',
|
||||
'filter' => 'is_head='.$false));
|
||||
return Pluf_Shortcuts_RenderToResponse('idf/wiki/deleteResource.html',
|
||||
array(
|
||||
'page_title' => $title,
|
||||
'resource' => $resource,
|
||||
'form' => $form,
|
||||
'rev' => $revision,
|
||||
'revs' => $revs,
|
||||
),
|
||||
$request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the wiki tags.
|
||||
*
|
||||
@@ -413,7 +673,7 @@ class IDF_Views_Wiki
|
||||
$sql = new Pluf_SQL('project=%s AND idf_tag_id=%s', array($project->id,
|
||||
$dtag->id));
|
||||
$ids = array();
|
||||
foreach (Pluf::factory('IDF_WikiPage')->getList(array('filter' => $sql->gen(), 'view' => 'join_tags'))
|
||||
foreach (Pluf::factory('IDF_Wiki_Page')->getList(array('filter' => $sql->gen(), 'view' => 'join_tags'))
|
||||
as $file) {
|
||||
$ids[] = (int) $file->id;
|
||||
}
|
||||
|
@@ -31,22 +31,28 @@
|
||||
class IDF_Webhook
|
||||
{
|
||||
/**
|
||||
* Perform the POST request given the webhook payload.
|
||||
* Perform the request given the webhook payload.
|
||||
*
|
||||
* @param array Payload
|
||||
* @return bool Success or error
|
||||
*/
|
||||
public static function postNotification($payload)
|
||||
public static function processNotification($payload)
|
||||
{
|
||||
$data = json_encode($payload['to_send']);
|
||||
$sign_header = 'Web-Hook-Hmac';
|
||||
// use the old signature header if we're asked for
|
||||
if (Pluf::f('webhook_processing', '') === 'compat') {
|
||||
$sign_header = 'Post-Commit-Hook-Hmac';
|
||||
}
|
||||
$sign = hash_hmac('md5', $data, $payload['authkey']);
|
||||
$params = array('http' => array(
|
||||
'method' => 'POST',
|
||||
// fall-back to POST for old queue items
|
||||
'method' => empty($payload['method']) ? 'POST' : $payload['method'],
|
||||
'content' => $data,
|
||||
'user_agent' => 'Indefero Hook Sender (http://www.indefero.net)',
|
||||
'max_redirects' => 0,
|
||||
'timeout' => 15,
|
||||
'header'=> 'Post-Commit-Hook-Hmac: '.$sign."\r\n"
|
||||
'header'=> $sign_header.': '.$sign."\r\n"
|
||||
.'Content-Type: application/json'."\r\n",
|
||||
)
|
||||
);
|
||||
@@ -76,7 +82,7 @@ class IDF_Webhook
|
||||
public static function process($sender, &$params)
|
||||
{
|
||||
$item = $params['item'];
|
||||
if ($item->type != 'new_commit') {
|
||||
if (!in_array($item->type, array('new_commit', 'upload'))) {
|
||||
// We do nothing.
|
||||
return;
|
||||
}
|
||||
@@ -90,7 +96,7 @@ class IDF_Webhook
|
||||
return;
|
||||
}
|
||||
// We have either to retry or to push for the first time.
|
||||
$res = self::postNotification($item->payload);
|
||||
$res = self::processNotification($item->payload);
|
||||
if ($res) {
|
||||
$params['res']['IDF_Webhook::process'] = true;
|
||||
} elseif ($item->trials >= 9) {
|
||||
|
@@ -28,10 +28,10 @@ Pluf::loadFunction('Pluf_Template_dateAgo');
|
||||
* Base definition of a wiki page.
|
||||
*
|
||||
* A wiki page can have tags and be starred by the users. The real
|
||||
* content of the page is stored in the IDF_WikiRevision
|
||||
* content of the page is stored in the IDF_Wiki_PageRevision
|
||||
* object. Several revisions are associated to a given page.
|
||||
*/
|
||||
class IDF_WikiPage extends Pluf_Model
|
||||
class IDF_Wiki_Page extends Pluf_Model
|
||||
{
|
||||
public $_model = __CLASS__;
|
||||
|
||||
@@ -113,12 +113,12 @@ class IDF_WikiPage extends Pluf_Model
|
||||
'type' => 'normal',
|
||||
),
|
||||
);
|
||||
$table = $this->_con->pfx.'idf_tag_idf_wikipage_assoc';
|
||||
$table = $this->_con->pfx.'idf_tag_idf_wiki_page_assoc';
|
||||
$this->_a['views'] = array(
|
||||
'join_tags' =>
|
||||
array(
|
||||
'join' => 'LEFT JOIN '.$table
|
||||
.' ON idf_wikipage_id=id',
|
||||
.' ON idf_wiki_page_id=id',
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -185,7 +185,7 @@ class IDF_WikiPage extends Pluf_Model
|
||||
*/
|
||||
public function timelineFragment($request)
|
||||
{
|
||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::view',
|
||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::viewPage',
|
||||
array($request->project->shortname,
|
||||
$this->title));
|
||||
$out = '<tr class="log"><td><a href="'.$url.'">'.
|
||||
@@ -195,17 +195,17 @@ class IDF_WikiPage extends Pluf_Model
|
||||
$user = $stag->start($this->get_submitter(), $request, '', false);
|
||||
$out .= sprintf(__('<a href="%1$s" title="View page">%2$s</a>, %3$s'), $url, Pluf_esc($this->title), Pluf_esc($this->summary)).'</td>';
|
||||
$out .= "\n".'<tr class="extra"><td colspan="2">
|
||||
<div class="helptext right">'.sprintf(__('Creation of <a href="%s">page %s</a>, by %s'), $url, Pluf_esc($this->title), $user).'</div></td></tr>';
|
||||
<div class="helptext right">'.sprintf(__('Creation of <a href="%1$s">page %2$s</a>, by %3$s'), $url, Pluf_esc($this->title), $user).'</div></td></tr>';
|
||||
return Pluf_Template::markSafe($out);
|
||||
}
|
||||
|
||||
public function feedFragment($request)
|
||||
{
|
||||
$url = Pluf::f('url_base')
|
||||
.Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::view',
|
||||
.Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::viewPage',
|
||||
array($request->project->shortname,
|
||||
$this->title));
|
||||
$title = sprintf(__('%s: Documentation page %s added - %s'),
|
||||
$title = sprintf(__('%1$s: Documentation page %2$s added - %3$s'),
|
||||
$request->project->name,
|
||||
$this->title, $this->summary);
|
||||
$date = Pluf_Date::gmDateToGmString($this->creation_dtime);
|
||||
@@ -218,7 +218,7 @@ class IDF_WikiPage extends Pluf_Model
|
||||
'create' => true,
|
||||
'date' => $date)
|
||||
);
|
||||
$tmpl = new Pluf_Template('idf/wiki/feedfragment.xml');
|
||||
$tmpl = new Pluf_Template('idf/wiki/feedfragment-page.xml');
|
||||
return $tmpl->render($context);
|
||||
}
|
||||
}
|
@@ -25,13 +25,13 @@
|
||||
* A revision of a wiki page.
|
||||
*
|
||||
*/
|
||||
class IDF_WikiRevision extends Pluf_Model
|
||||
class IDF_Wiki_PageRevision extends Pluf_Model
|
||||
{
|
||||
public $_model = __CLASS__;
|
||||
|
||||
function init()
|
||||
{
|
||||
$this->_a['table'] = 'idf_wikirevisions';
|
||||
$this->_a['table'] = 'idf_wikipagerevs';
|
||||
$this->_a['model'] = __CLASS__;
|
||||
$this->_a['cols'] = array(
|
||||
// It is mandatory to have an "id" column.
|
||||
@@ -43,7 +43,7 @@ class IDF_WikiRevision extends Pluf_Model
|
||||
'wikipage' =>
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Foreignkey',
|
||||
'model' => 'IDF_WikiPage',
|
||||
'model' => 'IDF_Wiki_Page',
|
||||
'blank' => false,
|
||||
'verbose' => __('page'),
|
||||
'relate_name' => 'revisions',
|
||||
@@ -83,7 +83,7 @@ class IDF_WikiRevision extends Pluf_Model
|
||||
'type' => 'Pluf_DB_Field_Serialized',
|
||||
'blank' => true,
|
||||
'verbose' => __('changes'),
|
||||
'help_text' => 'Serialized array of the changes in the issue.',
|
||||
'help_text' => 'Serialized array of the changes in the page.',
|
||||
),
|
||||
'creation_dtime' =>
|
||||
array(
|
||||
@@ -99,6 +99,14 @@ class IDF_WikiRevision extends Pluf_Model
|
||||
'type' => 'normal',
|
||||
),
|
||||
);
|
||||
$table = $this->_con->pfx.'idf_wiki_pagerevision_idf_wiki_resourcerevision_assoc';
|
||||
$this->_a['views'] = array(
|
||||
'join_pagerevision' =>
|
||||
array(
|
||||
'join' => 'LEFT JOIN '.$table
|
||||
.' ON idf_wiki_pagerevision_id=id',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
function changedRevision()
|
||||
@@ -129,6 +137,8 @@ class IDF_WikiRevision extends Pluf_Model
|
||||
|
||||
function postSave($create=false)
|
||||
{
|
||||
$page = $this->get_wikipage();
|
||||
|
||||
if ($create) {
|
||||
// Check if more than one revision for this page. We do
|
||||
// not want to insert the first revision in the timeline
|
||||
@@ -136,10 +146,9 @@ class IDF_WikiRevision extends Pluf_Model
|
||||
// update as update is performed to change the is_head
|
||||
// flag.
|
||||
$sql = new Pluf_SQL('wikipage=%s', array($this->wikipage));
|
||||
$rev = Pluf::factory('IDF_WikiRevision')->getList(array('filter'=>$sql->gen()));
|
||||
$rev = Pluf::factory('IDF_Wiki_PageRevision')->getList(array('filter'=>$sql->gen()));
|
||||
if ($rev->count() > 1) {
|
||||
IDF_Timeline::insert($this, $this->get_wikipage()->get_project(),
|
||||
$this->get_submitter());
|
||||
IDF_Timeline::insert($this, $page->get_project(), $this->get_submitter());
|
||||
foreach ($rev as $r) {
|
||||
if ($r->id != $this->id and $r->is_head) {
|
||||
$r->is_head = false;
|
||||
@@ -147,18 +156,38 @@ class IDF_WikiRevision extends Pluf_Model
|
||||
}
|
||||
}
|
||||
}
|
||||
$page = $this->get_wikipage();
|
||||
$page->update(); // Will update the modification timestamp.
|
||||
IDF_Search::index($page);
|
||||
}
|
||||
|
||||
IDF_Search::index($page);
|
||||
$page->update(); // Will update the modification timestamp.
|
||||
|
||||
// remember the resource revisions used in this page revision
|
||||
if ($this->is_head) {
|
||||
preg_match_all('#\[\[!([A-Za-z0-9\-]+)[^\]]*\]\]#im', $this->content, $matches, PREG_PATTERN_ORDER);
|
||||
if (count($matches) > 1 && count($matches[1]) > 0) {
|
||||
foreach ($matches[1] as $resourceName) {
|
||||
$sql = new Pluf_SQL('project=%s AND title=%s',
|
||||
array($page->get_project()->id, $resourceName));
|
||||
$resources = Pluf::factory('IDF_Wiki_Resource')->getList(array('filter'=>$sql->gen()));
|
||||
if ($resources->count() == 0)
|
||||
continue;
|
||||
|
||||
$current_revision = $resources[0]->get_current_revision();
|
||||
$current_revision->setAssoc($this);
|
||||
$this->setAssoc($current_revision);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function timelineFragment($request)
|
||||
{
|
||||
$page = $this->get_wikipage();
|
||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::view',
|
||||
array($request->project->shortname,
|
||||
$page->title));
|
||||
$url = Pluf::f('url_base')
|
||||
.Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::viewPage',
|
||||
array($request->project->shortname,
|
||||
$page->title),
|
||||
array('rev' => $this->id));
|
||||
$out = "\n".'<tr class="log"><td><a href="'.$url.'">'.
|
||||
Pluf_esc(Pluf_Template_dateAgo($this->creation_dtime, 'without')).
|
||||
'</a></td><td>';
|
||||
@@ -186,26 +215,20 @@ class IDF_WikiRevision extends Pluf_Model
|
||||
}
|
||||
$out .= '</td></tr>';
|
||||
$out .= "\n".'<tr class="extra"><td colspan="2">
|
||||
<div class="helptext right">'.sprintf(__('Change of <a href="%s">%s</a>, by %s'), $url, Pluf_esc($page->title), $user).'</div></td></tr>';
|
||||
<div class="helptext right">'.sprintf(__('Change of <a href="%1$s">%2$s</a>, by %3$s'), $url, Pluf_esc($page->title), $user).'</div></td></tr>';
|
||||
return Pluf_Template::markSafe($out);
|
||||
}
|
||||
|
||||
public function feedFragment($request)
|
||||
{
|
||||
$page = $this->get_wikipage();
|
||||
if (!$this->is_head) {
|
||||
$url = Pluf::f('url_base')
|
||||
.Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::view',
|
||||
array($request->project->shortname,
|
||||
$page->title),
|
||||
array('rev' => $this->id));
|
||||
} else {
|
||||
$url = Pluf::f('url_base')
|
||||
.Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::view',
|
||||
array($request->project->shortname,
|
||||
$page->title));
|
||||
}
|
||||
$title = sprintf(__('%s: Documentation page %s updated - %s'),
|
||||
$url = Pluf::f('url_base')
|
||||
.Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::viewPage',
|
||||
array($request->project->shortname,
|
||||
$page->title),
|
||||
array('rev' => $this->id));
|
||||
|
||||
$title = sprintf(__('%1$s: Documentation page %2$s updated - %3$s'),
|
||||
$request->project->name,
|
||||
$page->title, $page->summary);
|
||||
$date = Pluf_Date::gmDateToGmString($this->creation_dtime);
|
||||
@@ -218,14 +241,14 @@ class IDF_WikiRevision extends Pluf_Model
|
||||
'create' => false,
|
||||
'date' => $date)
|
||||
);
|
||||
$tmpl = new Pluf_Template('idf/wiki/feedfragment.xml');
|
||||
$tmpl = new Pluf_Template('idf/wiki/feedfragment-page.xml');
|
||||
return $tmpl->render($context);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Notification of change of a WikiPage.
|
||||
* Notification of change of a Wiki Page.
|
||||
*
|
||||
* The content of a WikiPage is in the IDF_WikiRevision object,
|
||||
* this is why we send the notificatin from there. This means that
|
||||
@@ -243,42 +266,49 @@ class IDF_WikiRevision extends Pluf_Model
|
||||
*/
|
||||
public function notify($conf, $create=true)
|
||||
{
|
||||
if ('' == $conf->getVal('wiki_notification_email', '')) {
|
||||
return;
|
||||
}
|
||||
$wikipage = $this->get_wikipage();
|
||||
$project = $wikipage->get_project();
|
||||
$current_locale = Pluf_Translation::getLocale();
|
||||
$langs = Pluf::f('languages', array('en'));
|
||||
Pluf_Translation::loadSetLocale($langs[0]);
|
||||
$context = new Pluf_Template_Context(
|
||||
array(
|
||||
'page' => $this->get_wikipage(),
|
||||
'rev' => $this,
|
||||
'project' => $this->get_wikipage()->get_project(),
|
||||
'url_base' => Pluf::f('url_base'),
|
||||
)
|
||||
);
|
||||
if ($create) {
|
||||
$template = 'idf/wiki/wiki-created-email.txt';
|
||||
$title = sprintf(__('New Documentation Page %s - %s (%s)'),
|
||||
$this->get_wikipage()->title,
|
||||
$this->get_wikipage()->summary,
|
||||
$this->get_wikipage()->get_project()->shortname);
|
||||
} else {
|
||||
$template = 'idf/wiki/wiki-updated-email.txt';
|
||||
$title = sprintf(__('Documentation Page Changed %s - %s (%s)'),
|
||||
$this->get_wikipage()->title,
|
||||
$this->get_wikipage()->summary,
|
||||
$this->get_wikipage()->get_project()->shortname);
|
||||
}
|
||||
$tmpl = new Pluf_Template($template);
|
||||
$text_email = $tmpl->render($context);
|
||||
|
||||
$addresses = explode(',', $conf->getVal('wiki_notification_email'));
|
||||
foreach ($addresses as $address) {
|
||||
$email = new Pluf_Mail(Pluf::f('from_email'),
|
||||
$from_email = Pluf::f('from_email');
|
||||
$messageId = '<'.md5('wiki'.$wikipage->id.md5(Pluf::f('secret_key'))).'@'.Pluf::f('mail_host', 'localhost').'>';
|
||||
$recipients = $project->getNotificationRecipientsForTab('wiki');
|
||||
|
||||
foreach ($recipients as $address => $language) {
|
||||
|
||||
if ($this->get_submitter()->email === $address) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Pluf_Translation::loadSetLocale($language);
|
||||
|
||||
$context = new Pluf_Template_Context(array(
|
||||
'page' => $wikipage,
|
||||
'rev' => $this,
|
||||
'project' => $project,
|
||||
'url_base' => Pluf::f('url_base'),
|
||||
));
|
||||
|
||||
$tplfile = 'idf/wiki/wiki-created-email.txt';
|
||||
$subject = __('New Documentation Page %1$s - %2$s (%3$s)');
|
||||
$headers = array('Message-ID' => $messageId);
|
||||
if (!$create) {
|
||||
$tplfile = 'idf/wiki/wiki-updated-email.txt';
|
||||
$subject = __('Documentation Page Changed %1$s - %2$s (%3$s)');
|
||||
$headers = array('References' => $messageId);
|
||||
}
|
||||
|
||||
$tmpl = new Pluf_Template($tplfile);
|
||||
$text_email = $tmpl->render($context);
|
||||
|
||||
$email = new Pluf_Mail($from_email,
|
||||
$address,
|
||||
$title);
|
||||
sprintf($subject,
|
||||
$wikipage->title,
|
||||
$wikipage->summary,
|
||||
$project->shortname));
|
||||
$email->addTextMessage($text_email);
|
||||
$email->addHeaders($headers);
|
||||
$email->sendMail();
|
||||
}
|
||||
|
204
src/IDF/Wiki/Resource.php
Normal file
204
src/IDF/Wiki/Resource.php
Normal file
@@ -0,0 +1,204 @@
|
||||
<?php
|
||||
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/*
|
||||
# ***** BEGIN LICENSE BLOCK *****
|
||||
# This file is part of InDefero, an open source project management application.
|
||||
# Copyright (C) 2008-2011 Céondo Ltd and contributors.
|
||||
#
|
||||
# InDefero 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.
|
||||
#
|
||||
# InDefero is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
#
|
||||
# ***** END LICENSE BLOCK ***** */
|
||||
|
||||
Pluf::loadFunction('Pluf_HTTP_URL_urlForView');
|
||||
Pluf::loadFunction('Pluf_Template_dateAgo');
|
||||
|
||||
/**
|
||||
* Base definition of a wiki resource.
|
||||
*
|
||||
* The resource groups several logical versions of a file into one and gives
|
||||
* it a unique title across a project's wiki. The current file version is
|
||||
* saved in a IDF_Wiki_ResourceRevision, which is also the entity that is
|
||||
* linked with a specific IDF_Wiki_PageRevision on which the resource is
|
||||
* displayed.
|
||||
*/
|
||||
class IDF_Wiki_Resource extends Pluf_Model
|
||||
{
|
||||
public $_model = __CLASS__;
|
||||
|
||||
function init()
|
||||
{
|
||||
$this->_a['table'] = 'idf_wikiresources';
|
||||
$this->_a['model'] = __CLASS__;
|
||||
$this->_a['cols'] = array(
|
||||
// It is mandatory to have an "id" column.
|
||||
'id' =>
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Sequence',
|
||||
'blank' => true,
|
||||
),
|
||||
'project' =>
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Foreignkey',
|
||||
'model' => 'IDF_Project',
|
||||
'blank' => false,
|
||||
'verbose' => __('project'),
|
||||
'relate_name' => 'wikipages',
|
||||
),
|
||||
'title' =>
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Varchar',
|
||||
'blank' => false,
|
||||
'size' => 250,
|
||||
'verbose' => __('title'),
|
||||
'help_text' => __('The title of the resource must only contain letters, digits, dots or the dash character. For example: my-resource.png.'),
|
||||
),
|
||||
'mime_type' =>
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Varchar',
|
||||
'blank' => false,
|
||||
'size' => 100,
|
||||
'verbose' => __('MIME media type'),
|
||||
'help_text' => __('The MIME media type of the resource.'),
|
||||
),
|
||||
'summary' =>
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Varchar',
|
||||
'blank' => false,
|
||||
'size' => 250,
|
||||
'verbose' => __('summary'),
|
||||
'help_text' => __('A one line description of the resource.'),
|
||||
),
|
||||
'submitter' =>
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Foreignkey',
|
||||
'model' => 'Pluf_User',
|
||||
'blank' => false,
|
||||
'verbose' => __('submitter'),
|
||||
'relate_name' => 'submitted_wikipages',
|
||||
),
|
||||
'creation_dtime' =>
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Datetime',
|
||||
'blank' => true,
|
||||
'verbose' => __('creation date'),
|
||||
),
|
||||
'modif_dtime' =>
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Datetime',
|
||||
'blank' => true,
|
||||
'verbose' => __('modification date'),
|
||||
),
|
||||
);
|
||||
$this->_a['idx'] = array(
|
||||
'modif_dtime_idx' =>
|
||||
array(
|
||||
'col' => 'modif_dtime',
|
||||
'type' => 'normal',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
function __toString()
|
||||
{
|
||||
return $this->title.' - '.$this->summary;
|
||||
}
|
||||
|
||||
/**
|
||||
* We drop the information from the timeline.
|
||||
*/
|
||||
function preDelete()
|
||||
{
|
||||
IDF_Timeline::remove($this);
|
||||
IDF_Search::remove($this);
|
||||
}
|
||||
|
||||
function get_current_revision()
|
||||
{
|
||||
$true = Pluf_DB_BooleanToDb(true, $this->getDbConnection());
|
||||
$rev = $this->get_revisions_list(array('filter' => 'is_head='.$true,
|
||||
'nb' => 1));
|
||||
return ($rev->count() == 1) ? $rev[0] : null;
|
||||
}
|
||||
|
||||
function preSave($create=false)
|
||||
{
|
||||
if ($this->id == '') {
|
||||
$this->creation_dtime = gmdate('Y-m-d H:i:s');
|
||||
}
|
||||
$this->modif_dtime = gmdate('Y-m-d H:i:s');
|
||||
}
|
||||
|
||||
function postSave($create=false)
|
||||
{
|
||||
// Note: No indexing is performed here. The indexing is
|
||||
// triggered in the postSave step of the revision to ensure
|
||||
// that the page as a given revision in the database when
|
||||
// doing the indexing.
|
||||
if ($create) {
|
||||
IDF_Timeline::insert($this, $this->get_project(),
|
||||
$this->get_submitter());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an HTML fragment used to display this resource in the
|
||||
* timeline.
|
||||
*
|
||||
* The request object is given to be able to check the rights and
|
||||
* as such create links to other items etc. You can consider that
|
||||
* if displayed, you can create a link to it.
|
||||
*
|
||||
* @param Pluf_HTTP_Request
|
||||
* @return Pluf_Template_SafeString
|
||||
*/
|
||||
public function timelineFragment($request)
|
||||
{
|
||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::viewResource',
|
||||
array($request->project->shortname,
|
||||
$this->title));
|
||||
$out = '<tr class="log"><td><a href="'.$url.'">'.
|
||||
Pluf_esc(Pluf_Template_dateAgo($this->creation_dtime, 'without')).
|
||||
'</a></td><td>';
|
||||
$stag = new IDF_Template_ShowUser();
|
||||
$user = $stag->start($this->get_submitter(), $request, '', false);
|
||||
$out .= sprintf(__('<a href="%1$s" title="View resource">%2$s</a>, %3$s'), $url, Pluf_esc($this->title), Pluf_esc($this->summary)).'</td>';
|
||||
$out .= "\n".'<tr class="extra"><td colspan="2">
|
||||
<div class="helptext right">'.sprintf(__('Creation of <a href="%1$s">resource %2$s</a>, by %3$s'), $url, Pluf_esc($this->title), $user).'</div></td></tr>';
|
||||
return Pluf_Template::markSafe($out);
|
||||
}
|
||||
|
||||
public function feedFragment($request)
|
||||
{
|
||||
$url = Pluf::f('url_base')
|
||||
.Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::viewResource',
|
||||
array($request->project->shortname,
|
||||
$this->title));
|
||||
$title = sprintf(__('%1$s: Documentation resource %2$s added - %3$s'),
|
||||
$request->project->name,
|
||||
$this->title, $this->summary);
|
||||
$date = Pluf_Date::gmDateToGmString($this->creation_dtime);
|
||||
$context = new Pluf_Template_Context_Request(
|
||||
$request,
|
||||
array('url' => $url,
|
||||
'title' => $title,
|
||||
'resource' => $this,
|
||||
'rev' => $this->get_current_revision(),
|
||||
'create' => true,
|
||||
'date' => $date)
|
||||
);
|
||||
$tmpl = new Pluf_Template('idf/wiki/feedfragment-resource.xml');
|
||||
return $tmpl->render($context);
|
||||
}
|
||||
}
|
340
src/IDF/Wiki/ResourceRevision.php
Normal file
340
src/IDF/Wiki/ResourceRevision.php
Normal file
@@ -0,0 +1,340 @@
|
||||
<?php
|
||||
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/*
|
||||
# ***** BEGIN LICENSE BLOCK *****
|
||||
# This file is part of InDefero, an open source project management application.
|
||||
# Copyright (C) 2008-2011 Céondo Ltd and contributors.
|
||||
#
|
||||
# InDefero 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.
|
||||
#
|
||||
# InDefero is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
#
|
||||
# ***** END LICENSE BLOCK ***** */
|
||||
|
||||
/**
|
||||
* A single resource revision.
|
||||
*/
|
||||
class IDF_Wiki_ResourceRevision extends Pluf_Model
|
||||
{
|
||||
public $_model = __CLASS__;
|
||||
|
||||
function init()
|
||||
{
|
||||
$this->_a['table'] = 'idf_wikiresourcerevs';
|
||||
$this->_a['model'] = __CLASS__;
|
||||
$this->_a['cols'] = array(
|
||||
// It is mandatory to have an "id" column.
|
||||
'id' =>
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Sequence',
|
||||
'blank' => true,
|
||||
),
|
||||
'wikiresource' =>
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Foreignkey',
|
||||
'model' => 'IDF_Wiki_Resource',
|
||||
'blank' => false,
|
||||
'verbose' => __('resource'),
|
||||
'relate_name' => 'revisions',
|
||||
),
|
||||
'is_head' =>
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Boolean',
|
||||
'blank' => false,
|
||||
'default' => false,
|
||||
'help_text' => 'If this revision is the latest, we mark it as being the head revision.',
|
||||
'index' => true,
|
||||
),
|
||||
'summary' =>
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Varchar',
|
||||
'blank' => false,
|
||||
'size' => 250,
|
||||
'verbose' => __('summary'),
|
||||
'help_text' => __('A one line description of the changes.'),
|
||||
),
|
||||
'filesize' =>
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Integer',
|
||||
'blank' => false,
|
||||
'default' => 0,
|
||||
'verbose' => __('file size in bytes'),
|
||||
),
|
||||
'fileext' =>
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Varchar',
|
||||
'blank' => false,
|
||||
'size' => 10,
|
||||
'verbose' => __('File extension'),
|
||||
'help_text' => __('The file extension of the uploaded resource.'),
|
||||
),
|
||||
'submitter' =>
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Foreignkey',
|
||||
'model' => 'Pluf_User',
|
||||
'blank' => false,
|
||||
'verbose' => __('submitter'),
|
||||
'relate_name' => 'submitted_downloads',
|
||||
),
|
||||
'pageusage' =>
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Manytomany',
|
||||
'model' => 'IDF_Wiki_PageRevision',
|
||||
'blank' => true,
|
||||
'verbose' => __('page usage'),
|
||||
'help_text' => 'Records on which pages this resource revision is used.',
|
||||
),
|
||||
'creation_dtime' =>
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Datetime',
|
||||
'blank' => true,
|
||||
'verbose' => __('creation date'),
|
||||
),
|
||||
);
|
||||
$table = $this->_con->pfx.'idf_wiki_pagerevision_idf_wiki_resourcerevision_assoc';
|
||||
$this->_a['views'] = array(
|
||||
'join_pagerevision' =>
|
||||
array(
|
||||
'join' => 'LEFT JOIN '.$table
|
||||
.' ON idf_wiki_resourcerevision_id=id',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
function __toString()
|
||||
{
|
||||
return sprintf(__('id %d: %s'), $this->id, $this->summary);
|
||||
}
|
||||
|
||||
function _toIndex()
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
function preDelete()
|
||||
{
|
||||
// if we kill off a head revision, ensure that we either mark a previous revision as head
|
||||
if ($this->is_head) {
|
||||
$sql = new Pluf_SQL('wikiresource=%s and id!=%s', array($this->wikiresource, $this->id));
|
||||
$revs = Pluf::factory('IDF_Wiki_ResourceRevision')->getList(array('filter'=>$sql->gen(), 'order'=>'id DESC'));
|
||||
if ($revs->count() > 0) {
|
||||
$previous = $revs[0];
|
||||
$previous->is_head = true;
|
||||
$previous->update();
|
||||
}
|
||||
}
|
||||
|
||||
@unlink($this->getFilePath());
|
||||
IDF_Timeline::remove($this);
|
||||
}
|
||||
|
||||
function preSave($create=false)
|
||||
{
|
||||
if ($this->id == '') {
|
||||
$this->creation_dtime = gmdate('Y-m-d H:i:s');
|
||||
$this->is_head = true;
|
||||
}
|
||||
}
|
||||
|
||||
function postSave($create=false)
|
||||
{
|
||||
$resource = $this->get_wikiresource();
|
||||
|
||||
if ($create) {
|
||||
$sql = new Pluf_SQL('wikiresource=%s', array($this->wikiresource));
|
||||
$rev = Pluf::factory('IDF_Wiki_ResourceRevision')->getList(array('filter'=>$sql->gen()));
|
||||
if ($rev->count() > 1) {
|
||||
IDF_Timeline::insert($this, $resource->get_project(), $this->get_submitter());
|
||||
foreach ($rev as $r) {
|
||||
if ($r->id != $this->id and $r->is_head) {
|
||||
$r->is_head = false;
|
||||
$r->update();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// update the modification timestamp
|
||||
$resource->update();
|
||||
}
|
||||
|
||||
function getFilePath()
|
||||
{
|
||||
return sprintf(Pluf::f('upload_path').'/'.$this->get_wikiresource()->get_project()->shortname.'/wiki/res/%d/%d.%s',
|
||||
$this->get_wikiresource()->id, $this->id, $this->fileext);
|
||||
}
|
||||
|
||||
function getViewURL()
|
||||
{
|
||||
$prj = $this->get_wikiresource()->get_project();
|
||||
$resource = $this->get_wikiresource();
|
||||
return Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::viewResource',
|
||||
array($prj->shortname, $resource->title),
|
||||
array('rev' => $this->id));
|
||||
}
|
||||
|
||||
function getRawURL($attachment = false)
|
||||
{
|
||||
$query = $attachment ? array('attachment' => 1) : array();
|
||||
$prj = $this->get_wikiresource()->get_project();
|
||||
return Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::rawResource',
|
||||
array($prj->shortname, $this->id),
|
||||
$query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the page revisions which contain references to this resource revision
|
||||
*/
|
||||
function getPageRevisions()
|
||||
{
|
||||
$db =& Pluf::db();
|
||||
$sql_results = $db->select(
|
||||
'SELECT idf_wiki_pagerevision_id as id '.
|
||||
'FROM '.Pluf::f('db_table_prefix', '').'idf_wiki_pagerevision_idf_wiki_resourcerevision_assoc '.
|
||||
'WHERE idf_wiki_resourcerevision_id='.$this->id
|
||||
);
|
||||
$ids = array(0);
|
||||
foreach ($sql_results as $id) {
|
||||
$ids[] = $id['id'];
|
||||
}
|
||||
$ids = implode (',', $ids);
|
||||
|
||||
$sql = new Pluf_SQL('id IN ('.$ids.')');
|
||||
return Pluf::factory('IDF_Wiki_PageRevision')
|
||||
->getList(array('filter' => $sql->gen()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the resource with the given view options, including a link to the resource' detail page
|
||||
*/
|
||||
function render($opts = array())
|
||||
{
|
||||
// give some reasonable defaults
|
||||
$opts = array_merge(array(
|
||||
'align' => 'left',
|
||||
'width' => '',
|
||||
'height' => '',
|
||||
'preview' => 'yes', // if possible
|
||||
'title' => '',
|
||||
), $opts);
|
||||
|
||||
$attrs = array('class="resource-container"');
|
||||
$styles = array();
|
||||
if (!empty($opts['align'])) {
|
||||
switch ($opts['align']) {
|
||||
case 'left':
|
||||
$styles[] = 'float: left';
|
||||
$styles[] = 'margin-right: 10px';
|
||||
break;
|
||||
case 'center':
|
||||
$styles[] = 'margin: 0 auto 0 auto';
|
||||
break;
|
||||
case 'right':
|
||||
$styles[] = 'float: right';
|
||||
$styles[] = 'margin-left: 10px';
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!empty($opts['width'])) {
|
||||
$styles[] = 'width:'.$opts['width'];
|
||||
}
|
||||
if (!empty($opts['height'])) {
|
||||
$styles[] = 'height:'.$opts['height'];
|
||||
}
|
||||
|
||||
$raw = $this->renderRaw();
|
||||
$viewUrl = $this->getViewURL();
|
||||
$download = '';
|
||||
$html = '<div class="resource-container" style="'.implode(';', $styles).'">';
|
||||
if ($opts['preview'] == 'yes' && !empty($raw)) {
|
||||
$html .= '<div class="preview">'.$raw.'</div>'."\n";
|
||||
} else {
|
||||
$rawUrl = $this->getRawURL(true);
|
||||
$download = '<a href="'.$rawUrl.'" class="download" title="'.sprintf(__('Download (%s)'), Pluf_Utils::prettySize($this->filesize)).'"></a>';
|
||||
}
|
||||
$resource = $this->get_wikiresource();
|
||||
$title = $opts['title'];
|
||||
if (empty($title)) {
|
||||
$title = $resource->title.' - '.$resource->mime_type.' - '.Pluf_Utils::prettySize($this->filesize);
|
||||
}
|
||||
$html .= '<div class="title">'.$download.'<a href="'.$viewUrl.'" title="'.__('View resource details').'">'.$title.'</a></div>'."\n";
|
||||
$html .= '</div>';
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a raw version of the resource, without any possibilities of formatting or the like
|
||||
*/
|
||||
function renderRaw()
|
||||
{
|
||||
$resource = $this->get_wikiresource();
|
||||
$url = $this->getRawURL();
|
||||
if (preg_match('#^image/(gif|jpeg|png|tiff)$#', $resource->mime_type)) {
|
||||
return sprintf('<img src="%s" alt="%s" />', $url, $resource->title);
|
||||
}
|
||||
|
||||
if (preg_match('#^text/(plain|xml|html|sgml|javascript|ecmascript|css)$#', $resource->mime_type)) {
|
||||
return sprintf('<iframe src="%s" alt="%s"></iframe>', $url, $resource->title);
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
|
||||
public function timelineFragment($request)
|
||||
{
|
||||
$resource = $this->get_wikiresource();
|
||||
$url = Pluf::f('url_base')
|
||||
.Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::viewResource',
|
||||
array($request->project->shortname,
|
||||
$resource->title),
|
||||
array('rev' => $this->id));
|
||||
|
||||
$out = "\n".'<tr class="log"><td><a href="'.$url.'">'.
|
||||
Pluf_esc(Pluf_Template_dateAgo($this->creation_dtime, 'without')).
|
||||
'</a></td><td>';
|
||||
$stag = new IDF_Template_ShowUser();
|
||||
$user = $stag->start($this->get_submitter(), $request, '', false);
|
||||
$out .= sprintf(__('<a href="%1$s" title="View resource">%2$s</a>, %3$s'), $url, Pluf_esc($resource->title), Pluf_esc($this->summary));
|
||||
$out .= '</td></tr>';
|
||||
$out .= "\n".'<tr class="extra"><td colspan="2">
|
||||
<div class="helptext right">'.sprintf(__('Change of <a href="%1$s">%2$s</a>, by %3$s'), $url, Pluf_esc($resource->title), $user).'</div></td></tr>';
|
||||
return Pluf_Template::markSafe($out);
|
||||
}
|
||||
|
||||
public function feedFragment($request)
|
||||
{
|
||||
$resource = $this->get_wikiresource();
|
||||
$url = Pluf::f('url_base')
|
||||
.Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::viewResource',
|
||||
array($request->project->shortname,
|
||||
$resource->title),
|
||||
array('rev' => $this->id));
|
||||
|
||||
$title = sprintf(__('%1$s: Documentation resource %2$s updated - %3$s'),
|
||||
$request->project->name,
|
||||
$resource->title, $resource->summary);
|
||||
$date = Pluf_Date::gmDateToGmString($this->creation_dtime);
|
||||
$context = new Pluf_Template_Context_Request(
|
||||
$request,
|
||||
array('url' => $url,
|
||||
'title' => $title,
|
||||
'resource' => $resource,
|
||||
'rev' => $this,
|
||||
'create' => false,
|
||||
'date' => $date)
|
||||
);
|
||||
$tmpl = new Pluf_Template('idf/wiki/feedfragment-resource.xml');
|
||||
return $tmpl->render($context);
|
||||
}
|
||||
}
|
@@ -119,7 +119,7 @@ $cfg['time_zone'] = 'Europe/Berlin';
|
||||
# Configure which languages should be available in your forge.
|
||||
# If you want to enable an additional language, ensure that the
|
||||
# language file in question resides in 'src/IDF/locale'.
|
||||
$cfg['languages'] = array('en', 'fr', 'de', 'es_ES');
|
||||
$cfg['languages'] = array('en', 'fr', 'de', 'es_ES', 'ru');
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------- #
|
||||
@@ -495,5 +495,53 @@ $cfg['idf_strong_key_check'] = false;
|
||||
# always have precedence.
|
||||
# $cfg['max_upload_size'] = 2097152; // Size in bytes
|
||||
|
||||
return $cfg;
|
||||
# If a download archive is uploaded, the size of the archive is limited to 20MB.
|
||||
# The php.ini upload_max_filesize and post_max_size configuration setting will
|
||||
# always have precedence.
|
||||
# $cfg['max_upload_archive_size'] = 20971520; // Size in bytes
|
||||
|
||||
# Older versions of Indefero submitted a POST request to a configured
|
||||
# post-commit web hook when new revisions arrived, whereas a PUT request
|
||||
# would have been more appropriate. Also, the payload's HMAC digest was
|
||||
# submitted as value of the HTTP header 'Post-Commit-Hook-Hmac' during
|
||||
# such a request. Since newer versions of Indefero use the same authentication
|
||||
# mechanism (based on the same secret key) for other web hooks of the same
|
||||
# project as well, the name of this HTTP header was no longer appropriate
|
||||
# and as such changed to simply 'Web-Hook-Hmac'.
|
||||
#
|
||||
# Setting the following configuration option to 'compat' now restores the
|
||||
# old behaviour in both cases. Please notice however that this compatibility
|
||||
# option is likely to go away in the next major version of Indefero, so you
|
||||
# should really change the other end of your web hooks!
|
||||
$cfg['webhook_processing'] = 'compat';
|
||||
|
||||
# If IDF recalculates the activity index of the forge's projects, it does so
|
||||
# by looking at the created and updated items in a particular tab / section
|
||||
# for each project.
|
||||
#
|
||||
# You can now edit the weights that are applied to the calculation for each
|
||||
# section in order to give other things more precendence. For example, if you
|
||||
# do not use the documentation part to a great extent in most of your projects,
|
||||
# you can weight this section lower and get an overall better activity value.
|
||||
#
|
||||
# If a section is removed, then activity in this section is neglected during
|
||||
# the calculation. The same is true in case a section is disabled in the
|
||||
# project administration.
|
||||
$cfg['activity_section_weights'] = array(
|
||||
'source' => 4,
|
||||
'issues' => 2,
|
||||
'wiki' => 2,
|
||||
'downloads' => 1,
|
||||
'review' => 1,
|
||||
);
|
||||
|
||||
# Here you can define the timespan in days how long the activity calculation
|
||||
# process should look into the history to get meaningful activity values for
|
||||
# each project.
|
||||
#
|
||||
# If you have many low-profile projects in your forge, i.e. projects that only
|
||||
# record very little activity, then it might be a good idea to bump this value
|
||||
# high enough to show a proper activity index for those projects as well.
|
||||
$cfg['activity_lookback'] = 7;
|
||||
|
||||
return $cfg;
|
@@ -29,6 +29,16 @@ $ctl[] = array('regex' => '#^/$#',
|
||||
'model' => 'IDF_Views',
|
||||
'method' => 'index');
|
||||
|
||||
$ctl[] = array('regex' => '#^/projects/$#',
|
||||
'base' => $base,
|
||||
'model' => 'IDF_Views',
|
||||
'method' => 'listProjects');
|
||||
|
||||
$ctl[] = array('regex' => '#^/projects/label/(\w+)/(\w+)/$#',
|
||||
'base' => $base,
|
||||
'model' => 'IDF_Views',
|
||||
'method' => 'listProjectsByLabel');
|
||||
|
||||
$ctl[] = array('regex' => '#^/login/$#',
|
||||
'base' => $base,
|
||||
'model' => 'IDF_Views',
|
||||
@@ -118,11 +128,26 @@ $ctl[] = array('regex' => '#^/p/([\-\w]+)/issues/$#',
|
||||
'model' => 'IDF_Views_Issue',
|
||||
'method' => 'index');
|
||||
|
||||
$ctl[] = array('regex' => '#^/p/([\-\w]+)/issues/summary/$#',
|
||||
'base' => $base,
|
||||
'model' => 'IDF_Views_Issue',
|
||||
'method' => 'summary');
|
||||
|
||||
$ctl[] = array('regex' => '#^/p/([\-\w]+)/issues/search/$#',
|
||||
'base' => $base,
|
||||
'model' => 'IDF_Views_Issue',
|
||||
'method' => 'search');
|
||||
|
||||
$ctl[] = array('regex' => '#^/p/([\-\w]+)/issues/search/status/(\w+)/$#',
|
||||
'base' => $base,
|
||||
'model' => 'IDF_Views_Issue',
|
||||
'method' => 'searchStatus');
|
||||
|
||||
$ctl[] = array('regex' => '#^/p/([\-\w]+)/issues/search/label/(\d+)/(\w+)/$#',
|
||||
'base' => $base,
|
||||
'model' => 'IDF_Views_Issue',
|
||||
'method' => 'searchLabel');
|
||||
|
||||
$ctl[] = array('regex' => '#^/p/([\-\w]+)/issues/(\d+)/$#',
|
||||
'base' => $base,
|
||||
'model' => 'IDF_Views_Issue',
|
||||
@@ -138,6 +163,11 @@ $ctl[] = array('regex' => '#^/p/([\-\w]+)/issues/status/(\w+)/$#',
|
||||
'model' => 'IDF_Views_Issue',
|
||||
'method' => 'listStatus');
|
||||
|
||||
$ctl[] = array('regex' => '#^/p/([\-\w]+)/issues/due/overdue/$#',
|
||||
'base' => $base,
|
||||
'model' => 'IDF_Views_Issue',
|
||||
'method' => 'listOverdue');
|
||||
|
||||
$ctl[] = array('regex' => '#^/p/([\-\w]+)/issues/label/(\d+)/(\w+)/$#',
|
||||
'base' => $base,
|
||||
'model' => 'IDF_Views_Issue',
|
||||
@@ -148,10 +178,10 @@ $ctl[] = array('regex' => '#^/p/([\-\w]+)/issues/create/$#',
|
||||
'model' => 'IDF_Views_Issue',
|
||||
'method' => 'create');
|
||||
|
||||
$ctl[] = array('regex' => '#^/p/([\-\w]+)/issues/my/(\w+)/$#',
|
||||
$ctl[] = array('regex' => '#^/p/([\-\w]+)/issues/(.*)/(\w+)/$#',
|
||||
'base' => $base,
|
||||
'model' => 'IDF_Views_Issue',
|
||||
'method' => 'myIssues');
|
||||
'method' => 'userIssues');
|
||||
|
||||
$ctl[] = array('regex' => '#^/p/([\-\w]+)/issues/attachment/(\d+)/(.*)$#',
|
||||
'base' => $base,
|
||||
@@ -173,7 +203,7 @@ $ctl[] = array('regex' => '#^/watchlist/(\w+)$#',
|
||||
'model' => 'IDF_Views_Issue',
|
||||
'method' => 'forgeWatchList');
|
||||
|
||||
$ctl[] = array('regex' => '#^/p/([\-\w]+)/issues/autocomplete/$#',
|
||||
$ctl[] = array('regex' => '#^/p/([\-\w]+)/issues/autocomplete/(\d*)$#',
|
||||
'base' => $base,
|
||||
'model' => 'IDF_Views_Issue',
|
||||
'method' => 'autoCompleteIssueList');
|
||||
@@ -240,17 +270,32 @@ $ctl[] = array('regex' => '#^/p/([\-\w]+)/source/changesrev/$#',
|
||||
'model' => 'IDF_Views_Source_Svn',
|
||||
'method' => 'changelogRev');
|
||||
|
||||
$ctl[] = array('regex' => '#^/p/([\-\w]+)/source/repo/(.*)$#',
|
||||
'base' => $base,
|
||||
'model' => 'IDF_Views_Source',
|
||||
'method' => 'repository');
|
||||
|
||||
// ---------- WIKI -----------------------------------------
|
||||
|
||||
$ctl[] = array('regex' => '#^/p/([\-\w]+)/doc/$#',
|
||||
'base' => $base,
|
||||
'model' => 'IDF_Views_Wiki',
|
||||
'method' => 'index');
|
||||
'method' => 'listPages');
|
||||
|
||||
$ctl[] = array('regex' => '#^/p/([\-\w]+)/res/$#',
|
||||
'base' => $base,
|
||||
'model' => 'IDF_Views_Wiki',
|
||||
'method' => 'listResources');
|
||||
|
||||
$ctl[] = array('regex' => '#^/p/([\-\w]+)/doc/create/$#',
|
||||
'base' => $base,
|
||||
'model' => 'IDF_Views_Wiki',
|
||||
'method' => 'create');
|
||||
'method' => 'createPage');
|
||||
|
||||
$ctl[] = array('regex' => '#^/p/([\-\w]+)/res/create/$#',
|
||||
'base' => $base,
|
||||
'model' => 'IDF_Views_Wiki',
|
||||
'method' => 'createResource');
|
||||
|
||||
$ctl[] = array('regex' => '#^/p/([\-\w]+)/doc/search/$#',
|
||||
'base' => $base,
|
||||
@@ -260,30 +305,60 @@ $ctl[] = array('regex' => '#^/p/([\-\w]+)/doc/search/$#',
|
||||
$ctl[] = array('regex' => '#^/p/([\-\w]+)/doc/label/(\d+)/$#',
|
||||
'base' => $base,
|
||||
'model' => 'IDF_Views_Wiki',
|
||||
'method' => 'listLabel');
|
||||
'method' => 'listPagesWithLabel');
|
||||
|
||||
$ctl[] = array('regex' => '#^/p/([\-\w]+)/doc/update/(.*)/$#',
|
||||
'base' => $base,
|
||||
'model' => 'IDF_Views_Wiki',
|
||||
'method' => 'update');
|
||||
'method' => 'updatePage');
|
||||
|
||||
$ctl[] = array('regex' => '#^/p/([\-\w]+)/res/update/(.*)/$#',
|
||||
'base' => $base,
|
||||
'model' => 'IDF_Views_Wiki',
|
||||
'method' => 'updateResource');
|
||||
|
||||
$ctl[] = array('regex' => '#^/p/([\-\w]+)/doc/delrev/(\d+)/$#',
|
||||
'base' => $base,
|
||||
'model' => 'IDF_Views_Wiki',
|
||||
'method' => 'deleteRev');
|
||||
'method' => 'deletePageRev');
|
||||
|
||||
$ctl[] = array('regex' => '#^/p/([\-\w]+)/res/delrev/(\d+)/$#',
|
||||
'base' => $base,
|
||||
'model' => 'IDF_Views_Wiki',
|
||||
'method' => 'deleteResourceRev');
|
||||
|
||||
$ctl[] = array('regex' => '#^/p/([\-\w]+)/doc/delete/(\d+)/$#',
|
||||
'base' => $base,
|
||||
'model' => 'IDF_Views_Wiki',
|
||||
'method' => 'delete');
|
||||
'method' => 'deletePage');
|
||||
|
||||
$ctl[] = array('regex' => '#^/p/([\-\w]+)/res/delete/(\d+)/$#',
|
||||
'base' => $base,
|
||||
'model' => 'IDF_Views_Wiki',
|
||||
'method' => 'deleteResource');
|
||||
|
||||
$ctl[] = array('regex' => '#^/p/([\-\w]+)/res/raw/(.*)/$#',
|
||||
'base' => $base,
|
||||
'model' => 'IDF_Views_Wiki',
|
||||
'method' => 'rawResource');
|
||||
|
||||
$ctl[] = array('regex' => '#^/p/([\-\w]+)/page/(.*)/$#',
|
||||
'base' => $base,
|
||||
'model' => 'IDF_Views_Wiki',
|
||||
'method' => 'view');
|
||||
'method' => 'viewPage');
|
||||
|
||||
$ctl[] = array('regex' => '#^/p/([\-\w]+)/resource/(.*)/$#',
|
||||
'base' => $base,
|
||||
'model' => 'IDF_Views_Wiki',
|
||||
'method' => 'viewResource');
|
||||
|
||||
// ---------- Downloads ------------------------------------
|
||||
|
||||
$ctl[] = array('regex' => '#^/help/archive-format/$#',
|
||||
'base' => $base,
|
||||
'model' => 'IDF_Views',
|
||||
'method' => 'faqArchiveFormat');
|
||||
|
||||
$ctl[] = array('regex' => '#^/p/([\-\w]+)/downloads/$#',
|
||||
'base' => $base,
|
||||
'model' => 'IDF_Views_Download',
|
||||
@@ -299,15 +374,25 @@ $ctl[] = array('regex' => '#^/p/([\-\w]+)/downloads/(\d+)/$#',
|
||||
'model' => 'IDF_Views_Download',
|
||||
'method' => 'view');
|
||||
|
||||
$ctl[] = array('regex' => '#^/p/([\-\w]+)/downloads/(\d+)/get/$#',
|
||||
$ctl[] = array('regex' => '#^/p/([\-\w]+)/downloads/get/(.+)$#',
|
||||
'base' => $base,
|
||||
'model' => 'IDF_Views_Download',
|
||||
'method' => 'download');
|
||||
|
||||
$ctl[] = array('regex' => '#^/p/([\-\w]+)/downloads/(\d+)/get/$#',
|
||||
'base' => $base,
|
||||
'model' => 'IDF_Views_Download',
|
||||
'method' => 'downloadById');
|
||||
|
||||
$ctl[] = array('regex' => '#^/p/([\-\w]+)/downloads/create/$#',
|
||||
'base' => $base,
|
||||
'model' => 'IDF_Views_Download',
|
||||
'method' => 'submit');
|
||||
'method' => 'create');
|
||||
|
||||
$ctl[] = array('regex' => '#^/p/([\-\w]+)/downloads/create/archive/$#',
|
||||
'base' => $base,
|
||||
'model' => 'IDF_Views_Download',
|
||||
'method' => 'createFromArchive');
|
||||
|
||||
$ctl[] = array('regex' => '#^/p/([\-\w]+)/downloads/(\d+)/delete/$#',
|
||||
'base' => $base,
|
||||
@@ -398,6 +483,11 @@ $ctl[] = array('regex' => '#^/api/$#',
|
||||
|
||||
// ---------- FORGE ADMIN --------------------------------
|
||||
|
||||
$ctl[] = array('regex' => '#^/admin/forge/$#',
|
||||
'base' => $base,
|
||||
'model' => 'IDF_Views_Admin',
|
||||
'method' => 'forge');
|
||||
|
||||
$ctl[] = array('regex' => '#^/admin/projects/$#',
|
||||
'base' => $base,
|
||||
'model' => 'IDF_Views_Admin',
|
||||
@@ -408,6 +498,11 @@ $ctl[] = array('regex' => '#^/admin/projects/(\d+)/$#',
|
||||
'model' => 'IDF_Views_Admin',
|
||||
'method' => 'projectUpdate');
|
||||
|
||||
$ctl[] = array('regex' => '#^/admin/projects/labels/$#',
|
||||
'base' => $base,
|
||||
'model' => 'IDF_Views_Admin',
|
||||
'method' => 'projectLabels');
|
||||
|
||||
$ctl[] = array('regex' => '#^/admin/projects/create/$#',
|
||||
'base' => $base,
|
||||
'model' => 'IDF_Views_Admin',
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user