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
|
||||
src/IDF/conf/idf.php
|
||||
src/IDF/conf/idf.test.php
|
||||
@ -6,3 +7,5 @@ www/media/upload
|
||||
src/IDF/gettexttemplates
|
||||
indefero-*.zip
|
||||
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
|
||||
Brian Armstrong
|
||||
Raphaël Emourgeon
|
||||
Jakub Viták
|
||||
Vladimir Solomatin
|
||||
|
||||
And all the nice users who spent time reporting issues and promoting
|
||||
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.
|
@ -212,4 +212,14 @@ If you access a Subversion server with a self-signed certificate, you
|
||||
may have problems as your certificate is not trusted, check the
|
||||
[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_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,10 +1,13 @@
|
||||
#!/bin/sh
|
||||
last="$1"
|
||||
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 "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 archive --format=zip --prefix=indefero-$new/ v$new > indefero-$new.zip"
|
||||
echo "git log --no-merges v$new ^v$last > ChangeLog-$new"
|
||||
echo "git shortlog --no-merges v$new ^v$last > ShortLog"
|
||||
echo "git diff --stat --summary -M v$last v$new > diffstat-$new"
|
||||
echo "git diff --stat --summary -M v$last v$new > diffstat-$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
|
||||
#
|
||||
|
||||
SCRIPTDIR=$(dirname $(readlink -f $0))
|
||||
FULL_GIT_DIR=$(readlink -f $GIT_DIR)
|
||||
SCRIPTDIR=$(dirname $(readlink $0))
|
||||
FULL_GIT_DIR=$(cd "$GIT_DIR" && /bin/pwd || "$GIT_DIR")
|
||||
PHP_POST_UPDATE=$SCRIPTDIR/gitpostupdate.php
|
||||
|
||||
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],
|
||||
'env' => array_merge($_ENV, $_SERVER));
|
||||
Pluf_Log::event(array('gitpostupdate.php', 'Send run signal.', $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
|
||||
#
|
||||
|
||||
SCRIPTDIR=$(dirname $(readlink -f $0))
|
||||
SCRIPTDIR=$(dirname $(readlink $0))
|
||||
PHP_POST_COMMIT=$SCRIPTDIR/svnpostcommit.php
|
||||
|
||||
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],
|
||||
'revision' => $argv[2],
|
||||
'env' => array_merge($_ENV, $_SERVER));
|
||||
Pluf_Log::event(array('svnpostcommit.php', 'Send run signal.', $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
|
||||
{
|
||||
public $_model = __CLASS__;
|
||||
public $extra = null; /**< Extra data as IDF_Gconf object */
|
||||
|
||||
function init()
|
||||
{
|
||||
@ -44,9 +45,9 @@ class IDF_Commit extends Pluf_Model
|
||||
'id' =>
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Sequence',
|
||||
'blank' => true,
|
||||
'blank' => true,
|
||||
),
|
||||
'project' =>
|
||||
'project' =>
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Foreignkey',
|
||||
'model' => 'IDF_Project',
|
||||
@ -54,7 +55,7 @@ class IDF_Commit extends Pluf_Model
|
||||
'verbose' => __('project'),
|
||||
'relate_name' => 'commits',
|
||||
),
|
||||
'author' =>
|
||||
'author' =>
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Foreignkey',
|
||||
'model' => 'Pluf_User',
|
||||
@ -118,7 +119,7 @@ class IDF_Commit extends Pluf_Model
|
||||
{
|
||||
IDF_Search::index($this);
|
||||
if ($create) {
|
||||
IDF_Timeline::insert($this, $this->get_project(),
|
||||
IDF_Timeline::insert($this, $this->get_project(),
|
||||
$this->get_author(), $this->creation_dtime);
|
||||
}
|
||||
}
|
||||
@ -127,6 +128,7 @@ class IDF_Commit extends Pluf_Model
|
||||
{
|
||||
IDF_Timeline::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));
|
||||
$r = Pluf::factory('IDF_Commit')->getList(array('filter'=>$sql->gen()));
|
||||
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];
|
||||
}
|
||||
if (!isset($change->full_message)) {
|
||||
@ -154,9 +160,16 @@ class IDF_Commit extends Pluf_Model
|
||||
$commit->summary = self::toUTF8($change->title);
|
||||
$commit->fullmessage = self::toUTF8($change->full_message);
|
||||
$commit->author = $scm->findAuthor($change->author);
|
||||
$commit->origauthor = $change->author;
|
||||
$commit->origauthor = self::toUTF8($change->author);
|
||||
$commit->creation_dtime = $change->date;
|
||||
$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());
|
||||
return $commit;
|
||||
}
|
||||
@ -200,13 +213,13 @@ class IDF_Commit extends Pluf_Model
|
||||
* Returns the timeline fragment for the commit.
|
||||
*
|
||||
*
|
||||
* @param Pluf_HTTP_Request
|
||||
* @param Pluf_HTTP_Request
|
||||
* @return Pluf_Template_SafeString
|
||||
*/
|
||||
public function timelineFragment($request)
|
||||
{
|
||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Source::commit',
|
||||
array($request->project->shortname,
|
||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Source::commit',
|
||||
array($request->project->shortname,
|
||||
$this->scm_id));
|
||||
$out = '<tr class="log"><td><a href="'.$url.'">'.
|
||||
Pluf_esc(Pluf_Template_dateAgo($this->creation_dtime, 'without')).
|
||||
@ -222,24 +235,24 @@ class IDF_Commit extends Pluf_Model
|
||||
</tr>
|
||||
<tr class="extra">
|
||||
<td colspan="2">
|
||||
<div class="helptext right">'.sprintf(__('Commit %s, by %s'), '<a href="'.$url.'" class="mono">'.$this->scm_id.'</a>', $user).'</div></td></tr>';
|
||||
<div class="helptext right">'.sprintf(__('Commit %s, by %s'), '<a href="'.$url.'" class="mono">'.$this->scm_id.'</a>', $user).'</div></td></tr>';
|
||||
return Pluf_Template::markSafe($out);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the feed fragment for the commit.
|
||||
*
|
||||
* @param Pluf_HTTP_Request
|
||||
* @param Pluf_HTTP_Request
|
||||
* @return Pluf_Template_SafeString
|
||||
*/
|
||||
public function feedFragment($request)
|
||||
{
|
||||
$url = Pluf::f('url_base')
|
||||
.Pluf_HTTP_URL_urlForView('IDF_Views_Source::commit',
|
||||
array($request->project->shortname,
|
||||
.Pluf_HTTP_URL_urlForView('IDF_Views_Source::commit',
|
||||
array($request->project->shortname,
|
||||
$this->scm_id));
|
||||
$date = Pluf_Date::gmDateToGmString($this->creation_dtime);
|
||||
$author = ($this->get_author()) ?
|
||||
$author = ($this->get_author()) ?
|
||||
$this->get_author() : $this->origauthor;
|
||||
$cproject = $this->get_project();
|
||||
$context = new Pluf_Template_Context_Request(
|
||||
@ -264,12 +277,41 @@ class IDF_Commit extends Pluf_Model
|
||||
*/
|
||||
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', '')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$current_locale = Pluf_Translation::getLocale();
|
||||
$langs = Pluf::f('languages', array('en'));
|
||||
Pluf_Translation::loadSetLocale($langs[0]);
|
||||
Pluf_Translation::loadSetLocale($langs[0]);
|
||||
|
||||
$context = new Pluf_Template_Context(
|
||||
array(
|
||||
@ -280,13 +322,16 @@ class IDF_Commit extends Pluf_Model
|
||||
);
|
||||
$tmpl = new Pluf_Template('idf/source/commit-created-email.txt');
|
||||
$text_email = $tmpl->render($context);
|
||||
$email = new Pluf_Mail(Pluf::f('from_email'),
|
||||
$conf->getVal('source_notification_email'),
|
||||
sprintf(__('New Commit %s - %s (%s)'),
|
||||
$this->scm_id, $this->summary,
|
||||
$this->get_project()->shortname));
|
||||
$email->addTextMessage($text_email);
|
||||
$email->sendMail();
|
||||
$addresses = explode(',', $conf->getVal('source_notification_email'));
|
||||
foreach ($addresses as $address) {
|
||||
$email = new Pluf_Mail(Pluf::f('from_email'),
|
||||
$address,
|
||||
sprintf(__('New Commit %s - %s (%s)'),
|
||||
$this->scm_id, $this->summary,
|
||||
$this->get_project()->shortname));
|
||||
$email->addTextMessage($text_email);
|
||||
$email->sendMail();
|
||||
}
|
||||
Pluf_Translation::loadSetLocale($current_locale);
|
||||
}
|
||||
}
|
||||
|
@ -51,7 +51,7 @@ class IDF_Diff
|
||||
$i = 0; // Used to skip the end of a git patch with --\nversion number
|
||||
foreach ($this->lines as $line) {
|
||||
$i++;
|
||||
if (0 === strpos($line, '--') and isset($this->lines[$i])
|
||||
if (0 === strpos($line, '--') and isset($this->lines[$i])
|
||||
and preg_match('/^\d+\.\d+\.\d+\.\d+$/', $this->lines[$i])) {
|
||||
break;
|
||||
}
|
||||
@ -71,6 +71,28 @@ class IDF_Diff
|
||||
$current_chunk = 0;
|
||||
$indiff = true;
|
||||
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: ')) {
|
||||
$current_file = self::getSvnFile($line);
|
||||
$files[$current_file] = array();
|
||||
@ -133,6 +155,12 @@ class IDF_Diff
|
||||
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.
|
||||
*/
|
||||
@ -141,8 +169,8 @@ class IDF_Diff
|
||||
$out = '';
|
||||
foreach ($this->files as $filename=>$file) {
|
||||
$pretty = '';
|
||||
$fileinfo = IDF_Views_Source::getMimeType($filename);
|
||||
if (IDF_Views_Source::isSupportedExtension($fileinfo[2])) {
|
||||
$fileinfo = IDF_FileUtil::getMimeType($filename);
|
||||
if (IDF_FileUtil::isSupportedExtension($fileinfo[2])) {
|
||||
$pretty = ' prettyprint';
|
||||
}
|
||||
$out .= "\n".'<table class="diff" summary="">'."\n";
|
||||
@ -215,14 +243,14 @@ class IDF_Diff
|
||||
* @param int Number of lines before/after the chunk to be displayed (10)
|
||||
* @return Pluf_Template_SafeString The table body
|
||||
*/
|
||||
public function fileCompare($orig, $chunks, $filename, $context=10)
|
||||
public function fileCompare($orig, $chunks, $filename, $context=10)
|
||||
{
|
||||
$orig_lines = preg_split("/\015\012|\015|\012/", $orig);
|
||||
$new_chunks = $this->mergeChunks($orig_lines, $chunks, $context);
|
||||
return $this->renderCompared($new_chunks, $filename);
|
||||
}
|
||||
|
||||
public function mergeChunks($orig_lines, $chunks, $context=10)
|
||||
public function mergeChunks($orig_lines, $chunks, $context=10)
|
||||
{
|
||||
$spans = array();
|
||||
$new_chunks = array();
|
||||
@ -250,7 +278,7 @@ class IDF_Diff
|
||||
for ($lc=$spans[$i][0];$lc<$chunk[0][0];$lc++) {
|
||||
$exists = false;
|
||||
foreach ($chunk_lines as $line) {
|
||||
if ($lc == $line[0]
|
||||
if ($lc == $line[0]
|
||||
or ($chunk[0][1]-$chunk[0][0]+$lc) == $line[1]) {
|
||||
$exists = true;
|
||||
break;
|
||||
@ -259,7 +287,7 @@ class IDF_Diff
|
||||
if (!$exists) {
|
||||
$orig = isset($orig_lines[$lc-1]) ? $orig_lines[$lc-1] : '';
|
||||
$n_chunk[] = array(
|
||||
$lc,
|
||||
$lc,
|
||||
$chunk[0][1]-$chunk[0][0]+$lc,
|
||||
$orig
|
||||
);
|
||||
@ -283,7 +311,7 @@ class IDF_Diff
|
||||
}
|
||||
if (!$exists) {
|
||||
$n_chunk[] = array(
|
||||
$lc,
|
||||
$lc,
|
||||
$lline[1]-$lline[0]+$lc,
|
||||
$orig_lines[$lc-1]
|
||||
);
|
||||
@ -305,7 +333,7 @@ class IDF_Diff
|
||||
foreach ($chunk as $line) {
|
||||
if ($line[0] > $lline[0] or empty($line[0])) {
|
||||
$nnew_chunks[$i-1][] = $line;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$nnew_chunks[] = $chunk;
|
||||
@ -322,9 +350,9 @@ class IDF_Diff
|
||||
|
||||
public function renderCompared($chunks, $filename)
|
||||
{
|
||||
$fileinfo = IDF_Views_Source::getMimeType($filename);
|
||||
$fileinfo = IDF_FileUtil::getMimeType($filename);
|
||||
$pretty = '';
|
||||
if (IDF_Views_Source::isSupportedExtension($fileinfo[2])) {
|
||||
if (IDF_FileUtil::isSupportedExtension($fileinfo[2])) {
|
||||
$pretty = ' prettyprint';
|
||||
}
|
||||
$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'),
|
||||
'svn' => __('Subversion'),
|
||||
'mercurial' => __('mercurial'),
|
||||
'mtn' => __('monotone'),
|
||||
);
|
||||
foreach (Pluf::f('allowed_scm', array()) as $key => $class) {
|
||||
$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".'),
|
||||
));
|
||||
|
||||
$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(
|
||||
array('required' => true,
|
||||
'label' => __('Repository type'),
|
||||
@ -92,6 +101,14 @@ class IDF_Form_Admin_ProjectCreate extends Pluf_Form
|
||||
'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(
|
||||
array('required' => false,
|
||||
'label' => __('Project owners'),
|
||||
@ -109,6 +126,20 @@ class IDF_Form_Admin_ProjectCreate extends Pluf_Form
|
||||
'cols' => 40),
|
||||
'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]
|
||||
*
|
||||
@ -156,6 +187,34 @@ class IDF_Form_Admin_ProjectCreate extends Pluf_Form
|
||||
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()
|
||||
{
|
||||
$shortname = mb_strtolower($this->cleaned_data['shortname']);
|
||||
@ -184,6 +243,11 @@ class IDF_Form_Admin_ProjectCreate extends Pluf_Form
|
||||
$this->cleaned_data[$key] = '';
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->cleaned_data['scm'] != 'mtn') {
|
||||
$this->cleaned_data['mtn_master_branch'] = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* [signal]
|
||||
*
|
||||
@ -217,24 +281,88 @@ class IDF_Form_Admin_ProjectCreate extends Pluf_Form
|
||||
$project = new IDF_Project();
|
||||
$project->name = $this->cleaned_data['name'];
|
||||
$project->shortname = $this->cleaned_data['shortname'];
|
||||
$project->private = $this->cleaned_data['private_project'];
|
||||
$project->description = __('Click on the Project Management tab to set the description of your project.');
|
||||
$project->shortdesc = $this->cleaned_data['shortdesc'];
|
||||
|
||||
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();
|
||||
$conf = new IDF_Conf();
|
||||
$conf->setProject($project);
|
||||
$keys = array('scm', 'svn_remote_url',
|
||||
'svn_username', 'svn_password');
|
||||
$keys = array('scm', 'svn_remote_url', 'svn_username',
|
||||
'svn_password', 'mtn_master_branch');
|
||||
foreach ($keys as $key) {
|
||||
$this->cleaned_data[$key] = (!empty($this->cleaned_data[$key])) ?
|
||||
$this->cleaned_data[$key] = (!empty($this->cleaned_data[$key])) ?
|
||||
$this->cleaned_data[$key] : '';
|
||||
$conf->setVal($key, $this->cleaned_data[$key]);
|
||||
}
|
||||
if ($this->cleaned_data['template'] != '--') {
|
||||
$tmplconf = new IDF_Conf();
|
||||
$tmplconf->setProject($tmpl);
|
||||
// We need to get all the configuration variables we want from
|
||||
// the old project and put them into the new project.
|
||||
$props = array(
|
||||
'labels_download_predefined' => IDF_Form_UploadConf::init_predefined,
|
||||
'labels_download_one_max' => IDF_Form_UploadConf::init_one_max,
|
||||
'labels_wiki_predefined' => IDF_Form_WikiConf::init_predefined,
|
||||
'labels_wiki_one_max' => IDF_Form_WikiConf::init_one_max,
|
||||
'labels_issue_template' => IDF_Form_IssueTrackingConf::init_template,
|
||||
'labels_issue_open' => IDF_Form_IssueTrackingConf::init_open,
|
||||
'labels_issue_closed' => IDF_Form_IssueTrackingConf::init_closed,
|
||||
'labels_issue_predefined' => IDF_Form_IssueTrackingConf::init_predefined,
|
||||
'labels_issue_one_max' => IDF_Form_IssueTrackingConf::init_one_max,
|
||||
'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();
|
||||
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();
|
||||
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'];
|
||||
$members = $this->project->getMembershipData('string');
|
||||
$conf = $this->project->getConf();
|
||||
|
||||
$this->fields['name'] = new Pluf_Form_Field_Varchar(
|
||||
array('required' => true,
|
||||
'label' => __('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(
|
||||
array('required' => false,
|
||||
'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()
|
||||
{
|
||||
return IDF_Form_MembersConf::checkBadLogins($this->cleaned_data['owners']);
|
||||
@ -76,11 +120,19 @@ class IDF_Form_Admin_ProjectUpdate extends Pluf_Form
|
||||
if (!$this->isValid()) {
|
||||
throw new Exception(__('Cannot save the model from an invalid form.'));
|
||||
}
|
||||
IDF_Form_MembersConf::updateMemberships($this->project,
|
||||
IDF_Form_MembersConf::updateMemberships($this->project,
|
||||
$this->cleaned_data);
|
||||
$this->project->membershipsUpdated();
|
||||
$this->project->name = $this->cleaned_data['name'];
|
||||
$this->project->shortdesc = $this->cleaned_data['shortdesc'];
|
||||
$this->project->update();
|
||||
|
||||
$keys = array('mtn_master_branch');
|
||||
foreach ($keys as $key) {
|
||||
if (!empty($this->cleaned_data[$key])) {
|
||||
$this->project->getConf()->setVal($key, $this->cleaned_data[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -77,22 +77,20 @@ class IDF_Form_Admin_UserCreate extends Pluf_Form
|
||||
'initial' => '',
|
||||
'widget' => 'Pluf_Form_Widget_SelectInput',
|
||||
'widget_attrs' => array(
|
||||
'choices' =>
|
||||
'choices' =>
|
||||
Pluf_L10n::getInstalledLanguages()
|
||||
),
|
||||
));
|
||||
|
||||
$this->fields['ssh_key'] = new Pluf_Form_Field_Varchar(
|
||||
$this->fields['public_key'] = new Pluf_Form_Field_Varchar(
|
||||
array('required' => false,
|
||||
'label' => __('Add a public SSH key'),
|
||||
'label' => __('Add a public key'),
|
||||
'initial' => '',
|
||||
'widget_attrs' => array('rows' => 3,
|
||||
'cols' => 40),
|
||||
'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);
|
||||
Pluf_Signal::send('Pluf_User::passwordUpdated',
|
||||
'IDF_Form_Admin_UserCreate', $params);
|
||||
// Create the ssh key as needed
|
||||
if ('' !== $this->cleaned_data['ssh_key']) {
|
||||
// Create the public key as needed
|
||||
if ('' !== $this->cleaned_data['public_key']) {
|
||||
$key = new IDF_Key();
|
||||
$key->user = $user;
|
||||
$key->content = $this->cleaned_data['ssh_key'];
|
||||
$key->content = $this->cleaned_data['public_key'];
|
||||
$key->create();
|
||||
}
|
||||
// Send an email to the user with the password
|
||||
@ -162,16 +160,11 @@ class IDF_Form_Admin_UserCreate extends Pluf_Form
|
||||
return $user;
|
||||
}
|
||||
|
||||
function clean_ssh_key()
|
||||
{
|
||||
return IDF_Form_UserAccount::checkSshKey($this->cleaned_data['ssh_key']);
|
||||
}
|
||||
|
||||
function clean_last_name()
|
||||
{
|
||||
$last_name = trim($this->cleaned_data['last_name']);
|
||||
if ($last_name == mb_strtoupper($last_name)) {
|
||||
return mb_convert_case(mb_strtolower($last_name),
|
||||
return mb_convert_case(mb_strtolower($last_name),
|
||||
MB_CASE_TITLE, 'UTF-8');
|
||||
}
|
||||
return $last_name;
|
||||
@ -181,7 +174,7 @@ class IDF_Form_Admin_UserCreate extends Pluf_Form
|
||||
{
|
||||
$first_name = trim($this->cleaned_data['first_name']);
|
||||
if ($first_name == mb_strtoupper($first_name)) {
|
||||
return mb_convert_case(mb_strtolower($first_name),
|
||||
return mb_convert_case(mb_strtolower($first_name),
|
||||
MB_CASE_TITLE, 'UTF-8');
|
||||
}
|
||||
return $first_name;
|
||||
@ -211,4 +204,12 @@ class IDF_Form_Admin_UserCreate extends Pluf_Form
|
||||
}
|
||||
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.
|
||||
*/
|
||||
class IDF_Form_Admin_UserUpdate extends Pluf_Form
|
||||
class IDF_Form_Admin_UserUpdate extends Pluf_Form
|
||||
{
|
||||
public $user = null;
|
||||
|
||||
public function initFields($extra=array())
|
||||
{
|
||||
$this->user = $extra['user'];
|
||||
$user_data = IDF_UserData::factory($this->user);
|
||||
|
||||
$this->fields['first_name'] = new Pluf_Form_Field_Varchar(
|
||||
array('required' => false,
|
||||
'label' => __('First name'),
|
||||
@ -66,7 +68,7 @@ class IDF_Form_Admin_UserUpdate extends Pluf_Form
|
||||
'initial' => $this->user->language,
|
||||
'widget' => 'Pluf_Form_Widget_SelectInput',
|
||||
'widget_attrs' => array(
|
||||
'choices' =>
|
||||
'choices' =>
|
||||
Pluf_L10n::getInstalledLanguages()
|
||||
),
|
||||
));
|
||||
@ -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) {
|
||||
$this->fields['staff'] = new Pluf_Form_Field_Boolean(
|
||||
array('required' => false,
|
||||
@ -136,8 +198,37 @@ class IDF_Form_Admin_UserUpdate extends Pluf_Form
|
||||
$update_pass = true;
|
||||
}
|
||||
$this->user->setFromFormData($this->cleaned_data);
|
||||
|
||||
if ($commit) {
|
||||
$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) {
|
||||
/**
|
||||
* [signal]
|
||||
@ -170,7 +261,7 @@ class IDF_Form_Admin_UserUpdate extends Pluf_Form
|
||||
{
|
||||
$last_name = trim($this->cleaned_data['last_name']);
|
||||
if ($last_name == mb_strtoupper($last_name)) {
|
||||
return mb_convert_case(mb_strtolower($last_name),
|
||||
return mb_convert_case(mb_strtolower($last_name),
|
||||
MB_CASE_TITLE, 'UTF-8');
|
||||
}
|
||||
return $last_name;
|
||||
@ -183,7 +274,7 @@ class IDF_Form_Admin_UserUpdate extends Pluf_Form
|
||||
throw new Pluf_Form_Invalid(__('--- is not a valid first name.'));
|
||||
}
|
||||
if ($first_name == mb_strtoupper($first_name)) {
|
||||
$first_name = mb_convert_case(mb_strtolower($first_name),
|
||||
$first_name = mb_convert_case(mb_strtolower($first_name),
|
||||
MB_CASE_TITLE, 'UTF-8');
|
||||
}
|
||||
return $first_name;
|
||||
@ -201,12 +292,23 @@ class IDF_Form_Admin_UserUpdate extends Pluf_Form
|
||||
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()
|
||||
{
|
||||
if (!isset($this->errors['password'])
|
||||
if (!isset($this->errors['password'])
|
||||
&& !isset($this->errors['password2'])) {
|
||||
$password1 = $this->cleaned_data['password'];
|
||||
$password2 = $this->cleaned_data['password2'];
|
||||
|
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)) {
|
||||
$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(
|
||||
array('required' => true,
|
||||
'label' => __('Summary'),
|
||||
@ -57,7 +60,7 @@ class IDF_Form_IssueCreate extends Pluf_Form
|
||||
$this->fields['content'] = new Pluf_Form_Field_Varchar(
|
||||
array('required' => true,
|
||||
'label' => __('Description'),
|
||||
'initial' => '',
|
||||
'initial' => $contentTemplate,
|
||||
'widget' => 'Pluf_Form_Widget_TextareaInput',
|
||||
'widget_attrs' => array(
|
||||
'cols' => 58,
|
||||
@ -105,14 +108,41 @@ class IDF_Form_IssueCreate extends Pluf_Form
|
||||
'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++) {
|
||||
$initial = '';
|
||||
switch ($i) {
|
||||
case 1:
|
||||
$initial = 'Type:Defect';
|
||||
$initial = $predefined_type;
|
||||
break;
|
||||
case 2:
|
||||
$initial = 'Priority:Medium';
|
||||
$initial = $predefined_priority;
|
||||
break;
|
||||
}
|
||||
$this->fields['label'.$i] = new Pluf_Form_Field_Varchar(
|
||||
@ -276,6 +306,7 @@ class IDF_Form_IssueCreate extends Pluf_Form
|
||||
$comment->create();
|
||||
// If we have a file, create the IDF_IssueFile and attach
|
||||
// it to the comment.
|
||||
$created_files = array();
|
||||
for ($i=1;$i<4;$i++) {
|
||||
if ($this->cleaned_data['attachment'.$i]) {
|
||||
$file = new IDF_IssueFile();
|
||||
@ -283,8 +314,36 @@ class IDF_Form_IssueCreate extends Pluf_Form
|
||||
$file->submitter = $this->user;
|
||||
$file->comment = $comment;
|
||||
$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;
|
||||
}
|
||||
|
||||
|
@ -31,6 +31,15 @@ class IDF_Form_IssueTrackingConf extends Pluf_Form
|
||||
* Defined as constants to easily access the value in the
|
||||
* 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
|
||||
Accepted = Problem reproduced / Need acknowledged
|
||||
Started = Work on this issue has begun';
|
||||
@ -66,6 +75,15 @@ Maintainability = Hinders future changes';
|
||||
|
||||
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(
|
||||
array('required' => true,
|
||||
'label' => __('Open issue status values'),
|
||||
@ -87,6 +105,7 @@ Maintainability = Hinders future changes';
|
||||
array('required' => true,
|
||||
'label' => __('Predefined issue labels'),
|
||||
'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,
|
||||
'cols' => 75),
|
||||
'widget' => 'Pluf_Form_Widget_TextareaInput',
|
||||
@ -99,8 +118,6 @@ Maintainability = Hinders future changes';
|
||||
'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->setAssoc($this->user); // interested user.
|
||||
}
|
||||
$attached_files = array();
|
||||
for ($i=1;$i<4;$i++) {
|
||||
if ($this->cleaned_data['attachment'.$i]) {
|
||||
$file = new IDF_IssueFile();
|
||||
@ -312,8 +313,36 @@ class IDF_Form_IssueUpdate extends IDF_Form_IssueCreate
|
||||
$file->submitter = $this->user;
|
||||
$file->comment = $comment;
|
||||
$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;
|
||||
}
|
||||
}
|
||||
|
@ -101,7 +101,7 @@ class IDF_Form_MembersConf extends Pluf_Form
|
||||
$n = count($bad);
|
||||
if ($n) {
|
||||
$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;
|
||||
}
|
||||
|
@ -205,6 +205,30 @@ class IDF_Form_ReviewCreate extends Pluf_Form
|
||||
$patch->patch = $this->cleaned_data['patch'];
|
||||
$patch->create();
|
||||
$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;
|
||||
}
|
||||
|
||||
|
@ -52,7 +52,7 @@ class IDF_Form_ReviewFileComment extends Pluf_Form
|
||||
));
|
||||
}
|
||||
$this->fields['content'] = new Pluf_Form_Field_Varchar(
|
||||
array('required' => true,
|
||||
array('required' => false,
|
||||
'label' => __('General comment'),
|
||||
'initial' => '',
|
||||
'widget' => 'Pluf_Form_Widget_TextareaInput',
|
||||
@ -94,24 +94,40 @@ class IDF_Form_ReviewFileComment extends Pluf_Form
|
||||
*/
|
||||
public function clean()
|
||||
{
|
||||
foreach ($this->files as $filename => $def) {
|
||||
if (!empty($this->cleaned_data[md5($filename)])) {
|
||||
return $this->cleaned_data;
|
||||
$isOk = false;
|
||||
|
||||
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()
|
||||
{
|
||||
$content = trim($this->cleaned_data['content']);
|
||||
if (!$this->show_full and strlen($content) == 0) {
|
||||
throw new Pluf_Form_Invalid(__('You need to provide your general comment about the proposal.'));
|
||||
if(empty($content)) {
|
||||
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.
|
||||
*
|
||||
|
@ -34,7 +34,7 @@ class IDF_Form_SourceConf extends Pluf_Form
|
||||
public function initFields($extra=array())
|
||||
{
|
||||
$this->conf = $extra['conf'];
|
||||
if ($this->conf->getVal('scm', 'git') == 'svn') {
|
||||
if ($extra['remote_svn']) {
|
||||
$this->fields['svn_username'] = new Pluf_Form_Field_Varchar(
|
||||
array('required' => false,
|
||||
'label' => __('Repository username'),
|
||||
@ -49,6 +49,16 @@ class IDF_Form_SourceConf extends Pluf_Form
|
||||
'widget' => 'Pluf_Form_Widget_PasswordInput',
|
||||
));
|
||||
}
|
||||
Pluf::loadFunction('Pluf_HTTP_URL_urlForView');
|
||||
$url = Pluf_HTTP_URL_urlForView('idf_faq').'#webhooks';
|
||||
$this->fields['webhook_url'] = new Pluf_Form_Field_Url(
|
||||
array('required' => false,
|
||||
'label' => __('Webhook URL'),
|
||||
'initial' => $this->conf->getVal('webhook_url', ''),
|
||||
'help_text' => sprintf(__('Learn more about the <a href="%s">post-commit web hooks</a>.'), $url),
|
||||
'widget_attrs' => array('size' => 35),
|
||||
));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -45,7 +45,7 @@ class IDF_Form_TabsConf extends Pluf_Form
|
||||
array('required' => true,
|
||||
'label' => $label,
|
||||
'initial' => $this->conf->getVal($key, 'all'),
|
||||
'widget_attrs' => array('choices' =>
|
||||
'widget_attrs' => array('choices' =>
|
||||
array(
|
||||
__('Open to all') => 'all',
|
||||
__('Signed in users') => 'login',
|
||||
@ -63,10 +63,11 @@ class IDF_Form_TabsConf extends Pluf_Form
|
||||
'source_notification_email',
|
||||
'issues_notification_email',);
|
||||
foreach ($ak as $key) {
|
||||
$this->fields[$key] = new Pluf_Form_Field_Email(
|
||||
$this->fields[$key] = new IDF_Form_Field_EmailList(
|
||||
array('required' => false,
|
||||
'label' => $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->update();
|
||||
$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;
|
||||
}
|
||||
}
|
||||
|
@ -176,6 +176,28 @@ class IDF_Form_Upload extends Pluf_Form
|
||||
}
|
||||
// Send the notification
|
||||
$upload->notify($this->project->getConf());
|
||||
/**
|
||||
* [signal]
|
||||
*
|
||||
* IDF_Upload::create
|
||||
*
|
||||
* [sender]
|
||||
*
|
||||
* IDF_Form_Upload
|
||||
*
|
||||
* [description]
|
||||
*
|
||||
* This signal allows an application to perform a set of tasks
|
||||
* just after the upload of a file and after the notification run.
|
||||
*
|
||||
* [parameters]
|
||||
*
|
||||
* array('upload' => $upload);
|
||||
*
|
||||
*/
|
||||
$params = array('upload' => $upload);
|
||||
Pluf_Signal::send('IDF_Upload::create', 'IDF_Form_Upload',
|
||||
$params);
|
||||
return $upload;
|
||||
}
|
||||
}
|
||||
|
@ -33,6 +33,8 @@ class IDF_Form_UserAccount extends Pluf_Form
|
||||
public function initFields($extra=array())
|
||||
{
|
||||
$this->user = $extra['user'];
|
||||
$user_data = IDF_UserData::factory($this->user);
|
||||
|
||||
$this->fields['first_name'] = new Pluf_Form_Field_Varchar(
|
||||
array('required' => false,
|
||||
'label' => __('First name'),
|
||||
@ -65,7 +67,7 @@ class IDF_Form_UserAccount extends Pluf_Form
|
||||
'initial' => $this->user->language,
|
||||
'widget' => 'Pluf_Form_Widget_SelectInput',
|
||||
'widget_attrs' => array(
|
||||
'choices' =>
|
||||
'choices' =>
|
||||
Pluf_L10n::getInstalledLanguages()
|
||||
),
|
||||
));
|
||||
@ -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,
|
||||
'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' => '',
|
||||
'widget_attrs' => array('rows' => 3,
|
||||
'cols' => 40),
|
||||
'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,
|
||||
'user'=> $this->user,
|
||||
)
|
||||
);
|
||||
);
|
||||
$tmpl = new Pluf_Template('idf/user/changeemail-email.txt');
|
||||
$text_email = $tmpl->render($context);
|
||||
$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);
|
||||
// Add key as needed.
|
||||
if ('' !== $this->cleaned_data['ssh_key']) {
|
||||
if ('' !== $this->cleaned_data['public_key']) {
|
||||
$key = new IDF_Key();
|
||||
$key->user = $this->user;
|
||||
$key->content = $this->cleaned_data['ssh_key'];
|
||||
$key->content = $this->cleaned_data['public_key'];
|
||||
if ($commit) {
|
||||
$key->create();
|
||||
}
|
||||
}
|
||||
|
||||
if ($commit) {
|
||||
$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) {
|
||||
/**
|
||||
* [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
|
||||
* 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)
|
||||
* @return string The clean key
|
||||
*/
|
||||
public static function checkSshKey($key, $user=0)
|
||||
public static function checkPublicKey($key, $user=0)
|
||||
{
|
||||
$key = trim($key);
|
||||
if (strlen($key) == 0) {
|
||||
return '';
|
||||
}
|
||||
$key = str_replace(array("\n", "\r"), '', $key);
|
||||
if (!preg_match('#^ssh\-[a-z]{3}\s(\S+)\s\S+$#', $key, $matches)) {
|
||||
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.'));
|
||||
}
|
||||
if (Pluf::f('idf_strong_key_check', false)) {
|
||||
$tmpfile = Pluf::f('tmp_folder', '/tmp').'/'.$user.'-key';
|
||||
file_put_contents($tmpfile, $key, LOCK_EX);
|
||||
$cmd = Pluf::f('idf_exec_cmd_prefix', '').
|
||||
'ssh-keygen -l -f '.escapeshellarg($tmpfile).' > /dev/null 2>&1';
|
||||
exec($cmd, $out, $return);
|
||||
unlink($tmpfile);
|
||||
if ($return != 0) {
|
||||
throw new Pluf_Form_Invalid(__('Please check the key as it does not appears to be a valid key.'));
|
||||
|
||||
if (preg_match('#^ssh\-[a-z]{3}\s\S+(\s\S+)?$#', $key)) {
|
||||
$key = str_replace(array("\n", "\r"), '', $key);
|
||||
|
||||
if (Pluf::f('idf_strong_key_check', false)) {
|
||||
|
||||
$tmpfile = Pluf::f('tmp_folder', '/tmp').'/'.$user.'-key';
|
||||
file_put_contents($tmpfile, $key, LOCK_EX);
|
||||
$cmd = Pluf::f('idf_exec_cmd_prefix', '').
|
||||
'ssh-keygen -l -f '.escapeshellarg($tmpfile).' > /dev/null 2>&1';
|
||||
exec($cmd, $out, $return);
|
||||
unlink($tmpfile);
|
||||
|
||||
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) {
|
||||
$ruser = Pluf::factory('Pluf_User', $user);
|
||||
@ -227,24 +348,33 @@ class IDF_Form_UserAccount extends Pluf_Form
|
||||
$sql = new Pluf_SQL('content=%s', array($key));
|
||||
$keys = Pluf::factory('IDF_Key')->getList(array('filter' => $sql->gen()));
|
||||
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;
|
||||
}
|
||||
|
||||
function clean_ssh_key()
|
||||
|
||||
function clean_custom_avatar()
|
||||
{
|
||||
return self::checkSshKey($this->cleaned_data['ssh_key'],
|
||||
$this->user->id);
|
||||
// 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'];
|
||||
}
|
||||
|
||||
|
||||
function clean_last_name()
|
||||
{
|
||||
$last_name = trim($this->cleaned_data['last_name']);
|
||||
if ($last_name == mb_strtoupper($last_name)) {
|
||||
return mb_convert_case(mb_strtolower($last_name),
|
||||
return mb_convert_case(mb_strtolower($last_name),
|
||||
MB_CASE_TITLE, 'UTF-8');
|
||||
}
|
||||
return $last_name;
|
||||
@ -254,7 +384,7 @@ class IDF_Form_UserAccount extends Pluf_Form
|
||||
{
|
||||
$first_name = trim($this->cleaned_data['first_name']);
|
||||
if ($first_name == mb_strtoupper($first_name)) {
|
||||
return mb_convert_case(mb_strtolower($first_name),
|
||||
return mb_convert_case(mb_strtolower($first_name),
|
||||
MB_CASE_TITLE, 'UTF-8');
|
||||
}
|
||||
return $first_name;
|
||||
@ -264,7 +394,7 @@ class IDF_Form_UserAccount extends Pluf_Form
|
||||
{
|
||||
$this->cleaned_data['email'] = mb_strtolower(trim($this->cleaned_data['email']));
|
||||
$guser = new Pluf_User();
|
||||
$sql = new Pluf_SQL('email=%s AND id!=%s',
|
||||
$sql = new Pluf_SQL('email=%s AND id!=%s',
|
||||
array($this->cleaned_data['email'], $this->user->id));
|
||||
if ($guser->getCount(array('filter' => $sql->gen())) > 0) {
|
||||
throw new Pluf_Form_Invalid(sprintf(__('The email "%s" is already used.'), $this->cleaned_data['email']));
|
||||
@ -272,12 +402,20 @@ class IDF_Form_UserAccount extends Pluf_Form
|
||||
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()
|
||||
{
|
||||
if (!isset($this->errors['password'])
|
||||
if (!isset($this->errors['password'])
|
||||
&& !isset($this->errors['password2'])) {
|
||||
$password1 = $this->cleaned_data['password'];
|
||||
$password2 = $this->cleaned_data['password2'];
|
||||
@ -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.'));
|
||||
}
|
||||
}
|
||||
|
||||
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']);
|
||||
}
|
||||
}
|
@ -42,9 +42,9 @@ class IDF_Issue extends Pluf_Model
|
||||
'id' =>
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Sequence',
|
||||
'blank' => true,
|
||||
'blank' => true,
|
||||
),
|
||||
'project' =>
|
||||
'project' =>
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Foreignkey',
|
||||
'model' => 'IDF_Project',
|
||||
@ -59,7 +59,7 @@ class IDF_Issue extends Pluf_Model
|
||||
'size' => 250,
|
||||
'verbose' => __('summary'),
|
||||
),
|
||||
'submitter' =>
|
||||
'submitter' =>
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Foreignkey',
|
||||
'model' => 'Pluf_User',
|
||||
@ -67,7 +67,7 @@ class IDF_Issue extends Pluf_Model
|
||||
'verbose' => __('submitter'),
|
||||
'relate_name' => 'submitted_issue',
|
||||
),
|
||||
'owner' =>
|
||||
'owner' =>
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Foreignkey',
|
||||
'model' => 'Pluf_User',
|
||||
@ -76,7 +76,7 @@ class IDF_Issue extends Pluf_Model
|
||||
'verbose' => __('owner'),
|
||||
'relate_name' => 'owned_issue',
|
||||
),
|
||||
'interested' =>
|
||||
'interested' =>
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Manytomany',
|
||||
'model' => 'Pluf_User',
|
||||
@ -86,14 +86,14 @@ class IDF_Issue extends Pluf_Model
|
||||
),
|
||||
'tags' =>
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Manytomany',
|
||||
'type' => 'Pluf_DB_Field_Manytomany',
|
||||
'blank' => true,
|
||||
'model' => 'IDF_Tag',
|
||||
'verbose' => __('labels'),
|
||||
),
|
||||
'status' =>
|
||||
'status' =>
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Foreignkey',
|
||||
'type' => 'Pluf_DB_Field_Foreignkey',
|
||||
'blank' => false,
|
||||
'model' => 'IDF_Tag',
|
||||
'verbose' => __('status'),
|
||||
@ -111,7 +111,7 @@ class IDF_Issue extends Pluf_Model
|
||||
'verbose' => __('modification date'),
|
||||
),
|
||||
);
|
||||
$this->_a['idx'] = array(
|
||||
$this->_a['idx'] = array(
|
||||
'modif_dtime_idx' =>
|
||||
array(
|
||||
'col' => 'modif_dtime',
|
||||
@ -120,7 +120,7 @@ class IDF_Issue extends Pluf_Model
|
||||
);
|
||||
$table = $this->_con->pfx.'idf_issue_idf_tag_assoc';
|
||||
$this->_a['views'] = array(
|
||||
'join_tags' =>
|
||||
'join_tags' =>
|
||||
array(
|
||||
'join' => 'LEFT JOIN '.$table
|
||||
.' ON idf_issue_id=id',
|
||||
@ -164,7 +164,7 @@ class IDF_Issue extends Pluf_Model
|
||||
// that the issue as at least one comment in the database when
|
||||
// doing the indexing.
|
||||
if ($create) {
|
||||
IDF_Timeline::insert($this, $this->get_project(),
|
||||
IDF_Timeline::insert($this, $this->get_project(),
|
||||
$this->get_submitter());
|
||||
}
|
||||
}
|
||||
@ -177,12 +177,12 @@ class IDF_Issue extends Pluf_Model
|
||||
* as such create links to other items etc. You can consider that
|
||||
* if displayed, you can create a link to it.
|
||||
*
|
||||
* @param Pluf_HTTP_Request
|
||||
* @param Pluf_HTTP_Request
|
||||
* @return Pluf_Template_SafeString
|
||||
*/
|
||||
public function timelineFragment($request)
|
||||
{
|
||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Issue::view',
|
||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Issue::view',
|
||||
array($request->project->shortname,
|
||||
$this->id));
|
||||
$out = '<tr class="log"><td><a href="'.$url.'">'.
|
||||
@ -193,14 +193,14 @@ class IDF_Issue extends Pluf_Model
|
||||
$ic = (in_array($this->status, $request->project->getTagIdsByStatus('closed'))) ? 'issue-c' : 'issue-o';
|
||||
$out .= sprintf(__('<a href="%1$s" class="%2$s" title="View issue">Issue %3$d</a>, %4$s'), $url, $ic, $this->id, Pluf_esc($this->summary)).'</td>';
|
||||
$out .= "\n".'<tr class="extra"><td colspan="2">
|
||||
<div class="helptext right">'.sprintf(__('Creation of <a href="%s" class="%s">issue %d</a>, by %s'), $url, $ic, $this->id, $user).'</div></td></tr>';
|
||||
<div class="helptext right">'.sprintf(__('Creation of <a href="%s" class="%s">issue %d</a>, by %s'), $url, $ic, $this->id, $user).'</div></td></tr>';
|
||||
return Pluf_Template::markSafe($out);
|
||||
}
|
||||
|
||||
public function feedFragment($request)
|
||||
{
|
||||
$url = Pluf::f('url_base')
|
||||
.Pluf_HTTP_URL_urlForView('IDF_Views_Issue::view',
|
||||
.Pluf_HTTP_URL_urlForView('IDF_Views_Issue::view',
|
||||
array($request->project->shortname,
|
||||
$this->id));
|
||||
$title = sprintf(__('%s: Issue %d created - %s'),
|
||||
@ -241,15 +241,17 @@ class IDF_Issue extends Pluf_Model
|
||||
$prj = $this->get_project();
|
||||
$to_email = array();
|
||||
if ('' != $conf->getVal('issues_notification_email', '')) {
|
||||
$langs = Pluf::f('languages', array('en'));
|
||||
$to_email[] = array($conf->getVal('issues_notification_email'),
|
||||
$langs[0]);
|
||||
$langs = Pluf::f('languages', array('en'));
|
||||
$addresses = explode(',', $conf->getVal('issues_notification_email'));
|
||||
foreach ($addresses as $address) {
|
||||
$to_email[] = array($address, $langs[0]);
|
||||
}
|
||||
}
|
||||
$current_locale = Pluf_Translation::getLocale();
|
||||
$id = '<'.md5($this->id.md5(Pluf::f('secret_key'))).'@'.Pluf::f('mail_host', 'localhost').'>';
|
||||
if ($create) {
|
||||
if (null != $this->get_owner() and $this->owner != $this->submitter) {
|
||||
$email_lang = array($this->get_owner()->email,
|
||||
if (null != $this->get_owner() and $this->owner != $this->submitter) {
|
||||
$email_lang = array($this->get_owner()->email,
|
||||
$this->get_owner()->language);
|
||||
if (!in_array($email_lang, $to_email)) {
|
||||
$to_email[] = $email_lang;
|
||||
|
@ -41,9 +41,9 @@ class IDF_IssueComment extends Pluf_Model
|
||||
'id' =>
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Sequence',
|
||||
'blank' => true,
|
||||
'blank' => true,
|
||||
),
|
||||
'issue' =>
|
||||
'issue' =>
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Foreignkey',
|
||||
'model' => 'IDF_Issue',
|
||||
@ -57,7 +57,7 @@ class IDF_IssueComment extends Pluf_Model
|
||||
'blank' => false,
|
||||
'verbose' => __('comment'),
|
||||
),
|
||||
'submitter' =>
|
||||
'submitter' =>
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Foreignkey',
|
||||
'model' => 'Pluf_User',
|
||||
@ -79,7 +79,7 @@ class IDF_IssueComment extends Pluf_Model
|
||||
'verbose' => __('creation date'),
|
||||
),
|
||||
);
|
||||
$this->_a['idx'] = array(
|
||||
$this->_a['idx'] = array(
|
||||
'creation_dtime_idx' =>
|
||||
array(
|
||||
'col' => 'creation_dtime',
|
||||
@ -119,7 +119,7 @@ class IDF_IssueComment extends Pluf_Model
|
||||
$sql = new Pluf_SQL('issue=%s', array($this->issue));
|
||||
$co = Pluf::factory('IDF_IssueComment')->getList(array('filter'=>$sql->gen()));
|
||||
if ($co->count() > 1) {
|
||||
IDF_Timeline::insert($this, $this->get_issue()->get_project(),
|
||||
IDF_Timeline::insert($this, $this->get_issue()->get_project(),
|
||||
$this->get_submitter());
|
||||
}
|
||||
}
|
||||
@ -129,7 +129,7 @@ class IDF_IssueComment extends Pluf_Model
|
||||
public function timelineFragment($request)
|
||||
{
|
||||
$issue = $this->get_issue();
|
||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Issue::view',
|
||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Issue::view',
|
||||
array($request->project->shortname,
|
||||
$issue->id));
|
||||
$url .= '#ic'.$this->id;
|
||||
@ -168,7 +168,7 @@ class IDF_IssueComment extends Pluf_Model
|
||||
}
|
||||
$out .= '</td></tr>';
|
||||
$out .= "\n".'<tr class="extra"><td colspan="2">
|
||||
<div class="helptext right">'.sprintf(__('Comment on <a href="%s" class="%s">issue %d</a>, by %s'), $url, $ic, $issue->id, $user).'</div></td></tr>';
|
||||
<div class="helptext right">'.sprintf(__('Comment on <a href="%s" class="%s">issue %d</a>, by %s'), $url, $ic, $issue->id, $user).'</div></td></tr>';
|
||||
return Pluf_Template::markSafe($out);
|
||||
}
|
||||
|
||||
@ -176,7 +176,7 @@ class IDF_IssueComment extends Pluf_Model
|
||||
{
|
||||
$issue = $this->get_issue();
|
||||
$url = Pluf::f('url_base')
|
||||
.Pluf_HTTP_URL_urlForView('IDF_Views_Issue::view',
|
||||
.Pluf_HTTP_URL_urlForView('IDF_Views_Issue::view',
|
||||
array($request->project->shortname,
|
||||
$issue->id));
|
||||
$title = sprintf(__('%s: Comment on issue %d - %s'),
|
||||
@ -196,4 +196,9 @@ class IDF_IssueComment extends Pluf_Model
|
||||
$tmpl = new Pluf_Template('idf/issues/feedfragment.xml');
|
||||
return $tmpl->render($context);
|
||||
}
|
||||
|
||||
public function get_submitter_data()
|
||||
{
|
||||
return IDF_UserData::factory($this->get_submitter());
|
||||
}
|
||||
}
|
||||
|
@ -39,9 +39,9 @@ class IDF_IssueFile extends Pluf_Model
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Sequence',
|
||||
//It is automatically added.
|
||||
'blank' => true,
|
||||
'blank' => true,
|
||||
),
|
||||
'comment' =>
|
||||
'comment' =>
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Foreignkey',
|
||||
'model' => 'IDF_IssueComment',
|
||||
@ -49,7 +49,7 @@ class IDF_IssueFile extends Pluf_Model
|
||||
'verbose' => __('comment'),
|
||||
'relate_name' => 'attachment',
|
||||
),
|
||||
'submitter' =>
|
||||
'submitter' =>
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Foreignkey',
|
||||
'model' => 'Pluf_User',
|
||||
@ -63,7 +63,7 @@ class IDF_IssueFile extends Pluf_Model
|
||||
'size' => 100,
|
||||
'verbose' => __('file name'),
|
||||
),
|
||||
'attachment' =>
|
||||
'attachment' =>
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_File',
|
||||
'blank' => false,
|
||||
@ -76,7 +76,7 @@ class IDF_IssueFile extends Pluf_Model
|
||||
'verbose' => __('file size'),
|
||||
'help_text' => 'Size in bytes.',
|
||||
),
|
||||
'type' =>
|
||||
'type' =>
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Varchar',
|
||||
'blank' => false,
|
||||
@ -111,7 +111,7 @@ class IDF_IssueFile extends Pluf_Model
|
||||
$file = Pluf::f('upload_issue_path').'/'.$this->attachment;
|
||||
$this->filesize = filesize($file);
|
||||
// remove .dummy
|
||||
$this->filename = substr(basename($file), 0, -6);
|
||||
$this->filename = substr(basename($file), 0, -6);
|
||||
$img_extensions = array('jpeg', 'jpg', 'png', 'gif');
|
||||
$info = pathinfo($this->filename);
|
||||
if (!isset($info['extension'])) $info['extension'] = '';
|
||||
@ -128,4 +128,10 @@ class IDF_IssueFile extends Pluf_Model
|
||||
{
|
||||
@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 ***** */
|
||||
|
||||
/**
|
||||
* Storage of the SSH keys.
|
||||
* Storage of the public keys (ssh or monotone).
|
||||
*
|
||||
*/
|
||||
class IDF_Key extends Pluf_Model
|
||||
@ -39,9 +39,9 @@ class IDF_Key extends Pluf_Model
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Sequence',
|
||||
//It is automatically added.
|
||||
'blank' => true,
|
||||
'blank' => true,
|
||||
),
|
||||
'user' =>
|
||||
'user' =>
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Foreignkey',
|
||||
'model' => 'Pluf_User',
|
||||
@ -52,14 +52,14 @@ class IDF_Key extends Pluf_Model
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Text',
|
||||
'blank' => false,
|
||||
'verbose' => __('ssh key'),
|
||||
'verbose' => __('public key'),
|
||||
),
|
||||
);
|
||||
// WARNING: Not using getSqlTable on the Pluf_User object to
|
||||
// avoid recursion.
|
||||
$t_users = $this->_con->pfx.'users';
|
||||
$t_users = $this->_con->pfx.'users';
|
||||
$this->_a['views'] = array(
|
||||
'join_user' =>
|
||||
'join_user' =>
|
||||
array(
|
||||
'join' => 'LEFT JOIN '.$t_users
|
||||
.' ON '.$t_users.'.id='.$this->_con->qn('user'),
|
||||
@ -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)));
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
/**
|
||||
@ -89,7 +141,7 @@ class IDF_Key extends Pluf_Model
|
||||
* [description]
|
||||
*
|
||||
* 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]
|
||||
*
|
||||
@ -127,5 +179,4 @@ class IDF_Key extends Pluf_Model
|
||||
Pluf_Signal::send('IDF_Key::preDelete',
|
||||
'IDF_Key', $params);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -92,6 +92,7 @@ class IDF_Middleware
|
||||
array(
|
||||
'size' => 'IDF_Views_Source_PrettySize',
|
||||
'ssize' => 'IDF_Views_Source_PrettySizeSimple',
|
||||
'shorten' => 'IDF_Views_Source_ShortenString',
|
||||
));
|
||||
}
|
||||
}
|
||||
@ -104,12 +105,14 @@ function IDF_Middleware_ContextPreProcessor($request)
|
||||
$c['isAdmin'] = ($request->user->administrator or $request->user->staff);
|
||||
if (isset($request->project)) {
|
||||
$c['project'] = $request->project;
|
||||
$c['isOwner'] = $request->user->hasPerm('IDF.project-owner',
|
||||
$c['isOwner'] = $request->user->hasPerm('IDF.project-owner',
|
||||
$request->project);
|
||||
$c['isMember'] = $request->user->hasPerm('IDF.project-member',
|
||||
$c['isMember'] = $request->user->hasPerm('IDF.project-member',
|
||||
$request->project);
|
||||
$c = array_merge($c, $request->rights);
|
||||
}
|
||||
$c['usherConfigured'] = Pluf::f("mtn_usher_conf", null) !== null;
|
||||
$c['allProjects'] = IDF_Views::getProjects($request->user);
|
||||
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_Key',
|
||||
'IDF_Scm_Cache_Git',
|
||||
'IDF_Queue',
|
||||
'IDF_Gconf',
|
||||
);
|
||||
$db = Pluf::db();
|
||||
// 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_Key',
|
||||
'IDF_Scm_Cache_Git',
|
||||
'IDF_Queue',
|
||||
'IDF_Gconf',
|
||||
);
|
||||
$db = Pluf::db();
|
||||
$schema = new Pluf_DB_Schema($db);
|
||||
|
@ -48,6 +48,8 @@ function IDF_Migrations_Install_setup($params=null)
|
||||
'IDF_Review_FileComment',
|
||||
'IDF_Key',
|
||||
'IDF_Scm_Cache_Git',
|
||||
'IDF_Queue',
|
||||
'IDF_Gconf',
|
||||
);
|
||||
$db = Pluf::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');
|
||||
if ($perm) $perm->delete();
|
||||
$models = array(
|
||||
'IDF_Gconf',
|
||||
'IDF_Queue',
|
||||
'IDF_Scm_Cache_Git',
|
||||
'IDF_Key',
|
||||
'IDF_Review_FileComment',
|
||||
|
@ -48,13 +48,18 @@ class IDF_Plugin_SyncGit_Cron
|
||||
$out = '';
|
||||
$keys = Pluf::factory('IDF_Key')->getList(array('view'=>'join_user'));
|
||||
foreach ($keys as $key) {
|
||||
if (strlen($key->content) > 40 // minimal check
|
||||
and preg_match('/^[a-zA-Z][a-zA-Z0-9_.-]*(@[a-zA-Z][a-zA-Z0-9.-]*)?$/', $key->login)) {
|
||||
try {
|
||||
$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));
|
||||
$out .= sprintf($template, $cmd, $key->login, $content)."\n";
|
||||
}
|
||||
}
|
||||
file_put_contents($authorized_keys, $out, LOCK_EX);
|
||||
file_put_contents($authorized_keys, $out, LOCK_EX);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -99,6 +104,7 @@ class IDF_Plugin_SyncGit_Cron
|
||||
if (count($orphans)) {
|
||||
$cmd = Pluf::f('idf_exec_cmd_prefix', '').'rm -rf '.implode(' ', $orphans);
|
||||
exec($cmd);
|
||||
clearstatcache();
|
||||
while (list(, $project) = each($orphans)) {
|
||||
if (is_dir($project)) {
|
||||
throw new Exception(sprintf('Cannot remove %s directory.', $project));
|
||||
|
@ -196,6 +196,8 @@ class IDF_Plugin_SyncGit_Serve
|
||||
if (!file_exists($fullpath)) {
|
||||
mkdir($fullpath, 0750, true);
|
||||
}
|
||||
$out = array();
|
||||
$res = 0;
|
||||
exec(sprintf(Pluf::f('idf_exec_cmd_prefix', '').
|
||||
Pluf::f('git_path', 'git').' --git-dir=%s init', escapeshellarg($fullpath)),
|
||||
$out, $res);
|
||||
@ -214,6 +216,8 @@ class IDF_Plugin_SyncGit_Serve
|
||||
$fullpath.'/hooks/post-update'));
|
||||
return;
|
||||
}
|
||||
$out = array();
|
||||
$res = 0;
|
||||
exec(sprintf(Pluf::f('idf_exec_cmd_prefix', '').'ln -s %s %s',
|
||||
escapeshellarg($p),
|
||||
escapeshellarg($fullpath.'/hooks/post-update')),
|
||||
@ -226,6 +230,24 @@ class IDF_Plugin_SyncGit_Serve
|
||||
}
|
||||
Pluf_Log::debug(array('IDF_Plugin_Git_Serve::initRepository',
|
||||
'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));
|
||||
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
|
||||
$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);
|
||||
}
|
||||
|
||||
|
@ -39,7 +39,7 @@ class IDF_Project extends Pluf_Model
|
||||
*
|
||||
* @see self::isRestricted
|
||||
*/
|
||||
protected $_isRestricted = null;
|
||||
protected $_isRestricted = null;
|
||||
|
||||
function init()
|
||||
{
|
||||
@ -52,7 +52,7 @@ class IDF_Project extends Pluf_Model
|
||||
'id' =>
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Sequence',
|
||||
'blank' => true,
|
||||
'blank' => true,
|
||||
),
|
||||
'name' =>
|
||||
array(
|
||||
@ -113,7 +113,7 @@ class IDF_Project extends Pluf_Model
|
||||
return '';
|
||||
}
|
||||
|
||||
|
||||
|
||||
function preSave($create=false)
|
||||
{
|
||||
if ($this->id == '') {
|
||||
@ -181,7 +181,7 @@ class IDF_Project extends Pluf_Model
|
||||
*/
|
||||
public function getTagIdsByStatus($status='open', $cache_refresh=false)
|
||||
{
|
||||
if (!$cache_refresh
|
||||
if (!$cache_refresh
|
||||
and isset($this->_extra_cache['getTagIdsByStatus-'.$status])) {
|
||||
return $this->_extra_cache['getTagIdsByStatus-'.$status];
|
||||
}
|
||||
@ -197,7 +197,7 @@ class IDF_Project extends Pluf_Model
|
||||
break;
|
||||
}
|
||||
$tags = array();
|
||||
foreach ($this->getTagsFromConfig($key, $default, 'Status') as $tag) {
|
||||
foreach ($this->getTagsFromConfig($key, $default, 'Status') as $tag) {
|
||||
$tags[] = (int) $tag->id;
|
||||
}
|
||||
$this->_extra_cache['getTagIdsByStatus-'.$status] = $tags;
|
||||
@ -289,9 +289,9 @@ class IDF_Project extends Pluf_Model
|
||||
if ($fmt == 'objects') {
|
||||
return new Pluf_Template_ContextVars(array('members' => $members, 'owners' => $owners, 'authorized' => $authorized));
|
||||
} else {
|
||||
return array('members' => implode("\n", (array) $members),
|
||||
return array('members' => implode("\n", (array) $members),
|
||||
'owners' => implode("\n", (array) $owners),
|
||||
'authorized' => implode("\n", (array) $authorized),
|
||||
'authorized' => implode("\n", (array) $authorized),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -366,12 +366,13 @@ class IDF_Project extends Pluf_Model
|
||||
public function getRepositorySize($force=false)
|
||||
{
|
||||
$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);
|
||||
}
|
||||
$this->getConf()->setVal('repository_size_check_date', time());
|
||||
$scm = IDF_Scm::get($this);
|
||||
$this->getConf()->setVal('repository_size', $scm->getRepositorySize());
|
||||
$this->getConf()->setVal('repository_size_check_date', time());
|
||||
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.
|
||||
*
|
||||
* @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');
|
||||
if (($user == null or $user->isAnonymous())
|
||||
if (($user == null or $user->isAnonymous())
|
||||
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.
|
||||
*
|
||||
* 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();
|
||||
$scm = $conf->getVal('scm', 'git');
|
||||
$scms = Pluf::f('allowed_scm');
|
||||
Pluf::loadClass($scms[$scm]);
|
||||
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
|
||||
* same as the one to read. For example, you do a checkout with
|
||||
* 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();
|
||||
$scm = $conf->getVal('scm', 'git');
|
||||
$scms = Pluf::f('allowed_scm');
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -433,9 +450,10 @@ class IDF_Project extends Pluf_Model
|
||||
{
|
||||
$conf = $this->getConf();
|
||||
$roots = array(
|
||||
'git' => 'master',
|
||||
'svn' => 'HEAD',
|
||||
'mercurial' => 'tip'
|
||||
'git' => 'master',
|
||||
'svn' => 'HEAD',
|
||||
'mercurial' => 'tip',
|
||||
'mtn' => 'h:'.$conf->getVal('mtn_master_branch', '*'),
|
||||
);
|
||||
$scm = $conf->getVal('scm', 'git');
|
||||
return $roots[$scm];
|
||||
@ -448,7 +466,7 @@ class IDF_Project extends Pluf_Model
|
||||
* By convention, all the objects belonging to a project have the
|
||||
* 'project' property set, so this is easy to check.
|
||||
*
|
||||
* @param Pluf_Model
|
||||
* @param Pluf_Model
|
||||
*/
|
||||
public function inOr404($obj)
|
||||
{
|
||||
@ -505,7 +523,7 @@ class IDF_Project extends Pluf_Model
|
||||
*
|
||||
* [description]
|
||||
*
|
||||
* This signal allows an application to update the statistics
|
||||
* This signal allows an application to update the statistics
|
||||
* array of a project. For example to add the on disk size
|
||||
* of the repository if available.
|
||||
*
|
||||
@ -617,7 +635,7 @@ class IDF_Project extends Pluf_Model
|
||||
Pluf_Signal::send('IDF_Project::preDelete',
|
||||
'IDF_Project', $params);
|
||||
$what = array('IDF_Upload', 'IDF_Review', 'IDF_Issue',
|
||||
'IDF_WikiPage', 'IDF_Commit',
|
||||
'IDF_WikiPage', 'IDF_Commit', 'IDF_Tag',
|
||||
);
|
||||
foreach ($what as $m) {
|
||||
foreach (Pluf::factory($m)->getList(array('filter' => 'project='.(int)$this->id)) as $item) {
|
||||
@ -649,7 +667,7 @@ class IDF_Project extends Pluf_Model
|
||||
);
|
||||
$conf = $this->getConf();
|
||||
foreach ($tabs as $tab) {
|
||||
if (!in_array($conf->getVal($tab, 'all'),
|
||||
if (!in_array($conf->getVal($tab, 'all'),
|
||||
array('all', 'none'))) {
|
||||
$this->_isRestricted = true;
|
||||
return true;
|
||||
|
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();
|
||||
}
|
||||
}
|
||||
}
|
@ -25,12 +25,12 @@
|
||||
* A comment set on a review.
|
||||
*
|
||||
* A comment is associated to a patch as a review can have many
|
||||
* patches associated to it.
|
||||
* patches associated to it.
|
||||
*
|
||||
* A comment is also tracking the changes in the review in the same
|
||||
* way the issue comment is tracking the changes in the issue.
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
class IDF_Review_Comment extends Pluf_Model
|
||||
{
|
||||
@ -45,9 +45,9 @@ class IDF_Review_Comment extends Pluf_Model
|
||||
'id' =>
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Sequence',
|
||||
'blank' => true,
|
||||
'blank' => true,
|
||||
),
|
||||
'patch' =>
|
||||
'patch' =>
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Foreignkey',
|
||||
'model' => 'IDF_Review_Patch',
|
||||
@ -61,7 +61,7 @@ class IDF_Review_Comment extends Pluf_Model
|
||||
'blank' => true, // if only commented on lines
|
||||
'verbose' => __('comment'),
|
||||
),
|
||||
'submitter' =>
|
||||
'submitter' =>
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Foreignkey',
|
||||
'model' => 'Pluf_User',
|
||||
@ -118,8 +118,8 @@ class IDF_Review_Comment extends Pluf_Model
|
||||
function postSave($create=false)
|
||||
{
|
||||
if ($create) {
|
||||
IDF_Timeline::insert($this,
|
||||
$this->get_patch()->get_review()->get_project(),
|
||||
IDF_Timeline::insert($this,
|
||||
$this->get_patch()->get_review()->get_project(),
|
||||
$this->get_submitter());
|
||||
}
|
||||
}
|
||||
@ -127,7 +127,7 @@ class IDF_Review_Comment extends Pluf_Model
|
||||
public function timelineFragment($request)
|
||||
{
|
||||
$review = $this->get_patch()->get_review();
|
||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Review::view',
|
||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Review::view',
|
||||
array($request->project->shortname,
|
||||
$review->id));
|
||||
$out = '<tr class="log"><td><a href="'.$url.'">'.
|
||||
@ -138,14 +138,14 @@ class IDF_Review_Comment extends Pluf_Model
|
||||
$ic = (in_array($review->status, $request->project->getTagIdsByStatus('closed'))) ? 'issue-c' : 'issue-o';
|
||||
$out .= sprintf(__('<a href="%1$s" class="%2$s" title="View review">Review %3$d</a>, %4$s'), $url, $ic, $review->id, Pluf_esc($review->summary)).'</td>';
|
||||
$out .= "\n".'<tr class="extra"><td colspan="2">
|
||||
<div class="helptext right">'.sprintf(__('Update of <a href="%s" class="%s">review %d</a>, by %s'), $url, $ic, $review->id, $user).'</div></td></tr>';
|
||||
<div class="helptext right">'.sprintf(__('Update of <a href="%s" class="%s">review %d</a>, by %s'), $url, $ic, $review->id, $user).'</div></td></tr>';
|
||||
return Pluf_Template::markSafe($out);
|
||||
}
|
||||
|
||||
public function feedFragment($request)
|
||||
{
|
||||
$review = $this->get_patch()->get_review();
|
||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Review::view',
|
||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Review::view',
|
||||
array($request->project->shortname,
|
||||
$review->id));
|
||||
$title = sprintf(__('%s: Updated review %d - %s'),
|
||||
@ -221,4 +221,9 @@ class IDF_Review_Comment extends Pluf_Model
|
||||
}
|
||||
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');
|
||||
$text_email = $tmpl->render($context);
|
||||
$email = new Pluf_Mail(Pluf::f('from_email'),
|
||||
$conf->getVal('review_notification_email'),
|
||||
sprintf(__('New Code Review %s - %s (%s)'),
|
||||
$this->get_review()->id,
|
||||
$this->get_review()->summary,
|
||||
$this->get_review()->get_project()->shortname));
|
||||
$email->addTextMessage($text_email);
|
||||
$email->sendMail();
|
||||
$addresses = explode(';',$conf->getVal('review_notification_email'));
|
||||
foreach ($addresses as $address) {
|
||||
$email = new Pluf_Mail(Pluf::f('from_email'),
|
||||
$address,
|
||||
sprintf(__('New Code Review %s - %s (%s)'),
|
||||
$this->get_review()->id,
|
||||
$this->get_review()->summary,
|
||||
$this->get_review()->get_project()->shortname));
|
||||
$email->addTextMessage($text_email);
|
||||
$email->sendMail();
|
||||
}
|
||||
Pluf_Translation::loadSetLocale($current_locale);
|
||||
}
|
||||
}
|
||||
|
@ -63,7 +63,7 @@ class IDF_Scm
|
||||
public $project = null;
|
||||
|
||||
/**
|
||||
* Cache storage.
|
||||
* Cache storage.
|
||||
*
|
||||
* It must only be used to store data for the lifetime of the
|
||||
* object. For example if you need to get the list of branches in
|
||||
@ -166,13 +166,28 @@ class IDF_Scm
|
||||
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
|
||||
* @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();
|
||||
}
|
||||
@ -217,7 +232,7 @@ class IDF_Scm
|
||||
* 'foo-branch' => 'branches/foo-branch',)
|
||||
* </pre>
|
||||
*
|
||||
* @return array Branches
|
||||
* @return array Branches
|
||||
*/
|
||||
public function getBranches()
|
||||
{
|
||||
@ -282,7 +297,7 @@ class IDF_Scm
|
||||
* @param string Revision or commit
|
||||
* @param string Folder ('/')
|
||||
* @param string Branch (null)
|
||||
* @return array
|
||||
* @return array
|
||||
*/
|
||||
public function getTree($rev, $folder='/', $branch=null)
|
||||
{
|
||||
@ -301,6 +316,34 @@ class IDF_Scm
|
||||
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.
|
||||
*
|
||||
@ -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 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();
|
||||
}
|
||||
@ -396,7 +454,7 @@ class IDF_Scm
|
||||
public static function syncTimeline($project, $force=false)
|
||||
{
|
||||
$cache = Pluf_Cache::factory();
|
||||
$key = 'IDF_Scm:'.$project->shortname.':lastsync';
|
||||
$key = 'IDF_Scm:'.$project->shortname.':lastsync';
|
||||
if ($force or null === ($res=$cache->get($key))) {
|
||||
$scm = IDF_Scm::get($project);
|
||||
if ($scm->isAvailable()) {
|
||||
|
@ -55,7 +55,9 @@ class IDF_Scm_Cache_Git extends Pluf_Model
|
||||
$cache->project = $this->_project;
|
||||
$cache->githash = $blob->hash;
|
||||
$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',
|
||||
array($this->_project->id, $blob->hash));
|
||||
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));
|
||||
foreach (Pluf::factory(__CLASS__)->getList(array('filter' => $sql->gen())) as $blob) {
|
||||
$tmp = explode(chr(31), $blob->content, 3);
|
||||
// sometimes the title might be empty
|
||||
if (!isset($tmp[2])) $tmp[2] = '';
|
||||
|
||||
$res[$blob->githash] = (object) array(
|
||||
'hash' => $blob->githash,
|
||||
@ -131,4 +135,4 @@ class IDF_Scm_Cache_Git extends Pluf_Model
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -27,13 +27,13 @@
|
||||
*/
|
||||
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';
|
||||
|
||||
/* ============================================== *
|
||||
* *
|
||||
* Common Methods Implemented By All The SCMs *
|
||||
* *
|
||||
* ============================================== */
|
||||
* ============================================== */
|
||||
|
||||
public function __construct($repo, $project=null)
|
||||
{
|
||||
@ -48,7 +48,7 @@ class IDF_Scm_Git extends IDF_Scm
|
||||
}
|
||||
$cmd = Pluf::f('idf_exec_cmd_prefix', '').'du -sk '
|
||||
.escapeshellarg($this->repo);
|
||||
$out = explode(' ',
|
||||
$out = explode(' ',
|
||||
self::shell_exec('IDF_Scm_Git::getRepositorySize', $cmd),
|
||||
2);
|
||||
return (int) $out[0]*1024;
|
||||
@ -70,13 +70,13 @@ class IDF_Scm_Git extends IDF_Scm
|
||||
return $this->cache['branches'];
|
||||
}
|
||||
$cmd = Pluf::f('idf_exec_cmd_prefix', '')
|
||||
.sprintf('GIT_DIR=%s '.Pluf::f('git_path', 'git').' branch',
|
||||
.sprintf('GIT_DIR=%s '.Pluf::f('git_path', 'git').' branch',
|
||||
escapeshellarg($this->repo));
|
||||
self::exec('IDF_Scm_Git::getBranches',
|
||||
self::exec('IDF_Scm_Git::getBranches',
|
||||
$cmd, $out, $return);
|
||||
if ($return != 0) {
|
||||
throw new IDF_Scm_Exception(sprintf($this->error_tpl,
|
||||
$cmd, $return,
|
||||
$cmd, $return,
|
||||
implode("\n", $out)));
|
||||
}
|
||||
$res = array();
|
||||
@ -116,6 +116,14 @@ class IDF_Scm_Git extends IDF_Scm
|
||||
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()
|
||||
**/
|
||||
@ -125,7 +133,7 @@ class IDF_Scm_Git extends IDF_Scm
|
||||
return $this->cache['tags'];
|
||||
}
|
||||
$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),
|
||||
Pluf::f('git_path', 'git'));
|
||||
self::exec('IDF_Scm_Git::getTags', $cmd, $out, $return);
|
||||
@ -134,12 +142,15 @@ class IDF_Scm_Git extends IDF_Scm
|
||||
$cmd, $return,
|
||||
implode("\n", $out)));
|
||||
}
|
||||
rsort($out);
|
||||
$res = array();
|
||||
foreach ($out as $b) {
|
||||
if (false !== strpos($b, '/')) {
|
||||
$res[$this->getCommit($b)->commit] = $b;
|
||||
$elts = explode(' ', $b, 5);
|
||||
$tag = substr(trim($elts[4]), 10);
|
||||
if (false !== strpos($tag, '/')) {
|
||||
$res[$elts[3]] = $b;
|
||||
} else {
|
||||
$res[$b] = '';
|
||||
$res[$tag] = '';
|
||||
}
|
||||
}
|
||||
$this->cache['tags'] = $res;
|
||||
@ -216,7 +227,7 @@ class IDF_Scm_Git extends IDF_Scm
|
||||
if ($folder) {
|
||||
// As we are limiting to a given folder, we need to find
|
||||
// the tree corresponding to this folder.
|
||||
$tinfo = $this->getTreeInfo($commit, $folder);
|
||||
$tinfo = $this->getTreeInfo($commit, $folder);
|
||||
if (isset($tinfo[0]) and $tinfo[0]->type == 'tree') {
|
||||
$tree = $tinfo[0]->hash;
|
||||
} else {
|
||||
@ -231,7 +242,7 @@ class IDF_Scm_Git extends IDF_Scm
|
||||
// information as possible.
|
||||
if ($file->type == 'blob') {
|
||||
$file->date = $co->date;
|
||||
$file->log = '----';
|
||||
$file->log = '----';
|
||||
$file->author = 'Unknown';
|
||||
}
|
||||
$file->fullpath = ($folder) ? $folder.'/'.$file->file : $file->file;
|
||||
@ -270,13 +281,21 @@ class IDF_Scm_Git extends IDF_Scm
|
||||
return null;
|
||||
}
|
||||
|
||||
public static function getAnonymousAccessUrl($project)
|
||||
public static function getAnonymousAccessUrl($project, $commit=null)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
@ -293,10 +312,12 @@ class IDF_Scm_Git extends IDF_Scm
|
||||
}
|
||||
|
||||
|
||||
public function isValidRevision($commit)
|
||||
public function validateRevision($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;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -320,7 +341,7 @@ class IDF_Scm_Git extends IDF_Scm
|
||||
/**
|
||||
* Get the tree info.
|
||||
*
|
||||
* @param string Tree hash
|
||||
* @param string Tree hash
|
||||
* @param bool Do we recurse in subtrees (true)
|
||||
* @param string Folder in which we want to get the info ('')
|
||||
* @return array Array of file information.
|
||||
@ -332,15 +353,15 @@ class IDF_Scm_Git extends IDF_Scm
|
||||
}
|
||||
$cmd_tmpl = 'GIT_DIR=%s '.Pluf::f('git_path', 'git').' ls-tree -l %s %s';
|
||||
$cmd = Pluf::f('idf_exec_cmd_prefix', '')
|
||||
.sprintf($cmd_tmpl, escapeshellarg($this->repo),
|
||||
.sprintf($cmd_tmpl, escapeshellarg($this->repo),
|
||||
escapeshellarg($tree), escapeshellarg($folder));
|
||||
$out = array();
|
||||
$res = array();
|
||||
self::exec('IDF_Scm_Git::getTreeInfo', $cmd, $out);
|
||||
foreach ($out as $line) {
|
||||
list($perm, $type, $hash, $size, $file) = preg_split('/ |\t/', $line, 5, PREG_SPLIT_NO_EMPTY);
|
||||
$res[] = (object) array('perm' => $perm, 'type' => $type,
|
||||
'size' => $size, 'hash' => $hash,
|
||||
$res[] = (object) array('perm' => $perm, 'type' => $type,
|
||||
'size' => $size, 'hash' => $hash,
|
||||
'file' => $file);
|
||||
}
|
||||
return $res;
|
||||
@ -356,8 +377,8 @@ class IDF_Scm_Git extends IDF_Scm
|
||||
public function getPathInfo($totest, $commit='HEAD')
|
||||
{
|
||||
$cmd_tmpl = 'GIT_DIR=%s '.Pluf::f('git_path', 'git').' ls-tree -r -t -l %s';
|
||||
$cmd = sprintf($cmd_tmpl,
|
||||
escapeshellarg($this->repo),
|
||||
$cmd = sprintf($cmd_tmpl,
|
||||
escapeshellarg($this->repo),
|
||||
escapeshellarg($commit));
|
||||
$out = array();
|
||||
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
|
||||
@ -366,8 +387,8 @@ class IDF_Scm_Git extends IDF_Scm
|
||||
list($perm, $type, $hash, $size, $file) = preg_split('/ |\t/', $line, 5, PREG_SPLIT_NO_EMPTY);
|
||||
if ($totest == $file) {
|
||||
$pathinfo = pathinfo($file);
|
||||
return (object) array('perm' => $perm, 'type' => $type,
|
||||
'size' => $size, 'hash' => $hash,
|
||||
return (object) array('perm' => $perm, 'type' => $type,
|
||||
'size' => $size, 'hash' => $hash,
|
||||
'fullpath' => $file,
|
||||
'file' => $pathinfo['basename']);
|
||||
}
|
||||
@ -379,9 +400,9 @@ class IDF_Scm_Git extends IDF_Scm
|
||||
{
|
||||
$cmd = sprintf(Pluf::f('idf_exec_cmd_prefix', '').
|
||||
'GIT_DIR=%s '.Pluf::f('git_path', 'git').' cat-file blob %s',
|
||||
escapeshellarg($this->repo),
|
||||
escapeshellarg($this->repo),
|
||||
escapeshellarg($def->hash));
|
||||
return ($cmd_only)
|
||||
return ($cmd_only)
|
||||
? $cmd : self::shell_exec('IDF_Scm_Git::getFile', $cmd);
|
||||
}
|
||||
|
||||
@ -396,13 +417,13 @@ class IDF_Scm_Git extends IDF_Scm
|
||||
{
|
||||
if ($getdiff) {
|
||||
$cmd = sprintf('GIT_DIR=%s '.Pluf::f('git_path', 'git').' show --date=iso --pretty=format:%s %s',
|
||||
escapeshellarg($this->repo),
|
||||
"'".$this->mediumtree_fmt."'",
|
||||
escapeshellarg($this->repo),
|
||||
"'".$this->mediumtree_fmt."'",
|
||||
escapeshellarg($commit));
|
||||
} else {
|
||||
$cmd = sprintf('GIT_DIR=%s '.Pluf::f('git_path', 'git').' log -1 --date=iso --pretty=format:%s %s',
|
||||
escapeshellarg($this->repo),
|
||||
"'".$this->mediumtree_fmt."'",
|
||||
escapeshellarg($this->repo),
|
||||
"'".$this->mediumtree_fmt."'",
|
||||
escapeshellarg($commit));
|
||||
}
|
||||
$out = array();
|
||||
@ -426,11 +447,13 @@ class IDF_Scm_Git extends IDF_Scm
|
||||
}
|
||||
}
|
||||
$out = self::parseLog($log);
|
||||
$out[0]->changes = implode("\n", $change);
|
||||
$out[0]->diff = implode("\n", $change);
|
||||
} else {
|
||||
$out = self::parseLog($out);
|
||||
$out[0]->changes = '';
|
||||
$out[0]->diff = '';
|
||||
}
|
||||
|
||||
$out[0]->branch = implode(', ', $this->inBranches($commit, null));
|
||||
return $out[0];
|
||||
}
|
||||
|
||||
@ -443,8 +466,8 @@ class IDF_Scm_Git extends IDF_Scm
|
||||
public function isCommitLarge($commit='HEAD')
|
||||
{
|
||||
$cmd = sprintf('GIT_DIR=%s '.Pluf::f('git_path', 'git').' log --numstat -1 --pretty=format:%s %s',
|
||||
escapeshellarg($this->repo),
|
||||
"'commit %H%n'",
|
||||
escapeshellarg($this->repo),
|
||||
"'commit %H%n'",
|
||||
escapeshellarg($commit));
|
||||
$out = array();
|
||||
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
|
||||
@ -477,7 +500,7 @@ class IDF_Scm_Git extends IDF_Scm
|
||||
if ($n === null) $n = '';
|
||||
else $n = ' -'.$n;
|
||||
$cmd = sprintf('GIT_DIR=%s '.Pluf::f('git_path', 'git').' log%s --date=iso --pretty=format:\'%s\' %s',
|
||||
escapeshellarg($this->repo), $n, $this->mediumtree_fmt,
|
||||
escapeshellarg($this->repo), $n, $this->mediumtree_fmt,
|
||||
escapeshellarg($commit));
|
||||
$out = array();
|
||||
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
|
||||
@ -503,6 +526,9 @@ class IDF_Scm_Git extends IDF_Scm
|
||||
$c['full_message'] = trim($c['full_message']);
|
||||
$c['full_message'] = IDF_Commit::toUTF8($c['full_message']);
|
||||
$c['title'] = IDF_Commit::toUTF8($c['title']);
|
||||
if (isset($c['parents'])) {
|
||||
$c['parents'] = explode(' ', trim($c['parents']));
|
||||
}
|
||||
$res[] = (object) $c;
|
||||
}
|
||||
$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'] = IDF_Commit::toUTF8($c['full_message']);
|
||||
$c['title'] = IDF_Commit::toUTF8($c['title']);
|
||||
if (isset($c['parents'])) {
|
||||
$c['parents'] = explode(' ', trim($c['parents']));
|
||||
}
|
||||
$res[] = (object) $c;
|
||||
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',
|
||||
escapeshellarg($this->repo),
|
||||
escapeshellarg($prefix),
|
||||
escapeshellarg($commit));
|
||||
return new Pluf_HTTP_Response_CommandPassThru($cmd, 'application/x-zip');
|
||||
}
|
||||
|
||||
/*
|
||||
@ -683,7 +713,7 @@ class IDF_Scm_Git extends IDF_Scm
|
||||
/**
|
||||
* Build the blob info cache.
|
||||
*
|
||||
* We build the blob info cache 500 commits at a time.
|
||||
* We build the blob info cache 500 commits at a time.
|
||||
*/
|
||||
public function buildBlobInfoCache()
|
||||
{
|
||||
@ -737,7 +767,7 @@ class IDF_Scm_Git extends IDF_Scm
|
||||
|
||||
/**
|
||||
* Cache blob info.
|
||||
*
|
||||
*
|
||||
* Given a series of blob info, cache them.
|
||||
*
|
||||
* @param array Blob info
|
||||
@ -765,7 +795,7 @@ class IDF_Scm_Git extends IDF_Scm
|
||||
foreach ($data as $rec) {
|
||||
if (isset($hashes[substr($rec, 0, 40)])) {
|
||||
$tmp = explode(chr(31), substr($rec, 40), 3);
|
||||
$res[substr($rec, 0, 40)] =
|
||||
$res[substr($rec, 0, 40)] =
|
||||
(object) array('hash' => substr($rec, 0, 40),
|
||||
'date' => $tmp[0],
|
||||
'title' => $tmp[2],
|
||||
@ -777,7 +807,7 @@ class IDF_Scm_Git extends IDF_Scm
|
||||
|
||||
/**
|
||||
* File cache blob info.
|
||||
*
|
||||
*
|
||||
* Given a series of blob info, cache them.
|
||||
*
|
||||
* @param array Blob info
|
||||
@ -792,13 +822,13 @@ class IDF_Scm_Git extends IDF_Scm
|
||||
}
|
||||
$data = implode(chr(30), $data).chr(30);
|
||||
$cache = Pluf::f('tmp_folder').'/IDF_Scm_Git-'.md5($this->repo).'.cache.db';
|
||||
$fp = fopen($cache, 'ab');
|
||||
$fp = fopen($cache, 'ab');
|
||||
if ($fp) {
|
||||
flock($fp, LOCK_EX);
|
||||
flock($fp, LOCK_EX);
|
||||
fwrite($fp, $data, strlen($data));
|
||||
fclose($fp); // releases the lock too
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -37,9 +37,9 @@ class IDF_Scm_Mercurial extends IDF_Scm
|
||||
{
|
||||
$cmd = Pluf::f('idf_exec_cmd_prefix', '').'du -sk '
|
||||
.escapeshellarg($this->repo);
|
||||
$out = explode(' ',
|
||||
$out = explode(' ',
|
||||
self::shell_exec('IDF_Scm_Mercurial::getRepositorySize',
|
||||
$cmd),
|
||||
$cmd),
|
||||
2);
|
||||
return (int) $out[0]*1024;
|
||||
}
|
||||
@ -77,24 +77,29 @@ class IDF_Scm_Mercurial extends IDF_Scm
|
||||
return 'tip';
|
||||
}
|
||||
|
||||
public static function getAnonymousAccessUrl($project)
|
||||
public static function getAnonymousAccessUrl($project, $commit=null)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
public function isValidRevision($rev)
|
||||
public function validateRevision($rev)
|
||||
{
|
||||
$cmd = sprintf(Pluf::f('hg_path', 'hg').' log -R %s -r %s',
|
||||
escapeshellarg($this->repo),
|
||||
escapeshellarg($rev));
|
||||
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -109,11 +114,11 @@ class IDF_Scm_Mercurial extends IDF_Scm
|
||||
$cmd = sprintf(Pluf::f('hg_path', 'hg').' log -R %s -r %s',
|
||||
escapeshellarg($this->repo),
|
||||
escapeshellarg($hash));
|
||||
$ret = 0;
|
||||
$ret = 0;
|
||||
$out = array();
|
||||
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
|
||||
self::exec('IDF_Scm_Mercurial::testHash', $cmd, $out, $ret);
|
||||
return ($ret != 0) ? false : 'commit';
|
||||
return ($ret != 0) ? false : 'commit';
|
||||
}
|
||||
|
||||
public function getTree($commit, $folder='/', $branch=null)
|
||||
@ -130,7 +135,7 @@ class IDF_Scm_Mercurial extends IDF_Scm
|
||||
$found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!$found) {
|
||||
throw new Exception(sprintf(__('Folder %1$s not found in commit %2$s.'), $folder, $commit));
|
||||
}
|
||||
@ -142,7 +147,7 @@ class IDF_Scm_Mercurial extends IDF_Scm
|
||||
/**
|
||||
* Get the tree info.
|
||||
*
|
||||
* @param string Tree hash
|
||||
* @param string Tree hash
|
||||
* @param bool Do we recurse in subtrees (true)
|
||||
* @return array Array of file information.
|
||||
*/
|
||||
@ -152,7 +157,7 @@ class IDF_Scm_Mercurial extends IDF_Scm
|
||||
throw new Exception(sprintf(__('Not a valid tree: %s.'), $tree));
|
||||
}
|
||||
$cmd_tmpl = Pluf::f('hg_path', 'hg').' manifest -R %s --debug -r %s';
|
||||
$cmd = sprintf($cmd_tmpl, escapeshellarg($this->repo), $tree, ($recurse) ? '' : '');
|
||||
$cmd = sprintf($cmd_tmpl, escapeshellarg($this->repo), $tree, ($recurse) ? '' : '');
|
||||
$out = array();
|
||||
$res = array();
|
||||
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
|
||||
@ -192,7 +197,7 @@ class IDF_Scm_Mercurial extends IDF_Scm
|
||||
}
|
||||
$fullpath = ($folder) ? $folder.'/'.$file : $file;
|
||||
$efullpath = self::smartEncode($fullpath);
|
||||
$res[] = (object) array('perm' => $perm, 'type' => $type,
|
||||
$res[] = (object) array('perm' => $perm, 'type' => $type,
|
||||
'hash' => $hash, 'fullpath' => $fullpath,
|
||||
'efullpath' => $efullpath, 'file' => $file);
|
||||
}
|
||||
@ -202,7 +207,7 @@ class IDF_Scm_Mercurial extends IDF_Scm
|
||||
public function getPathInfo($totest, $commit='tip')
|
||||
{
|
||||
$cmd_tmpl = Pluf::f('hg_path', 'hg').' manifest -R %s --debug -r %s';
|
||||
$cmd = sprintf($cmd_tmpl, escapeshellarg($this->repo), $commit);
|
||||
$cmd = sprintf($cmd_tmpl, escapeshellarg($this->repo), $commit);
|
||||
$out = array();
|
||||
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
|
||||
self::exec('IDF_Scm_Mercurial::getPathInfo', $cmd, $out);
|
||||
@ -219,8 +224,8 @@ class IDF_Scm_Mercurial extends IDF_Scm
|
||||
$tmp .= $dir[$i];
|
||||
if ($tmp == $totest) {
|
||||
$pathinfo = pathinfo($totest);
|
||||
return (object) array('perm' => '000', 'type' => 'tree',
|
||||
'hash' => $hash,
|
||||
return (object) array('perm' => '000', 'type' => 'tree',
|
||||
'hash' => $hash,
|
||||
'fullpath' => $totest,
|
||||
'file' => $pathinfo['basename'],
|
||||
'commit' => $commit
|
||||
@ -239,8 +244,8 @@ class IDF_Scm_Mercurial extends IDF_Scm
|
||||
}
|
||||
if ($totest == $file) {
|
||||
$pathinfo = pathinfo($totest);
|
||||
return (object) array('perm' => $perm, 'type' => $type,
|
||||
'hash' => $hash,
|
||||
return (object) array('perm' => $perm, 'type' => $type,
|
||||
'hash' => $hash,
|
||||
'fullpath' => $totest,
|
||||
'file' => $pathinfo['basename'],
|
||||
'commit' => $commit
|
||||
@ -249,15 +254,15 @@ class IDF_Scm_Mercurial extends IDF_Scm
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public function getFile($def, $cmd_only=false)
|
||||
{
|
||||
$cmd = sprintf(Pluf::f('hg_path', 'hg').' cat -R %s -r %s %s',
|
||||
escapeshellarg($this->repo),
|
||||
escapeshellarg($def->commit),
|
||||
escapeshellarg($this->repo),
|
||||
escapeshellarg($def->commit),
|
||||
escapeshellarg($this->repo.'/'.$def->fullpath));
|
||||
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
|
||||
return ($cmd_only) ?
|
||||
return ($cmd_only) ?
|
||||
$cmd : self::shell_exec('IDF_Scm_Mercurial::getFile', $cmd);
|
||||
}
|
||||
|
||||
@ -272,7 +277,7 @@ class IDF_Scm_Mercurial extends IDF_Scm
|
||||
return $this->cache['branches'];
|
||||
}
|
||||
$out = array();
|
||||
$cmd = sprintf(Pluf::f('hg_path', 'hg').' branches -R %s',
|
||||
$cmd = sprintf(Pluf::f('hg_path', 'hg').' branches -R %s',
|
||||
escapeshellarg($this->repo));
|
||||
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
|
||||
self::exec('IDF_Scm_Mercurial::getBranches', $cmd, $out);
|
||||
@ -296,7 +301,7 @@ class IDF_Scm_Mercurial extends IDF_Scm
|
||||
return $this->cache['tags'];
|
||||
}
|
||||
$out = array();
|
||||
$cmd = sprintf(Pluf::f('hg_path', 'hg').' tags -R %s',
|
||||
$cmd = sprintf(Pluf::f('hg_path', 'hg').' tags -R %s',
|
||||
escapeshellarg($this->repo));
|
||||
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
|
||||
self::exec('IDF_Scm_Mercurial::getTags', $cmd, $out);
|
||||
@ -311,13 +316,13 @@ class IDF_Scm_Mercurial extends IDF_Scm
|
||||
|
||||
public function inBranches($commit, $path)
|
||||
{
|
||||
return (in_array($commit, array_keys($this->getBranches())))
|
||||
return (in_array($commit, array_keys($this->getBranches())))
|
||||
? array($commit) : array();
|
||||
}
|
||||
|
||||
public function inTags($commit, $path)
|
||||
{
|
||||
return (in_array($commit, array_keys($this->getTags())))
|
||||
return (in_array($commit, array_keys($this->getTags())))
|
||||
? array($commit) : array();
|
||||
}
|
||||
|
||||
@ -333,9 +338,9 @@ class IDF_Scm_Mercurial extends IDF_Scm
|
||||
if (!$this->isValidRevision($commit)) {
|
||||
return false;
|
||||
}
|
||||
$tmpl = ($getdiff) ?
|
||||
$tmpl = ($getdiff) ?
|
||||
Pluf::f('hg_path', 'hg').' log -p -r %s -R %s' : Pluf::f('hg_path', 'hg').' log -r %s -R %s';
|
||||
$cmd = sprintf($tmpl,
|
||||
$cmd = sprintf($tmpl,
|
||||
escapeshellarg($commit), escapeshellarg($this->repo));
|
||||
$out = array();
|
||||
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
|
||||
@ -354,7 +359,7 @@ class IDF_Scm_Mercurial extends IDF_Scm
|
||||
}
|
||||
}
|
||||
$out = self::parseLog($log, 6);
|
||||
$out[0]->changes = implode("\n", $change);
|
||||
$out[0]->diff = implode("\n", $change);
|
||||
return $out[0];
|
||||
}
|
||||
|
||||
@ -411,7 +416,7 @@ class IDF_Scm_Mercurial extends IDF_Scm
|
||||
$c['full_message'] = '';
|
||||
$i=1;
|
||||
continue;
|
||||
|
||||
|
||||
}
|
||||
if ($i == $hdrs) {
|
||||
$c['title'] = trim($line);
|
||||
@ -424,6 +429,8 @@ class IDF_Scm_Mercurial extends IDF_Scm
|
||||
$c['author'] = $match[2];
|
||||
} elseif ($match[1] == 'summary') {
|
||||
$c['title'] = $match[2];
|
||||
} elseif ($match[1] == 'branch') {
|
||||
$c['branch'] = $match[2];
|
||||
} else {
|
||||
$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['branch'] = empty($c['branch']) ? 'default' : $c['branch'];
|
||||
$c['full_message'] = !empty($c['full_message']) ? trim($c['full_message']) : '';
|
||||
$res[] = (object) $c;
|
||||
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 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 -',
|
||||
escapeshellarg($this->repo),
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
@ -24,7 +24,7 @@
|
||||
/**
|
||||
* Subversion backend.
|
||||
* When a branch is not a branch.
|
||||
*
|
||||
*
|
||||
* Contrary to most other SCMs, Subversion is using folders to manage
|
||||
* the branches and so what is either the commit or the branch in
|
||||
* other SCMs is the revision number with Subversion. So, do not be
|
||||
@ -48,6 +48,25 @@ class IDF_Scm_Svn extends IDF_Scm
|
||||
|
||||
public function isAvailable()
|
||||
{
|
||||
$cmd = sprintf(Pluf::f('svn_path', 'svn').' info --xml --username=%s --password=%s %s',
|
||||
escapeshellarg($this->username),
|
||||
escapeshellarg($this->password),
|
||||
escapeshellarg($this->repo));
|
||||
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
|
||||
$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;
|
||||
}
|
||||
|
||||
@ -80,12 +99,13 @@ class IDF_Scm_Svn extends IDF_Scm
|
||||
* Returns the URL of the subversion repository.
|
||||
*
|
||||
* @param IDF_Project
|
||||
* @param string
|
||||
* @return string URL
|
||||
*/
|
||||
public static function getAnonymousAccessUrl($project)
|
||||
public static function getAnonymousAccessUrl($project,$commit=null)
|
||||
{
|
||||
$conf = $project->getConf();
|
||||
if (false !== ($url=$conf->getVal('svn_remote_url', false))
|
||||
if (false !== ($url=$conf->getVal('svn_remote_url', false))
|
||||
&& !empty($url)) {
|
||||
// Remote repository
|
||||
return $url;
|
||||
@ -97,12 +117,13 @@ class IDF_Scm_Svn extends IDF_Scm
|
||||
* Returns the URL of the subversion repository.
|
||||
*
|
||||
* @param IDF_Project
|
||||
* @param string
|
||||
* @return string URL
|
||||
*/
|
||||
public static function getAuthAccessUrl($project, $user)
|
||||
public static function getAuthAccessUrl($project, $user, $commit=null)
|
||||
{
|
||||
$conf = $project->getConf();
|
||||
if (false !== ($url=$conf->getVal('svn_remote_url', false))
|
||||
if (false !== ($url=$conf->getVal('svn_remote_url', false))
|
||||
&& !empty($url)) {
|
||||
// Remote repository
|
||||
return $url;
|
||||
@ -120,7 +141,7 @@ class IDF_Scm_Svn extends IDF_Scm
|
||||
{
|
||||
$conf = $project->getConf();
|
||||
// Find the repository
|
||||
if (false !== ($rep=$conf->getVal('svn_remote_url', false))
|
||||
if (false !== ($rep=$conf->getVal('svn_remote_url', false))
|
||||
&& !empty($rep)) {
|
||||
// Remote repository
|
||||
$scm = new IDF_Scm_Svn($rep, $project);
|
||||
@ -136,19 +157,23 @@ class IDF_Scm_Svn extends IDF_Scm
|
||||
/**
|
||||
* Subversion revisions are either a number or 'HEAD'.
|
||||
*/
|
||||
public function isValidRevision($rev)
|
||||
public function validateRevision($rev)
|
||||
{
|
||||
if ($rev == 'HEAD') {
|
||||
return true;
|
||||
return IDF_Scm::REVISION_VALID;
|
||||
}
|
||||
|
||||
$cmd = sprintf(Pluf::f('svn_path', 'svn').' info --username=%s --password=%s %s@%s',
|
||||
escapeshellarg($this->username),
|
||||
escapeshellarg($this->password),
|
||||
escapeshellarg($this->repo),
|
||||
escapeshellarg($rev));
|
||||
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
|
||||
self::exec('IDF_Scm_Svn::isValidRevision', $cmd, $out, $ret);
|
||||
return (0 == $ret);
|
||||
self::exec('IDF_Scm_Svn::validateRevision', $cmd, $out, $ret);
|
||||
|
||||
if ($ret == 0)
|
||||
return IDF_Scm::REVISION_VALID;
|
||||
return IDF_Scm::REVISION_INVALID;
|
||||
}
|
||||
|
||||
|
||||
@ -268,7 +293,7 @@ class IDF_Scm_Svn extends IDF_Scm
|
||||
$file['type'] = $this->assoc[(string) $entry['kind']];
|
||||
$pathinfo = pathinfo($filename);
|
||||
$file['file'] = $pathinfo['basename'];
|
||||
$file['rev'] = $rev;
|
||||
$file['rev'] = $rev;
|
||||
$file['author'] = (string) $entry->author;
|
||||
$file['date'] = gmdate('Y-m-d H:i:s', strtotime((string) $entry->commit->date));
|
||||
$file['size'] = (string) $entry->size;
|
||||
@ -284,12 +309,12 @@ class IDF_Scm_Svn extends IDF_Scm
|
||||
escapeshellarg($this->repo.'/'.self::smartEncode($def->fullpath)),
|
||||
escapeshellarg($def->rev));
|
||||
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
|
||||
return ($cmd_only) ?
|
||||
return ($cmd_only) ?
|
||||
$cmd : self::shell_exec('IDF_Scm_Svn::getFile', $cmd);
|
||||
}
|
||||
|
||||
/**
|
||||
* Subversion branches are folder based.
|
||||
* Subversion branches are folder based.
|
||||
*
|
||||
* One need to list the folder to know them.
|
||||
*/
|
||||
@ -328,7 +353,7 @@ class IDF_Scm_Svn extends IDF_Scm
|
||||
}
|
||||
|
||||
/**
|
||||
* Subversion tags are folder based.
|
||||
* Subversion tags are folder based.
|
||||
*
|
||||
* One need to list the folder to know them.
|
||||
*/
|
||||
@ -392,7 +417,7 @@ class IDF_Scm_Svn extends IDF_Scm
|
||||
*/
|
||||
public function getCommit($commit, $getdiff=false)
|
||||
{
|
||||
if (!$this->isValidRevision($commit)) {
|
||||
if ($this->validateRevision($commit) != IDF_Scm::REVISION_VALID) {
|
||||
return false;
|
||||
}
|
||||
$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['title'] = (string) $xml->logentry->msg;
|
||||
$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['branch'] = '';
|
||||
return (object) $res;
|
||||
}
|
||||
|
||||
|
@ -51,9 +51,13 @@ class IDF_Template_IssueComment extends Pluf_Template_Tag
|
||||
array($this, 'callbackReviews'), $text);
|
||||
}
|
||||
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);
|
||||
$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);
|
||||
}
|
||||
if ($wordwrap) $text = Pluf_Text::wrapHtml($text, 69, "\n");
|
||||
@ -122,15 +126,12 @@ class IDF_Template_IssueComment extends Pluf_Template_Tag
|
||||
function callbackCommits($m)
|
||||
{
|
||||
$keyword = rtrim($m[1]);
|
||||
if ('commits' === $keyword) {
|
||||
// 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) {
|
||||
if (empty($m[3])) {
|
||||
// Single commit like 'commit 6e030e6'.
|
||||
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)
|
||||
{
|
||||
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];
|
||||
if (!empty($m[3])) $file = str_replace($m[3], '', $file);
|
||||
$request_file_info = $this->scm->getPathInfo($file);
|
||||
if (!empty($m[3]))
|
||||
$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) {
|
||||
return $m[0];
|
||||
}
|
||||
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>';
|
||||
if ($request_file_info->type == 'tree') {
|
||||
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'),
|
||||
$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.
|
||||
$text = preg_replace_callback('#\[\[([A-Za-z0-9\-]+)\]\]#im',
|
||||
array($this, 'callbackWikiPage'),
|
||||
array($this, 'callbackWikiPageNoName'),
|
||||
$text);
|
||||
$filter = new IDF_Template_MarkdownPrefilter();
|
||||
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)
|
||||
{
|
||||
$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()));
|
||||
if ($pages->count() != 1 and !$this->request->rights['hasWikiAccess']) {
|
||||
return $m[0];
|
||||
}
|
||||
if ($pages->count() != 1 and $this->request->rights['hasWikiAccess']
|
||||
and !$this->request->user->isAnonymous()) {
|
||||
return '<img style="vertical-align: text-bottom;" alt=" " src="'.Pluf::f('url_media').'/idf/img/add.png" /><a href="'.Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::create', array($this->project->shortname), array('name'=>$m[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) {
|
||||
return $m[1];
|
||||
@ -75,6 +82,9 @@ class IDF_Template_Markdown extends Pluf_Template_Tag
|
||||
function callbackEmbeddedDoc($m)
|
||||
{
|
||||
$scm = IDF_Scm::get($this->request->project);
|
||||
if (!$scm->isAvailable()) {
|
||||
return $m[0];
|
||||
}
|
||||
$view_source = new IDF_Views_Source();
|
||||
$match = array('dummy', $this->request->project->shortname);
|
||||
$match[] = (isset($m[2])) ? $m[2] : $scm->getMainBranch();
|
||||
@ -86,7 +96,7 @@ class IDF_Template_Markdown extends Pluf_Template_Tag
|
||||
$info = pathinfo($m[1]);
|
||||
$fileinfo = array($res->headers['Content-Type'], $m[1],
|
||||
isset($info['extension']) ? $info['extension'] : 'bin');
|
||||
if (!IDF_Views_Source::isText($fileinfo)) {
|
||||
if (!IDF_FileUtil::isText($fileinfo)) {
|
||||
return $m[0];
|
||||
}
|
||||
return $res->content;
|
||||
|
@ -99,6 +99,7 @@ class IDF_Template_MarkdownPrefilter extends Pluf_Text_HTML_Filter
|
||||
'caption' => array(),
|
||||
'code' => array(),
|
||||
'dd' => array(),
|
||||
'del' => array('cite', 'class', 'datetime', 'dir', 'id', 'title'),
|
||||
'div' => array('align', 'class'),
|
||||
'dl' => array(),
|
||||
'dt' => array(),
|
||||
@ -112,6 +113,7 @@ class IDF_Template_MarkdownPrefilter extends Pluf_Text_HTML_Filter
|
||||
'hr' => array(),
|
||||
'i' => array(),
|
||||
'img' => array('src', 'class', 'alt', 'height', 'width', 'style'),
|
||||
'ins' => array('cite', 'class', 'datetime', 'dir', 'id', 'title'),
|
||||
'li' => array(),
|
||||
'ol' => array(),
|
||||
'p' => array('align', 'class'),
|
||||
@ -151,7 +153,9 @@ class IDF_Template_MarkdownPrefilter extends Pluf_Text_HTML_Filter
|
||||
'blockquote',
|
||||
'pre',
|
||||
'iframe',
|
||||
'h1', 'h2', 'h3', 'address'
|
||||
'h1', 'h2', 'h3', 'address',
|
||||
'del',
|
||||
'ins',
|
||||
);
|
||||
// attributes which should be checked for valid protocols
|
||||
public $protocol_attributes = array(
|
||||
@ -164,6 +168,7 @@ class IDF_Template_MarkdownPrefilter extends Pluf_Text_HTML_Filter
|
||||
'https',
|
||||
'ftp',
|
||||
'mailto',
|
||||
'irc'
|
||||
);
|
||||
// tags which should be removed if they contain no content
|
||||
// (e.g. "<b></b>" or "<b />")
|
||||
@ -174,5 +179,7 @@ class IDF_Template_MarkdownPrefilter extends Pluf_Text_HTML_Filter
|
||||
'caption',
|
||||
'li',
|
||||
'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.');
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
$regex = '#^/p/([\-\w]+)/source/tree/([^\/]+)/(.*)$#';
|
||||
@ -61,4 +48,4 @@ class IDF_Tests_TestSource extends UnitTestCase
|
||||
$this->assertEqual($res[2], $m[3]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -39,9 +39,9 @@ class IDF_Upload extends Pluf_Model
|
||||
'id' =>
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Sequence',
|
||||
'blank' => true,
|
||||
'blank' => true,
|
||||
),
|
||||
'project' =>
|
||||
'project' =>
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Foreignkey',
|
||||
'model' => 'IDF_Project',
|
||||
@ -77,7 +77,7 @@ class IDF_Upload extends Pluf_Model
|
||||
'default' => 0,
|
||||
'verbose' => __('file size in bytes'),
|
||||
),
|
||||
'submitter' =>
|
||||
'submitter' =>
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Foreignkey',
|
||||
'model' => 'Pluf_User',
|
||||
@ -87,7 +87,7 @@ class IDF_Upload extends Pluf_Model
|
||||
),
|
||||
'tags' =>
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Manytomany',
|
||||
'type' => 'Pluf_DB_Field_Manytomany',
|
||||
'blank' => true,
|
||||
'model' => 'IDF_Tag',
|
||||
'verbose' => __('labels'),
|
||||
@ -112,7 +112,7 @@ class IDF_Upload extends Pluf_Model
|
||||
'verbose' => __('modification date'),
|
||||
),
|
||||
);
|
||||
$this->_a['idx'] = array(
|
||||
$this->_a['idx'] = array(
|
||||
'modif_dtime_idx' =>
|
||||
array(
|
||||
'col' => 'modif_dtime',
|
||||
@ -121,7 +121,7 @@ class IDF_Upload extends Pluf_Model
|
||||
);
|
||||
$table = $this->_con->pfx.'idf_tag_idf_upload_assoc';
|
||||
$this->_a['views'] = array(
|
||||
'join_tags' =>
|
||||
'join_tags' =>
|
||||
array(
|
||||
'join' => 'LEFT JOIN '.$table
|
||||
.' ON idf_upload_id=id',
|
||||
@ -150,7 +150,7 @@ class IDF_Upload extends Pluf_Model
|
||||
function postSave($create=false)
|
||||
{
|
||||
if ($create) {
|
||||
IDF_Timeline::insert($this, $this->get_project(),
|
||||
IDF_Timeline::insert($this, $this->get_project(),
|
||||
$this->get_submitter(), $this->creation_dtime);
|
||||
}
|
||||
}
|
||||
@ -173,13 +173,13 @@ class IDF_Upload extends Pluf_Model
|
||||
* Returns the timeline fragment for the file.
|
||||
*
|
||||
*
|
||||
* @param Pluf_HTTP_Request
|
||||
* @param Pluf_HTTP_Request
|
||||
* @return Pluf_Template_SafeString
|
||||
*/
|
||||
public function timelineFragment($request)
|
||||
{
|
||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Download::view',
|
||||
array($request->project->shortname,
|
||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Download::view',
|
||||
array($request->project->shortname,
|
||||
$this->id));
|
||||
$out = '<tr class="log"><td><a href="'.$url.'">'.
|
||||
Pluf_esc(Pluf_Template_dateAgo($this->creation_dtime, 'without')).
|
||||
@ -189,15 +189,15 @@ class IDF_Upload extends Pluf_Model
|
||||
$out .= sprintf(__('<a href="%1$s" title="View download">Download %2$d</a>, %3$s'), $url, $this->id, Pluf_esc($this->summary)).'</td>';
|
||||
$out .= '</tr>';
|
||||
$out .= "\n".'<tr class="extra"><td colspan="2">
|
||||
<div class="helptext right">'.sprintf(__('Addition of <a href="%s">download %d</a>, by %s'), $url, $this->id, $user).'</div></td></tr>';
|
||||
<div class="helptext right">'.sprintf(__('Addition of <a href="%s">download %d</a>, by %s'), $url, $this->id, $user).'</div></td></tr>';
|
||||
return Pluf_Template::markSafe($out);
|
||||
}
|
||||
|
||||
public function feedFragment($request)
|
||||
{
|
||||
$url = Pluf::f('url_base')
|
||||
.Pluf_HTTP_URL_urlForView('IDF_Views_Download::view',
|
||||
array($request->project->shortname,
|
||||
.Pluf_HTTP_URL_urlForView('IDF_Views_Download::view',
|
||||
array($request->project->shortname,
|
||||
$this->id));
|
||||
$title = sprintf(__('%s: Download %d added - %s'),
|
||||
$request->project->name,
|
||||
@ -227,7 +227,7 @@ class IDF_Upload extends Pluf_Model
|
||||
}
|
||||
$current_locale = Pluf_Translation::getLocale();
|
||||
$langs = Pluf::f('languages', array('en'));
|
||||
Pluf_Translation::loadSetLocale($langs[0]);
|
||||
Pluf_Translation::loadSetLocale($langs[0]);
|
||||
|
||||
$context = new Pluf_Template_Context(
|
||||
array('file' => $this,
|
||||
@ -237,14 +237,16 @@ class IDF_Upload extends Pluf_Model
|
||||
));
|
||||
$tmpl = new Pluf_Template('idf/downloads/download-created-email.txt');
|
||||
$text_email = $tmpl->render($context);
|
||||
$email = new Pluf_Mail(Pluf::f('from_email'),
|
||||
$conf->getVal('downloads_notification_email'),
|
||||
sprintf(__('New download - %s (%s)'),
|
||||
$this->summary,
|
||||
$this->get_project()->shortname));
|
||||
$email->addTextMessage($text_email);
|
||||
$email->sendMail();
|
||||
|
||||
$addresses = explode(',', $conf->getVal('downloads_notification_email'));
|
||||
foreach ($addresses as $address) {
|
||||
$email = new Pluf_Mail(Pluf::f('from_email'),
|
||||
$address,
|
||||
sprintf(__('New download - %s (%s)'),
|
||||
$this->summary,
|
||||
$this->get_project()->shortname));
|
||||
$email->addTextMessage($text_email);
|
||||
$email->sendMail();
|
||||
}
|
||||
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');
|
||||
$params = array('request'=>$request);
|
||||
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()) {
|
||||
$user = $form->save(); // It is sending the confirmation email
|
||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views::registerInputKey');
|
||||
@ -314,10 +317,10 @@ class IDF_Views
|
||||
if ($user->isAnonymous()) {
|
||||
$sql = sprintf('%s=%s', $db->qn('private'), $false);
|
||||
return Pluf::factory('IDF_Project')->getList(array('filter'=> $sql,
|
||||
'order' => 'shortname ASC'));
|
||||
'order' => 'name ASC'));
|
||||
}
|
||||
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
|
||||
// or authorized
|
||||
@ -338,6 +341,6 @@ class IDF_Views
|
||||
$sql .= sprintf(' OR id IN (%s)', implode(', ', $ids));
|
||||
}
|
||||
return Pluf::factory('IDF_Project')->getList(array('filter' => $sql,
|
||||
'order' => 'shortname ASC'));
|
||||
'order' => 'name ASC'));
|
||||
}
|
||||
}
|
@ -66,7 +66,7 @@ class IDF_Views_Admin
|
||||
'name' => __('Name'),
|
||||
array('id', 'IDF_Views_Admin_projectSize', __('Repository Size')),
|
||||
);
|
||||
$pag->configure($list_display, array(),
|
||||
$pag->configure($list_display, array(),
|
||||
array('shortname'));
|
||||
$pag->extra_classes = array('', '', 'right');
|
||||
$pag->items_per_page = 25;
|
||||
@ -118,6 +118,9 @@ class IDF_Views_Admin
|
||||
/**
|
||||
* 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 function projectCreate($request, $match)
|
||||
@ -211,8 +214,8 @@ class IDF_Views_Admin
|
||||
array('last_login', 'Pluf_Paginator_DateYMDHM', __('Last Login')),
|
||||
);
|
||||
$pag->extra_classes = array('', '', 'a-c', 'a-c', 'a-c', 'a-c');
|
||||
$pag->configure($list_display,
|
||||
array('login', 'last_name', 'email'),
|
||||
$pag->configure($list_display,
|
||||
array('login', 'last_name', 'email'),
|
||||
array('login', 'last_login'));
|
||||
$pag->items_per_page = 50;
|
||||
$pag->no_results_text = __('No users were found.');
|
||||
@ -225,7 +228,7 @@ class IDF_Views_Admin
|
||||
),
|
||||
$request);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Not validated users.
|
||||
*/
|
||||
@ -265,7 +268,9 @@ class IDF_Views_Admin
|
||||
}
|
||||
|
||||
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()) {
|
||||
$form->save();
|
||||
$request->user->setMessage(__('The user has been updated.'));
|
||||
@ -296,7 +301,9 @@ class IDF_Views_Admin
|
||||
'request' => $request,
|
||||
);
|
||||
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()) {
|
||||
$cuser = $form->save();
|
||||
$request->user->setMessage(sprintf(__('The user %s has been created.'), (string) $cuser));
|
||||
@ -314,6 +321,148 @@ class IDF_Views_Admin
|
||||
),
|
||||
$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)
|
||||
@ -326,7 +475,7 @@ function IDF_Views_Admin_bool($field, $item)
|
||||
/**
|
||||
* Display the size of the project.
|
||||
*
|
||||
* @param string Field
|
||||
* @param string Field
|
||||
* @param IDF_Project
|
||||
* @return string
|
||||
*/
|
||||
@ -344,25 +493,51 @@ function IDF_Views_Admin_projectSize($field, $project)
|
||||
*
|
||||
* @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['repositories'] = 0;
|
||||
foreach (Pluf::factory('IDF_Project')->getList() as $prj) {
|
||||
$size = $prj->getRepositorySize();
|
||||
$size = $prj->getRepositorySize($force);
|
||||
if ($size != -1) {
|
||||
$res['repositories'] += $size;
|
||||
}
|
||||
}
|
||||
$cmd = Pluf::f('idf_exec_cmd_prefix', '').'du -sk '
|
||||
.escapeshellarg(Pluf::f('upload_path'));
|
||||
$out = explode(' ', shell_exec($cmd), 2);
|
||||
$res['downloads'] = $out[0]*1024;
|
||||
$cmd = Pluf::f('idf_exec_cmd_prefix', '').'du -sk '
|
||||
.escapeshellarg(Pluf::f('upload_issue_path'));
|
||||
$out = explode(' ', shell_exec($cmd), 2);
|
||||
$res['attachments'] = $out[0]*1024;
|
||||
$res['database'] = IDF_Views_Admin_getForgeDbSize();
|
||||
$last_eval = $conf->getVal('downloads_size_check_date', 0);
|
||||
if (Pluf::f('idf_no_size_check', false) or
|
||||
(!$force and $last_eval > time()-172800)) {
|
||||
$res['downloads'] = $conf->getVal('downloads_size', 0);
|
||||
} else {
|
||||
$conf->setVal('downloads_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['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'];
|
||||
return $res;
|
||||
}
|
||||
@ -380,8 +555,8 @@ function IDF_Views_Admin_getForgeDbSize()
|
||||
}
|
||||
switch (Pluf::f('db_engine')) {
|
||||
case 'PostgreSQL':
|
||||
$sql = 'SELECT relname, pg_total_relation_size(CAST(relname AS
|
||||
TEXT)) AS size FROM pg_class AS pgc, pg_namespace AS pgn
|
||||
$sql = 'SELECT relname, pg_total_relation_size(CAST(relname AS
|
||||
TEXT)) AS size FROM pg_class AS pgc, pg_namespace AS pgn
|
||||
WHERE pg_table_is_visible(pgc.oid) IS TRUE AND relkind = \'r\'
|
||||
AND pgc.relnamespace = pgn.oid
|
||||
AND pgn.nspname NOT IN (\'information_schema\', \'pg_catalog\')';
|
||||
|
@ -152,6 +152,29 @@ class IDF_Views_Download
|
||||
if ($request->method == 'POST') {
|
||||
$fname = $upload->file;
|
||||
@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();
|
||||
$request->user->setMessage(__('The file has been deleted.'));
|
||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Download::index',
|
||||
|
@ -79,7 +79,165 @@ class IDF_Views_Issue
|
||||
}
|
||||
|
||||
/**
|
||||
* View the issues of a given user.
|
||||
* 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.
|
||||
*
|
||||
* Only open issues are shown.
|
||||
*/
|
||||
@ -201,7 +359,7 @@ class IDF_Views_Issue
|
||||
{
|
||||
$prj = $request->project;
|
||||
if (!isset($request->REQUEST['q']) or trim($request->REQUEST['q']) == '') {
|
||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Issue::index',
|
||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Issue::index',
|
||||
array($prj->shortname));
|
||||
return new Pluf_HTTP_Response_Redirect($url);
|
||||
}
|
||||
@ -263,7 +421,7 @@ class IDF_Views_Issue
|
||||
'issue' => $issue,
|
||||
);
|
||||
if ($request->method == 'POST') {
|
||||
$form = new IDF_Form_IssueUpdate(array_merge($request->POST,
|
||||
$form = new IDF_Form_IssueUpdate(array_merge($request->POST,
|
||||
$request->FILES),
|
||||
$params);
|
||||
if (!isset($request->POST['preview']) && $form->isValid()) {
|
||||
@ -279,6 +437,27 @@ class IDF_Views_Issue
|
||||
$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);
|
||||
return Pluf_Shortcuts_RenderToResponse('idf/issues/view.html',
|
||||
array_merge(
|
||||
@ -290,7 +469,9 @@ class IDF_Views_Issue
|
||||
'page_title' => $title,
|
||||
'closed' => $closed,
|
||||
'preview' => $preview,
|
||||
'interested' =>$interested->count(),
|
||||
'interested' => $interested->count(),
|
||||
'previous_issue_id' => $previous_issue_id,
|
||||
'next_issue_id' => $next_issue_id
|
||||
),
|
||||
$arrays),
|
||||
$request);
|
||||
@ -306,7 +487,7 @@ class IDF_Views_Issue
|
||||
$prj = $request->project;
|
||||
$attach = Pluf_Shortcuts_GetObjectOr404('IDF_IssueFile', $match[2]);
|
||||
$prj->inOr404($attach->get_comment()->get_issue());
|
||||
$info = IDF_Views_Source::getMimeType($attach->filename);
|
||||
$info = IDF_FileUtil::getMimeType($attach->filename);
|
||||
$mime = 'application/octet-stream';
|
||||
if (strpos($info[0], 'image/') === 0) {
|
||||
$mime = $info[0];
|
||||
@ -330,14 +511,14 @@ class IDF_Views_Issue
|
||||
$prj->inOr404($attach->get_comment()->get_issue());
|
||||
// If one cannot see the attachement, redirect to the
|
||||
// getAttachment view.
|
||||
$info = IDF_Views_Source::getMimeType($attach->filename);
|
||||
if (!IDF_Views_Source::isText($info)) {
|
||||
$info = IDF_FileUtil::getMimeType($attach->filename);
|
||||
if (!IDF_FileUtil::isText($info)) {
|
||||
return $this->getAttachment($request, $match);
|
||||
}
|
||||
// Now we want to look at the file but with links back to the
|
||||
// issue.
|
||||
$file = IDF_Views_Source::highLight($info,
|
||||
file_get_contents(Pluf::f('upload_issue_path').'/'.$attach->attachment));
|
||||
$file = IDF_FileUtil::highLight($info,
|
||||
file_get_contents(Pluf::f('upload_issue_path').'/'.$attach->attachment));
|
||||
$title = sprintf(__('View %s'), $attach->filename);
|
||||
return Pluf_Shortcuts_RenderToResponse('idf/issues/attachment.html',
|
||||
array(
|
||||
@ -406,7 +587,7 @@ class IDF_Views_Issue
|
||||
{
|
||||
$prj = $request->project;
|
||||
$tag = Pluf_Shortcuts_GetObjectOr404('IDF_Tag', $match[2]);
|
||||
$status = $match[3];
|
||||
$status = $match[3];
|
||||
if ($tag->project != $prj->id or !in_array($status, array('open', 'closed'))) {
|
||||
throw new Pluf_HTTP_Error404();
|
||||
}
|
||||
@ -414,7 +595,7 @@ class IDF_Views_Issue
|
||||
$title = sprintf(__('%1$s Issues with Label %2$s'), (string) $prj,
|
||||
(string) $tag);
|
||||
} else {
|
||||
$title = sprintf(__('%1$s Closed Issues with Label %2$s'),
|
||||
$title = sprintf(__('%1$s Closed Issues with Label %2$s'),
|
||||
(string) $prj, (string) $tag);
|
||||
}
|
||||
// Get stats about the open/closed issues having this tag.
|
||||
@ -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
|
||||
* list of labels with a link to a view "by label only".
|
||||
@ -547,11 +739,11 @@ class IDF_Views_Issue
|
||||
*/
|
||||
function IDF_Views_Issue_SummaryAndLabels($field, $issue, $extra='')
|
||||
{
|
||||
$edit = Pluf_HTTP_URL_urlForView('IDF_Views_Issue::view',
|
||||
$edit = Pluf_HTTP_URL_urlForView('IDF_Views_Issue::view',
|
||||
array($issue->shortname, $issue->id));
|
||||
$tags = array();
|
||||
foreach ($issue->get_tags_list() as $tag) {
|
||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Issue::listLabel',
|
||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Issue::listLabel',
|
||||
array($issue->shortname, $tag->id, 'open'));
|
||||
$tags[] = sprintf('<a class="label" href="%s">%s</a>', $url, Pluf_esc((string) $tag));
|
||||
}
|
||||
@ -574,4 +766,6 @@ function IDF_Views_Issue_SummaryAndLabels($field, $issue, $extra='')
|
||||
function IDF_Views_Issue_ShowStatus($field, $issue, $extra='')
|
||||
{
|
||||
return Pluf_esc($issue->get_status()->name);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -44,12 +44,12 @@ class IDF_Views_Project
|
||||
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();
|
||||
$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();
|
||||
$pages = $tags[0]->get_idf_wikipage_list();
|
||||
}
|
||||
return Pluf_Shortcuts_RenderToResponse('idf/project/home.html',
|
||||
array(
|
||||
@ -61,6 +61,66 @@ class IDF_Views_Project
|
||||
$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.
|
||||
*/
|
||||
@ -68,43 +128,26 @@ class IDF_Views_Project
|
||||
public function timeline($request, $match)
|
||||
{
|
||||
$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->class = 'recent-issues';
|
||||
$pag->item_extra_props = array('request' => $request);
|
||||
$pag->summary = __('This table shows the project updates.');
|
||||
// Need to check the rights
|
||||
$rights = array();
|
||||
if (true === IDF_Precondition::accessSource($request)) {
|
||||
$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,
|
||||
|
||||
$classes = self::determineModelClasses($request, $model_filter);
|
||||
$sql = sprintf('model_class IN (%s)', implode(', ', $classes));
|
||||
$pag->forced_where = new Pluf_SQL('project=%s AND '.$sql,
|
||||
array($prj->id));
|
||||
$pag->sort_order = array('creation_dtime', 'ASC');
|
||||
$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(
|
||||
'creation_dtime' => __('Age'),
|
||||
'id' => __('Change'),
|
||||
@ -113,32 +156,23 @@ class IDF_Views_Project
|
||||
$pag->items_per_page = 20;
|
||||
$pag->no_results_text = __('No changes were found.');
|
||||
$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()) {
|
||||
$feedurl = Pluf_HTTP_URL_urlForView('idf_project_timeline_feed_auth',
|
||||
array($prj->shortname,
|
||||
array($prj->shortname,
|
||||
$model_filter,
|
||||
IDF_Precondition::genFeedToken($prj, $request->user)));
|
||||
} else {
|
||||
$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',
|
||||
array(
|
||||
'page_title' => $title,
|
||||
'feedurl' => $feedurl,
|
||||
'timeline' => $pag,
|
||||
'team' => $team,
|
||||
'downloads' => $downloads,
|
||||
'model_filter' => $model_filter,
|
||||
'all_model_filters' => $all_model_filters,
|
||||
),
|
||||
$request);
|
||||
|
||||
@ -156,31 +190,17 @@ class IDF_Views_Project
|
||||
public function timelineFeed($request, $match)
|
||||
{
|
||||
$prj = $request->project;
|
||||
// Need to check the rights
|
||||
$rights = array();
|
||||
if (true === IDF_Precondition::accessSource($request)) {
|
||||
$rights[] = '\'IDF_Commit\'';
|
||||
IDF_Scm::syncTimeline($request->project);
|
||||
$model_filter = @$match[2];
|
||||
|
||||
$model_filter = @$match[2];
|
||||
$all_model_filters = self::getAvailableModelFilters();
|
||||
if (!array_key_exists($model_filter, $all_model_filters)) {
|
||||
$model_filter = 'all';
|
||||
}
|
||||
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\'';
|
||||
}
|
||||
$sqls = sprintf('model_class IN (%s)', implode(', ', $rights));
|
||||
$title = $all_model_filters[$model_filter];
|
||||
|
||||
$classes = self::determineModelClasses($request, $model_filter);
|
||||
$sqls = sprintf('model_class IN (%s)', implode(', ', $classes));
|
||||
$sql = new Pluf_SQL('project=%s AND '.$sqls, array($prj->id));
|
||||
$params = array(
|
||||
'filter' => $sql->gen(),
|
||||
@ -188,7 +208,7 @@ class IDF_Views_Project
|
||||
'nb' => 20,
|
||||
);
|
||||
$items = Pluf::factory('IDF_Timeline')->getList($params);
|
||||
$set = new Pluf_Model_Set($items,
|
||||
$set = new Pluf_Model_Set($items,
|
||||
array('public_dtime' => 'public_dtime'));
|
||||
$out = array();
|
||||
foreach ($set as $item) {
|
||||
@ -203,11 +223,10 @@ class IDF_Views_Project
|
||||
}
|
||||
$out = Pluf_Template::markSafe(implode("\n", $out));
|
||||
$tmpl = new Pluf_Template('idf/index.atom');
|
||||
$title = __('Updates');
|
||||
$feedurl = Pluf::f('url_base').Pluf::f('idf_base').$request->query;
|
||||
$viewurl = Pluf_HTTP_URL_urlForView('IDF_Views_Project::timeline',
|
||||
array($prj->shortname));
|
||||
$context = new Pluf_Template_Context_Request($request,
|
||||
$context = new Pluf_Template_Context_Request($request,
|
||||
array('body' => $out,
|
||||
'date' => $date,
|
||||
'title' => $title,
|
||||
@ -235,7 +254,7 @@ class IDF_Views_Project
|
||||
if ($form->isValid()) {
|
||||
$prj = $form->save();
|
||||
$request->user->setMessage(__('The project has been updated.'));
|
||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Project::admin',
|
||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Project::admin',
|
||||
array($prj->shortname));
|
||||
return new Pluf_HTTP_Response_Redirect($url);
|
||||
}
|
||||
@ -277,7 +296,8 @@ class IDF_Views_Project
|
||||
}
|
||||
} else {
|
||||
$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');
|
||||
foreach ($keys as $key) {
|
||||
$_val = $conf->getVal($key, false);
|
||||
@ -445,7 +465,7 @@ class IDF_Views_Project
|
||||
} else {
|
||||
$params = array();
|
||||
$keys = array('downloads_access_rights', 'source_access_rights',
|
||||
'issues_access_rights', 'review_access_rights',
|
||||
'issues_access_rights', 'review_access_rights',
|
||||
'wiki_access_rights',
|
||||
'downloads_notification_email',
|
||||
'review_notification_email',
|
||||
@ -475,50 +495,52 @@ class IDF_Views_Project
|
||||
|
||||
/**
|
||||
* 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 function adminSource($request, $match)
|
||||
{
|
||||
$prj = $request->project;
|
||||
$title = sprintf(__('%s Source'), (string) $prj);
|
||||
$form = null;
|
||||
$remote_svn = false;
|
||||
if ($request->conf->getVal('scm') == 'svn' and
|
||||
strlen($request->conf->getVal('svn_remote_url')) > 0) {
|
||||
$remote_svn = true;
|
||||
$extra = array(
|
||||
'conf' => $request->conf,
|
||||
);
|
||||
if ($request->method == 'POST') {
|
||||
$form = new IDF_Form_SourceConf($request->POST, $extra);
|
||||
if ($form->isValid()) {
|
||||
foreach ($form->cleaned_data as $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);
|
||||
|
||||
$remote_svn = ($request->conf->getVal('scm') == 'svn' and
|
||||
strlen($request->conf->getVal('svn_remote_url')) > 0);
|
||||
$extra = array(
|
||||
'conf' => $request->conf,
|
||||
'remote_svn' => $remote_svn,
|
||||
);
|
||||
if ($request->method == 'POST') {
|
||||
$form = new IDF_Form_SourceConf($request->POST, $extra);
|
||||
if ($form->isValid()) {
|
||||
foreach ($form->cleaned_data as $key=>$val) {
|
||||
$request->conf->setVal($key, $val);
|
||||
}
|
||||
} else {
|
||||
$params = array();
|
||||
foreach (array('svn_username', 'svn_password') 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);
|
||||
$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 {
|
||||
$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');
|
||||
$options = array(
|
||||
'git' => __('git'),
|
||||
'svn' => __('Subversion'),
|
||||
'mercurial' => __('mercurial'),
|
||||
'mtn' => __('monotone'),
|
||||
);
|
||||
$repository_type = $options[$scm];
|
||||
return Pluf_Shortcuts_RenderToResponse('idf/admin/source.html',
|
||||
@ -529,7 +551,8 @@ class IDF_Views_Project
|
||||
'repository_size' => $prj->getRepositorySize(),
|
||||
'page_title' => $title,
|
||||
'form' => $form,
|
||||
'hookkey' => $prj->getPostCommitHookKey(),
|
||||
),
|
||||
$request);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -89,10 +89,10 @@ class IDF_Views_Review
|
||||
));
|
||||
if ($form->isValid()) {
|
||||
$review = $form->save();
|
||||
$urlr = Pluf_HTTP_URL_urlForView('IDF_Views_Review::view',
|
||||
$urlr = Pluf_HTTP_URL_urlForView('IDF_Views_Review::view',
|
||||
array($prj->shortname, $review->id));
|
||||
$request->user->setMessage(sprintf(__('The <a href="%s">code review %d</a> has been created.'), $urlr, $review->id));
|
||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Review::index',
|
||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Review::index',
|
||||
array($prj->shortname));
|
||||
return new Pluf_HTTP_Response_Redirect($url);
|
||||
}
|
||||
@ -155,10 +155,10 @@ class IDF_Views_Review
|
||||
if ($form->isValid()) {
|
||||
$review_comment = $form->save();
|
||||
$review = $patch->get_review();
|
||||
$urlr = Pluf_HTTP_URL_urlForView('IDF_Views_Review::view',
|
||||
$urlr = Pluf_HTTP_URL_urlForView('IDF_Views_Review::view',
|
||||
array($prj->shortname, $review->id));
|
||||
$request->user->setMessage(sprintf(__('Your <a href="%s">code review %d</a> has been published.'), $urlr, $review->id));
|
||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Review::index',
|
||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Review::index',
|
||||
array($prj->shortname));
|
||||
$review_comment->notify($request->conf);
|
||||
return new Pluf_HTTP_Response_Redirect($url);
|
||||
@ -181,7 +181,7 @@ class IDF_Views_Review
|
||||
foreach ($cts as $ct) {
|
||||
$reviewers[] = $ct->get_comment()->get_submitter();
|
||||
}
|
||||
if (count($def['chunks'])) {
|
||||
if (count($def['chunks'])) {
|
||||
$orig_file = ($fileinfo) ? $scm->getFile($fileinfo) : '';
|
||||
$files[$filename] = array(
|
||||
$diff->fileCompare($orig_file, $def, $filename),
|
||||
@ -192,6 +192,7 @@ class IDF_Views_Review
|
||||
$files[$filename] = array('', $form->f->{md5($filename)}, $cts);
|
||||
}
|
||||
}
|
||||
|
||||
$reviewers = Pluf_Model_RemoveDuplicates($reviewers);
|
||||
return Pluf_Shortcuts_RenderToResponse('idf/review/view.html',
|
||||
array_merge(
|
||||
@ -219,7 +220,7 @@ class IDF_Views_Review
|
||||
*/
|
||||
function IDF_Views_Review_SummaryAndLabels($field, $review, $extra='')
|
||||
{
|
||||
$edit = Pluf_HTTP_URL_urlForView('IDF_Views_Review::view',
|
||||
$edit = Pluf_HTTP_URL_urlForView('IDF_Views_Review::view',
|
||||
array($review->shortname, $review->id));
|
||||
$tags = array();
|
||||
foreach ($review->get_tags_list() as $tag) {
|
||||
|
@ -31,16 +31,6 @@ Pluf::loadFunction('Pluf_Shortcuts_GetFormForModel');
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
@ -59,30 +49,56 @@ class IDF_Views_Source
|
||||
$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)
|
||||
{
|
||||
$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();
|
||||
$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,
|
||||
$this->getScmType($request));
|
||||
$changes = $scm->getChangeLog($commit, 25);
|
||||
@ -111,22 +127,17 @@ class IDF_Views_Source
|
||||
$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)
|
||||
{
|
||||
$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];
|
||||
|
||||
$cobject = $scm->getCommit($commit);
|
||||
if (!$cobject) {
|
||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Source::treeBase',
|
||||
array($request->project->shortname,
|
||||
$scm->getMainBranch()));
|
||||
return new Pluf_HTTP_Response_Redirect($url);
|
||||
throw new Exception('could not retrieve commit object for '. $commit);
|
||||
}
|
||||
$title = sprintf(__('%1$s %2$s Source Tree'),
|
||||
$request->project, $this->getScmType($request));
|
||||
@ -143,6 +154,7 @@ class IDF_Views_Source
|
||||
}
|
||||
$scmConf = $request->conf->getVal('scm', 'git');
|
||||
$props = $scm->getProperties($commit);
|
||||
$res->uasort(array('IDF_Views_Source', 'treeSort'));
|
||||
return Pluf_Shortcuts_RenderToResponse('idf/source/'.$scmConf.'/tree.html',
|
||||
array(
|
||||
'page_title' => $title,
|
||||
@ -159,20 +171,14 @@ class IDF_Views_Source
|
||||
$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)
|
||||
{
|
||||
$scm = IDF_Scm::get($request->project);
|
||||
$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];
|
||||
if (substr($request_file, -1) == '/') {
|
||||
$request_file = substr($request_file, 0, -1);
|
||||
@ -181,13 +187,13 @@ class IDF_Views_Source
|
||||
$request_file));
|
||||
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);
|
||||
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);
|
||||
}
|
||||
$branches = $scm->getBranches();
|
||||
@ -195,7 +201,7 @@ class IDF_Views_Source
|
||||
if ($request_file_info->type != 'tree') {
|
||||
$info = self::getRequestedFileMimeType($request_file_info,
|
||||
$commit, $scm);
|
||||
if (!self::isText($info)) {
|
||||
if (!IDF_FileUtil::isText($info)) {
|
||||
$rep = new Pluf_HTTP_Response($scm->getFile($request_file_info),
|
||||
$info[0]);
|
||||
$rep->headers['Content-Disposition'] = 'attachment; filename="'.$info[1].'"';
|
||||
@ -241,6 +247,7 @@ class IDF_Views_Source
|
||||
$previous = substr($request_file, 0, -strlen($l.' '));
|
||||
$scmConf = $request->conf->getVal('scm', 'git');
|
||||
$props = $scm->getProperties($commit, $request_file);
|
||||
$res->uasort(array('IDF_Views_Source', 'treeSort'));
|
||||
return Pluf_Shortcuts_RenderToResponse('idf/source/'.$scmConf.'/tree.html',
|
||||
array(
|
||||
'page_title' => $page_title,
|
||||
@ -277,44 +284,42 @@ class IDF_Views_Source
|
||||
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)
|
||||
{
|
||||
$scm = IDF_Scm::get($request->project);
|
||||
$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);
|
||||
$cobject = $scm->getCommit($commit, !$large);
|
||||
if (!$cobject) {
|
||||
// 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);
|
||||
throw new Exception('could not retrieve commit object for '. $commit);
|
||||
}
|
||||
$title = sprintf(__('%s Commit Details'), (string) $request->project);
|
||||
$page_title = sprintf(__('%s Commit Details - %s'), (string) $request->project, $commit);
|
||||
$rcommit = IDF_Commit::getOrAdd($cobject, $request->project);
|
||||
$diff = new IDF_Diff($cobject->changes);
|
||||
$diff = new IDF_Diff($cobject->diff);
|
||||
$diff->parse();
|
||||
$scmConf = $request->conf->getVal('scm', 'git');
|
||||
try {
|
||||
$changes = $scm->getChanges($commit);
|
||||
} catch (Exception $e) {
|
||||
// getChanges is not yes supported by this backend.
|
||||
$changes = array();
|
||||
}
|
||||
$branches = $scm->getBranches();
|
||||
$in_branches = $scm->inBranches($cobject->commit, '');
|
||||
$tags = $scm->getTags();
|
||||
$in_tags = $scm->inTags($cobject->commit, '');
|
||||
return Pluf_Shortcuts_RenderToResponse('idf/source/commit.html',
|
||||
return Pluf_Shortcuts_RenderToResponse('idf/source/'.$scmConf.'/commit.html',
|
||||
array(
|
||||
'page_title' => $page_title,
|
||||
'title' => $title,
|
||||
'diff' => $diff,
|
||||
'cobject' => $cobject,
|
||||
'commit' => $commit,
|
||||
'changes' => $changes,
|
||||
'branches' => $branches,
|
||||
'tree_in' => $in_branches,
|
||||
'tags' => $tags,
|
||||
@ -326,20 +331,18 @@ class IDF_Views_Source
|
||||
$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)
|
||||
{
|
||||
$scm = IDF_Scm::get($request->project);
|
||||
$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);
|
||||
$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"';
|
||||
return $rep;
|
||||
}
|
||||
@ -368,7 +371,7 @@ class IDF_Views_Source
|
||||
$previous = substr($request_file, 0, -strlen($l.' '));
|
||||
$scmConf = $request->conf->getVal('scm', 'git');
|
||||
$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',
|
||||
array(
|
||||
'page_title' => $page_title,
|
||||
@ -394,19 +397,14 @@ class IDF_Views_Source
|
||||
* 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)
|
||||
{
|
||||
$scm = IDF_Scm::get($request->project);
|
||||
$commit = $match[2];
|
||||
$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);
|
||||
if (!$request_file_info or $request_file_info->type == 'tree') {
|
||||
// Redirect to the first branch
|
||||
@ -415,7 +413,7 @@ class IDF_Views_Source
|
||||
$scm->getMainBranch()));
|
||||
return new Pluf_HTTP_Response_Redirect($url);
|
||||
}
|
||||
$info = self::getRequestedFileMimeType($request_file_info,
|
||||
$info = self::getRequestedFileMimeType($request_file_info,
|
||||
$commit, $scm);
|
||||
$rep = new Pluf_HTTP_Response($scm->getFile($request_file_info),
|
||||
$info[0]);
|
||||
@ -427,27 +425,20 @@ class IDF_Views_Source
|
||||
* 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)
|
||||
{
|
||||
$commit = trim($match[2]);
|
||||
$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;
|
||||
$cmd = $scm->getArchiveCommand($commit, $base.'/');
|
||||
$rep = new Pluf_HTTP_Response_CommandPassThru($cmd, 'application/x-zip');
|
||||
$rep = $scm->getArchiveStream($commit, $base.'/');
|
||||
$rep->headers['Content-Transfer-Encoding'] = 'binary';
|
||||
$rep->headers['Content-Disposition'] = 'attachment; filename="'.$base.'.zip"';
|
||||
return $rep;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Find the mime type of a requested file.
|
||||
*
|
||||
@ -458,120 +449,34 @@ class IDF_Views_Source
|
||||
*/
|
||||
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]) {
|
||||
return $mime;
|
||||
}
|
||||
return self::getMimeTypeFromContent($file_info->file,
|
||||
$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']);
|
||||
return IDF_FileUtil::getMimeTypeFromContent($file_info->file,
|
||||
$scm->getFile($file_info));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* Callback function to sort tree entries
|
||||
*/
|
||||
public static function isText($fileinfo)
|
||||
public static function treeSort($a, $b)
|
||||
{
|
||||
if (0 === strpos($fileinfo[0], 'text/')) {
|
||||
return true;
|
||||
// compare two nodes of the same type
|
||||
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)
|
||||
{
|
||||
$pretty = '';
|
||||
if (self::isSupportedExtension($fileinfo[2])) {
|
||||
$pretty = ' prettyprint';
|
||||
// compare two nodes of different types, directories ("tree")
|
||||
// should come before files ("blob")
|
||||
if ($a->type > $b->type) {
|
||||
return -1;
|
||||
}
|
||||
$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));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -597,3 +502,15 @@ function IDF_Views_Source_PrettySizeSimple($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);
|
||||
}
|
||||
}
|
||||
}
|
@ -33,13 +33,13 @@ Pluf::loadFunction('Pluf_Shortcuts_RenderToResponse');
|
||||
class IDF_Views_User
|
||||
{
|
||||
/**
|
||||
* Dashboard of a user.
|
||||
* Dashboard of a user.
|
||||
*
|
||||
* Shows all the open issues assigned to the user.
|
||||
*
|
||||
* TODO: This views is a SQL horror. What needs to be done to cut
|
||||
* by many the number of SQL queries:
|
||||
* - Add a table to cache the open/closed status ids for all the
|
||||
* - Add a table to cache the open/closed status ids for all the
|
||||
* projects.
|
||||
* - Left join the issues with the project to get the shortname.
|
||||
*
|
||||
@ -110,7 +110,10 @@ class IDF_Views_User
|
||||
$ext_pass = substr(sha1($request->user->password.Pluf::f('secret_key')), 0, 8);
|
||||
$params = array('user' => $request->user);
|
||||
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()) {
|
||||
$user = $form->save();
|
||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views_User::myAccount');
|
||||
@ -121,10 +124,11 @@ class IDF_Views_User
|
||||
} else {
|
||||
$data = $request->user->getData();
|
||||
unset($data['password']);
|
||||
$form = new IDF_Form_UserAccount($data, $params);
|
||||
$form = new IDF_Form_UserAccount(null, $params);
|
||||
}
|
||||
$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'),
|
||||
'api_key' => $api_key,
|
||||
'ext_pass' => $ext_pass,
|
||||
@ -134,7 +138,7 @@ class IDF_Views_User
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a SSH key.
|
||||
* Delete a public key.
|
||||
*
|
||||
* This is redirecting to the preferences
|
||||
*/
|
||||
@ -148,7 +152,7 @@ class IDF_Views_User
|
||||
return new Pluf_HTTP_Response_Forbidden($request);
|
||||
}
|
||||
$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);
|
||||
}
|
||||
@ -170,11 +174,11 @@ class IDF_Views_User
|
||||
} else {
|
||||
$form = new IDF_Form_UserChangeEmail();
|
||||
}
|
||||
return Pluf_Shortcuts_RenderToResponse('idf/user/changeemail.html',
|
||||
return Pluf_Shortcuts_RenderToResponse('idf/user/changeemail.html',
|
||||
array('page_title' => __('Confirm The Email Change'),
|
||||
'form' => $form),
|
||||
$request);
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -208,13 +212,17 @@ class IDF_Views_User
|
||||
public function view($request, $match)
|
||||
{
|
||||
$sql = new Pluf_SQL('login=%s', array($match[1]));
|
||||
$users = Pluf::factory('Pluf_User')->getList(array('filter'=>$sql->gen()));
|
||||
$users = Pluf::factory('Pluf_User')->getList(array('filter'=>$sql->gen()));
|
||||
if (count($users) != 1 or !$users[0]->active) {
|
||||
throw new Pluf_HTTP_Error404();
|
||||
}
|
||||
return Pluf_Shortcuts_RenderToResponse('idf/user/public.html',
|
||||
array('page_title' => (string) $users[0],
|
||||
'member' => $users[0],
|
||||
|
||||
$user = $users[0];
|
||||
$user_data = IDF_UserData::factory($user);
|
||||
return Pluf_Shortcuts_RenderToResponse('idf/user/public.html',
|
||||
array('page_title' => (string) $user,
|
||||
'member' => $user,
|
||||
'user_data' => $user_data,
|
||||
),
|
||||
$request);
|
||||
}
|
||||
@ -230,11 +238,11 @@ class IDF_Views_User
|
||||
function IDF_Views_IssueSummaryAndLabels($field, $issue, $extra='')
|
||||
{
|
||||
$project = $issue->get_project();
|
||||
$edit = Pluf_HTTP_URL_urlForView('IDF_Views_Issue::view',
|
||||
$edit = Pluf_HTTP_URL_urlForView('IDF_Views_Issue::view',
|
||||
array($project->shortname, $issue->id));
|
||||
$tags = array();
|
||||
foreach ($issue->get_tags_list() as $tag) {
|
||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Issue::listLabel',
|
||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Issue::listLabel',
|
||||
array($project->shortname, $tag->id, 'open'));
|
||||
$tags[] = sprintf('<a class="label" href="%s">%s</a>', $url, Pluf_esc((string) $tag));
|
||||
}
|
||||
|
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;
|
||||
}
|
||||
}
|
||||
}
|
@ -38,9 +38,9 @@ class IDF_WikiRevision extends Pluf_Model
|
||||
'id' =>
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Sequence',
|
||||
'blank' => true,
|
||||
'blank' => true,
|
||||
),
|
||||
'wikipage' =>
|
||||
'wikipage' =>
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Foreignkey',
|
||||
'model' => 'IDF_WikiPage',
|
||||
@ -55,7 +55,7 @@ class IDF_WikiRevision extends Pluf_Model
|
||||
'default' => false,
|
||||
'help_text' => 'If this revision is the latest, we mark it as being the head revision.',
|
||||
'index' => true,
|
||||
|
||||
|
||||
),
|
||||
'summary' =>
|
||||
array(
|
||||
@ -71,7 +71,7 @@ class IDF_WikiRevision extends Pluf_Model
|
||||
'blank' => false,
|
||||
'verbose' => __('content'),
|
||||
),
|
||||
'submitter' =>
|
||||
'submitter' =>
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Foreignkey',
|
||||
'model' => 'Pluf_User',
|
||||
@ -92,7 +92,7 @@ class IDF_WikiRevision extends Pluf_Model
|
||||
'verbose' => __('creation date'),
|
||||
),
|
||||
);
|
||||
$this->_a['idx'] = array(
|
||||
$this->_a['idx'] = array(
|
||||
'creation_dtime_idx' =>
|
||||
array(
|
||||
'col' => 'creation_dtime',
|
||||
@ -138,7 +138,7 @@ class IDF_WikiRevision extends Pluf_Model
|
||||
$sql = new Pluf_SQL('wikipage=%s', array($this->wikipage));
|
||||
$rev = Pluf::factory('IDF_WikiRevision')->getList(array('filter'=>$sql->gen()));
|
||||
if ($rev->count() > 1) {
|
||||
IDF_Timeline::insert($this, $this->get_wikipage()->get_project(),
|
||||
IDF_Timeline::insert($this, $this->get_wikipage()->get_project(),
|
||||
$this->get_submitter());
|
||||
foreach ($rev as $r) {
|
||||
if ($r->id != $this->id and $r->is_head) {
|
||||
@ -156,7 +156,7 @@ class IDF_WikiRevision extends Pluf_Model
|
||||
public function timelineFragment($request)
|
||||
{
|
||||
$page = $this->get_wikipage();
|
||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::view',
|
||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::view',
|
||||
array($request->project->shortname,
|
||||
$page->title));
|
||||
$out = "\n".'<tr class="log"><td><a href="'.$url.'">'.
|
||||
@ -186,7 +186,7 @@ class IDF_WikiRevision extends Pluf_Model
|
||||
}
|
||||
$out .= '</td></tr>';
|
||||
$out .= "\n".'<tr class="extra"><td colspan="2">
|
||||
<div class="helptext right">'.sprintf(__('Change of <a href="%s">%s</a>, by %s'), $url, Pluf_esc($page->title), $user).'</div></td></tr>';
|
||||
<div class="helptext right">'.sprintf(__('Change of <a href="%s">%s</a>, by %s'), $url, Pluf_esc($page->title), $user).'</div></td></tr>';
|
||||
return Pluf_Template::markSafe($out);
|
||||
}
|
||||
|
||||
@ -195,13 +195,13 @@ class IDF_WikiRevision extends Pluf_Model
|
||||
$page = $this->get_wikipage();
|
||||
if (!$this->is_head) {
|
||||
$url = Pluf::f('url_base')
|
||||
.Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::view',
|
||||
.Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::view',
|
||||
array($request->project->shortname,
|
||||
$page->title),
|
||||
array('rev' => $this->id));
|
||||
} else {
|
||||
$url = Pluf::f('url_base')
|
||||
.Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::view',
|
||||
.Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::view',
|
||||
array($request->project->shortname,
|
||||
$page->title));
|
||||
}
|
||||
@ -248,7 +248,7 @@ class IDF_WikiRevision extends Pluf_Model
|
||||
}
|
||||
$current_locale = Pluf_Translation::getLocale();
|
||||
$langs = Pluf::f('languages', array('en'));
|
||||
Pluf_Translation::loadSetLocale($langs[0]);
|
||||
Pluf_Translation::loadSetLocale($langs[0]);
|
||||
$context = new Pluf_Template_Context(
|
||||
array(
|
||||
'page' => $this->get_wikipage(),
|
||||
@ -260,23 +260,28 @@ class IDF_WikiRevision extends Pluf_Model
|
||||
if ($create) {
|
||||
$template = 'idf/wiki/wiki-created-email.txt';
|
||||
$title = sprintf(__('New Documentation Page %s - %s (%s)'),
|
||||
$this->get_wikipage()->title,
|
||||
$this->get_wikipage()->summary,
|
||||
$this->get_wikipage()->title,
|
||||
$this->get_wikipage()->summary,
|
||||
$this->get_wikipage()->get_project()->shortname);
|
||||
} else {
|
||||
$template = 'idf/wiki/wiki-updated-email.txt';
|
||||
$title = sprintf(__('Documentation Page Changed %s - %s (%s)'),
|
||||
$this->get_wikipage()->title,
|
||||
$this->get_wikipage()->summary,
|
||||
$this->get_wikipage()->title,
|
||||
$this->get_wikipage()->summary,
|
||||
$this->get_wikipage()->get_project()->shortname);
|
||||
}
|
||||
$tmpl = new Pluf_Template($template);
|
||||
$text_email = $tmpl->render($context);
|
||||
$email = new Pluf_Mail(Pluf::f('from_email'),
|
||||
$conf->getVal('wiki_notification_email'),
|
||||
$title);
|
||||
$email->addTextMessage($text_email);
|
||||
$email->sendMail();
|
||||
|
||||
$addresses = explode(',', $conf->getVal('wiki_notification_email'));
|
||||
foreach ($addresses as $address) {
|
||||
$email = new Pluf_Mail(Pluf::f('from_email'),
|
||||
$address,
|
||||
$title);
|
||||
$email->addTextMessage($text_email);
|
||||
$email->sendMail();
|
||||
}
|
||||
|
||||
Pluf_Translation::loadSetLocale($current_locale);
|
||||
}
|
||||
}
|
||||
|
@ -27,14 +27,14 @@ $cfg = array();
|
||||
# You must set them to false once everything is running ok.
|
||||
#
|
||||
$cfg['debug'] = true;
|
||||
# It will help you catch errors at beginning when configuring your
|
||||
# It will help you catch errors at beginning when configuring your
|
||||
# SCM backend. It must be turned off in production.
|
||||
$cfg['debug_scm'] = false;
|
||||
$cfg['debug_scm'] = false;
|
||||
|
||||
#
|
||||
# Note: By default, InDefero will not manage the repositories for
|
||||
# you, you can enable the repositories management with the
|
||||
# built-in plugins. The documentation of the plugins is available
|
||||
# Note: By default, InDefero will not manage the repositories for
|
||||
# you, you can enable the repositories management with the
|
||||
# built-in plugins. The documentation of the plugins is available
|
||||
# in the `doc/` folder.
|
||||
#
|
||||
|
||||
@ -44,9 +44,9 @@ $cfg['debug_scm'] = false;
|
||||
# For example: '/path/to/my/project/.git'
|
||||
#
|
||||
# If you have multiple repositories, you need to put %s where you
|
||||
# want the shortname of the project to be replaced.
|
||||
# want the shortname of the project to be replaced.
|
||||
# For example:
|
||||
# - You have many projects on your local computer and want to use
|
||||
# - You have many projects on your local computer and want to use
|
||||
# InDefero to see them. Put: '/home/yourlogin/Projects/%s/.git'
|
||||
# - You have many projects on a remote server with only "bare" git
|
||||
# repositories. Put: '/home/git/repositories/%s.git'
|
||||
@ -64,7 +64,7 @@ $cfg['git_remote_url'] = 'git://localhost/%s.git';
|
||||
$cfg['git_write_remote_url'] = 'git@localhost:%s.git';
|
||||
|
||||
# Same as for git, you can have multiple repositories, one for each
|
||||
# project or a single one for all the projects.
|
||||
# project or a single one for all the projects.
|
||||
#
|
||||
# In the case of subversion, the admin of a project can also select a
|
||||
# remote repository from the web interface. From the web interface
|
||||
@ -73,8 +73,51 @@ $cfg['git_write_remote_url'] = 'git@localhost:%s.git';
|
||||
$cfg['svn_repositories'] = 'file:///home/svn/repositories/%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
|
||||
#$cfg['mercurial_repositories'] = '/home/mercurial/repositories/%s';
|
||||
$cfg['mercurial_repositories'] = '/home/mercurial/repositories/%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
|
||||
@ -90,15 +133,15 @@ $cfg['mail_host'] = 'localhost';
|
||||
$cfg['mail_port'] = 25;
|
||||
|
||||
# Paths/Url configuration.
|
||||
#
|
||||
#
|
||||
# Examples:
|
||||
# You have:
|
||||
# You have:
|
||||
# http://www.mydomain.com/myfolder/index.php
|
||||
# Put:
|
||||
# $cfg['idf_base'] = '/myfolder/index.php';
|
||||
# $cfg['url_base'] = 'http://www.mydomain.com';
|
||||
#
|
||||
# You have mod_rewrite:
|
||||
# You have mod_rewrite:
|
||||
# http://www.mydomain.com/
|
||||
# Put:
|
||||
# $cfg['idf_base'] = '';
|
||||
@ -109,7 +152,7 @@ $cfg['mail_port'] = 25;
|
||||
$cfg['idf_base'] = '/index.php';
|
||||
$cfg['url_base'] = 'http://localhost';
|
||||
|
||||
# Url to access the media folder which is in the www folder
|
||||
# Url to access the media folder which is in the www folder
|
||||
# of the archive
|
||||
$cfg['url_media'] = 'http://localhost/media';
|
||||
|
||||
@ -120,9 +163,9 @@ $cfg['url_upload'] = 'http://localhost/media/upload';
|
||||
$cfg['upload_path'] = '/home/www/indefero/www/media/upload';
|
||||
|
||||
#
|
||||
# The following path *MUST NOT* be accessible through a web browser
|
||||
# as user will be able to upload .html, .php files and this can
|
||||
# create *TERRIBLE* security issues. In this folder, the attachments
|
||||
# The following path *MUST NOT* be accessible through a web browser
|
||||
# as user will be able to upload .html, .php files and this can
|
||||
# create *TERRIBLE* security issues. In this folder, the attachments
|
||||
# to the issues will be uploaded and we do not restrict the content type.
|
||||
#
|
||||
$cfg['upload_issue_path'] = '/home/www/indefero/attachments';
|
||||
@ -130,10 +173,10 @@ $cfg['upload_issue_path'] = '/home/www/indefero/attachments';
|
||||
#
|
||||
# write here a long random string unique for this installation. This
|
||||
# is critical to put a long string, with at least 40 characters.
|
||||
$cfg['secret_key'] = '';
|
||||
$cfg['secret_key'] = '';
|
||||
|
||||
# the sender of all the emails.
|
||||
$cfg['from_email'] = 'sender@example.com';
|
||||
$cfg['from_email'] = 'sender@example.com';
|
||||
|
||||
# Email address for the bounced messages.
|
||||
$cfg['bounce_email'] = 'no-reply@example.com';
|
||||
@ -150,22 +193,22 @@ $cfg['db_password'] = '';
|
||||
$cfg['db_server'] = '';
|
||||
$cfg['db_version'] = '5.1'; # Only needed for MySQL
|
||||
# If you want to have different installations with the same DB
|
||||
$cfg['db_table_prefix'] = 'indefero_';
|
||||
# ** DO NOT USE SQLITE IN PRODUCTION **
|
||||
$cfg['db_table_prefix'] = 'indefero_';
|
||||
# ** DO NOT USE SQLITE IN PRODUCTION **
|
||||
# This is not because of problems with the quality of the SQLite
|
||||
# driver or with SQLite itself, this is due to the lack of migration
|
||||
# support in Pluf for SQLite, this means we cannot modify the DB
|
||||
# easily once it is loaded with data.
|
||||
$cfg['db_engine'] = 'PostgreSQL'; # SQLite is also well tested or MySQL
|
||||
$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
|
||||
# extensions here. The list must start with a space.
|
||||
# $cfg['idf_extra_upload_ext'] = ' ext1 ext2';
|
||||
# extensions here.
|
||||
# $cfg['idf_extra_upload_ext'] = 'ext1 ext2';
|
||||
#
|
||||
# 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
|
||||
# always have precedence.
|
||||
# $cfg['max_upload_size'] = 2097152; // Size in bytes
|
||||
|
||||
@ -203,14 +246,28 @@ $cfg['template_context_processors'] = array('IDF_Middleware_ContextPreProcessor'
|
||||
$cfg['idf_views'] = dirname(__FILE__).'/urls.php';
|
||||
|
||||
# available languages
|
||||
$cfg['languages'] = array('en', 'fr');
|
||||
$cfg['languages'] = array('en', 'fr');
|
||||
|
||||
# SCM base configuration
|
||||
$cfg['allowed_scm'] = array('git' => 'IDF_Scm_Git',
|
||||
'svn' => 'IDF_Scm_Svn',
|
||||
'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
|
||||
# $cfg['idf_mimetypes_db'] = '/etc/mime.types';
|
||||
|
||||
@ -218,8 +275,8 @@ $cfg['allowed_scm'] = array('git' => 'IDF_Scm_Git',
|
||||
# $cfg['idf_extra_text_ext'] = 'ext1 ext2 ext3';
|
||||
|
||||
# If you can execute the shell commands executed to get info
|
||||
# from the scm with the user of your PHP process but it is
|
||||
# not working from within PHP, this can be due to the environment
|
||||
# from the scm with the user of your PHP process but it is
|
||||
# not working from within PHP, this can be due to the environment
|
||||
# variables not being set correctly. Note the trailing space.
|
||||
# $cfg['idf_exec_cmd_prefix'] = '/usr/bin/env -i ';
|
||||
|
||||
@ -229,10 +286,15 @@ $cfg['allowed_scm'] = array('git' => 'IDF_Scm_Git',
|
||||
# To know which path you need to provide, just run:
|
||||
# $ which git
|
||||
# from the command line. This will give you the path to git.
|
||||
# $cfg['svn_path'] = 'svn';
|
||||
# $cfg['svnlook_path'] = 'svnlook';
|
||||
# $cfg['svn_path'] = 'svn';
|
||||
# $cfg['svnlook_path'] = 'svnlook';
|
||||
# $cfg['svnadmin_path'] = 'svnadmin';
|
||||
# $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;
|
||||
|
@ -66,25 +66,26 @@ $ctl[] = array('regex' => '#^/logout/$#',
|
||||
$ctl[] = array('regex' => '#^/help/$#',
|
||||
'base' => $base,
|
||||
'model' => 'IDF_Views',
|
||||
'method' => 'faq');
|
||||
'method' => 'faq',
|
||||
'name' => 'idf_faq');
|
||||
|
||||
$ctl[] = array('regex' => '#^/p/([\-\w]+)/$#',
|
||||
'base' => $base,
|
||||
'model' => 'IDF_Views_Project',
|
||||
'method' => 'home');
|
||||
|
||||
$ctl[] = array('regex' => '#^/p/([\-\w]+)/timeline/$#',
|
||||
$ctl[] = array('regex' => '#^/p/([\-\w]+)/timeline/(\w+)/$#',
|
||||
'base' => $base,
|
||||
'model' => 'IDF_Views_Project',
|
||||
'method' => 'timeline');
|
||||
|
||||
$ctl[] = array('regex' => '#^/p/([\-\w]+)/feed/timeline/$#',
|
||||
$ctl[] = array('regex' => '#^/p/([\-\w]+)/feed/timeline/(\w+)/$#',
|
||||
'base' => $base,
|
||||
'model' => 'IDF_Views_Project',
|
||||
'method' => 'timelineFeed',
|
||||
'name' => 'idf_project_timeline_feed');
|
||||
|
||||
$ctl[] = array('regex' => '#^/p/([\-\w]+)/feed/timeline/token/(.*)/$#',
|
||||
$ctl[] = array('regex' => '#^/p/([\-\w]+)/feed/timeline/(\w+)/token/(.*)/$#',
|
||||
'base' => $base,
|
||||
'model' => 'IDF_Views_Project',
|
||||
'method' => 'timelineFeed',
|
||||
@ -140,6 +141,16 @@ $ctl[] = array('regex' => '#^/p/([\-\w]+)/issues/view/attachment/(\d+)/(.*)$#',
|
||||
'model' => 'IDF_Views_Issue',
|
||||
'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 ----------------------------------------
|
||||
|
||||
$ctl[] = array('regex' => '#^/p/([\-\w]+)/source/help/$#',
|
||||
@ -147,6 +158,16 @@ $ctl[] = array('regex' => '#^/p/([\-\w]+)/source/help/$#',
|
||||
'model' => 'IDF_Views_Source',
|
||||
'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/([^/]+)/$#',
|
||||
'base' => $base,
|
||||
'model' => 'IDF_Views_Source',
|
||||
@ -385,6 +406,29 @@ $ctl[] = array('regex' => '#^/admin/users/(\d+)/$#',
|
||||
'model' => 'IDF_Views_Admin',
|
||||
'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 -------------------------------
|
||||
|
||||
$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
Loading…
x
Reference in New Issue
Block a user