Merge branch 'develop' into feature.issue-due-date

This commit is contained in:
Simon Holywell 2012-03-22 11:01:34 +00:00
commit fa95cbd934
116 changed files with 6952 additions and 1354 deletions

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

@ -1,15 +1,62 @@
# InDefero 1.3 - xxx xxx xx xx:xx:xx UTC 201X # 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 ## 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 ## Bugfixes
- Ensure that IDF does not break UTF-8 encoded strings when - Ensure that IDF does not break UTF-8 encoded strings when
shortening them for view rendering (issue 785) shortening them for view rendering (issue 785)
- Indefero no longer confuses a non-owner of an issue with a notification that
## Documentation a particular ticket has been opened and assigned to him (fixes issue 562)
## Translations
# InDefero 1.2.1 - XXX XXX XX XX:XX:XX UTC 201X # InDefero 1.2.1 - XXX XXX XX XX:XX:XX UTC 201X

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

@ -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', '')) { $current_locale = Pluf_Translation::getLocale();
return;
$from_email = Pluf::f('from_email');
$recipients = $project->getNotificationRecipientsForTab('source');
foreach ($recipients as $address => $language) {
if (!empty($this->author) && $this->author->email === $address) {
continue;
} }
$current_locale = Pluf_Translation::getLocale(); Pluf_Translation::loadSetLocale($language);
$langs = Pluf::f('languages', array('en'));
Pluf_Translation::loadSetLocale($langs[0]);
$context = new Pluf_Template_Context( $context = new Pluf_Template_Context(array(
array( 'commit' => $this,
'c' => $this, 'project' => $project,
'project' => $this->get_project(),
'url_base' => Pluf::f('url_base'), 'url_base' => Pluf::f('url_base'),
) ));
);
$tmpl = new Pluf_Template('idf/source/commit-created-email.txt'); // 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); $text_email = $tmpl->render($context);
$addresses = explode(',', $conf->getVal('source_notification_email'));
foreach ($addresses as $address) { $email = new Pluf_Mail($from_email,
$email = new Pluf_Mail(Pluf::f('from_email'),
$address, $address,
sprintf(__('New Commit %1$s - %2$s (%3$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);
} }
} }

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;
@ -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 (array_key_exists($key, $this->cleaned_data)) {
if (!empty($this->cleaned_data[$key])) { if (!empty($this->cleaned_data[$key])) {
$this->project->getConf()->setVal($key, $this->cleaned_data[$key]); $conf->setVal($key, $this->cleaned_data[$key]);
}
else {
$conf->delVal($key);
}
} }
} }
} }

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);
@ -119,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

@ -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 webhooks</a>.'), $url),
'widget_attrs' => array('size' => 35), 'widget_attrs' => array('size' => 35),
)); ));

View File

@ -57,21 +57,45 @@ 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', $sections = array(
'wiki_notification_email', 'downloads_notification',
'source_notification_email', 'review_notification',
'issues_notification_email',); 'wiki_notification',
foreach ($ak as $key) { 'source_notification',
$this->fields[$key] = new IDF_Form_Field_EmailList( 'issues_notification',
);
foreach ($sections as $section) {
$this->fields[$section.'_owners_enabled'] = new Pluf_Form_Field_Boolean(
array('required' => false, array('required' => false,
'label' => $key, 'label' => __('Project owners'),
'initial' => $this->conf->getVal($key, ''), 'initial' => $this->conf->getVal($section.'_owners_enabled', false),
'widget_attrs' => array('size' => 40), '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,
'label' => __('Private project'), 'label' => __('Private project'),

View File

@ -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]
* *

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'])) {
@ -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 one 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.'));
} }
} }

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

@ -64,6 +64,14 @@ Deprecated = Most users should NOT download this';
'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

@ -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;
@ -109,7 +109,7 @@ Add your content here. Format your content with:
} }
$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.'));
} }
@ -183,7 +183,7 @@ Add your content here. Format your content with:
} }
} }
// 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,7 +27,7 @@
* 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;
@ -120,7 +120,7 @@ class IDF_Form_WikiUpdate extends Pluf_Form
} }
$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.'));
} }
@ -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

@ -263,91 +263,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) {
$email_lang = array($this->get_owner()->email,
$this->get_owner()->language);
if (!in_array($email_lang, $to_email)) {
$to_email[] = $email_lang;
}
}
$comments = $this->get_comments_list(array('order' => 'id ASC'));
$context = new Pluf_Template_Context(
array(
'issue' => $this,
'comment' => $comments[0],
'project' => $prj,
'url_base' => Pluf::f('url_base'),
)
);
foreach ($to_email as $email_lang) {
Pluf_Translation::loadSetLocale($email_lang[1]);
$email = new Pluf_Mail(Pluf::f('from_email'), $email_lang[0],
sprintf(__('Issue %1$s - %2$s (%3$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')); $comments = $this->get_comments_list(array('order' => 'id DESC'));
$email_sender = ''; $messageId = '<'.md5('issue'.$this->id.md5(Pluf::f('secret_key'))).'@'.Pluf::f('mail_host', 'localhost').'>';
if (isset($comments[0])) { $recipients = $project->getNotificationRecipientsForTab('issues');
$email_sender = $comments[0]->get_submitter()->email;
// the submitter (might be skipped later on if he is the one who also
// submitted the last comment)
if (!array_key_exists($this->get_submitter()->email, $recipients)) {
$recipients[$this->get_submitter()->email] = $this->get_submitter()->language;
} }
// the owner of the issue, if we have one
$owner = $this->get_owner();
if (null != $owner && !array_key_exists($owner->email, $recipients)) {
$recipients[$owner->email] = $owner->language;
}
// additional users who starred the issue
foreach ($this->get_interested_list() as $interested) { foreach ($this->get_interested_list() as $interested) {
$email_lang = array($interested->email, if (array_key_exists($interested->email, $recipients))
$interested->language); continue;
if (!in_array($email_lang, $to_email)) { $recipients[$interested->email] = $interested->language;
$to_email[] = $email_lang;
} }
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;
} }
$email_lang = array($this->get_submitter()->email,
$this->get_submitter()->language); Pluf_Translation::loadSetLocale($language);
if (!in_array($email_lang, $to_email)) {
$to_email[] = $email_lang; $context = new Pluf_Template_Context(array(
}
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, 'issue' => $this,
'owns_issue' => $owner !== null && $owner->email === $address,
// the initial comment for create, the last for update
'comment' => $comments[0],
'comments' => $comments, 'comments' => $comments,
'project' => $prj, 'project' => $project,
'url_base' => Pluf::f('url_base'), 'url_base' => Pluf::f('url_base'),
)); ));
foreach ($to_email as $email_lang) {
if ($email_lang[0] == $email_sender) { $tplfile = 'idf/issues/issue-created-email.txt';
continue; // Do not notify the one having created $subject = __('Issue %1$s - %2$s (%3$s)');
// the comment $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);
} }
Pluf_Translation::loadSetLocale($email_lang[1]);
$email = new Pluf_Mail(Pluf::f('from_email'), $email_lang[0], $tmpl = new Pluf_Template($tplfile);
sprintf(__('Updated Issue %1$s - %2$s (%3$s)'), $text_email = $tmpl->render($context);
$this->id, $this->summary, $prj->shortname));
$tmpl = new Pluf_Template('idf/issues/issue-updated-email.txt'); $email = new Pluf_Mail($from_email, $address,
$email->addTextMessage($tmpl->render($context)); sprintf($subject, $this->id, $this->summary, $project->shortname));
$email->addHeaders(array('References'=>$id)); $email->addTextMessage($text_email);
$email->addHeaders($headers);
$email->sendMail(); $email->sendMail();
} }
}
Pluf_Translation::loadSetLocale($current_locale); Pluf_Translation::loadSetLocale($current_locale);
} }
} }

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

@ -86,6 +86,13 @@ class IDF_Project extends Pluf_Model
'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(
'type' => 'Pluf_DB_Field_Integer', 'type' => 'Pluf_DB_Field_Integer',
@ -93,6 +100,28 @@ 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.
* *
@ -608,7 +653,7 @@ GROUP BY uid";
$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) {
@ -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

@ -178,47 +178,60 @@ class IDF_Review_Comment extends Pluf_Model
$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();
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(); $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( $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, 'review' => $review,
'patch' => $patch, 'patch' => $patch,
'comments' => $comments, 'comments' => $comments,
'gcomments' => $gcomments, 'gcomments' => $gcomments,
'project' => $prj, 'project' => $prj,
'url_base' => Pluf::f('url_base'), '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 %1$s - %2$s (%3$s)'),
$review->id, $review->summary, $prj->shortname));
$email->addTextMessage($tmpl->render($context)); // 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

@ -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');
foreach ($recipients as $address => $language) {
if ($review->get_submitter()->email === $address) {
continue;
}
Pluf_Translation::loadSetLocale($language);
$context = new Pluf_Template_Context(array(
'review' => $review,
'patch' => $this, 'patch' => $this,
'comments' => array(), 'comments' => array(),
'project' => $this->get_review()->get_project(), 'project' => $project,
'url_base' => Pluf::f('url_base'), 'url_base' => Pluf::f('url_base'),
) ));
);
$tmpl = new Pluf_Template('idf/review/review-created-email.txt'); // 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); $text_email = $tmpl->render($context);
$addresses = explode(';',$conf->getVal('review_notification_email'));
foreach ($addresses as $address) { $email = new Pluf_Mail($from_email,
$email = new Pluf_Mail(Pluf::f('from_email'),
$address, $address,
sprintf(__('New Code Review %1$s - %2$s (%3$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

@ -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.
@ -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

@ -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

@ -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(), }
$submitter = $this->get_submitter();
$payload = array(
'to_send' => array(
'project' => $project->shortname,
'id' => $this->id,
'summary' => $this->summary,
'changelog' => $this->changelog,
'filename' => $this->file,
'filesize' => $this->filesize,
'md5sum' => $this->md5,
'submitter_login' => $submitter->login,
'submitter_email' => $submitter->email,
'tags' => $tags,
),
'project_id' => $project->id,
'authkey' => $project->getWebHookKey(),
'url' => $url,
);
if ($create === true) {
$payload['method'] = 'PUT';
$payload['to_send']['creation_date'] = $this->creation_dtime;
} else {
$payload['method'] = 'POST';
$payload['to_send']['update_date'] = $this->modif_dtime;
}
$item = new IDF_Queue();
$item->type = 'upload';
$item->payload = $payload;
$item->create();
$current_locale = Pluf_Translation::getLocale();
$from_email = Pluf::f('from_email');
$messageId = '<'.md5('upload'.$this->id.md5(Pluf::f('secret_key'))).'@'.Pluf::f('mail_host', 'localhost').'>';
$recipients = $project->getNotificationRecipientsForTab('downloads');
foreach ($recipients as $address => $language) {
if ($this->get_submitter()->email === $address) {
continue;
}
Pluf_Translation::loadSetLocale($language);
$context = new Pluf_Template_Context(array(
'file' => $this,
'urlfile' => $this->getAbsoluteUrl($project),
'project' => $project,
'tags' => $this->get_tags_list(), 'tags' => $this->get_tags_list(),
)); ));
$tmpl = new Pluf_Template('idf/downloads/download-created-email.txt');
$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); $text_email = $tmpl->render($context);
$addresses = explode(',', $conf->getVal('downloads_notification_email'));
foreach ($addresses as $address) { $email = new Pluf_Mail($from_email,
$email = new Pluf_Mail(Pluf::f('from_email'),
$address, $address,
sprintf(__('New download - %1$s (%2$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');
return new Pluf_HTTP_Response_Redirect($url);
}
if ($api == true) return $projects;
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);
} }
@ -292,6 +339,22 @@ class IDF_Views
} }
/**
* Download archive FAQ.
*/
public function faqArchiveFormat($request, $match)
{
$title = __('InDefero Upload Archive Format');
$projects = self::getProjects($request->user);
return Pluf_Shortcuts_RenderToResponse('idf/faq-archive-format.html',
array(
'page_title' => $title,
'projects' => $projects,
),
$request);
}
/** /**
* API FAQ. * API FAQ.
*/ */
@ -309,43 +372,57 @@ 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);
} else
if (!$user->administrator) {
// grab the list of projects where the user is admin,
// member or authorized
$perms = array( $perms = array(
Pluf_Permission::getFromString('IDF.project-member'), Pluf_Permission::getFromString('IDF.project-member'),
Pluf_Permission::getFromString('IDF.project-owner'), Pluf_Permission::getFromString('IDF.project-owner'),
Pluf_Permission::getFromString('IDF.project-authorized-user') Pluf_Permission::getFromString('IDF.project-authorized-user')
); );
$sql = new Pluf_SQL("model_class='IDF_Project' AND owner_class='Pluf_User' AND owner_id=%s AND negative=".$false, $user->id); $permSql = new Pluf_SQL("model_class='IDF_Project' AND owner_class='Pluf_User' AND owner_id=%s AND negative=".$false, $user->id);
$rows = Pluf::factory('Pluf_RowPermission')->getList(array('filter' => $sql->gen())); $rows = Pluf::factory('Pluf_RowPermission')->getList(array('filter' => $permSql->gen()));
$sql = sprintf('%s=%s', $db->qn('private'), $false); $authSql = new Pluf_SQL('private=%s', $false);
if ($rows->count() > 0) { if ($rows->count() > 0) {
$ids = array(); $ids = array();
foreach ($rows as $row) { foreach ($rows as $row) {
$ids[] = $row->model_id; $ids[] = $row->model_id;
} }
$sql .= sprintf(' OR id IN (%s)', implode(', ', $ids)); $authSql->SOr(new Pluf_SQL(sprintf($db->pfx.'idf_projects.id IN (%s)', implode(', ', $ids))));
} }
return Pluf::factory('IDF_Project')->getList(array('filter' => $sql, $sql->SAnd($authSql);
'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],
));
} }
/** /**
@ -365,7 +442,7 @@ class IDF_Views
// 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'];
@ -373,9 +450,6 @@ class IDF_Views
$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')

View File

@ -32,17 +32,37 @@ 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);
} }
@ -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_merge(
array( array(
'page_title' => $title, 'page_title' => $title,
'project' => $project, 'project' => $project,
'form' => $form, '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_merge(
array( array(
'page_title' => $title, 'page_title' => $title,
'form' => $form, 'form' => $form,
'base_url' => $base, 'base_url' => $base,
), ),
$arrays
),
$request); $request);
} }

View File

@ -93,8 +93,7 @@ class IDF_Views_Api
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) {

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

@ -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_merge(
array( array(
'page_title' => $title, 'page_title' => $title,
'form' => $form, 'form' => $form,
'project' => $prj, 'project' => $prj,
'logo' => $logo, '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

@ -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');

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="%1$s">%2$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',
'IDF_Precondition::projectMemberOrOwner');
public function deleteResourceRev($request, $match)
{
$prj = $request->project;
$oldrev = Pluf_Shortcuts_GetObjectOr404('IDF_Wiki_ResourceRevision', $match[2]);
$resource = $oldrev->get_wikiresource();
$prj->inOr404($resource);
if ($oldrev->is_head == true) {
return new Pluf_HTTP_Response_NotFound($request);
}
if ($request->method == 'POST') {
$oldrev->delete();
$request->user->setMessage(__('The old revision has been deleted.'));
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::viewResource',
array($prj->shortname, $resource->title));
return new Pluf_HTTP_Response_Redirect($url);
}
$title = sprintf(__('Delete Old Revision of %s'), $resource->title);
$false = Pluf_DB_BooleanToDb(false, $resource->getDbConnection());
$revs = $resource->get_revisions_list(array('order' => 'creation_dtime DESC',
'filter' => 'is_head='.$false));
return Pluf_Shortcuts_RenderToResponse('idf/wiki/deleteResourceRev.html',
array(
'page_title' => $title,
'resource' => $resource,
'oldrev' => $oldrev,
'revs' => $revs,
),
$request);
}
/**
* Update a documentation page.
*/
public $updatePage_precond = array('IDF_Precondition::accessWiki',
'Pluf_Precondition::loginRequired'); 'Pluf_Precondition::loginRequired');
public function update($request, $match) 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,13 +488,14 @@ 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="%1$s">%2$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'])) {
@ -328,9 +503,9 @@ class IDF_Views_Wiki
} }
} 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;
} }

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",
) )
); );
@ -76,7 +82,7 @@ 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;
} }
@ -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__;
@ -113,12 +113,12 @@ class IDF_WikiPage extends Pluf_Model
'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',
), ),
); );
} }
@ -185,7 +185,7 @@ class IDF_WikiPage extends Pluf_Model
*/ */
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.'">'.
@ -202,7 +202,7 @@ class IDF_WikiPage extends Pluf_Model
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(__('%1$s: Documentation page %2$s added - %3$s'), $title = sprintf(__('%1$s: Documentation page %2$s added - %3$s'),
@ -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')
.Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::viewPage',
array($request->project->shortname, array($request->project->shortname,
$page->title)); $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>';
@ -193,18 +222,12 @@ class IDF_WikiRevision extends Pluf_Model
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::view', .Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::viewPage',
array($request->project->shortname, array($request->project->shortname,
$page->title), $page->title),
array('rev' => $this->id)); array('rev' => $this->id));
} else {
$url = Pluf::f('url_base')
.Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::view',
array($request->project->shortname,
$page->title));
}
$title = sprintf(__('%1$s: Documentation page %2$s updated - %3$s'), $title = sprintf(__('%1$s: Documentation page %2$s updated - %3$s'),
$request->project->name, $request->project->name,
$page->title, $page->summary); $page->title, $page->summary);
@ -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]); $from_email = Pluf::f('from_email');
$context = new Pluf_Template_Context( $messageId = '<'.md5('wiki'.$wikipage->id.md5(Pluf::f('secret_key'))).'@'.Pluf::f('mail_host', 'localhost').'>';
array( $recipients = $project->getNotificationRecipientsForTab('wiki');
'page' => $this->get_wikipage(),
'rev' => $this, foreach ($recipients as $address => $language) {
'project' => $this->get_wikipage()->get_project(),
'url_base' => Pluf::f('url_base'), if ($this->get_submitter()->email === $address) {
) continue;
);
if ($create) {
$template = 'idf/wiki/wiki-created-email.txt';
$title = sprintf(__('New Documentation Page %1$s - %2$s (%3$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 %1$s - %2$s (%3$s)'),
$this->get_wikipage()->title,
$this->get_wikipage()->summary,
$this->get_wikipage()->get_project()->shortname);
} }
$tmpl = new Pluf_Template($template);
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); $text_email = $tmpl->render($context);
$addresses = explode(',', $conf->getVal('wiki_notification_email')); $email = new Pluf_Mail($from_email,
foreach ($addresses as $address) {
$email = new Pluf_Mail(Pluf::f('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

@ -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

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 Emails'}</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(){ }
$("#id_private_project").click(function() {
if ($("#id_private_project").is(":checked")) { if ($("#id_private_project").is(":checked")) {
$("#authorized-users-row").show(); $("#authorized-users-row").show();
} else { } else {
$("#authorized-users-row").hide(); $("#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 {
$("#id_" + section + "_notification_email").hide();
}
}); });
});
});
</script> </script>
{/literal}{/block} {/literal}{/block}

View File

@ -39,14 +39,14 @@
<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

@ -39,14 +39,14 @@
<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}

View File

@ -0,0 +1,125 @@
{extends "idf/base-simple.html"}
{block docclass}yui-t3{/block}
{block body}
<p>At the moment, this documentation is only available in English.</p>
<ul>
<li><a href="#q-motivation">Motivation</a></li>
<li><a href="#q-manifest">The manifest format</a></li>
<li><a href="#q-notes">Final notes</a></li>
</ul>
<h2 id="q-motivation">Motivation</h2>
<p>
Adding multiple, individual downloads to a project for a release can be a tedious task if
one has to select each file manually, and then has to fill in the summary and correct labels
for each of these downloads individually.
</p>
<p>
InDefero therefore supports the upload of "archives" that contain multiple downloadable
files. These archives are standard PKZIP files with only one special property - they
contain an additional manifest file which describes the files that should be published.
</p>
<p>
Once such an archive has been uploaded and validated by InDefero, its files are extracted
and individual downloads are created for each of them. If the archive contains files
that should replace existing downloads, then InDefero takes care of this as well -
automatically. Files that exist in the archive but are not listed in the manifest are
not extracted.
</p>
<p>
An archive file and its manifest file can easily be compiled, either by hand with the help
of a text editor, or through an automated build system with the help of your build tool of
choice, such as Apache Ant.
</p>
<h2 id="q-manifest">The manifest format</h2>
<p>
The manifest is an XML file that follows a simple syntax. As it is always easier to look
at an example, here you have one:
</p>
<pre>
&lt;?xml version="1.0" encoding="UTF-8" ?&gt;
&lt;manifest>
&lt;file>
&lt;name>foo-1.2.tar.gz&lt;/name>
&lt;summary>Tarball&lt;/summary>
&lt;replaces>foo-1.1.tar.gz&lt;/replaces>
&lt;labels>
&lt;label>Type:Archive&lt;/label>
&lt;/labels>
&lt;/file>
&lt;file>
&lt;name>foo-1.2-installer.exe&lt;/name>
&lt;summary>Windows MSI Installer&lt;/summary>
&lt;description>This installer needs Windows XP SP2 or later.&lt;/description>
&lt;labels>
&lt;label>Type:Installer&lt;/label>
&lt;label>OpSys:Windows&lt;/label>
&lt;/labels>
&lt;/file>
&lt;/manifest>
</pre>
<p>
This is the DTD for the format:
</p>
<pre>
&lt;!DOCTYPE manifest [
&lt;!ELEMENT manifest (file+)>
&lt;!ELEMENT file (name,summary,replaces?,description?,tags?)>
&lt;!ELEMENT name (#PCDATA)>
&lt;!ELEMENT summary (#PCDATA)>
&lt;!ELEMENT replaces (#PCDATA)>
&lt;!ELEMENT description (#PCDATA)>
&lt;!ELEMENT labels (label+)>
&lt;!ELEMENT label (#PCDATA)>
]>
</pre>
<p>
The format is more or less self-explaining, all fields map to properties of a single download. Note
that there is a limit of <strong>six</strong> labels that you can attach to a download, similar to
what the regular file upload functionality allows.
</p>
<p>One special element has been introduced and this is named <code>replaces</code>. If this optional element
is given, InDefero looks for a file with that name in the project and acts in one of the following
two ways:
</p>
<ul>
<li>If the new file's name is distinct from the file's name that is given in <code>replaces</code>,
then the replaced file is <strong>marked as deprecated</strong> with the <code>Other:Deprecated</code> label
(or any other label that is configured as the very last label in the download project configuration).</li>
<li>If the new file's name is equal to the file's name that is given in <code>replaces</code>,
then the replaced file <strong>is deleted</strong> before the new file is created. This happens because each file name
has to be unique per project and a deprecated and a new file with the same name cannot coexist in InDefero.
Note that while the filename-based URI of the download keeps intact, the download counter will be reset by
this procedure.</li>
</ul>
If no existing file is found for the <code>replaces</code> tag, then this is completely ignored.
</p>
<h2 id="q-notes">Final notes</h2>
<p>Any file in the archive that is not enlisted in the manifest is ignored during
the upload process, so ensure that your <code>manifest.xml</code> contains all the file names (case
sensitive) you want to be processed.</p>
{/block}
{block context}
<p>{trans 'Here we are, just to help you.'}</p>
<h2>{trans 'Projects'}</h2>
<ul>{foreach $projects as $p}
<li><a href="{url 'IDF_Views_Project::home', array($p.shortname)}">{$p}</a></li>
{/foreach}</ul>
{/block}

View File

@ -5,6 +5,8 @@
<li><a href="#q-keyboard">{trans 'What are the keyboard shortcuts?'}</a></li> <li><a href="#q-keyboard">{trans 'What are the keyboard shortcuts?'}</a></li>
<li><a href="#q-duplicate">{trans 'How to mark an issue as duplicate?'}</a></li> <li><a href="#q-duplicate">{trans 'How to mark an issue as duplicate?'}</a></li>
<li><a href="#q-mugshot">{trans 'How can I display my head next to my comments?'}</a></li> <li><a href="#q-mugshot">{trans 'How can I display my head next to my comments?'}</a></li>
<li><a href="#q-wiki">{trans 'How can I embed images and other resources in my documentation pages?'}</a></li>
<li><a href="#q-archive-format">{trans 'What is this "Upload Archive" functionality about?'}</a></li>
<li><a href="#q-api">{trans 'What is the API and how is it used?'}</a></li> <li><a href="#q-api">{trans 'What is the API and how is it used?'}</a></li>
</ul> </ul>
@ -39,7 +41,9 @@
{blocktrans}<p>This is simple:</p> {blocktrans}<p>This is simple:</p>
<ol> <ol>
<li>Write in the comments "This is a duplicate of issue 123", change 123 with the corresponding issue number.</li> <li>Write in the comments "This is a duplicate of issue 123" or - if you are a member of the crew -
directly add the "duplicates" relation with the value "123" below the comment field. Change "123"
with the corresponding issue number.</li>
<li>Change the status of the current issue to <em>Duplicate</em>.</li> <li>Change the status of the current issue to <em>Duplicate</em>.</li>
<li>Submit the changes.</li> <li>Submit the changes.</li>
</ol>{/blocktrans} </ol>{/blocktrans}
@ -48,9 +52,43 @@
<p>{blocktrans}You need to create an account on <a href="http://en.gravatar.com/">Gravatar</a>, this takes about 5 minutes and is free.{/blocktrans}</p> <p>{blocktrans}You need to create an account on <a href="http://en.gravatar.com/">Gravatar</a>, this takes about 5 minutes and is free.{/blocktrans}</p>
<h2 id="q-wiki">{trans 'How can I embed images and other resources in my documentation pages?'}</h2>
{blocktrans}
<p>To embed any previously uploaded resource into your wiki page, you can use the <code>[[!ResourceName]]</code> syntax.</p>
<p>The rendering of the resource can then be further fine-tuned:
<ul>
<li><code>[[!ImageResource, align=right, width=200]]</code> renders "ImageResource" right-aligned and scale its width to 200</li>
<li><code>[[!TextResource, align=center, width=300, height=300]]</code> renders "TextResource" in a centered, 300 by 300 px iframe</li>
<li><code>[[!AnyResource, preview=no]]</code> does not render a preview of the resource, but only provides a download link (default for binary resources)</li>
<li><code>[[!BinaryResource, title=Download]]</code> renders the download link of "BinaryResource" with an alternative title</li>
</ul>
</p>
Resources are versioned, just like wiki pages. If you update a resource, old wiki pages still show the state of the resource
at the time when the wiki page was edited. If you specifically want to update a resource on a page, you therefor need to update
the resource at first and then also the page where it is referenced, otherwise the change won't be visible until the next regular edit.
{/blocktrans}
<h2 id="q-archive-format">{trans 'What is this "Upload Archive" functionality about?'}</h2>
{blocktrans}<p>If you have to publish many files at once for a new release, it is a very tedious task
to upload them one after another and enter meta information like a summary, a description or additional
labels for each of them.</p>
<p>InDefero therefore supports a special archive format that is basically a standard zip file which comes with
some meta information. These meta information are kept in a special manifest file, which is distinctly kept from
the rest of the files in the archive that should be published.</p>
<p>Once this archive has been uploaded, InDefero reads in the meta information, unpacks the other files from
the archive and creates new individual downloads for each of them.</p>{/blocktrans}
{aurl 'url', 'IDF_Views::faqArchiveFormat'}
<p>{blocktrans}<a href="{$url}">Learn more about the archive format</a>.{/blocktrans}</p>
<h2 id="q-api">{trans 'What is the API and how is it used?'}</h2> <h2 id="q-api">{trans 'What is the API and how is it used?'}</h2>
<p>{blocktrans}The API (Application Programming Interface) is used to interact with InDefero with another program. For example, this can be used to create a desktop program to submit new tickets easily.{/blocktrans}</p>{aurl 'url', 'IDF_Views::faqApi'} <p>{blocktrans}The API (Application Programming Interface) is used to interact with InDefero with another program. For example, this can be used to create a desktop program to submit new tickets easily.{/blocktrans}</p>
{aurl 'url', 'IDF_Views::faqApi'}
<p>{blocktrans}<a href="{$url}">Learn more about the API</a>.{/blocktrans}</p> <p>{blocktrans}<a href="{$url}">Learn more about the API</a>.{/blocktrans}</p>
{/block} {/block}

View File

@ -39,6 +39,7 @@
{include 'idf/main-menu.html'} {include 'idf/main-menu.html'}
<div id="header"> <div id="header">
<div id="main-tabs"> <div id="main-tabs">
<a href="{url 'IDF_Views_Admin::forge'}"{block tabforge}{/block}>{trans 'Forge'}</a>
<a href="{url 'IDF_Views_Admin::projects'}"{block tabprojects}{/block}>{trans 'Projects'}</a> <a href="{url 'IDF_Views_Admin::projects'}"{block tabprojects}{/block}>{trans 'Projects'}</a>
<a href="{url 'IDF_Views_Admin::users'}"{block tabusers}{/block}>{trans 'People'}</a> <a href="{url 'IDF_Views_Admin::users'}"{block tabusers}{/block}>{trans 'People'}</a>
{if $usherConfigured} {if $usherConfigured}

View File

@ -0,0 +1,5 @@
{extends "idf/gadmin/base.html"}
{block tabforge} class="active"{/block}
{block subtabs}
<a {if $inIndex}class="active" {/if}href="{url 'IDF_Views_Admin::forge'}">{trans 'Frontpage'}</a>
{/block}

View File

@ -0,0 +1,65 @@
{extends "idf/gadmin/forge/base.html"}
{block docclass}yui-t3{assign $inIndex=true}{/block}
{block body}
<form method="post" action=".">
<table class="form" summary="">
<colgroup>
<col width="20" />
<col width="*" />
</colgroup>
<tr>
<td>{$form.f.enabled|unsafe}</td>
<td>
{$form.f.enabled.labelTag}
{if $form.f.enabled.errors}{$form.f.enabled.fieldErrors}{/if}
</td>
</tr>
<tr>
<td colspan="2">
{if $form.f.content.errors}{$form.f.content.fieldErrors}{/if}
{$form.f.content|unsafe}
</td>
<tr>
<td colspan="2">
<input type="submit" value="{trans 'Save Changes'}" name="submit" />
</td>
</tr>
</table>
</form>
<script type="text/javascript" charset="utf-8">
// <!-- {literal}
$(document).ready(function() {
var handler = function() {
if ($(this).is(':checked')) {
$('#id_content').removeAttr('readonly');
} else {
$('#id_content').attr('readonly', 'readonly');
}
};
$('#id_enabled').bind('click', handler);
handler.call($('#id_enabled'));
});
// {/literal} -->
</script>
{/block}
{block context}
{assign $eurl = 'http://michelf.com/projects/php-markdown/extra/'}
{assign $burl = 'http://daringfireball.net/projects/markdown/syntax'}
<div class="issue-submit-info">
{blocktrans}
<p><strong>Instructions:</strong></p>
<p>You can set up a custom forge page that is used as entry page for the forge instead of the plain project listing. This page is then also accessible via the 'Home' link in main menu bar.</p>
<p>The content of the page can use the <a href="{$burl}">Markdown syntax</a> with the <a href="{$eurl}"><em>Extra</em> extension</a>.</p>
<p>Additionally, the following macros are available:<br />
<ul>
<li><code>&#x7b;projectlist, label=..., order=(name|activity), limit=...}</code> - Renders a project list that can optionally be filtered by label, ordered by 'name' or 'activity' and / or limited to a specific number of projects.</li>
</ul>
</p>
{/blocktrans}
</div>
{/block}

View File

@ -1,14 +0,0 @@
{extends "idf/gadmin/base.html"}
{block docclass}yui-t2{/block}
{block tabhome} class="active"{/block}
{block subtabs}
{trans 'Welcome'}
{/block}
{block body}
<p>{blocktrans}You have here access to the administration of the forge.{/blocktrans}</p>
{/block}
{block context}
{/block}
{block foot}<div id="branding">Powered by <a href="http://www.indefero.net" title="InDefero, bug tracking and more">InDefero</a>,<br />a <a href="http://www.ceondo.com">Céondo Ltd</a> initiative.</div>{/block}

View File

@ -2,5 +2,6 @@
{block tabprojects} class="active"{/block} {block tabprojects} class="active"{/block}
{block subtabs} {block subtabs}
<a {if $inIndex}class="active" {/if}href="{url 'IDF_Views_Admin::projects'}">{trans 'Project List'}</a> | <a {if $inIndex}class="active" {/if}href="{url 'IDF_Views_Admin::projects'}">{trans 'Project List'}</a> |
<a {if $inLabels}class="active" {/if}href="{url 'IDF_Views_Admin::projectLabels'}">{trans 'Project Labels'}</a> |
<a {if $inCreate}class="active" {/if}href="{url 'IDF_Views_Admin::projectCreate'}">{trans 'Create Project'}</a> {if $project} | <a href="{url 'IDF_Views_Project::admin', array($project.shortname)}">{trans 'Change Project Details'}</a>{/if} <a {if $inCreate}class="active" {/if}href="{url 'IDF_Views_Admin::projectCreate'}">{trans 'Create Project'}</a> {if $project} | <a href="{url 'IDF_Views_Project::admin', array($project.shortname)}">{trans 'Change Project Details'}</a>{/if}
{/block} {/block}

View File

@ -37,6 +37,12 @@
</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.scm.labelTag}:</strong></th> <th><strong>{$form.f.scm.labelTag}:</strong></th>
<td>{if $form.f.scm.errors}{$form.f.scm.fieldErrors}{/if} <td>{if $form.f.scm.errors}{$form.f.scm.fieldErrors}{/if}
{$form.f.scm|unsafe} {$form.f.scm|unsafe}
@ -75,6 +81,17 @@
</td> </td>
</tr> </tr>
<tr class="no-template"> <tr class="no-template">
<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}<br />
{if $form.f.label3.errors}{$form.f.label3.fieldErrors}{/if}{$form.f.label3|unsafe}
{if $form.f.label4.errors}{$form.f.label4.fieldErrors}{/if}{$form.f.label4|unsafe}<br />
{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 class="no-template">
<th><strong>{$form.f.owners.labelTag}:</strong></th> <th><strong>{$form.f.owners.labelTag}:</strong></th>
<td> <td>
{if $form.f.owners.errors}{$form.f.owners.fieldErrors}{/if} {if $form.f.owners.errors}{$form.f.owners.fieldErrors}{/if}
@ -103,7 +120,7 @@
</tr> </tr>
</table> </table>
</form> </form>
{include 'idf/project/js-autocomplete.html'}{/block}
{/block} {/block}
{block context} {block context}
@ -151,7 +168,7 @@ $(document).ready(function() {
} }
}); });
// Hide if not svn // Hide if not templated
if ($("#id_template option:selected").val() == "--") { if ($("#id_template option:selected").val() == "--") {
$(".no-template").show(); $(".no-template").show();
} else { } else {

View File

@ -0,0 +1,31 @@
{extends "idf/gadmin/projects/base.html"}
{block docclass}yui-t3{assign $inLabels=true}{/block}
{block body}
<form method="post" action=".">
<table class="form" summary="">
<tr>
<td colspan="2"><strong>{$form.f.project_labels.labelTag}:</strong><br />
{if $form.f.project_labels.errors}{$form.f.project_labels.fieldErrors}{/if}
{$form.f.project_labels|unsafe}
</td>
</tr>
<tr>
<td colspan="2">
<input type="submit" value="{trans 'Save Changes'}" name="submit" />
</td>
</tr>
</table>
</form>
{/block}
{block context}
<div class="issue-submit-info">
{blocktrans}
<p><strong>Instructions:</strong></p>
<p>List one status value per line in desired sort-order.</p>
<p>Optionally, use an equals-sign to document the meaning of each status value.</p>
{/blocktrans}
</div>
{/block}

View File

@ -25,6 +25,12 @@
<span class="helptext">{$form.f.shortdesc.help_text}</span> <span class="helptext">{$form.f.shortdesc.help_text}</span>
</td> </td>
</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>
{if $project.getConf().getVal('scm') == 'mtn'} {if $project.getConf().getVal('scm') == 'mtn'}
<tr class="mtn-form"> <tr class="mtn-form">
<th><strong>{$form.f.mtn_master_branch.labelTag}:</strong></th> <th><strong>{$form.f.mtn_master_branch.labelTag}:</strong></th>
@ -35,6 +41,17 @@
</tr> </tr>
{/if} {/if}
<tr> <tr>
<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}<br />
{if $form.f.label3.errors}{$form.f.label3.fieldErrors}{/if}{$form.f.label3|unsafe}
{if $form.f.label4.errors}{$form.f.label4.fieldErrors}{/if}{$form.f.label4|unsafe}<br />
{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><strong>{$form.f.owners.labelTag}:</strong></th> <th><strong>{$form.f.owners.labelTag}:</strong></th>
<td> <td>
{if $form.f.owners.errors}{$form.f.owners.fieldErrors}{/if} {if $form.f.owners.errors}{$form.f.owners.fieldErrors}{/if}
@ -60,7 +77,9 @@
</tr> </tr>
</table> </table>
</form> </form>
{include 'idf/project/js-autocomplete.html'}{/block}
{/block} {/block}
{block context} {block context}
<div class="issue-submit-info"> <div class="issue-submit-info">
{blocktrans} {blocktrans}

View File

@ -1,50 +1,16 @@
{extends "idf/base-simple.html"} {extends "idf/base-simple.html"}
{block docclass}yui-t2{/block} {block docclass}yui-t7{/block}
{block tabhome} class="active"{/block} {block tabhome} class="active"{/block}
{block subtabs}<a href="{url 'IDF_Views::index'}" class="active">{trans 'Projects'}</a>{/block} {block subtabs}
<a href="{url 'IDF_Views::index'}" class="active">{trans 'Home'}</a> |
<a href="{url 'IDF_Views::listProjects', array('all', 'name')}" class="active">{trans 'Projects'}</a>
{/block}
{block body} {block body}
{if $projects.count() == 0} <div class="forge-page">
<p>{trans 'No projects managed with InDefero were found.'}</p> {markdown_forge $content, $request}
{if $isAdmin}
{aurl 'url', 'IDF_Views_Admin::projectCreate'}
<p><a href="{$url}"><img style="vertical-align: text-bottom;" src="{media '/idf/img/add.png'}" alt="+" align="bottom" /></a> <a href="{$url}">{trans 'Create Project'}</a></p>{/if}
{else}
{foreach $projects as $p}
<div class="p-list-img">
<a href="{url 'IDF_Views_Project::home', array($p.shortname)}">
<img src="{url 'IDF_Views_Project::logo', array($p.shortname)}" alt="{trans 'Project logo'}" />
</a>
{if $p.private}
<div class="p-list-private">
<a href="{url 'IDF_Views_Project::home', array($p.shortname)}">
<img style="float:right" src="{media '/idf/img/lock.png'}" alt="{trans 'Private project'}" />
</a>
</div>
{/if}
</div>
<div class="p-list-prj">
<p>
<a href="{url 'IDF_Views_Project::home', array($p.shortname)}"><strong>{$p}</strong></a>
{if $p.private} - {trans 'Private project'}{/if}
</p>
<p>{$p.shortdesc}</p>
</div>
<div style="clear: both"></div>
{/foreach}
{/if}
{/block}
{block context}
<div id="stats" class="issue-submit-info">
<h3 class="a-c">{trans 'Forge statistics'}</h3>
<table>
<tr><td class="right">{trans 'Projects:'}</td><td>{$stats.projects}</td></tr>
<tr><td class="right">{trans 'Members:'}</td><td>{$stats.members}</td></tr>
<tr><td class="right">{trans 'Issues:'}</td><td>{$stats.issues}</td></tr>
<tr><td class="right">{trans 'Commits:'}</td><td>{$stats.commits}</td></tr>
<tr><td class="right">{trans 'Documentations:'}</td><td>{$stats.docpages}</td></tr>
<tr><td class="right">{trans 'Downloads:'}</td><td>{$stats.downloads}</td></tr>
<tr><td class="right">{trans 'Code reviews:'}</td><td>{$stats.reviews}</td></tr>
</table>
</div> </div>
{/block} {/block}
{block context}{/block}
{block foot}<div id="branding">Powered by <a href="http://www.indefero.net" title="InDefero, bug tracking and more">InDefero</a>,<br />a <a href="http://www.ceondo.com">Céondo Ltd</a> initiative.</div>{/block} {block foot}<div id="branding">Powered by <a href="http://www.indefero.net" title="InDefero, bug tracking and more">InDefero</a>,<br />a <a href="http://www.ceondo.com">Céondo Ltd</a> initiative.</div>{/block}

View File

@ -1,7 +1,6 @@
{trans 'Hello,'} {trans 'Hello,'}
{blocktrans}A new issue has been created and assigned {if $owns_issue}{blocktrans}A new issue has been created and assigned to you:{/blocktrans}{else}{blocktrans}A new issue has been created:{/blocktrans}{/if}
to you:{/blocktrans}
{$issue.id} - {$issue.summary|safe} {$issue.id} - {$issue.summary|safe}
{trans 'Project:'} {$project.name|safe} {trans 'Project:'} {$project.name|safe}

View File

@ -1,6 +1,6 @@
{trans 'Hello,'} {trans 'Hello,'}
{blocktrans}The following issue has been updated:{/blocktrans} {if $owns_issue}{blocktrans}The following issue you are owning has been updated:{/blocktrans}{else}{blocktrans}The following issue has been updated:{/blocktrans}{/if}
{$issue.id} - {$issue.summary|safe} {$issue.id} - {$issue.summary|safe}
{trans 'Project:'} {$project.name|safe} {trans 'Project:'} {$project.name|safe}

View File

@ -22,11 +22,11 @@
<a href="{$open_url}" class="label"><strong>{$tag.class}:</strong>{$tag.name}</a></p>{/blocktrans} <a href="{$open_url}" class="label"><strong>{$tag.class}:</strong>{$tag.name}</a></p>{/blocktrans}
{else} {else}
{* yes, this is duplicated from tags-cloud.html, but the code there cannot be easily overridden *} {* yes, this is duplicated from tags-cloud.html, but the code there cannot be easily overridden *}
<div id="tagscloud" class="smaller"><dl>{foreach $all_tags as $class => $labels} <dl class="tagscloud smaller">{foreach $all_tags as $class => $labels}
<dt class="label">{$class}</dt> <dt class="label">{$class}</dt>
{foreach $labels as $idx => $label} {foreach $labels as $idx => $label}
{aurl 'url', 'IDF_Views_Issue::searchLabel', array($project.shortname, $label.id, $status), array('q'=> $query)} {aurl 'url', 'IDF_Views_Issue::searchLabel', array($project.shortname, $label.id, $status), array('q'=> $query)}
<dd><a href="{$url}" class="label">{$label.name}{if $idx != count($labels) - 1},{/if}</a></dd> <dd><a href="{$url}" class="label">{$label.name}{if $idx != count($labels) - 1},{/if}</a></dd>
{/foreach}{/foreach}</dl></p> {/foreach}{/foreach}</dl>
{/if} {/if}
{/block} {/block}

View File

@ -7,7 +7,7 @@
{if $hasIssuesAccess}{hotkey 'Shift+a', 'IDF_Views_Issue::create', array($project.shortname)} {if $hasIssuesAccess}{hotkey 'Shift+a', 'IDF_Views_Issue::create', array($project.shortname)}
{hotkey 'Shift+i', 'IDF_Views_Issue::index', array($project.shortname)}{/if} {hotkey 'Shift+i', 'IDF_Views_Issue::index', array($project.shortname)}{/if}
{if $hasDownloadsAccess}{hotkey 'Shift+d', 'IDF_Views_Download::index', array($project.shortname)}{/if} {if $hasDownloadsAccess}{hotkey 'Shift+d', 'IDF_Views_Download::index', array($project.shortname)}{/if}
{if $hasWikiAccess}{hotkey 'Shift+o', 'IDF_Views_Wiki::index', array($project.shortname)}{/if} {if $hasWikiAccess}{hotkey 'Shift+o', 'IDF_Views_Wiki::listPages', array($project.shortname)}{/if}
{if $hasSourceAccess}{hotkey 'Shift+s', 'IDF_Views_Source::treeBase', array($project.shortname, $project.getScmRoot())}{/if} {if $hasSourceAccess}{hotkey 'Shift+s', 'IDF_Views_Source::treeBase', array($project.shortname, $project.getScmRoot())}{/if}
{if $hasIssuesAccess and !$user.isAnonymous()} {if $hasIssuesAccess and !$user.isAnonymous()}
{hotkey 'Shift+m', 'IDF_Views_Issue::userIssues', array($project.shortname, $user.login, 'submit')} {hotkey 'Shift+m', 'IDF_Views_Issue::userIssues', array($project.shortname, $user.login, 'submit')}

View File

@ -0,0 +1,60 @@
{extends "idf/base-simple.html"}
{block docclass}yui-t2{/block}
{block body}
{if $projects.count() == 0}
<p>{trans 'No projects managed with InDefero were found.'}</p>
{if $isAdmin}
{aurl 'url', 'IDF_Views_Admin::projectCreate'}
<p><a href="{$url}"><img style="vertical-align: text-bottom;" src="{media '/idf/img/add.png'}" alt="+" align="bottom" /></a> <a href="{$url}">{trans 'Create Project'}</a></p>{/if}
{else}
{include 'idf/project-list.html'}
{/if}
{/block}
{block context}
<strong>{trans 'Filter projects by label'}</strong>
{if count($projectLabels) == 0}
<p class="smaller">{trans 'No projects with labels found.'}</p>
{else}
<dl class="tagscloud smaller">{foreach $projectLabels as $class => $labels}
<dt class="label">{$class}</dt>
{foreach $labels as $idx => $label}
{* 0.75 - or 75% - is the minimum font size we'd like to see in this tag cloud *}
{assign $fontScale = round($label.rel_project_count * 100) + 75}
<dd><a href="{url 'IDF_Views::listProjectsByLabel', array($label->id, $order)}"
class="label"
title="{blocktrans $label.project_count}1 project{plural}{$label.project_count} projects{/blocktrans}"
style="font-size: {$fontScale}%">{$label.name}{if $idx != count($labels) - 1},{/if}</a></dd>
{/foreach}
{/foreach}</dl>
{if $tag}
<p class="smaller"><a href="{url 'IDF_Views::listProjectsByLabel', array('all', $order)}">
{blocktrans}Remove filter for {$tag}{/blocktrans}</a></p>
{/if}
{/if}
<br />
<strong>{trans 'Order'}</strong>
{assign $labelPart = 'all'}
{if $tag}{assign $labelPart = $tag->id}{/if}
<p class="smaller">
{if $order != 'name'}<a href="{url 'IDF_Views::listProjectsByLabel', array($labelPart, 'name')}">{/if}
{trans 'By name'}{if $order != 'name'}</a>{/if}
&ndash;
{if $order != 'activity'}<a href="{url 'IDF_Views::listProjectsByLabel', array($labelPart, 'activity')}">{/if}
{trans 'By activity'}{if $order != 'activity'}</a>{/if}
</p>
<br />
<strong>{trans 'Filtered project stats'}</strong>
<dl class="statistics smaller">
<dt>{trans 'Members:'}</dt><dd>{$stats.members}</dd>
<dt>{trans 'Issues:'}</dt><dd>{$stats.issues}</dd>
<dt>{trans 'Commits:'}</dt><dd>{$stats.commits}</dd>
<dt>{trans 'Documentations:'}</dt><dd>{$stats.docpages}</dd>
<dt>{trans 'Downloads:'}</dt><dd>{$stats.downloads}</dd>
<dt>{trans 'Code reviews:'}</dt><dd>{$stats.reviews}</dd>
</dl>
{/block}
{block foot}<div id="branding">Powered by <a href="http://www.indefero.net" title="InDefero, bug tracking and more">InDefero</a>,<br />a <a href="http://www.ceondo.com">Céondo Ltd</a> initiative.</div>{/block}

View File

@ -5,12 +5,14 @@
<li>{blocktrans}Welcome, <strong><a class="userw" href="{$url}">{$user}</a></strong>.{/blocktrans} <li>{blocktrans}Welcome, <strong><a class="userw" href="{$url}">{$user}</a></strong>.{/blocktrans}
<a href="{url 'IDF_Views::logout'}">{trans 'Sign Out'}</a></li>{else}<li> <a href="{url 'IDF_Views::logout'}">{trans 'Sign Out'}</a></li>{else}<li>
<a href="{url 'IDF_Views::login'}">{trans 'Sign in or create your account'}</a></li> <a href="{url 'IDF_Views::login'}">{trans 'Sign in or create your account'}</a></li>
{/if}<li id="project-list"><a href="{url 'IDF_Views::index'}">{trans 'Project List'} &#x25be;</a> {/if}{if $customForgePageEnabled}
<li><a href="{url 'IDF_Views::index'}">{trans 'Home'}</a></li>
{/if}<li id="project-list"><a href="{url 'IDF_Views::listProjects'}">{trans 'Project List'} &#x25be;</a>
{if $allProjects.count() != 0} {if $allProjects.count() != 0}
<ul>{foreach $allProjects as $p} <ul>{foreach $allProjects as $p}
<li><a href="{url 'IDF_Views_Project::home', array($p.shortname)}"><img class="logo" src="{url 'IDF_Views_Project::logo', array($p.shortname)}" alt="{trans 'Project logo'}" />{if $p.private}<img class="lock" src="{media '/idf/img/lock.png'}" alt="{trans 'Private project'}" />{/if}{$p}</a></li> <li><a href="{url 'IDF_Views_Project::home', array($p.shortname)}"><img class="logo" src="{url 'IDF_Views_Project::logo', array($p.shortname)}" alt="{trans 'Project logo'}" />{if $p.private}<img class="lock" src="{media '/idf/img/lock.png'}" alt="{trans 'Private project'}" />{/if}{$p}</a></li>
{/foreach}</ul> {/foreach}</ul>
{/if}</li>{if $isAdmin}<li><a href="{url 'IDF_Views_Admin::projects'}">{trans 'Forge Management'}</a></li>{/if}<li> {/if}</li>{if $isAdmin}<li><a href="{url 'IDF_Views_Admin::forge'}">{trans 'Forge Management'}</a></li>{/if}<li>
<a href="{url 'IDF_Views::faq'}" title="{trans 'Help and accessibility features'}">{trans 'Help'}</a></li> <a href="{url 'IDF_Views::faq'}" title="{trans 'Help and accessibility features'}">{trans 'Help'}</a></li>
</ul> </ul>

View File

@ -0,0 +1,41 @@
<div class="p-list">
{foreach $projects as $p}
<div class="p-list-prj">
<div class="logo">
<a href="{url 'IDF_Views_Project::home', array($p.shortname)}">
<img src="{url 'IDF_Views_Project::logo', array($p.shortname)}" alt="{trans 'Project logo'}" />
</a>
{if $p.private}
<div class="private">
<a href="{url 'IDF_Views_Project::home', array($p.shortname)}">
<img style="float:right" src="{media '/idf/img/lock.png'}" alt="{trans 'Private project'}" />
</a>
</div>
{/if}
{if $p.current_activity_value}
{assign $activity = $p.current_activity_value * 100}
<div class="activity" title="{blocktrans}Project activity: {$activity}%{/blocktrans}"><div class="bar" style="width: {$activity}%"></div></div>
{/if}
</div>
<p>
<a href="{url 'IDF_Views_Project::home', array($p.shortname)}"><strong>{$p}</strong></a>
{assign $url = $p.external_project_url}
{if $url != ''}
<a href="{$url}" target="_blank" class="external-link" title="{trans 'External link to project'}" />&nbsp;</a>
{/if}
{if $p.private} - <span class="smaller">{trans 'Private project'}</span>{/if}
</p>
<p class="smaller">{$p.shortdesc}</p>
<p class="smaller">{trans 'Labels:'}
{assign $tags = $p.get_tags_list()}
{if count($tags) == 0}{trans 'n/a'}{else}
{foreach $p.get_tags_list() as $idx => $label}
{if $idx != 0}, {/if}
<a class="label" href="{url 'IDF_Views::listProjectsByLabel', array($label->id, $order)}">{$label}</a>
{/foreach}
{/if}
</p>
</div>
{/foreach}
<div style="clear:both"></div>
</div>

View File

@ -20,9 +20,9 @@
{if count($pages) > 0} {if count($pages) > 0}
<p><strong>{trans 'Featured Documentation'}</strong><br /> <p><strong>{trans 'Featured Documentation'}</strong><br />
{foreach $pages as $page} {foreach $pages as $page}
<span class="label"><a href="{url 'IDF_Views_Wiki::view', array($project.shortname, $page.title)}" title="{$page.summary}">{$page.title}</a></span><br /> <span class="label"><a href="{url 'IDF_Views_Wiki::viewPage', array($project.shortname, $page.title)}" title="{$page.summary}">{$page.title}</a></span><br />
{/foreach} {/foreach}
<span class="label"> </span><span class="note"><a href="{url 'IDF_Views_Wiki::index', array($project.shortname)}" title="{trans 'Show more featured documentation'}">{trans 'show more...'}</a></span> <span class="label"> </span><span class="note"><a href="{url 'IDF_Views_Wiki::listPages', array($project.shortname)}" title="{trans 'Show more featured documentation'}">{trans 'show more...'}</a></span>
{/if} {/if}
{assign $ko = 'owners'} {assign $ko = 'owners'}
{assign $km = 'members'} {assign $km = 'members'}

View File

@ -0,0 +1,25 @@
<script type="text/javascript" src="{media '/idf/js/jquery.bgiframe.min.js'}"></script>
<script type="text/javascript" src="{media '/idf/js/jquery.autocomplete.min.js'}"></script>
<script type="text/javascript" charset="utf-8">
// <!-- {literal}
$(document).ready(function(){
var auto_labels = [{/literal}{$auto_labels|safe}{literal}];
for (j=1;j<7;j++) {
$("#id_label"+j).autocomplete(auto_labels, {
minChars: 0,
width: 310,
matchContains: true,
max: 50,
highlightItem: false,
formatItem: function(row, i, max, term) {
return row.to.replace(new RegExp("(" + term + ")", "gi"), "<strong>$1</strong>") + " <span style='font-size: 80%;'>" + row.name + "</span>";
},
formatResult: function(row) {
return row.to;
}
});
}
});
{/literal} //-->
</script>

View File

@ -2,16 +2,16 @@
{blocktrans}A new commit has been created:{/blocktrans} {blocktrans}A new commit has been created:{/blocktrans}
{$c.summary|safe} {$commit.summary|safe}
{trans 'Commit:'} {$c.scm_id|safe} {trans 'Commit:'} {$commit.scm_id|safe}
{trans 'Project:'} {$project.name|safe} {trans 'Project:'} {$project.name|safe}
{trans 'Created by:'} {$c.get_author|safe} {trans 'Created by:'} {$commit.get_author|safe}
{trans 'Created at:'} {$c.creation_dtime|date:"%Y-%m-%d %H:%M:%S"} {trans 'Created at:'} {$commit.creation_dtime|date:"%Y-%m-%d %H:%M:%S"}
{if $c.fullmessage} {if $commit.fullmessage}
{trans 'Content:'} {trans 'Content:'}
{$c.fullmessage} {$commit.fullmessage}
{/if} {/if}
-- --
{trans 'Commit details:'} {$url_base}{url 'IDF_Views_Source::commit', array($project.shortname, $c.scm_id)} {trans 'Commit details:'} {$url_base}{url 'IDF_Views_Source::commit', array($project.shortname, $commit.scm_id)}

View File

@ -1,6 +1,6 @@
<div id="tagscloud" class="smaller"><dl>{foreach $project.getTagCloud($cloud) as $class => $labels} <dl class="tagscloud smaller">{foreach $project.getTagCloud($cloud) as $class => $labels}
<dt class="label">{$class}</dt> <dt class="label">{$class}</dt>
{foreach $labels as $idx => $label} {foreach $labels as $idx => $label}
{aurl 'url', $cloud_url, array($project.shortname, $label.id, 'open')} {aurl 'url', $cloud_url, array($project.shortname, $label.id, 'open')}
<dd><a href="{$url}" class="label">{$label.name}{if $idx != count($labels) - 1},{/if}</a></dd> <dd><a href="{$url}" class="label">{$label.name}{if $idx != count($labels) - 1},{/if}</a></dd>
{/foreach}{/foreach}</dl></p> {/foreach}{/foreach}</dl>

View File

@ -2,11 +2,19 @@
{block tabwiki} class="active"{/block} {block tabwiki} class="active"{/block}
{block subtabs} {block subtabs}
<div id="sub-tabs"> <div id="sub-tabs">
<a {if $inWiki}class="active" {/if}href="{url 'IDF_Views_Wiki::index', array($project.shortname)}">{trans 'List Pages'}</a> <a {if $inPageList}class="active" {/if}href="{url 'IDF_Views_Wiki::listPages', array($project.shortname)}">{trans 'List Pages'}</a>
{if !$user.isAnonymous()} | <a {if $inCreate}class="active" {/if}href="{url 'IDF_Views_Wiki::create', array($project.shortname)}">{trans 'New Page'}</a> {/if} {if !$user.isAnonymous()}
{if !$user.isAnonymous() and $inView} | <a href="{url 'IDF_Views_Wiki::update', array($project.shortname, $page.title)}">{trans 'Update This Page'}</a> {/if} | <a {if $inResourceList}class="active" {/if}href="{url 'IDF_Views_Wiki::listResources', array($project.shortname)}">{trans 'List Resources'}</a>
| | <a {if $inCreatePage}class="active" {/if}href="{url 'IDF_Views_Wiki::createPage', array($project.shortname)}">{trans 'New Page'}</a>
<form class="star" action="{url 'IDF_Views_Wiki::search', array($project.shortname)}" method="get"> | <a {if $inCreateResource}class="active" {/if}href="{url 'IDF_Views_Wiki::createResource', array($project.shortname)}">{trans 'New Resource'}</a>
{if $inPageView}
| <a href="{url 'IDF_Views_Wiki::updatePage', array($project.shortname, $page.title)}">{trans 'Update This Page'}</a>
{/if}
{if $inResourceView}
| <a href="{url 'IDF_Views_Wiki::updateResource', array($project.shortname, $resource.title)}">{trans 'Update This Resource'}</a>
{/if}
{/if}
| <form class="star" action="{url 'IDF_Views_Wiki::search', array($project.shortname)}" method="get">
<input accesskey="4" type="text" value="{$q}" name="q" size="20" /> <input accesskey="4" type="text" value="{$q}" name="q" size="20" />
<input type="submit" name="s" value="{trans 'Search'}" /> <input type="submit" name="s" value="{trans 'Search'}" />
</form> </form>

View File

@ -1,5 +1,5 @@
{extends "idf/wiki/base.html"} {extends "idf/wiki/base.html"}
{block docclass}yui-t1{assign $inCreate = true}{/block} {block docclass}yui-t2{assign $inCreatePage = true}{/block}
{block body} {block body}
{if $preview} {if $preview}
@ -49,7 +49,7 @@
</tr>{/if} </tr>{/if}
<tr> <tr>
<td>&nbsp;</td> <td>&nbsp;</td>
<td><input type="submit" value="{trans 'Preview'}" name="preview" /> &nbsp; <input type="submit" value="{trans 'Create Page'}" name="submit" /> | <a href="{url 'IDF_Views_Wiki::index', array($project.shortname)}">{trans 'Cancel'}</a> <td><input type="submit" value="{trans 'Preview'}" name="preview" /> &nbsp; <input type="submit" value="{trans 'Create Page'}" name="submit" /> | <a href="{url 'IDF_Views_Wiki::listPages', array($project.shortname)}">{trans 'Cancel'}</a>
</td> </td>
</tr> </tr>
</table> </table>

View File

@ -0,0 +1,56 @@
{extends "idf/wiki/base.html"}
{block docclass}yui-t1{assign $inCreateResource = true}{/block}
{block body}
{if $form.errors}
<div class="px-message-error">
<p>{trans 'The form contains some errors. Please correct them to create the page.'}</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.title.labelTag}:</strong></th>
<td>{if $form.f.title.errors}{$form.f.title.fieldErrors}{/if}
{$form.f.title|unsafe}<br />
<span class="helptext">{$form.f.title.help_text}</span>
</td>
</tr>
<tr>
<th><strong>{$form.f.summary.labelTag}:</strong></th>
<td>{if $form.f.summary.errors}{$form.f.summary.fieldErrors}{/if}
{$form.f.summary|unsafe}<br />
<span class="helptext">{$form.f.summary.help_text}</span>
</td>
</tr>
<tr>
<th><strong>{$form.f.file.labelTag}:</strong></th>
<td>{if $form.f.file.errors}{$form.f.file.fieldErrors}{/if}
{$form.f.file|unsafe}
</td>
</tr>
<td>&nbsp;</td>
<td><input type="submit" value="{trans 'Create Resource'}" name="submit" /> | <a href="{url 'IDF_Views_Wiki::listResources', array($project.shortname)}">{trans 'Cancel'}</a>
</td>
</tr>
</table>
</form>
{/block}
{block context}
<div class="issue-submit-info">
{blocktrans}
Wiki resources are later addressed in wiki pages by their title, so ensure that you
give your resource a unique and an easy to remember name.
{/blocktrans}
</div>
{/block}
{block javascript}
<script type="text/javascript">
document.getElementById('id_title').focus()
</script>
{/block}

View File

@ -1,5 +1,5 @@
{extends "idf/wiki/base.html"} {extends "idf/wiki/base.html"}
{block docclass}yui-t3{assign $inView=true}{/block} {block docclass}yui-t3{assign $inPageView=true}{/block}
{block body} {block body}
<p>{blocktrans}If you delete this documentation page, it will be removed from the database with all the associated revisions and <strong>you will not be able to recover it</strong>.{/blocktrans}</p> <p>{blocktrans}If you delete this documentation page, it will be removed from the database with all the associated revisions and <strong>you will not be able to recover it</strong>.{/blocktrans}</p>
@ -10,7 +10,7 @@
<td> <td>
{$form.f.confirm|unsafe} <strong>{$form.f.confirm.labelTag}</strong><br /> {$form.f.confirm|unsafe} <strong>{$form.f.confirm.labelTag}</strong><br />
{if $form.f.confirm.errors}{$form.f.confirm.fieldErrors}{/if} {if $form.f.confirm.errors}{$form.f.confirm.fieldErrors}{/if}
<input type="submit" value="{trans 'Delete Page'}" name="submit" /> | <a href="{url 'IDF_Views_Wiki::view', array($project.shortname, $page.title)}">{trans 'Cancel'}</a> <input type="submit" value="{trans 'Delete Page'}" name="submit" /> | <a href="{url 'IDF_Views_Wiki::viewPage', array($project.shortname, $page.title)}">{trans 'Cancel'}</a>
</td> </td>
</tr> </tr>
</table> </table>
@ -36,7 +36,7 @@
{if $revs.count() > 0} {if $revs.count() > 0}
<p><strong>{trans 'Old Revisions'}</strong></p> <p><strong>{trans 'Old Revisions'}</strong></p>
<ul>{foreach $revs as $old} <ul>{foreach $revs as $old}
<li><a href="{url 'IDF_Views_Wiki::view', array($project.shortname, $page.title), array('rev'=>$old.id)}">{$old.summary}</a></li> <li><a href="{url 'IDF_Views_Wiki::viewPage', array($project.shortname, $page.title), array('rev'=>$old.id)}">{$old.summary}</a></li>
{/foreach}</ul> {/foreach}</ul>
{/if} {/if}
{/block} {/block}

View File

@ -1,9 +1,9 @@
{extends "idf/wiki/base.html"} {extends "idf/wiki/base.html"}
{block extraheader}{if $oldrev}<meta name="ROBOTS" content="NOINDEX" />{/if}{/block} {block extraheader}{if $oldrev}<meta name="ROBOTS" content="NOINDEX" />{/if}{/block}
{block docclass}yui-t3{assign $inView=true}{/block} {block docclass}yui-t3{assign $inPageView=true}{/block}
{block body} {block body}
{assign $submitter = $oldrev.get_submitter()}{aurl 'url', 'IDF_Views_Wiki::view', array($project.shortname, $page.title)} {assign $submitter = $oldrev.get_submitter()}{aurl 'url', 'IDF_Views_Wiki::viewPage', array($project.shortname, $page.title)}
<div class="old-rev"> <div class="old-rev">
<p>{blocktrans}You are looking at an old revision (<em>{$oldrev.summary}</em>) of the page <p>{blocktrans}You are looking at an old revision (<em>{$oldrev.summary}</em>) of the page
<a href="{$url}">{$page.title}</a>. This revision was created <a href="{$url}">{$page.title}</a>. This revision was created
@ -14,7 +14,7 @@ by {$submitter}.{/blocktrans}</p>
<table class="form" summary=""> <table class="form" summary="">
<tr> <tr>
<td>&nbsp;</td> <td>&nbsp;</td>
<td><input type="submit" value="{trans 'Delete Revision'}" name="submit" /> | <a href="{url 'IDF_Views_Wiki::view', array($project.shortname, $page.title)}">{trans 'Cancel'}</a> <td><input type="submit" value="{trans 'Delete Revision'}" name="submit" /> | <a href="{url 'IDF_Views_Wiki::viewPage', array($project.shortname, $page.title)}">{trans 'Cancel'}</a>
</td> </td>
</tr> </tr>
</table> </table>
@ -40,7 +40,7 @@ by {$submitter}.{/blocktrans}</p>
{if $revs.count() > 0} {if $revs.count() > 0}
<p><strong>{trans 'Old Revisions'}</strong></p> <p><strong>{trans 'Old Revisions'}</strong></p>
<ul>{foreach $revs as $old} <ul>{foreach $revs as $old}
<li><a href="{url 'IDF_Views_Wiki::view', array($project.shortname, $page.title), array('rev'=>$old.id)}">{$old.summary}</a></li> <li><a href="{url 'IDF_Views_Wiki::viewPage', array($project.shortname, $page.title), array('rev'=>$old.id)}">{$old.summary}</a></li>
{/foreach}</ul> {/foreach}</ul>
{/if} {/if}
{/block} {/block}

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