Started ticket 39, add code review.
We now have a limited support of the code review. Still some work to be done to allow the submission of new patches on a given review and update the status. For the moment, only pre-commit review is supported.
This commit is contained in:
parent
fbe364462d
commit
f690968b11
158
src/IDF/Diff.php
158
src/IDF/Diff.php
@ -169,4 +169,162 @@ class IDF_Diff
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Review patch.
|
||||
*
|
||||
* Given the original file as a string and the parsed
|
||||
* corresponding diff chunks, generate a side by side view of the
|
||||
* original file and new file with added/removed lines.
|
||||
*
|
||||
* Example of use:
|
||||
*
|
||||
* $diff = new IDF_Diff(file_get_contents($diff_file));
|
||||
* $orig = file_get_contents($orig_file);
|
||||
* $diff->parse();
|
||||
* echo $diff->fileCompare($orig, $diff->files[$orig_file], $diff_file);
|
||||
*
|
||||
* @param string Original file
|
||||
* @param array Chunk description of the diff corresponding to the file
|
||||
* @param string Original file name
|
||||
* @param int Number of lines before/after the chunk to be displayed (10)
|
||||
* @return Pluf_Template_SafeString The table body
|
||||
*/
|
||||
public function fileCompare($orig, $chunks, $filename, $context=10)
|
||||
{
|
||||
$orig_lines = preg_split("/\015\012|\015|\012/", $orig);
|
||||
$new_chunks = $this->mergeChunks($orig_lines, $chunks, $context);
|
||||
return $this->renderCompared($new_chunks, $filename);
|
||||
}
|
||||
|
||||
public function mergeChunks($orig_lines, $chunks, $context=10)
|
||||
{
|
||||
$spans = array();
|
||||
$new_chunks = array();
|
||||
$min_line = 0;
|
||||
$max_line = 0;
|
||||
//if (count($chunks['chunks_def']) == 0) return '';
|
||||
foreach ($chunks['chunks_def'] as $chunk) {
|
||||
$start = ($chunk[0][0] > $context) ? $chunk[0][0]-$context : 0;
|
||||
$end = (($chunk[0][0]+$chunk[0][1]+$context-1) < count($orig_lines)) ? $chunk[0][0]+$chunk[0][1]+$context-1 : count($orig_lines);
|
||||
$spans[] = array($start, $end);
|
||||
}
|
||||
// merge chunks/get the chunk lines
|
||||
// these are reference lines
|
||||
$chunk_lines = array();
|
||||
foreach ($chunks['chunks'] as $chunk) {
|
||||
foreach ($chunk as $line) {
|
||||
$chunk_lines[] = $line;
|
||||
}
|
||||
}
|
||||
$i = 0;
|
||||
foreach ($chunks['chunks'] as $chunk) {
|
||||
$n_chunk = array();
|
||||
// add lines before
|
||||
if ($chunk[0][0] > $spans[$i][0]) {
|
||||
for ($lc=$spans[$i][0];$lc<$chunk[0][0];$lc++) {
|
||||
$exists = false;
|
||||
foreach ($chunk_lines as $line) {
|
||||
if ($lc == $line[0] or ($chunk[0][1]-$chunk[0][0]+$lc) == $line[1]) {
|
||||
$exists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!$exists) {
|
||||
$n_chunk[] = array(
|
||||
$lc,
|
||||
$chunk[0][1]-$chunk[0][0]+$lc,
|
||||
$orig_lines[$lc-1]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
// add chunk lines
|
||||
foreach ($chunk as $line) {
|
||||
$n_chunk[] = $line;
|
||||
}
|
||||
// add lines after
|
||||
$lline = $line;
|
||||
if (!empty($lline[0]) and $lline[0] < $spans[$i][1]) {
|
||||
for ($lc=$lline[0];$lc<=$spans[$i][1];$lc++) {
|
||||
$exists = false;
|
||||
foreach ($chunk_lines as $line) {
|
||||
if ($lc == $line[0] or ($lline[1]-$lline[0]+$lc) == $line[1]) {
|
||||
$exists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!$exists) {
|
||||
$n_chunk[] = array(
|
||||
$lc,
|
||||
$lline[1]-$lline[0]+$lc,
|
||||
$orig_lines[$lc-1]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
$new_chunks[] = $n_chunk;
|
||||
$i++;
|
||||
}
|
||||
// Now, each chunk has the right length, we need to merge them
|
||||
// when needed
|
||||
$nnew_chunks = array();
|
||||
$i = 0;
|
||||
foreach ($new_chunks as $chunk) {
|
||||
if ($i>0) {
|
||||
$lline = end($nnew_chunks[$i-1]);
|
||||
if ($chunk[0][0] <= $lline[0]+1) {
|
||||
// need merging
|
||||
foreach ($chunk as $line) {
|
||||
if ($line[0] > $lline[0] or empty($line[0])) {
|
||||
$nnew_chunks[$i-1][] = $line;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$nnew_chunks[] = $chunk;
|
||||
$i++;
|
||||
}
|
||||
} else {
|
||||
$nnew_chunks[] = $chunk;
|
||||
$i++;
|
||||
}
|
||||
}
|
||||
return $nnew_chunks;
|
||||
}
|
||||
|
||||
|
||||
public function renderCompared($chunks, $filename)
|
||||
{
|
||||
$fileinfo = IDF_Views_Source::getMimeType($filename);
|
||||
$pretty = '';
|
||||
if (IDF_Views_Source::isSupportedExtension($fileinfo[2])) {
|
||||
$pretty = ' prettyprint';
|
||||
}
|
||||
$out = '';
|
||||
$cc = 1;
|
||||
$i = 0;
|
||||
foreach ($chunks as $chunk) {
|
||||
foreach ($chunk as $line) {
|
||||
$line1 = ' ';
|
||||
$line2 = ' ';
|
||||
$line[2] = (strlen($line[2])) ? self::padLine(Pluf_esc($line[2])) : ' ';
|
||||
if ($line[0] and $line[1]) {
|
||||
$class = 'diff-c';
|
||||
$line1 = $line2 = $line[2];
|
||||
} elseif ($line[0]) {
|
||||
$class = 'diff-r';
|
||||
$line1 = $line[2];
|
||||
} else {
|
||||
$class = 'diff-a';
|
||||
$line2 = $line[2];
|
||||
}
|
||||
$out .= sprintf('<tr class="diff-line"><td class="diff-lc">%s</td><td class="%s mono%s"><code>%s</code></td><td class="diff-lc">%s</td><td class="%s mono%s"><code>%s</code></td></tr>'."\n", $line[0], $class, $pretty, $line1, $line[1], $class, $pretty, $line2);
|
||||
}
|
||||
if (count($chunks) > $cc)
|
||||
$out .= '<tr class="diff-next"><td>...</td><td> </td><td>...</td><td> </td></tr>'."\n";
|
||||
$cc++;
|
||||
$i++;
|
||||
}
|
||||
return Pluf_Template::markSafe($out);
|
||||
|
||||
}
|
||||
}
|
203
src/IDF/Form/ReviewCommentFile.php
Normal file
203
src/IDF/Form/ReviewCommentFile.php
Normal file
@ -0,0 +1,203 @@
|
||||
<?php
|
||||
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/*
|
||||
# ***** BEGIN LICENSE BLOCK *****
|
||||
# This file is part of InDefero, an open source project management application.
|
||||
# Copyright (C) 2008 Céondo Ltd and contributors.
|
||||
#
|
||||
# InDefero is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# InDefero is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
#
|
||||
# ***** END LICENSE BLOCK ***** */
|
||||
|
||||
/**
|
||||
* Create a new documentation page.
|
||||
*
|
||||
* This create a new page and the corresponding revision.
|
||||
*
|
||||
*/
|
||||
class IDF_Form_WikiCreate extends Pluf_Form
|
||||
{
|
||||
public $user = null;
|
||||
public $project = null;
|
||||
public $show_full = false;
|
||||
|
||||
public function initFields($extra=array())
|
||||
{
|
||||
$initial = __('# Introduction
|
||||
|
||||
Add your content here.
|
||||
|
||||
|
||||
# Details
|
||||
|
||||
Add your content here. Format your content with:
|
||||
|
||||
* Text in **bold** or *italic*.
|
||||
* Headings, paragraphs, and lists.
|
||||
* Links to other [[WikiPage]].
|
||||
');
|
||||
$this->user = $extra['user'];
|
||||
$this->project = $extra['project'];
|
||||
if ($this->user->hasPerm('IDF.project-owner', $this->project)
|
||||
or $this->user->hasPerm('IDF.project-member', $this->project)) {
|
||||
$this->show_full = true;
|
||||
}
|
||||
$this->fields['title'] = new Pluf_Form_Field_Varchar(
|
||||
array('required' => true,
|
||||
'label' => __('Page title'),
|
||||
'initial' => __('PageName'),
|
||||
'widget_attrs' => array(
|
||||
'maxlength' => 200,
|
||||
'size' => 67,
|
||||
),
|
||||
'help_text' => __('The page 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 pages.'),
|
||||
'initial' => '',
|
||||
'widget_attrs' => array(
|
||||
'maxlength' => 200,
|
||||
'size' => 67,
|
||||
),
|
||||
));
|
||||
$this->fields['content'] = new Pluf_Form_Field_Varchar(
|
||||
array('required' => true,
|
||||
'label' => __('Content'),
|
||||
'initial' => $initial,
|
||||
'widget' => 'Pluf_Form_Widget_TextareaInput',
|
||||
'widget_attrs' => array(
|
||||
'cols' => 58,
|
||||
'rows' => 26,
|
||||
),
|
||||
));
|
||||
|
||||
if ($this->show_full) {
|
||||
for ($i=1;$i<4;$i++) {
|
||||
$this->fields['label'.$i] = new Pluf_Form_Field_Varchar(
|
||||
array('required' => false,
|
||||
'label' => __('Labels'),
|
||||
'initial' => '',
|
||||
'widget_attrs' => array(
|
||||
'maxlength' => 50,
|
||||
'size' => 20,
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
$pages = Pluf::factory('IDF_WikiPage')->getList(array('filter'=>$sql->gen()));
|
||||
if ($pages->count() > 0) {
|
||||
throw new Pluf_Form_Invalid(__('A page with this title already exists.'));
|
||||
}
|
||||
return $title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the interconnection in the form.
|
||||
*/
|
||||
public function clean()
|
||||
{
|
||||
if (!$this->show_full) {
|
||||
return $this->cleaned_data;
|
||||
}
|
||||
$conf = new IDF_Conf();
|
||||
$conf->setProject($this->project);
|
||||
$onemax = array();
|
||||
foreach (split(',', $conf->getVal('labels_wiki_one_max', IDF_Form_WikiConf::init_one_max)) as $class) {
|
||||
if (trim($class) != '') {
|
||||
$onemax[] = mb_strtolower(trim($class));
|
||||
}
|
||||
}
|
||||
$count = array();
|
||||
for ($i=1;$i<4;$i++) {
|
||||
$this->cleaned_data['label'.$i] = trim($this->cleaned_data['label'.$i]);
|
||||
if (strpos($this->cleaned_data['label'.$i], ':') !== false) {
|
||||
list($class, $name) = explode(':', $this->cleaned_data['label'.$i], 2);
|
||||
list($class, $name) = array(mb_strtolower(trim($class)),
|
||||
trim($name));
|
||||
} else {
|
||||
$class = 'other';
|
||||
$name = $this->cleaned_data['label'.$i];
|
||||
}
|
||||
if (!isset($count[$class])) $count[$class] = 1;
|
||||
else $count[$class] += 1;
|
||||
if (in_array($class, $onemax) and $count[$class] > 1) {
|
||||
if (!isset($this->errors['label'.$i])) $this->errors['label'.$i] = array();
|
||||
$this->errors['label'.$i][] = sprintf(__('You cannot provide more than label from the %s class to a page.'), $class);
|
||||
throw new Pluf_Form_Invalid(__('You provided an invalid label.'));
|
||||
}
|
||||
}
|
||||
return $this->cleaned_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.'));
|
||||
}
|
||||
// Add a tag for each label
|
||||
$tags = array();
|
||||
if ($this->show_full) {
|
||||
for ($i=1;$i<4;$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]);
|
||||
}
|
||||
$tags[] = IDF_Tag::add($name, $this->project, $class);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Create the page
|
||||
$page = new IDF_WikiPage();
|
||||
$page->project = $this->project;
|
||||
$page->submitter = $this->user;
|
||||
$page->summary = trim($this->cleaned_data['summary']);
|
||||
$page->title = trim($this->cleaned_data['title']);
|
||||
$page->create();
|
||||
foreach ($tags as $tag) {
|
||||
$page->setAssoc($tag);
|
||||
}
|
||||
// add the first revision
|
||||
$rev = new IDF_WikiRevision();
|
||||
$rev->wikipage = $page;
|
||||
$rev->content = $this->cleaned_data['content'];
|
||||
$rev->submitter = $this->user;
|
||||
$rev->summary = __('Initial page creation');
|
||||
$rev->create();
|
||||
return $page;
|
||||
}
|
||||
}
|
205
src/IDF/Form/ReviewCreate.php
Normal file
205
src/IDF/Form/ReviewCreate.php
Normal file
@ -0,0 +1,205 @@
|
||||
<?php
|
||||
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/*
|
||||
# ***** BEGIN LICENSE BLOCK *****
|
||||
# This file is part of InDefero, an open source project management application.
|
||||
# Copyright (C) 2008 Céondo Ltd and contributors.
|
||||
#
|
||||
# InDefero is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# InDefero is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
#
|
||||
# ***** END LICENSE BLOCK ***** */
|
||||
|
||||
/**
|
||||
* Create a new code review.
|
||||
*
|
||||
* This creates an IDF_Review and the corresponding IDF_Review_Patch.
|
||||
*/
|
||||
class IDF_Form_ReviewCreate extends Pluf_Form
|
||||
{
|
||||
public $user = null;
|
||||
public $project = null;
|
||||
public $show_full = false;
|
||||
|
||||
public function initFields($extra=array())
|
||||
{
|
||||
$this->user = $extra['user'];
|
||||
$this->project = $extra['project'];
|
||||
if ($this->user->hasPerm('IDF.project-owner', $this->project)
|
||||
or $this->user->hasPerm('IDF.project-member', $this->project)) {
|
||||
$this->show_full = true;
|
||||
}
|
||||
$this->fields['summary'] = new Pluf_Form_Field_Varchar(
|
||||
array('required' => true,
|
||||
'label' => __('Summary'),
|
||||
'initial' => '',
|
||||
'widget_attrs' => array(
|
||||
'maxlength' => 200,
|
||||
'size' => 67,
|
||||
),
|
||||
));
|
||||
$this->fields['description'] = new Pluf_Form_Field_Varchar(
|
||||
array('required' => true,
|
||||
'label' => __('Description'),
|
||||
'initial' => '',
|
||||
'widget' => 'Pluf_Form_Widget_TextareaInput',
|
||||
'widget_attrs' => array(
|
||||
'cols' => 58,
|
||||
'rows' => 7,
|
||||
),
|
||||
));
|
||||
$this->fields['commit'] = new Pluf_Form_Field_Varchar(
|
||||
array('required' => true,
|
||||
'label' => __('Commit'),
|
||||
'initial' => '',
|
||||
'widget_attrs' => array(
|
||||
'size' => 42,
|
||||
),
|
||||
));
|
||||
$upload_path = Pluf::f('upload_issue_path', false);
|
||||
if (false === $upload_path) {
|
||||
throw new Pluf_Exception_SettingError(__('The "upload_issue_path" configuration variable was not set.'));
|
||||
}
|
||||
$md5 = md5(rand().microtime().Pluf_Utils::getRandomString());
|
||||
// We add .dummy to try to mitigate security issues in the
|
||||
// case of someone allowing the upload path to be accessible
|
||||
// to everybody.
|
||||
$filename = substr($md5, 0, 2).'/'.substr($md5, 2, 2).'/'.substr($md5, 4).'/%s.dummy';
|
||||
$this->fields['patch'] = new Pluf_Form_Field_File(
|
||||
array('required' => true,
|
||||
'label' => __('Patch'),
|
||||
'move_function_params' =>
|
||||
array('upload_path' => $upload_path,
|
||||
'upload_path_create' => true,
|
||||
'file_name' => $filename,
|
||||
)
|
||||
)
|
||||
);
|
||||
if ($this->show_full) {
|
||||
$this->fields['status'] = new Pluf_Form_Field_Varchar(
|
||||
array('required' => true,
|
||||
'label' => __('Status'),
|
||||
'initial' => 'New',
|
||||
'widget_attrs' => array(
|
||||
'maxlength' => 20,
|
||||
'size' => 15,
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
public function clean_commit()
|
||||
{
|
||||
$commit = self::findCommit($this->cleaned_data['commit']);
|
||||
if (null == $commit) {
|
||||
throw new Pluf_Form_Invalid(__('You provided an invalid commit.'));
|
||||
}
|
||||
return $this->cleaned_data['commit'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the interconnection in the form.
|
||||
*/
|
||||
public function clean()
|
||||
{
|
||||
return $this->cleaned_data;
|
||||
}
|
||||
|
||||
function clean_status()
|
||||
{
|
||||
// Check that the status is in the list of official status
|
||||
$tags = $this->project->getTagsFromConfig('labels_issue_open',
|
||||
IDF_Form_IssueTrackingConf::init_open,
|
||||
'Status');
|
||||
$tags = array_merge($this->project->getTagsFromConfig('labels_issue_closed',
|
||||
IDF_Form_IssueTrackingConf::init_closed,
|
||||
'Status')
|
||||
, $tags);
|
||||
$found = false;
|
||||
foreach ($tags as $tag) {
|
||||
if ($tag->name == trim($this->cleaned_data['status'])) {
|
||||
$found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!$found) {
|
||||
throw new Pluf_Form_Invalid(__('You provided an invalid status.'));
|
||||
}
|
||||
return $this->cleaned_data['status'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean the attachments post failure.
|
||||
*/
|
||||
function failed()
|
||||
{
|
||||
$upload_path = Pluf::f('upload_issue_path', false);
|
||||
if ($upload_path == false) return;
|
||||
if (!empty($this->cleaned_data['patch']) and
|
||||
file_exists($upload_path.'/'.$this->cleaned_data['patch'])) {
|
||||
@unlink($upload_path.'/'.$this->cleaned_data['patch']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.'));
|
||||
}
|
||||
// Create the review
|
||||
$review = new IDF_Review();
|
||||
$review->project = $this->project;
|
||||
$review->summary = $this->cleaned_data['summary'];
|
||||
$review->submitter = $this->user;
|
||||
$review->status = IDF_Tag::add(trim($this->cleaned_data['status']), $this->project, 'Status');
|
||||
$review->create();
|
||||
// add the first patch
|
||||
$patch = new IDF_Review_Patch();
|
||||
$patch->review = $review;
|
||||
$patch->summary = __('Initial patch to be reviewed.');
|
||||
$patch->description = $this->cleaned_data['description'];
|
||||
$patch->commit = self::findCommit($this->cleaned_data['commit']);
|
||||
$patch->patch = $this->cleaned_data['patch'];
|
||||
$patch->create();
|
||||
return $review;
|
||||
}
|
||||
|
||||
/**
|
||||
* Based on the given string, try to find the matching commit.
|
||||
*
|
||||
* If no user found, simply returns null.
|
||||
*
|
||||
* @param string Commit
|
||||
* @return IDF_Commit or null
|
||||
*/
|
||||
public static function findCommit($string)
|
||||
{
|
||||
$string = trim($string);
|
||||
if (strlen($string) == 0) return null;
|
||||
$gc = new IDF_Commit();
|
||||
$sql = new Pluf_SQL('scm_id=%s', array($string));
|
||||
$gcs = $gc->getList(array('filter' => $sql->gen()));
|
||||
if ($gcs->count() > 0) {
|
||||
return $gcs[0];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
94
src/IDF/Form/ReviewFileComment.php
Normal file
94
src/IDF/Form/ReviewFileComment.php
Normal file
@ -0,0 +1,94 @@
|
||||
<?php
|
||||
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/*
|
||||
# ***** BEGIN LICENSE BLOCK *****
|
||||
# This file is part of InDefero, an open source project management application.
|
||||
# Copyright (C) 2008 Céondo Ltd and contributors.
|
||||
#
|
||||
# InDefero is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# InDefero is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
#
|
||||
# ***** END LICENSE BLOCK ***** */
|
||||
|
||||
/**
|
||||
* Add comments to files in a review.
|
||||
*
|
||||
*/
|
||||
class IDF_Form_ReviewFileComment extends Pluf_Form
|
||||
{
|
||||
public $files = null;
|
||||
public $patch = null;
|
||||
public $user = null;
|
||||
|
||||
public function initFields($extra=array())
|
||||
{
|
||||
$this->files = $extra['files'];
|
||||
$this->patch = $extra['patch'];
|
||||
$this->user = $extra['user'];
|
||||
foreach ($this->files as $filename => $def) {
|
||||
$this->fields[md5($filename)] = new Pluf_Form_Field_Varchar(
|
||||
array('required' => false,
|
||||
'label' => __('Comment'),
|
||||
'initial' => '',
|
||||
'widget' => 'Pluf_Form_Widget_TextareaInput',
|
||||
'widget_attrs' => array(
|
||||
'cols' => 58,
|
||||
'rows' => 9,
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Validate the interconnection in the form.
|
||||
*/
|
||||
public function clean()
|
||||
{
|
||||
foreach ($this->files as $filename => $def) {
|
||||
if (!empty($this->cleaned_data[md5($filename)])) {
|
||||
return $this->cleaned_data;
|
||||
}
|
||||
}
|
||||
throw new Pluf_Form_Invalid(__('You need to provide comments on at least one 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.'));
|
||||
}
|
||||
foreach ($this->files as $filename => $def) {
|
||||
if (!empty($this->cleaned_data[md5($filename)])) {
|
||||
// Add a comment.
|
||||
$c = new IDF_Review_FileComment();
|
||||
$c->patch = $this->patch;
|
||||
$c->cfile = $filename;
|
||||
$c->submitter = $this->user;
|
||||
$c->content = $this->cleaned_data[md5($filename)];
|
||||
$c->create();
|
||||
}
|
||||
}
|
||||
$this->patch->get_review()->update(); // reindex and put up in
|
||||
// the list.
|
||||
return $this->patch;
|
||||
}
|
||||
}
|
@ -36,6 +36,7 @@ class IDF_Form_TabsConf extends Pluf_Form
|
||||
$this->project = $extra['project'];
|
||||
|
||||
$ak = array('downloads_access_rights' => __('Downloads'),
|
||||
'review_access_rights' => __('Code Review'),
|
||||
'wiki_access_rights' => __('Documentation'),
|
||||
'source_access_rights' => __('Source'),
|
||||
'issues_access_rights' => __('Issues'),);
|
||||
|
@ -54,6 +54,7 @@ class IDF_Middleware
|
||||
$request->conf->setProject($request->project);
|
||||
$ak = array('downloads_access_rights' => 'hasDownloadsAccess',
|
||||
'wiki_access_rights' => 'hasWikiAccess',
|
||||
'review_access_rights' => 'hasReviewAccess',
|
||||
'source_access_rights' => 'hasSourceAccess',
|
||||
'issues_access_rights' => 'hasIssuesAccess');
|
||||
$request->rights = array();
|
||||
|
56
src/IDF/Migrations/8CodeReview.php
Normal file
56
src/IDF/Migrations/8CodeReview.php
Normal file
@ -0,0 +1,56 @@
|
||||
<?php
|
||||
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/*
|
||||
# ***** BEGIN LICENSE BLOCK *****
|
||||
# This file is part of InDefero, an open source project management application.
|
||||
# Copyright (C) 2008 Céondo Ltd and contributors.
|
||||
#
|
||||
# InDefero is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# InDefero is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
#
|
||||
# ***** END LICENSE BLOCK ***** */
|
||||
|
||||
/**
|
||||
* Add the code review.
|
||||
*/
|
||||
|
||||
function IDF_Migrations_8CodeReview_up($params=null)
|
||||
{
|
||||
$models = array(
|
||||
'IDF_Review',
|
||||
'IDF_Review_Patch',
|
||||
'IDF_Review_FileComment',
|
||||
);
|
||||
$db = Pluf::db();
|
||||
$schema = new Pluf_DB_Schema($db);
|
||||
foreach ($models as $model) {
|
||||
$schema->model = new $model();
|
||||
$schema->createTables();
|
||||
}
|
||||
}
|
||||
|
||||
function IDF_Migrations_8CodeReview_down($params=null)
|
||||
{
|
||||
$models = array(
|
||||
'IDF_Review_FileComment',
|
||||
'IDF_Review_Patch',
|
||||
'IDF_Review',
|
||||
);
|
||||
$db = Pluf::db();
|
||||
$schema = new Pluf_DB_Schema($db);
|
||||
foreach ($models as $model) {
|
||||
$schema->model = new $model();
|
||||
$schema->dropTables();
|
||||
}
|
||||
}
|
@ -42,6 +42,9 @@ function IDF_Migrations_Install_setup($params=null)
|
||||
'IDF_Timeline',
|
||||
'IDF_WikiPage',
|
||||
'IDF_WikiRevision',
|
||||
'IDF_Review',
|
||||
'IDF_Review_Patch',
|
||||
'IDF_Review_FileComment',
|
||||
);
|
||||
$db = Pluf::db();
|
||||
$schema = new Pluf_DB_Schema($db);
|
||||
@ -79,6 +82,9 @@ function IDF_Migrations_Install_teardown($params=null)
|
||||
$perm = Pluf_Permission::getFromString('IDF.project-authorized-user');
|
||||
if ($perm) $perm->delete();
|
||||
$models = array(
|
||||
'IDF_Review_FileComment',
|
||||
'IDF_Review_Patch',
|
||||
'IDF_Review',
|
||||
'IDF_WikiRevision',
|
||||
'IDF_WikiPage',
|
||||
'IDF_Timeline',
|
||||
|
@ -152,6 +152,15 @@ class IDF_Precondition
|
||||
return self::accessTabGeneric($request, 'wiki_access_rights');
|
||||
}
|
||||
|
||||
static public function accessReview($request)
|
||||
{
|
||||
$res = self::baseAccess($request);
|
||||
if (true !== $res) {
|
||||
return $res;
|
||||
}
|
||||
return self::accessTabGeneric($request, 'review_access_rights');
|
||||
}
|
||||
|
||||
/**
|
||||
* Based on the request, it is automatically setting the user.
|
||||
*
|
||||
|
168
src/IDF/Review.php
Normal file
168
src/IDF/Review.php
Normal file
@ -0,0 +1,168 @@
|
||||
<?php
|
||||
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/*
|
||||
# ***** BEGIN LICENSE BLOCK *****
|
||||
# This file is part of InDefero, an open source project management application.
|
||||
# Copyright (C) 2008 Céondo Ltd and contributors.
|
||||
#
|
||||
# InDefero is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# InDefero is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
#
|
||||
# ***** END LICENSE BLOCK ***** */
|
||||
|
||||
Pluf::loadFunction('Pluf_HTTP_URL_urlForView');
|
||||
Pluf::loadFunction('Pluf_Template_dateAgo');
|
||||
|
||||
/**
|
||||
* Base definition of a code review.
|
||||
*
|
||||
* A code review has a status, submitter, summary, description and is
|
||||
* associated to a project.
|
||||
*
|
||||
* The real content of the review is in the IDF_Review_Patch which
|
||||
* contains a given patch and associated comments from reviewers.
|
||||
*/
|
||||
class IDF_Review extends Pluf_Model
|
||||
{
|
||||
public $_model = __CLASS__;
|
||||
|
||||
function init()
|
||||
{
|
||||
$this->_a['table'] = 'idf_reviews';
|
||||
$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' => 'reviews',
|
||||
),
|
||||
'summary' =>
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Varchar',
|
||||
'blank' => false,
|
||||
'size' => 250,
|
||||
'verbose' => __('summary'),
|
||||
),
|
||||
'submitter' =>
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Foreignkey',
|
||||
'model' => 'Pluf_User',
|
||||
'blank' => false,
|
||||
'verbose' => __('submitter'),
|
||||
'relate_name' => 'submitted_review',
|
||||
),
|
||||
'reviewers' =>
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Manytomany',
|
||||
'model' => 'Pluf_User',
|
||||
'blank' => true,
|
||||
'help_text' => 'Reviewers will get an email notification when the review is changed.',
|
||||
),
|
||||
'tags' =>
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Manytomany',
|
||||
'blank' => true,
|
||||
'model' => 'IDF_Tag',
|
||||
'verbose' => __('labels'),
|
||||
),
|
||||
'status' =>
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Foreignkey',
|
||||
'blank' => false,
|
||||
'model' => 'IDF_Tag',
|
||||
'verbose' => __('status'),
|
||||
),
|
||||
'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',
|
||||
),
|
||||
);
|
||||
$table = $this->_con->pfx.'idf_review_idf_tag_assoc';
|
||||
$this->_a['views'] = array(
|
||||
'join_tags' =>
|
||||
array(
|
||||
'join' => 'LEFT JOIN '.$table
|
||||
.' ON idf_issue_id=id',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
function __toString()
|
||||
{
|
||||
return $this->id.' - '.$this->summary;
|
||||
}
|
||||
|
||||
function _toIndex()
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
function preDelete()
|
||||
{
|
||||
IDF_Timeline::remove($this);
|
||||
IDF_Search::remove($this);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an HTML fragment used to display this review 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)
|
||||
{
|
||||
return '';
|
||||
}
|
||||
}
|
113
src/IDF/Review/FileComment.php
Normal file
113
src/IDF/Review/FileComment.php
Normal file
@ -0,0 +1,113 @@
|
||||
<?php
|
||||
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/*
|
||||
# ***** BEGIN LICENSE BLOCK *****
|
||||
# This file is part of InDefero, an open source project management application.
|
||||
# Copyright (C) 2008 Céondo Ltd and contributors.
|
||||
#
|
||||
# InDefero is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# InDefero is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
#
|
||||
# ***** END LICENSE BLOCK ***** */
|
||||
|
||||
/**
|
||||
* A comment to a file affected by a patch.
|
||||
*
|
||||
*/
|
||||
class IDF_Review_FileComment extends Pluf_Model
|
||||
{
|
||||
public $_model = __CLASS__;
|
||||
|
||||
function init()
|
||||
{
|
||||
$this->_a['table'] = 'idf_review_filecomments';
|
||||
$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,
|
||||
),
|
||||
'patch' =>
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Foreignkey',
|
||||
'model' => 'IDF_Review_Patch',
|
||||
'blank' => false,
|
||||
'verbose' => __('patch'),
|
||||
'relate_name' => 'filecomments',
|
||||
),
|
||||
'cfile' =>
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Varchar',
|
||||
'blank' => false,
|
||||
'size' => 250,
|
||||
'help_text' => 'The changed file, for example src/foo/bar.txt, this is the path to access it in the repository.',
|
||||
),
|
||||
'content' =>
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Text',
|
||||
'blank' => false,
|
||||
'verbose' => __('comment'),
|
||||
),
|
||||
'submitter' =>
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Foreignkey',
|
||||
'model' => 'Pluf_User',
|
||||
'blank' => false,
|
||||
'verbose' => __('submitter'),
|
||||
'relate_name' => 'commented_patched_files',
|
||||
),
|
||||
'creation_dtime' =>
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Datetime',
|
||||
'blank' => true,
|
||||
'verbose' => __('creation date'),
|
||||
),
|
||||
);
|
||||
$this->_a['idx'] = array(
|
||||
'creation_dtime_idx' =>
|
||||
array(
|
||||
'col' => 'creation_dtime',
|
||||
'type' => 'normal',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
function _toIndex()
|
||||
{
|
||||
return $this->cfile.' '.$this->content;
|
||||
}
|
||||
|
||||
function preDelete()
|
||||
{
|
||||
IDF_Timeline::remove($this);
|
||||
}
|
||||
|
||||
function preSave($create=false)
|
||||
{
|
||||
if ($this->id == '') {
|
||||
$this->creation_dtime = gmdate('Y-m-d H:i:s');
|
||||
}
|
||||
}
|
||||
|
||||
function postSave($create=false)
|
||||
{
|
||||
}
|
||||
|
||||
public function timelineFragment($request)
|
||||
{
|
||||
return '';
|
||||
}
|
||||
}
|
118
src/IDF/Review/Patch.php
Normal file
118
src/IDF/Review/Patch.php
Normal 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 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 patch to be reviewed.
|
||||
*
|
||||
*/
|
||||
class IDF_Review_Patch extends Pluf_Model
|
||||
{
|
||||
public $_model = __CLASS__;
|
||||
|
||||
function init()
|
||||
{
|
||||
$this->_a['table'] = 'idf_review_patches';
|
||||
$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,
|
||||
),
|
||||
'review' =>
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Foreignkey',
|
||||
'model' => 'IDF_Review',
|
||||
'blank' => false,
|
||||
'verbose' => __('review'),
|
||||
'relate_name' => 'patches',
|
||||
),
|
||||
'summary' =>
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Varchar',
|
||||
'blank' => false,
|
||||
'size' => 250,
|
||||
'verbose' => __('summary'),
|
||||
),
|
||||
'commit' =>
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Foreignkey',
|
||||
'model' => 'IDF_Commit',
|
||||
'blank' => false,
|
||||
'verbose' => __('commit'),
|
||||
'relate_name' => 'patches',
|
||||
),
|
||||
'description' =>
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Text',
|
||||
'blank' => false,
|
||||
'verbose' => __('description'),
|
||||
),
|
||||
'patch' =>
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_File',
|
||||
'blank' => false,
|
||||
'verbose' => __('patch'),
|
||||
'help_text' => 'The patch is stored at the same place as the issue attachments with the same approach for the name.',
|
||||
),
|
||||
'creation_dtime' =>
|
||||
array(
|
||||
'type' => 'Pluf_DB_Field_Datetime',
|
||||
'blank' => true,
|
||||
'verbose' => __('creation date'),
|
||||
),
|
||||
);
|
||||
$this->_a['idx'] = array(
|
||||
'creation_dtime_idx' =>
|
||||
array(
|
||||
'col' => 'creation_dtime',
|
||||
'type' => 'normal',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
function _toIndex()
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
function preDelete()
|
||||
{
|
||||
}
|
||||
|
||||
function preSave($create=false)
|
||||
{
|
||||
if ($this->id == '') {
|
||||
$this->creation_dtime = gmdate('Y-m-d H:i:s');
|
||||
}
|
||||
}
|
||||
|
||||
function postSave($create=false)
|
||||
{
|
||||
}
|
||||
|
||||
public function timelineFragment($request)
|
||||
{
|
||||
}
|
||||
}
|
@ -55,7 +55,7 @@ class IDF_Scm
|
||||
$cache = Pluf_Cache::factory();
|
||||
if (null === ($res=$cache->get($key))) {
|
||||
$ll = exec($command, $output, $return);
|
||||
if ($return != 0 and Pluf::f('debug', false)) {
|
||||
if ($return != 0 and Pluf::f('debug_scm', false)) {
|
||||
throw new IDF_Scm_Exception(sprintf('Error when running command: "%s", return code: %d', $command, $return));
|
||||
}
|
||||
$cache->set($key, array($ll, $return, $output));
|
||||
|
@ -54,4 +54,21 @@ class IDF_Tests_TestDiff extends UnitTestCase
|
||||
$i++;
|
||||
}
|
||||
}
|
||||
|
||||
public function testBinaryDiff()
|
||||
{
|
||||
$diff_content = file_get_contents(dirname(__FILE__).'/test-diff.diff');
|
||||
$orig = file_get_contents(dirname(__FILE__).'/test-diff-view.html');
|
||||
$diff = new IDF_Diff($diff_content);
|
||||
$diff->parse();
|
||||
$def = $diff->files['src/IDF/templates/idf/issues/view.html'];
|
||||
|
||||
$orig_lines = preg_split("/\015\012|\015|\012/", $orig);
|
||||
$merged = $diff->mergeChunks($orig_lines, $def, 10);
|
||||
$lchunk = end($merged);
|
||||
$lline = end($lchunk);
|
||||
$this->assertEqual(array('', '166', '{/if}{/block}'),
|
||||
$lline);
|
||||
//print_r($diff->mergeChunks($orig_lines, $def, 10));
|
||||
}
|
||||
}
|
125
src/IDF/Tests/test-diff-view.html
Normal file
125
src/IDF/Tests/test-diff-view.html
Normal file
@ -0,0 +1,125 @@
|
||||
{extends "idf/issues/base.html"}
|
||||
{block titleicon}{if $form}<form class="star" method="post" action="{url 'IDF_Views_Issue::star', array($project.shortname, $issue.id)}"><input type="image" src="{if $starred}{media '/idf/img/star.png'}{else}{media '/idf/img/star-grey.png'}{/if}" name="submit" /></form> {/if}{/block}
|
||||
{block body}
|
||||
{assign $i = 0}
|
||||
{assign $nc = $comments.count()}
|
||||
{foreach $comments as $c}
|
||||
<div class="issue-comment{if $i == 0} issue-comment-first{/if}{if $i == ($nc-1)} issue-comment-last{/if}" id="ic{$c.id}">{assign $who = $c.get_submitter()}{aurl 'whourl', 'IDF_Views_User::view', array($who.login)}
|
||||
{if $i == 0}
|
||||
<p>{blocktrans}Reported by <a href="{$whourl}">{$who}</a>, {$c.creation_dtime|date}{/blocktrans}</p>
|
||||
{else}
|
||||
{aurl 'url', 'IDF_Views_Issue::view', array($project.shortname, $issue.id)}
|
||||
{assign $id = $c.id}
|
||||
{assign $url = $url~'#ic'~$c.id}
|
||||
<p>{blocktrans}Comment <a href="{$url}">{$i}</a> by <a href="{$whourl}">{$who}</a>, {$c.creation_dtime|date}{/blocktrans}</p>
|
||||
{/if}
|
||||
|
||||
<pre class="issue-comment-text">{if strlen($c.content) > 0}{issuetext $c.content, $request}{else}<i>{trans '(No comments were given for this change.)'}</i>{/if}</pre>
|
||||
{assign $attachments = $c.get_attachment_list()}
|
||||
{if $attachments.count() > 0}
|
||||
<hr align="left" class="attach" />
|
||||
<ul>
|
||||
{foreach $attachments as $a}<li><a href="{url 'IDF_Views_Issue::getAttachment', array($project.shortname, $a.id, $a.filename)}">{$a.filename}</a> - {$a.filesize|size}</li>{/foreach}
|
||||
</ul>{/if}
|
||||
{if $i> 0 and $c.changedIssue()}
|
||||
<div class="issue-changes">
|
||||
{foreach $c.changes as $w => $v}
|
||||
<strong>{if $w == 'su'}{trans 'Summary:'}{/if}{if $w == 'st'}{trans 'Status:'}{/if}{if $w == 'ow'}{trans 'Owner:'}{/if}{if $w == 'lb'}{trans 'Labels:'}{/if}</strong> {if $w == 'lb'}{assign $l = implode(', ', $v)}{$l}{else}{$v}{/if}<br />
|
||||
{/foreach}
|
||||
</div>
|
||||
{/if}
|
||||
</div>{assign $i = $i + 1}{if $i == $nc and false == $form}
|
||||
<div class="issue-comment-signin">
|
||||
{aurl 'url', 'IDF_Views::login'}{blocktrans}<a href="{$url}">Sign in</a> to reply to this comment.{/blocktrans}
|
||||
</div>
|
||||
{/if}
|
||||
{/foreach}
|
||||
|
||||
{if $form}
|
||||
<hr />
|
||||
|
||||
{if $form.errors}
|
||||
<div class="px-message-error">
|
||||
<p>{trans 'The form contains some errors. Please correct them to change the issue.'}</p>
|
||||
{if $form.get_top_errors}
|
||||
{$form.render_top_errors|unsafe}
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{if $closed and (!$isOwner and !$isMember)}
|
||||
<p><img src="{media '/idf/img/warning.png'}" style="vertical-align: text-bottom;" alt=" " /> {blocktrans}This issue is marked as closed, add a comment only if you think this issue is still valid and more work is needed to fully fix it.{/blocktrans}</p>
|
||||
|
||||
{/if}
|
||||
<form method="post" enctype="multipart/form-data" action="{url 'IDF_Views_Issue::view', array($project.shortname, $issue.id)}" >
|
||||
<table class="form" summary="">
|
||||
<tr>
|
||||
<th><strong>{$form.f.content.labelTag}:</strong></th>
|
||||
<td>{if $form.f.content.errors}{$form.f.content.fieldErrors}{/if}
|
||||
{$form.f.content|unsafe}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{$form.f.attachment.labelTag}:</th>
|
||||
<td>{if $form.f.attachment.errors}{$form.f.attachment.fieldErrors}{/if}
|
||||
{$form.f.attachment|unsafe}
|
||||
</td>
|
||||
</tr>{if $isOwner or $isMember}
|
||||
<tr>
|
||||
<th><strong>{$form.f.summary.labelTag}:</strong></th>
|
||||
<td>{if $form.f.summary.errors}{$form.f.summary.fieldErrors}{/if}
|
||||
{$form.f.summary|unsafe}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><strong>{$form.f.status.labelTag}:</strong></th>
|
||||
<td>{if $form.f.status.errors}{$form.f.status.fieldErrors}{/if}
|
||||
{$form.f.status|unsafe}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{$form.f.owner.labelTag}:</th>
|
||||
<td>{if $form.f.owner.errors}{$form.f.owner.fieldErrors}{/if}
|
||||
{$form.f.owner|unsafe}
|
||||
</td>
|
||||
</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}
|
||||
{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>{/if}
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td><input type="submit" value="{trans 'Submit Changes'}" name="submit" /> | <a href="{url 'IDF_Views_Issue::view', array($project.shortname, $issue.id)}">{trans 'Cancel'}</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
{/if}
|
||||
{/block}
|
||||
{block context}
|
||||
<div class="issue-info">
|
||||
{assign $submitter = $issue.get_submitter()}{aurl 'url', 'IDF_Views_User::view', array($submitter.login)}
|
||||
<p><strong>{trans 'Created:'}</strong> <span class="nobrk">{$issue.creation_dtime|dateago}</span> <span class="nobrk">{blocktrans}by <a href="{$url}">{$submitter}</a>{/blocktrans}</span></p>
|
||||
{if $issue.modif_dtime != $issue.creation_dtime}<p>
|
||||
<strong>{trans 'Updated:'}</strong> <span class="nobrk">{$issue.modif_dtime|dateago}</span></p>{/if}
|
||||
<p>
|
||||
<strong>{trans 'Status:'}</strong> {$issue.get_status.name}</p>
|
||||
{if $issue.get_owner != null}<p>{aurl 'url', 'IDF_Views_User::view', array($issue.get_owner().login)}
|
||||
<strong>{trans 'Owner:'}</strong> <a href="{$url}">{$issue.get_owner}</a>
|
||||
</p>{/if}{assign $tags = $issue.get_tags_list()}{if $tags.count()}
|
||||
<p>
|
||||
<strong>{trans 'Labels:'}</strong><br />
|
||||
{foreach $tags as $tag}{aurl 'url', 'IDF_Views_Issue::listLabel', array($project.shortname, $tag.id, 'open')}
|
||||
<span class="label"><a href="{$url}" class="label"><strong>{$tag.class}:</strong>{$tag.name}</a></span><br />
|
||||
{/foreach}
|
||||
</p>{/if}
|
||||
</div>
|
||||
{/block}
|
||||
{block javascript}{if $form}{include 'idf/issues/js-autocomplete.html'}{/if}{/block}
|
515
src/IDF/Tests/test-diff.diff
Normal file
515
src/IDF/Tests/test-diff.diff
Normal file
@ -0,0 +1,515 @@
|
||||
diff --git a/src/IDF/Form/IssueCreate.php b/src/IDF/Form/IssueCreate.php
|
||||
index 0743e72..67afca7 100644
|
||||
--- a/src/IDF/Form/IssueCreate.php
|
||||
+++ b/src/IDF/Form/IssueCreate.php
|
||||
@@ -72,8 +72,9 @@ class IDF_Form_IssueCreate extends Pluf_Form
|
||||
// We add .dummy to try to mitigate security issues in the
|
||||
// case of someone allowing the upload path to be accessible
|
||||
// to everybody.
|
||||
- $filename = substr($md5, 0, 2).'/'.substr($md5, 2, 2).'/'.substr($md5, 4).'/%s.dummy';
|
||||
- $this->fields['attachment'] = new Pluf_Form_Field_File(
|
||||
+ for ($i=1;$i<4;$i++) {
|
||||
+ $filename = substr($md5, 0, 2).'/'.substr($md5, 2, 2).'/'.substr($md5, 4).'/%s.dummy';
|
||||
+ $this->fields['attachment'.$i] = new Pluf_Form_Field_File(
|
||||
array('required' => false,
|
||||
'label' => __('Attach a file'),
|
||||
'move_function_params' =>
|
||||
@@ -83,6 +84,7 @@ class IDF_Form_IssueCreate extends Pluf_Form
|
||||
)
|
||||
)
|
||||
);
|
||||
+ }
|
||||
|
||||
if ($this->show_full) {
|
||||
$this->fields['status'] = new Pluf_Form_Field_Varchar(
|
||||
@@ -195,6 +197,21 @@ class IDF_Form_IssueCreate extends Pluf_Form
|
||||
}
|
||||
|
||||
/**
|
||||
+ * Clean the attachments post failure.
|
||||
+ */
|
||||
+ function failed()
|
||||
+ {
|
||||
+ $upload_path = Pluf::f('upload_issue_path', false);
|
||||
+ if ($upload_path == false) return;
|
||||
+ for ($i=1;$i<4;$i++) {
|
||||
+ if (!empty($this->cleaned_data['attachment'.$i]) and
|
||||
+ file_exists($upload_path.'/'.$this->cleaned_data['attachment'.$i])) {
|
||||
+ @unlink($upload_path.'/'.$this->cleaned_data['attachment'.$i]);
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ /**
|
||||
* Save the model in the database.
|
||||
*
|
||||
* @param bool Commit in the database or not. If not, the object
|
||||
@@ -203,61 +220,63 @@ class IDF_Form_IssueCreate extends Pluf_Form
|
||||
*/
|
||||
function save($commit=true)
|
||||
{
|
||||
- if ($this->isValid()) {
|
||||
- // Add a tag for each label
|
||||
- $tags = array();
|
||||
- if ($this->show_full) {
|
||||
- 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]);
|
||||
- }
|
||||
- $tags[] = IDF_Tag::add($name, $this->project, $class);
|
||||
+ if (!$this->isValid()) {
|
||||
+ throw new Exception(__('Cannot save the model from an invalid form.'));
|
||||
+ }
|
||||
+ // Add a tag for each label
|
||||
+ $tags = array();
|
||||
+ if ($this->show_full) {
|
||||
+ 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]);
|
||||
}
|
||||
+ $tags[] = IDF_Tag::add($name, $this->project, $class);
|
||||
}
|
||||
- } else {
|
||||
- $tags[] = IDF_Tag::add('Medium', $this->project, 'Priority');
|
||||
- $tags[] = IDF_Tag::add('Defect', $this->project, 'Type');
|
||||
- }
|
||||
- // Create the issue
|
||||
- $issue = new IDF_Issue();
|
||||
- $issue->project = $this->project;
|
||||
- $issue->submitter = $this->user;
|
||||
- if ($this->show_full) {
|
||||
- $issue->status = IDF_Tag::add(trim($this->cleaned_data['status']), $this->project, 'Status');
|
||||
- $issue->owner = self::findUser($this->cleaned_data['owner']);
|
||||
- } else {
|
||||
- $_t = $this->project->getTagIdsByStatus('open');
|
||||
- $issue->status = new IDF_Tag($_t[0]); // first one is the default
|
||||
- $issue->owner = null;
|
||||
- }
|
||||
- $issue->summary = trim($this->cleaned_data['summary']);
|
||||
- $issue->create();
|
||||
- foreach ($tags as $tag) {
|
||||
- $issue->setAssoc($tag);
|
||||
}
|
||||
- // add the first comment
|
||||
- $comment = new IDF_IssueComment();
|
||||
- $comment->issue = $issue;
|
||||
- $comment->content = $this->cleaned_data['content'];
|
||||
- $comment->submitter = $this->user;
|
||||
- $comment->create();
|
||||
- // If we have a file, create the IDF_IssueFile and attach
|
||||
- // it to the comment.
|
||||
- if ($this->cleaned_data['attachment']) {
|
||||
+ } else {
|
||||
+ $tags[] = IDF_Tag::add('Medium', $this->project, 'Priority');
|
||||
+ $tags[] = IDF_Tag::add('Defect', $this->project, 'Type');
|
||||
+ }
|
||||
+ // Create the issue
|
||||
+ $issue = new IDF_Issue();
|
||||
+ $issue->project = $this->project;
|
||||
+ $issue->submitter = $this->user;
|
||||
+ if ($this->show_full) {
|
||||
+ $issue->status = IDF_Tag::add(trim($this->cleaned_data['status']), $this->project, 'Status');
|
||||
+ $issue->owner = self::findUser($this->cleaned_data['owner']);
|
||||
+ } else {
|
||||
+ $_t = $this->project->getTagIdsByStatus('open');
|
||||
+ $issue->status = new IDF_Tag($_t[0]); // first one is the default
|
||||
+ $issue->owner = null;
|
||||
+ }
|
||||
+ $issue->summary = trim($this->cleaned_data['summary']);
|
||||
+ $issue->create();
|
||||
+ foreach ($tags as $tag) {
|
||||
+ $issue->setAssoc($tag);
|
||||
+ }
|
||||
+ // add the first comment
|
||||
+ $comment = new IDF_IssueComment();
|
||||
+ $comment->issue = $issue;
|
||||
+ $comment->content = $this->cleaned_data['content'];
|
||||
+ $comment->submitter = $this->user;
|
||||
+ $comment->create();
|
||||
+ // If we have a file, create the IDF_IssueFile and attach
|
||||
+ // it to the comment.
|
||||
+ for ($i=1;$i<4;$i++) {
|
||||
+ if ($this->cleaned_data['attachment'.$i]) {
|
||||
$file = new IDF_IssueFile();
|
||||
- $file->attachment = $this->cleaned_data['attachment'];
|
||||
+ $file->attachment = $this->cleaned_data['attachment'.$i];
|
||||
$file->submitter = $this->user;
|
||||
$file->comment = $comment;
|
||||
$file->create();
|
||||
}
|
||||
- return $issue;
|
||||
}
|
||||
- throw new Exception(__('Cannot save the model from an invalid form.'));
|
||||
+ return $issue;
|
||||
}
|
||||
|
||||
/**
|
||||
diff --git a/src/IDF/Form/IssueUpdate.php b/src/IDF/Form/IssueUpdate.php
|
||||
index 550889e..0d36e72 100644
|
||||
--- a/src/IDF/Form/IssueUpdate.php
|
||||
+++ b/src/IDF/Form/IssueUpdate.php
|
||||
@@ -68,8 +68,9 @@ class IDF_Form_IssueUpdate extends IDF_Form_IssueCreate
|
||||
// We add .dummy to try to mitigate security issues in the
|
||||
// case of someone allowing the upload path to be accessible
|
||||
// to everybody.
|
||||
- $filename = substr($md5, 0, 2).'/'.substr($md5, 2, 2).'/'.substr($md5, 4).'/%s.dummy';
|
||||
- $this->fields['attachment'] = new Pluf_Form_Field_File(
|
||||
+ for ($i=1;$i<4;$i++) {
|
||||
+ $filename = substr($md5, 0, 2).'/'.substr($md5, 2, 2).'/'.substr($md5, 4).'/%s.dummy';
|
||||
+ $this->fields['attachment'.$i] = new Pluf_Form_Field_File(
|
||||
array('required' => false,
|
||||
'label' => __('Attach a file'),
|
||||
'move_function_params' =>
|
||||
@@ -79,6 +80,7 @@ class IDF_Form_IssueUpdate extends IDF_Form_IssueCreate
|
||||
)
|
||||
)
|
||||
);
|
||||
+ }
|
||||
|
||||
if ($this->show_full) {
|
||||
$this->fields['status'] = new Pluf_Form_Field_Varchar(
|
||||
@@ -124,6 +126,21 @@ class IDF_Form_IssueUpdate extends IDF_Form_IssueCreate
|
||||
}
|
||||
|
||||
/**
|
||||
+ * Clean the attachments post failure.
|
||||
+ */
|
||||
+ function failed()
|
||||
+ {
|
||||
+ $upload_path = Pluf::f('upload_issue_path', false);
|
||||
+ if ($upload_path == false) return;
|
||||
+ for ($i=1;$i<4;$i++) {
|
||||
+ if (!empty($this->cleaned_data['attachment'.$i]) and
|
||||
+ file_exists($upload_path.'/'.$this->cleaned_data['attachment'.$i])) {
|
||||
+ @unlink($upload_path.'/'.$this->cleaned_data['attachment'.$i]);
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ /**
|
||||
* We check that something is really changed.
|
||||
*/
|
||||
public function clean()
|
||||
@@ -202,90 +219,92 @@ class IDF_Form_IssueUpdate extends IDF_Form_IssueCreate
|
||||
*/
|
||||
function save($commit=true)
|
||||
{
|
||||
- if ($this->isValid()) {
|
||||
- if ($this->show_full) {
|
||||
- // Add a tag for each label
|
||||
- $tags = array();
|
||||
- $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::add($name, $this->project, $class);
|
||||
- $tags[] = $tag;
|
||||
- $tagids[] = $tag->id;
|
||||
+ if (!$this->isValid()) {
|
||||
+ throw new Exception(__('Cannot save the model from an invalid form.'));
|
||||
+ }
|
||||
+ if ($this->show_full) {
|
||||
+ // Add a tag for each label
|
||||
+ $tags = array();
|
||||
+ $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::add($name, $this->project, $class);
|
||||
+ $tags[] = $tag;
|
||||
+ $tagids[] = $tag->id;
|
||||
}
|
||||
- // Compare between the old and the new data
|
||||
- $changes = array();
|
||||
- $oldtags = $this->issue->get_tags_list();
|
||||
- foreach ($tags as $tag) {
|
||||
- if (!Pluf_Model_InArray($tag, $oldtags)) {
|
||||
- if (!isset($changes['lb'])) $changes['lb'] = array();
|
||||
- if ($tag->class != 'Other') {
|
||||
- $changes['lb'][] = (string) $tag; //new tag
|
||||
- } else {
|
||||
- $changes['lb'][] = (string) $tag->name;
|
||||
- }
|
||||
+ }
|
||||
+ // Compare between the old and the new data
|
||||
+ $changes = array();
|
||||
+ $oldtags = $this->issue->get_tags_list();
|
||||
+ foreach ($tags as $tag) {
|
||||
+ if (!Pluf_Model_InArray($tag, $oldtags)) {
|
||||
+ if (!isset($changes['lb'])) $changes['lb'] = array();
|
||||
+ if ($tag->class != 'Other') {
|
||||
+ $changes['lb'][] = (string) $tag; //new tag
|
||||
+ } else {
|
||||
+ $changes['lb'][] = (string) $tag->name;
|
||||
}
|
||||
}
|
||||
- foreach ($oldtags as $tag) {
|
||||
- if (!Pluf_Model_InArray($tag, $tags)) {
|
||||
- if (!isset($changes['lb'])) $changes['lb'] = array();
|
||||
- if ($tag->class != 'Other') {
|
||||
- $changes['lb'][] = '-'.(string) $tag; //new tag
|
||||
- } else {
|
||||
- $changes['lb'][] = '-'.(string) $tag->name;
|
||||
- }
|
||||
+ }
|
||||
+ foreach ($oldtags as $tag) {
|
||||
+ if (!Pluf_Model_InArray($tag, $tags)) {
|
||||
+ if (!isset($changes['lb'])) $changes['lb'] = array();
|
||||
+ if ($tag->class != 'Other') {
|
||||
+ $changes['lb'][] = '-'.(string) $tag; //new tag
|
||||
+ } else {
|
||||
+ $changes['lb'][] = '-'.(string) $tag->name;
|
||||
}
|
||||
}
|
||||
- // Status, summary and owner
|
||||
- $status = IDF_Tag::add(trim($this->cleaned_data['status']), $this->project, 'Status');
|
||||
- if ($status->id != $this->issue->status) {
|
||||
- $changes['st'] = $status->name;
|
||||
- }
|
||||
- if (trim($this->issue->summary) != trim($this->cleaned_data['summary'])) {
|
||||
- $changes['su'] = trim($this->cleaned_data['summary']);
|
||||
- }
|
||||
- $owner = self::findUser($this->cleaned_data['owner']);
|
||||
- if ((is_null($owner) and !is_null($this->issue->get_owner()))
|
||||
- or (!is_null($owner) and is_null($this->issue->get_owner()))
|
||||
- or ((!is_null($owner) and !is_null($this->issue->get_owner())) and $owner->id != $this->issue->get_owner()->id)) {
|
||||
- $changes['ow'] = (is_null($owner)) ? '---' : $owner->login;
|
||||
- }
|
||||
- // Update the issue
|
||||
- $this->issue->batchAssoc('IDF_Tag', $tagids);
|
||||
- $this->issue->summary = trim($this->cleaned_data['summary']);
|
||||
- $this->issue->status = $status;
|
||||
- $this->issue->owner = $owner;
|
||||
}
|
||||
- // Create the comment
|
||||
- $comment = new IDF_IssueComment();
|
||||
- $comment->issue = $this->issue;
|
||||
- $comment->content = $this->cleaned_data['content'];
|
||||
- $comment->submitter = $this->user;
|
||||
- if (!$this->show_full) $changes = array();
|
||||
- $comment->changes = $changes;
|
||||
- $comment->create();
|
||||
- $this->issue->update();
|
||||
- if ($this->issue->owner != $this->user->id and
|
||||
- $this->issue->submitter != $this->user->id) {
|
||||
- $this->issue->setAssoc($this->user); // interested user.
|
||||
+ // Status, summary and owner
|
||||
+ $status = IDF_Tag::add(trim($this->cleaned_data['status']), $this->project, 'Status');
|
||||
+ if ($status->id != $this->issue->status) {
|
||||
+ $changes['st'] = $status->name;
|
||||
}
|
||||
- if ($this->cleaned_data['attachment']) {
|
||||
+ if (trim($this->issue->summary) != trim($this->cleaned_data['summary'])) {
|
||||
+ $changes['su'] = trim($this->cleaned_data['summary']);
|
||||
+ }
|
||||
+ $owner = self::findUser($this->cleaned_data['owner']);
|
||||
+ if ((is_null($owner) and !is_null($this->issue->get_owner()))
|
||||
+ or (!is_null($owner) and is_null($this->issue->get_owner()))
|
||||
+ or ((!is_null($owner) and !is_null($this->issue->get_owner())) and $owner->id != $this->issue->get_owner()->id)) {
|
||||
+ $changes['ow'] = (is_null($owner)) ? '---' : $owner->login;
|
||||
+ }
|
||||
+ // Update the issue
|
||||
+ $this->issue->batchAssoc('IDF_Tag', $tagids);
|
||||
+ $this->issue->summary = trim($this->cleaned_data['summary']);
|
||||
+ $this->issue->status = $status;
|
||||
+ $this->issue->owner = $owner;
|
||||
+ }
|
||||
+ // Create the comment
|
||||
+ $comment = new IDF_IssueComment();
|
||||
+ $comment->issue = $this->issue;
|
||||
+ $comment->content = $this->cleaned_data['content'];
|
||||
+ $comment->submitter = $this->user;
|
||||
+ if (!$this->show_full) $changes = array();
|
||||
+ $comment->changes = $changes;
|
||||
+ $comment->create();
|
||||
+ $this->issue->update();
|
||||
+ if ($this->issue->owner != $this->user->id and
|
||||
+ $this->issue->submitter != $this->user->id) {
|
||||
+ $this->issue->setAssoc($this->user); // interested user.
|
||||
+ }
|
||||
+ for ($i=1;$i<4;$i++) {
|
||||
+ if ($this->cleaned_data['attachment'.$i]) {
|
||||
$file = new IDF_IssueFile();
|
||||
- $file->attachment = $this->cleaned_data['attachment'];
|
||||
+ $file->attachment = $this->cleaned_data['attachment'.$i];
|
||||
$file->submitter = $this->user;
|
||||
$file->comment = $comment;
|
||||
$file->create();
|
||||
}
|
||||
- return $this->issue;
|
||||
}
|
||||
- throw new Exception(__('Cannot save the model from an invalid form.'));
|
||||
+ return $this->issue;
|
||||
}
|
||||
}
|
||||
diff --git a/src/IDF/IssueFile.php b/src/IDF/IssueFile.php
|
||||
index f4367dd..f0745e8 100644
|
||||
--- a/src/IDF/IssueFile.php
|
||||
+++ b/src/IDF/IssueFile.php
|
||||
@@ -114,6 +114,7 @@ class IDF_IssueFile extends Pluf_Model
|
||||
$this->filename = substr(basename($file), 0, -6);
|
||||
$img_extensions = array('jpeg', 'jpg', 'png', 'gif');
|
||||
$info = pathinfo($this->filename);
|
||||
+ if (!isset($info['extension'])) $info['extension'] = '';
|
||||
if (in_array(strtolower($info['extension']), $img_extensions)) {
|
||||
$this->type = 'img';
|
||||
} else {
|
||||
diff --git a/src/IDF/templates/idf/issues/create.html b/src/IDF/templates/idf/issues/create.html
|
||||
index e8f4a5b..faaa743 100644
|
||||
--- a/src/IDF/templates/idf/issues/create.html
|
||||
+++ b/src/IDF/templates/idf/issues/create.html
|
||||
@@ -24,10 +24,22 @@
|
||||
{$form.f.content|unsafe}
|
||||
</td>
|
||||
</tr>
|
||||
-<tr>
|
||||
-<th>{$form.f.attachment.labelTag}:</th>
|
||||
-<td>{if $form.f.attachment.errors}{$form.f.attachment.fieldErrors}{/if}
|
||||
-{$form.f.attachment|unsafe}
|
||||
+<tr id="form-attachment-1">
|
||||
+<th>{$form.f.attachment1.labelTag}:</th>
|
||||
+<td>{if $form.f.attachment1.errors}{$form.f.attachment1.fieldErrors}{/if}
|
||||
+{$form.f.attachment1|unsafe}
|
||||
+</td>
|
||||
+</tr>
|
||||
+<tr id="form-attachment-2">
|
||||
+<th>{$form.f.attachment2.labelTag}:</th>
|
||||
+<td>{if $form.f.attachment2.errors}{$form.f.attachment2.fieldErrors}{/if}
|
||||
+{$form.f.attachment2|unsafe}
|
||||
+</td>
|
||||
+</tr>
|
||||
+<tr id="form-attachment-3">
|
||||
+<th>{$form.f.attachment3.labelTag}:</th>
|
||||
+<td>{if $form.f.attachment3.errors}{$form.f.attachment3.fieldErrors}{/if}
|
||||
+{$form.f.attachment3|unsafe}
|
||||
</td>
|
||||
</tr>{if $isOwner or $isMember}
|
||||
<tr>
|
||||
@@ -74,7 +86,34 @@
|
||||
{/block}
|
||||
{block javascript}
|
||||
<script type="text/javascript">
|
||||
-document.getElementById('id_summary').focus()
|
||||
+document.getElementById('id_summary').focus();{literal}
|
||||
+$(document).ready(function(){
|
||||
+
|
||||
+ // Hide the upload forms, we insert before the first attach file
|
||||
+ // row an "Attach File" little link.
|
||||
+ // We hide all the rows.
|
||||
+ $("#form-attachment-1").before("{/literal}<tr id=\"form-block-0\"><td> </td><td><img style=\"vertical-align: text-bottom;\" src=\"{media '/idf/img/attachment.png'}\" alt=\" \" align=\"bottom\" /><a id=\"form-show-0\" href=\"#\">{trans 'Attach file'}{literal}</a></td></tr>");
|
||||
+ $("#form-show-0").click(function(){
|
||||
+ $("#form-attachment-1").show();
|
||||
+ $("#form-block-0").hide();
|
||||
+ });
|
||||
+ $("#form-attachment-1 td").append("<span id=\"form-block-1\"><a id=\"form-show-1\" href=\"#\">{/literal}{trans 'Attach another file'}{literal}</a></span>");
|
||||
+ $("#form-show-1").click(function(){
|
||||
+ $("#form-attachment-2").show();
|
||||
+ $("#form-block-1").hide();
|
||||
+ });
|
||||
+ $("#form-attachment-2 td").append("<span id=\"form-block-2\"><a id=\"form-show-2\" href=\"#\">{/literal}{trans 'Attach another file'}{literal}</a></span>");
|
||||
+ $("#form-show-2").click(function(){
|
||||
+ $("#form-attachment-3").show();
|
||||
+ $("#form-block-2").hide();
|
||||
+ });
|
||||
+ var j=0;
|
||||
+ for (j=1;j<4;j=j+1) {
|
||||
+ $("#form-attachment-"+j).hide();
|
||||
+ }
|
||||
+ });
|
||||
</script>
|
||||
+{/literal}{/block}
|
||||
+
|
||||
{include 'idf/issues/js-autocomplete.html'}{/block}
|
||||
|
||||
diff --git a/src/IDF/templates/idf/issues/view.html b/src/IDF/templates/idf/issues/view.html
|
||||
index cb9e085..ad56d05 100644
|
||||
--- a/src/IDF/templates/idf/issues/view.html
|
||||
+++ b/src/IDF/templates/idf/issues/view.html
|
||||
@@ -59,10 +59,22 @@
|
||||
{$form.f.content|unsafe}
|
||||
</td>
|
||||
</tr>
|
||||
-<tr>
|
||||
-<th>{$form.f.attachment.labelTag}:</th>
|
||||
-<td>{if $form.f.attachment.errors}{$form.f.attachment.fieldErrors}{/if}
|
||||
-{$form.f.attachment|unsafe}
|
||||
+<tr id="form-attachment-1">
|
||||
+<th>{$form.f.attachment1.labelTag}:</th>
|
||||
+<td>{if $form.f.attachment1.errors}{$form.f.attachment1.fieldErrors}{/if}
|
||||
+{$form.f.attachment1|unsafe}
|
||||
+</td>
|
||||
+</tr>
|
||||
+<tr id="form-attachment-2">
|
||||
+<th>{$form.f.attachment2.labelTag}:</th>
|
||||
+<td>{if $form.f.attachment2.errors}{$form.f.attachment2.fieldErrors}{/if}
|
||||
+{$form.f.attachment2|unsafe}
|
||||
+</td>
|
||||
+</tr>
|
||||
+<tr id="form-attachment-3">
|
||||
+<th>{$form.f.attachment3.labelTag}:</th>
|
||||
+<td>{if $form.f.attachment3.errors}{$form.f.attachment3.fieldErrors}{/if}
|
||||
+{$form.f.attachment3|unsafe}
|
||||
</td>
|
||||
</tr>{if $isOwner or $isMember}
|
||||
<tr>
|
||||
@@ -122,4 +134,33 @@
|
||||
</p>{/if}
|
||||
</div>
|
||||
{/block}
|
||||
-{block javascript}{if $form}{include 'idf/issues/js-autocomplete.html'}{/if}{/block}
|
||||
+{block javascript}{if $form}{include 'idf/issues/js-autocomplete.html'}
|
||||
+<script type="text/javascript">
|
||||
+{literal}
|
||||
+$(document).ready(function(){
|
||||
+
|
||||
+ // Hide the upload forms, we insert before the first attach file
|
||||
+ // row an "Attach File" little link.
|
||||
+ // We hide all the rows.
|
||||
+ $("#form-attachment-1").before("{/literal}<tr id=\"form-block-0\"><td> </td><td><img style=\"vertical-align: text-bottom;\" src=\"{media '/idf/img/attachment.png'}\" alt=\" \" align=\"bottom\" /><a id=\"form-show-0\" href=\"#\">{trans 'Attach file'}{literal}</a></td></tr>");
|
||||
+ $("#form-show-0").click(function(){
|
||||
+ $("#form-attachment-1").show();
|
||||
+ $("#form-block-0").hide();
|
||||
+ });
|
||||
+ $("#form-attachment-1 td").append("<span id=\"form-block-1\"><a id=\"form-show-1\" href=\"#\">{/literal}{trans 'Attach another file'}{literal}</a></span>");
|
||||
+ $("#form-show-1").click(function(){
|
||||
+ $("#form-attachment-2").show();
|
||||
+ $("#form-block-1").hide();
|
||||
+ });
|
||||
+ $("#form-attachment-2 td").append("<span id=\"form-block-2\"><a id=\"form-show-2\" href=\"#\">{/literal}{trans 'Attach another file'}{literal}</a></span>");
|
||||
+ $("#form-show-2").click(function(){
|
||||
+ $("#form-attachment-3").show();
|
||||
+ $("#form-block-2").hide();
|
||||
+ });
|
||||
+ var j=0;
|
||||
+ for (j=1;j<4;j=j+1) {
|
||||
+ $("#form-attachment-"+j).hide();
|
||||
+ }
|
||||
+ });{/literal}
|
||||
+</script>
|
||||
+{/if}{/block}
|
||||
diff --git a/www/media/idf/img/attachment.png b/www/media/idf/img/attachment.png
|
||||
new file mode 100644
|
||||
index 0000000..529bb7f
|
||||
Binary files /dev/null and b/www/media/idf/img/attachment.png differ
|
@ -347,7 +347,7 @@ class IDF_Views_Project
|
||||
$params = array();
|
||||
$keys = array('downloads_access_rights', 'source_access_rights',
|
||||
'issues_access_rights', 'private_project',
|
||||
'wiki_access_rights');
|
||||
'review_access_rights', 'wiki_access_rights');
|
||||
foreach ($keys as $key) {
|
||||
$_val = $request->conf->getVal($key, false);
|
||||
if ($_val !== false) {
|
||||
|
227
src/IDF/Views/Review.php
Normal file
227
src/IDF/Views/Review.php
Normal 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 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_Shortcuts_RenderToResponse');
|
||||
Pluf::loadFunction('Pluf_Shortcuts_GetObjectOr404');
|
||||
Pluf::loadFunction('Pluf_Shortcuts_GetFormForModel');
|
||||
|
||||
/**
|
||||
* Review views.
|
||||
*/
|
||||
class IDF_Views_Review
|
||||
{
|
||||
/**
|
||||
* View list of reviews for a given project.
|
||||
*/
|
||||
public $index_precond = array('IDF_Precondition::accessReview');
|
||||
public function index($request, $match)
|
||||
{
|
||||
$prj = $request->project;
|
||||
$title = sprintf(__('%s Code Reviews'), (string) $prj);
|
||||
// Paginator to paginate the pages
|
||||
$pag = new Pluf_Paginator(new IDF_Review());
|
||||
$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 latest reviews.');
|
||||
$pag->action = array('IDF_Views_Review::index', array($prj->shortname));
|
||||
$otags = $prj->getTagIdsByStatus('open');
|
||||
if (count($otags) == 0) $otags[] = 0;
|
||||
$pag->forced_where = new Pluf_SQL('project=%s AND status IN ('.implode(', ', $otags).')', array($prj->id));
|
||||
$pag->action = array('IDF_Views_Issue::index', array($prj->shortname));
|
||||
$pag->sort_order = array('modif_dtime', 'ASC'); // will be reverted
|
||||
$pag->sort_reverse_order = array('modif_dtime');
|
||||
$list_display = array(
|
||||
'id' => __('Id'),
|
||||
array('summary', 'IDF_Views_Review_SummaryAndLabels', __('Summary')),
|
||||
array('status', 'IDF_Views_Issue_ShowStatus', __('Status')),
|
||||
array('modif_dtime', 'Pluf_Paginator_DateAgo', __('Last Updated')),
|
||||
);
|
||||
$pag->configure($list_display, array(), array('title', 'modif_dtime'));
|
||||
$pag->items_per_page = 25;
|
||||
$pag->no_results_text = __('No reviews were found.');
|
||||
$pag->sort_order = array('modif_dtime', 'ASC');
|
||||
$pag->setFromRequest($request);
|
||||
return Pluf_Shortcuts_RenderToResponse('idf/review/index.html',
|
||||
array(
|
||||
'page_title' => $title,
|
||||
'reviews' => $pag,
|
||||
),
|
||||
$request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new code review.
|
||||
*/
|
||||
public $create_precond = array('IDF_Precondition::accessReview',
|
||||
'Pluf_Precondition::loginRequired');
|
||||
public function create($request, $match)
|
||||
{
|
||||
$prj = $request->project;
|
||||
$title = __('Start Code Review');
|
||||
if ($request->method == 'POST') {
|
||||
$form = new IDF_Form_ReviewCreate(array_merge($request->POST,
|
||||
$request->FILES),
|
||||
array('project' => $prj,
|
||||
'user' => $request->user
|
||||
));
|
||||
if ($form->isValid()) {
|
||||
$review = $form->save();
|
||||
$urlr = Pluf_HTTP_URL_urlForView('IDF_Views_Review::view',
|
||||
array($prj->shortname, $review->id));
|
||||
$request->user->setMessage(sprintf(__('The <a href="%s">code review %d</a> has been created.'), $urlr, $review->id));
|
||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Review::index',
|
||||
array($prj->shortname));
|
||||
return new Pluf_HTTP_Response_Redirect($url);
|
||||
}
|
||||
} else {
|
||||
$form = new IDF_Form_ReviewCreate(null,
|
||||
array('project' => $prj,
|
||||
'user' => $request->user));
|
||||
}
|
||||
return Pluf_Shortcuts_RenderToResponse('idf/review/create.html',
|
||||
array(
|
||||
'page_title' => $title,
|
||||
'form' => $form,
|
||||
),
|
||||
$request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Download the patch of a review.
|
||||
*/
|
||||
public $getPatch_precond = array('IDF_Precondition::accessReview');
|
||||
public function getPatch($request, $match)
|
||||
{
|
||||
$prj = $request->project;
|
||||
$patch = Pluf_Shortcuts_GetObjectOr404('IDF_Review_Patch', $match[2]);
|
||||
$prj->inOr404($patch->get_review());
|
||||
$file = Pluf::f('upload_issue_path').'/'.$patch->patch;
|
||||
|
||||
$rep = new Pluf_HTTP_Response_File($file, 'text/plain');
|
||||
$rep->headers['Content-Disposition'] = 'attachment; filename="'.$patch->id.'.diff"';
|
||||
return $rep;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* View a code review.
|
||||
*/
|
||||
public $view_precond = array('IDF_Precondition::accessReview');
|
||||
public function view($request, $match)
|
||||
{
|
||||
$prj = $request->project;
|
||||
$review = Pluf_Shortcuts_GetObjectOr404('IDF_Review', $match[2]);
|
||||
$prj->inOr404($review);
|
||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Review::view',
|
||||
array($prj->shortname, $review->id));
|
||||
$title = Pluf_Template::markSafe(sprintf(__('Review <a href="%s">%d</a>: %s'), $url, $review->id, $review->summary));
|
||||
|
||||
$patches = $review->get_patches_list();
|
||||
$patch = $patches[0];
|
||||
$diff = new IDF_Diff(file_get_contents(Pluf::f('upload_issue_path').'/'.$patch->patch));
|
||||
$diff->parse();
|
||||
// The form to submit comments is based on the files in the
|
||||
// diff
|
||||
if ($request->method == 'POST' and !$request->user->isAnonymous()) {
|
||||
$form = new IDF_Form_ReviewFileComment($request->POST,
|
||||
array('files' => $diff->files,
|
||||
'user' => $request->user,
|
||||
'patch' => $patch,
|
||||
));
|
||||
if ($form->isValid()) {
|
||||
$patch = $form->save();
|
||||
$urlr = Pluf_HTTP_URL_urlForView('IDF_Views_Review::view',
|
||||
array($prj->shortname, $patch->get_review()->id));
|
||||
$request->user->setMessage(sprintf(__('Your <a href="%s">code review %d</a> has been published.'), $urlr, $patch->get_review()->id));
|
||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Review::index',
|
||||
array($prj->shortname));
|
||||
return new Pluf_HTTP_Response_Redirect($url);
|
||||
}
|
||||
} else {
|
||||
$form = new IDF_Form_ReviewFileComment(null,
|
||||
array('files' => $diff->files,
|
||||
'user' => $request->user,
|
||||
'patch' => $patch,));
|
||||
}
|
||||
$scm = IDF_Scm::get($request);
|
||||
$files = array();
|
||||
$reviewers = array();
|
||||
foreach ($diff->files as $filename => $def) {
|
||||
$fileinfo = $scm->getFileInfo($filename, $patch->get_commit()->scm_id);
|
||||
|
||||
$sql = new Pluf_SQL('cfile=%s', array($filename));
|
||||
$cts = $patch->get_filecomments_list(array('filter'=>$sql->gen(),
|
||||
'order'=>'creation_dtime ASC'));
|
||||
foreach ($cts as $ct) {
|
||||
$reviewers[] = $ct->get_submitter();
|
||||
}
|
||||
if (count($def['chunks'])) {
|
||||
$orig_file = $scm->getBlob($fileinfo);
|
||||
$files[$filename] = array(
|
||||
$diff->fileCompare($orig_file, $def, $filename),
|
||||
$form->f->{md5($filename)},
|
||||
$cts,
|
||||
);
|
||||
} else {
|
||||
$files[$filename] = array('', $form->f->{md5($filename)}, $cts);
|
||||
}
|
||||
}
|
||||
$reviewers = Pluf_Model_RemoveDuplicates($reviewers);
|
||||
return Pluf_Shortcuts_RenderToResponse('idf/review/view.html',
|
||||
array(
|
||||
'page_title' => $title,
|
||||
'review' => $review,
|
||||
'files' => $files,
|
||||
'diff' => $diff,
|
||||
'patch' => $patch,
|
||||
'form' => $form,
|
||||
'reviewers' => $reviewers,
|
||||
),
|
||||
$request);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the summary of an review, then on a new line, display the
|
||||
* list of labels with a link to a view "by label only".
|
||||
*
|
||||
* The summary of the review is linking to the review.
|
||||
*/
|
||||
function IDF_Views_Review_SummaryAndLabels($field, $review, $extra='')
|
||||
{
|
||||
$edit = Pluf_HTTP_URL_urlForView('IDF_Views_Review::view',
|
||||
array($review->shortname, $review->id));
|
||||
$tags = array();
|
||||
foreach ($review->get_tags_list() as $tag) {
|
||||
$tags[] = Pluf_esc($tag);
|
||||
}
|
||||
$out = '';
|
||||
if (count($tags)) {
|
||||
$out = '<br /><span class="label note">'.implode(', ', $tags).'</span>';
|
||||
}
|
||||
return sprintf('<a href="%s">%s</a>', $edit, Pluf_esc($review->summary)).$out;
|
||||
}
|
||||
|
@ -24,9 +24,12 @@
|
||||
$cfg = array();
|
||||
|
||||
#
|
||||
# You must set it to false once everything is running ok.
|
||||
# You must set them to false once everything is running ok.
|
||||
#
|
||||
$cfg['debug'] = true;
|
||||
# It will help you catch errors at beginning when configuring your
|
||||
# SCM backend. It must be turned off in production.
|
||||
$cfg['debug_scm'] = true;
|
||||
|
||||
# If you have a single git repository, just put the full path to it
|
||||
# without trailing slash. The path is the path to the git database,
|
||||
|
@ -283,6 +283,32 @@ $ctl[] = array('regex' => '#^/p/([\-\w]+)/downloads/(\d+)/delete/$#',
|
||||
'model' => 'IDF_Views_Download',
|
||||
'method' => 'delete');
|
||||
|
||||
// ---------- CODE REVIEW --------------------------------
|
||||
|
||||
$ctl[] = array('regex' => '#^/p/([\-\w]+)/review/$#',
|
||||
'base' => $base,
|
||||
'priority' => 4,
|
||||
'model' => 'IDF_Views_Review',
|
||||
'method' => 'index');
|
||||
|
||||
$ctl[] = array('regex' => '#^/p/([\-\w]+)/review/(\d+)/$#',
|
||||
'base' => $base,
|
||||
'priority' => 4,
|
||||
'model' => 'IDF_Views_Review',
|
||||
'method' => 'view');
|
||||
|
||||
$ctl[] = array('regex' => '#^/p/([\-\w]+)/review/create/$#',
|
||||
'base' => $base,
|
||||
'priority' => 4,
|
||||
'model' => 'IDF_Views_Review',
|
||||
'method' => 'create');
|
||||
|
||||
$ctl[] = array('regex' => '#^/p/([\-\w]+)/review/getpatch/(\d+)/$#',
|
||||
'base' => $base,
|
||||
'priority' => 4,
|
||||
'model' => 'IDF_Views_Review',
|
||||
'method' => 'getPatch');
|
||||
|
||||
|
||||
// ---------- ADMIN --------------------------------------
|
||||
|
||||
|
@ -33,5 +33,9 @@ $m['IDF_Search_Occ'] = array('relate_to' => array('IDF_Project'),);
|
||||
$m['IDF_WikiPage'] = array('relate_to' => array('IDF_Project', 'Pluf_User'),
|
||||
'relate_to_many' => array('IDF_Tag', 'Pluf_User'));
|
||||
$m['IDF_WikiRevision'] = array('relate_to' => array('IDF_WikiPage', 'Pluf_User'));
|
||||
$m['IDF_Review'] = array('relate_to' => array('IDF_Project', 'Pluf_User', 'IDF_Tag'),
|
||||
'relate_to_many' => array('IDF_Tag', 'Pluf_User'));
|
||||
$m['IDF_Review_Patch'] = array('relate_to' => array('IDF_Review', 'Pluf_User'));
|
||||
$m['IDF_Review_FileComment'] = array('relate_to' => array('IDF_Review_Patch', 'Pluf_User'));
|
||||
|
||||
return $m;
|
||||
|
@ -36,6 +36,12 @@
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<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}
|
||||
{$form.f.review_access_rights|unsafe}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{if $form.f.private_project.errors}{$form.f.private_project.fieldErrors}{/if}
|
||||
{$form.f.private_project|unsafe}
|
||||
</th>
|
||||
|
87
src/IDF/templates/idf/base-full.html
Normal file
87
src/IDF/templates/idf/base-full.html
Normal file
@ -0,0 +1,87 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
|
||||
{*
|
||||
# ***** BEGIN LICENSE BLOCK *****
|
||||
# This file is part of InDefero, an open source project management application.
|
||||
# Copyright (C) 2008 Céondo Ltd and contributors.
|
||||
#
|
||||
# InDefero is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# InDefero is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
#
|
||||
# ***** END LICENSE BLOCK *****
|
||||
*}<html lang="en">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<link rel="stylesheet" type="text/css" href="{media '/idf/css/yui.css'}" />
|
||||
<link rel="stylesheet" type="text/css" href="{media '/idf/css/style.css'}" />
|
||||
<!--[if lt IE 7]>
|
||||
<link rel="stylesheet" type="text/css" href="{media '/idf/css/ie6.css'}" />
|
||||
<![endif]-->
|
||||
{block extraheader}{/block}
|
||||
<title>{block pagetitle}{$page_title|strip_tags}{/block}</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="{block docid}doc3{/block}">
|
||||
<div id="hd">
|
||||
{if $project}<h1 class="project-title">{$project}</h1>{/if}
|
||||
<p class="top"><a href="#title" accesskey="2"></a>
|
||||
{if !$user.isAnonymous()}{aurl 'url', 'IDF_Views_User::myAccount'}{blocktrans}Welcome, <strong><a class="userw" href="{$url}">{$user}</a></strong>.{/blocktrans} <a href="{url 'IDF_Views::logout'}">{trans 'Sign Out'}</a>{else}<a href="{url 'IDF_Views::login'}">{trans 'Sign in or create your account'}</a>{/if}
|
||||
{if $project} | <a href="{url 'IDF_Views::index'}">{trans 'Project List'}</a>{/if}
|
||||
| <a href="{url 'IDF_Views::faq'}" title="{trans 'Help and accessibility features'}">{trans 'Help'}</a>
|
||||
</p>
|
||||
<div id="header">
|
||||
<div id="main-tabs">
|
||||
{if $project}
|
||||
<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 $hasWikiAccess} <a href="{url 'IDF_Views_Wiki::index', 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 $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 $isOwner}
|
||||
<a href="{url 'IDF_Views_Project::admin', array($project.shortname)}"{block tabadmin}{/block}>{trans 'Administer'}</a>{/if}{/if}
|
||||
</div>
|
||||
{block subtabs}{if $user.isAnonymous()} | {aurl 'url', 'IDF_Views::login'}{blocktrans}<a href="{$url}">Sign in or create your account</a> to create issues or add comments{/blocktrans}{/if}{/block}
|
||||
</div>
|
||||
|
||||
<h1 class="title" id="title">{block titleicon}{/block}{block title}{$page_title}{/block}</h1>
|
||||
|
||||
</div>
|
||||
<div id="bd">
|
||||
<div id="yui-main">
|
||||
<div class="yui-b">
|
||||
<div class="yui-g">
|
||||
{if $user and $user.id}{getmsgs $user}{/if}
|
||||
<div class="content">{block body}{/block}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="ft">{block foot}{/block}</div>
|
||||
</div>
|
||||
<script type="text/javascript" src="{media '/idf/js/jquery-1.2.6.min.js'}"></script>
|
||||
{include 'idf/js-hotkeys.html'}
|
||||
{block javascript}{/block}
|
||||
{if $project}
|
||||
<script type="text/javascript">{literal}
|
||||
<!-- //
|
||||
$(document).ready(function(){
|
||||
var frag = location.hash;
|
||||
if (frag.length > 3 && frag.substring(0, 3) == '#ic') {
|
||||
$(frag).addClass("issue-comment-focus");
|
||||
}
|
||||
});
|
||||
// -->{/literal}
|
||||
</script>{/if}
|
||||
</body>
|
||||
</html>
|
@ -51,7 +51,7 @@
|
||||
</div>
|
||||
<div class="yui-b context">{block context}{/block}</div>
|
||||
</div>
|
||||
<div id="ft">{block foot}<a href="http://www.indefero.net" title="InDefero, bug tracking and more"><img src="{media '/idf/img/powered-by-indefero.png'}" alt="InDefero Logo" /></a>{/block}</div>
|
||||
<div id="ft">{block foot}{/block}</div>
|
||||
</div>
|
||||
<script type="text/javascript" src="{media '/idf/js/jquery-1.2.6.min.js'}"></script>
|
||||
{include 'idf/js-hotkeys.html'}
|
||||
|
@ -47,6 +47,7 @@
|
||||
{if $hasWikiAccess} <a href="{url 'IDF_Views_Wiki::index', 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 $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 $isOwner}
|
||||
<a href="{url 'IDF_Views_Project::admin', array($project.shortname)}"{block tabadmin}{/block}>{trans 'Administer'}</a>{/if}{/if}
|
||||
</div>
|
||||
|
@ -13,3 +13,4 @@
|
||||
{block context}
|
||||
<p><strong>{trans 'Managed Projects:'}</strong> {$projects.count()}</p>
|
||||
{/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}
|
||||
|
@ -113,7 +113,6 @@ $(document).ready(function(){
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{/literal}{/block}
|
||||
|
||||
{/literal}
|
||||
{include 'idf/issues/js-autocomplete.html'}{/block}
|
||||
|
||||
|
@ -40,4 +40,4 @@
|
||||
</p>
|
||||
{/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}
|
||||
|
15
src/IDF/templates/idf/review/base-full.html
Normal file
15
src/IDF/templates/idf/review/base-full.html
Normal file
@ -0,0 +1,15 @@
|
||||
{extends "idf/base-full.html"}
|
||||
{block tabreview} class="active"{/block}
|
||||
{block subtabs}
|
||||
<div id="sub-tabs">
|
||||
<a {if $inOpenReviews}class="active" {/if}href="{url 'IDF_Views_Review::index', array($project.shortname)}">{trans 'Open Reviews'}</a> {*
|
||||
|
||||
{if !$user.isAnonymous()} | <a {if $inCreate}class="active" {/if}href="{url 'IDF_Views_Issue::create', array($project.shortname)}">{trans 'New Issue'}</a> | <a {if $inMyIssues}class="active" {/if}href="{url 'IDF_Views_Issue::myIssues', array($project.shortname, 'submit')}">{trans 'My Issues'}</a>{/if} |
|
||||
<form class="star" action="{url 'IDF_Views_Issue::search', array($project.shortname)}" method="get">
|
||||
<input accesskey="4" type="text" value="{$q}" name="q" size="20" />
|
||||
<input type="submit" name="s" value="{trans 'Search'}" />
|
||||
</form>
|
||||
*}
|
||||
{superblock}
|
||||
</div>
|
||||
{/block}
|
9
src/IDF/templates/idf/review/base.html
Normal file
9
src/IDF/templates/idf/review/base.html
Normal file
@ -0,0 +1,9 @@
|
||||
{extends "idf/base.html"}
|
||||
{block tabreview} class="active"{/block}
|
||||
{block subtabs}
|
||||
<div id="sub-tabs">
|
||||
<a {if $inOpenReviews}class="active" {/if}href="{url 'IDF_Views_Review::index', array($project.shortname)}">{trans 'Open Reviews'}</a>
|
||||
{if !$user.isAnonymous()} | <a {if $inCreate}class="active" {/if}href="{url 'IDF_Views_Review::create', array($project.shortname)}">{trans 'Start Code Review'}</a> {/if}
|
||||
{superblock}
|
||||
</div>
|
||||
{/block}
|
69
src/IDF/templates/idf/review/create.html
Normal file
69
src/IDF/templates/idf/review/create.html
Normal file
@ -0,0 +1,69 @@
|
||||
{extends "idf/review/base.html"}
|
||||
{block docclass}yui-t3{assign $inCreate = 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 code review.'}</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.summary.labelTag}:</strong></th>
|
||||
<td>{if $form.f.summary.errors}{$form.f.summary.fieldErrors}{/if}
|
||||
{$form.f.summary|unsafe}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><strong>{$form.f.description.labelTag}:</strong></th>
|
||||
<td>{if $form.f.description.errors}{$form.f.description.fieldErrors}{/if}
|
||||
{$form.f.description|unsafe}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><strong>{$form.f.commit.labelTag}:</strong></th>
|
||||
<td>{if $form.f.commit.errors}{$form.f.commit.fieldErrors}{/if}
|
||||
{$form.f.commit|unsafe}<br />
|
||||
<span class="helptext">{trans 'Be sure to provide the right commit/revision reference for your patch to correctly apply.'}</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><strong>{$form.f.patch.labelTag}:</strong></th>
|
||||
<td>{if $form.f.patch.errors}{$form.f.patch.fieldErrors}{/if}
|
||||
{$form.f.patch|unsafe}
|
||||
</td>
|
||||
</tr>{if $isOwner or $isMember}
|
||||
<tr>
|
||||
<th><strong>{$form.f.status.labelTag}:</strong></th>
|
||||
<td>{if $form.f.status.errors}{$form.f.status.fieldErrors}{/if}
|
||||
{$form.f.status|unsafe}
|
||||
</td>
|
||||
</tr>{/if}
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td><input type="submit" value="{trans 'Start Code Review'}" name="submit" /> | <a href="{url 'IDF_Views_Review::index', array($project.shortname)}">{trans 'Cancel'}</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
{/block}
|
||||
{block context}
|
||||
<div class="issue-submit-info">
|
||||
{blocktrans}<p>To start a code review, you need to provide:</p>
|
||||
<ul>
|
||||
<li>A commit or revision of the current code in the repository from which you started your work.</li>
|
||||
<li>A patch describing your changes with respect to the reference commit.</li>
|
||||
<li><strong>Check your patch to not provide any password or confidential information!</strong></li>
|
||||
</ul>{/blocktrans}
|
||||
</div>
|
||||
{/block}
|
||||
{block javascript}
|
||||
<script type="text/javascript">
|
||||
document.getElementById('id_summary').focus();
|
||||
</script>{/block}
|
||||
|
||||
|
21
src/IDF/templates/idf/review/index.html
Normal file
21
src/IDF/templates/idf/review/index.html
Normal file
@ -0,0 +1,21 @@
|
||||
{extends "idf/review/base.html"}
|
||||
{block docclass}yui-t2{assign $inOpenReviews=true}{/block}
|
||||
{block body}
|
||||
{$reviews.render}
|
||||
{if !$user.isAnonymous()}
|
||||
{aurl 'url', 'IDF_Views_Review::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 Code Review'}</a></p>{/if}
|
||||
|
||||
{/block}
|
||||
{block context}
|
||||
{*
|
||||
{aurl 'open_url', 'IDF_Views_Issue::index', array($project.shortname)}
|
||||
{aurl 'closed_url', 'IDF_Views_Issue::listStatus', array($project.shortname, 'closed')}
|
||||
{blocktrans}<p><strong>Open issues:</strong> <a href="{$open_url}">{$open}</a></p>
|
||||
<p><strong>Closed issues:</strong> <a href="{$closed_url}">{$closed}</a></p>{/blocktrans}
|
||||
{assign $class = ''}{assign $i = 0}
|
||||
<p class="smaller">{foreach $project.getTagCloud($cloud) as $label}
|
||||
{aurl 'url', 'IDF_Views_Issue::listLabel', array($project.shortname, $label.id, 'open')}
|
||||
{if $class != $label.class}{if $i != 0}<br />{/if}<strong class="label">{$label.class}:</strong> {/if}
|
||||
<a href="{$url}" class="label">{$label.name}</a>,{assign $class = $label.class}{assign $i = $i + 1}{/foreach}</p>
|
||||
*}{/block}
|
116
src/IDF/templates/idf/review/view.html
Normal file
116
src/IDF/templates/idf/review/view.html
Normal file
@ -0,0 +1,116 @@
|
||||
{extends "idf/review/base-full.html"}
|
||||
{block extraheader}<link rel="stylesheet" type="text/css" href="{media '/idf/css/prettify.css'}" />{/block}
|
||||
{block docclass}yui-t1{assign $inCreate = true}{/block}
|
||||
{block body}
|
||||
{if $form.errors}
|
||||
<div class="px-message-error">
|
||||
<p>{trans 'The form contains some errors. Please correct them to submit your review.'}</p>
|
||||
{if $form.get_top_errors}
|
||||
{$form.render_top_errors|unsafe}
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
<table class="commit" summary="">
|
||||
<tr>
|
||||
<th><strong>{trans 'Created:'}</strong></th><td>{$patch.creation_dtime|date:"%Y-%m-%d %H:%M:%S"} ({$patch.creation_dtime|dateago})</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><strong>{trans 'Updated:'}</strong></th><td>{$review.modif_dtime|dateago}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><strong>{trans 'Author:'}</strong></th><td>{$review.get_submitter()}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><strong>{trans 'Commit:'}</strong></th><td class="mono"><a href="{url 'IDF_Views_Source::treeBase', array($project.shortname, $patch.get_commit().scm_id)}" title="{trans 'View corresponding source tree'}">{$patch.get_commit().scm_id}</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><strong>{trans 'Description:'}</strong></th><td>{issuetext $review.summary, $request}<br /><br />{issuetext $patch.summary, $request}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><strong>{trans 'Reviewers:'}</strong></th><td>{if count($reviewers)}{foreach $reviewers as $r}{$r}, {/foreach}{else}{trans 'No reviewers at the moment.'}{/if}</td>
|
||||
</tr>{if count($diff.files)}
|
||||
<tr>
|
||||
<th><strong>{trans 'Files:'}</strong></th>
|
||||
<td>
|
||||
{foreach $diff.files as $filename=>$diffdef}
|
||||
{assign $ndiff = count($diffdef['chunks'])}
|
||||
{assign $nc = $files[$filename][2]->count()}
|
||||
<a href="{url 'IDF_Views_Source::tree', array($project.shortname, $patch.get_commit().scm_id, $filename)}">{$filename}</a> (<a href="#diff-{$filename|md5}">{blocktrans $ndiff}{$ndiff} diff{plural}{$ndiff} diffs{/blocktrans}</a>{if $nc}, <a href="#ct-{$filename|md5}">{blocktrans $nc}{$nc} comment{plural}{$nc} comments{/blocktrans}</a>{/if})<br />
|
||||
{/foreach}
|
||||
</td>
|
||||
</tr>{/if}
|
||||
<tr>{aurl 'url', 'IDF_Views_Review::getPatch', array($project.shortname, $patch.id)}
|
||||
<th> </th><td><a href="{$url}"><img style="vertical-align: text-bottom;" src="{media '/idf/img/package-grey.png'}" alt="{trans 'Archive'}" align="bottom" /></a> <a href="{$url}" class="soft">{trans 'Download the corresponding diff file'}</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
{if !$user.isAnonymous()}
|
||||
<div class="issue-submit-info" style="width: 50%">
|
||||
<p><strong>{trans 'How to Participate in a Code Review'}</strong></p>
|
||||
|
||||
<p>{blocktrans}Code review is a process in which
|
||||
after or before changes are commited into the code repository,
|
||||
different people discuss the code changes. The goal is
|
||||
to <strong>improve the quality of the code and the
|
||||
contributions</strong>, as such, you must be pragmatic when writing
|
||||
your review. Correctly mention the line numbers (in the old or in the
|
||||
new file) and try to keep a good balance between seriousness and fun.
|
||||
{/blocktrans}</p>
|
||||
<p>{blocktrans}
|
||||
<strong>Proposing code for review is intimidating</strong>, you know
|
||||
you will receive critics, so please, as a reviewer, <strong>keep this
|
||||
process fun</strong>, use it to help your contributor learn your
|
||||
coding standards and the structure of the code and <strong>make them want
|
||||
to propose more contributions</strong>.
|
||||
{/blocktrans}</p></div>
|
||||
{/if}
|
||||
|
||||
|
||||
<form method="post" action=".">
|
||||
{foreach $files as $file=>$def}
|
||||
<table class="diff" summary=" ">
|
||||
<tbody>
|
||||
<tr id="diff-{$file|md5}"><th colspan="4">{$file}</th></tr>
|
||||
<tr><th colspan="2">{trans 'Old'}</th><th colspan="2">{trans 'New'}</th></tr>
|
||||
{$def[0]}
|
||||
</tbody>
|
||||
</table>
|
||||
{assign $comments = $def[2]}
|
||||
{assign $nc = $comments.count()}
|
||||
{assign $i = 1}
|
||||
{foreach $comments as $c}
|
||||
<div class="issue-comment{if $i == 1} issue-comment-first{/if}{if $i == $nc} issue-comment-last{/if}" id="ic{$c.id}">{assign $who = $c.get_submitter()}{aurl 'whourl', 'IDF_Views_User::view', array($who.login)}
|
||||
{aurl 'url', 'IDF_Views_Review::view', array($project.shortname, $review.id)}
|
||||
{assign $id = $c.id}
|
||||
{assign $url = $url~'#ic'~$c.id}
|
||||
<p{if $i == 1} id="ct-{$file|md5}"{/if}>{blocktrans}Comment <a href="{$url}">{$i}</a> by <a href="{$whourl}">{$who}</a>, {$c.creation_dtime|date}{/blocktrans}</p>
|
||||
|
||||
<pre class="issue-comment-text">{issuetext $c.content, $request}</pre>
|
||||
</div> {assign $i = $i + 1}
|
||||
{/foreach}
|
||||
{if !$user.isAnonymous()}
|
||||
<table class="form" summary=" ">
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td>
|
||||
<p>{blocktrans}Your comments on the changes in file <em>{$file}</em>:{/blocktrans}<br />{$def[1]|safe}</p>
|
||||
</td>
|
||||
</tr></table>{/if}
|
||||
{/foreach}
|
||||
{if !$user.isAnonymous()}
|
||||
<table class="form" summary=" ">
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td><input type="submit" value="{trans 'Submit Code Review'}" name="submit" /> | <a href="{url 'IDF_Views_Review::index', array($project.shortname)}">{trans 'Cancel'}</a>
|
||||
</td>
|
||||
</tr></table>
|
||||
{/if}
|
||||
</form>
|
||||
{/block}
|
||||
u
|
||||
{block javascript}
|
||||
<script type="text/javascript" src="{media '/idf/js/prettify.js'}"></script>
|
||||
<script type="text/javascript">
|
||||
prettyPrint();
|
||||
</script>
|
||||
{/block}
|
@ -69,6 +69,14 @@ a.userw {
|
||||
color: #777;
|
||||
}
|
||||
|
||||
a.soft {
|
||||
color: #777;
|
||||
}
|
||||
|
||||
a.soft:visited {
|
||||
color: #777;
|
||||
}
|
||||
|
||||
div.context {
|
||||
padding-left: 1em;
|
||||
}
|
||||
@ -518,6 +526,7 @@ table.diff tr.diff-next td {
|
||||
padding: 1px 5px;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* view file content
|
||||
*/
|
||||
@ -633,3 +642,35 @@ div.deprecated-page {
|
||||
.delp a {
|
||||
color: #a00;
|
||||
}
|
||||
|
||||
#branding {
|
||||
float: right;
|
||||
position: relative;
|
||||
margin-right: -10px;
|
||||
width: 115px;
|
||||
font-size: 8px;
|
||||
text-align: right;
|
||||
padding-right: 20px;
|
||||
padding-left: 0px;
|
||||
background-color: #eeeeec;
|
||||
-moz-border-radius: 3px 0 0 3px;
|
||||
-webkit-border-radius: 3px 0 0 3px;
|
||||
color: #888a85;
|
||||
clear: both;
|
||||
background-image: url("../img/ceondo.png");
|
||||
background-repeat: no-repeat;
|
||||
background-position: top right;
|
||||
}
|
||||
|
||||
#branding a {
|
||||
color: #777;
|
||||
}
|
||||
|
||||
#branding a:visited {
|
||||
color: #777;
|
||||
}
|
||||
|
||||
#ft {
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
}
|
||||
|
BIN
www/media/idf/img/ceondo.png
Normal file
BIN
www/media/idf/img/ceondo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 662 B |
Loading…
Reference in New Issue
Block a user