Compare commits
275 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f88e529b85 | ||
|
|
f8b49c805a | ||
|
|
7ac254169c | ||
|
|
f95ff57db9 | ||
|
|
c2207452bd | ||
|
|
a28604ae4f | ||
|
|
4e53b7d178 | ||
|
|
2b7de0b7a4 | ||
|
|
f590b1c5f8 | ||
|
|
bae73f266c | ||
|
|
b00dbfaeaa | ||
|
|
e2e1e50890 | ||
|
|
423548dc2c | ||
|
|
545117eeae | ||
|
|
0b0392a274 | ||
|
|
0c0236c766 | ||
|
|
067d88136e | ||
|
|
d0cd0bb9bf | ||
|
|
69ecb1a049 | ||
|
|
e50f2f2b5f | ||
|
|
905a81a715 | ||
|
|
4d0d41ef02 | ||
|
|
15e7adaefb | ||
|
|
fea67af4df | ||
|
|
86832e4a36 | ||
|
|
c6ffc47c24 | ||
|
|
40881bb4e5 | ||
|
|
7cedd4af7a | ||
|
|
6832e45b1f | ||
|
|
e31f10e648 | ||
|
|
336faa4503 | ||
|
|
fbd1ebc294 | ||
|
|
7bcfb806b0 | ||
|
|
2908e28999 | ||
|
|
a188e1b275 | ||
|
|
52d76cd146 | ||
|
|
4eb34044ff | ||
|
|
ccb1bd33d4 | ||
|
|
ba14aec7a3 | ||
|
|
41fb1bf13c | ||
|
|
ddb05e68a3 | ||
|
|
b544792f36 | ||
|
|
a069661b4a | ||
|
|
07c94b97f9 | ||
|
|
116a7e34db | ||
|
|
f4fc2342d5 | ||
|
|
f7228ef2ec | ||
|
|
207eb1322d | ||
|
|
390c9b5048 | ||
|
|
58804923ef | ||
|
|
296091e977 | ||
|
|
77ba17eb61 | ||
|
|
a489cb15b5 | ||
|
|
b0bebb44e6 | ||
|
|
74baebde96 | ||
|
|
caac979263 | ||
|
|
bbc9bd6ef4 | ||
|
|
d445a65788 | ||
|
|
cdebac0b13 | ||
|
|
651617b8bf | ||
|
|
0e6eb9059a | ||
|
|
22d6453d56 | ||
|
|
a7b62a30ff | ||
|
|
d5e6d355ab | ||
|
|
431e199c1c | ||
|
|
3884bcdd01 | ||
|
|
fa974eb8dd | ||
|
|
023a3ce879 | ||
|
|
c67e61cbaa | ||
|
|
cdeefb43a5 | ||
|
|
93af6a68bd | ||
|
|
146e956432 | ||
|
|
afa91188d8 | ||
|
|
c2a9a60aa7 | ||
|
|
d654c95689 | ||
|
|
439f1fefe2 | ||
|
|
4245617c6f | ||
|
|
5635cdcac7 | ||
|
|
dd05a58c8c | ||
|
|
a437da6a4c | ||
|
|
0334e88625 | ||
|
|
05b081e6d4 | ||
|
|
c7c39c6fa1 | ||
|
|
0a7c2bca85 | ||
|
|
fd7a53a854 | ||
|
|
9ccbcea743 | ||
|
|
4b75a55639 | ||
|
|
90b9279c3a | ||
|
|
80be99890a | ||
|
|
d4929622bf | ||
|
|
7059150ac8 | ||
|
|
777937b70c | ||
|
|
b4b53d3e22 | ||
|
|
97bc383bc0 | ||
|
|
dbd088458e | ||
|
|
0739b856e0 | ||
|
|
81acb276d8 | ||
|
|
80e965a904 | ||
|
|
39c29dbe10 | ||
|
|
51c42a65c5 | ||
|
|
784c9718eb | ||
|
|
874b5aa7e9 | ||
|
|
bf28a24b72 | ||
|
|
dffeb1f9d5 | ||
|
|
6d7d7ebbfa | ||
|
|
5b41fe3167 | ||
|
|
28f36dc7b0 | ||
|
|
dc2881ad02 | ||
|
|
653299f4d8 | ||
|
|
704850f5c6 | ||
|
|
1548d4184e | ||
|
|
abc8b8f4ab | ||
|
|
08ef485ca9 | ||
|
|
35e670a1ab | ||
|
|
deb1ea4d2b | ||
|
|
3aac4d528a | ||
|
|
70e8d12420 | ||
|
|
ad15b13f7e | ||
|
|
b4bc6abace | ||
|
|
e6f255bc56 | ||
|
|
470a961a80 | ||
|
|
d08fee129b | ||
|
|
8993d2988b | ||
|
|
e776fc0713 | ||
|
|
c0ccdc768a | ||
|
|
039ae01cfa | ||
|
|
612d00ade2 | ||
|
|
fe001abd26 | ||
|
|
b800ffcc1f | ||
|
|
8a55952204 | ||
|
|
8b2363fd6f | ||
|
|
e7a0d9d497 | ||
|
|
c807c4b734 | ||
|
|
0af51d90ba | ||
|
|
972df3b231 | ||
|
|
1887e9effd | ||
|
|
7e10524f92 | ||
|
|
c3ff90c4f8 | ||
|
|
2c4f2d3037 | ||
|
|
07aec736f5 | ||
|
|
b30bdc9833 | ||
|
|
d171a249c5 | ||
|
|
d994e0efb0 | ||
|
|
0379b862ec | ||
|
|
5af2ab4d97 | ||
|
|
b518385962 | ||
|
|
d25bc74d71 | ||
|
|
5641173a04 | ||
|
|
806e69b858 | ||
|
|
a29a2a0fa4 | ||
|
|
4951498c0b | ||
|
|
97ea828532 | ||
|
|
d539eaf64b | ||
|
|
90edbf0d8b | ||
|
|
0c575ccc74 | ||
|
|
eebdc5ad12 | ||
|
|
617589f41b | ||
|
|
50638c768f | ||
|
|
29b8bf8a4e | ||
|
|
42936cc51d | ||
|
|
b138548a10 | ||
|
|
7d5ba6248e | ||
|
|
36a58dcae2 | ||
|
|
2106a5fbdc | ||
|
|
0897c8608f | ||
|
|
a32d6d8265 | ||
|
|
0f9f337e66 | ||
|
|
bb13722a2f | ||
|
|
77cdbefe0c | ||
|
|
f68bba1292 | ||
|
|
37d0ccc728 | ||
|
|
7557a73014 | ||
|
|
f3f00dd182 | ||
|
|
a2297decfd | ||
|
|
07b2b2f305 | ||
|
|
85df9e5ab2 | ||
|
|
439014b0b1 | ||
|
|
f6fc5ae466 | ||
|
|
132c4f6c89 | ||
|
|
21cdf60c31 | ||
|
|
b4f8cf8c50 | ||
|
|
f4dbabe8de | ||
|
|
c5c7ebff04 | ||
|
|
5d263e78e0 | ||
|
|
5f4e1da0c8 | ||
|
|
187365db76 | ||
|
|
e26a5c8cdf | ||
|
|
a384c60937 | ||
|
|
6b4abac08e | ||
|
|
e789263068 | ||
|
|
82aaf43d5d | ||
|
|
adae73080c | ||
|
|
b648e6f7a7 | ||
|
|
194dcad0e3 | ||
|
|
af3df142d4 | ||
|
|
f2a9518b5c | ||
|
|
593240b420 | ||
|
|
8aae0f29d4 | ||
|
|
14d07a22e2 | ||
|
|
3eb0247b37 | ||
|
|
7f32a5679d | ||
|
|
a442fd588e | ||
|
|
e7ce32fc26 | ||
|
|
f0a606e5cf | ||
|
|
0c4846c986 | ||
|
|
31e81118dd | ||
|
|
e47d51d14c | ||
|
|
ce436cc6ec | ||
|
|
3d1ac97dc3 | ||
|
|
7a2065c687 | ||
|
|
9e6c7dad88 | ||
|
|
4f23ea4dd5 | ||
|
|
f25dbd8872 | ||
|
|
de036920a4 | ||
|
|
061c806588 | ||
|
|
780267978d | ||
|
|
0ad7f47885 | ||
|
|
d2f0bac907 | ||
|
|
e46a6fa171 | ||
|
|
7cbc690890 | ||
|
|
a46fd28dae | ||
|
|
8decc383aa | ||
|
|
f3268b3d37 | ||
|
|
4fdf248cb6 | ||
|
|
1fd1e63043 | ||
|
|
5ce324f35f | ||
|
|
7303e9dd58 | ||
|
|
e57fc18bcb | ||
|
|
9a8148079d | ||
|
|
24762adecc | ||
|
|
2ee665ac96 | ||
|
|
2e1a91622e | ||
|
|
4687355351 | ||
|
|
38dd610319 | ||
|
|
a4408de74d | ||
|
|
7a952215aa | ||
|
|
982b330739 | ||
|
|
598451f470 | ||
|
|
3e9229be5f | ||
|
|
ad6148cae7 | ||
|
|
2aebc0e099 | ||
|
|
06022bf378 | ||
|
|
430c9cb00e | ||
|
|
89780b0317 | ||
|
|
c534894995 | ||
|
|
a91ce1600f | ||
|
|
145f6d0d1d | ||
|
|
04e7d7c99b | ||
|
|
7ed69c7f4a | ||
|
|
8f914c44a1 | ||
|
|
28ce82c6f6 | ||
|
|
641a3b24a5 | ||
|
|
0d61866b89 | ||
|
|
c1a477e7d0 | ||
|
|
692d2e53b2 | ||
|
|
47acc73451 | ||
|
|
2f22d48dd0 | ||
|
|
e0b0a732b4 | ||
|
|
59ad0f5b11 | ||
|
|
3b53ceedcd | ||
|
|
15a2bd90b3 | ||
|
|
c49a8204e0 | ||
|
|
601e894935 | ||
|
|
445c90fefe | ||
|
|
b7ced5fa69 | ||
|
|
995f1a13c3 | ||
|
|
cf22909722 | ||
|
|
02603fd8fd | ||
|
|
af4f5aaeb0 | ||
|
|
f8012c37d1 | ||
|
|
5954cd0ad1 | ||
|
|
5ef6e6c08f | ||
|
|
94a5464155 | ||
|
|
18ba8d0ac5 | ||
|
|
9fd4334dec |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,3 +1,4 @@
|
|||||||
|
*~
|
||||||
tmp
|
tmp
|
||||||
src/IDF/conf/idf.php
|
src/IDF/conf/idf.php
|
||||||
src/IDF/conf/idf.test.php
|
src/IDF/conf/idf.test.php
|
||||||
@@ -6,3 +7,5 @@ www/media/upload
|
|||||||
src/IDF/gettexttemplates
|
src/IDF/gettexttemplates
|
||||||
indefero-*.zip
|
indefero-*.zip
|
||||||
src/IDF/conf/path.php
|
src/IDF/conf/path.php
|
||||||
|
.tx/config
|
||||||
|
src/IDF/locale/idf.pot.bak
|
||||||
|
|||||||
2
AUTHORS
2
AUTHORS
@@ -23,6 +23,8 @@ Much appreciated contributors:
|
|||||||
Ludovic Bellière
|
Ludovic Bellière
|
||||||
Brian Armstrong
|
Brian Armstrong
|
||||||
Raphaël Emourgeon
|
Raphaël Emourgeon
|
||||||
|
Jakub Viták
|
||||||
|
Vladimir Solomatin
|
||||||
|
|
||||||
And all the nice users who spent time reporting issues and promoting
|
And all the nice users who spent time reporting issues and promoting
|
||||||
the project. The project could not live without them.
|
the project. The project could not live without them.
|
||||||
|
|||||||
115
CONTRIBUTE.mdtext
Normal file
115
CONTRIBUTE.mdtext
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
[Indefero][idf] is not only a software you can use either hosted for
|
||||||
|
you or hosted by you, but also a free software you can contribute to.
|
||||||
|
|
||||||
|
Here you will get how to contribute and what to contribute.
|
||||||
|
|
||||||
|
[idf]: http://www.indefero.net
|
||||||
|
|
||||||
|
# Quick Way on How to Contribute
|
||||||
|
|
||||||
|
Simple contribution:
|
||||||
|
|
||||||
|
1. Open a ticket with your idea. You can directly propose a patch if
|
||||||
|
you have it.
|
||||||
|
|
||||||
|
2. Wait for it to be checked by the devs or meet us on the #indefero
|
||||||
|
channel on [FreeNode][freenode].
|
||||||
|
|
||||||
|
Bigger contribution:
|
||||||
|
|
||||||
|
1. Fork Indefero where you want (fork from the develop branch).
|
||||||
|
|
||||||
|
2. Code your change and document it.
|
||||||
|
|
||||||
|
3. Open a ticket with a pull request and talk about it on IRC.
|
||||||
|
|
||||||
|
# The General Contribution Workflow for Regular Contributors
|
||||||
|
|
||||||
|
1. Fork Indefero from the **develop** branch.
|
||||||
|
2. Request a pull request if you do not have write access on the repository.
|
||||||
|
3. Merge your changes without fast forward in develop. This keeps track of
|
||||||
|
the history of the changes and makes understanding what is going on easy.
|
||||||
|
4. Merge your changes with fast forward **only if a single commit**.
|
||||||
|
|
||||||
|
Indefero is composed of two main branches:
|
||||||
|
|
||||||
|
1. **master**: this is the shipped branch, only a select number of people
|
||||||
|
can push into it.
|
||||||
|
2. **develop**: this is the development branch, all the people having write
|
||||||
|
access to the repository are welcomed to push in.
|
||||||
|
|
||||||
|
**Note:** The branching model we use is [explained in details here][bmi]. You
|
||||||
|
**must** understand it to really contribute to the code base in an
|
||||||
|
efficient way.
|
||||||
|
|
||||||
|
[bmi]: http://nvie.com/git-model "A successful Git branching model"
|
||||||
|
|
||||||
|
# What to Contribute
|
||||||
|
|
||||||
|
Contribution is easy, you can contribute in a lot of different fields,
|
||||||
|
contributions small or big are always appreciated. Here is an example
|
||||||
|
list of what you can do:
|
||||||
|
|
||||||
|
- Install InDefero on your system and report the problem you had.
|
||||||
|
- Find the bad English and typos and propose corrections.
|
||||||
|
- Help with the translation effort.
|
||||||
|
- Find little bugs or usability problems and provide ideas on how to fix them.
|
||||||
|
- Register to the [discussion group][group] and help new users.
|
||||||
|
- Come and chat on IRC #indefero on the [FreeNode][freenode] servers.
|
||||||
|
- Find ways to improve the design while keeping it **beautifully simple**.
|
||||||
|
- Write a blog post about the project, what you think is good or bad.
|
||||||
|
- Translate InDefero for the sake of the community.
|
||||||
|
- Or maybe really hack into the code.
|
||||||
|
|
||||||
|
As you can see, the real hacking into the code is just a small part of the work, so even if you are not a coder you can do a lot.
|
||||||
|
|
||||||
|
[group]: http://groups.google.com/group/indefero-users
|
||||||
|
[freenode]: http://freenode.net/
|
||||||
|
|
||||||
|
## I am a simple user
|
||||||
|
|
||||||
|
Thanks a lot! Really! As a project leader, I consider **you** as
|
||||||
|
**the most important person in the success of the project**. So do not
|
||||||
|
worry, I will really listen to your needs and make you love this
|
||||||
|
project.
|
||||||
|
|
||||||
|
What you can do to help:
|
||||||
|
|
||||||
|
- Use the software and each time you find something a bit annoying in your daily use, report a bug. Usability issues are high priority issues.
|
||||||
|
- Find typos, grammar mistakes, etc. and report a bug.
|
||||||
|
- Write about InDefero on your blog/website.
|
||||||
|
- Read the issues submitted by the users and provide answers if you have them.
|
||||||
|
- ...
|
||||||
|
|
||||||
|
## I am a designer
|
||||||
|
|
||||||
|
A lot of things to do for you:
|
||||||
|
|
||||||
|
- Check the design and find the flaws in it. Is the space well used, does it look really nice and is it also functional for the first users?
|
||||||
|
- Do we have good support of all the major browsers?
|
||||||
|
- ...
|
||||||
|
|
||||||
|
## I am a coder
|
||||||
|
|
||||||
|
Checkout the code and have fun, but keep in mind that your results
|
||||||
|
must be simple to use. Do not worry about the beautiful part, the
|
||||||
|
designers can work on that.
|
||||||
|
|
||||||
|
## I am a security guy
|
||||||
|
|
||||||
|
Please, do try to break it, if you find a problem, come on IRC or
|
||||||
|
contact the developers to get the issue fixed as soon as
|
||||||
|
possible. Please, be nice, do not release the issue in the wild
|
||||||
|
without first talking to us.
|
||||||
|
|
||||||
|
## I am a translator
|
||||||
|
|
||||||
|
We currently use (transifex)[http://trac.transifex.org] to help our
|
||||||
|
users to translate indefero. You don't have to use it, but it's an
|
||||||
|
easy way to do the job. You can visit the indefero page at transifex
|
||||||
|
here : http://www.transifex.net/projects/p/indefero/c/indefero/
|
||||||
|
|
||||||
|
Please understand that your changes will not be commited instantly,
|
||||||
|
but are sent to the maintainers e-mails before. Then, your changes
|
||||||
|
will not be in the main repository until da-loic push the changes. In
|
||||||
|
that way, try to do big changes with less submissions.
|
||||||
@@ -213,3 +213,13 @@ may have problems as your certificate is not trusted, check the
|
|||||||
[procedure provided here][svnfix] to solve the problem.
|
[procedure provided here][svnfix] to solve the problem.
|
||||||
|
|
||||||
[svnfix]: http://projects.ceondo.com/p/indefero/issues/319/#ic1358
|
[svnfix]: http://projects.ceondo.com/p/indefero/issues/319/#ic1358
|
||||||
|
|
||||||
|
## If the registration links are not working
|
||||||
|
|
||||||
|
If You have standard instalaction of PHP ie in Debian, php.ini sets
|
||||||
|
mbstring.func_overload to value "2" for overloading str*
|
||||||
|
functions. You need to prevent the overload as it does not make sense
|
||||||
|
anyway (magic in the background is bad!).
|
||||||
|
See the [corresponding ticket][reglink].
|
||||||
|
|
||||||
|
[reglink]: http://projects.ceondo.com/p/indefero/issues/481/
|
||||||
110
Makefile
Normal file
110
Makefile
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
# ***** BEGIN LICENSE BLOCK *****
|
||||||
|
# This file is part of InDefero, an open source project management application.
|
||||||
|
# Copyright (C) 2010 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 ***** */
|
||||||
|
|
||||||
|
# Installation of external tools : transifex-client
|
||||||
|
# sudo apt-get install python-setuptools
|
||||||
|
# sudo easy_install -U transifex-client
|
||||||
|
|
||||||
|
PLUF_PATH=$(shell php -r "require_once('src/IDF/conf/path.php'); echo PLUF_PATH;")
|
||||||
|
|
||||||
|
all help:
|
||||||
|
@echo "Rules for generate tarball :"
|
||||||
|
@for b in `git branch | sed "s/^. //g"`; do \
|
||||||
|
echo -e "\t"$$b"_tarball - Generate a zip archive of the "$$b" branch."; \
|
||||||
|
done
|
||||||
|
@echo -e "\nRules for internationnalization :";
|
||||||
|
@echo -e "\tpot-update - Update the POT file from HTML template and PHP source, then merge it with PO file"
|
||||||
|
@echo -e "\tpot-push - Send the POT file on transifex server"
|
||||||
|
@echo -e "\tpo-update - Merge POT file into PO file. POT is not regenerated."
|
||||||
|
@echo -e "\tpo-push - Send the all PO file on transifex server"
|
||||||
|
@echo -e "\tpo-pull - Get all PO file from transifex server"
|
||||||
|
|
||||||
|
#
|
||||||
|
# Internationnalization rule, POT & PO file manipulation
|
||||||
|
#
|
||||||
|
.PHONY: pot-update po-update
|
||||||
|
pot-update:
|
||||||
|
# Backup pot file
|
||||||
|
@if [ -e src/IDF/locale/idf.pot ]; then \
|
||||||
|
mv -f src/IDF/locale/idf.pot src/IDF/locale/idf.pot.bak; \
|
||||||
|
fi
|
||||||
|
touch src/IDF/locale/idf.pot;
|
||||||
|
# Extract string
|
||||||
|
@cd src; php $(PLUF_PATH)/extracttemplates.php IDF/conf/idf.php IDF/gettexttemplates
|
||||||
|
@cd src; for phpfile in `find . -iname "*.php"`; do \
|
||||||
|
echo "Parsing file : "$$phpfile; \
|
||||||
|
xgettext -o idf.pot -p ./IDF/locale/ --from-code=UTF-8 -j --keyword --keyword=__ --keyword=_n:1,2 -L PHP $$phpfile ; \
|
||||||
|
done
|
||||||
|
# Remove tmp folder
|
||||||
|
rm -Rf src/IDF/gettexttemplates
|
||||||
|
# Update PO
|
||||||
|
@make po-update
|
||||||
|
|
||||||
|
po-update:
|
||||||
|
@for pofile in `ls src/IDF/locale/*/idf.po`; do \
|
||||||
|
echo "Updating file : "$$pofile; \
|
||||||
|
msgmerge -v -U $$pofile src/IDF/locale/idf.pot; \
|
||||||
|
echo ; \
|
||||||
|
done
|
||||||
|
|
||||||
|
#
|
||||||
|
# Transifex
|
||||||
|
#
|
||||||
|
.PHONY: check-tx-config
|
||||||
|
check-tx-config:
|
||||||
|
@if [ ! -e .tx/config ]; then \
|
||||||
|
mkdir -p .tx; \
|
||||||
|
touch .tx/config; \
|
||||||
|
echo "[main]" >> .tx/config; \
|
||||||
|
echo "host = http://www.transifex.net" >> .tx/config; \
|
||||||
|
echo "" >> .tx/config; \
|
||||||
|
echo "[indefero.idfpot]" >> .tx/config; \
|
||||||
|
echo "file_filter = src/IDF/locale/<lang>/idf.po" >> .tx/config; \
|
||||||
|
echo "source_file = src/IDF/locale/idf.pot" >> .tx/config; \
|
||||||
|
echo "source_lang = en" >> .tx/config; \
|
||||||
|
fi
|
||||||
|
@if [ ! -e $(HOME)/.transifexrc ]; then \
|
||||||
|
touch $(HOME)/.transifexrc; \
|
||||||
|
echo "[http://www.transifex.net]" >> $(HOME)/.transifexrc; \
|
||||||
|
echo "username = " >> $(HOME)/.transifexrc; \
|
||||||
|
echo "token = " >> $(HOME)/.transifexrc; \
|
||||||
|
echo "password = " >> $(HOME)/.transifexrc; \
|
||||||
|
echo "hostname = http://www.transifex.net" >> $(HOME)/.transifexrc; \
|
||||||
|
echo "You must edit the file ~/.transifexrc to setup your transifex account (login & password) !"; \
|
||||||
|
exit 1; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
pot-push: check-tx-config
|
||||||
|
@tx push -s
|
||||||
|
|
||||||
|
po-push: check-tx-config
|
||||||
|
@tx push -t
|
||||||
|
|
||||||
|
po-pull: check-tx-config
|
||||||
|
@tx pull -a
|
||||||
|
|
||||||
|
#
|
||||||
|
# Generic rule to build a tarball of indefero for a specified branch
|
||||||
|
# ex: make master_tarball
|
||||||
|
# make dev_tarball
|
||||||
|
#
|
||||||
|
%_tarball:
|
||||||
|
@git archive --format=zip --prefix="indefero/" $(@:_tarball=) > indefero-$(@:_tarball=)-`git log $(@:_tarball=) -n 1 --pretty=format:%H`.zip
|
||||||
|
|
||||||
20
contrib/zipstream-php-0.2.2/COPYING
Normal file
20
contrib/zipstream-php-0.2.2/COPYING
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
Copyright (C) 2007-2009 Paul Duncan <pabs@pablotron.org>
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
|
copy of this software and associated documentation files (the
|
||||||
|
"Software"), to deal in the Software without restriction, including
|
||||||
|
without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included
|
||||||
|
in all copies or substantial portions of the of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||||
|
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||||
|
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
DEALINGS IN THE SOFTWARE.
|
||||||
42
contrib/zipstream-php-0.2.2/README
Normal file
42
contrib/zipstream-php-0.2.2/README
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
ZipStream 0.2.2 README
|
||||||
|
======================
|
||||||
|
|
||||||
|
Please see the file COPYING for licensing and warranty information. The
|
||||||
|
latest version of this software is available at the following URL:
|
||||||
|
|
||||||
|
http://pablotron.org/software/zipstream-php/
|
||||||
|
|
||||||
|
Overview
|
||||||
|
========
|
||||||
|
A fast and simple streaming zip file downloader for PHP. Here's a
|
||||||
|
simple example:
|
||||||
|
|
||||||
|
# create a new zipstream object
|
||||||
|
$zip = new ZipStream('example.zip');
|
||||||
|
|
||||||
|
# create a file named 'hello.txt'
|
||||||
|
$zip->add_file('some_image.jpg', 'This is the contents of hello.txt');
|
||||||
|
|
||||||
|
# add a file named 'image.jpg' from a local file 'path/to/image.jpg'
|
||||||
|
$zip->add_file_from_path('some_image.jpg', 'path/to/image.jpg');
|
||||||
|
|
||||||
|
# finish the zip stream
|
||||||
|
$zip->finish();
|
||||||
|
|
||||||
|
You can also add comments, modify file timestamps, and customize (or
|
||||||
|
disable) the HTTP headers. See the class file for details. There are a
|
||||||
|
couple of additional examples in the initial release announcement at the
|
||||||
|
following URL:
|
||||||
|
|
||||||
|
http://pablotron.org/?cid=1535
|
||||||
|
|
||||||
|
Requirements
|
||||||
|
============
|
||||||
|
|
||||||
|
* PHP version 5.1.2 or newer (specifically, the hash_init and
|
||||||
|
hash_file functions).
|
||||||
|
|
||||||
|
About the Author
|
||||||
|
================
|
||||||
|
Paul Duncan <pabs@pablotron.org>
|
||||||
|
http://pablotron.org/
|
||||||
2
contrib/zipstream-php-0.2.2/extras/README
Normal file
2
contrib/zipstream-php-0.2.2/extras/README
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
Based on PKZIP appnotes, which are included here.
|
||||||
|
|
||||||
3071
contrib/zipstream-php-0.2.2/extras/zip-appnote-6.3.1-20070411.txt
Normal file
3071
contrib/zipstream-php-0.2.2/extras/zip-appnote-6.3.1-20070411.txt
Normal file
File diff suppressed because it is too large
Load Diff
52
contrib/zipstream-php-0.2.2/test/index.php
Normal file
52
contrib/zipstream-php-0.2.2/test/index.php
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
# load zipstream class
|
||||||
|
require '../zipstream.php';
|
||||||
|
|
||||||
|
# get path to current file
|
||||||
|
$pwd = dirname(__FILE__);
|
||||||
|
|
||||||
|
# add some random files
|
||||||
|
$files = array(
|
||||||
|
'../extras/zip-appnote-6.3.1-20070411.txt',
|
||||||
|
'../zipstream.php',
|
||||||
|
);
|
||||||
|
|
||||||
|
# create new zip stream object
|
||||||
|
$zip = new ZipStream('test.zip', array(
|
||||||
|
'comment' => 'this is a zip file comment. hello?'
|
||||||
|
));
|
||||||
|
|
||||||
|
# common file options
|
||||||
|
$file_opt = array(
|
||||||
|
# file creation time (2 hours ago)
|
||||||
|
'time' => time() - 2 * 3600,
|
||||||
|
|
||||||
|
# file comment
|
||||||
|
'comment' => 'this is a file comment. hi!',
|
||||||
|
);
|
||||||
|
|
||||||
|
# add files under folder 'asdf'
|
||||||
|
foreach ($files as $file) {
|
||||||
|
# build absolute path and get file data
|
||||||
|
$path = ($file[0] == '/') ? $file : "$pwd/$file";
|
||||||
|
$data = file_get_contents($path);
|
||||||
|
|
||||||
|
# add file to archive
|
||||||
|
$zip->add_file('asdf/' . basename($file), $data, $file_opt);
|
||||||
|
}
|
||||||
|
|
||||||
|
# add same files again wihtout a folder
|
||||||
|
foreach ($files as $file) {
|
||||||
|
# build absolute path and get file data
|
||||||
|
$path = ($file[0] == '/') ? $file : "$pwd/$file";
|
||||||
|
$data = file_get_contents($path);
|
||||||
|
|
||||||
|
# add file to archive
|
||||||
|
$zip->add_file(basename($file), $data, $file_opt);
|
||||||
|
}
|
||||||
|
|
||||||
|
# finish archive
|
||||||
|
$zip->finish();
|
||||||
|
|
||||||
|
?>
|
||||||
580
contrib/zipstream-php-0.2.2/zipstream.php
Normal file
580
contrib/zipstream-php-0.2.2/zipstream.php
Normal file
@@ -0,0 +1,580 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
##########################################################################
|
||||||
|
# ZipStream - Streamed, dynamically generated zip archives. #
|
||||||
|
# by Paul Duncan <pabs@pablotron.org> #
|
||||||
|
# #
|
||||||
|
# Copyright (C) 2007-2009 Paul Duncan <pabs@pablotron.org> #
|
||||||
|
# #
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining #
|
||||||
|
# a copy of this software and associated documentation files (the #
|
||||||
|
# "Software"), to deal in the Software without restriction, including #
|
||||||
|
# without limitation the rights to use, copy, modify, merge, publish, #
|
||||||
|
# distribute, sublicense, and/or sell copies of the Software, and to #
|
||||||
|
# permit persons to whom the Software is furnished to do so, subject to #
|
||||||
|
# the following conditions: #
|
||||||
|
# #
|
||||||
|
# The above copyright notice and this permission notice shall be #
|
||||||
|
# included in all copies or substantial portions of the of the Software. #
|
||||||
|
# #
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, #
|
||||||
|
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF #
|
||||||
|
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. #
|
||||||
|
# IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR #
|
||||||
|
# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, #
|
||||||
|
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR #
|
||||||
|
# OTHER DEALINGS IN THE SOFTWARE. #
|
||||||
|
##########################################################################
|
||||||
|
|
||||||
|
#
|
||||||
|
# ZipStream - Streamed, dynamically generated zip archives.
|
||||||
|
# by Paul Duncan <pabs@pablotron.org>
|
||||||
|
#
|
||||||
|
# Requirements:
|
||||||
|
#
|
||||||
|
# * PHP version 5.1.2 or newer.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
#
|
||||||
|
# Streaming zip archives is a simple, three-step process:
|
||||||
|
#
|
||||||
|
# 1. Create the zip stream:
|
||||||
|
#
|
||||||
|
# $zip = new ZipStream('example.zip');
|
||||||
|
#
|
||||||
|
# 2. Add one or more files to the archive:
|
||||||
|
#
|
||||||
|
# # add first file
|
||||||
|
# $data = file_get_contents('some_file.gif');
|
||||||
|
# $zip->add_file('some_file.gif', $data);
|
||||||
|
#
|
||||||
|
# # add second file
|
||||||
|
# $data = file_get_contents('some_file.gif');
|
||||||
|
# $zip->add_file('another_file.png', $data);
|
||||||
|
#
|
||||||
|
# 3. Finish the zip stream:
|
||||||
|
#
|
||||||
|
# $zip->finish();
|
||||||
|
#
|
||||||
|
# You can also add an archive comment, add comments to individual files,
|
||||||
|
# and adjust the timestamp of files. See the API documentation for each
|
||||||
|
# method below for additional information.
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
#
|
||||||
|
# # create a new zip stream object
|
||||||
|
# $zip = new ZipStream('some_files.zip');
|
||||||
|
#
|
||||||
|
# # list of local files
|
||||||
|
# $files = array('foo.txt', 'bar.jpg');
|
||||||
|
#
|
||||||
|
# # read and add each file to the archive
|
||||||
|
# foreach ($files as $path)
|
||||||
|
# $zip->add_file($path, file_get_contents($path));
|
||||||
|
#
|
||||||
|
# # write archive footer to stream
|
||||||
|
# $zip->finish();
|
||||||
|
#
|
||||||
|
class ZipStream {
|
||||||
|
const VERSION = '0.2.2';
|
||||||
|
|
||||||
|
var $opt = array(),
|
||||||
|
$files = array(),
|
||||||
|
$cdr_ofs = 0,
|
||||||
|
$need_headers = false,
|
||||||
|
$ofs = 0;
|
||||||
|
|
||||||
|
#
|
||||||
|
# Create a new ZipStream object.
|
||||||
|
#
|
||||||
|
# Parameters:
|
||||||
|
#
|
||||||
|
# $name - Name of output file (optional).
|
||||||
|
# $opt - Hash of archive options (optional, see "Archive Options"
|
||||||
|
# below).
|
||||||
|
#
|
||||||
|
# Archive Options:
|
||||||
|
#
|
||||||
|
# comment - Comment for this archive.
|
||||||
|
# content_type - HTTP Content-Type. Defaults to 'application/x-zip'.
|
||||||
|
# content_disposition - HTTP Content-Disposition. Defaults to
|
||||||
|
# 'attachment; filename=\"FILENAME\"', where
|
||||||
|
# FILENAME is the specified filename.
|
||||||
|
# large_file_size - Size, in bytes, of the largest file to try
|
||||||
|
# and load into memory (used by
|
||||||
|
# add_file_from_path()). Large files may also
|
||||||
|
# be compressed differently; see the
|
||||||
|
# 'large_file_method' option.
|
||||||
|
# large_file_method - How to handle large files. Legal values are
|
||||||
|
# 'store' (the default), or 'deflate'. Store
|
||||||
|
# sends the file raw and is significantly
|
||||||
|
# faster, while 'deflate' compresses the file
|
||||||
|
# and is much, much slower. Note that deflate
|
||||||
|
# must compress the file twice and extremely
|
||||||
|
# slow.
|
||||||
|
# send_http_headers - Boolean indicating whether or not to send
|
||||||
|
# the HTTP headers for this file.
|
||||||
|
#
|
||||||
|
# Note that content_type and content_disposition do nothing if you are
|
||||||
|
# not sending HTTP headers.
|
||||||
|
#
|
||||||
|
# Large File Support:
|
||||||
|
#
|
||||||
|
# By default, the method add_file_from_path() will send send files
|
||||||
|
# larger than 20 megabytes along raw rather than attempting to
|
||||||
|
# compress them. You can change both the maximum size and the
|
||||||
|
# compression behavior using the large_file_* options above, with the
|
||||||
|
# following caveats:
|
||||||
|
#
|
||||||
|
# * For "small" files (e.g. files smaller than large_file_size), the
|
||||||
|
# memory use can be up to twice that of the actual file. In other
|
||||||
|
# words, adding a 10 megabyte file to the archive could potentially
|
||||||
|
# occupty 20 megabytes of memory.
|
||||||
|
#
|
||||||
|
# * Enabling compression on large files (e.g. files larger than
|
||||||
|
# large_file_size) is extremely slow, because ZipStream has to pass
|
||||||
|
# over the large file once to calculate header information, and then
|
||||||
|
# again to compress and send the actual data.
|
||||||
|
#
|
||||||
|
# Examples:
|
||||||
|
#
|
||||||
|
# # create a new zip file named 'foo.zip'
|
||||||
|
# $zip = new ZipStream('foo.zip');
|
||||||
|
#
|
||||||
|
# # create a new zip file named 'bar.zip' with a comment
|
||||||
|
# $zip = new ZipStream('bar.zip', array(
|
||||||
|
# 'comment' => 'this is a comment for the zip file.',
|
||||||
|
# ));
|
||||||
|
#
|
||||||
|
# Notes:
|
||||||
|
#
|
||||||
|
# If you do not set a filename, then this library _DOES NOT_ send HTTP
|
||||||
|
# headers by default. This behavior is to allow software to send its
|
||||||
|
# own headers (including the filename), and still use this library.
|
||||||
|
#
|
||||||
|
function __construct($name = null, $opt = array()) {
|
||||||
|
# save options
|
||||||
|
$this->opt = $opt;
|
||||||
|
|
||||||
|
# set large file defaults: size = 20 megabytes, method = store
|
||||||
|
if (!isset($this->opt['large_file_size']))
|
||||||
|
$this->opt['large_file_size'] = 20 * 1024 * 1024;
|
||||||
|
if (!isset($this->opt['large_file_method']))
|
||||||
|
$this->opt['large_file_method'] = 'store';
|
||||||
|
|
||||||
|
$this->output_name = $name;
|
||||||
|
if ($name || (isset($opt['send_http_headers'])
|
||||||
|
&& $opt['send_http_headers']))
|
||||||
|
$this->need_headers = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# add_file - add a file to the archive
|
||||||
|
#
|
||||||
|
# Parameters:
|
||||||
|
#
|
||||||
|
# $name - path of file in archive (including directory).
|
||||||
|
# $data - contents of file
|
||||||
|
# $opt - Hash of options for file (optional, see "File Options"
|
||||||
|
# below).
|
||||||
|
#
|
||||||
|
# File Options:
|
||||||
|
# time - Last-modified timestamp (seconds since the epoch) of
|
||||||
|
# this file. Defaults to the current time.
|
||||||
|
# comment - Comment related to this file.
|
||||||
|
#
|
||||||
|
# Examples:
|
||||||
|
#
|
||||||
|
# # add a file named 'foo.txt'
|
||||||
|
# $data = file_get_contents('foo.txt');
|
||||||
|
# $zip->add_file('foo.txt', $data);
|
||||||
|
#
|
||||||
|
# # add a file named 'bar.jpg' with a comment and a last-modified
|
||||||
|
# # time of two hours ago
|
||||||
|
# $data = file_get_contents('bar.jpg');
|
||||||
|
# $zip->add_file('bar.jpg', $data, array(
|
||||||
|
# 'time' => time() - 2 * 3600,
|
||||||
|
# 'comment' => 'this is a comment about bar.jpg',
|
||||||
|
# ));
|
||||||
|
#
|
||||||
|
function add_file($name, $data, $opt = array()) {
|
||||||
|
# compress data
|
||||||
|
$zdata = gzdeflate($data);
|
||||||
|
|
||||||
|
# calculate header attributes
|
||||||
|
$crc = crc32($data);
|
||||||
|
$zlen = strlen($zdata);
|
||||||
|
$len = strlen($data);
|
||||||
|
$meth = 0x08;
|
||||||
|
|
||||||
|
# send file header
|
||||||
|
$this->add_file_header($name, $opt, $meth, $crc, $zlen, $len);
|
||||||
|
|
||||||
|
# print data
|
||||||
|
$this->send($zdata);
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# add_file_from_path - add a file at path to the archive.
|
||||||
|
#
|
||||||
|
# Note that large files may be compresed differently than smaller
|
||||||
|
# files; see the "Large File Support" section above for more
|
||||||
|
# information.
|
||||||
|
#
|
||||||
|
# Parameters:
|
||||||
|
#
|
||||||
|
# $name - name of file in archive (including directory path).
|
||||||
|
# $path - path to file on disk (note: paths should be encoded using
|
||||||
|
# UNIX-style forward slashes -- e.g '/path/to/some/file').
|
||||||
|
# $opt - Hash of options for file (optional, see "File Options"
|
||||||
|
# below).
|
||||||
|
#
|
||||||
|
# File Options:
|
||||||
|
# time - Last-modified timestamp (seconds since the epoch) of
|
||||||
|
# this file. Defaults to the current time.
|
||||||
|
# comment - Comment related to this file.
|
||||||
|
#
|
||||||
|
# Examples:
|
||||||
|
#
|
||||||
|
# # add a file named 'foo.txt' from the local file '/tmp/foo.txt'
|
||||||
|
# $zip->add_file_from_path('foo.txt', '/tmp/foo.txt');
|
||||||
|
#
|
||||||
|
# # add a file named 'bigfile.rar' from the local file
|
||||||
|
# # '/usr/share/bigfile.rar' with a comment and a last-modified
|
||||||
|
# # time of two hours ago
|
||||||
|
# $path = '/usr/share/bigfile.rar';
|
||||||
|
# $zip->add_file_from_path('bigfile.rar', $path, array(
|
||||||
|
# 'time' => time() - 2 * 3600,
|
||||||
|
# 'comment' => 'this is a comment about bar.jpg',
|
||||||
|
# ));
|
||||||
|
#
|
||||||
|
function add_file_from_path($name, $path, $opt = array()) {
|
||||||
|
if ($this->is_large_file($path)) {
|
||||||
|
# file is too large to be read into memory; add progressively
|
||||||
|
$this->add_large_file($name, $path, $opt);
|
||||||
|
} else {
|
||||||
|
# file is small enough to read into memory; read file contents and
|
||||||
|
# handle with add_file()
|
||||||
|
$data = file_get_contents($path);
|
||||||
|
$this->add_file($name, $data, $opt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# finish - Write zip footer to stream.
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
#
|
||||||
|
# # add a list of files to the archive
|
||||||
|
# $files = array('foo.txt', 'bar.jpg');
|
||||||
|
# foreach ($files as $path)
|
||||||
|
# $zip->add_file($path, file_get_contents($path));
|
||||||
|
#
|
||||||
|
# # write footer to stream
|
||||||
|
# $zip->finish();
|
||||||
|
#
|
||||||
|
function finish() {
|
||||||
|
# add trailing cdr record
|
||||||
|
$this->add_cdr($this->opt);
|
||||||
|
$this->clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
###################
|
||||||
|
# PRIVATE METHODS #
|
||||||
|
###################
|
||||||
|
|
||||||
|
#
|
||||||
|
# Create and send zip header for this file.
|
||||||
|
#
|
||||||
|
private function add_file_header($name, $opt, $meth, $crc, $zlen, $len) {
|
||||||
|
# strip leading slashes from file name
|
||||||
|
# (fixes bug in windows archive viewer)
|
||||||
|
$name = preg_replace('/^\\/+/', '', $name);
|
||||||
|
|
||||||
|
# calculate name length
|
||||||
|
$nlen = strlen($name);
|
||||||
|
|
||||||
|
# create dos timestamp
|
||||||
|
$opt['time'] = isset($opt['time']) ? $opt['time'] : time();
|
||||||
|
$dts = $this->dostime($opt['time']);
|
||||||
|
|
||||||
|
# build file header
|
||||||
|
$fields = array( # (from V.A of APPNOTE.TXT)
|
||||||
|
array('V', 0x04034b50), # local file header signature
|
||||||
|
array('v', (6 << 8) + 3), # version needed to extract
|
||||||
|
array('v', 0x00), # general purpose bit flag
|
||||||
|
array('v', $meth), # compresion method (deflate or store)
|
||||||
|
array('V', $dts), # dos timestamp
|
||||||
|
array('V', $crc), # crc32 of data
|
||||||
|
array('V', $zlen), # compressed data length
|
||||||
|
array('V', $len), # uncompressed data length
|
||||||
|
array('v', $nlen), # filename length
|
||||||
|
array('v', 0), # extra data len
|
||||||
|
);
|
||||||
|
|
||||||
|
# pack fields and calculate "total" length
|
||||||
|
$ret = $this->pack_fields($fields);
|
||||||
|
$cdr_len = strlen($ret) + $nlen + $zlen;
|
||||||
|
|
||||||
|
# print header and filename
|
||||||
|
$this->send($ret . $name);
|
||||||
|
|
||||||
|
# add to central directory record and increment offset
|
||||||
|
$this->add_to_cdr($name, $opt, $meth, $crc, $zlen, $len, $cdr_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# Add a large file from the given path.
|
||||||
|
#
|
||||||
|
private function add_large_file($name, $path, $opt = array()) {
|
||||||
|
$st = stat($path);
|
||||||
|
$block_size = 1048576; # process in 1 megabyte chunks
|
||||||
|
$algo = 'crc32b';
|
||||||
|
|
||||||
|
# calculate header attributes
|
||||||
|
$zlen = $len = $st['size'];
|
||||||
|
|
||||||
|
$meth_str = $this->opt['large_file_method'];
|
||||||
|
if ($meth_str == 'store') {
|
||||||
|
# store method
|
||||||
|
$meth = 0x00;
|
||||||
|
$crc = unpack('V', hash_file($algo, $path, true));
|
||||||
|
$crc = $crc[1];
|
||||||
|
} elseif ($meth_str == 'deflate') {
|
||||||
|
# deflate method
|
||||||
|
$meth = 0x08;
|
||||||
|
|
||||||
|
# open file, calculate crc and compressed file length
|
||||||
|
$fh = fopen($path, 'rb');
|
||||||
|
$hash_ctx = hash_init($algo);
|
||||||
|
$zlen = 0;
|
||||||
|
|
||||||
|
# read each block, update crc and zlen
|
||||||
|
while ($data = fgets($fh, $block_size)) {
|
||||||
|
hash_update($hash_ctx, $data);
|
||||||
|
$data = gzdeflate($data);
|
||||||
|
$zlen += strlen($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
# close file and finalize crc
|
||||||
|
fclose($fh);
|
||||||
|
$crc = unpack('V', hash_final($hash_ctx, true));
|
||||||
|
$crc = $crc[1];
|
||||||
|
} else {
|
||||||
|
die("unknown large_file_method: $meth_str");
|
||||||
|
}
|
||||||
|
|
||||||
|
# send file header
|
||||||
|
$this->add_file_header($name, $opt, $meth, $crc, $zlen, $len);
|
||||||
|
|
||||||
|
# open input file
|
||||||
|
$fh = fopen($path, 'rb');
|
||||||
|
|
||||||
|
# send file blocks
|
||||||
|
while ($data = fgets($fh, $block_size)) {
|
||||||
|
if ($meth_str == 'deflate')
|
||||||
|
$data = gzdeflate($data);
|
||||||
|
|
||||||
|
# send data
|
||||||
|
$this->send($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
# close input file
|
||||||
|
fclose($fh);
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# Is this file larger than large_file_size?
|
||||||
|
#
|
||||||
|
function is_large_file($path) {
|
||||||
|
$st = stat($path);
|
||||||
|
return ($this->opt['large_file_size'] > 0) &&
|
||||||
|
($st['size'] > $this->opt['large_file_size']);
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# Save file attributes for trailing CDR record.
|
||||||
|
#
|
||||||
|
private function add_to_cdr($name, $opt, $meth, $crc, $zlen, $len, $rec_len) {
|
||||||
|
$this->files[] = array($name, $opt, $meth, $crc, $zlen, $len, $this->ofs);
|
||||||
|
$this->ofs += $rec_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# Send CDR record for specified file.
|
||||||
|
#
|
||||||
|
private function add_cdr_file($args) {
|
||||||
|
list ($name, $opt, $meth, $crc, $zlen, $len, $ofs) = $args;
|
||||||
|
|
||||||
|
# get attributes
|
||||||
|
$comment = isset($opt['comment']) ? $opt['comment'] : '';
|
||||||
|
|
||||||
|
# get dos timestamp
|
||||||
|
$dts = $this->dostime($opt['time']);
|
||||||
|
|
||||||
|
$fields = array( # (from V,F of APPNOTE.TXT)
|
||||||
|
array('V', 0x02014b50), # central file header signature
|
||||||
|
array('v', (6 << 8) + 3), # version made by
|
||||||
|
array('v', (6 << 8) + 3), # version needed to extract
|
||||||
|
array('v', 0x00), # general purpose bit flag
|
||||||
|
array('v', $meth), # compresion method (deflate or store)
|
||||||
|
array('V', $dts), # dos timestamp
|
||||||
|
array('V', $crc), # crc32 of data
|
||||||
|
array('V', $zlen), # compressed data length
|
||||||
|
array('V', $len), # uncompressed data length
|
||||||
|
array('v', strlen($name)), # filename length
|
||||||
|
array('v', 0), # extra data len
|
||||||
|
array('v', strlen($comment)), # file comment length
|
||||||
|
array('v', 0), # disk number start
|
||||||
|
array('v', 0), # internal file attributes
|
||||||
|
array('V', 32), # external file attributes
|
||||||
|
array('V', $ofs), # relative offset of local header
|
||||||
|
);
|
||||||
|
|
||||||
|
# pack fields, then append name and comment
|
||||||
|
$ret = $this->pack_fields($fields) . $name . $comment;
|
||||||
|
|
||||||
|
$this->send($ret);
|
||||||
|
|
||||||
|
# increment cdr offset
|
||||||
|
$this->cdr_ofs += strlen($ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# Send CDR EOF (Central Directory Record End-of-File) record.
|
||||||
|
#
|
||||||
|
private function add_cdr_eof($opt = null) {
|
||||||
|
$num = count($this->files);
|
||||||
|
$cdr_len = $this->cdr_ofs;
|
||||||
|
$cdr_ofs = $this->ofs;
|
||||||
|
|
||||||
|
# grab comment (if specified)
|
||||||
|
$comment = '';
|
||||||
|
if ($opt && isset($opt['comment']))
|
||||||
|
$comment = $opt['comment'];
|
||||||
|
|
||||||
|
$fields = array( # (from V,F of APPNOTE.TXT)
|
||||||
|
array('V', 0x06054b50), # end of central file header signature
|
||||||
|
array('v', 0x00), # this disk number
|
||||||
|
array('v', 0x00), # number of disk with cdr
|
||||||
|
array('v', $num), # number of entries in the cdr on this disk
|
||||||
|
array('v', $num), # number of entries in the cdr
|
||||||
|
array('V', $cdr_len), # cdr size
|
||||||
|
array('V', $cdr_ofs), # cdr ofs
|
||||||
|
array('v', strlen($comment)), # zip file comment length
|
||||||
|
);
|
||||||
|
|
||||||
|
$ret = $this->pack_fields($fields) . $comment;
|
||||||
|
$this->send($ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# Add CDR (Central Directory Record) footer.
|
||||||
|
#
|
||||||
|
private function add_cdr($opt = null) {
|
||||||
|
foreach ($this->files as $file)
|
||||||
|
$this->add_cdr_file($file);
|
||||||
|
$this->add_cdr_eof($opt);
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# Clear all internal variables. Note that the stream object is not
|
||||||
|
# usable after this.
|
||||||
|
#
|
||||||
|
function clear() {
|
||||||
|
$this->files = array();
|
||||||
|
$this->ofs = 0;
|
||||||
|
$this->cdr_ofs = 0;
|
||||||
|
$this->opt = array();
|
||||||
|
}
|
||||||
|
|
||||||
|
###########################
|
||||||
|
# PRIVATE UTILITY METHODS #
|
||||||
|
###########################
|
||||||
|
|
||||||
|
#
|
||||||
|
# Send HTTP headers for this stream.
|
||||||
|
#
|
||||||
|
private function send_http_headers() {
|
||||||
|
# grab options
|
||||||
|
$opt = $this->opt;
|
||||||
|
|
||||||
|
# grab content type from options
|
||||||
|
$content_type = 'application/x-zip';
|
||||||
|
if (isset($opt['content_type']))
|
||||||
|
$content_type = $this->opt['content_type'];
|
||||||
|
|
||||||
|
# grab content disposition
|
||||||
|
$disposition = 'attachment';
|
||||||
|
if (isset($opt['content_disposition']))
|
||||||
|
$disposition = $opt['content_disposition'];
|
||||||
|
|
||||||
|
if ($this->output_name)
|
||||||
|
$disposition .= "; filename=\"{$this->output_name}\"";
|
||||||
|
|
||||||
|
$headers = array(
|
||||||
|
'Content-Type' => $content_type,
|
||||||
|
'Content-Disposition' => $disposition,
|
||||||
|
'Pragma' => 'public',
|
||||||
|
'Cache-Control' => 'public, must-revalidate',
|
||||||
|
'Content-Transfer-Encoding' => 'binary',
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ($headers as $key => $val)
|
||||||
|
header("$key: $val");
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# Send string, sending HTTP headers if necessary.
|
||||||
|
#
|
||||||
|
private function send($str) {
|
||||||
|
if ($this->need_headers)
|
||||||
|
$this->send_http_headers();
|
||||||
|
$this->need_headers = false;
|
||||||
|
|
||||||
|
echo $str;
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# Convert a UNIX timestamp to a DOS timestamp.
|
||||||
|
#
|
||||||
|
function dostime($when = 0) {
|
||||||
|
# get date array for timestamp
|
||||||
|
$d = getdate($when);
|
||||||
|
|
||||||
|
# set lower-bound on dates
|
||||||
|
if ($d['year'] < 1980) {
|
||||||
|
$d = array('year' => 1980, 'mon' => 1, 'mday' => 1,
|
||||||
|
'hours' => 0, 'minutes' => 0, 'seconds' => 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
# remove extra years from 1980
|
||||||
|
$d['year'] -= 1980;
|
||||||
|
|
||||||
|
# return date string
|
||||||
|
return ($d['year'] << 25) | ($d['mon'] << 21) | ($d['mday'] << 16) |
|
||||||
|
($d['hours'] << 11) | ($d['minutes'] << 5) | ($d['seconds'] >> 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# Create a format string and argument list for pack(), then call
|
||||||
|
# pack() and return the result.
|
||||||
|
#
|
||||||
|
function pack_fields($fields) {
|
||||||
|
list ($fmt, $args) = array('', array());
|
||||||
|
|
||||||
|
# populate format string and argument list
|
||||||
|
foreach ($fields as $field) {
|
||||||
|
$fmt .= $field[0];
|
||||||
|
$args[] = $field[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
# prepend format string to argument list
|
||||||
|
array_unshift($args, $fmt);
|
||||||
|
|
||||||
|
# build output string from header and compressed data
|
||||||
|
return call_user_func_array('pack', $args);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
?>
|
||||||
272
doc/syncmonotone.mdtext
Normal file
272
doc/syncmonotone.mdtext
Normal file
@@ -0,0 +1,272 @@
|
|||||||
|
# Plugin SyncMonotone by Thomas Keller (me@thomaskeller.biz)
|
||||||
|
|
||||||
|
The SyncMonotone plugin allow the direct creation and synchronisation of
|
||||||
|
monotone repositories with the InDefero database. It has been built to
|
||||||
|
work together with monotone's "super server" usher, which is used to control
|
||||||
|
several repositories at once, acts as proxy and single entrance.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
* a unixoid operating system
|
||||||
|
* monotone >= 0.99.1
|
||||||
|
* for a proxy setup with usher:
|
||||||
|
* boost headers (for usher compilation)
|
||||||
|
* a current version of usher
|
||||||
|
* a daemonizer, like supervise
|
||||||
|
|
||||||
|
## Installation of monotone
|
||||||
|
|
||||||
|
If you install monotone from a distribution package, ensure you do not
|
||||||
|
install and / or activate the server component. We just need a plain
|
||||||
|
client installation which usually consists only of the `mtn` binary and
|
||||||
|
a few docs.
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
The monotone plugin can be used in several different ways:
|
||||||
|
|
||||||
|
1. One database for everything. This is the easiest setup and of possible
|
||||||
|
use in case you do not want indefero to manage the access to your project.
|
||||||
|
Your `idf.php` should look like this:
|
||||||
|
|
||||||
|
$ cat idf.php
|
||||||
|
...
|
||||||
|
$cfg['mtn_path'] = 'mtn';
|
||||||
|
$cfg['mtn_opts'] = array('--no-workspace', '--no-standard-rcfiles');
|
||||||
|
$cfg['mtn_repositories'] = '/home/monotone/all_projects.mtn';
|
||||||
|
$cfg['mtn_remote_url'] = 'ssh://monotone@my.server.com:~all_projects.mtn';
|
||||||
|
$cfg['mtn_db_access'] = 'local';
|
||||||
|
...
|
||||||
|
|
||||||
|
Pro:
|
||||||
|
* easy to setup and to manage
|
||||||
|
|
||||||
|
Con:
|
||||||
|
* you need to give committers SSH access to your machine
|
||||||
|
* database lock problem: the database from which
|
||||||
|
indefero reads its data might be locked in case a user
|
||||||
|
syncs at the very moment via SSH
|
||||||
|
|
||||||
|
2. One database for every project. Similar to the above setup, but this
|
||||||
|
time you use the '%s' placeholder which is replaced with the short name
|
||||||
|
of the indefero project:
|
||||||
|
|
||||||
|
$ cat idf.php
|
||||||
|
...
|
||||||
|
$cfg['mtn_path'] = 'mtn';
|
||||||
|
$cfg['mtn_opts'] = array('--no-workspace', '--no-standard-rcfiles');
|
||||||
|
$cfg['mtn_repositories'] = '/home/monotone/%s.mtn';
|
||||||
|
$cfg['mtn_remote_url'] = 'ssh://monotone@my.server.com:~%s.mtn';
|
||||||
|
$cfg['mtn_db_access'] = 'local';
|
||||||
|
...
|
||||||
|
|
||||||
|
The same pro's and con's apply. Additionally you have to be careful about
|
||||||
|
not giving people physical read/write access of another project's database.
|
||||||
|
|
||||||
|
Furthermore, if you do not want to use `ssh`, but `netsync` transport,
|
||||||
|
each project's database must be served over a separate port.
|
||||||
|
|
||||||
|
3. One database for every project, all managed with usher. This is the
|
||||||
|
recommended setup for a mid-size forge setup. The remaining part of this
|
||||||
|
document will describe the process to set this up in detail.
|
||||||
|
|
||||||
|
Pro:
|
||||||
|
* access rights can be granted per project and are automatically
|
||||||
|
managed by indefero, just like the user's public monotone keys
|
||||||
|
* no database locking issues
|
||||||
|
* one public server running on the one well-known port
|
||||||
|
|
||||||
|
Con:
|
||||||
|
* harder to setup
|
||||||
|
|
||||||
|
## Installation and configuration of usher
|
||||||
|
|
||||||
|
1. Clone usher's monotone repository:
|
||||||
|
|
||||||
|
$ mtn clone "mtn://monotone.ca?net.venge.monotone.contrib.usher"
|
||||||
|
|
||||||
|
2. Compile usher:
|
||||||
|
|
||||||
|
$ autoreconf -i
|
||||||
|
$ ./configure && make
|
||||||
|
$ sudo make install
|
||||||
|
|
||||||
|
This installs the usher binary in $prefix/bin.
|
||||||
|
|
||||||
|
3. Create a new usher user:
|
||||||
|
|
||||||
|
$ adduser --system --disabled-login --home /var/lib/usher usher
|
||||||
|
|
||||||
|
4. Create the basic usher setup:
|
||||||
|
|
||||||
|
$ cd /var/lib/usher
|
||||||
|
$ mkdir projects logs
|
||||||
|
$ cat > usher.conf
|
||||||
|
userpass "admin" "<secret-password>"
|
||||||
|
adminaddr "127.0.0.1:12345"
|
||||||
|
logdir "log"
|
||||||
|
^D
|
||||||
|
$ chmod 600 usher.conf
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
$ setfacl -m u:www:rw usher.conf
|
||||||
|
$ setfacl -m d:u:www:rwx projects/
|
||||||
|
|
||||||
|
5. Wrap a daemonizer around usher, for example supervise from daemontools
|
||||||
|
(<http://cr.yp.to/damontools.html>):
|
||||||
|
|
||||||
|
$ cat > run
|
||||||
|
#!/bin/sh
|
||||||
|
cd /var/lib/usher
|
||||||
|
exec 2>&1
|
||||||
|
exec \
|
||||||
|
setuidgid usher \
|
||||||
|
usher usher.conf
|
||||||
|
^D
|
||||||
|
|
||||||
|
The service can now be started through supervise:
|
||||||
|
|
||||||
|
$ supervise /var/lib/usher
|
||||||
|
|
||||||
|
## Configuration of indefero
|
||||||
|
|
||||||
|
Based on the above setup, the configuration in `src/IDF/conf/idf.php` should
|
||||||
|
look like this:
|
||||||
|
|
||||||
|
$ cat idf.php
|
||||||
|
...
|
||||||
|
$cfg['mtn_path'] = 'mtn';
|
||||||
|
$cfg['mtn_opts'] = array('--no-workspace', '--no-standard-rcfiles');
|
||||||
|
$cfg['mtn_repositories'] = '/var/lib/usher/projects/%s/';
|
||||||
|
$cfg['mtn_remote_url'] = 'mtn://my.server.com/%s';
|
||||||
|
$cfg['mtn_db_access'] = 'remote';
|
||||||
|
$cfg['mtn_usher_conf'] = '/var/lib/usher/usher.conf';
|
||||||
|
...
|
||||||
|
|
||||||
|
The `%s` placeholders are automatically replaced by the name of the
|
||||||
|
indefero project. The plugin assumes that every project is separated
|
||||||
|
by a distinct server name in the monotone URL (hence the use of `/%s`),
|
||||||
|
so if a user calls
|
||||||
|
|
||||||
|
$ mtn sync mtn://my.server.com/project1
|
||||||
|
|
||||||
|
then the database / repository of the indefero `project1` is used.
|
||||||
|
Note that 'mtn_remote_url' is also used as internal URI to query the data
|
||||||
|
for indefero's source view, so it *must* be a valid host!
|
||||||
|
|
||||||
|
Usher also allows the identification of a project repository by hostname,
|
||||||
|
which would allow an URL template like `mtn://%s.my.server.com`, however
|
||||||
|
the plugin does not write out the configuration which is needed for this
|
||||||
|
yet.
|
||||||
|
|
||||||
|
For even more advanced setups, usher can also be used to forward sync
|
||||||
|
requests to other remote servers for load balancing, please consult the
|
||||||
|
README file for more information.
|
||||||
|
|
||||||
|
## Security and remote access
|
||||||
|
|
||||||
|
Indefero distinguishs between public and private projects and so does
|
||||||
|
the monotone plugin.
|
||||||
|
|
||||||
|
Public projects can be pulled by everybody and pushed by team members
|
||||||
|
or additional invited people. Remote command execution is enabled, but
|
||||||
|
only for read-only commands.
|
||||||
|
|
||||||
|
Remote commands can be helpful for a user or a 3rd party tool (like
|
||||||
|
[mtn-browse](http://mtn-browse.sourceforge.net) or
|
||||||
|
[guitone](http://guitone.thomaskeller.biz)) to browse the database
|
||||||
|
contents remotely without having to pull everything in first instance.
|
||||||
|
|
||||||
|
Private projects on the other hand can only be synced by team members
|
||||||
|
or additional invited people. Remote command execution is disabled
|
||||||
|
by default. If you want to enable that, simply put the keys of the users
|
||||||
|
you want to give access to in your project's `remote-automate-permissions`
|
||||||
|
file. In the future this plugin might handle this file just as it handles
|
||||||
|
`read-permissions` and `write-permissions`.
|
||||||
|
|
||||||
|
## Notifications
|
||||||
|
|
||||||
|
If you have successfully set up your monotone instance, you probably want
|
||||||
|
to notify 3rd party systems for incoming changes or simply mirror them
|
||||||
|
somewhere else for backup purposes. The monotone source tree already comes
|
||||||
|
with [many example scripts and hooks](http://code.monotone.ca/p/monotone/source/tree/h:net.venge.monotone/contrib)
|
||||||
|
which serve these purposes, after only little additional configuration.
|
||||||
|
|
||||||
|
The usher/indefero-controlled setup automatically looks for *.lua files
|
||||||
|
in a directory called `hooks.d` right under the project's base directory
|
||||||
|
(configured via $cfg['mtn_repositories']) and this is the ideal place to
|
||||||
|
put or link these additional lua sources.
|
||||||
|
|
||||||
|
## Custom project configurations and templates
|
||||||
|
|
||||||
|
If a new project is created in IDF, the SyncMonotone plugin creates a new
|
||||||
|
configuration tree for the project into the project's configuration directory,
|
||||||
|
determined by `$cfg['mtn_repositories']`. IDF ships with the minimum set of
|
||||||
|
files for this configuration tree and sets up everything automatically for you.
|
||||||
|
|
||||||
|
Even more, most of the configuration files from the newly created tree are only
|
||||||
|
symlinked to the original configuration directory which is configurable via
|
||||||
|
`$cfg['mtn_confdir']` and defaults to `src/IDF/Plugin/SyncMonotone/`. This has
|
||||||
|
the advantage that your standard IDF setup automatically receives updates to
|
||||||
|
existing (symlinked) configuration files as soon as you update to a newer
|
||||||
|
version.
|
||||||
|
|
||||||
|
You could, however, also choose to place the directory tree somewhere else
|
||||||
|
and adapt the contents of the individual files yourself, so these changes get
|
||||||
|
automatically applied to all new projects you create. You could even go so far
|
||||||
|
and add new files to the tree and let them be processed automatically just
|
||||||
|
as the basic files! All you need to do is to copy your files and / or directories
|
||||||
|
underknees your `$cfg['mtn_confdir']` and add their relative paths to
|
||||||
|
`$cfg['mtn_confdir_extra']`.
|
||||||
|
|
||||||
|
By convention, all entries which end with a slash are considered directories,
|
||||||
|
so mkdir(1) is issued for these entries, all files which do not end up with
|
||||||
|
".in" are considered to be static script files which are just symlinked from
|
||||||
|
the basic configuration dir and all entries ending on ".in" are considered
|
||||||
|
configuration files or templates, which are copied over to the project's
|
||||||
|
configuration tree and which get some basic project-specific values replaced.
|
||||||
|
|
||||||
|
The following placeholders are currently recognized and replaced for these files:
|
||||||
|
|
||||||
|
* %%PROJECT%% - the name of the created project
|
||||||
|
* %%MTNPOSTPUSH%% - the absolute path to the `mtn-post-push` script
|
||||||
|
* %%MTNCLIENTKEY%% - the public key hash of the key which is used by IDF
|
||||||
|
to authenticate remote stdio access
|
||||||
|
|
||||||
|
Thats it - I hope you find it useful :)
|
||||||
|
|
||||||
|
## Q&A
|
||||||
|
|
||||||
|
### After I created a new project, IDF throws an exception and tells me that it couldn't save the membership data with a cryptic error message. Whats wrong?
|
||||||
|
|
||||||
|
Multiple issues could cause that. If you've set up usher, make sure the usher
|
||||||
|
can fork your database at all and look out for specific errors in the log file
|
||||||
|
of your project. If you stumble upon permission issues, ensure that the user
|
||||||
|
who runs the usher can access all files in your project's configuration directory,
|
||||||
|
including symlinked files.
|
||||||
|
|
||||||
|
### I pushed a branch to my server, but it does not show up in IDF. Whats wrong?
|
||||||
|
|
||||||
|
Check if the heads of your branch are not suspended, i.e. do not carry a
|
||||||
|
`suspend` certificate. This usually hides the branch and all of its history
|
||||||
|
from monotone's eyes and therefor also from indefero. You can either choose
|
||||||
|
to "unsuspend" the branch simply by committing and pushing another head or
|
||||||
|
by letting monotone ignore all suspend certs. For the latter, its usually
|
||||||
|
enough to add `--ignore-suspend-certs` to the list of options in `$cfg['mtn_opts']`.
|
||||||
|
|
||||||
|
### I want to display another default branch when I click the "Source" tab. How can I do that?
|
||||||
|
|
||||||
|
Let the forge admin know the new master branch for your project. He is able
|
||||||
|
to change that quickly. Depending on the backend / server setup this might
|
||||||
|
also require some changes in the usher configuration, but only if usher
|
||||||
|
recognizes and proxies your database on a branch name level.
|
||||||
|
|
||||||
@@ -79,3 +79,12 @@ the following configuration variables:
|
|||||||
* **idf_plugin_syncsvn_access_public ('r')**: Anonymous access.
|
* **idf_plugin_syncsvn_access_public ('r')**: Anonymous access.
|
||||||
* **idf_plugin_syncsvn_access_private ('')**: Anonymous access in the case of a private project.
|
* **idf_plugin_syncsvn_access_private ('')**: Anonymous access in the case of a private project.
|
||||||
|
|
||||||
|
## svn: Can't open file '/root/.subversion/servers': Permission denied error
|
||||||
|
|
||||||
|
If you get the error:
|
||||||
|
|
||||||
|
svn: Can't open file '/root/.subversion/servers': Permission denied
|
||||||
|
|
||||||
|
Check the [fix available](http://projects.ceondo.com/p/indefero/issues/458/)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
86
logo/indefero-logo-lite.svg
Normal file
86
logo/indefero-logo-lite.svg
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
<?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="16"
|
||||||
|
height="16"
|
||||||
|
id="svg2"
|
||||||
|
sodipodi:version="0.32"
|
||||||
|
inkscape:version="0.48.0 r9654"
|
||||||
|
version="1.0"
|
||||||
|
sodipodi:docname="indefero-logo-lite.svg"
|
||||||
|
inkscape:output_extension="org.inkscape.output.svg.inkscape">
|
||||||
|
<defs
|
||||||
|
id="defs4">
|
||||||
|
<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" />
|
||||||
|
</defs>
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="base"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
gridtolerance="10000"
|
||||||
|
guidetolerance="10"
|
||||||
|
objecttolerance="10"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:zoom="11.313708"
|
||||||
|
inkscape:cx="31.568929"
|
||||||
|
inkscape:cy="-0.35578703"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
inkscape:current-layer="layer1"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:window-width="1280"
|
||||||
|
inkscape:window-height="723"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="24"
|
||||||
|
inkscape:window-maximized="1" />
|
||||||
|
<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" />
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(0,-728.09451)">
|
||||||
|
<g
|
||||||
|
id="g2401"
|
||||||
|
transform="matrix(0.13580542,0,0,0.13580542,-47.580342,708.10521)"
|
||||||
|
style="fill:#8ae234;stroke:#4e9a06;stroke-width:2.4000001;stroke-miterlimit:4;stroke-dasharray:none"
|
||||||
|
inkscape:export-filename="/home/loa/Projects/indefero/logo/powered-by-indefero.png"
|
||||||
|
inkscape:export-xdpi="12.330909"
|
||||||
|
inkscape:export-ydpi="12.330909">
|
||||||
|
<path
|
||||||
|
id="path2383"
|
||||||
|
d="m 396.19089,173.14471 c -7.67621,0.80661 -14.40195,5.39406 -19.58101,10.89131 -7.23597,7.88004 -11.69742,18.07908 -13.32198,28.60362 -1.7236,11.28173 -0.25925,23.20635 5.07686,33.37271 3.78607,7.24384 9.53161,13.92339 17.29701,16.96772 3.86478,1.53937 8.98362,1.03284 11.67912,-2.41036 2.64357,-3.5671 2.69463,-8.234 2.85756,-12.48867 0.045,-7.61054 -0.54749,-15.25544 0.45618,-22.83193 0.87131,-9.50623 4.03944,-18.56751 6.71612,-27.66851 1.16242,-4.44333 2.25094,-9.02808 1.97499,-13.64988 -0.48817,-4.62476 -3.58059,-9.31042 -8.2964,-10.4067 -1.57489,-0.44882 -3.23412,-0.48948 -4.85845,-0.37931 z"
|
||||||
|
style="fill:#8ae234;fill-opacity:1;fill-rule:nonzero;stroke:#4e9a06;stroke-width:2.4000001;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
<path
|
||||||
|
id="path2391"
|
||||||
|
d="m 433.14691,149.28687 c 7.2059,2.76589 12.51512,8.93778 16.09494,15.58815 4.94991,9.48434 6.61962,20.49058 5.46486,31.07695 -1.25505,11.34342 -5.75582,22.48271 -13.54134,30.92159 -5.53192,6.01709 -12.81048,10.98198 -21.09918,11.91276 -4.13154,0.4866 -8.94486,-1.32748 -10.65734,-5.35104 -1.63027,-4.12976 -0.4717,-8.65084 0.47212,-12.80269 1.92628,-7.36287 4.47721,-14.59393 5.4687,-22.17201 1.61875,-9.40784 0.90381,-18.98034 0.67386,-28.46402 0.0272,-4.59278 0.1624,-9.30303 1.62515,-13.69592 1.66851,-4.34082 5.86829,-8.06645 10.70716,-7.90484 1.63738,-0.0259 3.25061,0.36424 4.79107,0.89107 z"
|
||||||
|
style="fill:#8ae234;fill-opacity:1;fill-rule:nonzero;stroke:#4e9a06;stroke-width:2.4000001;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 4.1 KiB |
@@ -1,8 +1,11 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
last="$1"
|
last="$1"
|
||||||
new="$2"
|
new="$2"
|
||||||
|
PLUF_PATH=`php -r "require_once('./IDF/conf/path.php'); echo PLUF_PATH;"`
|
||||||
|
echo "php $PLUF_PATH/extracttemplates.php ./IDF/conf/idf.php ./IDF/gettexttemplates"
|
||||||
echo "xgettext -o idf.pot -p ./IDF/locale --force-po --from-code=UTF-8 --keyword --keyword=__ --keyword=_n:1,2 -L PHP ./IDF/*.php"
|
echo "xgettext -o idf.pot -p ./IDF/locale --force-po --from-code=UTF-8 --keyword --keyword=__ --keyword=_n:1,2 -L PHP ./IDF/*.php"
|
||||||
echo "find ./ -iname \"*.php\" -exec xgettext -o idf.pot -p ./IDF/locale/ --from-code=UTF-8 -j --keyword --keyword=__ --keyword=_n:1,2 -L PHP {} \;"
|
echo "find ./ -iname \"*.php\" -exec xgettext -o idf.pot -p ./IDF/locale/ --from-code=UTF-8 -j --keyword --keyword=__ --keyword=_n:1,2 -L PHP {} \;"
|
||||||
|
echo 'for pofile in `ls ./IDF/locale/*/idf.po`; do msgmerge -U $pofile ./IDF/locale/idf.pot; done'
|
||||||
echo "# git tag v$new"
|
echo "# git tag v$new"
|
||||||
echo "git archive --format=zip --prefix=indefero-$new/ v$new > indefero-$new.zip"
|
echo "git archive --format=zip --prefix=indefero-$new/ v$new > indefero-$new.zip"
|
||||||
echo "git log --no-merges v$new ^v$last > ChangeLog-$new"
|
echo "git log --no-merges v$new ^v$last > ChangeLog-$new"
|
||||||
|
|||||||
0
scripts/SyncMercurial.sh
Normal file → Executable file
0
scripts/SyncMercurial.sh
Normal file → Executable file
@@ -17,8 +17,8 @@
|
|||||||
# git$ ln -s /home/www/indefero/scripts/git-post-update post-update
|
# git$ ln -s /home/www/indefero/scripts/git-post-update post-update
|
||||||
#
|
#
|
||||||
|
|
||||||
SCRIPTDIR=$(dirname $(readlink -f $0))
|
SCRIPTDIR=$(dirname $(readlink $0))
|
||||||
FULL_GIT_DIR=$(readlink -f $GIT_DIR)
|
FULL_GIT_DIR=$(cd "$GIT_DIR" && /bin/pwd || "$GIT_DIR")
|
||||||
PHP_POST_UPDATE=$SCRIPTDIR/gitpostupdate.php
|
PHP_POST_UPDATE=$SCRIPTDIR/gitpostupdate.php
|
||||||
|
|
||||||
echo php $PHP_POST_UPDATE $FULL_GIT_DIR | at now > /dev/null 2>&1
|
echo php $PHP_POST_UPDATE $FULL_GIT_DIR | at now > /dev/null 2>&1
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ Pluf_Dispatcher::loadControllers(Pluf::f('idf_views'));
|
|||||||
*/
|
*/
|
||||||
$params = array('git_dir' => $argv[1],
|
$params = array('git_dir' => $argv[1],
|
||||||
'env' => array_merge($_ENV, $_SERVER));
|
'env' => array_merge($_ENV, $_SERVER));
|
||||||
|
Pluf_Log::event(array('gitpostupdate.php', 'Send run signal.', $params));
|
||||||
Pluf_Signal::send('gitpostupdate.php::run', 'gitpostupdate.php', $params);
|
Pluf_Signal::send('gitpostupdate.php::run', 'gitpostupdate.php', $params);
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
22
scripts/mtn-post-push
Executable file
22
scripts/mtn-post-push
Executable file
@@ -0,0 +1,22 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
#
|
||||||
|
# This hook informs IDF that new revisions arrived in the database
|
||||||
|
# of the specified project.
|
||||||
|
#
|
||||||
|
# This hook is normally installed automatically at the creation of your
|
||||||
|
# repository if you have everything configured correctly. If you want
|
||||||
|
# to enable it later, you need to call it into your monotonerc file
|
||||||
|
# from the hook "note_netsync_end". (See chapter "Event Notifications
|
||||||
|
# and Triggers" on <http://monotone.ca/docs/Hooks.html#Hooks>.)
|
||||||
|
#
|
||||||
|
|
||||||
|
dir=$(dirname "$0")
|
||||||
|
res=$(cd "$dir" && /bin/pwd || "$dir")
|
||||||
|
SCRIPTDIR="$res/$(readlink $0)"
|
||||||
|
PHP_POST_PUSH=$SCRIPTDIR/mtnpostpush.php
|
||||||
|
|
||||||
|
TMPFILE=$(mktemp /tmp/mtn-post-push.XXXXXX) || exit 1
|
||||||
|
while read rev; do echo $rev >> $TMPFILE; done
|
||||||
|
|
||||||
|
echo php $PHP_POST_PUSH "$1" \< $TMPFILE \&\& rm -f $TMPFILE |\
|
||||||
|
at now > /dev/null 2>&1
|
||||||
63
scripts/mtnpostpush.php
Normal file
63
scripts/mtnpostpush.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-2010 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 will send the notifications after a push in your
|
||||||
|
* repository.
|
||||||
|
*/
|
||||||
|
|
||||||
|
require dirname(__FILE__).'/../src/IDF/conf/path.php';
|
||||||
|
require 'Pluf.php';
|
||||||
|
Pluf::start(dirname(__FILE__).'/../src/IDF/conf/idf.php');
|
||||||
|
Pluf_Dispatcher::loadControllers(Pluf::f('idf_views'));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [signal]
|
||||||
|
*
|
||||||
|
* mtnpostpush.php::run
|
||||||
|
*
|
||||||
|
* [sender]
|
||||||
|
*
|
||||||
|
* mtnpostpush.php
|
||||||
|
*
|
||||||
|
* [description]
|
||||||
|
*
|
||||||
|
* This signal allows an application to perform a set of tasks
|
||||||
|
* after a push to a monotone repository.
|
||||||
|
*
|
||||||
|
* [parameters]
|
||||||
|
*
|
||||||
|
* array('project' => 'name-of-the-project',
|
||||||
|
* 'revisions' => array('123abc...', '456def...', ...));
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
fwrite(STDERR, "waiting for revisions on STDIN...\n");
|
||||||
|
$stdin = file_get_contents('php://stdin');
|
||||||
|
|
||||||
|
$params = array('project' => $argv[1],
|
||||||
|
'revisions' => explode("\n", chop($stdin)));
|
||||||
|
Pluf_Signal::send('mtnpostpush.php::run', 'mtnpostpush.php', $params);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
69
scripts/queuecron.php
Normal file
69
scripts/queuecron.php
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
<?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 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 process the queue of items.
|
||||||
|
*
|
||||||
|
* At the moment the queue is only used for the webhooks, but it would
|
||||||
|
* be good in the future to use it for indexing and email
|
||||||
|
* notifications.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
require dirname(__FILE__).'/../src/IDF/conf/path.php';
|
||||||
|
require 'Pluf.php';
|
||||||
|
Pluf::start(dirname(__FILE__).'/../src/IDF/conf/idf.php');
|
||||||
|
Pluf_Dispatcher::loadControllers(Pluf::f('idf_views'));
|
||||||
|
|
||||||
|
#;*/ ::
|
||||||
|
$lock_file = Pluf::f('idf_queuecron_lock',
|
||||||
|
Pluf::f('tmp_folder', '/tmp').'/queuecron.lock');
|
||||||
|
if (file_exists($lock_file)) {
|
||||||
|
Pluf_Log::event(array('queuecron.php', 'skip'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
file_put_contents($lock_file, time(), LOCK_EX);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [signal]
|
||||||
|
*
|
||||||
|
* queuecron.php::run
|
||||||
|
*
|
||||||
|
* [sender]
|
||||||
|
*
|
||||||
|
* queuecron.php
|
||||||
|
*
|
||||||
|
* [description]
|
||||||
|
*
|
||||||
|
* This signal allows an application to perform a set of tasks when
|
||||||
|
* the queue cron job is run. This is done usually every 5 minutes.
|
||||||
|
*
|
||||||
|
* [parameters]
|
||||||
|
*
|
||||||
|
* array()
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
$params = array();
|
||||||
|
Pluf_Signal::send('queuecron.php::run', 'queuecron.php', $params);
|
||||||
|
|
||||||
|
unlink($lock_file);
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
# www$ ln -s /home/www/indefero/scripts/svn-post-commit post-commit
|
# www$ ln -s /home/www/indefero/scripts/svn-post-commit post-commit
|
||||||
#
|
#
|
||||||
|
|
||||||
SCRIPTDIR=$(dirname $(readlink -f $0))
|
SCRIPTDIR=$(dirname $(readlink $0))
|
||||||
PHP_POST_COMMIT=$SCRIPTDIR/svnpostcommit.php
|
PHP_POST_COMMIT=$SCRIPTDIR/svnpostcommit.php
|
||||||
|
|
||||||
echo php $PHP_POST_COMMIT "$1" "$2" | at now > /dev/null 2>&1
|
echo php $PHP_POST_COMMIT "$1" "$2" | at now > /dev/null 2>&1
|
||||||
|
|||||||
29
scripts/svn-post-revprop-change
Executable file
29
scripts/svn-post-revprop-change
Executable file
@@ -0,0 +1,29 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
#
|
||||||
|
# This hook does only one thing:
|
||||||
|
#
|
||||||
|
# 1. It calls the svnpostrevpropchange.php script with the current repository
|
||||||
|
# and revision as argument. The svnpostrevpropchange.php script will then
|
||||||
|
# trigger the 'svnpostrevpropchange.php::run' event with the repository
|
||||||
|
# path, revision, username, property name and action as arguments together
|
||||||
|
# with merged $_ENV and $_SERVER array.
|
||||||
|
#
|
||||||
|
# This hook is normally installed automatically at the creation of your
|
||||||
|
# repository if you have everything configured correctly. If you want
|
||||||
|
# to enable it later, you need to symlink it as "post-revprop-change" in your
|
||||||
|
# $REPOSITORY/hooks folder. It needs to be executable.
|
||||||
|
#
|
||||||
|
# www$ chmod +x /home/www/indefero/scripts/svn-post-revprop-change
|
||||||
|
# www$ cd /home/svn/repositories/project/hooks
|
||||||
|
# www$ ln -s /home/www/indefero/scripts/svn-post-revprop-change post-revprop-change
|
||||||
|
#
|
||||||
|
|
||||||
|
SCRIPTDIR=$(dirname $(readlink $0))
|
||||||
|
PHP_POST_REVPROP=$SCRIPTDIR/svnpostrevpropchange.php
|
||||||
|
|
||||||
|
echo php $PHP_POST_REVPROP "$1" "$2" "$3" "$4" "$5" | at now > /dev/null 2>&1
|
||||||
|
REPOS="$1"
|
||||||
|
REV="$2"
|
||||||
|
USER="$3"
|
||||||
|
PROPNAME="$4"
|
||||||
|
ACTION="$5"
|
||||||
@@ -55,6 +55,7 @@ Pluf_Dispatcher::loadControllers(Pluf::f('idf_views'));
|
|||||||
$params = array('repo_dir' => $argv[1],
|
$params = array('repo_dir' => $argv[1],
|
||||||
'revision' => $argv[2],
|
'revision' => $argv[2],
|
||||||
'env' => array_merge($_ENV, $_SERVER));
|
'env' => array_merge($_ENV, $_SERVER));
|
||||||
|
Pluf_Log::event(array('svnpostcommit.php', 'Send run signal.', $params));
|
||||||
Pluf_Signal::send('svnpostcommit.php::run', 'svnpostcommit.php', $params);
|
Pluf_Signal::send('svnpostcommit.php::run', 'svnpostcommit.php', $params);
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
70
scripts/svnpostrevpropchange.php
Normal file
70
scripts/svnpostrevpropchange.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-2010 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 allows you to hook into the post-revprop-change action
|
||||||
|
* of your subversion repository. I am using it to perform near real
|
||||||
|
* time backup of the repositories on indefero.net.
|
||||||
|
*/
|
||||||
|
|
||||||
|
require dirname(__FILE__).'/../src/IDF/conf/path.php';
|
||||||
|
require 'Pluf.php';
|
||||||
|
Pluf::start(dirname(__FILE__).'/../src/IDF/conf/idf.php');
|
||||||
|
Pluf_Dispatcher::loadControllers(Pluf::f('idf_views'));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [signal]
|
||||||
|
*
|
||||||
|
* svnpostrevpropchange.php::run
|
||||||
|
*
|
||||||
|
* [sender]
|
||||||
|
*
|
||||||
|
* svnpostrevpropchange.php
|
||||||
|
*
|
||||||
|
* [description]
|
||||||
|
*
|
||||||
|
* This signal allows an application to perform a set of tasks on a
|
||||||
|
* post property revision change of a subversion repository.
|
||||||
|
*
|
||||||
|
* [parameters]
|
||||||
|
*
|
||||||
|
* array('repo_dir' => '/path/to/subversion/repository',
|
||||||
|
* 'revision' => 1234,
|
||||||
|
* 'user' => 'username',
|
||||||
|
* 'propname' => 'changed-property',
|
||||||
|
* 'action' => 'the action M, A or D',
|
||||||
|
* 'env' => array_merge($_ENV, $_SERVER));
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
$params = array('repo_dir' => $argv[1],
|
||||||
|
'revision' => $argv[2],
|
||||||
|
'user' => $argv[3],
|
||||||
|
'propname' => $argv[4],
|
||||||
|
'action' => $argv[5],
|
||||||
|
'env' => array_merge($_ENV, $_SERVER));
|
||||||
|
Pluf_Signal::send('svnpostrevpropchange.php::run', 'svnpostrevpropchange.php',
|
||||||
|
$params);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -34,6 +34,7 @@ Pluf::loadFunction('Pluf_Template_dateAgo');
|
|||||||
class IDF_Commit extends Pluf_Model
|
class IDF_Commit extends Pluf_Model
|
||||||
{
|
{
|
||||||
public $_model = __CLASS__;
|
public $_model = __CLASS__;
|
||||||
|
public $extra = null; /**< Extra data as IDF_Gconf object */
|
||||||
|
|
||||||
function init()
|
function init()
|
||||||
{
|
{
|
||||||
@@ -127,6 +128,7 @@ class IDF_Commit extends Pluf_Model
|
|||||||
{
|
{
|
||||||
IDF_Timeline::remove($this);
|
IDF_Timeline::remove($this);
|
||||||
IDF_Search::remove($this);
|
IDF_Search::remove($this);
|
||||||
|
IDF_Gconf::dropForModel($this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -142,6 +144,10 @@ class IDF_Commit extends Pluf_Model
|
|||||||
array($project->id, $change->commit));
|
array($project->id, $change->commit));
|
||||||
$r = Pluf::factory('IDF_Commit')->getList(array('filter'=>$sql->gen()));
|
$r = Pluf::factory('IDF_Commit')->getList(array('filter'=>$sql->gen()));
|
||||||
if ($r->count() > 0) {
|
if ($r->count() > 0) {
|
||||||
|
$r[0]->extra = new IDF_Gconf();
|
||||||
|
$r[0]->extra->serialize = true;
|
||||||
|
$r[0]->extra->setModel($r[0]);
|
||||||
|
$r[0]->extra->initCache();
|
||||||
return $r[0];
|
return $r[0];
|
||||||
}
|
}
|
||||||
if (!isset($change->full_message)) {
|
if (!isset($change->full_message)) {
|
||||||
@@ -154,9 +160,16 @@ class IDF_Commit extends Pluf_Model
|
|||||||
$commit->summary = self::toUTF8($change->title);
|
$commit->summary = self::toUTF8($change->title);
|
||||||
$commit->fullmessage = self::toUTF8($change->full_message);
|
$commit->fullmessage = self::toUTF8($change->full_message);
|
||||||
$commit->author = $scm->findAuthor($change->author);
|
$commit->author = $scm->findAuthor($change->author);
|
||||||
$commit->origauthor = $change->author;
|
$commit->origauthor = self::toUTF8($change->author);
|
||||||
$commit->creation_dtime = $change->date;
|
$commit->creation_dtime = $change->date;
|
||||||
$commit->create();
|
$commit->create();
|
||||||
|
$extra = $scm->getExtraProperties($change);
|
||||||
|
$commit->extra = new IDF_Gconf();
|
||||||
|
$commit->extra->serialize = true; // As we can store arrays
|
||||||
|
$commit->extra->setModel($commit);
|
||||||
|
foreach ($extra as $key => $val) {
|
||||||
|
$commit->extra->setVal($key, $val);
|
||||||
|
}
|
||||||
$commit->notify($project->getConf());
|
$commit->notify($project->getConf());
|
||||||
return $commit;
|
return $commit;
|
||||||
}
|
}
|
||||||
@@ -264,9 +277,38 @@ class IDF_Commit extends Pluf_Model
|
|||||||
*/
|
*/
|
||||||
public function notify($conf, $create=true)
|
public function notify($conf, $create=true)
|
||||||
{
|
{
|
||||||
|
// Now we add to the queue, soon we will push everything in
|
||||||
|
// the queue, including email notifications and indexing.
|
||||||
|
// Even if the url is empty, we add to the queue as some
|
||||||
|
// plugins may want to do something with this information in
|
||||||
|
// an asynchronous way.
|
||||||
|
$project = $this->get_project();
|
||||||
|
$scm = $project->getConf()->getVal('scm', 'git');
|
||||||
|
$url = str_replace(array('%p', '%r'),
|
||||||
|
array($project->shortname, $this->scm_id),
|
||||||
|
$conf->getVal('webhook_url', ''));
|
||||||
|
$payload = array('to_send' => array(
|
||||||
|
'project' => $project->shortname,
|
||||||
|
'rev' => $this->scm_id,
|
||||||
|
'scm' => $scm,
|
||||||
|
'summary' => $this->summary,
|
||||||
|
'fullmessage' => $this->fullmessage,
|
||||||
|
'author' => $this->origauthor,
|
||||||
|
'creation_date' => $this->creation_dtime,
|
||||||
|
),
|
||||||
|
'project_id' => $project->id,
|
||||||
|
'authkey' => $project->getPostCommitHookKey(),
|
||||||
|
'url' => $url,
|
||||||
|
);
|
||||||
|
$item = new IDF_Queue();
|
||||||
|
$item->type = 'new_commit';
|
||||||
|
$item->payload = $payload;
|
||||||
|
$item->create();
|
||||||
|
|
||||||
if ('' == $conf->getVal('source_notification_email', '')) {
|
if ('' == $conf->getVal('source_notification_email', '')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$current_locale = Pluf_Translation::getLocale();
|
$current_locale = Pluf_Translation::getLocale();
|
||||||
$langs = Pluf::f('languages', array('en'));
|
$langs = Pluf::f('languages', array('en'));
|
||||||
Pluf_Translation::loadSetLocale($langs[0]);
|
Pluf_Translation::loadSetLocale($langs[0]);
|
||||||
@@ -280,13 +322,16 @@ class IDF_Commit extends Pluf_Model
|
|||||||
);
|
);
|
||||||
$tmpl = new Pluf_Template('idf/source/commit-created-email.txt');
|
$tmpl = new Pluf_Template('idf/source/commit-created-email.txt');
|
||||||
$text_email = $tmpl->render($context);
|
$text_email = $tmpl->render($context);
|
||||||
$email = new Pluf_Mail(Pluf::f('from_email'),
|
$addresses = explode(',', $conf->getVal('source_notification_email'));
|
||||||
$conf->getVal('source_notification_email'),
|
foreach ($addresses as $address) {
|
||||||
sprintf(__('New Commit %s - %s (%s)'),
|
$email = new Pluf_Mail(Pluf::f('from_email'),
|
||||||
$this->scm_id, $this->summary,
|
$address,
|
||||||
$this->get_project()->shortname));
|
sprintf(__('New Commit %s - %s (%s)'),
|
||||||
$email->addTextMessage($text_email);
|
$this->scm_id, $this->summary,
|
||||||
$email->sendMail();
|
$this->get_project()->shortname));
|
||||||
|
$email->addTextMessage($text_email);
|
||||||
|
$email->sendMail();
|
||||||
|
}
|
||||||
Pluf_Translation::loadSetLocale($current_locale);
|
Pluf_Translation::loadSetLocale($current_locale);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,6 +71,28 @@ class IDF_Diff
|
|||||||
$current_chunk = 0;
|
$current_chunk = 0;
|
||||||
$indiff = true;
|
$indiff = true;
|
||||||
continue;
|
continue;
|
||||||
|
} else if (!$indiff && 0 === strpos($line, '=========')) {
|
||||||
|
// ignore pseudo stanzas with a hint of a binary file
|
||||||
|
if (preg_match("/^# (.+) is binary/", $this->lines[$i]))
|
||||||
|
continue;
|
||||||
|
// by default always use the new name of a possibly renamed file
|
||||||
|
$current_file = self::getMtnFile($this->lines[$i+1]);
|
||||||
|
// mtn 0.48 and newer set /dev/null as file path for dropped files
|
||||||
|
// so we display the old name here
|
||||||
|
if ($current_file == "/dev/null") {
|
||||||
|
$current_file = self::getMtnFile($this->lines[$i]);
|
||||||
|
}
|
||||||
|
if ($current_file == "/dev/null") {
|
||||||
|
throw new Exception(
|
||||||
|
"could not determine path from diff"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
$files[$current_file] = array();
|
||||||
|
$files[$current_file]['chunks'] = array();
|
||||||
|
$files[$current_file]['chunks_def'] = array();
|
||||||
|
$current_chunk = 0;
|
||||||
|
$indiff = true;
|
||||||
|
continue;
|
||||||
} else if (0 === strpos($line, 'Index: ')) {
|
} else if (0 === strpos($line, 'Index: ')) {
|
||||||
$current_file = self::getSvnFile($line);
|
$current_file = self::getSvnFile($line);
|
||||||
$files[$current_file] = array();
|
$files[$current_file] = array();
|
||||||
@@ -133,6 +155,12 @@ class IDF_Diff
|
|||||||
return substr(trim($line), 7);
|
return substr(trim($line), 7);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function getMtnFile($line)
|
||||||
|
{
|
||||||
|
preg_match("/^[+-]{3} ([^\t]+)/", $line, $m);
|
||||||
|
return $m[1];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the html version of a parsed diff.
|
* Return the html version of a parsed diff.
|
||||||
*/
|
*/
|
||||||
@@ -141,8 +169,8 @@ class IDF_Diff
|
|||||||
$out = '';
|
$out = '';
|
||||||
foreach ($this->files as $filename=>$file) {
|
foreach ($this->files as $filename=>$file) {
|
||||||
$pretty = '';
|
$pretty = '';
|
||||||
$fileinfo = IDF_Views_Source::getMimeType($filename);
|
$fileinfo = IDF_FileUtil::getMimeType($filename);
|
||||||
if (IDF_Views_Source::isSupportedExtension($fileinfo[2])) {
|
if (IDF_FileUtil::isSupportedExtension($fileinfo[2])) {
|
||||||
$pretty = ' prettyprint';
|
$pretty = ' prettyprint';
|
||||||
}
|
}
|
||||||
$out .= "\n".'<table class="diff" summary="">'."\n";
|
$out .= "\n".'<table class="diff" summary="">'."\n";
|
||||||
@@ -322,9 +350,9 @@ class IDF_Diff
|
|||||||
|
|
||||||
public function renderCompared($chunks, $filename)
|
public function renderCompared($chunks, $filename)
|
||||||
{
|
{
|
||||||
$fileinfo = IDF_Views_Source::getMimeType($filename);
|
$fileinfo = IDF_FileUtil::getMimeType($filename);
|
||||||
$pretty = '';
|
$pretty = '';
|
||||||
if (IDF_Views_Source::isSupportedExtension($fileinfo[2])) {
|
if (IDF_FileUtil::isSupportedExtension($fileinfo[2])) {
|
||||||
$pretty = ' prettyprint';
|
$pretty = ' prettyprint';
|
||||||
}
|
}
|
||||||
$out = '';
|
$out = '';
|
||||||
|
|||||||
165
src/IDF/FileUtil.php
Normal file
165
src/IDF/FileUtil.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) 2010 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 ***** */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* File utilities.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class IDF_FileUtil
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Extension supported by the syntax highlighter.
|
||||||
|
*/
|
||||||
|
public static $supportedExtenstions = array(
|
||||||
|
'ascx', 'ashx', 'asmx', 'aspx', 'browser', 'bsh', 'c', 'cl', 'cc',
|
||||||
|
'config', 'cpp', 'cs', 'csh', 'csproj', 'css', 'cv', 'cyc', 'el', 'fs',
|
||||||
|
'h', 'hh', 'hpp', 'hs', 'html', 'html', 'java', 'js', 'lisp', 'master',
|
||||||
|
'pas', 'perl', 'php', 'pl', 'pm', 'py', 'rb', 'scm', 'sh', 'sitemap',
|
||||||
|
'skin', 'sln', 'svc', 'vala', 'vb', 'vbproj', 'vbs', 'wsdl', 'xhtml',
|
||||||
|
'xml', 'xsd', 'xsl', 'xslt');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test if an extension is supported by the syntax highlighter.
|
||||||
|
*
|
||||||
|
* @param string The extension to test
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function isSupportedExtension($extension)
|
||||||
|
{
|
||||||
|
return in_array($extension, self::$supportedExtenstions);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a HTML snippet with a line-by-line pre-rendered table
|
||||||
|
* for the given source content
|
||||||
|
*
|
||||||
|
* @param array file information as returned by getMimeType or getMimeTypeFromContent
|
||||||
|
* @param string the content of the file
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function highLight($fileinfo, $content)
|
||||||
|
{
|
||||||
|
$pretty = '';
|
||||||
|
if (self::isSupportedExtension($fileinfo[2])) {
|
||||||
|
$pretty = ' prettyprint';
|
||||||
|
}
|
||||||
|
$table = array();
|
||||||
|
$i = 1;
|
||||||
|
foreach (preg_split("/\015\012|\015|\012/", $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>';
|
||||||
|
$i++;
|
||||||
|
}
|
||||||
|
return Pluf_Template::markSafe(implode("\n", $table));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the mime type of a file.
|
||||||
|
*
|
||||||
|
* Use /etc/mime.types to find the type.
|
||||||
|
*
|
||||||
|
* @param string Filename/Filepath
|
||||||
|
* @param array Mime type found or 'application/octet-stream', basename, extension
|
||||||
|
*/
|
||||||
|
public static function getMimeType($file)
|
||||||
|
{
|
||||||
|
static $mimes = null;
|
||||||
|
if ($mimes == null) {
|
||||||
|
$mimes = array();
|
||||||
|
$src = Pluf::f('idf_mimetypes_db', '/etc/mime.types');
|
||||||
|
$filecontent = @file_get_contents($src);
|
||||||
|
if ($filecontent !== false) {
|
||||||
|
$mimes = preg_split("/\015\012|\015|\012/", $filecontent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$info = pathinfo($file);
|
||||||
|
if (isset($info['extension'])) {
|
||||||
|
foreach ($mimes as $mime) {
|
||||||
|
if ('#' != substr($mime, 0, 1)) {
|
||||||
|
$elts = preg_split('/ |\t/', $mime, -1, PREG_SPLIT_NO_EMPTY);
|
||||||
|
if (in_array($info['extension'], $elts)) {
|
||||||
|
return array($elts[0], $info['basename'], $info['extension']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// we consider that if no extension and base name is all
|
||||||
|
// uppercase, then we have a text file.
|
||||||
|
if ($info['basename'] == strtoupper($info['basename'])) {
|
||||||
|
return array('text/plain', $info['basename'], 'txt');
|
||||||
|
}
|
||||||
|
$info['extension'] = 'bin';
|
||||||
|
}
|
||||||
|
return array('application/octet-stream', $info['basename'], $info['extension']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the mime type of a file using the fileinfo class.
|
||||||
|
*
|
||||||
|
* @param string Filename/Filepath
|
||||||
|
* @param string File content
|
||||||
|
* @return array Mime type found or 'application/octet-stream', basename, extension
|
||||||
|
*/
|
||||||
|
public static function getMimeTypeFromContent($file, $filedata)
|
||||||
|
{
|
||||||
|
$info = pathinfo($file);
|
||||||
|
$res = array('application/octet-stream',
|
||||||
|
$info['basename'],
|
||||||
|
isset($info['extension']) ? $info['extension'] : 'bin');
|
||||||
|
if (function_exists('finfo_open')) {
|
||||||
|
$finfo = finfo_open(FILEINFO_MIME);
|
||||||
|
$mime = finfo_buffer($finfo, $filedata);
|
||||||
|
finfo_close($finfo);
|
||||||
|
if ($mime) {
|
||||||
|
$res[0] = $mime;
|
||||||
|
}
|
||||||
|
if (!isset($info['extension']) && $mime) {
|
||||||
|
$res[2] = (0 === strpos($mime, 'text/')) ? 'txt' : 'bin';
|
||||||
|
} elseif (!isset($info['extension'])) {
|
||||||
|
$res[2] = 'bin';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $res;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find if a given mime type is a text file.
|
||||||
|
* This uses the output of the self::getMimeType function.
|
||||||
|
*
|
||||||
|
* @param array (Mime type, file name, extension)
|
||||||
|
* @return bool Is text
|
||||||
|
*/
|
||||||
|
public static function isText($fileinfo)
|
||||||
|
{
|
||||||
|
if (0 === strpos($fileinfo[0], 'text/')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
$ext = 'mdtext php-dist h gitignore diff patch';
|
||||||
|
$extra_ext = trim(Pluf::f('idf_extra_text_ext', ''));
|
||||||
|
if (!empty($extra_ext))
|
||||||
|
$ext .= ' ' . $extra_ext;
|
||||||
|
$ext = array_merge(self::$supportedExtenstions, explode(' ' , $ext));
|
||||||
|
return (in_array($fileinfo[2], $ext));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -38,6 +38,7 @@ class IDF_Form_Admin_ProjectCreate extends Pluf_Form
|
|||||||
'git' => __('git'),
|
'git' => __('git'),
|
||||||
'svn' => __('Subversion'),
|
'svn' => __('Subversion'),
|
||||||
'mercurial' => __('mercurial'),
|
'mercurial' => __('mercurial'),
|
||||||
|
'mtn' => __('monotone'),
|
||||||
);
|
);
|
||||||
foreach (Pluf::f('allowed_scm', array()) as $key => $class) {
|
foreach (Pluf::f('allowed_scm', array()) as $key => $class) {
|
||||||
$choices[$options[$key]] = $key;
|
$choices[$options[$key]] = $key;
|
||||||
@@ -63,6 +64,14 @@ class IDF_Form_Admin_ProjectCreate extends Pluf_Form
|
|||||||
'help_text' => __('It must be unique for each project and composed only of letters, digits and dash (-) like "my-project".'),
|
'help_text' => __('It must be unique for each project and composed only of letters, digits and dash (-) like "my-project".'),
|
||||||
));
|
));
|
||||||
|
|
||||||
|
$this->fields['shortdesc'] = new Pluf_Form_Field_Varchar(
|
||||||
|
array('required' => true,
|
||||||
|
'label' => __('Short description'),
|
||||||
|
'help_text' => __('A one line description of the project.'),
|
||||||
|
'initial' => '',
|
||||||
|
'widget_attrs' => array('size' => '35'),
|
||||||
|
));
|
||||||
|
|
||||||
$this->fields['scm'] = new Pluf_Form_Field_Varchar(
|
$this->fields['scm'] = new Pluf_Form_Field_Varchar(
|
||||||
array('required' => true,
|
array('required' => true,
|
||||||
'label' => __('Repository type'),
|
'label' => __('Repository type'),
|
||||||
@@ -92,6 +101,14 @@ class IDF_Form_Admin_ProjectCreate extends Pluf_Form
|
|||||||
'widget' => 'Pluf_Form_Widget_PasswordInput',
|
'widget' => 'Pluf_Form_Widget_PasswordInput',
|
||||||
));
|
));
|
||||||
|
|
||||||
|
$this->fields['mtn_master_branch'] = new Pluf_Form_Field_Varchar(
|
||||||
|
array('required' => false,
|
||||||
|
'label' => __('Master branch'),
|
||||||
|
'initial' => '',
|
||||||
|
'widget_attrs' => array('size' => '35'),
|
||||||
|
'help_text' => __('This should be a world-wide unique identifier for your project. A reverse DNS notation like "com.my-domain.my-project" is a good idea.'),
|
||||||
|
));
|
||||||
|
|
||||||
$this->fields['owners'] = new Pluf_Form_Field_Varchar(
|
$this->fields['owners'] = new Pluf_Form_Field_Varchar(
|
||||||
array('required' => false,
|
array('required' => false,
|
||||||
'label' => __('Project owners'),
|
'label' => __('Project owners'),
|
||||||
@@ -109,6 +126,20 @@ class IDF_Form_Admin_ProjectCreate extends Pluf_Form
|
|||||||
'cols' => 40),
|
'cols' => 40),
|
||||||
'widget' => 'Pluf_Form_Widget_TextareaInput',
|
'widget' => 'Pluf_Form_Widget_TextareaInput',
|
||||||
));
|
));
|
||||||
|
|
||||||
|
$projects = array('--' => '--');
|
||||||
|
foreach (Pluf::factory('IDF_Project')->getList(array('order' => 'name ASC')) as $proj) {
|
||||||
|
$projects[$proj->name] = $proj->shortname;
|
||||||
|
}
|
||||||
|
$this->fields['template'] = new Pluf_Form_Field_Varchar(
|
||||||
|
array('required' => false,
|
||||||
|
'label' => __('Project template'),
|
||||||
|
'initial' => '--',
|
||||||
|
'help_text' => __('Use the given project to initialize the new project. Access rights and general configuration will be taken from the template project.'),
|
||||||
|
'widget' => 'Pluf_Form_Widget_SelectInput',
|
||||||
|
'widget_attrs' => array('choices' => $projects),
|
||||||
|
));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* [signal]
|
* [signal]
|
||||||
*
|
*
|
||||||
@@ -156,6 +187,34 @@ class IDF_Form_Admin_ProjectCreate extends Pluf_Form
|
|||||||
return $url;
|
return $url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function clean_mtn_master_branch()
|
||||||
|
{
|
||||||
|
// do not validate, but empty the field if a different
|
||||||
|
// SCM should be used
|
||||||
|
if ($this->cleaned_data['scm'] != 'mtn')
|
||||||
|
return '';
|
||||||
|
|
||||||
|
$mtn_master_branch = mb_strtolower($this->cleaned_data['mtn_master_branch']);
|
||||||
|
if (!preg_match('/^([\w\d]+([-][\w\d]+)*)(\.[\w\d]+([-][\w\d]+)*)*$/',
|
||||||
|
$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.'
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql = new Pluf_SQL('vkey=%s AND vdesc=%s',
|
||||||
|
array('mtn_master_branch', $mtn_master_branch));
|
||||||
|
$l = Pluf::factory('IDF_Conf')->getList(array('filter'=>$sql->gen()));
|
||||||
|
if ($l->count() > 0) {
|
||||||
|
throw new Pluf_Form_Invalid(__(
|
||||||
|
'This master branch is already used. Please select another one.'
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $mtn_master_branch;
|
||||||
|
}
|
||||||
|
|
||||||
public function clean_shortname()
|
public function clean_shortname()
|
||||||
{
|
{
|
||||||
$shortname = mb_strtolower($this->cleaned_data['shortname']);
|
$shortname = mb_strtolower($this->cleaned_data['shortname']);
|
||||||
@@ -184,6 +243,11 @@ class IDF_Form_Admin_ProjectCreate extends Pluf_Form
|
|||||||
$this->cleaned_data[$key] = '';
|
$this->cleaned_data[$key] = '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($this->cleaned_data['scm'] != 'mtn') {
|
||||||
|
$this->cleaned_data['mtn_master_branch'] = '';
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* [signal]
|
* [signal]
|
||||||
*
|
*
|
||||||
@@ -217,24 +281,88 @@ class IDF_Form_Admin_ProjectCreate extends Pluf_Form
|
|||||||
$project = new IDF_Project();
|
$project = new IDF_Project();
|
||||||
$project->name = $this->cleaned_data['name'];
|
$project->name = $this->cleaned_data['name'];
|
||||||
$project->shortname = $this->cleaned_data['shortname'];
|
$project->shortname = $this->cleaned_data['shortname'];
|
||||||
$project->private = $this->cleaned_data['private_project'];
|
$project->shortdesc = $this->cleaned_data['shortdesc'];
|
||||||
$project->description = __('Click on the Project Management tab to set the description of your project.');
|
|
||||||
|
if ($this->cleaned_data['template'] != '--') {
|
||||||
|
// Find the template project
|
||||||
|
$sql = new Pluf_SQL('shortname=%s',
|
||||||
|
array($this->cleaned_data['template']));
|
||||||
|
$tmpl = Pluf::factory('IDF_Project')->getOne(array('filter' => $sql->gen()));
|
||||||
|
$project->private = $tmpl->private;
|
||||||
|
$project->description = $tmpl->description;
|
||||||
|
} else {
|
||||||
|
$project->private = $this->cleaned_data['private_project'];
|
||||||
|
$project->description = __('Click on the Project Management tab to set the description of your project.');
|
||||||
|
}
|
||||||
$project->create();
|
$project->create();
|
||||||
$conf = new IDF_Conf();
|
$conf = new IDF_Conf();
|
||||||
$conf->setProject($project);
|
$conf->setProject($project);
|
||||||
$keys = array('scm', 'svn_remote_url',
|
$keys = array('scm', 'svn_remote_url', 'svn_username',
|
||||||
'svn_username', 'svn_password');
|
'svn_password', 'mtn_master_branch');
|
||||||
foreach ($keys as $key) {
|
foreach ($keys as $key) {
|
||||||
$this->cleaned_data[$key] = (!empty($this->cleaned_data[$key])) ?
|
$this->cleaned_data[$key] = (!empty($this->cleaned_data[$key])) ?
|
||||||
$this->cleaned_data[$key] : '';
|
$this->cleaned_data[$key] : '';
|
||||||
$conf->setVal($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,
|
||||||
|
'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));
|
||||||
|
}
|
||||||
|
}
|
||||||
$project->created();
|
$project->created();
|
||||||
IDF_Form_MembersConf::updateMemberships($project,
|
|
||||||
$this->cleaned_data);
|
if ($this->cleaned_data['template'] == '--') {
|
||||||
|
IDF_Form_MembersConf::updateMemberships($project,
|
||||||
|
$this->cleaned_data);
|
||||||
|
} else {
|
||||||
|
// Get the membership of the template $tmpl
|
||||||
|
IDF_Form_MembersConf::updateMemberships($project,
|
||||||
|
$tmpl->getMembershipData('string'));
|
||||||
|
}
|
||||||
$project->membershipsUpdated();
|
$project->membershipsUpdated();
|
||||||
return $project;
|
return $project;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check that the template project exists.
|
||||||
|
*/
|
||||||
|
public function clean_template()
|
||||||
|
{
|
||||||
|
if ($this->cleaned_data['template'] == '--') {
|
||||||
|
return $this->cleaned_data['template'];
|
||||||
|
}
|
||||||
|
$sql = new Pluf_SQL('shortname=%s', array($this->cleaned_data['template']));
|
||||||
|
if (Pluf::factory('IDF_Project')->getOne(array('filter' => $sql->gen())) == null) {
|
||||||
|
throw new Pluf_Form_Invalid(__('This project is not available.'));
|
||||||
|
}
|
||||||
|
return $this->cleaned_data['template'];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -37,12 +37,32 @@ class IDF_Form_Admin_ProjectUpdate extends Pluf_Form
|
|||||||
{
|
{
|
||||||
$this->project = $extra['project'];
|
$this->project = $extra['project'];
|
||||||
$members = $this->project->getMembershipData('string');
|
$members = $this->project->getMembershipData('string');
|
||||||
|
$conf = $this->project->getConf();
|
||||||
|
|
||||||
$this->fields['name'] = new Pluf_Form_Field_Varchar(
|
$this->fields['name'] = new Pluf_Form_Field_Varchar(
|
||||||
array('required' => true,
|
array('required' => true,
|
||||||
'label' => __('Name'),
|
'label' => __('Name'),
|
||||||
'initial' => $this->project->name,
|
'initial' => $this->project->name,
|
||||||
));
|
));
|
||||||
|
|
||||||
|
$this->fields['shortdesc'] = new Pluf_Form_Field_Varchar(
|
||||||
|
array('required' => true,
|
||||||
|
'label' => __('Short description'),
|
||||||
|
'help_text' => __('A one line description of the project.'),
|
||||||
|
'initial' => $this->project->shortdesc,
|
||||||
|
'widget_attrs' => array('size' => '35'),
|
||||||
|
));
|
||||||
|
|
||||||
|
if ($this->project->getConf()->getVal('scm') == 'mtn') {
|
||||||
|
$this->fields['mtn_master_branch'] = new Pluf_Form_Field_Varchar(
|
||||||
|
array('required' => false,
|
||||||
|
'label' => __('Master branch'),
|
||||||
|
'initial' => $conf->getVal('mtn_master_branch'),
|
||||||
|
'widget_attrs' => array('size' => '35'),
|
||||||
|
'help_text' => __('This should be a world-wide unique identifier for your project. A reverse DNS notation like "com.my-domain.my-project" is a good idea.'),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
$this->fields['owners'] = new Pluf_Form_Field_Varchar(
|
$this->fields['owners'] = new Pluf_Form_Field_Varchar(
|
||||||
array('required' => false,
|
array('required' => false,
|
||||||
'label' => __('Project owners'),
|
'label' => __('Project owners'),
|
||||||
@@ -61,6 +81,30 @@ class IDF_Form_Admin_ProjectUpdate extends Pluf_Form
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function clean_mtn_master_branch()
|
||||||
|
{
|
||||||
|
$mtn_master_branch = mb_strtolower($this->cleaned_data['mtn_master_branch']);
|
||||||
|
if (!preg_match('/^([\w\d]+([-][\w\d]+)*)(\.[\w\d]+([-][\w\d]+)*)*$/',
|
||||||
|
$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.'
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql = new Pluf_SQL('vkey=%s AND vdesc=%s AND project!=%s',
|
||||||
|
array('mtn_master_branch', $mtn_master_branch,
|
||||||
|
(string)$this->project->id));
|
||||||
|
$l = Pluf::factory('IDF_Conf')->getList(array('filter'=>$sql->gen()));
|
||||||
|
if ($l->count() > 0) {
|
||||||
|
throw new Pluf_Form_Invalid(__(
|
||||||
|
'This master branch is already used. Please select another one.'
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $mtn_master_branch;
|
||||||
|
}
|
||||||
|
|
||||||
public function clean_owners()
|
public function clean_owners()
|
||||||
{
|
{
|
||||||
return IDF_Form_MembersConf::checkBadLogins($this->cleaned_data['owners']);
|
return IDF_Form_MembersConf::checkBadLogins($this->cleaned_data['owners']);
|
||||||
@@ -80,7 +124,15 @@ class IDF_Form_Admin_ProjectUpdate extends Pluf_Form
|
|||||||
$this->cleaned_data);
|
$this->cleaned_data);
|
||||||
$this->project->membershipsUpdated();
|
$this->project->membershipsUpdated();
|
||||||
$this->project->name = $this->cleaned_data['name'];
|
$this->project->name = $this->cleaned_data['name'];
|
||||||
|
$this->project->shortdesc = $this->cleaned_data['shortdesc'];
|
||||||
$this->project->update();
|
$this->project->update();
|
||||||
|
|
||||||
|
$keys = array('mtn_master_branch');
|
||||||
|
foreach ($keys as $key) {
|
||||||
|
if (!empty($this->cleaned_data[$key])) {
|
||||||
|
$this->project->getConf()->setVal($key, $this->cleaned_data[$key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -82,17 +82,15 @@ class IDF_Form_Admin_UserCreate extends Pluf_Form
|
|||||||
),
|
),
|
||||||
));
|
));
|
||||||
|
|
||||||
$this->fields['ssh_key'] = new Pluf_Form_Field_Varchar(
|
$this->fields['public_key'] = new Pluf_Form_Field_Varchar(
|
||||||
array('required' => false,
|
array('required' => false,
|
||||||
'label' => __('Add a public SSH key'),
|
'label' => __('Add a public key'),
|
||||||
'initial' => '',
|
'initial' => '',
|
||||||
'widget_attrs' => array('rows' => 3,
|
'widget_attrs' => array('rows' => 3,
|
||||||
'cols' => 40),
|
'cols' => 40),
|
||||||
'widget' => 'Pluf_Form_Widget_TextareaInput',
|
'widget' => 'Pluf_Form_Widget_TextareaInput',
|
||||||
'help_text' => __('Be careful to provide the public key and not the private key!')
|
'help_text' => __('Paste a SSH or monotone public key. Be careful to not provide your private key here!')
|
||||||
));
|
));
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -137,11 +135,11 @@ class IDF_Form_Admin_UserCreate extends Pluf_Form
|
|||||||
$params = array('user' => $user);
|
$params = array('user' => $user);
|
||||||
Pluf_Signal::send('Pluf_User::passwordUpdated',
|
Pluf_Signal::send('Pluf_User::passwordUpdated',
|
||||||
'IDF_Form_Admin_UserCreate', $params);
|
'IDF_Form_Admin_UserCreate', $params);
|
||||||
// Create the ssh key as needed
|
// Create the public key as needed
|
||||||
if ('' !== $this->cleaned_data['ssh_key']) {
|
if ('' !== $this->cleaned_data['public_key']) {
|
||||||
$key = new IDF_Key();
|
$key = new IDF_Key();
|
||||||
$key->user = $user;
|
$key->user = $user;
|
||||||
$key->content = $this->cleaned_data['ssh_key'];
|
$key->content = $this->cleaned_data['public_key'];
|
||||||
$key->create();
|
$key->create();
|
||||||
}
|
}
|
||||||
// Send an email to the user with the password
|
// Send an email to the user with the password
|
||||||
@@ -162,11 +160,6 @@ class IDF_Form_Admin_UserCreate extends Pluf_Form
|
|||||||
return $user;
|
return $user;
|
||||||
}
|
}
|
||||||
|
|
||||||
function clean_ssh_key()
|
|
||||||
{
|
|
||||||
return IDF_Form_UserAccount::checkSshKey($this->cleaned_data['ssh_key']);
|
|
||||||
}
|
|
||||||
|
|
||||||
function clean_last_name()
|
function clean_last_name()
|
||||||
{
|
{
|
||||||
$last_name = trim($this->cleaned_data['last_name']);
|
$last_name = trim($this->cleaned_data['last_name']);
|
||||||
@@ -211,4 +204,12 @@ class IDF_Form_Admin_UserCreate extends Pluf_Form
|
|||||||
}
|
}
|
||||||
return $this->cleaned_data['login'];
|
return $this->cleaned_data['login'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function clean_public_key()
|
||||||
|
{
|
||||||
|
$this->cleaned_data['public_key'] =
|
||||||
|
IDF_Form_UserAccount::checkPublicKey($this->cleaned_data['public_key']);
|
||||||
|
|
||||||
|
return $this->cleaned_data['public_key'];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,13 +24,15 @@
|
|||||||
/**
|
/**
|
||||||
* Update user's details.
|
* Update user's details.
|
||||||
*/
|
*/
|
||||||
class IDF_Form_Admin_UserUpdate extends Pluf_Form
|
class IDF_Form_Admin_UserUpdate extends Pluf_Form
|
||||||
{
|
{
|
||||||
public $user = null;
|
public $user = null;
|
||||||
|
|
||||||
public function initFields($extra=array())
|
public function initFields($extra=array())
|
||||||
{
|
{
|
||||||
$this->user = $extra['user'];
|
$this->user = $extra['user'];
|
||||||
|
$user_data = IDF_UserData::factory($this->user);
|
||||||
|
|
||||||
$this->fields['first_name'] = new Pluf_Form_Field_Varchar(
|
$this->fields['first_name'] = new Pluf_Form_Field_Varchar(
|
||||||
array('required' => false,
|
array('required' => false,
|
||||||
'label' => __('First name'),
|
'label' => __('First name'),
|
||||||
@@ -93,6 +95,66 @@ class IDF_Form_Admin_UserUpdate extends Pluf_Form
|
|||||||
),
|
),
|
||||||
));
|
));
|
||||||
|
|
||||||
|
$this->fields['description'] = new Pluf_Form_Field_Varchar(
|
||||||
|
array('required' => false,
|
||||||
|
'label' => __('Description'),
|
||||||
|
'initial' => $user_data->description,
|
||||||
|
'widget_attrs' => array('rows' => 3,
|
||||||
|
'cols' => 40),
|
||||||
|
'widget' => 'Pluf_Form_Widget_TextareaInput',
|
||||||
|
));
|
||||||
|
|
||||||
|
$this->fields['twitter'] = new Pluf_Form_Field_Varchar(
|
||||||
|
array('required' => false,
|
||||||
|
'label' => __('Twitter username'),
|
||||||
|
'initial' => $user_data->twitter,
|
||||||
|
'widget_attrs' => array(
|
||||||
|
'maxlength' => 50,
|
||||||
|
'size' => 15,
|
||||||
|
),
|
||||||
|
));
|
||||||
|
|
||||||
|
$this->fields['public_email'] = new Pluf_Form_Field_Email(
|
||||||
|
array('required' => false,
|
||||||
|
'label' => __('Public email address'),
|
||||||
|
'initial' => $user_data->public_email,
|
||||||
|
'widget_attrs' => array(
|
||||||
|
'maxlength' => 50,
|
||||||
|
'size' => 15,
|
||||||
|
),
|
||||||
|
));
|
||||||
|
|
||||||
|
$this->fields['website'] = new Pluf_Form_Field_Url(
|
||||||
|
array('required' => false,
|
||||||
|
'label' => __('Website URL'),
|
||||||
|
'initial' => $user_data->website,
|
||||||
|
'widget_attrs' => array(
|
||||||
|
'maxlength' => 50,
|
||||||
|
'size' => 15,
|
||||||
|
),
|
||||||
|
));
|
||||||
|
|
||||||
|
$this->fields['custom_avatar'] = new Pluf_Form_Field_File(
|
||||||
|
array('required' => false,
|
||||||
|
'label' => __('Upload custom avatar'),
|
||||||
|
'initial' => '',
|
||||||
|
'max_size' => Pluf::f('max_upload_size', 2097152),
|
||||||
|
'move_function_params' => array('upload_path' => Pluf::f('upload_path').'/avatars',
|
||||||
|
'upload_path_create' => true,
|
||||||
|
'upload_overwrite' => true,
|
||||||
|
'file_name' => 'user_'.$this->user->id.'_%s'),
|
||||||
|
'help_text' => __('An image file with a width and height not larger than 60 pixels (bigger images are scaled down).'),
|
||||||
|
));
|
||||||
|
|
||||||
|
$this->fields['remove_custom_avatar'] = new Pluf_Form_Field_Boolean(
|
||||||
|
array('required' => false,
|
||||||
|
'label' => __('Remove custom avatar'),
|
||||||
|
'initial' => false,
|
||||||
|
'widget' => 'Pluf_Form_Widget_CheckboxInput',
|
||||||
|
'widget_attrs' => array(),
|
||||||
|
'help_text' => __('Tick this to delete the custom avatar.'),
|
||||||
|
));
|
||||||
|
|
||||||
if ($extra['request']->user->administrator) {
|
if ($extra['request']->user->administrator) {
|
||||||
$this->fields['staff'] = new Pluf_Form_Field_Boolean(
|
$this->fields['staff'] = new Pluf_Form_Field_Boolean(
|
||||||
array('required' => false,
|
array('required' => false,
|
||||||
@@ -136,8 +198,37 @@ class IDF_Form_Admin_UserUpdate extends Pluf_Form
|
|||||||
$update_pass = true;
|
$update_pass = true;
|
||||||
}
|
}
|
||||||
$this->user->setFromFormData($this->cleaned_data);
|
$this->user->setFromFormData($this->cleaned_data);
|
||||||
|
|
||||||
if ($commit) {
|
if ($commit) {
|
||||||
$this->user->update();
|
$this->user->update();
|
||||||
|
|
||||||
|
// FIXME: go the extra mile and check the input lengths for
|
||||||
|
// all fields here!
|
||||||
|
// FIXME: this is all doubled in UserAccount!
|
||||||
|
|
||||||
|
$user_data = IDF_UserData::factory($this->user);
|
||||||
|
|
||||||
|
// Add or remove avatar - we need to do this here because every
|
||||||
|
// single setter directly leads to a save in the database
|
||||||
|
if ($user_data->avatar != '' &&
|
||||||
|
($this->cleaned_data['remove_custom_avatar'] == 1 ||
|
||||||
|
$this->cleaned_data['custom_avatar'] != '')) {
|
||||||
|
$avatar_path = Pluf::f('upload_path').'/avatars/'.basename($user_data->avatar);
|
||||||
|
if (basename($avatar_path) != '' && is_file($avatar_path)) {
|
||||||
|
unlink($avatar_path);
|
||||||
|
}
|
||||||
|
$user_data->avatar = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->cleaned_data['custom_avatar'] != '') {
|
||||||
|
$user_data->avatar = $this->cleaned_data['custom_avatar'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$user_data->description = $this->cleaned_data['description'];
|
||||||
|
$user_data->twitter = $this->cleaned_data['twitter'];
|
||||||
|
$user_data->public_email = $this->cleaned_data['public_email'];
|
||||||
|
$user_data->website = $this->cleaned_data['website'];
|
||||||
|
|
||||||
if ($update_pass) {
|
if ($update_pass) {
|
||||||
/**
|
/**
|
||||||
* [signal]
|
* [signal]
|
||||||
@@ -201,8 +292,19 @@ class IDF_Form_Admin_UserUpdate extends Pluf_Form
|
|||||||
return $email;
|
return $email;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function clean_custom_avatar()
|
||||||
|
{
|
||||||
|
// Just png, jpeg/jpg or gif
|
||||||
|
if (!preg_match('/\.(png|jpg|jpeg|gif)$/i', $this->cleaned_data['custom_avatar']) &&
|
||||||
|
$this->cleaned_data['custom_avatar'] != '') {
|
||||||
|
@unlink(Pluf::f('upload_path').'/avatars/'.$this->cleaned_data['custom_avatar']);
|
||||||
|
throw new Pluf_Form_Invalid(__('For security reason, you cannot upload a file with this extension.'));
|
||||||
|
}
|
||||||
|
return $this->cleaned_data['custom_avatar'];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check to see if the 2 passwords are the same.
|
* Check to see if the two passwords are the same.
|
||||||
*/
|
*/
|
||||||
public function clean()
|
public function clean()
|
||||||
{
|
{
|
||||||
|
|||||||
51
src/IDF/Form/Field/EmailList.php
Normal file
51
src/IDF/Form/Field/EmailList.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) 2010 Céondo Ltd and contributors.
|
||||||
|
#
|
||||||
|
# Plume Framework is free software; you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
# the Free Software Foundation; either version 2.1 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Plume Framework 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 Lesser General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Lesser 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 ***** */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Similar to Pluf_Form_Field_Email, this form field validates one or more
|
||||||
|
* email addresses separated by a comma
|
||||||
|
*/
|
||||||
|
class IDF_Form_Field_EmailList extends Pluf_Form_Field
|
||||||
|
{
|
||||||
|
public $widget = 'Pluf_Form_Widget_TextInput';
|
||||||
|
|
||||||
|
public function clean($value)
|
||||||
|
{
|
||||||
|
parent::clean($value);
|
||||||
|
if (in_array($value, $this->empty_values)) {
|
||||||
|
$value = '';
|
||||||
|
}
|
||||||
|
if ($value == '') {
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
$emails = preg_split('/\s*,\s*/', $value, -1, PREG_SPLIT_NO_EMPTY);
|
||||||
|
foreach ($emails as $email) {
|
||||||
|
if (!Pluf_Utils::isValidEmail($email)) {
|
||||||
|
throw new Pluf_Form_Invalid(__(
|
||||||
|
'Please enter one or more valid email addresses.'
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return implode(',', $emails);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -45,6 +45,9 @@ class IDF_Form_IssueCreate extends Pluf_Form
|
|||||||
or $this->user->hasPerm('IDF.project-member', $this->project)) {
|
or $this->user->hasPerm('IDF.project-member', $this->project)) {
|
||||||
$this->show_full = true;
|
$this->show_full = true;
|
||||||
}
|
}
|
||||||
|
$contentTemplate = $this->project->getConf()->getVal(
|
||||||
|
'labels_issue_template', IDF_Form_IssueTrackingConf::init_template
|
||||||
|
);
|
||||||
$this->fields['summary'] = new Pluf_Form_Field_Varchar(
|
$this->fields['summary'] = new Pluf_Form_Field_Varchar(
|
||||||
array('required' => true,
|
array('required' => true,
|
||||||
'label' => __('Summary'),
|
'label' => __('Summary'),
|
||||||
@@ -57,7 +60,7 @@ class IDF_Form_IssueCreate extends Pluf_Form
|
|||||||
$this->fields['content'] = new Pluf_Form_Field_Varchar(
|
$this->fields['content'] = new Pluf_Form_Field_Varchar(
|
||||||
array('required' => true,
|
array('required' => true,
|
||||||
'label' => __('Description'),
|
'label' => __('Description'),
|
||||||
'initial' => '',
|
'initial' => $contentTemplate,
|
||||||
'widget' => 'Pluf_Form_Widget_TextareaInput',
|
'widget' => 'Pluf_Form_Widget_TextareaInput',
|
||||||
'widget_attrs' => array(
|
'widget_attrs' => array(
|
||||||
'cols' => 58,
|
'cols' => 58,
|
||||||
@@ -105,14 +108,41 @@ class IDF_Form_IssueCreate extends Pluf_Form
|
|||||||
'size' => 15,
|
'size' => 15,
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
|
|
||||||
|
/*
|
||||||
|
* get predefined tags for issues from current project
|
||||||
|
*
|
||||||
|
* first Type:<...> and Priority:<...> will be used
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
$predefined = preg_split("/[\r\n]+/", $extra['project']->getConf()->getVal(
|
||||||
|
'labels_issue_predefined'
|
||||||
|
));
|
||||||
|
$predefined_type = 'Type:Defect';
|
||||||
|
foreach ($predefined as $tag) {
|
||||||
|
if (strpos($tag, 'Type:') === 0) {
|
||||||
|
$predefined_type = explode('=', $tag, 2);
|
||||||
|
$predefined_type = trim($predefined_type[0]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$predefined_priority = 'Priority:Medium';
|
||||||
|
foreach ($predefined as $tag) {
|
||||||
|
if (strpos($tag, 'Priority:') === 0) {
|
||||||
|
$predefined_priority = explode('=', $tag, 2);
|
||||||
|
$predefined_priority = trim($predefined_priority[0]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for ($i=1;$i<7;$i++) {
|
for ($i=1;$i<7;$i++) {
|
||||||
$initial = '';
|
$initial = '';
|
||||||
switch ($i) {
|
switch ($i) {
|
||||||
case 1:
|
case 1:
|
||||||
$initial = 'Type:Defect';
|
$initial = $predefined_type;
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
$initial = 'Priority:Medium';
|
$initial = $predefined_priority;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
$this->fields['label'.$i] = new Pluf_Form_Field_Varchar(
|
$this->fields['label'.$i] = new Pluf_Form_Field_Varchar(
|
||||||
@@ -276,6 +306,7 @@ class IDF_Form_IssueCreate extends Pluf_Form
|
|||||||
$comment->create();
|
$comment->create();
|
||||||
// If we have a file, create the IDF_IssueFile and attach
|
// If we have a file, create the IDF_IssueFile and attach
|
||||||
// it to the comment.
|
// it to the comment.
|
||||||
|
$created_files = array();
|
||||||
for ($i=1;$i<4;$i++) {
|
for ($i=1;$i<4;$i++) {
|
||||||
if ($this->cleaned_data['attachment'.$i]) {
|
if ($this->cleaned_data['attachment'.$i]) {
|
||||||
$file = new IDF_IssueFile();
|
$file = new IDF_IssueFile();
|
||||||
@@ -283,8 +314,36 @@ class IDF_Form_IssueCreate extends Pluf_Form
|
|||||||
$file->submitter = $this->user;
|
$file->submitter = $this->user;
|
||||||
$file->comment = $comment;
|
$file->comment = $comment;
|
||||||
$file->create();
|
$file->create();
|
||||||
|
$created_files[] = $file;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* [signal]
|
||||||
|
*
|
||||||
|
* IDF_Issue::create
|
||||||
|
*
|
||||||
|
* [sender]
|
||||||
|
*
|
||||||
|
* IDF_Form_IssueCreate
|
||||||
|
*
|
||||||
|
* [description]
|
||||||
|
*
|
||||||
|
* This signal allows an application to perform a set of tasks
|
||||||
|
* just after the creation of an issue. The comment contains
|
||||||
|
* the description of the issue.
|
||||||
|
*
|
||||||
|
* [parameters]
|
||||||
|
*
|
||||||
|
* array('issue' => $issue,
|
||||||
|
* 'comment' => $comment,
|
||||||
|
* 'files' => $attached_files);
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
$params = array('issue' => $issue,
|
||||||
|
'comment' => $comment,
|
||||||
|
'files' => $created_files);
|
||||||
|
Pluf_Signal::send('IDF_Issue::create', 'IDF_Form_IssueCreate',
|
||||||
|
$params);
|
||||||
return $issue;
|
return $issue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,15 @@ class IDF_Form_IssueTrackingConf extends Pluf_Form
|
|||||||
* Defined as constants to easily access the value in the
|
* Defined as constants to easily access the value in the
|
||||||
* IssueUpdate/Create form in the case nothing is in the db yet.
|
* IssueUpdate/Create form in the case nothing is in the db yet.
|
||||||
*/
|
*/
|
||||||
|
const init_template = 'Steps to reproduce the problem:
|
||||||
|
1.
|
||||||
|
2.
|
||||||
|
3.
|
||||||
|
|
||||||
|
Expected result:
|
||||||
|
|
||||||
|
Actual result:
|
||||||
|
';
|
||||||
const init_open = 'New = Issue has not had initial review yet
|
const init_open = 'New = Issue has not had initial review yet
|
||||||
Accepted = Problem reproduced / Need acknowledged
|
Accepted = Problem reproduced / Need acknowledged
|
||||||
Started = Work on this issue has begun';
|
Started = Work on this issue has begun';
|
||||||
@@ -66,6 +75,15 @@ Maintainability = Hinders future changes';
|
|||||||
|
|
||||||
public function initFields($extra=array())
|
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 the reporter to provide certain information'),
|
||||||
|
'initial' => self::init_template,
|
||||||
|
'widget_attrs' => array('rows' => 7,
|
||||||
|
'cols' => 75),
|
||||||
|
'widget' => 'Pluf_Form_Widget_TextareaInput',
|
||||||
|
));
|
||||||
|
|
||||||
$this->fields['labels_issue_open'] = new Pluf_Form_Field_Varchar(
|
$this->fields['labels_issue_open'] = new Pluf_Form_Field_Varchar(
|
||||||
array('required' => true,
|
array('required' => true,
|
||||||
'label' => __('Open issue status values'),
|
'label' => __('Open issue status values'),
|
||||||
@@ -87,6 +105,7 @@ Maintainability = Hinders future changes';
|
|||||||
array('required' => true,
|
array('required' => true,
|
||||||
'label' => __('Predefined issue labels'),
|
'label' => __('Predefined issue labels'),
|
||||||
'initial' => self::init_predefined,
|
'initial' => self::init_predefined,
|
||||||
|
'help_text' => __('The first "Type:" and "Priority:" entries found in this list are automatically chosen as defaults for new issues.'),
|
||||||
'widget_attrs' => array('rows' => 7,
|
'widget_attrs' => array('rows' => 7,
|
||||||
'cols' => 75),
|
'cols' => 75),
|
||||||
'widget' => 'Pluf_Form_Widget_TextareaInput',
|
'widget' => 'Pluf_Form_Widget_TextareaInput',
|
||||||
@@ -99,8 +118,6 @@ Maintainability = Hinders future changes';
|
|||||||
'widget_attrs' => array('size' => 60),
|
'widget_attrs' => array('size' => 60),
|
||||||
));
|
));
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -305,6 +305,7 @@ class IDF_Form_IssueUpdate extends IDF_Form_IssueCreate
|
|||||||
$this->issue->submitter != $this->user->id) {
|
$this->issue->submitter != $this->user->id) {
|
||||||
$this->issue->setAssoc($this->user); // interested user.
|
$this->issue->setAssoc($this->user); // interested user.
|
||||||
}
|
}
|
||||||
|
$attached_files = array();
|
||||||
for ($i=1;$i<4;$i++) {
|
for ($i=1;$i<4;$i++) {
|
||||||
if ($this->cleaned_data['attachment'.$i]) {
|
if ($this->cleaned_data['attachment'.$i]) {
|
||||||
$file = new IDF_IssueFile();
|
$file = new IDF_IssueFile();
|
||||||
@@ -312,8 +313,36 @@ class IDF_Form_IssueUpdate extends IDF_Form_IssueCreate
|
|||||||
$file->submitter = $this->user;
|
$file->submitter = $this->user;
|
||||||
$file->comment = $comment;
|
$file->comment = $comment;
|
||||||
$file->create();
|
$file->create();
|
||||||
|
$attached_files[] = $file;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* [signal]
|
||||||
|
*
|
||||||
|
* IDF_Issue::update
|
||||||
|
*
|
||||||
|
* [sender]
|
||||||
|
*
|
||||||
|
* IDF_Form_IssueUpdate
|
||||||
|
*
|
||||||
|
* [description]
|
||||||
|
*
|
||||||
|
* This signal allows an application to perform a set of tasks
|
||||||
|
* just after the update of an issue.
|
||||||
|
*
|
||||||
|
* [parameters]
|
||||||
|
*
|
||||||
|
* array('issue' => $issue,
|
||||||
|
* 'comment' => $comment,
|
||||||
|
* 'files' => $attached_files);
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
$params = array('issue' => $this->issue,
|
||||||
|
'comment' => $comment,
|
||||||
|
'files' => $attached_files);
|
||||||
|
Pluf_Signal::send('IDF_Issue::update', 'IDF_Form_IssueUpdate',
|
||||||
|
$params);
|
||||||
|
|
||||||
return $this->issue;
|
return $this->issue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ class IDF_Form_MembersConf extends Pluf_Form
|
|||||||
$n = count($bad);
|
$n = count($bad);
|
||||||
if ($n) {
|
if ($n) {
|
||||||
$badlogins = Pluf_esc(implode(', ', $bad));
|
$badlogins = Pluf_esc(implode(', ', $bad));
|
||||||
throw new Pluf_Form_Invalid(sprintf(_n('The following login is invalid: %s.', 'The following login are invalids: %s.', $n), $badlogins));
|
throw new Pluf_Form_Invalid(sprintf(_n('The following login is invalid: %s.', 'The following logins are invalid: %s.', $n), $badlogins));
|
||||||
}
|
}
|
||||||
return $logins;
|
return $logins;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -205,6 +205,30 @@ class IDF_Form_ReviewCreate extends Pluf_Form
|
|||||||
$patch->patch = $this->cleaned_data['patch'];
|
$patch->patch = $this->cleaned_data['patch'];
|
||||||
$patch->create();
|
$patch->create();
|
||||||
$patch->notify($this->project->getConf());
|
$patch->notify($this->project->getConf());
|
||||||
|
/**
|
||||||
|
* [signal]
|
||||||
|
*
|
||||||
|
* IDF_Review::create
|
||||||
|
*
|
||||||
|
* [sender]
|
||||||
|
*
|
||||||
|
* IDF_Form_ReviewCreate
|
||||||
|
*
|
||||||
|
* [description]
|
||||||
|
*
|
||||||
|
* This signal allows an application to perform a set of tasks
|
||||||
|
* just after the creation of a review and the notification.
|
||||||
|
*
|
||||||
|
* [parameters]
|
||||||
|
*
|
||||||
|
* array('review' => $review,
|
||||||
|
* 'patch' => $patch);
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
$params = array('review' => $review,
|
||||||
|
'patch' => $patch);
|
||||||
|
Pluf_Signal::send('IDF_Review::create', 'IDF_Form_ReviewCreate',
|
||||||
|
$params);
|
||||||
return $review;
|
return $review;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ class IDF_Form_ReviewFileComment extends Pluf_Form
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
$this->fields['content'] = new Pluf_Form_Field_Varchar(
|
$this->fields['content'] = new Pluf_Form_Field_Varchar(
|
||||||
array('required' => true,
|
array('required' => false,
|
||||||
'label' => __('General comment'),
|
'label' => __('General comment'),
|
||||||
'initial' => '',
|
'initial' => '',
|
||||||
'widget' => 'Pluf_Form_Widget_TextareaInput',
|
'widget' => 'Pluf_Form_Widget_TextareaInput',
|
||||||
@@ -94,23 +94,39 @@ class IDF_Form_ReviewFileComment extends Pluf_Form
|
|||||||
*/
|
*/
|
||||||
public function clean()
|
public function clean()
|
||||||
{
|
{
|
||||||
foreach ($this->files as $filename => $def) {
|
$isOk = false;
|
||||||
if (!empty($this->cleaned_data[md5($filename)])) {
|
|
||||||
return $this->cleaned_data;
|
foreach($this->files as $filename => $def) {
|
||||||
|
$this->cleaned_data[md5($filename)] = trim($this->cleaned_data[md5($filename)]);
|
||||||
|
if(!empty($this->cleaned_data[md5($filename)])) {
|
||||||
|
$isOk = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw new Pluf_Form_Invalid(__('You need to provide comments on at least one file.'));
|
|
||||||
|
if(!empty($this->cleaned_data['content'])) {
|
||||||
|
$isOk = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$isOk) {
|
||||||
|
throw new Pluf_Form_Invalid(__('You need to provide your general comment about the proposal, or comments on at least one file.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->cleaned_data;
|
||||||
}
|
}
|
||||||
|
|
||||||
function clean_content()
|
function clean_content()
|
||||||
{
|
{
|
||||||
$content = trim($this->cleaned_data['content']);
|
$content = trim($this->cleaned_data['content']);
|
||||||
if (!$this->show_full and strlen($content) == 0) {
|
if(empty($content)) {
|
||||||
throw new Pluf_Form_Invalid(__('You need to provide your general comment about the proposal.'));
|
if ($this->fields['status']->initial != $this->fields['status']->value) {
|
||||||
|
return __('The status have been updated.');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return $content;
|
||||||
}
|
}
|
||||||
return $content;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
throw new Pluf_Form_Invalid(__('This field is required.'));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save the model in the database.
|
* Save the model in the database.
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ class IDF_Form_SourceConf extends Pluf_Form
|
|||||||
public function initFields($extra=array())
|
public function initFields($extra=array())
|
||||||
{
|
{
|
||||||
$this->conf = $extra['conf'];
|
$this->conf = $extra['conf'];
|
||||||
if ($this->conf->getVal('scm', 'git') == 'svn') {
|
if ($extra['remote_svn']) {
|
||||||
$this->fields['svn_username'] = new Pluf_Form_Field_Varchar(
|
$this->fields['svn_username'] = new Pluf_Form_Field_Varchar(
|
||||||
array('required' => false,
|
array('required' => false,
|
||||||
'label' => __('Repository username'),
|
'label' => __('Repository username'),
|
||||||
@@ -49,6 +49,16 @@ class IDF_Form_SourceConf extends Pluf_Form
|
|||||||
'widget' => 'Pluf_Form_Widget_PasswordInput',
|
'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),
|
||||||
|
));
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -63,10 +63,11 @@ class IDF_Form_TabsConf extends Pluf_Form
|
|||||||
'source_notification_email',
|
'source_notification_email',
|
||||||
'issues_notification_email',);
|
'issues_notification_email',);
|
||||||
foreach ($ak as $key) {
|
foreach ($ak as $key) {
|
||||||
$this->fields[$key] = new Pluf_Form_Field_Email(
|
$this->fields[$key] = new IDF_Form_Field_EmailList(
|
||||||
array('required' => false,
|
array('required' => false,
|
||||||
'label' => $key,
|
'label' => $key,
|
||||||
'initial' => $this->conf->getVal($key, ''),
|
'initial' => $this->conf->getVal($key, ''),
|
||||||
|
'widget_attrs' => array('size' => 40),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -146,6 +146,28 @@ class IDF_Form_UpdateUpload extends Pluf_Form
|
|||||||
$this->upload->modif_dtime = gmdate('Y-m-d H:i:s');
|
$this->upload->modif_dtime = gmdate('Y-m-d H:i:s');
|
||||||
$this->upload->update();
|
$this->upload->update();
|
||||||
$this->upload->batchAssoc('IDF_Tag', $tags);
|
$this->upload->batchAssoc('IDF_Tag', $tags);
|
||||||
|
/**
|
||||||
|
* [signal]
|
||||||
|
*
|
||||||
|
* IDF_Upload::update
|
||||||
|
*
|
||||||
|
* [sender]
|
||||||
|
*
|
||||||
|
* IDF_Form_UpdateUpload
|
||||||
|
*
|
||||||
|
* [description]
|
||||||
|
*
|
||||||
|
* This signal allows an application to perform a set of tasks
|
||||||
|
* just after the update of an uploaded file.
|
||||||
|
*
|
||||||
|
* [parameters]
|
||||||
|
*
|
||||||
|
* array('upload' => $upload);
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
$params = array('upload' => $this->upload);
|
||||||
|
Pluf_Signal::send('IDF_Upload::update',
|
||||||
|
'IDF_Form_UpdateUpload', $params);
|
||||||
return $this->upload;
|
return $this->upload;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -176,6 +176,28 @@ class IDF_Form_Upload extends Pluf_Form
|
|||||||
}
|
}
|
||||||
// Send the notification
|
// Send the notification
|
||||||
$upload->notify($this->project->getConf());
|
$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);
|
||||||
return $upload;
|
return $upload;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ class IDF_Form_UserAccount extends Pluf_Form
|
|||||||
public function initFields($extra=array())
|
public function initFields($extra=array())
|
||||||
{
|
{
|
||||||
$this->user = $extra['user'];
|
$this->user = $extra['user'];
|
||||||
|
$user_data = IDF_UserData::factory($this->user);
|
||||||
|
|
||||||
$this->fields['first_name'] = new Pluf_Form_Field_Varchar(
|
$this->fields['first_name'] = new Pluf_Form_Field_Varchar(
|
||||||
array('required' => false,
|
array('required' => false,
|
||||||
'label' => __('First name'),
|
'label' => __('First name'),
|
||||||
@@ -92,17 +94,75 @@ class IDF_Form_UserAccount extends Pluf_Form
|
|||||||
),
|
),
|
||||||
));
|
));
|
||||||
|
|
||||||
$this->fields['ssh_key'] = new Pluf_Form_Field_Varchar(
|
$this->fields['description'] = new Pluf_Form_Field_Varchar(
|
||||||
array('required' => false,
|
array('required' => false,
|
||||||
'label' => __('Add a public SSH key'),
|
'label' => __('Description'),
|
||||||
|
'initial' => $user_data->description,
|
||||||
|
'widget_attrs' => array('rows' => 3,
|
||||||
|
'cols' => 40),
|
||||||
|
'widget' => 'Pluf_Form_Widget_TextareaInput',
|
||||||
|
));
|
||||||
|
|
||||||
|
$this->fields['twitter'] = new Pluf_Form_Field_Varchar(
|
||||||
|
array('required' => false,
|
||||||
|
'label' => __('Twitter username'),
|
||||||
|
'initial' => $user_data->twitter,
|
||||||
|
'widget_attrs' => array(
|
||||||
|
'maxlength' => 50,
|
||||||
|
'size' => 15,
|
||||||
|
),
|
||||||
|
));
|
||||||
|
|
||||||
|
$this->fields['public_email'] = new Pluf_Form_Field_Email(
|
||||||
|
array('required' => false,
|
||||||
|
'label' => __('Public email address'),
|
||||||
|
'initial' => $user_data->public_email,
|
||||||
|
'widget_attrs' => array(
|
||||||
|
'maxlength' => 50,
|
||||||
|
'size' => 15,
|
||||||
|
),
|
||||||
|
));
|
||||||
|
|
||||||
|
$this->fields['website'] = new Pluf_Form_Field_Url(
|
||||||
|
array('required' => false,
|
||||||
|
'label' => __('Website URL'),
|
||||||
|
'initial' => $user_data->website,
|
||||||
|
'widget_attrs' => array(
|
||||||
|
'maxlength' => 50,
|
||||||
|
'size' => 15,
|
||||||
|
),
|
||||||
|
));
|
||||||
|
|
||||||
|
$this->fields['custom_avatar'] = new Pluf_Form_Field_File(
|
||||||
|
array('required' => false,
|
||||||
|
'label' => __('Upload custom avatar'),
|
||||||
|
'initial' => '',
|
||||||
|
'max_size' => Pluf::f('max_upload_size', 2097152),
|
||||||
|
'move_function_params' => array('upload_path' => Pluf::f('upload_path').'/avatars',
|
||||||
|
'upload_path_create' => true,
|
||||||
|
'upload_overwrite' => true,
|
||||||
|
'file_name' => 'user_'.$this->user->id.'_%s'),
|
||||||
|
'help_text' => __('An image file with a width and height not larger than 60 pixels (bigger images are scaled down).'),
|
||||||
|
));
|
||||||
|
|
||||||
|
$this->fields['remove_custom_avatar'] = new Pluf_Form_Field_Boolean(
|
||||||
|
array('required' => false,
|
||||||
|
'label' => __('Remove custom avatar'),
|
||||||
|
'initial' => false,
|
||||||
|
'widget' => 'Pluf_Form_Widget_CheckboxInput',
|
||||||
|
'widget_attrs' => array(),
|
||||||
|
'help_text' => __('Tick this to delete the custom avatar.'),
|
||||||
|
));
|
||||||
|
|
||||||
|
$this->fields['public_key'] = new Pluf_Form_Field_Varchar(
|
||||||
|
array('required' => false,
|
||||||
|
'label' => __('Add a public key'),
|
||||||
'initial' => '',
|
'initial' => '',
|
||||||
'widget_attrs' => array('rows' => 3,
|
'widget_attrs' => array('rows' => 3,
|
||||||
'cols' => 40),
|
'cols' => 40),
|
||||||
'widget' => 'Pluf_Form_Widget_TextareaInput',
|
'widget' => 'Pluf_Form_Widget_TextareaInput',
|
||||||
'help_text' => __('Be careful to provide your public key and not your private key!')
|
'help_text' => __('Paste a SSH or monotone public key. Be careful to not provide your private key here!')
|
||||||
));
|
));
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -140,7 +200,7 @@ class IDF_Form_UserAccount extends Pluf_Form
|
|||||||
'email' => $new_email,
|
'email' => $new_email,
|
||||||
'user'=> $this->user,
|
'user'=> $this->user,
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
$tmpl = new Pluf_Template('idf/user/changeemail-email.txt');
|
$tmpl = new Pluf_Template('idf/user/changeemail-email.txt');
|
||||||
$text_email = $tmpl->render($context);
|
$text_email = $tmpl->render($context);
|
||||||
$email = new Pluf_Mail(Pluf::f('from_email'), $new_email,
|
$email = new Pluf_Mail(Pluf::f('from_email'), $new_email,
|
||||||
@@ -151,16 +211,45 @@ class IDF_Form_UserAccount extends Pluf_Form
|
|||||||
}
|
}
|
||||||
$this->user->setFromFormData($this->cleaned_data);
|
$this->user->setFromFormData($this->cleaned_data);
|
||||||
// Add key as needed.
|
// Add key as needed.
|
||||||
if ('' !== $this->cleaned_data['ssh_key']) {
|
if ('' !== $this->cleaned_data['public_key']) {
|
||||||
$key = new IDF_Key();
|
$key = new IDF_Key();
|
||||||
$key->user = $this->user;
|
$key->user = $this->user;
|
||||||
$key->content = $this->cleaned_data['ssh_key'];
|
$key->content = $this->cleaned_data['public_key'];
|
||||||
if ($commit) {
|
if ($commit) {
|
||||||
$key->create();
|
$key->create();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($commit) {
|
if ($commit) {
|
||||||
$this->user->update();
|
$this->user->update();
|
||||||
|
|
||||||
|
// FIXME: go the extra mile and check the input lengths for
|
||||||
|
// all fields here!
|
||||||
|
// FIXME: this is all doubled in admin/UserUpdate!
|
||||||
|
|
||||||
|
$user_data = IDF_UserData::factory($this->user);
|
||||||
|
|
||||||
|
// Add or remove avatar - we need to do this here because every
|
||||||
|
// single setter directly leads to a save in the database
|
||||||
|
if ($user_data->avatar != '' &&
|
||||||
|
($this->cleaned_data['remove_custom_avatar'] == 1 ||
|
||||||
|
$this->cleaned_data['custom_avatar'] != '')) {
|
||||||
|
$avatar_path = Pluf::f('upload_path').'/avatars/'.basename($user_data->avatar);
|
||||||
|
if (basename($avatar_path) != '' && is_file($avatar_path)) {
|
||||||
|
unlink($avatar_path);
|
||||||
|
}
|
||||||
|
$user_data->avatar = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->cleaned_data['custom_avatar'] != '') {
|
||||||
|
$user_data->avatar = $this->cleaned_data['custom_avatar'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$user_data->description = $this->cleaned_data['description'];
|
||||||
|
$user_data->twitter = $this->cleaned_data['twitter'];
|
||||||
|
$user_data->public_email = $this->cleaned_data['public_email'];
|
||||||
|
$user_data->website = $this->cleaned_data['website'];
|
||||||
|
|
||||||
if ($update_pass) {
|
if ($update_pass) {
|
||||||
/**
|
/**
|
||||||
* [signal]
|
* [signal]
|
||||||
@@ -190,7 +279,7 @@ class IDF_Form_UserAccount extends Pluf_Form
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check an ssh key.
|
* Check arbitrary public keys.
|
||||||
*
|
*
|
||||||
* It will throw a Pluf_Form_Invalid exception if it cannot
|
* It will throw a Pluf_Form_Invalid exception if it cannot
|
||||||
* validate the key.
|
* validate the key.
|
||||||
@@ -199,27 +288,59 @@ class IDF_Form_UserAccount extends Pluf_Form
|
|||||||
* @param $user int The user id of the user of the key (0)
|
* @param $user int The user id of the user of the key (0)
|
||||||
* @return string The clean key
|
* @return string The clean key
|
||||||
*/
|
*/
|
||||||
public static function checkSshKey($key, $user=0)
|
public static function checkPublicKey($key, $user=0)
|
||||||
{
|
{
|
||||||
$key = trim($key);
|
$key = trim($key);
|
||||||
if (strlen($key) == 0) {
|
if (strlen($key) == 0) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
$key = str_replace(array("\n", "\r"), '', $key);
|
|
||||||
if (!preg_match('#^ssh\-[a-z]{3}\s(\S+)\s\S+$#', $key, $matches)) {
|
if (preg_match('#^ssh\-[a-z]{3}\s\S+(\s\S+)?$#', $key)) {
|
||||||
throw new Pluf_Form_Invalid(__('The format of the key is not valid. It must start with ssh-dss or ssh-rsa, a long string on a single line and at the end a comment.'));
|
$key = str_replace(array("\n", "\r"), '', $key);
|
||||||
}
|
|
||||||
if (Pluf::f('idf_strong_key_check', false)) {
|
if (Pluf::f('idf_strong_key_check', false)) {
|
||||||
$tmpfile = Pluf::f('tmp_folder', '/tmp').'/'.$user.'-key';
|
|
||||||
file_put_contents($tmpfile, $key, LOCK_EX);
|
$tmpfile = Pluf::f('tmp_folder', '/tmp').'/'.$user.'-key';
|
||||||
$cmd = Pluf::f('idf_exec_cmd_prefix', '').
|
file_put_contents($tmpfile, $key, LOCK_EX);
|
||||||
'ssh-keygen -l -f '.escapeshellarg($tmpfile).' > /dev/null 2>&1';
|
$cmd = Pluf::f('idf_exec_cmd_prefix', '').
|
||||||
exec($cmd, $out, $return);
|
'ssh-keygen -l -f '.escapeshellarg($tmpfile).' > /dev/null 2>&1';
|
||||||
unlink($tmpfile);
|
exec($cmd, $out, $return);
|
||||||
if ($return != 0) {
|
unlink($tmpfile);
|
||||||
throw new Pluf_Form_Invalid(__('Please check the key as it does not appears to be a valid key.'));
|
|
||||||
|
if ($return != 0) {
|
||||||
|
throw new Pluf_Form_Invalid(
|
||||||
|
__('Please check the key as it does not appear '.
|
||||||
|
'to be a valid SSH public key.')
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (preg_match('#^\[pubkey [^\]]+\]\s*\S+\s*\[end\]$#', $key)) {
|
||||||
|
if (Pluf::f('idf_strong_key_check', false)) {
|
||||||
|
|
||||||
|
// if monotone can read it, it should be valid
|
||||||
|
$mtn_opts = implode(' ', Pluf::f('mtn_opts', array()));
|
||||||
|
$cmd = Pluf::f('idf_exec_cmd_prefix', '').
|
||||||
|
sprintf('%s %s -d :memory: read >/tmp/php-out 2>&1',
|
||||||
|
Pluf::f('mtn_path', 'mtn'), $mtn_opts);
|
||||||
|
$fp = popen($cmd, 'w');
|
||||||
|
fwrite($fp, $key);
|
||||||
|
$return = pclose($fp);
|
||||||
|
|
||||||
|
if ($return != 0) {
|
||||||
|
throw new Pluf_Form_Invalid(
|
||||||
|
__('Please check the key as it does not appear '.
|
||||||
|
'to be a valid monotone public key.')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new Pluf_Form_Invalid(
|
||||||
|
__('Public key looks neither like a SSH '.
|
||||||
|
'nor monotone public key.'));
|
||||||
|
}
|
||||||
|
|
||||||
// If $user, then check if not the same key stored
|
// If $user, then check if not the same key stored
|
||||||
if ($user) {
|
if ($user) {
|
||||||
$ruser = Pluf::factory('Pluf_User', $user);
|
$ruser = Pluf::factory('Pluf_User', $user);
|
||||||
@@ -227,19 +348,28 @@ class IDF_Form_UserAccount extends Pluf_Form
|
|||||||
$sql = new Pluf_SQL('content=%s', array($key));
|
$sql = new Pluf_SQL('content=%s', array($key));
|
||||||
$keys = Pluf::factory('IDF_Key')->getList(array('filter' => $sql->gen()));
|
$keys = Pluf::factory('IDF_Key')->getList(array('filter' => $sql->gen()));
|
||||||
if (count($keys) > 0) {
|
if (count($keys) > 0) {
|
||||||
throw new Pluf_Form_Invalid(__('You already have uploaded this SSH key.'));
|
throw new Pluf_Form_Invalid(
|
||||||
|
__('You already have uploaded this key.')
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return $key;
|
return $key;
|
||||||
}
|
}
|
||||||
|
|
||||||
function clean_ssh_key()
|
|
||||||
|
function clean_custom_avatar()
|
||||||
{
|
{
|
||||||
return self::checkSshKey($this->cleaned_data['ssh_key'],
|
// Just png, jpeg/jpg or gif
|
||||||
$this->user->id);
|
if (!preg_match('/\.(png|jpg|jpeg|gif)$/i', $this->cleaned_data['custom_avatar']) &&
|
||||||
|
$this->cleaned_data['custom_avatar'] != '') {
|
||||||
|
@unlink(Pluf::f('upload_path').'/avatars/'.$this->cleaned_data['custom_avatar']);
|
||||||
|
throw new Pluf_Form_Invalid(__('For security reason, you cannot upload a file with this extension.'));
|
||||||
|
}
|
||||||
|
return $this->cleaned_data['custom_avatar'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function clean_last_name()
|
function clean_last_name()
|
||||||
{
|
{
|
||||||
$last_name = trim($this->cleaned_data['last_name']);
|
$last_name = trim($this->cleaned_data['last_name']);
|
||||||
@@ -272,8 +402,16 @@ class IDF_Form_UserAccount extends Pluf_Form
|
|||||||
return $this->cleaned_data['email'];
|
return $this->cleaned_data['email'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function clean_public_key()
|
||||||
|
{
|
||||||
|
$this->cleaned_data['public_key'] =
|
||||||
|
self::checkPublicKey($this->cleaned_data['public_key'],
|
||||||
|
$this->user->id);
|
||||||
|
return $this->cleaned_data['public_key'];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check to see if the 2 passwords are the same.
|
* Check to see if the 2 passwords are the same
|
||||||
*/
|
*/
|
||||||
public function clean()
|
public function clean()
|
||||||
{
|
{
|
||||||
@@ -285,6 +423,9 @@ class IDF_Form_UserAccount extends Pluf_Form
|
|||||||
throw new Pluf_Form_Invalid(__('The passwords do not match. Please give them again.'));
|
throw new Pluf_Form_Invalid(__('The passwords do not match. Please give them again.'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->cleaned_data;
|
return $this->cleaned_data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
210
src/IDF/Gconf.php
Normal file
210
src/IDF/Gconf.php
Normal file
@@ -0,0 +1,210 @@
|
|||||||
|
<?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 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 objects.
|
||||||
|
*
|
||||||
|
* It is just storing a list of key/value pairs associated to
|
||||||
|
* different objects. If you use this table for your model, do not
|
||||||
|
* forget to drop the corresponding keys in your preDelete call.
|
||||||
|
*/
|
||||||
|
class IDF_Gconf extends Pluf_Model
|
||||||
|
{
|
||||||
|
public $_model = __CLASS__;
|
||||||
|
public $datacache = null;
|
||||||
|
public $dirty = array();
|
||||||
|
public $f = null;
|
||||||
|
protected $_mod = null;
|
||||||
|
/**
|
||||||
|
* Do we (un)serialize the data when getting/setting them.
|
||||||
|
*/
|
||||||
|
public $serialize = false;
|
||||||
|
|
||||||
|
function init()
|
||||||
|
{
|
||||||
|
$this->_a['table'] = 'idf_gconf';
|
||||||
|
$this->_a['model'] = __CLASS__;
|
||||||
|
$this->_a['cols'] = array(
|
||||||
|
// It is mandatory to have an "id" column.
|
||||||
|
'id' =>
|
||||||
|
array(
|
||||||
|
'type' => 'Pluf_DB_Field_Sequence',
|
||||||
|
//It is automatically added.
|
||||||
|
'blank' => true,
|
||||||
|
),
|
||||||
|
'model_class' =>
|
||||||
|
array(
|
||||||
|
'type' => 'Pluf_DB_Field_Varchar',
|
||||||
|
'blank' => false,
|
||||||
|
'size' => 150,
|
||||||
|
'verbose' => __('model class'),
|
||||||
|
),
|
||||||
|
'model_id' =>
|
||||||
|
array(
|
||||||
|
'type' => 'Pluf_DB_Field_Integer',
|
||||||
|
'blank' => false,
|
||||||
|
'verbose' => __('model id'),
|
||||||
|
),
|
||||||
|
'vkey' =>
|
||||||
|
array(
|
||||||
|
'type' => 'Pluf_DB_Field_Varchar',
|
||||||
|
'blank' => false,
|
||||||
|
'size' => 50,
|
||||||
|
'verbose' => __('key'),
|
||||||
|
),
|
||||||
|
'vdesc' =>
|
||||||
|
array(
|
||||||
|
'type' => 'Pluf_DB_Field_Text',
|
||||||
|
'blank' => false,
|
||||||
|
'verbose' => __('value'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
$this->_a['idx'] = array('model_vkey_idx' =>
|
||||||
|
array(
|
||||||
|
'col' => 'model_class, model_id, vkey',
|
||||||
|
'type' => 'unique',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
$this->f = new IDF_Config_DataProxy($this);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setModel($model)
|
||||||
|
{
|
||||||
|
$this->datacache = null;
|
||||||
|
$this->_mod = $model;
|
||||||
|
}
|
||||||
|
|
||||||
|
function initCache()
|
||||||
|
{
|
||||||
|
$this->datacache = array();
|
||||||
|
$this->dirty = array();
|
||||||
|
$sql = new Pluf_SQL('model_class=%s AND model_id=%s',
|
||||||
|
array($this->_mod->_model, $this->_mod->id));
|
||||||
|
foreach ($this->getList(array('filter' => $sql->gen())) as $val) {
|
||||||
|
$this->datacache[$val->vkey] = ($this->serialize) ? unserialize($val->vdesc) : $val->vdesc;
|
||||||
|
$this->dirty[$val->vkey] = $val->id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FIXME: This is not efficient when setting a large number of
|
||||||
|
* values in a loop.
|
||||||
|
*/
|
||||||
|
function setVal($key, $value)
|
||||||
|
{
|
||||||
|
if (!is_null($this->getVal($key, null))
|
||||||
|
and $value == $this->getVal($key)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$svalue = ($this->serialize) ? serialize($value) : $value;
|
||||||
|
if (isset($this->dirty[$key])) {
|
||||||
|
// we get to check if deleted by other process + update
|
||||||
|
$conf = new IDF_Gconf($this->dirty[$key]);
|
||||||
|
if ($conf->id == $this->dirty[$key]) {
|
||||||
|
$conf->vdesc = $svalue;
|
||||||
|
$conf->update();
|
||||||
|
$this->datacache[$key] = $value;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// we insert
|
||||||
|
$conf = new IDF_Gconf();
|
||||||
|
$conf->model_class = $this->_mod->_model;
|
||||||
|
$conf->model_id = $this->_mod->id;
|
||||||
|
$conf->vkey = $key;
|
||||||
|
$conf->vdesc = $svalue;
|
||||||
|
$conf->create();
|
||||||
|
$this->datacache[$key] = $value;
|
||||||
|
$this->dirty[$key] = $conf->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getVal($key, $default='')
|
||||||
|
{
|
||||||
|
if ($this->datacache === null) {
|
||||||
|
$this->initCache();
|
||||||
|
}
|
||||||
|
return (isset($this->datacache[$key])) ? $this->datacache[$key] : $default;
|
||||||
|
}
|
||||||
|
|
||||||
|
function delVal($key, $initcache=true)
|
||||||
|
{
|
||||||
|
$gconf = new IDF_Gconf();
|
||||||
|
$sql = new Pluf_SQL('vkey=%s AND model_class=%s AND model_id=%s', array($key, $this->_mod->_model, $this->_mod->id));
|
||||||
|
foreach ($gconf->getList(array('filter' => $sql->gen())) as $c) {
|
||||||
|
$c->delete();
|
||||||
|
}
|
||||||
|
if ($initcache) {
|
||||||
|
$this->initCache();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collection selection.
|
||||||
|
*
|
||||||
|
* Suppose you have 5 objects with associated meta data in the
|
||||||
|
* Gconf storage, if you load the data independently for each
|
||||||
|
* object, you end up with 5 SELECT queries. With 25 objects, 25
|
||||||
|
* SELECT. You can select with one query all the data and merge in
|
||||||
|
* the code. It is faster. The collection selection get a
|
||||||
|
* model_class and a list of ids and returns an id indexed array
|
||||||
|
* of associative array data. This is for read only access as you
|
||||||
|
* do not get a series of Gconf objects.
|
||||||
|
*/
|
||||||
|
public static function collect($class, $ids)
|
||||||
|
{
|
||||||
|
$gconf = new IDF_Gconf();
|
||||||
|
$stmpl = sprintf('model_class=%%s AND model_id IN (%s)',
|
||||||
|
implode(',' , $ids));
|
||||||
|
$sql = new Pluf_SQL($stmpl, array($class));
|
||||||
|
$out = array_fill_keys($ids, array());
|
||||||
|
foreach ($gconf->getList(array('filter' => $sql->gen())) as $c) {
|
||||||
|
$out[$c->model_id][$c->vkey] = $c->vdesc;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Drop the conf of a model.
|
||||||
|
*
|
||||||
|
* If your model is using this table, just add the following line
|
||||||
|
* in your preDelete() method:
|
||||||
|
*
|
||||||
|
* IDF_Gconf::dropForModel($this)
|
||||||
|
*
|
||||||
|
* It will take care of the cleaning.
|
||||||
|
*/
|
||||||
|
static public function dropForModel($model)
|
||||||
|
{
|
||||||
|
$table = Pluf::factory(__CLASS__)->getSqlTable();
|
||||||
|
$sql = new Pluf_SQL('model_class=%s AND model_id=%s',
|
||||||
|
array($model->_model, $model->id));
|
||||||
|
$db = &Pluf::db();
|
||||||
|
$db->execute('DELETE FROM '.$table.' WHERE '.$sql->gen());
|
||||||
|
}
|
||||||
|
|
||||||
|
static public function dropUser($signal, &$params)
|
||||||
|
{
|
||||||
|
self::dropForModel($params['user']);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -241,9 +241,11 @@ class IDF_Issue extends Pluf_Model
|
|||||||
$prj = $this->get_project();
|
$prj = $this->get_project();
|
||||||
$to_email = array();
|
$to_email = array();
|
||||||
if ('' != $conf->getVal('issues_notification_email', '')) {
|
if ('' != $conf->getVal('issues_notification_email', '')) {
|
||||||
$langs = Pluf::f('languages', array('en'));
|
$langs = Pluf::f('languages', array('en'));
|
||||||
$to_email[] = array($conf->getVal('issues_notification_email'),
|
$addresses = explode(',', $conf->getVal('issues_notification_email'));
|
||||||
$langs[0]);
|
foreach ($addresses as $address) {
|
||||||
|
$to_email[] = array($address, $langs[0]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
$current_locale = Pluf_Translation::getLocale();
|
$current_locale = Pluf_Translation::getLocale();
|
||||||
$id = '<'.md5($this->id.md5(Pluf::f('secret_key'))).'@'.Pluf::f('mail_host', 'localhost').'>';
|
$id = '<'.md5($this->id.md5(Pluf::f('secret_key'))).'@'.Pluf::f('mail_host', 'localhost').'>';
|
||||||
|
|||||||
@@ -196,4 +196,9 @@ class IDF_IssueComment extends Pluf_Model
|
|||||||
$tmpl = new Pluf_Template('idf/issues/feedfragment.xml');
|
$tmpl = new Pluf_Template('idf/issues/feedfragment.xml');
|
||||||
return $tmpl->render($context);
|
return $tmpl->render($context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function get_submitter_data()
|
||||||
|
{
|
||||||
|
return IDF_UserData::factory($this->get_submitter());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -128,4 +128,10 @@ class IDF_IssueFile extends Pluf_Model
|
|||||||
{
|
{
|
||||||
@unlink(Pluf::f('upload_issue_path').'/'.$this->attachment);
|
@unlink(Pluf::f('upload_issue_path').'/'.$this->attachment);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isText()
|
||||||
|
{
|
||||||
|
$info = IDF_FileUtil::getMimeType($this->filename);
|
||||||
|
return IDF_FileUtil::isText($info);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
# ***** END LICENSE BLOCK ***** */
|
# ***** END LICENSE BLOCK ***** */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Storage of the SSH keys.
|
* Storage of the public keys (ssh or monotone).
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
class IDF_Key extends Pluf_Model
|
class IDF_Key extends Pluf_Model
|
||||||
@@ -52,7 +52,7 @@ class IDF_Key extends Pluf_Model
|
|||||||
array(
|
array(
|
||||||
'type' => 'Pluf_DB_Field_Text',
|
'type' => 'Pluf_DB_Field_Text',
|
||||||
'blank' => false,
|
'blank' => false,
|
||||||
'verbose' => __('ssh key'),
|
'verbose' => __('public key'),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
// WARNING: Not using getSqlTable on the Pluf_User object to
|
// WARNING: Not using getSqlTable on the Pluf_User object to
|
||||||
@@ -75,6 +75,58 @@ class IDF_Key extends Pluf_Model
|
|||||||
return Pluf_Template::markSafe(Pluf_esc(substr($this->content, 0, 25)).' [...] '.Pluf_esc(substr($this->content, -55)));
|
return Pluf_Template::markSafe(Pluf_esc(substr($this->content, 0, 25)).' [...] '.Pluf_esc(substr($this->content, -55)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function parseContent()
|
||||||
|
{
|
||||||
|
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)) {
|
||||||
|
return array('ssh', $m[2], $m[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Exception(__('Invalid or unknown key data detected.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the type of the public key
|
||||||
|
*
|
||||||
|
* @return string 'ssh' or 'mtn'
|
||||||
|
*/
|
||||||
|
function getType()
|
||||||
|
{
|
||||||
|
list($type, , ) = $this->parseContent();
|
||||||
|
return $type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the key name of the key
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
function getName()
|
||||||
|
{
|
||||||
|
list(, $keyName, ) = $this->parseContent();
|
||||||
|
return $keyName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function should be used to calculate the key id from the
|
||||||
|
* public key hash for authentication purposes. This avoids clashes
|
||||||
|
* in case the key name is not unique across the project
|
||||||
|
*
|
||||||
|
* And yes, this is actually how monotone itself calculates the key
|
||||||
|
* id...
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
function getMtnId()
|
||||||
|
{
|
||||||
|
list($type, $keyName, $keyData) = $this->parseContent();
|
||||||
|
if ($type != 'mtn')
|
||||||
|
throw new Exception('key is not a monotone public key');
|
||||||
|
return sha1($keyName.":".$keyData);
|
||||||
|
}
|
||||||
|
|
||||||
function postSave($create=false)
|
function postSave($create=false)
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
@@ -89,7 +141,7 @@ class IDF_Key extends Pluf_Model
|
|||||||
* [description]
|
* [description]
|
||||||
*
|
*
|
||||||
* This signal allows an application to perform special
|
* This signal allows an application to perform special
|
||||||
* operations after the saving of a SSH Key.
|
* operations after the saving of a public Key.
|
||||||
*
|
*
|
||||||
* [parameters]
|
* [parameters]
|
||||||
*
|
*
|
||||||
@@ -127,5 +179,4 @@ class IDF_Key extends Pluf_Model
|
|||||||
Pluf_Signal::send('IDF_Key::preDelete',
|
Pluf_Signal::send('IDF_Key::preDelete',
|
||||||
'IDF_Key', $params);
|
'IDF_Key', $params);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,6 +92,7 @@ class IDF_Middleware
|
|||||||
array(
|
array(
|
||||||
'size' => 'IDF_Views_Source_PrettySize',
|
'size' => 'IDF_Views_Source_PrettySize',
|
||||||
'ssize' => 'IDF_Views_Source_PrettySizeSimple',
|
'ssize' => 'IDF_Views_Source_PrettySizeSimple',
|
||||||
|
'shorten' => 'IDF_Views_Source_ShortenString',
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -110,6 +111,8 @@ function IDF_Middleware_ContextPreProcessor($request)
|
|||||||
$request->project);
|
$request->project);
|
||||||
$c = array_merge($c, $request->rights);
|
$c = array_merge($c, $request->rights);
|
||||||
}
|
}
|
||||||
|
$c['usherConfigured'] = Pluf::f("mtn_usher_conf", null) !== null;
|
||||||
|
$c['allProjects'] = IDF_Views::getProjects($request->user);
|
||||||
return $c;
|
return $c;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
53
src/IDF/Migrations/14Queue.php
Normal file
53
src/IDF/Migrations/14Queue.php
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
<?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 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_Queue model.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
function IDF_Migrations_14Queue_up($params=null)
|
||||||
|
{
|
||||||
|
$models = array(
|
||||||
|
'IDF_Queue',
|
||||||
|
);
|
||||||
|
$db = Pluf::db();
|
||||||
|
$schema = new Pluf_DB_Schema($db);
|
||||||
|
foreach ($models as $model) {
|
||||||
|
$schema->model = new $model();
|
||||||
|
$schema->createTables();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function IDF_Migrations_14Queue_down($params=null)
|
||||||
|
{
|
||||||
|
$models = array(
|
||||||
|
'IDF_Queue',
|
||||||
|
);
|
||||||
|
$db = Pluf::db();
|
||||||
|
$schema = new Pluf_DB_Schema($db);
|
||||||
|
foreach ($models as $model) {
|
||||||
|
$schema->model = new $model();
|
||||||
|
$schema->dropTables();
|
||||||
|
}
|
||||||
|
}
|
||||||
53
src/IDF/Migrations/15AddGconf.php
Normal file
53
src/IDF/Migrations/15AddGconf.php
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
<?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 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_Gconf model.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
function IDF_Migrations_15AddGconf_up($params=null)
|
||||||
|
{
|
||||||
|
$models = array(
|
||||||
|
'IDF_Gconf',
|
||||||
|
);
|
||||||
|
$db = Pluf::db();
|
||||||
|
$schema = new Pluf_DB_Schema($db);
|
||||||
|
foreach ($models as $model) {
|
||||||
|
$schema->model = new $model();
|
||||||
|
$schema->createTables();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function IDF_Migrations_15AddGconf_down($params=null)
|
||||||
|
{
|
||||||
|
$models = array(
|
||||||
|
'IDF_Gconf',
|
||||||
|
);
|
||||||
|
$db = Pluf::db();
|
||||||
|
$schema = new Pluf_DB_Schema($db);
|
||||||
|
foreach ($models as $model) {
|
||||||
|
$schema->model = new $model();
|
||||||
|
$schema->dropTables();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -51,6 +51,8 @@ function IDF_Migrations_Backup_run($folder, $name=null)
|
|||||||
'IDF_Review_FileComment',
|
'IDF_Review_FileComment',
|
||||||
'IDF_Key',
|
'IDF_Key',
|
||||||
'IDF_Scm_Cache_Git',
|
'IDF_Scm_Cache_Git',
|
||||||
|
'IDF_Queue',
|
||||||
|
'IDF_Gconf',
|
||||||
);
|
);
|
||||||
$db = Pluf::db();
|
$db = Pluf::db();
|
||||||
// Now, for each table, we dump the content in json, this is a
|
// Now, for each table, we dump the content in json, this is a
|
||||||
@@ -94,6 +96,8 @@ function IDF_Migrations_Backup_restore($folder, $name)
|
|||||||
'IDF_Review_FileComment',
|
'IDF_Review_FileComment',
|
||||||
'IDF_Key',
|
'IDF_Key',
|
||||||
'IDF_Scm_Cache_Git',
|
'IDF_Scm_Cache_Git',
|
||||||
|
'IDF_Queue',
|
||||||
|
'IDF_Gconf',
|
||||||
);
|
);
|
||||||
$db = Pluf::db();
|
$db = Pluf::db();
|
||||||
$schema = new Pluf_DB_Schema($db);
|
$schema = new Pluf_DB_Schema($db);
|
||||||
|
|||||||
@@ -48,6 +48,8 @@ function IDF_Migrations_Install_setup($params=null)
|
|||||||
'IDF_Review_FileComment',
|
'IDF_Review_FileComment',
|
||||||
'IDF_Key',
|
'IDF_Key',
|
||||||
'IDF_Scm_Cache_Git',
|
'IDF_Scm_Cache_Git',
|
||||||
|
'IDF_Queue',
|
||||||
|
'IDF_Gconf',
|
||||||
);
|
);
|
||||||
$db = Pluf::db();
|
$db = Pluf::db();
|
||||||
$schema = new Pluf_DB_Schema($db);
|
$schema = new Pluf_DB_Schema($db);
|
||||||
@@ -85,6 +87,8 @@ function IDF_Migrations_Install_teardown($params=null)
|
|||||||
$perm = Pluf_Permission::getFromString('IDF.project-authorized-user');
|
$perm = Pluf_Permission::getFromString('IDF.project-authorized-user');
|
||||||
if ($perm) $perm->delete();
|
if ($perm) $perm->delete();
|
||||||
$models = array(
|
$models = array(
|
||||||
|
'IDF_Gconf',
|
||||||
|
'IDF_Queue',
|
||||||
'IDF_Scm_Cache_Git',
|
'IDF_Scm_Cache_Git',
|
||||||
'IDF_Key',
|
'IDF_Key',
|
||||||
'IDF_Review_FileComment',
|
'IDF_Review_FileComment',
|
||||||
|
|||||||
@@ -48,8 +48,13 @@ class IDF_Plugin_SyncGit_Cron
|
|||||||
$out = '';
|
$out = '';
|
||||||
$keys = Pluf::factory('IDF_Key')->getList(array('view'=>'join_user'));
|
$keys = Pluf::factory('IDF_Key')->getList(array('view'=>'join_user'));
|
||||||
foreach ($keys as $key) {
|
foreach ($keys as $key) {
|
||||||
if (strlen($key->content) > 40 // minimal check
|
try {
|
||||||
and preg_match('/^[a-zA-Z][a-zA-Z0-9_.-]*(@[a-zA-Z][a-zA-Z0-9.-]*)?$/', $key->login)) {
|
$key_type = $key->getType();
|
||||||
|
} catch (Exception $e) {
|
||||||
|
// The key is a bad key, skip it
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($key_type == 'ssh' and preg_match('/^[a-zA-Z][a-zA-Z0-9_.-]*(@[a-zA-Z][a-zA-Z0-9.-]*)?$/', $key->login)) {
|
||||||
$content = trim(str_replace(array("\n", "\r"), '', $key->content));
|
$content = trim(str_replace(array("\n", "\r"), '', $key->content));
|
||||||
$out .= sprintf($template, $cmd, $key->login, $content)."\n";
|
$out .= sprintf($template, $cmd, $key->login, $content)."\n";
|
||||||
}
|
}
|
||||||
@@ -99,6 +104,7 @@ class IDF_Plugin_SyncGit_Cron
|
|||||||
if (count($orphans)) {
|
if (count($orphans)) {
|
||||||
$cmd = Pluf::f('idf_exec_cmd_prefix', '').'rm -rf '.implode(' ', $orphans);
|
$cmd = Pluf::f('idf_exec_cmd_prefix', '').'rm -rf '.implode(' ', $orphans);
|
||||||
exec($cmd);
|
exec($cmd);
|
||||||
|
clearstatcache();
|
||||||
while (list(, $project) = each($orphans)) {
|
while (list(, $project) = each($orphans)) {
|
||||||
if (is_dir($project)) {
|
if (is_dir($project)) {
|
||||||
throw new Exception(sprintf('Cannot remove %s directory.', $project));
|
throw new Exception(sprintf('Cannot remove %s directory.', $project));
|
||||||
|
|||||||
@@ -196,6 +196,8 @@ class IDF_Plugin_SyncGit_Serve
|
|||||||
if (!file_exists($fullpath)) {
|
if (!file_exists($fullpath)) {
|
||||||
mkdir($fullpath, 0750, true);
|
mkdir($fullpath, 0750, true);
|
||||||
}
|
}
|
||||||
|
$out = array();
|
||||||
|
$res = 0;
|
||||||
exec(sprintf(Pluf::f('idf_exec_cmd_prefix', '').
|
exec(sprintf(Pluf::f('idf_exec_cmd_prefix', '').
|
||||||
Pluf::f('git_path', 'git').' --git-dir=%s init', escapeshellarg($fullpath)),
|
Pluf::f('git_path', 'git').' --git-dir=%s init', escapeshellarg($fullpath)),
|
||||||
$out, $res);
|
$out, $res);
|
||||||
@@ -214,6 +216,8 @@ class IDF_Plugin_SyncGit_Serve
|
|||||||
$fullpath.'/hooks/post-update'));
|
$fullpath.'/hooks/post-update'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
$out = array();
|
||||||
|
$res = 0;
|
||||||
exec(sprintf(Pluf::f('idf_exec_cmd_prefix', '').'ln -s %s %s',
|
exec(sprintf(Pluf::f('idf_exec_cmd_prefix', '').'ln -s %s %s',
|
||||||
escapeshellarg($p),
|
escapeshellarg($p),
|
||||||
escapeshellarg($fullpath.'/hooks/post-update')),
|
escapeshellarg($fullpath.'/hooks/post-update')),
|
||||||
@@ -226,6 +230,24 @@ class IDF_Plugin_SyncGit_Serve
|
|||||||
}
|
}
|
||||||
Pluf_Log::debug(array('IDF_Plugin_Git_Serve::initRepository',
|
Pluf_Log::debug(array('IDF_Plugin_Git_Serve::initRepository',
|
||||||
'Added post-update hook.', $fullpath));
|
'Added post-update hook.', $fullpath));
|
||||||
|
// Configure the core.quotepath option
|
||||||
|
$quotepath = (Pluf::f('git_core_quotepath', true) == true) ? 'true' : 'false';
|
||||||
|
$out = array();
|
||||||
|
$res = 0;
|
||||||
|
exec(sprintf(Pluf::f('idf_exec_cmd_prefix', '').
|
||||||
|
Pluf::f('git_path', 'git').' config -f %s/config --add core.quotepath %s',
|
||||||
|
escapeshellarg($fullpath),
|
||||||
|
escapeshellarg($quotepath)
|
||||||
|
),
|
||||||
|
$out, $res);
|
||||||
|
if ($res != 0) {
|
||||||
|
Pluf_Log::warn(array('IDF_Plugin_Git_Serve::initRepository',
|
||||||
|
'core.quotepath configuration error.',
|
||||||
|
$quotepath));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Pluf_Log::debug(array('IDF_Plugin_Git_Serve::initRepository',
|
||||||
|
'core.quotepath configured.', $quotepath));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
830
src/IDF/Plugin/SyncMonotone.php
Normal file
830
src/IDF/Plugin/SyncMonotone.php
Normal file
@@ -0,0 +1,830 @@
|
|||||||
|
<?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) 2010 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 classes is a plugin which allows to synchronise access rights
|
||||||
|
* between indefero and monotone usher setups.
|
||||||
|
*/
|
||||||
|
class IDF_Plugin_SyncMonotone
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Entry point of the plugin.
|
||||||
|
*/
|
||||||
|
static public function entry($signal, &$params)
|
||||||
|
{
|
||||||
|
$plug = new IDF_Plugin_SyncMonotone();
|
||||||
|
switch ($signal) {
|
||||||
|
case 'IDF_Project::created':
|
||||||
|
$plug->processProjectCreate($params['project']);
|
||||||
|
break;
|
||||||
|
case 'IDF_Project::membershipsUpdated':
|
||||||
|
$plug->processMembershipsUpdated($params['project']);
|
||||||
|
break;
|
||||||
|
case 'IDF_Project::preDelete':
|
||||||
|
$plug->processProjectDelete($params['project']);
|
||||||
|
break;
|
||||||
|
case 'IDF_Key::postSave':
|
||||||
|
$plug->processKeyCreate($params['key']);
|
||||||
|
break;
|
||||||
|
case 'IDF_Key::preDelete':
|
||||||
|
$plug->processKeyDelete($params['key']);
|
||||||
|
break;
|
||||||
|
case 'mtnpostpush.php::run':
|
||||||
|
$plug->processSyncTimeline($params['project']);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initial steps to setup a new monotone project:
|
||||||
|
*
|
||||||
|
* 1) run mtn db init to initialize a new database underknees
|
||||||
|
* 'mtn_repositories'
|
||||||
|
* 2) create a new server key in the same directory
|
||||||
|
* 3) create a new client key for IDF and store it in the project conf
|
||||||
|
* 4) setup the configuration
|
||||||
|
* 5) add the database as new local server in the usher configuration
|
||||||
|
* 6) reload the running usher instance so it acknowledges the new server
|
||||||
|
*
|
||||||
|
* The initial right setup happens in processMembershipsUpdated()
|
||||||
|
*
|
||||||
|
* @param IDF_Project
|
||||||
|
*/
|
||||||
|
function processProjectCreate($project)
|
||||||
|
{
|
||||||
|
if ($project->getConf()->getVal('scm') != 'mtn') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Pluf::f('mtn_db_access', 'local') == 'local') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$projecttempl = Pluf::f('mtn_repositories', false);
|
||||||
|
if ($projecttempl === false) {
|
||||||
|
throw new IDF_Scm_Exception(
|
||||||
|
__('"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.')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$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
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// check some static configuration files
|
||||||
|
$confdir = Pluf::f('mtn_confdir', false);
|
||||||
|
if ($confdir === false) {
|
||||||
|
$confdir = dirname(__FILE__).'/SyncMonotone/';
|
||||||
|
}
|
||||||
|
$confdir_contents = array(
|
||||||
|
'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',
|
||||||
|
);
|
||||||
|
if (!$project->private) {
|
||||||
|
// this is linked and not copied to be able to update
|
||||||
|
// the list of read-only commands on upgrades
|
||||||
|
$confdir_contents[] = 'hooks.d/indefero_authorize_remote_automate.conf';
|
||||||
|
}
|
||||||
|
|
||||||
|
// check whether we should handle additional files in the config directory
|
||||||
|
$confdir_extra_contents = Pluf::f('mtn_confdir_extra', false);
|
||||||
|
if ($confdir_extra_contents !== false) {
|
||||||
|
$confdir_contents =
|
||||||
|
array_merge($confdir_contents, $confdir_extra_contents);
|
||||||
|
}
|
||||||
|
foreach ($confdir_contents as $content) {
|
||||||
|
if (!file_exists($confdir.$content)) {
|
||||||
|
throw new IDF_Scm_Exception(sprintf(
|
||||||
|
__('The configuration file %s is missing.'), $content
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$shortname = $project->shortname;
|
||||||
|
$projectpath = sprintf($projecttempl, $shortname);
|
||||||
|
if (file_exists($projectpath)) {
|
||||||
|
throw new IDF_Scm_Exception(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
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// step 1) create a new database
|
||||||
|
//
|
||||||
|
$dbfile = $projectpath.'/database.mtn';
|
||||||
|
$cmd = sprintf('db init -d %s', escapeshellarg($dbfile));
|
||||||
|
self::_mtn_exec($cmd);
|
||||||
|
|
||||||
|
//
|
||||||
|
// step 2) create a server key
|
||||||
|
//
|
||||||
|
// try to parse the key's domain part from the remote_url's host
|
||||||
|
// name, otherwise fall back to the configured Apache server name
|
||||||
|
$server = $_SERVER['SERVER_NAME'];
|
||||||
|
$remote_url = Pluf::f('mtn_remote_url');
|
||||||
|
if (($parsed = parse_url($remote_url)) !== false &&
|
||||||
|
!empty($parsed['host'])) {
|
||||||
|
$server = $parsed['host'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$serverkey = $shortname.'-server@'.$server;
|
||||||
|
$cmd = sprintf('au generate_key --confdir=%s %s ""',
|
||||||
|
escapeshellarg($projectpath),
|
||||||
|
escapeshellarg($serverkey)
|
||||||
|
);
|
||||||
|
self::_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
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$clientkey_name = $shortname.'-client@'.$server;
|
||||||
|
$cmd = sprintf('au generate_key --keydir=%s %s ""',
|
||||||
|
escapeshellarg($keydir),
|
||||||
|
escapeshellarg($clientkey_name)
|
||||||
|
);
|
||||||
|
$keyinfo = self::_mtn_exec($cmd);
|
||||||
|
|
||||||
|
$parsed_keyinfo = array();
|
||||||
|
try {
|
||||||
|
$parsed_keyinfo = IDF_Scm_Monotone_BasicIO::parse($keyinfo);
|
||||||
|
}
|
||||||
|
catch (Exception $e) {
|
||||||
|
throw new IDF_Scm_Exception(sprintf(
|
||||||
|
__('Could not parse key information: %s'), $e->getMessage()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
$clientkey_hash = $parsed_keyinfo[0][1]['hash'];
|
||||||
|
$clientkey_file = $keydir . '/' . $clientkey_name . '.' . $clientkey_hash;
|
||||||
|
$clientkey_data = file_get_contents($clientkey_file);
|
||||||
|
|
||||||
|
$project->getConf()->setVal('mtn_client_key_name', $clientkey_name);
|
||||||
|
$project->getConf()->setVal('mtn_client_key_hash', $clientkey_hash);
|
||||||
|
$project->getConf()->setVal('mtn_client_key_data', $clientkey_data);
|
||||||
|
|
||||||
|
// add the public client key to the server
|
||||||
|
$cmd = sprintf('au get_public_key --keydir=%s %s',
|
||||||
|
escapeshellarg($keydir),
|
||||||
|
escapeshellarg($clientkey_hash)
|
||||||
|
);
|
||||||
|
$clientkey_pubdata = self::_mtn_exec($cmd);
|
||||||
|
|
||||||
|
$cmd = sprintf('au put_public_key --db=%s %s',
|
||||||
|
escapeshellarg($dbfile),
|
||||||
|
escapeshellarg($clientkey_pubdata)
|
||||||
|
);
|
||||||
|
self::_mtn_exec($cmd);
|
||||||
|
|
||||||
|
//
|
||||||
|
// step 4) setup the configuration
|
||||||
|
//
|
||||||
|
|
||||||
|
// we assume that all confdir entries ending with a slash mean a
|
||||||
|
// directory that has to be created, that all files ending on ".in"
|
||||||
|
// have to be processed and copied in place and that all other files
|
||||||
|
// just need to be symlinked from the original location
|
||||||
|
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
|
||||||
|
));
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (substr($content, -3) != '.in') {
|
||||||
|
if (!symlink($confdir.$content, $filepath)) {
|
||||||
|
IDF_Scm_Exception(sprintf(
|
||||||
|
__('Could not create symlink "%s"'), $filepath
|
||||||
|
));
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$filecontents = file_get_contents($confdir.'/'.$content);
|
||||||
|
$filecontents = str_replace(
|
||||||
|
array('%%MTNPOSTPUSH%%', '%%PROJECT%%', '%%MTNCLIENTKEY%%'),
|
||||||
|
array($mtnpostpush, $shortname, $clientkey_hash),
|
||||||
|
$filecontents
|
||||||
|
);
|
||||||
|
|
||||||
|
// 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
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// step 5) read in and append the usher config with the new server
|
||||||
|
//
|
||||||
|
$usher_rc = file_get_contents($usher_config);
|
||||||
|
$parsed_config = array();
|
||||||
|
try {
|
||||||
|
$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'),
|
||||||
|
$usher_config, $e->getMessage()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensure we haven't configured a server with this name already
|
||||||
|
foreach ($parsed_config as $stanzas) {
|
||||||
|
foreach ($stanzas as $stanza_line) {
|
||||||
|
if ($stanza_line['key'] == 'server' &&
|
||||||
|
$stanza_line['values'][0] == $shortname) {
|
||||||
|
throw new IDF_Scm_Exception(sprintf(
|
||||||
|
__('usher configuration already contains a server '.
|
||||||
|
'entry named "%s"'),
|
||||||
|
$shortname
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$new_server = array(
|
||||||
|
array('key' => 'server', 'values' => array($shortname)),
|
||||||
|
array('key' => 'local', 'values' => array(
|
||||||
|
'--confdir', $projectpath,
|
||||||
|
'-d', $dbfile,
|
||||||
|
'--timestamps',
|
||||||
|
'--ticker=dot'
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
|
||||||
|
$parsed_config[] = $new_server;
|
||||||
|
$usher_rc = IDF_Scm_Monotone_BasicIO::compile($parsed_config);
|
||||||
|
|
||||||
|
// 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
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// step 6) reload usher to pick up the new configuration
|
||||||
|
//
|
||||||
|
IDF_Scm_Monotone_Usher::reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the read / write permissions for the monotone database
|
||||||
|
*
|
||||||
|
* @param IDF_Project
|
||||||
|
*/
|
||||||
|
public function processMembershipsUpdated($project)
|
||||||
|
{
|
||||||
|
if ($project->getConf()->getVal('scm') != 'mtn') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Pluf::f('mtn_db_access', 'local') == 'local') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$mtn = IDF_Scm_Monotone::factory($project);
|
||||||
|
$stdio = $mtn->getStdio();
|
||||||
|
|
||||||
|
$projectpath = self::_get_project_path($project);
|
||||||
|
$auth_ids = self::_get_authorized_user_ids($project);
|
||||||
|
$key_ids = array();
|
||||||
|
foreach ($auth_ids as $auth_id) {
|
||||||
|
$sql = new Pluf_SQL('user=%s', array($auth_id));
|
||||||
|
$keys = Pluf::factory('IDF_Key')->getList(array('filter' => $sql->gen()));
|
||||||
|
foreach ($keys as $key) {
|
||||||
|
if ($key->getType() != 'mtn')
|
||||||
|
continue;
|
||||||
|
$stdio->exec(array('put_public_key', $key->content));
|
||||||
|
$key_ids[] = $key->getMtnId();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$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 ($project->private) {
|
||||||
|
$stanza = array(
|
||||||
|
array('key' => 'pattern', 'values' => array('*')),
|
||||||
|
);
|
||||||
|
foreach ($key_ids as $key_id)
|
||||||
|
{
|
||||||
|
$stanza[] = array('key' => 'allow', 'values' => array($key_id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$stanza = array(
|
||||||
|
array('key' => 'pattern', 'values' => array('*')),
|
||||||
|
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
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// link / unlink the read-only automate permissions for the project
|
||||||
|
$confdir = Pluf::f('mtn_confdir', false);
|
||||||
|
if ($confdir === false) {
|
||||||
|
$confdir = dirname(__FILE__).'/SyncMonotone/';
|
||||||
|
}
|
||||||
|
$file = 'hooks.d/indefero_authorize_remote_automate.conf';
|
||||||
|
$projectfile = $projectpath.'/'.$file;
|
||||||
|
$templatefile = $confdir.'/'.$file;
|
||||||
|
|
||||||
|
$serverRestartRequired = false;
|
||||||
|
if ($project->private && file_exists($projectfile) && is_link($projectfile)) {
|
||||||
|
if (!unlink($projectfile)) {
|
||||||
|
IDF_Scm_Exception(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(
|
||||||
|
__('Could not create symlink "%s"'), $projectfile
|
||||||
|
));
|
||||||
|
}
|
||||||
|
$serverRestartRequired = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($serverRestartRequired) {
|
||||||
|
// FIXME: we should actually use stopServer() here, but this
|
||||||
|
// seems to be ignored when the server should be started
|
||||||
|
// again immediately afterwards
|
||||||
|
IDF_Scm_Monotone_Usher::killServer($project->shortname);
|
||||||
|
IDF_Scm_Monotone_Usher::startServer($project->shortname);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clean up after a mtn project was deleted
|
||||||
|
*
|
||||||
|
* @param IDF_Project
|
||||||
|
*/
|
||||||
|
public function processProjectDelete($project)
|
||||||
|
{
|
||||||
|
if ($project->getConf()->getVal('scm') != 'mtn') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Pluf::f('mtn_db_access', 'local') == 'local') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$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.')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$shortname = $project->shortname;
|
||||||
|
IDF_Scm_Monotone_Usher::killServer($shortname);
|
||||||
|
|
||||||
|
$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, $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
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$keydir = Pluf::f('tmp_folder').'/mtn-client-keys';
|
||||||
|
$keyname = $project->getConf()->getVal('mtn_client_key_name', false);
|
||||||
|
$keyhash = $project->getConf()->getVal('mtn_client_key_hash', false);
|
||||||
|
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
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$usher_rc = file_get_contents($usher_config);
|
||||||
|
$parsed_config = array();
|
||||||
|
try {
|
||||||
|
$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'),
|
||||||
|
$usher_config, $e->getMessage()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($parsed_config as $idx => $stanzas) {
|
||||||
|
foreach ($stanzas as $stanza_line) {
|
||||||
|
if ($stanza_line['key'] == 'server' &&
|
||||||
|
$stanza_line['values'][0] == $shortname) {
|
||||||
|
unset($parsed_config[$idx]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$usher_rc = IDF_Scm_Monotone_BasicIO::compile($parsed_config);
|
||||||
|
|
||||||
|
// 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
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
IDF_Scm_Monotone_Usher::reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the (monotone) key to all monotone projects of this forge
|
||||||
|
* where the user of the key has write access to
|
||||||
|
*/
|
||||||
|
public function processKeyCreate($key)
|
||||||
|
{
|
||||||
|
if ($key->getType() != 'mtn') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Pluf::f('mtn_db_access', 'local') == 'local') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (Pluf::factory('IDF_Project')->getList() as $project) {
|
||||||
|
$conf = new IDF_Conf();
|
||||||
|
$conf->setProject($project);
|
||||||
|
$scm = $conf->getVal('scm', 'mtn');
|
||||||
|
if ($scm != 'mtn')
|
||||||
|
continue;
|
||||||
|
|
||||||
|
$projectpath = self::_get_project_path($project);
|
||||||
|
$auth_ids = self::_get_authorized_user_ids($project);
|
||||||
|
if (!in_array($key->user, $auth_ids))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
$mtn_key_id = $key->getMtnId();
|
||||||
|
|
||||||
|
// if the project is not defined as private, all people have
|
||||||
|
// read access already, so we don't need to write anything
|
||||||
|
// and we currently do not check if read-permissions really
|
||||||
|
// contains
|
||||||
|
// pattern "*"
|
||||||
|
// allow "*"
|
||||||
|
// which is the default for non-private projects
|
||||||
|
if ($project->private == true) {
|
||||||
|
$read_perms = file_get_contents($projectpath.'/read-permissions');
|
||||||
|
$parsed_read_perms = array();
|
||||||
|
try {
|
||||||
|
$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'),
|
||||||
|
$shortname, $e->getMessage()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
$wildcard_section = null;
|
||||||
|
for ($i=0; $i<count($parsed_read_perms); ++$i) {
|
||||||
|
foreach ($parsed_read_perms[$i] as $stanza_line) {
|
||||||
|
if ($stanza_line['key'] == 'pattern' &&
|
||||||
|
$stanza_line['values'][0] == '*') {
|
||||||
|
$wildcard_section =& $parsed_read_perms[$i];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($wildcard_section == null)
|
||||||
|
{
|
||||||
|
$wildcard_section = array(
|
||||||
|
array('key' => 'pattern', 'values' => array('*'))
|
||||||
|
);
|
||||||
|
$parsed_read_perms[] =& $wildcard_section;
|
||||||
|
}
|
||||||
|
|
||||||
|
$key_found = false;
|
||||||
|
foreach ($wildcard_section as $line)
|
||||||
|
{
|
||||||
|
if ($line['key'] == 'allow' && $line['values'][0] == $mtn_key_id) {
|
||||||
|
$key_found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$key_found) {
|
||||||
|
$wildcard_section[] = array(
|
||||||
|
'key' => 'allow', 'values' => array($mtn_key_id)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$read_perms = IDF_Scm_Monotone_BasicIO::compile($parsed_read_perms);
|
||||||
|
|
||||||
|
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
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$write_perms = file_get_contents($projectpath.'/write-permissions');
|
||||||
|
$lines = preg_split("/(\n|\r\n)/", $write_perms, -1, PREG_SPLIT_NO_EMPTY);
|
||||||
|
if (!in_array('*', $lines) && !in_array($mtn_key_id, $lines)) {
|
||||||
|
$lines[] = $mtn_key_id;
|
||||||
|
}
|
||||||
|
if (file_put_contents($projectpath.'/write-permissions',
|
||||||
|
implode("\n", $lines) . "\n", LOCK_EX) === false) {
|
||||||
|
throw new IDF_Scm_Exception(sprintf(
|
||||||
|
__('Could not write write-permissions file for project "%s"'),
|
||||||
|
$shortname
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
$mtn = IDF_Scm_Monotone::factory($project);
|
||||||
|
$stdio = $mtn->getStdio();
|
||||||
|
$stdio->exec(array('put_public_key', $key->content));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the (monotone) key from all monotone projects of this forge
|
||||||
|
* where the user of the key has write access to
|
||||||
|
*/
|
||||||
|
public function processKeyDelete($key)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if ($key->getType() != 'mtn') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (Exception $e) {
|
||||||
|
// bad key type, skip it.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Pluf::f('mtn_db_access', 'local') == 'local') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (Pluf::factory('IDF_Project')->getList() as $project) {
|
||||||
|
$conf = new IDF_Conf();
|
||||||
|
$conf->setProject($project);
|
||||||
|
$scm = $conf->getVal('scm', 'mtn');
|
||||||
|
if ($scm != 'mtn')
|
||||||
|
continue;
|
||||||
|
|
||||||
|
$projectpath = self::_get_project_path($project);
|
||||||
|
$auth_ids = self::_get_authorized_user_ids($project);
|
||||||
|
if (!in_array($key->user, $auth_ids))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
$mtn_key_id = $key->getMtnId();
|
||||||
|
|
||||||
|
// if the project is not defined as private, all people have
|
||||||
|
// read access already, so we don't need to write anything
|
||||||
|
// and we currently do not check if read-permissions really
|
||||||
|
// contains
|
||||||
|
// pattern "*"
|
||||||
|
// allow "*"
|
||||||
|
// which is the default for non-private projects
|
||||||
|
if ($project->private) {
|
||||||
|
$read_perms = file_get_contents($projectpath.'/read-permissions');
|
||||||
|
$parsed_read_perms = array();
|
||||||
|
try {
|
||||||
|
$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'),
|
||||||
|
$shortname, $e->getMessage()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// while we add new keys only to an existing wild-card entry
|
||||||
|
// we remove dropped keys from all sections since the key
|
||||||
|
// should be simply unavailable for all of them
|
||||||
|
for ($h=0; $h<count($parsed_read_perms); ++$h) {
|
||||||
|
for ($i=0; $i<count($parsed_read_perms[$h]); ++$i) {
|
||||||
|
if ($parsed_read_perms[$h][$i]['key'] == 'allow' &&
|
||||||
|
$parsed_read_perms[$h][$i]['values'][0] == $mtn_key_id) {
|
||||||
|
unset($parsed_read_perms[$h][$i]);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$read_perms = IDF_Scm_Monotone_BasicIO::compile($parsed_read_perms);
|
||||||
|
|
||||||
|
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
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$write_perms = file_get_contents($projectpath.'/write-permissions');
|
||||||
|
$lines = preg_split("/(\n|\r\n)/", $write_perms, -1, PREG_SPLIT_NO_EMPTY);
|
||||||
|
for ($i=0; $i<count($lines); ++$i) {
|
||||||
|
if ($lines[$i] == $mtn_key_id) {
|
||||||
|
unset($lines[$i]);
|
||||||
|
// the key should actually only exist once in the
|
||||||
|
// file, but we're paranoid
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (file_put_contents($projectpath.'/write-permissions',
|
||||||
|
implode("\n", $lines) . "\n", LOCK_EX) === false) {
|
||||||
|
throw new IDF_Scm_Exception(sprintf(
|
||||||
|
__('Could not write write-permissions file for project "%s"'),
|
||||||
|
$shortname
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
$mtn = IDF_Scm_Monotone::factory($project);
|
||||||
|
$stdio = $mtn->getStdio();
|
||||||
|
// if the public key did not sign any revisions, drop it from
|
||||||
|
// the database as well
|
||||||
|
try {
|
||||||
|
if (strlen($stdio->exec(array('select', 'k:' . $mtn_key_id))) == 0) {
|
||||||
|
$stdio->exec(array('drop_public_key', $mtn_key_id));
|
||||||
|
}
|
||||||
|
} catch (IDF_Scm_Exception $e) {
|
||||||
|
if (strpos($e->getMessage(), 'there is no key named') === false)
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the timeline after a push
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function processSyncTimeline($project_name)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$project = IDF_Project::getOr404($project_name);
|
||||||
|
} catch (Pluf_HTTP_Error404 $e) {
|
||||||
|
Pluf_Log::event(array(
|
||||||
|
'IDF_Plugin_SyncMonotone::processSyncTimeline',
|
||||||
|
'Project not found.',
|
||||||
|
array($project_name, $params)
|
||||||
|
));
|
||||||
|
return false; // Project not found
|
||||||
|
}
|
||||||
|
|
||||||
|
Pluf_Log::debug(array(
|
||||||
|
'IDF_Plugin_SyncMonotone::processSyncTimeline',
|
||||||
|
'Project found', $project_name, $project->id
|
||||||
|
));
|
||||||
|
IDF_Scm::syncTimeline($project, true);
|
||||||
|
Pluf_Log::event(array(
|
||||||
|
'IDF_Plugin_SyncMonotone::processSyncTimeline',
|
||||||
|
'sync', array($project_name, $project->id)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function _get_authorized_user_ids($project)
|
||||||
|
{
|
||||||
|
$mem = $project->getMembershipData();
|
||||||
|
$members = array_merge((array)$mem['members'],
|
||||||
|
(array)$mem['owners'],
|
||||||
|
(array)$mem['authorized']);
|
||||||
|
$userids = array();
|
||||||
|
foreach ($members as $member) {
|
||||||
|
$userids[] = $member->id;
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
if (is_file($path) || is_link($path)) {
|
||||||
|
return @unlink($path);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_dir($path)) {
|
||||||
|
$scan = glob(rtrim($path, '/') . '/*');
|
||||||
|
$status = 0;
|
||||||
|
foreach ($scan as $subpath) {
|
||||||
|
$status |= self::_delete_recursive($subpath);
|
||||||
|
}
|
||||||
|
$status |= rmdir($path);
|
||||||
|
return $status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
ARA_safe_commands = {
|
||||||
|
"get_corresponding_path", "get_content_changed", "tags", "branches",
|
||||||
|
"common_ancestors", "packet_for_fdelta", "packet_for_fdata",
|
||||||
|
"packets_for_certs", "packet_for_rdata", "get_manifest_of",
|
||||||
|
"get_revision", "select", "graph", "children", "parents", "roots",
|
||||||
|
"leaves", "ancestry_difference", "toposort", "erase_ancestors",
|
||||||
|
"descendents", "ancestors", "heads", "get_file_of", "get_file",
|
||||||
|
"interface_version", "get_attributes", "content_diff",
|
||||||
|
"file_merge", "show_conflicts", "certs", "keys", "get_extended_manifest_of"
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
-- ***** BEGIN LICENSE BLOCK *****
|
||||||
|
-- This file is part of InDefero, an open source project management application.
|
||||||
|
-- Copyright (C) 2011 Céondo Ltd and contributors.
|
||||||
|
-- Copyright (C) 2010 Thomas Keller <me@thomaskeller.biz>
|
||||||
|
-- Richard Levitte <richard@levitte.org>
|
||||||
|
--
|
||||||
|
-- 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 reads key identities from a file "remote-automate-permissions"
|
||||||
|
-- in the configuration directory and permits those authenticating with one
|
||||||
|
-- of those keys to perform dangerous (read/write) remote automate operations.
|
||||||
|
-- The format of the file is very simple, one key identity on every line.
|
||||||
|
-- Lines starting with # are ignore, as well as empty lines.
|
||||||
|
--
|
||||||
|
-- It's possible to configure this script to allow the performance of some
|
||||||
|
-- remote automate commands anonymously, through the variable
|
||||||
|
-- ARA_safe_commands, which has to be a table of commands as strings.
|
||||||
|
-- One example configuration, taken from the setup at code.monotone.ca, could
|
||||||
|
-- be this:
|
||||||
|
--
|
||||||
|
-- ARA_safe_commands = {
|
||||||
|
-- "get_corresponding_path", "get_content_changed", "tags", "branches",
|
||||||
|
-- "common_ancestors", "packet_for_fdelta", "packet_for_fdata",
|
||||||
|
-- "packets_for_certs", "packet_for_rdata", "get_manifest_of",
|
||||||
|
-- "get_revision", "select", "graph", "children", "parents", "roots",
|
||||||
|
-- "leaves", "ancestry_difference", "toposort", "erase_ancestors",
|
||||||
|
-- "descendents", "ancestors", "heads", "get_file_of", "get_file",
|
||||||
|
-- "interface_version", "get_attributes", "content_diff",
|
||||||
|
-- "file_merge", "show_conflicts", "certs", "keys", "get_extended_manifest_of"
|
||||||
|
-- }
|
||||||
|
--
|
||||||
|
do
|
||||||
|
local _safe_commands = {}
|
||||||
|
if ARA_safe_commands then
|
||||||
|
_safe_commands = ARA_safe_commands
|
||||||
|
end
|
||||||
|
|
||||||
|
local _save_get_remote_automate_permitted = get_remote_automate_permitted
|
||||||
|
function get_remote_automate_permitted(key_identity, command, options)
|
||||||
|
local permfile =
|
||||||
|
io.open(get_confdir() .. "/remote-automate-permissions", "r")
|
||||||
|
if (permfile == nil) then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- See if the incoming key matches any of the key identities or
|
||||||
|
-- patterns found in the permissions file.
|
||||||
|
local matches = false
|
||||||
|
local line = permfile:read()
|
||||||
|
while (not matches and line ~= nil) do
|
||||||
|
if not globish_match("#*", line) then
|
||||||
|
local _, _, ln = string.find(line, "%s*([^%s]*)%s*")
|
||||||
|
if ln == "*" then matches = true end
|
||||||
|
if ln == key_identity.id then matches = true end
|
||||||
|
if globish_match(ln, key_identity.name) then matches = true end
|
||||||
|
line = permfile:read()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
io.close(permfile)
|
||||||
|
if matches then return true end
|
||||||
|
|
||||||
|
-- No matching key found, let's see if the command matches one the
|
||||||
|
-- admin allowed to be performed anonymously
|
||||||
|
for _,v in ipairs(_safe_commands) do
|
||||||
|
if (v == command[1]) then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- No matches found anywhere, then don't permit this operation
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
IDF_project = "%%PROJECT%%"
|
||||||
|
IDF_push_script = "%%MTNPOSTPUSH%%"
|
||||||
58
src/IDF/Plugin/SyncMonotone/hooks.d/indefero_post_push.lua
Normal file
58
src/IDF/Plugin/SyncMonotone/hooks.d/indefero_post_push.lua
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
-- ***** 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 *****
|
||||||
|
|
||||||
|
--
|
||||||
|
-- let IDF know of new arriving revisions to fill its timeline
|
||||||
|
--
|
||||||
|
_idf_revs = {}
|
||||||
|
push_hook_functions(
|
||||||
|
{
|
||||||
|
start =
|
||||||
|
function (session_id)
|
||||||
|
_idf_revs[session_id] = {}
|
||||||
|
return "continue",nil
|
||||||
|
end,
|
||||||
|
revision_received =
|
||||||
|
function (new_id, revision, certs, session_id)
|
||||||
|
table.insert(_idf_revs[session_id], new_id)
|
||||||
|
return "continue",nil
|
||||||
|
end,
|
||||||
|
["end"] =
|
||||||
|
function (session_id, ...)
|
||||||
|
if table.getn(_idf_revs[session_id]) == 0 then
|
||||||
|
return "continue",nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local pin,pout,pid = spawn_pipe(IDF_push_script, IDF_project);
|
||||||
|
if pid == -1 then
|
||||||
|
print("could not execute " .. IDF_push_script)
|
||||||
|
return "continue",nil
|
||||||
|
end
|
||||||
|
|
||||||
|
for _,r in ipairs(_idf_revs[session_id]) do
|
||||||
|
pin:write(r .. "\n")
|
||||||
|
end
|
||||||
|
pin:close()
|
||||||
|
|
||||||
|
wait(pid)
|
||||||
|
return "continue",nil
|
||||||
|
end
|
||||||
|
})
|
||||||
|
|
||||||
30
src/IDF/Plugin/SyncMonotone/monotonerc.in
Normal file
30
src/IDF/Plugin/SyncMonotone/monotonerc.in
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
-- ***** 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 *****
|
||||||
|
|
||||||
|
---- Load local hooks if they exist.
|
||||||
|
-- The way this is supposed to work is that hooks.d can contain symbolic
|
||||||
|
-- links to lua scripts. These links MUST have the extension .lua
|
||||||
|
-- If the script needs some configuration, a corresponding file with
|
||||||
|
-- the extension .conf is the right spot.
|
||||||
|
----
|
||||||
|
-- First load the configuration of the hooks, if applicable
|
||||||
|
includedirpattern(get_confdir() .. "/hooks.d/","*.conf")
|
||||||
|
-- Then load the hooks themselves
|
||||||
|
includedirpattern(get_confdir() .. "/hooks.d/","*.lua")
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
%%MTNCLIENTKEY%%
|
||||||
@@ -87,6 +87,36 @@ class IDF_Plugin_SyncSvn
|
|||||||
escapeshellarg($svn_path.'/'.$shortname));
|
escapeshellarg($svn_path.'/'.$shortname));
|
||||||
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
|
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
|
||||||
$ll = exec($cmd, $output, $return);
|
$ll = exec($cmd, $output, $return);
|
||||||
|
if ($return != 0) {
|
||||||
|
Pluf_Log::error(array('IDF_Plugin_SyncSvn::processSvnCreate',
|
||||||
|
'Error',
|
||||||
|
array('path' => $svn_path.'/'.$shortname,
|
||||||
|
'output' => $output)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$p = realpath(dirname(__FILE__).'/../../../scripts/svn-post-commit');
|
||||||
|
exec(sprintf(Pluf::f('idf_exec_cmd_prefix', '').'ln -s %s %s',
|
||||||
|
escapeshellarg($p),
|
||||||
|
escapeshellarg($svn_path.'/'.$shortname.'/hooks/post-commit')),
|
||||||
|
$out, $res);
|
||||||
|
if ($res != 0) {
|
||||||
|
Pluf_Log::warn(array('IDF_Plugin_SyncSvn::processSvnCreate',
|
||||||
|
'post-commit hook creation error.',
|
||||||
|
$svn_path.'/'.$shortname.'/hooks/post-commit'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$p = realpath(dirname(__FILE__).'/../../../scripts/svn-post-revprop-change');
|
||||||
|
exec(sprintf(Pluf::f('idf_exec_cmd_prefix', '').'ln -s %s %s',
|
||||||
|
escapeshellarg($p),
|
||||||
|
escapeshellarg($svn_path.'/'.$shortname.'/hooks/post-revprop-change')),
|
||||||
|
$out, $res);
|
||||||
|
if ($res != 0) {
|
||||||
|
Pluf_Log::warn(array('IDF_Plugin_SyncSvn::processSvnCreate',
|
||||||
|
'post-revprop-change hook creation error.',
|
||||||
|
$svn_path.'/'.$shortname.'/hooks/post-revprop-change'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
return ($return == 0);
|
return ($return == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -366,12 +366,13 @@ class IDF_Project extends Pluf_Model
|
|||||||
public function getRepositorySize($force=false)
|
public function getRepositorySize($force=false)
|
||||||
{
|
{
|
||||||
$last_eval = $this->getConf()->getVal('repository_size_check_date', 0);
|
$last_eval = $this->getConf()->getVal('repository_size_check_date', 0);
|
||||||
if (!$force and $last_eval > time()-86400) {
|
if (Pluf::f('idf_no_size_check', false) or
|
||||||
|
(!$force and $last_eval > time()-172800)) {
|
||||||
return $this->getConf()->getVal('repository_size', -1);
|
return $this->getConf()->getVal('repository_size', -1);
|
||||||
}
|
}
|
||||||
|
$this->getConf()->setVal('repository_size_check_date', time());
|
||||||
$scm = IDF_Scm::get($this);
|
$scm = IDF_Scm::get($this);
|
||||||
$this->getConf()->setVal('repository_size', $scm->getRepositorySize());
|
$this->getConf()->setVal('repository_size', $scm->getRepositorySize());
|
||||||
$this->getConf()->setVal('repository_size_check_date', time());
|
|
||||||
return $this->getConf()->getVal('repository_size', -1);
|
return $this->getConf()->getVal('repository_size', -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -381,15 +382,16 @@ class IDF_Project extends Pluf_Model
|
|||||||
* This will return the right url based on the user.
|
* This will return the right url based on the user.
|
||||||
*
|
*
|
||||||
* @param Pluf_User The user (null)
|
* @param Pluf_User The user (null)
|
||||||
|
* @param string A specific commit to access
|
||||||
*/
|
*/
|
||||||
public function getSourceAccessUrl($user=null)
|
public function getSourceAccessUrl($user=null, $commit=null)
|
||||||
{
|
{
|
||||||
$right = $this->getConf()->getVal('source_access_rights', 'all');
|
$right = $this->getConf()->getVal('source_access_rights', 'all');
|
||||||
if (($user == null or $user->isAnonymous())
|
if (($user == null or $user->isAnonymous())
|
||||||
and $right == 'all' and !$this->private) {
|
and $right == 'all' and !$this->private) {
|
||||||
return $this->getRemoteAccessUrl();
|
return $this->getRemoteAccessUrl($commit);
|
||||||
}
|
}
|
||||||
return $this->getWriteRemoteAccessUrl($user);
|
return $this->getWriteRemoteAccessUrl($user, $commit);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -397,15 +399,17 @@ class IDF_Project extends Pluf_Model
|
|||||||
* Get the remote access url to the repository.
|
* Get the remote access url to the repository.
|
||||||
*
|
*
|
||||||
* This will always return the anonymous access url.
|
* This will always return the anonymous access url.
|
||||||
|
*
|
||||||
|
* @param string A specific commit to access
|
||||||
*/
|
*/
|
||||||
public function getRemoteAccessUrl()
|
public function getRemoteAccessUrl($commit=null)
|
||||||
{
|
{
|
||||||
$conf = $this->getConf();
|
$conf = $this->getConf();
|
||||||
$scm = $conf->getVal('scm', 'git');
|
$scm = $conf->getVal('scm', 'git');
|
||||||
$scms = Pluf::f('allowed_scm');
|
$scms = Pluf::f('allowed_scm');
|
||||||
Pluf::loadClass($scms[$scm]);
|
Pluf::loadClass($scms[$scm]);
|
||||||
return call_user_func(array($scms[$scm], 'getAnonymousAccessUrl'),
|
return call_user_func(array($scms[$scm], 'getAnonymousAccessUrl'),
|
||||||
$this);
|
$this, $commit);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -414,14 +418,27 @@ class IDF_Project extends Pluf_Model
|
|||||||
* Some SCM have a remote access URL to write which is not the
|
* Some SCM have a remote access URL to write which is not the
|
||||||
* same as the one to read. For example, you do a checkout with
|
* same as the one to read. For example, you do a checkout with
|
||||||
* git-daemon and push with SSH.
|
* git-daemon and push with SSH.
|
||||||
|
*
|
||||||
|
* @param string A specific commit to access
|
||||||
*/
|
*/
|
||||||
public function getWriteRemoteAccessUrl($user)
|
public function getWriteRemoteAccessUrl($user,$commit=null)
|
||||||
{
|
{
|
||||||
$conf = $this->getConf();
|
$conf = $this->getConf();
|
||||||
$scm = $conf->getVal('scm', 'git');
|
$scm = $conf->getVal('scm', 'git');
|
||||||
$scms = Pluf::f('allowed_scm');
|
$scms = Pluf::f('allowed_scm');
|
||||||
return call_user_func(array($scms[$scm], 'getAuthAccessUrl'),
|
return call_user_func(array($scms[$scm], 'getAuthAccessUrl'),
|
||||||
$this, $user);
|
$this, $user, $commit);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the post commit hook key.
|
||||||
|
*
|
||||||
|
* The goal is to get something predictable but from which one
|
||||||
|
* cannot reverse find the secret key.
|
||||||
|
*/
|
||||||
|
public function getPostCommitHookKey()
|
||||||
|
{
|
||||||
|
return md5($this->id.sha1(Pluf::f('secret_key')).$this->shortname);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -435,7 +452,8 @@ class IDF_Project extends Pluf_Model
|
|||||||
$roots = array(
|
$roots = array(
|
||||||
'git' => 'master',
|
'git' => 'master',
|
||||||
'svn' => 'HEAD',
|
'svn' => 'HEAD',
|
||||||
'mercurial' => 'tip'
|
'mercurial' => 'tip',
|
||||||
|
'mtn' => 'h:'.$conf->getVal('mtn_master_branch', '*'),
|
||||||
);
|
);
|
||||||
$scm = $conf->getVal('scm', 'git');
|
$scm = $conf->getVal('scm', 'git');
|
||||||
return $roots[$scm];
|
return $roots[$scm];
|
||||||
@@ -617,7 +635,7 @@ class IDF_Project extends Pluf_Model
|
|||||||
Pluf_Signal::send('IDF_Project::preDelete',
|
Pluf_Signal::send('IDF_Project::preDelete',
|
||||||
'IDF_Project', $params);
|
'IDF_Project', $params);
|
||||||
$what = array('IDF_Upload', 'IDF_Review', 'IDF_Issue',
|
$what = array('IDF_Upload', 'IDF_Review', 'IDF_Issue',
|
||||||
'IDF_WikiPage', 'IDF_Commit',
|
'IDF_WikiPage', 'IDF_Commit', 'IDF_Tag',
|
||||||
);
|
);
|
||||||
foreach ($what as $m) {
|
foreach ($what as $m) {
|
||||||
foreach (Pluf::factory($m)->getList(array('filter' => 'project='.(int)$this->id)) as $item) {
|
foreach (Pluf::factory($m)->getList(array('filter' => 'project='.(int)$this->id)) as $item) {
|
||||||
|
|||||||
222
src/IDF/Queue.php
Normal file
222
src/IDF/Queue.php
Normal file
@@ -0,0 +1,222 @@
|
|||||||
|
<?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 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
|
||||||
|
n# 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 ***** */
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Queue system for the management of asynchronous operations.
|
||||||
|
*
|
||||||
|
* Anybody can add an item to the queue and any application can
|
||||||
|
* register itself to process an item from the queue.
|
||||||
|
*
|
||||||
|
* An item in the queue is considered as fully processed when all the
|
||||||
|
* handlers have processed it successfully.
|
||||||
|
*
|
||||||
|
* To push a new item in the queue:
|
||||||
|
*
|
||||||
|
* <code>
|
||||||
|
* $item = new IDF_Queue();
|
||||||
|
* $item->type = 'new_commit';
|
||||||
|
* $item->payload = array('what', 'ever', array('data'));
|
||||||
|
* $item->create();
|
||||||
|
* </code>
|
||||||
|
*
|
||||||
|
* To process one item from the queue, you first need to register an
|
||||||
|
* handler, by adding the following in your relations.php file before
|
||||||
|
* the return statement or in your config file.
|
||||||
|
*
|
||||||
|
* <code>
|
||||||
|
* Pluf_Signal::connect('IDF_Queue::processItem',
|
||||||
|
* array('YourApp_Class', 'processItem'));
|
||||||
|
* </code>
|
||||||
|
*
|
||||||
|
* The processItem method will be called with two arguments, the first
|
||||||
|
* is the name of the signal ('IDF_Queue::processItem') and the second
|
||||||
|
* is an array with:
|
||||||
|
*
|
||||||
|
* <code>
|
||||||
|
* array('item' => $item,
|
||||||
|
* 'res' => array('OtherApp_Class::handler' => false,
|
||||||
|
* 'FooApp_Class::processItem' => true));
|
||||||
|
* </code>
|
||||||
|
*
|
||||||
|
* When you process an item, you need first to check if the type is
|
||||||
|
* corresponding to what you want to work with, then you need to check
|
||||||
|
* in 'res' if you have not already processed successfully the item,
|
||||||
|
* that is the key 'YourApp_Class::processItem' must be set to true,
|
||||||
|
* and then you can process the item. At the end of your processing,
|
||||||
|
* you need to modify by reference the 'res' key to add your status.
|
||||||
|
*
|
||||||
|
* All the data except for the type is in the payload, this makes the
|
||||||
|
* queue flexible to manage many different kind of tasks.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class IDF_Queue extends Pluf_Model
|
||||||
|
{
|
||||||
|
public $_model = __CLASS__;
|
||||||
|
|
||||||
|
function init()
|
||||||
|
{
|
||||||
|
$this->_a['table'] = 'idf_queue';
|
||||||
|
$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,
|
||||||
|
),
|
||||||
|
'status' =>
|
||||||
|
array(
|
||||||
|
'type' => 'Pluf_DB_Field_Integer',
|
||||||
|
'blank' => false,
|
||||||
|
'choices' => array(
|
||||||
|
'pending' => 0,
|
||||||
|
'in_progress' => 1,
|
||||||
|
'need_retry' => 2,
|
||||||
|
'done' => 3,
|
||||||
|
'error' => 4,
|
||||||
|
),
|
||||||
|
'default' => 0,
|
||||||
|
),
|
||||||
|
'trials' =>
|
||||||
|
array(
|
||||||
|
'type' => 'Pluf_DB_Field_Integer',
|
||||||
|
'default' => 0,
|
||||||
|
),
|
||||||
|
'type' =>
|
||||||
|
array(
|
||||||
|
'type' => 'Pluf_DB_Field_Varchar',
|
||||||
|
'blank' => false,
|
||||||
|
'size' => 50,
|
||||||
|
),
|
||||||
|
'payload' =>
|
||||||
|
array(
|
||||||
|
'type' => 'Pluf_DB_Field_Serialized',
|
||||||
|
'blank' => false,
|
||||||
|
),
|
||||||
|
'results' =>
|
||||||
|
array(
|
||||||
|
'type' => 'Pluf_DB_Field_Serialized',
|
||||||
|
'blank' => false,
|
||||||
|
),
|
||||||
|
'lasttry_dtime' =>
|
||||||
|
array(
|
||||||
|
'type' => 'Pluf_DB_Field_Datetime',
|
||||||
|
'blank' => true,
|
||||||
|
),
|
||||||
|
'creation_dtime' =>
|
||||||
|
array(
|
||||||
|
'type' => 'Pluf_DB_Field_Datetime',
|
||||||
|
'blank' => true,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function preSave($create=false)
|
||||||
|
{
|
||||||
|
if ($create) {
|
||||||
|
$this->creation_dtime = gmdate('Y-m-d H:i:s');
|
||||||
|
$this->lasttry_dtime = gmdate('Y-m-d H:i:s');
|
||||||
|
$this->results = array();
|
||||||
|
$this->trials = 0;
|
||||||
|
$this->status = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current item is going to be processed.
|
||||||
|
*/
|
||||||
|
function processItem()
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* [signal]
|
||||||
|
*
|
||||||
|
* IDF_Queue::processItem
|
||||||
|
*
|
||||||
|
* [sender]
|
||||||
|
*
|
||||||
|
* IDF_Queue
|
||||||
|
*
|
||||||
|
* [description]
|
||||||
|
*
|
||||||
|
* This signal allows an application to run an asynchronous
|
||||||
|
* job. The handler gets the queue item and the results from
|
||||||
|
* the previous run. If the handler key is not set, then the
|
||||||
|
* job was not run. If set it can be either true (already done)
|
||||||
|
* or false (error at last run).
|
||||||
|
*
|
||||||
|
* [parameters]
|
||||||
|
*
|
||||||
|
* array('item' => $item, 'res' => $res)
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
$params = array('item' => $this, 'res' => $this->results);
|
||||||
|
Pluf_Signal::send('IDF_Queue::processItem',
|
||||||
|
'IDF_Queue', $params);
|
||||||
|
$this->status = 3; // Success
|
||||||
|
foreach ($params['res'] as $handler=>$ok) {
|
||||||
|
if (!$ok) {
|
||||||
|
$this->status = 2; // Set to need retry
|
||||||
|
$this->trials += 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->results = $params['res'];
|
||||||
|
$this->lasttry_dtime = gmdate('Y-m-d H:i:s');
|
||||||
|
$this->update();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the queue.
|
||||||
|
*
|
||||||
|
* It is a signal handler to just hook itself at the right time in
|
||||||
|
* the cron job performing the maintainance work.
|
||||||
|
*
|
||||||
|
* The processing relies on the fact that no other processing jobs
|
||||||
|
* must run at the same time. That is, your cron job must use a
|
||||||
|
* lock file or something like to not run in parallel.
|
||||||
|
*
|
||||||
|
* The processing is simple, first get 500 queue items, mark them
|
||||||
|
* as being processed and for each of them call the processItem()
|
||||||
|
* method which will trigger another event for processing.
|
||||||
|
*
|
||||||
|
* If you are processing more than 500 items per batch, you need
|
||||||
|
* to switch to a different solution.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public static function process($sender, &$params)
|
||||||
|
{
|
||||||
|
$where = 'status=0 OR status=2';
|
||||||
|
$items = Pluf::factory('IDF_Queue')->getList(array('filter'=>$where,
|
||||||
|
'nb'=> 500));
|
||||||
|
Pluf_Log::event(array('IDF_Queue::process', $items->count()));
|
||||||
|
foreach ($items as $item) {
|
||||||
|
$item->status = 1;
|
||||||
|
$item->update();
|
||||||
|
}
|
||||||
|
foreach ($items as $item) {
|
||||||
|
$item->status = 1;
|
||||||
|
$item->processItem();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -221,4 +221,9 @@ class IDF_Review_Comment extends Pluf_Model
|
|||||||
}
|
}
|
||||||
Pluf_Translation::loadSetLocale($current_locale);
|
Pluf_Translation::loadSetLocale($current_locale);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function get_submitter_data()
|
||||||
|
{
|
||||||
|
return IDF_UserData::factory($this->get_submitter());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -197,14 +197,17 @@ class IDF_Review_Patch extends Pluf_Model
|
|||||||
);
|
);
|
||||||
$tmpl = new Pluf_Template('idf/review/review-created-email.txt');
|
$tmpl = new Pluf_Template('idf/review/review-created-email.txt');
|
||||||
$text_email = $tmpl->render($context);
|
$text_email = $tmpl->render($context);
|
||||||
$email = new Pluf_Mail(Pluf::f('from_email'),
|
$addresses = explode(';',$conf->getVal('review_notification_email'));
|
||||||
$conf->getVal('review_notification_email'),
|
foreach ($addresses as $address) {
|
||||||
sprintf(__('New Code Review %s - %s (%s)'),
|
$email = new Pluf_Mail(Pluf::f('from_email'),
|
||||||
$this->get_review()->id,
|
$address,
|
||||||
$this->get_review()->summary,
|
sprintf(__('New Code Review %s - %s (%s)'),
|
||||||
$this->get_review()->get_project()->shortname));
|
$this->get_review()->id,
|
||||||
$email->addTextMessage($text_email);
|
$this->get_review()->summary,
|
||||||
$email->sendMail();
|
$this->get_review()->get_project()->shortname));
|
||||||
|
$email->addTextMessage($text_email);
|
||||||
|
$email->sendMail();
|
||||||
|
}
|
||||||
Pluf_Translation::loadSetLocale($current_locale);
|
Pluf_Translation::loadSetLocale($current_locale);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -166,13 +166,28 @@ class IDF_Scm
|
|||||||
throw new Pluf_Exception_NotImplemented();
|
throw new Pluf_Exception_NotImplemented();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const REVISION_VALID = 0;
|
||||||
|
const REVISION_INVALID = 1;
|
||||||
|
const REVISION_AMBIGUOUS = 2;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if a revision or commit is valid.
|
* Check if a revision or commit is valid, invalid or ambiguous.
|
||||||
*
|
*
|
||||||
* @param string Revision or commit
|
* @param string Revision or commit
|
||||||
* @return bool
|
* @return int One of REVISION_VALID, REVISION_INVALID or REVISION_AMBIGIOUS
|
||||||
*/
|
*/
|
||||||
public function isValidRevision($rev)
|
public function validateRevision($rev)
|
||||||
|
{
|
||||||
|
throw new Pluf_Exception_NotImplemented();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array of single commit objects for ambiguous commit identifiers
|
||||||
|
*
|
||||||
|
* @param string Ambiguous commit identifier
|
||||||
|
* @return array of objects
|
||||||
|
*/
|
||||||
|
public function disambiguateRevision($commit)
|
||||||
{
|
{
|
||||||
throw new Pluf_Exception_NotImplemented();
|
throw new Pluf_Exception_NotImplemented();
|
||||||
}
|
}
|
||||||
@@ -301,6 +316,34 @@ class IDF_Scm
|
|||||||
throw new Pluf_Exception_NotImplemented();
|
throw new Pluf_Exception_NotImplemented();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all recorded changes which lead to the particular commit
|
||||||
|
* or revision.
|
||||||
|
*
|
||||||
|
* Example output:
|
||||||
|
*
|
||||||
|
* 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', ...)
|
||||||
|
* 'patches' => array('path/to/file', ...),
|
||||||
|
* 'properties' => array('path/to/file' => array(
|
||||||
|
* 'propname' => 'propvalue', 'deletedprop' => null, ...)
|
||||||
|
* ),
|
||||||
|
* ...)
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* Each member of the returned object is mandatory, but may contain
|
||||||
|
* an empty array if no changes were recorded.
|
||||||
|
*
|
||||||
|
* @param string A commit identifier
|
||||||
|
* @return object with arrays of individual changes
|
||||||
|
*/
|
||||||
|
public function getChanges($commit)
|
||||||
|
{
|
||||||
|
throw new Pluf_Exception_NotImplemented();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get latest changes.
|
* Get latest changes.
|
||||||
*
|
*
|
||||||
@@ -378,13 +421,28 @@ class IDF_Scm
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate the command to create a zip archive at a given commit.
|
* Given a changelog parsed node, returns extra data.
|
||||||
|
*
|
||||||
|
* For example, if the node is a commit object from git, it will a
|
||||||
|
* stdClass object with the parents array. The extra value could
|
||||||
|
* then be the parent(s) commit(s).
|
||||||
|
*
|
||||||
|
* @param stdClass Commit object/Parse object
|
||||||
|
* @return array Extra properties
|
||||||
|
*/
|
||||||
|
public function getExtraProperties($obj)
|
||||||
|
{
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a zip archive at a given commit, wrapped in a HTTP response, suitable for pushing to client.
|
||||||
*
|
*
|
||||||
* @param string Commit
|
* @param string Commit
|
||||||
* @param string Prefix ('repository/')
|
* @param string Prefix ('repository/')
|
||||||
* @return string Command
|
* @return Pluf_HTTP_Response The HTTP Response containing the zip archive
|
||||||
*/
|
*/
|
||||||
public function getArchiveCommand($commit, $prefix='repository/')
|
public function getArchiveStream($commit, $prefix='repository/')
|
||||||
{
|
{
|
||||||
throw new Pluf_Exception_NotImplemented();
|
throw new Pluf_Exception_NotImplemented();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,7 +55,9 @@ class IDF_Scm_Cache_Git extends Pluf_Model
|
|||||||
$cache->project = $this->_project;
|
$cache->project = $this->_project;
|
||||||
$cache->githash = $blob->hash;
|
$cache->githash = $blob->hash;
|
||||||
$blob->title = IDF_Commit::toUTF8($blob->title);
|
$blob->title = IDF_Commit::toUTF8($blob->title);
|
||||||
$cache->content = $blob->date.chr(31).$blob->author.chr(31).$blob->title;
|
$cache->content = IDF_Commit::toUTF8($blob->date) . chr(31)
|
||||||
|
. IDF_Commit::toUTF8($blob->author) . chr(31)
|
||||||
|
. IDF_Commit::toUTF8($blob->title);
|
||||||
$sql = new Pluf_SQL('project=%s AND githash=%s',
|
$sql = new Pluf_SQL('project=%s AND githash=%s',
|
||||||
array($this->_project->id, $blob->hash));
|
array($this->_project->id, $blob->hash));
|
||||||
if (0 == Pluf::factory(__CLASS__)->getCount(array('filter' => $sql->gen()))) {
|
if (0 == Pluf::factory(__CLASS__)->getCount(array('filter' => $sql->gen()))) {
|
||||||
@@ -85,6 +87,8 @@ class IDF_Scm_Cache_Git extends Pluf_Model
|
|||||||
array($this->_project->id));
|
array($this->_project->id));
|
||||||
foreach (Pluf::factory(__CLASS__)->getList(array('filter' => $sql->gen())) as $blob) {
|
foreach (Pluf::factory(__CLASS__)->getList(array('filter' => $sql->gen())) as $blob) {
|
||||||
$tmp = explode(chr(31), $blob->content, 3);
|
$tmp = explode(chr(31), $blob->content, 3);
|
||||||
|
// sometimes the title might be empty
|
||||||
|
if (!isset($tmp[2])) $tmp[2] = '';
|
||||||
|
|
||||||
$res[$blob->githash] = (object) array(
|
$res[$blob->githash] = (object) array(
|
||||||
'hash' => $blob->githash,
|
'hash' => $blob->githash,
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
*/
|
*/
|
||||||
class IDF_Scm_Git extends IDF_Scm
|
class IDF_Scm_Git extends IDF_Scm
|
||||||
{
|
{
|
||||||
public $mediumtree_fmt = 'commit %H%nAuthor: %an <%ae>%nTree: %T%nDate: %ai%n%n%s%n%n%b';
|
public $mediumtree_fmt = 'commit %H%nAuthor: %an <%ae>%nTree: %T%nParents: %P%nDate: %ai%n%n%s%n%n%b';
|
||||||
|
|
||||||
/* ============================================== *
|
/* ============================================== *
|
||||||
* *
|
* *
|
||||||
@@ -116,6 +116,14 @@ class IDF_Scm_Git extends IDF_Scm
|
|||||||
return $this->_inObject($commit, 'branch');
|
return $this->_inObject($commit, 'branch');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will find the parents if available.
|
||||||
|
*/
|
||||||
|
public function getExtraProperties($obj)
|
||||||
|
{
|
||||||
|
return (isset($obj->parents)) ? array('parents' => $obj->parents) : array();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see IDF_Scm::getTags()
|
* @see IDF_Scm::getTags()
|
||||||
**/
|
**/
|
||||||
@@ -125,7 +133,7 @@ class IDF_Scm_Git extends IDF_Scm
|
|||||||
return $this->cache['tags'];
|
return $this->cache['tags'];
|
||||||
}
|
}
|
||||||
$cmd = Pluf::f('idf_exec_cmd_prefix', '')
|
$cmd = Pluf::f('idf_exec_cmd_prefix', '')
|
||||||
.sprintf('GIT_DIR=%s %s tag',
|
.sprintf('GIT_DIR=%s %s for-each-ref --format="%%(taggerdate:iso)%%(committerdate:iso) %%(objectname) %%(refname)" refs/tags',
|
||||||
escapeshellarg($this->repo),
|
escapeshellarg($this->repo),
|
||||||
Pluf::f('git_path', 'git'));
|
Pluf::f('git_path', 'git'));
|
||||||
self::exec('IDF_Scm_Git::getTags', $cmd, $out, $return);
|
self::exec('IDF_Scm_Git::getTags', $cmd, $out, $return);
|
||||||
@@ -134,12 +142,15 @@ class IDF_Scm_Git extends IDF_Scm
|
|||||||
$cmd, $return,
|
$cmd, $return,
|
||||||
implode("\n", $out)));
|
implode("\n", $out)));
|
||||||
}
|
}
|
||||||
|
rsort($out);
|
||||||
$res = array();
|
$res = array();
|
||||||
foreach ($out as $b) {
|
foreach ($out as $b) {
|
||||||
if (false !== strpos($b, '/')) {
|
$elts = explode(' ', $b, 5);
|
||||||
$res[$this->getCommit($b)->commit] = $b;
|
$tag = substr(trim($elts[4]), 10);
|
||||||
|
if (false !== strpos($tag, '/')) {
|
||||||
|
$res[$elts[3]] = $b;
|
||||||
} else {
|
} else {
|
||||||
$res[$b] = '';
|
$res[$tag] = '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$this->cache['tags'] = $res;
|
$this->cache['tags'] = $res;
|
||||||
@@ -270,13 +281,21 @@ class IDF_Scm_Git extends IDF_Scm
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function getAnonymousAccessUrl($project)
|
public static function getAnonymousAccessUrl($project, $commit=null)
|
||||||
{
|
{
|
||||||
return sprintf(Pluf::f('git_remote_url'), $project->shortname);
|
return sprintf(Pluf::f('git_remote_url'), $project->shortname);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function getAuthAccessUrl($project, $user)
|
public static function getAuthAccessUrl($project, $user, $commit=null)
|
||||||
{
|
{
|
||||||
|
// if the user haven't registred a public ssh key,
|
||||||
|
// he can't use the write url which use the SSH authentification
|
||||||
|
if ($user != null) {
|
||||||
|
$keys = $user->get_idf_key_list();
|
||||||
|
if (count ($keys) == 0)
|
||||||
|
return self::getAnonymousAccessUrl($project);
|
||||||
|
}
|
||||||
|
|
||||||
return sprintf(Pluf::f('git_write_remote_url'), $project->shortname);
|
return sprintf(Pluf::f('git_write_remote_url'), $project->shortname);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -293,10 +312,12 @@ class IDF_Scm_Git extends IDF_Scm
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function isValidRevision($commit)
|
public function validateRevision($commit)
|
||||||
{
|
{
|
||||||
$type = $this->testHash($commit);
|
$type = $this->testHash($commit);
|
||||||
return ('commit' == $type || 'tag' == $type);
|
if ('commit' == $type || 'tag' == $type)
|
||||||
|
return IDF_Scm::REVISION_VALID;
|
||||||
|
return IDF_Scm::REVISION_INVALID;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -426,11 +447,13 @@ class IDF_Scm_Git extends IDF_Scm
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
$out = self::parseLog($log);
|
$out = self::parseLog($log);
|
||||||
$out[0]->changes = implode("\n", $change);
|
$out[0]->diff = implode("\n", $change);
|
||||||
} else {
|
} else {
|
||||||
$out = self::parseLog($out);
|
$out = self::parseLog($out);
|
||||||
$out[0]->changes = '';
|
$out[0]->diff = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$out[0]->branch = implode(', ', $this->inBranches($commit, null));
|
||||||
return $out[0];
|
return $out[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -503,6 +526,9 @@ class IDF_Scm_Git extends IDF_Scm
|
|||||||
$c['full_message'] = trim($c['full_message']);
|
$c['full_message'] = trim($c['full_message']);
|
||||||
$c['full_message'] = IDF_Commit::toUTF8($c['full_message']);
|
$c['full_message'] = IDF_Commit::toUTF8($c['full_message']);
|
||||||
$c['title'] = IDF_Commit::toUTF8($c['title']);
|
$c['title'] = IDF_Commit::toUTF8($c['title']);
|
||||||
|
if (isset($c['parents'])) {
|
||||||
|
$c['parents'] = explode(' ', trim($c['parents']));
|
||||||
|
}
|
||||||
$res[] = (object) $c;
|
$res[] = (object) $c;
|
||||||
}
|
}
|
||||||
$c = array();
|
$c = array();
|
||||||
@@ -538,17 +564,21 @@ class IDF_Scm_Git extends IDF_Scm
|
|||||||
$c['full_message'] = !empty($c['full_message']) ? trim($c['full_message']) : '';
|
$c['full_message'] = !empty($c['full_message']) ? trim($c['full_message']) : '';
|
||||||
$c['full_message'] = IDF_Commit::toUTF8($c['full_message']);
|
$c['full_message'] = IDF_Commit::toUTF8($c['full_message']);
|
||||||
$c['title'] = IDF_Commit::toUTF8($c['title']);
|
$c['title'] = IDF_Commit::toUTF8($c['title']);
|
||||||
|
if (isset($c['parents'])) {
|
||||||
|
$c['parents'] = explode(' ', trim($c['parents']));
|
||||||
|
}
|
||||||
$res[] = (object) $c;
|
$res[] = (object) $c;
|
||||||
return $res;
|
return $res;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getArchiveCommand($commit, $prefix='repository/')
|
public function getArchiveStream($commit, $prefix='repository/')
|
||||||
{
|
{
|
||||||
return sprintf(Pluf::f('idf_exec_cmd_prefix', '').
|
$cmd = sprintf(Pluf::f('idf_exec_cmd_prefix', '').
|
||||||
'GIT_DIR=%s '.Pluf::f('git_path', 'git').' archive --format=zip --prefix=%s %s',
|
'GIT_DIR=%s '.Pluf::f('git_path', 'git').' archive --format=zip --prefix=%s %s',
|
||||||
escapeshellarg($this->repo),
|
escapeshellarg($this->repo),
|
||||||
escapeshellarg($prefix),
|
escapeshellarg($prefix),
|
||||||
escapeshellarg($commit));
|
escapeshellarg($commit));
|
||||||
|
return new Pluf_HTTP_Response_CommandPassThru($cmd, 'application/x-zip');
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -77,24 +77,29 @@ class IDF_Scm_Mercurial extends IDF_Scm
|
|||||||
return 'tip';
|
return 'tip';
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function getAnonymousAccessUrl($project)
|
public static function getAnonymousAccessUrl($project, $commit=null)
|
||||||
{
|
{
|
||||||
return sprintf(Pluf::f('mercurial_remote_url'), $project->shortname);
|
return sprintf(Pluf::f('mercurial_remote_url'), $project->shortname);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function getAuthAccessUrl($project, $user)
|
public static function getAuthAccessUrl($project, $user, $commit=null)
|
||||||
{
|
{
|
||||||
return sprintf(Pluf::f('mercurial_remote_url'), $project->shortname);
|
return sprintf(Pluf::f('mercurial_remote_url'), $project->shortname);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function isValidRevision($rev)
|
public function validateRevision($rev)
|
||||||
{
|
{
|
||||||
$cmd = sprintf(Pluf::f('hg_path', 'hg').' log -R %s -r %s',
|
$cmd = sprintf(Pluf::f('hg_path', 'hg').' log -R %s -r %s',
|
||||||
escapeshellarg($this->repo),
|
escapeshellarg($this->repo),
|
||||||
escapeshellarg($rev));
|
escapeshellarg($rev));
|
||||||
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
|
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
|
||||||
self::exec('IDF_Scm_Mercurial::isValidRevision', $cmd, $out, $ret);
|
self::exec('IDF_Scm_Mercurial::isValidRevision', $cmd, $out, $ret);
|
||||||
return ($ret == 0) && (count($out) > 0);
|
|
||||||
|
// FIXME: apparently a given hg revision can also be ambigious -
|
||||||
|
// handle this case here sometime
|
||||||
|
if ($ret == 0 && count($out) > 0)
|
||||||
|
return IDF_Scm::REVISION_VALID;
|
||||||
|
return IDF_Scm::REVISION_INVALID;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -354,7 +359,7 @@ class IDF_Scm_Mercurial extends IDF_Scm
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
$out = self::parseLog($log, 6);
|
$out = self::parseLog($log, 6);
|
||||||
$out[0]->changes = implode("\n", $change);
|
$out[0]->diff = implode("\n", $change);
|
||||||
return $out[0];
|
return $out[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -424,6 +429,8 @@ class IDF_Scm_Mercurial extends IDF_Scm
|
|||||||
$c['author'] = $match[2];
|
$c['author'] = $match[2];
|
||||||
} elseif ($match[1] == 'summary') {
|
} elseif ($match[1] == 'summary') {
|
||||||
$c['title'] = $match[2];
|
$c['title'] = $match[2];
|
||||||
|
} elseif ($match[1] == 'branch') {
|
||||||
|
$c['branch'] = $match[2];
|
||||||
} else {
|
} else {
|
||||||
$c[$match[1]] = trim($match[2]);
|
$c[$match[1]] = trim($match[2]);
|
||||||
}
|
}
|
||||||
@@ -438,23 +445,25 @@ class IDF_Scm_Mercurial extends IDF_Scm
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
$c['tree'] = !empty($c['commit']) ? trim($c['commit']) : '';
|
$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']) : '';
|
$c['full_message'] = !empty($c['full_message']) ? trim($c['full_message']) : '';
|
||||||
$res[] = (object) $c;
|
$res[] = (object) $c;
|
||||||
return $res;
|
return $res;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate the command to create a zip archive at a given commit.
|
* Generate a zip archive at a given commit.
|
||||||
*
|
*
|
||||||
* @param string Commit
|
* @param string Commit
|
||||||
* @param string Prefix ('git-repo-dump')
|
* @param string Prefix ('git-repo-dump')
|
||||||
* @return string Command
|
* @return Pluf_HTTP_Response The HTTP response containing the zip archive
|
||||||
*/
|
*/
|
||||||
public function getArchiveCommand($commit, $prefix='')
|
protected function getArchiveStream($commit, $prefix='')
|
||||||
{
|
{
|
||||||
return sprintf(Pluf::f('idf_exec_cmd_prefix', '').
|
$cmd = sprintf(Pluf::f('idf_exec_cmd_prefix', '').
|
||||||
Pluf::f('hg_path', 'hg').' archive --type=zip -R %s -r %s -',
|
Pluf::f('hg_path', 'hg').' archive --type=zip -R %s -r %s -',
|
||||||
escapeshellarg($this->repo),
|
escapeshellarg($this->repo),
|
||||||
escapeshellarg($commit));
|
escapeshellarg($commit));
|
||||||
|
return new Pluf_HTTP_Response_CommandPassThru($cmd, 'application/x-zip');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
798
src/IDF/Scm/Monotone.php
Normal file
798
src/IDF/Scm/Monotone.php
Normal file
@@ -0,0 +1,798 @@
|
|||||||
|
<?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) 2010 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 ***** */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Monotone scm class
|
||||||
|
*
|
||||||
|
* @author Thomas Keller <me@thomaskeller.biz>
|
||||||
|
*/
|
||||||
|
class IDF_Scm_Monotone extends IDF_Scm
|
||||||
|
{
|
||||||
|
/** the minimum supported interface version */
|
||||||
|
public static $MIN_INTERFACE_VERSION = 12.0;
|
||||||
|
|
||||||
|
private $stdio;
|
||||||
|
|
||||||
|
private static $instances = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see IDF_Scm::__construct()
|
||||||
|
*/
|
||||||
|
public function __construct($project)
|
||||||
|
{
|
||||||
|
$this->project = $project;
|
||||||
|
$this->stdio = new IDF_Scm_Monotone_Stdio($project);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the stdio instance in use
|
||||||
|
*
|
||||||
|
* @return IDF_Scm_Monotone_Stdio
|
||||||
|
*/
|
||||||
|
public function getStdio()
|
||||||
|
{
|
||||||
|
return $this->stdio;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see IDF_Scm::getRepositorySize()
|
||||||
|
*/
|
||||||
|
public function getRepositorySize()
|
||||||
|
{
|
||||||
|
// FIXME: this obviously won't work with remote databases - upstream
|
||||||
|
// needs to implement mtn db info in automate at first
|
||||||
|
$repo = sprintf(Pluf::f('mtn_repositories'), $this->project->shortname);
|
||||||
|
if (!file_exists($repo)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$cmd = Pluf::f('idf_exec_cmd_prefix', '').'du -sk '
|
||||||
|
.escapeshellarg($repo);
|
||||||
|
$out = explode(' ',
|
||||||
|
self::shell_exec('IDF_Scm_Monotone::getRepositorySize', $cmd),
|
||||||
|
2);
|
||||||
|
return (int) $out[0]*1024;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see IDF_Scm::isAvailable()
|
||||||
|
*/
|
||||||
|
public function isAvailable()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
$out = $this->stdio->exec(array('interface_version'));
|
||||||
|
return floatval($out) >= self::$MIN_INTERFACE_VERSION;
|
||||||
|
}
|
||||||
|
catch (IDF_Scm_Exception $e) {}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see IDF_Scm::getBranches()
|
||||||
|
*/
|
||||||
|
public function getBranches()
|
||||||
|
{
|
||||||
|
if (isset($this->cache['branches'])) {
|
||||||
|
return $this->cache['branches'];
|
||||||
|
}
|
||||||
|
// FIXME: we could / should introduce handling of suspended
|
||||||
|
// (i.e. dead) branches here by hiding them from the user's eye...
|
||||||
|
$out = $this->stdio->exec(array('branches'));
|
||||||
|
|
||||||
|
// note: we could expand each branch with one of its head revisions
|
||||||
|
// here, but these would soon become bogus anyway and we cannot
|
||||||
|
// map multiple head revisions here either, so we just use the
|
||||||
|
// selector as placeholder
|
||||||
|
$res = array();
|
||||||
|
foreach (preg_split("/\n/", $out, -1, PREG_SPLIT_NO_EMPTY) as $b) {
|
||||||
|
$res["h:$b"] = $b;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->cache['branches'] = $res;
|
||||||
|
return $res;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* monotone has no concept of a "main" branch, so just return
|
||||||
|
* the configured one. Ensure however that we can select revisions
|
||||||
|
* with it at all.
|
||||||
|
*
|
||||||
|
* @see IDF_Scm::getMainBranch()
|
||||||
|
*/
|
||||||
|
public function getMainBranch()
|
||||||
|
{
|
||||||
|
$conf = $this->project->getConf();
|
||||||
|
if (false === ($branch = $conf->getVal('mtn_master_branch', false))
|
||||||
|
|| empty($branch)) {
|
||||||
|
$branch = "*";
|
||||||
|
}
|
||||||
|
|
||||||
|
return $branch;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see IDF_Scm::getArchiveStream
|
||||||
|
*/
|
||||||
|
public function getArchiveStream($commit, $prefix='repository/')
|
||||||
|
{
|
||||||
|
$revs = $this->_resolveSelector($commit);
|
||||||
|
// sanity: this should actually not happen, because the
|
||||||
|
// revision is validated before already
|
||||||
|
if (count($revs) == 0) {
|
||||||
|
return new Pluf_HTTP_Response_NotFound();
|
||||||
|
}
|
||||||
|
return new IDF_Scm_Monotone_ZipRender($this->stdio, $revs[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* expands a selector or a partial revision id to zero, one or
|
||||||
|
* multiple 40 byte revision ids
|
||||||
|
*
|
||||||
|
* @param string $selector
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private function _resolveSelector($selector)
|
||||||
|
{
|
||||||
|
$out = $this->stdio->exec(array('select', $selector));
|
||||||
|
return preg_split("/\n/", $out, -1, PREG_SPLIT_NO_EMPTY);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Queries the certs for a given revision and returns them in an
|
||||||
|
* associative array array("branch" => array("branch1", ...), ...)
|
||||||
|
*
|
||||||
|
* @param string
|
||||||
|
* @param array
|
||||||
|
*/
|
||||||
|
private function _getCerts($rev)
|
||||||
|
{
|
||||||
|
$cache = Pluf_Cache::factory();
|
||||||
|
$cachekey = 'mtn-plugin-certs-for-rev-' . $rev;
|
||||||
|
$certs = $cache->get($cachekey);
|
||||||
|
|
||||||
|
if ($certs === null) {
|
||||||
|
$out = $this->stdio->exec(array('certs', $rev));
|
||||||
|
|
||||||
|
$stanzas = IDF_Scm_Monotone_BasicIO::parse($out);
|
||||||
|
$certs = array();
|
||||||
|
foreach ($stanzas as $stanza) {
|
||||||
|
$certname = null;
|
||||||
|
foreach ($stanza as $stanzaline) {
|
||||||
|
// luckily, name always comes before value
|
||||||
|
if ($stanzaline['key'] == 'name') {
|
||||||
|
$certname = $stanzaline['values'][0];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($stanzaline['key'] == 'value') {
|
||||||
|
if (!array_key_exists($certname, $certs)) {
|
||||||
|
$certs[$certname] = array();
|
||||||
|
}
|
||||||
|
|
||||||
|
$certs[$certname][] = $stanzaline['values'][0];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$cache->set($cachekey, $certs);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $certs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns unique certificate values for the given revs and the specific
|
||||||
|
* cert name, optionally prefixed with $prefix
|
||||||
|
*
|
||||||
|
* @param array
|
||||||
|
* @param string
|
||||||
|
* @param string
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private function _getUniqueCertValuesFor($revs, $certName, $prefix)
|
||||||
|
{
|
||||||
|
$certValues = array();
|
||||||
|
foreach ($revs as $rev) {
|
||||||
|
$certs = $this->_getCerts($rev);
|
||||||
|
if (!array_key_exists($certName, $certs))
|
||||||
|
continue;
|
||||||
|
foreach ($certs[$certName] as $certValue) {
|
||||||
|
$certValues[] = "$prefix$certValue";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return array_unique($certValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see IDF_Scm::inBranches()
|
||||||
|
*/
|
||||||
|
public function inBranches($commit, $path)
|
||||||
|
{
|
||||||
|
$revs = $this->_resolveSelector($commit);
|
||||||
|
if (count($revs) == 0) return array();
|
||||||
|
return $this->_getUniqueCertValuesFor($revs, 'branch', 'h:');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see IDF_Scm::getTags()
|
||||||
|
*/
|
||||||
|
public function getTags()
|
||||||
|
{
|
||||||
|
if (isset($this->cache['tags'])) {
|
||||||
|
return $this->cache['tags'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$out = $this->stdio->exec(array('tags'));
|
||||||
|
|
||||||
|
$tags = array();
|
||||||
|
$stanzas = IDF_Scm_Monotone_BasicIO::parse($out);
|
||||||
|
foreach ($stanzas as $stanza) {
|
||||||
|
$tagname = null;
|
||||||
|
foreach ($stanza as $stanzaline) {
|
||||||
|
// revision comes directly after the tag stanza
|
||||||
|
if ($stanzaline['key'] == 'tag') {
|
||||||
|
$tagname = $stanzaline['values'][0];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($stanzaline['key'] == 'revision') {
|
||||||
|
// FIXME: warn if multiple revisions have
|
||||||
|
// equally named tags
|
||||||
|
if (!array_key_exists("t:$tagname", $tags)) {
|
||||||
|
$tags["t:$tagname"] = $tagname;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->cache['tags'] = $tags;
|
||||||
|
return $tags;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see IDF_Scm::inTags()
|
||||||
|
*/
|
||||||
|
public function inTags($commit, $path)
|
||||||
|
{
|
||||||
|
$revs = $this->_resolveSelector($commit);
|
||||||
|
if (count($revs) == 0) return array();
|
||||||
|
return $this->_getUniqueCertValuesFor($revs, 'tag', 't:');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes a single stanza coming from an extended manifest output
|
||||||
|
* and converts it into a file structure used by IDF
|
||||||
|
*
|
||||||
|
* @param string $forceBasedir If given then the element's path is checked
|
||||||
|
* to be directly beneath the given directory.
|
||||||
|
* If not, null is returned and the parsing is
|
||||||
|
* aborted.
|
||||||
|
* @return array | null
|
||||||
|
*/
|
||||||
|
private function _fillFileEntry(array $manifestEntry, $forceBasedir = null)
|
||||||
|
{
|
||||||
|
$fullpath = $manifestEntry[0]['values'][0];
|
||||||
|
$filename = basename($fullpath);
|
||||||
|
$dirname = dirname($fullpath);
|
||||||
|
$dirname = $dirname == '.' ? '' : $dirname;
|
||||||
|
|
||||||
|
if ($forceBasedir !== null && $forceBasedir != $dirname) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$file = array();
|
||||||
|
$file['file'] = $filename;
|
||||||
|
$file['fullpath'] = $fullpath;
|
||||||
|
$file['efullpath'] = self::smartEncode($fullpath);
|
||||||
|
|
||||||
|
$wanted_mark = '';
|
||||||
|
if ($manifestEntry[0]['key'] == 'dir') {
|
||||||
|
$file['type'] = 'tree';
|
||||||
|
$file['size'] = 0;
|
||||||
|
$wanted_mark = 'path_mark';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$file['type'] = 'blob';
|
||||||
|
$file['hash'] = $manifestEntry[1]['hash'];
|
||||||
|
$size = 0;
|
||||||
|
foreach ($manifestEntry as $line) {
|
||||||
|
if ($line['key'] == 'size') {
|
||||||
|
$size = $line['values'][0];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$file['size'] = $size;
|
||||||
|
$wanted_mark = 'content_mark';
|
||||||
|
}
|
||||||
|
|
||||||
|
$rev_mark = null;
|
||||||
|
foreach ($manifestEntry as $line) {
|
||||||
|
if ($line['key'] == $wanted_mark) {
|
||||||
|
$rev_mark = $line['hash'];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($rev_mark !== null) {
|
||||||
|
$file['rev'] = $rev_mark;
|
||||||
|
$certs = $this->_getCerts($rev_mark);
|
||||||
|
|
||||||
|
// FIXME: this assumes that author, date and changelog are always given
|
||||||
|
$file['author'] = implode(", ", $certs['author']);
|
||||||
|
|
||||||
|
$dates = array();
|
||||||
|
foreach ($certs['date'] as $date)
|
||||||
|
$dates[] = date('Y-m-d H:i:s', strtotime($date));
|
||||||
|
$file['date'] = implode(', ', $dates);
|
||||||
|
$combinedChangelog = implode("\n---\n", $certs['changelog']);
|
||||||
|
$split = preg_split("/[\n\r]/", $combinedChangelog, 2);
|
||||||
|
// FIXME: the complete log message is currently not used in the
|
||||||
|
// tree view (the same is true for the other SCM implementations)
|
||||||
|
// but we _should_ really use or at least return that here
|
||||||
|
// in case we want to do fancy stuff like described in
|
||||||
|
// issue 492
|
||||||
|
$file['log'] = $split[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $file;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see IDF_Scm::getTree()
|
||||||
|
*/
|
||||||
|
public function getTree($commit, $folder='/', $branch=null)
|
||||||
|
{
|
||||||
|
$revs = $this->_resolveSelector($commit);
|
||||||
|
if (count($revs) == 0) {
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
|
$out = $this->stdio->exec(array(
|
||||||
|
'get_extended_manifest_of', $revs[0]
|
||||||
|
));
|
||||||
|
|
||||||
|
$files = array();
|
||||||
|
$stanzas = IDF_Scm_Monotone_BasicIO::parse($out);
|
||||||
|
$folder = $folder == '/' || empty($folder) ? '' : $folder;
|
||||||
|
|
||||||
|
foreach ($stanzas as $stanza) {
|
||||||
|
if ($stanza[0]['key'] == 'format_version')
|
||||||
|
continue;
|
||||||
|
|
||||||
|
$file = $this->_fillFileEntry($stanza, $folder);
|
||||||
|
if ($file === null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
$files[] = (object) $file;
|
||||||
|
}
|
||||||
|
return $files;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see IDF_Scm::findAuthor()
|
||||||
|
*/
|
||||||
|
public function findAuthor($author)
|
||||||
|
{
|
||||||
|
// We extract anything which looks like an email.
|
||||||
|
$match = array();
|
||||||
|
if (!preg_match('/([^ ]+@[^ ]+)/', $author, $match)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
foreach (array('email', 'login') as $what) {
|
||||||
|
$sql = new Pluf_SQL($what.'=%s', array($match[1]));
|
||||||
|
$users = Pluf::factory('Pluf_User')->getList(array('filter'=>$sql->gen()));
|
||||||
|
if ($users->count() > 0) {
|
||||||
|
return $users[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see IDF_Scm::getAnonymousAccessUrl()
|
||||||
|
*/
|
||||||
|
public static function getAnonymousAccessUrl($project, $commit = null)
|
||||||
|
{
|
||||||
|
$scm = IDF_Scm::get($project);
|
||||||
|
$branch = $scm->getMainBranch();
|
||||||
|
|
||||||
|
if (!empty($commit)) {
|
||||||
|
$revs = $scm->_resolveSelector($commit);
|
||||||
|
if (count($revs) > 0) {
|
||||||
|
$certs = $scm->_getCerts($revs[0]);
|
||||||
|
// for the very seldom case that a revision
|
||||||
|
// has no branch certificate
|
||||||
|
if (!array_key_exists('branch', $certs)) {
|
||||||
|
$branch = '*';
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$branch = $certs['branch'][0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$remote_url = Pluf::f('mtn_remote_url', '');
|
||||||
|
if (empty($remote_url)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return sprintf($remote_url, $project->shortname).'?'.$branch;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see IDF_Scm::getAuthAccessUrl()
|
||||||
|
*/
|
||||||
|
public static function getAuthAccessUrl($project, $user, $commit = null)
|
||||||
|
{
|
||||||
|
$url = self::getAnonymousAccessUrl($project, $commit);
|
||||||
|
return preg_replace("#^ssh://#", "ssh://$user@", $url);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns this object correctly initialized for the project.
|
||||||
|
*
|
||||||
|
* @param IDF_Project
|
||||||
|
* @return IDF_Scm_Monotone
|
||||||
|
*/
|
||||||
|
public static function factory($project)
|
||||||
|
{
|
||||||
|
if (!array_key_exists($project->shortname, self::$instances)) {
|
||||||
|
self::$instances[$project->shortname] =
|
||||||
|
new IDF_Scm_Monotone($project);
|
||||||
|
}
|
||||||
|
return self::$instances[$project->shortname];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see IDF_Scm::validateRevision()
|
||||||
|
*/
|
||||||
|
public function validateRevision($commit)
|
||||||
|
{
|
||||||
|
$revs = $this->_resolveSelector($commit);
|
||||||
|
if (count($revs) == 0)
|
||||||
|
return IDF_Scm::REVISION_INVALID;
|
||||||
|
|
||||||
|
if (count($revs) > 1)
|
||||||
|
return IDF_Scm::REVISION_AMBIGUOUS;
|
||||||
|
|
||||||
|
return IDF_Scm::REVISION_VALID;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see IDF_Scm::disambiguateRevision
|
||||||
|
*/
|
||||||
|
public function disambiguateRevision($commit)
|
||||||
|
{
|
||||||
|
$revs = $this->_resolveSelector($commit);
|
||||||
|
|
||||||
|
$out = array();
|
||||||
|
foreach ($revs as $rev)
|
||||||
|
{
|
||||||
|
$certs = $this->_getCerts($rev);
|
||||||
|
|
||||||
|
$log = array();
|
||||||
|
$log['author'] = implode(', ', $certs['author']);
|
||||||
|
|
||||||
|
$log['branch'] = implode(', ', $certs['branch']);
|
||||||
|
|
||||||
|
$dates = array();
|
||||||
|
foreach ($certs['date'] as $date)
|
||||||
|
$dates[] = date('Y-m-d H:i:s', strtotime($date));
|
||||||
|
$log['date'] = implode(', ', $dates);
|
||||||
|
|
||||||
|
$combinedChangelog = implode("\n---\n", $certs['changelog']);
|
||||||
|
$split = preg_split("/[\n\r]/", $combinedChangelog, 2);
|
||||||
|
$log['title'] = $split[0];
|
||||||
|
$log['full_message'] = (isset($split[1])) ? trim($split[1]) : '';
|
||||||
|
|
||||||
|
$log['commit'] = $rev;
|
||||||
|
|
||||||
|
$out[] = (object)$log;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see IDF_Scm::getPathInfo()
|
||||||
|
*/
|
||||||
|
public function getPathInfo($file, $commit = null)
|
||||||
|
{
|
||||||
|
if ($commit === null) {
|
||||||
|
$commit = 'h:' . $this->getMainBranch();
|
||||||
|
}
|
||||||
|
|
||||||
|
$revs = $this->_resolveSelector($commit);
|
||||||
|
if (count($revs) == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
$out = $this->stdio->exec(array(
|
||||||
|
'get_extended_manifest_of', $revs[0]
|
||||||
|
));
|
||||||
|
|
||||||
|
$files = array();
|
||||||
|
$stanzas = IDF_Scm_Monotone_BasicIO::parse($out);
|
||||||
|
|
||||||
|
foreach ($stanzas as $stanza) {
|
||||||
|
if ($stanza[0]['key'] == 'format_version')
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if ($stanza[0]['values'][0] != $file)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
$file = $this->_fillFileEntry($stanza);
|
||||||
|
return (object) $file;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see IDF_Scm::getFile()
|
||||||
|
*/
|
||||||
|
public function getFile($def, $cmd_only=false)
|
||||||
|
{
|
||||||
|
// this won't work with remote databases
|
||||||
|
if ($cmd_only) {
|
||||||
|
throw new Pluf_Exception_NotImplemented();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->stdio->exec(array('get_file', $def->hash));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the differences between two revisions as unified diff
|
||||||
|
*
|
||||||
|
* @param string The target of the diff
|
||||||
|
* @param string The source of the diff, if not given, the first
|
||||||
|
* parent of the target is used
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private function _getDiff($target, $source = null)
|
||||||
|
{
|
||||||
|
if (empty($source)) {
|
||||||
|
$source = "p:$target";
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: add real support for merge revisions here which have
|
||||||
|
// two distinct diff sets
|
||||||
|
$targets = $this->_resolveSelector($target);
|
||||||
|
$sources = $this->_resolveSelector($source);
|
||||||
|
|
||||||
|
if (count($targets) == 0 || count($sources) == 0) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// if target contains a root revision, we cannot produce a diff
|
||||||
|
if (empty($sources[0])) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->stdio->exec(
|
||||||
|
array('content_diff'),
|
||||||
|
array('r' => array($sources[0], $targets[0]))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see IDF_Scm::getChanges()
|
||||||
|
*/
|
||||||
|
public function getChanges($commit)
|
||||||
|
{
|
||||||
|
$revs = $this->_resolveSelector($commit);
|
||||||
|
if (count($revs) == 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
$revision = $revs[0];
|
||||||
|
$out = $this->stdio->exec(array('get_revision', $revision));
|
||||||
|
$stanzas = IDF_Scm_Monotone_BasicIO::parse($out);
|
||||||
|
|
||||||
|
$return = (object) array(
|
||||||
|
'additions' => array(),
|
||||||
|
'deletions' => array(),
|
||||||
|
'renames' => array(),
|
||||||
|
'patches' => array(),
|
||||||
|
'properties' => array(),
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ($stanzas as $stanza) {
|
||||||
|
if ($stanza[0]['key'] == 'format_version' ||
|
||||||
|
$stanza[0]['key'] == 'old_revision' ||
|
||||||
|
$stanza[0]['key'] == 'new_manifest')
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if ($stanza[0]['key'] == 'add_file' ||
|
||||||
|
$stanza[0]['key'] == 'add_dir') {
|
||||||
|
$return->additions[] = $stanza[0]['values'][0];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($stanza[0]['key'] == 'delete') {
|
||||||
|
$return->deletions[] = $stanza[0]['values'][0];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($stanza[0]['key'] == 'rename') {
|
||||||
|
$return->renames[$stanza[0]['values'][0]] =
|
||||||
|
$stanza[1]['values'][0];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($stanza[0]['key'] == 'patch') {
|
||||||
|
$return->patches[] = $stanza[0]['values'][0];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($stanza[0]['key'] == 'clear' ||
|
||||||
|
$stanza[0]['key'] == 'set') {
|
||||||
|
|
||||||
|
$filename = $stanza[0]['values'][0];
|
||||||
|
if (!array_key_exists($filename, $return->properties)) {
|
||||||
|
$return->properties[$filename] = array();
|
||||||
|
}
|
||||||
|
$key = $stanza[1]['values'][0];
|
||||||
|
$value = null;
|
||||||
|
if (isset($stanza[2])) {
|
||||||
|
$value = $stanza[2]['values'][0];
|
||||||
|
}
|
||||||
|
$return->properties[$filename][$key] = $value;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see IDF_Scm::getCommit()
|
||||||
|
*/
|
||||||
|
public function getCommit($commit, $getdiff=false)
|
||||||
|
{
|
||||||
|
$revs = $this->_resolveSelector($commit);
|
||||||
|
if (count($revs) == 0)
|
||||||
|
return array();
|
||||||
|
|
||||||
|
$res = array();
|
||||||
|
|
||||||
|
$parents = $this->stdio->exec(array('parents', $revs[0]));
|
||||||
|
$res['parents'] = preg_split("/\n/", $parents, -1, PREG_SPLIT_NO_EMPTY);
|
||||||
|
|
||||||
|
$certs = $this->_getCerts($revs[0]);
|
||||||
|
// FIXME: this assumes that author, date and changelog are always given
|
||||||
|
$res['author'] = implode(', ', $certs['author']);
|
||||||
|
|
||||||
|
$dates = array();
|
||||||
|
foreach ($certs['date'] as $date)
|
||||||
|
$dates[] = date('Y-m-d H:i:s', strtotime($date));
|
||||||
|
$res['date'] = implode(', ', $dates);
|
||||||
|
|
||||||
|
$combinedChangelog = implode("\n---\n", $certs['changelog']);
|
||||||
|
$split = preg_split("/[\n\r]/", $combinedChangelog, 2);
|
||||||
|
$res['title'] = $split[0];
|
||||||
|
$res['full_message'] = (isset($split[1])) ? trim($split[1]) : '';
|
||||||
|
|
||||||
|
$res['branch'] = implode(', ', $certs['branch']);
|
||||||
|
$res['commit'] = $revs[0];
|
||||||
|
|
||||||
|
$res['diff'] = ($getdiff) ? $this->_getDiff($revs[0]) : '';
|
||||||
|
|
||||||
|
return (object) $res;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see IDF_Scm::getExtraProperties
|
||||||
|
*/
|
||||||
|
public function getExtraProperties($obj)
|
||||||
|
{
|
||||||
|
return (isset($obj->parents)) ? array('parents' => $obj->parents) : array();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see IDF_Scm::isCommitLarge()
|
||||||
|
*/
|
||||||
|
public function isCommitLarge($commit=null)
|
||||||
|
{
|
||||||
|
if (empty($commit)) {
|
||||||
|
$commit = 'h:'.$this->getMainBranch();
|
||||||
|
}
|
||||||
|
|
||||||
|
$revs = $this->_resolveSelector($commit);
|
||||||
|
if (count($revs) == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
$out = $this->stdio->exec(array(
|
||||||
|
'get_revision', $revs[0]
|
||||||
|
));
|
||||||
|
|
||||||
|
$newAndPatchedFiles = 0;
|
||||||
|
$stanzas = IDF_Scm_Monotone_BasicIO::parse($out);
|
||||||
|
|
||||||
|
foreach ($stanzas as $stanza) {
|
||||||
|
if ($stanza[0]['key'] == 'patch' || $stanza[0]['key'] == 'add_file')
|
||||||
|
$newAndPatchedFiles++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $newAndPatchedFiles > 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see IDF_Scm::getChangeLog()
|
||||||
|
*/
|
||||||
|
public function getChangeLog($commit=null, $n=10)
|
||||||
|
{
|
||||||
|
$horizont = $this->_resolveSelector($commit);
|
||||||
|
$initialBranches = array();
|
||||||
|
$logs = array();
|
||||||
|
|
||||||
|
while (!empty($horizont) && $n > 0) {
|
||||||
|
if (count($horizont) > 1) {
|
||||||
|
$out = $this->stdio->exec(array('toposort') + $horizont);
|
||||||
|
$horizont = preg_split("/\n/", $out, -1, PREG_SPLIT_NO_EMPTY);
|
||||||
|
}
|
||||||
|
|
||||||
|
$rev = array_shift($horizont);
|
||||||
|
$certs = $this->_getCerts($rev);
|
||||||
|
|
||||||
|
// read in the initial branches we should follow
|
||||||
|
if (count($initialBranches) == 0) {
|
||||||
|
if (!isset($certs['branch'])) {
|
||||||
|
// this revision has no branch cert, we cannot start logging
|
||||||
|
// from this revision
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$initialBranches = $certs['branch'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// only add it to our log if it is on one of the initial branches
|
||||||
|
// ignore revisions without any branch certificate
|
||||||
|
if (count(array_intersect($initialBranches, (array)@$certs['branch'])) > 0) {
|
||||||
|
--$n;
|
||||||
|
|
||||||
|
$log = array();
|
||||||
|
$log['author'] = implode(', ', $certs['author']);
|
||||||
|
|
||||||
|
$dates = array();
|
||||||
|
foreach ($certs['date'] as $date)
|
||||||
|
$dates[] = date('Y-m-d H:i:s', strtotime($date));
|
||||||
|
$log['date'] = implode(', ', $dates);
|
||||||
|
|
||||||
|
$combinedChangelog = implode("\n---\n", $certs['changelog']);
|
||||||
|
$split = preg_split("/[\n\r]/", $combinedChangelog, 2);
|
||||||
|
$log['title'] = $split[0];
|
||||||
|
$log['full_message'] = (isset($split[1])) ? trim($split[1]) : '';
|
||||||
|
|
||||||
|
$log['commit'] = $rev;
|
||||||
|
|
||||||
|
$logs[] = (object)$log;
|
||||||
|
|
||||||
|
$out = $this->stdio->exec(array('parents', $rev));
|
||||||
|
$horizont += preg_split("/\n/", $out, -1, PREG_SPLIT_NO_EMPTY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $logs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
174
src/IDF/Scm/Monotone/BasicIO.php
Normal file
174
src/IDF/Scm/Monotone/BasicIO.php
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
<?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) 2010 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 ***** */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility class to parse and compile basic_io stanzas
|
||||||
|
*
|
||||||
|
* @author Thomas Keller <me@thomaskeller.biz>
|
||||||
|
*/
|
||||||
|
class IDF_Scm_Monotone_BasicIO
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Parses monotone's basic_io format
|
||||||
|
*
|
||||||
|
* @param string $in
|
||||||
|
* @return array of arrays
|
||||||
|
*/
|
||||||
|
public static function parse($in)
|
||||||
|
{
|
||||||
|
$pos = 0;
|
||||||
|
$stanzas = array();
|
||||||
|
$length = strlen($in);
|
||||||
|
|
||||||
|
while ($pos < $length) {
|
||||||
|
$stanza = array();
|
||||||
|
while ($pos < $length) {
|
||||||
|
if ($in[$pos] == "\n") break;
|
||||||
|
|
||||||
|
$stanzaLine = array('key' => '', 'values' => array(), 'hash' => null);
|
||||||
|
while ($pos < $length) {
|
||||||
|
$ch = $in[$pos];
|
||||||
|
if ($ch == '"' || $ch == '[') break;
|
||||||
|
++$pos;
|
||||||
|
if ($ch == ' ') continue;
|
||||||
|
$stanzaLine['key'] .= $ch;
|
||||||
|
}
|
||||||
|
|
||||||
|
// symbol w/o a value list
|
||||||
|
if ($pos >= $length || $in[$pos] == "\n") break;
|
||||||
|
|
||||||
|
if ($in[$pos] == '[') {
|
||||||
|
unset($stanzaLine['values']);
|
||||||
|
++$pos; // opening square bracket
|
||||||
|
$stanzaLine['hash'] = substr($in, $pos, 40);
|
||||||
|
$pos += 40;
|
||||||
|
++$pos; // closing square bracket
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
unset($stanzaLine['hash']);
|
||||||
|
$valCount = 0;
|
||||||
|
// if hashs and plain values are encountered in the same
|
||||||
|
// value list, we add the hash values as simple values as well
|
||||||
|
while ($in[$pos] == '"' || $in[$pos] == '[') {
|
||||||
|
$isHashValue = $in[$pos] == '[';
|
||||||
|
++$pos; // opening quote / bracket
|
||||||
|
$stanzaLine['values'][$valCount] = '';
|
||||||
|
while ($pos < $length) {
|
||||||
|
$ch = $in[$pos]; $pr = $in[$pos-1];
|
||||||
|
if (($isHashValue && $ch == ']')
|
||||||
|
||(!$isHashValue && $ch == '"' && $pr != '\\'))
|
||||||
|
break;
|
||||||
|
++$pos;
|
||||||
|
$stanzaLine['values'][$valCount] .= $ch;
|
||||||
|
}
|
||||||
|
++$pos; // closing quote
|
||||||
|
|
||||||
|
if (!$isHashValue) {
|
||||||
|
$stanzaLine['values'][$valCount] = str_replace(
|
||||||
|
array("\\\\", "\\\""),
|
||||||
|
array("\\", "\""),
|
||||||
|
$stanzaLine['values'][$valCount]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($pos >= $length)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if ($in[$pos] == ' ') {
|
||||||
|
++$pos; // space
|
||||||
|
++$valCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$stanza[] = $stanzaLine;
|
||||||
|
++$pos; // newline
|
||||||
|
}
|
||||||
|
$stanzas[] = $stanza;
|
||||||
|
++$pos; // newline
|
||||||
|
}
|
||||||
|
return $stanzas;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compiles monotone's basicio format
|
||||||
|
*
|
||||||
|
* @param array $in Array of arrays
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function compile($in)
|
||||||
|
{
|
||||||
|
$out = "";
|
||||||
|
$first = true;
|
||||||
|
foreach ((array)$in as $sx => $stanza) {
|
||||||
|
if ($first)
|
||||||
|
$first = false;
|
||||||
|
else
|
||||||
|
$out .= "\n";
|
||||||
|
|
||||||
|
$maxkeylength = 0;
|
||||||
|
foreach ((array)$stanza as $lx => $line) {
|
||||||
|
if (!array_key_exists('key', $line)) {
|
||||||
|
throw new IDF_Scm_Exception(
|
||||||
|
'"key" not found in basicio stanza '.$sx.', line '.$lx
|
||||||
|
);
|
||||||
|
}
|
||||||
|
$maxkeylength = max($maxkeylength, strlen($line['key']));
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ((array)$stanza as $lx => $line) {
|
||||||
|
$out .= str_pad($line['key'], $maxkeylength, ' ', STR_PAD_LEFT);
|
||||||
|
|
||||||
|
if (array_key_exists('hash', $line)) {
|
||||||
|
$out .= ' ['.$line['hash'].']';
|
||||||
|
} else
|
||||||
|
if (array_key_exists('values', $line)) {
|
||||||
|
if (!is_array($line['values']) || count($line['values']) == 0) {
|
||||||
|
throw new IDF_Scm_Exception(
|
||||||
|
'"values" must be an array of a size >= 1 '.
|
||||||
|
'in basicio stanza '.$sx.', line '.$lx
|
||||||
|
);
|
||||||
|
}
|
||||||
|
foreach ($line['values'] as $value) {
|
||||||
|
$out .= ' "'.str_replace(
|
||||||
|
array("\\", "\""),
|
||||||
|
array("\\\\", "\\\""),
|
||||||
|
$value).'"';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new IDF_Scm_Exception(
|
||||||
|
'neither "hash" nor "values" found in basicio '.
|
||||||
|
'stanza '.$sx.', line '.$lx
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$out .= "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
403
src/IDF/Scm/Monotone/Stdio.php
Normal file
403
src/IDF/Scm/Monotone/Stdio.php
Normal file
@@ -0,0 +1,403 @@
|
|||||||
|
<?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) 2010 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 ***** */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Monotone stdio class
|
||||||
|
*
|
||||||
|
* Connects to a monotone process and executes commands via its
|
||||||
|
* stdio interface
|
||||||
|
*
|
||||||
|
* @author Thomas Keller <me@thomaskeller.biz>
|
||||||
|
*/
|
||||||
|
class IDF_Scm_Monotone_Stdio
|
||||||
|
{
|
||||||
|
/** this is the most recent STDIO version. The number is output
|
||||||
|
at the protocol start. Older versions of monotone (prior 0.47)
|
||||||
|
do not output it and are therefor incompatible */
|
||||||
|
public static $SUPPORTED_STDIO_VERSION = 2;
|
||||||
|
|
||||||
|
private $project;
|
||||||
|
private $proc;
|
||||||
|
private $pipes;
|
||||||
|
private $oob;
|
||||||
|
private $cmdnum;
|
||||||
|
private $lastcmd;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor - starts the stdio process
|
||||||
|
*
|
||||||
|
* @param IDF_Project
|
||||||
|
*/
|
||||||
|
public function __construct(IDF_Project $project)
|
||||||
|
{
|
||||||
|
$this->project = $project;
|
||||||
|
$this->start();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destructor - stops the stdio process
|
||||||
|
*/
|
||||||
|
public function __destruct()
|
||||||
|
{
|
||||||
|
$this->stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a string with additional options which are passed to
|
||||||
|
* an mtn instance connecting to remote databases
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function _getAuthOptions()
|
||||||
|
{
|
||||||
|
$prjconf = $this->project->getConf();
|
||||||
|
$name = $prjconf->getVal('mtn_client_key_name', false);
|
||||||
|
$hash = $prjconf->getVal('mtn_client_key_hash', false);
|
||||||
|
|
||||||
|
if (!$name || !$hash) {
|
||||||
|
throw new IDF_Scm_Exception(sprintf(
|
||||||
|
__('Monotone client key name or hash not in project conf.')
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
$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
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// in case somebody cleaned out the cache, we restore the key here
|
||||||
|
$keyfile = $keydir . '/' . $name .'.'. $hash;
|
||||||
|
if (!file_exists($keyfile)) {
|
||||||
|
$data = $prjconf->getVal('mtn_client_key_data');
|
||||||
|
if (!file_put_contents($keyfile, $data, LOCK_EX)) {
|
||||||
|
throw new IDF_Scm_Exception(sprintf(
|
||||||
|
__('Could not write client key "%s"'), $keyfile
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sprintf('--keydir=%s --key=%s ',
|
||||||
|
escapeshellarg($keydir),
|
||||||
|
escapeshellarg($hash)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the stdio process and resets the command counter
|
||||||
|
*/
|
||||||
|
public function start()
|
||||||
|
{
|
||||||
|
if (is_resource($this->proc))
|
||||||
|
$this->stop();
|
||||||
|
|
||||||
|
$remote_db_access = Pluf::f('mtn_db_access', 'remote') == 'remote';
|
||||||
|
|
||||||
|
$cmd = Pluf::f('idf_exec_cmd_prefix', '') .
|
||||||
|
Pluf::f('mtn_path', 'mtn') . ' ';
|
||||||
|
|
||||||
|
$opts = Pluf::f('mtn_opts', array());
|
||||||
|
foreach ($opts as $opt) {
|
||||||
|
$cmd .= sprintf('%s ', escapeshellarg($opt));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($remote_db_access) {
|
||||||
|
$cmd .= $this->_getAuthOptions();
|
||||||
|
$host = sprintf(Pluf::f('mtn_remote_url'), $this->project->shortname);
|
||||||
|
$cmd .= sprintf('automate remote_stdio %s', escapeshellarg($host));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$repo = sprintf(Pluf::f('mtn_repositories'), $this->project->shortname);
|
||||||
|
if (!file_exists($repo)) {
|
||||||
|
throw new IDF_Scm_Exception(
|
||||||
|
"repository file '$repo' does not exist"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
$cmd .= sprintf('--db %s automate stdio', escapeshellarg($repo));
|
||||||
|
}
|
||||||
|
|
||||||
|
$descriptors = array(
|
||||||
|
0 => array('pipe', 'r'),
|
||||||
|
1 => array('pipe', 'w'),
|
||||||
|
2 => array('pipe', 'w'),
|
||||||
|
);
|
||||||
|
|
||||||
|
$env = array('LANG' => 'en_US.UTF-8');
|
||||||
|
$this->proc = proc_open($cmd, $descriptors, $this->pipes,
|
||||||
|
null, $env);
|
||||||
|
|
||||||
|
if (!is_resource($this->proc)) {
|
||||||
|
throw new IDF_Scm_Exception('could not start stdio process');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->_checkVersion();
|
||||||
|
|
||||||
|
$this->cmdnum = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops the stdio process and closes all pipes
|
||||||
|
*/
|
||||||
|
public function stop()
|
||||||
|
{
|
||||||
|
if (!is_resource($this->proc))
|
||||||
|
return;
|
||||||
|
|
||||||
|
fclose($this->pipes[0]);
|
||||||
|
fclose($this->pipes[1]);
|
||||||
|
fclose($this->pipes[2]);
|
||||||
|
|
||||||
|
proc_close($this->proc);
|
||||||
|
$this->proc = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* select()'s on stdout and returns true as soon as we got new
|
||||||
|
* data to read, false if the select() timed out
|
||||||
|
*
|
||||||
|
* @return boolean
|
||||||
|
* @throws IDF_Scm_Exception
|
||||||
|
*/
|
||||||
|
private function _waitForReadyRead()
|
||||||
|
{
|
||||||
|
if (!is_resource($this->pipes[1]))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
$read = array($this->pipes[1], $this->pipes[2]);
|
||||||
|
$write = $except = null;
|
||||||
|
$streamsChanged = stream_select(
|
||||||
|
$read, $write, $except, 0, 20000
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($streamsChanged === false) {
|
||||||
|
throw new IDF_Scm_Exception(
|
||||||
|
'Could not select() on read pipe'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($streamsChanged == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks the version of the used stdio protocol
|
||||||
|
*
|
||||||
|
* @throws IDF_Scm_Exception
|
||||||
|
*/
|
||||||
|
private function _checkVersion()
|
||||||
|
{
|
||||||
|
$this->_waitForReadyRead();
|
||||||
|
|
||||||
|
$version = fgets($this->pipes[1]);
|
||||||
|
if ($version === false) {
|
||||||
|
throw new IDF_Scm_Exception(
|
||||||
|
"Could not determine stdio version, stderr is:\n".
|
||||||
|
$this->_readStderr()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!preg_match('/^format-version: (\d+)$/', $version, $m) ||
|
||||||
|
$m[1] != self::$SUPPORTED_STDIO_VERSION)
|
||||||
|
{
|
||||||
|
throw new IDF_Scm_Exception(
|
||||||
|
'stdio format version mismatch, expected "'.
|
||||||
|
self::$SUPPORTED_STDIO_VERSION.'", got "'.@$m[1].'"'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fgets($this->pipes[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes a command to stdio
|
||||||
|
*
|
||||||
|
* @param array
|
||||||
|
* @param array
|
||||||
|
* @throws IDF_Scm_Exception
|
||||||
|
*/
|
||||||
|
private function _write(array $args, array $options = array())
|
||||||
|
{
|
||||||
|
$cmd = '';
|
||||||
|
if (count($options) > 0) {
|
||||||
|
$cmd = 'o';
|
||||||
|
foreach ($options as $k => $vals) {
|
||||||
|
if (!is_array($vals))
|
||||||
|
$vals = array($vals);
|
||||||
|
|
||||||
|
foreach ($vals as $v) {
|
||||||
|
$cmd .= strlen((string)$k) . ':' . (string)$k;
|
||||||
|
$cmd .= strlen((string)$v) . ':' . (string)$v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$cmd .= 'e ';
|
||||||
|
}
|
||||||
|
|
||||||
|
$cmd .= 'l';
|
||||||
|
foreach ($args as $arg) {
|
||||||
|
$cmd .= strlen((string)$arg) . ':' . (string)$arg;
|
||||||
|
}
|
||||||
|
$cmd .= "e\n";
|
||||||
|
|
||||||
|
if (!fwrite($this->pipes[0], $cmd)) {
|
||||||
|
throw new IDF_Scm_Exception("could not write '$cmd' to process");
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->lastcmd = $cmd;
|
||||||
|
$this->cmdnum++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads all output from stderr and returns it
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private function _readStderr()
|
||||||
|
{
|
||||||
|
$err = "";
|
||||||
|
while (($line = fgets($this->pipes[2])) !== false) {
|
||||||
|
$err .= $line;
|
||||||
|
}
|
||||||
|
return empty($err) ? '<empty>' : $err;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads the last output from the stdio process, parses and returns it
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
* @throws IDF_Scm_Exception
|
||||||
|
*/
|
||||||
|
private function _readStdout()
|
||||||
|
{
|
||||||
|
$this->oob = array('w' => array(),
|
||||||
|
'p' => array(),
|
||||||
|
't' => array(),
|
||||||
|
'e' => array());
|
||||||
|
|
||||||
|
$output = "";
|
||||||
|
$errcode = 0;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
if (!$this->_waitForReadyRead())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
$data = array(0,"",0);
|
||||||
|
$idx = 0;
|
||||||
|
while (true) {
|
||||||
|
$c = fgetc($this->pipes[1]);
|
||||||
|
if ($c === false) {
|
||||||
|
throw new IDF_Scm_Exception(
|
||||||
|
"No data on stdin, stderr is:\n".
|
||||||
|
$this->_readStderr()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($c == ':') {
|
||||||
|
if ($idx == 2)
|
||||||
|
break;
|
||||||
|
|
||||||
|
++$idx;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_numeric($c))
|
||||||
|
$data[$idx] = $data[$idx] * 10 + $c;
|
||||||
|
else
|
||||||
|
$data[$idx] .= $c;
|
||||||
|
}
|
||||||
|
|
||||||
|
// sanity
|
||||||
|
if ($this->cmdnum != $data[0]) {
|
||||||
|
throw new IDF_Scm_Exception(
|
||||||
|
'command numbers out of sync; expected '.
|
||||||
|
$this->cmdnum .', got '. $data[0]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$toRead = $data[2];
|
||||||
|
$buffer = "";
|
||||||
|
while ($toRead > 0) {
|
||||||
|
$buffer .= fread($this->pipes[1], $toRead);
|
||||||
|
$toRead = $data[2] - strlen($buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($data[1]) {
|
||||||
|
case 'w':
|
||||||
|
case 'p':
|
||||||
|
case 't':
|
||||||
|
case 'e':
|
||||||
|
$this->oob[$data[1]][] = $buffer;
|
||||||
|
continue;
|
||||||
|
case 'm':
|
||||||
|
$output .= $buffer;
|
||||||
|
continue;
|
||||||
|
case 'l':
|
||||||
|
$errcode = $buffer;
|
||||||
|
break 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($errcode != 0) {
|
||||||
|
throw new IDF_Scm_Exception(
|
||||||
|
"command '{$this->lastcmd}' returned error code $errcode: ".
|
||||||
|
implode(' ', $this->oob['e'])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $output;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes a command over stdio and returns its result
|
||||||
|
*
|
||||||
|
* @param array Array of arguments
|
||||||
|
* @param array Array of options as key-value pairs. Multiple options
|
||||||
|
* can be defined in sub-arrays, like
|
||||||
|
* "r" => array("123...", "456...")
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function exec(array $args, array $options = array())
|
||||||
|
{
|
||||||
|
$this->_write($args, $options);
|
||||||
|
return $this->_readStdout();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the last out-of-band output for a previously executed
|
||||||
|
* command as associative array with 'e' (error), 'w' (warning),
|
||||||
|
* 'p' (progress) and 't' (ticker, unparsed) as keys
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getLastOutOfBandOutput()
|
||||||
|
{
|
||||||
|
return $this->oob;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
269
src/IDF/Scm/Monotone/Usher.php
Normal file
269
src/IDF/Scm/Monotone/Usher.php
Normal file
@@ -0,0 +1,269 @@
|
|||||||
|
<?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) 2010 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 ***** */
|
||||||
|
|
||||||
|
require_once(dirname(__FILE__) . "/BasicIO.php");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connects with the admininistrative interface of usher,
|
||||||
|
* the monotone proxy. This class contains only static methods because
|
||||||
|
* there is really no state to keep between each invocation, as usher
|
||||||
|
* closes the connection after every command.
|
||||||
|
*
|
||||||
|
* @author Thomas Keller <me@thomaskeller.biz>
|
||||||
|
*/
|
||||||
|
class IDF_Scm_Monotone_Usher
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Without giving a specific state, returns an array of all servers.
|
||||||
|
* When a state is given, the array contains only servers which are
|
||||||
|
* in the given state.
|
||||||
|
*
|
||||||
|
* @param string $state One of REMOTE, ACTIVE, WAITING, SLEEPING,
|
||||||
|
* STOPPING, STOPPED, SHUTTINGDOWN or SHUTDOWN
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public static function getServerList($state = null)
|
||||||
|
{
|
||||||
|
$conn = self::_triggerCommand('LIST '.$state);
|
||||||
|
if ($conn == 'none')
|
||||||
|
return array();
|
||||||
|
|
||||||
|
return preg_split('/[ ]/', $conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array of all open connections to the given server, or to
|
||||||
|
* any server if no server is specified.
|
||||||
|
* If there are no connections to list, an empty array is returned.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* array("server1" => array(
|
||||||
|
* array("address" => "192.168.1.0", "port" => "13456"),
|
||||||
|
* ...
|
||||||
|
* ),
|
||||||
|
* "server2" => ...
|
||||||
|
* )
|
||||||
|
*
|
||||||
|
* @param string $server
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public static function getConnectionList($server = null)
|
||||||
|
{
|
||||||
|
$conn = self::_triggerCommand('LISTCONNECTIONS '.$server);
|
||||||
|
if ($conn == 'none')
|
||||||
|
return array();
|
||||||
|
|
||||||
|
$single_conns = preg_split('/[ ]/', $conn);
|
||||||
|
$ret = array();
|
||||||
|
foreach ($single_conns as $conn) {
|
||||||
|
preg_match('/\((\w+)\)([^:]+):(\d+)/', $conn, $matches);
|
||||||
|
$ret[$matches[1]][] = (object)array(
|
||||||
|
'server' => $matches[1],
|
||||||
|
'address' => $matches[2],
|
||||||
|
'port' => $matches[3],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($server !== null) {
|
||||||
|
if (array_key_exists($server, $ret))
|
||||||
|
return $ret[$server];
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the status of a particular server, or of the usher as a whole if
|
||||||
|
* no server is specified.
|
||||||
|
*
|
||||||
|
* @param string $server
|
||||||
|
* @return One of REMOTE, SLEEPING, STOPPING, STOPPED for servers or
|
||||||
|
* ACTIVE, WAITING, SHUTTINGDOWN or SHUTDOWN for usher itself
|
||||||
|
*/
|
||||||
|
public static function getStatus($server = null)
|
||||||
|
{
|
||||||
|
return self::_triggerCommand('STATUS '.$server);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Looks up the name of the server that would be used for an incoming
|
||||||
|
* connection having the given host and pattern.
|
||||||
|
*
|
||||||
|
* @param string $host Host
|
||||||
|
* @param string $pattern Branch pattern
|
||||||
|
* @return server name
|
||||||
|
* @throws IDF_Scm_Exception
|
||||||
|
*/
|
||||||
|
public static function matchServer($host, $pattern)
|
||||||
|
{
|
||||||
|
$ret = self::_triggerCommand('MATCH '.$host.' '.$pattern);
|
||||||
|
if (preg_match('/^OK: (.+)/', $ret, $m))
|
||||||
|
return $m[1];
|
||||||
|
preg_match('/^ERROR: (.+)/', $ret, $m);
|
||||||
|
throw new IDF_Scm_Exception('could not match server: '.$m[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prevent the given local server from receiving further connections,
|
||||||
|
* and stop it once all connections are closed. The return value will
|
||||||
|
* be the new status of that server: ACTIVE local servers will become
|
||||||
|
* STOPPING, and WAITING and SLEEPING serveres become STOPPED.
|
||||||
|
* Servers in other states are not affected.
|
||||||
|
*
|
||||||
|
* @param string $server
|
||||||
|
* @return string State of the server after the command
|
||||||
|
*/
|
||||||
|
public static function stopServer($server)
|
||||||
|
{
|
||||||
|
return self::_triggerCommand("STOP $server");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allow a STOPPED or STOPPING server to receive connections again.
|
||||||
|
* The return value is the new status of that server: STOPPING servers
|
||||||
|
* become ACTIVE, and STOPPED servers become SLEEPING. Servers in other
|
||||||
|
* states are not affected.
|
||||||
|
*
|
||||||
|
* @param string $server
|
||||||
|
* @return string State of the server after the command
|
||||||
|
*/
|
||||||
|
public static function startServer($server)
|
||||||
|
{
|
||||||
|
return self::_triggerCommand('START '.$server);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Immediately kill the given local server, dropping any open connections,
|
||||||
|
* and prevent is from receiving new connections and restarting. The named
|
||||||
|
* server will immediately change to state STOPPED.
|
||||||
|
*
|
||||||
|
* @param string $server
|
||||||
|
* @return bool True if successful
|
||||||
|
*/
|
||||||
|
public static function killServer($server)
|
||||||
|
{
|
||||||
|
return self::_triggerCommand('KILL_NOW '.$server) == 'ok';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Do not accept new connections for any servers, local or remote.
|
||||||
|
*
|
||||||
|
* @return bool True if successful
|
||||||
|
*/
|
||||||
|
public static function shutDown()
|
||||||
|
{
|
||||||
|
return self::_triggerCommand('SHUTDOWN') == 'ok';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Begin accepting connections after a SHUTDOWN.
|
||||||
|
*
|
||||||
|
* @return bool True if successful
|
||||||
|
*/
|
||||||
|
public static function startUp()
|
||||||
|
{
|
||||||
|
return self::_triggerCommand('STARTUP') == 'ok';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reload the config file, the same as sending SIGHUP.
|
||||||
|
*
|
||||||
|
* @return bool True if successful (after the configuration was reloaded)
|
||||||
|
*/
|
||||||
|
public static function reload()
|
||||||
|
{
|
||||||
|
return self::_triggerCommand('RELOAD') == 'ok';
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function _triggerCommand($cmd)
|
||||||
|
{
|
||||||
|
$uc = Pluf::f('mtn_usher_conf', false);
|
||||||
|
if (!$uc || !is_readable($uc)) {
|
||||||
|
throw new IDF_Scm_Exception(
|
||||||
|
'"mtn_usher_conf" is not configured or not readable'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$parsed_config =
|
||||||
|
IDF_Scm_Monotone_BasicIO::parse(file_get_contents($uc));
|
||||||
|
$host = $port = $user = $pass = null;
|
||||||
|
foreach ($parsed_config as $stanza) {
|
||||||
|
foreach ($stanza as $line) {
|
||||||
|
if ($line['key'] == 'adminaddr') {
|
||||||
|
list($host, $port) = explode(":", @$line['values'][0]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if ($line['key'] == 'userpass') {
|
||||||
|
$user = @$line['values'][0];
|
||||||
|
$pass = @$line['values'][1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($host)) {
|
||||||
|
throw new IDF_Scm_Exception('usher host is empty');
|
||||||
|
}
|
||||||
|
if (!preg_match('/^\d+$/', $port))
|
||||||
|
{
|
||||||
|
throw new IDF_Scm_Exception('usher port is invalid');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($user)) {
|
||||||
|
throw new IDF_Scm_Exception('usher user is empty');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($pass)) {
|
||||||
|
throw new IDF_Scm_Exception('usher pass is empty');
|
||||||
|
}
|
||||||
|
|
||||||
|
$sock = @fsockopen($host, $port, $errno, $errstr);
|
||||||
|
if (!$sock) {
|
||||||
|
throw new IDF_Scm_Exception(
|
||||||
|
"could not connect to usher: $errstr ($errno)"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fwrite($sock, 'USERPASS '.$user.' '.$pass."\n");
|
||||||
|
if (feof($sock)) {
|
||||||
|
throw new IDF_Scm_Exception(
|
||||||
|
'usher closed the connection - this should not happen'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fwrite($sock, $cmd."\n");
|
||||||
|
$out = '';
|
||||||
|
while (!feof($sock)) {
|
||||||
|
$out .= fgets($sock);
|
||||||
|
}
|
||||||
|
fclose($sock);
|
||||||
|
$out = rtrim($out);
|
||||||
|
|
||||||
|
if ($out == 'unknown command') {
|
||||||
|
throw new IDF_Scm_Exception('unknown command: '.$cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
78
src/IDF/Scm/Monotone/ZipRender.php
Normal file
78
src/IDF/Scm/Monotone/ZipRender.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) 2010 Céondo Ltd and contributors.
|
||||||
|
#
|
||||||
|
# Plume Framework is free software; you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
# the Free Software Foundation; either version 2.1 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Plume Framework 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 Lesser General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Lesser 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 ***** */
|
||||||
|
|
||||||
|
require_once(IDF_PATH.'/../contrib/zipstream-php-0.2.2/zipstream.php');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Special response object to output
|
||||||
|
*
|
||||||
|
* The Content-Length will not be set as it is not possible to predict it.
|
||||||
|
*
|
||||||
|
* Note: The ZipArchive version 0.2.2 has been patched in-tree with this
|
||||||
|
* patch http://pastebin.ca/1977584 to avoid a couple of PHP notices
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class IDF_Scm_Monotone_ZipRender extends Pluf_HTTP_Response
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The revision argument must be a safe string!
|
||||||
|
*
|
||||||
|
* @param Object stdio context
|
||||||
|
* @param string revision
|
||||||
|
* @param string Mimetype (null)
|
||||||
|
*/
|
||||||
|
|
||||||
|
private $stdio = null;
|
||||||
|
private $revision = null;
|
||||||
|
|
||||||
|
function __construct($stdio, $revision)
|
||||||
|
{
|
||||||
|
parent::__construct($revision, 'application/x-zip');
|
||||||
|
$this->stdio = $stdio;
|
||||||
|
$this->revision = $revision;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render a response object.
|
||||||
|
*/
|
||||||
|
function render($output_body=true)
|
||||||
|
{
|
||||||
|
$this->outputHeaders();
|
||||||
|
|
||||||
|
if ($output_body) {
|
||||||
|
$manifest = $this->stdio->exec(array('get_manifest_of', $this->revision));
|
||||||
|
$stanzas = IDF_Scm_Monotone_BasicIO::parse($manifest);
|
||||||
|
|
||||||
|
$zip = new ZipStream();
|
||||||
|
|
||||||
|
foreach ($stanzas as $stanza) {
|
||||||
|
if ($stanza[0]['key'] != 'file')
|
||||||
|
continue;
|
||||||
|
$content = $this->stdio->exec(array('get_file', $stanza[1]['hash']));
|
||||||
|
$zip->add_file($stanza[0]['values'][0], $content);
|
||||||
|
}
|
||||||
|
|
||||||
|
$zip->finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -48,6 +48,25 @@ class IDF_Scm_Svn extends IDF_Scm
|
|||||||
|
|
||||||
public function isAvailable()
|
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;
|
||||||
|
$xmlInfo = self::shell_exec('IDF_Scm_Svn::isAvailable', $cmd);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$xml = simplexml_load_string($xmlInfo);
|
||||||
|
}
|
||||||
|
catch (Exception $e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!isset($xml->entry->commit['revision'])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (0 == (int)$xml->entry->commit['revision']) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,9 +99,10 @@ class IDF_Scm_Svn extends IDF_Scm
|
|||||||
* Returns the URL of the subversion repository.
|
* Returns the URL of the subversion repository.
|
||||||
*
|
*
|
||||||
* @param IDF_Project
|
* @param IDF_Project
|
||||||
|
* @param string
|
||||||
* @return string URL
|
* @return string URL
|
||||||
*/
|
*/
|
||||||
public static function getAnonymousAccessUrl($project)
|
public static function getAnonymousAccessUrl($project,$commit=null)
|
||||||
{
|
{
|
||||||
$conf = $project->getConf();
|
$conf = $project->getConf();
|
||||||
if (false !== ($url=$conf->getVal('svn_remote_url', false))
|
if (false !== ($url=$conf->getVal('svn_remote_url', false))
|
||||||
@@ -97,9 +117,10 @@ class IDF_Scm_Svn extends IDF_Scm
|
|||||||
* Returns the URL of the subversion repository.
|
* Returns the URL of the subversion repository.
|
||||||
*
|
*
|
||||||
* @param IDF_Project
|
* @param IDF_Project
|
||||||
|
* @param string
|
||||||
* @return string URL
|
* @return string URL
|
||||||
*/
|
*/
|
||||||
public static function getAuthAccessUrl($project, $user)
|
public static function getAuthAccessUrl($project, $user, $commit=null)
|
||||||
{
|
{
|
||||||
$conf = $project->getConf();
|
$conf = $project->getConf();
|
||||||
if (false !== ($url=$conf->getVal('svn_remote_url', false))
|
if (false !== ($url=$conf->getVal('svn_remote_url', false))
|
||||||
@@ -136,19 +157,23 @@ class IDF_Scm_Svn extends IDF_Scm
|
|||||||
/**
|
/**
|
||||||
* Subversion revisions are either a number or 'HEAD'.
|
* Subversion revisions are either a number or 'HEAD'.
|
||||||
*/
|
*/
|
||||||
public function isValidRevision($rev)
|
public function validateRevision($rev)
|
||||||
{
|
{
|
||||||
if ($rev == 'HEAD') {
|
if ($rev == 'HEAD') {
|
||||||
return true;
|
return IDF_Scm::REVISION_VALID;
|
||||||
}
|
}
|
||||||
|
|
||||||
$cmd = sprintf(Pluf::f('svn_path', 'svn').' info --username=%s --password=%s %s@%s',
|
$cmd = sprintf(Pluf::f('svn_path', 'svn').' info --username=%s --password=%s %s@%s',
|
||||||
escapeshellarg($this->username),
|
escapeshellarg($this->username),
|
||||||
escapeshellarg($this->password),
|
escapeshellarg($this->password),
|
||||||
escapeshellarg($this->repo),
|
escapeshellarg($this->repo),
|
||||||
escapeshellarg($rev));
|
escapeshellarg($rev));
|
||||||
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
|
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
|
||||||
self::exec('IDF_Scm_Svn::isValidRevision', $cmd, $out, $ret);
|
self::exec('IDF_Scm_Svn::validateRevision', $cmd, $out, $ret);
|
||||||
return (0 == $ret);
|
|
||||||
|
if ($ret == 0)
|
||||||
|
return IDF_Scm::REVISION_VALID;
|
||||||
|
return IDF_Scm::REVISION_INVALID;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -392,7 +417,7 @@ class IDF_Scm_Svn extends IDF_Scm
|
|||||||
*/
|
*/
|
||||||
public function getCommit($commit, $getdiff=false)
|
public function getCommit($commit, $getdiff=false)
|
||||||
{
|
{
|
||||||
if (!$this->isValidRevision($commit)) {
|
if ($this->validateRevision($commit) != IDF_Scm::REVISION_VALID) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
$res = array();
|
$res = array();
|
||||||
@@ -408,8 +433,12 @@ class IDF_Scm_Svn extends IDF_Scm
|
|||||||
$res['date'] = gmdate('Y-m-d H:i:s', strtotime((string) $xml->logentry->date));
|
$res['date'] = gmdate('Y-m-d H:i:s', strtotime((string) $xml->logentry->date));
|
||||||
$res['title'] = (string) $xml->logentry->msg;
|
$res['title'] = (string) $xml->logentry->msg;
|
||||||
$res['commit'] = (string) $xml->logentry['revision'];
|
$res['commit'] = (string) $xml->logentry['revision'];
|
||||||
$res['changes'] = ($getdiff) ? $this->getDiff($commit) : '';
|
$res['parents'] = $xml->logentry['revision'] > 1
|
||||||
|
? array((string) $xml->logentry['revision'] - 1)
|
||||||
|
: array();
|
||||||
|
$res['diff'] = ($getdiff) ? $this->getDiff($commit) : '';
|
||||||
$res['tree'] = '';
|
$res['tree'] = '';
|
||||||
|
$res['branch'] = '';
|
||||||
return (object) $res;
|
return (object) $res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -51,9 +51,13 @@ class IDF_Template_IssueComment extends Pluf_Template_Tag
|
|||||||
array($this, 'callbackReviews'), $text);
|
array($this, 'callbackReviews'), $text);
|
||||||
}
|
}
|
||||||
if ($request->rights['hasSourceAccess']) {
|
if ($request->rights['hasSourceAccess']) {
|
||||||
$text = preg_replace_callback('#(commits?\s+)([0-9a-f]{1,40}(?:(?:\s+and|\s+or|,)\s+[0-9a-f]{1,40})*)\b#i',
|
$verbs = array('added', 'fixed', 'reverted', 'changed', 'removed');
|
||||||
|
$nouns = array('commit', 'commits', 'revision', 'revisions', 'rev', 'revs');
|
||||||
|
$prefix = implode(' in|', $verbs).' in' . '|'.
|
||||||
|
implode('|', $nouns);
|
||||||
|
$text = preg_replace_callback('#((?:'.$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);
|
array($this, 'callbackCommits'), $text);
|
||||||
$text = preg_replace_callback('#(src:)([^\s\(\)\\\\]+(?:(\\\\)\s+[^\s\(\)\\\\]+){0,})+#im',
|
$text = preg_replace_callback('=(src:)([^\s@#,\(\)\\\\]+(?:(\\\\)[\s@#][^\s@#,\(\)\\\\]+){0,})+(?:\@([^\s#,]+))(?:#(\d+))?=im',
|
||||||
array($this, 'callbackSource'), $text);
|
array($this, 'callbackSource'), $text);
|
||||||
}
|
}
|
||||||
if ($wordwrap) $text = Pluf_Text::wrapHtml($text, 69, "\n");
|
if ($wordwrap) $text = Pluf_Text::wrapHtml($text, 69, "\n");
|
||||||
@@ -122,15 +126,12 @@ class IDF_Template_IssueComment extends Pluf_Template_Tag
|
|||||||
function callbackCommits($m)
|
function callbackCommits($m)
|
||||||
{
|
{
|
||||||
$keyword = rtrim($m[1]);
|
$keyword = rtrim($m[1]);
|
||||||
if ('commits' === $keyword) {
|
if (empty($m[3])) {
|
||||||
// Multiple commits like 'commits 6e030e6, a25bfc1 and
|
|
||||||
// 3c094f8'.
|
|
||||||
return $m[1].preg_replace_callback('#\b[0-9a-f]{4,40}\b#i', array($this, 'callbackCommit'), $m[2]);
|
|
||||||
} else if ('commit' === $keyword) {
|
|
||||||
// Single commit like 'commit 6e030e6'.
|
// Single commit like 'commit 6e030e6'.
|
||||||
return $m[1].call_user_func(array($this, 'callbackCommit'), array($m[2]));
|
return $m[1].call_user_func(array($this, 'callbackCommit'), array($m[2]));
|
||||||
}
|
}
|
||||||
return $m[0];
|
// Multiple commits like 'commits 6e030e6, a25bfc1 and 3c094f8'.
|
||||||
|
return $m[1].preg_replace_callback('#\b[0-9a-f]{1,40}\b#i', array($this, 'callbackCommit'), $m[2]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -189,17 +190,38 @@ class IDF_Template_IssueComment extends Pluf_Template_Tag
|
|||||||
|
|
||||||
function callbackSource($m)
|
function callbackSource($m)
|
||||||
{
|
{
|
||||||
if (!$this->scm->isAvailable()) return $m[0];
|
if (!$this->scm->isAvailable())
|
||||||
|
return $m[0];
|
||||||
|
$commit = null;
|
||||||
|
if (!empty($m[4])) {
|
||||||
|
if (!$this->scm->getCommit($m[4])) {
|
||||||
|
return $m[0];
|
||||||
|
}
|
||||||
|
$commit = $m[4];
|
||||||
|
}
|
||||||
$file = $m[2];
|
$file = $m[2];
|
||||||
if (!empty($m[3])) $file = str_replace($m[3], '', $file);
|
if (!empty($m[3]))
|
||||||
$request_file_info = $this->scm->getPathInfo($file);
|
$file = str_replace($m[3], '', $file);
|
||||||
|
$linktext = $file;
|
||||||
|
if (!empty($commit))
|
||||||
|
$linktext .= '@'.$commit;
|
||||||
|
$request_file_info = $this->scm->getPathInfo($file, $commit);
|
||||||
if (!$request_file_info) {
|
if (!$request_file_info) {
|
||||||
return $m[0];
|
return $m[0];
|
||||||
}
|
}
|
||||||
if ($request_file_info->type != 'tree') {
|
if ($request_file_info->type == 'tree') {
|
||||||
return $m[1].'<a href="'.Pluf_HTTP_URL_urlForView('IDF_Views_Source::tree', array($this->project->shortname, $this->scm->getMainBranch(), $file)).'">'.$file.'</a>';
|
return $m[0];
|
||||||
}
|
}
|
||||||
return $m[0];
|
$link = Pluf_HTTP_URL_urlForView('IDF_Views_Source::tree', array(
|
||||||
|
$this->project->shortname,
|
||||||
|
$commit == null ? $this->scm->getMainBranch() : $commit,
|
||||||
|
$file
|
||||||
|
));
|
||||||
|
if (!empty($m[5])) {
|
||||||
|
$link .= '#L'.$m[5];
|
||||||
|
$linktext .= '#'.$m[5];
|
||||||
|
}
|
||||||
|
return $m[1].'<a href="'.$link.'">'.$linktext.'</a>';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -46,25 +46,32 @@ class IDF_Template_Markdown extends Pluf_Template_Tag
|
|||||||
array($this, 'callbackEmbeddedDoc'),
|
array($this, 'callbackEmbeddedDoc'),
|
||||||
$text);
|
$text);
|
||||||
}
|
}
|
||||||
|
// Replace [Page]([[PageName]]) with corresponding link to the page, with link text being Page.
|
||||||
|
$text = preg_replace_callback('#\[([^\]]+)\]\(\[\[([A-Za-z0-9\-]+)\]\]\)#im',
|
||||||
|
array($this, 'callbackWikiPage'),
|
||||||
|
$text);
|
||||||
// Replace [[PageName]] with corresponding link to the page.
|
// Replace [[PageName]] with corresponding link to the page.
|
||||||
$text = preg_replace_callback('#\[\[([A-Za-z0-9\-]+)\]\]#im',
|
$text = preg_replace_callback('#\[\[([A-Za-z0-9\-]+)\]\]#im',
|
||||||
array($this, 'callbackWikiPage'),
|
array($this, 'callbackWikiPageNoName'),
|
||||||
$text);
|
$text);
|
||||||
$filter = new IDF_Template_MarkdownPrefilter();
|
$filter = new IDF_Template_MarkdownPrefilter();
|
||||||
echo $filter->go(Pluf_Text_MarkDown_parse($text));
|
echo $filter->go(Pluf_Text_MarkDown_parse($text));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function callbackWikiPageNoName($m)
|
||||||
|
{
|
||||||
|
$m[2] = $m[1]; //Set the link text to be the same as the page name.
|
||||||
|
return $this->callbackWikiPage($m);
|
||||||
|
}
|
||||||
|
|
||||||
function callbackWikiPage($m)
|
function callbackWikiPage($m)
|
||||||
{
|
{
|
||||||
$sql = new Pluf_SQL('project=%s AND title=%s',
|
$sql = new Pluf_SQL('project=%s AND title=%s',
|
||||||
array($this->project->id, $m[1]));
|
array($this->project->id, $m[2]));
|
||||||
$pages = Pluf::factory('IDF_WikiPage')->getList(array('filter'=>$sql->gen()));
|
$pages = Pluf::factory('IDF_WikiPage')->getList(array('filter'=>$sql->gen()));
|
||||||
if ($pages->count() != 1 and !$this->request->rights['hasWikiAccess']) {
|
|
||||||
return $m[0];
|
|
||||||
}
|
|
||||||
if ($pages->count() != 1 and $this->request->rights['hasWikiAccess']
|
if ($pages->count() != 1 and $this->request->rights['hasWikiAccess']
|
||||||
and !$this->request->user->isAnonymous()) {
|
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[1])).'" 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::create', 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) {
|
if (!$this->request->rights['hasWikiAccess'] or $pages->count() == 0) {
|
||||||
return $m[1];
|
return $m[1];
|
||||||
@@ -75,6 +82,9 @@ class IDF_Template_Markdown extends Pluf_Template_Tag
|
|||||||
function callbackEmbeddedDoc($m)
|
function callbackEmbeddedDoc($m)
|
||||||
{
|
{
|
||||||
$scm = IDF_Scm::get($this->request->project);
|
$scm = IDF_Scm::get($this->request->project);
|
||||||
|
if (!$scm->isAvailable()) {
|
||||||
|
return $m[0];
|
||||||
|
}
|
||||||
$view_source = new IDF_Views_Source();
|
$view_source = new IDF_Views_Source();
|
||||||
$match = array('dummy', $this->request->project->shortname);
|
$match = array('dummy', $this->request->project->shortname);
|
||||||
$match[] = (isset($m[2])) ? $m[2] : $scm->getMainBranch();
|
$match[] = (isset($m[2])) ? $m[2] : $scm->getMainBranch();
|
||||||
@@ -86,7 +96,7 @@ class IDF_Template_Markdown extends Pluf_Template_Tag
|
|||||||
$info = pathinfo($m[1]);
|
$info = pathinfo($m[1]);
|
||||||
$fileinfo = array($res->headers['Content-Type'], $m[1],
|
$fileinfo = array($res->headers['Content-Type'], $m[1],
|
||||||
isset($info['extension']) ? $info['extension'] : 'bin');
|
isset($info['extension']) ? $info['extension'] : 'bin');
|
||||||
if (!IDF_Views_Source::isText($fileinfo)) {
|
if (!IDF_FileUtil::isText($fileinfo)) {
|
||||||
return $m[0];
|
return $m[0];
|
||||||
}
|
}
|
||||||
return $res->content;
|
return $res->content;
|
||||||
|
|||||||
@@ -99,6 +99,7 @@ class IDF_Template_MarkdownPrefilter extends Pluf_Text_HTML_Filter
|
|||||||
'caption' => array(),
|
'caption' => array(),
|
||||||
'code' => array(),
|
'code' => array(),
|
||||||
'dd' => array(),
|
'dd' => array(),
|
||||||
|
'del' => array('cite', 'class', 'datetime', 'dir', 'id', 'title'),
|
||||||
'div' => array('align', 'class'),
|
'div' => array('align', 'class'),
|
||||||
'dl' => array(),
|
'dl' => array(),
|
||||||
'dt' => array(),
|
'dt' => array(),
|
||||||
@@ -112,6 +113,7 @@ class IDF_Template_MarkdownPrefilter extends Pluf_Text_HTML_Filter
|
|||||||
'hr' => array(),
|
'hr' => array(),
|
||||||
'i' => array(),
|
'i' => array(),
|
||||||
'img' => array('src', 'class', 'alt', 'height', 'width', 'style'),
|
'img' => array('src', 'class', 'alt', 'height', 'width', 'style'),
|
||||||
|
'ins' => array('cite', 'class', 'datetime', 'dir', 'id', 'title'),
|
||||||
'li' => array(),
|
'li' => array(),
|
||||||
'ol' => array(),
|
'ol' => array(),
|
||||||
'p' => array('align', 'class'),
|
'p' => array('align', 'class'),
|
||||||
@@ -151,7 +153,9 @@ class IDF_Template_MarkdownPrefilter extends Pluf_Text_HTML_Filter
|
|||||||
'blockquote',
|
'blockquote',
|
||||||
'pre',
|
'pre',
|
||||||
'iframe',
|
'iframe',
|
||||||
'h1', 'h2', 'h3', 'address'
|
'h1', 'h2', 'h3', 'address',
|
||||||
|
'del',
|
||||||
|
'ins',
|
||||||
);
|
);
|
||||||
// attributes which should be checked for valid protocols
|
// attributes which should be checked for valid protocols
|
||||||
public $protocol_attributes = array(
|
public $protocol_attributes = array(
|
||||||
@@ -164,6 +168,7 @@ class IDF_Template_MarkdownPrefilter extends Pluf_Text_HTML_Filter
|
|||||||
'https',
|
'https',
|
||||||
'ftp',
|
'ftp',
|
||||||
'mailto',
|
'mailto',
|
||||||
|
'irc'
|
||||||
);
|
);
|
||||||
// tags which should be removed if they contain no content
|
// tags which should be removed if they contain no content
|
||||||
// (e.g. "<b></b>" or "<b />")
|
// (e.g. "<b></b>" or "<b />")
|
||||||
@@ -174,5 +179,7 @@ class IDF_Template_MarkdownPrefilter extends Pluf_Text_HTML_Filter
|
|||||||
'caption',
|
'caption',
|
||||||
'li',
|
'li',
|
||||||
'span',
|
'span',
|
||||||
|
'del',
|
||||||
|
'ins',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
47
src/IDF/Tests/TestFileUtil.php
Normal file
47
src/IDF/Tests/TestFileUtil.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 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 ***** */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests some of the FileUtils
|
||||||
|
*/
|
||||||
|
class IDF_Tests_TestFileUtil extends UnitTestCase
|
||||||
|
{
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
parent::__construct('Test the file utils.');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetMimeType()
|
||||||
|
{
|
||||||
|
$files = array(
|
||||||
|
'whatever.php' => 'application/x-httpd-php',
|
||||||
|
'whatever.pht' => 'application/x-httpd-php',
|
||||||
|
'README' => 'text/plain',
|
||||||
|
);
|
||||||
|
foreach ($files as $file => $mime) {
|
||||||
|
$m = IDF_Views_Source::getMimeType($file);
|
||||||
|
$this->assertEqual($mime, $m[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
239
src/IDF/Tests/TestMonotone.php
Normal file
239
src/IDF/Tests/TestMonotone.php
Normal file
@@ -0,0 +1,239 @@
|
|||||||
|
<?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) 2010 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 ***** */
|
||||||
|
|
||||||
|
require_once("simpletest/autorun.php");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test the monotone class.
|
||||||
|
*/
|
||||||
|
class IDF_Tests_TestMonotone extends UnitTestCase
|
||||||
|
{
|
||||||
|
private $tmpdir, $dbfile, $mtnInstance;
|
||||||
|
|
||||||
|
private function mtnCall($args, $stdin = null, $dir = null)
|
||||||
|
{
|
||||||
|
// if you have an SSH agent running for key caching,
|
||||||
|
// please disable it
|
||||||
|
$cmdline = array("mtn",
|
||||||
|
"--confdir", $this->tmpdir,
|
||||||
|
"--db", $this->dbfile,
|
||||||
|
"--norc",
|
||||||
|
"--timestamps");
|
||||||
|
|
||||||
|
$cmdline = array_merge($cmdline, $args);
|
||||||
|
|
||||||
|
$descriptorspec = array(
|
||||||
|
0 => array("pipe", "r"),
|
||||||
|
1 => array("pipe", "w"),
|
||||||
|
2 => array("file", "{$this->tmpdir}/mtn-errors", "a")
|
||||||
|
);
|
||||||
|
|
||||||
|
$pipes = array();
|
||||||
|
$dir = !empty($dir) ? $dir : $this->tmpdir;
|
||||||
|
$process = proc_open(implode(" ", $cmdline),
|
||||||
|
$descriptorspec,
|
||||||
|
$pipes,
|
||||||
|
$dir);
|
||||||
|
|
||||||
|
if (!is_resource($process)) {
|
||||||
|
throw new Exception("could not create process");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($stdin)) {
|
||||||
|
fwrite($pipes[0], $stdin);
|
||||||
|
fclose($pipes[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stdout = stream_get_contents($pipes[1]);
|
||||||
|
fclose($pipes[1]);
|
||||||
|
|
||||||
|
$ret = proc_close($process);
|
||||||
|
if ($ret != 0) {
|
||||||
|
throw new Exception(
|
||||||
|
"call ended with a non-zero error code (complete cmdline was: ".
|
||||||
|
implode(" ", $cmdline).")"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $stdout;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
parent::__construct("Test the monotone class.");
|
||||||
|
|
||||||
|
$this->tmpdir = sys_get_temp_dir() . "/mtn-test";
|
||||||
|
$this->dbfile = "{$this->tmpdir}/test.mtn";
|
||||||
|
|
||||||
|
set_include_path(get_include_path() . ":../../../pluf-master/src");
|
||||||
|
require_once("Pluf.php");
|
||||||
|
|
||||||
|
Pluf::start(dirname(__FILE__)."/../conf/idf.php");
|
||||||
|
|
||||||
|
// Pluf::f() mocking
|
||||||
|
$GLOBALS['_PX_config']['mtn_repositories'] = "{$this->tmpdir}/%s.mtn";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function deleteRecursive($dirname)
|
||||||
|
{
|
||||||
|
if (is_dir($dirname))
|
||||||
|
$dir_handle=opendir($dirname);
|
||||||
|
|
||||||
|
while ($file = readdir($dir_handle)) {
|
||||||
|
if ($file!="." && $file!="..") {
|
||||||
|
if (!is_dir($dirname."/".$file)) {
|
||||||
|
unlink ($dirname."/".$file);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
self::deleteRecursive($dirname."/".$file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
closedir($dir_handle);
|
||||||
|
rmdir($dirname);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setUp()
|
||||||
|
{
|
||||||
|
if (is_dir($this->tmpdir)) {
|
||||||
|
self::deleteRecursive($this->tmpdir);
|
||||||
|
}
|
||||||
|
|
||||||
|
mkdir($this->tmpdir);
|
||||||
|
|
||||||
|
$this->mtnCall(array("db", "init"));
|
||||||
|
|
||||||
|
$this->mtnCall(array("genkey", "test@test.de"), "\n\n");
|
||||||
|
|
||||||
|
$workspaceRoot = "{$this->tmpdir}/test-workspace";
|
||||||
|
mkdir($workspaceRoot);
|
||||||
|
|
||||||
|
$this->mtnCall(array("setup", "-b", "testbranch", "."), null, $workspaceRoot);
|
||||||
|
|
||||||
|
file_put_contents("$workspaceRoot/foo", "blubber");
|
||||||
|
$this->mtnCall(array("add", "foo"), null, $workspaceRoot);
|
||||||
|
|
||||||
|
$this->mtnCall(array("commit", "-m", "initial"), null, $workspaceRoot);
|
||||||
|
|
||||||
|
file_put_contents("$workspaceRoot/bar", "blafoo");
|
||||||
|
mkdir("$workspaceRoot/subdir");
|
||||||
|
file_put_contents("$workspaceRoot/subdir/bla", "blabla");
|
||||||
|
$this->mtnCall(array("add", "-R", "--unknown"), null, $workspaceRoot);
|
||||||
|
|
||||||
|
$this->mtnCall(array("commit", "-m", "second"), null, $workspaceRoot);
|
||||||
|
|
||||||
|
$rev = $this->mtnCall(array("au", "get_base_revision_id"), null, $workspaceRoot);
|
||||||
|
$this->mtnCall(array("tag", rtrim($rev), "release-1.0"));
|
||||||
|
|
||||||
|
$project = new IDF_Project();
|
||||||
|
$project->shortname = "test";
|
||||||
|
$this->mtnInstance = new IDF_Scm_Monotone($project);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testIsAvailable()
|
||||||
|
{
|
||||||
|
$this->assertTrue($this->mtnInstance->isAvailable());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetBranches()
|
||||||
|
{
|
||||||
|
$branches = $this->mtnInstance->getBranches();
|
||||||
|
$this->assertEqual(1, count($branches));
|
||||||
|
list($key, $value) = each($branches);
|
||||||
|
$this->assertEqual("h:testbranch", $key);
|
||||||
|
$this->assertEqual("testbranch", $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetTags()
|
||||||
|
{
|
||||||
|
$tags = $this->mtnInstance->getTags();
|
||||||
|
$this->assertEqual(1, count($tags));
|
||||||
|
list($key, $value) = each($tags);
|
||||||
|
$this->assertEqual("t:release-1.0", $key);
|
||||||
|
$this->assertEqual("release-1.0", $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testInBranches()
|
||||||
|
{
|
||||||
|
$revOut = $this->mtnCall(array("au", "select", "b:testbranch"));
|
||||||
|
$revs = preg_split('/\n/', $revOut, -1, PREG_SPLIT_NO_EMPTY);
|
||||||
|
|
||||||
|
$branches = $this->mtnInstance->inBranches($revs[0], null);
|
||||||
|
$this->assertEqual(1, count($branches));
|
||||||
|
$this->assertEqual("h:testbranch", $branches[0]);
|
||||||
|
|
||||||
|
$branches = $this->mtnInstance->inBranches("t:release-1.0", null);
|
||||||
|
$this->assertEqual(1, count($branches));
|
||||||
|
$this->assertEqual("h:testbranch", $branches[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testInTags()
|
||||||
|
{
|
||||||
|
$rev = $this->mtnCall(array("au", "select", "t:release-1.0"));
|
||||||
|
$tags = $this->mtnInstance->inTags(rtrim($rev), null);
|
||||||
|
$this->assertEqual(1, count($tags));
|
||||||
|
$this->assertEqual("t:release-1.0", $tags[0]);
|
||||||
|
|
||||||
|
// pick the first (root) revisions in this database
|
||||||
|
$rev = $this->mtnCall(array("au", "roots"));
|
||||||
|
$tags = $this->mtnInstance->inTags(rtrim($rev), null);
|
||||||
|
$this->assertEqual(0, count($tags));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetTree()
|
||||||
|
{
|
||||||
|
$files = $this->mtnInstance->getTree("t:release-1.0");
|
||||||
|
$this->assertEqual(3, count($files));
|
||||||
|
|
||||||
|
$this->assertEqual("bar", $files[0]->file);
|
||||||
|
$this->assertEqual("blob", $files[0]->type);
|
||||||
|
$this->assertEqual(6, $files[0]->size); // "blafoo"
|
||||||
|
$this->assertEqual("second\n", $files[0]->log);
|
||||||
|
|
||||||
|
$this->assertEqual("foo", $files[1]->file);
|
||||||
|
$this->assertEqual("blob", $files[1]->type);
|
||||||
|
$this->assertEqual(7, $files[1]->size); // "blubber"
|
||||||
|
$this->assertEqual("initial\n", $files[1]->log);
|
||||||
|
|
||||||
|
$this->assertEqual("subdir", $files[2]->file);
|
||||||
|
$this->assertEqual("tree", $files[2]->type);
|
||||||
|
$this->assertEqual(0, $files[2]->size);
|
||||||
|
|
||||||
|
$files = $this->mtnInstance->getTree("t:release-1.0", "subdir");
|
||||||
|
$this->assertEqual(1, count($files));
|
||||||
|
|
||||||
|
$this->assertEqual("bla", $files[0]->file);
|
||||||
|
$this->assertEqual("subdir/bla", $files[0]->fullpath);
|
||||||
|
$this->assertEqual("blob", $files[0]->type);
|
||||||
|
$this->assertEqual(6, $files[0]->size); // "blabla"
|
||||||
|
$this->assertEqual("second\n", $files[0]->log);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testIsValidRevision()
|
||||||
|
{
|
||||||
|
$this->assertTrue($this->mtnInstance->isValidRevision("t:release-1.0"));
|
||||||
|
$this->assertFalse($this->mtnInstance->isValidRevision("abcdef12345"));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -32,19 +32,6 @@ class IDF_Tests_TestSource extends UnitTestCase
|
|||||||
parent::__construct('Test the source class.');
|
parent::__construct('Test the source class.');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetMimeType()
|
|
||||||
{
|
|
||||||
$files = array(
|
|
||||||
'whatever.php' => 'application/x-httpd-php',
|
|
||||||
'whatever.pht' => 'application/x-httpd-php',
|
|
||||||
'README' => 'text/plain',
|
|
||||||
);
|
|
||||||
foreach ($files as $file => $mime) {
|
|
||||||
$m = IDF_Views_Source::getMimeType($file);
|
|
||||||
$this->assertEqual($mime, $m[0]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testRegexCommit()
|
public function testRegexCommit()
|
||||||
{
|
{
|
||||||
$regex = '#^/p/([\-\w]+)/source/tree/([^\/]+)/(.*)$#';
|
$regex = '#^/p/([\-\w]+)/source/tree/([^\/]+)/(.*)$#';
|
||||||
|
|||||||
@@ -237,14 +237,16 @@ class IDF_Upload extends Pluf_Model
|
|||||||
));
|
));
|
||||||
$tmpl = new Pluf_Template('idf/downloads/download-created-email.txt');
|
$tmpl = new Pluf_Template('idf/downloads/download-created-email.txt');
|
||||||
$text_email = $tmpl->render($context);
|
$text_email = $tmpl->render($context);
|
||||||
$email = new Pluf_Mail(Pluf::f('from_email'),
|
$addresses = explode(',', $conf->getVal('downloads_notification_email'));
|
||||||
$conf->getVal('downloads_notification_email'),
|
foreach ($addresses as $address) {
|
||||||
sprintf(__('New download - %s (%s)'),
|
$email = new Pluf_Mail(Pluf::f('from_email'),
|
||||||
$this->summary,
|
$address,
|
||||||
$this->get_project()->shortname));
|
sprintf(__('New download - %s (%s)'),
|
||||||
$email->addTextMessage($text_email);
|
$this->summary,
|
||||||
$email->sendMail();
|
$this->get_project()->shortname));
|
||||||
|
$email->addTextMessage($text_email);
|
||||||
|
$email->sendMail();
|
||||||
|
}
|
||||||
Pluf_Translation::loadSetLocale($current_locale);
|
Pluf_Translation::loadSetLocale($current_locale);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
59
src/IDF/UserData.php
Normal file
59
src/IDF/UserData.php
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
<?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 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 ***** */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thin wrapper around the general purpose Gconf data driver
|
||||||
|
* to model a userdata object as key value store
|
||||||
|
*/
|
||||||
|
class IDF_UserData extends IDF_Gconf
|
||||||
|
{
|
||||||
|
/** columns for the underlying model for which we do not want to
|
||||||
|
override __get and __set */
|
||||||
|
private static $protectedVars =
|
||||||
|
array('id', 'model_class', 'model_id', 'vkey', 'vdesc');
|
||||||
|
|
||||||
|
function __set($key, $value)
|
||||||
|
{
|
||||||
|
if (in_array($key, self::$protectedVars))
|
||||||
|
{
|
||||||
|
parent::__set($key, $value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$this->setVal($key, $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function __get($key)
|
||||||
|
{
|
||||||
|
if (in_array($key, self::$protectedVars))
|
||||||
|
return parent::__get($key);
|
||||||
|
return $this->getVal($key, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function factory($user)
|
||||||
|
{
|
||||||
|
$conf = new IDF_UserData();
|
||||||
|
$conf->setModel((object) array('_model'=>'IDF_UserData', 'id' => $user->id));
|
||||||
|
$conf->initCache();
|
||||||
|
return $conf;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -86,7 +86,10 @@ class IDF_Views
|
|||||||
$title = __('Create Your Account');
|
$title = __('Create Your Account');
|
||||||
$params = array('request'=>$request);
|
$params = array('request'=>$request);
|
||||||
if ($request->method == 'POST') {
|
if ($request->method == 'POST') {
|
||||||
$form = new IDF_Form_Register($request->POST, $params);
|
$form = new IDF_Form_Register(array_merge(
|
||||||
|
(array)$request->POST,
|
||||||
|
(array)$request->FILES
|
||||||
|
), $params);
|
||||||
if ($form->isValid()) {
|
if ($form->isValid()) {
|
||||||
$user = $form->save(); // It is sending the confirmation email
|
$user = $form->save(); // It is sending the confirmation email
|
||||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views::registerInputKey');
|
$url = Pluf_HTTP_URL_urlForView('IDF_Views::registerInputKey');
|
||||||
@@ -314,10 +317,10 @@ class IDF_Views
|
|||||||
if ($user->isAnonymous()) {
|
if ($user->isAnonymous()) {
|
||||||
$sql = sprintf('%s=%s', $db->qn('private'), $false);
|
$sql = sprintf('%s=%s', $db->qn('private'), $false);
|
||||||
return Pluf::factory('IDF_Project')->getList(array('filter'=> $sql,
|
return Pluf::factory('IDF_Project')->getList(array('filter'=> $sql,
|
||||||
'order' => 'shortname ASC'));
|
'order' => 'name ASC'));
|
||||||
}
|
}
|
||||||
if ($user->administrator) {
|
if ($user->administrator) {
|
||||||
return Pluf::factory('IDF_Project')->getList(array('order' => 'shortname ASC'));
|
return Pluf::factory('IDF_Project')->getList(array('order' => 'name ASC'));
|
||||||
}
|
}
|
||||||
// grab the list of projects where the user is admin, member
|
// grab the list of projects where the user is admin, member
|
||||||
// or authorized
|
// or authorized
|
||||||
@@ -338,6 +341,6 @@ class IDF_Views
|
|||||||
$sql .= sprintf(' OR id IN (%s)', implode(', ', $ids));
|
$sql .= sprintf(' OR id IN (%s)', implode(', ', $ids));
|
||||||
}
|
}
|
||||||
return Pluf::factory('IDF_Project')->getList(array('filter' => $sql,
|
return Pluf::factory('IDF_Project')->getList(array('filter' => $sql,
|
||||||
'order' => 'shortname ASC'));
|
'order' => 'name ASC'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -118,6 +118,9 @@ class IDF_Views_Admin
|
|||||||
/**
|
/**
|
||||||
* Creation of a project.
|
* Creation of a project.
|
||||||
*
|
*
|
||||||
|
* A project can use another project as template. In that case,
|
||||||
|
* everything but the data in the input at creation time is
|
||||||
|
* reused, including the wiki pages.
|
||||||
*/
|
*/
|
||||||
public $projectCreate_precond = array('Pluf_Precondition::staffRequired');
|
public $projectCreate_precond = array('Pluf_Precondition::staffRequired');
|
||||||
public function projectCreate($request, $match)
|
public function projectCreate($request, $match)
|
||||||
@@ -265,7 +268,9 @@ class IDF_Views_Admin
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($request->method == 'POST') {
|
if ($request->method == 'POST') {
|
||||||
$form = new IDF_Form_Admin_UserUpdate($request->POST, $params);
|
$form = new IDF_Form_Admin_UserUpdate(array_merge($request->POST,
|
||||||
|
$request->FILES),
|
||||||
|
$params);
|
||||||
if ($form->isValid()) {
|
if ($form->isValid()) {
|
||||||
$form->save();
|
$form->save();
|
||||||
$request->user->setMessage(__('The user has been updated.'));
|
$request->user->setMessage(__('The user has been updated.'));
|
||||||
@@ -296,7 +301,9 @@ class IDF_Views_Admin
|
|||||||
'request' => $request,
|
'request' => $request,
|
||||||
);
|
);
|
||||||
if ($request->method == 'POST') {
|
if ($request->method == 'POST') {
|
||||||
$form = new IDF_Form_Admin_UserCreate($request->POST, $params);
|
$form = new IDF_Form_Admin_UserCreate(array_merge($request->POST,
|
||||||
|
$request->FILES),
|
||||||
|
$params);
|
||||||
if ($form->isValid()) {
|
if ($form->isValid()) {
|
||||||
$cuser = $form->save();
|
$cuser = $form->save();
|
||||||
$request->user->setMessage(sprintf(__('The user %s has been created.'), (string) $cuser));
|
$request->user->setMessage(sprintf(__('The user %s has been created.'), (string) $cuser));
|
||||||
@@ -314,6 +321,148 @@ class IDF_Views_Admin
|
|||||||
),
|
),
|
||||||
$request);
|
$request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Usher servers overview
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public $usher_precond = array('Pluf_Precondition::staffRequired');
|
||||||
|
public function usher($request, $match)
|
||||||
|
{
|
||||||
|
$title = __('Usher management');
|
||||||
|
$servers = array();
|
||||||
|
foreach (IDF_Scm_Monotone_Usher::getServerList() as $server) {
|
||||||
|
$servers[] = (object)array(
|
||||||
|
"name" => $server,
|
||||||
|
"status" => IDF_Scm_Monotone_Usher::getStatus($server),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Pluf_Shortcuts_RenderToResponse(
|
||||||
|
'idf/gadmin/usher/index.html',
|
||||||
|
array(
|
||||||
|
'page_title' => $title,
|
||||||
|
'servers' => $servers,
|
||||||
|
),
|
||||||
|
$request
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Usher control
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public $usherControl_precond = array('Pluf_Precondition::staffRequired');
|
||||||
|
public function usherControl($request, $match)
|
||||||
|
{
|
||||||
|
$title = __('Usher control');
|
||||||
|
$action = $match[1];
|
||||||
|
|
||||||
|
if (!empty($action)) {
|
||||||
|
if (!in_array($action, array('reload', 'shutdown', 'startup'))) {
|
||||||
|
throw new Pluf_HTTP_Error404();
|
||||||
|
}
|
||||||
|
|
||||||
|
$msg = null;
|
||||||
|
if ($action == 'reload') {
|
||||||
|
IDF_Scm_Monotone_Usher::reload();
|
||||||
|
$msg = __('Usher configuration has been reloaded');
|
||||||
|
}
|
||||||
|
else if ($action == 'shutdown') {
|
||||||
|
IDF_Scm_Monotone_Usher::shutDown();
|
||||||
|
$msg = __('Usher has been shut down');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
IDF_Scm_Monotone_Usher::startUp();
|
||||||
|
$msg = __('Usher has been started up');
|
||||||
|
}
|
||||||
|
|
||||||
|
$request->user->setMessage($msg);
|
||||||
|
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Admin::usherControl', array(''));
|
||||||
|
return new Pluf_HTTP_Response_Redirect($url);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Pluf_Shortcuts_RenderToResponse(
|
||||||
|
'idf/gadmin/usher/control.html',
|
||||||
|
array(
|
||||||
|
'page_title' => $title,
|
||||||
|
'status' => IDF_Scm_Monotone_Usher::getStatus(),
|
||||||
|
),
|
||||||
|
$request
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Usher control
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public $usherServerControl_precond = array('Pluf_Precondition::staffRequired');
|
||||||
|
public function usherServerControl($request, $match)
|
||||||
|
{
|
||||||
|
$server = $match[1];
|
||||||
|
if (!in_array($server, IDF_Scm_Monotone_Usher::getServerList())) {
|
||||||
|
throw new Pluf_HTTP_Error404();
|
||||||
|
}
|
||||||
|
|
||||||
|
$action = $match[2];
|
||||||
|
if (!in_array($action, array('start', 'stop', 'kill'))) {
|
||||||
|
throw new Pluf_HTTP_Error404();
|
||||||
|
}
|
||||||
|
|
||||||
|
$msg = null;
|
||||||
|
if ($action == 'start') {
|
||||||
|
IDF_Scm_Monotone_Usher::startServer($server);
|
||||||
|
$msg = sprintf(__('The server "%s" has been started'), $server);
|
||||||
|
}
|
||||||
|
else if ($action == 'stop') {
|
||||||
|
IDF_Scm_Monotone_Usher::stopServer($server);
|
||||||
|
$msg = sprintf(__('The server "%s" has been stopped'), $server);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
IDF_Scm_Monotone_Usher::killServer($server);
|
||||||
|
$msg = sprintf(__('The server "%s" has been killed'), $server);
|
||||||
|
}
|
||||||
|
|
||||||
|
$request->user->setMessage($msg);
|
||||||
|
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Admin::usher');
|
||||||
|
return new Pluf_HTTP_Response_Redirect($url);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open connections for a configured server
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public $usherServerConnections_precond = array('Pluf_Precondition::staffRequired');
|
||||||
|
public function usherServerConnections($request, $match)
|
||||||
|
{
|
||||||
|
$server = $match[1];
|
||||||
|
if (!in_array($server, IDF_Scm_Monotone_Usher::getServerList())) {
|
||||||
|
throw new Pluf_HTTP_Error404();
|
||||||
|
}
|
||||||
|
|
||||||
|
$title = sprintf(__('Open connections for "%s"'), $server);
|
||||||
|
|
||||||
|
$connections = IDF_Scm_Monotone_Usher::getConnectionList($server);
|
||||||
|
if (count($connections) == 0) {
|
||||||
|
$request->user->setMessage(sprintf(
|
||||||
|
__('no connections for server "%s"'), $server
|
||||||
|
));
|
||||||
|
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Admin::usher');
|
||||||
|
return new Pluf_HTTP_Response_Redirect($url);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Pluf_Shortcuts_RenderToResponse(
|
||||||
|
'idf/gadmin/usher/connections.html',
|
||||||
|
array(
|
||||||
|
'page_title' => $title,
|
||||||
|
'server' => $server,
|
||||||
|
'connections' => $connections,
|
||||||
|
),
|
||||||
|
$request
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function IDF_Views_Admin_bool($field, $item)
|
function IDF_Views_Admin_bool($field, $item)
|
||||||
@@ -344,25 +493,51 @@ function IDF_Views_Admin_projectSize($field, $project)
|
|||||||
*
|
*
|
||||||
* @return array Associative array with the size of each element
|
* @return array Associative array with the size of each element
|
||||||
*/
|
*/
|
||||||
function IDF_Views_Admin_getForgeSize()
|
function IDF_Views_Admin_getForgeSize($force=false)
|
||||||
{
|
{
|
||||||
|
$conf = new IDF_Gconf();
|
||||||
|
$conf->setModel((object) array('_model'=>'IDF_Forge', 'id'=> 1));
|
||||||
$res = array();
|
$res = array();
|
||||||
$res['repositories'] = 0;
|
$res['repositories'] = 0;
|
||||||
foreach (Pluf::factory('IDF_Project')->getList() as $prj) {
|
foreach (Pluf::factory('IDF_Project')->getList() as $prj) {
|
||||||
$size = $prj->getRepositorySize();
|
$size = $prj->getRepositorySize($force);
|
||||||
if ($size != -1) {
|
if ($size != -1) {
|
||||||
$res['repositories'] += $size;
|
$res['repositories'] += $size;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$cmd = Pluf::f('idf_exec_cmd_prefix', '').'du -sk '
|
$last_eval = $conf->getVal('downloads_size_check_date', 0);
|
||||||
.escapeshellarg(Pluf::f('upload_path'));
|
if (Pluf::f('idf_no_size_check', false) or
|
||||||
$out = explode(' ', shell_exec($cmd), 2);
|
(!$force and $last_eval > time()-172800)) {
|
||||||
$res['downloads'] = $out[0]*1024;
|
$res['downloads'] = $conf->getVal('downloads_size', 0);
|
||||||
$cmd = Pluf::f('idf_exec_cmd_prefix', '').'du -sk '
|
} else {
|
||||||
.escapeshellarg(Pluf::f('upload_issue_path'));
|
$conf->setVal('downloads_size_check_date', time());
|
||||||
$out = explode(' ', shell_exec($cmd), 2);
|
$cmd = Pluf::f('idf_exec_cmd_prefix', '').'du -sk '
|
||||||
$res['attachments'] = $out[0]*1024;
|
.escapeshellarg(Pluf::f('upload_path'));
|
||||||
$res['database'] = IDF_Views_Admin_getForgeDbSize();
|
$out = explode(' ', shell_exec($cmd), 2);
|
||||||
|
$res['downloads'] = $out[0]*1024;
|
||||||
|
$conf->setVal('downloads_size', $res['downloads']);
|
||||||
|
}
|
||||||
|
$last_eval = $conf->getVal('attachments_size_check_date', 0);
|
||||||
|
if (Pluf::f('idf_no_size_check', false) or
|
||||||
|
(!$force and $last_eval > time()-172800)) {
|
||||||
|
$res['attachments'] = $conf->getVal('attachments_size', 0);
|
||||||
|
} else {
|
||||||
|
$conf->setVal('attachments_size_check_date', time());
|
||||||
|
$cmd = Pluf::f('idf_exec_cmd_prefix', '').'du -sk '
|
||||||
|
.escapeshellarg(Pluf::f('upload_path'));
|
||||||
|
$out = explode(' ', shell_exec($cmd), 2);
|
||||||
|
$res['attachments'] = $out[0]*1024;
|
||||||
|
$conf->setVal('attachments_size', $res['attachments']);
|
||||||
|
}
|
||||||
|
$last_eval = $conf->getVal('database_size_check_date', 0);
|
||||||
|
if (Pluf::f('idf_no_size_check', false) or
|
||||||
|
(!$force and $last_eval > time()-172800)) {
|
||||||
|
$res['database'] = $conf->getVal('database_size', 0);
|
||||||
|
} else {
|
||||||
|
$conf->setVal('database_size_check_date', time());
|
||||||
|
$res['database'] = IDF_Views_Admin_getForgeDbSize();
|
||||||
|
$conf->setVal('database_size', $res['database']);
|
||||||
|
}
|
||||||
$res['total'] = $res['repositories'] + $res['downloads'] + $res['attachments'] + $res['database'];
|
$res['total'] = $res['repositories'] + $res['downloads'] + $res['attachments'] + $res['database'];
|
||||||
return $res;
|
return $res;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -152,6 +152,29 @@ class IDF_Views_Download
|
|||||||
if ($request->method == 'POST') {
|
if ($request->method == 'POST') {
|
||||||
$fname = $upload->file;
|
$fname = $upload->file;
|
||||||
@unlink(Pluf::f('upload_path').'/'.$prj->shortname.'/files/'.$fname);
|
@unlink(Pluf::f('upload_path').'/'.$prj->shortname.'/files/'.$fname);
|
||||||
|
/**
|
||||||
|
* [signal]
|
||||||
|
*
|
||||||
|
* IDF_Upload::delete
|
||||||
|
*
|
||||||
|
* [sender]
|
||||||
|
*
|
||||||
|
* IDF_Form_UpdateUpload
|
||||||
|
*
|
||||||
|
* [description]
|
||||||
|
*
|
||||||
|
* This signal allows an application to perform a set of tasks
|
||||||
|
* just before the deletion of the corresponding object in the
|
||||||
|
* database but just after the deletion from the storage.
|
||||||
|
*
|
||||||
|
* [parameters]
|
||||||
|
*
|
||||||
|
* array('upload' => $upload);
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
$params = array('upload' => $upload);
|
||||||
|
Pluf_Signal::send('IDF_Upload::delete',
|
||||||
|
'IDF_Views_Download', $params);
|
||||||
$upload->delete();
|
$upload->delete();
|
||||||
$request->user->setMessage(__('The file has been deleted.'));
|
$request->user->setMessage(__('The file has been deleted.'));
|
||||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Download::index',
|
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Download::index',
|
||||||
|
|||||||
@@ -78,6 +78,164 @@ class IDF_Views_Issue
|
|||||||
$params, $request);
|
$params, $request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View the issues watch list of a given user.
|
||||||
|
* Limited to a specified project
|
||||||
|
*/
|
||||||
|
public $watchList_precond = array('IDF_Precondition::accessIssues',
|
||||||
|
'Pluf_Precondition::loginRequired');
|
||||||
|
public function watchList($request, $match)
|
||||||
|
{
|
||||||
|
$prj = $request->project;
|
||||||
|
$otags = $prj->getTagIdsByStatus('open');
|
||||||
|
$ctags = $prj->getTagIdsByStatus('closed');
|
||||||
|
if (count($otags) == 0) $otags[] = 0;
|
||||||
|
if (count($ctags) == 0) $ctags[] = 0;
|
||||||
|
|
||||||
|
// Get the id list of issue in the user watch list (for all projects !)
|
||||||
|
$db =& Pluf::db();
|
||||||
|
$sql_results = $db->select('SELECT idf_issue_id as id FROM '.Pluf::f('db_table_prefix', '').'idf_issue_pluf_user_assoc WHERE pluf_user_id='.$request->user->id);
|
||||||
|
$issue_ids = array(0);
|
||||||
|
foreach ($sql_results as $id) {
|
||||||
|
$issue_ids[] = $id['id'];
|
||||||
|
}
|
||||||
|
$issue_ids = implode (',', $issue_ids);
|
||||||
|
|
||||||
|
// Count open and close issues
|
||||||
|
$sql = new Pluf_SQL('project=%s AND id IN ('.$issue_ids.') AND status IN ('.implode(', ', $otags).')', array($prj->id));
|
||||||
|
$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()));
|
||||||
|
|
||||||
|
// Generate a filter for the paginator
|
||||||
|
switch ($match[2]) {
|
||||||
|
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);
|
||||||
|
$f_sql = new Pluf_SQL('project=%s AND id IN ('.$issue_ids.') AND status IN ('.implode(', ', $ctags).')', array($prj->id));
|
||||||
|
break;
|
||||||
|
case 'open':
|
||||||
|
default:
|
||||||
|
$title = sprintf(__('Watch List: Open Issues for %s'), (string) $prj);
|
||||||
|
$summary = __('This table shows the open 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).')', array($prj->id));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 = $summary;
|
||||||
|
$pag->forced_where = $f_sql;
|
||||||
|
$pag->action = array('IDF_Views_Issue::watchList', array($prj->shortname, $match[1]));
|
||||||
|
$pag->sort_order = array('modif_dtime', 'ASC'); // will be reverted
|
||||||
|
$pag->sort_reverse_order = array('modif_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('modif_dtime', 'Pluf_Paginator_DateAgo', __('Last Updated')),
|
||||||
|
);
|
||||||
|
$pag->configure($list_display, array(), array('id', 'status', 'modif_dtime'));
|
||||||
|
$pag->items_per_page = 10;
|
||||||
|
$pag->no_results_text = __('No issues were found.');
|
||||||
|
$pag->setFromRequest($request);
|
||||||
|
return Pluf_Shortcuts_RenderToResponse('idf/issues/project-watchlist.html',
|
||||||
|
array('project' => $prj,
|
||||||
|
'page_title' => $title,
|
||||||
|
'open' => $nb_open,
|
||||||
|
'closed' => $nb_closed,
|
||||||
|
'issues' => $pag,
|
||||||
|
),
|
||||||
|
$request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View the issues watch list of a given user.
|
||||||
|
* For all projects
|
||||||
|
*/
|
||||||
|
public $forgeWatchList_precond = array('Pluf_Precondition::loginRequired');
|
||||||
|
public function forgeWatchList($request, $match)
|
||||||
|
{
|
||||||
|
$otags = array();
|
||||||
|
$ctags = array();
|
||||||
|
// Note that this approach does not scale, we will need to add
|
||||||
|
// a table to cache the meaning of the tags for large forges.
|
||||||
|
foreach (IDF_Views::getProjects($request->user) as $project) {
|
||||||
|
$otags = array_merge($otags, $project->getTagIdsByStatus('open'));
|
||||||
|
}
|
||||||
|
foreach (IDF_Views::getProjects($request->user) as $project) {
|
||||||
|
$ctags = array_merge($ctags, $project->getTagIdsByStatus('closed'));
|
||||||
|
}
|
||||||
|
if (count($otags) == 0) $otags[] = 0;
|
||||||
|
if (count($ctags) == 0) $ctags[] = 0;
|
||||||
|
|
||||||
|
// Get the id list of issue in the user watch list (for all projects !)
|
||||||
|
$db =& Pluf::db();
|
||||||
|
$sql_results = $db->select('SELECT idf_issue_id as id FROM '.Pluf::f('db_table_prefix', '').'idf_issue_pluf_user_assoc WHERE pluf_user_id='.$request->user->id);
|
||||||
|
$issue_ids = array(0);
|
||||||
|
foreach ($sql_results as $id) {
|
||||||
|
$issue_ids[] = $id['id'];
|
||||||
|
}
|
||||||
|
$issue_ids = implode (',', $issue_ids);
|
||||||
|
|
||||||
|
// Count open and close issues
|
||||||
|
$sql = new Pluf_SQL('id IN ('.$issue_ids.') AND status IN ('.implode(', ', $otags).')', array());
|
||||||
|
$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()));
|
||||||
|
|
||||||
|
// Generate a filter for the paginator
|
||||||
|
switch ($match[1]) {
|
||||||
|
case 'closed':
|
||||||
|
$title = sprintf(__('Watch List: Closed Issues'));
|
||||||
|
$summary = __('This table shows the closed issues in your watch list.');
|
||||||
|
$f_sql = new Pluf_SQL('id IN ('.$issue_ids.') AND status IN ('.implode(', ', $ctags).')', array());
|
||||||
|
break;
|
||||||
|
case 'open':
|
||||||
|
default:
|
||||||
|
$title = sprintf(__('Watch List: Open Issues'));
|
||||||
|
$summary = __('This table shows the open issues in your watch list.');
|
||||||
|
$f_sql = new Pluf_SQL('id IN ('.$issue_ids.') AND status IN ('.implode(', ', $otags).')', array());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Paginator to paginate the issues
|
||||||
|
$pag = new Pluf_Paginator(new IDF_Issue());
|
||||||
|
$pag->class = 'recent-issues';
|
||||||
|
$pag->item_extra_props = array('current_user' => $request->user);
|
||||||
|
$pag->summary = $summary;
|
||||||
|
$pag->forced_where = $f_sql;
|
||||||
|
$pag->action = array('IDF_Views_Issue::forgeWatchList', array($match[1]));
|
||||||
|
$pag->sort_order = array('modif_dtime', 'ASC'); // will be reverted
|
||||||
|
$pag->sort_reverse_order = array('modif_dtime');
|
||||||
|
$pag->sort_link_title = true;
|
||||||
|
$pag->extra_classes = array('a-c', '', 'a-c', 'a-c', 'a-c');
|
||||||
|
$list_display = array(
|
||||||
|
'id' => __('Id'),
|
||||||
|
array('summary', 'IDF_Views_Issue_SummaryAndLabelsUnknownProject', __('Summary')),
|
||||||
|
array('project', 'Pluf_Paginator_FkToString', __('Project')),
|
||||||
|
array('status', 'IDF_Views_Issue_ShowStatus', __('Status')),
|
||||||
|
array('modif_dtime', 'Pluf_Paginator_DateAgo', __('Last Updated')),
|
||||||
|
);
|
||||||
|
$pag->configure($list_display, array(), array('id', 'project', 'status', 'modif_dtime'));
|
||||||
|
$pag->items_per_page = 10;
|
||||||
|
$pag->no_results_text = __('No issues were found.');
|
||||||
|
$pag->setFromRequest($request);
|
||||||
|
return Pluf_Shortcuts_RenderToResponse('idf/issues/forge-watchlist.html',
|
||||||
|
array('page_title' => $title,
|
||||||
|
'open' => $nb_open,
|
||||||
|
'closed' => $nb_closed,
|
||||||
|
'issues' => $pag,
|
||||||
|
),
|
||||||
|
$request);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* View the issues of a given user.
|
* View the issues of a given user.
|
||||||
*
|
*
|
||||||
@@ -279,6 +437,27 @@ class IDF_Views_Issue
|
|||||||
$form = new IDF_Form_IssueUpdate(null, $params);
|
$form = new IDF_Form_IssueUpdate(null, $params);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Search previous and next issue id
|
||||||
|
$octags = $prj->getTagIdsByStatus(($closed) ? 'closed' : 'open');
|
||||||
|
if (count($octags) == 0) $octags[] = 0;
|
||||||
|
$sql_previous = new Pluf_SQL('project=%s AND status IN ('.implode(', ', $octags).') AND id<%s',
|
||||||
|
array($prj->id, $match[2])
|
||||||
|
);
|
||||||
|
$sql_next = new Pluf_SQL('project=%s AND status IN ('.implode(', ', $octags).') AND id>%s',
|
||||||
|
array($prj->id, $match[2])
|
||||||
|
);
|
||||||
|
$previous_issue = Pluf::factory('IDF_Issue')->getList(array('filter' => $sql_previous->gen(),
|
||||||
|
'order' => 'id DESC',
|
||||||
|
'nb' => 1
|
||||||
|
));
|
||||||
|
$next_issue = Pluf::factory('IDF_Issue')->getList(array('filter' => $sql_next->gen(),
|
||||||
|
'order' => 'id ASC',
|
||||||
|
'nb' => 1
|
||||||
|
));
|
||||||
|
$previous_issue_id = (isset($previous_issue[0])) ? $previous_issue[0]->id : 0;
|
||||||
|
$next_issue_id = (isset($next_issue[0])) ? $next_issue[0]->id : 0;
|
||||||
|
|
||||||
$arrays = self::autoCompleteArrays($prj);
|
$arrays = self::autoCompleteArrays($prj);
|
||||||
return Pluf_Shortcuts_RenderToResponse('idf/issues/view.html',
|
return Pluf_Shortcuts_RenderToResponse('idf/issues/view.html',
|
||||||
array_merge(
|
array_merge(
|
||||||
@@ -290,7 +469,9 @@ class IDF_Views_Issue
|
|||||||
'page_title' => $title,
|
'page_title' => $title,
|
||||||
'closed' => $closed,
|
'closed' => $closed,
|
||||||
'preview' => $preview,
|
'preview' => $preview,
|
||||||
'interested' =>$interested->count(),
|
'interested' => $interested->count(),
|
||||||
|
'previous_issue_id' => $previous_issue_id,
|
||||||
|
'next_issue_id' => $next_issue_id
|
||||||
),
|
),
|
||||||
$arrays),
|
$arrays),
|
||||||
$request);
|
$request);
|
||||||
@@ -306,7 +487,7 @@ class IDF_Views_Issue
|
|||||||
$prj = $request->project;
|
$prj = $request->project;
|
||||||
$attach = Pluf_Shortcuts_GetObjectOr404('IDF_IssueFile', $match[2]);
|
$attach = Pluf_Shortcuts_GetObjectOr404('IDF_IssueFile', $match[2]);
|
||||||
$prj->inOr404($attach->get_comment()->get_issue());
|
$prj->inOr404($attach->get_comment()->get_issue());
|
||||||
$info = IDF_Views_Source::getMimeType($attach->filename);
|
$info = IDF_FileUtil::getMimeType($attach->filename);
|
||||||
$mime = 'application/octet-stream';
|
$mime = 'application/octet-stream';
|
||||||
if (strpos($info[0], 'image/') === 0) {
|
if (strpos($info[0], 'image/') === 0) {
|
||||||
$mime = $info[0];
|
$mime = $info[0];
|
||||||
@@ -330,14 +511,14 @@ class IDF_Views_Issue
|
|||||||
$prj->inOr404($attach->get_comment()->get_issue());
|
$prj->inOr404($attach->get_comment()->get_issue());
|
||||||
// If one cannot see the attachement, redirect to the
|
// If one cannot see the attachement, redirect to the
|
||||||
// getAttachment view.
|
// getAttachment view.
|
||||||
$info = IDF_Views_Source::getMimeType($attach->filename);
|
$info = IDF_FileUtil::getMimeType($attach->filename);
|
||||||
if (!IDF_Views_Source::isText($info)) {
|
if (!IDF_FileUtil::isText($info)) {
|
||||||
return $this->getAttachment($request, $match);
|
return $this->getAttachment($request, $match);
|
||||||
}
|
}
|
||||||
// Now we want to look at the file but with links back to the
|
// Now we want to look at the file but with links back to the
|
||||||
// issue.
|
// issue.
|
||||||
$file = IDF_Views_Source::highLight($info,
|
$file = IDF_FileUtil::highLight($info,
|
||||||
file_get_contents(Pluf::f('upload_issue_path').'/'.$attach->attachment));
|
file_get_contents(Pluf::f('upload_issue_path').'/'.$attach->attachment));
|
||||||
$title = sprintf(__('View %s'), $attach->filename);
|
$title = sprintf(__('View %s'), $attach->filename);
|
||||||
return Pluf_Shortcuts_RenderToResponse('idf/issues/attachment.html',
|
return Pluf_Shortcuts_RenderToResponse('idf/issues/attachment.html',
|
||||||
array(
|
array(
|
||||||
@@ -539,6 +720,17 @@ class IDF_Views_Issue
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When you access to your forge watch list, issue don't known
|
||||||
|
* the project shortname.
|
||||||
|
*/
|
||||||
|
function IDF_Views_Issue_SummaryAndLabelsUnknownProject($field, $issue, $extra='')
|
||||||
|
{
|
||||||
|
$shortname = $issue->get_project()->shortname;
|
||||||
|
$issue->__set('shortname', $shortname);
|
||||||
|
return IDF_Views_Issue_SummaryAndLabels ($field, $issue, $extra);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display the summary of an issue, then on a new line, display the
|
* 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".
|
* list of labels with a link to a view "by label only".
|
||||||
@@ -575,3 +767,5 @@ function IDF_Views_Issue_ShowStatus($field, $issue, $extra='')
|
|||||||
{
|
{
|
||||||
return Pluf_esc($issue->get_status()->name);
|
return Pluf_esc($issue->get_status()->name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -61,6 +61,66 @@ class IDF_Views_Project
|
|||||||
$request);
|
$request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an associative array with available model filters
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private static function getAvailableModelFilters()
|
||||||
|
{
|
||||||
|
return array(
|
||||||
|
'all' => __('All Updates'),
|
||||||
|
'commits' => __('Commits'),
|
||||||
|
'issues' => __('Issues and Comments'),
|
||||||
|
'downloads' => __('Downloads'),
|
||||||
|
'documents' => __('Documents'),
|
||||||
|
'reviews' => __('Reviews and Patches'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array of model classes for which the current user
|
||||||
|
* has rights and which should be used according to his filter
|
||||||
|
*
|
||||||
|
* @param object $request
|
||||||
|
* @param string $model_filter
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private static function determineModelClasses($request, $model_filter = 'all')
|
||||||
|
{
|
||||||
|
$classes = array();
|
||||||
|
if (true === IDF_Precondition::accessSource($request) &&
|
||||||
|
($model_filter == 'all' || $model_filter == 'commits')) {
|
||||||
|
$classes[] = '\'IDF_Commit\'';
|
||||||
|
// FIXME: this looks like a hack...
|
||||||
|
IDF_Scm::syncTimeline($request->project);
|
||||||
|
}
|
||||||
|
if (true === IDF_Precondition::accessIssues($request) &&
|
||||||
|
($model_filter == 'all' || $model_filter == 'issues')) {
|
||||||
|
$classes[] = '\'IDF_Issue\'';
|
||||||
|
$classes[] = '\'IDF_IssueComment\'';
|
||||||
|
}
|
||||||
|
if (true === IDF_Precondition::accessDownloads($request) &&
|
||||||
|
($model_filter == 'all' || $model_filter == 'downloads')) {
|
||||||
|
$classes[] = '\'IDF_Upload\'';
|
||||||
|
}
|
||||||
|
if (true === IDF_Precondition::accessWiki($request) &&
|
||||||
|
($model_filter == 'all' || $model_filter == 'documents')) {
|
||||||
|
$classes[] = '\'IDF_WikiPage\'';
|
||||||
|
$classes[] = '\'IDF_WikiRevision\'';
|
||||||
|
}
|
||||||
|
if (true === IDF_Precondition::accessReview($request) &&
|
||||||
|
($model_filter == 'all' || $model_filter == 'reviews')) {
|
||||||
|
$classes[] = '\'IDF_Review_Comment\'';
|
||||||
|
$classes[] = '\'IDF_Review_Patch\'';
|
||||||
|
}
|
||||||
|
if (count($classes) == 0) {
|
||||||
|
$classes[] = '\'IDF_Dummy\'';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $classes;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Timeline of the project.
|
* Timeline of the project.
|
||||||
*/
|
*/
|
||||||
@@ -68,43 +128,26 @@ class IDF_Views_Project
|
|||||||
public function timeline($request, $match)
|
public function timeline($request, $match)
|
||||||
{
|
{
|
||||||
$prj = $request->project;
|
$prj = $request->project;
|
||||||
$title = sprintf(__('%s Updates'), (string) $prj);
|
|
||||||
$team = $prj->getMembershipData();
|
$model_filter = @$match[2];
|
||||||
|
$all_model_filters = self::getAvailableModelFilters();
|
||||||
|
if (!array_key_exists($model_filter, $all_model_filters)) {
|
||||||
|
$model_filter = 'all';
|
||||||
|
}
|
||||||
|
$title = (string)$prj . ' ' . $all_model_filters[$model_filter];
|
||||||
|
|
||||||
$pag = new IDF_Timeline_Paginator(new IDF_Timeline());
|
$pag = new IDF_Timeline_Paginator(new IDF_Timeline());
|
||||||
$pag->class = 'recent-issues';
|
$pag->class = 'recent-issues';
|
||||||
$pag->item_extra_props = array('request' => $request);
|
$pag->item_extra_props = array('request' => $request);
|
||||||
$pag->summary = __('This table shows the project updates.');
|
$pag->summary = __('This table shows the project updates.');
|
||||||
// Need to check the rights
|
|
||||||
$rights = array();
|
$classes = self::determineModelClasses($request, $model_filter);
|
||||||
if (true === IDF_Precondition::accessSource($request)) {
|
$sql = sprintf('model_class IN (%s)', implode(', ', $classes));
|
||||||
$rights[] = '\'IDF_Commit\'';
|
|
||||||
IDF_Scm::syncTimeline($request->project);
|
|
||||||
}
|
|
||||||
if (true === IDF_Precondition::accessIssues($request)) {
|
|
||||||
$rights[] = '\'IDF_Issue\'';
|
|
||||||
$rights[] = '\'IDF_IssueComment\'';
|
|
||||||
}
|
|
||||||
if (true === IDF_Precondition::accessDownloads($request)) {
|
|
||||||
$rights[] = '\'IDF_Upload\'';
|
|
||||||
}
|
|
||||||
if (true === IDF_Precondition::accessWiki($request)) {
|
|
||||||
$rights[] = '\'IDF_WikiPage\'';
|
|
||||||
$rights[] = '\'IDF_WikiRevision\'';
|
|
||||||
}
|
|
||||||
if (true === IDF_Precondition::accessReview($request)) {
|
|
||||||
$rights[] = '\'IDF_Review_Comment\'';
|
|
||||||
$rights[] = '\'IDF_Review_Patch\'';
|
|
||||||
}
|
|
||||||
if (count($rights) == 0) {
|
|
||||||
$rights[] = '\'IDF_Dummy\'';
|
|
||||||
}
|
|
||||||
$sql = sprintf('model_class IN (%s)', implode(', ', $rights));
|
|
||||||
$pag->forced_where = new Pluf_SQL('project=%s AND '.$sql,
|
$pag->forced_where = new Pluf_SQL('project=%s AND '.$sql,
|
||||||
array($prj->id));
|
array($prj->id));
|
||||||
$pag->sort_order = array('creation_dtime', 'ASC');
|
$pag->sort_order = array('creation_dtime', 'ASC');
|
||||||
$pag->sort_reverse_order = array('creation_dtime');
|
$pag->sort_reverse_order = array('creation_dtime');
|
||||||
$pag->action = array('IDF_Views_Project::timeline', array($prj->shortname));
|
$pag->action = array('IDF_Views_Project::timeline', array($prj->shortname, $model_filter));
|
||||||
$list_display = array(
|
$list_display = array(
|
||||||
'creation_dtime' => __('Age'),
|
'creation_dtime' => __('Age'),
|
||||||
'id' => __('Change'),
|
'id' => __('Change'),
|
||||||
@@ -113,32 +156,23 @@ class IDF_Views_Project
|
|||||||
$pag->items_per_page = 20;
|
$pag->items_per_page = 20;
|
||||||
$pag->no_results_text = __('No changes were found.');
|
$pag->no_results_text = __('No changes were found.');
|
||||||
$pag->setFromRequest($request);
|
$pag->setFromRequest($request);
|
||||||
$downloads = array();
|
|
||||||
if ($request->rights['hasDownloadsAccess']) {
|
|
||||||
$tags = IDF_Views_Download::getDownloadTags($prj);
|
|
||||||
// the first tag is the featured, the last is the deprecated.
|
|
||||||
$downloads = $tags[0]->get_idf_upload_list();
|
|
||||||
}
|
|
||||||
$pages = array();
|
|
||||||
if ($request->rights['hasWikiAccess']) {
|
|
||||||
$tags = IDF_Views_Wiki::getWikiTags($prj);
|
|
||||||
$pages = $tags[0]->get_idf_wikipage_list();
|
|
||||||
}
|
|
||||||
if (!$request->user->isAnonymous() and $prj->isRestricted()) {
|
if (!$request->user->isAnonymous() and $prj->isRestricted()) {
|
||||||
$feedurl = Pluf_HTTP_URL_urlForView('idf_project_timeline_feed_auth',
|
$feedurl = Pluf_HTTP_URL_urlForView('idf_project_timeline_feed_auth',
|
||||||
array($prj->shortname,
|
array($prj->shortname,
|
||||||
|
$model_filter,
|
||||||
IDF_Precondition::genFeedToken($prj, $request->user)));
|
IDF_Precondition::genFeedToken($prj, $request->user)));
|
||||||
} else {
|
} else {
|
||||||
$feedurl = Pluf_HTTP_URL_urlForView('idf_project_timeline_feed',
|
$feedurl = Pluf_HTTP_URL_urlForView('idf_project_timeline_feed',
|
||||||
array($prj->shortname));
|
array($prj->shortname, $model_filter));
|
||||||
}
|
}
|
||||||
return Pluf_Shortcuts_RenderToResponse('idf/project/timeline.html',
|
return Pluf_Shortcuts_RenderToResponse('idf/project/timeline.html',
|
||||||
array(
|
array(
|
||||||
'page_title' => $title,
|
'page_title' => $title,
|
||||||
'feedurl' => $feedurl,
|
'feedurl' => $feedurl,
|
||||||
'timeline' => $pag,
|
'timeline' => $pag,
|
||||||
'team' => $team,
|
'model_filter' => $model_filter,
|
||||||
'downloads' => $downloads,
|
'all_model_filters' => $all_model_filters,
|
||||||
),
|
),
|
||||||
$request);
|
$request);
|
||||||
|
|
||||||
@@ -156,31 +190,17 @@ class IDF_Views_Project
|
|||||||
public function timelineFeed($request, $match)
|
public function timelineFeed($request, $match)
|
||||||
{
|
{
|
||||||
$prj = $request->project;
|
$prj = $request->project;
|
||||||
// Need to check the rights
|
$model_filter = @$match[2];
|
||||||
$rights = array();
|
|
||||||
if (true === IDF_Precondition::accessSource($request)) {
|
$model_filter = @$match[2];
|
||||||
$rights[] = '\'IDF_Commit\'';
|
$all_model_filters = self::getAvailableModelFilters();
|
||||||
IDF_Scm::syncTimeline($request->project);
|
if (!array_key_exists($model_filter, $all_model_filters)) {
|
||||||
|
$model_filter = 'all';
|
||||||
}
|
}
|
||||||
if (true === IDF_Precondition::accessIssues($request)) {
|
$title = $all_model_filters[$model_filter];
|
||||||
$rights[] = '\'IDF_Issue\'';
|
|
||||||
$rights[] = '\'IDF_IssueComment\'';
|
$classes = self::determineModelClasses($request, $model_filter);
|
||||||
}
|
$sqls = sprintf('model_class IN (%s)', implode(', ', $classes));
|
||||||
if (true === IDF_Precondition::accessDownloads($request)) {
|
|
||||||
$rights[] = '\'IDF_Upload\'';
|
|
||||||
}
|
|
||||||
if (true === IDF_Precondition::accessWiki($request)) {
|
|
||||||
$rights[] = '\'IDF_WikiPage\'';
|
|
||||||
$rights[] = '\'IDF_WikiRevision\'';
|
|
||||||
}
|
|
||||||
if (true === IDF_Precondition::accessReview($request)) {
|
|
||||||
$rights[] = '\'IDF_Review_Comment\'';
|
|
||||||
$rights[] = '\'IDF_Review_Patch\'';
|
|
||||||
}
|
|
||||||
if (count($rights) == 0) {
|
|
||||||
$rights[] = '\'IDF_Dummy\'';
|
|
||||||
}
|
|
||||||
$sqls = sprintf('model_class IN (%s)', implode(', ', $rights));
|
|
||||||
$sql = new Pluf_SQL('project=%s AND '.$sqls, array($prj->id));
|
$sql = new Pluf_SQL('project=%s AND '.$sqls, array($prj->id));
|
||||||
$params = array(
|
$params = array(
|
||||||
'filter' => $sql->gen(),
|
'filter' => $sql->gen(),
|
||||||
@@ -203,7 +223,6 @@ class IDF_Views_Project
|
|||||||
}
|
}
|
||||||
$out = Pluf_Template::markSafe(implode("\n", $out));
|
$out = Pluf_Template::markSafe(implode("\n", $out));
|
||||||
$tmpl = new Pluf_Template('idf/index.atom');
|
$tmpl = new Pluf_Template('idf/index.atom');
|
||||||
$title = __('Updates');
|
|
||||||
$feedurl = Pluf::f('url_base').Pluf::f('idf_base').$request->query;
|
$feedurl = Pluf::f('url_base').Pluf::f('idf_base').$request->query;
|
||||||
$viewurl = Pluf_HTTP_URL_urlForView('IDF_Views_Project::timeline',
|
$viewurl = Pluf_HTTP_URL_urlForView('IDF_Views_Project::timeline',
|
||||||
array($prj->shortname));
|
array($prj->shortname));
|
||||||
@@ -277,7 +296,8 @@ class IDF_Views_Project
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$params = array();
|
$params = array();
|
||||||
$keys = array('labels_issue_open', 'labels_issue_closed',
|
$keys = array('labels_issue_template',
|
||||||
|
'labels_issue_open', 'labels_issue_closed',
|
||||||
'labels_issue_predefined', 'labels_issue_one_max');
|
'labels_issue_predefined', 'labels_issue_one_max');
|
||||||
foreach ($keys as $key) {
|
foreach ($keys as $key) {
|
||||||
$_val = $conf->getVal($key, false);
|
$_val = $conf->getVal($key, false);
|
||||||
@@ -475,50 +495,52 @@ class IDF_Views_Project
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Administrate the source control.
|
* Administrate the source control.
|
||||||
|
*
|
||||||
|
* There, the login/password of the subversion remote repo can be
|
||||||
|
* change together with the webhook url.
|
||||||
*/
|
*/
|
||||||
public $adminSource_precond = array('IDF_Precondition::projectOwner');
|
public $adminSource_precond = array('IDF_Precondition::projectOwner');
|
||||||
public function adminSource($request, $match)
|
public function adminSource($request, $match)
|
||||||
{
|
{
|
||||||
$prj = $request->project;
|
$prj = $request->project;
|
||||||
$title = sprintf(__('%s Source'), (string) $prj);
|
$title = sprintf(__('%s Source'), (string) $prj);
|
||||||
$form = null;
|
|
||||||
$remote_svn = false;
|
$remote_svn = ($request->conf->getVal('scm') == 'svn' and
|
||||||
if ($request->conf->getVal('scm') == 'svn' and
|
strlen($request->conf->getVal('svn_remote_url')) > 0);
|
||||||
strlen($request->conf->getVal('svn_remote_url')) > 0) {
|
$extra = array(
|
||||||
$remote_svn = true;
|
'conf' => $request->conf,
|
||||||
$extra = array(
|
'remote_svn' => $remote_svn,
|
||||||
'conf' => $request->conf,
|
);
|
||||||
);
|
if ($request->method == 'POST') {
|
||||||
if ($request->method == 'POST') {
|
$form = new IDF_Form_SourceConf($request->POST, $extra);
|
||||||
$form = new IDF_Form_SourceConf($request->POST, $extra);
|
if ($form->isValid()) {
|
||||||
if ($form->isValid()) {
|
foreach ($form->cleaned_data as $key=>$val) {
|
||||||
foreach ($form->cleaned_data as $key=>$val) {
|
$request->conf->setVal($key, $val);
|
||||||
$request->conf->setVal($key, $val);
|
|
||||||
}
|
|
||||||
$request->user->setMessage(__('The project source configuration has been saved.'));
|
|
||||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Project::adminSource',
|
|
||||||
array($prj->shortname));
|
|
||||||
return new Pluf_HTTP_Response_Redirect($url);
|
|
||||||
}
|
}
|
||||||
} else {
|
$request->user->setMessage(__('The project source configuration has been saved.'));
|
||||||
$params = array();
|
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Project::adminSource',
|
||||||
foreach (array('svn_username', 'svn_password') as $key) {
|
array($prj->shortname));
|
||||||
$_val = $request->conf->getVal($key, false);
|
return new Pluf_HTTP_Response_Redirect($url);
|
||||||
if ($_val !== false) {
|
|
||||||
$params[$key] = $_val;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (count($params) == 0) {
|
|
||||||
$params = null; //Nothing in the db, so new form.
|
|
||||||
}
|
|
||||||
$form = new IDF_Form_SourceConf($params, $extra);
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
$params = array();
|
||||||
|
foreach (array('svn_username', 'svn_password', 'webhook_url') as $key) {
|
||||||
|
$_val = $request->conf->getVal($key, false);
|
||||||
|
if ($_val !== false) {
|
||||||
|
$params[$key] = $_val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (count($params) == 0) {
|
||||||
|
$params = null; //Nothing in the db, so new form.
|
||||||
|
}
|
||||||
|
$form = new IDF_Form_SourceConf($params, $extra);
|
||||||
}
|
}
|
||||||
$scm = $request->conf->getVal('scm', 'git');
|
$scm = $request->conf->getVal('scm', 'git');
|
||||||
$options = array(
|
$options = array(
|
||||||
'git' => __('git'),
|
'git' => __('git'),
|
||||||
'svn' => __('Subversion'),
|
'svn' => __('Subversion'),
|
||||||
'mercurial' => __('mercurial'),
|
'mercurial' => __('mercurial'),
|
||||||
|
'mtn' => __('monotone'),
|
||||||
);
|
);
|
||||||
$repository_type = $options[$scm];
|
$repository_type = $options[$scm];
|
||||||
return Pluf_Shortcuts_RenderToResponse('idf/admin/source.html',
|
return Pluf_Shortcuts_RenderToResponse('idf/admin/source.html',
|
||||||
@@ -529,6 +551,7 @@ class IDF_Views_Project
|
|||||||
'repository_size' => $prj->getRepositorySize(),
|
'repository_size' => $prj->getRepositorySize(),
|
||||||
'page_title' => $title,
|
'page_title' => $title,
|
||||||
'form' => $form,
|
'form' => $form,
|
||||||
|
'hookkey' => $prj->getPostCommitHookKey(),
|
||||||
),
|
),
|
||||||
$request);
|
$request);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -192,6 +192,7 @@ class IDF_Views_Review
|
|||||||
$files[$filename] = array('', $form->f->{md5($filename)}, $cts);
|
$files[$filename] = array('', $form->f->{md5($filename)}, $cts);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$reviewers = Pluf_Model_RemoveDuplicates($reviewers);
|
$reviewers = Pluf_Model_RemoveDuplicates($reviewers);
|
||||||
return Pluf_Shortcuts_RenderToResponse('idf/review/view.html',
|
return Pluf_Shortcuts_RenderToResponse('idf/review/view.html',
|
||||||
array_merge(
|
array_merge(
|
||||||
|
|||||||
@@ -31,16 +31,6 @@ Pluf::loadFunction('Pluf_Shortcuts_GetFormForModel');
|
|||||||
*/
|
*/
|
||||||
class IDF_Views_Source
|
class IDF_Views_Source
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Extension supported by the syntax highlighter.
|
|
||||||
*/
|
|
||||||
public static $supportedExtenstions = array(
|
|
||||||
'ascx', 'ashx', 'asmx', 'aspx', 'browser', 'bsh', 'c', 'cc',
|
|
||||||
'config', 'cpp', 'cs', 'csh', 'csproj', 'css', 'cv', 'cyc',
|
|
||||||
'html', 'html', 'java', 'js', 'master', 'perl', 'php', 'pl',
|
|
||||||
'pm', 'py', 'rb', 'sh', 'sitemap', 'skin', 'sln', 'svc', 'vala',
|
|
||||||
'vb', 'vbproj', 'wsdl', 'xhtml', 'xml', 'xsd', 'xsl', 'xslt');
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display help on how to checkout etc.
|
* Display help on how to checkout etc.
|
||||||
*/
|
*/
|
||||||
@@ -59,30 +49,56 @@ class IDF_Views_Source
|
|||||||
$params, $request);
|
$params, $request);
|
||||||
}
|
}
|
||||||
|
|
||||||
public $changeLog_precond = array('IDF_Precondition::accessSource');
|
/**
|
||||||
|
* Is displayed in case an invalid revision is requested
|
||||||
|
*/
|
||||||
|
public $invalidRevision_precond = array('IDF_Precondition::accessSource');
|
||||||
|
public function invalidRevision($request, $match)
|
||||||
|
{
|
||||||
|
$title = sprintf(__('%s Invalid Revision'), (string) $request->project);
|
||||||
|
$commit = $match[2];
|
||||||
|
$params = array(
|
||||||
|
'page_title' => $title,
|
||||||
|
'title' => $title,
|
||||||
|
'commit' => $commit,
|
||||||
|
);
|
||||||
|
return Pluf_Shortcuts_RenderToResponse('idf/source/invalid_revision.html',
|
||||||
|
$params, $request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is displayed in case a revision identifier cannot be uniquely resolved
|
||||||
|
* to one single revision
|
||||||
|
*/
|
||||||
|
public $disambiguateRevision_precond = array('IDF_Precondition::accessSource',
|
||||||
|
'IDF_Views_Source_Precondition::scmAvailable');
|
||||||
|
public function disambiguateRevision($request, $match)
|
||||||
|
{
|
||||||
|
$title = sprintf(__('%s Ambiguous Revision'), (string) $request->project);
|
||||||
|
$commit = $match[2];
|
||||||
|
$redirect = $match[3];
|
||||||
|
$scm = IDF_Scm::get($request->project);
|
||||||
|
$revisions = $scm->disambiguateRevision($commit);
|
||||||
|
$params = array(
|
||||||
|
'page_title' => $title,
|
||||||
|
'title' => $title,
|
||||||
|
'commit' => $commit,
|
||||||
|
'revisions' => $revisions,
|
||||||
|
'redirect' => $redirect,
|
||||||
|
);
|
||||||
|
return Pluf_Shortcuts_RenderToResponse('idf/source/disambiguate_revision.html',
|
||||||
|
$params, $request);
|
||||||
|
}
|
||||||
|
|
||||||
|
public $changeLog_precond = array('IDF_Precondition::accessSource',
|
||||||
|
'IDF_Views_Source_Precondition::scmAvailable',
|
||||||
|
'IDF_Views_Source_Precondition::revisionValid');
|
||||||
public function changeLog($request, $match)
|
public function changeLog($request, $match)
|
||||||
{
|
{
|
||||||
$scm = IDF_Scm::get($request->project);
|
$scm = IDF_Scm::get($request->project);
|
||||||
if (!$scm->isAvailable()) {
|
|
||||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Source::help',
|
|
||||||
array($request->project->shortname));
|
|
||||||
return new Pluf_HTTP_Response_Redirect($url);
|
|
||||||
}
|
|
||||||
$branches = $scm->getBranches();
|
$branches = $scm->getBranches();
|
||||||
$commit = $match[2];
|
$commit = $match[2];
|
||||||
if (!$scm->isValidRevision($commit)) {
|
|
||||||
if (count($branches) == 0) {
|
|
||||||
// Redirect to the project source help
|
|
||||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Source::help',
|
|
||||||
array($request->project->shortname));
|
|
||||||
return new Pluf_HTTP_Response_Redirect($url);
|
|
||||||
}
|
|
||||||
// Redirect to the first branch
|
|
||||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Source::changeLog',
|
|
||||||
array($request->project->shortname,
|
|
||||||
$scm->getMainBranch()));
|
|
||||||
return new Pluf_HTTP_Response_Redirect($url);
|
|
||||||
}
|
|
||||||
$title = sprintf(__('%1$s %2$s Change Log'), (string) $request->project,
|
$title = sprintf(__('%1$s %2$s Change Log'), (string) $request->project,
|
||||||
$this->getScmType($request));
|
$this->getScmType($request));
|
||||||
$changes = $scm->getChangeLog($commit, 25);
|
$changes = $scm->getChangeLog($commit, 25);
|
||||||
@@ -111,22 +127,17 @@ class IDF_Views_Source
|
|||||||
$request);
|
$request);
|
||||||
}
|
}
|
||||||
|
|
||||||
public $treeBase_precond = array('IDF_Precondition::accessSource');
|
public $treeBase_precond = array('IDF_Precondition::accessSource',
|
||||||
|
'IDF_Views_Source_Precondition::scmAvailable',
|
||||||
|
'IDF_Views_Source_Precondition::revisionValid');
|
||||||
public function treeBase($request, $match)
|
public function treeBase($request, $match)
|
||||||
{
|
{
|
||||||
$scm = IDF_Scm::get($request->project);
|
$scm = IDF_Scm::get($request->project);
|
||||||
if (!$scm->isAvailable()) {
|
|
||||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Source::help',
|
|
||||||
array($request->project->shortname));
|
|
||||||
return new Pluf_HTTP_Response_Redirect($url);
|
|
||||||
}
|
|
||||||
$commit = $match[2];
|
$commit = $match[2];
|
||||||
|
|
||||||
$cobject = $scm->getCommit($commit);
|
$cobject = $scm->getCommit($commit);
|
||||||
if (!$cobject) {
|
if (!$cobject) {
|
||||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Source::treeBase',
|
throw new Exception('could not retrieve commit object for '. $commit);
|
||||||
array($request->project->shortname,
|
|
||||||
$scm->getMainBranch()));
|
|
||||||
return new Pluf_HTTP_Response_Redirect($url);
|
|
||||||
}
|
}
|
||||||
$title = sprintf(__('%1$s %2$s Source Tree'),
|
$title = sprintf(__('%1$s %2$s Source Tree'),
|
||||||
$request->project, $this->getScmType($request));
|
$request->project, $this->getScmType($request));
|
||||||
@@ -143,6 +154,7 @@ class IDF_Views_Source
|
|||||||
}
|
}
|
||||||
$scmConf = $request->conf->getVal('scm', 'git');
|
$scmConf = $request->conf->getVal('scm', 'git');
|
||||||
$props = $scm->getProperties($commit);
|
$props = $scm->getProperties($commit);
|
||||||
|
$res->uasort(array('IDF_Views_Source', 'treeSort'));
|
||||||
return Pluf_Shortcuts_RenderToResponse('idf/source/'.$scmConf.'/tree.html',
|
return Pluf_Shortcuts_RenderToResponse('idf/source/'.$scmConf.'/tree.html',
|
||||||
array(
|
array(
|
||||||
'page_title' => $title,
|
'page_title' => $title,
|
||||||
@@ -159,20 +171,14 @@ class IDF_Views_Source
|
|||||||
$request);
|
$request);
|
||||||
}
|
}
|
||||||
|
|
||||||
public $tree_precond = array('IDF_Precondition::accessSource');
|
public $tree_precond = array('IDF_Precondition::accessSource',
|
||||||
|
'IDF_Views_Source_Precondition::scmAvailable',
|
||||||
|
'IDF_Views_Source_Precondition::revisionValid');
|
||||||
public function tree($request, $match)
|
public function tree($request, $match)
|
||||||
{
|
{
|
||||||
$scm = IDF_Scm::get($request->project);
|
$scm = IDF_Scm::get($request->project);
|
||||||
$commit = $match[2];
|
$commit = $match[2];
|
||||||
|
|
||||||
if (!$scm->isAvailable()) {
|
|
||||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Source::help',
|
|
||||||
array($request->project->shortname));
|
|
||||||
return new Pluf_HTTP_Response_Redirect($url);
|
|
||||||
}
|
|
||||||
$fburl = Pluf_HTTP_URL_urlForView('IDF_Views_Source::treeBase',
|
|
||||||
array($request->project->shortname,
|
|
||||||
$scm->getMainBranch()));
|
|
||||||
$request_file = $match[3];
|
$request_file = $match[3];
|
||||||
if (substr($request_file, -1) == '/') {
|
if (substr($request_file, -1) == '/') {
|
||||||
$request_file = substr($request_file, 0, -1);
|
$request_file = substr($request_file, 0, -1);
|
||||||
@@ -181,13 +187,13 @@ class IDF_Views_Source
|
|||||||
$request_file));
|
$request_file));
|
||||||
return new Pluf_HTTP_Response_Redirect($url, 301);
|
return new Pluf_HTTP_Response_Redirect($url, 301);
|
||||||
}
|
}
|
||||||
if (!$scm->isValidRevision($commit, $request_file)) {
|
|
||||||
// Redirect to the first branch
|
|
||||||
return new Pluf_HTTP_Response_Redirect($fburl);
|
|
||||||
}
|
|
||||||
$request_file_info = $scm->getPathInfo($request_file, $commit);
|
$request_file_info = $scm->getPathInfo($request_file, $commit);
|
||||||
if (!$request_file_info) {
|
if (!$request_file_info) {
|
||||||
// Redirect to the first branch
|
// Redirect to the main branch
|
||||||
|
$fburl = Pluf_HTTP_URL_urlForView('IDF_Views_Source::treeBase',
|
||||||
|
array($request->project->shortname,
|
||||||
|
$scm->getMainBranch()));
|
||||||
return new Pluf_HTTP_Response_Redirect($fburl);
|
return new Pluf_HTTP_Response_Redirect($fburl);
|
||||||
}
|
}
|
||||||
$branches = $scm->getBranches();
|
$branches = $scm->getBranches();
|
||||||
@@ -195,7 +201,7 @@ class IDF_Views_Source
|
|||||||
if ($request_file_info->type != 'tree') {
|
if ($request_file_info->type != 'tree') {
|
||||||
$info = self::getRequestedFileMimeType($request_file_info,
|
$info = self::getRequestedFileMimeType($request_file_info,
|
||||||
$commit, $scm);
|
$commit, $scm);
|
||||||
if (!self::isText($info)) {
|
if (!IDF_FileUtil::isText($info)) {
|
||||||
$rep = new Pluf_HTTP_Response($scm->getFile($request_file_info),
|
$rep = new Pluf_HTTP_Response($scm->getFile($request_file_info),
|
||||||
$info[0]);
|
$info[0]);
|
||||||
$rep->headers['Content-Disposition'] = 'attachment; filename="'.$info[1].'"';
|
$rep->headers['Content-Disposition'] = 'attachment; filename="'.$info[1].'"';
|
||||||
@@ -241,6 +247,7 @@ class IDF_Views_Source
|
|||||||
$previous = substr($request_file, 0, -strlen($l.' '));
|
$previous = substr($request_file, 0, -strlen($l.' '));
|
||||||
$scmConf = $request->conf->getVal('scm', 'git');
|
$scmConf = $request->conf->getVal('scm', 'git');
|
||||||
$props = $scm->getProperties($commit, $request_file);
|
$props = $scm->getProperties($commit, $request_file);
|
||||||
|
$res->uasort(array('IDF_Views_Source', 'treeSort'));
|
||||||
return Pluf_Shortcuts_RenderToResponse('idf/source/'.$scmConf.'/tree.html',
|
return Pluf_Shortcuts_RenderToResponse('idf/source/'.$scmConf.'/tree.html',
|
||||||
array(
|
array(
|
||||||
'page_title' => $page_title,
|
'page_title' => $page_title,
|
||||||
@@ -277,44 +284,42 @@ class IDF_Views_Source
|
|||||||
return '<span class="breadcrumb">'.implode('<span class="sep">'.$sep.'</span>', $out).'</span>';
|
return '<span class="breadcrumb">'.implode('<span class="sep">'.$sep.'</span>', $out).'</span>';
|
||||||
}
|
}
|
||||||
|
|
||||||
public $commit_precond = array('IDF_Precondition::accessSource');
|
public $commit_precond = array('IDF_Precondition::accessSource',
|
||||||
|
'IDF_Views_Source_Precondition::scmAvailable',
|
||||||
|
'IDF_Views_Source_Precondition::revisionValid');
|
||||||
public function commit($request, $match)
|
public function commit($request, $match)
|
||||||
{
|
{
|
||||||
$scm = IDF_Scm::get($request->project);
|
$scm = IDF_Scm::get($request->project);
|
||||||
$commit = $match[2];
|
$commit = $match[2];
|
||||||
if (!$scm->isValidRevision($commit)) {
|
|
||||||
// Redirect to the first branch
|
|
||||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Source::treeBase',
|
|
||||||
array($request->project->shortname,
|
|
||||||
$scm->getMainBranch()));
|
|
||||||
return new Pluf_HTTP_Response_Redirect($url);
|
|
||||||
}
|
|
||||||
$large = $scm->isCommitLarge($commit);
|
$large = $scm->isCommitLarge($commit);
|
||||||
$cobject = $scm->getCommit($commit, !$large);
|
$cobject = $scm->getCommit($commit, !$large);
|
||||||
if (!$cobject) {
|
if (!$cobject) {
|
||||||
// Redirect to the first branch
|
throw new Exception('could not retrieve commit object for '. $commit);
|
||||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Source::treeBase',
|
|
||||||
array($request->project->shortname,
|
|
||||||
$scm->getMainBranch()));
|
|
||||||
return new Pluf_HTTP_Response_Redirect($url);
|
|
||||||
}
|
}
|
||||||
$title = sprintf(__('%s Commit Details'), (string) $request->project);
|
$title = sprintf(__('%s Commit Details'), (string) $request->project);
|
||||||
$page_title = sprintf(__('%s Commit Details - %s'), (string) $request->project, $commit);
|
$page_title = sprintf(__('%s Commit Details - %s'), (string) $request->project, $commit);
|
||||||
$rcommit = IDF_Commit::getOrAdd($cobject, $request->project);
|
$rcommit = IDF_Commit::getOrAdd($cobject, $request->project);
|
||||||
$diff = new IDF_Diff($cobject->changes);
|
$diff = new IDF_Diff($cobject->diff);
|
||||||
$diff->parse();
|
$diff->parse();
|
||||||
$scmConf = $request->conf->getVal('scm', 'git');
|
$scmConf = $request->conf->getVal('scm', 'git');
|
||||||
|
try {
|
||||||
|
$changes = $scm->getChanges($commit);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
// getChanges is not yes supported by this backend.
|
||||||
|
$changes = array();
|
||||||
|
}
|
||||||
$branches = $scm->getBranches();
|
$branches = $scm->getBranches();
|
||||||
$in_branches = $scm->inBranches($cobject->commit, '');
|
$in_branches = $scm->inBranches($cobject->commit, '');
|
||||||
$tags = $scm->getTags();
|
$tags = $scm->getTags();
|
||||||
$in_tags = $scm->inTags($cobject->commit, '');
|
$in_tags = $scm->inTags($cobject->commit, '');
|
||||||
return Pluf_Shortcuts_RenderToResponse('idf/source/commit.html',
|
return Pluf_Shortcuts_RenderToResponse('idf/source/'.$scmConf.'/commit.html',
|
||||||
array(
|
array(
|
||||||
'page_title' => $page_title,
|
'page_title' => $page_title,
|
||||||
'title' => $title,
|
'title' => $title,
|
||||||
'diff' => $diff,
|
'diff' => $diff,
|
||||||
'cobject' => $cobject,
|
'cobject' => $cobject,
|
||||||
'commit' => $commit,
|
'commit' => $commit,
|
||||||
|
'changes' => $changes,
|
||||||
'branches' => $branches,
|
'branches' => $branches,
|
||||||
'tree_in' => $in_branches,
|
'tree_in' => $in_branches,
|
||||||
'tags' => $tags,
|
'tags' => $tags,
|
||||||
@@ -326,20 +331,18 @@ class IDF_Views_Source
|
|||||||
$request);
|
$request);
|
||||||
}
|
}
|
||||||
|
|
||||||
public $downloadDiff_precond = array('IDF_Precondition::accessSource');
|
public $downloadDiff_precond = array('IDF_Precondition::accessSource',
|
||||||
|
'IDF_Views_Source_Precondition::scmAvailable',
|
||||||
|
'IDF_Views_Source_Precondition::revisionValid');
|
||||||
public function downloadDiff($request, $match)
|
public function downloadDiff($request, $match)
|
||||||
{
|
{
|
||||||
$scm = IDF_Scm::get($request->project);
|
$scm = IDF_Scm::get($request->project);
|
||||||
$commit = $match[2];
|
$commit = $match[2];
|
||||||
if (!$scm->isValidRevision($commit)) {
|
|
||||||
// Redirect to the first branch
|
|
||||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Source::treeBase',
|
|
||||||
array($request->project->shortname,
|
|
||||||
$scm->getMainBranch()));
|
|
||||||
return new Pluf_HTTP_Response_Redirect($url);
|
|
||||||
}
|
|
||||||
$cobject = $scm->getCommit($commit, true);
|
$cobject = $scm->getCommit($commit, true);
|
||||||
$rep = new Pluf_HTTP_Response($cobject->changes, 'text/plain');
|
if (!$cobject) {
|
||||||
|
throw new Exception('could not retrieve commit object for '. $commit);
|
||||||
|
}
|
||||||
|
$rep = new Pluf_HTTP_Response($cobject->diff, 'text/plain');
|
||||||
$rep->headers['Content-Disposition'] = 'attachment; filename="'.$commit.'.diff"';
|
$rep->headers['Content-Disposition'] = 'attachment; filename="'.$commit.'.diff"';
|
||||||
return $rep;
|
return $rep;
|
||||||
}
|
}
|
||||||
@@ -368,7 +371,7 @@ class IDF_Views_Source
|
|||||||
$previous = substr($request_file, 0, -strlen($l.' '));
|
$previous = substr($request_file, 0, -strlen($l.' '));
|
||||||
$scmConf = $request->conf->getVal('scm', 'git');
|
$scmConf = $request->conf->getVal('scm', 'git');
|
||||||
$props = $scm->getProperties($commit, $request_file);
|
$props = $scm->getProperties($commit, $request_file);
|
||||||
$content = self::highLight($extra['mime'], $scm->getFile($request_file_info));
|
$content = IDF_FileUtil::highLight($extra['mime'], $scm->getFile($request_file_info));
|
||||||
return Pluf_Shortcuts_RenderToResponse('idf/source/'.$scmConf.'/file.html',
|
return Pluf_Shortcuts_RenderToResponse('idf/source/'.$scmConf.'/file.html',
|
||||||
array(
|
array(
|
||||||
'page_title' => $page_title,
|
'page_title' => $page_title,
|
||||||
@@ -394,19 +397,14 @@ class IDF_Views_Source
|
|||||||
* Get a given file at a given commit.
|
* Get a given file at a given commit.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public $getFile_precond = array('IDF_Precondition::accessSource');
|
public $getFile_precond = array('IDF_Precondition::accessSource',
|
||||||
|
'IDF_Views_Source_Precondition::scmAvailable',
|
||||||
|
'IDF_Views_Source_Precondition::revisionValid');
|
||||||
public function getFile($request, $match)
|
public function getFile($request, $match)
|
||||||
{
|
{
|
||||||
$scm = IDF_Scm::get($request->project);
|
$scm = IDF_Scm::get($request->project);
|
||||||
$commit = $match[2];
|
$commit = $match[2];
|
||||||
$request_file = $match[3];
|
$request_file = $match[3];
|
||||||
if (!$scm->isValidRevision($commit)) {
|
|
||||||
// Redirect to the first branch
|
|
||||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Source::treeBase',
|
|
||||||
array($request->project->shortname,
|
|
||||||
$scm->getMainBranch()));
|
|
||||||
return new Pluf_HTTP_Response_Redirect($url);
|
|
||||||
}
|
|
||||||
$request_file_info = $scm->getPathInfo($request_file, $commit);
|
$request_file_info = $scm->getPathInfo($request_file, $commit);
|
||||||
if (!$request_file_info or $request_file_info->type == 'tree') {
|
if (!$request_file_info or $request_file_info->type == 'tree') {
|
||||||
// Redirect to the first branch
|
// Redirect to the first branch
|
||||||
@@ -427,27 +425,20 @@ class IDF_Views_Source
|
|||||||
* Get a zip archive of the current commit.
|
* Get a zip archive of the current commit.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public $download_precond = array('IDF_Precondition::accessSource');
|
public $download_precond = array('IDF_Precondition::accessSource',
|
||||||
|
'IDF_Views_Source_Precondition::scmAvailable',
|
||||||
|
'IDF_Views_Source_Precondition::revisionValid');
|
||||||
public function download($request, $match)
|
public function download($request, $match)
|
||||||
{
|
{
|
||||||
$commit = trim($match[2]);
|
$commit = trim($match[2]);
|
||||||
$scm = IDF_Scm::get($request->project);
|
$scm = IDF_Scm::get($request->project);
|
||||||
if (!$scm->isValidRevision($commit)) {
|
|
||||||
// Redirect to the first branch
|
|
||||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Source::treeBase',
|
|
||||||
array($request->project->shortname,
|
|
||||||
$scm->getMainBranch()));
|
|
||||||
return new Pluf_HTTP_Response_Redirect($url);
|
|
||||||
}
|
|
||||||
$base = $request->project->shortname.'-'.$commit;
|
$base = $request->project->shortname.'-'.$commit;
|
||||||
$cmd = $scm->getArchiveCommand($commit, $base.'/');
|
$rep = $scm->getArchiveStream($commit, $base.'/');
|
||||||
$rep = new Pluf_HTTP_Response_CommandPassThru($cmd, 'application/x-zip');
|
|
||||||
$rep->headers['Content-Transfer-Encoding'] = 'binary';
|
$rep->headers['Content-Transfer-Encoding'] = 'binary';
|
||||||
$rep->headers['Content-Disposition'] = 'attachment; filename="'.$base.'.zip"';
|
$rep->headers['Content-Disposition'] = 'attachment; filename="'.$base.'.zip"';
|
||||||
return $rep;
|
return $rep;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find the mime type of a requested file.
|
* Find the mime type of a requested file.
|
||||||
*
|
*
|
||||||
@@ -458,120 +449,34 @@ class IDF_Views_Source
|
|||||||
*/
|
*/
|
||||||
public static function getRequestedFileMimeType($file_info, $commit, $scm)
|
public static function getRequestedFileMimeType($file_info, $commit, $scm)
|
||||||
{
|
{
|
||||||
$mime = self::getMimeType($file_info->file);
|
$mime = IDF_FileUtil::getMimeType($file_info->file);
|
||||||
if ('application/octet-stream' != $mime[0]) {
|
if ('application/octet-stream' != $mime[0]) {
|
||||||
return $mime;
|
return $mime;
|
||||||
}
|
}
|
||||||
return self::getMimeTypeFromContent($file_info->file,
|
return IDF_FileUtil::getMimeTypeFromContent($file_info->file,
|
||||||
$scm->getFile($file_info));
|
$scm->getFile($file_info));
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find the mime type of a file using the fileinfo class.
|
|
||||||
*
|
|
||||||
* @param string Filename/Filepath
|
|
||||||
* @param string File content
|
|
||||||
* @return array Mime type found or 'application/octet-stream', basename, extension
|
|
||||||
*/
|
|
||||||
public static function getMimeTypeFromContent($file, $filedata)
|
|
||||||
{
|
|
||||||
$info = pathinfo($file);
|
|
||||||
$res = array('application/octet-stream',
|
|
||||||
$info['basename'],
|
|
||||||
isset($info['extension']) ? $info['extension'] : 'bin');
|
|
||||||
if (function_exists('finfo_open')) {
|
|
||||||
$finfo = finfo_open(FILEINFO_MIME);
|
|
||||||
$mime = finfo_buffer($finfo, $filedata);
|
|
||||||
finfo_close($finfo);
|
|
||||||
if ($mime) {
|
|
||||||
$res[0] = $mime;
|
|
||||||
}
|
|
||||||
if (!isset($info['extension']) && $mime) {
|
|
||||||
$res[2] = (0 === strpos($mime, 'text/')) ? 'txt' : 'bin';
|
|
||||||
} elseif (!isset($info['extension'])) {
|
|
||||||
$res[2] = 'bin';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $res;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find the mime type of a file.
|
|
||||||
*
|
|
||||||
* Use /etc/mime.types to find the type.
|
|
||||||
*
|
|
||||||
* @param string Filename/Filepath
|
|
||||||
* @param array Mime type found or 'application/octet-stream', basename, extension
|
|
||||||
*/
|
|
||||||
public static function getMimeType($file)
|
|
||||||
{
|
|
||||||
$src= Pluf::f('idf_mimetypes_db', '/etc/mime.types');
|
|
||||||
$mimes = preg_split("/\015\012|\015|\012/", file_get_contents($src));
|
|
||||||
$info = pathinfo($file);
|
|
||||||
if (isset($info['extension'])) {
|
|
||||||
foreach ($mimes as $mime) {
|
|
||||||
if ('#' != substr($mime, 0, 1)) {
|
|
||||||
$elts = preg_split('/ |\t/', $mime, -1, PREG_SPLIT_NO_EMPTY);
|
|
||||||
if (in_array($info['extension'], $elts)) {
|
|
||||||
return array($elts[0], $info['basename'], $info['extension']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// we consider that if no extension and base name is all
|
|
||||||
// uppercase, then we have a text file.
|
|
||||||
if ($info['basename'] == strtoupper($info['basename'])) {
|
|
||||||
return array('text/plain', $info['basename'], 'txt');
|
|
||||||
}
|
|
||||||
$info['extension'] = 'bin';
|
|
||||||
}
|
|
||||||
return array('application/octet-stream', $info['basename'], $info['extension']);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find if a given mime type is a text file.
|
* Callback function to sort tree entries
|
||||||
* This uses the output of the self::getMimeType function.
|
|
||||||
*
|
|
||||||
* @param array (Mime type, file name, extension)
|
|
||||||
* @return bool Is text
|
|
||||||
*/
|
*/
|
||||||
public static function isText($fileinfo)
|
public static function treeSort($a, $b)
|
||||||
{
|
{
|
||||||
if (0 === strpos($fileinfo[0], 'text/')) {
|
// compare two nodes of the same type
|
||||||
return true;
|
if ($a->type === $b->type) {
|
||||||
|
if (mb_convert_case($a->file, MB_CASE_LOWER) <
|
||||||
|
mb_convert_case ($b->file, MB_CASE_LOWER)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
$ext = 'mdtext php-dist h gitignore diff patch'
|
|
||||||
.Pluf::f('idf_extra_text_ext', '');
|
|
||||||
$ext = array_merge(self::$supportedExtenstions, explode(' ' , $ext));
|
|
||||||
return (in_array($fileinfo[2], $ext));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function highLight($fileinfo, $content)
|
// compare two nodes of different types, directories ("tree")
|
||||||
{
|
// should come before files ("blob")
|
||||||
$pretty = '';
|
if ($a->type > $b->type) {
|
||||||
if (self::isSupportedExtension($fileinfo[2])) {
|
return -1;
|
||||||
$pretty = ' prettyprint';
|
|
||||||
}
|
}
|
||||||
$table = array();
|
return 1;
|
||||||
$i = 1;
|
|
||||||
foreach (preg_split("/\015\012|\015|\012/", $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>';
|
|
||||||
$i++;
|
|
||||||
}
|
|
||||||
return Pluf_Template::markSafe(implode("\n", $table));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test if an extension is supported by the syntax highlighter.
|
|
||||||
*
|
|
||||||
* @param string The extension to test
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public static function isSupportedExtension($extension)
|
|
||||||
{
|
|
||||||
return in_array($extension, self::$supportedExtenstions);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -597,3 +502,15 @@ function IDF_Views_Source_PrettySizeSimple($size)
|
|||||||
return Pluf_Utils::prettySize($size);
|
return Pluf_Utils::prettySize($size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function IDF_Views_Source_ShortenString($string, $length)
|
||||||
|
{
|
||||||
|
$ellipse = "...";
|
||||||
|
$length = max(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)));
|
||||||
|
}
|
||||||
|
|||||||
74
src/IDF/Views/Source/Precondition.php
Normal file
74
src/IDF/Views/Source/Precondition.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) 2010 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_Views_Source_Precondition
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Ensures that the configured SCM for the project is available
|
||||||
|
*
|
||||||
|
* @param $request
|
||||||
|
* @return true | Pluf_HTTP_Response_Redirect
|
||||||
|
*/
|
||||||
|
static public function scmAvailable($request)
|
||||||
|
{
|
||||||
|
$scm = IDF_Scm::get($request->project);
|
||||||
|
if (!$scm->isAvailable()) {
|
||||||
|
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Source::help',
|
||||||
|
array($request->project->shortname));
|
||||||
|
return new Pluf_HTTP_Response_Redirect($url);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates the revision given in the URL path and acts accordingly
|
||||||
|
*
|
||||||
|
* @param $request
|
||||||
|
* @return true | Pluf_HTTP_Response_Redirect
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
static public function revisionValid($request)
|
||||||
|
{
|
||||||
|
list($url_info, $url_matches) = $request->view;
|
||||||
|
list(, $project, $commit) = $url_matches;
|
||||||
|
|
||||||
|
$scm = IDF_Scm::get($request->project);
|
||||||
|
$res = $scm->validateRevision($commit);
|
||||||
|
switch ($res) {
|
||||||
|
case IDF_Scm::REVISION_VALID:
|
||||||
|
return true;
|
||||||
|
case IDF_Scm::REVISION_INVALID:
|
||||||
|
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Source::invalidRevision',
|
||||||
|
array($request->project->shortname, $commit));
|
||||||
|
return new Pluf_HTTP_Response_Redirect($url);
|
||||||
|
case IDF_Scm::REVISION_AMBIGUOUS:
|
||||||
|
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Source::disambiguateRevision',
|
||||||
|
array($request->project->shortname,
|
||||||
|
$commit,
|
||||||
|
$url_info['model'].'::'.$url_info['method']));
|
||||||
|
return new Pluf_HTTP_Response_Redirect($url);
|
||||||
|
default:
|
||||||
|
throw new Exception('unknown validation result: '. $res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -110,7 +110,10 @@ class IDF_Views_User
|
|||||||
$ext_pass = substr(sha1($request->user->password.Pluf::f('secret_key')), 0, 8);
|
$ext_pass = substr(sha1($request->user->password.Pluf::f('secret_key')), 0, 8);
|
||||||
$params = array('user' => $request->user);
|
$params = array('user' => $request->user);
|
||||||
if ($request->method == 'POST') {
|
if ($request->method == 'POST') {
|
||||||
$form = new IDF_Form_UserAccount($request->POST, $params);
|
$form = new IDF_Form_UserAccount(array_merge(
|
||||||
|
(array)$request->POST,
|
||||||
|
(array)$request->FILES
|
||||||
|
), $params);
|
||||||
if ($form->isValid()) {
|
if ($form->isValid()) {
|
||||||
$user = $form->save();
|
$user = $form->save();
|
||||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views_User::myAccount');
|
$url = Pluf_HTTP_URL_urlForView('IDF_Views_User::myAccount');
|
||||||
@@ -121,9 +124,10 @@ class IDF_Views_User
|
|||||||
} else {
|
} else {
|
||||||
$data = $request->user->getData();
|
$data = $request->user->getData();
|
||||||
unset($data['password']);
|
unset($data['password']);
|
||||||
$form = new IDF_Form_UserAccount($data, $params);
|
$form = new IDF_Form_UserAccount(null, $params);
|
||||||
}
|
}
|
||||||
$keys = $request->user->get_idf_key_list();
|
$keys = $request->user->get_idf_key_list();
|
||||||
|
|
||||||
return Pluf_Shortcuts_RenderToResponse('idf/user/myaccount.html',
|
return Pluf_Shortcuts_RenderToResponse('idf/user/myaccount.html',
|
||||||
array('page_title' => __('Your Account'),
|
array('page_title' => __('Your Account'),
|
||||||
'api_key' => $api_key,
|
'api_key' => $api_key,
|
||||||
@@ -134,7 +138,7 @@ class IDF_Views_User
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete a SSH key.
|
* Delete a public key.
|
||||||
*
|
*
|
||||||
* This is redirecting to the preferences
|
* This is redirecting to the preferences
|
||||||
*/
|
*/
|
||||||
@@ -148,7 +152,7 @@ class IDF_Views_User
|
|||||||
return new Pluf_HTTP_Response_Forbidden($request);
|
return new Pluf_HTTP_Response_Forbidden($request);
|
||||||
}
|
}
|
||||||
$key->delete();
|
$key->delete();
|
||||||
$request->user->setMessage(__('The SSH key has been deleted.'));
|
$request->user->setMessage(__('The public key has been deleted.'));
|
||||||
}
|
}
|
||||||
return new Pluf_HTTP_Response_Redirect($url);
|
return new Pluf_HTTP_Response_Redirect($url);
|
||||||
}
|
}
|
||||||
@@ -212,9 +216,13 @@ class IDF_Views_User
|
|||||||
if (count($users) != 1 or !$users[0]->active) {
|
if (count($users) != 1 or !$users[0]->active) {
|
||||||
throw new Pluf_HTTP_Error404();
|
throw new Pluf_HTTP_Error404();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$user = $users[0];
|
||||||
|
$user_data = IDF_UserData::factory($user);
|
||||||
return Pluf_Shortcuts_RenderToResponse('idf/user/public.html',
|
return Pluf_Shortcuts_RenderToResponse('idf/user/public.html',
|
||||||
array('page_title' => (string) $users[0],
|
array('page_title' => (string) $user,
|
||||||
'member' => $users[0],
|
'member' => $user,
|
||||||
|
'user_data' => $user_data,
|
||||||
),
|
),
|
||||||
$request);
|
$request);
|
||||||
}
|
}
|
||||||
|
|||||||
104
src/IDF/Webhook.php
Normal file
104
src/IDF/Webhook.php
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
<?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, 2009, 2010 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 ***** */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Management of the webhooks.
|
||||||
|
*
|
||||||
|
* The class provides the tools to perform the POST request with
|
||||||
|
* authentication for the webhooks.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class IDF_Webhook
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Perform the POST request given the webhook payload.
|
||||||
|
*
|
||||||
|
* @param array Payload
|
||||||
|
* @return bool Success or error
|
||||||
|
*/
|
||||||
|
public static function postNotification($payload)
|
||||||
|
{
|
||||||
|
$data = json_encode($payload['to_send']);
|
||||||
|
$sign = hash_hmac('md5', $data, $payload['authkey']);
|
||||||
|
$params = array('http' => array(
|
||||||
|
'method' => 'POST',
|
||||||
|
'content' => $data,
|
||||||
|
'user_agent' => 'Indefero Hook Sender (http://www.indefero.net)',
|
||||||
|
'max_redirects' => 0,
|
||||||
|
'timeout' => 15,
|
||||||
|
'header'=> 'Post-Commit-Hook-Hmac: '.$sign."\r\n"
|
||||||
|
.'Content-Type: application/json'."\r\n",
|
||||||
|
)
|
||||||
|
);
|
||||||
|
$url = $payload['url'];
|
||||||
|
$ctx = stream_context_create($params);
|
||||||
|
$fp = @fopen($url, 'rb', false, $ctx);
|
||||||
|
if (!$fp) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$meta = stream_get_meta_data($fp);
|
||||||
|
@fclose($fp);
|
||||||
|
if (!isset($meta['wrapper_data'][0]) or $meta['timed_out']) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (0 === strpos($meta['wrapper_data'][0], 'HTTP/1.1 2') or
|
||||||
|
0 === strpos($meta['wrapper_data'][0], 'HTTP/1.1 3')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process the webhook.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public static function process($sender, &$params)
|
||||||
|
{
|
||||||
|
$item = $params['item'];
|
||||||
|
if ($item->type != 'new_commit') {
|
||||||
|
// We do nothing.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isset($params['res']['IDF_Webhook::process']) and
|
||||||
|
$params['res']['IDF_Webhook::process'] == true) {
|
||||||
|
// Already processed.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ($item->payload['url'] == '') {
|
||||||
|
// We do nothing.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// We have either to retry or to push for the first time.
|
||||||
|
$res = self::postNotification($item->payload);
|
||||||
|
if ($res) {
|
||||||
|
$params['res']['IDF_Webhook::process'] = true;
|
||||||
|
} elseif ($item->trials >= 9) {
|
||||||
|
// We are at trial 10, give up
|
||||||
|
$params['res']['IDF_Webhook::process'] = true;
|
||||||
|
} else {
|
||||||
|
// Need to try again
|
||||||
|
$params['res']['IDF_Webhook::process'] = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -272,11 +272,16 @@ class IDF_WikiRevision extends Pluf_Model
|
|||||||
}
|
}
|
||||||
$tmpl = new Pluf_Template($template);
|
$tmpl = new Pluf_Template($template);
|
||||||
$text_email = $tmpl->render($context);
|
$text_email = $tmpl->render($context);
|
||||||
$email = new Pluf_Mail(Pluf::f('from_email'),
|
|
||||||
$conf->getVal('wiki_notification_email'),
|
$addresses = explode(',', $conf->getVal('wiki_notification_email'));
|
||||||
$title);
|
foreach ($addresses as $address) {
|
||||||
$email->addTextMessage($text_email);
|
$email = new Pluf_Mail(Pluf::f('from_email'),
|
||||||
$email->sendMail();
|
$address,
|
||||||
|
$title);
|
||||||
|
$email->addTextMessage($text_email);
|
||||||
|
$email->sendMail();
|
||||||
|
}
|
||||||
|
|
||||||
Pluf_Translation::loadSetLocale($current_locale);
|
Pluf_Translation::loadSetLocale($current_locale);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,8 +73,51 @@ $cfg['git_write_remote_url'] = 'git@localhost:%s.git';
|
|||||||
$cfg['svn_repositories'] = 'file:///home/svn/repositories/%s';
|
$cfg['svn_repositories'] = 'file:///home/svn/repositories/%s';
|
||||||
$cfg['svn_remote_url'] = 'http://localhost/svn/%s';
|
$cfg['svn_remote_url'] = 'http://localhost/svn/%s';
|
||||||
|
|
||||||
|
#
|
||||||
|
# You can setup monotone for use with indefero in several ways.
|
||||||
|
# Please look into doc/syncmonotone.mdtext for more information.
|
||||||
|
#
|
||||||
|
|
||||||
|
# Path to the monotone binary
|
||||||
|
$cfg['mtn_path'] = 'mtn';
|
||||||
|
|
||||||
|
# Additional options for the started monotone process
|
||||||
|
$cfg['mtn_opts'] = array('--no-workspace', '--no-standard-rcfiles');
|
||||||
|
|
||||||
|
# The path to a specific database (local use) or a writable project
|
||||||
|
# directory (remote / usher use). %s is replaced with the project name
|
||||||
|
$cfg['mtn_repositories'] = '/home/mtn/repositories/%s.mtn';
|
||||||
|
|
||||||
|
# The URL which is displayed as sync URL to the user and which is also
|
||||||
|
# used to connect to a remote usher
|
||||||
|
$cfg['mtn_remote_url'] = 'mtn://my-host.biz/%s';
|
||||||
|
|
||||||
|
# Whether the particular database(s) are accessed locally (via automate stdio)
|
||||||
|
# or remotely (via automate remote_stdio). 'remote' is the default for
|
||||||
|
# use with usher and the SyncMonotone plugin, while 'local' access should be
|
||||||
|
# choosed for manual setups and / or ssh access.
|
||||||
|
$cfg['mtn_db_access'] = 'local';
|
||||||
|
|
||||||
|
# Full path to the directory tree which contains default configuration files
|
||||||
|
# that are automatically created for new projects. This is only needed
|
||||||
|
# if $cfg['mtn_db_access'] is set to remote, i.e. in case the SyncMonotone
|
||||||
|
# plugin should be used. If unset, it defaults to the tree underknees
|
||||||
|
# src/IDF/Plugin/SyncMonotone/. Don't forget the trailing slash!
|
||||||
|
#$cfg['mtn_confdir'] = '/path/to/dir/tree/';
|
||||||
|
|
||||||
|
# Additional configuration files you want to create / copy for new setups.
|
||||||
|
# All these file paths have to be relative to $cfg['mtn_confdir'].
|
||||||
|
#$cfg['mtn_confdir_extra'] = array('hooks.d/something.lua')
|
||||||
|
|
||||||
|
# Needs to be configured for remote / usher usage.
|
||||||
|
# This allows basic control of a running usher process via the forge
|
||||||
|
# administration. The variable must point to the full (writable)
|
||||||
|
# path of the usher configuration file which gets updated when new projects
|
||||||
|
# are added
|
||||||
|
#$cfg['mtn_usher_conf'] = '/path/to/usher.conf';
|
||||||
|
|
||||||
# Mercurial repositories path
|
# Mercurial repositories path
|
||||||
#$cfg['mercurial_repositories'] = '/home/mercurial/repositories/%s';
|
$cfg['mercurial_repositories'] = '/home/mercurial/repositories/%s';
|
||||||
#$cfg['mercurial_remote_url'] = 'http://projects.ceondo.com/hg/%s';
|
#$cfg['mercurial_remote_url'] = 'http://projects.ceondo.com/hg/%s';
|
||||||
|
|
||||||
# admins will get an email in case of errors in the system in non
|
# admins will get an email in case of errors in the system in non
|
||||||
@@ -161,8 +204,8 @@ $cfg['db_database'] = 'website'; # put absolute path to the db if you
|
|||||||
# are using SQLite.
|
# are using SQLite.
|
||||||
#
|
#
|
||||||
# The extension of the downloads are limited. You can add extra
|
# The extension of the downloads are limited. You can add extra
|
||||||
# extensions here. The list must start with a space.
|
# extensions here.
|
||||||
# $cfg['idf_extra_upload_ext'] = ' ext1 ext2';
|
# $cfg['idf_extra_upload_ext'] = 'ext1 ext2';
|
||||||
#
|
#
|
||||||
# By default, the size of the downloads is limited to 2MB.
|
# By default, the size of the downloads is limited to 2MB.
|
||||||
# The php.ini upload_max_filesize configuration setting will
|
# The php.ini upload_max_filesize configuration setting will
|
||||||
@@ -209,8 +252,22 @@ $cfg['languages'] = array('en', 'fr');
|
|||||||
$cfg['allowed_scm'] = array('git' => 'IDF_Scm_Git',
|
$cfg['allowed_scm'] = array('git' => 'IDF_Scm_Git',
|
||||||
'svn' => 'IDF_Scm_Svn',
|
'svn' => 'IDF_Scm_Svn',
|
||||||
'mercurial' => 'IDF_Scm_Mercurial',
|
'mercurial' => 'IDF_Scm_Mercurial',
|
||||||
|
'mtn' => 'IDF_Scm_Monotone',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
# Specific git config
|
||||||
|
# The core.quotepath is configured on new repository
|
||||||
|
# True -> All characters upper than 0x80 will be escape (default)
|
||||||
|
# False -> Characters is print directly, enable accented character in a UTF-8 shell
|
||||||
|
# $cfg['git_core_quotepath'] = false;
|
||||||
|
|
||||||
|
# Set to true when uploaded public keys should not only be validated
|
||||||
|
# syntactically, but also by the specific backend. For SSH public
|
||||||
|
# keys, ssh-keygen(3) must be available and usable in PATH, for
|
||||||
|
# monotone public keys, the monotone binary (as configured above)
|
||||||
|
# is used.
|
||||||
|
# $cfg['idf_strong_key_check'] = false;
|
||||||
|
|
||||||
# If you want to use another memtypes database
|
# If you want to use another memtypes database
|
||||||
# $cfg['idf_mimetypes_db'] = '/etc/mime.types';
|
# $cfg['idf_mimetypes_db'] = '/etc/mime.types';
|
||||||
|
|
||||||
@@ -235,4 +292,9 @@ $cfg['allowed_scm'] = array('git' => 'IDF_Scm_Git',
|
|||||||
# $cfg['hg_path'] = 'hg';
|
# $cfg['hg_path'] = 'hg';
|
||||||
# $cfg['git_path'] = 'git';
|
# $cfg['git_path'] = 'git';
|
||||||
|
|
||||||
|
# If you do not want to have calculations of the repositories, attachments
|
||||||
|
# and downloads size, set it to true. You can set to false some
|
||||||
|
# times to times to check the size.
|
||||||
|
# $cfg['idf_no_size_check'] = false;
|
||||||
|
|
||||||
return $cfg;
|
return $cfg;
|
||||||
|
|||||||
@@ -66,25 +66,26 @@ $ctl[] = array('regex' => '#^/logout/$#',
|
|||||||
$ctl[] = array('regex' => '#^/help/$#',
|
$ctl[] = array('regex' => '#^/help/$#',
|
||||||
'base' => $base,
|
'base' => $base,
|
||||||
'model' => 'IDF_Views',
|
'model' => 'IDF_Views',
|
||||||
'method' => 'faq');
|
'method' => 'faq',
|
||||||
|
'name' => 'idf_faq');
|
||||||
|
|
||||||
$ctl[] = array('regex' => '#^/p/([\-\w]+)/$#',
|
$ctl[] = array('regex' => '#^/p/([\-\w]+)/$#',
|
||||||
'base' => $base,
|
'base' => $base,
|
||||||
'model' => 'IDF_Views_Project',
|
'model' => 'IDF_Views_Project',
|
||||||
'method' => 'home');
|
'method' => 'home');
|
||||||
|
|
||||||
$ctl[] = array('regex' => '#^/p/([\-\w]+)/timeline/$#',
|
$ctl[] = array('regex' => '#^/p/([\-\w]+)/timeline/(\w+)/$#',
|
||||||
'base' => $base,
|
'base' => $base,
|
||||||
'model' => 'IDF_Views_Project',
|
'model' => 'IDF_Views_Project',
|
||||||
'method' => 'timeline');
|
'method' => 'timeline');
|
||||||
|
|
||||||
$ctl[] = array('regex' => '#^/p/([\-\w]+)/feed/timeline/$#',
|
$ctl[] = array('regex' => '#^/p/([\-\w]+)/feed/timeline/(\w+)/$#',
|
||||||
'base' => $base,
|
'base' => $base,
|
||||||
'model' => 'IDF_Views_Project',
|
'model' => 'IDF_Views_Project',
|
||||||
'method' => 'timelineFeed',
|
'method' => 'timelineFeed',
|
||||||
'name' => 'idf_project_timeline_feed');
|
'name' => 'idf_project_timeline_feed');
|
||||||
|
|
||||||
$ctl[] = array('regex' => '#^/p/([\-\w]+)/feed/timeline/token/(.*)/$#',
|
$ctl[] = array('regex' => '#^/p/([\-\w]+)/feed/timeline/(\w+)/token/(.*)/$#',
|
||||||
'base' => $base,
|
'base' => $base,
|
||||||
'model' => 'IDF_Views_Project',
|
'model' => 'IDF_Views_Project',
|
||||||
'method' => 'timelineFeed',
|
'method' => 'timelineFeed',
|
||||||
@@ -140,6 +141,16 @@ $ctl[] = array('regex' => '#^/p/([\-\w]+)/issues/view/attachment/(\d+)/(.*)$#',
|
|||||||
'model' => 'IDF_Views_Issue',
|
'model' => 'IDF_Views_Issue',
|
||||||
'method' => 'viewAttachment');
|
'method' => 'viewAttachment');
|
||||||
|
|
||||||
|
$ctl[] = array('regex' => '#^/p/([\-\w]+)/issues/watchlist/(\w+)$#',
|
||||||
|
'base' => $base,
|
||||||
|
'model' => 'IDF_Views_Issue',
|
||||||
|
'method' => 'watchList');
|
||||||
|
|
||||||
|
$ctl[] = array('regex' => '#^/watchlist/(\w+)$#',
|
||||||
|
'base' => $base,
|
||||||
|
'model' => 'IDF_Views_Issue',
|
||||||
|
'method' => 'forgeWatchList');
|
||||||
|
|
||||||
// ---------- SCM ----------------------------------------
|
// ---------- SCM ----------------------------------------
|
||||||
|
|
||||||
$ctl[] = array('regex' => '#^/p/([\-\w]+)/source/help/$#',
|
$ctl[] = array('regex' => '#^/p/([\-\w]+)/source/help/$#',
|
||||||
@@ -147,6 +158,16 @@ $ctl[] = array('regex' => '#^/p/([\-\w]+)/source/help/$#',
|
|||||||
'model' => 'IDF_Views_Source',
|
'model' => 'IDF_Views_Source',
|
||||||
'method' => 'help');
|
'method' => 'help');
|
||||||
|
|
||||||
|
$ctl[] = array('regex' => '#^/p/([\-\w]+)/source/invalid/([^/]+)/$#',
|
||||||
|
'base' => $base,
|
||||||
|
'model' => 'IDF_Views_Source',
|
||||||
|
'method' => 'invalidRevision');
|
||||||
|
|
||||||
|
$ctl[] = array('regex' => '#^/p/([\-\w]+)/source/disambiguate/([^/]+)/from/([^/]+)/$#',
|
||||||
|
'base' => $base,
|
||||||
|
'model' => 'IDF_Views_Source',
|
||||||
|
'method' => 'disambiguateRevision');
|
||||||
|
|
||||||
$ctl[] = array('regex' => '#^/p/([\-\w]+)/source/tree/([^/]+)/$#',
|
$ctl[] = array('regex' => '#^/p/([\-\w]+)/source/tree/([^/]+)/$#',
|
||||||
'base' => $base,
|
'base' => $base,
|
||||||
'model' => 'IDF_Views_Source',
|
'model' => 'IDF_Views_Source',
|
||||||
@@ -385,6 +406,29 @@ $ctl[] = array('regex' => '#^/admin/users/(\d+)/$#',
|
|||||||
'model' => 'IDF_Views_Admin',
|
'model' => 'IDF_Views_Admin',
|
||||||
'method' => 'userUpdate');
|
'method' => 'userUpdate');
|
||||||
|
|
||||||
|
if (Pluf::f("mtn_usher_conf", null) !== null)
|
||||||
|
{
|
||||||
|
$ctl[] = array('regex' => '#^/admin/usher/$#',
|
||||||
|
'base' => $base,
|
||||||
|
'model' => 'IDF_Views_Admin',
|
||||||
|
'method' => 'usher');
|
||||||
|
|
||||||
|
$ctl[] = array('regex' => '#^/admin/usher/control/(.*)$#',
|
||||||
|
'base' => $base,
|
||||||
|
'model' => 'IDF_Views_Admin',
|
||||||
|
'method' => 'usherControl');
|
||||||
|
|
||||||
|
$ctl[] = array('regex' => '#^/admin/usher/server/(.+)/control/(.+)$#',
|
||||||
|
'base' => $base,
|
||||||
|
'model' => 'IDF_Views_Admin',
|
||||||
|
'method' => 'usherServerControl');
|
||||||
|
|
||||||
|
$ctl[] = array('regex' => '#^/admin/usher/server/(.+)/connections/$#',
|
||||||
|
'base' => $base,
|
||||||
|
'model' => 'IDF_Views_Admin',
|
||||||
|
'method' => 'usherServerConnections');
|
||||||
|
}
|
||||||
|
|
||||||
// ---------- UTILITY VIEWS -------------------------------
|
// ---------- UTILITY VIEWS -------------------------------
|
||||||
|
|
||||||
$ctl[] = array('regex' => '#^/register/$#',
|
$ctl[] = array('regex' => '#^/register/$#',
|
||||||
|
|||||||
4498
src/IDF/locale/cs/idf.po
Normal file
4498
src/IDF/locale/cs/idf.po
Normal file
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user