Merge branch 'release-1.3' into develop

This commit is contained in:
Thomas Keller 2012-05-06 22:49:48 +02:00
commit 7221135849
11 changed files with 165 additions and 105 deletions

View File

@ -22,7 +22,7 @@ Much appreciated contributors (in alphabetical order):
Janez Troha <http://www.dz0ny.info> - Slovenian translation
Jean-Philippe Fleury <jpfleury>
Jerry <lxb429@gmail.com> - Chinese translation
Jonathan Aillet - French translation
Jonathan Aillet <jonathan.aillet@gmail.com> - French translation
Julien Issler <julien@issler.net>
Litew <litew9@gmail.com> - Russian translation
Ludovic Bellière <xrogaan>

View File

@ -1,8 +1,16 @@
# InDefero 1.3.1 - xxx xxx xx xx:xx 2012 UTC
# InDefero 1.3.2 - xxx xxx xx xx:xx 2012 UTC
- ...
# InDefero 1.3.1 - Sun May 06 20:32 2012 UTC
## Bugfixes
- Compatiblity fixes for PostgreSQL
- Compatiblity fixes for PostgreSQL (issue 800)
- Fix TOC generation in wiki (issue 803)
- Make the display of large files and diffs faster (issue 804)
- Make the project list faster and its tag list more accurate by not
counting projects that are invisible to the current user
## Language and Translations
@ -44,7 +52,7 @@ or newer to properly run this version of Indefero!
emails for one object are uniquely identifyable to support a grouped view
in email clients that support that. (fixes issues 334, 452, 480, and 791)
- Indefero can now be configured to record activity metrics for all projects
in a forge. This needs a special cron job named 'activitycron.php`
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.

View File

@ -2,7 +2,7 @@
<?php
$xmlfile = dirname(__FILE__) .'/test/report.xml';
passthru('phpunit --coverage-clover='.$xmlfile);
passthru('phpunit --coverage-clover='.escapeshellarg($xmlfile));
$xml = simplexml_load_string(file_get_contents($xmlfile));
unlink($xmlfile);
printf(

View File

@ -44,10 +44,15 @@ class IDF_ActivityTaxonomy
{
public static function recalculateTaxnomies(DateTime $date)
{
$sectionWeights = Pluf::f('activity_section_weights', null);
$lookback = Pluf::f('activity_lookback', null);
if ($sectionWeights === null || $lookback === null) {
throw new LogicException('activity configuration is missing in idf.php');
}
//
// query and normalize the section weights
//
$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');
@ -59,7 +64,6 @@ class IDF_ActivityTaxonomy
//
// determine the date boundaries
//
$lookback = Pluf::f('activity_lookback', 0);
if ($lookback < 1) {
throw new LogicException('lookback must be greater or equal to 1');
}

View File

@ -147,7 +147,7 @@ class IDF_FileUtil
* Splits a string into separate lines while retaining the individual
* line ending character for every line.
*
* OS9 line endings are not supported.
* OS 9 line endings are not supported.
*
* @param string content
* @param boolean if true, skip completely empty lines
@ -155,19 +155,16 @@ class IDF_FileUtil
*/
public static function splitIntoLines($content, $skipEmpty = false)
{
$flags = PREG_SPLIT_OFFSET_CAPTURE;
if ($skipEmpty) $flags |= PREG_SPLIT_NO_EMPTY;
$splitted = preg_split("/\r\n|\n/", $content, -1, $flags);
$last_off = -1;
$last_off = 0;
$lines = array();
while (($split = array_shift($splitted)) !== null) {
if ($last_off != -1) {
$lines[] .= substr($content, $last_off, $split[1] - $last_off);
}
$last_off = $split[1];
while (preg_match("/\r\n|\n/", $content, $m, PREG_OFFSET_CAPTURE, $last_off)) {
$next_off = strlen($m[0][0]) + $m[0][1];
$line = substr($content, $last_off, $next_off - $last_off);
$last_off = $next_off;
if ($line !== $m[0][0] || !$skipEmpty) $lines[] = $line;
}
$lines[] = substr($content, $last_off);
$line = substr($content, $last_off);
if ($line !== false && strlen($line) > 0) $lines[] = $line;
return $lines;
}

View File

@ -52,31 +52,6 @@ class IDF_Forge
$this->conf->setVal('project_labels', $labels);
}
public function getProjectLabelsWithCounts() {
$sql = new Pluf_SQL('project IS NULL');
$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);
}

View File

@ -85,7 +85,7 @@ class IDF_Views
$projects = self::getProjects($request->user, $tag, $order);
$stats = self::getProjectsStatistics($projects);
$projectLabels = IDF_Forge::instance()->getProjectLabelsWithCounts();
$projectLabels = self::getProjectLabelsWithCounts($request->user);
return Pluf_Shortcuts_RenderToResponse('idf/listProjects.html',
array('page_title' => __('Projects'),
@ -371,6 +371,61 @@ class IDF_Views
}
/**
* Returns a list of ids of projects that are visible for the given user
*
* @param Pluf_User $user
*/
private static function getUserVisibleProjectIds($user)
{
$db =& Pluf::db();
// the administrator can see all projects
if ($user->administrator) {
$sql_results = $db->select(
'SELECT id FROM '.Pluf::f('db_table_prefix', '').'idf_projects'
);
foreach ($sql_results as $id) {
$ids[] = $id['id'];
}
return $ids;
}
// anonymous users can only see non-private projects
$false = Pluf_DB_BooleanToDb(false, $db);
$sql_results = $db->select(
'SELECT id FROM '.$db->pfx.'idf_projects '.
'WHERE '.$db->qn('private').'='.$false
);
$ids = array();
foreach ($sql_results as $id) {
$ids[] = $id['id'];
}
// registered users may additionally see private projects with which
// they're somehow affiliated
if (!$user->isAnonymous()) {
$perms = array(
Pluf_Permission::getFromString('IDF.project-member'),
Pluf_Permission::getFromString('IDF.project-owner'),
Pluf_Permission::getFromString('IDF.project-authorized-user')
);
$permSql = new Pluf_SQL(
"model_class='IDF_Project' AND owner_class='Pluf_User' ".
"AND owner_id=%s AND negative=".$false, $user->id
);
$rows = Pluf::factory('Pluf_RowPermission')->getList(array('filter' => $permSql->gen()));
if ($rows->count() > 0) {
foreach ($rows as $row) {
if (in_array($row->model_id, $ids))
continue;
$ids[] = $row->model_id;
}
}
}
return $ids;
}
/**
* Returns a list of projects accessible for the user and optionally filtered by tag.
*
@ -381,37 +436,14 @@ class IDF_Views
public static function getProjects($user, $tag = false, $order = 'name')
{
$db =& Pluf::db();
$false = Pluf_DB_BooleanToDb(false, $db);
$sql = new Pluf_SQL('1=1');
if ($tag !== false) {
$sql->SAnd(new Pluf_SQL('idf_tag_id=%s', $tag->id));
}
if ($user->isAnonymous())
{
$authSql = new Pluf_SQL($db->qn('private').'='.$false);
$sql->SAnd($authSql);
} else
if (!$user->administrator) {
// grab the list of projects where the user is admin,
// member or authorized
$perms = array(
Pluf_Permission::getFromString('IDF.project-member'),
Pluf_Permission::getFromString('IDF.project-owner'),
Pluf_Permission::getFromString('IDF.project-authorized-user')
);
$permSql = new Pluf_SQL("model_class='IDF_Project' AND owner_class='Pluf_User' AND owner_id=%s AND negative=".$false, $user->id);
$rows = Pluf::factory('Pluf_RowPermission')->getList(array('filter' => $permSql->gen()));
$authSql = new Pluf_SQL($db->qn('private').'='.$false);
if ($rows->count() > 0) {
$ids = array();
foreach ($rows as $row) {
$ids[] = $row->model_id;
}
$authSql->SOr(new Pluf_SQL(sprintf($db->pfx.'idf_projects.id IN (%s)', implode(', ', $ids))));
}
$sql->SAnd($authSql);
$projectIds = self::getUserVisibleProjectIds($user);
if (count($projectIds) > 0) {
$sql->SAnd(new Pluf_SQL(sprintf($db->pfx.'idf_projects.id IN (%s)', implode(', ', $projectIds))));
}
$orderTypes = array(
@ -425,6 +457,46 @@ class IDF_Views
));
}
/**
* Returns a list of global tags each carrying the number of projects that have the
* particular tag set
*
* @param Pluf_User $user
* @return array
*/
public static function getProjectLabelsWithCounts($user) {
$sql = new Pluf_SQL('project IS NULL');
$projectIds = self::getUserVisibleProjectIds($user);
if (count($projectIds) > 0) {
$sql->SAnd(new Pluf_SQL(sprintf('idf_project_id IN (%s)', implode(', ', $projectIds))));
}
$tagList = Pluf::factory('IDF_Tag')->getList(array(
'filter' => $sql->gen(),
'view' => 'join_projects',
'order' => 'class ASC, lcname ASC'
));
$maxProjectCount = 0;
foreach ($tagList as $tag) {
$maxProjectCount = max($maxProjectCount, $tag->project_count);
}
$tags = array();
foreach ($tagList as $tag) {
// group by class
if (!array_key_exists($tag->class, $tags)) {
$tags[$tag->class] = array();
}
$tag->rel_project_count = $tag->project_count / (double) $maxProjectCount;
$tags[$tag->class][] = $tag;
}
return $tags;
}
/**
* Returns statistics on a list of projects.
*
@ -433,27 +505,30 @@ class IDF_Views
*/
public static function getProjectsStatistics($projects)
{
// Init the return var
$forgestats = array('downloads' => 0,
'reviews' => 0,
'issues' => 0,
'docpages' => 0,
'commits' => 0);
// Count for each projects
foreach ($projects as $p) {
$pstats = $p->getStats();
$forgestats['downloads'] += $pstats['downloads'];
$forgestats['reviews'] += $pstats['reviews'];
$forgestats['issues'] += $pstats['issues'];
$forgestats['docpages'] += $pstats['docpages'];
$forgestats['commits'] += $pstats['commits'];
$projectIds = array();
foreach ($projects as $project) {
$projectIds[] = $project->id;
}
// Count members
$sql = new Pluf_SQL('first_name != %s', array('---'));
$forgestats['members'] = Pluf::factory('Pluf_User')
->getCount(array('filter' => $sql->gen()));
$forgestats = array();
// count overall project stats
$forgestats['total'] = 0;
$what = array(
'downloads' => 'IDF_Upload',
'reviews' => 'IDF_Review',
'issues' => 'IDF_Issue',
'docpages' => 'IDF_Wiki_Page',
'commits' => 'IDF_Commit',
);
foreach ($what as $key => $model) {
$count = Pluf::factory($model)->getCount(array(
'filter' => sprintf('project IN (%s)', implode(', ', $projectIds))
));
$forgestats[$key] = $count;
$forgestats['total'] += $count;
}
return $forgestats;
}

View File

@ -13,7 +13,7 @@ msgstr ""
"Project-Id-Version: Indefero\n"
"Report-Msgid-Bugs-To: http://projects.ceondo.com/p/indefero/issues/\n"
"POT-Creation-Date: 2012-03-22 01:16+0100\n"
"PO-Revision-Date: 2012-04-17 08:06+0000\n"
"PO-Revision-Date: 2012-04-21 08:44+0000\n"
"Last-Translator: William MARTIN <wysman@gmail.com>\n"
"Language-Team: French (http://www.transifex.net/projects/p/indefero/language/fr/)\n"
"MIME-Version: 1.0\n"
@ -812,7 +812,7 @@ msgstr "Ce champ est obligatoire."
#: IDF/Form/SourceConf.php:54 IDF/Form/UploadConf.php:70
msgid "Webhook URL"
msgstr "URL du webhook"
msgstr "URL de notification"
#: IDF/Form/TabsConf.php:38 IDF/gettexttemplates/idf/admin/base.html.php:4
#: IDF/gettexttemplates/idf/base-full.html.php:8
@ -1210,7 +1210,7 @@ msgid ""
"<p>For example, updating download 123 of project 'my-project' with\n"
"web hook URL <code>http://mydomain.com/%p/%d</code> would send a POST request to\n"
"<code>http://mydomain.com/my-project/123</code>.</p>"
msgstr ""
msgstr "<p>Il est possible de mettre en place l'envoie de méta-information à l'aide d'une requête HTTP PUT lors de la création, ou POST lors de la mise a jour d'un téléchargement. Laisser ce champs vide pour désactiver les notifications.</p>\n\n<p>Le lien ne doit pas contenir de caractères spéciaux, par exemple :</p>\n\n<ul>\n<li><code>http://domain.com/upload</code></li>\n<li><code>http://domain.com/upload?my%20param</code></li>\n</ul>\n\n<p>De plus, il peut contenir les séquences suivantes qui seront remplacer par les valeurs spécifiques au projet et au téléchargement en question.</p>\n\n<ul>\n<li><code>%p</code> - le nom du projet</li>\n<li><code>%d</code> - l'identifiant du téléchargement</li>\n</ul>\n\n<p>Par exemple, la mise a jour du téléchargement 123 dans le projet 'my-project', avec le lien suivant <code>http://mydomain.com/%p/%d</code>\nenverra une requête POST à ladresse suivante <code>http://mydomain.com/my-project/123</code>.</p>"
#: IDF/gettexttemplates/idf/admin/downloads.html.php:31
#: IDF/gettexttemplates/idf/admin/source.html.php:30
@ -1277,7 +1277,7 @@ msgid ""
"<p>For example, committing revision 123 to project 'my-project' with\n"
"post-commit URL <code>http://mydomain.com/%p/%r</code> would send a request to\n"
"<code>http://mydomain.com/my-project/123</code>.</p>"
msgstr ""
msgstr "<p>Il est possible de mettre en place l'envoie de méta-information à l'aide d'une requête HTTP <strong>%%hook_request_method%%</strong> lors de la réception de commit dans le dépôt .Laisser ce champs vide pour désactiver les notifications.</p>\n\n<p>Le lien ne doit pas contenir de caractères spéciaux, par exemple :</p>\n\n<ul>\n<li><code>http://domain.com/commit</code></li>\n<li><code>http://domain.com/commit?my%20param</code></li>\n</ul>\n\n<p>De plus, il peut contenir les séquences suivantes qui seront remplacer par les valeurs spécifiques au projet et au téléchargement en question.</p>\n\n<ul>\n<li><code>%p</code> - le nom du projet</li>\n<li><code>%r</code> - l'identifiant du commit</li>\n</ul>\n\n<p>Par exemple, la réception du commit 123 dans le projet 'my-project', avec le lien suivant <code>http://mydomain.com/%p/%r</code>enverra une requête à ladresse suivante \n<code>http://mydomain.com/my-project/123</code>.</p>"
#: IDF/gettexttemplates/idf/admin/source.html.php:26
msgid ""
@ -1757,7 +1757,7 @@ msgid ""
"Resources are versioned, just like wiki pages. If you update a resource, old wiki pages still show the state of the resource\n"
"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\n"
"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. \n"
msgstr ""
msgstr "\n<p>Pour insérer des ressources dans votre page de Wiki, vous pouvez utiliser la syntaxe suivante <code>[[!ResourceName]]</code>.</p>\n\n<p>Vous pouvez ajuster l'affichage des resources avec les paramètres suivants :\n<ul>\n<li><code>[[!ImageResource, align=right, width=200]]</code> affiche \"ImageResource\" aligné a droite, et mit a l'échelle pour avoir une largeur de 200 pixels.</li>\n<li><code>[[!TextResource, align=center, width=300, height=300]]</code> affiche \"TextResource\" aligné au centre de la page avec une taille de 300 par 300 pixels.</li>\n<li><code>[[!AnyResource, preview=no]]</code> permet de fournir uniquement un lien de téléchargement vers la ressource.</li>\n<li><code>[[!BinaryResource, title=Download]]</code> affiche le lien téléchargement \"BinaryResource\" avec un `title` alternatif</li>\n</ul>\n</p>\n\nLes resources sont versionnées, comme les pages de Wiki. Si vous mettez a jour une ressource, les pages de wiki continueront d'afficher la resource dans l'état ou elle était lors de la dernière édition. Une simple édition de la page mettra a jour les ressources contenu dans celle ci.\n"
#: IDF/gettexttemplates/idf/faq.html.php:28
msgid ""
@ -1769,7 +1769,7 @@ msgid ""
"the rest of the files in the archive that should be published.</p>\n"
"<p>Once this archive has been uploaded, InDefero reads in the meta information, unpacks the other files from\n"
"the archive and creates new individual downloads for each of them.</p>"
msgstr ""
msgstr "<p>Si vous avez a publié de nombreux fichiers à la fois, il peut être long et ennuyeux de les envoyés un par un, de leur mettre un nom, une description et des labels.</p>\n<p>InDefero supporte l'envoie d'une archive au format ZIP qui contient des meta-informations. Ces informations sont contenu dans un fichier de manifest, qui est a part des autres fichiers a publier.</p>\n<p>Lors de l'envoie de cette archive, InDefero va créer un nouveau téléchargement pour chaque fichier contenu dans l'archive et remplira les meta-information de ceux-ci à l'aide du fichier manifest.</p>"
#: IDF/gettexttemplates/idf/faq.html.php:36
#, php-format
@ -1902,7 +1902,7 @@ msgid ""
"<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>\n"
"</ul>\n"
"</p>\n"
msgstr ""
msgstr "\n<p><strong>Procédure :</strong></p>\n<p>Vous pouvez définir une page personnalisée qui sera utilisé à la place de la page ou sont listés les projets.Cette page est aussi accessible par le lien 'Home', dans la bar de menu principale.</p>\n\n<p>Le contenu de cette page peut utilisé la <a href=\"%%burl%%\">syntaxe Markdown</a> avec cette <a href=\"%%eurl%%\"><em>Extra</em> extension</a>.</p>\n\n<p>Les macros suivantes sont aussi disponibles :<br />\n<ul>\n<li><code>&#x7b;projectlist, label=..., order=(name|activity), limit=...}</code> - Affiche la liste des projets qui peut être filtrée par labels, et ordonnées par nom ou activité, le nombre de résultat affiché peut être limité.</li>\n</ul>\n</p>\n"
#: IDF/gettexttemplates/idf/gadmin/projects/base.html.php:3
#: IDF/gettexttemplates/idf/main-menu.html.php:7
@ -4785,7 +4785,7 @@ msgstr "Projet"
#: IDF/Views/Issue.php:344
#, php-format
msgid "%1$s %2$s Submitted %3$s Issues"
msgstr "Tickets soumis par %1$s %2$s pour %2$s"
msgstr "Tickets soumis par %1$s %2$s pour %3$s"
#: IDF/Views/Issue.php:348
#, php-format

View File

@ -48,7 +48,6 @@
<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>

View File

@ -80,7 +80,6 @@
0 => '',
1 => 11,
2 => '
',
),
),
@ -102,4 +101,4 @@
),
),
),
);
);

View File

@ -1,9 +1,12 @@
$(document).ready(function() {
$(":header", "#wiki-content").map(function(index) {
this.id = "wikititle_" + index;
$("<a href='#" + this.id + "'>" + jQuery.fn.text([this]) + "</a>")
.addClass("wiki-" + this.tagName.toLowerCase())
.appendTo('#wiki-toc-content');
var $header = $(this);
var $toc = $('#wiki-toc-content');
$header.attr('id', 'wikititle_' + index);
$('<a />').attr('href', '#' + $header.attr('id'))
.text($header.text())
.addClass("wiki-" + $header[0].tagName.toLowerCase())
.appendTo($toc);
});
if ($('#wiki-toc-content *').size() < 2)
$('#wiki-toc').hide();