Merge branch 'feature.scilab' into develop

This commit is contained in:
Thomas Keller 2012-03-22 00:54:53 +01:00
commit b529c05d11
152 changed files with 33407 additions and 18108 deletions

View File

@ -17,20 +17,25 @@ Much appreciated contributors (in alphabetical order):
Denis Kot <denis.kot@gmail.com> - Russian translation Denis Kot <denis.kot@gmail.com> - Russian translation
Dmitry Dulepov <dmitryd> Dmitry Dulepov <dmitryd>
Fernando Sayago Gil <mikados.mikados@gmail.com> - Spanish translation Fernando Sayago Gil <mikados.mikados@gmail.com> - Spanish translation
Gert van Valkenhoef <gertvv>
Jakub Viták <mainiak@gmail.com> - Czech translation Jakub Viták <mainiak@gmail.com> - Czech translation
Janez Troha <http://www.dz0ny.info> - Slovenian translation Janez Troha <http://www.dz0ny.info> - Slovenian translation
Jean-Philippe Fleury <jpfleury> Jean-Philippe Fleury <jpfleury>
Jerry <lxb429@gmail.com> - Chinese translation Jerry <lxb429@gmail.com> - Chinese translation
Julien Issler <julien@issler.net> Julien Issler <julien@issler.net>
Litew <litew9@gmail.com> - Russian translation
Ludovic Bellière <xrogaan> Ludovic Bellière <xrogaan>
Manuel Eidenberger <eidenberger@gmail.com> Manuel Eidenberger <eidenberger@gmail.com>
Matthew Dawson <mjd> Matthew Dawson <mjd>
Matías Halles <matias@halles.cl> Matías Halles <matias@halles.cl>
Mehdi Kabab <http://pioupioum.fr/> Mehdi Kabab <http://pioupioum.fr/>
Nicolas Lassalle <nicolas@beroot.org> - Subversion support Nicolas Lassalle <nicolas@beroot.org> - Subversion support
Ozan <uobasar@gmail.com> - Turkish translation
Patrick Georgi <patrick.georgi@coresystems.de> Patrick Georgi <patrick.georgi@coresystems.de>
Pedro Kiefer <pedro@kiefer.com.br> - Brazilian Portuguese translation
Raphaël Emourgeon <raphael> Raphaël Emourgeon <raphael>
Samuel Suther <info@suther.de> - German translation Samuel Suther <info@suther.de> - German translation
Simon Holywell <treffynnon@php.net>
Sindre R. Myren <sindrero@stud.ntnu.no> Sindre R. Myren <sindrero@stud.ntnu.no>
Stewart Platt <stew@futurete.ch> Stewart Platt <stew@futurete.ch>
Stéphane Baron <sbaron> Stéphane Baron <sbaron>

View File

@ -6,9 +6,15 @@ the installation of InDefero by itself.
## PHP modules for indefero ## PHP modules for indefero
Indefero need the GD module for PHP. It's named "php5-gd" in debian. Indefero needs additional PHP modules to function correctly, namely
$ apt-get install php5-gd - gd (for graphic operations)
- zip (for upload archive processing)
The package names of these modules might vary between distributions,
for Debian they are
$ apt-get install php5-gd php5-zip
## Recommended Layout of the Files ## Recommended Layout of the Files

View File

@ -62,11 +62,11 @@ pot-update: pluf_path
fi fi
touch src/IDF/locale/idf.pot; touch src/IDF/locale/idf.pot;
# Extract string # Extract string
@cd src; php $(PLUF_PATH)/extracttemplates.php IDF/conf/idf.php IDF/gettexttemplates @cd src; php "$(PLUF_PATH)/extracttemplates.php" IDF/conf/idf.php IDF/gettexttemplates
@cd src; for phpfile in `find . -iname "*.php"`; do \ @cd src; for phpfile in `find . -iname "*.php"`; do \
printf "Parsing file : "$$phpfile"\n"; \ printf "Parsing file : "$$phpfile"\n"; \
xgettext -o idf.pot -p ./IDF/locale/ --from-code=UTF-8 -j \ xgettext -o idf.pot -p ./IDF/locale/ --from-code=UTF-8 -j \
--keyword --keyword=__ --keyword=_n:1,2 -L PHP $$phpfile ; \ --keyword --keyword=__ --keyword=_n:1,2 -L PHP "$$phpfile" ; \
done done
# Remove tmp folder # Remove tmp folder
rm -Rf src/IDF/gettexttemplates rm -Rf src/IDF/gettexttemplates
@ -76,7 +76,7 @@ pot-update: pluf_path
po-update: pluf_path po-update: pluf_path
@for pofile in `ls src/IDF/locale/*/idf.po`; do \ @for pofile in `ls src/IDF/locale/*/idf.po`; do \
printf "Updating file : "$$pofile"\n"; \ printf "Updating file : "$$pofile"\n"; \
msgmerge -v -U $$pofile src/IDF/locale/idf.pot; \ msgmerge -v -U "$$pofile" src/IDF/locale/idf.pot; \
printf "\n"; \ printf "\n"; \
done done
@ -144,7 +144,7 @@ po-stats:
--pretty=format:%h`.zip --pretty=format:%h`.zip
db-install: db-install:
@cd src && php $(PLUF_PATH)/migrate.php --conf=IDF/conf/idf.php -a -d -i @cd src && php "$(PLUF_PATH)/migrate.php" --conf=IDF/conf/idf.php -a -d -i
db-update: db-update:
@cd src && php $(PLUF_PATH)/migrate.php --conf=IDF/conf/idf.php -a -d @cd src && php "$(PLUF_PATH)/migrate.php" --conf=IDF/conf/idf.php -a -d

View File

@ -1,4 +1,76 @@
# InDefero 1.2 - xxx xxx xx xx:xx 2011 UTC # InDefero 1.3 - xxx xxx xx xx:xx 2011 UTC
The development of this version of Indefero has been sponsored
by the friendly folks from Scilab <http://www.scilab.org/>!
ATTENTION: You need Pluf [4121ca4](http://projects.ceondo.com/p/pluf/source/commit/4121ca4)
or newer to properly run this version of Indefero!
## Changes
- Indefero's post-commit web hook now by default issues HTTP PUT instead of
HTTP POST requests and carries the authentication digest in the new
`Web-Hook-Hmac` header. The old behaviour can be re-enabled by setting the
`$cfg['webhook_processing']` flag to "compat", we urge you to change the
implementations of this web hook as this setting is likely to be removed
in future versions of Indefero.
- Indefero now needs PHP's zip module which is not enabled by default.
- Existing email notifications now have to be explicitely activated in the
project's administrative area.
## New Features
- It is now possible to upload and embed resources like images or text
files into wiki pages. If no preview for a resource's mime type is
available, than a download link is provided for it instead.
- The notification system has been overhauled; it is now possible to configure
what kind of user group, project administrators, members and / or additional
mail addresses are notified about updates in a certain section, such as
issues, downloads, reviews, and so on. We now also ensure that notification
emails for one object are uniquely identifyable to support a grouped view
in email clients that support that. (fixes issues 334, 452, and 480)
- Indefero can now be configured to record activity metrics for all projects
in a forge. This needs a special cron job named 'activitycron.php`
(under `scripts`) that is run on a regular basis. The metrics can be
fine-tuned via `activity_section_weights` and `activity_lookback` in
`idf.php` and the result is visible as green bar in the project list view.
- The forge's project list has been overhauled - its now possible to attach
labels on projects and to filter and order the project list by various
criteria. Additionally, projects can now get an external project URL
configured that is displayed as linkable icon right beside the project name
(if available)
- Forge administrators can furthermore configure an alternative entry page
for the forge that is displayed instead of the plain project list. This
page accepts standard Markdown syntax and has support for the new
`projectlist` macro that allows the (partial) inline rendering of the
known global project list.
- It is now also possible to configure a web hook that informs an external
URL about new and updated downloads for a specific project, similar to the
available post-commit web hook.
- One can now upload multiple files at once by using a special archive format
which Indefero processes in the background and for which individual upload
records are created.
## Bugfixes
- Ensure that IDF does not break UTF-8 encoded strings when
shortening them for view rendering (issue 785)
- Indefero no longer confuses a non-owner of an issue with a notification that
a particular ticket has been opened and assigned to him (fixes issue 562)
# InDefero 1.2.1 - XXX XXX XX XX:XX:XX UTC 201X
## Bugfixes
- The diff view now renders properly in Firefox when a minimum font size
is configured or the user zooms the web page (fixes issue 773)
## Language and Translations
- Multiple fixes to English source strings (fixes issues 763, 766, and 772,
thanks to JP Fleury!)
# InDefero 1.2 - Sun Nov 6 23:04:00 UTC 2011
ATTENTION: You need Pluf [46b7f251](http://projects.ceondo.com/p/pluf/source/commit/46b7f251) ATTENTION: You need Pluf [46b7f251](http://projects.ceondo.com/p/pluf/source/commit/46b7f251)
or newer to properly run this version of Indefero! or newer to properly run this version of Indefero!
@ -50,9 +122,13 @@ or newer to properly run this version of Indefero!
middle (issue 697) middle (issue 697)
- Indefero now sends the MD5 checksum as HTTP header when downloading a file from the - Indefero now sends the MD5 checksum as HTTP header when downloading a file from the
download area; additionally, a unneeded redirect has been removed (issue 716) download area; additionally, a unneeded redirect has been removed (issue 716)
- Avatar URL generation use correctly the configuration (issue 732)
- Source links without a specific revision did not work due to a wrong regex - Source links without a specific revision did not work due to a wrong regex
(issue 730) (issue 730)
- Avatar URL generation use correctly the configuration (issue 732)
- The SyncGit plugin no longer fails to remove a non-existing post-update hook
on repository creation (issue 752)
- When uploading a project logo, an existing uploaded file with the same name
no longer leads to an error, but is simple overwritten (fixes issue 740)
- The error detection and reporting in the SyncMonotone plugin has been improved - The error detection and reporting in the SyncMonotone plugin has been improved
- The branch links users of the Subversion frontend get when they enter a wrong - The branch links users of the Subversion frontend get when they enter a wrong
revision are fixed; this list is now also only displayed (for any SCM) if revision are fixed; this list is now also only displayed (for any SCM) if
@ -71,6 +147,11 @@ or newer to properly run this version of Indefero!
## Translations ## Translations
- The Russian translation has been enabled by default (thanks for all the great
work, Denis Kot and Litew!)
- Brazilian Portuguese translation started (thanks to Pedro Kiefer!)
- Turkish translation started (thanks to Ozan!)
# InDefero 1.1.2 - Thu May 26 07:42:25 2011 UTC # InDefero 1.1.2 - Thu May 26 07:42:25 2011 UTC
## Bugfixes ## Bugfixes

284
logo/external_link.svg Normal file
View File

@ -0,0 +1,284 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="190.51302"
height="182.16527"
id="svg2"
version="1.1"
inkscape:version="0.47 r22583"
sodipodi:docname="external_link.svg"
inkscape:export-filename="/Users/tommyd/Entwicklung/Open Source/indefero/www/media/idf/img/external_link.png"
inkscape:export-xdpi="7.4108529"
inkscape:export-ydpi="7.4108529"
enable-background="new">
<defs
id="defs4">
<inkscape:path-effect
effect="skeletal"
id="path-effect4079"
is_visible="true"
pattern="M 0,5 10,10 10,0 z"
copytype="single_stretched"
prop_scale="1"
scale_y_rel="false"
spacing="0"
normal_offset="0"
tang_offset="0"
prop_units="false"
vertical_pattern="false"
fuse_tolerance="0" />
<inkscape:path-effect
effect="skeletal"
id="path-effect4071"
is_visible="true"
pattern="M 0,5 10,10 10,0 z"
copytype="single_stretched"
prop_scale="1"
scale_y_rel="false"
spacing="0"
normal_offset="0"
tang_offset="0"
prop_units="false"
vertical_pattern="false"
fuse_tolerance="0" />
<marker
inkscape:stockid="Arrow1Lend"
orient="auto"
refY="0"
refX="0"
id="Arrow1Lend"
style="overflow:visible">
<path
id="path3627"
d="M 0,0 5,-5 -12.5,0 5,5 0,0 z"
style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;marker-start:none"
transform="matrix(-0.8,0,0,-0.8,-10,0)" />
</marker>
<inkscape:path-effect
effect="skeletal"
id="path-effect3619"
is_visible="true"
pattern="M 0,5 10,10 10,0 z"
copytype="single_stretched"
prop_scale="1"
scale_y_rel="false"
spacing="0"
normal_offset="0"
tang_offset="0"
prop_units="false"
vertical_pattern="false"
fuse_tolerance="0" />
<inkscape:path-effect
effect="skeletal"
id="path-effect3615"
is_visible="true"
pattern="M 0,5 10,10 10,0 z"
copytype="single_stretched"
prop_scale="1"
scale_y_rel="false"
spacing="0"
normal_offset="0"
tang_offset="0"
prop_units="false"
vertical_pattern="false"
fuse_tolerance="0" />
<inkscape:path-effect
effect="skeletal"
id="path-effect3611"
is_visible="true"
pattern="M 0,5 10,10 10,0 z"
copytype="single_stretched"
prop_scale="1"
scale_y_rel="false"
spacing="0"
normal_offset="0"
tang_offset="0"
prop_units="false"
vertical_pattern="false"
fuse_tolerance="0" />
<inkscape:path-effect
effect="skeletal"
id="path-effect3607"
is_visible="true"
pattern="M 0,5 10,10 10,0 z"
copytype="single_stretched"
prop_scale="1"
scale_y_rel="false"
spacing="0"
normal_offset="0"
tang_offset="0"
prop_units="false"
vertical_pattern="false"
fuse_tolerance="0" />
<inkscape:path-effect
effect="skeletal"
id="path-effect3603"
is_visible="true"
pattern="M 0,5 10,10 10,0 z"
copytype="single_stretched"
prop_scale="1"
scale_y_rel="false"
spacing="0"
normal_offset="0"
tang_offset="0"
prop_units="false"
vertical_pattern="false"
fuse_tolerance="0" />
<inkscape:path-effect
effect="skeletal"
id="path-effect3599"
is_visible="true"
pattern="M 0,5 10,10 10,0 z"
copytype="single_stretched"
prop_scale="1"
scale_y_rel="false"
spacing="0"
normal_offset="0"
tang_offset="0"
prop_units="false"
vertical_pattern="false"
fuse_tolerance="0" />
<inkscape:path-effect
effect="skeletal"
id="path-effect3595"
is_visible="true"
pattern="M 0,5 10,10 10,0 z"
copytype="single_stretched"
prop_scale="1"
scale_y_rel="false"
spacing="0"
normal_offset="0"
tang_offset="0"
prop_units="false"
vertical_pattern="false"
fuse_tolerance="0" />
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 526.18109 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="744.09448 : 526.18109 : 1"
inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
id="perspective10" />
<inkscape:perspective
id="perspective4900"
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
inkscape:vp_z="1 : 0.5 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 0.5 : 1"
sodipodi:type="inkscape:persp3d" />
<filter
inkscape:collect="always"
id="filter4921"
x="-0.12982728"
width="1.2596545"
y="-0.092135489"
height="1.184271"
color-interpolation-filters="sRGB">
<feGaussianBlur
inkscape:collect="always"
stdDeviation="6.8004762"
id="feGaussianBlur4923" />
</filter>
<inkscape:perspective
id="perspective4933"
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
inkscape:vp_z="1 : 0.5 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 0.5 : 1"
sodipodi:type="inkscape:persp3d" />
<filter
color-interpolation-filters="sRGB"
inkscape:collect="always"
id="filter4921-3"
x="-0.12982728"
width="1.2596545"
y="-0.092135489"
height="1.184271">
<feGaussianBlur
inkscape:collect="always"
stdDeviation="6.8004762"
id="feGaussianBlur4923-8" />
</filter>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="1"
inkscape:pageshadow="2"
inkscape:zoom="1.78"
inkscape:cx="138.94497"
inkscape:cy="111.09667"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="1389"
inkscape:window-height="803"
inkscape:window-x="47"
inkscape:window-y="0"
inkscape:window-maximized="0" />
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:groupmode="layer"
id="layer2"
inkscape:label="Ebene"
transform="translate(16.297732,13.906157)"
style="display:inline"
sodipodi:insensitive="true">
<g
id="g2818-7"
style="fill:#e6e6e6;fill-opacity:1;stroke:#a0a0a0;stroke-width:2.26057386;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;filter:url(#filter4921-3)"
transform="matrix(0.97722285,0,0,-0.85847241,-166.837,487.59193)">
<rect
ry="9.2957697"
rx="8.07693"
y="389.50504"
x="171.42857"
height="177.14285"
width="125.71429"
id="rect2816-9"
style="opacity:0.79710143;fill:#000000;fill-opacity:1;stroke:#a0a0a0;stroke-width:2.26057386;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
</g>
</g>
<g
inkscape:label="Ebene 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-155.26623,-395.13431)"
style="display:inline;">
<rect
style="fill:#e6e6e6;fill-opacity:1;fill-rule:nonzero;stroke:#a0a0a0;stroke-width:2.41343927;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
id="rect4985"
width="129.92365"
height="152.95734"
x="168.88869"
y="407.28156"
rx="9.9356585"
ry="11.003931" />
<path
style="fill:#00000f;fill-opacity:0.94117647;stroke:none;stroke-width:2.17028474999999998;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
d="m 205.32036,460.1042 67.83031,0 0,-32.45771 71.54343,60.58773 -72.50161,61.39918 0,-33.26916 -66.75258,0 0,-56.26004"
id="path4081"
sodipodi:nodetypes="cccccccc" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.6 KiB

48
scripts/activitycron.php Normal file
View File

@ -0,0 +1,48 @@
<?php
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
# ***** BEGIN LICENSE BLOCK *****
# This file is part of InDefero, an open source project management application.
# Copyright (C) 2008-2011 Céondo Ltd and contributors.
#
# InDefero is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# InDefero is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
# ***** END LICENSE BLOCK ***** */
/**
* This script recalculates the "project activity" for all of the
* forge's projects for the given date.
* If no date is given, yesterday's date is used.
*
* This script should run once a day. You can configure its behaviour
* with $cfg['activity_section_weights'] and $cfg['activity_lookback'].
*
* If the script runs more than once with the same date argument,
* previously recorded project activity values are replaced with the
* newly created ones.
*/
require dirname(__FILE__).'/../src/IDF/conf/path.php';
require 'Pluf.php';
Pluf::start(dirname(__FILE__).'/../src/IDF/conf/idf.php');
$date = new DateTime('yesterday');
if (count($_SERVER['argv']) > 1) {
$date = new DateTime($_SERVER['argv'][1]);
}
echo 'recalculating project activity for '.$date->format('Y-m-d')."\n";
IDF_ActivityTaxonomy::recalculateTaxnomies($date);

View File

@ -0,0 +1,156 @@
<?php
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
# ***** BEGIN LICENSE BLOCK *****
# This file is part of InDefero, an open source project management application.
# Copyright (C) 2008-2011 Céondo Ltd and contributors.
#
# InDefero is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# InDefero is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
# ***** END LICENSE BLOCK ***** */
/**
* Class that calculates the activity value for all projects on a
* specific date and time.
*
* We do this by counting adds or updates of database objects in
* the particular section (according to the timeline) and relate this
* value to the overall activity of a section in the forge.
*
* To illustrate the behaviour, a simple example could be a forge with
* only two projects that both have only issue tracking enabled.
* The first project created or updated 10 tickets during the past period,
* the other 20. The activity index for the first should therefor be
* calculated as 0.33 and the second as 0.66.
* Note that this simple example doesn't take activity in other
* sections into account, so the the total activity of all projects
* for a certain time period might add up to more than 1.0.
*
* @author tommyd
*/
class IDF_ActivityTaxonomy
{
public static function recalculateTaxnomies(DateTime $date)
{
//
// query and normalize the section weights
//
$sectionWeights = Pluf::f('activity_section_weights', array());
$allWeights = array_sum($sectionWeights);
if ($allWeights == 0) {
throw new LogicException('the sum of all "activity_section_weights" must not be 0');
}
foreach ($sectionWeights as $section => $weight) {
$sectionWeights[$section] = $weight / (float) $allWeights;
}
//
// determine the date boundaries
//
$lookback = Pluf::f('activity_lookback', 0);
if ($lookback < 1) {
throw new LogicException('lookback must be greater or equal to 1');
}
$dateCopy = new DateTime();
$dateCopy->setTimestamp($date->getTimestamp());
$dateBoundaries = array(
$dateCopy->format('Y-m-d 23:59:59'),
$dateCopy->sub(new DateInterval('P'.$lookback.'D'))->format('Y-m-d 00:00:00')
);
//
// now recalculate the values for all projects
//
$projects = Pluf::factory('IDF_Project')->getList();
foreach ($projects as $project) {
self::recalculateTaxonomy($date, $project, $dateBoundaries, $sectionWeights);
}
}
private static function recalculateTaxonomy(DateTime $date, IDF_Project $project, array $dateBoundaries, array $sectionWeights)
{
$conf = new IDF_Conf();
$conf->setProject($project);
$sectionClasses = array(
'source' => array('IDF_Commit'),
'issues' => array('IDF_Issue'),
'wiki' => array('IDF_Wiki_Page', 'IDF_Wiki_Resource'),
'review' => array('IDF_Review'),
'downloads' => array('IDF_Upload')
);
$value = 0;
foreach ($sectionWeights as $section => $weight) {
// skip closed / non-existant sections
if ($conf->getVal($section.'_access_rights') === 'none')
continue;
if (!array_key_exists($section, $sectionClasses))
continue;
$sectionValue = self::calculateActivityValue(
$dateBoundaries, $sectionClasses[$section], $project->id);
$value = ((1 - $weight) * $value) + ($weight * $sectionValue);
}
echo "project {$project->name} has an activity value of $value\n";
$sql = new Pluf_SQL('project=%s AND date=%s', array($project->id, $date->format('Y-m-d')));
$activity = Pluf::factory('IDF_ProjectActivity')->getOne(array('filter' => $sql->gen()));
if ($activity == null) {
$activity = new IDF_ProjectActivity();
$activity->project = $project;
$activity->date = $date->format('Y-m-d');
$activity->value = $value;
$activity->create();
} else {
$activity->value = $value;
$activity->update();
}
}
private static function calculateActivityValue(array $dateBoundaries, array $classes, $projectId)
{
$allCount = self::countActivityFor($dateBoundaries, $classes);
if ($allCount == 0) return 0;
$prjCount = self::countActivityFor($dateBoundaries, $classes, $projectId);
return $prjCount / (float) $allCount;
}
private static function countActivityFor(array $dateBoundaries, array $classes, $projectId = null)
{
static $cache = array();
$argIdent = md5(serialize(func_get_args()));
if (array_key_exists($argIdent, $cache)) {
return $cache[$argIdent];
}
$cache[$argIdent] = 0;
list($higher, $lower) = $dateBoundaries;
$sql = new Pluf_SQL('model_class IN ("'.implode('","', $classes).'") '.
'AND creation_dtime >= %s AND creation_dtime <= %s',
array($lower, $higher));
if ($projectId !== null) {
$sql->SAnd(new Pluf_SQL('project=%s', array($projectId)));
}
$cache[$argIdent] = Pluf::factory('IDF_Timeline')->getCount(array('filter' => $sql->gen()));
return $cache[$argIdent];
}
}

View File

@ -235,7 +235,7 @@ class IDF_Commit extends Pluf_Model
</tr> </tr>
<tr class="extra"> <tr class="extra">
<td colspan="2"> <td colspan="2">
<div class="helptext right">'.sprintf(__('Commit&nbsp;%s, by %s'), '<a href="'.$url.'" class="mono">'.$this->scm_id.'</a>', $user).'</div></td></tr>'; <div class="helptext right">'.sprintf(__('Commit %1$s, by %2$s'), '<a href="'.$url.'" class="mono">'.$this->scm_id.'</a>', $user).'</div></td></tr>';
return Pluf_Template::markSafe($out); return Pluf_Template::markSafe($out);
} }
@ -287,6 +287,12 @@ class IDF_Commit extends Pluf_Model
$url = str_replace(array('%p', '%r'), $url = str_replace(array('%p', '%r'),
array($project->shortname, $this->scm_id), array($project->shortname, $this->scm_id),
$conf->getVal('webhook_url', '')); $conf->getVal('webhook_url', ''));
// trigger a POST instead of the standard PUT if we're asked for
$method = 'PUT';
if (Pluf::f('webhook_processing', '') === 'compat') {
$method = 'POST';
}
$payload = array('to_send' => array( $payload = array('to_send' => array(
'project' => $project->shortname, 'project' => $project->shortname,
'rev' => $this->scm_id, 'rev' => $this->scm_id,
@ -297,41 +303,51 @@ class IDF_Commit extends Pluf_Model
'creation_date' => $this->creation_dtime, 'creation_date' => $this->creation_dtime,
), ),
'project_id' => $project->id, 'project_id' => $project->id,
'authkey' => $project->getPostCommitHookKey(), 'authkey' => $project->getWebHookKey(),
'url' => $url, 'url' => $url,
'method' => $method,
); );
$item = new IDF_Queue(); $item = new IDF_Queue();
$item->type = 'new_commit'; $item->type = 'new_commit';
$item->payload = $payload; $item->payload = $payload;
$item->create(); $item->create();
if ('' == $conf->getVal('source_notification_email', '')) {
return;
}
$current_locale = Pluf_Translation::getLocale(); $current_locale = Pluf_Translation::getLocale();
$langs = Pluf::f('languages', array('en'));
Pluf_Translation::loadSetLocale($langs[0]);
$context = new Pluf_Template_Context( $from_email = Pluf::f('from_email');
array( $recipients = $project->getNotificationRecipientsForTab('source');
'c' => $this,
'project' => $this->get_project(), foreach ($recipients as $address => $language) {
'url_base' => Pluf::f('url_base'),
) if (!empty($this->author) && $this->author->email === $address) {
); continue;
$tmpl = new Pluf_Template('idf/source/commit-created-email.txt'); }
$text_email = $tmpl->render($context);
$addresses = explode(',', $conf->getVal('source_notification_email')); Pluf_Translation::loadSetLocale($language);
foreach ($addresses as $address) {
$email = new Pluf_Mail(Pluf::f('from_email'), $context = new Pluf_Template_Context(array(
'commit' => $this,
'project' => $project,
'url_base' => Pluf::f('url_base'),
));
// commits are usually not updated, therefor we do not
// distinguish between create and update here
$tplfile = 'idf/source/commit-created-email.txt';
$subject = __('New Commit %1$s - %2$s (%3$s)');
$tmpl = new Pluf_Template($tplfile);
$text_email = $tmpl->render($context);
$email = new Pluf_Mail($from_email,
$address, $address,
sprintf(__('New Commit %s - %s (%s)'), sprintf($subject,
$this->scm_id, $this->summary, $this->scm_id, $this->summary,
$this->get_project()->shortname)); $project->shortname));
$email->addTextMessage($text_email); $email->addTextMessage($text_email);
$email->sendMail(); $email->sendMail();
} }
Pluf_Translation::loadSetLocale($current_locale); Pluf_Translation::loadSetLocale($current_locale);
} }
} }

View File

@ -184,12 +184,12 @@ class IDF_Diff
$added = $added[0] + $added[1]; $added = $added[0] + $added[1];
$leftwidth = 0; $leftwidth = 0;
if ($added > 0) if ($added > 0)
$leftwidth = ((ceil(log10($added)) + 1) * 8) + 12; $leftwidth = ((ceil(log10($added)) + 1) * 8) + 17;
$removed = $removed[0] + $removed[1]; $removed = $removed[0] + $removed[1];
$rightwidth = 0; $rightwidth = 0;
if ($removed > 0) if ($removed > 0)
$rightwidth = ((ceil(log10($removed)) + 1) * 8) + 12; $rightwidth = ((ceil(log10($removed)) + 1) * 8) + 17;
// we need to correct the width of a single column a little // we need to correct the width of a single column a little
// to take less space and to hide the empty one // to take less space and to hide the empty one
@ -411,11 +411,11 @@ class IDF_Diff
$leftwidth = 1; $leftwidth = 1;
if ($max_lineno_left > 0) if ($max_lineno_left > 0)
$leftwidth = ((ceil(log10($max_lineno_left)) + 1) * 8) + 12; $leftwidth = ((ceil(log10($max_lineno_left)) + 1) * 8) + 17;
$rightwidth = 1; $rightwidth = 1;
if ($max_lineno_right > 0) if ($max_lineno_right > 0)
$rightwidth = ((ceil(log10($max_lineno_right)) + 1) * 8) + 12; $rightwidth = ((ceil(log10($max_lineno_right)) + 1) * 8) + 17;
$inner_linecounts_left = $inner_linecounts_left =
'<table class="diff-linecounts">' ."\n". '<table class="diff-linecounts">' ."\n".

95
src/IDF/Forge.php Normal file
View File

@ -0,0 +1,95 @@
<?php
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
# ***** BEGIN LICENSE BLOCK *****
# This file is part of InDefero, an open source project management application.
# Copyright (C) 2008-2011 Céondo Ltd and contributors.
#
# InDefero is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# InDefero is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
# ***** END LICENSE BLOCK ***** */
/**
* A lightweight model for the singleton forge entity
*/
class IDF_Forge
{
public $_model = __CLASS__;
public $id = 1;
/**
* @var IDF_Gconf
*/
private $conf;
private function __construct() {
$this->conf = new IDF_Gconf();
$this->conf->setModel($this);
}
public static function instance() {
return new IDF_Forge();
}
public function getProjectLabels($default = '') {
return $this->conf->getVal('project_labels', $default);
}
public function setProjectLabels($labels) {
$this->conf->setVal('project_labels', $labels);
}
public function getProjectLabelsWithCounts() {
$sql = new Pluf_SQL('project=0');
$tagList = Pluf::factory('IDF_Tag')->getList(array(
'filter' => $sql->gen(),
'view' => 'join_projects',
'order' => 'class ASC, lcname ASC'
));
$maxProjectCount = 0;
foreach ($tagList as $tag) {
$maxProjectCount = max($maxProjectCount, $tag->project_count);
}
$tags = array();
foreach ($tagList as $tag) {
// group by class
if (!array_key_exists($tag->class, $tags)) {
$tags[$tag->class] = array();
}
$tag->rel_project_count = $tag->project_count / (double) $maxProjectCount;
$tags[$tag->class][] = $tag;
}
return $tags;
}
public function setCustomForgePageEnabled($enabled) {
$this->conf->setVal('custom_forge_page_enabled', $enabled);
}
public function isCustomForgePageEnabled($default = false) {
return $this->conf->getVal('custom_forge_page_enabled', $default);
}
public function getCustomForgePageContent($default = '') {
return $this->conf->getVal('custom_forge_page_content', $default);
}
public function setCustomForgePageContent($content) {
$this->conf->setVal('custom_forge_page_content', $content);
}
}

View File

@ -0,0 +1,47 @@
<?php
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
# ***** BEGIN LICENSE BLOCK *****
# This file is part of InDefero, an open source project management application.
# Copyright(C) 2008-2011 Céondo Ltd and contributors.
#
# InDefero is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
#(at your option) any later version.
#
# InDefero is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
# ***** END LICENSE BLOCK ***** */
/**
* Configuration of the forge's start page.
*/
class IDF_Form_Admin_ForgeConf extends Pluf_Form
{
public function initFields($extra=array())
{
$this->fields['enabled'] = new Pluf_Form_Field_Boolean(
array('required' => false,
'label' => __('Custom forge page enabled'),
'widget' => 'Pluf_Form_Widget_CheckboxInput',
));
$this->fields['content'] = new Pluf_Form_Field_Varchar(
array('required' => true,
'label' => __('Content'),
'widget' => 'Pluf_Form_Widget_TextareaInput',
'widget_attrs' => array(
'cols' => 68,
'rows' => 26,
),
));
}
}

View File

@ -0,0 +1,62 @@
<?php
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
# ***** BEGIN LICENSE BLOCK *****
# This file is part of InDefero, an open source project management application.
# Copyright(C) 2008-2011 Céondo Ltd and contributors.
#
# InDefero is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
#(at your option) any later version.
#
# InDefero is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
# ***** END LICENSE BLOCK ***** */
/**
* Configuration of forge labels.
*/
class IDF_Form_Admin_LabelConf extends Pluf_Form
{
const init_project_labels = 'UI:GUI = Applications with graphical user interfaces
UI:CLI = Applications with no graphical user interfaces
License:BSD = Applications with BSD license
License:GPL = Applications with GPL license
';
public function initFields($extra=array())
{
$this->fields['project_labels'] = new Pluf_Form_Field_Varchar(
array('required' => true,
'label' => __('Predefined project labels'),
'initial' => self::init_project_labels,
'widget_attrs' => array('rows' => 13,
'cols' => 75),
'widget' => 'Pluf_Form_Widget_TextareaInput',
));
}
public function clean_project_labels()
{
$labels = preg_split("/\s*\n\s*/", $this->cleaned_data['project_labels'], -1, PREG_SPLIT_NO_EMPTY);
for ($i=0; $i<count($labels); ++$i) {
$labels[$i] = trim($labels[$i]);
if (!preg_match('/^[\w-]+(:[\w-]+)?(\s*=\s*[^=]+)?$/', $labels[$i])) {
throw new Pluf_Form_Invalid(sprintf(
__('The label "%s" is invalid: A label must only consist of alphanumeric '.
'characters and dashes, and can optionally contain a ":" with a group prefix.'),
$labels[$i]));
}
}
return implode("\n", $labels);
}
}

View File

@ -72,6 +72,13 @@ class IDF_Form_Admin_ProjectCreate extends Pluf_Form
'widget_attrs' => array('size' => '35'), 'widget_attrs' => array('size' => '35'),
)); ));
$this->fields['external_project_url'] = new Pluf_Form_Field_Varchar(
array('required' => false,
'label' => __('External URL'),
'widget_attrs' => array('size' => '35'),
'initial' => '',
));
$this->fields['scm'] = new Pluf_Form_Field_Varchar( $this->fields['scm'] = new Pluf_Form_Field_Varchar(
array('required' => true, array('required' => true,
'label' => __('Repository type'), 'label' => __('Repository type'),
@ -127,6 +134,18 @@ class IDF_Form_Admin_ProjectCreate extends Pluf_Form
'widget' => 'Pluf_Form_Widget_TextareaInput', 'widget' => 'Pluf_Form_Widget_TextareaInput',
)); ));
for ($i=1;$i<7;$i++) {
$this->fields['label'.$i] = new Pluf_Form_Field_Varchar(
array('required' => false,
'label' => __('Labels'),
'initial' => '',
'widget_attrs' => array(
'maxlength' => 50,
'size' => 20,
),
));
}
$projects = array('--' => '--'); $projects = array('--' => '--');
foreach (Pluf::factory('IDF_Project')->getList(array('order' => 'name ASC')) as $proj) { foreach (Pluf::factory('IDF_Project')->getList(array('order' => 'name ASC')) as $proj) {
$projects[$proj->name] = $proj->shortname; $projects[$proj->name] = $proj->shortname;
@ -199,7 +218,7 @@ class IDF_Form_Admin_ProjectCreate extends Pluf_Form
$mtn_master_branch)) { $mtn_master_branch)) {
throw new Pluf_Form_Invalid(__( throw new Pluf_Form_Invalid(__(
'The master branch is empty or contains illegal characters, '. 'The master branch is empty or contains illegal characters, '.
'please use only letters, digits, dashs and dots as separators.' 'please use only letters, digits, dashes and dots as separators.'
)); ));
} }
@ -235,6 +254,11 @@ class IDF_Form_Admin_ProjectCreate extends Pluf_Form
return $shortname; return $shortname;
} }
public function clean_external_project_url()
{
return IDF_Form_ProjectConf::checkWebURL($this->cleaned_data['external_project_url']);
}
public function clean() public function clean()
{ {
if ($this->cleaned_data['scm'] != 'svn') { if ($this->cleaned_data['scm'] != 'svn') {
@ -278,11 +302,29 @@ class IDF_Form_Admin_ProjectCreate extends Pluf_Form
if (!$this->isValid()) { if (!$this->isValid()) {
throw new Exception(__('Cannot save the model from an invalid form.')); throw new Exception(__('Cannot save the model from an invalid form.'));
} }
// Add a tag for each label
$tagids = array();
for ($i=1;$i<7;$i++) {
if (strlen($this->cleaned_data['label'.$i]) > 0) {
if (strpos($this->cleaned_data['label'.$i], ':') !== false) {
list($class, $name) = explode(':', $this->cleaned_data['label'.$i], 2);
list($class, $name) = array(trim($class), trim($name));
} else {
$class = 'Other';
$name = trim($this->cleaned_data['label'.$i]);
}
$tag = IDF_Tag::addGlobal($name, $class);
$tagids[] = $tag->id;
}
}
$project = new IDF_Project(); $project = new IDF_Project();
$project->name = $this->cleaned_data['name']; $project->name = $this->cleaned_data['name'];
$project->shortname = $this->cleaned_data['shortname']; $project->shortname = $this->cleaned_data['shortname'];
$project->shortdesc = $this->cleaned_data['shortdesc']; $project->shortdesc = $this->cleaned_data['shortdesc'];
$tagids = array();
if ($this->cleaned_data['template'] != '--') { if ($this->cleaned_data['template'] != '--') {
// Find the template project // Find the template project
$sql = new Pluf_SQL('shortname=%s', $sql = new Pluf_SQL('shortname=%s',
@ -290,15 +332,36 @@ class IDF_Form_Admin_ProjectCreate extends Pluf_Form
$tmpl = Pluf::factory('IDF_Project')->getOne(array('filter' => $sql->gen())); $tmpl = Pluf::factory('IDF_Project')->getOne(array('filter' => $sql->gen()));
$project->private = $tmpl->private; $project->private = $tmpl->private;
$project->description = $tmpl->description; $project->description = $tmpl->description;
foreach ($tmpl->get_tags_list() as $tag) {
$tagids[] = $tag->id;
}
} else { } else {
$project->private = $this->cleaned_data['private_project']; $project->private = $this->cleaned_data['private_project'];
$project->description = __('Click on the Project Management tab to set the description of your project.'); $project->description = __('Click on the Project Management tab to set the description of your project.');
// Add a tag for each label
for ($i=1;$i<7;$i++) {
if (strlen($this->cleaned_data['label'.$i]) > 0) {
if (strpos($this->cleaned_data['label'.$i], ':') !== false) {
list($class, $name) = explode(':', $this->cleaned_data['label'.$i], 2);
list($class, $name) = array(trim($class), trim($name));
} else {
$class = 'Other';
$name = trim($this->cleaned_data['label'.$i]);
}
$tag = IDF_Tag::addGlobal($name, $class);
$tagids[] = $tag->id;
}
}
} }
$project->create(); $project->create();
$project->batchAssoc('IDF_Tag', $tagids);
$conf = new IDF_Conf(); $conf = new IDF_Conf();
$conf->setProject($project); $conf->setProject($project);
$keys = array('scm', 'svn_remote_url', 'svn_username', $keys = array('scm', 'svn_remote_url', 'svn_username',
'svn_password', 'mtn_master_branch'); 'svn_password', 'mtn_master_branch', 'external_project_url');
foreach ($keys as $key) { foreach ($keys as $key) {
$this->cleaned_data[$key] = (!empty($this->cleaned_data[$key])) ? $this->cleaned_data[$key] = (!empty($this->cleaned_data[$key])) ?
$this->cleaned_data[$key] : ''; $this->cleaned_data[$key] : '';

View File

@ -53,6 +53,13 @@ class IDF_Form_Admin_ProjectUpdate extends Pluf_Form
'widget_attrs' => array('size' => '35'), 'widget_attrs' => array('size' => '35'),
)); ));
$this->fields['external_project_url'] = new Pluf_Form_Field_Varchar(
array('required' => false,
'label' => __('External URL'),
'widget_attrs' => array('size' => '35'),
'initial' => $conf->getVal('external_project_url'),
));
if ($this->project->getConf()->getVal('scm') == 'mtn') { if ($this->project->getConf()->getVal('scm') == 'mtn') {
$this->fields['mtn_master_branch'] = new Pluf_Form_Field_Varchar( $this->fields['mtn_master_branch'] = new Pluf_Form_Field_Varchar(
array('required' => false, array('required' => false,
@ -63,6 +70,26 @@ class IDF_Form_Admin_ProjectUpdate extends Pluf_Form
)); ));
} }
$tags = $this->project->get_tags_list();
for ($i=1;$i<7;$i++) {
$initial = '';
if (isset($tags[$i-1])) {
if ($tags[$i-1]->class != 'Other') {
$initial = (string) $tags[$i-1];
} else {
$initial = $tags[$i-1]->name;
}
}
$this->fields['label'.$i] = new Pluf_Form_Field_Varchar(
array('required' => false,
'label' => __('Labels'),
'initial' => $initial,
'widget_attrs' => array(
'maxlength' => 50,
'size' => 20,
),
));
}
$this->fields['owners'] = new Pluf_Form_Field_Varchar( $this->fields['owners'] = new Pluf_Form_Field_Varchar(
array('required' => false, array('required' => false,
'label' => __('Project owners'), 'label' => __('Project owners'),
@ -115,22 +142,52 @@ class IDF_Form_Admin_ProjectUpdate extends Pluf_Form
return IDF_Form_MembersConf::checkBadLogins($this->cleaned_data['members']); return IDF_Form_MembersConf::checkBadLogins($this->cleaned_data['members']);
} }
public function clean_external_project_url()
{
return IDF_Form_ProjectConf::checkWebURL($this->cleaned_data['external_project_url']);
}
public function save($commit=true) public function save($commit=true)
{ {
if (!$this->isValid()) { if (!$this->isValid()) {
throw new Exception(__('Cannot save the model from an invalid form.')); throw new Exception(__('Cannot save the model from an invalid form.'));
} }
// Add a tag for each label
$tagids = array();
for ($i=1;$i<7;$i++) {
if (strlen($this->cleaned_data['label'.$i]) > 0) {
if (strpos($this->cleaned_data['label'.$i], ':') !== false) {
list($class, $name) = explode(':', $this->cleaned_data['label'.$i], 2);
list($class, $name) = array(trim($class), trim($name));
} else {
$class = 'Other';
$name = trim($this->cleaned_data['label'.$i]);
}
$tag = IDF_Tag::addGlobal($name, $class);
$tagids[] = $tag->id;
}
}
$this->project->batchAssoc('IDF_Tag', $tagids);
IDF_Form_MembersConf::updateMemberships($this->project, IDF_Form_MembersConf::updateMemberships($this->project,
$this->cleaned_data); $this->cleaned_data);
$this->project->membershipsUpdated(); $this->project->membershipsUpdated();
$this->project->name = $this->cleaned_data['name']; $this->project->name = $this->cleaned_data['name'];
$this->project->shortdesc = $this->cleaned_data['shortdesc']; $this->project->shortdesc = $this->cleaned_data['shortdesc'];
$this->project->update(); $this->project->update();
$keys = array('mtn_master_branch'); $conf = $this->project->getConf();
$keys = array('mtn_master_branch', 'external_project_url');
foreach ($keys as $key) { foreach ($keys as $key) {
if (!empty($this->cleaned_data[$key])) { if (array_key_exists($key, $this->cleaned_data)) {
$this->project->getConf()->setVal($key, $this->cleaned_data[$key]); if (!empty($this->cleaned_data[$key])) {
$conf->setVal($key, $this->cleaned_data[$key]);
}
else {
$conf->delVal($key);
}
} }
} }
} }

View File

@ -161,7 +161,7 @@ class IDF_Form_Admin_UserUpdate extends Pluf_Form
'label' => __('Staff'), 'label' => __('Staff'),
'initial' => $this->user->staff, 'initial' => $this->user->staff,
'widget' => 'Pluf_Form_Widget_CheckboxInput', 'widget' => 'Pluf_Form_Widget_CheckboxInput',
'help_text' => __('If you give staff rights to a user, you really need to trust them.'), 'help_text' => __('If you give staff rights to a user, you really need to trust him.'),
)); ));
} }

View File

@ -214,7 +214,7 @@ class IDF_Form_IssueCreate extends Pluf_Form
else $count[$class] += 1; else $count[$class] += 1;
if (in_array($class, $onemax) and $count[$class] > 1) { if (in_array($class, $onemax) and $count[$class] > 1) {
if (!isset($this->errors['label'.$i])) $this->errors['label'.$i] = array(); if (!isset($this->errors['label'.$i])) $this->errors['label'.$i] = array();
$this->errors['label'.$i][] = sprintf(__('You cannot provide more than label from the %s class to an issue.'), $class); $this->errors['label'.$i][] = sprintf(__('You cannot provide more than one label from the %s class to an issue.'), $class);
throw new Pluf_Form_Invalid(__('You provided an invalid label.')); throw new Pluf_Form_Invalid(__('You provided an invalid label.'));
} }
} }

View File

@ -130,7 +130,7 @@ duplicates, is duplicated by';
$this->fields['labels_issue_one_max'] = new Pluf_Form_Field_Varchar( $this->fields['labels_issue_one_max'] = new Pluf_Form_Field_Varchar(
array('required' => false, array('required' => false,
'label' => __('Each issue may have at most one label with each of these classes'), 'label' => __('Each issue may have at most one label with each of these classes.'),
'initial' => self::init_one_max, 'initial' => self::init_one_max,
'widget_attrs' => array('size' => 60), 'widget_attrs' => array('size' => 60),
)); ));
@ -139,7 +139,7 @@ duplicates, is duplicated by';
array('required' => true, array('required' => true,
'label' => __('Issue relations'), 'label' => __('Issue relations'),
'initial' => self::init_relations, 'initial' => self::init_relations,
'help_text' => __('You can define bidirectional relations like "is related to" or "blocks, is blocked by".'), 'help_text' => __('You can define bidirectional relations like "is related to" or "blocks, is blocked by". For standard relations pre-configured translations exist, new relations should however be defined in a language that is understood by all project members.'),
'widget_attrs' => array('rows' => 7, 'widget_attrs' => array('rows' => 7,
'cols' => 75), 'cols' => 75),
'widget' => 'Pluf_Form_Widget_TextareaInput', 'widget' => 'Pluf_Form_Widget_TextareaInput',

View File

@ -32,6 +32,7 @@ class IDF_Form_ProjectConf extends Pluf_Form
public function initFields($extra=array()) public function initFields($extra=array())
{ {
$this->project = $extra['project']; $this->project = $extra['project'];
$conf = $this->project->getConf();
// Basic part // Basic part
$this->fields['name'] = new Pluf_Form_Field_Varchar(array('required' => true, $this->fields['name'] = new Pluf_Form_Field_Varchar(array('required' => true,
@ -51,6 +52,32 @@ class IDF_Form_ProjectConf extends Pluf_Form
), ),
'widget' => 'Pluf_Form_Widget_TextareaInput', 'widget' => 'Pluf_Form_Widget_TextareaInput',
)); ));
$this->fields['external_project_url'] = new Pluf_Form_Field_Varchar(array('required' => false,
'label' => __('External URL'),
'widget_attrs' => array('size' => '68'),
'initial' => $conf->getVal('external_project_url'),
));
$tags = $this->project->get_tags_list();
for ($i=1;$i<7;$i++) {
$initial = '';
if (isset($tags[$i-1])) {
if ($tags[$i-1]->class != 'Other') {
$initial = (string) $tags[$i-1];
} else {
$initial = $tags[$i-1]->name;
}
}
$this->fields['label'.$i] = new Pluf_Form_Field_Varchar(
array('required' => false,
'label' => __('Labels'),
'initial' => $initial,
'widget_attrs' => array(
'maxlength' => 50,
'size' => 20,
),
));
}
// Logo part // Logo part
$upload_path = Pluf::f('upload_path', false); $upload_path = Pluf::f('upload_path', false);
@ -67,6 +94,7 @@ class IDF_Form_ProjectConf extends Pluf_Form
'move_function_params' => 'move_function_params' =>
array('upload_path' => $upload_path, array('upload_path' => $upload_path,
'upload_path_create' => true, 'upload_path_create' => true,
'upload_overwrite' => true,
'file_name' => $filename, 'file_name' => $filename,
) )
)); ));
@ -118,20 +146,63 @@ class IDF_Form_ProjectConf extends Pluf_Form
return $this->cleaned_data['logo']; return $this->cleaned_data['logo'];
} }
public function clean_external_project_url()
{
return self::checkWebURL($this->cleaned_data['external_project_url']);
}
public static function checkWebURL($url)
{
$url = trim($url);
if (empty($url)) {
return '';
}
$parsed = parse_url($url);
if ($parsed === false || !array_key_exists('scheme', $parsed) ||
($parsed['scheme'] != 'http' && $parsed['scheme'] != 'https')) {
throw new Pluf_Form_Invalid(__('The entered URL is invalid. Only http and https URLs are allowed.'));
}
return $url;
}
public function save($commit=true) public function save($commit=true)
{ {
$conf = $this->project->getConf(); // Add a tag for each label
$tagids = array();
for ($i=1;$i<7;$i++) {
if (strlen($this->cleaned_data['label'.$i]) > 0) {
if (strpos($this->cleaned_data['label'.$i], ':') !== false) {
list($class, $name) = explode(':', $this->cleaned_data['label'.$i], 2);
list($class, $name) = array(trim($class), trim($name));
} else {
$class = 'Other';
$name = trim($this->cleaned_data['label'.$i]);
}
$tag = IDF_Tag::addGlobal($name, $class);
$tagids[] = $tag->id;
}
}
// Basic part // Basic part
$this->project->name = $this->cleaned_data['name']; $this->project->name = $this->cleaned_data['name'];
$this->project->shortdesc = $this->cleaned_data['shortdesc']; $this->project->shortdesc = $this->cleaned_data['shortdesc'];
$this->project->description = $this->cleaned_data['description']; $this->project->description = $this->cleaned_data['description'];
$this->project->batchAssoc('IDF_Tag', $tagids);
$this->project->update(); $this->project->update();
// Logo part $conf = $this->project->getConf();
if ($this->cleaned_data['logo'] !== "") { if (!empty($this->cleaned_data['logo'])) {
$conf->setVal('logo', $this->cleaned_data['logo']); $conf->setVal('logo', $this->cleaned_data['logo']);
} }
if (!empty($this->cleaned_data['external_project_url'])) {
$conf->setVal('external_project_url', $this->cleaned_data['external_project_url']);
}
else {
$conf->delVal('external_project_url');
}
if ($this->cleaned_data['logo_remove'] === true) { if ($this->cleaned_data['logo_remove'] === true) {
@unlink(Pluf::f('upload_path') . '/' . $this->project->shortname . $conf->getVal('logo')); @unlink(Pluf::f('upload_path') . '/' . $this->project->shortname . $conf->getVal('logo'));
$conf->delVal('logo'); $conf->delVal('logo');

View File

@ -94,7 +94,7 @@ class IDF_Form_Register extends Pluf_Form
{ {
$this->cleaned_data['email'] = mb_strtolower(trim($this->cleaned_data['email'])); $this->cleaned_data['email'] = mb_strtolower(trim($this->cleaned_data['email']));
if (Pluf::factory('IDF_EmailAddress')->get_user_for_email_address($this->cleaned_data['email']) != null) { if (Pluf::factory('IDF_EmailAddress')->get_user_for_email_address($this->cleaned_data['email']) != null) {
throw new Pluf_Form_Invalid(sprintf(__('The email "%s" is already used. If you need to, click on the help link to recover your password.'), $this->cleaned_data['email'])); throw new Pluf_Form_Invalid(sprintf(__('The email "%1$s" is already used. If you need to, you can <a href="%2$s">recover your password</a>.'), $this->cleaned_data['email'], Pluf_HTTP_URL_urlForView('IDF_Views::passwordRecoveryAsk')));
} }
return $this->cleaned_data['email']; return $this->cleaned_data['email'];
} }

View File

@ -107,7 +107,7 @@ class IDF_Form_RegisterConfirmation extends Pluf_Form
throw new Pluf_Form_Invalid($error); throw new Pluf_Form_Invalid($error);
} }
if ($users[0]->active) { if ($users[0]->active) {
throw new Pluf_Form_Invalid(__('This account has already been confirmed. Maybe should you try to recover your password using the help link.')); throw new Pluf_Form_Invalid(sprintf(__('This account has already been confirmed. Maybe should you try to <a href="%s">recover your password</a>.'), Pluf_HTTP_URL_urlForView('IDF_Views::passwordRecoveryAsk')));
} }
$this->_user_id = $email_id[1]; $this->_user_id = $email_id[1];
return $this->cleaned_data['key']; return $this->cleaned_data['key'];

View File

@ -49,13 +49,10 @@ class IDF_Form_SourceConf extends Pluf_Form
'widget' => 'Pluf_Form_Widget_PasswordInput', 'widget' => 'Pluf_Form_Widget_PasswordInput',
)); ));
} }
Pluf::loadFunction('Pluf_HTTP_URL_urlForView');
$url = Pluf_HTTP_URL_urlForView('idf_faq').'#webhooks';
$this->fields['webhook_url'] = new Pluf_Form_Field_Url( $this->fields['webhook_url'] = new Pluf_Form_Field_Url(
array('required' => false, array('required' => false,
'label' => __('Webhook URL'), 'label' => __('Webhook URL'),
'initial' => $this->conf->getVal('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), 'widget_attrs' => array('size' => 35),
)); ));

View File

@ -57,20 +57,44 @@ class IDF_Form_TabsConf extends Pluf_Form
'widget' => 'Pluf_Form_Widget_SelectInput', 'widget' => 'Pluf_Form_Widget_SelectInput',
)); ));
} }
$ak = array('downloads_notification_email',
'review_notification_email',
'wiki_notification_email',
'source_notification_email',
'issues_notification_email',);
foreach ($ak as $key) {
$this->fields[$key] = new IDF_Form_Field_EmailList(
array('required' => false,
'label' => $key,
'initial' => $this->conf->getVal($key, ''),
'widget_attrs' => array('size' => 40),
));
}
$sections = array(
'downloads_notification',
'review_notification',
'wiki_notification',
'source_notification',
'issues_notification',
);
foreach ($sections as $section) {
$this->fields[$section.'_owners_enabled'] = new Pluf_Form_Field_Boolean(
array('required' => false,
'label' => __('Project owners'),
'initial' => $this->conf->getVal($section.'_owners_enabled', false),
'widget' => 'Pluf_Form_Widget_CheckboxInput',
));
$this->fields[$section.'_members_enabled'] = new Pluf_Form_Field_Boolean(
array('required' => false,
'label' => __('Project members'),
'initial' => $this->conf->getVal($section.'_members_enabled', false),
'widget' => 'Pluf_Form_Widget_CheckboxInput',
));
$this->fields[$section.'_email_enabled'] = new Pluf_Form_Field_Boolean(
array('required' => false,
'label' => __('Others'),
'initial' => $this->conf->getVal($section.'_email_enabled', false),
'widget' => 'Pluf_Form_Widget_CheckboxInput',
));
if ($this->conf->getVal($section.'_email_enabled', false)) {
$attrs['readonly'] = 'readonly';
}
$this->fields[$section.'_email'] = new IDF_Form_Field_EmailList(
array('required' => false,
'label' => null,
'initial' => $this->conf->getVal($section.'_email', ''),
'widget_attrs' => array('size' => 20),
));
}
$this->fields['private_project'] = new Pluf_Form_Field_Boolean( $this->fields['private_project'] = new Pluf_Form_Field_Boolean(
array('required' => false, array('required' => false,

View File

@ -96,7 +96,7 @@ class IDF_Form_UpdateUpload extends Pluf_Form
$this->cleaned_data['label'.$i] = trim($this->cleaned_data['label'.$i]); $this->cleaned_data['label'.$i] = trim($this->cleaned_data['label'.$i]);
if (strpos($this->cleaned_data['label'.$i], ':') !== false) { if (strpos($this->cleaned_data['label'.$i], ':') !== false) {
list($class, $name) = explode(':', $this->cleaned_data['label'.$i], 2); list($class, $name) = explode(':', $this->cleaned_data['label'.$i], 2);
list($class, $name) = array(mb_strtolower(trim($class)), list($class, $name) = array(mb_strtolower(trim($class)),
trim($name)); trim($name));
} else { } else {
$class = 'other'; $class = 'other';
@ -106,7 +106,7 @@ class IDF_Form_UpdateUpload extends Pluf_Form
else $count[$class] += 1; else $count[$class] += 1;
if (in_array($class, $onemax) and $count[$class] > 1) { if (in_array($class, $onemax) and $count[$class] > 1) {
if (!isset($this->errors['label'.$i])) $this->errors['label'.$i] = array(); if (!isset($this->errors['label'.$i])) $this->errors['label'.$i] = array();
$this->errors['label'.$i][] = sprintf(__('You cannot provide more than label from the %s class to an issue.'), $class); $this->errors['label'.$i][] = sprintf(__('You cannot provide more than one label from the %s class to an issue.'), $class);
throw new Pluf_Form_Invalid(__('You provided an invalid label.')); throw new Pluf_Form_Invalid(__('You provided an invalid label.'));
} }
} }
@ -146,6 +146,9 @@ class IDF_Form_UpdateUpload extends Pluf_Form
$this->upload->modif_dtime = gmdate('Y-m-d H:i:s'); $this->upload->modif_dtime = gmdate('Y-m-d H:i:s');
$this->upload->update(); $this->upload->update();
$this->upload->batchAssoc('IDF_Tag', $tags); $this->upload->batchAssoc('IDF_Tag', $tags);
// Send the notification
$this->upload->notify($this->project->getConf(), false);
/** /**
* [signal] * [signal]
* *
@ -166,7 +169,7 @@ class IDF_Form_UpdateUpload extends Pluf_Form
* *
*/ */
$params = array('upload' => $this->upload); $params = array('upload' => $this->upload);
Pluf_Signal::send('IDF_Upload::update', Pluf_Signal::send('IDF_Upload::update',
'IDF_Form_UpdateUpload', $params); 'IDF_Form_UpdateUpload', $params);
return $this->upload; return $this->upload;
} }

View File

@ -79,6 +79,7 @@ class IDF_Form_Upload extends Pluf_Form
public function clean_file() public function clean_file()
{ {
// FIXME: we do the same in IDF_Form_WikiResourceCreate and a couple of other places as well
$extra = strtolower(implode('|', explode(' ', Pluf::f('idf_extra_upload_ext')))); $extra = strtolower(implode('|', explode(' ', Pluf::f('idf_extra_upload_ext'))));
if (strlen($extra)) $extra .= '|'; if (strlen($extra)) $extra .= '|';
if (!preg_match('/\.('.$extra.'png|jpg|jpeg|gif|bmp|psd|tif|aiff|asf|avi|bz2|css|doc|eps|gz|jar|mdtext|mid|mov|mp3|mpg|ogg|pdf|ppt|ps|qt|ra|ram|rm|rtf|sdd|sdw|sit|sxi|sxw|swf|tgz|txt|wav|xls|xml|war|wmv|zip)$/i', $this->cleaned_data['file'])) { if (!preg_match('/\.('.$extra.'png|jpg|jpeg|gif|bmp|psd|tif|aiff|asf|avi|bz2|css|doc|eps|gz|jar|mdtext|mid|mov|mp3|mpg|ogg|pdf|ppt|ps|qt|ra|ram|rm|rtf|sdd|sdw|sit|sxi|sxw|swf|tgz|txt|wav|xls|xml|war|wmv|zip)$/i', $this->cleaned_data['file'])) {
@ -106,7 +107,7 @@ class IDF_Form_Upload extends Pluf_Form
$this->cleaned_data['label'.$i] = trim($this->cleaned_data['label'.$i]); $this->cleaned_data['label'.$i] = trim($this->cleaned_data['label'.$i]);
if (strpos($this->cleaned_data['label'.$i], ':') !== false) { if (strpos($this->cleaned_data['label'.$i], ':') !== false) {
list($class, $name) = explode(':', $this->cleaned_data['label'.$i], 2); list($class, $name) = explode(':', $this->cleaned_data['label'.$i], 2);
list($class, $name) = array(mb_strtolower(trim($class)), list($class, $name) = array(mb_strtolower(trim($class)),
trim($name)); trim($name));
} else { } else {
$class = 'other'; $class = 'other';
@ -116,7 +117,7 @@ class IDF_Form_Upload extends Pluf_Form
else $count[$class] += 1; else $count[$class] += 1;
if (in_array($class, $onemax) and $count[$class] > 1) { if (in_array($class, $onemax) and $count[$class] > 1) {
if (!isset($this->errors['label'.$i])) $this->errors['label'.$i] = array(); if (!isset($this->errors['label'.$i])) $this->errors['label'.$i] = array();
$this->errors['label'.$i][] = sprintf(__('You cannot provide more than label from the %s class to an issue.'), $class); $this->errors['label'.$i][] = sprintf(__('You cannot provide more than one label from the %s class to a download.'), $class);
throw new Pluf_Form_Invalid(__('You provided an invalid label.')); throw new Pluf_Form_Invalid(__('You provided an invalid label.'));
} }
} }
@ -129,7 +130,7 @@ class IDF_Form_Upload extends Pluf_Form
*/ */
function failed() function failed()
{ {
if (!empty($this->cleaned_data['file']) if (!empty($this->cleaned_data['file'])
and file_exists(Pluf::f('upload_path').'/'.$this->project->shortname.'/files/'.$this->cleaned_data['file'])) { and file_exists(Pluf::f('upload_path').'/'.$this->project->shortname.'/files/'.$this->cleaned_data['file'])) {
@unlink(Pluf::f('upload_path').'/'.$this->project->shortname.'/files/'.$this->cleaned_data['file']); @unlink(Pluf::f('upload_path').'/'.$this->project->shortname.'/files/'.$this->cleaned_data['file']);
} }

View File

@ -0,0 +1,227 @@
<?php
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
# ***** BEGIN LICENSE BLOCK *****
# This file is part of InDefero, an open source project management application.
# Copyright (C) 2008-2011 Céondo Ltd and contributors.
#
# InDefero is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# InDefero is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
# ***** END LICENSE BLOCK ***** */
/**
* Upload and process an archive file.
*
*/
class IDF_Form_UploadArchive extends Pluf_Form
{
public $user = null;
public $project = null;
private $archiveHelper = null;
public function initFields($extra=array())
{
$this->user = $extra['user'];
$this->project = $extra['project'];
$this->fields['archive'] = new Pluf_Form_Field_File(
array('required' => true,
'label' => __('Archive file'),
'initial' => '',
'max_size' => Pluf::f('max_upload_archive_size', 20971520),
'move_function_params' => array(
'upload_path' => Pluf::f('upload_path').'/'.$this->project->shortname.'/archives',
'upload_path_create' => true,
'upload_overwrite' => true,
)));
}
public function clean_archive()
{
$this->archiveHelper = new IDF_Form_UploadArchiveHelper(
Pluf::f('upload_path').'/'.$this->project->shortname.'/archives/'.$this->cleaned_data['archive']);
// basic archive validation
$this->archiveHelper->validate();
// extension validation
$fileNames = $this->archiveHelper->getEntryNames();
foreach ($fileNames as $fileName) {
$extra = strtolower(implode('|', explode(' ', Pluf::f('idf_extra_upload_ext'))));
if (strlen($extra)) $extra .= '|';
if (!preg_match('/\.('.$extra.'png|jpg|jpeg|gif|bmp|psd|tif|aiff|asf|avi|bz2|css|doc|eps|gz|jar|mdtext|mid|mov|mp3|mpg|ogg|pdf|ppt|ps|qt|ra|ram|rm|rtf|sdd|sdw|sit|sxi|sxw|swf|tgz|txt|wav|xls|xml|war|wmv|zip)$/i', $fileName)) {
@unlink(Pluf::f('upload_path').'/'.$this->project->shortname.'/files/'.$this->cleaned_data['archive']);
throw new Pluf_Form_Invalid(sprintf(__('For security reasons, you cannot upload a file (%s) with this extension.'), $fileName));
}
}
// label and file name validation
$conf = new IDF_Conf();
$conf->setProject($this->project);
$onemax = array();
foreach (explode(',', $conf->getVal('labels_download_one_max', IDF_Form_UploadConf::init_one_max)) as $class) {
if (trim($class) != '') {
$onemax[] = mb_strtolower(trim($class));
}
}
foreach ($fileNames as $fileName) {
$meta = $this->archiveHelper->getMetaData($fileName);
$count = array();
foreach ($meta['labels'] as $label) {
$label = trim($label);
if (strpos($label, ':') !== false) {
list($class, $name) = explode(':', $label, 2);
list($class, $name) = array(mb_strtolower(trim($class)),
trim($name));
} else {
$class = 'other';
$name = $label;
}
if (!isset($count[$class])) $count[$class] = 1;
else $count[$class] += 1;
if (in_array($class, $onemax) and $count[$class] > 1) {
throw new Pluf_Form_Invalid(
sprintf(__('You cannot provide more than label from the %1$s class to a download (%2$s).'), $class, $name)
);
}
}
$sql = new Pluf_SQL('file=%s AND project=%s', array($fileName, $this->project->id));
$upload = Pluf::factory('IDF_Upload')->getOne(array('filter' => $sql->gen()));
$meta = $this->archiveHelper->getMetaData($fileName);
if ($upload != null && $meta['replaces'] !== $fileName) {
throw new Pluf_Form_Invalid(
sprintf(__('A file with the name "%s" has already been uploaded and is not marked to be replaced.'), $fileName));
}
}
return $this->cleaned_data['archive'];
}
/**
* If we have uploaded a file, but the form failed remove it.
*
*/
function failed()
{
if (!empty($this->cleaned_data['archive'])
and file_exists(Pluf::f('upload_path').'/'.$this->project->shortname.'/archives/'.$this->cleaned_data['archive'])) {
@unlink(Pluf::f('upload_path').'/'.$this->project->shortname.'/archives/'.$this->cleaned_data['archive']);
}
}
/**
* Save the model in the database.
*
* @param bool Commit in the database or not. If not, the object
* is returned but not saved in the database.
*/
function save($commit=true)
{
if (!$this->isValid()) {
throw new Exception(__('Cannot save the model from an invalid form.'));
}
$uploadDir = Pluf::f('upload_path').'/'.$this->project->shortname.'/files/';
$fileNames = $this->archiveHelper->getEntryNames();
foreach ($fileNames as $fileName) {
$meta = $this->archiveHelper->getMetaData($fileName);
// add a tag for each label
$tags = array();
foreach ($meta['labels'] as $label) {
$label = trim($label);
if (strlen($label) > 0) {
if (strpos($label, ':') !== false) {
list($class, $name) = explode(':', $label, 2);
list($class, $name) = array(trim($class), trim($name));
} else {
$class = 'Other';
$name = $label;
}
$tags[] = IDF_Tag::add($name, $this->project, $class);
}
}
// process a possible replacement
if (!empty($meta['replaces'])) {
$sql = new Pluf_SQL('file=%s AND project=%s', array($meta['replaces'], $this->project->id));
$oldUpload = Pluf::factory('IDF_Upload')->getOne(array('filter' => $sql->gen()));
if ($oldUpload) {
if ($meta['replaces'] === $fileName) {
$oldUpload->delete();
} else {
$tags = $this->project->getTagsFromConfig('labels_download_predefined',
IDF_Form_UploadConf::init_predefined);
// the deprecate tag is - by definition - always the last one
$deprecatedTag = array_pop($tags);
$oldUpload->setAssoc($deprecatedTag);
}
}
}
// extract the file
$this->archiveHelper->extract($fileName, $uploadDir);
// create the upload
$upload = new IDF_Upload();
$upload->project = $this->project;
$upload->submitter = $this->user;
$upload->summary = trim($meta['summary']);
$upload->changelog = trim($meta['description']);
$upload->file = $fileName;
$upload->filesize = filesize($uploadDir.$fileName);
$upload->downloads = 0;
$upload->create();
foreach ($tags as $tag) {
$upload->setAssoc($tag);
}
// send the notification
$upload->notify($this->project->getConf());
/**
* [signal]
*
* IDF_Upload::create
*
* [sender]
*
* IDF_Form_Upload
*
* [description]
*
* This signal allows an application to perform a set of tasks
* just after the upload of a file and after the notification run.
*
* [parameters]
*
* array('upload' => $upload);
*
*/
$params = array('upload' => $upload);
Pluf_Signal::send('IDF_Upload::create', 'IDF_Form_Upload',
$params);
}
// finally unlink the uploaded archive
@unlink(Pluf::f('upload_path').'/'.$this->project->shortname.'/archives/'.$this->cleaned_data['archive']);
}
}

View File

@ -0,0 +1,158 @@
<?php
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
# ***** BEGIN LICENSE BLOCK *****
# This file is part of InDefero, an open source project management application.
# Copyright (C) 2008-2011 Céondo Ltd and contributors.
#
# InDefero is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# InDefero is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
# ***** END LICENSE BLOCK ***** */
class IDF_Form_UploadArchiveHelper
{
private $file = null;
private $entries = array();
public function __construct($file)
{
$this->file = $file;
}
/**
* Validates the archive; throws a invalid form exception in case the
* archive contains invalid data or cannot be read.
*/
public function validate()
{
if (!file_exists($this->file)) {
throw new Pluf_Form_Invalid(__('The archive does not exist.'));
}
$za = new ZipArchive();
$res = $za->open($this->file);
if ($res !== true) {
throw new Pluf_Form_Invalid(
sprintf(__('The archive could not be read (code %d).'), $res));
}
$manifest = $za->getFromName('manifest.xml');
if ($manifest === false) {
throw new Pluf_Form_Invalid(__('The archive does not contain a manifest.xml.'));
}
libxml_use_internal_errors(true);
$xml = @simplexml_load_string($manifest);
if ($xml === false) {
$error = libxml_get_last_error();
throw new Pluf_Form_Invalid(
sprintf(__('The archive\'s manifest is invalid: %s'), $error->message));
}
foreach (@$xml->file as $idx => $file)
{
$entry = array(
'name' => (string)@$file->name,
'summary' => (string)@$file->summary,
'description' => (string)@$file->description,
'replaces' => (string)@$file->replaces,
'labels' => array(),
'stream' => null
);
if (empty($entry['name'])) {
throw new Pluf_Form_Invalid(
sprintf(__('The entry %d in the manifest is missing a file name.'), $idx));
}
if (empty($entry['summary'])) {
throw new Pluf_Form_Invalid(
sprintf(__('The entry %d in the manifest is missing a summary.'), $idx));
}
if ($entry['name'] === 'manifest.xml') {
throw new Pluf_Form_Invalid(__('The manifest must not reference itself.'));
}
if ($za->locateName($entry['name']) === false) {
throw new Pluf_Form_Invalid(
sprintf(__('The entry %s in the manifest does not exist in the archive.'), $entry['name']));
}
if (in_array($entry['name'], $this->entries)) {
throw new Pluf_Form_Invalid(
sprintf(__('The entry %s in the manifest is referenced more than once.'), $entry['name']));
}
if ($file->labels) {
foreach (@$file->labels->label as $label) {
$entry['labels'][] = (string)$label;
}
}
// FIXME: remove this once we allow more than six labels everywhere
if (count($entry['labels']) > 6) {
throw new Pluf_Form_Invalid(
sprintf(__('The entry %s in the manifest has more than the six allowed labels set.'), $entry['name']));
}
$this->entries[$entry['name']] = $entry;
}
$za->close();
}
/**
* Returns all entry names
*
* @return array of string
*/
public function getEntryNames()
{
return array_keys($this->entries);
}
/**
* Returns meta data for the given entry
*
* @param string $name
* @throws Exception
*/
public function getMetaData($name)
{
if (!array_key_exists($name, $this->entries)) {
throw new Exception('unknown file ' . $name);
}
return $this->entries[$name];
}
/**
* Extracts the file entry $name at $path
*
* @param string $name
* @param string $path
* @throws Exception
*/
public function extract($name, $path)
{
if (!array_key_exists($name, $this->entries)) {
throw new Exception('unknown file ' . $name);
}
$za = new ZipArchive();
$za->open($this->file);
$za->extractTo($path, $name);
$za->close();
}
}

View File

@ -60,10 +60,18 @@ Deprecated = Most users should NOT download this';
$this->fields['labels_download_one_max'] = new Pluf_Form_Field_Varchar( $this->fields['labels_download_one_max'] = new Pluf_Form_Field_Varchar(
array('required' => false, array('required' => false,
'label' => __('Each download may have at most one label with each of these classes'), 'label' => __('Each download may have at most one label with each of these classes'),
'initial' => self::init_one_max, 'initial' => self::init_one_max,
'widget_attrs' => array('size' => 60), 'widget_attrs' => array('size' => 60),
)); ));
$this->conf = $extra['conf'];
$this->fields['upload_webhook_url'] = new Pluf_Form_Field_Url(
array('required' => false,
'label' => __('Webhook URL'),
'initial' => $this->conf->getVal('upload_webhook_url', ''),
'widget_attrs' => array('size' => 60),
));
} }
} }

View File

@ -56,7 +56,7 @@ class IDF_Form_UserAccount extends Pluf_Form
$this->fields['email'] = new Pluf_Form_Field_Email( $this->fields['email'] = new Pluf_Form_Field_Email(
array('required' => true, array('required' => true,
'label' => __('Your mail'), 'label' => __('Your email'),
'initial' => $this->user->email, 'initial' => $this->user->email,
'help_text' => __('If you change your email address, an email will be sent to the new address to confirm it.'), 'help_text' => __('If you change your email address, an email will be sent to the new address to confirm it.'),
)); ));
@ -168,9 +168,9 @@ class IDF_Form_UserAccount extends Pluf_Form
$this->fields['secondary_mail'] = new Pluf_Form_Field_Email( $this->fields['secondary_mail'] = new Pluf_Form_Field_Email(
array('required' => false, array('required' => false,
'label' => __('Add a secondary mail address'), 'label' => __('Add a secondary email address'),
'initial' => '', 'initial' => '',
'help_text' => __('You will get a mail to confirm that you own the address you specify.'), 'help_text' => __('You will get an email to confirm that you own the address you specify.'),
)); ));
} }

View File

@ -27,7 +27,7 @@
* This create a new page and the corresponding revision. * This create a new page and the corresponding revision.
* *
*/ */
class IDF_Form_WikiCreate extends Pluf_Form class IDF_Form_WikiPageCreate extends Pluf_Form
{ {
public $user = null; public $user = null;
public $project = null; public $project = null;
@ -107,9 +107,9 @@ Add your content here. Format your content with:
if (preg_match('/[^a-zA-Z0-9\-]/', $title)) { if (preg_match('/[^a-zA-Z0-9\-]/', $title)) {
throw new Pluf_Form_Invalid(__('The title contains invalid characters.')); throw new Pluf_Form_Invalid(__('The title contains invalid characters.'));
} }
$sql = new Pluf_SQL('project=%s AND title=%s', $sql = new Pluf_SQL('project=%s AND title=%s',
array($this->project->id, $title)); array($this->project->id, $title));
$pages = Pluf::factory('IDF_WikiPage')->getList(array('filter'=>$sql->gen())); $pages = Pluf::factory('IDF_Wiki_Page')->getList(array('filter'=>$sql->gen()));
if ($pages->count() > 0) { if ($pages->count() > 0) {
throw new Pluf_Form_Invalid(__('A page with this title already exists.')); throw new Pluf_Form_Invalid(__('A page with this title already exists.'));
} }
@ -137,7 +137,7 @@ Add your content here. Format your content with:
$this->cleaned_data['label'.$i] = trim($this->cleaned_data['label'.$i]); $this->cleaned_data['label'.$i] = trim($this->cleaned_data['label'.$i]);
if (strpos($this->cleaned_data['label'.$i], ':') !== false) { if (strpos($this->cleaned_data['label'.$i], ':') !== false) {
list($class, $name) = explode(':', $this->cleaned_data['label'.$i], 2); list($class, $name) = explode(':', $this->cleaned_data['label'.$i], 2);
list($class, $name) = array(mb_strtolower(trim($class)), list($class, $name) = array(mb_strtolower(trim($class)),
trim($name)); trim($name));
} else { } else {
$class = 'other'; $class = 'other';
@ -147,7 +147,7 @@ Add your content here. Format your content with:
else $count[$class] += 1; else $count[$class] += 1;
if (in_array($class, $onemax) and $count[$class] > 1) { if (in_array($class, $onemax) and $count[$class] > 1) {
if (!isset($this->errors['label'.$i])) $this->errors['label'.$i] = array(); if (!isset($this->errors['label'.$i])) $this->errors['label'.$i] = array();
$this->errors['label'.$i][] = sprintf(__('You cannot provide more than label from the %s class to a page.'), $class); $this->errors['label'.$i][] = sprintf(__('You cannot provide more than one label from the %s class to a page.'), $class);
throw new Pluf_Form_Invalid(__('You provided an invalid label.')); throw new Pluf_Form_Invalid(__('You provided an invalid label.'));
} }
} }
@ -181,9 +181,9 @@ Add your content here. Format your content with:
$tags[] = IDF_Tag::add($name, $this->project, $class); $tags[] = IDF_Tag::add($name, $this->project, $class);
} }
} }
} }
// Create the page // Create the page
$page = new IDF_WikiPage(); $page = new IDF_Wiki_Page();
$page->project = $this->project; $page->project = $this->project;
$page->submitter = $this->user; $page->submitter = $this->user;
$page->summary = trim($this->cleaned_data['summary']); $page->summary = trim($this->cleaned_data['summary']);
@ -193,7 +193,7 @@ Add your content here. Format your content with:
$page->setAssoc($tag); $page->setAssoc($tag);
} }
// add the first revision // add the first revision
$rev = new IDF_WikiRevision(); $rev = new IDF_Wiki_PageRevision();
$rev->wikipage = $page; $rev->wikipage = $page;
$rev->content = $this->cleaned_data['content']; $rev->content = $this->cleaned_data['content'];
$rev->submitter = $this->user; $rev->submitter = $this->user;

View File

@ -27,7 +27,7 @@
* This is a hard delete of the page and the revisions. * This is a hard delete of the page and the revisions.
* *
*/ */
class IDF_Form_WikiDelete extends Pluf_Form class IDF_Form_WikiPageDelete extends Pluf_Form
{ {
protected $page = null; protected $page = null;

View File

@ -27,13 +27,13 @@
* This add a corresponding revision. * This add a corresponding revision.
* *
*/ */
class IDF_Form_WikiUpdate extends Pluf_Form class IDF_Form_WikiPageUpdate extends Pluf_Form
{ {
public $user = null; public $user = null;
public $project = null; public $project = null;
public $page = null; public $page = null;
public $show_full = false; public $show_full = false;
public function initFields($extra=array()) public function initFields($extra=array())
{ {
@ -118,9 +118,9 @@ class IDF_Form_WikiUpdate extends Pluf_Form
if (preg_match('/[^a-zA-Z0-9\-]/', $title)) { if (preg_match('/[^a-zA-Z0-9\-]/', $title)) {
throw new Pluf_Form_Invalid(__('The title contains invalid characters.')); throw new Pluf_Form_Invalid(__('The title contains invalid characters.'));
} }
$sql = new Pluf_SQL('project=%s AND title=%s', $sql = new Pluf_SQL('project=%s AND title=%s',
array($this->project->id, $title)); array($this->project->id, $title));
$pages = Pluf::factory('IDF_WikiPage')->getList(array('filter'=>$sql->gen())); $pages = Pluf::factory('IDF_Wiki_Page')->getList(array('filter'=>$sql->gen()));
if ($pages->count() > 0 and $pages[0]->id != $this->page->id) { if ($pages->count() > 0 and $pages[0]->id != $this->page->id) {
throw new Pluf_Form_Invalid(__('A page with this title already exists.')); throw new Pluf_Form_Invalid(__('A page with this title already exists.'));
} }
@ -148,7 +148,7 @@ class IDF_Form_WikiUpdate extends Pluf_Form
$this->cleaned_data['label'.$i] = trim($this->cleaned_data['label'.$i]); $this->cleaned_data['label'.$i] = trim($this->cleaned_data['label'.$i]);
if (strpos($this->cleaned_data['label'.$i], ':') !== false) { if (strpos($this->cleaned_data['label'.$i], ':') !== false) {
list($class, $name) = explode(':', $this->cleaned_data['label'.$i], 2); list($class, $name) = explode(':', $this->cleaned_data['label'.$i], 2);
list($class, $name) = array(mb_strtolower(trim($class)), list($class, $name) = array(mb_strtolower(trim($class)),
trim($name)); trim($name));
} else { } else {
$class = 'other'; $class = 'other';
@ -158,7 +158,7 @@ class IDF_Form_WikiUpdate extends Pluf_Form
else $count[$class] += 1; else $count[$class] += 1;
if (in_array($class, $onemax) and $count[$class] > 1) { if (in_array($class, $onemax) and $count[$class] > 1) {
if (!isset($this->errors['label'.$i])) $this->errors['label'.$i] = array(); if (!isset($this->errors['label'.$i])) $this->errors['label'.$i] = array();
$this->errors['label'.$i][] = sprintf(__('You cannot provide more than label from the %s class to a page.'), $class); $this->errors['label'.$i][] = sprintf(__('You cannot provide more than one label from the %s class to a page.'), $class);
throw new Pluf_Form_Invalid(__('You provided an invalid label.')); throw new Pluf_Form_Invalid(__('You provided an invalid label.'));
} }
} }
@ -229,7 +229,7 @@ class IDF_Form_WikiUpdate extends Pluf_Form
} }
$this->page->update(); $this->page->update();
// add the new revision // add the new revision
$rev = new IDF_WikiRevision(); $rev = new IDF_Wiki_PageRevision();
$rev->wikipage = $this->page; $rev->wikipage = $this->page;
$rev->content = $this->cleaned_data['content']; $rev->content = $this->cleaned_data['content'];
$rev->submitter = $this->user; $rev->submitter = $this->user;

View File

@ -0,0 +1,169 @@
<?php
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
# ***** BEGIN LICENSE BLOCK *****
# This file is part of InDefero, an open source project management application.
# Copyright (C) 2008-2011 Céondo Ltd and contributors.
#
# InDefero is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# InDefero is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
# ***** END LICENSE BLOCK ***** */
/**
* Create a new resource.
*
* This create a new resource and the corresponding revision.
*
*/
class IDF_Form_WikiResourceCreate extends Pluf_Form
{
public $user = null;
public $project = null;
public $show_full = false;
public function initFields($extra=array())
{
$this->project = $extra['project'];
$this->user = $extra['user'];
$initname = (!empty($extra['name'])) ? $extra['name'] : __('ResourceName');
$this->fields['title'] = new Pluf_Form_Field_Varchar(
array('required' => true,
'label' => __('Resource title'),
'initial' => $initname,
'widget_attrs' => array(
'maxlength' => 200,
'size' => 67,
),
'help_text' => __('The resource name must contains only letters, digits and the dash (-) character.'),
));
$this->fields['summary'] = new Pluf_Form_Field_Varchar(
array('required' => true,
'label' => __('Description'),
'help_text' => __('This one line description is displayed in the list of resources.'),
'initial' => '',
'widget_attrs' => array(
'maxlength' => 200,
'size' => 67,
),
));
$this->fields['file'] = new Pluf_Form_Field_File(
array('required' => true,
'label' => __('File'),
'initial' => '',
'max_size' => Pluf::f('max_upload_size', 2097152),
'move_function_params' => array('upload_path' => $this->getTempUploadPath(),
'upload_path_create' => true,
'upload_overwrite' => true),
));
}
public function clean_title()
{
$title = $this->cleaned_data['title'];
if (preg_match('/[^a-zA-Z0-9\-]/', $title)) {
throw new Pluf_Form_Invalid(__('The title contains invalid characters.'));
}
$sql = new Pluf_SQL('project=%s AND title=%s',
array($this->project->id, $title));
$resources = Pluf::factory('IDF_Wiki_Resource')->getList(array('filter'=>$sql->gen()));
if ($resources->count() > 0) {
throw new Pluf_Form_Invalid(__('A resource with this title already exists.'));
}
return $title;
}
public function clean_file()
{
// FIXME: we do the same in IDF_Form_Upload and a couple of other places as well
$extra = strtolower(implode('|', explode(' ', Pluf::f('idf_extra_upload_ext'))));
if (strlen($extra)) $extra .= '|';
if (!preg_match('/\.('.$extra.'png|jpg|jpeg|gif|bmp|psd|tif|aiff|asf|avi|bz2|css|doc|eps|gz|jar|mdtext|mid|mov|mp3|mpg|ogg|pdf|ppt|ps|qt|ra|ram|rm|rtf|sdd|sdw|sit|sxi|sxw|swf|tgz|txt|wav|xls|xml|war|wmv|zip)$/i', $this->cleaned_data['file'])) {
@unlink($this->getTempUploadPath().$this->cleaned_data['file']);
throw new Pluf_Form_Invalid(__('For security reasons, you cannot upload a file with this extension.'));
}
return $this->cleaned_data['file'];
}
/**
* If we have uploaded a file, but the form failed remove it.
*
*/
function failed()
{
if (!empty($this->cleaned_data['file'])
and file_exists($this->getTempUploadPath().$this->cleaned_data['file'])) {
@unlink($this->getTempUploadPath().$this->cleaned_data['file']);
}
}
/**
* Save the model in the database.
*
* @param bool Commit in the database or not. If not, the object
* is returned but not saved in the database.
* @return Object Model with data set from the form.
*/
function save($commit=true)
{
if (!$this->isValid()) {
throw new Exception(__('Cannot save the model from an invalid form.'));
}
$tempFile = $this->getTempUploadPath().$this->cleaned_data['file'];
list($mimeType, , $extension) = IDF_FileUtil::getMimeType($tempFile);
// create the resource
$resource = new IDF_Wiki_Resource();
$resource->project = $this->project;
$resource->submitter = $this->user;
$resource->summary = trim($this->cleaned_data['summary']);
$resource->title = trim($this->cleaned_data['title']);
$resource->mime_type = $mimeType;
$resource->create();
// add the first revision
$rev = new IDF_Wiki_ResourceRevision();
$rev->wikiresource = $resource;
$rev->submitter = $this->user;
$rev->summary = __('Initial resource creation');
$rev->filesize = filesize($tempFile);
$rev->fileext = $extension;
$rev->create();
$finalFile = $rev->getFilePath();
if (!@mkdir(dirname($finalFile), 0755, true)) {
@unlink($tempFile);
$rev->delete();
$resource->delete();
throw new Exception('could not create final resource path');
}
if (!@rename($tempFile, $finalFile)) {
@unlink($tempFile);
$rev->delete();
$resource->delete();
throw new Exception('could not move resource to final location');
}
return $resource;
}
private function getTempUploadPath()
{
return Pluf::f('upload_path').'/'.$this->project->shortname.'/wiki/temp/';
}
}

View File

@ -0,0 +1,64 @@
<?php
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
# ***** BEGIN LICENSE BLOCK *****
# This file is part of InDefero, an open source project management application.
# Copyright (C) 2008-2011 Céondo Ltd and contributors.
#
# InDefero is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# InDefero is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
# ***** END LICENSE BLOCK ***** */
/**
* Delete a documentation page.
*
* This is a hard delete of the page and the revisions.
*
*/
class IDF_Form_WikiResourceDelete extends Pluf_Form
{
protected $resource = null;
public function initFields($extra=array())
{
$this->resource = $extra['resource'];
$this->fields['confirm'] = new Pluf_Form_Field_Boolean(
array('required' => true,
'label' => __('Yes, I understand that the resource and all its revisions will be deleted.'),
'initial' => '',
));
}
/**
* Check the confirmation.
*/
public function clean_confirm()
{
if (!$this->cleaned_data['confirm']) {
throw new Pluf_Form_Invalid(__('You need to confirm the deletion.'));
}
return $this->cleaned_data['confirm'];
}
function save($commit=true)
{
if (!$this->isValid()) {
throw new Exception(__('Cannot save the model from an invalid form.'));
}
$this->resource->delete();
return true;
}
}

View File

@ -0,0 +1,161 @@
<?php
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
# ***** BEGIN LICENSE BLOCK *****
# This file is part of InDefero, an open source project management application.
# Copyright (C) 2008-2011 Céondo Ltd and contributors.
#
# InDefero is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# InDefero is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
# ***** END LICENSE BLOCK ***** */
/**
* Update a documentation page.
*
* This add a corresponding revision.
*
*/
class IDF_Form_WikiResourceUpdate extends Pluf_Form
{
public $user = null;
public $project = null;
public $page = null;
public $show_full = false;
public function initFields($extra=array())
{
$this->resource = $extra['resource'];
$this->user = $extra['user'];
$this->project = $extra['project'];
$this->fields['summary'] = new Pluf_Form_Field_Varchar(
array('required' => true,
'label' => __('Description'),
'help_text' => __('This one line description is displayed in the list of resources.'),
'initial' => $this->resource->summary,
'widget_attrs' => array(
'maxlength' => 200,
'size' => 67,
),
));
$this->fields['file'] = new Pluf_Form_Field_File(
array('required' => true,
'label' => __('File'),
'initial' => '',
'max_size' => Pluf::f('max_upload_size', 2097152),
'move_function_params' => array('upload_path' => $this->getTempUploadPath(),
'upload_path_create' => true,
'upload_overwrite' => true),
));
$this->fields['comment'] = new Pluf_Form_Field_Varchar(
array('required' => true,
'label' => __('Comment'),
'help_text' => __('One line to describe the changes you made.'),
'initial' => '',
'widget_attrs' => array(
'maxlength' => 200,
'size' => 67,
),
));
}
public function clean_file()
{
// FIXME: we do the same in IDF_Form_Upload and a couple of other places as well
$extra = strtolower(implode('|', explode(' ', Pluf::f('idf_extra_upload_ext'))));
if (strlen($extra)) $extra .= '|';
if (!preg_match('/\.('.$extra.'png|jpg|jpeg|gif|bmp|psd|tif|aiff|asf|avi|bz2|css|doc|eps|gz|jar|mdtext|mid|mov|mp3|mpg|ogg|pdf|ppt|ps|qt|ra|ram|rm|rtf|sdd|sdw|sit|sxi|sxw|swf|tgz|txt|wav|xls|xml|war|wmv|zip)$/i', $this->cleaned_data['file'])) {
@unlink($this->getTempUploadPath().$this->cleaned_data['file']);
throw new Pluf_Form_Invalid(__('For security reasons, you cannot upload a file with this extension.'));
}
list($mimeType, , $extension) = IDF_FileUtil::getMimeType($this->getTempUploadPath().$this->cleaned_data['file']);
if ($this->resource->mime_type != $mimeType) {
throw new Pluf_Form_Invalid(sprintf(
__('The mime type of the uploaded file "%1$s" does not match the mime type of this resource "%2$s"'),
$mimeType, $this->resource->mime_type
));
}
$this->cleaned_data['fileext'] = $extension;
if (md5_file($this->getTempUploadPath().$this->cleaned_data['file']) ===
md5_file($this->resource->get_current_revision()->getFilePath())) {
throw new Pluf_Form_Invalid(__('The current version of the resource and the uploaded file are equal.'));
}
return $this->cleaned_data['file'];
}
/**
* If we have uploaded a file, but the form failed remove it.
*
*/
function failed()
{
if (!empty($this->cleaned_data['file'])
and file_exists($this->getTempUploadPath().$this->cleaned_data['file'])) {
@unlink($this->getTempUploadPath().$this->cleaned_data['file']);
}
}
/**
* Save the model in the database.
*
* @param bool Commit in the database or not. If not, the object
* is returned but not saved in the database.
* @return Object Model with data set from the form.
*/
function save($commit=true)
{
if (!$this->isValid()) {
throw new Exception(__('Cannot save the model from an invalid form.'));
}
$tempFile = $this->getTempUploadPath().$this->cleaned_data['file'];
$this->resource->summary = trim($this->cleaned_data['summary']);
$this->resource->update();
// add the new revision
$rev = new IDF_Wiki_ResourceRevision();
$rev->wikiresource = $this->resource;
$rev->submitter = $this->user;
$rev->summary = $this->cleaned_data['comment'];
$rev->filesize = filesize($tempFile);
$rev->fileext = $this->cleaned_data['fileext'];
$rev->create();
$finalFile = $rev->getFilePath();
if (!is_dir(dirname($finalFile))) {
@unlink($tempFile);
$rev->delete();
throw new Exception('resource path does not exist');
}
if (!@rename($tempFile, $finalFile)) {
@unlink($tempFile);
$rev->delete();
throw new Exception('could not move resource to final location');
}
return $this->resource;
}
private function getTempUploadPath()
{
return Pluf::f('upload_path').'/'.$this->project->shortname.'/wiki/temp/';
}
}

View File

@ -211,7 +211,7 @@ class IDF_Issue extends Pluf_Model
$ic = (in_array($this->status, $request->project->getTagIdsByStatus('closed'))) ? 'issue-c' : 'issue-o'; $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 .= 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"> $out .= "\n".'<tr class="extra"><td colspan="2">
<div class="helptext right">'.sprintf(__('Creation of <a href="%s" class="%s">issue&nbsp;%d</a>, by %s'), $url, $ic, $this->id, $user).'</div></td></tr>'; <div class="helptext right">'.sprintf(__('Creation of <a href="%1$s" class="%2$s">issue %3$d</a>, by %4$s'), $url, $ic, $this->id, $user).'</div></td></tr>';
return Pluf_Template::markSafe($out); return Pluf_Template::markSafe($out);
} }
@ -221,7 +221,7 @@ class IDF_Issue extends Pluf_Model
.Pluf_HTTP_URL_urlForView('IDF_Views_Issue::view', .Pluf_HTTP_URL_urlForView('IDF_Views_Issue::view',
array($request->project->shortname, array($request->project->shortname,
$this->id)); $this->id));
$title = sprintf(__('%s: Issue %d created - %s'), $title = sprintf(__('%1$s: Issue %2$d created - %3$s'),
$request->project->name, $request->project->name,
$this->id, $this->summary); $this->id, $this->summary);
$cts = $this->get_comments_list(array('order' => 'id ASC', $cts = $this->get_comments_list(array('order' => 'id ASC',
@ -256,91 +256,72 @@ class IDF_Issue extends Pluf_Model
*/ */
public function notify($conf, $create=true) public function notify($conf, $create=true)
{ {
$prj = $this->get_project(); $project = $this->get_project();
$to_email = array();
if ('' != $conf->getVal('issues_notification_email', '')) {
$langs = Pluf::f('languages', array('en'));
$addresses = explode(',', $conf->getVal('issues_notification_email'));
foreach ($addresses as $address) {
$to_email[] = array($address, $langs[0]);
}
}
$current_locale = Pluf_Translation::getLocale(); $current_locale = Pluf_Translation::getLocale();
$id = '<'.md5($this->id.md5(Pluf::f('secret_key'))).'@'.Pluf::f('mail_host', 'localhost').'>';
if ($create) { $from_email = Pluf::f('from_email');
if (null != $this->get_owner() and $this->owner != $this->submitter) { $comments = $this->get_comments_list(array('order' => 'id DESC'));
$email_lang = array($this->get_owner()->email, $messageId = '<'.md5('issue'.$this->id.md5(Pluf::f('secret_key'))).'@'.Pluf::f('mail_host', 'localhost').'>';
$this->get_owner()->language); $recipients = $project->getNotificationRecipientsForTab('issues');
if (!in_array($email_lang, $to_email)) {
$to_email[] = $email_lang; // the submitter (might be skipped later on if he is the one who also
} // submitted the last comment)
} if (!array_key_exists($this->get_submitter()->email, $recipients)) {
$comments = $this->get_comments_list(array('order' => 'id ASC')); $recipients[$this->get_submitter()->email] = $this->get_submitter()->language;
$context = new Pluf_Template_Context(
array(
'issue' => $this,
'comment' => $comments[0],
'project' => $prj,
'url_base' => Pluf::f('url_base'),
)
);
foreach ($to_email as $email_lang) {
Pluf_Translation::loadSetLocale($email_lang[1]);
$email = new Pluf_Mail(Pluf::f('from_email'), $email_lang[0],
sprintf(__('Issue %s - %s (%s)'),
$this->id, $this->summary, $prj->shortname));
$tmpl = new Pluf_Template('idf/issues/issue-created-email.txt');
$email->addTextMessage($tmpl->render($context));
$email->addHeaders(array('Message-ID'=>$id));
$email->sendMail();
}
} else {
$comments = $this->get_comments_list(array('order' => 'id DESC'));
$email_sender = '';
if (isset($comments[0])) {
$email_sender = $comments[0]->get_submitter()->email;
}
foreach ($this->get_interested_list() as $interested) {
$email_lang = array($interested->email,
$interested->language);
if (!in_array($email_lang, $to_email)) {
$to_email[] = $email_lang;
}
}
$email_lang = array($this->get_submitter()->email,
$this->get_submitter()->language);
if (!in_array($email_lang, $to_email)) {
$to_email[] = $email_lang;
}
if (null != $this->get_owner()) {
$email_lang = array($this->get_owner()->email,
$this->get_owner()->language);
if (!in_array($email_lang, $to_email)) {
$to_email[] = $email_lang;
}
}
$context = new Pluf_Template_Context(
array(
'issue' => $this,
'comments' => $comments,
'project' => $prj,
'url_base' => Pluf::f('url_base'),
));
foreach ($to_email as $email_lang) {
if ($email_lang[0] == $email_sender) {
continue; // Do not notify the one having created
// the comment
}
Pluf_Translation::loadSetLocale($email_lang[1]);
$email = new Pluf_Mail(Pluf::f('from_email'), $email_lang[0],
sprintf(__('Updated Issue %s - %s (%s)'),
$this->id, $this->summary, $prj->shortname));
$tmpl = new Pluf_Template('idf/issues/issue-updated-email.txt');
$email->addTextMessage($tmpl->render($context));
$email->addHeaders(array('References'=>$id));
$email->sendMail();
}
} }
// the owner of the issue, if we have one
$owner = $this->get_owner();
if (null != $owner && !array_key_exists($owner->email, $recipients)) {
$recipients[$owner->email] = $owner->language;
}
// additional users who starred the issue
foreach ($this->get_interested_list() as $interested) {
if (array_key_exists($interested->email, $recipients))
continue;
$recipients[$interested->email] = $interested->language;
}
foreach ($recipients as $address => $language) {
// do not notify the creator of the last comment,
// i.e. the user who triggered this notification
if ($comments[0]->get_submitter()->email === $address) {
continue;
}
Pluf_Translation::loadSetLocale($language);
$context = new Pluf_Template_Context(array(
'issue' => $this,
'owns_issue' => $owner !== null && $owner->email === $address,
// the initial comment for create, the last for update
'comment' => $comments[0],
'comments' => $comments,
'project' => $project,
'url_base' => Pluf::f('url_base'),
));
$tplfile = 'idf/issues/issue-created-email.txt';
$subject = __('Issue %1$s - %2$s (%3$s)');
$headers = array('Message-ID' => $messageId);
if (!$create) {
$tplfile = 'idf/issues/issue-updated-email.txt';
$subject = __('Updated Issue %1$s - %2$s (%3$s)');
$headers = array('References' => $messageId);
}
$tmpl = new Pluf_Template($tplfile);
$text_email = $tmpl->render($context);
$email = new Pluf_Mail($from_email, $address,
sprintf($subject, $this->id, $this->summary, $project->shortname));
$email->addTextMessage($text_email);
$email->addHeaders($headers);
$email->sendMail();
}
Pluf_Translation::loadSetLocale($current_locale); Pluf_Translation::loadSetLocale($current_locale);
} }
} }

View File

@ -177,7 +177,7 @@ class IDF_IssueComment extends Pluf_Model
} }
$out .= '</td></tr>'; $out .= '</td></tr>';
$out .= "\n".'<tr class="extra"><td colspan="2"> $out .= "\n".'<tr class="extra"><td colspan="2">
<div class="helptext right">'.sprintf(__('Comment on <a href="%s" class="%s">issue&nbsp;%d</a>, by %s'), $url, $ic, $issue->id, $user).'</div></td></tr>'; <div class="helptext right">'.sprintf(__('Comment on <a href="%1$s" class="%2$s">issue %3$d</a>, by %4$s'), $url, $ic, $issue->id, $user).'</div></td></tr>';
return Pluf_Template::markSafe($out); return Pluf_Template::markSafe($out);
} }
@ -188,7 +188,7 @@ class IDF_IssueComment extends Pluf_Model
.Pluf_HTTP_URL_urlForView('IDF_Views_Issue::view', .Pluf_HTTP_URL_urlForView('IDF_Views_Issue::view',
array($request->project->shortname, array($request->project->shortname,
$issue->id)); $issue->id));
$title = sprintf(__('%s: Comment on issue %d - %s'), $title = sprintf(__('%1$s: Comment on issue %2$d - %3$s'),
Pluf_esc($request->project->name), Pluf_esc($request->project->name),
$issue->id, Pluf_esc($issue->summary)); $issue->id, Pluf_esc($issue->summary));
$url .= '#ic'.$this->id; $url .= '#ic'.$this->id;

View File

@ -85,6 +85,7 @@ class IDF_Middleware
'issuetext' => 'IDF_Template_IssueComment', 'issuetext' => 'IDF_Template_IssueComment',
'timeline' => 'IDF_Template_TimelineFragment', 'timeline' => 'IDF_Template_TimelineFragment',
'markdown' => 'IDF_Template_Markdown', 'markdown' => 'IDF_Template_Markdown',
'markdown_forge' => 'IDF_Template_MarkdownForge',
'showuser' => 'IDF_Template_ShowUser', 'showuser' => 'IDF_Template_ShowUser',
'ashowuser' => 'IDF_Template_AssignShowUser', 'ashowuser' => 'IDF_Template_AssignShowUser',
'appversion' => 'IDF_Template_AppVersion', 'appversion' => 'IDF_Template_AppVersion',
@ -102,6 +103,7 @@ class IDF_Middleware
function IDF_Middleware_ContextPreProcessor($request) function IDF_Middleware_ContextPreProcessor($request)
{ {
$forge = IDF_Forge::instance();
$c = array(); $c = array();
$c['request'] = $request; $c['request'] = $request;
$c['isAdmin'] = ($request->user->administrator or $request->user->staff); $c['isAdmin'] = ($request->user->administrator or $request->user->staff);
@ -115,6 +117,7 @@ function IDF_Middleware_ContextPreProcessor($request)
} }
$c['usherConfigured'] = Pluf::f("mtn_usher_conf", null) !== null; $c['usherConfigured'] = Pluf::f("mtn_usher_conf", null) !== null;
$c['allProjects'] = IDF_Views::getProjects($request->user); $c['allProjects'] = IDF_Views::getProjects($request->user);
$c['customForgePageEnabled'] = $forge->isCustomForgePageEnabled();
return $c; return $c;
} }

View File

@ -0,0 +1,74 @@
<?php
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
# ***** BEGIN LICENSE BLOCK *****
# This file is part of InDefero, an open source project management application.
# Copyright (C) 2008-2011 Céondo Ltd and contributors.
#
# InDefero is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# InDefero is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
# ***** END LICENSE BLOCK ***** */
function IDF_Migrations_19WikiPageAssocs_up($params=null)
{
$engine = Pluf::f('db_engine');
if (!in_array($engine, array('MySQL', 'PostgreSQL'))) {
throw new Exception('unsupported engine '.$engine);
}
$db = Pluf::db();
$intro = new Pluf_DB_Introspect($db);
if (in_array($db->pfx.'idf_tag_idf_wiki_page_assoc', $intro->listTables())) {
echo '19 skipping up migration - relation table has correct name already'."\n";
return;
}
$db->execute('ALTER TABLE '.$db->pfx.'idf_tag_idf_wikipage_assoc RENAME TO '.$db->pfx.'idf_tag_idf_wiki_page_assoc');
$db->execute('ALTER TABLE '.$db->pfx.'idf_wikipage_pluf_user_assoc RENAME TO '.$db->pfx.'idf_wiki_page_pluf_user_assoc');
if ($engine === 'PostgreSQL') {
$db->execute('ALTER TABLE '.$db->pfx.'idf_tag_idf_wiki_page_assoc RENAME COLUMN idf_wikipage_id TO idf_wiki_page_id');
$db->execute('ALTER TABLE '.$db->pfx.'idf_wiki_page_pluf_user_assoc RENAME COLUMN idf_wikipage_id TO idf_wiki_page_id');
} else if ($engine === 'MySQL') {
$db->execute('ALTER TABLE '.$db->pfx.'idf_tag_idf_wiki_page_assoc CHANGE idf_wikipage_id idf_wiki_page_id MEDIUMINT NOT NULL');
$db->execute('ALTER TABLE '.$db->pfx.'idf_wiki_page_pluf_user_assoc CHANGE idf_wikipage_id idf_wiki_page_id MEDIUMINT NOT NULL');
}
}
function IDF_Migrations_19WikiPageAssocs_down($params=null)
{
$engine = Pluf::f('db_engine');
if (!in_array($engine, array('MySQL', 'PostgreSQL'))) {
throw new Exception('unsupported engine '.$engine);
}
$db = Pluf::db();
$intro = new Pluf_DB_Introspect($db);
if (in_array($db->pfx.'idf_tag_idf_wikipage_assoc', $intro->listTables())) {
echo '19 skipping down migration - relation table has correct name already'."\n";
return;
}
$db->execute('ALTER TABLE '.$db->pfx.'idf_tag_idf_wiki_page_assoc RENAME TO '.$db->pfx.'idf_tag_idf_wikipage_assoc');
$db->execute('ALTER TABLE '.$db->pfx.'idf_wiki_page_pluf_user_assoc RENAME TO '.$db->pfx.'idf_wikipage_pluf_user_assoc');
if ($engine === 'PostgreSQL') {
$db->execute('ALTER TABLE '.$db->pfx.'idf_tag_idf_wikipage_assoc RENAME COLUMN idf_wiki_page_id TO idf_wikipage_id');
$db->execute('ALTER TABLE '.$db->pfx.'idf_wikipage_pluf_user_assoc RENAME COLUMN idf_wiki_page_id TO idf_wikipage_id');
} else if ($engine === 'MySQL') {
$db->execute('ALTER TABLE '.$db->pfx.'idf_tag_idf_wikipage_assoc CHANGE idf_wiki_page_id idf_wikipage_id MEDIUMINT NOT NULL');
$db->execute('ALTER TABLE '.$db->pfx.'idf_wikipage_pluf_user_assoc CHANGE idf_wiki_page_id idf_wikipage_id MEDIUMINT NOT NULL');
}
}

View File

@ -0,0 +1,51 @@
<?php
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
# ***** BEGIN LICENSE BLOCK *****
# This file is part of InDefero, an open source project management application.
# Copyright (C) 2008-2011 Céondo Ltd and contributors.
#
# InDefero is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# InDefero is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
# ***** END LICENSE BLOCK ***** */
/**
* Add the new IDF_Wiki_Resource and IDF_Wiki_ResourceRevision models.
*
*/
function IDF_Migrations_20AddWikiResources_up($params=null)
{
$db = Pluf::db();
$schema = new Pluf_DB_Schema($db);
$schema->model = new IDF_Wiki_Resource();
$schema->createTables();
$schema->model = new IDF_Wiki_ResourceRevision();
$schema->createTables();
}
function IDF_Migrations_20AddWikiResources_down($params=null)
{
$db = Pluf::db();
$schema = new Pluf_DB_Schema($db);
$schema->model = new IDF_Wiki_ResourceRevision();
$schema->dropTables();
$schema->model = new IDF_Wiki_Resource();
$schema->dropTables();
}

View File

@ -0,0 +1,60 @@
<?php
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
# ***** BEGIN LICENSE BLOCK *****
# This file is part of InDefero, an open source project management application.
# Copyright (C) 2008-2011 Céondo Ltd and contributors.
#
# InDefero is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# InDefero is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
# ***** END LICENSE BLOCK ***** */
function IDF_Migrations_21WikiPageRevisionName_up($params=null)
{
$engine = Pluf::f('db_engine');
if (!in_array($engine, array('MySQL', 'PostgreSQL'))) {
throw new Exception('unsupported engine '.$engine);
}
$db = Pluf::db();
$intro = new Pluf_DB_Introspect($db);
if (in_array($db->pfx.'idf_wikipagerevs', $intro->listTables())) {
echo '21 skipping up migration - table has correct name already'."\n";
return;
}
$db->execute('ALTER TABLE '.$db->pfx.'idf_wikirevisions RENAME TO '.$db->pfx.'idf_wikipagerevs');
$db->execute("UPDATE ".$db->pfx."idf_timeline SET model_class='IDF_Wiki_Page' WHERE model_class LIKE 'IDF_WikiPage'");
$db->execute("UPDATE ".$db->pfx."idf_timeline SET model_class='IDF_Wiki_PageRevision' WHERE model_class LIKE 'IDF_WikiRevision'");
}
function IDF_Migrations_21WikiPageRevisionName_down($params=null)
{
$engine = Pluf::f('db_engine');
if (!in_array($engine, array('MySQL', 'PostgreSQL'))) {
throw new Exception('unsupported engine '.$engine);
}
$db = Pluf::db();
$intro = new Pluf_DB_Introspect($db);
if (in_array($db->pfx.'idf_wikirevisions', $intro->listTables())) {
echo '21 skipping down migration - table has correct name already'."\n";
return;
}
$db->execute('ALTER TABLE '.$db->pfx.'idf_wikipagerevs RENAME TO '.$db->pfx.'idf_wikirevisions');
$db->execute("UPDATE ".$db->pfx."idf_timeline SET model_class='IDF_WikiPage' WHERE model_class LIKE 'IDF_Wiki_Page'");
$db->execute("UPDATE ".$db->pfx."idf_timeline SET model_class='IDF_WikiRevision' WHERE model_class LIKE 'IDF_Wiki_PageRevision'");
}

View File

@ -0,0 +1,60 @@
<?php
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
# ***** BEGIN LICENSE BLOCK *****
# This file is part of InDefero, an open source project management application.
# Copyright (C) 2008-2011 Céondo Ltd and contributors.
#
# InDefero is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# InDefero is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
# ***** END LICENSE BLOCK ***** */
function IDF_Migrations_22ProjectTagRelationTable_up($params=null)
{
$db = Pluf::db();
$table = $db->pfx.'idf_project_idf_tag_assoc';
if (!in_array($db->engine, array('MySQL', 'PostgreSQL'))) {
throw new Exception('unsupported engine '.$engine);
}
$intro = new Pluf_DB_Introspect($db);
if (in_array($table, $intro->listTables())) {
echo '21 skipping up migration - table already exists'."\n";
return;
}
$schema = Pluf::factory('Pluf_DB_Schema_'.$db->engine, $db);
$sql = $schema->getSqlCreate(new IDF_Project());
$db->execute($sql[$table]);
}
function IDF_Migrations_22ProjectTagRelationTable_down($params=null)
{
$db = Pluf::db();
$table = $db->pfx.'idf_project_idf_tag_assoc';
if (!in_array($db->engine, array('MySQL', 'PostgreSQL'))) {
throw new Exception('unsupported engine '.$engine);
}
$intro = new Pluf_DB_Introspect($db);
if (!in_array($table, $intro->listTables())) {
echo '22 skipping down migration - table does not exist'."\n";
return;
}
$schema = Pluf::factory('Pluf_DB_Schema_'.$db->engine, $db);
$sql = $schema->getSqlDelete(new IDF_Project());
$db->execute($sql[$table]);
}

View File

@ -0,0 +1,42 @@
<?php
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
# ***** BEGIN LICENSE BLOCK *****
# This file is part of InDefero, an open source project management application.
# Copyright (C) 2008-2011 Céondo Ltd and contributors.
#
# InDefero is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# InDefero is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
# ***** END LICENSE BLOCK ***** */
/**
* Add the new IDF_ProjectActivity model.
*
*/
function IDF_Migrations_23ProjectActivity_up($params=null)
{
$db = Pluf::db();
$schema = new Pluf_DB_Schema($db);
$schema->model = new IDF_ProjectActivity();
$schema->createTables();
}
function IDF_Migrations_23ProjectActivity_down($params=null)
{
$db = Pluf::db();
$schema = new Pluf_DB_Schema($db);
$schema->model = new IDF_ProjectActivity();
$schema->dropTables();
}

View File

@ -0,0 +1,40 @@
<?php
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
# ***** BEGIN LICENSE BLOCK *****
# This file is part of InDefero, an open source project management application.
# Copyright (C) 2008-2011 Céondo Ltd and contributors.
#
# InDefero is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# InDefero is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
# ***** END LICENSE BLOCK ***** */
function IDF_Migrations_24CurrentProjectActivity_up($params=null)
{
$engine = Pluf::f('db_engine');
$db = Pluf::db();
if ($engine === 'PostgreSQL') {
$db->execute('ALTER TABLE '.$db->pfx.'idf_projects ADD COLUMN current_activity INTEGER');
} else if ($engine === 'MySQL') {
$db->execute('ALTER TABLE '.$db->pfx.'idf_projects ADD COLUMN current_activity MEDIUMINT');
}
}
function IDF_Migrations_24CurrentProjectActivity_down($params=null)
{
$db = Pluf::db();
$db->execute('ALTER TABLE '.$db->pfx.'idf_projects DROP COLUMN current_activity');
}

View File

@ -22,14 +22,14 @@
# ***** END LICENSE BLOCK ***** */ # ***** END LICENSE BLOCK ***** */
/** /**
* Add the download of files. * Add wiki functionality.
*/ */
function IDF_Migrations_7Wiki_up($params=null) function IDF_Migrations_7Wiki_up($params=null)
{ {
$models = array( $models = array(
'IDF_WikiPage', 'IDF_Wiki_Page',
'IDF_WikiRevision', 'IDF_Wiki_PageRevision',
); );
$db = Pluf::db(); $db = Pluf::db();
$schema = new Pluf_DB_Schema($db); $schema = new Pluf_DB_Schema($db);
@ -42,8 +42,8 @@ function IDF_Migrations_7Wiki_up($params=null)
function IDF_Migrations_7Wiki_down($params=null) function IDF_Migrations_7Wiki_down($params=null)
{ {
$models = array( $models = array(
'IDF_WikiRevision', 'IDF_Wiki_PageRevision',
'IDF_WikiPage', 'IDF_Wiki_Page',
); );
$db = Pluf::db(); $db = Pluf::db();
$schema = new Pluf_DB_Schema($db); $schema = new Pluf_DB_Schema($db);

View File

@ -34,6 +34,7 @@ function IDF_Migrations_Backup_run($folder, $name=null)
{ {
$models = array( $models = array(
'IDF_Project', 'IDF_Project',
'IDF_ProjectActivity',
'IDF_Tag', 'IDF_Tag',
'IDF_Issue', 'IDF_Issue',
'IDF_IssueComment', 'IDF_IssueComment',
@ -43,8 +44,10 @@ function IDF_Migrations_Backup_run($folder, $name=null)
'IDF_IssueFile', 'IDF_IssueFile',
'IDF_Commit', 'IDF_Commit',
'IDF_Timeline', 'IDF_Timeline',
'IDF_WikiPage', 'IDF_Wiki_Page',
'IDF_WikiRevision', 'IDF_Wiki_PageRevision',
'IDF_Wiki_Resource',
'IDF_Wiki_ResourceRevision',
'IDF_Review', 'IDF_Review',
'IDF_Review_Patch', 'IDF_Review_Patch',
'IDF_Review_Comment', 'IDF_Review_Comment',
@ -81,6 +84,7 @@ function IDF_Migrations_Backup_restore($folder, $name)
{ {
$models = array( $models = array(
'IDF_Project', 'IDF_Project',
'IDF_ProjectActivity',
'IDF_Tag', 'IDF_Tag',
'IDF_Issue', 'IDF_Issue',
'IDF_IssueComment', 'IDF_IssueComment',
@ -90,8 +94,10 @@ function IDF_Migrations_Backup_restore($folder, $name)
'IDF_IssueFile', 'IDF_IssueFile',
'IDF_Commit', 'IDF_Commit',
'IDF_Timeline', 'IDF_Timeline',
'IDF_WikiPage', 'IDF_Wiki_Resource',
'IDF_WikiRevision', 'IDF_Wiki_ResourceRevision',
'IDF_Wiki_Page',
'IDF_Wiki_PageRevision',
'IDF_Review', 'IDF_Review',
'IDF_Review_Patch', 'IDF_Review_Patch',
'IDF_Review_Comment', 'IDF_Review_Comment',

View File

@ -31,6 +31,7 @@ function IDF_Migrations_Install_setup($params=null)
{ {
$models = array( $models = array(
'IDF_Project', 'IDF_Project',
'IDF_ProjectActivity',
'IDF_Tag', 'IDF_Tag',
'IDF_Issue', 'IDF_Issue',
'IDF_IssueComment', 'IDF_IssueComment',
@ -40,8 +41,10 @@ function IDF_Migrations_Install_setup($params=null)
'IDF_IssueFile', 'IDF_IssueFile',
'IDF_Commit', 'IDF_Commit',
'IDF_Timeline', 'IDF_Timeline',
'IDF_WikiPage', 'IDF_Wiki_Resource',
'IDF_WikiRevision', 'IDF_Wiki_ResourceRevision',
'IDF_Wiki_Page',
'IDF_Wiki_PageRevision',
'IDF_Review', 'IDF_Review',
'IDF_Review_Patch', 'IDF_Review_Patch',
'IDF_Review_Comment', 'IDF_Review_Comment',
@ -97,8 +100,10 @@ function IDF_Migrations_Install_teardown($params=null)
'IDF_Review_Comment', 'IDF_Review_Comment',
'IDF_Review_Patch', 'IDF_Review_Patch',
'IDF_Review', 'IDF_Review',
'IDF_WikiRevision', 'IDF_Wiki_PageRevision',
'IDF_WikiPage', 'IDF_Wiki_Page',
'IDF_Wiki_ResourceRevision',
'IDF_Wiki_Resource',
'IDF_Timeline', 'IDF_Timeline',
'IDF_IssueFile', 'IDF_IssueFile',
'IDF_Search_Occ', 'IDF_Search_Occ',
@ -108,6 +113,7 @@ function IDF_Migrations_Install_teardown($params=null)
'IDF_Issue', 'IDF_Issue',
'IDF_Tag', 'IDF_Tag',
'IDF_Commit', 'IDF_Commit',
'IDF_ProjectActivity',
'IDF_Project', 'IDF_Project',
'IDF_EmailAddress', 'IDF_EmailAddress',
'IDF_IssueRelation', 'IDF_IssueRelation',

View File

@ -210,22 +210,23 @@ class IDF_Plugin_SyncGit_Serve
// Indefero's one. // Indefero's one.
$p = realpath(dirname(__FILE__).'/../../../../scripts/git-post-update'); $p = realpath(dirname(__FILE__).'/../../../../scripts/git-post-update');
$p = Pluf::f('idf_plugin_syncgit_post_update', $p); $p = Pluf::f('idf_plugin_syncgit_post_update', $p);
if (!@unlink($fullpath.'/hooks/post-update')) { $post_update_hook = $fullpath.'/hooks/post-update';
if (file_exists($post_update_hook) && !@unlink($post_update_hook)) {
Pluf_Log::warn(array('IDF_Plugin_Git_Serve::initRepository', Pluf_Log::warn(array('IDF_Plugin_Git_Serve::initRepository',
'post-update hook removal error.', 'post-update hook removal error.',
$fullpath.'/hooks/post-update')); $post_update_hook));
return; return;
} }
$out = array(); $out = array();
$res = 0; $res = 0;
exec(sprintf(Pluf::f('idf_exec_cmd_prefix', '').'ln -s %s %s', exec(sprintf(Pluf::f('idf_exec_cmd_prefix', '').'ln -s %s %s',
escapeshellarg($p), escapeshellarg($p),
escapeshellarg($fullpath.'/hooks/post-update')), escapeshellarg($post_update_hook)),
$out, $res); $out, $res);
if ($res != 0) { if ($res != 0) {
Pluf_Log::warn(array('IDF_Plugin_Git_Serve::initRepository', Pluf_Log::warn(array('IDF_Plugin_Git_Serve::initRepository',
'post-update hook creation error.', 'post-update hook creation error.',
$fullpath.'/hooks/post-update')); $post_update_hook));
return; return;
} }
Pluf_Log::debug(array('IDF_Plugin_Git_Serve::initRepository', Pluf_Log::debug(array('IDF_Plugin_Git_Serve::initRepository',

View File

@ -306,7 +306,7 @@ class IDF_Plugin_SyncMonotone
} }
catch (Exception $e) { catch (Exception $e) {
$this->_diagnoseProblem(sprintf( $this->_diagnoseProblem(sprintf(
__('Could not parse usher configuration in "%s": %s'), __('Could not parse usher configuration in "%1$s": %2$s'),
$usher_config, $e->getMessage() $usher_config, $e->getMessage()
)); ));
} }
@ -522,7 +522,7 @@ class IDF_Plugin_SyncMonotone
} }
catch (Exception $e) { catch (Exception $e) {
$this->_diagnoseProblem(sprintf( $this->_diagnoseProblem(sprintf(
__('Could not parse usher configuration in "%s": %s'), __('Could not parse usher configuration in "%1$s": %2$s'),
$usher_config, $e->getMessage() $usher_config, $e->getMessage()
)); ));
} }
@ -596,7 +596,7 @@ class IDF_Plugin_SyncMonotone
} }
catch (Exception $e) { catch (Exception $e) {
$this->_diagnoseProblem(sprintf( $this->_diagnoseProblem(sprintf(
__('Could not parse read-permissions for project "%s": %s'), __('Could not parse read-permissions for project "%1$s": %2$s'),
$shortname, $e->getMessage() $shortname, $e->getMessage()
)); ));
} }
@ -715,7 +715,7 @@ class IDF_Plugin_SyncMonotone
} }
catch (Exception $e) { catch (Exception $e) {
$this->_diagnoseProblem(sprintf( $this->_diagnoseProblem(sprintf(
__('Could not parse read-permissions for project "%s": %s'), __('Could not parse read-permissions for project "%1$s": %2$s'),
$shortname, $e->getMessage() $shortname, $e->getMessage()
)); ));
} }

View File

@ -67,7 +67,7 @@ class IDF_Project extends Pluf_Model
'blank' => false, 'blank' => false,
'size' => 50, 'size' => 50,
'verbose' => __('short name'), 'verbose' => __('short name'),
'help_text' => __('Used in the url to access the project, must be short with only letters and numbers.'), 'help_text' => __('Used in the URL to access the project, must be short with only letters and numbers.'),
'unique' => true, 'unique' => true,
), ),
'shortdesc' => 'shortdesc' =>
@ -84,7 +84,14 @@ class IDF_Project extends Pluf_Model
'blank' => false, 'blank' => false,
'size' => 250, 'size' => 250,
'verbose' => __('description'), 'verbose' => __('description'),
'help_text' => __('The description can be extended using the markdown syntax.'), 'help_text' => __('The description can be extended using the Markdown syntax.'),
),
'tags' =>
array(
'type' => 'Pluf_DB_Field_Manytomany',
'blank' => true,
'model' => 'IDF_Tag',
'verbose' => __('labels'),
), ),
'private' => 'private' =>
array( array(
@ -93,7 +100,29 @@ class IDF_Project extends Pluf_Model
'verbose' => __('private'), 'verbose' => __('private'),
'default' => 0, 'default' => 0,
), ),
); 'current_activity' =>
array(
'type' => 'Pluf_DB_Field_Foreignkey',
'model' => 'IDF_ProjectActivity',
'blank' => true,
'verbose' => __('current project activity'),
),
);
$activityTable = $this->_con->pfx.'idf_projectactivities';
$tagTable = $this->_con->pfx.'idf_project_idf_tag_assoc';
$this->_a['views'] = array(
'join_activities_and_tags' =>
array(
'join' => 'LEFT JOIN '.$activityTable.' ON current_activity='.$activityTable.'.id '
.'LEFT JOIN '.$tagTable.' ON idf_project_id='.$this->getSqlTable().'.id',
'select' => $this->getSelect().', date, value',
'group' => $this->getSqlTable().'.id',
'props' => array(
'date' => 'current_activity_date',
'value' => 'current_activity_value'
),
),
);
} }
@ -427,13 +456,13 @@ GROUP BY uid";
$dep_ids = IDF_Views_Wiki::getDeprecatedPagesIds($this); $dep_ids = IDF_Views_Wiki::getDeprecatedPagesIds($this);
$extra = ''; $extra = '';
if (count($dep_ids)) { if (count($dep_ids)) {
$extra = ' AND idf_wikipage_id NOT IN ('.implode(', ', $dep_ids).') '; $extra = ' AND idf_wiki_page_id NOT IN ('.implode(', ', $dep_ids).') ';
} }
$what_t = Pluf::factory('IDF_WikiPage')->getSqlTable(); $what_t = Pluf::factory('IDF_Wiki_Page')->getSqlTable();
$asso_t = $this->_con->pfx.'idf_tag_idf_wikipage_assoc'; $asso_t = $this->_con->pfx.'idf_tag_idf_wiki_page_assoc';
$sql = 'SELECT '.$tag_t.'.id AS id, COUNT(*) AS nb_use FROM '.$tag_t.' '."\n". $sql = 'SELECT '.$tag_t.'.id AS id, COUNT(*) AS nb_use FROM '.$tag_t.' '."\n".
'LEFT JOIN '.$asso_t.' ON idf_tag_id='.$tag_t.'.id '."\n". 'LEFT JOIN '.$asso_t.' ON idf_tag_id='.$tag_t.'.id '."\n".
'LEFT JOIN '.$what_t.' ON idf_wikipage_id='.$what_t.'.id '."\n". 'LEFT JOIN '.$what_t.' ON idf_wiki_page_id='.$what_t.'.id '."\n".
'WHERE idf_tag_id IS NOT NULL '.$extra.' AND '.$what_t.'.project='.$this->id.' GROUP BY '.$tag_t.'.id, '.$tag_t.'.class, '.$tag_t.'.name ORDER BY '.$tag_t.'.class ASC, '.$tag_t.'.name ASC'; 'WHERE idf_tag_id IS NOT NULL '.$extra.' AND '.$what_t.'.project='.$this->id.' GROUP BY '.$tag_t.'.id, '.$tag_t.'.class, '.$tag_t.'.name ORDER BY '.$tag_t.'.class ASC, '.$tag_t.'.name ASC';
} elseif ($what == 'downloads') { } elseif ($what == 'downloads') {
$dep_ids = IDF_Views_Download::getDeprecatedFilesIds($this); $dep_ids = IDF_Views_Download::getDeprecatedFilesIds($this);
@ -535,12 +564,12 @@ GROUP BY uid";
} }
/** /**
* Get the post commit hook key. * Get the web hook key.
* *
* The goal is to get something predictable but from which one * The goal is to get something predictable but from which one
* cannot reverse find the secret key. * cannot reverse find the secret key.
*/ */
public function getPostCommitHookKey() public function getWebHookKey()
{ {
return md5($this->id.sha1(Pluf::f('secret_key')).$this->shortname); return md5($this->id.sha1(Pluf::f('secret_key')).$this->shortname);
} }
@ -593,6 +622,22 @@ GROUP BY uid";
return $this->_pconf; return $this->_pconf;
} }
/**
* Magic overload that falls back to the values of the internal configuration
* if no getter / caller matched
*
* @param string $key
*/
public function __get($key)
{
try {
return parent::__get($key);
}
catch (Exception $e) {
return $this->getConf()->getVal($key);
}
}
/** /**
* Get simple statistics about the project. * Get simple statistics about the project.
* *
@ -606,10 +651,10 @@ GROUP BY uid";
$stats = array(); $stats = array();
$stats['total'] = 0; $stats['total'] = 0;
$what = array('downloads' => 'IDF_Upload', $what = array('downloads' => 'IDF_Upload',
'reviews' => 'IDF_Review', 'reviews' => 'IDF_Review',
'issues' => 'IDF_Issue', 'issues' => 'IDF_Issue',
'docpages' => 'IDF_WikiPage', 'docpages' => 'IDF_Wiki_Page',
'commits' => 'IDF_Commit', 'commits' => 'IDF_Commit',
); );
foreach ($what as $key=>$m) { foreach ($what as $key=>$m) {
$i = Pluf::factory($m)->getCount(array('filter' => 'project='.(int)$this->id)); $i = Pluf::factory($m)->getCount(array('filter' => 'project='.(int)$this->id));
@ -739,7 +784,8 @@ GROUP BY uid";
Pluf_Signal::send('IDF_Project::preDelete', Pluf_Signal::send('IDF_Project::preDelete',
'IDF_Project', $params); 'IDF_Project', $params);
$what = array('IDF_Upload', 'IDF_Review', 'IDF_Issue', $what = array('IDF_Upload', 'IDF_Review', 'IDF_Issue',
'IDF_WikiPage', 'IDF_Commit', 'IDF_Tag', 'IDF_Wiki_Page', 'IDF_Wiki_Resource',
'IDF_Commit', 'IDF_Tag',
); );
foreach ($what as $m) { foreach ($what as $m) {
foreach (Pluf::factory($m)->getList(array('filter' => 'project='.(int)$this->id)) as $item) { foreach (Pluf::factory($m)->getList(array('filter' => 'project='.(int)$this->id)) as $item) {
@ -780,4 +826,52 @@ GROUP BY uid";
$this->_isRestricted = false; $this->_isRestricted = false;
return false; return false;
} }
/**
* Returns an associative array of email addresses to notify about changes
* in a certain tab like 'issues', 'source', and so on.
*
* @param string $tab
* @return array Key is the email address, value is the preferred language setting
*/
public function getNotificationRecipientsForTab($tab)
{
if (!in_array($tab, array('source', 'issues', 'downloads', 'wiki', 'review'))) {
throw new Exception(sprintf('unknown tab %s', $tab));
}
$conf = $this->getConf();
$recipients = array();
$membership_data = $this->getMembershipData();
if ($conf->getVal($tab.'_notification_owners_enabled', false)) {
foreach ($membership_data['owners'] as $owner) {
$recipients[$owner->email] = $owner->language;
}
}
if ($conf->getVal($tab.'_notification_members_enabled', false)) {
foreach ($membership_data['members'] as $member) {
$recipients[$member->email] = $member->language;
}
}
if ($conf->getVal($tab.'_notification_email_enabled', false)) {
$addresses = preg_split('/\s*,\s*/',
$conf->getVal($tab.'_notification_email', ''),
-1, PREG_SPLIT_NO_EMPTY);
// we use a default language setting for this plain list of
// addresses, but we ensure that we do not overwrite an existing
// address which might come with a proper setting already
$languages = Pluf::f('languages', array('en'));
foreach ($addresses as $address) {
if (array_key_exists($address, $recipients))
continue;
$recipients[$address] = $languages[0];
}
}
return $recipients;
}
} }

View File

@ -0,0 +1,78 @@
<?php
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
# ***** BEGIN LICENSE BLOCK *****
# This file is part of InDefero, an open source project management application.
# Copyright (C) 2008-2011 Céondo Ltd and contributors.
#
# InDefero is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# InDefero is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
# ***** END LICENSE BLOCK ***** */
/**
* Models the activity value for a project and a given date
*
* @author tommyd
*/
class IDF_ProjectActivity extends Pluf_Model
{
public $_model = __CLASS__;
function init()
{
$this->_a['table'] = 'idf_projectactivities';
$this->_a['model'] = __CLASS__;
$this->_a['cols'] = array(
// It is mandatory to have an "id" column.
'id' =>
array(
'type' => 'Pluf_DB_Field_Sequence',
'blank' => true,
),
'project' =>
array(
'type' => 'Pluf_DB_Field_Foreignkey',
'model' => 'IDF_Project',
'blank' => false,
'verbose' => __('project'),
'relate_name' => 'activities',
),
'date' =>
array(
'type' => 'Pluf_DB_Field_Datetime',
'blank' => false,
'verbose' => __('date'),
),
'value' =>
array(
'type' => 'Pluf_DB_Field_Float',
'blank' => false,
'verbose' => __('value'),
'default' => 0,
),
);
}
function postSave($create=false)
{
$prj = $this->get_project();
$sql = new Pluf_SQL('project=%s', array($prj->id));
$list = Pluf::factory('IDF_ProjectActivity')->getList(array('filter' => $sql->gen(), 'order' => 'date desc'));
if (count($list) > 0 && $prj->current_activity != $list[0]->id) {
$prj->current_activity = $list[0];
$prj->update();
}
}
}

View File

@ -138,7 +138,7 @@ class IDF_Review_Comment extends Pluf_Model
$ic = (in_array($review->status, $request->project->getTagIdsByStatus('closed'))) ? 'issue-c' : 'issue-o'; $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 .= 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"> $out .= "\n".'<tr class="extra"><td colspan="2">
<div class="helptext right">'.sprintf(__('Update of <a href="%s" class="%s">review&nbsp;%d</a>, by %s'), $url, $ic, $review->id, $user).'</div></td></tr>'; <div class="helptext right">'.sprintf(__('Update of <a href="%1$s" class="%2$s">review %3$d</a>, by %4$s'), $url, $ic, $review->id, $user).'</div></td></tr>';
return Pluf_Template::markSafe($out); return Pluf_Template::markSafe($out);
} }
@ -148,7 +148,7 @@ class IDF_Review_Comment extends Pluf_Model
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Review::view', $url = Pluf_HTTP_URL_urlForView('IDF_Views_Review::view',
array($request->project->shortname, array($request->project->shortname,
$review->id)); $review->id));
$title = sprintf(__('%s: Updated review %d - %s'), $title = sprintf(__('%1$s: Updated review %2$d - %3$s'),
Pluf_esc($request->project->name), Pluf_esc($request->project->name),
$review->id, Pluf_esc($review->summary)); $review->id, Pluf_esc($review->summary));
$url .= '#ic'.$this->id; $url .= '#ic'.$this->id;
@ -175,50 +175,63 @@ class IDF_Review_Comment extends Pluf_Model
*/ */
public function notify($conf, $create=true) public function notify($conf, $create=true)
{ {
$patch = $this->get_patch(); $patch = $this->get_patch();
$review = $patch->get_review(); $review = $patch->get_review();
$prj = $review->get_project(); $prj = $review->get_project();
$to_email = array(); $reviewers = $review->getReviewers();
if ('' != $conf->getVal('review_notification_email', '')) {
$langs = Pluf::f('languages', array('en'));
$to_email[] = array($conf->getVal('issues_notification_email'),
$langs[0]);
}
$current_locale = Pluf_Translation::getLocale();
$reviewers = $review->getReviewers();
if (!Pluf_Model_InArray($review->get_submitter(), $reviewers)) { if (!Pluf_Model_InArray($review->get_submitter(), $reviewers)) {
$reviewers[] = $review->get_submitter(); $reviewers[] = $review->get_submitter();
} }
$comments = $patch->getFileComments(array('order' => 'id DESC')); $comments = $patch->getFileComments(array('order' => 'id DESC'));
$gcomments = $patch->get_comments_list(array('order' => 'id DESC')); $gcomments = $patch->get_comments_list(array('order' => 'id DESC'));
$context = new Pluf_Template_Context(
array(
'review' => $review,
'patch' => $patch,
'comments' => $comments,
'gcomments' => $gcomments,
'project' => $prj,
'url_base' => Pluf::f('url_base'),
)
);
// build the list of emails and lang
foreach ($reviewers as $user) {
$email_lang = array($user->email,
$user->language);
if (!in_array($email_lang, $to_email)) {
$to_email[] = $email_lang;
}
}
$tmpl = new Pluf_Template('idf/review/review-updated-email.txt');
foreach ($to_email as $email_lang) {
Pluf_Translation::loadSetLocale($email_lang[1]);
$email = new Pluf_Mail(Pluf::f('from_email'), $email_lang[0],
sprintf(__('Updated Code Review %s - %s (%s)'),
$review->id, $review->summary, $prj->shortname));
$email->addTextMessage($tmpl->render($context)); $recipients = $prj->getNotificationRecipientsForTab('review');
foreach ($reviewers as $user) {
if (array_key_exists($user->email, $recipients))
continue;
$recipients[$user->email] = $user->language;
}
$current_locale = Pluf_Translation::getLocale();
$from_email = Pluf::f('from_email');
$messageId = '<'.md5('review'.$review->id.md5(Pluf::f('secret_key'))).'@'.Pluf::f('mail_host', 'localhost').'>';
foreach ($recipients as $address => $language) {
if ($this->get_submitter()->email === $address) {
continue;
}
Pluf_Translation::loadSetLocale($language);
$context = new Pluf_Template_Context(array(
'review' => $review,
'patch' => $patch,
'comments' => $comments,
'gcomments' => $gcomments,
'project' => $prj,
'url_base' => Pluf::f('url_base'),
));
// reviews only updated through comments, see IDF_Review_Patch::notify()
$tplfile = 'idf/review/review-updated-email.txt';
$subject = __('Updated Code Review %1$s - %2$s (%3$s)');
$headers = array('References' => $messageId);
$tmpl = new Pluf_Template($tplfile);
$text_email = $tmpl->render($context);
$email = new Pluf_Mail($from_email, $address,
sprintf($subject, $review->id, $review->summary, $prj->shortname));
$email->addTextMessage($text_email);
$email->addHeaders($headers);
$email->sendMail(); $email->sendMail();
} }
Pluf_Translation::loadSetLocale($current_locale); Pluf_Translation::loadSetLocale($current_locale);
} }

View File

@ -42,9 +42,9 @@ class IDF_Review_Patch extends Pluf_Model
'id' => 'id' =>
array( array(
'type' => 'Pluf_DB_Field_Sequence', 'type' => 'Pluf_DB_Field_Sequence',
'blank' => true, 'blank' => true,
), ),
'review' => 'review' =>
array( array(
'type' => 'Pluf_DB_Field_Foreignkey', 'type' => 'Pluf_DB_Field_Foreignkey',
'model' => 'IDF_Review', 'model' => 'IDF_Review',
@ -59,7 +59,7 @@ class IDF_Review_Patch extends Pluf_Model
'size' => 250, 'size' => 250,
'verbose' => __('summary'), 'verbose' => __('summary'),
), ),
'commit' => 'commit' =>
array( array(
'type' => 'Pluf_DB_Field_Foreignkey', 'type' => 'Pluf_DB_Field_Foreignkey',
'model' => 'IDF_Commit', 'model' => 'IDF_Commit',
@ -129,8 +129,8 @@ class IDF_Review_Patch extends Pluf_Model
function postSave($create=false) function postSave($create=false)
{ {
if ($create) { if ($create) {
IDF_Timeline::insert($this, IDF_Timeline::insert($this,
$this->get_review()->get_project(), $this->get_review()->get_project(),
$this->get_review()->get_submitter()); $this->get_review()->get_submitter());
IDF_Search::index($this->get_review()); IDF_Search::index($this->get_review());
} }
@ -139,7 +139,7 @@ class IDF_Review_Patch extends Pluf_Model
public function timelineFragment($request) public function timelineFragment($request)
{ {
$review = $this->get_review(); $review = $this->get_review();
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Review::view', $url = Pluf_HTTP_URL_urlForView('IDF_Views_Review::view',
array($request->project->shortname, array($request->project->shortname,
$review->id)); $review->id));
$out = '<tr class="log"><td><a href="'.$url.'">'. $out = '<tr class="log"><td><a href="'.$url.'">'.
@ -150,17 +150,17 @@ class IDF_Review_Patch extends Pluf_Model
$ic = (in_array($review->status, $request->project->getTagIdsByStatus('closed'))) ? 'issue-c' : 'issue-o'; $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 .= 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"> $out .= "\n".'<tr class="extra"><td colspan="2">
<div class="helptext right">'.sprintf(__('Creation of <a href="%s" class="%s">review&nbsp;%d</a>, by %s'), $url, $ic, $review->id, $user).'</div></td></tr>'; <div class="helptext right">'.sprintf(__('Creation of <a href="%1$s" class="%2$s">review %3$d</a>, by %4$s'), $url, $ic, $review->id, $user).'</div></td></tr>';
return Pluf_Template::markSafe($out); return Pluf_Template::markSafe($out);
} }
public function feedFragment($request) public function feedFragment($request)
{ {
$review = $this->get_review(); $review = $this->get_review();
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Review::view', $url = Pluf_HTTP_URL_urlForView('IDF_Views_Review::view',
array($request->project->shortname, array($request->project->shortname,
$review->id)); $review->id));
$title = sprintf(__('%s: Creation of Review %d - %s'), $title = sprintf(__('%1$s: Creation of Review %2$d - %3$s'),
Pluf_esc($request->project->name), Pluf_esc($request->project->name),
$review->id, Pluf_esc($review->summary)); $review->id, Pluf_esc($review->summary));
$date = Pluf_Date::gmDateToGmString($this->creation_dtime); $date = Pluf_Date::gmDateToGmString($this->creation_dtime);
@ -179,35 +179,49 @@ class IDF_Review_Patch extends Pluf_Model
public function notify($conf, $create=true) public function notify($conf, $create=true)
{ {
if ('' == $conf->getVal('review_notification_email', '')) { $review = $this->get_review();
return; $project = $review->get_project();
}
$current_locale = Pluf_Translation::getLocale(); $current_locale = Pluf_Translation::getLocale();
$langs = Pluf::f('languages', array('en'));
Pluf_Translation::loadSetLocale($langs[0]);
$context = new Pluf_Template_Context( $from_email = Pluf::f('from_email');
array( $messageId = '<'.md5('review'.$review->id.md5(Pluf::f('secret_key'))).'@'.Pluf::f('mail_host', 'localhost').'>';
'review' => $this->get_review(), $recipients = $project->getNotificationRecipientsForTab('review');
'patch' => $this,
'comments' => array(), foreach ($recipients as $address => $language) {
'project' => $this->get_review()->get_project(),
'url_base' => Pluf::f('url_base'), if ($review->get_submitter()->email === $address) {
) continue;
); }
$tmpl = new Pluf_Template('idf/review/review-created-email.txt');
$text_email = $tmpl->render($context); Pluf_Translation::loadSetLocale($language);
$addresses = explode(';',$conf->getVal('review_notification_email'));
foreach ($addresses as $address) { $context = new Pluf_Template_Context(array(
$email = new Pluf_Mail(Pluf::f('from_email'), 'review' => $review,
'patch' => $this,
'comments' => array(),
'project' => $project,
'url_base' => Pluf::f('url_base'),
));
// reviews are updated through comments, see IDF_Review_Comment::notify()
$tplfile = 'idf/review/review-created-email.txt';
$subject = __('New Code Review %1$s - %2$s (%3$s)');
$headers = array('Message-ID' => $messageId);
$tmpl = new Pluf_Template($tplfile);
$text_email = $tmpl->render($context);
$email = new Pluf_Mail($from_email,
$address, $address,
sprintf(__('New Code Review %s - %s (%s)'), sprintf($subject,
$this->get_review()->id, $review->id,
$this->get_review()->summary, $review->summary,
$this->get_review()->get_project()->shortname)); $project->shortname));
$email->addTextMessage($text_email); $email->addTextMessage($text_email);
$email->addHeaders($headers);
$email->sendMail(); $email->sendMail();
} }
Pluf_Translation::loadSetLocale($current_locale); Pluf_Translation::loadSetLocale($current_locale);
} }
} }

View File

@ -497,5 +497,10 @@ class IDF_Scm
{ {
return 0; return 0;
} }
public function repository($request, $match)
{
throw new Exception('This repository does not support web based repository access');
}
} }

View File

@ -918,4 +918,82 @@ class IDF_Scm_Git extends IDF_Scm
} }
return false; return false;
} }
public function repository($request, $match)
{
// authenticate: authenticate connection through "extra" password
if (isset($_SERVER['HTTP_AUTHORIZATION']) && $_SERVER['HTTP_AUTHORIZATION'] != '')
list($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW']) = explode(':' , base64_decode(substr($_SERVER['HTTP_AUTHORIZATION'], 6)));
if (isset($_SERVER['PHP_AUTH_USER'])) {
$sql = new Pluf_SQL('login=%s', array($_SERVER['PHP_AUTH_USER']));
$users = Pluf::factory('Pluf_User')->getList(array('filter'=>$sql->gen()));
if ((count($users) == 1) && ($users[0]->active)) {
$user = $users[0];
$realkey = substr(sha1($user->password.Pluf::f('secret_key')), 0, 8);
if ($_SERVER['PHP_AUTH_PW'] == $realkey) {
$request->user = $user;
}
}
}
if (IDF_Precondition::accessSource($request) !== true) {
$response = new Pluf_HTTP_Response("");
$response->status_code = 401;
$response->headers['WWW-Authenticate']='Basic realm="git for '.$this->project.'"';
return $response;
}
$path = $match[2];
// update files before delivering them
if (($path == 'objects/info/pack') || ($path == 'info/refs')) {
$cmd = sprintf(Pluf::f('idf_exec_cmd_prefix', '').
'GIT_DIR=%s '.Pluf::f('git_path', 'git').' update-server-info -f',
escapeshellarg($this->repo));
self::shell_exec('IDF_Scm_Git::repository', $cmd);
}
// smart HTTP discovery
if (($path == 'info/refs') &&
(array_key_exists('service', $request->GET))){
$service = $request->GET["service"];
switch ($service) {
case 'git-upload-pack':
case 'git-receive-pack':
$content = sprintf('%04x',strlen($service)+15).
'# service='.$service."\n0000";
$content .= self::shell_exec('IDF_Scm_Git::repository',
$service.' --stateless-rpc --advertise-refs '.
$this->repo);
$response = new Pluf_HTTP_Response($content,
'application/x-'.$service.'-advertisement');
return $response;
default:
throw new Exception('unknown service: '.$service);
}
}
switch($path) {
// smart HTTP RPC
case 'git-upload-pack':
case 'git-receive-pack':
$response = new Pluf_HTTP_Response_CommandPassThru($path.
' --stateless-rpc '.$this->repo,
'application/x-'.$path.'-result');
$response->addStdin('php://input');
return $response;
// regular file
default:
// make sure we're inside the repo hierarchy (ie. no break-out)
if (is_file($this->repo.'/'.$path) &&
strpos(realpath($this->repo.'/'.$path), $this->repo.'/') == 0) {
return new Pluf_HTTP_Response_File($this->repo.'/'.$path,
'application/octet-stream');
} else {
return new Pluf_HTTP_Response_NotFound($request);
}
}
}
} }

View File

@ -119,7 +119,7 @@ class IDF_Scm_Monotone_Stdio implements IDF_Scm_Monotone_IStdio
$remote_db_access = Pluf::f('mtn_db_access', 'remote') == 'remote'; $remote_db_access = Pluf::f('mtn_db_access', 'remote') == 'remote';
$cmd = Pluf::f('idf_exec_cmd_prefix', '') . $cmd = Pluf::f('idf_exec_cmd_prefix', '') .
Pluf::f('mtn_path', 'mtn') . ' '; escapeshellarg(Pluf::f('mtn_path', 'mtn')) . ' ';
$opts = Pluf::f('mtn_opts', array()); $opts = Pluf::f('mtn_opts', array());
foreach ($opts as $opt) { foreach ($opts as $opt) {

View File

@ -22,7 +22,7 @@
# ***** END LICENSE BLOCK ***** */ # ***** END LICENSE BLOCK ***** */
/** /**
* Storage of the occurence of the words. * Storage of the occurrence of the words.
*/ */
class IDF_Search_Occ extends Pluf_Model class IDF_Search_Occ extends Pluf_Model
{ {
@ -30,7 +30,7 @@ class IDF_Search_Occ extends Pluf_Model
function init() function init()
{ {
$this->_a['verbose'] = __('occurence'); $this->_a['verbose'] = __('occurrence');
$this->_a['table'] = 'idf_search_occs'; $this->_a['table'] = 'idf_search_occs';
$this->_a['model'] = 'IDF_Search_Occ'; $this->_a['model'] = 'IDF_Search_Occ';
$this->_a['cols'] = array( $this->_a['cols'] = array(
@ -72,13 +72,13 @@ class IDF_Search_Occ extends Pluf_Model
array( array(
'type' => 'Pluf_DB_Field_Integer', 'type' => 'Pluf_DB_Field_Integer',
'blank' => false, 'blank' => false,
'verbose' => __('occurences'), 'verbose' => __('occurrences'),
), ),
'pondocc' => 'pondocc' =>
array( array(
'type' => 'Pluf_DB_Field_Float', 'type' => 'Pluf_DB_Field_Float',
'blank' => false, 'blank' => false,
'verbose' => __('ponderated occurence'), 'verbose' => __('ponderated occurrence'),
), ),
); );
$this->_a['idx'] = array( $this->_a['idx'] = array(

View File

@ -42,16 +42,16 @@ class IDF_Tag extends Pluf_Model
array( array(
'type' => 'Pluf_DB_Field_Sequence', 'type' => 'Pluf_DB_Field_Sequence',
//It is automatically added. //It is automatically added.
'blank' => true, 'blank' => true,
), ),
'project' => 'project' =>
array( array(
'type' => 'Pluf_DB_Field_Foreignkey', 'type' => 'Pluf_DB_Field_Foreignkey',
'model' => 'IDF_Project', 'model' => 'IDF_Project',
'blank' => false, 'blank' => false,
'verbose' => __('project'), 'verbose' => __('project'),
), ),
'class' => 'class' =>
array( array(
'type' => 'Pluf_DB_Field_Varchar', 'type' => 'Pluf_DB_Field_Varchar',
'blank' => false, 'blank' => false,
@ -59,13 +59,13 @@ class IDF_Tag extends Pluf_Model
'verbose' => __('tag class'), 'verbose' => __('tag class'),
'help_text' => __('The class of the tag.'), 'help_text' => __('The class of the tag.'),
), ),
'name' => 'name' =>
array( array(
'type' => 'Pluf_DB_Field_Varchar', 'type' => 'Pluf_DB_Field_Varchar',
'blank' => false, 'blank' => false,
'verbose' => __('name'), 'verbose' => __('name'),
), ),
'lcname' => 'lcname' =>
array( array(
'type' => 'Pluf_DB_Field_Varchar', 'type' => 'Pluf_DB_Field_Varchar',
'blank' => false, 'blank' => false,
@ -75,6 +75,18 @@ class IDF_Tag extends Pluf_Model
), ),
); );
$table = $this->_con->pfx.'idf_project_idf_tag_assoc';
$this->_a['views'] = array(
'join_projects' =>
array(
'join' => 'LEFT JOIN '.$table
.' ON idf_tag_id=id',
'select' => $this->getSelect().',COUNT(idf_project_id) as project_count',
'group' => 'idf_tag_id',
'props' => array('project_count' => 'project_count'),
),
);
$this->_a['idx'] = array( $this->_a['idx'] = array(
'lcname_idx' => 'lcname_idx' =>
array( array(
@ -95,7 +107,7 @@ class IDF_Tag extends Pluf_Model
} }
/** /**
* Add a tag if not already existing. * Add a project-specific tag if not already existing.
* *
* @param string Name of the tag. * @param string Name of the tag.
* @param IDF_Project Project of the tag. * @param IDF_Project Project of the tag.
@ -107,7 +119,7 @@ class IDF_Tag extends Pluf_Model
$class = trim($class); $class = trim($class);
$name = trim($name); $name = trim($name);
$gtag = new IDF_Tag(); $gtag = new IDF_Tag();
$sql = new Pluf_SQL('class=%s AND lcname=%s AND project=%s', $sql = new Pluf_SQL('class=%s AND lcname=%s AND project=%s',
array($class, mb_strtolower($name), $project->id)); array($class, mb_strtolower($name), $project->id));
$tags = $gtag->getList(array('filter' => $sql->gen())); $tags = $gtag->getList(array('filter' => $sql->gen()));
if ($tags->count() < 1) { if ($tags->count() < 1) {
@ -122,6 +134,32 @@ class IDF_Tag extends Pluf_Model
return $tags[0]; return $tags[0];
} }
/**
* Add a global tag if not already existing
*
* @param string Name of the tag.
* @param string Class of the tag (IDF_TAG_DEFAULT_CLASS)
* @return IDF_Tag The tag.
*/
public static function addGlobal($name, $class=IDF_TAG_DEFAULT_CLASS)
{
$class = trim($class);
$name = trim($name);
$gtag = new IDF_Tag();
$sql = new Pluf_SQL('class=%s AND lcname=%s AND project=0',
array($class, mb_strtolower($name)));
$tags = $gtag->getList(array('filter' => $sql->gen()));
if ($tags->count() < 1) {
// create a new tag
$tag = new IDF_Tag();
$tag->name = $name;
$tag->class = $class;
$tag->create();
return $tag;
}
return $tags[0];
}
function __toString() function __toString()
{ {
if ($this->class != IDF_TAG_DEFAULT_CLASS) { if ($this->class != IDF_TAG_DEFAULT_CLASS) {

View File

@ -31,6 +31,6 @@ class IDF_Template_HotKey extends Pluf_Template_Tag
function start($key, $view, $params=array(), $get_params=array()) function start($key, $view, $params=array(), $get_params=array())
{ {
$url = addslashes(Pluf_HTTP_URL_urlForView($view, $params, $get_params)); $url = addslashes(Pluf_HTTP_URL_urlForView($view, $params, $get_params));
echo "jQuery.hotkeys.add('$key',{disableInInput: true},function (){window.location.href='$url';});"; echo "$(document).bind('keydown','$key',function (){window.location.href='$url';});";
} }
} }

View File

@ -55,8 +55,17 @@ class IDF_Template_Markdown extends Pluf_Template_Tag
$text = IDF_Template_safePregReplace('#\[\[([A-Za-z0-9\-]+)\]\]#im', $text = IDF_Template_safePregReplace('#\[\[([A-Za-z0-9\-]+)\]\]#im',
array($this, 'callbackWikiPageNoName'), array($this, 'callbackWikiPageNoName'),
$text); $text);
$filter = new IDF_Template_MarkdownPrefilter(); $filter = new IDF_Template_MarkdownPrefilter();
echo $filter->go(Pluf_Text_MarkDown_parse($text)); $text = $filter->go(Pluf_Text_MarkDown_parse($text));
// Replace [[!ResourceName]] with corresponding HTML for the resource;
// we need to do that after the HTML filtering as we'd otherwise be unable to use
// certain HTML elements, such as iframes, that are used to display text content
// FIXME: no support for escaping yet in place
echo IDF_Template_safePregReplace('#\[\[!([A-Za-z0-9\-]+)(?:,\s*([^\]]+))?\]\]#im',
array($this, 'callbackWikiResource'),
$text);
} }
function callbackWikiPageNoName($m) function callbackWikiPageNoName($m)
@ -69,15 +78,99 @@ class IDF_Template_Markdown extends Pluf_Template_Tag
{ {
$sql = new Pluf_SQL('project=%s AND title=%s', $sql = new Pluf_SQL('project=%s AND title=%s',
array($this->project->id, $m[2])); array($this->project->id, $m[2]));
$pages = Pluf::factory('IDF_WikiPage')->getList(array('filter'=>$sql->gen())); $pages = Pluf::factory('IDF_Wiki_Page')->getList(array('filter'=>$sql->gen()));
if ($pages->count() != 1 and $this->request->rights['hasWikiAccess'] if ($pages->count() != 1 and $this->request->rights['hasWikiAccess']
and !$this->request->user->isAnonymous()) { and !$this->request->user->isAnonymous()) {
return '<img style="vertical-align: text-bottom;" alt=" " src="'.Pluf::f('url_media').'/idf/img/add.png" /><a href="'.Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::create', array($this->project->shortname), array('name'=>$m[2])).'" title="'.__('Create this documentation page').'">'.$m[1].'</a>'; return '<img style="vertical-align: text-bottom;" alt=" " src="'.Pluf::f('url_media').'/idf/img/add.png" /><a href="'.Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::createPage', array($this->project->shortname), array('name'=>$m[2])).'" title="'.__('Create this documentation page').'">'.$m[1].'</a>';
} }
if (!$this->request->rights['hasWikiAccess'] or $pages->count() == 0) { if (!$this->request->rights['hasWikiAccess'] or $pages->count() == 0) {
return $m[1]; return $m[1];
} }
return '<a href="'.Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::view', array($this->project->shortname, $pages[0]->title)).'" title="'.Pluf_esc($pages[0]->summary).'">'.$m[1].'</a>'; return '<a href="'.Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::viewPage', array($this->project->shortname, $pages[0]->title)).'" title="'.Pluf_esc($pages[0]->summary).'">'.$m[1].'</a>';
}
function callbackWikiResource($m)
{
@list($match, $resourceName, $opts) = $m;
if (!$this->request->rights['hasWikiAccess']) {
return '<span title="'.__('You are not allowed to access the wiki.').'">'.$match.'</span>';
}
$sql = new Pluf_SQL('project=%s AND title=%s',
array($this->project->id, $resourceName));
$resources = Pluf::factory('IDF_Wiki_Resource')->getList(array('filter'=>$sql->gen()));
if ($resources->count() == 0) {
if ($this->request->user->isAnonymous()) {
return '<span title="'.__('The wiki resource has not been found.').'">'.$match.'</span>';
}
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::createResource',
array($this->project->shortname),
array('name' => $resourceName));
return '<img style="vertical-align: text-bottom;" alt=" " src="'.Pluf::f('url_media').'/idf/img/add.png" />'.
'<a href="'.$url.'" title="'.__('The wiki resource has not been found. Create it!').'">'.$match.'</a>';
}
// by default, render the most recent revision
$resourceRevision = $resources[0]->get_current_revision();
list($urlConf, $urlMatches) = $this->request->view;
// if we currently look at an existing wiki page, look up its name and find the proper resource (if any)
if ($urlConf['model'] == 'IDF_Views_Wiki' && $urlConf['method'] == 'viewPage') {
$sql = new Pluf_SQL('project=%s AND title=%s',
array($this->project->id, $urlMatches[2]));
$pages = Pluf::factory('IDF_Wiki_Page')->getList(array('filter'=>$sql->gen()));
if ($pages->count() == 0) throw new Exception('page not found');
$pageRevision = $pages[0]->get_current_revision();
// if we look at an old version of the page, figure out the resource version back then
if (isset($this->request->GET['rev']) and preg_match('/^[0-9]+$/', $this->request->GET['rev'])) {
$pageRevision = Pluf_Shortcuts_GetObjectOr404('IDF_Wiki_PageRevision',
$this->request->GET['rev']);
// this is actually an invariant since we came so far looking at
// and rendering the old revision already
if ($pageRevision == null) {
throw new Exception('page revision with id '.$this->request->GET['rev'].' not found');
}
}
$sql = new Pluf_SQL('wikiresource=%s AND idf_wiki_pagerevision_id=%s',
array($resources[0]->id, $pageRevision->id));
$resourceRevision = Pluf::factory('IDF_Wiki_ResourceRevision')->getOne(
array('filter' => $sql->gen(), 'view' => 'join_pagerevision'));
if ($resourceRevision == null) {
return '<span title="'.__('This revision of the resource is no longer available.').'">'.$match.'</span>';
}
}
$validOpts = array(
'align' => '/^(left|right|center)$/',
'width' => '/^\d+(%|px|em)?$/',
'height' => '/^\d+(%|px|em)?$/',
'preview' => '/^yes|no$/',
'title' => '/.+/',
);
$parsedOpts = array();
// FIXME: no support for escaping yet in place
$opts = preg_split('/\s*,\s*/', $opts, -1, PREG_SPLIT_NO_EMPTY);
foreach ((array)@$opts as $opt)
{
list($key, $value) = preg_split('/\s*=\s*/', $opt, 2);
if (!array_key_exists($key, $validOpts)) {
continue;
}
if (!preg_match($validOpts[$key], $value)) {
continue;
}
$parsedOpts[$key] = $value;
}
return $resourceRevision->render($parsedOpts);
} }
function callbackEmbeddedDoc($m) function callbackEmbeddedDoc($m)

View File

@ -0,0 +1,118 @@
<?php
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
# ***** BEGIN LICENSE BLOCK *****
# This file is part of InDefero, an open source project management application.
# Copyright (C) 2008-2011 Céondo Ltd and contributors.
#
# InDefero is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# InDefero is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
# ***** END LICENSE BLOCK ***** */
Pluf::loadFunction('Pluf_Text_MarkDown_parse');
class IDF_Template_MarkdownForge extends Pluf_Template_Tag
{
private $request;
public function start($text, $request)
{
$this->request = $request;
$filter = new IDF_Template_MarkdownPrefilter();
$text = $filter->go(Pluf_Text_MarkDown_parse($text));
// replace {}-macros with the corresponding template rendering
echo IDF_Template_safePregReplace('#\{(\w+)(?:,\s*([^\}]+))?\}#im',
array($this, 'callbackMacros'),
$text);
}
public function callbackMacros($matches)
{
@list(, $macro, $opts) = $matches;
$known_macros = array('projectlist');
if (!in_array($macro, $known_macros)) {
return $matches[0];
}
$callbackName = 'callback'.ucfirst(strtolower($macro)).'Macro';
return $this->callbackProjectlistMacro($opts);
}
public function callbackProjectlistMacro($opts)
{
$validOpts = array(
'label' => '/^\d+|(\w+:)?\w+$/',
'order' => '/^name|activity$/',
'limit' => '/^\d+$/',
);
$parsedOpts = array();
// FIXME: no support for escaping yet in place
$opts = preg_split('/\s*,\s*/', $opts, -1, PREG_SPLIT_NO_EMPTY);
foreach ((array)@$opts as $opt)
{
list($key, $value) = preg_split('/\s*=\s*/', $opt, 2);
if (!array_key_exists($key, $validOpts)) {
continue;
}
if (!preg_match($validOpts[$key], $value)) {
continue;
}
$parsedOpts[$key] = $value;
}
$tag = false;
if (!empty($parsedOpts['label'])) {
if (is_numeric($parsedOpts['label'])) {
$tag = Pluf::factory('IDF_Tag')->get($parsedOpts['label']);
} else {
@list($class, $name) = preg_split('/:/', $parsedOpts['label'], 2);
if (empty($name)) {
$name = $class;
$class = IDF_TAG_DEFAULT_CLASS;
}
$sql = new Pluf_SQL('class=%s AND lcname=%s AND project=0',
array(strtolower($class), mb_strtolower($name)));
$tag = Pluf::factory('IDF_Tag')->getOne(array('filter' => $sql->gen()));
}
// ignore non-global tags
if ($tag !== false && $tag->project > 0) {
$tag = false;
}
}
$order = 'name';
if (!empty($parsedOpts['order'])) {
$order = $parsedOpts['order'];
}
$projects = IDF_Views::getProjects($this->request->user, $tag, $order);
if (!empty($parsedOpts['limit']) && $parsedOpts['limit'] < count($projects)) {
// there is no array_slice on ArrayObject, do'h!
$projectsCopy = array();
for ($i=0; $i<$parsedOpts['limit']; ++$i)
$projectsCopy[] = $projects[$i];
$projects = $projectsCopy;
}
$tmpl = new Pluf_Template('idf/project-list.html');
$context = new Pluf_Template_Context(array(
'projects' => $projects,
'order' => 'name',
));
return $tmpl->render($context);
}
}

View File

@ -201,7 +201,7 @@ class IDF_Upload extends Pluf_Model
$out .= sprintf(__('<a href="%1$s" title="View download">Download %2$d</a>, %3$s'), $url, $this->id, Pluf_esc($this->summary)).'</td>'; $out .= 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 .= '</tr>';
$out .= "\n".'<tr class="extra"><td colspan="2"> $out .= "\n".'<tr class="extra"><td colspan="2">
<div class="helptext right">'.sprintf(__('Addition of <a href="%s">download&nbsp;%d</a>, by %s'), $url, $this->id, $user).'</div></td></tr>'; <div class="helptext right">'.sprintf(__('Addition of <a href="%1$s">download %2$d</a>, by %3$s'), $url, $this->id, $user).'</div></td></tr>';
return Pluf_Template::markSafe($out); return Pluf_Template::markSafe($out);
} }
@ -211,7 +211,7 @@ class IDF_Upload extends Pluf_Model
.Pluf_HTTP_URL_urlForView('IDF_Views_Download::view', .Pluf_HTTP_URL_urlForView('IDF_Views_Download::view',
array($request->project->shortname, array($request->project->shortname,
$this->id)); $this->id));
$title = sprintf(__('%s: Download %d added - %s'), $title = sprintf(__('%1$s: Download %2$d added - %3$s'),
$request->project->name, $request->project->name,
$this->id, $this->summary); $this->id, $this->summary);
$date = Pluf_Date::gmDateToGmString($this->creation_dtime); $date = Pluf_Date::gmDateToGmString($this->creation_dtime);
@ -234,31 +234,91 @@ class IDF_Upload extends Pluf_Model
*/ */
public function notify($conf, $create=true) public function notify($conf, $create=true)
{ {
if ('' == $conf->getVal('downloads_notification_email', '')) { $project = $this->get_project();
return; $url = str_replace(array('%p', '%d'),
} array($project->shortname, $this->id),
$current_locale = Pluf_Translation::getLocale(); $conf->getVal('upload_webhook_url', ''));
$langs = Pluf::f('languages', array('en'));
Pluf_Translation::loadSetLocale($langs[0]);
$context = new Pluf_Template_Context( $tags = array();
array('file' => $this, foreach ($this->get_tags_list() as $tag) {
'urlfile' => $this->getAbsoluteUrl($this->get_project()), $tags[] = $tag->class.':'.$tag->name;
'project' => $this->get_project(), }
'tags' => $this->get_tags_list(),
)); $submitter = $this->get_submitter();
$tmpl = new Pluf_Template('idf/downloads/download-created-email.txt'); $payload = array(
$text_email = $tmpl->render($context); 'to_send' => array(
$addresses = explode(',', $conf->getVal('downloads_notification_email')); 'project' => $project->shortname,
foreach ($addresses as $address) { 'id' => $this->id,
$email = new Pluf_Mail(Pluf::f('from_email'), 'summary' => $this->summary,
'changelog' => $this->changelog,
'filename' => $this->file,
'filesize' => $this->filesize,
'md5sum' => $this->md5,
'submitter_login' => $submitter->login,
'submitter_email' => $submitter->email,
'tags' => $tags,
),
'project_id' => $project->id,
'authkey' => $project->getWebHookKey(),
'url' => $url,
);
if ($create === true) {
$payload['method'] = 'PUT';
$payload['to_send']['creation_date'] = $this->creation_dtime;
} else {
$payload['method'] = 'POST';
$payload['to_send']['update_date'] = $this->modif_dtime;
}
$item = new IDF_Queue();
$item->type = 'upload';
$item->payload = $payload;
$item->create();
$current_locale = Pluf_Translation::getLocale();
$from_email = Pluf::f('from_email');
$messageId = '<'.md5('upload'.$this->id.md5(Pluf::f('secret_key'))).'@'.Pluf::f('mail_host', 'localhost').'>';
$recipients = $project->getNotificationRecipientsForTab('downloads');
foreach ($recipients as $address => $language) {
if ($this->get_submitter()->email === $address) {
continue;
}
Pluf_Translation::loadSetLocale($language);
$context = new Pluf_Template_Context(array(
'file' => $this,
'urlfile' => $this->getAbsoluteUrl($project),
'project' => $project,
'tags' => $this->get_tags_list(),
));
$tplfile = 'idf/downloads/download-created-email.txt';
$subject = __('New download - %1$s (%2$s)');
$headers = array('Message-ID' => $messageId);
if (!$create) {
$tplfile = 'idf/downloads/download-updated-email.txt';
$subject = __('Updated download - %1$s (%2$s)');
$headers = array('References' => $messageId);
}
$tmpl = new Pluf_Template($tplfile);
$text_email = $tmpl->render($context);
$email = new Pluf_Mail($from_email,
$address, $address,
sprintf(__('New download - %s (%s)'), sprintf($subject,
$this->summary, $this->summary,
$this->get_project()->shortname)); $project->shortname));
$email->addTextMessage($text_email); $email->addTextMessage($text_email);
$email->addHeaders($headers);
$email->sendMail(); $email->sendMail();
} }
Pluf_Translation::loadSetLocale($current_locale); Pluf_Translation::loadSetLocale($current_locale);
} }
} }

View File

@ -32,20 +32,67 @@ Pluf::loadFunction('Pluf_Shortcuts_GetFormForModel');
class IDF_Views class IDF_Views
{ {
/** /**
* List all the projects managed by InDefero. * The index view.
*
* Only the public projects are listed or the private with correct
* rights.
*/ */
public function index($request, $match, $api=false) public function index($request, $match)
{ {
$projects = self::getProjects($request->user); $forge = IDF_Forge::instance();
$stats = self::getProjectsStatistics ($projects); if (!$forge->isCustomForgePageEnabled()) {
$url = Pluf_HTTP_URL_urlForView('IDF_Views::listProjects');
if ($api == true) return $projects; return new Pluf_HTTP_Response_Redirect($url);
return Pluf_Shortcuts_RenderToResponse('idf/index.html', }
return Pluf_Shortcuts_RenderToResponse('idf/index.html',
array('page_title' => __('Welcome'),
'content' => $forge->getCustomForgePageContent(),
),
$request);
}
/**
* List all projects unfiltered
*
* @param unknown_type $request
* @param unknown_type $match
* @return Pluf_HTTP_Response
*/
public function listProjects($request, $match)
{
$match = array('', 'all', 'name');
return $this->listProjectsByLabel($request, $match);
}
/**
* List projects, optionally filtered by label
*
* @param unknown_type $request
* @param unknown_type $match
* @return Pluf_HTTP_Response
*/
public function listProjectsByLabel($request, $match)
{
list(, $tagId, $order) = $match;
$tag = false;
if ($tagId !== 'all') {
$tag = Pluf::factory('IDF_Tag')->get($match[1]);
// ignore non-global tags
if ($tag !== false && $tag->project > 0) {
$tag = false;
}
}
$order = in_array($order, array('name', 'activity')) ? $order : 'name';
$projects = self::getProjects($request->user, $tag, $order);
$stats = self::getProjectsStatistics($projects);
$projectLabels = IDF_Forge::instance()->getProjectLabelsWithCounts();
return Pluf_Shortcuts_RenderToResponse('idf/listProjects.html',
array('page_title' => __('Projects'), array('page_title' => __('Projects'),
'projects' => $projects, 'projects' => $projects,
'projectLabels' => $projectLabels,
'tag' => $tag,
'order' => $order,
'stats' => new Pluf_Template_ContextVars($stats)), 'stats' => new Pluf_Template_ContextVars($stats)),
$request); $request);
} }
@ -55,7 +102,7 @@ class IDF_Views
*/ */
public function login($request, $match) public function login($request, $match)
{ {
if (isset($request->POST['action']) if (isset($request->POST['action'])
and $request->POST['action'] == 'new-user') { and $request->POST['action'] == 'new-user') {
$login = (isset($request->POST['login'])) ? $request->POST['login'] : ''; $login = (isset($request->POST['login'])) ? $request->POST['login'] : '';
$url = Pluf_HTTP_URL_urlForView('IDF_Views::register', array(), $url = Pluf_HTTP_URL_urlForView('IDF_Views::register', array(),
@ -91,7 +138,7 @@ class IDF_Views
$params = array('request'=>$request); $params = array('request'=>$request);
if ($request->method == 'POST') { if ($request->method == 'POST') {
$form = new IDF_Form_Register(array_merge( $form = new IDF_Form_Register(array_merge(
(array)$request->POST, (array)$request->POST,
(array)$request->FILES (array)$request->FILES
), $params); ), $params);
if ($form->isValid()) { if ($form->isValid()) {
@ -108,7 +155,7 @@ class IDF_Views
$context = new Pluf_Template_Context(array()); $context = new Pluf_Template_Context(array());
$tmpl = new Pluf_Template('idf/terms.html'); $tmpl = new Pluf_Template('idf/terms.html');
$terms = Pluf_Template::markSafe($tmpl->render($context)); $terms = Pluf_Template::markSafe($tmpl->render($context));
return Pluf_Shortcuts_RenderToResponse('idf/register/index.html', return Pluf_Shortcuts_RenderToResponse('idf/register/index.html',
array('page_title' => $title, array('page_title' => $title,
'form' => $form, 'form' => $form,
'terms' => $terms), 'terms' => $terms),
@ -133,7 +180,7 @@ class IDF_Views
} else { } else {
$form = new IDF_Form_RegisterInputKey(); $form = new IDF_Form_RegisterInputKey();
} }
return Pluf_Shortcuts_RenderToResponse('idf/register/inputkey.html', return Pluf_Shortcuts_RenderToResponse('idf/register/inputkey.html',
array('page_title' => $title, array('page_title' => $title,
'form' => $form), 'form' => $form),
$request); $request);
@ -168,7 +215,7 @@ class IDF_Views
$request->session->clear(); $request->session->clear();
$request->session->setData('login_time', gmdate('Y-m-d H:i:s')); $request->session->setData('login_time', gmdate('Y-m-d H:i:s'));
$user->last_login = gmdate('Y-m-d H:i:s'); $user->last_login = gmdate('Y-m-d H:i:s');
$user->update(); $user->update();
$request->user->setMessage(__('Welcome! You can now participate in the life of your project of choice.')); $request->user->setMessage(__('Welcome! You can now participate in the life of your project of choice.'));
$url = Pluf_HTTP_URL_urlForView('IDF_Views::index'); $url = Pluf_HTTP_URL_urlForView('IDF_Views::index');
return new Pluf_HTTP_Response_Redirect($url); return new Pluf_HTTP_Response_Redirect($url);
@ -176,7 +223,7 @@ class IDF_Views
} else { } else {
$form = new IDF_Form_RegisterConfirmation(null, $extra); $form = new IDF_Form_RegisterConfirmation(null, $extra);
} }
return Pluf_Shortcuts_RenderToResponse('idf/register/confirmation.html', return Pluf_Shortcuts_RenderToResponse('idf/register/confirmation.html',
array('page_title' => $title, array('page_title' => $title,
'new_user' => $user, 'new_user' => $user,
'form' => $form), 'form' => $form),
@ -213,7 +260,7 @@ class IDF_Views
/** /**
* If the key is valid, provide a nice form to reset the password * If the key is valid, provide a nice form to reset the password
* and automatically login the user. * and automatically login the user.
* *
* This is also firing the password change event for the plugins. * This is also firing the password change event for the plugins.
*/ */
@ -238,7 +285,7 @@ class IDF_Views
$request->session->clear(); $request->session->clear();
$request->session->setData('login_time', gmdate('Y-m-d H:i:s')); $request->session->setData('login_time', gmdate('Y-m-d H:i:s'));
$user->last_login = gmdate('Y-m-d H:i:s'); $user->last_login = gmdate('Y-m-d H:i:s');
$user->update(); $user->update();
$request->user->setMessage(__('Welcome back! Next time, you can use your broswer options to remember the password.')); $request->user->setMessage(__('Welcome back! Next time, you can use your broswer options to remember the password.'));
$url = Pluf_HTTP_URL_urlForView('IDF_Views::index'); $url = Pluf_HTTP_URL_urlForView('IDF_Views::index');
return new Pluf_HTTP_Response_Redirect($url); return new Pluf_HTTP_Response_Redirect($url);
@ -246,12 +293,12 @@ class IDF_Views
} else { } else {
$form = new IDF_Form_PasswordReset(null, $extra); $form = new IDF_Form_PasswordReset(null, $extra);
} }
return Pluf_Shortcuts_RenderToResponse('idf/user/passrecovery.html', return Pluf_Shortcuts_RenderToResponse('idf/user/passrecovery.html',
array('page_title' => $title, array('page_title' => $title,
'new_user' => $user, 'new_user' => $user,
'form' => $form), 'form' => $form),
$request); $request);
} }
/** /**
@ -270,7 +317,7 @@ class IDF_Views
} else { } else {
$form = new IDF_Form_PasswordInputKey(); $form = new IDF_Form_PasswordInputKey();
} }
return Pluf_Shortcuts_RenderToResponse('idf/user/passrecovery-inputkey.html', return Pluf_Shortcuts_RenderToResponse('idf/user/passrecovery-inputkey.html',
array('page_title' => $title, array('page_title' => $title,
'form' => $form), 'form' => $form),
$request); $request);
@ -283,7 +330,23 @@ class IDF_Views
{ {
$title = __('Here to Help You!'); $title = __('Here to Help You!');
$projects = self::getProjects($request->user); $projects = self::getProjects($request->user);
return Pluf_Shortcuts_RenderToResponse('idf/faq.html', return Pluf_Shortcuts_RenderToResponse('idf/faq.html',
array(
'page_title' => $title,
'projects' => $projects,
),
$request);
}
/**
* Download archive FAQ.
*/
public function faqArchiveFormat($request, $match)
{
$title = __('InDefero Upload Archive Format');
$projects = self::getProjects($request->user);
return Pluf_Shortcuts_RenderToResponse('idf/faq-archive-format.html',
array( array(
'page_title' => $title, 'page_title' => $title,
'projects' => $projects, 'projects' => $projects,
@ -299,7 +362,7 @@ class IDF_Views
{ {
$title = __('InDefero API (Application Programming Interface)'); $title = __('InDefero API (Application Programming Interface)');
$projects = self::getProjects($request->user); $projects = self::getProjects($request->user);
return Pluf_Shortcuts_RenderToResponse('idf/faq-api.html', return Pluf_Shortcuts_RenderToResponse('idf/faq-api.html',
array( array(
'page_title' => $title, 'page_title' => $title,
'projects' => $projects, 'projects' => $projects,
@ -309,45 +372,59 @@ class IDF_Views
} }
/** /**
* Returns a list of projects accessible for the user. * Returns a list of projects accessible for the user and optionally filtered by tag.
* *
* @param Pluf_User * @param Pluf_User
* @param IDF_Tag
* @return ArrayObject IDF_Project * @return ArrayObject IDF_Project
*/ */
public static function getProjects($user) public static function getProjects($user, $tag = false, $order = 'name')
{ {
$db =& Pluf::db(); $db =& Pluf::db();
$false = Pluf_DB_BooleanToDb(false, $db); $false = Pluf_DB_BooleanToDb(false, $db);
if ($user->isAnonymous()) { $sql = new Pluf_SQL(1);
$sql = sprintf('%s=%s', $db->qn('private'), $false); if ($tag !== false) {
return Pluf::factory('IDF_Project')->getList(array('filter'=> $sql, $sql->SAnd(new Pluf_SQL('idf_tag_id=%s', $tag->id));
'order' => 'name ASC'));
} }
if ($user->administrator) {
return Pluf::factory('IDF_Project')->getList(array('order' => 'name ASC')); if ($user->isAnonymous())
} {
// grab the list of projects where the user is admin, member $authSql = new Pluf_SQL('private=%s', $false);
// or authorized $sql->SAnd($authSql);
$perms = array( } else
Pluf_Permission::getFromString('IDF.project-member'), if (!$user->administrator) {
Pluf_Permission::getFromString('IDF.project-owner'), // grab the list of projects where the user is admin,
Pluf_Permission::getFromString('IDF.project-authorized-user') // member or authorized
); $perms = array(
$sql = new Pluf_SQL("model_class='IDF_Project' AND owner_class='Pluf_User' AND owner_id=%s AND negative=".$false, $user->id); Pluf_Permission::getFromString('IDF.project-member'),
$rows = Pluf::factory('Pluf_RowPermission')->getList(array('filter' => $sql->gen())); Pluf_Permission::getFromString('IDF.project-owner'),
Pluf_Permission::getFromString('IDF.project-authorized-user')
$sql = sprintf('%s=%s', $db->qn('private'), $false); );
if ($rows->count() > 0) { $permSql = new Pluf_SQL("model_class='IDF_Project' AND owner_class='Pluf_User' AND owner_id=%s AND negative=".$false, $user->id);
$ids = array(); $rows = Pluf::factory('Pluf_RowPermission')->getList(array('filter' => $permSql->gen()));
foreach ($rows as $row) {
$ids[] = $row->model_id; $authSql = new Pluf_SQL('private=%s', $false);
if ($rows->count() > 0) {
$ids = array();
foreach ($rows as $row) {
$ids[] = $row->model_id;
}
$authSql->SOr(new Pluf_SQL(sprintf($db->pfx.'idf_projects.id IN (%s)', implode(', ', $ids))));
} }
$sql .= sprintf(' OR id IN (%s)', implode(', ', $ids)); $sql->SAnd($authSql);
} }
return Pluf::factory('IDF_Project')->getList(array('filter' => $sql,
'order' => 'name ASC')); $orderTypes = array(
'name' => 'name ASC',
'activity' => 'value DESC, name ASC',
);
return Pluf::factory('IDF_Project')->getList(array(
'filter'=> $sql->gen(),
'view' => 'join_activities_and_tags',
'order' => $orderTypes[$order],
));
} }
/** /**
* Returns statistics on a list of projects. * Returns statistics on a list of projects.
* *
@ -362,25 +439,22 @@ class IDF_Views
'issues' => 0, 'issues' => 0,
'docpages' => 0, 'docpages' => 0,
'commits' => 0); 'commits' => 0);
// Count for each projects // Count for each projects
foreach ($projects as $p) { foreach ($projects as $p) {
$pstats = $p->getStats (); $pstats = $p->getStats();
$forgestats['downloads'] += $pstats['downloads']; $forgestats['downloads'] += $pstats['downloads'];
$forgestats['reviews'] += $pstats['reviews']; $forgestats['reviews'] += $pstats['reviews'];
$forgestats['issues'] += $pstats['issues']; $forgestats['issues'] += $pstats['issues'];
$forgestats['docpages'] += $pstats['docpages']; $forgestats['docpages'] += $pstats['docpages'];
$forgestats['commits'] += $pstats['commits']; $forgestats['commits'] += $pstats['commits'];
} }
// Count projects
$forgestats['projects'] = count($projects);
// Count members // Count members
$sql = new Pluf_SQL('first_name != %s', array('---')); $sql = new Pluf_SQL('first_name != %s', array('---'));
$forgestats['members'] = Pluf::factory('Pluf_User') $forgestats['members'] = Pluf::factory('Pluf_User')
->getCount(array('filter' => $sql->gen())); ->getCount(array('filter' => $sql->gen()));
return $forgestats; return $forgestats;
} }
} }

View File

@ -32,20 +32,40 @@ Pluf::loadFunction('Pluf_Shortcuts_GetFormForModel');
class IDF_Views_Admin class IDF_Views_Admin
{ {
/** /**
* Home page of the administration. * Start page of the administration.
*
* It should provide an overview of the forge status.
*/ */
public $home_precond = array('Pluf_Precondition::staffRequired'); public $forge_precond = array('Pluf_Precondition::staffRequired');
public function home($request, $match) public function forge($request, $match)
{ {
$title = __('Forge Management'); $title = __('Forge Management');
return Pluf_Shortcuts_RenderToResponse('idf/gadmin/home.html', $forge = IDF_Forge::instance();
if ($request->method == 'POST') {
$form = new IDF_Form_Admin_ForgeConf($request->POST);
if ($form->isValid()) {
$forge->setCustomForgePageEnabled($form->cleaned_data['enabled']);
$forge->setCustomForgePageContent($form->cleaned_data['content']);
$request->user->setMessage(__('The forge configuration has been saved.'));
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Admin::forge');
return new Pluf_HTTP_Response_Redirect($url);
}
} else {
$params = array();
$params['enabled'] = $forge->isCustomForgePageEnabled();
if (($content = $forge->getCustomForgePageContent(false)) !== false) {
$params['content'] = $content;
}
if (count($params) == 0) {
$params = null; //Nothing in the db, so new form.
}
$form = new IDF_Form_Admin_ForgeConf($params);
}
return Pluf_Shortcuts_RenderToResponse('idf/gadmin/forge/index.html',
array( array(
'page_title' => $title, 'page_title' => $title,
), 'form' => $form,
),
$request); $request);
} }
/** /**
* Projects overview. * Projects overview.
@ -81,6 +101,40 @@ class IDF_Views_Admin
$request); $request);
} }
/**
* Administrate the labels of a project.
*/
public $projectLabels_precond = array('Pluf_Precondition::staffRequired');
public function projectLabels($request, $match)
{
$title = __('Project Labels');
$forge = IDF_Forge::instance();
if ($request->method == 'POST') {
$form = new IDF_Form_Admin_LabelConf($request->POST);
if ($form->isValid()) {
$forge->setProjectLabels($form->cleaned_data['project_labels']);
$request->user->setMessage(__('The label configuration has been saved.'));
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Admin::projectLabels');
return new Pluf_HTTP_Response_Redirect($url);
}
} else {
$params = array();
if (($labels = $forge->getProjectLabels(false)) !== false) {
$params['project_labels'] = $labels;
}
if (count($params) == 0) {
$params = null; //Nothing in the db, so new form.
}
$form = new IDF_Form_Admin_LabelConf($params);
}
return Pluf_Shortcuts_RenderToResponse('idf/gadmin/projects/labels.html',
array(
'page_title' => $title,
'form' => $form,
),
$request);
}
/** /**
* Edition of a project. * Edition of a project.
* *
@ -106,12 +160,16 @@ class IDF_Views_Admin
} else { } else {
$form = new IDF_Form_Admin_ProjectUpdate(null, $params); $form = new IDF_Form_Admin_ProjectUpdate(null, $params);
} }
$arrays = IDF_Views_Project::autoCompleteArrays();
return Pluf_Shortcuts_RenderToResponse('idf/gadmin/projects/update.html', return Pluf_Shortcuts_RenderToResponse('idf/gadmin/projects/update.html',
array( array_merge(
'page_title' => $title, array(
'project' => $project, 'page_title' => $title,
'form' => $form, 'project' => $project,
), 'form' => $form,
),
$arrays
),
$request); $request);
} }
@ -139,12 +197,17 @@ class IDF_Views_Admin
$form = new IDF_Form_Admin_ProjectCreate(null, $extra); $form = new IDF_Form_Admin_ProjectCreate(null, $extra);
} }
$base = Pluf::f('url_base').Pluf::f('idf_base').'/p/'; $base = Pluf::f('url_base').Pluf::f('idf_base').'/p/';
$arrays = IDF_Views_Project::autoCompleteArrays();
return Pluf_Shortcuts_RenderToResponse('idf/gadmin/projects/create.html', return Pluf_Shortcuts_RenderToResponse('idf/gadmin/projects/create.html',
array( array_merge(
'page_title' => $title, array(
'form' => $form, 'page_title' => $title,
'base_url' => $base, 'form' => $form,
), 'base_url' => $base,
),
$arrays
),
$request); $request);
} }

View File

@ -29,7 +29,7 @@
* JSON instead of HTML. * JSON instead of HTML.
* *
* A special precondition is used to set the $request->user from the * A special precondition is used to set the $request->user from the
* _login, _hash and _salt parameters. * _login, _hash and _salt parameters.
*/ */
class IDF_Views_Api class IDF_Views_Api
{ {
@ -90,17 +90,16 @@ class IDF_Views_Api
* List all the projects * List all the projects
*/ */
public $projectIndex_precond = array('IDF_Precondition::apiSetUser'); public $projectIndex_precond = array('IDF_Precondition::apiSetUser');
public function projectIndex($request, $match) public function projectIndex($request, $match)
{ {
$view = new IDF_Views(); $projects = IDF_Views::getProjects($request->user);
$projects = $view->index($request, $match, true);
$data = array(); $data = array();
foreach ($projects as $p) { foreach ($projects as $p) {
$data[] = array("shortname" => $p->shortname, "name" => $p->name, "shortdesc" => $p->shortdesc, "private" => $p->private); $data[] = array("shortname" => $p->shortname, "name" => $p->name, "shortdesc" => $p->shortdesc, "private" => $p->private);
} }
$out = array(); $out = array();
$out['message'] = 'success'; $out['message'] = 'success';
$out['projects'] = $data; $out['projects'] = $data;

View File

@ -205,7 +205,8 @@ class IDF_Views_Download
$path = $upload->getFullPath(); $path = $upload->getFullPath();
$mime = IDF_FileUtil::getMimeType($path); $mime = IDF_FileUtil::getMimeType($path);
$render = new Pluf_HTTP_Response_File($path, $mime[0]); $render = new Pluf_HTTP_Response_File($path, $mime[0]);
$render->headers["Content-MD5"] = $upload->md5; $render->headers['Content-MD5'] = $upload->md5;
$render->headers['Content-Disposition'] = 'attachment; filename="'.$upload->file.'"';
return $render; return $render;
} }
@ -224,11 +225,11 @@ class IDF_Views_Download
} }
/** /**
* Submit a new file for download. * Create a new file for download.
*/ */
public $submit_precond = array('IDF_Precondition::accessDownloads', public $create_precond = array('IDF_Precondition::accessDownloads',
'IDF_Precondition::projectMemberOrOwner'); 'IDF_Precondition::projectMemberOrOwner');
public function submit($request, $match) public function create($request, $match)
{ {
$prj = $request->project; $prj = $request->project;
$title = __('New Download'); $title = __('New Download');
@ -250,7 +251,7 @@ class IDF_Views_Download
array('project' => $prj, array('project' => $prj,
'user' => $request->user)); 'user' => $request->user));
} }
return Pluf_Shortcuts_RenderToResponse('idf/downloads/submit.html', return Pluf_Shortcuts_RenderToResponse('idf/downloads/create.html',
array( array(
'auto_labels' => self::autoCompleteArrays($prj), 'auto_labels' => self::autoCompleteArrays($prj),
'page_title' => $title, 'page_title' => $title,
@ -259,6 +260,39 @@ class IDF_Views_Download
$request); $request);
} }
/**
* Create new downloads from an uploaded archive.
*/
public $createFromArchive_precond = array('IDF_Precondition::accessDownloads',
'IDF_Precondition::projectMemberOrOwner');
public function createFromArchive($request, $match)
{
$prj = $request->project;
$title = __('New Downloads from Archive');
if ($request->method == 'POST') {
$form = new IDF_Form_UploadArchive(array_merge($request->POST, $request->FILES),
array('project' => $prj,
'user' => $request->user));
if ($form->isValid()) {
$upload = $form->save();
$request->user->setMessage(__('The archive has been uploaded and processed.'));
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Download::index',
array($prj->shortname));
return new Pluf_HTTP_Response_Redirect($url);
}
} else {
$form = new IDF_Form_UploadArchive(null,
array('project' => $prj,
'user' => $request->user));
}
return Pluf_Shortcuts_RenderToResponse('idf/downloads/createFromArchive.html',
array(
'page_title' => $title,
'form' => $form,
),
$request);
}
/** /**
* Create the autocomplete arrays for the little AJAX stuff. * Create the autocomplete arrays for the little AJAX stuff.
*/ */

View File

@ -119,6 +119,7 @@ class IDF_Views_Issue
} }
$ownerStatistics[$key] = array($nb, (int)(100 * $nb / $opened), $login); $ownerStatistics[$key] = array($nb, (int)(100 * $nb / $opened), $login);
} }
arsort($ownerStatistics);
// Issue class tag statistics // Issue class tag statistics
$grouped_tags = $prj->getTagCloud(); $grouped_tags = $prj->getTagCloud();
@ -126,6 +127,12 @@ class IDF_Views_Issue
foreach ($tags as $tag) { foreach ($tags as $tag) {
$tagStatistics[$class][$tag->name] = array($tag->nb_use, $tag->id); $tagStatistics[$class][$tag->name] = array($tag->nb_use, $tag->id);
} }
uasort($tagStatistics[$class], function ($a, $b) {
if ($a[0] === $b[0])
return 0;
return ($a[0] > $b[0]) ? -1 : 1;
});
} }
foreach($tagStatistics as $k => $v) { foreach($tagStatistics as $k => $v) {
$nbIssueInClass = 0; $nbIssueInClass = 0;
@ -136,10 +143,6 @@ class IDF_Views_Issue
$tagStatistics[$k][$kk] = array($vv[0], (int)(100 * $vv[0] / $nbIssueInClass), $vv[1]); $tagStatistics[$k][$kk] = array($vv[0], (int)(100 * $vv[0] / $nbIssueInClass), $vv[1]);
} }
} }
// Sort
krsort($tagStatistics);
arsort($ownerStatistics);
} }
} }
@ -338,19 +341,19 @@ class IDF_Views_Issue
if (count($ctags) == 0) $ctags[] = 0; if (count($ctags) == 0) $ctags[] = 0;
switch ($match[3]) { switch ($match[3]) {
case 'submit': case 'submit':
$titleFormat = __('%s %s Submitted %s Issues'); $titleFormat = __('%1$s %2$s Submitted %3$s Issues');
$f_sql = new Pluf_SQL('project=%s AND submitter=%s AND status IN ('.implode(', ', $otags).')', array($prj->id, $user->id)); $f_sql = new Pluf_SQL('project=%s AND submitter=%s AND status IN ('.implode(', ', $otags).')', array($prj->id, $user->id));
break; break;
case 'submitclosed': case 'submitclosed':
$titleFormat = __('%s %s Closed Submitted %s Issues'); $titleFormat = __('%1$s %2$s Closed Submitted %3$s Issues');
$f_sql = new Pluf_SQL('project=%s AND submitter=%s AND status IN ('.implode(', ', $ctags).')', array($prj->id, $user->id)); $f_sql = new Pluf_SQL('project=%s AND submitter=%s AND status IN ('.implode(', ', $ctags).')', array($prj->id, $user->id));
break; break;
case 'ownerclosed': case 'ownerclosed':
$titleFormat = __('%s %s Closed Working %s Issues'); $titleFormat = __('%1$s %2$s Closed Working %3$s Issues');
$f_sql = new Pluf_SQL('project=%s AND owner=%s AND status IN ('.implode(', ', $ctags).')', array($prj->id, $user->id)); $f_sql = new Pluf_SQL('project=%s AND owner=%s AND status IN ('.implode(', ', $ctags).')', array($prj->id, $user->id));
break; break;
default: default:
$titleFormat = __('%s %s Working %s Issues'); $titleFormat = __('%1$s %2$s Working %3$s Issues');
$f_sql = new Pluf_SQL('project=%s AND owner=%s AND status IN ('.implode(', ', $otags).')', array($prj->id, $user->id)); $f_sql = new Pluf_SQL('project=%s AND owner=%s AND status IN ('.implode(', ', $otags).')', array($prj->id, $user->id));
break; break;
} }
@ -427,7 +430,7 @@ class IDF_Views_Issue
array($prj->shortname, $issue->id)); array($prj->shortname, $issue->id));
$issue->notify($request->conf); $issue->notify($request->conf);
if ($api) return $issue; if ($api) return $issue;
$request->user->setMessage(sprintf(__('<a href="%s">Issue %d</a> has been created.'), $url, $issue->id)); $request->user->setMessage(sprintf(__('<a href="%1$s">Issue %2$d</a> has been created.'), $url, $issue->id));
return new Pluf_HTTP_Response_Redirect($url); return new Pluf_HTTP_Response_Redirect($url);
} }
} else { } else {
@ -598,7 +601,7 @@ class IDF_Views_Issue
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Issue::view', $url = Pluf_HTTP_URL_urlForView('IDF_Views_Issue::view',
array($prj->shortname, $issue->id)); array($prj->shortname, $issue->id));
$title = Pluf_Template::markSafe(sprintf(__('Issue <a href="%s">%d</a>: %s'), $url, $issue->id, $issue->summary)); $title = Pluf_Template::markSafe(sprintf(__('Issue <a href="%1$s">%2$d</a>: %3$s'), $url, $issue->id, $issue->summary));
$form = false; // The form is available only if logged in. $form = false; // The form is available only if logged in.
$starred = false; $starred = false;
$closed = in_array($issue->status, $prj->getTagIdsByStatus('closed')); $closed = in_array($issue->status, $prj->getTagIdsByStatus('closed'));
@ -622,7 +625,7 @@ class IDF_Views_Issue
$issue->notify($request->conf, false); $issue->notify($request->conf, false);
$comments = $issue->get_comments_list(array('order' => 'id DESC')); $comments = $issue->get_comments_list(array('order' => 'id DESC'));
$url .= '#ic' . $comments[0]->id; $url .= '#ic' . $comments[0]->id;
$request->user->setMessage(sprintf(__('<a href="%s">Issue %d</a> has been updated.'), $url, $issue->id)); $request->user->setMessage(sprintf(__('<a href="%1$s">Issue %2$d</a> has been updated.'), $url, $issue->id));
return new Pluf_HTTP_Response_Redirect($url); return new Pluf_HTTP_Response_Redirect($url);
} }
} else { } else {
@ -993,9 +996,8 @@ class IDF_Views_Issue
$r = $project->getRelationsFromConfig(); $r = $project->getRelationsFromConfig();
$auto['auto_relation_types'] = ''; $auto['auto_relation_types'] = '';
foreach ($r as $rt) { foreach ($r as $rt) {
$esc = Pluf_esc($rt);
$auto['auto_relation_types'] .= sprintf('{ name: "%s", to: "%s" }, ', $auto['auto_relation_types'] .= sprintf('{ name: "%s", to: "%s" }, ',
$esc, $esc); Pluf_esc(__($rt)), Pluf_esc($rt));
} }
$auto['auto_relation_types'] = substr($auto['auto_relation_types'], 0, -2); $auto['auto_relation_types'] = substr($auto['auto_relation_types'], 0, -2);
return $auto; return $auto;

View File

@ -68,7 +68,7 @@ class IDF_Views_Project
$pages = array(); $pages = array();
if ($request->rights['hasWikiAccess']) { if ($request->rights['hasWikiAccess']) {
$tags = IDF_Views_Wiki::getWikiTags($prj); $tags = IDF_Views_Wiki::getWikiTags($prj);
$pages = $tags[0]->get_idf_wikipage_list(); $pages = $tags[0]->get_idf_wiki_page_list();
} }
return Pluf_Shortcuts_RenderToResponse('idf/project/home.html', return Pluf_Shortcuts_RenderToResponse('idf/project/home.html',
array( array(
@ -131,8 +131,10 @@ class IDF_Views_Project
} }
if (true === IDF_Precondition::accessWiki($request) && if (true === IDF_Precondition::accessWiki($request) &&
($model_filter == 'all' || $model_filter == 'documents')) { ($model_filter == 'all' || $model_filter == 'documents')) {
$classes[] = '\'IDF_WikiPage\''; $classes[] = '\'IDF_Wiki_Page\'';
$classes[] = '\'IDF_WikiRevision\''; $classes[] = '\'IDF_Wiki_PageRevision\'';
$classes[] = '\'IDF_Wiki_Resource\'';
$classes[] = '\'IDF_Wiki_ResourceRevision\'';
} }
if (true === IDF_Precondition::accessReview($request) && if (true === IDF_Precondition::accessReview($request) &&
($model_filter == 'all' || $model_filter == 'reviews')) { ($model_filter == 'all' || $model_filter == 'reviews')) {
@ -305,17 +307,21 @@ class IDF_Views_Project
return new Pluf_HTTP_Response_Redirect($url); return new Pluf_HTTP_Response_Redirect($url);
} }
} else { } else {
$form = new IDF_Form_ProjectConf($prj->getData(), $extra); $form = new IDF_Form_ProjectConf(null, $extra);
} }
$logo = $prj->getConf()->getVal('logo'); $logo = $prj->getConf()->getVal('logo');
$arrays = self::autoCompleteArrays();
return Pluf_Shortcuts_RenderToResponse('idf/admin/summary.html', return Pluf_Shortcuts_RenderToResponse('idf/admin/summary.html',
array( array_merge(
'page_title' => $title, array(
'form' => $form, 'page_title' => $title,
'project' => $prj, 'form' => $form,
'logo' => $logo, 'project' => $prj,
), 'logo' => $logo,
),
$arrays
),
$request); $request);
} }
@ -375,8 +381,11 @@ class IDF_Views_Project
$title = sprintf(__('%s Downloads Configuration'), (string) $prj); $title = sprintf(__('%s Downloads Configuration'), (string) $prj);
$conf = new IDF_Conf(); $conf = new IDF_Conf();
$conf->setProject($prj); $conf->setProject($prj);
$extra = array(
'conf' => $conf,
);
if ($request->method == 'POST') { if ($request->method == 'POST') {
$form = new IDF_Form_UploadConf($request->POST); $form = new IDF_Form_UploadConf($request->POST, $extra);
if ($form->isValid()) { if ($form->isValid()) {
foreach ($form->cleaned_data as $key=>$val) { foreach ($form->cleaned_data as $key=>$val) {
$conf->setVal($key, $val); $conf->setVal($key, $val);
@ -388,7 +397,7 @@ class IDF_Views_Project
} }
} else { } else {
$params = array(); $params = array();
$keys = array('labels_download_predefined', 'labels_download_one_max'); $keys = array('labels_download_predefined', 'labels_download_one_max', 'upload_webhook_url');
foreach ($keys as $key) { foreach ($keys as $key) {
$_val = $conf->getVal($key, false); $_val = $conf->getVal($key, false);
if ($_val !== false) { if ($_val !== false) {
@ -398,12 +407,13 @@ class IDF_Views_Project
if (count($params) == 0) { if (count($params) == 0) {
$params = null; //Nothing in the db, so new form. $params = null; //Nothing in the db, so new form.
} }
$form = new IDF_Form_UploadConf($params); $form = new IDF_Form_UploadConf($params, $extra);
} }
return Pluf_Shortcuts_RenderToResponse('idf/admin/downloads.html', return Pluf_Shortcuts_RenderToResponse('idf/admin/downloads.html',
array( array(
'page_title' => $title, 'page_title' => $title,
'form' => $form, 'form' => $form,
'hookkey' => $prj->getWebHookKey(),
), ),
$request); $request);
} }
@ -504,21 +514,24 @@ class IDF_Views_Project
} }
} }
$form->save(); // Save the authorized users. $form->save(); // Save the authorized users.
$request->user->setMessage(__('The project tabs access rights have been saved.')); $request->user->setMessage(__('The project tabs access rights and notification settings have been saved.'));
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Project::adminTabs', $url = Pluf_HTTP_URL_urlForView('IDF_Views_Project::adminTabs',
array($prj->shortname)); array($prj->shortname));
return new Pluf_HTTP_Response_Redirect($url); return new Pluf_HTTP_Response_Redirect($url);
} }
} else { } else {
$params = array(); $params = array();
$keys = array('downloads_access_rights', 'source_access_rights', $sections = array('downloads', 'wiki', 'source', 'issues', 'review');
'issues_access_rights', 'review_access_rights', $keys = array();
'wiki_access_rights',
'downloads_notification_email', foreach ($sections as $section) {
'review_notification_email', $keys[] = $section.'_access_rights';
'wiki_notification_email', $keys[] = $section.'_notification_owners_enabled';
'source_notification_email', $keys[] = $section.'_notification_members_enabled';
'issues_notification_email'); $keys[] = $section.'_notification_email_enabled';
$keys[] = $section.'_notification_email';
}
foreach ($keys as $key) { foreach ($keys as $key) {
$_val = $request->conf->getVal($key, false); $_val = $request->conf->getVal($key, false);
if ($_val !== false) { if ($_val !== false) {
@ -590,6 +603,10 @@ class IDF_Views_Project
'mtn' => __('monotone'), 'mtn' => __('monotone'),
); );
$repository_type = $options[$scm]; $repository_type = $options[$scm];
$hook_request_method = 'PUT';
if (Pluf::f('webhook_processing','') === 'compat') {
$hook_request_method = 'POST';
}
return Pluf_Shortcuts_RenderToResponse('idf/admin/source.html', return Pluf_Shortcuts_RenderToResponse('idf/admin/source.html',
array( array(
'remote_svn' => $remote_svn, 'remote_svn' => $remote_svn,
@ -598,8 +615,41 @@ class IDF_Views_Project
'repository_size' => $prj->getRepositorySize(), 'repository_size' => $prj->getRepositorySize(),
'page_title' => $title, 'page_title' => $title,
'form' => $form, 'form' => $form,
'hookkey' => $prj->getPostCommitHookKey(), 'hookkey' => $prj->getWebHookKey(),
'hook_request_method' => $hook_request_method,
), ),
$request); $request);
} }
/**
* Create the autocomplete arrays for the little AJAX stuff.
*/
public static function autoCompleteArrays()
{
$forge = IDF_Forge::instance();
$labels = $forge->getProjectLabels(IDF_Form_Admin_LabelConf::init_project_labels);
$auto = array('auto_labels' => '');
$auto_raw = array('auto_labels' => $labels);
foreach ($auto_raw as $key => $st) {
$st = preg_split("/\015\012|\015|\012/", $st, -1, PREG_SPLIT_NO_EMPTY);
foreach ($st as $s) {
$v = '';
$d = '';
$_s = explode('=', $s, 2);
if (count($_s) > 1) {
$v = trim($_s[0]);
$d = trim($_s[1]);
} else {
$v = trim($_s[0]);
}
$auto[$key] .= sprintf('{ name: "%s", to: "%s" }, ',
Pluf_esc($d),
Pluf_esc($v));
}
$auto[$key] = substr($auto[$key], 0, -2);
}
return $auto;
}
} }

View File

@ -91,7 +91,7 @@ class IDF_Views_Review
$review = $form->save(); $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)); array($prj->shortname, $review->id));
$request->user->setMessage(sprintf(__('The <a href="%s">code review %d</a> has been created.'), $urlr, $review->id)); $request->user->setMessage(sprintf(__('The <a href="%1$s">code review %2$d</a> has been created.'), $urlr, $review->id));
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Review::index', $url = Pluf_HTTP_URL_urlForView('IDF_Views_Review::index',
array($prj->shortname)); array($prj->shortname));
return new Pluf_HTTP_Response_Redirect($url); return new Pluf_HTTP_Response_Redirect($url);
@ -137,7 +137,7 @@ class IDF_Views_Review
$prj->inOr404($review); $prj->inOr404($review);
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Review::view', $url = Pluf_HTTP_URL_urlForView('IDF_Views_Review::view',
array($prj->shortname, $review->id)); array($prj->shortname, $review->id));
$title = Pluf_Template::markSafe(sprintf(__('Review <a href="%s">%d</a>: %s'), $url, $review->id, $review->summary)); $title = Pluf_Template::markSafe(sprintf(__('Review <a href="%1$s">%2$d</a>: %3$s'), $url, $review->id, $review->summary));
$patches = $review->get_patches_list(); $patches = $review->get_patches_list();
$patch = $patches[0]; $patch = $patches[0];
@ -157,7 +157,7 @@ class IDF_Views_Review
$review = $patch->get_review(); $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)); array($prj->shortname, $review->id));
$request->user->setMessage(sprintf(__('Your <a href="%s">code review %d</a> has been published.'), $urlr, $review->id)); $request->user->setMessage(sprintf(__('Your <a href="%1$s">code review %2$d</a> has been published.'), $urlr, $review->id));
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Review::index', $url = Pluf_HTTP_URL_urlForView('IDF_Views_Review::index',
array($prj->shortname)); array($prj->shortname));
$review_comment->notify($request->conf); $review_comment->notify($request->conf);

View File

@ -132,6 +132,12 @@ class IDF_Views_Source
$request); $request);
} }
public function repository($request, $match)
{
$scm = IDF_Scm::get($request->project);
return $scm->repository($request, $match);
}
public $treeBase_precond = array('IDF_Precondition::accessSource', public $treeBase_precond = array('IDF_Precondition::accessSource',
'IDF_Views_Source_Precondition::scmAvailable', 'IDF_Views_Source_Precondition::scmAvailable',
'IDF_Views_Source_Precondition::revisionValid'); 'IDF_Views_Source_Precondition::revisionValid');
@ -302,7 +308,7 @@ class IDF_Views_Source
throw new Exception('could not retrieve commit object for '. $commit); throw new Exception('could not retrieve commit object for '. $commit);
} }
$title = sprintf(__('%s Commit Details'), (string) $request->project); $title = sprintf(__('%s Commit Details'), (string) $request->project);
$page_title = sprintf(__('%s Commit Details - %s'), (string) $request->project, $commit); $page_title = sprintf(__('%1$s Commit Details - %2$s'), (string) $request->project, $commit);
$rcommit = IDF_Commit::getOrAdd($cobject, $request->project); $rcommit = IDF_Commit::getOrAdd($cobject, $request->project);
$diff = new IDF_Diff($cobject->diff, $scm->getDiffPathStripLevel()); $diff = new IDF_Diff($cobject->diff, $scm->getDiffPathStripLevel());
$cobject->diff = null; $cobject->diff = null;
@ -506,12 +512,12 @@ function IDF_Views_Source_PrettySizeSimple($size)
function IDF_Views_Source_ShortenString($string, $length) function IDF_Views_Source_ShortenString($string, $length)
{ {
$ellipse = "..."; $ellipse = "...";
$length = max(strlen($ellipse) + 2, $length); $length = max(mb_strlen($ellipse) + 2, $length);
$preflen = ceil($length / 10); $preflen = ceil($length / 10);
if (mb_strlen($string) < $length) if (mb_strlen($string) < $length)
return $string; return $string;
return substr($string, 0, $preflen).$ellipse. return mb_substr($string, 0, $preflen).$ellipse.
substr($string, -($length - $preflen - mb_strlen($ellipse))); mb_substr($string, -($length - $preflen - mb_strlen($ellipse)));
} }

View File

@ -32,22 +32,22 @@ Pluf::loadFunction('Pluf_Shortcuts_GetFormForModel');
class IDF_Views_Wiki class IDF_Views_Wiki
{ {
/** /**
* View list of issues for a given project. * View list of pages for a given project.
*/ */
public $index_precond = array('IDF_Precondition::accessWiki'); public $listPages_precond = array('IDF_Precondition::accessWiki');
public function index($request, $match, $api=false) public function listPages($request, $match, $api=false)
{ {
$prj = $request->project; $prj = $request->project;
$title = sprintf(__('%s Documentation'), (string) $prj); $title = sprintf(__('%s Documentation'), (string) $prj);
// Paginator to paginate the pages // Paginator to paginate the pages
$pag = new Pluf_Paginator(new IDF_WikiPage()); $pag = new Pluf_Paginator(new IDF_Wiki_Page());
$pag->class = 'recent-issues'; $pag->class = 'recent-issues';
$pag->item_extra_props = array('project_m' => $prj, $pag->item_extra_props = array('project_m' => $prj,
'shortname' => $prj->shortname, 'shortname' => $prj->shortname,
'current_user' => $request->user); 'current_user' => $request->user);
$pag->summary = __('This table shows the documentation pages.'); $pag->summary = __('This table shows the documentation pages.');
$pag->action = array('IDF_Views_Wiki::index', array($prj->shortname)); $pag->action = array('IDF_Views_Wiki::listPages', array($prj->shortname));
$pag->edit_action = array('IDF_Views_Wiki::view', 'shortname', 'title'); $pag->edit_action = array('IDF_Views_Wiki::viewPage', 'shortname', 'title');
$sql = 'project=%s'; $sql = 'project=%s';
$ptags = self::getWikiTags($prj); $ptags = self::getWikiTags($prj);
$dtag = array_pop($ptags); // The last tag is the deprecated tag. $dtag = array_pop($ptags); // The last tag is the deprecated tag.
@ -67,7 +67,7 @@ class IDF_Views_Wiki
$pag->no_results_text = __('No documentation pages were found.'); $pag->no_results_text = __('No documentation pages were found.');
$pag->sort_order = array('title', 'ASC'); $pag->sort_order = array('title', 'ASC');
$pag->setFromRequest($request); $pag->setFromRequest($request);
return Pluf_Shortcuts_RenderToResponse('idf/wiki/index.html', return Pluf_Shortcuts_RenderToResponse('idf/wiki/listPages.html',
array( array(
'page_title' => $title, 'page_title' => $title,
'pages' => $pag, 'pages' => $pag,
@ -77,18 +77,56 @@ class IDF_Views_Wiki
$request); $request);
} }
/**
* View list of resources for a given project.
*/
public $listResources_precond = array('IDF_Precondition::accessWiki',
'Pluf_Precondition::loginRequired');
public function listResources($request, $match)
{
$prj = $request->project;
$title = sprintf(__('%s Documentation Resources'), (string) $prj);
$pag = new Pluf_Paginator(new IDF_Wiki_Resource());
$pag->class = 'recent-issues';
$pag->item_extra_props = array('project_m' => $prj,
'shortname' => $prj->shortname,
'current_user' => $request->user);
$pag->summary = __('This table shows the resources that can be used on documentation pages.');
$pag->action = array('IDF_Views_Wiki::listResources', array($prj->shortname));
$pag->edit_action = array('IDF_Views_Wiki::viewResource', 'shortname', 'title');
$pag->forced_where = new Pluf_SQL('project=%s', array($prj->id));
$pag->extra_classes = array('right', 'a-c', 'left', 'a-c');
$list_display = array(
'title' => __('Resource Title'),
'mime_type' => __('MIME type'),
'summary' => __('Description'),
array('modif_dtime', 'Pluf_Paginator_DateYMD', __('Updated')),
);
$pag->configure($list_display, array(), array('title', 'modif_dtime'));
$pag->items_per_page = 25;
$pag->no_results_text = __('No resources were found.');
$pag->sort_order = array('title', 'ASC');
$pag->setFromRequest($request);
return Pluf_Shortcuts_RenderToResponse('idf/wiki/listResources.html',
array(
'page_title' => $title,
'resources' => $pag,
),
$request);
}
public $search_precond = array('IDF_Precondition::accessWiki',); public $search_precond = array('IDF_Precondition::accessWiki',);
public function search($request, $match) public function search($request, $match)
{ {
$prj = $request->project; $prj = $request->project;
if (!isset($request->REQUEST['q']) or trim($request->REQUEST['q']) == '') { if (!isset($request->REQUEST['q']) or trim($request->REQUEST['q']) == '') {
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::index', $url = Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::listPages',
array($prj->shortname)); array($prj->shortname));
return new Pluf_HTTP_Response_Redirect($url); return new Pluf_HTTP_Response_Redirect($url);
} }
$q = $request->REQUEST['q']; $q = $request->REQUEST['q'];
$title = sprintf(__('Documentation Search - %s'), $q); $title = sprintf(__('Documentation Search - %s'), $q);
$pages = new Pluf_Search_ResultSet(IDF_Search::mySearch($q, $prj, 'IDF_WikiPage')); $pages = new Pluf_Search_ResultSet(IDF_Search::mySearch($q, $prj, 'IDF_Wiki_Page'));
if (count($pages) > 100) { if (count($pages) > 100) {
$pages->results = array_slice($pages->results, 0, 100); $pages->results = array_slice($pages->results, 0, 100);
} }
@ -100,7 +138,7 @@ class IDF_Views_Wiki
'current_user' => $request->user); 'current_user' => $request->user);
$pag->summary = __('This table shows the pages found.'); $pag->summary = __('This table shows the pages found.');
$pag->action = array('IDF_Views_Wiki::search', array($prj->shortname), array('q'=> $q)); $pag->action = array('IDF_Views_Wiki::search', array($prj->shortname), array('q'=> $q));
$pag->edit_action = array('IDF_Views_Wiki::view', 'shortname', 'title'); $pag->edit_action = array('IDF_Views_Wiki::viewPage', 'shortname', 'title');
$pag->extra_classes = array('right', '', 'a-c'); $pag->extra_classes = array('right', '', 'a-c');
$list_display = array( $list_display = array(
'title' => __('Page Title'), 'title' => __('Page Title'),
@ -122,8 +160,8 @@ class IDF_Views_Wiki
/** /**
* View list of pages with a given label. * View list of pages with a given label.
*/ */
public $listLabel_precond = array('IDF_Precondition::accessWiki'); public $listPagesWithLabel_precond = array('IDF_Precondition::accessWiki');
public function listLabel($request, $match) public function listPagesWithLabel($request, $match)
{ {
$prj = $request->project; $prj = $request->project;
$tag = Pluf_Shortcuts_GetObjectOr404('IDF_Tag', $match[2]); $tag = Pluf_Shortcuts_GetObjectOr404('IDF_Tag', $match[2]);
@ -133,15 +171,15 @@ class IDF_Views_Wiki
// Paginator to paginate the pages // Paginator to paginate the pages
$ptags = self::getWikiTags($prj); $ptags = self::getWikiTags($prj);
$dtag = array_pop($ptags); // The last tag is the deprecated tag. $dtag = array_pop($ptags); // The last tag is the deprecated tag.
$pag = new Pluf_Paginator(new IDF_WikiPage()); $pag = new Pluf_Paginator(new IDF_Wiki_Page());
$pag->model_view = 'join_tags'; $pag->model_view = 'join_tags';
$pag->class = 'recent-issues'; $pag->class = 'recent-issues';
$pag->item_extra_props = array('project_m' => $prj, $pag->item_extra_props = array('project_m' => $prj,
'shortname' => $prj->shortname); 'shortname' => $prj->shortname);
$pag->summary = sprintf(__('This table shows the documentation pages with label %s.'), (string) $tag); $pag->summary = sprintf(__('This table shows the documentation pages with label %s.'), (string) $tag);
$pag->forced_where = new Pluf_SQL('project=%s AND idf_tag_id=%s', array($prj->id, $tag->id)); $pag->forced_where = new Pluf_SQL('project=%s AND idf_tag_id=%s', array($prj->id, $tag->id));
$pag->action = array('IDF_Views_Wiki::listLabel', array($prj->shortname, $tag->id)); $pag->action = array('IDF_Views_Wiki::listPagesWithLabel', array($prj->shortname, $tag->id));
$pag->edit_action = array('IDF_Views_Wiki::view', 'shortname', 'title'); $pag->edit_action = array('IDF_Views_Wiki::viewPage', 'shortname', 'title');
$pag->extra_classes = array('right', '', 'a-c'); $pag->extra_classes = array('right', '', 'a-c');
$list_display = array( $list_display = array(
'title' => __('Page Title'), 'title' => __('Page Title'),
@ -152,7 +190,7 @@ class IDF_Views_Wiki
$pag->items_per_page = 25; $pag->items_per_page = 25;
$pag->no_results_text = __('No documentation pages were found.'); $pag->no_results_text = __('No documentation pages were found.');
$pag->setFromRequest($request); $pag->setFromRequest($request);
return Pluf_Shortcuts_RenderToResponse('idf/wiki/index.html', return Pluf_Shortcuts_RenderToResponse('idf/wiki/listPages.html',
array( array(
'page_title' => $title, 'page_title' => $title,
'label' => $tag, 'label' => $tag,
@ -165,24 +203,25 @@ class IDF_Views_Wiki
/** /**
* Create a new documentation page. * Create a new documentation page.
*/ */
public $create_precond = array('IDF_Precondition::accessWiki', public $createPage_precond = array('IDF_Precondition::accessWiki',
'Pluf_Precondition::loginRequired'); 'Pluf_Precondition::loginRequired');
public function create($request, $match) public function createPage($request, $match)
{ {
$prj = $request->project; $prj = $request->project;
$title = __('New Page'); $title = __('New Page');
$preview = false; $preview = false;
if ($request->method == 'POST') { if ($request->method == 'POST') {
$form = new IDF_Form_WikiCreate($request->POST, $form = new IDF_Form_WikiPageCreate($request->POST,
array('project' => $prj, array('project' => $prj,
'user' => $request->user 'user' => $request->user
)); ));
if ($form->isValid() and !isset($request->POST['preview'])) { if ($form->isValid() and !isset($request->POST['preview'])) {
$page = $form->save(); $page = $form->save();
$urlpage = Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::view', $urlpage = Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::viewPage',
array($prj->shortname, $page->title)); array($prj->shortname, $page->title));
$request->user->setMessage(sprintf(__('The page <a href="%s">%s</a> has been created.'), $urlpage, Pluf_esc($page->title))); $request->user->setMessage(sprintf(__('The page <a href="%1$s">%2$s</a> has been created.'),
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::index', $urlpage, Pluf_esc($page->title)));
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::listPages',
array($prj->shortname)); array($prj->shortname));
return new Pluf_HTTP_Response_Redirect($url); return new Pluf_HTTP_Response_Redirect($url);
} elseif (isset($request->POST['preview'])) { } elseif (isset($request->POST['preview'])) {
@ -191,12 +230,12 @@ class IDF_Views_Wiki
} else { } else {
$pagename = (isset($request->GET['name'])) ? $pagename = (isset($request->GET['name'])) ?
$request->GET['name'] : ''; $request->GET['name'] : '';
$form = new IDF_Form_WikiCreate(null, $form = new IDF_Form_WikiPageCreate(null,
array('name' => $pagename, array('name' => $pagename,
'project' => $prj, 'project' => $prj,
'user' => $request->user)); 'user' => $request->user));
} }
return Pluf_Shortcuts_RenderToResponse('idf/wiki/create.html', return Pluf_Shortcuts_RenderToResponse('idf/wiki/createPage.html',
array( array(
'auto_labels' => self::autoCompleteArrays($prj), 'auto_labels' => self::autoCompleteArrays($prj),
'page_title' => $title, 'page_title' => $title,
@ -206,27 +245,65 @@ class IDF_Views_Wiki
$request); $request);
} }
/**
* Create a new resource.
*/
public $createResource_precond = array('IDF_Precondition::accessWiki',
'Pluf_Precondition::loginRequired');
public function createResource($request, $match)
{
$prj = $request->project;
$title = __('New Resource');
$preview = false;
if ($request->method == 'POST') {
$form = new IDF_Form_WikiResourceCreate(array_merge($request->POST, $request->FILES),
array('project' => $prj, 'user' => $request->user));
if ($form->isValid()) {
$resource = $form->save();
$urlresource = Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::viewResource',
array($prj->shortname, $resource->title));
$request->user->setMessage(sprintf(__('The resource <a href="%1$s">%2$s</a> has been created.'), $urlresource, Pluf_esc($resource->title)));
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::listResources',
array($prj->shortname));
return new Pluf_HTTP_Response_Redirect($url);
}
} else {
$resourcename = (isset($request->GET['name'])) ?
$request->GET['name'] : '';
$form = new IDF_Form_WikiResourceCreate(null,
array('name' => $resourcename,
'project' => $prj, 'user' => $request->user));
}
return Pluf_Shortcuts_RenderToResponse('idf/wiki/createResource.html',
array(
'page_title' => $title,
'form' => $form,
),
$request);
}
/** /**
* View a documentation page. * View a documentation page.
*/ */
public $view_precond = array('IDF_Precondition::accessWiki'); public $viewPage_precond = array('IDF_Precondition::accessWiki');
public function view($request, $match) public function viewPage($request, $match)
{ {
$prj = $request->project; $prj = $request->project;
// Find the page // Find the page
$sql = new Pluf_SQL('project=%s AND title=%s', $sql = new Pluf_SQL('project=%s AND title=%s',
array($prj->id, $match[2])); array($prj->id, $match[2]));
$pages = Pluf::factory('IDF_WikiPage')->getList(array('filter'=>$sql->gen())); $pages = Pluf::factory('IDF_Wiki_Page')->getList(array('filter'=>$sql->gen()));
if ($pages->count() != 1) { if ($pages->count() != 1) {
return new Pluf_HTTP_Response_NotFound($request); return new Pluf_HTTP_Response_NotFound($request);
} }
$page = $pages[0]; $page = $pages[0];
$oldrev = false; $revision = $page->get_current_revision();
// We grab the old revision if requested. // We grab the old revision if requested.
if (isset($request->GET['rev']) and preg_match('/^[0-9]+$/', $request->GET['rev'])) { if (isset($request->GET['rev']) and preg_match('/^[0-9]+$/', $request->GET['rev'])) {
$oldrev = Pluf_Shortcuts_GetObjectOr404('IDF_WikiRevision', $revision = Pluf_Shortcuts_GetObjectOr404('IDF_Wiki_PageRevision',
$request->GET['rev']); $request->GET['rev']);
if ($oldrev->wikipage != $page->id or $oldrev->is_head == true) { if ($revision->wikipage != $page->id) {
return new Pluf_HTTP_Response_NotFound($request); return new Pluf_HTTP_Response_NotFound($request);
} }
} }
@ -235,15 +312,13 @@ class IDF_Views_Wiki
$tags = $page->get_tags_list(); $tags = $page->get_tags_list();
$dep = Pluf_Model_InArray($dtag, $tags); $dep = Pluf_Model_InArray($dtag, $tags);
$title = $page->title; $title = $page->title;
$revision = $page->get_current_revision();
$false = Pluf_DB_BooleanToDb(false, $page->getDbConnection()); $false = Pluf_DB_BooleanToDb(false, $page->getDbConnection());
$revs = $page->get_revisions_list(array('order' => 'creation_dtime DESC', $revs = $page->get_revisions_list(array('order' => 'creation_dtime DESC',
'filter' => 'is_head='.$false)); 'filter' => 'is_head='.$false));
return Pluf_Shortcuts_RenderToResponse('idf/wiki/view.html', return Pluf_Shortcuts_RenderToResponse('idf/wiki/viewPage.html',
array( array(
'page_title' => $title, 'page_title' => $title,
'page' => $page, 'page' => $page,
'oldrev' => $oldrev,
'rev' => $revision, 'rev' => $revision,
'revs' => $revs, 'revs' => $revs,
'tags' => $tags, 'tags' => $tags,
@ -253,14 +328,77 @@ class IDF_Views_Wiki
} }
/** /**
* Remove a revision of a page. * View a documentation resource.
*/ */
public $deleteRev_precond = array('IDF_Precondition::accessWiki', public $viewResource_precond = array('IDF_Precondition::accessWiki');
'IDF_Precondition::projectMemberOrOwner'); public function viewResource($request, $match)
public function deleteRev($request, $match)
{ {
$prj = $request->project; $prj = $request->project;
$oldrev = Pluf_Shortcuts_GetObjectOr404('IDF_WikiRevision', $match[2]); $sql = new Pluf_SQL('project=%s AND title=%s',
array($prj->id, $match[2]));
$resources = Pluf::factory('IDF_Wiki_Resource')->getList(array('filter'=>$sql->gen()));
if ($resources->count() != 1) {
return new Pluf_HTTP_Response_NotFound($request);
}
$resource = $resources[0];
$revision = $resource->get_current_revision();
// grab the old revision if requested.
if (isset($request->GET['rev']) and preg_match('/^[0-9]+$/', $request->GET['rev'])) {
$revision = Pluf_Shortcuts_GetObjectOr404('IDF_Wiki_ResourceRevision',
$request->GET['rev']);
if ($revision->wikiresource != $resource->id) {
return new Pluf_HTTP_Response_NotFound($request);
}
}
$pagerevs = $revision->getPageRevisions();
$title = $resource->title;
$false = Pluf_DB_BooleanToDb(false, $resource->getDbConnection());
$revs = $resource->get_revisions_list(array('order' => 'creation_dtime DESC',
'filter' => 'is_head='.$false));
return Pluf_Shortcuts_RenderToResponse('idf/wiki/viewResource.html',
array(
'page_title' => $title,
'resource' => $resource,
'rev' => $revision,
'revs' => $revs,
'pagerevs' => $pagerevs,
),
$request);
}
/**
* Returns a bytestream to the given raw resource revision
*/
public $rawResource_precond = array('IDF_Precondition::accessWiki');
public function rawResource($request, $match)
{
$prj = $request->project;
$rev = Pluf_Shortcuts_GetObjectOr404('IDF_Wiki_ResourceRevision',
$match[2]);
$res = $rev->get_wikiresource();
if ($res->get_project()->id != $prj->id) {
return new Pluf_HTTP_Response_NotFound($request);
}
$response = new Pluf_HTTP_Response_File($rev->getFilePath(), $res->mime_type);
if (isset($request->GET['attachment']) && $request->GET['attachment']) {
$response->headers['Content-Disposition'] =
'attachment; filename="'.$res->title.'.'.$rev->fileext.'"';
}
return $response;
}
/**
* Remove a revision of a page.
*/
public $deletePageRev_precond = array('IDF_Precondition::accessWiki',
'IDF_Precondition::projectMemberOrOwner');
public function deletePageRev($request, $match)
{
$prj = $request->project;
$oldrev = Pluf_Shortcuts_GetObjectOr404('IDF_Wiki_PageRevision', $match[2]);
$page = $oldrev->get_wikipage(); $page = $oldrev->get_wikipage();
$prj->inOr404($page); $prj->inOr404($page);
if ($oldrev->is_head == true) { if ($oldrev->is_head == true) {
@ -269,7 +407,7 @@ class IDF_Views_Wiki
if ($request->method == 'POST') { if ($request->method == 'POST') {
$oldrev->delete(); $oldrev->delete();
$request->user->setMessage(__('The old revision has been deleted.')); $request->user->setMessage(__('The old revision has been deleted.'));
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::view', $url = Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::viewPage',
array($prj->shortname, $page->title)); array($prj->shortname, $page->title));
return new Pluf_HTTP_Response_Redirect($url); return new Pluf_HTTP_Response_Redirect($url);
} }
@ -279,7 +417,7 @@ class IDF_Views_Wiki
$false = Pluf_DB_BooleanToDb(false, $page->getDbConnection()); $false = Pluf_DB_BooleanToDb(false, $page->getDbConnection());
$revs = $page->get_revisions_list(array('order' => 'creation_dtime DESC', $revs = $page->get_revisions_list(array('order' => 'creation_dtime DESC',
'filter' => 'is_head='.$false)); 'filter' => 'is_head='.$false));
return Pluf_Shortcuts_RenderToResponse('idf/wiki/delete.html', return Pluf_Shortcuts_RenderToResponse('idf/wiki/deletePageRev.html',
array( array(
'page_title' => $title, 'page_title' => $title,
'page' => $page, 'page' => $page,
@ -292,17 +430,53 @@ class IDF_Views_Wiki
} }
/** /**
* View a documentation page. * Remove a revision of a resource.
*/ */
public $update_precond = array('IDF_Precondition::accessWiki', public $deleteResourceRev_precond = array('IDF_Precondition::accessWiki',
'Pluf_Precondition::loginRequired'); 'IDF_Precondition::projectMemberOrOwner');
public function update($request, $match) public function deleteResourceRev($request, $match)
{
$prj = $request->project;
$oldrev = Pluf_Shortcuts_GetObjectOr404('IDF_Wiki_ResourceRevision', $match[2]);
$resource = $oldrev->get_wikiresource();
$prj->inOr404($resource);
if ($oldrev->is_head == true) {
return new Pluf_HTTP_Response_NotFound($request);
}
if ($request->method == 'POST') {
$oldrev->delete();
$request->user->setMessage(__('The old revision has been deleted.'));
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::viewResource',
array($prj->shortname, $resource->title));
return new Pluf_HTTP_Response_Redirect($url);
}
$title = sprintf(__('Delete Old Revision of %s'), $resource->title);
$false = Pluf_DB_BooleanToDb(false, $resource->getDbConnection());
$revs = $resource->get_revisions_list(array('order' => 'creation_dtime DESC',
'filter' => 'is_head='.$false));
return Pluf_Shortcuts_RenderToResponse('idf/wiki/deleteResourceRev.html',
array(
'page_title' => $title,
'resource' => $resource,
'oldrev' => $oldrev,
'revs' => $revs,
),
$request);
}
/**
* Update a documentation page.
*/
public $updatePage_precond = array('IDF_Precondition::accessWiki',
'Pluf_Precondition::loginRequired');
public function updatePage($request, $match)
{ {
$prj = $request->project; $prj = $request->project;
// Find the page // Find the page
$sql = new Pluf_SQL('project=%s AND title=%s', $sql = new Pluf_SQL('project=%s AND title=%s',
array($prj->id, $match[2])); array($prj->id, $match[2]));
$pages = Pluf::factory('IDF_WikiPage')->getList(array('filter'=>$sql->gen())); $pages = Pluf::factory('IDF_Wiki_Page')->getList(array('filter'=>$sql->gen()));
if ($pages->count() != 1) { if ($pages->count() != 1) {
return new Pluf_HTTP_Response_NotFound($request); return new Pluf_HTTP_Response_NotFound($request);
} }
@ -314,23 +488,24 @@ class IDF_Views_Wiki
'user' => $request->user, 'user' => $request->user,
'page' => $page); 'page' => $page);
if ($request->method == 'POST') { if ($request->method == 'POST') {
$form = new IDF_Form_WikiUpdate($request->POST, $params); $form = new IDF_Form_WikiPageUpdate($request->POST, $params);
if ($form->isValid() and !isset($request->POST['preview'])) { if ($form->isValid() and !isset($request->POST['preview'])) {
$page = $form->save(); $page = $form->save();
$urlpage = Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::view', $urlpage = Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::viewPage',
array($prj->shortname, $page->title)); array($prj->shortname, $page->title));
$request->user->setMessage(sprintf(__('The page <a href="%s">%s</a> has been updated.'), $urlpage, Pluf_esc($page->title))); $request->user->setMessage(sprintf(__('The page <a href="%1$s">%2$s</a> has been updated.'),
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::index', $urlpage, Pluf_esc($page->title)));
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::listPages',
array($prj->shortname)); array($prj->shortname));
return new Pluf_HTTP_Response_Redirect($url); return new Pluf_HTTP_Response_Redirect($url);
} elseif (isset($request->POST['preview'])) { } elseif (isset($request->POST['preview'])) {
$preview = $request->POST['content']; $preview = $request->POST['content'];
} }
} else { } else {
$form = new IDF_Form_WikiUpdate(null, $params); $form = new IDF_Form_WikiPageUpdate(null, $params);
} }
return Pluf_Shortcuts_RenderToResponse('idf/wiki/update.html', return Pluf_Shortcuts_RenderToResponse('idf/wiki/updatePage.html',
array( array(
'auto_labels' => self::autoCompleteArrays($prj), 'auto_labels' => self::autoCompleteArrays($prj),
'page_title' => $title, 'page_title' => $title,
@ -343,34 +518,82 @@ class IDF_Views_Wiki
} }
/** /**
* Delete a Wiki page. * Update a documentation resource.
*/ */
public $delete_precond = array('IDF_Precondition::accessWiki', public $updateResource_precond = array('IDF_Precondition::accessWiki',
'IDF_Precondition::projectMemberOrOwner'); 'Pluf_Precondition::loginRequired');
public function delete($request, $match) public function updateResource($request, $match)
{ {
$prj = $request->project; $prj = $request->project;
$page = Pluf_Shortcuts_GetObjectOr404('IDF_WikiPage', $match[2]); // Find the page
$prj->inOr404($page); $sql = new Pluf_SQL('project=%s AND title=%s',
$params = array('page' => $page); array($prj->id, $match[2]));
$resources = Pluf::factory('IDF_Wiki_Resource')->getList(array('filter'=>$sql->gen()));
if ($resources->count() != 1) {
return new Pluf_HTTP_Response_NotFound($request);
}
$resource = $resources[0];
$title = sprintf(__('Update %s'), $resource->title);
$revision = $resource->get_current_revision();
$params = array('project' => $prj,
'user' => $request->user,
'resource' => $resource);
if ($request->method == 'POST') { if ($request->method == 'POST') {
$form = new IDF_Form_WikiDelete($request->POST, $params); $form = new IDF_Form_WikiResourceUpdate(array_merge($request->POST, $request->FILES),
$params);
if ($form->isValid()) { if ($form->isValid()) {
$form->save(); $page = $form->save();
$request->user->setMessage(__('The documentation page has been deleted.')); $url = Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::viewResource',
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::index', array($prj->shortname, $resource->title));
$request->user->setMessage(sprintf(__('The resource <a href="%1$s">%2$s</a> has been updated.'),
$url, Pluf_esc($resource->title)));
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::listResources',
array($prj->shortname)); array($prj->shortname));
return new Pluf_HTTP_Response_Redirect($url); return new Pluf_HTTP_Response_Redirect($url);
} }
} else { } else {
$form = new IDF_Form_WikiDelete(null, $params); $form = new IDF_Form_WikiResourceUpdate(null, $params);
}
return Pluf_Shortcuts_RenderToResponse('idf/wiki/updateResource.html',
array(
'page_title' => $title,
'resource' => $resource,
'rev' => $revision,
'form' => $form,
),
$request);
}
/**
* Delete a Wiki page.
*/
public $deletePage_precond = array('IDF_Precondition::accessWiki',
'IDF_Precondition::projectMemberOrOwner');
public function deletePage($request, $match)
{
$prj = $request->project;
$page = Pluf_Shortcuts_GetObjectOr404('IDF_Wiki_Page', $match[2]);
$prj->inOr404($page);
$params = array('page' => $page);
if ($request->method == 'POST') {
$form = new IDF_Form_WikiPageDelete($request->POST, $params);
if ($form->isValid()) {
$form->save();
$request->user->setMessage(__('The documentation page has been deleted.'));
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::listPages',
array($prj->shortname));
return new Pluf_HTTP_Response_Redirect($url);
}
} else {
$form = new IDF_Form_WikiPageDelete(null, $params);
} }
$title = sprintf(__('Delete Page %s'), $page->title); $title = sprintf(__('Delete Page %s'), $page->title);
$revision = $page->get_current_revision(); $revision = $page->get_current_revision();
$false = Pluf_DB_BooleanToDb(false, $page->getDbConnection()); $false = Pluf_DB_BooleanToDb(false, $page->getDbConnection());
$revs = $page->get_revisions_list(array('order' => 'creation_dtime DESC', $revs = $page->get_revisions_list(array('order' => 'creation_dtime DESC',
'filter' => 'is_head='.$false)); 'filter' => 'is_head='.$false));
return Pluf_Shortcuts_RenderToResponse('idf/wiki/deletepage.html', return Pluf_Shortcuts_RenderToResponse('idf/wiki/deletePage.html',
array( array(
'page_title' => $title, 'page_title' => $title,
'page' => $page, 'page' => $page,
@ -382,6 +605,45 @@ class IDF_Views_Wiki
$request); $request);
} }
/**
* Delete a Wiki resource.
*/
public $deleteResource_precond = array('IDF_Precondition::accessWiki',
'IDF_Precondition::projectMemberOrOwner');
public function deleteResource($request, $match)
{
$prj = $request->project;
$resource = Pluf_Shortcuts_GetObjectOr404('IDF_Wiki_Resource', $match[2]);
$prj->inOr404($resource);
$params = array('resource' => $resource);
if ($request->method == 'POST') {
$form = new IDF_Form_WikiResourceDelete($request->POST, $params);
if ($form->isValid()) {
$form->save();
$request->user->setMessage(__('The documentation resource has been deleted.'));
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::listResources',
array($prj->shortname));
return new Pluf_HTTP_Response_Redirect($url);
}
} else {
$form = new IDF_Form_WikiResourceDelete(null, $params);
}
$title = sprintf(__('Delete Resource %s'), $resource->title);
$revision = $resource->get_current_revision();
$false = Pluf_DB_BooleanToDb(false, $resource->getDbConnection());
$revs = $resource->get_revisions_list(array('order' => 'creation_dtime DESC',
'filter' => 'is_head='.$false));
return Pluf_Shortcuts_RenderToResponse('idf/wiki/deleteResource.html',
array(
'page_title' => $title,
'resource' => $resource,
'form' => $form,
'rev' => $revision,
'revs' => $revs,
),
$request);
}
/** /**
* Get the wiki tags. * Get the wiki tags.
* *
@ -411,7 +673,7 @@ class IDF_Views_Wiki
$sql = new Pluf_SQL('project=%s AND idf_tag_id=%s', array($project->id, $sql = new Pluf_SQL('project=%s AND idf_tag_id=%s', array($project->id,
$dtag->id)); $dtag->id));
$ids = array(); $ids = array();
foreach (Pluf::factory('IDF_WikiPage')->getList(array('filter' => $sql->gen(), 'view' => 'join_tags')) foreach (Pluf::factory('IDF_Wiki_Page')->getList(array('filter' => $sql->gen(), 'view' => 'join_tags'))
as $file) { as $file) {
$ids[] = (int) $file->id; $ids[] = (int) $file->id;
} }
@ -425,7 +687,7 @@ class IDF_Views_Wiki
{ {
$conf = new IDF_Conf(); $conf = new IDF_Conf();
$conf->setProject($project); $conf->setProject($project);
$st = preg_split("/\015\012|\015|\012/", $st = preg_split("/\015\012|\015|\012/",
$conf->getVal('labels_wiki_predefined', IDF_Form_WikiConf::init_predefined), -1, PREG_SPLIT_NO_EMPTY); $conf->getVal('labels_wiki_predefined', IDF_Form_WikiConf::init_predefined), -1, PREG_SPLIT_NO_EMPTY);
$auto = ''; $auto = '';
foreach ($st as $s) { foreach ($st as $s) {

View File

@ -31,22 +31,28 @@
class IDF_Webhook class IDF_Webhook
{ {
/** /**
* Perform the POST request given the webhook payload. * Perform the request given the webhook payload.
* *
* @param array Payload * @param array Payload
* @return bool Success or error * @return bool Success or error
*/ */
public static function postNotification($payload) public static function processNotification($payload)
{ {
$data = json_encode($payload['to_send']); $data = json_encode($payload['to_send']);
$sign_header = 'Web-Hook-Hmac';
// use the old signature header if we're asked for
if (Pluf::f('webhook_processing', '') === 'compat') {
$sign_header = 'Post-Commit-Hook-Hmac';
}
$sign = hash_hmac('md5', $data, $payload['authkey']); $sign = hash_hmac('md5', $data, $payload['authkey']);
$params = array('http' => array( $params = array('http' => array(
'method' => 'POST', // fall-back to POST for old queue items
'method' => empty($payload['method']) ? 'POST' : $payload['method'],
'content' => $data, 'content' => $data,
'user_agent' => 'Indefero Hook Sender (http://www.indefero.net)', 'user_agent' => 'Indefero Hook Sender (http://www.indefero.net)',
'max_redirects' => 0, 'max_redirects' => 0,
'timeout' => 15, 'timeout' => 15,
'header'=> 'Post-Commit-Hook-Hmac: '.$sign."\r\n" 'header'=> $sign_header.': '.$sign."\r\n"
.'Content-Type: application/json'."\r\n", .'Content-Type: application/json'."\r\n",
) )
); );
@ -61,7 +67,7 @@ class IDF_Webhook
if (!isset($meta['wrapper_data'][0]) or $meta['timed_out']) { if (!isset($meta['wrapper_data'][0]) or $meta['timed_out']) {
return false; return false;
} }
if (0 === strpos($meta['wrapper_data'][0], 'HTTP/1.1 2') or if (0 === strpos($meta['wrapper_data'][0], 'HTTP/1.1 2') or
0 === strpos($meta['wrapper_data'][0], 'HTTP/1.1 3')) { 0 === strpos($meta['wrapper_data'][0], 'HTTP/1.1 3')) {
return true; return true;
} }
@ -76,11 +82,11 @@ class IDF_Webhook
public static function process($sender, &$params) public static function process($sender, &$params)
{ {
$item = $params['item']; $item = $params['item'];
if ($item->type != 'new_commit') { if (!in_array($item->type, array('new_commit', 'upload'))) {
// We do nothing. // We do nothing.
return; return;
} }
if (isset($params['res']['IDF_Webhook::process']) and if (isset($params['res']['IDF_Webhook::process']) and
$params['res']['IDF_Webhook::process'] == true) { $params['res']['IDF_Webhook::process'] == true) {
// Already processed. // Already processed.
return; return;
@ -90,7 +96,7 @@ class IDF_Webhook
return; return;
} }
// We have either to retry or to push for the first time. // We have either to retry or to push for the first time.
$res = self::postNotification($item->payload); $res = self::processNotification($item->payload);
if ($res) { if ($res) {
$params['res']['IDF_Webhook::process'] = true; $params['res']['IDF_Webhook::process'] = true;
} elseif ($item->trials >= 9) { } elseif ($item->trials >= 9) {

View File

@ -28,10 +28,10 @@ Pluf::loadFunction('Pluf_Template_dateAgo');
* Base definition of a wiki page. * Base definition of a wiki page.
* *
* A wiki page can have tags and be starred by the users. The real * A wiki page can have tags and be starred by the users. The real
* content of the page is stored in the IDF_WikiRevision * content of the page is stored in the IDF_Wiki_PageRevision
* object. Several revisions are associated to a given page. * object. Several revisions are associated to a given page.
*/ */
class IDF_WikiPage extends Pluf_Model class IDF_Wiki_Page extends Pluf_Model
{ {
public $_model = __CLASS__; public $_model = __CLASS__;
@ -44,9 +44,9 @@ class IDF_WikiPage extends Pluf_Model
'id' => 'id' =>
array( array(
'type' => 'Pluf_DB_Field_Sequence', 'type' => 'Pluf_DB_Field_Sequence',
'blank' => true, 'blank' => true,
), ),
'project' => 'project' =>
array( array(
'type' => 'Pluf_DB_Field_Foreignkey', 'type' => 'Pluf_DB_Field_Foreignkey',
'model' => 'IDF_Project', 'model' => 'IDF_Project',
@ -70,7 +70,7 @@ class IDF_WikiPage extends Pluf_Model
'verbose' => __('summary'), 'verbose' => __('summary'),
'help_text' => __('A one line description of the page content.'), 'help_text' => __('A one line description of the page content.'),
), ),
'submitter' => 'submitter' =>
array( array(
'type' => 'Pluf_DB_Field_Foreignkey', 'type' => 'Pluf_DB_Field_Foreignkey',
'model' => 'Pluf_User', 'model' => 'Pluf_User',
@ -78,7 +78,7 @@ class IDF_WikiPage extends Pluf_Model
'verbose' => __('submitter'), 'verbose' => __('submitter'),
'relate_name' => 'submitted_wikipages', 'relate_name' => 'submitted_wikipages',
), ),
'interested' => 'interested' =>
array( array(
'type' => 'Pluf_DB_Field_Manytomany', 'type' => 'Pluf_DB_Field_Manytomany',
'model' => 'Pluf_User', 'model' => 'Pluf_User',
@ -88,7 +88,7 @@ class IDF_WikiPage extends Pluf_Model
), ),
'tags' => 'tags' =>
array( array(
'type' => 'Pluf_DB_Field_Manytomany', 'type' => 'Pluf_DB_Field_Manytomany',
'blank' => true, 'blank' => true,
'model' => 'IDF_Tag', 'model' => 'IDF_Tag',
'verbose' => __('labels'), 'verbose' => __('labels'),
@ -106,19 +106,19 @@ class IDF_WikiPage extends Pluf_Model
'verbose' => __('modification date'), 'verbose' => __('modification date'),
), ),
); );
$this->_a['idx'] = array( $this->_a['idx'] = array(
'modif_dtime_idx' => 'modif_dtime_idx' =>
array( array(
'col' => 'modif_dtime', 'col' => 'modif_dtime',
'type' => 'normal', 'type' => 'normal',
), ),
); );
$table = $this->_con->pfx.'idf_tag_idf_wikipage_assoc'; $table = $this->_con->pfx.'idf_tag_idf_wiki_page_assoc';
$this->_a['views'] = array( $this->_a['views'] = array(
'join_tags' => 'join_tags' =>
array( array(
'join' => 'LEFT JOIN '.$table 'join' => 'LEFT JOIN '.$table
.' ON idf_wikipage_id=id', .' ON idf_wiki_page_id=id',
), ),
); );
} }
@ -144,7 +144,7 @@ class IDF_WikiPage extends Pluf_Model
IDF_Search::remove($this); IDF_Search::remove($this);
} }
function get_current_revision() function get_current_revision()
{ {
$true = Pluf_DB_BooleanToDb(true, $this->getDbConnection()); $true = Pluf_DB_BooleanToDb(true, $this->getDbConnection());
$rev = $this->get_revisions_list(array('filter' => 'is_head='.$true, $rev = $this->get_revisions_list(array('filter' => 'is_head='.$true,
@ -167,7 +167,7 @@ class IDF_WikiPage extends Pluf_Model
// that the page as a given revision in the database when // that the page as a given revision in the database when
// doing the indexing. // doing the indexing.
if ($create) { if ($create) {
IDF_Timeline::insert($this, $this->get_project(), IDF_Timeline::insert($this, $this->get_project(),
$this->get_submitter()); $this->get_submitter());
} }
} }
@ -180,12 +180,12 @@ class IDF_WikiPage extends Pluf_Model
* as such create links to other items etc. You can consider that * as such create links to other items etc. You can consider that
* if displayed, you can create a link to it. * if displayed, you can create a link to it.
* *
* @param Pluf_HTTP_Request * @param Pluf_HTTP_Request
* @return Pluf_Template_SafeString * @return Pluf_Template_SafeString
*/ */
public function timelineFragment($request) public function timelineFragment($request)
{ {
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::view', $url = Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::viewPage',
array($request->project->shortname, array($request->project->shortname,
$this->title)); $this->title));
$out = '<tr class="log"><td><a href="'.$url.'">'. $out = '<tr class="log"><td><a href="'.$url.'">'.
@ -195,17 +195,17 @@ class IDF_WikiPage extends Pluf_Model
$user = $stag->start($this->get_submitter(), $request, '', false); $user = $stag->start($this->get_submitter(), $request, '', false);
$out .= sprintf(__('<a href="%1$s" title="View page">%2$s</a>, %3$s'), $url, Pluf_esc($this->title), Pluf_esc($this->summary)).'</td>'; $out .= sprintf(__('<a href="%1$s" title="View page">%2$s</a>, %3$s'), $url, Pluf_esc($this->title), Pluf_esc($this->summary)).'</td>';
$out .= "\n".'<tr class="extra"><td colspan="2"> $out .= "\n".'<tr class="extra"><td colspan="2">
<div class="helptext right">'.sprintf(__('Creation of <a href="%s">page&nbsp;%s</a>, by %s'), $url, Pluf_esc($this->title), $user).'</div></td></tr>'; <div class="helptext right">'.sprintf(__('Creation of <a href="%1$s">page %2$s</a>, by %3$s'), $url, Pluf_esc($this->title), $user).'</div></td></tr>';
return Pluf_Template::markSafe($out); return Pluf_Template::markSafe($out);
} }
public function feedFragment($request) public function feedFragment($request)
{ {
$url = Pluf::f('url_base') $url = Pluf::f('url_base')
.Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::view', .Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::viewPage',
array($request->project->shortname, array($request->project->shortname,
$this->title)); $this->title));
$title = sprintf(__('%s: Documentation page %s added - %s'), $title = sprintf(__('%1$s: Documentation page %2$s added - %3$s'),
$request->project->name, $request->project->name,
$this->title, $this->summary); $this->title, $this->summary);
$date = Pluf_Date::gmDateToGmString($this->creation_dtime); $date = Pluf_Date::gmDateToGmString($this->creation_dtime);
@ -218,7 +218,7 @@ class IDF_WikiPage extends Pluf_Model
'create' => true, 'create' => true,
'date' => $date) 'date' => $date)
); );
$tmpl = new Pluf_Template('idf/wiki/feedfragment.xml'); $tmpl = new Pluf_Template('idf/wiki/feedfragment-page.xml');
return $tmpl->render($context); return $tmpl->render($context);
} }
} }

View File

@ -25,13 +25,13 @@
* A revision of a wiki page. * A revision of a wiki page.
* *
*/ */
class IDF_WikiRevision extends Pluf_Model class IDF_Wiki_PageRevision extends Pluf_Model
{ {
public $_model = __CLASS__; public $_model = __CLASS__;
function init() function init()
{ {
$this->_a['table'] = 'idf_wikirevisions'; $this->_a['table'] = 'idf_wikipagerevs';
$this->_a['model'] = __CLASS__; $this->_a['model'] = __CLASS__;
$this->_a['cols'] = array( $this->_a['cols'] = array(
// It is mandatory to have an "id" column. // It is mandatory to have an "id" column.
@ -43,7 +43,7 @@ class IDF_WikiRevision extends Pluf_Model
'wikipage' => 'wikipage' =>
array( array(
'type' => 'Pluf_DB_Field_Foreignkey', 'type' => 'Pluf_DB_Field_Foreignkey',
'model' => 'IDF_WikiPage', 'model' => 'IDF_Wiki_Page',
'blank' => false, 'blank' => false,
'verbose' => __('page'), 'verbose' => __('page'),
'relate_name' => 'revisions', 'relate_name' => 'revisions',
@ -83,7 +83,7 @@ class IDF_WikiRevision extends Pluf_Model
'type' => 'Pluf_DB_Field_Serialized', 'type' => 'Pluf_DB_Field_Serialized',
'blank' => true, 'blank' => true,
'verbose' => __('changes'), 'verbose' => __('changes'),
'help_text' => 'Serialized array of the changes in the issue.', 'help_text' => 'Serialized array of the changes in the page.',
), ),
'creation_dtime' => 'creation_dtime' =>
array( array(
@ -99,6 +99,14 @@ class IDF_WikiRevision extends Pluf_Model
'type' => 'normal', 'type' => 'normal',
), ),
); );
$table = $this->_con->pfx.'idf_wiki_pagerevision_idf_wiki_resourcerevision_assoc';
$this->_a['views'] = array(
'join_pagerevision' =>
array(
'join' => 'LEFT JOIN '.$table
.' ON idf_wiki_pagerevision_id=id',
),
);
} }
function changedRevision() function changedRevision()
@ -129,6 +137,8 @@ class IDF_WikiRevision extends Pluf_Model
function postSave($create=false) function postSave($create=false)
{ {
$page = $this->get_wikipage();
if ($create) { if ($create) {
// Check if more than one revision for this page. We do // Check if more than one revision for this page. We do
// not want to insert the first revision in the timeline // not want to insert the first revision in the timeline
@ -136,10 +146,9 @@ class IDF_WikiRevision extends Pluf_Model
// update as update is performed to change the is_head // update as update is performed to change the is_head
// flag. // flag.
$sql = new Pluf_SQL('wikipage=%s', array($this->wikipage)); $sql = new Pluf_SQL('wikipage=%s', array($this->wikipage));
$rev = Pluf::factory('IDF_WikiRevision')->getList(array('filter'=>$sql->gen())); $rev = Pluf::factory('IDF_Wiki_PageRevision')->getList(array('filter'=>$sql->gen()));
if ($rev->count() > 1) { if ($rev->count() > 1) {
IDF_Timeline::insert($this, $this->get_wikipage()->get_project(), IDF_Timeline::insert($this, $page->get_project(), $this->get_submitter());
$this->get_submitter());
foreach ($rev as $r) { foreach ($rev as $r) {
if ($r->id != $this->id and $r->is_head) { if ($r->id != $this->id and $r->is_head) {
$r->is_head = false; $r->is_head = false;
@ -147,18 +156,38 @@ class IDF_WikiRevision extends Pluf_Model
} }
} }
} }
$page = $this->get_wikipage(); }
$page->update(); // Will update the modification timestamp.
IDF_Search::index($page); IDF_Search::index($page);
$page->update(); // Will update the modification timestamp.
// remember the resource revisions used in this page revision
if ($this->is_head) {
preg_match_all('#\[\[!([A-Za-z0-9\-]+)[^\]]*\]\]#im', $this->content, $matches, PREG_PATTERN_ORDER);
if (count($matches) > 1 && count($matches[1]) > 0) {
foreach ($matches[1] as $resourceName) {
$sql = new Pluf_SQL('project=%s AND title=%s',
array($prj->id, $resourceName));
$resources = Pluf::factory('IDF_Wiki_Resource')->getList(array('filter'=>$sql->gen()));
if ($resources->count() == 0)
continue;
$current_revision = $resources[0]->get_current_revision();
$current_revision->setAssoc($this);
$this->setAssoc($current_revision);
}
}
} }
} }
public function timelineFragment($request) public function timelineFragment($request)
{ {
$page = $this->get_wikipage(); $page = $this->get_wikipage();
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::view', $url = Pluf::f('url_base')
array($request->project->shortname, .Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::viewPage',
$page->title)); array($request->project->shortname,
$page->title),
array('rev' => $this->id));
$out = "\n".'<tr class="log"><td><a href="'.$url.'">'. $out = "\n".'<tr class="log"><td><a href="'.$url.'">'.
Pluf_esc(Pluf_Template_dateAgo($this->creation_dtime, 'without')). Pluf_esc(Pluf_Template_dateAgo($this->creation_dtime, 'without')).
'</a></td><td>'; '</a></td><td>';
@ -186,26 +215,20 @@ class IDF_WikiRevision extends Pluf_Model
} }
$out .= '</td></tr>'; $out .= '</td></tr>';
$out .= "\n".'<tr class="extra"><td colspan="2"> $out .= "\n".'<tr class="extra"><td colspan="2">
<div class="helptext right">'.sprintf(__('Change of <a href="%s">%s</a>, by %s'), $url, Pluf_esc($page->title), $user).'</div></td></tr>'; <div class="helptext right">'.sprintf(__('Change of <a href="%1$s">%2$s</a>, by %3$s'), $url, Pluf_esc($page->title), $user).'</div></td></tr>';
return Pluf_Template::markSafe($out); return Pluf_Template::markSafe($out);
} }
public function feedFragment($request) public function feedFragment($request)
{ {
$page = $this->get_wikipage(); $page = $this->get_wikipage();
if (!$this->is_head) { $url = Pluf::f('url_base')
$url = Pluf::f('url_base') .Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::viewPage',
.Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::view', array($request->project->shortname,
array($request->project->shortname, $page->title),
$page->title), array('rev' => $this->id));
array('rev' => $this->id));
} else { $title = sprintf(__('%1$s: Documentation page %2$s updated - %3$s'),
$url = Pluf::f('url_base')
.Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::view',
array($request->project->shortname,
$page->title));
}
$title = sprintf(__('%s: Documentation page %s updated - %s'),
$request->project->name, $request->project->name,
$page->title, $page->summary); $page->title, $page->summary);
$date = Pluf_Date::gmDateToGmString($this->creation_dtime); $date = Pluf_Date::gmDateToGmString($this->creation_dtime);
@ -218,14 +241,14 @@ class IDF_WikiRevision extends Pluf_Model
'create' => false, 'create' => false,
'date' => $date) 'date' => $date)
); );
$tmpl = new Pluf_Template('idf/wiki/feedfragment.xml'); $tmpl = new Pluf_Template('idf/wiki/feedfragment-page.xml');
return $tmpl->render($context); return $tmpl->render($context);
} }
/** /**
* Notification of change of a WikiPage. * Notification of change of a Wiki Page.
* *
* The content of a WikiPage is in the IDF_WikiRevision object, * The content of a WikiPage is in the IDF_WikiRevision object,
* this is why we send the notificatin from there. This means that * this is why we send the notificatin from there. This means that
@ -243,42 +266,49 @@ class IDF_WikiRevision extends Pluf_Model
*/ */
public function notify($conf, $create=true) public function notify($conf, $create=true)
{ {
if ('' == $conf->getVal('wiki_notification_email', '')) { $wikipage = $this->get_wikipage();
return; $project = $wikipage->get_project();
}
$current_locale = Pluf_Translation::getLocale(); $current_locale = Pluf_Translation::getLocale();
$langs = Pluf::f('languages', array('en'));
Pluf_Translation::loadSetLocale($langs[0]);
$context = new Pluf_Template_Context(
array(
'page' => $this->get_wikipage(),
'rev' => $this,
'project' => $this->get_wikipage()->get_project(),
'url_base' => Pluf::f('url_base'),
)
);
if ($create) {
$template = 'idf/wiki/wiki-created-email.txt';
$title = sprintf(__('New Documentation Page %s - %s (%s)'),
$this->get_wikipage()->title,
$this->get_wikipage()->summary,
$this->get_wikipage()->get_project()->shortname);
} else {
$template = 'idf/wiki/wiki-updated-email.txt';
$title = sprintf(__('Documentation Page Changed %s - %s (%s)'),
$this->get_wikipage()->title,
$this->get_wikipage()->summary,
$this->get_wikipage()->get_project()->shortname);
}
$tmpl = new Pluf_Template($template);
$text_email = $tmpl->render($context);
$addresses = explode(',', $conf->getVal('wiki_notification_email')); $from_email = Pluf::f('from_email');
foreach ($addresses as $address) { $messageId = '<'.md5('wiki'.$wikipage->id.md5(Pluf::f('secret_key'))).'@'.Pluf::f('mail_host', 'localhost').'>';
$email = new Pluf_Mail(Pluf::f('from_email'), $recipients = $project->getNotificationRecipientsForTab('wiki');
foreach ($recipients as $address => $language) {
if ($this->get_submitter()->email === $address) {
continue;
}
Pluf_Translation::loadSetLocale($language);
$context = new Pluf_Template_Context(array(
'page' => $wikipage,
'rev' => $this,
'project' => $project,
'url_base' => Pluf::f('url_base'),
));
$tplfile = 'idf/wiki/wiki-created-email.txt';
$subject = __('New Documentation Page %1$s - %2$s (%3$s)');
$headers = array('Message-ID' => $messageId);
if (!$create) {
$tplfile = 'idf/wiki/wiki-updated-email.txt';
$subject = __('Documentation Page Changed %1$s - %2$s (%3$s)');
$headers = array('References' => $messageId);
}
$tmpl = new Pluf_Template($tplfile);
$text_email = $tmpl->render($context);
$email = new Pluf_Mail($from_email,
$address, $address,
$title); sprintf($subject,
$wikipage->title,
$wikipage->summary,
$project->shortname));
$email->addTextMessage($text_email); $email->addTextMessage($text_email);
$email->addHeaders($headers);
$email->sendMail(); $email->sendMail();
} }

204
src/IDF/Wiki/Resource.php Normal file
View File

@ -0,0 +1,204 @@
<?php
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
# ***** BEGIN LICENSE BLOCK *****
# This file is part of InDefero, an open source project management application.
# Copyright (C) 2008-2011 Céondo Ltd and contributors.
#
# InDefero is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# InDefero is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
# ***** END LICENSE BLOCK ***** */
Pluf::loadFunction('Pluf_HTTP_URL_urlForView');
Pluf::loadFunction('Pluf_Template_dateAgo');
/**
* Base definition of a wiki resource.
*
* The resource groups several logical versions of a file into one and gives
* it a unique title across a project's wiki. The current file version is
* saved in a IDF_Wiki_ResourceRevision, which is also the entity that is
* linked with a specific IDF_Wiki_PageRevision on which the resource is
* displayed.
*/
class IDF_Wiki_Resource extends Pluf_Model
{
public $_model = __CLASS__;
function init()
{
$this->_a['table'] = 'idf_wikiresources';
$this->_a['model'] = __CLASS__;
$this->_a['cols'] = array(
// It is mandatory to have an "id" column.
'id' =>
array(
'type' => 'Pluf_DB_Field_Sequence',
'blank' => true,
),
'project' =>
array(
'type' => 'Pluf_DB_Field_Foreignkey',
'model' => 'IDF_Project',
'blank' => false,
'verbose' => __('project'),
'relate_name' => 'wikipages',
),
'title' =>
array(
'type' => 'Pluf_DB_Field_Varchar',
'blank' => false,
'size' => 250,
'verbose' => __('title'),
'help_text' => __('The title of the resource must only contain letters, digits, dots or the dash character. For example: my-resource.png.'),
),
'mime_type' =>
array(
'type' => 'Pluf_DB_Field_Varchar',
'blank' => false,
'size' => 100,
'verbose' => __('MIME media type'),
'help_text' => __('The MIME media type of the resource.'),
),
'summary' =>
array(
'type' => 'Pluf_DB_Field_Varchar',
'blank' => false,
'size' => 250,
'verbose' => __('summary'),
'help_text' => __('A one line description of the resource.'),
),
'submitter' =>
array(
'type' => 'Pluf_DB_Field_Foreignkey',
'model' => 'Pluf_User',
'blank' => false,
'verbose' => __('submitter'),
'relate_name' => 'submitted_wikipages',
),
'creation_dtime' =>
array(
'type' => 'Pluf_DB_Field_Datetime',
'blank' => true,
'verbose' => __('creation date'),
),
'modif_dtime' =>
array(
'type' => 'Pluf_DB_Field_Datetime',
'blank' => true,
'verbose' => __('modification date'),
),
);
$this->_a['idx'] = array(
'modif_dtime_idx' =>
array(
'col' => 'modif_dtime',
'type' => 'normal',
),
);
}
function __toString()
{
return $this->title.' - '.$this->summary;
}
/**
* We drop the information from the timeline.
*/
function preDelete()
{
IDF_Timeline::remove($this);
IDF_Search::remove($this);
}
function get_current_revision()
{
$true = Pluf_DB_BooleanToDb(true, $this->getDbConnection());
$rev = $this->get_revisions_list(array('filter' => 'is_head='.$true,
'nb' => 1));
return ($rev->count() == 1) ? $rev[0] : null;
}
function preSave($create=false)
{
if ($this->id == '') {
$this->creation_dtime = gmdate('Y-m-d H:i:s');
}
$this->modif_dtime = gmdate('Y-m-d H:i:s');
}
function postSave($create=false)
{
// Note: No indexing is performed here. The indexing is
// triggered in the postSave step of the revision to ensure
// that the page as a given revision in the database when
// doing the indexing.
if ($create) {
IDF_Timeline::insert($this, $this->get_project(),
$this->get_submitter());
}
}
/**
* Returns an HTML fragment used to display this resource in the
* timeline.
*
* The request object is given to be able to check the rights and
* as such create links to other items etc. You can consider that
* if displayed, you can create a link to it.
*
* @param Pluf_HTTP_Request
* @return Pluf_Template_SafeString
*/
public function timelineFragment($request)
{
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::viewResource',
array($request->project->shortname,
$this->title));
$out = '<tr class="log"><td><a href="'.$url.'">'.
Pluf_esc(Pluf_Template_dateAgo($this->creation_dtime, 'without')).
'</a></td><td>';
$stag = new IDF_Template_ShowUser();
$user = $stag->start($this->get_submitter(), $request, '', false);
$out .= sprintf(__('<a href="%1$s" title="View resource">%2$s</a>, %3$s'), $url, Pluf_esc($this->title), Pluf_esc($this->summary)).'</td>';
$out .= "\n".'<tr class="extra"><td colspan="2">
<div class="helptext right">'.sprintf(__('Creation of <a href="%1$s">resource %2$s</a>, by %3$s'), $url, Pluf_esc($this->title), $user).'</div></td></tr>';
return Pluf_Template::markSafe($out);
}
public function feedFragment($request)
{
$url = Pluf::f('url_base')
.Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::viewResource',
array($request->project->shortname,
$this->title));
$title = sprintf(__('%1$s: Documentation resource %2$s added - %3$s'),
$request->project->name,
$this->title, $this->summary);
$date = Pluf_Date::gmDateToGmString($this->creation_dtime);
$context = new Pluf_Template_Context_Request(
$request,
array('url' => $url,
'title' => $title,
'resource' => $this,
'rev' => $this->get_current_revision(),
'create' => true,
'date' => $date)
);
$tmpl = new Pluf_Template('idf/wiki/feedfragment-resource.xml');
return $tmpl->render($context);
}
}

View File

@ -0,0 +1,340 @@
<?php
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
# ***** BEGIN LICENSE BLOCK *****
# This file is part of InDefero, an open source project management application.
# Copyright (C) 2008-2011 Céondo Ltd and contributors.
#
# InDefero is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# InDefero is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
# ***** END LICENSE BLOCK ***** */
/**
* A single resource revision.
*/
class IDF_Wiki_ResourceRevision extends Pluf_Model
{
public $_model = __CLASS__;
function init()
{
$this->_a['table'] = 'idf_wikiresourcerevs';
$this->_a['model'] = __CLASS__;
$this->_a['cols'] = array(
// It is mandatory to have an "id" column.
'id' =>
array(
'type' => 'Pluf_DB_Field_Sequence',
'blank' => true,
),
'wikiresource' =>
array(
'type' => 'Pluf_DB_Field_Foreignkey',
'model' => 'IDF_Wiki_Resource',
'blank' => false,
'verbose' => __('resource'),
'relate_name' => 'revisions',
),
'is_head' =>
array(
'type' => 'Pluf_DB_Field_Boolean',
'blank' => false,
'default' => false,
'help_text' => 'If this revision is the latest, we mark it as being the head revision.',
'index' => true,
),
'summary' =>
array(
'type' => 'Pluf_DB_Field_Varchar',
'blank' => false,
'size' => 250,
'verbose' => __('summary'),
'help_text' => __('A one line description of the changes.'),
),
'filesize' =>
array(
'type' => 'Pluf_DB_Field_Integer',
'blank' => false,
'default' => 0,
'verbose' => __('file size in bytes'),
),
'fileext' =>
array(
'type' => 'Pluf_DB_Field_Varchar',
'blank' => false,
'size' => 10,
'verbose' => __('File extension'),
'help_text' => __('The file extension of the uploaded resource.'),
),
'submitter' =>
array(
'type' => 'Pluf_DB_Field_Foreignkey',
'model' => 'Pluf_User',
'blank' => false,
'verbose' => __('submitter'),
'relate_name' => 'submitted_downloads',
),
'pageusage' =>
array(
'type' => 'Pluf_DB_Field_Manytomany',
'model' => 'IDF_Wiki_PageRevision',
'blank' => true,
'verbose' => __('page usage'),
'help_text' => 'Records on which pages this resource revision is used.',
),
'creation_dtime' =>
array(
'type' => 'Pluf_DB_Field_Datetime',
'blank' => true,
'verbose' => __('creation date'),
),
);
$table = $this->_con->pfx.'idf_wiki_pagerevision_idf_wiki_resourcerevision_assoc';
$this->_a['views'] = array(
'join_pagerevision' =>
array(
'join' => 'LEFT JOIN '.$table
.' ON idf_wiki_resourcerevision_id=id',
),
);
}
function __toString()
{
return sprintf(__('id %d: %s'), $this->id, $this->summary);
}
function _toIndex()
{
return '';
}
function preDelete()
{
// if we kill off a head revision, ensure that we either mark a previous revision as head
if ($this->is_head) {
$sql = new Pluf_SQL('wikiresource=%s and id!=%s', array($this->wikiresource, $this->id));
$revs = Pluf::factory('IDF_Wiki_ResourceRevision')->getList(array('filter'=>$sql->gen(), 'order'=>'id DESC'));
if ($revs->count() > 0) {
$previous = $revs[0];
$previous->is_head = true;
$previous->update();
}
}
@unlink($this->getFilePath());
IDF_Timeline::remove($this);
}
function preSave($create=false)
{
if ($this->id == '') {
$this->creation_dtime = gmdate('Y-m-d H:i:s');
$this->is_head = true;
}
}
function postSave($create=false)
{
$resource = $this->get_wikiresource();
if ($create) {
$sql = new Pluf_SQL('wikiresource=%s', array($this->wikiresource));
$rev = Pluf::factory('IDF_Wiki_ResourceRevision')->getList(array('filter'=>$sql->gen()));
if ($rev->count() > 1) {
IDF_Timeline::insert($this, $resource->get_project(), $this->get_submitter());
foreach ($rev as $r) {
if ($r->id != $this->id and $r->is_head) {
$r->is_head = false;
$r->update();
}
}
}
}
// update the modification timestamp
$resource->update();
}
function getFilePath()
{
return sprintf(Pluf::f('upload_path').'/'.$this->get_wikiresource()->get_project()->shortname.'/wiki/res/%d/%d.%s',
$this->get_wikiresource()->id, $this->id, $this->fileext);
}
function getViewURL()
{
$prj = $this->get_wikiresource()->get_project();
$resource = $this->get_wikiresource();
return Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::viewResource',
array($prj->shortname, $resource->title),
array('rev' => $this->id));
}
function getRawURL($attachment = false)
{
$query = $attachment ? array('attachment' => 1) : array();
$prj = $this->get_wikiresource()->get_project();
return Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::rawResource',
array($prj->shortname, $this->id),
$query);
}
/**
* Returns the page revisions which contain references to this resource revision
*/
function getPageRevisions()
{
$db =& Pluf::db();
$sql_results = $db->select(
'SELECT idf_wiki_pagerevision_id as id '.
'FROM '.Pluf::f('db_table_prefix', '').'idf_wiki_pagerevision_idf_wiki_resourcerevision_assoc '.
'WHERE idf_wiki_resourcerevision_id='.$this->id
);
$ids = array(0);
foreach ($sql_results as $id) {
$ids[] = $id['id'];
}
$ids = implode (',', $ids);
$sql = new Pluf_SQL('id IN ('.$ids.')');
return Pluf::factory('IDF_Wiki_PageRevision')
->getList(array('filter' => $sql->gen()));
}
/**
* Renders the resource with the given view options, including a link to the resource' detail page
*/
function render($opts = array())
{
// give some reasonable defaults
$opts = array_merge(array(
'align' => 'left',
'width' => '',
'height' => '',
'preview' => 'yes', // if possible
'title' => '',
), $opts);
$attrs = array('class="resource-container"');
$styles = array();
if (!empty($opts['align'])) {
switch ($opts['align']) {
case 'left':
$styles[] = 'float: left';
$styles[] = 'margin-right: 10px';
break;
case 'center':
$styles[] = 'margin: 0 auto 0 auto';
break;
case 'right':
$styles[] = 'float: right';
$styles[] = 'margin-left: 10px';
break;
}
}
if (!empty($opts['width'])) {
$styles[] = 'width:'.$opts['width'];
}
if (!empty($opts['height'])) {
$styles[] = 'height:'.$opts['height'];
}
$raw = $this->renderRaw();
$viewUrl = $this->getViewURL();
$download = '';
$html = '<div class="resource-container" style="'.implode(';', $styles).'">';
if ($opts['preview'] == 'yes' && !empty($raw)) {
$html .= '<div class="preview">'.$raw.'</div>'."\n";
} else {
$rawUrl = $this->getRawURL(true);
$download = '<a href="'.$rawUrl.'" class="download" title="'.sprintf(__('Download (%s)'), Pluf_Utils::prettySize($this->filesize)).'"></a>';
}
$resource = $this->get_wikiresource();
$title = $opts['title'];
if (empty($title)) {
$title = $resource->title.' - '.$resource->mime_type.' - '.Pluf_Utils::prettySize($this->filesize);
}
$html .= '<div class="title">'.$download.'<a href="'.$viewUrl.'" title="'.__('View resource details').'">'.$title.'</a></div>'."\n";
$html .= '</div>';
return $html;
}
/**
* Renders a raw version of the resource, without any possibilities of formatting or the like
*/
function renderRaw()
{
$resource = $this->get_wikiresource();
$url = $this->getRawURL();
if (preg_match('#^image/(gif|jpeg|png|tiff)$#', $resource->mime_type)) {
return sprintf('<img src="%s" alt="%s" />', $url, $resource->title);
}
if (preg_match('#^text/(plain|xml|html|sgml|javascript|ecmascript|css)$#', $resource->mime_type)) {
return sprintf('<iframe src="%s" alt="%s"></iframe>', $url, $resource->title);
}
return '';
}
public function timelineFragment($request)
{
$resource = $this->get_wikiresource();
$url = Pluf::f('url_base')
.Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::viewResource',
array($request->project->shortname,
$resource->title),
array('rev' => $this->id));
$out = "\n".'<tr class="log"><td><a href="'.$url.'">'.
Pluf_esc(Pluf_Template_dateAgo($this->creation_dtime, 'without')).
'</a></td><td>';
$stag = new IDF_Template_ShowUser();
$user = $stag->start($this->get_submitter(), $request, '', false);
$out .= sprintf(__('<a href="%1$s" title="View resource">%2$s</a>, %3$s'), $url, Pluf_esc($resource->title), Pluf_esc($this->summary));
$out .= '</td></tr>';
$out .= "\n".'<tr class="extra"><td colspan="2">
<div class="helptext right">'.sprintf(__('Change of <a href="%1$s">%2$s</a>, by %3$s'), $url, Pluf_esc($resource->title), $user).'</div></td></tr>';
return Pluf_Template::markSafe($out);
}
public function feedFragment($request)
{
$resource = $this->get_wikiresource();
$url = Pluf::f('url_base')
.Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::viewResource',
array($request->project->shortname,
$resource->title),
array('rev' => $this->id));
$title = sprintf(__('%1$s: Documentation resource %2$s updated - %3$s'),
$request->project->name,
$resource->title, $resource->summary);
$date = Pluf_Date::gmDateToGmString($this->creation_dtime);
$context = new Pluf_Template_Context_Request(
$request,
array('url' => $url,
'title' => $title,
'resource' => $resource,
'rev' => $this,
'create' => false,
'date' => $date)
);
$tmpl = new Pluf_Template('idf/wiki/feedfragment-resource.xml');
return $tmpl->render($context);
}
}

View File

@ -119,7 +119,7 @@ $cfg['time_zone'] = 'Europe/Berlin';
# Configure which languages should be available in your forge. # Configure which languages should be available in your forge.
# If you want to enable an additional language, ensure that the # If you want to enable an additional language, ensure that the
# language file in question resides in 'src/IDF/locale'. # language file in question resides in 'src/IDF/locale'.
$cfg['languages'] = array('en', 'fr', 'de', 'es_ES'); $cfg['languages'] = array('en', 'fr', 'de', 'es_ES', 'ru');
# ---------------------------------------------------------------------------- # # ---------------------------------------------------------------------------- #
@ -495,5 +495,54 @@ $cfg['idf_strong_key_check'] = false;
# always have precedence. # always have precedence.
# $cfg['max_upload_size'] = 2097152; // Size in bytes # $cfg['max_upload_size'] = 2097152; // Size in bytes
# If a download archive is uploaded, the size of the archive is limited to 20MB.
# The php.ini upload_max_filesize and post_max_size configuration setting will
# always have precedence.
# $cfg['max_upload_archive_size'] = 20971520; // Size in bytes
# Older versions of Indefero submitted a POST request to a configured
# post-commit web hook when new revisions arrived, whereas a PUT request
# would have been more appropriate. Also, the payload's HMAC digest was
# submitted as value of the HTTP header 'Post-Commit-Hook-Hmac' during
# such a request. Since newer versions of Indefero use the same authentication
# mechanism (based on the same secret key) for other web hooks of the same
# project as well, the name of this HTTP header was no longer appropriate
# and as such changed to simply 'Web-Hook-Hmac'.
#
# Setting the following configuration option to 'compat' now restores the
# old behaviour in both cases. Please notice however that this compatibility
# option is likely to go away in the next major version of Indefero, so you
# should really change the other end of your web hooks!
$cfg['webhook_processing'] = 'compat';
# If IDF recalculates the activity index of the forge's projects, it does so
# by looking at the created and updated items in a particular tab / section
# for each project.
#
# You can now edit the weights that are applied to the calculation for each
# section in order to give other things more precendence. For example, if you
# do not use the documentation part to a great extent in most of your projects,
# you can weight this section lower and get an overall better activity value.
#
# If a section is removed, then activity in this section is neglected during
# the calculation. The same is true in case a section is disabled in the
# project administration.
$cfg['activity_section_weights'] = array(
'source' => 4,
'issues' => 2,
'wiki' => 2,
'downloads' => 1,
'review' => 1,
);
# Here you can define the timespan in days how long the activity calculation
# process should look into the history to get meaningful activity values for
# each project.
#
# If you have many low-profile projects in your forge, i.e. projects that only
# record very little activity, then it might be a good idea to bump this value
# high enough to show a proper activity index for those projects as well.
$cfg['activity_lookback'] = 7;
return $cfg; return $cfg;

View File

@ -29,6 +29,16 @@ $ctl[] = array('regex' => '#^/$#',
'model' => 'IDF_Views', 'model' => 'IDF_Views',
'method' => 'index'); 'method' => 'index');
$ctl[] = array('regex' => '#^/projects/$#',
'base' => $base,
'model' => 'IDF_Views',
'method' => 'listProjects');
$ctl[] = array('regex' => '#^/projects/label/(\w+)/(\w+)/$#',
'base' => $base,
'model' => 'IDF_Views',
'method' => 'listProjectsByLabel');
$ctl[] = array('regex' => '#^/login/$#', $ctl[] = array('regex' => '#^/login/$#',
'base' => $base, 'base' => $base,
'model' => 'IDF_Views', 'model' => 'IDF_Views',
@ -255,17 +265,32 @@ $ctl[] = array('regex' => '#^/p/([\-\w]+)/source/changesrev/$#',
'model' => 'IDF_Views_Source_Svn', 'model' => 'IDF_Views_Source_Svn',
'method' => 'changelogRev'); 'method' => 'changelogRev');
$ctl[] = array('regex' => '#^/p/([\-\w]+)/source/repo/(.*)$#',
'base' => $base,
'model' => 'IDF_Views_Source',
'method' => 'repository');
// ---------- WIKI ----------------------------------------- // ---------- WIKI -----------------------------------------
$ctl[] = array('regex' => '#^/p/([\-\w]+)/doc/$#', $ctl[] = array('regex' => '#^/p/([\-\w]+)/doc/$#',
'base' => $base, 'base' => $base,
'model' => 'IDF_Views_Wiki', 'model' => 'IDF_Views_Wiki',
'method' => 'index'); 'method' => 'listPages');
$ctl[] = array('regex' => '#^/p/([\-\w]+)/res/$#',
'base' => $base,
'model' => 'IDF_Views_Wiki',
'method' => 'listResources');
$ctl[] = array('regex' => '#^/p/([\-\w]+)/doc/create/$#', $ctl[] = array('regex' => '#^/p/([\-\w]+)/doc/create/$#',
'base' => $base, 'base' => $base,
'model' => 'IDF_Views_Wiki', 'model' => 'IDF_Views_Wiki',
'method' => 'create'); 'method' => 'createPage');
$ctl[] = array('regex' => '#^/p/([\-\w]+)/res/create/$#',
'base' => $base,
'model' => 'IDF_Views_Wiki',
'method' => 'createResource');
$ctl[] = array('regex' => '#^/p/([\-\w]+)/doc/search/$#', $ctl[] = array('regex' => '#^/p/([\-\w]+)/doc/search/$#',
'base' => $base, 'base' => $base,
@ -275,30 +300,60 @@ $ctl[] = array('regex' => '#^/p/([\-\w]+)/doc/search/$#',
$ctl[] = array('regex' => '#^/p/([\-\w]+)/doc/label/(\d+)/$#', $ctl[] = array('regex' => '#^/p/([\-\w]+)/doc/label/(\d+)/$#',
'base' => $base, 'base' => $base,
'model' => 'IDF_Views_Wiki', 'model' => 'IDF_Views_Wiki',
'method' => 'listLabel'); 'method' => 'listPagesWithLabel');
$ctl[] = array('regex' => '#^/p/([\-\w]+)/doc/update/(.*)/$#', $ctl[] = array('regex' => '#^/p/([\-\w]+)/doc/update/(.*)/$#',
'base' => $base, 'base' => $base,
'model' => 'IDF_Views_Wiki', 'model' => 'IDF_Views_Wiki',
'method' => 'update'); 'method' => 'updatePage');
$ctl[] = array('regex' => '#^/p/([\-\w]+)/res/update/(.*)/$#',
'base' => $base,
'model' => 'IDF_Views_Wiki',
'method' => 'updateResource');
$ctl[] = array('regex' => '#^/p/([\-\w]+)/doc/delrev/(\d+)/$#', $ctl[] = array('regex' => '#^/p/([\-\w]+)/doc/delrev/(\d+)/$#',
'base' => $base, 'base' => $base,
'model' => 'IDF_Views_Wiki', 'model' => 'IDF_Views_Wiki',
'method' => 'deleteRev'); 'method' => 'deletePageRev');
$ctl[] = array('regex' => '#^/p/([\-\w]+)/res/delrev/(\d+)/$#',
'base' => $base,
'model' => 'IDF_Views_Wiki',
'method' => 'deleteResourceRev');
$ctl[] = array('regex' => '#^/p/([\-\w]+)/doc/delete/(\d+)/$#', $ctl[] = array('regex' => '#^/p/([\-\w]+)/doc/delete/(\d+)/$#',
'base' => $base, 'base' => $base,
'model' => 'IDF_Views_Wiki', 'model' => 'IDF_Views_Wiki',
'method' => 'delete'); 'method' => 'deletePage');
$ctl[] = array('regex' => '#^/p/([\-\w]+)/res/delete/(\d+)/$#',
'base' => $base,
'model' => 'IDF_Views_Wiki',
'method' => 'deleteResource');
$ctl[] = array('regex' => '#^/p/([\-\w]+)/res/raw/(.*)/$#',
'base' => $base,
'model' => 'IDF_Views_Wiki',
'method' => 'rawResource');
$ctl[] = array('regex' => '#^/p/([\-\w]+)/page/(.*)/$#', $ctl[] = array('regex' => '#^/p/([\-\w]+)/page/(.*)/$#',
'base' => $base, 'base' => $base,
'model' => 'IDF_Views_Wiki', 'model' => 'IDF_Views_Wiki',
'method' => 'view'); 'method' => 'viewPage');
$ctl[] = array('regex' => '#^/p/([\-\w]+)/resource/(.*)/$#',
'base' => $base,
'model' => 'IDF_Views_Wiki',
'method' => 'viewResource');
// ---------- Downloads ------------------------------------ // ---------- Downloads ------------------------------------
$ctl[] = array('regex' => '#^/help/archive-format/$#',
'base' => $base,
'model' => 'IDF_Views',
'method' => 'faqArchiveFormat');
$ctl[] = array('regex' => '#^/p/([\-\w]+)/downloads/$#', $ctl[] = array('regex' => '#^/p/([\-\w]+)/downloads/$#',
'base' => $base, 'base' => $base,
'model' => 'IDF_Views_Download', 'model' => 'IDF_Views_Download',
@ -327,7 +382,12 @@ $ctl[] = array('regex' => '#^/p/([\-\w]+)/downloads/(\d+)/get/$#',
$ctl[] = array('regex' => '#^/p/([\-\w]+)/downloads/create/$#', $ctl[] = array('regex' => '#^/p/([\-\w]+)/downloads/create/$#',
'base' => $base, 'base' => $base,
'model' => 'IDF_Views_Download', 'model' => 'IDF_Views_Download',
'method' => 'submit'); 'method' => 'create');
$ctl[] = array('regex' => '#^/p/([\-\w]+)/downloads/create/archive/$#',
'base' => $base,
'model' => 'IDF_Views_Download',
'method' => 'createFromArchive');
$ctl[] = array('regex' => '#^/p/([\-\w]+)/downloads/(\d+)/delete/$#', $ctl[] = array('regex' => '#^/p/([\-\w]+)/downloads/(\d+)/delete/$#',
'base' => $base, 'base' => $base,
@ -418,6 +478,11 @@ $ctl[] = array('regex' => '#^/api/$#',
// ---------- FORGE ADMIN -------------------------------- // ---------- FORGE ADMIN --------------------------------
$ctl[] = array('regex' => '#^/admin/forge/$#',
'base' => $base,
'model' => 'IDF_Views_Admin',
'method' => 'forge');
$ctl[] = array('regex' => '#^/admin/projects/$#', $ctl[] = array('regex' => '#^/admin/projects/$#',
'base' => $base, 'base' => $base,
'model' => 'IDF_Views_Admin', 'model' => 'IDF_Views_Admin',
@ -428,6 +493,11 @@ $ctl[] = array('regex' => '#^/admin/projects/(\d+)/$#',
'model' => 'IDF_Views_Admin', 'model' => 'IDF_Views_Admin',
'method' => 'projectUpdate'); 'method' => 'projectUpdate');
$ctl[] = array('regex' => '#^/admin/projects/labels/$#',
'base' => $base,
'model' => 'IDF_Views_Admin',
'method' => 'projectLabels');
$ctl[] = array('regex' => '#^/admin/projects/create/$#', $ctl[] = array('regex' => '#^/admin/projects/create/$#',
'base' => $base, 'base' => $base,
'model' => 'IDF_Views_Admin', 'model' => 'IDF_Views_Admin',

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

4587
src/IDF/locale/pt_BR/idf.po Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

4581
src/IDF/locale/tr/idf.po Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -22,7 +22,9 @@
# ***** END LICENSE BLOCK ***** */ # ***** END LICENSE BLOCK ***** */
$m = array(); $m = array();
$m['IDF_Tag'] = array('relate_to' => array('IDF_Project')); $m['IDF_ProjectActivity'] = array('relate_to' => array('IDF_Project'));
$m['IDF_Tag'] = array('relate_to' => array('IDF_Project'),
'relate_to_many' => array('IDF_Project'));
$m['IDF_Issue'] = array('relate_to' => array('IDF_Project', 'Pluf_User', 'IDF_Tag'), $m['IDF_Issue'] = array('relate_to' => array('IDF_Project', 'Pluf_User', 'IDF_Tag'),
'relate_to_many' => array('IDF_Tag', 'Pluf_User')); 'relate_to_many' => array('IDF_Tag', 'Pluf_User'));
$m['IDF_IssueComment'] = array('relate_to' => array('IDF_Issue', 'Pluf_User')); $m['IDF_IssueComment'] = array('relate_to' => array('IDF_Issue', 'Pluf_User'));
@ -30,9 +32,12 @@ $m['IDF_IssueFile'] = array('relate_to' => array('IDF_IssueComment', 'Pluf_User'
$m['IDF_Upload'] = array('relate_to' => array('IDF_Project', 'Pluf_User'), $m['IDF_Upload'] = array('relate_to' => array('IDF_Project', 'Pluf_User'),
'relate_to_many' => array('IDF_Tag')); 'relate_to_many' => array('IDF_Tag'));
$m['IDF_Search_Occ'] = array('relate_to' => array('IDF_Project'),); $m['IDF_Search_Occ'] = array('relate_to' => array('IDF_Project'),);
$m['IDF_WikiPage'] = array('relate_to' => array('IDF_Project', 'Pluf_User'), $m['IDF_Wiki_Page'] = array('relate_to' => array('IDF_Project', 'Pluf_User'),
'relate_to_many' => array('IDF_Tag', 'Pluf_User')); 'relate_to_many' => array('IDF_Tag', 'Pluf_User'));
$m['IDF_WikiRevision'] = array('relate_to' => array('IDF_WikiPage', 'Pluf_User')); $m['IDF_Wiki_PageRevision'] = array('relate_to' => array('IDF_Wiki_Page', 'Pluf_User'));
$m['IDF_Wiki_Resource'] = array('relate_to' => array('IDF_Project', 'Pluf_User'));
$m['IDF_Wiki_ResourceRevision'] = array('relate_to' => array('IDF_Wiki_Resource', 'Pluf_User'),
'relate_to_many' => array('IDF_PageRevision', 'Pluf_User'));
$m['IDF_Review'] = array('relate_to' => array('IDF_Project', 'Pluf_User', 'IDF_Tag'), $m['IDF_Review'] = array('relate_to' => array('IDF_Project', 'Pluf_User', 'IDF_Tag'),
'relate_to_many' => array('IDF_Tag', 'Pluf_User')); 'relate_to_many' => array('IDF_Tag', 'Pluf_User'));
$m['IDF_Review_Patch'] = array('relate_to' => array('IDF_Review', 'Pluf_User')); $m['IDF_Review_Patch'] = array('relate_to' => array('IDF_Review', 'Pluf_User'));

View File

@ -1,5 +1,5 @@
{extends "idf/admin/base.html"} {extends "idf/admin/base.html"}
{block docclass}yui-t1{assign $inDownloads = true}{/block} {block docclass}yui-t3{assign $inDownloads = true}{/block}
{block body} {block body}
<form method="post" action="."> <form method="post" action=".">
<table class="form" summary=""> <table class="form" summary="">
@ -16,6 +16,15 @@
</td> </td>
</tr> </tr>
<tr> <tr>
<td>{$form.f.upload_webhook_url.labelTag}:<br />
{if $form.f.upload_webhook_url.errors}{$form.f.upload_webhook_url.fieldErrors}{/if}
{$form.f.upload_webhook_url|unsafe}<br>
</td>
</tr>
<tr>
<td>{trans 'Web-Hook authentication key:'} {$hookkey}</td>
</tr>
<tr>
<td colspan="2"> <td colspan="2">
<input type="submit" value="{trans 'Save Changes'}" name="submit" /> <input type="submit" value="{trans 'Save Changes'}" name="submit" />
</td> </td>
@ -31,4 +40,29 @@
<p>Optionally, use an equals-sign to document the meaning of each status value.</p> <p>Optionally, use an equals-sign to document the meaning of each status value.</p>
{/blocktrans} {/blocktrans}
</div> </div>
<div class="issue-submit-info">
{blocktrans}<p>The webhook URL setting specifies an URL to which a HTTP <strong>PUT</strong>
request is sent after a new download has been added or to which a HTTP <strong>POST</strong>
request is sent after an existing download has been updated.
If this field is empty, notifications are disabled.</p>
<p>Only properly-escaped <strong>HTTP</strong> URLs are supported, for example:</p>
<ul>
<li><code>http://domain.com/upload</code></li>
<li><code>http://domain.com/upload?my%20param</code></li>
</ul>
<p>In addition, the URL may contain the following "%" notation, which
will be replaced with specific project values for each download:</p>
<ul>
<li><code>%p</code> - project name</li>
<li><code>%d</code> - download id</li>
</ul>
<p>For example, updating download 123 of project 'my-project' with
web hook URL <code>http://mydomain.com/%p/%d</code> would send a POST request to
<code>http://mydomain.com/my-project/123</code>.</p>{/blocktrans}</div>
{/block} {/block}

View File

@ -41,12 +41,11 @@
<tr> <tr>
<th>{$form.f.webhook_url.labelTag}:</th> <th>{$form.f.webhook_url.labelTag}:</th>
<td>{if $form.f.webhook_url.errors}{$form.f.webhook_url.fieldErrors}{/if} <td>{if $form.f.webhook_url.errors}{$form.f.webhook_url.fieldErrors}{/if}
{$form.f.webhook_url|unsafe}<br> {$form.f.webhook_url|unsafe}<br />
</td> </td>
</tr> </tr>
<tr> <tr>
<th>{trans 'Post-commit authentication key:'}</th> <th>{trans 'Web-Hook authentication key:'}</th>
<td>{$hookkey} <td>{$hookkey}
</td> </td>
</tr> </tr>
@ -68,26 +67,26 @@
<br> <br>
<div class="issue-submit-info"> <div class="issue-submit-info">
{blocktrans}<p>The webhook URL setting specifies a URL to which a HTTP POST {blocktrans}<p>The webhook URL setting specifies an URL to which a HTTP
request is sent after each repository commit. If this field is empty, <strong>{$hook_request_method}</strong> request is sent after each repository
notifications are disabled.</p> commit. If this field is empty, notifications are disabled.</p>
<p>Only properly-escaped <strong>HTTP</strong> URLs are supported, for example:</p> <p>Only properly-escaped <strong>HTTP</strong> URLs are supported, for example:</p>
<ul> <ul>
<li>http://domain.com/commit</li> <li><code>http://domain.com/commit</code></li>
<li>http://domain.com/commit?my%20param</li> <li><code>http://domain.com/commit?my%20param</code></li>
</ul> </ul>
<p>In addition, the URL may contain the following "%" notation, which <p>In addition, the URL may contain the following "%" notation, which
will be replaced with specific project values for each commit:</p> will be replaced with specific project values for each commit:</p>
<ul> <ul>
<li>%p - project name</li> <li><code>%p</code> - project name</li>
<li>%r - revision number</li> <li><code>%r</code> - revision number</li>
</ul> </ul>
<p>For example, committing revision 123 to project 'my-project' with <p>For example, committing revision 123 to project 'my-project' with
post-commit URL http://mydomain.com/%p/%r would send a request to post-commit URL <code>http://mydomain.com/%p/%r</code> would send a request to
http://mydomain.com/my-project/123.</p>{/blocktrans}</div> <code>http://mydomain.com/my-project/123</code>.</p>{/blocktrans}</div>
{/block} {/block}

View File

@ -25,13 +25,30 @@
</td> </td>
</tr> </tr>
<tr> <tr>
<th>{$form.f.external_project_url.labelTag}:</th>
<td>{if $form.f.external_project_url.errors}{$form.f.external_project_url.fieldErrors}{/if}
{$form.f.external_project_url|unsafe}
</td>
</tr>
<tr>
<th><strong>{$form.f.description.labelTag}:</strong></th> <th><strong>{$form.f.description.labelTag}:</strong></th>
<td>{if $form.f.description.errors}{$form.f.description.fieldErrors}{/if} <td>{if $form.f.description.errors}{$form.f.description.fieldErrors}{/if}
{$form.f.description|unsafe} {$form.f.description|unsafe}
</td> </td>
</tr> </tr>
<tr> <tr>
<th><strong>{trans 'Current logo'}:</strong></th> <th>{$form.f.label1.labelTag}:</th>
<td>
{if $form.f.label1.errors}{$form.f.label1.fieldErrors}{/if}{$form.f.label1|unsafe}
{if $form.f.label2.errors}{$form.f.label2.fieldErrors}{/if}{$form.f.label2|unsafe}
{if $form.f.label3.errors}{$form.f.label3.fieldErrors}{/if}{$form.f.label3|unsafe}<br />
{if $form.f.label4.errors}{$form.f.label4.fieldErrors}{/if}{$form.f.label4|unsafe}
{if $form.f.label5.errors}{$form.f.label5.fieldErrors}{/if}{$form.f.label5|unsafe}
{if $form.f.label6.errors}{$form.f.label6.fieldErrors}{/if}{$form.f.label6|unsafe}
</td>
</tr>
<tr>
<th>{trans 'Current logo'}:</th>
<td> <td>
{if $logo} {if $logo}
<img src="{url 'IDF_Views_Project::logo', array($project.shortname)}" alt="{trans 'Project logo'}" /> <img src="{url 'IDF_Views_Project::logo', array($project.shortname)}" alt="{trans 'Project logo'}" />
@ -41,14 +58,14 @@
</td> </td>
</tr> </tr>
<tr> <tr>
<th><strong>{$form.f.logo.labelTag}:</strong></th> <th>{$form.f.logo.labelTag}:</th>
<td>{if $form.f.logo.errors}{$form.f.logo.fieldErrors}{/if} <td>{if $form.f.logo.errors}{$form.f.logo.fieldErrors}{/if}
{$form.f.logo|unsafe} {$form.f.logo|unsafe}
</td> </td>
</tr> </tr>
{if $logo} {if $logo}
<tr> <tr>
<th><strong>{$form.f.logo_remove.labelTag}:</strong></th> <th>{$form.f.logo_remove.labelTag}:</th>
<td>{if $form.f.logo_remove.errors}{$form.f.logo_remove.fieldErrors}{/if} <td>{if $form.f.logo_remove.errors}{$form.f.logo_remove.fieldErrors}{/if}
{$form.f.logo_remove|unsafe} {$form.f.logo_remove|unsafe}
</td> </td>
@ -60,6 +77,7 @@
</td> </td>
</table> </table>
</form> </form>
{include 'idf/project/js-autocomplete.html'}{/block}
{/block} {/block}
{block context} {block context}

View File

@ -10,11 +10,11 @@
</div> </div>
{/if} {/if}
<form method="post" action="."> <form method="post" action=".">
<table class="form" summary=""> <table class="form access-rights" summary="">
<tr> <tr>
<th>&nbsp;</th> <th>&nbsp;</th>
<th class="a-c"><strong>{trans 'Access Rights'}</strong></th> <th class="left"><strong>{trans 'Access Rights'}</strong></th>
<th class="a-c"><strong>{trans 'Notification Email'}</strong></th> <th class="left"><strong>{trans 'Notifications'}</strong></th>
</tr> </tr>
<tr> <tr>
<th><strong>{$form.f.downloads_access_rights.labelTag}:</strong></th> <th><strong>{$form.f.downloads_access_rights.labelTag}:</strong></th>
@ -22,6 +22,12 @@
{$form.f.downloads_access_rights|unsafe} {$form.f.downloads_access_rights|unsafe}
</td> </td>
<td>{if $form.f.downloads_notification_email.errors}{$form.f.downloads_notification_email.fieldErrors}{/if} <td>{if $form.f.downloads_notification_email.errors}{$form.f.downloads_notification_email.fieldErrors}{/if}
{$form.f.downloads_notification_owners_enabled|unsafe}
{$form.f.downloads_notification_owners_enabled.labelTag}
{$form.f.downloads_notification_members_enabled|unsafe}
{$form.f.downloads_notification_members_enabled.labelTag}
{$form.f.downloads_notification_email_enabled|unsafe}
{$form.f.downloads_notification_email_enabled.labelTag}
{$form.f.downloads_notification_email|unsafe} {$form.f.downloads_notification_email|unsafe}
</td> </td>
</tr> </tr>
@ -31,6 +37,12 @@
{$form.f.wiki_access_rights|unsafe} {$form.f.wiki_access_rights|unsafe}
</td> </td>
<td>{if $form.f.wiki_notification_email.errors}{$form.f.wiki_notification_email.fieldErrors}{/if} <td>{if $form.f.wiki_notification_email.errors}{$form.f.wiki_notification_email.fieldErrors}{/if}
{$form.f.wiki_notification_owners_enabled|unsafe}
{$form.f.wiki_notification_owners_enabled.labelTag}
{$form.f.wiki_notification_members_enabled|unsafe}
{$form.f.wiki_notification_members_enabled.labelTag}
{$form.f.wiki_notification_email_enabled|unsafe}
{$form.f.wiki_notification_email_enabled.labelTag}
{$form.f.wiki_notification_email|unsafe} {$form.f.wiki_notification_email|unsafe}
</td> </td>
</tr> </tr>
@ -40,6 +52,12 @@
{$form.f.issues_access_rights|unsafe} {$form.f.issues_access_rights|unsafe}
</td> </td>
<td>{if $form.f.issues_notification_email.errors}{$form.f.issues_notification_email.fieldErrors}{/if} <td>{if $form.f.issues_notification_email.errors}{$form.f.issues_notification_email.fieldErrors}{/if}
{$form.f.issues_notification_owners_enabled|unsafe}
{$form.f.issues_notification_owners_enabled.labelTag}
{$form.f.issues_notification_members_enabled|unsafe}
{$form.f.issues_notification_members_enabled.labelTag}
{$form.f.issues_notification_email_enabled|unsafe}
{$form.f.issues_notification_email_enabled.labelTag}
{$form.f.issues_notification_email|unsafe} {$form.f.issues_notification_email|unsafe}
</td> </td>
</tr> </tr>
@ -49,32 +67,37 @@
{$form.f.source_access_rights|unsafe} {$form.f.source_access_rights|unsafe}
</td> </td>
<td>{if $form.f.source_notification_email.errors}{$form.f.source_notification_email.fieldErrors}{/if} <td>{if $form.f.source_notification_email.errors}{$form.f.source_notification_email.fieldErrors}{/if}
{$form.f.source_notification_owners_enabled|unsafe}
{$form.f.source_notification_owners_enabled.labelTag}
{$form.f.source_notification_members_enabled|unsafe}
{$form.f.source_notification_members_enabled.labelTag}
{$form.f.source_notification_email_enabled|unsafe}
{$form.f.source_notification_email_enabled.labelTag}
{$form.f.source_notification_email|unsafe} {$form.f.source_notification_email|unsafe}
</td> </td>
</tr> </tr>
<tr> <tr>
<td>&nbsp;</td>
<td colspan="2" class="helptext">
{blocktrans}
Only project members and admins have write access to the source.<br />
If you restrict the access to the source, anonymous access is<br />
not provided and the users must authenticate themselves with their<br />
password or SSH key.{/blocktrans}
</td></tr>
<tr>
<th><strong>{$form.f.review_access_rights.labelTag}:</strong></th> <th><strong>{$form.f.review_access_rights.labelTag}:</strong></th>
<td>{if $form.f.review_access_rights.errors}{$form.f.review_access_rights.fieldErrors}{/if} <td>{if $form.f.review_access_rights.errors}{$form.f.review_access_rights.fieldErrors}{/if}
{$form.f.review_access_rights|unsafe} {$form.f.review_access_rights|unsafe}
</td> </td>
<td>{if $form.f.review_notification_email.errors}{$form.f.review_notification_email.fieldErrors}{/if} <td>{if $form.f.review_notification_email.errors}{$form.f.review_notification_email.fieldErrors}{/if}
{$form.f.review_notification_owners_enabled|unsafe}
{$form.f.review_notification_owners_enabled.labelTag}
{$form.f.review_notification_members_enabled|unsafe}
{$form.f.review_notification_members_enabled.labelTag}
{$form.f.review_notification_email_enabled|unsafe}
{$form.f.review_notification_email_enabled.labelTag}
{$form.f.review_notification_email|unsafe} {$form.f.review_notification_email|unsafe}
</td> </td>
</tr> </tr>
<tr> <tr>
<th>{if $form.f.private_project.errors}{$form.f.private_project.fieldErrors}{/if} <th>&nbsp;</th>
<td>
{if $form.f.private_project.errors}{$form.f.private_project.fieldErrors}{/if}
{$form.f.private_project|unsafe} {$form.f.private_project|unsafe}
</th> {$form.f.private_project.labelTag}
<td>{$form.f.private_project.labelTag}</td> </td>
</tr> </tr>
<tr id="authorized-users-row"> <tr id="authorized-users-row">
<td>&nbsp;</td> <td>&nbsp;</td>
@ -94,27 +117,45 @@ password or SSH key.{/blocktrans}
{/block} {/block}
{block context} {block context}
<div class="issue-submit-info"> <div class="issue-submit-info">
<p><strong>{trans 'Instructions:'}</strong></p> <p>{blocktrans}This section allows you to configure project tabs access rights and notifications.{/blocktrans}</p>
<p>{blocktrans}You can configure here the project tabs access rights and notification emails.{/blocktrans}</p> <p><strong>{trans 'Access Rights'}</strong></p>
<p>{blocktrans}Notification emails will be sent from the <strong>{$from_email}</strong> address, if you send the email to a mailing list, you may need to register this email address. Multiple email addresses must be separated through commas (','). If you do not want to send emails for a given type of changes, simply leave the corresponding field empty.{/blocktrans}</p> <p>{blocktrans}Tab access controls whether a single user can navigate into a particular section of your project via the main menu or automatically generated object links.{/blocktrans}</p>
<p>{blocktrans}If you mark a project as private, only the project members and administrators, together with the extra authorized users you provide will have access to the project. You will still be able to define further access rights for the different tabs but the "Open to all" and "Signed in users" will default to authorized users only.{/blocktrans}</p> <p>{blocktrans}If you mark a project as private, only the project members and administrators, together with the extra authorized users you provide will have access to the project as a whole. You will still be able to define further access rights for the different tabs but the "Open to all" and "Signed in users" will default to authorized users only.{/blocktrans}</p>
<p>{blocktrans}Specify each person by its login. Each person must have already registered with the given login. Separate the logins with commas and/or new lines.{/blocktrans}</p> <p>{blocktrans}For the extra authorized user list, specify each person by its login. Each person must have already registered with the given login. Separate the logins with commas and/or new lines.{/blocktrans}</p>
<p>{blocktrans}Only project members and admins have write access to the source. If you restrict the access to the source, anonymous access is not provided and the users must authenticate themselves with their password or SSH key.{/blocktrans}</p>
<p><strong>{trans 'Notifications'}</strong></p>
<p>{blocktrans}Here you can configure who should be notified about changes in a particular section. You can also configure additional addresses, like the one of a mailing list, that should be notified. (Keep in mind that you might have to register the sender address <strong>{$from_email}</strong> to let the mailing list actually accept notification emails.) Multiple email addresses must be separated through commas (',').{/blocktrans}</p>
</div> </div>
{/block} {/block}
{block javascript}{literal} {block javascript}{literal}
<script type="text/javascript"> <script type="text/javascript">
$(document).ready(function(){ $(document).ready(function() {
// If not checked, hide // if not checked, hide
if (!$("#id_private_project").is(":checked")) if (!$("#id_private_project").is(":checked")) {
$("#authorized-users-row").hide(); $("#authorized-users-row").hide();
$("#id_private_project").click(function(){ }
if ($("#id_private_project").is(":checked")) { $("#id_private_project").click(function() {
$("#authorized-users-row").show(); if ($("#id_private_project").is(":checked")) {
$("#authorized-users-row").show();
} else {
$("#authorized-users-row").hide();
}
});
// if not checked, hide
$.each(['downloads', 'wiki', 'issues', 'source', 'review'], function(index, section) {
if (!$("#id_" + section + "_notification_email_enabled").is(":checked")) {
$("#id_" + section + "_notification_email").hide();
}
$("#id_" + section + "_notification_email_enabled").click(function() {
if ($("#id_" + section + "_notification_email_enabled").is(":checked")) {
$("#id_" + section + "_notification_email").show();
} else { } else {
$("#authorized-users-row").hide(); $("#id_" + section + "_notification_email").hide();
} }
}); });
}); });
});
</script> </script>
{/literal}{/block} {/literal}{/block}

View File

@ -31,20 +31,20 @@
<![endif]--> <![endif]-->
{block extraheader}{/block} {block extraheader}{/block}
<title>{block pagetitle}{$page_title|strip_tags}{/block}{if $project} - {$project.shortdesc}{/if}</title> <title>{block pagetitle}{$page_title|strip_tags}{/block}{if $project} - {$project.shortdesc}{/if}</title>
<script type="text/javascript" src="{media '/idf/js/jquery-1.2.6.min.js'}"></script> <script type="text/javascript" src="{media '/idf/js/jquery-1.7.1.min.js'}"></script>
{appversion} {appversion}
</head> </head>
<body> <body>
<div id="{block docid}doc3{/block}"> <div id="{block docid}doc3{/block}">
<div id="hd"> <div id="hd">
{if $project}<h1 class="project-title">{$project}<img class="logo" src="{url 'IDF_Views_Project::logo', array($project.shortname)}" alt="{trans 'Project logo'}" />{if $project.private}<img class="lock" src="{media '/idf/img/lock.png'}" alt="{trans 'Private project'}" />{/if}{$p}</h1>{/if} {if $project}<h1 class="project-title">{$project}<img class="logo" src="{url 'IDF_Views_Project::logo', array($project.shortname)}" alt="{trans 'Project logo'}" />{if $project.private}<img class="lock" src="{media '/idf/img/lock.png'}" alt="{trans 'Private project'}" />{/if}{$p}{assign $url = $project.external_project_url}{if $url != ''}<a href="{$url}" target="_blank" class="external-link" title="{trans 'External link to project'}" />&nbsp;</a>{/if}</h1>{/if}
{include 'idf/main-menu.html'} {include 'idf/main-menu.html'}
<div id="header"> <div id="header">
<div id="main-tabs"> <div id="main-tabs">
{if $project} {if $project}
<a accesskey="1" href="{url 'IDF_Views_Project::home', array($project.shortname)}"{block tabhome}{/block}>{trans 'Project Home'}</a> <a accesskey="1" href="{url 'IDF_Views_Project::home', array($project.shortname)}"{block tabhome}{/block}>{trans 'Project Home'}</a>
{if $hasDownloadsAccess} <a href="{url 'IDF_Views_Download::index', array($project.shortname)}"{block tabdownloads}{/block}>{trans 'Downloads'}</a>{/if} {if $hasDownloadsAccess} <a href="{url 'IDF_Views_Download::index', array($project.shortname)}"{block tabdownloads}{/block}>{trans 'Downloads'}</a>{/if}
{if $hasWikiAccess} <a href="{url 'IDF_Views_Wiki::index', array($project.shortname)}"{block tabwiki}{/block}>{trans 'Documentation'}</a>{/if} {if $hasWikiAccess} <a href="{url 'IDF_Views_Wiki::listPages', array($project.shortname)}"{block tabwiki}{/block}>{trans 'Documentation'}</a>{/if}
{if $hasIssuesAccess} <a href="{url 'IDF_Views_Issue::index', array($project.shortname)}"{block tabissues}{/block}>{trans 'Issues'}</a>{/if} {if $hasIssuesAccess} <a href="{url 'IDF_Views_Issue::index', array($project.shortname)}"{block tabissues}{/block}>{trans 'Issues'}</a>{/if}
{if $hasSourceAccess} <a href="{url 'IDF_Views_Source::treeBase', array($project.shortname, $project.getScmRoot())}"{block tabsource}{/block}>{trans 'Source'}</a>{/if} {if $hasSourceAccess} <a href="{url 'IDF_Views_Source::treeBase', array($project.shortname, $project.getScmRoot())}"{block tabsource}{/block}>{trans 'Source'}</a>{/if}
{if $hasReviewAccess} <a href="{url 'IDF_Views_Review::index', array($project.shortname)}"{block tabreview}{/block}>{trans 'Code Review'}</a>{/if} {if $hasReviewAccess} <a href="{url 'IDF_Views_Review::index', array($project.shortname)}"{block tabreview}{/block}>{trans 'Code Review'}</a>{/if}

View File

@ -31,7 +31,7 @@
<![endif]--> <![endif]-->
{block extraheader}{/block} {block extraheader}{/block}
<title>{block pagetitle}{$page_title|strip_tags}{/block}</title> <title>{block pagetitle}{$page_title|strip_tags}{/block}</title>
<script type="text/javascript" src="{media '/idf/js/jquery-1.2.6.min.js'}"></script> <script type="text/javascript" src="{media '/idf/js/jquery-1.7.1.min.js'}"></script>
{appversion} {appversion}
</head> </head>
<body> <body>

View File

@ -31,20 +31,20 @@
<![endif]--> <![endif]-->
{block extraheader}{/block} {block extraheader}{/block}
<title>{block pagetitle}{$page_title|strip_tags}{/block}{if $project} - {$project.shortdesc}{/if}</title> <title>{block pagetitle}{$page_title|strip_tags}{/block}{if $project} - {$project.shortdesc}{/if}</title>
<script type="text/javascript" src="{media '/idf/js/jquery-1.2.6.min.js'}"></script> <script type="text/javascript" src="{media '/idf/js/jquery-1.7.1.min.js'}"></script>
{appversion} {appversion}
</head> </head>
<body> <body>
<div id="{block docid}doc3{/block}" class="{block docclass}yui-t3{/block}"> <div id="{block docid}doc3{/block}" class="{block docclass}yui-t3{/block}">
<div id="hd"> <div id="hd">
{if $project}<h1 class="project-title">{$project}<img class="logo" src="{url 'IDF_Views_Project::logo', array($project.shortname)}" alt="{trans 'Project logo'}" />{if $project.private}<img class="lock" src="{media '/idf/img/lock.png'}" alt="{trans 'Private project'}" />{/if}{$p}</h1>{/if} {if $project}<h1 class="project-title">{$project}<img class="logo" src="{url 'IDF_Views_Project::logo', array($project.shortname)}" alt="{trans 'Project logo'}" />{if $project.private}<img class="lock" src="{media '/idf/img/lock.png'}" alt="{trans 'Private project'}" />{/if}{$p}{assign $url = $project.external_project_url}{if $url != ''}<a href="{$url}" target="_blank" class="external-link" title="{trans 'External link to project'}" />&nbsp;</a>{/if}</h1>{/if}
{include 'idf/main-menu.html'} {include 'idf/main-menu.html'}
<div id="header"> <div id="header">
<div id="main-tabs"> <div id="main-tabs">
{if $project} {if $project}
<a accesskey="1" href="{url 'IDF_Views_Project::home', array($project.shortname)}"{block tabhome}{/block}>{trans 'Project Home'}</a> <a accesskey="1" href="{url 'IDF_Views_Project::home', array($project.shortname)}"{block tabhome}{/block}>{trans 'Project Home'}</a>
{if $hasDownloadsAccess} <a href="{url 'IDF_Views_Download::index', array($project.shortname)}"{block tabdownloads}{/block}>{trans 'Downloads'}</a>{/if} {if $hasDownloadsAccess} <a href="{url 'IDF_Views_Download::index', array($project.shortname)}"{block tabdownloads}{/block}>{trans 'Downloads'}</a>{/if}
{if $hasWikiAccess} <a href="{url 'IDF_Views_Wiki::index', array($project.shortname)}"{block tabwiki}{/block}>{trans 'Documentation'}</a>{/if} {if $hasWikiAccess} <a href="{url 'IDF_Views_Wiki::listPages', array($project.shortname)}"{block tabwiki}{/block}>{trans 'Documentation'}</a>{/if}
{if $hasIssuesAccess} <a href="{url 'IDF_Views_Issue::index', array($project.shortname)}"{block tabissues}{/block}>{trans 'Issues'}</a>{/if} {if $hasIssuesAccess} <a href="{url 'IDF_Views_Issue::index', array($project.shortname)}"{block tabissues}{/block}>{trans 'Issues'}</a>{/if}
{if $hasSourceAccess} <a href="{url 'IDF_Views_Source::treeBase', array($project.shortname, $project.getScmRoot())}"{block tabsource}{/block}>{trans 'Source'}</a>{/if} {if $hasSourceAccess} <a href="{url 'IDF_Views_Source::treeBase', array($project.shortname, $project.getScmRoot())}"{block tabsource}{/block}>{trans 'Source'}</a>{/if}
{if $hasReviewAccess} <a href="{url 'IDF_Views_Review::index', array($project.shortname)}"{block tabreview}{/block}>{trans 'Code Review'}</a>{/if} {if $hasReviewAccess} <a href="{url 'IDF_Views_Review::index', array($project.shortname)}"{block tabreview}{/block}>{trans 'Code Review'}</a>{/if}

View File

@ -2,6 +2,10 @@
{block tabdownloads} class="active"{/block} {block tabdownloads} class="active"{/block}
{block subtabs} {block subtabs}
<div id="sub-tabs"> <div id="sub-tabs">
<a {if $inDownloads}class="active" {/if}href="{url 'IDF_Views_Download::index', array($project.shortname)}">{trans 'Downloads'}</a> {if $isOwner or $isMember}| <a {if $inSubmit}class="active" {/if}href="{url 'IDF_Views_Download::submit', array($project.shortname)}">{trans 'New Download'}</a> {/if} <a {if $inDownloads}class="active" {/if}href="{url 'IDF_Views_Download::index', array($project.shortname)}">{trans 'Downloads'}</a>
{if $isOwner or $isMember}
| <a {if $inCreate}class="active" {/if}href="{url 'IDF_Views_Download::create', array($project.shortname)}">{trans 'New Download'}</a>
| <a {if $inCreateFromArchive}class="active" {/if}href="{url 'IDF_Views_Download::createFromArchive', array($project.shortname)}">{trans 'Upload Archive'}</a>
{/if}
</div> </div>
{/block} {/block}

View File

@ -1,5 +1,5 @@
{extends "idf/downloads/base.html"} {extends "idf/downloads/base.html"}
{block docclass}yui-t3{assign $inSubmit=true}{/block} {block docclass}yui-t3{assign $inCreate=true}{/block}
{block body} {block body}
{if $form.errors} {if $form.errors}
<div class="px-message-error"> <div class="px-message-error">

View File

@ -0,0 +1,38 @@
{extends "idf/downloads/base.html"}
{block docclass}yui-t3{assign $inCreateFromArchive=true}{/block}
{block body}
{if $form.errors}
<div class="px-message-error">
<p>{trans 'The form contains some errors. Please correct them to submit the archive.'}</p>
{if $form.get_top_errors}
{$form.render_top_errors|unsafe}
{/if}
</div>
{/if}
<form method="post" enctype="multipart/form-data" action=".">
<table class="form" summary="">
<tr>
<th><strong>{$form.f.archive.labelTag}:</strong></th>
<td>{if $form.f.archive.errors}{$form.f.archive.fieldErrors}{/if}
{$form.f.archive|unsafe}
</td>
</tr>
<tr>
<td>&nbsp;</td>
<td><input type="submit" value="{trans 'Submit Archive'}" name="submit" /> | <a href="{url 'IDF_Views_Download::index', array($project.shortname)}">{trans 'Cancel'}</a>
</td>
</tr>
</table>
</form>
{/block}
{block context}
<div class="issue-submit-info">
<h2>{trans 'Instructions'}</h2>
<p>{blocktrans}The archive must include a <code>manifest.xml</code> file with meta information about the
files to process inside the archive. All processed files must be unique or replace existing files explicitely.{/blocktrans}</p>
{aurl 'url', 'IDF_Views::faqArchiveFormat'}
<p>{blocktrans}You can learn more about the archive format <a href="{$url}">here</a>.{/blocktrans}</p>
</div>
{/block}

View File

@ -0,0 +1,17 @@
{trans 'Hello,'}
{blocktrans}A file download was updated:{/blocktrans}
{$file.summary|safe}
{$file} - {$file.filesize|ssize}
{trans 'Project:'} {$project.name|safe}
{trans 'Submitted by:'} {$file.get_submitter|safe}
{if $tags.count()}{trans 'Labels:'}
{foreach $tags as $tag} {$tag.class|safe}:{$tag.name|safe}
{/foreach}{/if}
{trans 'Download:'} {$urlfile}
{if $file.changelog}
{trans 'Description:'}
{$file.changelog}
{/if}

View File

@ -3,7 +3,7 @@
{block body} {block body}
{$downloads.render} {$downloads.render}
{if $isOwner or $isMember} {if $isOwner or $isMember}
{aurl 'url', 'IDF_Views_Download::submit', array($project.shortname)} {aurl 'url', 'IDF_Views_Download::create', array($project.shortname)}
<p><a href="{$url}"><img style="vertical-align: text-bottom;" src="{media '/idf/img/add.png'}" alt="+" align="bottom" /></a> <a href="{$url}">{trans 'New Download'}</a></p>{/if} <p><a href="{$url}"><img style="vertical-align: text-bottom;" src="{media '/idf/img/add.png'}" alt="+" align="bottom" /></a> <a href="{$url}">{trans 'New Download'}</a></p>{/if}
{/block} {/block}

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